View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.geronimo.mavenplugins.geronimo.reporting;
21  
22  import java.io.BufferedInputStream;
23  import java.io.BufferedWriter;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStreamWriter;
31  import java.io.PrintWriter;
32  import java.io.UnsupportedEncodingException;
33  import java.io.FileWriter;
34  import java.text.NumberFormat;
35  import java.util.ArrayList;
36  import java.util.Collections;
37  import java.util.Enumeration;
38  import java.util.Iterator;
39  import java.util.List;
40  import java.util.Locale;
41  import java.util.Properties;
42  
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  
46  import org.apache.maven.surefire.report.ReporterException;
47  import org.apache.maven.surefire.util.PrettyPrintXMLWriter;
48  
49  import org.codehaus.plexus.util.FileUtils;
50  import org.codehaus.plexus.util.IOUtil;
51  import org.codehaus.plexus.util.StringUtils;
52  import org.codehaus.plexus.util.xml.Xpp3Dom;
53  import org.codehaus.plexus.util.xml.Xpp3DomWriter;
54  
55  /**
56   * A reporter that generates Surefire result data, so the Surefire report can be used.
57   *
58   * @version $Rev: 450613 $ $Date: 2006-09-27 15:45:46 -0700 (Wed, 27 Sep 2006) $
59   */
60  public class SurefireReporter
61      implements Reporter
62  {
63      private static final Log log = LogFactory.getLog(SurefireReporter.class);
64  
65      private static final String LS = System.getProperty("line.separator");
66  
67      /**
68       * The name of the test (goal name).
69       */
70      private String testName;
71  
72      /**
73       * The input log file, may or may not exist.
74       */
75      private File logFile;
76  
77      /**
78       * The failure cause.
79       */
80      private Throwable failureCause;
81  
82      /**
83       * @parameter expression="${project.build.directory}/surefire-reports"
84       */
85      private File reportsDirectory = null;
86      
87      /**
88       * The file where the test output text will be written.
89       */
90      private File outputFile;
91  
92      /**
93       * The file where the text result xml will be written.
94       */
95      private File reportFile;
96  
97      /**
98       * The time when the test started.
99       */
100     private long startTime;
101 
102     /**
103      * The number of errors.
104      */
105     private int numErrors = 0;
106 
107     private static final int MS_PER_SEC = 1000;
108 
109     private NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
110 
111     private List results = Collections.synchronizedList(new ArrayList());
112 
113     //
114     // Reporter
115     //
116 
117     public void reportBegin(final Reportable source) {
118         assert source != null;
119 
120         logFile = source.getLogFile();
121         testName = source.getName();
122         startTime = source.getStartTime().getTime();
123 
124         try {
125             FileUtils.forceMkdir(reportsDirectory);
126         }
127         catch (IOException e) {
128             //
129             // HACK: Maybe need to add a throws to beginReport() ?
130             //
131             
132             log.error("Failed to make reports directory: " + reportsDirectory, e);
133         }
134 
135         outputFile = new File(reportsDirectory, testName + ".txt");
136         reportFile = new File(reportsDirectory, "TEST-" + testName + ".xml");
137         if (reportFile.exists()) {
138             reportFile.delete();
139         }
140     }
141 
142     public void reportError(final Throwable cause) {
143         assert cause != null;
144 
145         log.debug("Capturing failed report from cause", cause);
146         
147         this.failureCause = cause;
148     }
149 
150     public void reportEnd() {
151         try {
152             if (logFile.exists()) {
153                 FileUtils.copyFile(logFile, outputFile);
154             }
155         }
156         catch (Exception e) {
157             log.warn("Failed to update outputFile", e);
158         }
159         
160         if (failureCause != null) {
161             try {
162                 boolean append = outputFile.exists();
163                 PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(outputFile, append)));
164                 try {
165                     writer.println("Exception Detail");
166                     writer.println("================");
167                     failureCause.printStackTrace(writer);
168                     writer.flush();
169                 }
170                 finally {
171                     writer.close();
172                 }
173             }
174             catch (Exception e) {
175                 log.warn("Failed to append error detail to outputFile", e);    
176             }
177 
178             testFailed();
179         }
180         else {
181             testSucceeded();
182         }
183 
184         try {
185             testSetCompleted();
186         }
187         catch (ReporterException e) {
188             log.warn("Failed to set test completed", e);
189         }
190     }
191 
192     //
193     // Surefire Support
194     //
195 
196     private void testSetCompleted() throws ReporterException {
197         long runTime = System.currentTimeMillis() - this.startTime;
198 
199         Xpp3Dom testSuite = createTestElement("testsuite", testName, runTime);
200 
201         showProperties(testSuite);
202 
203         testSuite.setAttribute("tests", "1");
204         testSuite.setAttribute("errors", String.valueOf(numErrors));
205         testSuite.setAttribute("skipped", "0");
206         testSuite.setAttribute("failures", "0");
207 
208         for (Iterator i = results.iterator(); i.hasNext();) {
209             Xpp3Dom testcase = (Xpp3Dom) i.next();
210             testSuite.addChild(testcase);
211         }
212 
213         PrintWriter writer = null;
214 
215         try {
216             writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(reportFile), "UTF-8")));
217             writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + LS);
218             Xpp3DomWriter.write(new PrettyPrintXMLWriter(writer), testSuite);
219         }
220         catch (UnsupportedEncodingException e) {
221             throw new ReporterException("Unable to use UTF-8 encoding", e);
222         }
223         catch (FileNotFoundException e) {
224             throw new ReporterException("Unable to create file: " + e.getMessage(), e);
225         }
226         finally {
227             IOUtil.close(writer);
228         }
229     }
230 
231     private Xpp3Dom createTestElement(String element, String testName, long runTime) {
232         Xpp3Dom testCase = new Xpp3Dom(element);
233         testCase.setAttribute("name", testName);
234         testCase.setAttribute("time", elapsedTimeAsString(runTime));
235 
236         return testCase;
237     }
238 
239     private Xpp3Dom createElement(Xpp3Dom element, String testName) {
240         Xpp3Dom component = new Xpp3Dom(testName);
241         element.addChild(component);
242 
243         return component;
244     }
245 
246     private void testSucceeded() {
247         long runTime = System.currentTimeMillis() - this.startTime;
248         Xpp3Dom testCase = createTestElement("testcase", testName, runTime);
249         results.add(testCase);
250     }
251 
252     private void testFailed() {
253         ++numErrors;
254 
255         try {
256             InputStream input = new BufferedInputStream(new FileInputStream(outputFile));
257             int length = input.available();
258             byte[] b = new byte[length];
259             input.read(b, 0, length);
260             writeTestProblems(testName, new String(b));
261         }
262         catch (IOException e) {
263             log.error("Failed to write test problems", e);
264         }
265     }
266 
267     private void writeTestProblems(String testName, String stdErr) {
268         long runTime = System.currentTimeMillis() - this.startTime;
269 
270         Xpp3Dom testCase = createTestElement("testcase", testName, runTime);
271         Xpp3Dom element = createElement(testCase, "failure");
272 
273         element.setAttribute("message", escapeAttribute(getMessage(stdErr)));
274         element.setAttribute("type", getType(stdErr));
275         element.setValue(stdErr);
276 
277         results.add(testCase);
278     }
279 
280     /**
281      * Adds system properties to the XML report.
282      */
283     private void showProperties(Xpp3Dom testSuite) {
284         Xpp3Dom properties = createElement(testSuite, "properties");
285 
286         Properties systemProperties = System.getProperties();
287 
288         if (systemProperties != null) {
289             Enumeration propertyKeys = systemProperties.propertyNames();
290 
291             while (propertyKeys.hasMoreElements()) {
292                 String key = (String) propertyKeys.nextElement();
293                 String value = systemProperties.getProperty(key);
294 
295                 if (value == null) {
296                     value = "null";
297                 }
298 
299                 Xpp3Dom property = createElement(properties, "property");
300 
301                 property.setAttribute("name", key);
302                 property.setAttribute("value", escapeAttribute(value));
303             }
304         }
305     }
306 
307     private static String escapeAttribute(String attribute) {
308         // Shouldn't Xpp3Dom do this itself?
309         String s = StringUtils.replace(attribute, "<", "&lt;");
310         return StringUtils.replace(s, ">", "&gt;");
311     }
312 
313     private Iterator getResults() {
314         return results.iterator();
315     }
316 
317     private String elapsedTimeAsString(long runTime) {
318         return numberFormat.format((double) runTime / MS_PER_SEC);
319     }
320 
321     /**
322      * Gets the messages following the exception type.
323      */
324     private String getMessage(String stdErr) {
325         int beginMarker = stdErr.indexOf("Exception:") + 10;
326         int endMarker = stdErr.indexOf("\n", beginMarker);
327         return stdErr.substring(beginMarker, endMarker);
328     }
329 
330     /**
331      * Gets the type of exception from the stacktrace.
332      */
333     private String getType(String stdErr) {
334         int endMarker = stdErr.indexOf("Exception:") + 9;
335         int beginMarker = stdErr.lastIndexOf("\n", endMarker) + 1;
336         return stdErr.substring(beginMarker, endMarker);
337     }
338 
339     /** 
340      * The generated reports xml file for surefire..
341      * 
342      * @return generated reports xml file
343      */
344     public File getReportsFile() {
345         return this.reportFile;
346     }
347 
348     /**
349      * The text file which holds the stdout or stderr.
350      *
351      * @return File
352      */
353     public File getOutputFile() {
354         return this.outputFile;
355     }
356 }