structure_compare 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/lib/structure_compare.rb +3 -151
- data/lib/structure_compare/minitest.rb +16 -0
- data/lib/structure_compare/structure_comparison.rb +145 -0
- data/lib/{version.rb → structure_compare/version.rb} +1 -1
- data/structure_compare.gemspec +1 -1
- data/test/structure_compare_test.rb +2 -16
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ebebf0cb0eafd7f627df962d71f2553c3de2d62
|
4
|
+
data.tar.gz: 3a858990738e61837a4a47d0497667ba94dacf52
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4e814993fe0e4fe32345c1908f739f337aea61fb6880387bdc33d76245925e9e485a0ab06278ebcdefa5bfd9fc4e2487eef1b272bd160a0e216ab800b42ea2b
|
7
|
+
data.tar.gz: 643bb740f4c6e0392c06cb5b17f812329fa36b008446a14f2997661a5a9719e66c971ffe727bdb10ef1295e9bdc2ea75474a77c07c3b6b5c7a5156760cb571ae
|
data/lib/structure_compare.rb
CHANGED
@@ -1,156 +1,8 @@
|
|
1
|
-
# TODO
|
2
|
-
# TODO Enumerable to support non-array enumerables
|
3
|
-
# TODO doc
|
1
|
+
# TODO: doc (interface, assertions, ...)
|
4
2
|
module StructureCompare
|
5
|
-
|
6
3
|
class StructuresNotEqualError < RuntimeError; end
|
7
4
|
class ArgumentError < ArgumentError; end
|
8
|
-
|
9
|
-
class StructureComparison
|
10
|
-
def initialize(options = {})
|
11
|
-
@options = {
|
12
|
-
strict_key_order: false,
|
13
|
-
check_values: false,
|
14
|
-
treat_hash_symbols_as_strings: false,
|
15
|
-
float_tolerance_factor: 0
|
16
|
-
}.merge(options)
|
17
|
-
end
|
18
|
-
|
19
|
-
# TODO doc
|
20
|
-
def structures_are_equal?(expected, actual)
|
21
|
-
@path = []
|
22
|
-
@error = nil
|
23
|
-
|
24
|
-
begin
|
25
|
-
check_structures_equal!(expected, actual)
|
26
|
-
rescue StructuresNotEqualError => _error
|
27
|
-
return false
|
28
|
-
end
|
29
|
-
|
30
|
-
return true
|
31
|
-
end
|
32
|
-
|
33
|
-
# TODO doc
|
34
|
-
def error
|
35
|
-
"#{@path.join} : #{@error}"
|
36
|
-
end
|
37
|
-
|
38
|
-
protected
|
39
|
-
|
40
|
-
def check_structures_equal!(expected, actual)
|
41
|
-
check_kind_of!(expected, actual)
|
42
|
-
|
43
|
-
case expected
|
44
|
-
when Array
|
45
|
-
check_arrays_equal!(expected, actual)
|
46
|
-
when Hash
|
47
|
-
check_hashes_equal!(expected, actual)
|
48
|
-
else
|
49
|
-
check_leafs_equal!(expected, actual)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def check_arrays_equal!(expected, actual)
|
54
|
-
check_equal!(
|
55
|
-
expected.count, actual.count, "array length: #{expected.count} != #{actual.count}"
|
56
|
-
)
|
57
|
-
|
58
|
-
expected.each_with_index do |expected_value, index|
|
59
|
-
path_segment = "[#{index}]"
|
60
|
-
@path.push(path_segment)
|
61
|
-
|
62
|
-
check_structures_equal!(expected_value, actual[index])
|
63
|
-
@path.pop
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def check_hashes_equal!(expected, actual)
|
68
|
-
check_hash_keys_equal!(expected, actual)
|
69
|
-
|
70
|
-
expected_values = expected.values
|
71
|
-
actual_values = actual.values
|
72
|
-
|
73
|
-
expected_values.each_with_index do |expected_value, index|
|
74
|
-
key = expected.keys[index]
|
75
|
-
path_segment = key.is_a?(Symbol) ? "[:#{key}]" : "[\"#{key}\"]"
|
76
|
-
@path.push(path_segment)
|
77
|
-
|
78
|
-
check_structures_equal!(expected_value, actual_values[index])
|
79
|
-
@path.pop
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def check_hash_keys_equal!(expected, actual)
|
84
|
-
expected_keys = expected.keys
|
85
|
-
actual_keys = actual.keys
|
86
|
-
|
87
|
-
if @options[:treat_hash_symbols_as_strings]
|
88
|
-
# NOTE: not all hash keys are symbols/strings, only convert symbols
|
89
|
-
expected_keys.map! { |key| key.is_a?(Symbol) ? key.to_s : key }
|
90
|
-
actual_keys.map! { |key| key.is_a?(Symbol) ? key.to_s : key }
|
91
|
-
end
|
92
|
-
|
93
|
-
if @options[:strict_key_order]
|
94
|
-
check_equal!(expected_keys, actual_keys)
|
95
|
-
else
|
96
|
-
begin
|
97
|
-
expected_keys.sort!
|
98
|
-
actual_keys.sort!
|
99
|
-
rescue ::ArgumentError => error
|
100
|
-
raise StructureCompare::ArgumentError.new(
|
101
|
-
"Unable to sort hash keys: \"#{error}\"." +
|
102
|
-
'Try enabling :strict_key_order option to prevent sorting of mixed-type hash keys.'
|
103
|
-
)
|
104
|
-
end
|
105
|
-
|
106
|
-
check_equal!(expected_keys, actual_keys)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def check_leafs_equal!(expected, actual)
|
111
|
-
check_equal!(expected, actual) if @options[:check_values]
|
112
|
-
end
|
113
|
-
|
114
|
-
def check_kind_of!(expected, actual)
|
115
|
-
unless actual.kind_of?(expected.class)
|
116
|
-
failure_message = "expected #{actual.class.to_s} to be kind of #{expected.class.to_s}"
|
117
|
-
not_equal_error!(expected, actual, failure_message)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def check_equal!(expected, actual, failure_message = nil)
|
122
|
-
if expected.is_a?(Float) && actual.is_a?(Float)
|
123
|
-
is_equal = float_equal_with_tolerance_factor?(
|
124
|
-
expected, actual, @options[:float_tolerance_factor]
|
125
|
-
)
|
126
|
-
else
|
127
|
-
is_equal = (expected == actual)
|
128
|
-
end
|
129
|
-
|
130
|
-
if !is_equal
|
131
|
-
failure_message ||= "expected: #{expected.inspect}, got: #{actual.inspect}"
|
132
|
-
not_equal_error!(expected, actual, failure_message)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def float_equal_with_tolerance_factor?(expected, actual, tolerance_factor)
|
137
|
-
raise StructureCompare::ArgumentError.new("tolerance_factor must be > 0") if tolerance_factor < 0
|
138
|
-
|
139
|
-
lower_bound = (expected * (1.0 - tolerance_factor) - Float::EPSILON)
|
140
|
-
upper_bound = (expected * (1.0 + tolerance_factor) + Float::EPSILON)
|
141
|
-
|
142
|
-
return (lower_bound <= actual) && (actual <= upper_bound)
|
143
|
-
end
|
144
|
-
|
145
|
-
# TODO make this part of an overridden exception?
|
146
|
-
def not_equal_error!(expected, actual, failure_message)
|
147
|
-
@error = failure_message
|
148
|
-
raise StructuresNotEqualError.new() # TODO error message, path
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
5
|
end
|
153
6
|
|
154
|
-
|
155
|
-
|
156
|
-
require 'version'
|
7
|
+
require 'structure_compare/version'
|
8
|
+
require 'structure_compare/structure_comparison'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Minitest::Test
|
2
|
+
|
3
|
+
protected
|
4
|
+
|
5
|
+
def assert_structures_equal(expected, actual, options = {})
|
6
|
+
comparison = StructureCompare::StructureComparison.new(options)
|
7
|
+
is_equal = comparison.structures_are_equal?(expected, actual)
|
8
|
+
assert is_equal, comparison.error
|
9
|
+
end
|
10
|
+
|
11
|
+
def refute_structures_equal(expected, actual, options = {})
|
12
|
+
comparison = StructureCompare::StructureComparison.new(options)
|
13
|
+
is_equal = comparison.structures_are_equal?(expected, actual)
|
14
|
+
refute is_equal, "structures are not expected to be equal"
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module StructureCompare
|
2
|
+
# TODO: Enumerable to support non-array enumerables
|
3
|
+
class StructureComparison
|
4
|
+
def initialize(options = {})
|
5
|
+
@options = {
|
6
|
+
strict_key_order: false,
|
7
|
+
check_values: false,
|
8
|
+
treat_hash_symbols_as_strings: false,
|
9
|
+
float_tolerance_factor: 0
|
10
|
+
}.merge(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO doc
|
14
|
+
def structures_are_equal?(expected, actual)
|
15
|
+
@path = []
|
16
|
+
@error = nil
|
17
|
+
|
18
|
+
begin
|
19
|
+
check_structures_equal!(expected, actual)
|
20
|
+
rescue StructuresNotEqualError => _error
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
|
27
|
+
# TODO doc
|
28
|
+
def error
|
29
|
+
"#{@path.join} : #{@error}"
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def check_structures_equal!(expected, actual)
|
35
|
+
check_kind_of!(expected, actual)
|
36
|
+
|
37
|
+
case expected
|
38
|
+
when Array
|
39
|
+
check_arrays_equal!(expected, actual)
|
40
|
+
when Hash
|
41
|
+
check_hashes_equal!(expected, actual)
|
42
|
+
else
|
43
|
+
check_leafs_equal!(expected, actual)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def check_arrays_equal!(expected, actual)
|
48
|
+
check_equal!(
|
49
|
+
expected.count, actual.count, "array length: #{expected.count} != #{actual.count}"
|
50
|
+
)
|
51
|
+
|
52
|
+
expected.each_with_index do |expected_value, index|
|
53
|
+
path_segment = "[#{index}]"
|
54
|
+
@path.push(path_segment)
|
55
|
+
|
56
|
+
check_structures_equal!(expected_value, actual[index])
|
57
|
+
@path.pop
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def check_hashes_equal!(expected, actual)
|
62
|
+
check_hash_keys_equal!(expected, actual)
|
63
|
+
|
64
|
+
expected_values = expected.values
|
65
|
+
actual_values = actual.values
|
66
|
+
|
67
|
+
expected_values.each_with_index do |expected_value, index|
|
68
|
+
key = expected.keys[index]
|
69
|
+
path_segment = key.is_a?(Symbol) ? "[:#{key}]" : "[\"#{key}\"]"
|
70
|
+
@path.push(path_segment)
|
71
|
+
|
72
|
+
check_structures_equal!(expected_value, actual_values[index])
|
73
|
+
@path.pop
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_hash_keys_equal!(expected, actual)
|
78
|
+
expected_keys = expected.keys
|
79
|
+
actual_keys = actual.keys
|
80
|
+
|
81
|
+
if @options[:treat_hash_symbols_as_strings]
|
82
|
+
# NOTE: not all hash keys are symbols/strings, only convert symbols
|
83
|
+
expected_keys.map! { |key| key.is_a?(Symbol) ? key.to_s : key }
|
84
|
+
actual_keys.map! { |key| key.is_a?(Symbol) ? key.to_s : key }
|
85
|
+
end
|
86
|
+
|
87
|
+
if @options[:strict_key_order]
|
88
|
+
check_equal!(expected_keys, actual_keys)
|
89
|
+
else
|
90
|
+
begin
|
91
|
+
expected_keys.sort!
|
92
|
+
actual_keys.sort!
|
93
|
+
rescue ::ArgumentError => error
|
94
|
+
raise StructureCompare::ArgumentError.new(
|
95
|
+
"Unable to sort hash keys: \"#{error}\"." +
|
96
|
+
'Try enabling :strict_key_order option to prevent sorting of mixed-type hash keys.'
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
check_equal!(expected_keys, actual_keys)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def check_leafs_equal!(expected, actual)
|
105
|
+
check_equal!(expected, actual) if @options[:check_values]
|
106
|
+
end
|
107
|
+
|
108
|
+
def check_kind_of!(expected, actual)
|
109
|
+
unless actual.kind_of?(expected.class)
|
110
|
+
failure_message = "expected #{actual.class.to_s} to be kind of #{expected.class.to_s}"
|
111
|
+
not_equal_error!(expected, actual, failure_message)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def check_equal!(expected, actual, failure_message = nil)
|
116
|
+
if expected.is_a?(Float) && actual.is_a?(Float)
|
117
|
+
is_equal = float_equal_with_tolerance_factor?(
|
118
|
+
expected, actual, @options[:float_tolerance_factor]
|
119
|
+
)
|
120
|
+
else
|
121
|
+
is_equal = (expected == actual)
|
122
|
+
end
|
123
|
+
|
124
|
+
if !is_equal
|
125
|
+
failure_message ||= "expected: #{expected.inspect}, got: #{actual.inspect}"
|
126
|
+
not_equal_error!(expected, actual, failure_message)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def float_equal_with_tolerance_factor?(expected, actual, tolerance_factor)
|
131
|
+
raise StructureCompare::ArgumentError.new("tolerance_factor must be > 0") if tolerance_factor < 0
|
132
|
+
|
133
|
+
lower_bound = (expected * (1.0 - tolerance_factor) - Float::EPSILON)
|
134
|
+
upper_bound = (expected * (1.0 + tolerance_factor) + Float::EPSILON)
|
135
|
+
|
136
|
+
return (lower_bound <= actual) && (actual <= upper_bound)
|
137
|
+
end
|
138
|
+
|
139
|
+
# TODO make this part of an overridden exception?
|
140
|
+
def not_equal_error!(expected, actual, failure_message)
|
141
|
+
@error = failure_message
|
142
|
+
raise StructuresNotEqualError.new() # TODO error message, path
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/structure_compare.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative "test_helper"
|
2
|
-
require_relative
|
2
|
+
require_relative '../lib/structure_compare'
|
3
|
+
require_relative '../lib/structure_compare/minitest'
|
3
4
|
|
4
5
|
class StructureCompareTest < MiniTest::Test
|
5
6
|
|
@@ -158,19 +159,4 @@ class StructureCompareTest < MiniTest::Test
|
|
158
159
|
)
|
159
160
|
end
|
160
161
|
end
|
161
|
-
|
162
|
-
protected
|
163
|
-
|
164
|
-
def assert_structures_equal(expected, actual, options = {})
|
165
|
-
comparison = StructureCompare::StructureComparison.new(options)
|
166
|
-
is_equal = comparison.structures_are_equal?(expected, actual)
|
167
|
-
assert is_equal, comparison.error
|
168
|
-
end
|
169
|
-
|
170
|
-
def refute_structures_equal(expected, actual, options = {})
|
171
|
-
comparison = StructureCompare::StructureComparison.new(options)
|
172
|
-
is_equal = comparison.structures_are_equal?(expected, actual)
|
173
|
-
refute is_equal, "structures are not expected to be equal"
|
174
|
-
end
|
175
|
-
|
176
162
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: structure_compare
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Markus Seeger
|
@@ -123,7 +123,9 @@ files:
|
|
123
123
|
- README.md
|
124
124
|
- Rakefile
|
125
125
|
- lib/structure_compare.rb
|
126
|
-
- lib/
|
126
|
+
- lib/structure_compare/minitest.rb
|
127
|
+
- lib/structure_compare/structure_comparison.rb
|
128
|
+
- lib/structure_compare/version.rb
|
127
129
|
- structure_compare.gemspec
|
128
130
|
- test/structure_compare_test.rb
|
129
131
|
- test/test_helper.rb
|