001    /*
002     * Created on Jun 6, 2007
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     *
013     * Copyright @2007-2009 the original author or authors.
014     */
015    package org.fest.swing.junit.ant;
016    
017    import static org.apache.tools.ant.taskdefs.optional.junit.XMLConstants.*;
018    
019    import java.io.OutputStream;
020    
021    import junit.framework.AssertionFailedError;
022    import junit.framework.Test;
023    
024    import org.apache.tools.ant.BuildException;
025    import org.apache.tools.ant.taskdefs.optional.junit.JUnitResultFormatter;
026    import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
027    import org.fest.swing.junit.xml.XmlDocument;
028    import org.fest.swing.junit.xml.XmlNode;
029    
030    /**
031     * Understands a copy of the original <code>XMLJUnitResultFormatter</code>, with flexibility for extension.
032     *
033     * @author Alex Ruiz
034     */
035    public class XmlJUnitResultFormatter implements JUnitResultFormatter {
036    
037      private XmlNode xmlRoot;
038    
039      private OutputStream out;  // where to write the log to
040    
041      private final TestCollection tests;
042    
043      private final SuiteXmlNodeWriter suiteXmlNodeWriter;
044      private final EnvironmentXmlNodeWriter environmentXmlNodeWriter;
045      private final TestXmlNodeWriter testXmlNodeWriter;
046    
047      private final XmlOutputWriter xmlOutputWriter;
048    
049      /**
050       * Creates a new </code>{@link XmlJUnitResultFormatter}</code>.
051       */
052      public XmlJUnitResultFormatter() {
053        tests = new TestCollection();
054        suiteXmlNodeWriter = new SuiteXmlNodeWriter();
055        environmentXmlNodeWriter = new EnvironmentXmlNodeWriter();
056        testXmlNodeWriter = new TestXmlNodeWriter();
057        xmlOutputWriter = new XmlOutputWriter();
058      }
059    
060      // for testing only
061      final TestCollection tests() { return tests; }
062    
063      /**
064       * Sets the stream the formatter is supposed to write its results to.
065       * @param out the output stream to use.
066       */
067      public final void setOutput(OutputStream out) {
068        this.out = out;
069      }
070    
071      /**
072       * This is what the test has written to <code>System.out</code>,
073       * @param out the <code>String</code> to write.
074       */
075      public final void setSystemOutput(String out) {
076        formatOutput(SYSTEM_OUT, out);
077      }
078    
079      /**
080       * This is what the test has written to <code>System.err</code>.
081       * @param out the <code>String</code> to write.
082       */
083      public final void setSystemError(String out) {
084        formatOutput(SYSTEM_ERR, out);
085      }
086    
087      private void formatOutput(String type, String output) {
088        xmlRoot.addNewNode(type).addCdata(output);
089      }
090    
091      protected final XmlNode xmlRootNode() { return xmlRoot; }
092    
093      /**
094       * The whole test suite started. This method starts creation of the XML report.
095       * @param suite the test suite.
096       * @throws ExceptionInInitializerError if the underlying XML document could not be created.
097       */
098      public final void startTestSuite(JUnitTest suite) {
099        XmlDocument document = new XmlDocument();
100        xmlRoot = document.newRoot(TESTSUITE);
101        suiteXmlNodeWriter.writeSuiteName(xmlRoot, suite)
102                          .writeSuiteProperties(xmlRoot, suite);
103        environmentXmlNodeWriter.writeHostName(xmlRoot)
104                                .writeTimestamp(xmlRoot);
105        onStartTestSuite(suite);
106      }
107    
108      /**
109       * Hook for subclasses to add extra functionality after the whole test suite started.
110       * @param suite the test suite.
111       */
112      protected void onStartTestSuite(JUnitTest suite) {}
113    
114      /**
115       * The whole test suite ended. This method finishes writing the XML report and writes its contents to this
116       * formatter's <code>{@link OutputStream}</code>.
117       * @param suite the test suite.
118       * @throws BuildException on error.
119       */
120      public final void endTestSuite(JUnitTest suite) {
121        suiteXmlNodeWriter.writeSuiteStatistics(xmlRoot, suite);
122        if (out == null) return;
123        xmlOutputWriter.write(xmlRoot, out);
124      }
125    
126      /**
127       * A new test is started.
128       * @param test the test.
129       */
130      public final void startTest(Test test) {
131        tests.started(test);
132      }
133    
134      /**
135       * A test is finished.
136       * @param test the test.
137       */
138      public final void endTest(Test test) {
139        if (!tests.wasStarted(test)) startTest(test);
140        XmlNode testNode = xmlNodeForFinished(test);
141        testXmlNodeWriter.writeTestExecutionTime(testNode, tests.startTimeOf(test));
142      }
143    
144      private XmlNode xmlNodeForFinished(Test test) {
145        if (tests.wasFailed(test)) return tests.xmlNodeFor(test);
146        XmlNode newTestXmlNode = testXmlNodeWriter.addNewTestXmlNode(xmlRoot, test);
147        tests.addXmlNode(test, newTestXmlNode);
148        return newTestXmlNode;
149      }
150    
151      /**
152       * A test failed.
153       * @param test the test.
154       * @param failedAssertion the failed assertion.
155       */
156      public final void addFailure(Test test, AssertionFailedError failedAssertion) {
157        addFailure(test, (Throwable)failedAssertion);
158      }
159    
160      /**
161       * A test failed.
162       * @param test the test.
163       * @param error the exception.
164       */
165      public final void addFailure(Test test, Throwable error) {
166        XmlNode errorXmlNode = formatError(FAILURE, test, error);
167        onFailureOrError(test, error, errorXmlNode);
168      }
169    
170      /**
171       * An error occurred while running the test.
172       * @param test the test.
173       * @param error the error.
174       */
175      public final void addError(Test test, Throwable error) {
176        XmlNode errorXmlNode = formatError(ERROR, test, error);
177        onFailureOrError(test, error, errorXmlNode);
178      }
179    
180      private XmlNode formatError(String type, Test test, Throwable error) {
181        if (test != null) {
182          endTest(test);
183          tests.failed(test);
184        }
185        XmlNode errorXmlNode = xmlForFailed(test).addNewNode(type);
186        writeErrorAndStackTrace(error, errorXmlNode);
187        return errorXmlNode;
188      }
189    
190      private XmlNode xmlForFailed(Test test) {
191        if (test != null) return tests.xmlNodeFor(test);
192        return xmlRoot;
193      }
194    
195      /**
196       * Writes the stack trace and message of the given error to the given XML node.
197       * @param error the given error.
198       * @param errorXmlNode the XML node to write to.
199       */
200      protected final void writeErrorAndStackTrace(Throwable error, XmlNode errorXmlNode) {
201        testXmlNodeWriter.writeErrorAndStackTrace(errorXmlNode, error);
202      }
203    
204      /**
205       * Hook for subclasses to add extra functionality after a test failure or a test execution error.
206       * @param test the executing test.
207       * @param error the reason of the failure or error.
208       * @param errorXmlNode the XML element containing information about the test failure or error.
209       */
210      protected void onFailureOrError(Test test, Throwable error, XmlNode errorXmlNode) {}
211    }