package apet.absunit; import static abs.backend.tests.AbsASTBuilderUtil.findMethodImpl; import static abs.backend.tests.AbsASTBuilderUtil.findMethodSig; import static abs.backend.tests.AbsASTBuilderUtil.getDecl; import static apet.absunit.ABSUnitTestCaseTranslatorConstants.CONFIGURATION_NAME; import static apet.absunit.ABSUnitTestCaseTranslatorConstants.FEATURE_NAME; import static apet.absunit.ABSUnitTestCaseTranslatorConstants.MAIN; import static apet.absunit.ABSUnitTestCaseTranslatorConstants.PRODUCT_NAME; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import abs.backend.prettyprint.DefaultABSFormatter; import abs.backend.tests.AbsASTBuilderUtil; import abs.backend.tests.AbsASTBuilderUtil.DeclNamePredicate; import abs.backend.tests.AbsASTBuilderUtil.MethodNamePredicate; import abs.backend.tests.AbsASTBuilderUtil.MethodSigNamePredicate; import abs.backend.tests.AbsASTBuilderUtil.Predicate; import abs.common.StringUtils; import abs.frontend.analyser.SemanticCondition; import abs.frontend.analyser.SemanticConditionList; import abs.frontend.ast.ASTNode; import abs.frontend.ast.Access; import abs.frontend.ast.AppCond; import abs.frontend.ast.AppCondFeature; import abs.frontend.ast.ClassDecl; import abs.frontend.ast.CompilationUnit; import abs.frontend.ast.DeltaAccess; import abs.frontend.ast.DeltaClause; import abs.frontend.ast.DeltaDecl; import abs.frontend.ast.DeltaID; import abs.frontend.ast.Deltaspec; import abs.frontend.ast.Feature; import abs.frontend.ast.FunctionDecl; 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.ModuleDecl; import abs.frontend.ast.ProductDecl; import abs.frontend.ast.ProductFeatureSet; import abs.frontend.ast.ProductLine; import abs.frontend.ast.StarExport; import abs.frontend.ast.StarImport; import abs.frontend.tests.ABSFormatter; import apet.absunit.DeltaForGetSetFieldsBuilder.DeltaWrapper; import apet.console.ConsoleHandler; import apet.testCases.ApetTestSuite; import apet.testCases.TestCase; /** * * @author pwong * */ public class ABSUnitTestCaseTranslator { private final Model model; private final ModuleDecl module; private final Set<DeltaWrapper> deltas; private final Set<String> importModules; private ProductLine productline; private ProductDecl product; private File outputFile; private final boolean verbose; private final TestCaseNamesBuilder testCaseNameBuilder = new TestCaseNamesBuilder(); private final ABSUnitTestCaseTranslatorHelper translatorHelper = new ABSUnitTestCaseTranslatorHelper(); private final PureExpressionBuilder pureExpBuilder; private final DeltaForGetSetFieldsBuilder deltaBuilder; private final MethodTestCaseBuilder methodBuilder; private final FunctionTestCaseBuilder functionBuilder; public ABSUnitTestCaseTranslator(Model model, File outputFile, boolean verbose) { if (model == null) throw new IllegalArgumentException("Model cannot be null!"); this.model = model; this.module = new ModuleDecl(); this.module.setName(MAIN); this.deltas = new HashSet<DeltaWrapper>(); this.importModules = new HashSet<String>(); this.pureExpBuilder = new PureExpressionBuilder(this.model, importModules); this.deltaBuilder = new DeltaForGetSetFieldsBuilder(deltas); this.methodBuilder = new MethodTestCaseBuilder(pureExpBuilder, deltaBuilder, this.model); this.functionBuilder = new FunctionTestCaseBuilder(pureExpBuilder, deltaBuilder, this.model); this.verbose = verbose; console("Gathering ABSUnit annotations"); /* * Do not search for test class definitions if this model does not * contain the necessary ABSUnit annotations */ if (! translatorHelper.gatherABSUnitAnnotations(this.model)) { return; } this.outputFile = outputFile; this.translatorHelper.setABSAssertImpl(model); } public boolean hasABSUnit() { return translatorHelper.hasABSUnit(); } /** * Generates an ABS module {@link ModuleDecl} that defines the * given test suite. * * @param suite * @param validate * @return */ @SuppressWarnings("rawtypes") public ModuleDecl generateABSUnitTests(ApetTestSuite suite, boolean validate) { console("Add basic imports..."); for (String key : suite.keySet()) { console("Generating test suite for "+key+"..."); generateABSUnitTest(suite.get(key), key); } Set<DeltaDecl> deltaDecls = new HashSet<DeltaDecl>(); for (DeltaWrapper w : deltas) { DeltaDecl delta = w.getDelta(); deltaDecls.add(delta); abs.frontend.ast.List<DeltaAccess> access = delta.getDeltaAccesss(); if (access.hasChildren()) { String use = access.getChild(0).getModuleName(); importModules.add(use); } } addImports(module); buildProductLine(module); console("Pretty printing ABSUnit tests..."); List<ASTNode<ASTNode>> nodes = new ArrayList<ASTNode<ASTNode>>(); nodes.add(module); nodes.addAll(deltaDecls); nodes.add(productline); nodes.add(product); printToFile(nodes, outputFile); if (validate) { console("Validating ABSUnit tests..."); validateOutput(); } console("ABSUnit tests generation successful"); return module; } void buildProductLine(ModuleDecl module) { if (deltas.isEmpty()) { return; } console("Generating product line description..."); productline = new ProductLine(); productline.setName(CONFIGURATION_NAME); Feature feature = new Feature(); feature.setName(FEATURE_NAME); productline.addFeature(feature); AppCond ac = new AppCondFeature(FEATURE_NAME); Set<String> applicationConditions = new HashSet<String>(); DeltaClause lastClause = null; for (DeltaWrapper d : deltas) { DeltaClause clause = new DeltaClause(); Deltaspec spec = new Deltaspec(); String name = d.getDelta().getName(); spec.setDeltaID(name); clause.setDeltaspec(spec); clause.setAppCond(ac); if (d.isLast()) { lastClause = clause; } else { applicationConditions.add(name); } productline.addDeltaClause(clause); } if (lastClause != null) { for (String n : applicationConditions) { lastClause.addAfterDeltaID(new DeltaID(n)); } } product = new ProductDecl(); product.setName(PRODUCT_NAME); ProductFeatureSet featureSet = new ProductFeatureSet(); featureSet.addFeature(feature); product.setProductExpr(featureSet); } void generateABSUnitTest(List<TestCase> cs, String mn) { String[] sp = mn.split("\\."); final String methodName; final String className; if (sp.length == 2) { className = sp[0]; methodName = sp[1]; } else if (sp.length == 1) { className = null; methodName = mn; } else { throw new IllegalArgumentException(); } InterfaceDecl ti = createTestFixture(cs.size(), className, methodName); if (className == null) { createTestSuiteForFunction(cs, ti, methodName); } else { createTestSuiteForClassMethod(cs, ti, className, methodName); } } void console(String txt) { console(txt, false); } void console(String txt, boolean force) { if (verbose || force) { ConsoleHandler.write(txt); } } private void validateOutput() { CompilationUnit unit = new CompilationUnit(); ModuleDecl cm = module.treeCopyNoTransform(); unit.addModuleDecl(cm); for (DeltaWrapper d : deltas) { unit.addDeltaDecl(d.getDelta().treeCopyNoTransform()); } unit.setProductLine(productline.treeCopyNoTransform()); unit.addProductDecl(product.treeCopyNoTransform()); Model copy = model.treeCopyNoTransform(); copy.addCompilationUnit(unit); validateOutput(copy.treeCopyNoTransform(), null); validateOutput(copy.treeCopyNoTransform(), module.getName().concat(".").concat(PRODUCT_NAME)); } private void validateOutput(Model model, String product) { Model copy = model.treeCopyNoTransform(); if (product != null) { try { copy.flattenForProduct(product); } catch (Exception e) { throw new IllegalStateException("Cannot select product "+product, e); } } SemanticConditionList typeerrors = copy.typeCheck(); for (SemanticCondition se : typeerrors) { System.err.println(se.getHelpMessage()); } } private void addImports(ModuleDecl module) { //export *; //import * from AbsUnit; //import * from AbsUnit.Hamcrest; //import * from AbsUnit.Hamcrest.Core; module.addExport(new StarExport()); module.addImport(new StarImport("AbsUnit")); module.addImport(new StarImport("AbsUnit.Hamcrest")); module.addImport(new StarImport("AbsUnit.Hamcrest.Core")); for (String ip : importModules) { module.addImport(new StarImport(ip)); } } @SuppressWarnings("rawtypes") private void printToFile(List<ASTNode<ASTNode>> nodes, File file) { try { PrintStream stream = new PrintStream(file); PrintWriter writer = new PrintWriter(stream, true); ABSFormatter formatter = new DefaultABSFormatter(writer); for (ASTNode<ASTNode> n : nodes) { n.doPrettyPrint(writer, formatter); } } catch (FileNotFoundException e) { e.printStackTrace(new PrintStream( ConsoleHandler.getDefault().newMessageStream())); } } /** * Create a test suite for testing a function. * * @param testCases * @param testInterface * @param className * @param functionName */ private void createTestSuiteForFunction( List<TestCase> testCases, InterfaceDecl testInterface, String functionName) { //create test class ([Suite]) final ClassDecl testClass = translatorHelper.createTestClass(testInterface); module.addDecl(testClass); //find function under test FunctionDecl functionUnderTest = getDecl(model, FunctionDecl.class, new DeclNamePredicate<FunctionDecl>(functionName)); final Access access = functionUnderTest.getTypeUse(); importModules.add(functionUnderTest.getModuleDecl().getName()); /* * Test methods and Test cases are ordered that is, * test case 1 is implemented by test method 1 and so on... */ for (int i=0; i<testCases.size(); i++) { console("Generating test case "+i+"..."); TestCase testCase = testCases.get(i); MethodImpl method = testClass.getMethod(i); functionBuilder.buildTestCase(testCase, testClass, method, access, functionName); } } /** * Find an interface the given class implements that exposes the given method. * * @param methodName * @param classUnderTest * @return */ InterfaceDecl findInterfaceUnderTest(String methodName, ClassDecl classUnderTest) { Predicate<MethodSig> mp = new MethodSigNamePredicate(methodName); for (InterfaceTypeUse iu : classUnderTest.getImplementedInterfaceUseList()) { InterfaceDecl inf = getDecl(model, InterfaceDecl.class, new DeclNamePredicate<InterfaceDecl>(iu.getName())); if (findMethodSig(inf, mp) != null) { return inf; } } //no interface exposed the given method. return null; } /** * Create a test suite for testing a method. * * @param testCases * @param testInterface * @param className * @param methodName */ private void createTestSuiteForClassMethod( List<TestCase> testCases, InterfaceDecl testInterface, String className, String methodName) { //create test class ([Suite]) final ClassDecl testClass = translatorHelper.createTestClass(testInterface); module.addDecl(testClass); //find class under test. ClassDecl classUnderTest = getDecl(model, ClassDecl.class, new DeclNamePredicate<ClassDecl>(className)); assert classUnderTest != null : "It should not be possible to not " + "find class under test"; //find method under test. MethodImpl methodUnderTest = findMethodImpl(classUnderTest, new MethodNamePredicate(methodName)); assert methodUnderTest != null : "It should not be possible to not " + "find method under test"; //find interface of class under test. InterfaceDecl interfaceOfClassUnderTest = findInterfaceUnderTest(methodName, classUnderTest); if (interfaceOfClassUnderTest == null) { //this method is not exposed by any interface! } //return type MethodSig signature = methodUnderTest.getMethodSig(); final Access access = signature.getReturnType(); //add imports of class/interface under test importModules.add(classUnderTest.getModuleDecl().getName()); importModules.add(interfaceOfClassUnderTest.getModuleDecl().getName()); /* * Test methods and Test cases are ordered that is, * test case 1 is implemented by test method 1 and so on... */ for (int i=0; i<testCases.size(); i++) { console("Generating test case "+i+"..."); TestCase testCase = testCases.get(i); MethodImpl method = testClass.getMethod(i); methodBuilder.buildTestCase(testCase, testClass, method, access, methodName); } } /** * Create Test Fixture for a given Class method or a function. * * @param testCaseSize * @param className * @param methodName * @return */ private InterfaceDecl createTestFixture(int testCaseSize, String className, String methodName) { String capMethodName = StringUtils.capitalize(methodName); if (className == null) { className = testCaseNameBuilder.functionClassName(capMethodName); } final String testInterfaceName = testCaseNameBuilder.testInterfaceName(className, capMethodName); //create fixture InterfaceDecl testInterface = getDecl(module, InterfaceDecl.class, AbsASTBuilderUtil.<InterfaceDecl>namePred(testInterfaceName)); if (testInterface == null) { testInterface = translatorHelper.createTestInterface(testInterfaceName); module.addDecl(testInterface); } for (int i=1; i<=testCaseSize; i++) { testInterface.addBody( translatorHelper.createTestMethodSig(testCaseNameBuilder.testMethodName(capMethodName, Integer.valueOf(i).toString()))); } return testInterface; } }