mezza-testunitxml 0.1.6

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.
@@ -0,0 +1,21 @@
1
+ =begin rdoc
2
+ This file mixes in XML assertions in the Test::Unit::TestCase
3
+ class.
4
+
5
+ See #xml_assertions.rb for information about the assertions
6
+ that are mixed in.
7
+ =end
8
+
9
+ require 'test/unit/xml/xml_assertions'
10
+
11
+ module Test
12
+ module Unit
13
+
14
+ # The module Test::Unit::XML is mixed in into the class
15
+ # Test::Unit::TestCase
16
+ class TestCase
17
+ include Test::Unit::XML
18
+ end
19
+ end
20
+ end
21
+
@@ -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,141 @@
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
+ return false unless actual_node.instance_of? expected_node.class
26
+ case actual_node
27
+ when REXML::Document
28
+ # TODO: Implement Document comparison
29
+ true
30
+ when REXML::DocType
31
+ compare_doctypes(expected_node, actual_node)
32
+ when REXML::Element :
33
+ compare_elements(expected_node, actual_node)
34
+ when REXML::CData
35
+ compare_texts(expected_node, actual_node)
36
+ when REXML::Text
37
+ compare_texts(expected_node, actual_node)
38
+ when REXML::Comment
39
+ compare_comments(expected_node, actual_node)
40
+ when REXML::Instruction
41
+ compare_pi(expected_node, actual_node)
42
+ when REXML::XMLDecl
43
+ compare_xml_declaration(expected_node, actual_node)
44
+ #when REXML::Entity
45
+ # compare_xml_entities(expected_node, actual_node)
46
+ else
47
+ puts "Unknown node type #{actual_node.class}"
48
+ false
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def compare_doctypes(expected_node, actual_node)
55
+ return compare_system_id(expected_node.system, actual_node.system) &&
56
+ expected_node.public == actual_node.public &&
57
+ compare_xml_internal_dtd_subset(expected_node, actual_node)
58
+ end
59
+
60
+ def compare_system_id(expected_id, actual_id)
61
+ is_expected_urn = expected_id =~ /^urn:/i
62
+ is_actual_urn = actual_id =~ /^urn:/i
63
+ if is_expected_urn || is_actual_urn
64
+ expected_id == actual_id
65
+ else
66
+ true
67
+ end
68
+ end
69
+
70
+ def compare_elements(expected_node, actual_node)
71
+ return expected_node.name == actual_node.name &&
72
+ expected_node.namespace() == actual_node.namespace() &&
73
+ compare_attributes(expected_node.attributes, actual_node.attributes)
74
+ end
75
+
76
+ def compare_attributes(expected_attributes, actual_attributes)
77
+ return false unless attribute_count(expected_attributes) == attribute_count(actual_attributes)
78
+ expected_attributes.each_attribute do |expected_attribute|
79
+ expected_prefix = expected_attribute.prefix()
80
+ unless expected_prefix == 'xmlns' then
81
+ expected_name = expected_attribute.name
82
+ expected_namespace = expected_attribute.namespace
83
+ actual_attribute = actual_attributes.get_attribute_ns(expected_namespace, expected_name)
84
+ return false unless actual_attribute
85
+ return false if expected_attribute.value() != actual_attribute.value()
86
+ end
87
+ end
88
+ true
89
+ end
90
+
91
+ def attribute_count(attributes)
92
+ # Do not count namespace declarations
93
+ attributes.size - attributes.prefixes.size
94
+ end
95
+
96
+ def compare_texts(expected_node, actual_node)
97
+ expected_node.value.eql?(actual_node.value)
98
+ end
99
+
100
+ def compare_comments(expected_node, actual_node)
101
+ expected_node == actual_node
102
+ end
103
+
104
+ def compare_pi(expected_pi, actual_pi)
105
+ return expected_pi.target == actual_pi.target &&
106
+ expected_pi.content == actual_pi.content
107
+ end
108
+
109
+ def compare_xml_declaration(expected_decl, actual_decl)
110
+ return expected_decl == actual_decl
111
+ end
112
+
113
+ def compare_xml_internal_dtd_subset(expected_node, actual_node)
114
+ expected_subset = expected_node.children()
115
+ actual_subset = actual_node.children()
116
+ return false unless expected_subset.length == actual_subset.length
117
+ expected_subset.inject(true) { |memo, expected_decl|
118
+ case expected_decl
119
+ when REXML::Entity
120
+ memo &&
121
+ expected_decl.value == actual_node.entities[expected_decl.name].value &&
122
+ expected_decl.ndata == actual_node.entities[expected_decl.name].ndata
123
+ when REXML::NotationDecl
124
+ actual_notation_decl = actual_node.notation(expected_decl.name)
125
+ memo &&
126
+ actual_notation_decl != nil &&
127
+ expected_decl.name == actual_notation_decl.name &&
128
+ expected_decl.public == actual_notation_decl.public &&
129
+ expected_decl.system == actual_notation_decl.system
130
+ when REXML::Comment
131
+ true
132
+ else
133
+ raise "Unexpected node type in internal DTD subset of expected document: " + expected_decl.inspect
134
+ end
135
+ }
136
+ end
137
+
138
+ end
139
+ end
140
+ end
141
+ 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,61 @@
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
+ next_node = nil
19
+ if NodeIterator.has_children?(node) then
20
+ next_node = node[0] # The index should be 1 according to the REXML docs
21
+ elsif node.next_sibling_node
22
+ next_node = node.next_sibling_node
23
+ elsif NodeIterator.has_parent_with_sibling?(node)
24
+ next_node = node.parent.next_sibling_node
25
+ end
26
+ return next_node if node_filter.accept(next_node) || next_node == nil
27
+ find_next_node(next_node, node_filter)
28
+ end
29
+
30
+
31
+ def initialize(node, node_filter = NullNodeFilter.new)
32
+ @node_filter = node_filter
33
+ @next_node = node
34
+ end
35
+
36
+ def has_next()
37
+ @next_node ? true : false
38
+ end
39
+
40
+ def next
41
+ node = @next_node
42
+ @next_node = NodeIterator.find_next_node(node, @node_filter)
43
+ node
44
+ end
45
+
46
+
47
+
48
+
49
+ private
50
+
51
+ def NodeIterator.has_children?(node)
52
+ node.respond_to?(:[]) && node.respond_to?(:size) && node.size > 0
53
+ end
54
+
55
+ def NodeIterator.has_parent_with_sibling?(node)
56
+ node.parent && node.parent.next_sibling_node
57
+ end
58
+ end
59
+ end
60
+ end
61
+ 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
+
@@ -0,0 +1,31 @@
1
+ #! /usr/bin/ruby
2
+
3
+ module Test
4
+ module Unit
5
+ module XML
6
+
7
+ # This filter class accepts any node except text nodes
8
+ # that contain non-significant whitespace
9
+ class XmlEqualFilter
10
+ def accept(node)
11
+ case
12
+ when node.kind_of?(REXML::Text)
13
+ is_significant?(node.value)
14
+ when node.kind_of?(REXML::Entity)
15
+ false
16
+ when node.kind_of?(REXML::NotationDecl)
17
+ false
18
+ else
19
+ true
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def is_significant?(string)
26
+ string =~ /^\s*$/ ? false : true
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mezza-testunitxml
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.6
6
+ platform: ruby
7
+ authors:
8
+ - "Henrik M\xC3\xA5rtensson"
9
+ - Merul Patel
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2011-06-02 00:00:00 +01:00
15
+ default_executable:
16
+ dependencies: []
17
+
18
+ description:
19
+ email:
20
+ - dag.henrik.martensson@gmail.com
21
+ - merul.patel@gmail.com
22
+ executables: []
23
+
24
+ extensions: []
25
+
26
+ extra_rdoc_files: []
27
+
28
+ files:
29
+ - lib/test/unit/xml/attributes_mixin.rb
30
+ - lib/test/unit/xml/conditionals.rb
31
+ - lib/test/unit/xml/doctype_mixin.rb
32
+ - lib/test/unit/xml/nodeiterator.rb
33
+ - lib/test/unit/xml/notationdecl_mixin.rb
34
+ - lib/test/unit/xml/xml_assertions.rb
35
+ - lib/test/unit/xml/xmlequalfilter.rb
36
+ - lib/test/unit/xml.rb
37
+ has_rdoc: true
38
+ homepage: https://github.com/mezza/testunitxml
39
+ licenses: []
40
+
41
+ post_install_message:
42
+ rdoc_options: []
43
+
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 1.8.6
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.5.2
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: testunitxml extends the Test::Unit framework with an assertion for testing well-formed XML documents.
65
+ test_files: []
66
+