rubyjedi-testunitxml 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +19 -0
- data/MIT-LICENSE +21 -0
- data/README +119 -0
- data/lib/test/unit/xml/attributes_mixin.rb +21 -0
- data/lib/test/unit/xml/conditionals.rb +147 -0
- data/lib/test/unit/xml/doctype_mixin.rb +54 -0
- data/lib/test/unit/xml/nodeiterator.rb +67 -0
- data/lib/test/unit/xml/notationdecl_mixin.rb +54 -0
- data/lib/test/unit/xml/xml_assertions.rb +163 -0
- data/lib/test/unit/xml/xmlequalfilter.rb +31 -0
- data/lib/test/unit/xml.rb +21 -0
- data/test/data/test1.xml +23 -0
- data/test/tc_attributes_mixin.rb +37 -0
- data/test/tc_doctype_mixin.rb +71 -0
- data/test/tc_notationdecl_mixin.rb +68 -0
- data/test/tc_testunitxml.rb +315 -0
- data/test/tc_testunitxml2.rb +34 -0
- data/test/ts_testunitxml.rb +12 -0
- metadata +79 -0
data/CHANGES
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= Changes
|
2
|
+
|
3
|
+
== Version 0.1.5
|
4
|
+
|
5
|
+
* Fixed a bug when comparing attributes containing entity references.
|
6
|
+
* Added an <tt>assert_xml_not_equal</tt> method.
|
7
|
+
|
8
|
+
== Version 0.1.4
|
9
|
+
|
10
|
+
* Added support for Doctype comparisons in +assert_xml_equal+
|
11
|
+
* Added <tt>setup.rb</tt> file to distribution packages
|
12
|
+
* Added installation section to README file
|
13
|
+
* Added links to online tutorial to README file
|
14
|
+
* Added CHANGES file
|
15
|
+
|
16
|
+
|
17
|
+
== Version 0.1.3
|
18
|
+
|
19
|
+
The initial release.
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
= MIT-LICENSE
|
2
|
+
|
3
|
+
Copyright � 2006 Henrik M�rtensson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
6
|
+
copy of this software and associated documentation files (the "Software"),
|
7
|
+
to deal in the Software without restriction, including without limitation
|
8
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
9
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
10
|
+
Software is furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
18
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
+
IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
= Test::Unit::XML
|
2
|
+
== An XML Test Framework
|
3
|
+
|
4
|
+
Version: 0.1.5
|
5
|
+
|
6
|
+
Author: Henrik M�rtensson
|
7
|
+
|
8
|
+
(c) 2006 by Henrik M�rtensson
|
9
|
+
|
10
|
+
== Introduction
|
11
|
+
|
12
|
+
Test::Unit::XML extends the Test::Unit framework with
|
13
|
+
an assertion for testing well-formed XML documents.
|
14
|
+
|
15
|
+
Using Test::Unit::XML is easy. All you have to do is to
|
16
|
+
require +testunitxml+, and you will then have an
|
17
|
+
+assert_xml_equal+ assertion available in the
|
18
|
+
Test::Unit::TestCase class.
|
19
|
+
|
20
|
+
In addition to the API documentation included in the package, you can get information about how
|
21
|
+
to use Test::Unit::XML from the following sources:
|
22
|
+
|
23
|
+
* The online tutorial[http://kallokain.blogspot.com/2006/01/testunitxml-quick-start-tutorial.html] at
|
24
|
+
the Kallokain[http://kallokain.blogspot.com/] blog.
|
25
|
+
* The {test cases}[link:../../test/] included in the distribution package.
|
26
|
+
|
27
|
+
== Installation
|
28
|
+
|
29
|
+
=== Install Using the +RubyGem+ Package Manager
|
30
|
+
|
31
|
+
The easiest way to install Test::Unit::XML is to make a remote installation via the RubyGem
|
32
|
+
package manager:
|
33
|
+
|
34
|
+
<tt>gem install testunitxml</tt>
|
35
|
+
|
36
|
+
If you have downloaded a gem package from Rubyforge, you can do a local installation:
|
37
|
+
|
38
|
+
<tt>cd <em>download_directory_path</em></tt>
|
39
|
+
|
40
|
+
<tt>gem install testunitxml -l</tt>
|
41
|
+
|
42
|
+
=== Install from a Zip file or Tarball
|
43
|
+
|
44
|
+
If you do not have RubyGem installed, you can download a Zip file or tarball and install
|
45
|
+
from it instead:
|
46
|
+
|
47
|
+
1:: Unpack the Zip or tarball archive.
|
48
|
+
2:: cd to the directory you just unpacked.
|
49
|
+
3:: Run the command:
|
50
|
+
<tt>ruby setup.rb install</tt>
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
== What Does _Equal_ XML Documents Mean?
|
55
|
+
|
56
|
+
It is hard to define exactly what _equal_ means in the
|
57
|
+
context of XML documents. I have tried to follow W3C
|
58
|
+
XML recommendations as far as possible. There are a
|
59
|
+
few things worthy of note:
|
60
|
+
|
61
|
+
* Namespace _declarations_, i.e. attributes that declare
|
62
|
+
namespaces, are ignored for comparison purposes. (The
|
63
|
+
namespaces _are_ used in comparisons.) The reason is
|
64
|
+
that XML processors may move the declarations from one
|
65
|
+
element to another, or change prefixes in ways that
|
66
|
+
cannot be directly controlled by a programmer. For
|
67
|
+
example, two different XSLT processors could use
|
68
|
+
the same stylesheet and produce XML documents that use
|
69
|
+
different namespace prefixes, and have declarations
|
70
|
+
on different elements, but are still considered equal.
|
71
|
+
|
72
|
+
* Text nodes that are empty or contain only whitespace
|
73
|
+
are ignored for comparison purposes. This makes it
|
74
|
+
easier to test output from various transformation
|
75
|
+
engines. These often produce extraneous whitespace.
|
76
|
+
|
77
|
+
== The Future
|
78
|
+
|
79
|
+
There are a few things in the pipeline:
|
80
|
+
|
81
|
+
* assert_xml_equal_structure - checks that the structure
|
82
|
+
of two documents are equal, but ignores content, attributes,
|
83
|
+
processing istructions, comments, CDATA, and doctype
|
84
|
+
declarations.
|
85
|
+
* assert_xml_similar - Like assert_xml_equal, but ignores the
|
86
|
+
order of child elements.
|
87
|
+
* Configurability. It _might_ be useful to be able to set
|
88
|
+
configuration options for testing. I'll have to think a
|
89
|
+
bit about this though.
|
90
|
+
* Document difference functions, like the Java XMLUnit test
|
91
|
+
suite.
|
92
|
+
|
93
|
+
I plan to implement these features as I need them in other
|
94
|
+
projects, so there is no time plan, and no guarantee as to the
|
95
|
+
order in which I'll implement anything.
|
96
|
+
|
97
|
+
== License
|
98
|
+
|
99
|
+
See the {MIT-LICENSE}[link:files/MIT-LICENSE.html] file.
|
100
|
+
|
101
|
+
== Contact
|
102
|
+
|
103
|
+
You can email bug reports, opinions and questions to
|
104
|
+
mailto:self@henrikmartensson.org. You may also wish to visit
|
105
|
+
my home page, www.henrikmartensson.org, for more information
|
106
|
+
about Test::Unit::XML and other projects. I will write about
|
107
|
+
Test::Unit::XML at the the Kallokain[http://kallokain.blogspot.com/]
|
108
|
+
blog. You are welcome to visit, and comment.
|
109
|
+
|
110
|
+
If you find Test::Unit::XML useful, please do tell me about it. I would like
|
111
|
+
to list projects that use it on the {Test::Unit::XML web site}[http://testunitxml.rubyforge.org/].
|
112
|
+
|
113
|
+
If you find Test::Unit::XML lacking in some respect, or buggy,
|
114
|
+
I am even more interested. I can't fix bugs I do not know about.
|
115
|
+
|
116
|
+
Finally, if you write about Test::Unit::XML, I'd like to link to
|
117
|
+
the article on my web site, or at least mention it if you write
|
118
|
+
for a magazine, so please tell me.
|
119
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module REXML
|
2
|
+
|
3
|
+
# The REXML::Attributes mix-in adds methods that are useful for
|
4
|
+
# attribute collections, but not present in the standard
|
5
|
+
# REXML::Attributes class
|
6
|
+
class Attributes
|
7
|
+
|
8
|
+
# The +get_attribute_ns+ method retrieves a method by its namespace
|
9
|
+
# and name. Thus it is possible to reliably identify an attribute
|
10
|
+
# even if an XML processor has changed the prefix.
|
11
|
+
def get_attribute_ns(namespace, name)
|
12
|
+
each_attribute() { |attribute|
|
13
|
+
if name == attribute.name &&
|
14
|
+
namespace == attribute.namespace()
|
15
|
+
return attribute
|
16
|
+
end
|
17
|
+
}
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
|
2
|
+
module Test
|
3
|
+
module Unit
|
4
|
+
module XML
|
5
|
+
|
6
|
+
# This singleton class compares all types of REXML nodes.
|
7
|
+
class Conditionals
|
8
|
+
|
9
|
+
private_class_method :new
|
10
|
+
@@conditionals = nil
|
11
|
+
|
12
|
+
# The +create+ method is used to create a singleton instance
|
13
|
+
# of the Conditionals class.
|
14
|
+
def Conditionals.create
|
15
|
+
@@conditionals = new unless @@conditionals
|
16
|
+
@@conditionals
|
17
|
+
end
|
18
|
+
|
19
|
+
# The method compares two REXML nodes representing an XML document,
|
20
|
+
# or part of a document. If the nodes are equal, the method returns
|
21
|
+
# +true+. If the nodes are not equal, the method returns +false+.
|
22
|
+
# If the nodes have child nodes, for example if the nodes are
|
23
|
+
# +Element+ nodes with content, they will _not_ be recursively compared.
|
24
|
+
def compare_xml_nodes(expected_node, actual_node)
|
25
|
+
#puts "Conditionals, Expected: #{expected_node.class} : #{expected_node.name}"
|
26
|
+
#puts "Conditionals, Expected: #{actual_node.class} : #{actual_node.name}"
|
27
|
+
return false unless actual_node.instance_of? expected_node.class
|
28
|
+
return false if expected_node == nil && actual_node != nil
|
29
|
+
return false if expected_node != nil && actual_node == nil
|
30
|
+
#puts "actual_node is nil" unless actual_node
|
31
|
+
#puts "expected_node is nil" unless expected_node
|
32
|
+
case actual_node
|
33
|
+
when REXML::Document
|
34
|
+
# TODO: Implement Document comparison
|
35
|
+
true
|
36
|
+
when REXML::DocType
|
37
|
+
compare_doctypes(expected_node, actual_node)
|
38
|
+
when REXML::Element
|
39
|
+
compare_elements(expected_node, actual_node)
|
40
|
+
when REXML::CData
|
41
|
+
compare_texts(expected_node, actual_node)
|
42
|
+
when REXML::Text
|
43
|
+
compare_texts(expected_node, actual_node)
|
44
|
+
when REXML::Comment
|
45
|
+
compare_comments(expected_node, actual_node)
|
46
|
+
when REXML::Instruction
|
47
|
+
compare_pi(expected_node, actual_node)
|
48
|
+
when REXML::XMLDecl
|
49
|
+
compare_xml_declaration(expected_node, actual_node)
|
50
|
+
#when REXML::Entity
|
51
|
+
# compare_xml_entities(expected_node, actual_node)
|
52
|
+
else
|
53
|
+
puts "Unknown node type #{actual_node.class}"
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def compare_doctypes(expected_node, actual_node)
|
61
|
+
return compare_system_id(expected_node.system, actual_node.system) &&
|
62
|
+
expected_node.public == actual_node.public &&
|
63
|
+
compare_xml_internal_dtd_subset(expected_node, actual_node)
|
64
|
+
end
|
65
|
+
|
66
|
+
def compare_system_id(expected_id, actual_id)
|
67
|
+
is_expected_urn = expected_id =~ /^urn:/i
|
68
|
+
is_actual_urn = actual_id =~ /^urn:/i
|
69
|
+
if is_expected_urn || is_actual_urn
|
70
|
+
expected_id == actual_id
|
71
|
+
else
|
72
|
+
true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def compare_elements(expected_node, actual_node)
|
77
|
+
return expected_node.name == actual_node.name &&
|
78
|
+
expected_node.namespace() == actual_node.namespace() &&
|
79
|
+
compare_attributes(expected_node.attributes, actual_node.attributes)
|
80
|
+
end
|
81
|
+
|
82
|
+
def compare_attributes(expected_attributes, actual_attributes)
|
83
|
+
return false unless attribute_count(expected_attributes) == attribute_count(actual_attributes)
|
84
|
+
expected_attributes.each_attribute do |expected_attribute|
|
85
|
+
expected_prefix = expected_attribute.prefix()
|
86
|
+
unless expected_prefix == 'xmlns' then
|
87
|
+
expected_name = expected_attribute.name
|
88
|
+
expected_namespace = expected_attribute.namespace
|
89
|
+
actual_attribute = actual_attributes.get_attribute_ns(expected_namespace, expected_name)
|
90
|
+
return false unless actual_attribute
|
91
|
+
return false if expected_attribute.value() != actual_attribute.value()
|
92
|
+
end
|
93
|
+
end
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
def attribute_count(attributes)
|
98
|
+
# Do not count namespace declarations
|
99
|
+
attributes.size - attributes.prefixes.size
|
100
|
+
end
|
101
|
+
|
102
|
+
def compare_texts(expected_node, actual_node)
|
103
|
+
expected_node.value.eql?(actual_node.value)
|
104
|
+
end
|
105
|
+
|
106
|
+
def compare_comments(expected_node, actual_node)
|
107
|
+
expected_node == actual_node
|
108
|
+
end
|
109
|
+
|
110
|
+
def compare_pi(expected_pi, actual_pi)
|
111
|
+
return expected_pi.target == actual_pi.target &&
|
112
|
+
expected_pi.content == actual_pi.content
|
113
|
+
end
|
114
|
+
|
115
|
+
def compare_xml_declaration(expected_decl, actual_decl)
|
116
|
+
return expected_decl == actual_decl
|
117
|
+
end
|
118
|
+
|
119
|
+
def compare_xml_internal_dtd_subset(expected_node, actual_node)
|
120
|
+
expected_subset = expected_node.children()
|
121
|
+
actual_subset = actual_node.children()
|
122
|
+
return false unless expected_subset.length == actual_subset.length
|
123
|
+
expected_subset.inject(true) { |memo, expected_decl|
|
124
|
+
case expected_decl
|
125
|
+
when REXML::Entity
|
126
|
+
memo &&
|
127
|
+
expected_decl.value == actual_node.entities[expected_decl.name].value &&
|
128
|
+
expected_decl.ndata == actual_node.entities[expected_decl.name].ndata
|
129
|
+
when REXML::NotationDecl
|
130
|
+
actual_notation_decl = actual_node.notation(expected_decl.name)
|
131
|
+
memo &&
|
132
|
+
actual_notation_decl != nil &&
|
133
|
+
expected_decl.name == actual_notation_decl.name &&
|
134
|
+
expected_decl.public == actual_notation_decl.public &&
|
135
|
+
expected_decl.system == actual_notation_decl.system
|
136
|
+
when REXML::Comment
|
137
|
+
true
|
138
|
+
else
|
139
|
+
raise "Unexpected node type in internal DTD subset of expected document: " + expected_decl.inspect
|
140
|
+
end
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
require 'test/unit/xml/notationdecl_mixin'
|
3
|
+
|
4
|
+
module REXML
|
5
|
+
|
6
|
+
# The REXML::DocType mix-in adds methods that are useful for
|
7
|
+
# Doctype declarations, but not present in the standard
|
8
|
+
# REXML::DocType class
|
9
|
+
class DocType
|
10
|
+
|
11
|
+
# This method retrieves the public identifier identifying the document's DTD.
|
12
|
+
def public
|
13
|
+
case @external_id
|
14
|
+
when "SYSTEM"
|
15
|
+
nil
|
16
|
+
when "PUBLIC"
|
17
|
+
strip_quotes(@long_name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# This method retrieves the system identifier identifying the document's DTD
|
22
|
+
def system
|
23
|
+
case @external_id
|
24
|
+
when "SYSTEM"
|
25
|
+
strip_quotes(@long_name)
|
26
|
+
when "PUBLIC"
|
27
|
+
@uri.kind_of?(String) ? strip_quotes(@uri) : nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method returns a list of notations that have been declared in the
|
32
|
+
# _internal_ DTD subset. Notations in the external DTD subset are not listed.
|
33
|
+
def notations
|
34
|
+
children().select {|node| node.kind_of?(REXML::NotationDecl)}
|
35
|
+
end
|
36
|
+
|
37
|
+
# Retrieves a named notation. Only notations declared in the internal
|
38
|
+
# DTD subset can be retrieved.
|
39
|
+
def notation(name)
|
40
|
+
notations.find { |notation_decl|
|
41
|
+
notation_decl.name == name
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def strip_quotes(quoted_string)
|
48
|
+
quoted_string =~ /^[\'\"].*[\�\"]$/ ?
|
49
|
+
quoted_string[1, quoted_string.length-2] :
|
50
|
+
quoted_string
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#! /usr/bin/ruby
|
2
|
+
|
3
|
+
module Test
|
4
|
+
module Unit
|
5
|
+
module XML
|
6
|
+
class NodeIterator
|
7
|
+
|
8
|
+
class NullNodeFilter
|
9
|
+
def accept(node)
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# This class method takes a node as an argument and locates the
|
15
|
+
# next node. The first argument is a node. The second argument
|
16
|
+
# is an optional node filter.
|
17
|
+
def NodeIterator.find_next_node(node, node_filter = NullNodeFilter.new)
|
18
|
+
#puts "In NodeIterator: #{node.class}"
|
19
|
+
#puts " has_children: #{NodeIterator.has_children?(node)}"
|
20
|
+
#puts " next_sibling_node: #{node.next_sibling_node}"
|
21
|
+
#puts " has_parent_with_sibling?: #{NodeIterator.has_parent_with_sibling?(node)}"
|
22
|
+
#node.write if node.respond_to?(:name) && node.name == 'TestThing'
|
23
|
+
return nil unless node
|
24
|
+
next_node = nil
|
25
|
+
if NodeIterator.has_children?(node) then
|
26
|
+
next_node = node[0] # The index should be 1 according to the REXML docs
|
27
|
+
elsif node.next_sibling_node
|
28
|
+
next_node = node.next_sibling_node
|
29
|
+
elsif NodeIterator.has_parent_with_sibling?(node)
|
30
|
+
next_node = node.parent.next_sibling_node
|
31
|
+
end
|
32
|
+
return next_node if node_filter.accept(next_node) || next_node == nil
|
33
|
+
find_next_node(next_node, node_filter)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def initialize(node, node_filter = NullNodeFilter.new)
|
38
|
+
@node_filter = node_filter
|
39
|
+
@next_node = node
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_next()
|
43
|
+
@next_node ? true : false
|
44
|
+
end
|
45
|
+
|
46
|
+
def next
|
47
|
+
node = @next_node
|
48
|
+
@next_node = NodeIterator.find_next_node(node, @node_filter)
|
49
|
+
node
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def NodeIterator.has_children?(node)
|
58
|
+
node.respond_to?(:[]) && node.respond_to?(:size) && node.size > 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def NodeIterator.has_parent_with_sibling?(node)
|
62
|
+
node.parent && node.parent.next_sibling_node
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module REXML
|
2
|
+
|
3
|
+
# The REXML::NotationDecl mix-in adds methods that are useful for
|
4
|
+
# notation declarations, but not present in the standard
|
5
|
+
# REXML::NotationDecl class
|
6
|
+
class NotationDecl
|
7
|
+
|
8
|
+
# This method retrieves the name of the notation.
|
9
|
+
def name
|
10
|
+
@name
|
11
|
+
end
|
12
|
+
|
13
|
+
# This method retrieves the system identifier specified in the notation
|
14
|
+
# declaration. If there is no system identifier defined, the method returns
|
15
|
+
# +nil+
|
16
|
+
def system
|
17
|
+
parse_rest(@rest)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
# This method retrieves the public identifier specified in the notation
|
21
|
+
# declaration. If there is no public identifier defined, the method returns
|
22
|
+
# +nil+
|
23
|
+
def public
|
24
|
+
return nil unless @middle == "PUBLIC"
|
25
|
+
parse_rest(@rest)[0]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def parse_rest(rest)
|
31
|
+
case rest
|
32
|
+
when /^"([^"]+)"\s+"([^"]+)"$/
|
33
|
+
return [$1,$2]
|
34
|
+
when /^'([^']+)'\s+'([^']+)'$/
|
35
|
+
return [$1,$2]
|
36
|
+
when /^"([^"]+)"\s+'([^']+)'$/
|
37
|
+
return [$1,$2]
|
38
|
+
when /^'([^']+)'\s+"([^"]+)"$/
|
39
|
+
return [$1,$2]
|
40
|
+
when /^"([^"]+)"$/
|
41
|
+
return [nil, $1] if @middle == 'SYSTEM'
|
42
|
+
return [$1, nil] if @middle == 'PUBLIC'
|
43
|
+
raise "Unknown notation keyword: #{@middle}"
|
44
|
+
when /^'([^']+)'$/
|
45
|
+
return [nil, $1] if @middle == 'SYSTEM'
|
46
|
+
return [$1, nil] if @middle == 'PUBLIC'
|
47
|
+
raise "Unknown notation keyword: #{@middle}"
|
48
|
+
else
|
49
|
+
raise "Could not parse \@rest variable in REXML::NotationDecl: |#{@rest}|"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
#! /usr/bin/ruby
|
2
|
+
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'test/unit/xml/attributes_mixin' # Must be required after rexml/document
|
5
|
+
require 'test/unit/xml/doctype_mixin' # Must be required after rexml/document
|
6
|
+
require 'test/unit/xml/notationdecl_mixin' # Must be required after rexml/document
|
7
|
+
require 'test/unit'
|
8
|
+
require 'test/unit/xml/conditionals'
|
9
|
+
require 'test/unit/xml/xmlequalfilter'
|
10
|
+
require 'test/unit/xml/nodeiterator'
|
11
|
+
|
12
|
+
=begin rdoc
|
13
|
+
This module contains assertions about XML documents. The assertions are
|
14
|
+
meant to be mixed in to test classes such as Test::Unit::TestCase.
|
15
|
+
=end
|
16
|
+
module Test
|
17
|
+
module Unit
|
18
|
+
module XML
|
19
|
+
|
20
|
+
# This method checks whether two well-formed XML documents are equal.
|
21
|
+
# Two XML documents are considered equal if:
|
22
|
+
# * They contain the same type of nodes, in the same order,
|
23
|
+
# except for text nodes that are empty, or contain only
|
24
|
+
# whitespace. Such text nodes are ignored.
|
25
|
+
# * The corresponding nodes in the two documents are equal.
|
26
|
+
#
|
27
|
+
# Nodes are tested for equality as follows:
|
28
|
+
# XML Declarations::
|
29
|
+
# XML declarations are equal if they have the same version,
|
30
|
+
# encoding, and stand-alone pseudo-attributes.
|
31
|
+
# Doctype::
|
32
|
+
# Doctypes are equal if they fulfill all of the following conditions:
|
33
|
+
# * They have the same public identifier, or neither has a public identifier
|
34
|
+
# * If one of the doctypes has a system identifier that is a URN,
|
35
|
+
# the other doctype must have a system identifier that is the same URN.
|
36
|
+
# System identifiers that are URLs are ignored for comparison purposes.
|
37
|
+
# The reason is that the same DTD is very often stored in many different
|
38
|
+
# locations (for example different directories on different computers).
|
39
|
+
# Therefore the physical location of the DTD does not say anything useful
|
40
|
+
# about whether two documents are equal.
|
41
|
+
# * An entity declaration present in one of the doctype declarations
|
42
|
+
# must also be present in the other.
|
43
|
+
# * A notation declaration present in one of the doctype declarations
|
44
|
+
# must also be present in the other.
|
45
|
+
# Internal General Entity Declaration::
|
46
|
+
# Internal General entity declarations are equal if they have the same name,
|
47
|
+
# and the same value.
|
48
|
+
# External General Entity Declaration::
|
49
|
+
# External general entity declarations are equal if they have the same name,
|
50
|
+
# and if the identifiers are of the same type (PUBLIC or SYSTEM) and have
|
51
|
+
# the same value. Note that if the identifiers are URLs, a comparison may
|
52
|
+
# fail even though both URLS point to the same resource, for example if one
|
53
|
+
# URL is relative and the other is absolute.
|
54
|
+
# Notation Declaration::
|
55
|
+
# Notation declarations are equal if they have the same name,
|
56
|
+
# and if the identifiers are of the same type (PUBLIC or SYSTEM) and have
|
57
|
+
# the same value.
|
58
|
+
# Elements::
|
59
|
+
# Elements are considered equal if they have the same generic
|
60
|
+
# identifier (tag name), belong to the same namespace, and have the same
|
61
|
+
# attributes. Note that the namespace _prefixes_ of two elements may be different
|
62
|
+
# as long as they belong to the same namespace.
|
63
|
+
# Attributes::
|
64
|
+
# Attributes are equal if they belong to the same namespace,
|
65
|
+
# have the same name, and the same value.
|
66
|
+
# Namespace Declarations::
|
67
|
+
# Namespace _declarations_ (attributes named <tt>xmlns:<em>prefix</em></tt>)
|
68
|
+
# are ignored. There are several reasons for this:
|
69
|
+
# - As long as two elements or attributes
|
70
|
+
# belong to the same namespace, it does not matter what prefixes
|
71
|
+
# are used. XML processors may also change prefixes in unpredictable
|
72
|
+
# ways without this being an error.
|
73
|
+
# - XML processors may _move_ namespace
|
74
|
+
# declarations from one element to another (usually an ancestor,
|
75
|
+
# sometimes a descendant) without this being an error, or under
|
76
|
+
# control by the programmer.
|
77
|
+
# - XML processors may _add_ extraneous namespace declarations
|
78
|
+
# in a manner that is hard for programmers to control.
|
79
|
+
# Processing Instructions::
|
80
|
+
# Processing instructions are considered equal if the string
|
81
|
+
# values of their targets and contents are equal.
|
82
|
+
# Text::
|
83
|
+
# Text nodes are equal if their values are equal. However, empty
|
84
|
+
# text nodes, and text nodes containing only whitespace are ignored.
|
85
|
+
# CDATA::
|
86
|
+
# CDATA nodes are equal if their text content is equal. Whitespace
|
87
|
+
# is _not_ normalized.
|
88
|
+
# Comments::
|
89
|
+
# Comments are equal if they have the same content.
|
90
|
+
#
|
91
|
+
# The +expected_doc+ and +actual_doc+ arguments to this method may be of
|
92
|
+
# the following types:
|
93
|
+
# * A +REXML+ node, usually a <tt>REXML::Document</tt> or <tt>REXML::Element</tt>
|
94
|
+
# * A +File+ or other +IO+ object representing an XML document
|
95
|
+
# * A string containing an XML document
|
96
|
+
def assert_xml_equal(expected_doc, actual_doc, message = nil)
|
97
|
+
expected_doc = parse_xml(expected_doc)
|
98
|
+
actual_doc = parse_xml(actual_doc)
|
99
|
+
_wrap_assertion do
|
100
|
+
full_message = build_message(message, <<EOT, actual_doc.inspect, expected_doc.inspect)
|
101
|
+
|
102
|
+
<?> expected to be equal to
|
103
|
+
<?> but was not.
|
104
|
+
EOT
|
105
|
+
assert_block(full_message){are_equal?(expected_doc, actual_doc)}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# This method compares two XML documents and returns +true+ if they are
|
111
|
+
# _not_ equal, +false+ otherwise. This is the inverse of assert_xml_equal.
|
112
|
+
def assert_xml_not_equal(expected_doc, actual_doc, message = nil)
|
113
|
+
expected_doc = parse_xml(expected_doc)
|
114
|
+
actual_doc = parse_xml(actual_doc)
|
115
|
+
_wrap_assertion do
|
116
|
+
full_message = build_message(message, <<EOT, actual_doc.inspect, expected_doc.inspect)
|
117
|
+
|
118
|
+
<?> expected not to be equal to
|
119
|
+
<?> but was equal.
|
120
|
+
EOT
|
121
|
+
assert_block(full_message){ ! are_equal?(expected_doc, actual_doc)}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def parse_xml(xml)
|
129
|
+
case xml
|
130
|
+
when IO
|
131
|
+
REXML::Document.new(xml)
|
132
|
+
when String
|
133
|
+
REXML::Document.new(xml)
|
134
|
+
else
|
135
|
+
xml
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def are_equal?(expected_doc, actual_doc)
|
140
|
+
comparator = Conditionals.create
|
141
|
+
iterate(expected_doc, actual_doc) do |expected_node, actual_node|
|
142
|
+
unless comparator.compare_xml_nodes(expected_node, actual_node)
|
143
|
+
return false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
149
|
+
def iterate(expected_doc, actual_doc)
|
150
|
+
filter = Test::Unit::XML::XmlEqualFilter.new()
|
151
|
+
expected_iterator = NodeIterator.new(expected_doc, filter)
|
152
|
+
actual_iterator = NodeIterator.new(actual_doc, filter)
|
153
|
+
while expected_iterator.has_next()
|
154
|
+
expected_node = expected_iterator.next()
|
155
|
+
actual_node = actual_iterator.next()
|
156
|
+
yield expected_node, actual_node
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|