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