org.evosuite.testcase.localsearch.DSETestGenerator 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.testcase.localsearch;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.evosuite.Properties;
import org.evosuite.ga.localsearch.LocalSearchBudget;
import org.evosuite.ga.localsearch.LocalSearchObjective;
import org.evosuite.symbolic.BranchCondition;
import org.evosuite.symbolic.ConcolicExecution;
import org.evosuite.symbolic.DSEStats;
import org.evosuite.symbolic.PathCondition;
import org.evosuite.symbolic.expr.Constraint;
import org.evosuite.symbolic.expr.Expression;
import org.evosuite.symbolic.expr.Variable;
import org.evosuite.symbolic.solver.SolverCache;
import org.evosuite.symbolic.solver.Solver;
import org.evosuite.symbolic.solver.SolverFactory;
import org.evosuite.symbolic.solver.SolverResult;
import org.evosuite.testcase.DefaultTestCase;
import org.evosuite.testcase.execution.ExecutionResult;
import org.evosuite.testcase.statements.Statement;
import org.evosuite.testcase.TestCase;
import org.evosuite.testcase.TestChromosome;
import org.evosuite.testcase.variable.VariableReference;
import org.evosuite.testsuite.TestSuiteChromosome;
import org.evosuite.testcase.statements.PrimitiveStatement;
import org.evosuite.utils.Randomness;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Attempts to create a new test case by applying DSE. The algorithm
* systematically negates all uncovered branches trying to satisfy the missing
* branches.
*
* @author galeotti
*
*/
public class DSETestGenerator {
private final TestSuiteChromosome suite;
/**
* Creates a new test generator with no suite. Only the test case will be
* used
*/
public DSETestGenerator() {
this(null);
}
/**
* Creates a new test generator using a test suite. The test case will be
* added to the test suite.
*
* @param suite
*/
public DSETestGenerator(TestSuiteChromosome suite) {
this.suite = suite;
}
private static final Logger logger = LoggerFactory.getLogger(DSETestGenerator.class);
/**
* Applies DSE to the passed test using as symbolic variables only those
* that are declared in the set of statement indexes. The objective is used
* to detect if the DSE has improved the fitness.
*
* @param test
* the test case to be used as parameterised unit test
*
* @param statementIndexes
* a set with statement indexes with primitive value declarations
* that can be used as symbolic variables. This set must be
* non-empty.
*
* @param objective
* the local search objective to measure fitness improvement.
*/
public TestChromosome generateNewTest(final TestChromosome test, Set statementIndexes,
LocalSearchObjective objective) {
logger.info("APPLYING DSE EEEEEEEEEEEEEEEEEEEEEEE");
logger.info(test.getTestCase().toCode());
logger.info("Starting concolic execution");
// Backup copy
// test.getMutationHistory().clear();
test.clone(); // I am not sure what is the purpose of this
DefaultTestCase clone_test_case = (DefaultTestCase) test.getTestCase().clone();
List branchConditions = ConcolicExecution.executeConcolic(clone_test_case);
final PathCondition collectedPathCondition = new PathCondition(branchConditions);
logger.info("Done concolic execution");
if (collectedPathCondition.isEmpty()) {
return null;
}
for (BranchCondition c : collectedPathCondition.getBranchConditions()) {
logger.info(" -> " + c.getConstraint());
}
Set symbolicVariables = new HashSet();
for (Integer position : statementIndexes) {
final VariableReference variableReference = test.getTestCase().getStatement(position).getReturnValue();
symbolicVariables.add(variableReference);
}
logger.info("Checking {} conditions", collectedPathCondition.size());
List conditionIndexesNotCoveredTwoWays = computeConditionIndexesNotCoveredTwoWays(test,
collectedPathCondition);
//
for (int conditionIndex = 0; conditionIndex < collectedPathCondition.size(); conditionIndex++) {
BranchCondition condition = collectedPathCondition.get(conditionIndex);
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");
if (!conditionIndexesNotCoveredTwoWays.contains(conditionIndex)) {
// skip branches covered two ways
continue;
}
logger.info("Current condition: " + conditionIndex + "/" + collectedPathCondition.size() + ": "
+ condition.getConstraint());
// Determine if this a branch condition depending on the target
// statement
Constraint> currentConstraint = condition.getConstraint();
if (!isRelevant(currentConstraint, symbolicVariables)) {
// if(!isRelevant(currentConstraint, test.getTestCase(),
// statement)) {
logger.info("Is not relevant for " + symbolicVariables);
continue;
}
logger.info("Is relevant for " + symbolicVariables);
List> query = buildQuery(collectedPathCondition, conditionIndex);
logger.info("Trying to solve: ");
for (Constraint> c : query) {
logger.info(" " + c);
}
DSEStats.getInstance().reportNewConstraints(query);
// Get solution
Solver solver = SolverFactory.getInstance().buildNewSolver();
long startSolvingTime = System.currentTimeMillis();
SolverCache solverCache = SolverCache.getInstance();
SolverResult solverResult = solverCache.solve(solver, query);
long estimatedSolvingTime = System.currentTimeMillis() - startSolvingTime;
DSEStats.getInstance().reportNewSolvingTime(estimatedSolvingTime);
if (solverResult == null) {
logger.info("Found no result");
} else if (solverResult.isUNSAT()) {
logger.info("Found UNSAT result");
DSEStats.getInstance().reportNewUNSAT();
} else {
logger.info("Found SAT result");
DSEStats.getInstance().reportNewSAT();
Map model = solverResult.getModel();
TestCase oldTest = test.getTestCase();
ExecutionResult oldResult = test.getLastExecutionResult().clone();
TestCase newTest = updateTest(oldTest, model);
logger.info("New test: " + newTest.toCode());
test.setTestCase(newTest);
// test.clearCachedMutationResults(); // TODO Mutation
test.clearCachedResults();
if (objective.hasImproved(test)) {
DSEStats.getInstance().reportNewTestUseful();
logger.info("Solution improves fitness, finishing DSE");
/* new test was created */
return test;
} else {
DSEStats.getInstance().reportNewTestUnuseful();
test.setTestCase(oldTest);
// FIXXME: How can this be null?
if (oldResult != null)
test.setLastExecutionResult(oldResult);
// TODO Mutation
}
}
}
/* no new test was created */
return null;
}
/**
* Compute the set of branch conditions in the path condition that are not
* covered two ways. If the test case belongs to a whole test suite, the
* coverage of the whole test suite is used, otherwise, only the coverage of
* the single test case.
*
* @param test
* the original test case
* @param collectedPathCondition
* a path condition obtained from concolic execution
* @return
*/
private List computeConditionIndexesNotCoveredTwoWays(final TestChromosome test,
final PathCondition collectedPathCondition) {
List conditionIndexesNotCoveredTwoWays = new LinkedList();
for (int conditionIndex = 0; conditionIndex < collectedPathCondition.size(); conditionIndex++) {
BranchCondition b = collectedPathCondition.get(conditionIndex);
if (!isCoveredTwoWays(test, b.getBranchIndex())) {
conditionIndexesNotCoveredTwoWays.add(conditionIndex);
}
}
return conditionIndexesNotCoveredTwoWays;
}
/**
* Returns if the true and false branches for this were already covered. If
* the test case belongs to a whole test suite, then the coverage of the
* test suite is used, otherwise the single test case is used.
*
* @param className
* @param methodName
* @param branchIndex
* @return
*/
private boolean isCoveredTwoWays(TestChromosome test, int branchIndex) {
Set trueIndexes = new HashSet();
Set falseIndexes = new HashSet();
if (suite != null) {
for (ExecutionResult execResult : this.suite.getLastExecutionResults()) {
Set trueIndexesInTrace = execResult.getTrace().getCoveredTrueBranches();
Set falseIndexesInTrace = execResult.getTrace().getCoveredFalseBranches();
trueIndexes.addAll(trueIndexesInTrace);
falseIndexes.addAll(falseIndexesInTrace);
}
} else {
ExecutionResult execResult = test.getLastExecutionResult();
Set trueIndexesInTest = execResult.getTrace().getCoveredTrueBranches();
Set falseIndexesInTest = execResult.getTrace().getCoveredFalseBranches();
trueIndexes.addAll(trueIndexesInTest);
falseIndexes.addAll(falseIndexesInTest);
}
final boolean trueIsCovered = trueIndexes.contains(branchIndex);
final boolean falseIsCovered = falseIndexes.contains(branchIndex);
return trueIsCovered && falseIsCovered;
}
/**
* Creates a Solver query give a branch condition
*
* @param condition
* @return
*/
private List> buildQuery(PathCondition pc, int conditionIndex) {
// negate target branch condition
PathCondition negatedPathCondition = pc.negate(conditionIndex);
// get constraints for negated path condition
List> query = negatedPathCondition.getConstraints();
// Compute cone of influence reduction
List> simplified_query = reduce(query);
return simplified_query;
}
/**
* Returns true iff the constraint has at least one variable that is
*
* @param constraint
* @param targets
* @return
*/
private boolean isRelevant(Constraint> constraint, Set targets) {
Set> variables = constraint.getVariables();
Set targetNames = new HashSet();
for (VariableReference v : targets) {
targetNames.add(v.getName());
}
for (Variable> var : variables) {
if (targetNames.contains(var.getName()))
return true;
}
return false;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private TestCase updateTest(TestCase test, Map values) {
TestCase newTest = test.clone();
for (Object key : values.keySet()) {
Object val = values.get(key);
if (val != null) {
logger.info("New value: " + key + ": " + val);
if (val instanceof Long) {
Long value = (Long) val;
String name = ((String) key).replace("__SYM", "");
// logger.warn("New long value for " + name + " is " +
// value);
PrimitiveStatement p = getStatement(newTest, name);
if (p.getValue().getClass().equals(Character.class))
p.setValue((char) value.intValue());
else if (p.getValue().getClass().equals(Long.class))
p.setValue(value);
else if (p.getValue().getClass().equals(Integer.class))
p.setValue(value.intValue());
else if (p.getValue().getClass().equals(Short.class))
p.setValue(value.shortValue());
else if (p.getValue().getClass().equals(Boolean.class))
p.setValue(value.intValue() > 0);
else if (p.getValue().getClass().equals(Byte.class))
p.setValue(value.byteValue() > 0);
else
logger.warn("New value is of an unsupported type: " + p.getValue().getClass() + val);
} else if (val instanceof String) {
String name = ((String) key).replace("__SYM", "");
PrimitiveStatement p = getStatement(newTest, name);
// logger.warn("New string value for " + name + " is " +
// val);
assert (p != null) : "Could not find variable " + name + " in test: " + newTest.toCode()
+ " / Orig test: " + test.toCode() + ", seed: " + Randomness.getSeed();
if (p.getValue().getClass().equals(Character.class))
p.setValue((char) Integer.parseInt(val.toString()));
else
p.setValue(val.toString());
} else if (val instanceof Double) {
Double value = (Double) val;
String name = ((String) key).replace("__SYM", "");
PrimitiveStatement p = getStatement(newTest, name);
// logger.warn("New double value for " + name + " is " +
// value);
assert (p != null) : "Could not find variable " + name + " in test: " + newTest.toCode()
+ " / Orig test: " + test.toCode() + ", seed: " + Randomness.getSeed();
if (p.getValue().getClass().equals(Double.class))
p.setValue(value);
else if (p.getValue().getClass().equals(Float.class))
p.setValue(value.floatValue());
else
logger.warn("New value is of an unsupported type: " + val);
} else {
logger.debug("New value is of an unsupported type: " + val);
}
} else {
logger.debug("New value is null");
}
}
return newTest;
}
/**
* Apply cone of influence reduction to constraints with respect to the last
* constraint in the list
*
* @param constraints
* @return
*/
private List> reduce(List> constraints) {
Constraint> target = constraints.get(constraints.size() - 1);
Set> dependencies = getVariables(target);
LinkedList> coi = new LinkedList>();
if (dependencies.size() <= 0)
return coi;
coi.add(target);
for (int i = constraints.size() - 2; i >= 0; i--) {
Constraint> constraint = constraints.get(i);
Set> variables = getVariables(constraint);
for (Variable> var : dependencies) {
if (variables.contains(var)) {
dependencies.addAll(variables);
coi.addFirst(constraint);
break;
}
}
}
return coi;
}
/**
* Get the statement that defines this variable
*
* @param test
* @param name
* @return
*/
private PrimitiveStatement> getStatement(TestCase test, String name) {
for (Statement statement : test) {
if (statement instanceof PrimitiveStatement>) {
if (statement.getReturnValue().getName().equals(name))
return (PrimitiveStatement>) statement;
}
}
return null;
}
/**
* Determine the set of variable referenced by this constraint
*
* @param constraint
* @return
*/
private Set> getVariables(Constraint> constraint) {
Set> variables = new HashSet>();
getVariables(constraint.getLeftOperand(), variables);
getVariables(constraint.getRightOperand(), variables);
return variables;
}
/**
* Recursively determine constraints in expression
*
* @param expr
* a {@link org.evosuite.symbolic.expr.Expression} object.
* @param variables
* a {@link java.util.Set} object.
*/
public static void getVariables(Expression> expr, Set> variables) {
variables.addAll(expr.getVariables());
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy