abusalimov

SublimePythonCoverage.py

Jun 5th, 2013
66
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.44 KB | None | 0 0
  1. # bootstrap
  2. import os
  3. plugin_path = os.path.dirname(__file__)
  4. if not os.path.exists(os.path.join(plugin_path, 'coverage')):
  5.     # Fetch coverage.py
  6.     print 'SublimePythonCoverage installing coverage.py.'
  7.  
  8.     from StringIO import StringIO
  9.     import tarfile
  10.     import urllib
  11.     from hashlib import md5
  12.  
  13.     SOURCE = 'http://pypi.python.org/packages/source/c/coverage/coverage-3.5.2.tar.gz'
  14.     MD5SUM = '28c43d41b13f8987ea14d7b1d4a4e3ec'
  15.  
  16.     payload = urllib.urlopen(SOURCE).read()
  17.     if md5(payload).hexdigest() != MD5SUM:
  18.         raise ImportError('Invalid checksum.')
  19.  
  20.     tar = tarfile.open(mode='r:gz', fileobj=StringIO(payload))
  21.     for m in tar.getmembers():
  22.         if not m.name.startswith('coverage-3.5.2/coverage/'):
  23.             continue
  24.         m.name = '/'.join(m.name.split('/')[2:])
  25.         tar.extract(m, os.path.join(plugin_path, 'coverage'))
  26.  
  27.     print 'SublimePythonCoverage successfully installed coverage.py.'
  28. # end bootstrap
  29.  
  30.  
  31. import sublime
  32. import sublime_plugin
  33. from coverage import coverage
  34. from coverage.files import FnmatchMatcher
  35. PLUGIN_FILE = os.path.abspath(__file__)
  36.  
  37.  
  38. def find(base, rel, access=os.R_OK):
  39.     if not isinstance(rel, basestring):
  40.         rel = os.path.join(*rel)
  41.     while 1:
  42.         path = os.path.join(base, rel)
  43.         if os.access(path, access):
  44.             return path
  45.         baseprev = base
  46.         base = os.path.dirname(base)
  47.         if not base or base == baseprev:
  48.             return
  49.  
  50.  
  51. def find_cmd(base, cmd):
  52.     return find(base, ('bin', cmd), os.X_OK)
  53.  
  54.  
  55. def find_tests(fname):
  56.     dirname = os.path.dirname(fname)
  57.     init = os.path.join(dirname, '__init__.py')
  58.     if not os.path.exists(init):
  59.         # not a package; run tests for the file
  60.         return fname
  61.  
  62.     setup = find(dirname, 'setup.py')
  63.     if setup:
  64.         # run tests for the whole distribution
  65.         return os.path.dirname(setup)
  66.  
  67.     # run tests for the package
  68.     return os.path.dirname(fname)
  69.  
  70.  
  71. class SublimePythonCoverageListener(sublime_plugin.EventListener):
  72.     """Event listener to highlight uncovered lines when a Python file is loaded."""
  73.  
  74.     def on_load(self, view):
  75.         if 'source.python' not in view.scope_name(0):
  76.             return
  77.  
  78.         # view.run_command('show_python_coverage')
  79.  
  80.  
  81. class ShowPythonCoverageCommand(sublime_plugin.TextCommand):
  82.     """Highlight uncovered lines in the current file based on a previous coverage run."""
  83.  
  84.     def run(self, edit, show=True):
  85.         view = self.view
  86.         fname = view.file_name()
  87.         if not fname:
  88.             return
  89.  
  90.         # update highlighted regions
  91.         view.erase_regions('SublimePythonCoverage')
  92.  
  93.         if not show:
  94.             return
  95.  
  96.         cov_file = find(fname, '.coverage')
  97.         if not cov_file:
  98.             print 'Could not find .coverage file.'
  99.             return
  100.  
  101.         # run analysis and find uncovered lines
  102.         cov = coverage(data_file=cov_file)
  103.         outlines = []
  104.         omit_matcher = FnmatchMatcher(cov.omit)
  105.         if not omit_matcher.match(fname):
  106.             cov_dir = os.path.dirname(cov_file)
  107.             os.chdir(cov_dir)
  108.             relpath = os.path.relpath(fname, cov_dir)
  109.             cov.load()
  110.             f, s, excluded, missing, m = cov.analysis2(relpath)
  111.             for line in missing:
  112.                 outlines.append(view.full_line(view.text_point(line - 1, 0)))
  113.         if outlines:
  114.             view.add_regions('SublimePythonCoverage', outlines, 'comment',
  115.                 sublime.DRAW_EMPTY | sublime.DRAW_OUTLINED)
  116.             # view.add_regions('SublimePythonCoverage', outlines,
  117.             #                  'markup.inserted', 'bookmark', sublime.HIDDEN)
  118.  
  119.  
  120. # manually import the module containing ST2's default build command,
  121. # since it's in a module whose name is a Python keyword :-s
  122. ExecCommand = __import__('exec').ExecCommand
  123.  
  124.  
  125. class TestExecCommand(ExecCommand):
  126.     """An generic extension of the default build system which shows coverage at the end."""
  127.  
  128.     runner = None
  129.  
  130.     def cmd(self, runner, testpath):
  131.         NotImplemented
  132.  
  133.     def run(self, **kw):
  134.         if 'cmd' not in kw:
  135.             fname = self.window.active_view().file_name()
  136.  
  137.             # look for a virtualenv with nosetests, py.test etc
  138.             runner = find_cmd(fname, self.runner)
  139.             if runner is None:
  140.                 # no virtualenv; maybe there's a global one
  141.                 runner = self.runner
  142.  
  143.             testpath = find_tests(fname)
  144.             if os.path.isdir(testpath):
  145.                 kw['working_dir'] = testpath
  146.             else:
  147.                 kw['working_dir'] = os.path.dirname(testpath)
  148.  
  149.             kw['cmd'] = self.cmd(runner, testpath)
  150.  
  151.         super(TestExecCommand, self).run(**kw)
  152.  
  153.     def finish(self, proc):
  154.         super(TestExecCommand, self).finish(proc)
  155.         for view in self.window.views():
  156.             view.run_command('show_python_coverage')
  157.  
  158.  
  159. class NoseExecCommand(TestExecCommand):
  160.     """An extension of the default build system using the Python Nose test
  161.       runner to generate coverage information."""
  162.  
  163.     runner = 'nosetests'
  164.  
  165.     def cmd(self, runner, testpath):
  166.         return [runner, '--with-coverage', testpath]
  167.  
  168.  
  169. class PytestExecCommand(TestExecCommand):
  170.     """An extension of the default build system using the py.test test
  171.       runner to generate coverage information."""
  172.  
  173.     runner = 'py.test'
  174.  
  175.     def cmd(self, runner, testpath):
  176.         return [runner]
Advertisement
Add Comment
Please, Sign In to add comment