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