2011/08/05 - Jakarta Cactus has been retired.

For more information, please explore the Attic.

View Javadoc

1   /* 
2    * ========================================================================
3    * 
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   * 
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * 
19   * ========================================================================
20   */
21  package org.apache.cactus.internal.server.runner;
22  
23  import java.text.NumberFormat;
24  import java.util.Locale;
25  
26  import junit.framework.AssertionFailedError;
27  import junit.framework.Test;
28  import junit.framework.TestFailure;
29  import junit.framework.TestListener;
30  import junit.framework.TestResult;
31  
32  import org.apache.cactus.internal.util.JUnitVersionHelper;
33  import org.apache.cactus.internal.util.StringUtil;
34  
35  /**
36   * Format the test results in XML.
37   *
38   * @version $Id: XMLFormatter.java 239169 2005-05-05 09:21:54Z vmassol $
39   */
40  public class XMLFormatter implements XMLConstants, TestListener
41  {
42      /**
43       * Default stack filter patterns.
44       */
45      private static final String[] DEFAULT_STACK_FILTER_PATTERNS = new String[]
46          {
47              "org.apache.cactus.AbstractTestCase",
48              "org.apache.cactus.AbstractWebTestCase",
49              "org.apache.cactus.FilterTestCase",
50              "org.apache.cactus.JspTestCase",
51              "org.apache.cactus.ServletTestCase",
52              "junit.framework.TestCase",
53              "junit.framework.TestResult",
54              "junit.framework.TestSuite",
55              "junit.framework.Assert.", // don't filter AssertionFailure
56              "java.lang.reflect.Method.invoke("
57          };
58  
59      /**
60       * (optional) Name of the XSL stylesheet to put in the returned XML string
61       * so that the browser will try to apply it (IE at least, I don't know
62       * about the others).
63       */
64      private String xslFileName;
65  
66      /**
67       * The name of the test suite class.
68       */
69      private String suiteClassName;
70  
71      /**
72       * Duration it took to execute all the tests.
73       */
74      private long totalDuration;
75  
76      /**
77       * Encoding to use for the returned XML. Defaults to "UTF-8". 
78       */
79      private String encoding = "UTF-8";
80      
81      /**
82       * Time current test was started.
83       */
84      private long currentTestStartTime;
85  
86      /**
87       * The number format used to convert durations into strings. Don't use the
88       * default locale for that, because the resulting string needs to use 
89       * dotted decimal notation for an XSLT transformation to work correctly.
90       */
91      private NumberFormat durationFormat = NumberFormat.getInstance(Locale.US);
92  
93      /**
94       * XML string containing executed test case results.
95       */
96      private StringBuffer currentTestCaseResults = new StringBuffer();
97  
98      /**
99       * Current test failure (XML string) : failure or error.
100      */
101     private String currentTestFailure;
102 
103     /**
104      * Sets the XSL stylesheet file name to put in the returned XML string
105      * so that the browser will try to apply it (IE at least, I don't know
106      * about the others).
107      *
108      * @param theXslFileName the file name (relative to the webapp root)
109      */
110     public void setXslFileName(String theXslFileName)
111     {
112         this.xslFileName = theXslFileName;
113     }
114 
115     /**
116      * @param theEncoding the encoding to use for the returned XML.
117      */
118     public void setEncoding(String theEncoding)
119     {
120         this.encoding = theEncoding;
121     }
122 
123     /**
124      * @return the encoding to use for the returned XML
125      */
126     public String getEncoding()
127     {
128         return this.encoding;
129     }
130     
131     /**
132      * @return the suite class name
133      */
134     public String getSuiteClassName()
135     {
136         return this.suiteClassName;
137     }
138 
139     /**
140      * Sets the suite class name that was executed.
141      *
142      * @param theSuiteClassName the suite class name
143      */
144     public void setSuiteClassName(String theSuiteClassName)
145     {
146         this.suiteClassName = theSuiteClassName;
147     }
148 
149     /**
150      * @return the total duration as a string
151      */
152     public String getTotalDurationAsString()
153     {
154         return getDurationAsString(this.totalDuration);
155     }
156 
157     /**
158      * Comvert a duration expressed as a long into a string.
159      *
160      * @param theDuration the duration to convert to string
161      * @return the total duration as a string
162      */
163     private String getDurationAsString(long theDuration)
164     {
165         return durationFormat.format((double) theDuration / 1000);
166     }
167 
168     /**
169      * Sets the duration it took to execute all the tests.
170      *
171      * @param theDuration the time it took
172      */
173     public void setTotalDuration(long theDuration)
174     {
175         this.totalDuration = theDuration;
176     }
177 
178     /**
179      * Formats the test result as an XML string.
180      *
181      * @param theResult the test result object
182      * @return the XML string representation of the test results
183      */
184     public String toXML(TestResult theResult)
185     {
186         StringBuffer xml = new StringBuffer();
187 
188         xml.append("<?xml version=\"1.0\" encoding=\"" + getEncoding()
189             + "\"?>");
190 
191         if (this.xslFileName != null)
192         {
193             xml.append("<?xml-stylesheet type=\"text/xsl\" " + "href=\""
194                 + this.xslFileName + "\"?>");
195         }
196 
197         xml.append("<" + TESTSUITES + ">");
198 
199         xml.append("<" + TESTSUITE + " " + ATTR_NAME + "=\""
200             + getSuiteClassName() + "\" " + ATTR_TESTS + "=\""
201             + theResult.runCount() + "\" " + ATTR_FAILURES + "=\""
202             + theResult.failureCount() + "\" " + ATTR_ERRORS + "=\""
203             + theResult.errorCount() + "\" " + ATTR_TIME + "=\""
204             + getTotalDurationAsString() + "\">");
205 
206         xml.append(this.currentTestCaseResults.toString());
207 
208         xml.append("</" + TESTSUITE + ">");
209         xml.append("</" + TESTSUITES + ">");
210 
211         return xml.toString();
212     }
213 
214     /**
215      * Event called by the base test runner when the test starts.
216      *
217      * @param theTest the test object being executed
218      */
219     public void startTest(Test theTest)
220     {
221         this.currentTestStartTime = System.currentTimeMillis();
222         this.currentTestFailure = null;
223     }
224 
225     /**
226      * Event called by the base test runner when the test fails with an error.
227      *
228      * @param theTest the test object that failed
229      * @param theThrowable the exception that was thrown
230      */
231     public void addError(Test theTest, Throwable theThrowable)
232     {
233         TestFailure failure = new TestFailure(theTest, theThrowable);
234         StringBuffer xml = new StringBuffer();
235 
236         xml.append("<" + ERROR + " " + ATTR_MESSAGE + "=\""
237             + xmlEncode(failure.thrownException().getMessage()) + "\" "
238             + ATTR_TYPE + "=\""
239             + failure.thrownException().getClass().getName() + "\">");
240         xml.append(xmlEncode(StringUtil.exceptionToString(
241             failure.thrownException(), DEFAULT_STACK_FILTER_PATTERNS)));
242         xml.append("</" + ERROR + ">");
243 
244         this.currentTestFailure = xml.toString();
245     }
246 
247     /**
248      * Event called by the base test runner when the test fails with a failure.
249      *
250      * @param theTest the test object that failed
251      * @param theError the exception that was thrown
252      */
253     public void addFailure(Test theTest, AssertionFailedError theError)
254     {
255         TestFailure failure = new TestFailure(theTest, theError);
256         StringBuffer xml = new StringBuffer();
257 
258         xml.append("<" + FAILURE + " " + ATTR_MESSAGE + "=\""
259             + xmlEncode(failure.thrownException().getMessage()) + "\" "
260             + ATTR_TYPE + "=\""
261             + failure.thrownException().getClass().getName() + "\">");
262         xml.append(xmlEncode(StringUtil.exceptionToString(
263             failure.thrownException(), DEFAULT_STACK_FILTER_PATTERNS)));
264         xml.append("</" + FAILURE + ">");
265 
266         this.currentTestFailure = xml.toString();
267     }
268 
269     /**
270      * Event called by the base test runner when the test ends.
271      *
272      * @param theTest the test object being executed
273      */
274     public void endTest(Test theTest)
275     {
276         StringBuffer xml = new StringBuffer();
277         String duration = getDurationAsString(System.currentTimeMillis()
278             - this.currentTestStartTime);
279 
280         xml.append("<" + TESTCASE + " " + ATTR_NAME + "=\""
281             + JUnitVersionHelper.getTestCaseName(theTest) + "\" "
282             + ATTR_TIME + "=\"" + duration + "\">");
283 
284         if (this.currentTestFailure != null)
285         {
286             xml.append(this.currentTestFailure);
287         }
288 
289         xml.append("</" + TESTCASE + ">");
290 
291         this.currentTestCaseResults.append(xml.toString());
292     }
293 
294     /**
295      * Escapes reserved XML characters.
296      *
297      * @param theString the string to escape
298      * @return the escaped string
299      */
300     private String xmlEncode(String theString)
301     {
302         String newString;
303 
304         // It is important to replace the "&" first as the other replacements
305         // also introduces "&" chars ...
306         newString = StringUtil.replace(theString, '&', "&amp;");
307 
308         newString = StringUtil.replace(newString, '<', "&lt;");
309         newString = StringUtil.replace(newString, '>', "&gt;");
310         newString = StringUtil.replace(newString, '\"', "&quot;");
311 
312         return newString;
313     }
314 }