Skip to content

Improve TooManyInvocationsError reporting#2315

Open
leonard84 wants to merge 1 commit intospockframework:masterfrom
leonard84:report-unmatched-on-too-many-invocations
Open

Improve TooManyInvocationsError reporting#2315
leonard84 wants to merge 1 commit intospockframework:masterfrom
leonard84:report-unmatched-on-too-many-invocations

Conversation

@leonard84
Copy link
Member

@leonard84 leonard84 commented Mar 7, 2026

it now reports unsatisfied interactions with argument mismatch details.

Varargs methods now correctly expand args in mismatch descriptions instead of reporting <too few arguments>.

Summary by CodeRabbit

  • Bug Fixes

    • Improved error messages for failed mock invocations to include details about unsatisfied interactions and argument mismatches with similarity scoring.
    • Fixed argument mismatch descriptions for varargs to correctly report arguments instead of "".
  • Tests

    • Added comprehensive test coverage for error enrichment and diagnostic information in mock invocation failures.

it now reports unsatisfied interactions with argument mismatch details.

Varargs methods now correctly expand args in mismatch descriptions
instead of reporting `<too few arguments>`.
@leonard84 leonard84 self-assigned this Mar 7, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

📝 Walkthrough

Walkthrough

These changes enhance mock invocation error diagnostics by introducing an enrichment mechanism for TooManyInvocationsError. New interface methods enable error enrichment with unsatisfied interactions and argument mismatch details. Implementation distributes this logic across interaction scopes, mock controllers, and constraint handlers, with comprehensive test coverage.

Changes

Cohort / File(s) Summary
Interface Definitions
spock-core/src/main/java/org/spockframework/mock/IInteractionScope.java, spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java
Added enrichError(TooManyInvocationsError) to IInteractionScope and matchesTargetAndMethod(IMockInvocation) to IMockInteraction, establishing new interface contracts for error enrichment and target-method matching.
Core Interaction Implementations
spock-core/src/main/java/org/spockframework/mock/runtime/MockInteraction.java, spock-core/src/main/java/org/spockframework/mock/DefaultInteraction.java, spock-core/src/main/java/org/spockframework/mock/runtime/MockInteractionDecorator.java
Implemented matchesTargetAndMethod() to check non-argument constraints, enabling target-and-method-only matching for diagnostic purposes.
Error Enrichment Logic
spock-core/src/main/java/org/spockframework/mock/TooManyInvocationsError.java
Added enrichWithScopeContext() method and enhanced getMessage() to include "Unmatched invocations" section with scoring-based ranking of mismatches. Introduces helper methods for similarity-based invocation scoring and mismatch rendering.
Scope and Controller Integration
spock-core/src/main/java/org/spockframework/mock/runtime/InteractionScope.java, spock-core/src/main/java/org/spockframework/mock/runtime/MockController.java
InteractionScope implements enrichError() and adds getUnsatisfiedInteractions() helper. MockController now intercepts TooManyInvocationsError to call enrichError() for scope-level context augmentation.
Default Interactions
spock-core/src/main/java/org/spockframework/mock/DefaultJavaLangObjectInteractions.java
Implements no-op enrichError() for default Java Object interactions.
Constraint Improvements
spock-core/src/main/java/org/spockframework/mock/constraint/PositionalArgumentListConstraint.java
Enhanced describeMismatch() to expand varargs before analysis when argument counts differ, enabling accurate mismatch reporting for variable arguments.
Documentation & Tests
docs/release_notes.adoc, spock-specs/src/test/groovy/org/spockframework/smoke/mock/TooManyInvocations.groovy
Release notes document enhanced error diagnostics. Test file adds 16+ new test methods covering enrichment scenarios, introduces normalize() helper for message comparison, and validates varargs and wildcard interaction error output.

Sequence Diagram

sequenceDiagram
    participant Controller as MockController
    participant Scope as InteractionScope
    participant Error as TooManyInvocationsError
    participant Message as getMessage()
    
    Controller->>Controller: catch TooManyInvocationsError
    Controller->>Scope: enrichError(error)
    Scope->>Scope: getUnsatisfiedInteractions()
    Scope->>Error: enrichWithScopeContext(unsatisfied)
    Error->>Error: store unsatisfiedInteractions
    Error->>Error: clear cached message
    
    Note over Controller,Error: Later, when error details requested
    Controller->>Error: getMessage()
    Error->>Message: check if unsatisfiedInteractions present
    alt has unsatisfied
        Message->>Message: appendUnmatchedInvocations()
        Message->>Message: scoreInvocations()
        Message->>Message: render mismatches with ranking
    end
    Message-->>Controller: enhanced error message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 When mocks cry out in error streams,
With "too many" woes and shattered dreams,
We now enrich with context bright—
Unmatched invocations sorted right!
A rabbit's gift: clearer sight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improve TooManyInvocationsError reporting' clearly and directly summarizes the main change - enhancing error reporting for TooManyInvocationsError with unsatisfied interactions and mismatch details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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 and usage tips.

@greptile-apps
Copy link

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR significantly improves TooManyInvocationsError diagnostics by appending an "Unmatched invocations (ordered by similarity)" section that shows unsatisfied interactions alongside per-argument mismatch details, and fixes a pre-existing bug where varargs methods incorrectly reported <too few arguments> in mismatch descriptions.

Key changes:

  • TooManyInvocationsError gains enrichWithScopeContext + appendUnmatchedInvocations to render unsatisfied interactions with argument diffs against the accepted invocations pool
  • MockController.handle intercepts TooManyInvocationsError and calls the new enrichError hook on the active scope
  • IMockInteraction.matchesTargetAndMethod is added to separate target+method matching from argument matching, enabling the "relevant unsatisfied" pre-filter
  • PositionalArgumentListConstraint.describeMismatch is fixed to expand varargs before comparing argument counts
  • ScoredInvocation.count is set in the constructor but never read — not in compareTo, not in display — making it dead code; if it was intended for secondary tie-breaking it is missing from the comparison logic
  • Both IInteractionScope.enrichError and IMockInteraction.matchesTargetAndMethod are added to public interfaces without default implementations, which is a source-incompatible change for any third-party code implementing these interfaces directly

Confidence Score: 4/5

  • Safe to merge; issues are style/API-compatibility concerns that do not affect runtime correctness.
  • The feature logic is sound and is backed by a comprehensive test suite. The two deductions are: (1) an unused count field in ScoredInvocation that is dead code (minor), and (2) two new methods added to public interfaces without default implementations, which is a source-incompatible change for third-party implementors of IMockInteraction or IInteractionScope. Neither issue causes incorrect behavior within the framework itself, but the API compatibility concern is worth addressing before the release.
  • IMockInteraction.java and IInteractionScope.java — missing default implementations on newly added interface methods. TooManyInvocationsError.java — unused count field in ScoredInvocation.

Important Files Changed

Filename Overview
spock-core/src/main/java/org/spockframework/mock/TooManyInvocationsError.java Core change: adds enrichWithScopeContext and appendUnmatchedInvocations to enrich the error message with unsatisfied interactions and argument-mismatch details. Contains an unused count field in ScoredInvocation and the associated dead-code argument in scoreInvocations.
spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java Adds matchesTargetAndMethod to the public interface without a default implementation, which is a binary and source-incompatible change for external implementors.
spock-core/src/main/java/org/spockframework/mock/IInteractionScope.java Adds enrichError to the public interface without a default implementation, a source-incompatible change for third-party implementations.
spock-core/src/main/java/org/spockframework/mock/runtime/MockController.java Intercepts TooManyInvocationsError in handle() and enriches it with the current scope's unsatisfied interactions. The synchronized block is correctly placed and uses scopes.getFirst(), which is the active then-block scope.
spock-core/src/main/java/org/spockframework/mock/runtime/MockInteraction.java Implements matchesTargetAndMethod by skipping PositionalArgumentListConstraint and NamedArgumentListConstraint — correctly isolating target+method matching from argument matching.
spock-core/src/main/java/org/spockframework/mock/constraint/PositionalArgumentListConstraint.java Fixes varargs expansion in describeMismatch so that vararg methods no longer report <too few arguments>. The guard condition correctly mirrors the one already present in isSatisfiedBy.
spock-core/src/main/java/org/spockframework/mock/runtime/InteractionScope.java Refactors verifyInteractions to reuse getUnsatisfiedInteractions() and adds enrichError which forwards to TooManyInvocationsError.enrichWithScopeContext.
spock-specs/src/test/groovy/org/spockframework/smoke/mock/TooManyInvocations.groovy Comprehensive new tests covering wildcard interactions, multiple unsatisfied interactions, varargs, multi-argument mismatch, cross-mock/cross-method filtering, and satisfied/exhausted interactions. Well-structured and provides good regression coverage.

Sequence Diagram

sequenceDiagram
    participant SUT as SUT (when block)
    participant MC as MockController
    participant IS as InteractionScope
    participant MI as MockInteraction
    participant TMIE as TooManyInvocationsError

    SUT->>MC: handle(invocation)
    MC->>IS: match(invocation)
    IS-->>MC: exhausted interaction
    MC->>MI: accept(invocation)
    MI-->>MC: Supplier that throws TooManyInvocationsError
    MC->>MC: resultSupplier.get()
    MC->>IS: enrichError(error) [synchronized]
    IS->>IS: getUnsatisfiedInteractions()
    IS->>TMIE: enrichWithScopeContext(unsatisfiedList)
    MC->>MC: errors.add(e) then rethrow

    Note over TMIE: getMessage() called later
    TMIE->>TMIE: build header and accepted invocations
    loop for each relevant unsatisfied interaction
        TMIE->>MI: matchesTargetAndMethod(acceptedInvocation)
        MI-->>TMIE: true or false
        TMIE->>MI: computeSimilarityScore(invocation)
        MI-->>TMIE: score
        TMIE->>MI: describeMismatch(invocation)
        MI-->>TMIE: mismatch text
    end
    TMIE-->>SUT: formatted error with mismatch details
Loading

Last reviewed commit: faba9f0

Comment on lines +164 to +168
final IMockInvocation invocation;
final int count;
final int score;

private ScoredInvocation(IMockInvocation invocation, int count, int score) {
Copy link

Choose a reason for hiding this comment

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

Unused count field in ScoredInvocation

The count field is assigned in the constructor but is never read anywhere — not in compareTo, not in the rendering loop in appendUnmatchedInvocations, and not elsewhere. This is dead code that will confuse future readers, who might assume count-based tie-breaking is happening.

If the original intent was to rank more-frequently-seen invocations higher when scores are equal (which would make the "ordered by similarity" section more useful), the compareTo method is missing that secondary sort key. Otherwise the field should be removed.

Suggested change
final IMockInvocation invocation;
final int count;
final int score;
private ScoredInvocation(IMockInvocation invocation, int count, int score) {
private static class ScoredInvocation implements Comparable<ScoredInvocation> {
final IMockInvocation invocation;
final int score;
private ScoredInvocation(IMockInvocation invocation, int score) {
this.invocation = invocation;
this.score = score;
}

Comment on lines +148 to +155
private List<ScoredInvocation> scoreInvocations(IMockInteraction interaction, IMultiset<IMockInvocation> invocations) {
List<ScoredInvocation> result = new ArrayList<>();
for (Map.Entry<IMockInvocation, Integer> entry : invocations.entrySet()) {
result.add(new ScoredInvocation(entry.getKey(), entry.getValue(), interaction.computeSimilarityScore(entry.getKey())));
}
sort(result);
return result;
}
Copy link

Choose a reason for hiding this comment

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

count argument passed but never used

scoreInvocations passes entry.getValue() as the count to ScoredInvocation, but since count is unused (see the inner class below), this call site passes unnecessary data. If count is intentionally kept for future use, a comment explaining the intent would help; otherwise, removing both the field and the argument here keeps the code clean.

Suggested change
private List<ScoredInvocation> scoreInvocations(IMockInteraction interaction, IMultiset<IMockInvocation> invocations) {
List<ScoredInvocation> result = new ArrayList<>();
for (Map.Entry<IMockInvocation, Integer> entry : invocations.entrySet()) {
result.add(new ScoredInvocation(entry.getKey(), entry.getValue(), interaction.computeSimilarityScore(entry.getKey())));
}
sort(result);
return result;
}
private List<ScoredInvocation> scoreInvocations(IMockInteraction interaction, IMultiset<IMockInvocation> invocations) {
List<ScoredInvocation> result = new ArrayList<>();
for (Map.Entry<IMockInvocation, Integer> entry : invocations.entrySet()) {
result.add(new ScoredInvocation(entry.getKey(), interaction.computeSimilarityScore(entry.getKey())));
}
sort(result);
return result;
}


boolean isRequired();

boolean matchesTargetAndMethod(IMockInvocation invocation);
Copy link

Choose a reason for hiding this comment

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

Breaking API change — no default implementation

IMockInteraction is a public interface, so adding matchesTargetAndMethod without a default method is a source-incompatible change for any third-party code that directly implements this interface (not via MockInteractionDecorator).

Providing a sensible default — e.g., delegating to matches(invocation) as a conservative fallback — would preserve binary and source compatibility:

Suggested change
boolean matchesTargetAndMethod(IMockInvocation invocation);
default boolean matchesTargetAndMethod(IMockInvocation invocation) {
return matches(invocation);
}


void verifyInteractions();

void enrichError(TooManyInvocationsError error);
Copy link

Choose a reason for hiding this comment

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

Breaking API change — no default implementation

IInteractionScope is a public interface. Adding enrichError without a default is a source-incompatible change. A no-op default would allow third-party implementations to compile without modifications — mirroring the same no-op used in DefaultJavaLangObjectInteractions:

Suggested change
void enrichError(TooManyInvocationsError error);
default void enrichError(TooManyInvocationsError error) {
// no enrichment by default
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
spock-specs/src/test/groovy/org/spockframework/smoke/mock/TooManyInvocations.groovy (1)

44-56: Consider a small normalized-message assertion helper.

The new tests repeat the same normalize(e.message) / expected = normalize(...) scaffold many times. A tiny helper would leave each test focused on the message body and make future formatter edits cheaper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@spock-specs/src/test/groovy/org/spockframework/smoke/mock/TooManyInvocations.groovy`
around lines 44 - 56, Introduce a small helper to centralize the repeated
normalize-and-compare logic: add a method (e.g.
assertNormalizedMessage(actualMessage, expectedBody) or
assertNormalizedException(e, expectedBody)) that calls normalize on the actual
and on the trimmed expected body and performs the equality assertion; replace
the repeated patterns in TooManyInvocations.groovy (where code uses
normalize(e.message), builds expected = normalize("""...""".trim()), and then
compares) with calls to this new helper so each test only contains the expected
message body.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/release_notes.adoc`:
- Line 15: Rewrite the release note bullet to a full, scannable sentence:
replace the fragment "* Fix argument mismatch description for varargs methods
now correctly expanding varargs instead of reporting `<too few arguments>`" with
a clearer line like "Fix argument mismatch descriptions for varargs methods by
expanding varargs instead of reporting `<too few arguments>`", ensuring the
phrasing mentions "argument mismatch descriptions", "varargs methods", and the
behavior change (expanding varargs vs reporting `<too few arguments>`).

In
`@spock-core/src/main/java/org/spockframework/mock/constraint/PositionalArgumentListConstraint.java`:
- Around line 53-55: The current logic in PositionalArgumentListConstraint only
calls expandVarArgs(args) when argConstraints.size() != args.size(), which fails
for single-vararg calls where the raw invocation.getArguments() is an array
wrapper; change the condition to call expandVarArgs whenever
hasExpandableVarArgs(invocation.getMethod(), args) is true (regardless of
argConstraints.size() equality) so args is expanded before comparison; update
the block that references argConstraints, args,
hasExpandableVarArgs(invocation.getMethod(), args), and expandVarArgs(args) to
always expand when hasExpandableVarArgs returns true.

In `@spock-core/src/main/java/org/spockframework/mock/IInteractionScope.java`:
- Around line 35-36: Change the abstract method declaration "void
enrichError(TooManyInvocationsError error);" in the public interface
IInteractionScope into a default method: declare "default void
enrichError(TooManyInvocationsError error) { ... }" so external custom
implementations keep working; implement the default body with the common
behavior used by Spock's internal implementations (or a safe no-op if the common
logic cannot be moved here) and, if needed, extract shared logic into a
private/static helper used by the default method and existing implementations so
callers of IInteractionScope.enrichError get the same behavior without breaking
binary compatibility.

In `@spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java`:
- Around line 51-52: Change the new abstract method signature boolean
matchesTargetAndMethod(IMockInvocation invocation) in the IMockInteraction
interface to a default method that delegates to the existing
matches(IMockInvocation invocation) to preserve binary compatibility; implement
default boolean matchesTargetAndMethod(IMockInvocation invocation) { return
matches(invocation); } so third‑party implementations need not be recompiled and
can still override if desired (mirroring the delegation used in
DefaultInteraction).

In
`@spock-core/src/main/java/org/spockframework/mock/runtime/MockController.java`:
- Around line 65-68: The catch block uses scopes.getFirst() which can change
before the catch runs; instead capture the specific scope that matched the
invocation while still inside the original synchronized section and use that
captured scope to call enrichError; modify MockController so the code path that
performs the matching (inside the synchronized block where scopes and invocation
checks occur) assigns the matching scope to a local variable (e.g.
matchingScope) and then in the TooManyInvocationsError catch call
matchingScope.enrichError((TooManyInvocationsError)e) rather than
scopes.getFirst(), ensuring the enrichError call uses the exact scope that
produced the error.

---

Nitpick comments:
In
`@spock-specs/src/test/groovy/org/spockframework/smoke/mock/TooManyInvocations.groovy`:
- Around line 44-56: Introduce a small helper to centralize the repeated
normalize-and-compare logic: add a method (e.g.
assertNormalizedMessage(actualMessage, expectedBody) or
assertNormalizedException(e, expectedBody)) that calls normalize on the actual
and on the trimmed expected body and performs the equality assertion; replace
the repeated patterns in TooManyInvocations.groovy (where code uses
normalize(e.message), builds expected = normalize("""...""".trim()), and then
compares) with calls to this new helper so each test only contains the expected
message body.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2226f5d9-204b-4b43-8ffe-bbfe2e7e073b

📥 Commits

Reviewing files that changed from the base of the PR and between 383b3ea and faba9f0.

📒 Files selected for processing (12)
  • docs/release_notes.adoc
  • spock-core/src/main/java/org/spockframework/mock/DefaultInteraction.java
  • spock-core/src/main/java/org/spockframework/mock/DefaultJavaLangObjectInteractions.java
  • spock-core/src/main/java/org/spockframework/mock/IInteractionScope.java
  • spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java
  • spock-core/src/main/java/org/spockframework/mock/TooManyInvocationsError.java
  • spock-core/src/main/java/org/spockframework/mock/constraint/PositionalArgumentListConstraint.java
  • spock-core/src/main/java/org/spockframework/mock/runtime/InteractionScope.java
  • spock-core/src/main/java/org/spockframework/mock/runtime/MockController.java
  • spock-core/src/main/java/org/spockframework/mock/runtime/MockInteraction.java
  • spock-core/src/main/java/org/spockframework/mock/runtime/MockInteractionDecorator.java
  • spock-specs/src/test/groovy/org/spockframework/smoke/mock/TooManyInvocations.groovy


=== Misc

* Fix argument mismatch description for varargs methods now correctly expanding varargs instead of reporting `<too few arguments>`
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reword this bullet for readability.

This currently reads like a sentence fragment. Something like “Fix argument mismatch descriptions for varargs methods by expanding varargs instead of reporting <too few arguments>” is easier to scan in the release notes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/release_notes.adoc` at line 15, Rewrite the release note bullet to a
full, scannable sentence: replace the fragment "* Fix argument mismatch
description for varargs methods now correctly expanding varargs instead of
reporting `<too few arguments>`" with a clearer line like "Fix argument mismatch
descriptions for varargs methods by expanding varargs instead of reporting `<too
few arguments>`", ensuring the phrasing mentions "argument mismatch
descriptions", "varargs methods", and the behavior change (expanding varargs vs
reporting `<too few arguments>`).

Comment on lines +53 to +55
if (argConstraints.size() != args.size() && hasExpandableVarArgs(invocation.getMethod(), args)) {
args = expandVarArgs(args);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Expand varargs here whenever the raw argument form mismatches.

Line 53 only expands when the counts differ. For a call like foo(String... xs) with one actual vararg, invocation.getArguments() is still the array wrapper, so a mismatch such as expected foo("a") vs actual foo("b") skips expansion and Line 72 reports against the array object instead of the expanded element. That leaves the new diagnostics wrong for the common single-vararg case.

Possible fix
-    if (argConstraints.size() != args.size() && hasExpandableVarArgs(invocation.getMethod(), args)) {
+    if (hasExpandableVarArgs(invocation.getMethod(), args) && !areConstraintsSatisfiedBy(args)) {
       args = expandVarArgs(args);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@spock-core/src/main/java/org/spockframework/mock/constraint/PositionalArgumentListConstraint.java`
around lines 53 - 55, The current logic in PositionalArgumentListConstraint only
calls expandVarArgs(args) when argConstraints.size() != args.size(), which fails
for single-vararg calls where the raw invocation.getArguments() is an array
wrapper; change the condition to call expandVarArgs whenever
hasExpandableVarArgs(invocation.getMethod(), args) is true (regardless of
argConstraints.size() equality) so args is expanded before comparison; update
the block that references argConstraints, args,
hasExpandableVarArgs(invocation.getMethod(), args), and expandVarArgs(args) to
always expand when hasExpandableVarArgs returns true.

Comment on lines +35 to +36

void enrichError(TooManyInvocationsError error);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine the IInteractionScope interface
find . -name "IInteractionScope.java" -type f

Repository: spockframework/spock

Length of output: 137


🏁 Script executed:

# Examine the interface content
cat -n spock-core/src/main/java/org/spockframework/mock/IInteractionScope.java

Repository: spockframework/spock

Length of output: 1455


🏁 Script executed:

# Check for implementations of IInteractionScope
rg "implements IInteractionScope" -t java

Repository: spockframework/spock

Length of output: 369


🏁 Script executed:

# Check if this is a public API interface and understand its visibility
rg "public interface IInteractionScope" -A 20 -B 5

Repository: spockframework/spock

Length of output: 1837


🏁 Script executed:

# Check implementations for enrichError method
rg "enrichError" -A 3 spock-core/src/main/java/org/spockframework/mock/runtime/InteractionScope.java

Repository: spockframework/spock

Length of output: 191


🏁 Script executed:

# Check DefaultJavaLangObjectInteractions implementation
rg "enrichError" -A 3 spock-core/src/main/java/org/spockframework/mock/DefaultJavaLangObjectInteractions.java

Repository: spockframework/spock

Length of output: 179


🏁 Script executed:

# Check project Java version target to confirm default methods are supported
rg "source|target|maven.compiler" spock-core/pom.xml | head -20

Repository: spockframework/spock

Length of output: 122


🏁 Script executed:

# Search for any documentation about binary compatibility or API guarantees
find . -name "*.md" -o -name "CHANGELOG*" -o -name "HISTORY*" | head -10

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Find pom.xml files
find . -name "pom.xml" -type f | head -5

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Check main pom.xml for Java version
cat -n pom.xml | head -50

Repository: spockframework/spock

Length of output: 103


🏁 Script executed:

# Look for Java version configuration
rg "source|target" --type xml -A 1 -B 1 | head -30

Repository: spockframework/spock

Length of output: 2598


🏁 Script executed:

# Check if this is part of a public API specification or if there are release notes
find . -name "*.md" -type f | xargs grep -l "API\|compatibility\|breaking" 2>/dev/null | head -5

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Find build files - try gradle or other build systems
find . -maxdepth 2 -name "build.gradle*" -o -name "build.gradle.kts" -o -name "settings.gradle*"

Repository: spockframework/spock

Length of output: 126


🏁 Script executed:

# Check for any gradle or maven files in the root
ls -la | grep -E "pom|gradle|build"

Repository: spockframework/spock

Length of output: 506


🏁 Script executed:

# Look for version info and compatibility notes
find . -name "README*" -o -name "RELEASE*" -o -name "VERSION*" | head -5

Repository: spockframework/spock

Length of output: 109


🏁 Script executed:

# Check if enrichError method is new or was previously optional
git log --oneline --all -S "enrichError" | head -10

Repository: spockframework/spock

Length of output: 150


🏁 Script executed:

# Check build.gradle for Java version
cat build.gradle | grep -A 5 -B 5 "java\|sourceCompatibility\|targetCompatibility" | head -40

Repository: spockframework/spock

Length of output: 1540


🏁 Script executed:

# Search for any additional implementations of IInteractionScope
rg "class.*implements.*IInteractionScope" -t java

Repository: spockframework/spock

Length of output: 369


🏁 Script executed:

# Check if there are any subclasses that might also be affected
rg "extends InteractionScope" -t java

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Look for external documentation about this being public API
cat README.adoc | head -100

Repository: spockframework/spock

Length of output: 4348


🏁 Script executed:

# Check git history to understand the context of this change
git log --oneline --all | grep -i "enrichError\|IInteractionScope" | head -5

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Check if there are any external packages or submodules that might implement IInteractionScope
rg "org\.spockframework\.mock\.IInteractionScope" --type java | grep -v "^spock-core"

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Verify that Java 8+ is indeed the minimum target
grep -A 10 "java" build.gradle | grep -i "version\|compatibility\|source\|target" | head -10

Repository: spockframework/spock

Length of output: 566


🏁 Script executed:

# Check the current state of the method to be absolutely certain it's abstract and not default
sed -n '35,37p' spock-core/src/main/java/org/spockframework/mock/IInteractionScope.java

Repository: spockframework/spock

Length of output: 117


🏁 Script executed:

# Look for any documentation about API compatibility in Spock
find . -name "*.adoc" -o -name "*.md" | xargs grep -l "API\|breaking\|compatibility" 2>/dev/null | head -3

Repository: spockframework/spock

Length of output: 122


🏁 Script executed:

# Check if there's a changelog or migration guide
find . -name "*CHANGE*" -o -name "*MIGRATE*" -o -name "*RELEASE*" 2>/dev/null | head -5

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Check Spock's versioning and release policy
cat -n docs/extensions.adoc | head -50

Repository: spockframework/spock

Length of output: 2403


🏁 Script executed:

# Look for any semantic versioning or API stability guarantees
rg "breaking\|API.*change\|compatibility.*policy" -i docs/

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Verify the project structure - confirm IInteractionScope is in the public API
find spock-core/src/main -name "*.java" | xargs grep "public.*class.*IInteraction" | head -5

Repository: spockframework/spock

Length of output: 535


Make enrichError a default method for backwards compatibility with custom scope implementations.

IInteractionScope is public API, so adding enrichError(...) as an abstract method breaks existing external implementations. Since the method has concrete behavior in all known implementations within Spock itself, using a default method instead ensures external implementations don't break on upgrade.

Recommended fix
-  void enrichError(TooManyInvocationsError error);
+  default void enrichError(TooManyInvocationsError error) {
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spock-core/src/main/java/org/spockframework/mock/IInteractionScope.java`
around lines 35 - 36, Change the abstract method declaration "void
enrichError(TooManyInvocationsError error);" in the public interface
IInteractionScope into a default method: declare "default void
enrichError(TooManyInvocationsError error) { ... }" so external custom
implementations keep working; implement the default body with the common
behavior used by Spock's internal implementations (or a safe no-op if the common
logic cannot be moved here) and, if needed, extract shared logic into a
private/static helper used by the default method and existing implementations so
callers of IInteractionScope.enrichError get the same behavior without breaking
binary compatibility.

Comment on lines +51 to +52

boolean matchesTargetAndMethod(IMockInvocation invocation);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine IMockInteraction interface
fd IMockInteraction.java --type f -x cat -n {}

Repository: spockframework/spock

Length of output: 1810


🏁 Script executed:

# Check for implementations of IMockInteraction
rg "implements IMockInteraction" -A 2

Repository: spockframework/spock

Length of output: 1537


🏁 Script executed:

# Look for the matches method referenced in the suggested delegation
rg "boolean matches\(" spock-core/src/main/java/org/spockframework/mock/ -A 2

Repository: spockframework/spock

Length of output: 3797


🏁 Script executed:

# Check Java version in build configuration
fd -e pom.xml -e build.gradle | head -5 | xargs grep -l "source\|target\|java" 2>/dev/null | head -3

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Check if matchesTargetAndMethod is already implemented in the codebase
rg "matchesTargetAndMethod" --type java -A 3

Repository: spockframework/spock

Length of output: 2792


🏁 Script executed:

# Check Java version in pom.xml
find . -name "pom.xml" -type f | head -1 | xargs grep -E "source|target|java.version" | head -20

Repository: spockframework/spock

Length of output: 46


🏁 Script executed:

# Look for where matchesTargetAndMethod is being called
rg "matchesTargetAndMethod" -B 2 -A 2

Repository: spockframework/spock

Length of output: 3602


🏁 Script executed:

# Check git history to see if matchesTargetAndMethod is a new addition
git log --oneline -p -- spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java | head -100

Repository: spockframework/spock

Length of output: 1854


🏁 Script executed:

# Check for Java version in root pom.xml
cat pom.xml | grep -A 5 -B 5 "source\|target\|java.version" | head -30

Repository: spockframework/spock

Length of output: 103


🏁 Script executed:

# Check all Default*Interaction classes to ensure complete picture
fd "Default.*Interaction\.java" spock-core/src/main/java/org/spockframework/mock/ -x basename {}

Repository: spockframework/spock

Length of output: 246


🏁 Script executed:

# Find the correct pom.xml or gradle file
find . -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" | head -3

Repository: spockframework/spock

Length of output: 78


🏁 Script executed:

# Check root directory for build config
ls -la | head -20

Repository: spockframework/spock

Length of output: 1223


🏁 Script executed:

# Look for Java version in spock-core pom.xml
cat spock-core/pom.xml | grep -A 5 -B 5 "source\|target\|java.version" | head -40

Repository: spockframework/spock

Length of output: 114


🏁 Script executed:

# Check Java version in build.gradle
cat build.gradle | grep -A 3 -B 3 "source\|target\|java\|jvm" | head -50

Repository: spockframework/spock

Length of output: 1825


🏁 Script executed:

# Also check if there's a gradle.properties or settings.gradle for version info
cat gradle.properties 2>/dev/null || cat settings.gradle 2>/dev/null | head -30

Repository: spockframework/spock

Length of output: 958


🏁 Script executed:

# Verify all Default*Interaction subclasses to see if they implement matchesTargetAndMethod
rg "class Default.*Interaction" -A 20 | grep -E "class Default|matchesTargetAndMethod"

Repository: spockframework/spock

Length of output: 1093


Make this a default method to avoid breaking third-party implementations.

IMockInteraction is public API. Adding a new abstract method means any external implementation compiled against a previous release will fail to load without recompilation and implementation of matchesTargetAndMethod. A default implementation delegating to matches(invocation) preserves compatibility while allowing overrides. The same delegation pattern already exists in DefaultInteraction, confirming semantic correctness.

Compatibility-safe shape
-  boolean matchesTargetAndMethod(IMockInvocation invocation);
+  default boolean matchesTargetAndMethod(IMockInvocation invocation) {
+    return matches(invocation);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
boolean matchesTargetAndMethod(IMockInvocation invocation);
default boolean matchesTargetAndMethod(IMockInvocation invocation) {
return matches(invocation);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spock-core/src/main/java/org/spockframework/mock/IMockInteraction.java`
around lines 51 - 52, Change the new abstract method signature boolean
matchesTargetAndMethod(IMockInvocation invocation) in the IMockInteraction
interface to a default method that delegates to the existing
matches(IMockInvocation invocation) to preserve binary compatibility; implement
default boolean matchesTargetAndMethod(IMockInvocation invocation) { return
matches(invocation); } so third‑party implementations need not be recompiled and
can still override if desired (mirroring the delegation used in
DefaultInteraction).

Comment on lines +65 to +68
if (e instanceof TooManyInvocationsError) {
synchronized (this) {
scopes.getFirst().enrichError((TooManyInvocationsError) e);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enrich with the matched scope, not whatever is first now.

Line 67 always uses scopes.getFirst(), but the interaction that produced the TooManyInvocationsError can come from a later scope when earlier scopes do not match. Since this happens after leaving the original synchronized block, another thread can also change the deque before the catch runs. That can attach unrelated unsatisfied interactions and make the new diagnostics misleading.

Suggested fix
   public Object handle(IMockInvocation invocation) {
     Supplier<Object> resultSupplier = null;
+    IInteractionScope matchedScope = null;
     synchronized (this) {
       for (IInteractionScope scope : scopes) {
         IMockInteraction interaction = scope.match(invocation);
         if (interaction != null) {
+          matchedScope = scope;
           resultSupplier = requireNonNull(interaction.accept(invocation), "interaction must not return null");
           break;
         }
       }
       if (resultSupplier == null) {
@@
       try {
         return resultSupplier.get();
       } catch (InteractionNotSatisfiedError e) {
-        if (e instanceof TooManyInvocationsError) {
-          synchronized (this) {
-            scopes.getFirst().enrichError((TooManyInvocationsError) e);
-          }
+        if (e instanceof TooManyInvocationsError && matchedScope != null) {
+          matchedScope.enrichError((TooManyInvocationsError) e);
         }
         errors.add(e);
         throw e;
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spock-core/src/main/java/org/spockframework/mock/runtime/MockController.java`
around lines 65 - 68, The catch block uses scopes.getFirst() which can change
before the catch runs; instead capture the specific scope that matched the
invocation while still inside the original synchronized section and use that
captured scope to call enrichError; modify MockController so the code path that
performs the matching (inside the synchronized block where scopes and invocation
checks occur) assigns the matching scope to a local variable (e.g.
matchingScope) and then in the TooManyInvocationsError catch call
matchingScope.enrichError((TooManyInvocationsError)e) rather than
scopes.getFirst(), ensuring the enrichError call uses the exact scope that
produced the error.

@codecov
Copy link

codecov bot commented Mar 7, 2026

Codecov Report

❌ Patch coverage is 83.95062% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.22%. Comparing base (383b3ea) to head (faba9f0).

Files with missing lines Patch % Lines
...g/spockframework/mock/TooManyInvocationsError.java 84.21% 5 Missing and 4 partials ⚠️
...g/spockframework/mock/runtime/MockInteraction.java 66.66% 1 Missing and 1 partial ⚠️
...va/org/spockframework/mock/DefaultInteraction.java 0.00% 1 Missing ⚠️
...mework/mock/DefaultJavaLangObjectInteractions.java 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #2315      +/-   ##
============================================
+ Coverage     82.13%   82.22%   +0.08%     
- Complexity     4794     4823      +29     
============================================
  Files           469      469              
  Lines         14954    15032      +78     
  Branches       1888     1907      +19     
============================================
+ Hits          12283    12360      +77     
+ Misses         1984     1982       -2     
- Partials        687      690       +3     
Files with missing lines Coverage Δ
...k/constraint/PositionalArgumentListConstraint.java 97.67% <100.00%> (+0.11%) ⬆️
.../spockframework/mock/runtime/InteractionScope.java 100.00% <100.00%> (ø)
...rg/spockframework/mock/runtime/MockController.java 97.18% <100.00%> (+0.16%) ⬆️
...amework/mock/runtime/MockInteractionDecorator.java 62.50% <100.00%> (+2.50%) ⬆️
...va/org/spockframework/mock/DefaultInteraction.java 10.00% <0.00%> (-1.12%) ⬇️
...mework/mock/DefaultJavaLangObjectInteractions.java 58.33% <0.00%> (-5.31%) ⬇️
...g/spockframework/mock/runtime/MockInteraction.java 81.96% <66.66%> (-1.67%) ⬇️
...g/spockframework/mock/TooManyInvocationsError.java 87.50% <84.21%> (-6.05%) ⬇️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.


=== Enhancements

* `TooManyInvocationsError` now reports unsatisfied interactions with argument mismatch details, making it easier to diagnose why invocations didn't match expected interactions
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* `TooManyInvocationsError` now reports unsatisfied interactions with argument mismatch details, making it easier to diagnose why invocations didn't match expected interactions
* `TooManyInvocationsError` now reports unsatisfied interactions with argument mismatch details, making it easier to diagnose why invocations didn't match expected interactions spockPull:2315[]


=== Misc

* Fix argument mismatch description for varargs methods now correctly expanding varargs instead of reporting `<too few arguments>`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Fix argument mismatch description for varargs methods now correctly expanding varargs instead of reporting `<too few arguments>`
* Fix argument mismatch description for varargs methods now correctly expanding varargs instead of reporting `<too few arguments>` spockPull:2315[]

final int score;

private ScoredInvocation(IMockInvocation invocation, int count, int score) {
this.invocation = invocation;
Copy link
Member

Choose a reason for hiding this comment

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

Please add a null check here.

out.defaultWriteObject();
}

private static class ScoredInvocation implements Comparable<ScoredInvocation> {
Copy link
Member

Choose a reason for hiding this comment

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

This is the same code as in TooFewInvocationsError, how about extract the logic into a package-private Util class?

Comment on lines +133 to +141
for (ScoredInvocation si : scored) {
if (idx++ < 5) {
try {
builder.append(unsatisfied.describeMismatch(si.invocation));
} catch (AssertionError | Exception e) {
builder.append("<Renderer threw Exception>: ").append(e.getMessage());
}
builder.append('\n');
}
Copy link
Member

Choose a reason for hiding this comment

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

This is similar code as in TooFewInvocationsError, how about extract the logic into a package-private Util class?

| | |
0 | 1
false
'''.trim())
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'''.trim())
''')

| | |
2 | 1
false
'''.trim())
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'''.trim())
''')

| | |
2 | 1
false
'''.trim())
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'''.trim())
''')

Matching invocations (ordered by last occurrence):

1 * list.add(2) <-- this triggered the error
'''.trim())
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'''.trim())
''')

Copy link
Member

Choose a reason for hiding this comment

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

Note: All the trim() calls, you already do that in the normalize() method.
I stopped to mark everything.

Comment on lines +65 to +69
if (e instanceof TooManyInvocationsError) {
synchronized (this) {
scopes.getFirst().enrichError((TooManyInvocationsError) e);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Maybe extract this into a new method to make it more readable.

@AndreasTu AndreasTu added this to the 2.5 milestone Mar 8, 2026
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.

2 participants