Skip to content

Commit 24c5328

Browse files
committed
Fix cross-multiplication of multi-assignment data providers (#2074)
1 parent 9d1e819 commit 24c5328

File tree

2 files changed

+97
-40
lines changed

2 files changed

+97
-40
lines changed

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

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.*;
88

99
import org.jetbrains.annotations.NotNull;
10+
import org.spockframework.util.Assert;
1011
import org.spockframework.util.Nullable;
1112
import spock.config.RunnerConfiguration;
1213

@@ -431,23 +432,31 @@ private IDataIterator[] createDataProviderIterators() {
431432
nextDataVariableMultiplication = null;
432433
}
433434

435+
List<DataProviderInfo> dataProviderInfos = context.getCurrentFeature().getDataProviders();
434436
List<IDataIterator> dataIterators = new ArrayList<>(dataProviders.length);
435-
for (int i = 0; i < dataProviders.length; i++) {
436-
String nextDataVariableName = dataVariableNames.get(i);
437+
for (int dataProviderIndex = 0, dataVariableNameIndex = 0; dataProviderIndex < dataProviders.length; dataProviderIndex++, dataVariableNameIndex++) {
438+
String nextDataVariableName = dataVariableNames.get(dataVariableNameIndex);
437439
if ((nextDataVariableMultiplication != null)
438440
&& (nextDataVariableMultiplication.getDataVariables()[0].equals(nextDataVariableName))) {
439441

440442
// a cross multiplication starts
441-
dataIterators.add(createDataProviderMultiplier(nextDataVariableMultiplication, i));
442-
// skip processed variables
443-
i += nextDataVariableMultiplication.getDataVariables().length - 1;
443+
dataIterators.add(createDataProviderMultiplier(nextDataVariableMultiplication, dataProviderIndex));
444+
// skip processed providers and variables
445+
int remainingVariables = nextDataVariableMultiplication.getDataVariables().length;
446+
dataVariableNameIndex += remainingVariables - 1;
447+
while (remainingVariables > 0) {
448+
remainingVariables -= dataProviderInfos.get(dataProviderIndex).getDataVariables().size();
449+
dataProviderIndex++;
450+
}
451+
dataProviderIndex--;
452+
Assert.that(remainingVariables == 0);
444453
// wait for next cross multiplication
445454
nextDataVariableMultiplication = dataVariableMultiplications.hasNext() ? dataVariableMultiplications.next() : null;
446455
} else {
447456
// not a cross multiplication, just use a data provider iterator
448457
dataIterators.add(new DataProviderIterator(
449458
supervisor, context, nextDataVariableName,
450-
context.getCurrentFeature().getDataProviders().get(i), dataProviders[i]));
459+
dataProviderInfos.get(dataProviderIndex), dataProviders[dataProviderIndex]));
451460
}
452461
}
453462
return dataIterators.toArray(new IDataIterator[0]);
@@ -457,16 +466,13 @@ private IDataIterator[] createDataProviderIterators() {
457466
* Creates a multiplier that is backed by data providers and on-the-fly multiplies them as they are processed.
458467
*
459468
* @param dataVariableMultiplication the multiplication for which to create the multiplier
460-
* @param i the index of the first data variable for the given multiplication
469+
* @param dataProviderOffset the index of the first data provider for the given multiplication
461470
* @return the data provider multiplier
462471
*/
463-
private DataProviderMultiplier createDataProviderMultiplier(DataVariableMultiplication dataVariableMultiplication, int i) {
472+
private DataProviderMultiplier createDataProviderMultiplier(DataVariableMultiplication dataVariableMultiplication, int dataProviderOffset) {
464473
DataVariableMultiplicationFactor multiplier = dataVariableMultiplication.getMultiplier();
465474
DataVariableMultiplicationFactor multiplicand = dataVariableMultiplication.getMultiplicand();
466475

467-
int multiplierDataVariablesLength = multiplier.getDataVariables().length;
468-
int multiplicandDataVariablesLength = multiplicand.getDataVariables().length;
469-
470476
if (multiplier instanceof DataVariableMultiplication) {
471477
// recursively dive into the multiplication depth-first
472478
// if you combined a with b with c with d, the multiplication is represented as
@@ -478,49 +484,48 @@ private DataProviderMultiplier createDataProviderMultiplier(DataVariableMultipli
478484
// here we first build the data provider multiplier for the multiplier
479485
// then we collect the data provider infos and data providers for the multiplicand
480486
// and then create the data provider multiplier for them
481-
DataProviderMultiplier multiplierProvider = createDataProviderMultiplier((DataVariableMultiplication) multiplier, i);
487+
DataProviderMultiplier multiplierProvider = createDataProviderMultiplier((DataVariableMultiplication) multiplier, dataProviderOffset);
482488
List<DataProviderInfo> multiplicandProviderInfos = new ArrayList<>();
483-
Object[] multiplicandProviders = new Object[multiplicandDataVariablesLength];
484-
485-
List<DataProviderInfo> dataProviderInfos = context.getCurrentFeature().getDataProviders();
486-
int j = multiplierDataVariablesLength;
487-
int j2 = multiplierDataVariablesLength + multiplicandDataVariablesLength;
488-
int k = 0;
489-
for (; j < j2; j++, k++) {
490-
multiplicandProviderInfos.add(dataProviderInfos.get(i + j));
491-
multiplicandProviders[k] = dataProviders[i + j];
492-
}
489+
List<Object> multiplicandProviders = new ArrayList<>();
490+
collectDataProviders(dataProviderOffset + multiplierProvider.getProcessedDataProviders(), multiplicand, multiplicandProviderInfos, multiplicandProviders);
493491

494492
return new DataProviderMultiplier(supervisor, context,
495493
Arrays.asList(dataVariableMultiplication.getDataVariables()),
496-
multiplicandProviderInfos, multiplierProvider, multiplicandProviders);
494+
multiplicandProviderInfos, multiplierProvider,
495+
multiplicandProviders.toArray(new Object[0]));
497496
} else {
498497
// this path handles the innermost multiplication (a * b)
499498
//
500499
// it collects the data provider infos and data providers for a and b
501500
// and then creates a data provider multiplier for them
502501
List<DataProviderInfo> multiplierProviderInfos = new ArrayList<>();
503502
List<DataProviderInfo> multiplicandProviderInfos = new ArrayList<>();
504-
Object[] multiplierProviders = new Object[multiplierDataVariablesLength];
505-
Object[] multiplicandProviders = new Object[multiplicandDataVariablesLength];
506-
507-
List<DataProviderInfo> dataProviderInfos = context.getCurrentFeature().getDataProviders();
508-
int j = 0;
509-
int j2 = multiplierDataVariablesLength;
510-
for (; j < j2; j++) {
511-
multiplierProviderInfos.add(dataProviderInfos.get(i + j));
512-
multiplierProviders[j] = dataProviders[i + j];
513-
}
514-
int k = 0;
515-
for (j2 += multiplicandDataVariablesLength; j < j2; j++, k++) {
516-
multiplicandProviderInfos.add(dataProviderInfos.get(i + j));
517-
multiplicandProviders[k] = dataProviders[i + j];
518-
}
503+
List<Object> multiplierProviders = new ArrayList<>();
504+
List<Object> multiplicandProviders = new ArrayList<>();
505+
collectDataProviders(dataProviderOffset, multiplier, multiplierProviderInfos, multiplierProviders);
506+
collectDataProviders(dataProviderOffset + multiplierProviderInfos.size(), multiplicand, multiplicandProviderInfos, multiplicandProviders);
519507

520508
return new DataProviderMultiplier(supervisor, context,
521509
Arrays.asList(dataVariableMultiplication.getDataVariables()),
522-
multiplierProviderInfos, multiplicandProviderInfos, multiplierProviders, multiplicandProviders);
510+
multiplierProviderInfos, multiplicandProviderInfos,
511+
multiplierProviders.toArray(new Object[0]),
512+
multiplicandProviders.toArray(new Object[0]));
513+
}
514+
}
515+
516+
private void collectDataProviders(int dataProviderOffset, DataVariableMultiplicationFactor factor,
517+
List<DataProviderInfo> factorProviderInfos, List<Object> factorProviders) {
518+
List<DataProviderInfo> dataProviderInfos = context.getCurrentFeature().getDataProviders();
519+
int factorDataVariables = factor.getDataVariables().length;
520+
int remainingDataVariables = factorDataVariables;
521+
while (remainingDataVariables > 0) {
522+
int dataProviderIndex = dataProviderOffset + factorDataVariables - remainingDataVariables;
523+
DataProviderInfo dataProviderInfo = dataProviderInfos.get(dataProviderIndex);
524+
factorProviderInfos.add(dataProviderInfo);
525+
factorProviders.add(dataProviders[dataProviderIndex]);
526+
remainingDataVariables -= dataProviderInfo.getDataVariables().size();
523527
}
528+
Assert.that(remainingDataVariables == 0);
524529
}
525530

526531
@NotNull
@@ -667,7 +672,7 @@ private static class DataProviderMultiplier extends BaseDataIterator {
667672
/**
668673
* The data providers that are used as multiplicand
669674
* in this multiplication, if it represents in an
670-
* {@code ((a * b) * c)} multiplication the {@code (ab * b)}
675+
* {@code ((a * b) * c)} multiplication the {@code (ab * c)}
671676
* or the {@code (a * b)} part or otherwise {@code null}.
672677
* Outside the constructor it is only used for the final cleanup,
673678
*/
@@ -929,6 +934,26 @@ public List<String> getDataVariableNames() {
929934
return dataVariableNames;
930935
}
931936

937+
/**
938+
* Returns how many data providers are processed by this data provider multiplier.
939+
*
940+
* @return how many data providers are processed by this data provider multiplier
941+
*/
942+
public int getProcessedDataProviders() {
943+
int result = 0;
944+
if (multiplierProvider != null) {
945+
result += multiplierProvider.getProcessedDataProviders();
946+
} else if (multiplierProviders != null) {
947+
result += multiplierProviders.length;
948+
}
949+
if (multiplicandProvider != null) {
950+
result += multiplicandProvider.getProcessedDataProviders();
951+
} else if (multiplicandProviders != null) {
952+
result += multiplicandProviders.length;
953+
}
954+
return result;
955+
}
956+
932957
private Iterator<?>[] createIterators(Object[] dataProviders, List<DataProviderInfo> dataProviderInfos) {
933958
if (context.getErrorInfoCollector().hasErrors()) {
934959
return null;

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,38 @@ where: a << b
297297
]
298298
}
299299

300+
@Issue('https://github.com/spockframework/spock/issues/2074')
301+
def 'multi-assignments can be combined'() {
302+
when:
303+
def results = runner.runSpecBody '''
304+
def 'a feature (#a #b #c #d #e #f)'() {
305+
expect:
306+
a + b == c
307+
308+
where:
309+
[a, b] << [[1, 2], [1, 2]]
310+
combined:
311+
[c, d] << [[3, 1], [3, 2]]
312+
combined:
313+
[e, f] << [[1, 2], [3, 4]]
314+
}
315+
'''
316+
317+
then:
318+
results.testsStartedCount == 1 + (2 * 2 * 2)
319+
results.testEvents().started().list().testDescriptor.displayName == [
320+
'a feature (#a #b #c #d #e #f)',
321+
'a feature (1 2 3 1 1 2)',
322+
'a feature (1 2 3 1 3 4)',
323+
'a feature (1 2 3 2 1 2)',
324+
'a feature (1 2 3 2 3 4)',
325+
'a feature (1 2 3 1 1 2)',
326+
'a feature (1 2 3 1 3 4)',
327+
'a feature (1 2 3 2 1 2)',
328+
'a feature (1 2 3 2 3 4)'
329+
]
330+
}
331+
300332
static class MyIterator implements Iterator {
301333
def elems = [1, 2, 3]
302334

0 commit comments

Comments
 (0)