/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.debug.pyunit;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.model.XMLUtils;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.structure.Tuple;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.ProcessingInstruction;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class PyUnitTestRun {
private final ArrayList<PyUnitTestResult> results;
private final Map<Tuple<String, String>, PyUnitTestStarted> testsRunning;
private final Object resultsLock = new Object();
private final Object testsRunningLock = new Object();
public final String name;
private static int currentRun = 0;
private static Object lock = new Object();
private int numberOfErrors;
private int numberOfFailures;
private String totalNumberOfRuns = "0";
private boolean finished;
private IPyUnitLaunch pyUnitLaunch;
private int nextIndex = 0;
private String totalTime; //null while not set.
/**
* Helper to know whether we've already saved this PyUnitTestRun to the disk to be restored later.
*/
public Integer savedDiskIndex;
public PyUnitTestRun(IPyUnitLaunch server) {
synchronized (lock) {
this.name = "Test Run: " + currentRun;
currentRun += 1;
}
this.pyUnitLaunch = server;
this.results = new ArrayList<PyUnitTestResult>();
this.testsRunning = new LinkedHashMap<Tuple<String, String>, PyUnitTestStarted>();
}
public Collection<PyUnitTestStarted> getTestsRunning() {
synchronized (testsRunningLock) {
return new ArrayList<PyUnitTestStarted>(testsRunning.values());
}
}
public void setTotalNumberOfRuns(String totalNumberOfRuns) {
this.totalNumberOfRuns = totalNumberOfRuns;
}
public synchronized void addResult(PyUnitTestResult result) {
if (result.status.equals("fail")) {
numberOfFailures += 1;
} else if (result.status.equals("error")) {
numberOfErrors += 1;
} else if (result.isOk() || result.isSkip()) {
//ignore
} else {
Log.log("Unexpected status: " + result.status);
}
Tuple<String, String> key = new Tuple<String, String>(result.location, result.test);
synchronized (testsRunningLock) {
this.testsRunning.remove(key);//when a result is added, it should be removed from the tests running.
}
synchronized (resultsLock) {
results.add(result);
}
}
public void addStartTest(PyUnitTestStarted result) {
Tuple<String, String> key = new Tuple<String, String>(result.location, result.test);
synchronized (testsRunningLock) {
this.testsRunning.put(key, result);
}
}
/**
* @return the same instance that's used internally to back up the results (use with care outside of this api
* mostly for testing).
*/
public List<PyUnitTestResult> getSharedResultsList() {
return results;
}
public int getNumberOfRuns() {
synchronized (resultsLock) {
return results.size();
}
}
public int getNumberOfErrors() {
return numberOfErrors;
}
public int getNumberOfFailures() {
return numberOfFailures;
}
public String getTotalNumberOfRuns() {
return totalNumberOfRuns;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
public boolean getFinished() {
return finished;
}
public void stop() {
if (this.pyUnitLaunch != null) {
IPyUnitLaunch s = this.pyUnitLaunch;
if (s != null) {
s.stop();
}
}
}
public void relaunch() {
if (this.pyUnitLaunch != null) {
IPyUnitLaunch s = this.pyUnitLaunch;
if (s != null) {
s.relaunch();
}
}
}
@Override
public String toString() {
return "PyUnitTestResult.\n" + " Finished: " + this.finished + "\n" + " Number of runs: "
+ this.results.size() + "" + " Number of failures:" + this.numberOfFailures + "\n"
+ " Number of errors: " + this.numberOfErrors + "\n" + "";
}
public void relaunchOnlyErrors() {
IPyUnitLaunch s = this.pyUnitLaunch;
if (s != null) {
ArrayList<PyUnitTestResult> arrayList = new ArrayList<PyUnitTestResult>(this.results.size());
for (PyUnitTestResult pyUnitTestResult : this.results) {
if (!pyUnitTestResult.isOk() && !pyUnitTestResult.isSkip()) {
arrayList.add(pyUnitTestResult);
}
}
s.relaunchTestResults(arrayList);
}
}
/**
* @param mode ILaunchManager.DEBUG_MODE or ILaunchManager.RUN_MODE
*/
public void relaunch(List<PyUnitTestResult> resultsToRelaunch, String mode) {
IPyUnitLaunch s = this.pyUnitLaunch;
if (s != null) {
s.relaunchTestResults(resultsToRelaunch, mode);
} else {
Log.log("Unable to relaunch (the original launch is no longer available or it was not properly restored).");
}
}
public synchronized String getNextTestIndex() {
return Integer.toString(++nextIndex);
}
public void setTotalTime(String totalTime) {
this.totalTime = totalTime;
}
public String getTotalTime() {
return this.totalTime;
}
public String getShortDescription() {
FastStringBuffer buf = new FastStringBuffer(this.name, 20);
buf.append(" (");
buf.append("Tests: ");
buf.append(this.getTotalNumberOfRuns());
if (this.getNumberOfErrors() > 0) {
buf.append(" Errors: ");
buf.append(this.getNumberOfErrors());
}
if (this.getNumberOfFailures() > 0) {
buf.append(" Failures: ");
buf.append(this.getNumberOfFailures());
}
buf.append(")");
return buf.toString();
}
public String toXML() {
try {
DocumentBuilderFactory icFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder icBuilder = icFactory.newDocumentBuilder();
Document document = icBuilder.newDocument();
ProcessingInstruction version = document.createProcessingInstruction("pydev-testrun", "version=\"1.0\""); //$NON-NLS-1$ //$NON-NLS-2$
document.appendChild(version);
PyUnitTestRun pyUnitTestRun = this;
Element root = document.createElement("pydev-testsuite");
document.appendChild(root);
Element summary = document.createElement("summary");
summary.setAttribute("name", name);
summary.setAttribute("errors", String.valueOf(pyUnitTestRun.getNumberOfErrors()));
summary.setAttribute("failures", String.valueOf(pyUnitTestRun.getNumberOfFailures()));
summary.setAttribute("tests", String.valueOf(pyUnitTestRun.getTotalNumberOfRuns()));
summary.setAttribute("finished", String.valueOf(pyUnitTestRun.getFinished()));
summary.setAttribute("total_time", String.valueOf(pyUnitTestRun.getTotalTime()));
root.appendChild(summary);
for (PyUnitTestResult pyUnitTestResult : pyUnitTestRun.getResults()) {
Element test = document.createElement("test");
test.setAttribute("status", pyUnitTestResult.status);
test.setAttribute("location", pyUnitTestResult.location);
test.setAttribute("test", pyUnitTestResult.test);
test.setAttribute("time", pyUnitTestResult.time);
Element stdout = document.createElement("stdout");
test.appendChild(stdout);
stdout.appendChild(document.createCDATASection(pyUnitTestResult.capturedOutput));
Element stderr = document.createElement("stderr");
test.appendChild(stderr);
stderr.appendChild(document.createCDATASection(pyUnitTestResult.errorContents));
root.appendChild(test);
}
Element launchElement = document.createElement("launch");
root.appendChild(launchElement);
if (pyUnitLaunch != null) {
pyUnitLaunch.fillXMLElement(document, launchElement);
}
ByteArrayOutputStream s = new ByteArrayOutputStream();
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
DOMSource source = new DOMSource(document);
StreamResult outputTarget = new StreamResult(s);
transformer.transform(source, outputTarget);
return new String(s.toByteArray(), StandardCharsets.UTF_8);
} catch (Exception e) {
Log.log(e);
}
return "";
}
private List<PyUnitTestResult> getResults() {
List<PyUnitTestResult> lst;
synchronized (resultsLock) {
lst = new ArrayList<>(results);
}
return lst;
}
public static class FillTestRunXmlHandler extends DefaultHandler {
private final PyUnitTestRun testRun;
private String fStatus;
private String fLocation;
private String fTest;
private String fTime;
private boolean fInStdout;
private boolean fInStderr;
private String fErrorContents;
private String fCapturedOutput;
private boolean fInLaunchMemento;
private String fLaunchMementoContents;
private String fLaunchMode;
public FillTestRunXmlHandler(PyUnitTestRun testRun) {
this.testRun = testRun;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//System.out.println("chars: " + new String(ch, start, length));
if (fInStdout) {
fCapturedOutput = new String(ch, start, length);
} else if (fInStderr) {
fErrorContents = new String(ch, start, length);
} else if (fInLaunchMemento) {
fLaunchMementoContents = new String(ch, start, length);
}
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
//int len = attributes.getLength();
//System.out.println("start: " + localName + " - " + qName + " attrs:" + len);
//for (int i = 0; i < len; i++) {
// System.out.println("attr: " + attributes.getQName(i) + " - " + attributes.getValue(i));
//}
if ("stdout".equals(qName)) {
this.fInStdout = true;
} else if ("stderr".equals(qName)) {
this.fInStderr = true;
} else if ("launch_memento".equals(qName)) {
this.fInLaunchMemento = true;
} else if ("launch".equals(qName)) {
this.fLaunchMode = attributes.getValue("mode");
} else if ("test".equals(qName)) {
fStatus = attributes.getValue("status");
fLocation = attributes.getValue("location");
fTest = attributes.getValue("test");
fTime = attributes.getValue("time");
} else if ("summary".equals(qName)) {
// name: auto
// errors: auto
// failures: auto
// tests: numberOfRuns
String totalNumberOfRuns = attributes.getValue("tests");
if (totalNumberOfRuns != null) {
testRun.setTotalNumberOfRuns(totalNumberOfRuns);
}
// finished: If null not finished
testRun.setFinished("true".equals(attributes.getValue("finished")));
String totalTime = attributes.getValue("total_time");
if (totalTime != null) {
testRun.setTotalTime(totalTime);
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
//System.out.println("end: " + localName + " - " + qName);
if ("stdout".equals(qName)) {
this.fInStdout = false;
} else if ("stderr".equals(qName)) {
this.fInStderr = false;
} else if ("launch_memento".equals(qName)) {
this.fInLaunchMemento = false;
} else if ("launch".equals(qName)) {
IPyUnitLaunch fromIO = PyUnitLaunch.fromIO(fLaunchMode, fLaunchMementoContents);
testRun.pyUnitLaunch = fromIO;
} else if ("test".equals(qName)) {
if (fStatus == null) {
fStatus = "<no status>";
}
if (fLocation == null) {
fLocation = "<no location>";
}
if (fTest == null) {
fTest = "<no test>";
}
if (fTime == null) {
fTime = "<no time>";
}
if (fCapturedOutput == null) {
fCapturedOutput = "";
}
if (fErrorContents == null) {
fErrorContents = "";
}
PyUnitTestResult result = new PyUnitTestResult(testRun, fStatus, fLocation, fTest, fCapturedOutput,
fErrorContents, fTime);
testRun.addResult(result);
fCapturedOutput = null;
fErrorContents = null;
} else if ("summary".equals(qName)) {
// we only have attributes, so, it's filled at open.
}
}
}
public static PyUnitTestRun fromXML(String exportToClipboard) throws Exception {
PyUnitTestRun testRun = new PyUnitTestRun(null); // the actuall launch will be filled during the parsing.
SAXParser parser = XMLUtils.getSAXParser();
FillTestRunXmlHandler handler = new FillTestRunXmlHandler(testRun);
parser.parse(new ByteArrayInputStream(exportToClipboard.getBytes(StandardCharsets.UTF_8)), handler);
return testRun;
}
/*default*/ IPyUnitLaunch getPyUnitLaunch() {
return this.pyUnitLaunch;
}
}