/** * Copyright (C) 2010-2017 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 <http://www.gnu.org/licenses/>. */ package org.evosuite.coverage.branch; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.evosuite.Properties; import org.evosuite.TestGenerationContext; import org.evosuite.coverage.archive.TestsArchive; import org.evosuite.testcase.ExecutableChromosome; import org.evosuite.testcase.TestFitnessFunction; import org.evosuite.testcase.execution.ExecutionResult; import org.evosuite.testsuite.AbstractTestSuiteChromosome; import org.evosuite.testsuite.TestSuiteFitnessFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Fitness function for a whole test suite for all branches * * @author Gordon Fraser, Jose Miguel Rojas */ public class OnlyBranchCoverageSuiteFitness extends TestSuiteFitnessFunction { private static final long serialVersionUID = 2991632394620406243L; private final static Logger logger = LoggerFactory.getLogger(TestSuiteFitnessFunction.class); // Coverage targets public int totalBranches; public int totalGoals; private final Set<Integer> branchesId; // Some stuff for debug output public int maxCoveredBranches = 0; public double bestFitness = Double.MAX_VALUE; // Each test gets a set of distinct covered goals, these are mapped by branch id private final Map<Integer, TestFitnessFunction> branchCoverageTrueMap = new HashMap<Integer, TestFitnessFunction>(); private final Map<Integer, TestFitnessFunction> branchCoverageFalseMap = new HashMap<Integer, TestFitnessFunction>(); private final Set<Integer> toRemoveBranchesT = new HashSet<>(); private final Set<Integer> toRemoveBranchesF = new HashSet<>(); private final Set<Integer> removedBranchesT = new HashSet<>(); private final Set<Integer> removedBranchesF = new HashSet<>(); /** * <p> * Constructor for BranchCoverageSuiteFitness. * </p> */ public OnlyBranchCoverageSuiteFitness() { String prefix = Properties.TARGET_CLASS_PREFIX; if (prefix.isEmpty()) { prefix = Properties.TARGET_CLASS; totalBranches = BranchPool.getInstance(TestGenerationContext.getInstance().getClassLoaderForSUT()).getBranchCountForPrefix(prefix); } else { totalBranches = BranchPool.getInstance(TestGenerationContext.getInstance().getClassLoaderForSUT()).getBranchCountForPrefix(prefix); } branchesId = new HashSet<>(); totalGoals = 2 * totalBranches; logger.info("Total branch coverage goals: " + totalGoals); logger.info("Total branches: " + totalBranches); determineCoverageGoals(); } /** * Initialize the set of known coverage goals */ private void determineCoverageGoals() { List<OnlyBranchCoverageTestFitness> goals = new OnlyBranchCoverageFactory().getCoverageGoals(); for (OnlyBranchCoverageTestFitness goal : goals) { if(Properties.TEST_ARCHIVE) TestsArchive.instance.addGoalToCover(this, goal); branchesId.add(goal.getBranch().getActualBranchId()); if (goal.getBranchExpressionValue()) branchCoverageTrueMap.put(goal.getBranch().getActualBranchId(), goal); else branchCoverageFalseMap.put(goal.getBranch().getActualBranchId(), goal); } } /** * Iterate over all execution results and summarize statistics * * @param results * @param predicateCount * @param callCount * @param trueDistance * @param falseDistance * @return */ private boolean analyzeTraces( AbstractTestSuiteChromosome<? extends ExecutableChromosome> suite, List<ExecutionResult> results, Map<Integer, Integer> predicateCount, Map<Integer, Double> trueDistance, Map<Integer, Double> falseDistance) { boolean hasTimeoutOrTestException = false; for (ExecutionResult result : results) { if (result.hasTimeout() || result.hasTestException()) { hasTimeoutOrTestException = true; continue; } for (Entry<Integer, Integer> entry : result.getTrace().getPredicateExecutionCount().entrySet()) { if (!branchesId.contains(entry.getKey()) || (removedBranchesT.contains(entry.getKey()) && removedBranchesF.contains(entry.getKey()))) continue; if (!predicateCount.containsKey(entry.getKey())) predicateCount.put(entry.getKey(), entry.getValue()); else { predicateCount.put(entry.getKey(), predicateCount.get(entry.getKey()) + entry.getValue()); } } for (Entry<Integer, Double> entry : result.getTrace().getTrueDistances().entrySet()) { if(!branchesId.contains(entry.getKey())||removedBranchesT.contains(entry.getKey())) continue; if (!trueDistance.containsKey(entry.getKey())) trueDistance.put(entry.getKey(), entry.getValue()); else { trueDistance.put(entry.getKey(), Math.min(trueDistance.get(entry.getKey()), entry.getValue())); } if ((Double.compare(entry.getValue(), 0.0) ==0)) { result.test.addCoveredGoal(branchCoverageTrueMap.get(entry.getKey())); if(Properties.TEST_ARCHIVE) { TestsArchive.instance.putTest(this, branchCoverageTrueMap.get(entry.getKey()), result); toRemoveBranchesT.add(entry.getKey()); suite.isToBeUpdated(true); } } } for (Entry<Integer, Double> entry : result.getTrace().getFalseDistances().entrySet()) { if(!branchesId.contains(entry.getKey())||removedBranchesF.contains(entry.getKey())) continue; if (!falseDistance.containsKey(entry.getKey())) falseDistance.put(entry.getKey(), entry.getValue()); else { falseDistance.put(entry.getKey(), Math.min(falseDistance.get(entry.getKey()), entry.getValue())); } if ((Double.compare(entry.getValue(), 0.0) ==0)) { result.test.addCoveredGoal(branchCoverageFalseMap.get(entry.getKey())); if(Properties.TEST_ARCHIVE) { TestsArchive.instance.putTest(this, branchCoverageFalseMap.get(entry.getKey()), result); toRemoveBranchesF.add(entry.getKey()); suite.isToBeUpdated(true); } } } } return hasTimeoutOrTestException; } @Override public boolean updateCoveredGoals() { if(!Properties.TEST_ARCHIVE) return false; for (Integer branch : toRemoveBranchesT) { TestFitnessFunction f = branchCoverageTrueMap.remove(branch); if (f != null) { removedBranchesT.add(branch); if (removedBranchesF.contains(branch)) { totalBranches--; //if(isFullyCovered(f.getTargetClass(), f.getTargetMethod())) { // removeTestCall(f.getTargetClass(), f.getTargetMethod()); //} } } else { throw new IllegalStateException("goal to remove not found"); } } for (Integer branch : toRemoveBranchesF) { TestFitnessFunction f = branchCoverageFalseMap.remove(branch); if (f != null) { removedBranchesF.add(branch); if (removedBranchesT.contains(branch)) { totalBranches--; //if(isFullyCovered(f.getTargetClass(), f.getTargetMethod())) { // removeTestCall(f.getTargetClass(), f.getTargetMethod()); //} } } else { throw new IllegalStateException("goal to remove not found"); } } toRemoveBranchesF.clear(); toRemoveBranchesT.clear(); logger.info("Current state of archive: "+TestsArchive.instance.toString()); return true; } /** * {@inheritDoc} * * Execute all tests and count covered branches */ @Override public double getFitness( AbstractTestSuiteChromosome<? extends ExecutableChromosome> suite) { logger.trace("Calculating branch fitness"); double fitness = 0.0; List<ExecutionResult> results = runTestSuite(suite); Map<Integer, Double> trueDistance = new HashMap<Integer, Double>(); Map<Integer, Double> falseDistance = new HashMap<Integer, Double>(); Map<Integer, Integer> predicateCount = new HashMap<Integer, Integer>(); // Collect stats in the traces boolean hasTimeoutOrTestException = analyzeTraces(suite, results, predicateCount, trueDistance, falseDistance); // Collect branch distances of covered branches int numCoveredBranches = 0; for (Integer key : predicateCount.keySet()) { double df = 0.0; double dt = 0.0; int numExecuted = predicateCount.get(key); if(removedBranchesT.contains(key)) numExecuted++; if(removedBranchesF.contains(key)) numExecuted++; if (trueDistance.containsKey(key)) { dt = trueDistance.get(key); } if(falseDistance.containsKey(key)){ df = falseDistance.get(key); } // If the branch predicate was only executed once, then add 1 if (numExecuted == 1) { fitness += 1.0; } else { fitness += normalize(df) + normalize(dt); } if (falseDistance.containsKey(key)&&(Double.compare(df, 0.0) == 0)) numCoveredBranches++; if (trueDistance.containsKey(key)&&(Double.compare(dt, 0.0) == 0)) numCoveredBranches++; } // +1 for every branch that was not executed fitness += 2 * (totalBranches - predicateCount.size()); printStatusMessages(suite, numCoveredBranches, fitness); // Calculate coverage int coverage = numCoveredBranches; coverage +=removedBranchesF.size(); coverage +=removedBranchesT.size(); if (totalGoals > 0) suite.setCoverage(this, (double) coverage / (double) totalGoals); else suite.setCoverage(this, 1); suite.setNumOfCoveredGoals(this, coverage); suite.setNumOfNotCoveredGoals(this, totalGoals-coverage); if (hasTimeoutOrTestException) { logger.info("Test suite has timed out, setting fitness to max value " + (totalBranches * 2)); fitness = totalBranches * 2; //suite.setCoverage(0.0); } updateIndividual(this, suite, fitness); assert (coverage <= totalGoals) : "Covered " + coverage + " vs total goals " + totalGoals; assert (fitness >= 0.0); assert (fitness != 0.0 || coverage == totalGoals) : "Fitness: " + fitness + ", " + "coverage: " + coverage + "/" + totalGoals; assert (suite.getCoverage(this) <= 1.0) && (suite.getCoverage(this) >= 0.0) : "Wrong coverage value " + suite.getCoverage(this); return fitness; } /** * Some useful debug information * * @param coveredBranches * @param coveredMethods * @param fitness */ private void printStatusMessages( AbstractTestSuiteChromosome<? extends ExecutableChromosome> suite, int coveredBranches, double fitness) { if (coveredBranches > maxCoveredBranches) { maxCoveredBranches = coveredBranches; logger.info("(Branches) Best individual covers " + coveredBranches + "/" + (totalBranches * 2) + " branches"); logger.info("Fitness: " + fitness + ", size: " + suite.size() + ", length: " + suite.totalLengthOfTestCases()); } if (fitness < bestFitness) { logger.info("(Fitness) Best individual covers " + coveredBranches + "/" + (totalBranches * 2) + " branches"); bestFitness = fitness; logger.info("Fitness: " + fitness + ", size: " + suite.size() + ", length: " + suite.totalLengthOfTestCases()); } } }