Skip to content

Commit 313a25a

Browse files
fix: implement RulePreprocessor and resolve test failures
1 parent 40d4bda commit 313a25a

22 files changed

+1604
-1331
lines changed

src/main/java/io/github/mikeddavydov/jpwise/JPWise.java

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.slf4j.LoggerFactory;
1010

1111
import io.github.mikeddavydov.jpwise.algo.CombinatorialAlgorithm;
12+
import io.github.mikeddavydov.jpwise.algo.LegacyPairwiseAlgorithm;
1213
import io.github.mikeddavydov.jpwise.algo.PairwiseAlgorithm;
1314
import io.github.mikeddavydov.jpwise.core.CombinationTable;
1415
import io.github.mikeddavydov.jpwise.core.CompatibilityPredicate;
@@ -158,7 +159,7 @@ public static CombinationTable generatePairwise(
158159
* @throws NullPointerException if input is null
159160
*/
160161
public static CombinationTable generatePairwise(TestInput input) {
161-
return executeGeneration(input, new PairwiseAlgorithm(), 2);
162+
return executeGeneration(input, new LegacyPairwiseAlgorithm(), 2);
162163
}
163164

164165
/**
@@ -263,16 +264,23 @@ public static CombinationTable generateCombinatorial(
263264
if (limit < 1) {
264265
throw new IllegalArgumentException("limit must be positive");
265266
}
266-
return executeGeneration(input, algorithm, limit);
267+
CombinatorialAlgorithm effectiveAlgorithm = new CombinatorialAlgorithm(limit);
268+
return executeGeneration(input, effectiveAlgorithm, limit);
267269
}
268270

269271
private static CombinationTable executeGeneration(
270272
TestInput input, GenerationAlgorithm algorithm, int nWiseOrLimit) {
271-
logger.debug("Executing test generation with {} algorithm and {} limit",
273+
logger.debug("JPWise.executeGeneration called with algorithm: {}, nWiseOrLimit: {}",
272274
algorithm.getClass().getSimpleName(), nWiseOrLimit);
275+
Objects.requireNonNull(input, "input must not be null");
276+
Objects.requireNonNull(algorithm, "algorithm must not be null");
277+
if (nWiseOrLimit < 1) {
278+
throw new IllegalArgumentException("nWiseOrLimit must generally be positive.");
279+
}
280+
273281
TestGenerator generator = new TestGenerator(input);
274-
generator.generate(algorithm, nWiseOrLimit);
275-
CombinationTable result = generator.result();
282+
logger.debug("JPWise.executeGeneration: TestGenerator created. Calling TestGenerator.generate()...");
283+
CombinationTable result = generator.generate(algorithm, nWiseOrLimit);
276284
logger.info("Generated {} test combinations", result.size());
277285
return result;
278286
}
@@ -383,22 +391,25 @@ public TestInput build() {
383391
* @return A table of generated test combinations
384392
*/
385393
public CombinationTable generatePairwise() {
386-
return JPWise.generatePairwise(testInput);
394+
logger.info("InputBuilder.generatePairwise() called. Using PairwiseAlgorithm by default.");
395+
return JPWise.executeGeneration(testInput, new PairwiseAlgorithm(), 2);
387396
}
388397

389398
/**
390-
* Convenience method to generate test combinations using a configured pairwise
391-
* algorithm.
399+
* Generates pairwise combinations using the legacy pairwise algorithm with a
400+
* specific jump value.
401+
* This method is kept for specific scenarios or comparison and will likely be
402+
* deprecated.
392403
*
393-
* @param jumpValue The jump value for the pairwise algorithm
394-
* @return A table of generated test combinations
395-
* @throws IllegalArgumentException if jumpValue is less than 1
404+
* @param jumpValue The jump value for the legacy pairwise algorithm.
405+
* @return The generated {@link CombinationTable}.
396406
*/
397-
public CombinationTable generatePairwise(int jumpValue) {
398-
if (jumpValue < 1) {
399-
throw new IllegalArgumentException("jumpValue must be positive");
400-
}
401-
return JPWise.generatePairwise(testInput, new PairwiseAlgorithm(jumpValue));
407+
@Deprecated
408+
public CombinationTable generateLegacyPairwise(int jumpValue) {
409+
logger.info(
410+
"InputBuilder.generateLegacyPairwise(jumpValue={}) called. Using LegacyPairwiseAlgorithm.",
411+
jumpValue);
412+
return JPWise.executeGeneration(testInput, new LegacyPairwiseAlgorithm(jumpValue), 2);
402413
}
403414

404415
/**
@@ -423,7 +434,7 @@ public CombinationTable generateCombinatorial() {
423434
*/
424435
public CombinationTable generateCombinatorial(int limit) {
425436
return JPWise.generateCombinatorial(
426-
testInput, new CombinatorialAlgorithm(), Integer.valueOf(limit));
437+
testInput, new CombinatorialAlgorithm(limit), Integer.valueOf(limit));
427438
}
428439
}
429440
}

src/main/java/io/github/mikeddavydov/jpwise/algo/CombinatorialAlgorithm.java

Lines changed: 47 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -7,190 +7,86 @@
77
import org.slf4j.LoggerFactory;
88

99
import io.github.mikeddavydov.jpwise.core.Combination;
10+
import io.github.mikeddavydov.jpwise.core.CombinationTable;
1011
import io.github.mikeddavydov.jpwise.core.EquivalencePartition;
1112
import io.github.mikeddavydov.jpwise.core.GenerationAlgorithm;
12-
import io.github.mikeddavydov.jpwise.core.TestGenerator;
13+
import io.github.mikeddavydov.jpwise.core.TestInput;
1314
import io.github.mikeddavydov.jpwise.core.TestParameter;
1415

1516
/**
16-
* Implements a full combinatorial test case generation algorithm. This
17-
* algorithm generates all
18-
* possible combinations of parameter values while respecting compatibility
19-
* rules between values.
20-
*
17+
* Algorithm that generates all possible combinations of parameter values.
2118
* <p>
22-
* Unlike the pairwise algorithm that only covers pairs of values, this
23-
* algorithm generates every
24-
* possible combination, which provides complete coverage but results in a much
25-
* larger number of
26-
* test cases.
27-
*
19+
* This algorithm generates the complete set of possible combinations by
20+
* taking the cartesian product of all parameter values. It then filters
21+
* out combinations that violate compatibility rules.
2822
* <p>
29-
* The number of test cases grows exponentially with the number of parameters
30-
* and their values.
31-
* For example, with 3 parameters having 3 values each, this could generate up
32-
* to 27 test cases
33-
* (3^3).
34-
*
35-
* <p>
36-
* Example usage:
37-
*
38-
* <pre>
39-
* TestInput input = new TestInput();
40-
* input.add(new TestParameter("browser", browserValues));
41-
* input.add(new TestParameter("os", osValues));
42-
*
43-
* TestGenerator generator = new TestGenerator(input);
44-
* generator.generate(new CombinatorialAlgorithm(), 99); // 99 indicates full coverage
45-
* CombinationTable results = generator.result();
46-
* </pre>
47-
*
48-
* <p>
49-
* Use this algorithm when:
50-
*
51-
* <ul>
52-
* <li>Complete test coverage is required
53-
* <li>The number of parameters and values is small
54-
* <li>You need to verify all possible interactions
55-
* </ul>
56-
*
57-
* @author DavydovMD
58-
* @see GenerationAlgorithm
59-
* @see TestGenerator
23+
* Note that this algorithm can generate a very large number of combinations
24+
* if there are many parameters or values per parameter. Use with caution.
6025
*/
6126
public class CombinatorialAlgorithm extends GenerationAlgorithm {
62-
private static final Logger logger = LoggerFactory.getLogger(CombinatorialAlgorithm.class);
27+
private static final Logger logger = LoggerFactory.getLogger(
28+
CombinatorialAlgorithm.class);
29+
private int limit = Integer.MAX_VALUE;
6330

64-
/** Creates a new combinatorial algorithm instance. */
6531
public CombinatorialAlgorithm() {
6632
super();
67-
logger.debug("Created new CombinatorialAlgorithm instance");
6833
}
6934

7035
/**
71-
* Generates all possible combinations of parameter values. The algorithm builds
72-
* combinations
73-
* recursively, checking compatibility rules at each step to avoid generating
74-
* invalid
75-
* combinations.
76-
*
77-
* @param testGenerator The test generator containing input parameters
78-
* @param limit The maximum number of combinations to generate
36+
* Constructs a CombinatorialAlgorithm with a specific limit.
37+
* This limit can be overridden by the one passed to the generate method.
38+
*
39+
* @param limit The maximum number of combinations to generate.
7940
*/
80-
@Override
81-
public void generate(TestGenerator testGenerator, int limit) {
82-
logger.info("Starting combinatorial test generation with limit {}", limit);
83-
84-
List<TestParameter> parameters = testGenerator.input().getTestParameters();
85-
if (parameters.isEmpty()) {
86-
logger.warn("No parameters provided for test generation");
87-
return;
88-
}
89-
90-
List<Combination> results = new ArrayList<>();
91-
Combination current = new Combination(parameters.size());
92-
93-
generateCombinationsRecursive(parameters, 0, current, results, limit);
94-
95-
// Add all valid combinations to the result table
96-
for (Combination combination : results) {
97-
if (combination.isFilled()) {
98-
testGenerator.result().add(combination);
99-
}
41+
public CombinatorialAlgorithm(int limit) {
42+
super();
43+
if (limit <= 0) {
44+
throw new IllegalArgumentException("Limit must be positive.");
10045
}
101-
102-
logger.info(
103-
"Generated {} valid combinations out of {} possible combinations",
104-
results.size(),
105-
calculateTotalCombinations(parameters));
46+
this.limit = limit;
10647
}
10748

108-
/**
109-
* Thoroughly checks if all values in a combination are compatible with each
110-
* other.
111-
* This method checks all possible combinations of values, not just pairs.
112-
* It is slower but more accurate for complex compatibility rules.
113-
*
114-
* @param combination The combination to check
115-
* @return true if all values are compatible, false otherwise
116-
*/
117-
private boolean checkCombinationThoroughly(Combination combination) {
118-
if (combination == null) {
119-
logger.warn("Combination is null, skipping compatibility check");
120-
return true;
121-
}
122-
123-
EquivalencePartition[] values = combination.getValues();
124-
if (values == null || values.length < 2) {
125-
return true;
126-
}
127-
128-
// Check all possible combinations of values
129-
for (int i = 0; i < values.length; i++) {
130-
if (values[i] == null) {
131-
continue;
132-
}
49+
@Override
50+
public CombinationTable generate(TestInput input, int nWiseOrLimit) {
51+
// The nWiseOrLimit parameter for CombinatorialAlgorithm is the actual limit.
52+
final int effectiveLimit = (nWiseOrLimit > 0) ? nWiseOrLimit : this.limit;
53+
logger.info("Generating all possible combinations for {} parameters, limit: {}",
54+
input.getTestParameters().size(), effectiveLimit);
13355

134-
// Check compatibility with all other values
135-
for (int j = 0; j < values.length; j++) {
136-
if (i == j || values[j] == null) {
137-
continue;
138-
}
56+
List<Combination> combinations = new ArrayList<>();
57+
List<TestParameter> parameters = input.getTestParameters();
13958

140-
// Check compatibility in both directions
141-
if (!values[i].isCompatibleWith(values[j]) || !values[j].isCompatibleWith(values[i])) {
142-
logger.debug("Found incompatible values: {} and {}", values[i], values[j]);
143-
return false;
144-
}
145-
}
146-
}
59+
Combination current = new Combination(parameters);
60+
// Pass effectiveLimit to the recursive helper
61+
generateCombinationsRecursive(combinations, current, parameters, 0, effectiveLimit);
14762

148-
return true;
63+
logger.info("Generated {} combinations (limit was {})", combinations.size(), effectiveLimit);
64+
return new CombinationTable(combinations);
14965
}
15066

151-
private void generateCombinationsRecursive(
152-
List<TestParameter> parameters,
153-
int currentIndex,
154-
Combination current,
155-
List<Combination> results,
156-
int limit) {
157-
if (currentIndex >= parameters.size()) {
158-
if (current.isFilled() && checkCombinationThoroughly(current)) {
159-
results.add(new Combination(current));
160-
if (results.size() >= limit) {
161-
return;
162-
}
67+
private void generateCombinationsRecursive(List<Combination> combinations, Combination current,
68+
List<TestParameter> parameters, int index, final int effectiveLimit) {
69+
if (index == parameters.size()) {
70+
if (combinations.size() >= effectiveLimit) {
71+
return;
72+
}
73+
if (isValidCombination(current)) {
74+
combinations.add(new Combination(current));
16375
}
16476
return;
16577
}
16678

167-
TestParameter parameter = parameters.get(currentIndex);
168-
if (parameter == null || parameter.getPartitions().isEmpty()) {
169-
logger.warn("Parameter at index {} is null or has no partitions", currentIndex);
79+
if (combinations.size() >= effectiveLimit) {
17080
return;
17181
}
17282

83+
TestParameter parameter = parameters.get(index);
17384
for (EquivalencePartition partition : parameter.getPartitions()) {
174-
if (partition == null) {
175-
logger.warn("Null partition found in parameter {}", parameter.getName());
176-
continue;
177-
}
178-
179-
current.setValue(currentIndex, partition);
180-
if (checkCombinationThoroughly(current)) {
181-
generateCombinationsRecursive(parameters, currentIndex + 1, current, results, limit);
182-
if (results.size() >= limit) {
183-
return;
184-
}
85+
current.setValue(index, partition);
86+
generateCombinationsRecursive(combinations, current, parameters, index + 1, effectiveLimit);
87+
if (combinations.size() >= effectiveLimit) {
88+
return;
18589
}
18690
}
18791
}
188-
189-
private long calculateTotalCombinations(List<TestParameter> parameters) {
190-
long total = 1;
191-
for (TestParameter parameter : parameters) {
192-
total *= parameter.getPartitions().size();
193-
}
194-
return total;
195-
}
19692
}

0 commit comments

Comments
 (0)