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