rspec-enriched_json 0.6.2 → 0.8.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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 840927ecfa09fc825a0f9656e11b7e10c4f4f5b368c144e5536dfa0595714192
|
4
|
+
data.tar.gz: 5616ee6697817e0321938e53ae71410c110d9dc13bbd697dd0f2e974325efa7e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea0dcb189eafca59326cab5550444fe262f4475575b0860381c203dc3c16886fb7c4244df7123bdd854c398ad0b47b4fdaf0d523425fcba843845ea1f72f3eb0
|
7
|
+
data.tar.gz: d8c279936a9641ea12377e4952c747bc9da60abaa15542bd973143180c1249bbcb8504e161411cb78a5cb07cd406c96612ce2ed24a00bfd695c268e24c5ea799
|
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# RSpec::EnrichedJson
|
2
2
|
|
3
|
+
[](https://github.com/firstdraft/rspec-enriched_json/actions/workflows/ci.yml)
|
4
|
+
[](https://badge.fury.io/rb/rspec-enriched_json)
|
5
|
+
[](https://github.com/standardrb/standard)
|
6
|
+
|
3
7
|
A drop-in replacement for RSpec's built-in JSON formatter that enriches the output with structured failure data. This makes it easy to programmatically analyze test results, extract expected/actual values, and build better CI/CD integrations.
|
4
8
|
|
5
9
|
## Quick Demo
|
@@ -30,7 +34,7 @@ This interactive demo script runs the same failing tests with both formatters an
|
|
30
34
|
Add this line to your application's Gemfile:
|
31
35
|
|
32
36
|
```ruby
|
33
|
-
gem
|
37
|
+
gem "rspec-enriched_json"
|
34
38
|
```
|
35
39
|
|
36
40
|
And then execute:
|
@@ -95,14 +99,14 @@ With this gem, you get structured data alongside the original message:
|
|
95
99
|
|
96
100
|
## Features
|
97
101
|
|
98
|
-
- **Drop-in replacement**: Inherits from RSpec's JsonFormatter, maintaining 100% compatibility
|
99
|
-
- **Structured data extraction**: Expected and actual values as proper JSON objects
|
100
|
-
- **Rich object support**: Arrays, hashes, and custom objects are properly serialized
|
101
|
-
- **Original message preservation**: When you override with a custom message, the original is preserved
|
102
|
-
- **Graceful degradation**: Regular exceptions (non-expectation failures) work normally
|
103
|
-
- **Enhanced metadata capture**: Test location, tags, hierarchy, and custom metadata
|
104
|
-
- **Robust error recovery**: Handles objects that fail to serialize without crashing
|
105
|
-
- **Diff information**: Includes `diffable` to help tools determine if values can be meaningfully diffed
|
102
|
+
- **Drop-in replacement**: Inherits from RSpec's JsonFormatter, maintaining 100% compatibility.
|
103
|
+
- **Structured data extraction**: Expected and actual values as proper JSON objects.
|
104
|
+
- **Rich object support**: Arrays, hashes, and custom objects are properly serialized.
|
105
|
+
- **Original message preservation**: When you override with a custom message, the original is preserved.
|
106
|
+
- **Graceful degradation**: Regular exceptions (non-expectation failures) work normally.
|
107
|
+
- **Enhanced metadata capture**: Test location, tags, hierarchy, and custom metadata.
|
108
|
+
- **Robust error recovery**: Handles objects that fail to serialize without crashing.
|
109
|
+
- **Diff information**: Includes `diffable` to help tools determine if values can be meaningfully diffed.
|
106
110
|
|
107
111
|
## Examples
|
108
112
|
|
@@ -147,36 +151,50 @@ end
|
|
147
151
|
|
148
152
|
## Use Cases
|
149
153
|
|
150
|
-
- **CI/CD Integration**: Parse test results to create rich error reports
|
151
|
-
- **Test Analytics**: Track which values commonly cause test failures
|
152
|
-
- **Debugging Tools**: Build tools that can display expected vs actual diffs
|
153
|
-
- **Learning Platforms**: Provide detailed feedback on why tests failed
|
154
|
+
- **CI/CD Integration**: Parse test results to create rich error reports.
|
155
|
+
- **Test Analytics**: Track which values commonly cause test failures.
|
156
|
+
- **Debugging Tools**: Build tools that can display expected vs actual diffs.
|
157
|
+
- **Learning Platforms**: Provide detailed feedback on why tests failed.
|
154
158
|
|
155
159
|
## How It Works
|
156
160
|
|
157
161
|
The gem works by:
|
158
162
|
|
159
|
-
1. Patching RSpec's expectation system to capture structured data when expectations fail
|
160
|
-
2. Extending the JsonFormatter to include this data in the JSON output
|
161
|
-
3. Maintaining full backward compatibility with existing tools
|
163
|
+
1. Patching RSpec's expectation system to capture structured data when expectations fail.
|
164
|
+
2. Extending the JsonFormatter to include this data in the JSON output.
|
165
|
+
3. Maintaining full backward compatibility with existing tools.
|
162
166
|
|
163
167
|
## Development
|
164
168
|
|
165
169
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
166
170
|
|
167
|
-
|
171
|
+
This project uses [StandardRB](https://github.com/standardrb/standard) for code formatting and style. Before committing:
|
172
|
+
|
173
|
+
```bash
|
174
|
+
# Check for style violations
|
175
|
+
bundle exec standardrb
|
176
|
+
|
177
|
+
# Auto-fix style violations
|
178
|
+
bundle exec standardrb --fix
|
179
|
+
```
|
180
|
+
|
181
|
+
## Additional Features
|
182
|
+
|
183
|
+
### Passing Test Value Capture
|
184
|
+
The formatter also captures expected/actual values for passing tests, useful for:
|
185
|
+
- Test analytics and insights.
|
186
|
+
- Understanding test coverage patterns.
|
187
|
+
- Debugging flaky tests.
|
168
188
|
|
169
|
-
|
170
|
-
|
171
|
-
- Limits serialization depth to prevent infinite recursion
|
172
|
-
- Truncates large strings and collections to maintain reasonable output sizes
|
173
|
-
- No impact on test execution time, only on failure reporting
|
189
|
+
### Negation Detection
|
190
|
+
Tests using `not_to` or `to_not` include a `negated: true` flag in the details.
|
174
191
|
|
175
|
-
|
176
|
-
|
177
|
-
-
|
178
|
-
-
|
179
|
-
-
|
192
|
+
### Serialization
|
193
|
+
Values are serialized using [Oj](https://github.com/ohler55/oj) in object mode, providing:
|
194
|
+
- Circular reference handling.
|
195
|
+
- Proper Ruby object serialization.
|
196
|
+
- Excellent performance.
|
197
|
+
- Special handling for Regexp objects (serialized as inspect strings).
|
180
198
|
|
181
199
|
## Contributing
|
182
200
|
|
@@ -1,275 +1,201 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
+
require "oj"
|
4
5
|
require "rspec/expectations"
|
5
6
|
require "rspec/support/differ"
|
6
7
|
|
7
8
|
module RSpec
|
8
9
|
module EnrichedJson
|
9
|
-
|
10
|
+
@all_test_values = {}
|
11
|
+
|
12
|
+
def self.all_test_values
|
13
|
+
@all_test_values
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.clear_test_values
|
17
|
+
@all_test_values = {}
|
18
|
+
end
|
19
|
+
|
10
20
|
module ExpectationHelperWrapper
|
11
|
-
MAX_SERIALIZATION_DEPTH = 5
|
12
|
-
MAX_ARRAY_SIZE = 100
|
13
|
-
MAX_HASH_SIZE = 100
|
14
|
-
MAX_STRING_LENGTH = 1000
|
15
21
|
def self.install!
|
16
22
|
RSpec::Expectations::ExpectationHelper.singleton_class.prepend(self)
|
23
|
+
RSpec::Expectations::PositiveExpectationHandler.singleton_class.prepend(PositiveHandlerWrapper)
|
24
|
+
RSpec::Expectations::NegativeExpectationHandler.singleton_class.prepend(NegativeHandlerWrapper)
|
17
25
|
end
|
18
26
|
|
19
|
-
# Make serialize_value accessible for other components
|
20
27
|
module Serializer
|
21
28
|
extend self
|
22
29
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
)
|
38
|
-
when Symbol
|
39
|
-
serialize_object(value)
|
40
|
-
when nil
|
41
|
-
serialize_object(value)
|
42
|
-
when Array
|
43
|
-
return "[Large array: #{value.size} items]" if value.size > MAX_ARRAY_SIZE
|
44
|
-
value.map { |v| serialize_value(v, depth + 1) }
|
45
|
-
when Hash
|
46
|
-
return "[Large hash: #{value.size} keys]" if value.size > MAX_HASH_SIZE
|
47
|
-
value.transform_values { |v| serialize_value(v, depth + 1) }
|
48
|
-
else
|
49
|
-
serialize_object(value, depth)
|
50
|
-
end
|
51
|
-
rescue => e
|
52
|
-
{
|
53
|
-
"class" => value.class.name,
|
54
|
-
"serialization_error" => e.message
|
55
|
-
}
|
56
|
-
end
|
57
|
-
|
58
|
-
def serialize_object(obj, depth = 0)
|
59
|
-
result = {
|
60
|
-
"class" => obj.class.name,
|
61
|
-
"inspect" => safe_inspect(obj),
|
62
|
-
"to_s" => safe_to_s(obj)
|
63
|
-
}
|
64
|
-
|
65
|
-
# Handle Structs specially
|
66
|
-
if obj.is_a?(Struct)
|
67
|
-
result["struct_values"] = obj.to_h.transform_values { |v| serialize_value(v, depth + 1) }
|
68
|
-
end
|
69
|
-
|
70
|
-
# Include instance variables only for small objects
|
71
|
-
ivars = obj.instance_variables
|
72
|
-
if ivars.any? && ivars.length <= 10
|
73
|
-
result["instance_variables"] = ivars.each_with_object({}) do |ivar, hash|
|
74
|
-
hash[ivar.to_s] = serialize_value(obj.instance_variable_get(ivar), depth + 1)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
result
|
79
|
-
end
|
80
|
-
|
81
|
-
def truncate_string(str)
|
82
|
-
return str if str.length <= MAX_STRING_LENGTH
|
83
|
-
"#{str[0...MAX_STRING_LENGTH]}... (truncated)"
|
84
|
-
end
|
30
|
+
OJ_OPTIONS = {
|
31
|
+
mode: :object,
|
32
|
+
circular: true,
|
33
|
+
class_cache: false,
|
34
|
+
create_additions: false,
|
35
|
+
symbol_keys: false,
|
36
|
+
auto_define: false,
|
37
|
+
create_id: nil,
|
38
|
+
use_to_json: false,
|
39
|
+
use_as_json: false,
|
40
|
+
use_raw_json: false,
|
41
|
+
bigdecimal_as_decimal: true,
|
42
|
+
nan: :word
|
43
|
+
}
|
85
44
|
|
86
|
-
def
|
87
|
-
if
|
88
|
-
|
89
|
-
# Only undump if it's a valid dumped string
|
90
|
-
# Check if the string is properly escaped by attempting undump
|
91
|
-
str.undump
|
92
|
-
rescue RuntimeError => e
|
93
|
-
# If undump fails, just return the original string
|
94
|
-
# This handles cases where the string has quotes but isn't a valid dump format
|
95
|
-
str
|
96
|
-
end
|
97
|
-
else
|
98
|
-
str
|
45
|
+
def serialize_value(value)
|
46
|
+
if value.is_a?(Regexp)
|
47
|
+
return Oj.dump(value.inspect, mode: :compat)
|
99
48
|
end
|
100
|
-
end
|
101
49
|
|
102
|
-
|
103
|
-
truncate_string(obj.inspect)
|
50
|
+
Oj.dump(value, OJ_OPTIONS)
|
104
51
|
rescue => e
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
52
|
+
Oj.dump({
|
53
|
+
"_serialization_error" => e.message,
|
54
|
+
"_class" => value.class.name,
|
55
|
+
"_to_s" => begin
|
56
|
+
value.to_s
|
57
|
+
rescue
|
58
|
+
"[to_s failed]"
|
59
|
+
end
|
60
|
+
}, mode: :compat)
|
112
61
|
end
|
113
62
|
end
|
114
63
|
|
115
64
|
def handle_failure(matcher, message, failure_message_method)
|
116
|
-
# If a custom message is provided, capture the original message first
|
117
65
|
original_message = nil
|
118
66
|
if message
|
119
67
|
original_message = matcher.send(failure_message_method)
|
120
68
|
end
|
121
69
|
|
122
|
-
# Call original handler with the original message
|
123
70
|
super
|
124
71
|
rescue RSpec::Expectations::ExpectationNotMetError => e
|
125
|
-
|
126
|
-
|
127
|
-
|
72
|
+
expected_raw = extract_value(matcher, :expected, failure_message_method)
|
73
|
+
actual_raw = extract_value(matcher, :actual, failure_message_method)
|
74
|
+
negated = failure_message_method == :failure_message_when_negated
|
128
75
|
|
129
|
-
# Collect structured data
|
130
76
|
details = {
|
131
77
|
expected: Serializer.serialize_value(expected_raw),
|
132
78
|
actual: Serializer.serialize_value(actual_raw),
|
133
|
-
original_message: original_message,
|
79
|
+
original_message: original_message,
|
134
80
|
matcher_name: matcher.class.name,
|
135
|
-
diffable:
|
81
|
+
diffable: matcher.respond_to?(:diffable?) && matcher.diffable?,
|
82
|
+
negated: negated
|
136
83
|
}
|
137
84
|
|
138
|
-
# Generate diff if values are diffable
|
139
85
|
if details[:diffable] && expected_raw && actual_raw
|
140
86
|
diff = generate_diff(actual_raw, expected_raw)
|
141
87
|
details[:diff] = diff unless diff.nil? || diff.strip.empty?
|
142
88
|
end
|
143
89
|
|
144
|
-
# Capture matcher-specific instance variables
|
145
|
-
matcher_data = extract_matcher_specific_data(matcher)
|
146
|
-
details.merge!(matcher_data) unless matcher_data.empty?
|
147
|
-
|
148
|
-
# Raise new exception with data attached
|
149
90
|
raise EnrichedExpectationNotMetError.new(e.message, details)
|
150
91
|
end
|
151
92
|
|
152
93
|
private
|
153
94
|
|
154
|
-
def extract_value(matcher, method_name)
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
95
|
+
def extract_value(matcher, method_name, failure_message_method = nil)
|
96
|
+
if matcher.is_a?(RSpec::Matchers::BuiltIn::BePredicate) || matcher.is_a?(RSpec::Matchers::BuiltIn::Has)
|
97
|
+
case method_name
|
98
|
+
when :expected
|
99
|
+
!(failure_message_method == :failure_message_when_negated)
|
100
|
+
when :actual
|
101
|
+
if matcher.instance_variable_defined?(:@predicate_result)
|
102
|
+
matcher.instance_variable_get(:@predicate_result)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
else
|
106
|
+
return nil unless matcher.respond_to?(method_name)
|
107
|
+
value = matcher.send(method_name)
|
108
|
+
(value == matcher && !value.nil?) ? nil : value
|
109
|
+
end
|
161
110
|
rescue
|
162
111
|
nil
|
163
112
|
end
|
164
113
|
|
165
|
-
def
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
# Skip RSpec internals
|
178
|
-
:@matcher_execution_context,
|
179
|
-
:@chained_method_with_args_combos
|
180
|
-
]
|
114
|
+
def generate_diff(actual, expected)
|
115
|
+
differ = RSpec::Support::Differ.new(
|
116
|
+
object_preparer: lambda { |obj|
|
117
|
+
RSpec::Matchers::Composable.surface_descriptions_in(obj)
|
118
|
+
},
|
119
|
+
color: RSpec.configuration.color_enabled?
|
120
|
+
)
|
121
|
+
differ.diff(actual, expected)
|
122
|
+
rescue
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
end
|
181
126
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
:@expecteds, :@actuals, # Include
|
186
|
-
:@operator, :@delta, :@tolerance, # Comparison matchers
|
187
|
-
:@expected_before, :@expected_after, :@actual_before, :@actual_after, # Change matcher
|
188
|
-
:@from, :@to, :@minimum, :@maximum, :@count, # Various matchers
|
189
|
-
:@failure_message, :@failure_message_when_negated,
|
190
|
-
:@description
|
191
|
-
]
|
127
|
+
module HandlerWrapperShared
|
128
|
+
def capture_test_values(actual, initial_matcher, negated: false)
|
129
|
+
return unless initial_matcher && RSpec.current_example
|
192
130
|
|
193
|
-
|
194
|
-
|
195
|
-
|
131
|
+
begin
|
132
|
+
if initial_matcher.is_a?(RSpec::Matchers::BuiltIn::BePredicate) || initial_matcher.is_a?(RSpec::Matchers::BuiltIn::Has)
|
133
|
+
expected_value = !negated
|
196
134
|
|
197
|
-
|
198
|
-
|
135
|
+
if negated && initial_matcher.respond_to?(:does_not_match?)
|
136
|
+
initial_matcher.does_not_match?(actual)
|
137
|
+
else
|
138
|
+
initial_matcher.matches?(actual)
|
139
|
+
end
|
199
140
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
141
|
+
actual_value = if initial_matcher.instance_variable_defined?(:@predicate_result)
|
142
|
+
initial_matcher.instance_variable_get(:@predicate_result)
|
143
|
+
end
|
144
|
+
else
|
145
|
+
expected_value = initial_matcher.respond_to?(:expected) ? initial_matcher.expected : nil
|
146
|
+
actual_value = actual
|
204
147
|
end
|
205
148
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
149
|
+
key = RSpec.current_example.id
|
150
|
+
RSpec::EnrichedJson.all_test_values[key] = {
|
151
|
+
expected: ExpectationHelperWrapper::Serializer.serialize_value(expected_value),
|
152
|
+
actual: ExpectationHelperWrapper::Serializer.serialize_value(actual_value),
|
153
|
+
matcher_name: initial_matcher.class.name,
|
154
|
+
passed: nil
|
155
|
+
}
|
210
156
|
|
211
|
-
|
212
|
-
|
213
|
-
|
157
|
+
RSpec::EnrichedJson.all_test_values[key][:negated] = true if negated
|
158
|
+
rescue => e
|
159
|
+
if defined?(RSpec.configuration) && RSpec.configuration.reporter
|
160
|
+
RSpec.configuration.reporter.message("Warning: Error capturing test values: #{e.message}")
|
161
|
+
elsif ENV["DEBUG"]
|
162
|
+
puts "Error capturing test values: #{e.message}"
|
214
163
|
end
|
215
|
-
|
216
|
-
# Convert instance variable name to a more readable format
|
217
|
-
# @missing_items -> missing_items
|
218
|
-
key = ivar.to_s.delete_prefix("@").to_sym
|
219
|
-
|
220
|
-
# Serialize the value
|
221
|
-
matcher_data[key] = Serializer.serialize_value(value)
|
222
|
-
rescue
|
223
|
-
# Skip this instance variable if we can't serialize it
|
224
|
-
next
|
225
164
|
end
|
226
|
-
|
227
|
-
matcher_data
|
228
165
|
end
|
229
166
|
|
230
|
-
def
|
231
|
-
|
232
|
-
if matcher.respond_to?(:diffable?)
|
233
|
-
return matcher.diffable?
|
234
|
-
end
|
167
|
+
def mark_as_passed(initial_matcher)
|
168
|
+
return unless initial_matcher && RSpec.current_example
|
235
169
|
|
236
|
-
|
237
|
-
|
170
|
+
key = RSpec.current_example.id
|
171
|
+
if RSpec::EnrichedJson.all_test_values[key]
|
172
|
+
RSpec::EnrichedJson.all_test_values[key][:passed] = true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
238
176
|
|
239
|
-
|
240
|
-
|
177
|
+
module PositiveHandlerWrapper
|
178
|
+
include HandlerWrapperShared
|
241
179
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
else
|
248
|
-
# For other types, they're diffable if they respond to to_s
|
249
|
-
# and their string representations would be meaningful
|
250
|
-
expected.respond_to?(:to_s) && actual.respond_to?(:to_s)
|
251
|
-
end
|
252
|
-
rescue
|
253
|
-
# If any error occurs during checking, assume not diffable
|
254
|
-
false
|
180
|
+
def handle_matcher(actual, initial_matcher, custom_message = nil, &block)
|
181
|
+
capture_test_values(actual, initial_matcher, negated: false)
|
182
|
+
result = super
|
183
|
+
mark_as_passed(initial_matcher)
|
184
|
+
result
|
255
185
|
end
|
186
|
+
end
|
256
187
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
differ.diff(actual, expected)
|
266
|
-
rescue
|
267
|
-
# If diff generation fails, return nil rather than crashing
|
268
|
-
nil
|
188
|
+
module NegativeHandlerWrapper
|
189
|
+
include HandlerWrapperShared
|
190
|
+
|
191
|
+
def handle_matcher(actual, initial_matcher, custom_message = nil, &block)
|
192
|
+
capture_test_values(actual, initial_matcher, negated: true)
|
193
|
+
result = super
|
194
|
+
mark_as_passed(initial_matcher)
|
195
|
+
result
|
269
196
|
end
|
270
197
|
end
|
271
198
|
end
|
272
199
|
end
|
273
200
|
|
274
|
-
# Auto-install when this file is required
|
275
201
|
RSpec::EnrichedJson::ExpectationHelperWrapper.install!
|
@@ -12,9 +12,7 @@ module RSpec
|
|
12
12
|
def stop(group_notification)
|
13
13
|
@output_hash[:examples] = group_notification.notifications.map do |notification|
|
14
14
|
format_example(notification.example).tap do |hash|
|
15
|
-
# Add enhanced metadata
|
16
15
|
add_metadata(hash, notification.example)
|
17
|
-
|
18
16
|
e = notification.example.exception
|
19
17
|
|
20
18
|
if e
|
@@ -24,16 +22,14 @@ module RSpec
|
|
24
22
|
backtrace: notification.formatted_backtrace
|
25
23
|
}
|
26
24
|
|
27
|
-
# Add structured data if available
|
28
25
|
if e.is_a?(RSpec::EnrichedJson::EnrichedExpectationNotMetError) && e.details
|
29
|
-
hash[:details] =
|
26
|
+
hash[:details] = e.details
|
30
27
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
28
|
+
else
|
29
|
+
key = notification.example.id
|
30
|
+
if RSpec::EnrichedJson.all_test_values.key?(key)
|
31
|
+
captured_values = RSpec::EnrichedJson.all_test_values[key]
|
32
|
+
hash[:details] = captured_values
|
37
33
|
end
|
38
34
|
end
|
39
35
|
end
|
@@ -45,38 +41,25 @@ module RSpec
|
|
45
41
|
def add_metadata(hash, example)
|
46
42
|
metadata = example.metadata.dup
|
47
43
|
|
48
|
-
# Extract custom tags (all symbols and specific keys)
|
49
44
|
custom_tags = {}
|
50
45
|
metadata.each do |key, value|
|
51
|
-
# Include all symbol keys (like :focus, :slow, etc.)
|
52
46
|
if key.is_a?(Symbol) && value == true
|
53
47
|
custom_tags[key] = true
|
54
|
-
# Include specific metadata that might be useful
|
55
48
|
elsif [:type, :priority, :severity, :db, :js].include?(key)
|
56
49
|
custom_tags[key] = value
|
57
50
|
end
|
58
51
|
end
|
59
52
|
|
60
|
-
# Add enhanced metadata
|
61
53
|
hash[:metadata] = {
|
62
|
-
# Location information
|
63
54
|
location: example.location,
|
64
55
|
absolute_file_path: File.expand_path(example.metadata[:file_path]),
|
65
56
|
rerun_file_path: example.location_rerun_argument,
|
66
|
-
|
67
|
-
# Example hierarchy
|
68
57
|
example_group: example.example_group.description,
|
69
58
|
example_group_hierarchy: extract_group_hierarchy(example),
|
70
|
-
|
71
|
-
# Described class if available
|
72
59
|
described_class: metadata[:described_class]&.to_s,
|
73
|
-
|
74
|
-
# Custom tags and metadata
|
75
60
|
tags: custom_tags.empty? ? nil : custom_tags,
|
76
|
-
|
77
|
-
# Shared example information if applicable
|
78
61
|
shared_group_inclusion_backtrace: metadata[:shared_group_inclusion_backtrace]
|
79
|
-
}.compact
|
62
|
+
}.compact
|
80
63
|
end
|
81
64
|
|
82
65
|
def extract_group_hierarchy(example)
|
@@ -91,54 +74,9 @@ module RSpec
|
|
91
74
|
hierarchy
|
92
75
|
end
|
93
76
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
expected: safe_serialize(details[:expected]),
|
98
|
-
actual: safe_serialize(details[:actual]),
|
99
|
-
matcher_name: details[:matcher_name],
|
100
|
-
original_message: details[:original_message],
|
101
|
-
diffable: details[:diffable]
|
102
|
-
}
|
103
|
-
|
104
|
-
# Add any additional matcher-specific fields
|
105
|
-
details.each do |key, value|
|
106
|
-
next if [:expected, :actual, :matcher_name, :original_message, :diffable].include?(key)
|
107
|
-
result[key] = safe_serialize(value)
|
108
|
-
end
|
109
|
-
|
110
|
-
result.compact
|
111
|
-
end
|
112
|
-
|
113
|
-
def safe_serialize(value)
|
114
|
-
# Delegate to the existing serialization logic in ExpectationHelperWrapper
|
115
|
-
RSpec::EnrichedJson::ExpectationHelperWrapper::Serializer.serialize_value(value)
|
116
|
-
rescue => e
|
117
|
-
# Better error recovery - provide context about what failed
|
118
|
-
begin
|
119
|
-
obj_class = value.class.name
|
120
|
-
rescue
|
121
|
-
obj_class = "Unknown"
|
122
|
-
end
|
123
|
-
|
124
|
-
{
|
125
|
-
"serialization_error" => true,
|
126
|
-
"error_class" => e.class.name,
|
127
|
-
"error_message" => e.message,
|
128
|
-
"object_class" => obj_class,
|
129
|
-
"fallback_value" => safe_fallback_value(value)
|
130
|
-
}
|
131
|
-
end
|
132
|
-
|
133
|
-
def safe_fallback_value(value)
|
134
|
-
# Try multiple fallback strategies
|
135
|
-
value.to_s
|
136
|
-
rescue
|
137
|
-
begin
|
138
|
-
value.class.name
|
139
|
-
rescue
|
140
|
-
"Unable to serialize"
|
141
|
-
end
|
77
|
+
def close(_notification)
|
78
|
+
super
|
79
|
+
RSpec::EnrichedJson.clear_test_values
|
142
80
|
end
|
143
81
|
end
|
144
82
|
end
|
data/rspec-enriched_json.gemspec
CHANGED
@@ -20,9 +20,10 @@ Gem::Specification.new do |spec|
|
|
20
20
|
"source_code_uri" => "https://github.com/firstdraft/rspec-enriched_json"
|
21
21
|
}
|
22
22
|
|
23
|
-
spec.required_ruby_version = ">= 2
|
23
|
+
spec.required_ruby_version = ">= 3.2"
|
24
24
|
spec.add_dependency "rspec-core", ">= 3.0"
|
25
25
|
spec.add_dependency "rspec-expectations", ">= 3.0"
|
26
|
+
spec.add_dependency "oj", "~> 3.16"
|
26
27
|
|
27
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
28
29
|
spec.add_development_dependency "rake", "~> 13.0"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-enriched_json
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Raghu Betina
|
@@ -37,6 +37,20 @@ dependencies:
|
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '3.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: oj
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.16'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.16'
|
40
54
|
- !ruby/object:Gem::Dependency
|
41
55
|
name: rspec
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -113,7 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
127
|
requirements:
|
114
128
|
- - ">="
|
115
129
|
- !ruby/object:Gem::Version
|
116
|
-
version: 2
|
130
|
+
version: '3.2'
|
117
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
132
|
requirements:
|
119
133
|
- - ">="
|