Skip to content

Add standalone @DataProvider methods#2379

Draft
leonard84 wants to merge 7 commits into
spockframework:masterfrom
leonard84:standalone-data-provider-methods
Draft

Add standalone @DataProvider methods#2379
leonard84 wants to merge 7 commits into
spockframework:masterfrom
leonard84:standalone-data-provider-methods

Conversation

@leonard84

Copy link
Copy Markdown
Member

Summary

Introduces @DataProvider methods: a method whose body is written exactly like the content of a where: block, and whose call returns a fresh, lazy Iterator over the resulting rows. Each row is an arity-specific Groovy tuple (Tuple1, Tuple2, ...), so the provider can feed any feature through the existing multi-variable data pipe ([a, b] << provider()).

This makes the full where: block grammar reusable outside a single feature:

  • accepts local variables, data tables, data pipes, derived variables, combined: multiplications, and a trailing filter: block;
  • all resulting data variables become tuple columns in declaration order (where-block local variables do not);
  • may be declared on any class (not only specifications), static or instance, and may declare method parameters;
  • inside a spec it follows the same access rules as a where: block (@Shared/static readable, plain instance fields not);
  • def providers get an inferred Iterator<TupleN> return type; an explicit return type must be java.util.Iterator;
  • the returned iterator is AutoCloseable and closes any AutoCloseable where-block local variables in reverse declaration order, including when consumed indirectly or via a feature's data pipe.

Implementation notes

  • Extracts IDataIterationContext / IBaseDataIterator from DataIteratorFactory so standalone providers and feature methods share the tuple-iteration machinery.
  • Splits where-block parsing from feature-method emission in WhereBlockRewriter, exposing the parsed providers/variables for reuse by the new DataProviderMethodRewriter/DataProviderMethodTransformation.

Test plan

  • New StandaloneDataProviderMethods smoke spec (47 cases), all green.
  • Full org.spockframework.smoke.parameterization.* and org.spockframework.docs.datadriven.* suites pass (501 passed, 1 skipped, 0 failed), confirming existing where-block behavior is unaffected.

🤖 Generated with Claude Code

Behavior-preserving refactor preparing for standalone @dataProvider
methods:

- Introduce IBaseDataIterator<T> as the generic base of IDataIterator
  (which stays a non-generic Object[] specialization, so all existing
  references keep compiling unchanged) and move the shared members
  (estimated count, data variable names, where-variable values,
  NO_WHERE_VARIABLE_VALUES) to the base.
- Widen the estimate check in BaseDataIterator.estimateNumIterations
  from IDataIterator to IBaseDataIterator.
- Introduce IDataIterationContext abstracting the iterator chain's
  coupling to SpockExecutionContext, IRunSupervisor and FeatureInfo
  (error sink, generated methods, providers, invocation targets,
  filter-logging config), with FeatureDataIterationContext as the
  execution-context-backed adapter used by the feature path.
- Rename FeatureDataProviderIterator to DataProviderIterators and
  expose the chain construction as createDataIterator(context) so a
  standalone context can reuse the identical chain.
Behavior-preserving refactor preparing for standalone @dataProvider
methods:

- WhereBlockRewriter.rewrite() now runs an explicit parse step that
  records per-provider DataProviderArtifacts (provider expression, the
  data variables it supplies, previous data table variables) followed
  by the unchanged feature emission (provider/processor/wherevars/
  multiplications/filter methods plus feature parameter handling).
- Per-provider state is captured eagerly while parsing because it
  depends on parse progress (e.g. derived variables declared after a
  provider must not leak into its DataProviderMetadata).
- New WhereBlockRewriter.parse(...) entry point exposes the parsed
  artifacts without emitting methods, for the standalone path that
  emits them as inline closures instead; instance-field access checking
  is optional there because plain (non-spec) classes allow instance
  field access.
A method annotated with spock.lang.DataProvider has its body parsed as
where-block content (where-block variables, data tables, data pipes,
derived columns, combined: multiplications, filter: blocks) and returns
a fresh, lazy Iterator<Tuple> of rows that any feature can consume via
the existing multi-variable data pipe ([a, b] << pairs()).

Compiler side:
- DataProviderMethodTransformation, a local SEMANTIC_ANALYSIS transform
  attached to the annotation, so it fires on any class, spec or not.
  SpecParser skips @dataProvider methods so the global SpockTransform
  hands the local transform the identical raw body on both paths.
- DataProviderMethodRewriter splits the body at the filter: label,
  parses it via the shared WhereBlockRewriter builder, and replaces the
  body with a StandaloneDataProviders.dataIterator(...) call whose
  arguments are the parsed artifacts emitted as inline closures, so
  method parameters and 'this' are captured naturally. It then reruns
  a VariableScopeVisitor to rebuild the new closures' scopes.
- Method parameters are body-wide inputs (like final where-block
  variables), kept verbatim in the signature, never columns, and never
  auto-closed; name collisions with data variables are compile errors.
- def/Object return types get a synthesized Iterator<TupleN> (resolved
  dynamically, degrading to Iterator<Tuple> for arities the compiling
  Groovy version does not ship); explicit return types are validated.
- @CompileStatic is rejected with a clear error; the instance-field
  access rule applies only inside specifications.
- @StandaloneDataProviderMetadata records column names and line.

Runtime side:
- StandaloneDataProviders builds the regular data iterator chain via
  the standalone IDataIterationContext (closure-backed MethodInfos,
  throw-on-error, no supervisor) and decorates it as an
  IStandaloneDataIterator: rows map to arity-specific tuples (Tuple1..
  Tuple16 resolved reflectively, generic Tuple fallback on Groovy 2.5
  for arity 10+), close() is idempotent, and exhaustion closes eagerly
  so AutoCloseable where-block variables are released even when the
  iterator is consumed indirectly and discarded.
- The consuming feature picks up the estimated iteration count through
  the IBaseDataIterator estimate widening from the engine refactor.
- Add a 'Standalone Data Provider Methods' section to the data-driven
  testing docs with runnable samples in DataSpec (basic data table
  provider consumed via [a, b] << provider(), and a parameterized
  static provider with a where-block local variable, derived column
  and filter block), plus a release notes entry.
- Extend the test suite with the estimated-iteration-count pickup by a
  consuming feature (known and unknown counts), where-block variables
  visible to pipes and the filter block, and the '||' table separator.
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b203a80e-ea9b-43f8-aad2-1ed8d8cf33db

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces standalone @DataProvider methods to Spock 2.5, allowing methods annotated with @DataProvider to reuse where: block syntax and return lazy iterators of tuples consumable by any feature. The implementation spans a compiler rewriting pipeline, runtime iteration context abstraction, and comprehensive lifecycle/cleanup semantics.

Changes

Standalone @DataProvider Methods

Layer / File(s) Summary
Public annotation and runtime API contracts
spock/lang/DataProvider.java, spock-core/src/main/java/org/spockframework/runtime/IBaseDataIterator.java, IDataIterationContext.java, IStandaloneDataIterator.java, IDataIterator.java, StandaloneDataProviderDescriptor.java, model/StandaloneDataProviderMetadata.java
New @DataProvider annotation (runtime-retained, method-targeted, AST-transform-enabled). New generic IBaseDataIterator<T> base interface exposing iteration count, data variable names, and where-variable values. Refactored IDataIterator to extend IBaseDataIterator<Object[]>. New IStandaloneDataIterator extending IBaseDataIterator<Tuple>. New StandaloneDataProviderDescriptor and StandaloneDataProviderMetadata classes for capturing provider state and metadata.
Compiler AST infrastructure
spock-core/src/main/java/org/spockframework/compiler/AstNodeCache.java, SpecParser.java
AstNodeCache now caches class nodes for StandaloneDataProviders/StandaloneDataProviderDescriptor and StandaloneDataProviderMetadata. SpecParser treats @DataProvider methods as ignored to prevent misclassification as feature methods.
DataProviderMethodRewriter: core transformation
spock-core/src/main/java/org/spockframework/compiler/DataProviderMethodRewriter.java
Rewrite component that parses method body into where/filter blocks, validates data variables and parameter collisions, synthesizes Iterator<TupleN> return types, and rebuilds the method body to call StandaloneDataProviders.dataIterator(...) with formatted closures (where-variables, data providers, processor, filter, multiplications). Attaches metadata and rescopes closures after AST replacement.
Compiler transformation pipeline
spock-core/src/main/java/org/spockframework/compiler/DataProviderMethodTransformation.java, WhereBlockRewriter.java
DataProviderMethodTransformation is the AST transformation entry point (CompilePhase.SEMANTIC_ANALYSIS). Refactored WhereBlockRewriter to support instance-field access checking and eager recording of data-provider artifacts for later feature-method emission. Adds public accessors for parse results and new DataProviderArtifact type.
Runtime iteration context abstraction
spock-core/src/main/java/org/spockframework/runtime/IDataIterationContext.java, FeatureDataIterationContext.java, StandaloneDataIterationContext.java
New IDataIterationContext interface abstracts iterator environment (error state, method metadata, targets, provider defs). FeatureDataIterationContext implements it for feature methods (bridges to supervisor/execution context). StandaloneDataIterationContext implements it for standalone providers (fail-fast errors, null targets, no logging).
DataIteratorFactory refactoring
spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java
Refactors entire iterator chain to accept IDataIterationContext instead of direct supervisor/context. Migrates error reporting to use context.error(...) and context.hasErrors(). Delegates all method/target/provider lookups to the context. Removes direct RunnerConfiguration import (logging/filtering now contextualized).
StandaloneDataProviders: runtime execution
spock-core/src/main/java/org/spockframework/runtime/StandaloneDataProviders.java
Public factory dataIterator(...) wires StandaloneDataIterationContext and delegates iteration to DataIteratorFactory. Internal StandaloneDataIterator wrapper performs lookahead to skip null rows, closes eagerly on exhaustion, and materializes tuples by selecting arity-specific TupleN constructors (1..16) via reflection with fallback to generic Tuple.
Documentation
docs/data_driven_testing.adoc, docs/release_notes.adoc
New "Standalone Data Provider Methods" section documenting supported where-block grammar, scoping/access rules, parameter behavior, return-type constraints, lifecycle/auto-closing semantics, and Groovy-version notes on available TupleN arities. Release notes entry for version 2.5 features.
Examples and comprehensive tests
spock-specs/src/test/groovy/org/spockframework/docs/datadriven/DataSpec.groovy, org/spockframework/smoke/parameterization/StandaloneDataProviderMethods.groovy
DataSpec adds instance and static @DataProvider examples. StandaloneDataProviderMethods test class validates 40+ scenarios: data-pipe consumption, tuple arity mapping, combined providers, static/instance/access rules, iterator freshness and counts, where-variable semantics, AutoCloseable cleanup (reverse-order, on exhaustion, on failure, idempotent), return-type rewriting/validation, and compile-time errors for missing data variables, collisions, and @CompileStatic.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • spockframework/spock#2370: Modifies WhereBlockRewriter and the runtime iterator pipeline to support where-block variable handling, including evaluation-once semantics and passing where values into generated provider/processor/filter calls.

Suggested reviewers

  • AndreasTu

Poem

🐰 A provider hops alone,
With tuples fresh, no longer clone,
Where blocks bloom where methods dare,
Closures dance with proper care,
Cleanup's here—reverse the pair!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: introducing standalone @DataProvider methods that enable reusable where: block grammar outside of feature methods.
Description check ✅ Passed The description comprehensively covers the feature, its usage, access rules, return types, lifecycle behavior, and implementation details with clear examples of the new capability.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@spock-core/src/main/java/org/spockframework/compiler/DataProviderMethodRewriter.java`:
- Around line 203-205: handleReturnType() currently returns for any
generics[0].isWildcard(), which incorrectly allows bounded wildcards; change the
early-return condition so it only returns for an unbounded wildcard
(Iterator<?>). Concretely, in DataProviderMethodRewriter.handleReturnType(),
replace the generics[0].isWildcard() check with a check that ensures the
wildcard has no explicit bounds (use generics[0].getUpperBounds() and
generics[0].getLowerBound() to detect an unbounded wildcard — e.g. upper bound
is Object or no meaningful upper bound and lower bound is null). If the wildcard
is bounded (has a non-Object upper bound or a non-null lower bound), do NOT
return early; instead report a validation error (use the class’s existing error
reporting helper used elsewhere in this rewriter, e.g., addError(...) or the
same mechanism used for other return-type errors) so Iterator<? extends
...>/Iterator<? super ...> do not bypass Tuple/arity checks.

In
`@spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java`:
- Around line 516-523: The code calls Arrays.stream(...) on the result of
invokeRaw(...) without checking for null; change the block in
DataIteratorFactory so you first capture the invokeRaw result into a temporary
DataVariableMultiplication[] (e.g. DataVariableMultiplication[] arr =
(DataVariableMultiplication[]) invokeRaw(null,
dataVariableMultiplicationsMethod)), then if arr == null return null immediately
(since invokeRaw already recorded the error), otherwise set
dataVariableMultiplications = Arrays.stream(arr).iterator() and
nextDataVariableMultiplication = dataVariableMultiplications.next(); keep the
existing try/catch around the invokeRaw/streaming to handle any other Throwable
and call context.error(...) there as before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef6cdfc2-0227-4c87-a1ca-00e00ce405d3

📥 Commits

Reviewing files that changed from the base of the PR and between b2699ee and edfa511.

📒 Files selected for processing (20)
  • docs/data_driven_testing.adoc
  • docs/release_notes.adoc
  • spock-core/src/main/java/org/spockframework/compiler/AstNodeCache.java
  • spock-core/src/main/java/org/spockframework/compiler/DataProviderMethodRewriter.java
  • spock-core/src/main/java/org/spockframework/compiler/DataProviderMethodTransformation.java
  • spock-core/src/main/java/org/spockframework/compiler/SpecParser.java
  • spock-core/src/main/java/org/spockframework/compiler/WhereBlockRewriter.java
  • spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java
  • spock-core/src/main/java/org/spockframework/runtime/FeatureDataIterationContext.java
  • spock-core/src/main/java/org/spockframework/runtime/IBaseDataIterator.java
  • spock-core/src/main/java/org/spockframework/runtime/IDataIterationContext.java
  • spock-core/src/main/java/org/spockframework/runtime/IDataIterator.java
  • spock-core/src/main/java/org/spockframework/runtime/IStandaloneDataIterator.java
  • spock-core/src/main/java/org/spockframework/runtime/StandaloneDataIterationContext.java
  • spock-core/src/main/java/org/spockframework/runtime/StandaloneDataProviderDescriptor.java
  • spock-core/src/main/java/org/spockframework/runtime/StandaloneDataProviders.java
  • spock-core/src/main/java/org/spockframework/runtime/model/StandaloneDataProviderMetadata.java
  • spock-core/src/main/java/spock/lang/DataProvider.java
  • spock-specs/src/test/groovy/org/spockframework/docs/datadriven/DataSpec.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/parameterization/StandaloneDataProviderMethods.groovy

Comment on lines +203 to +205
if (generics == null || generics.length != 1 || generics[0].isWildcard()) {
// raw Iterator (or an unbounded wildcard) is acceptable
return;

Copy link
Copy Markdown

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

🌐 Web query:

In Groovy's org.codehaus.groovy.ast.GenericsType API, does isWildcard() return true for bounded wildcards like ? extends Foo and ? super Foo, with bounds exposed via getUpperBounds()/getLowerBound()?

💡 Result:

In Groovy's org.codehaus.groovy.ast.GenericsType API, the isWildcard method returns true for bounded wildcards such as? extends Foo and? super Foo [1][2]. The GenericsType class is specifically designed to represent these generic type definitions, and it provides the getUpperBounds and getLowerBound methods to expose the respective bounds of the wildcard [1][3][2]. You can inspect the bounds of a wildcard-enabled GenericsType instance using these methods once isWildcard returns true [1][2].

Citations:


Fix bounded-wildcard Iterator element types bypassing @DataProvider Tuple validation

handleReturnType() returns early for any generics[0].isWildcard(), and Groovy org.codehaus.groovy.ast.GenericsType.isWildcard() is true for bounded wildcards (? extends Foo / ? super Foo). As a result, Iterator<? extends ...> / Iterator<? super ...> can skip the subsequent tuple/arity contract checks; the early return should allow only an unbounded wildcard (e.g., Iterator<?>) and otherwise report an error (using getUpperBounds() / getLowerBound() to detect “unbounded” vs “bounded”).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@spock-core/src/main/java/org/spockframework/compiler/DataProviderMethodRewriter.java`
around lines 203 - 205, handleReturnType() currently returns for any
generics[0].isWildcard(), which incorrectly allows bounded wildcards; change the
early-return condition so it only returns for an unbounded wildcard
(Iterator<?>). Concretely, in DataProviderMethodRewriter.handleReturnType(),
replace the generics[0].isWildcard() check with a check that ensures the
wildcard has no explicit bounds (use generics[0].getUpperBounds() and
generics[0].getLowerBound() to detect an unbounded wildcard — e.g. upper bound
is Object or no meaningful upper bound and lower bound is null). If the wildcard
is bounded (has a non-Object upper bound or a non-null lower bound), do NOT
return early; instead report a validation error (use the class’s existing error
reporting helper used elsewhere in this rewriter, e.g., addError(...) or the
same mechanism used for other return-type errors) so Iterator<? extends
...>/Iterator<? super ...> do not bypass Tuple/arity checks.

Comment on lines 516 to 523
if (dataVariableMultiplicationsMethod != null) {
try {
dataVariableMultiplications = Arrays.stream(((DataVariableMultiplication[]) invokeRaw(null, dataVariableMultiplicationsMethod))).iterator();
nextDataVariableMultiplication = dataVariableMultiplications.next();
} catch (Throwable t) {
supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(dataVariableMultiplicationsMethod, t, getErrorContext()));
context.error(dataVariableMultiplicationsMethod, t);
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the multiplication-method result before streaming it.

If invokeRaw(...) fails in the feature path, it already records the real error and returns null. This block then calls Arrays.stream(...) on that null, which adds a second NullPointerException on top of the original failure and breaks the new IDataIterationContext record-and-return contract.

💡 Suggested fix
       if (dataVariableMultiplicationsMethod != null) {
         try {
-          dataVariableMultiplications = Arrays.stream(((DataVariableMultiplication[]) invokeRaw(null, dataVariableMultiplicationsMethod))).iterator();
+          DataVariableMultiplication[] multiplications =
+            (DataVariableMultiplication[]) invokeRaw(null, dataVariableMultiplicationsMethod);
+          if (multiplications == null || context.hasErrors()) {
+            return null;
+          }
+          dataVariableMultiplications = Arrays.stream(multiplications).iterator();
           nextDataVariableMultiplication = dataVariableMultiplications.next();
         } catch (Throwable t) {
           context.error(dataVariableMultiplicationsMethod, t);
           return null;
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java`
around lines 516 - 523, The code calls Arrays.stream(...) on the result of
invokeRaw(...) without checking for null; change the block in
DataIteratorFactory so you first capture the invokeRaw result into a temporary
DataVariableMultiplication[] (e.g. DataVariableMultiplication[] arr =
(DataVariableMultiplication[]) invokeRaw(null,
dataVariableMultiplicationsMethod)), then if arr == null return null immediately
(since invokeRaw already recorded the error), otherwise set
dataVariableMultiplications = Arrays.stream(arr).iterator() and
nextDataVariableMultiplication = dataVariableMultiplications.next(); keep the
existing try/catch around the invokeRaw/streaming to handle any other Throwable
and call context.error(...) there as before.

@leonard84 leonard84 marked this pull request as draft June 13, 2026 17:24
On the standalone path the iteration context rethrows errors instead of
recording them, so a data provider failing during iterator chain
construction aborted the DataProviderIterators constructor after the
where-block variables had already been evaluated, leaking any
AutoCloseable values. Release everything created so far (including
partially created providers) before rethrowing; feature contexts record
errors and never throw here, so that path is unaffected.
- Replace deprecated Class.newInstance() with a getDeclaredConstructor()
  based instantiate() helper and use verifyEach for per-row assertions.
- Parse plain classes with the embedded compiler's per-test loader
  instead of constructing a dedicated GroovyClassLoader; compile()
  itself cannot be used because it filters its result to specifications.
- Tidy imports.
@leonard84 leonard84 force-pushed the standalone-data-provider-methods branch from edfa511 to e892b42 Compare June 13, 2026 17:29
@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds standalone @DataProvider methods to Spock, allowing where-block-style data tables and pipes to be defined outside of feature methods and reused across tests. The implementation introduces a new AST transformation pipeline (DataProviderMethodTransformationDataProviderMethodRewriter) that rewrites annotated methods to return a IStandaloneDataIterator, and refactors DataIteratorFactory around a new IDataIterationContext interface to share the existing data-iteration machinery between the feature path and the standalone path.

Confidence Score: 4/5

Safe to merge; findings are non-blocking quality concerns with no correctness impact under current usage

Well-structured implementation with thorough test coverage. Two P2 findings: a fragile UnsupportedOperationException in getStackTraceFilter() guarded only by a hard-coded false flag, and a documented non-volatile benign race in the tuple constructor cache. Neither blocks correctness in current code.

StandaloneDataIterationContext.java (getStackTraceFilter UnsupportedOperationException), StandaloneDataProviders.java (non-volatile tuple constructor cache)

Important Files Changed

Filename Overview
spock-core/src/main/java/org/spockframework/runtime/StandaloneDataProviders.java New runtime helper; StandaloneDataIterator look-ahead/auto-close logic is correct; lazy TupleN constructor cache uses a documented non-volatile benign race (P2)
spock-core/src/main/java/org/spockframework/runtime/StandaloneDataIterationContext.java getStackTraceFilter() throws UnsupportedOperationException; safe today because isLogFilteredIterations() is hard-coded false, but fragile (P2)
spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java Refactored to use IDataIterationContext; DataProviderIterators constructor now wrapped in try/catch for resource cleanup; null-guard on closeWhereVariables is correct
spock-core/src/main/java/org/spockframework/runtime/IDataIterationContext.java New interface cleanly abstracts feature-path vs standalone execution; Javadoc contracts are clear
spock-core/src/main/java/org/spockframework/runtime/FeatureDataIterationContext.java New implementation backed by IRunSupervisor/SpockExecutionContext; correctly delegates error reporting and stack trace filtering
spock-core/src/main/java/org/spockframework/compiler/DataProviderMethodRewriter.java Core AST rewriter; validates CompileStatic absence, emits inline closures, adds metadata annotation, and re-runs VariableScopeVisitor; logic is thorough
spock-core/src/main/java/org/spockframework/compiler/DataProviderMethodTransformation.java Local AST transform wired at SEMANTIC_ANALYSIS; follows established SpockTransform conventions for nodeCache
spock-core/src/main/java/org/spockframework/compiler/WhereBlockRewriter.java Refactored rewrite() into parse() + handleFeatureParameters() + emitFeatureMethods(); new getters expose artifacts for standalone path; change is backward-compatible
spock-core/src/main/java/org/spockframework/compiler/SpecParser.java isIgnoredMethod() correctly extended to skip @dataProvider methods so SpockTransform never interferes with the standalone path
spock-core/src/main/java/spock/lang/DataProvider.java New public annotation wiring the AST transformation; @beta marker and Javadoc are appropriate
spock-core/src/main/java/org/spockframework/runtime/IBaseDataIterator.java New common base interface for IDataIterator and IStandaloneDataIterator; clean design
spock-specs/src/test/groovy/org/spockframework/smoke/parameterization/StandaloneDataProviderMethods.groovy Comprehensive test suite (40+ cases) covering happy paths, error paths, AutoCloseable lifecycle, arity-specific tuples, and compile-error rejection

Reviews (1): Last reviewed commit: "Polish standalone @DataProvider tests" | Re-trigger Greptile

Comment on lines +120 to +124
}

@Override
public List<DataProviderInfo> getDataProviders() {
return dataProviders;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 getStackTraceFilter() unconditionally throws UnsupportedOperationException, but the contract in IDataIterationContext says it is "only consulted when isLogFilteredIterations() is true". Since isLogFilteredIterations() always returns false here the throw will never be reached at runtime, but any future caller that adds a new call-site without checking the flag first will get an unexpected runtime explosion. Returning a no-op filter makes the class safe regardless of call order.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +146 to +149
// lazily resolved (benign race); the classes must not be referenced statically
// because not all of them exist on every supported Groovy version
private static final Constructor<?>[] TUPLE_CONSTRUCTORS = new Constructor<?>[HIGHEST_FIXED_TUPLE_ARITY + 1];
private static final boolean[] TUPLE_CONSTRUCTOR_MISSING = new boolean[HIGHEST_FIXED_TUPLE_ARITY + 1];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 TUPLE_CONSTRUCTORS and TUPLE_CONSTRUCTOR_MISSING are plain (non-volatile) static arrays. The comment calls this a "benign race", but without a memory barrier a thread that writes TUPLE_CONSTRUCTORS[arity] may not have its write seen by other threads before they read TUPLE_CONSTRUCTOR_MISSING[arity], so two threads can both conclude the constructor is missing and both call resolveTupleConstructor. The duplicate work is harmless, but making both arrays volatile or using an AtomicReferenceArray / AtomicBooleanArray would eliminate the data race formally and avoid confusion in future maintenance.

A @dataProvider body is where-block content, so it may now open with an
optional `where:` label to mirror a feature method's where-block. The body is
split into blocks and grammar-checked by the same parser feature methods use:
the block-detection logic is extracted from SpecParser into a shared
BlockParser, and the @dataProvider rewriter injects a leading `where:` label
(when the first statement has none) before delegating to it.

Previously every statement label was silently ignored. Now only the where-block
grammar is accepted (a leading `where:` opener plus the `and:`, `combined:` and
`filter:` continuations); a feature-method label, a typo, or a misplaced
`where:` is a compile error instead of being swallowed.
@leonard84 leonard84 force-pushed the standalone-data-provider-methods branch from 2eae3ee to fbe9214 Compare June 13, 2026 18:18
@leonard84 leonard84 self-assigned this Jun 13, 2026
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.52197% with 71 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.50%. Comparing base (b2699ee) to head (fbe9214).

Files with missing lines Patch % Lines
...framework/compiler/DataProviderMethodRewriter.java 88.98% 9 Missing and 17 partials ⚠️
...rg/spockframework/runtime/DataIteratorFactory.java 70.42% 10 Missing and 11 partials ⚠️
...pockframework/runtime/StandaloneDataProviders.java 74.07% 7 Missing and 7 partials ⚠️
...mework/runtime/StandaloneDataIterationContext.java 87.50% 5 Missing ⚠️
...ork/compiler/DataProviderMethodTransformation.java 78.57% 3 Missing ⚠️
...framework/runtime/FeatureDataIterationContext.java 97.14% 0 Missing and 1 partial ⚠️
.../org/spockframework/runtime/IBaseDataIterator.java 50.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #2379      +/-   ##
============================================
+ Coverage     82.33%   82.50%   +0.17%     
- Complexity     4877     5005     +128     
============================================
  Files           474      481       +7     
  Lines         15205    15620     +415     
  Branches       1935     1984      +49     
============================================
+ Hits          12519    12888     +369     
- Misses         1993     2015      +22     
- Partials        693      717      +24     
Files with missing lines Coverage Δ
...java/org/spockframework/compiler/AstNodeCache.java 100.00% <100.00%> (ø)
.../java/org/spockframework/compiler/BlockParser.java 100.00% <100.00%> (ø)
...n/java/org/spockframework/compiler/SpecParser.java 93.40% <100.00%> (-1.98%) ⬇️
...rg/spockframework/compiler/WhereBlockRewriter.java 95.51% <100.00%> (+0.26%) ⬆️
...work/runtime/StandaloneDataProviderDescriptor.java 100.00% <100.00%> (ø)
...framework/runtime/FeatureDataIterationContext.java 97.14% <97.14%> (ø)
.../org/spockframework/runtime/IBaseDataIterator.java 50.00% <50.00%> (ø)
...ork/compiler/DataProviderMethodTransformation.java 78.57% <78.57%> (ø)
...mework/runtime/StandaloneDataIterationContext.java 87.50% <87.50%> (ø)
...pockframework/runtime/StandaloneDataProviders.java 74.07% <74.07%> (ø)
... and 2 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@testlens-app

testlens-app Bot commented Jun 13, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: fbe9214
▶️ Tests: 71954 executed
⚪️ Checks: 33/33 completed


Learn more about TestLens at testlens.app.

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