org.evosuite.setup.TestCluster 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.setup;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.evosuite.Properties;
import org.evosuite.TestGenerationContext;
import org.evosuite.ga.ConstructionFailedException;
import org.evosuite.ga.archive.Archive;
import org.evosuite.junit.CoverageAnalysis;
import org.evosuite.runtime.util.AtMostOnceLogger;
import org.evosuite.runtime.util.Inputs;
import org.evosuite.seeding.CastClassManager;
import org.evosuite.testcase.ConstraintHelper;
import org.evosuite.testcase.ConstraintVerifier;
import org.evosuite.testcase.TestCase;
import org.evosuite.testcase.jee.InstanceOnlyOnce;
import org.evosuite.testcase.variable.VariableReference;
import org.evosuite.utils.ListUtil;
import org.evosuite.utils.generic.GenericAccessibleObject;
import org.evosuite.utils.generic.GenericClass;
import org.evosuite.utils.generic.GenericConstructor;
import org.evosuite.utils.generic.GenericMethod;
import org.evosuite.utils.Randomness;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Gordon Fraser
*
*/
public class TestCluster {
protected static final Logger logger = LoggerFactory.getLogger(TestCluster.class);
/** Singleton instance */
private static TestCluster instance = null;
/** Set of all classes already analyzed */
@Deprecated
private final static Set> analyzedClasses = new LinkedHashSet<>();
/** UUT methods we want to cover when testing */
private final static Set> testMethods = new LinkedHashSet<>();
/**
* Methods used to modify and set the environment of the UUT
*/
private final Set> environmentMethods;
/** Static information about how to generate types */
private final static Map>> generators = new LinkedHashMap<>();
/** Cached information about how to generate types */
private final static Map>> generatorCache = new LinkedHashMap<>();
/** Static information about how to modify types */
private final static Map>> modifiers = new LinkedHashMap<>();
private static InheritanceTree inheritanceTree = null;
private EnvironmentTestClusterAugmenter environmentAugmenter;
//-------------------------------------------------------------------
protected TestCluster(){
environmentAugmenter = new EnvironmentTestClusterAugmenter(this);
environmentMethods = new LinkedHashSet<>();
}
/**
* Instance accessor
*
* @return
*/
public static synchronized TestCluster getInstance() {
if (instance == null) {
instance = new TestCluster();
}
// TODO: Need property to switch between test clusters
return instance;
}
public static void reset() {
analyzedClasses.clear();
testMethods.clear();
generators.clear();
generatorCache.clear();
modifiers.clear();
CastClassManager.getInstance().clear();
instance = null;
}
/**
* A generator for X might be a non-static method M of Y, but what if Y itself has no generator?
* In that case, M should not be a generator for X, as it is impossible to instantiate Y
*/
public void removeUnusableGenerators(){
generatorCache.clear();
Set removed = new LinkedHashSet<>();
for(Map.Entry>> entry : generators.entrySet()){
if(entry.getValue().isEmpty()){
recursiveRemoveGenerators(entry.getKey());
}
Set toRemove = new LinkedHashSet<>();
for(GenericAccessibleObject> gao : entry.getValue()){
GenericClass owner = gao.getOwnerClass();
if(removed.contains(owner)){
continue;
}
try {
cacheGenerators(owner);
} catch (ConstructionFailedException e) {
continue;
}
if(generatorCache.get(owner).isEmpty()){
toRemove.add(owner);
}
}
for(GenericClass tr : toRemove) {
recursiveRemoveGenerators(tr);
removed.add(tr);
}
}
removeOnlySelfGenerator();
removeDirectCycle();
generatorCache.clear();
}
/**
if a class X has a generator non-static method for Y, but, among its own generators for X it has one
that uses Y as input, then do not use any non-static method of X as generator for Y.
This is to avoid nasty cycles.
For example, consider the case of:
X(Y y){...}
Y getY(){...}
If we need Y, we could end up using x.getY(), which for instantiating x would need a Y, which might
end up in an infinite recursion...
*/
private void removeDirectCycle() {
//check each generator Y
for(Map.Entry>> entry : generators.entrySet()){
if(entry.getValue().isEmpty()){
continue;
}
//for a given type Y, check all its generators X, like "Y x.getY()"
Iterator> iter = entry.getValue().iterator();
while(iter.hasNext()){
GenericAccessibleObject> gao = iter.next();
// TODO: This is not working correctly. Until we have figured out
// the problem here, we either need to deactivate this entirely,
// or at least make sure that we don't delete constructors.
if(gao.isConstructor() || gao.isStatic()) {
continue;
}
GenericClass owner = gao.getOwnerClass(); // eg X
try {
cacheGenerators(owner);
} catch (ConstructionFailedException e) {
continue;
}
for(GenericAccessibleObject> genOwner : generatorCache.get(owner)){
if(genOwner.isStatic()){
continue; //as there is no need to instantiate X, it is not an issue
}
//is any generator for X using as input an instance of Y?
if(Arrays.asList(genOwner.getGenericParameterTypes())
.stream().anyMatch(
t -> t.equals(entry.getKey().getType()))
){
iter.remove();
break;
}
}
}
if(entry.getValue().isEmpty()){
recursiveRemoveGenerators(entry.getKey());
}
}
}
private void removeOnlySelfGenerator() {
for(Map.Entry>> entry : generators.entrySet()){
boolean toRemove = true;
for(GenericAccessibleObject gao : entry.getValue()){
if(! (!gao.isStatic() && gao.isMethod() && gao.getOwnerClass().equals(entry.getKey()))){
toRemove = false; //at least one good generator
break;
}
}
if(toRemove){
entry.getValue().clear();
}
}
}
private void recursiveRemoveGenerators(GenericClass toRemove) {
for(Map.Entry>> entry : generators.entrySet()){
boolean recursion = false;
Iterator> iter = entry.getValue().iterator();
while(iter.hasNext()){
GenericAccessibleObject> gao = iter.next();
if(gao.isMethod() && !gao.isStatic() && gao.getOwnerClass().equals(toRemove)){
iter.remove();
recursion = true;
}
}
if(recursion && entry.getValue().isEmpty()){
recursiveRemoveGenerators(entry.getKey());
}
}
}
public void invalidateGeneratorCache(GenericClass klass){
Iterator>>> iter = generatorCache.entrySet().iterator();
while(iter.hasNext()){
Map.Entry entry = iter.next();
GenericClass gen = (GenericClass) entry.getKey();
if(gen.isAssignableFrom(klass)){
iter.remove();
}
}
}
public void handleRuntimeAccesses(TestCase test) {
environmentAugmenter.handleRuntimeAccesses(test);
}
/**
* @return the inheritancetree
*/
public static InheritanceTree getInheritanceTree() {
return inheritanceTree;
}
/**
* @param inheritancetree
* the inheritancetree to set
*/
protected static void setInheritanceTree(InheritanceTree inheritancetree) {
inheritanceTree = inheritancetree;
}
public static boolean isTargetClassName(String className) {
if (!Properties.TARGET_CLASS_PREFIX.isEmpty()
&& className.startsWith(Properties.TARGET_CLASS_PREFIX)) {
// exclude existing tests from the target project
try {
Class> clazz = TestGenerationContext.getInstance().getClassLoaderForSUT().loadClass(className);
return !CoverageAnalysis.isTest(clazz);
} catch (ClassNotFoundException e) {
logger.info("Could not load class: ", className);
}
}
if (className.equals(Properties.TARGET_CLASS)
|| className.startsWith(Properties.TARGET_CLASS + "$")) {
return true;
}
if (Properties.INSTRUMENT_PARENT) {
return inheritanceTree.getSubclasses(Properties.TARGET_CLASS).contains(className);
}
return false;
}
/**
* Add a generator reflection object
*
* @param target
* is assumed to have wildcard types
* @param call
*/
public void addGenerator(GenericClass target, GenericAccessibleObject> call) {
if (!generators.containsKey(target))
generators.put(target, new LinkedHashSet>());
logger.debug("Adding generator for class " + target + ": " + call);
generators.get(target).add(call);
// Make sure cache is up to date
generatorCache.entrySet().removeIf(entry -> entry.getKey().isAssignableFrom(target));
}
/**
* Add a modifier reflection object
*
* @param target
* is assumed to have wildcard types
* @param call
*/
public void addModifier(GenericClass target, GenericAccessibleObject> call) {
if (!modifiers.containsKey(target))
modifiers.put(target, new LinkedHashSet<>());
modifiers.get(target).add(call);
}
/**
* Add a test call
*
* @return
*/
public void addTestCall(GenericAccessibleObject> call) throws IllegalArgumentException{
Inputs.checkNull(call);
testMethods.add(call);
}
public void removeTestCall(GenericAccessibleObject> call) {
testMethods.remove(call);
}
public void addEnvironmentTestCall(GenericAccessibleObject> call) throws IllegalArgumentException{
Inputs.checkNull(call);
environmentMethods.add(call);
}
/**
* Add a new class observed at runtime for container methods
*
* @param clazz
*/
public void addCastClassForContainer(Class> clazz) {
if (TestUsageChecker.canUse(clazz)) {
CastClassManager.getInstance().addCastClass(clazz, 1);
clearGeneratorCache(new GenericClass(clazz));
}
}
/**
* Calculate and cache all generators for a particular type. All generic
* types on the generator are instantiated according to the produced type
*
* @param clazz
* @throws ConstructionFailedException
*/
private void cacheGenerators(GenericClass clazz) throws ConstructionFailedException {
if (generatorCache.containsKey(clazz)) {
return;
}
logger.debug("1. Caching generators for {}", clazz);
Set> targetGenerators = new LinkedHashSet<>();
if (clazz.isObject()) {
logger.debug("2. Target class is object: {}", clazz);
for (GenericClass generatorClazz : generators.keySet()) {
if (generatorClazz.isObject()) {
targetGenerators.addAll(generators.get(generatorClazz));
}
}
} else {
logger.debug("2. Target class is not object: {}", clazz);
for (GenericClass generatorClazz : generators.keySet()) {
// logger.debug("3. Considering original generator: " + generatorClazz + " for " + clazz);
if (generatorClazz.canBeInstantiatedTo(clazz)) {
//logger.debug("4. generator " + generatorClazz + " can be instantiated to " + clazz);
GenericClass instantiatedGeneratorClazz = generatorClazz.getWithParametersFromSuperclass(clazz);
logger.debug("Instantiated type: {} for {} and superclass {}",
instantiatedGeneratorClazz,generatorClazz, clazz);
for (GenericAccessibleObject> generator : generators.get(generatorClazz)) {
logger.debug("5. current instantiated generator: {}", generator);
try {
if((generator.isMethod() || generator.isField()) && clazz.isParameterizedType() && GenericClass.isMissingTypeParameters(generator.getGenericGeneratedType())) {
logger.debug("No type parameters present in generator for {}: {}", clazz, generator);
continue;
}
// Set owner type parameters from new return type
GenericAccessibleObject> newGenerator = generator.copyWithOwnerFromReturnType(instantiatedGeneratorClazz);
boolean hadTypeParameters = false;
// Instantiate potential further type variables based on type variables of return type
if (newGenerator.getOwnerClass().hasWildcardOrTypeVariables()) {
logger.debug("Instantiating type parameters of owner type: {}",newGenerator.getOwnerClass());
GenericClass concreteClass = newGenerator.getOwnerClass()
.getGenericInstantiation(clazz.getTypeVariableMap());
newGenerator = newGenerator.copyWithNewOwner(concreteClass);
hadTypeParameters = true;
}
// If it is a generic method, instantiate generic type variables for the produced class
if (newGenerator.hasTypeParameters()) {
logger.debug("Instantiating type parameters");
/*
* TODO:
* public class Foo {
* public Foo getFoo() {
* // ...
* }
* }
*
* Here X and X are two different type variables, and these need to be matched here!
*
*/
newGenerator = newGenerator.getGenericInstantiationFromReturnValue(clazz);
hadTypeParameters = true;
// newGenerator = newGenerator.getGenericInstantiation(clazz);
}
logger.debug("Current generator: {}", newGenerator);
if ((!hadTypeParameters && generatorClazz.equals(clazz))
|| clazz.isAssignableFrom(newGenerator.getGeneratedType())) {
logger.debug("Got new generator: {} which generated: {}",
newGenerator, newGenerator.getGeneratedClass());
logger.debug("{} vs {}", (!hadTypeParameters && generatorClazz.equals(clazz)), clazz.isAssignableFrom(newGenerator.getGeneratedType()));
targetGenerators.add(newGenerator);
} else if (logger.isDebugEnabled()) {
logger.debug("New generator not assignable: {}", newGenerator);
logger.debug("Had type parameters: {}", hadTypeParameters);
logger.debug("generatorClazz.equals(clazz): {}", generatorClazz.equals(clazz));
try {
logger.debug("clazz.isAssignableFrom({}): ",newGenerator.getGeneratedType());
logger.debug(" {}",
clazz.isAssignableFrom(newGenerator.getGeneratedType()));
} catch (Throwable t) {
logger.debug("Error",t);
}
}
} catch (ConstructionFailedException e) {
logger.debug("5. ERROR", e);
}
}
// FIXME:
// There are cases where this might lead to relevant cast classes not being included
// but in manycases it will pull in large numbers of useless dependencies.
// Commented out for now, until we find a case where the problem can be properly studied.
// } else {
// logger.debug("4. generator {} CANNOT be instantiated to {}", generatorClazz, clazz);
// for(GenericClass boundClass : generatorClazz.getGenericBounds()) {
// CastClassManager.getInstance().addCastClass(boundClass, 0);
// }
}
}
logger.debug("Found generators for {}: {}",clazz, targetGenerators.size());
}
logger.debug("]");
generatorCache.put(clazz, targetGenerators);
}
/**
* Forget everything we have cached
*
* @param target
*/
public void clearGeneratorCache(GenericClass target) {
generatorCache.clear();
}
/**
* Get modifiers for instantiation of a generic type
*
* @param clazz
* @return
* @throws ConstructionFailedException
*/
private Set> determineGenericModifiersFor(
GenericClass clazz) throws ConstructionFailedException {
Set> genericModifiers = new LinkedHashSet>();
if (clazz.isParameterizedType()) {
logger.debug("Is parameterized class");
for (Entry>> entry : modifiers.entrySet()) {
logger.debug("Considering " + entry.getKey());
//if (entry.getKey().canBeInstantiatedTo(clazz)) {
if (entry.getKey().getWithWildcardTypes().isGenericSuperTypeOf(clazz)) {
logger.debug(entry.getKey() + " can be instantiated to " + clazz);
for (GenericAccessibleObject> modifier : entry.getValue()) {
try {
GenericAccessibleObject> newModifier = modifier.getGenericInstantiation(clazz);
logger.debug("Adding new modifier: " + newModifier);
genericModifiers.add(newModifier);
} catch (ConstructionFailedException e) {
// This may happen on generic methods with bounded types?
logger.debug("Cannot be added: " + modifier);
}
}
} else {
logger.debug("Nope");
}
/*
if (entry.getKey().getRawClass().equals(clazz.getRawClass())) {
logger.debug("Considering raw assignable case: " + entry.getKey());
List parameters = new ArrayList();
List targetParameters = clazz.getParameterTypes();
List modifierParameters = entry.getKey().getParameterTypes();
if (targetParameters.size() != modifierParameters.size()) {
continue;
}
boolean compatibleParameters = true;
for (int i = 0; i < targetParameters.size(); i++) {
logger.debug("Comparing target parameter "
+ targetParameters.get(i) + " with modifier parameter "
+ modifierParameters.get(i));
Type modifierType = modifierParameters.get(i);
Type targetType = targetParameters.get(i);
// FIXME: Which one is the lhs and the rhs?
// if (!TypeUtils.isAssignable(targetType, modifierType)) {
// if (!GenericClass.isAssignable(targetType, modifierType)) {
if (!GenericClass.isAssignable(modifierType, targetType)) {
compatibleParameters = false;
logger.debug("Incompatible parameter: " + targetType
+ " vs. " + modifierType);
break;
}
parameters.add(targetType);
}
if (compatibleParameters) {
logger.debug("Parameters compatible");
Type[] actualParameters = new Type[parameters.size()];
parameters.toArray(actualParameters);
GenericClass newOwner = entry.getKey().getWithParameterTypes(actualParameters);
for (GenericAccessibleObject> modifier : entry.getValue()) {
logger.debug("Considering modifier: " + modifier);
if (!modifier.getOwnerClass().isParameterizedType()) {
logger.debug("Owner class has no parameters, so we can only assume it would work: "
+ modifier);
genericModifiers.add(modifier);
} else {
// TODO: FIXXME
//if (modifier.getOwnerClass().getNumParameters() == 0) {
// logger.info("Skipping potentially problematic case of parameterized type without parameters (owner likely has types)");
// continue;
//}
GenericAccessibleObject> newModifier = modifier.copyWithOwnerFromReturnType(newOwner);
logger.debug("Modifier with new owner: " + newModifier);
if (newModifier.getOwnerClass().hasWildcardOrTypeVariables()) {
GenericClass concreteClass = newModifier.getOwnerClass().getGenericInstantiation(clazz.getTypeVariableMap());
GenericAccessibleObject> concreteNewModifier = newModifier.copyWithNewOwner(concreteClass);
logger.debug("Modifier with new owner and instantiated types: "
+ concreteNewModifier);
genericModifiers.add(concreteNewModifier);
} else {
logger.debug("Adding modifier directly");
genericModifiers.add(newModifier);
}
}
}
}
}
*/
}
} else {
logger.debug("Is NOT parameterized class!");
}
return genericModifiers;
}
/**
* @return the analyzedClasses
*
*/
@Deprecated
public Set> getAnalyzedClasses() {
return analyzedClasses;
}
/**
* Return all calls that have a parameter with given type
*
* @param clazz
* @param resolve
* @return
* @throws ConstructionFailedException
*/
public Set> getCallsFor(GenericClass clazz, boolean resolve)
throws ConstructionFailedException {
logger.debug("Getting calls for " + clazz);
if (clazz.hasWildcardOrTypeVariables()) {
logger.debug("Resolving generic type before getting modifiers");
GenericClass concreteClass = clazz.getGenericInstantiation();
if (!concreteClass.equals(clazz))
return getCallsFor(concreteClass, false);
}
if (isSpecialCase(clazz)) {
logger.debug("Getting modifiers for special case " + clazz);
return getCallsForSpecialCase(clazz);
}
logger.debug("Getting modifiers for regular case " + clazz);
if (!modifiers.containsKey(clazz)) {
return determineGenericModifiersFor(clazz);
}
return modifiers.get(clazz);
}
public GenericAccessibleObject> getRandomCallFor(GenericClass clazz, TestCase test, int position)
throws ConstructionFailedException {
Set> calls = getCallsFor(clazz, true);
Iterator> iter = calls.iterator();
while(iter.hasNext()) {
GenericAccessibleObject> gao = iter.next();
if (! ConstraintVerifier.isValidPositionForInsertion(gao,test,position)){
iter.remove();
}
}
if (calls.isEmpty()) {
throw new ConstructionFailedException("No modifiers for " + clazz);
}
logger.debug("Possible modifiers for " + clazz + ": " + calls);
GenericAccessibleObject> call = Randomness.choice(calls);
if (call.hasTypeParameters()) {
logger.debug("Modifier has type parameters");
call = call.getGenericInstantiation(clazz);
}
return call;
}
/**
* Get modifiers for special cases
*
* @param clazz
* @return
* @throws ConstructionFailedException
*/
private Set> getCallsForSpecialCase(GenericClass clazz)
throws ConstructionFailedException {
Set> all = new LinkedHashSet<>();
if (!modifiers.containsKey(clazz)) {
logger.debug("Don't have that specific class, so have to check generic modifiers");
all.addAll(determineGenericModifiersFor(clazz));
} else {
logger.debug("Got modifiers");
all.addAll(modifiers.get(clazz));
}
Set> calls = new LinkedHashSet>();
if (clazz.isAssignableTo(Collection.class)) {
for (GenericAccessibleObject> call : all) {
if (call.isConstructor() && call.getNumParameters() == 0) {
calls.add(call);
} else if (call.isMethod()
&& ((GenericMethod) call).getName().equals("add")
&& call.getNumParameters() == 1) {
calls.add(call);
} else {
if (Randomness.nextDouble() < Properties.P_SPECIAL_TYPE_CALL) {
calls.add(call);
}
}
}
}
else if (clazz.isAssignableTo(Map.class)) {
for (GenericAccessibleObject> call : all) {
if (call.isConstructor() && call.getNumParameters() == 0) {
calls.add(call);
} else if (call.isMethod()
&& ((GenericMethod) call).getName().equals("put")) {
calls.add(call);
} else {
if (Randomness.nextDouble() < Properties.P_SPECIAL_TYPE_CALL) {
calls.add(call);
}
}
}
}
else if (clazz.isAssignableTo(Number.class)) {
if (modifiers.containsKey(clazz)) {
for (GenericAccessibleObject> call : modifiers.get(clazz)) {
if (!call.getName().startsWith("java.lang")
|| Randomness.nextDouble() < Properties.P_SPECIAL_TYPE_CALL) {
calls.add(call);
}
}
}
return calls;
}
return calls;
}
/**
* Find a class that matches the given name
*
* @param name
* @return
* @throws ClassNotFoundException
*/
public Class> getClass(String name) throws ClassNotFoundException {
// First try to find exact match
for (Class> clazz : analyzedClasses) {
if (clazz.getName().equals(name)
|| clazz.getName().equals(Properties.CLASS_PREFIX + "." + name)
|| clazz.getName().equals(Properties.CLASS_PREFIX + "."
+ name.replace(".", "$"))) {
return clazz;
}
}
// Then try to match a postfix
for (Class> clazz : analyzedClasses) {
if (clazz.getName().endsWith("." + name)) {
return clazz;
}
}
// Or try java.lang
try {
TestGenerationContext.getInstance().getClassLoaderForSUT().loadClass("java.lang."
+ name);
} catch (ClassNotFoundException e) {
// Ignore it as we throw our own
}
throw new ClassNotFoundException(name);
}
/**
* Retrieve all generators
*
* @return
*/
public Set> getGenerators() {
Set> calls = new LinkedHashSet<>();
for (Set> generatorCalls : generators.values())
calls.addAll(generatorCalls);
return calls;
}
/**
* Get a list of all generator objects for the type
*
* @param clazz
* @param resolve
* @return
* @throws ConstructionFailedException
*/
public Set> getGenerators(GenericClass clazz,
boolean resolve) throws ConstructionFailedException {
// Instantiate generic type
if (clazz.hasWildcardOrTypeVariables()) {
GenericClass concreteClass = clazz.getGenericInstantiation();
if (!concreteClass.equals(clazz))
return getGenerators(concreteClass, false);
}
if (isSpecialCase(clazz)) {
return getGeneratorsForSpecialCase(clazz);
}
if (!hasGenerator(clazz))
throw new ConstructionFailedException("No generators of type " + clazz);
return generatorCache.get(clazz);
}
/**
* Predetermined generators for special cases
*
* @param clazz
* @return
* @throws ConstructionFailedException
*/
private Set> getGeneratorsForSpecialCase(GenericClass clazz)
throws ConstructionFailedException {
logger.debug("Getting generator for special case: " + clazz);
Set> calls = new LinkedHashSet>();
if (clazz.isAssignableTo(Collection.class) || clazz.isAssignableTo(Map.class)) {
Set> all = new LinkedHashSet<>();
if (!generatorCache.containsKey(clazz)) {
cacheGenerators(clazz);
}
if(!hasGenerator(clazz)) {
throw new ConstructionFailedException("No generators of type " + clazz);
}
all.addAll(generatorCache.get(clazz));
for (GenericAccessibleObject> call : all) {
// TODO: Need to instantiate, or check?
if (call.isConstructor() && call.getNumParameters() == 0) {
calls.add(call);
} else if (!Collection.class.isAssignableFrom(call.getDeclaringClass())
&& !Map.class.isAssignableFrom(call.getDeclaringClass())) {
// Methods that return collections are candidates, unless they are methods of the collections
calls.add(call);
} else if (!call.getDeclaringClass().getName().startsWith("java")) {
calls.add(call);
} else {
if (Randomness.nextDouble() < Properties.P_SPECIAL_TYPE_CALL) {
calls.add(call);
}
}
}
// This may happen e.g. for java.util.concurrent.ArrayBlockingQueue which has no default constructor
if(calls.isEmpty()) {
calls.addAll(all);
}
} else if (clazz.isAssignableTo(Number.class)) {
logger.debug("Found special case " + clazz);
Set> all = new LinkedHashSet>();
if (!generatorCache.containsKey(clazz)) {
cacheGenerators(clazz);
}
all.addAll(generatorCache.get(clazz));
if (all.isEmpty()) {
addNumericConstructor(clazz);
all.addAll(generatorCache.get(clazz));
}
for (GenericAccessibleObject> call : all) {
if (call.isConstructor() && call.getNumParameters() == 1) {
if (!((GenericConstructor) call).getRawParameterTypes()[0].equals(String.class))
calls.add(call);
} else if (call.isField()) {
calls.add(call);
} else {
if (Randomness.nextDouble() < Properties.P_SPECIAL_TYPE_CALL) {
calls.add(call);
}
}
}
logger.debug("Generators for special case " + clazz + ": " + calls);
// FIXXME: This is a workaround for the temporary workaround.
if (calls.isEmpty()) {
addNumericConstructor(clazz);
return generatorCache.get(clazz);
}
}
return calls;
}
/**
* FIXME: This is a workaround for a bug where Integer is not contained in
* the generatorCache, but there is a key. No idea how it comes to place
*
* @param clazz
*/
private void addNumericConstructor(GenericClass clazz) {
if (!generatorCache.containsKey(clazz)) {
generatorCache.put(clazz, new LinkedHashSet>());
}
if (!generators.containsKey(clazz)) {
generators.put(clazz, new LinkedHashSet>());
}
logger.info("addNumericConstructor for class " + clazz);
for (Constructor> constructor : clazz.getRawClass().getConstructors()) {
if (constructor.getParameterTypes().length == 1) {
Class> parameterClass = constructor.getParameterTypes()[0];
if (!parameterClass.equals(String.class)) {
GenericConstructor genericConstructor = new GenericConstructor(
constructor, clazz);
generatorCache.get(clazz).add(genericConstructor);
generators.get(clazz).add(genericConstructor);
}
}
}
logger.info("Constructors for class " + clazz + ": "
+ generators.get(clazz).size());
}
/**
* Retrieve all classes that match the given postfix
*
* @param name
* @return
*/
public Collection> getKnownMatchingClasses(String name) {
Set> classes = new LinkedHashSet>();
for (Class> c : analyzedClasses) {
if (c.getName().endsWith(name))
classes.add(c);
}
return classes;
}
/**
* Retrieve all modifiers
*
* @return
*/
public Set> getModifiers() {
Set> calls = new LinkedHashSet>();
for (Set> modifierCalls : modifiers.values())
calls.addAll(modifierCalls);
return calls;
}
/**
* Determine the set of generators for an Object.class instance
*
* @return a collection of all cast classes stored in {@link CastClassManager}; cannot be null
>.
*/
public Set> getObjectGenerators() {
// TODO: Use probabilities based on distance to SUT
Set> result = new LinkedHashSet<>();
List classes = new ArrayList<>(
CastClassManager.getInstance().getCastClasses());
for (GenericClass clazz : classes) {
try {
result.addAll(getGenerators(clazz, true));
} catch (ConstructionFailedException e) {
// ignore
}
}
try {
result.addAll(getGenerators(new GenericClass(Object.class), true));
} catch (ConstructionFailedException e) {
// ignore
}
return result;
}
/**
* Randomly select one generator
*
* @param clazz
* @return
* @throws ConstructionFailedException
*/
public GenericAccessibleObject> getRandomGenerator(GenericClass clazz)
throws ConstructionFailedException {
if (clazz.hasWildcardOrTypeVariables()) {
GenericClass concreteClass = clazz.getGenericInstantiation();
if(concreteClass.hasWildcardOrTypeVariables())
throw new ConstructionFailedException("Could not found concrete instantiation of generic type");
return getRandomGenerator(concreteClass);
}
GenericAccessibleObject> generator = null;
if (isSpecialCase(clazz)) {
Collection> generators = getGeneratorsForSpecialCase(clazz);
if (generators.isEmpty()) {
logger.warn("No generators for class: " + clazz);
}
generator = Randomness.choice(generators);
} else {
if (!hasGenerator(clazz))
throw new ConstructionFailedException("No generators of type " + clazz);
generator = Randomness.choice(generatorCache.get(clazz));
}
if (generator == null)
throw new ConstructionFailedException("No generators of type " + clazz);
if (generator.hasTypeParameters()) {
generator = generator.getGenericInstantiation(clazz);
}
return generator;
}
/**
* Randomly select one generator
*
* @param clazz
* @param excluded
* @param test
* @return {@code null} if there is no valid generator
* @throws ConstructionFailedException
*/
public GenericAccessibleObject> getRandomGenerator(GenericClass clazz,
Set> excluded, TestCase test, int position,
VariableReference generatorRefToExclude, int recursionDepth) throws ConstructionFailedException {
logger.debug("Getting random generator for " + clazz);
// Instantiate generics
if (clazz.hasWildcardOrTypeVariables()) {
logger.debug("Target class is generic: " + clazz);
GenericClass concreteClass = clazz.getGenericInstantiation();
if (!concreteClass.equals(clazz)) {
logger.debug("Target class is generic: " + clazz + ", getting instantiation " + concreteClass);
return getRandomGenerator(concreteClass, excluded, test, position, generatorRefToExclude, recursionDepth);
}
}
GenericAccessibleObject> generator = null;
// Collection, Map, Number
if (isSpecialCase(clazz)) {
generator = Randomness.choice(getGeneratorsForSpecialCase(clazz));
if (generator == null) {
logger.warn("No generator for special case class: " + clazz);
throw new ConstructionFailedException("Have no generators for special case: " + clazz);
}
} else {
cacheGenerators(clazz);
Set> candidates = new LinkedHashSet<>(generatorCache.get(clazz));
candidates.removeAll(excluded);
if(Properties.JEE) {
Iterator> iter = candidates.iterator();
while (iter.hasNext()) {
GenericAccessibleObject> gao = iter.next();
if (gao instanceof GenericConstructor) {
Class> klass = gao.getDeclaringClass();
if(InstanceOnlyOnce.canInstantiateOnlyOnce(klass) &&
ConstraintHelper.countNumberOfNewInstances(test, klass) != 0){
iter.remove();
}
}
if(! ConstraintVerifier.isValidPositionForInsertion(gao,test,position)){
iter.remove();
}
}
}
if(generatorRefToExclude != null){
Iterator> iter = candidates.iterator();
while (iter.hasNext()) {
GenericAccessibleObject> gao = iter.next();
//if current generator could be called from excluded ref, then we cannot use it
if(generatorRefToExclude.isAssignableTo(gao.getOwnerType())){
iter.remove();
}
}
}
logger.debug("Candidate generators for " + clazz + ": " + candidates.size());
if (candidates.isEmpty()) {
return null;
}
if(recursionDepth >= Properties.MAX_RECURSION / 2){
/*
if going long into the recursion, then do prefer direct constructors or static methods,
as non-static methods would require to get a caller which, if it is missing, would need
to be created, and that could lead to further calls if its generators need input parameters
*/
Set> set = candidates.stream()
.filter(p -> p.isStatic() || p.isConstructor())
.collect(Collectors.toCollection(() -> new LinkedHashSet>()));
if(! set.isEmpty()){
candidates = set;
}
}
generator = Randomness.choice(candidates);
logger.debug("Chosen generator: " + generator);
}
if (generator.getOwnerClass().hasWildcardOrTypeVariables()) {
logger.debug("Owner class has a wildcard: " + clazz.getTypeName());
generator = generator.copyWithNewOwner(generator.getOwnerClass().getGenericInstantiation());
}
if (generator.hasTypeParameters()) {
logger.debug("Generator has a type parameter: " + generator);
generator = generator.getGenericInstantiationFromReturnValue(clazz);
if(!generator.getGeneratedClass().isAssignableTo(clazz)) {
throw new ConstructionFailedException("Generics error");
}
}
return generator;
}
/**
* Randomly select a generator for an Object.class instance
*
* @return a generator of type GenericAccessibleObject> or null
* @throws ConstructionFailedException
*/
public GenericAccessibleObject> getRandomObjectGenerator()
throws ConstructionFailedException {
logger.debug("Getting random object generator");
GenericAccessibleObject> generator = Randomness.choice(getObjectGenerators());
if (generator == null) {
// should NOT occur (from getObjectGenerators())
logger.warn("Random object generator is null");
throw new ConstructionFailedException("Random object generator is null");
}
if (generator.getOwnerClass().hasWildcardOrTypeVariables()) {
logger.debug("Generator has wildcard or type: " + generator);
GenericClass concreteClass = generator.getOwnerClass().getGenericInstantiation();
generator = generator.copyWithNewOwner(concreteClass);
}
if (generator.hasTypeParameters()) {
logger.debug("Generator has type parameters");
generator = generator.getGenericInstantiation();
}
return generator;
}
public List> getRandomizedCallsToEnvironment(){
if(environmentMethods.isEmpty()){
return null;
}
List> list = new ArrayList<>();
for(GenericAccessibleObject> obj : environmentMethods) {
try {
if (obj.getOwnerClass().hasWildcardOrTypeVariables()) {
GenericClass concreteClass = obj.getOwnerClass().getGenericInstantiation();
obj = obj.copyWithNewOwner(concreteClass);
}
if (obj.hasTypeParameters()) {
obj = obj.getGenericInstantiation();
}
} catch (ConstructionFailedException e) {
logger.error("Failed generic instantiation in "+obj);
continue;
}
list.add(obj);
}
Collections.shuffle(list);
return list;
}
public int getNumOfEnvironmentCalls(){
return environmentMethods.size();
}
/**
* Simply check if there is any generator that gives us a SUT instance
*
* @param test
* @return
*/
private boolean doesTestHaveSUTInstance(TestCase test) {
return test.hasObject(Properties.getInitializedTargetClass(), test.size());
}
/**
* Remove all calls that are constructors
*
* @param testMethods
* @return
*/
private List> filterConstructors(List> testMethods) {
return testMethods.stream().filter(call -> !call.isConstructor()).collect(Collectors.toList());
}
private String getKey(GenericAccessibleObject> call) {
String name = call.getDeclaringClass().getCanonicalName();
if(call.isMethod()) {
GenericMethod method = (GenericMethod)call;
name += method.getNameWithDescriptor();
} else if(call.isConstructor()) {
GenericConstructor constructor = (GenericConstructor)call;
name += constructor.getNameWithDescriptor();
} else {
throw new RuntimeException("Coverage goals must be methods or constructors");
}
return name;
}
/**
* Sort by remaining uncovered goals to bias search towards most rewarding methods
*
* @param testMethods
* @return
*/
private List> sortCalls(List> testMethods) {
// TODO: This can be done more efficiently, but we're just trying to see if this makes a difference at all
Map, String> mapCallToName = new LinkedHashMap<>();
for(GenericAccessibleObject> call : testMethods) {
String name = call.getDeclaringClass().getCanonicalName();
if(call.isMethod()) {
GenericMethod method = (GenericMethod)call;
name += method.getNameWithDescriptor();
} else if(call.isConstructor()) {
GenericConstructor constructor = (GenericConstructor)call;
name += constructor.getNameWithDescriptor();
} else {
throw new RuntimeException("Coverage goals must be methods or constructors");
}
mapCallToName.put(call, name);
}
Map mapMethodToGoals = new LinkedHashMap<>();
for(String methodName : mapCallToName.values()) {
// MethodKey is class+method+desc
mapMethodToGoals.put(methodName, Archive.getArchiveInstance().getNumOfRemainingTargets(methodName));
}
return testMethods.stream().sorted(Comparator.comparingInt(item -> mapMethodToGoals.get(mapCallToName.get(item))).reversed()).collect(Collectors.toList());
}
/**
* Get random method or constructor of unit under test
*
* @return
* @throws ConstructionFailedException
*/
public GenericAccessibleObject> getRandomTestCall(TestCase test)
throws ConstructionFailedException {
List> candidateTestMethods = new ArrayList<>(testMethods);
if(candidateTestMethods.isEmpty()) {
logger.debug("No more calls");
// TODO: return null, or throw ConstructionFailedException?
return null;
}
// If test already has a SUT call, remove all constructors
if(doesTestHaveSUTInstance(test)) {
candidateTestMethods = filterConstructors(candidateTestMethods);
// It may happen that all remaining test calls are constructors. In this case it's ok.
if(candidateTestMethods.isEmpty())
candidateTestMethods = new ArrayList<>(testMethods);
}
if(Properties.SORT_CALLS) {
candidateTestMethods = sortCalls(candidateTestMethods);
}
GenericAccessibleObject> choice = Properties.SORT_CALLS ? ListUtil.selectRankBiased(candidateTestMethods) : Randomness.choice(candidateTestMethods);
logger.debug("Chosen call: " + choice);
if (choice.getOwnerClass().hasWildcardOrTypeVariables()) {
GenericClass concreteClass = choice.getOwnerClass().getGenericInstantiation();
logger.debug("Concrete class is: " + concreteClass.getTypeName());
choice = choice.copyWithNewOwner(concreteClass);
logger.debug("Concrete class is: " + choice.getOwnerClass().getTypeName());
logger.debug("Type variables: " + choice.getOwnerClass().getTypeVariableMap());
logger.debug(Arrays.asList(choice.getTypeParameters()).toString());
logger.debug("Chosen call with generic parameter set: " + choice);
logger.debug("Call owner type: " + choice.getOwnerClass().getTypeName());
}
if (choice.hasTypeParameters()) {
logger.debug("Instantiating chosen call: " + choice);
choice = choice.getGenericInstantiation();
logger.debug("Chosen instantiation: " + choice);
}
return choice;
}
public int getNumTestCalls() {
return testMethods.size();
}
/**
* Get a list of all test calls (i.e., constructors and methods)
*
* @return
* @throws ConstructionFailedException
*/
public List> getTestCalls() {
// TODO: Check for generic methods
List> result = new ArrayList<>();
for (GenericAccessibleObject> ao : testMethods) {
if (ao.getOwnerClass().hasWildcardOrTypeVariables()) {
try {
GenericClass concreteClass = ao.getOwnerClass().getGenericInstantiation();
result.add(ao.copyWithNewOwner(concreteClass));
} catch (ConstructionFailedException e) {
logger.debug("Failed to instantiate " + ao);
}
} else {
result.add(ao);
}
}
return result;
}
/**
* Determine if there are generators
*
* @param clazz
* @return
*/
public boolean hasGenerator(GenericClass clazz) {
try {
cacheGenerators(clazz);
} catch (ConstructionFailedException e) {
AtMostOnceLogger.error(logger, "Failed to check cache for "+clazz+" : "+e.getMessage());
}
if (!generatorCache.containsKey(clazz))
return false;
return !generatorCache.get(clazz).isEmpty();
}
/**
* Determine if there are generators
*
* @param type
* @return
*/
public boolean hasGenerator(Type type) {
return hasGenerator(new GenericClass(type));
}
/**
* Integrate a new class into the test cluster
*
* @param name
* @throws ClassNotFoundException
*/
public Class> importClass(String name) throws ClassNotFoundException {
throw new RuntimeException("Not implemented");
}
/**
* Some standard classes need to be treated specially to increase
* performance
*
* @param clazz
* @return
*/
private boolean isSpecialCase(GenericClass clazz) {
if(clazz.getRawClass().equals(Properties.getInitializedTargetClass()))
return false;
if (clazz.isAssignableTo(Collection.class))
return true;
if (clazz.isAssignableTo(Map.class))
return true;
if (clazz.isAssignableTo(Number.class))
return true;
return false;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("Analyzed classes:\n");
for (Class> clazz : analyzedClasses) {
result.append(clazz.getName());
result.append("\n");
}
result.append("Generators:\n");
for (GenericClass clazz : generators.keySet()) {
result.append(" Generators for " + clazz.getTypeName() + ": "
+ generators.get(clazz).size() + "\n");
for (GenericAccessibleObject> o : generators.get(clazz)) {
result.append(" " + clazz.getTypeName() + " <- " + o + " " + "\n");
}
}
result.append("Modifiers:\n");
for (GenericClass clazz : modifiers.keySet()) {
result.append(" Modifiers for " + clazz.getSimpleName() + ": "
+ modifiers.get(clazz).size() + "\n");
for (GenericAccessibleObject> o : modifiers.get(clazz)) {
result.append(" " + clazz.getSimpleName() + " <- " + o + "\n");
}
}
result.append("Test calls\n");
for (GenericAccessibleObject> testCall : testMethods) {
result.append(" " + testCall + "\n");
}
result.append("Environment calls\n");
for (GenericAccessibleObject> testCall : environmentMethods) {
result.append(" " + testCall + "\n");
}
return result.toString();
}
}