Skip to content

Commit aa59979

Browse files
authored
Fix filter blocks with shared fields and derived data variables (#2088)
Fixes #2087
1 parent 792e592 commit aa59979

File tree

5 files changed

+181
-79
lines changed

5 files changed

+181
-79
lines changed

spock-core/src/main/java/org/spockframework/compiler/WhereBlockRewriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ private void createFilterMethod() {
905905

906906
MethodNode filterMethod = new MethodNode(
907907
InternalIdentifiers.getFilterName(filterBlock.getParent().getAst().getName()),
908-
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
908+
Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
909909
ClassHelper.VOID_TYPE,
910910
dataProcessorVars
911911
.stream()

spock-core/src/main/java/org/spockframework/runtime/DataIteratorFactory.java

Lines changed: 101 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ public IDataIterator createFeatureDataIterator(SpockExecutionContext context) {
2929
if (context.getCurrentFeature().getDataProcessorMethod() == null) {
3030
return new SingleEmptyIterationDataIterator();
3131
}
32-
return new DataProcessorIterator(supervisor, context, new FeatureDataProviderIterator(supervisor, context));
32+
return new IterationFilterIterator(supervisor, context,
33+
new DataProcessorIterator(supervisor, context,
34+
new FeatureDataProviderIterator(supervisor, context)));
3335
}
3436

3537
private abstract static class BaseDataIterator implements IDataIterator {
@@ -201,6 +203,88 @@ public void close() throws Exception {
201203
}
202204
}
203205

206+
private static class IterationFilterIterator extends BaseDataIterator {
207+
private final IDataIterator delegate;
208+
private final List<String> dataVariableNames;
209+
private final boolean logFilteredIterations;
210+
private IStackTraceFilter stackTraceFilter;
211+
212+
private IterationFilterIterator(IRunSupervisor supervisor, SpockExecutionContext context, IDataIterator delegate) {
213+
super(supervisor, context);
214+
this.delegate = delegate;
215+
dataVariableNames = delegate.getDataVariableNames();
216+
217+
RunnerConfiguration runnerConfiguration = context
218+
.getRunContext()
219+
.getConfiguration(RunnerConfiguration.class);
220+
logFilteredIterations = runnerConfiguration.logFilteredIterations;
221+
if (logFilteredIterations) {
222+
stackTraceFilter = runnerConfiguration.filterStackTrace ? new StackTraceFilter(context.getSpec()) : new DummyStackTraceFilter();
223+
}
224+
}
225+
226+
@Override
227+
public int getEstimatedNumIterations() {
228+
return delegate.getEstimatedNumIterations();
229+
}
230+
231+
@Override
232+
public List<String> getDataVariableNames() {
233+
return dataVariableNames;
234+
}
235+
236+
@Override
237+
public void close() throws Exception {
238+
delegate.close();
239+
}
240+
241+
@Override
242+
public boolean hasNext() {
243+
return delegate.hasNext();
244+
}
245+
246+
@Override
247+
public Object[] next() {
248+
while (true) {
249+
Object[] next = delegate.next();
250+
251+
// delegate.next() will return null if an error occurred
252+
if (next == null) {
253+
return null;
254+
}
255+
256+
MethodInfo filterMethod = context.getCurrentFeature().getFilterMethod();
257+
if (filterMethod == null) {
258+
return next;
259+
}
260+
261+
try {
262+
// do not use invokeRaw here, as that would report Assertion Error to the supervisor
263+
filterMethod.invoke(context.getSharedInstance(), next);
264+
return next;
265+
} catch (AssertionError ae) {
266+
if (logFilteredIterations) {
267+
StringJoiner stringJoiner = new StringJoiner(", ", "Filtered iteration [", "]:\n");
268+
for (int i = 0; i < dataVariableNames.size(); i++) {
269+
stringJoiner.add(dataVariableNames.get(i) + ": " + next[i]);
270+
}
271+
StringWriter sw = new StringWriter();
272+
sw.write(stringJoiner.toString());
273+
stackTraceFilter.filter(ae);
274+
try (PrintWriter pw = new PrintWriter(sw)) {
275+
ae.printStackTrace(pw);
276+
}
277+
System.err.println(sw);
278+
}
279+
// filter block does not like these values, try next ones if available
280+
} catch (Throwable t) {
281+
supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(filterMethod, t, getErrorContext()));
282+
return null;
283+
}
284+
}
285+
}
286+
}
287+
204288
private static class DataProcessorIterator extends BaseDataIterator {
205289
private final IDataIterator delegate;
206290
private final List<String> dataVariableNames;
@@ -262,8 +346,6 @@ private static class FeatureDataProviderIterator extends BaseDataIterator {
262346
private final int estimatedNumIterations;
263347
private final List<String> dataVariableNames;
264348
private boolean firstIteration = true;
265-
private boolean logFilteredIterations = true;
266-
private IStackTraceFilter stackTraceFilter;
267349

268350
public FeatureDataProviderIterator(IRunSupervisor supervisor, SpockExecutionContext context) {
269351
super(supervisor, context);
@@ -272,14 +354,6 @@ public FeatureDataProviderIterator(IRunSupervisor supervisor, SpockExecutionCont
272354
dataProviders = createDataProviders();
273355
dataProviderIterators = createDataProviderIterators();
274356
estimatedNumIterations = estimateNumIterations(dataProviderIterators);
275-
276-
RunnerConfiguration runnerConfiguration = context
277-
.getRunContext()
278-
.getConfiguration(RunnerConfiguration.class);
279-
logFilteredIterations = runnerConfiguration.logFilteredIterations;
280-
if (logFilteredIterations) {
281-
stackTraceFilter = runnerConfiguration.filterStackTrace ? new StackTraceFilter(context.getSpec()) : new DummyStackTraceFilter();
282-
}
283357
}
284358

285359
@Override
@@ -308,60 +382,29 @@ public Object[] next() {
308382
}
309383
firstIteration = false;
310384

311-
while (true) {
312-
// advances iterators and computes args
313-
Object[] next = new Object[dataProviders.length];
314-
for (int i = 0; i < dataProviders.length; ) {
315-
try {
316-
// if the filter block excluded an iteration
317-
// this might be called after the last iteration
318-
// so just return null if no further data is available
319-
// to just cause the iteration to be skipped
320-
if (!dataProviderIterators[i].hasNext()) {
321-
return null;
322-
}
323-
Object[] nextValues = dataProviderIterators[i].next();
324-
if (nextValues == null) {
325-
return null;
326-
}
327-
System.arraycopy(nextValues, 0, next, i, nextValues.length);
328-
i += nextValues.length;
329-
} catch (Throwable t) {
330-
supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(context.getCurrentFeature().getDataProviders().get(i).getDataProviderMethod(), t, getErrorContext()));
385+
// advances iterators and computes args
386+
Object[] next = new Object[dataProviders.length];
387+
for (int i = 0; i < dataProviders.length; ) {
388+
try {
389+
// if the filter block excluded an iteration
390+
// this might be called after the last iteration
391+
// so just return null if no further data is available
392+
// to just cause the iteration to be skipped
393+
if (!dataProviderIterators[i].hasNext()) {
331394
return null;
332395
}
333-
}
334-
335-
MethodInfo filterMethod = context.getCurrentFeature().getFilterMethod();
336-
337-
if (filterMethod == null) {
338-
return next;
339-
}
340-
341-
try {
342-
// do not use invokeRaw here, as that would report Assertion Error to the supervisor
343-
filterMethod.invoke(null, next);
344-
return next;
345-
} catch (AssertionError ae) {
346-
if (logFilteredIterations) {
347-
StringJoiner stringJoiner = new StringJoiner(", ", "Filtered iteration [", "]:\n");
348-
for (int i = 0; i < dataVariableNames.size(); i++) {
349-
stringJoiner.add(dataVariableNames.get(i) + ": " + next[i]);
350-
}
351-
StringWriter sw = new StringWriter();
352-
sw.write(stringJoiner.toString());
353-
stackTraceFilter.filter(ae);
354-
try (PrintWriter pw = new PrintWriter(sw)) {
355-
ae.printStackTrace(pw);
356-
}
357-
System.err.println(sw);
396+
Object[] nextValues = dataProviderIterators[i].next();
397+
if (nextValues == null) {
398+
return null;
358399
}
359-
// filter block does not like these values, try next ones if available
400+
System.arraycopy(nextValues, 0, next, i, nextValues.length);
401+
i += nextValues.length;
360402
} catch (Throwable t) {
361-
supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(filterMethod, t, getErrorContext()));
403+
supervisor.error(context.getErrorInfoCollector(), new ErrorInfo(context.getCurrentFeature().getDataProviders().get(i).getDataProviderMethod(), t, getErrorContext()));
362404
return null;
363405
}
364406
}
407+
return next;
365408
}
366409

367410
@Override

spock-specs/src/test/groovy/org/spockframework/smoke/parameterization/DataTables.groovy

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,84 @@ i != 6
400400
result.testsSucceededCount == 6
401401
}
402402
403+
@Issue("https://github.com/spockframework/spock/issues/2087")
404+
def "filter block supports derived data variables"() {
405+
when:
406+
def result = runner.runFeatureBody '''
407+
expect:
408+
i != 2
409+
410+
where:
411+
i << (1..2)
412+
b = 42
413+
414+
filter:
415+
i != 2
416+
b == 42
417+
'''
418+
419+
then:
420+
result.testsAbortedCount == 0
421+
result.testsFailedCount == 0
422+
result.testsSkippedCount == 0
423+
result.testsStartedCount == 2
424+
result.testsSucceededCount == 2
425+
}
426+
427+
@Issue("https://github.com/spockframework/spock/issues/2087")
428+
def "filter block supports shared fields"() {
429+
when:
430+
def result = runner.runSpecBody '''
431+
@Shared
432+
def b = 42
433+
434+
def "a feature"() {
435+
expect:
436+
i != 2
437+
438+
where:
439+
i << (1..2)
440+
441+
filter:
442+
i != 2
443+
b == 42
444+
}
445+
'''
446+
447+
then:
448+
result.testsAbortedCount == 0
449+
result.testsFailedCount == 0
450+
result.testsSkippedCount == 0
451+
result.testsStartedCount == 2
452+
result.testsSucceededCount == 2
453+
}
454+
455+
def "filter block supports static fields"() {
456+
when:
457+
def result = runner.runSpecBody '''
458+
static b = 42
459+
460+
def "a feature"() {
461+
expect:
462+
i != 2
463+
464+
where:
465+
i << (1..2)
466+
467+
filter:
468+
i != 2
469+
b == 42
470+
}
471+
'''
472+
473+
then:
474+
result.testsAbortedCount == 0
475+
result.testsFailedCount == 0
476+
result.testsSkippedCount == 0
477+
result.testsStartedCount == 2
478+
result.testsSucceededCount == 2
479+
}
480+
403481
def "filter block can not exclude all iterations"() {
404482
when:
405483
runner.runFeatureBody '''

spock-specs/src/test/groovy/org/spockframework/smoke/parameterization/InvalidWhereBlocks.groovy

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -205,25 +205,6 @@ def foo() {
205205
e.message.contains("@Shared")
206206
}
207207
208-
def "filter block may not reference local variables"() {
209-
when:
210-
compiler.compileFeatureBody '''
211-
def local = 1
212-
213-
expect:
214-
x == 1
215-
216-
where:
217-
x = 1
218-
219-
filter:
220-
local
221-
'''
222-
223-
then:
224-
thrown(SyntaxException)
225-
}
226-
227208
def 'referencing a data variable in a data provider is not allowed'() {
228209
when:
229210
runner.runFeatureBody """

spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/DataTablesAstSpec/filter_block_becomes_its_own_method.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static org.spockframework.runtime.model.DataVariableMultiplication[] $spo
3737
return new org.spockframework.runtime.model.DataVariableMultiplication[]{new org.spockframework.runtime.model.DataVariableMultiplication(new java.lang.String[]{'a', 'b'}, new org.spockframework.runtime.model.DataVariableMultiplicationFactor(new java.lang.String[]{'a'}), new org.spockframework.runtime.model.DataVariableMultiplicationFactor(new java.lang.String[]{'b'}))}
3838
}
3939

40-
public static void $spock_feature_0_0filter(java.lang.Object a, java.lang.Object b) {
40+
public void $spock_feature_0_0filter(java.lang.Object a, java.lang.Object b) {
4141
org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE
4242
org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder()
4343
try {

0 commit comments

Comments
 (0)