package org.infinispan.commons.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import org.testng.ISuite;
import org.testng.ISuiteListener;
import org.testng.ISuiteResult;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.annotations.Test;
import org.testng.collections.Maps;
import org.testng.internal.IResultListener2;
import org.testng.internal.Utils;
import org.testng.reporters.XMLConstants;
import org.testng.reporters.XMLStringBuffer;
/**
* A JUnit XML report generator for Polarion based on the JUnitXMLReporter
*
* @author <a href='mailto:afield[at]redhat[dot]com'>Alan Field</a>
*/
public class PolarionJUnitXMLReporter implements IResultListener2, ISuiteListener {
private static final Pattern ENTITY = Pattern.compile("&[a-zA-Z]+;.*");
private static final Pattern LESS = Pattern.compile("<");
private static final Pattern GREATER = Pattern.compile(">");
private static final Pattern SINGLE_QUOTE = Pattern.compile("'");
private static final Pattern QUOTE = Pattern.compile("\"");
private static final Map<String, Pattern> ATTR_ESCAPES = Maps.newHashMap();
static {
ATTR_ESCAPES.put("<", LESS);
ATTR_ESCAPES.put(">", GREATER);
ATTR_ESCAPES.put("'", SINGLE_QUOTE);
ATTR_ESCAPES.put(""", QUOTE);
}
/**
* keep lists of all the results
*/
private AtomicInteger m_numFailed = new AtomicInteger(0);
private AtomicInteger m_numSkipped = new AtomicInteger(0);
private Map<String, List<ITestResult>> m_allTests = Collections.synchronizedMap(Maps.newHashMap());
/**
* @see org.testng.IConfigurationListener2#beforeConfiguration(ITestResult)
*/
@Override
public void beforeConfiguration(ITestResult tr) {
}
/**
* @see org.testng.ITestListener#onTestStart(ITestResult)
*/
@Override
public void onTestStart(ITestResult result) {
}
/**
* @see org.testng.ITestListener#onTestSuccess(ITestResult)
*/
@Override
public void onTestSuccess(ITestResult tr) {
checkDuplicatesAndAdd(tr);
}
/**
* @see org.testng.ITestListener#onTestFailure(ITestResult)
*/
@Override
public void onTestFailure(ITestResult tr) {
checkDuplicatesAndAdd(tr);
m_numFailed.incrementAndGet();
}
/**
* @see org.testng.ITestListener#onTestFailedButWithinSuccessPercentage(ITestResult)
*/
@Override
public void onTestFailedButWithinSuccessPercentage(ITestResult tr) {
checkDuplicatesAndAdd(tr);
m_numFailed.incrementAndGet();
}
/**
* @see org.testng.ITestListener#onTestSkipped(ITestResult)
*/
@Override
public void onTestSkipped(ITestResult tr) {
checkDuplicatesAndAdd(tr);
m_numSkipped.incrementAndGet();
}
/**
* @see org.testng.ITestListener#onStart(ITestContext)
*/
@Override
public void onStart(ITestContext context) {
}
/**
* @see org.testng.ITestListener#onFinish(ITestContext)
*/
@Override
public void onFinish(ITestContext context) {
}
/**
* @see org.testng.ISuiteListener#onStart(ISuite)
*/
@Override
public void onStart(ISuite suite) {
resetAll();
}
/**
* @see org.testng.ISuiteListener#onFinish(ISuite)
*/
@Override
public void onFinish(ISuite suite) {
generateReport(suite);
}
/**
* @see org.testng.IConfigurationListener#onConfigurationFailure(org.testng.ITestResult)
*/
@Override
public void onConfigurationFailure(ITestResult tr) {
checkDuplicatesAndAdd(tr);
m_numFailed.incrementAndGet();
}
/**
* @see org.testng.IConfigurationListener#onConfigurationSkip(org.testng.ITestResult)
*/
@Override
public void onConfigurationSkip(ITestResult tr) {
}
/**
* @see org.testng.IConfigurationListener#onConfigurationSuccess(org.testng.ITestResult)
*/
@Override
public void onConfigurationSuccess(ITestResult itr) {
}
/**
* generate the XML report given what we know from all the test results
*/
protected void generateReport(ISuite suite) {
XMLStringBuffer document = new XMLStringBuffer();
document.addComment("Generated by " + getClass().getName());
// Get elapsed time for testsuite element
long elapsedTime = 0;
long testCount = 0;
for (List<ITestResult> testResults : m_allTests.values()) {
for (ITestResult tr : testResults) {
elapsedTime += (tr.getEndMillis() - tr.getStartMillis());
// if (tr.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class) != null) {
// testCount += tr.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class)
// .invocationCount();
// } else {
testCount++;
// }
}
}
Properties attrs = new Properties();
attrs.setProperty(XMLConstants.ATTR_TESTS, "" + testCount);
attrs.setProperty(XMLConstants.ATTR_TIME, "" + elapsedTime / 1000.0);
attrs.setProperty(XMLConstants.ATTR_NAME, getModuleSuffix());
attrs.setProperty("skipped", "" + m_numSkipped);
attrs.setProperty(XMLConstants.ATTR_ERRORS, "0");
attrs.setProperty(XMLConstants.ATTR_FAILURES, "" + (m_numFailed.get()));
document.push(XMLConstants.TESTSUITE, attrs);
showProperties(document);
document.addComment("Tests results");
createElementFromTestResults(document, m_allTests.values());
document.pop();
// Reset output directory
Utils.writeUtf8File(suite.getOutputDirectory().replaceAll(".Surefire suite", ""),
generateFileName(suite) + ".xml", document.toXML());
}
private void createElementFromTestResults(XMLStringBuffer document, Collection<List<ITestResult>> results) {
synchronized (results) {
for (List<ITestResult> testResults : results) {
if (testResults.size() == 1) {
createElement(document, testResults.get(0));
} else {
boolean hasFailures = false;
for (ITestResult tr : testResults) {
if (!tr.isSuccess()) {
hasFailures = true;
// Report all failures
createElement(document, tr);
}
}
if (!hasFailures) {
// If there were no failures, report a single success
createElement(document, testResults.get(0));
}
}
}
}
}
private void createElement(XMLStringBuffer doc, ITestResult tr) {
Properties attrs = new Properties();
long elapsedTimeMillis = tr.getEndMillis() - tr.getStartMillis();
attrs.setProperty(XMLConstants.ATTR_NAME, testName(tr));
attrs.setProperty(XMLConstants.ATTR_CLASSNAME, tr.getTestClass().getRealClass().getName());
attrs.setProperty(XMLConstants.ATTR_TIME, "" + (((double) elapsedTimeMillis) / 1000));
if ((ITestResult.FAILURE == tr.getStatus()) || (ITestResult.SKIP == tr.getStatus())) {
doc.push(XMLConstants.TESTCASE, attrs);
if (ITestResult.FAILURE == tr.getStatus()) {
createFailureElement(doc, tr);
} else if (ITestResult.SKIP == tr.getStatus()) {
createSkipElement(doc, tr);
}
doc.pop();
} else {
doc.addEmptyElement(XMLConstants.TESTCASE, attrs);
}
}
private void createFailureElement(XMLStringBuffer doc, ITestResult tr) {
Properties attrs = new Properties();
Throwable t = tr.getThrowable();
if (t != null) {
attrs.setProperty(XMLConstants.ATTR_TYPE, t.getClass().getName());
String message = t.getMessage();
if ((message != null) && (message.length() > 0)) {
attrs.setProperty(XMLConstants.ATTR_MESSAGE, encodeAttr(message)); // ENCODE
}
doc.push(XMLConstants.FAILURE, attrs);
doc.addCDATA(Utils.stackTrace(t, false)[0]);
doc.pop();
} else {
doc.addEmptyElement(XMLConstants.FAILURE); // THIS IS AN ERROR
}
}
private void createSkipElement(XMLStringBuffer doc, ITestResult tr) {
doc.addEmptyElement("skipped");
}
private String encodeAttr(String attr) {
String result = replaceAmpersand(attr, ENTITY);
for (Map.Entry<String, Pattern> e : ATTR_ESCAPES.entrySet()) {
result = e.getValue().matcher(result).replaceAll(e.getKey());
}
return result;
}
private String replaceAmpersand(String str, Pattern pattern) {
int start = 0;
int idx = str.indexOf('&', start);
if (idx == -1) {
return str;
}
StringBuilder result = new StringBuilder();
while (idx != -1) {
result.append(str.substring(start, idx));
if (pattern.matcher(str.substring(idx)).matches()) {
// do nothing it is an entity;
result.append("&");
} else {
result.append("&");
}
start = idx + 1;
idx = str.indexOf('&', start);
}
result.append(str.substring(start));
return result.toString();
}
/**
* Reset all member variables for next test.
*/
private void resetAll() {
m_allTests = Collections.synchronizedMap(Maps.newHashMap());
m_numFailed.set(0);
m_numSkipped.set(0);
}
private String generateFileName(ISuite suite) {
String name = getModuleSuffix();
Collection<ISuiteResult> suiteResults = suite.getResults().values();
if (suiteResults.size() == 1) {
ITestNGMethod[] testMethods = suiteResults.iterator().next().getTestContext().getAllTestMethods();
if (testMethods.length > 0) {
Class<?> testClass = testMethods[0].getConstructorOrMethod().getDeclaringClass();
// If only one test class executed, then use that as the filename
String className = testClass.getName();
// If only one test package executed, then use that as the filename
String packageName = testClass.getPackage().getName();
boolean oneTestClass = true;
boolean oneTestPackage = true;
for (ITestNGMethod method : testMethods) {
if (!method.getConstructorOrMethod().getDeclaringClass().getName().equals(className)) {
oneTestClass = false;
}
if (!method.getConstructorOrMethod().getDeclaringClass().getPackage().getName().equals(packageName)) {
oneTestPackage = false;
}
}
if (oneTestClass) {
name = className;
} else {
if (oneTestPackage) {
name = packageName;
}
}
} else {
System.out.println(
"[" + this.getClass().getSimpleName() + "] Test suite '" + name + "' results have no test methods");
}
}
return String.format("TEST-%s", name);
}
private String testName(ITestResult res) {
StringBuilder result = new StringBuilder(res.getMethod().getMethodName());
if (res.getMethod().getConstructorOrMethod().getMethod().isAnnotationPresent(Test.class)) {
String dataProviderName = res.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class)
.dataProvider();
// Add parameters for methods that use a data provider only
if (res.getParameters().length != 0 && (dataProviderName != null && !dataProviderName.isEmpty())) {
result.append("(").append(Arrays.deepToString(res.getParameters()));
}
// Add number of invocations to method name
if (res.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).invocationCount() > 1) {
if (result.indexOf("(") == -1) {
result.append("(");
} else {
result.append(", ");
}
result.append("invoked ").append(
res.getMethod().getConstructorOrMethod().getMethod().getAnnotation(Test.class).invocationCount())
.append(" times");
}
// JCache tests are a special case
if (getModuleSuffix().contains("jcache")) {
if (result.indexOf("(") == -1) {
result.append("(");
} else {
result.append(", ");
}
if (getModuleSuffix().contains("infinispan-jcache-remote")) {
result.append("remote");
} else {
result.append("embedded");
}
}
if (result.indexOf("(") != -1) {
result.append(")");
}
}
return result.toString();
}
private void showProperties(XMLStringBuffer report) {
report.push(XMLConstants.PROPERTIES);
// Add all system properties
report.addComment("Java System properties");
for (Object key : System.getProperties().keySet()) {
Properties property = new Properties();
property.setProperty(XMLConstants.ATTR_NAME, key.toString());
property.setProperty(XMLConstants.ATTR_VALUE, System.getProperty(key.toString()));
report.addEmptyElement(XMLConstants.PROPERTY, property);
}
// Add all environment variables
report.addComment("Environment variables");
for (String key : System.getenv().keySet()) {
Properties property = new Properties();
property.setProperty(XMLConstants.ATTR_NAME, key.toString());
property.setProperty(XMLConstants.ATTR_VALUE, System.getenv(key.toString()));
report.addEmptyElement(XMLConstants.PROPERTY, property);
}
report.pop();
}
private String getModuleSuffix() {
// Remove the "-" from the beginning of the string
return System.getProperty("infinispan.module-suffix").substring(1);
}
private void checkDuplicatesAndAdd(ITestResult tr) {
// Need fully qualified name to guarantee uniqueness in the results map
String key = tr.getTestClass().getRealClass().getName() + "." + testName(tr);
if (m_allTests.containsKey(key)) {
if (tr.getMethod().getCurrentInvocationCount() == 1) {
System.out.println("[" + this.getClass().getSimpleName() + "] Test case '" + testName(tr)
+ "' already exists in the results");
} else {
List<ITestResult> itrList = m_allTests.get(key);
itrList.add(tr);
m_allTests.put(key, itrList);
}
} else {
ArrayList<ITestResult> itrList = new ArrayList<ITestResult>();
itrList.add(tr);
m_allTests.put(key, itrList);
}
}
}