Skip to content

test: driver contract tests formalizing the current driver seam - #204

Merged
pftg merged 1 commit into
masterfrom
test/driver-contract
Aug 22, 2026
Merged

test: driver contract tests formalizing the current driver seam#204
pftg merged 1 commit into
masterfrom
test/driver-contract

Conversation

@pftg

@pftg pftg commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Why

Step 1 of the v2 PR sequence (PR 10.0). Before any driver file moves or the
BaseDriverDriver mixin refactor land, this formalizes the driver seam
v2 will document as SnapDiff::Driver with a regression net: shared contract
tests that assert the current de-facto driver interface against both
ChunkyPNGDriver and VipsDriver identically. Advances issue #166 without
touching the gate.

Per dissent #4 in the v2 architecture design, driver method renames are
out of scope here — only class/module renames are approved for v2, and the
~14 existing method names (load_images, find_difference_region, etc.)
stay exactly as they are. This PR pins those current names/signatures so a
later inheritance→mixin refactor (which changes method resolution order) has
something to catch an accidental override with.

What

Extends the existing DriverContractTests shared module
(test/support/driver_contract_tests.rb, already included by both
ChunkyPNGDriverTest and VipsDriverTest) rather than duplicating it, with:

  • Method presence/signature — asserts the ~15-method shared surface
    (load_images, add_black_box, find_difference_region, crop,
    from_file, save_image_to, resize_image_to, draw_rectangles,
    same_pixels?, same_dimension?, height_for, width_for,
    image_area_size, dimension, supports?) and arity for the
    comparison-taking methods and load_images.
  • load_images behavior — loads both images from disk.
  • find_difference_region result shape — a Difference exposing
    region, meta (Hash), and comparison.
  • Dimension handlingsame_dimension? true/false, and
    width_for/height_for/dimension/image_area_size agree with each
    other.
  • Same-image fast pathssame_pixels? true/false.
  • Option handlingtolerance, color_distance_limit, and skip_area,
    which both drivers support identically. Thresholds are chosen with
    headroom on each driver's actual measured values (verified numerically
    before writing the assertions, not guessed).
  • Error behaviorArgumentError on a missing base or new image file.

No production code changed. No driver method renames.

Gate evidence (mutation testing, reverted before commit)

Broke two real driver behaviors one at a time and confirmed exactly the
matching contract assertions (plus expected pre-existing collateral tests)
went red, then reverted:

  1. BaseDriver#same_dimension? forced to always return true → the new
    [contract] #same_dimension? returns false when images differ in dimensions test failed in all 6 execution contexts (both driver test
    classes + ChunkyPNG's nested subclasses), plus 2 pre-existing
    dimension-related tests as expected collateral. 8 failures, 2 errors.
  2. Difference#tolerable? forced to always return false → the new
    [contract] tolerance option treats small differences as equal test
    failed in all 6 execution contexts, plus 4 pre-existing tolerance tests
    as expected collateral. 11 failures.

Both mutations reverted; suite back to green before commit.

Discovered driver divergences (reported, not fixed — per task scope)

  • VipsDriver#resize_image_to appears to compute the wrong scale
    factor.
    resize_image_to(image, new_width, new_height) is documented
    by its ChunkyPNG counterpart (image.resample_bilinear(new_width, new_height)) to resize to the literal target dimensions. Verified
    empirically: ChunkyPNGDriver#resize_image_to(image, 40, 30) on an
    80x80 source image correctly yields [40, 30]; VipsDriver yields
    [107, 107] — nowhere near the requested target. Root cause looks
    like image.resize(new_width.to_f / new_height) computing the source
    aspect ratio instead of a width-based scale factor (should likely be
    new_width.to_f / width_for(image)). Not covered by any existing test.
    Recommend a follow-up bug fix + regression test — deliberately not
    touched here per this task's "STOP and report, don't fix" scope.
  • add_black_box is not actually a black-box operation on ChunkyPNG.
    ChunkyPNGDriver#add_black_box is a no-op that returns the image
    unchanged; VipsDriver#add_black_box genuinely draws a black rectangle.
    skip_area still behaves correctly end-to-end on both drivers (verified
    and now covered by the new shared skip_area contract tests) because
    ChunkyPNG implements the skip semantics separately, via coordinate
    checks in its own difference finder. Not a bug, but a real divergence in
    what the method itself does — worth knowing before the v2 mixin
    refactor treats add_black_box as one contract.
  • difference_level is not on the same interface surface.
    VipsDriver#difference_level(diff_mask, old_img, region = nil) is a
    public driver method; ChunkyPNG's equivalent lives as a private method
    on its internal DifferenceRegionFinder, not on ChunkyPNGDriver
    itself (chunky_driver.respond_to?(:difference_level) is false).
    Excluded from the shared contract for that reason.
  • The existing bare top-level unless defined?(Vips) ... return end guard
    in test/unit/drivers/vips_driver_test.rb (already flagged in
    test/support/driver_coverage.rb's own comments as a known issue) means
    that in a vips-less environment none of these new contract tests — or
    any test in that file — would even be defined, let alone show up as
    skipped. Left untouched here (restructuring it is a separate, riskier
    change outside this PR's additive scope); every new test added in this
    PR uses ordinary test "..." do ... end blocks with no top-level
    returns, so nothing here adds to that anti-pattern.

Test numbers

  • Baseline (before this PR, verified first): 267 runs, 806 assertions, 0 failures, 0 errors, 0 skips.
  • After this PR: 369 runs, 1076 assertions, 0 failures, 0 errors, 0 skips (both vips and chunky_png drivers detected in this
    environment). The jump from +22 new test methods to +102 runs is
    because ChunkyPNGDriverTest has 4 pre-existing nested subclasses
    (QuickEqualTest, DifferentTest, ColorDistanceTest, HelpersTest)
    that inherit the shared module's tests too — same multiplication the
    pre-existing 5 contract tests already had.
  • standardrb (full repo): 97 files inspected, no offenses.

Ruby: mise x ruby@4.0.6.

🤖 Generated with Claude Code

Summary by Sourcery

Formalize the existing driver seam with shared regression tests for both supported driver implementations.

Enhancements:

  • Expand shared driver contract coverage to formalize the common driver interface, method signatures, comparison results, image dimensions, fast paths, options, and missing-file errors across supported drivers.

Tests:

  • Add shared contract tests covering driver behavior and interface compatibility for the ChunkyPNG and Vips drivers.

Review note: under a vips-less runner the 17 new contract tests are silently not defined (the host file gates on defined?(Vips) with a top-level return — pre-existing pattern); CI-level protection for that case lives in test/support/driver_coverage.rb.

Extends the existing DriverContractTests shared module (test/support/driver_contract_tests.rb)
with tests that pin the current de-facto driver interface shared by ChunkyPNGDriver and
VipsDriver, run against both via the shared module:

- method presence/arity for the ~15-method shared surface (load_images, add_black_box,
  find_difference_region, crop, from_file, save_image_to, resize_image_to, draw_rectangles,
  same_pixels?, same_dimension?, height_for, width_for, image_area_size, dimension, supports?)
- load_images behavior (loads both images, dimensions comparable)
- find_difference_region result shape (Difference exposing region/meta/comparison)
- dimension handling (same_dimension? true/false, width/height/dimension/area_size agreement)
- same-image fast paths (same_pixels? true/false)
- option handling: tolerance, color_distance_limit, skip_area (both drivers support these
  identically; thresholds chosen with headroom on both drivers' measured values)
- error behavior on missing base/new image files (ArgumentError)

No production code changed. No driver method renames (dissent #4 in the v2 architecture
design excludes those from this PR).
@sourcery-ai

sourcery-ai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR expands the shared DriverContractTests module to formalize and pin the current driver interface/behavior across ChunkyPNGDriver and VipsDriver, by adding contract tests for method presence/signatures, image loading, difference result shape, dimension and pixel equality handling, options, and error cases, without touching production code.

File-Level Changes

Change Details Files
Expanded shared driver contract tests to cover the current driver interface, behaviors, and options for both drivers.
  • Added a test asserting that the driver responds to the full shared method surface of the current interface.
  • Added arity/signature tests for find_difference_region, same_pixels?, same_dimension?, and load_images to pin their comparison argument and parameter counts.
  • Added load_images behavior test that verifies both images are loaded from disk in correct order and have compatible dimensions.
  • Added tests that validate the shape and types of the Difference object exposed by comparisons, including region, meta hash, and back-reference to the comparison/driver.
  • Added dimension-related tests for same_dimension? under equal and unequal dimensions and for consistency among width_for, height_for, dimension, and image_area_size.
  • Added same-image fast path tests ensuring same_pixels? returns true for identical images and false for different ones.
  • Added option handling tests for tolerance, color_distance_limit, and skip_area to assert shared semantics across drivers with thresholds chosen to allow headroom.
  • Added error behavior tests asserting ArgumentError and specific messages when either the base or new image file is missing in ImageCompare.
test/support/driver_contract_tests.rb

Possibly linked issues


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

@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: 17 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: a9c639b7-a935-4915-a917-e9e91a528475

📥 Commits

Reviewing files that changed from the base of the PR and between fb6ff9d and 3800be0.

📒 Files selected for processing (1)
  • test/support/driver_contract_tests.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 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="test/support/driver_contract_tests.rb" line_range="72-80" />
<code_context>
+
+    # load_images -------------------------------------------------------------
+
+    test "[contract] #load_images loads both images from disk in (old, new) order" do
+      driver = make_comparison(:a, :a).driver
+
+      old_image, new_image = driver.load_images(TEST_IMAGES_DIR / "a.png", TEST_IMAGES_DIR / "b.png")
+
+      assert_not_nil old_image
+      assert_not_nil new_image
+      assert_equal driver.dimension(old_image), driver.dimension(new_image)
+    end
+
+    # find_difference_region result shape --------------------------------------
</code_context>
<issue_to_address>
**issue (testing):** The `#load_images` contract test claims to verify `(old, new)` ordering but only asserts that both returned images are non-nil and have equal dimensions. Because fixtures `a.png` and `b.png` share dimensions, an implementation that returns the images in reverse order passes this test silently.

**Triggers:** When a driver regresses by swapping the old and new images returned from `load_images`.

**Suggested fix:** Assert the returned image contents against the corresponding fixture, not just their dimensions.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: test/support/driver_contract_tests.rb:80


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +72 to +80
test "[contract] #load_images loads both images from disk in (old, new) order" do
driver = make_comparison(:a, :a).driver

old_image, new_image = driver.load_images(TEST_IMAGES_DIR / "a.png", TEST_IMAGES_DIR / "b.png")

assert_not_nil old_image
assert_not_nil new_image
assert_equal driver.dimension(old_image), driver.dimension(new_image)
end

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.

issue (testing): The #load_images contract test claims to verify (old, new) ordering but only asserts that both returned images are non-nil and have equal dimensions. Because fixtures a.png and b.png share dimensions, an implementation that returns the images in reverse order passes this test silently.

Triggers: When a driver regresses by swapping the old and new images returned from load_images.

Suggested fix: Assert the returned image contents against the corresponding fixture, not just their dimensions.

@pftg
pftg merged commit 9614078 into master Aug 22, 2026
8 checks passed
@pftg
pftg deleted the test/driver-contract branch August 22, 2026 14:45
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