From 563e02c6b27c88c2429cc8e19291f812b0b9fdb7 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Mon, 25 Jun 2012 13:17:05 -0700 Subject: Add .travis.yml for Travis CI (http://travis-ci.org/) --- .travis.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a6e7265 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: python + +python: + - 2.5 + - 2.6 + - 2.7 + - 3.2 + - pypy + +env: + - COVERAGE_TEST_TRACER=py + - COVERAGE_TEST_TRACER=c + +install: + - python setup.py clean install + +script: + - make testdata + - PYTHONPATH=test/eggsrc nosetests -e test_farm -e is_like_python test -- cgit v1.2.1 From 405d0cf5760032ac6bc3f9a07c72094ff20a454c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 3 May 2013 20:58:23 -0400 Subject: Fix the new test for py3, and add a test for #212. --- tests/test_arcs.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tests/test_arcs.py b/tests/test_arcs.py index f3c5fc3..f09af8c 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -172,9 +172,9 @@ if sys.version_info >= (2, 6): self.check_coverage("""\ for i in range(2): with open("test", "w") as f: - print 3 - print 4 - print 5 + print(3) + print(4) + print(5) """, arcz=".1 12 23 34 41 15 5." ) @@ -510,6 +510,26 @@ class ExceptionArcTest(CoverageTest): arcz=".1 12 23 35 56 61 17 7.", arcz_missing="", arcz_unpredicted="") + def test_bug_212(self): + self.check_coverage("""\ + def b(exc): + try: + while True: + raise Exception(exc) + except Exception as e: + if e.args != ('expected',): + raise e + q = 1 + + b('expected') + try: + b('unexpected') + except: + pass + """, + arcz=".1 .2 1A 23 34 56 67 68 8. 85 AB BC C. DE E.", + arcz_missing="") + if sys.version_info >= (2, 5): # Try-except-finally was new in 2.5 def test_except_finally(self): -- cgit v1.2.1 From 9d5a487c36f2cf7823021afa96b8d9ead9eb5ff7 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 9 Jun 2013 19:10:28 -0400 Subject: Branch coverage improvement, fixes #90. Bug #212 fixed on py2, but not py3. --- CHANGES.txt | 3 ++- coverage/parser.py | 5 ---- tests/test_arcs.py | 75 +++++++++++++++++++++++++++--------------------------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 316df0c..2f4516e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,7 +5,7 @@ Change history for Coverage.py 3.6.1 ----- -- Improved the branch coverage mechanism, fixing `issue 175`_. +- Improved the branch coverage facility, fixing `issue 90`_ and `issue 175`_. - Running code with ``coverage run -m`` now behaves more like Python does, setting sys.path properly, which fixes `issue 207`_ and `issue 242`_. @@ -18,6 +18,7 @@ Change history for Coverage.py a spurious warning about the trace function changing: "Trace function changed, measurement is likely wrong: None." This fixes `issue 164`_. +.. _issue 92: https://bitbucket.org/ned/coveragepy/issue/92/finally-clauses-arent-treated-properly-in .. _issue 164: https://bitbucket.org/ned/coveragepy/issue/164/trace-function-changed-warning-when-using .. _issue 175: https://bitbucket.org/ned/coveragepy/issue/175/branch-coverage-gets-confused-in-certain .. _issue 207: https://bitbucket.org/ned/coveragepy/issue/207/run-m-cannot-find-module-or-package-in diff --git a/coverage/parser.py b/coverage/parser.py index 8d6a077..2d777a5 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -512,11 +512,6 @@ class ByteParser(object): chunk.exits.add(block_stack[-1][1]) chunk = None if bc.op == OP_END_FINALLY: - if block_stack: - # A break that goes through a finally will jump to whatever - # block is on top of the stack. - # print self._block_stack_repr(block_stack) - chunk.exits.add(block_stack[-1][1]) # For the finally clause we need to find the closest exception # block, and use its jump target as an exit. for block in reversed(block_stack): diff --git a/tests/test_arcs.py b/tests/test_arcs.py index f09af8c..d7e2e48 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -141,7 +141,7 @@ class SimpleArcTest(CoverageTest): ) if 0: # expected failure - def test_lambdas_are_confusing_bug_90(self): + def test_unused_lambdas_are_confusing_bug_90(self): self.check_coverage("""\ a = 1 fn = lambda x: x @@ -456,8 +456,8 @@ class ExceptionArcTest(CoverageTest): d = 12 # C assert a == 5 and c == 10 and d == 12 # D """, - arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB AD BC CD D.", - arcz_missing="3D AD", arcz_unpredicted="7A") + arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB BC CD D.", + arcz_missing="3D", arcz_unpredicted="7A") self.check_coverage("""\ a, c, d, i = 1, 1, 1, 99 try: @@ -473,8 +473,8 @@ class ExceptionArcTest(CoverageTest): d = 12 # C assert a == 8 and c == 10 and d == 1 # D """, - arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB AD BC CD D.", - arcz_missing="67 AB AD BC CD", arcz_unpredicted="") + arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB BC CD D.", + arcz_missing="67 AB BC CD", arcz_unpredicted="") def test_break_in_finally(self): @@ -493,42 +493,43 @@ class ExceptionArcTest(CoverageTest): d = 12 # C assert a == 5 and c == 10 and d == 1 # D """, - arcz=".1 12 23 34 3D 45 56 67 68 7A 8A A3 AB AD BC CD D.", - arcz_missing="3D AB BC CD", arcz_unpredicted="") + arcz=".1 12 23 34 3D 45 56 67 68 7A 8A A3 AB BC CD D.", + arcz_missing="3D AB BC CD", arcz_unpredicted="AD") - if 0: # expected failure - def test_finally_in_loop_bug_92(self): - self.check_coverage("""\ - for i in range(5): - try: - j = 3 - finally: - f = 5 - g = 6 - h = 7 - """, - arcz=".1 12 23 35 56 61 17 7.", - arcz_missing="", arcz_unpredicted="") - - def test_bug_212(self): + def test_finally_in_loop_bug_92(self): self.check_coverage("""\ - def b(exc): + for i in range(5): try: - while True: - raise Exception(exc) - except Exception as e: - if e.args != ('expected',): - raise e - q = 1 - - b('expected') - try: - b('unexpected') - except: - pass + j = 3 + finally: + f = 5 + g = 6 + h = 7 """, - arcz=".1 .2 1A 23 34 56 67 68 8. 85 AB BC C. DE E.", - arcz_missing="") + arcz=".1 12 23 35 56 61 17 7.", + arcz_missing="", arcz_unpredicted="") + + if sys.version_info >= (2, 6): + # "except Exception as e" is crucial here. + def test_bug_212(self): + self.check_coverage("""\ + def b(exc): + try: + while 1: + raise Exception(exc) # 4 + except Exception as e: + if exc != 'expected': + raise + q = 8 + + b('expected') + try: + b('unexpected') # C + except: + pass + """, + arcz=".1 .2 1A 23 34 56 67 68 8. AB BC C. DE E.", + arcz_missing="C.", arcz_unpredicted="45 7. CD") if sys.version_info >= (2, 5): # Try-except-finally was new in 2.5 -- cgit v1.2.1 From d685c5c27ad433afa35c7627070c1fcdbde160ee Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 14 Aug 2013 08:10:41 -0400 Subject: Get test_html working under OS X when run from a venv. Thank, Brett Cannon. --- tests/test_html.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_html.py b/tests/test_html.py index 40852ac..eff3ca3 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -291,6 +291,7 @@ class HtmlTest(CoverageTest): os.remove("sub/another.py") missing_file = os.path.join(self.temp_dir, "sub", "another.py") + missing_file = os.path.realpath(missing_file) self.assertRaisesRegexp(NoSource, "(?i)No source for code: '%s'" % re.escape(missing_file), cov.html_report -- cgit v1.2.1 From eff1ff7311086c694d43443458127725a7f6618e Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 14 Aug 2013 08:12:21 -0400 Subject: Make igor's do_help work under Python 3. Thanks, Brett Cannon. --- igor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igor.py b/igor.py index e093d49..79a8b4b 100644 --- a/igor.py +++ b/igor.py @@ -217,7 +217,7 @@ def print_banner(label): def do_help(): """List the available commands""" - items = globals().items() + items = list(globals().items()) items.sort() for name, value in items: if name.startswith('do_'): -- cgit v1.2.1 From ed15f264b3183f63eebbb2f35e68f88e2f70f403 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 15 Aug 2013 20:36:34 -0400 Subject: Include __main__.py in the source kit. Fixes #255. --- CHANGES.txt | 4 ++++ MANIFEST.in | 1 + Makefile | 4 +++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2f4516e..79794dc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -18,12 +18,16 @@ Change history for Coverage.py a spurious warning about the trace function changing: "Trace function changed, measurement is likely wrong: None." This fixes `issue 164`_. +- The source kit omitted the __main__.py file in the root coverage directory. + It's now included, fixing `issue 255`_. + .. _issue 92: https://bitbucket.org/ned/coveragepy/issue/92/finally-clauses-arent-treated-properly-in .. _issue 164: https://bitbucket.org/ned/coveragepy/issue/164/trace-function-changed-warning-when-using .. _issue 175: https://bitbucket.org/ned/coveragepy/issue/175/branch-coverage-gets-confused-in-certain .. _issue 207: https://bitbucket.org/ned/coveragepy/issue/207/run-m-cannot-find-module-or-package-in .. _issue 242: https://bitbucket.org/ned/coveragepy/issue/242/running-a-two-level-package-doesnt-work .. _issue 218: https://bitbucket.org/ned/coveragepy/issue/218/run-command-does-not-respect-the-omit-flag +.. _issue 255: https://bitbucket.org/ned/coveragepy/issue/255/directory-level-__main__py-not-included-in Version 3.6 --- 5 January 2013 diff --git a/MANIFEST.in b/MANIFEST.in index d2712d9..7224674 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ recursive-include coverage/fullcoverage * include coverage.egg-info/*.* include setup.py +include __main__.py include README.txt include CHANGES.txt include AUTHORS.txt diff --git a/Makefile b/Makefile index 0588105..1ce7497 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,9 @@ metahtml: # Kitting -SDIST_CMD = python setup.py sdist --keep-temp --formats=gztar fixtar --owner=ned --group=coverage --clean +# For kitting on Windows: +# SDIST_CMD = python setup.py sdist --keep-temp --formats=gztar fixtar --owner=ned --group=coverage --clean +SDIST_CMD = python setup.py sdist --formats=gztar kit: $(SDIST_CMD) -- cgit v1.2.1 From 1ffbdc3328de90783d0ba962838b3f7924adf77c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 16 Aug 2013 21:59:20 -0400 Subject: Keep things working on PyPy 2.1 _structseq is in the lib_pypy directory on sys.path, but for some reason when imported, it claims to be from /opt/pypy//pypy-c-jit-linux-x86-64/build/lib_pypy/_structseq.py I don't know why that is, but importing _structseq and treating its directory as part of the stdlib makes the tests pass. --- coverage/control.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/coverage/control.py b/coverage/control.py index 8821bfb..98e4e78 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -17,6 +17,14 @@ from coverage.results import Analysis, Numbers from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter +# Pypy has some unusual stuff in the "stdlib". Consider those locations +# when deciding where the stdlib is. +try: + import _structseq # pylint: disable=F0401 +except ImportError: + _structseq = None + + class coverage(object): """Programmatic access to coverage.py. @@ -158,8 +166,8 @@ class coverage(object): # environments (virtualenv, for example), these modules may be # spread across a few locations. Look at all the candidate modules # we've imported, and take all the different ones. - for m in (atexit, os, random, socket): - if hasattr(m, "__file__"): + for m in (atexit, os, random, socket, _structseq): + if m is not None and hasattr(m, "__file__"): m_dir = self._canonical_dir(m) if m_dir not in self.pylib_dirs: self.pylib_dirs.append(m_dir) -- cgit v1.2.1 From f94f6a874e8466ecd825b796419606225a770668 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 4 Sep 2013 09:25:13 -0400 Subject: New flag for run: --debug, can trace why files aren't being traced. --- CHANGES.txt | 4 +++ coverage/cmdline.py | 8 ++++++ coverage/collector.py | 4 +-- coverage/config.py | 4 ++- coverage/control.py | 52 +++++++++++++++++++-------------- tests/test_cmdline.py | 80 +++++++++++++++++++++++++++++++++++---------------- 6 files changed, 103 insertions(+), 49 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 79794dc..f3b2e45 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,10 @@ Change history for Coverage.py 3.6.1 ----- +- Added the ``--debug`` switch to ``coverage run``. It accepts one option now, + ``notrace``, to log decisions not to trace files. I'm hoping this will help + people diagnose why their code isn't being traced. + - Improved the branch coverage facility, fixing `issue 90`_ and `issue 175`_. - Running code with ``coverage run -m`` now behaves more like Python does, diff --git a/coverage/cmdline.py b/coverage/cmdline.py index ac80310..51d8043 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -19,6 +19,10 @@ class Opts(object): '', '--branch', action='store_true', help="Measure branch coverage in addition to statement coverage." ) + debug = optparse.make_option( + '', '--debug', action='store', metavar="OPTS", + help="Debug options, separated by commas" + ) directory = optparse.make_option( '-d', '--directory', action='store', metavar="DIR", help="Write the output files to DIR." @@ -117,6 +121,7 @@ class CoverageOptionParser(optparse.OptionParser, object): self.set_defaults( actions=[], branch=None, + debug=None, directory=None, fail_under=None, help=None, @@ -310,6 +315,7 @@ CMDS = { [ Opts.append, Opts.branch, + Opts.debug, Opts.pylib, Opts.parallel_mode, Opts.module, @@ -404,6 +410,7 @@ class CoverageScript(object): source = unshell_list(options.source) omit = unshell_list(options.omit) include = unshell_list(options.include) + debug = unshell_list(options.debug) # Do something. self.coverage = self.covpkg.coverage( @@ -415,6 +422,7 @@ class CoverageScript(object): source = source, omit = omit, include = include, + debug = debug, ) if 'debug' in options.actions: diff --git a/coverage/collector.py b/coverage/collector.py index 781a0fa..2abec16 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -166,7 +166,7 @@ class Collector(object): """Create a collector. `should_trace` is a function, taking a filename, and returning a - canonicalized filename, or False depending on whether the file should + canonicalized filename, or None depending on whether the file should be traced or not. If `timid` is true, then a slower simpler trace function will be @@ -210,7 +210,7 @@ class Collector(object): # A cache of the results from should_trace, the decision about whether # to trace execution in a file. A dict of filename to (filename or - # False). + # None). self.should_trace_cache = {} # Our active Tracers. diff --git a/coverage/config.py b/coverage/config.py index c2ebecb..87318ff 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -111,6 +111,7 @@ class CoverageConfig(object): self.parallel = False self.timid = False self.source = None + self.debug = [] # Defaults for [report] self.exclude_list = DEFAULT_EXCLUDE[:] @@ -142,7 +143,7 @@ class CoverageConfig(object): if env: self.timid = ('--timid' in env) - MUST_BE_LIST = ["omit", "include"] + MUST_BE_LIST = ["omit", "include", "debug"] def from_args(self, **kwargs): """Read config values from `kwargs`.""" @@ -178,6 +179,7 @@ class CoverageConfig(object): ('branch', 'run:branch', 'boolean'), ('cover_pylib', 'run:cover_pylib', 'boolean'), ('data_file', 'run:data_file'), + ('debug', 'run:debug', 'list'), ('include', 'run:include', 'list'), ('omit', 'run:omit', 'list'), ('parallel', 'run:parallel', 'boolean'), diff --git a/coverage/control.py b/coverage/control.py index 98e4e78..fa85be6 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -41,7 +41,7 @@ class coverage(object): """ 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): + source=None, omit=None, include=None, debug=()): """ `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 @@ -76,6 +76,9 @@ class coverage(object): `include` will be measured, files that match `omit` will not. Each will also accept a single string argument. + `debug` is a sequence of strings indicating what debugging information + is desired. + """ from coverage import __version__ @@ -108,7 +111,7 @@ class coverage(object): self.config.from_args( data_file=data_file, cover_pylib=cover_pylib, timid=timid, branch=branch, parallel=bool_or_none(data_suffix), - source=source, omit=omit, include=include + source=source, omit=omit, include=include, debug=debug, ) self.auto_data = auto_data @@ -209,26 +212,28 @@ class coverage(object): filename = filename[:-9] + ".py" return filename - def _should_trace(self, filename, frame): - """Decide whether to trace execution in `filename` + def _should_trace_with_reason(self, filename, frame): + """Decide whether to trace execution in `filename`, with a reason. This function is called from the trace function. As each new file name is encountered, this function determines whether it is traced or not. - Returns a canonicalized filename if it should be traced, False if it - should not. + Returns a pair of values: the first indicates whether the file should + be traced: it's a canonicalized filename if it should be traced, None + if it should not. The second value is a string, the resason for the + decision. """ if not filename: # Empty string is pretty useless - return False + return None, "empty string isn't a filename" if filename.startswith('<'): # Lots of non-file execution is represented with artificial # filenames like "", "", or # "". Don't ever trace these executions, since we # can't do anything with the data later anyway. - return False + return None, "not a real filename" self._check_for_packages() @@ -254,35 +259,38 @@ class coverage(object): # stdlib and coverage.py directories. if self.source_match: if not self.source_match.match(canonical): - return False + return None, "falls outside the --source trees" elif self.include_match: if not self.include_match.match(canonical): - return False + return None, "falls outside the --include trees" else: # If we aren't supposed to trace installed code, then check if this # is near the Python standard library and skip it if so. if self.pylib_match and self.pylib_match.match(canonical): - return False + return None, "is in the stdlib" # We exclude the coverage code itself, since a little of it will be # measured otherwise. if self.cover_match and self.cover_match.match(canonical): - return False + return None, "is part of coverage.py" # Check the file against the omit pattern. if self.omit_match and self.omit_match.match(canonical): - return False + return None, "is inside an --omit pattern" - return canonical + return canonical, "because we love you" + + def _should_trace(self, filename, frame): + """Decide whether to trace execution in `filename`. + + Calls `_should_trace_with_reason`, and returns just the decision. - # To log what should_trace returns, change this to "if 1:" - if 0: - _real_should_trace = _should_trace - def _should_trace(self, filename, frame): # pylint: disable=E0102 - """A logging decorator around the real _should_trace function.""" - ret = self._real_should_trace(filename, frame) - print("should_trace: %r -> %r" % (filename, ret)) - return ret + """ + canonical, reason = self._should_trace_with_reason(filename, frame) + if not canonical: + if 'notrace' in self.config.debug: + sys.stderr.write("Not tracing %r: %s\n" % (filename, reason)) + return canonical def _warn(self, msg): """Use `msg` as a warning.""" diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 6b11b3e..493ce18 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -15,7 +15,7 @@ class CmdLineTest(CoverageTest): run_in_temp_dir = False INIT_LOAD = """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load()\n""" def model_object(self): @@ -104,7 +104,7 @@ class ClassicCmdLineTest(CmdLineTest): def test_erase(self): # coverage -e self.cmd_executes("-e", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .erase() """) self.cmd_executes_same("-e", "--erase") @@ -114,7 +114,7 @@ class ClassicCmdLineTest(CmdLineTest): # -x calls coverage.load first. self.cmd_executes("-x foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load() .start() .run_python_file('foo.py', ['foo.py']) @@ -123,7 +123,7 @@ class ClassicCmdLineTest(CmdLineTest): """) # -e -x calls coverage.erase first. self.cmd_executes("-e -x foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -132,7 +132,7 @@ class ClassicCmdLineTest(CmdLineTest): """) # --timid sets a flag, and program arguments get passed through. self.cmd_executes("-x --timid foo.py abc 123", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=True, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=True, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load() .start() .run_python_file('foo.py', ['foo.py', 'abc', '123']) @@ -141,7 +141,7 @@ class ClassicCmdLineTest(CmdLineTest): """) # -L sets a flag, and flags for the program don't confuse us. self.cmd_executes("-x -p -L foo.py -a -b", """\ - .coverage(cover_pylib=True, data_suffix=True, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=True, data_suffix=True, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load() .start() .run_python_file('foo.py', ['foo.py', '-a', '-b']) @@ -158,7 +158,7 @@ class ClassicCmdLineTest(CmdLineTest): def test_combine(self): # coverage -c self.cmd_executes("-c", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load() .combine() .save() @@ -180,13 +180,13 @@ class ClassicCmdLineTest(CmdLineTest): show_missing=True) """) self.cmd_executes("-r -o fooey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None) .load() .report(ignore_errors=None, omit=["fooey"], include=None, morfs=[], show_missing=None) """) self.cmd_executes("-r -o fooey,booey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None) .load() .report(ignore_errors=None, omit=["fooey", "booey"], include=None, morfs=[], show_missing=None) @@ -225,13 +225,13 @@ class ClassicCmdLineTest(CmdLineTest): omit=None, include=None, morfs=[]) """) self.cmd_executes("-a -o fooey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None) .load() .annotate(directory=None, ignore_errors=None, omit=["fooey"], include=None, morfs=[]) """) self.cmd_executes("-a -o fooey,booey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None) .load() .annotate(directory=None, ignore_errors=None, omit=["fooey", "booey"], include=None, morfs=[]) @@ -270,13 +270,13 @@ class ClassicCmdLineTest(CmdLineTest): omit=None, include=None, morfs=[]) """) self.cmd_executes("-b -o fooey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None) .load() .html_report(directory=None, ignore_errors=None, title=None, omit=["fooey"], include=None, morfs=[]) """) self.cmd_executes("-b -o fooey,booey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None) .load() .html_report(directory=None, ignore_errors=None, title=None, omit=["fooey", "booey"], include=None, morfs=[]) @@ -481,7 +481,7 @@ class NewCmdLineTest(CmdLineTest): self.cmd_executes_same("run --timid f.py", "-e -x --timid f.py") self.cmd_executes_same("run", "-x") self.cmd_executes("run --branch foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -489,7 +489,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run --rcfile=myrc.rc foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file="myrc.rc", source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file="myrc.rc", source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -497,7 +497,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run --include=pre1,pre2 foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=["pre1", "pre2"], omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=["pre1", "pre2"], omit=None, debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -505,7 +505,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run --omit=opre1,opre2 foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["opre1", "opre2"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["opre1", "opre2"], debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -517,7 +517,9 @@ class NewCmdLineTest(CmdLineTest): .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=["pre1", "pre2"], - omit=["opre1", "opre2"]) + omit=["opre1", "opre2"], + debug=None, + ) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -529,7 +531,37 @@ class NewCmdLineTest(CmdLineTest): .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=["quux", "hi.there", "/home/bar"], include=None, - omit=None) + omit=None, + debug=None, + ) + .erase() + .start() + .run_python_file('foo.py', ['foo.py']) + .stop() + .save() + """) + + def test_run_debug(self): + self.cmd_executes("run --debug=opt1 foo.py", """\ + .coverage(cover_pylib=None, data_suffix=None, timid=None, + branch=None, config_file=True, + source=None, include=None, + omit=None, + debug=["opt1"], + ) + .erase() + .start() + .run_python_file('foo.py', ['foo.py']) + .stop() + .save() + """) + self.cmd_executes("run --debug=opt1,opt2 foo.py", """\ + .coverage(cover_pylib=None, data_suffix=None, timid=None, + branch=None, config_file=True, + source=None, include=None, + omit=None, + debug=["opt1","opt2"], + ) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -539,7 +571,7 @@ class NewCmdLineTest(CmdLineTest): def test_run_module(self): self.cmd_executes("run -m mymodule", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_module('mymodule', ['mymodule']) @@ -547,7 +579,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run -m mymodule -qq arg1 arg2", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_module('mymodule', ['mymodule', '-qq', 'arg1', 'arg2']) @@ -555,7 +587,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run --branch -m mymodule", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_module('mymodule', ['mymodule']) @@ -583,13 +615,13 @@ class NewCmdLineTest(CmdLineTest): outfile="-") """) self.cmd_executes("xml --omit fooey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None) .load() .xml_report(ignore_errors=None, omit=["fooey"], include=None, morfs=[], outfile=None) """) self.cmd_executes("xml --omit fooey,booey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None) .load() .xml_report(ignore_errors=None, omit=["fooey", "booey"], include=None, morfs=[], outfile=None) -- cgit v1.2.1 From 122bcadca51bc81fd24e8e6b8e8b98ecb7464b80 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 4 Sep 2013 10:11:25 -0400 Subject: Call this 3.6.1a1 --- coverage/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage/version.py b/coverage/version.py index 422fbad..db4bca5 100644 --- a/coverage/version.py +++ b/coverage/version.py @@ -1,7 +1,7 @@ """The version and URL for coverage.py""" # This file is exec'ed in setup.py, don't import anything! -__version__ = "3.6.1a0" # see detailed history in CHANGES.txt +__version__ = "3.6.1a1" # see detailed history in CHANGES.txt __url__ = "http://nedbatchelder.com/code/coverage" if max(__version__).isalpha(): -- cgit v1.2.1 From b30ba4ca6b176f1642dec718ed8c26c108dc78ff Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 4 Sep 2013 12:38:17 -0400 Subject: PyTracer's cache broke when I changed False to None from should_trace. Fixed. --- coverage/collector.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/coverage/collector.py b/coverage/collector.py index 2abec16..9a74700 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -75,10 +75,11 @@ class PyTracer(object): # in this file. self.data_stack.append((self.cur_file_data, self.last_line)) filename = frame.f_code.co_filename - tracename = self.should_trace_cache.get(filename) - if tracename is None: + if filename not in self.should_trace_cache: tracename = self.should_trace(filename, frame) self.should_trace_cache[filename] = tracename + else: + tracename = self.should_trace_cache[filename] #print("called, stack is %d deep, tracename is %r" % ( # len(self.data_stack), tracename)) if tracename: -- cgit v1.2.1 From c45f4f848fac4634cad94f9cf5b35f0409e8c011 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 6 Sep 2013 20:50:01 -0400 Subject: Make debug=trace trace all the yes/no tracing decisions. --- CHANGES.txt | 4 ++-- coverage/control.py | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f3b2e45..b88b2eb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,8 +6,8 @@ Change history for Coverage.py ----- - Added the ``--debug`` switch to ``coverage run``. It accepts one option now, - ``notrace``, to log decisions not to trace files. I'm hoping this will help - people diagnose why their code isn't being traced. + ``trace``, to log decisions whether to trace files. I'm hoping this will + help people diagnose why their code isn't being traced. - Improved the branch coverage facility, fixing `issue 90`_ and `issue 175`_. diff --git a/coverage/control.py b/coverage/control.py index fa85be6..a767a26 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -41,7 +41,7 @@ class coverage(object): """ 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=()): + source=None, omit=None, include=None, debug=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 @@ -287,9 +287,12 @@ class coverage(object): """ canonical, reason = self._should_trace_with_reason(filename, frame) - if not canonical: - if 'notrace' in self.config.debug: - sys.stderr.write("Not tracing %r: %s\n" % (filename, reason)) + if 'trace' in self.config.debug: + if not canonical: + msg = "Not tracing %r: %s\n" % (filename, reason) + else: + msg = "Tracing %r\n" % (filename,) + sys.stderr.write(msg) return canonical def _warn(self, msg): -- cgit v1.2.1 From 7cf52d30fc8238ab183790ec52b0be86c9df1818 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 6 Sep 2013 22:08:38 -0400 Subject: Move the info formatting into its own function. --- coverage/cmdline.py | 13 +++---------- coverage/misc.py | 20 ++++++++++++++++++++ tests/test_misc.py | 23 ++++++++++++++++++++++- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 51d8043..9306d9b 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -5,6 +5,7 @@ import optparse, os, sys, traceback from coverage.backward import sorted # pylint: disable=W0622 from coverage.execfile import run_python_file, run_python_module from coverage.misc import CoverageException, ExceptionDuringRun, NoSource +from coverage.misc import info_formatter class Opts(object): @@ -592,16 +593,8 @@ class CoverageScript(object): for info in args: if info == 'sys': print("-- sys ----------------------------------------") - for label, info in self.coverage.sysinfo(): - if info == []: - info = "-none-" - if isinstance(info, list): - prefix = "%15s:" % label - for e in info: - print("%16s %s" % (prefix, e)) - prefix = "" - else: - print("%15s: %s" % (label, info)) + for line in info_formatter(self.coverage.sysinfo()): + print(line) elif info == 'data': print("-- data ---------------------------------------") self.coverage.load() diff --git a/coverage/misc.py b/coverage/misc.py index 473d7d4..a8773eb 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -138,6 +138,26 @@ class Hasher(object): return self.md5.digest() +def info_formatter(info): + """Produce a sequence of formatted lines from info. + + `info` is a sequence of pairs (label, data). The produced lines are + nicely formatted, ready to print. + + """ + label_len = max([len(l) for l, d in info]) + 1 + for label, data in info: + if data == []: + data = "-none-" + if isinstance(data, (list, tuple)): + prefix = "%*s:" % (label_len, label) + for e in data: + yield "%*s %s" % (label_len+1, prefix, e) + prefix = "" + else: + yield "%*s: %s" % (label_len, label, data) + + class CoverageException(Exception): """An exception specific to Coverage.""" pass diff --git a/tests/test_misc.py b/tests/test_misc.py index e7fa436..58c7164 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,7 +1,7 @@ """Tests of miscellaneous stuff.""" import sys -from coverage.misc import Hasher, file_be_gone +from coverage.misc import Hasher, file_be_gone, info_formatter from coverage import __version__, __url__ from tests.coveragetest import CoverageTest @@ -45,6 +45,27 @@ class RemoveFileTest(CoverageTest): self.assertRaises(OSError, file_be_gone, ".") +class InfoFormatterTest(CoverageTest): + """Tests of misc.info_formatter.""" + + def test_info_formatter(self): + lines = list(info_formatter([ + ('x', 'hello there'), + ('very long label', ['one element']), + ('regular', ['abc', 'def', 'ghi', 'jkl']), + ('nothing', []), + ])) + self.assertEqual(lines, [ + ' x: hello there', + ' very long label: one element', + ' regular: abc', + ' def', + ' ghi', + ' jkl', + ' nothing: -none-', + ]) + + class SetupPyTest(CoverageTest): """Tests of setup.py""" -- cgit v1.2.1 From d918e93a2a87aa730932d901b3bc19291b1a2dc9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 6 Sep 2013 22:09:26 -0400 Subject: Give the matchers a way to get info out of them. --- coverage/files.py | 8 ++++++++ tests/test_files.py | 9 +++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/coverage/files.py b/coverage/files.py index 4c55151..8d154c6 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -144,6 +144,10 @@ class TreeMatcher(object): def __repr__(self): return "" % self.dirs + def info(self): + """A list of strings for displaying when dumping state.""" + return self.dirs + def add(self, directory): """Add another directory to the list we match for.""" self.dirs.append(directory) @@ -169,6 +173,10 @@ class FnmatchMatcher(object): def __repr__(self): return "" % self.pats + def info(self): + """A list of strings for displaying when dumping state.""" + return self.pats + def match(self, fpath): """Does `fpath` match one of our filename patterns?""" for pat in self.pats: diff --git a/tests/test_files.py b/tests/test_files.py index 509c23b..eeb2264 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -58,16 +58,19 @@ class MatcherTest(CoverageTest): file4 = self.make_file("sub3/file4.py") file5 = self.make_file("sub3/file5.c") fl = FileLocator() - tm = TreeMatcher([ + trees = [ fl.canonical_filename("sub"), fl.canonical_filename(file4), - ]) + ] + tm = TreeMatcher(trees) self.assertTrue(tm.match(fl.canonical_filename(file1))) self.assertTrue(tm.match(fl.canonical_filename(file2))) self.assertFalse(tm.match(fl.canonical_filename(file3))) self.assertTrue(tm.match(fl.canonical_filename(file4))) self.assertFalse(tm.match(fl.canonical_filename(file5))) + self.assertEqual(tm.info(), trees) + def test_fnmatch_matcher(self): file1 = self.make_file("sub/file1.py") file2 = self.make_file("sub/file2.c") @@ -82,6 +85,8 @@ class MatcherTest(CoverageTest): self.assertTrue(fnm.match(fl.canonical_filename(file4))) self.assertFalse(fnm.match(fl.canonical_filename(file5))) + self.assertEqual(fnm.info(), ["*.py", "*/sub2/*"]) + class PathAliasesTest(CoverageTest): """Tests for coverage/files.py:PathAliases""" -- cgit v1.2.1 From 258e78e165b4884276392c6ed85fafe84c5e6e2f Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 6 Sep 2013 22:36:19 -0400 Subject: Better to leave the space out. --- coverage/cmdline.py | 2 +- coverage/misc.py | 2 +- tests/test_misc.py | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 9306d9b..2d820bb 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -594,7 +594,7 @@ class CoverageScript(object): if info == 'sys': print("-- sys ----------------------------------------") for line in info_formatter(self.coverage.sysinfo()): - print(line) + print(" %s" % line) elif info == 'data': print("-- data ---------------------------------------") self.coverage.load() diff --git a/coverage/misc.py b/coverage/misc.py index a8773eb..295bd2c 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -145,7 +145,7 @@ def info_formatter(info): nicely formatted, ready to print. """ - label_len = max([len(l) for l, d in info]) + 1 + label_len = max([len(l) for l, d in info]) for label, data in info: if data == []: data = "-none-" diff --git a/tests/test_misc.py b/tests/test_misc.py index 58c7164..32243ed 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -56,13 +56,13 @@ class InfoFormatterTest(CoverageTest): ('nothing', []), ])) self.assertEqual(lines, [ - ' x: hello there', - ' very long label: one element', - ' regular: abc', - ' def', - ' ghi', - ' jkl', - ' nothing: -none-', + ' x: hello there', + 'very long label: one element', + ' regular: abc', + ' def', + ' ghi', + ' jkl', + ' nothing: -none-', ]) -- cgit v1.2.1 From 7540342d61b42d689cf824f8f4dfd674c474f909 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 7 Sep 2013 10:27:42 -0400 Subject: Oops, it's 2013 already. --- coverage/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage/__init__.py b/coverage/__init__.py index 0ccc699..193b7a1 100644 --- a/coverage/__init__.py +++ b/coverage/__init__.py @@ -92,7 +92,7 @@ except KeyError: # COPYRIGHT AND LICENSE # # Copyright 2001 Gareth Rees. All rights reserved. -# Copyright 2004-2012 Ned Batchelder. All rights reserved. +# Copyright 2004-2013 Ned Batchelder. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are -- cgit v1.2.1 From 83411f84debeefa313a1b61121ef4fc9c9607fd6 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 8 Sep 2013 14:49:35 -0400 Subject: More --debug options, split code into separate objects. --- CHANGES.txt | 17 ++++++++--------- coverage/cmdline.py | 2 +- coverage/control.py | 53 ++++++++++++++++++++++++++++++++++++++++------------ coverage/data.py | 10 +++++++++- coverage/debug.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ coverage/misc.py | 20 -------------------- tests/test_debug.py | 25 +++++++++++++++++++++++++ tests/test_misc.py | 24 ++---------------------- 8 files changed, 140 insertions(+), 65 deletions(-) create mode 100644 coverage/debug.py create mode 100644 tests/test_debug.py diff --git a/CHANGES.txt b/CHANGES.txt index b88b2eb..47976b3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,11 +5,10 @@ Change history for Coverage.py 3.6.1 ----- -- Added the ``--debug`` switch to ``coverage run``. It accepts one option now, - ``trace``, to log decisions whether to trace files. I'm hoping this will - help people diagnose why their code isn't being traced. +- Added the ``--debug`` switch to ``coverage run``. It accepts a list of + options indicating the type of internal activity to log to stderr. -- Improved the branch coverage facility, fixing `issue 90`_ and `issue 175`_. +- Improved the branch coverage facility, fixing `issue 92`_ and `issue 175`_. - Running code with ``coverage run -m`` now behaves more like Python does, setting sys.path properly, which fixes `issue 207`_ and `issue 242`_. @@ -18,12 +17,12 @@ Change history for Coverage.py cause them to be incorrectly marked as unexecuted, as described in `issue 218`_. This is now fixed. -- When running a threaded program under the Python tracer, coverage would issue - a spurious warning about the trace function changing: "Trace function - changed, measurement is likely wrong: None." This fixes `issue 164`_. +- When running a threaded program under the Python tracer, coverage no longer + issues a spurious warning about the trace function changing: "Trace function + changed, measurement is likely wrong: None." This fixes `issue 164`_. -- The source kit omitted the __main__.py file in the root coverage directory. - It's now included, fixing `issue 255`_. +- The source kit now includes the `__main__.py` file in the root coverage + directory, fixing `issue 255`_. .. _issue 92: https://bitbucket.org/ned/coveragepy/issue/92/finally-clauses-arent-treated-properly-in .. _issue 164: https://bitbucket.org/ned/coveragepy/issue/164/trace-function-changed-warning-when-using diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 2d820bb..0881313 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -5,7 +5,7 @@ import optparse, os, sys, traceback from coverage.backward import sorted # pylint: disable=W0622 from coverage.execfile import run_python_file, run_python_module from coverage.misc import CoverageException, ExceptionDuringRun, NoSource -from coverage.misc import info_formatter +from coverage.debug import info_formatter class Opts(object): diff --git a/coverage/control.py b/coverage/control.py index a767a26..4b76121 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -3,11 +3,12 @@ import atexit, os, random, socket, sys from coverage.annotate import AnnotateReporter -from coverage.backward import string_class, iitems +from coverage.backward import string_class, iitems, sorted # pylint: disable=W0622 from coverage.codeunit import code_unit_factory, CodeUnit from coverage.collector import Collector from coverage.config import CoverageConfig from coverage.data import CoverageData +from coverage.debug import DebugControl from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher from coverage.files import PathAliases, find_python_files, prep_patterns from coverage.html import HtmlReporter @@ -41,7 +42,8 @@ class coverage(object): """ 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): + source=None, omit=None, include=None, debug=None, + debug_file=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 @@ -76,8 +78,9 @@ class coverage(object): `include` will be measured, files that match `omit` will not. Each will also accept a single string argument. - `debug` is a sequence of strings indicating what debugging information - is desired. + `debug` is a list of strings indicating what debugging information is + desired. `debug_file` is the file to write debug messages to, + defaulting to stderr. """ from coverage import __version__ @@ -114,6 +117,9 @@ class coverage(object): source=source, omit=omit, include=include, debug=debug, ) + # Create and configure the debugging controller. + self.debug = DebugControl(self.config.debug, debug_file or sys.stderr) + self.auto_data = auto_data # _exclude_re is a dict mapping exclusion list names to compiled @@ -158,7 +164,8 @@ class coverage(object): # started rather than wherever the process eventually chdir'd to. self.data = CoverageData( basename=self.config.data_file, - collector="coverage v%s" % __version__ + collector="coverage v%s" % __version__, + debug=self.debug, ) # The dirs for files considered "installed with the interpreter". @@ -179,7 +186,7 @@ class coverage(object): # where we are. self.cover_dir = self._canonical_dir(__file__) - # The matchers for _should_trace, created when tracing starts. + # The matchers for _should_trace. self.source_match = None self.pylib_match = self.cover_match = None self.include_match = self.omit_match = None @@ -287,12 +294,12 @@ class coverage(object): """ canonical, reason = self._should_trace_with_reason(filename, frame) - if 'trace' in self.config.debug: + if self.debug.should('trace'): if not canonical: - msg = "Not tracing %r: %s\n" % (filename, reason) + msg = "Not tracing %r: %s" % (filename, reason) else: - msg = "Tracing %r\n" % (filename,) - sys.stderr.write(msg) + msg = "Tracing %r" % (filename,) + self.debug.write(msg) return canonical def _warn(self, msg): @@ -383,6 +390,16 @@ class coverage(object): if self.omit: self.omit_match = FnmatchMatcher(self.omit) + # The user may want to debug things, show info if desired. + if self.debug.should('config'): + self.debug.write("Configuration values:") + config_info = sorted(self.config.__dict__.items()) + self.debug.write_formatted_info(config_info) + + if self.debug.should('sys'): + self.debug.write("Debugging info:") + self.debug.write_formatted_info(self.sysinfo()) + self.collector.start() self._started = True self._measured = True @@ -707,11 +724,23 @@ class coverage(object): ('executable', sys.executable), ('cwd', os.getcwd()), ('path', sys.path), - ('environment', [ + ('environment', sorted([ ("%s = %s" % (k, v)) for k, v in iitems(os.environ) if re.search(r"^COV|^PY", k) - ]), + ])), + ('command_line', " ".join(getattr(sys, 'argv', ['???']))), ] + if self.source_match: + info.append(('source_match', self.source_match.info())) + if self.include_match: + info.append(('include_match', self.include_match.info())) + if self.omit_match: + info.append(('omit_match', self.omit_match.info())) + if self.cover_match: + info.append(('cover_match', self.cover_match.info())) + if self.pylib_match: + info.append(('pylib_match', self.pylib_match.info())) + return info diff --git a/coverage/data.py b/coverage/data.py index c86a77f..fb88c5b 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -23,15 +23,18 @@ class CoverageData(object): """ - def __init__(self, basename=None, collector=None): + def __init__(self, basename=None, collector=None, debug=None): """Create a CoverageData. `basename` is the name of the file to use for storing data. `collector` is a string describing the coverage measurement software. + `debug` is a `DebugControl` object for writing debug messages. + """ self.collector = collector or 'unknown' + self.debug = debug self.use_file = True @@ -121,6 +124,9 @@ class CoverageData(object): if self.collector: data['collector'] = self.collector + if self.debug and self.debug.should('dataio'): + self.debug.write("Writing data to %r" % (filename,)) + # Write the pickle to the file. fdata = open(filename, 'wb') try: @@ -134,6 +140,8 @@ class CoverageData(object): def raw_data(self, filename): """Return the raw pickled data from `filename`.""" + if self.debug and self.debug.should('dataio'): + self.debug.write("Reading data from %r" % (filename,)) fdata = open(filename, 'rb') try: data = pickle.load(fdata) diff --git a/coverage/debug.py b/coverage/debug.py new file mode 100644 index 0000000..104f3b1 --- /dev/null +++ b/coverage/debug.py @@ -0,0 +1,54 @@ +"""Control of and utilities for debugging.""" + +import os + + +# When debugging, it can be helpful to force some options, especially when +# debugging the configuration mechanisms you usually use to control debugging! +# This is a list of forced debugging options. +FORCED_DEBUG = [] + + +class DebugControl(object): + """Control and output for debugging.""" + + def __init__(self, options, output): + """Configure the options and output file for debugging.""" + self.options = options + self.output = output + + def should(self, option): + """Decide whether to output debug information in category `option`.""" + return (option in self.options or option in FORCED_DEBUG) + + def write(self, msg): + """Write a line of debug output.""" + if self.should('pid'): + msg = "pid %5d: %s" % (os.getpid(), msg) + self.output.write(msg+"\n") + self.output.flush() + + def write_formatted_info(self, info): + """Write a sequence of (label,data) pairs nicely.""" + for line in info_formatter(info): + self.write(" %s" % line) + + +def info_formatter(info): + """Produce a sequence of formatted lines from info. + + `info` is a sequence of pairs (label, data). The produced lines are + nicely formatted, ready to print. + + """ + label_len = max([len(l) for l, _d in info]) + for label, data in info: + if data == []: + data = "-none-" + if isinstance(data, (list, tuple)): + prefix = "%*s:" % (label_len, label) + for e in data: + yield "%*s %s" % (label_len+1, prefix, e) + prefix = "" + else: + yield "%*s: %s" % (label_len, label, data) diff --git a/coverage/misc.py b/coverage/misc.py index 295bd2c..473d7d4 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -138,26 +138,6 @@ class Hasher(object): return self.md5.digest() -def info_formatter(info): - """Produce a sequence of formatted lines from info. - - `info` is a sequence of pairs (label, data). The produced lines are - nicely formatted, ready to print. - - """ - label_len = max([len(l) for l, d in info]) - for label, data in info: - if data == []: - data = "-none-" - if isinstance(data, (list, tuple)): - prefix = "%*s:" % (label_len, label) - for e in data: - yield "%*s %s" % (label_len+1, prefix, e) - prefix = "" - else: - yield "%*s: %s" % (label_len, label, data) - - class CoverageException(Exception): """An exception specific to Coverage.""" pass diff --git a/tests/test_debug.py b/tests/test_debug.py new file mode 100644 index 0000000..f3ae31f --- /dev/null +++ b/tests/test_debug.py @@ -0,0 +1,25 @@ +"""Tests of coverage/debug.py""" + +from coverage.debug import info_formatter +from tests.coveragetest import CoverageTest + + +class InfoFormatterTest(CoverageTest): + """Tests of misc.info_formatter.""" + + def test_info_formatter(self): + lines = list(info_formatter([ + ('x', 'hello there'), + ('very long label', ['one element']), + ('regular', ['abc', 'def', 'ghi', 'jkl']), + ('nothing', []), + ])) + self.assertEqual(lines, [ + ' x: hello there', + 'very long label: one element', + ' regular: abc', + ' def', + ' ghi', + ' jkl', + ' nothing: -none-', + ]) diff --git a/tests/test_misc.py b/tests/test_misc.py index 32243ed..50ddb17 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,7 +1,8 @@ """Tests of miscellaneous stuff.""" + import sys -from coverage.misc import Hasher, file_be_gone, info_formatter +from coverage.misc import Hasher, file_be_gone from coverage import __version__, __url__ from tests.coveragetest import CoverageTest @@ -45,27 +46,6 @@ class RemoveFileTest(CoverageTest): self.assertRaises(OSError, file_be_gone, ".") -class InfoFormatterTest(CoverageTest): - """Tests of misc.info_formatter.""" - - def test_info_formatter(self): - lines = list(info_formatter([ - ('x', 'hello there'), - ('very long label', ['one element']), - ('regular', ['abc', 'def', 'ghi', 'jkl']), - ('nothing', []), - ])) - self.assertEqual(lines, [ - ' x: hello there', - 'very long label: one element', - ' regular: abc', - ' def', - ' ghi', - ' jkl', - ' nothing: -none-', - ]) - - class SetupPyTest(CoverageTest): """Tests of setup.py""" -- cgit v1.2.1 From fda9a46afb56ce3d9d4f403a3f0d85b2899ebace Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 8 Sep 2013 15:31:10 -0400 Subject: A test that the collector is using the should_trace_cache properly. --- tests/test_collector.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/test_collector.py diff --git a/tests/test_collector.py b/tests/test_collector.py new file mode 100644 index 0000000..d0aa997 --- /dev/null +++ b/tests/test_collector.py @@ -0,0 +1,47 @@ +"""Tests of coverage/collector.py and other collectors.""" + +import re + +import coverage +from coverage.backward import StringIO + +from tests.coveragetest import CoverageTest + + +class CollectorTest(CoverageTest): + """Test specific aspects of the collection process.""" + + def test_should_trace_cache(self): + # The tracers should only invoke should_trace once for each file name. + + # Make some files that invoke each other. + self.make_file("f1.py", """\ + def f1(x, f): + return f(x) + """) + + self.make_file("f2.py", """\ + import f1 + + def func(x): + return f1.f1(x, otherfunc) + + def otherfunc(x): + return x*x + + for i in range(10): + func(i) + """) + + debug_out = StringIO() + cov = coverage.coverage( + include=["f1.py"], debug=['trace'], debug_file=debug_out + ) + + # Import the python file, executing it. + self.start_import_stop(cov, "f2") + + # Grab all the filenames mentioned in debug output, there should be no + # duplicates. + filenames = re.findall(r"'[^']+'", debug_out.getvalue()) + self.assertEqual(len(filenames), len(set(filenames))) -- cgit v1.2.1 From bd632f8f58a25be4d1500ddd882c9cd5645748a9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 8 Sep 2013 15:47:45 -0400 Subject: Make the should_trace_cache test a little more bullet-proof. --- tests/test_collector.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_collector.py b/tests/test_collector.py index d0aa997..29c3999 100644 --- a/tests/test_collector.py +++ b/tests/test_collector.py @@ -43,5 +43,12 @@ class CollectorTest(CoverageTest): # Grab all the filenames mentioned in debug output, there should be no # duplicates. - filenames = re.findall(r"'[^']+'", debug_out.getvalue()) + trace_lines = [ + l for l in debug_out.getvalue().splitlines() + if l.startswith("Tracing ") or l.startswith("Not tracing ") + ] + filenames = [re.search(r"'[^']+'", l).group() for l in trace_lines] self.assertEqual(len(filenames), len(set(filenames))) + + # Double-check that the tracing messages are in there somewhere. + self.assertTrue(len(filenames) > 5) -- cgit v1.2.1 From cdbdcb48ee378234cf269ea87dbd314db4bea66e Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 11 Sep 2013 20:56:38 -0400 Subject: Fix some line endings and whitespace. --- .hgignore | 64 ++++++++++---------- igor.py | 1 + lab/hack_pyc.py | 164 ++++++++++++++++++++++++++-------------------------- lab/sample.py | 10 ++-- lab/show_pyc.py | 140 ++++++++++++++++++++++---------------------- lab/trace_sample.py | 114 ++++++++++++++++++------------------ 6 files changed, 247 insertions(+), 246 deletions(-) diff --git a/.hgignore b/.hgignore index d8b5935..34fa8a7 100644 --- a/.hgignore +++ b/.hgignore @@ -1,32 +1,32 @@ -syntax: glob - -# Files that can appear anywhere in the tree. -*.pyc -*.pyo -*$py.class -*.pyd -*.so -*.bak -.coverage -.coverage.* -*.swp - -# Stuff generated by editors. -.idea/ - -# Stuff in the root. -build -*.egg-info -dist -htmlcov -MANIFEST -setuptools-*.egg -.tox -.tox_kits - -# Stuff in the test directory. -zipmods.zip - -# Stuff in the doc directory. -_build -sample_html_beta +syntax: glob + +# Files that can appear anywhere in the tree. +*.pyc +*.pyo +*$py.class +*.pyd +*.so +*.bak +.coverage +.coverage.* +*.swp + +# Stuff generated by editors. +.idea/ + +# Stuff in the root. +build +*.egg-info +dist +htmlcov +MANIFEST +setuptools-*.egg +.tox +.tox_kits + +# Stuff in the test directory. +zipmods.zip + +# Stuff in the doc directory. +_build +sample_html_beta diff --git a/igor.py b/igor.py index 79a8b4b..0c37efc 100644 --- a/igor.py +++ b/igor.py @@ -195,6 +195,7 @@ def do_check_eol(): check_file("setup.py") check_file("igor.py") check_file("Makefile") + check_file(".hgignore") check_files("doc", ["*.rst"]) check_files(".", ["*.txt"]) diff --git a/lab/hack_pyc.py b/lab/hack_pyc.py index e8992b9..1cdc476 100644 --- a/lab/hack_pyc.py +++ b/lab/hack_pyc.py @@ -1,82 +1,82 @@ -""" Wicked hack to get .pyc files to do bytecode tracing instead of - line tracing. -""" - -import marshal, new, opcode, sys, types - -from lnotab import lnotab_numbers, lnotab_string - -class PycFile: - def read(self, f): - if isinstance(f, basestring): - f = open(f, "rb") - self.magic = f.read(4) - self.modtime = f.read(4) - self.code = marshal.load(f) - - def write(self, f): - if isinstance(f, basestring): - f = open(f, "wb") - f.write(self.magic) - f.write(self.modtime) - marshal.dump(self.code, f) - - def hack_line_numbers(self): - self.code = hack_line_numbers(self.code) - -def hack_line_numbers(code): - """ Replace a code object's line number information to claim that every - byte of the bytecode is a new source line. Returns a new code - object. Also recurses to hack the line numbers in nested code objects. - """ - - # Create a new lnotab table. Each opcode is claimed to be at - # 1000*lineno + (opcode number within line), so for example, the opcodes on - # source line 12 will be given new line numbers 12000, 12001, 12002, etc. - old_num = list(lnotab_numbers(code.co_lnotab, code.co_firstlineno)) - n_bytes = len(code.co_code) - new_num = [] - line = 0 - opnum_in_line = 0 - i_byte = 0 - while i_byte < n_bytes: - if old_num and i_byte == old_num[0][0]: - line = old_num.pop(0)[1] - opnum_in_line = 0 - new_num.append((i_byte, 100000000 + 1000*line + opnum_in_line)) - if ord(code.co_code[i_byte]) >= opcode.HAVE_ARGUMENT: - i_byte += 3 - else: - i_byte += 1 - opnum_in_line += 1 - - # new_num is a list of pairs, (byteoff, lineoff). Turn it into an lnotab. - new_firstlineno = new_num[0][1]-1 - new_lnotab = lnotab_string(new_num, new_firstlineno) - - # Recurse into code constants in this code object. - new_consts = [] - for const in code.co_consts: - if type(const) == types.CodeType: - new_consts.append(hack_line_numbers(const)) - else: - new_consts.append(const) - - # Create a new code object, just like the old one, except with new - # line numbers. - new_code = new.code( - code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags, - code.co_code, tuple(new_consts), code.co_names, code.co_varnames, - code.co_filename, code.co_name, new_firstlineno, new_lnotab - ) - - return new_code - -def hack_file(f): - pyc = PycFile() - pyc.read(f) - pyc.hack_line_numbers() - pyc.write(f) - -if __name__ == '__main__': - hack_file(sys.argv[1]) +""" Wicked hack to get .pyc files to do bytecode tracing instead of + line tracing. +""" + +import marshal, new, opcode, sys, types + +from lnotab import lnotab_numbers, lnotab_string + +class PycFile: + def read(self, f): + if isinstance(f, basestring): + f = open(f, "rb") + self.magic = f.read(4) + self.modtime = f.read(4) + self.code = marshal.load(f) + + def write(self, f): + if isinstance(f, basestring): + f = open(f, "wb") + f.write(self.magic) + f.write(self.modtime) + marshal.dump(self.code, f) + + def hack_line_numbers(self): + self.code = hack_line_numbers(self.code) + +def hack_line_numbers(code): + """ Replace a code object's line number information to claim that every + byte of the bytecode is a new source line. Returns a new code + object. Also recurses to hack the line numbers in nested code objects. + """ + + # Create a new lnotab table. Each opcode is claimed to be at + # 1000*lineno + (opcode number within line), so for example, the opcodes on + # source line 12 will be given new line numbers 12000, 12001, 12002, etc. + old_num = list(lnotab_numbers(code.co_lnotab, code.co_firstlineno)) + n_bytes = len(code.co_code) + new_num = [] + line = 0 + opnum_in_line = 0 + i_byte = 0 + while i_byte < n_bytes: + if old_num and i_byte == old_num[0][0]: + line = old_num.pop(0)[1] + opnum_in_line = 0 + new_num.append((i_byte, 100000000 + 1000*line + opnum_in_line)) + if ord(code.co_code[i_byte]) >= opcode.HAVE_ARGUMENT: + i_byte += 3 + else: + i_byte += 1 + opnum_in_line += 1 + + # new_num is a list of pairs, (byteoff, lineoff). Turn it into an lnotab. + new_firstlineno = new_num[0][1]-1 + new_lnotab = lnotab_string(new_num, new_firstlineno) + + # Recurse into code constants in this code object. + new_consts = [] + for const in code.co_consts: + if type(const) == types.CodeType: + new_consts.append(hack_line_numbers(const)) + else: + new_consts.append(const) + + # Create a new code object, just like the old one, except with new + # line numbers. + new_code = new.code( + code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags, + code.co_code, tuple(new_consts), code.co_names, code.co_varnames, + code.co_filename, code.co_name, new_firstlineno, new_lnotab + ) + + return new_code + +def hack_file(f): + pyc = PycFile() + pyc.read(f) + pyc.hack_line_numbers() + pyc.write(f) + +if __name__ == '__main__': + hack_file(sys.argv[1]) diff --git a/lab/sample.py b/lab/sample.py index cf4f6dc..bb62848 100644 --- a/lab/sample.py +++ b/lab/sample.py @@ -1,5 +1,5 @@ -a, b = 1, 0 -if a or b or fn(): - # Hey - a = 3 -d = 4 \ No newline at end of file +a, b = 1, 0 +if a or b or fn(): + # Hey + a = 3 +d = 4 diff --git a/lab/show_pyc.py b/lab/show_pyc.py index 7dacc2b..b2cbb34 100644 --- a/lab/show_pyc.py +++ b/lab/show_pyc.py @@ -1,70 +1,70 @@ -import dis, marshal, struct, sys, time, types - -def show_pyc_file(fname): - f = open(fname, "rb") - magic = f.read(4) - moddate = f.read(4) - modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0])) - print "magic %s" % (magic.encode('hex')) - print "moddate %s (%s)" % (moddate.encode('hex'), modtime) - code = marshal.load(f) - show_code(code) - -def show_py_file(fname): - text = open(fname).read().replace('\r\n', '\n') - show_py_text(text, fname=fname) - -def show_py_text(text, fname=""): - code = compile(text, fname, "exec") - show_code(code) - -def show_code(code, indent=''): - print "%scode" % indent - indent += ' ' - print "%sargcount %d" % (indent, code.co_argcount) - print "%snlocals %d" % (indent, code.co_nlocals) - print "%sstacksize %d" % (indent, code.co_stacksize) - print "%sflags %04x" % (indent, code.co_flags) - show_hex("code", code.co_code, indent=indent) - dis.disassemble(code) - print "%sconsts" % indent - for const in code.co_consts: - if type(const) == types.CodeType: - show_code(const, indent+' ') - else: - print " %s%r" % (indent, const) - print "%snames %r" % (indent, code.co_names) - print "%svarnames %r" % (indent, code.co_varnames) - print "%sfreevars %r" % (indent, code.co_freevars) - print "%scellvars %r" % (indent, code.co_cellvars) - print "%sfilename %r" % (indent, code.co_filename) - print "%sname %r" % (indent, code.co_name) - print "%sfirstlineno %d" % (indent, code.co_firstlineno) - show_hex("lnotab", code.co_lnotab, indent=indent) - -def show_hex(label, h, indent): - h = h.encode('hex') - if len(h) < 60: - print "%s%s %s" % (indent, label, h) - else: - print "%s%s" % (indent, label) - for i in range(0, len(h), 60): - print "%s %s" % (indent, h[i:i+60]) - -def show_file(fname): - if fname.endswith('pyc'): - show_pyc_file(fname) - elif fname.endswith('py'): - show_py_file(fname) - else: - print "Odd file:", fname - -def main(args): - if args[0] == '-c': - show_py_text(" ".join(args[1:]).replace(";", "\n")) - else: - for a in args: - show_file(a) - -if __name__ == '__main__': - main(sys.argv[1:]) +import dis, marshal, struct, sys, time, types + +def show_pyc_file(fname): + f = open(fname, "rb") + magic = f.read(4) + moddate = f.read(4) + modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0])) + print "magic %s" % (magic.encode('hex')) + print "moddate %s (%s)" % (moddate.encode('hex'), modtime) + code = marshal.load(f) + show_code(code) + +def show_py_file(fname): + text = open(fname).read().replace('\r\n', '\n') + show_py_text(text, fname=fname) + +def show_py_text(text, fname=""): + code = compile(text, fname, "exec") + show_code(code) + +def show_code(code, indent=''): + print "%scode" % indent + indent += ' ' + print "%sargcount %d" % (indent, code.co_argcount) + print "%snlocals %d" % (indent, code.co_nlocals) + print "%sstacksize %d" % (indent, code.co_stacksize) + print "%sflags %04x" % (indent, code.co_flags) + show_hex("code", code.co_code, indent=indent) + dis.disassemble(code) + print "%sconsts" % indent + for const in code.co_consts: + if type(const) == types.CodeType: + show_code(const, indent+' ') + else: + print " %s%r" % (indent, const) + print "%snames %r" % (indent, code.co_names) + print "%svarnames %r" % (indent, code.co_varnames) + print "%sfreevars %r" % (indent, code.co_freevars) + print "%scellvars %r" % (indent, code.co_cellvars) + print "%sfilename %r" % (indent, code.co_filename) + print "%sname %r" % (indent, code.co_name) + print "%sfirstlineno %d" % (indent, code.co_firstlineno) + show_hex("lnotab", code.co_lnotab, indent=indent) + +def show_hex(label, h, indent): + h = h.encode('hex') + if len(h) < 60: + print "%s%s %s" % (indent, label, h) + else: + print "%s%s" % (indent, label) + for i in range(0, len(h), 60): + print "%s %s" % (indent, h[i:i+60]) + +def show_file(fname): + if fname.endswith('pyc'): + show_pyc_file(fname) + elif fname.endswith('py'): + show_py_file(fname) + else: + print "Odd file:", fname + +def main(args): + if args[0] == '-c': + show_py_text(" ".join(args[1:]).replace(";", "\n")) + else: + for a in args: + show_file(a) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/lab/trace_sample.py b/lab/trace_sample.py index 9fa3724..3f81919 100644 --- a/lab/trace_sample.py +++ b/lab/trace_sample.py @@ -1,57 +1,57 @@ -import os, sys - -global nest -nest = 0 - -def trace(frame, event, arg): - #if event == 'line': - global nest - - print "%s%s %s %d" % ( - " " * nest, - event, - os.path.basename(frame.f_code.co_filename), - frame.f_lineno, - ) - - if event == 'call': - nest += 1 - if event == 'return': - nest -= 1 - - return trace - -def trace2(frame, event, arg): - #if event == 'line': - global nest - - print "2: %s%s %s %d" % ( - " " * nest, - event, - os.path.basename(frame.f_code.co_filename), - frame.f_lineno, - ) - - if event == 'call': - nest += 1 - if event == 'return': - nest -= 1 - - return trace2 - -sys.settrace(trace) - -def bar(): - print "nar" - -a = 26 -def foo(n): - a = 28 - sys.settrace(sys.gettrace()) - bar() - a = 30 - return 2*n - -print foo(a) -#import sample -#import littleclass +import os, sys + +global nest +nest = 0 + +def trace(frame, event, arg): + #if event == 'line': + global nest + + print "%s%s %s %d" % ( + " " * nest, + event, + os.path.basename(frame.f_code.co_filename), + frame.f_lineno, + ) + + if event == 'call': + nest += 1 + if event == 'return': + nest -= 1 + + return trace + +def trace2(frame, event, arg): + #if event == 'line': + global nest + + print "2: %s%s %s %d" % ( + " " * nest, + event, + os.path.basename(frame.f_code.co_filename), + frame.f_lineno, + ) + + if event == 'call': + nest += 1 + if event == 'return': + nest -= 1 + + return trace2 + +sys.settrace(trace) + +def bar(): + print "nar" + +a = 26 +def foo(n): + a = 28 + sys.settrace(sys.gettrace()) + bar() + a = 30 + return 2*n + +print foo(a) +#import sample +#import littleclass -- cgit v1.2.1 From b638afe3a6bf28b5a0d9e3d52079cbb6d96ae69c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 12 Sep 2013 07:32:21 -0400 Subject: Tests for debug tracing features. --- tests/test_collector.py | 5 ++- tests/test_debug.py | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/tests/test_collector.py b/tests/test_collector.py index 29c3999..af3814f 100644 --- a/tests/test_collector.py +++ b/tests/test_collector.py @@ -13,6 +13,8 @@ class CollectorTest(CoverageTest): def test_should_trace_cache(self): # The tracers should only invoke should_trace once for each file name. + # TODO: Might be better to do this with a mocked _should_trace, + # rather than by examining debug output. # Make some files that invoke each other. self.make_file("f1.py", """\ @@ -33,6 +35,7 @@ class CollectorTest(CoverageTest): func(i) """) + # Trace one file, but not the other, and get the debug output. debug_out = StringIO() cov = coverage.coverage( include=["f1.py"], debug=['trace'], debug_file=debug_out @@ -51,4 +54,4 @@ class CollectorTest(CoverageTest): self.assertEqual(len(filenames), len(set(filenames))) # Double-check that the tracing messages are in there somewhere. - self.assertTrue(len(filenames) > 5) + self.assertGreater(len(filenames), 5) diff --git a/tests/test_debug.py b/tests/test_debug.py index f3ae31f..0132c1c 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -1,5 +1,10 @@ """Tests of coverage/debug.py""" +import os +import re + +import coverage +from coverage.backward import StringIO from coverage.debug import info_formatter from tests.coveragetest import CoverageTest @@ -23,3 +28,86 @@ class InfoFormatterTest(CoverageTest): ' jkl', ' nothing: -none-', ]) + + +class DebugTraceTest(CoverageTest): + """Tests of debug output.""" + + def f1_debug_output(self, debug): + """Runs some code with `debug` option, returns the debug output.""" + # Make code to run. + self.make_file("f1.py", """\ + def f1(x): + return x+1 + + for i in range(5): + f1(i) + """) + + debug_out = StringIO() + cov = coverage.coverage(debug=debug, debug_file=debug_out) + self.start_import_stop(cov, "f1") + + out_lines = debug_out.getvalue().splitlines() + return out_lines + + def test_debug_no_trace(self): + out_lines = self.f1_debug_output([]) + + # We should have no output at all. + self.assertFalse(out_lines) + + def test_debug_trace(self): + out_lines = self.f1_debug_output(["trace"]) + + # We should have a line like "Tracing 'f1.py'" + self.assertIn("Tracing 'f1.py'", out_lines) + + # We should lines like "Not tracing 'collector.py'..." + coverage_lines = lines_matching( + out_lines, + r"^Not tracing .*: is part of coverage.py$" + ) + self.assertTrue(coverage_lines) + + def test_debug_trace_pid(self): + out_lines = self.f1_debug_output(["trace", "pid"]) + + # Now our lines are always prefixed with the process id. + pid_prefix = "^pid %5d: " % os.getpid() + pid_lines = lines_matching(out_lines, pid_prefix) + self.assertEqual(pid_lines, out_lines) + + # We still have some tracing, and some not tracing. + self.assertTrue(lines_matching(out_lines, pid_prefix + "Tracing ")) + self.assertTrue(lines_matching(out_lines, pid_prefix + "Not tracing ")) + + def test_debug_config(self): + out_lines = self.f1_debug_output(["config"]) + + labels = """ + attempted_config_files branch config_files cover_pylib data_file + debug exclude_list extra_css html_dir html_title ignore_errors + include omit parallel partial_always_list partial_list paths + precision show_missing source timid xml_output + """.split() + for label in labels: + label_pat = r"^\s*%s: " % label + self.assertEqual(len(lines_matching(out_lines, label_pat)), 1) + + def test_debug_sys(self): + out_lines = self.f1_debug_output(["sys"]) + + labels = """ + version coverage cover_dir pylib_dirs tracer config_files + configs_read data_path python platform implementation executable + cwd path environment command_line cover_match pylib_match + """.split() + for label in labels: + label_pat = r"^\s*%s: " % label + self.assertEqual(len(lines_matching(out_lines, label_pat)), 1) + + +def lines_matching(lines, pat): + """Gives the list of lines from `lines` that match `pat`.""" + return [l for l in lines if re.search(pat, l)] -- cgit v1.2.1 From 2b409c0bdcead63936974aaf8e94f6b4efe1c2f2 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 12 Sep 2013 21:25:26 -0400 Subject: Clean up .travis.yml --- .travis.yml | 2 +- igor.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a6e7265..4238452 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,4 +16,4 @@ install: script: - make testdata - - PYTHONPATH=test/eggsrc nosetests -e test_farm -e is_like_python test + - PYTHONPATH=test/eggsrc nosetests -e test_farm -e is_like_python test diff --git a/igor.py b/igor.py index 0c37efc..17b434d 100644 --- a/igor.py +++ b/igor.py @@ -196,6 +196,7 @@ def do_check_eol(): check_file("igor.py") check_file("Makefile") check_file(".hgignore") + check_file(".travis.yml") check_files("doc", ["*.rst"]) check_files(".", ["*.txt"]) -- cgit v1.2.1 From 5828e052838fe5ee5de28f5cdb8c8d2a7b74006b Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 13 Sep 2013 21:36:15 -0400 Subject: Make a change to see a change. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4238452..e96d683 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +# Tell Travis (travisci.org) what to do + language: python python: -- cgit v1.2.1 From 1e6a8fe664aa1319dfb3f6f28643d5343d9c3950 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 13 Sep 2013 21:48:41 -0400 Subject: Update travis to the latest way of running tests. --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index e96d683..4ca91cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,15 +7,13 @@ python: - 2.6 - 2.7 - 3.2 + - 3.3 - pypy -env: - - COVERAGE_TEST_TRACER=py - - COVERAGE_TEST_TRACER=c - install: - python setup.py clean install + - python igor.py zip_mods install_egg remove_extension script: - - make testdata - - PYTHONPATH=test/eggsrc nosetests -e test_farm -e is_like_python test + - python igor.py test_with_tracer py + - python igor.py test_with_tracer c -- cgit v1.2.1 From 753e8375128eb4395da455a41d4f6b374b50a389 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 13 Sep 2013 21:59:33 -0400 Subject: Make travis.yml more like tox.ini --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4ca91cf..6d4e45b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,10 +10,13 @@ python: - 3.3 - pypy -install: - - python setup.py clean install - - python igor.py zip_mods install_egg remove_extension +#install: +# - python setup.py clean install +# - python igor.py zip_mods install_egg remove_extension script: + - python setup.py clean develop + - python igor.py zip_mods install_egg remove_extension - python igor.py test_with_tracer py + - python setup.py build_ext --inplace - python igor.py test_with_tracer c -- cgit v1.2.1 From d0c295d6cfc3ab081edf9c0cf04c942da184f80d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 14 Sep 2013 06:44:44 -0400 Subject: Maybe this will be right for travis --- .travis.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6d4e45b..57e9b1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ -# Tell Travis (travisci.org) what to do +# Tell Travis what to do +# https://travis-ci.org/nedbat/coveragepy language: python @@ -10,13 +11,13 @@ python: - 3.3 - pypy -#install: -# - python setup.py clean install -# - python igor.py zip_mods install_egg remove_extension +install: + - python setup.py clean install + #- python igor.py zip_mods install_egg remove_extension script: - - python setup.py clean develop - - python igor.py zip_mods install_egg remove_extension - - python igor.py test_with_tracer py - - python setup.py build_ext --inplace + #- python setup.py clean develop + - python igor.py zip_mods install_egg - python igor.py test_with_tracer c + - python igor.py remove_extension + - python igor.py test_with_tracer py -- cgit v1.2.1 From 04098b5f1bf86a807a8fd7775d8d08e0d23cbb7d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 14 Sep 2013 07:53:46 -0400 Subject: Maybe 'develop' is better than 'install'? --- .travis.yml | 2 +- igor.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 57e9b1d..9e915e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ python: - pypy install: - - python setup.py clean install + - python setup.py clean develop #- python igor.py zip_mods install_egg remove_extension script: diff --git a/igor.py b/igor.py index 17b434d..12ec6c8 100644 --- a/igor.py +++ b/igor.py @@ -15,6 +15,10 @@ import sys import zipfile +# Functions named do_* are executable from the command line: do_blah is run +# by "python igor.py blah". + + def do_remove_extension(): """Remove the compiled C extension, no matter what its name.""" -- cgit v1.2.1 From dac067e97e6466254fa1d31ac3adef9b746f6455 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 14 Sep 2013 11:49:06 -0400 Subject: This works on travisci.org --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9e915e4..2dfd698 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,10 +13,8 @@ python: install: - python setup.py clean develop - #- python igor.py zip_mods install_egg remove_extension script: - #- python setup.py clean develop - python igor.py zip_mods install_egg - python igor.py test_with_tracer c - python igor.py remove_extension -- cgit v1.2.1 From 59ed861fd72f1577e696616796155cbb665e5f75 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 15 Sep 2013 11:51:05 -0400 Subject: Make our sys.path adjustment more robust. Nose uses a custom importer that seems to cache. Using "" for the current directory gets it confused for some reason, but only on a Mac, using Py3.3, in a virtualenv! --- tests/coveragetest.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 5613fa6..4cc777d 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -61,8 +61,11 @@ class CoverageTest(TestCase): self.old_dir = os.getcwd() os.chdir(self.temp_dir) - # Modules should be importable from this temp directory. - sys.path.insert(0, '') + # Modules should be importable from this temp directory. We don't + # use '' because we make lots of different temp directories and + # nose's caching importer can get confused. The full path prevents + # problems. + sys.path.insert(0, os.getcwd()) # Keep a counter to make every call to check_coverage unique. self.n = 0 -- cgit v1.2.1 From d15d0f8e32f90511c2f956d5f383ca7b13fdedd4 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 15 Sep 2013 12:05:46 -0400 Subject: Skip bug212 on py3, it isn't fixed yet. --- tests/test_arcs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_arcs.py b/tests/test_arcs.py index d7e2e48..6268e28 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -509,7 +509,9 @@ class ExceptionArcTest(CoverageTest): arcz=".1 12 23 35 56 61 17 7.", arcz_missing="", arcz_unpredicted="") - if sys.version_info >= (2, 6): + # Run this test only on 2.6 and 2.7 for now. I hope to fix it on Py3 + # eventually... + if (2, 6) <= sys.version_info < (3,): # "except Exception as e" is crucial here. def test_bug_212(self): self.check_coverage("""\ -- cgit v1.2.1 From acd8b535203ee183f3477b727f75f9be6c3d8f76 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 15 Sep 2013 12:25:18 -0400 Subject: Get rid of locals(). Thanks for the prod, Brett Cannon. --- coverage/html.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/coverage/html.py b/coverage/html.py index ed8920f..aef43be 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -10,10 +10,6 @@ from coverage.report import Reporter from coverage.results import Numbers from coverage.templite import Templite -# Disable pylint msg W0612, because a bunch of variables look unused, but -# they're accessed in a Templite context via locals(). -# pylint: disable=W0612 - def data_filename(fname): """Return the path to a data file of ours.""" return os.path.join(os.path.split(__file__)[0], fname) @@ -162,7 +158,6 @@ class HtmlReporter(Reporter): nums = analysis.numbers missing_branch_arcs = analysis.missing_branch_arcs() - arcs = self.arcs # These classes determine which lines are highlighted by default. c_run = "run hide_run" @@ -220,13 +215,17 @@ class HtmlReporter(Reporter): }) # Write the HTML page for this file. - html_filename = flat_rootname + ".html" - html_path = os.path.join(self.directory, html_filename) - extra_css = self.extra_css + html = spaceless(self.source_tmpl.render({ + 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, + 'arcs': self.arcs, 'extra_css': self.extra_css, + 'cu': cu, 'nums': nums, 'lines': lines, + })) - html = spaceless(self.source_tmpl.render(locals())) if sys.version_info < (3, 0): html = html.decode(encoding) + + html_filename = flat_rootname + ".html" + html_path = os.path.join(self.directory, html_filename) self.write_html(html_path, html) # Save this file's information for the index file. @@ -244,13 +243,15 @@ class HtmlReporter(Reporter): data("htmlfiles/index.html"), self.template_globals ) - files = self.files - arcs = self.arcs + self.totals = sum([f['nums'] for f in self.files]) - self.totals = totals = sum([f['nums'] for f in files]) - extra_css = self.extra_css + html = index_tmpl.render({ + 'arcs': self.arcs, + 'extra_css': self.extra_css, + 'files': self.files, + 'totals': self.totals, + }) - html = index_tmpl.render(locals()) if sys.version_info < (3, 0): html = html.decode("utf-8") self.write_html( -- cgit v1.2.1 From db679ea31c10baf93410ed352dd82b83d4e3e53e Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 15 Sep 2013 13:43:33 -0400 Subject: Always show long diffs in test failures. --- tests/coveragetest.py | 4 ++++ tests/test_html.py | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 4cc777d..5060b53 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -36,8 +36,12 @@ OK, ERR = 0, 1 class CoverageTest(TestCase): """A base class for Coverage test cases.""" + # Our own setting: most CoverageTests run in their own temp directory. run_in_temp_dir = True + # Standard unittest setting: show me diffs even if they are very long. + maxDiff = None + def setUp(self): super(CoverageTest, self).setUp() diff --git a/tests/test_html.py b/tests/test_html.py index eff3ca3..c004303 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -52,8 +52,6 @@ class HtmlDeltaTest(HtmlTestHelpers, CoverageTest): # so grab it here to restore it later. self.real_coverage_version = coverage.__version__ - self.maxDiff = None - def tearDown(self): coverage.__version__ = self.real_coverage_version super(HtmlDeltaTest, self).tearDown() -- cgit v1.2.1 From 2ea7f7bcd43fc000646e65bb1ef51075c5cefc47 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 15 Sep 2013 18:29:05 -0400 Subject: Satisfy pylint. --- tests/test_farm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_farm.py b/tests/test_farm.py index e514e66..fee2806 100644 --- a/tests/test_farm.py +++ b/tests/test_farm.py @@ -195,9 +195,9 @@ class FarmTestCase(object): """Compare files matching `file_pattern` in `dir1` and `dir2`. `dir2` is interpreted as a prefix, with Python version numbers appended - to find the actual directory to compare with. "foo" will compare against - "foo_v241", "foo_v24", "foo_v2", or "foo", depending on which directory - is found first. + to find the actual directory to compare with. "foo" will compare + against "foo_v241", "foo_v24", "foo_v2", or "foo", depending on which + directory is found first. `size_within` is a percentage delta for the file sizes. If non-zero, then the file contents are not compared (since they are expected to -- cgit v1.2.1 From f5675121528fe9d4729f0ba8052282de99ec9b79 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 15 Sep 2013 19:09:35 -0400 Subject: Add one more assertion to a test about COVERAGE_PROCESS_START. --- tests/test_process.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_process.py b/tests/test_process.py index 8554eb5..97e2f82 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -583,7 +583,7 @@ class ProcessStartupTest(CoverageTest): pass finally: pth.close() - else: # pragma: not covered + else: # pragma: not covered raise Exception("Couldn't find a place for the .pth file") def tearDown(self): @@ -615,7 +615,9 @@ class ProcessStartupTest(CoverageTest): import main # pylint: disable=F0401,W0612 self.assertEqual(open("out.txt").read(), "Hello, world!\n") + # Read the data from .coverage + self.assert_exists(".mycovdata") data = coverage.CoverageData() data.read_file(".mycovdata") self.assertEqual(data.summary()['sub.py'], 3) -- cgit v1.2.1