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

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