Pipeline.java
package com.github.dakusui.jcunitx.pipeline;
import com.github.dakusui.jcunitx.core.AArray;
import com.github.dakusui.jcunitx.exceptions.InvalidTestException;
import com.github.dakusui.jcunitx.exceptions.TestDefinitionException;
import com.github.dakusui.jcunitx.factorspace.Constraint;
import com.github.dakusui.jcunitx.factorspace.Factor;
import com.github.dakusui.jcunitx.factorspace.FactorSpace;
import com.github.dakusui.jcunitx.metamodel.Parameter;
import com.github.dakusui.jcunitx.metamodel.ParameterSpace;
import com.github.dakusui.jcunitx.metamodel.parameters.SimpleParameter;
import com.github.dakusui.jcunitx.pipeline.stages.Generator;
import com.github.dakusui.jcunitx.pipeline.stages.generators.Negative;
import com.github.dakusui.jcunitx.pipeline.stages.generators.Passthrough;
import com.github.dakusui.jcunitx.testsuite.SchemafulAArraySet;
import com.github.dakusui.jcunitx.testsuite.TestScenario;
import com.github.dakusui.jcunitx.testsuite.TestSuite;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
/**
* A pipeline object.
*/
public interface Pipeline {
TestSuite execute(Config config, ParameterSpace parameterSpace, TestScenario testScenarioFactory);
class Standard implements Pipeline {
public static Pipeline create() {
return new Standard();
}
@Override
public TestSuite execute(Config config, ParameterSpace parameterSpace, TestScenario testScenario) {
return generateTestSuite(config, preprocess(config, parameterSpace), testScenario);
}
public TestSuite generateTestSuite(Config config, ParameterSpace parameterSpace, TestScenario testScenario) {
validateSeeds(config.getRequirement().seeds(), parameterSpace);
TestSuite.Builder<?> builder = new TestSuite.Builder<>(parameterSpace, testScenario);
builder = builder.addAllToSeedTuples(config.getRequirement().seeds());
List<AArray> regularTestTuples = engine(config, parameterSpace);
builder = builder.addAllToRegularTuples(regularTestTuples);
if (config.getRequirement().generateNegativeTests())
builder = builder.addAllToNegativeTuples(
negativeTestGenerator(
config.getRequirement().generateNegativeTests(),
toFactorSpaceForNegativeTestGeneration(parameterSpace),
regularTestTuples,
config.getRequirement().seeds(),
config.getRequirement())
.generate());
return builder.build();
}
private void validateSeeds(List<AArray> seeds, ParameterSpace parameterSpace) {
List<Function<AArray, String>> checks = asList(
(AArray tuple) -> !parameterSpace.getParameterNames().containsAll(tuple.keySet()) ?
String.format("Unknown parameter(s) were found: %s in tuple: %s",
new LinkedList<String>() {{
addAll(tuple.keySet());
removeAll(parameterSpace.getParameterNames());
}},
tuple) :
null,
(AArray tuple) -> !tuple.keySet().containsAll(parameterSpace.getParameterNames()) ?
String.format("Parameter(s) were not found: %s in tuple: %s",
new LinkedList<String>() {{
addAll(parameterSpace.getParameterNames());
removeAll(tuple.keySet());
}},
tuple) :
null
);
List<String> errors = seeds.stream()
.flatMap(seed -> checks.stream().map(each -> each.apply(seed)))
.filter(Objects::nonNull)
.collect(toList());
if (!errors.isEmpty())
throw new InvalidTestException(
String.format(
"Error(s) are found in seeds: %s",
errors
));
}
public ParameterSpace preprocess(Config config, ParameterSpace parameterSpace) {
return new ParameterSpace.Builder()
.addAllParameters(
parameterSpace.getParameterNames()
.stream()
.map((String parameterName) -> toSimpleParameterIfNecessary(
config,
parameterSpace.getParameter(parameterName),
parameterSpace.getConstraints()))
.collect(toList()))
.addAllConstraints(parameterSpace.getConstraints())
.build();
}
public SchemafulAArraySet engine(Config config, ParameterSpace parameterSpace) {
return config.partitioner().apply(config.encoder().apply(parameterSpace))
.stream()
.map(config.optimizer())
.filter((Predicate<FactorSpace>) factorSpace -> !factorSpace.getFactors().isEmpty())
.map(config.generator(parameterSpace, config.getRequirement()))
.reduce(config.joiner())
.map((SchemafulAArraySet rows) -> decode(parameterSpace, rows))
.orElseThrow(TestDefinitionException::noParameterFound);
}
private SchemafulAArraySet decode(ParameterSpace parameterSpace, SchemafulAArraySet rows) {
return new SchemafulAArraySet.Builder(parameterSpace.getParameterNames()).addAll(
rows.stream()
.map((AArray encodedRow) -> decodeRow(parameterSpace, encodedRow))
.collect(toList()))
.build();
}
private AArray decodeRow(ParameterSpace parameterSpace, AArray encodedRow) {
AArray.Builder builder = new AArray.Builder();
for (String parameterName : parameterSpace.getParameterNames()) {
builder.put(parameterName, parameterSpace.getParameter(parameterName).composeValue(encodedRow));
}
return builder.build();
}
/**
* This method should be used for a parameter space that does not contain a
* constraint involving a non-simple parameter.
*/
private FactorSpace toFactorSpaceForNegativeTestGeneration(ParameterSpace parameterSpace) {
PipelineException.checkIfNoNonSimpleParameterIsInvolvedByAnyConstraint(parameterSpace);
return FactorSpace.create(
parameterSpace.getParameterNames().stream()
.map((String s) -> {
Parameter<Object> parameter = parameterSpace.getParameter(s);
return Factor.create(
s,
parameter.getKnownValues().toArray()
);
})
.collect(toList()),
new ArrayList<>(parameterSpace.getConstraints())
);
}
private Generator negativeTestGenerator(boolean generateNegativeTests, FactorSpace factorSpace, List<AArray> tuplesForRegularTests, List<AArray> encodedSeeds, Requirement requirement) {
return generateNegativeTests ?
new Negative(tuplesForRegularTests, encodedSeeds, factorSpace, requirement) :
new Passthrough(tuplesForRegularTests, factorSpace, requirement);
}
private Parameter<?> toSimpleParameterIfNecessary(Config config, Parameter<?> parameter, List<Constraint> constraints) {
if (!(parameter instanceof SimpleParameter) && isInvolvedByAnyConstraint(parameter, constraints)) {
// Extraction
return SimpleParameter.Descriptor.of(Stream.concat(
parameter.getKnownValues().stream(),
streamParameterValuesGeneratedByEngine(parameter, config))
.distinct()
.collect(toList()))
.create(parameter.getName());
}
return parameter;
}
private Stream<Object> streamParameterValuesGeneratedByEngine(Parameter<?> parameter, Config config) {
return engine(config, new ParameterSpace.Builder().addParameter(parameter).build())
.stream()
.map(row -> row.get(parameter.getName()));
}
/**
* Checks is a parameter is referenced by any constraint in a given list or it
* has any known actual values.
* <p>
* The reason why we check if it has known levels at the same time is like this.
* We can state that a conventional constraint requires some combinations are
* absent from the final test suite. On the other hans, a 'known level' requires
* it to be presented in a final test suite (seeds). Then we can state that
* known levels are 'presence constraints' while conventional ones are 'absence
* constraints'.
*
* @param parameter A parameter to be checked.
* @param constraints A list of constraints to be checked with the {@code parameter}.
*/
private boolean isInvolvedByAnyConstraint(Parameter<?> parameter, List<Constraint> constraints) {
return isReferencedBy(parameter, constraints) || !parameter.getKnownValues().isEmpty();
}
private boolean isReferencedBy(Parameter<?> parameter, List<Constraint> constraints) {
return constraints.stream().anyMatch(each -> each.involvedKeys().contains(parameter.getName()));
}
}
}