canon 0.3.26 → 0.3.28
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/.rubocop.yml +2 -0
- data/README.adoc +30 -2
- data/docs/features/regenerate-expected.adoc +157 -0
- data/lib/canon/comparison/diff_node_builder.rb +9 -0
- data/lib/canon/comparison/xml_comparator/attribute_filter.rb +34 -4
- data/lib/canon/config/env_schema.rb +6 -0
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +4 -296
- data/lib/canon/rebaseliner/atomic_writer.rb +32 -0
- data/lib/canon/rebaseliner/call_site_resolver.rb +126 -0
- data/lib/canon/rebaseliner/heredoc_locator.rb +140 -0
- data/lib/canon/rebaseliner/heredoc_rewriter.rb +56 -0
- data/lib/canon/rebaseliner/heredoc_target.rb +19 -0
- data/lib/canon/rebaseliner/logger.rb +24 -0
- data/lib/canon/rebaseliner.rb +162 -0
- data/lib/canon/rspec_matchers.rb +101 -23
- data/lib/canon/version.rb +1 -1
- metadata +10 -2
data/lib/canon/rspec_matchers.rb
CHANGED
|
@@ -4,6 +4,9 @@ require "canon" unless defined?(Canon)
|
|
|
4
4
|
require "canon/comparison"
|
|
5
5
|
require "canon/diff_formatter"
|
|
6
6
|
require "canon/config"
|
|
7
|
+
require "canon/rebaseliner"
|
|
8
|
+
require "canon/pretty_printer/xml"
|
|
9
|
+
require "canon/pretty_printer/html"
|
|
7
10
|
|
|
8
11
|
begin
|
|
9
12
|
require "rspec/expectations"
|
|
@@ -88,60 +91,135 @@ module Canon
|
|
|
88
91
|
end
|
|
89
92
|
|
|
90
93
|
def matches?(target)
|
|
94
|
+
# Capture caller_locations only when rebaseliner is enabled so that
|
|
95
|
+
# passing assertions don't pay for stack walking.
|
|
96
|
+
@rebaseliner_caller = caller_locations(1, 12) if Canon::Rebaseliner.enabled?
|
|
97
|
+
equivalent = compute_equivalent(target)
|
|
98
|
+
return true if equivalent
|
|
99
|
+
|
|
100
|
+
attempt_rebaseline
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Skip the rebaseliner path for `.not_to`. RSpec invokes
|
|
104
|
+
# `does_not_match?` if defined, instead of negating `matches?`.
|
|
105
|
+
def does_not_match?(target)
|
|
106
|
+
!compute_equivalent(target)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def failure_message
|
|
110
|
+
"expected #{format_name} to be equivalent\n\n#{diff_output}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def failure_message_when_negated
|
|
114
|
+
"expected #{format_name} not to be equivalent"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def expected
|
|
118
|
+
@expected
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def actual
|
|
122
|
+
@target
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def diffable # rubocop:disable Naming/PredicateMethod
|
|
126
|
+
false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
# Run the actual comparison and set @target / @comparison_result.
|
|
132
|
+
# Returns the boolean equivalence result.
|
|
133
|
+
def compute_equivalent(target)
|
|
91
134
|
@target = target
|
|
92
135
|
|
|
93
|
-
# Build comparison options from config and matcher params
|
|
94
136
|
opts = build_comparison_options
|
|
95
|
-
|
|
96
|
-
# Add format hint if explicitly provided
|
|
97
137
|
opts[:format] = @format if @format
|
|
98
138
|
|
|
99
|
-
# Delegate to Canon::Comparison.equivalent? - the SINGLE source of truth
|
|
100
|
-
# Comparison handles format detection, HTML parsing, and all business logic
|
|
101
139
|
@comparison_result = Canon::Comparison.equivalent?(
|
|
102
140
|
@expected,
|
|
103
141
|
@target,
|
|
104
142
|
opts,
|
|
105
143
|
)
|
|
106
144
|
|
|
107
|
-
# When verbose: true, result is a ComparisonResult object
|
|
108
|
-
# Use the equivalent? method to check for normative differences
|
|
109
145
|
case @comparison_result
|
|
110
146
|
when Canon::Comparison::ComparisonResult
|
|
111
147
|
@comparison_result.equivalent?
|
|
112
148
|
when Hash
|
|
113
|
-
# Legacy format - Hash with :differences array and :preprocessed strings
|
|
114
149
|
@comparison_result[:differences].empty?
|
|
115
150
|
when Array
|
|
116
|
-
# Legacy format - XML/JSON/YAML returns []
|
|
117
151
|
@comparison_result.empty?
|
|
118
152
|
else
|
|
119
|
-
# Boolean result
|
|
120
153
|
@comparison_result
|
|
121
154
|
end
|
|
122
155
|
end
|
|
123
156
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
157
|
+
# When the rebaseliner env var is on and the comparison failed, try to
|
|
158
|
+
# rewrite the heredoc that backs the expected value. Returns `true` to
|
|
159
|
+
# the matcher (so the assertion is treated as passing) when a rewrite
|
|
160
|
+
# succeeded; otherwise `false` so the normal failure path runs.
|
|
161
|
+
def attempt_rebaseline
|
|
162
|
+
return false unless Canon::Rebaseliner.enabled?
|
|
163
|
+
return false unless @rebaseliner_caller
|
|
127
164
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
end
|
|
165
|
+
frame = first_user_frame(@rebaseliner_caller)
|
|
166
|
+
return false unless frame
|
|
131
167
|
|
|
132
|
-
|
|
133
|
-
|
|
168
|
+
prettyprinted = pretty_print_actual
|
|
169
|
+
return false unless prettyprinted
|
|
170
|
+
|
|
171
|
+
status = Canon::Rebaseliner.rewrite!(
|
|
172
|
+
spec_path: frame.absolute_path || frame.path,
|
|
173
|
+
line: frame.lineno,
|
|
174
|
+
prettyprinted_actual: prettyprinted,
|
|
175
|
+
)
|
|
176
|
+
status == :rewritten
|
|
177
|
+
rescue StandardError => e
|
|
178
|
+
Canon::Rebaseliner::Logger.log(
|
|
179
|
+
:error,
|
|
180
|
+
spec_path: frame&.path.to_s,
|
|
181
|
+
line: frame&.lineno.to_i,
|
|
182
|
+
detail: "#{e.class}: #{e.message}",
|
|
183
|
+
)
|
|
184
|
+
false
|
|
134
185
|
end
|
|
135
186
|
|
|
136
|
-
|
|
137
|
-
|
|
187
|
+
# Drop frames inside RSpec internals and canon itself; the first
|
|
188
|
+
# remaining is the user's spec file.
|
|
189
|
+
def first_user_frame(locations)
|
|
190
|
+
locations.find do |loc|
|
|
191
|
+
path = loc.absolute_path || loc.path
|
|
192
|
+
next false unless path
|
|
193
|
+
next false if %r{/gems/rspec-(expectations|core|mocks|support)-}.match?(path)
|
|
194
|
+
next false if %r{/lib/rspec/(expectations|core|mocks|support)/}.match?(path)
|
|
195
|
+
next false if path.include?("/canon/lib/canon/")
|
|
196
|
+
next false if path.end_with?("/lib/canon/rspec_matchers.rb")
|
|
197
|
+
|
|
198
|
+
true
|
|
199
|
+
end
|
|
138
200
|
end
|
|
139
201
|
|
|
140
|
-
|
|
141
|
-
|
|
202
|
+
# Format the actual value with the appropriate pretty-printer for the
|
|
203
|
+
# heredoc rewrite. Returns nil when no prettyprinter is wired (e.g.
|
|
204
|
+
# JSON/YAML in v1).
|
|
205
|
+
def pretty_print_actual
|
|
206
|
+
fmt = @format || detect_format
|
|
207
|
+
case fmt
|
|
208
|
+
when :xml
|
|
209
|
+
Canon::PrettyPrinter::Xml.new.format(@target.to_s)
|
|
210
|
+
when :html, :html4, :html5
|
|
211
|
+
# fixture_ready: true emits actually-indented XHTML-shaped
|
|
212
|
+
# output suitable for direct paste into a heredoc.
|
|
213
|
+
Canon::PrettyPrinter::Html.new(fixture_ready: true)
|
|
214
|
+
.format(@target.to_s)
|
|
215
|
+
end
|
|
142
216
|
end
|
|
143
217
|
|
|
144
|
-
|
|
218
|
+
def detect_format
|
|
219
|
+
Canon::Comparison::FormatDetector.detect(@expected)
|
|
220
|
+
rescue StandardError
|
|
221
|
+
nil
|
|
222
|
+
end
|
|
145
223
|
|
|
146
224
|
def format_name
|
|
147
225
|
# Use explicitly provided format if available
|
data/lib/canon/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: canon
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.28
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -187,6 +187,7 @@ files:
|
|
|
187
187
|
- docs/features/match-options/index.adoc
|
|
188
188
|
- docs/features/match-options/pretty-printed-fixtures.adoc
|
|
189
189
|
- docs/features/performance.adoc
|
|
190
|
+
- docs/features/regenerate-expected.adoc
|
|
190
191
|
- docs/getting-started/index.adoc
|
|
191
192
|
- docs/getting-started/quick-start.adoc
|
|
192
193
|
- docs/guides/choosing-configuration.adoc
|
|
@@ -342,6 +343,13 @@ files:
|
|
|
342
343
|
- lib/canon/pretty_printer/json.rb
|
|
343
344
|
- lib/canon/pretty_printer/xml.rb
|
|
344
345
|
- lib/canon/pretty_printer/xml_normalized.rb
|
|
346
|
+
- lib/canon/rebaseliner.rb
|
|
347
|
+
- lib/canon/rebaseliner/atomic_writer.rb
|
|
348
|
+
- lib/canon/rebaseliner/call_site_resolver.rb
|
|
349
|
+
- lib/canon/rebaseliner/heredoc_locator.rb
|
|
350
|
+
- lib/canon/rebaseliner/heredoc_rewriter.rb
|
|
351
|
+
- lib/canon/rebaseliner/heredoc_target.rb
|
|
352
|
+
- lib/canon/rebaseliner/logger.rb
|
|
345
353
|
- lib/canon/rspec_matchers.rb
|
|
346
354
|
- lib/canon/tree_diff.rb
|
|
347
355
|
- lib/canon/tree_diff/adapters.rb
|