/** * * 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.ga.metaheuristics.mosa; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import org.evosuite.ProgressMonitor; import org.evosuite.Properties; import org.evosuite.Properties.Criterion; import org.evosuite.coverage.branch.BranchCoverageSuiteFitness; import org.evosuite.coverage.line.LineCoverageSuiteFitness; import org.evosuite.coverage.mutation.StrongMutationSuiteFitness; import org.evosuite.coverage.mutation.WeakMutationSuiteFitness; import org.evosuite.coverage.statement.StatementCoverageSuiteFitness; import org.evosuite.ga.Chromosome; import org.evosuite.ga.ChromosomeFactory; import org.evosuite.ga.ConstructionFailedException; import org.evosuite.ga.FitnessFunction; import org.evosuite.ga.metaheuristics.GeneticAlgorithm; import org.evosuite.ga.metaheuristics.SearchListener; import org.evosuite.ga.metaheuristics.mosa.comparators.MOSADominanceComparator; import org.evosuite.ga.operators.selection.SelectionFunction; import org.evosuite.testcase.TestCase; import org.evosuite.testcase.TestChromosome; import org.evosuite.testcase.TestFitnessFunction; import org.evosuite.testcase.statements.ArrayStatement; import org.evosuite.testcase.statements.ConstructorStatement; import org.evosuite.testcase.statements.MethodStatement; import org.evosuite.testcase.statements.PrimitiveStatement; import org.evosuite.testcase.statements.Statement; import org.evosuite.testcase.statements.StringPrimitiveStatement; import org.evosuite.testcase.variable.VariableReference; import org.evosuite.testsuite.TestSuiteChromosome; import org.evosuite.testsuite.TestSuiteFitnessFunction; import org.evosuite.utils.ArrayUtil; import org.evosuite.utils.Randomness; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract class for MOSA * * @author Annibale Panichella, Fitsum M. Kifetew * * @param <T> */ public abstract class AbstractMOSA<T extends Chromosome> extends GeneticAlgorithm<T> { private static final long serialVersionUID = 146182080947267628L; private static final Logger logger = LoggerFactory.getLogger(MOSA.class); /** keep track of overall suite fitness and coverage */ protected TestSuiteFitnessFunction suiteFitness; /** Selection function to select parents */ protected SelectionFunction<T> selectionFunction = new MOSATournamentSelection<T>(); /** Selected ranking strategy **/ protected Ranking<T> ranking; /** * Constructor * * @param factory * a {@link org.evosuite.ga.ChromosomeFactory} object */ public AbstractMOSA(ChromosomeFactory<T> factory) { super(factory); if (ArrayUtil.contains(Properties.CRITERION, Criterion.BRANCH)) suiteFitness = new BranchCoverageSuiteFitness(); else if (ArrayUtil.contains(Properties.CRITERION, Criterion.STRONGMUTATION) || ArrayUtil.contains(Properties.CRITERION, Criterion.MUTATION)) suiteFitness = new StrongMutationSuiteFitness(); else if (ArrayUtil.contains(Properties.CRITERION, Criterion.WEAKMUTATION)) suiteFitness = new WeakMutationSuiteFitness(); else if (ArrayUtil.contains(Properties.CRITERION, Criterion.STATEMENT)) suiteFitness = new StatementCoverageSuiteFitness(); else if (ArrayUtil.contains(Properties.CRITERION, Criterion.LINE)) suiteFitness = new LineCoverageSuiteFitness(); else{ logger.warn("MOSA currently supports BRANCH, LINE, STATEMENT, WEAKMUTATION and STRONGMUTATION criteria : " + Properties.CRITERION + ", defaulting to BRANCH."); } // set the ranking strategy if (Properties.RANKING_TYPE == Properties.RankingType.PREFERENCE_SORTING) ranking = new RankBasedPreferenceSorting<T>(); else if (Properties.RANKING_TYPE == Properties.RankingType.FAST_NON_DOMINATED_SORTING) ranking = new FastNonDominatedSorting<T>(); else ranking = new RankBasedPreferenceSorting<T>(); // default ranking strategy } /** * This method is used to generate new individuals (offsprings) from * the current population * @return offspring population */ @SuppressWarnings("unchecked") protected List<T> breedNextGeneration() { List<T> offspringPopulation = new ArrayList<T>(Properties.POPULATION); // we apply only Properties.POPULATION/2 iterations since in each generation // we generate two offsprings for (int i=0; i < Properties.POPULATION/2 && !isFinished(); i++){ // select best individuals T parent1 = selectionFunction.select(population); T parent2 = selectionFunction.select(population); T offspring1 = (T) parent1.clone(); T offspring2 = (T) parent2.clone(); // apply crossover try { if (Randomness.nextDouble() <= Properties.CROSSOVER_RATE) { crossoverFunction.crossOver(offspring1, offspring2); } } catch (ConstructionFailedException e) { logger.debug("CrossOver failed."); continue; } removeUnusedVariables(offspring1); removeUnusedVariables(offspring2); // apply mutation on offspring1 mutate(offspring1, parent1); if (offspring1.isChanged()) { clearCachedResults(offspring1); offspring1.updateAge(currentIteration); calculateFitness(offspring1); offspringPopulation.add(offspring1); } // apply mutation on offspring2 mutate(offspring2, parent2); if (offspring2.isChanged()) { clearCachedResults(offspring2); offspring2.updateAge(currentIteration); calculateFitness(offspring2); offspringPopulation.add(offspring2); } } // Add new randomly generate tests for (int i = 0; i<Properties.POPULATION * Properties.P_TEST_INSERTION; i++){ T tch = null; if (this.getCoveredGoals().size() == 0 || Randomness.nextBoolean()){ tch = this.chromosomeFactory.getChromosome(); tch.setChanged(true); } else { tch = (T) Randomness.choice(getArchive()).clone(); tch.mutate(); tch.mutate(); } if (tch.isChanged()) { tch.updateAge(currentIteration); calculateFitness(tch); offspringPopulation.add(tch); } } logger.info("Number of offsprings = {}", offspringPopulation.size()); return offspringPopulation; } /** * Method used to mutate an offspring */ private void mutate(T offspring, T parent){ offspring.mutate(); TestChromosome tch = (TestChromosome) offspring; if (!offspring.isChanged()) { // if offspring is not changed, we try // to mutate it once again offspring.mutate(); } if (!hasMethodCall(offspring)){ tch.setTestCase(((TestChromosome) parent).getTestCase().clone()); boolean changed = tch.mutationInsert(); if (changed){ for (Statement s : tch.getTestCase()) s.isValid(); } offspring.setChanged(changed); } notifyMutation(offspring); } /** This method checks whether the test has only primitive type statements. Indeed, * crossover and mutation can lead to tests with no method calls (methods or constructors call), * thus, when executed they will never cover something in the class under test. * @param test to check * @return true if the test has at least one method or constructor call (i.e., the test may * cover something when executed; false otherwise */ private boolean hasMethodCall(T test){ boolean flag = false; TestCase tc = ((TestChromosome) test).getTestCase(); for (Statement s : tc){ if (s instanceof MethodStatement){ MethodStatement ms = (MethodStatement) s; boolean isTargetMethod = ms.getDeclaringClassName().equals(Properties.TARGET_CLASS); if (isTargetMethod) return true; } if (s instanceof ConstructorStatement){ ConstructorStatement ms = (ConstructorStatement) s; boolean isTargetMethod = ms.getDeclaringClassName().equals(Properties.TARGET_CLASS); if (isTargetMethod) return true; } } return flag; } /** * This method clears the cached results for a specific chromosome (e.g., fitness function * values computed in previous generations). Since a test case is changed via crossover * and/or mutation, previous data must be recomputed. * @param chromosome TestChromosome to clean */ public void clearCachedResults(T chromosome){ ((TestChromosome) chromosome).clearCachedMutationResults(); ((TestChromosome) chromosome).clearCachedResults(); ((TestChromosome) chromosome).clearMutationHistory(); ((TestChromosome) chromosome).getFitnessValues().clear(); } /** * When a test case is changed via crossover and/or mutation, it can contains some * primitive variables that are not used as input (or to store the output) of method calls. * Thus, this method removes all these "trash" statements. * @param chromosome * @return true or false depending on whether "unused variables" are removed */ public boolean removeUnusedVariables(T chromosome) { int sizeBefore = chromosome.size(); TestCase t = ((TestChromosome) chromosome).getTestCase(); List<Integer> to_delete = new ArrayList<Integer>(chromosome.size()); boolean has_deleted = false; int num = 0; for (Statement s : t) { VariableReference var = s.getReturnValue(); boolean delete = false; delete = delete || s instanceof PrimitiveStatement; delete = delete || s instanceof ArrayStatement; delete = delete || s instanceof StringPrimitiveStatement; if (!t.hasReferences(var) && delete) { to_delete.add(num); has_deleted = true; } num++; } Collections.sort(to_delete, Collections.reverseOrder()); for (Integer position : to_delete) { t.remove(position); } int sizeAfter = chromosome.size(); if (has_deleted) logger.debug("Removed {} unused statements", (sizeBefore - sizeAfter)); return has_deleted; } /** * Notify all search listeners of fitness evaluation * * @param chromosome * a {@link org.evosuite.ga.Chromosome} object. */ @Override protected void notifyEvaluation(Chromosome chromosome) { for (SearchListener listener : listeners) { if (listener instanceof ProgressMonitor) continue; listener.fitnessEvaluation(chromosome); } } /** * Calculate fitness for the whole population */ protected void calculateFitness() { logger.debug("Calculating fitness for " + population.size() + " individuals"); Iterator<T> iterator = population.iterator(); while (iterator.hasNext()) { T c = iterator.next(); if (isFinished()) { if (c.isChanged()) iterator.remove(); } else { calculateFitness(c); } } } @SuppressWarnings("unchecked") @Override public List<T> getBestIndividuals() { //get final test suite (i.e., non dominated solutions in Archive) TestSuiteChromosome bestTestCases = new TestSuiteChromosome(); for (T test : getFinalTestSuite()) { bestTestCases.addTest((TestChromosome) test); } for (FitnessFunction<T> f : this.getCoveredGoals()){ bestTestCases.getCoveredGoals().add((TestFitnessFunction) f); } // compute overall fitness and coverage double fitness = this.fitnessFunctions.size() - numberOfCoveredTargets(); double coverage = ((double) numberOfCoveredTargets()) / ((double) this.fitnessFunctions.size()); bestTestCases.setFitness(suiteFitness, fitness); bestTestCases.setCoverage(suiteFitness, coverage); bestTestCases.setNumOfCoveredGoals(suiteFitness, (int) numberOfCoveredTargets()); bestTestCases.setNumOfNotCoveredGoals(suiteFitness, (int) (this.fitnessFunctions.size()-numberOfCoveredTargets())); List<T> bests = new ArrayList<T>(1); bests.add((T) bestTestCases); return bests; } /** * This method computes the fitness scores only for the current goals * @param c chromosome */ protected abstract void calculateFitness(T c); protected abstract List<T> getFinalTestSuite(); protected abstract List<T> getArchive(); /** * This method extracts non-dominated solutions (tests) according to all covered goal (e.g., branches) * @param solutionSet set of test cases to analyze with the "dominance" relationship * @return the non-dominated set of test cases */ protected List<T> getNonDominatedSolutions(List<T> solutions){ MOSADominanceComparator<T> comparator = new MOSADominanceComparator<>(this.getCoveredGoals()); List<T> next_front = new ArrayList<T>(solutions.size()); boolean isDominated; for (T p : solutions){ isDominated = false; List<T> dominatedSolutions = new ArrayList<T>(solutions.size()); for (T best : next_front){ int flag = comparator.compare(p, best); if (flag == -1) { dominatedSolutions.add(best); } if (flag == +1){ isDominated = true; } } if (isDominated) continue; next_front.add(p); next_front.removeAll(dominatedSolutions); } return next_front; } /** * This method verifies whether two TestCromosome contain * the same test case. Here the equality is computed looking at * the strings composing the tests. This method is strongly needed * in {@link AbstractMOSA#breedNextGeneration()}. * @param test1 first test * @param test2 second test * @return true if the test1 and test 2 (meant as String) are equal * to each other; false otherwise. */ protected boolean areEqual(T test1, T test2){ TestChromosome tch1 = (TestChromosome) test1; TestChromosome tch2 = (TestChromosome) test2; if (tch1.size() != tch2.size()) return false; if (tch1.size() == 0) return false; if (tch2.size() == 0) return false; return tch1.getTestCase().toCode().equals(tch2.getTestCase().toCode()); } /** {@inheritDoc} */ @Override public void initializePopulation() { logger.info("executing initializePopulation function"); notifySearchStarted(); currentIteration = 0; // Create a random parent population P0 generateInitialPopulation(Properties.POPULATION); // Determine fitness calculateFitness(); this.notifyIteration(); } protected abstract double numberOfCoveredTargets(); public abstract Set<FitnessFunction<T>> getCoveredGoals(); }