/**
* Copyright (c) 2012 Cloudsmith Inc. and other contributors, as listed below.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cloudsmith
*
*/
package org.cloudsmith.geppetto.junitresult.util;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Date;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.cloudsmith.geppetto.junitresult.Error;
import org.cloudsmith.geppetto.junitresult.Failure;
import org.cloudsmith.geppetto.junitresult.JunitResult;
import org.cloudsmith.geppetto.junitresult.JunitresultPackage;
import org.cloudsmith.geppetto.junitresult.NegativeResult;
import org.cloudsmith.geppetto.junitresult.Testcase;
import org.cloudsmith.geppetto.junitresult.Testrun;
import org.cloudsmith.geppetto.junitresult.Testsuite;
import org.cloudsmith.geppetto.junitresult.Testsuites;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.google.common.base.Strings;
/**
* Serializes a Junitresult model to XML DOM.
*
*/
public class JunitresultDomSerializer {
private Document doc;
/**
* Format time using as many digits as possible for seconds, and always three digits for ms.
*/
final private DecimalFormat timeFormat = new DecimalFormat("0.000");
/**
* Appends an element with CDATA content to the given Node n with given elementName and containing
* given cdata. If cdata is null or empty, not element is appended to n.
*
* @param n
* @param cdata
* @param elementName
*/
private void appendCDATANode(Node n, String cdata, String elementName) {
if(Strings.isNullOrEmpty(cdata))
return;
Element cdataElement = doc.createElement(elementName);
n.appendChild(cdataElement);
cdataElement.appendChild(doc.createCDATASection(cdata));
}
private String dateToString(Date d) {
Calendar cal = Calendar.getInstance();
cal.setTime(d);
return javax.xml.bind.DatatypeConverter.printDateTime(cal);
}
private String formatTime(double d) {
return timeFormat.format(d);
}
/**
* @param negativeResult
* @return
*/
private String negativeResultToTag(NegativeResult negativeResult) {
switch(negativeResult.eClass().getClassifierID()) {
case JunitresultPackage.ERROR:
return "error";
case JunitresultPackage.FAILURE:
return "failure";
case JunitresultPackage.SKIPPED:
return "skipped";
default:
throw new IllegalArgumentException("Not a supported negative result type");
}
}
private void processNegativeResult(Node n, NegativeResult negativeResult) {
if(negativeResult != null) {
Element ne = doc.createElement(negativeResultToTag(negativeResult));
ne.setAttribute("message", negativeResult.getMessage());
ne.setAttribute("type", negativeResult.getType());
if(!Strings.isNullOrEmpty(negativeResult.getValue())) {
CDATASection value = doc.createCDATASection(negativeResult.getValue());
ne.appendChild(value);
}
n.appendChild(ne);
}
}
private void processSystemErr(Node n, String s) {
appendCDATANode(n, s, "system-err");
}
private void processSystemOut(Node n, String s) {
appendCDATANode(n, s, "system-out");
}
/**
* @param e
* @param tc
*/
private void processTestcase(Node n, Testcase tc) {
Element e = doc.createElement("testcase");
e.setAttribute("name", tc.getName());
e.setAttribute("time", formatTime(tc.getTime()));
if(!Strings.isNullOrEmpty(tc.getClassname()))
e.setAttribute("classname", tc.getClassname());
for(Error error : tc.getErrors())
processNegativeResult(e, error);
for(Failure failure : tc.getFailures())
processNegativeResult(e, failure);
if(tc.getSkipped() != null)
processNegativeResult(e, tc.getSkipped());
for(String s : tc.getSystem_out())
processSystemOut(e, s);
for(String s : tc.getSystem_err())
processSystemErr(e, s);
n.appendChild(e);
}
private void processTestsuite(Node n, Testsuite r) {
Element e = doc.createElement("testsuite");
e.setAttribute("name", r.getName());
e.setAttribute("time", formatTime(r.getTime()));
if(r.getTimestamp() != null)
e.setAttribute("timestamp", dateToString(r.getTimestamp()));
e.setAttribute("tests", Integer.toString(r.getTests()));
e.setAttribute("errors", Integer.toString(r.getErrors()));
e.setAttribute("failures", Integer.toString(r.getFailures()));
// TODO: don't know what the correct choice is for ignore/disabled/skipped
e.setAttribute("skipped", Integer.toString(r.getSkipped()));
e.setAttribute("disabled", Integer.toString(r.getDisabled()));
for(Testcase tc : r.getTestcases())
processTestcase(e, tc);
for(Testsuite ts : r.getTestsuites())
processTestsuite(e, ts);
processSystemOut(e, r.getSystem_out());
processSystemErr(e, r.getSystem_err());
n.appendChild(e);
}
/**
* Serializes and outputs the result as XML text to the given output stream using
* the default encoding.
*
* @param r
* @param stream
* @throws TransformerException
* @throws ParserConfigurationException
*/
public void serialize(JunitResult r, OutputStream stream) throws TransformerException, ParserConfigurationException {
serialize(r, stream, null);
}
/**
* Serializes and outputs the result as XML text to the given output stream.
*
* @param r
* @param stream
* @param encoding
* The encoding to use for the transformation or <code>null</code> for default
* @throws TransformerException
* @throws ParserConfigurationException
*/
public void serialize(JunitResult r, OutputStream stream, String encoding) throws TransformerException,
ParserConfigurationException {
// write the content into xml file
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(serializeToDom(r));
StreamResult result = new StreamResult(stream);
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
if(encoding != null)
transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.transform(source, result);
}
/**
* Serializes a testrun as the root of the document.
*
* @param r
*/
private void serializeTestrun(Testrun r) {
Element e = doc.createElement("testrun");
e.setAttribute("name", r.getName());
e.setAttribute("tests", Integer.toString(r.getTests()));
e.setAttribute("errors", Integer.toString(r.getErrors()));
e.setAttribute("failures", Integer.toString(r.getFailures()));
e.setAttribute("ignored", Integer.toString(r.getIgnored()));
e.setAttribute("started", Integer.toString(r.getStarted()));
for(Testsuite ts : r.getTestsuites())
processTestsuite(e, ts);
doc.appendChild(e);
}
/**
* Serializes a testsuite as the root of the document.
*
* @param r
*/
private void serializeTestsuite(Testsuite r) {
processTestsuite(doc, r);
}
/**
* Serializes a testsuites as the root of the document
*
* @param r
*/
private void serializeTestsuites(Testsuites r) {
Element e = doc.createElement("testsuites");
e.setAttribute("name", r.getName());
e.setAttribute("time", formatTime(r.getTime()));
e.setAttribute("tests", Integer.toString(r.getTests()));
e.setAttribute("errors", Integer.toString(r.getErrors()));
e.setAttribute("failures", Integer.toString(r.getFailures()));
e.setAttribute("disabled", Integer.toString(r.getDisabled()));
for(Testsuite ts : r.getTestsuites())
processTestsuite(e, ts);
doc.appendChild(e);
}
public Document serializeToDom(JunitResult r) throws ParserConfigurationException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
// root elements
doc = docBuilder.newDocument();
doc.setXmlStandalone(true); // informal, no validation should be performed
switch(r.eClass().getClassifierID()) {
case JunitresultPackage.TESTSUITE:
serializeTestsuite((Testsuite) r);
break;
case JunitresultPackage.TESTRUN:
serializeTestrun((Testrun) r);
break;
case JunitresultPackage.TESTSUITES:
serializeTestsuites((Testsuites) r);
break;
default:
throw new IllegalArgumentException("given JUnitResult is not one of the expected types.");
}
return doc;
}
}