1 package org.testng.reporters; 2 3 import org.testng.IReporter; 4 import org.testng.ISuite; 5 import org.testng.ISuiteResult; 6 import org.testng.ITestContext; 7 import org.testng.ITestNGMethod; 8 import org.testng.ITestResult; 9 import org.testng.collections.ListMultiMap; 10 import org.testng.collections.Lists; 11 import org.testng.collections.Maps; 12 import org.testng.collections.Sets; 13 import org.testng.internal.Utils; 14 import org.testng.xml.XmlSuite; 15 16 import java.io.File; 17 import java.io.PrintWriter; 18 import java.io.StringWriter; 19 import java.net.InetAddress; 20 import java.net.UnknownHostException; 21 import java.text.DecimalFormat; 22 import java.text.DecimalFormatSymbols; 23 import java.util.Calendar; 24 import java.util.Date; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Properties; 28 import java.util.Set; 29 30 public class JUnitReportReporter implements IReporter { 31 32 @Override generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String defaultOutputDirectory)33 public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, 34 String defaultOutputDirectory) { 35 36 Map<Class<?>, Set<ITestResult>> results = Maps.newHashMap(); 37 Map<Class<?>, Set<ITestResult>> failedConfigurations = Maps.newHashMap(); 38 ListMultiMap<Object, ITestResult> befores = Maps.newListMultiMap(); 39 ListMultiMap<Object, ITestResult> afters = Maps.newListMultiMap(); 40 for (ISuite suite : suites) { 41 Map<String, ISuiteResult> suiteResults = suite.getResults(); 42 for (ISuiteResult sr : suiteResults.values()) { 43 ITestContext tc = sr.getTestContext(); 44 addResults(tc.getPassedTests().getAllResults(), results); 45 addResults(tc.getFailedTests().getAllResults(), results); 46 addResults(tc.getSkippedTests().getAllResults(), results); 47 addResults(tc.getFailedConfigurations().getAllResults(), failedConfigurations); 48 for (ITestResult tr : tc.getPassedConfigurations().getAllResults()) { 49 if (tr.getMethod().isBeforeMethodConfiguration()) { 50 befores.put(tr.getInstance(), tr); 51 } 52 if (tr.getMethod().isAfterMethodConfiguration()) { 53 afters.put(tr.getInstance(), tr); 54 } 55 } 56 } 57 } 58 59 // A list of iterators for all the passed configuration, explanation below 60 // ListMultiMap<Class<?>, ITestResult> beforeConfigurations = Maps.newListMultiMap(); 61 // ListMultiMap<Class<?>, ITestResult> afterConfigurations = Maps.newListMultiMap(); 62 // for (Map.Entry<Class<?>, Set<ITestResult>> es : passedConfigurations.entrySet()) { 63 // for (ITestResult tr : es.getValue()) { 64 // ITestNGMethod method = tr.getMethod(); 65 // if (method.isBeforeMethodConfiguration()) { 66 // beforeConfigurations.put(method.getRealClass(), tr); 67 // } 68 // if (method.isAfterMethodConfiguration()) { 69 // afterConfigurations.put(method.getRealClass(), tr); 70 // } 71 // } 72 // } 73 // Map<Object, Iterator<ITestResult>> befores = Maps.newHashMap(); 74 // for (Map.Entry<Class<?>, List<ITestResult>> es : beforeConfigurations.getEntrySet()) { 75 // List<ITestResult> tr = es.getValue(); 76 // for (ITestResult itr : es.getValue()) { 77 // } 78 // } 79 // Map<Class<?>, Iterator<ITestResult>> afters = Maps.newHashMap(); 80 // for (Map.Entry<Class<?>, List<ITestResult>> es : afterConfigurations.getEntrySet()) { 81 // afters.put(es.getKey(), es.getValue().iterator()); 82 // } 83 84 for (Map.Entry<Class<?>, Set<ITestResult>> entry : results.entrySet()) { 85 Class<?> cls = entry.getKey(); 86 Properties p1 = new Properties(); 87 p1.setProperty("name", cls.getName()); 88 Date timeStamp = Calendar.getInstance().getTime(); 89 p1.setProperty(XMLConstants.ATTR_TIMESTAMP, timeStamp.toGMTString()); 90 91 List<TestTag> testCases = Lists.newArrayList(); 92 int failures = 0; 93 int errors = 0; 94 int testCount = 0; 95 float totalTime = 0; 96 97 for (ITestResult tr: entry.getValue()) { 98 TestTag testTag = new TestTag(); 99 100 boolean isSuccess = tr.getStatus() == ITestResult.SUCCESS; 101 if (! isSuccess) { 102 if (tr.getThrowable() instanceof AssertionError) { 103 failures++; 104 } else { 105 errors++; 106 } 107 } 108 109 Properties p2 = new Properties(); 110 p2.setProperty("classname", cls.getName()); 111 p2.setProperty("name", getTestName(tr)); 112 long time = tr.getEndMillis() - tr.getStartMillis(); 113 114 time += getNextConfiguration(befores, tr); 115 time += getNextConfiguration(afters, tr); 116 117 p2.setProperty("time", "" + formatTime(time)); 118 Throwable t = getThrowable(tr, failedConfigurations); 119 if (! isSuccess && t != null) { 120 StringWriter sw = new StringWriter(); 121 PrintWriter pw = new PrintWriter(sw); 122 t.printStackTrace(pw); 123 testTag.message = t.getMessage(); 124 testTag.type = t.getClass().getName(); 125 testTag.stackTrace = sw.toString(); 126 testTag.errorTag = tr.getThrowable() instanceof AssertionError ? "failure" : "error"; 127 } 128 totalTime += time; 129 testCount++; 130 testTag.properties = p2; 131 testCases.add(testTag); 132 } 133 134 p1.setProperty("failures", "" + failures); 135 p1.setProperty("errors", "" + errors); 136 p1.setProperty("name", cls.getName()); 137 p1.setProperty("tests", "" + testCount); 138 p1.setProperty("time", "" + formatTime(totalTime)); 139 try { 140 p1.setProperty(XMLConstants.ATTR_HOSTNAME, InetAddress.getLocalHost().getHostName()); 141 } catch (UnknownHostException e) { 142 // ignore 143 } 144 145 // 146 // Now that we have all the information we need, generate the file 147 // 148 XMLStringBuffer xsb = new XMLStringBuffer(); 149 xsb.addComment("Generated by " + getClass().getName()); 150 151 xsb.push("testsuite", p1); 152 for (TestTag testTag : testCases) { 153 if (testTag.stackTrace == null) { 154 xsb.addEmptyElement("testcase", testTag.properties); 155 } 156 else { 157 xsb.push("testcase", testTag.properties); 158 159 Properties p = new Properties(); 160 if (testTag.message != null) { 161 p.setProperty("message", testTag.message); 162 } 163 p.setProperty("type", testTag.type); 164 xsb.push(testTag.errorTag, p); 165 xsb.addCDATA(testTag.stackTrace); 166 xsb.pop(testTag.errorTag); 167 168 xsb.pop("testcase"); 169 } 170 } 171 xsb.pop("testsuite"); 172 173 String outputDirectory = defaultOutputDirectory + File.separator + "junitreports"; 174 Utils.writeUtf8File(outputDirectory, getFileName(cls), xsb.toXML()); 175 } 176 177 // System.out.println(xsb.toXML()); 178 // System.out.println(""); 179 180 } 181 182 /** 183 * Add the time of the configuration method to this test method. 184 * 185 * The only problem with this method is that the timing of a test method 186 * might not be added to the time of the same configuration method that ran before 187 * it but since they should all be equivalent, this should never be an issue. 188 */ getNextConfiguration(ListMultiMap<Object, ITestResult> configurations, ITestResult tr)189 private long getNextConfiguration(ListMultiMap<Object, ITestResult> configurations, 190 ITestResult tr) 191 { 192 long result = 0; 193 194 List<ITestResult> confResults = configurations.get(tr.getInstance()); 195 Map<ITestNGMethod, ITestResult> seen = Maps.newHashMap(); 196 if (confResults != null) { 197 for (ITestResult r : confResults) { 198 if (! seen.containsKey(r.getMethod())) { 199 result += r.getEndMillis() - r.getStartMillis(); 200 seen.put(r.getMethod(), r); 201 } 202 } 203 confResults.removeAll(seen.values()); 204 } 205 206 return result; 207 } 208 getFileName(Class cls)209 protected String getFileName(Class cls) { 210 return "TEST-" + cls.getName() + ".xml"; 211 } 212 getTestName(ITestResult tr)213 protected String getTestName(ITestResult tr) { 214 return tr.getMethod().getMethodName(); 215 } 216 formatTime(float time)217 private String formatTime(float time) { 218 DecimalFormatSymbols symbols = new DecimalFormatSymbols(); 219 // JUnitReports wants points here, regardless of the locale 220 symbols.setDecimalSeparator('.'); 221 DecimalFormat format = new DecimalFormat("#.###", symbols); 222 format.setMinimumFractionDigits(3); 223 return format.format(time / 1000.0f); 224 } 225 getThrowable(ITestResult tr, Map<Class<?>, Set<ITestResult>> failedConfigurations)226 private Throwable getThrowable(ITestResult tr, 227 Map<Class<?>, Set<ITestResult>> failedConfigurations) { 228 Throwable result = tr.getThrowable(); 229 if (result == null && tr.getStatus() == ITestResult.SKIP) { 230 // Attempt to grab the stack trace from the configuration failure 231 for (Set<ITestResult> failures : failedConfigurations.values()) { 232 for (ITestResult failure : failures) { 233 // Naive implementation for now, eventually, we need to try to find 234 // out if it's this failure that caused the skip since (maybe by 235 // seeing if the class of the configuration method is assignable to 236 // the class of the test method, although that's not 100% fool proof 237 if (failure.getThrowable() != null) { 238 return failure.getThrowable(); 239 } 240 } 241 } 242 } 243 244 return result; 245 } 246 247 static class TestTag { 248 public Properties properties; 249 public String message; 250 public String type; 251 public String stackTrace; 252 public String errorTag; 253 } 254 addResults(Set<ITestResult> allResults, Map<Class<?>, Set<ITestResult>> out)255 private void addResults(Set<ITestResult> allResults, Map<Class<?>, Set<ITestResult>> out) { 256 for (ITestResult tr : allResults) { 257 Class<?> cls = tr.getMethod().getTestClass().getRealClass(); 258 Set<ITestResult> l = out.get(cls); 259 if (l == null) { 260 l = Sets.newHashSet(); 261 out.put(cls, l); 262 } 263 l.add(tr); 264 } 265 } 266 267 } 268