json-deep-compare 0.0.1 → 0.1.0

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.
@@ -1,114 +1,201 @@
1
1
  module JsonDeepCompare
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
+
4
+ class DocumentComparison
5
+ def initialize(lval, rval, options = {})
6
+ if exclusions = options[:exclusions]
7
+ options[:exclusions] = [exclusions] unless exclusions.is_a?(Array)
8
+ else
9
+ options[:exclusions] = []
10
+ end
11
+ @root_comparisons = []
12
+ @root_comparisons << NodeComparison.new(lval, rval, ":root", options)
13
+ @root_comparisons << NodeComparison.new(rval, lval, ":root", options)
14
+ end
15
+
16
+ def difference_messages
17
+ ldiffs = @root_comparisons.first.differences
18
+ rdiffs = @root_comparisons.last.differences
19
+ differences = ldiffs.dup
20
+ ldiff_selectors = ldiffs.map { |ldiff| ldiff.selector }
21
+ rdiffs.each do |rdiff|
22
+ unless ldiff_selectors.include?(rdiff.selector)
23
+ differences << rdiff.reverse
24
+ end
25
+ end
26
+ differences = differences.sort_by &:selector
27
+ differences.map(&:message).join("\n")
28
+ end
29
+
30
+ def equal?
31
+ @root_comparisons.all?(&:equal?)
32
+ end
33
+ end
3
34
 
4
35
  class NodeComparison
5
36
  ExcerptPadding = 15
6
- attr_reader :left_value, :right_value, :selector
37
+ attr_reader :lval, :rval, :selector
7
38
 
8
- def initialize(left_value, right_value, options = {})
9
- @left_value, @right_value = left_value, right_value
10
- @selector = options[:selector] || ':root'
11
- @exclusions = options[:exclusions]
12
- @exclusions = [@exclusions] unless @exclusions.is_a?(Array)
39
+ def initialize(lval, rval, selector, options = {})
40
+ @lval, @rval, @selector, @options = lval, rval, selector, options
13
41
  @children = []
14
- if left_value.is_a?(Hash)
15
- if right_value.is_a?(Hash)
16
- left_value.each do |key, left_sub_value|
42
+ if lval.is_a?(Hash)
43
+ if rval.is_a?(Hash)
44
+ lval.each do |key, left_sub_value|
17
45
  @children << NodeComparison.new(
18
- left_sub_value, right_value[key],
19
- selector: "#{selector} > .#{key}", exclusions: @exclusions
46
+ left_sub_value,
47
+ rval[key],
48
+ "#{selector} > .#{key}",
49
+ options
20
50
  )
21
51
  end
22
52
  end
23
- elsif left_value.is_a?(Array)
24
- if right_value.is_a?(Array)
25
- left_value.each_with_index do |left_sub_value, i|
53
+ elsif lval.is_a?(Array)
54
+ if rval.is_a?(Array)
55
+ lval.each_with_index do |left_sub_value, i|
26
56
  @children << NodeComparison.new(
27
- left_sub_value, right_value[i],
28
- selector: "#{selector} :nth-child(#{i+1})",
29
- exclusions: @exclusions
57
+ left_sub_value,
58
+ rval[i],
59
+ "#{selector} :nth-child(#{i+1})",
60
+ options
30
61
  )
31
62
  end
32
63
  end
33
64
  end
34
65
  end
35
66
 
36
- def value_inspect(value)
37
- str = value.inspect
38
- if str.length >= 40
39
- "#{value.class.name} #{str[0..37]}..."
40
- else
41
- str
42
- end
67
+ def blank?(value)
68
+ value.respond_to?(:empty?) ? value.empty? : !value
43
69
  end
44
70
 
45
- def difference_message
46
- unless equal?
71
+ def blank_equality?
72
+ @options[:blank_equality]
73
+ end
74
+
75
+ def differences
76
+ if equal?
77
+ []
78
+ else
47
79
  if leaf?
48
80
  if excerptable_difference?
49
- excerpted_difference
81
+ [excerpted_difference]
50
82
  else
51
- "#{@selector.inspect} expected to be #{value_inspect(@left_value)} but was #{value_inspect(@right_value)}"
83
+ [Difference.new(
84
+ @selector, "expected to be :lval but was :rval",
85
+ lval: value_inspect(@lval), rval: value_inspect(@rval)
86
+ )]
52
87
  end
53
88
  else
54
- @children.reject(&:equal?).map(&:difference_message).join("\n")
89
+ @children.map(&:differences).compact.flatten
55
90
  end
56
91
  end
57
92
  end
58
93
 
59
94
  def equal?
60
95
  if leaf?
61
- @exclusions.include?(@selector) || @left_value == @right_value
96
+ if selector_excluded?
97
+ true
98
+ elsif equality_proc
99
+ equality_proc.call(@lval, @rval)
100
+ else
101
+ @lval == @rval || (blank_equality? && blank?(@lval) && blank?(@rval))
102
+ end
62
103
  else
63
104
  @children.all?(&:equal?)
64
105
  end
65
106
  end
66
107
 
108
+ def equality_proc
109
+ @options[:equality]
110
+ end
111
+
67
112
  def excerptable_difference?
68
- @left_value.is_a?(String) and @right_value.is_a?(String) && (
69
- @left_value.size > ExcerptPadding * 2 ||
70
- @right_value.size > ExcerptPadding * 2
113
+ @lval.is_a?(String) and @rval.is_a?(String) && (
114
+ @lval.size > ExcerptPadding * 2 || @rval.size > ExcerptPadding * 2
71
115
  )
72
116
  end
73
117
 
74
118
  def excerpted_difference
75
- difference_start = (0..@left_value.length).detect { |i|
76
- @left_value[i] != @right_value[i]
77
- }
119
+ difference_start = (0..@lval.length).detect { |i| @lval[i] != @rval[i] }
78
120
  range_start = if difference_start > ExcerptPadding
79
121
  difference_start - ExcerptPadding
80
122
  else
81
123
  0
82
124
  end
83
- left_excerpt = @left_value[
84
- range_start..difference_start+ExcerptPadding
85
- ]
86
- right_excerpt = @right_value[
87
- range_start..difference_start+ExcerptPadding
88
- ]
125
+ left_excerpt = @lval[range_start..difference_start+ExcerptPadding]
126
+ right_excerpt = @rval[range_start..difference_start+ExcerptPadding]
89
127
  if difference_start - ExcerptPadding > 0
90
128
  left_excerpt = "..." + left_excerpt
91
129
  right_excerpt = "..." + right_excerpt
92
130
  end
93
- if difference_start + ExcerptPadding < @left_value.length
131
+ if difference_start + ExcerptPadding < @lval.length
94
132
  left_excerpt = left_excerpt + '...'
95
133
  end
96
- if difference_start + ExcerptPadding < @right_value.length
134
+ if difference_start + ExcerptPadding < @rval.length
97
135
  right_excerpt = right_excerpt + '...'
98
136
  end
99
- "#{@selector.inspect} differs starting at char #{difference_start}: #{left_excerpt.inspect} differs from #{right_excerpt.inspect}"
137
+ Difference.new(
138
+ @selector,
139
+ "differs starting at char :difference_start: :lval differs from :rval",
140
+ difference_start: difference_start.to_s,
141
+ lval: left_excerpt.inspect, rval: right_excerpt.inspect
142
+ )
100
143
  end
101
144
 
102
145
  def leaf?
103
146
  @children.empty?
104
147
  end
148
+
149
+ def selector_excluded?
150
+ @options[:exclusions].any? { |exclusion|
151
+ if exclusion.is_a?(String)
152
+ exclusion == @selector
153
+ else
154
+ @selector =~ exclusion
155
+ end
156
+ }
157
+ end
158
+
159
+ def value_inspect(value)
160
+ str = value.inspect
161
+ if str.length >= 40
162
+ "#{value.class.name} #{str[0..37]}..."
163
+ else
164
+ str
165
+ end
166
+ end
167
+
168
+ class Difference
169
+ attr_reader :selector
170
+
171
+ def initialize(selector, msg_template, variables)
172
+ @selector, @msg_template, @variables =
173
+ selector, msg_template, variables
174
+ end
175
+
176
+ def message
177
+ msg = @msg_template
178
+ @variables.each do |name, value|
179
+ msg = msg.gsub(/#{name.inspect}/, value)
180
+ end
181
+ "#{@selector.inspect} #{msg}"
182
+ end
183
+
184
+ def reverse
185
+ reversed_variables = {lval: @variables[:rval], rval: @variables[:lval]}
186
+ (@variables.keys - [:lval, :rval]).each do |other_var|
187
+ reversed_variables[other_var] = @variables[other_var]
188
+ end
189
+ Difference.new(@selector, @msg_template, reversed_variables)
190
+ end
191
+ end
105
192
  end
106
193
 
107
194
  module Assertions
108
195
  def assert_json_equal(expected, actual, exclusions = nil)
109
- comparison = NodeComparison.new(expected, actual, exclusions: exclusions)
196
+ comparison = DocumentComparison.new(expected, actual, exclusions: exclusions)
110
197
  unless comparison.equal?
111
- fail comparison.difference_message
198
+ fail comparison.difference_messages
112
199
  end
113
200
  end
114
201
  end
@@ -2,7 +2,41 @@ require 'test/unit'
2
2
  $: << '.'
3
3
  require 'lib/json-deep-compare'
4
4
 
5
- class NodeComparisonTestCase < Test::Unit::TestCase
5
+ class DocumentComparisonTestCase < Test::Unit::TestCase
6
+ def assert_symmetrically_different(
7
+ lval, rval, message_template = nil, sub_pairs = nil
8
+ )
9
+ sub_pairs = [sub_pairs] if sub_pairs and sub_pairs.first.is_a?(String)
10
+ comparison1 = JsonDeepCompare::DocumentComparison.new(lval, rval)
11
+ assert(
12
+ !comparison1.equal?,
13
+ "Comparison of #{lval.inspect} to #{rval.inspect} should not have been equal"
14
+ )
15
+ if message_template
16
+ message1 = message_template
17
+ sub_pairs.each_with_index do |sub_pair, i|
18
+ message1 = message1.
19
+ sub(/:left#{i}/, sub_pair.first).
20
+ sub(/:right#{i}/, sub_pair.last)
21
+ end
22
+ assert_equal(message1, comparison1.difference_messages)
23
+ end
24
+ comparison2 = JsonDeepCompare::DocumentComparison.new(rval, lval)
25
+ assert(
26
+ !comparison2.equal?,
27
+ "Comparison of #{rval.inspect} to #{lval.inspect} should not have been equal"
28
+ )
29
+ if message_template
30
+ message2 = message_template
31
+ sub_pairs.each_with_index do |sub_pair, i|
32
+ message2 = message2.
33
+ sub(/:left#{i}/, sub_pair.last).
34
+ sub(/:right#{i}/, sub_pair.first)
35
+ end
36
+ assert_equal(message2, comparison2.difference_messages)
37
+ end
38
+ end
39
+
6
40
  def test_detects_difference_in_atoms
7
41
  left_value = {
8
42
  'total_rows' => 2,
@@ -30,29 +64,36 @@ class NodeComparisonTestCase < Test::Unit::TestCase
30
64
  }
31
65
  ]
32
66
  }
33
- comparison = JsonDeepCompare::NodeComparison.new(left_value, right_value)
34
- assert !comparison.equal?
35
- assert_equal(
36
- "\":root > .rows :nth-child(1) > .doc > .sub_document > .one\" expected to be \"two\" but was \"1\"",
37
- comparison.difference_message
67
+ assert_symmetrically_different(
68
+ left_value, right_value,
69
+ "\":root > .rows :nth-child(1) > .doc > .sub_document > .one\" expected to be :left0 but was :right0",
70
+ ["two".inspect, "1".inspect]
38
71
  )
39
72
  end
40
73
 
41
74
  def test_detects_differences_in_keys
42
75
  left_value = {"doc" => {"foo" => "bar"}}
43
76
  right_value = {"doc" => {"biz" => "bang", "bing" => nil}}
44
- comparison = JsonDeepCompare::NodeComparison.new(left_value, right_value)
45
- assert !comparison.equal?
77
+ assert_symmetrically_different(left_value, right_value)
78
+ end
79
+
80
+ def test_missing_keys
81
+ lval = {"outer" => {"key1" => "value1"}}
82
+ rval = {"outer" => {"key1" => "value1", "key2" => {"foo" => "bar"}}}
83
+ assert_symmetrically_different(
84
+ lval, rval,
85
+ "\":root > .outer > .key2\" expected to be :left0 but was :right0",
86
+ ["nil", {"foo" => "bar"}.inspect]
87
+ )
46
88
  end
47
89
 
48
90
  def test_detects_difference_in_type
49
91
  lval = {"foo" => {'bar' => 'bang'}}
50
92
  rval = {"foo" => [3,4,5]}
51
- comparison = JsonDeepCompare::NodeComparison.new(lval, rval)
52
- assert !comparison.equal?
53
- assert_equal(
54
- "\":root > .foo\" expected to be {\"bar\"=>\"bang\"} but was [3, 4, 5]",
55
- comparison.difference_message
93
+ assert_symmetrically_different(
94
+ lval, rval,
95
+ "\":root > .foo\" expected to be :left0 but was :right0",
96
+ [{"bar" => "bang"}.inspect, [3,4,5].inspect]
56
97
  )
57
98
  end
58
99
 
@@ -63,64 +104,98 @@ class NodeComparisonTestCase < Test::Unit::TestCase
63
104
  end
64
105
  lval = {"foo" => lval_hash}
65
106
  rval = {"foo" => [3,4,5]}
66
- comparison = JsonDeepCompare::NodeComparison.new(lval, rval)
67
- assert !comparison.equal?
68
- assert_equal(
69
- "\":root > .foo\" expected to be Hash {\"A\"=>\"A\", \"B\"=>\"B\", \"C\"=>\"C\", \"D\"=>\"D... but was [3, 4, 5]",
70
- comparison.difference_message
107
+ assert_symmetrically_different(
108
+ lval, rval,
109
+ "\":root > .foo\" expected to be :left0 but was :right0",
110
+ [
111
+ "Hash {\"A\"=>\"A\", \"B\"=>\"B\", \"C\"=>\"C\", \"D\"=>\"D...",
112
+ [3,4,5].inspect
113
+ ]
71
114
  )
72
115
  end
73
116
 
74
117
  def test_lists_multliple_differences
75
118
  lval = {'one' => {'two' => 'three', 'four' => 'five'}, 'six' => 'seven'}
76
119
  rval = {'one' => {'two' => 'TWO', 'four' => 'five'}, 'six' => 'SIX'}
77
- comparison = JsonDeepCompare::NodeComparison.new(lval, rval)
78
- assert !comparison.equal?
79
- assert_equal(
80
- "\":root > .one > .two\" expected to be \"three\" but was \"TWO\"\n\":root > .six\" expected to be \"seven\" but was \"SIX\"",
81
- comparison.difference_message
120
+ assert_symmetrically_different(
121
+ lval, rval,
122
+ "\":root > .one > .two\" expected to be :left0 but was :right0\n\":root > .six\" expected to be :left1 but was :right1",
123
+ [["three".inspect, "TWO".inspect], ["seven".inspect, "SIX".inspect]]
82
124
  )
83
125
  end
84
126
 
85
127
  def test_detects_difference_between_array_and_nil
86
128
  lval = {'one' => [1,2,3]}
87
129
  rval = {'one' => nil}
88
- comparison = JsonDeepCompare::NodeComparison.new(lval, rval)
89
- assert !comparison.equal?
90
- assert_equal(
91
- "\":root > .one\" expected to be [1, 2, 3] but was nil",
92
- comparison.difference_message
130
+ assert_symmetrically_different(
131
+ lval, rval,
132
+ "\":root > .one\" expected to be :left0 but was :right0",
133
+ [[1,2,3].inspect, 'nil']
93
134
  )
94
135
  end
95
136
 
96
137
  def test_shows_first_string_difference
97
138
  lval = {'body' => "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal."}
98
139
  rval = {'body' => "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal."}
99
- comparison = JsonDeepCompare::NodeComparison.new(lval, rval)
100
- assert !comparison.equal?
101
- assert_equal(
102
- "\":root > .body\" differs starting at char 103: \"..., conceived in Liberty, and ded...\" differs from \"..., conceived in liberty, and ded...\"",
103
- comparison.difference_message
140
+ assert_symmetrically_different(
141
+ lval, rval,
142
+ "\":root > .body\" differs starting at char 103: :left0 differs from :right0",
143
+ [
144
+ "..., conceived in Liberty, and ded...".inspect,
145
+ "..., conceived in liberty, and ded...".inspect
146
+ ]
104
147
  )
105
148
  end
106
149
 
107
150
  def test_shows_string_difference_start
108
151
  lval = {'rev' => "22-23c92a95665bb692313229c8224b7088"}
109
152
  rval = {'rev' => "23-54a5106f8c522a57d6d4c6963bc36611"}
110
- comparison = JsonDeepCompare::NodeComparison.new(lval, rval)
111
- assert !comparison.equal?
112
- assert_equal(
113
- "\":root > .rev\" differs starting at char 1: \"22-23c92a95665bb6...\" differs from \"23-54a5106f8c522a...\"",
114
- comparison.difference_message
153
+ assert_symmetrically_different(
154
+ lval, rval,
155
+ "\":root > .rev\" differs starting at char 1: :left0 differs from :right0",
156
+ ["22-23c92a95665bb6...".inspect, "23-54a5106f8c522a...".inspect]
115
157
  )
116
158
  end
117
159
 
118
160
  def test_simple_exclusion
119
161
  lval = {'one' => 'two', 'three' => 'four'}
120
162
  rval = {'one' => 'two', 'three' => 'THREE'}
121
- comparison = JsonDeepCompare::NodeComparison.new(
163
+ comparison = JsonDeepCompare::DocumentComparison.new(
122
164
  lval, rval, exclusions: [":root > .three"]
123
165
  )
124
166
  assert comparison.equal?
125
167
  end
168
+
169
+ def test_exclusion_regexp
170
+ lval = {'one' => 'two', 'three' => 'four'}
171
+ rval = {'one' => 'two', 'three' => 'THREE'}
172
+ comparison = JsonDeepCompare::DocumentComparison.new(
173
+ lval, rval, exclusions: [/> \.three$/]
174
+ )
175
+ assert comparison.equal?
176
+ end
177
+
178
+ def test_blank_equality_option
179
+ lval = {'one' => 'two', 'three' => ''}
180
+ rval = {'one' => 'two', 'three' => nil}
181
+ comparison = JsonDeepCompare::DocumentComparison.new(
182
+ lval, rval, blank_equality: true
183
+ )
184
+ assert comparison.equal?
185
+ end
186
+
187
+ def test_equality_proc_option
188
+ lval = {'one' => 2, 'three' => "He says 'hi'"}
189
+ rval = {'one' => 2, 'three' => "He says \"hi\""}
190
+ comparison = JsonDeepCompare::DocumentComparison.new(
191
+ lval, rval, equality: Proc.new { |lval, rval|
192
+ if lval.is_a?(String) && rval.is_a?(String)
193
+ lval.gsub(/"/, "'") == rval.gsub(/"/, "'")
194
+ else
195
+ lval == rval
196
+ end
197
+ }
198
+ )
199
+ assert comparison.equal?
200
+ end
126
201
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json-deep-compare
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-01-03 00:00:00.000000000 Z
12
+ date: 2014-02-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -73,7 +73,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
73
  version: '0'
74
74
  segments:
75
75
  - 0
76
- hash: 383603838726367257
76
+ hash: 634763774305586060
77
77
  required_rubygems_version: !ruby/object:Gem::Requirement
78
78
  none: false
79
79
  requirements:
@@ -82,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
82
82
  version: '0'
83
83
  segments:
84
84
  - 0
85
- hash: 383603838726367257
85
+ hash: 634763774305586060
86
86
  requirements: []
87
87
  rubyforge_project:
88
88
  rubygems_version: 1.8.23