![JAR search and dependency download from the Maven repository](/logo.png)
org.evosuite.testsuite.localsearch.TestSuiteLocalSearch Maven / Gradle / Ivy
The newest version!
/**
* Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
* contributors
*
* This file is part of EvoSuite.
*
* EvoSuite is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* EvoSuite is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EvoSuite. If not, see .
*/
package org.evosuite.testsuite.localsearch;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.evosuite.Properties;
import org.evosuite.ga.Chromosome;
import org.evosuite.ga.FitnessFunction;
import org.evosuite.ga.localsearch.LocalSearch;
import org.evosuite.ga.localsearch.LocalSearchBudget;
import org.evosuite.ga.localsearch.LocalSearchObjective;
import org.evosuite.testcase.TestCase;
import org.evosuite.testcase.TestCaseExpander;
import org.evosuite.testcase.TestChromosome;
import org.evosuite.testcase.execution.ExecutionResult;
import org.evosuite.testcase.execution.TestCaseExecutor;
import org.evosuite.testcase.localsearch.AVMTestCaseLocalSearch;
import org.evosuite.testcase.localsearch.BranchCoverageMap;
import org.evosuite.testcase.localsearch.DSETestCaseLocalSearch;
import org.evosuite.testcase.localsearch.TestCaseLocalSearch;
import org.evosuite.testsuite.TestSuiteChromosome;
import org.evosuite.testsuite.TestSuiteFitnessFunction;
import org.evosuite.utils.Randomness;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* . This class applies local search on a test suite. Depending on the values
* for properties DSE_PROBABILITY
and LOCAL_SEARCH_DSE
* one of the following three modes is applied:
*
* - apply DSE on all test cases
*
* - apply AVM on all test cases
*
* - apply DSE on some tests and AVM on other tests
*
* @author galeotti
*
*/
public class TestSuiteLocalSearch implements LocalSearch {
private static final Logger logger = LoggerFactory.getLogger(TestSuiteLocalSearch.class);
/**
* Updates the given list of fitness functions using for the individual
* passed as a parameter
*
* @param individual
* an individual
*
* @param fitnessFunctions
* the list of fitness functions to be updated
*/
private void updateFitness(TestSuiteChromosome individual,
List> fitnessFunctions) {
for (FitnessFunction extends Chromosome> ff : fitnessFunctions) {
((TestSuiteFitnessFunction) ff).getFitness(individual);
}
}
/**
* Decides the kind of local search that will be applied to the Test Suite.
*
* @return a TestSuiteLocalSearch
instance to use for local
* search
*/
public static TestSuiteLocalSearch selectTestSuiteLocalSearch() {
return new TestSuiteLocalSearch();
}
/**
* Before applying DSE we expand test cases, such that each primitive value
* is used at only exactly one position as a parameter
*
* For example, given the following test case:
*
*
* foo0.bar(1);
* foo1.bar(1);
*
*
* is rewritten as:
*
*
* int int0 = 1;
* int int1 = 1;
* foo0.bar(int0);
* foo1.bar(int1);
*
*
* @param suite
* @return
*/
private static void expandTestSuite(TestSuiteChromosome suite,
LocalSearchObjective objective) {
logger.debug("Expanding tests for local search");
TestSuiteChromosome newTestSuite = new TestSuiteChromosome();
for (TestChromosome test : suite.getTestChromosomes()) {
// First make sure we are up to date with the execution
if (test.getLastExecutionResult() == null || test.isChanged()) {
test.setLastExecutionResult(TestCaseExecutor.runTest(test.getTestCase()));
test.setChanged(false);
}
// We skip tests that have problems
if (test.getLastExecutionResult().hasTimeout() || test.getLastExecutionResult().hasTestException()) {
logger.info("Skipping test with timeout or exception");
continue;
}
// If local search has already been applied on the original test
// then we also set that flag on the expanded test
boolean hasLocalSearchBeenApplied = test.hasLocalSearchBeenApplied();
TestCase newTest = test.getTestCase().clone();
TestCase expandedTest = expandTestCase(newTest);
TestChromosome expandedTestChromosome = newTestSuite.addTest(expandedTest);
expandedTestChromosome.setLocalSearchApplied(hasLocalSearchBeenApplied);
}
List oldTests = suite.getTestChromosomes();
oldTests.clear();
oldTests.addAll(newTestSuite.getTestChromosomes());
suite.setChanged(true);
for (FitnessFunction extends Chromosome> ff : objective.getFitnessFunctions()) {
((TestSuiteFitnessFunction) ff).getFitness(suite);
}
}
/**
* Returns a new test case by explicitly declaring a variable for each used
* primitive value. Repeated values are declared as different variables. For
* example, given the following test case:
*
*
* foo0.bar(1);
* foo1.bar(1);
*
*
* is rewritten as:
*
*
* int int0 = 1;
* int int1 = 1;
* foo0.bar(int0);
* foo1.bar(int1);
*
*
* @param test
* the test to expand
* @return the expanded test case
*/
private static TestCase expandTestCase(TestCase test) {
if (!Properties.LOCAL_SEARCH_EXPAND_TESTS)
return test;
TestCaseExpander expander = new TestCaseExpander();
return expander.expandTestCase(test);
}
/**
* Ensure that all branches are executed twice For each branch such that
* exists only one test case in the suite that covers that branch, it
* creates a duplicate of that test case.
*
* By doing this, we avoid to incorrectly mark a new test case produced by
* the local search as an improving test case because it simply executes
* again a predicate.
*/
protected static void ensureDoubleExecution(TestSuiteChromosome individual,
LocalSearchObjective objective) {
logger.debug("Ensuring double execution");
Set duplicates = new HashSet();
TestSuiteFitnessFunction defaultFitness = (TestSuiteFitnessFunction) objective.getFitnessFunctions().get(0);
Map covered = new HashMap();
Map testMap = new HashMap();
for (TestChromosome test : individual.getTestChromosomes()) {
// Make sure we have an execution result
if (test.getLastExecutionResult() == null || test.isChanged()) {
ExecutionResult result = test.executeForFitnessFunction(defaultFitness);
test.setLastExecutionResult(result); // .clone();
test.setChanged(false);
}
for (Entry entry : test.getLastExecutionResult().getTrace().getPredicateExecutionCount()
.entrySet()) {
if (!covered.containsKey(entry.getKey())) {
covered.put(entry.getKey(), 0);
}
covered.put(entry.getKey(), covered.get(entry.getKey()) + entry.getValue());
testMap.put(entry.getKey(), test);
}
}
for (Entry entry : covered.entrySet()) {
int branchId = entry.getKey();
int count = entry.getValue();
if (count == 1) {
TestChromosome duplicate = (TestChromosome) testMap.get(branchId).clone();
ExecutionResult result = duplicate.executeForFitnessFunction(defaultFitness);
duplicate.setLastExecutionResult(result); // .clone();
duplicate.setChanged(false);
duplicates.add(duplicate);
}
}
if (!duplicates.isEmpty()) {
logger.info("Adding " + duplicates.size() + " tests to cover branches sufficiently");
for (TestChromosome test : duplicates) {
individual.addTest(test);
}
individual.setChanged(true);
for (FitnessFunction extends Chromosome> ff : objective.getFitnessFunctions()) {
((TestSuiteFitnessFunction) ff).getFitness(individual);
}
}
}
/**
* Returns the set of predicate indexes whose true branches were covered by
* the suite
*
* @param suite
* @return
*/
private static Set getCoveredTrueBranches(TestSuiteChromosome suite) {
Set covered = new LinkedHashSet();
for (TestChromosome testChromosome : suite.getTestChromosomes()) {
ExecutionResult lastResult = testChromosome.getLastExecutionResult();
if (lastResult != null) {
covered.addAll(lastResult.getTrace().getCoveredTrueBranches());
}
}
return covered;
}
/**
* Returns the set of the predicate indexes whose false branch were covered
* by the test suite
*
* @param suite
* @return the set of predicate indexes whose false branch were covered
*/
private static Set getCoveredFalseBranches(TestSuiteChromosome suite) {
Set covered = new LinkedHashSet();
for (TestChromosome testChromosome : suite.getTestChromosomes()) {
ExecutionResult lastResult = testChromosome.getLastExecutionResult();
if (lastResult != null) {
covered.addAll(lastResult.getTrace().getCoveredFalseBranches());
}
}
return covered;
}
/**
* Ensure that all branches are executed twice
*/
private void restoreBranchCoverage(TestSuiteChromosome individual, TestSuiteFitnessFunction objective) {
logger.debug("Adding branches already covered previously");
BranchCoverageMap branchMap = BranchCoverageMap.getInstance();
Set uncoveredTrueBranches = new LinkedHashSet(branchMap.getCoveredTrueBranches());
Set uncoveredFalseBranches = new LinkedHashSet(branchMap.getCoveredFalseBranches());
uncoveredTrueBranches.removeAll(getCoveredTrueBranches(individual));
uncoveredFalseBranches.removeAll(getCoveredFalseBranches(individual));
for (Integer branchId : uncoveredTrueBranches) {
individual.addTest(branchMap.getTestCoveringTrue(branchId).clone());
}
for (Integer branchId : uncoveredFalseBranches) {
individual.addTest(branchMap.getTestCoveringFalse(branchId).clone());
}
}
/**
* Indicates if the fitness of the individual has improved with respected to
* parameter fitnessBefore
*
* @param fitnessBefore
* the previous fitness of the individual
* @param individual
* the individual
* @param objective
* the local search objective
* @return true if fitness improved, false otherwise
*/
private boolean hasImproved(double fitnessBefore, TestSuiteChromosome individual,
LocalSearchObjective objective) {
return objective.isMaximizationObjective() ? fitnessBefore < individual.getFitness()
: fitnessBefore > individual.getFitness();
}
/**
* Applies local search to the suite targeting the objective passed as
* parameter. The type of local search will be decided according to the
* DSE_PROBABITY
and LOCAL_SEARCH_DSE
properties.
*
* @param suite
* the test suite to apply local search on
*
* @param objective
* the local search objective
*
* @return true iff the test suite has improved.
*/
@Override
public boolean doSearch(TestSuiteChromosome suite, LocalSearchObjective objective) {
updateFitness(suite, objective.getFitnessFunctions());
double fitnessBefore = suite.getFitness();
// logger.info("Test suite before local search: " + individual);
List originalTests = new ArrayList(suite.getTestChromosomes());
List tests = suite.getTestChromosomes();
/*
* When we apply local search, due to budget constraints we might not be
* able to evaluate all the test cases in a test suite. When we apply LS
* several times on same individual in different generations, to avoid
* having always the same test cases searched for and others skipped,
* then we shuffle the test cases, so each time the order is different
*/
Randomness.shuffle(tests);
/*
* We duplicate each test case that executes a predicate only once to
* ensure that when measuring the coverage of the new test case produced
* by DSE or AVM the fitness improvement is due to an actual
* improvement, not because the new test case is executing a predicate
* that was only executed once in the original test suite.
*/
if (Properties.LOCAL_SEARCH_ENSURE_DOUBLE_EXECUTION) {
ensureDoubleExecution(suite, objective);
}
if (Properties.LOCAL_SEARCH_RESTORE_COVERAGE) {
restoreBranchCoverage(suite, (TestSuiteFitnessFunction) objective.getFitnessFunctions().get(0));
}
if (Properties.LOCAL_SEARCH_EXPAND_TESTS) {
expandTestSuite(suite, objective);
}
applyLocalSearch(suite, objective);
LocalSearchBudget.getInstance().countLocalSearchOnTestSuite();
/*
* Fitness value may actually get worse if we are dealing with static
* state. As long as EvoSuite can't handle this, we cannot check this
* assertion.
*/
/*
* assert (objective.getFitnessFunction().isMaximizationFunction() ?
* fitnessBefore <= individual.getFitness() : fitnessBefore >=
* individual.getFitness()) : "Fitness was " + fitnessBefore +
* " and now is " + individual.getFitness();
*/
// Return true if fitness has improved
boolean hasImproved = hasImproved(fitnessBefore, suite, objective);
if (!hasImproved) {
// restore original tests
suite.clearTests();
suite.addTests(originalTests);
}
return hasImproved;
}
/**
* This enumerate represents which type of local search will be applied on
* the suite
*
* @author galeotti
*/
enum LocalSearchSuiteType {
/**
* Always apply DSE on all test cases in the suite
*/
ALWAYS_DSE,
/**
* Always apply AVM on all test cases in the suite
*/
ALWAYS_AVM,
/**
* Apply AVM/DSE on a test case according to the
* DSE_PROBABILITY
*/
DSE_AND_AVM
}
/**
* Selects the type of local search according to the
* LOCAL_SEARCH_DSE
and the DSE_PROBABILITY
* properties.
*
* @return the type of Local Search to be applied
*/
private static LocalSearchSuiteType chooseLocalSearchSuiteType() {
final LocalSearchSuiteType localSearchType;
if (Properties.DSE_PROBABILITY <= 0.0) {
localSearchType = LocalSearchSuiteType.ALWAYS_AVM;
} else if (Properties.LOCAL_SEARCH_DSE == Properties.DSEType.SUITE) {
if (Randomness.nextDouble() <= Properties.DSE_PROBABILITY) {
localSearchType = LocalSearchSuiteType.ALWAYS_DSE;
} else {
localSearchType = LocalSearchSuiteType.ALWAYS_AVM;
}
} else {
assert (Properties.LOCAL_SEARCH_DSE == Properties.DSEType.TEST);
localSearchType = LocalSearchSuiteType.DSE_AND_AVM;
}
return localSearchType;
}
/**
* Decides the type of local search to be applied, and invokes the
* corresponding local search procedure.
*
* @param suite
* the suite to optimise
* @param objective
* the local search objective
*/
private void applyLocalSearch(TestSuiteChromosome suite, LocalSearchObjective objective) {
final LocalSearchSuiteType localSearchType;
localSearchType = chooseLocalSearchSuiteType();
/*
* We make a copy of the original test cases before Local Search
*/
List originalTests = new ArrayList(suite.getTestChromosomes());
for (final TestChromosome test : originalTests) {
// If we have already tried local search before on this test
// without success, we reset all primitive values before trying
// again
if (test.hasLocalSearchBeenApplied()) {
TestCaseLocalSearch.randomizePrimitives(test.getTestCase());
updateFitness(suite, objective.getFitnessFunctions());
}
if (LocalSearchBudget.getInstance().isFinished()) {
logger.debug("Local search budget used up: " + Properties.LOCAL_SEARCH_BUDGET_TYPE);
break;
}
logger.debug("Local search budget not yet used up");
final double tossCoin = Randomness.nextDouble();
final boolean shouldApplyDSE = localSearchType == LocalSearchSuiteType.ALWAYS_DSE
|| (localSearchType == LocalSearchSuiteType.DSE_AND_AVM && tossCoin <= Properties.DSE_PROBABILITY);
/*
* We create a cloned test case to play local search with it. This
* resembles the deprecated ensureDoubleExecution
*/
TestChromosome clonedTest = (TestChromosome) test.clone();
suite.addTest(clonedTest);
final int lastIndex = suite.size() - 1;
final boolean improved;
if (shouldApplyDSE) {
improved = applyDSE(suite, lastIndex, clonedTest, objective);
} else {
improved = applyAVM(suite, lastIndex, clonedTest, objective);
}
if (improved) {
updateFitness(suite, objective.getFitnessFunctions());
} else {
// remove cloned test case if there was no improvement
suite.deleteTest(clonedTest);
}
test.setLocalSearchApplied(true);
}
}
/**
* Applies AVM on the test case in the suite
*
* @param suite
* @param testIndex
* @param test
* @param localSearchObjective
* @return
*/
private boolean applyAVM(TestSuiteChromosome suite, int testIndex, TestChromosome test,
LocalSearchObjective objective) {
logger.debug("Local search on test " + testIndex + ", current fitness: " + suite.getFitness());
final List> fitnessFunctions = objective.getFitnessFunctions();
TestSuiteLocalSearchObjective testCaseLocalSearchObjective = TestSuiteLocalSearchObjective
.buildNewTestSuiteLocalSearchObjective(fitnessFunctions, suite, testIndex);
AVMTestCaseLocalSearch testCaselocalSearch = new AVMTestCaseLocalSearch();
boolean improved = testCaselocalSearch.doSearch(test, testCaseLocalSearchObjective);
return improved;
}
/**
* Applies DSE on the test case of the suite
*
* @param suite
* @param testIndex
* @param test
* @param objective
* @return
*/
private boolean applyDSE(TestSuiteChromosome suite, int testIndex, TestChromosome test,
LocalSearchObjective objective) {
TestSuiteLocalSearchObjective testSuiteObject = TestSuiteLocalSearchObjective
.buildNewTestSuiteLocalSearchObjective(objective.getFitnessFunctions(), suite, testIndex);
DSETestCaseLocalSearch dseTestCaseLocalSearch = new DSETestCaseLocalSearch(suite);
boolean improved = dseTestCaseLocalSearch.doSearch(test, testSuiteObject);
return improved;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy