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 }