package apet.absunit;
import static abs.backend.tests.AbsASTBuilderUtil.findClassOrIfaceModifier;
import static abs.backend.tests.AbsASTBuilderUtil.getCall;
import static abs.backend.tests.AbsASTBuilderUtil.getDecl;
import static abs.backend.tests.AbsASTBuilderUtil.getExpStmt;
import static abs.backend.tests.AbsASTBuilderUtil.getThis;
import static abs.backend.tests.AbsASTBuilderUtil.getUnit;
import static abs.backend.tests.AbsASTBuilderUtil.getVAssign;
import static abs.backend.tests.AbsASTBuilderUtil.getVarDecl;
import static abs.backend.tests.AbsASTBuilderUtil.newObj;
import static apet.absunit.ABSUnitTestCaseTranslatorConstants.ASSERT_HELPER;
import static apet.absunit.ABSUnitTestCaseTranslatorConstants.NULL;
import static apet.testCases.ABSTestCaseExtractor.getABSDataType;
import static apet.testCases.ABSTestCaseExtractor.getABSDataValue;
import static apet.testCases.ABSTestCaseExtractor.getABSObjectFields;
import static apet.testCases.ABSTestCaseExtractor.getABSObjectType;
import static apet.testCases.ABSTestCaseExtractor.getABSTermArgs;
import static apet.testCases.ABSTestCaseExtractor.getAfterState;
import static apet.testCases.ABSTestCaseExtractor.getInitialState;
import static apet.testCases.ABSTestCaseExtractor.getInputArgs;
import static apet.testCases.ABSTestCaseExtractor.getPreviousCalls;
import static apet.testCases.ABSTestCaseExtractor.getReturnData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import abs.backend.tests.AbsASTBuilderUtil.DeclNamePredicate;
import abs.backend.tests.AbsASTBuilderUtil.ModifyClassModifierNamePredicate;
import abs.frontend.ast.Access;
import abs.frontend.ast.AddFieldModifier;
import abs.frontend.ast.Block;
import abs.frontend.ast.Call;
import abs.frontend.ast.ClassDecl;
import abs.frontend.ast.DataTypeUse;
import abs.frontend.ast.DeltaDecl;
import abs.frontend.ast.EqExp;
import abs.frontend.ast.Exp;
import abs.frontend.ast.FieldDecl;
import abs.frontend.ast.InterfaceDecl;
import abs.frontend.ast.InterfaceTypeUse;
import abs.frontend.ast.MethodImpl;
import abs.frontend.ast.MethodSig;
import abs.frontend.ast.Model;
import abs.frontend.ast.ModifyClassModifier;
import abs.frontend.ast.ModifyMethodModifier;
import abs.frontend.ast.ModuleModifier;
import abs.frontend.ast.NullExp;
import abs.frontend.ast.ParamDecl;
import abs.frontend.ast.PureExp;
import abs.frontend.ast.RemoveFieldModifier;
import abs.frontend.ast.Stmt;
import abs.frontend.ast.VarUse;
import apet.testCases.ABSData;
import apet.testCases.ABSObject;
import apet.testCases.ABSRef;
import apet.testCases.ABSTerm;
import apet.testCases.PreviousCall;
import apet.testCases.TestCase;
abstract class ABSUnitTestCaseBuilder {
private final HeapReferenceBuilder heapRefBuilder = new HeapReferenceBuilder();
private final TestCaseNamesBuilder testCaseNameBuilder = new TestCaseNamesBuilder();
private final PureExpressionBuilder pureExpBuilder;
private final DeltaForGetSetFieldsBuilder deltaBuilder;
private final Model model;
ABSUnitTestCaseBuilder(PureExpressionBuilder pureExpBuilder,
DeltaForGetSetFieldsBuilder deltaBuilder,
Model model) {
this.pureExpBuilder = pureExpBuilder;
this.deltaBuilder = deltaBuilder;
this.model = model;
}
Set<String> referenceNames(Set<ABSRef> refs) {
Set<String> names = new HashSet<String>();
for (ABSRef r : refs) {
names.add(getABSDataValue(r));
}
return names;
}
Map<String, InterfaceTypeUse> getTypesFromABSObject(
String testName, ABSObject obj) {
Map<String, InterfaceTypeUse> map = new HashMap<String, InterfaceTypeUse>();
for (ABSData data : getABSObjectFields(obj).values()) {
map.putAll(getTypesFromABSData(testName, data));
}
return map;
}
Map<String, InterfaceTypeUse> getTypesFromABSData(String testName, ABSData data) {
Map<String, InterfaceTypeUse> map = new HashMap<String, InterfaceTypeUse>();
if (data instanceof ABSRef) {
String type = getABSDataType(data);
String value = getABSDataValue(data);
if (! value.equals(NULL)) {
map.put(heapRefBuilder.heapReferenceForTest(testName, value),
new InterfaceTypeUse(type, new abs.frontend.ast.List<abs.frontend.ast.Annotation>()));
}
} else if (data instanceof ABSTerm) {
ABSTerm term = (ABSTerm) data;
for (ABSData t : getABSTermArgs(term)) {
map.putAll(getTypesFromABSData(testName, t));
}
}
return map;
}
/**
*
* @param testCase
* @param testClass
* @param method
* @param access
* @param unitUnderTest
*/
void buildTestCase(TestCase testCase, ClassDecl testClass,
MethodImpl method, Access access, String unitUnderTest) {
//initial arg
List<ABSData> inputArguments = getInputArgs(testCase);
String testName = method.getMethodSig().getName();
Block block = method.getBlock();
Map<String,InterfaceTypeUse> typesOfObjectInHeap = new HashMap<String, InterfaceTypeUse>();
for (ABSData d : inputArguments) {
typesOfObjectInHeap.putAll(getTypesFromABSData(testName, d));
}
Map<ABSRef,ABSObject> initial = getInitialState(testCase);
for (ABSObject obj : initial.values()) {
typesOfObjectInHeap.putAll(getTypesFromABSObject(testName, obj));
}
Set<String> initialHeapNames = referenceNames(initial.keySet());
createObjectsInHeap(testName, initialHeapNames, typesOfObjectInHeap,
testClass, initial, block);
List<PreviousCall> calls = getPreviousCalls(testCase);
List<Exp> previous = makePreviousCalls(testName, initialHeapNames, calls);
for (Exp pc : previous) {
block.addStmtNoTransform(getExpStmt(pc)); //does not care about return value
}
//test execution
Exp test = makeTestExecution(testName, initialHeapNames, unitUnderTest, inputArguments);
final boolean hasReturnValue;
if (access instanceof DataTypeUse &&
((DataTypeUse) access).getName().equals("Unit")) {
block.addStmtNoTransform(getExpStmt(test)); //no return value
hasReturnValue = false;
} else {
block.addStmtNoTransform(getVarDecl("returnValue", access.treeCopyNoTransform(), test));
hasReturnValue = true;
}
Map<ABSRef,ABSObject> finalHeap = getAfterState(testCase);
if (finalHeap.isEmpty()) {
//the method under test is side-effect free?
//use the initial heap as oracle
finalHeap = initial;
}
Set<String> finalHeapNames = referenceNames(finalHeap.keySet());
//need to remember which objects in the heap we have already handled.
Set<String> visited = new HashSet<String>();
//check return value
//only look at reference and data values
//assertions of object states can be done in the heap assertions
if (hasReturnValue) {
ABSData rd = getReturnData(testCase);
PureExp exp = pureExpBuilder.createPureExpression(testName, finalHeapNames, rd);
block.addStmtNoTransform(getExpStmt(getCall(
new VarUse(ASSERT_HELPER), "assertTrue", true,
new EqExp(new VarUse("returnValue"), exp))));
}
//check return value (using deltas)
makeGetAndAssertStatements(testName, finalHeapNames, testClass, finalHeap, visited, block);
}
/**
* Initialise (create if necessary) a delta to modify the given test class.
* In particular it ensures the delta contains a class modifier for the
* given test class and within that modifier, a method modifier for the given
* method name.
*
* @param testClass
* @param setOrAssertMethodForTest
* @return the method block of the method modifier.
*/
Block initialiseDeltaForTestClass(ClassDecl testClass,
String setOrAssertMethodForTest) {
String testClassName = testClass.getName();
DeltaDecl delta = deltaBuilder.getDeltaFor(testClassName);
if (delta == null) {
delta = deltaBuilder.createDeltaFor(testClass);
}
ModifyClassModifier modifier =
findClassOrIfaceModifier(delta, ModifyClassModifier.class,
new ModifyClassModifierNamePredicate(testClassName));
if (modifier == null) {
modifier = new ModifyClassModifier();
modifier.setName(testClassName);
delta.addModuleModifier(modifier);
}
MethodSig sig = new MethodSig();
sig.setName(setOrAssertMethodForTest);
sig.setReturnType(getUnit());
//add an empty method to be modified
MethodImpl setOrAssertMethodForObjectImpl = new MethodImpl(sig, new Block(), false);
testClass.addMethod(setOrAssertMethodForObjectImpl);
ModifyMethodModifier mmm = new ModifyMethodModifier(
setOrAssertMethodForObjectImpl.treeCopyNoTransform());
Block modifyBlock = mmm.getMethodImpl().getBlock();
modifier.addModifier(mmm);
return modifyBlock;
}
void createObjectsInHeap(
String testMethodName,
Set<String> heapNames,
Map<String,InterfaceTypeUse> objectsInHeap,
ClassDecl testClass,
Map<ABSRef,ABSObject> initialHeap,
Block testMethodBlock) {
String setMethodForTest =
testCaseNameBuilder.initialTestMethodName(testMethodName);
Block modifyBlock = initialiseDeltaForTestClass(testClass,
testCaseNameBuilder.initialTestMethodName(testMethodName));
testMethodBlock.addStmtNoTransform(
getExpStmt(getCall(getThis(), setMethodForTest, true)));
Map<String, String> typeHierarchy = new HashMap<String, String>();
Map<String, List<Stmt>> initialisations = new HashMap<String, List<Stmt>>();
List<String> initialisationsOrders = new ArrayList<String>();
for (ABSRef r : initialHeap.keySet()) {
makeSetStatements(
typeHierarchy,
initialisations,
initialisationsOrders,
testMethodName, heapNames, initialHeap,
objectsInHeap, r, initialHeap.get(r),
testClass);
}
for (String ref : initialisationsOrders) {
for (Stmt s : initialisations.get(ref)) {
modifyBlock.addStmtNoTransform(s);
}
}
String testClassName = testClass.getName();
DeltaDecl delta = deltaBuilder.getDeltaFor(testClassName);
ModifyClassModifier cm = null;
for (ModuleModifier m : delta.getModuleModifiers()) {
if (m.getName().equals(testClassName)) {
cm = (ModifyClassModifier) m;
break;
}
}
for (String r : objectsInHeap.keySet()) {
FieldDecl field = new FieldDecl();
field.setName(r);
InterfaceTypeUse inf = objectsInHeap.get(r);
field.setAccess(inf);
testClass.addField(field);
//allow access of subtype information
cm.addModifier(new RemoveFieldModifier(field.treeCopyNoTransform()));
FieldDecl newField = new FieldDecl();
newField.setName(r);
newField.setAccess(new InterfaceTypeUse(typeHierarchy.get(inf.getName()), new abs.frontend.ast.List<abs.frontend.ast.Annotation>()));
cm.addModifier(new AddFieldModifier(newField));
}
}
void makeSetStatements(
Map<String, String> typeHierarchy,
Map<String, List<Stmt>> initialisations,
List<String> initialisationsOrders,
String testName,
Set<String> heapNames,
Map<ABSRef, ABSObject> initialHeap,
Map<String, InterfaceTypeUse> objectsInHeap,
ABSRef ref, ABSObject state,
ClassDecl testClass) {
String rn = heapRefBuilder.heapReferenceForTest(testName, getABSDataValue(ref));
String concreteTypeName = getABSObjectType(state);
ClassDecl concreteType =
getDecl(model, ClassDecl.class,
new DeclNamePredicate<ClassDecl>(concreteTypeName));
if (concreteType == null) {
throw new IllegalStateException("Cannot find class: "+concreteTypeName);
}
List<Stmt> statements = new ArrayList<Stmt>();
initialisations.put(rn, statements);
if (! initialisationsOrders.contains(rn)) {
initialisationsOrders.add(rn);
}
Map<String,ABSData> fields = getABSObjectFields(state);
abs.frontend.ast.List<ParamDecl> params = concreteType.getParamList();
PureExp[] constructorArgs = new PureExp[params.getNumChild()];
for (int i=0; i < params.getNumChild(); i++) {
ParamDecl param = params.getChild(i);
String name = param.getName();
assert fields.containsKey(name);
ABSData d = fields.remove(name);
objectsInHeap.putAll(getTypesFromABSData(testName, d));
PureExp exp = pureExpBuilder.createPureExpression(rn, initialisationsOrders, testName, heapNames, d);
constructorArgs[i] = exp;
}
statements.add(getVAssign(rn, newObj(concreteType, false, constructorArgs)));
for (String fn : fields.keySet()) {
ABSData d = fields.get(fn);
objectsInHeap.putAll(getTypesFromABSData(testName, d));
PureExp exp = pureExpBuilder.createPureExpression(rn, initialisationsOrders, testName, heapNames, d);
Call call = getCall(new VarUse(rn), testCaseNameBuilder.setterMethodName(fn), true, exp);
statements.add(getExpStmt(call));
}
//ADD getter and setter
deltaBuilder.updateDelta(
typeHierarchy,
objectsInHeap.get(rn),
getDecl(model, ClassDecl.class,
new DeclNamePredicate<ClassDecl>(concreteTypeName)));
}
void makeGetAndAssertStatements(
String testMethodName,
Set<String> heapNames,
ClassDecl testClass,
Map<ABSRef,ABSObject> finalHeap,
Set<String> visited,
Block testMethodBlock) {
String assertMethodForTest =
testCaseNameBuilder.assertTestMethodName(testMethodName);
Block modifyBlock = initialiseDeltaForTestClass(testClass, assertMethodForTest);
testMethodBlock.addStmtNoTransform(
getExpStmt(getCall(getThis(), assertMethodForTest, true)));
for (ABSRef r : finalHeap.keySet()) {
makeGetAndAssertStatementsForHeapRef(testMethodName, heapNames, finalHeap, r, finalHeap.get(r), visited, modifyBlock);
}
}
void makeGetAndAssertStatementsForHeapRef(
String testName,
Set<String> heapNames,
Map<ABSRef, ABSObject> finalHeap,
ABSRef ref,
ABSObject state,
Set<String> visited,
Block block) {
String rn = heapRefBuilder.heapReferenceForTest(testName, getABSDataValue(ref));
if (! visited.add(rn)) {
return;
}
Map<String,ABSData> fields = getABSObjectFields(state);
ClassDecl clazz =
getDecl(model, ClassDecl.class,
new DeclNamePredicate<ClassDecl>(getABSObjectType(state)));
abs.frontend.ast.List<FieldDecl> fieldDecls = clazz.getFieldList();
for (int i=0; i<fieldDecls.getNumChild(); i++) {
FieldDecl field = fieldDecls.getChild(i);
String fn = field.getName();
if (fields.containsKey(fn)) {
ABSData d = fields.get(fn);
block.addStmtNoTransform(getVarDecl(testCaseNameBuilder.resultOfGetterMethodName(fn),
field.getAccess().treeCopyNoTransform(),
getCall(new VarUse(rn), testCaseNameBuilder.getterMethodName(fn), true)));
makeOracle(testName, heapNames, finalHeap,
testCaseNameBuilder.resultOfGetterMethodName(fn),
field.getAccess().treeCopyNoTransform(), d, visited, block);
}
}
}
void makeOracle(String testName, Set<String> heapNames,
Map<ABSRef,ABSObject> finalHeap,
String actual, Access access, ABSData data, Set<String> visited, Block block) {
InterfaceDecl inf = null;
if (access instanceof DataTypeUse) {
inf = getDecl(model, InterfaceDecl.class,
new DeclNamePredicate<InterfaceDecl>(((DataTypeUse) access).getName()));
}
PureExp exp = pureExpBuilder.createPureExpression(testName, heapNames, data);
block.addStmtNoTransform(getExpStmt(getCall(
new VarUse(ASSERT_HELPER), "assertTrue", true,
new EqExp(new VarUse(actual), exp))));
if (inf != null && ! (exp instanceof NullExp)) {
String ref = getABSDataValue(data);
for (ABSRef r : finalHeap.keySet()) {
if (getABSDataValue(r).equals(ref)) {
makeGetAndAssertStatementsForHeapRef(
testName, heapNames, finalHeap, r, finalHeap.get(r), visited, block);
break;
}
}
}
}
abstract List<Exp> makePreviousCalls(String testName, Set<String> heapNames, List<PreviousCall> calls);
abstract Exp makeTestExecution(String testName, Set<String> heap,
String functionName, List<ABSData> inArgs);
}