]> source.dussan.org Git - aspectj.git/commitdiff
Added support to ease programmatic testing of compiler (AjcTestCase
authoracolyer <acolyer>
Wed, 4 Aug 2004 11:59:41 +0000 (11:59 +0000)
committeracolyer <acolyer>
Wed, 4 Aug 2004 11:59:41 +0000 (11:59 +0000)
class and accompanying support).

org.aspectj.ajdt.core/.classpath
org.aspectj.ajdt.core/src/org/aspectj/tools/ajc/Main.java
org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/Ajc.java [new file with mode: 0644]
org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/AjcTestCase.java [new file with mode: 0644]
org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/AjcTestCaseTest.java [new file with mode: 0644]
org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/CompilationResult.java [new file with mode: 0644]
org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/MainTest.java

index 0f9073da2331f0e45bf618a591ddfc698d2e2953..32c74b39c76147a204ee87c4db76c200d5b2b1ed 100644 (file)
@@ -1,16 +1,17 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-    <classpathentry kind="src" path="src"/>
-    <classpathentry kind="var" path="JRE_LIB" sourcepath="JRE_SRC"/>
-    <classpathentry kind="src" path="/org.eclipse.jdt.core"/>
-    <classpathentry kind="src" path="/bridge"/>
-    <classpathentry kind="src" path="/weaver"/>
-    <classpathentry kind="src" path="/asm"/>
-    <classpathentry kind="src" path="/util"/>
-    <classpathentry kind="src" path="/runtime"/>
-    <classpathentry kind="src" path="testsrc"/>
-    <classpathentry kind="lib" path="/lib/bcel/bcel.jar" sourcepath="/lib/bcel/bcel-src.zip"/>
-    <classpathentry kind="lib" path="/lib/junit/junit.jar" sourcepath="/lib/junit/junit-src.jar"/>
-    <classpathentry kind="src" path="/testing-util"/>
-    <classpathentry kind="output" path="bin"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry sourcepath="JRE_SRC" kind="var" path="JRE_LIB"/>
+       <classpathentry kind="src" path="/org.eclipse.jdt.core"/>
+       <classpathentry kind="src" path="/bridge"/>
+       <classpathentry kind="src" path="/weaver"/>
+       <classpathentry kind="src" path="/asm"/>
+       <classpathentry kind="src" path="/util"/>
+       <classpathentry kind="src" path="/runtime"/>
+       <classpathentry kind="src" path="testsrc"/>
+       <classpathentry sourcepath="/lib/bcel/bcel-src.zip" kind="lib" path="/lib/bcel/bcel.jar"/>
+       <classpathentry sourcepath="/lib/junit/junit-src.jar" kind="lib" path="/lib/junit/junit.jar"/>
+       <classpathentry kind="src" path="/testing-util"/>
+       <classpathentry kind="src" path="/testing-client"/>
+       <classpathentry kind="output" path="bin"/>
 </classpath>
index 1ec80de037c4bf7339e08f265823254862eadec3..fa790ae7c5f5db106cf32a06e1365c60adbeeee4 100644 (file)
@@ -192,6 +192,11 @@ public class Main {
        return ourHandler;
     }
     
+    // for unit testing...
+    void setController(CommandController controller) {
+       this.controller = controller;
+    }
+    
     /**
      * Run without throwing exceptions but optionally using System.exit(..).
      * This sets up a message handler which emits messages immediately,
@@ -284,7 +289,7 @@ public class Main {
                 boolean passed = command.runCommand(args, holder);
                 if (report(passed, holder) && controller.incremental()) {
 //                    final boolean onCommandLine = controller.commandLineIncremental();
-                    while (controller.doRepeatCommand()) {
+                    while (controller.doRepeatCommand(command)) {
                         holder.clearMessages();
                         if (controller.buildFresh()) {
                             continue outer;
@@ -646,7 +651,7 @@ public class Main {
         }
         
         /** @return false if we should quit, true to do another command */
-        boolean doRepeatCommand() {
+        boolean doRepeatCommand(ICommand command) {
             if (!running) {
                 return false;
             }
diff --git a/org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/Ajc.java b/org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/Ajc.java
new file mode 100644 (file)
index 0000000..5df4fea
--- /dev/null
@@ -0,0 +1,396 @@
+/* *******************************************************************
+ * Copyright (c) 2004 IBM Corporation
+ * All rights reserved. 
+ * This program and the accompanying materials are made available 
+ * under the terms of the Common Public License v1.0 
+ * which accompanies this distribution and is available at 
+ * http://www.eclipse.org/legal/cpl-v10.html 
+ *  
+ * Contributors: 
+ *     Adrian Colyer, 
+ * ******************************************************************/
+package org.aspectj.tools.ajc;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import junit.framework.AssertionFailedError;
+
+import org.aspectj.bridge.ICommand;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.MessageHandler;
+import org.aspectj.util.FileUtil;
+
+/**
+ * The Ajc class is intended for use as part of a unit-test suite, it drives
+ * the AspectJ compiler and lets you check the compilation results. Compilations
+ * run in a sandbox that is created in C:\temp\ajcSandbox or /tmp/ajcSandbox
+ * depending on your platform.
+ * <p>
+ * The expected usage of Ajc is through the TestCase superclass,
+ * AjcTestCase, which provides helper methods that conveniently 
+ * drive the base functions exposed by this class.
+ * </p>
+ * @see org.aspectj.tools.ajc.AjcTestCase
+ */
+public class Ajc {
+
+       private static final String SANDBOX_NAME = "ajcSandbox";
+       private static final String TESTER_PATH = 
+               "../testing-client/bin" + File.pathSeparator + "../runtime/bin";
+       private CompilationResult result;
+       private File sandbox;
+       private File baseDir;
+       private Main main;
+       private String[] ajcArgs;
+       private int incrementalStage = 10;
+       private boolean shouldEmptySandbox = true;
+       private AjcCommandController controller;
+       
+       /**
+        * Constructs a new Ajc instance, with a new AspectJ compiler
+        * inside. 
+        */
+       public Ajc() {
+               main = new Main();
+               controller = new AjcCommandController();
+               main.setController(controller);
+       }
+       
+       /**
+        * By default, each call to <code>compile</code> creates a new
+        * sandbox (C:\temp\ajcSandbox\ajtTestxxx.tmp, or /tmp/ajcSandbox/ajcTestxxx.tmp
+        * depending on your platform). To write a test that performs multiple
+        * (non-incremental) compiles, building on the results of previous
+        * compilations, set 'should empty sandbox' to false after the first
+        * compile, which will cause subsequent compiles in the test to use the
+        * same directory and contents.
+        */
+       public void setShouldEmptySandbox(boolean empty) {
+               this.shouldEmptySandbox = empty;
+       }
+       
+       /**
+        * Call the compiler with the given arguments (args are exactly the same
+        * as you would pass to ajc on the command-line). The results of the 
+        * compile are returned in a <code>CompilationResult</code>, which
+        * provides for easy testing of results.
+        * <p>The compilation happens in a sandbox (C:\temp\ajcSandbox\ajTestxxx.tmp or
+        * /tmp/ajcSandbox/ajcTestxxx.tmp depending on platform). Compiler arguments are
+        * adapted to the sandbox as follows.</p>
+        * <p>For every file or directory listed in an argument (source file, or component
+        * of inpath, aspectpath, sourceroots, classpath,...), if the file is specified
+        * using an absolute path then it is left unchanged, but if the file is specified
+        * using a relative path, and a base directory (see setBaseDir) has been provided,
+        * then files/directories are copied from the base directory to the sandbox, and the
+        * compiler arguments adjusted to reflect their new location. 
+        * </p>
+        * <p>For example, given a baseDir of "tests/pr12345" and a compile command: 
+        * "ajc src/A.java src/B.java", the files in 
+        * <pre>
+        *    tests/pr12345/
+        *                  src/
+        *                      A.java
+        *                      B.java
+        * </pre>
+        * are copied to:
+        * <pre>
+        *     ajcSandbox/ajcTestxxx.tmp/
+        *                               src/
+        *                                   A.java
+        *                                   B.java
+        * </pre>
+        * <p>
+        * If no classpath is specified (no -classpath in the arguments) the classpath will
+        * be set to include the sandbox directory, testing-client/bin (for the Tester class), 
+        * and runtime/bin (for the AspectJ runtime). If a classpath <i>is</i> specified,
+        * then any relative directories in it will be made relative to the sandbox, and
+        * the testing-client and runtime bin directories are also added.
+        * </p>
+        * <p>
+        * If no output directory is specified (no -d in the arguments), the output directory
+        * is set to the sandbox. If a directory is specified, and the path is relative, it
+        * will be made relative to the sandbox.
+        * </p>
+        * <ul>
+        * </ul>
+        * </p>
+        * @param args The compiler arguments. 
+        * @return a CompilationResult object with all the messages produced by
+        * the compiler, a description of the ajc command that was issued,
+        * and the standard output and error of the compile (excluding messages
+        * which are provided separately)
+        * @throws IOException
+        * @see org.aspectj.tools.ajc.CompilationResult
+        */
+       public CompilationResult compile(String[] args) throws IOException {
+               incrementalStage = 10;
+               return compile(args,false);
+       }
+       
+       private CompilationResult compile(String[] args,boolean isIncremental) throws IOException {
+               result = null;
+               ajcArgs = args;
+               ByteArrayOutputStream out = new ByteArrayOutputStream();
+               PrintStream pout = new PrintStream(out);
+               ByteArrayOutputStream err = new ByteArrayOutputStream();
+               PrintStream perr = new PrintStream(err);
+               PrintStream systemOut = System.out;
+               PrintStream systemErr = System.err;
+               System.setOut(pout);
+               System.setErr(perr);
+
+               List fails = new ArrayList();
+               List errors = new ArrayList();
+               List warnings = new ArrayList();
+               List infos = new ArrayList();
+               
+               try {
+                       if (!isIncremental && shouldEmptySandbox) {
+                               createEmptySandbox();
+                       }
+                       args = adjustToSandbox(args,!isIncremental);
+                       MessageHandler holder = new MessageHandler();
+                       main.setHolder(holder);
+                       if (isIncremental) {
+                               controller.doIncremental(holder);
+                       } else {
+                               main.runMain(args,false);
+                       }
+                       addMessagesTo(infos,holder.getMessages(IMessage.INFO,false));
+                       addMessagesTo(warnings,holder.getWarnings());
+                       addMessagesTo(errors,holder.getErrors());
+                       addMessagesTo(fails,holder.getMessages(IMessage.FAIL,true));
+                       String stdOut = new String(out.toByteArray());
+                       String stdErr = new String(err.toByteArray());
+                       result = new CompilationResult(args,stdOut,stdErr,infos,errors,warnings,fails);
+               } finally {
+                       System.setOut(systemOut);
+                       System.setErr(systemErr);
+               }
+               return result;
+       }
+       
+       /**
+        * After compiling for the first time with compile(), if the -incremental option was specified
+        * you can do as many subsequent incremental compiles as you like by calling this method.
+        * <p>
+        * Throws an IllegalStateException if you try and call this method without first doing a compile
+        * that specified the -incremental option.
+        * </p>
+        * @return A CompilationResult giving the results of the most recent increment.
+        * @throws IOException
+        */
+       public CompilationResult doIncrementalCompile() throws IOException {
+               if ((ajcArgs == null) || !isIncremental(ajcArgs)) {
+                       throw new IllegalStateException("Can't do incremental compile unless -incremental specified and first compile has taken place");
+               }
+               incrementalStage += 10;
+               return compile(ajcArgs,true);
+       }
+       
+       /**
+        * Return the result of the last compile or incremental compile. This is the same as the 
+        * return value from the compile() or doIncrementalCompile() methods.
+        */
+       public CompilationResult getLastCompilationResult() { 
+               return result;
+       }
+       
+       /**
+        * Get the sandbox directory used for the compilation.
+        */
+       public File getSandboxDirectory() {
+               if (sandbox == null) {createEmptySandbox();}
+               return sandbox;
+       }
+       
+       /**
+        * Set the base directory relative to which all relative paths specified in the arguments to a compile will be
+        * interpreted.
+        */
+       public void setBaseDir(File dir) {
+               if ((dir != null) && !dir.isDirectory()) throw new IllegalArgumentException(dir.getPath() + " is not a directory");
+               baseDir = dir;
+       }
+       
+       private void addMessagesTo(List aList, IMessage[] messages) {
+               for (int i = 0; i < messages.length; i++) {
+                       aList.add(messages[i]);
+               }
+       }
+       
+       private boolean isIncremental(String[] args) {
+               for (int i = 0; i < args.length; i++) {
+                       if (args[i].trim().equals("-incremental")) return true;
+               }
+               return false;
+       }
+       
+       private void createEmptySandbox() {
+               String os = System.getProperty("os.name");
+               File tempDir = null;
+               // AMC - I did this rather than use the JDK default as I hate having to go look
+               // in c:\documents and settings\......... for the results of a failed test.
+               if (os.startsWith("Windows")) {
+                       tempDir = new File("C:\\temp");
+               } else {
+                       tempDir = new File("/tmp");
+               }
+               File sandboxRoot = new File(tempDir,SANDBOX_NAME);
+               if (!sandboxRoot.exists()) {
+                       sandboxRoot.mkdir();
+               }
+
+               FileUtil.deleteContents(sandboxRoot);
+
+               try {
+                       sandbox = File.createTempFile("ajcTest",".tmp",sandboxRoot);
+                       sandbox.delete();
+                       sandbox.mkdir();
+               } catch (IOException ioEx) {
+                       throw new AssertionFailedError("Unable to create sandbox directory for test");
+               }
+       }
+       
+       /**
+        * Make every relative file name and dir be absolute under sandbox
+        * Add TESTER_PATH to classpath
+        */
+       private String[] adjustToSandbox(String[] args,boolean doCopy) throws IOException {
+               String[] newArgs = new String[args.length];
+               boolean hasClasspath = false;
+               boolean hasOutdir = false;
+               for (int i = 0; i < args.length; i++) {
+                       newArgs[i] = args[i];
+                       if (FileUtil.hasSourceSuffix(args[i])) {
+                               File f = new File(args[i]);
+                               //newArgs[i] = new File(baseDir,args[i]).getAbsolutePath(); // might be quicker?
+                               newArgs[i] = adjustFileOrDir(f,doCopy).getAbsolutePath();
+                       } else {
+                               if ((args[i].equals("-aspectpath") ||
+                                    args[i].equals("-inpath") ||
+                                        args[i].equals("-injars") ||
+                                        args[i].equals("-classpath") ||
+                                        args[i].equals("-sourceroots") ||
+                                        args[i].equals("-Xlintfile") ||
+                                        args[i].equals("-extdirs") ||
+                                        args[i].equals("-d")) &&
+                                       args.length > (i+1)) {
+                                       newArgs[i] = args[i];
+                                       StringBuffer buff = new StringBuffer();
+                                       boolean copyThisTime = doCopy;
+                                       if (args[i].equals("-d")) { 
+                                               copyThisTime = false;
+                                               hasOutdir = true;
+                                       }
+                                   StringTokenizer strTok = new StringTokenizer(args[++i],File.pathSeparator);
+                                   while (strTok.hasMoreTokens()) {
+                                       File f = new File(strTok.nextToken());
+                                       buff.append(adjustFileOrDir(f,copyThisTime).getAbsolutePath());
+                                       if (strTok.hasMoreTokens()) buff.append(File.pathSeparator);
+                                   }
+                                   newArgs[i] = buff.toString();
+                                   if (args[i-1].equals("-classpath")) {
+                                       hasClasspath = true;
+                                       newArgs[i] = newArgs[i] + File.pathSeparator + TESTER_PATH;
+                                   }
+                               } else {
+                                       // could be resource file
+                                       File f = new File(args[i]);
+                                       if (f.exists()) {
+                                               newArgs[i] = adjustFileOrDir(f,doCopy).getAbsolutePath();
+                                       }
+                               }
+                       } 
+               }
+               if (!hasClasspath) {
+                       String[] oldArgs = newArgs;
+                       newArgs = new String[oldArgs.length + 2];
+                       System.arraycopy(oldArgs,0,newArgs,0,oldArgs.length);
+                       newArgs[oldArgs.length] = "-classpath";
+                       newArgs[oldArgs.length + 1] = TESTER_PATH + File.pathSeparator + getSandboxDirectory().getAbsolutePath();
+               }
+               if (!hasOutdir) {
+                       String[] oldArgs = newArgs;
+                       newArgs = new String[oldArgs.length + 2];
+                       System.arraycopy(oldArgs,0,newArgs,0,oldArgs.length);
+                       newArgs[oldArgs.length] = "-d";
+                       newArgs[oldArgs.length + 1] = getSandboxDirectory().getPath();                  
+               }
+               return newArgs;
+       }
+       
+       private File adjustFileOrDir(File from,boolean doCopy) throws IOException {
+               File to = from;
+               File ret = from;
+               if (!from.isAbsolute()) {
+                       ret = new File(sandbox,from.getPath());
+                       File fromParent = from.getParentFile();
+                       String relativeToPath = (fromParent != null) ? (fromParent.getPath() + File.separator) : "";
+                       if (baseDir != null) {
+                               from = new File(baseDir,from.getPath());
+                       }
+                       if (!from.exists()) return ret;
+                       if (doCopy) {
+                               // harness requires that any files with the same name, and a different extension,
+                               // get copied too (e.g. .out, .err, .event files)
+                               if (from.isFile()) {
+                                       final String prefix = from.getName().substring(0,from.getName().lastIndexOf('.'));
+                                       String[] toCopy = from.getParentFile().list(new FilenameFilter() {
+                                               public boolean accept(File dir, String name) {
+                                                       if (name.indexOf('.') == -1) return false;
+                                                       String toMatch = name.substring(0,name.lastIndexOf('.'));
+                                                       return (toMatch.equals(prefix));
+                                               }
+                                               });
+                                       for (int i = 0; i < toCopy.length; i++) {
+                                               String toPath = relativeToPath + toCopy[i];
+                                               to = new File(sandbox,toPath);
+                                               FileUtil.copyFile(new File(from.getParentFile(),toCopy[i]),
+                                                                         to);                                  
+                                       }
+                               } else {
+                                       FileUtil.copyFile(from,ret);
+                               }
+                       }
+               }
+               return ret;
+       }
+}
+
+/*
+ * So that we can drive incremental compilation easily from a unit
+ * test.
+ */
+class AjcCommandController extends Main.CommandController {
+       
+       private ICommand command;
+       
+       /* (non-Javadoc)
+        * @see org.aspectj.tools.ajc.Main.CommandController#doRepeatCommand()
+        */
+       boolean doRepeatCommand(ICommand command) {
+               this.command = command;
+               return false;  // ensure that control returns to caller
+       }
+       
+       /* (non-Javadoc)
+        * @see org.aspectj.tools.ajc.Main.CommandController#running()
+        */
+       public boolean running() {
+               return false;  // so that we can come back for more...
+       }
+       
+       public void doIncremental(IMessageHandler handler) {
+               if (command == null) throw new IllegalArgumentException("Can't repeat command until it has executed at least once!");
+               command.repeatCommand(handler);
+       }
+}
diff --git a/org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/AjcTestCase.java b/org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/AjcTestCase.java
new file mode 100644 (file)
index 0000000..acfc9ac
--- /dev/null
@@ -0,0 +1,646 @@
+/* *******************************************************************
+ * Copyright (c) 2004 IBM Corporation
+ * All rights reserved. 
+ * This program and the accompanying materials are made available 
+ * under the terms of the Common Public License v1.0 
+ * which accompanies this distribution and is available at 
+ * http://www.eclipse.org/legal/cpl-v10.html 
+ *  
+ * Contributors: 
+ *     Adrian Colyer, 
+ * ******************************************************************/
+package org.aspectj.tools.ajc;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.ISourceLocation;
+
+import junit.framework.TestCase;
+
+/**
+ * A TestCase class that acts as the superclass for all test cases wishing
+ * to drive the ajc compiler.
+ * <p>
+ * This class provides a number of utility methods that make programmatic 
+ * testing of the compiler easy. See AjcTestCaseTest for a couple of simple
+ * tests written using this class.
+ * </p>
+ * <p>
+ * See the XMLBasedAjcTestCase subclass for TestCase class that can be 
+ * used to drive compiler tests based on an ajcTests.xml format test
+ * specification file.</p>
+ * @see org.aspectj.tools.ajc.AjcTestCase.Message
+ * @see org.aspectj.tools.ajc.AjcTestCase.MessageSpec
+ * @see org.aspectj.tools.ajc.AjcTestCase.RunResult
+ * @see org.aspectj.tools.ajc.AjcTestCaseTest
+ * @see org.aspectj.testing.XMLBasedAjcTestCase
+ */
+public class AjcTestCase extends TestCase {
+
+       /**
+        * The Ajc (compiler) instance used for thet test. Created afresh
+        * during the test setup.
+        */
+       protected Ajc ajc;
+       
+       private static final String DEFAULT_CLASSPATH_ENTRIES = 
+        File.pathSeparator + 
+               ".." + File.separator + "runtime" + File.separator + "bin" + 
+               File.pathSeparator +
+               ".." + File.separator + "testing-client" + File.separator + "bin" +
+               File.pathSeparator +
+               ".." + File.separator + "bridge" + File.separator + "bin" +
+               File.pathSeparator +
+               ".." + File.separator + "util" + File.separator + "bin";
+
+       /**
+        * Helper class that represents the specification of an individual
+        * message expected to be produced during a compilation run.
+        * <p>
+        * Message objects are combined in a MessageSpec which can then be 
+        * passed to the various assertMessage methods.</p>
+        * @see org.aspectj.tools.ajc.AjcTestCase.MessageSpec
+        */
+       public static class Message {
+               private int line = -1;
+               private String text;
+               private String sourceFileName;
+               private ISourceLocation[] seeAlsos;
+               
+               /**
+                * Create a message that will match any compiler message on
+                * the given line.
+                */
+               public Message(int line) {
+                       this.line = line;
+               }
+               
+               /**
+                * Create a message that will match any compiler message on
+                * the given line, where the message text contains <code>text</code>.
+                */
+               public Message(int line, String text) {
+                       this.line = line;
+                       this.text = text;
+               }
+               
+               /**
+                * Create a message that will match any compiler message on
+                * the given line, where the message text contains <code>text</code>.
+                * <p>
+                * If srcFile is non-null, the source file location of the message must
+                * end with <code>srcFile</code>.
+                * </p>
+                * <p>
+                * If <code>seeAlso</code> is non-null, each source location in seeAlso
+                * must be matched by an extraSourceLocation in the message.
+                * </p>
+                */
+               public Message(int line, String srcFile, String text, ISourceLocation[] seeAlso) {
+                       this.line = line;
+                       StringBuffer srcFileName = new StringBuffer();
+                       if (srcFile != null) {
+                               char[] chars = srcFile.toCharArray();
+                               for (int i = 0; i < chars.length; i++) {
+                                       if ((chars[i] == '\\') || (chars[i] == '/')) {
+                                               srcFileName.append(File.separator);
+                                       } else {
+                                               srcFileName.append(chars[i]);
+                                       }
+                               }
+                               this.sourceFileName = srcFileName.toString();                   
+                       }
+                       this.text = text;
+                       this.seeAlsos = seeAlso;
+               }
+               
+               /**
+                * Create a message spec that will match any compiler message where
+                * the message text includes <code>text</code>.
+                */
+               public Message(String text) {
+                       this.text = text;
+               }
+
+               /**
+                * Return true if this message spec matches the given compiler message.
+                */
+               public boolean matches(IMessage message) {
+                       ISourceLocation loc = message.getSourceLocation();
+                       if ((loc == null) && ((line != -1) || (sourceFileName != null))) {
+                               return false;
+                       }
+                       if (line != -1) {
+                               if (loc.getLine() != line) {
+                                       return false;
+                               }
+                       }
+                       if (sourceFileName != null) {
+                               if (!loc.getSourceFile().getPath().endsWith(sourceFileName)) {
+                                       return false;
+                               }
+                       }
+                       if (text != null) {
+                               if (message.getMessage().indexOf(text) == -1) {
+                                       return false;
+                               }
+                       }
+                       if (seeAlsos != null) {
+                               List extraLocations = message.getExtraSourceLocations();
+                               if (extraLocations.size() != seeAlsos.length) {
+                                       return false;
+                               }
+                               for (int i = 0; i < seeAlsos.length; i++) {
+                                       if (!hasAMatch(extraLocations,seeAlsos[i])) {
+                                               return false;
+                                       }
+                               }
+                       }
+                       return true;
+               }
+               
+               private boolean hasAMatch(List srcLocations,ISourceLocation sLoc) {
+                       for (Iterator iter = srcLocations.iterator(); iter.hasNext();) {
+                               ISourceLocation thisLoc = (ISourceLocation) iter.next();
+                               if (thisLoc.getLine() == sLoc.getLine()) {
+                                       if (thisLoc.getSourceFile().getPath().equals(sLoc.getSourceFile().getPath())) {
+                                               return true;
+                                       }
+                               }                               
+                       }
+                       return false;
+               }
+
+               /**
+                * Returns a string indicating what this <code>Message</code> will match.
+                */
+               public String toString() {
+                       StringBuffer buff = new StringBuffer();
+                       buff.append("message ");
+                       if (sourceFileName != null) {
+                               buff.append("in file ");
+                               buff.append(sourceFileName);
+                               buff.append(" ");
+                       }
+                       if (line != -1) {
+                               buff.append("on line ");
+                               buff.append(line);
+                               buff.append(" ");
+                       }
+                       if (text != null) {
+                               buff.append("containing text '");
+                               buff.append(text);
+                               buff.append("' ");
+                       }
+                       if (seeAlsos != null) {
+                               buff.append("\n\twith see alsos:");
+                               for (int i = 0; i < seeAlsos.length; i++) {
+                                       buff.append("\t\t");
+                                       buff.append(seeAlsos[i].getSourceFile().getPath());
+                                       buff.append(":");
+                                       buff.append(seeAlsos[i].getLine());
+                               }
+                       }
+                       return buff.toString();
+               }
+       }
+       
+       /**
+        * Helper class that represents the specification of a set of
+        * messages expected to be produced from a compiler run.
+        * <p>
+        * Instances of MessageSpec are passed to the assertMessage methods
+        * to validate <code>CompilationResult</code>s.
+        */
+       public static class MessageSpec {
+               
+               /**
+                * Convenience constant that matches a CompilationResult with
+                * any number of information messages, but no others.
+                */
+               public static final MessageSpec EMPTY_MESSAGE_SET = 
+                       new MessageSpec(
+                                       null,
+                                       Collections.EMPTY_LIST,
+                                       Collections.EMPTY_LIST,
+                                       Collections.EMPTY_LIST);
+               
+               boolean ignoreInfos = true;
+               public List fails;
+               public List infos;
+               public List warnings;
+               public List errors;
+               
+               /**
+                * Set to true to enable or disable comparison of information messages. 
+                */
+               public void setInfoComparison(boolean enabled) {
+                       this.ignoreInfos = !enabled;
+               }
+               
+               /**
+                * True if information messages are not being included in matching. 
+                */
+               public boolean isIgnoringInfoMessages() {
+                       return ignoreInfos;
+               }
+               
+               /**
+                * Create a message specification to test a CompilationResult for a 
+                * given set of info, warning, error, and fail messages.
+                * @param infos The set of info messages to test for. Specifying a non-null value
+                * for this parameter enables info message comparison.
+                * @param warnings The set of warning messages to test for - can pass null to indicate
+                * empty set.
+                * @param errors The set of error messages to test for - can pass null to indicate
+                * empty set.
+                * @param fails The set of fail or abort messages to test for - can pass null to indicate
+                * empty set.
+                */
+               public MessageSpec(List infos, List warnings, List errors, List fails) {
+                       if (infos != null) {
+                               this.infos = infos;
+                               ignoreInfos = false;
+                       } else {
+                               this.infos = Collections.EMPTY_LIST;
+                       }
+                       this.warnings = ((warnings == null) ? Collections.EMPTY_LIST : warnings);
+                       this.errors = ((errors == null) ? Collections.EMPTY_LIST : errors);
+                       this.fails = ((fails == null) ? Collections.EMPTY_LIST : fails);
+               }
+               
+               /**
+                * Create a message specification to test a CompilationResult for a given
+                * set of info, warning, and error messages. The presence of any fail or
+                * abort messages in a CompilationResult will be a test failure.
+                */
+               public MessageSpec(List infos, List warnings, List errors) {
+                       this(infos,warnings,errors,null);
+               }
+
+               /**
+                * Create a message specification to test a CompilationResult for a given
+                * set of warning, and error messages. The presence of any fail or
+                * abort messages in a CompilationResult will be a test failure. Informational
+                * messages will be ignored.
+                */
+               public MessageSpec(List warnings, List errors) {
+                       this(null,warnings,errors,null);
+               }
+       }
+       
+       /**
+        * Helper class representing the results of running a test program built
+        * by the compiler. Provides access to the standard out and error of the
+        * program, and the actual command that was executed.
+        */
+       public static class RunResult {
+               private String command;
+               private String stdOut;
+               private String stdErr;
+               
+               protected RunResult(String command, String stdOut, String stdErr) {
+                       this.command = command;
+                       this.stdOut = stdOut;
+                       this.stdErr = stdErr;
+               }
+               
+               /**
+                * Return the command that was executed, e.g. "java Driver".
+                */
+               public String getCommand() { return command; }
+               /**
+                * The standard output from the run.
+                */
+               public String getStdOut() {return stdOut;}
+               /**
+                * The standard error from the run.
+                */
+               public String getStdErr() {return stdErr;}
+               
+               /**
+                * Returns the command that was executed to produce this result.
+                */
+               public String toString() { return command; }
+       }
+       
+       /**
+        * Assert that no (non-informational) messages where produced during a compiler run.
+        */
+       public void assertNoMessages(CompilationResult result) {
+               assertNoMessages(result,"Not expecting any compiler messages to be produced");
+       }
+       
+       /**
+        * Assert that no (non-informational) messages where produced during a compiler run.
+        */
+       public void assertNoMessages(CompilationResult result, String message) {
+               assertMessages(result, message,MessageSpec.EMPTY_MESSAGE_SET);
+       }
+
+       /**
+        * Assert that messages in accordance with the <code>expected</code> message specification
+        * where produced during a compiler run.
+        */
+       public void assertMessages(CompilationResult result, MessageSpec expected) {
+               assertMessages(result, "Compilation results did not meet expected messages specification",expected);
+       }
+       
+       /**
+        * Assert that messages in accordance with the <code>expected</code> message specification
+        * where produced during a compiler run.
+        */
+       public void assertMessages(CompilationResult result, String message, MessageSpec expected) {
+               if (result == null) fail("Attempt to compare null compilation results against expected.");
+               List missingFails = copyAll(expected.fails);
+               List missingInfos = copyAll(expected.infos);
+               List missingWarnings = copyAll(expected.warnings);
+               List missingErrors = copyAll(expected.errors);
+               List extraFails = copyAll(result.getFailMessages());
+               List extraInfos = copyAll(result.getInfoMessages());
+               List extraWarnings = copyAll(result.getWarningMessages());
+               List extraErrors = copyAll(result.getErrorMessages());
+               compare(expected.fails,result.getFailMessages(),missingFails,extraFails);
+               compare(expected.warnings,result.getWarningMessages(),missingWarnings,extraWarnings);
+               compare(expected.errors,result.getErrorMessages(),missingErrors,extraErrors);
+               if (!expected.isIgnoringInfoMessages()) {
+                       compare(expected.infos,result.getInfoMessages(),missingInfos,extraInfos);
+               }
+
+               boolean infosEmpty = expected.isIgnoringInfoMessages() ? true: (missingInfos.isEmpty() && extraInfos.isEmpty());
+               if ( !(missingFails.isEmpty() && missingWarnings.isEmpty() && missingErrors.isEmpty() &&
+                          extraFails.isEmpty() && extraWarnings.isEmpty() && extraErrors.isEmpty() && infosEmpty)) {
+                       StringBuffer failureReport = new StringBuffer(message);
+                       failureReport.append("\n");
+                       if (!expected.isIgnoringInfoMessages()) {
+                               addMissing(failureReport,"info",missingInfos);
+                       }
+                       addMissing(failureReport,"warning",missingWarnings);
+                       addMissing(failureReport,"error",missingErrors);
+                       addMissing(failureReport,"fail",missingFails);
+                       if (!expected.isIgnoringInfoMessages()) {
+                               addExtra(failureReport,"info",extraInfos);
+                       }
+                       addExtra(failureReport,"warning",extraWarnings);
+                       addExtra(failureReport,"error",extraErrors);
+                       addExtra(failureReport,"fail",extraFails);
+                       failureReport.append("\ncommand was: ajc");
+                       String[] args = result.getArgs();
+                       for (int i = 0; i < args.length; i++) {
+                               failureReport.append(" ");
+                               failureReport.append(args[i]);
+                       }
+                       String report = failureReport.toString();
+                       fail(message + "\n" + report);
+               }
+       }
+
+       /**
+        * Helper method to build a new message list for passing to a MessageSpec.
+        */
+       protected List newMessageList(Message m1) {
+               List ret = new ArrayList();
+               ret.add(m1);
+               return ret;
+       }
+       
+       /**
+        * Helper method to build a new message list for passing to a MessageSpec.
+        */
+       protected List newMessageList(Message m1, Message m2) {
+               List ret = new ArrayList();
+               ret.add(m1);
+               ret.add(m2);
+               return ret;             
+       }
+       
+       /**
+        * Helper method to build a new message list for passing to a MessageSpec.
+        */
+       protected List newMessageList(Message m1, Message m2, Message m3) {
+               List ret = new ArrayList();
+               ret.add(m1);
+               ret.add(m2);
+               ret.add(m3);
+               return ret;                             
+       }
+       
+       /**
+        * Helper method to build a new message list for passing to a MessageSpec.
+        */
+       protected List newMessageList(Message[] messages) {
+               List ret = new ArrayList();
+               for (int i = 0; i < messages.length; i++) {
+                       ret.add(messages[i]);
+               }
+               return ret;
+       }
+
+       /**
+        * Perform a compilation and return the result.
+        * @param baseDir the base directory relative to which all relative paths and
+        * directories in the arguments will be interpreted.
+        * @param args the compiler arguments, as you would specify on the command-line. 
+        * See the Ajc class for a description of the argument processing done in
+        * order to run the compilation in a sandbox.
+        * @see org.aspectj.tools.ajc.Ajc  
+        */
+       public CompilationResult ajc(File baseDir, String[] args) {
+               try {
+                       ajc.setBaseDir(baseDir);
+                       return ajc.compile(args);
+               } catch(IOException ioEx ) {
+                       fail("IOException thrown during compilation: " + ioEx);
+               }
+               return null;
+       }
+       
+       /**
+        * Indicate whether or not the sandbox should be emptied before the next compile.
+        * @see org.aspectj.tools.ajc.Ajc#setShouldEmptySandbox(boolean)
+        */
+       public void setShouldEmptySandbox(boolean empty) {
+               ajc.setShouldEmptySandbox(empty);
+       }
+
+       /**
+        * Run the given class (main method), and return the result in a RunResult. The program runs with
+        * a classpath containing the sandbox directory, runtime, testing-client, bridge, and
+        * util projects (all used by the Tester class), and any jars in the sandbox.
+        */
+       public RunResult run(String className){
+               return run(className,new String[0],null);
+       }
+       
+       /**
+        * Run the given class, and return the result in a RunResult. The program runs with
+        * a classpath containing the sandbox directory, runtime, testing-client, bridge, and
+        * util projects (all used by the Tester class), and any jars in the sandbox.
+        * @param args the arguments to pass to the program.
+        * @param classpath the execution classpath, the sandbox directory, runtime, testing-client,
+        * bridge, and util projects will all be appended to the classpath, as will any jars in
+        * the sandbox.
+        */
+       public RunResult run(String className, String[] args, String classpath)  {
+               RunResult result = null;
+               StringBuffer cp = new StringBuffer();
+               if (classpath != null) {
+                       cp.append(classpath);
+                       cp.append(File.pathSeparator);
+               }
+               cp.append(ajc.getSandboxDirectory().getAbsolutePath());
+               cp.append(DEFAULT_CLASSPATH_ENTRIES);
+               getAnyJars(ajc.getSandboxDirectory(),cp);
+               classpath = cp.toString();
+               StringBuffer command = new StringBuffer("java -classpath ");
+               command.append(classpath);
+               command.append(" ");
+               command.append(className);
+               for (int i = 0; i < args.length; i++) {
+                       command.append(" ");
+                       command.append(args[i]);
+               }
+               PrintStream systemOut = System.out;
+               PrintStream systemErr = System.err;
+               ByteArrayOutputStream baosOut = new ByteArrayOutputStream();
+               ByteArrayOutputStream baosErr = new ByteArrayOutputStream();
+               StringTokenizer strTok = new StringTokenizer(classpath,File.pathSeparator);
+               URL[] urls = new URL[strTok.countTokens()];
+               try {
+                       for (int i = 0; i < urls.length; i++) {
+                               urls[i] = new File(strTok.nextToken()).getCanonicalFile().toURL();
+                       }
+               } catch (Exception malEx) {
+                       fail("Bad classpath specification: " + classpath);
+               }
+               URLClassLoader cLoader = new URLClassLoader(urls,null);
+               //System.out.println(cLoader.getParent());
+               try {
+                       try {
+                               Class testerClass = cLoader.loadClass("org.aspectj.testing.Tester");
+                               Method setBaseDir = testerClass.getDeclaredMethod("setBASEDIR",new Class[] {File.class});
+                               setBaseDir.invoke(null,new Object[] {ajc.getSandboxDirectory()});
+                       } catch (Exception ex) {
+                               fail ("Unable to prepare org.aspectj.testing.Tester for test run: " + ex);
+                       }
+                       Class toRun = cLoader.loadClass(className);
+                       Method mainMethod = toRun.getMethod("main",new Class[] {String[].class});
+                       System.setOut(new PrintStream(baosOut));
+                       System.setErr(new PrintStream(baosErr));
+                       mainMethod.invoke(null,new Object[] {args});
+                       result = new RunResult(command.toString(),new String(baosOut.toByteArray()),new String(baosErr.toByteArray()));
+               } catch(ClassNotFoundException cnf) {
+                       fail("Can't find class: " + className);
+               } catch(NoSuchMethodException nsm) {
+                       fail(className + " does not have a main method");
+               } catch (IllegalAccessException illEx) {
+                       fail("main method in class " + className + " is not public");
+               } catch (InvocationTargetException invTgt) {
+                       // the main method threw an exception...
+                       fail("Exception thrown by " + className + ".main(String[]) :" + invTgt.getTargetException());
+               } finally {
+                       System.setOut(systemOut);
+                       System.setErr(systemErr);
+               }
+               return result;
+       }
+       
+       private List copyAll(List in) {
+               if (in == Collections.EMPTY_LIST) return in;
+               
+               List out = new ArrayList();
+               for (Iterator iter = in.iterator(); iter.hasNext();) {
+                       out.add(iter.next());                   
+               }
+               return out;
+       }
+       
+       /**
+        * Compare the set of expected messages against the set of actual messages,
+        * leaving in missingElements the set of messages that were expected but did not
+        * occur, and in extraElements the set of messages that occured but were not 
+        * excpected
+        * @param expected  the expected messages
+        * @param actual the actual messages
+        * @param missingElements the missing messages, when passed in must contain all of the expected messages
+        * @param extraElements the additional messages, when passed in must contain all of the actual messages
+        */
+       private void compare(List expected, List actual, List missingElements, List extraElements) {
+               for (Iterator expectedIter = expected.iterator(); expectedIter.hasNext();) {
+                       Message expectedMessage = (Message) expectedIter.next();
+                       for (Iterator actualIter = actual.iterator(); actualIter.hasNext();) {
+                               IMessage actualMessage = (IMessage) actualIter.next();
+                               if (expectedMessage.matches(actualMessage)) {
+                                       missingElements.remove(expectedMessage);
+                                       extraElements.remove(actualMessage);
+                               }
+                       }
+               }
+       }
+
+       private void addMissing(StringBuffer buff,String type, List messages) {
+               if (!messages.isEmpty()) {
+                       buff.append("Missing expected ");
+                       buff.append(type);
+                       buff.append(" messages:\n");
+                       for (Iterator iter = messages.iterator(); iter.hasNext();) {
+                               buff.append("\t");
+                               buff.append(iter.next().toString());
+                               buff.append("\n");
+                       }
+               }
+       }
+       
+       private void addExtra(StringBuffer buff, String type, List messages) {
+               if (!messages.isEmpty()) {
+                       buff.append("Unexpected ");
+                       buff.append(type);
+                       buff.append(" messages:\n");
+                       for (Iterator iter = messages.iterator(); iter.hasNext();) {
+                               buff.append("\t");
+                               buff.append(iter.next().toString());
+                               buff.append("\n");
+                       }
+               }               
+       }
+       
+       // add any jars in the directory to the classpath
+       private void getAnyJars(File dir,StringBuffer buff) {
+               File[] files = dir.listFiles();
+               for (int i = 0; i < files.length; i++) {
+                       if (files[i].getName().endsWith(".jar")) {
+                               buff.append(File.pathSeparator);
+                               buff.append(files[i].getAbsolutePath());
+                       } else if (files[i].isDirectory()) {
+                               getAnyJars(files[i],buff);
+                       }
+               }
+       }
+       
+       /* (non-Javadoc)
+        * @see junit.framework.TestCase#setUp()
+        */
+       protected void setUp() throws Exception {
+               super.setUp();
+               ajc = new Ajc();
+       }
+       /* (non-Javadoc)
+        * @see junit.framework.TestCase#tearDown()
+        */
+       protected void tearDown() throws Exception {
+               super.tearDown();
+       }
+}
diff --git a/org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/AjcTestCaseTest.java b/org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/AjcTestCaseTest.java
new file mode 100644 (file)
index 0000000..79a06df
--- /dev/null
@@ -0,0 +1,51 @@
+/* *******************************************************************
+ * Copyright (c) 2004 IBM Corporation
+ * All rights reserved. 
+ * This program and the accompanying materials are made available 
+ * under the terms of the Common Public License v1.0 
+ * which accompanies this distribution and is available at 
+ * http://www.eclipse.org/legal/cpl-v10.html 
+ *  
+ * Contributors: 
+ *     Adrian Colyer, 
+ * ******************************************************************/
+package org.aspectj.tools.ajc;
+
+import java.io.File;
+
+import org.aspectj.util.FileUtil;
+
+/**
+ * @author colyer
+ * Exercise the features of the AjcTestCase class and check they do as
+ * expected
+ */
+public class AjcTestCaseTest extends AjcTestCase {
+
+       public void testCompile() {
+               File baseDir = new File("../tests/base/test106");
+               String[] args = new String[] {"Driver.java","pkg/Obj.java"};
+               CompilationResult result = ajc(baseDir,args);
+               assertNoMessages(result);
+               RunResult rresult = run("Driver",new String[0],null);
+               System.out.println(rresult.getStdOut());
+       }
+       
+       public void testIncrementalCompile() throws Exception {
+               File baseDir = new File("../tests/incrementalju/initialTests/classAdded");
+               String[] args = new String[] {"-sourceroots","src","-d",".","-incremental"};
+               CompilationResult result = ajc(baseDir,args);
+               assertNoMessages(result);
+               RunResult rr = run("main.Main",new String[0],null);
+               // prepare for increment
+               FileUtil.copyFile(new File(baseDir,"src.20/main/Main.java"),
+                                                new File(ajc.getSandboxDirectory(),"src/main/Main.java"));
+               assertFalse("main.Target does not exist",new File(ajc.getSandboxDirectory(),"main/Target.class").exists());
+               result = ajc.doIncrementalCompile();
+               assertNoMessages(result);
+               assertTrue("main.Target created",new File(ajc.getSandboxDirectory(),"main/Target.class").exists());
+               rr = run("main.Main",new String[0],null);
+               System.out.println(rr.getStdOut());
+       }
+       
+}
diff --git a/org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/CompilationResult.java b/org.aspectj.ajdt.core/testsrc/org/aspectj/tools/ajc/CompilationResult.java
new file mode 100644 (file)
index 0000000..31e2afd
--- /dev/null
@@ -0,0 +1,202 @@
+/* *******************************************************************
+ * Copyright (c) 2004 IBM Corporation
+ * All rights reserved. 
+ * This program and the accompanying materials are made available 
+ * under the terms of the Common Public License v1.0 
+ * which accompanies this distribution and is available at 
+ * http://www.eclipse.org/legal/cpl-v10.html 
+ *  
+ * Contributors: 
+ *     Adrian Colyer, 
+ * ******************************************************************/
+package org.aspectj.tools.ajc;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Utility class that makes the results of a compiler run available.
+ * <p>
+ * Instances of this class are returned by the Ajc.compile() and 
+ * doIncrementalCompile() methods (and the AjcTestCase.ajc() wrapper).
+ * </p>
+ * <p>
+ * This class provides a useful toString() method that is very helpful when
+ * debugging or creating messages for assert statements.
+ * </p>
+ * <p>Note that the stdOut and stdErr captured from the compiler run do
+ * not contain any rendered messages - these are in the messages lists
+ * instead. Therefore for many compiler runs, they will be empty.
+ * </p>
+ */
+public class CompilationResult {
+       
+       private String[] args;
+       private String stdOut;
+       private String stdErr;
+       private List /*IMessage*/ infoMessages;
+       private List /*IMessage*/ errorMessages;
+       private List /*IMessage*/ warningMessages;
+       private List /*IMessage*/ failMessages;
+
+       /**
+        * Build a compilation result - called by the Ajc.compile and
+        * Ajc.doIncrementalCompile methods. Should be no need for you
+        * to construct an instance yourself. 
+        */
+       protected CompilationResult(
+                       String[] args,
+                       String stdOut,
+                       String stdErr,
+                       List infoMessages,
+                       List errorMessages,
+                       List warningMessages,
+                       List failMessages) {
+               this.args = args;
+               this.stdOut = stdOut;
+               this.stdErr = stdErr;
+               this.infoMessages = (infoMessages == null) ? Collections.EMPTY_LIST : infoMessages;
+               this.errorMessages = (errorMessages == null) ? Collections.EMPTY_LIST : errorMessages;
+               this.warningMessages = (warningMessages == null) ? Collections.EMPTY_LIST : warningMessages;            
+               this.failMessages = (failMessages == null) ? Collections.EMPTY_LIST : failMessages;             
+       }
+       
+       /**
+        * The arguments that were passed to the compiler.
+        */
+       public String[] getArgs() { return args; }
+       /**
+        * The standard output written by the compiler, excluding any messages. 
+        */
+       public String getStandardOutput() { return stdOut; }
+       /**
+        * The standard error written by the compiler, excluding any messages. 
+        */
+       public String getStandardError() { return stdErr; }
+       
+       /**
+        * True if the compiler issued any messages of any kind.
+        */
+       public boolean hasMessages() { return (hasInfoMessages() || hasErrorMessages() || hasWarningMessages() || hasFailMessages()); }
+       /**
+        * True if the compiler issued one or more informational messages.
+        */
+       public boolean hasInfoMessages() { return !infoMessages.isEmpty(); }
+       /**
+        * True if the compiler issued one or more error messages.
+        */
+       public boolean hasErrorMessages() { return !errorMessages.isEmpty(); }
+       /**
+        * True if the compiler issued one or more warning messages.
+        */
+       public boolean hasWarningMessages() { return !warningMessages.isEmpty(); }
+       /**
+        * True if the compiler issued one or more fail or abort messages.
+        */
+       public boolean hasFailMessages() { return !failMessages.isEmpty(); }
+       
+       /**
+        * The informational messages produced by the compiler. The list
+        * entries are the <code>IMessage</code> objects created during the
+        * compile - so that you can programmatically test source locations
+        * etc. etc.. It may often be easier to use the <code>assertMessages</code>
+        * helper methods defined in the AjcTestCase class to test for messages
+        * though.
+        * @see org.aspectj.tools.ajc.AjcTestCase
+        */
+       public List /*IMessage*/ getInfoMessages() { return infoMessages; }
+       /**
+        * The error messages produced by the compiler. The list
+        * entries are the <code>IMessage</code> objects created during the
+        * compile - so that you can programmatically test source locations
+        * etc. etc.. It may often be easier to use the <code>assertMessages</code>
+        * helper methods defined in the AjcTestCase class to test for messages
+        * though.
+        * @see org.aspectj.tools.ajc.AjcTestCase
+        */
+       public List /*IMessage*/ getErrorMessages() { return errorMessages; }
+       /**
+        * The warning messages produced by the compiler. The list
+        * entries are the <code>IMessage</code> objects created during the
+        * compile - so that you can programmatically test source locations
+        * etc. etc.. It may often be easier to use the <code>assertMessages</code>
+        * helper methods defined in the AjcTestCase class to test for messages
+        * though.
+        * @see org.aspectj.tools.ajc.AjcTestCase
+        */
+       public List /*IMessage*/ getWarningMessages() { return warningMessages; }
+       /**
+        * The fail or abort messages produced by the compiler. The list
+        * entries are the <code>IMessage</code> objects created during the
+        * compile - so that you can programmatically test source locations
+        * etc. etc.. It may often be easier to use the <code>assertMessages</code>
+        * helper methods defined in the AjcTestCase class to test for messages
+        * though.
+        * @see org.aspectj.tools.ajc.AjcTestCase
+        */
+       public List /*IMessage*/ getFailMessages() { return failMessages; }
+       
+       /**
+        * Returns string containing message count summary, list of messages
+        * by type, and the actual ajc compilation command that was issued.
+        */
+       public String toString() {
+               StringBuffer buff = new StringBuffer();
+               buff.append("AspectJ Compilation Result:\n");
+               int totalMessages = infoMessages.size() + warningMessages.size() + errorMessages.size() + failMessages.size();
+               buff.append(totalMessages);
+               buff.append(" messages");
+               if (totalMessages > 0) {
+                       buff.append(" (");
+                       buff.append(infoMessages.size());
+                       buff.append(" info, ");
+                       buff.append(warningMessages.size());
+                       buff.append(" warning, ");
+                       buff.append(errorMessages.size());
+                       buff.append(" error, ");
+                       buff.append(failMessages.size());
+                       buff.append(" fail)");
+               } 
+               buff.append("\n");
+               int msgNo = 1;
+               for (Iterator iter = failMessages.iterator(); iter.hasNext();) {
+                       buff.append("[fail ");
+                       buff.append(msgNo++);
+                       buff.append("] ");
+                       buff.append(iter.next().toString());
+                       buff.append("\n");
+               }
+               msgNo = 1;
+               for (Iterator iter = errorMessages.iterator(); iter.hasNext();) {
+                       buff.append("[error ");
+                       buff.append(msgNo++);
+                       buff.append("] ");
+                       buff.append(iter.next().toString());                    
+                       buff.append("\n");
+               }
+               msgNo = 1;
+               for (Iterator iter = warningMessages.iterator(); iter.hasNext();) {
+                       buff.append("[warning ");
+                       buff.append(msgNo++);
+                       buff.append("] ");
+                       buff.append(iter.next().toString());                    
+                       buff.append("\n");
+               }
+               msgNo = 1;
+               for (Iterator iter = infoMessages.iterator(); iter.hasNext();) {
+                       buff.append("[info ");
+                       buff.append(msgNo++);
+                       buff.append("] ");
+                       buff.append(iter.next().toString());                    
+                       buff.append("\n");
+               }
+               buff.append("\ncommand was: 'ajc");
+               for (int i = 0; i < args.length; i++) {
+                       buff.append(' ');
+                       buff.append(args[i]);
+               }
+               buff.append("'\n");
+               return buff.toString();
+       }
+}
index 13d7216dde2c33724cf471221cf10a8f8d75b5e7..aa2110c0326811986b1397269e1043ac10909c49 100644 (file)
 package org.aspectj.tools.ajc;
 
 import java.util.ArrayList;
-
-import junit.framework.TestCase;
+import java.util.ResourceBundle;
 
 /**
  * 
  */
-public class MainTest extends TestCase {
+public class MainTest extends AjcTestCase {
        
     public void testMainbare() {
         ArrayList list = new ArrayList();
@@ -32,4 +31,12 @@ public class MainTest extends TestCase {
 //        assertTrue(-1 != ((String)o).indexOf("-aspectpath"));
 //        assertTrue(-1 != ((String)o).indexOf("-incremental"));
     }
+    
+    public void testDashX() {
+       String xoptionText = ResourceBundle.getBundle("org.aspectj.ajdt.ajc.messages").getString("xoption.usage");
+        xoptionText = xoptionText.substring("{compiler.name}".length());
+               CompilationResult result = ajc(null,new String[] {"-X"});
+               assertMessages(result,"Expecting xoptions usage message",
+                               new MessageSpec(null,null,null,newMessageList(new Message(xoptionText))));
+    }
 }