diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit')
29 files changed, 9132 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java new file mode 100644 index 00000000..ec3506d4 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Delete; +import org.apache.tools.ant.taskdefs.TempFile; +import org.apache.tools.ant.taskdefs.XSLTProcess; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.resources.URLResource; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.JAXPUtils; +import org.w3c.dom.Document; + +/** + * Transform a JUnit xml report. + * The default transformation generates an html report in either framed or non-framed + * style. The non-framed style is convenient to have a concise report via mail, the + * framed report is much more convenient if you want to browse into different + * packages or testcases since it is a Javadoc like report. + * + */ +public class AggregateTransformer { + /** + * name of the frames format. + */ + public static final String FRAMES = "frames"; + + /** + * name of the no frames format. + */ + public static final String NOFRAMES = "noframes"; + + /** + * defines acceptable formats. + */ + public static class Format extends EnumeratedAttribute { + /** + * list authorized values. + * @return authorized values. + */ + public String[] getValues() { + return new String[]{FRAMES, NOFRAMES}; + } + } + + // CheckStyle:VisibilityModifier OFF - bc + /** Task */ + protected Task task; + + /** the xml document to process */ + protected Document document; + + /** the style directory. XSLs should be read from here if necessary */ + protected File styleDir; + + /** the destination directory, this is the root from where html should be generated */ + protected File toDir; + + /** + * The internal XSLT task used to perform the transformation. + * + * @since Ant 1.9.5 + */ + private XSLTProcess xsltTask; + + /** + * Instance of a utility class to use for file operations. + * + * @since Ant 1.7 + */ + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** + * Used to ensure the uniqueness of a property + */ + private static int counter = 0; + + /** the format to use for the report. Must be <tt>FRAMES</tt> or <tt>NOFRAMES</tt> */ + protected String format = FRAMES; + + /** XML Parser factory */ + private static DocumentBuilderFactory privateDBFactory; + + /** XML Parser factory accessible to subclasses */ + protected static DocumentBuilderFactory dbfactory; + + static { + privateDBFactory = DocumentBuilderFactory.newInstance(); + dbfactory = privateDBFactory; + } + // CheckStyle:VisibilityModifier ON + + /** + * constructor creating the transformer from the junitreport task. + * @param task task delegating to this class + */ + public AggregateTransformer(Task task) { + this.task = task; + xsltTask = new XSLTProcess(); + xsltTask.bindToOwner(task); + } + + /** + * Get the Document Builder Factory + * + * @return the DocumentBuilderFactory instance in use + */ + protected static DocumentBuilderFactory getDocumentBuilderFactory() { + return privateDBFactory; + } + + /** + * sets the format. + * @param format Must be <tt>FRAMES</tt> or <tt>NOFRAMES</tt> + */ + public void setFormat(Format format) { + this.format = format.getValue(); + } + + /** + * sets the input document. + * @param doc input dom tree + */ + public void setXmlDocument(Document doc) { + this.document = doc; + } + + /** + * Set the xml file to be processed. This is a helper if you want + * to set the file directly. Much more for testing purposes. + * @param xmlfile xml file to be processed + * @throws BuildException if the document cannot be parsed. + */ + protected void setXmlfile(File xmlfile) throws BuildException { + try { + DocumentBuilder builder = privateDBFactory.newDocumentBuilder(); + InputStream in = new FileInputStream(xmlfile); + try { + Document doc = builder.parse(in); + setXmlDocument(doc); + } finally { + in.close(); + } + } catch (Exception e) { + throw new BuildException("Error while parsing document: " + xmlfile, e); + } + } + + /** + * set the style directory. It is optional and will override the + * default xsl used. + * @param styledir the directory containing the xsl files if the user + * would like to override with its own style. + */ + public void setStyledir(File styledir) { + this.styleDir = styledir; + } + + /** set the destination directory. + * @param todir the destination directory + */ + public void setTodir(File todir) { + this.toDir = todir; + } + + /** set the extension of the output files + * @param ext extension. + */ + public void setExtension(String ext) { + task.log("extension is not used anymore", Project.MSG_WARN); + } + + /** + * Create an instance of an XSL parameter for configuration by Ant. + * + * @return an instance of the Param class to be configured. + * @since Ant 1.7 + */ + public XSLTProcess.Param createParam() { + return xsltTask.createParam(); + } + + /** + * Creates a classpath to be used for the internal XSLT task. + * + * @return the classpath to be configured + * @since Ant 1.9.5 + */ + public Path createClasspath() { + return xsltTask.createClasspath(); + } + + /** + * Creates a factory configuration to be used for the internal XSLT task. + * + * @return the factory description to be configured + * @since Ant 1.9.5 + */ + public XSLTProcess.Factory createFactory() { + return xsltTask.createFactory(); + } + + /** + * transformation + * @throws BuildException exception if something goes wrong with the transformation. + */ + public void transform() throws BuildException { + checkOptions(); + Project project = task.getProject(); + + TempFile tempFileTask = new TempFile(); + tempFileTask.bindToOwner(task); + + xsltTask.setXslResource(getStylesheet()); + + // acrobatic cast. + xsltTask.setIn(((XMLResultAggregator) task).getDestinationFile()); + File outputFile = null; + if (format.equals(FRAMES)) { + String tempFileProperty = getClass().getName() + String.valueOf(counter++); + File tmp = FILE_UTILS.resolveFile(project.getBaseDir(), project + .getProperty("java.io.tmpdir")); + tempFileTask.setDestDir(tmp); + tempFileTask.setProperty(tempFileProperty); + tempFileTask.execute(); + outputFile = new File(project.getProperty(tempFileProperty)); + } else { + outputFile = new File(toDir, "junit-noframes.html"); + } + xsltTask.setOut(outputFile); + XSLTProcess.Param paramx = xsltTask.createParam(); + paramx.setProject(task.getProject()); + paramx.setName("output.dir"); + paramx.setExpression(toDir.getAbsolutePath()); + final long t0 = System.currentTimeMillis(); + try { + xsltTask.execute(); + } catch (Exception e) { + throw new BuildException("Errors while applying transformations: " + e.getMessage(), e); + } + final long dt = System.currentTimeMillis() - t0; + task.log("Transform time: " + dt + "ms"); + if (format.equals(FRAMES)) { + Delete delete = new Delete(); + delete.bindToOwner(task); + delete.setFile(outputFile); + delete.execute(); + } + } + + /** + * access the stylesheet to be used as a resource. + * @return stylesheet as a resource + */ + protected Resource getStylesheet() { + String xslname = "junit-frames.xsl"; + if (NOFRAMES.equals(format)) { + xslname = "junit-noframes.xsl"; + } + if (styleDir == null) { + // If style dir is not specified we have to retrieve + // the stylesheet from the classloader + URL stylesheetURL = getClass().getClassLoader().getResource( + "org/apache/tools/ant/taskdefs/optional/junit/xsl/" + xslname); + return new URLResource(stylesheetURL); + } + // If we are here, then the style dir is here and we + // should read the stylesheet from the filesystem + return new FileResource(new File(styleDir, xslname)); + } + + /** check for invalid options + * @throws BuildException if something goes wrong. + */ + protected void checkOptions() throws BuildException { + // set the destination directory relative from the project if needed. + if (toDir == null) { + toDir = task.getProject().resolveFile("."); + } else if (!toDir.isAbsolute()) { + toDir = task.getProject().resolveFile(toDir.getPath()); + } + } + + /** + * Get the systemid of the appropriate stylesheet based on its + * name and styledir. If no styledir is defined it will load + * it as a java resource in the xsl child package, otherwise it + * will get it from the given directory. + * @return system ID of the stylesheet. + * @throws IOException thrown if the requested stylesheet does + * not exist. + */ + protected String getStylesheetSystemId() throws IOException { + String xslname = "junit-frames.xsl"; + if (NOFRAMES.equals(format)) { + xslname = "junit-noframes.xsl"; + } + if (styleDir == null) { + URL url = getClass().getResource("xsl/" + xslname); + if (url == null) { + throw new FileNotFoundException("Could not find jar resource " + xslname); + } + return url.toExternalForm(); + } + File file = new File(styleDir, xslname); + if (!file.exists()) { + throw new FileNotFoundException("Could not find file '" + file + "'"); + } + return JAXPUtils.getSystemId(file); + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java new file mode 100644 index 00000000..55e7a5d5 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java @@ -0,0 +1,241 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.File; +import java.util.Vector; + +/** + * Baseclass for BatchTest and JUnitTest. + * + */ +public abstract class BaseTest { + // CheckStyle:VisibilityModifier OFF - bc + protected boolean haltOnError = false; + protected boolean haltOnFail = false; + protected boolean filtertrace = true; + protected boolean fork = false; + protected String ifProperty = null; + protected String unlessProperty = null; + protected Vector formatters = new Vector(); + /** destination directory */ + protected File destDir = null; + + protected String failureProperty; + protected String errorProperty; + // CheckStyle:VisibilityModifier ON + + private Object ifCond, unlessCond; + private boolean skipNonTests; + + /** + * Set the filtertrace attribute. + * @param value a <code>boolean</code> value. + */ + public void setFiltertrace(boolean value) { + filtertrace = value; + } + + /** + * Get the filtertrace attribute. + * @return the attribute. + */ + public boolean getFiltertrace() { + return filtertrace; + } + + /** + * Set the fork attribute. + * @param value a <code>boolean</code> value. + */ + public void setFork(boolean value) { + fork = value; + } + + /** + * Get the fork attribute. + * @return the attribute. + */ + public boolean getFork() { + return fork; + } + + /** + * Set the haltonerror attribute. + * @param value a <code>boolean</code> value. + */ + public void setHaltonerror(boolean value) { + haltOnError = value; + } + + /** + * Set the haltonfailure attribute. + * @param value a <code>boolean</code> value. + */ + public void setHaltonfailure(boolean value) { + haltOnFail = value; + } + + /** + * Get the haltonerror attribute. + * @return the attribute. + */ + public boolean getHaltonerror() { + return haltOnError; + } + + /** + * Get the haltonfailure attribute. + * @return the attribute. + */ + public boolean getHaltonfailure() { + return haltOnFail; + } + + /** + * Set the if attribute. + * If this expression evaluates to true or the name of a property + * which is present in project, the test will be run. + * @param ifCondition the expression to evaluate + * @since Ant 1.8.0 + */ + public void setIf(Object ifCondition) { + ifCond = ifCondition; + ifProperty = ifCondition != null ? String.valueOf(ifCondition) : null; + } + + /** + * Set the if attribute. + * If this expression evaluates to true or the name of a property + * which is present in project, the test will be run. + * @param propertyName the expression to evaluate + */ + public void setIf(String propertyName) { + setIf((Object) propertyName); + } + + /** + * The if expression + * @since Ant 1.8.0 + */ + public Object getIfCondition() { + return ifCond; + } + + /** + * Set the unless attribute. If this expression evaluates to + * false or the name of a property which is not present in + * project, the test will be run. + * @param unlessCondition the expression to evaluate + * @since Ant 1.8.0 + */ + public void setUnless(Object unlessCondition) { + unlessCond = unlessCondition; + unlessProperty = unlessCondition != null + ? String.valueOf(unlessCondition) : null; + } + + /** + * Set the unless attribute. If this expression evaluates to + * false or the name of a property which is not present in + * project, the test will be run. + * @param propertyName the expression to evaluate + */ + public void setUnless(String propertyName) { + setUnless((Object) propertyName); + } + + /** + * The unless expression + * @since Ant 1.8.0 + */ + public Object getUnlessCondition() { + return unlessCond; + } + + /** + * Allow a formatter nested element. + * @param elem a formatter nested element. + */ + public void addFormatter(FormatterElement elem) { + formatters.addElement(elem); + } + + /** + * Sets the destination directory. + * @param destDir the destination directory. + */ + public void setTodir(File destDir) { + this.destDir = destDir; + } + + /** + * Get the destination directory. + * @return the destination directory as an absolute path if it exists + * otherwise return <tt>null</tt> + */ + public String getTodir() { + if (destDir != null) { + return destDir.getAbsolutePath(); + } + return null; + } + + /** + * Get the failure property name. + * @return the name of the property to set on failure. + */ + public String getFailureProperty() { + return failureProperty; + } + + /** + * Set the name of the failure property. + * @param failureProperty the name of the property to set if + * the test fails. + */ + public void setFailureProperty(String failureProperty) { + this.failureProperty = failureProperty; + } + + /** + * Get the failure property name. + * @return the name of the property to set on failure. + */ + public String getErrorProperty() { + return errorProperty; + } + + /** + * Set the name of the error property. + * @param errorProperty the name of the property to set if + * the test has an error. + */ + public void setErrorProperty(String errorProperty) { + this.errorProperty = errorProperty; + } + + public void setSkipNonTests(boolean skipNonTests) { + this.skipNonTests = skipNonTests; + } + + public boolean isSkipNonTests() { + return skipNonTests; + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java new file mode 100644 index 00000000..f41b96f1 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + + +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.resources.Resources; + +/** + * <p> Create then run <code>JUnitTest</code>'s based on the list of files + * given by the fileset attribute. + * + * <p> Every <code>.java</code> or <code>.class</code> file in the fileset is + * assumed to be a testcase. + * A <code>JUnitTest</code> is created for each of these named classes with + * basic setup inherited from the parent <code>BatchTest</code>. + * + * @see JUnitTest + */ +public final class BatchTest extends BaseTest { + + /** the reference to the project */ + private Project project; + + /** the list of filesets containing the testcase filename rules */ + private Resources resources = new Resources(); + + /** + * create a new batchtest instance + * @param project the project it depends on. + */ + public BatchTest(Project project) { + this.project = project; + resources.setCache(true); + } + + /** + * Add a new fileset instance to this batchtest. Whatever the fileset is, + * only filename that are <tt>.java</tt> or <tt>.class</tt> will be + * considered as 'candidates'. + * @param fs the new fileset containing the rules to get the testcases. + */ + public void addFileSet(FileSet fs) { + add(fs); + + // this one is here because the changes to support ResourceCollections + // have broken Magic's JUnitTestTask. + // + // The task adds a FileSet to a BatchTest instance using the + // Java API and without telling the FileSet about its project + // instance. The original code would pass in project on the + // call to getDirectoryScanner - which is now hidden deep into + // Resources.iterator() and not reachable. + if (fs.getProject() == null) { + fs.setProject(project); + } + } + + + /** + * Add a new ResourceCollection instance to this + * batchtest. Whatever the collection is, only names that are + * <tt>.java</tt> or <tt>.class</tt> will be considered as + * 'candidates'. + * @param rc the new ResourceCollection containing the rules to + * get the testcases. + * @since Ant 1.7 + */ + public void add(ResourceCollection rc) { + resources.add(rc); + } + + /** + * Return all <tt>JUnitTest</tt> instances obtain by applying the fileset rules. + * @return an enumeration of all elements of this batchtest that are + * a <tt>JUnitTest</tt> instance. + */ + public Enumeration elements() { + JUnitTest[] tests = createAllJUnitTest(); + return Enumerations.fromArray(tests); + } + + /** + * Convenient method to merge the <tt>JUnitTest</tt>s of this batchtest + * to a <tt>Vector</tt>. + * @param v the vector to which should be added all individual tests of this + * batch test. + */ + void addTestsTo(Vector v) { + JUnitTest[] tests = createAllJUnitTest(); + v.ensureCapacity(v.size() + tests.length); + for (int i = 0; i < tests.length; i++) { + v.addElement(tests[i]); + } + } + + /** + * Create all <tt>JUnitTest</tt>s based on the filesets. Each instance + * is configured to match this instance properties. + * @return the array of all <tt>JUnitTest</tt>s that belongs to this batch. + */ + private JUnitTest[] createAllJUnitTest() { + String[] filenames = getFilenames(); + JUnitTest[] tests = new JUnitTest[filenames.length]; + for (int i = 0; i < tests.length; i++) { + String classname = javaToClass(filenames[i]); + tests[i] = createJUnitTest(classname); + } + return tests; + } + + /** + * Iterate over all filesets and return the filename of all files + * that end with <tt>.java</tt> or <tt>.class</tt>. This is to avoid + * wrapping a <tt>JUnitTest</tt> over an xml file for example. A Testcase + * is obviously a java file (compiled or not). + * @return an array of filenames without their extension. As they should + * normally be taken from their root, filenames should match their fully + * qualified class name (If it is not the case it will fail when running the test). + * For the class <tt>org/apache/Whatever.class</tt> it will return <tt>org/apache/Whatever</tt>. + */ + private String[] getFilenames() { + Vector v = new Vector(); + for (Resource r : resources) { + if (r.isExists()) { + String pathname = r.getName(); + if (pathname.endsWith(".java")) { + v.addElement(pathname.substring(0, pathname.length() - ".java".length())); + } else if (pathname.endsWith(".class")) { + v.addElement(pathname.substring(0, pathname.length() - ".class".length())); + } + } + } + + String[] files = new String[v.size()]; + v.copyInto(files); + return files; + } + + /** + * Convenient method to convert a pathname without extension to a + * fully qualified classname. For example <tt>org/apache/Whatever</tt> will + * be converted to <tt>org.apache.Whatever</tt> + * @param filename the filename to "convert" to a classname. + * @return the classname matching the filename. + */ + public static String javaToClass(String filename) { + return filename.replace(File.separatorChar, '.').replace('/', '.') + .replace('\\', '.'); + } + + /** + * Create a <tt>JUnitTest</tt> that has the same property as this + * <tt>BatchTest</tt> instance. + * @param classname the name of the class that should be run as a + * <tt>JUnitTest</tt>. It must be a fully qualified name. + * @return the <tt>JUnitTest</tt> over the given classname. + */ + private JUnitTest createJUnitTest(String classname) { + JUnitTest test = new JUnitTest(); + test.setName(classname); + test.setHaltonerror(this.haltOnError); + test.setHaltonfailure(this.haltOnFail); + test.setFiltertrace(this.filtertrace); + test.setFork(this.fork); + test.setIf(getIfCondition()); + test.setUnless(getUnlessCondition()); + test.setTodir(this.destDir); + test.setFailureProperty(failureProperty); + test.setErrorProperty(errorProperty); + test.setSkipNonTests(isSkipNonTests()); + Enumeration list = this.formatters.elements(); + while (list.hasMoreElements()) { + test.addFormatter((FormatterElement) list.nextElement()); + } + return test; + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java new file mode 100644 index 00000000..46d6c616 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java @@ -0,0 +1,300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.text.NumberFormat; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.StringUtils; + +/** + * Prints plain text output of the test to a specified Writer. + * Inspired by the PlainJUnitResultFormatter. + * + * @see FormatterElement + * @see PlainJUnitResultFormatter + */ +public class BriefJUnitResultFormatter implements JUnitResultFormatter, IgnoredTestListener { + + private static final double ONE_SECOND = 1000.0; + + /** + * Where to write the log to. + */ + private OutputStream out; + + /** + * Used for writing the results. + */ + private BufferedWriter output; + + /** + * Used as part of formatting the results. + */ + private StringWriter results; + + /** + * Used for writing formatted results to. + */ + private BufferedWriter resultWriter; + + /** + * Formatter for timings. + */ + private NumberFormat numberFormat = NumberFormat.getInstance(); + + /** + * Output suite has written to System.out + */ + private String systemOutput = null; + + /** + * Output suite has written to System.err + */ + private String systemError = null; + + /** + * Constructor for BriefJUnitResultFormatter. + */ + public BriefJUnitResultFormatter() { + results = new StringWriter(); + resultWriter = new BufferedWriter(results); + } + + /** + * Sets the stream the formatter is supposed to write its results to. + * @param out the output stream to write to + */ + public void setOutput(OutputStream out) { + this.out = out; + output = new BufferedWriter(new java.io.OutputStreamWriter(out)); + } + + /** + * @see JUnitResultFormatter#setSystemOutput(String) + */ + /** {@inheritDoc}. */ + public void setSystemOutput(String out) { + systemOutput = out; + } + + /** + * @see JUnitResultFormatter#setSystemError(String) + */ + /** {@inheritDoc}. */ + public void setSystemError(String err) { + systemError = err; + } + + + /** + * The whole testsuite started. + * @param suite the test suite + */ + public void startTestSuite(JUnitTest suite) { + if (output == null) { + return; // Quick return - no output do nothing. + } + StringBuffer sb = new StringBuffer("Testsuite: "); + sb.append(suite.getName()); + sb.append(StringUtils.LINE_SEP); + try { + output.write(sb.toString()); + output.flush(); + } catch (IOException ex) { + throw new BuildException(ex); + } + } + + /** + * The whole testsuite ended. + * @param suite the test suite + */ + public void endTestSuite(JUnitTest suite) { + StringBuffer sb = new StringBuffer("Tests run: "); + sb.append(suite.runCount()); + sb.append(", Failures: "); + sb.append(suite.failureCount()); + sb.append(", Errors: "); + sb.append(suite.errorCount()); + sb.append(", Skipped: "); + sb.append(suite.skipCount()); + sb.append(", Time elapsed: "); + sb.append(numberFormat.format(suite.getRunTime() / ONE_SECOND)); + sb.append(" sec"); + sb.append(StringUtils.LINE_SEP); + sb.append(StringUtils.LINE_SEP); + + // append the err and output streams to the log + if (systemOutput != null && systemOutput.length() > 0) { + sb.append("------------- Standard Output ---------------") + .append(StringUtils.LINE_SEP) + .append(systemOutput) + .append("------------- ---------------- ---------------") + .append(StringUtils.LINE_SEP); + } + + if (systemError != null && systemError.length() > 0) { + sb.append("------------- Standard Error -----------------") + .append(StringUtils.LINE_SEP) + .append(systemError) + .append("------------- ---------------- ---------------") + .append(StringUtils.LINE_SEP); + } + + if (output != null) { + try { + output.write(sb.toString()); + resultWriter.close(); + output.write(results.toString()); + } catch (IOException ex) { + throw new BuildException(ex); + } finally { + try { + output.flush(); + } catch (IOException ex) { + // swallow, there has likely been an exception before this + } + if (out != System.out && out != System.err) { + FileUtils.close(out); + } + } + } + } + + /** + * A test started. + * @param test a test + */ + public void startTest(Test test) { + } + + /** + * A test ended. + * @param test a test + */ + public void endTest(Test test) { + } + + /** + * Interface TestListener for JUnit <= 3.4. + * + * <p>A Test failed. + * @param test a test + * @param t the exception thrown by the test + */ + public void addFailure(Test test, Throwable t) { + formatError("\tFAILED", test, t); + } + + /** + * Interface TestListener for JUnit > 3.4. + * + * <p>A Test failed. + * @param test a test + * @param t the assertion failed by the test + */ + public void addFailure(Test test, AssertionFailedError t) { + addFailure(test, (Throwable) t); + } + + /** + * A test caused an error. + * @param test a test + * @param error the error thrown by the test + */ + public void addError(Test test, Throwable error) { + formatError("\tCaused an ERROR", test, error); + } + + /** + * Format the test for printing.. + * @param test a test + * @return the formatted testname + */ + protected String formatTest(Test test) { + if (test == null) { + return "Null Test: "; + } else { + return "Testcase: " + test.toString() + ":"; + } + } + + /** + * Format an error and print it. + * @param type the type of error + * @param test the test that failed + * @param error the exception that the test threw + */ + protected synchronized void formatError(String type, Test test, + Throwable error) { + if (test != null) { + endTest(test); + } + + try { + resultWriter.write(formatTest(test) + type); + resultWriter.newLine(); + resultWriter.write(String.valueOf(error.getMessage())); + resultWriter.newLine(); + String strace = JUnitTestRunner.getFilteredTrace(error); + resultWriter.write(strace); + resultWriter.newLine(); + resultWriter.newLine(); + } catch (IOException ex) { + throw new BuildException(ex); + } + } + + + public void testIgnored(Test test) { + formatSkip(test, JUnitVersionHelper.getIgnoreMessage(test)); + } + + + public void formatSkip(Test test, String message) { + if (test != null) { + endTest(test); + } + + try { + resultWriter.write(formatTest(test) + "SKIPPED"); + if (message != null) { + resultWriter.write(": "); + resultWriter.write(message); + } + resultWriter.newLine(); + } catch (IOException ex) { + throw new BuildException(ex); + } + + } + + public void testAssumptionFailure(Test test, Throwable cause) { + formatSkip(test, cause.getMessage()); + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java new file mode 100644 index 00000000..368c72e2 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +/** + * Constants, like filenames shared between various classes in this package. + */ +public class Constants { + + private Constants() { + } + + static final String METHOD_NAMES = "methods="; + static final String HALT_ON_ERROR = "haltOnError="; + static final String HALT_ON_FAILURE = "haltOnFailure="; + static final String FILTERTRACE = "filtertrace="; + static final String CRASHFILE = "crashfile="; + static final String BEFORE_FIRST_TEST = "BeforeFirstTest"; + static final String PROPSFILE = "propsfile="; + static final String SHOWOUTPUT = "showoutput="; + static final String OUTPUT_TO_FORMATTERS = "outputtoformatters="; + static final String FORMATTER = "formatter="; + static final String LOGTESTLISTENEREVENTS = "logtestlistenerevents="; + static final String TESTSFILE = "testsfile="; + static final String TERMINATED_SUCCESSFULLY = "terminated successfully"; + static final String LOG_FAILED_TESTS="logfailedtests="; + static final String SKIP_NON_TESTS = "skipNonTests="; + /** @since Ant 1.9.4 */ + static final String THREADID="threadid="; +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/CustomJUnit4TestAdapterCache.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/CustomJUnit4TestAdapterCache.java new file mode 100644 index 00000000..8ad40dd6 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/CustomJUnit4TestAdapterCache.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import junit.framework.JUnit4TestAdapter; +import junit.framework.JUnit4TestAdapterCache; +import junit.framework.TestResult; + +import org.junit.runner.Description; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; + +/** + * Provides a custom implementation of the notifier for a JUnit4TestAdapter + * so that skipped and ignored tests can be reported to the existing + * <tt>TestListener</tt>s. + * + */ +public class CustomJUnit4TestAdapterCache extends JUnit4TestAdapterCache { + + private static final CustomJUnit4TestAdapterCache INSTANCE = new CustomJUnit4TestAdapterCache(); + + public static CustomJUnit4TestAdapterCache getInstance() { + return INSTANCE; + } + + private CustomJUnit4TestAdapterCache() { + super(); + } + + public RunNotifier getNotifier(final TestResult result, final JUnit4TestAdapter adapter) { + return getNotifier(result); + } + + public RunNotifier getNotifier(final TestResult result) { + + final IgnoredTestResult resultWrapper = (IgnoredTestResult) result; + + RunNotifier notifier = new RunNotifier(); + notifier.addListener(new RunListener() { + @Override + public void testFailure(Failure failure) throws Exception { + result.addError(asTest(failure.getDescription()), failure.getException()); + } + + @Override + public void testFinished(Description description) throws Exception { + result.endTest(asTest(description)); + } + + @Override + public void testStarted(Description description) throws Exception { + result.startTest(asTest(description)); + } + + @Override + public void testIgnored(Description description) throws Exception { + if (resultWrapper != null) { + resultWrapper.testIgnored(asTest(description)); + } + } + + @Override + public void testAssumptionFailure(Failure failure) { + if (resultWrapper != null) { + resultWrapper.testAssumptionFailure(asTest(failure.getDescription()), failure.getException()); + } + } + }); + + return notifier; + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java new file mode 100644 index 00000000..325f44cf --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.util.Vector; + +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; + +/** + * Some utilities that might be useful when manipulating DOM trees. + * + */ +public final class DOMUtil { + + /** unused constructor */ + private DOMUtil() { + } + + /** + * Filter interface to be applied when iterating over a DOM tree. + * Just think of it like a <tt>FileFilter</tt> clone. + */ + public interface NodeFilter { + /** + * @param node the node to check for acceptance. + * @return <tt>true</tt> if the node is accepted by this filter, + * otherwise <tt>false</tt> + */ + boolean accept(Node node); + } + + /** + * list a set of node that match a specific filter. The list can be made + * recursively or not. + * @param parent the parent node to search from + * @param filter the filter that children should match. + * @param recurse <tt>true</tt> if you want the list to be made recursively + * otherwise <tt>false</tt>. + * @return the node list that matches the filter. + */ + public static NodeList listChildNodes(Node parent, NodeFilter filter, boolean recurse) { + NodeListImpl matches = new NodeListImpl(); + NodeList children = parent.getChildNodes(); + if (children != null) { + final int len = children.getLength(); + for (int i = 0; i < len; i++) { + Node child = children.item(i); + if (filter.accept(child)) { + matches.addElement(child); + } + if (recurse) { + NodeList recmatches = listChildNodes(child, filter, recurse); + final int reclength = recmatches.getLength(); + for (int j = 0; j < reclength; j++) { + matches.addElement(recmatches.item(i)); + } + } + } + } + return matches; + } + + /** custom implementation of a nodelist */ + public static class NodeListImpl extends Vector implements NodeList { + private static final long serialVersionUID = 3175749150080946423L; + + /** + * Get the number of nodes in the list. + * @return the length of the list. + */ + public int getLength() { + return size(); + } + /** + * Get a particular node. + * @param i the index of the node to get. + * @return the node if the index is in bounds, null otherwise. + */ + public Node item(int i) { + try { + return (Node) elementAt(i); + } catch (ArrayIndexOutOfBoundsException e) { + return null; // conforming to NodeList interface + } + } + } + + /** + * return the attribute value of an element. + * @param node the node to get the attribute from. + * @param name the name of the attribute we are looking for the value. + * @return the value of the requested attribute or <tt>null</tt> if the + * attribute was not found or if <tt>node</tt> is not an <tt>Element</tt>. + */ + public static String getNodeAttribute(Node node, String name) { + if (node instanceof Element) { + Element element = (Element) node; + return element.getAttribute(name); + } + return null; + } + + + /** + * Iterate over the children of a given node and return the first node + * that has a specific name. + * @param parent the node to search child from. Can be <tt>null</tt>. + * @param tagname the child name we are looking for. Cannot be <tt>null</tt>. + * @return the first child that matches the given name or <tt>null</tt> if + * the parent is <tt>null</tt> or if a child does not match the + * given name. + */ + public static Element getChildByTagName (Node parent, String tagname) { + if (parent == null) { + return null; + } + NodeList childList = parent.getChildNodes(); + final int len = childList.getLength(); + for (int i = 0; i < len; i++) { + Node child = childList.item(i); + if (child != null && child.getNodeType() == Node.ELEMENT_NODE + && child.getNodeName().equals(tagname)) { + return (Element) child; + } + } + return null; + } + + /** + * Simple tree walker that will clone recursively a node. This is to + * avoid using parser-specific API such as Sun's <tt>changeNodeOwner</tt> + * when we are dealing with DOM L1 implementations since <tt>cloneNode(boolean)</tt> + * will not change the owner document. + * <tt>changeNodeOwner</tt> is much faster and avoid the costly cloning process. + * <tt>importNode</tt> is in the DOM L2 interface. + * @param parent the node parent to which we should do the import to. + * @param child the node to clone recursively. Its clone will be + * appended to <tt>parent</tt>. + * @return the cloned node that is appended to <tt>parent</tt> + */ + public static Node importNode(Node parent, Node child) { + Node copy = null; + final Document doc = parent.getOwnerDocument(); + + switch (child.getNodeType()) { + case Node.CDATA_SECTION_NODE: + copy = doc.createCDATASection(((CDATASection) child).getData()); + break; + case Node.COMMENT_NODE: + copy = doc.createComment(((Comment) child).getData()); + break; + case Node.DOCUMENT_FRAGMENT_NODE: + copy = doc.createDocumentFragment(); + break; + case Node.ELEMENT_NODE: + final Element elem = doc.createElement(((Element) child).getTagName()); + copy = elem; + final NamedNodeMap attributes = child.getAttributes(); + if (attributes != null) { + final int size = attributes.getLength(); + for (int i = 0; i < size; i++) { + final Attr attr = (Attr) attributes.item(i); + elem.setAttribute(attr.getName(), attr.getValue()); + } + } + break; + case Node.ENTITY_REFERENCE_NODE: + copy = doc.createEntityReference(child.getNodeName()); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + final ProcessingInstruction pi = (ProcessingInstruction) child; + copy = doc.createProcessingInstruction(pi.getTarget(), pi.getData()); + break; + case Node.TEXT_NODE: + copy = doc.createTextNode(((Text) child).getData()); + break; + default: + // this should never happen + throw new IllegalStateException("Invalid node type: " + child.getNodeType()); + } + + // okay we have a copy of the child, now the child becomes the parent + // and we are iterating recursively over its children. + try { + final NodeList children = child.getChildNodes(); + if (children != null) { + final int size = children.getLength(); + for (int i = 0; i < size; i++) { + final Node newChild = children.item(i); + if (newChild != null) { + importNode(copy, newChild); + } + } + } + } catch (DOMException ignored) { + // Ignore + } + + // bingo append it. (this should normally not be done here) + parent.appendChild(copy); + return copy; + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java new file mode 100644 index 00000000..327547ef --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * A couple of methods related to enumerations that might be useful. + * This class should probably disappear once the required JDK is set to 1.2 + * instead of 1.1. + * + */ +public final class Enumerations { + + private Enumerations() { + } + + /** + * creates an enumeration from an array of objects. + * @param array the array of object to enumerate. + * @return the enumeration over the array of objects. + */ + public static Enumeration fromArray(Object[] array) { + return new ArrayEnumeration(array); + } + + /** + * creates an enumeration from an array of enumeration. The created enumeration + * will sequentially enumerate over all elements of each enumeration and skip + * <tt>null</tt> enumeration elements in the array. + * @param enums the array of enumerations. + * @return the enumeration over the array of enumerations. + */ + public static Enumeration fromCompound(Enumeration[] enums) { + return new CompoundEnumeration(enums); + } + +} + + +/** + * Convenient enumeration over an array of objects. + */ +class ArrayEnumeration implements Enumeration { + + /** object array */ + private Object[] array; + + /** current index */ + private int pos; + + /** + * Initialize a new enumeration that wraps an array. + * @param array the array of object to enumerate. + */ + public ArrayEnumeration(Object[] array) { + this.array = array; + this.pos = 0; + } + /** + * Tests if this enumeration contains more elements. + * + * @return <code>true</code> if and only if this enumeration object + * contains at least one more element to provide; + * <code>false</code> otherwise. + */ + public boolean hasMoreElements() { + return (pos < array.length); + } + + /** + * Returns the next element of this enumeration if this enumeration + * object has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() throws NoSuchElementException { + if (hasMoreElements()) { + Object o = array[pos]; + pos++; + return o; + } + throw new NoSuchElementException(); + } +} +/** + * Convenient enumeration over an array of enumeration. For example: + * <pre> + * Enumeration e1 = v1.elements(); + * while (e1.hasMoreElements()) { + * // do something + * } + * Enumeration e2 = v2.elements(); + * while (e2.hasMoreElements()) { + * // do the same thing + * } + * </pre> + * can be written as: + * <pre> + * Enumeration[] enums = { v1.elements(), v2.elements() }; + * Enumeration e = Enumerations.fromCompound(enums); + * while (e.hasMoreElements()) { + * // do something + * } + * </pre> + * Note that the enumeration will skip null elements in the array. The following is + * thus possible: + * <pre> + * Enumeration[] enums = { v1.elements(), null, v2.elements() }; // a null enumeration in the array + * Enumeration e = Enumerations.fromCompound(enums); + * while (e.hasMoreElements()) { + * // do something + * } + * </pre> + */ + class CompoundEnumeration implements Enumeration { + + /** enumeration array */ + private Enumeration[] enumArray; + + /** index in the enums array */ + private int index = 0; + + public CompoundEnumeration(Enumeration[] enumarray) { + this.enumArray = enumarray; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return <code>true</code> if and only if this enumeration object + * contains at least one more element to provide; + * <code>false</code> otherwise. + */ + public boolean hasMoreElements() { + while (index < enumArray.length) { + if (enumArray[index] != null && enumArray[index].hasMoreElements()) { + return true; + } + index++; + } + return false; + } + + /** + * Returns the next element of this enumeration if this enumeration + * object has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() throws NoSuchElementException { + if (hasMoreElements()) { + return enumArray[index].nextElement(); + } + throw new NoSuchElementException(); + } +} + + diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/FailureRecorder.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/FailureRecorder.java new file mode 100644 index 00000000..3046b75a --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/FailureRecorder.java @@ -0,0 +1,448 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Iterator; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.Vector; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; + +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.util.FileUtils; + +/** + * <p>Collects all failing test <i>cases</i> and creates a new JUnit test class containing + * a suite() method which calls these failed tests.</p> + * <p>Having classes <i>A</i> ... <i>D</i> with each several testcases you could earn a new + * test class like + * <pre> + * // generated on: 2007.08.06 09:42:34,555 + * import junit.framework.*; + * public class FailedTests extends TestCase { + * public FailedTests(String testname) { + * super(testname); + * } + * public static Test suite() { + * TestSuite suite = new TestSuite(); + * suite.addTest( new B("test04") ); + * suite.addTest( new org.D("test10") ); + * return suite; + * } + * } + * </pre> + * + * Because each running test case gets its own formatter, we collect + * the failing test cases in a static list. Because we dont have a finalizer + * method in the formatters "lifecycle", we register this formatter as + * BuildListener and generate the new java source on taskFinished event. + * + * @since Ant 1.8.0 + */ +public class FailureRecorder extends ProjectComponent implements JUnitResultFormatter, BuildListener { + + /** + * This is the name of a magic System property ({@value}). The value of this + * <b>System</b> property should point to the location where to store the + * generated class (without suffix). + * Default location and name is defined in DEFAULT_CLASS_LOCATION. + * @see #DEFAULT_CLASS_LOCATION + */ + public static final String MAGIC_PROPERTY_CLASS_LOCATION + = "ant.junit.failureCollector"; + + /** Default location and name for the generated JUnit class file, + * in the temp directory + FailedTests */ + public static final String DEFAULT_CLASS_LOCATION + = System.getProperty("java.io.tmpdir") + "FailedTests"; + + /** Prefix for logging. {@value} */ + private static final String LOG_PREFIX = " [junit]"; + + /** Class names of failed tests without duplicates. */ + private static SortedSet/*<TestInfos>*/ failedTests = new TreeSet(); + + /** A writer for writing the generated source to. */ + private BufferedWriter writer; + + /** + * Location and name of the generated JUnit class. + * Lazy instantiated via getLocationName(). + */ + private static String locationName; + + /** + * Returns the (lazy evaluated) location for the collector class. + * Order for evaluation: System property > Ant property > default value + * @return location for the collector class + * @see #MAGIC_PROPERTY_CLASS_LOCATION + * @see #DEFAULT_CLASS_LOCATION + */ + private String getLocationName() { + if (locationName == null) { + String syspropValue = System.getProperty(MAGIC_PROPERTY_CLASS_LOCATION); + String antpropValue = getProject().getProperty(MAGIC_PROPERTY_CLASS_LOCATION); + + if (syspropValue != null) { + locationName = syspropValue; + verbose("System property '" + MAGIC_PROPERTY_CLASS_LOCATION + "' set, so use " + + "its value '" + syspropValue + "' as location for collector class."); + } else if (antpropValue != null) { + locationName = antpropValue; + verbose("Ant property '" + MAGIC_PROPERTY_CLASS_LOCATION + "' set, so use " + + "its value '" + antpropValue + "' as location for collector class."); + } else { + locationName = DEFAULT_CLASS_LOCATION; + verbose("System property '" + MAGIC_PROPERTY_CLASS_LOCATION + "' not set, so use " + + "value as location for collector class: '" + + DEFAULT_CLASS_LOCATION + "'"); + } + + File locationFile = new File(locationName); + if (!locationFile.isAbsolute()) { + File f = new File(getProject().getBaseDir(), locationName); + locationName = f.getAbsolutePath(); + verbose("Location file is relative (" + locationFile + ")" + + " use absolute path instead (" + locationName + ")"); + } + } + + return locationName; + } + + /** + * This method is called by the Ant runtime by reflection. We use the project reference for + * registration of this class as BuildListener. + * + * @param project + * project reference + */ + public void setProject(Project project) { + // store project reference for logging + super.setProject(project); + // check if already registered + boolean alreadyRegistered = false; + Vector allListeners = project.getBuildListeners(); + final int size = allListeners.size(); + for (int i = 0; i < size; i++) { + Object listener = allListeners.get(i); + if (listener instanceof FailureRecorder) { + alreadyRegistered = true; + break; + } + } + // register if needed + if (!alreadyRegistered) { + verbose("Register FailureRecorder (@" + this.hashCode() + ") as BuildListener"); + project.addBuildListener(this); + } + } + + // ===== JUnitResultFormatter ===== + + /** + * Not used + * {@inheritDoc} + */ + public void endTestSuite(JUnitTest suite) throws BuildException { + } + + /** + * Add the failed test to the list. + * @param test the test that errored. + * @param throwable the reason it errored. + * @see junit.framework.TestListener#addError(junit.framework.Test, java.lang.Throwable) + */ + public void addError(Test test, Throwable throwable) { + failedTests.add(new TestInfos(test)); + } + + // CheckStyle:LineLengthCheck OFF - @see is long + /** + * Add the failed test to the list. + * @param test the test that failed. + * @param error the assertion that failed. + * @see junit.framework.TestListener#addFailure(junit.framework.Test, junit.framework.AssertionFailedError) + */ + // CheckStyle:LineLengthCheck ON + public void addFailure(Test test, AssertionFailedError error) { + failedTests.add(new TestInfos(test)); + } + + /** + * Not used + * {@inheritDoc} + */ + public void setOutput(OutputStream out) { + // unused, close output file so it can be deleted before the VM exits + if (out != System.out) { + FileUtils.close(out); + } + } + + /** + * Not used + * {@inheritDoc} + */ + public void setSystemError(String err) { + } + + /** + * Not used + * {@inheritDoc} + */ + public void setSystemOutput(String out) { + } + + /** + * Not used + * {@inheritDoc} + */ + public void startTestSuite(JUnitTest suite) throws BuildException { + } + + /** + * Not used + * {@inheritDoc} + */ + public void endTest(Test test) { + } + + /** + * Not used + * {@inheritDoc} + */ + public void startTest(Test test) { + } + + // ===== "Templates" for generating the JUnit class ===== + + private void writeJavaClass() { + try { + File sourceFile = new File((getLocationName() + ".java")); + verbose("Write collector class to '" + sourceFile.getAbsolutePath() + "'"); + + if (sourceFile.exists() && !sourceFile.delete()) { + throw new IOException("could not delete " + sourceFile); + } + writer = new BufferedWriter(new FileWriter(sourceFile)); + + createClassHeader(); + createSuiteMethod(); + createClassFooter(); + + } catch (IOException e) { + e.printStackTrace(); + } finally { + FileUtils.close(writer); + } + } + + private void createClassHeader() throws IOException { + String className = getLocationName().replace('\\', '/'); + if (className.indexOf('/') > -1) { + className = className.substring(className.lastIndexOf('/') + 1); + } + SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss,SSS"); + writer.write("// generated on: "); + writer.write(sdf.format(new Date())); + writer.newLine(); + writer.write("import junit.framework.*;"); + writer.newLine(); + writer.write("public class "); + writer.write(className); + // If this class does not extend TC, Ant doesn't run these + writer.write(" extends TestCase {"); + writer.newLine(); + // standard String-constructor + writer.write(" public "); + writer.write(className); + writer.write("(String testname) {"); + writer.newLine(); + writer.write(" super(testname);"); + writer.newLine(); + writer.write(" }"); + writer.newLine(); + } + + private void createSuiteMethod() throws IOException { + writer.write(" public static Test suite() {"); + writer.newLine(); + writer.write(" TestSuite suite = new TestSuite();"); + writer.newLine(); + for (Iterator iter = failedTests.iterator(); iter.hasNext();) { + TestInfos testInfos = (TestInfos) iter.next(); + writer.write(" suite.addTest("); + writer.write(String.valueOf(testInfos)); + writer.write(");"); + writer.newLine(); + } + writer.write(" return suite;"); + writer.newLine(); + writer.write(" }"); + writer.newLine(); + } + + private void createClassFooter() throws IOException { + writer.write("}"); + writer.newLine(); + } + + // ===== Helper classes and methods ===== + + /** + * Logging facade in INFO-mode. + * @param message Log-message + */ + public void log(String message) { + getProject().log(LOG_PREFIX + " " + message, Project.MSG_INFO); + } + + /** + * Logging facade in VERBOSE-mode. + * @param message Log-message + */ + public void verbose(String message) { + getProject().log(LOG_PREFIX + " " + message, Project.MSG_VERBOSE); + } + + /** + * TestInfos holds information about a given test for later use. + */ + public static class TestInfos implements Comparable { + + /** The class name of the test. */ + private final String className; + + /** The method name of the testcase. */ + private final String methodName; + + /** + * This constructor extracts the needed information from the given test. + * @param test Test to analyze + */ + public TestInfos(Test test) { + className = test.getClass().getName(); + String _methodName = test.toString(); + methodName = _methodName.substring(0, _methodName.indexOf('(')); + } + + /** + * This String-Representation can directly be used for instantiation of + * the JUnit testcase. + * @return the string representation. + * @see java.lang.Object#toString() + * @see FailureRecorder#createSuiteMethod() + */ + public String toString() { + return "new " + className + "(\"" + methodName + "\")"; + } + + /** + * The SortedMap needs comparable elements. + * @param other the object to compare to. + * @return the result of the comparison. + * @see java.lang.Comparable#compareTo + * @see SortedSet#comparator() + */ + public int compareTo(Object other) { + if (other instanceof TestInfos) { + TestInfos otherInfos = (TestInfos) other; + return toString().compareTo(otherInfos.toString()); + } else { + return -1; + } + } + public boolean equals(Object obj) { + return obj instanceof TestInfos && toString().equals(obj.toString()); + } + public int hashCode() { + return toString().hashCode(); + } + } + + // ===== BuildListener ===== + + /** + * Not used + * {@inheritDoc} + */ + public void buildFinished(BuildEvent event) { + } + + /** + * Not used + * {@inheritDoc} + */ + public void buildStarted(BuildEvent event) { + } + + /** + * Not used + * {@inheritDoc} + */ + public void messageLogged(BuildEvent event) { + } + + /** + * Not used + * {@inheritDoc} + */ + public void targetFinished(BuildEvent event) { + } + + /** + * Not used + * {@inheritDoc} + */ + public void targetStarted(BuildEvent event) { + } + + /** + * The task outside of this JUnitResultFormatter is the <junit> task. So all tests passed + * and we could create the new java class. + * @param event not used + * @see org.apache.tools.ant.BuildListener#taskFinished(org.apache.tools.ant.BuildEvent) + */ + public void taskFinished(BuildEvent event) { + if (!failedTests.isEmpty()) { + writeJavaClass(); + } + } + + /** + * Not used + * {@inheritDoc} + */ + public void taskStarted(BuildEvent event) { + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java new file mode 100644 index 00000000..f9fbcb0a --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java @@ -0,0 +1,401 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.PropertyHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.util.KeepAliveOutputStream; + +/** + * <p> A wrapper for the implementations of <code>JUnitResultFormatter</code>. + * In particular, used as a nested <code><formatter></code> element in + * a <code><junit></code> task. + * <p> For example, + * <code><pre> + * <junit printsummary="no" haltonfailure="yes" fork="false"> + * <formatter type="plain" usefile="false" /> + * <test name="org.apache.ecs.InternationalCharTest" /> + * </junit></pre></code> + * adds a <code>plain</code> type implementation + * (<code>PlainJUnitResultFormatter</code>) to display the results of the test. + * + * <p> Either the <code>type</code> or the <code>classname</code> attribute + * must be set. + * + * @see JUnitTask + * @see XMLJUnitResultFormatter + * @see BriefJUnitResultFormatter + * @see PlainJUnitResultFormatter + * @see FailureRecorder + * @see JUnitResultFormatter + */ +public class FormatterElement { + + private String classname; + private String extension; + private OutputStream out = new KeepAliveOutputStream(System.out); + private File outFile; + private boolean useFile = true; + private Object ifCond; + private Object unlessCond; + + /** + * Store the project reference for passing it to nested components. + * @since Ant 1.8 + */ + private Project project; + + /** xml formatter class */ + public static final String XML_FORMATTER_CLASS_NAME = + "org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter"; + /** brief formatter class */ + public static final String BRIEF_FORMATTER_CLASS_NAME = + "org.apache.tools.ant.taskdefs.optional.junit.BriefJUnitResultFormatter"; + /** plain formatter class */ + public static final String PLAIN_FORMATTER_CLASS_NAME = + "org.apache.tools.ant.taskdefs.optional.junit.PlainJUnitResultFormatter"; + /** failure recorder class */ + public static final String FAILURE_RECORDER_CLASS_NAME = + "org.apache.tools.ant.taskdefs.optional.junit.FailureRecorder"; + + /** + * <p> Quick way to use a standard formatter. + * + * <p> At the moment, there are three supported standard formatters. + * <ul> + * <li> The <code>xml</code> type uses a <code>XMLJUnitResultFormatter</code>. + * <li> The <code>brief</code> type uses a <code>BriefJUnitResultFormatter</code>. + * <li> The <code>plain</code> type (the default) uses a <code>PlainJUnitResultFormatter</code>. + * <li> The <code>failure</code> type uses a <code>FailureRecorder</code>. + * </ul> + * + * <p> Sets <code>classname</code> attribute - so you can't use that + * attribute if you use this one. + * @param type the enumerated value to use. + */ + public void setType(TypeAttribute type) { + if ("xml".equals(type.getValue())) { + setClassname(XML_FORMATTER_CLASS_NAME); + } else { + if ("brief".equals(type.getValue())) { + setClassname(BRIEF_FORMATTER_CLASS_NAME); + } else { + if ("failure".equals(type.getValue())) { + setClassname(FAILURE_RECORDER_CLASS_NAME); + } else { // must be plain, ensured by TypeAttribute + setClassname(PLAIN_FORMATTER_CLASS_NAME); + } + } + } + } + + /** + * <p> Set name of class to be used as the formatter. + * + * <p> This class must implement <code>JUnitResultFormatter</code> + * @param classname the name of the formatter class. + */ + public void setClassname(String classname) { + this.classname = classname; + if (XML_FORMATTER_CLASS_NAME.equals(classname)) { + setExtension(".xml"); + } else if (PLAIN_FORMATTER_CLASS_NAME.equals(classname)) { + setExtension(".txt"); + } else if (BRIEF_FORMATTER_CLASS_NAME.equals(classname)) { + setExtension(".txt"); + } + } + + /** + * Get name of class to be used as the formatter. + * @return the name of the class. + */ + public String getClassname() { + return classname; + } + + /** + * Set the extension to use for the report file. + * @param ext the extension to use. + */ + public void setExtension(String ext) { + this.extension = ext; + } + + /** + * Get the extension used for the report file. + * @return the extension. + */ + public String getExtension() { + return extension; + } + + /** + * <p> Set the file which the formatte should log to. + * + * <p> Note that logging to file must be enabled . + */ + void setOutfile(File out) { + this.outFile = out; + } + + /** + * <p> Set output stream for formatter to use. + * + * <p> Defaults to standard out. + * @param out the output stream to use. + */ + public void setOutput(OutputStream out) { + if (out == System.out || out == System.err) { + out = new KeepAliveOutputStream(out); + } + this.out = out; + } + + /** + * Set whether the formatter should log to file. + * @param useFile if true use a file, if false send + * to standard out. + */ + public void setUseFile(boolean useFile) { + this.useFile = useFile; + } + + /** + * Get whether the formatter should log to file. + */ + boolean getUseFile() { + return useFile; + } + + /** + * Set whether this formatter should be used. It will be used if + * the expression evaluates to true or the name of a property + * which has been set, otherwise it won't. + * @param ifCond name of property + * @since Ant 1.8.0 + */ + public void setIf(Object ifCond) { + this.ifCond = ifCond; + } + + /** + * Set whether this formatter should be used. It will be used if + * the expression evaluates to true or the name of a property + * which has been set, otherwise it won't. + * @param ifCond name of property + */ + public void setIf(String ifCond) { + setIf((Object) ifCond); + } + + /** + * Set whether this formatter should NOT be used. It will be used + * if the expression evaluates to false or the name of a property + * which has not been set, orthwise it will not be used. + * @param unlessCond name of property + * @since Ant 1.8.0 + */ + public void setUnless(Object unlessCond) { + this.unlessCond = unlessCond; + } + + /** + * Set whether this formatter should NOT be used. It will be used + * if the expression evaluates to false or the name of a property + * which has not been set, orthwise it will not be used. + * @param unlessCond name of property + */ + public void setUnless(String unlessCond) { + setUnless((Object) unlessCond); + } + + /** + * Ensures that the selector passes the conditions placed + * on it with <code>if</code> and <code>unless</code> properties. + * @param t the task the this formatter is used in. + * @return true if the formatter should be used. + */ + public boolean shouldUse(Task t) { + PropertyHelper ph = PropertyHelper.getPropertyHelper(t.getProject()); + return ph.testIfCondition(ifCond) + && ph.testUnlessCondition(unlessCond); + } + + /** + * @since Ant 1.2 + */ + JUnitTaskMirror.JUnitResultFormatterMirror createFormatter() throws BuildException { + return createFormatter(null); + } + + /** + * Store the project reference for passing it to nested components. + * @param project the reference + * @since Ant 1.8 + */ + public void setProject(Project project) { + this.project = project; + } + + + /** + * @since Ant 1.6 + */ + JUnitTaskMirror.JUnitResultFormatterMirror createFormatter(ClassLoader loader) + throws BuildException { + + if (classname == null) { + throw new BuildException("you must specify type or classname"); + } + //although this code appears to duplicate that of ClasspathUtils.newInstance, + //we cannot use that because this formatter may run in a forked process, + //without that class. + Class f = null; + try { + if (loader == null) { + f = Class.forName(classname); + } else { + f = Class.forName(classname, true, loader); + } + } catch (ClassNotFoundException e) { + throw new BuildException( + "Using loader " + loader + " on class " + classname + + ": " + e, e); + } catch (NoClassDefFoundError e) { + throw new BuildException( + "Using loader " + loader + " on class " + classname + + ": " + e, e); + } + + Object o = null; + try { + o = f.newInstance(); + } catch (InstantiationException e) { + throw new BuildException(e); + } catch (IllegalAccessException e) { + throw new BuildException(e); + } + + if (!(o instanceof JUnitTaskMirror.JUnitResultFormatterMirror)) { + throw new BuildException(classname + " is not a JUnitResultFormatter"); + } + JUnitTaskMirror.JUnitResultFormatterMirror r = + (JUnitTaskMirror.JUnitResultFormatterMirror) o; + if (useFile && outFile != null) { + out = new DelayedFileOutputStream(outFile); + } + r.setOutput(out); + + + boolean needToSetProjectReference = true; + try { + Field field = r.getClass().getField("project"); + Object value = field.get(r); + if (value instanceof Project) { + // there is already a project reference so dont overwrite this + needToSetProjectReference = false; + } + } catch (NoSuchFieldException e) { + // no field present, so no previous reference exists + } catch (IllegalAccessException e) { + throw new BuildException(e); + } + + if (needToSetProjectReference) { + Method setter; + try { + setter = r.getClass().getMethod("setProject", new Class[] {Project.class}); + setter.invoke(r, new Object[] {project}); + } catch (NoSuchMethodException e) { + // no setProject to invoke; just ignore + } catch (IllegalAccessException e) { + throw new BuildException(e); + } catch (InvocationTargetException e) { + throw new BuildException(e); + } + } + + return r; + } + + /** + * <p> Enumerated attribute with the values "plain", "xml", "brief" and "failure". + * + * <p> Use to enumerate options for <code>type</code> attribute. + */ + public static class TypeAttribute extends EnumeratedAttribute { + /** {@inheritDoc}. */ + public String[] getValues() { + return new String[] {"plain", "xml", "brief", "failure"}; + } + } + + /** + * A standard FileOutputStream creates a file as soon as it's opened. This + * class delays the creation of the file until the first time a caller attempts + * to write to it so we don't end up with empty files if the listeners don't use + * them. + */ + private static class DelayedFileOutputStream extends OutputStream { + + private BufferedOutputStream outputStream; + private final File file; + + public DelayedFileOutputStream(File file) { + this.file = file; + } + + @Override + public void write(int b) throws IOException { + synchronized (this) { + if (outputStream == null) { + outputStream = new BufferedOutputStream(new FileOutputStream(file)); + } + } + outputStream.write(b); + } + + @Override + public void flush() throws IOException { + if (outputStream != null) { + outputStream.flush(); + } + } + + @Override + public void close() throws IOException { + if (outputStream != null) { + outputStream.close(); + } + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/IgnoredTestListener.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/IgnoredTestListener.java new file mode 100644 index 00000000..6741912e --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/IgnoredTestListener.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import junit.framework.Test; +import junit.framework.TestListener; + +/** + * Provides the functionality for TestListeners to be able to be notified of + * the necessary JUnit4 events for test being ignored (@Ignore annotation) + * or skipped (Assume failures). Tests written in JUnit4 will report against + * the methods in this interface alongside the methods in the existing TestListener + */ +public interface IgnoredTestListener extends TestListener { + + /** + * Reports when a test has been marked with the @Ignore annotation. The parameter + * should normally be typed to JUnit's {@link junit.framework.JUnit4TestCaseFacade} + * so implementing classes should be able to get the details of the ignore by casting + * the argument and retrieving the descriptor from the test. + * @param test the details of the test and failure that have triggered this report. + */ + void testIgnored(Test test); + + /** + * Receive a report that a test has failed an assumption. Within JUnit4 + * this is normally treated as a test being skipped, although how any + * listener handles this is up to that specific listener.<br /> + * <b>Note:</b> Tests that throw assumption failures will still report + * the endTest method, which may differ from how the addError and addFailure + * methods work, it's up for any implementing classes to handle this. + * @param test the details of the test and failure that have triggered this report. + * @param exception the AssumptionViolatedException thrown from the current assumption failure. + */ + void testAssumptionFailure(Test test, Throwable exception); +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/IgnoredTestResult.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/IgnoredTestResult.java new file mode 100644 index 00000000..c3bb18da --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/IgnoredTestResult.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.Test; +import junit.framework.TestListener; +import junit.framework.TestResult; + +/** + * Records ignored and skipped tests reported as part of the execution of + * JUnit 4 tests. + * + */ +public class IgnoredTestResult extends TestResult { + + + private List<IgnoredTestListener> listeners = new ArrayList<IgnoredTestListener>(); + private List<TestIgnored> ignored = new ArrayList<TestIgnored>(); + private List<TestIgnored> skipped = new ArrayList<TestIgnored>(); + + public IgnoredTestResult() { + super(); + } + + + public synchronized void addListener(TestListener listener) { + if (listener instanceof IgnoredTestListener) { + listeners.add((IgnoredTestListener)listener); + } + super.addListener(listener); + } + + public synchronized void removeListener(TestListener listener) { + if (listener instanceof IgnoredTestListener) { + listeners.remove(listener); + } + super.removeListener(listener); + } + + /** + * Record a test as having been ignored, normally by the @Ignore annotation. + * @param test the test that was ignored. + * @throws Exception is the listener thrown an exception on handling the notification. + */ + public synchronized void testIgnored(Test test) throws Exception { + ignored.add(new TestIgnored(test)); + for (IgnoredTestListener listener : listeners) { + listener.testIgnored(test); + } + } + + /** + * Report how many tests were ignored. + * @return the number of tests reported as ignored during the current execution. + */ + public long ignoredCount() { + return ignored.size(); + } + + /** + * Records a test as having an assumption failure so JUnit will no longer be executing it. + * Under normal circumstances this would be counted as a skipped test. + * @param test the test to record + * @param cause the details of the test and assumption failure. + */ + public void testAssumptionFailure(Test test, Throwable cause) { + skipped.add(new TestIgnored(test)); + for (IgnoredTestListener listener : listeners) { + listener.testAssumptionFailure(test, cause); + } + } + + /** + * Report how many tests has assumption failures. + * @return the number of tests that reported assumption failures during the current execution. + */ + public long skippedCount() { + return skipped.size(); + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.java new file mode 100644 index 00000000..f03a409b --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.java @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.util.Iterator; +import java.util.List; + +import junit.framework.Test; +import junit.framework.TestResult; + +import org.junit.runner.Description; +import org.junit.runner.Request; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; + +/** + * Adapter between JUnit 3.8.x API and JUnit 4.x API for execution of tests + * and listening of events (test start, test finish, test failure, test skipped). + * The constructor is passed a JUnit 4 test class and a list of name of methods + * in it that should be executed. Method {@link #run run(TestResult)} executes + * the given JUnit-4-style test methods and notifies the given {@code TestResult} + * object using its old (JUnit 3.8.x style) API. + * + * @author Marian Petras + */ +public class JUnit4TestMethodAdapter implements Test { + + private final Class testClass; + private final String[] methodNames; + private final Runner runner; + private final CustomJUnit4TestAdapterCache cache; + + /** + * Creates a new adapter for the given class and a method within the class. + * + * @param testClass test class containing the method to be executed + * @param methodNames names of the test methods that are to be executed + * @exception java.lang.IllegalArgumentException + * if any of the arguments is {@code null} + * or if any of the given method names is {@code null} or empty + */ + public JUnit4TestMethodAdapter(final Class testClass, + final String[] methodNames) { + if (testClass == null) { + throw new IllegalArgumentException("testClass is <null>"); + } + if (methodNames == null) { + throw new IllegalArgumentException("methodNames is <null>"); + } + for (int i = 0; i < methodNames.length; i++) { + if (methodNames[i] == null) { + throw new IllegalArgumentException("method name #" + i + " is <null>"); + } + if (methodNames[i].length() == 0) { + throw new IllegalArgumentException("method name #" + i + " is empty"); + } + } + this.testClass = testClass; + this.methodNames = methodNames.clone(); + this.cache = CustomJUnit4TestAdapterCache.getInstance(); + + // Warning: If 'testClass' is an old-style (pre-JUnit-4) class, + // then all its test methods will be executed by the returned runner! + Request request; + if (methodNames.length == 1) { + request = Request.method(testClass, methodNames[0]); + } else { + request = Request.aClass(testClass).filterWith( + new MultipleMethodsFilter(testClass, methodNames)); + } + runner = request.getRunner(); + } + + public int countTestCases() { + return runner.testCount(); + } + + public Description getDescription() { + return runner.getDescription(); + } + + public List/*<Test>*/ getTests() { + return cache.asTestList(getDescription()); + } + + public Class getTestClass() { + return testClass; + } + + public void run(final TestResult result) { + runner.run(cache.getNotifier(result)); + } + + @Override + public String toString() { + String testClassName = testClass.getName(); + StringBuilder buf = new StringBuilder(testClassName.length() + + 12 * methodNames.length) + .append(':'); + if (methodNames.length != 0) { + buf.append(methodNames[0]); + for (int i = 1; i < methodNames.length; i++) { + buf.append(',') + .append(methodNames[i]); + } + } + return buf.toString(); + } + + private static final class MultipleMethodsFilter extends Filter { + + private final Description methodsListDescription; + private final Class testClass; + private final String[] methodNames; + + private MultipleMethodsFilter(Class testClass, String[] methodNames) { + if (testClass == null) { + throw new IllegalArgumentException("testClass is <null>"); + } + if (methodNames == null) { + throw new IllegalArgumentException("methodNames is <null>"); + } + methodsListDescription = Description.createSuiteDescription(testClass); + for (int i = 0; i < methodNames.length; i++) { + methodsListDescription.addChild( + Description.createTestDescription(testClass, methodNames[i])); + } + this.testClass = testClass; + this.methodNames = methodNames; + } + + @Override + public boolean shouldRun(Description description) { + if (methodNames.length == 0) { + return false; + } + if (description.isTest()) { + Iterator/*<Description>*/ it = methodsListDescription.getChildren().iterator(); + while (it.hasNext()) { + Description methodDescription = (Description) it.next(); + if (methodDescription.equals(description)) { + return true; + } + } + } else { + Iterator/*<Description>*/ it = description.getChildren().iterator(); + while (it.hasNext()) { + Description each = (Description) it.next(); + if (shouldRun(each)) { + return true; + } + } + } + return false; + } + + @Override + public String describe() { + StringBuilder buf = new StringBuilder(40); + if (methodNames.length == 0) { + buf.append("No methods"); + } else { + buf.append(methodNames.length == 1 ? "Method" : "Methods"); + buf.append(' '); + buf.append(methodNames[0]); + for (int i = 1; i < methodNames.length; i++) { + buf.append(',').append(methodNames[i]); + } + } + buf.append('(').append(testClass.getName()).append(')'); + return buf.toString(); + } + + } + + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java new file mode 100644 index 00000000..2119fc94 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.OutputStream; + +import junit.framework.TestListener; + +import org.apache.tools.ant.BuildException; + +/** + * This Interface describes classes that format the results of a JUnit + * testrun. + * + */ +public interface JUnitResultFormatter + extends TestListener, JUnitTaskMirror.JUnitResultFormatterMirror { + /** + * The whole testsuite started. + * @param suite the suite. + * @throws BuildException on error. + */ + void startTestSuite(JUnitTest suite) throws BuildException; + + /** + * The whole testsuite ended. + * @param suite the suite. + * @throws BuildException on error. + */ + void endTestSuite(JUnitTest suite) throws BuildException; + + /** + * Sets the stream the formatter is supposed to write its results to. + * @param out the output stream to use. + */ + void setOutput(OutputStream out); + + /** + * This is what the test has written to System.out + * @param out the string to write. + */ + void setSystemOutput(String out); + + /** + * This is what the test has written to System.err + * @param err the string to write. + */ + void setSystemError(String err); +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java new file mode 100644 index 00000000..459bd3d5 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java @@ -0,0 +1,2283 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.reflect.Constructor; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Vector; + +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.PumpStreamHandler; +import org.apache.tools.ant.types.Assertions; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Permissions; +import org.apache.tools.ant.types.PropertySet; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.LoaderUtils; +import org.apache.tools.ant.util.SplitClassLoader; + +/** + * Runs JUnit tests. + * + * <p> JUnit is a framework to create unit tests. It has been initially + * created by Erich Gamma and Kent Beck. JUnit can be found at <a + * href="http://www.junit.org">http://www.junit.org</a>. + * + * <p> <code>JUnitTask</code> can run a single specific + * <code>JUnitTest</code> using the <code>test</code> element.</p> + * For example, the following target <code><pre> + * <target name="test-int-chars" depends="jar-test"> + * <echo message="testing international characters"/> + * <junit printsummary="no" haltonfailure="yes" fork="false"> + * <classpath refid="classpath"/> + * <formatter type="plain" usefile="false" /> + * <test name="org.apache.ecs.InternationalCharTest" /> + * </junit> + * </target> + * </pre></code> + * <p>runs a single junit test + * (<code>org.apache.ecs.InternationalCharTest</code>) in the current + * VM using the path with id <code>classpath</code> as classpath and + * presents the results formatted using the standard + * <code>plain</code> formatter on the command line.</p> + * + * <p> This task can also run batches of tests. The + * <code>batchtest</code> element creates a <code>BatchTest</code> + * based on a fileset. This allows, for example, all classes found in + * directory to be run as testcases.</p> + * + * <p>For example,</p><code><pre> + * <target name="run-tests" depends="dump-info,compile-tests" if="junit.present"> + * <junit printsummary="no" haltonfailure="yes" fork="${junit.fork}"> + * <jvmarg value="-classic"/> + * <classpath refid="tests-classpath"/> + * <sysproperty key="build.tests" value="${build.tests}"/> + * <formatter type="brief" usefile="false" /> + * <batchtest> + * <fileset dir="${tests.dir}"> + * <include name="**/*Test*" /> + * </fileset> + * </batchtest> + * </junit> + * </target> + * </pre></code> + * <p>this target finds any classes with a <code>test</code> directory + * anywhere in their path (under the top <code>${tests.dir}</code>, of + * course) and creates <code>JUnitTest</code>'s for each one.</p> + * + * <p> Of course, <code><junit></code> and + * <code><batch></code> elements can be combined for more + * complex tests. For an example, see the ant <code>build.xml</code> + * target <code>run-tests</code> (the second example is an edited + * version).</p> + * + * <p> To spawn a new Java VM to prevent interferences between + * different testcases, you need to enable <code>fork</code>. A + * number of attributes and elements allow you to set up how this JVM + * runs. + * + * + * @since Ant 1.2 + * + * @see JUnitTest + * @see BatchTest + */ +public class JUnitTask extends Task { + + private static final String LINE_SEP + = System.getProperty("line.separator"); + private static final String CLASSPATH = "CLASSPATH"; + private CommandlineJava commandline; + private final Vector<JUnitTest> tests = new Vector<JUnitTest>(); + private final Vector<BatchTest> batchTests = new Vector<BatchTest>(); + private final Vector<FormatterElement> formatters = new Vector<FormatterElement>(); + private File dir = null; + + private Integer timeout = null; + private boolean summary = false; + private boolean reloading = true; + private String summaryValue = ""; + private JUnitTaskMirror.JUnitTestRunnerMirror runner = null; + + private boolean newEnvironment = false; + private final Environment env = new Environment(); + + private boolean includeAntRuntime = true; + private Path antRuntimeClasses = null; + + // Do we send output to System.out/.err in addition to the formatters? + private boolean showOutput = false; + + // Do we send output to the formatters ? + private boolean outputToFormatters = true; + + private boolean logFailedTests = true; + + private File tmpDir; + private AntClassLoader classLoader = null; + private Permissions perm = null; + private ForkMode forkMode = new ForkMode("perTest"); + + private boolean splitJUnit = false; + private boolean enableTestListenerEvents = false; + private JUnitTaskMirror delegate; + private ClassLoader mirrorLoader; + + /** A boolean on whether to get the forked path for ant classes */ + private boolean forkedPathChecked = false; + + /* set when a test fails/errs with haltonfailure/haltonerror and >1 thread to stop other threads */ + private volatile BuildException caughtBuildException = null; + + // Attributes for basetest + private boolean haltOnError = false; + private boolean haltOnFail = false; + private boolean filterTrace = true; + private boolean fork = false; + private int threads = 1; + private String failureProperty; + private String errorProperty; + + private static final int STRING_BUFFER_SIZE = 128; + /** + * @since Ant 1.7 + */ + public static final String TESTLISTENER_PREFIX = + "junit.framework.TestListener: "; + + /** + * Name of magic property that enables test listener events. + */ + public static final String ENABLE_TESTLISTENER_EVENTS = + "ant.junit.enabletestlistenerevents"; + + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** + * If true, force ant to re-classload all classes for each JUnit TestCase + * + * @param value force class reloading for each test case + */ + public void setReloading(final boolean value) { + reloading = value; + } + + /** + * If true, smartly filter the stack frames of + * JUnit errors and failures before reporting them. + * + * <p>This property is applied on all BatchTest (batchtest) and + * JUnitTest (test) however it can possibly be overridden by their + * own properties.</p> + * @param value <tt>false</tt> if it should not filter, otherwise + * <tt>true<tt> + * + * @since Ant 1.5 + */ + public void setFiltertrace(final boolean value) { + this.filterTrace = value; + } + + /** + * If true, stop the build process when there is an error in a test. + * This property is applied on all BatchTest (batchtest) and JUnitTest + * (test) however it can possibly be overridden by their own + * properties. + * @param value <tt>true</tt> if it should halt, otherwise + * <tt>false</tt> + * + * @since Ant 1.2 + */ + public void setHaltonerror(final boolean value) { + this.haltOnError = value; + } + + /** + * Property to set to "true" if there is a error in a test. + * + * <p>This property is applied on all BatchTest (batchtest) and + * JUnitTest (test), however, it can possibly be overridden by + * their own properties.</p> + * @param propertyName the name of the property to set in the + * event of an error. + * + * @since Ant 1.4 + */ + public void setErrorProperty(final String propertyName) { + this.errorProperty = propertyName; + } + + /** + * If true, stop the build process if a test fails + * (errors are considered failures as well). + * This property is applied on all BatchTest (batchtest) and + * JUnitTest (test) however it can possibly be overridden by their + * own properties. + * @param value <tt>true</tt> if it should halt, otherwise + * <tt>false</tt> + * + * @since Ant 1.2 + */ + public void setHaltonfailure(final boolean value) { + this.haltOnFail = value; + } + + /** + * Property to set to "true" if there is a failure in a test. + * + * <p>This property is applied on all BatchTest (batchtest) and + * JUnitTest (test), however, it can possibly be overridden by + * their own properties.</p> + * @param propertyName the name of the property to set in the + * event of an failure. + * + * @since Ant 1.4 + */ + public void setFailureProperty(final String propertyName) { + this.failureProperty = propertyName; + } + + /** + * If true, JVM should be forked for each test. + * + * <p>It avoids interference between testcases and possibly avoids + * hanging the build. this property is applied on all BatchTest + * (batchtest) and JUnitTest (test) however it can possibly be + * overridden by their own properties.</p> + * @param value <tt>true</tt> if a JVM should be forked, otherwise + * <tt>false</tt> + * @see #setTimeout + * + * @since Ant 1.2 + */ + public void setFork(final boolean value) { + this.fork = value; + } + + /** + * Set the behavior when {@link #setFork fork} fork has been enabled. + * + * <p>Possible values are "once", "perTest" and "perBatch". If + * set to "once", only a single Java VM will be forked for all + * tests, with "perTest" (the default) each test will run in a + * fresh Java VM and "perBatch" will run all tests from the same + * <batchtest> in the same Java VM.</p> + * + * <p>This attribute will be ignored if tests run in the same VM + * as Ant.</p> + * + * <p>Only tests with the same configuration of haltonerror, + * haltonfailure, errorproperty, failureproperty and filtertrace + * can share a forked Java VM, so even if you set the value to + * "once", Ant may need to fork multiple VMs.</p> + * @param mode the mode to use. + * @since Ant 1.6.2 + */ + public void setForkMode(final ForkMode mode) { + this.forkMode = mode; + } + + /** + * Set the number of test threads to be used for parallel test + * execution. The default is 1, which is the same behavior as + * before parallel test execution was possible. + * + * <p>This attribute will be ignored if tests run in the same VM + * as Ant.</p> + * + * @since Ant 1.9.4 + */ + public void setThreads(final int threads) { + if (threads >= 0) { + this.threads = threads; + } + } + + /** + * If true, print one-line statistics for each test, or "withOutAndErr" + * to also show standard output and error. + * + * Can take the values on, off, and withOutAndErr. + * @param value <tt>true</tt> to print a summary, + * <tt>withOutAndErr</tt> to include the test's output as + * well, <tt>false</tt> otherwise. + * @see SummaryJUnitResultFormatter + * + * @since Ant 1.2 + */ + public void setPrintsummary(final SummaryAttribute value) { + summaryValue = value.getValue(); + summary = value.asBoolean(); + } + + /** + * Print summary enumeration values. + */ + public static class SummaryAttribute extends EnumeratedAttribute { + /** + * list the possible values + * @return array of allowed values + */ + @Override + public String[] getValues() { + return new String[] {"true", "yes", "false", "no", + "on", "off", "withOutAndErr"}; + } + + /** + * gives the boolean equivalent of the authorized values + * @return boolean equivalent of the value + */ + public boolean asBoolean() { + final String v = getValue(); + return "true".equals(v) + || "on".equals(v) + || "yes".equals(v) + || "withOutAndErr".equals(v); + } + } + + /** + * Set the timeout value (in milliseconds). + * + * <p>If the test is running for more than this value, the test + * will be canceled. (works only when in 'fork' mode).</p> + * @param value the maximum time (in milliseconds) allowed before + * declaring the test as 'timed-out' + * @see #setFork(boolean) + * + * @since Ant 1.2 + */ + public void setTimeout(final Integer value) { + timeout = value; + } + + /** + * Set the maximum memory to be used by all forked JVMs. + * @param max the value as defined by <tt>-mx</tt> or <tt>-Xmx</tt> + * in the java command line options. + * + * @since Ant 1.2 + */ + public void setMaxmemory(final String max) { + getCommandline().setMaxmemory(max); + } + + /** + * The command used to invoke the Java Virtual Machine, + * default is 'java'. The command is resolved by + * java.lang.Runtime.exec(). Ignored if fork is disabled. + * + * @param value the new VM to use instead of <tt>java</tt> + * @see #setFork(boolean) + * + * @since Ant 1.2 + */ + public void setJvm(final String value) { + getCommandline().setVm(value); + } + + /** + * Adds a JVM argument; ignored if not forking. + * + * @return create a new JVM argument so that any argument can be + * passed to the JVM. + * @see #setFork(boolean) + * + * @since Ant 1.2 + */ + public Commandline.Argument createJvmarg() { + return getCommandline().createVmArgument(); + } + + /** + * The directory to invoke the VM in. Ignored if no JVM is forked. + * @param dir the directory to invoke the JVM from. + * @see #setFork(boolean) + * + * @since Ant 1.2 + */ + public void setDir(final File dir) { + this.dir = dir; + } + + /** + * Adds a system property that tests can access. + * This might be useful to transfer Ant properties to the + * testcases when JVM forking is not enabled. + * + * @since Ant 1.3 + * @deprecated since ant 1.6 + * @param sysp environment variable to add + */ + @Deprecated + public void addSysproperty(final Environment.Variable sysp) { + + getCommandline().addSysproperty(sysp); + } + + /** + * Adds a system property that tests can access. + * This might be useful to transfer Ant properties to the + * testcases when JVM forking is not enabled. + * @param sysp new environment variable to add + * @since Ant 1.6 + */ + public void addConfiguredSysproperty(final Environment.Variable sysp) { + // get a build exception if there is a missing key or value + // see bugzilla report 21684 + final String testString = sysp.getContent(); + getProject().log("sysproperty added : " + testString, Project.MSG_DEBUG); + getCommandline().addSysproperty(sysp); + } + + /** + * Adds a set of properties that will be used as system properties + * that tests can access. + * + * This might be useful to transfer Ant properties to the + * testcases when JVM forking is not enabled. + * + * @param sysp set of properties to be added + * @since Ant 1.6 + */ + public void addSyspropertyset(final PropertySet sysp) { + getCommandline().addSyspropertyset(sysp); + } + + /** + * Adds path to classpath used for tests. + * + * @return reference to the classpath in the embedded java command line + * @since Ant 1.2 + */ + public Path createClasspath() { + return getCommandline().createClasspath(getProject()).createPath(); + } + + /** + * Adds a path to the bootclasspath. + * @return reference to the bootclasspath in the embedded java command line + * @since Ant 1.6 + */ + public Path createBootclasspath() { + return getCommandline().createBootclasspath(getProject()).createPath(); + } + + /** + * Adds an environment variable; used when forking. + * + * <p>Will be ignored if we are not forking a new VM.</p> + * @param var environment variable to be added + * @since Ant 1.5 + */ + public void addEnv(final Environment.Variable var) { + env.addVariable(var); + } + + /** + * If true, use a new environment when forked. + * + * <p>Will be ignored if we are not forking a new VM.</p> + * + * @param newenv boolean indicating if setting a new environment is wished + * @since Ant 1.5 + */ + public void setNewenvironment(final boolean newenv) { + newEnvironment = newenv; + } + + /** + * Preset the attributes of the test + * before configuration in the build + * script. + * This allows attributes in the <junit> task + * be be defaults for the tests, but allows + * individual tests to override the defaults. + */ + private void preConfigure(final BaseTest test) { + test.setFiltertrace(filterTrace); + test.setHaltonerror(haltOnError); + if (errorProperty != null) { + test.setErrorProperty(errorProperty); + } + test.setHaltonfailure(haltOnFail); + if (failureProperty != null) { + test.setFailureProperty(failureProperty); + } + test.setFork(fork); + } + + /** + * Add a new single testcase. + * @param test a new single testcase + * @see JUnitTest + * + * @since Ant 1.2 + */ + public void addTest(final JUnitTest test) { + tests.addElement(test); + preConfigure(test); + } + + /** + * Adds a set of tests based on pattern matching. + * + * @return a new instance of a batch test. + * @see BatchTest + * + * @since Ant 1.2 + */ + public BatchTest createBatchTest() { + final BatchTest test = new BatchTest(getProject()); + batchTests.addElement(test); + preConfigure(test); + return test; + } + + /** + * Add a new formatter to all tests of this task. + * + * @param fe formatter element + * @since Ant 1.2 + */ + public void addFormatter(final FormatterElement fe) { + formatters.addElement(fe); + } + + /** + * If true, include ant.jar, optional.jar and junit.jar in the forked VM. + * + * @param b include ant run time yes or no + * @since Ant 1.5 + */ + public void setIncludeantruntime(final boolean b) { + includeAntRuntime = b; + } + + /** + * If true, send any output generated by tests to Ant's logging system + * as well as to the formatters. + * By default only the formatters receive the output. + * + * <p>Output will always be passed to the formatters and not by + * shown by default. This option should for example be set for + * tests that are interactive and prompt the user to do + * something.</p> + * + * @param showOutput if true, send output to Ant's logging system too + * @since Ant 1.5 + */ + public void setShowOutput(final boolean showOutput) { + this.showOutput = showOutput; + } + + /** + * If true, send any output generated by tests to the formatters. + * + * @param outputToFormatters if true, send output to formatters (Default + * is true). + * @since Ant 1.7.0 + */ + public void setOutputToFormatters(final boolean outputToFormatters) { + this.outputToFormatters = outputToFormatters; + } + + /** + * If true, write a single "FAILED" line for failed tests to Ant's + * log system. + * + * @since Ant 1.8.0 + */ + public void setLogFailedTests(final boolean logFailedTests) { + this.logFailedTests = logFailedTests; + } + + /** + * Assertions to enable in this program (if fork=true) + * @since Ant 1.6 + * @param asserts assertion set + */ + public void addAssertions(final Assertions asserts) { + if (getCommandline().getAssertions() != null) { + throw new BuildException("Only one assertion declaration is allowed"); + } + getCommandline().setAssertions(asserts); + } + + /** + * Sets the permissions for the application run inside the same JVM. + * @since Ant 1.6 + * @return . + */ + public Permissions createPermissions() { + if (perm == null) { + perm = new Permissions(); + } + return perm; + } + + /** + * If set, system properties will be copied to the cloned VM - as + * well as the bootclasspath unless you have explicitly specified + * a bootclasspath. + * + * <p>Doesn't have any effect unless fork is true.</p> + * @param cloneVm a <code>boolean</code> value. + * @since Ant 1.7 + */ + public void setCloneVm(final boolean cloneVm) { + getCommandline().setCloneVm(cloneVm); + } + + /** + * Creates a new JUnitRunner and enables fork of a new Java VM. + * + * @throws Exception under ??? circumstances + * @since Ant 1.2 + */ + public JUnitTask() throws Exception { + } + + /** + * Where Ant should place temporary files. + * + * @param tmpDir location where temporary files should go to + * @since Ant 1.6 + */ + public void setTempdir(final File tmpDir) { + if (tmpDir != null) { + if (!tmpDir.exists() || !tmpDir.isDirectory()) { + throw new BuildException(tmpDir.toString() + + " is not a valid temp directory"); + } + } + this.tmpDir = tmpDir; + } + + /** + * Whether test listener events shall be generated. + * + * <p>Defaults to false.</p> + * + * <p>This value will be overridden by the magic property + * ant.junit.enabletestlistenerevents if it has been set.</p> + * + * @since Ant 1.8.2 + */ + public void setEnableTestListenerEvents(final boolean b) { + enableTestListenerEvents = b; + } + + /** + * Whether test listener events shall be generated. + * @since Ant 1.8.2 + */ + public boolean getEnableTestListenerEvents() { + final String e = getProject().getProperty(ENABLE_TESTLISTENER_EVENTS); + if (e != null) { + return Project.toBoolean(e); + } + return enableTestListenerEvents; + } + + /** + * Adds the jars or directories containing Ant, this task and + * JUnit to the classpath - this should make the forked JVM work + * without having to specify them directly. + * + * @since Ant 1.4 + */ + @Override + public void init() { + antRuntimeClasses = new Path(getProject()); + splitJUnit = !addClasspathResource("/junit/framework/TestCase.class"); + addClasspathEntry("/org/apache/tools/ant/launch/AntMain.class"); + addClasspathEntry("/org/apache/tools/ant/Task.class"); + addClasspathEntry("/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.class"); + addClasspathEntry("/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.class"); + } + + private static JUnitTaskMirror createMirror(final JUnitTask task, final ClassLoader loader) { + try { + loader.loadClass("junit.framework.Test"); // sanity check + } catch (final ClassNotFoundException e) { + throw new BuildException( + "The <classpath> for <junit> must include junit.jar " + + "if not in Ant's own classpath", + e, task.getLocation()); + } + try { + final Class c = loader.loadClass(JUnitTaskMirror.class.getName() + "Impl"); + if (c.getClassLoader() != loader) { + throw new BuildException("Overdelegating loader", task.getLocation()); + } + final Constructor cons = c.getConstructor(new Class[] {JUnitTask.class}); + return (JUnitTaskMirror) cons.newInstance(new Object[] {task}); + } catch (final Exception e) { + throw new BuildException(e, task.getLocation()); + } + } + + /** + * Sets up the delegate that will actually run the tests. + * + * <p>Will be invoked implicitly once the delegate is needed.</p> + * + * @since Ant 1.7.1 + */ + protected void setupJUnitDelegate() { + final ClassLoader myLoader = JUnitTask.class.getClassLoader(); + if (splitJUnit) { + final Path path = new Path(getProject()); + path.add(antRuntimeClasses); + final Path extra = getCommandline().getClasspath(); + if (extra != null) { + path.add(extra); + } + mirrorLoader = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return new SplitClassLoader(myLoader, path, getProject(), + new String[] { + "BriefJUnitResultFormatter", + "JUnit4TestMethodAdapter", + "JUnitResultFormatter", + "JUnitTaskMirrorImpl", + "JUnitTestRunner", + "JUnitVersionHelper", + "OutErrSummaryJUnitResultFormatter", + "PlainJUnitResultFormatter", + "SummaryJUnitResultFormatter", + "TearDownOnVmCrash", + "XMLJUnitResultFormatter", + "IgnoredTestListener", + "IgnoredTestResult", + "CustomJUnit4TestAdapterCache", + "TestListenerWrapper" + }); + } + }); + } else { + mirrorLoader = myLoader; + } + delegate = createMirror(this, mirrorLoader); + } + + /** + * Runs the testcase. + * + * @throws BuildException in case of test failures or errors + * @since Ant 1.2 + */ + @Override + public void execute() throws BuildException { + checkMethodLists(); + + setupJUnitDelegate(); + + final List<List> testLists = new ArrayList<List>(); + /* parallel test execution is only supported for multi-process execution */ + final int threads = ((!fork) || (forkMode.getValue().equals(ForkMode.ONCE)) + ? 1 + : this.threads); + + final boolean forkPerTest = forkMode.getValue().equals(ForkMode.PER_TEST); + if (forkPerTest || forkMode.getValue().equals(ForkMode.ONCE)) { + testLists.addAll(executeOrQueue(getIndividualTests(), + forkPerTest)); + } else { /* forkMode.getValue().equals(ForkMode.PER_BATCH) */ + final int count = batchTests.size(); + for (int i = 0; i < count; i++) { + final BatchTest batchtest = batchTests.elementAt(i); + testLists.addAll(executeOrQueue(batchtest.elements(), false)); + } + testLists.addAll(executeOrQueue(tests.elements(), forkPerTest)); + } + + try { + /* prior to parallel the code in 'oneJunitThread' used to be here. */ + runTestsInThreads(testLists, threads); + } finally { + cleanup(); + } + } + + /* + * When the list of tests is established, an array of threads is created to pick the + * tests off the list one at a time and execute them until the list is empty. Tests are + * not assigned to threads until the thread is available. + * + * This class is the runnable thread subroutine that takes care of passing the shared + * list iterator and the handle back to the main class to the test execution subroutine + * code 'runTestsInThreads'. One object is created for each thread and each one gets + * a unique thread id that can be useful for tracing test starts and stops. + * + * Because the threads are picking tests off the same list, it is the list *iterator* + * that must be shared, not the list itself - and the iterator must have a thread-safe + * ability to pop the list - hence the synchronized 'getNextTest'. + */ + private class JunitTestThread implements Runnable { + + JunitTestThread(final JUnitTask master, final Iterator<List> iterator, final int id) { + this.masterTask = master; + this.iterator = iterator; + this.id = id; + } + + public void run() { + try { + masterTask.oneJunitThread(iterator, id); + } catch (final BuildException b) { + /* saved to rethrow in main thread to be like single-threaded case */ + caughtBuildException = b; + } + } + + private final JUnitTask masterTask; + private final Iterator<List> iterator; + private final int id; + } + + /* + * Because the threads are picking tests off the same list, it is the list *iterator* + * that must be shared, not the list itself - and the iterator must have a thread-safe + * ability to pop the list - hence the synchronized 'getNextTest'. We can't have two + * threads get the same test, or two threads simultaneously pop the list so that a test + * gets skipped! + */ + private List getNextTest(final Iterator<List> iter) { + synchronized(iter) { + if (iter.hasNext()) { + return iter.next(); + } + return null; + } + } + + /* + * This code loops keeps executing the next test or test bunch (depending on fork mode) + * on the list of test cases until none are left. Basically this body of code used to + * be in the execute routine above; now, several copies (one for each test thread) execute + * simultaneously. The while loop was modified to call the new thread-safe atomic list + * popping subroutine and the logging messages were added. + * + * If one thread aborts due to a BuildException (haltOnError, haltOnFailure, or any other + * fatal reason, no new tests/batches will be started but the running threads will be + * permitted to complete. Additional tests may start in already-running batch-test threads. + */ + private void oneJunitThread(final Iterator<List> iter, final int threadId) { + + List l; + log("Starting test thread " + threadId, Project.MSG_VERBOSE); + while ((caughtBuildException == null) && ((l = getNextTest(iter)) != null)) { + log("Running test " + l.get(0).toString() + "(" + l.size() + ") in thread " + threadId, Project.MSG_VERBOSE); + if (l.size() == 1) { + execute((JUnitTest) l.get(0), threadId); + } else { + execute(l, threadId); + } + } + log("Ending test thread " + threadId, Project.MSG_VERBOSE); + } + + + private void runTestsInThreads(final List<List> testList, final int numThreads) { + + Iterator<List> iter = testList.iterator(); + + if (numThreads == 1) { + /* with just one thread just run the test - don't create any threads */ + oneJunitThread(iter, 0); + } else { + final Thread[] threads = new Thread[numThreads]; + int i; + boolean exceptionOccurred; + + /* Need to split apart tests, which are still grouped in batches */ + /* is there a simpler Java mechanism to do this? */ + /* I assume we don't want to do this with "per batch" forking. */ + List<List> newlist = new ArrayList<List>(); + if (forkMode.getValue().equals(ForkMode.PER_TEST)) { + final Iterator<List> i1 = testList.iterator(); + while (i1.hasNext()) { + final List l = i1.next(); + if (l.size() == 1) { + newlist.add(l); + } else { + final Iterator i2 = l.iterator(); + while (i2.hasNext()) { + final List tmpSingleton = new ArrayList(); + tmpSingleton.add(i2.next()); + newlist.add(tmpSingleton); + } + } + } + } else { + newlist = testList; + } + iter = newlist.iterator(); + + /* create 1 thread using the passthrough class, and let each thread start */ + for (i = 0; i < numThreads; i++) { + threads[i] = new Thread(new JunitTestThread(this, iter, i+1)); + threads[i].start(); + } + + /* wait for all of the threads to complete. Not sure if the exception can actually occur in this use case. */ + do { + exceptionOccurred = false; + + try { + for (i = 0; i < numThreads; i++) { + threads[i].join(); + } + } catch (final InterruptedException e) { + exceptionOccurred = true; + } + } while (exceptionOccurred); + + /* an exception occurred in one of the threads - usually a haltOnError/Failure. + throw the exception again so it behaves like the single-thread case */ + if (caughtBuildException != null) { + throw new BuildException(caughtBuildException); + } + + /* all threads are completed - that's all there is to do. */ + /* control will flow back to the test cleanup call and then execute is done. */ + } + } + + /** + * Run the tests. + * @param arg one JUnitTest + * @param thread Identifies which thread is test running in (0 for single-threaded runs) + * @throws BuildException in case of test failures or errors + */ + protected void execute(final JUnitTest arg, final int thread) throws BuildException { + validateTestName(arg.getName()); + + final JUnitTest test = (JUnitTest) arg.clone(); + test.setThread(thread); + + // set the default values if not specified + //@todo should be moved to the test class instead. + if (test.getTodir() == null) { + test.setTodir(getProject().resolveFile(".")); + } + + if (test.getOutfile() == null) { + test.setOutfile("TEST-" + test.getName()); + } + + // execute the test and get the return code + TestResultHolder result = null; + if (!test.getFork()) { + result = executeInVM(test); + } else { + final ExecuteWatchdog watchdog = createWatchdog(); + result = executeAsForked(test, watchdog, null); + // null watchdog means no timeout, you'd better not check with null + } + actOnTestResult(result, test, "Test " + test.getName()); + } + + /** + * Run the tests. + * @param arg one JUnitTest + * @throws BuildException in case of test failures or errors + */ + protected void execute(final JUnitTest arg) throws BuildException { + execute(arg, 0); + } + + /** + * Throws a <code>BuildException</code> if the given test name is invalid. + * Validity is defined as not <code>null</code>, not empty, and not the + * string "null". + * @param testName the test name to be validated + * @throws BuildException if <code>testName</code> is not a valid test name + */ + private void validateTestName(final String testName) throws BuildException { + if (testName == null || testName.length() == 0 + || testName.equals("null")) { + throw new BuildException("test name must be specified"); + } + } + + /** + * Execute a list of tests in a single forked Java VM. + * @param testList the list of tests to execute. + * @param thread Identifies which thread is test running in (0 for single-threaded runs) + * @throws BuildException on error. + */ + protected void execute(final List testList, final int thread) throws BuildException { + JUnitTest test = null; + // Create a temporary file to pass the test cases to run to + // the runner (one test case per line) + final File casesFile = createTempPropertiesFile("junittestcases"); + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new FileWriter(casesFile)); + + log("Creating casesfile '" + casesFile.getAbsolutePath() + + "' with content: ", Project.MSG_VERBOSE); + final PrintStream logWriter = + new PrintStream(new LogOutputStream(this, Project.MSG_VERBOSE)); + + final Iterator iter = testList.iterator(); + while (iter.hasNext()) { + test = (JUnitTest) iter.next(); + test.setThread(thread); + printDual(writer, logWriter, test.getName()); + if (test.getMethods() != null) { + printDual(writer, logWriter, ":" + test.getMethodsString().replace(',', '+')); + } + if (test.getTodir() == null) { + printDual(writer, logWriter, + "," + getProject().resolveFile(".")); + } else { + printDual(writer, logWriter, "," + test.getTodir()); + } + + if (test.getOutfile() == null) { + printlnDual(writer, logWriter, + "," + "TEST-" + test.getName()); + } else { + printlnDual(writer, logWriter, "," + test.getOutfile()); + } + } + writer.flush(); + writer.close(); + writer = null; + + // execute the test and get the return code + final ExecuteWatchdog watchdog = createWatchdog(); + final TestResultHolder result = + executeAsForked(test, watchdog, casesFile); + actOnTestResult(result, test, "Tests"); + } catch (final IOException e) { + log(e.toString(), Project.MSG_ERR); + throw new BuildException(e); + } finally { + FileUtils.close(writer); + + try { + FILE_UTILS.tryHardToDelete(casesFile); + } catch (final Exception e) { + log(e.toString(), Project.MSG_ERR); + } + } + } + + /** + * Execute a list of tests in a single forked Java VM. + * @param testList the list of tests to execute. + * @throws BuildException on error. + */ + protected void execute(final List testList) throws BuildException { + execute(testList, 0); + } + + /** + * Execute a testcase by forking a new JVM. The command will block + * until it finishes. To know if the process was destroyed or not + * or whether the forked Java VM exited abnormally, use the + * attributes of the returned holder object. + * @param test the testcase to execute. + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be <tt>null</tt>, in this case + * the test could probably hang forever. + * @param casesFile list of test cases to execute. Can be <tt>null</tt>, + * in this case only one test is executed. + * @return the test results from the JVM itself. + * @throws BuildException in case of error creating a temporary property file, + * or if the junit process can not be forked + */ + private TestResultHolder executeAsForked(JUnitTest test, + final ExecuteWatchdog watchdog, + final File casesFile) + throws BuildException { + + if (perm != null) { + log("Permissions ignored when running in forked mode!", + Project.MSG_WARN); + } + + CommandlineJava cmd; + try { + cmd = (CommandlineJava) (getCommandline().clone()); + } catch (final CloneNotSupportedException e) { + throw new BuildException("This shouldn't happen", e, getLocation()); + } + if (casesFile == null) { + cmd.createArgument().setValue(test.getName()); + if (test.getMethods() != null) { + cmd.createArgument().setValue(Constants.METHOD_NAMES + test.getMethodsString()); + } + } else { + log("Running multiple tests in the same VM", Project.MSG_VERBOSE); + cmd.createArgument().setValue(Constants.TESTSFILE + casesFile); + } + + cmd.createArgument().setValue(Constants.SKIP_NON_TESTS + String.valueOf(test.isSkipNonTests())); + cmd.createArgument().setValue(Constants.FILTERTRACE + test.getFiltertrace()); + cmd.createArgument().setValue(Constants.HALT_ON_ERROR + test.getHaltonerror()); + cmd.createArgument().setValue(Constants.HALT_ON_FAILURE + + test.getHaltonfailure()); + checkIncludeAntRuntime(cmd); + + checkIncludeSummary(cmd); + + cmd.createArgument().setValue(Constants.SHOWOUTPUT + + String.valueOf(showOutput)); + cmd.createArgument().setValue(Constants.OUTPUT_TO_FORMATTERS + + String.valueOf(outputToFormatters)); + cmd.createArgument().setValue(Constants.LOG_FAILED_TESTS + + String.valueOf(logFailedTests)); + cmd.createArgument().setValue(Constants.THREADID + + String.valueOf(test.getThread())); + + // #31885 + cmd.createArgument().setValue(Constants.LOGTESTLISTENEREVENTS + + String.valueOf(getEnableTestListenerEvents())); + + StringBuffer formatterArg = new StringBuffer(STRING_BUFFER_SIZE); + final FormatterElement[] feArray = mergeFormatters(test); + for (int i = 0; i < feArray.length; i++) { + final FormatterElement fe = feArray[i]; + if (fe.shouldUse(this)) { + formatterArg.append(Constants.FORMATTER); + formatterArg.append(fe.getClassname()); + final File outFile = getOutput(fe, test); + if (outFile != null) { + formatterArg.append(","); + formatterArg.append(outFile); + } + cmd.createArgument().setValue(formatterArg.toString()); + formatterArg = new StringBuffer(); + } + } + + final File vmWatcher = createTempPropertiesFile("junitvmwatcher"); + cmd.createArgument().setValue(Constants.CRASHFILE + + vmWatcher.getAbsolutePath()); + final File propsFile = createTempPropertiesFile("junit"); + cmd.createArgument().setValue(Constants.PROPSFILE + + propsFile.getAbsolutePath()); + final Hashtable p = getProject().getProperties(); + final Properties props = new Properties(); + for (final Enumeration e = p.keys(); e.hasMoreElements();) { + final Object key = e.nextElement(); + props.put(key, p.get(key)); + } + try { + final FileOutputStream outstream = new FileOutputStream(propsFile); + props.store(outstream, "Ant JUnitTask generated properties file"); + outstream.close(); + } catch (final java.io.IOException e) { + FILE_UTILS.tryHardToDelete(propsFile); + throw new BuildException("Error creating temporary properties " + + "file.", e, getLocation()); + } + + final Execute execute = new Execute( + new JUnitLogStreamHandler( + this, + Project.MSG_INFO, + Project.MSG_WARN), + watchdog); + execute.setCommandline(cmd.getCommandline()); + execute.setAntRun(getProject()); + if (dir != null) { + execute.setWorkingDirectory(dir); + } + + final String[] environment = env.getVariables(); + if (environment != null) { + for (int i = 0; i < environment.length; i++) { + log("Setting environment variable: " + environment[i], + Project.MSG_VERBOSE); + } + } + execute.setNewenvironment(newEnvironment); + execute.setEnvironment(environment); + + log(cmd.describeCommand(), Project.MSG_VERBOSE); + + checkForkedPath(cmd); + + final TestResultHolder result = new TestResultHolder(); + try { + result.exitCode = execute.execute(); + } catch (final IOException e) { + throw new BuildException("Process fork failed.", e, getLocation()); + } finally { + String vmCrashString = "unknown"; + BufferedReader br = null; + try { + if (vmWatcher.exists()) { + br = new BufferedReader(new FileReader(vmWatcher)); + vmCrashString = br.readLine(); + } else { + vmCrashString = "Monitor file (" + + vmWatcher.getAbsolutePath() + + ") missing, location not writable," + + " testcase not started or mixing ant versions?"; + } + } catch (final Exception e) { + e.printStackTrace(); + // ignored. + } finally { + FileUtils.close(br); + if (vmWatcher.exists()) { + FILE_UTILS.tryHardToDelete(vmWatcher); + } + } + + final boolean crash = (watchdog != null && watchdog.killedProcess()) + || !Constants.TERMINATED_SUCCESSFULLY.equals(vmCrashString); + + if (casesFile != null && crash) { + test = createDummyTestForBatchTest(test); + } + + if (watchdog != null && watchdog.killedProcess()) { + result.timedOut = true; + logTimeout(feArray, test, vmCrashString); + } else if (crash) { + result.crashed = true; + logVmCrash(feArray, test, vmCrashString); + } + + if (!FILE_UTILS.tryHardToDelete(propsFile)) { + throw new BuildException("Could not delete temporary " + + "properties file '" + + propsFile.getAbsolutePath() + "'."); + } + } + + return result; + } + + /** + * Adding ant runtime. + * @param cmd command to run + */ + private void checkIncludeAntRuntime(final CommandlineJava cmd) { + if (includeAntRuntime) { + final Map/*<String, String>*/ env = Execute.getEnvironmentVariables(); + final String cp = (String) env.get(CLASSPATH); + if (cp != null) { + cmd.createClasspath(getProject()).createPath() + .append(new Path(getProject(), cp)); + } + log("Implicitly adding " + antRuntimeClasses + " to CLASSPATH", + Project.MSG_VERBOSE); + cmd.createClasspath(getProject()).createPath() + .append(antRuntimeClasses); + } + } + + + /** + * check for the parameter being "withoutanderr" in a locale-independent way. + * @param summaryOption the summary option -can be null + * @return true if the run should be withoutput and error + */ + private boolean equalsWithOutAndErr(final String summaryOption) { + return "withoutanderr".equalsIgnoreCase(summaryOption); + } + + private void checkIncludeSummary(final CommandlineJava cmd) { + if (summary) { + String prefix = ""; + if (equalsWithOutAndErr(summaryValue)) { + prefix = "OutErr"; + } + cmd.createArgument() + .setValue(Constants.FORMATTER + + "org.apache.tools.ant.taskdefs.optional.junit." + + prefix + "SummaryJUnitResultFormatter"); + } + } + + /** + * Check the path for multiple different versions of + * ant. + * @param cmd command to execute + */ + private void checkForkedPath(final CommandlineJava cmd) { + if (forkedPathChecked) { + return; + } + forkedPathChecked = true; + if (!cmd.haveClasspath()) { + return; + } + AntClassLoader loader = null; + try { + loader = + AntClassLoader.newAntClassLoader(null, getProject(), + cmd.createClasspath(getProject()), + true); + final String projectResourceName = + LoaderUtils.classNameToResource(Project.class.getName()); + URL previous = null; + try { + for (final Enumeration e = loader.getResources(projectResourceName); + e.hasMoreElements();) { + final URL current = (URL) e.nextElement(); + if (previous != null && !urlEquals(current, previous)) { + log("WARNING: multiple versions of ant detected " + + "in path for junit " + + LINE_SEP + " " + previous + + LINE_SEP + " and " + current, + Project.MSG_WARN); + return; + } + previous = current; + } + } catch (final Exception ex) { + // Ignore exception + } + } finally { + if (loader != null) { + loader.cleanup(); + } + } + } + + /** + * Compares URLs for equality but takes case-sensitivity into + * account when comparing file URLs and ignores the jar specific + * part of the URL if present. + */ + private static boolean urlEquals(final URL u1, final URL u2) { + final String url1 = maybeStripJarAndClass(u1); + final String url2 = maybeStripJarAndClass(u2); + if (url1.startsWith("file:") && url2.startsWith("file:")) { + return new File(FILE_UTILS.fromURI(url1)) + .equals(new File(FILE_UTILS.fromURI(url2))); + } + return url1.equals(url2); + } + + private static String maybeStripJarAndClass(final URL u) { + String s = u.toString(); + if (s.startsWith("jar:")) { + final int pling = s.indexOf('!'); + s = s.substring(4, pling == -1 ? s.length() : pling); + } + return s; + } + + /** + * Create a temporary file to pass the properties to a new process. + * Will auto-delete on (graceful) exit. + * The file will be in the project basedir unless tmpDir declares + * something else. + * @param prefix + * @return created file + */ + private File createTempPropertiesFile(final String prefix) { + final File propsFile = + FILE_UTILS.createTempFile(prefix, ".properties", + tmpDir != null ? tmpDir : getProject().getBaseDir(), true, true); + return propsFile; + } + + + /** + * Pass output sent to System.out to the TestRunner so it can + * collect it for the formatters. + * + * @param output output coming from System.out + * @since Ant 1.5 + */ + @Override + protected void handleOutput(final String output) { + if (output.startsWith(TESTLISTENER_PREFIX)) { + log(output, Project.MSG_VERBOSE); + } else if (runner != null) { + if (outputToFormatters) { + runner.handleOutput(output); + } + if (showOutput) { + super.handleOutput(output); + } + } else { + super.handleOutput(output); + } + } + + /** + * Handle an input request by this task. + * @see Task#handleInput(byte[], int, int) + * This implementation delegates to a runner if it + * present. + * @param buffer the buffer into which data is to be read. + * @param offset the offset into the buffer at which data is stored. + * @param length the amount of data to read. + * + * @return the number of bytes read. + * @exception IOException if the data cannot be read. + * + * @since Ant 1.6 + */ + @Override + protected int handleInput(final byte[] buffer, final int offset, final int length) + throws IOException { + if (runner != null) { + return runner.handleInput(buffer, offset, length); + } else { + return super.handleInput(buffer, offset, length); + } + } + + + /** + * Pass output sent to System.out to the TestRunner so it can + * collect ot for the formatters. + * + * @param output output coming from System.out + * @since Ant 1.5.2 + */ + @Override + protected void handleFlush(final String output) { + if (runner != null) { + runner.handleFlush(output); + if (showOutput) { + super.handleFlush(output); + } + } else { + super.handleFlush(output); + } + } + + /** + * Pass output sent to System.err to the TestRunner so it can + * collect it for the formatters. + * + * @param output output coming from System.err + * @since Ant 1.5 + */ + @Override + public void handleErrorOutput(final String output) { + if (runner != null) { + runner.handleErrorOutput(output); + if (showOutput) { + super.handleErrorOutput(output); + } + } else { + super.handleErrorOutput(output); + } + } + + + /** + * Pass output sent to System.err to the TestRunner so it can + * collect it for the formatters. + * + * @param output coming from System.err + * @since Ant 1.5.2 + */ + @Override + public void handleErrorFlush(final String output) { + if (runner != null) { + runner.handleErrorFlush(output); + if (showOutput) { + super.handleErrorFlush(output); + } + } else { + super.handleErrorFlush(output); + } + } + + // in VM is not very nice since it could probably hang the + // whole build. IMHO this method should be avoided and it would be best + // to remove it in future versions. TBD. (SBa) + + /** + * Execute inside VM. + * @param arg one JUnitTest + * @throws BuildException under unspecified circumstances + * @return the results + */ + private TestResultHolder executeInVM(final JUnitTest arg) throws BuildException { + if (delegate == null) { + setupJUnitDelegate(); + } + + final JUnitTest test = (JUnitTest) arg.clone(); + test.setProperties(getProject().getProperties()); + if (dir != null) { + log("dir attribute ignored if running in the same VM", + Project.MSG_WARN); + } + + if (newEnvironment || null != env.getVariables()) { + log("Changes to environment variables are ignored if running in " + + "the same VM.", Project.MSG_WARN); + } + + if (getCommandline().getBootclasspath() != null) { + log("bootclasspath is ignored if running in the same VM.", + Project.MSG_WARN); + } + + final CommandlineJava.SysProperties sysProperties = + getCommandline().getSystemProperties(); + if (sysProperties != null) { + sysProperties.setSystem(); + } + + try { + log("Using System properties " + System.getProperties(), + Project.MSG_VERBOSE); + if (splitJUnit) { + classLoader = (AntClassLoader) delegate.getClass().getClassLoader(); + } else { + createClassLoader(); + } + if (classLoader != null) { + classLoader.setThreadContextLoader(); + } + runner = delegate.newJUnitTestRunner(test, test.getMethods(), test.getHaltonerror(), + test.getFiltertrace(), + test.getHaltonfailure(), false, + getEnableTestListenerEvents(), + classLoader); + if (summary) { + + final JUnitTaskMirror.SummaryJUnitResultFormatterMirror f = + delegate.newSummaryJUnitResultFormatter(); + f.setWithOutAndErr(equalsWithOutAndErr(summaryValue)); + f.setOutput(getDefaultOutput()); + runner.addFormatter(f); + } + + runner.setPermissions(perm); + + final FormatterElement[] feArray = mergeFormatters(test); + for (int i = 0; i < feArray.length; i++) { + final FormatterElement fe = feArray[i]; + if (fe.shouldUse(this)) { + final File outFile = getOutput(fe, test); + if (outFile != null) { + fe.setOutfile(outFile); + } else { + fe.setOutput(getDefaultOutput()); + } + runner.addFormatter(fe.createFormatter(classLoader)); + } + } + + runner.run(); + final TestResultHolder result = new TestResultHolder(); + result.exitCode = runner.getRetCode(); + return result; + } finally { + if (sysProperties != null) { + sysProperties.restoreSystem(); + } + if (classLoader != null) { + classLoader.resetThreadContextLoader(); + } + } + } + + /** + * @return <tt>null</tt> if there is a timeout value, otherwise the + * watchdog instance. + * + * @throws BuildException under unspecified circumstances + * @since Ant 1.2 + */ + protected ExecuteWatchdog createWatchdog() throws BuildException { + if (timeout == null) { + return null; + } + return new ExecuteWatchdog((long) timeout.intValue()); + } + + /** + * Get the default output for a formatter. + * + * @return default output stream for a formatter + * @since Ant 1.3 + */ + protected OutputStream getDefaultOutput() { + return new LogOutputStream(this, Project.MSG_INFO); + } + + /** + * Merge all individual tests from the batchtest with all individual tests + * and return an enumeration over all <tt>JUnitTest</tt>. + * + * @return enumeration over individual tests + * @since Ant 1.3 + */ + protected Enumeration<JUnitTest> getIndividualTests() { + final int count = batchTests.size(); + final Enumeration[] enums = new Enumeration[ count + 1]; + for (int i = 0; i < count; i++) { + final BatchTest batchtest = batchTests.elementAt(i); + enums[i] = batchtest.elements(); + } + enums[enums.length - 1] = tests.elements(); + return Enumerations.fromCompound(enums); + } + + /** + * Verifies all <code>test</code> elements having the <code>methods</code> + * attribute specified and having the <code>if</code>-condition resolved + * to true, that the value of the <code>methods</code> attribute is valid. + * @exception BuildException if some of the tests matching the described + * conditions has invalid value of the + * <code>methods</code> attribute + * @since 1.8.2 + */ + private void checkMethodLists() throws BuildException { + if (tests.isEmpty()) { + return; + } + + final Enumeration<JUnitTest> testsEnum = tests.elements(); + while (testsEnum.hasMoreElements()) { + final JUnitTest test = testsEnum.nextElement(); + if (test.hasMethodsSpecified() && test.shouldRun(getProject())) { + test.resolveMethods(); + } + } + } + + /** + * return an enumeration listing each test, then each batchtest + * @return enumeration + * @since Ant 1.3 + */ + protected Enumeration<JUnitTest> allTests() { + final Enumeration[] enums = {tests.elements(), batchTests.elements()}; + return Enumerations.fromCompound(enums); + } + + /** + * @param test junit test + * @return array of FormatterElement + * @since Ant 1.3 + */ + private FormatterElement[] mergeFormatters(final JUnitTest test) { + final Vector<FormatterElement> feVector = (Vector<FormatterElement>) formatters.clone(); + test.addFormattersTo(feVector); + final FormatterElement[] feArray = new FormatterElement[feVector.size()]; + feVector.copyInto(feArray); + return feArray; + } + + /** + * If the formatter sends output to a file, return that file. + * null otherwise. + * @param fe formatter element + * @param test one JUnit test + * @return file reference + * @since Ant 1.3 + */ + protected File getOutput(final FormatterElement fe, final JUnitTest test) { + if (fe.getUseFile()) { + String base = test.getOutfile(); + if (base == null) { + base = JUnitTaskMirror.JUnitTestRunnerMirror.IGNORED_FILE_NAME; + } + final String filename = base + fe.getExtension(); + final File destFile = new File(test.getTodir(), filename); + final String absFilename = destFile.getAbsolutePath(); + return getProject().resolveFile(absFilename); + } + return null; + } + + /** + * Search for the given resource and add the directory or archive + * that contains it to the classpath. + * + * <p>Doesn't work for archives in JDK 1.1 as the URL returned by + * getResource doesn't contain the name of the archive.</p> + * + * @param resource resource that one wants to lookup + * @since Ant 1.4 + */ + protected void addClasspathEntry(final String resource) { + addClasspathResource(resource); + } + + /** + * Implementation of addClasspathEntry. + * + * @param resource resource that one wants to lookup + * @return true if something was in fact added + * @since Ant 1.7.1 + */ + private boolean addClasspathResource(String resource) { + /* + * pre Ant 1.6 this method used to call getClass().getResource + * while Ant 1.6 will call ClassLoader.getResource(). + * + * The difference is that Class.getResource expects a leading + * slash for "absolute" resources and will strip it before + * delegating to ClassLoader.getResource - so we now have to + * emulate Class's behavior. + */ + if (resource.startsWith("/")) { + resource = resource.substring(1); + } else { + resource = "org/apache/tools/ant/taskdefs/optional/junit/" + + resource; + } + + final File f = LoaderUtils.getResourceSource(JUnitTask.class.getClassLoader(), + resource); + if (f != null) { + log("Found " + f.getAbsolutePath(), Project.MSG_DEBUG); + antRuntimeClasses.createPath().setLocation(f); + return true; + } else { + log("Couldn\'t find " + resource, Project.MSG_DEBUG); + return false; + } + } + + static final String TIMEOUT_MESSAGE = + "Timeout occurred. Please note the time in the report does" + + " not reflect the time until the timeout."; + + /** + * Take care that some output is produced in report files if the + * watchdog kills the test. + * + * @since Ant 1.5.2 + */ + private void logTimeout(final FormatterElement[] feArray, final JUnitTest test, + final String testCase) { + logVmExit(feArray, test, TIMEOUT_MESSAGE, testCase); + } + + /** + * Take care that some output is produced in report files if the + * forked machine exited before the test suite finished but the + * reason is not a timeout. + * + * @since Ant 1.7 + */ + private void logVmCrash(final FormatterElement[] feArray, final JUnitTest test, final String testCase) { + logVmExit( + feArray, test, + "Forked Java VM exited abnormally. Please note the time in the report" + + " does not reflect the time until the VM exit.", + testCase); + } + + /** + * Take care that some output is produced in report files if the + * forked machine terminated before the test suite finished + * + * @since Ant 1.7 + */ + private void logVmExit(final FormatterElement[] feArray, final JUnitTest test, + final String message, final String testCase) { + if (delegate == null) { + setupJUnitDelegate(); + } + + try { + log("Using System properties " + System.getProperties(), + Project.MSG_VERBOSE); + if (splitJUnit) { + classLoader = (AntClassLoader) delegate.getClass().getClassLoader(); + } else { + createClassLoader(); + } + if (classLoader != null) { + classLoader.setThreadContextLoader(); + } + + test.setCounts(1, 0, 1, 0); + test.setProperties(getProject().getProperties()); + for (int i = 0; i < feArray.length; i++) { + final FormatterElement fe = feArray[i]; + if (fe.shouldUse(this)) { + final JUnitTaskMirror.JUnitResultFormatterMirror formatter = + fe.createFormatter(classLoader); + if (formatter != null) { + OutputStream out = null; + final File outFile = getOutput(fe, test); + if (outFile != null) { + try { + out = new FileOutputStream(outFile); + } catch (final IOException e) { + // ignore + } + } + if (out == null) { + out = getDefaultOutput(); + } + delegate.addVmExit(test, formatter, out, message, + testCase); + } + } + } + if (summary) { + final JUnitTaskMirror.SummaryJUnitResultFormatterMirror f = + delegate.newSummaryJUnitResultFormatter(); + f.setWithOutAndErr(equalsWithOutAndErr(summaryValue)); + delegate.addVmExit(test, f, getDefaultOutput(), message, testCase); + } + } finally { + if (classLoader != null) { + classLoader.resetThreadContextLoader(); + } + } + } + + /** + * Creates and configures an AntClassLoader instance from the + * nested classpath element. + * + * @since Ant 1.6 + */ + private void createClassLoader() { + final Path userClasspath = getCommandline().getClasspath(); + if (userClasspath != null) { + if (reloading || classLoader == null) { + deleteClassLoader(); + final Path classpath = (Path) userClasspath.clone(); + if (includeAntRuntime) { + log("Implicitly adding " + antRuntimeClasses + + " to CLASSPATH", Project.MSG_VERBOSE); + classpath.append(antRuntimeClasses); + } + classLoader = getProject().createClassLoader(classpath); + if (getClass().getClassLoader() != null + && getClass().getClassLoader() != Project.class.getClassLoader()) { + classLoader.setParent(getClass().getClassLoader()); + } + classLoader.setParentFirst(false); + classLoader.addJavaLibraries(); + log("Using CLASSPATH " + classLoader.getClasspath(), + Project.MSG_VERBOSE); + // make sure the test will be accepted as a TestCase + classLoader.addSystemPackageRoot("junit"); + // make sure the test annotation are accepted + classLoader.addSystemPackageRoot("org.junit"); + // will cause trouble in JDK 1.1 if omitted + classLoader.addSystemPackageRoot("org.apache.tools.ant"); + } + } + } + + /** + * Removes resources. + * + * <p>Is invoked in {@link #execute execute}. Subclasses that + * don't invoke execute should invoke this method in a finally + * block.</p> + * + * @since Ant 1.7.1 + */ + protected void cleanup() { + deleteClassLoader(); + delegate = null; + } + + /** + * Removes a classloader if needed. + * @since Ant 1.7 + */ + private void deleteClassLoader() { + if (classLoader != null) { + classLoader.cleanup(); + classLoader = null; + } + if (mirrorLoader instanceof SplitClassLoader) { + ((SplitClassLoader) mirrorLoader).cleanup(); + } + mirrorLoader = null; + } + + /** + * Get the command line used to run the tests. + * @return the command line. + * @since Ant 1.6.2 + */ + protected CommandlineJava getCommandline() { + if (commandline == null) { + commandline = new CommandlineJava(); + commandline.setClassname("org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner"); + } + return commandline; + } + + /** + * Forked test support + * @since Ant 1.6.2 + */ + private static final class ForkedTestConfiguration { + private final boolean filterTrace; + private final boolean haltOnError; + private final boolean haltOnFailure; + private final String errorProperty; + private final String failureProperty; + + /** + * constructor for forked test configuration + * @param filterTrace + * @param haltOnError + * @param haltOnFailure + * @param errorProperty + * @param failureProperty + */ + ForkedTestConfiguration(final boolean filterTrace, final boolean haltOnError, + final boolean haltOnFailure, final String errorProperty, + final String failureProperty) { + this.filterTrace = filterTrace; + this.haltOnError = haltOnError; + this.haltOnFailure = haltOnFailure; + this.errorProperty = errorProperty; + this.failureProperty = failureProperty; + } + + /** + * configure from a test; sets member variables to attributes of the test + * @param test + */ + ForkedTestConfiguration(final JUnitTest test) { + this(test.getFiltertrace(), + test.getHaltonerror(), + test.getHaltonfailure(), + test.getErrorProperty(), + test.getFailureProperty()); + } + + /** + * equality test checks all the member variables + * @param other + * @return true if everything is equal + */ + @Override + public boolean equals(final Object other) { + if (other == null + || other.getClass() != ForkedTestConfiguration.class) { + return false; + } + final ForkedTestConfiguration o = (ForkedTestConfiguration) other; + return filterTrace == o.filterTrace + && haltOnError == o.haltOnError + && haltOnFailure == o.haltOnFailure + && ((errorProperty == null && o.errorProperty == null) + || + (errorProperty != null + && errorProperty.equals(o.errorProperty))) + && ((failureProperty == null && o.failureProperty == null) + || + (failureProperty != null + && failureProperty.equals(o.failureProperty))); + } + + /** + * hashcode is based only on the boolean members, and returns a value + * in the range 0-7. + * @return hash code value + */ + @Override + public int hashCode() { + // CheckStyle:MagicNumber OFF + return (filterTrace ? 1 : 0) + + (haltOnError ? 2 : 0) + + (haltOnFailure ? 4 : 0); + // CheckStyle:MagicNumber ON + } + } + + /** + * These are the different forking options + * @since 1.6.2 + */ + public static final class ForkMode extends EnumeratedAttribute { + + /** + * fork once only + */ + public static final String ONCE = "once"; + /** + * fork once per test class + */ + public static final String PER_TEST = "perTest"; + /** + * fork once per batch of tests + */ + public static final String PER_BATCH = "perBatch"; + + /** No arg constructor. */ + public ForkMode() { + super(); + } + + /** + * Constructor using a value. + * @param value the value to use - once, perTest or perBatch. + */ + public ForkMode(final String value) { + super(); + setValue(value); + } + + /** {@inheritDoc}. */ + @Override + public String[] getValues() { + return new String[] {ONCE, PER_TEST, PER_BATCH}; + } + } + + /** + * Executes all tests that don't need to be forked (or all tests + * if the runIndividual argument is true. Returns a collection of + * lists of tests that share the same VM configuration and haven't + * been executed yet. + * @param testList the list of tests to be executed or queued. + * @param runIndividual if true execute each test individually. + * @return a list of tasks to be executed. + * @since 1.6.2 + */ + protected Collection<List> executeOrQueue(final Enumeration<JUnitTest> testList, + final boolean runIndividual) { + final Map<ForkedTestConfiguration, List> testConfigurations = new HashMap<ForkedTestConfiguration, List>(); + while (testList.hasMoreElements()) { + final JUnitTest test = testList.nextElement(); + if (test.shouldRun(getProject())) { + /* with multi-threaded runs need to defer execution of even */ + /* individual tests so the threads can pick tests off the queue. */ + if ((runIndividual || !test.getFork()) && (threads == 1)) { + execute(test, 0); + } else { + final ForkedTestConfiguration c = + new ForkedTestConfiguration(test); + List<JUnitTest> l = testConfigurations.get(c); + if (l == null) { + l = new ArrayList<JUnitTest>(); + testConfigurations.put(c, l); + } + l.add(test); + } + } + } + return testConfigurations.values(); + } + + /** + * Logs information about failed tests, potentially stops + * processing (by throwing a BuildException) if a failure/error + * occurred or sets a property. + * @param exitValue the exitValue of the test. + * @param wasKilled if true, the test had been killed. + * @param test the test in question. + * @param name the name of the test. + * @since Ant 1.6.2 + */ + protected void actOnTestResult(final int exitValue, final boolean wasKilled, + final JUnitTest test, final String name) { + final TestResultHolder t = new TestResultHolder(); + t.exitCode = exitValue; + t.timedOut = wasKilled; + actOnTestResult(t, test, name); + } + + /** + * Logs information about failed tests, potentially stops + * processing (by throwing a BuildException) if a failure/error + * occurred or sets a property. + * @param result the result of the test. + * @param test the test in question. + * @param name the name of the test. + * @since Ant 1.7 + */ + protected void actOnTestResult(final TestResultHolder result, final JUnitTest test, + final String name) { + // if there is an error/failure and that it should halt, stop + // everything otherwise just log a statement + final boolean fatal = result.timedOut || result.crashed; + final boolean errorOccurredHere = + result.exitCode == JUnitTaskMirror.JUnitTestRunnerMirror.ERRORS || fatal; + final boolean failureOccurredHere = + result.exitCode != JUnitTaskMirror.JUnitTestRunnerMirror.SUCCESS || fatal; + if (errorOccurredHere || failureOccurredHere) { + if ((errorOccurredHere && test.getHaltonerror()) + || (failureOccurredHere && test.getHaltonfailure())) { + throw new BuildException(name + " failed" + + (result.timedOut ? " (timeout)" : "") + + (result.crashed ? " (crashed)" : ""), getLocation()); + } else { + if (logFailedTests) { + log(name + " FAILED" + + (result.timedOut ? " (timeout)" : "") + + (result.crashed ? " (crashed)" : ""), + Project.MSG_ERR); + } + if (errorOccurredHere && test.getErrorProperty() != null) { + getProject().setNewProperty(test.getErrorProperty(), "true"); + } + if (failureOccurredHere && test.getFailureProperty() != null) { + getProject().setNewProperty(test.getFailureProperty(), "true"); + } + } + } + } + + /** + * A value class that contains the result of a test. + */ + protected static class TestResultHolder { + // CheckStyle:VisibilityModifier OFF - bc + /** the exit code of the test. */ + public int exitCode = JUnitTaskMirror.JUnitTestRunnerMirror.ERRORS; + /** true if the test timed out */ + public boolean timedOut = false; + /** true if the test crashed */ + public boolean crashed = false; + // CheckStyle:VisibilityModifier ON + } + + /** + * A stream handler for handling the junit task. + * @since Ant 1.7 + */ + protected static class JUnitLogOutputStream extends LogOutputStream { + private final Task task; // local copy since LogOutputStream.task is private + + /** + * Constructor. + * @param task the task being logged. + * @param level the log level used to log data written to this stream. + */ + public JUnitLogOutputStream(final Task task, final int level) { + super(task, level); + this.task = task; + } + + /** + * Logs a line. + * If the line starts with junit.framework.TestListener: set the level + * to MSG_VERBOSE. + * @param line the line to log. + * @param level the logging level to use. + */ + @Override + protected void processLine(final String line, final int level) { + if (line.startsWith(TESTLISTENER_PREFIX)) { + task.log(line, Project.MSG_VERBOSE); + } else { + super.processLine(line, level); + } + } + } + + /** + * A log stream handler for junit. + * @since Ant 1.7 + */ + protected static class JUnitLogStreamHandler extends PumpStreamHandler { + /** + * Constructor. + * @param task the task to log. + * @param outlevel the level to use for standard output. + * @param errlevel the level to use for error output. + */ + public JUnitLogStreamHandler(final Task task, final int outlevel, final int errlevel) { + super(new JUnitLogOutputStream(task, outlevel), + new LogOutputStream(task, errlevel)); + } + } + + static final String NAME_OF_DUMMY_TEST = "Batch-With-Multiple-Tests"; + + /** + * Creates a JUnitTest instance that shares all flags with the + * passed in instance but has a more meaningful name. + * + * <p>If a VM running multiple tests crashes, we don't know which + * test failed. Prior to Ant 1.8.0 Ant would log the error with + * the last test of the batch test, which caused some confusion + * since the log might look as if a test had been executed last + * that was never started. With Ant 1.8.0 the test's name will + * indicate that something went wrong with a test inside the batch + * without giving it a real name.</p> + * + * @see "https://issues.apache.org/bugzilla/show_bug.cgi?id=45227" + */ + private static JUnitTest createDummyTestForBatchTest(final JUnitTest test) { + final JUnitTest t = (JUnitTest) test.clone(); + final int index = test.getName().lastIndexOf('.'); + // make sure test looks as if it was in the same "package" as + // the last test of the batch + final String pack = index > 0 ? test.getName().substring(0, index + 1) : ""; + t.setName(pack + NAME_OF_DUMMY_TEST); + return t; + } + + private static void printDual(final BufferedWriter w, final PrintStream s, final String text) + throws IOException { + w.write(String.valueOf(text)); + s.print(text); + } + + private static void printlnDual(final BufferedWriter w, final PrintStream s, final String text) + throws IOException { + w.write(String.valueOf(text)); + w.newLine(); + s.println(text); + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java new file mode 100644 index 00000000..694e1d8c --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.types.Permissions; + +/** + * Handles the portions of {@link JUnitTask} which need to directly access + * actual JUnit classes, so that junit.jar need not be on Ant's startup classpath. + * Neither JUnitTask.java nor JUnitTaskMirror.java nor their transitive static + * deps may import any junit.** classes! + * Specifically, need to not refer to + * - JUnitResultFormatter or its subclasses + * - JUnitVersionHelper + * - JUnitTestRunner + * Cf. JUnitTask.SplitLoader#isSplit(String) + * Public only to permit access from classes in this package; do not use directly. + * + * @since 1.7 + * @see "bug #38799" + */ +public interface JUnitTaskMirror { + + /** + * Add the formatter to be called when the jvm exits before + * the test suite finishes. + * @param test the test. + * @param formatter the formatter to use. + * @param out the output stream to use. + * @param message the message to write out. + * @param testCase the name of the test. + */ + void addVmExit(JUnitTest test, JUnitResultFormatterMirror formatter, + OutputStream out, String message, String testCase); + + /** + * Create a new test runner for a test. + * @param test the test to run. + * @param methods names of the test methods to be run. + * @param haltOnError if true halt the tests if an error occurs. + * @param filterTrace if true filter the stack traces. + * @param haltOnFailure if true halt the test if a failure occurs. + * @param showOutput if true show output. + * @param logTestListenerEvents if true log test listener events. + * @param classLoader the classloader to use to create the runner. + * @return the test runner. + */ + JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test, String[] methods, boolean haltOnError, + boolean filterTrace, boolean haltOnFailure, boolean showOutput, + boolean logTestListenerEvents, AntClassLoader classLoader); + + /** + * Create a summary result formatter. + * @return the created formatter. + */ + SummaryJUnitResultFormatterMirror newSummaryJUnitResultFormatter(); + + + /** The interface that JUnitResultFormatter extends. */ + public interface JUnitResultFormatterMirror { + /** + * Set the output stream. + * @param outputStream the stream to use. + */ + void setOutput(OutputStream outputStream); + } + + /** The interface that SummaryJUnitResultFormatter extends. */ + public interface SummaryJUnitResultFormatterMirror + extends JUnitResultFormatterMirror { + + /** + * Set where standard out and standard error should be included. + * @param value if true include the outputs in the summary. + */ + void setWithOutAndErr(boolean value); + } + + /** Interface that test runners implement. */ + public interface JUnitTestRunnerMirror { + + /** + * Used in formatter arguments as a placeholder for the basename + * of the output file (which gets replaced by a test specific + * output file name later). + * + * @since Ant 1.6.3 + */ + String IGNORED_FILE_NAME = "IGNORETHIS"; + + /** + * No problems with this test. + */ + int SUCCESS = 0; + + /** + * Some tests failed. + */ + int FAILURES = 1; + + /** + * An error occurred. + */ + int ERRORS = 2; + + /** + * Permissions for the test run. + * @param perm the permissions to use. + */ + void setPermissions(Permissions perm); + + /** Run the test. */ + void run(); + + /** + * Add a formatter to the test. + * @param formatter the formatter to use. + */ + void addFormatter(JUnitResultFormatterMirror formatter); + + /** + * Returns what System.exit() would return in the standalone version. + * + * @return 2 if errors occurred, 1 if tests failed else 0. + */ + int getRetCode(); + + /** + * Handle output sent to System.err. + * + * @param output coming from System.err + */ + void handleErrorFlush(String output); + + /** + * Handle output sent to System.err. + * + * @param output output for System.err + */ + void handleErrorOutput(String output); + + /** + * Handle output sent to System.out. + * + * @param output output for System.out. + */ + void handleOutput(String output); + + /** + * Handle an input request. + * + * @param buffer the buffer into which data is to be read. + * @param offset the offset into the buffer at which data is stored. + * @param length the amount of data to read. + * + * @return the number of bytes read. + * + * @exception IOException if the data cannot be read. + */ + int handleInput(byte[] buffer, int offset, int length) throws IOException; + + /** + * Handle output sent to System.out. + * + * @param output output for System.out. + */ + void handleFlush(String output); + + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java new file mode 100644 index 00000000..c7dae258 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.OutputStream; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; +import junit.framework.TestResult; + +import org.apache.tools.ant.AntClassLoader; + +/** + * Implementation of the part of the junit task which can directly refer to junit.* classes. + * Public only to permit use of reflection; do not use directly. + * @see JUnitTaskMirror + * @see "bug #38799" + * @since 1.7 + */ +public final class JUnitTaskMirrorImpl implements JUnitTaskMirror { + + private final JUnitTask task; + + /** + * Constructor. + * @param task the junittask that uses this mirror. + */ + public JUnitTaskMirrorImpl(JUnitTask task) { + this.task = task; + } + + /** {@inheritDoc}. */ + public void addVmExit(JUnitTest test, JUnitTaskMirror.JUnitResultFormatterMirror aFormatter, + OutputStream out, String message, String testCase) { + JUnitResultFormatter formatter = (JUnitResultFormatter) aFormatter; + formatter.setOutput(out); + formatter.startTestSuite(test); + //the trick to integrating test output to the formatter, is to + //create a special test class that asserts an error + //and tell the formatter that it raised. + TestCase t = new VmExitErrorTest(message, test, testCase); + formatter.startTest(t); + formatter.addError(t, new AssertionFailedError(message)); + formatter.endTestSuite(test); + } + + /** {@inheritDoc}. */ + public JUnitTaskMirror.JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test, + String[] methods, + boolean haltOnError, boolean filterTrace, boolean haltOnFailure, + boolean showOutput, boolean logTestListenerEvents, AntClassLoader classLoader) { + return new JUnitTestRunner(test, methods, haltOnError, filterTrace, haltOnFailure, + showOutput, logTestListenerEvents, classLoader); + } + + /** {@inheritDoc}. */ + public JUnitTaskMirror.SummaryJUnitResultFormatterMirror newSummaryJUnitResultFormatter() { + return new SummaryJUnitResultFormatter(); + } + + static class VmExitErrorTest extends TestCase { + + private String message; + private JUnitTest test; + private String testCase; + + VmExitErrorTest(String aMessage, JUnitTest anOriginalTest, String aTestCase) { + message = aMessage; + test = anOriginalTest; + testCase = aTestCase; + } + + public int countTestCases() { + return 1; + } + + public void run(TestResult r) { + throw new AssertionFailedError(message); + } + + public String getName() { + return testCase; + } + + String getClassName() { + return test.getName(); + } + + public String toString() { + return test.getName() + ":" + testCase; + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java new file mode 100644 index 00000000..835c013b --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java @@ -0,0 +1,542 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.PropertyHelper; + +/** + * <p> Run a single JUnit test. + * + * <p> The JUnit test is actually run by {@link JUnitTestRunner}. + * So read the doc comments for that class :) + * + * @since Ant 1.2 + * + * @see JUnitTask + * @see JUnitTestRunner + */ +public class JUnitTest extends BaseTest implements Cloneable { + + /** the name of the test case */ + private String name = null; + + /** + * whether the list of test methods has been specified + * @see #setMethods(java.lang.String) + * @see #setMethods(java.lang.String[]) + */ + private boolean methodsSpecified = false; + + /** comma-separated list of names of test methods to execute */ + private String methodsList = null; + + /** the names of test methods to execute */ + private String[] methods = null; + + /** the name of the result file */ + private String outfile = null; + + // @todo this is duplicating TestResult information. Only the time is not + // part of the result. So we'd better derive a new class from TestResult + // and deal with it. (SB) + private long runs, failures, errors; + /** + @since Ant 1.9.0 + */ + private long skips; + + private long runTime; + + private int antThreadID; + + // Snapshot of the system properties + private Properties props = null; + + /** No arg constructor. */ + public JUnitTest() { + } + + /** + * Constructor with name. + * @param name the name of the test. + */ + public JUnitTest(String name) { + this.name = name; + } + + /** + * Constructor with options. + * @param name the name of the test. + * @param haltOnError if true halt the tests if there is an error. + * @param haltOnFailure if true halt the tests if there is a failure. + * @param filtertrace if true filter stack traces. + */ + public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure, + boolean filtertrace) { + this(name, haltOnError, haltOnFailure, filtertrace, null, 0); + } + + /** + * Constructor with options. + * @param name the name of the test. + * @param haltOnError if true halt the tests if there is an error. + * @param haltOnFailure if true halt the tests if there is a failure. + * @param filtertrace if true filter stack traces. + * @param methods if non-null run only these test methods + * @since 1.8.2 + */ + public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure, + boolean filtertrace, String[] methods) { + this(name, haltOnError, haltOnFailure, filtertrace, methods, 0); + } + + /** + * Constructor with options. + * @param name the name of the test. + * @param haltOnError if true halt the tests if there is an error. + * @param haltOnFailure if true halt the tests if there is a failure. + * @param filtertrace if true filter stack traces. + * @param methods if non-null run only these test methods + * @param thread Ant thread ID in which test is currently running + * @since 1.9.4 + */ + public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure, + boolean filtertrace, String[] methods, int thread) { + this.name = name; + this.haltOnError = haltOnError; + this.haltOnFail = haltOnFailure; + this.filtertrace = filtertrace; + this.methodsSpecified = methods != null; + this.methods = methodsSpecified ? (String[]) methods.clone() : null; + this.antThreadID = thread; + } + + /** + * Sets names of individual test methods to be executed. + * @param value comma-separated list of names of individual test methods + * to be executed, + * or <code>null</code> if all test methods should be executed + * @since 1.8.2 + */ + public void setMethods(String value) { + methodsList = value; + methodsSpecified = (value != null); + methods = null; + } + + /** + * Sets names of individual test methods to be executed. + * @param value non-empty array of names of test methods to be executed + * @see #setMethods(String) + * @since 1.8.2 + */ + void setMethods(String[] value) { + methods = value; + methodsSpecified = (value != null); + methodsList = null; + } + + /** + * Set the name of the test class. + * @param value the name to use. + */ + public void setName(String value) { + name = value; + } + + /** + * Set the thread id + * @param thread the Ant id of the thread running this test + * (this is not the system process or thread id) + * (this will be 0 in single-threaded mode). + * @since Ant 1.9.4 + */ + public void setThread(int thread) { + this.antThreadID = thread; + } + + /** + * Set the name of the output file. + * @param value the name of the output file to use. + */ + public void setOutfile(String value) { + outfile = value; + } + + /** + * Informs whether a list of test methods has been specified in this test. + * @return <code>true</code> if test methods to be executed have been + * specified, <code>false</code> otherwise + * @see #setMethods(java.lang.String) + * @see #setMethods(java.lang.String[]) + * @since 1.8.2 + */ + boolean hasMethodsSpecified() { + return methodsSpecified; + } + + /** + * Get names of individual test methods to be executed. + * + * @return array of names of the individual test methods to be executed, + * or <code>null</code> if all test methods in the suite + * defined by the test class will be executed + * @since 1.8.2 + */ + String[] getMethods() { + if (methodsSpecified && (methods == null)) { + resolveMethods(); + } + return methods; + } + + /** + * Gets a comma-separated list of names of methods that are to be executed + * by this test. + * @return the comma-separated list of test method names, or an empty + * string of no method is to be executed, or <code>null</code> + * if no method is specified + * @since 1.8.2 + */ + String getMethodsString() { + if ((methodsList == null) && methodsSpecified) { + if (methods.length == 0) { + methodsList = ""; + } else if (methods.length == 1) { + methodsList = methods[0]; + } else { + StringBuffer buf = new StringBuffer(methods.length * 16); + buf.append(methods[0]); + for (int i = 1; i < methods.length; i++) { + buf.append(',').append(methods[i]); + } + methodsList = buf.toString(); + } + } + return methodsList; + } + + /** + * Computes the value of the {@link #methods} field from the value + * of the {@link #methodsList} field, if it has not been computed yet. + * @exception BuildException if the value of the {@link #methodsList} field + * was invalid + * @since 1.8.2 + */ + void resolveMethods() { + if ((methods == null) && methodsSpecified) { + try { + methods = parseTestMethodNamesList(methodsList); + } catch (IllegalArgumentException ex) { + throw new BuildException( + "Invalid specification of test methods: \"" + + methodsList + + "\"; expected: comma-separated list of valid Java identifiers", + ex); + } + } + } + + /** + * Parses a comma-separated list of method names and check their validity. + * @param methodNames comma-separated list of method names to be parsed + * @return array of individual test method names + * @exception java.lang.IllegalArgumentException + * if the given string is <code>null</code> or if it is not + * a comma-separated list of valid Java identifiers; + * an empty string is acceptable and is handled as an empty + * list + * @since 1.8.2 + */ + public static String[] parseTestMethodNamesList(String methodNames) + throws IllegalArgumentException { + if (methodNames == null) { + throw new IllegalArgumentException("methodNames is <null>"); + } + + methodNames = methodNames.trim(); + + int length = methodNames.length(); + if (length == 0) { + return new String[0]; + } + + /* strip the trailing comma, if any */ + if (methodNames.charAt(length - 1) == ',') { + methodNames = methodNames.substring(0, length - 1).trim(); + length = methodNames.length(); + if (length == 0) { + throw new IllegalArgumentException("Empty method name"); + } + } + + final char[] chars = methodNames.toCharArray(); + /* easy detection of one particular case of illegal string: */ + if (chars[0] == ',') { + throw new IllegalArgumentException("Empty method name"); + } + /* count number of method names: */ + int wordCount = 1; + for (int i = 1; i < chars.length; i++) { + if (chars[i] == ',') { + wordCount++; + } + } + /* prepare the resulting array: */ + String[] result = new String[wordCount]; + /* parse the string: */ + final int stateBeforeWord = 1; + final int stateInsideWord = 2; + final int stateAfterWord = 3; + // + int state = stateBeforeWord; + int wordStartIndex = -1; + int wordIndex = 0; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + switch (state) { + case stateBeforeWord: + if (c == ',') { + throw new IllegalArgumentException("Empty method name"); + } else if (c == ' ') { + // remain in the same state + } else if (Character.isJavaIdentifierStart(c)) { + wordStartIndex = i; + state = stateInsideWord; + } else { + throw new IllegalArgumentException("Illegal start of method name: " + c); + } + break; + case stateInsideWord: + if (c == ',') { + result[wordIndex++] = methodNames.substring(wordStartIndex, i); + state = stateBeforeWord; + } else if (c == ' ') { + result[wordIndex++] = methodNames.substring(wordStartIndex, i); + state = stateAfterWord; + } else if (Character.isJavaIdentifierPart(c)) { + // remain in the same state + } else { + throw new IllegalArgumentException("Illegal character in method name: " + c); + } + break; + case stateAfterWord: + if (c == ',') { + state = stateBeforeWord; + } else if (c == ' ') { + // remain in the same state + } else { + throw new IllegalArgumentException("Space in method name"); + } + break; + default: + // this should never happen + } + } + switch (state) { + case stateBeforeWord: + case stateAfterWord: + break; + case stateInsideWord: + result[wordIndex++] = methodNames.substring(wordStartIndex, chars.length); + break; + default: + // this should never happen + } + return result; + } + + /** + * Get the name of the test class. + * @return the name of the test. + */ + public String getName() { + return name; + } + + /** + * Get the Ant id of the thread running the test. + * @return the thread id + */ + public int getThread() { + return antThreadID; + } + + /** + * Get the name of the output file + * + * @return the name of the output file. + */ + public String getOutfile() { + return outfile; + } + + /** + * Set the number of runs, failures, errors, and skipped tests. + * @param runs the number of runs. + * @param failures the number of failures. + * @param errors the number of errors. + * Kept for backward compatibility with Ant 1.8.4 + */ + public void setCounts(long runs, long failures, long errors) { + this.runs = runs; + this.failures = failures; + this.errors = errors; + } + /** + * Set the number of runs, failures, errors, and skipped tests. + * @param runs the number of runs. + * @param failures the number of failures. + * @param errors the number of errors. + * @param skips the number of skipped tests. + * @since Ant 1.9.0 + */ + public void setCounts(long runs, long failures, long errors, long skips) { + this.runs = runs; + this.failures = failures; + this.errors = errors; + this.skips = skips; + } + + /** + * Set the runtime. + * @param runTime the time in milliseconds. + */ + public void setRunTime(long runTime) { + this.runTime = runTime; + } + + /** + * Get the number of runs. + * @return the number of runs. + */ + public long runCount() { + return runs; + } + + /** + * Get the number of failures. + * @return the number of failures. + */ + public long failureCount() { + return failures; + } + + /** + * Get the number of errors. + * @return the number of errors. + */ + public long errorCount() { + return errors; + } + + /** + * Get the number of skipped tests. + * @return the number of skipped tests. + */ + public long skipCount() { + return skips; + } + + /** + * Get the run time. + * @return the run time in milliseconds. + */ + public long getRunTime() { + return runTime; + } + + /** + * Get the properties used in the test. + * @return the properties. + */ + public Properties getProperties() { + return props; + } + + /** + * Set the properties to be used in the test. + * @param p the properties. + * This is a copy of the projects ant properties. + */ + public void setProperties(Hashtable p) { + props = new Properties(); + for (Enumeration e = p.keys(); e.hasMoreElements();) { + Object key = e.nextElement(); + props.put(key, p.get(key)); + } + } + + /** + * Check if this test should run based on the if and unless + * attributes. + * @param p the project to use to check if the if and unless + * properties exist in. + * @return true if this test or testsuite should be run. + */ + public boolean shouldRun(Project p) { + PropertyHelper ph = PropertyHelper.getPropertyHelper(p); + return ph.testIfCondition(getIfCondition()) + && ph.testUnlessCondition(getUnlessCondition()); + } + + /** + * Get the formatters set for this test. + * @return the formatters as an array. + */ + public FormatterElement[] getFormatters() { + FormatterElement[] fes = new FormatterElement[formatters.size()]; + formatters.copyInto(fes); + return fes; + } + + /** + * Convenient method to add formatters to a vector + */ + void addFormattersTo(Vector v) { + final int count = formatters.size(); + for (int i = 0; i < count; i++) { + v.addElement(formatters.elementAt(i)); + } + } + + /** + * @since Ant 1.5 + * @return a clone of this test. + */ + @Override + public Object clone() { + try { + JUnitTest t = (JUnitTest) super.clone(); + t.props = props == null ? null : (Properties) props.clone(); + t.formatters = (Vector) formatters.clone(); + return t; + } catch (CloneNotSupportedException e) { + // plain impossible + return this; + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java new file mode 100644 index 00000000..a457375e --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java @@ -0,0 +1,1297 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestFailure; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.framework.TestSuite; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Permissions; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.StringUtils; +import org.apache.tools.ant.util.TeeOutputStream; + +/** + * Simple Testrunner for JUnit that runs all tests of a testsuite. + * + * <p>This TestRunner expects a name of a TestCase class as its + * argument. If this class provides a static suite() method it will be + * called and the resulting Test will be run. So, the signature should be + * <pre><code> + * public static junit.framework.Test suite() + * </code></pre> + * + * <p> If no such method exists, all public methods starting with + * "test" and taking no argument will be run. + * + * <p> Summary output is generated at the end. + * + * @since Ant 1.2 + */ + +public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestRunnerMirror { + + /** + * Holds the registered formatters. + */ + private final Vector<JUnitTaskMirror.JUnitResultFormatterMirror> formatters = new Vector(); + + /** + * Collects TestResults. + */ + private IgnoredTestResult res; + + /** + * Do we filter junit.*.* stack frames out of failure and error exceptions. + */ + private static boolean filtertrace = true; + + /** + * Do we send output to System.out/.err in addition to the formatters? + */ + private boolean showOutput = false; + + private boolean outputToFormatters = true; + + /** + * The permissions set for the test to run. + */ + private Permissions perm = null; + + private static final String JUNIT_4_TEST_ADAPTER + = "junit.framework.JUnit4TestAdapter"; + + private static final String[] DEFAULT_TRACE_FILTERS = new String[] { + "junit.framework.TestCase", + "junit.framework.TestResult", + "junit.framework.TestSuite", + "junit.framework.Assert.", // don't filter AssertionFailure + "junit.swingui.TestRunner", + "junit.awtui.TestRunner", + "junit.textui.TestRunner", + "java.lang.reflect.Method.invoke(", + "sun.reflect.", + "org.apache.tools.ant.", + // JUnit 4 support: + "org.junit.", + "junit.framework.JUnit4TestAdapter", + " more", + }; + + + /** + * Do we stop on errors. + */ + private boolean haltOnError = false; + + /** + * Do we stop on test failures. + */ + private boolean haltOnFailure = false; + + /** + * Returncode + */ + private int retCode = SUCCESS; + + /** + * The TestSuite we are currently running. + */ + private final JUnitTest junitTest; + + /** output written during the test */ + private PrintStream systemError; + + /** Error output during the test */ + private PrintStream systemOut; + + /** is this runner running in forked mode? */ + private boolean forked = false; + + /** Running more than one test suite? */ + private static boolean multipleTests = false; + + /** ClassLoader passed in in non-forked mode. */ + private final ClassLoader loader; + + /** Do we print TestListener events? */ + private boolean logTestListenerEvents = false; + + /** Turned on if we are using JUnit 4 for this test suite. see #38811 */ + private boolean junit4; + + /** + * The file used to indicate that the build crashed. + * File will be empty in case the build did not crash. + */ + private static String crashFile = null; + + /** Names of test methods to execute */ + private String[] methods = null; + + /** + * Constructor for fork=true or when the user hasn't specified a + * classpath. + * @param test the test to run. + * @param haltOnError whether to stop the run if an error is found. + * @param filtertrace whether to filter junit.*.* stack frames out of exceptions + * @param haltOnFailure whether to stop the run if failure is found. + */ + public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, + final boolean filtertrace, final boolean haltOnFailure) { + this(test, haltOnError, filtertrace, haltOnFailure, false); + } + + /** + * Constructor for fork=true or when the user hasn't specified a + * classpath. + * @param test the test to run. + * @param haltOnError whether to stop the run if an error is found. + * @param filtertrace whether to filter junit.*.* stack frames out of exceptions + * @param haltOnFailure whether to stop the run if failure is found. + * @param showOutput whether to send output to System.out/.err as well as formatters. + */ + public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, + final boolean filtertrace, final boolean haltOnFailure, + final boolean showOutput) { + this(test, haltOnError, filtertrace, haltOnFailure, showOutput, false); + } + + /** + * Constructor for fork=true or when the user hasn't specified a + * classpath. + * @param test the test to run. + * @param haltOnError whether to stop the run if an error is found. + * @param filtertrace whether to filter junit.*.* stack frames out of exceptions + * @param haltOnFailure whether to stop the run if failure is found. + * @param showOutput whether to send output to System.out/.err as well as formatters. + * @param logTestListenerEvents whether to print TestListener events. + * @since Ant 1.7 + */ + public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, + final boolean filtertrace, final boolean haltOnFailure, + final boolean showOutput, final boolean logTestListenerEvents) { + this(test, null, haltOnError, filtertrace, haltOnFailure, showOutput, + logTestListenerEvents, null); + } + + /** + * Constructor for fork=true or when the user hasn't specified a + * classpath. + * @param test the test to run. + * @param methods names of methods of the test to be executed. + * @param haltOnError whether to stop the run if an error is found. + * @param filtertrace whether to filter junit.*.* stack frames out of exceptions + * @param haltOnFailure whether to stop the run if failure is found. + * @param showOutput whether to send output to System.out/.err as well as formatters. + * @param logTestListenerEvents whether to print TestListener events. + * @since 1.8.2 + */ + public JUnitTestRunner(final JUnitTest test, final String[] methods, final boolean haltOnError, + final boolean filtertrace, final boolean haltOnFailure, + final boolean showOutput, final boolean logTestListenerEvents) { + this(test, methods, haltOnError, filtertrace, haltOnFailure, showOutput, + logTestListenerEvents, null); + } + + /** + * Constructor to use when the user has specified a classpath. + * @param test the test to run. + * @param haltOnError whether to stop the run if an error is found. + * @param filtertrace whether to filter junit.*.* stack frames out of exceptions + * @param haltOnFailure whether to stop the run if failure is found. + * @param loader the classloader to use running the test. + */ + public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, + final boolean filtertrace, final boolean haltOnFailure, + final ClassLoader loader) { + this(test, haltOnError, filtertrace, haltOnFailure, false, loader); + } + + /** + * Constructor to use when the user has specified a classpath. + * @param test the test to run. + * @param haltOnError whether to stop the run if an error is found. + * @param filtertrace whether to filter junit.*.* stack frames out of exceptions + * @param haltOnFailure whether to stop the run if failure is found. + * @param showOutput whether to send output to System.out/.err as well as formatters. + * @param loader the classloader to use running the test. + */ + public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, + final boolean filtertrace, final boolean haltOnFailure, + final boolean showOutput, final ClassLoader loader) { + this(test, haltOnError, filtertrace, haltOnFailure, showOutput, + false, loader); + } + + /** + * Constructor to use when the user has specified a classpath. + * @param test the test to run. + * @param haltOnError whether to stop the run if an error is found. + * @param filtertrace whether to filter junit.*.* stack frames out of exceptions + * @param haltOnFailure whether to stop the run if failure is found. + * @param showOutput whether to send output to System.out/.err as well as formatters. + * @param logTestListenerEvents whether to print TestListener events. + * @param loader the classloader to use running the test. + * @since Ant 1.7 + */ + public JUnitTestRunner(final JUnitTest test, final boolean haltOnError, + final boolean filtertrace, final boolean haltOnFailure, + final boolean showOutput, final boolean logTestListenerEvents, + final ClassLoader loader) { + this(test, null, haltOnError, filtertrace, haltOnFailure, showOutput, + logTestListenerEvents, loader); + } + + + /** + * Constructor to use when the user has specified a classpath. + * @since 1.8.2 + */ + public JUnitTestRunner(final JUnitTest test, final String[] methods, final boolean haltOnError, + final boolean filtertrace, final boolean haltOnFailure, + final boolean showOutput, final boolean logTestListenerEvents, + final ClassLoader loader) { + super(); + JUnitTestRunner.filtertrace = filtertrace; // TODO clumsy, should use instance field somehow + this.junitTest = test; + this.haltOnError = haltOnError; + this.haltOnFailure = haltOnFailure; + this.showOutput = showOutput; + this.logTestListenerEvents = logTestListenerEvents; + this.methods = methods != null ? (String[]) methods.clone() : null; + this.loader = loader; + } + + private PrintStream savedOut = null; + private PrintStream savedErr = null; + + private PrintStream createEmptyStream() { + return new PrintStream( + new OutputStream() { + @Override + public void write(final int b) { + } + }); + } + + private PrintStream createTeePrint(final PrintStream ps1, final PrintStream ps2) { + return new PrintStream(new TeeOutputStream(ps1, ps2)); + } + + private void setupIOStreams(final ByteArrayOutputStream o, + final ByteArrayOutputStream e) { + systemOut = new PrintStream(o); + systemError = new PrintStream(e); + + if (forked) { + if (!outputToFormatters) { + if (!showOutput) { + savedOut = System.out; + savedErr = System.err; + System.setOut(createEmptyStream()); + System.setErr(createEmptyStream()); + } + } else { + savedOut = System.out; + savedErr = System.err; + if (!showOutput) { + System.setOut(systemOut); + System.setErr(systemError); + } else { + System.setOut(createTeePrint(savedOut, systemOut)); + System.setErr(createTeePrint(savedErr, systemError)); + } + perm = null; + } + } else { + if (perm != null) { + perm.setSecurityManager(); + } + } + } + + /** + * Run the test. + */ + public void run() { + res = new IgnoredTestResult(); + res.addListener(wrapListener(this)); + final int size = formatters.size(); + for (int i = 0; i < size; i++) { + res.addListener(wrapListener((TestListener) formatters.elementAt(i))); + } + + final ByteArrayOutputStream errStrm = new ByteArrayOutputStream(); + final ByteArrayOutputStream outStrm = new ByteArrayOutputStream(); + + setupIOStreams(outStrm, errStrm); + + Test suite = null; + Throwable exception = null; + boolean startTestSuiteSuccess = false; + + try { + + try { + Class testClass = null; + if (loader == null) { + testClass = Class.forName(junitTest.getName()); + } else { + testClass = Class.forName(junitTest.getName(), true, + loader); + } + + final boolean testMethodsSpecified = (methods != null); + + // check for a static suite method first, even when using + // JUnit 4 + Method suiteMethod = null; + if (!testMethodsSpecified) { + try { + // check if there is a suite method + suiteMethod = testClass.getMethod("suite", new Class[0]); + } catch (final NoSuchMethodException e) { + // no appropriate suite method found. We don't report any + // error here since it might be perfectly normal. + } + } + + if (suiteMethod != null) { + // if there is a suite method available, then try + // to extract the suite from it. If there is an error + // here it will be caught below and reported. + suite = (Test) suiteMethod.invoke(null, new Object[0]); + + } else { + Class junit4TestAdapterClass = null; + Class junit4TestAdapterCacheClass = null; + boolean useSingleMethodAdapter = false; + + if (junit.framework.TestCase.class.isAssignableFrom(testClass)) { + // Do not use JUnit 4 API for running JUnit 3.x + // tests - it is not able to run individual test + // methods. + // + // Technical details: + // org.junit.runner.Request.method(Class, String).getRunner() + // would return a runner which always executes all + // test methods. The reason is that the Runner would be + // an instance of class + // org.junit.internal.runners.OldTestClassRunner + // that does not implement interface Filterable - so it + // is unable to filter out test methods not matching + // the requested name. + } else { + // Check for JDK 5 first. Will *not* help on JDK 1.4 + // if only junit-4.0.jar in CP because in that case + // linkage of whole task will already have failed! But + // will help if CP has junit-3.8.2.jar:junit-4.0.jar. + + // In that case first C.fN will fail with CNFE and we + // will avoid UnsupportedClassVersionError. + + try { + Class.forName("java.lang.annotation.Annotation"); + junit4TestAdapterCacheClass = Class.forName("org.apache.tools.ant.taskdefs.optional.junit.CustomJUnit4TestAdapterCache"); + if (loader == null) { + junit4TestAdapterClass = + Class.forName(JUNIT_4_TEST_ADAPTER); + if (testMethodsSpecified) { + /* + * We cannot try to load the JUnit4TestAdapter + * before trying to load JUnit4TestMethodAdapter + * because it might fail with + * NoClassDefFoundException, instead of plain + * ClassNotFoundException. + */ + junit4TestAdapterClass = Class.forName( + "org.apache.tools.ant.taskdefs.optional.junit.JUnit4TestMethodAdapter"); + useSingleMethodAdapter = true; + } + } else { + junit4TestAdapterClass = + Class.forName(JUNIT_4_TEST_ADAPTER, + true, loader); + if (testMethodsSpecified) { + junit4TestAdapterClass = + Class.forName( + "org.apache.tools.ant.taskdefs.optional.junit.JUnit4TestMethodAdapter", + true, loader); + useSingleMethodAdapter = true; + } + } + } catch (final ClassNotFoundException e) { + // OK, fall back to JUnit 3. + } + } + junit4 = junit4TestAdapterClass != null; + + if (junitTest.isSkipNonTests()) { + if (!containsTests(testClass, junit4)) { + return; + } + } + + + if (junit4) { + // Let's use it! + Class[] formalParams; + Object[] actualParams; + if (useSingleMethodAdapter) { + formalParams = new Class[] {Class.class, String[].class}; + actualParams = new Object[] {testClass, methods}; + } else { + formalParams = new Class[] {Class.class, Class.forName("junit.framework.JUnit4TestAdapterCache")}; + actualParams = new Object[] {testClass, junit4TestAdapterCacheClass.getMethod("getInstance").invoke(null)}; + } + suite = + (Test) junit4TestAdapterClass + .getConstructor(formalParams). + newInstance(actualParams); + } else { + // Use JUnit 3. + + // try to extract a test suite automatically this + // will generate warnings if the class is no + // suitable Test + if (!testMethodsSpecified) { + suite = new TestSuite(testClass); + } else if (methods.length == 1) { + suite = TestSuite.createTest(testClass, methods[0]); + } else { + final TestSuite testSuite = new TestSuite(testClass.getName()); + for (int i = 0; i < methods.length; i++) { + testSuite.addTest( + TestSuite.createTest(testClass, methods[i])); + } + suite = testSuite; + } + } + + } + + } catch (final Throwable e) { + retCode = ERRORS; + exception = e; + } + + final long start = System.currentTimeMillis(); + + fireStartTestSuite(); + startTestSuiteSuccess = true; + if (exception != null) { // had an exception constructing suite + final int formatterSize = formatters.size(); + for (int i = 0; i < formatterSize; i++) { + ((TestListener) formatters.elementAt(i)) + .addError(null, exception); + } + junitTest.setCounts(1, 0, 1, 0); + junitTest.setRunTime(0); + } else { + try { + logTestListenerEvent("tests to run: " + suite.countTestCases()); + suite.run(res); + } finally { + if (junit4 || + suite.getClass().getName().equals(JUNIT_4_TEST_ADAPTER)) { + final int[] cnts = findJUnit4FailureErrorCount(res); + junitTest.setCounts(res.runCount() + res.ignoredCount(), cnts[0], cnts[1], res.ignoredCount() + res.skippedCount()); + } else { + junitTest.setCounts(res.runCount() + res.ignoredCount(), res.failureCount(), + res.errorCount(), res.ignoredCount() + res.skippedCount()); + } + junitTest.setRunTime(System.currentTimeMillis() - start); + } + } + } finally { + if (perm != null) { + perm.restoreSecurityManager(); + } + if (savedOut != null) { + System.setOut(savedOut); + } + if (savedErr != null) { + System.setErr(savedErr); + } + + systemError.close(); + systemError = null; + systemOut.close(); + systemOut = null; + if (startTestSuiteSuccess) { + String out, err; + try { + out = new String(outStrm.toByteArray()); + } catch (final OutOfMemoryError ex) { + out = "out of memory on output stream"; + } + try { + err = new String(errStrm.toByteArray()); + } catch (final OutOfMemoryError ex) { + err = "out of memory on error stream"; + } + sendOutAndErr(out, err); + } + } + fireEndTestSuite(); + + // junitTest has the correct counts for JUnit4, while res doesn't + if (retCode != SUCCESS || junitTest.errorCount() != 0) { + retCode = ERRORS; + } else if (junitTest.failureCount() != 0) { + retCode = FAILURES; + } + } + + private static boolean containsTests(final Class<?> testClass, final boolean isJUnit4) { + Class testAnnotation = null; + Class suiteAnnotation = null; + Class runWithAnnotation = null; + + try { + testAnnotation = Class.forName("org.junit.Test"); + } catch (final ClassNotFoundException e) { + if (isJUnit4) { + // odd - we think we're JUnit4 but don't support the test annotation. We therefore can't have any tests! + return false; + } + // else... we're a JUnit3 test and don't need the annotation + } + + try { + suiteAnnotation = Class.forName("org.junit.Suite.SuiteClasses"); + } catch(final ClassNotFoundException ex) { + // ignore - we don't have this annotation so make sure we don't check for it + } + try { + runWithAnnotation = Class.forName("org.junit.runner.RunWith"); + } catch(final ClassNotFoundException ex) { + // also ignore as this annotation doesn't exist so tests can't use it + } + + + if (!isJUnit4 && !TestCase.class.isAssignableFrom(testClass)) { + //a test we think is JUnit3 but does not extend TestCase. Can't really be a test. + return false; + } + + // check if we have any inner classes that contain suitable test methods + for (final Class<?> innerClass : testClass.getDeclaredClasses()) { + if (containsTests(innerClass, isJUnit4) || containsTests(innerClass, !isJUnit4)) { + return true; + } + } + + if (Modifier.isAbstract(testClass.getModifiers()) || Modifier.isInterface(testClass.getModifiers())) { + // can't instantiate class and no inner classes are tests either + return false; + } + + if (isJUnit4) { + if (suiteAnnotation != null && testClass.getAnnotation(suiteAnnotation) != null) { + // class is marked as a suite. Let JUnit try and work its magic on it. + return true; + } + if (runWithAnnotation != null && testClass.getAnnotation(runWithAnnotation) != null) { + /* Class is marked with @RunWith. If this class is badly written (no test methods, multiple + * constructors, private constructor etc) then the class is automatically run and fails in the + * IDEs I've tried... so I'm happy handing the class to JUnit to try and run, and let JUnit + * report a failure if a bad test case is provided. Trying to do anything else is likely to + * result in us filtering out cases that could be valid for future versions of JUnit so would + * just increase future maintenance work. + */ + return true; + } + } + + for (final Method m : testClass.getMethods()) { + if (isJUnit4) { + // check if suspected JUnit4 classes have methods with @Test annotation + if (m.getAnnotation(testAnnotation) != null) { + return true; + } + } else { + // check if JUnit3 class have public or protected no-args methods starting with names starting with test + if (m.getName().startsWith("test") && m.getParameterTypes().length == 0 + && (Modifier.isProtected(m.getModifiers()) || Modifier.isPublic(m.getModifiers()))) { + return true; + } + } + // check if JUnit3 or JUnit4 test have a public or protected, static, + // no-args 'suite' method + if (m.getName().equals("suite") && m.getParameterTypes().length == 0 + && (Modifier.isProtected(m.getModifiers()) || Modifier.isPublic(m.getModifiers())) + && Modifier.isStatic(m.getModifiers())) { + return true; + } + } + + // no test methods found + return false; + } + + /** + * Returns what System.exit() would return in the standalone version. + * + * @return 2 if errors occurred, 1 if tests failed else 0. + */ + public int getRetCode() { + return retCode; + } + + /** + * Interface TestListener. + * + * <p>A new Test is started. + * @param t the test. + */ + public void startTest(final Test t) { + final String testName = JUnitVersionHelper.getTestCaseName(t); + logTestListenerEvent("startTest(" + testName + ")"); + } + + /** + * Interface TestListener. + * + * <p>A Test is finished. + * @param test the test. + */ + public void endTest(final Test test) { + final String testName = JUnitVersionHelper.getTestCaseName(test); + logTestListenerEvent("endTest(" + testName + ")"); + } + + private void logTestListenerEvent(String msg) { + if (logTestListenerEvents) { + final PrintStream out = savedOut != null ? savedOut : System.out; + out.flush(); + if (msg == null) { + msg = "null"; + } + final StringTokenizer msgLines = new StringTokenizer(msg, "\r\n", false); + while (msgLines.hasMoreTokens()) { + out.println(JUnitTask.TESTLISTENER_PREFIX + + msgLines.nextToken()); + } + out.flush(); + } + } + + /** + * Interface TestListener for JUnit <= 3.4. + * + * <p>A Test failed. + * @param test the test. + * @param t the exception thrown by the test. + */ + public void addFailure(final Test test, final Throwable t) { + final String testName = JUnitVersionHelper.getTestCaseName(test); + logTestListenerEvent("addFailure(" + testName + ", " + t.getMessage() + ")"); + if (haltOnFailure) { + res.stop(); + } + } + + /** + * Interface TestListener for JUnit > 3.4. + * + * <p>A Test failed. + * @param test the test. + * @param t the assertion thrown by the test. + */ + public void addFailure(final Test test, final AssertionFailedError t) { + addFailure(test, (Throwable) t); + } + + /** + * Interface TestListener. + * + * <p>An error occurred while running the test. + * @param test the test. + * @param t the error thrown by the test. + */ + public void addError(final Test test, final Throwable t) { + final String testName = JUnitVersionHelper.getTestCaseName(test); + logTestListenerEvent("addError(" + testName + ", " + t.getMessage() + ")"); + if (haltOnError) { + res.stop(); + } + } + + /** + * Permissions for the test run. + * @since Ant 1.6 + * @param permissions the permissions to use. + */ + public void setPermissions(final Permissions permissions) { + perm = permissions; + } + + /** + * Handle a string destined for standard output. + * @param output the string to output + */ + public void handleOutput(final String output) { + if (!logTestListenerEvents && output.startsWith(JUnitTask.TESTLISTENER_PREFIX)) { + // ignore + } else if (systemOut != null) { + systemOut.print(output); + } + } + + /** + * Handle input. + * @param buffer not used. + * @param offset not used. + * @param length not used. + * @return -1 always. + * @throws IOException never. + * @see org.apache.tools.ant.Task#handleInput(byte[], int, int) + * + * @since Ant 1.6 + */ + public int handleInput(final byte[] buffer, final int offset, final int length) + throws IOException { + return -1; + } + + /** {@inheritDoc}. */ + public void handleErrorOutput(final String output) { + if (systemError != null) { + systemError.print(output); + } + } + + /** {@inheritDoc}. */ + public void handleFlush(final String output) { + if (systemOut != null) { + systemOut.print(output); + } + } + + /** {@inheritDoc}. */ + public void handleErrorFlush(final String output) { + if (systemError != null) { + systemError.print(output); + } + } + + private void sendOutAndErr(final String out, final String err) { + final int size = formatters.size(); + for (int i = 0; i < size; i++) { + final JUnitResultFormatter formatter = + ((JUnitResultFormatter) formatters.elementAt(i)); + + formatter.setSystemOutput(out); + formatter.setSystemError(err); + } + } + + private void fireStartTestSuite() { + final int size = formatters.size(); + for (int i = 0; i < size; i++) { + ((JUnitResultFormatter) formatters.elementAt(i)) + .startTestSuite(junitTest); + } + } + + private void fireEndTestSuite() { + final int size = formatters.size(); + for (int i = 0; i < size; i++) { + ((JUnitResultFormatter) formatters.elementAt(i)) + .endTestSuite(junitTest); + } + } + + /** + * Add a formatter. + * @param f the formatter to add. + */ + public void addFormatter(final JUnitResultFormatter f) { + formatters.addElement(f); + } + + /** {@inheritDoc}. */ + public void addFormatter(final JUnitTaskMirror.JUnitResultFormatterMirror f) { + formatters.addElement(f); + } + + /** + * Entry point for standalone (forked) mode. + * + * Parameters: testcaseclassname plus parameters in the format + * key=value, none of which is required. + * + * <table cols="4" border="1"> + * <tr><th>key</th><th>description</th><th>default value</th></tr> + * + * <tr><td>haltOnError</td><td>halt test on + * errors?</td><td>false</td></tr> + * + * <tr><td>haltOnFailure</td><td>halt test on + * failures?</td><td>false</td></tr> + * + * <tr><td>formatter</td><td>A JUnitResultFormatter given as + * classname,filename. If filename is omitted, System.out is + * assumed.</td><td>none</td></tr> + * + * <tr><td>showoutput</td><td>send output to System.err/.out as + * well as to the formatters?</td><td>false</td></tr> + * + * <tr><td>logtestlistenerevents</td><td>log TestListener events to + * System.out.</td><td>false</td></tr> + * + * <tr><td>methods</td><td>Comma-separated list of names of individual + * test methods to execute. + * </td><td>null</td></tr> + * + * </table> + * @param args the command line arguments. + * @throws IOException on error. + */ + public static void main(final String[] args) throws IOException { + String[] methods = null; + boolean haltError = false; + boolean haltFail = false; + boolean stackfilter = true; + final Properties props = new Properties(); + boolean showOut = false; + boolean outputToFormat = true; + boolean logFailedTests = true; + boolean logTestListenerEvents = false; + boolean skipNonTests = false; + int antThreadID = 0; /* Ant id of thread running this unit test, 0 in single-threaded mode */ + + if (args.length == 0) { + System.err.println("required argument TestClassName missing"); + System.exit(ERRORS); + } + + if (args[0].startsWith(Constants.TESTSFILE)) { + multipleTests = true; + args[0] = args[0].substring(Constants.TESTSFILE.length()); + } + + for (int i = 1; i < args.length; i++) { + if (args[i].startsWith(Constants.METHOD_NAMES)) { + try { + final String methodsList = args[i].substring(Constants.METHOD_NAMES.length()); + methods = JUnitTest.parseTestMethodNamesList(methodsList); + } catch (final IllegalArgumentException ex) { + System.err.println("Invalid specification of test method names: " + args[i]); + System.exit(ERRORS); + } + } else if (args[i].startsWith(Constants.HALT_ON_ERROR)) { + haltError = Project.toBoolean(args[i].substring(Constants.HALT_ON_ERROR.length())); + } else if (args[i].startsWith(Constants.HALT_ON_FAILURE)) { + haltFail = Project.toBoolean(args[i].substring(Constants.HALT_ON_FAILURE.length())); + } else if (args[i].startsWith(Constants.FILTERTRACE)) { + stackfilter = Project.toBoolean(args[i].substring(Constants.FILTERTRACE.length())); + } else if (args[i].startsWith(Constants.CRASHFILE)) { + crashFile = args[i].substring(Constants.CRASHFILE.length()); + registerTestCase(Constants.BEFORE_FIRST_TEST); + } else if (args[i].startsWith(Constants.FORMATTER)) { + try { + createAndStoreFormatter(args[i].substring(Constants.FORMATTER.length())); + } catch (final BuildException be) { + System.err.println(be.getMessage()); + System.exit(ERRORS); + } + } else if (args[i].startsWith(Constants.PROPSFILE)) { + final FileInputStream in = new FileInputStream(args[i] + .substring(Constants.PROPSFILE.length())); + props.load(in); + in.close(); + } else if (args[i].startsWith(Constants.SHOWOUTPUT)) { + showOut = Project.toBoolean(args[i].substring(Constants.SHOWOUTPUT.length())); + } else if (args[i].startsWith(Constants.LOGTESTLISTENEREVENTS)) { + logTestListenerEvents = Project.toBoolean( + args[i].substring(Constants.LOGTESTLISTENEREVENTS.length())); + } else if (args[i].startsWith(Constants.OUTPUT_TO_FORMATTERS)) { + outputToFormat = Project.toBoolean( + args[i].substring(Constants.OUTPUT_TO_FORMATTERS.length())); + } else if (args[i].startsWith(Constants.LOG_FAILED_TESTS)) { + logFailedTests = Project.toBoolean( + args[i].substring(Constants.LOG_FAILED_TESTS.length())); + } else if (args[i].startsWith(Constants.SKIP_NON_TESTS)) { + skipNonTests = Project.toBoolean( + args[i].substring(Constants.SKIP_NON_TESTS.length())); + } else if (args[i].startsWith(Constants.THREADID)) { + antThreadID = Integer.parseInt(args[i].substring(Constants.THREADID.length())); + } + } + + // Add/overlay system properties on the properties from the Ant project + final Hashtable p = System.getProperties(); + for (final Enumeration e = p.keys(); e.hasMoreElements();) { + final Object key = e.nextElement(); + props.put(key, p.get(key)); + } + + int returnCode = SUCCESS; + if (multipleTests) { + try { + final java.io.BufferedReader reader = + new java.io.BufferedReader(new java.io.FileReader(args[0])); + String testCaseName; + String[] testMethodNames; + int code = 0; + boolean errorOccurred = false; + boolean failureOccurred = false; + String line = null; + while ((line = reader.readLine()) != null) { + final StringTokenizer st = new StringTokenizer(line, ","); + final String testListSpec = st.nextToken(); + final int colonIndex = testListSpec.indexOf(':'); + if (colonIndex == -1) { + testCaseName = testListSpec; + testMethodNames = null; + } else { + testCaseName = testListSpec.substring(0, colonIndex); + testMethodNames = JUnitTest.parseTestMethodNamesList( + testListSpec + .substring(colonIndex + 1) + .replace('+', ',')); + } + final JUnitTest t = new JUnitTest(testCaseName); + t.setTodir(new File(st.nextToken())); + t.setOutfile(st.nextToken()); + t.setProperties(props); + t.setSkipNonTests(skipNonTests); + t.setThread(antThreadID); + code = launch(t, testMethodNames, haltError, stackfilter, haltFail, + showOut, outputToFormat, + logTestListenerEvents); + errorOccurred = (code == ERRORS); + failureOccurred = (code != SUCCESS); + if (errorOccurred || failureOccurred) { + if ((errorOccurred && haltError) + || (failureOccurred && haltFail)) { + registerNonCrash(); + System.exit(code); + } else { + if (code > returnCode) { + returnCode = code; + } + if (logFailedTests) { + System.out.println("TEST " + t.getName() + + " FAILED"); + } + } + } + } + } catch (final IOException e) { + e.printStackTrace(); + } + } else { + final JUnitTest t = new JUnitTest(args[0]); + t.setThread(antThreadID); + t.setProperties(props); + t.setSkipNonTests(skipNonTests); + returnCode = launch( + t, methods, haltError, stackfilter, haltFail, + showOut, outputToFormat, logTestListenerEvents); + } + + registerNonCrash(); + System.exit(returnCode); + } + + private static Vector fromCmdLine = new Vector(); + + private static void transferFormatters(final JUnitTestRunner runner, + final JUnitTest test) { + runner.addFormatter(new JUnitResultFormatter() { + + public void startTestSuite(final JUnitTest suite) throws BuildException { + } + + public void endTestSuite(final JUnitTest suite) throws BuildException { + } + + public void setOutput(final OutputStream out) { + } + + public void setSystemOutput(final String out) { + } + + public void setSystemError(final String err) { + } + + public void addError(final Test arg0, final Throwable arg1) { + } + + public void addFailure(final Test arg0, final AssertionFailedError arg1) { + } + + public void endTest(final Test arg0) { + } + + public void startTest(final Test arg0) { + registerTestCase(JUnitVersionHelper.getTestCaseName(arg0)); + } + }); + final int size = fromCmdLine.size(); + for (int i = 0; i < size; i++) { + final FormatterElement fe = (FormatterElement) fromCmdLine.elementAt(i); + if (multipleTests && fe.getUseFile()) { + final File destFile = + new File(test.getTodir(), + test.getOutfile() + fe.getExtension()); + fe.setOutfile(destFile); + } + runner.addFormatter((JUnitResultFormatter) fe.createFormatter()); + } + } + + /** + * Line format is: formatter=<classname>(,<pathname>)? + */ + private static void createAndStoreFormatter(final String line) + throws BuildException { + final FormatterElement fe = new FormatterElement(); + final int pos = line.indexOf(','); + if (pos == -1) { + fe.setClassname(line); + fe.setUseFile(false); + } else { + fe.setClassname(line.substring(0, pos)); + fe.setUseFile(true); + if (!multipleTests) { + fe.setOutfile(new File(line.substring(pos + 1))); + } else { + final int fName = line.indexOf(IGNORED_FILE_NAME); + if (fName > -1) { + fe.setExtension(line + .substring(fName + + IGNORED_FILE_NAME.length())); + } + } + } + fromCmdLine.addElement(fe); + } + + /** + * Returns a filtered stack trace. + * This is ripped out of junit.runner.BaseTestRunner. + * @param t the exception to filter. + * @return the filtered stack trace. + */ + public static String getFilteredTrace(final Throwable t) { + final String trace = StringUtils.getStackTrace(t); + return JUnitTestRunner.filterStack(trace); + } + + /** + * Filters stack frames from internal JUnit and Ant classes + * @param stack the stack trace to filter. + * @return the filtered stack. + */ + public static String filterStack(final String stack) { + if (!filtertrace) { + return stack; + } + final StringWriter sw = new StringWriter(); + final BufferedWriter pw = new BufferedWriter(sw); + final StringReader sr = new StringReader(stack); + final BufferedReader br = new BufferedReader(sr); + + String line; + try { + boolean firstLine = true; + while ((line = br.readLine()) != null) { + if (firstLine || !filterLine(line)) { + pw.write(line); + pw.newLine(); + } + firstLine = false; + } + } catch (final Exception e) { + return stack; // return the stack unfiltered + } finally { + FileUtils.close(pw); + } + return sw.toString(); + } + + private static boolean filterLine(final String line) { + for (int i = 0; i < DEFAULT_TRACE_FILTERS.length; i++) { + if (line.indexOf(DEFAULT_TRACE_FILTERS[i]) != -1) { + return true; + } + } + return false; + } + + /** + * @since Ant 1.6.2 + */ + private static int launch(final JUnitTest t, final String[] methods, final boolean haltError, + final boolean stackfilter, final boolean haltFail, + final boolean showOut, final boolean outputToFormat, + final boolean logTestListenerEvents) { + final JUnitTestRunner runner = + new JUnitTestRunner(t, methods, haltError, stackfilter, haltFail, showOut, + logTestListenerEvents, null); + runner.forked = true; + runner.outputToFormatters = outputToFormat; + transferFormatters(runner, t); + + runner.run(); + return runner.getRetCode(); + } + + /** + * @since Ant 1.7 + */ + private static void registerNonCrash() + throws IOException { + if (crashFile != null) { + FileWriter out = null; + try { + out = new FileWriter(crashFile); + out.write(Constants.TERMINATED_SUCCESSFULLY + "\n"); + out.flush(); + } finally { + FileUtils.close(out); + } + } + } + + private static void registerTestCase(final String testCase) { + if (crashFile != null) { + try { + FileWriter out = null; + try { + out = new FileWriter(crashFile); + out.write(testCase + "\n"); + out.flush(); + } finally { + FileUtils.close(out); + } + } catch (final IOException e) { + // ignored. + } + } + } + + /** + * Modifies a TestListener when running JUnit 4: treats AssertionFailedError + * as a failure not an error. + * + * @since Ant 1.7 + */ + private TestListenerWrapper wrapListener(final TestListener testListener) { + return new TestListenerWrapper(testListener) { + @Override + public void addError(final Test test, final Throwable t) { + if (junit4 && t instanceof AssertionFailedError) { + // JUnit 4 does not distinguish between errors and failures + // even in the JUnit 3 adapter. + // So we need to help it a bit to retain compatibility for JUnit 3 tests. + testListener.addFailure(test, (AssertionFailedError) t); + } else if (junit4 && t instanceof AssertionError) { + // Not strictly necessary but probably desirable. + // JUnit 4-specific test GUIs will show just "failures". + // But Ant's output shows "failures" vs. "errors". + // We would prefer to show "failure" for things that logically are. + final String msg = t.getMessage(); + final AssertionFailedError failure = msg != null + ? new AssertionFailedError(msg) : new AssertionFailedError(); + failure.setStackTrace(t.getStackTrace()); + testListener.addFailure(test, failure); + } else { + testListener.addError(test, t); + } + } + @Override + public void addFailure(final Test test, final AssertionFailedError t) { + testListener.addFailure(test, t); + } + public void addFailure(final Test test, final Throwable t) { // pre-3.4 + if (t instanceof AssertionFailedError) { + testListener.addFailure(test, (AssertionFailedError) t); + } else { + testListener.addError(test, t); + } + } + @Override + public void endTest(final Test test) { + testListener.endTest(test); + } + @Override + public void startTest(final Test test) { + testListener.startTest(test); + } + }; + } + + /** + * Use instead of TestResult.get{Failure,Error}Count on JUnit 4, + * since the adapter claims that all failures are errors. + * @since Ant 1.7 + */ + private int[] findJUnit4FailureErrorCount(final TestResult result) { + int failures = 0; + int errors = 0; + Enumeration e = result.failures(); + while (e.hasMoreElements()) { + e.nextElement(); + failures++; + } + e = result.errors(); + while (e.hasMoreElements()) { + final Throwable t = ((TestFailure) e.nextElement()).thrownException(); + if (t instanceof AssertionFailedError + || t instanceof AssertionError) { + failures++; + } else { + errors++; + } + } + return new int[] {failures, errors}; + } + +} // JUnitTestRunner diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java new file mode 100644 index 00000000..9a21caee --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import junit.framework.Test; +import junit.framework.TestCase; + +/** + * Work around for some changes to the public JUnit API between + * different JUnit releases. + * @since Ant 1.7 + */ +// CheckStyle:HideUtilityClassConstructorCheck OFF (bc) +public class JUnitVersionHelper { + + private static Method testCaseName = null; + + /** + * Name of the JUnit4 class we look for. + * {@value} + * @since Ant 1.7.1 + */ + public static final String JUNIT_FRAMEWORK_JUNIT4_TEST_CASE_FACADE + = "junit.framework.JUnit4TestCaseFacade"; + private static final String UNKNOWN_TEST_CASE_NAME = "unknown"; + + static { + try { + testCaseName = TestCase.class.getMethod("getName", new Class[0]); + } catch (NoSuchMethodException e) { + // pre JUnit 3.7 + try { + testCaseName = TestCase.class.getMethod("name", new Class[0]); + } catch (NoSuchMethodException ignored) { + // ignore + } + } + } + + /** + * JUnit 3.7 introduces TestCase.getName() and subsequent versions + * of JUnit remove the old name() method. This method provides + * access to the name of a TestCase via reflection that is + * supposed to work with version before and after JUnit 3.7. + * + * <p>since Ant 1.5.1 this method will invoke "<code>public + * String getName()</code>" on any implementation of Test if + * it exists.</p> + * + * <p>Since Ant 1.7 also checks for JUnit4TestCaseFacade explicitly. + * This is used by junit.framework.JUnit4TestAdapter.</p> + * @param t the test. + * @return the name of the test. + */ + public static String getTestCaseName(Test t) { + if (t == null) { + return UNKNOWN_TEST_CASE_NAME; + } + if (t.getClass().getName().equals(JUNIT_FRAMEWORK_JUNIT4_TEST_CASE_FACADE)) { + // Self-describing as of JUnit 4 (#38811). But trim "(ClassName)". + String name = t.toString(); + if (name.endsWith(")")) { + int paren = name.lastIndexOf('('); + return name.substring(0, paren); + } else { + return name; + } + } + if (t instanceof TestCase && testCaseName != null) { + try { + return (String) testCaseName.invoke(t, new Object[0]); + } catch (Throwable ignored) { + // ignore + } + } else { + try { + Method getNameMethod = null; + try { + getNameMethod = + t.getClass().getMethod("getName", new Class [0]); + } catch (NoSuchMethodException e) { + getNameMethod = t.getClass().getMethod("name", + new Class [0]); + } + if (getNameMethod != null + && getNameMethod.getReturnType() == String.class) { + return (String) getNameMethod.invoke(t, new Object[0]); + } + } catch (Throwable ignored) { + // ignore + } + } + return UNKNOWN_TEST_CASE_NAME; + } + + /** + * Tries to find the name of the class which a test represents + * across JUnit 3 and 4. For JUnit4 it parses the toString() value of the + * test, and extracts it from there. + * @since Ant 1.7.1 (it was private until then) + * @param test test case to look at + * @return the extracted class name. + */ + public static String getTestCaseClassName(Test test) { + String className = test.getClass().getName(); + if (test instanceof JUnitTaskMirrorImpl.VmExitErrorTest) { + className = ((JUnitTaskMirrorImpl.VmExitErrorTest) test).getClassName(); + } else + if (className.equals(JUNIT_FRAMEWORK_JUNIT4_TEST_CASE_FACADE)) { + // JUnit 4 wraps solo tests this way. We can extract + // the original test name with a little hack. + String name = test.toString(); + int paren = name.lastIndexOf('('); + if (paren != -1 && name.endsWith(")")) { + className = name.substring(paren + 1, name.length() - 1); + } + } + return className; + } + + public static String getIgnoreMessage(Test test) { + String message = null; + + try { + Class<?> junit4FacadeClass = Class.forName("junit.framework.JUnit4TestCaseFacade"); + if (test != null && test.getClass().isAssignableFrom(junit4FacadeClass)) { + //try and get the message coded as part of the ignore + /* + * org.junit.runner.Description contains a getAnnotation(Class) method... but this + * wasn't in older versions of JUnit4 so we have to try and do this by reflection + */ + Class<?> testClass = Class.forName(JUnitVersionHelper.getTestCaseClassName(test)); + + Method testMethod = testClass.getMethod(JUnitVersionHelper.getTestCaseName(test)); + Class ignoreAnnotation = Class.forName("org.junit.Ignore"); + Annotation annotation = testMethod.getAnnotation(ignoreAnnotation); + if (annotation != null) { + Method valueMethod = annotation.getClass().getMethod("value"); + String value = (String) valueMethod.invoke(annotation); + if (value != null && value.length() > 0) { + message = value; + } + } + + } + } catch (NoSuchMethodException e) { + // silently ignore - we'll report a skip with no message + } catch (ClassNotFoundException e) { + // silently ignore - we'll report a skip with no message + } catch (InvocationTargetException e) { + // silently ignore - we'll report a skip with no message + } catch (IllegalAccessException e) { + // silently ignore - we'll report a skip with no message + } + return message; + + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/OutErrSummaryJUnitResultFormatter.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/OutErrSummaryJUnitResultFormatter.java new file mode 100644 index 00000000..e0dc16c3 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/OutErrSummaryJUnitResultFormatter.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +/** + * Used instead of SummaryJUnitResultFormatter in forked tests if + * withOutAndErr is requested. + */ + +public class OutErrSummaryJUnitResultFormatter + extends SummaryJUnitResultFormatter { + + /** + * Empty + */ + public OutErrSummaryJUnitResultFormatter() { + super(); + setWithOutAndErr(true); + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java new file mode 100644 index 00000000..3386ee50 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.text.NumberFormat; +import java.util.Hashtable; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.StringUtils; + + +/** + * Prints plain text output of the test to a specified Writer. + * + */ + +public class PlainJUnitResultFormatter implements JUnitResultFormatter, IgnoredTestListener { + + private static final double ONE_SECOND = 1000.0; + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + /** + * Timing helper. + */ + private Hashtable testStarts = new Hashtable(); + /** + * Where to write the log to. + */ + private OutputStream out; + /** + * Helper to store intermediate output. + */ + private StringWriter inner; + /** + * Convenience layer on top of {@link #inner inner}. + */ + private BufferedWriter wri; + /** + * Suppress endTest if testcase failed. + */ + private Hashtable failed = new Hashtable(); + + private String systemOutput = null; + private String systemError = null; + + /** No arg constructor */ + public PlainJUnitResultFormatter() { + inner = new StringWriter(); + wri = new BufferedWriter(inner); + } + + /** {@inheritDoc}. */ + public void setOutput(OutputStream out) { + this.out = out; + } + + /** {@inheritDoc}. */ + public void setSystemOutput(String out) { + systemOutput = out; + } + + /** {@inheritDoc}. */ + public void setSystemError(String err) { + systemError = err; + } + + /** + * The whole testsuite started. + * @param suite the test suite + * @throws BuildException if unable to write the output + */ + public void startTestSuite(JUnitTest suite) throws BuildException { + if (out == null) { + return; // Quick return - no output do nothing. + } + StringBuffer sb = new StringBuffer("Testsuite: "); + sb.append(suite.getName()); + sb.append(StringUtils.LINE_SEP); + try { + out.write(sb.toString().getBytes()); + out.flush(); + } catch (IOException ex) { + throw new BuildException("Unable to write output", ex); + } + } + + /** + * The whole testsuite ended. + * @param suite the test suite + * @throws BuildException if unable to write the output + */ + public void endTestSuite(JUnitTest suite) throws BuildException { + try { + StringBuffer sb = new StringBuffer("Tests run: "); + sb.append(suite.runCount()); + sb.append(", Failures: "); + sb.append(suite.failureCount()); + sb.append(", Errors: "); + sb.append(suite.errorCount()); + sb.append(", Skipped: "); + sb.append(suite.skipCount()); + sb.append(", Time elapsed: "); + sb.append(nf.format(suite.getRunTime() / ONE_SECOND)); + sb.append(" sec"); + sb.append(StringUtils.LINE_SEP); + write(sb.toString()); + + // write the err and output streams to the log + if (systemOutput != null && systemOutput.length() > 0) { + write("------------- Standard Output ---------------"); + write(StringUtils.LINE_SEP); + write(systemOutput); + write("------------- ---------------- ---------------"); + write(StringUtils.LINE_SEP); + } + + if (systemError != null && systemError.length() > 0) { + write("------------- Standard Error -----------------"); + write(StringUtils.LINE_SEP); + write(systemError); + write("------------- ---------------- ---------------"); + write(StringUtils.LINE_SEP); + } + + write(StringUtils.LINE_SEP); + if (out != null) { + try { + wri.flush(); + write(inner.toString()); + } catch (IOException ioex) { + throw new BuildException("Unable to write output", ioex); + } + } + } finally { + if (out != null) { + try { + wri.close(); + } catch (IOException ioex) { + throw new BuildException("Unable to flush output", ioex); + } finally { + if (out != System.out && out != System.err) { + FileUtils.close(out); + } + wri = null; + out = null; + } + } + } + } + + /** + * Interface TestListener. + * + * <p>A new Test is started. + * @param t the test. + */ + public void startTest(Test t) { + testStarts.put(t, new Long(System.currentTimeMillis())); + failed.put(t, Boolean.FALSE); + } + + /** + * Interface TestListener. + * + * <p>A Test is finished. + * @param test the test. + */ + public void endTest(Test test) { + if (Boolean.TRUE.equals(failed.get(test))) { + return; + } + synchronized (wri) { + try { + wri.write("Testcase: " + + JUnitVersionHelper.getTestCaseName(test)); + Long l = (Long) testStarts.get(test); + double seconds = 0; + // can be null if an error occurred in setUp + if (l != null) { + seconds = + (System.currentTimeMillis() - l.longValue()) / ONE_SECOND; + } + + wri.write(" took " + nf.format(seconds) + " sec"); + wri.newLine(); + } catch (IOException ex) { + throw new BuildException(ex); + } + } + } + + /** + * Interface TestListener for JUnit <= 3.4. + * + * <p>A Test failed. + * @param test the test. + * @param t the exception. + */ + public void addFailure(Test test, Throwable t) { + formatError("\tFAILED", test, t); + } + + /** + * Interface TestListener for JUnit > 3.4. + * + * <p>A Test failed. + * @param test the test. + * @param t the assertion that failed. + */ + public void addFailure(Test test, AssertionFailedError t) { + addFailure(test, (Throwable) t); + } + + /** + * Interface TestListener. + * + * <p>An error occurred while running the test. + * @param test the test. + * @param t the exception. + */ + public void addError(Test test, Throwable t) { + formatError("\tCaused an ERROR", test, t); + } + + private void formatError(String type, Test test, Throwable t) { + synchronized (wri) { + if (test != null) { + endTest(test); + failed.put(test, Boolean.TRUE); + } + + try { + wri.write(type); + wri.newLine(); + wri.write(String.valueOf(t.getMessage())); + wri.newLine(); + String strace = JUnitTestRunner.getFilteredTrace(t); + wri.write(strace); + wri.newLine(); + } catch (IOException ex) { + throw new BuildException(ex); + } + } + } + + public void testIgnored(Test test) { + formatSkip(test, JUnitVersionHelper.getIgnoreMessage(test)); + } + + + public void formatSkip(Test test, String message) { + if (test != null) { + endTest(test); + } + + try { + wri.write("\tSKIPPED"); + if (message != null) { + wri.write(": "); + wri.write(message); + } + wri.newLine(); + } catch (IOException ex) { + throw new BuildException(ex); + } + + } + + public void testAssumptionFailure(Test test, Throwable throwable) { + formatSkip(test, throwable.getMessage()); + } + + /** + * Print out some text, and flush the output stream; encoding in the platform + * local default encoding. + * @param text text to write. + * @throws BuildException on IO Problems. + */ + private void write(String text) { + if (out == null) { + return; + } + try { + out.write(text.getBytes()); + out.flush(); + } catch (IOException ex) { + throw new BuildException("Unable to write output " + ex, ex); + } + } +} // PlainJUnitResultFormatter diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java new file mode 100644 index 00000000..0b09fa20 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.IOException; +import java.io.OutputStream; +import java.text.NumberFormat; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; + +import org.apache.tools.ant.BuildException; + +/** + * Prints short summary output of the test to Ant's logging system. + * + */ + +public class SummaryJUnitResultFormatter + implements JUnitResultFormatter, JUnitTaskMirror.SummaryJUnitResultFormatterMirror { + + private static final double ONE_SECOND = 1000.0; + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + /** + * OutputStream to write to. + */ + private OutputStream out; + + private boolean withOutAndErr = false; + private String systemOutput = null; + private String systemError = null; + + /** + * Empty + */ + public SummaryJUnitResultFormatter() { + } + + /** + * Insures that a line of log output is written and flushed as a single + * operation, to prevent lines from being spliced into other lines. + * (Hopefully this solves the issue of run on lines - + * [junit] Tests Run: 2 Failures: 2 [junit] Tests run: 5... + * synchronized doesn't seem to be to harsh a penalty since it only + * occurs twice per test - at the beginning and end. Note that message + * construction occurs outside the locked block. + * + * @param b data to be written as an unbroken block + */ + private synchronized void writeOutputLine(byte[] b) { + try { + out.write(b); + out.flush(); + } catch (IOException ioex) { + throw new BuildException("Unable to write summary output", ioex); + } + } + + /** + * The testsuite started. + * @param suite the testsuite. + */ + public void startTestSuite(JUnitTest suite) { + String newLine = System.getProperty("line.separator"); + StringBuffer sb = new StringBuffer("Running "); + int antThreadID = suite.getThread(); + + sb.append(suite.getName()); + /* only write thread id in multi-thread mode so default old way doesn't change output */ + if (antThreadID > 0) { + sb.append(" in thread "); + sb.append(antThreadID); + } + sb.append(newLine); + writeOutputLine(sb.toString().getBytes()); + } + /** + * Empty + * @param t not used. + */ + public void startTest(Test t) { + } + /** + * Empty + * @param test not used. + */ + public void endTest(Test test) { + } + /** + * Empty + * @param test not used. + * @param t not used. + */ + public void addFailure(Test test, Throwable t) { + } + /** + * Interface TestListener for JUnit > 3.4. + * + * <p>A Test failed. + * @param test not used. + * @param t not used. + */ + public void addFailure(Test test, AssertionFailedError t) { + addFailure(test, (Throwable) t); + } + /** + * Empty + * @param test not used. + * @param t not used. + */ + public void addError(Test test, Throwable t) { + } + + /** {@inheritDoc}. */ + public void setOutput(OutputStream out) { + this.out = out; + } + + /** {@inheritDoc}. */ + public void setSystemOutput(String out) { + systemOutput = out; + } + + /** {@inheritDoc}. */ + public void setSystemError(String err) { + systemError = err; + } + + /** + * Should the output to System.out and System.err be written to + * the summary. + * @param value if true write System.out and System.err to the summary. + */ + public void setWithOutAndErr(boolean value) { + withOutAndErr = value; + } + + /** + * The whole testsuite ended. + * @param suite the testsuite. + * @throws BuildException if there is an error. + */ + public void endTestSuite(JUnitTest suite) throws BuildException { + String newLine = System.getProperty("line.separator"); + StringBuffer sb = new StringBuffer("Tests run: "); + sb.append(suite.runCount()); + sb.append(", Failures: "); + sb.append(suite.failureCount()); + sb.append(", Errors: "); + sb.append(suite.errorCount()); + sb.append(", Skipped: "); + sb.append(suite.skipCount()); + sb.append(", Time elapsed: "); + sb.append(nf.format(suite.getRunTime() / ONE_SECOND)); + sb.append(" sec"); + + /* class name needed with multi-threaded execution because + results line may not appear immediately below start line. + only write thread id, class name in multi-thread mode so + the line still looks as much like the old line as possible. */ + if (suite.getThread() > 0) { + sb.append(", Thread: "); + sb.append(suite.getThread()); + sb.append(", Class: "); + sb.append(suite.getName()); + } + sb.append(newLine); + + if (withOutAndErr) { + if (systemOutput != null && systemOutput.length() > 0) { + sb.append("Output:").append(newLine).append(systemOutput) + .append(newLine); + } + + if (systemError != null && systemError.length() > 0) { + sb.append("Error: ").append(newLine).append(systemError) + .append(newLine); + } + } + + try { + writeOutputLine(sb.toString().getBytes()); + } finally { + if (out != System.out && out != System.err) { + try { + out.close(); + } catch (IOException e) { + // ignore + } + } + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/TearDownOnVmCrash.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/TearDownOnVmCrash.java new file mode 100644 index 00000000..e381a70c --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/TearDownOnVmCrash.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; + +/** + * Formatter that doesn't create any output but tries to invoke the + * tearDown method on a testcase if that test was forked and caused a + * timeout or VM crash. + * + * <p>This formatter has some limitations, for details see the + * <junit> task's manual.</p> + * + * @since Ant 1.8.0 + */ +public class TearDownOnVmCrash implements JUnitResultFormatter { + + private String suiteName; + + /** + * Records the suite's name to later determine the class to invoke + * tearDown on. + */ + public void startTestSuite(final JUnitTest suite) { + suiteName = suite.getName(); + if (suiteName != null && + suiteName.endsWith(JUnitTask.NAME_OF_DUMMY_TEST)) { + // no way to know which class caused the timeout + suiteName = null; + } + } + + /** + * Only invoke tearDown if the suite is known and not the dummy + * test we get when a Batch fails and the error is an actual + * error generated by Ant. + */ + public void addError(final Test fakeTest, final Throwable t) { + if (suiteName != null + && fakeTest instanceof JUnitTaskMirrorImpl.VmExitErrorTest) { + tearDown(); + } + } + + // no need to implement the rest + public void addFailure(Test test, Throwable t) {} + + public void addFailure(Test test, AssertionFailedError t) {} + + public void startTest(Test test) {} + + public void endTest(Test test) {} + + public void endTestSuite(JUnitTest suite) {} + + public void setOutput(OutputStream out) {} + + public void setSystemOutput(String out) {} + + public void setSystemError(String err) {} + + private void tearDown() { + try { + // first try to load the class and let's hope it is on our + // classpath + Class testClass = null; + if (Thread.currentThread().getContextClassLoader() != null) { + try { + testClass = Thread.currentThread().getContextClassLoader() + .loadClass(suiteName); + } catch (ClassNotFoundException cnfe) { + // ignore + } + } + if (testClass == null && getClass().getClassLoader() != null) { + try { + testClass = + getClass().getClassLoader().loadClass(suiteName); + } catch (ClassNotFoundException cnfe) { + // ignore + } + } + if (testClass == null) { + // fall back to system classloader + testClass = Class.forName(suiteName); + } + + // if the test has a suite method, then we can't know + // which test of the executed suite timed out, ignore it + try { + // check if there is a suite method + testClass.getMethod("suite", new Class[0]); + return; + } catch (NoSuchMethodException e) { + // no suite method + } + + // a loadable class and no suite method + // no reason to check for JUnit 4 since JUnit4TestAdapter + // doesn't have any tearDown method. + + try { + Method td = testClass.getMethod("tearDown", new Class[0]); + if (td.getReturnType() == Void.TYPE) { + td.invoke(testClass.newInstance(), new Object[0]); + } + } catch (NoSuchMethodException nsme) { + // no tearDown, fine + } + + } catch (ClassNotFoundException cnfe) { + // class probably is not in our classpath, there is + // nothing we can do + } catch (InvocationTargetException ite) { + System.err.println("Caught an exception while trying to invoke" + + " tearDown: " + ite.getMessage()); + } catch (Throwable t) { + System.err.println("Caught an exception while trying to invoke" + + " tearDown: " + t.getMessage()); + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/TestIgnored.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/TestIgnored.java new file mode 100644 index 00000000..79a88785 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/TestIgnored.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import junit.framework.Test; + +public class TestIgnored { + + private Test test; + + public TestIgnored(Test test) { + this.test = test; + } + + public Test getTest() { + return test; + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/TestListenerWrapper.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/TestListenerWrapper.java new file mode 100644 index 00000000..692e4fc9 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/TestListenerWrapper.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; + + +public class TestListenerWrapper implements TestListener, IgnoredTestListener { + + private TestListener wrapped; + + public TestListenerWrapper(TestListener listener) { + super(); + wrapped = listener; + } + + public void addError(Test test, Throwable throwable) { + wrapped.addError(test, throwable); + } + + public void addFailure(Test test, AssertionFailedError assertionFailedError) { + wrapped.addFailure(test, assertionFailedError); + } + + public void endTest(Test test) { + wrapped.endTest(test); + } + + public void startTest(Test test) { + wrapped.startTest(test); + } + + public void testIgnored(Test test) { + if (wrapped instanceof IgnoredTestListener) { + ((IgnoredTestListener)wrapped).testIgnored(test); + } + } + + public void testAssumptionFailure(Test test, Throwable throwable) { + if (wrapped instanceof IgnoredTestListener) { + ((IgnoredTestListener)wrapped).testAssumptionFailure(test, throwable); + } + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java new file mode 100644 index 00000000..03760cc9 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +/** + * <p> Interface groups XML constants. + * Interface that groups all constants used throughout the <tt>XML</tt> + * documents that are generated by the <tt>XMLJUnitResultFormatter</tt>. + * <p> + * As of now the DTD is: + * <code><pre> + * <!ELEMENT testsuites (testsuite*)> + * + * <!ELEMENT testsuite (properties, testcase*, + * failure?, error?, + * system-out?, system-err?)> + * <!ATTLIST testsuite name CDATA #REQUIRED> + * <!ATTLIST testsuite tests CDATA #REQUIRED> + * <!ATTLIST testsuite failures CDATA #REQUIRED> + * <!ATTLIST testsuite errors CDATA #REQUIRED> + * <!ATTLIST testsuite time CDATA #REQUIRED> + * <!ATTLIST testsuite package CDATA #IMPLIED> + * <!ATTLIST testsuite id CDATA #IMPLIED> + * + * + * <!ELEMENT properties (property*)> + * + * <!ELEMENT property EMPTY> + * <!ATTLIST property name CDATA #REQUIRED> + * <!ATTLIST property value CDATA #REQUIRED> + * + * <!ELEMENT testcase (failure?, error?)> + * <!ATTLIST testcase name CDATA #REQUIRED> + * <!ATTLIST testcase classname CDATA #IMPLIED> + * <!ATTLIST testcase time CDATA #REQUIRED> + * + * <!ELEMENT failure (#PCDATA)> + * <!ATTLIST failure message CDATA #IMPLIED> + * <!ATTLIST failure type CDATA #REQUIRED> + * + * <!ELEMENT error (#PCDATA)> + * <!ATTLIST error message CDATA #IMPLIED> + * <!ATTLIST error type CDATA #REQUIRED> + * + * <!ELEMENT system-err (#PCDATA)> + * + * <!ELEMENT system-out (#PCDATA)> + * + * </pre></code> + * @see XMLJUnitResultFormatter + * @see XMLResultAggregator + */ +// CheckStyle:InterfaceIsTypeCheck OFF (bc) +public interface XMLConstants { + /** the testsuites element for the aggregate document */ + String TESTSUITES = "testsuites"; + + /** the testsuite element */ + String TESTSUITE = "testsuite"; + + /** the testcase element */ + String TESTCASE = "testcase"; + + /** the error element */ + String ERROR = "error"; + + /** the failure element */ + String FAILURE = "failure"; + + /** the system-err element */ + String SYSTEM_ERR = "system-err"; + + /** the system-out element */ + String SYSTEM_OUT = "system-out"; + + /** package attribute for the aggregate document */ + String ATTR_PACKAGE = "package"; + + /** name attribute for property, testcase and testsuite elements */ + String ATTR_NAME = "name"; + + /** time attribute for testcase and testsuite elements */ + String ATTR_TIME = "time"; + + /** errors attribute for testsuite elements */ + String ATTR_ERRORS = "errors"; + + /** failures attribute for testsuite elements */ + String ATTR_FAILURES = "failures"; + + /** tests attribute for testsuite elements */ + String ATTR_TESTS = "tests"; + + String ATTR_SKIPPED = "skipped"; + + /** type attribute for failure and error elements */ + String ATTR_TYPE = "type"; + + /** message attribute for failure elements */ + String ATTR_MESSAGE = "message"; + + /** the properties element */ + String PROPERTIES = "properties"; + + /** the property element */ + String PROPERTY = "property"; + + /** value attribute for property elements */ + String ATTR_VALUE = "value"; + + /** classname attribute for testcase elements */ + String ATTR_CLASSNAME = "classname"; + + /** id attribute */ + String ATTR_ID = "id"; + + /** + * timestamp of test cases + */ + String TIMESTAMP = "timestamp"; + + /** + * name of host running the tests + */ + String HOSTNAME = "hostname"; +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java new file mode 100644 index 00000000..416c10d4 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java @@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.DOMElementWriter; +import org.apache.tools.ant.util.DateUtils; +import org.apache.tools.ant.util.FileUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + + +/** + * Prints XML output of the test to a specified Writer. + * + * @see FormatterElement + */ + +public class XMLJUnitResultFormatter implements JUnitResultFormatter, XMLConstants, IgnoredTestListener { + + private static final double ONE_SECOND = 1000.0; + + /** constant for unnnamed testsuites/cases */ + private static final String UNKNOWN = "unknown"; + + private static DocumentBuilder getDocumentBuilder() { + try { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (final Exception exc) { + throw new ExceptionInInitializerError(exc); + } + } + + /** + * The XML document. + */ + private Document doc; + + /** + * The wrapper for the whole testsuite. + */ + private Element rootElement; + + /** + * Element for the current test. + * + * The keying of this map is a bit of a hack: tests are keyed by caseName(className) since + * the Test we get for Test-start isn't the same as the Test we get during test-assumption-fail, + * so we can't easily match Test objects without manually iterating over all keys and checking + * individual fields. + */ + private final Hashtable<String, Element> testElements = new Hashtable<String, Element>(); + + /** + * tests that failed. + */ + private final Hashtable failedTests = new Hashtable(); + + /** + * Tests that were skipped. + */ + private final Hashtable<String, Test> skippedTests = new Hashtable<String, Test>(); + /** + * Tests that were ignored. See the note above about the key being a bit of a hack. + */ + private final Hashtable<String, Test> ignoredTests = new Hashtable<String, Test>(); + /** + * Timing helper. + */ + private final Hashtable<String, Long> testStarts = new Hashtable<String, Long>(); + /** + * Where to write the log to. + */ + private OutputStream out; + + /** No arg constructor. */ + public XMLJUnitResultFormatter() { + } + + /** {@inheritDoc}. */ + public void setOutput(final OutputStream out) { + this.out = out; + } + + /** {@inheritDoc}. */ + public void setSystemOutput(final String out) { + formatOutput(SYSTEM_OUT, out); + } + + /** {@inheritDoc}. */ + public void setSystemError(final String out) { + formatOutput(SYSTEM_ERR, out); + } + + /** + * The whole testsuite started. + * @param suite the testsuite. + */ + public void startTestSuite(final JUnitTest suite) { + doc = getDocumentBuilder().newDocument(); + rootElement = doc.createElement(TESTSUITE); + final String n = suite.getName(); + rootElement.setAttribute(ATTR_NAME, n == null ? UNKNOWN : n); + + //add the timestamp + final String timestamp = DateUtils.format(new Date(), + DateUtils.ISO8601_DATETIME_PATTERN); + rootElement.setAttribute(TIMESTAMP, timestamp); + //and the hostname. + rootElement.setAttribute(HOSTNAME, getHostname()); + + // Output properties + final Element propsElement = doc.createElement(PROPERTIES); + rootElement.appendChild(propsElement); + final Properties props = suite.getProperties(); + if (props != null) { + final Enumeration e = props.propertyNames(); + while (e.hasMoreElements()) { + final String name = (String) e.nextElement(); + final Element propElement = doc.createElement(PROPERTY); + propElement.setAttribute(ATTR_NAME, name); + propElement.setAttribute(ATTR_VALUE, props.getProperty(name)); + propsElement.appendChild(propElement); + } + } + } + + /** + * get the local hostname + * @return the name of the local host, or "localhost" if we cannot work it out + */ + private String getHostname() { + String hostname = "localhost"; + try { + final InetAddress localHost = InetAddress.getLocalHost(); + if (localHost != null) { + hostname = localHost.getHostName(); + } + } catch (final UnknownHostException e) { + // fall back to default 'localhost' + } + return hostname; + } + + /** + * The whole testsuite ended. + * @param suite the testsuite. + * @throws BuildException on error. + */ + public void endTestSuite(final JUnitTest suite) throws BuildException { + rootElement.setAttribute(ATTR_TESTS, "" + suite.runCount()); + rootElement.setAttribute(ATTR_FAILURES, "" + suite.failureCount()); + rootElement.setAttribute(ATTR_ERRORS, "" + suite.errorCount()); + rootElement.setAttribute(ATTR_SKIPPED, "" + suite.skipCount()); + rootElement.setAttribute( + ATTR_TIME, "" + (suite.getRunTime() / ONE_SECOND)); + if (out != null) { + Writer wri = null; + try { + wri = new BufferedWriter(new OutputStreamWriter(out, "UTF8")); + wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"); + (new DOMElementWriter()).write(rootElement, wri, 0, " "); + } catch (final IOException exc) { + throw new BuildException("Unable to write log file", exc); + } finally { + if (wri != null) { + try { + wri.flush(); + } catch (final IOException ex) { + // ignore + } + } + if (out != System.out && out != System.err) { + FileUtils.close(wri); + } + } + } + } + + /** + * Interface TestListener. + * + * <p>A new Test is started. + * @param t the test. + */ + public void startTest(final Test t) { + testStarts.put(createDescription(t), System.currentTimeMillis()); + } + + private static String createDescription(final Test test) throws BuildException { + return JUnitVersionHelper.getTestCaseName(test) + "(" + JUnitVersionHelper.getTestCaseClassName(test) + ")"; + } + + /** + * Interface TestListener. + * + * <p>A Test is finished. + * @param test the test. + */ + public void endTest(final Test test) { + final String testDescription = createDescription(test); + + // Fix for bug #5637 - if a junit.extensions.TestSetup is + // used and throws an exception during setUp then startTest + // would never have been called + if (!testStarts.containsKey(testDescription)) { + startTest(test); + } + Element currentTest; + if (!failedTests.containsKey(test) && !skippedTests.containsKey(testDescription) && !ignoredTests.containsKey(testDescription)) { + currentTest = doc.createElement(TESTCASE); + final String n = JUnitVersionHelper.getTestCaseName(test); + currentTest.setAttribute(ATTR_NAME, + n == null ? UNKNOWN : n); + // a TestSuite can contain Tests from multiple classes, + // even tests with the same name - disambiguate them. + currentTest.setAttribute(ATTR_CLASSNAME, + JUnitVersionHelper.getTestCaseClassName(test)); + rootElement.appendChild(currentTest); + testElements.put(createDescription(test), currentTest); + } else { + currentTest = testElements.get(testDescription); + } + + final Long l = testStarts.get(createDescription(test)); + currentTest.setAttribute(ATTR_TIME, + "" + ((System.currentTimeMillis() - l) / ONE_SECOND)); + } + + /** + * Interface TestListener for JUnit <= 3.4. + * + * <p>A Test failed. + * @param test the test. + * @param t the exception. + */ + public void addFailure(final Test test, final Throwable t) { + formatError(FAILURE, test, t); + } + + /** + * Interface TestListener for JUnit > 3.4. + * + * <p>A Test failed. + * @param test the test. + * @param t the assertion. + */ + public void addFailure(final Test test, final AssertionFailedError t) { + addFailure(test, (Throwable) t); + } + + /** + * Interface TestListener. + * + * <p>An error occurred while running the test. + * @param test the test. + * @param t the error. + */ + public void addError(final Test test, final Throwable t) { + formatError(ERROR, test, t); + } + + private void formatError(final String type, final Test test, final Throwable t) { + if (test != null) { + endTest(test); + failedTests.put(test, test); + } + + final Element nested = doc.createElement(type); + Element currentTest; + if (test != null) { + currentTest = testElements.get(createDescription(test)); + } else { + currentTest = rootElement; + } + + currentTest.appendChild(nested); + + final String message = t.getMessage(); + if (message != null && message.length() > 0) { + nested.setAttribute(ATTR_MESSAGE, t.getMessage()); + } + nested.setAttribute(ATTR_TYPE, t.getClass().getName()); + + final String strace = JUnitTestRunner.getFilteredTrace(t); + final Text trace = doc.createTextNode(strace); + nested.appendChild(trace); + } + + private void formatOutput(final String type, final String output) { + final Element nested = doc.createElement(type); + rootElement.appendChild(nested); + nested.appendChild(doc.createCDATASection(output)); + } + + public void testIgnored(final Test test) { + formatSkip(test, JUnitVersionHelper.getIgnoreMessage(test)); + if (test != null) { + ignoredTests.put(createDescription(test), test); + } + } + + + public void formatSkip(final Test test, final String message) { + if (test != null) { + endTest(test); + } + + final Element nested = doc.createElement("skipped"); + + if (message != null) { + nested.setAttribute("message", message); + } + + Element currentTest; + if (test != null) { + currentTest = testElements.get(createDescription(test)); + } else { + currentTest = rootElement; + } + + currentTest.appendChild(nested); + + } + + public void testAssumptionFailure(final Test test, final Throwable failure) { + formatSkip(test, failure.getMessage()); + skippedTests.put(createDescription(test), test); + + } +} // XMLJUnitResultFormatter diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java new file mode 100644 index 00000000..4f76c968 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java @@ -0,0 +1,329 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Vector; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.DOMElementWriter; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + + +/** + * Aggregates all <junit> XML formatter testsuite data under + * a specific directory and transforms the results via XSLT. + * It is not particularly clean but + * should be helpful while I am thinking about another technique. + * + * <p> The main problem is due to the fact that a JVM can be forked for a testcase + * thus making it impossible to aggregate all testcases since the listener is + * (obviously) in the forked JVM. A solution could be to write a + * TestListener that will receive events from the TestRunner via sockets. This + * is IMHO the simplest way to do it to avoid this file hacking thing. + * + * @ant.task name="junitreport" category="testing" + */ +public class XMLResultAggregator extends Task implements XMLConstants { + + // CheckStyle:VisibilityModifier OFF - bc + /** the list of all filesets, that should contains the xml to aggregate */ + protected Vector filesets = new Vector(); + + /** the name of the result file */ + protected String toFile; + + /** the directory to write the file to */ + protected File toDir; + + protected Vector transformers = new Vector(); + + /** The default directory: <tt>.</tt>. It is resolved from the project directory */ + public static final String DEFAULT_DIR = "."; + + /** the default file name: <tt>TESTS-TestSuites.xml</tt> */ + public static final String DEFAULT_FILENAME = "TESTS-TestSuites.xml"; + + /** the current generated id */ + protected int generatedId = 0; + + /** + * text checked for in tests, {@value} + */ + static final String WARNING_IS_POSSIBLY_CORRUPTED + = " is not a valid XML document. It is possibly corrupted."; + /** + * text checked for in tests, {@value} + */ + static final String WARNING_INVALID_ROOT_ELEMENT + = " is not a valid testsuite XML document"; + /** + * text checked for in tests, {@value} + */ + static final String WARNING_EMPTY_FILE + = " is empty.\nThis can be caused by the test JVM exiting unexpectedly"; + // CheckStyle:VisibilityModifier ON + + /** + * Generate a report based on the document created by the merge. + * @return the report + */ + public AggregateTransformer createReport() { + AggregateTransformer transformer = new AggregateTransformer(this); + transformers.addElement(transformer); + return transformer; + } + + /** + * Set the name of the aggregegated results file. It must be relative + * from the <tt>todir</tt> attribute. If not set it will use {@link #DEFAULT_FILENAME} + * @param value the name of the file. + * @see #setTodir(File) + */ + public void setTofile(String value) { + toFile = value; + } + + /** + * Set the destination directory where the results should be written. If not + * set if will use {@link #DEFAULT_DIR}. When given a relative directory + * it will resolve it from the project directory. + * @param value the directory where to write the results, absolute or + * relative. + */ + public void setTodir(File value) { + toDir = value; + } + + /** + * Add a new fileset containing the XML results to aggregate + * @param fs the new fileset of xml results. + */ + public void addFileSet(FileSet fs) { + filesets.addElement(fs); + } + + /** + * Aggregate all testsuites into a single document and write it to the + * specified directory and file. + * @throws BuildException thrown if there is a serious error while writing + * the document. + */ + public void execute() throws BuildException { + Element rootElement = createDocument(); + File destFile = getDestinationFile(); + // write the document + try { + writeDOMTree(rootElement.getOwnerDocument(), destFile); + } catch (IOException e) { + throw new BuildException("Unable to write test aggregate to '" + destFile + "'", e); + } + // apply transformation + Enumeration e = transformers.elements(); + while (e.hasMoreElements()) { + AggregateTransformer transformer = + (AggregateTransformer) e.nextElement(); + transformer.setXmlDocument(rootElement.getOwnerDocument()); + transformer.transform(); + } + } + + /** + * Get the full destination file where to write the result. It is made of + * the <tt>todir</tt> and <tt>tofile</tt> attributes. + * @return the destination file where should be written the result file. + */ + public File getDestinationFile() { + if (toFile == null) { + toFile = DEFAULT_FILENAME; + } + if (toDir == null) { + toDir = getProject().resolveFile(DEFAULT_DIR); + } + return new File(toDir, toFile); + } + + /** + * Get all <code>.xml</code> files in the fileset. + * + * @return all files in the fileset that end with a '.xml'. + */ + protected File[] getFiles() { + Vector v = new Vector(); + final int size = filesets.size(); + for (int i = 0; i < size; i++) { + FileSet fs = (FileSet) filesets.elementAt(i); + DirectoryScanner ds = fs.getDirectoryScanner(getProject()); + ds.scan(); + String[] f = ds.getIncludedFiles(); + for (int j = 0; j < f.length; j++) { + String pathname = f[j]; + if (pathname.endsWith(".xml")) { + File file = new File(ds.getBasedir(), pathname); + file = getProject().resolveFile(file.getPath()); + v.addElement(file); + } + } + } + + File[] files = new File[v.size()]; + v.copyInto(files); + return files; + } + + //----- from now, the methods are all related to DOM tree manipulation + + /** + * Write the DOM tree to a file. + * @param doc the XML document to dump to disk. + * @param file the filename to write the document to. Should obviously be a .xml file. + * @throws IOException thrown if there is an error while writing the content. + */ + protected void writeDOMTree(Document doc, File file) throws IOException { + OutputStream os = new FileOutputStream(file); + try { + PrintWriter wri = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(os), "UTF8")); + wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"); + (new DOMElementWriter()).write(doc.getDocumentElement(), wri, 0, " "); + wri.flush(); + // writers do not throw exceptions, so check for them. + if (wri.checkError()) { + throw new IOException("Error while writing DOM content"); + } + } finally { + os.close(); + } + } + + /** + * <p> Create a DOM tree. + * Has 'testsuites' as firstchild and aggregates all + * testsuite results that exists in the base directory. + * @return the root element of DOM tree that aggregates all testsuites. + */ + protected Element createDocument() { + // create the dom tree + DocumentBuilder builder = getDocumentBuilder(); + Document doc = builder.newDocument(); + Element rootElement = doc.createElement(TESTSUITES); + doc.appendChild(rootElement); + + generatedId = 0; + + // get all files and add them to the document + File[] files = getFiles(); + for (int i = 0; i < files.length; i++) { + File file = files[i]; + try { + log("Parsing file: '" + file + "'", Project.MSG_VERBOSE); + if (file.length() > 0) { + Document testsuiteDoc + = builder.parse( + FileUtils.getFileUtils().toURI(files[i].getAbsolutePath())); + Element elem = testsuiteDoc.getDocumentElement(); + // make sure that this is REALLY a testsuite. + if (TESTSUITE.equals(elem.getNodeName())) { + addTestSuite(rootElement, elem); + generatedId++; + } else { + //wrong root element name + // issue a warning. + log("the file " + file + + WARNING_INVALID_ROOT_ELEMENT, + Project.MSG_WARN); + } + } else { + log("the file " + file + + WARNING_EMPTY_FILE, + Project.MSG_WARN); + } + } catch (SAXException e) { + // a testcase might have failed and write a zero-length document, + // It has already failed, but hey.... mm. just put a warning + log("The file " + file + WARNING_IS_POSSIBLY_CORRUPTED, Project.MSG_WARN); + log(StringUtils.getStackTrace(e), Project.MSG_DEBUG); + } catch (IOException e) { + log("Error while accessing file " + file + ": " + + e.getMessage(), Project.MSG_ERR); + log("Error while accessing file " + file + ": " + + e.getMessage(), e, Project.MSG_VERBOSE); + } + } + return rootElement; + } + + /** + * <p> Add a new testsuite node to the document. + * The main difference is that it + * split the previous fully qualified name into a package and a name. + * <p> For example: <tt>org.apache.Whatever</tt> will be split into + * <tt>org.apache</tt> and <tt>Whatever</tt>. + * @param root the root element to which the <tt>testsuite</tt> node should + * be appended. + * @param testsuite the element to append to the given root. It will slightly + * modify the original node to change the name attribute and add + * a package one. + */ + protected void addTestSuite(Element root, Element testsuite) { + String fullclassname = testsuite.getAttribute(ATTR_NAME); + int pos = fullclassname.lastIndexOf('.'); + + // a missing . might imply no package at all. Don't get fooled. + String pkgName = (pos == -1) ? "" : fullclassname.substring(0, pos); + String classname = (pos == -1) ? fullclassname : fullclassname.substring(pos + 1); + Element copy = (Element) DOMUtil.importNode(root, testsuite); + + // modify the name attribute and set the package + copy.setAttribute(ATTR_NAME, classname); + copy.setAttribute(ATTR_PACKAGE, pkgName); + copy.setAttribute(ATTR_ID, Integer.toString(generatedId)); + } + + /** + * Create a new document builder. Will issue an <tt>ExceptionInitializerError</tt> + * if something is going wrong. It is fatal anyway. + * @todo factorize this somewhere else. It is duplicated code. + * @return a new document builder to create a DOM + */ + private static DocumentBuilder getDocumentBuilder() { + try { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (Exception exc) { + throw new ExceptionInInitializerError(exc); + } + } + +} |