org.aspectj.testing.XMLBasedAjcTestCase.java Source code

Java tutorial

Introduction

Here is the source code for org.aspectj.testing.XMLBasedAjcTestCase.java

Source

/* *******************************************************************
 * Copyright (c) 2004 IBM Corporation
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/epl-v10.html 
 *  
 * Contributors: 
 *     Adrian Colyer, 
 * ******************************************************************/
package org.aspectj.testing;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestSuite;

import org.apache.commons.digester.Digester;
import org.aspectj.apache.bcel.classfile.Attribute;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.classfile.LocalVariable;
import org.aspectj.apache.bcel.classfile.LocalVariableTable;
import org.aspectj.apache.bcel.classfile.Method;
import org.aspectj.apache.bcel.util.ClassPath;
import org.aspectj.apache.bcel.util.SyntheticRepository;
import org.aspectj.tools.ajc.AjcTestCase;
import org.aspectj.tools.ajc.CompilationResult;
import org.aspectj.util.FileUtil;

/**
 * Root class for all Test suites that are based on an AspectJ XML test suite file. Extends AjcTestCase allowing a mix of
 * programmatic and spec-file driven testing. See org.aspectj.systemtest.incremental.IncrementalTests for an example of this mixed
 * style.
 * <p>
 * The class org.aspectj.testing.MakeTestClass will generate a subclass of this class for you, given a suite spec. file as input...
 * </p>
 */
public abstract class XMLBasedAjcTestCase extends AjcTestCase {

    private static Map<String, AjcTest> testMap = new HashMap<String, AjcTest>();
    private static boolean suiteLoaded = false;
    private AjcTest currentTest = null;
    private Stack<Boolean> clearTestAfterRun = new Stack<Boolean>();

    public XMLBasedAjcTestCase() {
    }

    /**
     * You must define a suite() method in subclasses, and return the result of calling this method. (Don't you hate static methods
     * in programming models). For example:
     * 
     * <pre>
     * public static Test suite() {
     *    return XMLBasedAjcTestCase.loadSuite(MyTestCaseClass.class);
     * }
     * </pre>
     * 
     * @param testCaseClass
     * @return
     */
    public static Test loadSuite(Class<?> testCaseClass) {
        TestSuite suite = new TestSuite(testCaseClass.getName());
        suite.addTestSuite(testCaseClass);
        TestSetup wrapper = new TestSetup(suite) {
            /*
             * (non-Javadoc)
             * 
             * @see junit.extensions.TestSetup#setUp()
             */
            protected void setUp() throws Exception {
                super.setUp();
                suiteLoaded = false;
            }

            /*
             * (non-Javadoc)
             * 
             * @see junit.extensions.TestSetup#tearDown()
             */
            protected void tearDown() throws Exception {
                super.tearDown();
                suiteLoaded = false;
            }
        };
        return wrapper;
    }

    /**
     * The file containing the XML specification for the tests.
     */
    protected abstract File getSpecFile();

    /*
     * Return a map from (String) test title -> AjcTest
     */
    protected Map<String, AjcTest> getSuiteTests() {
        return testMap;
    }

    /**
     * This helper method runs the test with the given title in the suite spec file. All tests steps in given ajc-test execute in
     * the same sandbox.
     */
    protected void runTest(String title, boolean print) {
        try {
            currentTest = (AjcTest) testMap.get(title);
            final boolean clearTest = clearTestAfterRun();
            if (currentTest == null) {
                if (clearTest) {
                    System.err.println("test already run: " + title);
                    return;
                } else {
                    fail("No test '" + title + "' in suite.");
                }
            }
            boolean run = currentTest.runTest(this);
            assertTrue("Test not run", run);
            if (clearTest) {
                testMap.remove(title);
            }
        } finally {
            if (print) {
                System.out.println("SYSOUT");
                System.out.println(ajc.getLastCompilationResult().getStandardOutput());
            }
        }
    }

    protected void runTest(String title) {
        runTest(title, false);
    }

    /**
     * Get the currently executing test. Useful for access to e.g. AjcTest.getTitle() etc..
     */
    protected AjcTest getCurrentTest() {
        return currentTest;
    }

    /**
     * For use by the Digester. As the XML document is parsed, it creates instances of AjcTest objects, which are added to this
     * TestCase by the Digester by calling this method.
     */
    public void addTest(AjcTest test) {
        testMap.put(test.getTitle(), test);
    }

    protected final void pushClearTestAfterRun(boolean val) {
        clearTestAfterRun.push(val ? Boolean.FALSE : Boolean.TRUE);
    }

    protected final boolean popClearTestAfterRun() {
        return clearTest(true);
    }

    protected final boolean clearTestAfterRun() {
        return clearTest(false);
    }

    private boolean clearTest(boolean pop) {
        if (clearTestAfterRun.isEmpty()) {
            return false;
        }
        boolean result = clearTestAfterRun.peek().booleanValue();
        if (pop) {
            clearTestAfterRun.pop();
        }
        return result;
    }

    /*
     * The rules for parsing a suite spec file. The Digester using bean properties to match attributes in the XML document to
     * properties in the associated classes, so this simple implementation should be very easy to maintain and extend should you
     * ever need to.
     */
    protected Digester getDigester() {
        Digester digester = new Digester();
        digester.push(this);
        digester.addObjectCreate("suite/ajc-test", AjcTest.class);
        digester.addSetProperties("suite/ajc-test");
        digester.addSetNext("suite/ajc-test", "addTest", "org.aspectj.testing.AjcTest");
        digester.addObjectCreate("suite/ajc-test/compile", CompileSpec.class);
        digester.addSetProperties("suite/ajc-test/compile");
        digester.addSetNext("suite/ajc-test/compile", "addTestStep", "org.aspectj.testing.ITestStep");
        digester.addObjectCreate("suite/ajc-test/file", FileSpec.class);
        digester.addSetProperties("suite/ajc-test/file");
        digester.addSetNext("suite/ajc-test/file", "addTestStep", "org.aspectj.testing.ITestStep");
        digester.addObjectCreate("suite/ajc-test/run", RunSpec.class);
        digester.addSetProperties("suite/ajc-test/run", "class", "classToRun");
        digester.addSetProperties("suite/ajc-test/run", "ltw", "ltwFile");
        digester.addSetProperties("suite/ajc-test/run", "xlintfile", "xlintFile");
        digester.addSetProperties("suite/ajc-test/run/stderr", "ordered", "orderedStderr");
        digester.addSetNext("suite/ajc-test/run", "addTestStep", "org.aspectj.testing.ITestStep");
        digester.addObjectCreate("*/message", ExpectedMessageSpec.class);
        digester.addSetProperties("*/message");
        digester.addSetNext("*/message", "addExpectedMessage", "org.aspectj.testing.ExpectedMessageSpec");
        digester.addObjectCreate("suite/ajc-test/weave", WeaveSpec.class);
        digester.addSetProperties("suite/ajc-test/weave");
        digester.addSetNext("suite/ajc-test/weave", "addTestStep", "org.aspectj.testing.ITestStep");

        digester.addObjectCreate("suite/ajc-test/ant", AntSpec.class);
        digester.addSetProperties("suite/ajc-test/ant");
        digester.addSetNext("suite/ajc-test/ant", "addTestStep", "org.aspectj.testing.ITestStep");
        digester.addObjectCreate("suite/ajc-test/ant/stderr", OutputSpec.class);
        digester.addSetProperties("suite/ajc-test/ant/stderr");
        digester.addSetNext("suite/ajc-test/ant/stderr", "addStdErrSpec", "org.aspectj.testing.OutputSpec");
        digester.addObjectCreate("suite/ajc-test/ant/stdout", OutputSpec.class);
        digester.addSetProperties("suite/ajc-test/ant/stdout");
        digester.addSetNext("suite/ajc-test/ant/stdout", "addStdOutSpec", "org.aspectj.testing.OutputSpec");

        digester.addObjectCreate("suite/ajc-test/run/stderr", OutputSpec.class);
        digester.addSetProperties("suite/ajc-test/run/stderr");
        digester.addSetNext("suite/ajc-test/run/stderr", "addStdErrSpec", "org.aspectj.testing.OutputSpec");
        digester.addObjectCreate("suite/ajc-test/run/stdout", OutputSpec.class);
        digester.addSetProperties("suite/ajc-test/run/stdout");
        digester.addSetNext("suite/ajc-test/run/stdout", "addStdOutSpec", "org.aspectj.testing.OutputSpec");
        digester.addObjectCreate("*/line", OutputLine.class);
        digester.addSetProperties("*/line");
        digester.addSetNext("*/line", "addLine", "org.aspectj.testing.OutputLine");
        return digester;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.aspectj.tools.ajc.AjcTestCase#setUp()
     */
    protected void setUp() throws Exception {
        super.setUp();
        if (!suiteLoaded) {
            testMap = new HashMap<String, AjcTest>();
            System.out.println("LOADING SUITE: " + getSpecFile().getPath());
            Digester d = getDigester();
            try {
                InputStreamReader isr = new InputStreamReader(new FileInputStream(getSpecFile()));
                d.parse(isr);
            } catch (Exception ex) {
                fail("Unable to load suite " + getSpecFile().getPath() + " : " + ex);
            }
            suiteLoaded = true;
        }
    }

    protected long nextIncrement(boolean doWait) {
        long time = System.currentTimeMillis();
        if (doWait) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException intEx) {
            }
        }
        return time;
    }

    protected void copyFile(String from, String to) throws Exception {
        String dir = getCurrentTest().getDir();
        FileUtil.copyFile(new File(dir + File.separator + from), new File(ajc.getSandboxDirectory(), to));
    }

    protected void copyFileAndDoIncrementalBuild(String from, String to) throws Exception {
        copyFile(from, to);
        CompilationResult result = ajc.doIncrementalCompile();
        assertNoMessages(result, "Expected clean compile from test '" + getCurrentTest().getTitle() + "'");
    }

    protected void copyFileAndDoIncrementalBuild(String from, String to, MessageSpec expectedResults)
            throws Exception {
        String dir = getCurrentTest().getDir();
        FileUtil.copyFile(new File(dir + File.separator + from), new File(ajc.getSandboxDirectory(), to));
        CompilationResult result = ajc.doIncrementalCompile();
        assertMessages(result, "Test '" + getCurrentTest().getTitle() + "' did not produce expected messages",
                expectedResults);
    }

    protected void deleteFile(String file) {
        new File(ajc.getSandboxDirectory(), file).delete();
    }

    protected void deleteFileAndDoIncrementalBuild(String file, MessageSpec expectedResult) throws Exception {
        deleteFile(file);
        CompilationResult result = ajc.doIncrementalCompile();
        assertMessages(result, "Test '" + getCurrentTest().getTitle() + "' did not produce expected messages",
                expectedResult);
    }

    protected void deleteFileAndDoIncrementalBuild(String file) throws Exception {
        deleteFileAndDoIncrementalBuild(file, MessageSpec.EMPTY_MESSAGE_SET);
    }

    protected void assertAdded(String file) {
        assertTrue("File " + file + " should have been added", new File(ajc.getSandboxDirectory(), file).exists());
    }

    protected void assertDeleted(String file) {
        assertFalse("File " + file + " should have been deleted",
                new File(ajc.getSandboxDirectory(), file).exists());
    }

    protected void assertUpdated(String file, long sinceTime) {
        File f = new File(ajc.getSandboxDirectory(), file);
        assertTrue("File " + file + " should have been updated", f.lastModified() > sinceTime);
    }

    public SyntheticRepository createRepos(File cpentry) {
        ClassPath cp = new ClassPath(cpentry + File.pathSeparator + System.getProperty("java.class.path"));
        return SyntheticRepository.getInstance(cp);
    }

    protected byte[] loadFileAsByteArray(File f) {
        try {
            byte[] bs = new byte[100000];
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f));
            int pos = 0;
            int len = 0;
            while ((len = bis.read(bs, pos, 100000 - pos)) != -1) {
                pos += len;
            }
            bis.close();
            return bs;
        } catch (Exception e) {
            return null;
        }
    }

    public JavaClass getClassFrom(File where, String clazzname) throws ClassNotFoundException {
        SyntheticRepository repos = createRepos(where);
        return repos.loadClass(clazzname);
    }

    protected Method getMethodStartsWith(JavaClass jc, String prefix) {
        return getMethodStartsWith(jc, prefix, 1);
    }

    protected Attribute getAttributeStartsWith(Attribute[] attributes, String prefix) {
        StringBuilder buf = new StringBuilder();
        for (Attribute a : attributes) {
            if (a.getName().startsWith(prefix)) {
                return a;
            }
            buf.append(a.toString()).append("\n");
        }
        fail("Failed to find '" + prefix + "' in attributes:\n" + buf.toString());
        return null;
    }

    protected Method getMethodStartsWith(JavaClass jc, String prefix, int whichone) {
        Method[] meths = jc.getMethods();
        for (int i = 0; i < meths.length; i++) {
            Method method = meths[i];
            System.out.println(method);
            if (method.getName().startsWith(prefix)) {
                whichone--;
                if (whichone == 0) {
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * Sort it by name then start position
     */
    public List<LocalVariable> sortedLocalVariables(LocalVariableTable lvt) {
        List<LocalVariable> l = new ArrayList<LocalVariable>();
        LocalVariable lv[] = lvt.getLocalVariableTable();
        for (int i = 0; i < lv.length; i++) {
            LocalVariable lvEntry = lv[i];
            l.add(lvEntry);
        }
        Collections.sort(l, new MyComparator());
        return l;
    }

    public String stringify(LocalVariableTable lvt, int slotIndex) {
        LocalVariable lv[] = lvt.getLocalVariableTable();
        LocalVariable lvEntry = lv[slotIndex];
        StringBuffer sb = new StringBuffer();
        sb.append(lvEntry.getSignature()).append(" ").append(lvEntry.getName()).append("(")
                .append(lvEntry.getIndex()).append(") start=").append(lvEntry.getStartPC()).append(" len=")
                .append(lvEntry.getLength());
        return sb.toString();
    }

    public String stringify(List<LocalVariable> l, int slotIndex) {
        LocalVariable lvEntry = (LocalVariable) l.get(slotIndex);
        StringBuffer sb = new StringBuffer();
        sb.append(lvEntry.getSignature()).append(" ").append(lvEntry.getName()).append("(")
                .append(lvEntry.getIndex()).append(") start=").append(lvEntry.getStartPC()).append(" len=")
                .append(lvEntry.getLength());
        return sb.toString();
    }

    public String stringify(LocalVariableTable lvt) {
        if (lvt == null) {
            return "";
        }
        StringBuffer sb = new StringBuffer();
        sb.append("LocalVariableTable.  Entries=#" + lvt.getTableLength()).append("\n");
        LocalVariable lv[] = lvt.getLocalVariableTable();
        for (int i = 0; i < lv.length; i++) {
            LocalVariable lvEntry = lv[i];
            sb.append(lvEntry.getSignature()).append(" ").append(lvEntry.getName()).append("(")
                    .append(lvEntry.getIndex()).append(") start=").append(lvEntry.getStartPC()).append(" len=")
                    .append(lvEntry.getLength()).append("\n");
        }

        return sb.toString();
    }

    public static class CountingFilenameFilter implements FilenameFilter {

        private String suffix;
        private int count;

        public CountingFilenameFilter(String s) {
            this.suffix = s;
        }

        public boolean accept(File dir, String name) {
            if (name.endsWith(suffix)) {
                count++;
            }
            return false;
        }

        public int getCount() {
            return count;
        }
    }

    public static class MyComparator implements Comparator<LocalVariable> {
        public int compare(LocalVariable o1, LocalVariable o2) {
            LocalVariable l1 = (LocalVariable) o1;
            LocalVariable l2 = (LocalVariable) o2;
            if (l1.getName().equals(l2.getName())) {
                return l1.getStartPC() - l2.getStartPC();
            } else {
                return l1.getName().compareTo(l2.getName());
            }
        }

    }

    protected Method getMethodFromClass(JavaClass clazz, String methodName) {
        Method[] meths = clazz.getMethods();
        for (int i = 0; i < meths.length; i++) {
            Method method = meths[i];
            if (method.getName().equals(methodName)) {
                return meths[i];
            }
        }
        return null;
    }

    protected File getClassResource(String resourceName) {
        return new File(getClass().getResource(resourceName).getFile());
    }

}