/** * Copyright (c) 2009-2011, The HATS Consortium. All rights reserved. * This file is licensed under the terms of the Modified BSD License. */ package abs.backend.tests; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import abs.frontend.ast.Annotation; import abs.frontend.ast.ClassDecl; import abs.frontend.ast.DataConstructor; import abs.frontend.ast.DataConstructorExp; import abs.frontend.ast.Decl; import abs.frontend.ast.InterfaceDecl; import abs.frontend.ast.InterfaceTypeUse; import abs.frontend.ast.List; import abs.frontend.ast.MethodImpl; import abs.frontend.ast.MethodSig; import abs.frontend.ast.Model; import abs.frontend.ast.ParametricDataTypeDecl; import abs.frontend.ast.ParametricDataTypeUse; import abs.frontend.ast.PureExp; /** * * @author pwong * */ abstract class AbstractABSTestRunnerGenerator implements ABSTestRunnerGenerator { protected static final String ignore = "AbsUnit.Ignored"; protected static final String test = "AbsUnit.Test"; protected static final String dataPoint = "AbsUnit.DataPoint"; protected static final String suite = "AbsUnit.Suite"; protected static final String fixture = "AbsUnit.Fixture"; protected static final String absStdSet = "ABS.StdLib.Set"; protected static final String absStdLib = "ABS.StdLib"; protected static final String dataValue = "d"; protected static final String futs = "futs"; protected static final String fut = "fut"; protected DataConstructor ignoreType; protected DataConstructor testType; protected DataConstructor dataPointType; protected DataConstructor suiteType; protected DataConstructor fixtureType; protected Map<InterfaceDecl, Set<ClassDecl>> tests = new HashMap<InterfaceDecl, Set<ClassDecl>>(); protected final Model model; protected boolean isEmpty = true; /** * The constructor takes a type checked {@link Model} of the ABS model * * @param model * @throws IllegalArgumentException if model is null */ protected AbstractABSTestRunnerGenerator(Model model) { if (model == null) throw new IllegalArgumentException("Model cannot be null!"); this.model = model; gatherABSUnitAnnotations(); /* * Do not search for test class definitions if this model does not * contain the necessary ABSUnit annotations */ if (ignoreType == null || testType == null || dataPointType == null || suiteType == null || fixtureType == null) { return; } gatherTestClasses(); /* * An ABSUnit ABS model must have defined at least one interface with * TestClass annotation. */ if (tests.isEmpty()) return; /* * An ABSUnit ABS model must have defined at least one class with * TestClassImpl annotation. */ for (InterfaceDecl inf : tests.keySet()) { isEmpty &= tests.get(inf).isEmpty(); } } private InterfaceDecl getTestClass(ClassDecl clazz) { for (InterfaceTypeUse inf : clazz.getImplementedInterfaceUseList()) { if (inf.getDecl() instanceof InterfaceDecl) { InterfaceDecl decl = (InterfaceDecl) inf.getDecl(); if (hasTestAnnotation(decl.getAnnotations(), fixtureType) && ! hasTestAnnotation(decl.getAnnotations(), ignoreType)) { return decl; } } } return null; } private boolean isTestClassImpl(ClassDecl clazz) { return hasTestAnnotation(clazz.getAnnotations(), suiteType); } private void addTest(InterfaceDecl inf, ClassDecl clazz) { if (!tests.containsKey(inf)) { tests.put(inf, new HashSet<ClassDecl>()); } tests.get(inf).add(clazz); } /** * Checks if this generator contains a {@link Model} that defines ABSUnit * tests. * * @return */ @Override public boolean hasUnitTest() { return !isEmpty; } protected void gatherABSUnitAnnotations() { for (Decl decl : this.model.getDecls()) { if (decl instanceof ParametricDataTypeDecl) { String name = decl.getType().getQualifiedName(); if (test.equals(name)) { testType = ((ParametricDataTypeDecl) decl).getDataConstructor(0); } else if (fixture.equals(name)) { fixtureType = ((ParametricDataTypeDecl) decl).getDataConstructor(0); } else if (suite.equals(name)) { suiteType = ((ParametricDataTypeDecl) decl).getDataConstructor(0); } else if (dataPoint.equals(name)) { dataPointType = ((ParametricDataTypeDecl) decl).getDataConstructor(0); } else if (ignore.equals(name)) { ignoreType = ((ParametricDataTypeDecl) decl).getDataConstructor(0); } } } } protected void gatherTestClasses() { for (Decl decl : this.model.getDecls()) { if (decl instanceof ClassDecl) { ClassDecl clazz = (ClassDecl) decl; if (isTestClassImpl(clazz)) { InterfaceDecl inf = getTestClass(clazz); if (inf != null) { addTest(inf, clazz); } continue; } } } } protected boolean hasTestAnnotation(List<Annotation> annotations, DataConstructor... constructors) { java.util.List<DataConstructor> cs = Arrays.asList(constructors); for (Annotation ta : annotations) { PureExp exp = ta.getValue(); if (exp instanceof DataConstructorExp && cs.contains(((DataConstructorExp) exp).getDataConstructor())) { return true; } } return false; } protected String uncap(String word) { return new StringBuilder().append(Character.toLowerCase(word.charAt(0))).append(word.substring(1)).toString(); } protected String dataPointSetName(ClassDecl clazz) { return uncap(clazz.getName()) + "dataPointSet"; } /** * Find a method defined in {@code inf} that is annotated with [DataPoint], * takes no argument and returns a Set of data values. * * @param inf * @return the method defined in {@code inf} that is annotated with * [DataPoint], or null if such a method does not exist. */ protected MethodSig findDataPoints(InterfaceDecl inf) { for (MethodSig meth : inf.getAllMethodSigs()) { if (hasTestAnnotation(meth.getAnnotations(), dataPointType)) { Decl d = ((ParametricDataTypeUse) meth.getReturnType()).getDecl(); if (d.getType().getQualifiedName().equals(absStdSet)) { return meth; } return null; } } return null; } protected Set<MethodSig> getTestMethods(InterfaceDecl inf) { Set<MethodSig> testmethods = new HashSet<MethodSig>(); for (MethodSig meth : inf.getAllMethodSigs()) { /* * Add those methods that are tests but are not ignored */ if (hasTestAnnotation(meth.getAnnotations(), testType) && ! hasTestAnnotation(meth.getAnnotations(), ignoreType)) { testmethods.add(meth); } } return testmethods; } protected boolean isIgnored(ClassDecl clazz, MethodSig method) { for (MethodImpl m : clazz.getMethodList()) { if (m.getMethodSig().getName().equals(method.getName())) { return hasTestAnnotation(m.getMethodSig().getAnnotationList(), ignoreType); } } return false; } }