org.evosuite.continuous.job.schedule.SeedingSchedule Maven / Gradle / Ivy
/**
* 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.continuous.job.schedule;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import org.evosuite.continuous.job.JobDefinition;
import org.evosuite.continuous.job.JobScheduler;
import org.evosuite.continuous.project.ProjectGraph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Choose a precise order in which the CUTs will be targeted.
* Test cases for a CUT can be used for seeding in the search
* of the following CUTs in the schedule
*
* @author arcuri
*
*/
public class SeedingSchedule extends OneTimeSchedule{
private static Logger logger = LoggerFactory.getLogger(SeedingSchedule.class);
protected final OneTimeSchedule base;
public SeedingSchedule(JobScheduler scheduler) {
this(scheduler, new SimpleSchedule(scheduler));
}
protected SeedingSchedule(JobScheduler scheduler, OneTimeSchedule base) {
super(scheduler);
this.base = base;
}
@Override
protected List createScheduleOnce() {
List jobs = base.createScheduleOnce();
if(logger.isDebugEnabled()){
logger.debug("Base schedule: "+jobs);
}
return addDepenciesAndSort(jobs);
}
@Override
protected List createScheduleForWhenNotEnoughBudget(){
/*
* even if we do not have enough budget to target all CUTs, we
* still want to use seeding.
*/
List jobs = super.createScheduleForWhenNotEnoughBudget();
if(logger.isDebugEnabled()){
logger.debug("Base, reduced schedule: "+jobs);
}
return addDepenciesAndSort(jobs);
}
protected List addDepenciesAndSort(List jobs){
jobs = addDependenciesForSeeding(jobs);
if(logger.isDebugEnabled()){
logger.debug("Schedule after adding dependencies: "+jobs);
}
jobs = getSortedToSatisfyDependencies(jobs);
if(logger.isDebugEnabled()){
logger.debug("Final schedule after sorting: "+jobs);
}
return jobs;
}
/**
* Try (best effort) to sort the jobs in a way in which dependent jobs
* are executed first. Try to maintain the relative order of the input list.
* Note: even if sorting is not precise, still the job executor will be able to handle it
*
*
* @param jobs A sorted copy of the input list
*/
protected static List getSortedToSatisfyDependencies(List jobs){
Queue toAssign = new LinkedList(jobs);
List postponed = new LinkedList();
List out = new ArrayList(jobs.size());
Set assigned = new HashSet();
/*
* Note: the code here is similar to what done in JobExecutor
*/
mainLoop: while(!toAssign.isEmpty() || !postponed.isEmpty()){
JobDefinition chosenJob = null;
//postponed jobs have the priority
if(!postponed.isEmpty()){
Iterator iterator = postponed.iterator();
postponedLoop : while(iterator.hasNext()){
JobDefinition job = iterator.next();
if(job.areDependenciesSatisfied(jobs,assigned)){
chosenJob = job;
iterator.remove();
break postponedLoop;
}
}
}
if(chosenJob == null && toAssign.isEmpty()){
/*
* nothing satisfied in 'postponed', and nothing left in 'toAssign'.
* so just pick up one (the oldest)
*/
assert !postponed.isEmpty();
chosenJob = postponed.remove((int) 0);
}
if(chosenJob == null){
assert !toAssign.isEmpty();
toExecuteLoop : while(!toAssign.isEmpty()){
JobDefinition job = toAssign.poll();
if(job.areDependenciesSatisfied(jobs,assigned)){
chosenJob = job;
break toExecuteLoop;
} else {
postponed.add(job);
}
}
if(chosenJob == null){
assert !postponed.isEmpty() && toAssign.isEmpty();
continue mainLoop;
}
}
out.add(chosenJob);
assigned.add(chosenJob.cut);
}
return out;
}
/**
* For each input job, identify all the others jobs we want to generate
* test cases first.
* The scheduler will use this information to first try to generate
* the test cases for the "dependency" jobs.
*
*
* There can be different strategies to define a dependency:
* - ancestor, non-interface classes
* - classes used as input objects
* - subtypes of classes used as input objects
*
* @param jobs
* @return a copy given input list, but with new jobs objects
*/
protected List addDependenciesForSeeding(List jobs){
List list = new ArrayList(jobs.size());
for(int i=0; i inputs = calculateInputClasses(job);
Set parents = calculateAncestors(job);
list.add(job.getByAddingDependencies(inputs,parents));
}
return list;
}
/**
* If CUT A takes as input an object B, then to cover A we might need B
* set in a specific way. Using the test cases generated for B can give
* us a pool of interesting instances of B.
*
* @param job
* @param dep
*/
private Set calculateInputClasses(JobDefinition job) {
Set dep = new LinkedHashSet();
ProjectGraph graph = scheduler.getProjectData().getProjectGraph();
for(String input : graph.getCUTsDirectlyUsedAsInput(job.cut, true)){
if(graph.isInterface(input)){
continue;
}
dep.add(input);
}
return dep;
}
/**
* The motivation for adding ancestors is as follows:
* consider CUT A extends B. If B is not an interface,
* then likely it will have an internal state.
* Test cases for B might bring it to some interesting/hard
* to reach configurations.
* If the methods in A rely on those states in the upper class,
* then such test cases from B "might" be helpful.
* But, to do so, the seeded test cases need to change the concrete class.
* For example, if we have:
*
* B foo = new B();
* foo.doSomething(x,y);
*
*
then we should transform it into:
*
*
B foo = new A();
* foo.doSomething(x,y);
*
*
There is a potentially tricky case.
* Consider for example if B is abstract and,
* when test cases were generated for it, a subclass
* C was chosen instead of A. This would mean the test
* case for B would look like:
*
*
B foo = new C();
* foo.doSomething(x,y);
*
*
However, it is actually not a big deal, as it is safe
* to modify new C()
with new A()
.
*
*
Note: if the test cases for B are in the
* form C foo = new C();
, then that would rather
* be a bug/problem in how EvoSuite generated test cases for B
*
* @param job
*/
private Set calculateAncestors(JobDefinition job) {
Set dep = new LinkedHashSet();
ProjectGraph graph = scheduler.getProjectData().getProjectGraph();
if(graph.isInterface(job.cut)){
/*
* even if an interface has code, it will have no class state (ie fields).
* so, no point in looking at its ancestors
*/
return dep;
}
for(String parent : graph.getAllCUTsParents(job.cut)){
if(graph.isInterface(parent)){
continue;
}
dep.add(parent);
}
return dep;
}
}