/**
* 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.junit;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.evosuite.testcase.AbstractStatement;
import org.evosuite.testcase.AssignmentStatement;
import org.evosuite.testcase.DefaultTestCase;
import org.evosuite.testcase.PrimitiveExpression;
import org.evosuite.testcase.Scope;
import org.evosuite.testcase.StatementInterface;
import org.evosuite.testcase.TestCase;
import org.evosuite.testcase.VariableReference;
import org.evosuite.utils.GenericAccessibleObject;
import org.objectweb.asm.commons.GeneratorAdapter;
/**
* A compound test case is a test case that is read from an existing JUnit test
* file, and may have a complex structure. It contains static code sections,
* fields, constructors, @BeforeClass, @Before, @After, @AfterClass annotated
* methods and possibly a class hierarchy. A CompoundTestCase is used to gather
* all those statements to eventually combine them into a normal
* {@link TestCase} when {@link #finalizeTestCase()} is called.
*
* @author roessler
*/
public class CompoundTestCase implements Serializable {
public static class MethodDef {
private final String name;
private final List<VariableReference> params = new ArrayList<VariableReference>();
final List<StatementInterface> code = new ArrayList<StatementInterface>();
public MethodDef(String name) {
super();
this.name = name;
}
public void add(StatementInterface statement) {
code.add(statement);
}
public List<StatementInterface> getCode() {
return code;
}
public String getName() {
return name;
}
public List<VariableReference> getParams() {
return params;
}
@Override
public String toString() {
return name;
}
}
public static class ReturnStatementPlaceholder extends AbstractStatement {
private static final long serialVersionUID = 1L;
protected ReturnStatementPlaceholder(TestCase tc, VariableReference returnValue) {
super(tc, returnValue.getType());
retval = returnValue;
}
@Override
public StatementInterface copy(TestCase newTestCase, int offset) {
throw new UnsupportedOperationException("Method copy not implemented!");
}
@Override
public Throwable execute(Scope scope, PrintStream out)
throws InvocationTargetException, IllegalArgumentException,
IllegalAccessException, InstantiationException {
throw new UnsupportedOperationException("Method execute not implemented!");
}
@Override
public GenericAccessibleObject getAccessibleObject() {
throw new UnsupportedOperationException(
"Method getAccessibleObject not implemented!");
}
@Override
public void getBytecode(GeneratorAdapter mg, Map<Integer, Integer> locals,
Throwable exception) {
throw new UnsupportedOperationException("Method getBytecode not implemented!");
}
@Override
public List<VariableReference> getUniqueVariableReferences() {
throw new UnsupportedOperationException(
"Method getUniqueVariableReferences not implemented!");
}
@Override
public Set<VariableReference> getVariableReferences() {
throw new UnsupportedOperationException(
"Method getVariableReferences not implemented!");
}
@Override
public boolean isAssignmentStatement() {
return false;
}
@Override
public void replace(VariableReference oldVar, VariableReference newVar) {
if (retval.equals(oldVar)) {
retval = newVar;
}
}
@Override
public boolean same(StatementInterface s) {
throw new UnsupportedOperationException("Method same not implemented!");
}
}
public static enum TestScope {
BEFORE_CLASS,
STATIC,
STATICFIELDS,
BEFORE,
FIELDS,
CONSTRUCTOR,
AFTER,
AFTER_CLASS,
METHOD;
}
/** Constant <code>STATIC_BLOCK_METHODNAME="<static block>"</code> */
public static final String STATIC_BLOCK_METHODNAME = "<static block>";
private static final long serialVersionUID = 1L;
private final Map<String, MethodDef> methodDefs = new LinkedHashMap<String, MethodDef>();
private final Map<String, VariableReference> currentMethodVars = new HashMap<String, VariableReference>();
private final Map<String, VariableReference> fieldVars = new HashMap<String, VariableReference>();
private final List<MethodDef> constructors = new ArrayList<MethodDef>();
private final List<MethodDef> afterMethods = new ArrayList<MethodDef>();
private final List<MethodDef> afterClassMethods = new ArrayList<MethodDef>();
private final List<MethodDef> beforeMethods = new ArrayList<MethodDef>();
private final List<MethodDef> beforeClassMethods = new ArrayList<MethodDef>();
private final List<StatementInterface> staticFields = new ArrayList<StatementInterface>();
private final List<StatementInterface> fields = new ArrayList<StatementInterface>();
private final List<StatementInterface> staticCode = new ArrayList<StatementInterface>();
private final String className;
private final String testMethod;
// find here or up the hierarchy
private CompoundTestCase parent;
// Needed for methods and fields:
// find method in actual class or up the hierarchy
private final CompoundTestCase originalDescendant;
private TestScope currentScope = TestScope.FIELDS;
private MethodDef currentMethod = null;
private final DelegatingTestCase delegate;
/**
* <p>
* Constructor for CompoundTestCase.
* </p>
*
* @param className
* a {@link java.lang.String} object.
* @param child
* a {@link org.evosuite.junit.CompoundTestCase} object.
*/
public CompoundTestCase(String className, CompoundTestCase child) {
child.parent = this;
delegate = child.getReference();
originalDescendant = child.originalDescendant;
this.testMethod = null;
this.className = className;
}
/**
* <p>
* Constructor for CompoundTestCase.
* </p>
*
* @param className
* a {@link java.lang.String} object.
* @param methodName
* a {@link java.lang.String} object.
*/
public CompoundTestCase(String className, String methodName) {
this.testMethod = methodName;
this.className = className;
delegate = new DelegatingTestCase();
originalDescendant = this;
}
/**
* <p>
* addParameter
* </p>
*
* @param varRef
* a {@link org.evosuite.testcase.VariableReference} object.
*/
public void addParameter(VariableReference varRef) {
currentMethod.getParams().add(varRef);
}
/**
* <p>
* addStatement
* </p>
*
* @param statement
* a {@link org.evosuite.testcase.StatementInterface} object.
*/
public void addStatement(StatementInterface statement) {
if (currentScope == TestScope.FIELDS) {
fields.add(statement);
return;
}
if (currentScope == TestScope.STATICFIELDS) {
staticFields.add(statement);
return;
}
if (currentScope == TestScope.STATIC) {
staticCode.add(statement);
return;
}
currentMethod.add(statement);
}
/**
* <p>
* addVariable
* </p>
*
* @param varBinding
* a {@link org.eclipse.jdt.core.dom.IVariableBinding} object.
* @param varRef
* a {@link org.evosuite.testcase.VariableReference} object.
*/
public void addVariable(IVariableBinding varBinding, VariableReference varRef) {
if ((currentScope == TestScope.FIELDS)
|| (currentScope == TestScope.STATICFIELDS)) {
fieldVars.put(varBinding.toString(), varRef);
return;
}
currentMethodVars.put(varBinding.toString(), varRef);
}
/**
* <p>
* convertMethod
* </p>
*
* @param methodDef
* a {@link org.evosuite.junit.CompoundTestCase.MethodDef}
* object.
* @param params
* a {@link java.util.List} object.
* @param retVal
* a {@link org.evosuite.testcase.VariableReference} object.
*/
public void convertMethod(MethodDef methodDef, List<VariableReference> params,
VariableReference retVal) {
assert methodDef.getParams().size() == params.size();
Map<VariableReference, VariableReference> methodVarsMap = new HashMap<VariableReference, VariableReference>();
for (StatementInterface statement : methodDef.getCode()) {
for (int idx = 0; idx < params.size(); idx++) {
statement.replace(methodDef.getParams().get(idx), params.get(idx));
}
if (statement instanceof ReturnStatementPlaceholder) {
VariableReference resultVal = methodVarsMap.get(statement.getReturnValue());
if (resultVal == null) {
throw new IllegalStateException();
}
AssignmentStatement assignmentStatement = new AssignmentStatement(
delegate, retVal, resultVal);
addStatement(assignmentStatement);
return;
}
StatementInterface newStmt = statement;
if (!(statement instanceof PrimitiveExpression)) {
// Since the delegate code is not yet finished,
// cloning of PrimitiveExpressions does not work.
newStmt = statement.clone(delegate);
}
addReplacementVariable(statement.getReturnValue(), newStmt.getReturnValue());
methodVarsMap.put(statement.getReturnValue(), newStmt.getReturnValue());
addStatement(newStmt);
}
}
/**
* <p>
* discardMethod
* </p>
*/
public void discardMethod() {
currentScope = TestScope.FIELDS;
currentMethod = null;
currentMethodVars.clear();
}
/**
* <p>
* finalizeMethod
* </p>
*/
public void finalizeMethod() {
String currentMethodName = currentMethod.getName();
methodDefs.put(currentMethodName, currentMethod);
switch (currentScope) {
case AFTER:
afterMethods.add(currentMethod);
break;
case AFTER_CLASS:
afterClassMethods.add(currentMethod);
break;
case BEFORE:
beforeMethods.add(currentMethod);
break;
case BEFORE_CLASS:
beforeClassMethods.add(currentMethod);
break;
case CONSTRUCTOR:
constructors.add(currentMethod);
break;
default:
break;
}
currentScope = TestScope.FIELDS;
currentMethod = null;
currentMethodVars.clear();
}
/**
* <p>
* finalizeTestCase
* </p>
*
* @return a {@link org.evosuite.testcase.TestCase} object.
*/
public TestCase finalizeTestCase() {
Set<String> overridenMethods = Collections.emptySet();
delegate.setDelegate(new DefaultTestCase());
delegate.addStatements(getStaticInitializationBeforeClassMethods(overridenMethods));
delegate.addStatements(getInitializationCode());
delegate.addStatements(getBeforeMethods(overridenMethods));
if (testMethod == null) {
throw new RuntimeException("Test did not contain any statements!");
}
if (methodDefs.get(testMethod) == null) {
throw new RuntimeException("Error reading test method " + testMethod + "!");
}
delegate.addStatements(methodDefs.get(testMethod).getCode());
delegate.addStatements(getAfterMethods(overridenMethods));
delegate.addStatements(getAfterClassMethods(overridenMethods));
assert delegate.clone().toCode().equals(delegate.toCode());
return delegate;
}
/**
* <p>
* Getter for the field <code>className</code>.
* </p>
*
* @return a {@link java.lang.String} object.
*/
public String getClassName() {
return className;
}
/**
* <p>
* Getter for the field <code>currentMethod</code>.
* </p>
*
* @return a {@link java.lang.Object} object.
*/
public Object getCurrentMethod() {
return currentMethod.getName();
}
/**
* <p>
* Getter for the field <code>currentScope</code>.
* </p>
*
* @return a {@link org.evosuite.junit.CompoundTestCase.TestScope} object.
*/
public TestScope getCurrentScope() {
return currentScope;
}
/**
* <p>
* getLastStatement
* </p>
*
* @return a {@link org.evosuite.testcase.StatementInterface} object.
*/
public StatementInterface getLastStatement() {
assert currentScope == TestScope.METHOD;
return currentMethod.code.get(currentMethod.code.size() - 1);
}
/**
* <p>
* getMethod
* </p>
*
* @param name
* a {@link java.lang.String} object.
* @return a {@link org.evosuite.junit.CompoundTestCase.MethodDef} object.
*/
public MethodDef getMethod(String name) {
if (originalDescendant != this) {
return originalDescendant.getMethodInternally(name);
}
return getMethodInternally(name);
}
/**
* <p>
* Getter for the field <code>parent</code>.
* </p>
*
* @return a {@link org.evosuite.junit.CompoundTestCase} object.
*/
public CompoundTestCase getParent() {
return parent;
}
/**
* <p>
* getReference
* </p>
*
* @return a {@link org.evosuite.junit.DelegatingTestCase} object.
*/
public DelegatingTestCase getReference() {
return delegate;
}
/**
* <p>
* getVariableReference
* </p>
*
* @param varBinding
* a {@link org.eclipse.jdt.core.dom.IVariableBinding} object.
* @return a {@link org.evosuite.testcase.VariableReference} object.
*/
public VariableReference getVariableReference(IVariableBinding varBinding) {
if (originalDescendant != this) {
return originalDescendant.getVariableReferenceInternally(varBinding);
}
return getVariableReferenceInternally(varBinding);
}
/**
* <p>
* isDescendantOf
* </p>
*
* @param declaringClass
* a {@link java.lang.Class} object.
* @return a boolean.
*/
public boolean isDescendantOf(Class<?> declaringClass) {
if (parent == null) {
return false;
}
if (parent.className.equals(declaringClass.getName())) {
return true;
}
return parent.isDescendantOf(declaringClass);
}
/**
* <p>
* newMethod
* </p>
*
* @param methodName
* a {@link java.lang.String} object.
*/
public void newMethod(String methodName) {
assert (currentMethod == null) || currentMethod.getCode().isEmpty();
assert currentMethodVars.isEmpty();
currentScope = TestScope.METHOD;
currentMethod = new MethodDef(methodName);
}
/**
* <p>
* Setter for the field <code>currentScope</code>.
* </p>
*
* @param scope
* a {@link org.evosuite.junit.CompoundTestCase.TestScope}
* object.
*/
public void setCurrentScope(TestScope scope) {
this.currentScope = scope;
}
/** {@inheritDoc} */
@Override
public String toString() {
return className;
}
/**
* <p>
* variableAssignment
* </p>
*
* @param varRef
* a {@link org.evosuite.testcase.VariableReference} object.
* @param newAssignment
* a {@link org.evosuite.testcase.VariableReference} object.
*/
public void variableAssignment(VariableReference varRef,
VariableReference newAssignment) {
for (Map.Entry<String, VariableReference> entry : currentMethodVars.entrySet()) {
if (entry.getValue() == varRef) {
currentMethodVars.put(entry.getKey(), newAssignment);
return;
}
}
for (Map.Entry<String, VariableReference> entry : fieldVars.entrySet()) {
if (entry.getValue() == varRef) {
fieldVars.put(entry.getKey(), newAssignment);
return;
}
}
if (parent != null) {
for (Map.Entry<String, VariableReference> entry : parent.fieldVars.entrySet()) {
if (entry.getValue() == varRef) {
parent.fieldVars.put(entry.getKey(), newAssignment);
return;
}
}
}
throw new RuntimeException("Assignment " + varRef + " not found!");
}
private void addReplacementVariable(VariableReference oldValue,
VariableReference newValue) {
String variable = getVariableFromCurrentScope(oldValue);
fieldVars.put(variable, newValue);
}
private List<StatementInterface> getAfterClassMethods(Set<String> overridenMethods) {
List<StatementInterface> result = new ArrayList<StatementInterface>();
for (MethodDef methodDef : afterClassMethods) {
if (!overridenMethods.contains(methodDef.getName())) {
result.addAll(methodDef.getCode());
}
}
if (parent != null) {
// @AfterClass IF NOT OVERRIDEN
// According to Kent Beck, there is no defined order
// in which methods of the same leve within one class are called:
// http://tech.groups.yahoo.com/group/junit/message/20758
result.addAll(parent.getAfterClassMethods(methodDefs.keySet()));
}
return result;
}
private List<StatementInterface> getAfterMethods(Set<String> overridenMethods) {
List<StatementInterface> result = new ArrayList<StatementInterface>();
// @After
for (MethodDef methodDef : afterMethods) {
if (!overridenMethods.contains(methodDef.getName())) {
result.addAll(methodDef.getCode());
}
}
if (parent != null) {
// parent: @After
result.addAll(parent.getAfterMethods(methodDefs.keySet()));
}
return result;
}
private List<StatementInterface> getBeforeMethods(Set<String> overridenMethods) {
List<StatementInterface> result = new ArrayList<StatementInterface>();
if (parent != null) {
result.addAll(parent.getBeforeMethods(methodDefs.keySet()));
}
// @Before IF NOT OVERRIDEN
// According to Kent Beck, there is no defined order
// in which methods of the same leve within one class are called:
// http://tech.groups.yahoo.com/group/junit/message/20758
for (MethodDef methodDef : beforeMethods) {
if (!overridenMethods.contains(methodDef.getName())) {
result.addAll(methodDef.getCode());
}
}
return result;
}
private List<StatementInterface> getInitializationCode() {
List<StatementInterface> result = new ArrayList<StatementInterface>();
if (parent != null) {
result.addAll(parent.getInitializationCode());
}
// initialization
result.addAll(fields);
// constructor
result.addAll(getNoArgsConstructor());
return result;
}
private MethodDef getMethodInternally(String name) {
MethodDef result = methodDefs.get(name);
if (result != null) {
return result;
}
if (parent != null) {
return parent.getMethodInternally(name);
}
throw new RuntimeException("Method " + name + " not found!");
}
private List<StatementInterface> getNoArgsConstructor() {
for (MethodDef constructor : constructors) {
if (constructor.getParams().isEmpty()) {
return constructor.getCode();
}
}
if (constructors.size() > 1) {
throw new RuntimeException("Found " + constructors.size()
+ " constructors, but on no-args constructor!");
}
return Collections.emptyList();
}
private List<StatementInterface> getStaticInitializationBeforeClassMethods(
Set<String> overridenMethods) {
List<StatementInterface> result = new ArrayList<StatementInterface>();
if (parent != null) {
// @BeforeClass IF NOT OVERRIDEN
// According to Kent Beck, there is no defined order
// in which methods of the same leve within one class are called:
// http://tech.groups.yahoo.com/group/junit/message/20758
result.addAll(parent.getStaticInitializationBeforeClassMethods(methodDefs.keySet()));
}
// this: static initialization
result.addAll(staticFields);
// static blocks
result.addAll(staticCode);
// @BeforeClass methods
for (MethodDef methodDef : beforeClassMethods) {
if (!overridenMethods.contains(methodDef.getName())) {
result.addAll(methodDef.getCode());
}
}
return result;
}
private String getVariableFromCurrentScope(VariableReference varRef) {
if ((currentScope != TestScope.FIELDS)
&& (currentScope != TestScope.STATICFIELDS)) {
for (Map.Entry<String, VariableReference> entry : currentMethodVars.entrySet()) {
if (entry.getValue().equals(varRef)) {
return entry.getKey();
}
}
}
for (Map.Entry<String, VariableReference> entry : fieldVars.entrySet()) {
if (entry.getValue().equals(varRef)) {
return entry.getKey();
}
}
if (parent != null) {
return parent.getVariableFromCurrentScope(varRef);
}
return null;
}
private VariableReference getVariableReferenceInternally(IVariableBinding varBinding) {
VariableReference varRef = currentMethodVars.get(varBinding.toString());
if (varRef != null) {
return varRef;
}
varRef = fieldVars.get(varBinding.toString());
if (varRef != null) {
return varRef;
}
if (parent != null) {
return parent.getVariableReferenceInternally(varBinding);
}
return null;
}
}