Home | History | Annotate | Download | only in reporters
      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.ITestResult;
      9 import org.testng.collections.ListMultiMap;
     10 import org.testng.collections.Lists;
     11 import org.testng.collections.Maps;
     12 import org.testng.collections.Sets;
     13 import org.testng.internal.Utils;
     14 import org.testng.xml.XmlSuite;
     15 
     16 import java.io.File;
     17 import java.io.PrintWriter;
     18 import java.io.StringWriter;
     19 import java.net.InetAddress;
     20 import java.net.UnknownHostException;
     21 import java.text.DecimalFormat;
     22 import java.text.DecimalFormatSymbols;
     23 import java.util.Calendar;
     24 import java.util.Date;
     25 import java.util.List;
     26 import java.util.Map;
     27 import java.util.Properties;
     28 import java.util.Set;
     29 
     30 public class JUnitReportReporter implements IReporter {
     31 
     32   @Override
     33   public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
     34       String defaultOutputDirectory) {
     35 
     36     Map<Class<?>, Set<ITestResult>> results = Maps.newHashMap();
     37     Map<Class<?>, Set<ITestResult>> failedConfigurations = Maps.newHashMap();
     38     ListMultiMap<Object, ITestResult> befores = Maps.newListMultiMap();
     39     ListMultiMap<Object, ITestResult> afters = Maps.newListMultiMap();
     40     for (ISuite suite : suites) {
     41       Map<String, ISuiteResult> suiteResults = suite.getResults();
     42       for (ISuiteResult sr : suiteResults.values()) {
     43         ITestContext tc = sr.getTestContext();
     44         addResults(tc.getPassedTests().getAllResults(), results);
     45         addResults(tc.getFailedTests().getAllResults(), results);
     46         addResults(tc.getSkippedTests().getAllResults(), results);
     47         addResults(tc.getFailedConfigurations().getAllResults(), failedConfigurations);
     48         for (ITestResult tr : tc.getPassedConfigurations().getAllResults()) {
     49           if (tr.getMethod().isBeforeMethodConfiguration()) {
     50             befores.put(tr.getInstance(), tr);
     51           }
     52           if (tr.getMethod().isAfterMethodConfiguration()) {
     53             afters.put(tr.getInstance(), tr);
     54           }
     55         }
     56       }
     57     }
     58 
     59     // A list of iterators for all the passed configuration, explanation below
     60 //    ListMultiMap<Class<?>, ITestResult> beforeConfigurations = Maps.newListMultiMap();
     61 //    ListMultiMap<Class<?>, ITestResult> afterConfigurations = Maps.newListMultiMap();
     62 //    for (Map.Entry<Class<?>, Set<ITestResult>> es : passedConfigurations.entrySet()) {
     63 //      for (ITestResult tr : es.getValue()) {
     64 //        ITestNGMethod method = tr.getMethod();
     65 //        if (method.isBeforeMethodConfiguration()) {
     66 //          beforeConfigurations.put(method.getRealClass(), tr);
     67 //        }
     68 //        if (method.isAfterMethodConfiguration()) {
     69 //          afterConfigurations.put(method.getRealClass(), tr);
     70 //        }
     71 //      }
     72 //    }
     73 //    Map<Object, Iterator<ITestResult>> befores = Maps.newHashMap();
     74 //    for (Map.Entry<Class<?>, List<ITestResult>> es : beforeConfigurations.getEntrySet()) {
     75 //      List<ITestResult> tr = es.getValue();
     76 //      for (ITestResult itr : es.getValue()) {
     77 //      }
     78 //    }
     79 //    Map<Class<?>, Iterator<ITestResult>> afters = Maps.newHashMap();
     80 //    for (Map.Entry<Class<?>, List<ITestResult>> es : afterConfigurations.getEntrySet()) {
     81 //      afters.put(es.getKey(), es.getValue().iterator());
     82 //    }
     83 
     84     for (Map.Entry<Class<?>, Set<ITestResult>> entry : results.entrySet()) {
     85       Class<?> cls = entry.getKey();
     86       Properties p1 = new Properties();
     87       p1.setProperty("name", cls.getName());
     88       Date timeStamp = Calendar.getInstance().getTime();
     89       p1.setProperty(XMLConstants.ATTR_TIMESTAMP, timeStamp.toGMTString());
     90 
     91       List<TestTag> testCases = Lists.newArrayList();
     92       int failures = 0;
     93       int errors = 0;
     94       int testCount = 0;
     95       float totalTime = 0;
     96 
     97       for (ITestResult tr: entry.getValue()) {
     98         TestTag testTag = new TestTag();
     99 
    100         boolean isSuccess = tr.getStatus() == ITestResult.SUCCESS;
    101         if (! isSuccess) {
    102           if (tr.getThrowable() instanceof AssertionError) {
    103             failures++;
    104           } else {
    105             errors++;
    106           }
    107         }
    108 
    109         Properties p2 = new Properties();
    110         p2.setProperty("classname", cls.getName());
    111         p2.setProperty("name", getTestName(tr));
    112         long time = tr.getEndMillis() - tr.getStartMillis();
    113 
    114         time += getNextConfiguration(befores, tr);
    115         time += getNextConfiguration(afters, tr);
    116 
    117         p2.setProperty("time", "" + formatTime(time));
    118         Throwable t = getThrowable(tr, failedConfigurations);
    119         if (! isSuccess && t != null) {
    120           StringWriter sw = new StringWriter();
    121           PrintWriter pw = new PrintWriter(sw);
    122           t.printStackTrace(pw);
    123           testTag.message = t.getMessage();
    124           testTag.type = t.getClass().getName();
    125           testTag.stackTrace = sw.toString();
    126           testTag.errorTag = tr.getThrowable() instanceof AssertionError ? "failure" : "error";
    127         }
    128         totalTime += time;
    129         testCount++;
    130         testTag.properties = p2;
    131         testCases.add(testTag);
    132       }
    133 
    134       p1.setProperty("failures", "" + failures);
    135       p1.setProperty("errors", "" + errors);
    136       p1.setProperty("name", cls.getName());
    137       p1.setProperty("tests", "" + testCount);
    138       p1.setProperty("time", "" + formatTime(totalTime));
    139       try {
    140         p1.setProperty(XMLConstants.ATTR_HOSTNAME, InetAddress.getLocalHost().getHostName());
    141       } catch (UnknownHostException e) {
    142         // ignore
    143       }
    144 
    145       //
    146       // Now that we have all the information we need, generate the file
    147       //
    148       XMLStringBuffer xsb = new XMLStringBuffer();
    149       xsb.addComment("Generated by " + getClass().getName());
    150 
    151       xsb.push("testsuite", p1);
    152       for (TestTag testTag : testCases) {
    153         if (testTag.stackTrace == null) {
    154           xsb.addEmptyElement("testcase", testTag.properties);
    155         }
    156         else {
    157           xsb.push("testcase", testTag.properties);
    158 
    159           Properties p = new Properties();
    160           if (testTag.message != null) {
    161             p.setProperty("message", testTag.message);
    162           }
    163           p.setProperty("type", testTag.type);
    164           xsb.push(testTag.errorTag, p);
    165           xsb.addCDATA(testTag.stackTrace);
    166           xsb.pop(testTag.errorTag);
    167 
    168           xsb.pop("testcase");
    169         }
    170       }
    171       xsb.pop("testsuite");
    172 
    173       String outputDirectory = defaultOutputDirectory + File.separator + "junitreports";
    174       Utils.writeUtf8File(outputDirectory, getFileName(cls), xsb.toXML());
    175     }
    176 
    177 //    System.out.println(xsb.toXML());
    178 //    System.out.println("");
    179 
    180   }
    181 
    182   /**
    183    * Add the time of the configuration method to this test method.
    184    *
    185    * The only problem with this method is that the timing of a test method
    186    * might not be added to the time of the same configuration method that ran before
    187    * it but since they should all be equivalent, this should never be an issue.
    188    */
    189   private long getNextConfiguration(ListMultiMap<Object, ITestResult> configurations,
    190       ITestResult tr)
    191   {
    192     long result = 0;
    193 
    194     List<ITestResult> confResults = configurations.get(tr.getInstance());
    195     Map<ITestNGMethod, ITestResult> seen = Maps.newHashMap();
    196     if (confResults != null) {
    197       for (ITestResult r : confResults) {
    198         if (! seen.containsKey(r.getMethod())) {
    199           result += r.getEndMillis() - r.getStartMillis();
    200           seen.put(r.getMethod(), r);
    201         }
    202       }
    203       confResults.removeAll(seen.values());
    204     }
    205 
    206     return result;
    207   }
    208 
    209   protected String getFileName(Class cls) {
    210     return "TEST-" + cls.getName() + ".xml";
    211   }
    212 
    213   protected String getTestName(ITestResult tr) {
    214     return tr.getMethod().getMethodName();
    215   }
    216 
    217   private String formatTime(float time) {
    218     DecimalFormatSymbols symbols = new DecimalFormatSymbols();
    219     // JUnitReports wants points here, regardless of the locale
    220     symbols.setDecimalSeparator('.');
    221     DecimalFormat format = new DecimalFormat("#.###", symbols);
    222     format.setMinimumFractionDigits(3);
    223     return format.format(time / 1000.0f);
    224   }
    225 
    226   private Throwable getThrowable(ITestResult tr,
    227       Map<Class<?>, Set<ITestResult>> failedConfigurations) {
    228     Throwable result = tr.getThrowable();
    229     if (result == null && tr.getStatus() == ITestResult.SKIP) {
    230       // Attempt to grab the stack trace from the configuration failure
    231       for (Set<ITestResult> failures : failedConfigurations.values()) {
    232         for (ITestResult failure : failures) {
    233           // Naive implementation for now, eventually, we need to try to find
    234           // out if it's this failure that caused the skip since (maybe by
    235           // seeing if the class of the configuration method is assignable to
    236           // the class of the test method, although that's not 100% fool proof
    237           if (failure.getThrowable() != null) {
    238             return failure.getThrowable();
    239           }
    240         }
    241       }
    242     }
    243 
    244     return result;
    245   }
    246 
    247   static class TestTag {
    248     public Properties properties;
    249     public String message;
    250     public String type;
    251     public String stackTrace;
    252     public String errorTag;
    253   }
    254 
    255   private void addResults(Set<ITestResult> allResults, Map<Class<?>, Set<ITestResult>> out) {
    256     for (ITestResult tr : allResults) {
    257       Class<?> cls = tr.getMethod().getTestClass().getRealClass();
    258       Set<ITestResult> l = out.get(cls);
    259       if (l == null) {
    260         l = Sets.newHashSet();
    261         out.put(cls, l);
    262       }
    263       l.add(tr);
    264     }
    265   }
    266 
    267 }
    268