Home | History | Annotate | Download | only in reporters
      1 package org.testng.reporters;
      2 
      3 
      4 import org.testng.ITestContext;
      5 import org.testng.ITestNGMethod;
      6 import org.testng.ITestResult;
      7 import org.testng.collections.Lists;
      8 import org.testng.collections.Maps;
      9 import org.testng.collections.Sets;
     10 import org.testng.internal.IResultListener2;
     11 import org.testng.internal.Utils;
     12 
     13 import java.net.InetAddress;
     14 import java.net.UnknownHostException;
     15 import java.util.Calendar;
     16 import java.util.Collections;
     17 import java.util.Date;
     18 import java.util.List;
     19 import java.util.Map;
     20 import java.util.Properties;
     21 import java.util.Set;
     22 import java.util.regex.Pattern;
     23 
     24 /**
     25  * A JUnit XML report generator (replacing the original JUnitXMLReporter that was
     26  * based on XML APIs).
     27  *
     28  * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
     29  */
     30 public class JUnitXMLReporter implements IResultListener2 {
     31   private static final Pattern ENTITY= Pattern.compile("&[a-zA-Z]+;.*");
     32   private static final Pattern LESS= Pattern.compile("<");
     33   private static final Pattern GREATER= Pattern.compile(">");
     34   private static final Pattern SINGLE_QUOTE = Pattern.compile("'");
     35   private static final Pattern QUOTE = Pattern.compile("\"");
     36   private static final Map<String, Pattern> ATTR_ESCAPES= Maps.newHashMap();
     37 
     38   static {
     39     ATTR_ESCAPES.put("&lt;", LESS);
     40     ATTR_ESCAPES.put("&gt;", GREATER);
     41     ATTR_ESCAPES.put("&apos;", SINGLE_QUOTE);
     42     ATTR_ESCAPES.put("&quot;", QUOTE);
     43   }
     44 
     45 
     46   /**
     47    * keep lists of all the results
     48    */
     49   private int m_numPassed= 0;
     50   private int m_numFailed= 0;
     51   private int m_numSkipped= 0;
     52   private int m_numFailedButIgnored= 0;
     53   private List<ITestResult> m_allTests =
     54       Collections.synchronizedList(Lists.<ITestResult>newArrayList());
     55   private List<ITestResult> m_configIssues =
     56       Collections.synchronizedList(Lists.<ITestResult>newArrayList());
     57   private Map<String, String> m_fileNameMap = Maps.newHashMap();
     58   private int m_fileNameIncrementer = 0;
     59 
     60   @Override
     61   public void onTestStart(ITestResult result) {
     62   }
     63 
     64   @Override
     65   public void beforeConfiguration(ITestResult tr) {
     66   }
     67 
     68   /**
     69    * Invoked each time a test succeeds.
     70    */
     71   @Override
     72   public void onTestSuccess(ITestResult tr) {
     73     m_allTests.add(tr);
     74     m_numPassed++;
     75   }
     76 
     77   @Override
     78   public void onTestFailedButWithinSuccessPercentage(ITestResult tr) {
     79     m_allTests.add(tr);
     80     m_numFailedButIgnored++;
     81   }
     82 
     83   /**
     84    * Invoked each time a test fails.
     85    */
     86   @Override
     87   public void onTestFailure(ITestResult tr) {
     88     m_allTests.add(tr);
     89     m_numFailed++;
     90   }
     91 
     92   /**
     93    * Invoked each time a test is skipped.
     94    */
     95   @Override
     96   public void onTestSkipped(ITestResult tr) {
     97     m_allTests.add(tr);
     98     m_numSkipped++;
     99   }
    100 
    101   /**
    102    * Invoked after the test class is instantiated and before
    103    * any configuration method is called.
    104    *
    105    */
    106   @Override
    107   public void onStart(ITestContext context) {
    108 
    109   }
    110 
    111   /**
    112    * Invoked after all the tests have run and all their
    113    * Configuration methods have been called.
    114    *
    115    */
    116   @Override
    117   public void onFinish(ITestContext context) {
    118 	generateReport(context);
    119     resetAll();
    120   }
    121 
    122   /**
    123    * @see org.testng.IConfigurationListener#onConfigurationFailure(org.testng.ITestResult)
    124    */
    125   @Override
    126   public void onConfigurationFailure(ITestResult itr) {
    127     m_configIssues.add(itr);
    128   }
    129 
    130   /**
    131    * @see org.testng.IConfigurationListener#onConfigurationSkip(org.testng.ITestResult)
    132    */
    133   @Override
    134   public void onConfigurationSkip(ITestResult itr) {
    135     m_configIssues.add(itr);
    136   }
    137 
    138   /**
    139    * @see org.testng.IConfigurationListener#onConfigurationSuccess(org.testng.ITestResult)
    140    */
    141   @Override
    142   public void onConfigurationSuccess(ITestResult itr) {
    143   }
    144 
    145   /**
    146    * generate the XML report given what we know from all the test results
    147    */
    148   protected void generateReport(ITestContext context) {
    149 
    150       XMLStringBuffer document= new XMLStringBuffer();
    151       document.addComment("Generated by " + getClass().getName());
    152 
    153       Properties attrs= new Properties();
    154       attrs.setProperty(XMLConstants.ATTR_ERRORS, "0");
    155       attrs.setProperty(XMLConstants.ATTR_FAILURES, "" + m_numFailed);
    156       try {
    157         attrs.setProperty(XMLConstants.ATTR_HOSTNAME, InetAddress.getLocalHost().getHostName());
    158       } catch (UnknownHostException e) {
    159         // ignore
    160       }
    161       Set<String> packages = getPackages(context);
    162       if (packages.size() > 0) {
    163         attrs.setProperty(XMLConstants.ATTR_NAME, context.getCurrentXmlTest().getName());
    164 //        attrs.setProperty(XMLConstants.ATTR_PACKAGE, packages.iterator().next());
    165       }
    166 
    167       attrs.setProperty(XMLConstants.ATTR_TESTS, "" + m_allTests.size());
    168       attrs.setProperty(XMLConstants.ATTR_TIME, ""
    169           + ((context.getEndDate().getTime() - context.getStartDate().getTime()) / 1000.0));
    170 
    171       Date timeStamp = Calendar.getInstance().getTime();
    172       attrs.setProperty(XMLConstants.ATTR_TIMESTAMP, timeStamp.toGMTString());
    173 
    174       document.push(XMLConstants.TESTSUITE, attrs);
    175 //      document.addEmptyElement(XMLConstants.PROPERTIES);
    176 
    177       createElementFromTestResults(document, m_configIssues);
    178       createElementFromTestResults(document, m_allTests);
    179 
    180       document.pop();
    181       Utils.writeUtf8File(context.getOutputDirectory(),generateFileName(context) + ".xml", document.toXML());
    182   }
    183 
    184   private void createElementFromTestResults(XMLStringBuffer document, List<ITestResult> results) {
    185     synchronized(results) {
    186       for(ITestResult tr : results) {
    187         createElement(document, tr);
    188       }
    189     }
    190   }
    191 
    192   private Set<String> getPackages(ITestContext context) {
    193     Set<String> result = Sets.newHashSet();
    194     for (ITestNGMethod m : context.getAllTestMethods()) {
    195       Package pkg = m.getMethod().getDeclaringClass().getPackage();
    196       if (pkg != null) {
    197         result.add(pkg.getName());
    198       }
    199     }
    200     return result;
    201   }
    202 
    203   private void createElement(XMLStringBuffer doc, ITestResult tr) {
    204     Properties attrs= new Properties();
    205     long elapsedTimeMillis= tr.getEndMillis() - tr.getStartMillis();
    206     String name= tr.getMethod().isTest() ? tr.getName() : Utils.detailedMethodName(tr.getMethod(), false);
    207     attrs.setProperty(XMLConstants.ATTR_NAME, name);
    208     attrs.setProperty(XMLConstants.ATTR_CLASSNAME, tr.getTestClass().getRealClass().getName());
    209     attrs.setProperty(XMLConstants.ATTR_TIME, "" + (((double) elapsedTimeMillis) / 1000));
    210 
    211     if((ITestResult.FAILURE == tr.getStatus()) || (ITestResult.SKIP == tr.getStatus())) {
    212       doc.push(XMLConstants.TESTCASE, attrs);
    213 
    214       if(ITestResult.FAILURE == tr.getStatus()) {
    215         createFailureElement(doc, tr);
    216       }
    217       else if(ITestResult.SKIP == tr.getStatus()) {
    218         createSkipElement(doc, tr);
    219       }
    220 
    221       doc.pop();
    222     }
    223     else {
    224       doc.addEmptyElement(XMLConstants.TESTCASE, attrs);
    225     }
    226   }
    227 
    228   private void createFailureElement(XMLStringBuffer doc, ITestResult tr) {
    229     Properties attrs= new Properties();
    230     Throwable t= tr.getThrowable();
    231     if(t != null) {
    232       attrs.setProperty(XMLConstants.ATTR_TYPE, t.getClass().getName());
    233       String message= t.getMessage();
    234       if((message != null) && (message.length() > 0)) {
    235         attrs.setProperty(XMLConstants.ATTR_MESSAGE, encodeAttr(message)); // ENCODE
    236       }
    237       doc.push(XMLConstants.FAILURE, attrs);
    238       doc.addCDATA(Utils.stackTrace(t, false)[0]);
    239       doc.pop();
    240     }
    241     else {
    242       doc.addEmptyElement(XMLConstants.FAILURE); // THIS IS AN ERROR
    243     }
    244   }
    245 
    246   private void createSkipElement(XMLStringBuffer doc, ITestResult tr) {
    247     doc.addEmptyElement("skipped");
    248   }
    249 
    250   private String encodeAttr(String attr) {
    251     String result= replaceAmpersand(attr, ENTITY);
    252     for(Map.Entry<String, Pattern> e: ATTR_ESCAPES.entrySet()) {
    253       result= e.getValue().matcher(result).replaceAll(e.getKey());
    254     }
    255 
    256     return result;
    257   }
    258 
    259   private String replaceAmpersand(String str, Pattern pattern) {
    260     int start = 0;
    261     int idx = str.indexOf('&', start);
    262     if(idx == -1) {
    263       return str;
    264     }
    265     StringBuffer result= new StringBuffer();
    266     while(idx != -1) {
    267       result.append(str.substring(start, idx));
    268       if(pattern.matcher(str.substring(idx)).matches()) {
    269         // do nothing it is an entity;
    270         result.append("&");
    271       }
    272       else {
    273         result.append("&amp;");
    274       }
    275       start= idx + 1;
    276       idx= str.indexOf('&', start);
    277     }
    278     result.append(str.substring(start));
    279 
    280     return result.toString();
    281   }
    282 
    283 
    284   /**
    285 	 * Reset all member variables for next test.
    286 	 * */
    287 	private void resetAll() {
    288 		m_allTests = Collections.synchronizedList(Lists.<ITestResult>newArrayList());
    289 		m_configIssues = Collections.synchronizedList(Lists.<ITestResult>newArrayList());
    290 		m_numFailed = 0;
    291 		m_numFailedButIgnored = 0;
    292 		m_numPassed = 0;
    293 		m_numSkipped = 0;
    294 	}
    295 
    296 	/**
    297 	 * @author Borojevic Created this method to guarantee unique file names for
    298 	 *         reports.<br>
    299 	 *         Also, this will guarantee that the old reports are overwritten
    300 	 *         when tests are run again.
    301 	 * @param context
    302 	 *            test context
    303 	 * @return unique name for the file associated with this test context.
    304 	 * */
    305 	private String generateFileName(ITestContext context) {
    306 		String fileName = null;
    307 		String keyToSearch = context.getSuite().getName() + context.getName();
    308 		if (m_fileNameMap.get(keyToSearch) == null) {
    309 			fileName = context.getName();
    310 		} else {
    311 			fileName = context.getName() + m_fileNameIncrementer++;
    312 		}
    313 
    314 		m_fileNameMap.put(keyToSearch, fileName);
    315 		return fileName;
    316 	}
    317 }
    318