refactor: SnapDiff::Config becomes the single config storage (ADR-008 step 1) - #224
Conversation
… step 1) Inverts config storage ownership: SnapDiff::Config now holds every setting as instance state on the eager SnapDiff.config singleton, and the legacy Capybara::Screenshot / Capybara::Screenshot::Diff accessors are thin delegators generated from Config::MAPPING (singleton + instance methods, mirroring the old mattr_accessor surface; root keeps its reader-only instance asymmetry and its Pathname-coercing writer, now in Config#root=). Require topology: snap_diff/config is the new leaf (it predefines the empty legacy module skeleton, same technique as legacy_shims.rb, so MAPPING's module references resolve); config_legacy requires it, and the old config.rb -> config_legacy edge is gone. Graph stays acyclic. Default timing (pinned by config_default_timing_test.rb, all 12 green): fail_if_new (ENV["CI"]) and root (Rails.root/pwd) evaluate in Config#initialize at the eager Config.new at require time of the leaf -- the same load moment the mattr default blocks ran at. default_options[:wait] stays a live method-body read of Capybara.default_max_wait_time.
The global-state snapshot/restore in test_helper walked the legacy modules' class_variables, which no longer exist after the storage inversion -- it would have silently become a no-op. Snapshot the single storage (SnapDiff.config's instance variables) instead; one storage means this covers both surfaces.
|
Warning Review limit reached
Next review available in: 48 minutes Limit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Reviewer's GuideSnapDiff::Config is refactored into the single source of truth for configuration, with legacy Capybara::Screenshot / Capybara::Screenshot::Diff accessors turned into delegators, the require graph kept acyclic, default-evaluation timing preserved, and test isolation updated to snapshot the new storage. File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Nil-defaulted settings had no ivar until first write, so test_helper's per-test ivar snapshot missed them and teardown could not restore them -- a legacy write to e.g. tolerance mid-test leaked into the next test. Pre-set all MAPPING keys to nil before the explicit defaults so the full ivar set always exists. Also drops the now-unused active_support attribute_accessors require in snap_manager (nothing in lib uses mattr_* since the storage inversion).
The old reflection test derived settings from mattr class variables, which no longer exist -- it passed vacuously. Replace with two directions: every legacy singleton writer must be mapped (a future mattr_accessor or hand-rolled writer on the legacy modules would create unmapped storage), and SnapDiff.config must store exactly one ivar per MAPPING key (red repro for the ivar-initialization fix; also guards the test_helper snapshot completeness).
ADR-008 step 1b: the config storage inversion — the riskiest PR of the accepted end-state plan, kept deliberately small and revertable.
What changed
Before: the legacy
mattr_accessors onCapybara::Screenshot/Capybara::Screenshot::Diffwere the storage;SnapDiff::Configwas a view forwarding every read/write to them.After:
SnapDiff::Config(the eagerSnapDiff.configsingleton) IS the single storage; the legacy accessors are thin delegators generated fromConfig::MAPPING. One storage, two views — bidirectional visibility is structural, not synchronized. The adopter-visible write surface (Capybara::Screenshot.window_size = ...,Capybara::Screenshot::Diff.tolerance = ..., bothconfigureshapes,SnapDiff.start/SnapDiff.configure) is unchanged.Require topology (stays acyclic)
snap_diff/config.rbis the new leaf of the config graph. It predefines the emptyCapybara::Screenshot::Diffmodule skeleton (same technique legacy_shims.rb already uses) soMAPPING's module references resolve at class-body eval, and requires onlysnap_diff/screenshoter+snap_diff/snap_manager(needed as default values at require time; neither requires back).config_legacy.rbnow requiressnap_diff/config(plussnap_diff/utilsforAVAILABLE_DRIVERS) and installs the delegators. The oldconfig.rb -> config_legacyedge is deleted, so the dependency arrow flipped without ever being bidirectional.snap_diffentry still never loads the umbrella (snap_diff_test.rbacyclicity guard green);docs/thread_safety.mdload-order section updated to name the new leaf.Default timing contract (#223 guard: 12/12 green, untouched)
fail_if_new(ENV["CI"])Config#initialize, run by the eagerConfig.newat the bottom ofsnap_diff/config.rb— the same load moment the mattr default block ran atroot(Rails.root/ pwd)Rails.root, no coercion — coercion only in theConfig#root=writer, as before)default_options[:wait]Capybara.default_max_wait_timeinDiff.default_options— never storedConfig#initializeivars; both surfaces read/write the same ivarDelegator surface parity: mattr_accessor defined both singleton and instance accessors (
include Capybara::Screenshot::Diffrelies on the instance ones), so both are installed;rootkeeps its historical asymmetry (instance reader only, module-level coercing writer).Mutation evidence (each applied, run, reverted)
Diff.fail_if_new = xno longer reaches storage):snap_diff_config_testred — 2 failures, both legacy-write round-trip tests (writing fail_if_new via the old mattr_accessor is visible via config, same forwindow_size). Timing guard stays green for this direction (it pins defaults, not writes — the round-trip tests are the write guard).snap_diff_config_testred — 5 failures (every mapped setting is readable..., therootandwindow_sizeround-trips,screenshot_enabled/enabledindependence,SnapDiff.configureset-through). Thefail_if_newround-trip passes only coincidentally under this mutation: with CI unset, the frozen initialfalsehappens to match the value the test writes back.waitfrozen into load-time storage: timing guard red — 4 failures, thedefault_options[:wait] follows Capybara.default_max_wait_time set after requireprobe, once per entry point (expected 42.5, got 2).Adversarial-review fixes (post-open)
4118a53): nil-defaulted settings had no ivar until first write, so test_helper's ivar snapshot missed them and a mid-test legacy write (e.g.tolerance) leaked into the next test.Config#initializenow pre-sets every MAPPING key to nil before the explicit defaults, so all 27 ivars always exist. TDD evidence: the new "stores exactly one ivar per MAPPING key" assertion landed first and was red (17 ivars present — including leaked@window_size/@disable_animationsfrom earlier tests, demonstrating the leak — vs 27 expected), green after the fix.50bb057): the old class_variables-based completeness check passed vacuously post-inversion. Replaced with two directions: (a) every singleton writer on the legacy modules must appear inMAPPING— enumeration found 13 writers onCapybara::Screenshotand 14 onCapybara::Screenshot::Diff, all mapped, so the explicitNON_CONFIG_WRITERSexclusion list is currently empty — and (b)SnapDiff.configstores exactly one ivar perMAPPINGkey (also guards test_helper snapshot completeness).active_support/core_ext/module/attribute_accessorsrequire insnap_manager.rb— nothing in lib usesmattr_*since the inversion; full suite green confirms nothing else needed it loaded.Test results
rake test:unit504 runs / 0 failures. After (incl. review fixes):rake test:unit505 runs / 1442 assertions / 0 failures;rake test538 runs / 0 failures / 0 errors (6 pre-existing skips); standardrb clean.test/test_helper.rbper-test isolation snapshottedclass_variables; it now snapshotsSnapDiff.config's instance variables (e954ba7) — otherwise isolation would have silently become a no-op.Rollback plan
Revert all four commits in one step (lib inversion + test-helper isolation + review fixes — the helper and the reworked guard read Config ivars, so they travel with the inversion in either direction):
What signals rollback:
test/unit/config_default_timing_test.rb(the 12-test timing guard) on this branch or after merge — a default's evaluation moment shifted.snap_diff_test.rbload-order/standalone probes, or the timing guard's per-entry-point probes disagreeing betweencapybara_screenshot_diff,capybara_screenshot_diff/minitest,snap_diff,capybara/screenshot/diff.Capybara::Screenshot.x = ...) not being visible throughSnapDiff.configor vice versa.🤖 Generated with Claude Code