/** * 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.continuous.job; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Definition of a "job", ie a run of EvoSuite on a CUT. * * <p> * Note: this class is/should be immutable * * @author arcuri * */ public class JobDefinition { private static Logger logger = LoggerFactory.getLogger(JobDefinition.class); /** * counter used to create unique ids in a thread-safe manner */ private static final AtomicInteger counter = new AtomicInteger(0); /** * A unique, human-readable identifier for this job */ public final int jobID; /** * define for how long this job should be run */ public final int seconds; /** * define how much memory this job can allocate */ public final int memoryInMB; /** * full qualifying name of the class under test (CUT) */ public final String cut; /** * the configuration id, identify which parameter settings were used */ public final int configurationId; /** * the name of all classes this CUT depends on, and that would be good to * have generated test cases before starting this job. This is a union of * all the the types of dependency (eg, input and parent) */ public final Set<String> dependentOnClasses; /** * All dependent classes used as input for this CUT */ public final Set<String> inputClasses; /** * All dependent classes in the parent hierarchy */ public final Set<String> parentClasses; /** * Main constructor * * @param seconds * @param memoryInMB * @param cut * @param configurationId */ public JobDefinition(int seconds, int memoryInMB, String cut, int configurationId, Set<String> inputDependencies, Set<String> parentDependencies) { super(); this.jobID = counter.getAndIncrement(); this.seconds = seconds; this.memoryInMB = memoryInMB; this.cut = cut; this.configurationId = configurationId; HashSet<String> union = new HashSet<String>(); if (inputDependencies != null && inputDependencies.size() > 0) { this.inputClasses = Collections.unmodifiableSet(new HashSet<String>( inputDependencies)); union.addAll(inputClasses); } else { this.inputClasses = null; } if (parentDependencies != null && parentDependencies.size() > 0) { this.parentClasses = Collections.unmodifiableSet(new HashSet<String>( parentDependencies)); union.addAll(parentClasses); } else { this.parentClasses = null; } if (union.size() == 0) { this.dependentOnClasses = null; } else { this.dependentOnClasses = Collections.unmodifiableSet(new HashSet<String>( union)); } } /** * Create a copy of this job, and add the input and parent dependencies to * the set of CUT dependencies * * <p> * It is OK to have one of the sets null, but not both * * @param input * @return */ public JobDefinition getByAddingDependencies(Set<String> inputs, Set<String> parents) throws IllegalArgumentException { if (inputs == null && parents == null) { throw new IllegalArgumentException("Both sets are null"); } if(inputs!=null && inputs.contains(cut)){ throw new IllegalArgumentException("'inputs' contains reference to this job"); } if(parents!=null && parents.contains(cut)){ throw new IllegalArgumentException("'parents' contains reference to this job"); } if (inputClasses != null) { logger.debug("Adding "+inputClasses.size()+"input dependecies in job "+jobID); if (inputs == null) { inputs = inputClasses; } else { inputs.addAll(inputClasses); } } if (parentClasses != null) { if (parents == null) { parents = parentClasses; } else { parents.addAll(parentClasses); } } return new JobDefinition(seconds, memoryInMB, cut, configurationId, inputs, parents); } /** * Create a copy of this job by adding extra seconds * * @param input * @return */ public JobDefinition getByAddingBudget(int moreSeconds) throws IllegalArgumentException { if(moreSeconds <= 0){ throw new IllegalArgumentException("Invalid extra seconds: "+moreSeconds); } return new JobDefinition(seconds+moreSeconds, memoryInMB, cut, configurationId, inputClasses, parentClasses); } /** * Does the execution of this job depend on the other? * @param other * @return */ public boolean dependOn(JobDefinition other){ return dependentOnClasses!=null && dependentOnClasses.contains(other.cut); } /** * The number of classes this job depends on and * should be executed before this job * * @return */ public int getNumberOfDependencies(){ if(dependentOnClasses==null){ return 0; } else { return dependentOnClasses.size(); } } /** * Check if all jobs this one depends on are finished * * @param job * @return */ public boolean areDependenciesSatisfied(List<JobDefinition> schedule, Set<String> done){ if(dependentOnClasses == null){ return true; // no dependencies to satisfy } for(String name : dependentOnClasses){ /* * It could happen that a schedule is not complete, in the sense that * we do not create jobs for each single CUT in the project. * If A depends on B, but we have no job for B, then no point in postponing * a job for A */ if(!inTheSchedule(schedule,name)){ continue; } if(!done.contains(name)){ return false; } } return true; } private boolean inTheSchedule(List<JobDefinition> jobs, String cut){ for(JobDefinition job : jobs){ if(job.cut.equals(cut)){ return true; } } return false; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + configurationId; result = prime * result + ((cut == null) ? 0 : cut.hashCode()); result = prime * result + ((dependentOnClasses == null) ? 0 : dependentOnClasses.hashCode()); result = prime * result + jobID; result = prime * result + memoryInMB; result = prime * result + seconds; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; JobDefinition other = (JobDefinition) obj; if (configurationId != other.configurationId) return false; if (cut == null) { if (other.cut != null) return false; } else if (!cut.equals(other.cut)) return false; if (dependentOnClasses == null) { if (other.dependentOnClasses != null) return false; } else if (!dependentOnClasses.equals(other.dependentOnClasses)) return false; if (jobID != other.jobID) return false; if (memoryInMB != other.memoryInMB) return false; if (seconds != other.seconds) return false; return true; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "job "+jobID + ", target " + cut+ ", number of dependencies "+getNumberOfDependencies(); } }