Commits

Ned Batchelder committed 2a5c83e

Peter Portante's coroutine support, but it doesn't work yet.

  • Participants
  • Parent commits 77c627c

Comments (0)

Files changed (7)

     - .format() ?
     + try/except/finally
     + with assertRaises
+    - addCleaup instead of tearDown
     + exec statement can look like a function in py2 (since when?)
     - runpy ?
 

coverage/cmdline.py

         '', '--branch', action='store_true',
         help="Measure branch coverage in addition to statement coverage."
         )
+    coroutine = optparse.make_option(
+        '', '--coroutine', action='store', metavar="LIB",
+        help="Properly measure code using coroutines."
+        )
     debug = optparse.make_option(
         '', '--debug', action='store', metavar="OPTS",
         help="Debug options, separated by commas"
         self.set_defaults(
             actions=[],
             branch=None,
+            coroutine=None,
             debug=None,
             directory=None,
             fail_under=None,
         [
             Opts.append,
             Opts.branch,
+            Opts.coroutine,
             Opts.debug,
             Opts.pylib,
             Opts.parallel_mode,
             omit = omit,
             include = include,
             debug = debug,
+            coroutine = options.coroutine,
             )
 
         if 'debug' in options.actions:

coverage/collector.py

 """Raw data collector for Coverage."""
 
-import os, sys, threading
+import collections, os, sys, threading
 
 try:
     # Use the C extension code when we can, for speed.
         self.cur_file_data = None
         self.last_line = 0
         self.data_stack = []
+        self.data_stacks = collections.defaultdict(list)
         self.last_exc_back = None
         self.last_exc_firstlineno = 0
         self.arcs = False
         self.thread = None
         self.stopped = False
+        self.coroutine_id_func = None
+        self.last_coroutine = None
 
     def _trace(self, frame, event, arg_unused):
         """The trace function passed to sys.settrace."""
                 if self.arcs and self.cur_file_data:
                     pair = (self.last_line, -self.last_exc_firstlineno)
                     self.cur_file_data[pair] = None
+                if self.coroutine_id_func:
+                    self.data_stack = self.data_stacks[self.coroutine_id_func()]
                 self.cur_file_data, self.last_line = self.data_stack.pop()
             self.last_exc_back = None
 
         if event == 'call':
             # Entering a new function context.  Decide if we should trace
             # in this file.
+            if self.coroutine_id_func:
+                self.data_stack = self.data_stacks[self.coroutine_id_func()]
+                self.last_coroutine = self.coroutine_id_func()
             self.data_stack.append((self.cur_file_data, self.last_line))
             filename = frame.f_code.co_filename
             if filename not in self.should_trace_cache:
             self.last_line = -1
         elif event == 'line':
             # Record an executed line.
+            #if self.coroutine_id_func:
+            #    assert self.last_coroutine == self.coroutine_id_func()
             if self.cur_file_data is not None:
                 if self.arcs:
                     #print("lin", self.last_line, frame.f_lineno)
                 first = frame.f_code.co_firstlineno
                 self.cur_file_data[(self.last_line, -first)] = None
             # Leaving this function, pop the filename stack.
+            if self.coroutine_id_func:
+                self.data_stack = self.data_stacks[self.coroutine_id_func()]
+                self.last_coroutine = self.coroutine_id_func()
             self.cur_file_data, self.last_line = self.data_stack.pop()
             #print("returned, stack is %d deep" % (len(self.data_stack)))
         elif event == 'exception':
     # the top, and resumed when they become the top again.
     _collectors = []
 
-    def __init__(self, should_trace, timid, branch, warn):
+    def __init__(self, should_trace, timid, branch, warn, coroutine):
         """Create a collector.
 
         `should_trace` is a function, taking a filename, and returning a
         self.should_trace = should_trace
         self.warn = warn
         self.branch = branch
+        if coroutine == "greenlet":
+            import greenlet
+            self.coroutine_id_func = greenlet.getcurrent
+        elif coroutine == "eventlet":
+            import eventlet.greenthread
+            self.coroutine_id_func = eventlet.greenthread.getcurrent
+        elif coroutine == "gevent":
+            import gevent
+            self.coroutine_id_func = gevent.getcurrent
+        else:
+            self.coroutine_id_func = None
         self.reset()
 
         if timid:
         tracer.should_trace = self.should_trace
         tracer.should_trace_cache = self.should_trace_cache
         tracer.warn = self.warn
+        if hasattr(tracer, 'coroutine_id_func'):
+            tracer.coroutine_id_func = self.coroutine_id_func
         fn = tracer.start()
         self.tracers.append(tracer)
         return fn

coverage/config.py

 
         # Defaults for [run]
         self.branch = False
+        self.coroutine = None
         self.cover_pylib = False
         self.data_file = ".coverage"
         self.parallel = False
     CONFIG_FILE_OPTIONS = [
         # [run]
         ('branch', 'run:branch', 'boolean'),
+        ('coroutine', 'run:coroutine'),
         ('cover_pylib', 'run:cover_pylib', 'boolean'),
         ('data_file', 'run:data_file'),
         ('debug', 'run:debug', 'list'),

coverage/control.py

     def __init__(self, data_file=None, data_suffix=None, cover_pylib=None,
                 auto_data=False, timid=None, branch=None, config_file=True,
                 source=None, omit=None, include=None, debug=None,
-                debug_file=None):
+                debug_file=None, coroutine=None):
         """
         `data_file` is the base name of the data file to use, defaulting to
         ".coverage".  `data_suffix` is appended (with a dot) to `data_file` to
         desired. `debug_file` is the file to write debug messages to,
         defaulting to stderr.
 
+        `coroutine` is a string indicating the coroutining library being used
+        in the measured code.  Without this, coverage.py will get incorrect
+        results.  Valid strings are "greenlet", "eventlet", or "gevent", which
+        are all equivalent.
+
         """
         from coverage import __version__
 
             data_file=data_file, cover_pylib=cover_pylib, timid=timid,
             branch=branch, parallel=bool_or_none(data_suffix),
             source=source, omit=omit, include=include, debug=debug,
+            coroutine=coroutine,
             )
 
         # Create and configure the debugging controller.
 
         self.collector = Collector(
             self._should_trace, timid=self.config.timid,
-            branch=self.config.branch, warn=self._warn
+            branch=self.config.branch, warn=self._warn,
+            coroutine=self.config.coroutine,
             )
 
         # Suffixes are a bit tricky.  We want to use the data suffix only when

tests/test_cmdline.py

     defaults.coverage(
         cover_pylib=None, data_suffix=None, timid=None, branch=None,
         config_file=True, source=None, include=None, omit=None, debug=None,
+        coroutine=None,
     )
     defaults.annotate(
         directory=None, ignore_errors=None, include=None, omit=None, morfs=[],
     nose
     mock
     unittest2
+    gevent
+    eventlet
 
 [testenv:py27]
 deps =
     nose
     mock
     unittest2
+    gevent
+    eventlet
 
 [testenv:pypy]
 # PyPy has no C extensions