specdiff 0.2.0 → 0.3.0.rc2

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,134 @@
1
+ class Specdiff::Inspect
2
+ TIME_FORMAT = "%Y-%m-%d %H:%M:%S %z"
3
+ DATE_FORMAT = "%Y-%m-%d"
4
+
5
+ def self.call(...)
6
+ new.call(...)
7
+ end
8
+
9
+ def initialize
10
+ @recursion_trail = []
11
+ end
12
+
13
+ # #=== allows us to rely on Module implementing #=== instead of relying on the
14
+ # thing (which could be any kind of wacky object) having to implement
15
+ # #is_a? or #kind_of?
16
+ def call(thing)
17
+ if Hash === thing || Array === thing
18
+ recursive_replace_inspect(thing).inspect
19
+ elsif Time === thing
20
+ "#<Time: #{thing.strftime(TIME_FORMAT)}>"
21
+ elsif DateTime === thing
22
+ "#<DateTime: #{thing.rfc3339}>"
23
+ elsif Date === thing
24
+ "#<Date: #{thing.strftime(DATE_FORMAT)}>"
25
+ elsif defined?(BigDecimal) && BigDecimal === thing
26
+ "#<BigDecimal: #{thing.to_s('F')}>"
27
+ elsif rspec_matcher?(thing)
28
+ # Turns out rspec depends on the recursion in its inspection logic to
29
+ # print the "description" of rspec matchers, in situations such as when
30
+ # using multi-matchers (.all, .or or .and), or when nesting them inside
31
+ # eachother (such as match([have_attributes(...)])).
32
+ thing.description
33
+ else
34
+ begin
35
+ thing.inspect
36
+ rescue NoMethodError
37
+ inspect_anyway(thing)
38
+ end
39
+ end
40
+ end
41
+
42
+ private def rspec_matcher?(thing)
43
+ defined?(::Specdiff::RSpecIntegration) &&
44
+ ::RSpec::Support.is_a_matcher?(thing) &&
45
+ thing.respond_to?(:description)
46
+ end
47
+
48
+ private def inspect_anyway(uninspectable)
49
+ "#<uninspectable #{class_of(uninspectable)}>"
50
+ end
51
+
52
+ private def class_of(uninspectable)
53
+ uninspectable.class
54
+ rescue NoMethodError
55
+ singleton_class = class << uninspectable; self; end
56
+ singleton_class.ancestors
57
+ .find { |ancestor| !ancestor.equal?(singleton_class) }
58
+ end
59
+
60
+ # recursion below
61
+
62
+ InspectWrapper = Struct.new(:text) do
63
+ def inspect
64
+ text
65
+ end
66
+ end
67
+
68
+ private def recursive_replace_inspect(thing)
69
+ if deja_vu?(thing)
70
+ # I've just been in this place before
71
+ # And I know it's my time to go...
72
+ return InspectWrapper.new(inspect_deja_vu(thing))
73
+ end
74
+
75
+ case thing
76
+ when Array
77
+ track_recursion(thing) do
78
+ thing.map { |element| recursive_replace_inspect(element) }
79
+ end
80
+ when Hash
81
+ track_recursion(thing) do
82
+ new_hash = {}
83
+
84
+ thing.each do |key, value|
85
+ new_hash[recursive_replace_inspect(key)] = recursive_replace_inspect(value)
86
+ end
87
+
88
+ new_hash
89
+ end
90
+ else
91
+ wrap_inspect(thing)
92
+ end
93
+ rescue SystemStackError => e
94
+ wrap_inspect(
95
+ thing,
96
+ text: "#{e.class}: #{e.message}\n\n" \
97
+ "encountered when inspecting #{thing.inspect}"
98
+ )
99
+ end
100
+
101
+ private def track_recursion(thing)
102
+ @recursion_trail.push(thing)
103
+ result = yield
104
+ @recursion_trail.pop
105
+ result
106
+ end
107
+
108
+ private def deja_vu?(current_place)
109
+ @recursion_trail.any? { |previous_place| previous_place == current_place }
110
+ end
111
+
112
+ private def wrap_inspect(thing, text: :_use_diff_inspect)
113
+ text = call(thing) if text == :_use_diff_inspect
114
+ InspectWrapper.new(text)
115
+ end
116
+
117
+ # The stdlib inspect code returns this when you have recursive structures.
118
+ STANDARD_INSPECT_RECURSIVE_ARRAY = "[...]".freeze
119
+ STANDARD_INSPECT_RECURSIVE_HASH = "{...}".freeze
120
+
121
+ private def inspect_deja_vu(thing)
122
+ case thing
123
+ when Array
124
+ # "#<Array ##{thing.object_id}>"
125
+ STANDARD_INSPECT_RECURSIVE_ARRAY
126
+ when Hash
127
+ # "#<Hash ##{thing.object_id}>"
128
+ STANDARD_INSPECT_RECURSIVE_HASH
129
+ else
130
+ # this should never happen
131
+ raise "Specdiff::Inspect missing deja vu for: #{thing.inspect}"
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,26 @@
1
+ raise "rspec must be required before specdiff/rspec!" unless defined?(RSpec)
2
+ raise "RSpec::Support is missing????" unless defined?(RSpec::Support)
3
+
4
+ class RSpec::Support::Differ
5
+ alias old_diff diff
6
+
7
+ def diff(actual, expected)
8
+ diff = ::Specdiff.diff(expected, actual)
9
+ if diff.empty?
10
+ ""
11
+ else
12
+ "\n#{diff}"
13
+ end
14
+ end
15
+ end
16
+
17
+ # This stops rspec from truncating strings w/ ellipsis, as well as making the
18
+ # "inspect" output consistent with specdiff's.
19
+ class RSpec::Support::ObjectFormatter
20
+ def format(object)
21
+ ::Specdiff.diff_inspect(object)
22
+ end
23
+ end
24
+
25
+ # marker for successfully loading this integration
26
+ class Specdiff::RSpecIntegration; end # rubocop: disable Lint/EmptyClass
@@ -1,3 +1,3 @@
1
1
  module Specdiff
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0.rc2"
3
3
  end
@@ -1 +1,43 @@
1
- require_relative "webmock/request_body_diff"
1
+ raise "webmock must be required before specdiff/webmock" unless defined?(WebMock)
2
+
3
+ module WebMock
4
+ class RequestBodyDiff
5
+ def initialize(request_signature, request_stub)
6
+ @request_signature = request_signature
7
+ @request_stub = request_stub
8
+ end
9
+
10
+ PrettyPrintableThingy = Struct.new(:specdiff) do
11
+ # webmock does not print the diff if it responds true to this.
12
+ def empty?
13
+ specdiff.empty?
14
+ end
15
+
16
+ # webmock prints the diff by passing us to PP.pp, which in turn uses this
17
+ # method.
18
+ def pretty_print(pp)
19
+ pp.text("\r") # remove a space that isn't supposed to be there
20
+ pp.text(specdiff.to_s)
21
+ end
22
+ end
23
+
24
+ def body_diff
25
+ specdiff = Specdiff.diff(request_stub_body, request_signature.body)
26
+ PrettyPrintableThingy.new(specdiff)
27
+ end
28
+
29
+ attr_reader :request_signature, :request_stub
30
+ private :request_signature, :request_stub
31
+
32
+ private
33
+
34
+ def request_stub_body
35
+ request_stub.request_pattern &&
36
+ request_stub.request_pattern.body_pattern &&
37
+ request_stub.request_pattern.body_pattern.pattern
38
+ end
39
+ end
40
+ end
41
+
42
+ # marker for successfully loading this integration
43
+ class Specdiff::WebmockIntegration; end # rubocop: disable Lint/EmptyClass
data/lib/specdiff.rb CHANGED
@@ -1,13 +1,27 @@
1
1
  require_relative "specdiff/version"
2
2
  require_relative "specdiff/config"
3
3
  require_relative "specdiff/colorize"
4
+ require_relative "specdiff/inspect"
5
+ require_relative "specdiff/hashprint"
4
6
  require_relative "specdiff/compare"
5
7
 
6
8
  module Specdiff
7
- # Diff two things
9
+ # Compare two things, returns a Specdiff::Diff.
8
10
  def self.diff(...)
9
11
  ::Specdiff::Compare.call(...)
10
12
  end
13
+
14
+ # Use Specdiff's implementation for turning a nested hash/array structure
15
+ # into a string. Optimized for diff quality.
16
+ def self.hashprint(...)
17
+ ::Specdiff::Hashprint.call(...)
18
+ end
19
+
20
+ # Use Specdiff's inspect, which has some extra logic layered in for
21
+ # dates/time/bigdecimal. For most objects this just delegates to #inspect.
22
+ def self.diff_inspect(...)
23
+ ::Specdiff::Inspect.call(...)
24
+ end
11
25
  end
12
26
 
13
27
  require_relative "specdiff/diff"
data/specdiff.gemspec CHANGED
@@ -17,13 +17,12 @@ Gem::Specification.new do |spec|
17
17
  spec.metadata["source_code_uri"] = spec.homepage
18
18
  spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
19
19
 
20
- # Specify which files should be added to the gem when it is released.
21
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
- `git ls-files -z`.split("\x0").reject do |f|
24
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
- end
26
- end
20
+ spec.files =
21
+ Dir["*.gemspec"] +
22
+ Dir["*.md"] +
23
+ Dir["*.txt"] +
24
+ Dir[".gitignore"] +
25
+ Dir["lib/**/*.rb"]
27
26
  spec.require_paths = ["lib"]
28
27
 
29
28
  spec.add_dependency "hashdiff", "~> 1.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: specdiff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Odin Heggvold Bekkelund
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-08 00:00:00.000000000 Z
11
+ date: 2024-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashdiff
@@ -38,26 +38,17 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.5'
41
- description:
41
+ description:
42
42
  email:
43
43
  - odinhb@protonmail.com
44
44
  executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
- - ".rspec"
49
- - ".rubocop.yml"
50
- - ".tool-versions"
48
+ - ".gitignore"
51
49
  - CHANGELOG.md
52
- - Gemfile
53
- - Gemfile.lock
54
50
  - LICENSE.txt
55
51
  - README.md
56
- - Rakefile
57
- - examples/webmock/Gemfile
58
- - examples/webmock/Gemfile.lock
59
- - examples/webmock/json.rb
60
- - examples/webmock/text.rb
61
52
  - glossary.txt
62
53
  - lib/specdiff.rb
63
54
  - lib/specdiff/colorize.rb
@@ -65,15 +56,17 @@ files:
65
56
  - lib/specdiff/config.rb
66
57
  - lib/specdiff/diff.rb
67
58
  - lib/specdiff/differ.rb
68
- - lib/specdiff/differ/hashdiff.rb
59
+ - lib/specdiff/differ/hash.rb
69
60
  - lib/specdiff/differ/not_found.rb
70
61
  - lib/specdiff/differ/text.rb
62
+ - lib/specdiff/hashprint.rb
63
+ - lib/specdiff/inspect.rb
71
64
  - lib/specdiff/plugin.rb
72
65
  - lib/specdiff/plugins.rb
73
66
  - lib/specdiff/plugins/json.rb
67
+ - lib/specdiff/rspec.rb
74
68
  - lib/specdiff/version.rb
75
69
  - lib/specdiff/webmock.rb
76
- - lib/specdiff/webmock/request_body_diff.rb
77
70
  - specdiff.gemspec
78
71
  homepage: https://github.com/odinhb/specdiff
79
72
  licenses:
@@ -82,7 +75,7 @@ metadata:
82
75
  homepage_uri: https://github.com/odinhb/specdiff
83
76
  source_code_uri: https://github.com/odinhb/specdiff
84
77
  changelog_uri: https://github.com/odinhb/specdiff/CHANGELOG.md
85
- post_install_message:
78
+ post_install_message:
86
79
  rdoc_options: []
87
80
  require_paths:
88
81
  - lib
@@ -93,12 +86,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
93
86
  version: 3.0.0
94
87
  required_rubygems_version: !ruby/object:Gem::Requirement
95
88
  requirements:
96
- - - ">="
89
+ - - ">"
97
90
  - !ruby/object:Gem::Version
98
- version: '0'
91
+ version: 1.3.1
99
92
  requirements: []
100
- rubygems_version: 3.4.10
101
- signing_key:
93
+ rubygems_version: 3.2.22
94
+ signing_key:
102
95
  specification_version: 4
103
96
  summary: Improved request body diffs for webmock
104
97
  test_files: []
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,203 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 3.0
3
-
4
- require:
5
- - rubocop-rspec
6
- - rubocop-rake
7
-
8
- ##
9
- # LAYOUT COPS
10
- ##
11
-
12
- Layout:
13
- Enabled: false
14
-
15
- Layout/LineLength:
16
- Enabled: true
17
- Max: 120
18
-
19
- Layout/SpaceInsideBlockBraces:
20
- Enabled: true
21
- EnforcedStyle: space
22
- SpaceBeforeBlockParameters: no_space
23
- Layout/SpaceInsideHashLiteralBraces:
24
- Enabled: true
25
- EnforcedStyle: no_space
26
-
27
- Layout/MultilineMethodCallIndentation:
28
- Enabled: true
29
- EnforcedStyle: indented
30
-
31
- ##
32
- # STYLE COPS
33
- ##
34
-
35
- Style:
36
- Enabled: false
37
-
38
- Style/TrailingCommaInArrayLiteral:
39
- Enabled: true
40
- EnforcedStyleForMultiline: consistent_comma
41
-
42
- Style/TrailingCommaInHashLiteral:
43
- Enabled: true
44
- EnforcedStyleForMultiline: consistent_comma
45
-
46
- Style/SpecialGlobalVars:
47
- Enabled: true
48
- EnforcedStyle: use_english_names
49
-
50
- ##
51
- # NAMING COPS
52
- ##
53
-
54
- Naming/BlockForwarding: # new in 1.24
55
- Enabled: false
56
- Naming/MethodParameterName:
57
- Enabled: false
58
-
59
- ##
60
- # SECURITY COPS
61
- ##
62
-
63
- Security/CompoundHash: # new in 1.28
64
- Enabled: true
65
- Security/IoMethods: # new in 1.22
66
- Enabled: true
67
-
68
- ##
69
- # LINT COPS
70
- ##
71
-
72
- Lint/SuppressedException:
73
- AllowComments: true
74
- Lint/AmbiguousAssignment: # new in 1.7
75
- Enabled: true
76
- Lint/AmbiguousOperatorPrecedence: # new in 1.21
77
- Enabled: true
78
- Lint/AmbiguousRange: # new in 1.19
79
- Enabled: true
80
- Lint/ConstantOverwrittenInRescue: # new in 1.31
81
- Enabled: true
82
- Lint/DeprecatedConstants: # new in 1.8
83
- Enabled: true
84
- Lint/DuplicateBranch: # new in 1.3
85
- Enabled: true
86
- IgnoreLiteralBranches: true
87
- IgnoreConstantBranches: true
88
- Lint/DuplicateMagicComment: # new in 1.37
89
- Enabled: true
90
- Lint/DuplicateMatchPattern: # new in 1.50
91
- Enabled: true
92
- Lint/DuplicateRegexpCharacterClassElement: # new in 1.1
93
- Enabled: true
94
- Lint/EmptyBlock: # new in 1.1
95
- Enabled: true
96
- Lint/EmptyClass: # new in 1.3
97
- Enabled: true
98
- Lint/EmptyInPattern: # new in 1.16
99
- Enabled: true
100
- Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21
101
- Enabled: true
102
- Lint/LambdaWithoutLiteralBlock: # new in 1.8
103
- Enabled: true
104
- Lint/MixedCaseRange: # new in 1.53
105
- Enabled: true
106
- Lint/NoReturnInBeginEndBlocks: # new in 1.2
107
- Enabled: true
108
- Lint/NonAtomicFileOperation: # new in 1.31
109
- Enabled: true
110
- Lint/NumberedParameterAssignment: # new in 1.9
111
- Enabled: true
112
- Lint/OrAssignmentToConstant: # new in 1.9
113
- Enabled: true
114
- Lint/RedundantDirGlobSort: # new in 1.8
115
- Enabled: true
116
- Lint/RedundantRegexpQuantifiers: # new in 1.53
117
- Enabled: true
118
- Lint/RefinementImportMethods: # new in 1.27
119
- Enabled: true
120
- Lint/RequireRangeParentheses: # new in 1.32
121
- Enabled: true
122
- Lint/RequireRelativeSelfPath: # new in 1.22
123
- Enabled: true
124
- Lint/SymbolConversion: # new in 1.9
125
- Enabled: true
126
- Lint/ToEnumArguments: # new in 1.1
127
- Enabled: true
128
- Lint/TripleQuotes: # new in 1.9
129
- Enabled: true
130
- Lint/UnexpectedBlockArity: # new in 1.5
131
- Enabled: true
132
- Lint/UnmodifiedReduceAccumulator: # new in 1.1
133
- Enabled: true
134
- Lint/UselessRescue: # new in 1.43
135
- Enabled: true
136
- Lint/UselessRuby2Keywords: # new in 1.23
137
- Enabled: true
138
-
139
- ##
140
- # METRICS COPS
141
- ##
142
-
143
- Metrics:
144
- Enabled: false
145
-
146
- ##
147
- # GEMSPEC COPS
148
- ##
149
-
150
- Gemspec/DeprecatedAttributeAssignment: # new in 1.30
151
- Enabled: true
152
- Gemspec/DevelopmentDependencies: # new in 1.44
153
- Enabled: true
154
- Gemspec/RequireMFA: # new in 1.23
155
- Enabled: false
156
- Gemspec/OrderedDependencies:
157
- Enabled: false
158
-
159
- ##
160
- # BUNDLER COPS
161
- ##
162
-
163
- Bundler/OrderedGems:
164
- Enabled: false
165
-
166
- ##
167
- # RSPEC COPS
168
- ##
169
-
170
- # documentation can be found in
171
- # http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/<name>
172
- RSpec:
173
- Enabled: false
174
-
175
- RSpec/HookArgument:
176
- Enabled: true
177
- EnforcedStyle: each
178
- RSpec/MessageSpies:
179
- Enabled: true
180
- EnforcedStyle: receive
181
-
182
- ##
183
- # CAPYBARA COPS
184
- ##
185
-
186
- # cant remember installing capybara but whatever
187
- Capybara:
188
- Enabled: false
189
-
190
- ##
191
- # FACTORYBOT COPS
192
- ##
193
-
194
- # cant remember installing factorybot but whatever
195
- FactoryBot:
196
- Enabled: false
197
-
198
- ##
199
- # RAKE COPS
200
- ##
201
-
202
- Rake:
203
- Enabled: true
data/.tool-versions DELETED
@@ -1 +0,0 @@
1
- ruby 3.2.2
data/Gemfile DELETED
@@ -1,12 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in specdiff.gemspec
4
- gemspec
5
-
6
- gem "rake", "~> 13.0"
7
-
8
- gem "rspec", "~> 3.0"
9
-
10
- gem "rubocop", "~> 1.21"
11
- gem "rubocop-rspec", '~> 2.25'
12
- gem 'rubocop-rake', '~> 0.6.0'
data/Gemfile.lock DELETED
@@ -1,76 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- specdiff (0.2.0)
5
- diff-lcs (~> 1.5)
6
- hashdiff (~> 1.0)
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- ast (2.4.2)
12
- diff-lcs (1.5.0)
13
- hashdiff (1.0.1)
14
- json (2.6.3)
15
- language_server-protocol (3.17.0.3)
16
- parallel (1.23.0)
17
- parser (3.2.2.4)
18
- ast (~> 2.4.1)
19
- racc
20
- racc (1.7.3)
21
- rainbow (3.1.1)
22
- rake (13.1.0)
23
- regexp_parser (2.8.2)
24
- rexml (3.2.6)
25
- rspec (3.12.0)
26
- rspec-core (~> 3.12.0)
27
- rspec-expectations (~> 3.12.0)
28
- rspec-mocks (~> 3.12.0)
29
- rspec-core (3.12.2)
30
- rspec-support (~> 3.12.0)
31
- rspec-expectations (3.12.3)
32
- diff-lcs (>= 1.2.0, < 2.0)
33
- rspec-support (~> 3.12.0)
34
- rspec-mocks (3.12.6)
35
- diff-lcs (>= 1.2.0, < 2.0)
36
- rspec-support (~> 3.12.0)
37
- rspec-support (3.12.1)
38
- rubocop (1.57.2)
39
- json (~> 2.3)
40
- language_server-protocol (>= 3.17.0)
41
- parallel (~> 1.10)
42
- parser (>= 3.2.2.4)
43
- rainbow (>= 2.2.2, < 4.0)
44
- regexp_parser (>= 1.8, < 3.0)
45
- rexml (>= 3.2.5, < 4.0)
46
- rubocop-ast (>= 1.28.1, < 2.0)
47
- ruby-progressbar (~> 1.7)
48
- unicode-display_width (>= 2.4.0, < 3.0)
49
- rubocop-ast (1.30.0)
50
- parser (>= 3.2.1.0)
51
- rubocop-capybara (2.19.0)
52
- rubocop (~> 1.41)
53
- rubocop-factory_bot (2.24.0)
54
- rubocop (~> 1.33)
55
- rubocop-rake (0.6.0)
56
- rubocop (~> 1.0)
57
- rubocop-rspec (2.25.0)
58
- rubocop (~> 1.40)
59
- rubocop-capybara (~> 2.17)
60
- rubocop-factory_bot (~> 2.22)
61
- ruby-progressbar (1.13.0)
62
- unicode-display_width (2.5.0)
63
-
64
- PLATFORMS
65
- x86_64-linux
66
-
67
- DEPENDENCIES
68
- rake (~> 13.0)
69
- rspec (~> 3.0)
70
- rubocop (~> 1.21)
71
- rubocop-rake (~> 0.6.0)
72
- rubocop-rspec (~> 2.25)
73
- specdiff!
74
-
75
- BUNDLED WITH
76
- 2.3.5
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:test)
7
-
8
- require "rubocop/rake_task"
9
-
10
- RuboCop::RakeTask.new(:lint)
11
-
12
- task default: %i[test lint]
@@ -1,6 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "specdiff", require: false, path: "../../"
4
-
5
- gem "webmock", require: false
6
- gem "http"