diff --git a/lib/rexml/xpath_parser.rb b/lib/rexml/xpath_parser.rb index 761b5281..20bdd14d 100644 --- a/lib/rexml/xpath_parser.rb +++ b/lib/rexml/xpath_parser.rb @@ -1,6 +1,5 @@ # frozen_string_literal: false -require "pp" require "set" require_relative 'namespace' @@ -801,6 +800,11 @@ def evaluate_predicate(expression, nodesets) end def trace(*args) + # Loaded here rather than at the top of the file because this method is + # only reached when REXML_XPATH_PARSER_DEBUG is set. Requiring pp + # eagerly made every REXML user pay for a debugging aid. + require "pp" + indent = " " * @nest PP.pp(args, "").each_line do |line| puts("#{indent}#{line}") diff --git a/test/test_require.rb b/test/test_require.rb new file mode 100644 index 00000000..71e11461 --- /dev/null +++ b/test/test_require.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: false + +require "test/unit" + +module REXMLTests + # Guards the deferred `require "pp"` in REXML::XPathParser#trace. The checks + # run in a subprocess because this one has pp loaded already. + class TestRequire < Test::Unit::TestCase + PP_LOADED = '$LOADED_FEATURES.any? { |f| File.basename(f) == "pp.rb" }' + + def subprocess(script) + lib = File.join(File.dirname(File.expand_path(__dir__)), "lib") + IO.popen([RbConfig.ruby, "-I", lib, "-e", script], &:read) + end + + def test_requiring_document_does_not_load_pp + assert_equal("false", subprocess("require 'rexml/document'; print #{PP_LOADED}")) + end + + def test_xpath_works_without_pp + script = "require 'rexml/document'; " \ + "d = REXML::Document.new('xy'); " \ + "print REXML::XPath.match(d, '//a').map(&:text).join(',')" + assert_equal("x,y", subprocess(script)) + end + + def test_xpath_does_not_load_pp + script = "require 'rexml/document'; " \ + "d = REXML::Document.new('x'); " \ + "REXML::XPath.match(d, '//a'); " \ + "print #{PP_LOADED}" + assert_equal("false", subprocess(script)) + end + end +end