specdiff 0.2.0 → 0.3.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"