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

org.evosuite.ga.metaheuristics.mosa.AbstractMOSA 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.ga.metaheuristics.mosa;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.evosuite.ProgressMonitor;
import org.evosuite.Properties;
import org.evosuite.Properties.SelectionFunction;
import org.evosuite.coverage.FitnessFunctions;
import org.evosuite.coverage.exception.ExceptionCoverageSuiteFitness;
import org.evosuite.ga.Chromosome;
import org.evosuite.ga.ChromosomeFactory;
import org.evosuite.ga.ConstructionFailedException;
import org.evosuite.ga.FitnessFunction;
import org.evosuite.ga.archive.Archive;
import org.evosuite.ga.comparators.DominanceComparator;
import org.evosuite.ga.metaheuristics.GeneticAlgorithm;
import org.evosuite.ga.metaheuristics.SearchListener;
import org.evosuite.testcase.TestCase;
import org.evosuite.testcase.TestChromosome;
import org.evosuite.testcase.TestFitnessFunction;
import org.evosuite.testcase.secondaryobjectives.TestCaseSecondaryObjective;
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.BudgetConsumptionMonitor;
import org.evosuite.utils.LoggingUtils;
import org.evosuite.utils.Randomness;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract class for MOSA or variants of MOSA.
 *
 * @author Annibale Panichella, Fitsum M. Kifetew
 */
public abstract class AbstractMOSA extends GeneticAlgorithm {

	private static final long serialVersionUID = 146182080947267628L;

	private static final Logger logger = LoggerFactory.getLogger(AbstractMOSA.class);

	/** Keep track of overall suite fitness functions and correspondent test fitness functions */
	protected final Map> suiteFitnessFunctions;

	/** Object used to keep track of the execution time needed to reach the maximum coverage */
	protected final BudgetConsumptionMonitor budgetMonitor;

	/**
	 * Constructor.
	 *
	 * @param factory a {@link org.evosuite.ga.ChromosomeFactory} object
	 */
	public AbstractMOSA(ChromosomeFactory factory) {
		super(factory);

		this.suiteFitnessFunctions = new LinkedHashMap>();
		setupSuiteFitness();

		this.budgetMonitor = new BudgetConsumptionMonitor();

		// set the secondary objectives of test cases (useful when MOSA compares two test
		// cases to, for example, update the archive)
		TestCaseSecondaryObjective.setSecondaryObjectives();

		if (Properties.SELECTION_FUNCTION != SelectionFunction.RANK_CROWD_DISTANCE_TOURNAMENT) {
		  LoggingUtils.getEvoLogger()
		  .warn("Originally, MOSA was implemented with a '"
		      + SelectionFunction.RANK_CROWD_DISTANCE_TOURNAMENT.name()
		      + "' selection function. You may want to consider using it.");
		}
	}


	protected void setupSuiteFitness(){
		for (Properties.Criterion criterion : Properties.CRITERION) {
			TestSuiteFitnessFunction suiteFit = FitnessFunctions.getFitnessFunction(criterion);
			Class testFit = FitnessFunctions.getTestFitnessFunctionClass(criterion);
			this.suiteFitnessFunctions.put(suiteFit, testFit);
		}
	}

	/**
	 * This method is used to generate new individuals (offsprings) from
	 * the current population.
	 *
	 * @return offspring population
	 */
	@SuppressWarnings("unchecked")
	protected List breedNextGeneration() {
		List offspringPopulation = new ArrayList(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 && !this.isFinished(); i++) {
			// select best individuals
			T parent1 = this.selectionFunction.select(this.population);
			T parent2 = this.selectionFunction.select(this.population);
			T offspring1 = (T) parent1.clone();
			T offspring2 = (T) parent2.clone();
			// apply crossover
			try {
				if (Randomness.nextDouble() <= Properties.CROSSOVER_RATE) {
					this.crossoverFunction.crossOver(offspring1, offspring2);
				}
			} catch (ConstructionFailedException e) {
				logger.debug("CrossOver failed.");
				continue;
			}

			this.removeUnusedVariables(offspring1);
			this.removeUnusedVariables(offspring2);

			// apply mutation on offspring1
			this.mutate(offspring1, parent1);
			if (offspring1.isChanged()) {
				this.clearCachedResults(offspring1);
				offspring1.updateAge(this.currentIteration);
				this.calculateFitness(offspring1);
				offspringPopulation.add(offspring1);
			}

			// apply mutation on offspring2
			this.mutate(offspring2, parent2);
			if (offspring2.isChanged()) {
				this.clearCachedResults(offspring2);
				offspring2.updateAge(this.currentIteration);
				this.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(this.getSolutions()).clone();
				tch.mutate(); tch.mutate(); // TODO why is it mutated twice?
			}
			if (tch.isChanged()) {
				tch.updateAge(this.currentIteration);
				this.calculateFitness(tch);
				offspringPopulation.add(tch);
			}
		}
		logger.info("Number of offsprings = {}", offspringPopulation.size());
		return offspringPopulation;
	}

	/**
	 * Method used to mutate an offspring.
	 *
	 * @param offspring
	 * @param parent
	 */
	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 (!this.hasMethodCall(offspring)) {
			tch.setTestCase(((TestChromosome) parent).getTestCase().clone());
			boolean changed = tch.mutationInsert();
			if (changed) {
				for (Statement s : tch.getTestCase()) {
					s.isValid();
				}
			}
			offspring.setChanged(changed);
		}
		this.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
	 */
	protected 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
	 */
	protected boolean removeUnusedVariables(T chromosome) {
		int sizeBefore = chromosome.size();
		TestCase t = ((TestChromosome) chromosome).getTestCase();
		List to_delete = new ArrayList(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;
	}

	/**
	 * This method extracts non-dominated solutions (tests) according to all covered goal
	 * (e.g., branches).
	 *
	 * @param solutions list of test cases to analyze with the "dominance" relationship
	 * @return the non-dominated set of test cases
	 */
	protected List getNonDominatedSolutions(List solutions) {
		DominanceComparator comparator = new DominanceComparator(this.getCoveredGoals());
		List next_front = new ArrayList(solutions.size());
		boolean isDominated;
		for (T p : solutions) {
			isDominated = false;
			List dominatedSolutions = new ArrayList(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;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void initializePopulation() {
		logger.info("executing initializePopulation function");

		this.notifySearchStarted();
		this.currentIteration = 0;

		// Create a random parent population P0
		this.generateInitialPopulation(Properties.POPULATION);

		// Determine fitness
		this.calculateFitness();
		this.notifyIteration();
	}

    /**
     * Returns the goals that have been covered by the test cases stored in the archive.
     *
     * @return
     */
    @SuppressWarnings("unchecked")
    protected Set> getCoveredGoals() {
      Set> coveredGoals = new LinkedHashSet>();
      Archive.getArchiveInstance().getCoveredTargets()
          .forEach(ff -> coveredGoals.add((FitnessFunction) ff));
      return coveredGoals;
    }

    /**
     * Returns the number of goals that have been covered by the test cases stored in the archive.
     *
     * @return
     */
    protected int getNumberOfCoveredGoals() {
      return Archive.getArchiveInstance().getNumberOfCoveredTargets();
    }

    protected void addUncoveredGoal(FitnessFunction goal) {
      Archive.getArchiveInstance().addTarget((TestFitnessFunction) goal);
    }

    /**
     * Returns the goals that have not been covered by the test cases stored in the archive.
     *
     * @return
     */
    @SuppressWarnings("unchecked")
    protected Set> getUncoveredGoals() {
      Set> uncoveredGoals = new LinkedHashSet>();
      Archive.getArchiveInstance().getUncoveredTargets()
          .forEach(ff -> uncoveredGoals.add((FitnessFunction) ff));
      return uncoveredGoals;
    }

    /**
     * Returns the goals that have not been covered by the test cases stored in the archive.
     *
     * @return
     */
    protected int getNumberOfUncoveredGoals() {
      return Archive.getArchiveInstance().getNumberOfUncoveredTargets();
    }

    /**
     * Returns the total number of goals, i.e., number of covered goals + number of uncovered goals.
     *
     * @return
     */
    protected int getTotalNumberOfGoals() {
      return Archive.getArchiveInstance().getNumberOfTargets();
    }

    /**
     * Return the test cases in the archive as a list.
     *
     * @return
     */
    @SuppressWarnings("unchecked")
    protected List getSolutions() {
      List solutions = new ArrayList();
      Archive.getArchiveInstance().getSolutions().forEach(test -> solutions.add((T) test));
      return solutions;
    }

    /**
     * Generates a {@link org.evosuite.testsuite.TestSuiteChromosome} object with all test cases
     * in the archive.
     *
     * @return
     */
    protected TestSuiteChromosome generateSuite() {
      TestSuiteChromosome suite = new TestSuiteChromosome();
      Archive.getArchiveInstance().getSolutions().forEach(test -> suite.addTest(test));
      return suite;
    }

	///// ----------------------

	/**
	 * Some methods of the super class (i.e., {@link org.evosuite.ga.metaheuristics.GeneticAlgorithm}
	 * class) require a {@link org.evosuite.testsuite.TestSuiteChromosome} object. However, MOSA
	 * evolves {@link org.evosuite.testsuite.TestChromosome} objects. Therefore, we must override
	 * those methods and create a {@link org.evosuite.testsuite.TestSuiteChromosome} object with all
	 * the evolved {@link org.evosuite.testsuite.TestChromosome} objects (either in the population or
	 * in the {@link org.evosuite.ga.archive.Archive).
	 */

	/**
     * Notify all search listeners but ProgressMonitor of fitness evaluation.
     *
     * @param chromosome a {@link org.evosuite.ga.Chromosome} object.
     */
    @Override
    protected void notifyEvaluation(Chromosome chromosome) {
        for (SearchListener listener : this.listeners) {
            if (listener instanceof ProgressMonitor) {
                continue; // ProgressMonitor requires a TestSuiteChromosome
            }
            listener.fitnessEvaluation(chromosome);
        }
    }

    /**
     * Notify all search listeners but ProgressMonitor of a mutation.
     *
     * @param chromosome a {@link org.evosuite.ga.Chromosome} object.
     */
    @Override
    protected void notifyMutation(Chromosome chromosome) {
      for (SearchListener listener : this.listeners) {
          if (listener instanceof ProgressMonitor) {
              continue; // ProgressMonitor requires a TestSuiteChromosome
          }
          listener.modification(chromosome);
      }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void calculateFitness(T c) {
        this.fitnessFunctions.forEach(fitnessFunction -> fitnessFunction.getFitness(c));

        // if one of the coverage criterion is Criterion.EXCEPTION, then we have to analyse the results
        // of the execution to look for generated exceptions
        if (ArrayUtil.contains(Properties.CRITERION, Properties.Criterion.EXCEPTION)) {
          TestChromosome testChromosome = (TestChromosome) c;
          ExceptionCoverageSuiteFitness.calculateExceptionInfo(
              Arrays.asList(testChromosome.getLastExecutionResult()),
              new HashMap<>(), new HashMap<>(), new HashMap<>(), new ExceptionCoverageSuiteFitness());
        }

        this.notifyEvaluation(c);
        // update the time needed to reach the max coverage
        this.budgetMonitor.checkMaxCoverage(this.getNumberOfCoveredGoals());
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public List getBestIndividuals() {
        // get final test suite (i.e., non dominated solutions in Archive)
        TestSuiteChromosome bestTestCases = Archive.getArchiveInstance().mergeArchiveAndSolution(new TestSuiteChromosome());
        if (bestTestCases.getTestChromosomes().isEmpty()) {
          for (T test : this.getNonDominatedSolutions(this.population)) {
            bestTestCases.addTest((TestChromosome) test);
          }
        }

        // compute overall fitness and coverage
        this.computeCoverageAndFitness(bestTestCases);

        List bests = new ArrayList(1);
        bests.add((T) bestTestCases);

        return bests;
    }

    /**
     * {@inheritDoc}
     *
     * 

This method is used by the Progress Monitor at the and of each generation to show the total coverage reached by the algorithm. * Since the Progress Monitor requires a {@link org.evosuite.testsuite.TestSuiteChromosome} object, this method artificially creates * a {@link org.evosuite.testsuite.TestSuiteChromosome} object as the union of all solutions stored in the {@link * org.evosuite.ga.archive.Archive}.

* *

The coverage score of the {@link org.evosuite.testsuite.TestSuiteChromosome} object is given by the percentage of targets marked * as covered in the archive.

* * @return a {@link org.evosuite.testsuite.TestSuiteChromosome} object to be consumable by the Progress Monitor. */ @SuppressWarnings("unchecked") @Override public T getBestIndividual() { TestSuiteChromosome best = this.generateSuite(); if (best.getTestChromosomes().isEmpty()) { for (T test : this.getNonDominatedSolutions(this.population)) { best.addTest((TestChromosome) test); } for (TestSuiteFitnessFunction suiteFitness : this.suiteFitnessFunctions.keySet()) { best.setCoverage(suiteFitness, 0.0); best.setFitness(suiteFitness, 1.0); } return (T) best; } // compute overall fitness and coverage this.computeCoverageAndFitness(best); return (T) best; } protected void computeCoverageAndFitness(TestSuiteChromosome suite) { for (Entry> entry : this.suiteFitnessFunctions .entrySet()) { TestSuiteFitnessFunction suiteFitnessFunction = entry.getKey(); Class testFitnessFunction = entry.getValue(); int numberCoveredTargets = Archive.getArchiveInstance().getNumberOfCoveredTargets(testFitnessFunction); int numberUncoveredTargets = Archive.getArchiveInstance().getNumberOfUncoveredTargets(testFitnessFunction); int totalNumberTargets = numberCoveredTargets + numberUncoveredTargets; double coverage = totalNumberTargets == 0 ? 1.0 : ((double) numberCoveredTargets) / ((double) totalNumberTargets); suite.setFitness(suiteFitnessFunction, ((double) numberUncoveredTargets)); suite.setCoverage(suiteFitnessFunction, coverage); suite.setNumOfCoveredGoals(suiteFitnessFunction, numberCoveredTargets); suite.setNumOfNotCoveredGoals(suiteFitnessFunction, numberUncoveredTargets); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy