All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.rwth.swc.coffee4j.engine.characterization.ben.Ben Maven / Gradle / Ivy

package de.rwth.swc.coffee4j.engine.characterization.ben;

import de.rwth.swc.coffee4j.engine.TestResult;
import de.rwth.swc.coffee4j.engine.characterization.FaultCharacterizationAlgorithm;
import de.rwth.swc.coffee4j.engine.characterization.FaultCharacterizationAlgorithmFactory;
import de.rwth.swc.coffee4j.engine.characterization.FaultCharacterizationConfiguration;
import de.rwth.swc.coffee4j.engine.characterization.SuspiciousCombinationAlgorithm;
import de.rwth.swc.coffee4j.engine.util.CombinationUtil;
import de.rwth.swc.coffee4j.engine.util.Combinator;
import de.rwth.swc.coffee4j.engine.util.IntArrayWrapper;
import de.rwth.swc.coffee4j.engine.util.Preconditions;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static de.rwth.swc.coffee4j.engine.util.CombinationUtil.NO_VALUE;
import static de.rwth.swc.coffee4j.engine.util.IntArrayWrapper.wrap;
import static de.rwth.swc.coffee4j.engine.util.PredicateUtil.not;

/**
 * The implementation of the BEN fault characterization algorithm based on the paper "Identifying Failure-Inducing
 * Combinations in a Combinatorial Test Set". Generates multiple sets of new test inputs if necessary. This means it is
 * not known at which iteration an empty list is returned to signal the algorithm is stopping. Failure-Inducing
 * combinations are ranked by an internal measure of probability. This means that combinations at the beginning of the
 * returned list are more likely to be failure-inducing. The list of failure-inducing combinations is build out of lists
 * for each combination size. That means first all 1-value-combinations are returned in probability order, then all
 * two-value-combinations, ..., then all t-value combinations where t is the testing strength.
 * 

* Internally, the algorithm works with two important structures: t-value-combinations and so called components * (1-value-combinations). In each iteration all components are ranked according to appearance in failed or successful * test inputs as well as suspicious combinations. Next, all suspicious t-value combinations (those combinations * appearing in only failed test inputs) are ranked according to the suspiciousness of their contained components, and * other components in their test inputs. Consider the following example: There is a failed test input (1, 2, 3) when * testing with strength 2. (1, 2, -) appears in no successful test input so its a suspicious combinations. 1 appears in * many failed test inputs and suspicious combinations in the whole test suite and it is therefore likely that some * failure-inducing combinations contains 1. With 2, the same story is the input. 3 does not appear in any failed test * input at all. This means (1, 2, -) is very likely the combination causing the failure of (1, 2, 3). * Of course, a combination with fewer than t values can also be failure inducing. Therefore, BEN recudes all t-value- * combinations at the end. This is done by looking if all possible containing combinations are suspicious. * For example, if each parameter has 2 values and (0, 1) and (0, 2) are suspicious, (0, -) is therefore also suspicious * since there is no non-suspicious combination containing (0, -). *

* Important Information: * -Will not find any failure-inducing combination involving more than t parameters. If you expect such failure-inducing * combinations, your t is already chose wrong * -Can generate many additional test inputs if needed. The number of generated test inputs per iteration is configurable * -Orders failure-inducing combinations by probability * -Considers constraints as an addition to the original algorithm * -Not very efficient if failure-inducing combination is smaller than t values */ public class Ben extends SuspiciousCombinationAlgorithm { private static final int DEFAULT_NUMBER_OF_COMBINATIONS_PER_STEP = 10; private static final int DEFAULT_MAX_GENERATION_ATTEMPTS = 120; private final int numberOfCombinationsPerStep; private final int maxGenerationAttempts; private boolean endInNextIteration = false; /** * Builds a new instance of the algorithm for a given configuration. 10 test inputs will be generated per step, and * no further test inputs are generated in the next iteration if no test input can be found for a failure inducing * combinations after 50 attempts. * * @param configuration for knowing which combinations can be failure-inducing/which test inputs can be generated. * Must not be {@code null} * @throws NullPointerException if configuration is {@code null} */ public Ben(FaultCharacterizationConfiguration configuration) { this(configuration, DEFAULT_NUMBER_OF_COMBINATIONS_PER_STEP, DEFAULT_MAX_GENERATION_ATTEMPTS); } /** * Builds a new instance of the algorithm for the given configuration. * * @param configuration for knowing which combinations can be failure-inducing/which test inputs can be generated. * Must not be {@code null} * @param numberOfCombinationsPerStep how many test inputs should be generated per step. Must be positive * @param maxGenerationAttempts after how many attempts the algorithm should give up on generating a previously * untested test inputs containing a given failure-inducing combination. Must be positive * @throws NullPointerException if configuration is {@code null} * @throws IllegalArgumentException if one of the integers is not positive */ public Ben(FaultCharacterizationConfiguration configuration, int numberOfCombinationsPerStep, int maxGenerationAttempts) { super(configuration); Preconditions.check(numberOfCombinationsPerStep > 0); Preconditions.check(maxGenerationAttempts > 0); this.numberOfCombinationsPerStep = numberOfCombinationsPerStep; this.maxGenerationAttempts = maxGenerationAttempts; } /** * Can be used as a convenience method to describe that BEN should be used as a * {@link FaultCharacterizationAlgorithmFactory}. * * @return a factory using ben as a fault characterization algorithm. Each instance of BEN is configured to create 10 * new test inputs per iteration and uses 50 generation attempts before considering a combination to * be failure-inducing */ public static FaultCharacterizationAlgorithmFactory ben() { return configuration -> new Ben(configuration, DEFAULT_NUMBER_OF_COMBINATIONS_PER_STEP, DEFAULT_MAX_GENERATION_ATTEMPTS); } /** * Can be used as a convenience method to describe that BEN should be used as a * {@link FaultCharacterizationAlgorithmFactory}. * * @param numberOfCombinationsPerStep see {@link Ben#Ben(FaultCharacterizationConfiguration, int, int)} * @param maxGenerationAttempts see {@link Ben#Ben(FaultCharacterizationConfiguration, int, int)} * @return a factory using the constructor ({@link Ben#Ben(FaultCharacterizationConfiguration, int, int)}) to create new * {@link FaultCharacterizationAlgorithm} instances * @throws IllegalArgumentException if one of the integers is not positive */ public static FaultCharacterizationAlgorithmFactory ben(int numberOfCombinationsPerStep, int maxGenerationAttempts) { Preconditions.check(numberOfCombinationsPerStep > 0); Preconditions.check(maxGenerationAttempts > 0); return configuration -> new Ben(configuration, numberOfCombinationsPerStep, maxGenerationAttempts); } @Override public Set getRelevantSubCombinations(int[] combination) { return Combinator.computeSubCombinations(combination, getModel().getStrength()).stream().map(IntArrayWrapper::new).collect(Collectors.toSet()); } @Override public boolean shouldGenerateFurtherTestInputs() { return previousSuspiciousCombinations.size() != suspiciousCombinations.size() && !endInNextIteration && getModel().getStrength() < getModel().getNumberOfParameters(); } @Override public List generateNextTestInputs(Map newTestResults) { final Object2DoubleMap componentSuspiciousnessMap = computeComponentSuspiciousness(suspiciousCombinations); final List suspiciousCombinationsRanking = computeSuspiciousCombinationsRanking(componentSuspiciousnessMap, suspiciousCombinations); final int numberOfNewTestInputs = Math.min(suspiciousCombinationsRanking.size(), numberOfCombinationsPerStep); final List newTestInputs = new ArrayList<>(numberOfNewTestInputs); for (int i = 0; i < numberOfNewTestInputs; i++) { final int[] currentCombination = suspiciousCombinationsRanking.get(i).getArray(); final IntArrayWrapper newTestInput = computeNewTestInputFor(currentCombination, componentSuspiciousnessMap); if (newTestInput != null) { newTestInputs.add(newTestInput); } else { endInNextIteration = true; } } return newTestInputs; } private Object2DoubleMap computeComponentSuspiciousness(Set relevantSuspiciousCombinations) { final Object2IntMap numberOfFailedTestAppearances = new Object2IntOpenHashMap<>(); final Object2IntMap numberOfTestAppearances = new Object2IntOpenHashMap<>(); final Object2IntMap numberOfCombinationsAppearances = new Object2IntOpenHashMap<>(); final int numberOfSuspiciousCombinations = relevantSuspiciousCombinations.size(); int numberOfFailedTestInputs = 0; for (Map.Entry entry : testResults.entrySet()) { final int[] currentCombination = entry.getKey().getArray(); for (int parameter = 0; parameter < currentCombination.length; parameter++) { final Component component = new Component(parameter, currentCombination[parameter]); numberOfTestAppearances.put(component, numberOfTestAppearances.getOrDefault(component, 0) + 1); if (entry.getValue().isUnsuccessful()) { numberOfFailedTestAppearances.put(component, numberOfFailedTestAppearances.getOrDefault(component, 0) + 1); } } if (entry.getValue().isUnsuccessful()) { numberOfFailedTestInputs++; } } for (IntArrayWrapper suspiciousCombination : relevantSuspiciousCombinations) { final int[] suspiciousCombinationArray = suspiciousCombination.getArray(); for (int parameter = 0; parameter < suspiciousCombinationArray.length; parameter++) { final Component component = new Component(parameter, suspiciousCombinationArray[parameter]); numberOfCombinationsAppearances.put(component, numberOfCombinationsAppearances.getOrDefault(component, 0) + 1); } } final Object2DoubleMap componentSuspiciousnessMap = new Object2DoubleOpenHashMap<>(); for (int parameter = 0; parameter < getModel().getNumberOfParameters(); parameter++) { for (int value = 0; value < getModel().getSizeOfParameter(parameter); value++) { final Component component = new Component(parameter, value); final int failedTestAppearances = numberOfFailedTestAppearances.getOrDefault(component, 0); final int testAppearances = numberOfTestAppearances.getOrDefault(component, 0); final int combinationAppearances = numberOfCombinationsAppearances.getOrDefault(component, 0); final double suspiciousness = (zeroSafeDivision(failedTestAppearances, numberOfFailedTestInputs) + failedTestAppearances / (double) testAppearances + combinationAppearances / (double) numberOfSuspiciousCombinations) / 3.0; componentSuspiciousnessMap.put(component, suspiciousness); } } return componentSuspiciousnessMap; } private double zeroSafeDivision(double value, double divisor) { return divisor == 0 ? 0 : value / divisor; } private List computeSuspiciousCombinationsRanking(Object2DoubleMap componentSuspiciousnessMap, Set relevantSuspiciousCombinations) { final List suspiciousnessOfCombinationRanking = computeSuspiciousnessOfCombinationRanking(componentSuspiciousnessMap, relevantSuspiciousCombinations); final List suspiciousnessOfEnvironmentRanking = computeSuspiciousnessOfEnvironmentRanking(componentSuspiciousnessMap, relevantSuspiciousCombinations); return relevantSuspiciousCombinations.stream().sorted(Comparator.comparingInt(combination -> suspiciousnessOfCombinationRanking.indexOf(combination) + suspiciousnessOfEnvironmentRanking.indexOf(combination))).collect(Collectors.toList()); } private List computeSuspiciousnessOfCombinationRanking(Object2DoubleMap componentSuspiciousnessMap, Set relevantSuspiciousCombinations) { final Object2DoubleMap suspiciousnessOfCombinations = new Object2DoubleOpenHashMap<>(); for (IntArrayWrapper combination : relevantSuspiciousCombinations) { final int[] combinationArray = combination.getArray(); double sum = 0; double numberOfParameters = 0; for (int parameter = 0; parameter < combinationArray.length; parameter++) { if (combinationArray[parameter] != NO_VALUE) { final Component component = new Component(parameter, combinationArray[parameter]); sum += componentSuspiciousnessMap.getDouble(component); numberOfParameters++; } } suspiciousnessOfCombinations.put(combination, zeroSafeDivision(sum, numberOfParameters)); } List suspiciousnessOfCombinationRanking = new ArrayList<>(relevantSuspiciousCombinations); suspiciousnessOfCombinationRanking.sort(Comparator.comparing(suspiciousnessOfCombinations::getDouble).reversed()); return suspiciousnessOfCombinationRanking; } private List computeSuspiciousnessOfEnvironmentRanking(Object2DoubleMap componentSuspiciousnessMap, Set relevantSuspiciousCombinations) { final Object2DoubleMap suspiciousnessOfEnvironment = new Object2DoubleOpenHashMap<>(); for (IntArrayWrapper combination : relevantSuspiciousCombinations) { final double minimumAverage = computeMinimumAverage(combination.getArray(), componentSuspiciousnessMap); suspiciousnessOfEnvironment.put(combination, minimumAverage); } List suspiciousnessOfEnvironmentRanking = new ArrayList<>(relevantSuspiciousCombinations); suspiciousnessOfEnvironmentRanking.sort(Comparator.comparing(suspiciousnessOfEnvironment::getDouble)); return suspiciousnessOfEnvironmentRanking; } private double computeMinimumAverage(int[] combinationArray, Object2DoubleMap componentSuspiciousnessMap) { double minimumAverage = Double.MAX_VALUE; for (IntArrayWrapper testInput : testResults.keySet()) { final int[] testInputArray = testInput.getArray(); if (CombinationUtil.contains(testInputArray, combinationArray)) { double sum = 0; int numberOfParameters = 0; for (int parameter = 0; parameter < testInputArray.length; parameter++) { if (combinationArray[parameter] == NO_VALUE) { final Component component = new Component(parameter, testInputArray[parameter]); sum += componentSuspiciousnessMap.getDouble(component); numberOfParameters++; } } final double average = zeroSafeDivision(sum, numberOfParameters); if (average < minimumAverage) { minimumAverage = average; } } } return minimumAverage; } private IntArrayWrapper computeNewTestInputFor(int[] subCombination, Object2DoubleMap componentSuspiciousnessMap) { final IntList[] parameterValueRanking = computeParameterValueRanking(componentSuspiciousnessMap); final IntList environmentParameters = computeEnvironmentParameters(subCombination); final int[] newTestInputArray = computeLowestEnvironmentSuspicionTestInput(subCombination, parameterValueRanking); final IntArrayWrapper newTestInput = wrap(newTestInputArray); final Random random = new Random(); for (int i = 0; i < maxGenerationAttempts; i++) { final int changingParameter = environmentParameters.getInt(random.nextInt(environmentParameters.size())); final IntList valueRanking = parameterValueRanking[changingParameter]; final int currentValue = newTestInputArray[changingParameter]; final int nextValueIndex = (valueRanking.indexOf(currentValue) + 1) % valueRanking.size(); final int nextValue = valueRanking.getInt(nextValueIndex); newTestInputArray[changingParameter] = nextValue; if (!testResults.containsKey(newTestInput) && getChecker().isValid(newTestInputArray)) { break; } } return testResults.containsKey(newTestInput) || !getChecker().isValid(newTestInputArray) ? null : newTestInput; } private IntList[] computeParameterValueRanking(Object2DoubleMap componentSuspiciousnessMap) { final int numberOfParameter = getModel().getNumberOfParameters(); final IntList[] parameterValueRanking = new IntList[numberOfParameter]; for (int parameter = 0; parameter < numberOfParameter; parameter++) { final int finalParameter = parameter; parameterValueRanking[parameter] = new IntArrayList(IntStream.range(0, getModel().getSizeOfParameter(parameter)).boxed().sorted(Comparator.comparingDouble(value -> componentSuspiciousnessMap.getDouble(new Component(finalParameter, value)))).mapToInt(Integer::intValue).toArray()); } return parameterValueRanking; } private IntList computeEnvironmentParameters(int[] subCombination) { final IntList environmentParameters = new IntArrayList(); for (int parameter = 0; parameter < subCombination.length; parameter++) { if (subCombination[parameter] == NO_VALUE) { environmentParameters.add(parameter); } } return environmentParameters; } private int[] computeLowestEnvironmentSuspicionTestInput(int[] subCombination, IntList[] parameterValueRanking) { int[] testInput = Arrays.copyOf(subCombination, subCombination.length); for (int parameter = 0; parameter < subCombination.length; parameter++) { if (testInput[parameter] == NO_VALUE) { testInput[parameter] = parameterValueRanking[parameter].getInt(0); } } return testInput; } @Override public List computeFailureInducingCombinations() { final List> suspiciousCombinationsByStrength = new LinkedList<>(); suspiciousCombinationsByStrength.add(suspiciousCombinations); for (int i = getModel().getStrength() - 1; i > 0; i--) { suspiciousCombinationsByStrength.add(0, SuspiciousCombinationReducer.reduce(getModel().getParameterSizes(), suspiciousCombinationsByStrength.get(0))); } return suspiciousCombinationsByStrength.stream().filter(not(Collection::isEmpty)).map(this::computeSuspiciousCombinationsRanking).flatMap(List::stream).map(IntArrayWrapper::getArray).collect(Collectors.toList()); } private List computeSuspiciousCombinationsRanking(Set suspiciousCombinations) { return computeSuspiciousCombinationsRanking(computeComponentSuspiciousness(suspiciousCombinations), suspiciousCombinations); } private static final class Component { private final int parameter; private final int value; private Component(int parameter, int value) { this.parameter = parameter; this.value = value; } @Override public int hashCode() { int result = parameter; result = 31 * result + value; return result; } @Override public boolean equals(Object object) { if (object == null || getClass() != object.getClass()) { return false; } Component other = (Component) object; return parameter == other.parameter && value == other.value; } @Override public String toString() { return "Component{" + "parameter=" + parameter + ", value=" + value + '}'; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy