Skip to content

refactor: session surface and reporter registration get SnapDiff homes (ADR-008 step 6) - #228

Merged
pftg merged 1 commit into
masterfrom
refactor/adr8-session-surface
Aug 22, 2026
Merged

pftg merged 1 commit into
masterfrom
refactor/adr8-session-surface

Conversation

@pftg

@pftg pftg commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

ADR-008 step 6 — the last alias-safe move of the accepted ADR. Two things get their SnapDiff home: the per-test session and reporter registration.

(a) Session surface

CapybaraScreenshotDiff's class << self block owned the thread-local per-test registry. The canonical accessor now lives next to the class it hands back, in lib/snap_diff/screenshot_assertion.rb:

New canonical name What it is
SnapDiff.session the per-test AssertionRegistry (`Thread.current[...]
SnapDiff.reset notify reporters, then clear the session — the one bridge between the two lifecycles
SnapDiff.pending_screenshots_message the pending_if_new skip message

Everything else the adapters need is already a registry method, so it reads SnapDiff.session.<verb> — no new module-level forwarders were invented for verify / add_assertion / screenshot_namer / record_new_screenshot.

Fiber-local semantics are unchanged. Thread.current[] splits per fiber; that is pre-existing documented behaviour (issue #217) and this PR relocates the accessor only — no migration to thread_variable_get. The note moved with the code, and snap_manager.rb's cross-reference to it was updated.

Adapter-call inventory (what actually called CapybaraScreenshotDiff.*)

Caller Called Now calls
dsl.rb (×3) screenshot_namer SnapDiff.session.screenshot_namer
dsl.rb add_assertion SnapDiff.session.add_assertion
screenshot_matcher.rb record_new_screenshot SnapDiff.session.record_new_screenshot
integrations/minitest.rb, integrations/rspec.rb verify SnapDiff.session.verify
integrations/{minitest,rspec,cucumber}.rb pending_screenshots_message SnapDiff.pending_screenshots_message
integrations/{minitest,rspec,cucumber}.rb reset SnapDiff.reset
integrations/{minitest,rspec,cucumber}.rb finalize_reporters! SnapDiff::Reporting.finalize!
reporters/html.rb reporters / reporters << SnapDiff::Reporting.reporters / .register
AssertionRegistry#verify (internal) CapybaraScreenshotDiff.assertions / .registry.failed_assertions its own assertions / failed_assertions

Those three integration files (and reporters/html.rb) each carried a comment explaining that they had to require "capybara_screenshot_diff/screenshot_assertion" because the registry machinery lived in the old file. They now require snap_diff/screenshot_assertion + snap_diff/reporting directly, and the comments say so.

Public surface diff — must be empty, and is

CapybaraScreenshotDiff keeps its entire surface as thin forwarders. Dumped singleton_methods(false) + private singleton methods, with arity and full parameters, on 2ed7073 and on this branch:

$ diff surface_before.txt surface_after.txt && echo "SURFACE DIFF EMPTY"
SURFACE DIFF EMPTY

16 public + 1 private (notify_reporters), identical names, arities and parameter lists. reset and pending_screenshots_message are hand-written one-line forwarders rather than def_delegators precisely to keep arity 0 — a Forwardable-generated method reports (*args, **kwargs, &block) and would have shown up in that diff.

(b) Reporting completion

SnapDiff::Reporting.register(reporter) is new and appends under the existing mutex — that closes issue #217 item 2's unsynchronized-append hole on the canonical path. .reporters stays public and mutable for compatibility (appending to it directly still works, it just skips the lock), so CapybaraScreenshotDiff.reporters / reporters_mutex are unaffected. reporters/html.rb's auto-registration uses .register.

Guards added

All four are new pins on seams this PR moves:

  • same-object probe (registry_concurrency_test.rb) — SnapDiff.session and CapybaraScreenshotDiff.registry must be the same object, not two registries that look alike; a write through one is visible through the other.
  • register-goes-to-the-same-array probe (reporters_mutex_test.rb) — register returns the reporter, and SnapDiff::Reporting.reporters is the same array object CapybaraScreenshotDiff.reporters exposes, with the reporter in it.
  • threads probe (reporters_mutex_test.rb) — 32 threads registering concurrently, all 32 retained.
  • auto-registration probe (reporters/html_reporter_test.rb) — requiring the reporter leaves exactly one HTML reporter registered. This seam had no coverage at all before (mutation iii below found that), and this PR changes the line that does it.

Existing tests pass unchanged — no canonical-name migrations were forced by the raise-on-warn guard in test_helper (the flipped call sites are all in lib/, and the compat surface the tests use is byte-identical).

Mutation evidence

(i) forwarder shadows instead of sharesCapybaraScreenshotDiff.registry returns SnapDiff::AssertionRegistry.new instead of SnapDiff.session:

1) Failure: RegistryConcurrencyTest#test_SnapDiff.session_and_CapybaraScreenshotDiff.registry_are_the_same_object
   Expected #<SnapDiff::AssertionRegistry ...> (oid=1184) to be the same as #<...> (oid=1192).

plus 2 collateral failures in the pre-existing concurrency tests. Restored → green.

(ii) unsynchronized .register — dropping the mutex around @reporters << reporter leaves the threads probe green, 5/5 runs. That is MRI's GVL making a single Array#<< effectively atomic, exactly as the task anticipated, so the probe is documented in the test as best-effort and the mutex is named as the actual defense. To show the probe still has teeth and that the mutex is what provides the guarantee, the same racy body was tested both ways:

register body Result
current = @reporters.dup; Thread.pass; current << reporter; @reporters.replace(current)no lock 1 failure, 3/3 runs
the identical body wrapped in @mutex.synchronize 0 failures, 3/3 runs

(iii) html auto-registration broken — the register call replaced with a no-op. On the pre-existing suite this reds nothing (527 runs, 0 failures) — the seam was entirely unguarded. With the new probe:

1) Failure: HTMLReporterTest#test_requiring_the_reporter_auto-registers_exactly_one_HTML_reporter
   Expected: 1
     Actual: 0

Restored → green.

Test numbers (mise x ruby@4.0.6 -- bundle exec)

baseline 2ed7073 this branch
rake test:unit 524 runs / 1476 assertions / 0 failures / 0 skips 528 / 1486 / 0 / 0
rake test 557 runs / 1521 assertions / 0 failures / 6 skips 561 / 1531 / 0 / 6

+4 in both = the four new guards; same skips. Baseline for the full suite was measured by checking out 2ed7073 in this worktree and re-running, not inferred.

standardrb on all 13 touched files: no offenses.

🤖 Generated with Claude Code

Summary by Sourcery

Move per-test session and reporter registration ownership to their canonical SnapDiff namespaces while preserving backward compatibility.

New Features:

  • Add canonical SnapDiff session lifecycle accessors and a thread-safe reporter registration API.

Bug Fixes:

  • Close the reporter registration synchronization gap by protecting canonical registrations with the existing mutex.

Enhancements:

  • Move adapter and internal lifecycle usage from CapybaraScreenshotDiff to SnapDiff and SnapDiff::Reporting while preserving the legacy compatibility surface and session semantics.

Tests:

  • Add coverage for shared session identity, reporter registration compatibility, concurrent registrations, and HTML reporter auto-registration.

ADR-008 step 6, the last alias-safe move of the accepted ADR.

Session lifecycle: the per-test AssertionRegistry accessor moves to
SnapDiff.session, with SnapDiff.reset and
SnapDiff.pending_screenshots_message alongside it in
snap_diff/screenshot_assertion.rb (the file that already owns the registry
class). CapybaraScreenshotDiff keeps its entire surface as thin
forwarders -- singleton_methods + arities dumped before and after, diff is
empty (16 public + 1 private, unchanged). Thread.current is fiber-local;
that is left exactly as it was (issue #217) -- this relocates the
canonical accessor, it does not change the semantics.

Reporting completion: SnapDiff::Reporting.register(reporter) is the
canonical way in, with the append under the existing mutex (issue #217
item 2); .reporters stays public and mutable for compatibility. The
integrations (Minitest, RSpec, Cucumber) and the HTML reporter's
auto-registration now call SnapDiff.session / SnapDiff.reset /
SnapDiff.pending_screenshots_message / SnapDiff::Reporting.finalize! /
SnapDiff::Reporting.register directly instead of routing through
CapybaraScreenshotDiff, and require the snap_diff files they actually use.

AssertionRegistry#verify no longer reaches back through
CapybaraScreenshotDiff for its own assertions -- same objects, one less
round trip through the compat surface.

Guards: SnapDiff.session and CapybaraScreenshotDiff.registry are the same
object; register lands in the array .reporters exposes; 32 concurrent
register calls retain all 32; requiring the HTML reporter auto-registers
exactly one.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @pftg, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@pftg, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 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 @coderabbitai review or push new commits to the PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f45d15f-bb5b-4468-9884-6500530bb57a

📥 Commits

Reviewing files that changed from the base of the PR and between 2ed7073 and 1deaedb.

📒 Files selected for processing (13)
  • lib/capybara_screenshot_diff/screenshot_assertion.rb
  • lib/snap_diff/dsl.rb
  • lib/snap_diff/integrations/cucumber.rb
  • lib/snap_diff/integrations/minitest.rb
  • lib/snap_diff/integrations/rspec.rb
  • lib/snap_diff/reporters/html.rb
  • lib/snap_diff/reporting.rb
  • lib/snap_diff/screenshot_assertion.rb
  • lib/snap_diff/screenshot_matcher.rb
  • lib/snap_diff/snap_manager.rb
  • test/unit/registry_concurrency_test.rb
  • test/unit/reporters/html_reporter_test.rb
  • test/unit/reporters_mutex_test.rb

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors the per-test session and reporter registration to live under the SnapDiff namespace while keeping CapybaraScreenshotDiff’s public API as thin, alias-safe forwarders; adds a synchronized reporter registration path and tests that pin object identity and concurrency guarantees.

Sequence diagram for test reset and reporter notification

sequenceDiagram
    participant Adapter as Framework adapter
    participant SnapDiff as SnapDiff
    participant Session as AssertionRegistry session
    participant Reporting as SnapDiff::Reporting
    participant Reporter as Registered reporter

    Adapter->>SnapDiff: reset()
    SnapDiff->>Session: assertions
    SnapDiff->>Reporting: notify(assertions)
    Reporting->>Reporter: notify(assertions)
    SnapDiff->>Session: reset()
Loading

Sequence diagram for synchronized reporter registration

sequenceDiagram
    participant HTML as HTML reporter loader
    participant Reporting as SnapDiff::Reporting
    participant Mutex as reporters mutex
    participant Reporters as reporters array

    HTML->>Reporting: reporters
    Reporting-->>HTML: reporters array
    HTML->>Reporting: register(reporter)
    Reporting->>Mutex: synchronize
    Mutex->>Reporters: << reporter
    Reporters-->>Mutex: updated array
    Mutex-->>Reporting: release
    Reporting-->>HTML: reporter
Loading

File-Level Changes

Change Details Files
Move the canonical per-test session accessors from CapybaraScreenshotDiff into SnapDiff and update callers to use the new API while preserving the legacy surface as forwarders.
  • Introduce SnapDiff.session, SnapDiff.reset, and SnapDiff.pending_screenshots_message as the canonical session lifecycle API, using the existing Thread.current-based registry implementation.
  • Change CapybaraScreenshotDiff.registry, reset, and pending_screenshots_message to delegate to the SnapDiff session API, keeping arities and signatures unchanged.
  • Update SnapDiff DSL, screenshot matcher, and framework integrations (Minitest, RSpec, Cucumber) to call SnapDiff.session.* and SnapDiff.pending_screenshots_message / SnapDiff.reset instead of CapybaraScreenshotDiff.*.
  • Adjust comments and requires in integration files to reference snap_diff/screenshot_assertion and snap_diff/reporting instead of the old capybara_screenshot_diff/screenshot_assertion path.
  • Make AssertionRegistry#verify operate on its own assertions/failed_assertions instead of reaching back through CapybaraScreenshotDiff.registry.
lib/capybara_screenshot_diff/screenshot_assertion.rb
lib/snap_diff/screenshot_assertion.rb
lib/snap_diff/dsl.rb
lib/snap_diff/screenshot_matcher.rb
lib/snap_diff/integrations/minitest.rb
lib/snap_diff/integrations/rspec.rb
lib/snap_diff/integrations/cucumber.rb
lib/snap_diff/snap_manager.rb
Add a canonical, mutex-protected reporter registration API under SnapDiff::Reporting and switch HTML reporter auto-registration to use it while keeping the legacy reporters surface compatible.
  • Add SnapDiff::Reporting.register(reporter) that appends under the existing mutex and returns the reporter.
  • Change CapybaraScreenshotDiff.reporters to delegate to SnapDiff::Reporting.reporters while leaving the array mutable for compatibility.
  • Update HTML reporter auto-registration to check SnapDiff::Reporting.reporters and call SnapDiff::Reporting.register instead of mutating CapybaraScreenshotDiff.reporters directly.
  • Refresh documentation comments around reporter lifecycle and finalize hooks to reference SnapDiff::Reporting.finalize! as the canonical mechanism.
lib/snap_diff/reporting.rb
lib/capybara_screenshot_diff/screenshot_assertion.rb
lib/snap_diff/reporters/html.rb
lib/snap_diff/integrations/minitest.rb
lib/snap_diff/integrations/rspec.rb
lib/snap_diff/integrations/cucumber.rb
Introduce targeted tests that assert object identity between legacy and new APIs and exercise reporter registration concurrency and HTML auto-registration behavior.
  • Add a test ensuring SnapDiff.session and CapybaraScreenshotDiff.registry return the same AssertionRegistry instance and that writes through one are visible through the other.
  • Add tests verifying SnapDiff::Reporting.register appends to the same reporters array exposed by CapybaraScreenshotDiff.reporters and that concurrent register calls retain all reporters.
  • Add a test confirming that requiring the HTML reporter results in exactly one HTML reporter being registered via SnapDiff::Reporting.
  • Keep existing registry and reporter mutex tests intact and extend them to cover the new APIs without changing the legacy signatures.
test/unit/registry_concurrency_test.rb
test/unit/reporters_mutex_test.rb
test/unit/reporters/html_reporter_test.rb

Possibly linked issues

  • #unknown: The PR directly addresses the issue’s fiber-semantics documentation and reporter-registration synchronization items.
  • #ADR-004: The PR implements an alias-safe namespace migration step, matching ADR-004 Phase 3's additive SnapDiff namespace aliases.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@pftg
pftg merged commit 923f588 into master Aug 22, 2026
6 checks passed
@pftg
pftg deleted the refactor/adr8-session-surface branch August 22, 2026 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant