From 20017eea807e8fa386aa5c79ae779004d8b366dd Mon Sep 17 00:00:00 2001
From: Sutou Kouhei <kou@clear-code.com>
Date: Tue, 25 Jun 2024 11:26:33 +0900
Subject: [PATCH] Add 3.3.1 entry

Backport from https://github.com/ruby/rexml/tree/v3.3.1/test

---
 test/rexml/data/much_ado.xml                  |   2 +-
 test/rexml/data/ofbiz-issues-full-177.xml     |   4 +-
 test/rexml/data/t75.xml                       |   2 +-
 test/rexml/data/test/tests.xml                |   4 +-
 test/rexml/data/tutorial.xml                  |   2 +-
 test/rexml/formatter/test_default.rb          |  17 ++
 .../test_base.rb}                             |  45 ++++
 test/rexml/functions/test_boolean.rb          |  75 ++++++
 test/rexml/functions/test_local_name.rb       |  44 +++
 test/rexml/functions/test_number.rb           |  38 +++
 test/rexml/helper.rb                          |  35 +++
 test/rexml/parse/test_comment.rb              | 109 ++++++++
 .../parse/test_document_type_declaration.rb   |  60 +++++
 test/rexml/parse/test_element.rb              |  13 +
 test/rexml/parse/test_entity_declaration.rb   |  36 +++
 test/rexml/parse/test_notation_declaration.rb |   2 +-
 .../parse/test_processing_instruction.rb      |  44 +++
 test/rexml/parser/test_base_parser.rb         |  27 ++
 test/rexml/parser/test_ultra_light.rb         |   2 +-
 test/rexml/parser/test_xpath.rb               | 115 ++++++++
 test/rexml/rexml_test_utils.rb                |   7 -
 test/rexml/test_attribute.rb                  |  12 +
 test/rexml/test_attributes.rb                 |  13 +-
 test/rexml/test_attributes_mixin.rb           |   2 -
 test/rexml/test_comment.rb                    |   3 -
 test/rexml/test_contrib.rb                    |  10 +-
 test/rexml/test_core.rb                       | 114 ++++++--
 test/rexml/test_doctype.rb                    | 173 +++++++++++-
 test/rexml/test_document.rb                   |  58 +++-
 test/rexml/test_element.rb                    |   3 -
 test/rexml/test_elements.rb                   |   7 +-
 test/rexml/test_encoding.rb                   |  25 +-
 test/rexml/test_entity.rb                     |   2 -
 test/rexml/test_functions_number.rb           |  35 ---
 test/rexml/test_instruction.rb                |  12 +
 test/rexml/test_jaxen.rb                      | 192 ++++++-------
 test/rexml/test_light.rb                      |  10 +-
 test/rexml/test_lightparser.rb                |   4 +-
 test/rexml/test_listener.rb                   |   5 +-
 test/rexml/test_martin_fowler.rb              |   8 +-
 test/rexml/test_namespace.rb                  |   5 +-
 test/rexml/test_order.rb                      |   5 +-
 test/rexml/test_preceding_sibling.rb          |   2 -
 test/rexml/test_pullparser.rb                 |  21 +-
 test/rexml/test_rexml_issuezilla.rb           |   4 +-
 test/rexml/test_sax.rb                        |  11 +-
 test/rexml/test_stream.rb                     |   7 +-
 test/rexml/test_text.rb                       |  53 +++-
 test/rexml/test_ticket_80.rb                  |   3 -
 test/rexml/test_validation_rng.rb             | 142 +++++-----
 test/rexml/test_xml_declaration.rb            |  21 +-
 test/rexml/xpath/test_attribute.rb            |  12 +-
 .../xpath/test_axis_preceding_sibling.rb      |   2 -
 test/rexml/xpath/test_axis_self.rb            |  20 ++
 test/rexml/xpath/test_base.rb                 | 180 +++++++++----
 test/rexml/xpath/test_compare.rb              | 252 ++++++++++++++++++
 test/rexml/xpath/test_node.rb                 |   4 -
 test/rexml/xpath/test_predicate.rb            |  14 +-
 test/rexml/xpath/test_text.rb                 |   3 +-
 59 files changed, 1727 insertions(+), 405 deletions(-)
 create mode 100644 test/rexml/formatter/test_default.rb
 rename test/rexml/{test_functions.rb => functions/test_base.rb} (87%)
 create mode 100644 test/rexml/functions/test_boolean.rb
 create mode 100644 test/rexml/functions/test_local_name.rb
 create mode 100644 test/rexml/functions/test_number.rb
 create mode 100644 test/rexml/helper.rb
 create mode 100644 test/rexml/parse/test_comment.rb
 create mode 100644 test/rexml/parse/test_entity_declaration.rb
 create mode 100644 test/rexml/parse/test_processing_instruction.rb
 create mode 100644 test/rexml/parser/test_base_parser.rb
 create mode 100644 test/rexml/parser/test_xpath.rb
 delete mode 100644 test/rexml/rexml_test_utils.rb
 create mode 100644 test/rexml/test_attribute.rb
 delete mode 100644 test/rexml/test_functions_number.rb
 create mode 100644 test/rexml/test_instruction.rb
 create mode 100644 test/rexml/xpath/test_axis_self.rb
 create mode 100644 test/rexml/xpath/test_compare.rb

diff --git a/test/rexml/data/much_ado.xml b/test/rexml/data/much_ado.xml
index f008fad..0040088 100644
--- a/test/rexml/data/much_ado.xml
+++ b/test/rexml/data/much_ado.xml
@@ -4735,7 +4735,7 @@ CLAUDIO, BENEDICK, HERO, BEATRICE, and Attendants</STAGEDIR>
 <LINE>But they shall find, awaked in such a kind,</LINE>
 <LINE>Both strength of limb and policy of mind,</LINE>
 <LINE>Ability in means and choice of friends,</LINE>
-<LINE>To quit me of them throughly.</LINE>
+<LINE>To quit me of them thoroughly.</LINE>
 </SPEECH>
 
 <SPEECH>
diff --git a/test/rexml/data/ofbiz-issues-full-177.xml b/test/rexml/data/ofbiz-issues-full-177.xml
index bfff771..e1f7bdf 100644
--- a/test/rexml/data/ofbiz-issues-full-177.xml
+++ b/test/rexml/data/ofbiz-issues-full-177.xml
@@ -152,8 +152,8 @@
           <!-- desc         : Short description for attachment.              -->
           <!-- ispatch      : Whether attachment is a patch file.            -->
           <!-- filename     : Filename of attachment.                        -->
-          <!-- submitter_id : Issuezilla ID of attachement submitter.        --> 
-          <!-- submitting_username    : username of attachement submitter.   --> 
+          <!-- submitter_id : Issuezilla ID of attachment submitter.        --> 
+          <!-- submitting_username    : username of attachment submitter.   --> 
           <!-- data         : Encoded attachment.                            --> 
           <!-- attachment_iz_url : URL to attachment in iz.                  --> 
     
diff --git a/test/rexml/data/t75.xml b/test/rexml/data/t75.xml
index 0911fb1..eb3ccce 100644
--- a/test/rexml/data/t75.xml
+++ b/test/rexml/data/t75.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?><?pos="3"?>
+<?xml version="1.0" encoding="ISO-8859-1"?>
 <!-- generated by hnb 1.9.17 (http://hnb.sourceforge.net) -->
 
 <!DOCTYPE tree[
diff --git a/test/rexml/data/test/tests.xml b/test/rexml/data/test/tests.xml
index cf03b42..fd41567 100644
--- a/test/rexml/data/test/tests.xml
+++ b/test/rexml/data/test/tests.xml
@@ -299,7 +299,7 @@
       <valueOf select="name(/.)"></valueOf>
       <valueOf select="name(/self::node())"></valueOf>
 
-      <!-- name of root elemet -->
+      <!-- name of root element -->
       <valueOf select="name(node())">web-app</valueOf>
       <valueOf select="name(/node())">web-app</valueOf>
       <valueOf select="name(/*)">web-app</valueOf>
@@ -318,7 +318,7 @@
       <valueOf select="name(parent::node())"></valueOf>
       <valueOf select="name(parent::*)"></valueOf>
 
-      <!-- name of root elemet -->
+      <!-- name of root element -->
       <valueOf select="name()">web-app</valueOf>
       <valueOf select="name(.)">web-app</valueOf>
       <valueOf select="name(../*)">web-app</valueOf>
diff --git a/test/rexml/data/tutorial.xml b/test/rexml/data/tutorial.xml
index bf5783d..9c4639b 100644
--- a/test/rexml/data/tutorial.xml
+++ b/test/rexml/data/tutorial.xml
@@ -286,7 +286,7 @@ el1 &lt;&lt; Text.new(" cruel world")
         strings.</p>
 
         <p>I can't emphasize this enough, because people do have problems with
-        this. REXML can't possibly alway guess correctly how your text is
+        this. REXML can't possibly always guess correctly how your text is
         encoded, so it always assumes the text is UTF-8. It also does not warn
         you when you try to add text which isn't properly encoded, for the
         same reason. You must make sure that you are adding UTF-8 text.
diff --git a/test/rexml/formatter/test_default.rb b/test/rexml/formatter/test_default.rb
new file mode 100644
index 0000000..aa403db
--- /dev/null
+++ b/test/rexml/formatter/test_default.rb
@@ -0,0 +1,17 @@
+module REXMLTests
+  class DefaultFormatterTest < Test::Unit::TestCase
+    def format(node)
+      formatter = REXML::Formatters::Default.new
+      output = +""
+      formatter.write(node, output)
+      output
+    end
+
+    class InstructionTest < self
+      def test_content_nil
+        instruction = REXML::Instruction.new("target")
+        assert_equal("<?target?>", format(instruction))
+      end
+    end
+  end
+end
diff --git a/test/rexml/test_functions.rb b/test/rexml/functions/test_base.rb
similarity index 87%
rename from test/rexml/test_functions.rb
rename to test/rexml/functions/test_base.rb
index a77be38..daa3815 100644
--- a/test/rexml/test_functions.rb
+++ b/test/rexml/functions/test_base.rb
@@ -3,9 +3,16 @@ require "test/unit/testcase"
 
 require "rexml/document"
 
+# TODO: Split me
 module REXMLTests
   class FunctionsTester < Test::Unit::TestCase
     include REXML
+
+    def setup
+      super
+      REXML::Functions.context = nil
+    end
+
     def test_functions
       # trivial text() test
       # confuse-a-function
@@ -222,6 +229,44 @@ module REXMLTests
       assert_equal( [REXML::Comment.new("COMMENT A")], m )
     end
 
+    def test_normalize_space_strings
+      source = <<-XML
+<a><b>breakfast     boosts\t\t
+ 
+concentration   </b><c>
+Coffee beans
+ aroma
+
+
+
+</c><d>        Dessert
+ \t\t    after    dinner</d></a>
+      XML
+      normalized_texts = REXML::XPath.each(REXML::Document.new(source), "normalize-space(//text())").to_a
+      assert_equal([
+                     "breakfast boosts concentration",
+                     "Coffee beans aroma",
+                     "Dessert after dinner",
+                   ],
+                   normalized_texts)
+    end
+
+    def test_string_nil_without_context
+      doc = REXML::Document.new(<<~XML)
+      <?xml version="1.0" encoding="UTF-8"?>
+      <root>
+      <foo bar="baz"/>
+      <foo bar=""/>
+      </root>
+      XML
+
+      assert_equal([doc.root.elements[2]],
+                   REXML::XPath.match(doc,
+                                      "//foo[@bar=$n]",
+                                      nil,
+                                      {"n" => nil}))
+    end
+
     def test_unregistered_method
       doc = Document.new("<root/>")
       assert_nil(XPath::first(doc.root, "to_s()"))
diff --git a/test/rexml/functions/test_boolean.rb b/test/rexml/functions/test_boolean.rb
new file mode 100644
index 0000000..b3e2117
--- /dev/null
+++ b/test/rexml/functions/test_boolean.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: false
+
+require "test/unit"
+require "rexml/document"
+require "rexml/functions"
+
+module REXMLTests
+  class TestFunctionsBoolean < Test::Unit::TestCase
+    def setup
+      REXML::Functions.context = nil
+    end
+
+    def test_true
+      assert_equal(true, REXML::Functions.boolean(true))
+    end
+
+    def test_false
+      assert_equal(false, REXML::Functions.boolean(false))
+    end
+
+    def test_integer_true
+      assert_equal(true, REXML::Functions.boolean(1))
+    end
+
+    def test_integer_positive_zero
+      assert_equal(false, REXML::Functions.boolean(0))
+    end
+
+    def test_integer_negative_zero
+      assert_equal(false, REXML::Functions.boolean(-0))
+    end
+
+    def test_float_true
+      assert_equal(true, REXML::Functions.boolean(1.1))
+    end
+
+    def test_float_positive_zero
+      assert_equal(false, REXML::Functions.boolean(-0.0))
+    end
+
+    def test_float_negative_zero
+      assert_equal(false, REXML::Functions.boolean(-0.0))
+    end
+
+    def test_float_nan
+      assert_equal(false, REXML::Functions.boolean(Float::NAN))
+    end
+
+    def test_string_true
+      assert_equal(true, REXML::Functions.boolean("content"))
+    end
+
+    def test_string_empty
+      assert_equal(false, REXML::Functions.boolean(""))
+    end
+
+    def test_node_set_true
+      root = REXML::Document.new("<root/>").root
+      assert_equal(true, REXML::Functions.boolean([root]))
+    end
+
+    def test_node_set_empty
+      assert_equal(false, REXML::Functions.boolean([]))
+    end
+
+    def test_nil
+      assert_equal(false, REXML::Functions.boolean(nil))
+    end
+
+    def test_context
+      REXML::Functions.context = {node: true}
+      assert_equal(true, REXML::Functions.boolean())
+    end
+  end
+end
diff --git a/test/rexml/functions/test_local_name.rb b/test/rexml/functions/test_local_name.rb
new file mode 100644
index 0000000..97c9e74
--- /dev/null
+++ b/test/rexml/functions/test_local_name.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: false
+
+require "test/unit"
+require "rexml/document"
+require "rexml/functions"
+
+module REXMLTests
+  class TestFunctionsLocalName < Test::Unit::TestCase
+    def setup
+      REXML::Functions.context = nil
+    end
+
+    def test_one
+      document = REXML::Document.new(<<-XML)
+<root xmlns:x="http://example.com/x/">
+  <x:child/>
+</root>
+      XML
+      node_set = document.root.children
+      assert_equal("child", REXML::Functions.local_name(node_set))
+    end
+
+    def test_multiple
+      document = REXML::Document.new(<<-XML)
+<root xmlns:x="http://example.com/x/">
+  <x:child1/>
+  <x:child2/>
+</root>
+      XML
+      node_set = document.root.children
+      assert_equal("child1", REXML::Functions.local_name(node_set))
+    end
+
+    def test_nonexistent
+      assert_equal("", REXML::Functions.local_name([]))
+    end
+
+    def test_context
+      document = REXML::Document.new("<root/>")
+      REXML::Functions.context = {node: document.root}
+      assert_equal("root", REXML::Functions.local_name())
+    end
+  end
+end
diff --git a/test/rexml/functions/test_number.rb b/test/rexml/functions/test_number.rb
new file mode 100644
index 0000000..16e6357
--- /dev/null
+++ b/test/rexml/functions/test_number.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: false
+
+require "test/unit"
+require "rexml/document"
+require "rexml/functions"
+
+module REXMLTests
+  class TestFunctionsNumber < Test::Unit::TestCase
+    def setup
+      REXML::Functions.context = nil
+    end
+
+    def test_true
+      assert_equal(1, REXML::Functions.number(true))
+    end
+
+    def test_false
+      assert_equal(0, REXML::Functions.number(false))
+    end
+
+    def test_numeric
+      assert_equal(29, REXML::Functions.number(29))
+    end
+
+    def test_string_integer
+      assert_equal(100, REXML::Functions.number("100"))
+    end
+
+    def test_string_float
+      assert_equal(-9.13, REXML::Functions.number("-9.13"))
+    end
+
+    def test_node_set
+      root = REXML::Document.new("<root>100</root>").root
+      assert_equal(100, REXML::Functions.number([root]))
+    end
+  end
+end
diff --git a/test/rexml/helper.rb b/test/rexml/helper.rb
new file mode 100644
index 0000000..3de1327
--- /dev/null
+++ b/test/rexml/helper.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: false
+
+require "test-unit"
+
+require "rexml/document"
+
+module Helper
+  module Fixture
+    def fixture_path(*components)
+      File.join(__dir__, "data", *components)
+    end
+  end
+
+  module Global
+    def suppress_warning
+      verbose = $VERBOSE
+      begin
+        $VERBOSE = nil
+        yield
+      ensure
+        $VERBOSE = verbose
+      end
+    end
+
+    def with_default_internal(encoding)
+      default_internal = Encoding.default_internal
+      begin
+        suppress_warning {Encoding.default_internal = encoding}
+        yield
+      ensure
+        suppress_warning {Encoding.default_internal = default_internal}
+      end
+    end
+  end
+end
diff --git a/test/rexml/parse/test_comment.rb b/test/rexml/parse/test_comment.rb
new file mode 100644
index 0000000..ce6678e
--- /dev/null
+++ b/test/rexml/parse/test_comment.rb
@@ -0,0 +1,109 @@
+require "test/unit"
+require "rexml/document"
+
+module REXMLTests
+  class TestParseComment < Test::Unit::TestCase
+    def parse(xml)
+      REXML::Document.new(xml)
+    end
+
+    class TestInvalid < self
+      def test_toplevel_unclosed_comment
+        exception = assert_raise(REXML::ParseException) do
+          parse("<!--")
+        end
+        assert_equal(<<~DETAIL, exception.to_s)
+          Unclosed comment
+          Line: 1
+          Position: 4
+          Last 80 unconsumed characters:
+        DETAIL
+      end
+
+      def test_toplevel_malformed_comment_inner
+        exception = assert_raise(REXML::ParseException) do
+          parse("<!-- -- -->")
+        end
+        assert_equal(<<~DETAIL, exception.to_s)
+          Malformed comment
+          Line: 1
+          Position: 11
+          Last 80 unconsumed characters:
+        DETAIL
+      end
+
+      def test_toplevel_malformed_comment_end
+        exception = assert_raise(REXML::ParseException) do
+          parse("<!-- --->")
+        end
+        assert_equal(<<~DETAIL, exception.to_s)
+          Malformed comment
+          Line: 1
+          Position: 9
+          Last 80 unconsumed characters:
+        DETAIL
+      end
+
+      def test_doctype_malformed_comment_inner
+        exception = assert_raise(REXML::ParseException) do
+          parse("<!DOCTYPE foo [<!-- -- -->")
+        end
+        assert_equal(<<~DETAIL, exception.to_s)
+          Malformed comment
+          Line: 1
+          Position: 26
+          Last 80 unconsumed characters:
+        DETAIL
+      end
+
+      def test_doctype_malformed_comment_end
+        exception = assert_raise(REXML::ParseException) do
+          parse("<!DOCTYPE foo [<!-- --->")
+        end
+        assert_equal(<<~DETAIL, exception.to_s)
+          Malformed comment
+          Line: 1
+          Position: 24
+          Last 80 unconsumed characters:
+        DETAIL
+      end
+
+      def test_after_doctype_malformed_comment_short
+        exception = assert_raise(REXML::ParseException) do
+          parse("<a><!-->")
+        end
+        assert_equal(<<~DETAIL.chomp, exception.to_s)
+          Malformed comment
+          Line: 1
+          Position: 8
+          Last 80 unconsumed characters:
+          -->
+        DETAIL
+      end
+
+      def test_after_doctype_malformed_comment_inner
+        exception = assert_raise(REXML::ParseException) do
+          parse("<a><!-- -- -->")
+        end
+        assert_equal(<<~DETAIL, exception.to_s)
+          Malformed comment
+          Line: 1
+          Position: 14
+          Last 80 unconsumed characters:
+        DETAIL
+      end
+
+      def test_after_doctype_malformed_comment_end
+        exception = assert_raise(REXML::ParseException) do
+          parse("<a><!-- --->")
+        end
+        assert_equal(<<~DETAIL, exception.to_s)
+          Malformed comment
+          Line: 1
+          Position: 12
+          Last 80 unconsumed characters:
+        DETAIL
+      end
+    end
+  end
+end
diff --git a/test/rexml/parse/test_document_type_declaration.rb b/test/rexml/parse/test_document_type_declaration.rb
index 5571390..3ca0b53 100644
--- a/test/rexml/parse/test_document_type_declaration.rb
+++ b/test/rexml/parse/test_document_type_declaration.rb
@@ -36,6 +36,66 @@ Last 80 unconsumed characters:
 + r SYSTEM "urn:x-rexml:test" [ ]>  <r/> 
         DETAIL
       end
+
+      def test_no_name
+        exception = assert_raise(REXML::ParseException) do
+          parse(<<-DOCTYPE)
+<!DOCTYPE>
+          DOCTYPE
+        end
+        assert_equal(<<-DETAIL.chomp, exception.to_s)
+Malformed DOCTYPE: name is missing
+Line: 3
+Position: 17
+Last 80 unconsumed characters:
+<!DOCTYPE>  <r/> 
+        DETAIL
+      end
+    end
+
+    class TestUnclosed < self
+      def test_no_extra_node
+        exception = assert_raise(REXML::ParseException) do
+          REXML::Document.new("<!DOCTYPE foo [")
+        end
+        assert_equal(<<~DETAIL.chomp, exception.to_s)
+          Malformed DOCTYPE: unclosed
+          Line: 1
+          Position: 15
+          Last 80 unconsumed characters:
+
+        DETAIL
+      end
+
+      def test_start_element
+        exception = assert_raise(REXML::ParseException) do
+          REXML::Document.new(<<~DOCTYPE)
+            <!DOCTYPE foo [ <r>
+          DOCTYPE
+        end
+        assert_equal(<<~DETAIL.chomp, exception.to_s)
+          Malformed DOCTYPE: invalid declaration
+          Line: 1
+          Position: 20
+          Last 80 unconsumed characters:
+          <r>#{' '}
+        DETAIL
+      end
+
+      def test_text
+        exception = assert_raise(REXML::ParseException) do
+          REXML::Document.new(<<~DOCTYPE)
+            <!DOCTYPE foo [ text
+          DOCTYPE
+        end
+        assert_equal(<<~DETAIL.chomp, exception.to_s)
+          Malformed DOCTYPE: invalid declaration
+          Line: 1
+          Position: 21
+          Last 80 unconsumed characters:
+          text#{' '}
+        DETAIL
+      end
     end
 
     class TestExternalID < self
diff --git a/test/rexml/parse/test_element.rb b/test/rexml/parse/test_element.rb
index e8dce4b..14d0703 100644
--- a/test/rexml/parse/test_element.rb
+++ b/test/rexml/parse/test_element.rb
@@ -43,7 +43,20 @@ Invalid attribute name: <:a="">
 Line: 1
 Position: 13
 Last 80 unconsumed characters:
+:a=""></x>
+        DETAIL
+      end
 
+      def test_empty_namespace_attribute_name_with_utf8_character
+        exception = assert_raise(REXML::ParseException) do
+          parse("<x :\xE2\x80\x8B>") # U+200B ZERO WIDTH SPACE
+        end
+        assert_equal(<<-DETAIL.chomp.force_encoding("ASCII-8BIT"), exception.to_s)
+Invalid attribute name: <:\xE2\x80\x8B>
+Line: 1
+Position: 8
+Last 80 unconsumed characters:
+:\xE2\x80\x8B>
         DETAIL
       end
 
diff --git a/test/rexml/parse/test_entity_declaration.rb b/test/rexml/parse/test_entity_declaration.rb
new file mode 100644
index 0000000..e15deec
--- /dev/null
+++ b/test/rexml/parse/test_entity_declaration.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'rexml/document'
+
+module REXMLTests
+  class TestParseEntityDeclaration < Test::Unit::TestCase
+    private
+    def xml(internal_subset)
+      <<-XML
+<!DOCTYPE r SYSTEM "urn:x-henrikmartensson:test" [
+#{internal_subset}
+]>
+<r/>
+      XML
+    end
+
+    def parse(internal_subset)
+      REXML::Document.new(xml(internal_subset)).doctype
+    end
+
+    def test_empty
+      exception = assert_raise(REXML::ParseException) do
+        parse(<<-INTERNAL_SUBSET)
+<!ENTITY>
+        INTERNAL_SUBSET
+      end
+      assert_equal(<<-DETAIL.chomp, exception.to_s)
+Malformed notation declaration: name is missing
+Line: 5
+Position: 72
+Last 80 unconsumed characters:
+ <!ENTITY>  ]> <r/>
+      DETAIL
+    end
+  end
+end
diff --git a/test/rexml/parse/test_notation_declaration.rb b/test/rexml/parse/test_notation_declaration.rb
index 19a0536..9e81b6a 100644
--- a/test/rexml/parse/test_notation_declaration.rb
+++ b/test/rexml/parse/test_notation_declaration.rb
@@ -35,7 +35,7 @@ Malformed notation declaration: name is missing
 Line: 5
 Position: 72
 Last 80 unconsumed characters:
- <!NOTATION>  ]> <r/> 
+<!NOTATION>  ]> <r/> 
         DETAIL
       end
 
diff --git a/test/rexml/parse/test_processing_instruction.rb b/test/rexml/parse/test_processing_instruction.rb
new file mode 100644
index 0000000..f0c0c24
--- /dev/null
+++ b/test/rexml/parse/test_processing_instruction.rb
@@ -0,0 +1,44 @@
+require "test/unit"
+require "rexml/document"
+
+module REXMLTests
+  class TestParseProcessinInstruction < Test::Unit::TestCase
+    def parse(xml)
+      REXML::Document.new(xml)
+    end
+
+    class TestInvalid < self
+      def test_no_name
+        exception = assert_raise(REXML::ParseException) do
+          parse("<??>")
+        end
+        assert_equal(<<-DETAIL.chomp, exception.to_s)
+Invalid processing instruction node
+Line: 1
+Position: 4
+Last 80 unconsumed characters:
+<??>
+        DETAIL
+      end
+
+      def test_garbage_text
+        # TODO: This should be parse error.
+        # Create test/parse/test_document.rb or something and move this to it.
+        doc = parse(<<-XML)
+x<?x y
+<!--?><?x -->?>
+<r/>
+        XML
+        pi = doc.children[1]
+        assert_equal([
+                       "x",
+                       "y\n<!--",
+                     ],
+                     [
+                       pi.target,
+                       pi.content,
+                     ])
+      end
+    end
+  end
+end
diff --git a/test/rexml/parser/test_base_parser.rb b/test/rexml/parser/test_base_parser.rb
new file mode 100644
index 0000000..17d0197
--- /dev/null
+++ b/test/rexml/parser/test_base_parser.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: false
+
+require 'rexml/parsers/baseparser'
+
+module REXMLTests
+  class BaseParserTester < Test::Unit::TestCase
+    def test_large_xml
+      large_text = "a" * 100_000
+      xml = <<-XML
+        <?xml version="1.0"?>
+        <root>
+          <child>#{large_text}</child>
+          <child>#{large_text}</child>
+        </root>
+      XML
+
+      parser = REXML::Parsers::BaseParser.new(xml)
+      while parser.has_next?
+        parser.pull
+      end
+
+      assert do
+        parser.position < xml.bytesize
+      end
+    end
+  end
+end
diff --git a/test/rexml/parser/test_ultra_light.rb b/test/rexml/parser/test_ultra_light.rb
index cb6ee5a..44fd1d1 100644
--- a/test/rexml/parser/test_ultra_light.rb
+++ b/test/rexml/parser/test_ultra_light.rb
@@ -54,7 +54,7 @@ class TestUltraLightParser < Test::Unit::TestCase
         normalized_doctype[1] = normalized_parent
         normalized_doctype
       when :start_element
-        tag, parent, name, attributes, *children = child
+        tag, _parent, name, attributes, *children = child
         normalized_parent = :parent
         normalized_children = children.collect do |sub_child|
           normalize_child(sub_child)
diff --git a/test/rexml/parser/test_xpath.rb b/test/rexml/parser/test_xpath.rb
new file mode 100644
index 0000000..9143d25
--- /dev/null
+++ b/test/rexml/parser/test_xpath.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: false
+
+require "test/unit"
+require "rexml/parsers/xpathparser"
+
+module REXMLTests
+  class TestXPathParser < Test::Unit::TestCase
+    sub_test_case("#abbreviate") do
+      def abbreviate(xpath)
+        parser = REXML::Parsers::XPathParser.new
+        parser.abbreviate(xpath)
+      end
+
+      def test_document
+        assert_equal("/",
+                     abbreviate("/"))
+      end
+
+      def test_descendant_or_self_only
+        assert_equal("//",
+                     abbreviate("/descendant-or-self::node()/"))
+      end
+
+      def test_descendant_or_self_absolute
+        assert_equal("//a/b",
+                     abbreviate("/descendant-or-self::node()/a/b"))
+      end
+
+      def test_descendant_or_self_relative
+        assert_equal("a//b",
+                     abbreviate("a/descendant-or-self::node()/b"))
+      end
+
+      def test_descendant_or_self_not_node
+        assert_equal("/descendant-or-self::text()",
+                     abbreviate("/descendant-or-self::text()"))
+      end
+
+      def test_self_absolute
+        assert_equal("/a/./b",
+                     abbreviate("/a/self::node()/b"))
+      end
+
+      def test_self_relative
+        assert_equal("a/./b",
+                     abbreviate("a/self::node()/b"))
+      end
+
+      def test_self_not_node
+        assert_equal("/self::text()",
+                     abbreviate("/self::text()"))
+      end
+
+      def test_parent_absolute
+        assert_equal("/a/../b",
+                     abbreviate("/a/parent::node()/b"))
+      end
+
+      def test_parent_relative
+        assert_equal("a/../b",
+                     abbreviate("a/parent::node()/b"))
+      end
+
+      def test_parent_not_node
+        assert_equal("/a/parent::text()",
+                     abbreviate("/a/parent::text()"))
+      end
+
+      def test_any_absolute
+        assert_equal("/*/a",
+                     abbreviate("/*/a"))
+      end
+
+      def test_any_relative
+        assert_equal("a/*/b",
+                     abbreviate("a/*/b"))
+      end
+
+      def test_following_sibling_absolute
+        assert_equal("/following-sibling::a/b",
+                     abbreviate("/following-sibling::a/b"))
+      end
+
+      def test_following_sibling_relative
+        assert_equal("a/following-sibling::b/c",
+                     abbreviate("a/following-sibling::b/c"))
+      end
+
+      def test_predicate_index
+        assert_equal("a[5]/b",
+                     abbreviate("a[5]/b"))
+      end
+
+      def test_attribute_relative
+        assert_equal("a/@b",
+                     abbreviate("a/attribute::b"))
+      end
+
+      def test_filter_attribute
+        assert_equal("a/b[@i = 1]/c",
+                     abbreviate("a/b[attribute::i=1]/c"))
+      end
+
+      def test_filter_string_single_quote
+        assert_equal("a/b[@name = \"single ' quote\"]/c",
+                     abbreviate("a/b[attribute::name=\"single ' quote\"]/c"))
+      end
+
+      def test_filter_string_double_quote
+        assert_equal("a/b[@name = 'double \" quote']/c",
+                     abbreviate("a/b[attribute::name='double \" quote']/c"))
+      end
+    end
+  end
+end
diff --git a/test/rexml/rexml_test_utils.rb b/test/rexml/rexml_test_utils.rb
deleted file mode 100644
index 7c59629..0000000
--- a/test/rexml/rexml_test_utils.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: false
-require 'test/unit'
-module REXMLTestUtils
-  def fixture_path(*components)
-    File.join(File.dirname(__FILE__), "data", *components)
-  end
-end
diff --git a/test/rexml/test_attribute.rb b/test/rexml/test_attribute.rb
new file mode 100644
index 0000000..b66e462
--- /dev/null
+++ b/test/rexml/test_attribute.rb
@@ -0,0 +1,12 @@
+module REXMLTests
+  class AttributeTest < Test::Unit::TestCase
+    def test_empty_prefix
+      error = assert_raise(ArgumentError) do
+        REXML::Attribute.new(":x")
+      end
+      assert_equal("name must be " +
+                   "\#{PREFIX}:\#{LOCAL_NAME} or \#{LOCAL_NAME}: <\":x\">",
+                   error.message)
+    end
+  end
+end
diff --git a/test/rexml/test_attributes.rb b/test/rexml/test_attributes.rb
index d6f566b..09fde44 100644
--- a/test/rexml/test_attributes.rb
+++ b/test/rexml/test_attributes.rb
@@ -1,6 +1,4 @@
 # frozen_string_literal: false
-require 'test/unit/testcase'
-require 'rexml/document'
 
 module REXMLTests
   class AttributesTester < Test::Unit::TestCase
@@ -180,18 +178,27 @@ module REXMLTests
       attr_test('name','value with LF &#x000a; &amp; ampersand')
     end
 
-    def test_quoting
+    def test_quote_root
       d = Document.new(%q{<a x='1' y="2"/>})
       assert_equal( %q{<a x='1' y='2'/>}, d.to_s )
       d.root.context[:attribute_quote] = :quote
       assert_equal( %q{<a x="1" y="2"/>}, d.to_s )
+    end
 
+    def test_quote_sub_element
       d = Document.new(%q{<a x='1' y="2"><b z='3'/></a>})
       assert_equal( %q{<a x='1' y='2'><b z='3'/></a>}, d.to_s )
       d.root.context[:attribute_quote] = :quote
       assert_equal( %q{<a x="1" y="2"><b z="3"/></a>}, d.to_s )
     end
 
+    def test_quote_to_s_value
+      doc = Document.new(%q{<root a="'"/>}, {attribute_quote: :quote})
+      assert_equal(%q{<root a="'"/>}, doc.to_s)
+      assert_equal("'", doc.root.attribute("a").value)
+      assert_equal(%q{<root a="'"/>}, doc.to_s)
+    end
+
     def test_ticket_127
       doc = Document.new
       doc.add_element 'a', { 'v' => 'x & y' }
diff --git a/test/rexml/test_attributes_mixin.rb b/test/rexml/test_attributes_mixin.rb
index 3a9f54e..2b9108c 100644
--- a/test/rexml/test_attributes_mixin.rb
+++ b/test/rexml/test_attributes_mixin.rb
@@ -1,6 +1,4 @@
 # frozen_string_literal: false
-require 'test/unit'
-require 'rexml/document'
 
 module REXMLTests
   class TestAttributes < Test::Unit::TestCase
diff --git a/test/rexml/test_comment.rb b/test/rexml/test_comment.rb
index 0af2f5c..f6f4d80 100644
--- a/test/rexml/test_comment.rb
+++ b/test/rexml/test_comment.rb
@@ -1,7 +1,4 @@
 # frozen_string_literal: false
-require "test/unit/testcase"
-
-require 'rexml/document'
 
 module REXMLTests
   class CommentTester < Test::Unit::TestCase
diff --git a/test/rexml/test_contrib.rb b/test/rexml/test_contrib.rb
index 8462b3c..23ee35b 100644
--- a/test/rexml/test_contrib.rb
+++ b/test/rexml/test_contrib.rb
@@ -1,15 +1,12 @@
 # coding: binary
 # frozen_string_literal: false
 
-require_relative "rexml_test_utils"
-
-require "rexml/document"
 require "rexml/parseexception"
 require "rexml/formatters/default"
 
 module REXMLTests
   class ContribTester < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     include REXML
 
     XML_STRING_01 = <<DELIMITER
@@ -73,6 +70,7 @@ DELIMITER
          >
          ]
        >
+       <svg/>
       EOF
       doc = REXML::Document.new source
       doc.write(out="")
@@ -82,7 +80,7 @@ DELIMITER
 
     # Peter Verhage
     def test_namespace_Peter
-      source = <<-EOF
+      source = <<~EOF
       <?xml version="1.0"?>
       <config:myprog-config xmlns:config="http://someurl/program/version">
       <!-- main options -->
@@ -379,7 +377,7 @@ EOF
     end
 
     def test_entities_Holden_Glova
-      document = <<-EOL
+      document = <<~EOL
       <?xml version="1.0" encoding="UTF-8"?>
       <!DOCTYPE rubynet [
       <!ENTITY rbconfig.MAJOR "1">
diff --git a/test/rexml/test_core.rb b/test/rexml/test_core.rb
index ee5438d..44e2e7e 100644
--- a/test/rexml/test_core.rb
+++ b/test/rexml/test_core.rb
@@ -1,8 +1,6 @@
-# coding: utf-8
+# -*- coding: utf-8 -*-
 # frozen_string_literal: false
 
-require_relative "rexml_test_utils"
-
 require "rexml/document"
 require "rexml/parseexception"
 require "rexml/output"
@@ -14,10 +12,10 @@ require_relative "listener"
 
 module REXMLTests
   class Tester < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     include REXML
     def setup
-      @xsa_source = <<-EOL
+      @xsa_source = <<~EOL
         <?xml version="1.0"?>
         <?xsl stylesheet="blah.xsl"?>
         <!-- The first line tests the XMLDecl, the second tests PI.
@@ -91,7 +89,7 @@ module REXMLTests
 
       # Bryan Murphy <murphybryanp@yahoo.com>
       text = "this is a {target[@name='test']/@value} test"
-      source = <<-EOL
+      source = <<~EOL
       <?xml version="1.0"?>
       <doc search="#{text}"/>
       EOL
@@ -116,6 +114,55 @@ module REXMLTests
         name4='test4'/>).join(' '), e.to_s
     end
 
+    def test_attribute_namespace_conflict
+      # https://www.w3.org/TR/xml-names/#uniqAttrs
+      message = <<-MESSAGE.chomp
+Duplicate attribute "a"
+Line: 4
+Position: 140
+Last 80 unconsumed characters:
+/>
+      MESSAGE
+      assert_raise(REXML::ParseException.new(message)) do
+        Document.new(<<-XML)
+<!-- http://www.w3.org is bound to n1 and n2 -->
+<x xmlns:n1="http://www.w3.org"
+   xmlns:n2="http://www.w3.org" >
+  <bad a="1"     a="2" />
+  <bad n1:a="1"  n2:a="2" />
+</x>
+        XML
+      end
+    end
+
+    def test_attribute_default_namespace
+      # https://www.w3.org/TR/xml-names/#uniqAttrs
+      document = Document.new(<<-XML)
+<!-- http://www.w3.org is bound to n1 and is the default -->
+<x xmlns:n1="http://www.w3.org"
+   xmlns="http://www.w3.org" >
+  <good a="1"     b="2" />
+  <good a="1"     n1:a="2" />
+</x>
+      XML
+      attributes = document.root.elements.collect do |element|
+        element.attributes.each_attribute.collect do |attribute|
+          [attribute.prefix, attribute.namespace, attribute.name]
+        end
+      end
+      assert_equal([
+                     [
+                       ["", "", "a"],
+                       ["", "", "b"],
+                     ],
+                     [
+                       ["", "", "a"],
+                       ["n1", "http://www.w3.org", "a"],
+                     ],
+                   ],
+                   attributes)
+    end
+
     def test_cdata
       test = "The quick brown fox jumped
         & < & < \" '
@@ -681,7 +728,7 @@ module REXMLTests
       koln_iso_8859_1 = "K\xF6ln"
       koln_utf8 = "K\xc3\xb6ln"
       source = Source.new( koln_iso_8859_1, 'iso-8859-1' )
-      results = source.scan(/.*/)[0]
+      results = source.match(/.*/)[0]
       koln_utf8.force_encoding('UTF-8') if koln_utf8.respond_to?(:force_encoding)
       assert_equal koln_utf8, results
       output << results
@@ -823,7 +870,7 @@ EOL
       assert_equal 'two', doc.root.elements[1].namespace
       assert_equal 'foo', doc.root.namespace
 
-      doc = Document.new <<-EOL
+      doc = Document.new <<~EOL
       <?xml version="1.0"?>
       <!DOCTYPE schema SYSTEM "XMLSchema.dtd" [
       <!ENTITY % p ''>
@@ -877,18 +924,18 @@ EOL
       EOL
 
       # The most common case.  People not caring about the namespaces much.
-      assert_equal( "XY", XPath.match( doc, "/test/a/text()" ).join )
-      assert_equal( "XY", XPath.match( doc, "/test/x:a/text()" ).join )
+      assert_equal( "XY", XPath.match( doc, "/*:test/*:a/text()" ).join )
+      assert_equal( "XY", XPath.match( doc, "/*:test/x:a/text()" ).join )
       # Surprising?  I don't think so, if you believe my definition of the "common case"
-      assert_equal( "XYZ", XPath.match( doc, "//a/text()" ).join )
+      assert_equal( "XYZ", XPath.match( doc, "//*:a/text()" ).join )
 
       # These are the uncommon cases.  Namespaces are actually important, so we define our own
       # mappings, and pass them in.
       assert_equal( "XY", XPath.match( doc, "/f:test/f:a/text()", { "f" => "1" } ).join )
       # The namespaces are defined, and override the original mappings
-      assert_equal( "", XPath.match( doc, "/test/a/text()", { "f" => "1" } ).join )
+      assert_equal( "XY", XPath.match( doc, "/*:test/*:a/text()", { "f" => "1" } ).join )
       assert_equal( "", XPath.match( doc, "/x:test/x:a/text()", { "f" => "1" } ).join )
-      assert_equal( "", XPath.match( doc, "//a/text()", { "f" => "1" } ).join )
+      assert_equal( "XYZ", XPath.match( doc, "//*:a/text()", { "f" => "1" } ).join )
     end
 
     def test_processing_instruction
@@ -900,7 +947,7 @@ EOL
     end
 
     def test_oses_with_bad_EOLs
-      Document.new("\n\n\n<?xml version='1.0'?>\n\n\n<a/>\n\n")
+      Document.new("<?xml version='1.0'?>\n\n\n<a/>\n\n")
     end
 
     # Contributed (with patch to fix bug) by Kouhei
@@ -927,7 +974,7 @@ EOL
     end
 
     def test_hyphens_in_doctype
-      doc = REXML::Document.new <<-EOQ
+      doc = REXML::Document.new <<~EOQ
        <?xml version="1.0"?>
        <!DOCTYPE a-b-c>
        <a-b-c>
@@ -1043,7 +1090,7 @@ EOL
     def test_text_raw
       # From the REXML tutorial
       # (http://www.germane-software.com/software/rexml/test/data/tutorial.html)
-      doc = Document.new <<-EOL
+      doc = Document.new <<~EOL
       <?xml version="1.0"?>
       <!DOCTYPE schema SYSTEM "XMLSchema.dtd" [
       <!ENTITY % s 'Sean'>
@@ -1277,11 +1324,26 @@ EOL
       exception = assert_raise(ParseException) do
         Document.new(src)
       end
-      assert_equal(<<-DETAIL, exception.to_s)
+      assert_equal(<<-DETAIL.chomp, exception.to_s)
 Missing attribute value start quote: <bar>
 Line: 1
 Position: 16
 Last 80 unconsumed characters:
+value/>
+      DETAIL
+    end
+
+    def test_parse_exception_on_missing_attribute_end_quote
+      src = '<foo bar="value/>'
+      exception = assert_raise(ParseException) do
+        Document.new(src)
+      end
+      assert_equal(<<-DETAIL.chomp, exception.to_s)
+Missing attribute value end quote: <bar>: <">
+Line: 1
+Position: 17
+Last 80 unconsumed characters:
+value/>
       DETAIL
     end
 
@@ -1377,7 +1439,7 @@ ENDXML
       d.root.add_element( "bah" )
       p=REXML::Formatters::Pretty.new(2)
       p.compact = true    # Don't add whitespace to text nodes unless necessary
-      p.write(d,out="")
+      p.write(d,out=+"")
       assert_equal( expected, out )
     end
 
@@ -1391,8 +1453,8 @@ ENDXML
 
     def test_ticket_102
       doc = REXML::Document.new '<doc xmlns="ns"><item name="foo"/></doc>'
-      assert_equal( "foo", doc.root.elements["item"].attribute("name","ns").to_s )
-      assert_equal( "item", doc.root.elements["item[@name='foo']"].name )
+      assert_equal( "foo", doc.root.elements["*:item"].attribute("name","ns").to_s )
+      assert_equal( "item", doc.root.elements["*:item[@name='foo']"].name )
     end
 
     def test_ticket_14
@@ -1421,11 +1483,11 @@ ENDXML
       doc = REXML::Document.new(
         '<doc xmlns="ns" xmlns:phantom="ns"><item name="foo">text</item></doc>'
       )
-      assert_equal 'text', doc.text( "/doc/item[@name='foo']" )
+      assert_equal 'text', doc.text( "/*:doc/*:item[@name='foo']" )
       assert_equal "name='foo'",
-        doc.root.elements["item"].attribute("name", "ns").inspect
+        doc.root.elements["*:item"].attribute("name", "ns").inspect
       assert_equal "<item name='foo'>text</item>",
-        doc.root.elements["item[@name='foo']"].to_s
+        doc.root.elements["*:item[@name='foo']"].to_s
     end
 
     def test_ticket_135
@@ -1453,8 +1515,10 @@ ENDXML
           "" => attribute("version", "1.0"),
         },
       }
-      assert_equal(expected, doc.root.attributes)
-      assert_equal(expected, REXML::Document.new(doc.root.to_s).root.attributes)
+      assert_equal(expected,
+                   doc.root.attributes.to_h)
+      assert_equal(expected,
+                   REXML::Document.new(doc.root.to_s).root.attributes.to_h)
     end
 
     def test_empty_doc
diff --git a/test/rexml/test_doctype.rb b/test/rexml/test_doctype.rb
index d728cba..b20d30a 100644
--- a/test/rexml/test_doctype.rb
+++ b/test/rexml/test_doctype.rb
@@ -1,6 +1,4 @@
 # frozen_string_literal: false
-require 'test/unit'
-require 'rexml/document'
 
 module REXMLTests
   class TestDocTypeAccessor < Test::Unit::TestCase
@@ -41,6 +39,12 @@ module REXMLTests
                    @doc_type_public_system.to_s)
     end
 
+    def test_to_s_apostrophe
+      @doc_type_public_system.parent.context[:prologue_quote] = :apostrophe
+      assert_equal("<!DOCTYPE root PUBLIC '#{@pubid}' '#{@sysid}'>",
+                   @doc_type_public_system.to_s)
+    end
+
     def test_system
       assert_equal([
                      @sysid,
@@ -82,6 +86,35 @@ module REXMLTests
             assert_equal("<!DOCTYPE root SYSTEM \"root.dtd\">",
                          doctype.to_s)
           end
+
+          def test_to_s_apostrophe
+            doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root.dtd"])
+            doc = REXML::Document.new
+            doc << doctype
+            doctype.parent.context[:prologue_quote] = :apostrophe
+            assert_equal("<!DOCTYPE root SYSTEM 'root.dtd'>",
+                         doctype.to_s)
+          end
+
+          def test_to_s_single_quote_apostrophe
+            doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root'.dtd"])
+            doc = REXML::Document.new
+            doc << doctype
+            # This isn't used.
+            doctype.parent.context[:prologue_quote] = :apostrophe
+            assert_equal("<!DOCTYPE root SYSTEM \"root'.dtd\">",
+                         doctype.to_s)
+          end
+
+          def test_to_s_double_quote
+            doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root\".dtd"])
+            doc = REXML::Document.new
+            doc << doctype
+            # This isn't used.
+            doctype.parent.context[:prologue_quote] = :apostrophe
+            assert_equal("<!DOCTYPE root SYSTEM 'root\".dtd'>",
+                         doctype.to_s)
+          end
         end
       end
 
@@ -92,6 +125,25 @@ module REXMLTests
             assert_equal("<!DOCTYPE root PUBLIC \"pub\" \"root.dtd\">",
                          doctype.to_s)
           end
+
+          def test_to_s_apostrophe
+            doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"])
+            doc = REXML::Document.new
+            doc << doctype
+            doctype.parent.context[:prologue_quote] = :apostrophe
+            assert_equal("<!DOCTYPE root PUBLIC 'pub' 'root.dtd'>",
+                         doctype.to_s)
+          end
+
+          def test_to_s_apostrophe_include_apostrophe
+            doctype = REXML::DocType.new(["root", "PUBLIC", "pub'", "root.dtd"])
+            doc = REXML::Document.new
+            doc << doctype
+            # This isn't used.
+            doctype.parent.context[:prologue_quote] = :apostrophe
+            assert_equal("<!DOCTYPE root PUBLIC \"pub'\" 'root.dtd'>",
+                         doctype.to_s)
+          end
         end
 
         class TestSystemLiteral < self
@@ -101,6 +153,25 @@ module REXMLTests
                          doctype.to_s)
           end
 
+          def test_to_s_apostrophe
+            doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"])
+            doc = REXML::Document.new
+            doc << doctype
+            doctype.parent.context[:prologue_quote] = :apostrophe
+            assert_equal("<!DOCTYPE root PUBLIC 'pub' 'root.dtd'>",
+                         doctype.to_s)
+          end
+
+          def test_to_s_apostrophe_include_apostrophe
+            doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root'.dtd"])
+            doc = REXML::Document.new
+            doc << doctype
+            # This isn't used.
+            doctype.parent.context[:prologue_quote] = :apostrophe
+            assert_equal("<!DOCTYPE root PUBLIC 'pub' \"root'.dtd\">",
+                         doctype.to_s)
+          end
+
           def test_to_s_double_quote
             doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root\".dtd"])
             assert_equal("<!DOCTYPE root PUBLIC \"pub\" 'root\".dtd'>",
@@ -143,6 +214,62 @@ module REXMLTests
                    decl(@id, "system\"literal").to_s)
     end
 
+    def test_to_s_apostrophe
+      document = REXML::Document.new(<<-XML)
+      <!DOCTYPE root SYSTEM "urn:x-test:sysid" [
+        #{decl(@id, @uri).to_s}
+      ]>
+      <root/>
+      XML
+      document.context[:prologue_quote] = :apostrophe
+      notation = document.doctype.notations[0]
+      assert_equal("<!NOTATION #{@name} PUBLIC '#{@id}' '#{@uri}'>",
+                   notation.to_s)
+    end
+
+    def test_to_s_apostrophe_pubid_literal_include_apostrophe
+      document = REXML::Document.new(<<-XML)
+      <!DOCTYPE root SYSTEM "urn:x-test:sysid" [
+        #{decl("#{@id}'", @uri).to_s}
+      ]>
+      <root/>
+      XML
+      # This isn't used for PubidLiteral because PubidChar includes '.
+      document.context[:prologue_quote] = :apostrophe
+      notation = document.doctype.notations[0]
+      assert_equal("<!NOTATION #{@name} PUBLIC \"#{@id}'\" '#{@uri}'>",
+                   notation.to_s)
+    end
+
+    def test_to_s_apostrophe_system_literal_include_apostrophe
+      document = REXML::Document.new(<<-XML)
+      <!DOCTYPE root SYSTEM "urn:x-test:sysid" [
+        #{decl(@id, "system'literal").to_s}
+      ]>
+      <root/>
+      XML
+      # This isn't used for SystemLiteral because SystemLiteral includes '.
+      document.context[:prologue_quote] = :apostrophe
+      notation = document.doctype.notations[0]
+      assert_equal("<!NOTATION #{@name} PUBLIC '#{@id}' \"system'literal\">",
+                   notation.to_s)
+    end
+
+    def test_to_s_apostrophe_system_literal_include_double_quote
+      document = REXML::Document.new(<<-XML)
+      <!DOCTYPE root SYSTEM "urn:x-test:sysid" [
+        #{decl(@id, "system\"literal").to_s}
+      ]>
+      <root/>
+      XML
+      # This isn't used for SystemLiteral because SystemLiteral includes ".
+      # But quoted by ' because SystemLiteral includes ".
+      document.context[:prologue_quote] = :apostrophe
+      notation = document.doctype.notations[0]
+      assert_equal("<!NOTATION #{@name} PUBLIC '#{@id}' 'system\"literal'>",
+                   notation.to_s)
+    end
+
     private
     def decl(id, uri)
       REXML::NotationDecl.new(@name, "PUBLIC", id, uri)
@@ -170,6 +297,48 @@ module REXMLTests
                    decl("#{@id}\"").to_s)
     end
 
+    def test_to_s_apostrophe
+      document = REXML::Document.new(<<-XML)
+      <!DOCTYPE root SYSTEM "urn:x-test:sysid" [
+        #{decl(@id).to_s}
+      ]>
+      <root/>
+      XML
+      document.context[:prologue_quote] = :apostrophe
+      notation = document.doctype.notations[0]
+      assert_equal("<!NOTATION #{@name} SYSTEM '#{@id}'>",
+                   notation.to_s)
+    end
+
+    def test_to_s_apostrophe_include_apostrophe
+      document = REXML::Document.new(<<-XML)
+      <!DOCTYPE root SYSTEM "urn:x-test:sysid" [
+        #{decl("#{@id}'").to_s}
+      ]>
+      <root/>
+      XML
+      # This isn't used for SystemLiteral because SystemLiteral includes '.
+      document.context[:prologue_quote] = :apostrophe
+      notation = document.doctype.notations[0]
+      assert_equal("<!NOTATION #{@name} SYSTEM \"#{@id}'\">",
+                   notation.to_s)
+    end
+
+    def test_to_s_apostrophe_include_double_quote
+      document = REXML::Document.new(<<-XML)
+      <!DOCTYPE root SYSTEM "urn:x-test:sysid" [
+        #{decl("#{@id}\"").to_s}
+      ]>
+      <root/>
+      XML
+      # This isn't used for SystemLiteral because SystemLiteral includes ".
+      # But quoted by ' because SystemLiteral includes ".
+      document.context[:prologue_quote] = :apostrophe
+      notation = document.doctype.notations[0]
+      assert_equal("<!NOTATION #{@name} SYSTEM '#{@id}\"'>",
+                   notation.to_s)
+    end
+
     private
     def decl(id)
       REXML::NotationDecl.new(@name, "SYSTEM", nil, id)
diff --git a/test/rexml/test_document.rb b/test/rexml/test_document.rb
index c0faae4..2b0a8a7 100644
--- a/test/rexml/test_document.rb
+++ b/test/rexml/test_document.rb
@@ -1,13 +1,14 @@
 # -*- coding: utf-8 -*-
 # frozen_string_literal: false
 
-require "rexml/document"
-require "test/unit"
+require 'core_assertions'
 
 module REXMLTests
   class TestDocument < Test::Unit::TestCase
+    include Test::Unit::CoreAssertions
+
     def test_version_attributes_to_s
-      doc = REXML::Document.new(<<-eoxml)
+      doc = REXML::Document.new(<<~eoxml)
         <?xml version="1.0" encoding="UTF-8" standalone="no"?>
         <svg  id="svg2"
               xmlns:sodipodi="foo"
@@ -169,11 +170,9 @@ EOF
 <cd></cd>
 EOF
 
-          assert_raise(REXML::ParseException) do
-            REXML::Document.new(xml)
-          end
-          REXML::Security.entity_expansion_limit = 100
-          assert_equal(100, REXML::Security.entity_expansion_limit)
+          REXML::Document.new(xml)
+          REXML::Security.entity_expansion_limit = 90
+          assert_equal(90, REXML::Security.entity_expansion_limit)
           assert_raise(REXML::ParseException) do
             REXML::Document.new(xml)
           end
@@ -203,6 +202,49 @@ EOX
       assert_equal('no', doc.stand_alone?, bug2539)
     end
 
+    def test_gt_linear_performance_attribute_value
+      seq = [10000, 50000, 100000, 150000, 200000]
+      assert_linear_performance(seq, rehearsal: 10) do |n|
+        REXML::Document.new('<test testing="' + ">" * n + '"></test>')
+      end
+    end
+
+    def test_each_recursive
+      xml_source = <<~XML
+        <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+        <root name="root">
+          <x name="1_1">
+            <x name="1_2">
+              <x name="1_3" />
+            </x>
+          </x>
+          <x name="2_1">
+            <x name="2_2">
+              <x name="2_3" />
+            </x>
+          </x>
+          <!-- comment -->
+          <![CDATA[ cdata ]]>
+        </root>
+      XML
+
+      expected_names = %w[
+        root
+        1_1 1_2 1_3
+        2_1 2_2 2_3
+      ]
+
+      document = REXML::Document.new(xml_source)
+
+      # Node#each_recursive iterates elements only.
+      # This does not iterate XML declarations, comments, attributes, CDATA sections, etc.
+      actual_names = []
+      document.each_recursive do |element|
+        actual_names << element.attributes["name"]
+      end
+      assert_equal(expected_names, actual_names)
+    end
+
     class WriteTest < Test::Unit::TestCase
       def setup
         @document = REXML::Document.new(<<-EOX)
diff --git a/test/rexml/test_element.rb b/test/rexml/test_element.rb
index 82830b4..2021689 100644
--- a/test/rexml/test_element.rb
+++ b/test/rexml/test_element.rb
@@ -1,8 +1,5 @@
 # frozen_string_literal: false
 
-require "test/unit/testcase"
-require "rexml/document"
-
 module REXMLTests
   class ElementTester < Test::Unit::TestCase
     def test_array_reference_string
diff --git a/test/rexml/test_elements.rb b/test/rexml/test_elements.rb
index a850e62..c0f1b22 100644
--- a/test/rexml/test_elements.rb
+++ b/test/rexml/test_elements.rb
@@ -1,6 +1,4 @@
 # frozen_string_literal: false
-require 'test/unit/testcase'
-require 'rexml/document'
 
 module REXMLTests
   class ElementsTester < Test::Unit::TestCase
@@ -115,5 +113,10 @@ module REXMLTests
       }
       assert_equal 6, r
     end
+
+    def test_parent
+      doc = Document.new( "<a><b id='1'/><b id='2'/></a>" )
+      assert_equal('a', doc.root.elements.parent.name)
+    end
   end
 end
diff --git a/test/rexml/test_encoding.rb b/test/rexml/test_encoding.rb
index 919db13..6887ffb 100644
--- a/test/rexml/test_encoding.rb
+++ b/test/rexml/test_encoding.rb
@@ -1,14 +1,13 @@
-# coding: binary
+# coding: utf-8
 # frozen_string_literal: false
 
-require_relative "rexml_test_utils"
-
 require 'rexml/source'
 require 'rexml/document'
 
 module REXMLTests
   class EncodingTester < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
+    include Helper::Global
     include REXML
 
     def setup
@@ -23,7 +22,7 @@ module REXMLTests
       doc = Document.new( @encoded )
       doc.write( out="" )
       out.force_encoding(::Encoding::ASCII_8BIT)
-      assert_equal( @encoded, out )
+      assert_equal( @encoded.b, out )
     end
 
     # Given an encoded document, try to change the encoding and write it out
@@ -33,10 +32,10 @@ module REXMLTests
       assert_equal("UTF-8", doc.encoding)
       REXML::Formatters::Default.new.write( doc.root, out="" )
       out.force_encoding(::Encoding::ASCII_8BIT)
-      assert_equal( @not_encoded, out )
+      assert_equal( @not_encoded.b, out )
       char = XPath.first( doc, "/a/b/text()" ).to_s
       char.force_encoding(::Encoding::ASCII_8BIT)
-      assert_equal( "ĉ", char )
+      assert_equal( "ĉ".b, char )
     end
 
     # * Given an encoded document, try to write it to a different encoding
@@ -44,7 +43,7 @@ module REXMLTests
       doc = Document.new( @encoded )
       REXML::Formatters::Default.new.write( doc.root, Output.new( out="", "UTF-8" ) )
       out.force_encoding(::Encoding::ASCII_8BIT)
-      assert_equal( @not_encoded, out )
+      assert_equal( @not_encoded.b, out )
     end
 
     # * Given a non-encoded document, change the encoding
@@ -54,7 +53,7 @@ module REXMLTests
       assert_equal("ISO-8859-3", doc.encoding)
       doc.write( out="" )
       out.force_encoding(::Encoding::ASCII_8BIT)
-      assert_equal( @encoded, out )
+      assert_equal( @encoded.b, out )
     end
 
     # * Given a non-encoded document, write to a different encoding
@@ -62,13 +61,13 @@ module REXMLTests
       doc = Document.new( @not_encoded )
       doc.write( Output.new( out="", "ISO-8859-3" ) )
       out.force_encoding(::Encoding::ASCII_8BIT)
-      assert_equal( "<?xml version='1.0'?>#{@encoded_root}", out )
+      assert_equal( "<?xml version='1.0'?>#{@encoded_root}".b, out )
     end
 
     # * Given an encoded document, accessing text and attribute nodes
     #   should provide UTF-8 text.
     def test_in_different_access
-      doc = Document.new <<-EOL
+      doc = Document.new <<~EOL
       <?xml version='1.0' encoding='ISO-8859-1'?>
       <a a="\xFF">\xFF</a>
       EOL
@@ -80,7 +79,7 @@ module REXMLTests
 
 
     def test_ticket_89
-      doc = Document.new <<-EOL
+      doc = Document.new <<~EOL
          <?xml version="1.0" encoding="CP-1252" ?>
          <xml><foo></foo></xml>
          EOL
@@ -97,7 +96,7 @@ module REXMLTests
     end
 
     def test_parse_utf16_with_utf8_default_internal
-      EnvUtil.with_default_internal("UTF-8") do
+      with_default_internal("UTF-8") do
         utf16 = File.open(fixture_path("utf16.xml")) do |f|
           REXML::Document.new(f)
         end
diff --git a/test/rexml/test_entity.rb b/test/rexml/test_entity.rb
index 6dc6637..a2b262f 100644
--- a/test/rexml/test_entity.rb
+++ b/test/rexml/test_entity.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: false
-require "test/unit/testcase"
 
-require 'rexml/document'
 require 'rexml/entity'
 require 'rexml/source'
 
diff --git a/test/rexml/test_functions_number.rb b/test/rexml/test_functions_number.rb
deleted file mode 100644
index 84ec5c7..0000000
--- a/test/rexml/test_functions_number.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: false
-require 'rexml/document'
-require 'test/unit'
-require 'rexml/functions'
-
-module REXMLTests
-  class TC_Rexml_Functions_Number < Test::Unit::TestCase
-
-    def test_functions_number_int
-      telem = REXML::Element.new("elem")
-      telem.text="9"
-      assert_equal(9, REXML::Functions::number(telem))
-    end
-    def test_functions_number_float
-      telem = REXML::Element.new("elem")
-      telem.text="10.4"
-      assert_equal(10.4, REXML::Functions::number(telem))
-    end
-    def test_functions_number_negative_int
-      telem = REXML::Element.new("elem")
-      telem.text="-9"
-      assert_equal(-9, REXML::Functions::number(telem))
-    end
-    def test_functions_number_negative_float
-      telem = REXML::Element.new("elem")
-      telem.text="-9.13"
-      assert_equal(-9.13, REXML::Functions::number(telem))
-    end
-    #def test_functions_number_scientific_notation
-    #  telem = REXML::Element.new("elem")
-    #  telem.text="9.13E12"
-    #  assert_equal(9.13E12, REXML::Functions::number(telem))
-    #end
-  end
-end
diff --git a/test/rexml/test_instruction.rb b/test/rexml/test_instruction.rb
new file mode 100644
index 0000000..5451e36
--- /dev/null
+++ b/test/rexml/test_instruction.rb
@@ -0,0 +1,12 @@
+module REXMLTests
+  class InstructionTest < Test::Unit::TestCase
+    def test_target_nil
+      error = assert_raise(ArgumentError) do
+        REXML::Instruction.new(nil)
+      end
+      assert_equal("processing instruction target must be String or " +
+                   "REXML::Instruction: <nil>",
+                   error.message)
+    end
+  end
+end
diff --git a/test/rexml/test_jaxen.rb b/test/rexml/test_jaxen.rb
index 9cd7bee..6038e88 100644
--- a/test/rexml/test_jaxen.rb
+++ b/test/rexml/test_jaxen.rb
@@ -1,5 +1,4 @@
 # frozen_string_literal: false
-require_relative 'rexml_test_utils'
 
 require "rexml/document"
 require "rexml/xpath"
@@ -9,122 +8,123 @@ require "rexml/xpath"
 
 module REXMLTests
   class JaxenTester < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     include REXML
 
-    def test_axis ; test("axis") ; end
-    def test_basic ; test("basic") ; end
-    def test_basicupdate ; test("basicupdate") ; end
-    def test_contents ; test("contents") ; end
-    def test_defaultNamespace ; test("defaultNamespace") ; end
-    def test_fibo ; test("fibo") ; end
-    def test_id ; test("id") ; end
-    def test_jaxen24 ; test("jaxen24") ; end
-    def test_lang ; test("lang") ; end
-    def test_message ; test("message") ; end
-    def test_moreover ; test("moreover") ; end
-    def test_much_ado ; test("much_ado") ; end
-    def test_namespaces ; test("namespaces") ; end
-    def test_nitf ; test("nitf") ; end
-    def test_numbers ; test("numbers") ; end
-    def test_pi ; test("pi") ; end
-    def test_pi2 ; test("pi2") ; end
-    def test_simple ; test("simple") ; end
-    def test_testNamespaces ; test("testNamespaces") ; end
-    def test_text ; test("text") ; end
-    def test_underscore ; test("underscore") ; end
-    def test_web ; test("web") ; end
-    def test_web2 ; test("web2") ; end
+    def test_axis ; process_test_case("axis") ; end
+    def test_basic ; process_test_case("basic") ; end
+    def test_basicupdate ; process_test_case("basicupdate") ; end
+    def test_contents ; process_test_case("contents") ; end
+    def test_defaultNamespace ; process_test_case("defaultNamespace") ; end
+    def test_fibo ; process_test_case("fibo") ; end
+    def test_id ; process_test_case("id") ; end
+    def test_jaxen24 ; process_test_case("jaxen24") ; end
+    def test_lang ; process_test_case("lang") ; end
+    # document() function for XSLT isn't supported
+    def _test_message ; process_test_case("message") ; end
+    def test_moreover ; process_test_case("moreover") ; end
+    def test_much_ado ; process_test_case("much_ado") ; end
+    def test_namespaces ; process_test_case("namespaces") ; end
+    def test_nitf ; process_test_case("nitf") ; end
+    # Exception should be considered
+    def _test_numbers ; process_test_case("numbers") ; end
+    def test_pi ; process_test_case("pi") ; end
+    def test_pi2 ; process_test_case("pi2") ; end
+    def test_simple ; process_test_case("simple") ; end
+    # TODO: namespace node is needed
+    def _test_testNamespaces ; process_test_case("testNamespaces") ; end
+    # document() function for XSLT isn't supported
+    def _test_text ; process_test_case("text") ; end
+    def test_underscore ; process_test_case("underscore") ; end
+    def _test_web ; process_test_case("web") ; end
+    def test_web2 ; process_test_case("web2") ; end
 
     private
-    def test( fname )
-#      Dir.entries( xml_dir ).each { |fname|
-#        if fname =~ /\.xml$/
-          doc = File.open(fixture_path(fname+".xml")) do |file|
-            Document.new(file)
-          end
-          XPath.each( doc, "/tests/document" ) {|e| handleDocument(e)}
-#        end
-#      }
+    def process_test_case(name)
+      xml_path = "#{name}.xml"
+      doc = File.open(fixture_path(xml_path)) do |file|
+        Document.new(file)
+      end
+      test_doc = File.open(fixture_path("test/tests.xml")) do |file|
+        Document.new(file)
+      end
+      XPath.each(test_doc,
+                 "/tests/document[@url='xml/#{xml_path}']/context") do |context|
+        process_context(doc, context)
+      end
     end
 
     # processes a tests/document/context node
-    def handleContext( testDoc, ctxElement)
-      testCtx = XPath.match( testDoc, ctxElement.attributes["select"] )[0]
-      namespaces = {}
-      if testCtx.class == Element
-        testCtx.prefixes.each { |pre| handleNamespace( testCtx, pre, namespaces ) }
-      end
+    def process_context(doc, context)
+      test_context = XPath.match(doc, context.attributes["select"])
+      namespaces = context.namespaces
+      namespaces.delete("var")
+      namespaces = nil if namespaces.empty?
       variables = {}
-      XPath.each( ctxElement, "@*[namespace-uri() = 'http://jaxen.org/test-harness/var']") { |attrib| handleVariable(testCtx, variables, attrib) }
-      XPath.each( ctxElement, "valueOf") { |e| handleValueOf(testCtx, variables, namespaces, e) }
-      XPath.each( ctxElement, "test[not(@exception) or (@exception != 'true') ]") { |e| handleNominalTest(testCtx,variables, namespaces, e) }
-      XPath.each( ctxElement, "test[@exception = 'true']") { |e| handleExceptionalTest(testCtx,variables, namespaces, e) }
+      var_namespace = "http://jaxen.org/test-harness/var"
+      XPath.each(context,
+                 "@*[namespace-uri() = '#{var_namespace}']") do |attribute|
+        variables[attribute.name] = attribute.value
+      end
+      XPath.each(context, "valueOf") do |value|
+        process_value_of(test_context, variables, namespaces, value)
+      end
+      XPath.each(context,
+                 "test[not(@exception) or (@exception != 'true')]") do |test|
+        process_nominal_test(test_context, variables, namespaces, test)
+      end
+      XPath.each(context,
+                 "test[@exception = 'true']") do |test|
+        process_exceptional_test(test_context, variables, namespaces, test)
+      end
     end
 
     # processes a tests/document/context/valueOf or tests/document/context/test/valueOf node
-    def handleValueOf(ctx,variables, namespaces, valueOfElement)
-      expected = valueOfElement.text
-      got = XPath.match( ctx, valueOfElement.attributes["select"], namespaces, variables )[0]
-      assert_true( (got.nil? && expected.nil?) || !got.nil? )
-      case got.class
-      when Element
-        assert_equal( got.class, Element )
-      when Attribute, Text, Comment, TrueClass, FalseClass
-        assert_equal( expected, got.to_s )
-      when Instruction
-        assert_equal( expected, got.content )
-      when Integer
-        assert_equal( exected.to_f, got )
-      when String
-        # normalize values for comparison
-        got = "" if got == nil or got == ""
-        expected = "" if expected == nil or expected == ""
-        assert_equal( expected, got )
-      else
-        assert_fail( "Wassup?" )
-      end
-    end
+    def process_value_of(context, variables, namespaces, value_of)
+      expected = value_of.text
+      xpath = value_of.attributes["select"]
+      matched = XPath.match(context, xpath, namespaces, variables, strict: true)
 
+      message = user_message(context, xpath, matched)
+      assert_equal(expected || "",
+                   REXML::Functions.string(matched),
+                   message)
+    end
 
     # processes a tests/document/context/test node ( where @exception is false or doesn't exist )
-    def handleNominalTest(ctx, variables, namespaces, testElement)
-      expected = testElement.attributes["count"]
-      got = XPath.match( ctx, testElement.attributes["select"], namespaces, variables )
+    def process_nominal_test(context, variables, namespaces, test)
+      xpath = test.attributes["select"]
+      matched = XPath.match(context, xpath, namespaces, variables, strict: true)
       # might be a test with no count attribute, but nested valueOf elements
-      assert( expected == got.size.to_s ) if !expected.nil?
+      expected = test.attributes["count"]
+      if expected
+        assert_equal(Integer(expected, 10),
+                     matched.size,
+                     user_message(context, xpath, matched))
+      end
 
-      XPath.each( testElement, "valueOf") { |e|
-        handleValueOf(got, variables, namespaces, e)
-      }
+      XPath.each(test, "valueOf") do |value_of|
+        process_value_of(matched, variables, namespaces, value_of)
+      end
     end
 
     # processes a tests/document/context/test node ( where @exception is true )
-    def handleExceptionalTest(ctx, variables, namespaces, testElement)
-      assert_raise( Exception ) {
-        XPath.match( ctx, testElement.attributes["select"], namespaces, variables )
-      }
-    end
-
-    # processes a tests/document node
-    def handleDocument(docElement)
-      puts "- Processing document: #{docElement.attributes['url']}"
-      testFile = File.new( docElement.attributes["url"] )
-      testDoc = Document.new testFile
-      XPath.each( docElement, "context") { |e| handleContext(testDoc, e) }
-    end
-
-    # processes a variable definition in a namespace like <test var:foo="bar">
-    def handleVariable( ctx, variables, attrib )
-      puts "--- Found attribute: #{attrib.name}"
-      variables[attrib.name] = attrib.value
+    def process_exceptional_test(context, variables, namespaces, test)
+      xpath = test.attributes["select"]
+      assert_raise(REXML::ParseException) do
+        XPath.match(context, xpath, namespaces, variables, strict: true)
+      end
     end
 
-    # processes a namespace definition like <test xmlns:foo="fiz:bang:bam">
-    def handleNamespace( ctx, prefix, namespaces )
-      puts "--- Found namespace: #{prefix}"
-      namespaces[prefix] = ctx.namespaces[prefix]
+    def user_message(context, xpath, matched)
+      message = ""
+      context.each_with_index do |node, i|
+        message << "Node#{i}:\n"
+        message << "#{node}\n"
+      end
+      message << "XPath: <#{xpath}>\n"
+      message << "Matched <#{matched}>"
+      message
     end
-
   end
 end
diff --git a/test/rexml/test_light.rb b/test/rexml/test_light.rb
index 99bd9ca..c556c97 100644
--- a/test/rexml/test_light.rb
+++ b/test/rexml/test_light.rb
@@ -1,18 +1,18 @@
 # frozen_string_literal: false
-require_relative "rexml_test_utils"
+
 require "rexml/light/node"
 require "rexml/parsers/lightparser"
 
 module REXMLTests
   class LightTester < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     include REXML::Light
 
     def test_parse_large
-      xml_string = fixture_path("documentation.xml")
+      xml_string = File.read(fixture_path("documentation.xml"))
       parser = REXML::Parsers::LightParser.new(xml_string)
       tag, content = parser.parse
-      assert_equal([:document, :text], [tag, content.first])
+      assert_equal([:document, :xmldecl], [tag, content.first])
     end
 
     # FIXME INCOMPLETE
@@ -62,7 +62,7 @@ module REXMLTests
       assert_equal( 'c', a[1].name )
     end
 
-    def test_itterate_over_children
+    def test_iterate_over_children
       foo = make_small_document
       ctr = 0
       foo[0].each { ctr += 1 }
diff --git a/test/rexml/test_lightparser.rb b/test/rexml/test_lightparser.rb
index 1aeac07..533f9fb 100644
--- a/test/rexml/test_lightparser.rb
+++ b/test/rexml/test_lightparser.rb
@@ -1,10 +1,10 @@
 # frozen_string_literal: false
-require_relative 'rexml_test_utils'
+
 require 'rexml/parsers/lightparser'
 
 module REXMLTests
   class LightParserTester < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     include REXML
     def test_parsing
       File.open(fixture_path("documentation.xml")) do |f|
diff --git a/test/rexml/test_listener.rb b/test/rexml/test_listener.rb
index 322d368..5e40d7f 100644
--- a/test/rexml/test_listener.rb
+++ b/test/rexml/test_listener.rb
@@ -1,14 +1,11 @@
 # coding: binary
 # frozen_string_literal: false
 
-require_relative 'rexml_test_utils'
-
-require 'rexml/document'
 require 'rexml/streamlistener'
 
 module REXMLTests
   class BaseTester < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     def test_empty
       return unless defined? @listener
       # Empty.
diff --git a/test/rexml/test_martin_fowler.rb b/test/rexml/test_martin_fowler.rb
index da685a8..ce27d72 100644
--- a/test/rexml/test_martin_fowler.rb
+++ b/test/rexml/test_martin_fowler.rb
@@ -1,9 +1,7 @@
 # frozen_string_literal: false
-require 'test/unit'
-require 'rexml/document'
 
 module REXMLTests
-  class OrderTester < Test::Unit::TestCase
+  class OrderTesterMF < Test::Unit::TestCase
     DOC = <<END
 <paper>
 <title>Remove this element and figs order differently</title>
@@ -18,12 +16,12 @@ module REXMLTests
 </paper>
 END
 
-    def initialize n
+    def setup
       @doc = REXML::Document.new(DOC)
       @figs = REXML::XPath.match(@doc,'//figure')
       @names = @figs.collect {|f| f.attributes['src']}
-      super
     end
+
     def test_fig1
       assert_equal 'fig1', @figs[0].attributes['src']
     end
diff --git a/test/rexml/test_namespace.rb b/test/rexml/test_namespace.rb
index 90e1d36..a41e505 100644
--- a/test/rexml/test_namespace.rb
+++ b/test/rexml/test_namespace.rb
@@ -1,11 +1,8 @@
 # frozen_string_literal: false
-require_relative "rexml_test_utils"
-
-require "rexml/document"
 
 module REXMLTests
   class TestNamespace < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     include REXML
 
     def setup
diff --git a/test/rexml/test_order.rb b/test/rexml/test_order.rb
index 807d9fa..f3f9cc5 100644
--- a/test/rexml/test_order.rb
+++ b/test/rexml/test_order.rb
@@ -1,6 +1,5 @@
 # frozen_string_literal: false
-require_relative 'rexml_test_utils'
-require 'rexml/document'
+
 begin
   require 'zlib'
 rescue LoadError
@@ -8,7 +7,7 @@ end
 
 module REXMLTests
   class OrderTester < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
 
     TESTDOC = <<END
 <a>
diff --git a/test/rexml/test_preceding_sibling.rb b/test/rexml/test_preceding_sibling.rb
index d89a1e1..7e661eb 100644
--- a/test/rexml/test_preceding_sibling.rb
+++ b/test/rexml/test_preceding_sibling.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: false
 # ISSUE 32
-require 'test/unit'
-require 'rexml/document'
 
 module REXMLTests
   # daz - for report by Dan Kohn in:
diff --git a/test/rexml/test_pullparser.rb b/test/rexml/test_pullparser.rb
index 31b5b74..b6a48c9 100644
--- a/test/rexml/test_pullparser.rb
+++ b/test/rexml/test_pullparser.rb
@@ -1,5 +1,4 @@
 # frozen_string_literal: false
-require "test/unit/testcase"
 
 require 'rexml/parsers/pullparser'
 
@@ -63,6 +62,26 @@ module REXMLTests
       end
     end
 
+    def test_character_references
+      source = '<a>&#65;</a><b>&#x42;</b>'
+      parser = REXML::Parsers::PullParser.new( source )
+      element_name = ''
+      while parser.has_next?
+        event = parser.pull
+        case event.event_type
+        when :start_element
+          element_name = event[0]
+        when :text
+          case element_name
+          when 'a'
+            assert_equal('A', event[1])
+          when 'b'
+            assert_equal('B', event[1])
+          end
+        end
+      end
+    end
+
     def test_peek_unshift
       source = "<a><b/></a>"
       REXML::Parsers::PullParser.new(source)
diff --git a/test/rexml/test_rexml_issuezilla.rb b/test/rexml/test_rexml_issuezilla.rb
index 1c54c9d..7bcbefc 100644
--- a/test/rexml/test_rexml_issuezilla.rb
+++ b/test/rexml/test_rexml_issuezilla.rb
@@ -1,10 +1,8 @@
 # frozen_string_literal: false
-require_relative 'rexml_test_utils'
-require 'rexml/document'
 
 module REXMLTests
   class TestIssuezillaParsing < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     def test_rexml
       doc = File.open(fixture_path("ofbiz-issues-full-177.xml")) do |f|
         REXML::Document.new(f)
diff --git a/test/rexml/test_sax.rb b/test/rexml/test_sax.rb
index 00539f0..8e905f2 100644
--- a/test/rexml/test_sax.rb
+++ b/test/rexml/test_sax.rb
@@ -1,12 +1,11 @@
 # frozen_string_literal: false
-require_relative "rexml_test_utils"
+
 require 'rexml/sax2listener'
 require 'rexml/parsers/sax2parser'
-require 'rexml/document'
 
 module REXMLTests
   class SAX2Tester < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     include REXML
     def test_characters
       d = Document.new( "<A>@blah@</A>" )
@@ -110,7 +109,7 @@ module REXMLTests
     # test simple non-entity doctype in sax listener
     # submitted by Jeff Barczewski
     def test_simple_doctype_listener
-      xml = <<-END
+      xml = <<~END
         <?xml version="1.0"?>
         <!DOCTYPE greeting PUBLIC "Hello Greeting DTD" "http://foo/hello.dtd">
         <greeting>Hello, world!</greeting>
@@ -141,8 +140,8 @@ module REXMLTests
 
     # test doctype with missing name, should throw ParseException
     # submitted by Jeff Barczewseki
-    def test_doctype_with_mising_name_throws_exception
-      xml = <<-END
+    def test_doctype_with_missing_name_throws_exception
+      xml = <<~END
         <?xml version="1.0"?>
         <!DOCTYPE >
         <greeting>Hello, world!</greeting>
diff --git a/test/rexml/test_stream.rb b/test/rexml/test_stream.rb
index d7ceedc..545d534 100644
--- a/test/rexml/test_stream.rb
+++ b/test/rexml/test_stream.rb
@@ -1,6 +1,5 @@
 # frozen_string_literal: false
-require "test/unit/testcase"
-require "rexml/document"
+
 require 'rexml/streamlistener'
 require 'stringio'
 
@@ -15,8 +14,8 @@ module REXMLTests
     def test_listener
       data = %Q{<session1 user="han" password="rootWeiler" />\n<session2 user="han" password="rootWeiler" />}
 
-      b = RequestReader.new( data )
-      b = RequestReader.new( data )
+      RequestReader.new( data )
+      RequestReader.new( data )
     end
 
     def test_ticket_49
diff --git a/test/rexml/test_text.rb b/test/rexml/test_text.rb
index 3f8036e..bae2165 100644
--- a/test/rexml/test_text.rb
+++ b/test/rexml/test_text.rb
@@ -1,10 +1,55 @@
 # frozen_string_literal: false
-require "rexml/text"
 
 module REXMLTests
   class TextTester < Test::Unit::TestCase
     include REXML
 
+    def test_new_text_response_whitespace_default
+      text = Text.new("a  b\t\tc", true)
+      assert_equal("a b\tc", Text.new(text).to_s)
+    end
+
+    def test_new_text_response_whitespace_true
+      text = Text.new("a  b\t\tc", true)
+      assert_equal("a  b\t\tc", Text.new(text, true).to_s)
+    end
+
+    def test_new_text_raw_default
+      text = Text.new("&amp;lt;", false, nil, true)
+      assert_equal("&amp;lt;", Text.new(text).to_s)
+    end
+
+    def test_new_text_raw_false
+      text = Text.new("&amp;lt;", false, nil, true)
+      assert_equal("&amp;amp;lt;", Text.new(text, false, nil, false).to_s)
+    end
+
+    def test_new_text_entity_filter_default
+      document = REXML::Document.new(<<-XML)
+<!DOCTYPE root [
+  <!ENTITY a "aaa">
+  <!ENTITY b "bbb">
+]>
+<root/>
+      XML
+      text = Text.new("aaa bbb", false, document.root, nil, ["a"])
+      assert_equal("aaa &b;",
+                   Text.new(text, false, document.root).to_s)
+    end
+
+    def test_new_text_entity_filter_custom
+      document = REXML::Document.new(<<-XML)
+<!DOCTYPE root [
+  <!ENTITY a "aaa">
+  <!ENTITY b "bbb">
+]>
+<root/>
+      XML
+      text = Text.new("aaa bbb", false, document.root, nil, ["a"])
+      assert_equal("&a; bbb",
+                   Text.new(text, false, document.root, nil, ["b"]).to_s)
+    end
+
     def test_shift_operator_chain
       text = Text.new("original\r\n")
       text << "append1\r\n" << "append2\r\n"
@@ -18,5 +63,11 @@ module REXMLTests
       text << "append3\r\n" << "append4\r\n"
       assert_equal("original\nappend1\nappend2\nappend3\nappend4\n", text.to_s)
     end
+
+    def test_clone
+      text = Text.new("&amp;lt; <")
+      assert_equal(text.to_s,
+                   text.clone.to_s)
+    end
   end
 end
diff --git a/test/rexml/test_ticket_80.rb b/test/rexml/test_ticket_80.rb
index ab6a57e..daebdc5 100644
--- a/test/rexml/test_ticket_80.rb
+++ b/test/rexml/test_ticket_80.rb
@@ -7,9 +7,6 @@
 # copy: (C) CopyLoose 2006 Bib Development Team <bib-devel>at<uberdev>dot<org>
 #------------------------------------------------------------------------------
 
-require 'test/unit'
-require 'rexml/document'
-
 module REXMLTests
   class Ticket80 < Test::Unit::TestCase
 
diff --git a/test/rexml/test_validation_rng.rb b/test/rexml/test_validation_rng.rb
index b5b5045..4872396 100644
--- a/test/rexml/test_validation_rng.rb
+++ b/test/rexml/test_validation_rng.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: false
-require "test/unit/testcase"
 
-require "rexml/document"
 require "rexml/validation/relaxng"
 
 module REXMLTests
@@ -9,7 +7,7 @@ module REXMLTests
     include REXML
 
     def test_validate
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -26,7 +24,7 @@ module REXMLTests
     </element>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       no_error( validator, %q{<A><B><C X="x"><E/><E/></C><D/></B></A>} )
@@ -35,7 +33,7 @@ module REXMLTests
 
 
     def test_sequence
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -47,7 +45,7 @@ module REXMLTests
     </element>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B><C/><C/><D/></B></A>} )
@@ -58,7 +56,7 @@ module REXMLTests
 
 
     def test_choice
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -72,7 +70,7 @@ module REXMLTests
     </choice>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B><C/><D/></B></A>} )
@@ -81,7 +79,7 @@ module REXMLTests
     end
 
     def test_optional
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -92,7 +90,7 @@ module REXMLTests
     </optional>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       no_error( validator, %q{<A><B/></A>} )
@@ -102,7 +100,7 @@ module REXMLTests
     end
 
     def test_zero_or_more
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -113,7 +111,7 @@ module REXMLTests
     </zeroOrMore>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
       no_error( validator, %q{<A><B/></A>} )
       no_error( validator, %q{<A><B><C/></B></A>} )
@@ -121,7 +119,7 @@ module REXMLTests
       error( validator, %q{<A><B><D/></B></A>} )
       error( validator, %q{<A></A>} )
 
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -135,7 +133,7 @@ module REXMLTests
     </zeroOrMore>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       no_error( validator, %q{<A><B/></A>} )
@@ -145,7 +143,7 @@ module REXMLTests
     end
 
     def test_one_or_more
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -156,7 +154,7 @@ module REXMLTests
     </oneOrMore>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -167,13 +165,13 @@ module REXMLTests
     end
 
     def test_attribute
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <attribute name="X"/>
   <attribute name="Y"/>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -183,7 +181,7 @@ module REXMLTests
     end
 
     def test_choice_attributes
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <choice>
@@ -191,7 +189,7 @@ module REXMLTests
     <attribute name="Y"/>
   </choice>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A X="1" Y="1"/>} )
@@ -201,7 +199,7 @@ module REXMLTests
     end
 
     def test_choice_attribute_element
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <choice>
@@ -209,7 +207,7 @@ module REXMLTests
     <element name="B"/>
   </choice>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A X="1"><B/></A>} )
@@ -219,12 +217,12 @@ module REXMLTests
     end
 
     def test_empty
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <empty/>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -233,12 +231,12 @@ module REXMLTests
     end
 
     def test_text_val
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <text/>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -247,7 +245,7 @@ module REXMLTests
     end
 
     def test_choice_text
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <choice>
@@ -255,7 +253,7 @@ module REXMLTests
     <text/>
   </choice>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/>Text</A>} )
@@ -265,7 +263,7 @@ module REXMLTests
     end
 
     def test_group
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <choice>
@@ -276,7 +274,7 @@ module REXMLTests
     </group>
   </choice>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/><C/></A>} )
@@ -284,7 +282,7 @@ module REXMLTests
       no_error( validator, %q{<A><B/></A>} )
       no_error( validator, %q{<A><C/><D/></A>} )
 
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B"/>
@@ -293,7 +291,7 @@ module REXMLTests
     <element name="D"/>
   </group>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/><C/></A>} )
@@ -304,14 +302,14 @@ module REXMLTests
 
     def test_value
       # Values as text nodes
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
     <value>VaLuE</value>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B>X</B></A>} )
@@ -319,7 +317,7 @@ module REXMLTests
       no_error( validator, %q{<A><B>VaLuE</B></A>} )
 
       # Values as text nodes, via choice
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -329,7 +327,7 @@ module REXMLTests
     </choice>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -338,14 +336,14 @@ module REXMLTests
       no_error( validator, %q{<A><B>Option 2</B></A>} )
 
       # Attribute values
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <attribute name="B">
     <value>VaLuE</value>
   </attribute>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A/>} )
@@ -354,7 +352,7 @@ module REXMLTests
       no_error( validator, %q{<A B="VaLuE"/>} )
 
       # Attribute values via choice
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <attribute name="B">
@@ -364,7 +362,7 @@ module REXMLTests
     </choice>
   </attribute>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A B=""/>} )
@@ -374,7 +372,7 @@ module REXMLTests
     end
 
     def test_interleave
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -385,7 +383,7 @@ module REXMLTests
     </interleave>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B><C/></B></A>} )
@@ -398,7 +396,7 @@ module REXMLTests
     end
 
     def test_mixed
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <element name="A" xmlns="http://relaxng.org/ns/structure/1.0">
   <element name="B">
@@ -407,7 +405,7 @@ module REXMLTests
     </mixed>
   </element>
 </element>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       no_error( validator, %q{<A><B>Text<D/></B></A>} )
@@ -415,7 +413,7 @@ module REXMLTests
     end
 
     def test_ref_sequence
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -431,7 +429,7 @@ module REXMLTests
     </element>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       no_error( validator, %q{<A><B X=''/><B X=''/></A>} )
@@ -439,7 +437,7 @@ module REXMLTests
     end
 
     def test_ref_choice
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -455,7 +453,7 @@ module REXMLTests
     <element name="C"/>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><D/></A>} )
@@ -463,7 +461,7 @@ module REXMLTests
       no_error( validator, %q{<A><B/></A>} )
       no_error( validator, %q{<A><C/></A>} )
 
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -479,7 +477,7 @@ module REXMLTests
     </choice>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><D/></A>} )
@@ -487,7 +485,7 @@ module REXMLTests
       no_error( validator, %q{<A><B/></A>} )
       no_error( validator, %q{<A><C/></A>} )
 
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -504,7 +502,7 @@ module REXMLTests
     <element name="C"/>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/><C/></A>} )
@@ -515,7 +513,7 @@ module REXMLTests
 
 
     def test_ref_zero_plus
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -532,7 +530,7 @@ module REXMLTests
     </element>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -540,7 +538,7 @@ module REXMLTests
       no_error( validator, %q{<A><B X=''/></A>} )
       no_error( validator, %q{<A><B X=''/><B X=''/><B X=''/></A>} )
 
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -557,7 +555,7 @@ module REXMLTests
     </zeroOrMore>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -568,7 +566,7 @@ module REXMLTests
 
 
     def test_ref_one_plus
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -585,7 +583,7 @@ module REXMLTests
     </element>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -593,7 +591,7 @@ module REXMLTests
       no_error( validator, %q{<A><B X=''/></A>} )
       no_error( validator, %q{<A><B X=''/><B X=''/><B X=''/></A>} )
 
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -610,7 +608,7 @@ module REXMLTests
     </oneOrMore>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -620,7 +618,7 @@ module REXMLTests
     end
 
     def test_ref_interleave
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -636,7 +634,7 @@ module REXMLTests
     <element name="C"/>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -645,7 +643,7 @@ module REXMLTests
       no_error( validator, %q{<A><B/><C/></A>} )
       no_error( validator, %q{<A><C/><B/></A>} )
 
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -661,7 +659,7 @@ module REXMLTests
     </interleave>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -670,7 +668,7 @@ module REXMLTests
       no_error( validator, %q{<A><B/><C/></A>} )
       no_error( validator, %q{<A><C/><B/></A>} )
 
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -689,7 +687,7 @@ module REXMLTests
     <element name="C"/>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A><B/></A>} )
@@ -700,7 +698,7 @@ module REXMLTests
     end
 
     def test_ref_recurse
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -717,7 +715,7 @@ module REXMLTests
     </element>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       error( validator, %q{<A></A>} )
@@ -726,7 +724,7 @@ module REXMLTests
     end
 
     def test_ref_optional
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -742,7 +740,7 @@ module REXMLTests
     </element>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       no_error( validator, %q{<A></A>} )
@@ -750,7 +748,7 @@ module REXMLTests
       error( validator, %q{<A><B/><B/></A>} )
       error( validator, %q{<A><C/></A>} )
 
-      rng = %q{
+      rng = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
 <grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
@@ -766,7 +764,7 @@ module REXMLTests
     </optional>
   </define>
 </grammar>
-      }
+      XML
       validator = REXML::Validation::RelaxNG.new( rng )
 
       no_error( validator, %q{<A></A>} )
diff --git a/test/rexml/test_xml_declaration.rb b/test/rexml/test_xml_declaration.rb
index a4d97c4..6a1f4df 100644
--- a/test/rexml/test_xml_declaration.rb
+++ b/test/rexml/test_xml_declaration.rb
@@ -1,20 +1,16 @@
-# -*- coding: utf-8 -*-
 # frozen_string_literal: false
 #
 #  Created by Henrik Mårtensson on 2007-02-18.
 #  Copyright (c) 2007. All rights reserved.
 
-require "rexml/document"
-require "test/unit"
-
 module REXMLTests
   class TestXmlDeclaration < Test::Unit::TestCase
     def setup
-      xml = <<-'END_XML'
+      xml = <<~XML
       <?xml encoding= 'UTF-8' standalone='yes'?>
       <root>
       </root>
-      END_XML
+      XML
       @doc = REXML::Document.new xml
       @root = @doc.root
       @xml_declaration = @doc.children[0]
@@ -32,5 +28,18 @@ module REXMLTests
       assert_kind_of(REXML::XMLDecl, @root.previous_sibling.previous_sibling)
       assert_kind_of(REXML::Element, @xml_declaration.next_sibling.next_sibling)
     end
+
+    def test_write_prologue_quote
+      @doc.context[:prologue_quote] = :quote
+      assert_equal("<?xml version=\"1.0\" " +
+                   "encoding=\"UTF-8\" standalone=\"yes\"?>",
+                   @xml_declaration.to_s)
+    end
+
+    def test_is_writethis_attribute_copied_by_clone
+      assert_equal(true, @xml_declaration.clone.writethis)
+      @xml_declaration.nowrite
+      assert_equal(false, @xml_declaration.clone.writethis)
+    end
   end
 end
diff --git a/test/rexml/xpath/test_attribute.rb b/test/rexml/xpath/test_attribute.rb
index 9304db4..b778ff8 100644
--- a/test/rexml/xpath/test_attribute.rb
+++ b/test/rexml/xpath/test_attribute.rb
@@ -1,13 +1,11 @@
 # frozen_string_literal: false
-require 'test/unit'
-require 'rexml/document'
 
 module REXMLTests
   class TestXPathAttribute < Test::Unit::TestCase
     def setup
       @xml = <<-XML
 <?xml version="1.0" encoding="UTF-8"?>
-<root>
+<root xmlns="http://example.com/">
   <child name="one">child1</child>
   <child name="two">child2</child>
   <child name="three">child3</child>
@@ -26,5 +24,13 @@ module REXMLTests
       children = REXML::XPath.each(@document, "/root/child[@name='two']")
       assert_equal(["child2"], children.collect(&:text))
     end
+
+    def test_no_namespace
+      children = REXML::XPath.match(@document,
+                                    "/root/child[@nothing:name='two']",
+                                    "" => "http://example.com/",
+                                    "nothing" => "")
+      assert_equal(["child2"], children.collect(&:text))
+    end
   end
 end
diff --git a/test/rexml/xpath/test_axis_preceding_sibling.rb b/test/rexml/xpath/test_axis_preceding_sibling.rb
index 5842c6b..9c44ad6 100644
--- a/test/rexml/xpath/test_axis_preceding_sibling.rb
+++ b/test/rexml/xpath/test_axis_preceding_sibling.rb
@@ -1,6 +1,4 @@
 # frozen_string_literal: false
-require "test/unit/testcase"
-require "rexml/document"
 
 module REXMLTests
   class TestXPathAxisPredcedingSibling < Test::Unit::TestCase
diff --git a/test/rexml/xpath/test_axis_self.rb b/test/rexml/xpath/test_axis_self.rb
new file mode 100644
index 0000000..4e422f5
--- /dev/null
+++ b/test/rexml/xpath/test_axis_self.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: false
+
+module REXMLTests
+  class TestXPathAxisSelf < Test::Unit::TestCase
+    def test_only
+      doc = REXML::Document.new("<root><child/></root>")
+      assert_equal([doc.root],
+                   REXML::XPath.match(doc.root, "."))
+    end
+
+    def test_have_predicate
+      doc = REXML::Document.new("<root><child/></root>")
+      error = assert_raise(REXML::ParseException) do
+        REXML::XPath.match(doc.root, ".[child]")
+      end
+      assert_equal("Garbage component exists at the end: <[child]>: <.[child]>",
+                   error.message)
+    end
+  end
+end
diff --git a/test/rexml/xpath/test_base.rb b/test/rexml/xpath/test_base.rb
index 5079fdd..1dacd69 100644
--- a/test/rexml/xpath/test_base.rb
+++ b/test/rexml/xpath/test_base.rb
@@ -1,11 +1,8 @@
 # frozen_string_literal: false
-require_relative "../rexml_test_utils"
-
-require "rexml/document"
 
 module REXMLTests
   class TestXPathBase < Test::Unit::TestCase
-    include REXMLTestUtils
+    include Helper::Fixture
     include REXML
     SOURCE = <<-EOF
       <a id='1'>
@@ -369,11 +366,15 @@ module REXMLTests
       assert_equal 2, c
     end
 
+    def match(xpath)
+      XPath.match(@@doc, xpath).collect(&:to_s)
+    end
+
     def test_grouping
-      t = XPath.first( @@doc, "a/d/*[name()='d' and (name()='f' or name()='q')]" )
-      assert_nil t
-      t = XPath.first( @@doc, "a/d/*[(name()='d' and name()='f') or name()='q']" )
-      assert_equal 'q', t.name
+      assert_equal([],
+                   match("a/d/*[name()='d' and (name()='f' or name()='q')]"))
+      assert_equal(["<q id='19'/>"],
+                   match("a/d/*[(name()='d' and name()='f') or name()='q']"))
     end
 
     def test_preceding
@@ -450,6 +451,46 @@ module REXMLTests
     #  puts results
     #end
 
+    def test_nested_predicates
+      doc = Document.new <<-EOF
+        <div>
+          <div>
+            <test>ab</test>
+            <test>cd</test>
+          </div>
+          <div>
+            <test>ef</test>
+            <test>gh</test>
+          </div>
+          <div>
+            <test>hi</test>
+          </div>
+        </div>
+      EOF
+
+      matches = XPath.match(doc, '(/div/div/test[0])').map(&:text)
+      assert_equal [], matches
+      matches = XPath.match(doc, '(/div/div/test[1])').map(&:text)
+      assert_equal ["ab", "ef", "hi"], matches
+      matches = XPath.match(doc, '(/div/div/test[2])').map(&:text)
+      assert_equal ["cd", "gh"], matches
+      matches = XPath.match(doc, '(/div/div/test[3])').map(&:text)
+      assert_equal [], matches
+
+      matches = XPath.match(doc, '(/div/div/test[1])[1]').map(&:text)
+      assert_equal ["ab"], matches
+      matches = XPath.match(doc, '(/div/div/test[1])[2]').map(&:text)
+      assert_equal ["ef"], matches
+      matches = XPath.match(doc, '(/div/div/test[1])[3]').map(&:text)
+      assert_equal ["hi"], matches
+      matches = XPath.match(doc, '(/div/div/test[2])[1]').map(&:text)
+      assert_equal ["cd"], matches
+      matches = XPath.match(doc, '(/div/div/test[2])[2]').map(&:text)
+      assert_equal ["gh"], matches
+      matches = XPath.match(doc, '(/div/div/test[2])[3]').map(&:text)
+      assert_equal [], matches
+    end
+
     # Contributed by Mike Stok
     def test_starts_with
       source = <<-EOF
@@ -610,7 +651,7 @@ module REXMLTests
       source = "<a><b id='1'/><b id='2'/><b id='3'/></a>"
       doc = REXML::Document.new(source)
 
-      # NOTE TO SER: check that number() is required
+      # NOTE: check that number() is required
       assert_equal 2, REXML::XPath.match(doc, "//b[number(@id) > 1]").size
       assert_equal 3, REXML::XPath.match(doc, "//b[number(@id) >= 1]").size
       assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) <= 1]").size
@@ -632,29 +673,36 @@ module REXMLTests
           <c id='a'/>
         </b>
         <c id='b'/>
+        <c id='c'/>
+        <c/>
       </a>")
-      assert_equal( 1, REXML::XPath.match(doc,
-        "//*[local-name()='c' and @id='b']").size )
-      assert_equal( 1, REXML::XPath.match(doc,
-        "//*[ local-name()='c' and @id='b' ]").size )
-      assert_equal( 1, REXML::XPath.match(doc,
-        "//*[ local-name() = 'c' and @id = 'b' ]").size )
-      assert_equal( 1,
-        REXML::XPath.match(doc, '/a/c[@id]').size )
-      assert_equal( 1,
-        REXML::XPath.match(doc, '/a/c[(@id)]').size )
-      assert_equal( 1,
-        REXML::XPath.match(doc, '/a/c[ @id ]').size )
-      assert_equal( 1,
-        REXML::XPath.match(doc, '/a/c[ (@id) ]').size )
-      assert_equal( 1,
-        REXML::XPath.match(doc, '/a/c[( @id )]').size )
-      assert_equal( 1, REXML::XPath.match(doc.root,
-        '/a/c[ ( @id ) ]').size )
-      assert_equal( 1, REXML::XPath.match(doc,
-        '/a/c [ ( @id ) ] ').size )
-      assert_equal( 1, REXML::XPath.match(doc,
-        ' / a / c [ ( @id ) ] ').size )
+      match = lambda do |xpath|
+        REXML::XPath.match(doc, xpath).collect(&:to_s)
+      end
+      assert_equal(["<c id='b'/>"],
+                   match.call("//*[local-name()='c' and @id='b']"))
+      assert_equal(["<c id='b'/>"],
+                   match.call("//*[ local-name()='c' and @id='b' ]"))
+      assert_equal(["<c id='b'/>"],
+                   match.call("//*[ local-name() = 'c' and @id = 'b' ]"))
+      assert_equal(["<c id='b'/>", "<c id='c'/>"],
+                   match.call('/a/c[@id]'))
+      assert_equal(["<c id='b'/>", "<c id='c'/>"],
+                   match.call('/a/c[(@id)]'))
+      assert_equal(["<c id='b'/>", "<c id='c'/>"],
+                   match.call('/a/c[ @id ]'))
+      assert_equal(["<c id='b'/>", "<c id='c'/>"],
+                   match.call('/a/c[ (@id) ]'))
+      assert_equal(["<c id='b'/>", "<c id='c'/>"],
+                   match.call('/a/c[( @id )]'))
+      assert_equal(["<c id='b'/>", "<c id='c'/>"],
+                   match.call('/a/c[ ( @id ) ]'))
+      assert_equal(["<c id='b'/>", "<c id='c'/>"],
+                   match.call('/a/c [ ( @id ) ] '))
+      assert_equal(["<c id='b'/>", "<c id='c'/>"],
+                   match.call(' / a / c [ ( @id ) ] '))
+      assert_equal(["<c id='b'/>", "<c id='c'/>"],
+                   match.call('/ a / child:: c [( @id )] /'))
     end
 
     def test_text_nodes
@@ -692,11 +740,22 @@ module REXMLTests
     end
 
     def test_ordering
-      source = "<a><b><c id='1'/><c id='2'/></b><b><d id='1'/><d id='2'/></b></a>"
+      source = <<-XML
+<a>
+  <b>
+    <c id='1'/>
+    <c id='2'/>
+  </b>
+  <b>
+    <d id='3'/>
+    <d id='4'/>
+  </b>
+</a>
+      XML
       d = REXML::Document.new( source )
       r = REXML::XPath.match( d, %q{/a/*/*[1]} )
-      assert_equal( 1, r.size )
-      r.each { |el| assert_equal( '1', el.attribute('id').value ) }
+      assert_equal(["1", "3"],
+                   r.collect {|element| element.attribute("id").value})
     end
 
     def test_descendant_or_self_ordering
@@ -830,31 +889,44 @@ module REXMLTests
 </a>
       EOL
       d = REXML::Document.new( string )
-      c1 = XPath.match( d, '/a/*/*[1]' )
-      assert_equal( 1, c1.length )
-      assert_equal( 'c1', c1[0].name )
+      cs = XPath.match( d, '/a/*/*[1]' )
+      assert_equal(["c1", "c2"], cs.collect(&:name))
     end
 
     def test_sum
-      d = Document.new("<a>"+
-      "<b>1</b><b>2</b><b>3</b>"+
-      "<c><d>1</d><d>2</d></c>"+
-      "<e att='1'/><e att='2'/>"+
-      "</a>")
-
-      for v,p in [[6, "sum(/a/b)"],
-        [9, "sum(//b | //d)"],
-        [3, "sum(/a/e/@*)"] ]
-        assert_equal( v, XPath::match( d, p ).first )
-      end
+      d = Document.new(<<-XML)
+<a>
+  <b>1</b>
+  <b>2</b>
+  <b>3</b>
+  <c>
+    <d>1</d>
+    <d>2</d>
+  </c>
+  <e att='1'/>
+  <e att='2'/>
+</a>
+      XML
+
+      assert_equal([6], XPath::match(d, "sum(/a/b)"))
+      assert_equal([9], XPath::match(d, "sum(//b | //d)"))
+      assert_equal([3], XPath::match(d, "sum(/a/e/@*)"))
     end
 
     def test_xpath_namespace
-      d = REXML::Document.new("<tag1 xmlns='ns1'><tag2 xmlns='ns2'/><tada>xa</tada></tag1>")
-      x = d.root
-      num = 0
-      x.each_element('tada') {  num += 1 }
-      assert_equal(1, num)
+      d = REXML::Document.new(<<-XML)
+<tag1 xmlns='ns1'>
+  <tag2 xmlns='ns2'/>
+  <tada>xa</tada>
+  <tada xmlns=''>xb</tada>
+</tag1>
+      XML
+      actual = []
+      d.root.each_element('tada') do |element|
+        actual << element.to_s
+      end
+      assert_equal(["<tada>xa</tada>", "<tada xmlns=''>xb</tada>"],
+                   actual)
     end
 
     def test_ticket_39
@@ -990,7 +1062,7 @@ EOF
       </a>"
       d = Document.new(data)
       res = d.elements.to_a( "//c" ).collect {|e| e.attributes['id'].to_i}
-      assert_equal( res, res.sort )
+      assert_equal((1..12).to_a, res)
     end
 
     def ticket_61_fixture(doc, xpath)
diff --git a/test/rexml/xpath/test_compare.rb b/test/rexml/xpath/test_compare.rb
new file mode 100644
index 0000000..11d11e5
--- /dev/null
+++ b/test/rexml/xpath/test_compare.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: false
+
+module REXMLTests
+  class TestXPathCompare < Test::Unit::TestCase
+    def match(xml, xpath)
+      document = REXML::Document.new(xml)
+      REXML::XPath.match(document, xpath)
+    end
+
+    class TestEqual < self
+      class TestNodeSet < self
+        def test_boolean_true
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child/>
+  <child/>
+</root>
+          XML
+          assert_equal([true],
+                       match(xml, "/root/child=true()"))
+        end
+
+        def test_boolean_false
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+</root>
+        XML
+          assert_equal([false],
+                       match(xml, "/root/child=true()"))
+        end
+
+        def test_number_true
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child>100</child>
+  <child>200</child>
+</root>
+          XML
+          assert_equal([true],
+                       match(xml, "/root/child=100"))
+        end
+
+        def test_number_false
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child>100</child>
+  <child>200</child>
+</root>
+          XML
+          assert_equal([false],
+                       match(xml, "/root/child=300"))
+        end
+
+        def test_string_true
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child>text</child>
+  <child>string</child>
+</root>
+          XML
+          assert_equal([true],
+                       match(xml, "/root/child='string'"))
+        end
+
+        def test_string_false
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child>text</child>
+  <child>string</child>
+</root>
+          XML
+          assert_equal([false],
+                       match(xml, "/root/child='nonexistent'"))
+        end
+      end
+
+      class TestBoolean < self
+        def test_number_true
+          xml = "<root/>"
+          assert_equal([true],
+                       match(xml, "true()=1"))
+        end
+
+        def test_number_false
+          xml = "<root/>"
+          assert_equal([false],
+                       match(xml, "true()=0"))
+        end
+
+        def test_string_true
+          xml = "<root/>"
+          assert_equal([true],
+                       match(xml, "true()='string'"))
+        end
+
+        def test_string_false
+          xml = "<root/>"
+          assert_equal([false],
+                       match(xml, "true()=''"))
+        end
+      end
+
+      class TestNumber < self
+        def test_string_true
+          xml = "<root/>"
+          assert_equal([true],
+                       match(xml, "1='1'"))
+        end
+
+        def test_string_false
+          xml = "<root/>"
+          assert_equal([false],
+                       match(xml, "1='2'"))
+        end
+      end
+    end
+
+    class TestGreaterThan < self
+      class TestNodeSet < self
+        def test_boolean_truex
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child/>
+</root>
+          XML
+          assert_equal([true],
+                       match(xml, "/root/child>false()"))
+        end
+
+        def test_boolean_false
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child/>
+</root>
+        XML
+          assert_equal([false],
+                       match(xml, "/root/child>true()"))
+        end
+
+        def test_number_true
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child>100</child>
+  <child>200</child>
+</root>
+          XML
+          assert_equal([true],
+                       match(xml, "/root/child>199"))
+        end
+
+        def test_number_false
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child>100</child>
+  <child>200</child>
+</root>
+          XML
+          assert_equal([false],
+                       match(xml, "/root/child>200"))
+        end
+
+        def test_string_true
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child>100</child>
+  <child>200</child>
+</root>
+          XML
+          assert_equal([true],
+                       match(xml, "/root/child>'199'"))
+        end
+
+        def test_string_false
+          xml = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <child>100</child>
+  <child>200</child>
+</root>
+          XML
+          assert_equal([false],
+                       match(xml, "/root/child>'200'"))
+        end
+      end
+
+      class TestBoolean < self
+        def test_string_true
+          xml = "<root/>"
+          assert_equal([true],
+                       match(xml, "true()>'0'"))
+        end
+
+        def test_string_false
+          xml = "<root/>"
+          assert_equal([false],
+                       match(xml, "true()>'1'"))
+        end
+      end
+
+      class TestNumber < self
+        def test_boolean_true
+          xml = "<root/>"
+          assert_equal([true],
+                       match(xml, "true()>0"))
+        end
+
+        def test_number_false
+          xml = "<root/>"
+          assert_equal([false],
+                       match(xml, "true()>1"))
+        end
+
+        def test_string_true
+          xml = "<root/>"
+          assert_equal([true],
+                       match(xml, "1>'0'"))
+        end
+
+        def test_string_false
+          xml = "<root/>"
+          assert_equal([false],
+                       match(xml, "1>'1'"))
+        end
+      end
+
+      class TestString < self
+        def test_string_true
+          xml = "<root/>"
+          assert_equal([true],
+                       match(xml, "'1'>'0'"))
+        end
+
+        def test_string_false
+          xml = "<root/>"
+          assert_equal([false],
+                       match(xml, "'1'>'1'"))
+        end
+      end
+    end
+  end
+end
diff --git a/test/rexml/xpath/test_node.rb b/test/rexml/xpath/test_node.rb
index e0e958e..742bfbb 100644
--- a/test/rexml/xpath/test_node.rb
+++ b/test/rexml/xpath/test_node.rb
@@ -1,10 +1,6 @@
 # -*- coding: utf-8 -*-
 # frozen_string_literal: false
 
-require_relative "../rexml_test_utils"
-
-require "rexml/document"
-
 module REXMLTests
   class TestXPathNode < Test::Unit::TestCase
     def matches(xml, xpath)
diff --git a/test/rexml/xpath/test_predicate.rb b/test/rexml/xpath/test_predicate.rb
index ce1aaa3..278e376 100644
--- a/test/rexml/xpath/test_predicate.rb
+++ b/test/rexml/xpath/test_predicate.rb
@@ -1,13 +1,12 @@
 # frozen_string_literal: false
-require "test/unit/testcase"
-require "rexml/document"
+
 require "rexml/xpath"
 require "rexml/parsers/xpathparser"
 
 module REXMLTests
   class TestXPathPredicate < Test::Unit::TestCase
     include REXML
-    SRC=<<-EOL
+    SRC=<<~EOL
     <article>
        <section role="subdivision" id="1">
           <para>free flowing text.</para>
@@ -29,6 +28,15 @@ module REXMLTests
 
     end
 
+    def test_predicate_only
+      error = assert_raise(REXML::ParseException) do
+        do_path("[article]")
+      end
+      assert_equal("Garbage component exists at the end: " +
+                   "<[article]>: <[article]>",
+                   error.message)
+    end
+
     def test_predicates_parent
       path = '//section[../self::section[@role="division"]]'
       m = do_path( path )
diff --git a/test/rexml/xpath/test_text.rb b/test/rexml/xpath/test_text.rb
index 7222388..dccc4c8 100644
--- a/test/rexml/xpath/test_text.rb
+++ b/test/rexml/xpath/test_text.rb
@@ -1,6 +1,5 @@
 # frozen_string_literal: false
-require 'test/unit'
-require 'rexml/document'
+
 require 'rexml/element'
 require 'rexml/xpath'
 
-- 
2.27.0

