Added EmailableReporter
diff --git a/CHANGES.txt b/CHANGES.txt
index 528ef47..1e7262c 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -6,6 +6,7 @@
===========================================================================
5.2
+Added: EmailableReported (from Paul Mendelson)
Added: parallel can now be "methods" or "tests". Boolean version deprecated
Added: TestNGAntTask now uses the @ syntax to invoke TestNG
Added: Command line understands @ syntax
diff --git a/bin/testng.bat b/bin/testng.bat
index ffb7bd4..8ea1edf 100644
--- a/bin/testng.bat
+++ b/bin/testng.bat
@@ -1,7 +1,5 @@
-set ROOT=c:\java\TestNG
-set VERSION=4.7beta
-set JAR=%ROOT%\testng-%VERSION%-jdk15.jar;%ROOT%\test\test.jar
- rem set JAR=%ROOT%\testng-%VERSION%-jdk15.jar;%ROOT%\test\build
- rem set JAR=%ROOT%\testng-%VERSION%-jdk14.jar;%ROOT%\test-14\build
-
-java -ea -classpath %ROOT%\3rdparty\junit.jar;%JAVA_HOME%\lib\tools.jar;%JAR%;%CLASSPATH% org.testng.TestNG %1 %2 %3 %4 %5 %6 %7 %8 %9
+set ROOT=c:\java\TestNG
+set JAR=%ROOT%\testng-5.2beta-jdk15.jar;%ROOT%\test\build
+ rem set JAR=%ROOT%\testng-4.5-jdk14.jar;%ROOT%\test-14\build
+
+java -ea -classpath %ROOT%\3rdparty\junit.jar;%JAVA_HOME%\lib\tools.jar;%JAR%;%CLASSPATH% org.testng.TestNG %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/master.bat b/master.bat
deleted file mode 100644
index a4040d8..0000000
--- a/master.bat
+++ /dev/null
@@ -1 +0,0 @@
-testng -hostfile test\hosts.properties -d test\test-output %1 %2 %3 %4 %5 test\testng.xml
\ No newline at end of file
diff --git a/slave.bat b/slave.bat
deleted file mode 100644
index 24d2fb1..0000000
--- a/slave.bat
+++ /dev/null
@@ -1 +0,0 @@
-testng -d client-output -slave %1 %2 %3
diff --git a/src/main/org/testng/TestNG.java b/src/main/org/testng/TestNG.java
index 45a10d5..c820710 100644
--- a/src/main/org/testng/TestNG.java
+++ b/src/main/org/testng/TestNG.java
@@ -36,6 +36,7 @@
import org.testng.remote.ConnectionInfo;
import org.testng.remote.RemoteSuiteWorker;
import org.testng.remote.RemoteTestWorker;
+import org.testng.reporters.EmailableReporter;
import org.testng.reporters.FailedReporter;
import org.testng.reporters.SuiteHTMLReporter;
import org.testng.xml.Parser;
@@ -163,6 +164,7 @@
if (useDefaultListeners) {
m_reporters.add(new SuiteHTMLReporter());
m_reporters.add(new FailedReporter());
+ m_reporters.add(new EmailableReporter());
m_outputDir = DEFAULT_OUTPUTDIR;
}
diff --git a/src/main/org/testng/reporters/EmailableReporter.java b/src/main/org/testng/reporters/EmailableReporter.java
new file mode 100644
index 0000000..9678479
--- /dev/null
+++ b/src/main/org/testng/reporters/EmailableReporter.java
@@ -0,0 +1,370 @@
+package org.testng.reporters;
+
+import org.testng.IReporter;
+import org.testng.IResultMap;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+
+import org.testng.log4testng.Logger;
+import org.testng.xml.XmlSuite;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Reported designed to render self-contained HTML top down view
+ * of a testing suite.
+ *
+ * @author Paul Mendelson
+ * @version $Revision: 3 $
+ */
+public class EmailableReporter implements IReporter
+{
+ Logger logger=Logger.getLogger(EmailableReporter.class);
+ //~ Instance fields ------------------------------------------------------
+
+ private PrintWriter out;
+ int row;
+
+ //~ Methods --------------------------------------------------------------
+
+ /** Creates summary of the run */
+ public void generateReport(
+ List<XmlSuite> xml,
+ List<ISuite> suites,
+ String outdir)
+ {
+ try {
+ out=new PrintWriter(new FileWriter(new File(outdir,"emailable-report.html")));
+ } catch(IOException e) {
+ logger.error("output file",e);
+ return;
+ }
+ startHtml(out);
+ for(ISuite suite : suites) {
+ summarize(suite);
+ out.println("<a id=summary></a>");
+ Map<String,ISuiteResult> r=suite.getResults();
+ for(ISuiteResult r2 : r.values()) {
+ resultSummary(
+ r2.getTestContext().getFailedTests(),
+ "failed");
+ resultSummary(
+ r2.getTestContext().getPassedTests(),
+ "passed");
+ resultDetail(
+ r2.getTestContext().getFailedTests(),
+ "failed");
+ resultDetail(
+ r2.getTestContext().getPassedTests(),
+ "passed");
+ }
+ }
+ out.println("</body></html>");
+ out.close();
+ }
+
+ /**
+ * @param tests
+ */
+ private void resultSummary(IResultMap tests,String style)
+ {
+ if(tests.getAllResults().size()>0) {
+ tableStart(style);
+ out.println(
+ "<tr><th>Class</th>"
+ +"<th>Method</th><th># of<br/>Scenarios</th><th>Time<br/>(Msecs)</th></tr>");
+ int row=0;
+ StringBuffer buff=new StringBuffer();
+ String lastc="";
+ int mq=0;
+ int cq=0;
+ for(ITestNGMethod method : getMethodSet(tests)) {
+ row+=1;
+ String cname=method.getTestClass().getName();
+ if(!cname.equalsIgnoreCase(lastc)) {
+ if(mq>0) {
+ cq+=1;
+ out.println(
+ "<tr"+(cq%2==0
+ ? " class=\"stripe\""
+ : "")+">"+"<td rowspan=\""+mq+"\">"+lastc+buff);
+ }
+ mq=0;
+ buff.setLength(0);
+ lastc=cname;
+ }
+ Set<ITestResult> result_set=tests.getResults(method);
+ long end=Long.MIN_VALUE;
+ long start=Long.MAX_VALUE;
+ for(ITestResult ans : tests.getResults(method)) {
+ if(ans.getEndMillis()>end) {
+ end=ans.getEndMillis();
+ }
+ if(ans.getStartMillis()<start) {
+ start=ans.getStartMillis();
+ }
+ }
+ mq+=1;
+ if(mq>1) {
+ buff.append("<tr"+(cq%2==1
+ ? " class=\"stripe\""
+ : "")+">");
+ }
+ buff.append(
+ "<td><a href=\"#m"+row+"\">"+qualifiedName(method)+"</a></td>"
+ +"<td class=\"numi\">"+result_set.size()+"</td><td class=\"numi\">"
+ +(end-start)+"</td></tr>");
+ }
+ if(mq>0) {
+ row+=1;
+ out.println(
+ "<tr"+(row%2==0
+ ? " class=\"stripe\""
+ : "")+">"+"<td rowspan=\""+mq+"\">"+lastc+buff);
+ }
+ out.println("</table>");
+ }
+ }
+
+ private String qualifiedName(ITestNGMethod method) {
+ String addon="";
+ if(method.getGroups().length>0 && ! "basic".equalsIgnoreCase(method.getGroups()[0])) {
+ addon=" ("+method.getGroups()[0]+")";
+ }
+ return method.getMethodName()+addon;
+ }
+
+ private void resultDetail(IResultMap tests,String style)
+ {
+ if(tests.getAllResults().size()>0) {
+ int row=0;
+ StringBuffer buff=new StringBuffer();
+ for(ITestNGMethod method : getMethodSet(tests)) {
+ row+=1;
+ String cname=method.getTestClass().getName();
+ out.println(
+ "<a id=\"m"+row+"\"></a><h2>"+cname+":"+method.getMethodName()
+ +"</h2>");
+ // Set<ITestResult> result_set=tests.getResults(method);
+ // long end=Long.MIN_VALUE;
+ // long start=Long.MAX_VALUE;
+ int rq=0;
+ Set<ITestResult> result_set=tests.getResults(method);
+ for(ITestResult ans : result_set) {
+ rq+=1;
+ Object[] pset=ans.getParameters();
+ if(pset.length>0) {
+ if(rq==1) {
+ tableStart("param");
+ out.print("<tr>");
+ for(int x=1; x<=pset.length; x++) {
+ out.print(
+ "<th style=\"padding-left:1em;padding-right:1em\">Parameter #"+x
+ +"</th>");
+ }
+ out.println("</tr>");
+ }
+ out.print("<tr>");
+ for(Object p : pset) {
+ out.println(
+ "<td style=\"padding-left:.5em;padding-right:2em\">"+p+"</td>");
+ }
+ out.println("</tr>");
+ if(rq==result_set.size()) {
+ out.println("</table>");
+ }
+ }
+ List<String> msgs=Reporter.getOutput(ans);
+ if(msgs.size()>0) {
+ out.println("<div style=\"padding-left:3em\">");
+ for(String line : msgs) {
+ out.println(line+"<br/>");
+ }
+ out.println("</div>");
+ }
+ }
+ // mq+=1;
+ // if(mq>1)
+ // buff.append("<tr"+(row%2==1?" class=\"stripe\"":"")+">");
+ // buff.append("<td><a href=\"#"+method.getId()+">"+method.getMethodName()+"</a></td>"
+ // +"<td class=\"numi\">"+result_set.size()+"</td><td class=\"numi\">"
+ // +(end-start)
+ // +"</td></tr>");
+ // }
+ // if(mq>0) {
+ // row+=1;
+ // out.println("<tr"+(row%2==0?" class=\"stripe\"":"")+">"
+ // +"<td rowspan=\""+mq+"\">"+lastc+buff);
+ out.println("<p class=\"totop\"><a href=#top>back to summary</a></p>");
+ }
+
+ // out.println("</table>");
+ }
+ }
+
+ /**
+ * @param tests
+ * @return
+ */
+ private Collection<ITestNGMethod> getMethodSet(IResultMap tests)
+ {
+ Set r=new TreeSet<ITestNGMethod>(new TestSorter<ITestNGMethod>());
+ r.addAll(tests.getAllMethods());
+ return r;
+ }
+
+ private void summarize(ISuite suite)
+ {
+ tableStart("param");
+ Map<String,ISuiteResult> r=suite.getResults();
+ for(ISuiteResult r2 : r.values()) {
+ ITestContext overview=r2.getTestContext();
+ tableRow(
+ "# of Tests passed",
+ getMethodSet(overview.getPassedTests()).size());
+ tableRow(
+ "# of Scenarios passed",
+ overview.getPassedTests().size());
+ tableRow(
+ "# failed",
+ overview.getFailedTests().size());
+ tableRow(
+ "# skipped",
+ overview.getSkippedTests().size());
+ NumberFormat formatter=new DecimalFormat("#,##0.0");
+ tableRow(
+ "Total Time",
+ formatter.format(
+ (overview.getEndDate().getTime()-overview.getStartDate().getTime())/1000.)
+ +" seconds");
+ tableRow(
+ "Included Groups",
+ overview.getIncludedGroups());
+ tableRow(
+ "Excluded Groups",
+ overview.getExcludedGroups());
+ }
+ out.println("</table>");
+ }
+
+ /**
+ *
+ */
+ private void tableStart(String cssclass)
+ {
+ out.println(
+ "<table cellspacing=0 cellpadding=0"
+ +(
+ cssclass!=null
+ ? " class=\""+cssclass+"\""
+ : " style=\"padding-bottom:2em\""
+ )+">");
+ row=0;
+ }
+
+ private void tableRow(String label,String val)
+ {
+ row+=1;
+ out.println(
+ "<tr"+(row%2==0
+ ? " class=\"stripe\""
+ : "")+"><th style=\"text-align:left;padding-right:2em\">"+label
+ +"</th><td style=\"text-align:right\">"+val+"</td></tr>");
+ }
+
+ private void tableRow(String label,long val)
+ {
+ tableRow(
+ label,
+ String.valueOf(val));
+ }
+
+ private void tableRow(String label,String[] val)
+ {
+ StringBuffer b=new StringBuffer();
+ for(String v : val)
+ b.append(v+" ");
+ tableRow(
+ label,
+ b.toString().trim());
+ }
+
+ private void startHtml(PrintWriter out)
+ {
+ out.println("<html>");
+ out.println("<head>");
+ out.println("<title>TestNG: Unit Test</title>");
+ out.println("<style type=\"text/css\">");
+ out.println(
+ "table.info_table,table.param,table.passed,table.failed {margin-bottom:10px;border:1px solid #000099;border-collapse:collapse;empty-cells:show;}");
+ out.println(
+ "table.info_table td,table.info_table th,table.param td,table.param th,table.passed td,table.passed th,table.failed td,table.failed th {");
+ out.println("border:1px solid #000099;padding:.25em .5em .25em .5em");
+ out.println("}");
+ out.println("td.numi {");
+ out.println("text-align:right");
+ out.println("}");
+ out.println("table.passed td {background-color: #00AA00;}");
+ out.println("table.passed tr.stripe td {background-color: #33FF33;}");
+ out.println("table.failed td {background-color: #DD0000;}");
+ out.println("table.failed tr.stripe td {background-color: #FF3333;}");
+ out.println("tr.stripe td,tr.stripe th {background-color: #E6EBF9;}");
+ out.println(
+ "p.totop {font-size:85%;text-align:center;border-bottom:2px black solid}");
+ out.println("div.shootout {padding:2em;border:3px #4854A8 solid}");
+ out.println("</style>");
+ out.println("</head>");
+ out.println("<body>");
+ }
+
+ //~ Inner Classes --------------------------------------------------------
+
+ /**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision: 3 $
+ *
+ * @param <T> DOCUMENT ME!
+ */
+ private class TestSorter<T extends ITestNGMethod> implements Comparator
+ {
+ //~ Methods -------------------------------------------------------------
+
+ /**
+ * DOCUMENT ME!
+ *
+ * @param o1 DOCUMENT ME!
+ * @param o2 DOCUMENT ME!
+ *
+ * @return DOCUMENT ME!
+ */
+ public int compare(Object o1,Object o2)
+ {
+ int r=
+ ((T)o1).getTestClass().getName()
+ .compareTo(((T)o2).getTestClass().getName());
+ if(r==0) {
+ r=((T)o1).getMethodName().compareTo(((T)o2).getMethodName());
+ }
+ return r;
+ }
+ }
+}
diff --git a/testng.bat b/testng.bat
deleted file mode 100644
index 8ea1edf..0000000
--- a/testng.bat
+++ /dev/null
@@ -1,5 +0,0 @@
-set ROOT=c:\java\TestNG
-set JAR=%ROOT%\testng-5.2beta-jdk15.jar;%ROOT%\test\build
- rem set JAR=%ROOT%\testng-4.5-jdk14.jar;%ROOT%\test-14\build
-
-java -ea -classpath %ROOT%\3rdparty\junit.jar;%JAVA_HOME%\lib\tools.jar;%JAR%;%CLASSPATH% org.testng.TestNG %1 %2 %3 %4 %5 %6 %7 %8 %9