/** * 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. */ /* * Created on Oct 7, 2004 * * @author Fabio Zadrozny */ package org.python.pydev.debug.codecoverage; import java.io.File; import java.io.OutputStream; import java.util.Iterator; import org.eclipse.core.resources.IContainer; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.python.pydev.core.log.Log; import org.python.pydev.debug.core.PydevDebugPlugin; import org.python.pydev.debug.ui.launching.PythonRunnerConfig; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.runners.UniversalRunner; import org.python.pydev.runners.UniversalRunner.AbstractRunner; import org.python.pydev.shared_core.io.FileUtils; import org.python.pydev.shared_core.io.ThreadStreamReader; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.utils.PyFileListing; import org.python.pydev.utils.PyFileListing.PyFileInfo; /** * This class is used to make the code coverage. * * It works in this way: when the user requests the coverage for the execution of a module, we create a python process and execute the * module using the code coverage module that is packed with pydev. * * Other options are: * - Erasing the results obtained; * - Getting the results when requested (cached in this class). * * @author Fabio Zadrozny */ public class PyCoverage { public CoverageCache cache = new CoverageCache(); /** * This method contacts the python server so that we get the information on the files that are below the directory passed as a parameter * and stores the information needed on the cache. * * @param file * should be the root folder from where we want cache info. */ public void refreshCoverageInfo(IContainer container, IProgressMonitor monitor) throws CoverageException { int exitValue = 0; String stdOut = ""; String stdErr = ""; cache.clear(); if (container == null) { return; } try { if (!container.exists()) { throw new RuntimeException("The directory passed: " + container + " no longer exists."); } File file = container.getLocation().toFile(); PyFileListing pyFilesBelow = new PyFileListing(); if (file.exists()) { pyFilesBelow = PyFileListing.getPyFilesBelow(file, monitor, true, false); } if (pyFilesBelow.getFoundPyFileInfos().size() == 0) { //no files return; } //add the folders to the cache boolean added = false; for (Iterator<File> it = pyFilesBelow.getFoundFolders().iterator(); it.hasNext();) { File f = it.next(); if (!added) { cache.addFolder(f); added = true; } else { cache.addFolder(f, f.getParentFile()); } } PythonNature nature = PythonNature.getPythonNature(container); if (nature == null) { throw new RuntimeException("The directory passed: " + container + " does not have an associated nature."); } AbstractRunner runner = UniversalRunner.getRunner(nature); //First, combine the results of the many runs we may have. Tuple<String, String> output = runner.runScriptAndGetOutput(PythonRunnerConfig.getCoverageScript(), new String[] { "combine" }, getCoverageDirLocation(), monitor); if (output.o1 != null && output.o1.length() > 0) { Log.logInfo(output.o1); } if (output.o2 != null && output.o2.length() > 0) { if (output.o2.startsWith("Coverage.py warning:")) { Log.logInfo(output.o2); } else { Log.log(output.o2); } } //we have to make a process to execute the script. it should look // like: //coverage.py -r [-m] FILE1 FILE2 ... //Report on the statement coverage for the given files. With the -m //option, show line numbers of the statements that weren't // executed. //python coverage.py -r -m files.... monitor.setTaskName("Starting shell to get info..."); File coverageDirLocation = getCoverageDirLocation(); File coverageXmlLocation = new File(coverageDirLocation, "coverage.xml"); if (coverageXmlLocation.exists()) { coverageXmlLocation.delete(); } monitor.worked(1); Process p = null; try { // Will create the coverage.xml file for us to process later on. Tuple<Process, String> tup = runner.createProcess(PythonRunnerConfig.getCoverageScript(), new String[] { "--pydev-analyze" }, coverageDirLocation, monitor); p = tup.o1; String files = ""; for (Iterator<PyFileInfo> iter = pyFilesBelow.getFoundPyFileInfos().iterator(); iter.hasNext();) { String fStr = iter.next().getFile().toString(); files += fStr + "|"; } files += "\r"; monitor.setTaskName("Writing to shell..."); //No need to synchronize as we'll waitFor() the process before getting the contents. ThreadStreamReader inputStream = new ThreadStreamReader(p.getInputStream(), false); inputStream.start(); ThreadStreamReader errorStream = new ThreadStreamReader(p.getErrorStream(), false); errorStream.start(); monitor.worked(1); OutputStream outputStream = p.getOutputStream(); outputStream.write(files.getBytes()); outputStream.close(); monitor.setTaskName("Waiting for process to finish..."); monitor.worked(1); while (true) { try { exitValue = p.exitValue(); break; //process finished } catch (IllegalThreadStateException e) { //not finished } try { Thread.sleep(50); } catch (InterruptedException e) { //ignore } monitor.worked(1); if (monitor.isCanceled()) { try { p.destroy(); } catch (Exception e) { Log.log(e); } break; } } stdOut = inputStream.getAndClearContents().trim(); stdErr = errorStream.getAndClearContents().trim(); if (stdOut.length() > 0) { Log.log(stdOut); } if (stdErr.length() > 0) { Log.log(stdErr); } monitor.setTaskName("Getting coverage info...(please wait, this could take a while)"); monitor.worked(1); if (!coverageXmlLocation.exists()) { Log.log("Expected file: " + coverageXmlLocation + " to be written to analyze coverage info."); } else { CoverageXmlInfo.analyze(cache, coverageXmlLocation); } monitor.setTaskName("Finished"); } catch (Exception e) { if (p != null) { p.destroy(); } Log.log(e); } } catch (Exception e1) { Log.log(e1); throw new RuntimeException(e1); } if (exitValue != 0) { FastStringBuffer buf = new FastStringBuffer("Error with coverage action (exit value: ", stdErr.length() + stdOut.length() + 40); buf.append(exitValue).append(")."); if (stdOut.length() > 0) { buf.append("\nStandard outputt:\n"); buf.append(stdOut); } if (stdErr.length() > 0) { buf.append("\nError output:\n"); buf.append(stdErr); } throw new CoverageException(buf.toString()); } } /** * */ public void clearInfo() { cache.clear(); File dir = getCoverageDirLocation(); try { //Clear the files we created when running the coverages. FileUtils.clearTempFilesAt(dir, ".coverage."); } catch (Exception e) { Log.log(e); } try { //We also need to remove the file that consolidates all the info new File(dir, ".coverage").delete(); } catch (Exception e) { Log.log(e); } } private static PyCoverage pyCoverage; /** * @return Returns the pyCoverage. */ public static PyCoverage getPyCoverage() { if (pyCoverage == null) { pyCoverage = new PyCoverage(); } return pyCoverage; } public static File getCoverageDirLocation() { IPath stateLocation = PydevDebugPlugin.getDefault().getStateLocation(); stateLocation = stateLocation.append("coverage"); String loc = FileUtils.getFileAbsolutePath(stateLocation.toFile()); File dir = new File(loc); try { dir.mkdirs(); } catch (Exception e) { Log.log(e); } if (!dir.exists()) { throw new RuntimeException("The directory: " + loc + " could not be created."); } if (!dir.isDirectory()) { throw new RuntimeException("Expected the path: " + loc + " to be a directory."); } return dir; } /** * @return */ public static File getCoverageFileLocation() { return FileUtils.getTempFileAt(getCoverageDirLocation(), ".coverage."); } }