Skip to content

Commit

Permalink
initial experiments
Browse files Browse the repository at this point in the history
  • Loading branch information
davisagli committed May 1, 2012
0 parents commit 24dd42d
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Default.sublime-keymap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
{ "keys": ["super+shift+c"], "command": "show_python_coverage"}
]
6 changes: 6 additions & 0 deletions Python Nose Testrunner.sublime-build
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"target": "nose_exec",
"cmd": ["python", "/Users/davidg/Library/Application Support/Sublime Text 2/Packages/Coverage/SublimeCoverage/nose_runner.py", "$file"],
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
"selector": "source.python"
}
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Sublime Text 2 plugin to perform code coverage analysis and highlight uncovered lines. (Work in progress.)
70 changes: 70 additions & 0 deletions SublimeCoveragePlugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from subprocess import Popen, PIPE
import os
import sublime, sublime_plugin
import sys
from SublimePythonCoverage.utils import find, find_cmd


PLUGIN_FILE = os.path.abspath(__file__)


class SublimePythonCoverageListener(sublime_plugin.EventListener):
"""Event listener to highlight uncovered lines when a Python file is loaded."""

def on_load(self, view):
if 'source.python' not in view.scope_name(0):
return

view.run_command('show_python_coverage')


class ShowPythonCoverageCommand(sublime_plugin.TextCommand):
"""Highlight uncovered lines in the current file based on a previous coverage run."""

def run(self, edit):
view = self.view
fname = view.file_name()
if not fname:
return

cov_file = find(fname, '.coverage')
if not cov_file:
print 'Could not find .coverage file.'
return

python = find_cmd(PLUGIN_FILE, 'python')
reader = find(PLUGIN_FILE, ('SublimePythonCoverage', 'read_coverage.py'))
p = Popen([python, reader, cov_file, fname], stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate(None)
print stdout, stderr

outlines = []
for line in stdout.splitlines():
line = int(line)
outlines.append(view.full_line(view.text_point(line - 1, 0)))
view.erase_regions('SublimePythonCoverage')
view.add_regions('SublimePythonCoverage', outlines, 'comment',
sublime.DRAW_EMPTY | sublime.DRAW_OUTLINED)


# manually import the module containing ST2's default build command,
# since it's in a module whose name is a Python keyword :-s
ExecCommand = __import__('exec').ExecCommand
class NoseExecCommand(ExecCommand):
"""An extension of the default build system which shows coverage at the end.
Used by the Python Nose build system.
"""

def finish(self, proc):
super(NoseExecCommand, self).finish(proc)
for view in self.window.views():
view.run_command('show_python_coverage')


# TODO:
# - move read_coverage inline if possible
# - coverage egg embedded or installed via helper script
# - refactor nose_runner to be part of NoseExecCommand
# - instructions on installation
# - documentation
Empty file.
32 changes: 32 additions & 0 deletions SublimePythonCoverage/nose_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Helper to run nosetests appropriately for a given file.
If the file is in a Python package, nosetests is run for the package.
Otherwise it is run for the file.
Usage: python nose_runner.py file_under_test
"""
import os
from subprocess import Popen
import sys
from utils import find_cmd, find_tests


if __name__ == '__main__':
fname = sys.argv[1]
dirname = os.path.dirname(fname)

# look for a virtualenv with nosetests
nose = find_cmd(fname, 'nosetests')
if nose is None:
nose = find_cmd(__file__, 'nosetests')
if nose is None:
print 'Could not find nose.'
sys.exit()

testpath = find_tests(fname)
if os.path.isdir(testpath):
os.chdir(testpath)
else:
os.chdir(os.path.dirname(testpath))
p = Popen([nose, '--with-coverage', testpath])
sys.exit(p.wait())
20 changes: 20 additions & 0 deletions SublimePythonCoverage/read_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Helper to run coverage analysis and list uncovered lines.
Usage: python read_coverage.py coverage_file file_under_test
"""

import os
import sys
from coverage import coverage


if __name__ == '__main__':
cov_file, fname = sys.argv[1:]
cov = coverage(data_file=cov_file)
cov_dir = os.path.dirname(cov_file)
os.chdir(cov_dir)
relpath = os.path.relpath(fname, cov_dir)
cov.load()
f, s, excluded, missing, m = cov.analysis2(relpath)
for m in missing:
print m
33 changes: 33 additions & 0 deletions SublimePythonCoverage/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os


def find(base, rel, access=os.R_OK):
if not isinstance(rel, basestring):
rel = os.path.join(*rel)
while 1:
path = os.path.join(base, rel)
if os.access(path, access):
return path
base = os.path.dirname(base)
if not base or base == '/':
return


def find_cmd(base, cmd):
return find(base, ('bin', cmd), os.X_OK)


def find_tests(fname):
dirname = os.path.dirname(fname)
init = os.path.join(dirname, '__init__.py')
if not os.path.exists(init):
# not a package; run tests for the file
return fname

setup = find(dirname, 'setup.py')
if setup:
# run tests for the whole distribution
return os.path.dirname(setup)

# run tests for the package
return os.path.dirname(fname)

0 comments on commit 24dd42d

Please sign in to comment.