diff options
author | wisberg <wisberg> | 2002-12-16 18:14:27 +0000 |
---|---|---|
committer | wisberg <wisberg> | 2002-12-16 18:14:27 +0000 |
commit | 1025cde05abe95be6ca65aab3ba126258efc647f (patch) | |
tree | 7d40d6fa06fef869e4d38cbe44ce431011e2eff8 /testing | |
parent | d842c4f1139629c1f062b74ba818d233b2c31043 (diff) | |
download | aspectj-1025cde05abe95be6ca65aab3ba126258efc647f.tar.gz aspectj-1025cde05abe95be6ca65aab3ba126258efc647f.zip |
initial version
Diffstat (limited to 'testing')
101 files changed, 18448 insertions, 0 deletions
diff --git a/testing/.classpath b/testing/.classpath new file mode 100644 index 000000000..f24197aa8 --- /dev/null +++ b/testing/.classpath @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="testsrc"/> + <classpathentry kind="var" path="JRE_LIB" rootpath="JRE_SRCROOT" sourcepath="JRE_SRC"/> + <classpathentry kind="lib" path="/lib/ant/lib/ant.jar" sourcepath="/lib/ant/ant-src.zip"/> + <classpathentry kind="lib" path="/lib/junit/junit.jar" sourcepath="/lib/junit/junit-src.jar"/> + <classpathentry kind="lib" path="/lib/jdiff/jdiff.jar"/> + <classpathentry kind="lib" path="/lib/regexp/jakarta-regexp-1.2.jar"/> + <classpathentry kind="src" path="/bridge"/> + <classpathentry kind="src" path="/util"/> + <classpathentry kind="src" path="/testing-client"/> + <classpathentry kind="lib" path="/lib/ant/lib/xercesImpl.jar"/> + <classpathentry kind="lib" path="/lib/ant/lib/xml-apis.jar"/> + <classpathentry kind="lib" path="/lib/commons/commons.jar" sourcepath="/lib/commons/commons-src.zip"/> + <classpathentry kind="src" path="/testing-util"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/testing/.project b/testing/.project new file mode 100644 index 000000000..12bc71513 --- /dev/null +++ b/testing/.project @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>testing</name> + <comment></comment> + <projects> + <project>bridge</project> + <project>testing-client</project> + <project>testing-util</project> + <project>util</project> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/testing/src/.cvsignore b/testing/src/.cvsignore new file mode 100644 index 000000000..a3f0b1b77 --- /dev/null +++ b/testing/src/.cvsignore @@ -0,0 +1 @@ +*.lst diff --git a/testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java b/testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java new file mode 100644 index 000000000..ec112d2b5 --- /dev/null +++ b/testing/src/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java @@ -0,0 +1,1864 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.internal.tools.ant.taskdefs; + +import java.beans.*; +import java.io.*; +import java.util.*; +import java.util.List; +import java.text.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.*; +import org.apache.tools.ant.types.*; +import org.aspectj.util.LangUtil; + +import java.awt.*; +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; +import javax.swing.table.*; +import javax.swing.text.*; + +public class Ajctest extends Task implements PropertyChangeListener { + private static Ajctest CURRENT_AJCTEST; + + // todo shutdown hook assumes one task per VM + public Ajctest() { + super(); + CURRENT_AJCTEST = this; + } + + private static boolean firstTime = true; + + public final PropertyChangeSupport bean = new PropertyChangeSupport(this); + + { + bean.addPropertyChangeListener(this); + } + + public void propertyChange(PropertyChangeEvent evt) { + String name = evt.getPropertyName(); + if ("ajdoc.good".equals(name)) ajdocStats.goods++; + else if ("ajc.good".equals(name)) ajcStats.goods++; + else if ("run.good".equals(name)) runStats.goods++; + if ("ajdoc.fail".equals(name)) ajdocStats.fails++; + else if ("ajc.fail".equals(name)) ajcStats.fails++; + else if ("run.fail".equals(name)) runStats.fails++; + } + + private void fire(String prop, Object oldval, Object newval) { + bean.firePropertyChange(prop, oldval, newval); + } + + private void fire(String prop) { + fire(prop, "dummy-old", "dummy-new"); + } + + private static boolean dumpresults = false; + private static Stats ajdocStats = new Stats(); + private static Stats ajcStats = new Stats(); + private static Stats runStats = new Stats(); + private static Stats errorStats = new Stats(); + private static final String NO_TESTID = "NONE"; + private File workingdir = new File("ajworkingdir"); //XXX + + //fields + private String testId = NO_TESTID; + private List args = new Vector(); + private List testsets = new Vector(); + private Path classpath; + private Path internalclasspath; + private File destdir; + private File dir; + private File errordir; + private File errorfile; + private List testclasses = new Vector(); + private boolean nocompile; + private Ajdoc ajdoc = null; + private boolean noclean; + private boolean noverify; + private List depends = new Vector(); + //end-fields + + public Argfile createArgfile() { + return createTestset().createArgfile(); + } + + public void setNoverify(boolean input) { + if (input != noverify) noverify = input; + } + + public void setOwningTarget(Target target) { + super.setOwningTarget(target); + if (null != target) { + //setTestId(target.getName()); + } + } + + public void setTestId(String str) { + if ((null != str) && (0 < str.trim().length())) { + testId = str; + } + } + + public void setArgs(String str) { + if (str == null || str.length() < 1) return; + StringTokenizer tok = new StringTokenizer(str, ",", false); + while (tok.hasMoreTokens()) { + String name = tok.nextToken().trim(); + if (0 < name.length()) { + parse(name.startsWith("J") ? createJarg() : createArg(), name); + } + } + } + + private void parse(Argument arg, String name) { + int itilde = name.lastIndexOf("~"); + if (itilde != -1) { + name = name.substring(0, itilde) + name.substring(itilde+1); + } + int ieq = name.lastIndexOf("="); + int icolon = name.lastIndexOf(":"); + int ileft = name.lastIndexOf("["); + int iright = name.lastIndexOf("]"); + boolean always = true; + String rest = ""; + String newName = name; + if (ieq != -1) { + rest = name.substring(ieq+1); + newName = name.substring(0, ieq); + always = true; + } else if (icolon != -1) { + rest = name.substring(icolon+1); + newName = name.substring(0, icolon); + always = false; + } else if (ileft != -1) { + newName = name.substring(0, ileft); + always = true; + } + String values = ileft == -1 ? rest : + name.substring(ileft+1, iright > ileft ? iright : rest.length()-1); + String value = null; + if (itilde != -1) { + String prop = project.getUserProperty(values); + if (prop == null) { + prop = project.getProperty(values); + } + if (prop != null) { + value = prop; + } + } + if (value != null) { + arg.setValue(value); + } else { + arg.setValues(values); + } + arg.setName(newName); + arg.setAlways(always); + } + + public Argument createJarg() { + Argument arg = new Argument(true); + args.add(arg); + return arg; + } + + + public Argument createArg() { + Argument arg = new Argument(false); + args.add(arg); + return arg; + } + + public void setClasspath(Path path) { + if (classpath == null) { + classpath = path; + } else { + classpath.append(path); + } + } + + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(project); + } + return classpath.createPath(); + } + + public void setClasspathRef(Reference r) { + createClasspath().setRefid(r); + } + + public void setInternalclasspath(Path path) { + if (internalclasspath == null) { + internalclasspath = path; + } else { + internalclasspath.append(path); + } + } + + public Path createInternalclasspath() { + if (internalclasspath == null) { + internalclasspath = new Path(project); + } + return internalclasspath.createPath(); + } + + public void setInternalclasspathRef(Reference r) { + createInternalclasspath().setRefid(r); + } + + public void setDestdir(String destdir) { + this.destdir = project.resolveFile(destdir); + } + + public void setDir(File dir) { + this.dir = dir; + } + + public void setErrordir(File dir) { + this.errordir = errordir; + } + + public void setErrorfile(File errorfile) { + this.errorfile = errorfile; + } + + public Run createJava() { + Run testclass = new Run(project); + testclasses.add(testclass); + return testclass; + } + + public void setClasses(String str) { + for (StringTokenizer t = new StringTokenizer(str, ", ", false); + t.hasMoreTokens();) { + createJava().setClassname(t.nextToken().trim()); + } + } + + public void setTestclass(String testclass) { + createJava().setClassname(testclass); + } + + public void setAjdoc(boolean b) { + if (b && ajdoc == null) { + createAjdoc(); + } else if (!b) { + ajdoc = null; + } + } + + public void setAjdocargs(String str) { + createAjdoc(); + for (StringTokenizer t = new StringTokenizer(str, ", ", false); + t.hasMoreTokens();) { + ajdoc.createArg().setValue(t.nextToken().trim()); + } + } + + public void addAjdoc(Ajdoc ajdoc) { + this.ajdoc = ajdoc; + } + + public Ajdoc createAjdoc() { + return ajdoc = new Ajdoc(); + } + + public static class Argument { + private String name; + private List values = new Vector(); + private boolean always = true; + final boolean isj; + public Argument(boolean isj) { + this.isj = isj; + } + public void setName(String str) { + this.name = str.startsWith("-") ? str : + ("-" + (str.startsWith("J") ? str.substring(1) : str)); + } + public void setValues(String str) { + values = new Vector(); + StringTokenizer tok = new StringTokenizer(str, ", ", false); + while (tok.hasMoreTokens()) { + values.add(tok.nextToken().trim()); + } + } + public void setValue(String value) { + (values = new Vector()).add(value); + } + public void setAlways(boolean always) { + this.always = always; + } + public String toString() { return name + ":" + values; } + } + + public void setNocompile(boolean nocompile) { + this.nocompile = nocompile; + } + + private static class Stats { + int goods = 0; + int fails = 0; + } + + private static class Arg { + final String name; + final String value; + final boolean isj; + Arg(String name, String value, boolean isj) { + this.name = name; + this.value = value; + this.isj = isj; + } + public String toString() { + return name + (!"".equals(value) ? " " + value : ""); + } + } + + public Testset createTestset() { + Testset testset = new Testset(); + testsets.add(testset); + return testset; + } + + public void setNoclean(boolean noclean) { + this.noclean = noclean; + } + + public void setDepends(String depends) { + for (StringTokenizer t = new StringTokenizer(depends, ", ", false); + t.hasMoreTokens();) { + this.depends.add(t.nextToken().trim()); + } + } + //end-methods + + public static class Argfile { + private String name; + public void setName(String name) { this.name = name; } + } + + public class Ajdoc { + private Commandline cmd = new Commandline(); + public Commandline.Argument createArg() { return cmd.createArgument(); } + public Commandline getCommandline() { return cmd; } + public String toString() { return cmd + ""; } + } + + public class Testset extends FileSet { + private List argfileNames = new Vector(); + public List argfiles; + public List files; + public List args = new Vector(); + public String classname; + private boolean havecludes = false; + private List testclasses = new Vector(); + private Path classpath; + private Path internalclasspath; + private Ajdoc ajdoc = null; + private boolean fork = false; + private boolean noclean; + private List depends = new Vector(); + public String toString() { + String str = ""; + if (files.size() > 0) { + str += "files:" + "\n"; + for (Iterator i = files.iterator(); i.hasNext();) { + str += "\t" + i.next() + "\n"; + } + } + if (argfiles.size() > 0) { + str += "argfiles:" + "\n"; + for (Iterator i = argfiles.iterator(); i.hasNext();) { + str += "\t" + i.next() + "\n"; + } + } + if (args.size() > 0) { + str += "args:" + "\n"; + for (Iterator i = args.iterator(); i.hasNext();) { + str += "\t" + i.next() + "\n"; + } + } + if (testclasses.size() > 0) { + str += "classes:" + "\n"; + for (Iterator i = testclasses.iterator(); i.hasNext();) { + str += "\t" + i.next() + "\n"; + } + } + return str; + } + public void setIncludes(String includes) { + super.setIncludes(includes); + havecludes = true; + } + public void setExcludes(String excludes) { + super.setExcludes(excludes); + havecludes = true; + } + public void setIncludesfile(File includesfile) { + super.setIncludesfile(includesfile); + havecludes = true; + } + public void setExcludesfile(File excludesfile) { + super.setExcludesfile(excludesfile); + havecludes = true; + } + + public void setArgfile(String name) { + createArgfile().setName(name); + } + + public void setArgfiles(String str) { + StringTokenizer tok = new StringTokenizer(str, ", ", false); + while (tok.hasMoreTokens()) { + createArgfile().setName(tok.nextToken().trim()); + } + + } + public Argfile createArgfile() { + Argfile argfile = new Argfile(); + argfileNames.add(argfile); + return argfile; + } + public Run createJava() { + // See crashing note + //Run testclass = new Run(); + Run testclass = new Run(project); + this.testclasses.add(testclass); + return testclass; + } + public void addJava(Run run) { + this.testclasses.add(run); + } + public void setJava(String str) { + StringTokenizer t = new StringTokenizer(str, " "); + Run run = createJava(); + run.setClassname(t.nextToken().trim()); + while (t.hasMoreTokens()) { + run.createArg().setValue(t.nextToken().trim()); + } + } + public void setTestclass(String testclass) { + createJava().setClassname(testclass); + } + + public void setClasses(String str) { + for (StringTokenizer t = new StringTokenizer(str, ", ", false); + t.hasMoreTokens();) { + createJava().setClassname(t.nextToken().trim()); + } + } + public void setClasspath(Path path) { + if (classpath == null) { + classpath = path; + } else { + classpath.append(path); + } + } + + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(project); + } + return classpath.createPath(); + } + + public void setClasspathRef(Reference r) { + createClasspath().setRefid(r); + } + public void setInternalclasspath(Path path) { + if (internalclasspath == null) { + internalclasspath = path; + } else { + internalclasspath.append(path); + } + } + + public Path createInternalclasspath() { + if (internalclasspath == null) { + internalclasspath = new Path(project); + } + return internalclasspath.createPath(); + } + + public void setInternalclasspathRef(Reference r) { + createInternalclasspath().setRefid(r); + } + + public void setAjdoc(boolean b) { + if (b && ajdoc == null) { + createAjdoc(); + } else if (!b) { + this.ajdoc = null; + } + } + public Ajdoc getAjdoc() { return this.ajdoc; } + public void setAjdocargs(String str) { + createAjdoc(); + for (StringTokenizer t = new StringTokenizer(str, ", ", false); + t.hasMoreTokens();) { + this.ajdoc.createArg().setValue(t.nextToken().trim()); + } + } + public void addAjdoc(Ajdoc ajdoc) { + this.ajdoc = ajdoc; + } + public Ajdoc createAjdoc() { + return this.ajdoc = new Ajdoc(); + } + public void setFork(boolean fork) { + this.fork = fork; + } + public void setNoclean(boolean noclean) { + this.noclean = noclean; + } + public void setDepends(String depends) { + for (StringTokenizer t = new StringTokenizer(depends, ", ", false); + t.hasMoreTokens();) { + this.depends.add(t.nextToken().trim()); + } + } + //end-testset-methods + + public void resolve() throws BuildException { + if (dir != null) this.setDir(dir); + File src = getDir(project); + argfiles = new Vector(); + files = new Vector(); + for(Iterator iter = argfileNames.iterator(); iter.hasNext();) { + String name = ((Argfile)iter.next()).name; + File argfile = new File(src, name); + if (check(argfile, name, location)) argfiles.add(argfile); + } + if (havecludes || argfiles.size() <= 0) { + String[] filenames = + getDirectoryScanner(project).getIncludedFiles(); + for (int j = 0; j < filenames.length; j++) { + String name = filenames[j]; + if (name.endsWith(".java")) { + File file = new File(src, name); + if (check(file, name, location)) files.add(file); + } + } + } + for (Iterator i = Ajctest.this.testclasses.iterator(); + i.hasNext();) { + this.testclasses.add((Run)i.next()); + } + if (this.classpath == null) { + setClasspath(Ajctest.this.classpath); + } + if (this.internalclasspath == null) { + setInternalclasspath(Ajctest.this.internalclasspath); + } + if (this.ajdoc == null) { + this.ajdoc = Ajctest.this.ajdoc; + } + if (this.fork) { + for (Iterator i = this.testclasses.iterator(); i.hasNext();) { + ((Run)i.next()).setFork(fork); + } + } + if (!this.noclean) { + this.noclean = Ajctest.this.noclean; + } + this.depends.addAll(Ajctest.this.depends); + } + private boolean check(File file, String name, Location loc) + throws BuildException { + loc = loc != null ? loc : location; + if (file == null) { + throw new BuildException + ("file " + name + " is null!", loc); + } + if (!file.exists()) { + throw new BuildException + ("file " + file + " with name " + name + + " doesn't exist!", loc); + } + return true; + } + public void setArgs(String str) { + if (str == null || str.length() < 1) return; + StringTokenizer tok = new StringTokenizer(str, ",", false); + while (tok.hasMoreTokens()) { + String name = tok.nextToken().trim(); + parse(name.startsWith("J") ? createJarg() : createArg(), name); + } + } + + public Argument createJarg() { + Argument arg = new Argument(true); + args.add(arg); + return arg; + } + + public Argument createArg() { + Argument arg = new Argument(false); + args.add(arg); + return arg; + } + } + + private void prepare() throws BuildException { + + } + + private void finish() throws BuildException { + if (errors.size() > 0) { + log(""); + log("There " + w(errors) + " " + errors.size() + " errors:"); + for (int i = 0; i < errors.size(); i++) { + log(" ", (Failure)errors.get(i), i); + } + } + allErrors.addAll(errors); + } + + private void log(String space, Failure failure, int num) { + String number = "[" + num + "] "; + log(enough(number, 60, '-')); + for (int i = number.length()-1; i > 0; i--) space += " "; + log(space, failure.testset.files, "files:"); + log(space, failure.testset.argfiles, "argfiles:"); + log(space, failure.args, "args:"); + log(space + "msgs:" + failure.msgs); + } + + + private String enough(String str, int size, char filler) { + while (str.length() < size) str += filler; + return str; + } + + + private void log(String space, List list, String title) { + if (list == null || list.size() < 1) return; + log(space + title); + for (Iterator i = list.iterator(); i.hasNext();) { + log(space + " " + i.next()); + } + } + + private void execute(Testset testset, List args) throws BuildException { + if (testset.files.size() > 0) { + log("\tfiles:"); + for (Iterator i = testset.files.iterator(); + i.hasNext();) { + log("\t " + i.next()); + } + } + if (testset.argfiles.size() > 0) { + log("\targfiles:"); + for (Iterator i = testset.argfiles.iterator(); + i.hasNext();) { + log("\t " + i.next()); + } + } + if (args.size() > 0) { + log("\targs:"); + for (Iterator i = args.iterator(); + i.hasNext();) { + log("\t " + i.next()); + } + } + if (testset.testclasses.size() > 0) { + log("\tclasses:"); + for (Iterator i = testset.testclasses.iterator(); + i.hasNext();) { + log("\t " + i.next()); + } + } + if (!testset.noclean && + (!isSet("noclean") && !isSet("nocompile"))) { + delete(destdir); + make(destdir); + } + delete(workingdir); + make(workingdir); + for (Iterator i = testset.depends.iterator(); i.hasNext();) { + String target = i.next()+""; + // todo: capture failures here? + project.executeTarget(target); + } + int exit; + if (!isSet("nodoc") && testset.ajdoc != null) { + log("\tdoc... " + testset.ajdoc); + AjdocWrapper ajdoc = new AjdocWrapper(testset, args); + if ((exit = ajdoc.run()) != 0) { + post(testset, args, ajdoc.msgs, exit, "ajdoc"); + } else { + fire("ajdoc.good"); + } + fire("ajdoc.done"); + log("\tdone with ajdoc."); + } + boolean goodCompile = true; + if (!isSet("nocompile") && !nocompile) { + log("\tcompile" + + (testset.noclean ? "(boostrapped)" : "") + "..."); + //AjcWrapper ajc = new AjcWrapper(testset, args); + JavaCommandWrapper ajc; + // XXX dependency on Ant property ajctest.compiler + final String compiler = getAntProperty("ajctest.compiler"); + if ("eclipse".equals(compiler) || "eajc".equals(compiler)) { + ajc = new EAjcWrapper(testset, args); + } else if ((null == compiler) || "ajc".equals(compiler)) { + ajc = new AjcWrapper(testset, args); + } else if ("javac".equals(compiler)) { + throw new Error("javac not supported"); + //ajc = new JavacWrapper(testset, args); + } else { + throw new Error("unknown compiler: " + compiler); + } + + System.out.println("using compiler: " + ajc); + try { + if ((exit = ajc.run()) != 0) { + post(testset, args, ajc.msgs, exit, "ajc"); + goodCompile = false; + } else { + fire("ajc.good"); + } + fire("ajc.done"); + } catch (Throwable ___) { + post(testset, args, ___+"", -1, "ajc"); + goodCompile = false; + } + } + if (!goodCompile) { + post(testset, new Vector(), + "couldn't run classes " + testset.testclasses + + "due to failed compile", + -1, "run"); + + } else if (!isSet("norun")) { + for (Iterator i = testset.testclasses.iterator(); + i.hasNext();) { + Run testclass = (Run)i.next(); + log("\ttest..." + testclass.classname()); + if (null != destdir) { + testclass.setClassesDir(destdir.getAbsolutePath()); + } + if ((exit = testclass.executeJava()) != 0) { + post(testset, new Vector(), testclass.msgs, exit, "run"); + } else { + fire("run.good"); + } + fire("run.done"); + } + } + log(""); + } + + public void execute() throws BuildException { + gui(this); + dumpresults = isSet("dumpresults"); + prepare(); + log(testsets.size() + " testset" + s(testsets), + Project.MSG_VERBOSE); + Map testsetToArgcombo = new HashMap(); + List argcombos = new Vector(); + for (Iterator iter = testsets.iterator(); iter.hasNext();) { + Testset testset = (Testset)iter.next(); + testset.resolve(); + List bothargs = new Vector(args); + bothargs.addAll(testset.args); + List argcombo = argcombo(bothargs); + argcombos.add(new Integer(argcombo.size())); + testsetToArgcombo.put(testset, argcombo); + } + while (!testsetToArgcombo.isEmpty()) { + int _ = 1; + for (Iterator iter = testsets.iterator(); iter.hasNext(); _++) { + Testset testset = (Testset)iter.next(); + List argcombo = (List)testsetToArgcombo.get(testset); + if (argcombo.size() == 0) { + testsetToArgcombo.remove(testset); + continue; + } + List args = (List)argcombo.remove(0); + final String startStr = "Testset " + _ + " of " + testsets.size(); + String str = startStr + " / Combo " + _ + " of " + argcombos.size(); + log("---------- " + str + " ----------"); + execute(testset, args); + } + } + +// for (Iterator iter = testsets.iterator(); iter.hasNext(); _++) { +// Testset testset = (Testset)iter.next(); +// testset.resolve(); +// List bothargs = new Vector(args); +// bothargs.addAll(testset.args); +// int __ = 1; +// List argcombo = argcombo(bothargs); +// log(argcombo.size() + " combination" + s(argcombo), +// Project.MSG_VERBOSE); +// final String startStr = "Testset " + _ + " of " + testsets.size(); +// for (Iterator comboiter = argcombo.iterator(); +// comboiter.hasNext(); __++) { +// List args = (List)comboiter.next(); +// execute(testset, args); +// log(""); +// } +// } + finish(); + } + + private void delete(File dir) throws BuildException { + Delete delete = (Delete)project.createTask("delete"); + delete.setDir(dir); + delete.execute(); + } + + private void make(File dir) throws BuildException { + Mkdir mkdir = (Mkdir)project.createTask("mkdir"); + mkdir.setDir(dir); + mkdir.execute(); + } + + private String getAntProperty(String name) { + String uprop = project.getUserProperty(name); + if (null == uprop) { + uprop = project.getProperty(name); + } + return uprop; + } + + private boolean isSet(String name) { + String uprop = project.getUserProperty(name); + if (uprop == null || + "no".equals(uprop) || + "false".equals(uprop)) return false; + String prop = project.getProperty(name); + if (prop == null || + "no".equals(prop) || + "false".equals(prop)) return false; + return true; + } + + /** + * Interpose Wrapper class to catch and report exceptions + * by setting a positive value for System.exit(). + * (In some cases it seems that Exceptions are not being reported + * as errors in the tests.) + * This forces the VM to fork. A forked VM is required for + * two reasons: + * (1) The wrapper class may have been defined by a different + * class loader than the target class, so it would not be able + * to load the target class; + * <p> + * (2) Since the wrapper class is generic, we have to pass in + * the name of the target class. I choose to do this using + * VM properties rather than hacking up the arguments. + * <p>todo: relies on name/value of property "taskdef.jar" + * to add jar with wrapper to invoking classpath. + * <p> + * It is beneficial for another reason: + * (3) The wrapper class can be asked to test-load all classes + * in a classes dir, by setting a VM property. This class + * sets up the property if the value is defined using + * <code>setClassesDir(String)</code> + * <p>todo: if more tunnelling, generalize and parse. + */ + public class RunWrapper extends Java { + public final Class LINK_WRAPPER_CLASS = MainWrapper.class; + /** tracked in MainWrapper.PROP_NAME */ // todo: since reflective, avoid direct + public final String PROP_NAME = "MainWrapper.classname"; + /** tracked in MainWrapper.CLASSDIR_NAME */ + public final String CLASSDIR_NAME = "MainWrapper.classdir"; + public final String WRAPPER_CLASS + = "org.aspectj.internal.tools.ant.taskdefs.MainWrapper"; + private String classname; + protected String classesDir; + /** capture classname here, replace with WRAPPER_CLASS */ + public void setClassname(String classname) { + super.setClassname(WRAPPER_CLASS); + this.classname = classname; + } + + /** + * Setup the requirements for the wrapper class: + * <li>fork to get classpath and VM properties right</li> + * <li>set VM property</li> + * <li>add ${ajctest.wrapper.jar} (with wrapper class) to the classpath</li> + */ + private void setup() { + setFork(true); + Commandline.Argument cname = createJvmarg(); + cname.setValue("-D"+PROP_NAME+"="+classname); + if (!noverify) { + cname = createJvmarg(); + cname.setValue("-Xfuture"); // todo: 1.2 or later.. + } + if (null != classesDir) { + cname = createJvmarg(); + cname.setValue("-D"+CLASSDIR_NAME+"="+classesDir); + } + // todo dependence on name/setting of ajctest.wrapper.jar + String value = project.getProperty("ajctest.wrapper.jar"); + if (null != value) { + Path wrapperPath = new Path(project, value); + RunWrapper.this.createClasspath().append(wrapperPath); + } + } + + /** do setup, then super.execute() */ + public int executeJava() { + setup(); + int result = super.executeJava(); + // snarf - also load all classes? + return result; + } + + /** set directory to scan for classes */ + public void setClassesDir(String dir) { + classesDir = dir; + } + } + + public class Run extends RunWrapper { + //public class Run extends Java + private Path bootclasspath; + public void setBootbootclasspath(Path path) { + if (bootclasspath == null) { + bootclasspath = path; + } else { + bootclasspath.append(path); + } + } + public Path createBootbootclasspath() { + if (bootclasspath == null) bootclasspath = new Path(this.project); + return bootclasspath.createPath(); + } + public void setBootbootclasspathRef(Reference r) { + createBootbootclasspath().setRefid(r); + } + private Path bootclasspatha; + public void setBootbootclasspatha(Path path) { + if (bootclasspatha == null) { + bootclasspatha = path; + } else { + bootclasspatha.append(path); + } + } + public Path createBootbootclasspatha() { + if (bootclasspatha == null) bootclasspatha = new Path(this.project); + return bootclasspatha.createPath(); + } + public void setBootbootclasspathaRef(Reference r) { + createBootbootclasspatha().setRefid(r); + } + private Path bootclasspathp; + public void setBootbootclasspathp(Path path) { + if (bootclasspathp == null) { + bootclasspathp = path; + } else { + bootclasspathp.append(path); + } + } + public Path createBootbootclasspathp() { + if (bootclasspathp == null) bootclasspathp = new Path(this.project); + return bootclasspathp.createPath(); + } + public void setBootbootclasspathpRef(Reference r) { + createBootbootclasspathp().setRefid(r); + } + public Run(Project project) { + super(); + //this.project = Ajctest.this.project; + this.setTaskName("ajcjava"); + this.project = project; + } + public String msgs = ""; + public int executeJava() { + Path cp = Ajctest.this.classpath != null ? Ajctest.this.classpath : + new Path(this.project, destdir.getAbsolutePath()); + cp.append(Path.systemClasspath); + this.setClasspath(cp); + if (bootclasspath != null) { + setFork(true); + createJvmarg().setValue("-Xbootclasspath:" + bootclasspath); + } + if (bootclasspatha != null) { + setFork(true); + createJvmarg().setValue("-Xbootclasspath/a:" + bootclasspatha); + } + if (bootclasspathp != null) { + setFork(true); + createJvmarg().setValue("-Xbootclasspath/p:" + bootclasspathp); + } + int exit = -1; + // todo: add timeout feature todo: this or below? + try { + exit = super.executeJava(); + } catch (Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + t.printStackTrace(out); + msgs = sw.toString(); + out.close(); + // todo: return exit code + } + return exit; + } + public String _classname; + public String classname() { return _classname; } + public void setClassname(String classname) { + super.setClassname(_classname = classname); + } + public String toString() { return _classname; } + } + // class Run + // todo: need to run in a wrapper which report non-zero int on exception + // todo: unused method? see executeJava above. + private int java(String classname, Collection args) throws BuildException { + Java java = (Java)project.createTask("java"); + java.setClassname(classname); + for (Iterator i = args.iterator(); i.hasNext();) { + Object o = i.next(); + Commandline.Argument arg = java.createArg(); + if (o instanceof File) { + arg.setFile((File)o); + } else if (o instanceof Path) { + arg.setPath((Path)o); + } else { + arg.setValue(o+""); + } + } + return java.executeJava(); + } + + private static List allErrors = new Vector(); + private List errors = new Vector(); + + private void post(Testset testset, List args, + String msgs, int exit, String type) { + errors.add(new Failure(testset, args, msgs, exit, type, testId)); + fire(type + ".fail"); + } + + private static long startTime; + private static long stopTime; + + private static String date(long time) { + return DateFormat.getDateTimeInstance + (DateFormat.FULL, DateFormat.FULL). + format(new Date(time)); + } + + static { + final PrintStream err = System.err; + startTime = System.currentTimeMillis(); + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + private String ms(long start, long stop) { + long rest = Math.abs(stop-start) / 1000; + long days = rest / 86400; + long hours = (rest -= days*86400) / 3600; + long mins = (rest -= hours*3600) / 60; + long secs = (rest -= mins*60); + boolean req = false; + String str = ""; + if (req || days > 0) { + req = true; + str += days + " day" + (days != 1 ? "s" : "") + " "; + } + if (req || hours > 0) { + req = true; + str += hours + " hour" + (hours != 1 ? "s" : "") + " "; + } + if (req || mins > 0) { + req = true; + str += mins + " minute" + (mins != 1 ? "s" : "") + " "; + } + str += secs + " second" + (secs != 1 ? "s" : "") + " "; + return str; + } + + public void run() { + Ajctest current = CURRENT_AJCTEST; + String oneLine = "warning: oneLine not set."; + String multiLine = "warning: multiLine not set."; + + // setup oneLine + if (null == current) { + oneLine = "\nRESULT=\"ERROR\" null ACJTEST"; + } else { + StringBuffer sb = new StringBuffer("\n"); + int errs = current.allErrors.size(); + int allFails = errs + + current.ajdocStats.fails + + current.ajcStats.fails + + current.runStats.fails; + if (1 > allFails) { + sb.append("RESULT=\"PASS\"\terrors=\""); + } else { + sb.append("RESULT=\"FAIL\"\terrors=\""); + } + sb.append(""+errs); + sb.append("\"\tajdoc.pass=\""); + sb.append(""+current.ajdocStats.goods); + sb.append("\"\tajdoc.fail=\""); + sb.append(""+current.ajdocStats.fails); + sb.append("\"\tajc.pass=\""); + sb.append(""+current.ajcStats.goods); + sb.append("\"\tajc.fail=\""); + sb.append(""+current.ajcStats.fails); + sb.append("\"\trun.pass=\""); + sb.append(""+current.runStats.goods); + sb.append("\"\trun.fail=\""); + sb.append(""+current.runStats.fails); + sb.append("\"\ttestId=\""); + sb.append(current.testId); + sb.append("\"\tproject=\""); + Project p = current.getProject(); + if (null != p) sb.append(p.getName()); + sb.append("\"\tfile=\""); + sb.append(""+current.getLocation()); + sb.append("\""); + oneLine = sb.toString(); + } + + // setup multiLine + { + stopTime = System.currentTimeMillis(); + String str = ""; + str += "\n"; + str += + "===================================" + + "===================================" + "\n"; + str += "Test started : " + date(startTime) + "\n"; + str += "Test ended : " + date(stopTime) + "\n"; + str += "Total time : " + ms(startTime, stopTime) + "\n"; + str += + "------------------------------" + + " Summary " + + "------------------------------" + "\n"; + str += "Task\tPassed\tFailed" + "\n"; + Object[] os = new Object[] { + "ajdoc", ajdocStats.goods+"", ajdocStats.fails+"", + "ajc", ajcStats.goods +"", ajcStats.fails +"", + "run", runStats.goods +"", runStats.fails +"", + }; + for (int i = 0; i < os.length; i += 3) { + str += os[i] + "\t" + os[i+1] + "\t" + os[i+2] + "\n"; + } + if (allErrors.size() > 0) { + str += "" + "\n"; + str += + "There " + w(allErrors) + " " + + allErrors.size() + " error" + + s(allErrors) + ":" + "\n"; + for (int i = 0; i < allErrors.size(); i++) { + Failure failure = (Failure)allErrors.get(i); + str += + "---------- Error " + i + " [" + + failure.testId + "]" + + " ------------------------------" + "\n"; + str += " " + failure + "\n\n"; + } + } else { + str += "No errors." + "\n"; + } + str += "--------------------------" + + " End of Summary " + + "---------------------------" + "\n"; + multiLine = str; + } + + // print both multiLine and oneLine + err.println(multiLine); + err.println(oneLine); + if (dumpresults && (allErrors.size() + + ajdocStats.fails + + ajcStats.fails + + runStats.fails) > 0) { + String date = date(System.currentTimeMillis()); + String filename = "ajc-errors"; + for (StringTokenizer t = new StringTokenizer(date, ",: "); + t.hasMoreTokens();) { + filename += "-" + t.nextToken().trim(); + } + filename += ".txt"; + PrintWriter out = null; + File file = new File(filename); + System.err.println("dumping results to " + file); + try { + out = new PrintWriter(new FileWriter(file)); + out.println(multiLine); + out.println(oneLine); + System.err.println("dumped results to " + file); + } catch (IOException ioe) { + if (out != null) out.close(); + } + } + } + })); + } + + private static String w(List list) { return a(list, "were", "was"); } + private static String s(List list) { return a(list, "s", ""); } + + private static String a(List list, String some, String one) { + return list == null || list.size() != 1 ? some : one; + } + + static class Failure { + public final Testset testset; + public final List args; + public final String msgs; + public final int exit; + public final String type; + public final long time; + public final String testId; + public Failure(Testset testset, List args, + String msgs, int exit, String type, + String testId) { + this.testset = testset; + this.args = args; + this.msgs = msgs; + this.exit = exit; + this.type = type; + this.time = System.currentTimeMillis(); + this.testId = testId; + } + public String toString() { + String str = "testId:" + testId+ "\n"; + str += "type:" + type + "\n"; + str += testset + "\n"; + if (args.size() > 0) { + str += " args: " + args + "\n";; + } + str += " msgs:" + msgs + "\n"; + str += " exit:" + exit; + return str; + } + } + + private List argcombo(List arguments) { + List combos = new Vector(); + List always = new Vector(); + for (Iterator iter = arguments.iterator(); iter.hasNext();) { + Argument arg = (Argument)iter.next(); + if (arg.values.size() == 0) arg.values.add(""); + if (!arg.always && !arg.values.contains(null)) arg.values.add(null); + if (arg.values.size() > 0) { + combos.add(arg); + } else if (arg.always) { + always.add(new Arg(arg.name, arg.values.get(0)+"", arg.isj)); + } + } + List argcombo = combinations(combos); + for (Iterator iter = always.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + for (Iterator comboiter = argcombo.iterator(); comboiter.hasNext();) { + ((List)comboiter.next()).add(arg); + } + } + return argcombo; + } + + private abstract class ExecWrapper { + public String msgs; + public int run() { + return run(createCommandline()); + } + protected abstract Commandline createCommandline(); + protected final int run(Commandline cmd) { + Process process = null; + int exit = Integer.MIN_VALUE; + final StringBuffer buf = new StringBuffer(); + Thread errPumper = null; + Thread outPumper = null; + try { + log("\tcalling " + cmd, Project.MSG_VERBOSE); + process = Runtime.getRuntime().exec(cmd.getCommandline()); + OutputStream os = new OutputStream() { + StringBuffer sb = new StringBuffer(); + public void write(int b) throws IOException { + final char c = (char)b; + buf.append(c); + if (c != '\n') { + sb.append(c); + } else { + System.err.println(sb.toString()); + sb = new StringBuffer(); + } + } + }; + OutputStream los = new LogOutputStream(Ajctest.this, + Project.MSG_INFO); + outPumper = new Thread(new StreamPumper(process.getInputStream(), + los)); + errPumper = new Thread(new StreamPumper(process.getErrorStream(), + os)); + outPumper.setDaemon(true); + errPumper.setDaemon(true); + outPumper.start(); + errPumper.start(); + process.waitFor(); + } catch (Exception e) { + e.printStackTrace(); + //throw e; + } finally { + try { + if (outPumper != null) outPumper.join(); + if (errPumper != null) errPumper.join(); + } catch (InterruptedException ie) { + } finally { + outPumper = null; + errPumper = null; + } + exit = process.exitValue(); + msgs = buf.toString(); + if (exit != 0) { + log("Test failed with exit value: " + exit); + } else { + log("Success!", Project.MSG_VERBOSE); + } + if (process != null) process.destroy(); + process = null; + System.err.flush(); + System.out.flush(); + } + return exit; + } + } + + private class AjcWrapper extends JavaCommandWrapper { + public AjcWrapper(Testset testset, List args) { + super(testset, args, false); + if (testset.noclean) { + setExtraclasspath(new Path(project, + destdir.getAbsolutePath())); + } + } + String getMainClassName() { + return "org.aspectj.tools.ajc.Main"; + } + } + + private class EAjcWrapper extends JavaCommandWrapper { + public EAjcWrapper(Testset testset, List args) { + super(testset, args, false); + if (testset.noclean) { + setExtraclasspath(new Path(project, + destdir.getAbsolutePath())); + } + } + String getMainClassName() { + return "org.aspectj.ajdt.ajc.Main"; + } + } + + static List ajdocArgs(List args) { + List newargs = new Vector(); + for (Iterator i = args.iterator(); i.hasNext();) { + String arg = i.next() + ""; + if (arg.startsWith("-X")) { + newargs.add(arg); + } else if (arg.equals("-public") || + arg.equals("-package") || + arg.equals("-protected") || + arg.equals("-private")) { + newargs.add(arg); + } else if (arg.equals("-d") || + arg.equals("-classpath") || + arg.equals("-cp") || + arg.equals("-sourcepath") || + arg.equals("-bootclasspath") || + arg.equals("-argfile")) { + newargs.add(arg); + newargs.add(i.next()+""); + } else if (arg.startsWith("@")) { + newargs.add(arg); + } + } + return newargs; + } + + private class AjdocWrapper extends JavaCommandWrapper { + public AjdocWrapper(Testset testset, List args) { + super(testset, ajdocArgs(args), true); + String[] cmds = testset.getAjdoc().getCommandline().getCommandline(); + for (int i = 0; i < cmds.length; i++) { + this.args.add(cmds[i]); + } + } + String getMainClassName() { + return "org.aspectj.tools.ajdoc.Main"; + } + } + + private abstract class JavaCommandWrapper extends ExecWrapper { + abstract String getMainClassName(); + protected Testset testset; + protected List args; + protected boolean needsClasspath; + protected Path extraClasspath; + + public JavaCommandWrapper(Testset testset, List args, + boolean needsClasspath) { + this.testset = testset; + this.args = args; + this.needsClasspath = needsClasspath; + this.extraClasspath = testset.internalclasspath; + } + public void setExtraclasspath(Path extraClasspath) { + this.extraClasspath = extraClasspath; + } + + public String toString() { + return LangUtil.unqualifiedClassName(getClass()) + + "(" + getMainClassName() + ")"; + } + + protected Commandline createCommandline() { + Commandline cmd = new Commandline(); + cmd.setExecutable("java"); + cmd.createArgument().setValue("-classpath"); + Path cp = null; + if (extraClasspath != null) { + cp = extraClasspath; + } + if (extraClasspath == null) { + Path aspectjBuildDir = + new Path(project, + project.getProperty("ajctest.pathelement")); + // todo: dependency on ant script variable name ajctest.pathelement + if (cp == null) cp = aspectjBuildDir; + else cp.append(aspectjBuildDir); + } + if (cp == null) { + cp = Path.systemClasspath; + } else { + cp.append(Path.systemClasspath); + } + cmd.createArgument().setPath(cp); + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (arg.isj) { + cmd.createArgument().setValue(arg.name); + if (!arg.value.equals("")) { + cmd.createArgument().setValue(arg.value); + } + } + } + cmd.createArgument().setValue(getMainClassName()); + boolean alreadySetDestDir = false; + boolean alreadySetClasspath = false; + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (!arg.isj) { + cmd.createArgument().setValue(arg.name); + if (arg.name.equals("-d")) { + setDestdir(arg.value+""); + alreadySetDestDir = true; + } + if (arg.name.equals("-classpath")) { + alreadySetClasspath = true; + } + if (!arg.value.equals("")) { + cmd.createArgument().setValue(arg.value); + } + } + } + if (destdir == null) { + setDestdir("."); + } + if (!alreadySetDestDir) { + cmd.createArgument().setValue("-d"); + cmd.createArgument().setFile(destdir); + } + if (!alreadySetClasspath && testset.classpath != null) { + cmd.createArgument().setValue("-classpath"); + cmd.createArgument().setPath(testset.classpath); + } else if (needsClasspath) { + Path _cp = Ajctest.this.classpath != null ? Ajctest.this.classpath : + new Path(project, destdir.getAbsolutePath()); + _cp.append(Path.systemClasspath); + cmd.createArgument().setValue("-classpath"); + cmd.createArgument().setPath(_cp); + } + for (Iterator iter = testset.files.iterator(); iter.hasNext();) { + cmd.createArgument().setFile((File)iter.next()); + } + for (Iterator iter = testset.argfiles.iterator(); iter.hasNext();) { + cmd.createArgument().setValue("-argfile"); + cmd.createArgument().setFile((File)iter.next()); + } + return cmd; + } + } + + /** implement invocation of ajc */ + private void java(Testset testset, List args) throws BuildException { + Java java = (Java)project.createTask("java"); + java.setClassname("org.aspectj.tools.ajc.Main"); + if (classpath != null) { + java.setClasspath(classpath); + } + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (arg.isj) { + java.createJvmarg().setValue(arg.name); + if (!arg.value.equals("")) { + java.createJvmarg().setValue(arg.value); + } + } + } + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (!arg.isj) { + java.createArg().setValue(arg.name); + if (!arg.value.equals("")) { + java.createArg().setValue(arg.value); + } + } + } + for (Iterator iter = testset.files.iterator(); iter.hasNext();) { + java.createArg().setFile((File)iter.next()); + } + for (Iterator iter = testset.argfiles.iterator(); iter.hasNext();) { + java.createArg().setValue("-argfile"); + java.createArg().setFile((File)iter.next()); + } + java.setFork(true); + java.execute(); + } + + private void exec(Testset testset, List args) throws BuildException { + ExecTask exec = (ExecTask)project.createTask("exec"); + exec.setExecutable("java"); + if (classpath != null) { + exec.createArg().setValue("-classpath"); + exec.createArg().setPath(classpath); + } + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (arg.isj) { + exec.createArg().setValue(arg.name); + if (!arg.value.equals("")) { + exec.createArg().setValue(arg.value); + } + } + } + exec.createArg().setValue("org.aspectj.tools.ajc.Main"); + for (Iterator iter = args.iterator(); iter.hasNext();) { + Arg arg = (Arg)iter.next(); + if (!arg.isj) { + exec.createArg().setValue(arg.name); + if (!arg.value.equals("")) { + exec.createArg().setValue(arg.value); + } + } + } + for (Iterator iter = testset.files.iterator(); iter.hasNext();) { + exec.createArg().setFile((File)iter.next()); + } + for (Iterator iter = testset.argfiles.iterator(); iter.hasNext();) { + exec.createArg().setValue("-argfile"); + exec.createArg().setFile((File)iter.next()); + } + exec.execute(); + } + + public void handle(Throwable t) { + log("handling " + t); + if (t != null) t.printStackTrace(); + log("done handling " + t); + } + + private List combinations(List arglist) { + List result = new Vector(); + result.add(new Vector()); + for (Iterator iter = arglist.iterator(); iter.hasNext();) { + Argument arg = (Argument)iter.next(); + int N = result.size(); + for (int i = 0; i < N; i++) { + List to = (List)result.remove(0); + for (Iterator valiter = arg.values.iterator(); valiter.hasNext();) { + List newlist = new Vector(to); + Object val = valiter.next(); + if (val != null) newlist.add(new Arg(arg.name, val+"", arg.isj)); + result.add(newlist); + } + } + } + return result; + } + + /////////////////////// GUI support ////////////////////////////// + private static Gui gui; + + private static void gui(Ajctest ajc) { + if (firstTime && ajc.isSet("gui")) { + JFrame f = new JFrame("AspectJ Test Suite"); + f.getContentPane().add(BorderLayout.CENTER, gui = new Gui()); + f.pack(); + f.setVisible(true); + } + if (gui != null) { + ajc.bean.addPropertyChangeListener(gui); + } + firstTime = false; + } + + private static class Gui extends JPanel implements PropertyChangeListener { + private FailurePanel fail = new FailurePanel(); + private TablePanel table = new TablePanel(); + private StatusPanel status = new StatusPanel(); + public Gui() { + super(new BorderLayout()); + JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + split.setPreferredSize(new Dimension(500, 300)); + split.add(JSplitPane.BOTTOM, fail); + split.add(JSplitPane.TOP, table); + split.setDividerLocation(200); + add(BorderLayout.CENTER, split); + add(BorderLayout.SOUTH, status); + setPreferredSize(new Dimension(640, 680)); + } + public void propertyChange(PropertyChangeEvent evt) { + String name = evt.getPropertyName(); + if ("ajdoc.good".equals(name)) { + status.ajc.goods.inc(); + } else if ("ajc.good".equals(name)) { + status.ajc.goods.inc(); + } else if ("run.good".equals(name)) { + status.runs.goods.inc(); + } + if ("ajdoc.done".equals(name)) { + status.ajc.total.inc(); + } else if ("ajc.done".equals(name)) { + status.ajc.total.inc(); + } else if ("run.done".equals(name)) { + status.runs.total.inc(); + } + if ("ajdoc.fail".equals(name)) { + status.ajc.fails.inc(); + } else if ("ajc.fail".equals(name)) { + status.ajc.fails.inc(); + } else if ("run.fail".equals(name)) { + status.runs.fails.inc(); + } + } + + private abstract static class TitledPanel extends JPanel { + public TitledPanel(LayoutManager layout, String title) { + super(layout); + setBorder(BorderFactory.createTitledBorder(title)); + } + } + + private static class StatusPanel extends TitledPanel { + StatusInnerPanel ajdoc = new StatusInnerPanel("Ajdoc"); + StatusInnerPanel runs = new StatusInnerPanel("Runs"); + StatusInnerPanel ajc = new StatusInnerPanel("Ajc"); + + public StatusPanel() { + super(new FlowLayout(), "Status"); + add(ajdoc); + add(runs); + add(ajc); + } + + private static class StatusInnerPanel extends TitledPanel { + IntField goods = new IntField(5, Color.green.darker()); + IntField fails = new IntField(5, Color.red.darker()); + IntField total = new IntField(5, Color.blue.darker()); + public StatusInnerPanel(String str) { + super(null, str); + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + Object[] os = new Object[] { + "Passed", goods, + "Failed", fails, + "Totals", total, + }; + for (int i = 0; i < os.length; i += 2) { + JPanel p = p(); + p.add(new JLabel(os[i]+":")); + p.add((Component)os[i+1]); + this.add(p); + } + } + + private JPanel p() { + JPanel p = new JPanel(); + p.setLayout(new FlowLayout(FlowLayout.LEFT)); + return p; + } + + private class IntField extends JTextField { + public IntField(int i, Color fg) { + super("0", i); + this.setBackground(StatusInnerPanel.this.getBackground()); + this.setForeground(fg); + this.setEditable(false); + this.setBorder(BorderFactory.createEmptyBorder()); + } + public void add(int i) { + setText((Integer.parseInt(getText().trim())+i)+""); + } + public void inc() { add(1); } + } + } + } + + private class TablePanel extends TitledPanel { + private DefaultTableModel model = new DefaultTableModel(); + private TJable table; + private List failures = new Vector(); + public TablePanel() { + super(new BorderLayout(), "Failures"); + Object[] names = new String[] { + "Task", "Type", "Number", "Time" + }; + for (int i = 0; i < names.length; i++) { + model.addColumn(names[i]); + } + table = new TJable(model, failures); + this.add(new JScrollPane(table), BorderLayout.CENTER); + } + + private class TJable extends JTable { + private List list; + public TJable(TableModel model, List list) { + super(model); + this.list = list; + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + public void valueChanged(ListSelectionEvent e) { + super.valueChanged(e); + if (list == null) return; + int i = (e.getFirstIndex()-e.getLastIndex())/2; + if (list.size() > 0) { + Failure f = (Failure)list.get(i); + fail.setFailure(f); + } + } + } + + public void add(Failure f, String taskname, String type, + int num, long time) { + model.addRow(new Object[]{taskname, type, + new Integer(num), date(time)}); + failures.add(f); + } + } + + private static class FailurePanel extends TitledPanel { + private JTextArea msgs = new JTextArea(10,50); + private InfoPanel info = new InfoPanel(); + + public FailurePanel() { + super(new BorderLayout(), "Failure"); + msgs.setFont(StyleContext.getDefaultStyleContext(). + getFont("SansSerif", Font.PLAIN, 10)); + add(BorderLayout.NORTH, info); + JScrollPane sc = new JScrollPane(msgs); + sc.setBorder(BorderFactory.createTitledBorder("Messages")); + add(BorderLayout.CENTER, sc); + } + + public void setText(String str) { + msgs.setText(str); + } + + public void setFailure(Failure f) { + msgs.setText(f.msgs); + info.setText("Type" , f.type); + info.setText("Args" , f.args); + info.setText("Exit" , f.exit+""); + info.setText("Time" , date(f.time)); + info.setText("Files" , f.testset.files); + info.setText("Classnames" , f.testset.testclasses); + } + + private static class InfoPanel extends JPanel { + Map fields = new HashMap(); + public void setText(String key, Object str) { + ((JTextField)fields.get(key)).setText(str+""); + } + public InfoPanel() { + super(new GridBagLayout()); + LabelFieldGBC gbc = new LabelFieldGBC(); + Object[] os = new Object[] { + "Type", + "Args", + "Exit", + "Time", + "Files", + "Classnames", + }; + for (int i = 0; i < os.length; i++) { + String name = os[i]+""; + JLabel label = new JLabel(name+":"); + JTextField comp = new JTextField(25); + comp.setEditable(false); + comp.setBackground(Color.white); + comp.setBorder(BorderFactory. + createBevelBorder(BevelBorder.LOWERED)); + label.setLabelFor(comp); + fields.put(name, comp); + add(label, gbc.forLabel()); + add(comp, gbc.forField()); + } + add(new JLabel(), gbc.forLastLabel()); + } + } + + private static class LabelFieldGBC extends GridBagConstraints { + public LabelFieldGBC() { + insets = new Insets(1,3,1,3); + gridy = RELATIVE; + gridheight = 1; + gridwidth = 1; + } + public LabelFieldGBC forLabel() { + fill = NONE; + gridx = 0; + anchor = NORTHEAST; + weightx = 0.0; + return this; + } + + public LabelFieldGBC forLastLabel() { + forLabel(); + fill = VERTICAL; + weighty = 1.0; + return this; + } + + public LabelFieldGBC forField() { + fill = HORIZONTAL; + gridx = 1; + anchor = CENTER; + weightx = 1.0; + return this; + } + + public LabelFieldGBC forLastField() { + forField(); + fill = BOTH; + weighty = 1.0; + return this; + } + } + } + } + +} diff --git a/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java b/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java new file mode 100644 index 000000000..656a284f4 --- /dev/null +++ b/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java @@ -0,0 +1,181 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + + +package org.aspectj.internal.tools.ant.taskdefs; + +import org.aspectj.testing.util.LangUtil; // todo config management here + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.InvocationTargetException; +import java.io.*; + +/** + * Wrapper to invoke class identified by setting VM argument. + * Caller must set a system property "MainWrapper.classname" + * to the fully-qualified name of the target class to invoke, + * and the target class must be resolvable from the defining + * class loader of this class. + * VM argument name is available as <code>PROP_NAME</code>, but + * is set by adding the following to the command line: + * <code>-DMainWrapper.classname="fully.qualified.Classname"</code>. + * This returns -1 if unable to load the main method, + * 1 if the invoked method throws an exception, and 0 otherwise. + * TODO: prelminary + */ +public class MainWrapper { + /** MUST set the fully-qualified name of class to invoke using + * a VM property of this name + * tracked in Ajctest.java */ + public static final String PROP_NAME = "MainWrapper.classname"; + /** May set the path to a classes diretory, + * to interpret class names and load classes. + * Tracked in Ajctest.java */ + public static final String CLASSDIR_NAME = "MainWrapper.classdir"; + + /** to disable returning int via System.exit, set to boolean true value (todo: ignored) */ + public static final String SIGNAL_EXCEPTION_NAME = "MainWrapper.signalException"; + + /** to disable returning via System.exit on first Throwable, set to boolean true value (todo: ignored) */ + public static final String FAIL_ON_EXCEPTION_NAME = "MainWrapper.failOnException"; + + /** quit on first exception */ // todo public class controls - yuck + public static boolean FAIL_ON_EXCEPTION = true; + + /** signal number of exceptions with int return value */ + public static boolean SIGNAL_EXCEPTION = true; + + /** redirect messages for exceptions; if null, none printed */ + public static PrintStream OUT_STREAM = System.err; + + /** result accumulated, possibly from multiple threads */ + private static int result; + + /** + * Run target class's main(args), doing a System.exit() with + * a value > 0 for the number of Throwable that + * the target class threw that + * makes it through to a top-level ThreadGroup. (This is + * strictly speaking not correct since applications can live + * after their exceptions stop a thread.) + * Exit with a value < 0 if there were exceptions in loading + * the target class. Messages are printed to OUT_STREAM. + */ + public static void main(String[] args) { + String classname = "no property : " + PROP_NAME; + Method main = null; + // setup: this try block is for loading main method - return -1 if fail + try { + // access classname from jvm arg + classname = System.getProperty(PROP_NAME); + // this will fail if the class is not available from this classloader + Class cl = Class.forName(classname); + final Class[] argTypes = new Class[] {String[].class}; + // will fail if no main method + main = cl.getMethod("main", argTypes); + if (!Modifier.isStatic(main.getModifiers())) { + PrintStream outStream = OUT_STREAM; + if (null != outStream) outStream.println("main is not static"); + result = -1; + } + // if user also request loading of all classes... + String classesDir = System.getProperty(CLASSDIR_NAME); + if ((null != classesDir) && (0 < classesDir.length())) { + MainWrapper.loadAllClasses(new File(classesDir)); + } + } catch (Throwable t) { + if (1 != result) result--; + reportException("setup Throwable invoking class " + classname, t); + } + // run: this try block is for running things - get Throwable from our thread here + if ((null != main) && (0 == result)) { + try { + runInOurThreadGroup(main, args); + } catch (Throwable t) { + if (result > -1) { + result++; + } + reportException("run Throwable invoking class " + classname, t); + } + } + if ((0 != result) && (SIGNAL_EXCEPTION)) { + System.exit(result); + } + } + + static void runInOurThreadGroup(final Method main, final String[] args) { + final String classname = main.getDeclaringClass().getName(); + ThreadGroup ourGroup = new ThreadGroup("MainWrapper ThreadGroup") { + public void uncaughtException(Thread t, Throwable e) { + reportException("uncaughtException invoking " + classname, e); + result++; + if (FAIL_ON_EXCEPTION) { + System.exit((SIGNAL_EXCEPTION ? result : 0)); + } + } + }; + Runnable runner = new Runnable() { + public void run() { + try { + main.invoke(null, new Object[] {args}); + } catch (InvocationTargetException e) { + result = -1; + reportException("InvocationTargetException invoking " + classname, e); + } catch (IllegalAccessException e) { + result = -1; + reportException("IllegalAccessException invoking " + classname, e); + } + } + }; + Thread newMain = new Thread(ourGroup, runner, "pseudo-main"); + newMain.start(); + try { + newMain.join(); + } catch (InterruptedException e) { + result = -1; // todo: InterruptedException might be benign - retry? + reportException("Interrupted while waiting for to join " + newMain, e); + } + } + + /** + * Try to load all classes in a directory. + * @throws Error if any failed + */ + static protected void loadAllClasses(File classesDir) { + if (null != classesDir) { + String[] names = LangUtil.classesIn(classesDir); + StringBuffer err = new StringBuffer(); + LangUtil.loadClasses(names, null, err); + if (0 < err.length()) { + throw new Error("MainWrapper Errors loading classes: " + + err.toString()); + } + } + } + + static void reportException(String context, Throwable t) { + PrintStream outStream = OUT_STREAM; + if (null != outStream) { + while ((null != t) && + (InvocationTargetException.class.isAssignableFrom(t.getClass()))) { + t = ((InvocationTargetException) t).getTargetException(); + } + outStream.println(" context: " + context); + outStream.println(" message: " + t.getMessage()); + t.printStackTrace(outStream); + } + } + +} // MainWrapper diff --git a/testing/src/org/aspectj/testing/gui/resources/gui.properties b/testing/src/org/aspectj/testing/gui/resources/gui.properties new file mode 100644 index 000000000..56a5878ac --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/gui.properties @@ -0,0 +1,53 @@ +gui.actions=quit delete reload open save saveas stop start + + +quit.class=org.aspectj.testing.gui.Gui$QuitAction +quit.icon=Quit24.gif +quit.tooltip=Quit + +delete.class=org.aspectj.testing.gui.Gui$DeleteAction +delete.icon=Delete24.gif +delete.tooltip=Delete Test Cases + +stop.class=org.aspectj.testing.gui.Gui$StopAction +stop.icon=Stop24.gif +stop.tooltip=Stop running test + +reload.class=org.aspectj.testing.gui.Gui$ReloadAction +reload.icon=Reload24.gif +reload.tooltip=Reload current test + +open.class=org.aspectj.testing.gui.Gui$OpenAction +open.icon=Open24.gif +open.tooltip=Open a test + +save.class=org.aspectj.testing.gui.Gui$SaveAction +save.icon=Save24.gif +save.tooltip=Save current test + +saveas.class=org.aspectj.testing.gui.Gui$SaveAsAction +saveas.icon=SaveAs24.gif +saveas.tooltip=Save current test as... + +start.class=org.aspectj.testing.gui.Gui$StartAction +start.icon=Start24.gif +start.tooltip=Start test + +editor.actions=openfile savefile savefileas + +openfile.class=org.aspectj.testing.gui.Gui$OpenAction +openfile.icon=Open24.gif +openfile.tooltip=Open a file + +savefile.class=org.aspectj.testing.gui.Gui$SaveAction +savefile.icon=Save24.gif +savefile.tooltip=Save currect file + +savefileas.class=org.aspectj.testing.gui.Gui$SaveAsAction +savefileas.icon=SaveAs24.gif +savefileas.tooltip=Save current file as... + +gui.width=700 +gui.less.height=100 +gui.top.divider.location=0.70 +gui.center.divider.location=0.60 diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Back16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Back16.gif Binary files differnew file mode 100644 index 000000000..f48362d71 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Back16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Delete24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Delete24.gif Binary files differnew file mode 100644 index 000000000..d8c8c2d3d --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Delete24.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Forward16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Forward16.gif Binary files differnew file mode 100644 index 000000000..d25a3f956 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Forward16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Open16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Open16.gif Binary files differnew file mode 100644 index 000000000..e3ea2f564 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Open16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Open24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Open24.gif Binary files differnew file mode 100644 index 000000000..cbb2e08ba --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Open24.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Quit24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Quit24.gif Binary files differnew file mode 100644 index 000000000..90515c8ac --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Quit24.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Reload24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Reload24.gif Binary files differnew file mode 100644 index 000000000..3ead627fb --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Reload24.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Save16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Save16.gif Binary files differnew file mode 100644 index 000000000..954f1accd --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Save16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Save24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Save24.gif Binary files differnew file mode 100644 index 000000000..8813f46d2 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Save24.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/SaveAs16.gif b/testing/src/org/aspectj/testing/gui/resources/images/SaveAs16.gif Binary files differnew file mode 100644 index 000000000..8d3929c8a --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/SaveAs16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/SaveAs24.gif b/testing/src/org/aspectj/testing/gui/resources/images/SaveAs24.gif Binary files differnew file mode 100644 index 000000000..40a3d8739 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/SaveAs24.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Start16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Start16.gif Binary files differnew file mode 100644 index 000000000..a72a9ca5c --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Start16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Start24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Start24.gif Binary files differnew file mode 100644 index 000000000..63230a9e8 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Start24.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Stop16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Stop16.gif Binary files differnew file mode 100644 index 000000000..491845244 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Stop16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Stop24.gif b/testing/src/org/aspectj/testing/gui/resources/images/Stop24.gif Binary files differnew file mode 100644 index 000000000..3fae3827e --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Stop24.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Test16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Test16.gif Binary files differnew file mode 100644 index 000000000..b728bcbe2 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Test16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Testfailed16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Testfailed16.gif Binary files differnew file mode 100644 index 000000000..44b9ea6d5 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Testfailed16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gif Binary files differnew file mode 100644 index 000000000..cd090c884 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Testopen16.gif diff --git a/testing/src/org/aspectj/testing/gui/resources/images/Testpassed16.gif b/testing/src/org/aspectj/testing/gui/resources/images/Testpassed16.gif Binary files differnew file mode 100644 index 000000000..c5689e6e9 --- /dev/null +++ b/testing/src/org/aspectj/testing/gui/resources/images/Testpassed16.gif diff --git a/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java new file mode 100644 index 000000000..944f096d4 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java @@ -0,0 +1,883 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.util.BridgeUtil; +import org.aspectj.testing.xml.IXmlWritable; +import org.aspectj.testing.xml.SoftMessage; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.LangUtil; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Base class for initialization of components expecting messages, + * options, files/paths, and source locations (resolved files), + * and potentially containing child Spec. + * <p> + * <u>initialization</u>: This defines bean/xml setters for all. + * This converts String to IMessage using + * {@link MessageUtil#readMessage(String)} + * and String to ISourceLocation using + * {@link BridgeUtil#makeSourceLocation(input)}. + * See those APIs for input form and limitations. + * A Spec also accepts (or rejects) runtime configuration from a parent + * in {@link adoptParentValues(RT, IMessageHandler)}. + * Since some children Spec may balk but this parent Spec continue, + * use {@link getChildren()} to get the full list of children Spec, + * but {@link getWorkingChildren()} to get the list of children that + * are not being skipped in accordance with runtime configuration. + * <p> + * <u>subclassing</u>: subclasses wishing other than + * the default behavior for reading String input should override the + * corresponding (bean) setter. They can also override the + * add{foo} methods to get notice or modify objects constructed + * from the input. + * <p> + * <u>bean properties</u>: because this is designed to work + * by standard Java bean introspection, take care to follow + * bean rules when adding methods. In particular, a property + * is illegal if the setter takes a different type than the + * getter returns. That means, e.g., that all List and array[] getters + * should be named "get{property}[List|Array]". Otherwise + * the XML readers will silently fail to set the property + * (perhaps with trace information that the property had + * no write method or was read-only). + * <p> + * <u>Coordination with writers</u>: because this reads the contents + * of values written by IXmlWritable, they should ensure their + * values are readable. When flattening and unflattening + * lists, the convention is to use the {un}flattenList(..) methods + * in XMLWriter. + * @see XMLWriter@unflattenList(String) + * @see XMLWriter@flattenList(List) + */ +abstract public class AbstractRunSpec implements IRunSpec { // XXX use MessageHandler? + + /** true if we expect to use a staging directory */ + boolean isStaging; + + protected String description; + + /** optional source location of the specification itself */ + protected ISourceLocation sourceLocation; + + private BitSet skipSet; + private boolean skipAll; + + protected final String xmlElementName; + protected final ArrayList /*String*/ keywords; + protected final ArrayList /*IMessage*/ expectedMessages; + protected final ArrayList /*String*/ options; + protected final ArrayList /*String*/ paths; + protected final ArrayList /*ISourceLocation*/ sourceLocations; // XXX remove? + protected final ArrayList /*IRunSpec*/ children; + protected final ArrayList /*DirChanges.Spec*/ dirChanges; + protected XMLNames xmlNames; + protected String comment; + + /** These options are 1:1 with spec, but set at runtime (not saved) */ + public final RT runtime; + + /** if true, then any child skip causes this to skip */ + protected final boolean skipIfAnyChildSkipped; + + public AbstractRunSpec(String xmlElementName) { + this(xmlElementName, true); + } + + public AbstractRunSpec(String xmlElementName, boolean skipIfAnyChildSkipped) { + if (null == xmlElementName) { + xmlElementName = "spec"; + } + this.xmlElementName = xmlElementName; + expectedMessages = new ArrayList(); + options = new ArrayList(); + paths = new ArrayList(); + sourceLocations = new ArrayList(); + keywords = new ArrayList(); + children = new ArrayList(); + dirChanges = new ArrayList(); + xmlNames = XMLNames.DEFAULT; + runtime = new RT(); + this.skipIfAnyChildSkipped = skipIfAnyChildSkipped; + } + + + /** @param comment ignored if null */ + public void setComment(String comment) { + if (!LangUtil.isEmpty(comment)) { + this.comment = comment; + } + } + + public void setStaging(boolean staging) { + isStaging = staging; + } + + boolean isStaging() { + return isStaging; + } + + // ------- description (title, label...) + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + // ------- source location of the spec + + public void setSourceLocation(ISourceLocation sourceLocation) { + this.sourceLocation = sourceLocation; + } + + public ISourceLocation getSourceLocation() { + return sourceLocation; + } + + // ------- keywords + /** @param keyword added after trimming if not empty */ + public void setKeyword(String keyword) { + addKeyword(keyword); + } + + /** Add keyword if non-empty and not duplicate */ + public void addKeyword(String keyword) { + if (!LangUtil.isEmptyTrimmed(keyword)) { + keyword = keyword.trim(); + if (!keywords.contains(keyword)) { + keywords.add(keyword); + } + } + } + public void setKeywords(String items) { + addKeywords(items); + } + + public void addKeywords(String items) { + if (null != items) { + addKeywords(XMLWriter.unflattenList(items)); + } + } + public void addKeywords(String[] ra) { + if (null != ra) { + for (int i = 0; i < ra.length; i++) { + addKeyword(ra[i]); + } + } + } + public ArrayList getKeywordsList() { + return makeList(keywords); + } + + // ------- options - String args + + /** @return ArrayList of String options */ + public ArrayList getOptionsList() { + return makeList(options); + } + + /** @return String[] of options */ + public String[] getOptionsArray() { + return (String[]) options.toArray(new String[0]); + } + + public void setOption(String option) { + addOption(option); + } + + public void addOption(String option) { + if ((null != option) && (0 < option.length())) { + options.add(option); + } + } + + /** add options (from XML/bean) - removes any existing options */ + public void setOptions(String items) { + this.options.clear(); + addOptions(items); + } + + /** + * Set options, removing any existing options. + * @param options String[] options to use - may be null or empty + */ + public void setOptionsArray(String[] options) { + this.options.clear(); + if (!LangUtil.isEmpty(options)) { + this.options.addAll(Arrays.asList(options)); + } + } + + public void addOptions(String items) { + if (null != items) { + addOptions(XMLWriter.unflattenList(items)); + } + } + + public void addOptions(String[] ra) { + if (null != ra) { + for (int i = 0; i < ra.length; i++) { + addOption(ra[i]); + } + } + } + + // -------------- source locations +// /** @return ArrayList of ISourceLocation sourceLocs */ +// public ArrayList getSourceLocationsList() { +// return makeList(sourceLocations); +// } +// +// /** @return ISourceLocation[] sourceLocs */ +// public ISourceLocation[] getSourceLocationsArray() { +// return (ISourceLocation[]) sourceLocations.toArray(new ISourceLocation[0]); +// } +// +// +// public void setSourceLocation(String input) { +// if (null != input) { +// ISourceLocation sl = BridgeUtil.makeSourceLocation(input); +// if (null != sl) { +// addSourceLocation(sl); +// } +// // XXX need error-handling for bad input +// } +// } +// +// public void addSourceLocation(ISourceLocation sourceLoc) { +// if (null != sourceLoc) { +// sourceLocations.add(sourceLoc); +// } +// } + + // --------------- (String) paths + /** @return ArrayList of String paths */ + public ArrayList getPathsList() { + return makeList(paths); + } + + /** @return String[] of paths */ + public String[] getPathsArray() { + return (String[]) paths.toArray(new String[0]); + } + +// /** @return String[] of paths, removing any matching stripPrefix prefix */ +// public String[] getPathsArray(String stripPrefix) { +// String[] result = getPathsArray(); +// if (!LangUtil.isEmpty(stripPrefix)) { +// final int LEN = stripPrefix.length(); +// for (int i = 0; i < result.length; i++) { +// if ((null != result[i]) && result[i].startsWith(stripPrefix)) { +// result[i] = result[i].substring(LEN); +// } +// } +// } +// return result; +// } +// +// /** @return ArrayList of File baseDir/{path} */ +// public ArrayList getPathsAsFile(File baseDir) { +// if (null == baseDir) { +// baseDir = new File("."); +// } +// ArrayList result = makeList(null); +// for (Iterator iter = paths.iterator(); iter.hasNext();) { +// String path = (String) iter.next(); +// result.add(new File(baseDir, path)); +// } +// return result; +// } + + public void setPath(String path) { + addPath(path); + } + + public void setPaths(String paths) { + addPaths(paths); + } + + public void addPath(String path) { + if (null != path) { + paths.add(path); + } + } + + public void addPaths(String items) { + if (null != items) { + addPaths(XMLWriter.unflattenList(items)); + } + } + + public void addPaths(String[] ra) { + if (null != ra) { + for (int i = 0; i < ra.length; i++) { + addPath(ra[i]); + } + } + } + + public void addWrapFile(WrapFile file) { + if (null != file) { + paths.add(file.path); + } + } + + // --------------------- dir changes + public void addDirChanges(DirChanges.Spec dirChangesSpec) { + if (null != dirChangesSpec) { + dirChanges.add(dirChangesSpec); + } + } + + // --------------------- messages + public void setMessage(String message) { + addMessage(message); + } + + public void addMessage(IMessage message) { + if (null != message) { + expectedMessages.add(message); + } + } + + public void addMessage(String message) { + if (null != message) { + IMessage m = BridgeUtil.readMessage(message); + expectedMessages.add(m); + } + } + + /** this can ONLY work if each item has no internal comma + */ + public void addMessages(String items) { + if (null != items) { + String[] ra = XMLWriter.unflattenList(items); + for (int i = 0; i < ra.length; i++) { + addMessage(ra[i]); + } + } + } + public void addMessages(List messages) { + if (null != messages) { + for (Iterator iter = messages.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (o instanceof IMessage) { + addMessage((IMessage) o); + } else { + // XXX warning? + } + } + } + } + + public ArrayList getMessages(IMessage.Kind kind) { + return makeList(MessageUtil.getMessages(expectedMessages, kind)); + } + + + /** @return int number of message of this kind (optionally or greater */ + public int numMessages(IMessage.Kind kind, boolean orGreater) { + return MessageUtil.numMessages(expectedMessages, kind, orGreater); + } + + public ArrayList getMessages() { + return getMessages(null); // XXX special meaning of null + } + + + public void addChild(IRunSpec child) { + // fyi, child is added when complete (depth-first), not when initialized, + // so cannot affect initialization of child here + if (null != child) { + children.add(child); + } + } + + /** @return copy of children list */ + public ArrayList getChildren() { + return makeList(children); + } + + /** @return copy of children list without children to skip */ + public ArrayList getWorkingChildren() { + if (skipAll) { + return new ArrayList(); + } + if (null == skipSet) { + return getChildren(); + } + ArrayList result = new ArrayList(); + int i = 0; + for (Iterator iter = children.listIterator(); + iter.hasNext(); i++) { + Object child = iter.next(); + if (!skipSet.get(i)) { + result.add(child); + } + } + return result; + } + + /** + * Recursively absorb parent values if different. + * This implementation calls doAdoptParentValues(..) + * and then calls this for any children. + * This is when skipped children are determined. + * Children may elect to balk at this point, reducing the + * number of children or causing this spec to skip if + * skipIfAnyChildrenSkipped. For each test skipped, either + * this doAdoptParentValues(..) or the child's adoptParentValues(..) + * should add one info message with the reason this is being skipped. + * The only reason to override this would be to NOT + * invoke the same for children, or to do something similar + * for children which are not AbstractRunSpec. + * @param parentRuntime the RT values to adopt - ignored if null + * @param handler the IMessageHandler for info messages when skipping + * @return false if this wants to be skipped, true otherwise + */ + public boolean adoptParentValues(RT parentRuntime, IMessageHandler handler) { + boolean skipped = false; + skipAll = false; + skipSet = new BitSet(); + if (null != parentRuntime) { + skipped = !doAdoptParentValues(parentRuntime, handler); + if (skipped && skipIfAnyChildSkipped) { // no need to continue checking + skipAll = true; + return false; + } + int i = 0; + for (ListIterator iter = children.listIterator(); iter.hasNext(); i++) { + IRunSpec child = (IRunSpec) iter.next(); + if (child instanceof AbstractRunSpec) { // XXX ugly instanceof + AbstractRunSpec arsChild = (AbstractRunSpec) child; + if (!arsChild.adoptParentValues(runtime, handler)) { + skipSet.set(i); + //iter.remove(); + if (!skipped) { + skipped = true; + if (skipIfAnyChildSkipped) { // no need to continue checking +// String m = "skipping " + toString() + " because child " +// + arsChild + " skipped"; +// MessageUtil.info(handler, m); + skipAll = true; + //children.clear(); + return false; + } + } + } + } + } + } + return true; + } + + /** + * Adopt parent values. + * This implementation makes a local copy. + * If we interpret (and absorb) any options, they should be removed + * from parentRuntime. + * This sets verbose if different (override) + * and directly adopts parentOptions if ours is null + * and otherwise adds any non-null options we don't already have. + * setting verbose and adding to parent options. + * Implementors override this to affect how parent values are adopted. + * Implementors should not recurse into children. + * This method may be called multiple times, so implementors + * should not destroy any spec information. + * Always add an info message when returning false to skip + * @param parentRuntime the RT values to adopt - never null + * @return false if this wants to be skipped, true otherwise + */ + protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) { + if (runtime.verbose != parentRuntime.verbose) { + runtime.verbose = parentRuntime.verbose; + } + if (!LangUtil.isEmpty(runtime.parentOptions)) { + runtime.parentOptions.clear(); + } + if (!LangUtil.isEmpty(parentRuntime.parentOptions)) { + runtime.parentOptions.addAll(parentRuntime.parentOptions); + } + return true; + } + + /** + * Implementations call this when signalling skips to ensure consistency + * in message formatting + * @param handler the IMessageHandler sink - not null + * @param reason the String reason to skip - not null + */ + protected void skipMessage(IMessageHandler handler, String reason) { + LangUtil.throwIaxIfNull(handler, "handler"); + LangUtil.throwIaxIfNull(handler, "reason"); + // XXX for Runs, label does not identify the test + String label = toString(); + MessageUtil.info(handler, "skipping \"" + label + "\" because " + reason); + } + + // --------------------------- writing xml - would prefer castor.. + + /** + * Control XML output by renaming or suppressing output for + * attributes and subelements. + * Subelements are skipped by setting the XMLNames booleans to false. + * Attributes are skipped by setting their name to null. + * @param names XMLNames with new names and/or suppress flags. + */ + protected void setXMLNames(XMLNames names) { + if (null != names) { + xmlNames = names; + } + } + + /** @return null if value is null or name="{value}" otherwise */ + private String makeAttr(XMLWriter out, String name, String value) { + if (null == value) { + return null; + } + return out.makeAttribute(name, value); + } + + /** @return null if list is null or empty or name="{flattenedList}" otherwise */ + private String makeAttr(XMLWriter out, String name, List list) { + if (LangUtil.isEmpty(list)) { + return null; + } + String flat = out.flattenList(list); + return out.makeAttribute(name, flat); + } + + /** @return true if writeAttributes(..) will produce any output */ + protected boolean haveAttributes() { + return ((!LangUtil.isEmpty(xmlNames.descriptionName) + && !LangUtil.isEmpty(description)) + || (!LangUtil.isEmpty(xmlNames.keywordsName) + && !LangUtil.isEmpty(keywords)) + || (!LangUtil.isEmpty(xmlNames.optionsName) + && !LangUtil.isEmpty(options)) + || (!LangUtil.isEmpty(xmlNames.pathsName) + && !LangUtil.isEmpty(paths))); + } + + /** + * Write attributes without opening or closing elements/attributes. + * An attribute is written only if the value is not empty + * and the name in xmlNames is not empty + */ + protected void writeAttributes(XMLWriter out) { + if (!LangUtil.isEmpty(xmlNames.descriptionName) + && !LangUtil.isEmpty(description)) { + out.printAttribute(xmlNames.descriptionName, description); + } + if (!LangUtil.isEmpty(xmlNames.keywordsName) + && !LangUtil.isEmpty(keywords)) { + out.printAttribute(xmlNames.keywordsName, out.flattenList(keywords)); + } + if (!LangUtil.isEmpty(xmlNames.optionsName) + && !LangUtil.isEmpty(options)) { + out.printAttribute(xmlNames.optionsName, out.flattenList(options)); + } + if (!LangUtil.isEmpty(xmlNames.pathsName) + && !LangUtil.isEmpty(paths)) { + out.printAttribute(xmlNames.pathsName, out.flattenList(paths)); + } + if (!LangUtil.isEmpty(xmlNames.commentName) + && !LangUtil.isEmpty(comment)) { + out.printAttribute(xmlNames.commentName, comment); + } + } + + /** + * The default implementation writes everything as attributes, + * then subelements for dirChanges, messages, then subelements for children. + * Subclasses that override may delegate back for any of these. + * Subclasses may also set XMLNames to name or suppress any attribute + * or subelement. + * @see writeMessages(XMLWriter) + * @see writeChildren(XMLWriter) + * @see IXmlWritable#writeXml(XMLWriter) + */ + public void writeXml(XMLWriter out) { + out.startElement(xmlElementName,false); + writeAttributes(out); + out.endAttributes(); + if (!xmlNames.skipMessages) { + writeMessages(out); + } + if (!xmlNames.skipChildren) { + writeChildren(out); + } + out.endElement(xmlElementName); + } + + /** + * Write messages. Assumes attributes are closed, + * can write child elements of current element. + */ + protected void writeMessages(XMLWriter out) { + if (0 < expectedMessages.size()) { + SoftMessage.writeXml(out, expectedMessages); + } + + } + + /** + * Write children. Assumes attributes are closed, + * can write child elements of current element. + */ + protected void writeChildren(XMLWriter out) { + if (0 < children.size()) { + for (Iterator iter = children.iterator(); iter.hasNext();) { + IXmlWritable self = (IXmlWritable) iter.next(); + self.writeXml(out); + } + } + } + + // --------------------------- logging + + public void printAll(PrintStream out, String prefix) { + out.println(prefix + toString()); + for (Iterator iter = children.iterator(); iter.hasNext();) { + AbstractRunSpec child = (AbstractRunSpec) iter.next(); // IRunSpec + child.printAll(out, prefix + " "); + } + } + + /** + * default implementation returns the description if not empty + * or the unqualified class name otherwise. + * Subclasses should not call toString from here unless they reimplement it. + * @return name of this thing or type + */ + protected String getPrintName() { + if (!LangUtil.isEmpty(description)) { + return description; + } else { + return LangUtil.unqualifiedClassName(this); + } + } + + /** @return summary count of spec elements */ + public String toString() { + return getPrintName() + "(" + containedSummary() + ")"; + } + + /** @return String of the form (# [options|paths|locations|messages]).. */ + protected String containedSummary() { + int nOptions = options.size(); + int nPaths = paths.size(); + int nLoc = sourceLocations.size(); + int nMssg = expectedMessages.size(); + return + ( (nOptions == 0 ? "" : nOptions + " options " ) + + (nPaths == 0 ? "" : nPaths + " paths " ) + + (nLoc == 0 ? "" : nLoc + " locations " ) + + (nMssg == 0 ? "" : nMssg + " messages" )).trim(); + } + + public String toLongString() { + String mssg = ""; + if (expectedMessages.size() > 0) { + // XXX add MessageHandler.renderCounts(IMessage[] messages) + MessageHandler h = new MessageHandler(); + for (Iterator iter = expectedMessages.iterator(); iter.hasNext();) { + h.handleMessage((IMessage) iter.next()); + } + mssg = " expected messages (" + MessageUtil.renderCounts(h) + ")"; + } + return getPrintName() + containedSummary() + mssg.trim(); + } + + private ArrayList makeList(List list) { + ArrayList result = new ArrayList(); + if (null != list) { + result.addAll(list); + } + return result; + } + + /** + * Subclasses use this to rename attributes or omit attributes or subelements. + * To suppress output of an attribute, * pass "" as the name of the attribute. + * To use default entries, pass null for that entry. + */ + public static class XMLNames { + public static final XMLNames DEFAULT = + new XMLNames(null, "description", "sourceLocation", + "keywords", "options", "paths", "comment", false, false, false); + final String descriptionName; + final String sourceLocationName; + final String keywordsName; + final String optionsName; + final String pathsName; + final String commentName; + final boolean skipDirChanges; + final boolean skipMessages; + final boolean skipChildren; + /** reset all names/behavior or pass defaultNames + * as the defaults for any null elements + */ + XMLNames(XMLNames defaultNames, + String descriptionName, + String sourceLocationName, + String keywordsName, + String optionsName, + String pathsName, + String commentName, + boolean skipDirChanges, + boolean skipMessages, + boolean skipChildren) { + this.skipDirChanges = skipDirChanges; + this.skipMessages = skipMessages; + this.skipChildren = skipChildren; + if (null != defaultNames) { + this.descriptionName = (null != descriptionName? descriptionName : defaultNames.descriptionName); + this.sourceLocationName = (null != sourceLocationName ? sourceLocationName : defaultNames.sourceLocationName); + this.keywordsName = (null != keywordsName ? keywordsName : defaultNames.keywordsName); + this.optionsName = (null != optionsName ? optionsName : defaultNames.optionsName); + this.pathsName = (null != pathsName ? pathsName : defaultNames.pathsName); + this.commentName = (null != commentName ? commentName : defaultNames.commentName); + } else { + this.descriptionName = descriptionName; + this.sourceLocationName = sourceLocationName; + this.keywordsName = keywordsName; + this.optionsName = optionsName; + this.pathsName = pathsName; + this.commentName = commentName; + } + } + } + + + /** subclasses implement this to create and set up a run */ + abstract public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator); + + /** This is for separate file (sub-) elements with path attributes */ + public static class WrapFile { + public String path; + public void setPath(String path) { + this.path = path; + } + } + + /** segregate runtime-only state in spec */ + public static class RT { + /** true if we should emit verbose messages */ + private boolean verbose; + + /** null unless parent set options for children to consider */ + final private ArrayList /*String*/ parentOptions; + + public RT() { + parentOptions = new ArrayList(); + } + + public boolean isVerbose() { + return verbose; + } + + /** + * Set parent options - old options destroyed. + * Will result in duplicates if duplicates added. + * Null or empty entries are ignored + * @param options ignored if null or empty + */ + public void setOptions(String[] options) { + parentOptions.clear(); + if (!LangUtil.isEmpty(options)) { + for (int i = 0; i < options.length; i++) { + if (!LangUtil.isEmpty(options[i])) { + parentOptions.add(options[i]); + } + } + } + } + + /** + * Copy values from another RT + * @param toCopy the RT to copy from + * @throws IllegalArgumentException if toCopy is null + */ + public void copy(RT toCopy) { + LangUtil.throwIaxIfNull(toCopy, "parent"); + parentOptions.clear(); + parentOptions.addAll(toCopy.parentOptions); + verbose = toCopy.verbose; + } + + /** + * Return any parent option which has one of validOptions as a prefix, + * optionally absorbing (removing) the parent option. + * @param validOptions String[] of options to extract + * @param absorb if true, then remove any parent option matched + * @return String[] containing any validOptions[i] in parentOptions + * (at most once) + */ + public String[] extractOptions(String[] validOptions, boolean absorb) { + if (LangUtil.isEmpty(validOptions) || LangUtil.isEmpty(parentOptions)) { + return new String[0]; + } + ArrayList result = new ArrayList(); + boolean haveOption = false; + for (int i = 0; i < validOptions.length; i++) { + String option = validOptions[i]; + if (LangUtil.isEmpty(option)) { + continue; + } + for (ListIterator iter = parentOptions.listIterator(); iter.hasNext();) { + String parentOption = (String) iter.next(); + if (parentOption.startsWith(option)) { + result.add(parentOption); + if (absorb) { + iter.remove(); + } + } + } + } + return (String[]) result.toArray(new String[0]); + } + + /** Get ListIterator that permits removals */ + ListIterator getListIterator() { + return parentOptions.listIterator(); + } + + /** + * Enable verbose logging + * @param verbose if true, do verbose logging + */ + public void setVerbose(boolean verbose) { + if (this.verbose != verbose) { + this.verbose = verbose; + } + } + } // class RT +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java b/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java new file mode 100644 index 000000000..943110df3 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/AjcMessageHandler.java @@ -0,0 +1,282 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.util.BridgeUtil; +import org.aspectj.testing.util.Diffs; +import org.aspectj.util.LangUtil; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + + +/** + * Adapter to pass messages through to handler, + * suppress output of expected errors and warnings, + * and calculate differences between expected and + * actual messages. + */ +public class AjcMessageHandler extends MessageHandler { + + /** Comparator for enclosed IMessage diffs, etc. */ + public static final Comparator COMP_IMessage + = BridgeUtil.Comparators.WEAK_IMessage; + + /** Comparator for enclosed File diffs, etc. */ + public static final Comparator COMP_File + = BridgeUtil.Comparators.WEAK_File; + + + /** unmodifiable list of IMessage messages of type IMesssage.ERROR */ + final List expectedErrors; // revert to IMessageHolder for generality? + + /** unmodifiable list of IMessage messages of type IMesssage.WARNING */ + final List expectedWarnings; + + /** unmodifiable list of File expected to be recompiled */ + final List expectedRecompiled; + + /** list of File actually recompiled */ + List actualRecompiled; + + /** if true, ignore warnings when calculating diffs and passed() */ + boolean ignoreWarnings; + + /** cache expected/actual diffs, nullify if any new message */ + transient CompilerDiffs diffs; + + private boolean expectingCommandTrue; + + /** + * @param messages List of IMessage to extract ERROR and WARNING from + */ + AjcMessageHandler(List messages) { + this(MessageUtil.getMessages(messages, IMessage.ERROR), + MessageUtil.getMessages(messages, IMessage.WARNING)); + } + + /** + * @param errors unmodifiable List of IMessage of kind ERROR to adopt + * @param warnings unmodifiable List of IMessage of kind WARNING to adopt + */ + AjcMessageHandler(List errors, List warnings) { + this(errors, warnings, null); + } + + AjcMessageHandler(List errors, List warnings, List recompiled) { + this.expectedErrors = LangUtil.safeList(errors); + this.expectedWarnings = LangUtil.safeList(warnings); + this.expectedRecompiled = LangUtil.safeList(recompiled); + expectingCommandTrue = (0 == expectedErrors.size()); + } + + public void setIgnoreWarnings(boolean ignoreWarnings) { + this.ignoreWarnings = ignoreWarnings; + } + + /** clear out any actual values to be re-run */ + public void init() { + super.init(); + actualRecompiled = null; + diffs = null; + } + + /** + * Return true if we have this kind of + * message for the same line and store all messages. + * @see bridge.tools.impl.ErrorHandlerAdapter#doShowMessage(IMessage) + * @return true if message handled (processing should abort) + */ + public boolean handleMessage(IMessage message) { + if (null == message) { + throw new IllegalArgumentException("null message"); + } + messages.add(message); + IMessage.Kind kind = message.getKind(); + return expecting(message, getExpected(kind)); + } + + /** + * Set the actual files recompiled. + * @param List of File recompiled - may be null; adopted but not modified + * @throws IllegalStateException if they have been set already. + */ + public void setRecompiled(List list) { + if (null != actualRecompiled) { + throw new IllegalStateException("actual recompiled already set"); + } + this.actualRecompiled = LangUtil.safeList(list); + } + + /** @return immutable List of IMessage expected of this kind */ + public List getExpected(IMessage.Kind kind) { + if (IMessage.ERROR == kind) { + return expectedErrors; + } else if (IMessage.WARNING == kind) { + return expectedWarnings; + } else { + return Collections.EMPTY_LIST; + } + } + + /** + * Check if the message was expected, and clear diffs if not. + * @return true if we expect a message of this kind with this line number + */ + protected boolean expecting(IMessage message, List sink) { // XXX ignores File + if ((null != message) && (0 < sink.size())) { + // XXX need to cache list: int[] errLines, warningLines; + for (Iterator iter = sink.iterator(); iter.hasNext();) { + IMessage m = (IMessage) iter.next(); + if (0 == COMP_IMessage.compare(message, m)) { + return true; + } + } + } + if (null != diffs) { + diffs = null; + } + return false; + } + + /** Generate differences between expected and actual errors and warnings */ + public CompilerDiffs getCompilerDiffs() { + if (null == diffs) { + final List actualErrors = Arrays.asList(getMessages(IMessage.ERROR, IMessageHolder.EQUAL)); + final List actualWarnings = Arrays.asList(getMessages(IMessage.WARNING, IMessageHolder.EQUAL)); + Diffs errors = new Diffs("error", expectedErrors, actualErrors, COMP_IMessage); + Diffs warnings = new Diffs("warning", expectedWarnings, actualWarnings, COMP_IMessage); + Diffs recompiled = new Diffs("recompiled", expectedRecompiled, actualRecompiled, COMP_File); + diffs = new CompilerDiffs(errors, warnings, recompiled); + } + return diffs; + } + + /** calculate passed based on default or set value for ignoreWarnings */ + public boolean passed() { + return passed(ignoreWarnings); + } + + /** @return true if we are expecting the command to fail - i.e., any expected errors */ + public boolean expectingCommandTrue() { + return expectingCommandTrue; + } + + /** + * Get the (current) result of this run, + * ignoring differences in warnings on request. + * Note it may return passed (true) when there are expected error messages. + * @return false + * if there are any fail or abort messages, + * or if the expected errors, warnings, or recompiled do not match actual. + * + */ + public boolean passed(boolean ignoreWarnings) { + if (hasAnyMessage(IMessage.FAIL, IMessageHolder.EQUAL)) { + return false; + } + + CompilerDiffs diffs = getCompilerDiffs(); + if (!ignoreWarnings) { + return (!diffs.different); + } else { + return ((!diffs.errors.different) && (!diffs.recompiled.different)); + } + } + + /** + * Report results to a handler, + * adding all messages + * and creating fail messages for each diff. + */ + public void report(IMessageHandler handler) { + if (null == handler) { + MessageUtil.debug(this, "report got null handler"); + } + IMessage[] messages = getMessages(null, IMessageHolder.EQUAL); + for (int i = 0; i < messages.length; i++) { + handler.handleMessage(messages[i]); + } + CompilerDiffs diffs = getCompilerDiffs(); + if (diffs.different) { + diffs.errors.report(handler, IMessage.FAIL); + diffs.warnings.report(handler, IMessage.FAIL); + diffs.recompiled.report(handler, IMessage.FAIL); + } + } + + /** @return String consisting of differences and any other messages */ + public String toString() { + CompilerDiffs diffs = getCompilerDiffs(); + StringBuffer sb = new StringBuffer(super.toString()); + final String EOL = "\n"; + sb.append(EOL); + render(sb, " unexpected error ", EOL, diffs.errors.unexpected); + render(sb, "unexpected warning ", EOL, diffs.warnings.unexpected); + render(sb, " missing error ", EOL, diffs.errors.missing); + render(sb, " missing warning ", EOL, diffs.warnings.missing); + render(sb, " fail ", EOL, getList(IMessage.FAIL)); + render(sb, " abort ", EOL, getList(IMessage.ABORT)); + render(sb, " info ", EOL, getList(IMessage.INFO)); + return sb.toString(); // XXX cache toString + } + + /** @return immutable list of a given kind - use null for all kinds */ + private List getList(IMessage.Kind kind) { + if ((null == kind) || (0 == numMessages(kind, IMessageHolder.EQUAL))) { + return Collections.EMPTY_LIST; + } + return Arrays.asList(getMessages(kind, IMessageHolder.EQUAL)); + } + + /** @return "" if no items or {prefix}{item}{suffix}... otherwise */ + private void render( // LangUtil instead? + StringBuffer result, + String prefix, + String suffix, + List items) { + if ((null != items)) { + for (Iterator iter = items.iterator(); iter.hasNext();) { + result.append(prefix + iter.next() + suffix); + } + } + } + + /** compiler results for errors, warnings, and recompiled files */ + public static class CompilerDiffs { + public final Diffs errors; + public final Diffs warnings; + public final Diffs recompiled; + public final boolean different; + + public CompilerDiffs(Diffs errors, Diffs warnings) { + this(errors, warnings, Diffs.NONE); + } + public CompilerDiffs(Diffs errors, Diffs warnings, Diffs recompiled) { + this.errors = errors; + this.warnings = warnings; + this.recompiled = recompiled; + different = (warnings.different || errors.different + || recompiled.different); + } + } +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/AjcTest.java b/testing/src/org/aspectj/testing/harness/bridge/AjcTest.java new file mode 100644 index 000000000..b1e9e9b1b --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/AjcTest.java @@ -0,0 +1,371 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.Runner; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * An AjcTest has child subruns (compile, [inc-compile|run]*). + */ +public class AjcTest extends RunSpecIterator { + + /** Unwrap an AjcTest.Spec from an IRunStatus around an AjcTest */ + public static Spec unwrapSpec(IRunStatus status) { + if (null != status) { + Object id = status.getIdentifier(); + if (id instanceof Runner.IteratorWrapper) { + IRunIterator iter = ((Runner.IteratorWrapper) id).iterator; + if (iter instanceof AjcTest) { + return (Spec) ((AjcTest) iter).spec; + } + } + } + return null; + } + + /** Unwrap initial CompilerRun.Spec from an AjcTest.Spec */ + public static CompilerRun.Spec unwrapCompilerRunSpec(Spec spec) { + if (null != spec) { + List kids = spec.getChildren(); + if (0 < kids.size()) { + Object o = kids.get(0); + if (o instanceof CompilerRun.Spec) { + return (CompilerRun.Spec) o; + } + } + } + return null; + } + + /** The spec creates the sandbox, so we use it throughout */ + public AjcTest(Spec spec, Sandbox sandbox, Validator validator) { + super(spec, sandbox, validator, true); + } + + /** + * Clear the command from the sandbox, to avoid memory leaks. + * @see org.aspectj.testing.harness.bridge.RunSpecIterator#iterationCompleted() + */ + public void iterationCompleted() { + super.iterationCompleted(); + sandbox.clearCommand(this); + } + + + /** + * Specification for an ajc test. + * Keyword directives are global/parent options passed as + * <pre>-ajctest[Require|Skip]Keywords=keyword{,keyword}..</pre>. + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "ajc-test"; + /** + * do description as title, do sourceLocation, + * do keywords, do options, skip paths, do comment, + * skip dirChanges, do messages and do children + * (though we do children directly). + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + "title", null, null, null, "", null, true, false, false); + + private static final String OPTION_PREFIX = "-ajctest"; + private static final String[] VALID_OPTIONS = new String[] { OPTION_PREFIX }; + + private static final String REQUIRE_KEYWORDS = "RequireKeywords="; + private static final String SKIP_KEYWORDS = "SkipKeywords="; + private static final String PICK_PR = "PR="; + private static final List VALID_SUFFIXES + = Collections.unmodifiableList(Arrays.asList(new String[] + { REQUIRE_KEYWORDS, SKIP_KEYWORDS, PICK_PR })); + + /** base directory of the test suite - set before making run */ + private File suiteDir; + + /** path offset from suite directory to base of test directory */ + String testDirOffset; // XXX revert to private after fixes + + /** id of bug - if 0, then no bug associated with this test */ + private int bugId; + + public Spec() { + super(XMLNAME); + setXMLNames(NAMES); + } + + public void setSuiteDir(File suiteDir) { + this.suiteDir = suiteDir; + } + + public File getSuiteDir() { + return suiteDir; + } + + /** @param bugId 100..9999 */ + public void setBugId(int bugId) { + LangUtil.throwIaxIfFalse((bugId > 10) && (bugId < 10000), "bad bug id: " + bugId); + this.bugId = bugId; + } + + public int getBugId() { + return bugId; + } + + public void setTestDirOffset(String testDirOffset) { + if (!LangUtil.isEmpty(testDirOffset)) { + this.testDirOffset = testDirOffset; + } + } + + public String getTestDirOffset() { + return (null == testDirOffset ? "" : testDirOffset); + } + + /** + * @param sandbox ignored + * @see org.aspectj.testing.harness.bridge.AbstractRunSpec#makeAjcRun(Sandbox, Validator) + */ + public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) { + LangUtil.throwIaxIfNull(validator, "validator"); + + // if no one set suiteDir, see if we have a source location + if (null == suiteDir) { + ISourceLocation loc = getSourceLocation(); + if (!validator.nullcheck(loc, "suite file location") + || !validator.nullcheck(loc.getSourceFile(), "suite file")) { + return null; + } + File locDir = loc.getSourceFile().getParentFile(); + if (!validator.canReadDir(locDir, "source location dir")) { + return null; + } + suiteDir = locDir; + } + + // we make a new sandbox with more state for our subruns, keep that, + // in order to defer initialization to nextRun() + File testBaseDir; + String testDirOffset = getTestDirOffset(); + if (LangUtil.isEmpty(testDirOffset)) { + testBaseDir = suiteDir; + } else { + testBaseDir = new File(suiteDir, testDirOffset); + if (!validator.canReadDir(testBaseDir, "testBaseDir")) { + return null; + } + } + Sandbox childSandbox = null; + try { + childSandbox = new Sandbox(testBaseDir, validator); + validator.registerSandbox(childSandbox); + } catch (IllegalArgumentException e) { + validator.fail(e.getMessage()); + return null; + } + return new AjcTest(this, childSandbox, validator); + } + + /** @see IXmlWritable#writeXml(XMLWriter) */ + public void writeXml(XMLWriter out) { + out.println(""); + String value = (null == testDirOffset? "" : testDirOffset); + String attr = out.makeAttribute("dir", value); + if (0 != bugId) { + attr += " " + out.makeAttribute("pr", ""+bugId); + } + out.startElement(xmlElementName, attr, false); + super.writeAttributes(out); + out.endAttributes(); + super.writeChildren(out); + out.endElement(xmlElementName); + } + + /** + * AjcTest overrides this to skip if + * <ul> + * <li>the spec has a keyword the parent wants to skip</li> + * <li>the spec does not have keyword the parent requires</li> + * <li>the spec does not have the bugId required</li> + * </ul> + * @return false if this wants to be skipped, true otherwise + * @throws Error if selected option is not of the form + * <pre>-ajctest[Require|Skip]Keywords=keyword{,keyword}..</pre>. + */ + protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) { + if (!super.doAdoptParentValues(parentRuntime, handler)) { + return false; + } + runtime.copy(parentRuntime); + + String[] globalOptions = runtime.extractOptions(VALID_OPTIONS, true); + for (int i = 0; i < globalOptions.length; i++) { + String option = globalOptions[i]; + if (!option.startsWith(OPTION_PREFIX)) { + throw new Error("only expecting " + OPTION_PREFIX + "..: " + option); + } + option = option.substring(OPTION_PREFIX.length()); + boolean keywordMustExist = false; + String havePr = null; + if (option.startsWith(REQUIRE_KEYWORDS)) { + option = option.substring(REQUIRE_KEYWORDS.length()); + keywordMustExist = true; + } else if (option.startsWith(SKIP_KEYWORDS)) { + option = option.substring(SKIP_KEYWORDS.length()); + } else if (option.startsWith(PICK_PR)) { + if (0 == bugId) { + skipMessage(handler, "bugId required, but no bugId for this test"); + return false; + } else { + havePr = "" + bugId; + } + option = option.substring(PICK_PR.length()); + } else { + throw new Error("unrecognized suffix: " + globalOptions[i] + + " (expecting: " + OPTION_PREFIX + VALID_SUFFIXES + "...)"); + } + List specs = LangUtil.commaSplit(option); + // XXX also throw Error on empty specs... + for (Iterator iter = specs.iterator(); iter.hasNext();) { + String spec = (String) iter.next(); + if (null != havePr) { + if (havePr.equals(spec)) { // String.equals() + return true; + } + } else if (keywordMustExist != keywords.contains(spec)) { + String reason = "keyword " + spec + + " was " + (keywordMustExist ? "not found" : "found"); + skipMessage(handler, reason); + return false; + } + } + if (null != havePr) { + skipMessage(handler, "bugId required, but not matched for this test"); + return false; + } + } + return true; + } + + } // AjcTest.Spec + + /** + * A suite of AjcTest has children for each AjcTest + * and flows all options down as globals + */ + public static class Suite extends RunSpecIterator { + final Spec spec; + public Suite(Spec spec, Sandbox sandbox, Validator validator) { + super(spec, sandbox, validator, false); + this.spec = spec; + } + + /** + * While being called to make the sandbox for the child, + * set up the child's suite dir based on ours. + * @param child must be instanceof AjcTest.Spec + * @see org.aspectj.testing.harness.bridge.RunSpecIterator#makeSandbox(IRunSpec, Validator) + * @return super.makeSandbox(child, validator) + */ + protected Sandbox makeSandbox( + IRunSpec child, + Validator validator) { + if (!(child instanceof AjcTest.Spec)) { + validator.fail("only expecting AjcTest children"); + return null; + } + if (!validator.canReadDir(spec.suiteDir, "spec.suiteDir")) { + return null; + } + ((AjcTest.Spec) child).setSuiteDir(spec.suiteDir); + return super.makeSandbox(child, validator); + } + + /** + * A suite spec contains AjcTest children. + * The suite dir or source location should be set + * if the tests do not each have a source location + * with a source file in the suite dir. + * XXX whether to write out suiteDir in XML? + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "suite"; + /** + * do description, do sourceLocation, + * do keywords, do options, skip paths, do comment + * skip dirChanges, skip messages and do children + * (though we do children directly). + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + null, null, null, null, "", null, true, true, false); + File suiteDir; + public Spec() { + super(XMLNAME, false); // do not skip this even if children skip + } + + /** @param suiteDirPath the String path to the base suite dir */ + public void setSuiteDir(String suiteDirPath) { + if (!LangUtil.isEmpty(suiteDirPath)) { + this.suiteDir = new File(suiteDirPath); + } + } + + /** @param suiteDirFile the File for the base suite dir */ + public void setSuiteDirFile(File suiteDir) { + this.suiteDir = suiteDir; + } + + /** @get suiteDir from any set or source location if set */ + public File getSuiteDirFile() { + if (null == suiteDir) { + ISourceLocation loc = getSourceLocation(); + if (null != loc) { + File sourceFile = loc.getSourceFile(); + if (null != sourceFile) { + suiteDir = sourceFile.getParentFile(); + } + } + } + return suiteDir; + } + + /** + * @return + * @see org.aspectj.testing.harness.bridge.AbstractRunSpec#makeRunIterator(Sandbox, Validator) + */ + public IRunIterator makeRunIterator( + Sandbox sandbox, + Validator validator) { + return new Suite(this, sandbox, validator); + } + + public String toString() { + // removed nKids as misleading, since children.size() may change + //int nKids = children.size(); + //return "Suite.Spec(" + suiteDir + ", " + nKids + " tests)"; + return "Suite.Spec(" + suiteDir + ")"; + } + } + } +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java new file mode 100644 index 000000000..ccb38b2f4 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java @@ -0,0 +1,966 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.ICommand; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.ReflectionFactory; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.WrappedRunIterator; +import org.aspectj.testing.xml.SoftMessage; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Run the compiler once in non-incremental mode. + */ +public class CompilerRun implements IAjcRun { + static final String[] RA_String = new String[0]; + + static final String[] JAR_SUFFIXES = new String[] { ".jar", ".zip" }; + + static final String[] SOURCE_SUFFIXES + = (String[]) FileUtil.SOURCE_SUFFIXES.toArray(new String[0]); + + /** specifications, set on construction */ + Spec spec; + + //------------ calculated during setup + /** get shared stuff during setup */ + Sandbox sandbox; + + /** + * During run, these String are passed as the source and arg files to compile. + * The list is set up in setupAjcRun(..), when arg files are prefixed with "@". + */ + final List /*String*/ arguments; + + /** + * During run, these String are collapsed and passed as the injar option. + * The list is set up in setupAjcRun(..). + */ + final List /*String*/ injars; + + /** + * During run, these String are collapsed and passed as the aspectpath option. + * The list is set up in setupAjcRun(..). + */ + final List /*String*/ aspectpath; + + private CompilerRun(Spec spec) { + if (null == spec) { + throw new IllegalArgumentException("null spec"); + } + this.spec = spec; + arguments = new ArrayList(); + injars = new ArrayList(); + aspectpath = new ArrayList(); + } + + /** + * This checks that the spec is reasonable and does setup: + * <li>calculate and set sandbox testBaseSrcDir as + * {Sandbox.testBaseDir}/{Spec.testSrcDirOffset}/<li> + * <li>the list of source File to compile as + * {Sandbox.testBaseSrcDir}/{Spec.getPaths..}</li> + * All sources must be readable at this time. + * @param classesDir the File + * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File) + * @throws AbortException containing IOException or IllegalArgumentException + * if the staging operations fail + */ + public boolean setupAjcRun(Sandbox sandbox, Validator validator) { + + if (!validator.nullcheck(spec.getOptionsArray(), "localOptions") + || !validator.nullcheck(sandbox, "sandbox") + || !validator.nullcheck(spec.compiler, "compilerName") + || !validator.canRead(Globals.F_aspectjrt_jar, "aspectjrt.jar") + || !validator.canRead(Globals.F_testingclient_jar, "testing-client.jar") + //|| !validator.canRead(Main.F_bridge_jar, "bridge.jar") + ) { + return false; + } + + this.sandbox = sandbox; + + File[] cp = new File[] + { sandbox.classesDir, Globals.F_aspectjrt_jar, Globals.F_testingclient_jar }; + sandbox.setClasspath(cp, true, this); + + String rdir = spec.testSrcDirOffset; + File testBaseSrcDir; + if ((null == rdir) || (0 == rdir.length())) { + testBaseSrcDir = sandbox.testBaseDir; + } else { + testBaseSrcDir = new File(sandbox.testBaseDir, rdir); + if (!validator.canReadDir(testBaseSrcDir, "sandbox.testBaseSrcDir")) { + return false; + } + } + sandbox.setTestBaseSrcDir(testBaseSrcDir, this); + + arguments.clear(); + + // sources come as relative paths - check read, copy if staging + + // this renders paths absolute before run(RunStatusI) is called + // for a compile run to support relative paths + source base + // change so the run calculates the paths (differently when staging) + + final String[] injarPaths; + final String[] srcPaths; + { + final String[] paths = spec.getPathsArray(); + srcPaths = LangUtil.endsWith(paths, CompilerRun.SOURCE_SUFFIXES, true); + injarPaths = LangUtil.endsWith(paths, CompilerRun.JAR_SUFFIXES, true); + } + // validate readable for sources + if (!validator.canRead(testBaseSrcDir, srcPaths, "sources") + || !validator.canRead(testBaseSrcDir, injarPaths, "injars") + || !validator.canRead(testBaseSrcDir, spec.argfiles, "argfiles")) { + return false; + } + int numSources = srcPaths.length + injarPaths.length + spec.argfiles.length; + if (numSources < 1) { + validator.fail("no source files, input jars, or arg files"); + return false; + } + + File[] srcFiles; + File[] argFiles; + File[] injarFiles; + File[] aspectFiles; + if (!spec.isStaging()) { // XXX why this? was always? || (testBaseSrcDir != sandbox.stagingDir))) { + srcFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, srcPaths, CompilerRun.SOURCE_SUFFIXES); + argFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.argfiles); + injarFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, injarPaths); + aspectFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.aspectpath); + } else { // staging - copy files + try { + srcFiles = FileUtil.copyFiles(testBaseSrcDir, srcPaths, sandbox.stagingDir); + argFiles = FileUtil.copyFiles(testBaseSrcDir, spec.argfiles, sandbox.stagingDir); + injarFiles = FileUtil.copyFiles(testBaseSrcDir, injarPaths, sandbox.stagingDir); + aspectFiles = FileUtil.copyFiles(testBaseSrcDir, spec.aspectpath, sandbox.stagingDir); + } catch (IllegalArgumentException e) { + validator.fail("staging - bad input", e); + return false; + } catch (IOException e) { + validator.fail("staging - operations", e); + return false; + } + // validate readable for copied sources + if (!validator.canRead(srcFiles, "copied paths") + || !validator.canRead(argFiles, "copied argfiles")) { + return false; + } + } + arguments.clear(); + injars.clear(); + aspectpath.clear(); + if (!LangUtil.isEmpty(srcFiles)) { + arguments.addAll(Arrays.asList(FileUtil.getPaths(srcFiles))); + } + if (!LangUtil.isEmpty(injarFiles)) { + injars.addAll(Arrays.asList(FileUtil.getPaths(injarFiles))); + } + if (!LangUtil.isEmpty(aspectFiles)) { + aspectpath.addAll(Arrays.asList(FileUtil.getPaths(aspectFiles))); + } + if (!LangUtil.isEmpty(argFiles)) { + String[] ra = FileUtil.getPaths(argFiles); + for (int j = 0; j < ra.length; j++) { + arguments.add("@" + ra[j]); + } + if (spec.isStaging) { + validator.info("warning: files listed in argfiles not staged"); + } + } + + return true; + } +// if ((null != spec.dirChanges) && (0 < spec.numMessages(IMessage.ERROR, false))) { +// validator.info("CompilerRun warning: when expecting errors, cannot expect dir changes"); +// } + + + /** + * Setup result evaluation and command line, run, and evaluate result. + * <li>setup an AjcMessageHandler using the expected messages from + * {@link Spec#getMessages()}.<li> + * <li>heed any globals interpreted into a TestSetup by reading + * {@link Spec@getOptions()}. For a list of supported globals, see + * {@link setupArgs(ArrayList, IMessageHandler}.</li> + * <li>construct a command line, using as classpath + * {@link Sandbox.classpathToString()}<li> + * <li>construct a compiler using {@link Spec#compilerName} + * or any overriding value set in TestSetup.<li> + * <li>Just before running, set the compiler in the sandbox using + * {@link Sandbox.setCompiler(ICommand)}.<li> + * <li>After running, report AjcMessageHandler results to the status parameter. + * If the AjcMessageHandler reports a failure, then send info messages + * for the Spec, TestSetup, and command line.<li> + * XXX better to upgrade AjcMessageHandler to adopt status + * so the caller can control fast-fail, etc. + * @see org.aspectj.testing.run.IRun#run(IRunStatus) + */ + public boolean run(IRunStatus status) { + if (null == spec.testSetup) { + MessageUtil.abort(status, "no test setup - adoptParentValues not called"); + return false; + } else if (!spec.testSetup.result) { + MessageUtil.abort(status, spec.testSetup.failureReason); + return false; + } + AjcMessageHandler handler = new AjcMessageHandler(spec.getMessages()); + handler.init(); + boolean handlerResult = false; + boolean result = false; + boolean commandResult = false; + ArrayList argList = new ArrayList(); + final Spec.TestSetup setupResult = spec.testSetup; + try { + argList.addAll(setupResult.commandOptions); + argList.add("-d"); + String outputDirPath = sandbox.classesDir.getAbsolutePath(); + try { // worth it to try for canonical? + outputDirPath = sandbox.classesDir.getCanonicalPath(); + } catch (IOException e) { + MessageUtil.abort(status, "canonical " + sandbox.classesDir, e); + } + argList.add(outputDirPath); + argList.add("-classpath"); + argList.add(sandbox.classpathToString(this)); + // XXX classpath additions here? + + if (0 < injars.size()) { + argList.add("-injars"); + argList.add(FileUtil.flatten((String[]) injars.toArray(new String[0]), null)); + } + + if (0 < aspectpath.size()) { + argList.add("-aspectpath"); + argList.add(FileUtil.flatten((String[]) aspectpath.toArray(new String[0]), null)); + } + + // add both java/aspectj and argfiles + argList.addAll(arguments); + + // hack - do seeking on request as a side effect. reimplement as listener + if (null != setupResult.seek) { + String slopPrefix = Spec.SEEK_MESSAGE_PREFIX + " slop - "; + PrintStream slop = MessageUtil.handlerPrintStream( + status, + IMessage.INFO, + System.err, + slopPrefix); + List found = FileUtil.lineSeek(setupResult.seek, arguments, false, slop); + if (!LangUtil.isEmpty(found)) { + for (Iterator iter = found.iterator(); iter.hasNext();) { + MessageUtil.info(status, Spec.SEEK_MESSAGE_PREFIX + iter.next()); + } + } + } + + ICommand compiler = ReflectionFactory.makeCommand(setupResult.compilerName, status); + DirChanges dirChanges = null; + if (null == compiler) { + MessageUtil.fail(status, "unable to make compiler " + setupResult.compilerName); + return false; + } else { + if (status.aborted()) { + MessageUtil.debug(status, "aborted, but compiler valid?: " + compiler); + } else { + // same DirChanges handling for JavaRun, CompilerRun, IncCompilerRun + // XXX around advice or template method/class + if (!LangUtil.isEmpty(spec.dirChanges)) { + LangUtil.throwIaxIfFalse(1 == spec.dirChanges.size(), "expecting 0..1 dirChanges"); + dirChanges = new DirChanges((DirChanges.Spec) spec.dirChanges.get(0)); + if (!dirChanges.start(status, sandbox.classesDir)) { + return false; // setup failed + } + } + MessageUtil.info(status, compiler + "(" + argList + ")"); + sandbox.setCommand(compiler, this); + String[] args = (String[]) argList.toArray(RA_String); + commandResult = compiler.runCommand(args, handler); + } + } + if (!setupResult.ignoreWarningsSet) { + handlerResult = handler.passed(); + } else { + handlerResult = handler.passed(setupResult.ignoreWarnings); + } + if (!handlerResult) { + return false; + } else { + result = (commandResult == handler.expectingCommandTrue()); + if (! result) { + String m = commandResult + ? "compile did not fail as expected" + : "compile failed unexpectedly"; + MessageUtil.fail(status, m); + } else if (null != dirChanges) { + result = dirChanges.end(status, sandbox.testBaseDir); + } + } + return result; + } finally { + if (!handlerResult) { // more debugging context in case of failure + MessageUtil.info(handler, spec.toLongString()); + MessageUtil.info(handler, "" + argList); + if (null != setupResult) { + MessageUtil.info(handler, "" + setupResult); + } + } + handler.report(status); // XXX weak - actual messages not reported in real-time, no fast-fail + } + } + + + public String toString() { + return "CompilerRun(" + spec + ")"; + } +// String[] sourcePaths = (null == this.sourcePaths ? new String[0] : this.sourcePaths); +// List sources = (null == this.sources ? Collections.EMPTY_LIST : this.sources); +// return "CompilerRun-" + compilerName +// + "(" + Arrays.asList(sourcePaths) +// + ", " + sources +// + ", " + Arrays.asList(globalOptions) +// + ", " + Arrays.asList(localOptions) +// + ")"; + + /** + * Initializer/factory for CompilerRun + * any path or file is relative to this test base dir + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "compile"; + static final String SEEK_PREFIX = "-seek:"; + static final String SEEK_MESSAGE_PREFIX = "found: "; + + /** no support in the harness for these otherwise-valid options */ + private static final String[] INVALID_OPTIONS = new String[] + { "-workingdir", "-argfile", "-sourceroot", "-outjar"}; + // when updating these, update tests/harness/selectionTest.xml + + /** no support in the eclipse-based compiler for these otherwise-valid options */ + private static final String[] INVALID_ECLIPSE_OPTIONS = new String[] + { "-lenient", "-strict", "-usejavac", "-preprocess", + "-XOcodeSize", "-XSerializable", "-XaddSafePrefix", + "-XserializableAspects", "-XtargetNearSource" }; + + /** options supported by the harness */ + private static final String[] VALID_OPTIONS = new String[] + { + SEEK_PREFIX, + // eajc does not support -usejavac, -preprocess + // testFlag() handles -ajc, -eclipse, -ignoreWarnings + "-usejavac", "-preprocess", + "-Xlint", "-lenient", "-strict", + "-source14", "-verbose", "-emacssym", + "-ajc", "-eclipse", "-ignoreWarnings", + + "!usejavac", "!preprocess", + "!Xlint", "!lenient", "!strict", + "!source14", "!verbose", "!emacssym", + "!ajc", "!eclipse", + + "^usejavac", "^preprocess", + "^Xlint", "^lenient", "^strict", + "^source14", "^verbose", "^emacssym", + "^ajc", "^eclipse" + }; + public static final String DEFAULT_COMPILER + = ReflectionFactory.ECLIPSE; +// = ReflectionFactory.OLD_AJC; + /** + * Retitle description to title, paths to files, do comment + * do dirChanges, and print no chidren. + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + "title", null, null, null, "files", null, false, false, true); + + protected String compiler; + + protected TestSetup testSetup; + + protected String[] argfiles = new String[0]; + protected String[] aspectpath = new String[0]; + + /** src path = {suiteParentDir}/{testBaseDirOffset}/{testSrcDirOffset}/{path} */ + protected String testSrcDirOffset; + + public Spec() { + super(XMLNAME); + setXMLNames(NAMES); + compiler = DEFAULT_COMPILER; + } + + public void setCompiler(String compilerName) { + this.compiler = compilerName; + } + + public void setTestSrcDirOffset(String s) { + if (null != s) { + testSrcDirOffset = s; + } + } + + /** override to set dirToken to Sandbox.CLASSES and default suffix to ".class" */ + public void addDirChanges(DirChanges.Spec spec) { + if (null == spec) { + return; + } + spec.setDirToken(Sandbox.CLASSES_DIR); + spec.setDefaultSuffix(".class"); + super.addDirChanges(spec); + } + protected String getPrintName() { + return "CompilerRun.Spec " + getShortCompilerName(); + } + + public String toLongString() { + return getPrintName() + "(" + super.containedSummary() + ")"; + } + + public String toString() { + return getPrintName() + "(" + super.containedSummary() + ")"; + } + + /** bean mapping for writers */ + public void setFiles(String paths) { + addPaths(paths); + } + + /** + * Set aspectpath, deleting any old ones + * @param files comma-delimited list of argfiles - ignored if null or empty + */ + public void setAspectpath(String files) { + if (!LangUtil.isEmpty(files)) { + aspectpath = XMLWriter.unflattenList(files); + } + } + + /** + * Set argfiles, deleting any old ones + * @param files comma-delimited list of argfiles - ignored if null or empty + */ + public void setArgfiles(String files) { + if (!LangUtil.isEmpty(files)) { + argfiles = XMLWriter.unflattenList(files); + } + } + + /** @return String[] copy of argfiles array */ + public String[] getArgfilesArray() { + String[] argfiles = this.argfiles; + if (LangUtil.isEmpty(argfiles)) { + return new String[0]; + } + return (String[]) LangUtil.copy(argfiles); + } + + /** + * This implementation skips if: + * <ul> + * <li>incremental test, but using ajc (not eclipse)</li> + * <li>usejavac, but javac is not available on the classpath</li> + * <li>eclipse, but -usejavac or -preprocess test</li> + * <li>-source14, but running under 1.2 (XXX design)</li> + * <li>local/global option conflicts (-lenient/-strict)</li> + * <li>semantic conflicts (e.g., -lenient/-strict)</li> + * </ul> + * @return false if this wants to be skipped, true otherwise + */ + protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) { + if (!super.doAdoptParentValues(parentRuntime, handler)) { + return false; + } + testSetup = setupArgs(handler); + if (!testSetup.result) { + skipMessage(handler, testSetup.failureReason); + } + return testSetup.result; + } + + + private String getShortCompilerName() { + String cname = compiler; + if (null != testSetup) { + cname = testSetup.compilerName; + } + if (null != cname) { + int loc = cname.lastIndexOf("."); + if (-1 != loc) { + cname = cname.substring(loc+1); + } + } + return cname; + } + + /** @return a CompilerRun with this as spec if setup completes successfully. */ + public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) { + CompilerRun run = new CompilerRun(this); + if (run.setupAjcRun(sandbox, validator)) { + // XXX need name for compilerRun + return new WrappedRunIterator(this, run); + } + return null; + } + + /** + * Each non-incremental run, fold the global flags in with + * the run flags, which may involve adding or removing from + * either list, depending on the flag prefix: + * <ul> + * <li>-foo: use -foo unless forced off.<li> + * <li>^foo: (force off) remove any -foo option from the run flags</li> + * <li>!foo: (force on) require the -foo flag </li> + * </ul> + * If there is a force conflict, then the test is skipped + * ("skipping" info message, TestSetup.result is false). + * This means an option local to the test which was specified + * without forcing may be overridden by a globally-forced option. + * <p> + * There are some flags which are interpreted by the test + * and removed from the list of flags passed to the command + * (see testFlag()): + * <ul> + * <li>eclipse: use the new eclipse compiler (can force)</li> + * <li>ajc: use the old ajc compiler (can force)</li> + * <li>ignoreWarnings: ignore warnings in result evaluations (no force)</li> + * </ul> + * <p> + * There are some flags which are inconsistent with each other. + * These are treated as conflicts and the test is skipped: + * <ul> + * <li>lenient, strict</li> + * </ul> + * <p> + * The -source 1.4 flag should always be specified as -source14, + * as this will otherwise fail to process it correctly. + * This converts it back to -source 1.4. + * <p> + * Finally, compiler limitations are enforced here by skipping + * tests which the compiler cannot do: + * <ul> + * <li>eclipse does not do -lenient, -strict, -usejavac, -preprocess, + * -XOcodeSize, -XSerializable, XaddSafePrefix, + * -XserializableAspects,-XtargetNearSource</li> + * <li>ajc does not run in incremental (staging) mode, + * nor with -usejavac if javac is not on the classpath</li> + * </ul> + * <u>Errors</u>:This will remove an arg not prefixed by [-|!|^] after + * providing an info message. + * <u>TestSetup Result</u>: + * If this completes successfully, then TestSetup.result is true, + * and commandOptions is not null, and any test flags (ignore warning, + * compiler) are reflected in the TestSetup. + * If this fails, then TestSetup.result is false, + * and a TestSetup.failreason is set. + * @return TestSetup with results + * (TestSetup result=false if the run should not continue) + */ + protected TestSetup setupArgs(IMessageHandler handler) { + // warning: HarnessSelectionTest checks for specific error wording + ArrayList argList = new ArrayList(); + argList.addAll(getOptionsList()); + final Spec spec = this; + TestSetup result = new TestSetup(); + if (argList.contains("-source")) { + result.failureReason = "use -source14 for -source 1.4: " + argList; + return result; + } + result.compilerName = spec.compiler; + String[] globalOptions = spec.runtime.extractOptions(Spec.VALID_OPTIONS, true); + if ((null != globalOptions) && (globalOptions.length > 0)) { + // --- fold in globals, removing conflicts, etc. + for (int i = 0; i < globalOptions.length; i++) { + String globalArg = globalOptions[i]; + if ((null == globalArg) || (2 > globalArg.length())) { + continue; + } else if (globalArg.startsWith(SEEK_PREFIX)) { + result.seek = globalArg.substring(SEEK_PREFIX.length()); + continue; + } else if ("-source".equals(globalArg)) { + result.failureReason = "use -source14 for -source 1.4 [" + i + "]"; + return result; + } + char first = globalArg.charAt(0); + globalArg = globalArg.substring(1); + boolean globalForceOn = (first == '!'); + boolean globalForceOff = (first == '^'); + boolean globalSet = (first == '-'); + if (!globalSet && !globalForceOn && !globalForceOff) { + MessageUtil.info(handler, "ignoring bad global: " + globalOptions[i]); + continue; + } + int argIndex = indexOf(globalArg, argList); + if (-1 == argIndex) { // no apparent conflict - look for eclipse/ajc conflicts XXX unresolved + boolean ajcGlobal = true; + if ("ajc".equals(globalArg)) { + argIndex = indexOf("eclipse", argList); + } else if ("eclipse".equals(globalArg)) { + argIndex = indexOf("ajc", argList); + ajcGlobal = false; + } + if (-1 != argIndex) { // resolve eclipse/ajc conflict + String arg = ((String) argList.get(argIndex)); + char argFirst = arg.charAt(0); + argList.remove(arg); // replace with resolved variant... + char ajcFirst; + char eclipseFirst; + if (ajcGlobal) { + ajcFirst = first; + eclipseFirst = argFirst; + } else { + ajcFirst = argFirst; + eclipseFirst = first; + } + if ('!' == eclipseFirst) { + if ('!' == ajcFirst) { + result.failureReason = "conflict between !eclipse and !ajc"; + return result; + } else { + argList.add("-eclipse"); + } + } else if (('!' == ajcFirst) || ('^' == eclipseFirst)) { + argList.add("-ajc"); + } else if ('^' == ajcFirst) { + argList.add("-eclipse"); + } else if (('-' != ajcFirst) || ('-' != eclipseFirst)) { + result.failureReason = "harness logic error resolving " + + arg + " and global " + globalArg; + return result; + } else if (ajcGlobal) { + argList.add("-ajc"); + } else { + argList.add("-eclipse"); + } + continue; // resolved + } + } + + if (-1 == argIndex) { // no dup, so no conflict + if (!globalForceOff) { + argList.add("-" + globalArg); + } + } else { // have conflict - resolve + String arg = (String) argList.get(argIndex); + first = arg.charAt(0); + boolean localForceOn = (first == '!'); + boolean localForceOff = (first == '^'); + boolean localSet = (first == '-'); + if (!localSet && !localForceOn && !localForceOff) { + result.failureReason = "only handling [-^!]{arg}: " + arg; + return result; + } + if ((localForceOn && globalForceOff) + || (localForceOff && globalForceOn)) { + result.failureReason = "force conflict between arg=" + + arg + " and global=" + globalOptions[i]; + return result; + } + if (globalForceOn) { + if (localForceOn) { // localSet is already correct, localForceOff was conflict + argList.remove(arg); // no !funkiness + argList.add("-" + globalArg); + } + } else if (globalSet) { + if (localSet) { + // do nothing - already correct + } else if (localForceOn) { + argList.remove(arg); // no !funkiness + argList.add("-" + globalArg); + } + } else if (globalForceOff) { + argList.remove(arg); + } else { + throw new Error("illegal arg state?? : " + arg); + //MessageUtil.info(handler, "illegal arg state??: " + arg); + } + } + } + } + + // remove funky prefixes from remainder, fixup two-part flags + // and interpret special flags + boolean source14 = false; + ArrayList toAdd = new ArrayList(); + for (ListIterator iter = argList.listIterator(); iter.hasNext();) { + String arg = (String) iter.next(); + if (testFlag(arg, result)) { + iter.remove(); + continue; + } + char c = arg.charAt(0); + String rest = arg.substring(1); + if (c == '^') { + iter.remove(); + continue; + } + if (c == '!') { + iter.remove(); + if (!("source14".equals(rest))) { + toAdd.add("-" + rest); + } else { + source14 = true; + } + rest = null; + } else if ("source14".equals(rest)) { + iter.remove(); + source14 = true; + } + } + if (source14) { + // must run under 1.4 VM or (if ajc) set up bootclasspath + if (!LangUtil.supportsJava("1.4")) { + if (ReflectionFactory.ECLIPSE.equals(result.compilerName)) { + result.failureReason + = "eclipse must run under 1.4 to implement -source 1.4"; + return result; + } + + if (!FileUtil.canReadFile(Globals.J2SE14_RTJAR)) { + result.failureReason + = "unable to get 1.4 libraries to implement -source 1.4"; + return result; + } + toAdd.add("-bootclasspath"); + toAdd.add(Globals.J2SE14_RTJAR.getAbsolutePath()); + } + toAdd.add("-source"); + toAdd.add("1.4"); + } + argList.addAll(toAdd); + + // finally, check for semantic conflicts + String[] badOptions = LangUtil.selectOptions(argList, Spec.INVALID_OPTIONS); + if (!LangUtil.isEmpty(badOptions)) { + result.failureReason = "no support for (normally-valid) options " + + Arrays.asList(badOptions); + } else if (argList.contains("-lenient") && argList.contains("-strict")) { + result.failureReason = "semantic conflict -lenient | -strict"; + } else if (ReflectionFactory.OLD_AJC.equals(result.compilerName)) { + if (spec.isStaging) { + result.failureReason = "OLD_AJC does not do incremental compiles"; + } else if (argList.contains("-usejavac") && !haveJavac()) { + result.failureReason = "-usejavac but no javac on classpath"; + } else { + result.result = true; + } + } else if (!ReflectionFactory.ECLIPSE.equals(result.compilerName)) { + result.failureReason = "unrecognized compiler: " + result.compilerName; + } else { + badOptions = LangUtil.selectOptions(argList, Spec.INVALID_ECLIPSE_OPTIONS); + if (!LangUtil.isEmpty(badOptions)) { + result.failureReason = "no support in eclipse-based compiler" + + " for (normally-valid) options "+ Arrays.asList(badOptions); + } else { + result.result = true; + } + } + if (result.result) { + result.commandOptions = argList; + } + return result; + } + + /** @return true if javac is available on the classpath */ + private boolean haveJavac() { // XXX copy/paste from JavaCWrapper.java + Class compilerClass = null; + try { + compilerClass = Class.forName("com.sun.tools.javac.Main"); + } catch (ClassNotFoundException ce1) { + try { + compilerClass = Class.forName("sun.tools.javac.Main"); + } catch (ClassNotFoundException ce2) { + } + } + return (null != compilerClass); + } + + + /** + * Handle flags that are interpreted by the test rather than the + * underlying command. These flags are to be removed from the + * arg list. + * @return true if this is a flag to remove from the arg list + */ + protected boolean testFlag(String arg, TestSetup result) { + if ("-eclipse".equals(arg) || "!eclipse".equals(arg) || "^ajc".equals(arg)) { + result.compilerName = ReflectionFactory.ECLIPSE; + return true; + } else if ("-ajc".equals(arg) || "!ajc".equals(arg) || "^eclipse".equals(arg)) { + result.compilerName = ReflectionFactory.OLD_AJC; + return true; + } else if ("-ignoreWarnings".equals(arg)) { + result.ignoreWarnings = true; + result.ignoreWarningsSet = true; + return true; + } + return false; + } + + // XXX need keys, cache... + /** @return index of global in argList, ignoring first char */ + protected int indexOf(String global, ArrayList argList) { + int max = argList.size(); + for (int i = 0; i < max; i++) { + if (global.equals(((String) argList.get(i)).substring(1))) { + return i; + } + } + return -1; + } + + /** + * Write this out as a compile element as defined in + * AjcSpecXmlReader.DOCTYPE. + * @see AjcSpecXmlReader#DOCTYPE + * @see IXmlWritable#writeXml(XMLWriter) + */ + public void writeXml(XMLWriter out) { + // If our state is empty, we just delegate to super. + if (DEFAULT_COMPILER.equals(compiler) + && LangUtil.isEmpty(testSrcDirOffset) + && LangUtil.isEmpty(argfiles)) { + super.writeXml(out); + return; + } + String attr = ""; + out.startElement(xmlElementName, false); + if (!DEFAULT_COMPILER.equals(compiler)) { + out.printAttribute("compiler", compiler); + } + if (!LangUtil.isEmpty(testSrcDirOffset)) { + out.printAttribute("dir", testSrcDirOffset); + } + if (!LangUtil.isEmpty(argfiles)) { + out.printAttribute("argfiles", out.flattenFiles(argfiles)); + } + super.writeAttributes(out); + out.endAttributes(); + if (!LangUtil.isEmpty(dirChanges)) { + DirChanges.Spec.writeXml(out, dirChanges); + } + List messages = getMessages(); + if (!LangUtil.isEmpty(messages)) { + SoftMessage.writeXml(out, messages); + } + out.endElement(xmlElementName); + } + + /** + * Encapsulate the directives that can be set using + * global arguments supplied in {@link Spec.getOptions()}. + * This supports changing the compiler and ignoring warnings. + */ + class TestSetup { + /** null unless overriding the compiler to be used */ + String compilerName; + /** + * true if we should tell AjcMessageHandler whether + * to ignore warnings in its result evaluation + */ + boolean ignoreWarningsSet; + + /** if telling AjcMessageHandler, what we tell it */ + boolean ignoreWarnings; + + /** false if setup failed */ + boolean result; + + /** if setup failed, this has the reason why */ + String failureReason; + + /** beyond running test, also seek text in sources */ + String seek; + + /** if setup completed, this has the combined global/local options */ + ArrayList commandOptions; + + public String toString() { + return "TestSetup(" + + (null == compilerName ? "" : compilerName + " ") + + (!ignoreWarningsSet ? "" + : (ignoreWarnings ? "" : "do not ") + + "ignore warnings ") + + (result ? "" : "setup failed") + + ")"; + } + } + } // CompilerRun.Spec +} // CompilerRun + +// /** +// * Write this out as a compile element as defined in +// * AjcSpecXmlReader.DOCTYPE. +// * @see AjcSpecXmlReader#DOCTYPE +// * @see IXmlWritable#writeXml(XMLWriter) +// */ +// public void writeXml(XMLWriter out) { +// StringBuffer sb = new StringBuffer(); +// Spec spec = this; +// final String elementName = "compile"; +// List list = spec.getOptionsList(); +// String args = XMLWriter.flattenList(spec.getOptionsList()).trim(); +// String argsAttr = out.makeAttribute("options", args).trim(); +// String files = XMLWriter.flattenFiles(spec.getPathsArray()).trim(); +// String filesAttr = out.makeAttribute("files", files).trim(); +// List messages = spec.getMessages(); +// int nMessages = messages.size(); +// int both = argsAttr.length() + filesAttr.length(); +// final int MAX = 55; +// +// // tortured logic to make more readable XML... +// if ((both < MAX) || (0 == args.length())) { +// // if short enough, print entire or just start +// if (0 != args.length()) { +// filesAttr = argsAttr + " " + filesAttr; +// } +// if (0 == nMessages) { +// out.printElement(elementName, filesAttr); +// return; +// } else { +// out.startElement(elementName, filesAttr, true); +// } +// } else if (argsAttr.length() < filesAttr.length()) { +// out.startElement(elementName, argsAttr, false); +// out.printAttribute("files", files); +// out.endAttributes(); +// } else { +// out.startElement(elementName, filesAttr, false); +// out.printAttribute("options", args); +// out.endAttributes(); +// } +// if (0 < nMessages) { +// SoftMessage.writeXml(out, messages); +// } +// out.endElement(elementName); +// } + diff --git a/testing/src/org/aspectj/testing/harness/bridge/DirChanges.java b/testing/src/org/aspectj/testing/harness/bridge/DirChanges.java new file mode 100644 index 000000000..73b930d71 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/DirChanges.java @@ -0,0 +1,522 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.util.TestUtil; +import org.aspectj.testing.xml.IXmlWritable; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Calculate changes in a directory tree. + * Usage: + * <ul> + * <li>Set up with any expected changes and/or an expected directory</li> + * <li>Set up with any file checker</li> + * <li>start(..) before changes. + * This issues messages for any removed files not found, + * which represent an error in the expected changes.</li> + * <li>Do whatever operations will change the directory</li> + * <li>end(..). + * This issues messages for any files not removed, added, or updated, + * and, if any checker was set, any checker messages for matching + * added or updated files</li> + * </ul> + */ +public class DirChanges { + + private static final boolean EXISTS = true; + + final Spec spec; + + /** start time, in milliseconds - valid only from start(..)..end(..) */ + long startTime; + + /** base directory of actual files - valid only from start(..)..end(..) */ + File baseDir; + + /** if set, this is run against any resulting existing files */ + IFileChecker fileChecker; + + /** handler valid from start..end of start(..) and end(..) methods */ + IMessageHandler handler; + + /** + * Constructor for DirChanges. + */ + public DirChanges(Spec spec) { + LangUtil.throwIaxIfNull(spec, "spec"); + this.spec = spec; + } + + /** + * Inspect the base dir, and issue any messages for + * removed files not present. + * @param baseDir File for a readable directory + * @return true if this started without sending messages + * for removed files not present. + */ + public boolean start(IMessageHandler handler, File baseDir) { + FileUtil.throwIaxUnlessCanReadDir(baseDir, "baseDir"); + final IMessageHandler oldHandler = this.handler; + this.handler = handler; + this.baseDir = baseDir; + startTime = 0l; + final boolean doCompare = false; + boolean result + = exists("at start, did not expect file to be added", !EXISTS, spec.added, doCompare); + result &= exists("at start, expected file to be unchanged", EXISTS, spec.unchanged, doCompare); + result &= exists("at start, expected file to be updated", EXISTS, spec.updated, doCompare); + result &= exists("at start, expected file to be removed", EXISTS, spec.removed, doCompare); + startTime = System.currentTimeMillis(); + this.handler = oldHandler; + return result; + } + + + /** + * Inspect the base dir, issue any messages for + * files not added, files not updated, and files not removed, + * and compare expected/actual files added or updated. + * @throws IllegalStateException if called before start(..) + */ + public boolean end(IMessageHandler handler, File srcBaseDir) { + FileUtil.throwIaxUnlessCanReadDir(baseDir, "baseDir"); + if (0l == startTime) { + throw new IllegalStateException("called before start"); + } + final IMessageHandler oldHandler = this.handler; + this.handler = handler; + final boolean doCompare = (null != fileChecker); + final boolean fastFail = spec.fastFail; + boolean result + = exists("at end, expected file was not added", EXISTS, spec.added, doCompare); + if (result || !fastFail) { + result &= exists("at end, expected file was not unchanged", EXISTS, spec.unchanged, doCompare, false); + } + if (result || !fastFail) { + result &= exists("at end, expected file was not updated", EXISTS, spec.updated, doCompare); + } + if (result || !fastFail) { + result &= exists("at end, file exists, was not removed", !EXISTS, spec.removed, doCompare); + } + if (result || !fastFail) { + result &= compareDir(srcBaseDir); + } + // XXX validate that unchanged mod-time did not change + + this.handler = oldHandler; + baseDir = null; + startTime = 0l; + return result; + } + + /** + * Verify that all files in any specified expected directory + * have matching files in the base directory, putting any messages + * in the handler (only one if the spec indicates fast-fail). + * @param srcBaseDir the File for the base directory of the test sources + * (any expected dir is specified relative to this directory) + * @return true if the same, false otherwise + */ + private boolean compareDir(File srcBaseDir) { + if (null == spec.dirExpected) { + return true; + } + File expDir = new File(srcBaseDir, spec.dirExpected); + File actDir = baseDir; + return TestUtil.sameDirectoryContents(handler, expDir, actDir, spec.fastFail); + } + + + /** @param comp FileMessageComparer (if any) given matching messages to compare */ + protected void setFileComparer(IFileChecker comp) { + this.fileChecker = comp; + } + + + + /** + * Signal fail if any files do {not} exist or do {not} have last-mod-time after startTime + * @param handler the IMessageHandler sink for messages + * @param label the String infix for the fail message + * @param exists if true, then file must exist and be modified after start time; + * if false, then the file must not exist or must be modified before start time. + * @param pathList the List of path (without any Spec.defaultSuffix) of File + * in Spec.baseDir to find (or not, if !exists) + */ + protected boolean exists( + String label, + boolean exists, + List pathList, + boolean doCompare) { + boolean expectStartEarlier = true; + return exists(label, exists, pathList,doCompare, true); + } + protected boolean exists( + String label, + boolean exists, + List pathList, + boolean doCompare, + boolean expectStartEarlier) { + boolean result = true; + if (!LangUtil.isEmpty(pathList)) { + final File expDir = ((!doCompare || (null == spec.dirExpected)) + ? null + : new File(baseDir, spec.dirExpected)); + for (Iterator iter = pathList.iterator(); iter.hasNext();) { + final String entry = (String) iter.next() ; + String path = entry ; + if (null != spec.defaultSuffix) { + if (".class".equals(spec.defaultSuffix)) { + path = path.replace('.', '/'); + } + path = path + spec.defaultSuffix; + } + File actualFile = new File(baseDir, path); + if (exists != (actualFile.canRead() && actualFile.isFile() + && (expectStartEarlier + ? startTime <= actualFile.lastModified() + : startTime > actualFile.lastModified() + ))) { + failMessage(handler, exists, label, path, actualFile); + if (result) { + result = false; + } + } else if (exists && doCompare && (null != fileChecker)) { + if (!fileChecker.checkFile(handler, path, actualFile) && result) { + result = false; + } + } + } + } + return result; + } + + /** + * Generate fail message "{un}expected {label} file {path} in {baseDir}". + * @param handler the IMessageHandler sink + * @param label String message infix + * @param path the path to the file + */ + protected void failMessage( + IMessageHandler handler, + boolean exists, + String label, + String path, + File file) { + MessageUtil.fail(handler, label + " \"" + path + "\" in " + baseDir); + } + + + /** Check actual File found at a path, usually to diff expected/actual contents */ + public static interface IFileChecker { + /** + * Check file found at path. + * Implementations should return false when adding fail (or worse) + * message to the handler, and true otherwise. + * @param handler IMessageHandler sink for messages, esp. fail. + * @param path String for normalized path portion of actualFile.getPath() + * @param actualFile File to file found + * @return false if check failed and messages added to handler + */ + boolean checkFile(IMessageHandler handler, String path, File actualFile); + } + +// /** +// * Default FileChecker compares files literally, transforming any +// * with registered normalizers. +// */ +// public static class FileChecker implements IFileChecker { +// final File baseExpectedDir; +// NormalizedCompareFiles fileComparer; +// +// public FileChecker(File baseExpectedDir) { +// this.baseExpectedDir = baseExpectedDir; +// fileComparer = new NormalizedCompareFiles(); +// } +// public boolean checkFile(IMessageHandler handler, String path, File actualFile) { +// if (null == baseExpectedDir) { +// MessageUtil.error(handler, "null baseExpectedDir set on construction"); +// } else if (!baseExpectedDir.canRead() || !baseExpectedDir.isDirectory()) { +// MessageUtil.error(handler, "bad baseExpectedDir: " + baseExpectedDir); +// } else { +// File expectedFile = new File(baseExpectedDir, path); +// if (!expectedFile.canRead()) { +// MessageUtil.fail(handler, "cannot read expected file: " + expectedFile); +// } else { +// return doCheckFile(handler, expectedFile, actualFile, path); +// } +// } +// return false; +// } +// +// protected boolean doCheckFile( +// IMessageHandler handler, +// File expectedFile, +// File actualFile, +// String path) { +// fileComparer.setHandler(handler); +// FileLine[] expected = fileComparer.diff(); +// return false; +// } +// } + +// /** +// * CompareFiles implementation that pre-processes input +// * to normalize it. Currently it reads all files except +// * .class files, which it disassembles first. +// */ +// public static class NormalizedCompareFiles extends CompareFiles { +// private final static String[] NO_PATHS = new String[0]; +// private static String normalPath(File file) { // XXX util +// if (null == file) { +// return ""; +// } +// return file.getAbsolutePath().replace('\\', '/'); +// } +// +// private String[] baseDirs; +// private IMessageHandler handler; +// +// public NormalizedCompareFiles() { +// } +// +// void init(IMessageHandler handler, File[] baseDirs) { +// this.handler = handler; +// if (null == baseDirs) { +// this.baseDirs = NO_PATHS; +// } else { +// this.baseDirs = new String[baseDirs.length]; +// for (int i = 0; i < baseDirs.length; i++) { +// this.baseDirs[i] = normalPath(baseDirs[i]) + "/"; +// } +// } +// } +// +// private String getClassName(File file) { +// String result = null; +// String path = normalPath(file); +// if (!path.endsWith(".class")) { +// MessageUtil.error(handler, +// "NormalizedCompareFiles expected " +// + file +// + " to end with .class"); +// } else { +// path = path.substring(0, path.length()-6); +// for (int i = 0; i < baseDirs.length; i++) { +// if (path.startsWith(baseDirs[i])) { +// return path.substring(baseDirs[i].length()).replace('/', '.'); +// } +// } +// MessageUtil.error(handler, +// "NormalizedCompareFiles expected " +// + file +// + " to start with one of " +// + LangUtil.arrayAsList(baseDirs)); +// } +// +// return result; +// } +// +// /** +// * Read file as normalized lines, sending handler any messages +// * ERROR for input failures and FAIL for processing failures. +// * @return NOLINES on error or normalized lines from file otherwise +// */ +// public FileLine[] getFileLines(File file) { +// FileLineator capture = new FileLineator(); +// InputStream in = null; +// try { +// if (!file.getPath().endsWith(".class")) { +// in = new FileInputStream(file); +// FileUtil.copyStream( +// new BufferedReader(new InputStreamReader(in)), +// new PrintWriter(capture)); +// } else { +// String name = getClassName(file); +// if (null == name) { +// return new FileLine[0]; +// } +// String path = normalPath(file); +// path = path.substring(0, path.length()-name.length()); +// // XXX sole dependency on bcweaver/bcel +// LazyClassGen.disassemble(path, name, capture); +// } +// } catch (IOException e) { +// MessageUtil.fail(handler, +// "NormalizedCompareFiles IOException reading " + file, e); +// return null; +// } finally { +// if (null != in) { +// try { in.close(); } +// catch (IOException e) {} // ignore +// } +// capture.flush(); +// capture.close(); +// } +// String missed = capture.getMissed(); +// if (!LangUtil.isEmpty(missed)) { +// MessageUtil.fail(handler, +// "NormalizedCompareFiles missed input: " +// + missed); +// return null; +// } else { +// return capture.getFileLines(); +// } +// } +// +// +// } + + /** + * Specification for a set of File added, removed, or updated + * in a given directory, or for a directory base for a tree of expected files. + * If defaultSuffix is specified, entries may be added without it. + */ + public static class Spec implements IXmlWritable { + public static final String XMLNAME = "dir-changes"; + String dirToken; + String defaultSuffix; + String dirExpected; + /** if true, ok to fail on first mis-match */ + boolean fastFail; + final ArrayList added; + final ArrayList removed; + final ArrayList updated; + final ArrayList unchanged; + + /** + * @param dirToken the symbol name of the base directory + * @param clipSuffix the String suffix, if any, to clip automatically + */ + public Spec() { + added = new ArrayList(); + removed = new ArrayList(); + updated = new ArrayList(); + unchanged = new ArrayList(); + } + + /** + * @param dirToken the symbol name of the base directory (classes, run) + */ + public void setDirToken(String dirToken) { + this.dirToken = dirToken; + } + + /** + * Set the directory containing the expected files. + * Currently this only is used to verify files that are expected + * and found after the process completes; it does not stand on its + * own as a specification of files. + * @param expectedDirRelativePath path relative to the test base + * of the directory containing expected results for the output dir. + */ + public void setExpDir(String expectedDirRelativePath) { + } + + /** + * @param clipSuffix the String suffix, if any, to clip automatically + */ + public void setDefaultSuffix(String defaultSuffix) { + this.defaultSuffix = defaultSuffix; + } + + public void setAdded(String items) { + XMLWriter.addFlattenedItems(added, items); + } + + public void setRemoved(String items) { + XMLWriter.addFlattenedItems(removed, items); + } + + public void setUpdated(String items) { + XMLWriter.addFlattenedItems(updated, items); + } + + public void setUnchanged(String items) { + XMLWriter.addFlattenedItems(unchanged, items); + } + public void setFastfail(boolean fastFail) { + this.fastFail = fastFail; // XXX support in XML + } + + /** @return true if some list was specified */ + private boolean hasFileList() { + return (!LangUtil.isEmpty(added) + || !LangUtil.isEmpty(removed) + || !LangUtil.isEmpty(updated) + || !LangUtil.isEmpty(unchanged) + ); + } + + /** this writes nothing if there are no added, removed, or changed */ + public void writeXml(XMLWriter out) { + if (!hasFileList()) { + return; + } + String attr = ""; + // XXX need to permit defaults here... + if (null != dirToken) { + attr = out.makeAttribute("dirToken", dirToken) + " "; + } + if (null != defaultSuffix) { + attr += out.makeAttribute("defaultSuffix", defaultSuffix); + } + out.startElement(XMLNAME, attr, false); + if (!LangUtil.isEmpty(added)) { + out.printAttribute("added", XMLWriter.flattenList(added)); + } + if (!LangUtil.isEmpty(removed)) { + out.printAttribute("removed", XMLWriter.flattenList(removed)); + } + if (!LangUtil.isEmpty(updated)) { + out.printAttribute("updated", XMLWriter.flattenList(updated)); + } + if (!LangUtil.isEmpty(unchanged)) { + out.printAttribute("unchanged", XMLWriter.flattenList(unchanged)); + } + out.endElement(XMLNAME); + } + + /** + * Write list as elements to XMLWriter. + * @param out XMLWriter output sink + * @param dirChanges List of DirChanges.Spec to write + */ + public static void writeXml(XMLWriter out, List dirChanges) { + if (LangUtil.isEmpty(dirChanges)) { + return; + } + LangUtil.throwIaxIfNull(out, "out"); + for (Iterator iter = dirChanges.iterator(); iter.hasNext();) { + DirChanges.Spec spec = (DirChanges.Spec) iter.next(); + if (null == spec) { + continue; + } + spec.writeXml(out); + } + } + +} // class Spec + +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java b/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java new file mode 100644 index 000000000..dbf5dd259 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/FlatSuiteReader.java @@ -0,0 +1,377 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.SourceLocation; +import org.aspectj.bridge.IMessage.Kind; +import org.aspectj.testing.util.BridgeUtil; +import org.aspectj.testing.util.ObjectChecker; +import org.aspectj.testing.util.SFileReader; +import org.aspectj.testing.util.StandardObjectChecker; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; +import org.aspectj.util.LineReader; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * SFileReader.Maker implementation to read tests + * XXX supports iterative but not yet incremental compiles + */ +public class FlatSuiteReader implements SFileReader.Maker { + public static final String[] RA_String = new String[0]; + public static final FlatSuiteReader ME = new FlatSuiteReader(); + private static final SFileReader READER = new SFileReader(ME); + + static boolean isNumber(String s) { // XXX costly + if ((null == s) || (0 == s.length())) { + return false; + } + try { + Integer.valueOf(s); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** if true, clean up records before returning from make */ + public boolean clean; + + private FlatSuiteReader() { + } + + /** + * @see org.aspectj.testing.harness.bridge.SFileReader.Maker#getType() + */ + public Class getType() { + return AjcTest.Spec.class; + } + + /** + * This constructs an AjcTest.Spec assuming we are at the start of a + * test definition in reader and taking the parent directory of + * the reader as the base directory for the test suite root. + * @return the next AjcTest in reader, or null + * @see org.aspectj.testing.harness.bridge.SFileReader.Maker#make(LineReader) + */ + public Object make(final LineReader reader) + throws AbortException, IOException { + final AjcTest.Spec result = new AjcTest.Spec(); + boolean usingEclipse = false; // XXX + /** handle read errors by throwing AbortException with context info */ + class R { + public String read(String context) throws IOException { + return read(context, true); + } + public String read(String context, boolean required) + throws IOException { + final boolean skipEmpties = false; + String result = reader.nextLine(skipEmpties); + if ((null != result) && (0 == result.length())) { + result = null; + } + if ((null == result) && required) { + String s = "expecting " + context + " at " + reader; + throw new AbortException(s); + } + return result; + } + } + + final R r = new R(); + //final String baseDir = reader.getFile().getParent(); + String line; + String[] words; + boolean isRequired = true; + + final int startLine = reader.getLineNumber() - 1; + + // description first - get from last line read + // XXX permits exactly one blank line between test records? + result.description = reader.lastLine(); + if (null == result.description) { + throw new AbortException("expecting description at " + reader); + } + + // next line is baseDir {option..} + line = r.read("baseDir {option..}"); + words = LangUtil.split(line); + if ((null == words) || (0 == words.length)) { + throw new AbortException( + "expecting dir {option..} at " + reader); + } + // XXX per-test (shared) root + //final File sourceRoot = new File(baseDir, words[0]); + result.setTestDirOffset(words[0]); + + String[] compileOptions = new String[words.length - 1]; + System.arraycopy(words, 1, compileOptions, 0, words.length - 1); + + // next are 1..n source lines: source... + CompilerRun.Spec lastCompileSpec = null; + // save last source file as default for error/warning line + File lastFile = null; // XXX per-compiler-run errors + while (null != (line = r.read("source.."))) { + words = LangUtil.split(line); + if (0 == FileUtil.sourceSuffixLength(words[0])) { // XXX + break; + } else { + lastCompileSpec = new CompilerRun.Spec(); + lastCompileSpec.testSrcDirOffset = null; + // srcs are in test base for old + lastCompileSpec.addOptions(compileOptions); + lastCompileSpec.addPaths(words); + lastFile = new File(words[words.length - 1]); + result.addChild(lastCompileSpec); + } + } + if (null == lastCompileSpec) { + throw new AbortException("expected sources at " + reader); + } + + ArrayList exp = new ArrayList(); + // !compile || noerrors || className {runOption..} + String first = words[0]; + if ("!compile".equals(first)) { + //result.className = words[0]; + //result.runOptions = new String[words.length-1]; + //System.arraycopy(words, 0, result.runOptions, 0, words.length-1); + } else if ("noerrors".equals(first)) { + // className is null, but no errors expected + // so compile succeeds but run not attempted + //result.errors = Main.RA_ErrorLine; + // result.runOptions = Main.RA_String; + } else if (isNumber(first) || (-1 != first.indexOf(":"))) { + exp.addAll(makeMessages(IMessage.ERROR, words, 0, lastFile)); + } else { + String[] args = new String[words.length - 1]; + System.arraycopy(words, 0, args, 0, args.length); + JavaRun.Spec spec = new JavaRun.Spec(); + spec.className = first; + spec.addOptions(args); + //XXXrun.runDir = sourceRoot; + result.addChild(spec); + } + + // optional: warnings, eclipse.warnings, eclipse.errors + // XXX unable to specify error in eclipse but not ajc + boolean gotErrors = false; + while (null + != (line = + r.read( + " errors, warnings, eclipse.warnings, eclipse.error", + false))) { + words = LangUtil.split(line); + first = words[0]; + if ("eclipse.warnings:".equals(first)) { + if (usingEclipse) { + exp.addAll( + makeMessages( + IMessage.WARNING, + words, + 0, + lastFile)); + } + } else if ("eclipse.errors:".equals(first)) { + if (usingEclipse) { + exp.addAll( + makeMessages(IMessage.ERROR, words, 0, lastFile)); + } + } else if ("warnings:".equals(first)) { + exp.addAll( + makeMessages(IMessage.WARNING, words, 0, lastFile)); + } else if (gotErrors) { + exp.addAll( + makeMessages(IMessage.WARNING, words, 0, lastFile)); + } else { + exp.addAll( + makeMessages(IMessage.ERROR, words, 0, lastFile)); + gotErrors = true; + } + } + lastCompileSpec.addMessages(exp); + + int endLine = reader.getLineNumber(); + File sourceFile = reader.getFile(); + ISourceLocation sl = + new SourceLocation(sourceFile, startLine, endLine, 0); + result.setSourceLocation(sl); + + if (clean) { + cleanup(result, reader); + } + return result; + } + + /** post-process result + * - use file name as keyword + * - clip / for dir offsets + * - extract purejava keyword variants + * - extract bugID + * - convert test options to force-options + * - detect illegal xml characters + */ + private void cleanup(AjcTest.Spec result, LineReader lineReader) { + LangUtil.throwIaxIfNull(result, "result"); + LangUtil.throwIaxIfNull(lineReader, "lineReader"); + + File suiteFile = lineReader.getFile(); + String name = suiteFile.getName(); + if (!name.endsWith(".txt")) { + throw new Error("unexpected name: " + name); + } + result.addKeyword("from-" + name.substring(0,name.length()-4)); + + final String dir = result.testDirOffset; + if (dir.endsWith("/")) { + result.testDirOffset = dir.substring(0,dir.length()-1); + } + + StringBuffer description = new StringBuffer(result.description); + if (strip(description, "PUREJAVA")) { + result.addKeyword("purejava"); + } + if (strip(description, "PUREJAVE")) { + result.addKeyword("purejava"); + } + if (strip(description, "[purejava]")) { + result.addKeyword("purejava"); + } + String input = description.toString(); + int loc = input.indexOf("PR#"); + if (-1 != loc) { + String prefix = input.substring(0, loc).trim(); + String pr = input.substring(loc+3, loc+6).trim(); + String suffix = input.substring(loc+6).trim(); + description.setLength(0); + description.append((prefix + " " + suffix).trim()); + try { + result.setBugId(Integer.valueOf(pr).intValue()); + } catch (NumberFormatException e) { + throw new Error("unable to convert " + pr + " for " + result + + " at " + lineReader); + } + } + input = description.toString(); + String error = null; + if (-1 != input.indexOf("&")) { + error = "char &"; + } else if (-1 != input.indexOf("<")) { + error = "char <"; + } else if (-1 != input.indexOf(">")) { + error = "char >"; + } else if (-1 != input.indexOf("\"")) { + error = "char \""; + } + if (null != error) { + throw new Error(error + " in " + input + " at " + lineReader); + } + result.description = input; + + ArrayList newOptions = new ArrayList(); + ArrayList optionsCopy = result.getOptionsList(); + for (Iterator iter = optionsCopy.iterator(); iter.hasNext();) { + String option = (String) iter.next(); + if (option.startsWith("-")) { + newOptions.add("!" + option.substring(1)); + } else { + throw new Error("non-flag option? " + option); + } + } + result.setOptionsArray((String[]) newOptions.toArray(new String[0])); + } + + private boolean strip(StringBuffer sb, String infix) { + String input = sb.toString(); + int loc = input.indexOf(infix); + if (-1 != loc) { + String prefix = input.substring(0, loc); + String suffix = input.substring(loc+infix.length()); + input = (prefix.trim() + " " + suffix.trim()).trim(); + sb.setLength(0); + sb.append(input); + return true; + } + return false; + } + + /** + * Generate list of expected messages of this kind. + * @param kind any non-null kind, but s.b. IMessage.WARNING or ERROR + * @param words + * @param start index in words where to start + * @param lastFile default file for source location if the input does not specify + * @return List + */ + private List makeMessages(// XXX weak - also support expected exceptions, etc. + Kind kind, String[] words, int start, File lastFile) { + ArrayList result = new ArrayList(); + for (int i = start; i < words.length; i++) { + ISourceLocation sl = + BridgeUtil.makeSourceLocation(words[i], lastFile); + if (null == sl) { // XXX signalling during make + // System.err.println(...); + //MessageUtil.debug(handler, "not a source location: " + words[i]); + } else { + String text = + (("" + sl.getLine()).equals(words[i]) ? "" : words[i]); + result.add(new Message(text, kind, null, sl)); + } + } + return (0 == result.size() ? Collections.EMPTY_LIST : result); + } + + /** + * Read suite spec from a flat .txt file. + * @throws AbortException on failure + * @return AjcTest.Suite.Spec with any AjcTest.Spec as children + */ + public AjcTest.Suite.Spec readSuite(File suiteFile) { + LangUtil.throwIaxIfNull(suiteFile, "suiteFile"); + if (!suiteFile.isAbsolute()) { + suiteFile = suiteFile.getAbsoluteFile(); + } + final AjcTest.Suite.Spec result = new AjcTest.Suite.Spec(); + result.setSuiteDirFile(suiteFile.getParentFile()); + ObjectChecker collector = new StandardObjectChecker(IRunSpec.class) { + public boolean doIsValid(Object o) { + result.addChild((IRunSpec) o); + return true; + } + }; + boolean abortOnError = true; + try { + READER.readNodes( + suiteFile, + collector, + abortOnError, + System.err); + } catch (IOException e) { + IMessage m = Message.fail("reading " + suiteFile, e); + throw new AbortException(m); + } + + return result; + } +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/Globals.java b/testing/src/org/aspectj/testing/harness/bridge/Globals.java new file mode 100644 index 000000000..7c4f524a5 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/Globals.java @@ -0,0 +1,94 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + + package org.aspectj.testing.harness.bridge; + +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; + +/** + */ +public class Globals { + /** name/key of the System property to set to define library dir */ + public static final String LIBDIR_NAME = "harness.libdir"; + + /** name/key of the System property to set to define J2SE_HOME dir */ + public static final String J2SE14_RTJAR_NAME = "j2se14.rtjar"; + + /** assumed relative location of a library with required jars */ + public static final String LIBDIR = getSystemProperty(LIBDIR_NAME, "../lib/test"); + + /** Path to J2SE_HOME */ + public static final File J2SE14_RTJAR; + + /** array of parameter types for main(String[]) */ + public static final Class[] MAIN_PARM_TYPES = new Class[] {String[].class}; + public static final String S_testingclient_jar = LIBDIR + "/testing-client.jar"; + public static final String S_aspectjrt_jar = LIBDIR + "/aspectjrt.jar"; + public static final String S_bridge_jar = LIBDIR + "/bridge.jar"; + public static final File F_testingclient_jar = new File(S_testingclient_jar); + public static final File F_aspectjrt_jar = new File(S_aspectjrt_jar); + public static final File F_bridge_jar = new File(S_bridge_jar); + public static final boolean globalsValid; + + static { + File j2seJar = null; + String path = getSystemProperty(J2SE14_RTJAR_NAME, "c:/home/apps/jdk14"); + File j2seHome = new File(path); + if (j2seHome.exists() && j2seHome.isDirectory()) { + File rtjar = new File(j2seHome.getAbsolutePath() + "/jre/lib/rt.jar"); + if (rtjar.canRead() && rtjar.isFile()) { + j2seJar = rtjar; + } + } + J2SE14_RTJAR = j2seJar; + globalsValid = + (FileUtil.canReadFile(F_testingclient_jar) + && FileUtil.canReadFile(F_aspectjrt_jar) + && FileUtil.canReadFile(F_bridge_jar) + && FileUtil.canReadFile(J2SE14_RTJAR) + ); + } + + /** + * + * @return null if not found, or + * String with class path for compiler to load J2SE 1.4 classes from. + */ + public static String get14Bootclasspath() { + return null; + } + + /** + * Get System property completely safely. + * @param propertyName the String name of the property to get + * @param defaultValue the String default value to return value is null or empty + * @return String value or defaultValue if not available. + */ + private static String getSystemProperty( + String propertyName, + String defaultValue) { + String result = defaultValue; + try { + String value = System.getProperty(propertyName); + if (!LangUtil.isEmpty(value)) { + result = value; + } + } catch (Throwable t) {} + return result; + } + +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java b/testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java new file mode 100644 index 000000000..061fde4d1 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/IAjcRun.java @@ -0,0 +1,44 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.testing.run.IRun; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.xml.XMLWriter; + +// XXX candidate to be subsumed in class/constructors, since inner spec does setup +// at the same time it constructs the run. +public interface IAjcRun extends IRun { + boolean setupAjcRun(Sandbox sandbox, Validator validator); + // XXX add for result eval? ArrayList getExpectedMessages(); + + /** this IAjcRun does nothing, returning true always */ + public static final IAjcRun NULLRUN = new IAjcRun() { + public boolean setupAjcRun(Sandbox sandbox, Validator validator) { + return true; + } + public boolean run(IRunStatus status) { + if (!status.started()) { + status.start(); + } + status.finish(IRunStatus.PASS); + return true; + } + + public void writeXml(XMLWriter out) { + throw new UnsupportedOperationException("unimplemented"); + } + public String toString() { return "IAjcRun.NULLRUN"; } + }; + +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java b/testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java new file mode 100644 index 000000000..7cb00765b --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/IRunSpec.java @@ -0,0 +1,24 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.xml.IXmlWritable; + +/** + * A run spec can make a run iterator and write itself as XML. + */ +public interface IRunSpec extends IXmlWritable { + IRunIterator makeRunIterator(Sandbox sandbox, Validator validator); +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java b/testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java new file mode 100644 index 000000000..efdd089fe --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/IncCompilerRun.java @@ -0,0 +1,444 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.ICommand; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.WrappedRunIterator; +import org.aspectj.testing.xml.SoftMessage; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * An incremental compiler run takes an existing compiler commmand + * from the sandbox, updates the staging directory, and recompiles. + * The staging directory is updated by prefix/suffix rules applied + * to files found below Sandbox.testBaseSrcDir. + * Files with suffix .{tag}.java are owned by this run + * and are copied to the staging directory + * unless they are prefixed "delete.", in which case the + * corresponding file is deleted. Any "owned" file is passed to + * the compiler as the list of changed files. + * The files entry contains the expected files recompiled. XXX underinclusive + * XXX prefer messages for expected files? + * XXX later: also support specified paths, etc. + */ +public class IncCompilerRun implements IAjcRun { + + final Spec spec; // nonfinal later to make re-runnable + Sandbox sandbox; + + /** + * @param handler must not be null, but may be reused in the same thread + */ + public IncCompilerRun(Spec spec) { + LangUtil.throwIaxIfNull(spec, "spec"); + this.spec = spec; + } + + /** + * Initialize this from the sandbox, using compiler and changedFiles. + * @param sandbox the Sandbox setup for this test, including copying + * any changed files, etc. + * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File) + * @throws AbortException containing IOException or IllegalArgumentException + * if the staging operations fail + */ + public boolean setupAjcRun(Sandbox sandbox, Validator validator) { + LangUtil.throwIaxIfNull(validator, "validator"); + if (!validator.nullcheck(sandbox, "sandbox") + || !validator.nullcheck(spec, "spec") + || !validator.nullcheck(spec.tag, "fileSuffix")) { + return false; + } + File srcDir = sandbox.getTestBaseSrcDir(this); + File destDir = sandbox.stagingDir; + if (!validator.canReadDir(srcDir, "testBaseSrcDir") + || !validator.canReadDir(destDir, "stagingDir")) { + return false; + } + + this.sandbox = sandbox; + return doStaging(validator); + } + + /** + * Handle copying and deleting of files per tag. + * This returns false unless some file was copied or deleted successfully + * and there were no failures copying or deleting files. + * @return true if staging completed successfully + */ + boolean doStaging(final Validator validator) { + boolean result = false; + try { + //ArrayList changed = new ArrayList(); + final String toSuffix = ".java"; + final String fromSuffix = "." + spec.tag + toSuffix; + // copy our tagged generation of files to the staging directory, + // deleting any with ChangedFilesCollector.DELETE_SUFFIX + class intHolder { + int numCopies; + int numDeletes; + int numFails; + } + final intHolder holder = new intHolder(); + FileFilter deleteOrCount = new FileFilter() { + final String clip = ".delete" + toSuffix; + /** do copy unless file should be deleted */ + public boolean accept(File file) { + boolean doCopy = true; + String path = file.getAbsolutePath(); + if (!path.endsWith(clip)) { + holder.numCopies++; + } else { + doCopy = false; + path = path.substring(0, path.length()-clip.length()) + toSuffix; + File toDelete = new File(path); + if (toDelete.delete()) { + holder.numDeletes++; + } else { + validator.fail("unable to delete file: " + path); + holder.numFails++; + } + } + return doCopy; + } + + }; + File srcDir = sandbox.getTestBaseSrcDir(this); + File destDir = sandbox.stagingDir; + FileUtil.copyDir(srcDir, destDir, fromSuffix, toSuffix, deleteOrCount); + if ((0 == holder.numCopies) && (0 == holder.numDeletes)) { + validator.fail("no files changed??"); + } else { + result = (0 == holder.numFails); + } + } catch (NullPointerException npe) { + validator.fail("staging - input", npe); + } catch (IOException e) { + validator.fail("staging - operations", e); + } + return result; + } + + /** + * @see org.aspectj.testing.run.IRun#run(IRunStatus) + */ + public boolean run(IRunStatus status) { + + ICommand compiler = sandbox.getCommand(this); + if (null == compiler) { + MessageUtil.abort(status, "null compiler"); + } + +// // This is a list of expected classes (in File-normal form +// // relative to base class/src dir, without .class suffix +// // -- like "org/aspectj/tools/ajc/Main") +// // A preliminary list is generated in doStaging. +// ArrayList expectedClasses = doStaging(status); +// if (null == expectedClasses) { +// return false; +// } +// +// // now add any (additional) expected-class entries listed in the spec +// // normalize to a similar file path (and do info messages for redundancies). +// +// List alsoChanged = spec.getPathsAsFile(sandbox.stagingDir); +// for (Iterator iter = alsoChanged.iterator(); iter.hasNext();) { +// File f = (File) iter.next(); +// +// if (expectedClasses.contains(f)) { +// // XXX remove old comment changed.contains() works b/c getPathsAsFile producing both File +// // normalizes the paths, and File.equals(..) compares these lexically +// String s = "specification of changed file redundant with tagged file: "; +// MessageUtil.info(status, s + f); +// } else { +// expectedClasses.add(f); +// } +// } +// +// // now can create handler, use it for reporting +// List errors = spec.getMessages(IMessage.ERROR); +// List warnings = spec.getMessages(IMessage.WARNING); +// AjcMessageHandler handler = new AjcMessageHandler(errors, warnings, expectedClasses); + + // same DirChanges handling for JavaRun, CompilerRun, IncCompilerRun + // XXX around advice or template method/class + DirChanges dirChanges = null; + if (!LangUtil.isEmpty(spec.dirChanges)) { + LangUtil.throwIaxIfFalse(1 == spec.dirChanges.size(), "expecting only 1 dirChanges"); + dirChanges = new DirChanges((DirChanges.Spec) spec.dirChanges.get(0)); + if (!dirChanges.start(status, sandbox.classesDir)) { + return false; // setup failed + } + } + List errors = spec.getMessages(IMessage.ERROR); + List warnings = spec.getMessages(IMessage.WARNING); + List expectRecompiled = Collections.EMPTY_LIST; + AjcMessageHandler handler = new AjcMessageHandler(errors, warnings, expectRecompiled); + boolean handlerResult = false; + boolean commandResult = false; + boolean result = false; + boolean report = false; + try { + handler.init(); + final long startTime = System.currentTimeMillis(); + commandResult = compiler.repeatCommand(handler); + // XXX disabled LangUtil.throwIaxIfNotAllAssignable(actualRecompiled, File.class, "recompiled"); + report = true; + // handler does not verify sandbox... + handlerResult = handler.passed(); + if (!handlerResult) { + result = false; + } else { + result = (commandResult == handler.expectingCommandTrue()); + if (! result) { + String m = commandResult + ? "incremental compile command did not return false as expected" + : "incremental compile command returned false unexpectedly"; + MessageUtil.fail(status, m); + } else if (null != dirChanges) { + result = dirChanges.end(status, sandbox.testBaseDir); + } + } + } finally { + if (!result || spec.runtime.isVerbose()) { // more debugging context in case of failure + MessageUtil.info(handler, "spec: " + spec.toLongString()); + MessageUtil.info(handler, "sandbox: " + sandbox); + String[] classes = FileUtil.listFiles(sandbox.classesDir); + MessageUtil.info(handler, "sandbox.classes: " + Arrays.asList(classes)); + } + // XXX weak - actual messages not reported in real-time, no fast-fail + if (report) { + handler.report(status); + } + } + return result; + } + + private boolean hasFile(ArrayList changed, File f) { + return changed.contains(f); // d + } + + + public String toString() { + return "" + spec; + // return "IncCompilerRun(" + spec + ")"; // XXX + } + +// /** @see IXmlWritable#writeXml(XMLWriter) */ +// public void writeXml(XMLWriter out) { +// String elementName = "inc-compile"; +// String tagAttr = out.makeAttribute("tag", spec.tag); +// List messages = spec.getMessages(); +// int nMessages = messages.size(); +// if (0 == nMessages) { +// out.printElement(elementName, tagAttr); +// } else { +// out.startElement(elementName, tagAttr, true); +// SoftMessage.writeXml(out, messages); +// out.endElement(elementName); +// } +// } +// + /** + * initializer/factory for IncCompilerRun. + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "inc-compile"; + + protected ArrayList classesAdded; + protected ArrayList classesRemoved; + protected ArrayList classesUpdated; + + /** + * skip description, skip sourceLocation, + * do keywords, skip options, do paths as classes, do comment, + * do dirChanges, do messages but skip children. + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + "", "", null, "", "classes", null, false, false, true); + + /** identifies files this run owns, so {name}.{tag}.java maps to {name}.java */ + String tag; + + public Spec() { + super(XMLNAME); + setStaging(true); + classesAdded = new ArrayList(); + classesRemoved = new ArrayList(); + classesUpdated = new ArrayList(); + } + + public void setTag(String input) { + tag = input; + } + public String toString() { + return "IncCompile.Spec(" + tag + ", " + super.toString() + ")"; + } + + /** override to set dirToken to Sandbox.CLASSES and default suffix to ".class" */ + public void addDirChanges(DirChanges.Spec spec) { // XXX copy/paste of CompilerRun.Spec... + if (null == spec) { + return; + } + spec.setDirToken(Sandbox.CLASSES_DIR); + spec.setDefaultSuffix(".class"); + super.addDirChanges(spec); + } + + /** @return a IncCompilerRun with this as spec if setup completes successfully. */ + public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) { + IncCompilerRun run = new IncCompilerRun(this); + if (run.setupAjcRun(sandbox, validator)) { + // XXX need name + return new WrappedRunIterator(this, run); + } + return null; + } + + /** + * Write this out as a compile element as defined in + * AjcSpecXmlReader.DOCTYPE. + * @see AjcSpecXmlReader#DOCTYPE + * @see IXmlWritable#writeXml(XMLWriter) + */ + public void writeXml(XMLWriter out) { + String attr = out.makeAttribute("tag", tag); + out.startElement(xmlElementName, attr, false); + super.writeAttributes(out); + out.endAttributes(); + if (!LangUtil.isEmpty(dirChanges)) { + DirChanges.Spec.writeXml(out, dirChanges); + } + List messages = getMessages(); + if (0 < messages.size()) { + SoftMessage.writeXml(out, messages); + } + out.endElement(xmlElementName); + } + + public void setClassesAdded(String items) { + addItems(classesAdded, items); + } + + public void setClassesUpdated(String items) { + addItems(classesUpdated, items); + } + + public void setClassesRemoved(String items) { + addItems(classesRemoved, items); + } + + private void addItems(ArrayList list, String items) { + if (null != items) { + String[] classes = XMLWriter.unflattenList(items); + if (!LangUtil.isEmpty(classes)) { + for (int i = 0; i < classes.length; i++) { + if (!LangUtil.isEmpty(classes[i])) { + list.add(classes[i]); + } + } + } + } + } + } // class IncCompilerRun.Spec +} +// // XXX replaced with method-local class - revisit if useful +// +// /** +// * This class collects the list of all changed files and +// * deletes the corresponding file for those prefixed "delete." +// */ +// static class ChangedFilesCollector implements FileFilter { +// static final String DELETE_SUFFIX = ".delete.java"; +// static final String REPLACE_SUFFIX = ".java"; +// final ArrayList changed; +// final Validator validator; +// /** need this to generate paths by clipping */ +// final File destDir; +// +// /** @param changed the sink for all files changed (full paths) */ +// public ChangedFilesCollector(ArrayList changed, File destDir, Validator validator) { +// LangUtil.throwIaxIfNull(validator, "ChangedFilesCollector - handler"); +// this.changed = changed; +// this.validator = validator; +// this.destDir = destDir; +// } +// +// /** +// * This converts the input File to normal String path form +// * (without any source suffix) and adds it to the list changed. +// * If the name of the file is suffixed ".delete..", then +// * delete the corresponding file, and return false (no copy). +// * Return true otherwise (copy file). +// * @see java.io.FileFilter#accept(File) +// */ +// public boolean accept(File file) { +// final String aname = file.getAbsolutePath(); +// String name = file.getName(); +// boolean doCopy = true; +// boolean failed = false; +// if (name.endsWith(DELETE_SUFFIX)) { +// name = name.substring(0,name.length()-DELETE_SUFFIX.length()); +// file = file.getParentFile(); +// file = new File(file, name + REPLACE_SUFFIX); +// if (!file.canWrite()) { +// validator.fail("file to delete is not writable: " + file); +// failed = true; +// } else if (!file.delete()) { +// validator.fail("unable to delete file: " + file); +// failed = true; +// } +// doCopy = false; +// } +// if (!failed && doCopy) { +// int clip = FileUtil.sourceSuffixLength(file); +// if (-1 != clip) { +// name.substring(0, name.length()-clip); +// } +// if (null != destDir) { +// String path = destDir.getPath(); +// if (!LangUtil.isEmpty(path)) { +// // XXX incomplete +// if (name.startsWith(path)) { +// } else { +// int loc = name.lastIndexOf(path); +// if (-1 == loc) { // sigh +// +// } else { +// +// } +// } +// } +// } +// name = FileUtil.weakNormalize(name); +// changed.add(file); +// } +// return doCopy; +// } +// }; + diff --git a/testing/src/org/aspectj/testing/harness/bridge/JavaRun.java b/testing/src/org/aspectj/testing/harness/bridge/JavaRun.java new file mode 100644 index 000000000..5d7dd7010 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/JavaRun.java @@ -0,0 +1,298 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.Tester; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.WrappedRunIterator; +import org.aspectj.testing.util.TestClassLoader; +import org.aspectj.testing.xml.SoftMessage; +import org.aspectj.testing.xml.XMLWriter; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +/** + * Run a class in this VM using reflection. + */ +public class JavaRun implements IAjcRun { + Spec spec; + private Sandbox sandbox; + + /** programmatic initialization per spec */ + public JavaRun(Spec spec) { + this.spec = spec; + } + // XXX init(Spec) + + /** + * This checks the spec for a class name + * and checks the sandbox for a readable test source directory, + * a writable run dir, and (non-null, possibly-empty) lists + * of readable classpath dirs and jars. + * @return true if all checks pass + * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File) + */ + public boolean setupAjcRun(Sandbox sandbox, Validator validator) { + this.sandbox = sandbox; + return (validator.nullcheck(spec.className, "class name") + && validator.nullcheck(sandbox, "sandbox") + && validator.canReadDir(sandbox.getTestBaseSrcDir(this), "testBaseSrc dir") + && validator.canWriteDir(sandbox.runDir, "run dir") + && validator.canReadFiles(sandbox.getClasspathJars(true, this), "classpath jars") + && validator.canReadDirs(sandbox.getClasspathDirectories(true, this), "classpath dirs") + ); + + } + + /** caller must record any exceptions */ + public boolean run(IRunStatus status) + throws IllegalAccessException, + InvocationTargetException, + ClassNotFoundException, + NoSuchMethodException { + + boolean completedNormally = false; + Class targetClass = null; + if (!LangUtil.isEmpty(spec.dirChanges)) { + MessageUtil.info(status, "XXX dirChanges not implemented in JavaRun"); + } + try { + final boolean readable = true; + File[] libs = sandbox.getClasspathJars(readable, this); + URL[] urls = FileUtil.getFileURLs(libs); + File[] dirs = sandbox.getClasspathDirectories(readable, this); + ClassLoader loader = new TestClassLoader(urls, dirs); + // make the following load test optional + // Class testAspect = loader.loadClass("org.aspectj.lang.JoinPoint"); + targetClass = loader.loadClass(spec.className); + Method main = targetClass.getMethod("main", Globals.MAIN_PARM_TYPES); + setupTester(sandbox.getTestBaseSrcDir(this), loader, status); + main.invoke(null, new Object[] { spec.getOptionsArray() }); + completedNormally = true; + } catch (ClassNotFoundException e) { + String[] classes = FileUtil.listFiles(sandbox.classesDir); + MessageUtil.info(status, "sandbox.classes: " + Arrays.asList(classes)); + MessageUtil.fail(status, null, e); + } finally { + if (!completedNormally) { + MessageUtil.info(status, spec.toLongString()); + MessageUtil.info(status, "targetClass: " + targetClass); + MessageUtil.info(status, "" + sandbox); + } + } + return completedNormally; + } + + /** + * Clear (static) testing state and setup base directory, + * unless spec.skipTesting. + * @return null if successful, error message otherwise + */ + protected void setupTester(File baseDir, ClassLoader loader, IMessageHandler handler) { + if (null == loader) { + setupTester(baseDir, handler); + return; + } + File baseDirSet = null; + try { + if (!spec.skipTester) { + Class tc = loader.loadClass("org.aspectj.testing.Tester"); + // Tester.clear(); + Method m = tc.getMethod("clear", new Class[0]); + m.invoke(null, new Object[0]); + // Tester.setMessageHandler(handler); + m = tc.getMethod("setMessageHandler", new Class[] {IMessageHandler.class}); + m.invoke(null, new Object[] { handler}); + + //Tester.setBASEDIR(baseDir); + m = tc.getMethod("setBASEDIR", new Class[] {File.class}); + m.invoke(null, new Object[] { baseDir}); + + //baseDirSet = Tester.getBASEDIR(); + m = tc.getMethod("getBASEDIR", new Class[0]); + baseDirSet = (File) m.invoke(null, new Object[0]); + + if (!baseDirSet.equals(baseDir)) { + String l = "AjcScript.setupTester() setting " + + baseDir + " returned " + baseDirSet; + MessageUtil.debug(handler, l); + } + } + } catch (Throwable t) { + MessageUtil.abort(handler, "baseDir=" + baseDir, t); + } + } + + /** + * Clear (static) testing state and setup base directory, + * unless spec.skipTesting. + * This implementation assumes that Tester is defined for the + * same class loader as this class. + * @return null if successful, error message otherwise + */ + protected void setupTester(File baseDir, IMessageHandler handler) { + File baseDirSet = null; + try { + if (!spec.skipTester) { + Tester.clear(); + Tester.setMessageHandler(handler); + Tester.setBASEDIR(baseDir); + baseDirSet = Tester.getBASEDIR(); + if (!baseDirSet.equals(baseDir)) { + String l = "AjcScript.setupTester() setting " + + baseDir + " returned " + baseDirSet; + MessageUtil.debug(handler, l); + } + } + } catch (Throwable t) { + MessageUtil.abort(handler, "baseDir=" + baseDir, t); + } + } + public String toString() { + return "JavaRun(" + spec + ")"; + } + + /** + * Initializer/factory for JavaRun. + * The classpath is not here but precalculated in the Sandbox. XXX libs? + */ + public static class Spec extends AbstractRunSpec { + public static final String XMLNAME = "run"; + /** + * skip description, skip sourceLocation, + * do keywords, do options, skip paths, do comment, + * do dirChanges, do messages but skip children. + */ + private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT, + "", "", null, null, "", null, false, false, true); + + /** fully-qualified name of the class to run */ + protected String className; + + /** minimum required version of Java, if any */ + protected String javaVersion; + + /** if true, skip Tester setup (e.g., if Tester n/a) */ + protected boolean skipTester; + + public Spec() { + super(XMLNAME); + setXMLNames(NAMES); + } + + /** + * @param version "1.1", "1.2", "1.3", "1.4" + * @throws IllegalArgumentException if version is not recognized + */ + public void setJavaVersion(String version) { + LangUtil.supportsJava(version); + this.javaVersion = version; + } + + /** @className fully-qualified name of the class to run */ + public void setClassName(String className) { + this.className = className; + } + + /** @param skip if true, then do not set up Tester */ + public void setSkipTester(boolean skip) { + skipTester = skip; + } + + /** override to set dirToken to Sandbox.RUN_DIR */ + public void addDirChanges(DirChanges.Spec spec) { + if (null == spec) { + return; + } + spec.setDirToken(Sandbox.RUN_DIR); + super.addDirChanges(spec); + } + + /** @return a JavaRun with this as spec if setup completes successfully. */ + public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) { + JavaRun run = new JavaRun(this); + if (run.setupAjcRun(sandbox, validator)) { + // XXX need name for JavaRun + return new WrappedRunIterator(this, run); + } + return null; + } + + /** + * Write this out as a run element as defined in + * AjcSpecXmlReader.DOCTYPE. + * @see AjcSpecXmlReader#DOCTYPE + * @see IXmlWritable#writeXml(XMLWriter) + */ + public void writeXml(XMLWriter out) { + String attr = out.makeAttribute("class", className); + out.startElement(xmlElementName, attr, false); + if (skipTester) { + out.printAttribute("skipTester", "true"); + } + if (null != javaVersion) { + out.printAttribute("vm", javaVersion); + } + super.writeAttributes(out); + out.endAttributes(); + if (!LangUtil.isEmpty(dirChanges)) { + DirChanges.Spec.writeXml(out, dirChanges); + } + List messages = getMessages(); + if (0 < messages.size()) { + SoftMessage.writeXml(out, messages); + } + out.endElement(xmlElementName); + } + public String toLongString() { + return toString() + "[" + super.toLongString() + "]"; + } + + public String toString() { + if (skipTester) { + return "JavaRun(" + className + ", skipTester)"; + } else { + return "JavaRun(" + className + ")"; + } + } + + /** + * This implementation skips if: + * <ul> + * <li>current VM is not at least any specified javaVersion </li> + * </ul> + * @return false if this wants to be skipped, true otherwise + */ + protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) { + if (!super.doAdoptParentValues(parentRuntime, handler)) { + return false; + } + if ((null != javaVersion) && (!LangUtil.supportsJava(javaVersion))) { + skipMessage(handler, "requires Java version " + javaVersion); + return false; + } + return true; + } + } +} + diff --git a/testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java b/testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java new file mode 100644 index 000000000..ebc4c14c6 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/RunSpecIterator.java @@ -0,0 +1,256 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.Message; +import org.aspectj.testing.run.IRun; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.Runner; +import org.aspectj.testing.run.WrappedRunIterator; +import org.aspectj.util.LangUtil; + +import java.util.ArrayList; + + +/** + * This wraps an AbstractRunSpec, which has children that + * return IRunIterator, the results of which we return + * from nextRun(..) + * We extract global options from the AbstractRunSpec options + * and set the global options in the AbstractRunSpec, + * which is responsible for setting them in any children + * during makeRun(..). + */ +public class RunSpecIterator implements IRunIterator { + /* + * When constructed, this gets its own spec + * and a sandbox to be used for making all children. + * In nextRun() this uses the spec's child specs + * and the sandbox to create the next child run iterator. + * This returns all the run provided by that child iterator + * before going to the next child. + */ + + /** spec for this test */ + public final AbstractRunSpec spec; // XXX reconsider public after debugging + + /** current sandbox by default shared by all children */ + Sandbox sandbox; + + /** keep our copy to avoid recopying */ + ArrayList childSpecs; + + /** index into child specs of next run */ + int specIndex; + + /** child creation until the start of each run */ + final Validator validator; + + /** current child iterator */ + IRunIterator childIterator; + + final boolean haltOnFailure; + + private int numIncomplete; + + private final IMessage.Kind failureKind; + + private boolean didCleanup; + + /** + * Create a RunSpecIterator. + * Failure messages are of type IMessage.ABORT if abortOnFailure is true, + * or IMessage.ERROR otherwise. + * @param spec the AbstractRunSpec whose children we iterate - not null + * @param sandbox the default Sandbox to use for children to make runs - may be null + * @param haltOnFailure if true, stop after any failure in providing runs + */ + public RunSpecIterator( + AbstractRunSpec spec, + Sandbox sandbox, + Validator validator, + boolean haltOnFailure) { + this(spec, sandbox, validator, haltOnFailure, + (haltOnFailure ? IMessage.ABORT : IMessage.ERROR)); + } + + /** + * Create a RunSpecIterator, specifying any failure message kind. + * @param spec the AbstractRunSpec whose children we iterate - not null + * @param sandbox the default Sandbox to use for children to make runs - may be null + * @param haltOnFailure if true, stop after any failure in providing runs + * @param failureKind the IMessage.Kind for any failure messages - if null, no messages sent + */ + public RunSpecIterator( + AbstractRunSpec spec, + Sandbox sandbox, + Validator validator, + boolean haltOnFailure, + IMessage.Kind failureKind) { + LangUtil.throwIaxIfNull(spec, "spec"); + LangUtil.throwIaxIfNull(sandbox, "sandbox"); + LangUtil.throwIaxIfNull(validator, "validator"); + this.sandbox = sandbox; + this.spec = spec; + this.validator = validator; + this.haltOnFailure = haltOnFailure; + this.failureKind = failureKind; + reset(); + } + + /** + * @return value set on construction for abortOnError + * @see org.aspectj.testing.run.IRunIterator#abortOnFailure() + */ + public boolean abortOnFailure() { + return haltOnFailure; + } + + /** reset to start at the beginning of the child specs. */ + public void reset() { + specIndex = 0; + childSpecs = spec.getWorkingChildren(); + childIterator = null; + numIncomplete = 0; + } + + /** @return int number of child run attempts that did not produce IRun */ + public int getNumIncomplete() { + return numIncomplete; + } + + /** + * @see org.aspectj.testing.run.IRunIterator#hasNextRun() + */ + public boolean hasNextRun() { + return ((specIndex < childSpecs.size()) + || ((null != childIterator) + && childIterator.hasNextRun())); + } + + /** + * Get the next child IRunIterator as an IRun. + * In case of failure to get the next child, + * numIncomplete is incremented, and + * a message of type failureKind is passed to the handler + * (if failureKind was defined in the contructor). + * @return next child IRunIterator wrapped as an IRun + */ + public IRun nextRun(final IMessageHandler handler, Runner runner) { + validator.pushHandler(handler); + try { + IRun result = null; + IRunSpec childSpec = null; + String error = null; + String specLabel = "getting run for child of \"" + spec + "\" "; + while ((null == result) && hasNextRun() && (null == error)) { + if (null == childIterator) { + childSpec = (IRunSpec) childSpecs.get(specIndex++); + if (null == childSpec) { + error = "unexpected - no more child specs at " + --specIndex; + } else { + Sandbox sandbox = makeSandbox(childSpec, validator); + if (null == sandbox) { + error = "unable to make sandbox for \"" + childSpec + "\""; + childIterator = null; + } else { + IRunIterator iter = childSpec.makeRunIterator(sandbox, validator); + if (null == iter) { + // client should read reason why from validator + error = "child \"" + childSpec + "\".makeRunIterator(..) returned null"; + } else { + // hoist: result not wrapped but single IRun + if ((iter instanceof WrappedRunIterator)) { + if (!iter.hasNextRun()) { + error = "child \"" + childSpec + "\".hasNextRun()" + + " is not true - should be exactly one run"; + } else { + result = iter.nextRun(handler, runner); + if (null == result) { + error = "child \"" + childSpec + "\".nextRun()" + + " returned null - should be exactly one run"; + } else { + childIterator = null; + return result; + } + } + } else { + childIterator = iter; + } + } + } + } + } + if (null != childIterator) { + result = runner.wrap(childIterator, null); + childIterator = null; + } else if (null != error) { + numIncomplete++; + if (null != failureKind) { + handler.handleMessage(new Message(specLabel + error, failureKind, null, null)); + } + if (!haltOnFailure) { + error = null; + } else if (result != null) { + result = null; // do not return result if halting due to failure + } + } + } + return result; + } finally { + validator.popHandler(handler); + } + } + + /** + * @see org.aspectj.testing.run.IRunIterator#iterationCompleted() + */ + public void iterationCompleted() { + } + + public String toString() { + return "" + spec; + //return "RunSpecIterator(" + specIndex + ", " + spec + ")" ; + } + + /* + * Subclasses may: + * - set the sandbox on construction + * - lazily set it on first use + * - set it for each child + */ + + /** + * Create the sandbox used for each child. + * This implementation always uses the sandbox set on construction. + * Subclasses may decide to create one sandbox per child iterator. + */ + protected Sandbox makeSandbox(IRunSpec child, Validator validator) { + return getSandbox(); + } + + /** Get the sandbox currently in use */ + protected Sandbox getSandbox() { + return sandbox; + } + + /** Set the sandbox currently in use */ + protected void setSandbox(Sandbox sandbox) { + LangUtil.throwIaxIfNull(sandbox, "sandbox"); + this.sandbox = sandbox; + } + +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/Sandbox.java b/testing/src/org/aspectj/testing/harness/bridge/Sandbox.java new file mode 100644 index 000000000..6f4984bac --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/Sandbox.java @@ -0,0 +1,386 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.ICommand; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.testing.util.Diffs; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.ArrayList; + +/** + * A sandbox holds state shared by AjcTest sub-runs, + * mostly directories relevant to testing. + * It permits a limited amount of coordination and + * setup/cleanup operations (todo XXX). + * <p> + * AjcTest creates the Sandbox and initializes the final fields. + * To coordinate with each other, run components may set and get values, + * with the sources running first and the sinks second. + * To make the interactions clear + * (and to avoid accidentally violating these semantics), + * setters/getters for a coordinated property are constrained two ways: + * <li>Both have an extra (typed) "caller" parameter which must not + * be null, authenticating that the caller is known & valid.</li> + * <li>A getter throws IllegalStateException if called before the setter</li> + * <li>A setter throws IllegalStateException if called after the getter<li> + * XXX subclass more general sandbox? + */ +public class Sandbox { + /** classes directory token for DirChanges.Spec */ + public static final String RUN_DIR = "run"; + + /** run directory token for DirChanges.Spec */ + public static final String CLASSES_DIR = "classes"; + + private static boolean canRead(File dir) { + return ((null != dir) && dir.isDirectory() && dir.canRead()); + } + + private static boolean canWrite(File dir) { + return ((null != dir) && dir.isDirectory() && dir.canWrite()); + } + + private static void iaxWrite(File dir, String label) { + if (!canWrite(dir)) { + throw new IllegalArgumentException(label + " - " + dir); + } + } + + private static void iaxRead(File dir, String label) { + if (!canRead(dir)) { + throw new IllegalArgumentException(label + " - " + dir); + } + } + + /** @throws IllegalStateException(message) if test */ + private static void assertState(boolean test, String message) { + if (!test) { + throw new IllegalStateException(message); + } + } + + /** + * The (read-only) base of the test sources (which may or may not + * be the base of the java sources) + */ + public final File testBaseDir; + + /** the parent of a temporary workspace, probably includes some others */ + public final File sandboxDir; + + /** a shared working dir */ + public final File workingDir; + + /** a shared classes dir */ + public final File classesDir; + + /** a run dir (which will be ignored in non-forking runs) */ + public final File runDir; + + /** staging directory for IAjcRun requiring files be copied, deleted, etc. */ + public final File stagingDir; + + /** + * This manages creation and deletion of temporary directories. + * We hold a reference so that our clients can signal whether + * this should be deleted. + */ + private final Validator validator; // XXX required after completing tests? + + /** original base of the original java sources, set by CompileRun.setup(..) */ + private File testBaseSrcDir; + + /** directories and libraries on the classpath, set by CompileRun.setup(..) */ + private File[] classpath; + + /** track whether classpath getter ran */ + private boolean gotClasspath; + + /** command shared between runs using sandbox - i.e., compiler */ + private ICommand command; + + /** track whether command getter ran */ + private boolean gotCommand; + + /** cache results of rendering final fields */ + private transient String toStringLeader; + + /** @throws IllegalArgumentException unless validator validates + * testBaseDir as readable + */ + public Sandbox(File testBaseDir, Validator validator) { + LangUtil.throwIaxIfNull(validator, "validator"); + this.validator = validator; + Sandbox.iaxRead(testBaseDir, "testBaseDir"); + this.testBaseDir = testBaseDir; + + sandboxDir = FileUtil.getTempDir("Sandbox"); + Sandbox.iaxWrite(sandboxDir, "sandboxDir"); // XXX not really iax + + workingDir = FileUtil.makeNewChildDir(sandboxDir, "workingDir"); + Sandbox.iaxWrite(workingDir, "workingDir"); + + classesDir = FileUtil.makeNewChildDir(sandboxDir, "classes"); + Sandbox.iaxWrite(classesDir, "classesDir"); + + runDir = FileUtil.makeNewChildDir(sandboxDir, "run"); + Sandbox.iaxWrite(runDir, "runDir"); + + stagingDir = FileUtil.makeNewChildDir(sandboxDir, "staging"); + Sandbox.iaxWrite(stagingDir, "stagingDir"); + + validator.registerSandbox(this); + } + + private String getToStringLeader() { + if (null == toStringLeader) { + toStringLeader = "Sandbox(" + sandboxDir.getName() + + ", " + testBaseSrcDir.getName(); + } + return toStringLeader; + } + + /** @return "Sandbox(sandbox, src, classes)" with names only */ + public String toString() { + return getToStringLeader() + ", " + classesDir.getName() + ")"; + } + + /** @return "Sandbox(sandbox, src, classes)" with paths */ + public String toLongString() { + return getToStringLeader() + ", " + classesDir.getPath() + + (null == command ? ", (null command)" : ", " + command) + ")"; + } + + void setCommand(ICommand command, CompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + LangUtil.throwIaxIfNull(command, "command"); + LangUtil.throwIaxIfFalse(!gotCommand, "no command"); + this.command = command; + } + + /** When test is completed, clear the compiler to avoid memory leaks */ + void clearCommand(AjcTest caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + if (null != command) { + command = null; + } + } + +// /** +// * Populate the staging directory by copying any files in the +// * source directory ending with fromSuffix +// * to the staging directory, after renaming them with toSuffix. +// * If the source file name starts with "delete", then the +// * corresponding file in the staging directory is deleting. +// * @return a String[] of the files copied or deleted +// * (path after suffix changes and relative to staging dir) +// * @throws Error if no File using fromSuffix are found +// */ +// String[] populateStagingDir(String fromSuffix, String toSuffix, IAjcRun caller) { +// LangUtil.throwIaxIfNull(fromSuffix, "fromSuffix"); +// LangUtil.throwIaxIfNull(toSuffix, "toSuffix"); +// LangUtil.throwIaxIfNull(caller, "caller"); +// +// ArrayList result = new ArrayList(); +// FileUtil.copyDir( +// srcBase, +// targetSrc, +// fromSuffix, +// toSuffix, +// collector); +// +// final String canonicalFrom = srcBase.getCanonicalPath(); +// final Definition[] defs = getDefinitions(srcBase); +// if ((null == defs) || (defs.length < 9)) { +// throw new Error("did not get definitions"); +// } +// MessageHandler compilerMessages = new MessageHandler(); +// StringBuffer commandLine = new StringBuffer(); +// for (int i = 1; result && (i < 10); i++) { +// String fromSuffix = "." + i + "0.java"; +// // copy files, collecting as we go... +// files.clear(); +// if (0 == files.size()) { // XXX detect incomplete? +// break; +// } +// +// +// return (String[]) result.toArray(new String[0]); +// } + + // XXX move to more general in FileUtil + void reportClassDiffs( + final IMessageHandler handler, + IncCompilerRun caller, + long classesDirStartTime, + String[] expectedSources) { + LangUtil.throwIaxIfFalse(0 < classesDirStartTime, "0 >= " + classesDirStartTime); + boolean acceptPrefixes = true; + Diffs diffs = org.aspectj.testing.util.FileUtil.dirDiffs( + "classes", + classesDir, + classesDirStartTime, + ".class", + expectedSources, + acceptPrefixes); + diffs.report(handler, IMessage.ERROR); + } + +// // XXX replace with IMessage-based implementation +// // XXX move to more general in FileUtil +// void reportClassesDirDiffs(final IMessageHandler handler, IncCompilerRun caller, +// String[] expectedSources) { +// // normalize sources to ignore +// final ArrayList sources = new ArrayList(); +// if (!LangUtil.isEmpty(expectedSources)) { +// for (int i = 0; i < expectedSources.length; i++) { +// String srcPath = expectedSources[i]; +// int clip = FileUtil.sourceSuffixLength(srcPath); +// if (0 != clip) { +// srcPath = srcPath.substring(0, srcPath.length() - clip); +// sources.add(FileUtil.weakNormalize(srcPath)); +// } else if (srcPath.endsWith(".class")) { +// srcPath = srcPath.substring(0, srcPath.length() - 6); +// sources.add(FileUtil.weakNormalize(srcPath)); +// } else { +// MessageUtil.info(handler, "not source file: " + srcPath); +// } +// } +// } +// +// // gather, normalize paths changed +// final ArrayList changed = new ArrayList(); +// FileFilter touchedCollector = new FileFilter() { +// public boolean accept(File file) { +// if (file.lastModified() > classesDirTime) { +// String path = file.getPath(); +// if (!path.endsWith(".class")) { +// MessageUtil.info(handler, "changed file not a class: " + file); +// } else { +// String classPath = path.substring(0, path.length() - 6); +// classPath = FileUtil.weakNormalize(classPath); +// if (sources.contains(classPath)) { +// sources.remove(classPath); +// } else { +// changed.add(classPath); +// } +// } +// } +// return false; +// } +// }; +// classesDir.listFiles(touchedCollector); +// +// // report any unexpected changes +// Diffs diffs = new Diffs("classes", sources, changed, String.CASE_INSENSITIVE_ORDER); +// diffs.report(handler, IMessage.ERROR); +// } + + ICommand getCommand(IncCompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != command, "command never set"); + return command; + } + + File getTestBaseSrcDir(IncCompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + return testBaseSrcDir; + } + + File getTestBaseSrcDir(JavaRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + return testBaseSrcDir; + } + + /** @throws IllegalArgumentException unless a readable directory */ + void setTestBaseSrcDir(File dir, CompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + if ((null == dir) || !dir.isDirectory() || !dir.canRead()) { + throw new IllegalArgumentException("bad test base src dir: " + dir); + } + testBaseSrcDir = dir; + } + + /** @param readable if true, then throw IllegalArgumentException if not readable */ + void setClasspath(File[] files, boolean readable, CompilerRun caller) { + LangUtil.throwIaxIfNull(files, "files"); + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(!gotClasspath, "classpath already read"); + classpath = new File[files.length]; + for (int i = 0; i < files.length; i++) { + LangUtil.throwIaxIfNull(files[i], "files[i]"); + if (readable && !files[i].canRead()) { + throw new IllegalArgumentException("bad classpath entry: " + files[i]); + } + classpath[i] = files[i]; + } + } + + File[] getClasspath(JavaRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != classpath, "classpath not set"); + + File[] result = new File[classpath.length]; + System.arraycopy(classpath, 0, result, 0, result.length); + return result; + } + + /** @param readable if true, omit non-readable directories */ + File[] getClasspathDirectories(boolean readable, JavaRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != classpath, "classpath not set"); + ArrayList result = new ArrayList(); + File[] src = classpath; + for (int i = 0; i < src.length; i++) { + File f = src[i]; + if ((null != f) && (f.isDirectory()) && (!readable || f.canRead())) { + result.add(f); + } + } + return (File[]) result.toArray(new File[0]); + } + + /** @param readable if true, omit non-readable directories */ + File[] getClasspathJars(boolean readable, JavaRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != classpath, "classpath not set"); + ArrayList result = new ArrayList(); + File[] src = classpath; + for (int i = 0; i < src.length; i++) { + File f = src[i]; + if (FileUtil.hasZipSuffix(f) && (!readable || f.canRead())) { + result.add(f); + } + } + return (File[]) result.toArray(new File[0]); + } + + /** @return String of classpath entries delimited internally by File.pathSeparator */ + String classpathToString(CompilerRun caller) { + LangUtil.throwIaxIfNull(caller, "caller"); + assertState(null != classpath, "classpath not set"); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < classpath.length; i++) { + if (i > 0) { + sb.append(File.pathSeparator); + } + sb.append(classpath[i].getAbsolutePath()); + } + return sb.toString(); + } +} diff --git a/testing/src/org/aspectj/testing/harness/bridge/Validator.java b/testing/src/org/aspectj/testing/harness/bridge/Validator.java new file mode 100644 index 000000000..55c3962e8 --- /dev/null +++ b/testing/src/org/aspectj/testing/harness/bridge/Validator.java @@ -0,0 +1,557 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Stack; + +/** + * Check input and implement defaults. + * This handles failure messaging and collecting temp directories + * for later cleanup. + * The default behavior is to send a fail message to the message + * handler. Clients may instead: + * <li>Toggle abortOnException to throw AbortException on failure</li> + * <li>push their own message handler to redirect messages + * (pop to remove if any pushed)</li> + * <li>subclass this to reimplement <code>fail(String)</code> + * <p> + * A component given this to do validation should + * not change the reporting scheme established by the caller, + * so the caller may lock and unlock the error handling policy. + * When the policy is locked, this silently ignores attempts + * to toggle exceptions, or delete temporary files. + * XXX callers cannot prevent others from pushing other error handlers. + */ +public class Validator { + + /** stack of handlers */ + private final Stack handlers; + + /** list of File registered for deletion on demand */ + private final ArrayList tempFiles; // deleteTempFiles requires ListIterator.remove() + + /** list of Sandboxes registered for cleanup on demand */ + private final ArrayList sandboxes; + + /** if true, throw AbortException on failure */ + boolean abortOnFailure; + + /** this object prevents any changes to error-handling policy */ + private Object locker; + + public Validator(IMessageHandler handler) { + tempFiles = new ArrayList(); + sandboxes = new ArrayList(); + handlers = new Stack(); + pushHandler(handler); + } + + /** + * Push IMessageHandler onto stack, + * so it will be used until the next push or pop + * @param handler not null + * + */ + public void pushHandler(IMessageHandler handler) { + LangUtil.throwIaxIfNull(handler, "handler"); + handlers.push(handler); + } + + /** @throws IllegalStateException if handler is not on top */ + public void popHandler(IMessageHandler handler) { + LangUtil.throwIaxIfNull(handler, "handler"); + if (handler != handlers.peek()) { + throw new IllegalStateException("not current handler"); + } + handlers.pop(); + } + + /** @return true if this requestor now has locked the error handling policy */ + public boolean lock(Object requestor) { + if (null == locker) { + locker = requestor; + } + return (locker == requestor); + } + + /** @return true if the error handling policy is now unlocked */ + public boolean unlock(Object requestor) { + if (requestor == locker) { + locker = null; + } + return (locker == null); + } + + public void setAbortOnFailure(boolean abortOnFailure) { + if (null == locker) { + if (this.abortOnFailure != abortOnFailure) { + this.abortOnFailure = abortOnFailure; + } + } + } + + /** + * May fail with any of the messages + * <li>{null check} array<li> + * <li>{null check} {message}[#}<li> + */ + public boolean nullcheck(Object[] ra, String message) { + return ((nullcheck((Object) ra, message + " array")) + && nullcheck(Arrays.asList(ra), message)); + } + + /** + * Like nullcheck(Collection, message), except adding lower and upper bound + * @param atLeast fail if list size is smaller than this + * @param atMost fail if list size is greater than this + */ + public boolean nullcheck(Collection list, int atLeast, int atMost, String message) { + if (nullcheck(list, message)) { + int size = list.size(); + if (size < atLeast) { + fail(message + ": " + size + "<" + atLeast); + } else if (size > atMost) { + fail(message + ": " + size + ">" + atMost); + } else { + return true; + } + } + return false; + } + + /** + * May fail with any of the messages + * <li>{null check} list<li> + * <li>{null check} {message}[#}<li> + */ + public boolean nullcheck(Collection list, String message) { + if (nullcheck((Object) list, message + " list")) { + int i = 0; + for (Iterator iter = list.iterator(); iter.hasNext();) { + if (!nullcheck(iter.next(), message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + + /** + * May fail with the message "null {message}" + * if o and the def{ault} are null + * @return o if not null or default otherwise + */ + public Object nulldefault(Object o, String message, Object def) { + if (null == o) { + o = def; + } + nullcheck(o, message); + return o; + } + + /** may fail with the message "null {message}" */ + public boolean nullcheck(Object o, String message) { + if (null == o) { + if (null == message) message = "object"; + fail("null " + message); + return false; + } + return true; + } + + /** + * Verify that all paths are readable relative to baseDir. + * may fail with the message "cannot read {file}" + */ + public boolean canRead(File baseDir, String[] paths, String message) { + if (!canRead(baseDir, "baseDir - " + message) + || !nullcheck(paths, "paths - " + message)) { + return false; + } + final String dirPath = baseDir.getPath(); + File[] files = FileUtil.getBaseDirFiles(baseDir, paths); + for (int j = 0; j < files.length; j++) { + if (!canRead(files[j], "{" + dirPath + "} " + files[j])) { + return false; + } + } + return true; + } + + /** + * Verify that all paths are readable relative to baseDir. + * may fail with the message "cannot read {file}" + */ + public boolean canRead(File[] files, String message) { + if (!nullcheck(files, message)) { + return false; + } + for (int j = 0; j < files.length; j++) { + if (!canRead(files[j], files[j].getPath())) { + return false; + } + } + return true; + } + + /** may fail with the message "cannot read {file}" */ + public boolean canRead(File file, String message) { + if (nullcheck(file, message)) { + if (file.canRead()) { + return true; + } else { + fail("cannot read " + file); + } + } + return false; + } + + /** may fail with the message "cannot write {file}" */ + public boolean canWrite(File file, String message) { + if (nullcheck(file, message)) { + if (file.canRead()) { + return true; + } else { + fail("cannot write " + file); + } + } + return false; + } + + /** may fail with the message "not a directory {file}" */ + public boolean canReadDir(File file, String message) { + if (canRead(file, message)) { + if (file.isDirectory()) { + return true; + } else { + fail("not a directory " + file); + } + } + return false; + } + + /** may fail with the message "not a directory {file}" */ + public boolean canWriteDir(File file, String message) { + if (canWrite(file, message)) { + if (file.isDirectory()) { + return true; + } else { + fail("not a directory " + file); + } + } + return false; + } + + /** + * May fail with any of the messages + * <li>{null check} dir array<li> + * <li>"#: not a File {file}"<li> + * <li>{canRead} {message}[#}<li> + */ + public boolean canReadFiles(Object[] dirs, String message) { + return ((nullcheck((Object) dirs, message + " dir array")) + && canReadFiles(Arrays.asList(dirs), message)); + } + + /** + * May fail with any of the messages + * <li>{null check} files<li> + * <li>"#: not a File {file}"<li> + * <li>{canRead} {message}[#}<li> + */ + public boolean canReadFiles(Collection dirs, String message) { + if (nullcheck((Object) dirs, message + " files")) { + int i = 0; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (! (o instanceof File)) { + fail(i + ": not a file " + o); + } + if (!canRead((File) o, message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + /** + * May fail with any of the messages + * <li>{null check} dir array<li> + * <li>"#: not a File {file}"<li> + * <li>{canReadDir} {message}[#}<li> + */ + public boolean canReadDirs(Object[] dirs, String message) { + return ((nullcheck((Object) dirs, message + " dir array")) + && canReadDirs(Arrays.asList(dirs), message)); + } + + /** + * May fail with any of the messages + * <li>{null check} dirs<li> + * <li>"#: not a File {file}"<li> + * <li>{canReadDir} {message}[#}<li> + */ + public boolean canReadDirs(Collection dirs, String message) { + if (nullcheck((Object) dirs, message + " dirs")) { + int i = 0; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (! (o instanceof File)) { + fail(i + ": not a file " + o); + } + if (!canReadDir((File) o, message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + + /** + * May fail with any of the messages + * <li>{null check} dir array<li> + * <li>"#: not a File {file}"<li> + * <li>{canWrite} {message}[#}<li> + */ + public boolean canWriteFiles(Object[] dirs, String message) { + return ((nullcheck((Object) dirs, message + " dir array")) + && canWriteFiles(Arrays.asList(dirs), message)); + } + + /** + * May fail with any of the messages + * <li>{null check} files<li> + * <li>"#: not a File {file}"<li> + * <li>{canWrite} {message}[#}<li> + */ + public boolean canWriteFiles(Collection dirs, String message) { + if (nullcheck((Object) dirs, message + " files")) { + int i = 0; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (! (o instanceof File)) { + fail(i + ": not a file " + o); + } + if (!canWrite((File) o, message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + + /** + * May fail with any of the messages + * <li>{null check} dir array<li> + * <li>"#: not a File {file}"<li> + * <li>{canWriteDir} {message}[#}<li> + */ + public boolean canWriteDirs(Object[] dirs, String message) { + return ((nullcheck((Object) dirs, message + " dir array")) + && canWriteDirs(Arrays.asList(dirs), message)); + } + + /** + * May fail with any of the messages + * <li>{null check} dirs<li> + * <li>"#: not a File {file}"<li> + * <li>{canWriteDir} {message}[#}<li> + */ + public boolean canWriteDirs(Collection dirs, String message) { + if (nullcheck((Object) dirs, message + " dirs")) { + int i = 0; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (! (o instanceof File)) { + fail(i + ": not a file " + o); + } + if (!canWriteDir((File) o, message + "[" + i++ + "]")) { + return false; + } + } + return true; + } + return false; + } + + /** + * Send an info message to any underlying handler + * @param message ignored if null + */ + public void info(String message) { + if (null != message) { + IMessageHandler handler = getHandler(); + MessageUtil.info(handler, message); + } + } + + /** Fail via message or AbortException */ + public void fail(String message) { + fail(message, (Throwable) null); + } + + /** + * Fail via message or AbortException. + * All failure messages go through here, + * so subclasses may override to control + * failure-handling. + */ + public void fail(String message, Throwable thrown) { + if ((null == message) && (null == thrown)) { + message = "<Validator:no message>"; + } + IMessage m = MessageUtil.fail(message, thrown); + if (abortOnFailure) { + throw new AbortException(m); + } else { + IMessageHandler handler = getHandler(); + handler.handleMessage(m); + } + } + + /** + * Register a file temporary, i.e., to be + * deleted on completion. The file need not + * exist (yet or ever) and may be a duplicate + * of existing files registered. + */ + public void registerTempFile(File file) { + if (null != file) { + tempFiles.add(file); + } + } + + /** + * Get a writable {possibly-empty} directory. + * If the input dir is null, then try to create a temporary + * directory using name. + * If the input dir is not null, this tries to + * create it if it does not exist. + * Then if name is not null, + * it tries to get a temporary directory + * under this using name. + * If name is null, "Validator" is used. + * If deleteContents is true, this will try to delete + * any existing contents. If this is unable to delete + * the contents of the input directory, this may return + * a new, empty temporary directory. + * If register is true, then any directory returned is + * saved for later deletion using <code>deleteTempDirs()</code>, + * including the input directory. + * When this is unable to create a result, if failMessage + * is not null then this will fail; otherwise it returns false; + */ + public File getWritableDir(File dir, String name, + boolean deleteContents, boolean register, String failMessage) { + // check dir + if (null == dir) { + if (null == name) { + name = "Validator"; + } + dir = FileUtil.getTempDir(name); + } else { + if (!dir.exists()) { + dir.mkdirs(); + } + } + // fail if necessary + if ((null == dir) || (!dir.exists())) { + if (null != failMessage) { + fail(failMessage + ": unable to get parent " + dir); + } + } else { + File result = FileUtil.makeNewChildDir(dir, name); + if (deleteContents) { + FileUtil.deleteContents(dir); + } + if (register) { + tempFiles.add(dir); + } + } + return dir; + } + + /** + * Delete any temp sandboxes, files or directories saved, + * optionally reporting failures in the normal way. + * This will be ignored unless the failure policy + * is unlocked. + */ + public void deleteTempFiles(boolean reportFailures) { + if (null == locker) { + for (ListIterator iter = tempFiles.listIterator(); iter.hasNext();) { + deleteFile((File) iter.next(), reportFailures); + } + for (ListIterator iter = sandboxes.listIterator(); iter.hasNext();) { + Sandbox sandbox = (Sandbox) iter.next(); + // XXX assumes all dirs are in sandboxDir + deleteFile(sandbox.sandboxDir, reportFailures); + } + } + } + + /** + * Manage temp files and directories of registered sandboxes. + * Note that a sandbox may register before it is initialized, + * so this must do nothing other than save the reference. + * @param sandbox the uninitialized Sandbox to track + */ + public void registerSandbox(Sandbox sandbox) { + sandboxes.add(sandbox); + } + + + private void deleteFile(File file, boolean reportFailures) { + if (null == file) { + if (reportFailures) { + fail("unable to delete null file"); + } + return; + } + FileUtil.deleteContents(file); + if (file.exists()) { + file.delete(); + } + if (reportFailures && file.exists()) { + fail("unable to delete " + file); + } + } + + /** @throws IllegalStateException if handler is null */ + private IMessageHandler getHandler() { + IMessageHandler handler = (IMessageHandler) handlers.peek(); + if (null == handler) { + throw new IllegalStateException("no handler"); + } + return handler; + } + +} // class Validator + diff --git a/testing/src/org/aspectj/testing/run/IRun.java b/testing/src/org/aspectj/testing/run/IRun.java new file mode 100644 index 000000000..8cc33f619 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRun.java @@ -0,0 +1,62 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +/** + * A run is a Runnable that may know how to set its own status. + * @author isberg + */ +public interface IRun { + public static final IRun[] RA_IRun = new IRun[0]; + + /** Positive wrapper for the status parameter */ + public static final IRun OK + = new IRun() { + /** This returns false when the status is null + * or runResult is false */ + public boolean run(IRunStatus status) { + return ((null != status) && status.runResult()); + } + public IRunStatus makeStatus() { return null; } + }; + + /** Negative wrapper for the status parameter */ + public static final IRun NOTOK + = new IRun() { + public boolean run(IRunStatus status) { + return ((null == status) || !status.runResult()); + } + public IRunStatus makeStatus() { return null; } + }; + + /** + * Run process, setting any known status. + * Normally the caller starts the process + * and the callee completes it, so that + * status.isCompleted() returns true after + * the call completes. However, responsible + * callees ensure starting, and responsible + * callers ensure completed after the call. + * Anyone setting completion should ensure it + * is set recursively for all children, + * and anyone starting child runs should + * ensure children are registered and initialized + * appropriately. + * @param status the IRunStatus representing the + * outcome of the process (collecting parameter). + * @see Runners + */ + boolean run(IRunStatus status) throws Exception; // IMessageHandler? +} diff --git a/testing/src/org/aspectj/testing/run/IRunIterator.java b/testing/src/org/aspectj/testing/run/IRunIterator.java new file mode 100644 index 000000000..34bc12d76 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRunIterator.java @@ -0,0 +1,62 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessageHandler; + +/** + * Iterator for IRun. + * IRunIterator are useful if the underlying components + * can use a generic IRunStatus and a single listener. + * It is a requirement of any component runnin the IRunIterator + * that they call iterationCompleted() when done, to permit + * the IRunIterator to clean up. + * @see Runner#runIterator(IRunIterator, IRunStatus, IRunListener) + */ +public interface IRunIterator { + + /** + * @return true if nextRun() would return something non-null + * @throws IllegalStateException if called after + * <code>iterationCompleted()</code> + */ + boolean hasNextRun(); + + /** + * Get the next run. + * IRunIterator which contains child IRunIterator may either return + * the children IRun or wrap them using + * Runner.wrap(IRunIterator, IRunListener) + * @param handler the IMessageHandler to use for error and other messages + * @param runnere the Runner to use to wrap any nested IRunIterator as IRun. + * @return the next run, or null if there are no more. + * @throws IllegalStateException if called after + * <code>iterationCompleted()</code> + * @see Runner#wrap(IRunIterator, IRunListener) + */ + IRun nextRun(IMessageHandler handler, Runner runner); + + /** + * Signal a runner that further runs should be aborted. Runners + * should check this after each failure. + * @return true if the runner should stop iterating when an IRun fails + * @throws IllegalStateException if called after + * <code>iterationCompleted()</code> + */ + boolean abortOnFailure(); // XXX supply IRun or IRunStatus? + + /** called when hasNextRun() and nextRun() will no longer be called */ + void iterationCompleted(); +} diff --git a/testing/src/org/aspectj/testing/run/IRunListener.java b/testing/src/org/aspectj/testing/run/IRunListener.java new file mode 100644 index 000000000..85ab6148b --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRunListener.java @@ -0,0 +1,38 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +/** + * Listen to events in the run lifecycle - + * birth, death, and procreation. + * @author isberg + */ +public interface IRunListener { + + /** + * Called when run is about to be started. + */ + void runStarting(IRunStatus run); + + /** + * Called when run is has completed. + */ + void runCompleted(IRunStatus run); + + /** + * Called when adding a child to a parent run + */ + void addingChild(IRunStatus parent, IRunStatus child); +} diff --git a/testing/src/org/aspectj/testing/run/IRunStatus.java b/testing/src/org/aspectj/testing/run/IRunStatus.java new file mode 100644 index 000000000..6651c42f1 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRunStatus.java @@ -0,0 +1,175 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.MessageUtil; + +/** + * Encapsulate status and results for a run. + * A run starts and then completes normally + * (finished(Object result)), + * abruptly (thrown(Throwable thrown)), + * or by user request (abort(Object request)). + * @author isberg + */ +public interface IRunStatus extends IMessageHolder { + /** clients use this when signalling completion without a specific result */ + public static final Object VOID = Boolean.TRUE; + + /** result object for successful (unset) boolean run result */ + public static final Boolean PASS = Boolean.TRUE; + + /** result object for failed (unset) boolean run result */ + public static final Boolean FAIL = Boolean.FALSE; + + /** clients use this when signalling abort without any specific request */ + public static final Object ABORT = Boolean.FALSE; + + /** clients use this when signalling abort because no object to run */ + public static final Object ABORT_NORUN = MessageUtil.ABORT_NOTHING_TO_RUN; + + /** returned from getChildren when there are no children */ + public static final IRunStatus[] EMPTY_NEST = new IRunStatus[0]; + + //------------------- process controls + /** + * Set identifier associated with this run, if any + * @throws IllegalArgumentException if id is null + * @throws IllegalStateException if id has already been set + */ + void setIdentifier(Object id); + + //------------------- process controls + /** + * Call before any start() or after isCompleted() would return true + * to reset this to its pre-start state + * @throws IllegalStateException if start() has been called + * and isCompleted() is not true. + */ + void reset(); + + /** + * Call only once to signal this run has started. + * @throws IllegalStateException if start() has been called + */ + void start(); + + /** + * Call this or thrown only once after start() + * to signal this run has ended. + * If this represents a void process, use VOID. + * @param result the Object returned by this run. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + void finish(Object result); + + /** + * Call to signal this run is ending by request. + * If there is no message, use ABORT. + * @param request the Object request to abort, + * or ABORT if none is available. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + void abort(Object request); + + /** + * Call this or completed only once after start() + * to signal this run has ended. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + void thrown(Throwable thrown); + + + /** + * Call this for the status to throw an unchecked exception + * of the type that its controller understands. + * It is an error for a IRunStatus to continue normally + * after this is invoked. + */ + void completeAbruptly(); + //------------------- process messages + /** + * Call this any time to signal any messages. + * (In particular, the IRun caller may use this to register messages + * about the mishandling of the run by the ResultStatusI by the callee.) + * This is a shortcut for getMessageHandler().handleMessage(..); + */ + //boolean handleMessage(IMessage message); + + //------------------- process display + /** @return true if this run has started */ + boolean started(); + + /** @return true if one of the result, abort request, or thrown is available */ + boolean isCompleted(); + + /** @return true if this got an abort request */ + boolean aborted(); + + /** + * @return true if completed and not aborted and no thrown + * or messages with kind ABORT or FAIL or ERROR + */ + boolean runResult(); + + /** get the invoker for any subruns */ + Runner getRunner(); + + /** @return the Object result, if any, of this run */ + Object getResult(); + + /** @return the Object abort request, if any, of this run */ + Object getAbortRequest(); + + /** @return the Throwable thrown, if any, by this run */ + Throwable getThrown(); + + /** @return any Message[] signalled, or SILENCE if none */ + IMessage[] getMessages(); + + /** @return the identifier set for this run, if any */ + Object getIdentifier(); + + //------------------- subprocess + /** + * Add a record for a child run + * and install self as parent. + * @throws IllegalArgumentException if child is null + */ + void addChild(IRunStatus child); + + /** + * Register this as the run parent. + * (Any run that does addChild(IRunStatus) should register as parent.) + * @throws IllegalArgumentException if parent is null + * @throws IllegalStateException if parent exists already + */ + void registerParent(IRunStatus parent); + + /** + * @return the current children of this run, or EMPTY_NEST if none + */ + IRunStatus[] getChildren(); + + /** + * @return the currently-registered parent, or null if none + */ + IRunStatus getParent(); +} diff --git a/testing/src/org/aspectj/testing/run/IRunValidator.java b/testing/src/org/aspectj/testing/run/IRunValidator.java new file mode 100644 index 000000000..29f73f103 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/IRunValidator.java @@ -0,0 +1,28 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +/** + * These check whether particular runs have passed. + * @author isberg + */ +public interface IRunValidator { + /** + * Evaluate whether a run has passed. + * @param run the IRunStatus to see if it passed. + * @return true if run has passed + */ + boolean runPassed(IRunStatus run); +} diff --git a/testing/src/org/aspectj/testing/run/RunIterator.java b/testing/src/org/aspectj/testing/run/RunIterator.java new file mode 100644 index 000000000..37e3187ed --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunIterator.java @@ -0,0 +1,136 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.util.LangUtil; + +import java.util.Arrays; +import java.util.List; +import java.util.ListIterator; + +/** + * Adapt IRun or Run[] or List or ListIterator to RunIteratorI. + */ +public class RunIterator implements IRunIterator { + + protected String name; + protected ListIterator iter; + protected IRun run; + + public RunIterator(String name, IRun run) { + init(name, run); + } + + public RunIterator(String name, List list) { + init(name, list); + } + + public RunIterator(String name, IRun[] runs) { + init(name, Arrays.asList(runs).listIterator()); + } + + public RunIterator(String name, ListIterator iterator) { + init(name, iterator); + } + + public void init(String name, List list) { + init(name, list.listIterator()); + } + + public void init(String name, IRun[] runs) { + init(name, Arrays.asList(runs).listIterator()); + } + + /** @return true if the first IRun from nextRun can be the sole IRun */ + public boolean isHoistable() { + return (null != run); + } + + /** + * @param name if null, use iterator.toString(); + * @param iterator not null + * @throws IllegalArgumentException if iterator is null + */ + public void init(String name, ListIterator iterator) { + LangUtil.throwIaxIfNull(iterator, "iterator"); + iter = iterator; + name = (null != name? name : iterator.toString()); + run = null; + } + + /** + * @param name if null, use run(); + * @param run not null + * @throws IllegalArgumentException if iterator is null + */ + public void init(String name, IRun run) { + LangUtil.throwIaxIfNull(run, "run"); + this.run = run; + name = (null != name? name : run.toString()); + iter = null; + } + + /** + * @return false always + * @see org.aspectj.testing.run.IRunIterator#abortOnFailure() + */ + public boolean abortOnFailure() { + return false; + } + + /** + * @see org.aspectj.testing.run.RunIteratorI#hasNextRun() + */ + public boolean hasNextRun() { + return ((null != run) || ((null != iter) && (iter.hasNext()))); + } + + /** + * @see org.aspectj.testing.run.IRunIterator#iterationCompleted() + */ + public void iterationCompleted() { + } + + + /** + * @see org.aspectj.testing.run.RunIteratorI#nextRun(IMessageHandler, Runner) + */ + public IRun nextRun(IMessageHandler handler, Runner runner) { + if (null != run) { + IRun result = run; + run = null; + return result; + } + if (null != iter) { + for (Object o = iter.next(); iter.hasNext();) { + if (o instanceof IRunIterator) { + return runner.wrap((IRunIterator) o, null); + } else if (o instanceof IRun) { + return (IRun) o; + } else { + MessageUtil.error(handler, "not IRun or IRunIterator: " + o); + } + } + } + return null; + } + + /** @return name */ + public String toString() { + return name; + } +} diff --git a/testing/src/org/aspectj/testing/run/RunListener.java b/testing/src/org/aspectj/testing/run/RunListener.java new file mode 100644 index 000000000..523862991 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunListener.java @@ -0,0 +1,116 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.MessageUtil; + +import java.io.PrintWriter; +import java.util.List; + +/** + * A generic RunListener for easier partial implementations. + * It can take a RunI selector (called on completion) + * and/or a List to accumulate complete IRunStatus + * (if the selector is null or returns true). + * It can also take a PrintWriter and String to print traces of each event + * as "{prefix} [addingChild|runStarting|runCompleted]({IRunStatus})" + */ +public class RunListener implements IRunListener { + protected final List list; + protected final IRun selector; + protected final PrintWriter writer; + protected final String prefix; + + protected RunListener() { + this((List) null, (IRun) null, (PrintWriter) null, (String) null); + } + + /** + * @param sink the List sink for any IRunStatus if the selector is null + * or returns true for run(IRunStatus) - ignored if null. + * @param selector the IRun called on completion, + * perhaps to select those to be accumulated + * (should NOT throw Exception) + */ + public RunListener(List sink, IRun selector) { + this(sink, selector, (PrintWriter) null, (String) null); + } + + /** + * @param writer the PrintWriter to print events to - may be null + * @param prefix the String prefixing any printing - if null, "" + */ + public RunListener(PrintWriter writer, String prefix) { + this((List) null, (IRun) null, writer, prefix); + } + + /** + * @param sink the List sink for any IRunStatus if the selector is null + * or returns true for run(IRunStatus) - ignored if null. + * @param selector the IRun called on completion, + * perhaps to select those to be accumulated + * (should NOT throw Exception) + * @param writer the PrintWriter to print events to - may be null + * @param prefix the String prefixing any printing - if null, "" + */ + public RunListener(List sink, IRun selector, PrintWriter writer, String prefix) { + this.prefix = (null == prefix ? "" : prefix); + this.writer = writer; + this.selector = selector; + list = sink; + } + + /** + * @see org.aspectj.testing.harness.run.IRunListener#addingChild(IRunStatus, IRunStatus) + */ + public void addingChild(IRunStatus parent, IRunStatus child) { + if (null != writer) { + writer.println(prefix + " addingChild(\"" + parent + + "\", \"" + child + "\")"); + } + } + + /** + * @see org.aspectj.testing.harness.run.IRunListener#runStarting(IRunStatus) + */ + public void runStarting(IRunStatus run) { + if (null != writer) { + writer.println(prefix + " runStarting(\"" + run + "\")"); + } + } + + /** + * Print to writer (if any), run selector (if any), and add to list + * (if any and if selector is null or returns true). + * @see org.aspectj.testing.harness.run.IRunListener#runCompleted(IRunStatus) + * @throws AbortException wrapping any Exception thrown by any selector + * (error for selector to throw Exception) + */ + public void runCompleted(IRunStatus run) { + if (null != writer) { + writer.println(prefix + " runCompleted(\"" + run + "\")"); + } + try { + if (((null == selector) || selector.run(run)) && (null != list)) { + list.add(run); + } + } catch (Throwable e) { + String m = "Selectors should not throw exceptions!"; + throw new AbortException(MessageUtil.abort(m, e)); + } + } + +} diff --git a/testing/src/org/aspectj/testing/run/RunListeners.java b/testing/src/org/aspectj/testing/run/RunListeners.java new file mode 100644 index 000000000..9b27d8605 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunListeners.java @@ -0,0 +1,89 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Aggregate listeners into one and run synchronously in order added. + * @author isberg + */ +public class RunListeners extends RunListener implements IRunListener { + + ArrayList listeners; + public RunListeners() { + listeners = new ArrayList(); + } + + public void addListener(IRunListener listener) { + if (null != listener) { + listeners.add(listener); + } + } + + public void removeListener(IRunListener listener) { + if (null != listener) { + listeners.remove(listener); + } + } + + + /** + * Run all listeners with the given status. + * @see org.aspectj.testing.harness.run.IRunListener#runStarting(IRunStatus) + */ + public final void runStarting(IRunStatus status) { + if (null == status) { + throw new IllegalArgumentException("null RunStatusI"); + } + Iterator iter = listeners.iterator(); + while(!status.aborted() && iter.hasNext()) { + IRunListener element = (IRunListener) iter.next(); + element.runStarting(status); + } + } + + /** + * Signal all listeners with the given status. + * @see org.aspectj.testing.harness.run.IRunListener#runCompleted(IRunStatus) + */ + public final void runCompleted(IRunStatus status) { + if (null == status) { + throw new IllegalArgumentException("null RunStatusI"); + } + Iterator iter = listeners.iterator(); + while(!status.aborted() && iter.hasNext()) { + IRunListener element = (IRunListener) iter.next(); + element.runCompleted(status); + } + } + /** + * @see org.aspectj.testing.harness.run.IRunListener#addingChild(IRunStatus, IRunStatus) + */ + public final void addingChild(IRunStatus parent, IRunStatus child) { + if (null == child) { + throw new IllegalArgumentException("null child"); + } + if (null == parent) { + throw new IllegalArgumentException("null parent"); + } + Iterator iter = listeners.iterator(); + while(!parent.aborted() && ! child.aborted() && iter.hasNext()) { + IRunListener element = (IRunListener) iter.next(); + element.addingChild(parent, child); + } + } +} diff --git a/testing/src/org/aspectj/testing/run/RunStatus.java b/testing/src/org/aspectj/testing/run/RunStatus.java new file mode 100644 index 000000000..0a53080e1 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunStatus.java @@ -0,0 +1,408 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.testing.util.BridgeUtil; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Default implementation of {@link IRunStatus}. + * @author isberg + */ +public class RunStatus implements IRunStatus { + private static int INDEX; + + private final String name = "RunStatus[" + INDEX++ +"]"; + + /** true after isCompleted() evaluated true */ + private boolean evaluated; + + /** true after starting() called */ + private boolean started; // set only in starting() + + /** true after finished(int) or thrown(Throwable) called */ + private boolean completed; // set only in completed(boolean) + + /** contains any id set */ + private Object id; + + /** after finished(Object) called, contains that parameter */ + private Object result; + + /** after aborted(Object) called, contains that parameter */ + private Object abortRequest; + + /** use to set exception thrown, if any */ + private Throwable thrown; + + /** list of any messages submitted */ + private IMessageHolder messageHolder; + + /** list of any child status */ + private ArrayList children; + + /** parent of this status */ + private IRunStatus parent; + + /** invoker for any subruns */ + private Runner runner; + + /** controls runResult() */ + private IRunValidator validator; + +// public RunStatus() { +// reset(); +// validator = RunValidator.NORMAL; +// } + + public RunStatus(IMessageHolder holder, Runner runner) { + reset(holder, runner); + validator = RunValidator.NORMAL; + } + + //------------------- process controls + + /** + * Set identifier associated with this run, if any + * @throws IllegalArgumentException if id is null + * @throws IllegalStateException if id has already been set + */ + public void setIdentifier(Object id) { + if (null == id) { + throw new IllegalArgumentException("null id"); + } else if ((null != this.id) && (id != this.id)) { + throw new IllegalStateException( + "attempt to set id " + this.id + " to " + id); + } + this.id = id; + } + + /** + * Set the current validator. + * @param delegate the RunValidatorI to use when calculating runStatus + * @throws IllegalArgumentException if delegate is null + */ + public void setValidator(IRunValidator delegate) { + if (null == delegate) { + throw new IllegalArgumentException("null delegate"); + } + if (validator != delegate) { + validator = delegate; + } + } + + /** + * Call before any start() or after isCompleted() would return true + * to reset this to its pre-start state + * @throws IllegalStateException if start() has been called + * and isCompleted() is not true. + */ + public void reset() { + reset((IMessageHolder) null, (Runner) null); + } + + /** + * Call before any start() or after isCompleted() would return true + * to reset this to its pre-start state. Does not affect validator. + * @param holder the IMessageHolder to use after resetting. + * @throws IllegalStateException if start() has been called + * and isCompleted() is not true. + */ + public void reset(IMessageHolder holder, Runner runner) { + if (null == runner) { + throw new IllegalArgumentException("null runner"); + } + if (started && (!isCompleted())) { + throw new IllegalStateException("no reset() until isCompleted"); + } + started = false; + completed = false; + result = null; + abortRequest = null; + thrown = null; + parent = null; + id = null; + messageHolder = (null != holder ? holder : new MessageHandler()); + if (null != children) { + children.clear(); + } + this.runner = runner; + evaluated = false; + } + + /** + * Call only once to signal this run has started. + * @throws IllegalStateException if start() has been called + */ + public void start() { + if (started) { + throw new IllegalStateException("started already"); + } else if (isCompleted()) { + throw new IllegalStateException("start after completed (do reset)"); + } + started = true; + } + + /** + * Call this or thrown only once after start() + * to signal this run has ended. + * If this represents a void process, use VOID. + * @param result the Object returned by this run. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + public void finish(Object result) { + if (null == result) { + throw new IllegalArgumentException("null result"); + } else if (isCompleted()) { + throw new IllegalStateException( + "completed then finish " + result); + } + this.result = result; + } + + /** + * Call to signal this run is ending by request. + * If this represents a void process, use VOID. + * If there is no message, use ABORT. + * @param request the Object request to abort, + * or ABORT if none is available. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + public void abort(Object request) { + if (null == request) { + throw new IllegalArgumentException("null request"); + } else if (isCompleted()) { + throw new IllegalStateException( + "completed then abort " + request); + } + this.abortRequest = request; + } + + /** + * Call this or completed only once after start() + * to signal this run has ended. + * @throws IllegalStateException if start() was not called first + * or if either completed(Object) or thrown(Throwable) have been called. + */ + public void thrown(Throwable thrown) { + if (null == thrown) { + throw new IllegalArgumentException("null thrown"); + } else if (isCompleted()) { + throw new IllegalStateException( + "completed then thrown " + thrown); + } + this.thrown = thrown; + } + + public void completeAbruptly() { + throw new Error("completing abruptly"); // XXX configurable + } + + /** + * @return true if completed, not aborted, no thrown, no + * messages of kind ERROR, FAIL or ABORT, and + * result object is not IRunStatus.FAIL. + * @see org.aspectj.testing.harness.newbridge.IRunStatus#runResult() + */ + public boolean runResult() { + return validator.runPassed(this); + } + + //------------------- process messages + /** + * Call this any time before isCompleted() would return true + * to signal any messages. + * @throws IllegalStateException if isCompleted(). + */ + public boolean handleMessage(IMessage message) { + return messageHolder.handleMessage(message); + } + public boolean isIgnoring(IMessage.Kind kind) { + return messageHolder.isIgnoring(kind); + } + + /** + * @see org.aspectj.bridge.IMessageHolder#hasAnyMessage(Kind, boolean) + */ + public boolean hasAnyMessage(IMessage.Kind kind, boolean orGreater) { + return messageHolder.hasAnyMessage(kind, orGreater); + } + + /** + * @see org.aspectj.bridge.IMessageHolder#getMessages(Kind) + */ + public IMessage[] getMessages(IMessage.Kind kind, boolean orGreater) { + return messageHolder.getMessages(kind, orGreater); + } + + /** + * @see org.aspectj.bridge.IMessageHolder#numMessages(Kind) + */ + public int numMessages(IMessage.Kind kind, boolean orGreater) { + return messageHolder.numMessages(kind, orGreater); + } + + //------------------- process display + /** @return true if this run has started */ + public boolean started() { + return started; + } + + /** @return true if one of the result, abort request, or thrown is available */ + public boolean isCompleted() { + if (!evaluated) { + if (started + && ((null != thrown) + || (null != result) + || (null != abortRequest))) { + completed = true; + evaluated = true; + } + } + return completed; + } + + /** @return true if this got an abort request */ + public boolean aborted() { + return (completed && (null != abortRequest)); + } + + /** @return the Object result, if any, of this run */ + public Object getResult() { + return result; + } + + /** @return the Object abort request, if any, of this run */ + public Object getAbortRequest() { + return abortRequest; + } + + /** @return the Throwable thrown, if any, by this run */ + public Throwable getThrown() { + return thrown; + } + + /** + * @see org.aspectj.bridge.IMessageHolder#getUnmodifiableListView() + */ + public List getUnmodifiableListView() { + return messageHolder.getUnmodifiableListView(); + } + + /** @return any Message[] signalled, or IMessage.NONE if none */ + public IMessage[] getMessages() { + return messageHolder.getMessages(null, IMessageHolder.EQUAL); + } + + /** @return the identifier set for this run, if any */ + public Object getIdentifier() { + return id; + } + + /** + * @see org.aspectj.bridge.IMessageHolder#clearMessages() + * @throws UnsupportedOperationException always + */ + public void clearMessages() throws UnsupportedOperationException { + throw new UnsupportedOperationException("use reset"); + } + + //------------------- subprocess + + /** get the invoker for any subrunners */ + public Runner getRunner() { + return runner; + } + + /** + * Add a record for a child run + * and install self as parent. + * @throws IllegalArgumentException if child is null + */ + public void addChild(IRunStatus child) { + if (null == child) { + throw new IllegalArgumentException("null child"); + } + if (null == children) { + children = new ArrayList(); + } + children.add(child); + } + + /** + * Register this as the run parent. + * (Any run that does addChild(IRunStatus) should register as parent.) + * @throws IllegalArgumentException if parent is null + * @throws IllegalStateException if parent exists already + */ + public void registerParent(IRunStatus parent) { + if (null == parent) { + throw new IllegalArgumentException("null parent"); + } else if (null != this.parent) { + throw new IllegalStateException( + "adding parent " + parent + " to parent " + this.parent); + } + this.parent = parent; + } + + /** + * @return the current children of this run, or EMPTY_NEST if none + */ + public IRunStatus[] getChildren() { + if ((null == children) || (0 == children.size())) { + return EMPTY_NEST; + } else { + return (IRunStatus[]) children.toArray(EMPTY_NEST); + } + } + + /** + * @return the currently-registered parent, or null if none + */ + public IRunStatus getParent() { + return parent; + } + + public String toString() { + return BridgeUtil.toShortString(this); + } + + public String toLongString() { + StringBuffer sb = new StringBuffer(); + sb.append(BridgeUtil.toShortString(this)); + if ((null != children) && (0 < children.size())) { + String label = "### --------- " + name; + int index = 0; + for (Iterator iter = children.iterator(); iter.hasNext();) { + IRunStatus childStatus = (IRunStatus) iter.next(); + String childLabel = + "\n" + label + " child[" + index++ +"] " + + childStatus.getIdentifier(); + sb.append(childLabel + " ---- start\n"); + sb.append(childStatus.toString()); + sb.append(childLabel + " ---- end\n"); + } + } + return sb.toString(); + } +} diff --git a/testing/src/org/aspectj/testing/run/RunValidator.java b/testing/src/org/aspectj/testing/run/RunValidator.java new file mode 100644 index 000000000..9cf91e37a --- /dev/null +++ b/testing/src/org/aspectj/testing/run/RunValidator.java @@ -0,0 +1,206 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.testing.util.IntRange; +import org.aspectj.testing.util.ObjectChecker; + +/** + * This checks if a run status passes, as follows: + * <li>state: fail unless completed and not aborted</li> + * <li>messages: fail if any of type ABORT, FAIL - permit ERROR, WARNING, DEBUG... + * (which permits expected compiler errors and warnings) </li> + * <li>thrown: if type required, fail unless type thrown; + * if type permitted, fail if wrong type thrown</li> + * <li>result: fail unless no ObjectChecker or it validates + * and the result is not IRunStatus.FAIL.</li> + * <li>otherwise delegates to any subclass doPassed()<li> + * Client setup the expected and permitted exception classes + * and the result object checker, and may also subclass to + * query the IRunStatus more carefully. + * <p> + * Note that IRunStatus states can be out of sync with messages, + * e.g., as underlying components signal ABORT without using abort(...). + */ +public class RunValidator implements IRunValidator { + /** expect normal completion with any non-null result object */ + public static final IRunValidator NORMAL + = new RunValidator(ObjectChecker.ANY); + + /** expect normal completion and Integer result object with value 0 */ + public static final IRunValidator ZERO_STATUS + = new RunValidator(IntRange.ZERO); + + /** expect finished(IRunStatus.PASS) and no thrown, fail, etc. */ + public static final IRunValidator PASS + = new RunValidator(new ObjectChecker() { + public boolean isValid(Object o) { + return (o == IRunStatus.PASS); + } + }); + + /** expect finished(IRunStatus.FAIL) */ + public static final IRunValidator FAIL + = new RunValidator(new ObjectChecker() { + public boolean isValid(Object o) { + return (o == IRunStatus.FAIL); + } + }); + + /** range of status values required for passing */ + private ObjectChecker resultChecker; + + // XXX replace two exc. classes with one, plus boolean for how to interpret? + /** if non-null, passed() permits any thrown assignable to this class */ + private Class permittedExceptionsClass; + + /** if non-null, passed() requires some thrown assignable to this class */ + private Class requiredExceptionsClass; + + /** Create result validator that expects a certain int status */ + public RunValidator(ObjectChecker resultChecker) { + this(resultChecker, null, null); + } + + /** + * Create result validator that passes only when completed abruptly by + * a Throwable assignable to the specified class. + * @throws illegalArgumentException if requiredExceptionsClass is not Throwable + */ + public RunValidator(Class requiredExceptionsClass) { + this(null, null, requiredExceptionsClass); + } + + /** + * Create a result handler than knows how to evaluate {@link #passed()}. + * You cannot specify both permitted and required exception class, + * and any exception class specified must be assignable to throwable. + * + * @param resultChecker {@link #passed()} will return false if + * the int status is not accepted by this int validator - if null, + * any int status result is accepted. + * @param fastFailErrorClass an Error subclass with a (String) constructor to use to + * construct and throw Error from fail(String). If null, then fail(String) + * returns normally. + * @param permittedExceptionsClass if not null and any exceptions thrown are + * assignable to this class, {@link #passed()} will not return + * false as it normally does when exceptions are thrown. + * @param requiredExceptionsClass if not null, {@link #passed()} will return false + * unless some exception was thrown that is assignable to this class. + * @throws illegalArgumentException if any exception class is not Throwable + * or if fast fail class is illegal (can't make String constructor) + */ + protected RunValidator( + ObjectChecker resultChecker, + Class permittedExceptionsClass, + Class requiredExceptionsClass) { + init(resultChecker,permittedExceptionsClass, requiredExceptionsClass); + } + + /** same as init with existing values */ + protected void reset() { + init(resultChecker, permittedExceptionsClass, + requiredExceptionsClass); + } + + /** subclasses may use this to re-initialize this for re-use */ + protected void init( + ObjectChecker resultChecker, + Class permittedExceptionsClass, + Class requiredExceptionsClass) { + this.permittedExceptionsClass = permittedExceptionsClass; + this.requiredExceptionsClass = requiredExceptionsClass; + + if (null != resultChecker) { + this.resultChecker = resultChecker; + } else { + this.resultChecker = IntRange.ANY; + } + + if (null != permittedExceptionsClass) { + if (!Throwable.class.isAssignableFrom(permittedExceptionsClass)) { + String e = "permitted not throwable: " + permittedExceptionsClass; + throw new IllegalArgumentException(e); + } + } + if (null != requiredExceptionsClass) { + if (!Throwable.class.isAssignableFrom(requiredExceptionsClass)) { + String e = "required not throwable: " + requiredExceptionsClass; + throw new IllegalArgumentException(e); + } + } + if ((null != permittedExceptionsClass) + && (null != requiredExceptionsClass) ) { + String e = "define at most one of required or permitted exceptions"; + throw new IllegalArgumentException(e); + } + } + + /** @return true if this result passes per this validator */ + public final boolean runPassed(IRunStatus result) { + if (null == result) { + throw new IllegalArgumentException("null result"); + } + // After the result has completed, the result is stored. + if (!result.isCompleted()) { + return false; + } + if (result.aborted()) { + return false; + } + if (null != result.getAbortRequest()) { + return false; + } + Object resultObject = result.getResult(); + if (!resultChecker.isValid(resultObject)) { + return false; + } + if (resultObject == IRunStatus.FAIL) { + return false; + } + // need MessageHandler.getMessage(...) + if (result.hasAnyMessage(IMessage.FAIL, IMessageHolder.ORGREATER)) { + return false; + } + Throwable thrown = result.getThrown(); + if (null == thrown) { + if (null != requiredExceptionsClass) { + return false; + } + } else { + Class c = thrown.getClass(); + // at most one of the ExceptionsClass set + if (null != requiredExceptionsClass) { + if (!requiredExceptionsClass.isAssignableFrom(c)) { + return false; + } + } else if (null != permittedExceptionsClass) { + if (!permittedExceptionsClass.isAssignableFrom(c)) { + return false; + } + } else { + return false; + } + } + return dopassed(); + } + + /** subclasses implement subclass-specific behavior for passed() here */ + protected boolean dopassed() { + return true; + } +} diff --git a/testing/src/org/aspectj/testing/run/Runner.java b/testing/src/org/aspectj/testing/run/Runner.java new file mode 100644 index 000000000..6cb9e75fa --- /dev/null +++ b/testing/src/org/aspectj/testing/run/Runner.java @@ -0,0 +1,510 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.util.LangUtil; + +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * Run IRun, setting status and invoking listeners + * for simple and nested runs. + * <p> + * This manages baseline IRun status reporting: + * any throwables are caught and reported, and + * the status is started before running and + * (if not already completed) completed after. + * <p> + * This runs any IRunListeners specified directly in the + * run*(..., IRunListener) methods + * as well as any specified indirectly by registering listeners per-type + * in {@link registerListener(Class, IRunListener)} + * <p> + * For correct handling of nested runs, this sets up + * status parent/child relationships. + * It uses the child result object supplied directly in the + * runChild(..., IRunStatus childStatus,..) methods, + * or (if that is null) one obtained from the child IRun itself, + * or (if that is null) a generic IRunStatus. + * <p> + * For IRunIterator, this uses IteratorWrapper to wrap the + * iterator as an IRun. Runner and IteratorWrapper coordinate + * to handle fast-fail (aborting further iteration when an IRun fails). + * The IRunIterator itself may specify fast-fail by returning true + * from {@link IRunIterator#abortOnFailure()}, or clients can + * register IRunIterator by Object or type for fast-failure using + * {@link registerFastFailIterator(IRunIterator)} or + * {@link registerFastFailIterator(Class)}. + * This also ensures that + * {@link IRunIterator#iterationCompleted()} is + * called after the iteration process has completed. + */ +public class Runner { + // XXX need to consider wiring in a logger - sigh + + private static final IMessage FAIL_NORUN + = MessageUtil.fail("Null IRun parameter to Runner.run(IRun..)"); + private static final IMessage FAIL_NORUN_ITERATOR + = MessageUtil.fail("Null IRunterator parameter to Runner.run(IRunterator...)"); + + public Runner() { + } + + /** + * Do the run, setting run status, invoking + * listener, and aborting as necessary. + * If the run is null, the status is + * updated, but the listener is never run. + * If the listener is null, then the runner does a lookup + * for the listeners of this run type. + * Any exceptions thrown by the listener(s) are added + * to the status messages and processing continues. + * unless the status is aborted. + * The status will be completed when this method completes. + * @param run the IRun to run - if null, issue a FAIL message + * to that effect in the result. + * @throws IllegalArgumentException if status is null + * @return boolean result returned from IRun + * or false if IRun did not complete normally + * or status.runResult() if aborted. + */ + /* XXX later permit null status + * If the status is null, this tries to complete + * the run without a status. It ignores exceptions + * from the listeners, but does not catch any from the run. + */ + public boolean run(IRun run, IRunStatus status, + IRunListener listener) { + return run(run, status, listener, (Class) null); + } + + public boolean run(IRun run, IRunStatus status, + IRunListener listener, Class exceptionClass) { + if (!precheck(run, status)) { + return false; + } + RunListeners listeners = getListeners(run, listener); + return runPrivate(run, status, listeners, exceptionClass); + + } + + /** + * Run child of parent, handling interceptor registration, etc. + * @throws IllegalArgumentException if parent or child status is null + */ + public boolean runChild(IRun child, + IRunStatus parentStatus, + IRunStatus childStatus, + IRunListener listener) { + return runChild(child, parentStatus, childStatus, listener, null); + } + + /** + * Run child of parent, handling interceptor registration, etc. + * If the child run is supposed to throw an exception, then pass + * the exception class. + * After this returns, the childStatus is guaranteed to be completed. + * If an unexpected exception is thrown, an ABORT message + * is passed to childStatus. + * @param parentStatus the IRunStatus for the parent - must not be null + * @param childStatus the IRunStatus for the child - default will be created if null + * @param exceptionClass the Class of any expected exception + * @throws IllegalArgumentException if parent status is null + */ + public boolean runChild(IRun child, + IRunStatus parentStatus, + IRunStatus childStatus, + IRunListener listener, + Class exceptionClass) { + if (!precheck(child, parentStatus)) { + return false; + } + if (null == childStatus) { + childStatus = new RunStatus(new MessageHandler(), this); + } + installChildStatus(child, parentStatus, childStatus); + if (!precheck(child, childStatus)) { + return false; + } + RunListeners listeners = getListeners(child, listener); + if (null != listeners) { + try { + listeners.addingChild(parentStatus, childStatus); + } catch (Throwable t) { + String m = "RunListenerI.addingChild(..) exception " + listeners; + parentStatus.handleMessage(MessageUtil.abort(m, t)); // XXX + } + } + boolean result = false; + try { + result = runPrivate(child, childStatus, listeners, exceptionClass); + } finally { + if (!childStatus.isCompleted()) { + childStatus.finish(result ? IRunStatus.PASS : IRunStatus.FAIL); + childStatus.handleMessage(MessageUtil.debug("XXX parent runner set completion")); + } + } + boolean childResult = childStatus.runResult(); + if (childResult != result) { + childStatus.handleMessage(MessageUtil.info("childResult != result=" + result)); + } + return childResult; + } + + public IRunStatus makeChildStatus(IRun run, IRunStatus parent, IMessageHolder handler) { + return installChildStatus(run, parent, new RunStatus(handler, this)); + } + + /** + * Setup the child status before running + * @param run the IRun of the child process (not null) + * @param parent the IRunStatus parent of the child status (not null) + * @param child the IRunStatus to install - if null, a generic one is created + * @return the IRunStatus child status (as passed in or created if null) + */ + public IRunStatus installChildStatus(IRun run, IRunStatus parent, IRunStatus child) { + if (null == parent) { + throw new IllegalArgumentException("null parent"); + } + if (null == child) { + child = new RunStatus(new MessageHandler(), this); + } + child.setIdentifier(run); // XXX leak if ditching run... + parent.addChild(child); + return child; + } + + /** + * Do a run by running all the subrunners provided by the iterator, + * creating a new child status of status for each. + * If the iterator is null, the result is + * updated, but the interceptor is never run. + * @param iterator the IRunteratorI for all the IRun to run + * - if null, abort (if not completed) or message to status. + * @throws IllegalArgumentException if status is null + */ + public boolean runIterator(IRunIterator iterator, IRunStatus status, + IRunListener listener) { + LangUtil.throwIaxIfNull(status, "status"); + if (status.aborted()) { + return status.runResult(); + } + if (null == iterator) { + if (!status.isCompleted()) { + status.abort(IRunStatus.ABORT_NORUN); + } else { + status.handleMessage(FAIL_NORUN); + } + return false; + } + IRun wrapped = wrap(iterator, listener); + return run(wrapped, status, listener); + } + + /** + * Signal whether to abort on failure for this run and iterator, + * based on the iterator and any fast-fail registrations. + * @param iterator the IRunIterator to stop running if this returns true + * @param run the IRun that failed + * @return true to halt further iterations + */ + private boolean abortOnFailure(IRunIterator iterator, IRun run) { + return ((null == iterator) || iterator.abortOnFailure()); // XxX not complete + } + + + /** + * Tell Runner to stop iterating over IRun for an IRunIterator + * if any IRun.run(IRunStatus) fails. + * This overrides a false result from IRunIterator.abortOnFailure(). + * @param iterator the IRunIterator to fast-fail - ignored if null. + * @see IRunIterator#abortOnFailure() + */ + public void registerFastFailIterator(IRunIterator iterator) { // XXX unimplemented + throw new UnsupportedOperationException("ignoring " + iterator); + } + + /** + * Tell Runner to stop iterating over IRun for an IRunIterator + * if any IRun.run(IRunStatus) fails, + * if the IRunIterator is assignable to iteratorType. + * This overrides a false result from IRunIterator.abortOnFailure(). + * @param iteratorType the IRunIterator Class to fast-fail + * - ignored if null, must be assignable to IRunIterator + * @see IRunIterator#abortOnFailure() + */ + public void registerFastFailIteratorTypes(Class iteratorType) { // XXX unimplemented + throw new UnsupportedOperationException("ignoring " + iteratorType); + } + + /** + * Register a run listener for any run assignable to type. + * @throws IllegalArgumentException if either is null + */ + public void registerListener(Class type, IRunListener listener) { // XXX unregister + if (null == type) { + throw new IllegalArgumentException("null type"); + } + if (null == listener) { + throw new IllegalArgumentException("null listener"); + } + ClassListeners.addListener(type, listener); + } + + /** + * Wrap this IRunIterator. + * This wrapper takes care of calling + * <code>iterator.iterationCompleted()</code> when done, so + * after running this, clients should not invoker IRunIterator + * methods on this iterator. + * @return the iterator wrapped as a single IRun + */ + public IRun wrap(IRunIterator iterator, IRunListener listener) { + LangUtil.throwIaxIfNull(iterator, "iterator"); + return new IteratorWrapper(this, iterator, listener); + } + + /** + * This gets any listeners registered for the run + * based on the class of the run (or of the wrapped iterator, + * if the run is an IteratorWrapper). + * @return a listener with all registered listener and parm, + * or null if parm is null and there are no listeners + */ + protected RunListeners getListeners(IRun run, IRunListener listener) { + if (null == run) { + throw new IllegalArgumentException("null run"); + } + Class runClass = run.getClass(); + if (runClass == IteratorWrapper.class) { + IRunIterator it = ((IteratorWrapper) run).iterator; + if (null != it) { + runClass = it.getClass(); + } + // fyi expecting: listener == ((IteratorWrapper) run).listener + } + RunListeners listeners = ClassListeners.getListeners(runClass); + if (null != listener) { + listeners.addListener(listener); + } + return listeners; // XXX implement registration + } + + /** check status and run before running */ + private boolean precheck(IRun run, IRunStatus status) { + if (null == status) { + throw new IllegalArgumentException("null status"); + } + // check abort request coming in + if (status.aborted()) { + return status.runResult(); + } else if (status.isCompleted()) { + throw new IllegalStateException("status completed before starting"); + } + + if (!status.started()) { + status.start(); + } + return true; + } + + /** This assumes precheck has happened and listeners have been obtained */ + private boolean runPrivate(IRun run, IRunStatus status, + RunListeners listeners, Class exceptionClass) { + IRunListener listener = listeners; + if (null != listener) { + try { + listener.runStarting(status); + } catch (Throwable t) { + String m = listener + " RunListenerI.runStarting(..) exception"; + IMessage mssg = new Message(m, IMessage.WARNING, t, null); + // XXX need IMessage.EXCEPTION - WARNING is ambiguous + status.handleMessage(mssg); + } + } + // listener can set abort request + if (status.aborted()) { + return status.runResult(); + } + if (null == run) { + if (!status.isCompleted()) { + status.abort(IRunStatus.ABORT_NORUN); + } else { + status.handleMessage(MessageUtil.FAIL_INCOMPLETE); + } + return false; + } + + boolean result = false; + try { + result = run.run(status); + if (!status.isCompleted()) { + status.finish(result?IRunStatus.PASS: IRunStatus.FAIL); + } + } catch (Throwable thrown) { + if (!status.isCompleted()) { + status.thrown(thrown); + } else { + String m = "run status completed but run threw exception"; + status.handleMessage(MessageUtil.abort(m, thrown)); + result = false; + } + } finally { + if (!status.isCompleted()) { + // XXX should never get here... - hides errors to set result + status.finish(result ? IRunStatus.PASS : IRunStatus.FAIL); + if (!status.isCompleted()) { + status.handleMessage(MessageUtil.debug("child set of status failed")); + } + } + } + + + try { + if ((null != listener) && !status.aborted()) { + listener.runCompleted(status); + } + } catch (Throwable t) { + String m = listener + " RunListenerI.runCompleted(..) exception"; + status.handleMessage(MessageUtil.abort(m, t)); + } + return result; + } + + //---------------------------------- nested classes + /** + * Wrap an IRunIterator as a IRun, coordinating + * fast-fail IRunIterator and Runner + */ + public static class IteratorWrapper implements IRun { + final Runner runner; + public final IRunIterator iterator; + final IRunListener listener; + + public IteratorWrapper(Runner runner, IRunIterator iterator, IRunListener listener) { + LangUtil.throwIaxIfNull(iterator, "iterator"); + LangUtil.throwIaxIfNull(runner, "runner"); + this.runner = runner; + this.iterator = iterator; + this.listener = listener; + } + + /** @return null */ + public IRunStatus makeStatus() { + return null; + } + + /** @return true unless one failed */ + public boolean run(IRunStatus status) { + boolean result = true; + try { + int i = 0; + int numMessages = status.numMessages(IMessage.FAIL, IMessageHolder.ORGREATER); + while (iterator.hasNextRun()) { + IRun run = iterator.nextRun((IMessageHandler) status, runner); + if (null == run) { + MessageUtil.debug(status, "null run " + i + " from " + iterator); + continue; + } + + int newMessages = status.numMessages(IMessage.FAIL, IMessageHolder.ORGREATER); + if (newMessages > numMessages) { + numMessages = newMessages; + String m = "run " + i + " from " + iterator + + " not invoked, due to fail(+) message(s) "; + MessageUtil.debug(status, m); + continue; + } + RunStatus childStatus = null; // let runChild create + if (!runner.runChild(run, status, childStatus, listener)) { + if (result) { + result = false; + } + if (iterator.abortOnFailure() + || runner.abortOnFailure(iterator, run)) { + break; + } + } + i++; + } + return result; + } finally { + iterator.iterationCompleted(); + } + } + + /** @return iterator, clipped to 75 char */ + public String toString() { + String s = "" + iterator; + if (s.length() > 75) { + s = s.substring(0, 72) + "..."; + } + return s; + } + } + + /** per-class collection of IRun */ + static class ClassListeners extends RunListeners { + private static final Hashtable known = new Hashtable(); + + static RunListeners getListeners(Class c) { // XXX costly and stupid + Enumeration keys = known.keys(); + RunListeners many = new RunListeners(); + while (keys.hasMoreElements()) { + Class kc = (Class) keys.nextElement(); + if (kc.isAssignableFrom(c)) { + many.addListener((IRunListener) known.get(kc)); + } + } + return many; + } + + private static RunListeners makeListeners(Class c) { + RunListeners result = (ClassListeners) known.get(c); + if (null == result) { + result = new ClassListeners(c); + known.put(c, result); + } + return result; + } + + static void addListener(Class c, IRunListener listener) { + if (null == listener) { + throw new IllegalArgumentException("null listener"); + } + if (null == c) { + c = IRun.class; + } + makeListeners(c).addListener(listener); + } + + Class clazz; + + ClassListeners(Class clazz) { + this.clazz = clazz; + } + + public String toString() { + return clazz.getName() + " ClassListeners: " + super.toString(); + } + } +} diff --git a/testing/src/org/aspectj/testing/run/WrappedRunIterator.java b/testing/src/org/aspectj/testing/run/WrappedRunIterator.java new file mode 100644 index 000000000..e8c8bc064 --- /dev/null +++ b/testing/src/org/aspectj/testing/run/WrappedRunIterator.java @@ -0,0 +1,72 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.run; + +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.util.LangUtil; + +/** Adapt IRun to IRunIterator in a way that can be detected for hoisting. */ +public class WrappedRunIterator implements IRunIterator { + protected final Object id; + protected IRun run; + + /** + * @param id the Object used for toString(), if set + * @param run the IRun returned from the first call to + * nextRun(IMessageHandler handler, Runner runner) + */ + public WrappedRunIterator(Object id, IRun run) { + LangUtil.throwIaxIfNull(run, "run"); + this.id = id; + this.run = run; + } + + /** @return false always - we run only once anyway */ + public boolean abortOnFailure() { + return false; + } + + /** + * @return true until nextRun() completes + * @see org.aspectj.testing.run.RunIteratorI#hasNextRun() + */ + public boolean hasNextRun() { + return (null != run); + } + + /** + * @see org.aspectj.testing.run.IRunIterator#iterationCompleted() + */ + public void iterationCompleted() { + } + + /** + * @return the only IRun we have, and null thereafter + * @see org.aspectj.testing.run.RunIteratorI#nextRun(IMessageHandler, Runner) + */ + public IRun nextRun(IMessageHandler handler, Runner runner) { + if (null == run) { + return null; + } else { + IRun result = run; + run = null; + return result; + } + } + + /** @return name */ + public String toString() { + return (null == id ? run : id).toString(); + } +} diff --git a/testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java b/testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java new file mode 100644 index 000000000..5ef0ecf34 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/AccumulatingFileFilter.java @@ -0,0 +1,53 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import java.io.File; +import java.util.Vector; + +/** + * A FileFilter that accumulates the results when called if they exist. + * Subclasses override accumulate to determine whether it should be + * accumulated. + */ +public class AccumulatingFileFilter extends ValidFileFilter { + Vector files = new Vector(); + public final boolean accept(File f) { + if (super.accept(f) && (accumulate(f))) { + files.add(f); + } + return true; + } + + /** + * This implementation accumulates everything. + * Subclasses should override to implement filter + * @param file a File guaranteed to exist + * @return true if file should be accumulated. + */ + public boolean accumulate(File f) { + return true; + } + /** + * @return list of files currently accumulated + */ + public File[] getFiles() { + int numFiles = files.size(); + File[] result = new File[numFiles]; + if (0 < numFiles) { + files.copyInto(result); + } + return result; + } +} diff --git a/testing/src/org/aspectj/testing/util/BridgeUtil.java b/testing/src/org/aspectj/testing/util/BridgeUtil.java new file mode 100644 index 000000000..8e4b60fca --- /dev/null +++ b/testing/src/org/aspectj/testing/util/BridgeUtil.java @@ -0,0 +1,439 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.SourceLocation; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.RunValidator; +import org.aspectj.util.FileUtil; +import org.aspectj.util.LineReader; + +import java.io.File; +import java.util.Comparator; + +/** + * + */ +public class BridgeUtil { + + private static final String INDENT = " "; + + /** result value when writeMessage is passed null */ + private static final String NULL_MESSAGE_OUTPUT = "<null message output>"; + + /** result value when readMessage is passed null */ + private static final IMessage NULL_MESSAGE_INPUT = null; + + private static final String KIND_DELIM = ": \""; + private static final String MESSAGE_DELIM = "\" - "; + + + public static ISourceLocation makeSourceLocation(LineReader reader) { + LangUtil.throwIaxIfNull(reader, "reader"); + int line = reader.getLineNumber(); + + return new SourceLocation(reader.getFile(), line, line, 0); + } + + + /** + * Method readSourceLocation. + * @param sourceLocStr + * @return ISourceLocation + */ + private static ISourceLocation readSourceLocation(String sourceLocStr) { + return BridgeUtil.makeSourceLocation(sourceLocStr); + } + public static IMessage makeMessage(String message, IMessage.Kind kind, + Throwable thrown, LineReader reader) { + ISourceLocation sl = (null == reader ? null : MessageUtil.makeSourceLocation(reader)); + if (null == kind) kind = IMessage.INFO; + return new Message(message, kind, thrown, sl); + } + + /** + * Read a message from a string written by writeMessage(IMessage). + * Does not handle exceptions at all or source location well. XXX + * @param message the String representation of a message + * @return IMessage + */ + public static IMessage readMessage(String message) { + if (null == message) { + return NULL_MESSAGE_INPUT; + } + if (NULL_MESSAGE_OUTPUT.equals(message)) { + return null; + } + int kindEnd = message.indexOf(KIND_DELIM); + int messageEnd = message.indexOf(MESSAGE_DELIM); + int messageStart = kindEnd+KIND_DELIM.length(); + int sourceLocStart = messageEnd+MESSAGE_DELIM.length(); + String kindStr = message.substring(0, kindEnd); + String text = message.substring(messageStart, messageEnd); + String sourceLocStr = message.substring(sourceLocStart); + IMessage.Kind kind = MessageUtil.getKind(kindStr); + ISourceLocation loc = readSourceLocation(sourceLocStr); + return new Message(text, kind, null, loc); + } + + + /** + * Write a message to a string to be read by readMessage(String) + * @param message the String representation of a message + * @return IMessage + */ + public static String writeMessage(IMessage message) { + if (null == message) { + return NULL_MESSAGE_OUTPUT; + } + return message.getKind() + + KIND_DELIM + + message.getMessage() + + MESSAGE_DELIM + + message.getISourceLocation(); // XXX implement + } + + + public static class Comparators { + /** + * This is a weak ordering + * so use only for sorts, not to maintain maps. + * It returns 0 if one file path is a suffix of the other + * or a case-insensitive string comparison otherwise. + */ + public static final Comparator WEAK_File = new Comparator() { + public int compare(Object o1, Object o2) { + File one = (File) o1; + File two = (File) o2; + if (null == one) { + return (null == two ? 0 : 1); + } else if (null == two) { + return -1; + } else if (one == two) { + return 0; + } else { + String s1 = FileUtil.weakNormalize(one.getPath()); + String s2 = FileUtil.weakNormalize(two.getPath()); + if (s1.endsWith(s2) || s2.endsWith(s1)) { + return 0; + } else { + return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); + } + } + }}; + /** + * Ordering ignores filename, + * so use only for sorts, not to maintain maps + */ + public static final Comparator WEAK_ISourceLocation = new Comparator() { + public int compare(Object o1, Object o2) { + ISourceLocation one = (ISourceLocation) o1; + ISourceLocation two = (ISourceLocation) o2; + if (null == one) { + return (null == two ? 0 : 1); + } else if (null == two) { + return -1; + } else if (one == two) { + return 0; + } else { // XXX start with File to make strong + int i1 = one.getLine(); + int i2 = two.getLine(); + return i1 - i2; +// // defer subcomparisons until messages complete +// if (i1 != i2) { +// return i1 - i2; +// } else { +// i1 = one.getColumn(); +// i2 = two.getColumn(); +// if (i1 != i2) { +// return i1 - i2; +// } else { +// i1 = one.getEndLine(); +// i2 = two.getEndLine(); +// return i1 - i2; +// } +// } + } + }}; + + /** + * Ordering ignores filename and message + * so use only for sorts, not to maintain maps + */ + public static final Comparator WEAK_IMessage = new Comparator() { + public int compare(Object o1, Object o2) { + IMessage one = (IMessage) o1; + IMessage two = (IMessage) o2; + if (null == one) { + return (null == two ? 0 : 1); + } else if (null == two) { + return -1; + } else if (one == two) { + return 0; + } else { + IMessage.Kind kind1 = one.getKind(); + IMessage.Kind kind2= two.getKind(); + if (kind1 != kind2) { + return IMessage.Kind.COMPARATOR.compare(kind1, kind2); + } else { + ISourceLocation sl1 = one.getISourceLocation(); + ISourceLocation sl2 = two.getISourceLocation(); + return WEAK_ISourceLocation.compare(sl1, sl2); + } + } + }}; + + } + + public static SourceLocation makeSourceLocation(String input) { // XXX only for testing, not production + return makeSourceLocation(input, (File) null); + } + + public static SourceLocation makeSourceLocation(String input, String path) { + return makeSourceLocation(input, (null == path ? null : new File(path))); + } + + /** attempt to create a source location from the input */ + public static SourceLocation makeSourceLocation(String input, File defaultFile) { + /* + * Forms interpreted: + * # - line + * file - file + * file:# - file, line + * #:# - if defaultFile is not null, then file, line, column + * file:#:# - file, line, column + * file:#:#:? - file, line, column, message + */ + SourceLocation result = null; + if ((null == input) || (0 == input.length())) { + if (null == defaultFile) { + return null; + } else { + return new SourceLocation(defaultFile, 0, 0, 0); + } + } + input = input.trim(); + + String path = null; + int line = 0; + int endLine = 0; + int column = 0; + String message = null; + + // first try line only + line = convert(input); + if (-1 != line) { + return new SourceLocation(defaultFile, line, line, 0); + } + + // if not a line - must be > 2 characters + if (3 > input.length()) { + return null; // throw new IllegalArgumentException("too short: " + input); + } + final String fixTag = "FIXFIX"; + if (input.charAt(1) == ':') { // windows drive ambiguates ":" file:line:col separator + input = fixTag + input.substring(0,1) + input.substring(2); + } + // expecting max: path:line:column:message + // if 1 colon, delimits line (to second colon or end of string) + // if 2 colon, delimits column (to third colon or end of string) + // if 3 colon, delimits column (to fourth colon or end of string) + // todo: use this instead?? + final int colon1 = input.indexOf(":",2); // 2 to get past windows drives... + final int colon2 = (-1 == colon1?-1:input.indexOf(":", colon1+1)); + final int colon3 = (-1 == colon2?-1:input.indexOf(":", colon2+1)); + String s; + if (-1 == colon1) { // no colon; only path (number handled above) + path = input; + } else { // 1+ colon => file:line // XXX later or line:column + path = input.substring(0, colon1); + s = input.substring(colon1+1,(-1!=colon2?colon2:input.length())).trim(); + line = convert(s); + if (-1 == line) { + return null; + //line = "expecting line(number) at \"" + line + "\" in " + input; + //throw new IllegalArgumentException(line); + } else if (-1 != colon2) { // 2+ colon => col + s = input.substring(colon2+1,(-1!=colon3?colon3:input.length())).trim(); + column = convert(s); + if (-1 == column) { + return null; + //col = "expecting col(number) at \"" + col + "\" in " + input; + //throw new IllegalArgumentException(col); + } else if (-1 != colon3) { // 3 colon => message + message = input.substring(colon3+1); // do not trim message + } + } + } + + if (path.startsWith(fixTag)) { + int len = fixTag.length(); + path = path.substring(len, 1+len) + ":" + + path.substring(1+len); + } + if ((endLine == 0) && (line != 0)) { + endLine = line; + } + // XXX removed message/comment + return new SourceLocation(new File(path), line, endLine, column); + } + + // XXX reconsider convert if used in production code + /** + * Convert String to int using ascii and optionally + * tolerating text + * @param s the String to convert + * @param permitText if true, pick a sequence of numbers + * within a possibly non-numeric String + * @param last if permitText, then if this is true the + * last sequence is used - otherwise the first is used + * XXX only default u.s. encodings.. + * @return -1 or value if a valid, totally-numeric positive string 0..MAX_WIDTH + */ + private static int convert(String s) { + return convert(s, false, false); + } + + // XXX reconsider convert if used in production code + /** + * Convert String to int using ascii and optionally + * tolerating text + * @param s the String to convert + * @param permitText if true, pick a sequence of numbers + * within a possibly non-numeric String + * @param last if permitText, then if this is true the + * last sequence is used - otherwise the first is used + * XXX only default u.s. encodings.. + * @return -1 or value if a valid, positive string 0..MAX_WIDTH + */ + private static int convert(String s, boolean permitText, + boolean first) { + int result = -1; + int last = -1; + int max = s.length(); + boolean reading = false; + for (int i = 0; i < max; i++) { + char c = s.charAt(i); + if ((c >= '0') && (c <= '9')) { + if (-1 == result) { // prefix loop + result = 0; + reading = true; + } + result = ((result * 10) + (c - '0')); + } else if (!permitText) { + return -1; + } else if (reading) { // from numeric -> non-numeric + if (first) { + return result; + } else { + last = result; + } + reading = false; + } + } + if (permitText && !first && (-1 != last) && (-1 == result)) { + result = last; + } + return ((0 < result) && (result < ISourceLocation.MAX_LINE) ? result : -1); + } + + private BridgeUtil() {} + + /** @return String for status header, counting children passed/failed */ + public static String childString(IRunStatus runStatus, int numSkips, int numIncomplete) { + if (null == runStatus) { + return "((RunStatus) null)"; + } + if (0 > numSkips) { + numSkips = 0; + } + if (0 > numIncomplete) { + numIncomplete = 0; + } + StringBuffer sb = new StringBuffer(); + if (RunValidator.NORMAL.runPassed(runStatus)) { + sb.append("PASS "); + } else { + sb.append("FAIL "); + } + Object id = runStatus.getIdentifier(); + if (null != id) { + sb.append(id.toString() + " "); + } + IRunStatus[] children = runStatus.getChildren(); + final int numChildren = (null == children ? 0 : children.length); + final int numTests = numIncomplete + numChildren + numSkips; + int numFails = 0; + if (!LangUtil.isEmpty(children)) { + for (int i = 0; i < children.length; i++) { + if (!RunValidator.NORMAL.runPassed(children[i])) { + numFails++; + } + } + } + final int numPass = children.length - numFails; + sb.append(numTests + " tests"); + if (0 < numTests) { + sb.append(" ("); + } + if (0 < numSkips) { + sb.append(numSkips + " skipped"); + if (0 < (numFails + numPass + numIncomplete)) { + sb.append(", "); + } + } + if (0 < numIncomplete) { + sb.append(numIncomplete + " incomplete"); + if (0 < (numFails + numPass)) { + sb.append(", "); + } + } + if (0 < numFails) { + sb.append(numFails + " failed"); + if (0 < numPass) { + sb.append(", "); + } + } + if (0 < numPass) { + sb.append(numPass + " passed)"); + } else if (0 < numTests) { + sb.append(")"); + } + return sb.toString().trim(); + } + + /** @return String for status header */ + public static String toShortString(IRunStatus runStatus) { + if (null == runStatus) { + return "((RunStatus) null)"; + } + StringBuffer sb = new StringBuffer(); + if (RunValidator.NORMAL.runPassed(runStatus)) { + sb.append("PASS "); + } else { + sb.append("FAIL "); + } + Object id = runStatus.getIdentifier(); + if (null != id) { + sb.append(id.toString() + " "); + } + sb.append(MessageUtil.renderCounts(runStatus)); + return sb.toString().trim(); + } + +} diff --git a/testing/src/org/aspectj/testing/util/CollectorFileFilter.java b/testing/src/org/aspectj/testing/util/CollectorFileFilter.java new file mode 100644 index 000000000..ef268bcaa --- /dev/null +++ b/testing/src/org/aspectj/testing/util/CollectorFileFilter.java @@ -0,0 +1,88 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Wrap FileFilter to collect any accepted + */ +public class CollectorFileFilter implements FileFilter { + /** returned from getFiles() when there are no files to get */ + public static final List EMPTY + = Collections.unmodifiableList(new ArrayList(0)); + + /** used for collecting filters */ + protected ArrayList files; + + /** filter delegate - may be null */ + protected final FileFilter filter; + + /** return false from accept only when !alwaysTrue + * and filter is null or fails + */ + protected final boolean alwaysTrue; + + /** this(null, true) */ + public CollectorFileFilter() { + this(null, true); + } + + /* + * @param filter the FileFilter delegate - may be null + * @param alwaysTrue return false from accept only when !alwaysTrue + * and filter is null or fails + */ + public CollectorFileFilter(FileFilter filter, boolean alwaysTrue){ + this.filter = filter; + this.alwaysTrue = alwaysTrue; + } + + /** + * Accept file into collection if filter is null or passes. + * @return false only when !alwaysTrue and filter fails. + */ + public boolean accept(File f) { + if ((null == filter) || filter.accept(f)) { + add(f); + return true; + } + return alwaysTrue; + } + + /** gather files */ + protected synchronized void add(File f) { + if (null != f) { + if (null == files) { + files = new ArrayList(); + } + files.add(f); + } + } + + /** + * return clone of gathered-files + * @return EMPTY if no files or a clone of the collection otherwise + */ + public synchronized List getFiles() { + if ((null == files) || (0 == files.size())) { + return EMPTY; + } + return (List) files.clone(); + } +} diff --git a/testing/src/org/aspectj/testing/util/Diffs.java b/testing/src/org/aspectj/testing/util/Diffs.java new file mode 100644 index 000000000..1adf40924 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/Diffs.java @@ -0,0 +1,103 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.MessageUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** result struct for expected/actual diffs for Collection */ +public class Diffs { + + // XXX List -> Collection b/c comparator orders + public static final Diffs NONE + = new Diffs("NONE", Collections.EMPTY_LIST, Collections.EMPTY_LIST); + + /** name of the thing being diffed - used only for reporting */ + public final String label; + + /** immutable List */ + public final List missing; + + /** immutable List */ + public final List unexpected; + + /** true if there are any missing or unexpected */ + public final boolean different; + + private Diffs(String label, List missing, List unexpected) { + this.label = label; + this.missing = missing; + this.unexpected = unexpected; + different = ((0 != this.missing.size()) + || (0 != this.unexpected.size())); + } + + public Diffs(String label, List expected, List actual, Comparator comparator) { + label = label.trim(); + if (null == label) { + label = ": "; + } else if (!label.endsWith(":")) { + label += ": "; + } + this.label = " " + label; + ArrayList miss = new ArrayList(); + ArrayList unexpect = new ArrayList(); + + org.aspectj.testing.util.LangUtil.makeSoftDiffs(expected, actual, miss, unexpect, comparator); + missing = Collections.unmodifiableList(miss); + unexpected = Collections.unmodifiableList(unexpect); + different = ((0 != this.missing.size()) + || (0 != this.unexpected.size())); + } + + /** + * Report missing and extra items to handler. + * For each item in missing or unexpected, this creates a {kind} IMessage with + * the text "{missing|unexpected} {label}: {message}" + * where {message} is the result of + * <code>MessageUtil.renderMessage(IMessage)</code>. + * @param handler where the messages go - not null + * @param kind the kind of message to construct - not null + * @param label the prefix for the message text - if null, "" used + * @see MessageUtil#renderMessage(IMessage) + */ + public void report(IMessageHandler handler, IMessage.Kind kind) { + LangUtil.throwIaxIfNull(handler, "handler"); + LangUtil.throwIaxIfNull(kind, "kind"); + if (different) { + for (Iterator iter = missing.iterator(); iter.hasNext();) { + String s = MessageUtil.renderMessage((IMessage) iter.next()); + MessageUtil.fail(handler, "missing " + label + s); + } + for (Iterator iter = unexpected.iterator(); iter.hasNext();) { + String s = MessageUtil.renderMessage((IMessage) iter.next()); + MessageUtil.fail(handler, "unexpected " + label + s); + } + } + } + + /** @return "{label}: (unexpected={#}, missing={#})" */ + public String toString() { + return label + "(unexpected=" + unexpected.size() + + ", missing=" + missing.size() + ")"; + } +} + diff --git a/testing/src/org/aspectj/testing/util/FileUtil.java b/testing/src/org/aspectj/testing/util/FileUtil.java new file mode 100644 index 000000000..2caa5e203 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/FileUtil.java @@ -0,0 +1,745 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringBufferInputStream; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Vector; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/** + * misc file utilities + */ +public class FileUtil { + + /** default filename if URL has none (i.e., a directory URL): index.html */ + public static final String DEFAULT_URL_FILENAME = "index.html"; + + /** + * @param args the String[] + * <code>{ "-copy", "-srcFile" | "-srcUrl", {src}, "-destFile", {destFile} }</code> + */ + public static void main (String[] args) { + if (null == args) return; + for (int i = 0; (i+4) < args.length; i++) { + if ("-copy".equals(args[i])) { + String arg = args[++i]; + String src = null; + String destFile = null; + boolean srcIsFile = ("-srcFile".equals(arg)); + if (srcIsFile) { + src = args[++i]; + } else if ("-srcUrl".equals(arg)) { + src = args[++i]; + } + if ((null != src) && ("-destFile".equals(args[++i]))) { + destFile = args[++i]; + StringBuffer errs = new StringBuffer(); + if (srcIsFile) { + copyFile(new File(src), new File(destFile), errs); + } else { + URL url = null; + try { url = new URL(src) ; } + catch (MalformedURLException e) { render(e, errs); } + if (null != url) { + copyURL(url, new File(destFile), errs); + } + } + if (0 < errs.length()) { + System.err.println("Error copying " + src + " to " + destFile); + System.err.println(errs.toString()); + + } + } + } // ("-copy".equals(args[i])){ + } + } // end of main () + + /** + * Generate a list of missing and extra files by comparison to a + * timestamp, optionally excluding certain files. + * This is a call to select all files after a given time: + * + * <pre>Diffs d = dirDiffs(dir, givenTime, null, null, null);</pre> + * + * Given files + * <pre>classes/Foo.class + * classes/bar/Bash.class + * classes/Old.class + * classes/one/Unexpected.class + * classes/start.gif</pre> + * where only Old.class predated startTime, this is a call that + * reports "one/Unexpected.class" as unexpected and "Foo" + * as missing: + * <pre>String requireSuffix = ".class"; + * String[] expectedPaths = new String[] { "Foo", "bar/Bas" }; + * File file = new File("classes"); + * Diffs d = dirDiffs(dir, startTime, requireSuffix,expectedPaths, true);</pre> + * + * @param label the String to use for the Diffs label + * @param dir the File for the dir to search + * @param startTime collect files modified after this time + * (ignored if less than 0) + * @param requireSuffix ignore all actual files without this suffix + * (ignored if null) + * @param expectedPaths paths (relative to dir) of the expected files + * (if null, none expected) + * @param acceptFilePrefix if true, then accept a file which + * differs from an expected file name only by a suffix + * (which need not begin with "."). + */ + public static Diffs dirDiffs( // XXX too complicated, weak prefix checking + final String label, + final File dir, + final long startTime, + final String requiredSuffix, + final String[] expectedPaths, + final boolean acceptFilePrefix) { + + LangUtil.throwIaxIfNull(dir, "dir"); + final boolean checkExpected = !LangUtil.isEmpty(expectedPaths); + + // normalize sources to ignore + final ArrayList expected = (!checkExpected ? null : new ArrayList()); + if (checkExpected) { + for (int i = 0; i < expectedPaths.length; i++) { + String srcPath = expectedPaths[i]; + if (!LangUtil.isEmpty(srcPath)) { + expected.add(org.aspectj.util.FileUtil.weakNormalize(srcPath)); + } + } + } + + // gather, normalize paths changed + FileFilter touchedCollector = new FileFilter() { + /** + * For files complying with time and suffix rules, + * return true (accumulate - unexpected) + * unless they match expected files, + * (deleting any matches from sources + * so the remainder is missing). + * @return true for unexpected files after date */ + public boolean accept(File file) { + if (file.isFile() + && ((0 > startTime) + || (startTime < file.lastModified()))) { + String path = file.getPath(); + if ((null == requiredSuffix) || path.endsWith(requiredSuffix)) { + path = org.aspectj.util.FileUtil.weakNormalize(path); + if (checkExpected) { + if (!acceptFilePrefix) { + // File.equals(..) does lexical compare + if (expected.contains(path)) { + expected.remove(path); + // found - do not add to unexpected + return false; + } + } else { + for (Iterator iter = expected.iterator(); + iter.hasNext(); + ) { + String exp = (String) iter.next(); + if (path.startsWith(exp)) { + String suffix = path.substring(exp.length()); + if (-1 == suffix.indexOf("/")) { // normalized... + expected.remove(path); + // found - do not add to unexpected + return false; + } + } + } + } + } + // add if is file, right time, and have or don't need suffix + return true; + } + } + // skip if not file or not right time + return false; + } + }; + ArrayList unexp = new ArrayList(); + unexp.addAll(Arrays.asList(dir.listFiles(touchedCollector))); + + // report any unexpected changes + return new Diffs(label, expected, unexp, String.CASE_INSENSITIVE_ORDER); + } + + + /** + * Visit the entries in a zip file, halting when visitor balks. + * Errors are silently ignored. + * @throws IllegalArgumentException if zipfile or visitor is null + */ + public static void visitZipEntries(ZipFile zipfile, StringVisitor visitor) { + visitZipEntries(zipfile, visitor, (StringBuffer) null); + } + + /** + * Visit the entries in a zip file, halting when visitor balks. + * Errors are reported in errs, if not null. + * @throws IllegalArgumentException if zipfile or visitor is null + */ + public static void visitZipEntries(ZipFile zipfile, StringVisitor visitor, + StringBuffer errs) { + if (null == zipfile) throw new IllegalArgumentException("null zipfile"); + if (null == visitor) throw new IllegalArgumentException("null visitor"); + int index = 0; + try { + Enumeration enum = zipfile.entries(); + while (enum.hasMoreElements()) { + ZipEntry entry = (ZipEntry) enum.nextElement(); + index++; + if (! visitor.accept(entry.getName())) { + break; + } + } + } catch (Throwable e) { + if (null != errs) { + errs.append("FileUtil.visitZipEntries error accessing entry " + index + + ": " + e.getMessage()); + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + errs.append(sw.toString()); + } + } finally { + if (null != zipfile) { + try { zipfile.close(); } + catch (IOException x) {} // ignore + } + } + } + + /** + * descend filesystem tree, invoking FileFilter.accept() on files. + * E.g., To list files from current directory: + * <code><pre>descendFileTree(new File("."), new FileFilter() { + * public boolean accept(File f){ + * System.out.println(f.getAbsolutePath()); + * return true; + * }});</code></pre> + * @param file root/starting point. If a file, the only one visited. + * @param filter supplies accept(File) routine + */ + public static void descendFileTree(File file, FileFilter filter) { + descendFileTree(file, filter, false); + } + + /** + * Descend filesystem tree, invoking FileFilter.accept() on files + * and, if userRecursion, on dirs. If userRecursion, accept() must + * call descendFileTree() again to recurse down directories. + * This calls fileFilter.accept(File) on all files before doing any dirs. + * E.g., To list only files from Unix root: + * <code><pre>descendFileTree(new File("/"), new FileFilter() { + * public boolean run(File f){ + * System.out.println(f.getAbsolutePath()); + * return true; + * }}, false);</code></pre> + * To list files/dir from root using user recursion: + * <code><pre>descendFileTree(new File("/"), new FileFilter() { + * public boolean run(File f){ + * System.out.println(f.getAbsolutePath()); + * if (f.isDirectory() && (-1 == f.getName().indexOf("CVS"))) + * return descendFileTree(f, this, true); + * return true; + * }}, true);</code></pre> + * @param file root/starting point. If a file, the only one visited. + * @param filter supplies boolean accept(File) method + * @param userRecursion - if true, do accept() on dirs; else, recurse + * @return false if any fileFilter.accept(File) did. + * @throws IllegalArgumentException if file or fileFilter is null + */ + public static boolean descendFileTree(File file, FileFilter fileFilter, + boolean userRecursion) { + if (null == file) {throw new IllegalArgumentException("parm File"); } + if (null == fileFilter){throw new IllegalArgumentException("parm FileFilter");} + + if (!file.isDirectory()) { + return fileFilter.accept(file); + } else if (file.canRead()) { + // go through files first + File[] files = file.listFiles(ValidFileFilter.FILE_EXISTS); + if (null != files) { + for (int i = 0; i < files.length; i++) { + if (!fileFilter.accept(files[i])) { + return false; + } + } + } + // now recurse to handle directories + File[] dirs = file.listFiles(ValidFileFilter.DIR_EXISTS); + if (null != dirs) { + for (int i = 0; i < dirs.length; i++) { + if (userRecursion) { + if (!fileFilter.accept(dirs[i])) { + return false; + } + } else { + if (!descendFileTree(dirs[i], fileFilter,userRecursion)) { + return false; + } + } + } + } + } // readable directory (ignore unreadable ones) + return true; + } // descendFiles + + /** + * Return the names of all files below a directory. + * If file is a directory, then all files under the directory + * are returned. If file is absolute or relative, all the files are. + * If file is a zip or jar file, then all entries in the zip or jar + * are listed. Entries inside those jarfiles/zipfiles are not listed. + * There are no guarantees about ordering. + * @param dir the File to list for + * @param results the Collection to use for the results (may be null) + * @throws IllegalArgumentException if null == dir + * @return a Collection of String of paths, including paths inside jars + */ + public static Collection directoryToString(File dir, Collection results) { + if (null == dir) throw new IllegalArgumentException("null dir"); + final Collection result = (results != null? results : new Vector()); + if (isZipFile(dir)) { + zipFileToString(dir, result); + } else if (!dir.isDirectory()) { + throw new IllegalArgumentException("not a dir: " + dir); + } else { + AccumulatingFileFilter acFilter = new AccumulatingFileFilter() { + public boolean accumulate(File file) { + String name = file.getPath(); + result.add(name); + if (isZipFile(file)) { + zipFileToString(file, result); + } + return true; + } + }; + descendFileTree(dir, acFilter, false); + } + return result; + } // directoryToString + + /** + * Render as String the entries in a zip or jar file, + * converting each to String beforehand (as jarpath!jarentry) + * applying policies for whitespace, etc. + * @param file the File to enumerate ZipEntry for + * @param results the Colection to use to return the FileLine - may be null + * @return FileLines with string as text and + * canonical as string modified by any canonicalizing policies. + */ + public static Collection zipFileToString(final File zipfile, Collection results) { + Collection result = (results != null ? results : new Vector()); + ZipFile zip = null; + try { + zip = new ZipFile(zipfile); // ZipFile.OPEN_READ| ZipFile.OPEN_DELETE); delete is 1.3 only + Enumeration enum = zip.entries(); + while (enum.hasMoreElements()) { + results.add(renderZipEntry(zipfile, (ZipEntry) enum.nextElement())); + } + zip.close(); + zip = null; + } catch (Throwable t) { + String err = "Error opening " + zipfile + " attempting to continue..."; + System.err.println(err); + t.printStackTrace(System.err); + } finally { + if (null != zip) { + try { zip.close(); } + catch (IOException e) { + e.printStackTrace(System.err); + } + } + } + return result; + } + + /** + * @return true if file represents an existing file with a zip extension + */ + public static boolean isZipFile(File f) { + String s = null; + if ((null == f) || (null == (s = f.getPath()))) { + return false; + } else { + return (f.canRead() && !f.isDirectory() + && (s.endsWith(".zip") + || (s.endsWith(".jar")))); + } + } + + /** + * Render a zip/entry combination to String + */ + public static String renderZipEntry(File zipfile, ZipEntry entry) { + String filename = (null == zipfile ? "null File" : zipfile.getName()); + String entryname = (null == entry ? "null ZipEntry" : entry.getName()); + return filename + "!" + entryname; + } + + /** + * Write all files in directory out to jarFile + * @param jarFile the File to create and write to + * @param directory the File representing the directory to read + * @param mainClass the value of the main class attribute - may be null + */ + public static boolean createJarFile(File jarFile, File directory, + String mainClass, FileFilter filter) { + String label = "createJarFile("+jarFile + +","+directory +","+mainClass +","+filter + "): "; + Log.signal(label + " start"); + if (null == directory) + throw new IllegalArgumentException("null directory"); + Manifest manifest = createManifest(mainClass); + Log.signal(label + " manifest=" + manifest); + JarOutputStream out = null; + try { + File jarFileDir = jarFile.getParentFile(); + if (null == jarFileDir) { + Log.signal(label + " null jarFileDir"); + } else if (!jarFileDir.exists() && !jarFileDir.mkdirs()) { // XXX convert to Error + Log.signal(label + " unable to create jarFileDir: " + jarFileDir); + } + OutputStream os = new FileOutputStream(jarFile); + out = (null == manifest ? new JarOutputStream(os) + : new JarOutputStream(os, manifest)); + Log.signal(label + " out=" + out); + ZipAccumulator reader = new ZipAccumulator(directory, out, filter); + Log.signal(label + " reader=" + reader); + FileUtil.descendFileTree(directory, reader); + out.closeEntry(); + return true; + } catch (IOException e) { + e.printStackTrace(System.err); // todo + } finally { + if (null != out) { + try { out.close();} + catch (IOException e) {} // todo ignored + } + } + + return false; + } + + protected static Manifest createManifest(String mainClass) { + final String mainKey = "Main-Class"; + Manifest result = null; + if (null != mainClass) { + String entry = "Manifest-Version: 1.0\n" + + mainKey + ": " + mainClass + "\n"; + try { + result = new Manifest(new StringBufferInputStream(entry)); + Attributes attributes = result.getMainAttributes(); + String main = attributes.getValue(mainKey); + if (null == main) { + attributes.putValue(mainKey, mainClass); + main = attributes.getValue(mainKey); + if (null == main) { + Log.signal("createManifest unable to set main " + + mainClass); + } + } + } catch (IOException e) { // todo ignoring + Log.signal(e, " IOException creating manifest with " + mainClass); + } + } + return result; + } + + + /** read a file out to the zip stream */ + protected static void addFileToZip(File in, File parent, + ZipOutputStream out) + throws IOException { + String path = in.getCanonicalPath(); + String parentPath = parent.getCanonicalPath(); + if (!path.startsWith(parentPath)) { + throw new Error("not parent: " + parentPath + " of " + path); + } else { + path = path.substring(1+parentPath.length()); + path = path.replace('\\', '/'); // todo: use filesep + } + ZipEntry entry = new ZipEntry(path); + entry.setTime(in.lastModified()); + // todo: default behavior is DEFLATED + + out.putNextEntry(entry); + + InputStream input = null; + try { + input = new FileInputStream(in); + byte[] buf = new byte[1024]; + int count; + while (0 < (count = input.read(buf, 0, buf.length))) { + out.write(buf, 0, count); + } + } finally { + if (null != input) input.close(); + } + } + + + public static void returnTempDir(File dir) { + deleteDirectory(dir); + } + + /** @return true if path ends with gif, properties, jpg */ + public static boolean isResourcePath(String path) { + if (null == path) return false; + path = path.toLowerCase(); + return (path.endsWith(".gif") + || path.endsWith(".properties") + || path.endsWith(".jpg") + || path.endsWith(".jpeg") + || path.endsWith(".props") + ); + } + + public static void render(Throwable t, StringBuffer err) { // todo: move + String name = t.getClass().getName(); + int loc = name.lastIndexOf("."); + name = name.substring(1+loc); + err.append(name + ": " + t.getMessage() + "\n"); // todo + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + err.append(sw.toString()); + } + + private static boolean report(StringBuffer err, String context, String status, + Throwable throwable) { + boolean failed = ((null != status) || (null != throwable)); + if ((null != err) && (failed)) { + if (null != context) { + err.append(context); + } + if (null != status) { + err.append(status); + } + if (null != throwable) { + render(throwable, err); + } + } + return failed; + } + + /** + * Copy file. + * @param src the File to copy - must exist + * @param dest the File for the target file or directory (will not create directories) + * @param err the StringBuffer for returning any errors - may be null + **/ + public static boolean copyFile(File src, File dest, StringBuffer err) { + boolean result = false; + String label = "start"; + Throwable throwable = null; + try { + if (!ValidFileFilter.FILE_EXISTS.accept(src)) { + label = "src file does not exist"; + } else { + if (dest.isDirectory()) { + dest = new File(dest, src.getName()); + } + if (ValidFileFilter.FILE_EXISTS.accept(dest)) { + label = "dest file exists"; + } + boolean closeWhenDone = true; + result = copy(new FileInputStream(src), + new FileOutputStream(dest), + closeWhenDone); + } + label = null; + } catch (Throwable t) { + throwable = t; + } + String context = "FileUtil.copyFile(src, dest, err)"; + boolean report = report(err, context, label, throwable); + return (result && report); + } + + /** + * Copy URL to file. + * @param src the URL to copy - must exist + * @param dest the File for the target file or directory (will not create directories) + * @param err the StringBuffer for returning any errors - may be null + **/ + public static boolean copyURL(URL url, File dest, StringBuffer err) { // todo untested. + boolean result = false; + String label = "start"; + Throwable throwable = null; + try { + if (dest.isDirectory()) { + String filename = url.getFile(); + if ((null == filename) || (0 == filename.length())) { + filename = DEFAULT_URL_FILENAME; + } + dest = new File(dest, filename); + } + if (ValidFileFilter.FILE_EXISTS.accept(dest)) { + label = "dest file exists"; + } + boolean closeWhenDone = true; + result = copy(url.openConnection().getInputStream(), + new FileOutputStream(dest), + closeWhenDone); + label = null; + } catch (Throwable t) { + throwable = t; + } + String context = "FileUtil.copyURL(src, dest, err)"; // add actual parm to labels? + boolean report = report(err, context, label, throwable); + return (result && report); + } + + /** + * Copy input to output - does not close either + * @param src the InputStream to copy - must exist + * @param dest the OutputStream for the target + * @param close if true, close when done + */ + public static boolean copy(InputStream src, OutputStream dest, + boolean close) + throws IOException { + boolean result = false; + IOException throwable = null; + try { + byte[] buf = new byte[8*1024]; + int count; + while (0 < (count = src.read(buf, 0, buf.length))) { + dest.write(buf, 0, count); + } + result = true; + } catch (IOException t) { + throwable = t; + } finally { + if (close) { + try { if (null != src) src.close(); } + catch (IOException e) { + if (null == throwable) { throwable = e; } + } + try { if (null != dest) dest.close(); } + catch (IOException i) { + if (null == throwable) { throwable = i; } + } + } + } + if (null != throwable) throw throwable; + return result; + } + + /** + * @return true if dir was an existing directory that is now deleted + */ + protected static boolean deleteDirectory(File dir) { + return ((null != dir) + && dir.exists() + && dir.isDirectory() + && FileUtil.descendFileTree(dir, DELETE_FILES, false) + && FileUtil.descendFileTree(dir, DELETE_DIRS, true) + && dir.delete()); + } + + public static String[] getPaths(File[] files) { // util + String[] result = new String[files.length]; + for (int i = 0; i < result.length; i++) { + result[i] = files[i].getPath(); // preserves absolute? + } + return result; + } + + //-------- first-order, input and visible interface + + protected static final FileFilter DELETE_DIRS = new FileFilter() { + public boolean accept(File file) { + return ((null != file) && file.isDirectory() + && file.exists() && file.delete()); + } + }; + protected static final FileFilter DELETE_FILES = new FileFilter() { + public boolean accept(File file) { + return ((null != file) && !file.isDirectory() + && file.exists() && file.delete()); + } + }; + +} // class FileUtil + +/** + * Localize FileUtil log/signals for now + * ordinary signals are ignored, + * but exceptions are printed to err + * and errors are thrown as Error + */ +class Log { + /** ordinary logging - may be suppressed */ + public static final void signal(String s) { + //System.err.println(s); + } + /** print stack trace to System.err */ + public static final void signal(Throwable t, String s) { + System.err.println(s); + t.printStackTrace(System.err); + } + /** @throws Error(s) always */ + public static final void error(String s) { + throw new Error(s); + } +} + +/** read each file out to the zip file */ +class ZipAccumulator implements FileFilter { + final File parentDir; + final ZipOutputStream out; + final FileFilter filter; + public ZipAccumulator(File parentDir, ZipOutputStream out, + FileFilter filter) { + this.parentDir = parentDir; + this.out = out; + this.filter = filter; + } + public boolean accept(File f) { + if ((null != filter) && (!filter.accept(f))) { + return false; + } + try { + FileUtil.addFileToZip(f, parentDir, out); + return true; + } catch (IOException e) { + e.printStackTrace(System.err); // todo + } + return false; + } +} + diff --git a/testing/src/org/aspectj/testing/util/IntRange.java b/testing/src/org/aspectj/testing/util/IntRange.java new file mode 100644 index 000000000..80d547e47 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/IntRange.java @@ -0,0 +1,118 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + + +import java.io.Serializable; + +/** + * imutable class to enforce an integer range + */ +public class IntRange implements IntValidator, ObjectChecker, Serializable { + /** no values permitted */ + public static final IntRange NONE = new IntRange(0, 0); + /** 0 permitted */ + public static final IntRange ZERO = new IntRange(0, 1); + /** 1 permitted */ + public static final IntRange ONE = new IntRange(1, 2); + /** 0..1 permitted */ + public static final IntRange OPTIONAL = new IntRange(0, 2); + /** 1..1000 permitted */ + public static final IntRange MANY = new IntRange(1, 1001); + + /** all positive numbers permitted except Integer.MAX_VALUE */ + public static final IntRange POSITIVE = new IntRange(1, Integer.MAX_VALUE); + /** all negative numbers permitted */ + public static final IntRange NEGATIVE = new IntRange(Integer.MIN_VALUE, 0); + /** any int number permitted except Integer.MAX_VALUE */ + public static final IntRange ANY = new IntRange(Integer.MIN_VALUE, Integer.MAX_VALUE); + + /** + * Make an IntRange that accepts this value + * (using existing if available). + * @throws IllegalArgumentException if value is Integer.MAX_VALUE. + */ + public static final IntRange make(int value) { + switch (value) { + case (1) : return ONE; + case (0) : return ZERO; + case (Integer.MAX_VALUE) : + throw new IllegalArgumentException("illegal " + value); + default : + return new IntRange(value, value + 1); + } + } + + public final int min; + public final int max; + private transient String cache; + + /** use only for serialization + * @deprecated IntRange(int, int) + */ + protected IntRange() { + min = 0; + max = 0; + } + + /** + * @param min minimum permitted value, inclusive + * @param max maximum permitted value, exclusive + */ + public IntRange(int min, int max) { + this.min = min; + this.max = max; + if (min > max) { + throw new IllegalArgumentException( min + " > " + max); + } + toString(); // create cache to view during debugging + } + + /** @return true if integer instanceof Integer with acceptable intValue */ + public final boolean isValid(Object integer) { + return ((integer instanceof Integer) + && (acceptInt(((Integer) integer).intValue()))); + } + + /** @return true if min <= value < max */ + public final boolean acceptInt(int value) { + return ((value >= min) && (value < max)); + } + + + /** + * @deprecated acceptInt(int) + * @return true if min <= value < max + */ + public final boolean inRange(int value) { + return acceptInt(value); + } + /** + * @return true if, for any int x s.t. other.inRange(x) + * is true, this.inRange(x) is also true + */ + public final boolean inRange(IntRange other) { + return ((null != other) && (other.min >= min) + && (other.max <= max)); + } + + // XXX equals(Object) + + public String toString() { + if (null == cache) { + cache = "IntRange [" + min + ".." + max + "]"; + } + return cache; + } +} diff --git a/testing/src/org/aspectj/testing/util/IntValidator.java b/testing/src/org/aspectj/testing/util/IntValidator.java new file mode 100644 index 000000000..ea3074d0e --- /dev/null +++ b/testing/src/org/aspectj/testing/util/IntValidator.java @@ -0,0 +1,21 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +/** + * @author isberg + */ +public interface IntValidator { + /** @return true if this is a valid value */ + public boolean acceptInt(int value); +} diff --git a/testing/src/org/aspectj/testing/util/IteratorWrapper.java b/testing/src/org/aspectj/testing/util/IteratorWrapper.java new file mode 100644 index 000000000..266acb13b --- /dev/null +++ b/testing/src/org/aspectj/testing/util/IteratorWrapper.java @@ -0,0 +1,147 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.util.Iterator; +import java.util.List; + +/** + * This iterates in order through the permutations of Lists. + * Order and numericity depend on the underlying list iterators + * and the order in which the lists are supplied to the constructor. + * @author isberg + */ +public class IteratorWrapper implements Iterator { + + final List[] lists; + final Iterator[] iterators; + final Object[] current; + Object[] next; + + /** number of elements in each array */ + final int maxDepth; + + /** + * Current level being iterated. + * Set to 0 whenever depth is incremented. + * Incremented when iterator for the level has no more elements. + */ + int currentLevel; + + /** + * Maximum depth iterated-to thus far. + * Set to 0 on initialization. + * Incremented when incrementing currentLevel brings it past depth. + * Run completes when depth = maxDepth or any new iterator has no elements + */ + int depth; + + + /** @throws IllegalArgumentException if lists or any element null */ + public IteratorWrapper(List[] lists) { + if (null == lists) { + throw new IllegalArgumentException("null lists"); + } + maxDepth = lists.length; + currentLevel = 0; + depth = 0; + List[] temp = new List[maxDepth]; + System.arraycopy(lists, 0, temp, 0, temp.length); + for (int i = 0; i < maxDepth; i++) { + if (null == temp[i]) { + throw new IllegalArgumentException("null List[" + i + "]"); + } + } + this.lists = temp; + current = new Object[maxDepth]; + iterators = new Iterator[maxDepth]; + reset(); + } + + /** Reset to the initial state of the iterator */ + public void reset() { + next = null; + for (int i = 0; i < lists.length; i++) { + iterators[i] = lists[i].iterator(); + if (!iterators[i].hasNext()) { // one iterator is empty - never go + depth = maxDepth; + break; + } else { + current[i] = iterators[i].next(); + } + } + if (depth < maxDepth) { + next = getCurrent(); + } + } + + /** @throws UnsupportedOperationException always */ + public void remove() { + throw new UnsupportedOperationException("operation ambiguous"); + } + + public boolean hasNext() { + return (null != next); + } + + /** + * @return Object[] with each element from the iterator of the + * corresponding list which was passed to the constructor for this. + */ + public Object next() { + Object result = next; + next = getNext(); + return result; + } + + private Object[] getCurrent() { + Object[] result = new Object[maxDepth]; + System.arraycopy(current, 0, result, 0, maxDepth); + return result; + } + + private Object[] getNext() { + int initialLevel = currentLevel; + while (depth < maxDepth) { + if (iterators[currentLevel].hasNext()) { + current[currentLevel] = iterators[currentLevel].next(); + if (currentLevel > initialLevel) { + currentLevel = 0; + } + return getCurrent(); + } else { // pop + // reset this level + iterators[currentLevel] = lists[currentLevel].iterator(); + if (!iterators[currentLevel].hasNext()) { // empty iterator - quit + depth = maxDepth; + return null; + } + current[currentLevel] = iterators[currentLevel].next(); + + // do the next level + currentLevel++; + if (currentLevel > depth) { + depth++; + } + } + } + return null; + } + /** @return "IteratorWrapper({{field}={value}}..)" for current, ceiling, and max */ + public String toString() { + return "IteratorWrapper(currentLevel=" + currentLevel + + ", maxLevel=" + depth + + ", size=" + maxDepth + ")"; + } +} diff --git a/testing/src/org/aspectj/testing/util/LangUtil.java b/testing/src/org/aspectj/testing/util/LangUtil.java new file mode 100644 index 000000000..472d35a31 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/LangUtil.java @@ -0,0 +1,1192 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.StringTokenizer; + + +/** + * misc lang utilities + */ +public class LangUtil { + + /** Delimiter used by split(String) (and ArrayList.toString()?) */ + public static final String SPLIT_DELIM = ", "; + + /** prefix used by split(String) (and ArrayList.toString()?) */ + public static final String SPLIT_START = "["; + + /** suffix used by split(String) (and ArrayList.toString()?) */ + public static final String SPLIT_END = "]"; + + /** system-dependent classpath separator */ + public static final String CLASSPATH_SEP; + + private static final String[] NONE = new String[0]; + + /** bad: hard-wired unix, windows, mac path separators */ + private static final char[] SEPS = new char[] { '/', '\\', ':' }; + + static { + // XXX this has to be the wrong way to get system-dependent classpath separator + String ps = ";"; + try { + ps = System.getProperty("path.separator"); + if (null == ps) { + ps = ";"; + String cp = System.getProperty("java.class.path"); + if (null != cp) { + if (-1 != cp.indexOf(";")) { + ps = ";"; + } else if (-1 != cp.indexOf(":")) { + ps = ":"; + } + // else warn? + } + } + } catch (Throwable t) { // ignore + } finally { + CLASSPATH_SEP = ps; + } + } + + /** + * @return input if any are empty or no target in input, + * or input with escape prefixing all original target + */ + public static String escape(String input, String target, String escape) { + if (isEmpty(input) || isEmpty(target) || isEmpty(escape)) { + return input; + } + StringBuffer sink = new StringBuffer(); + escape(input, target, escape, sink); + return sink.toString(); + } + + + /** + * Append escaped input to sink. + * Cheap form of arbitrary escaping does not escape the escape String + * itself, but unflatten treats it as significant only before the target. + * (so this fails with input that ends with target). + */ + public static void escape(String input, String target, String escape, StringBuffer sink) { + if ((null == sink) || isEmpty(input) || isEmpty(target) || isEmpty(escape)) { + return; + } else if (-1 == input.indexOf(target)) { // avoid StringTokenizer construction + sink.append(input); + return; + } + throw new Error("unimplemented"); + } + + /** flatten list per spec to sink */ + public static void flatten(List list, FlattenSpec spec, StringBuffer sink) { + throwIaxIfNull(spec, "spec"); + final FlattenSpec s = spec; + flatten(list, s.prefix, s.nullFlattened, s.escape, s.delim, s.suffix, sink); + } + + /** + * Flatten a List to String by first converting to String[] + * (using toString() if the elements are not already String) + * and calling flatten(String[]...). + */ + public static void flatten( + List list, + String prefix, + String nullFlattened, + String escape, + String delim, + String suffix, + StringBuffer sink) { + throwIaxIfNull(list, "list"); + Object[] ra = list.toArray(); + String[] result; + if (String.class == ra.getClass().getComponentType()) { + result = (String[]) ra; + } else { + result = new String[ra.length]; + for (int i = 0; i < result.length; i++) { + if (null != ra[i]) { + result[i] = ra[i].toString(); + } + } + } + flatten(result, prefix, nullFlattened, escape, delim, suffix, sink); + } + + + + + /** flatten String[] per spec to sink */ + public static void flatten(String[] input, FlattenSpec spec, StringBuffer sink) { + throwIaxIfNull(spec, "spec"); + final FlattenSpec s = spec; + flatten(input, s.prefix, s.nullFlattened, s.escape, s.delim,s.suffix, sink); + } + + + + + /** + * Flatten a String[] to String by writing strings to sink, + * prefixing with leader (if not null), + * using nullRendering for null entries (skipped if null), + * escaping any delim in entry by prefixing with escape (if not null), + * separating entries with delim (if not null), + * and suffixing with trailer (if not null). + * Note that nullFlattened is not processed for internal delim, + * and strings is not copied before processing. + * @param strings the String[] input - not null + * @param prefix the output starts with this if not null + * @param nullFlattened the output of a null entry - entry is skipped (no delim) if null + * @param escape any delim in an item will be prefixed by escape if not null + * @param delim two items in the output will be separated by delim if not null + * @param suffix the output ends with this if not null + * @param sink the StringBuffer to use for output + * @return null if sink is not null (results added to sink) or rendering otherwise + */ + public static void flatten( + String[] strings, + String prefix, + String nullFlattened, + String escape, + String delim, + String suffix, + StringBuffer sink) { + throwIaxIfNull(strings, "strings"); + if (null == sink) { + return; + } + final boolean haveDelim = (!isEmpty(delim)); + final boolean haveNullFlattened = (null != nullFlattened); + final boolean escaping = (haveDelim && (null != escape)); + final int numStrings = (null == strings ? 0 : strings.length); + if (null != prefix) { + sink.append(prefix); + } + for (int i = 0; i < numStrings; i++) { + String s = strings[i]; + if (null == s) { + if (!haveNullFlattened) { + continue; + } + if (haveDelim && (i > 0)) { + sink.append(delim); + } + sink.append(nullFlattened); + } else { + if (haveDelim && (i > 0)) { + sink.append(delim); + } + + if (escaping) { + escape(s, delim, escape, sink); + } else { + sink.append(s); + } + } + } + if (null != suffix) { + sink.append(suffix); + } + } + + /** @return ((null == ra) || (0 == ra.length)) */ + public static boolean isEmpty(Object[] ra) { + return ((null == ra) || (0 == ra.length)); + } + + /** @return ((null == s) || (0 == s.length())); */ + public static boolean isEmpty(String s) { + return ((null == s) || (0 == s.length())); + } + + /** + * Shorthand for "if false, throw IllegalArgumentException" + * @throws IllegalArgumentException "{message}" if test is false + */ + public static final void throwIaxIfFalse(final boolean test, final String message) { + if (!test) { + throw new IllegalArgumentException(message); + } + } + + /** + * Shorthand for "if null, throw IllegalArgumentException" + * @throws IllegalArgumentException "null {name}" if o is null + */ + public static final void throwIaxIfNull(final Object o, final String name) { + if (null == o) { + String message = "null " + (null == name ? "input" : name); + throw new IllegalArgumentException(message); + } + } + + public static ArrayList unflatten(String input, FlattenSpec spec) { + throwIaxIfNull(spec, "spec"); + final FlattenSpec s = spec; + return unflatten(input,s.prefix, s.nullFlattened, s.escape, s.delim, s.suffix, s.emptyUnflattened); + } + + /** + * Unflatten a String to String[] by separating elements at delim, + * handling prefixes, suffixes, escapes, etc. + * Any prefix or suffix is stripped from the input + * (or, if not found, an IllegalArgumentException is thrown). + * If delim is null or empty or input contains no delim, + * then return new String[] {stripped input}. + * + * XXX fix comments + * prefixing with leader (if not null), + * using nullRendering for null entries (skipped if null), + * escaping any delim in entry by prefixing with escape (if not null), + * separating entries with delim (if not null), + * and suffixing with trailer (if not null). + * Note that nullRendering is not processed for internal delim, + * and strings is not copied before processing. + * @param strings the String[] input - not null + * @param prefix the output starts with this if not null + * @param nullRendering the output of a null entry - entry is skipped (no delim) if null + * @param escape any delim in an item will be prefixed by escape if not null + * @param delim two items in the output will be separated by delim if not null + * @param suffix the output ends with this if not null + * @param sink the StringBuffer to use for output + * @return null if sink is not null (results added to sink) or rendering otherwise + * @throws IllegalArgumentException if input is null + * or if any prefix does not start the input + * or if any suffix does not end the input + */ + public static ArrayList unflatten( + String input, + String prefix, + String nullFlattened, + String escape, + String delim, + String suffix, + String emptyUnflattened) { + throwIaxIfNull(input, "input"); + final boolean haveDelim = (!isEmpty(delim)); + final boolean haveNullFlattened = (null != nullFlattened); + final boolean escaping = (haveDelim && (null != escape)); + if (!isEmpty(prefix)) { + if (input.startsWith(prefix)) { + input = input.substring(prefix.length()); + } else { + String s = "expecting \"" + prefix + "\" at start of " + input + "\""; + throw new IllegalArgumentException(s); + } + } + if (!isEmpty(suffix)) { + if (input.endsWith(suffix)) { + input = input.substring(0, input.length() - suffix.length()); + } else { + String s = "expecting \"" + suffix + "\" at end of " + input + "\""; + throw new IllegalArgumentException(s); + } + } + + final ArrayList result = new ArrayList(); + if (isEmpty(input)) { + return result; + } + if ((!haveDelim) || (-1 == input.indexOf(delim))) { + result.add(input); + return result; + } + + StringTokenizer st = new StringTokenizer(input, delim, true); + StringBuffer cur = new StringBuffer(); + boolean lastEndedWithEscape = false; + boolean lastWasDelim = false; + while (st.hasMoreTokens()) { + String token = st.nextToken(); + System.out.println("reading " + token); + if (delim.equals(token)) { + } else { + result.add(token); + } + } + return result; + } + + /** combine two string arrays, removing null and duplicates + * @return concatenation of both arrays, less null in either or dups in two + * @see Util#combine(Object[], Object[]) + */ + public static String[] combine(String[] one, String[] two) { + ArrayList twoList = new ArrayList(); + twoList.addAll(org.aspectj.util.LangUtil.arrayAsList(two)); + ArrayList result = new ArrayList(); + if (null != one) { + for (int i = 0; i < one.length; i++) { + if (null != one[i]) { + twoList.remove(one[i]); + result.add(one[i]); + } + } + } + for (Iterator iterator = twoList.iterator(); iterator.hasNext(); ) { + String element = (String) iterator.next(); + if (null != element) { + result.add(element); + } + } + return (String[]) result.toArray(NONE); + } + + public static Properties combine(Properties dest, Properties add, boolean respectExisting) { // XXX + if (null == add) return dest; + if (null == dest) return add; + for (Iterator iterator = add.keySet().iterator(); iterator.hasNext(); ) { + String key = (String) iterator.next(); + if (null == key) { + continue; + } + String value = add.getProperty(key); + if (null == value) { + continue; + } + if (! respectExisting || (null == dest.getProperty(key))) { + dest.setProperty(key, value); + } + } + return dest; + } + + public static List arrayAsList(Object[] ra) { + return org.aspectj.util.LangUtil.arrayAsList(ra); + } + + /** + * return the fully-qualified class names + * inferred from the file names in dir + * assuming dir is the root of the source tree + * and class files end with ".class". + * @throws Error if dir is not properly named as prefix + * of class files found in dir. + */ + public static String[] classesIn(File dir) { + boolean alwaysTrue = true; + FileFilter filter = ValidFileFilter.CLASS_FILE; + CollectorFileFilter collector = new CollectorFileFilter(filter, alwaysTrue); + FileUtil.descendFileTree(dir, collector); + List list = collector.getFiles(); + String[] result = new String[list.size()]; + Iterator it = list.iterator(); + String dirPrefix = dir.getPath(); + for (int i = 0; i < result.length; i++) { + if (!it.hasNext()) { + throw new Error("unexpected end of list at " + i); + } + result[i] = fileToClassname((File) it.next(), dirPrefix); + } + return result; + } + + /** + * Convert String[] to String by using conventions for + * split. Will ignore any entries containing SPLIT_DELIM + * (and write as such to errs if not null). + * @param input the String[] to convert + * @param errs the StringBuffer for error messages (if any) + */ + public static String unsplit(String[] input, StringBuffer errs) { + StringBuffer sb = new StringBuffer(); + sb.append(SPLIT_START); + for (int i = 0; i < input.length; i++) { + if (-1 != input[i].indexOf(SPLIT_DELIM)) { + if (null != errs) { + errs.append("\nLangUtil.unsplit(..) - item " + i + ": \"" + input[i] + + " contains \"" + SPLIT_DELIM + "\""); + } + } else { + sb.append(input[i]); + if (1+i < input.length) { + sb.append(SPLIT_DELIM); + } + } + } + sb.append(SPLIT_END); + return sb.toString(); + + } + + /** + * Split input into substrings on the assumption that it is + * either only one string or it was generated using List.toString(), + * with tokens + * <pre>SPLIT_START {string} { SPLIT_DELIM {string}} SPLIT_END<pre> + * (e.g., <code>"[one, two, three]"</code>). + */ + public static String[] split(String s) { + if (null == s) { + return null; + } + if ((!s.startsWith(SPLIT_START)) || (!s.endsWith(SPLIT_END))) { + return new String[] { s }; + } + s = s.substring(SPLIT_START.length(),s.length()-SPLIT_END.length()); + final int LEN = s.length(); + int start = 0; + final ArrayList result = new ArrayList(); + final String DELIM = ", "; + int loc = s.indexOf(SPLIT_DELIM, start); + while ((start < LEN) && (-1 != loc)) { + result.add(s.substring(start, loc)); + start = DELIM.length() + loc; + loc = s.indexOf(SPLIT_DELIM, start); + } + result.add(s.substring(start)); + return (String[]) result.toArray(new String[0]); + } + + public static String[] strip(String[] src, String[] toStrip) { + if (null == toStrip) { + return strip(src, NONE); + } else if (null == src) { + return strip(NONE, toStrip); + } + List slist = org.aspectj.util.LangUtil.arrayAsList(src); + List tlist = org.aspectj.util.LangUtil.arrayAsList(toStrip); + slist.removeAll(tlist); + return (String[]) slist.toArray(NONE); + } + + /** + * Load all classes specified by args, logging success to out + * and fail to err. + */ + public static void loadClasses(String[] args, StringBuffer out, + StringBuffer err) { + if (null != args) { + for (int i = 0; i < args.length; i++) { + try { + Class c = Class.forName(args[i]); + if (null != out) { + out.append("\n"); + out.append(args[i]); + out.append(": "); + out.append(c.getName()); + } + } catch (Throwable t) { + if (null != err) { + err.append("\n"); + FileUtil.render(t, err); + } + } + } + + } + } + + private static String fileToClassname(File f, String prefix) { + // this can safely assume file exists, starts at base, ends with .class + // this WILL FAIL if full path with drive letter on windows + String path = f.getPath(); + if (!path.startsWith(prefix)) { + String err = "!\"" + path + "\".startsWith(\"" + prefix + "\")"; + throw new IllegalArgumentException(err); + } + int length = path.length() - ".class".length(); + path = path.substring(prefix.length()+1, length); + for (int i = 0; i < SEPS.length; i++) { + path = path.replace(SEPS[i], '.'); + } + return path; + } + + public static void main (String[] args) { // todo remove as testing + StringBuffer err = new StringBuffer(); + StringBuffer out = new StringBuffer(); + for (int i = 0; i < args.length; i++) { + String[] names = classesIn(new File(args[i])); + System.err.println(args[i] + " -> " + render(names)); + loadClasses(names, out, err); + } + if (0 < err.length()) { + System.err.println(err.toString()); + } + if (0 < out.length()) { + System.out.println(out.toString()); + } + } + + public static String render (String[] args) { // todo move as testing + if ((null == args) || (1 > args.length)) { + return "[]"; + } + boolean longFormat = (args.length < 10); + String sep = (longFormat ? ", " : "\n\t"); + StringBuffer sb = new StringBuffer(); + if (!longFormat) sb.append("["); + for (int i = 0; i < args.length; i++) { + if (0 < i) sb.append(sep); + sb.append(args[i]); + } + sb.append(longFormat ? "\n" : "]"); + return sb.toString(); + } + + + /** + * @param thrown the Throwable to render + */ + public static String debugStr(Throwable thrown) { + if (null == thrown) { + return "((Throwable) null)"; + } else if (thrown instanceof InvocationTargetException) { + return debugStr(((InvocationTargetException)thrown).getTargetException()); + } else if (thrown instanceof AbortException) { + IMessage m = ((AbortException) thrown).getIMessage(); + if (null != m) { + return "" + m; + } + } + StringWriter buf = new StringWriter(); + PrintWriter writer = new PrintWriter(buf); + writer.println(thrown.getMessage()); + thrown.printStackTrace(writer); + try { buf.close(); } + catch (IOException ioe) {} + return buf.toString(); + } + + /** + * <code>debugStr(o, false);</code> + * @param source the Object to render + */ + public static String debugStr(Object o) { + return debugStr(o, false); + } + + /** + * Render standard debug string for an object in normal, default form. + * @param source the Object to render + * @param recurse if true, then recurse on all non-primitives unless rendered + */ + public static String debugStr(Object o, boolean recurse) { + if (null == o) { + return "null"; + } else if (recurse) { + ArrayList rendering = new ArrayList(); + rendering.add(o); + return debugStr(o, rendering); + } else { + Class c = o.getClass(); + Field[] fields = c.getDeclaredFields(); + Object[] values = new Object[fields.length]; + String[] names = new String[fields.length]; + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + names[i] = field.getName(); + try { + values[i] = field.get(o); + if (field.getType().isArray()) { + List list = org.aspectj.util.LangUtil.arrayAsList((Object[]) values[i]); + values[i] = list.toString(); + } + } catch (IllegalAccessException e) { + values[i] = "<IllegalAccessException>"; + } + } + return debugStr(c, names, values); + } + } + + /** + * recursive variant avoids cycles. + * o added to rendering before call. + */ + private static String debugStr(Object o, ArrayList rendering) { + if (null == o) { + return "null"; + } else if (!rendering.contains(o)) { + throw new Error("o not in rendering"); + } + Class c = o.getClass(); + if (c.isArray()) { + Object[] ra = (Object[]) o; + StringBuffer sb = new StringBuffer(); + sb.append("["); + for (int i = 0; i < ra.length; i++) { + if (i > 0) { + sb.append(", "); + } + rendering.add(ra[i]); + sb.append(debugStr(ra[i], rendering)); + } + sb.append("]"); + return sb.toString(); + } + Field[] fields = nonStaticFields(c.getFields()); + Object[] values = new Object[fields.length]; + String[] names = new String[fields.length]; + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + names[i] = field.getName(); + // collapse to String + Object value = privilegedGetField(field,o); + if (null == value) { + values[i] = "null"; + } else if (rendering.contains(value)) { + values[i] = "<recursion>"; + } else { + rendering.add(value); + values[i] = debugStr(value, rendering); + } + } + return debugStr(c, names, values); + } + + /** incomplete - need protection domain */ + private static Object privilegedGetField(final Field field, final Object o) { + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() { + try { + return field.get(o); + } catch(IllegalAccessException e) { + return "<IllegalAccessException>"; + } + } + }); + } catch (PrivilegedActionException e) { + return "<IllegalAccessException>"; + } + } + + private static Field[] nonStaticFields(Field[] fields) { + if (null == fields) { + return new Field[0]; + } + int to = 0; + int from = 0; + while (from < fields.length) { + if (!Modifier.isStatic(fields[from].getModifiers())) { + if (to != from) { + fields[to] = fields[from]; + } + to++; + } + from++; + } + if (to < from) { + Field[] result = new Field[to]; + if (to > 0) { + System.arraycopy(fields, 0, result, 0, to); + } + fields = result; + } + return fields; + } + + /** <code> debugStr(source, names, items, null, null, null, null)<code> */ + public static String debugStr(Class source, String[] names, Object[] items) { + return debugStr(source, null, names, null, items, null, null); + } + + /** + * Render standard debug string for an object. + * This is the normal form and an example with the default values:<pre> + * {className}{prefix}{{name}{infix}{value}{delimiter}}..{suffix} + * Structure[head=root, tail=leaf]</pre> + * Passing null for the formatting entries provokes the default values, + * so to print nothing, you should pass "". Default values:<pre> + * prefix: "[" SPLIT_START + * infix: "=" + * delimiter: ", " SPLIT_DELIM + * suffix: "]" SPLIT_END + * @param source the Class prefix to render unqualified - omitted if null + * @param names the String[] (field) names of the items - omitted if null + * @param items the Object[] (field) values + * @param prefix the String to separate classname and start of name/values + * @param delimiter the String to separate name/value instances + * @param infix the String to separate name and value + * used only if both name and value exist + * @param suffix the String to delimit the end of the name/value instances + * used only if classname exists + */ + public static String debugStr(Class source, String prefix, String[] names, + String infix, Object[] items, String delimiter, String suffix) { + + if (null == delimiter) { + delimiter = SPLIT_DELIM; + } + if (null == prefix) { + prefix = SPLIT_START; + } + if (null == infix) { + infix = "="; + } + if (null == suffix) { + suffix = SPLIT_END; + } + StringBuffer sb = new StringBuffer(); + if (null != source) { + sb.append(org.aspectj.util.LangUtil.unqualifiedClassName(source)); + } + sb.append(prefix); + if (null == names) { + names = NONE; + } + if (null == items) { + items = NONE; + } + final int MAX + = (names.length > items.length ? names.length : items.length); + for (int i = 0; i < MAX; i++) { + if (i > 0) { + sb.append(delimiter); + } + if (i < names.length) { + sb.append(names[i]); + } + if (i < items.length) { + if (i < names.length) { + sb.append(infix); + } + sb.append(items[i] + ""); + } + } + sb.append(suffix); + return sb.toString(); + } + + /** + * @return a String with the unqualified class name of the object (or "null") + */ + public static String unqualifiedClassName(Object o) { + return unqualifiedClassName(null == o ? null : o.getClass()); + } + + /** + * @return a String with the unqualified class name of the class (or "null") + */ + public static String unqualifiedClassName(Class c) { + if (null == c) { + return "null"; + } + String name = c.getName(); + int loc = name.lastIndexOf("."); + if (-1 != loc) + name = name.substring(1 + loc); + return name; + } + + /** + * Calculate exact diffs and report missing and extra items. + * This assumes the input List are not modified concurrently. + * @param expectedListIn the List of expected results - treated as empty if null + * @param actualListIn the List of actual results - treated as empty if null + * @param extraListOut the List for any actual results not expected - ignored if null + * @param missingListOut the List for any expected results not found - ignored if null + * */ + public static void makeDiffs( + List expectedListIn, + List actualListIn, + List missingListOut, + List extraListOut) { + if ((null == missingListOut) && (null == extraListOut)) { + return; + } + if (null == expectedListIn) { + expectedListIn = Collections.EMPTY_LIST; + } + if (null == actualListIn) { + actualListIn = Collections.EMPTY_LIST; + } + if ((0 == actualListIn.size()) && (0 == expectedListIn.size()) ) { + return; + } + BitSet actualExpected = new BitSet(); + for (int i = 0; i < expectedListIn.size(); i++) { + Object expect = expectedListIn.get(i); + int loc = actualListIn.indexOf(expect); + if (-1 == loc) { + if (null != missingListOut) { + missingListOut.add(expect); + } + } else { + actualExpected.set(loc); + } + } + if (null != extraListOut) { + for (int i = 0; i < actualListIn.size(); i++) { + if (!actualExpected.get(i)) { + extraListOut.add(actualListIn.get(i)); + } + } + } + } + // XXX unit test for makeSoftDiffs + /** + * Calculate potentially "soft" diffs using + * Comparator.compare(expected, actual). + * This shallow-copies and sorts the input Lists. + * @param expectedListIn the List of expected results - treated as empty if null + * @param actualListIn the List of actual results - treated as empty if null + * @param extraListOut the List for any actual results not expected - ignored if null + * @param missingListOut the List for any expected results not found - ignored if null + * @param comparator the Comparator for comparisons - not null + * @throws IllegalArgumentException if comp is null + */ + public static void makeSoftDiffs( // XXX no intersect or union on collections??? + List expectedListIn, + List actualListIn, + List missingListOut, + List extraListOut, + Comparator comparator) { + if ((null == missingListOut) && (null == extraListOut)) { + return; + } + if (null == comparator) { + throw new IllegalArgumentException("null comparator"); + } + if (null == expectedListIn) { + expectedListIn = Collections.EMPTY_LIST; + } + if (null == actualListIn) { + actualListIn = Collections.EMPTY_LIST; + } + if ((0 == actualListIn.size()) && (0 == expectedListIn.size()) ) { + return; + } + + ArrayList expected = new ArrayList(); + expected.addAll(expectedListIn); + Collections.sort(expected, comparator); + Iterator expectedIter = expected.iterator(); + Object exp = null; + + ArrayList actual = new ArrayList(); + actual.addAll(actualListIn); + Collections.sort(actual, comparator); + Iterator actualIter = actual.iterator(); + Object act = null; + + while (((null != act) || actualIter.hasNext()) + && ((null != exp) || expectedIter.hasNext())) { + if (null == act) { + act = actualIter.next(); + } + if (null == exp) { + exp = expectedIter.next(); + } + int diff = comparator.compare(exp, act); + if (0 > diff) { // exp < act + if (null != missingListOut) { + missingListOut.add(exp); + exp = null; + } + } else if (0 < diff) { // exp > act + if (null != extraListOut) { + extraListOut.add(act); + act = null; + } + } else { // got match of actual to expected + // absorb all actual matching expected (duplicates) + while ((0 == diff) && actualIter.hasNext()) { + act = actualIter.next(); + diff = comparator.compare(exp, act); + } + if (0 == diff) { + act = null; + } + exp = null; + } + } + if (null != missingListOut) { + if (null != exp) { + missingListOut.add(exp); + } + while (expectedIter.hasNext()) { + missingListOut.add(expectedIter.next()); + } + } + if (null != extraListOut) { + if (null != act) { + extraListOut.add(act); + } + while (actualIter.hasNext()) { + extraListOut.add(actualIter.next()); + } + } + } + public static class FlattenSpec { + /** + * This tells unflatten(..) to throw IllegalArgumentException + * if it finds two contiguous delimiters. + */ + public static final String UNFLATTEN_EMPTY_ERROR + = "empty items not permitted when unflattening"; + /** + * This tells unflatten(..) to skip empty items when unflattening + * (since null means "use null") + */ + public static final String UNFLATTEN_EMPTY_AS_NULL + = "unflatten empty items as null"; + + /** + * This tells unflatten(..) to skip empty items when unflattening + * (since null means "use null") + */ + public static final String SKIP_EMPTY_IN_UNFLATTEN + = "skip empty items when unflattening"; + + /** + * For Ant-style attributes: "item,item" (with escaped commas). + * There is no way when unflattening to distinguish + * values which were empty from those which were null, + * so all are unflattened as empty. + */ + public static final FlattenSpec COMMA + = new FlattenSpec(null, "", "\\", ",", null, "") { + public String toString() { return "FlattenSpec.COMMA"; } + }; + + /** this attempts to mimic ((List)l).toString() */ + public static final FlattenSpec LIST + = new FlattenSpec("[", "", null, ", ", "]", UNFLATTEN_EMPTY_ERROR) { + public String toString() { return "FlattenSpec.LIST"; } + }; + + /** how toString renders null values */ + public static final String NULL = "<null>"; + private static String r(String s) { + return (null == s ? NULL : s); + } + + public final String prefix; + public final String nullFlattened; + public final String escape; + public final String delim; + public final String suffix; + public final String emptyUnflattened; + private transient String toString; + + public FlattenSpec( + String prefix, + String nullRendering, + String escape, + String delim, + String suffix, + String emptyUnflattened) { + this.prefix = prefix; + this.nullFlattened = nullRendering; + this.escape = escape; + this.delim = delim; + this.suffix = suffix; + this.emptyUnflattened = emptyUnflattened; + throwIaxIfNull(emptyUnflattened, "use UNFLATTEN_EMPTY_AS_NULL"); + } + + public String toString() { + if (null == toString) { + toString = "FlattenSpec(" + + "prefix=" + r(prefix) + + ", nullRendering=" + r(nullFlattened) + + ", escape=" + r(escape) + + ", delim=" + r(delim) + + ", suffix=" + r(suffix) + + ", emptyUnflattened=" + r(emptyUnflattened) + + ")"; + } + return toString; + } + } +} // class LangUtil + +// --------- java runs using Ant +// /** +// * Run a Java command separately. +// * @param className the fully-qualified String name of the class +// * with the main method to run +// * @param classpathFiles the File to put on the classpath +// * @param args to the main method of the class +// * @param outSink the PrintStream for the output stream - may be null +// */ +// public static void oldexecuteJava( +// String className, +// File[] classpathFiles, +// String[] args, +// PrintStream outSink) { +// Project project = new Project(); +// project.setName("LangUtil.executeJava(" + className + ")"); +// Path classpath = new Path(project, classpathFiles[0].getAbsolutePath()); +// for (int i = 1; i < classpathFiles.length; i++) { +// classpath.addExisting(new Path(project, classpathFiles[i].getAbsolutePath())); +// } +// +// Commandline cmds = new Commandline(); +// cmds.addArguments(new String[] {className}); +// cmds.addArguments(args); +// +// ExecuteJava runner = new ExecuteJava(); +// runner.setClasspath(classpath); +// runner.setJavaCommand(cmds); +// if (null != outSink) { +// runner.setOutput(outSink); // XXX todo +// } +// runner.execute(project); +// } + +// public static void executeJava( +// String className, +// File dir, +// File[] classpathFiles, +// String[] args, +// PrintStream outSink) { +// StringBuffer sb = new StringBuffer(); +// +// sb.append("c:/apps/jdk1.3.1/bin/java.exe -classpath \""); +// for (int i = 0; i < classpathFiles.length; i++) { +// if (i < 0) { +// sb.append(";"); +// } +// sb.append(classpathFiles[i].getAbsolutePath()); +// } +// sb.append("\" -verbose " + className); +// for (int i = 0; i < args.length; i++) { +// sb.append(" " + args[i]); +// } +// Exec exec = new Exec(); +// Project project = new Project(); +// project.setProperty("ant.home", "c:/home/wes/aj/aspectj/modules/lib/ant"); +// System.setProperty("ant.home", "c:/home/wes/aj/aspectj/modules/lib/ant"); +// exec.setProject(new Project()); +// exec.setCommand(sb.toString()); +// exec.setDir(dir.getAbsolutePath()); +// exec.execute(); +// } +// public static void execJavaProcess( +// String className, +// File dir, +// File[] classpathFiles, +// String[] args, +// PrintStream outSink) throws Throwable { +// StringBuffer sb = new StringBuffer(); +// +// sb.append("c:\\apps\\jdk1.3.1\\bin\\java.exe -classpath \""); +// for (int i = 0; i < classpathFiles.length; i++) { +// if (i > 0) { +// sb.append(";"); +// } +// sb.append(classpathFiles[i].getAbsolutePath()); +// } +// sb.append("\" -verbose " + className); +// for (int i = 0; i < args.length; i++) { +// sb.append(" " + args[i]); +// } +// String command = sb.toString(); +// System.err.println("launching process: " + command); +// Process process = Runtime.getRuntime().exec(command); +// // huh? err/out +// InputStream errStream = null; +// InputStream outStream = null; +// Throwable toThrow = null; +// int result = -1; +// try { +// System.err.println("waiting for process: " + command); +// errStream = null; // process.getErrorStream(); +// outStream = null; // process.getInputStream(); // misnamed - out +// result = process.waitFor(); +// System.err.println("Done waiting for process: " + command); +// process.destroy(); +// } catch (Throwable t) { +// toThrow = t; +// } finally { +// if (null != outStream) { +// FileUtil.copy(outStream, System.out, false); +// try { outStream.close(); } +// catch (IOException e) {} +// } +// if (null != errStream) { +// FileUtil.copy(errStream, System.err, false); +// try { errStream.close(); } +// catch (IOException e) {} +// } +// } +// if (null != toThrow) { +// throw toThrow; +// } +// } +// try { +// // show the command +// log(command, Project.MSG_VERBOSE); +// +// // exec command on system runtime +// Process proc = Runtime.getRuntime().exec(command); +// +// if (out != null) { +// fos = new PrintWriter(new FileWriter(out)); +// log("Output redirected to " + out, Project.MSG_VERBOSE); +// } +// +// // copy input and error to the output stream +// StreamPumper inputPumper = +// new StreamPumper(proc.getInputStream(), Project.MSG_INFO); +// StreamPumper errorPumper = +// new StreamPumper(proc.getErrorStream(), Project.MSG_WARN); +// +// // starts pumping away the generated output/error +// inputPumper.start(); +// errorPumper.start(); +// +// // Wait for everything to finish +// proc.waitFor(); +// inputPumper.join(); +// errorPumper.join(); +// proc.destroy(); +// +// // close the output file if required +// logFlush(); +// +// // check its exit value +// err = proc.exitValue(); +// if (err != 0) { +// if (failOnError) { +// throw new BuildException("Exec returned: " + err, getLocation()); +// } else { +// log("Result: " + err, Project.MSG_ERR); +// } +// } +// } catch (IOException ioe) { +// throw new BuildException("Error exec: " + command, ioe, getLocation()); +// } catch (InterruptedException ex) {} +// + + diff --git a/testing/src/org/aspectj/testing/util/LineReader.java b/testing/src/org/aspectj/testing/util/LineReader.java new file mode 100644 index 000000000..67f971022 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/LineReader.java @@ -0,0 +1,204 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +/* + * LineReader.java created on May 3, 2002 + * + */ +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.LineNumberReader; +import java.util.ArrayList; + +/** LineNumberReader which absorbs our lines and renders as file:line */ +public class LineReader extends LineNumberReader { + /** delimited multi-line output of readToBlankLine */ + public static final String RETURN= "\n\r"; + + private static final String[] NONE = new String[0]; + private static final String cSCRIPT = "#"; + private static final String cJAVA = "//"; + private static final String[] TESTER_LEAD = new String[] {cSCRIPT, cJAVA}; + + /** + * Convenience factory for tester suite files + * @return null if IOException or IllegalArgumentException thrown + */ + public static final LineReader createTester(File file) { + return create(file, TESTER_LEAD, null); + } + + /** + * convenience factory + * @return null if IOException or IllegalArgumentException thrown + */ + public static final LineReader create(File file, + String[] leadComments, String[] eolComments) { + try { + FileReader reader = new FileReader(file); + return new LineReader(reader, file, leadComments, eolComments); + } catch (IllegalArgumentException e) { + } catch (IOException e) { + } + return null; + } + + final private File file; + final private String[] eolComments; + final private String[] leadComments; + + /** + * @param file the File used to open the FileReader + * @param leadComments the String[] to be taken as the start of + * comments when they are the first non-blank text on a line - + * pass null to signal none. + * @param leadComments the String[] to be taken as the start of + * comment anywhere on a line - pass null to signal none. + *@throws IllegalArgumentException if any String in + * leadComments or eolComments is null. + */ + public LineReader(FileReader reader, File file, + String[] leadComments, String[] eolComments) { + super(reader); + this.file = file; + this.eolComments = normalize(eolComments); + this.leadComments = normalize(leadComments); + } + public LineReader(FileReader reader, File file) { + this(reader, file, null, null); + } + + /** @return file:line */ + public String toString() { + return file.getPath() + ":" + getLineNumber(); + } + + /** @return underlying file */ + public File getFile() { return file; } + + /** + * Reader first..last (inclusive) and return in String[]. + * This will return (1+(last-first)) elements only if this + * reader has not read past the first line and there are last lines + * and there are no IOExceptions during reads. + * @param first the first line to read - if negative, use 0 + * @param last the last line to read (inclusive) + * - if less than first, use first + * @return String[] of first..last (inclusive) lines read or + */ + public String[] readLines(int first, int last) { + if (0 > first) first = 0; + if (first > last) last = first; + ArrayList list = new ArrayList(); + try { + String line = null; + while (getLineNumber() < first) { + line = readLine(); + if (null == line) { + break; + } + } + if (getLineNumber() > first) { + // XXX warn? something else read past line + } + if ((null != line) && (first == getLineNumber())) { + list.add(line); + while (last >= getLineNumber()) { + line = readLine(); + if (null == line) { + break; + } + list.add(line); + } + } + } catch (IOException e) { + return NONE; + } + return (String[]) list.toArray(NONE); + } + + /** Skip to next blank line + * @return the String containing all lines skipped (delimited with RETURN) + */ + public String readToBlankLine() throws IOException { + StringBuffer sb = new StringBuffer(); + String input; + while (null != (input = nextLine(false))) { // get next empty line to restart + sb.append(input); + sb.append(RETURN);// XXX verify/ignore/correct + } + return sb.toString(); + } + + /** + * Get the next line from the input stream, stripping eol and + * leading comments. + * If emptyLinesOk is true, then this reads past lines which are + * empty after omitting comments and trimming until the next non-empty line. + * Otherwise, this returns null on reading an empty line. + * (The input stream is not exhausted until this + * returns null when emptyLines is true.) + * @param emptyLinesOk if false, return null if the line is empty + * @return next non-null, non-empty line in reader, + * ignoring comments + */ + public String nextLine(boolean emptyLinesOk) throws IOException { + int len = 0; + String result = null; + do { + result = readLine(); + if (result == null) + return null; + result = result.trim(); + for (int i = 0; i < eolComments.length; i++) { + int loc = result.indexOf(eolComments[i]); + if (-1 != loc) { + result = result.substring(0, loc); + break; + } + } + len = result.length(); + if (0 < len) { + for (int i = 0; i < leadComments.length; i++) { + if (result.startsWith(leadComments[i])) { + result = ""; + break; + } + } + len = result.length(); + } + len = result.length(); + if (!emptyLinesOk && (0 == len)) + return null; + } while (0 == len); + return result; + } + + private String[] normalize(String[] input) { + if ((null == input) || (0 == input.length)) return NONE; + String[] result = new String[input.length]; + System.arraycopy(input, 0, result, 0, result.length); + for (int i = 0; i < result.length; i++) { + if ((null == result[i]) || (0 == result[i].length())) { + throw new IllegalArgumentException("empty input at [" + i + "]"); + } + } + return result; + } + +} + diff --git a/testing/src/org/aspectj/testing/util/Node.java b/testing/src/org/aspectj/testing/util/Node.java new file mode 100644 index 000000000..11d197eb2 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/Node.java @@ -0,0 +1,162 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +/* + * Node.java created on May 14, 2002 + * + */ +package org.aspectj.testing.util; + + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +/** + * A node in a tree containing other Node or SpecElements items. + */ +public class Node { // XXX render + public static final Node[] EMPTY_NODES = new Node[0]; + public static final Object[] EMPTY_ITEMS = new Object[0]; + + /** + * Visit all the SpecElements (and Node) reachable from node + * in depth-first order, halting if checker objects. + * @param node the Node to pass to checker + * @param itemChecker the ObjectChecker to pass items to + * @param nodeVisitor if not null, then use instead of recursing + * @return false on first objection, true otherwise + * @throws IllegalArgumentExcpetion if checker is null + */ + public static final boolean visit(Node node, ObjectChecker itemChecker, + ObjectChecker nodeVisitor) { + if (null == node) { + return (null == itemChecker ? true : itemChecker.isValid(null)); + } + boolean result = true; + + Node[] nodes = node.getNodes(); + for (int i = 0; result && (i < nodes.length); i++) { + result = (null == nodeVisitor + ? visit(nodes[i], itemChecker, null) + : nodeVisitor.isValid(nodes[i])); + } + if (result) { + Object[] elements = node.getItems(); + for (int i = 0; result && (i < elements.length); i++) { + result = itemChecker.isValid(elements[i]); + } + } + + return result; + } + + public final String name; + public final Class type; + public final Object key; + protected final Object[] typeArray; + protected final List nodes; + protected final List items; + + public Node() { + this("Node"); + } + + public Node(String name) { + this(name, null); + } + + /** use the name as the key */ + public Node(String name, Class type) { + this(name, type, name); + } + /** */ + public Node(String name, Class type, Object key) { + if (null == name) { + throw new IllegalArgumentException("null name"); + } + if (null == key) { + throw new IllegalArgumentException("null key"); + } + this.name = name; + this.type = type; + this.key = key; + nodes = new ArrayList(); + items = new ArrayList(); + if (type == null) { + type = Object.class; + } + typeArray = (Object[]) Array.newInstance(type, 0); + } + + /** + * clear all items and nodes. + */ + public void clear() { // XXX synchronize + nodes.clear(); + items.clear(); + } + + /** + * Add item to list of items + * unless it is null, of the wrong type, or the collection fails to add + * @return true if added + */ + public boolean add(Object item) { + if (null == item) + throw new IllegalArgumentException("null item"); + if ((null != type) && (!type.isAssignableFrom(item.getClass()))) { + return false; + } + return items.add(item); + } + + /** + * Add node to list of nodes + * unless it is null, of the wrong type, or the collection fails to add + * @return true if added + */ + public boolean addNode(Node node) { + if (null == node) { + throw new IllegalArgumentException("null node"); + } + return nodes.add(node); + } + + /** + * Get the current list of nodes - never null + */ + public Node[] getNodes() { + if ((null == nodes) || (1 > nodes.size())) { + return EMPTY_NODES; + } + return (Node[]) nodes.toArray(EMPTY_NODES); + } + + /** + * Get the current list of items - never null + * @return items in current list, cast to type[] if type was not null + */ + public Object[] getItems() { + if ((null == items) || (1 > items.size())) { + return EMPTY_ITEMS; + } + return items.toArray(typeArray); + } + + /** @return name */ + public String toString() { + return name; + } +} diff --git a/testing/src/org/aspectj/testing/util/NullPrintStream.java b/testing/src/org/aspectj/testing/util/NullPrintStream.java new file mode 100644 index 000000000..7ba8e28f9 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/NullPrintStream.java @@ -0,0 +1,95 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +/* + * NullPrintStream.java created on May 29, 2002 + * + */ +package org.aspectj.testing.util; + +import java.io.OutputStream; +import java.io.PrintStream; + +/** + * Ignore any output to a NullPrintStream. + * Clients use singleton NULL_PrintStream or NULL_OutputStream. + * @author isberg + */ +public final class NullPrintStream extends PrintStream { + + public static final OutputStream NULL_OutputStream = NullOutputStream.ME; + public static final PrintStream NULL_PrintStream = new NullPrintStream(); + + private NullPrintStream() { + super(NULL_OutputStream); + } + public void write(int b) { + } + public void write(byte[] b) { + } + public void write(byte[] b, int off, int len) { + } + public void print(boolean arg0) { + } + public void print(char arg0) { + } + public void print(char[] arg0) { + } + public void print(double arg0) { + } + public void print(float arg0) { + } + public void print(int arg0) { + } + public void print(long arg0) { + } + public void print(Object arg0) { + } + public void print(String arg0) { + } + public void println() { + } + public void println(boolean arg0) { + } + public void println(char arg0) { + } + public void println(char[] arg0) { + } + public void println(double arg0) { + } + public void println(float arg0) { + } + public void println(int arg0) { + } + public void println(long arg0) { + } + public void println(Object arg0) { + } + public void println(String arg0) { + } + +} + +final class NullOutputStream extends OutputStream { + static final OutputStream ME = new NullOutputStream(); + + private NullOutputStream() { + } + public void write(int b) { + } + public void write(byte[] b) { + } + public void write(byte[] b, int off, int len) { + } +} diff --git a/testing/src/org/aspectj/testing/util/ObjectChecker.java b/testing/src/org/aspectj/testing/util/ObjectChecker.java new file mode 100644 index 000000000..e7d42755a --- /dev/null +++ b/testing/src/org/aspectj/testing/util/ObjectChecker.java @@ -0,0 +1,38 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +/** + * Check input for validity. + */ +public interface ObjectChecker { + /** this returns true for any input, even if null */ + public static final ObjectChecker ANY = new ObjectChecker() { + public final boolean isValid(Object input) { return true; } + public final String toString() { return "ObjectChecker.ANY"; } + }; + /** this returns true for any non-null object */ + public static final ObjectChecker NOT_NULL = new ObjectChecker() { + public boolean isValid(Object input) { return (null != input); } + public String toString() { return "ObjectChecker.MOT_NULL"; } + }; + + /** + * Check input for validity. + * @param input the Object to check + * @return true if input is ok + */ + public boolean isValid(Object input); +} diff --git a/testing/src/org/aspectj/testing/util/ProxyPrintStream.java b/testing/src/org/aspectj/testing/util/ProxyPrintStream.java new file mode 100644 index 000000000..378f58e36 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/ProxyPrintStream.java @@ -0,0 +1,104 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.IOException; +import java.io.PrintStream; + +/** Wrap a delegate PrintStream, permitting output to be suppressed. */ +public class ProxyPrintStream extends PrintStream { + + private final PrintStream delegate; + private boolean hiding; + public ProxyPrintStream(PrintStream delegate ) { + super(NullPrintStream.NULL_OutputStream); + LangUtil.throwIaxIfNull(delegate, "delegate"); + this.delegate = delegate; + } + public void hide() { + hiding = true; + } + public void show() { + hiding = false; + } + public boolean isHiding() { + return hiding; + } + public void write(int b) { + if (!hiding) delegate.write(b); + } + public void write(byte[] b) throws IOException { + if (!hiding) delegate.write(b); + } + public void write(byte[] b, int off, int len) { + if (!hiding) delegate.write(b, off, len); + } + public void print(boolean arg0) { + if (!hiding) delegate.print(arg0); + } + public void print(char arg0) { + if (!hiding) delegate.print(arg0); + } + public void print(char[] arg0) { + if (!hiding) delegate.print(arg0); + } + public void print(double arg0) { + if (!hiding) delegate.print(arg0); + } + public void print(float arg0) { + if (!hiding) delegate.print(arg0); + } + public void print(int arg0) { + if (!hiding) delegate.print(arg0); + } + public void print(long arg0) { + if (!hiding) delegate.print(arg0); + } + public void print(Object arg0) { + if (!hiding) delegate.print(arg0); + } + public void print(String arg0) { + if (!hiding) delegate.print(arg0); + } + public void println() { + if (!hiding) delegate.println(); + } + public void println(boolean arg0) { + if (!hiding) delegate.println(arg0); + } + public void println(char arg0) { + if (!hiding) delegate.println(arg0); + } + public void println(char[] arg0) { + if (!hiding) delegate.println(arg0); + } + public void println(double arg0) { + if (!hiding) delegate.println(arg0); + } + public void println(float arg0) { + if (!hiding) delegate.println(arg0); + } + public void println(int arg0) { + if (!hiding) delegate.println(arg0); + } + public void println(long arg0) { + if (!hiding) delegate.println(arg0); + } + public void println(Object arg0) { + if (!hiding) delegate.println(arg0); + } + public void println(String arg0) { + if (!hiding) delegate.println(arg0); + } +} diff --git a/testing/src/org/aspectj/testing/util/RunUtils.java b/testing/src/org/aspectj/testing/util/RunUtils.java new file mode 100644 index 000000000..febfac442 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/RunUtils.java @@ -0,0 +1,368 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.MessageUtil.IMessageRenderer; +import org.aspectj.testing.harness.bridge.AbstractRunSpec; +import org.aspectj.testing.harness.bridge.IRunSpec; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.RunValidator; +import org.aspectj.util.LangUtil; + +import java.io.PrintStream; +import java.util.Iterator; + +/** + * + */ +public class RunUtils { + + /** enable verbose for this an any related AbstractRunSpec children */ + public static void enableVerbose(AbstractRunSpec spec) { // instanceof hack + LangUtil.throwIaxIfNull(spec, "spec"); + spec.runtime.setVerbose(true); + for (Iterator iter = spec.getChildren().iterator(); iter.hasNext();) { + IRunSpec child = (IRunSpec) iter.next(); + if (child instanceof AbstractRunSpec) { + enableVerbose((AbstractRunSpec) child); + } + } + } + + /** + * Calculate failures for this status. + * If the input status has no children and failed, the result is 1. + * If it has children and recurse is false, then + * the result is the number of children whose status has failed + * (so a failed status with some passing and no failing children + * will return 0). + * If it has children and recurse is true, + * then return the number of leaf failures in the tree, + * ignoring (roll-up) node failures. + * @return number of failures in children of this status + */ + public static int numFailures(IRunStatus status, boolean recurse) { + int numFails = 0; + IRunStatus[] children = status.getChildren(); + int numChildren = (null == children? 0 : children.length); + if (0 == numChildren) { + if (!RunValidator.NORMAL.runPassed(status)) { + return 1; + } + } else { + int i = 0; + for (int j = 0; j < children.length; j++) { + if (recurse) { + numFails += numFailures(children[j], recurse); + } else { + if (!RunValidator.NORMAL.runPassed(children[j])) { + numFails++; + } + } + } + } + return numFails; + } + + // ------------------------ printing status + public static void printShort(PrintStream out, IRunStatus status) { + if ((null == out) || (null == status)) { + return; + } + printShort(out, "", status); + } + + public static void printShort(PrintStream out, String prefix, IRunStatus status) { + int numFails = numFailures(status, true); + String fails = (0 == numFails ? "" : " - " + numFails + " failures"); + out.println(prefix + toShortString(status) + fails); + IRunStatus[] children = status.getChildren(); + int numChildren = (null == children? 0 : children.length); + if (0 < numChildren) { + int i = 0; + for (int j = 0; j < children.length; j++) { + printShort(out, prefix + "[" + LangUtil.toSizedString(i++, 3) + "]: ", children[j]); + if (!RunValidator.NORMAL.runPassed(children[j])) { + numFails++; + } + } + } + } + + public static void print(PrintStream out, IRunStatus status) { + if ((null == out) || (null == status)) { + return; + } + print(out, "", status); + } + + public static void print(PrintStream out, String prefix, IRunStatus status) { + print(out, prefix, status, MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL); + } + + public static void print(PrintStream out, String prefix, IRunStatus status, + IMessageRenderer renderer, IMessageHandler selector) { + String label = status.getIdentifier() + + (status.runResult() ? "PASS" : "FAIL"); + out.println(prefix + label); + out.println(prefix + debugString(status)); + IMessageHolder messageHolder = status; + if ((null != messageHolder) && (0 < messageHolder.numMessages(null, true))) { + MessageUtil.print(out, messageHolder, prefix, renderer, selector); + } + Throwable thrown = status.getThrown(); + if (null != thrown) { + out.println(prefix + "--- printing stack trace for thrown"); + thrown.printStackTrace(out); + } + IRunStatus[] children = status.getChildren(); + int numChildren = (null == children? 0 : children.length); + int numFails = 0; + if (0 < numChildren) { + out.println(prefix + "--- printing children [" + numChildren + "]"); + int i = 0; + for (int j = 0; j < children.length; j++) { + print(out, prefix + "[" + LangUtil.toSizedString(i++, 3) + "]: ", children[j]); + if (!RunValidator.NORMAL.runPassed(children[j])) { + numFails++; + } + } + } + if (0 < numFails) { + label = numFails + " fails " + label; + } + out.println(""); + } + + + public static String debugString(IRunStatus status) { + if (null == status) { + return "null"; + } + final String[] LABELS = + new String[] { + "runResult", + "id", + "result", + "numChildren", + "completed", + //"parent", + "abort", + "started", + "thrown", + "messages" }; + String runResult = status.runResult() ? "PASS" : "FAIL"; + Throwable thrown = status.getThrown(); + String thrownString = LangUtil.unqualifiedClassName(thrown); + IRunStatus[] children = status.getChildren(); + String numChildren = (null == children? "0" : ""+children.length); + String numMessages = ""+status.numMessages(null, IMessageHolder.EQUAL); + Object[] values = + new Object[] { + runResult, + status.getIdentifier(), + status.getResult(), + numChildren, + new Boolean(status.isCompleted()), + //status.getParent(), // costly if parent printing us + status.getAbortRequest(), + new Boolean(status.started()), + thrownString, + numMessages }; + return org.aspectj.testing.util.LangUtil.debugStr(status.getClass(), LABELS, values); + } + + public static String toShortString(IRunStatus status) { + if (null == status) { + return "null"; + } + String runResult = status.runResult() ? " PASS: " : " FAIL: "; + return (runResult + status.getIdentifier()); + } + + /** renderer for IRunStatus */ + public static interface IRunStatusPrinter { + void printRunStatus(PrintStream out, IRunStatus status); + } + + public static final IRunStatusPrinter VERBOSE_PRINTER = new IRunStatusPrinter() { + public String toString() { return "VERBOSE_PRINTER"; } + /** Render IRunStatus produced by running an AjcTest */ + public void printRunStatus(PrintStream out, IRunStatus status) { + printRunStatus(out, status, ""); + } + private void printRunStatus(PrintStream out, IRunStatus status, String prefix) { + LangUtil.throwIaxIfNull(out, "out"); + LangUtil.throwIaxIfNull(status, "status"); + String label = (status.runResult() ? " PASS: " : " FAIL: ") + + status.getIdentifier(); + out.println(prefix + "------------ " + label); + out.println(prefix + "--- result: " + status.getResult()); + if (0 < status.numMessages(null, true)) { + out.println(prefix + "--- messages "); + MessageUtil.print(out, status, prefix, MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL); + } + Throwable thrown = status.getThrown(); + if (null != thrown) { + out.println(prefix + "--- thrown"); + thrown.printStackTrace(out); + } + IRunStatus[] children = status.getChildren(); + for (int i = 0; i < children.length; i++) { + String number = "[" + LangUtil.toSizedString(i,3) + "] "; + printRunStatus(out, children[i], prefix + number); + } + } + }; + + /** print only status and fail/abort messages */ + public static final IRunStatusPrinter TERSE_PRINTER = new IRunStatusPrinter() { + public String toString() { return "TERSE_PRINTER"; } + + /** print only status and fail messages */ + public void printRunStatus(PrintStream out, IRunStatus status) { + printRunStatus(out, status, ""); + } + private void printRunStatus(PrintStream out, IRunStatus status, String prefix) { + LangUtil.throwIaxIfNull(out, "out"); + LangUtil.throwIaxIfNull(status, "status"); + String label = (status.runResult() ? "PASS: " : "FAIL: ") + + status.getIdentifier(); + out.println(prefix + label); + Object result = status.getResult(); + if ((null != result) && (IRunStatus.PASS != result) && (IRunStatus.FAIL != result)) { + out.println(prefix + "--- result: " + status.getResult()); + } + if (0 < status.numMessages(IMessage.FAIL, true)) { + MessageUtil.print(out, status, prefix, MessageUtil.MESSAGE_ALL, MessageUtil.PICK_FAIL_PLUS); + } + Throwable thrown = status.getThrown(); + if (null != thrown) { + out.println(prefix + "--- thrown: " + LangUtil.renderException(thrown, true)); + } + IRunStatus[] children = status.getChildren(); + for (int i = 0; i < children.length; i++) { + if (!children[i].runResult()) { + String number = "[" + LangUtil.toSizedString(i,3) + "] "; + printRunStatus(out, children[i], prefix + number); + } + } + out.println(""); + } + }; + + /** Render IRunStatus produced by running an AjcTest.Suite. */ + public static final IRunStatusPrinter AJCSUITE_PRINTER = new IRunStatusPrinter() { + public String toString() { return "AJCSUITE_PRINTER"; } + + /** + * Render IRunStatus produced by running an AjcTest.Suite. + * This renders only test failures and + * a summary at the end. + */ + public void printRunStatus(PrintStream out, IRunStatus status) { + LangUtil.throwIaxIfNull(out, "out"); + LangUtil.throwIaxIfNull(status, "status"); + final String prefix = ""; + final boolean failed = status.runResult(); + String label = (status.runResult() ? "PASS: " : "FAIL: ") + + status.getIdentifier(); + out.println(prefix + label); + // print all messages - these are validator comments + if (0 < status.numMessages(null, true)) { + MessageUtil.print(out, status, "init", MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL); + } + // XXX ignore thrown if failed - will be printed as message anyway? + Throwable thrown = status.getThrown(); + if ((null != thrown) && !failed) { + out.println(prefix + "--- printing stack trace for thrown"); + thrown.printStackTrace(out); + } + IRunStatus[] children = status.getChildren(); + int numChildren = (null == children? 0 : children.length); + int numFails = 0; + if (0 < numChildren) { + for (int j = 0; j < children.length; j++) { + if (!RunValidator.NORMAL.runPassed(children[j])) { + numFails++; + } + } + } + if (0 < numFails) { + out.println(prefix + "--- " + numFails + " failures when running " + children.length + " tests"); + for (int j = 0; j < children.length; j++) { + if (!RunValidator.NORMAL.runPassed(children[j])) { + print(out, prefix + "[" + LangUtil.toSizedString(j, 3) + "]: ", children[j]); + out.println(""); + } + } + } + label = "ran " + children.length + " tests" + + (numFails == 0 ? "" : "(" + numFails + " fails)"); + out.println(""); + } + + }; + /** Render IRunStatus produced by running an AjcTest (verbose) */ + public static final IRunStatusPrinter AJCTEST_PRINTER = VERBOSE_PRINTER; + + /** print this with messages, then children using AJCRUN_PRINTER */ + public static final IRunStatusPrinter AJC_PRINTER = new IRunStatusPrinter() { + public String toString() { return "AJC_PRINTER"; } + /** Render IRunStatus produced by running an AjcTest */ + public void printRunStatus(PrintStream out, IRunStatus status) { + LangUtil.throwIaxIfNull(out, "out"); + LangUtil.throwIaxIfNull(status, "status"); + String label = (status.runResult() ? " PASS: " : " FAIL: ") + + status.getIdentifier(); + out.println("------------ " + label); + MessageUtil.print(out, status, "", MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ALL); + IRunStatus[] children = status.getChildren(); + for (int i = 0; i < children.length; i++) { + AJCRUN_PRINTER.printRunStatus(out, children[i]); + } + //out.println("------------ END " + label); + out.println(""); + } + }; + + + /** print only fail messages */ + public static final IRunStatusPrinter AJCRUN_PRINTER = new IRunStatusPrinter() { + public String toString() { return "AJCRUN_PRINTER"; } + /** Render IRunStatus produced by running an AjcTest child */ + public void printRunStatus(PrintStream out, IRunStatus status) { + LangUtil.throwIaxIfNull(out, "out"); + LangUtil.throwIaxIfNull(status, "status"); + final boolean orGreater = false; + int numFails = status.numMessages(IMessage.FAIL, orGreater); + if (0 < numFails) { + out.println("--- " + status.getIdentifier()); + IMessage[] fails = status.getMessages(IMessage.FAIL, orGreater); + for (int i = 0; i < fails.length; i++) { + out.println("[fail " + LangUtil.toSizedString(i, 3) + "]: " + + MessageUtil.MESSAGE_ALL.renderToString(fails[i])); + } + } + } + }; + + private RunUtils() { + } + +} diff --git a/testing/src/org/aspectj/testing/util/SFileReader.java b/testing/src/org/aspectj/testing/util/SFileReader.java new file mode 100644 index 000000000..f2b57e13d --- /dev/null +++ b/testing/src/org/aspectj/testing/util/SFileReader.java @@ -0,0 +1,185 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; +import org.aspectj.util.LangUtil; +import org.aspectj.util.LineReader; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; + + +/** + * This reads a structured (config) file, which may have + * lines with @ signalling a recursive read + * and EOL comments # or //. + * This duplicates ConfigFileUtil in some sense. + */ +public class SFileReader { + // XXX move into LineReader, but that forces util to depend on AbortException? + // Formerly in SuiteReader + + /** + * Read args as config files and echo to stderr. + * @param args String[] of fully-qualified paths to config files + */ + public static void main(String[] args) throws IOException { + ArrayList result = new ArrayList(); + ObjectChecker collector = new StandardObjectChecker(String.class, result); + SFileReader me = new SFileReader(null); + for (int i = 0; i < args.length; i++) { + Node node = me.readNodes(new File(args[i]), null, true, System.err); + if (!Node.visit(node, collector, null)) { + System.err.println("halted during copy of " +args[i]); + } else { + String s = org.aspectj.testing.util.LangUtil.debugStr(null, "\n ", null, + null, result.toArray(), "\n ", ""); + System.err.println(args[i] + ": " + s); + } + } + } + + /* + * readSuite(..) reads .txt file, and for each test case specification + * creates a spec using readTestSpecifications + * and (if the specifications match the constraints) + * creates a test case using creatTestCase. + */ + + /** pass this to protected methods requiring String[] if you have none */ + protected static final String[] NONE = new String[0]; + + final Maker maker; + + /** @param maker the Maker factory to use - if null, use Maker.ECHO */ + public SFileReader(Maker maker) { + this.maker = (null == maker ? Maker.ECHO : maker); + } + + /** + * Creates a (potentially recursive) tree of node + * by reading from the file and constructing using the maker. + * Clients may read results in Node tree form when complete + * or snoop the selector for a list of objects made. + * The selector can prevent collection in the node by + * returning false. + * Results are guaranteed by the Maker to be of the Maker's type. + * @param file an absolute path to a structured file + * @param selector determines whether not to keep an object made. + * (if null, then all are kept) + * @return Node with objects available from getItems() + * and sub-suite Node available from getNodes() + * @throws Error on any read error if abortOnReadError (default) + */ + public Node readNodes( + final File file, + final ObjectChecker selector, + final boolean abortOnReadError, + final PrintStream err) + throws IOException { + final Node result = new Node(file.getPath(), maker.getType()); + if (null == file) { + throw new IllegalArgumentException("null file"); + } else if (!file.isAbsolute()) { + throw new IllegalArgumentException("file not absolute"); + } + LineReader reader = null; + try { + reader = LineReader.createTester(file); + if (null == reader) { + throw new IOException("no reader for " + file); + } + final String baseDir = file.getParent(); + + String line; + boolean skipEmpties = true; + while (null != (line = reader.nextLine(skipEmpties))) { + if (line.charAt(0) == '@') { + if (line.length() > 1) { + String newFilePath = line.substring(1).trim(); + File newFile = new File(newFilePath); + if (!newFile.isAbsolute()) { + newFile = new File(baseDir, newFilePath); + } + Node node = readNodes(newFile, selector, abortOnReadError, err); + if (!result.addNode(node)) { + // XXX signal error? + System.err.println("warning: unable to add node: " + node); + break; + } + } + } else { + try { + Object made = maker.make(reader); + if ((null == selector) || (selector.isValid(made))) { + if (!result.add(made)) { + break; // XXX signal error? + } + } + } catch (AbortException e) { + if (abortOnReadError) { // XXX todo - verify message has context? + throw e; + } + if (null != err) { + String m; + IMessage mssg = e.getIMessage(); + if (null != mssg) { + m = "Message: " + mssg; + } else { + m = LangUtil.unqualifiedClassName(e) + "@" + e.getMessage(); + } + err.println(m); + } + reader.readToBlankLine(); + } + } + } + } finally { + try { + if (null != reader) { + reader.close(); + } + } catch (IOException e) { + } // ignore + } + + return result; + } + + /** factory produces objects by reading LineReader */ + public interface Maker { + /** + * Make the result using the input from the LineReader, + * starting with lastLine(). + */ + Object make(LineReader reader) throws AbortException, IOException; + + /** @return type of the Object made */ + Class getType(); + + /** This echoes each line, prefixed by the reader. + * @return file:line: {line} + */ + static final Maker ECHO = new Maker() { + public Object make(LineReader reader) { + return reader + ": " + reader.lastLine(); + } + public Class getType() { return String.class; } + }; + } +} diff --git a/testing/src/org/aspectj/testing/util/StandardObjectChecker.java b/testing/src/org/aspectj/testing/util/StandardObjectChecker.java new file mode 100644 index 000000000..ee5a466f5 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/StandardObjectChecker.java @@ -0,0 +1,127 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +/* + * StandardObjectChecker.java created on May 7, 2002 + * + */ +package org.aspectj.testing.util; + +import java.util.List; + +/** + * Superclass for checkers that require non-null input + * of a given type. + * Clients may supply delegator for further checks, + * or a list to collect results. + * Subclasses may instead implement doIsValid(). + * @author isberg + */ +public class StandardObjectChecker implements ObjectChecker { + + public final Class type; + private final ObjectChecker delegate; + private final List collector; + private final boolean collectionResult; + + /** + * Create one with no delegate. + * @param type the Class of the type required of the input + */ + public StandardObjectChecker(Class type) { + this(type, ANY, (List) null, true); + } + + /** + * @param type the Class of the type required of the input + * @param delegate the ObjectChecker to delegate to after + * checking for non-null input of the correct type. + */ + public StandardObjectChecker(Class type, ObjectChecker delegate) { + this(type, delegate, null, true); + } + + /** + * same as StandardObjectChecker(type, collector, true) + * @param type the Class of the type required of the input + * @param collector the list to collect valid entries + */ + public StandardObjectChecker(Class type, List collector) { + this(type, ANY, collector, true); + } + + /** + * @param type the Class of the type required of the input + * @param collector the list to collect valid entries + * @param collectionResult the value to return when entry was added + */ + public StandardObjectChecker(Class type, List collector, boolean collectionResult) { + this(type, ANY, collector, collectionResult); + } + + /** + * @param type the Class of the type required of the input + * @param collector the list to collect valid entries + */ + public StandardObjectChecker(Class type, ObjectChecker delegate, + List collector, boolean collectionResult) { + if (null == type) throw new IllegalArgumentException("null type"); + this.type = type; + this.delegate = delegate; + this.collector = collector; + this.collectionResult = collectionResult; + } + + /** + * Check if object is valid by confirming is is non-null and of the + * right type, then delegating to any delegate or calling doIsValid(), + * then (if true) passing to any collector, and returning + * false if the collector failed or the collection result otherwise. + * @see ObjectChecker#isValid(Object) + * @return true unless input is null or wrong type + * or if one of subclass (doIsValid(..)) or delegates + * (list, collector) returns false. + */ + public final boolean isValid(Object input) { + if ((null == input) || (!(type.isAssignableFrom(input.getClass())))) { + return false; + } else if (null != delegate) { + if (!delegate.isValid(input)) { + return false; + } + } + if (!doIsValid(input)) { + return false; + } + if (null == collector) { + return true; + } else if (!collector.add(input)) { + return false; + } else { + return collectionResult; + } + } + + /** + * Delegate of isValid guarantees that the input + * is not null as is assignable to the specified type. + * Subclasses implement their funtionality here. + * This implementation returns true; + * @return true + */ + public boolean doIsValid(Object input) { + return true; + } + +} diff --git a/testing/src/org/aspectj/testing/util/StreamSniffer.java b/testing/src/org/aspectj/testing/util/StreamSniffer.java new file mode 100644 index 000000000..8b740aa2b --- /dev/null +++ b/testing/src/org/aspectj/testing/util/StreamSniffer.java @@ -0,0 +1,76 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +/* + * StreamGrabber.java created on May 16, 2002 + * + */ +package org.aspectj.testing.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Listen to a stream using StringBuffer. + * Clients install and remove buffer to enable/disable listening. + * Does not affect data passed to underlying stream + */ +public class StreamSniffer extends FilterOutputStream { + StringBuffer buffer; + /** have to use delegate, not super, because super we will double-count input */ + final OutputStream delegate; + + public StreamSniffer(OutputStream stream) { + super(stream); + delegate = stream; + } + + /** set to null to stop copying */ + public void setBuffer(StringBuffer sb) { + buffer = sb; + } + + //---------------- FilterOutputStream + public void write(int b) throws IOException { + StringBuffer sb = buffer; + if (null != sb) { + if ((b > Character.MAX_VALUE) + || (b < Character.MIN_VALUE)) { + throw new Error("don't know double-byte"); // XXX + } else { + sb.append((char) b); + } + } + delegate.write(b); + } + + public void write(byte[] b) throws IOException { + StringBuffer sb = buffer; + if (null != sb) { + String s = new String(b); + sb.append(s); + } + delegate.write(b); + } + + public void write(byte[] b, int offset, int length) throws IOException { + StringBuffer sb = buffer; + if (null != sb) { + String s = new String(b, offset, length); + sb.append(s); + } + delegate.write(b, offset, length); + } +} diff --git a/testing/src/org/aspectj/testing/util/StreamsHandler.java b/testing/src/org/aspectj/testing/util/StreamsHandler.java new file mode 100644 index 000000000..bd550ada7 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/StreamsHandler.java @@ -0,0 +1,217 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.PrintStream; + +/** + * Manage system err and system out streams. + * Clients can suppress stream output during StreamsHandler lifecycle + * and intermittantly listen to both streams if signalled on construction. + * To print to the underlying streams (without hiding or listening), + * use either the log methods (which manage lineation) + * or the out and err fields. + * <pre> + * boolean hideStreams = true; + * boolean listen = true; + * StreamsHander streams = new StreamsHander(hideStreams, listen); + * streams.startListening(); + * ... + * streams.out.println("this goes out to without listening"); XXX verify + * StreamsHandler.Result result = streams.stopListening(); + * streams.restoreStreams(); + * System.out.println("Suppressed output stream follows"); + * System.out.print(result.out); + * System.out.println("Suppressed error stream follows"); + * System.out.print(result.err); + * </pre> + * Warning: does not distinguish streams from different threads. + */ +public class StreamsHandler { + + /** real output stream and sink for log if logToOut */ + public final PrintStream out; + + /** real error stream and sink for log if !logToOut */ + public final PrintStream err; + + /** if true, then can listen using startListening() */ + protected final boolean listening; + + /** if logToOut, then out, else err */ + private final PrintStream log; + + /** true if the last logged item was a newline */ + private boolean loggedLine; + + /** sniffs stream to gather test output to System.out */ + protected StreamSniffer outSniffer; + + /** sniffs stream to gather test output to System.err */ + protected StreamSniffer errSniffer; + + /** permits us to hide output stream (after sniffing by outSniffer */ + protected ProxyPrintStream outDelegate; + + /** permits us to hide error stream (after sniffing by errSniffer */ + protected ProxyPrintStream errDelegate; + + /** when sniffing, this has sniffed contents of output stream */ + protected StringBuffer outListener; + + /** when sniffing, this has sniffed contents of error stream */ + protected StringBuffer errListener; + + /** @param hide if true, then suppress stream output (can still listen) */ + public StreamsHandler(boolean listen) { + this(listen, false); + } + + /** + * @param listen possible to sniff streams only if true + * @param logToOut if true, then log methods go to System.out -- otherwise, System.err. + */ + public StreamsHandler( + boolean listen, + boolean logToOut) { + this.err = System.err; + this.out = System.out; + outDelegate = new ProxyPrintStream(System.out); + errDelegate = new ProxyPrintStream(System.err); + this.listening = listen; + final PrintStream HIDE = NullPrintStream.NULL_PrintStream; + outSniffer = new StreamSniffer(outDelegate); + System.setOut(new PrintStream(outSniffer)); + errSniffer = new StreamSniffer(errDelegate); + System.setErr(new PrintStream(errSniffer)); + log = (logToOut ? this.out : this.err); + loggedLine = true; + } + + /** render output and error streams (after sniffing) */ + public void show() { + outDelegate.show(); + errDelegate.show(); + } + + /** suppress output and error streams (after sniffing) */ + public void hide() { + outDelegate.hide(); + errDelegate.hide(); + } + + /** restore streams. Do not use this after restoring. */ + public void restoreStreams() { + if (null != outSniffer) { + outSniffer = null; + errSniffer = null; + System.setOut(out); + System.setErr(err); + } + } + + /** @return PrintStream used for direct logging */ + public PrintStream getLogStream() { + return log; + } + + /** log item without newline. */ + public void log(String s) { + log.print(s); + if (loggedLine) { + loggedLine = false; + } + } + + /** + * Log item with newline. + * If previous log did not have a newline, + * then this prepends a newline. + */ + public void lnlog(String s) { + if (!loggedLine) { + log.println(""); + } + log.println(s); + } + + /** + * Start listening to both streams. + * Tosses any old data captured. + * (Has no effect if not listening.) + * @throws IllegalStateException if called after restoreStreams() + * @see endListening() + */ + public void startListening() { + if (null == outSniffer) { + throw new IllegalStateException("no listening after restore"); + } + if (listening) { + if (null != outListener) { + outListener.setLength(0); + errListener.setLength(0); + } else { + outListener = new StringBuffer(); + outSniffer.setBuffer(outListener); + errListener = new StringBuffer(); + errSniffer.setBuffer(errListener); + } + } + } + + /** + * End listening to both streams and return data captured. + * Must call startListening() first. + * @throws IllegalStateException if called when not listening + * @return Result with sniffed output and error String + * @see startListening() + */ + public Result endListening() { + return endListening(true); + } + + /** + * End listening to both streams and return data captured. + * Must call startListening() first. + * @param getResult if false, return Result.EMPTY + * and avoid converting buffer to String. + * @throws IllegalStateException if called when not listening + * @return Result with sniffed output and error String + * @see startListening() + */ + public Result endListening(boolean getResult) { + if (!listening) { + return Result.EMPTY; + } + if (null == outListener) { + throw new IllegalStateException("listening not started"); + } + Result result = (!getResult ? Result.EMPTY + : new Result(outListener.toString(), errListener.toString())); + errListener = null; + outListener = null; + return result; + } + + /** output and error String */ + public static class Result { + static final Result EMPTY = new Result(null, null); + public final String out; + public final String err; + private Result(String out, String err) { + this.out = out; + this.err = err; + } + } +} diff --git a/testing/src/org/aspectj/testing/util/StringAccumulator.java b/testing/src/org/aspectj/testing/util/StringAccumulator.java new file mode 100644 index 000000000..ecff4fabc --- /dev/null +++ b/testing/src/org/aspectj/testing/util/StringAccumulator.java @@ -0,0 +1,99 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +/* + * StringAccumulator.java created on May 14, 2002 + * + */ +package org.aspectj.testing.util; + + + +/** + * Accumulate String with delimiters. + */ +public class StringAccumulator implements ObjectChecker { + + private final String prefix; + private final String infix; + private final String suffix; + private final String nullString; + private final StringBuffer sb; + private int index; + + /** + * Accumulate string with delimiter between elements, + * treaing null elements as "". + */ + public StringAccumulator(String delimiter) { + this(delimiter, null, null, ""); + } + + /** + * Constructor for StringAccumulator which specifies how to + * process each result, optionally postfixing or prefixing + * or infixing (adding index plus infix to prefix). e.g., + * for prefix="[", infix="]\"", postfix="\"\n", then each entry + * becomes a line: <pre>"[{index}]"{entry}"\n</pre> + * + * @param prefix if not null, prepend to each result + * @param infix if not null, the add index and infix before each result, after prefix + * @param postfix if not null, append to each result + * @param nullString if null, ignore null completely (no index); otherwise render null as nullString + * @param type + */ + public StringAccumulator(String prefix, String infix, String suffix, String nullString) { + this.prefix = prefix; + this.infix = infix; + this.suffix = suffix; + this.nullString = nullString; + sb = new StringBuffer(); + } + + /** Clear buffer and index */ + public synchronized void clear() { + sb.setLength(0); + index = 0; + } + + /** + * Accumulate input.toString into + * @return true + * @see StandardObjectChecker#doIsValid(Object) + */ + public synchronized boolean isValid(Object input) { + if (input == null) { + if (nullString == null) return true; // ignore + input = nullString; + } + if (null != prefix) sb.append(prefix); + if (null != infix) { + sb.append(index++ + infix); + } + sb.append(input.toString()); + if (null != suffix) sb.append(suffix); + return true; + } + + /** @return result accumulated so far */ + public String toString() { + return sb.toString(); + } + /** @return result accumulated so far */ + public String debugString() { + return "StringAccumulator prefix=" + prefix + " infix=" + infix + " suffix=" + suffix + + " nullString=" + nullString + " index=" + index + " toString=" + toString(); + } + +} diff --git a/testing/src/org/aspectj/testing/util/StringVisitor.java b/testing/src/org/aspectj/testing/util/StringVisitor.java new file mode 100644 index 000000000..3d14cb3f6 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/StringVisitor.java @@ -0,0 +1,28 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +// todo: non-distribution license? + +package org.aspectj.testing.util; + +/** + * Visitor interface for String +*/ +public interface StringVisitor { + /** + * @param input the String to evaluate - may be null + * @return true if input is accepted and/or process should continue + */ + public boolean accept(String input); +} + diff --git a/testing/src/org/aspectj/testing/util/TestClassLoader.java b/testing/src/org/aspectj/testing/util/TestClassLoader.java new file mode 100644 index 000000000..2af325e32 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/TestClassLoader.java @@ -0,0 +1,154 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Load classes as File from File[] dirs or URL[] jars. + */ +public class TestClassLoader extends URLClassLoader { + + List /*File*/ dirs; + + public TestClassLoader(URL[] urls, File[] dirs) { + super(urls); + if (null == dirs) { + throw new IllegalArgumentException("null dir"); + } + for (int i = 0; i < dirs.length; i++) { + if (null == dirs[i]) { + throw new IllegalArgumentException("null dir[" + i + "]"); + } + } + ArrayList dcopy = new ArrayList(); + + if ((null != dirs) && (0 < dirs.length)) { + dcopy.addAll(Arrays.asList(dirs)); + } + this.dirs = Collections.unmodifiableList(dcopy); + } + + + public URL getResource(String name) { + return ClassLoader.getSystemResource(name); + } + + public InputStream getResourceAsStream(String name) { + return ClassLoader.getSystemResourceAsStream(name); + } + + /** We don't expect test classes to have prefixes java, org, or com */ + protected boolean maybeTestClassName(String name) { + return (null != name) + && !name.startsWith("java") + && !name.startsWith("org.") + && !name.startsWith("com."); + } + + public synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + // search the cache, our dirs (if maybe test), + // the system, the superclass (URL[]), + // and our dirs again (if not maybe test) + ClassNotFoundException thrown = null; + final boolean maybeTestClass = maybeTestClassName(name); + + Class result = findLoadedClass(name); + if (null != result) { + resolve = false; + } else if (maybeTestClass) { + // subvert the dominant paradigm... + byte[] data = readClass(name); + if (data != null) { + result = defineClass(name, data, 0, data.length); + } // handle ClassFormatError? + } + if (null == result) { + try { + result = findSystemClass(name); + } catch (ClassNotFoundException e) { + thrown = e; + } + } + if (null == result) { + try { + result = super.loadClass(name, resolve); + } catch (ClassNotFoundException e) { + thrown = e; + } + if (null != result) { // resolved by superclass + return result; + } + } + if ((null == result) && !maybeTestClass) { + byte[] data = readClass(name); + if (data != null) { + result = defineClass(name, data, 0, data.length); + } // handle ClassFormatError? + } + + if (null == result) { + throw (null != thrown ? thrown : new ClassNotFoundException(name)); + } + if (resolve) { + resolveClass(result); + } + return result; + } + + private byte[] readClass(String className) throws ClassNotFoundException { + byte[] data= null; + final String fileName = className.replace('.', '/')+".class"; + for (Iterator iter = dirs.iterator(); iter.hasNext();) { + File file = new File((File) iter.next(), fileName); + if (file.canRead()) { + return getClassData(file); + } + if (data != null) { + return data; + } + } + throw new ClassNotFoundException(className); // expensive - fix? + } + + private byte[] getClassData(File f) { + try { + FileInputStream stream= new FileInputStream(f); + ByteArrayOutputStream out= new ByteArrayOutputStream(1000); + byte[] b= new byte[4096]; + int n; + while ((n= stream.read(b)) != -1) { + out.write(b, 0, n); + } + stream.close(); + out.close(); + return out.toByteArray(); + } catch (IOException e) { + } + return null; + } +} + diff --git a/testing/src/org/aspectj/testing/util/TestDiffs.java b/testing/src/org/aspectj/testing/util/TestDiffs.java new file mode 100644 index 000000000..2a847f039 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/TestDiffs.java @@ -0,0 +1,360 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.util.LangUtil; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Calculated differences between two test runs + * based on their output files + * assuming that tests are logged with prefix [PASS|FAIL] + * (as they are when using <tt>-traceTestsMin</tt> with the Harness). + * @see org.aspectj.testing.drivers.Harness + */ +public class TestDiffs { // XXX pretty dumb implementation + + /** @param args expected, actual test result files */ + public static void main(String[] args) { + if ((null == args) || (2 > args.length)) { + System.err.println("java " + TestDiffs.class.getName() + " expectedFile actualFile {test}"); + return; + } + File expected = new File(args[0]); + File actual = new File(args[1]); + + TestDiffs result = compareResults(expected, actual); + + System.out.println("## Differences between test runs"); + print(System.out, result.added, "added"); + print(System.out, result.missing, "missing"); + print(System.out, result.fixed, "fixed"); + print(System.out, result.broken, "broken"); + + System.out.println("## Summary"); + System.out.println(" # expected " + result.expected.size() + " tests: " + args[0] ); + System.out.println(" # actual " + result.actual.size() + " tests: " + args[1]); + StringBuffer sb = new StringBuffer(); + append(sb, result.added, " added"); + append(sb, result.missing, " missing"); + append(sb, result.broken, " broken"); + append(sb, result.fixed, " fixed"); + append(sb, result.stillPassing, " still passing"); + append(sb, result.stillFailing, " still failing"); + System.out.println(" # diffs: " + sb); + } + + /** + * @param expected the expected/old File results with Harness -traceTestsMin lines + * @param actual the actual/new File results with Harness -traceTestsMin lines + * @return TestDiffs null if error, valid otherwise + */ + public static TestDiffs compareResults(File expected, File actual) { + ArrayList exp = null; + ArrayList act = null; + File reading = expected; + try { + exp = TestDiffs.readTestResults(expected, expected.getPath()); + reading = actual; + act = TestDiffs.readTestResults(actual, actual.getPath()); + + Diffs tests = new Diffs("tests", exp, act, TestResult.BY_NAME); + // remove missing/unexpected (removed, added) tests from results + // otherwise, unexpected-[pass|fail] look like [fixes|broken] + ArrayList expResults = trimByName(exp, tests.missing); + ArrayList actResults = trimByName(act, tests.unexpected); + + Diffs results = new Diffs("results", expResults, actResults, TestResult.BY_PASSNAME); + + // broken tests show up in results as unexpected-fail or missing-pass + // fixed tests show up in results as unexpected-pass or missing-fail + ArrayList broken = new ArrayList(); + ArrayList fixed = new ArrayList(); + split(results.unexpected, fixed, broken); + + return new TestDiffs( + exp, + act, + tests.missing, + tests.unexpected, + broken, + fixed); + } catch (IOException e) { + System.err.println("error reading " + reading); + e.printStackTrace(System.err); // XXX + return null; + } + } + + private static void append(StringBuffer sb, List list, String label) { + if (!LangUtil.isEmpty(list)) { + if (0 < sb.length()) { + sb.append(" "); + } + sb.append(list.size() + label); + } + } + + private static void print(PrintStream out, List list, String label) { + if ((null == out) || LangUtil.isEmpty(list)) { + return; + } + int i = 0; + final String suffix = " " + label; + final String LABEL = list.size() + suffix; + out.println("## START " + LABEL); + for (Iterator iter = list.iterator(); iter.hasNext();) { + TestResult result = (TestResult) iter.next(); + out.println(" " + result.test + " ## " + suffix); + } + out.println("## END " + LABEL); + } + + /** + * Create ArrayList with input TestResult list + * but without elements in trim list, + * comparing based on test name only. + * @param input + * @param trim + * @return ArrayList with all input except those in trim (by name) + */ + private static ArrayList trimByName(List input, List trim) { + ArrayList result = new ArrayList(); + result.addAll(input); + if (!LangUtil.isEmpty(input) && !LangUtil.isEmpty(trim)) { + for (ListIterator iter = result.listIterator(); iter.hasNext();) { + TestResult inputItem = (TestResult) iter.next(); + for (Iterator iterator = trim.iterator(); + iterator.hasNext(); + ) { + TestResult trimItem = (TestResult) iterator.next(); + if (inputItem.test.equals(trimItem.test)) { + iter.remove(); + break; + } + } + } + } + return result; + } + + + /** split input List by whether the TestResult element passed or failed */ + private static void split(List input, ArrayList pass, ArrayList fail) { + for (ListIterator iter = input.listIterator(); iter.hasNext();) { + TestResult result = (TestResult) iter.next(); + if (result.pass) { + pass.add(result); + } else { + fail.add(result); + } + } + } + + /** + * Read a file of test results, + * defined as lines starting with [PASS|FAIL] + * (produced by Harness option <tt>-traceTestsmin</tt>). + * @return ArrayList of TestResult, one for every -traceTestsMin line in File + */ + private static ArrayList readTestResults(File file, String config) throws IOException { + LangUtil.throwIaxIfNull(file, "file"); + if (null == config) { + config = file.getPath(); + } + ArrayList result = new ArrayList(); + FileReader in = null; + try { + in = new FileReader(file); + BufferedReader input = new BufferedReader(in); + String line; + // XXX handle stream interleaving more carefully + // XXX clip trailing () + // XXX fix elision in test name rendering by -traceTestsMin? + while (null != (line = input.readLine())) { + boolean pass = line.startsWith("PASS"); + boolean fail = false; + if (!pass) { + fail = line.startsWith("FAIL"); + } + if (pass || fail) { + String test = line.substring(4).trim(); + result.add(new TestResult(test, config, pass)); + } + } + } finally { + if (null != in) { + try { in.close(); } + catch (IOException e) {} // ignore + } + } + return result; + } + + private static List safeList(List list) { + return (null == list + ? Collections.EMPTY_LIST + : Collections.unmodifiableList(list)); + } + + /** List of TestResult results from expected run. */ + public final List expected; + + /** List of TestResult results from actual run. */ + public final List actual; + + /** List of TestResult tests disappeared from test suite between expected and actual runs. */ + public final List missing; + + /** List of TestResult tests added to test suite between expected and actual runs. */ + public final List added; + + /** List of TestResult tests in both runs, expected to pass but actually failed */ + public final List broken; + + /** List of TestResult tests in both runs, expected to fail but actually passed */ + public final List fixed; + + /** List of TestResult passed tests in expected run */ + public final List expectedPassed; + + /** List of TestResult failed tests in expected run */ + public final List expectedFailed; + + /** List of TestResult passed tests in actual run */ + public final List actualPassed; + + /** List of TestResult tests failed in actual run */ + public final List actualFailed; + + /** List of TestResult tests passed in both expected and actual run */ + public final List stillPassing; + + /** List of TestResult tests failed in both expected and actual run */ + public final List stillFailing; + + private TestDiffs( + List expected, + List actual, + List missing, + List added, + List broken, + List fixed) { + this.expected = safeList(expected); + this.actual = safeList(actual); + this.missing = safeList(missing); + this.added = safeList(added); + this.broken = safeList(broken); + this.fixed = safeList(fixed); + // expected[Passed|Failed] + ArrayList passed = new ArrayList(); + ArrayList failed = new ArrayList(); + split(this.expected, passed, failed); + expectedPassed = safeList(passed); + expectedFailed = safeList(failed); + + // actual[Passed|Failed] + passed = new ArrayList(); + failed = new ArrayList(); + split(this.actual, passed, failed); + actualPassed = safeList(passed); + actualFailed = safeList(failed); + + // stillPassing: expected.passed w/o broken, missingPasses + passed = new ArrayList(); + passed.addAll(expectedPassed); + passed = trimByName(passed, this.broken); + ArrayList missingPasses = new ArrayList(); + ArrayList missingFails = new ArrayList(); + split(this.missing, missingPasses, missingFails); + passed = trimByName(passed, missingPasses); + stillPassing = safeList(passed); + + // stillFailing: expected.failed w/o fixed, missingFails + failed = new ArrayList(); + failed.addAll(expectedFailed); + failed = trimByName(failed, this.fixed); + failed = trimByName(failed, missingFails); + stillFailing = safeList(failed); + } + + /** results of a test */ + public static class TestResult { + public static final Comparator BY_PASSNAME = new Comparator() { + public int compare(Object o1, Object o2) { + if (o1 == o2) { + return 0; + } + TestResult lhs = (TestResult) o1; + TestResult rhs = (TestResult) o2; + return (lhs.pass == rhs.pass + ? lhs.test.compareTo(rhs.test) + : (lhs.pass ? 1 : -1 )); + } + + public boolean equals(Object lhs, Object rhs) { + return (0 == compare(lhs, rhs)); + } + }; + + public static final Comparator BY_NAME = new Comparator() { + public int compare(Object o1, Object o2) { + if (o1 == o2) { + return 0; + } + TestResult lhs = (TestResult) o1; + TestResult rhs = (TestResult) o2; + return lhs.test.compareTo(rhs.test); + } + + public boolean equals(Object lhs, Object rhs) { + return (0 == compare(lhs, rhs)); + } + }; + + //private static final ArrayList TESTS = new ArrayList(); + public static final String FIELDSEP = "\t"; + + public final String test; + public final String config; + public final boolean pass; + private final String toString; + + public TestResult(String test, String config, boolean pass) { + LangUtil.throwIaxIfNull(test, "test"); + LangUtil.throwIaxIfNull(test, "config"); + this.test = test; + this.config = config; + this.pass = pass; + toString = (pass ? "PASS" : "FAIL") + FIELDSEP + test + FIELDSEP + config; + + } + + /** @return [PASS|FAIL]{FIELDSEP}test{FIELDSEP}config */ + public String toString() { + return toString; + } + } +} diff --git a/testing/src/org/aspectj/testing/util/ValidFileFilter.java b/testing/src/org/aspectj/testing/util/ValidFileFilter.java new file mode 100644 index 000000000..ba9f74407 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/ValidFileFilter.java @@ -0,0 +1,123 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileFilter; + +/** + * FileFilter that accepts existing files + * with static singleton variants + * made from inner subclasses. + */ +public class ValidFileFilter implements FileFilter { + //----------------------------- singleton variants + public static final FileFilter EXIST = new ValidFileFilter(); + public static final FileFilter FILE_EXISTS = new FilesOnlyFilter(); + public static final FileFilter DIR_EXISTS = new DirsOnlyFilter(); + public static final FileFilter CLASS_FILE = new ClassOnlyFilter(); + public static final FileFilter JAVA_FILE = new JavaOnlyFilter(); + public static final FileFilter RESOURCE = new ResourcesOnlyFilter(); + + //----------------------------- members + protected final FileFilter delegate; + protected ValidFileFilter(){ + this(null); + } + protected ValidFileFilter(FileFilter delegate){ + this.delegate = delegate; + } + + /** + * Implement <code>FileFilter.accept(File)</code> by checking + * taht input is not null, exists, and is accepted by any delegate. + */ + public boolean accept(File f) { + return ((null != f) && (f.exists()) + && ((null == delegate) || delegate.accept(f))); + } + + //----------------------------- inner subclasses + static class FilesOnlyFilter extends ValidFileFilter { + public boolean accept(File f) { + return (super.accept(f) && (!f.isDirectory())); + } + } + static class ResourcesOnlyFilter extends FilesOnlyFilter { + public boolean accept(File f) { + return (super.accept(f) && (FileUtil.isResourcePath(f.getPath()))); + } + } + static class DirsOnlyFilter extends ValidFileFilter { + public final boolean accept(File f) { + return (super.accept(f) && (f.isDirectory())); + } + } + // todo: StringsFileFilter, accepts String[] variants for each + static class StringFileFilter extends ValidFileFilter { + public static final boolean IGNORE_CASE = true; + protected final String prefix; + protected final String substring; + protected final String suffix; + protected final boolean ignoreCase; + /** true if one of the String specifiers is not null */ + protected final boolean haveSpecifier; + public StringFileFilter(String prefix, String substring, + String suffix, boolean ignoreCase) { + this.ignoreCase = ignoreCase; + this.prefix = preprocess(prefix); + this.substring = preprocess(substring); + this.suffix = preprocess(suffix); + haveSpecifier = ((null != prefix) || (null != substring) + || (null != suffix)); + } + private final String preprocess(String input) { + if ((null != input) && ignoreCase) { + input = input.toLowerCase(); + } + return input; + } + public boolean accept(File f) { + if (!(super.accept(f))) { + return false; + } else if (haveSpecifier) { + String path = preprocess(f.getPath()); + if ((null == path) || (0 == path.length())) { + return false; + } + if ((null != prefix) && (!(path.startsWith(prefix)))) { + return false; + } + if ((null != substring) && (-1 == path.indexOf(substring))) { + return false; + } + if ((null != suffix) && (!(path.endsWith(suffix)))) { + return false; + } + } + return true; + } + } // class StringFileFilter + + static class ClassOnlyFilter extends StringFileFilter { + ClassOnlyFilter() { + super(null, null, ".class", IGNORE_CASE); + } + } + static class JavaOnlyFilter extends StringFileFilter { + JavaOnlyFilter() { + super(null, null, ".java", IGNORE_CASE); + } + } +} // class ValidFileFilter + diff --git a/testing/src/org/aspectj/testing/util/WebInstall.java b/testing/src/org/aspectj/testing/util/WebInstall.java new file mode 100644 index 000000000..4f8c96934 --- /dev/null +++ b/testing/src/org/aspectj/testing/util/WebInstall.java @@ -0,0 +1,208 @@ +/* ******************************************************************* + * Copyright (c) 1999-2000 Xerox 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileWriter; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * Install programmatically using http URL. + * (Very strange that java tool classpath does not accept http URL's.) + * + * Example: + * <p><code>java -classpath aj-testing.jar org.aspectj.testing.util.WebInstall + * http://aspectj.org/download/distribution/aspectj10-tools.jar -text + * install.properties</code> + * + * <p>You can omit the <code>-text install.properties</code> if there is + * a file called "install.properties" in the current directory. + * + * <p>The properties file must define the properties + * <code>output.dir</code> and <code>context.javaPath</code> + * in properties- and platform specifie ways. + * For property values, a backslash must be escaped with another backslash, + * and directory separators should be valid. E.g., on Windows: + * <pre>output.dir=c:\\output\\dir + * context.javaPath=c:\\apps\\jdk1.3.1</pre> + * + * For an installer to complete programmatically, + * the output directory must be empty of colliding files. + * This will fail with a stack trace if anything goes wrong, except for + * simple input errors. + * + * <p>You may also use this as a driver for the known installers + * by specifying the following options (-baseurl must be first):<pre> + * -baseurl {baseurl} + * -version {version} + * -context.javaPath {path to JDK} (properties form) + * -output.dir {path to outDir} (properties form, including trailing /) + * -outdir {path to outDir} (actual form) </pre> + * such that URL= + * <code>{baseurl}<packagePrefix>{version}<packageSuffix>.jar</code> + * and paths to context.javaPath and output.dir are specified in + * properties-compliant format + * + * @see ant script test-product.xml for example of installing from files + * which can be driven from the command-line. + */ +public class WebInstall { + private static final String EOL = "\n"; // todo where is this defined? + public static final String SYNTAX + = "java WebInstall url {args}" + EOL + + " url - to installer" + EOL + + " args - normally -text install.properties" + EOL + + " (if n/a, use install.properties)" + EOL; + + /** default arguments assume file <code>install.properties</code> + * is in current directory */ + private static final String[] ARGS = new String[] + { "-text", "install.properties" }; + + /** @param args the String[] <code>{ "<url>" {, "-text", "<propsPath>" }</code> */ + public static void main(String[] args) throws Exception { + if ((null != args) && (args.length > 0) + && ("-baseurl".equals(args[0]))) { + driver(args); + } else { + try { + new WebInstall().install(args); + } catch (Throwable t) { + System.err.println("Error installing args "); + for (int i = 0; i < args.length; i++) { + System.err.println(" " + i + ": " + args[i]); + } + t.printStackTrace(System.err); + } + } + } + + /** known .jar packages {(prefix, suffix}...} */ + protected static String[] packages = new String[] + { "aspectj-tools-", "" + , "aspectj-docs-", "" + , "ajde-forteModule-", "" + , "ajde-jbuilderOpenTool-", "" + }; + + /** + * Drive install of all jar-based installers. + * @param args the String[] containing<pre> + * -baseurl {baseurl} + * -version {version} + * -context.javaPath {path to JDK} (properties form) + * -output.dir {path to outDir} (properties form, including trailing /) + * -outdir {path to outDir} (actual form) </pre> + * such that URL= + * <code>{baseurl}<packagePrefix>{version}<packageSuffix>.jar</code> + * and paths to context.javaPath and output.dir are specified in + * properties-compliant format + */ + protected static void driver(String[] args) throws Exception { + String baseurl = null; + String version = null; + String outputDir = null; + File outdir = null; + String jdk = null; + for (int i = 0; i < args.length; i++) { + if ("-baseurl".equals(args[i])) { + baseurl = args[++i]; + } else if ("-version".equals(args[i])) { + version = args[++i]; + } else if ("-context.javaPath".equals(args[i])) { + jdk = args[++i]; + } else if ("-output.dir".equals(args[i])) { + outputDir=args[++i]; + } else if ("-outdir".equals(args[i])) { + outdir = new File(args[++i]).getCanonicalFile(); + if (!outdir.isDirectory()) { + outdir.mkdir(); + } + } + } + final File props = File.createTempFile("WebInstall", null); + final String[] ARGS = new String [] {null, "-text", props.getCanonicalPath()}; + for (int i = 0; i < packages.length; i++) { + String name = packages[i++] + version + packages[i]; + File outDir = new File(outdir, name); + FileWriter fw = null; + try { + if (!outDir.isDirectory()) { + outDir.mkdir(); + } + fw = new FileWriter(props); + fw.write("output.dir=" + outputDir + name + "\n"); + fw.write("context.javaPath=" + jdk + "\n"); + fw.close(); + fw = null; + ARGS[0] = baseurl + name + ".jar"; + main(ARGS); + } finally { + try { if (null != fw) fw.close(); } + catch (java.io.IOException e) {} // ignore + } + } + if (props.exists()) props.delete(); + } // driver + + private static boolean printError(String err) { + if (null != err) System.err.println(err); + System.err.println(SYNTAX); + return (null != err); + } + + /** + * Create a classloader using the first argument (presumed to be URL for classloader), + * construct the installer, and invoke it using remaining arguments (or default args). + */ + protected void install(String[] args) throws Exception { + if ((null == args) || (args.length < 1) + || (null == args[0]) || (1 > args[0].length())) { + if (printError("expecting installer URL")) return; + } + URL[] urls = new URL[] { new URL(args[0]) }; + //System.err.println("before: " + render(args)); + args = getArgs(args); + //System.err.println("after: " + render(args)); + URLClassLoader cl = new URLClassLoader(urls); + Class c = cl.loadClass("$installer$.org.aspectj.Main"); // todo: dependency on class name + Method ms = c.getMethod("main", new Class[]{String[].class}); + ms.invoke(null, new Object[] { args }); + } + public static final String render(String[] args) { + StringBuffer sb = new StringBuffer(); + sb.append("["); + for (int i = 0; i < args.length; i++) { + if (0 < i) sb.append(", "); + sb.append("" + args[i]); + } + sb.append("]"); + return sb.toString(); + } + + /** @return args less args[0] or default args if less than 3 arguments */ + + protected String[] getArgs(String[] args) { + if ((null == args) || (args.length < 3)) { + return ARGS; + } else { + String[] result = new String[args.length-1]; + System.arraycopy(args, 1, result, 0, result.length); + return result; + } + } + +} diff --git a/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java b/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java new file mode 100644 index 000000000..cc484f82d --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/AjcSpecXmlReader.java @@ -0,0 +1,432 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +import org.apache.commons.digester.Digester; +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.harness.bridge.AbstractRunSpec; +import org.aspectj.testing.harness.bridge.AjcTest; +import org.aspectj.testing.harness.bridge.CompilerRun; +import org.aspectj.testing.harness.bridge.DirChanges; +import org.aspectj.testing.harness.bridge.IncCompilerRun; +import org.aspectj.testing.harness.bridge.JavaRun; +import org.aspectj.testing.util.RunUtils; +import org.aspectj.util.LangUtil; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * Read an ajc test specification in xml form. + * Input files should comply with DOCTYPE + */ +public class AjcSpecXmlReader { + /* + * To add new elements or attributes: + * - update the DOCTYPE + * - update setupDigester(..) + * - new sub-elements should be created + * - new attributes should have values set as bean properties + * (possibly by mapping names) + * - new sub-elements should be added to parents + * => the parents need the add method - e.g., add{foo}({foo} toadd) + * - add tests + * - add compile-time checks for mapping APIs in + * setupDigesterComipileTimeCheck + * - when adding an attribute set by bean introspection, + * add to the list returned by expectedProperties() + * - update any client writers referring to the DOCTYPE, as necessary. + * - the parent IXmlWriter should delegate to the child component + * as IXmlWriter (or write the subelement itself) + */ + + private static final String EOL = "\n"; + + /** presumed relative-path to dtd file for any XML files written by writeSuiteToXmlFile */ + public static final String DTD_PATH = "../tests/ajcTestSuite.dtd"; + + /** expected doc type of AjcSpec XML files */ + public static final String DOCTYPE = "<!DOCTYPE " + + AjcTest.Suite.Spec.XMLNAME + " SYSTEM \"" + DTD_PATH + "\">"; + + /** xml leader */ + public static final String FILE_LEADER + = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"; + + /** @return a String suitable as an inlined DOCTYPE statement */ + public static String inlineDocType() { + return "<!DOCTYPE " + + AjcTest.Suite.Spec.XMLNAME + + " [" + + AjcSpecXmlReader.getDocType() + + EOL + " ]>"; + } + + /** + * @return the elements of a document type as a String, + * using EOL as a line delimiter + */ + public static String getDocType() { + if (true) { + throw new Error("XXX using ajcTestSuite.dtd"); + } + StringBuffer r = new StringBuffer(); + final String suiteX = AjcTest.Suite.Spec.XMLNAME; + final String ajctestX = AjcTest.Spec.XMLNAME; + final String compileX = CompilerRun.Spec.XMLNAME; + final String inccompileX = IncCompilerRun.Spec.XMLNAME; + final String runX = JavaRun.Spec.XMLNAME; + final String dirchangesX = DirChanges.Spec.XMLNAME; + final String messageX = SoftMessage.XMLNAME; + + r.append(EOL + " <!ELEMENT " + suiteX + " (" + ajctestX + "+)>"); + r.append(EOL + " <!ATTLIST " + suiteX + " suiteDir CDATA #IMPLIED >"); + r.append(EOL + ""); + r.append(EOL + " <!ELEMENT " + ajctestX + " (" + compileX + ", (" + compileX + " | " + inccompileX + " | " + runX + ")*)>"); + r.append(EOL + " <!ATTLIST " + ajctestX + " title CDATA #REQUIRED >"); + r.append(EOL + " <!ATTLIST " + ajctestX + " dir CDATA #REQUIRED >"); + r.append(EOL + " <!ATTLIST " + ajctestX + " pr CDATA #IMPLIED >"); + r.append(EOL + " <!ATTLIST " + ajctestX + " keywords CDATA #IMPLIED >"); + r.append(EOL + ""); + r.append(EOL + " <!ELEMENT " + compileX + " (" + dirchangesX + "*,file*," + messageX + "*)>"); // deprecate file? + r.append(EOL + " <!ATTLIST " + compileX + " staging CDATA #IMPLIED >"); // if precursor to incremental + r.append(EOL + " <!ATTLIST " + compileX + " files CDATA #IMPLIED >"); + r.append(EOL + " <!ATTLIST " + compileX + " options CDATA #IMPLIED >"); + r.append(EOL + ""); + r.append(EOL + " <!ELEMENT " + inccompileX + " (" + dirchangesX + "*," + messageX + "*)>"); // add file* if not deprecated + r.append(EOL + " <!ATTLIST " + inccompileX + " tag CDATA #REQUIRED >"); + r.append(EOL + ""); + r.append(EOL + " <!ELEMENT " + runX + " (" + dirchangesX + "*," + messageX + "*)>"); + r.append(EOL + " <!ATTLIST " + runX + " class CDATA #REQUIRED >"); + r.append(EOL + " <!ATTLIST " + runX + " skipTester CDATA #IMPLIED >"); + r.append(EOL + " <!ATTLIST " + runX + " options CDATA #IMPLIED >"); + r.append(EOL + ""); + r.append(EOL + " <!ELEMENT file (#PCDATA)>"); // deprecate? + r.append(EOL + " <!ATTLIST file path CDATA #IMPLIED >"); + r.append(EOL + ""); + r.append(EOL + " <!ELEMENT " + messageX + " (#PCDATA)>"); + r.append(EOL + " <!ATTLIST " + messageX + " kind (error | warning | info | Xlint) #REQUIRED >"); + r.append(EOL + " <!ATTLIST " + messageX + " line CDATA #REQUIRED >"); + r.append(EOL + " <!ATTLIST " + messageX + " text CDATA #IMPLIED >"); // but Message requires non-null... + r.append(EOL + " <!ATTLIST " + messageX + " file CDATA #IMPLIED >"); + r.append(EOL + ""); + r.append(EOL + " <!ELEMENT " + dirchangesX + " (#PCDATA)>"); + r.append(EOL + " <!ATTLIST " + dirchangesX + " dirToken (classes | run) #IMPLIED >"); + r.append(EOL + " <!ATTLIST " + dirchangesX + " defaultSuffix (.class) #IMPLIED >"); + r.append(EOL + " <!ATTLIST " + dirchangesX + " added CDATA #IMPLIED >"); + r.append(EOL + " <!ATTLIST " + dirchangesX + " removed CDATA #IMPLIED >"); + r.append(EOL + " <!ATTLIST " + dirchangesX + " updated CDATA #IMPLIED >"); + r.append(EOL + " <!ATTLIST " + dirchangesX + " unchanged CDATA #IMPLIED >"); + r.append(EOL + ""); + return r.toString(); + } + + private static final AjcSpecXmlReader ME + = new AjcSpecXmlReader(); + + /** @return shared instance */ + public static final AjcSpecXmlReader getReader() { + return ME; + } + + public static void main(String[] a) throws IOException { + writeDTD(new File("c:/home/wes/aj/aspectj/modules/tests/ajcTestSuite2.dtd")); + } + /** + * Write a DTD to dtdFile. + * @param dtdFile the File to write to + */ + public static void writeDTD(File dtdFile) throws IOException { + LangUtil.throwIaxIfNull(dtdFile, "dtdFile"); + PrintWriter out = new PrintWriter(new FileWriter(dtdFile)); + try { + out.println("<!-- document type for ajc test suite - see " + + AjcSpecXmlReader.class.getName() + " -->"); + out.println(getDocType()); + } finally { + out.close(); + } + } + + private static final String[] LOG = new String[] {"info", "debug", "trace" }; + + private int logLevel; + + private AjcSpecXmlReader() {} + + /** @param level 0..2, info..trace */ + public void setLogLevel(int level) { + if (level < 0) { + level = 0; + } + if (level > 2) { + level = 2; + } + logLevel = level; + } + + /** + * Print an IXmlWritable to the output file + * with our leader and DOCTYPE. + * @param output the File to write to - overwritten + * @param tests the List of IXmlWritable to write + * @return null if no warnings detected, warnings otherwise + */ + public String writeSuiteToXmlFile(File output, IXmlWritable topNode) throws IOException { + PrintWriter writer = new PrintWriter(new FileOutputStream(output)); + XMLWriter printSink = new XMLWriter(writer); + writer.println(""); + writer.println(AjcSpecXmlReader.DOCTYPE); + writer.println(""); + topNode.writeXml(printSink); + writer.close(); + String parent = output.getParent(); + if (null == parent) { + parent = "."; + } + String dtdPath = parent + "/" + DTD_PATH; + File dtdFile = new File(dtdPath); + if (!dtdFile.canRead()) { + return "expecting dtd file: " + dtdFile.getPath(); + } + return null; + } + + + /** + * Read the specifications for a suite of AjcTest from an XML file. + * This also sets the suite dir in the specification. + * @param file the File must be readable, comply with DOCTYPE. + * @return AjcTest.Suite.Spec read from file + * @see setLogLevel(int) + */ + public AjcTest.Suite.Spec readAjcSuite(File file) throws IOException, AbortException { + // setup loggers for digester and beanutils... + System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); // XXX + System.setProperty("org.apache.commons.logging.simplelog.defaultlog", LOG[logLevel]); // trace debug XXX + + final Digester digester = new Digester(); + setupDigester(digester); + + SuiteHolder holder = new SuiteHolder(); + digester.push(holder); + FileInputStream input = new FileInputStream(file); + try { + digester.parse(input); + } catch (SAXException e) { + MessageUtil.fail("parsing " + file, e); + } finally { + if (null != input) { + input.close(); + input = null; + } + } + AjcTest.Suite.Spec result = holder.spec; + if (null != result) { + File suiteDir = file.getParentFile(); + if (null == suiteDir) { + suiteDir = new File("."); // user.dir? + } + result.setSuiteDirFile(suiteDir); + if (result.runtime.isVerbose()) { // XXX hack fixup + RunUtils.enableVerbose(result); + } + } + return result; + } + + /** set up the mapping between the xml and Java. */ + private void setupDigester(Digester digester) { + // XXX supply sax parser to ignore white space? + digester.setValidating(true); + + // element names come from the element components + final String suiteX = AjcTest.Suite.Spec.XMLNAME; + final String ajctestX = suiteX + "/" + AjcTest.Spec.XMLNAME; + final String compileX = ajctestX + "/" + CompilerRun.Spec.XMLNAME; + final String inccompileX = ajctestX + "/" + IncCompilerRun.Spec.XMLNAME; + final String runX = ajctestX + "/" + JavaRun.Spec.XMLNAME; + final String dirchangesX = "*/" + DirChanges.Spec.XMLNAME; + final String messageX = "*/" + SoftMessage.XMLNAME; + final String messageSrcLocX = messageX + "/source-location"; + + // ---- each sub-element needs to be created + // handle messages the same at any level + digester.addObjectCreate(suiteX, AjcTest.Suite.Spec.class.getName()); + digester.addObjectCreate(ajctestX, AjcTest.Spec.class.getName()); + digester.addObjectCreate(compileX, CompilerRun.Spec.class.getName()); + digester.addObjectCreate(compileX + "/file", AbstractRunSpec.WrapFile.class.getName()); + digester.addObjectCreate(inccompileX, IncCompilerRun.Spec.class.getName()); + digester.addObjectCreate(runX, JavaRun.Spec.class.getName()); + digester.addObjectCreate(messageX, SoftMessage.class.getName()); + digester.addObjectCreate(messageSrcLocX, SoftSourceLocation.class.getName()); + digester.addObjectCreate(dirchangesX, DirChanges.Spec.class.getName()); + + // ---- set bean properties for sub-elements created automatically + // -- some remapped - warnings + // - if property exists, map will not be used + digester.addSetProperties(suiteX); // ok to have suite messages and global suite options, etc. + digester.addSetProperties(ajctestX, + new String[] { "title", "dir", "pr"}, + new String[] { "description", "testDirOffset", "bugId"}); + digester.addSetProperties(compileX, + new String[] { "files", "argfiles"}, + new String[] { "paths", "argfiles"}); + digester.addSetProperties(compileX + "/file"); + digester.addSetProperties(inccompileX, "classes", "paths"); + digester.addSetProperties(runX, //"class", "className"); + new String[] { "class", "vm", "skipTester"}, + new String[] { "className", "javaVersion", "skipTester"}); + digester.addSetProperties(dirchangesX); + digester.addSetProperties(messageX); + digester.addSetProperties(messageSrcLocX); + digester.addSetProperties(messageX, "kind", "kindAsString"); + digester.addSetProperties(messageX, "line", "lineAsString"); + // only file subelement of compile uses text as path... XXX vestigial + digester.addCallMethod(compileX + "/file", "setFile", 0); + + // ---- when subelements are created, add to parent + // add ajctest to suite, runs to ajctest, files to compile, messages to any parent... + // the method name (e.g., "addSuite") is in the parent (SuiteHolder) + // the class (e.g., AjcTest.Suite.Spec) refers to the type of the object created + digester.addSetNext(suiteX, "addSuite", AjcTest.Suite.Spec.class.getName()); + digester.addSetNext(ajctestX, "addChild", AjcTest.Spec.class.getName()); + digester.addSetNext(compileX, "addChild", CompilerRun.Spec.class.getName()); + digester.addSetNext(inccompileX, "addChild", IncCompilerRun.Spec.class.getName()); + digester.addSetNext(runX, "addChild", JavaRun.Spec.class.getName()); + digester.addSetNext(compileX + "/file", "addWrapFile", AbstractRunSpec.WrapFile.class.getName()); + digester.addSetNext(messageX, "addMessage", IMessage.class.getName()); + digester.addSetNext(messageSrcLocX, "setSourceLocation", ISourceLocation.class.getName()); + digester.addSetNext(dirchangesX, "addDirChanges", DirChanges.Spec.class.getName()); + + // can set parent, but prefer to have "knows-about" flow down only... + } + + // ------------------------------------------------------------ testing code + /** + * Get expected bean properties for introspection tests. + * This should return an expected property for every attribute in DOCTYPE, + * using any mapped-to names from setupDigester. + */ + static BProps[] expectedProperties() { + return new BProps[] + { + new BProps(AjcTest.Suite.Spec.class, + new String[] { "suiteDir"}), // verbose removed + new BProps(AjcTest.Spec.class, + new String[] { "description", "testDirOffset", "bugId"}), + // mapped from { "title", "dir", "pr"} + new BProps(CompilerRun.Spec.class, + new String[] { "files", "options"}), + new BProps(IncCompilerRun.Spec.class, + new String[] { "tag" }), + new BProps(JavaRun.Spec.class, + new String[] { "className", "skipTester", "options"}), + // mapped from { "class", ...} + new BProps(DirChanges.Spec.class, + new String[] { "added", "removed", "updated", "unchanged", "dirToken", "defaultSuffix"}), + new BProps(AbstractRunSpec.WrapFile.class, + new String[] { "path"}), + new BProps(SoftMessage.class, + new String[] { "kindAsString", "lineAsString", "text", "file"}) + // mapped from { "kind", "line", ...} + }; + } + + /** + * This is only to do compile-time checking for the APIs impliedly + * used in setupDigester(..). + * The property setter checks are redundant with tests based on + * expectedProperties(). + */ + private static void setupDigesterCompileTimeCheck() { + if (true) { throw new Error("never invoked"); } + AjcTest.Suite.Spec suite = new AjcTest.Suite.Spec(); + AjcTest.Spec test = new AjcTest.Spec(); +// AjcTest test = new AjcTest(); +// test.addRunSpec((AbstractRunSpec) null); +//// test.makeIncCompilerRun((IncCompilerRun.Spec) null); +//// test.makeJavaRun((JavaRun.Spec) null); +// test.setDescription((String) null); +// test.setTestBaseDirOffset((String) null); +// test.setBugId((String) null); +// test.setTestSourceLocation((ISourceLocation) null); + + CompilerRun.Spec crunSpec = new CompilerRun.Spec(); + crunSpec.addMessage((IMessage) null); + // XXX crunSpec.addSourceLocation((ISourceLocation) null); + crunSpec.addWrapFile((AbstractRunSpec.WrapFile) null); + crunSpec.setOptions((String) null); + crunSpec.setPaths((String) null); + + IncCompilerRun.Spec icrunSpec = new IncCompilerRun.Spec(); + icrunSpec.addMessage((IMessage) null); + icrunSpec.setTag((String) null); + + JavaRun.Spec jrunspec = new JavaRun.Spec(); + jrunspec.addMessage((IMessage) null); + jrunspec.setClassName((String) null); + jrunspec.addMessage((IMessage) null); + // input s.b. interpretable by Boolean.valueOf(String) + jrunspec.setSkipTester(true); + + DirChanges.Spec dcspec = new DirChanges.Spec(); + dcspec.setAdded((String) null); + dcspec.setRemoved((String) null); + dcspec.setUpdated((String) null); + dcspec.setDefaultSuffix((String) null); + dcspec.setDirToken((String) null); + + SoftMessage m = new SoftMessage(); + m.setSourceLocation((ISourceLocation) null); + m.setText((String) null); + m.setKindAsString((String) null); + + SoftSourceLocation sl = new SoftSourceLocation(); + sl.setFile((String) null); + sl.setLine((String) null); + sl.setColumn((String) null); + sl.setEndLine((String) null); + + // add attribute setters to validate? + } + + /** top element on Digester stack holds the test suite */ + public static class SuiteHolder { + AjcTest.Suite.Spec spec; + public void addSuite(AjcTest.Suite.Spec spec) { + this.spec = spec; + } + } + + /** hold class/properties association for testing */ + static class BProps { + final Class cl; + final String[] props; + BProps(Class cl, String[] props) { + this.cl = cl; + this.props = props; + } + } +} + + diff --git a/testing/src/org/aspectj/testing/xml/IXmlWritable.java b/testing/src/org/aspectj/testing/xml/IXmlWritable.java new file mode 100644 index 000000000..52f43c389 --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/IXmlWritable.java @@ -0,0 +1,25 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +/** + * + */ +public interface IXmlWritable { + /** + * Write self out to XML. + * @param out the XMLWriter to write to + */ + void writeXml(XMLWriter out); +} diff --git a/testing/src/org/aspectj/testing/xml/SoftMessage.java b/testing/src/org/aspectj/testing/xml/SoftMessage.java new file mode 100644 index 000000000..e57a4f3de --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/SoftMessage.java @@ -0,0 +1,287 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.xml; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.SourceLocation; +import org.aspectj.util.LangUtil; + +import java.io.File; +import java.util.Iterator; +import java.util.List; + + +/** + * Implement messages. + * This implementation is immutable if ISourceLocation is immutable. + */ +public class SoftMessage implements IMessage { // XXX mutable dup of Message + public static String XMLNAME = "message"; + public static final File NO_FILE = ISourceLocation.NO_FILE; + private String message; + private IMessage.Kind kind; + private Throwable thrown; + private ISourceLocation sourceLocation; + + + //private ISourceLocation pseudoSourceLocation; // set directly + // collapse enclosed source location for shorter, property-based xml + private String file; + private int line = Integer.MAX_VALUE; + + /** convenience for constructing failure messages */ + public static SoftMessage fail(String message, Throwable thrown) { + return new SoftMessage(message, IMessage.FAIL, thrown, null); + } + + /** + * Print messages. + * @param messages List of IMessage + */ + public static void writeXml(XMLWriter out, List messages) { + if ((null == out) || (null == messages)) { + return; + } + for (Iterator iter = messages.iterator(); iter.hasNext();) { + writeXml(out, (IMessage) iter.next()); + + } + } + + /** + * Print messages. + * @param messages IMessage[] + */ + public static void writeXml(XMLWriter out, IMessage[] messages) { + if ((null == out) || (null == messages)) { + return; + } + for (int i = 0; i < messages.length; i++) { + writeXml(out, messages[i]); + } + } + + /** print message as an element */ + public static void writeXml(XMLWriter out, IMessage message) { // XXX short form only, no files + if ((null == out) || (null == message)) { + return; + } + final String elementName = XMLNAME; + String kindStr = message.getKind().toString(); + String kindAttr = out.makeAttribute("kind",kindStr); + String mStr = message.getMessage(); + if ((null != mStr) && (0 == mStr.length())) { + mStr = null; + } + String mAttr = (null == mStr ? " " : " " + out.makeAttribute("text", mStr)); + int mAttrLen = (null == mStr ? 0 : mAttr.length()); + ISourceLocation sl = message.getISourceLocation(); + String lineStr = (null == sl ? null : "" + sl.getLine()); + String lineAttr = (null == lineStr ? " " : " " + out.makeAttribute("line", lineStr)); + int lineAttrLen = (null == lineStr ? 0 : lineAttr.length()); + int len = (kindAttr.length() + mAttrLen + lineAttrLen); + if (len < 65) { + String s = kindAttr + " " + + ((null == lineStr ? "" : lineAttr) + + (null == mStr ? "" : " " + mAttr)).trim(); + out.printElement(elementName, s); + } else { + out.startElement(elementName, kindAttr + lineAttr, false); + if (0 < mStr.length()) { + out.printAttribute("text", mStr); + } + out.endAttributes(); + out.endElement(elementName); + } + } + + + + public SoftMessage() {} // XXX programmatic only + + /** + * Create a (compiler) error or warning message + * @param message the String used as the underlying message + * @param sourceLocation the ISourceLocation, if any, associated with this message + * @param isError if true, use IMessage.ERROR; else use IMessage.WARNING + */ + public SoftMessage(String message, ISourceLocation location, boolean isError) { + this(message, (isError ? IMessage.ERROR : IMessage.WARNING), null, + location); + } + + /** + * Create a message, handling null values for message and kind + * if thrown is not null. + * @param message the String used as the underlying message + * @param kind the IMessage.Kind of message - not null + * @param thrown the Throwable, if any, associated with this message + * @param sourceLocation the ISourceLocation, if any, associated with this message + * @throws IllegalArgumentException if message is null and + * thrown is null or has a null message, or if kind is null + * and thrown is null. + */ + public SoftMessage(String message, IMessage.Kind kind, Throwable thrown, + ISourceLocation sourceLocation) { + this.message = message; + this.kind = kind; + this.thrown = thrown; + this.sourceLocation = sourceLocation; + if (null == message) { + if (null != thrown) { + message = thrown.getMessage(); + } + if (null == message) { + throw new IllegalArgumentException("null message"); + } + } + if (null == kind) { + throw new IllegalArgumentException("null kind"); + } + } + + /** @return the kind of this message */ + public IMessage.Kind getKind() { + return kind; + } + + /** @return true if kind == IMessage.ERROR */ + public boolean isError() { + return kind == IMessage.ERROR; + } + + /** @return true if kind == IMessage.WARNING */ + public boolean isWarning() { + return kind == IMessage.WARNING; + } + + /** @return true if kind == IMessage.DEBUG */ + public boolean isDebug() { + return kind == IMessage.DEBUG; + } + + /** + * @return true if kind == IMessage.INFO + */ + public boolean isInfo() { + return kind == IMessage.INFO; + } + + /** @return true if kind == IMessage.ABORT */ + public boolean isAbort() { + return kind == IMessage.ABORT; + } + + /** + * @return true if kind == IMessage.FAIL + */ + public boolean isFailed() { + return kind == IMessage.FAIL; + } + + /** @return non-null String with simple message */ + final public String getMessage() { + return message; + } + + /** @return Throwable associated with this message, or null if none */ + final public Throwable getThrown() { + return thrown; + } + + /** + * This returns any ISourceLocation set or a mock-up + * if file and/or line were set. + * @return ISourceLocation associated with this message, + * a mock-up if file or line is available, or null if none + */ + final public ISourceLocation getISourceLocation() { + if ((null == sourceLocation) + && ((null != file) || (line != Integer.MAX_VALUE))) { + File f = (null == file ? NO_FILE : new File(file)); + int line = (this.line == Integer.MAX_VALUE ? 0 : this.line); + sourceLocation = new SourceLocation(f, line); + } + return sourceLocation; + } + + /** set the kind of this message */ + public void setMessageKind(IMessage.Kind kind) { + this.kind = (null == kind ? IMessage.ERROR : kind); + } + + + /** set the file for the underlying source location of this message + * @throws IllegalStateException if source location was set directly + * or indirectly by calling getSourceLocation after setting + * file or line. + */ + public void setFile(String path) { + LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(path), "empty path"); + if (null != sourceLocation) { + throw new IllegalStateException("cannot set line after creating source location"); + } + this.file = path; + } + + /** set the kind of this message */ + public void setKindAsString(String kind) { + setMessageKind(MessageUtil.getKind(kind)); + } + + public void setSourceLocation(ISourceLocation sourceLocation) { + this.sourceLocation = sourceLocation; + } + + /** + * Set the line for the underlying source location. + * @throws IllegalStateException if source location was set directly + * or indirectly by calling getSourceLocation after setting + * file or line. + */ + public void setLineAsString(String line) { + if (null != sourceLocation) { + throw new IllegalStateException("cannot set line after creating source location"); + } + this.line = Integer.valueOf(line).intValue(); + SourceLocation.validLine(this.line); + } + + public void setText(String text) { + this.message = (null == text ? "" : text); + } + + public String toString() { + StringBuffer result = new StringBuffer(); + + result.append(getKind().toString()); + + String messageString = getMessage(); + if (!LangUtil.isEmpty(messageString)) { + result.append(messageString); + } + + ISourceLocation loc = getISourceLocation(); + if ((null != loc) && (loc != ISourceLocation.NO_FILE)) { + result.append(" at " + loc); + } + if (null != thrown) { + result.append(" -- " + LangUtil.renderExceptionShort(thrown)); + } + return result.toString(); + } +} diff --git a/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java b/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java new file mode 100644 index 000000000..2b3dfdad7 --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/SoftSourceLocation.java @@ -0,0 +1,89 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + + +import org.aspectj.bridge.ISourceLocation; + +import java.io.File; + +/** + * Immutable source location. + * This guarantees that the source file is not null + * and that the numeric values are positive and line <= endLine. + * @see org.aspectj.lang.reflect.SourceLocation + * @see org.aspectj.compiler.base.parser.SourceInfo + * @see org.aspectj.tools.ide.SourceLine + * @see org.aspectj.testing.harness.ErrorLine + */ +public class SoftSourceLocation implements ISourceLocation { // XXX endLine? + public static final File NONE = new File("SoftSourceLocation.NONE"); + private File sourceFile; + private int line; + private int column; + private int endLine; + + public SoftSourceLocation() { + } + + public File getSourceFile() { + return (null != sourceFile ? sourceFile : NONE); + } + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + + public int getEndLine() { + return line; + } + + public void setFile(String sourceFile) { + this.sourceFile = new File(sourceFile); + } + + public void setLine(String line) { + this.line = convert(line); + if (0 == endLine) { + endLine = this.line; + } + } + + public void setColumn(String column) { + this.column = convert(column); + } + + public void setEndLine(String line) { + this.endLine = convert(line); + } + + + private int convert(String in) { + return Integer.valueOf(in).intValue(); + } + + public String getLocationContext() { + return null; + } + + /** @return String : file:line:column */ + public String toString() { + return getSourceFile().getPath() + ":" + getLine() + ":" + getColumn(); + } + + +} diff --git a/testing/src/org/aspectj/testing/xml/XMLWriter.java b/testing/src/org/aspectj/testing/xml/XMLWriter.java new file mode 100644 index 000000000..fc2eda919 --- /dev/null +++ b/testing/src/org/aspectj/testing/xml/XMLWriter.java @@ -0,0 +1,353 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +import org.aspectj.util.LangUtil; + +import java.io.PrintWriter; +import java.util.List; +import java.util.Stack; + +/** + * Manage print stream to an XML document. + * This tracks start/end elements and signals on error, + * and optionally lineates buffer by accumulating + * up to a maximum width (including indent). + * This also has utilities for un/flattening lists and + * rendering buffer. + */ +public class XMLWriter { + static final String SP = " "; + static final String TAB = SP + SP; + + /** maximum value for maxWidth, when flowing buffer */ + public static final int MAX_WIDTH = 8000; + + /** default value for maxWidth, when flowing buffer */ + public static final int DEFAULT_WIDTH = 80; + + /** currently this just strips quotes and ampersands */ + public static String attributeValue(String input) { + input = input.replace('"', '~'); + input = input.replace('&', '='); + return input; + } + + /** @return name="{attributeValue({value})" */ + public static String makeAttribute(String name, String value) { + return (name + "=\"" + attributeValue(value) + "\""); + } + + /** same as flattenList, except also normalize \ -> / */ + public static String flattenFiles(String[] strings) { + return flattenList(strings).replace('\\', '/'); + } + + /** same as flattenList, except also normalize \ -> / */ + public static String flattenFiles(List paths) { + return flattenList(paths).replace('\\', '/'); + } + + /** + * Expand comma-delimited String into list of values, without trimming + * @param list List of items to print - null is silently ignored, + * so for empty items use "" + * @return String[]{} for null input, String[] {input} for input without comma, + * or new String[] {items..} otherwise + * @throws IllegalArgumentException if {any item}.toString() contains a comma + */ + public static String[] unflattenList(String input) { + return (String[]) LangUtil.commaSplit(input).toArray(new String[0]); + } + + /** flatten input and add to list */ + public static void addFlattenedItems(List list, String input) { + LangUtil.throwIaxIfNull(list, "list"); + if (null != input) { + String[] items = XMLWriter.unflattenList(input); + if (!LangUtil.isEmpty(items)) { + for (int i = 0; i < items.length; i++) { + if (!LangUtil.isEmpty(items[i])) { + list.add(items[i]); + } + } + } + } + } + + /** + * Collapse list into a single comma-delimited value (e.g., for list buffer) + * @param list List of items to print - null is silently ignored, + * so for empty items use "" + * @return item{,item}... + * @throws IllegalArgumentException if {any item}.toString() contains a comma + */ + public static String flattenList(List list) { + if ((null == list) || (0 == list.size())) { + return ""; + } + return flattenList(list.toArray()); + } + + + /** + * Collapse list into a single comma-delimited value (e.g., for list buffer) + * @param list the String[] items to print - null is silently ignored, + * so for empty items use "" + * @return item{,item}... + * @throws IllegalArgumentException if list[i].toString() contains a comma + */ + public static String flattenList(Object[] list) { + StringBuffer sb = new StringBuffer(); + if (null != list) { + boolean printed = false; + for (int i = 0; i < list.length; i++) { + Object o = list[i]; + if (null != o) { + if (printed) { + sb.append(","); + } else { + printed = true; + } + String s = o.toString(); + if (-1 != s.indexOf(",")) { + throw new IllegalArgumentException("comma in " + s); + } + sb.append(s); + } + } + } + return sb.toString(); + } + + /** output sink */ + PrintWriter out; + + /** stack of String element names */ + Stack stack = new Stack(); + + /** false if doing attributes */ + boolean attributesDone = true; + + /** current element prefix */ + String indent = ""; + + /** maximum width (in char) of indent and buffer when flowing */ + int maxWidth; + + /** + * Current text being flowed. + * length() is always less than maxWidth. + */ + StringBuffer buffer; + + /** @param out PrintWriter to print to - not null */ + public XMLWriter(PrintWriter out) { + LangUtil.throwIaxIfNull(out, "out"); + this.out = out; + buffer = new StringBuffer(); + maxWidth = DEFAULT_WIDTH; + } + + /** + * Set maximum width (in chars) of buffer to accumulate. + * @param maxWidth int 0..MAX_WIDTH for maximum number of char to accumulate + */ + public void setMaxWidth(int maxWidth) { + if (0 > maxWidth) { + this.maxWidth = 0; + } else if (MAX_WIDTH < maxWidth) { + this.maxWidth = MAX_WIDTH; + } else { + this.maxWidth = maxWidth; + } + } + + /** shortcut for entire element */ + public void printElement(String name, String attributes) { + if (!attributesDone) throw new IllegalStateException("finish attributes"); + if (0 != buffer.length()) { // output on subelement + outPrintln(buffer + ">"); + buffer.setLength(0); + } + String oldIndent = indent; + if (0 < stack.size()) { + indent += TAB; + ((StackElement) stack.peek()).numChildren++; + } + outPrintln(indent + "<" + name + " " + attributes + "/>"); + indent = oldIndent; + } + + /** + * Start element only + * @param name the String label of the element + * @param closeTag if true, delimit the end of the starting tag + */ + public void startElement(String name, boolean closeTag) { + startElement(name, "", closeTag); + } + + /** + * Start element with buffer on the same line. + * This does not flow buffer. + * @param name String tag for the element + * @param attr {name="value"}.. where value + * is a product of attributeValue(String) + */ + public void startElement(String name, String attr, boolean closeTag) { + if (!attributesDone) throw new IllegalStateException("finish attributes"); + LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(name), "empty name"); + + if (0 != buffer.length()) { // output on subelement + outPrintln(buffer + ">"); + buffer.setLength(0); + } + if (0 < stack.size()) { + indent += TAB; + } + StringBuffer sb = new StringBuffer(); + sb.append(indent); + sb.append("<"); + sb.append(name); + + if (!LangUtil.isEmpty(attr)) { + sb.append(" "); + sb.append(attr.trim()); + } + attributesDone = closeTag; + if (closeTag) { + sb.append(">"); + outPrintln(sb.toString()); + } else if (maxWidth <= sb.length()) { + outPrintln(sb.toString()); + } else { + if (0 != this.buffer.length()) { + throw new IllegalStateException("expected empty attributes starting " + name); + } + this.buffer.append(sb.toString()); + } + if (0 < stack.size()) { + ((StackElement) stack.peek()).numChildren++; + } + stack.push(new StackElement(name)); + } + + /** + * @param name should be the same as that given to start the element + * @throws IllegalStateException if start element does not match + */ + public void endElement(String name) { + int level = stack.size(); + String err = null; + StackElement element = null; + if (0 == stack.size()) { + err = "empty stack"; + } else { + element = (StackElement) stack.pop(); + if (!element.name.equals(name)) { + err = "expecting element " + element.name; + } + } + if (null != err) { + err = "endElement(" + name + ") " + stack + ": " + err; + throw new IllegalStateException(err); + } + if (0 < element.numChildren) { + outPrintln(indent + "</" + name + ">"); + } else if (0 < buffer.length()) { + outPrintln(buffer + "/>"); + buffer.setLength(0); + } else { + outPrintln(indent + "/>"); + } + if (!attributesDone) { + attributesDone = true; + } + if (0 < stack.size()) { + indent = indent.substring(0, indent.length() - TAB.length()); + } + } + + + /** + * Print name=value if neither is null and name is not empty after trimming, + * accumulating these until they are greater than maxWidth or buffer are + * terminated with endAttributes(..) or endElement(..). + * @param value the String to convert as attribute value - ignored if null + * @param name the String to use as the attribute name + * @throws IllegalArgumentException if name is null or empty after trimming + */ + public void printAttribute(String name, String value) { + if (attributesDone) throw new IllegalStateException("not in attributes"); + if (null == value) { + return; + } + if ((null == name) || (0 == name.trim().length())) { + throw new IllegalArgumentException("no name=" + name + "=" + value); + } + + String newAttr = name + "=\"" + attributeValue(value) + "\""; + int indentLen = indent.length(); + int bufferLen = buffer.length(); + int newAttrLen = (0 == bufferLen ? indentLen : 0) + newAttr.length(); + + if (maxWidth > (bufferLen + newAttrLen)) { + buffer.append(" "); + buffer.append(newAttr); + } else { // at least print old attributes; maybe also new + if (0 < bufferLen) { + outPrintln(buffer.toString()); + buffer.setLength(0); + } + buffer.append(indent + SP + newAttr); + } + } + + public void endAttributes() { + if (attributesDone) throw new IllegalStateException("not in attributes"); + attributesDone = true; + } + + public void printComment(String comment) { + if (!attributesDone) throw new IllegalStateException("in attributes"); + outPrintln(indent + "<!-- " + comment + "-->"); + } + + public void close() { + if (null != out) { + out.close(); + } + + } + + public void println(String string) { + outPrintln(string); + } + + private void outPrintln(String s) { + if (null == out) { + throw new IllegalStateException("used after close"); + } + out.println(s); + } + static class StackElement { + String name; + int numChildren; + public StackElement(String name) { + this.name = name; + } + } + +} diff --git a/testing/testdata/harnessList.txt b/testing/testdata/harnessList.txt new file mode 100644 index 000000000..2b146c125 --- /dev/null +++ b/testing/testdata/harnessList.txt @@ -0,0 +1 @@ +@../../../tests/harnessPasses.txt diff --git a/testing/testdata/suite.dtd b/testing/testdata/suite.dtd new file mode 100644 index 000000000..65025672a --- /dev/null +++ b/testing/testdata/suite.dtd @@ -0,0 +1,6 @@ +<!DOCTYPE suite [
+ <!ELEMENT suite (ajc-test+)>
+ <!ELEMENT ajc-test (compile+,run+)>
+ <!ELEMENT compile (#PCDATA)>
+ <!ELEMENT run (#PCDATA)>
+ ]>
\ No newline at end of file diff --git a/testing/testdata/suite.xml b/testing/testdata/suite.xml new file mode 100644 index 000000000..660af524f --- /dev/null +++ b/testing/testdata/suite.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> + +<!DOCTYPE suite [ + <!ELEMENT suite (ajc-test+)> + <!ELEMENT ajc-test (compile*,run*)> + <!ATTLIST ajc-test title CDATA #REQUIRED > + <!ATTLIST ajc-test dir CDATA #REQUIRED > + <!ATTLIST ajc-test pr CDATA #IMPLIED > + + <!ELEMENT compile (file*,message*)> + + <!ELEMENT run (#PCDATA)> + <!ATTLIST run class CDATA #REQUIRED > + <!ATTLIST run args CDATA "" > + + <!ELEMENT file (#PCDATA)> + <!ELEMENT message (#PCDATA)> + <!ATTLIST message kind (error | warning | Xlint) #REQUIRED > + <!ATTLIST message line CDATA #REQUIRED > + <!ATTLIST message file CDATA #IMPLIED > + ]> + + + +<suite> +<!--correct behavior for 1.0 is good error message, see real test in knownbugs--> +<ajc-test title="harness error test" + dir="harness" pr="9998"> +<compile file="ErrorTest.java"> +<message kind="error" line="5"/> +<message kind="error" line="6"/> +</compile> +</ajc-test> + + +<ajc-test title="harness test with run" + dir="harness" pr="101"> +<compile file="TestNoTester.java"/> +<run class="TestNoTester"/> +</ajc-test> + + +</suite>
\ No newline at end of file diff --git a/testing/testsrc/TestingModuleTests.java b/testing/testsrc/TestingModuleTests.java new file mode 100644 index 000000000..a07fd8617 --- /dev/null +++ b/testing/testsrc/TestingModuleTests.java @@ -0,0 +1,31 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +// default package + +import junit.framework.*; +import junit.framework.Test; + +public class TestingModuleTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(TestingModuleTests.class.getName()); + suite.addTest(org.aspectj.testing.util.UtilTests.suite()); + suite.addTest(org.aspectj.testing.xml.TestingXmlTests.suite()); + return suite; + } + + public TestingModuleTests(String name) { super(name); } + +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/AbstractRunSpecTest.java b/testing/testsrc/org/aspectj/testing/harness/bridge/AbstractRunSpecTest.java new file mode 100644 index 000000000..0e5b8651f --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/AbstractRunSpecTest.java @@ -0,0 +1,65 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.xml.XMLWriter; + +import java.io.PrintWriter; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public class AbstractRunSpecTest extends TestCase { + + public AbstractRunSpecTest(String name) { + super(name); + } + + public void skiptestXmlWrite() { + AbstractRunSpec spec = new TestSpec(); + spec.setOptions("-option1,-option2"); + spec.setKeywords("keyword1, keyword2"); + spec.setPaths("path1.java, path2.java"); + spec.setDescription("some description, with extra"); + XMLWriter out = new XMLWriter(new PrintWriter(System.out)); + spec.writeXml(out); + out.close(); + } + + public void testSetOptions() { + AbstractRunSpec spec = new TestSpec(); + spec.setOptions("1,2"); + List options = spec.getOptionsList(); + String s = "" + options; + assertTrue(s, "[1, 2]".equals(s)); + } + + static class TestSpec extends AbstractRunSpec { + TestSpec() { + super("testspec"); + } + /** + * @see org.aspectj.testing.harness.bridge.AbstractRunSpec#makeRunIterator(Sandbox, Validator) + */ + public IRunIterator makeRunIterator( + Sandbox sandbox, + Validator validator) { + return null; + } + } +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/AjcSpecTest.java b/testing/testsrc/org/aspectj/testing/harness/bridge/AjcSpecTest.java new file mode 100644 index 000000000..635790158 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/AjcSpecTest.java @@ -0,0 +1,340 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.harness.bridge.AjcTest.Suite.Spec; +import org.aspectj.testing.xml.AjcSpecXmlReaderTest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import junit.framework.Assert; +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +/** + * Primarily used by others to test AjcTest + */ +public class AjcSpecTest extends TestCase { + public static final String NOTSAME = " != "; + public static void sameAjcSuiteSpec( + AjcTest.Suite.Spec lhsSpec, + AjcTest.Suite.Spec rhsSpec, + Assert a) { + assertNotNull(lhsSpec); + assertNotNull(rhsSpec); + Iterator lhs = lhsSpec.getChildren().iterator(); + Iterator rhs = rhsSpec.getChildren().iterator(); + while (lhs.hasNext() && rhs.hasNext()) { + AjcTest.Spec lhsTest = (AjcTest.Spec) lhs.next(); + AjcTest.Spec rhsTest = (AjcTest.Spec) rhs.next(); + AjcSpecTest.sameAjcTestSpec(lhsTest, rhsTest, a); + } + a.assertTrue(!lhs.hasNext()); + a.assertTrue(!rhs.hasNext()); + } + + public static void sameAjcTestSpec( + AjcTest.Spec lhsTest, + AjcTest.Spec rhsTest, + Assert a) { + a.assertNotNull(lhsTest); + a.assertNotNull(rhsTest); + a.assertEquals(lhsTest.getBugId(), rhsTest.getBugId()); + a.assertEquals(lhsTest.getTestDirOffset(), rhsTest.getTestDirOffset()); + // XXX suiteDir varies by run.. + sameAbstractRunSpec(lhsTest, rhsTest, a); + } + + public static void sameAbstractRunSpec( + AbstractRunSpec lhs, + AbstractRunSpec rhs, + Assert a) { + a.assertEquals(lhs.description, rhs.description); + // XXX keywords added in .txt reading - + //sameList(lhs.getKeywordsList(), rhs.getKeywordsList(), a); + // XXX sameList(lhs.globalOptions, rhs.globalOptions, a); + sameList(lhs.getOptionsList(), rhs.getOptionsList(), a); + sameList(lhs.getPathsList(), rhs.getPathsList(), a); + // xml adds sourceloc? + //sameSourceLocation(lhs.getSourceLocation(), rhs.getSourceLocation(), a); + // XXX also sourceLocations? + sameMessages(lhs.getMessages(), rhs.getMessages(), a); + } + + /** @return normal form - null is "", "" is "", and others are {fully.qualified.class}.toString().trim() */ + static String normal(Object input) { + if ((null == input) || ("".equals(input))) { + return ""; + } else { + return input.getClass().getName() + "." + input.toString().trim(); + } + } + + /** @return true if these match after normalizing */ + public static void same(Object lhs, Object rhs, Assert a) { + lhs = normal(lhs); + rhs = normal(rhs); + a.assertTrue(lhs + NOTSAME + rhs, lhs.equals(rhs)); + } + + /** @return true if both are empty (null or no entries) or if all match */ + public static void sameRA(String[] lhs, String[] rhs, Assert a) { + if (null == lhs) { + a.assertTrue((null == rhs) || (0 == rhs.length)); + } else if (null == rhs) { + a.assertTrue(0 == lhs.length); + } else { + String l = normal(lhs); + String r = normal(rhs); + a.assertTrue(l + NOTSAME + r, l.equals(r)); + } + } + + /** @return normal form for String[] items*/ + static String normal(String[] items) { + return (null == items ? "[]" : normal(Arrays.asList(items))); + } + + /** @return normal form for list items */ + static String normal(List list) { + StringBuffer sb = new StringBuffer(); + sb.append("["); + boolean first = true; + for (Iterator iter = list.iterator(); iter.hasNext();) { + Object o = iter.next(); + if (!first) { + sb.append(", "); + } else { + first = false; + } + sb.append(normal(o)); + } + sb.append("]"); + return sb.toString(); + } + + /** @return true if both are empty (null or no entries) or if all match after trimming */ + public static void sameListSize(List lhs, List rhs, Assert a) { + if (null == lhs) { + a.assertTrue((null == rhs) || (0 == rhs.size())); + } else if (null == rhs) { + a.assertTrue(0 == lhs.size()); + } else { + a.assertTrue(rhs.size() == lhs.size()); + } + } + + /** @return true if both are empty (null or no entries) or if all match after trimming */ + public static void sameList(List lhs, List rhs, Assert a) { + sameListSize(lhs, rhs, a); + String l = normal(lhs); + String r = normal(rhs); + String label = l + NOTSAME + r; + a.assertTrue(label, l.equals(r)); + } + +// /** +// * Normalize and compare: +// * <li>bug id's are not compared since extracted during xml writing</li> +// * <li>keyword compare is disabled since keywords are generated during xml reading.</li> +// * <li>description compare is normalized by stripping bug ids</li> +// * <li>String and arrays are equal when empty (null or 0-length)</li> +// * @see Ajctest#stripBugId(String) +// */ +// public static void sameAjcTest(AjcTest lhs, AjcTest rhs, Assert reporter) { +// Assert a = reporter; +// String label = lhs + NOTSAME + rhs; +// a.assertTrue(label, null != lhs); +// a.assertTrue(label, null != rhs); +// //a.assertTrue(label, lhs.ignoreWarnings == rhs.ignoreWarnings); +// // XXX disabled - not in .txt +// // sameStringList(lhs.keywords, rhs.keywords, a); +// // sameString(lhs.bugId, rhs.bugId, a); +// // argh - bugid stripped from description +// //same(AjcTest.stripBugId(lhs.description), AjcTest.stripBugId(lhs.description), a); +// //sameRA(lhs.globals, rhs.globals, a); +// //lhs.reset(); +// //rhs.reset(); +// boolean gotOne = false; +// IMessageHolder holder = new MessageHandler(); +// a.assertTrue(label, !holder.hasAnyMessage(IMessage.FAIL, IMessageHolder.ORGREATER)); +// while (lhs.hasNextRun() && rhs.hasNextRun()) { +// sameIAjcRun((IAjcRun) lhs.nextRun(holder), (IAjcRun) rhs.nextRun(holder), reporter); +// a.assertTrue(label, !holder.hasAnyMessage(IMessage.FAIL, IMessageHolder.ORGREATER)); +// if (!gotOne) { +// gotOne = true; +// } +// } +// a.assertTrue(label, gotOne); +// a.assertTrue(label, !lhs.hasNextRun()); +// a.assertTrue(label, !rhs.hasNextRun()); +// } + + public static void sameIAjcRun(IAjcRun lhs, IAjcRun rhs, Assert reporter) { + Assert a = reporter; + a.assertTrue(lhs != null); + a.assertTrue(rhs != null); + Class c = lhs.getClass(); + a.assertTrue(c == rhs.getClass()); + AbstractRunSpec lhsSpec; + AbstractRunSpec rhsSpec; + + if (c == CompilerRun.class) { + CompilerRun.Spec l = ((CompilerRun) lhs).spec; + CompilerRun.Spec r = ((CompilerRun) rhs).spec; + lhsSpec = l; + rhsSpec = r; + a.assertEquals(l.testSrcDirOffset, r.testSrcDirOffset); + a.assertEquals(l.compiler, r.compiler); + } else if (c == JavaRun.class) { + JavaRun.Spec l = ((JavaRun) lhs).spec; + JavaRun.Spec r = ((JavaRun) rhs).spec; + lhsSpec = l; + rhsSpec = r; + a.assertTrue(l.skipTester == r.skipTester); + a.assertEquals(l.className, r.className); + } else if (c == IncCompilerRun.class) { + IncCompilerRun.Spec l = ((IncCompilerRun) lhs).spec; + IncCompilerRun.Spec r = ((IncCompilerRun) rhs).spec; + lhsSpec = l; + rhsSpec = r; + } else { + assertTrue(lhs.equals(rhs)); + return; + } + sameSpec(lhsSpec, rhsSpec, reporter); + } + + public static void sameSpec(AbstractRunSpec lhs, AbstractRunSpec rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(lhs != null); + a.assertTrue(rhs != null); + a.assertEquals(""+lhs.getOptionsList(), ""+rhs.getOptionsList()); + sameList(lhs.getPathsList(), rhs.getPathsList(), a); + sameMessages(lhs.getMessages(), rhs.getMessages(), a); + sameDirChangesList(lhs.dirChanges, rhs.dirChanges, a); + } + + public static void sameDirChangesList(ArrayList lhs, ArrayList rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(rhs != null); + a.assertTrue(lhs != null); + sameListSize(lhs, rhs, a); + Iterator lhsIter = lhs.iterator(); + Iterator rhsIter = rhs.iterator(); + while (lhsIter.hasNext() && rhsIter.hasNext()) { + sameDirChangesSpec((DirChanges.Spec) lhsIter.next(), (DirChanges.Spec) rhsIter.next(), a); + } + } + + public static void sameDirChangesSpec(DirChanges.Spec lhs, DirChanges.Spec rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(rhs != null); + a.assertTrue(lhs != null); + a.assertEquals(lhs.defaultSuffix, rhs.defaultSuffix); + a.assertEquals(lhs.dirToken, rhs.dirToken); + sameList(lhs.updated, rhs.updated, a); + sameList(lhs.removed, rhs.removed, a); + sameList(lhs.added, rhs.added, a); + } + + public static void sameMessages(List one, List two, Assert a) { + if ((null == one) && (null == two)) { + return; + } + Iterator lhs = one.iterator(); + Iterator rhs = two.iterator(); + while (lhs.hasNext() && rhs.hasNext()) { + sameMessage((IMessage) lhs.next(), (IMessage) rhs.next(), a); + } + assertTrue(!lhs.hasNext()); + assertTrue(!rhs.hasNext()); + } + + public static void sameMessage(IMessage lhs, IMessage rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(lhs != null); + a.assertTrue(rhs != null); + a.assertTrue(lhs.getKind() == rhs.getKind()); + same(lhs.getMessage(), rhs.getMessage(), a); + a.assertEquals(lhs.getThrown(), rhs.getThrown()); + sameSourceLocation(lhs.getISourceLocation(), rhs.getISourceLocation(), a); + } + + public static void sameSourceLocation(ISourceLocation lhs, ISourceLocation rhs, Assert a) { + if ((null == lhs) && (null == rhs)) { + return; + } + a.assertTrue(lhs != null); + a.assertTrue(rhs != null); + a.assertTrue(lhs.getLine() == rhs.getLine()); + a.assertTrue(lhs.getColumn() == rhs.getColumn()); + a.assertTrue(lhs.getEndLine() == rhs.getEndLine()); + // XXX need to compare files, permitting null == NONE + } + + /** + * Constructor for AjcSpecTest. + * @param name + */ + public AjcSpecTest(String name) { + super(name); + } + + public void testMinimal() { + AjcTest.Spec one = new AjcTest.Spec(); + AjcTest.Spec two = new AjcTest.Spec(); + // empty/identity tests + sameAjcTestSpec(one, two, this); + + one.addOption("-one"); + one.addKeyword("keyword"); + one.addPath("path"); + IMessage m = MessageUtil.info("info message"); + one.addMessage(m); + DirChanges.Spec dcspec = new DirChanges.Spec(); + dcspec.setDirToken("dirToken"); + dcspec.setDefaultSuffix(".suffix"); + one.addDirChanges(dcspec); + + // full/identity tests + sameAjcTestSpec(one, one, this); + // XXX need to clone... + + // XXX need to test that more differences are detected + boolean passed = false; + try { + sameAjcTestSpec(one, two, this); + } catch (AssertionFailedError e) { + passed = true; + } + assertTrue("did not get expected exception", passed); + } +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java b/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java new file mode 100644 index 000000000..ddd908b33 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/CompilerRunSpecTest.java @@ -0,0 +1,163 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.MessageHandler; + +import junit.framework.TestCase; + +/** + * + */ +public class CompilerRunSpecTest extends TestCase { + + /** + * Constructor for CompilerRunSpecTest. + * @param name + */ + public CompilerRunSpecTest(String name) { + super(name); + } + + public void testSetupArgs() { + checkSetupArgs("verbose", false); + checkSetupArgs("lenient", false); + checkSetupArgs("strict", false); + checkSetupArgs("ajc", true); // XXX need to predict/test compiler selection + checkSetupArgs("eclipse", true); + } + + void checkSetupArgs(String arg, boolean isTestArg) { + MessageHandler handler = new MessageHandler(); + CompilerRun.Spec spec = new CompilerRun.Spec(); + AbstractRunSpec.RT parentRuntime = new AbstractRunSpec.RT(); + String result; + String expResult; + + // -------- local set + // global ^ (force-off) to disable + spec.setOptions("-" + arg); + parentRuntime.setOptions(new String[] {"^" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + assertTrue(result, "[]".equals(result)); + + // global ! (force-on) does not change local-set + parentRuntime.setOptions(new String[] {"!" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // global - (set) does not change local-set + parentRuntime.setOptions(new String[] {"-" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // global (unset) does not change local-set + parentRuntime.setOptions(new String[] {""}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // -------- local force-on + // global ^ (force-off) conflicts with local force-on + spec.setOptions("!" + arg); + parentRuntime.setOptions(new String[] {"^" + arg}); + assertTrue(!spec.adoptParentValues(parentRuntime, handler)); + assertTrue(0 != handler.numMessages(null, true)); + handler.init(); + + // global ! (force-on) does not change local force-on + parentRuntime.setOptions(new String[] {"!" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // global - (set) does not change local force-on + parentRuntime.setOptions(new String[] {"-" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + // global (unset) does not change local force-on + parentRuntime.setOptions(new String[] {""}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + expResult = (isTestArg ? "[]" : "[-" + arg + "]"); + assertTrue(result, expResult.equals(result)); + + + // -------- local force-off + // global ^ (force-off) does not change local force-off + spec.setOptions("^" + arg); + parentRuntime.setOptions(new String[] {"^" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + assertTrue(result, ("[]").equals(result)); + + // global ! (force-on) conflicts with local force-off + parentRuntime.setOptions(new String[] {"!" + arg}); + assertTrue(!spec.adoptParentValues(parentRuntime, handler)); + assertTrue(0 != handler.numMessages(null, true)); + handler.init(); + + // global - (set) overridden by local force-off // XXX?? + parentRuntime.setOptions(new String[] {"-" + arg}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + assertTrue(result, ("[]").equals(result)); + + // global (unset) does not change local force-off + parentRuntime.setOptions(new String[] {""}); + assertTrue(spec.adoptParentValues(parentRuntime, handler)); + if (0 != handler.numMessages(null, true)) { + assertTrue(handler.toString(), false); + } + result = ""+spec.testSetup.commandOptions; + assertTrue(result, ("[]").equals(result)); + } +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/ParseTestCase.java b/testing/testsrc/org/aspectj/testing/harness/bridge/ParseTestCase.java new file mode 100644 index 000000000..c8a708e13 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/ParseTestCase.java @@ -0,0 +1,228 @@ +/* ******************************************************************* + * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.harness.bridge; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessageHandler; +import org.aspectj.bridge.IMessageHolder; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.SourceLocation; +import org.aspectj.testing.run.IRun; +import org.aspectj.testing.run.IRunIterator; +import org.aspectj.testing.run.IRunListener; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.IRunValidator; +import org.aspectj.testing.run.RunStatus; +import org.aspectj.testing.run.RunValidator; +import org.aspectj.testing.run.Runner; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import junit.framework.TestCase; + +public class ParseTestCase extends TestCase { + + public ParseTestCase(String name) { + super(name); + } + + + public void testParse() throws Exception { // XXX failing b/c of iteration + Runner runner = new Runner(); + IMessageHolder handler = new MessageHandler(); + RunStatus status; + Validator validator = new Validator(handler); + final File suiteFile = new File("../testing/testdata/suite.xml"); + List tests = parseSuite(suiteFile); + Sandbox sandbox = new Sandbox(new File("testdata"), validator); + IRunListener listenerNULL = null; + ISourceLocation sl = new SourceLocation(suiteFile, 0, 0,0); + for (Iterator iter = tests.iterator(); iter.hasNext();) { + status = new RunStatus(handler, runner); + AjcTest.Spec test = (AjcTest.Spec) iter.next(); + test.setSourceLocation(sl); + IRunIterator child = test.makeRunIterator(sandbox, validator); + //test.setup(new String[0], validator); // XXX + //IRun child = runner.wrap(test, null); + // huh? runIterator not generating child status? + //RunStatus childStatus = runner.makeChildStatus(); + runner.runIterator(child, status, listenerNULL); + MessageUtil.print(System.err, status); + } + } + + private List parseSuite(File file) throws ParserConfigurationException, IOException, SAXException{ + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setIgnoringComments(true); + + + DocumentBuilder builder = factory.newDocumentBuilder(); + System.out.println(file.getAbsoluteFile()); + Document doc = builder.parse(file); + + dump(doc.getDocumentElement(), 0); + + List ret = new ArrayList(); + Node suiteNode = doc.getDocumentElement(); + + NodeList children = suiteNode.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + ret.add(parseTest(children.item(i))); + } + + return ret; + } + + private AjcTest.Spec parseTest(Node node) { + String title = getAttributeString(node, "title"); + String pr = getAttributeString(node, "pr"); + String dir = getAttributeString(node, "dir"); + + ISourceLocation sourceLocation = + new SourceLocation(new File("Missing"), 0, 0, 0); + AjcTest.Spec test = new AjcTest.Spec(); + test.setDescription(title); + test.setTestDirOffset(dir); + test.setBugId(Integer.valueOf(pr).intValue()); + test.setSourceLocation(sourceLocation); + //AjcTest test = new AjcTest(title, dir, pr, sourceLocation); + + System.out.println(test); + + List ret = new ArrayList(); + + NodeList children = node.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + test.addChild(parseIRun(test, children.item(i), dir)); +// test.addRunSpec(parseIRun(test, children.item(i), dir)); + } + + return test; + } + + private IRunSpec parseIRun(AjcTest.Spec test, Node node, String baseDir) { + String kind = node.getNodeName(); + if (kind.equals("compile")) { + List args = parseChildrenStrings(node, "arg"); + List files = parseChildrenStrings(node, "file"); + List expectedMessages = parseChildrenMessages(node); + CompilerRun.Spec spec = new CompilerRun.Spec(); + spec.addOptions((String[]) args.toArray(new String[0])); + spec.addPaths((String[]) args.toArray(new String[0])); + spec.addMessages(expectedMessages); + spec.testSrcDirOffset = null; // baseDir; + return spec; + } else if (kind.equals("run")) { + JavaRun.Spec spec = new JavaRun.Spec(); + spec.className = getAttributeString(node, "class"); + spec.addOptions(new String[0]); //??? could add support here + JavaRun run = new JavaRun(spec); + return spec; + } + + return null; + } + + private List parseChildrenMessages(Node node) { + List ret = new ArrayList(); + + NodeList children = node.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeName().equals("message")) { + ret.add(parseMessage(child)); + } + } + return ret; + } + + private IMessage parseMessage(Node child) { + IMessage.Kind kind; + String sKind = getAttributeString(child, "kind"); + if (sKind.equals("error")) { kind = IMessage.ERROR; } + else if (sKind.equals("warning")) { kind = IMessage.WARNING; } + else { + throw new RuntimeException("unknown kind: " + sKind); + } + String filename = getAttributeString(child, "file"); + File file; + if (filename != null) { + file = new File(filename); + } else { + file = new File("XXX"); //XXX + } + + int line = Integer.valueOf(getAttributeString(child, "line")).intValue(); + + ISourceLocation sourceLocation = new SourceLocation(file, line, line, 0); + + return new Message("", kind, null, sourceLocation); + } + + + + private List parseChildrenStrings(Node node, String kind) { + List ret = new ArrayList(); + + NodeList children = node.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeName().equals(kind)) { + Node first = child.getFirstChild(); + if (null != first) { + ret.add(first.getNodeValue());// XXX + } + } + } + return ret; + } + + + + private String getAttributeString(Node node, String name) { + Node attrNode = node.getAttributes().getNamedItem(name); + if (attrNode == null) return null; + return attrNode.getNodeValue(); + } + + + + + private void dump(Node node, int indent) { + for (int i=0; i < indent; i++) System.out.print(" "); + System.out.println(node); + NodeList children = node.getChildNodes(); + for (int i=0; i < children.getLength(); i++) { + dump(children.item(i), indent+1); + } + } + + +} diff --git a/testing/testsrc/org/aspectj/testing/harness/bridge/TestingBridgeTests.java b/testing/testsrc/org/aspectj/testing/harness/bridge/TestingBridgeTests.java new file mode 100644 index 000000000..05d64b01d --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/harness/bridge/TestingBridgeTests.java @@ -0,0 +1,32 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.harness.bridge; + +import junit.framework.*; + +public class TestingBridgeTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(TestingBridgeTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(AjcSpecTest.class); + suite.addTestSuite(ParseTestCase.class); + //$JUnit-END$ + return suite; + } + + public TestingBridgeTests(String name) { super(name); } + +} diff --git a/testing/testsrc/org/aspectj/testing/util/BridgeUtilTest.java b/testing/testsrc/org/aspectj/testing/util/BridgeUtilTest.java new file mode 100644 index 000000000..ae0a7cdac --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/BridgeUtilTest.java @@ -0,0 +1,100 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.run.IRun; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.RunStatus; +import org.aspectj.testing.run.Runner; + +import junit.framework.TestCase; + +/** + * + */ +public class BridgeUtilTest extends TestCase { + + public BridgeUtilTest(String name) { + super(name); + } + + public void testChildString() { + String expect; + String id; + id = "run status identifier"; + expect = "PASS " + id + " 0 tests"; + checkChildString(id, 0, 0, 0, 0, expect); + expect = "PASS " + id + " 2 tests (2 skipped)"; + checkChildString(id, 2, 0, 0, 0, expect); + expect = "PASS " + id + " 3 tests (1 skipped, 2 passed)"; + checkChildString(id, 1, 0, 0, 2, expect); + expect = "FAIL " + id + " 3 tests (1 skipped, 2 failed)"; + checkChildString(id, 1, 0, 2, 0, expect); + expect = "FAIL " + id + " 6 tests (1 skipped, 2 failed, 3 passed)"; + checkChildString(id, 1, 0, 2, 3, expect); + expect = "FAIL " + id + " 1 tests (1 failed)"; + checkChildString(id, 0, 0, 1, 0, expect); + expect = "FAIL " + id + " 4 tests (1 failed, 3 passed)"; + checkChildString(id, 0, 0, 1, 3, expect); + expect = "PASS " + id + " 1 tests (1 passed)"; + checkChildString(id, 0, 0, 0, 1, expect); + + // "incomplete" variants + expect = "PASS " + id + " 5 tests (5 incomplete)"; + checkChildString(id, 0, 5, 0, 0, expect); + expect = "PASS " + id + " 7 tests (2 skipped, 5 incomplete)"; + checkChildString(id, 2, 5, 0, 0, expect); + expect = "PASS " + id + " 8 tests (1 skipped, 5 incomplete, 2 passed)"; + checkChildString(id, 1, 5, 0, 2, expect); + expect = "FAIL " + id + " 8 tests (1 skipped, 5 incomplete, 2 failed)"; + checkChildString(id, 1, 5, 2, 0, expect); + expect = "FAIL " + id + " 11 tests (1 skipped, 5 incomplete, 2 failed, 3 passed)"; + checkChildString(id, 1, 5, 2, 3, expect); + expect = "FAIL " + id + " 6 tests (5 incomplete, 1 failed)"; + checkChildString(id, 0, 5, 1, 0, expect); + expect = "FAIL " + id + " 9 tests (5 incomplete, 1 failed, 3 passed)"; + checkChildString(id, 0, 5, 1, 3, expect); + expect = "PASS " + id + " 6 tests (5 incomplete, 1 passed)"; + checkChildString(id, 0, 5, 0, 1, expect); + } + + void checkChildString(String id, int numSkips, int numIncomplete, int numFails, int numPasses, + String expected) { + Runner runner = new Runner(); + MessageHandler holder = new MessageHandler(); + RunStatus status = new RunStatus(holder, runner); + status.setIdentifier(id); + status.start(); + + final IRun failer = new IRun() { + public boolean run(IRunStatus status) { return false; } + }; + final IRun passer = new IRun() { + public boolean run(IRunStatus status) { return true; } + }; + final Object result = (numFails > 0 ? IRunStatus.FAIL : IRunStatus.PASS); + while (numFails-- > 0) { + runner.runChild(failer,status, null, null); + } + while (numPasses-- > 0) { + runner.runChild(passer,status, null, null); + } + status.finish(result); + String actual = BridgeUtil.childString(status, numSkips, numIncomplete); + String label = " expected \"" + expected + "\" got \"" + actual + "\""; + assertTrue(label, expected.equals(actual)); + } +} diff --git a/testing/testsrc/org/aspectj/testing/util/FileUtilTest.java b/testing/testsrc/org/aspectj/testing/util/FileUtilTest.java new file mode 100644 index 000000000..1ff4d6cf6 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/FileUtilTest.java @@ -0,0 +1,46 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.File; + +import junit.framework.TestCase; + +/** + * + */ +public class FileUtilTest extends TestCase { + + /** + * Constructor for FileUtilTest. + * @param arg0 + */ + public FileUtilTest(String arg0) { + super(arg0); + } + + public void testFileEquals() { + // File.equals(..) is based on lexical compare of filenames +// File rf = new File("testsrc/org/aspectj/testing/util/FileUtilTest.java"); +// File rb = new File("testsrc\\org\\aspectj\\testing\\util\\FileUtilTest.java"); +// String a = rf.getAbsolutePath().replace('\\', '/'); +// File af = new File(a); +// File ab = new File(a.replace('/', '\\')); +// list.add(af); +// list.add(ab); +// list.add(rb); +// assertTrue(list.contains(duplicateTwo)); +// assertTrue(list.contains(anotherOne)); + } +} diff --git a/testing/testsrc/org/aspectj/testing/util/IteratorWrapperTest.java b/testing/testsrc/org/aspectj/testing/util/IteratorWrapperTest.java new file mode 100644 index 000000000..fdcd695ac --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/IteratorWrapperTest.java @@ -0,0 +1,164 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public class IteratorWrapperTest extends TestCase { + + /** + * Constructor for IteratorWrapperTest. + * @param name + */ + public IteratorWrapperTest(String name) { + super(name); + } + + public void testIteratorWrapper() { + Object[][] exp = new Object[][] {}; + List[] in = new List[] {}; + checkIteratorWrapper(in, exp); + + in = new List[] {Collections.EMPTY_LIST}; + checkIteratorWrapper(in, exp); + + in = new List[] {Collections.EMPTY_LIST, Collections.EMPTY_LIST}; + checkIteratorWrapper(in, exp); + + Object[] ra1 = new Object[] { "1" }; + List one = Collections.unmodifiableList(Arrays.asList(ra1)); + in = new List[] {one}; + exp = new Object[][] { ra1 }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, one}; + exp = new Object[][] { new Object[] { "1", "1"} }; + checkIteratorWrapper(in, exp); + + Object[] RA_ab = new String[] { "a", "b" }; + List List_ab = Collections.unmodifiableList(Arrays.asList(RA_ab)); + in = new List[] {List_ab}; + exp = new Object[][] { + new Object[] { "a" }, + new Object[] { "b" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, List_ab}; + exp = new Object[][] { + new Object[] { "1", "a" }, + new Object[] { "1", "b" }, + }; + checkIteratorWrapper(in, exp); + + Object[] RA_cd = new String[] { "c", "d" }; + List List_cd = Collections.unmodifiableList(Arrays.asList(RA_cd)); + + in = new List[] {List_ab, List_cd}; + exp = new Object[][] { + new Object[] { "a", "c" }, + new Object[] { "b", "c" }, + new Object[] { "a", "d" }, + new Object[] { "b", "d" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, one, one}; + exp = new Object[][] { + new Object[] { "1", "1", "1" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {List_ab, List_ab, List_ab}; + exp = new Object[][] { + new Object[] { "a", "a", "a" }, + new Object[] { "b", "a", "a" }, + new Object[] { "a", "b", "a" }, + new Object[] { "b", "b", "a" }, + new Object[] { "a", "a", "b" }, + new Object[] { "b", "a", "b" }, + new Object[] { "a", "b", "b" }, + new Object[] { "b", "b", "b" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, List_ab, List_ab}; + exp = new Object[][] { + new Object[] { "1", "a", "a" }, + new Object[] { "1", "b", "a" }, + new Object[] { "1", "a", "b" }, + new Object[] { "1", "b", "b" }, + }; + checkIteratorWrapper(in, exp); + + in = new List[] {one, List_ab, one}; + exp = new Object[][] { + new Object[] { "1", "a", "1" }, + new Object[] { "1", "b", "1" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {List_ab, one, List_ab}; + exp = new Object[][] { + new Object[] { "a", "1", "a" }, + new Object[] { "b", "1", "a" }, + new Object[] { "a", "1", "b" }, + new Object[] { "b", "1", "b" } + }; + checkIteratorWrapper(in, exp); + + in = new List[] {List_ab, one, List_ab, List_ab, Collections.EMPTY_LIST}; + exp = new Object[][] {}; + checkIteratorWrapper(in, exp); + + } + + void checkIteratorWrapper(List[] lists, Object[][] exp) { + IteratorWrapper it = new IteratorWrapper(lists); + for (int i = 0; i < exp.length; i++) { + Object[] e = exp[i]; + if (!it.hasNext()) { + String s = "exp[" + i + "]: " + Arrays.asList(e) + " it=" + it; + assertTrue(s, false); + } + Object[] actual = (Object[]) it.next(); + checkEquals(e, actual, i); + } + if (it.hasNext()) { + String s = "> " + exp.length + " it=" + it; + assertTrue(s, false); + } + } + + void checkEquals(Object[] exp, Object[] actual, int index) { + if (null == exp) { + assertTrue(null == actual); + } else { + assertTrue(null != actual); + } + String s = "] exp=" + Arrays.asList(exp) + " act=" + Arrays.asList(actual); + assertTrue(s, exp.length == actual.length); + for (int i = 0; i < actual.length; i++) { + assertTrue(null != exp[i]); + assertTrue("[" + index + ", " + i + s, exp[i].equals(actual[i])); + } + } +} diff --git a/testing/testsrc/org/aspectj/testing/util/LangUtilTest.java b/testing/testsrc/org/aspectj/testing/util/LangUtilTest.java new file mode 100644 index 000000000..ebdb21cc5 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/LangUtilTest.java @@ -0,0 +1,342 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.StringTokenizer; + +import junit.framework.TestCase; +import junit.textui.TestRunner; + +/** + * + * @author isberg + */ +public class LangUtilTest extends TestCase { + + private static final String ME + = "org.aspectj.testing.util.LangUtilTest"; + + /** @param args ignored */ + public static void main(String[] args) { + TestRunner.main(new String[] {ME}); + } + + /** + * Constructor for LangUtilTest. + * @param name + */ + public LangUtilTest(String name) { + super(name); + } + + void check(String l, StringTokenizer st, int max, String delim) { + for (int i = 0; i < max; i++) { + if ((i > 0) && (null != delim)) { + assertEquals(l, delim, st.nextToken()); + } + assertEquals(l, ""+i, st.nextToken()); + } + assertTrue(l, !st.hasMoreTokens()); + } + + + void checkUnflatten(FTest test) { + String[] exp = test.unflattened; + ArrayList result = LangUtil.unflatten(test.toUnflatten, test.spec); + String label = test + " -> " + result; + assertNotNull(label, result); + + assertEquals(label, exp.length, result.size()); + for (int i = 0; i < exp.length; i++) { + assertEquals(label, exp[i], result.get(i)); + } + } + + + public void skiptestUnflatten() { + LangUtil.FlattenSpec COMMA = LangUtil.FlattenSpec.COMMA; + LangUtil.FlattenSpec LIST = LangUtil.FlattenSpec.LIST; + + FTest[] tests = new FTest[] + { new FTest("[]", new String[0], LIST) + , new FTest("[1]", new String[] {"1"}, LIST) + , new FTest("[1, 2]", new String[] {"1", "2"}, LIST) + , new FTest("[1,2]", new String[] {"1,2"}, LIST) + , new FTest("[1, 2, 3]", new String[] {"1","2","3"}, LIST) + }; + for (int i = 0; i < tests.length; i++) { + checkUnflatten(tests[i]); + } + } + + + public void testArrayList() { + ArrayList l = new ArrayList(); + l.add(null); + l.add(null); + assertTrue(null == l.get(0)); + assertTrue(null == l.get(1)); + assertTrue(2 == l.size()); + assertEquals("[null, null]", "" + l); + } + + public void testCombineStrings() { + String[] one = new String[]{}; + String[] two = new String[]{}; + String[] expect = new String[]{}; + checkCombineStrings(one, two, expect); + + one = new String[]{null}; + two = new String[]{null}; + expect = new String[]{}; + checkCombineStrings(one, two, expect); + + one = new String[]{"1"}; + two = new String[]{null}; + expect = new String[]{"1"}; + checkCombineStrings(one, two, expect); + + one = new String[]{null}; + two = new String[]{"2"}; + expect = new String[]{"2"}; + checkCombineStrings(one, two, expect); + + one = new String[]{"1"}; + two = new String[]{"2"}; + expect = new String[]{"1", "2"}; + checkCombineStrings(one, two, expect); + + one = new String[]{null, null, "1", null, null}; + two = new String[]{null, "2", null}; + expect = new String[]{"1", "2"}; + checkCombineStrings(one, two, expect); + + one = new String[]{"1", "2", "3", "4"}; + two = new String[]{"5", null, "6"}; + expect = new String[]{"1", "2", "3", "4", "5", "6"}; + checkCombineStrings(one, two, expect); + + } + void checkCombineStrings(String[] one, String[] two, String[] expected) { + String[] actual = LangUtil.combine(one, two); + String aString = LangUtil.arrayAsList(actual).toString(); + String eString = LangUtil.arrayAsList(expected).toString(); + String both = "actual=\"" + aString + "\" expected=\"" + eString + "\""; + assertTrue(both, aString.equals(eString)); + } + + final String[] sABCDE = new String[] {"A", "B", "C", "D", "E" }; + final String[] sABC = new String[] {"A", "B", "C" }; + final String[] sDE = new String[] {"D", "E" }; + final String[] sabcde = new String[] {"a", "b", "c", "d", "e" }; + final String[] sabc = new String[] {"a", "b", "c" }; + final String[] sde = new String[] {"d", "e" }; + final String[] s12345 = new String[] {"1", "2", "3", "4", "5" }; + final String[] s13579 = new String[] {"1", "3", "5", "7", "9" }; + final String[] s02468 = new String[] {"0", "2", "4", "6", "8" }; + final String[] s135 = new String[] {"1", "3", "5" }; + final String[] s79 = new String[] {"7", "9" }; + final String[] s24 = new String[] {"2", "4" }; + final String[] s0 = new String[] {"0"}; + final String[] s068 = new String[] {"0", "6", "8" }; + final boolean unmodifiable = true; + final boolean modifiable = false; + final List lABCDE = makeList(unmodifiable, sABCDE); + final List lABC = makeList(unmodifiable, sABC); + final List lDE = makeList(unmodifiable, sDE); + final List labcde = makeList(unmodifiable, sabcde); + final List labc = makeList(unmodifiable, sabc); + final List lde = makeList(unmodifiable, sde); + final List l12345 = makeList(unmodifiable, s12345); + final List l13579 = makeList(unmodifiable, s13579); + final List l02468 = makeList(unmodifiable, s02468); + final List l135 = makeList(unmodifiable, s135); + final List l79 = makeList(unmodifiable, s79); + final List l24 = makeList(unmodifiable, s24); + final List l0 = makeList(unmodifiable, s0); + final List l068 = makeList(unmodifiable, s068); + final List rlabcde = makeList(modifiable, sabcde); + final List rlabc = makeList(modifiable, sabc); + final List rlde = makeList(modifiable, sde); + final List rlABCDE = makeList(modifiable, sABCDE); + final List rlABC = makeList(modifiable, sABC); + final List rlDE = makeList(modifiable, sDE); + final List rl12345 = makeList(modifiable, s12345); + final List rl13579 = makeList(modifiable, s13579); + final List rl02468 = makeList(modifiable, s02468); + final List rl135 = makeList(modifiable, s135); + final List rl79 = makeList(modifiable, s79); + final List rl24 = makeList(modifiable, s24); + final List rl0 = makeList(modifiable, s0); + final List rl068 = makeList(modifiable, s068); + final List NONE = Collections.EMPTY_LIST; + { + Collections.shuffle(rlABCDE); + Collections.shuffle(rlABC); + Collections.shuffle(rlDE); + Collections.shuffle(rlabcde); + Collections.shuffle(rlabc); + Collections.shuffle(rlde); + Collections.shuffle(rl12345); + Collections.shuffle(rl13579); + Collections.shuffle(rl02468); + Collections.shuffle(rl135); + Collections.shuffle(rl79); + Collections.shuffle(rl24); + Collections.shuffle(rl0); + Collections.shuffle(rl068); + } + + + public void testDiffsEmptyIdentities() { + checkDiff(l02468, null, l02468, NONE); + checkDiff(null, l02468, NONE, l02468); + checkDiff(l0, null, l0, NONE); + checkDiff(null, l0, NONE, l0); + checkDiff(l0, rl0, NONE, NONE); + checkDiff(labc, rlabc, NONE, NONE); + } + + public void testDiffsEmpties() { + checkDiff(NONE, NONE, NONE, NONE); + checkDiff(null, NONE, NONE, NONE); + checkDiff(NONE, null, NONE, NONE); + checkDiff(null, null, NONE, NONE); + checkDiff(null, null, NONE, NONE); + } + + public void testDiffsIdentities() { + checkDiff(l02468, l02468, NONE, NONE); + checkDiff(rl02468, l02468, NONE, NONE); + checkDiff(l02468, rl02468, NONE, NONE); + checkDiff(l13579, l13579, NONE, NONE); + checkDiff(rl13579, l13579, NONE, NONE); + checkDiff(l13579, rl13579, NONE, NONE); + checkDiff(l13579, rl13579, NONE, NONE); + } + public void testDiffsEvens() { + checkDiff(l02468, l12345, l068, l135); + checkDiff(rl02468, rl12345, rl068, rl135); + } + + public void testDiffsOdds() { + checkDiff(l13579, l12345, l79, l24); + checkDiff(rl13579, rl12345, rl79, rl24); + checkDiff(l13579, rl12345, l79, rl24); + checkDiff(rl13579, l12345, rl79, l24); + } + + public void testSoftDiffs() { + checkDiffSoft(labcde, lABCDE, NONE, NONE); + checkDiffSoft(lABC, labc, NONE, NONE); + checkDiffSoft(lABCDE, lABC, lDE, NONE); + checkDiffSoft(lDE, lABCDE, NONE, lABC); + checkDiffSoft(rlABCDE, rlABC, rlDE, NONE); + checkDiffSoft(rlDE, rlABCDE, NONE, rlABC); + checkDiffSoft(labcde, lABC, lDE, NONE); + checkDiffSoft(lde, lABCDE, NONE, lABC); + checkDiffSoft(rlabcde, rlABC, rlDE, NONE); + checkDiffSoft(rlde, rlABCDE, NONE, rlABC); + } + + // ---------------------- utilities + List makeList(boolean unmodifiable, String[] ra) { + if (unmodifiable) { + return Collections.unmodifiableList(Arrays.asList(ra)); + } else { + ArrayList list = new ArrayList(); + list.addAll(Arrays.asList(ra)); + return list; + } + } + + /** check both hard and soft - assuming list contain String */ + void checkDiff(List expected, List actual, List missing, List extra) { + ArrayList extraOut = new ArrayList(); + ArrayList missingOut = new ArrayList(); + LangUtil.makeDiffs(expected, actual, missingOut, extraOut); + checkSame(missing, missingOut); + checkSame(extra, extraOut); + extraOut.clear(); + missingOut.clear(); + + LangUtil.makeSoftDiffs(expected, actual, missingOut, extraOut, + String.CASE_INSENSITIVE_ORDER); + checkSame(missing, missingOut); // XXX does not detect bad order + checkSame(extra, extraOut); + } + + void checkSame(Collection one, Collection two) { // just convert and string-compare? + String label = one + "?=" + two; + assertTrue(label, (null == one) == (null == two)); + if (null != one) { + assertTrue(label, one.containsAll(two)); + assertTrue(label, two.containsAll(one)); + } + } + + /** check only soft - assuming list contain String */ + void checkDiffSoft(List expected, List actual, List missing, List extra) { + ArrayList extraOut = new ArrayList(); + ArrayList missingOut = new ArrayList(); + LangUtil.makeSoftDiffs(expected, actual, missingOut, extraOut, + String.CASE_INSENSITIVE_ORDER); + checkSameSoft(missing, missingOut); + checkSameSoft(extra, extraOut); + } + + /** @param one modifiable List of String + * @param two modifiable List of String + */ + void checkSameSoft(List one, List two) { // assume String + String label = one + "?=" + two; + assertTrue(label, (null == one) == (null == two)); + if (null != one) { + ArrayList aone = new ArrayList(); + aone.addAll(one); + ArrayList atwo = new ArrayList(); + aone.addAll(two); + Collections.sort(aone); + Collections.sort(atwo); + String sone = (""+aone).toLowerCase(); + String stwo = (""+aone).toLowerCase(); + assertTrue(label, sone.equals(stwo)); + } + } + + static class FTest { + String toUnflatten; + String[] unflattened; + LangUtil.FlattenSpec spec; + FTest(String in, String[] out, LangUtil.FlattenSpec spec) { + toUnflatten = in; + unflattened = out; + this.spec = spec; + } + public String toString() { + return "FTest(" + + "toUnflatten=" + toUnflatten + + ", unflattened=" + Arrays.asList(unflattened) + + ", spec=" + spec + + ")"; + } + } + + +} diff --git a/testing/testsrc/org/aspectj/testing/util/MessageUtilTest.java b/testing/testsrc/org/aspectj/testing/util/MessageUtilTest.java new file mode 100644 index 000000000..26d8523dd --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/MessageUtilTest.java @@ -0,0 +1,130 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.Message; +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.bridge.SourceLocation; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public class MessageUtilTest extends TestCase { + public MessageUtilTest(String s) { + super(s); + } + + MessageHandler samples; + List /* Exception */ exceptions; + List /*ISourceLocation*/ locations; + List /*String */ messageTexts; + + + public void testMessageRendering() { + MessageHandler messages = getSampleMessages(); + System.out.println("testMessageRendering(): run manually evaluate by inspection"); + PrintStream oldOut = System.out; + // comment to inspect manually + System.setOut(NullPrintStream.NULL_PrintStream); + try { + MessageUtil.print(System.out, messages, "all label -> ", MessageUtil.MESSAGE_LABEL, MessageUtil.PICK_ALL); + MessageUtil.print(System.out, messages, "info short -> ", MessageUtil.MESSAGE_SHORT, MessageUtil.PICK_INFO); + MessageUtil.print(System.out, messages, "fail line -> ", MessageUtil.MESSAGE_LINE, MessageUtil.PICK_FAIL); + MessageUtil.print(System.out, messages, "debug wide line -> ", MessageUtil.MESSAGE_WIDELINE, MessageUtil.PICK_DEBUG); + MessageUtil.print(System.out, messages, "warn no-loc label -> ", MessageUtil.MESSAGE_LABEL_NOLOC, MessageUtil.PICK_WARNING); + MessageUtil.print(System.out, messages, "abort force-loc line -> ", MessageUtil.MESSAGE_LINE_FORCE_LOC, MessageUtil.PICK_ABORT); + MessageUtil.print(System.out, messages, "info+ short -> ", MessageUtil.MESSAGE_SHORT, MessageUtil.PICK_INFO_PLUS); + MessageUtil.print(System.out, messages, "fail+ line -> ", MessageUtil.MESSAGE_LINE, MessageUtil.PICK_FAIL_PLUS); + MessageUtil.print(System.out, messages, "debug+ wide line -> ", MessageUtil.MESSAGE_WIDELINE, MessageUtil.PICK_DEBUG_PLUS); + MessageUtil.print(System.out, messages, "warn+ no-loc label -> ", MessageUtil.MESSAGE_LABEL_NOLOC, MessageUtil.PICK_WARNING_PLUS); + MessageUtil.print(System.out, messages, "abort+ force-loc line -> ", MessageUtil.MESSAGE_LINE_FORCE_LOC, MessageUtil.PICK_ABORT_PLUS); + } finally { + System.setOut(oldOut); + } + } + + + List getSampleMessageTexts() { + if (null == messageTexts) { + ArrayList result = new ArrayList(); + result.addAll(Arrays.asList(new String[] + { "one", "two", "now is the time for all good men..." })); + messageTexts = result; + } + return messageTexts; + } + + List getSampleExceptions() { + if (null == exceptions) { + ArrayList result = new ArrayList(); + int i = 1; + result.add(new Error("Error " + i++)); + result.add(new RuntimeException("RuntimeException " + i++)); + result.add(new IOException("IOException " + i++)); + exceptions = result; + } + return exceptions; + } + + List getSampleLocations() { + if (null == locations) { + ArrayList result = new ArrayList(); + File file = new File("testsrc/org/aspectj/testing/util/MessageUtilTest.java"); + result.add(new SourceLocation(file, 1, 2, 1)); + result.add(new SourceLocation(file, 100, 100, 0)); + locations = result; + } + return locations; + } + + MessageHandler getSampleMessages() { + MessageHandler result = new MessageHandler(); + for (Iterator kinds = IMessage.KINDS.iterator(); kinds.hasNext();) { + IMessage.Kind kind = (IMessage.Kind) kinds.next(); + for (Iterator locs = getSampleLocations().iterator(); locs.hasNext();) { + ISourceLocation sourceLoc = (ISourceLocation) locs.next(); + for (Iterator texts = getSampleMessageTexts().iterator(); + texts.hasNext(); + ) { + String text = (String) texts.next(); + for (Iterator exs = getSampleExceptions().iterator(); + exs.hasNext(); + ) { + Throwable thrown = (Throwable) exs.next(); + result.handleMessage(new Message(text, kind, thrown, sourceLoc)); + } + result.handleMessage(new Message(text, kind, null, sourceLoc)); + } + result.handleMessage(new Message("", kind, null, sourceLoc)); + } + result.handleMessage(new Message("", kind, null, null)); + } + return result; + } + +} diff --git a/testing/testsrc/org/aspectj/testing/util/StreamGrabberTest.java b/testing/testsrc/org/aspectj/testing/util/StreamGrabberTest.java new file mode 100644 index 000000000..9cd882fa8 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/StreamGrabberTest.java @@ -0,0 +1,112 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +/* + * StreamGrabberTest.java created on May 16, 2002 + * + */ +package org.aspectj.testing.util; + + +import java.io.PrintStream; + +import junit.framework.TestCase; +import junit.textui.TestRunner; + +/** + * + * @author isberg + */ +public class StreamGrabberTest extends TestCase { + + + private static final String ME + = "org.aspectj.testing.util.StreamGrabberTest"; + + /** @param args ignored */ + public static void main(String[] args) { + TestRunner.main(new String[] {ME}); + } + + public StreamGrabberTest(String s) { super(s); } + + public void testHide() { + PrintStream restore = System.out; + System.setOut(new PrintStream(NullPrintStream.NULL_OutputStream)); + System.out.println("OutputStream should not print!!!!!!!!!!!!!!!!!!!"); + System.setOut(new PrintStream(NullPrintStream.NULL_PrintStream)); + System.out.println("PrintStream should not print!!!!!!!!!!!!!!!!!!!"); + System.setOut(restore); + } + + /** + * Test StreamSniffer by setting up a delegate System.out + * and a normal System.out (delegating to System.out) + * and verifying that both get the same result. + */ + public void testGrab() { + StringBuffer delegate = new StringBuffer(); + StreamSniffer out = new StreamSniffer(System.out); + out.setBuffer(delegate); + System.setOut(new PrintStream(out)); + StreamSniffer g = new StreamSniffer(System.out); + System.setOut(new PrintStream(g)); + StringBuffer buf = new StringBuffer(); + g.setBuffer(buf); + + printLoop("f", buf, delegate); + printLoop("now is the time for all good men...", buf, delegate); + printlnLoop("f", buf, delegate); + printlnLoop("now is the time for all good men...", buf, delegate); + } + + private void printLoop(String expect, StringBuffer buf, StringBuffer delegate) { + System.out.print(expect); + String actual = buf.toString(); + String delegateActual = delegate.toString(); + assertTrue(expect + "=" + actual, expect.equals(actual)); + assertTrue(expect + "=" + delegateActual, expect.equals(delegateActual)); + buf.setLength(0); + delegate.setLength(0); + System.out.print(expect); + + actual = buf.toString(); + delegateActual = delegate.toString(); + assertTrue(expect + "=" + actual, expect.equals(actual)); + assertTrue(expect + "=" + delegateActual, expect.equals(delegateActual)); + buf.setLength(0); + delegate.setLength(0); + } + + private void printlnLoop(String expect, StringBuffer buf, StringBuffer delegate) { + // copy/paste of printLoop, using println + expect = expect.trim(); + System.out.println(expect); + String actual = buf.toString().trim(); + String delegateActual = delegate.toString().trim(); + assertTrue(expect + "=" + actual, expect.equals(actual)); + assertTrue(expect + "=" + delegateActual, expect.equals(delegateActual)); + buf.setLength(0); + delegate.setLength(0); + + System.out.println(expect); + actual = buf.toString().trim(); + delegateActual = delegate.toString().trim(); + assertTrue(expect + "=" + actual, expect.equals(actual)); + assertTrue(expect + "=" + delegateActual, expect.equals(delegateActual)); + buf.setLength(0); + delegate.setLength(0); + } + +} diff --git a/testing/testsrc/org/aspectj/testing/util/TestDiffsTest.java b/testing/testsrc/org/aspectj/testing/util/TestDiffsTest.java new file mode 100644 index 000000000..4ce7792c6 --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/TestDiffsTest.java @@ -0,0 +1,114 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.util; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.ListIterator; + +import junit.framework.TestCase; +import org.aspectj.util.FileUtil; + +/** + * + */ +public class TestDiffsTest extends TestCase { + + /** + * Expected results in test below. + */ + private static void genTestInput(File expected, File actual) throws IOException { + FileWriter writer = null; + try { + writer = new FileWriter(expected); + PrintWriter pw = new PrintWriter(writer); + pw.println("PASS passed in both"); + pw.println("## random text to ignore: " + System.currentTimeMillis()); + pw.println("FAIL failed in both"); + pw.println("PASS failed in actual (broken)"); + pw.println("FAIL passed in actual (fixed)"); + pw.println("PASS not in actual (missing-pass)"); + pw.println("FAIL not in actual (missing-fail)"); + pw.flush(); + writer.close(); + + writer = new FileWriter(actual); + pw = new PrintWriter(writer); + pw.println("PASS passed in actual (fixed)"); + pw.println("## random text to ignore: " + System.currentTimeMillis()); + pw.println("PASS not in expected (added-pass)"); + pw.println("FAIL failed in both"); + pw.println("PASS passed in both"); + pw.println("FAIL failed in actual (broken)"); + pw.println("FAIL not in expected (added-fail)"); + pw.flush(); + writer.close(); + writer = null; + } finally { + if (null != writer) { + try { writer.close(); } + catch (IOException e) { } // ignore + } + } + } + + ArrayList tempFiles; + /** + * Constructor for FileUtilTest. + * @param arg0 + */ + public TestDiffsTest(String arg0) { + super(arg0); + tempFiles = new ArrayList(); + } + + public void tearDown() { + for (ListIterator iter = tempFiles.listIterator(); iter.hasNext();) { + File dir = (File) iter.next(); + FileUtil.deleteContents(dir); + dir.delete(); + iter.remove(); + } + } + + + public void testCompareResults() { + File tempDir = org.aspectj.util.FileUtil.getTempDir("testCompareResults"); + File expected = new File(tempDir, "expected.txt"); + File actual = new File(tempDir, "actual.txt"); + tempFiles.add(expected); + tempFiles.add(actual); + try { + genTestInput(expected, actual); + } catch (IOException e) { + assertTrue(e.getMessage(), false); + } + TestDiffs result = TestDiffs.compareResults(expected, actual); + assertEquals(2, result.missing.size()); + assertEquals(2, result.added.size()); + assertEquals(1, result.fixed.size()); + assertEquals(1, result.broken.size()); + assertEquals(3, result.actualFailed.size()); + assertEquals(3, result.actualPassed.size()); + assertEquals(6, result.actual.size()); + assertEquals(3, result.expectedFailed.size()); + assertEquals(3, result.expectedPassed.size()); + assertEquals(6, result.expected.size()); + assertEquals(1, result.stillFailing.size()); + assertEquals(1, result.stillPassing.size()); + } +} diff --git a/testing/testsrc/org/aspectj/testing/util/UtilTests.java b/testing/testsrc/org/aspectj/testing/util/UtilTests.java new file mode 100644 index 000000000..3ec14d7df --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/util/UtilTests.java @@ -0,0 +1,36 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.util; + +import junit.framework.*; + +public class UtilTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(UtilTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(BridgeUtilTest.class); + suite.addTestSuite(FileUtilTest.class); + suite.addTestSuite(IteratorWrapperTest.class); + suite.addTestSuite(LangUtilTest.class); + suite.addTestSuite(MessageUtilTest.class); + suite.addTestSuite(StreamGrabberTest.class); + //$JUnit-END$ + return suite; + } + + public UtilTests(String name) { super(name); } + +} diff --git a/testing/testsrc/org/aspectj/testing/xml/AjcSpecXmlReaderTest.java b/testing/testsrc/org/aspectj/testing/xml/AjcSpecXmlReaderTest.java new file mode 100644 index 000000000..157cf2dba --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/xml/AjcSpecXmlReaderTest.java @@ -0,0 +1,236 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +import org.aspectj.bridge.MessageHandler; +import org.aspectj.bridge.MessageUtil; +import org.aspectj.testing.harness.bridge.AjcSpecTest; +import org.aspectj.testing.harness.bridge.CompilerRun; +import org.aspectj.testing.harness.bridge.FlatSuiteReader; +import org.aspectj.testing.harness.bridge.AjcTest; +import org.aspectj.testing.run.IRunStatus; +import org.aspectj.testing.run.IRunValidator; +import org.aspectj.testing.run.RunValidator; +import org.aspectj.util.LangUtil; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + */ +public class AjcSpecXmlReaderTest extends TestCase { + + ArrayList tempFiles = new ArrayList(); + /** + * Constructor for AjcSpecXmlReaderTest. + * @param name + */ + public AjcSpecXmlReaderTest(String name) { + super(name); + } + + public void setUp() { + tempFiles.clear(); + //System.out.println("XXX test requires compiler and bridgeImpl projects on classpath"); + } + + public void tearDown() { + if (!LangUtil.isEmpty(tempFiles)) { + for (Iterator iter = tempFiles.iterator(); iter.hasNext();) { + File file = (File) iter.next(); + if (file.canRead()) { + file.delete(); + } + } + } + } + + /** test that all AjcSpecXmlReader.me.expectedProperties() are bean-writable */ + public void testBeanInfo() throws IntrospectionException { + AjcSpecXmlReader me = AjcSpecXmlReader.getReader(); + AjcSpecXmlReader.BProps[] expected = me.expectedProperties(); + PropertyDescriptor[] des; + for (int i = 0; i < expected.length; i++) { + Class clazz = expected[i].cl; + BeanInfo beanInfo = Introspector.getBeanInfo(clazz); + assertTrue(null != beanInfo); + des = beanInfo.getPropertyDescriptors(); + for (int j = 0; j < expected[i].props.length; j++) { + String name = expected[i].props[j]; + String fqn = clazz.getName() + "." + name; + boolean gotIt = false; + for (int k = 0; k < des.length; k++) { + String desName = des[k].getName(); + if (name.equals(desName)) { + assertTrue(fqn, null != des[k].getWriteMethod()); + gotIt = true; + } + } + assertTrue("no such property: " + fqn, gotIt); + } + } + + } + public void testAjcTests() throws IOException { + checkXmlRoundTrip("../tests/ajcTests"); + } + + public void testAjcTests10() throws IOException { + checkXmlRoundTrip("../tests/ajcTests10"); + } + + public void testAjcTestsBroken() throws IOException { + checkXmlRoundTrip("../tests/ajcTestsBroken"); + } + + public void testAjcTestsAttic() throws IOException { + checkXmlRoundTrip("../tests/ajcTestsAttic"); + } + + public void testAjcHarnessTests() throws IOException { + checkXmlRoundTrip("../tests/ajcHarnessTests"); + } + + void checkXmlRoundTrip(String path) throws IOException { + String xmlPath = path + ".xml"; + String xml2Path = path + ".tmp.xml"; + + final File file1 = new File(xmlPath); + final ArrayList toDelete = new ArrayList(); + final AjcSpecXmlReader writer = AjcSpecXmlReader.getReader(); + + assertTrue("" + file1, file1.canRead()); + AjcTest.Suite.Spec suite1 = writer.readAjcSuite(file1); + assertNotNull(suite1); + + File file2 = new File(xml2Path); + String warning = writer.writeSuiteToXmlFile(file2, suite1); + toDelete.add(file2); + assertTrue(warning, null == warning); + + AjcTest.Suite.Spec suite2 = writer.readAjcSuite(file1); + assertNotNull(suite2); + AjcSpecTest.sameAjcSuiteSpec(suite1, suite2, this); + + for (Iterator iter = toDelete.iterator(); iter.hasNext();) { + ((File) iter.next()).delete(); + } + } + + void checkRoundTrip(String path) throws IOException, Exception { + // XXX once .txt gone, add bugId and keywords to test + String txtPath = path + ".txt"; + String xmlPath = path + ".tmp.xml"; + String xml2Path = path + ".tmp2.xml"; + + // read flat, write the xml variant, read back, and compare + AjcSpecXmlReader writer = AjcSpecXmlReader.getReader(); + File file0 = new File(txtPath); + File file1 = new File(xmlPath); + ArrayList toDelete = new ArrayList(); + AjcTest.Suite.Spec suite0 = null; + if (file0.canRead()) { + System.out.println("reading " + file0); + suite0 = FlatSuiteReader.ME.readSuite(file0); + String warning = writer.writeSuiteToXmlFile(file1, suite0); + toDelete.add(file1); + assertTrue(warning, null == warning); + } else { + file1 = new File(path + ".xml"); + if (file1.canRead()) { + System.out.println("reading " + file1); + suite0 = writer.readAjcSuite(file1); + } else { + System.err.println("Skipping as not in module: " + file0); + return; + } + } + assertNotNull(suite0); + + //System.err.println("----------------------- suite0 " + txtPath); + //suite0.printAll(System.err, ""); + assertTrue("" + file1, file1.canRead()); + System.out.println("reading " + file1); + AjcTest.Suite.Spec suite1 = writer.readAjcSuite(file1); + assertNotNull(suite1); + //System.err.println("----------------------- suite1 " + xmlPath); + //suite0.printAll(System.err, ""); + AjcSpecTest.sameAjcSuiteSpec(suite0, suite1, this); + + // same for second-generation xml + file1 = new File(xml2Path); + String warning = writer.writeSuiteToXmlFile(file1, suite1); + toDelete.add(file1); + // XXX enable later assertTrue(warning, null == warning); + if (null != warning) { + System.out.println("warning " + file1 + ": " + warning); + } + System.out.println("reading " + file1); + AjcTest.Suite.Spec suite2 = writer.readAjcSuite(file1); + assertNotNull(suite2); + //System.err.println("----------------------- suite2 " + xml2Path); + AjcSpecTest.sameAjcSuiteSpec(suite1, suite2, this); + AjcSpecTest.sameAjcSuiteSpec(suite0, suite2, this); + + for (Iterator iter = toDelete.iterator(); iter.hasNext();) { + ((File) iter.next()).delete(); + } + } +} + + // ------------------- XXX retry execution round-trips when eclipse working + //AjcSpecTest.sameAjcTestSpec(txtList, xmlSpec, this); + +// List xmlList = writer.readAjcTests(xmlFile); +// AjcSpecTest.sameAjcTestLists(txtList, xmlList, this); +// List xml2List = writer.readAjcTests(xmlFile); +// AjcSpecTest.sameAjcTestLists(xmlList, xml2List, this); + + + // ------------------ now run them both and compare results +// // run the flat and xml variants, then compare +// MessageHandler xmlHandler = new MessageHandler(); +// IRunStatus xmlStatus = runFile(xmlPath, xmlHandler); +// MessageHandler txtHandler = new MessageHandler(); +// IRunStatus txtStatus = runFile(txtPath, txtHandler); +// +// +// // both should pass or fail.. +// IRunValidator v = RunValidator.NORMAL; +// boolean xmlPassed = v.runPassed(xmlStatus); +// boolean txtPassed = v.runPassed(txtStatus); +// boolean passed = (xmlPassed == txtPassed); +// if (!xmlPassed) { +// MessageUtil.print(System.err, xmlStatus); +// } +// if (!txtPassed) { +// MessageUtil.print(System.err, txtStatus); +// } +// if (!passed) { // calculate diffs +// } +// IRunStatus runFile(String path, MessageHandler handler) throws IOException { +// Main runner = new Main(new String[] {path}, handler); +// String id = "AjcSpecXmlReaderTest.runFile(" + path + ")"; +// return runner.runMain(id, handler, System.err); +// } diff --git a/testing/testsrc/org/aspectj/testing/xml/TestingXmlTests.java b/testing/testsrc/org/aspectj/testing/xml/TestingXmlTests.java new file mode 100644 index 000000000..fbefdba5d --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/xml/TestingXmlTests.java @@ -0,0 +1,31 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + + +package org.aspectj.testing.xml; + +import junit.framework.*; + +public class TestingXmlTests extends TestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(TestingXmlTests.class.getName()); + //$JUnit-BEGIN$ + suite.addTestSuite(AjcSpecXmlReaderTest.class); + //$JUnit-END$ + return suite; + } + + public TestingXmlTests(String name) { super(name); } + +} diff --git a/testing/testsrc/org/aspectj/testing/xml/XMLWriterTest.java b/testing/testsrc/org/aspectj/testing/xml/XMLWriterTest.java new file mode 100644 index 000000000..80790773e --- /dev/null +++ b/testing/testsrc/org/aspectj/testing/xml/XMLWriterTest.java @@ -0,0 +1,44 @@ +/* ******************************************************************* + * Copyright (c) 1999-2001 Xerox Corporation, + * 2002 Palo Alto Research Center, Incorporated (PARC). + * 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: + * Xerox/PARC initial implementation + * ******************************************************************/ + +package org.aspectj.testing.xml; + +import java.util.Arrays; + +import junit.framework.TestCase; + +/** + * + */ +public class XMLWriterTest extends TestCase { + + public XMLWriterTest(String name) { + super(name); + } + + /** @see LangUtilTest#testCommaSplit() */ + public void testUnflattenList() { + checkUnflattenList("", new String[] {""}); + checkUnflattenList("1", new String[] {"1"}); + checkUnflattenList(" 1 2 ", new String[] {"1 2"}); + checkUnflattenList(" 1 , 2 ", new String[] {"1", "2"}); + checkUnflattenList("1,2,3,4", new String[] {"1", "2", "3", "4"}); + } + + void checkUnflattenList(String input, String[] expected) { + String[] actual = XMLWriter.unflattenList(input); + String a = "" + Arrays.asList(actual); + String e = "" + Arrays.asList(expected); + assertTrue(e + "==" + a, e.equals(a)); + } +} |