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.Reporter;
9 import org.testng.internal.Utils;
10 import org.testng.xml.XmlSuite;
11 
12 import java.io.File;
13 import java.text.SimpleDateFormat;
14 import java.util.Collection;
15 import java.util.Date;
16 import java.util.LinkedHashSet;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Properties;
20 import java.util.Set;
21 import java.util.TimeZone;
22 
23 /**
24  * The main entry for the XML generation operation
25  *
26  * @author Cosmin Marginean, Mar 16, 2007
27  */
28 public class XMLReporter implements IReporter {
29   public static final String FILE_NAME = "testng-results.xml";
30 
31   private final XMLReporterConfig config = new XMLReporterConfig();
32   private XMLStringBuffer rootBuffer;
33 
34   @Override
generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory)35   public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
36       String outputDirectory) {
37     if (Utils.isStringEmpty(config.getOutputDirectory())) {
38       config.setOutputDirectory(outputDirectory);
39     }
40 
41     // Calculate passed/failed/skipped
42     int passed = 0;
43     int failed = 0;
44     int skipped = 0;
45     for (ISuite s : suites) {
46       for (ISuiteResult sr : s.getResults().values()) {
47         ITestContext testContext = sr.getTestContext();
48         passed += testContext.getPassedTests().size();
49         failed += testContext.getFailedTests().size();
50         skipped += testContext.getSkippedTests().size();
51       }
52     }
53 
54     rootBuffer = new XMLStringBuffer();
55     Properties p = new Properties();
56     p.put("passed", passed);
57     p.put("failed", failed);
58     p.put("skipped", skipped);
59     p.put("total", passed + failed + skipped);
60     rootBuffer.push(XMLReporterConfig.TAG_TESTNG_RESULTS, p);
61     writeReporterOutput(rootBuffer);
62     for (ISuite suite : suites) {
63       writeSuite(suite.getXmlSuite(), suite);
64     }
65     rootBuffer.pop();
66     Utils.writeUtf8File(config.getOutputDirectory(), FILE_NAME, rootBuffer, null /* no prefix */);
67   }
68 
writeReporterOutput(XMLStringBuffer xmlBuffer)69   private void writeReporterOutput(XMLStringBuffer xmlBuffer) {
70     // TODO: Cosmin - maybe a <line> element isn't indicated for each line
71     xmlBuffer.push(XMLReporterConfig.TAG_REPORTER_OUTPUT);
72     List<String> output = Reporter.getOutput();
73     for (String line : output) {
74       if (line != null) {
75         xmlBuffer.push(XMLReporterConfig.TAG_LINE);
76         xmlBuffer.addCDATA(line);
77         xmlBuffer.pop();
78       }
79     }
80     xmlBuffer.pop();
81   }
82 
writeSuite(XmlSuite xmlSuite, ISuite suite)83   private void writeSuite(XmlSuite xmlSuite, ISuite suite) {
84     switch (config.getFileFragmentationLevel()) {
85     case XMLReporterConfig.FF_LEVEL_NONE:
86       writeSuiteToBuffer(rootBuffer, suite);
87       break;
88     case XMLReporterConfig.FF_LEVEL_SUITE:
89     case XMLReporterConfig.FF_LEVEL_SUITE_RESULT:
90       File suiteFile = referenceSuite(rootBuffer, suite);
91       writeSuiteToFile(suiteFile, suite);
92     }
93   }
94 
writeSuiteToFile(File suiteFile, ISuite suite)95   private void writeSuiteToFile(File suiteFile, ISuite suite) {
96     XMLStringBuffer xmlBuffer = new XMLStringBuffer();
97     writeSuiteToBuffer(xmlBuffer, suite);
98     File parentDir = suiteFile.getParentFile();
99     if (parentDir.exists() || suiteFile.getParentFile().mkdirs()) {
100       Utils.writeFile(parentDir.getAbsolutePath(), FILE_NAME, xmlBuffer.toXML());
101     }
102   }
103 
referenceSuite(XMLStringBuffer xmlBuffer, ISuite suite)104   private File referenceSuite(XMLStringBuffer xmlBuffer, ISuite suite) {
105     String relativePath = suite.getName() + File.separatorChar + FILE_NAME;
106     File suiteFile = new File(config.getOutputDirectory(), relativePath);
107     Properties attrs = new Properties();
108     attrs.setProperty(XMLReporterConfig.ATTR_URL, relativePath);
109     xmlBuffer.addEmptyElement(XMLReporterConfig.TAG_SUITE, attrs);
110     return suiteFile;
111   }
112 
writeSuiteToBuffer(XMLStringBuffer xmlBuffer, ISuite suite)113   private void writeSuiteToBuffer(XMLStringBuffer xmlBuffer, ISuite suite) {
114     xmlBuffer.push(XMLReporterConfig.TAG_SUITE, getSuiteAttributes(suite));
115     writeSuiteGroups(xmlBuffer, suite);
116 
117     Map<String, ISuiteResult> results = suite.getResults();
118     XMLSuiteResultWriter suiteResultWriter = new XMLSuiteResultWriter(config);
119     for (Map.Entry<String, ISuiteResult> result : results.entrySet()) {
120       suiteResultWriter.writeSuiteResult(xmlBuffer, result.getValue());
121     }
122 
123     xmlBuffer.pop();
124   }
125 
writeSuiteGroups(XMLStringBuffer xmlBuffer, ISuite suite)126   private void writeSuiteGroups(XMLStringBuffer xmlBuffer, ISuite suite) {
127     xmlBuffer.push(XMLReporterConfig.TAG_GROUPS);
128     Map<String, Collection<ITestNGMethod>> methodsByGroups = suite.getMethodsByGroups();
129     for (Map.Entry<String, Collection<ITestNGMethod>> entry : methodsByGroups.entrySet()) {
130       Properties groupAttrs = new Properties();
131       groupAttrs.setProperty(XMLReporterConfig.ATTR_NAME, entry.getKey());
132       xmlBuffer.push(XMLReporterConfig.TAG_GROUP, groupAttrs);
133       Set<ITestNGMethod> groupMethods = getUniqueMethodSet(entry.getValue());
134       for (ITestNGMethod groupMethod : groupMethods) {
135         Properties methodAttrs = new Properties();
136         methodAttrs.setProperty(XMLReporterConfig.ATTR_NAME, groupMethod.getMethodName());
137         methodAttrs.setProperty(XMLReporterConfig.ATTR_METHOD_SIG, groupMethod.toString());
138         methodAttrs.setProperty(XMLReporterConfig.ATTR_CLASS, groupMethod.getRealClass().getName());
139         xmlBuffer.addEmptyElement(XMLReporterConfig.TAG_METHOD, methodAttrs);
140       }
141       xmlBuffer.pop();
142     }
143     xmlBuffer.pop();
144   }
145 
getSuiteAttributes(ISuite suite)146   private Properties getSuiteAttributes(ISuite suite) {
147     Properties props = new Properties();
148     props.setProperty(XMLReporterConfig.ATTR_NAME, suite.getName());
149 
150     // Calculate the duration
151     Map<String, ISuiteResult> results = suite.getResults();
152     Date minStartDate = new Date();
153     Date maxEndDate = null;
154     // TODO: We could probably optimize this in order not to traverse this twice
155     for (Map.Entry<String, ISuiteResult> result : results.entrySet()) {
156       ITestContext testContext = result.getValue().getTestContext();
157       Date startDate = testContext.getStartDate();
158       Date endDate = testContext.getEndDate();
159       if (minStartDate.after(startDate)) {
160         minStartDate = startDate;
161       }
162       if (maxEndDate == null || maxEndDate.before(endDate)) {
163         maxEndDate = endDate != null ? endDate : startDate;
164       }
165     }
166 
167     // The suite could be completely empty
168     if (maxEndDate == null) {
169       maxEndDate = minStartDate;
170     }
171     addDurationAttributes(config, props, minStartDate, maxEndDate);
172     return props;
173   }
174 
175   /**
176    * Add started-at, finished-at and duration-ms attributes to the <suite> tag
177    */
addDurationAttributes(XMLReporterConfig config, Properties attributes, Date minStartDate, Date maxEndDate)178   public static void addDurationAttributes(XMLReporterConfig config, Properties attributes,
179       Date minStartDate, Date maxEndDate) {
180     SimpleDateFormat format = new SimpleDateFormat(config.getTimestampFormat());
181     TimeZone utc = TimeZone.getTimeZone("UTC");
182     format.setTimeZone(utc);
183     String startTime = format.format(minStartDate);
184     String endTime = format.format(maxEndDate);
185     long duration = maxEndDate.getTime() - minStartDate.getTime();
186 
187     attributes.setProperty(XMLReporterConfig.ATTR_STARTED_AT, startTime);
188     attributes.setProperty(XMLReporterConfig.ATTR_FINISHED_AT, endTime);
189     attributes.setProperty(XMLReporterConfig.ATTR_DURATION_MS, Long.toString(duration));
190   }
191 
getUniqueMethodSet(Collection<ITestNGMethod> methods)192   private Set<ITestNGMethod> getUniqueMethodSet(Collection<ITestNGMethod> methods) {
193     Set<ITestNGMethod> result = new LinkedHashSet<>();
194     for (ITestNGMethod method : methods) {
195       result.add(method);
196     }
197     return result;
198   }
199 
200   // TODO: This is not the smartest way to implement the config
getFileFragmentationLevel()201   public int getFileFragmentationLevel() {
202     return config.getFileFragmentationLevel();
203   }
204 
setFileFragmentationLevel(int fileFragmentationLevel)205   public void setFileFragmentationLevel(int fileFragmentationLevel) {
206     config.setFileFragmentationLevel(fileFragmentationLevel);
207   }
208 
getStackTraceOutputMethod()209   public int getStackTraceOutputMethod() {
210     return config.getStackTraceOutputMethod();
211   }
212 
setStackTraceOutputMethod(int stackTraceOutputMethod)213   public void setStackTraceOutputMethod(int stackTraceOutputMethod) {
214     config.setStackTraceOutputMethod(stackTraceOutputMethod);
215   }
216 
getOutputDirectory()217   public String getOutputDirectory() {
218     return config.getOutputDirectory();
219   }
220 
setOutputDirectory(String outputDirectory)221   public void setOutputDirectory(String outputDirectory) {
222     config.setOutputDirectory(outputDirectory);
223   }
224 
isGenerateGroupsAttribute()225   public boolean isGenerateGroupsAttribute() {
226     return config.isGenerateGroupsAttribute();
227   }
228 
setGenerateGroupsAttribute(boolean generateGroupsAttribute)229   public void setGenerateGroupsAttribute(boolean generateGroupsAttribute) {
230     config.setGenerateGroupsAttribute(generateGroupsAttribute);
231   }
232 
isSplitClassAndPackageNames()233   public boolean isSplitClassAndPackageNames() {
234     return config.isSplitClassAndPackageNames();
235   }
236 
setSplitClassAndPackageNames(boolean splitClassAndPackageNames)237   public void setSplitClassAndPackageNames(boolean splitClassAndPackageNames) {
238     config.setSplitClassAndPackageNames(splitClassAndPackageNames);
239   }
240 
getTimestampFormat()241   public String getTimestampFormat() {
242     return config.getTimestampFormat();
243   }
244 
setTimestampFormat(String timestampFormat)245   public void setTimestampFormat(String timestampFormat) {
246     config.setTimestampFormat(timestampFormat);
247   }
248 
isGenerateDependsOnMethods()249   public boolean isGenerateDependsOnMethods() {
250     return config.isGenerateDependsOnMethods();
251   }
252 
setGenerateDependsOnMethods(boolean generateDependsOnMethods)253   public void setGenerateDependsOnMethods(boolean generateDependsOnMethods) {
254     config.setGenerateDependsOnMethods(generateDependsOnMethods);
255   }
256 
setGenerateDependsOnGroups(boolean generateDependsOnGroups)257   public void setGenerateDependsOnGroups(boolean generateDependsOnGroups) {
258     config.setGenerateDependsOnGroups(generateDependsOnGroups);
259   }
260 
isGenerateDependsOnGroups()261   public boolean isGenerateDependsOnGroups() {
262     return config.isGenerateDependsOnGroups();
263   }
264 
setGenerateTestResultAttributes(boolean generateTestResultAttributes)265   public void setGenerateTestResultAttributes(boolean generateTestResultAttributes) {
266     config.setGenerateTestResultAttributes(generateTestResultAttributes);
267   }
268 
isGenerateTestResultAttributes()269   public boolean isGenerateTestResultAttributes() {
270     return config.isGenerateTestResultAttributes();
271   }
272 
273 }
274