specdiff 0.3.0.pre.rc1 → 0.3.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.
- checksums.yaml +4 -4
- data/.gitignore +14 -0
- data/CHANGELOG.md +21 -1
- data/DEVELOPMENT.md +74 -0
- data/README.md +138 -98
- data/glossary.txt +4 -0
- data/lib/specdiff/compare.rb +2 -2
- data/lib/specdiff/diff.rb +10 -7
- data/lib/specdiff/differ/{hashdiff.rb → hash.rb} +53 -34
- data/lib/specdiff/differ.rb +1 -1
- data/lib/specdiff/hashprint.rb +82 -55
- data/lib/specdiff/inspect.rb +94 -1
- data/lib/specdiff/rspec.rb +8 -13
- data/lib/specdiff/version.rb +1 -1
- data/lib/specdiff/webmock.rb +5 -2
- data/lib/specdiff.rb +0 -5
- data/specdiff.gemspec +17 -10
- metadata +19 -29
- data/.rspec +0 -3
- data/.rubocop.yml +0 -203
- data/.tool-versions +0 -1
- data/Gemfile +0 -12
- data/Gemfile.lock +0 -76
- data/Rakefile +0 -12
- data/assets/webmock_json_with_specdiff.png +0 -0
- data/assets/webmock_text_with_specdiff.png +0 -0
- data/examples/rspec/.rspec +0 -2
- data/examples/rspec/Gemfile +0 -10
- data/examples/rspec/Gemfile.lock +0 -52
- data/examples/rspec/spec/example_spec.rb +0 -678
- data/examples/rspec/spec/spec_helper.rb +0 -68
- data/examples/webmock/Gemfile +0 -6
- data/examples/webmock/Gemfile.lock +0 -50
- data/examples/webmock/json.rb +0 -37
- data/examples/webmock/text.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16d43f0ff6248844478dc69d99ebb9e5de1212804fba96b859d947e446e5fae7
|
4
|
+
data.tar.gz: 0c9e973795920664617d2c57211e40569a0aee10ee6b6c35b02680a6789edf2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a21ca995b7418a69deaeb6c024f40b2fc6342200edc33f4179f979ce7555bf346b29012184784b59f0736ac55b089a55836dd21756e14100d7a76260b5c7d69a
|
7
|
+
data.tar.gz: e457b0266ddc5f11df693baae9594dd790498e687ce7a4d263b9b291108af2a14f976499dd3b1eee9294a6827e2f15f527c0ae451035c8f73dc2dbaea9245f2d
|
data/.gitignore
ADDED
data/CHANGELOG.md
CHANGED
@@ -7,7 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
-
## [0.3.0-
|
10
|
+
## [0.3.0] - 2024-04-16
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
|
14
|
+
- Hashprint now sorts keys (if all the keys are strings or symbols), leading to better text diffs from the hash differ.
|
15
|
+
|
16
|
+
## [0.3.0.rc2] - 2024-04-05
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
- Rework how hashdiff's output gets printed.
|
21
|
+
- Rework switching heuristic between text diff/hashdiff in hash differ.
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
|
25
|
+
- The RSpec integration now inspects hashes and arrays recursively. (Like rspec does by default)
|
26
|
+
- RSpec integration no longer breaks description output of matchers when using multi matchers (like .all or .and)
|
27
|
+
- The hash differ now deals with recursive hashes and arrays
|
28
|
+
- RSpec integration no longer breaks description output of matchers that are part of a diff inside an array or hash. (like when doing `match([have_attributes(...)])`)
|
29
|
+
|
30
|
+
## [0.3.0-rc1] - 2024-04-02
|
11
31
|
|
12
32
|
### Added
|
13
33
|
|
data/DEVELOPMENT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Development
|
2
|
+
|
3
|
+
## Glossary
|
4
|
+
|
5
|
+
Check out the [glossary](./glossary.txt) to make sure we are using the same
|
6
|
+
words for things.
|
7
|
+
|
8
|
+
## Setup
|
9
|
+
|
10
|
+
Install the software versions specified in `.tool-versions`.
|
11
|
+
|
12
|
+
Run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run
|
13
|
+
the tests and linter and make sure they're green
|
14
|
+
before starting to make your changes.
|
15
|
+
|
16
|
+
Run `bundle exec rake -AD` for a full list of all the available tasks you may use for development purposes.
|
17
|
+
|
18
|
+
Run `bundle exec rake` to run the tests and linter.
|
19
|
+
|
20
|
+
## Examples
|
21
|
+
|
22
|
+
The `examples/` directory is used as an ad-hoc integration testing method.
|
23
|
+
[Read more here](./examples/)
|
24
|
+
|
25
|
+
## Pull request checklist
|
26
|
+
|
27
|
+
- [ ] Make sure the tests are passing
|
28
|
+
- [ ] Make sure the linter is happy
|
29
|
+
- [ ] Make sure the `examples/` look the same or have suffered and improvement
|
30
|
+
- [ ] Update the unreleased section of the [changelog](./CHANGELOG.md) with a human readable explanation of your changes
|
31
|
+
|
32
|
+
## Architecture
|
33
|
+
|
34
|
+
High level description of the heuristic specdiff implements
|
35
|
+
|
36
|
+
1. receive 2 pieces of data: `a` and `b`
|
37
|
+
2. determine types for `a` and `b`
|
38
|
+
1. test against plugin types
|
39
|
+
2. test against built in types
|
40
|
+
3. fall back to the `:unknown` type
|
41
|
+
3. determine which differ is appropriate for the types
|
42
|
+
1. test against plugin differs
|
43
|
+
2. test against built in differs
|
44
|
+
3. fall back to the null differ (`NotFound`)
|
45
|
+
7. run the selected differ with a and b
|
46
|
+
8. package it into a `::Specdiff::Diff` which records the detected types
|
47
|
+
|
48
|
+
\<time passes>
|
49
|
+
|
50
|
+
6. at some point later when `#to_s` is invoked, stringify the diff using the differ's `#stringify`
|
51
|
+
|
52
|
+
## Maintainer's notes
|
53
|
+
|
54
|
+
### Release procedure
|
55
|
+
|
56
|
+
- [ ] unit tests are passing (`$ bundle exec rake test`)
|
57
|
+
- [ ] linter is happy (`$ bundle exec rake lint`)
|
58
|
+
- [ ] `examples/` look good
|
59
|
+
- [ ] check the package size using `$ bundle exec inspect_build`, make sure you haven't added any large files by accident
|
60
|
+
- [ ] update the version number in `version.rb`
|
61
|
+
- [ ] make sure the `examples/` `Gemfile.lock` files are updated (run bundle install)
|
62
|
+
- [ ] make sure `Gemfile.lock` is updated (run bundle install)
|
63
|
+
- [ ] move unreleased changes to the next version in the [changelog](./CHANGELOG.md)
|
64
|
+
- [ ] commit in the form "vX.X.X" and push
|
65
|
+
- [ ] make sure the pipeline is green
|
66
|
+
- [ ] `$ bundle exec rake release`
|
67
|
+
|
68
|
+
### Contemplated improvements (AKA the todo list)
|
69
|
+
|
70
|
+
- [ ] word diff
|
71
|
+
- [ ] yard documentation?
|
72
|
+
- [ ] 2.7.0 support
|
73
|
+
- [ ] actual integration tests (replacing `examples/`)
|
74
|
+
- [ ] real documentation for the plugin interface
|
data/README.md
CHANGED
@@ -1,36 +1,40 @@
|
|
1
1
|
# Specdiff
|
2
2
|
|
3
|
-
|
3
|
+
Specdiff is an opinionated gem that provides diffing between arbitrary data. It
|
4
|
+
was built in an effort to improve [WebMock](https://rubygems.org/gems/webmock)'s
|
5
|
+
and [RSpec](https://rubygems.org/gems/rspec)'s diff output, and
|
6
|
+
comes with integrations for both. It produces output that looks very similiar to
|
7
|
+
RSpec, but with better support for nested hash/array structures.
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
if they happen to both be json (including setting content type). This gem aims
|
8
|
-
to bring text diffing (ala rspec) to webmock via monkey-patch, as well as
|
9
|
-
dropping the content type requirement.
|
9
|
+
By default, specdiff will produce diff output for multiline text by using
|
10
|
+
[diff-lcs](https://rubygems.org/gems/diff-lcs):
|
10
11
|
|
11
|
-
|
12
|
-
diff between them.
|
12
|
+

|
13
13
|
|
14
|
-
|
14
|
+
It also diffs hashes and arrays:
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
diff
|
16
|
+
|  |  |
|
17
|
+
| --- | --- |
|
18
|
+
| Hashdiff based hash diff | Text based hash diff |
|
19
19
|
|
20
|
-
|
20
|
+
Specdiff will automatically switch between these two types of hash/array diff
|
21
|
+
output depending on what it thinks is more readable. The first type of output
|
22
|
+
showcased here comes from the
|
23
|
+
[hashdiff](https://rubygems.org/gems/hashdiff) gem, one of specdiff's
|
24
|
+
dependencies. The other is the *text diff* run on the output of *hashprint*.
|
25
|
+
Specdiff switches to using hashprint/text diff when it estimates that a lot of
|
26
|
+
the keys changed names, which is a point of weakness for hashdiff's output.
|
21
27
|
|
22
|
-
|
23
|
-
even if the request did not have the content type header:
|
24
|
-
|
25
|
-

|
28
|
+
## Installation
|
26
29
|
|
27
|
-
|
30
|
+
### Requirements
|
28
31
|
|
29
|
-
|
32
|
+
Specdiff is tested to work with:
|
33
|
+
- Ruby `>= 3.0`.
|
34
|
+
- RSpec `3.13.0`, `3.12.2`, `3.12.0`.
|
35
|
+
- WebMock `3.19.1`, `3.18.1`.
|
30
36
|
|
31
|
-
|
32
|
-
|
33
|
-
## Installation
|
37
|
+
### Use bundler
|
34
38
|
|
35
39
|
Add this line to your application's Gemfile:
|
36
40
|
|
@@ -40,123 +44,159 @@ gem "specdiff", require: false
|
|
40
44
|
|
41
45
|
And then execute:
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
Or install it yourself as:
|
47
|
+
`$ bundle install`
|
46
48
|
|
47
|
-
|
49
|
+
### Loading the gem and its integrations
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
Put the following in your `spec_helper.rb`. (Or equivalent initializer
|
52
|
-
for test environment) You probably don't want to load/use this gem in a release
|
53
|
-
environment.
|
51
|
+
Put the following in the initializer for your test environment (`spec_helper.rb`):
|
54
52
|
|
55
53
|
```rb
|
56
|
-
#
|
57
|
-
|
58
|
-
require "specdiff"
|
59
|
-
|
54
|
+
require "specdiff" # may be unneccessary if you're using bundler to load your gems
|
55
|
+
require "specdiff/rspec" # optional, enables RSpec integration
|
56
|
+
require "specdiff/webmock" # optional, enables WebMock integration
|
57
|
+
Specdiff.load!(:json) # optional, automatically detects json and uses the hash differ on it
|
60
58
|
|
61
|
-
# optionally, you can turn off terminal colors
|
62
59
|
Specdiff.configure do |config|
|
63
|
-
config.colorize = true
|
60
|
+
config.colorize = true # toggles color output
|
64
61
|
end
|
65
62
|
```
|
66
63
|
|
64
|
+
The monkey-patches should go after the gems they are patching.
|
65
|
+
|
66
|
+
*This gem is intended for use in test environments, not production or release environments.*
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
Specdiff has two integrations: [WebMock](https://rubygems.org/gems/webmock) and
|
71
|
+
[RSpec](https://rubygems.org/gems/rspec). These are the main way specdiff is
|
72
|
+
intended to be used.
|
73
|
+
|
74
|
+
### RSpec
|
75
|
+
|
76
|
+
The RSpec integration improves on RSpec's built in diffing functionality by
|
77
|
+
diffing nested hash/array structures with
|
78
|
+
[hashdiff](https://rubygems.org/gems/hashdiff). This produces clearer
|
79
|
+
output when deeply nested structures are involved:
|
80
|
+
|
81
|
+
|  |  |  |
|
82
|
+
| --- | --- | --- |
|
83
|
+
| Test source | RSpec's diff | Specdiff's diff |
|
84
|
+
|
85
|
+
In some cases specdiff may produce a text-based diff of deeply nested hashes or
|
86
|
+
arrays instead. This also represents an improvement over RSpec's text-based diff
|
87
|
+
of nested hashes/arrays, by virtue of custom pretty-printing code designed for
|
88
|
+
the text differ to work on:
|
89
|
+
|
90
|
+
|  |  |  |
|
91
|
+
| --- | --- | --- |
|
92
|
+
| Test source | RSpec's diff | Specdiff's diff |
|
93
|
+
|
94
|
+
The RSpec integration also prevents RSpec from truncating your data before
|
95
|
+
printing it (by replacing the inspect implementation), avoiding the necessity
|
96
|
+
of a diff in some instances:
|
97
|
+
|
98
|
+
|  |  |
|
99
|
+
| --- | --- |
|
100
|
+
| RSpec truncating your data | Specdiff preventing truncation |
|
101
|
+
|
102
|
+
(Although this is an instance where a "word diff" would be more helpful)
|
103
|
+
|
67
104
|
### WebMock
|
68
105
|
|
69
|
-
|
70
|
-
|
106
|
+
WebMock already ships with a dependency on
|
107
|
+
[hashdiff](https://rubygems.org/gems/hashdiff), providing diffs of json request
|
108
|
+
stubs. Specdiff prints hashdiff's output differently, and does not require the
|
109
|
+
content type to be specified:
|
71
110
|
|
72
|
-
|
111
|
+
(This requires you to enable the json plugin)
|
73
112
|
|
74
|
-
|
113
|
+
|  |  |  |
|
114
|
+
| --- | --- | --- |
|
115
|
+
| Source | WebMock | Specdiff |
|
75
116
|
|
76
|
-
|
77
|
-
diff = Specdiff.diff(something, and_something_else)
|
117
|
+
With specdiff enabled you also get text diffs where previously there were none:
|
78
118
|
|
79
|
-
|
80
|
-
|
81
|
-
|
119
|
+
|  |  |  |
|
120
|
+
| --- | --- | --- |
|
121
|
+
| Source | WebMock | Specdiff |
|
82
122
|
|
83
|
-
|
123
|
+
The WebMock integration continues to respect the `show_body_diff` setting in
|
124
|
+
WebMock:
|
84
125
|
|
85
126
|
```rb
|
86
|
-
#
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
Specdiff.load!(MyCustomType)
|
127
|
+
# this will cause diffs to not be produced, regardless of whether specdiff is
|
128
|
+
# loaded. body diffs are enabled by default in WebMock so you don't need to
|
129
|
+
# touch this.
|
130
|
+
WebMock.hide_body_diff!
|
91
131
|
```
|
92
132
|
|
93
|
-
|
133
|
+
### Direct usage
|
94
134
|
|
95
|
-
|
135
|
+
It is also possible to call into specdiff with arbitrary data.
|
136
|
+
This is essentially what the integrations do for you.
|
137
|
+
This is suitable if you are developing a library and intend to depend on
|
138
|
+
specdiff for diff output.
|
96
139
|
|
97
|
-
|
98
|
-
|
140
|
+
```rb
|
141
|
+
# Generate a diff, using all plugins available.
|
142
|
+
diff = Specdiff.diff(something, anything)
|
99
143
|
|
100
|
-
|
144
|
+
diff.empty? # => true/false, if true you probably don't want to actually show the diff
|
145
|
+
diff.to_s # => a String for showing to a developer who may or may not be scratching their head
|
101
146
|
|
102
|
-
|
147
|
+
# Generate an indented, pretty-printed string representation of a ruby hash.
|
148
|
+
printed_hash = Specdiff.hashprint(my_big_nested_hash)
|
149
|
+
printed_hash # => String
|
150
|
+
|
151
|
+
# Inspect something, but with prettier output for certain classes
|
152
|
+
# (Time/DateTime/BigDecimal). This is not indented. Usually defers to #inspect.
|
153
|
+
specdiff_inspected = Specdiff.diff_inspect(something)
|
154
|
+
specdiff_inspected # => String
|
155
|
+
```
|
103
156
|
|
104
|
-
|
157
|
+
Any methods or properties not documented here are considered private
|
158
|
+
implementation details.
|
105
159
|
|
106
|
-
|
160
|
+
## Plugins
|
107
161
|
|
108
|
-
|
162
|
+
It is possible to create and load plugins into specdiff to customize Specdiff's
|
163
|
+
behaviour. This was implemented in case you have special needs (e.g. diffing
|
164
|
+
things other than text, hashes, arrays and json).
|
109
165
|
|
110
|
-
|
166
|
+
### JSON
|
111
167
|
|
112
|
-
|
168
|
+
The built-in json plugin is optional, and loaded like so:
|
113
169
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
2. test against built in types
|
118
|
-
3. fall back to the `:unknown` type
|
119
|
-
3. determine which differ is appropriate for the types
|
120
|
-
1. test against plugin differs
|
121
|
-
2. test against built in differs
|
122
|
-
3. fall back to the null differ (`NotFound`)
|
123
|
-
7. run the selected differ with a and b
|
124
|
-
8. package it into a `::Specdiff::Diff` which records the detected types
|
170
|
+
```rb
|
171
|
+
Specdiff.load!(:json)
|
172
|
+
```
|
125
173
|
|
126
|
-
|
174
|
+
The JSON plugin attempts to parse strings into json using `JSON.parse`. If
|
175
|
+
successful, it considers that string a json, and will use the parse result as if
|
176
|
+
it had been passed directly to specdiff in the first place.
|
127
177
|
|
128
|
-
|
178
|
+
### Defining your own plugins
|
129
179
|
|
130
|
-
|
180
|
+
While other plugins are loaded by simply passing them to `::load!`:
|
131
181
|
|
132
|
-
|
182
|
+
```rb
|
183
|
+
class MySpecdiffPlugin
|
184
|
+
# Read the source code to figure out how plugins work. Sorry.
|
185
|
+
end
|
133
186
|
|
134
|
-
|
135
|
-
|
136
|
-
- [ ] `examples/` look good
|
137
|
-
- [ ] update the version number in `version.rb`
|
138
|
-
- [ ] make sure the `examples/` `Gemfile.lock` files are updated (run bundle install)
|
139
|
-
- [ ] make sure `Gemfile.lock` is updated (run bundle install)
|
140
|
-
- [ ] move unreleased changes to the next version in the [changelog](./CHANGELOG.md)
|
141
|
-
- [ ] commit in the form "vX.X.X" and push
|
142
|
-
- [ ] make sure the pipeline is green
|
143
|
-
- [ ] `$ bundle exec rake release`
|
187
|
+
Specdiff.load!(MySpecdiffPlugin)
|
188
|
+
```
|
144
189
|
|
145
|
-
|
190
|
+
This was intended to allow implementing (potentially janky and inefficient)
|
191
|
+
plugins to help diff XML, but in practice the text differ was good enough for
|
192
|
+
diffing indented XML.
|
146
193
|
|
147
194
|
## Contributing
|
148
195
|
|
196
|
+
Read [DEVELOPMENT.md](./DEVELOPMENT.md) for current development practices.
|
197
|
+
|
149
198
|
Bug reports and pull requests are welcome on GitHub at https://github.com/odinhb/specdiff.
|
150
199
|
|
151
200
|
## License
|
152
201
|
|
153
202
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
154
|
-
|
155
|
-
## TODO
|
156
|
-
|
157
|
-
This documents potential improvements/issues I know about or have thought about.
|
158
|
-
|
159
|
-
- [ ] test the webmock monkey-patch. currently there is an empty webmock_spec.rb (should we do this using rspec?) and the examples/ directory contains a few webmock examples (which are a good idea to run before releasing) but it would be nice to have the pipeline fail if it doesn't work for whatever reason
|
160
|
-
- [ ] finalize plugin interface (are the methods named intuitively? should we split type detector definitions and differ definitions?)
|
161
|
-
- [ ] document how to define a plugin properly (instead of just linking to the source code)
|
162
|
-
- [ ] is the stringification of hashdiff's output really better than pretty print? or just more wordy? (the colors are definitely nice)
|
data/glossary.txt
CHANGED
@@ -44,3 +44,7 @@ side
|
|
44
44
|
compare
|
45
45
|
the procedure that implements the main function of specdiff including
|
46
46
|
accounting for any plugin types and differs
|
47
|
+
|
48
|
+
integration
|
49
|
+
refers to the provided monkey-patches and conscious relationship between
|
50
|
+
specdiff and another gem or piece of software it was intended to work with.
|
data/lib/specdiff/compare.rb
CHANGED
@@ -80,9 +80,9 @@ private
|
|
80
80
|
elsif a.type == :text && b.type == :text
|
81
81
|
Specdiff::Differ::Text
|
82
82
|
elsif a.type == :hash && b.type == :hash
|
83
|
-
Specdiff::Differ::
|
83
|
+
Specdiff::Differ::Hash
|
84
84
|
elsif a.type == :array && b.type == :array
|
85
|
-
Specdiff::Differ::
|
85
|
+
Specdiff::Differ::Hash
|
86
86
|
else
|
87
87
|
Specdiff::Differ::NotFound
|
88
88
|
end
|
data/lib/specdiff/diff.rb
CHANGED
@@ -19,12 +19,15 @@
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def inspect
|
22
|
-
if empty?
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
raw_diff = if empty?
|
23
|
+
"empty"
|
24
|
+
elsif differ == ::Specdiff::Differ::Text
|
25
|
+
bytes = raw&.bytesize || 0
|
26
|
+
"#{bytes} bytes of #raw diff"
|
27
|
+
else
|
28
|
+
"#{raw.inspect}"
|
29
|
+
end
|
30
|
+
|
31
|
+
"<Specdiff::Diff (#{a.type}/#{b.type}) (#{differ}) (#{raw_diff})>"
|
29
32
|
end
|
30
33
|
end
|
@@ -1,19 +1,27 @@
|
|
1
1
|
require "hashdiff"
|
2
2
|
|
3
|
-
class Specdiff::Differ::
|
3
|
+
class Specdiff::Differ::Hash
|
4
4
|
extend ::Specdiff::Colorize
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
# The percentage of changes that must (potentially) be a key rename in a hash
|
7
|
+
# for text diffing to kick in. Expressed as a fraction of 1.
|
8
|
+
KEY_CHANGE_PERCENTAGE_THRESHOLD = 0.8
|
8
9
|
|
9
|
-
|
10
|
+
# The number of changes that must be detected by hashdiff before we print some
|
11
|
+
# extra newlines to better group extra/missing/new_values visually.
|
12
|
+
TOTAL_CHANGES_FOR_GROUPING_THRESHOLD = 9
|
10
13
|
|
11
14
|
def self.diff(a, b)
|
12
15
|
# array_path: true returns the path as an array, which differentiates
|
13
16
|
# between symbol keys and string keys in hashes, while the string
|
14
17
|
# representation does not.
|
18
|
+
|
15
19
|
# hmm it really seems like use_lcs: true gives much less human-readable
|
16
20
|
# (human-comprehensible) output when arrays are involved.
|
21
|
+
|
22
|
+
# use_lcs: true may also cause Hashdiff to use a lot of memory when BIG
|
23
|
+
# arrays are involved: https://github.com/liufengyun/hashdiff/issues/49
|
24
|
+
# so we might as well avoid that problem altogether.
|
17
25
|
hashdiff_diff = ::Hashdiff.diff(
|
18
26
|
a.value, b.value,
|
19
27
|
array_path: true,
|
@@ -24,54 +32,60 @@ class Specdiff::Differ::Hashdiff
|
|
24
32
|
|
25
33
|
change_percentage = _calculate_change_percentage(hashdiff_diff)
|
26
34
|
|
27
|
-
if change_percentage
|
35
|
+
if change_percentage <= KEY_CHANGE_PERCENTAGE_THRESHOLD
|
28
36
|
hashdiff_diff
|
29
37
|
else
|
30
38
|
a_text = ::Specdiff.hashprint(a.value)
|
31
39
|
b_text = ::Specdiff.hashprint(b.value)
|
32
40
|
|
33
|
-
|
41
|
+
text_diff = ::Specdiff.diff(a_text, b_text)
|
34
42
|
|
35
|
-
if
|
43
|
+
if text_diff.empty?
|
36
44
|
[]
|
37
45
|
else
|
38
|
-
|
39
|
-
diff.b.type = b.type
|
40
|
-
|
41
|
-
diff
|
46
|
+
text_diff
|
42
47
|
end
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
46
51
|
def self._calculate_change_percentage(hashdiff_diff)
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
extra_keys = hashdiff_diff.count { |element| element[0] == "+" }
|
53
|
+
missing_keys = hashdiff_diff.count { |element| element[0] == "-" }
|
54
|
+
new_values = hashdiff_diff.count { |element| element[0] == "~" }
|
50
55
|
# puts "hashdiff_diff: #{hashdiff_diff.inspect}"
|
51
|
-
# puts "
|
52
|
-
# puts "
|
53
|
-
# puts "
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# puts "
|
65
|
-
|
66
|
-
|
56
|
+
# puts "extra_keys: #{extra_keys.inspect}"
|
57
|
+
# puts "missing_keys: #{missing_keys.inspect}"
|
58
|
+
# puts "new_values: #{new_values.inspect}"
|
59
|
+
|
60
|
+
potential_changed_keys = [extra_keys, missing_keys].min
|
61
|
+
adjusted_extra_keys = extra_keys - potential_changed_keys
|
62
|
+
adjusted_missing_keys = missing_keys - potential_changed_keys
|
63
|
+
# puts "potential_changed_keys: #{potential_changed_keys.inspect}"
|
64
|
+
# puts "adjusted_extra_keys: #{adjusted_extra_keys.inspect}"
|
65
|
+
# puts "adjusted_missing_keys: #{adjusted_missing_keys.inspect}"
|
66
|
+
|
67
|
+
non_changed_keys = adjusted_extra_keys + adjusted_missing_keys + new_values
|
68
|
+
total_changes = non_changed_keys + potential_changed_keys
|
69
|
+
# puts "non_changed_keys: #{non_changed_keys.inspect}"
|
70
|
+
# puts "total_changes: #{total_changes.inspect}"
|
71
|
+
|
72
|
+
key_change_fraction = Rational(potential_changed_keys, total_changes)
|
73
|
+
key_change_percentage = key_change_fraction.to_f
|
74
|
+
# puts "key_change_fraction: #{key_change_fraction.inspect}"
|
75
|
+
# puts "key_change_percentage: #{key_change_percentage.inspect}"
|
76
|
+
|
77
|
+
key_change_percentage
|
67
78
|
end
|
68
79
|
|
69
80
|
def self.empty?(diff)
|
70
81
|
diff.raw.empty?
|
71
82
|
end
|
72
83
|
|
84
|
+
NEWLINE = "\n"
|
85
|
+
|
73
86
|
def self.stringify(diff)
|
74
87
|
result = +""
|
88
|
+
return result if diff.empty?
|
75
89
|
|
76
90
|
total_changes = diff.raw.size
|
77
91
|
group_with_newlines = total_changes >= TOTAL_CHANGES_FOR_GROUPING_THRESHOLD
|
@@ -92,6 +106,9 @@ class Specdiff::Differ::Hashdiff
|
|
92
106
|
additions = changes_grouped_by_type["+"] || []
|
93
107
|
value_changes = changes_grouped_by_type["~"] || []
|
94
108
|
|
109
|
+
result << "@@ +#{additions.size}/-#{deletions.size}/~#{value_changes.size} @@"
|
110
|
+
result << NEWLINE
|
111
|
+
|
95
112
|
deletions.each do |change|
|
96
113
|
value = change[2]
|
97
114
|
path = _stringify_path(change[1])
|
@@ -108,7 +125,7 @@ class Specdiff::Differ::Hashdiff
|
|
108
125
|
value = change[2]
|
109
126
|
path = _stringify_path(change[1])
|
110
127
|
|
111
|
-
result << "
|
128
|
+
result << " extra key: #{path} (#{::Specdiff.diff_inspect(value)})"
|
112
129
|
result << NEWLINE
|
113
130
|
end
|
114
131
|
|
@@ -123,17 +140,19 @@ class Specdiff::Differ::Hashdiff
|
|
123
140
|
|
124
141
|
from_inspected = ::Specdiff.diff_inspect(from)
|
125
142
|
to_inspected = ::Specdiff.diff_inspect(to)
|
126
|
-
result << "
|
143
|
+
result << " new value: #{path} (#{from_inspected} -> #{to_inspected})"
|
127
144
|
result << NEWLINE
|
128
145
|
end
|
129
146
|
|
130
147
|
colorize_by_line(result) do |line|
|
131
148
|
if line.start_with?("missing key:")
|
132
149
|
red(line)
|
133
|
-
elsif line.start_with?(
|
150
|
+
elsif line.start_with?(" extra key:")
|
134
151
|
green(line)
|
135
|
-
elsif line.start_with?("
|
152
|
+
elsif line.start_with?(" new value:")
|
136
153
|
yellow(line)
|
154
|
+
elsif line.start_with?("@@")
|
155
|
+
cyan(line)
|
137
156
|
else
|
138
157
|
reset_color(line)
|
139
158
|
end
|
data/lib/specdiff/differ.rb
CHANGED