View Javadoc

1   package junitour;
2   
3   import org.apache.tools.ant.BuildException;
4   import org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner;
5   import org.apache.tools.ant.taskdefs.optional.junit.XMLConstants;
6   import org.apache.tools.ant.taskdefs.optional.junit.JUnitVersionHelper;
7   import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
8   import org.apache.tools.ant.taskdefs.optional.junit.JUnitResultFormatter;
9   import org.apache.tools.ant.util.DOMElementWriter;
10  import org.w3c.dom.Document;
11  import org.w3c.dom.Element;
12  import org.w3c.dom.Text;
13  
14  import java.util.Hashtable;
15  import java.util.Properties;
16  import java.util.Enumeration;
17  import java.util.Map;
18  import java.io.*;
19  
20  import junit.framework.Test;
21  import junit.framework.AssertionFailedError;
22  
23  import javax.xml.parsers.DocumentBuilder;
24  import javax.xml.parsers.DocumentBuilderFactory;
25  import javax.xml.parsers.ParserConfigurationException;
26  
27  /**
28   * <p>Description: Ant - JunitResult Formatter, based on XMLJUnitResultFormatter from
29   * ant-optional tasks 1.5.3
30   * </p>
31   * $Log$
32   * Revision 1.5  2005/07/31 03:16:04  hostlows
33   * using Java 5 generics for making code simpler: less casts, simpler for-loops
34   *
35   * Revision 1.4  2004/12/04 22:09:43  hostlows
36   * direct referencing instead of inheritance of interface XMLConstants, just for it's constants
37   *
38   * Revision 1.3  2004/12/04 22:00:22  hostlows
39   * javadoc enhancements: more comments, more details
40   *
41   *
42   * @author $Author: hostlows $
43   * @version $Revision: 53 $
44   *
45   * @see org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter
46   */
47  public class UnitourResultFormatter implements JUnitResultFormatter {
48  
49    private static final String INCOMPLETE = "incomplete";
50    private static final String ATTR_INCOMPLETES = "incompletes";
51    private int currentSuiteIncompleteCounter;
52    private int suiteErrorCompensationCounter;
53    private int suiteFailureCompensationCounter;
54    static final double THOUSAND_DOUBLE = 1000.0;
55  
56    /** contains additional entries for filtering stack traces: Maven classes
57      * @see #filterLineByJunitour(String)
58      */
59    private static final String [] XTRA_TRACE_FILTERS = {
60      "com.werken.werkz",
61      "org.apache.maven.jelly",
62      "org.apache.commons.jelly",
63      "java.lang.reflect",
64      "sun.reflect.NativeMethodAccessorImpl.invoke",
65      "sun.reflect.DelegatingMethodAccessorImpl.invoke",
66      "com.werken.forehead",
67    };
68  
69    public UnitourResultFormatter() {
70    }
71  
72    private static DocumentBuilder getDocumentBuilder() {
73      try {
74        return DocumentBuilderFactory.newInstance().newDocumentBuilder();
75      }
76      catch (ParserConfigurationException ex) {
77        throw new ExceptionInInitializerError(ex);
78      }
79    }
80  
81    /** The XML document. */
82    private Document doc;
83    /** The wrapper for the whole testsuite. */
84    private Element rootElement;
85    /** Element for the current test. */
86    private Map<Test,Element> testElements = new Hashtable<Test,Element> (10);
87    /** Timing helper. */
88    private Map<Test,Long>  testStarts = new Hashtable<Test,Long>(10);
89    /** Where to write the log to. */
90    private OutputStream out;
91  
92    public void setOutput(OutputStream out) {
93      this.out = out;
94    }
95  
96    public void setSystemOutput(String out) {
97      formatOutput(XMLConstants.SYSTEM_OUT, out);
98    }
99  
100   public void setSystemError(String out) {
101     formatOutput(XMLConstants.SYSTEM_ERR, out);
102   }
103 
104   /**
105    * The whole testsuite started.
106    */
107   public void startTestSuite(JUnitTest suite) {
108 
109 //          super.startTestSuite(suite);
110 
111     currentSuiteIncompleteCounter = 0;
112     suiteFailureCompensationCounter = 0;
113     suiteErrorCompensationCounter = 0;
114 
115     doc = getDocumentBuilder().newDocument();
116     rootElement = doc.createElement(XMLConstants.TESTSUITE);
117     rootElement.setAttribute(XMLConstants.ATTR_NAME, suite.getName());
118 
119     // Output properties
120     Element propsElement = doc.createElement(XMLConstants.PROPERTIES);
121     rootElement.appendChild(propsElement);
122     Properties props = suite.getProperties();
123 
124     filterProperties(doc, props, propsElement);
125   }
126 
127   /**
128    * add and filter the system-properties to the given XML Element Node.
129    * Filters out the properties starting with these keys:
130    * java., maven., sun., line.separator
131    *
132    * @param document     this XML Document is needed for creating a element
133    * @param props        e.g. System Properties
134    * @param propsElement any Element to add these children
135    */
136   static void filterProperties(Document document, Properties props, Element propsElement) {
137     if (props != null) {
138       Enumeration e = props.propertyNames();
139       while (e.hasMoreElements()) {
140         String name = (String) e.nextElement();
141         if (name.startsWith("java.")) continue;
142         if (name.startsWith("maven.")) continue;
143         if (name.startsWith("sun.")) continue;
144         if (name.startsWith("line.separator")) continue;
145         Element propElement = document.createElement(XMLConstants.PROPERTY);
146         propElement.setAttribute(XMLConstants.ATTR_NAME, name);
147         propElement.setAttribute(XMLConstants.ATTR_VALUE, props.getProperty(name));
148         propsElement.appendChild(propElement);
149       }
150     }
151   }
152 
153   /**
154    * The whole testsuite ended.
155    */
156   public void endTestSuite(JUnitTest suite) throws BuildException {
157 
158     rootElement.setAttribute(XMLConstants.ATTR_TESTS, "" + suite.runCount());
159     rootElement.setAttribute(XMLConstants.ATTR_FAILURES, "" + (suite.failureCount()-suiteFailureCompensationCounter));
160     rootElement.setAttribute(XMLConstants.ATTR_ERRORS, "" + (suite.errorCount()-suiteErrorCompensationCounter));
161 
162     // if( suite instanceof UnitoJUnitTest) {
163     rootElement.setAttribute(ATTR_INCOMPLETES, "" + currentSuiteIncompleteCounter);
164 
165     rootElement.setAttribute(XMLConstants.ATTR_TIME, "" + (suite.getRunTime() / THOUSAND_DOUBLE));
166     if (out != null) {
167       Writer wri = null;
168       try {
169         wri = new OutputStreamWriter(out, "UTF8");
170         wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
171         (new DOMElementWriter()).write(rootElement, wri, 0, "  ");
172         wri.flush();
173       }
174       catch (UnsupportedEncodingException exc) {
175         throw new BuildException("Unable to write log file", exc);
176       }
177       catch (IOException ex) {
178         throw new BuildException("Unable to write log file", ex);
179       }
180       finally {
181         if (out != System.out && out != System.err) {
182           if (wri != null) {
183             try {
184               wri.close();
185             }
186             catch (IOException e) {
187             }
188           }
189         }
190       }
191     }
192   }
193 
194   /**
195    * Interface TestListener.
196    *
197    * <p>A new Test is started.
198    */
199   public void startTest(Test t) {
200     testStarts.put(t, new Long(System.currentTimeMillis()));
201 
202     Element currentTest = doc.createElement(XMLConstants.TESTCASE);
203     currentTest.setAttribute(XMLConstants.ATTR_NAME,
204                              JUnitVersionHelper.getTestCaseName(t));
205     rootElement.appendChild(currentTest);
206     testElements.put(t, currentTest);
207   }
208 
209   /**
210    * Interface TestListener.
211    *
212    * <p>A Test is finished.
213    */
214   public void endTest(Test test) {
215     Element currentTest = testElements.get(test);
216 
217     // Fix for bug #5637 - if a junit.extensions.TestSetup is
218     // used and throws an exception during setUp then startTest
219     // would never have been called
220     if (currentTest == null) {
221       startTest(test);
222       currentTest = testElements.get(test);
223     }
224 
225     Long testStartTime = testStarts.get(test);
226     currentTest.setAttribute(XMLConstants.ATTR_TIME, String.valueOf((double) (System.currentTimeMillis() -
227                                                                  testStartTime.longValue()) / THOUSAND_DOUBLE));
228   }
229 
230   /**
231    * implementing interface TestListener
232    *
233    * <p>Will be called, when the test failed </p>
234    */
235   public void addFailure(Test test, Throwable t) {
236 
237     final String name = t.getClass().getName();
238     debug(name, "addFailure", "testing type");
239     final boolean instance = UnitTestIncomplete.class.isInstance(t);
240     debug(name, "addFailure", "instance of UnitTestIncomplete ?" + instance);
241 //          final boolean assignable = t.getClass().isAssignableFrom(UnitTestIncomplete.class);
242 //          System.err.println(" "+name +" <- is assignable of UnitTestIncomplete ?"+( assignable )+" !!!!!!!!!!!!");
243     if (UnitourResultFormatter.isIncompleteTest(t) ||
244             UnitTestIncomplete.class.isAssignableFrom(t.getClass())) {
245       debug(name, "addFailure", "THIS is AN IN-COMPLETE TEST !!!!!!!!!!!!!!!!");
246       addIncomplete(test, t);
247       suiteFailureCompensationCounter++;
248     }
249     else {
250       debug(name, "addFailure", "THIS is an normal error, instead of an IN-COMPLETE TEST !!!!!!!!!!!!!!!!");
251       formatError(XMLConstants.FAILURE, test, t);
252     }
253   }
254 
255   private void addIncomplete(Test test, Throwable t) {
256     formatError(INCOMPLETE, test, t);
257     currentSuiteIncompleteCounter++;
258   }
259 
260   private void debug(final String name, String method, String message) {
261 //    final String indent = "    ";
262 //    System.err.println(new StringBuffer().append(indent).append(getClass().getName()).append(" ").
263 //                       append(method).append("() [").append(name).append("] ").
264 //                       append(message));
265   }
266 
267   /**
268    * implementing interface TestListener for JUnit &gt; 3.4.
269    *
270    * <p>Will be called, when the test failed </p>
271    */
272   public void addFailure(Test test, AssertionFailedError t) {
273     addFailure(test, (Throwable) t);
274   }
275 
276   /**
277    * implementing interface TestListener
278    *
279    * <p>Will be called, when an error occured while running any test </p>
280    */
281   public void addError(Test test, Throwable t) {
282     // OLD DEBUGGING code:
283     //  just to check , if UnitTestIncomplete - handling works....
284 
285     //final String name = t.getClass().getName();
286     //System.err.println("******************************** testing type of "+name+" !!!!!!!!!!!!!!!!");
287     //System.err.println("******************************** "+name +" is instnce of UnitTestIncomplete ?"+(t instanceof UnitTestIncomplete )+" !!!!!!!!!!!!!!!!");
288     //final boolean instance = UnitTestIncomplete.class.isInstance(t);
289     //System.err.println("******************************* "+name +" is instance of UnitTestIncomplete ?"+( instance )+" !!!!!!!!!!!!");
290     //final boolean assignable = t.getClass().isAssignableFrom(UnitTestIncomplete.class.getClass());
291     //System.err.println("******************************* "+name +" <- is instance of UnitTestIncomplete ?"+( assignable )+" !!!!!!!!!!!!");
292 
293     if (isIncompleteTest(t)) {
294       addIncomplete(test, t);
295       suiteErrorCompensationCounter++;
296     }
297     else {
298       formatError(XMLConstants.ERROR, test, t);
299     }
300   }
301 
302   static boolean isIncompleteTest(Throwable t) {
303     final boolean is = UnitTestIncompleteError.isTestIncompleteError(t) ||
304             UnitTestIncomplete.class.isAssignableFrom(t.getClass());
305     return is;
306   }
307 
308   /** uses {@link #filterStack(String)} to handle/filter Maven classes
309    */
310   private void formatError(String type, Test test, Throwable t) {
311     if (test != null) {
312       endTest(test);
313     }
314 
315     Element nested = doc.createElement(type);
316     Element currentTest;
317     if (test != null) {
318       currentTest = (Element) testElements.get(test);
319     }
320     else {
321       currentTest = rootElement;
322     }
323 
324     currentTest.appendChild(nested);
325 
326     String message = t.getMessage();
327     if (message != null && message.length() > 0) {
328       nested.setAttribute(XMLConstants.ATTR_MESSAGE, t.getMessage());
329     }
330     nested.setAttribute(XMLConstants.ATTR_TYPE, t.getClass().getName());
331 
332     String strace = filterStack(JUnitTestRunner.getFilteredTrace(t));
333     Text trace = doc.createTextNode(strace);
334     nested.appendChild(trace);
335   }
336 
337   private void formatOutput(String type, String output) {
338     Element nested = doc.createElement(type);
339     rootElement.appendChild(nested);
340     nested.appendChild(doc.createCDATASection(output));
341   }
342 
343   /**
344    * Filters stack traces: removes internal JUnit and Maven classes <br/>
345    * Just a copy of {@link JUnitTestRunner#filterStack(String)}, but modified,
346    * to use {@link #filterLineByJunitour}
347    */
348   public String filterStack(String stack) {
349       StringWriter sw = new StringWriter();
350       PrintWriter pw = new PrintWriter(sw);
351       StringReader sr = new StringReader(stack);
352       BufferedReader br = new BufferedReader(sr);
353 
354       String line;
355       try {
356           while ((line = br.readLine()) != null) {
357               if (!filterLineByJunitour(line)) {
358                   pw.println(line);
359               }
360           }
361       } catch (Exception IOException) {
362           return stack; // return the stack unfiltered
363       }
364       return sw.toString();
365   }
366 
367   /** I'm lazy ;-) and don't want to reinvent the wheel:  <br/>
368    * It's just a copy of {@link JUnitTestRunner#filterLine(String)}, but uses
369    * {@link #XTRA_TRACE_FILTERS} for filtering MAVEN classes from stack traces... </br>
370    * Sorry, guys, but why was the filterLine method static ? </br>
371    * Please make the code more customizable, next time! (just as example: Interface for a
372    * 'LineFilter' or a setter for the Filters attribute...)
373    *
374    * @param line
375    * @return true, if line should be ignored (contains any class from XTRA_TRACE_FILTERS)
376    * @see #XTRA_TRACE_FILTERS
377    */
378   boolean filterLineByJunitour(String line) {
379       for (int i = 0; i < XTRA_TRACE_FILTERS.length; i++) {
380           if (line.indexOf(XTRA_TRACE_FILTERS[i]) > 0) {
381               return true;
382           }
383       }
384       return false;
385   }
386 
387 }