/** * 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; } }