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

org.evosuite.ga.archive.MIOArchive 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.archive;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.evosuite.Properties;
import org.evosuite.ga.FitnessFunction;
import org.evosuite.testcase.TestChromosome;
import org.evosuite.testcase.TestFitnessFunction;
import org.evosuite.testcase.execution.ExecutionResult;
import org.evosuite.testsuite.TestSuiteChromosome;
import org.evosuite.utils.Randomness;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of the archive described in the 'Many Independent Objective (MIO) Algorithm for
 * Test Suite Generation' paper.
 * 
 * @author José Campos
 */
public class MIOArchive extends Archive {

  private static final long serialVersionUID = -6100903230303784634L;

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

  /**
   * Map used to store all targets (keys of the map) and the corresponding covering solutions
   * (values of the map)
   **/
  protected final Map archive = new LinkedHashMap();

  public static final MIOArchive instance =
      new MIOArchive();

  /**
   * {@inheritDoc}
   */
  @Override
  public void addTarget(F target) {
    super.addTarget(target);

    if (!this.archive.containsKey(target)) {
      logger.debug("Registering new target '" + target + "'");
      this.archive.put(target, new Population(Properties.NUMBER_OF_TESTS_PER_TARGET));
    }

    this.registerNonCoveredTargetOfAMethod(target);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void updateArchive(F target, T solution, double fitnessValue) {
    super.updateArchive(target, solution, fitnessValue);
    assert this.archive.containsKey(target);

    ExecutionResult executionResult = solution.getLastExecutionResult();
    // remove all statements after an exception
    if (!executionResult.noThrownExceptions()) {
      solution.getTestCase().chop(executionResult.getFirstPositionOfThrownException() + 1);
    }

    boolean isNewCoveredTarget = this.archive.get(target)
        .addSolution(1.0 - FitnessFunction.normalize(fitnessValue), solution);
    if (isNewCoveredTarget) {
      this.removeNonCoveredTargetOfAMethod(target);
      this.hasBeenUpdated = true;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isArchiveEmpty() {
    return this.getNumberOfSolutions() == 0;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getNumberOfTargets() {
    return this.archive.keySet().size();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getNumberOfCoveredTargets() {
    return this.getCoveredTargets().size();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getNumberOfCoveredTargets(Class targetClass) {
    return (int) this.getCoveredTargets().stream()
        .filter(target -> target.getClass() == targetClass).count();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Set getCoveredTargets() {
    return this.archive.keySet().stream().filter(target -> this.archive.get(target).isCovered())
        .collect(Collectors.toSet());
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getNumberOfUncoveredTargets() {
    return this.getUncoveredTargets().size();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getNumberOfUncoveredTargets(Class targetClass) {
    return (int) this.getUncoveredTargets().stream()
        .filter(target -> target.getClass() == targetClass).count();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Set getUncoveredTargets() {
    return this.archive.keySet().stream().filter(target -> !this.archive.get(target).isCovered())
        .collect(Collectors.toSet());
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean hasTarget(F target) {
    assert target != null;
    return this.archive.containsKey(target);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getNumberOfSolutions() {
    return this.getSolutions().size();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Set getSolutions() {
    Set solutions = new LinkedHashSet();
    for (Population population : this.archive.values()) {
      T solution = population.getBestSolutionIfAny();
      if (solution != null) {
        solutions.add(solution);
      }
    }
    return solutions;
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  @Override
  public T getSolution() {

    // Choose one target at random that has not been covered but contains some solutions. In case
    // there is not any non-covered target with at least one solution, either because all targets
    // have been covered or for the non-covered targets there is not any solution yet, then choose
    // one of the covered targets at random. Thereafter, choose one solution randomly from the list
    // of solutions of the chosen target.

    List targetsWithSolutions = this.archive.keySet().stream()
        .filter(target -> this.archive.get(target).numSolutions() > 0).collect(Collectors.toList());

    if (targetsWithSolutions.isEmpty()) {
      // there is not at least one target with at least one solution
      return null;
    }

    List potentialTargets = targetsWithSolutions.stream()
        .filter(target -> this.archive.get(target).isCovered() == false).collect(Collectors.toList());

    if (potentialTargets.isEmpty()) {
      potentialTargets =
          targetsWithSolutions.stream().filter(target -> this.archive.get(target).isCovered() == true)
              .collect(Collectors.toList());
    }
    assert !potentialTargets.isEmpty();

    // Instead of choosing a target at random, we choose the one with the lowest counter value.
    // (See Section 3.3 of the paper that describes this archive for more details)

    // F target = Randomness.choice(potentialTargets);
    // T randomSolution = (T) this.archive.get(target).sampleSolution();

    // ASC sort, i.e., from the population with the lowest counter to the population with the
    // highest counter
    potentialTargets.sort(new Comparator() {
      @Override
      public int compare(F f0, F f1) {
        if (archive.get(f0).counter() < archive.get(f1).counter()) {
          return -1;
        } else if (archive.get(f0).counter() > archive.get(f1).counter()) {
          return 1;
        }
        return 0;
      }
    });

    T randomSolution = this.archive.get(potentialTargets.get(0)).sampleSolution();
    return randomSolution == null ? null : (T) randomSolution.clone();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public T getSolution(F target) {
    assert target != null;
    assert this.archive.containsKey(target);
    return this.archive.get(target).getBestSolutionIfAny();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean hasSolution(F target) {
    assert target != null;
    assert this.archive.containsKey(target);
    return this.archive.get(target).isCovered();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public T getRandomSolution() {
    return Randomness.choice(this.getSolutions());
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  @Override
  protected TestSuiteChromosome createMergedSolution(TestSuiteChromosome solution) {
    // Deactivate in case a test is executed and would access the archive as this might cause a
    // concurrent access
    Properties.TEST_ARCHIVE = false;

    TestSuiteChromosome mergedSolution = (TestSuiteChromosome) solution.clone();

    // to avoid adding the same solution to 'mergedSolution' suite
    Set solutionsSampledFromArchive = new LinkedHashSet();

    for (F target : this.archive.keySet()) {
      // does solution cover target?
      if (!target.isCoveredBy(mergedSolution)) {
        Population population = this.archive.get(target);

        // is there any solution in the archive that covers it?
        T t = population.getBestSolutionIfAny();
        if (t != null) {
          // has t been considered?
          if (!solutionsSampledFromArchive.contains(t)) {
            solutionsSampledFromArchive.add(t);

            T tClone = (T) t.clone();
            mergedSolution.addTest(tClone);
          }
        }
      }
    }

    // re-evaluate merged solution
    for (FitnessFunction fitnessFunction : solution.getFitnessValues().keySet()) {
      fitnessFunction.getFitness(mergedSolution);
    }

    // re-active it
    Properties.TEST_ARCHIVE = true;

    logger.info("Final test suite size from archive: " + mergedSolution);
    return mergedSolution;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void shrinkSolutions(int newPopulationSize) {
    assert newPopulationSize > 0;
    for (F target : this.archive.keySet()) {
      this.archive.get(target).shrinkPopulation(newPopulationSize);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    return "NumTargets: " + this.getNumberOfTargets() + ", NumCoveredTargets: "
        + this.getNumberOfCoveredTargets() + ", NumSolutions: " + this.getNumberOfSolutions();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void reset() {
    super.reset();
    this.archive.clear();
  }

  private class Population implements Serializable {

    private static final long serialVersionUID = 1671692598239736237L;

    private int counter = 0;

    private int capacity;

    private List> solutions = null;

    /**
     * 
     * @param populationSize
     */
    private Population(int populationSize) {
      this.capacity = populationSize;
      this.solutions = new ArrayList>(populationSize);
    }

    /**
     * 
     * @return
     */
    private int counter() {
      return this.counter;
    }

    /**
     * 
     * @return
     */
    private boolean isCovered() {
      return this.solutions.size() == 1 && this.capacity == 1
          && this.solutions.get(0).getLeft() == 1.0;
    }

    /**
     * 
     * @param h [0,1] value, where 1 means that the target is covered, and whereas 0 is the worst
     *        possible heuristics value
     * @param t
     */
    private boolean addSolution(Double h, T t) {
      assert h >= 0.0 && h <= 1.0;

      if (h == 0.0) {
        // from the paper that describes this type of archive: "if h=0, the test is not added
        // regardless of the following conditions"
        return false;
      }

      if (h < 1.0 && this.isCovered()) {
        // candidate solution T does not cover the already fully covered target, therefore there is
        // no way it could be any better
        return false;
      }

      Pair candidateSolution = new ImmutablePair(h, t);

      boolean added = false;

      // does the candidate solution fully cover the target?
      if (h == 1.0) {
        // yes. has the target been fully covered by a previous solution?
        if (this.isCovered()) {
          Pair currentSolution = this.solutions.get(0);

          if (isPairBetterThanCurrent(currentSolution, candidateSolution)) {
            added = true;
            this.solutions.set(0, candidateSolution);
          }
        } else {
          // as the target is now fully covered by the candidate solution T, from now on there is no
          // need to keep more than one solution, only the single best one. therefore, we can get
          // rid of all solutions (if any) and shrink the number of solutions to only one.
          added = true;
          this.capacity = 1;
          this.solutions.clear();
          this.solutions.add(candidateSolution);
        }
      } else {
        // no, candidate solution T does not fully cover the target.

        // is there enough room for yet another solution?
        if (this.solutions.size() < this.capacity) {
          // yes, there is.

          // as an optimisation, in here we could check whether candidateSolution is an existing
          // solution, however it could be quite expensive to do it and most likely not worth it
          this.solutions.add(candidateSolution);
          this.sortPairSolutions(); // keep solutions sorted from the best to the worse
        } else {
          // no, there is not. so, replace the worst one, if candidate is better.
          this.sortPairSolutions();
          Pair worstSolution = this.solutions.get(this.capacity - 1);

          if (isPairBetterThanCurrent(worstSolution, candidateSolution)) {
            this.solutions.set(this.capacity - 1, candidateSolution);
          }
        }
      }

      // a set of solutions larger that a maximum capacity would be considered illegal
      assert this.solutions.size() <= this.capacity;

      if (added) {
        // reset counter if and only if a new/better solution has been found
        this.counter = 0;
      }

      return added;
    }

    /**
     * 
     * @param currentSolution
     * @param candidateSolution
     * @return
     */
    private boolean isPairBetterThanCurrent(Pair currentSolution,
        Pair candidateSolution) {
      int cmp = Double.compare(currentSolution.getLeft(), candidateSolution.getLeft());
      if (cmp < 0) {
        return true;
      } else if (cmp > 0) {
        return false;
      }
      assert cmp == 0;

      return isBetterThanCurrent(currentSolution.getRight(), candidateSolution.getRight());
    }

    /**
     * 
     * @return
     */
    private T sampleSolution() {
      if (this.numSolutions() == 0) {
        return null;
      }
      this.counter++;
      return Randomness.choice(this.solutions).getRight();
    }

    /**
     * DESC sort, i.e., from the pair with the highest h to the pair with the lowest h
     */
    private void sortPairSolutions() {
      this.solutions.sort(new Comparator>() {
        @Override
        public int compare(Pair solution0, Pair solution1) {
          if (solution0.getLeft() < solution1.getLeft()) {
            return 1;
          } else if (solution0.getLeft() > solution1.getLeft()) {
            return -1;
          }
          return 0;
        }
      });
    }

    /**
     * 
     * @return
     */
    private int numSolutions() {
      return this.solutions.size();
    }

    /**
     * 
     * @return
     */
    private T getBestSolutionIfAny() {
      if (this.numSolutions() == 0 || !this.isCovered()) {
        return null;
      }
      return this.solutions.get(0).getRight();
    }

    /**
     * 
     * @param newPopulationSize
     */
    private void shrinkPopulation(int newPopulationSize) {
      assert newPopulationSize > 0;

      if (this.isCovered()) {
        return;
      }

      this.capacity = newPopulationSize;

      if (this.numSolutions() < newPopulationSize) {
        // no need to shrink it
        return;
      }

      List> shrinkSolutions = new ArrayList>(newPopulationSize);
      for (int i = 0; i < newPopulationSize; i++) {
        shrinkSolutions.add(this.solutions.get(i));
      }
      this.solutions.clear();
      this.solutions.addAll(shrinkSolutions);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
      return 31 * counter + capacity + this.solutions.hashCode();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null) {
        return false;
      }
      if (this.getClass() != obj.getClass()) {
        return false;
      }

      Population p = (Population) obj;
      if (this.counter != p.counter) {
        return false;
      }
      if (this.capacity != p.capacity) {
        return false;
      }
      if (this.solutions.size() != p.solutions.size()) {
        return false;
      }

      return this.solutions.equals(p.solutions);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy