Skip to content

Commit 8802054

Browse files
authored
Capture values for all tests, make truly drop-in, refactor (#12)
This commit represents a significant evolution of the rspec-enriched_json gem, expanding its capabilities while simplifying its implementation. The journey involved several pivots and discoveries about RSpec's internals. Key Changes 1. Capture values for passing tests - Previously only failing tests had their expected/actual values captured. Now all tests benefit from structured data capture. 2. True drop-in replacement - Removed diff stripping functionality that was modifying RSpec's output, making this a pure enhancement rather than a modification. 3. Simplified serialization - Migrated from custom serialization to Oj (Optimized JSON) for better performance and circular reference handling. 4. Fixed predicate matcher support - BePredicate matchers now correctly capture their actual values instead of showing "null". 5. Added negation detection - Captures whether a matcher was used with not_to/to_not for better context. The Journey Challenges Faced - Double-encoding issues - Initial implementation was double-encoding values, resulting in escaped quotes in the output. Fixed by ensuring single serialization pass. - Memory management - Storing test data in a class variable risked memory leaks. Implemented automatic cleanup after test suite completion. - Predicate matcher edge case - be_* matchers don't have an actual method, requiring special handling to extract their values. - Test suite inception - Several specs were running RSpec within RSpec, causing infinite loops. Removed these in favor of focused unit tests. Tracks Explored and Abandoned - Safety limits - Started implementing string truncation and array limits, but realized Oj handles this better with its circular reference detection. - Matcher-specific extraction - Began with custom logic for each matcher type, but discovered the universal wrapper approach was cleaner and more maintainable. - Diff stripping - Initially modified RSpec's output to remove diffs from messages (since we capture them separately), but this made us not a true drop-in replacement. Design Decisions - Oj for serialization - Chose Oj over custom serialization for its robust handling of circular references, better performance, and maintenance-free approach. - Universal wrapper - Instead of patching specific matchers, we intercept all expectations at the ExpectationHelper level for complete coverage. - Respect RSpec's color mode - Now honors RSpec's color configuration for diff generation rather than forcing it off. - Clean up test suite - Removed specs that were testing Oj's behavior rather than our library's functionality. Technical Details - Bumped version to 0.8.0 (breaking change due to diff behavior modification). - Added GitHub Actions CI workflow for automated testing. - Fixed StandardRB compliance throughout. - Added comprehensive test coverage for new features. - Updated documentation to reflect new capabilities. Breaking Changes - The formatter no longer strips diffs from exception messages. If you were relying on this behavior, the diffs will now appear in both the exception message and the structured diff field.
1 parent 06dabfe commit 8802054

33 files changed

Lines changed: 832 additions & 969 deletions

.github/workflows/ci.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
ruby-version: ['3.2', '3.3', '3.4']
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Ruby
20+
uses: ruby/setup-ruby@v1
21+
with:
22+
ruby-version: ${{ matrix.ruby-version }}
23+
bundler-cache: true
24+
25+
- name: Run StandardRB
26+
run: bundle exec standardrb
27+
28+
- name: Run tests
29+
run: bundle exec rspec
30+
env:
31+
COVERAGE: true
32+
33+
- name: Upload coverage reports
34+
uses: actions/upload-artifact@v4
35+
if: always()
36+
with:
37+
name: coverage-report-${{ matrix.ruby-version }}
38+
path: coverage/

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,20 @@
2121
# Other gems for AI context
2222
/diffy/
2323
/super_diff/
24+
/fuzzy_match_poc/
25+
/oj/
26+
/parallel_tests/
27+
/amazing_print/
2428

2529
# Bundler
2630
Gemfile.lock
2731

28-
# Test output files (from old complex demo)
32+
# Test output files
33+
*.json
2934
enriched_output.json
3035
standard_output.json
36+
builtin_json_output.json
37+
vanilla_output.json
3138

3239
# IDE
3340
.idea/
@@ -36,3 +43,4 @@ standard_output.json
3643
*.swo
3744
*~
3845
.DS_Store
46+
.claude/

.standard.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ ignore:
44
- 'spec/edge_cases_spec.rb:40'
55
- 'spec/safety_limits_spec.rb:52'
66
- 'rspec/**/*'
7+
- 'fuzzy_match_poc/**/*'
8+
- 'oj/**/*'
9+
- 'diffy/**/*'
10+
- 'super_diff/**/*'

CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.7.0] - 2025-07-18
9+
10+
### Added
11+
- Add `negated` flag to detect when `not_to` or `to_not` is used
12+
- Add `passed` field to distinguish passing from failing tests in captured values
13+
14+
### Changed
15+
- Major code simplification - removed unnecessary abstractions and comments
16+
- Updated documentation to reflect actual features (removed non-existent performance limits)
17+
- **BREAKING**: No longer removes diff from exception messages - now a true drop-in replacement
18+
19+
### Fixed
20+
- Fixed spec files that were causing false CI failures
21+
- Updated integration tests to match current behavior
22+
23+
### Removed
24+
- Removed automatic diff stripping from exception messages (introduced in 0.6.1)
25+
26+
## [0.6.1] - 2025-07-18
27+
28+
### Added
29+
- Capture expected/actual values for passing tests (not just failures)
30+
- Memory-safe implementation with cleanup after formatter completes
31+
- Special handling for Regexp serialization (human-readable format like `/pattern/flags`)
32+
- Comprehensive test coverage for new features
33+
34+
### Fixed
35+
- Fixed key mismatch bug between storage and retrieval of test values
36+
- Fixed double-encoding issue in formatter that caused escaped strings in output
37+
38+
### Changed
39+
- Upgraded to Oj for JSON serialization (better performance and object handling)
40+
- Improved error handling with detailed fallback information
41+
842
## [0.5.0] - 2025-06-26
943

1044
### Changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This project creates a universal JSON output system for RSpec matchers:
2222
- `lib/rspec/enriched_json/formatters/enriched_json_formatter.rb` - JSON formatter that outputs enriched data
2323
- `rspec-enriched_json.gemspec` - Gem specification
2424
- `spec/` - Test suite with integration and unit tests
25-
- `demo_all_failures.rb` - Demo script showing various failure types
25+
- `demo.rb` - Demo script showing various failure types
2626
- `Gemfile` - Dependencies (rspec, standard)
2727
- `.standard.yml` - StandardRB configuration
2828

README.md

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# RSpec::EnrichedJson
22

3+
[![CI](https://github.com/firstdraft/rspec-enriched_json/actions/workflows/ci.yml/badge.svg)](https://github.com/firstdraft/rspec-enriched_json/actions/workflows/ci.yml)
4+
[![Gem Version](https://badge.fury.io/rb/rspec-enriched_json.svg)](https://badge.fury.io/rb/rspec-enriched_json)
5+
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/standardrb/standard)
6+
37
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.
48

59
## Quick Demo
@@ -30,7 +34,7 @@ This interactive demo script runs the same failing tests with both formatters an
3034
Add this line to your application's Gemfile:
3135

3236
```ruby
33-
gem 'rspec-enriched_json'
37+
gem "rspec-enriched_json"
3438
```
3539

3640
And then execute:
@@ -95,14 +99,14 @@ With this gem, you get structured data alongside the original message:
9599

96100
## Features
97101

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.
106110

107111
## Examples
108112

@@ -147,36 +151,50 @@ end
147151

148152
## Use Cases
149153

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.
154158

155159
## How It Works
156160

157161
The gem works by:
158162

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.
162166

163167
## Development
164168

165169
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
166170

167-
## Performance Considerations
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.
168188

169-
The enriched formatter adds minimal overhead:
170-
- Only processes failing tests (passing tests have no extra processing)
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.
174191

175-
Default limits:
176-
- Max serialization depth: 5 levels
177-
- Max array size: 100 items
178-
- Max hash size: 100 keys
179-
- Max string length: 1000 characters
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).
180198

181199
## Contributing
182200

TODOS.md

Lines changed: 0 additions & 161 deletions
This file was deleted.

0 commit comments

Comments
 (0)