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