content: /*
   * Created on Jun 6, 2007
   *
   * Licensed 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.
   *
   * Copyright @2007-2009 the original author or authors.
   */
  package org.fest.swing.junit.ant;
  
  import static org.apache.tools.ant.taskdefs.optional.junit.XMLConstants.*;
  
  import java.io.OutputStream;
  
  import junit.framework.AssertionFailedError;
  import junit.framework.Test;
  
  import org.apache.tools.ant.BuildException;
  import org.apache.tools.ant.taskdefs.optional.junit.JUnitResultFormatter;
  import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
  import org.fest.swing.junit.xml.XmlDocument;
  import org.fest.swing.junit.xml.XmlNode;
  
  /**
   * Understands a copy of the original <code>XMLJUnitResultFormatter</code>, with flexibility for extension.
   *
   * @author Alex Ruiz
   */
  public class XmlJUnitResultFormatter implements JUnitResultFormatter {
  
    private XmlNode xmlRoot;
  
    private OutputStream out;  // where to write the log to
  
    private final TestCollection tests;
  
    private final SuiteXmlNodeWriter suiteXmlNodeWriter;
    private final EnvironmentXmlNodeWriter environmentXmlNodeWriter;
    private final TestXmlNodeWriter testXmlNodeWriter;
  
    private final XmlOutputWriter xmlOutputWriter;
  
    /**
     * Creates a new </code>{@link XmlJUnitResultFormatter}</code>.
     */
    public XmlJUnitResultFormatter() {
      tests = new TestCollection();
      suiteXmlNodeWriter = new SuiteXmlNodeWriter();
      environmentXmlNodeWriter = new EnvironmentXmlNodeWriter();
      testXmlNodeWriter = new TestXmlNodeWriter();
      xmlOutputWriter = new XmlOutputWriter();
    }
  
    // for testing only
    final TestCollection tests() { return tests; }
  
    /**
     * Sets the stream the formatter is supposed to write its results to.
     * @param out the output stream to use.
     */
    public final void setOutput(OutputStream out) {
      this.out = out;
    }
  
    /**
     * This is what the test has written to <code>System.out</code>,
     * @param out the <code>String</code> to write.
     */
    public final void setSystemOutput(String out) {
      formatOutput(SYSTEM_OUT, out);
    }
  
    /**
     * This is what the test has written to <code>System.err</code>.
     * @param out the <code>String</code> to write.
     */
    public final void setSystemError(String out) {
      formatOutput(SYSTEM_ERR, out);
    }
  
    private void formatOutput(String type, String output) {
      xmlRoot.addNewNode(type).addCdata(output);
    }
  
    protected final XmlNode xmlRootNode() { return xmlRoot; }
  
    /**
     * The whole test suite started. This method starts creation of the XML report.
     * @param suite the test suite.
     * @throws ExceptionInInitializerError if the underlying XML document could not be created.
     */
    public final void startTestSuite(JUnitTest suite) {
      XmlDocument document = new XmlDocument();
      xmlRoot = document.newRoot(TESTSUITE);
      suiteXmlNodeWriter.writeSuiteName(xmlRoot, suite)
                        .writeSuiteProperties(xmlRoot, suite);
      environmentXmlNodeWriter.writeHostName(xmlRoot)
                              .writeTimestamp(xmlRoot);
      onStartTestSuite(suite);
    }
  
    /**
     * Hook for subclasses to add extra functionality after the whole test suite started.
     * @param suite the test suite.
     */
    protected void onStartTestSuite(JUnitTest suite) {}
  
    /**
     * The whole test suite ended. This method finishes writing the XML report and writes its contents to this
     * formatter's <code>{@link OutputStream}</code>.
     * @param suite the test suite.
     * @throws BuildException on error.
     */
    public final void endTestSuite(JUnitTest suite) {
      suiteXmlNodeWriter.writeSuiteStatistics(xmlRoot, suite);
      if (out == null) return;
      xmlOutputWriter.write(xmlRoot, out);
    }
  
    /**
     * A new test is started.
     * @param test the test.
     */
    public final void startTest(Test test) {
      tests.started(test);
    }
  
    /**
     * A test is finished.
     * @param test the test.
     */
    public final void endTest(Test test) {
      if (!tests.wasStarted(test)) startTest(test);
      XmlNode testNode = xmlNodeForFinished(test);
      testXmlNodeWriter.writeTestExecutionTime(testNode, tests.startTimeOf(test));
    }
  
    private XmlNode xmlNodeForFinished(Test test) {
      if (tests.wasFailed(test)) return tests.xmlNodeFor(test);
      XmlNode newTestXmlNode = testXmlNodeWriter.addNewTestXmlNode(xmlRoot, test);
      tests.addXmlNode(test, newTestXmlNode);
      return newTestXmlNode;
    }
  
    /**
     * A test failed.
     * @param test the test.
     * @param failedAssertion the failed assertion.
     */
    public final void addFailure(Test test, AssertionFailedError failedAssertion) {
      addFailure(test, (Throwable)failedAssertion);
    }
  
    /**
     * A test failed.
     * @param test the test.
     * @param error the exception.
     */
    public final void addFailure(Test test, Throwable error) {
      XmlNode errorXmlNode = formatError(FAILURE, test, error);
      onFailureOrError(test, error, errorXmlNode);
    }
  
    /**
     * An error occurred while running the test.
     * @param test the test.
     * @param error the error.
     */
    public final void addError(Test test, Throwable error) {
      XmlNode errorXmlNode = formatError(ERROR, test, error);
      onFailureOrError(test, error, errorXmlNode);
    }
  
    private XmlNode formatError(String type, Test test, Throwable error) {
      if (test != null) {
        endTest(test);
        tests.failed(test);
      }
      XmlNode errorXmlNode = xmlForFailed(test).addNewNode(type);
      writeErrorAndStackTrace(error, errorXmlNode);
      return errorXmlNode;
    }
  
    private XmlNode xmlForFailed(Test test) {
      if (test != null) return tests.xmlNodeFor(test);
      return xmlRoot;
    }
  
    /**
     * Writes the stack trace and message of the given error to the given XML node.
     * @param error the given error.
     * @param errorXmlNode the XML node to write to.
     */
    protected final void writeErrorAndStackTrace(Throwable error, XmlNode errorXmlNode) {
      testXmlNodeWriter.writeErrorAndStackTrace(errorXmlNode, error);
    }
  
    /**
     * Hook for subclasses to add extra functionality after a test failure or a test execution error.
     * @param test the executing test.
     * @param error the reason of the failure or error.
     * @param errorXmlNode the XML element containing information about the test failure or error.
     */
    protected void onFailureOrError(Test test, Throwable error, XmlNode errorXmlNode) {}
  }
  