001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020 package org.apache.geronimo.mavenplugins.geronimo.reporting;
021
022 import java.io.BufferedInputStream;
023 import java.io.BufferedWriter;
024 import java.io.File;
025 import java.io.FileInputStream;
026 import java.io.FileNotFoundException;
027 import java.io.FileOutputStream;
028 import java.io.IOException;
029 import java.io.InputStream;
030 import java.io.OutputStreamWriter;
031 import java.io.PrintWriter;
032 import java.io.UnsupportedEncodingException;
033 import java.io.FileWriter;
034 import java.text.NumberFormat;
035 import java.util.ArrayList;
036 import java.util.Collections;
037 import java.util.Enumeration;
038 import java.util.Iterator;
039 import java.util.List;
040 import java.util.Locale;
041 import java.util.Properties;
042
043 import org.apache.commons.logging.Log;
044 import org.apache.commons.logging.LogFactory;
045
046 import org.apache.maven.surefire.report.ReporterException;
047 import org.apache.maven.surefire.util.PrettyPrintXMLWriter;
048
049 import org.codehaus.plexus.util.FileUtils;
050 import org.codehaus.plexus.util.IOUtil;
051 import org.codehaus.plexus.util.StringUtils;
052 import org.codehaus.plexus.util.xml.Xpp3Dom;
053 import org.codehaus.plexus.util.xml.Xpp3DomWriter;
054
055 /**
056 * A reporter that generates Surefire result data, so the Surefire report can be used.
057 *
058 * @version $Rev: 476061 $ $Date: 2006-11-17 01:36:50 -0500 (Fri, 17 Nov 2006) $
059 */
060 public class SurefireReporter
061 implements Reporter
062 {
063 private static final Log log = LogFactory.getLog(SurefireReporter.class);
064
065 private static final String LS = System.getProperty("line.separator");
066
067 /**
068 * The name of the test (goal name).
069 */
070 private String testName;
071
072 /**
073 * The input log file, may or may not exist.
074 */
075 private File logFile;
076
077 /**
078 * The failure cause.
079 */
080 private Throwable failureCause;
081
082 /**
083 * @parameter expression="${project.build.directory}/surefire-reports"
084 */
085 private File reportsDirectory = null;
086
087 /**
088 * The file where the test output text will be written.
089 */
090 private File outputFile;
091
092 /**
093 * The file where the text result xml will be written.
094 */
095 private File reportFile;
096
097 /**
098 * The time when the test started.
099 */
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, "<", "<");
310 return StringUtils.replace(s, ">", ">");
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 }