summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/__init__.py5
-rw-r--r--tests/coveragetest.py178
-rw-r--r--tests/farm/annotate/run_encodings.py8
-rw-r--r--tests/farm/html/gold_x_xml/coverage.xml4
-rw-r--r--tests/farm/html/gold_y_xml_branch/coverage.xml4
-rw-r--r--tests/farm/html/src/partial.ini9
-rw-r--r--tests/farm/html/src/partial.py9
-rw-r--r--tests/farm/run/run_chdir.py10
-rw-r--r--tests/farm/run/run_timid.py12
-rw-r--r--tests/farm/run/run_xxx.py10
-rw-r--r--tests/goldtest.py2
-rw-r--r--tests/helpers.py76
-rw-r--r--tests/modules/process_test/try_execfile.py18
-rw-r--r--tests/modules/usepkgs.py2
-rw-r--r--tests/osinfo.py10
-rw-r--r--tests/plugin1.py2
-rw-r--r--tests/test_api.py129
-rw-r--r--tests/test_arcs.py188
-rw-r--r--tests/test_cmdline.py213
-rw-r--r--tests/test_concurrency.py23
-rw-r--r--tests/test_config.py87
-rw-r--r--tests/test_coverage.py4
-rw-r--r--tests/test_data.py7
-rw-r--r--tests/test_debug.py80
-rw-r--r--tests/test_execfile.py26
-rw-r--r--tests/test_farm.py127
-rw-r--r--tests/test_files.py12
-rw-r--r--tests/test_html.py113
-rw-r--r--tests/test_misc.py128
-rw-r--r--tests/test_oddball.py177
-rw-r--r--tests/test_parser.py2
-rw-r--r--tests/test_phystokens.py141
-rw-r--r--tests/test_plugins.py102
-rw-r--r--tests/test_process.py565
-rw-r--r--tests/test_python.py33
-rw-r--r--tests/test_results.py29
-rw-r--r--tests/test_setup.py47
-rw-r--r--tests/test_summary.py59
-rw-r--r--tests/test_templite.py2
-rw-r--r--tests/test_testing.py126
-rw-r--r--tests/test_version.py39
-rw-r--r--tests/test_xml.py30
42 files changed, 1999 insertions, 849 deletions
diff --git a/tests/__init__.py b/tests/__init__.py
index 5a0e30f..1ff1e1b 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1 +1,4 @@
-"""Automated tests. Run with nosetests."""
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+"""Automated tests. Run with pytest."""
diff --git a/tests/coveragetest.py b/tests/coveragetest.py
index 9410e07..0e6131f 100644
--- a/tests/coveragetest.py
+++ b/tests/coveragetest.py
@@ -5,13 +5,12 @@
import contextlib
import datetime
-import glob
import os
import random
import re
import shlex
-import shutil
import sys
+import types
from unittest_mixins import (
EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin,
@@ -19,24 +18,51 @@ from unittest_mixins import (
)
import coverage
-from coverage.backunittest import TestCase
+from coverage import env
+from coverage.backunittest import TestCase, unittest
from coverage.backward import StringIO, import_local_file, string_class, shlex_quote
from coverage.cmdline import CoverageScript
-from coverage.debug import _TEST_NAME_FILE, DebugControl
+from coverage.debug import _TEST_NAME_FILE
+from coverage.misc import StopEverything
-from tests.helpers import run_command
+from tests.helpers import run_command, SuperModuleCleaner
# Status returns for the command line.
OK, ERR = 0, 1
+def convert_skip_exceptions(method):
+ """A decorator for test methods to convert StopEverything to SkipTest."""
+ def wrapper(*args, **kwargs):
+ """Run the test method, and convert exceptions."""
+ try:
+ result = method(*args, **kwargs)
+ except StopEverything:
+ raise unittest.SkipTest("StopEverything!")
+ return result
+ return wrapper
+
+
+class SkipConvertingMetaclass(type):
+ """Decorate all test methods to convert StopEverything to SkipTest."""
+ def __new__(mcs, name, bases, attrs):
+ for attr_name, attr_value in attrs.items():
+ if attr_name.startswith('test_') and isinstance(attr_value, types.FunctionType):
+ attrs[attr_name] = convert_skip_exceptions(attr_value)
+
+ return super(SkipConvertingMetaclass, mcs).__new__(mcs, name, bases, attrs)
+
+
+CoverageTestMethodsMixin = SkipConvertingMetaclass('CoverageTestMethodsMixin', (), {})
+
class CoverageTest(
EnvironmentAwareMixin,
StdStreamCapturingMixin,
TempDirMixin,
DelayedAssertionMixin,
- TestCase
+ CoverageTestMethodsMixin,
+ TestCase,
):
"""A base class for coverage.py test cases."""
@@ -46,9 +72,14 @@ class CoverageTest(
# Tell newer unittest implementations to print long helpful messages.
longMessage = True
+ # Let stderr go to stderr, pytest will capture it for us.
+ show_stderr = True
+
def setUp(self):
super(CoverageTest, self).setUp()
+ self.module_cleaner = SuperModuleCleaner()
+
# Attributes for getting info about what happened.
self.last_command_status = None
self.last_command_output = None
@@ -67,25 +98,7 @@ class CoverageTest(
one test.
"""
- # So that we can re-import files, clean them out first.
- self.cleanup_modules()
- # Also have to clean out the .pyc file, since the timestamp
- # resolution is only one second, a changed file might not be
- # picked up.
- for pyc in glob.glob('*.pyc'):
- os.remove(pyc)
- if os.path.exists("__pycache__"):
- shutil.rmtree("__pycache__")
-
- def import_local_file(self, modname, modfile=None):
- """Import a local file as a module.
-
- Opens a file in the current directory named `modname`.py, imports it
- as `modname`, and returns the module object. `modfile` is the file to
- import if it isn't in the current directory.
-
- """
- return import_local_file(modname, modfile)
+ self.module_cleaner.clean_local_file_imports()
def start_import_stop(self, cov, modname, modfile=None):
"""Start coverage, import a file, then stop coverage.
@@ -100,7 +113,7 @@ class CoverageTest(
cov.start()
try: # pragma: nested
# Import the Python file, executing it.
- mod = self.import_local_file(modname, modfile)
+ mod = import_local_file(modname, modfile)
finally: # pragma: nested
# Stop coverage.py.
cov.stop()
@@ -237,17 +250,17 @@ class CoverageTest(
with self.delayed_assertions():
self.assert_equal_args(
analysis.arc_possibilities(), arcs,
- "Possible arcs differ",
+ "Possible arcs differ: minus is actual, plus is expected"
)
self.assert_equal_args(
analysis.arcs_missing(), arcs_missing,
- "Missing arcs differ"
+ "Missing arcs differ: minus is actual, plus is expected"
)
self.assert_equal_args(
analysis.arcs_unpredicted(), arcs_unpredicted,
- "Unpredicted arcs differ"
+ "Unpredicted arcs differ: minus is actual, plus is expected"
)
if report:
@@ -259,11 +272,27 @@ class CoverageTest(
return cov
@contextlib.contextmanager
- def assert_warnings(self, cov, warnings):
- """A context manager to check that particular warnings happened in `cov`."""
+ def assert_warnings(self, cov, warnings, not_warnings=()):
+ """A context manager to check that particular warnings happened in `cov`.
+
+ `cov` is a Coverage instance. `warnings` is a list of regexes. Every
+ regex must match a warning that was issued by `cov`. It is OK for
+ extra warnings to be issued by `cov` that are not matched by any regex.
+ Warnings that are disabled are still considered issued by this function.
+
+ `not_warnings` is a list of regexes that must not appear in the
+ warnings. This is only checked if there are some positive warnings to
+ test for in `warnings`.
+
+ If `warnings` is empty, then `cov` is not allowed to issue any
+ warnings.
+
+ """
saved_warnings = []
- def capture_warning(msg):
+ def capture_warning(msg, slug=None):
"""A fake implementation of Coverage._warn, to capture warnings."""
+ if slug:
+ msg = "%s (%s)" % (msg, slug)
saved_warnings.append(msg)
original_warn = cov._warn
@@ -274,12 +303,22 @@ class CoverageTest(
except:
raise
else:
- for warning_regex in warnings:
- for saved in saved_warnings:
- if re.search(warning_regex, saved):
- break
- else:
- self.fail("Didn't find warning %r in %r" % (warning_regex, saved_warnings))
+ if warnings:
+ for warning_regex in warnings:
+ for saved in saved_warnings:
+ if re.search(warning_regex, saved):
+ break
+ else:
+ self.fail("Didn't find warning %r in %r" % (warning_regex, saved_warnings))
+ for warning_regex in not_warnings:
+ for saved in saved_warnings:
+ if re.search(warning_regex, saved):
+ self.fail("Found warning %r in %r" % (warning_regex, saved_warnings))
+ else:
+ # No warnings expected. Raise if any warnings happened.
+ if saved_warnings:
+ self.fail("Unexpected warnings: %r" % (saved_warnings,))
+ finally:
cov._warn = original_warn
def nice_file(self, *fparts):
@@ -328,8 +367,7 @@ class CoverageTest(
Returns None.
"""
- script = CoverageScript(_covpkg=_covpkg)
- ret_actual = script.command_line(shlex.split(args))
+ ret_actual = command_line(args, _covpkg=_covpkg)
self.assertEqual(ret_actual, ret)
coverage_command = "coverage"
@@ -373,7 +411,7 @@ class CoverageTest(
"""
# Make sure "python" and "coverage" mean specifically what we want
# them to mean.
- split_commandline = cmd.split(" ", 1)
+ split_commandline = cmd.split()
command_name = split_commandline[0]
command_args = split_commandline[1:]
@@ -383,30 +421,49 @@ class CoverageTest(
# get executed as "python3.3 foo.py". This is important because
# Python 3.x doesn't install as "python", so you might get a Python
# 2 executable instead if you don't use the executable's basename.
- command_name = os.path.basename(sys.executable)
+ command_words = [os.path.basename(sys.executable)]
+
+ elif command_name == "coverage":
+ if env.JYTHON: # pragma: only jython
+ # Jython can't do reporting, so let's skip the test now.
+ if command_args and command_args[0] in ('report', 'html', 'xml', 'annotate'):
+ self.skipTest("Can't run reporting commands in Jython")
+ # Jython can't run "coverage" as a command because the shebang
+ # refers to another shebang'd Python script. So run them as
+ # modules.
+ command_words = "jython -m coverage".split()
+ else:
+ # The invocation requests the Coverage.py program. Substitute the
+ # actual Coverage.py main command name.
+ command_words = [self.coverage_command]
- if command_name == "coverage":
- # The invocation requests the Coverage.py program. Substitute the
- # actual Coverage.py main command name.
- command_name = self.coverage_command
+ else:
+ command_words = [command_name]
- cmd = " ".join([shlex_quote(command_name)] + command_args)
+ cmd = " ".join([shlex_quote(w) for w in command_words] + command_args)
# Add our test modules directory to PYTHONPATH. I'm sure there's too
# much path munging here, but...
- here = os.path.dirname(self.nice_file(coverage.__file__, ".."))
- testmods = self.nice_file(here, 'tests/modules')
- zipfile = self.nice_file(here, 'tests/zipmods.zip')
- pypath = os.getenv('PYTHONPATH', '')
+ pythonpath_name = "PYTHONPATH"
+ if env.JYTHON:
+ pythonpath_name = "JYTHONPATH" # pragma: only jython
+
+ testmods = self.nice_file(self.working_root(), 'tests/modules')
+ zipfile = self.nice_file(self.working_root(), 'tests/zipmods.zip')
+ pypath = os.getenv(pythonpath_name, '')
if pypath:
pypath += os.pathsep
pypath += testmods + os.pathsep + zipfile
- self.set_environ('PYTHONPATH', pypath)
+ self.set_environ(pythonpath_name, pypath)
self.last_command_status, self.last_command_output = run_command(cmd)
print(self.last_command_output)
return self.last_command_status, self.last_command_output
+ def working_root(self):
+ """Where is the root of the coverage.py working tree?"""
+ return os.path.dirname(self.nice_file(coverage.__file__, ".."))
+
def report_from_command(self, cmd):
"""Return the report from the `cmd`, with some convenience added."""
report = self.run_command(cmd).replace('\\', '/')
@@ -433,11 +490,14 @@ class CoverageTest(
return self.squeezed_lines(report)[-1]
-class DebugControlString(DebugControl):
- """A `DebugControl` that writes to a StringIO, for testing."""
- def __init__(self, options):
- super(DebugControlString, self).__init__(options, StringIO())
+def command_line(args, **kwargs):
+ """Run `args` through the CoverageScript command line.
+
+ `kwargs` are the keyword arguments to the CoverageScript constructor.
+
+ Returns the return code from CoverageScript.command_line.
- def get_output(self):
- """Get the output text from the `DebugControl`."""
- return self.output.getvalue()
+ """
+ script = CoverageScript(**kwargs)
+ ret = script.command_line(shlex.split(args))
+ return ret
diff --git a/tests/farm/annotate/run_encodings.py b/tests/farm/annotate/run_encodings.py
index 527cd88..46d8c64 100644
--- a/tests/farm/annotate/run_encodings.py
+++ b/tests/farm/annotate/run_encodings.py
@@ -1,10 +1,10 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
-copy("src", "out")
+copy("src", "out_encodings")
run("""
coverage run utf8.py
coverage annotate utf8.py
- """, rundir="out")
-compare("out", "gold_encodings", "*,cover")
-clean("out")
+ """, rundir="out_encodings")
+compare("out_encodings", "gold_encodings", "*,cover")
+clean("out_encodings")
diff --git a/tests/farm/html/gold_x_xml/coverage.xml b/tests/farm/html/gold_x_xml/coverage.xml
index b3e9854..162824a 100644
--- a/tests/farm/html/gold_x_xml/coverage.xml
+++ b/tests/farm/html/gold_x_xml/coverage.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" ?>
-<coverage branch-rate="0" line-rate="0.6667" timestamp="1437745880639" version="4.0a7">
+<coverage branch-rate="0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.6667" lines-covered="2" lines-valid="3" timestamp="1437745880639" version="4.0a7">
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/coverage-4.0a7 -->
- <!-- Based on https://raw.githubusercontent.com/cobertura/web/f0366e5e2cf18f111cbd61fc34ef720a6584ba02/htdocs/xml/coverage-03.dtd -->
+ <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources>
<source>/Users/ned/coverage/trunk/tests/farm/html/src</source>
</sources>
diff --git a/tests/farm/html/gold_y_xml_branch/coverage.xml b/tests/farm/html/gold_y_xml_branch/coverage.xml
index d8ff0bb..bcf1137 100644
--- a/tests/farm/html/gold_y_xml_branch/coverage.xml
+++ b/tests/farm/html/gold_y_xml_branch/coverage.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" ?>
-<coverage branch-rate="0.5" line-rate="0.8" timestamp="1437745880882" version="4.0a7">
+<coverage branch-rate="0.5" branches-covered="1" branches-valid="2" complexity="0" line-rate="0.8" lines-covered="4" lines-valid="5" timestamp="1437745880882" version="4.0a7">
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/coverage-4.0a7 -->
- <!-- Based on https://raw.githubusercontent.com/cobertura/web/f0366e5e2cf18f111cbd61fc34ef720a6584ba02/htdocs/xml/coverage-03.dtd -->
+ <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources>
<source>/Users/ned/coverage/trunk/tests/farm/html/src</source>
</sources>
diff --git a/tests/farm/html/src/partial.ini b/tests/farm/html/src/partial.ini
new file mode 100644
index 0000000..cdb241b
--- /dev/null
+++ b/tests/farm/html/src/partial.ini
@@ -0,0 +1,9 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+[run]
+branch = True
+
+[report]
+exclude_lines =
+ raise AssertionError
diff --git a/tests/farm/html/src/partial.py b/tests/farm/html/src/partial.py
index 66dddac..0f8fbe3c 100644
--- a/tests/farm/html/src/partial.py
+++ b/tests/farm/html/src/partial.py
@@ -1,9 +1,9 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
-# partial branches
+# partial branches and excluded lines
-a = 3
+a = 6
while True:
break
@@ -18,4 +18,7 @@ if 0:
never_happen()
if 1:
- a = 13
+ a = 21
+
+if a == 23:
+ raise AssertionError("Can't")
diff --git a/tests/farm/run/run_chdir.py b/tests/farm/run/run_chdir.py
index 9e3c751..1da4e9a 100644
--- a/tests/farm/run/run_chdir.py
+++ b/tests/farm/run/run_chdir.py
@@ -1,15 +1,15 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
-copy("src", "out")
+copy("src", "out_chdir")
run("""
coverage run chdir.py
coverage report
- """, rundir="out", outfile="stdout.txt")
-contains("out/stdout.txt",
+ """, rundir="out_chdir", outfile="stdout.txt")
+contains("out_chdir/stdout.txt",
"Line One",
"Line Two",
"chdir"
)
-doesnt_contain("out/stdout.txt", "No such file or directory")
-clean("out")
+doesnt_contain("out_chdir/stdout.txt", "No such file or directory")
+clean("out_chdir")
diff --git a/tests/farm/run/run_timid.py b/tests/farm/run/run_timid.py
index a632cea..0370cf8 100644
--- a/tests/farm/run/run_timid.py
+++ b/tests/farm/run/run_timid.py
@@ -17,16 +17,16 @@ import os
if os.environ.get('COVERAGE_COVERAGE', ''):
skip("Can't test timid during coverage measurement.")
-copy("src", "out")
+copy("src", "out_timid")
run("""
python showtrace.py none
coverage run showtrace.py regular
coverage run --timid showtrace.py timid
- """, rundir="out", outfile="showtraceout.txt")
+ """, rundir="out_timid", outfile="showtraceout.txt")
# When running without coverage, no trace function
# When running timidly, the trace function is always Python.
-contains("out/showtraceout.txt",
+contains("out_timid/showtraceout.txt",
"none None",
"timid PyTracer",
)
@@ -34,10 +34,10 @@ contains("out/showtraceout.txt",
if os.environ.get('COVERAGE_TEST_TRACER', 'c') == 'c':
# If the C trace function is being tested, then regular running should have
# the C function, which registers itself as f_trace.
- contains("out/showtraceout.txt", "regular CTracer")
+ contains("out_timid/showtraceout.txt", "regular CTracer")
else:
# If the Python trace function is being tested, then regular running will
# also show the Python function.
- contains("out/showtraceout.txt", "regular PyTracer")
+ contains("out_timid/showtraceout.txt", "regular PyTracer")
-clean("out")
+clean("out_timid")
diff --git a/tests/farm/run/run_xxx.py b/tests/farm/run/run_xxx.py
index 62a862e..1db5b0d 100644
--- a/tests/farm/run/run_xxx.py
+++ b/tests/farm/run/run_xxx.py
@@ -1,15 +1,15 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
-copy("src", "out")
+copy("src", "out_xxx")
run("""
coverage run xxx
coverage report
- """, rundir="out", outfile="stdout.txt")
-contains("out/stdout.txt",
+ """, rundir="out_xxx", outfile="stdout.txt")
+contains("out_xxx/stdout.txt",
"xxx: 3 4 0 7",
"\nxxx ", # The reporting line for xxx
" 7 1 86%" # The reporting data for xxx
)
-doesnt_contain("out/stdout.txt", "No such file or directory")
-clean("out")
+doesnt_contain("out_xxx/stdout.txt", "No such file or directory")
+clean("out_xxx")
diff --git a/tests/goldtest.py b/tests/goldtest.py
index 27a082e..baaa8f0 100644
--- a/tests/goldtest.py
+++ b/tests/goldtest.py
@@ -38,5 +38,5 @@ class CoverageGoldTest(CoverageTest):
# beginning of the test.
clean(the_dir)
- if not os.environ.get("COVERAGE_KEEP_OUTPUT"): # pragma: partial
+ if not os.environ.get("COVERAGE_KEEP_OUTPUT"): # pragma: part covered
self.addCleanup(clean, the_dir)
diff --git a/tests/helpers.py b/tests/helpers.py
index f4bff2b..f10169a 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -3,11 +3,18 @@
"""Helpers for coverage.py tests."""
+import glob
+import itertools
import os
+import re
+import shutil
import subprocess
import sys
+from unittest_mixins import ModuleCleaner
+
from coverage import env
+from coverage.backward import invalidate_import_caches, unicode_class
from coverage.misc import output_encoding
@@ -17,16 +24,14 @@ def run_command(cmd):
Returns the exit status code and the combined stdout and stderr.
"""
- if env.PY2 and isinstance(cmd, unicode):
+ if env.PY2 and isinstance(cmd, unicode_class):
cmd = cmd.encode(sys.getfilesystemencoding())
# In some strange cases (PyPy3 in a virtualenv!?) the stdout encoding of
# the subprocess is set incorrectly to ascii. Use an environment variable
# to force the encoding to be the same as ours.
sub_env = dict(os.environ)
- encoding = output_encoding()
- if encoding:
- sub_env['PYTHONIOENCODING'] = encoding
+ sub_env['PYTHONIOENCODING'] = output_encoding()
proc = subprocess.Popen(
cmd,
@@ -53,11 +58,19 @@ class CheckUniqueFilenames(object):
self.wrapped = wrapped
@classmethod
- def hook(cls, cov, method_name):
- """Replace a method with our checking wrapper."""
- method = getattr(cov, method_name)
+ def hook(cls, obj, method_name):
+ """Replace a method with our checking wrapper.
+
+ The method must take a string as a first argument. That argument
+ will be checked for uniqueness across all the calls to this method.
+
+ The values don't have to be file names actually, just strings, but
+ we only use it for filename arguments.
+
+ """
+ method = getattr(obj, method_name)
hook = cls(method)
- setattr(cov, method_name, hook.wrapper)
+ setattr(obj, method_name, hook.wrapper)
return hook
def wrapper(self, filename, *args, **kwargs):
@@ -68,3 +81,50 @@ class CheckUniqueFilenames(object):
self.filenames.add(filename)
ret = self.wrapped(filename, *args, **kwargs)
return ret
+
+
+def re_lines(text, pat, match=True):
+ """Return the text of lines that match `pat` in the string `text`.
+
+ If `match` is false, the selection is inverted: only the non-matching
+ lines are included.
+
+ Returns a string, the text of only the selected lines.
+
+ """
+ return "".join(l for l in text.splitlines(True) if bool(re.search(pat, l)) == match)
+
+
+def re_line(text, pat):
+ """Return the one line in `text` that matches regex `pat`.
+
+ Raises an AssertionError if more than one, or less than one, line matches.
+
+ """
+ lines = re_lines(text, pat).splitlines()
+ assert len(lines) == 1
+ return lines[0]
+
+
+class SuperModuleCleaner(ModuleCleaner):
+ """Remember the state of sys.modules and restore it later."""
+
+ def clean_local_file_imports(self):
+ """Clean up the results of calls to `import_local_file`.
+
+ Use this if you need to `import_local_file` the same file twice in
+ one test.
+
+ """
+ # So that we can re-import files, clean them out first.
+ self.cleanup_modules()
+
+ # Also have to clean out the .pyc file, since the timestamp
+ # resolution is only one second, a changed file might not be
+ # picked up.
+ for pyc in itertools.chain(glob.glob('*.pyc'), glob.glob('*$py.class')):
+ os.remove(pyc)
+ if os.path.exists("__pycache__"):
+ shutil.rmtree("__pycache__")
+
+ invalidate_import_caches()
diff --git a/tests/modules/process_test/try_execfile.py b/tests/modules/process_test/try_execfile.py
index 7090507..ec7dcbe 100644
--- a/tests/modules/process_test/try_execfile.py
+++ b/tests/modules/process_test/try_execfile.py
@@ -20,7 +20,10 @@ differences and get a clean diff.
"""
-import json, os, sys
+import itertools
+import json
+import os
+import sys
# sys.path varies by execution environments. Coverage.py uses setuptools to
# make console scripts, which means pkg_resources is imported. pkg_resources
@@ -65,19 +68,28 @@ FN_VAL = my_function("fooey")
loader = globals().get('__loader__')
fullname = getattr(loader, 'fullname', None) or getattr(loader, 'name', None)
+# A more compact grouped-by-first-letter list of builtins.
+def word_group(w):
+ """Clump AB, CD, EF, etc."""
+ return chr((ord(w[0]) + 1) & 0xFE)
+
+builtin_dir = [" ".join(s) for _, s in itertools.groupby(dir(__builtins__), key=word_group)]
+
globals_to_check = {
+ 'os.getcwd': os.getcwd(),
'__name__': __name__,
'__file__': __file__,
'__doc__': __doc__,
'__builtins__.has_open': hasattr(__builtins__, 'open'),
- '__builtins__.dir': dir(__builtins__),
+ '__builtins__.dir': builtin_dir,
'__loader__ exists': loader is not None,
'__loader__.fullname': fullname,
'__package__': __package__,
'DATA': DATA,
'FN_VAL': FN_VAL,
'__main__.DATA': getattr(__main__, "DATA", "nothing"),
- 'argv': sys.argv,
+ 'argv0': sys.argv[0],
+ 'argv1-n': sys.argv[1:],
'path': cleaned_sys_path,
}
diff --git a/tests/modules/usepkgs.py b/tests/modules/usepkgs.py
index 4e94aca..222e68c 100644
--- a/tests/modules/usepkgs.py
+++ b/tests/modules/usepkgs.py
@@ -1,7 +1,7 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
-import pkg1.p1a, pkg1.p1b
+import pkg1.p1a, pkg1.p1b, pkg1.sub
import pkg2.p2a, pkg2.p2b
import othermods.othera, othermods.otherb
import othermods.sub.osa, othermods.sub.osb
diff --git a/tests/osinfo.py b/tests/osinfo.py
index a7ebd2e..094fb09 100644
--- a/tests/osinfo.py
+++ b/tests/osinfo.py
@@ -34,8 +34,8 @@ if env.WINDOWS:
ctypes.byref(mem_struct),
ctypes.sizeof(mem_struct)
)
- if not ret:
- return 0
+ if not ret: # pragma: part covered
+ return 0 # pragma: cant happen
return mem_struct.PrivateUsage
elif env.LINUX:
@@ -50,13 +50,13 @@ elif env.LINUX:
# Get pseudo file /proc/<pid>/status
with open('/proc/%d/status' % os.getpid()) as t:
v = t.read()
- except IOError:
+ except IOError: # pragma: cant happen
return 0 # non-Linux?
# Get VmKey line e.g. 'VmRSS: 9999 kB\n ...'
i = v.index(key)
v = v[i:].split(None, 3)
- if len(v) < 3:
- return 0 # Invalid format?
+ if len(v) < 3: # pragma: part covered
+ return 0 # pragma: cant happen
# Convert Vm value to bytes.
return int(float(v[1]) * _scale[v[2].lower()])
diff --git a/tests/plugin1.py b/tests/plugin1.py
index af4dfc5..63ebacf 100644
--- a/tests/plugin1.py
+++ b/tests/plugin1.py
@@ -24,7 +24,7 @@ class FileTracer(coverage.FileTracer):
"""A FileTracer emulating a simple static plugin."""
def __init__(self, filename):
- """Claim that xyz.py was actually sourced from ABC.zz"""
+ """Claim that */*xyz.py was actually sourced from /src/*ABC.zz"""
self._filename = filename
self._source_filename = os.path.join(
"/src",
diff --git a/tests/test_api.py b/tests/test_api.py
index 6f14210..9a3fc82 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -11,11 +11,11 @@ import warnings
import coverage
from coverage import env
-from coverage.backward import StringIO
+from coverage.backward import StringIO, import_local_file
from coverage.misc import CoverageException
from coverage.report import Reporter
-from tests.coveragetest import CoverageTest
+from tests.coveragetest import CoverageTest, CoverageTestMethodsMixin
class ApiTest(CoverageTest):
@@ -35,7 +35,7 @@ class ApiTest(CoverageTest):
def assertFiles(self, files):
"""Assert that the files here are `files`, ignoring the usual junk."""
here = os.listdir(".")
- here = self.clean_files(here, ["*.pyc", "__pycache__"])
+ here = self.clean_files(here, ["*.pyc", "__pycache__", "*$py.class"])
self.assertCountEqual(here, files)
def test_unexecuted_file(self):
@@ -282,17 +282,70 @@ class ApiTest(CoverageTest):
self.check_code1_code2(cov)
def test_start_save_stop(self):
- self.skipTest("Expected failure: https://bitbucket.org/ned/coveragepy/issue/79")
self.make_code1_code2()
cov = coverage.Coverage()
cov.start()
- self.import_local_file("code1")
- cov.save()
- self.import_local_file("code2")
- cov.stop()
-
+ import_local_file("code1") # pragma: nested
+ cov.save() # pragma: nested
+ import_local_file("code2") # pragma: nested
+ cov.stop() # pragma: nested
self.check_code1_code2(cov)
+ def test_start_save_nostop(self):
+ self.make_code1_code2()
+ cov = coverage.Coverage()
+ cov.start()
+ import_local_file("code1") # pragma: nested
+ cov.save() # pragma: nested
+ import_local_file("code2") # pragma: nested
+ self.check_code1_code2(cov) # pragma: nested
+ # Then stop it, or the test suite gets out of whack.
+ cov.stop() # pragma: nested
+
+ def test_two_getdata_only_warn_once(self):
+ self.make_code1_code2()
+ cov = coverage.Coverage(source=["."], omit=["code1.py"])
+ cov.start()
+ import_local_file("code1") # pragma: nested
+ cov.stop() # pragma: nested
+ # We didn't collect any data, so we should get a warning.
+ with self.assert_warnings(cov, ["No data was collected"]):
+ cov.get_data()
+ # But calling get_data a second time with no intervening activity
+ # won't make another warning.
+ with self.assert_warnings(cov, []):
+ cov.get_data()
+
+ def test_two_getdata_only_warn_once_nostop(self):
+ self.make_code1_code2()
+ cov = coverage.Coverage(source=["."], omit=["code1.py"])
+ cov.start()
+ import_local_file("code1") # pragma: nested
+ # We didn't collect any data, so we should get a warning.
+ with self.assert_warnings(cov, ["No data was collected"]): # pragma: nested
+ cov.get_data() # pragma: nested
+ # But calling get_data a second time with no intervening activity
+ # won't make another warning.
+ with self.assert_warnings(cov, []): # pragma: nested
+ cov.get_data() # pragma: nested
+ # Then stop it, or the test suite gets out of whack.
+ cov.stop() # pragma: nested
+
+ def test_two_getdata_warn_twice(self):
+ self.make_code1_code2()
+ cov = coverage.Coverage(source=["."], omit=["code1.py", "code2.py"])
+ cov.start()
+ import_local_file("code1") # pragma: nested
+ # We didn't collect any data, so we should get a warning.
+ with self.assert_warnings(cov, ["No data was collected"]): # pragma: nested
+ cov.save() # pragma: nested
+ import_local_file("code2") # pragma: nested
+ # Calling get_data a second time after tracing some more will warn again.
+ with self.assert_warnings(cov, ["No data was collected"]): # pragma: nested
+ cov.get_data() # pragma: nested
+ # Then stop it, or the test suite gets out of whack.
+ cov.stop() # pragma: nested
+
def make_good_data_files(self):
"""Make some good data files."""
self.make_code1_code2()
@@ -348,6 +401,49 @@ class ApiTest(CoverageTest):
self.assertEqual(statements, [1, 2])
self.assertEqual(missing, [1, 2])
+ def test_warnings(self):
+ self.make_file("hello.py", """\
+ import sys, os
+ print("Hello")
+ """)
+ cov = coverage.Coverage(source=["sys", "xyzzy", "quux"])
+ self.start_import_stop(cov, "hello")
+ cov.get_data()
+
+ out = self.stdout()
+ self.assertIn("Hello\n", out)
+
+ err = self.stderr()
+ self.assertIn(textwrap.dedent("""\
+ Coverage.py warning: Module sys has no Python source. (module-not-python)
+ Coverage.py warning: Module xyzzy was never imported. (module-not-imported)
+ Coverage.py warning: Module quux was never imported. (module-not-imported)
+ Coverage.py warning: No data was collected. (no-data-collected)
+ """), err)
+
+ def test_warnings_suppressed(self):
+ self.make_file("hello.py", """\
+ import sys, os
+ print("Hello")
+ """)
+ self.make_file(".coveragerc", """\
+ [run]
+ disable_warnings = no-data-collected, module-not-imported
+ """)
+ cov = coverage.Coverage(source=["sys", "xyzzy", "quux"])
+ self.start_import_stop(cov, "hello")
+ cov.get_data()
+
+ out = self.stdout()
+ self.assertIn("Hello\n", out)
+
+ err = self.stderr()
+ self.assertIn(textwrap.dedent("""\
+ Coverage.py warning: Module sys has no Python source. (module-not-python)
+ """), err)
+ self.assertNotIn("module-not-imported", err)
+ self.assertNotIn("no-data-collected", err)
+
class NamespaceModuleTest(CoverageTest):
"""Test PEP-420 namespace modules."""
@@ -376,16 +472,14 @@ class UsingModulesMixin(object):
def setUp(self):
super(UsingModulesMixin, self).setUp()
- old_dir = os.getcwd()
- os.chdir(self.nice_file(os.path.dirname(__file__), 'modules'))
- self.addCleanup(os.chdir, old_dir)
+ self.chdir(self.nice_file(os.path.dirname(__file__), 'modules'))
# Parent class saves and restores sys.path, we can just modify it.
sys.path.append(".")
sys.path.append("../moremodules")
-class OmitIncludeTestsMixin(UsingModulesMixin):
+class OmitIncludeTestsMixin(UsingModulesMixin, CoverageTestMethodsMixin):
"""Test methods for coverage methods taking include and omit."""
def filenames_in(self, summary, filenames):
@@ -461,13 +555,20 @@ class SourceOmitIncludeTest(OmitIncludeTestsMixin, CoverageTest):
summary[k[:-3]] = v
return summary
- def test_source_package(self):
+ def test_source_package_as_dir(self):
+ # pkg1 is a directory, since we cd'd into tests/modules in setUp.
lines = self.coverage_usepkgs(source=["pkg1"])
self.filenames_in(lines, "p1a p1b")
self.filenames_not_in(lines, "p2a p2b othera otherb osa osb")
# Because source= was specified, we do search for unexecuted files.
self.assertEqual(lines['p1c'], 0)
+ def test_source_package_as_package(self):
+ lines = self.coverage_usepkgs(source=["pkg1.sub"])
+ self.filenames_not_in(lines, "p2a p2b othera otherb osa osb")
+ # Because source= was specified, we do search for unexecuted files.
+ self.assertEqual(lines['runmod3'], 0)
+
def test_source_package_dotted(self):
lines = self.coverage_usepkgs(source=["pkg1.p1b"])
self.filenames_in(lines, "p1b")
diff --git a/tests/test_arcs.py b/tests/test_arcs.py
index 5ea2fe1..7df623b 100644
--- a/tests/test_arcs.py
+++ b/tests/test_arcs.py
@@ -143,10 +143,17 @@ class SimpleArcTest(CoverageTest):
)
def test_what_is_the_sound_of_no_lines_clapping(self):
+ if env.JYTHON:
+ # Jython reports no lines for an empty file.
+ arcz_missing=".1 1." # pragma: only jython
+ else:
+ # Other Pythons report one line.
+ arcz_missing=""
self.check_coverage("""\
# __init__.py
""",
arcz=".1 1.",
+ arcz_missing=arcz_missing,
)
@@ -252,12 +259,12 @@ class LoopArcTest(CoverageTest):
""",
arcz=".1 12 23 34 45 36 63 57 7.",
)
- # With "while True", 2.x thinks it's computation, 3.x thinks it's
- # constant.
+ # With "while True", 2.x thinks it's computation,
+ # 3.x thinks it's constant.
if env.PY3:
arcz = ".1 12 23 34 45 36 63 57 7."
else:
- arcz = ".1 12 23 27 34 45 36 62 57 7."
+ arcz = ".1 12 23 34 45 36 62 57 7."
self.check_coverage("""\
a, i = 1, 0
while True:
@@ -270,6 +277,37 @@ class LoopArcTest(CoverageTest):
arcz=arcz,
)
+ def test_zero_coverage_while_loop(self):
+ # https://bitbucket.org/ned/coveragepy/issue/502
+ self.make_file("main.py", "print('done')")
+ self.make_file("zero.py", """\
+ def method(self):
+ while True:
+ return 1
+ """)
+ out = self.run_command("coverage run --branch --source=. main.py")
+ self.assertEqual(out, 'done\n')
+ report = self.report_from_command("coverage report -m")
+ squeezed = self.squeezed_lines(report)
+ self.assertIn("zero.py 3 3 0 0 0% 1-3", squeezed[3])
+
+ def test_bug_496_continue_in_constant_while(self):
+ # https://bitbucket.org/ned/coveragepy/issue/496
+ if env.PY3:
+ arcz = ".1 12 23 34 45 53 46 6."
+ else:
+ arcz = ".1 12 23 34 45 52 46 6."
+ self.check_coverage("""\
+ up = iter('ta')
+ while True:
+ char = next(up)
+ if char == 't':
+ continue
+ break
+ """,
+ arcz=arcz
+ )
+
def test_for_if_else_for(self):
self.check_coverage("""\
def branches_2(l):
@@ -370,7 +408,7 @@ class LoopArcTest(CoverageTest):
def test_other_comprehensions(self):
if env.PYVERSION < (2, 7):
- self.skipTest("Don't have set or dict comprehensions before 2.7")
+ self.skipTest("No set or dict comprehensions before 2.7")
# Set comprehension:
self.check_coverage("""\
o = ((1,2), (3,4))
@@ -394,7 +432,7 @@ class LoopArcTest(CoverageTest):
def test_multiline_dict_comp(self):
if env.PYVERSION < (2, 7):
- self.skipTest("Don't have set or dict comprehensions before 2.7")
+ self.skipTest("No set or dict comprehensions before 2.7")
if env.PYVERSION < (3, 5):
arcz = "-42 2B B-4 2-4"
else:
@@ -762,16 +800,19 @@ class ExceptionArcTest(CoverageTest):
def test_return_finally(self):
self.check_coverage("""\
a = [1]
- def func():
- try:
- return 10
- finally:
- a.append(6)
-
- assert func() == 10
- assert a == [1, 6]
- """,
- arcz=".1 12 28 89 9. -23 34 46 6-2",
+ def check_token(data):
+ if data:
+ try:
+ return 5
+ finally:
+ a.append(7)
+ return 8
+ assert check_token(False) == 8
+ assert a == [1]
+ assert check_token(True) == 5
+ assert a == [1, 7]
+ """,
+ arcz=".1 12 29 9A AB BC C-1 -23 34 45 57 7-2 38 8-2",
)
def test_except_jump_finally(self):
@@ -998,6 +1039,103 @@ class YieldTest(CoverageTest):
)
+class OptimizedIfTest(CoverageTest):
+ """Tests of if statements being optimized away."""
+
+ def test_optimized_away_if_0(self):
+ self.check_coverage("""\
+ a = 1
+ if len([2]):
+ c = 3
+ if 0: # this line isn't in the compiled code.
+ if len([5]):
+ d = 6
+ else:
+ e = 8
+ f = 9
+ """,
+ lines=[1, 2, 3, 8, 9],
+ arcz=".1 12 23 28 38 89 9.",
+ arcz_missing="28",
+ )
+
+ def test_optimized_away_if_1(self):
+ self.check_coverage("""\
+ a = 1
+ if len([2]):
+ c = 3
+ if 1: # this line isn't in the compiled code,
+ if len([5]): # but these are.
+ d = 6
+ else:
+ e = 8
+ f = 9
+ """,
+ lines=[1, 2, 3, 5, 6, 9],
+ arcz=".1 12 23 25 35 56 69 59 9.",
+ arcz_missing="25 59",
+ )
+ self.check_coverage("""\
+ a = 1
+ if 1:
+ b = 3
+ c = 4
+ d = 5
+ """,
+ lines=[1, 3, 4, 5],
+ arcz=".1 13 34 45 5.",
+ )
+
+ def test_optimized_nested(self):
+ self.check_coverage("""\
+ a = 1
+ if 0:
+ if 0:
+ b = 4
+ else:
+ c = 6
+ else:
+ if 0:
+ d = 9
+ else:
+ if 0: e = 11
+ f = 12
+ if 0: g = 13
+ h = 14
+ i = 15
+ """,
+ lines=[1, 12, 14, 15],
+ arcz=".1 1C CE EF F.",
+ )
+
+ def test_constant_if(self):
+ if env.PYPY:
+ self.skipTest("PyPy doesn't optimize away 'if __debug__:'")
+ # CPython optimizes away "if __debug__:"
+ self.check_coverage("""\
+ for value in [True, False]:
+ if value:
+ if __debug__:
+ x = 4
+ else:
+ x = 6
+ """,
+ arcz=".1 12 24 41 26 61 1.",
+ )
+ # No Python optimizes away "if not __debug__:"
+ self.check_coverage("""\
+ for value in [True, False]:
+ if value:
+ if not __debug__:
+ x = 4
+ else:
+ x = 6
+ """,
+ arcz=".1 12 23 31 34 41 26 61 1.",
+ arcz_missing="34 41",
+ )
+
+
class MiscArcTest(CoverageTest):
"""Miscellaneous arc-measuring tests."""
@@ -1067,6 +1205,9 @@ class MiscArcTest(CoverageTest):
)
def test_pathologically_long_code_object(self):
+ if env.JYTHON:
+ self.skipTest("Bytecode concerns are irrelevant on Jython")
+
# https://bitbucket.org/ned/coveragepy/issue/359
# The structure of this file is such that an EXTENDED_ARG bytecode is
# needed to encode the jump at the end. We weren't interpreting those
@@ -1090,21 +1231,6 @@ class MiscArcTest(CoverageTest):
arcs_missing=[], arcs_unpredicted=[],
)
- def test_optimized_away_lines(self):
- self.check_coverage("""\
- a = 1
- if len([2]):
- c = 3
- if 0: # this line isn't in the compiled code.
- if len([5]):
- d = 6
- e = 7
- """,
- lines=[1, 2, 3, 7],
- arcz=".1 12 23 27 37 7.",
- arcz_missing="27",
- )
-
def test_partial_generators(self):
# https://bitbucket.org/ned/coveragepy/issues/475/generator-expression-is-marked-as-not
# Line 2 is executed completely.
@@ -1340,7 +1466,7 @@ class AsyncTest(CoverageTest):
def __init__(self, obj): # 4
self._it = iter(obj)
- async def __aiter__(self): # 7
+ def __aiter__(self): # 7
return self
async def __anext__(self): # A
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
index 3b982eb..2378887 100644
--- a/tests/test_cmdline.py
+++ b/tests/test_cmdline.py
@@ -5,11 +5,11 @@
import pprint
import re
-import shlex
import sys
import textwrap
import mock
+import pytest
import coverage
import coverage.cmdline
@@ -18,7 +18,7 @@ from coverage.config import CoverageConfig
from coverage.data import CoverageData, CoverageDataFiles
from coverage.misc import ExceptionDuringRun
-from tests.coveragetest import CoverageTest, OK, ERR
+from tests.coveragetest import CoverageTest, OK, ERR, command_line
class BaseCmdLineTest(CoverageTest):
@@ -29,7 +29,7 @@ class BaseCmdLineTest(CoverageTest):
# Make a dict mapping function names to the default values that cmdline.py
# uses when calling the function.
defaults = mock.Mock()
- defaults.coverage(
+ defaults.Coverage(
cover_pylib=None, data_suffix=None, timid=None, branch=None,
config_file=True, source=None, include=None, omit=None, debug=None,
concurrency=None,
@@ -39,7 +39,7 @@ class BaseCmdLineTest(CoverageTest):
)
defaults.html_report(
directory=None, ignore_errors=None, include=None, omit=None, morfs=[],
- title=None,
+ skip_covered=None, title=None
)
defaults.report(
ignore_errors=None, include=None, omit=None, morfs=[],
@@ -54,9 +54,9 @@ class BaseCmdLineTest(CoverageTest):
def model_object(self):
"""Return a Mock suitable for use in CoverageScript."""
mk = mock.Mock()
- # We'll invoke .coverage as the constructor, and then keep using the
+ # We'll invoke .Coverage as the constructor, and then keep using the
# same object as the resulting coverage object.
- mk.coverage.return_value = mk
+ mk.Coverage.return_value = mk
# The mock needs to get options, but shouldn't need to set them.
config = CoverageConfig()
@@ -73,11 +73,12 @@ class BaseCmdLineTest(CoverageTest):
m = self.model_object()
m.path_exists.return_value = path_exists
- ret = coverage.cmdline.CoverageScript(
+ ret = command_line(
+ args,
_covpkg=m, _run_python_file=m.run_python_file,
_run_python_module=m.run_python_module, _help_fn=m.help_fn,
_path_exists=m.path_exists,
- ).command_line(shlex.split(args))
+ )
return m, ret
@@ -98,7 +99,7 @@ class BaseCmdLineTest(CoverageTest):
# calls them with many. But most of them are just the defaults, which
# we don't want to have to repeat in all tests. For each call, apply
# the defaults. This lets the tests just mention the interesting ones.
- for name, args, kwargs in m2.method_calls:
+ for name, _, kwargs in m2.method_calls:
for k, v in self.DEFAULT_KWARGS.get(name, {}).items():
if k not in kwargs:
kwargs[k] = v
@@ -151,37 +152,37 @@ class CmdLineTest(BaseCmdLineTest):
def test_annotate(self):
# coverage annotate [-d DIR] [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("annotate", """\
- .coverage()
+ .Coverage()
.load()
.annotate()
""")
self.cmd_executes("annotate -d dir1", """\
- .coverage()
+ .Coverage()
.load()
.annotate(directory="dir1")
""")
self.cmd_executes("annotate -i", """\
- .coverage()
+ .Coverage()
.load()
.annotate(ignore_errors=True)
""")
self.cmd_executes("annotate --omit fooey", """\
- .coverage(omit=["fooey"])
+ .Coverage(omit=["fooey"])
.load()
.annotate(omit=["fooey"])
""")
self.cmd_executes("annotate --omit fooey,booey", """\
- .coverage(omit=["fooey", "booey"])
+ .Coverage(omit=["fooey", "booey"])
.load()
.annotate(omit=["fooey", "booey"])
""")
self.cmd_executes("annotate mod1", """\
- .coverage()
+ .Coverage()
.load()
.annotate(morfs=["mod1"])
""")
self.cmd_executes("annotate mod1 mod2 mod3", """\
- .coverage()
+ .Coverage()
.load()
.annotate(morfs=["mod1", "mod2", "mod3"])
""")
@@ -189,20 +190,20 @@ class CmdLineTest(BaseCmdLineTest):
def test_combine(self):
# coverage combine with args
self.cmd_executes("combine datadir1", """\
- .coverage()
+ .Coverage()
.combine(["datadir1"], strict=True)
.save()
""")
# coverage combine, appending
self.cmd_executes("combine --append datadir1", """\
- .coverage()
+ .Coverage()
.load()
.combine(["datadir1"], strict=True)
.save()
""")
# coverage combine without args
self.cmd_executes("combine", """\
- .coverage()
+ .Coverage()
.combine(None, strict=True)
.save()
""")
@@ -210,12 +211,12 @@ class CmdLineTest(BaseCmdLineTest):
def test_combine_doesnt_confuse_options_with_args(self):
# https://bitbucket.org/ned/coveragepy/issues/385/coverage-combine-doesnt-work-with-rcfile
self.cmd_executes("combine --rcfile cov.ini", """\
- .coverage(config_file='cov.ini')
+ .Coverage(config_file='cov.ini')
.combine(None, strict=True)
.save()
""")
self.cmd_executes("combine --rcfile cov.ini data1 data2/more", """\
- .coverage(config_file='cov.ini')
+ .Coverage(config_file='cov.ini')
.combine(["data1", "data2/more"], strict=True)
.save()
""")
@@ -239,7 +240,7 @@ class CmdLineTest(BaseCmdLineTest):
def test_erase(self):
# coverage erase
self.cmd_executes("erase", """\
- .coverage()
+ .Coverage()
.erase()
""")
@@ -262,42 +263,42 @@ class CmdLineTest(BaseCmdLineTest):
def test_html(self):
# coverage html -d DIR [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("html", """\
- .coverage()
+ .Coverage()
.load()
.html_report()
""")
self.cmd_executes("html -d dir1", """\
- .coverage()
+ .Coverage()
.load()
.html_report(directory="dir1")
""")
self.cmd_executes("html -i", """\
- .coverage()
+ .Coverage()
.load()
.html_report(ignore_errors=True)
""")
self.cmd_executes("html --omit fooey", """\
- .coverage(omit=["fooey"])
+ .Coverage(omit=["fooey"])
.load()
.html_report(omit=["fooey"])
""")
self.cmd_executes("html --omit fooey,booey", """\
- .coverage(omit=["fooey", "booey"])
+ .Coverage(omit=["fooey", "booey"])
.load()
.html_report(omit=["fooey", "booey"])
""")
self.cmd_executes("html mod1", """\
- .coverage()
+ .Coverage()
.load()
.html_report(morfs=["mod1"])
""")
self.cmd_executes("html mod1 mod2 mod3", """\
- .coverage()
+ .Coverage()
.load()
.html_report(morfs=["mod1", "mod2", "mod3"])
""")
self.cmd_executes("html --title=Hello_there", """\
- .coverage()
+ .Coverage()
.load()
.html_report(title='Hello_there')
""")
@@ -305,42 +306,42 @@ class CmdLineTest(BaseCmdLineTest):
def test_report(self):
# coverage report [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("report", """\
- .coverage()
+ .Coverage()
.load()
.report(show_missing=None)
""")
self.cmd_executes("report -i", """\
- .coverage()
+ .Coverage()
.load()
.report(ignore_errors=True)
""")
self.cmd_executes("report -m", """\
- .coverage()
+ .Coverage()
.load()
.report(show_missing=True)
""")
self.cmd_executes("report --omit fooey", """\
- .coverage(omit=["fooey"])
+ .Coverage(omit=["fooey"])
.load()
.report(omit=["fooey"])
""")
self.cmd_executes("report --omit fooey,booey", """\
- .coverage(omit=["fooey", "booey"])
+ .Coverage(omit=["fooey", "booey"])
.load()
.report(omit=["fooey", "booey"])
""")
self.cmd_executes("report mod1", """\
- .coverage()
+ .Coverage()
.load()
.report(morfs=["mod1"])
""")
self.cmd_executes("report mod1 mod2 mod3", """\
- .coverage()
+ .Coverage()
.load()
.report(morfs=["mod1", "mod2", "mod3"])
""")
self.cmd_executes("report --skip-covered", """\
- .coverage()
+ .Coverage()
.load()
.report(skip_covered=True)
""")
@@ -350,7 +351,7 @@ class CmdLineTest(BaseCmdLineTest):
# run calls coverage.erase first.
self.cmd_executes("run foo.py", """\
- .coverage()
+ .Coverage()
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -359,7 +360,7 @@ class CmdLineTest(BaseCmdLineTest):
""")
# run -a combines with an existing data file before saving.
self.cmd_executes("run -a foo.py", """\
- .coverage()
+ .Coverage()
.start()
.run_python_file('foo.py', ['foo.py'])
.stop()
@@ -369,7 +370,7 @@ class CmdLineTest(BaseCmdLineTest):
""", path_exists=True)
# run -a doesn't combine anything if the data file doesn't exist.
self.cmd_executes("run -a foo.py", """\
- .coverage()
+ .Coverage()
.start()
.run_python_file('foo.py', ['foo.py'])
.stop()
@@ -378,7 +379,7 @@ class CmdLineTest(BaseCmdLineTest):
""", path_exists=False)
# --timid sets a flag, and program arguments get passed through.
self.cmd_executes("run --timid foo.py abc 123", """\
- .coverage(timid=True)
+ .Coverage(timid=True)
.erase()
.start()
.run_python_file('foo.py', ['foo.py', 'abc', '123'])
@@ -387,7 +388,7 @@ class CmdLineTest(BaseCmdLineTest):
""")
# -L sets a flag, and flags for the program don't confuse us.
self.cmd_executes("run -p -L foo.py -a -b", """\
- .coverage(cover_pylib=True, data_suffix=True)
+ .Coverage(cover_pylib=True, data_suffix=True)
.erase()
.start()
.run_python_file('foo.py', ['foo.py', '-a', '-b'])
@@ -395,7 +396,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --branch foo.py", """\
- .coverage(branch=True)
+ .Coverage(branch=True)
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -403,7 +404,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --rcfile=myrc.rc foo.py", """\
- .coverage(config_file="myrc.rc")
+ .Coverage(config_file="myrc.rc")
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -411,7 +412,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --include=pre1,pre2 foo.py", """\
- .coverage(include=["pre1", "pre2"])
+ .Coverage(include=["pre1", "pre2"])
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -419,7 +420,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --omit=opre1,opre2 foo.py", """\
- .coverage(omit=["opre1", "opre2"])
+ .Coverage(omit=["opre1", "opre2"])
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -427,7 +428,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --include=pre1,pre2 --omit=opre1,opre2 foo.py", """\
- .coverage(include=["pre1", "pre2"], omit=["opre1", "opre2"])
+ .Coverage(include=["pre1", "pre2"], omit=["opre1", "opre2"])
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -435,7 +436,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --source=quux,hi.there,/home/bar foo.py", """\
- .coverage(source=["quux", "hi.there", "/home/bar"])
+ .Coverage(source=["quux", "hi.there", "/home/bar"])
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -443,7 +444,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --concurrency=gevent foo.py", """\
- .coverage(concurrency='gevent')
+ .Coverage(concurrency='gevent')
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -451,7 +452,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --concurrency=multiprocessing foo.py", """\
- .coverage(concurrency='multiprocessing')
+ .Coverage(concurrency='multiprocessing')
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -461,16 +462,16 @@ class CmdLineTest(BaseCmdLineTest):
def test_bad_concurrency(self):
self.command_line("run --concurrency=nothing", ret=ERR)
- out = self.stdout()
- self.assertIn("option --concurrency: invalid choice: 'nothing'", out)
+ err = self.stderr()
+ self.assertIn("option --concurrency: invalid choice: 'nothing'", err)
def test_no_multiple_concurrency(self):
# You can't use multiple concurrency values on the command line.
# I would like to have a better message about not allowing multiple
# values for this option, but optparse is not that flexible.
self.command_line("run --concurrency=multiprocessing,gevent foo.py", ret=ERR)
- out = self.stdout()
- self.assertIn("option --concurrency: invalid choice: 'multiprocessing,gevent'", out)
+ err = self.stderr()
+ self.assertIn("option --concurrency: invalid choice: 'multiprocessing,gevent'", err)
def test_multiprocessing_needs_config_file(self):
# You can't use command-line args to add options to multiprocessing
@@ -479,12 +480,12 @@ class CmdLineTest(BaseCmdLineTest):
self.command_line("run --concurrency=multiprocessing --branch foo.py", ret=ERR)
self.assertIn(
"Options affecting multiprocessing must be specified in a configuration file.",
- self.stdout()
+ self.stderr()
)
def test_run_debug(self):
self.cmd_executes("run --debug=opt1 foo.py", """\
- .coverage(debug=["opt1"])
+ .Coverage(debug=["opt1"])
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -492,7 +493,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --debug=opt1,opt2 foo.py", """\
- .coverage(debug=["opt1","opt2"])
+ .Coverage(debug=["opt1","opt2"])
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -502,7 +503,7 @@ class CmdLineTest(BaseCmdLineTest):
def test_run_module(self):
self.cmd_executes("run -m mymodule", """\
- .coverage()
+ .Coverage()
.erase()
.start()
.run_python_module('mymodule', ['mymodule'])
@@ -510,7 +511,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run -m mymodule -qq arg1 arg2", """\
- .coverage()
+ .Coverage()
.erase()
.start()
.run_python_module('mymodule', ['mymodule', '-qq', 'arg1', 'arg2'])
@@ -518,7 +519,7 @@ class CmdLineTest(BaseCmdLineTest):
.save()
""")
self.cmd_executes("run --branch -m mymodule", """\
- .coverage(branch=True)
+ .Coverage(branch=True)
.erase()
.start()
.run_python_module('mymodule', ['mymodule'])
@@ -529,51 +530,51 @@ class CmdLineTest(BaseCmdLineTest):
def test_run_nothing(self):
self.command_line("run", ret=ERR)
- self.assertIn("Nothing to do", self.stdout())
+ self.assertIn("Nothing to do", self.stderr())
def test_cant_append_parallel(self):
self.command_line("run --append --parallel-mode foo.py", ret=ERR)
- self.assertIn("Can't append to data files in parallel mode.", self.stdout())
+ self.assertIn("Can't append to data files in parallel mode.", self.stderr())
def test_xml(self):
# coverage xml [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("xml", """\
- .coverage()
+ .Coverage()
.load()
.xml_report()
""")
self.cmd_executes("xml -i", """\
- .coverage()
+ .Coverage()
.load()
.xml_report(ignore_errors=True)
""")
self.cmd_executes("xml -o myxml.foo", """\
- .coverage()
+ .Coverage()
.load()
.xml_report(outfile="myxml.foo")
""")
self.cmd_executes("xml -o -", """\
- .coverage()
+ .Coverage()
.load()
.xml_report(outfile="-")
""")
self.cmd_executes("xml --omit fooey", """\
- .coverage(omit=["fooey"])
+ .Coverage(omit=["fooey"])
.load()
.xml_report(omit=["fooey"])
""")
self.cmd_executes("xml --omit fooey,booey", """\
- .coverage(omit=["fooey", "booey"])
+ .Coverage(omit=["fooey", "booey"])
.load()
.xml_report(omit=["fooey", "booey"])
""")
self.cmd_executes("xml mod1", """\
- .coverage()
+ .Coverage()
.load()
.xml_report(morfs=["mod1"])
""")
self.cmd_executes("xml mod1 mod2 mod3", """\
- .coverage()
+ .Coverage()
.load()
.xml_report(morfs=["mod1", "mod2", "mod3"])
""")
@@ -661,9 +662,9 @@ class CmdLineStdoutTest(BaseCmdLineTest):
def test_error(self):
self.command_line("fooey kablooey", ret=ERR)
- out = self.stdout()
- self.assertIn("fooey", out)
- self.assertIn("help", out)
+ err = self.stderr()
+ self.assertIn("fooey", err)
+ self.assertIn("help", err)
class CmdMainTest(CoverageTest):
@@ -693,13 +694,9 @@ class CmdMainTest(CoverageTest):
def setUp(self):
super(CmdMainTest, self).setUp()
- self.old_CoverageScript = coverage.cmdline.CoverageScript
+ old_CoverageScript = coverage.cmdline.CoverageScript
coverage.cmdline.CoverageScript = self.CoverageScriptStub
- self.addCleanup(self.cleanup_coverage_script)
-
- def cleanup_coverage_script(self):
- """Restore CoverageScript when the test is done."""
- coverage.cmdline.CoverageScript = self.old_CoverageScript
+ self.addCleanup(setattr, coverage.cmdline, 'CoverageScript', old_CoverageScript)
def test_normal(self):
ret = coverage.cmdline.main(['hello'])
@@ -722,3 +719,59 @@ class CmdMainTest(CoverageTest):
def test_exit(self):
ret = coverage.cmdline.main(['exit'])
self.assertEqual(ret, 23)
+
+
+class CoverageReportingFake(object):
+ """A fake Coverage and Coverage.coverage test double."""
+ # pylint: disable=missing-docstring
+ def __init__(self, report_result, html_result, xml_result):
+ self.report_result = report_result
+ self.html_result = html_result
+ self.xml_result = xml_result
+
+ def Coverage(self, *args_unused, **kwargs_unused):
+ return self
+
+ def set_option(self, optname, optvalue):
+ setattr(self, optname, optvalue)
+
+ def get_option(self, optname):
+ return getattr(self, optname)
+
+ def load(self):
+ pass
+
+ def report(self, *args_unused, **kwargs_unused):
+ return self.report_result
+
+ def html_report(self, *args_unused, **kwargs_unused):
+ return self.html_result
+
+ def xml_report(self, *args_unused, **kwargs_unused):
+ return self.xml_result
+
+
+@pytest.mark.parametrize("results, fail_under, cmd, ret", [
+ # Command-line switch properly checks the result of reporting functions.
+ ((20, 30, 40), None, "report --fail-under=19", 0),
+ ((20, 30, 40), None, "report --fail-under=21", 2),
+ ((20, 30, 40), None, "html --fail-under=29", 0),
+ ((20, 30, 40), None, "html --fail-under=31", 2),
+ ((20, 30, 40), None, "xml --fail-under=39", 0),
+ ((20, 30, 40), None, "xml --fail-under=41", 2),
+ # Configuration file setting properly checks the result of reporting.
+ ((20, 30, 40), 19, "report", 0),
+ ((20, 30, 40), 21, "report", 2),
+ ((20, 30, 40), 29, "html", 0),
+ ((20, 30, 40), 31, "html", 2),
+ ((20, 30, 40), 39, "xml", 0),
+ ((20, 30, 40), 41, "xml", 2),
+ # Command-line overrides configuration.
+ ((20, 30, 40), 19, "report --fail-under=21", 2),
+])
+def test_fail_under(results, fail_under, cmd, ret):
+ cov = CoverageReportingFake(*results)
+ if fail_under:
+ cov.set_option("report:fail_under", fail_under)
+ ret_actual = command_line(cmd, _covpkg=cov)
+ assert ret_actual == ret
diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py
index e36db30..841b5df 100644
--- a/tests/test_concurrency.py
+++ b/tests/test_concurrency.py
@@ -3,9 +3,10 @@
"""Tests for concurrency libraries."""
-import multiprocessing
import threading
+from flaky import flaky
+
import coverage
from coverage import env
from coverage.files import abs_file
@@ -16,6 +17,11 @@ from tests.coveragetest import CoverageTest
# These libraries aren't always available, we'll skip tests if they aren't.
try:
+ import multiprocessing
+except ImportError: # pragma: only jython
+ multiprocessing = None
+
+try:
import eventlet
except ImportError:
eventlet = None
@@ -25,7 +31,10 @@ try:
except ImportError:
gevent = None
-import greenlet
+try:
+ import greenlet
+except ImportError: # pragma: only jython
+ greenlet = None
def measurable_line(l):
@@ -40,6 +49,9 @@ def measurable_line(l):
return False
if l.startswith('else:'):
return False
+ if env.JYTHON and l.startswith(('try:', 'except:', 'except ', 'break', 'with ')):
+ # Jython doesn't measure these statements.
+ return False # pragma: only jython
return True
@@ -342,9 +354,15 @@ MULTI_CODE = """
"""
+@flaky # Sometimes a test fails due to inherent randomness. Try one more time.
class MultiprocessingTest(CoverageTest):
"""Test support of the multiprocessing module."""
+ def setUp(self):
+ super(MultiprocessingTest, self).setUp()
+ if not multiprocessing:
+ self.skipTest("No multiprocessing in this Python") # pragma: only jython
+
def try_multiprocessing_code(
self, code, expected_out, the_module, concurrency="multiprocessing"
):
@@ -353,6 +371,7 @@ class MultiprocessingTest(CoverageTest):
self.make_file(".coveragerc", """\
[run]
concurrency = %s
+ source = .
""" % concurrency)
if env.PYVERSION >= (3, 4):
diff --git a/tests/test_config.py b/tests/test_config.py
index cf8a6a7..9224046 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -95,6 +95,15 @@ class ConfigTest(CoverageTest):
cov = coverage.Coverage(data_file="fromarg.dat")
self.assertEqual(cov.config.data_file, "fromarg.dat")
+ def test_debug_from_environment(self):
+ self.make_file(".coveragerc", """\
+ [run]
+ debug = dataio, pids
+ """)
+ self.set_environ("COVERAGE_DEBUG", "callers, fooey")
+ cov = coverage.Coverage()
+ self.assertEqual(cov.config.debug, ["dataio", "pids", "callers", "fooey"])
+
def test_parse_errors(self):
# Im-parsable values raise CoverageException, with details.
bad_configs_and_msgs = [
@@ -206,7 +215,7 @@ class ConfigTest(CoverageTest):
[coverage:run]
huh = what?
""")
- msg = r"Unrecognized option '\[coverage:run\] huh=' in config file setup.cfg"
+ msg = (r"Unrecognized option '\[coverage:run\] huh=' in config file setup.cfg")
with self.assertRaisesRegex(CoverageException, msg):
_ = coverage.Coverage()
@@ -230,12 +239,13 @@ class ConfigFileTest(CoverageTest):
branch = 1
cover_pylib = TRUE
parallel = on
- include = a/ , b/
concurrency = thread
source = myapp
plugins =
plugins.a_plugin
plugins.another
+ debug = callers, pids , dataio
+ disable_warnings = abcd , efgh
[{section}report]
; these settings affect reporting.
@@ -294,19 +304,32 @@ class ConfigFileTest(CoverageTest):
examples/
"""
+ # Just some sample tox.ini text from the docs.
+ TOX_INI = """\
+ [tox]
+ envlist = py{26,27,33,34,35}-{c,py}tracer
+ skip_missing_interpreters = True
+
+ [testenv]
+ commands =
+ # Create tests/zipmods.zip, install the egg1 egg
+ python igor.py zip_mods install_egg
+ """
+
def assert_config_settings_are_correct(self, cov):
"""Check that `cov` has all the settings from LOTSA_SETTINGS."""
self.assertTrue(cov.config.timid)
self.assertEqual(cov.config.data_file, "something_or_other.dat")
self.assertTrue(cov.config.branch)
self.assertTrue(cov.config.cover_pylib)
+ self.assertEqual(cov.config.debug, ["callers", "pids", "dataio"])
self.assertTrue(cov.config.parallel)
self.assertEqual(cov.config.concurrency, ["thread"])
self.assertEqual(cov.config.source, ["myapp"])
+ self.assertEqual(cov.config.disable_warnings, ["abcd", "efgh"])
self.assertEqual(cov.get_exclude_list(), ["if 0:", r"pragma:?\s+no cover", "another_tab"])
self.assertTrue(cov.config.ignore_errors)
- self.assertEqual(cov.config.include, ["a/", "b/"])
self.assertEqual(cov.config.omit, ["one", "another", "some_more", "yet_more"])
self.assertEqual(cov.config.precision, 3)
@@ -338,29 +361,39 @@ class ConfigFileTest(CoverageTest):
cov = coverage.Coverage()
self.assert_config_settings_are_correct(cov)
- def test_config_file_settings_in_setupcfg(self):
- # Configuration will be read from setup.cfg from sections prefixed with
- # "coverage:"
+ def check_config_file_settings_in_other_file(self, fname, contents):
+ """Check config will be read from another file, with prefixed sections."""
nested = self.LOTSA_SETTINGS.format(section="coverage:")
- self.make_file("setup.cfg", nested + "\n" + self.SETUP_CFG)
+ fname = self.make_file(fname, nested + "\n" + contents)
cov = coverage.Coverage()
self.assert_config_settings_are_correct(cov)
- def test_config_file_settings_in_setupcfg_if_coveragerc_specified(self):
- # Configuration will be read from setup.cfg from sections prefixed with
- # "coverage:", even if the API said to read from a (non-existent)
- # .coveragerc file.
+ def test_config_file_settings_in_setupcfg(self):
+ self.check_config_file_settings_in_other_file("setup.cfg", self.SETUP_CFG)
+
+ def test_config_file_settings_in_toxini(self):
+ self.check_config_file_settings_in_other_file("tox.ini", self.TOX_INI)
+
+ def check_other_config_if_coveragerc_specified(self, fname, contents):
+ """Check that config `fname` is read if .coveragerc is missing, but specified."""
nested = self.LOTSA_SETTINGS.format(section="coverage:")
- self.make_file("setup.cfg", nested + "\n" + self.SETUP_CFG)
+ self.make_file(fname, nested + "\n" + contents)
cov = coverage.Coverage(config_file=".coveragerc")
self.assert_config_settings_are_correct(cov)
- def test_setupcfg_only_if_not_coveragerc(self):
+ def test_config_file_settings_in_setupcfg_if_coveragerc_specified(self):
+ self.check_other_config_if_coveragerc_specified("setup.cfg", self.SETUP_CFG)
+
+ def test_config_file_settings_in_tox_if_coveragerc_specified(self):
+ self.check_other_config_if_coveragerc_specified("tox.ini", self.TOX_INI)
+
+ def check_other_not_read_if_coveragerc(self, fname):
+ """Check config `fname` is not read if .coveragerc exists."""
self.make_file(".coveragerc", """\
[run]
include = foo
""")
- self.make_file("setup.cfg", """\
+ self.make_file(fname, """\
[coverage:run]
omit = bar
branch = true
@@ -370,8 +403,15 @@ class ConfigFileTest(CoverageTest):
self.assertEqual(cov.config.omit, None)
self.assertEqual(cov.config.branch, False)
- def test_setupcfg_only_if_prefixed(self):
- self.make_file("setup.cfg", """\
+ def test_setupcfg_only_if_not_coveragerc(self):
+ self.check_other_not_read_if_coveragerc("setup.cfg")
+
+ def test_toxini_only_if_not_coveragerc(self):
+ self.check_other_not_read_if_coveragerc("tox.ini")
+
+ def check_other_config_need_prefixes(self, fname):
+ """Check that `fname` sections won't be read if un-prefixed."""
+ self.make_file(fname, """\
[run]
omit = bar
branch = true
@@ -380,6 +420,21 @@ class ConfigFileTest(CoverageTest):
self.assertEqual(cov.config.omit, None)
self.assertEqual(cov.config.branch, False)
+ def test_setupcfg_only_if_prefixed(self):
+ self.check_other_config_need_prefixes("setup.cfg")
+
+ def test_toxini_only_if_prefixed(self):
+ self.check_other_config_need_prefixes("tox.ini")
+
+ def test_tox_ini_even_if_setup_cfg(self):
+ # There's a setup.cfg, but no coverage settings in it, so tox.ini
+ # is read.
+ nested = self.LOTSA_SETTINGS.format(section="coverage:")
+ self.make_file("tox.ini", self.TOX_INI + "\n" + nested)
+ self.make_file("setup.cfg", self.SETUP_CFG)
+ cov = coverage.Coverage()
+ self.assert_config_settings_are_correct(cov)
+
def test_non_ascii(self):
self.make_file(".coveragerc", """\
[report]
diff --git a/tests/test_coverage.py b/tests/test_coverage.py
index a52aced..bda61fc 100644
--- a/tests/test_coverage.py
+++ b/tests/test_coverage.py
@@ -418,7 +418,7 @@ class SimpleStatementTest(CoverageTest):
""",
[1,2,3,4,5], "4")
- def test_strange_unexecuted_continue(self):
+ def test_strange_unexecuted_continue(self): # pragma: not covered
# Peephole optimization of jumps to jumps can mean that some statements
# never hit the line tracer. The behavior is different in different
# versions of Python, so don't run this test:
@@ -567,7 +567,7 @@ class SimpleStatementTest(CoverageTest):
def test_nonascii(self):
self.check_coverage("""\
- # coding: utf8
+ # coding: utf-8
a = 2
b = 3
""",
diff --git a/tests/test_data.py b/tests/test_data.py
index 4bccdcf..46999f6 100644
--- a/tests/test_data.py
+++ b/tests/test_data.py
@@ -13,10 +13,11 @@ import mock
from coverage.backward import StringIO
from coverage.data import CoverageData, CoverageDataFiles, debug_main, canonicalize_json_data
+from coverage.debug import DebugControlString
from coverage.files import PathAliases, canonical_filename
from coverage.misc import CoverageException
-from tests.coveragetest import CoverageTest, DebugControlString
+from tests.coveragetest import CoverageTest
LINES_1 = {
@@ -554,9 +555,7 @@ class CoverageDataFilesTest(DataTestHelpers, CoverageTest):
self.assertRegex(
debug.get_output(),
- r"^Creating CoverageData object\n"
- r"Writing data to '.*\.coverage'\n"
- r"Creating CoverageData object\n"
+ r"^Writing data to '.*\.coverage'\n"
r"Reading data from '.*\.coverage'\n$"
)
diff --git a/tests/test_debug.py b/tests/test_debug.py
index 2d553ee..f733d72 100644
--- a/tests/test_debug.py
+++ b/tests/test_debug.py
@@ -4,13 +4,15 @@
"""Tests of coverage/debug.py"""
import os
-import re
+
+import pytest
import coverage
from coverage.backward import StringIO
-from coverage.debug import info_formatter, info_header, short_stack
+from coverage.debug import filter_text, info_formatter, info_header, short_id, short_stack
from tests.coveragetest import CoverageTest
+from tests.helpers import re_lines
class InfoFormatterTest(CoverageTest):
@@ -39,15 +41,34 @@ class InfoFormatterTest(CoverageTest):
lines = list(info_formatter(('info%d' % i, i) for i in range(3)))
self.assertEqual(lines, ['info0: 0', 'info1: 1', 'info2: 2'])
- def test_info_header(self):
- self.assertEqual(
- info_header("x"),
- "-- x ---------------------------------------------------------"
- )
- self.assertEqual(
- info_header("hello there"),
- "-- hello there -----------------------------------------------"
- )
+
+@pytest.mark.parametrize("label, header", [
+ ("x", "-- x ---------------------------------------------------------"),
+ ("hello there", "-- hello there -----------------------------------------------"),
+])
+def test_info_header(label, header):
+ assert info_header(label) == header
+
+
+@pytest.mark.parametrize("id64, id16", [
+ (0x1234, 0x1234),
+ (0x12340000, 0x1234),
+ (0xA5A55A5A, 0xFFFF),
+ (0x1234cba956780fed, 0x8008),
+])
+def test_short_id(id64, id16):
+ assert short_id(id64) == id16
+
+
+@pytest.mark.parametrize("text, filters, result", [
+ ("hello", [], "hello"),
+ ("hello\n", [], "hello\n"),
+ ("hello\nhello\n", [], "hello\nhello\n"),
+ ("hello\nbye\n", [lambda x: "="+x], "=hello\n=bye\n"),
+ ("hello\nbye\n", [lambda x: "="+x, lambda x: x+"\ndone\n"], "=hello\ndone\n=bye\ndone\n"),
+])
+def test_filter_text(text, filters, result):
+ assert filter_text(text, filters) == result
class DebugTraceTest(CoverageTest):
@@ -70,7 +91,7 @@ class DebugTraceTest(CoverageTest):
self.start_import_stop(cov, "f1")
cov.save()
- out_lines = debug_out.getvalue().splitlines()
+ out_lines = debug_out.getvalue()
return out_lines
def test_debug_no_trace(self):
@@ -86,7 +107,7 @@ class DebugTraceTest(CoverageTest):
self.assertIn("Tracing 'f1.py'", out_lines)
# We should have lines like "Not tracing 'collector.py'..."
- coverage_lines = lines_matching(
+ coverage_lines = re_lines(
out_lines,
r"^Not tracing .*: is part of coverage.py$"
)
@@ -96,28 +117,29 @@ class DebugTraceTest(CoverageTest):
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)
+ pid_prefix = r"^%5d\.[0-9a-f]{4}: " % os.getpid()
+ pid_lines = re_lines(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 "))
+ self.assertTrue(re_lines(out_lines, pid_prefix + "Tracing "))
+ self.assertTrue(re_lines(out_lines, pid_prefix + "Not tracing "))
def test_debug_callers(self):
out_lines = self.f1_debug_output(["pid", "dataop", "dataio", "callers"])
- print("\n".join(out_lines))
+ print(out_lines)
# For every real message, there should be a stack
# trace with a line like "f1_debug_output : /Users/ned/coverage/tests/test_debug.py @71"
- real_messages = lines_matching(out_lines, r"^pid\s+\d+: ")
+ real_messages = re_lines(out_lines, r" @\d+", match=False).splitlines()
frame_pattern = r"\s+f1_debug_output : .*tests[/\\]test_debug.py @\d+$"
- frames = lines_matching(out_lines, frame_pattern)
+ frames = re_lines(out_lines, frame_pattern).splitlines()
self.assertEqual(len(real_messages), len(frames))
# The last message should be "Writing data", and the last frame should
# be write_file in data.py.
- self.assertRegex(real_messages[-1], r"^pid\s+\d+: Writing data")
- self.assertRegex(out_lines[-1], r"\s+write_file : .*coverage[/\\]data.py @\d+$")
+ self.assertRegex(real_messages[-1], r"^\s*\d+\.\w{4}: Writing data")
+ last_line = out_lines.splitlines()[-1]
+ self.assertRegex(last_line, r"\s+write_file : .*coverage[/\\]data.py @\d+$")
def test_debug_config(self):
out_lines = self.f1_debug_output(["config"])
@@ -130,20 +152,23 @@ class DebugTraceTest(CoverageTest):
""".split()
for label in labels:
label_pat = r"^\s*%s: " % label
- self.assertEqual(len(lines_matching(out_lines, label_pat)), 1)
+ self.assertEqual(
+ len(re_lines(out_lines, label_pat).splitlines()),
+ 1
+ )
def test_debug_sys(self):
out_lines = self.f1_debug_output(["sys"])
labels = """
- version coverage cover_dirs pylib_dirs tracer config_files
+ version coverage cover_paths pylib_paths 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)),
+ len(re_lines(out_lines, label_pat).splitlines()),
1,
msg="Incorrect lines for %r" % label,
)
@@ -181,8 +206,3 @@ class ShortStackTest(CoverageTest):
def test_short_stack_skip(self):
stack = f_one(skip=1).splitlines()
self.assertIn("f_two", stack[-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)]
diff --git a/tests/test_execfile.py b/tests/test_execfile.py
index 889d6cf..bad3da9 100644
--- a/tests/test_execfile.py
+++ b/tests/test_execfile.py
@@ -10,6 +10,7 @@ import os.path
import re
import sys
+from coverage import env
from coverage.backward import binary_bytes
from coverage.execfile import run_python_file, run_python_module
from coverage.misc import NoCode, NoSource
@@ -43,7 +44,8 @@ class RunFileTest(CoverageTest):
self.assertEqual(mod_globs['__main__.DATA'], "xyzzy")
# Argv should have the proper values.
- self.assertEqual(mod_globs['argv'], [TRY_EXECFILE, "arg1", "arg2"])
+ self.assertEqual(mod_globs['argv0'], TRY_EXECFILE)
+ self.assertEqual(mod_globs['argv1-n'], ["arg1", "arg2"])
# __builtins__ should have the right values, like open().
self.assertEqual(mod_globs['__builtins__.has_open'], True)
@@ -102,6 +104,9 @@ class RunPycFileTest(CoverageTest):
def make_pyc(self):
"""Create a .pyc file, and return the relative path to it."""
+ if env.JYTHON:
+ self.skipTest("Can't make .pyc files on Jython")
+
self.make_file("compiled.py", """\
def doit():
print("I am here!")
@@ -145,6 +150,25 @@ class RunPycFileTest(CoverageTest):
with self.assertRaisesRegex(NoCode, "No file to run: 'xyzzy.pyc'"):
run_python_file("xyzzy.pyc", [])
+ def test_running_py_from_binary(self):
+ # Use make_file to get the bookkeeping. Ideally, it would
+ # be able to write binary files.
+ bf = self.make_file("binary")
+ with open(bf, "wb") as f:
+ f.write(b'\x7fELF\x02\x01\x01\x00\x00\x00')
+
+ msg = (
+ r"Couldn't run 'binary' as Python code: "
+ r"(TypeError|ValueError): "
+ r"("
+ r"compile\(\) expected string without null bytes" # for py2
+ r"|"
+ r"source code string cannot contain null bytes" # for py3
+ r")"
+ )
+ with self.assertRaisesRegex(Exception, msg):
+ run_python_file(bf, [bf])
+
class RunModuleTest(CoverageTest):
"""Test run_python_module."""
diff --git a/tests/test_farm.py b/tests/test_farm.py
index ae9e915..1b52bc2 100644
--- a/tests/test_farm.py
+++ b/tests/test_farm.py
@@ -1,7 +1,7 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
-"""Run tests in the farm sub-directory. Designed for nose."""
+"""Run tests in the farm sub-directory. Designed for pytest."""
import difflib
import filecmp
@@ -11,22 +11,28 @@ import os
import re
import shutil
import sys
-import unittest
-from nose.plugins.skip import SkipTest
+import pytest
-from unittest_mixins import ModuleAwareMixin, SysPathAwareMixin, change_dir, saved_sys_path
+from unittest_mixins import ModuleAwareMixin, SysPathAwareMixin, change_dir
from tests.helpers import run_command
from tests.backtest import execfile # pylint: disable=redefined-builtin
+from coverage import env
+from coverage.backunittest import unittest
from coverage.debug import _TEST_NAME_FILE
-def test_farm(clean_only=False):
- """A test-generating function for nose to find and run."""
- for fname in glob.glob("tests/farm/*/*.py"):
- case = FarmTestCase(fname, clean_only)
- yield (case,)
+# Look for files that become tests.
+TEST_FILES = glob.glob("tests/farm/*/*.py")
+
+
+@pytest.mark.parametrize("filename", TEST_FILES)
+def test_farm(filename):
+ if env.JYTHON:
+ # All of the farm tests use reporting, so skip them all.
+ skip("Farm tests don't run on Jython")
+ FarmTestCase(filename).run_fully()
# "rU" was deprecated in 3.4
@@ -51,8 +57,7 @@ class FarmTestCase(ModuleAwareMixin, SysPathAwareMixin, unittest.TestCase):
cleaning-only, or run and leave the results for debugging).
This class is a unittest.TestCase so that we can use behavior-modifying
- mixins, but it's only useful as a nose test function. Yes, this is
- confusing.
+ mixins, but it's only useful as a test function. Yes, this is confusing.
"""
@@ -75,38 +80,38 @@ class FarmTestCase(ModuleAwareMixin, SysPathAwareMixin, unittest.TestCase):
self.ok = True
def setUp(self):
- """Test set up, run by nose before __call__."""
+ """Test set up, run by the test runner before __call__."""
super(FarmTestCase, self).setUp()
# Modules should be importable from the current directory.
sys.path.insert(0, '')
def tearDown(self):
- """Test tear down, run by nose after __call__."""
+ """Test tear down, run by the test runner after __call__."""
# Make sure the test is cleaned up, unless we never want to, or if the
# test failed.
- if not self.dont_clean and self.ok: # pragma: part covered
+ if not self.dont_clean and self.ok: # pragma: part covered
self.clean_only = True
self()
super(FarmTestCase, self).tearDown()
- # This object will be run by nose via the __call__ method, and nose
- # doesn't do cleanups in that case. Do them now.
+ # This object will be run via the __call__ method, and test runners
+ # don't do cleanups in that case. Do them now.
self.doCleanups()
- def runTest(self):
+ def runTest(self): # pragma: not covered
"""Here to make unittest.TestCase happy, but will never be invoked."""
raise Exception("runTest isn't used in this class!")
def __call__(self):
"""Execute the test from the run.py file."""
- if _TEST_NAME_FILE: # pragma: debugging
+ if _TEST_NAME_FILE: # pragma: debugging
with open(_TEST_NAME_FILE, "w") as f:
f.write(self.description.replace("/", "_"))
# Prepare a dictionary of globals for the run.py files to use.
fns = """
- copy run runfunc clean skip
+ copy run clean skip
compare contains contains_any doesnt_contain
""".split()
if self.clean_only:
@@ -114,7 +119,7 @@ class FarmTestCase(ModuleAwareMixin, SysPathAwareMixin, unittest.TestCase):
glo['clean'] = clean
else:
glo = dict((fn, globals()[fn]) for fn in fns)
- if self.dont_clean: # pragma: not covered
+ if self.dont_clean: # pragma: debugging
glo['clean'] = noop
with change_dir(self.dir):
@@ -124,7 +129,7 @@ class FarmTestCase(ModuleAwareMixin, SysPathAwareMixin, unittest.TestCase):
self.ok = False
raise
- def run_fully(self): # pragma: not covered
+ def run_fully(self):
"""Run as a full test case, with setUp and tearDown."""
self.setUp()
try:
@@ -142,8 +147,6 @@ def noop(*args_unused, **kwargs_unused):
def copy(src, dst):
"""Copy a directory."""
- if os.path.exists(dst):
- shutil.rmtree(dst)
shutil.copytree(src, dst)
@@ -168,37 +171,15 @@ def run(cmds, rundir="src", outfile=None):
if outfile:
fout.write(output)
if retcode:
- raise Exception("command exited abnormally")
+ raise Exception("command exited abnormally") # pragma: only failure
finally:
if outfile:
fout.close()
-def runfunc(fn, rundir="src", addtopath=None):
- """Run a function.
-
- `fn` is a callable.
- `rundir` is the directory in which to run the function.
-
- """
- with change_dir(rundir):
- with saved_sys_path():
- if addtopath is not None:
- sys.path.insert(0, addtopath)
- fn()
-
-
-def compare(
- dir1, dir2, file_pattern=None, size_within=0,
- left_extra=False, right_extra=False, scrubs=None
-):
+def compare(dir1, dir2, file_pattern=None, size_within=0, left_extra=False, scrubs=None):
"""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.
-
`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
often be different), but the file sizes must be within this amount.
@@ -206,8 +187,7 @@ def compare(
within 10 percent of each other to compare equal.
`left_extra` true means the left directory can have extra files in it
- without triggering an assertion. `right_extra` means the right
- directory can.
+ without triggering an assertion.
`scrubs` is a list of pairs, regexes to find and literal strings to
replace them with to scrub the files of unimportant differences.
@@ -216,15 +196,6 @@ def compare(
matches.
"""
- # Search for a dir2 with a version suffix.
- version_suff = ''.join(map(str, sys.version_info[:3]))
- while version_suff:
- trydir = dir2 + '_v' + version_suff
- if os.path.exists(trydir):
- dir2 = trydir
- break
- version_suff = version_suff[:-1]
-
assert os.path.exists(dir1), "Left directory missing: %s" % dir1
assert os.path.exists(dir2), "Right directory missing: %s" % dir2
@@ -248,9 +219,9 @@ def compare(
if (big - little) / float(little) > size_within/100.0:
# print "%d %d" % (big, little)
# print "Left: ---\n%s\n-----\n%s" % (left, right)
- wrong_size.append("%s (%s,%s)" % (f, size_l, size_r))
+ wrong_size.append("%s (%s,%s)" % (f, size_l, size_r)) # pragma: only failure
if wrong_size:
- print("File sizes differ between %s and %s: %s" % (
+ print("File sizes differ between %s and %s: %s" % ( # pragma: only failure
dir1, dir2, ", ".join(wrong_size)
))
@@ -270,7 +241,7 @@ def compare(
if scrubs:
left = scrub(left, scrubs)
right = scrub(right, scrubs)
- if left != right:
+ if left != right: # pragma: only failure
text_diff.append(f)
left = left.splitlines()
right = right.splitlines()
@@ -279,8 +250,7 @@ def compare(
if not left_extra:
assert not left_only, "Files in %s only: %s" % (dir1, left_only)
- if not right_extra:
- assert not right_only, "Files in %s only: %s" % (dir2, right_only)
+ assert not right_only, "Files in %s only: %s" % (dir2, right_only)
def contains(filename, *strlist):
@@ -308,7 +278,10 @@ def contains_any(filename, *strlist):
for s in strlist:
if s in text:
return
- assert False, "Missing content in %s: %r [1 of %d]" % (filename, strlist[0], len(strlist),)
+
+ assert False, ( # pragma: only failure
+ "Missing content in %s: %r [1 of %d]" % (filename, strlist[0], len(strlist),)
+ )
def doesnt_contain(filename, *strlist):
@@ -334,7 +307,7 @@ def clean(cleandir):
if os.path.exists(cleandir):
try:
shutil.rmtree(cleandir)
- except OSError: # pragma: not covered
+ except OSError: # pragma: cant happen
if tries == 1:
raise
else:
@@ -345,7 +318,7 @@ def clean(cleandir):
def skip(msg=None):
"""Skip the current test."""
- raise SkipTest(msg)
+ raise unittest.SkipTest(msg)
# Helpers
@@ -371,12 +344,12 @@ def scrub(strdata, scrubs):
"""
for rgx_find, rgx_replace in scrubs:
- strdata = re.sub(rgx_find, re.escape(rgx_replace), strdata)
+ strdata = re.sub(rgx_find, rgx_replace.replace("\\", "\\\\"), strdata)
return strdata
-def main(): # pragma: not covered
- """Command-line access to test_farm.
+def main(): # pragma: debugging
+ """Command-line access to farm tests.
Commands:
@@ -392,21 +365,19 @@ def main(): # pragma: not covered
if op == 'run':
# Run the test for real.
- for test_case in sys.argv[2:]:
- case = FarmTestCase(test_case)
- case.run_fully()
+ for filename in sys.argv[2:]:
+ FarmTestCase(filename).run_fully()
elif op == 'out':
# Run the test, but don't clean up, so we can examine the output.
- for test_case in sys.argv[2:]:
- case = FarmTestCase(test_case, dont_clean=True)
- case.run_fully()
+ for filename in sys.argv[2:]:
+ FarmTestCase(filename, dont_clean=True).run_fully()
elif op == 'clean':
# Run all the tests, but just clean.
- for test in test_farm(clean_only=True):
- test[0].run_fully()
+ for filename in TEST_FILES:
+ FarmTestCase(filename, clean_only=True).run_fully()
else:
print(main.__doc__)
# So that we can run just one farm run.py at a time.
-if __name__ == '__main__':
+if __name__ == '__main__': # pragma: debugging
main()
diff --git a/tests/test_files.py b/tests/test_files.py
index 2d22730..dadb22b 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -38,7 +38,7 @@ class FilesTest(CoverageTest):
a1 = self.abs_path("sub/proj1/file1.py")
a2 = self.abs_path("sub/proj2/file2.py")
d = os.path.normpath("sub/proj1")
- os.chdir(d)
+ self.chdir(d)
files.set_relative_directory()
self.assertEqual(files.relative_filename(a1), "file1.py")
self.assertEqual(files.relative_filename(a2), a2)
@@ -163,18 +163,18 @@ class PathAliasesTest(CoverageTest):
"""
self.assertEqual(aliases.map(inp), files.canonical_filename(out))
- def assert_not_mapped(self, aliases, inp):
+ def assert_unchanged(self, aliases, inp):
"""Assert that `inp` mapped through `aliases` is unchanged."""
self.assertEqual(aliases.map(inp), inp)
def test_noop(self):
aliases = PathAliases()
- self.assert_not_mapped(aliases, '/ned/home/a.py')
+ self.assert_unchanged(aliases, '/ned/home/a.py')
def test_nomatch(self):
aliases = PathAliases()
aliases.add('/home/*/src', './mysrc')
- self.assert_not_mapped(aliases, '/home/foo/a.py')
+ self.assert_unchanged(aliases, '/home/foo/a.py')
def test_wildcard(self):
aliases = PathAliases()
@@ -188,7 +188,7 @@ class PathAliasesTest(CoverageTest):
def test_no_accidental_match(self):
aliases = PathAliases()
aliases.add('/home/*/src', './mysrc')
- self.assert_not_mapped(aliases, '/home/foo/srcetc')
+ self.assert_unchanged(aliases, '/home/foo/srcetc')
def test_multiple_patterns(self):
aliases = PathAliases()
@@ -284,4 +284,4 @@ class WindowsFileTest(CoverageTest):
super(WindowsFileTest, self).setUp()
def test_actual_path(self):
- self.assertEquals(actual_path(r'c:\Windows'), actual_path(r'C:\wINDOWS'))
+ self.assertEqual(actual_path(r'c:\Windows'), actual_path(r'C:\wINDOWS'))
diff --git a/tests/test_html.py b/tests/test_html.py
index 1df602f..9bb8f39 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -6,6 +6,7 @@
import datetime
import glob
+import json
import os
import os.path
import re
@@ -46,7 +47,7 @@ class HtmlTestHelpers(CoverageTest):
self.clean_local_file_imports()
cov = coverage.Coverage(**(covargs or {}))
self.start_import_stop(cov, "main_file")
- cov.html_report(**(htmlargs or {}))
+ return cov.html_report(**(htmlargs or {}))
def remove_html_files(self):
"""Remove the HTML files created as part of the HTML report."""
@@ -101,11 +102,7 @@ class HtmlDeltaTest(HtmlTestHelpers, CoverageTest):
# At least one of our tests monkey-patches the version of coverage.py,
# so grab it here to restore it later.
self.real_coverage_version = coverage.__version__
- self.addCleanup(self.cleanup_coverage_version)
-
- def cleanup_coverage_version(self):
- """A cleanup."""
- coverage.__version__ = self.real_coverage_version
+ self.addCleanup(setattr, coverage, "__version__", self.real_coverage_version)
def test_html_created(self):
# Test basic HTML generation: files should be created.
@@ -208,6 +205,44 @@ class HtmlDeltaTest(HtmlTestHelpers, CoverageTest):
fixed_index2 = index2.replace("XYZZY", self.real_coverage_version)
self.assertMultiLineEqual(index1, fixed_index2)
+ def test_file_becomes_100(self):
+ self.create_initial_files()
+ self.run_coverage()
+
+ # Now change a file and do it again
+ self.make_file("main_file.py", """\
+ import helper1, helper2
+ # helper1 is now 100%
+ helper1.func1(12)
+ helper1.func1(23)
+ """)
+
+ self.run_coverage(htmlargs=dict(skip_covered=True))
+
+ # The 100% file, skipped, shouldn't be here.
+ self.assert_doesnt_exist("htmlcov/helper1_py.html")
+
+ def test_status_format_change(self):
+ self.create_initial_files()
+ self.run_coverage()
+ self.remove_html_files()
+
+ with open("htmlcov/status.json") as status_json:
+ status_data = json.load(status_json)
+
+ self.assertEqual(status_data['format'], 1)
+ status_data['format'] = 2
+ with open("htmlcov/status.json", "w") as status_json:
+ json.dump(status_data, status_json)
+
+ self.run_coverage()
+
+ # All the files have been reported again.
+ self.assert_exists("htmlcov/index.html")
+ self.assert_exists("htmlcov/helper1_py.html")
+ self.assert_exists("htmlcov/main_file_py.html")
+ self.assert_exists("htmlcov/helper2_py.html")
+
class HtmlTitleTest(HtmlTestHelpers, CoverageTest):
"""Tests of the HTML title support."""
@@ -260,18 +295,20 @@ class HtmlWithUnparsableFilesTest(HtmlTestHelpers, CoverageTest):
"""Test the behavior when measuring unparsable files."""
def test_dotpy_not_python(self):
+ self.make_file("main.py", "import innocuous")
self.make_file("innocuous.py", "a = 1")
cov = coverage.Coverage()
- self.start_import_stop(cov, "innocuous")
+ self.start_import_stop(cov, "main")
self.make_file("innocuous.py", "<h1>This isn't python!</h1>")
msg = "Couldn't parse '.*innocuous.py' as Python source: .* at line 1"
with self.assertRaisesRegex(NotPython, msg):
cov.html_report()
def test_dotpy_not_python_ignored(self):
+ self.make_file("main.py", "import innocuous")
self.make_file("innocuous.py", "a = 2")
cov = coverage.Coverage()
- self.start_import_stop(cov, "innocuous")
+ self.start_import_stop(cov, "main")
self.make_file("innocuous.py", "<h1>This isn't python!</h1>")
cov.html_report(ignore_errors=True)
self.assertEqual(
@@ -337,7 +374,7 @@ class HtmlWithUnparsableFilesTest(HtmlTestHelpers, CoverageTest):
self.make_file("main.py", "import sub.not_ascii")
self.make_file("sub/__init__.py")
self.make_file("sub/not_ascii.py", """\
- # coding: utf8
+ # coding: utf-8
a = 1 # Isn't this great?!
""")
cov = coverage.Coverage()
@@ -346,7 +383,7 @@ class HtmlWithUnparsableFilesTest(HtmlTestHelpers, CoverageTest):
# Create the undecodable version of the file. make_file is too helpful,
# so get down and dirty with bytes.
with open("sub/not_ascii.py", "wb") as f:
- f.write(b"# coding: utf8\na = 1 # Isn't this great?\xcb!\n")
+ f.write(b"# coding: utf-8\na = 1 # Isn't this great?\xcb!\n")
with open("sub/not_ascii.py", "rb") as f:
undecodable = f.read()
@@ -425,18 +462,58 @@ class HtmlTest(HtmlTestHelpers, CoverageTest):
self.run_coverage()
self.assert_exists("htmlcov/status.dat")
+ def test_report_skip_covered_no_branches(self):
+ self.make_file("main_file.py", """
+ import not_covered
+
+ def normal():
+ print("z")
+ normal()
+ """)
+ self.make_file("not_covered.py", """
+ def not_covered():
+ print("n")
+ """)
+ self.run_coverage(htmlargs=dict(skip_covered=True))
+ self.assert_exists("htmlcov/index.html")
+ self.assert_doesnt_exist("htmlcov/main_file_py.html")
+ self.assert_exists("htmlcov/not_covered_py.html")
+
+ def test_report_skip_covered_100(self):
+ self.make_file("main_file.py", """
+ def normal():
+ print("z")
+ normal()
+ """)
+ res = self.run_coverage(covargs=dict(source="."), htmlargs=dict(skip_covered=True))
+ self.assertEqual(res, 100.0)
+ self.assert_doesnt_exist("htmlcov/main_file_py.html")
+
+ def test_report_skip_covered_branches(self):
+ self.make_file("main_file.py", """
+ import not_covered
+
+ def normal():
+ print("z")
+ normal()
+ """)
+ self.make_file("not_covered.py", """
+ def not_covered():
+ print("n")
+ """)
+ self.run_coverage(covargs=dict(branch=True), htmlargs=dict(skip_covered=True))
+ self.assert_exists("htmlcov/index.html")
+ self.assert_doesnt_exist("htmlcov/main_file_py.html")
+ self.assert_exists("htmlcov/not_covered_py.html")
+
class HtmlStaticFileTest(CoverageTest):
"""Tests of the static file copying for the HTML report."""
def setUp(self):
super(HtmlStaticFileTest, self).setUp()
- self.original_path = list(coverage.html.STATIC_PATH)
- self.addCleanup(self.cleanup_static_path)
-
- def cleanup_static_path(self):
- """A cleanup."""
- coverage.html.STATIC_PATH = self.original_path
+ original_path = list(coverage.html.STATIC_PATH)
+ self.addCleanup(setattr, coverage.html, 'STATIC_PATH', original_path)
def test_copying_static_files_from_system(self):
# Make a new place for static files.
@@ -686,7 +763,7 @@ class HtmlGoldTests(CoverageGoldTest):
with change_dir("src"):
# pylint: disable=import-error
- cov = coverage.Coverage(branch=True)
+ cov = coverage.Coverage(config_file="partial.ini")
cov.start()
import partial # pragma: nested
cov.stop() # pragma: nested
@@ -700,6 +777,8 @@ class HtmlGoldTests(CoverageGoldTest):
'<p id="t14" class="stm run hide_run">',
# The "if 0" and "if 1" statements are optimized away.
'<p id="t17" class="pln">',
+ # The "raise AssertionError" is excluded by regex in the .ini.
+ '<p id="t24" class="exc">',
)
contains(
"out/partial/index.html",
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 38be345..939b1c9 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -3,11 +3,10 @@
"""Tests of miscellaneous stuff."""
-import sys
+import pytest
-import coverage
-from coverage.version import _make_url, _make_version
-from coverage.misc import Hasher, file_be_gone
+from coverage.misc import contract, dummy_decorator_with_args, file_be_gone
+from coverage.misc import format_lines, Hasher, one_of
from tests.coveragetest import CoverageTest
@@ -34,6 +33,13 @@ class HasherTest(CoverageTest):
h2.update(b"Goodbye!")
self.assertNotEqual(h1.hexdigest(), h2.hexdigest())
+ def test_unicode_hashing(self):
+ h1 = Hasher()
+ h1.update(u"Hello, world! \N{SNOWMAN}")
+ h2 = Hasher()
+ h2.update(u"Goodbye!")
+ self.assertNotEqual(h1.hexdigest(), h2.hexdigest())
+
def test_dict_hashing(self):
h1 = Hasher()
h1.update({'a': 17, 'b': 23})
@@ -62,63 +68,63 @@ class RemoveFileTest(CoverageTest):
file_be_gone(".")
-class VersionTest(CoverageTest):
- """Tests of version.py"""
-
- run_in_temp_dir = False
-
- def test_version_info(self):
- # Make sure we didn't screw up the version_info tuple.
- self.assertIsInstance(coverage.version_info, tuple)
- self.assertEqual([type(d) for d in coverage.version_info], [int, int, int, str, int])
- self.assertIn(coverage.version_info[3], ['alpha', 'beta', 'candidate', 'final'])
-
- def test_make_version(self):
- self.assertEqual(_make_version(4, 0, 0, 'alpha', 0), "4.0a0")
- self.assertEqual(_make_version(4, 0, 0, 'alpha', 1), "4.0a1")
- self.assertEqual(_make_version(4, 0, 0, 'final', 0), "4.0")
- self.assertEqual(_make_version(4, 1, 2, 'beta', 3), "4.1.2b3")
- self.assertEqual(_make_version(4, 1, 2, 'final', 0), "4.1.2")
- self.assertEqual(_make_version(5, 10, 2, 'candidate', 7), "5.10.2rc7")
-
- def test_make_url(self):
- self.assertEqual(
- _make_url(4, 0, 0, 'final', 0),
- "https://coverage.readthedocs.io"
- )
- self.assertEqual(
- _make_url(4, 1, 2, 'beta', 3),
- "https://coverage.readthedocs.io/en/coverage-4.1.2b3"
- )
-
-
-class SetupPyTest(CoverageTest):
- """Tests of setup.py"""
+class ContractTest(CoverageTest):
+ """Tests of our contract decorators."""
run_in_temp_dir = False
- def test_metadata(self):
- status, output = self.run_command_status(
- "python setup.py --description --version --url --author"
- )
- self.assertEqual(status, 0)
- out = output.splitlines()
- self.assertIn("measurement", out[0])
- self.assertEqual(out[1], coverage.__version__)
- self.assertEqual(out[2], coverage.__url__)
- self.assertIn("Ned Batchelder", out[3])
-
- def test_more_metadata(self):
- # Let's be sure we pick up our own setup.py
- # CoverageTest restores the original sys.path for us.
- sys.path.insert(0, '')
- from setup import setup_args
-
- classifiers = setup_args['classifiers']
- self.assertGreater(len(classifiers), 7)
- self.assert_starts_with(classifiers[-1], "Development Status ::")
-
- long_description = setup_args['long_description'].splitlines()
- self.assertGreater(len(long_description), 7)
- self.assertNotEqual(long_description[0].strip(), "")
- self.assertNotEqual(long_description[-1].strip(), "")
+ def test_bytes(self):
+ @contract(text='bytes|None')
+ def need_bytes(text=None): # pylint: disable=missing-docstring
+ return text
+
+ assert need_bytes(b"Hey") == b"Hey"
+ assert need_bytes() is None
+ with pytest.raises(Exception):
+ need_bytes(u"Oops")
+
+ def test_unicode(self):
+ @contract(text='unicode|None')
+ def need_unicode(text=None): # pylint: disable=missing-docstring
+ return text
+
+ assert need_unicode(u"Hey") == u"Hey"
+ assert need_unicode() is None
+ with pytest.raises(Exception):
+ need_unicode(b"Oops")
+
+ def test_one_of(self):
+ @one_of("a, b, c")
+ def give_me_one(a=None, b=None, c=None): # pylint: disable=missing-docstring
+ return (a, b, c)
+
+ assert give_me_one(a=17) == (17, None, None)
+ assert give_me_one(b=set()) == (None, set(), None)
+ assert give_me_one(c=17) == (None, None, 17)
+ with pytest.raises(AssertionError):
+ give_me_one(a=17, b=set())
+ with pytest.raises(AssertionError):
+ give_me_one()
+
+ def test_dummy_decorator_with_args(self):
+ @dummy_decorator_with_args("anything", this=17, that="is fine")
+ def undecorated(a=None, b=None): # pylint: disable=missing-docstring
+ return (a, b)
+
+ assert undecorated() == (None, None)
+ assert undecorated(17) == (17, None)
+ assert undecorated(b=23) == (None, 23)
+ assert undecorated(b=42, a=3) == (3, 42)
+
+
+@pytest.mark.parametrize("statements, lines, result", [
+ (set([1,2,3,4,5,10,11,12,13,14]), set([1,2,5,10,11,13,14]), "1-2, 5-11, 13-14"),
+ ([1,2,3,4,5,10,11,12,13,14,98,99], [1,2,5,10,11,13,14,99], "1-2, 5-11, 13-14, 99"),
+ ([1,2,3,4,98,99,100,101,102,103,104], [1,2,99,102,103,104], "1-2, 99, 102-104"),
+ ([17], [17], "17"),
+ ([90,91,92,93,94,95], [90,91,92,93,94,95], "90-95"),
+ ([1, 2, 3, 4, 5], [], ""),
+ ([1, 2, 3, 4, 5], [4], "4"),
+])
+def test_format_lines(statements, lines, result):
+ assert format_lines(statements, lines) == result
diff --git a/tests/test_oddball.py b/tests/test_oddball.py
index 87c65b0..b54f4ef 100644
--- a/tests/test_oddball.py
+++ b/tests/test_oddball.py
@@ -5,7 +5,12 @@
import sys
+from flaky import flaky
+import pytest
+
import coverage
+from coverage import env
+from coverage.backward import import_local_file
from coverage.files import abs_file
from tests.coveragetest import CoverageTest
@@ -115,7 +120,7 @@ class RecursionTest(CoverageTest):
pytrace = (cov.collector.tracer_name() == "PyTracer")
expected_missing = [3]
- if pytrace:
+ if pytrace: # pragma: no metacov
expected_missing += [9, 10, 11]
_, statements, missing, _ = cov.analysis("recur.py")
@@ -123,7 +128,7 @@ class RecursionTest(CoverageTest):
self.assertEqual(missing, expected_missing)
# Get a warning about the stackoverflow effect on the tracing function.
- if pytrace:
+ if pytrace: # pragma: no metacov
self.assertEqual(cov._warnings,
["Trace function changed, measurement is likely wrong: None"]
)
@@ -140,7 +145,11 @@ class MemoryLeakTest(CoverageTest):
It may still fail occasionally, especially on PyPy.
"""
+ @flaky
def test_for_leaks(self):
+ if env.JYTHON:
+ self.skipTest("Don't bother on Jython")
+
# Our original bad memory leak only happened on line numbers > 255, so
# make a code object with more lines than that. Ugly string mumbo
# jumbo to get 300 blank lines at the beginning..
@@ -176,17 +185,56 @@ class MemoryLeakTest(CoverageTest):
# Running the code 10k times shouldn't grow the ram much more than
# running it 10 times.
ram_growth = (ram_10k - ram_10) - (ram_10 - ram_0)
- if ram_growth > 100000: # pragma: only failure
- fails += 1
-
- if fails > 8: # pragma: only failure
- self.fail("RAM grew by %d" % (ram_growth))
+ if ram_growth > 100000:
+ fails += 1 # pragma: only failure
+
+ if fails > 8:
+ self.fail("RAM grew by %d" % (ram_growth)) # pragma: only failure
+
+
+class MemoryFumblingTest(CoverageTest):
+ """Test that we properly manage the None refcount."""
+
+ def test_dropping_none(self): # pragma: not covered
+ if not env.C_TRACER:
+ self.skipTest("Only the C tracer has refcounting issues")
+ # TODO: Mark this so it will only be run sometimes.
+ self.skipTest("This is too expensive for now (30s)")
+ # Start and stop coverage thousands of times to flush out bad
+ # reference counting, maybe.
+ self.make_file("the_code.py", """\
+ import random
+ def f():
+ if random.random() > .5:
+ x = 1
+ else:
+ x = 2
+ """)
+ self.make_file("main.py", """\
+ import coverage
+ import sys
+ from the_code import f
+ for i in range(10000):
+ cov = coverage.Coverage(branch=True)
+ cov.start()
+ f()
+ cov.stop()
+ cov.erase()
+ print("Final None refcount: %d" % (sys.getrefcount(None)))
+ """)
+ status, out = self.run_command_status("python main.py")
+ self.assertEqual(status, 0)
+ self.assertIn("Final None refcount", out)
+ self.assertNotIn("Fatal", out)
class PyexpatTest(CoverageTest):
"""Pyexpat screws up tracing. Make sure we've counter-defended properly."""
def test_pyexpat(self):
+ if env.JYTHON:
+ self.skipTest("Pyexpat isn't a problem on Jython")
+
# pyexpat calls the trace function explicitly (inexplicably), and does
# it wrong for exceptions. Parsing a DOCTYPE for some reason throws
# an exception internally, and triggers its wrong behavior. This test
@@ -286,7 +334,7 @@ class ExceptionTest(CoverageTest):
# Import all the modules before starting coverage, so the def lines
# won't be in all the results.
for mod in "oops fly catch doit".split():
- self.import_local_file(mod)
+ import_local_file(mod)
# Each run nests the functions differently to get different
# combinations of catching exceptions and letting them fly.
@@ -337,6 +385,13 @@ class ExceptionTest(CoverageTest):
lines = data.lines(abs_file(filename))
clean_lines[filename] = sorted(lines)
+ if env.JYTHON: # pragma: only jython
+ # Jython doesn't report on try or except lines, so take those
+ # out of the expected lines.
+ invisible = [202, 206, 302, 304]
+ for lines in lines_expected.values():
+ lines[:] = [l for l in lines if l not in invisible]
+
self.assertEqual(clean_lines, lines_expected)
@@ -346,14 +401,11 @@ class DoctestTest(CoverageTest):
def setUp(self):
super(DoctestTest, self).setUp()
- # Oh, the irony! This test case exists because Python 2.4's
- # doctest module doesn't play well with coverage. But nose fixes
- # the problem by monkeypatching doctest. I want to undo the
- # monkeypatch to be sure I'm getting the doctest module that users
- # of coverage will get. Deleting the imported module here is
- # enough: when the test imports doctest again, it will get a fresh
- # copy without the monkeypatch.
- del sys.modules['doctest']
+ # This test case exists because Python 2.4's doctest module didn't play
+ # well with coverage. Nose fixes the problem by monkeypatching doctest.
+ # I want to be sure there's no monkeypatch and that I'm getting the
+ # doctest module that users of coverage will get.
+ assert 'doctest' not in sys.modules
def test_doctest(self):
self.check_coverage('''\
@@ -380,35 +432,38 @@ class DoctestTest(CoverageTest):
class GettraceTest(CoverageTest):
"""Tests that we work properly with `sys.gettrace()`."""
- def test_round_trip(self):
- self.check_coverage('''\
- import sys
- def foo(n):
- return 3*n
- def bar(n):
- return 5*n
- a = foo(6)
- sys.settrace(sys.gettrace())
- a = bar(8)
- ''',
- [1, 2, 3, 4, 5, 6, 7, 8], "")
-
- def test_multi_layers(self):
- self.check_coverage('''\
+ def test_round_trip_in_untraced_function(self):
+ # https://bitbucket.org/ned/coveragepy/issues/575/running-doctest-prevents-complete-coverage
+ self.make_file("main.py", """import sample""")
+ self.make_file("sample.py", """\
+ from swap import swap_it
+ def doit():
+ print(3)
+ swap_it()
+ print(5)
+ def doit_soon():
+ print(7)
+ doit()
+ print(9)
+ print(10)
+ doit_soon()
+ print(12)
+ """)
+ self.make_file("swap.py", """\
import sys
- def level1():
- a = 3
- level2()
- b = 5
- def level2():
- c = 7
+ def swap_it():
sys.settrace(sys.gettrace())
- d = 9
- e = 10
- level1()
- f = 12
- ''',
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "")
+ """)
+
+ # Use --source=sample to prevent measurement of swap.py.
+ cov = coverage.Coverage(source=["sample"])
+ self.start_import_stop(cov, "main")
+
+ self.assertEqual(self.stdout(), "10\n7\n3\n5\n9\n12\n")
+
+ _, statements, missing, _ = cov.analysis("sample.py")
+ self.assertEqual(statements, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
+ self.assertEqual(missing, [])
def test_setting_new_trace_function(self):
# https://bitbucket.org/ned/coveragepy/issues/436/disabled-coverage-ctracer-may-rise-from
@@ -433,8 +488,10 @@ class GettraceTest(CoverageTest):
old = sys.gettrace()
test_unsets_trace()
sys.settrace(old)
+ a = 21
+ b = 22
''',
- lines=[1, 3, 4, 5, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20],
+ lines=[1, 3, 4, 5, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20, 21, 22],
missing="4-5, 11-12",
)
@@ -450,6 +507,38 @@ class GettraceTest(CoverageTest):
),
)
+ @pytest.mark.expensive
+ def test_atexit_gettrace(self): # pragma: no metacov
+ # This is not a test of coverage at all, but of our understanding
+ # of this edge-case behavior in various Pythons.
+ if env.METACOV:
+ self.skipTest("Can't set trace functions during meta-coverage")
+
+ self.make_file("atexit_gettrace.py", """\
+ import atexit, sys
+
+ def trace_function(frame, event, arg):
+ return trace_function
+ sys.settrace(trace_function)
+
+ def show_trace_function():
+ tfn = sys.gettrace()
+ if tfn is not None:
+ tfn = tfn.__name__
+ print(tfn)
+ atexit.register(show_trace_function)
+
+ # This will show what the trace function is at the end of the program.
+ """)
+ status, out = self.run_command_status("python atexit_gettrace.py")
+ self.assertEqual(status, 0)
+ if env.PYPY and env.PYPYVERSION >= (5, 4):
+ # Newer PyPy clears the trace function before atexit runs.
+ self.assertEqual(out, "None\n")
+ else:
+ # Other Pythons leave the trace function in place.
+ self.assertEqual(out, "trace_function\n")
+
class ExecTest(CoverageTest):
"""Tests of exec."""
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 5fee823..aa96b59 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -193,7 +193,7 @@ class ParserMissingArcDescriptionTest(CoverageTest):
def parse_text(self, source):
"""Parse Python source, and return the parser object."""
- parser = PythonParser(textwrap.dedent(source))
+ parser = PythonParser(text=textwrap.dedent(source))
parser.parse_source()
return parser
diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py
index ccbe01b..ddb652e 100644
--- a/tests/test_phystokens.py
+++ b/tests/test_phystokens.py
@@ -5,6 +5,7 @@
import os.path
import re
+import textwrap
from coverage import env
from coverage.phystokens import source_token_lines, source_encoding
@@ -14,18 +15,35 @@ from coverage.python import get_python_source
from tests.coveragetest import CoverageTest
+# A simple program and its token stream.
SIMPLE = u"""\
# yay!
def foo():
say('two = %d' % 2)
"""
+SIMPLE_TOKENS = [
+ [('com', "# yay!")],
+ [('key', 'def'), ('ws', ' '), ('nam', 'foo'), ('op', '('), ('op', ')'), ('op', ':')],
+ [('ws', ' '), ('nam', 'say'), ('op', '('),
+ ('str', "'two = %d'"), ('ws', ' '), ('op', '%'),
+ ('ws', ' '), ('num', '2'), ('op', ')')],
+]
+
+# Mixed-whitespace program, and its token stream.
MIXED_WS = u"""\
def hello():
a="Hello world!"
\tb="indented"
"""
+MIXED_WS_TOKENS = [
+ [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ('op', ')'), ('op', ':')],
+ [('ws', ' '), ('nam', 'a'), ('op', '='), ('str', '"Hello world!"')],
+ [('ws', ' '), ('nam', 'b'), ('op', '='), ('str', '"indented"')],
+]
+
+# Where this file is, so we can find other files next to it.
HERE = os.path.dirname(__file__)
@@ -52,28 +70,16 @@ class PhysTokensTest(CoverageTest):
self.check_tokenization(get_python_source(fname))
def test_simple(self):
- self.assertEqual(list(source_token_lines(SIMPLE)),
- [
- [('com', "# yay!")],
- [('key', 'def'), ('ws', ' '), ('nam', 'foo'), ('op', '('),
- ('op', ')'), ('op', ':')],
- [('ws', ' '), ('nam', 'say'), ('op', '('),
- ('str', "'two = %d'"), ('ws', ' '), ('op', '%'),
- ('ws', ' '), ('num', '2'), ('op', ')')]
- ])
+ self.assertEqual(list(source_token_lines(SIMPLE)), SIMPLE_TOKENS)
self.check_tokenization(SIMPLE)
+ def test_missing_final_newline(self):
+ # We can tokenize source that is missing the final newline.
+ self.assertEqual(list(source_token_lines(SIMPLE.rstrip())), SIMPLE_TOKENS)
+
def test_tab_indentation(self):
# Mixed tabs and spaces...
- self.assertEqual(list(source_token_lines(MIXED_WS)),
- [
- [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('),
- ('op', ')'), ('op', ':')],
- [('ws', ' '), ('nam', 'a'), ('op', '='),
- ('str', '"Hello world!"')],
- [('ws', ' '), ('nam', 'b'), ('op', '='),
- ('str', '"indented"')],
- ])
+ self.assertEqual(list(source_token_lines(MIXED_WS)), MIXED_WS_TOKENS)
def test_tokenize_real_file(self):
# Check the tokenization of a real file (large, btw).
@@ -97,13 +103,15 @@ else:
ENCODING_DECLARATION_SOURCES = [
# Various forms from http://www.python.org/dev/peps/pep-0263/
- (1, b"# coding=cp850\n\n"),
- (1, b"#!/usr/bin/python\n# -*- coding: cp850 -*-\n"),
- (1, b"#!/usr/bin/python\n# vim: set fileencoding=cp850:\n"),
- (1, b"# This Python file uses this encoding: cp850\n"),
- (1, b"# This file uses a different encoding:\n# coding: cp850\n"),
- (1, b"\n# coding=cp850\n\n"),
- (2, b"# -*- coding:cp850 -*-\n# vim: fileencoding=cp850\n"),
+ (1, b"# coding=cp850\n\n", "cp850"),
+ (1, b"# coding=latin-1\n", "iso-8859-1"),
+ (1, b"# coding=iso-latin-1\n", "iso-8859-1"),
+ (1, b"#!/usr/bin/python\n# -*- coding: cp850 -*-\n", "cp850"),
+ (1, b"#!/usr/bin/python\n# vim: set fileencoding=cp850:\n", "cp850"),
+ (1, b"# This Python file uses this encoding: cp850\n", "cp850"),
+ (1, b"# This file uses a different encoding:\n# coding: cp850\n", "cp850"),
+ (1, b"\n# coding=cp850\n\n", "cp850"),
+ (2, b"# -*- coding:cp850 -*-\n# vim: fileencoding=cp850\n", "cp850"),
]
class SourceEncodingTest(CoverageTest):
@@ -112,15 +120,15 @@ class SourceEncodingTest(CoverageTest):
run_in_temp_dir = False
def test_detect_source_encoding(self):
- for _, source in ENCODING_DECLARATION_SOURCES:
+ for _, source, expected in ENCODING_DECLARATION_SOURCES:
self.assertEqual(
source_encoding(source),
- 'cp850',
+ expected,
"Wrong encoding in %r" % source
)
def test_detect_source_encoding_not_in_comment(self):
- if env.PYPY and env.PY3:
+ if env.PYPY and env.PY3: # pragma: no metacov
# PyPy3 gets this case wrong. Not sure what I can do about it,
# so skip the test.
self.skipTest("PyPy3 is wrong about non-comment encoding. Skip it.")
@@ -142,9 +150,19 @@ class SourceEncodingTest(CoverageTest):
source = b"\xEF\xBB\xBFtext = 'hello'\n"
self.assertEqual(source_encoding(source), 'utf-8-sig')
- # But it has to be the only authority.
+ def test_bom_with_encoding(self):
+ source = b"\xEF\xBB\xBF# coding: utf-8\ntext = 'hello'\n"
+ self.assertEqual(source_encoding(source), 'utf-8-sig')
+
+ def test_bom_is_wrong(self):
+ # A BOM with an explicit non-utf8 encoding is an error.
source = b"\xEF\xBB\xBF# coding: cp850\n"
- with self.assertRaises(SyntaxError):
+ with self.assertRaisesRegex(SyntaxError, "encoding problem: utf-8"):
+ source_encoding(source)
+
+ def test_unknown_encoding(self):
+ source = b"# coding: klingon\n"
+ with self.assertRaisesRegex(SyntaxError, "unknown encoding: klingon"):
source_encoding(source)
@@ -154,7 +172,7 @@ class NeuterEncodingDeclarationTest(CoverageTest):
run_in_temp_dir = False
def test_neuter_encoding_declaration(self):
- for lines_diff_expected, source in ENCODING_DECLARATION_SOURCES:
+ for lines_diff_expected, source, _ in ENCODING_DECLARATION_SOURCES:
neutered = neuter_encoding_declaration(source.decode("ascii"))
neutered = neutered.encode("ascii")
@@ -177,6 +195,67 @@ class NeuterEncodingDeclarationTest(CoverageTest):
"Wrong encoding in %r" % neutered
)
+ def test_two_encoding_declarations(self):
+ input_src = textwrap.dedent(u"""\
+ # -*- coding: ascii -*-
+ # -*- coding: utf-8 -*-
+ # -*- coding: utf-16 -*-
+ """)
+ expected_src = textwrap.dedent(u"""\
+ # (deleted declaration) -*-
+ # (deleted declaration) -*-
+ # -*- coding: utf-16 -*-
+ """)
+ output_src = neuter_encoding_declaration(input_src)
+ self.assertEqual(expected_src, output_src)
+
+ def test_one_encoding_declaration(self):
+ input_src = textwrap.dedent(u"""\
+ # -*- coding: utf-16 -*-
+ # Just a comment.
+ # -*- coding: ascii -*-
+ """)
+ expected_src = textwrap.dedent(u"""\
+ # (deleted declaration) -*-
+ # Just a comment.
+ # -*- coding: ascii -*-
+ """)
+ output_src = neuter_encoding_declaration(input_src)
+ self.assertEqual(expected_src, output_src)
+
+
+class Bug529Test(CoverageTest):
+ """Test of bug 529"""
+
+ def test_bug_529(self):
+ # Don't over-neuter coding declarations. This happened with a test
+ # file which contained code in multi-line strings, all with coding
+ # declarations. The neutering of the file also changed the multi-line
+ # strings, which it shouldn't have.
+ self.make_file("the_test.py", '''\
+ # -*- coding: utf-8 -*-
+ import unittest
+ class Bug529Test(unittest.TestCase):
+ def test_two_strings_are_equal(self):
+ src1 = u"""\\
+ # -*- coding: utf-8 -*-
+ # Just a comment.
+ """
+ src2 = u"""\\
+ # -*- coding: utf-8 -*-
+ # Just a comment.
+ """
+ self.assertEqual(src1, src2)
+
+ if __name__ == "__main__":
+ unittest.main()
+ ''')
+ status, out = self.run_command_status("coverage run the_test.py")
+ self.assertEqual(status, 0)
+ self.assertIn("OK", out)
+ # If this test fails, the output will be super-confusing, because it
+ # has a failing unit test contained within the failing unit test.
+
class CompileUnicodeTest(CoverageTest):
"""Tests of compiling Unicode strings."""
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index 8ea0a8f..5486216 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -333,9 +333,8 @@ class GoodPluginTest(FileTracerTest):
# quux_5.html will be omitted from the results.
assert render("quux_5.html", 3) == "[quux_5.html @ 3]"
- # In Python 2, either kind of string should be OK.
- if sys.version_info[0] == 2:
- assert render(u"uni_3.html", 2) == "[uni_3.html @ 2]"
+ # For Python 2, make sure unicode is working.
+ assert render(u"uni_3.html", 2) == "[uni_3.html @ 2]"
""")
# will try to read the actual source files, so make some
@@ -372,11 +371,10 @@ class GoodPluginTest(FileTracerTest):
self.assertNotIn("quux_5.html", cov.data.line_counts())
- if env.PY2:
- _, statements, missing, _ = cov.analysis("uni_3.html")
- self.assertEqual(statements, [1, 2, 3])
- self.assertEqual(missing, [1])
- self.assertIn("uni_3.html", cov.data.line_counts())
+ _, statements, missing, _ = cov.analysis("uni_3.html")
+ self.assertEqual(statements, [1, 2, 3])
+ self.assertEqual(missing, [1])
+ self.assertIn("uni_3.html", cov.data.line_counts())
def test_plugin2_with_branch(self):
self.make_render_and_caller()
@@ -507,6 +505,58 @@ class GoodPluginTest(FileTracerTest):
self.assertEqual(report, expected)
self.assertEqual(total, 50)
+ def test_find_unexecuted(self):
+ self.make_file("unexecuted_plugin.py", """\
+ import os
+ import coverage.plugin
+ class Plugin(coverage.CoveragePlugin):
+ def file_tracer(self, filename):
+ if filename.endswith("foo.py"):
+ return MyTracer(filename)
+ def file_reporter(self, filename):
+ return MyReporter(filename)
+ def find_executable_files(self, src_dir):
+ # Check that src_dir is the right value
+ files = os.listdir(src_dir)
+ assert "foo.py" in files
+ assert "unexecuted_plugin.py" in files
+ return ["chimera.py"]
+
+ class MyTracer(coverage.plugin.FileTracer):
+ def __init__(self, filename):
+ self.filename = filename
+ def source_filename(self):
+ return self.filename
+ def line_number_range(self, frame):
+ return (999, 999)
+
+ class MyReporter(coverage.FileReporter):
+ def lines(self):
+ return set([99, 999, 9999])
+
+ def coverage_init(reg, options):
+ reg.add_file_tracer(Plugin())
+ """)
+ self.make_file("foo.py", "a = 1\n")
+ cov = coverage.Coverage(source=['.'])
+ cov.set_option("run:plugins", ["unexecuted_plugin"])
+ self.start_import_stop(cov, "foo")
+
+ # The file we executed claims to have run line 999.
+ _, statements, missing, _ = cov.analysis("foo.py")
+ self.assertEqual(statements, [99, 999, 9999])
+ self.assertEqual(missing, [99, 9999])
+
+ # The completely missing file is in the results.
+ _, statements, missing, _ = cov.analysis("chimera.py")
+ self.assertEqual(statements, [99, 999, 9999])
+ self.assertEqual(missing, [99, 999, 9999])
+
+ # But completely new filenames are not in the results.
+ self.assertEqual(len(cov.get_data().measured_files()), 3)
+ with self.assertRaises(CoverageException):
+ cov.analysis("fictional.py")
+
class BadPluginTest(FileTracerTest):
"""Test error handling around plugins."""
@@ -542,7 +592,7 @@ class BadPluginTest(FileTracerTest):
self.start_import_stop(cov, "simple")
return cov
- def run_bad_plugin(self, module_name, plugin_name, our_error=True, excmsg=None):
+ def run_bad_plugin(self, module_name, plugin_name, our_error=True, excmsg=None, excmsgs=None):
"""Run a file, and see that the plugin failed.
`module_name` and `plugin_name` is the module and name of the plugin to
@@ -551,7 +601,10 @@ class BadPluginTest(FileTracerTest):
`our_error` is True if the error reported to the user will be an
explicit error in our test code, marked with an '# Oh noes!' comment.
- `excmsg`, if provided, is text that should appear in the stderr.
+ `excmsg`, if provided, is text that must appear in the stderr.
+
+ `excmsgs`, if provided, is a list of messages, one of which must
+ appear in the stderr.
The plugin will be disabled, and we check that a warning is output
explaining why.
@@ -560,7 +613,6 @@ class BadPluginTest(FileTracerTest):
self.run_plugin(module_name)
stderr = self.stderr()
- print(stderr) # for diagnosing test failures.
if our_error:
errors = stderr.count("# Oh noes!")
@@ -578,6 +630,8 @@ class BadPluginTest(FileTracerTest):
if excmsg:
self.assertIn(excmsg, stderr)
+ if excmsgs:
+ self.assertTrue(any(em in stderr for em in excmsgs), "expected one of %r" % excmsgs)
def test_file_tracer_has_no_file_tracer_method(self):
self.make_file("bad_plugin.py", """\
@@ -650,7 +704,9 @@ class BadPluginTest(FileTracerTest):
def coverage_init(reg, options):
reg.add_file_tracer(Plugin())
""")
- self.run_bad_plugin("bad_plugin", "Plugin", our_error=False)
+ self.run_bad_plugin(
+ "bad_plugin", "Plugin", our_error=False, excmsg="'float' object has no attribute",
+ )
def test_has_dynamic_source_filename_fails(self):
self.make_file("bad_plugin.py", """\
@@ -698,7 +754,15 @@ class BadPluginTest(FileTracerTest):
def coverage_init(reg, options):
reg.add_file_tracer(Plugin())
""")
- self.run_bad_plugin("bad_plugin", "Plugin", our_error=False)
+ self.run_bad_plugin(
+ "bad_plugin", "Plugin", our_error=False,
+ excmsgs=[
+ "expected str, bytes or os.PathLike object, not float",
+ "'float' object has no attribute",
+ "object of type 'float' has no len()",
+ "'float' object is unsubscriptable",
+ ],
+ )
def test_dynamic_source_filename_fails(self):
self.make_file("bad_plugin.py", """\
@@ -737,7 +801,9 @@ class BadPluginTest(FileTracerTest):
def coverage_init(reg, options):
reg.add_file_tracer(Plugin())
""")
- self.run_bad_plugin("bad_plugin", "Plugin", our_error=False)
+ self.run_bad_plugin(
+ "bad_plugin", "Plugin", our_error=False, excmsg="line_number_range must return 2-tuple",
+ )
def test_line_number_range_returns_triple(self):
self.make_file("bad_plugin.py", """\
@@ -757,7 +823,9 @@ class BadPluginTest(FileTracerTest):
def coverage_init(reg, options):
reg.add_file_tracer(Plugin())
""")
- self.run_bad_plugin("bad_plugin", "Plugin", our_error=False)
+ self.run_bad_plugin(
+ "bad_plugin", "Plugin", our_error=False, excmsg="line_number_range must return 2-tuple",
+ )
def test_line_number_range_returns_pair_of_strings(self):
self.make_file("bad_plugin.py", """\
@@ -777,4 +845,6 @@ class BadPluginTest(FileTracerTest):
def coverage_init(reg, options):
reg.add_file_tracer(Plugin())
""")
- self.run_bad_plugin("bad_plugin", "Plugin", our_error=False)
+ self.run_bad_plugin(
+ "bad_plugin", "Plugin", our_error=False, excmsg="an integer is required",
+ )
diff --git a/tests/test_process.py b/tests/test_process.py
index aa2045f..8a0f4e3 100644
--- a/tests/test_process.py
+++ b/tests/test_process.py
@@ -1,9 +1,10 @@
-# coding: utf8
+# coding: utf-8
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
"""Tests for process behavior of coverage.py."""
+import distutils.sysconfig # pylint: disable=import-error
import glob
import os
import os.path
@@ -11,13 +12,14 @@ import re
import sys
import textwrap
+import pytest
+
import coverage
from coverage import env, CoverageData
from coverage.misc import output_encoding
from tests.coveragetest import CoverageTest
-
-TRY_EXECFILE = os.path.join(os.path.dirname(__file__), "modules/process_test/try_execfile.py")
+from tests.helpers import re_lines
class ProcessTest(CoverageTest):
@@ -388,8 +390,7 @@ class ProcessTest(CoverageTest):
out2 = self.run_command("python throw.py")
if env.PYPY:
# Pypy has an extra frame in the traceback for some reason
- lines2 = out2.splitlines()
- out2 = "".join(l+"\n" for l in lines2 if "toplevel" not in l)
+ out2 = re_lines(out2, "toplevel", match=False)
self.assertMultiLineEqual(out, out2)
# But also make sure that the output is what we expect.
@@ -436,118 +437,6 @@ class ProcessTest(CoverageTest):
self.assertEqual(status, status2)
self.assertEqual(status, 0)
- def assert_execfile_output(self, out):
- """Assert that the output we got is a successful run of try_execfile.py"""
- self.assertIn('"DATA": "xyzzy"', out)
-
- def test_coverage_run_is_like_python(self):
- with open(TRY_EXECFILE) as f:
- self.make_file("run_me.py", f.read())
- out_cov = self.run_command("coverage run run_me.py")
- out_py = self.run_command("python run_me.py")
- self.assertMultiLineEqual(out_cov, out_py)
- self.assert_execfile_output(out_cov)
-
- def test_coverage_run_dashm_is_like_python_dashm(self):
- # These -m commands assume the coverage tree is on the path.
- out_cov = self.run_command("coverage run -m process_test.try_execfile")
- out_py = self.run_command("python -m process_test.try_execfile")
- self.assertMultiLineEqual(out_cov, out_py)
- self.assert_execfile_output(out_cov)
-
- def test_coverage_run_dir_is_like_python_dir(self):
- with open(TRY_EXECFILE) as f:
- self.make_file("with_main/__main__.py", f.read())
- out_cov = self.run_command("coverage run with_main")
- out_py = self.run_command("python with_main")
-
- # The coverage.py results are not identical to the Python results, and
- # I don't know why. For now, ignore those failures. If someone finds
- # a real problem with the discrepancies, we can work on it some more.
- ignored = r"__file__|__loader__|__package__"
- # PyPy includes the current directory in the path when running a
- # directory, while CPython and coverage.py do not. Exclude that from
- # the comparison also...
- if env.PYPY:
- ignored += "|"+re.escape(os.getcwd())
- out_cov = remove_matching_lines(out_cov, ignored)
- out_py = remove_matching_lines(out_py, ignored)
- self.assertMultiLineEqual(out_cov, out_py)
- self.assert_execfile_output(out_cov)
-
- def test_coverage_run_dashm_equal_to_doubledashsource(self):
- """regression test for #328
-
- When imported by -m, a module's __name__ is __main__, but we need the
- --source machinery to know and respect the original name.
- """
- # These -m commands assume the coverage tree is on the path.
- out_cov = self.run_command(
- "coverage run --source process_test.try_execfile -m process_test.try_execfile"
- )
- out_py = self.run_command("python -m process_test.try_execfile")
- self.assertMultiLineEqual(out_cov, out_py)
- self.assert_execfile_output(out_cov)
-
- def test_coverage_run_dashm_superset_of_doubledashsource(self):
- """Edge case: --source foo -m foo.bar"""
- # These -m commands assume the coverage tree is on the path.
- out_cov = self.run_command(
- "coverage run --source process_test -m process_test.try_execfile"
- )
- out_py = self.run_command("python -m process_test.try_execfile")
- self.assertMultiLineEqual(out_cov, out_py)
- self.assert_execfile_output(out_cov)
-
- st, out = self.run_command_status("coverage report")
- self.assertEqual(st, 0)
- self.assertEqual(self.line_count(out), 6, out)
-
- def test_coverage_run_script_imports_doubledashsource(self):
- # This file imports try_execfile, which compiles it to .pyc, so the
- # first run will have __file__ == "try_execfile.py" and the second will
- # have __file__ == "try_execfile.pyc", which throws off the comparison.
- # Setting dont_write_bytecode True stops the compilation to .pyc and
- # keeps the test working.
- self.make_file("myscript", """\
- import sys; sys.dont_write_bytecode = True
- import process_test.try_execfile
- """)
-
- # These -m commands assume the coverage tree is on the path.
- out_cov = self.run_command(
- "coverage run --source process_test myscript"
- )
- out_py = self.run_command("python myscript")
- self.assertMultiLineEqual(out_cov, out_py)
- self.assert_execfile_output(out_cov)
-
- st, out = self.run_command_status("coverage report")
- self.assertEqual(st, 0)
- self.assertEqual(self.line_count(out), 6, out)
-
- def test_coverage_run_dashm_is_like_python_dashm_off_path(self):
- # https://bitbucket.org/ned/coveragepy/issue/242
- self.make_file("sub/__init__.py", "")
- with open(TRY_EXECFILE) as f:
- self.make_file("sub/run_me.py", f.read())
- out_cov = self.run_command("coverage run -m sub.run_me")
- out_py = self.run_command("python -m sub.run_me")
- self.assertMultiLineEqual(out_cov, out_py)
- self.assert_execfile_output(out_cov)
-
- def test_coverage_run_dashm_is_like_python_dashm_with__main__207(self):
- if sys.version_info < (2, 7):
- # Coverage.py isn't bug-for-bug compatible in the behavior of -m for
- # Pythons < 2.7
- self.skipTest("-m doesn't work the same < Python 2.7")
- # https://bitbucket.org/ned/coveragepy/issue/207
- self.make_file("package/__init__.py", "print('init')")
- self.make_file("package/__main__.py", "print('main')")
- out_cov = self.run_command("coverage run -m package")
- out_py = self.run_command("python -m package")
- self.assertMultiLineEqual(out_cov, out_py)
-
def test_fork(self):
if not hasattr(os, 'fork'):
self.skipTest("Can't test os.fork since it doesn't exist.")
@@ -595,21 +484,6 @@ class ProcessTest(CoverageTest):
data.read_file(".coverage")
self.assertEqual(data.line_counts()['fork.py'], 9)
- def test_warnings(self):
- self.make_file("hello.py", """\
- import sys, os
- print("Hello")
- """)
- out = self.run_command("coverage run --source=sys,xyzzy,quux hello.py")
-
- self.assertIn("Hello\n", out)
- self.assertIn(textwrap.dedent("""\
- Coverage.py warning: Module sys has no Python source.
- Coverage.py warning: Module xyzzy was never imported.
- Coverage.py warning: Module quux was never imported.
- Coverage.py warning: No data was collected.
- """), out)
-
def test_warnings_during_reporting(self):
# While fixing issue #224, the warnings were being printed far too
# often. Make sure they're not any more.
@@ -692,7 +566,8 @@ class ProcessTest(CoverageTest):
self.assertEqual(len(infos), 1)
self.assertEqual(infos[0]['note'], u"These are musical notes: ♫𝅗𝅥♩")
- def test_fullcoverage(self): # pragma: not covered
+ @pytest.mark.expensive
+ def test_fullcoverage(self): # pragma: no metacov
if env.PY2: # This doesn't work on Python 2.
self.skipTest("fullcoverage doesn't work on Python 2.")
# It only works with the C tracer, and if we aren't measuring ourselves.
@@ -721,6 +596,26 @@ class ProcessTest(CoverageTest):
# about 5.
self.assertGreater(data.line_counts()['os.py'], 50)
+ def test_lang_c(self):
+ if env.PY3 and sys.version_info < (3, 4):
+ # Python 3.3 can't compile the non-ascii characters in the file name.
+ self.skipTest("3.3 can't handle this test")
+ # LANG=C forces getfilesystemencoding on Linux to 'ascii', which causes
+ # failures with non-ascii file names. We don't want to make a real file
+ # with strange characters, though, because that gets the test runners
+ # tangled up. This will isolate the concerns to the coverage.py code.
+ # https://bitbucket.org/ned/coveragepy/issues/533/exception-on-unencodable-file-name
+ self.make_file("weird_file.py", r"""
+ globs = {}
+ code = "a = 1\nb = 2\n"
+ exec(compile(code, "wut\xe9\xea\xeb\xec\x01\x02.py", 'exec'), globs)
+ print(globs['a'])
+ print(globs['b'])
+ """)
+ self.set_environ("LANG", "C")
+ out = self.run_command("coverage run weird_file.py")
+ self.assertEqual(out, "1\n2\n")
+
def test_deprecation_warnings(self):
# Test that coverage doesn't trigger deprecation warnings.
# https://bitbucket.org/ned/coveragepy/issue/305/pendingdeprecationwarning-the-imp-module
@@ -753,7 +648,8 @@ class ProcessTest(CoverageTest):
out = self.run_command("python run_twice.py")
self.assertEqual(
out,
- "Coverage.py warning: Module foo was previously imported, but not measured.\n"
+ "Coverage.py warning: Module foo was previously imported, but not measured. "
+ "(module-not-measured)\n"
)
def test_module_name(self):
@@ -766,11 +662,223 @@ class ProcessTest(CoverageTest):
self.assertIn("Use 'coverage help' for help", out)
+TRY_EXECFILE = os.path.join(os.path.dirname(__file__), "modules/process_test/try_execfile.py")
+
+class EnvironmentTest(CoverageTest):
+ """Tests using try_execfile.py to test the execution environment."""
+
+ def assert_tryexecfile_output(self, out1, out2):
+ """Assert that the output we got is a successful run of try_execfile.py.
+
+ `out1` and `out2` must be the same, modulo a few slight known platform
+ differences.
+
+ """
+ # First, is this even credible try_execfile.py output?
+ self.assertIn('"DATA": "xyzzy"', out1)
+
+ if env.JYTHON: # pragma: only jython
+ # Argv0 is different for Jython, remove that from the comparison.
+ out1 = re_lines(out1, r'\s+"argv0":', match=False)
+ out2 = re_lines(out2, r'\s+"argv0":', match=False)
+
+ self.assertMultiLineEqual(out1, out2)
+
+ def test_coverage_run_is_like_python(self):
+ with open(TRY_EXECFILE) as f:
+ self.make_file("run_me.py", f.read())
+ out_cov = self.run_command("coverage run run_me.py")
+ out_py = self.run_command("python run_me.py")
+ self.assert_tryexecfile_output(out_cov, out_py)
+
+ def test_coverage_run_dashm_is_like_python_dashm(self):
+ # These -m commands assume the coverage tree is on the path.
+ out_cov = self.run_command("coverage run -m process_test.try_execfile")
+ out_py = self.run_command("python -m process_test.try_execfile")
+ self.assert_tryexecfile_output(out_cov, out_py)
+
+ def test_coverage_run_dir_is_like_python_dir(self):
+ with open(TRY_EXECFILE) as f:
+ self.make_file("with_main/__main__.py", f.read())
+
+ out_cov = self.run_command("coverage run with_main")
+ out_py = self.run_command("python with_main")
+
+ # The coverage.py results are not identical to the Python results, and
+ # I don't know why. For now, ignore those failures. If someone finds
+ # a real problem with the discrepancies, we can work on it some more.
+ ignored = r"__file__|__loader__|__package__"
+ # PyPy includes the current directory in the path when running a
+ # directory, while CPython and coverage.py do not. Exclude that from
+ # the comparison also...
+ if env.PYPY:
+ ignored += "|"+re.escape(os.getcwd())
+ out_cov = re_lines(out_cov, ignored, match=False)
+ out_py = re_lines(out_py, ignored, match=False)
+ self.assert_tryexecfile_output(out_cov, out_py)
+
+ def test_coverage_run_dashm_equal_to_doubledashsource(self):
+ """regression test for #328
+
+ When imported by -m, a module's __name__ is __main__, but we need the
+ --source machinery to know and respect the original name.
+ """
+ # These -m commands assume the coverage tree is on the path.
+ out_cov = self.run_command(
+ "coverage run --source process_test.try_execfile -m process_test.try_execfile"
+ )
+ out_py = self.run_command("python -m process_test.try_execfile")
+ self.assert_tryexecfile_output(out_cov, out_py)
+
+ def test_coverage_run_dashm_superset_of_doubledashsource(self):
+ """Edge case: --source foo -m foo.bar"""
+ # These -m commands assume the coverage tree is on the path.
+ out_cov = self.run_command(
+ "coverage run --source process_test -m process_test.try_execfile"
+ )
+ out_py = self.run_command("python -m process_test.try_execfile")
+ self.assert_tryexecfile_output(out_cov, out_py)
+
+ st, out = self.run_command_status("coverage report")
+ self.assertEqual(st, 0)
+ self.assertEqual(self.line_count(out), 6, out)
+
+ def test_coverage_run_script_imports_doubledashsource(self):
+ # This file imports try_execfile, which compiles it to .pyc, so the
+ # first run will have __file__ == "try_execfile.py" and the second will
+ # have __file__ == "try_execfile.pyc", which throws off the comparison.
+ # Setting dont_write_bytecode True stops the compilation to .pyc and
+ # keeps the test working.
+ self.make_file("myscript", """\
+ import sys; sys.dont_write_bytecode = True
+ import process_test.try_execfile
+ """)
+
+ # These -m commands assume the coverage tree is on the path.
+ out_cov = self.run_command(
+ "coverage run --source process_test myscript"
+ )
+ out_py = self.run_command("python myscript")
+ self.assert_tryexecfile_output(out_cov, out_py)
+
+ st, out = self.run_command_status("coverage report")
+ self.assertEqual(st, 0)
+ self.assertEqual(self.line_count(out), 6, out)
+
+ def test_coverage_run_dashm_is_like_python_dashm_off_path(self):
+ # https://bitbucket.org/ned/coveragepy/issue/242
+ self.make_file("sub/__init__.py", "")
+ with open(TRY_EXECFILE) as f:
+ self.make_file("sub/run_me.py", f.read())
+
+ out_cov = self.run_command("coverage run -m sub.run_me")
+ out_py = self.run_command("python -m sub.run_me")
+ self.assert_tryexecfile_output(out_cov, out_py)
+
+ def test_coverage_run_dashm_is_like_python_dashm_with__main__207(self):
+ if sys.version_info < (2, 7):
+ # Coverage.py isn't bug-for-bug compatible in the behavior
+ # of -m for Pythons < 2.7
+ self.skipTest("-m doesn't work the same < Python 2.7")
+ # https://bitbucket.org/ned/coveragepy/issue/207
+ self.make_file("package/__init__.py", "print('init')")
+ self.make_file("package/__main__.py", "print('main')")
+ out_cov = self.run_command("coverage run -m package")
+ out_py = self.run_command("python -m package")
+ self.assertMultiLineEqual(out_cov, out_py)
+
+
+class ExcepthookTest(CoverageTest):
+ """Tests of sys.excepthook support."""
+
+ def test_excepthook(self):
+ self.make_file("excepthook.py", """\
+ import sys
+
+ def excepthook(*args):
+ print('in excepthook')
+ if maybe == 2:
+ print('definitely')
+
+ sys.excepthook = excepthook
+
+ maybe = 1
+ raise RuntimeError('Error Outside')
+ """)
+ cov_st, cov_out = self.run_command_status("coverage run excepthook.py")
+ py_st, py_out = self.run_command_status("python excepthook.py")
+ if not env.JYTHON:
+ self.assertEqual(cov_st, py_st)
+ self.assertEqual(cov_st, 1)
+
+ self.assertIn("in excepthook", py_out)
+ self.assertEqual(cov_out, py_out)
+
+ # Read the coverage file and see that excepthook.py has 7 lines
+ # executed.
+ data = coverage.CoverageData()
+ data.read_file(".coverage")
+ self.assertEqual(data.line_counts()['excepthook.py'], 7)
+
+ def test_excepthook_exit(self):
+ if env.PYPY or env.JYTHON:
+ self.skipTest("non-CPython handles excepthook exits differently, punt for now.")
+ self.make_file("excepthook_exit.py", """\
+ import sys
+
+ def excepthook(*args):
+ print('in excepthook')
+ sys.exit(0)
+
+ sys.excepthook = excepthook
+
+ raise RuntimeError('Error Outside')
+ """)
+ cov_st, cov_out = self.run_command_status("coverage run excepthook_exit.py")
+ py_st, py_out = self.run_command_status("python excepthook_exit.py")
+ self.assertEqual(cov_st, py_st)
+ self.assertEqual(cov_st, 0)
+
+ self.assertIn("in excepthook", py_out)
+ self.assertEqual(cov_out, py_out)
+
+ def test_excepthook_throw(self):
+ if env.PYPY:
+ self.skipTest("PyPy handles excepthook throws differently, punt for now.")
+ self.make_file("excepthook_throw.py", """\
+ import sys
+
+ def excepthook(*args):
+ # Write this message to stderr so that we don't have to deal
+ # with interleaved stdout/stderr comparisons in the assertions
+ # in the test.
+ sys.stderr.write('in excepthook\\n')
+ raise RuntimeError('Error Inside')
+
+ sys.excepthook = excepthook
+
+ raise RuntimeError('Error Outside')
+ """)
+ cov_st, cov_out = self.run_command_status("coverage run excepthook_throw.py")
+ py_st, py_out = self.run_command_status("python excepthook_throw.py")
+ if not env.JYTHON:
+ self.assertEqual(cov_st, py_st)
+ self.assertEqual(cov_st, 1)
+
+ self.assertIn("in excepthook", py_out)
+ self.assertEqual(cov_out, py_out)
+
+
class AliasedCommandTest(CoverageTest):
"""Tests of the version-specific command aliases."""
run_in_temp_dir = False
+ def setUp(self):
+ super(AliasedCommandTest, self).setUp()
+ if env.JYTHON:
+ self.skipTest("Coverage command names don't work on Jython")
+
def test_major_version_works(self):
# "coverage2" works on py2
cmd = "coverage%d" % sys.version_info[0]
@@ -779,6 +887,7 @@ class AliasedCommandTest(CoverageTest):
def test_wrong_alias_doesnt_work(self):
# "coverage3" doesn't work on py2
+ assert sys.version_info[0] in [2, 3] # Let us know when Python 4 is out...
badcmd = "coverage%d" % (5 - sys.version_info[0])
out = self.run_command(badcmd)
self.assertNotIn("Code coverage for Python", out)
@@ -850,125 +959,40 @@ class FailUnderTest(CoverageTest):
)
def test_report(self):
- st, _ = self.run_command_status("coverage report --fail-under=42")
- self.assertEqual(st, 0)
st, _ = self.run_command_status("coverage report --fail-under=43")
self.assertEqual(st, 0)
st, _ = self.run_command_status("coverage report --fail-under=44")
self.assertEqual(st, 2)
- def test_html_report(self):
- st, _ = self.run_command_status("coverage html --fail-under=42")
- self.assertEqual(st, 0)
- st, _ = self.run_command_status("coverage html --fail-under=43")
- self.assertEqual(st, 0)
- st, _ = self.run_command_status("coverage html --fail-under=44")
- self.assertEqual(st, 2)
-
- def test_xml_report(self):
- st, _ = self.run_command_status("coverage xml --fail-under=42")
- self.assertEqual(st, 0)
- st, _ = self.run_command_status("coverage xml --fail-under=43")
- self.assertEqual(st, 0)
- st, _ = self.run_command_status("coverage xml --fail-under=44")
- self.assertEqual(st, 2)
-
- def test_fail_under_in_config(self):
- self.make_file(".coveragerc", "[report]\nfail_under = 43\n")
- st, _ = self.run_command_status("coverage report")
- self.assertEqual(st, 0)
-
- self.make_file(".coveragerc", "[report]\nfail_under = 44\n")
- st, _ = self.run_command_status("coverage report")
- self.assertEqual(st, 2)
-
class FailUnderNoFilesTest(CoverageTest):
"""Test that nothing to report results in an error exit status."""
- def setUp(self):
- super(FailUnderNoFilesTest, self).setUp()
- self.make_file(".coveragerc", "[report]\nfail_under = 99\n")
-
def test_report(self):
+ self.make_file(".coveragerc", "[report]\nfail_under = 99\n")
st, out = self.run_command_status("coverage report")
self.assertIn('No data to report.', out)
self.assertEqual(st, 1)
- def test_xml(self):
- st, out = self.run_command_status("coverage xml")
- self.assertIn('No data to report.', out)
- self.assertEqual(st, 1)
-
- def test_html(self):
- st, out = self.run_command_status("coverage html")
- self.assertIn('No data to report.', out)
- self.assertEqual(st, 1)
-
class FailUnderEmptyFilesTest(CoverageTest):
"""Test that empty files produce the proper fail_under exit status."""
- def setUp(self):
- super(FailUnderEmptyFilesTest, self).setUp()
-
+ def test_report(self):
self.make_file(".coveragerc", "[report]\nfail_under = 99\n")
self.make_file("empty.py", "")
st, _ = self.run_command_status("coverage run empty.py")
self.assertEqual(st, 0)
-
- def test_report(self):
st, _ = self.run_command_status("coverage report")
self.assertEqual(st, 2)
- def test_xml(self):
- st, _ = self.run_command_status("coverage xml")
- self.assertEqual(st, 2)
-
- def test_html(self):
- st, _ = self.run_command_status("coverage html")
- self.assertEqual(st, 2)
-
-
-class FailUnder100Test(CoverageTest):
- """Tests of the --fail-under switch."""
-
- def test_99_8(self):
- self.make_file("ninety_nine_eight.py",
- "".join("v{i} = {i}\n".format(i=i) for i in range(498)) +
- "if v0 > 498:\n v499 = 499\n"
- )
- st, _ = self.run_command_status("coverage run ninety_nine_eight.py")
- self.assertEqual(st, 0)
- st, out = self.run_command_status("coverage report")
- self.assertEqual(st, 0)
- self.assertEqual(
- self.last_line_squeezed(out),
- "ninety_nine_eight.py 500 1 99%"
- )
-
- st, _ = self.run_command_status("coverage report --fail-under=100")
- self.assertEqual(st, 2)
-
-
- def test_100(self):
- self.make_file("one_hundred.py",
- "".join("v{i} = {i}\n".format(i=i) for i in range(500))
- )
- st, _ = self.run_command_status("coverage run one_hundred.py")
- self.assertEqual(st, 0)
- st, out = self.run_command_status("coverage report")
- self.assertEqual(st, 0)
- self.assertEqual(
- self.last_line_squeezed(out),
- "one_hundred.py 500 0 100%"
- )
-
- st, _ = self.run_command_status("coverage report --fail-under=100")
- self.assertEqual(st, 0)
-
class UnicodeFilePathsTest(CoverageTest):
"""Tests of using non-ascii characters in the names of files."""
+ def setUp(self):
+ super(UnicodeFilePathsTest, self).setUp()
+ if env.JYTHON:
+ self.skipTest("Jython doesn't like accented file names")
+
def test_accented_dot_py(self):
# Make a file with a non-ascii character in the filename.
self.make_file(u"h\xe2t.py", "print('accented')")
@@ -998,7 +1022,6 @@ class UnicodeFilePathsTest(CoverageTest):
)
if env.PY2:
- # pylint: disable=redefined-variable-type
report_expected = report_expected.encode(output_encoding())
out = self.run_command("coverage report")
@@ -1037,7 +1060,6 @@ class UnicodeFilePathsTest(CoverageTest):
)
if env.PY2:
- # pylint: disable=redefined-variable-type
report_expected = report_expected.encode(output_encoding())
out = self.run_command("coverage report")
@@ -1046,17 +1068,35 @@ class UnicodeFilePathsTest(CoverageTest):
def possible_pth_dirs():
"""Produce a sequence of directories for trying to write .pth files."""
- # First look through sys.path, and we find a .pth file, then it's a good
+ # First look through sys.path, and if we find a .pth file, then it's a good
# place to put ours.
- for d in sys.path:
- g = glob.glob(os.path.join(d, "*.pth"))
- if g:
- yield d
+ for pth_dir in sys.path: # pragma: part covered
+ pth_files = glob.glob(os.path.join(pth_dir, "*.pth"))
+ if pth_files:
+ yield pth_dir
# If we're still looking, then try the Python library directory.
# https://bitbucket.org/ned/coveragepy/issue/339/pth-test-malfunctions
- import distutils.sysconfig # pylint: disable=import-error
- yield distutils.sysconfig.get_python_lib()
+ yield distutils.sysconfig.get_python_lib() # pragma: cant happen
+
+
+def find_writable_pth_directory():
+ """Find a place to write a .pth file."""
+ for pth_dir in possible_pth_dirs(): # pragma: part covered
+ try_it = os.path.join(pth_dir, "touch_{0}.it".format(WORKER))
+ with open(try_it, "w") as f:
+ try:
+ f.write("foo")
+ except (IOError, OSError): # pragma: cant happen
+ continue
+
+ os.remove(try_it)
+ return pth_dir
+
+ return None # pragma: cant happen
+
+WORKER = os.environ.get('PYTEST_XDIST_WORKER', '')
+PTH_DIR = find_writable_pth_directory()
class ProcessCoverageMixin(object):
@@ -1064,19 +1104,14 @@ class ProcessCoverageMixin(object):
def setUp(self):
super(ProcessCoverageMixin, self).setUp()
- # Find a place to put a .pth file.
+
+ # Create the .pth file.
+ self.assertTrue(PTH_DIR)
pth_contents = "import coverage; coverage.process_startup()\n"
- for pth_dir in possible_pth_dirs(): # pragma: part covered
- pth_path = os.path.join(pth_dir, "subcover.pth")
- with open(pth_path, "w") as pth:
- try:
- pth.write(pth_contents)
- self.pth_path = pth_path
- break
- except (IOError, OSError): # pragma: not covered
- pass
- else: # pragma: not covered
- raise Exception("Couldn't find a place for the .pth file")
+ pth_path = os.path.join(PTH_DIR, "subcover_{0}.pth".format(WORKER))
+ with open(pth_path, "w") as pth:
+ pth.write(pth_contents)
+ self.pth_path = pth_path
self.addCleanup(os.remove, self.pth_path)
@@ -1095,11 +1130,12 @@ class ProcessStartupTest(ProcessCoverageMixin, CoverageTest):
""")
# sub.py will write a few lines.
self.make_file("sub.py", """\
- with open("out.txt", "w") as f:
- f.write("Hello, world!\\n")
+ f = open("out.txt", "w")
+ f.write("Hello, world!\\n")
+ f.close()
""")
- def test_subprocess_with_pth_files(self): # pragma: not covered
+ def test_subprocess_with_pth_files(self): # pragma: no metacov
if env.METACOV:
self.skipTest("Can't test sub-process pth file suppport during metacoverage")
@@ -1124,9 +1160,9 @@ class ProcessStartupTest(ProcessCoverageMixin, CoverageTest):
self.assert_exists(".mycovdata")
data = coverage.CoverageData()
data.read_file(".mycovdata")
- self.assertEqual(data.line_counts()['sub.py'], 2)
+ self.assertEqual(data.line_counts()['sub.py'], 3)
- def test_subprocess_with_pth_files_and_parallel(self): # pragma: not covered
+ def test_subprocess_with_pth_files_and_parallel(self): # pragma: no metacov
# https://bitbucket.org/ned/coveragepy/issues/492/subprocess-coverage-strange-detection-of
if env.METACOV:
self.skipTest("Can't test sub-process pth file suppport during metacoverage")
@@ -1148,12 +1184,12 @@ class ProcessStartupTest(ProcessCoverageMixin, CoverageTest):
self.assert_exists(".coverage")
data = coverage.CoverageData()
data.read_file(".coverage")
- self.assertEqual(data.line_counts()['sub.py'], 2)
+ self.assertEqual(data.line_counts()['sub.py'], 3)
# assert that there are *no* extra data files left over after a combine
data_files = glob.glob(os.getcwd() + '/.coverage*')
- self.assertEquals(len(data_files), 1,
- "Expected only .coverage after combine, looks like there are " + \
+ self.assertEqual(len(data_files), 1,
+ "Expected only .coverage after combine, looks like there are "
"extra data files that were not cleaned up: %r" % data_files)
@@ -1173,7 +1209,7 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest):
def assert_pth_and_source_work_together(
self, dashm, package, source
- ): # pragma: not covered
+ ): # pragma: no metacov
"""Run the test for a particular combination of factors.
The arguments are all strings:
@@ -1205,14 +1241,17 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest):
# Main will run sub.py.
self.make_file(path("main.py"), """\
import %s
- if True: pass
+ a = 2
+ b = 3
""" % fullname('sub'))
if package:
self.make_file(path("__init__.py"), "")
# sub.py will write a few lines.
self.make_file(path("sub.py"), """\
- with open("out.txt", "w") as f:
- f.write("Hello, world!")
+ # Avoid 'with' so Jython can play along.
+ f = open("out.txt", "w")
+ f.write("Hello, world!")
+ f.close()
""")
self.make_file("coverage.ini", """\
[run]
@@ -1237,7 +1276,7 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest):
data.read_file(".coverage")
summary = data.line_counts()
print(summary)
- self.assertEqual(summary[source + '.py'], 2)
+ self.assertEqual(summary[source + '.py'], 3)
self.assertEqual(len(summary), 1)
def test_dashm_main(self):
@@ -1263,9 +1302,3 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest):
def test_script_pkg_sub(self):
self.assert_pth_and_source_work_together('', 'pkg', 'sub')
-
-
-def remove_matching_lines(text, pat):
- """Return `text` with all lines matching `pat` removed."""
- lines = [l for l in text.splitlines(True) if not re.search(pat, l)]
- return "".join(lines)
diff --git a/tests/test_python.py b/tests/test_python.py
index ee1e1f9..9027aa6 100644
--- a/tests/test_python.py
+++ b/tests/test_python.py
@@ -6,7 +6,10 @@
import os
import sys
-from coverage.python import get_zip_bytes
+import pytest
+
+from coverage import env
+from coverage.python import get_zip_bytes, source_for_file
from tests.coveragetest import CoverageTest
@@ -28,3 +31,31 @@ class GetZipBytesTest(CoverageTest):
self.assertIn('All OK', zip_text)
# Run the code to see that we really got it encoded properly.
__import__("encoded_"+encoding)
+
+
+def test_source_for_file(tmpdir):
+ path = tmpdir.join("a.py")
+ src = str(path)
+ assert source_for_file(src) == src
+ assert source_for_file(src + 'c') == src
+ assert source_for_file(src + 'o') == src
+ unknown = src + 'FOO'
+ assert source_for_file(unknown) == unknown
+
+
+@pytest.mark.skipif(not env.WINDOWS, reason="not windows")
+def test_source_for_file_windows(tmpdir):
+ path = tmpdir.join("a.py")
+ src = str(path)
+
+ # On windows if a pyw exists, it is an acceptable source
+ path_windows = tmpdir.ensure("a.pyw")
+ assert str(path_windows) == source_for_file(src + 'c')
+
+ # If both pyw and py exist, py is preferred
+ path.ensure(file=True)
+ assert source_for_file(src + 'c') == src
+
+
+def test_source_for_file_jython():
+ assert source_for_file("a$py.class") == "a.py"
diff --git a/tests/test_results.py b/tests/test_results.py
index 54c2f6d..280694d 100644
--- a/tests/test_results.py
+++ b/tests/test_results.py
@@ -3,7 +3,10 @@
"""Tests for coverage.py's results analysis."""
-from coverage.results import Numbers
+import pytest
+
+from coverage.results import Numbers, should_fail_under
+
from tests.coveragetest import CoverageTest
@@ -40,6 +43,8 @@ class NumbersTest(CoverageTest):
self.assertAlmostEqual(n3.pc_covered, 86.666666666)
def test_pc_covered_str(self):
+ # Numbers._precision is a global, which is bad.
+ Numbers.set_precision(0)
n0 = Numbers(n_files=1, n_statements=1000, n_missing=0)
n1 = Numbers(n_files=1, n_statements=1000, n_missing=1)
n999 = Numbers(n_files=1, n_statements=1000, n_missing=999)
@@ -50,7 +55,7 @@ class NumbersTest(CoverageTest):
self.assertEqual(n1000.pc_covered_str, "0")
def test_pc_covered_str_precision(self):
- assert Numbers._precision == 0
+ # Numbers._precision is a global, which is bad.
Numbers.set_precision(1)
n0 = Numbers(n_files=1, n_statements=10000, n_missing=0)
n1 = Numbers(n_files=1, n_statements=10000, n_missing=1)
@@ -71,3 +76,23 @@ class NumbersTest(CoverageTest):
n_branches=10, n_missing_branches=3, n_partial_branches=1000,
)
self.assertEqual(n.ratio_covered, (160, 210))
+
+
+@pytest.mark.parametrize("total, fail_under, result", [
+ # fail_under==0 means anything is fine!
+ (0, 0, False),
+ (0.001, 0, False),
+ # very small fail_under is possible to fail.
+ (0.001, 0.01, True),
+ # Rounding should work properly.
+ (42.1, 42, False),
+ (42.1, 43, True),
+ (42.857, 42, False),
+ (42.857, 43, False),
+ (42.857, 44, True),
+ # Values near 100 should only be treated as 100 if they are 100.
+ (99.8, 100, True),
+ (100.0, 100, False),
+])
+def test_should_fail_under(total, fail_under, result):
+ assert should_fail_under(total, fail_under) == result
diff --git a/tests/test_setup.py b/tests/test_setup.py
new file mode 100644
index 0000000..6533418
--- /dev/null
+++ b/tests/test_setup.py
@@ -0,0 +1,47 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+"""Tests of miscellaneous stuff."""
+
+import sys
+
+import coverage
+
+from tests.coveragetest import CoverageTest
+
+
+class SetupPyTest(CoverageTest):
+ """Tests of setup.py"""
+
+ run_in_temp_dir = False
+
+ def setUp(self):
+ super(SetupPyTest, self).setUp()
+ # Force the most restrictive interpretation.
+ self.set_environ('LC_ALL', 'C')
+
+ def test_metadata(self):
+ status, output = self.run_command_status(
+ "python setup.py --description --version --url --author"
+ )
+ self.assertEqual(status, 0)
+ out = output.splitlines()
+ self.assertIn("measurement", out[0])
+ self.assertEqual(out[1], coverage.__version__)
+ self.assertEqual(out[2], coverage.__url__)
+ self.assertIn("Ned Batchelder", out[3])
+
+ def test_more_metadata(self):
+ # Let's be sure we pick up our own setup.py
+ # CoverageTest restores the original sys.path for us.
+ sys.path.insert(0, '')
+ from setup import setup_args
+
+ classifiers = setup_args['classifiers']
+ self.assertGreater(len(classifiers), 7)
+ self.assert_starts_with(classifiers[-1], "Development Status ::")
+
+ long_description = setup_args['long_description'].splitlines()
+ self.assertGreater(len(long_description), 7)
+ self.assertNotEqual(long_description[0].strip(), "")
+ self.assertNotEqual(long_description[-1].strip(), "")
diff --git a/tests/test_summary.py b/tests/test_summary.py
index bda6568..7c9f4c1 100644
--- a/tests/test_summary.py
+++ b/tests/test_summary.py
@@ -1,4 +1,4 @@
-# coding: utf8
+# coding: utf-8
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
@@ -361,6 +361,27 @@ class SummaryTest(CoverageTest):
squeezed = self.squeezed_lines(report)
self.assertEqual(squeezed[3], "1 file skipped due to complete coverage.")
+ def test_report_skip_covered_longfilename(self):
+ self.make_file("long_______________filename.py", """
+ def foo():
+ pass
+ foo()
+ """)
+ out = self.run_command("coverage run --branch long_______________filename.py")
+ self.assertEqual(out, "")
+ report = self.report_from_command("coverage report --skip-covered")
+
+ # Name Stmts Miss Branch BrPart Cover
+ # -----------------------------------------
+ #
+ # 1 file skipped due to complete coverage.
+
+ self.assertEqual(self.line_count(report), 4, report)
+ lines = self.report_lines(report)
+ self.assertEqual(lines[0], "Name Stmts Miss Branch BrPart Cover")
+ squeezed = self.squeezed_lines(report)
+ self.assertEqual(squeezed[3], "1 file skipped due to complete coverage.")
+
def test_report_skip_covered_no_data(self):
report = self.report_from_command("coverage report --skip-covered")
@@ -381,22 +402,25 @@ class SummaryTest(CoverageTest):
self.make_file("mycode.py", "This isn't python at all!")
report = self.report_from_command("coverage report mycode.py")
+ # mycode NotPython: Couldn't parse '...' as Python source: 'invalid syntax' at line 1
# Name Stmts Miss Cover
# ----------------------------
- # mycode NotPython: Couldn't parse '...' as Python source: 'invalid syntax' at line 1
# No data to report.
- last = self.squeezed_lines(report)[-2]
+ errmsg = self.squeezed_lines(report)[0]
# The actual file name varies run to run.
- last = re.sub(r"parse '.*mycode.py", "parse 'mycode.py", last)
+ errmsg = re.sub(r"parse '.*mycode.py", "parse 'mycode.py", errmsg)
# The actual error message varies version to version
- last = re.sub(r": '.*' at", ": 'error' at", last)
+ errmsg = re.sub(r": '.*' at", ": 'error' at", errmsg)
self.assertEqual(
- last,
+ errmsg,
"mycode.py NotPython: Couldn't parse 'mycode.py' as Python source: 'error' at line 1"
)
def test_accenteddotpy_not_python(self):
+ if env.JYTHON:
+ self.skipTest("Jython doesn't like accented file names")
+
# We run a .py file with a non-ascii name, and when reporting, we can't
# parse it as Python. We should get an error message in the report.
@@ -405,24 +429,23 @@ class SummaryTest(CoverageTest):
self.make_file(u"accented\xe2.py", "This isn't python at all!")
report = self.report_from_command(u"coverage report accented\xe2.py")
+ # xxxx NotPython: Couldn't parse '...' as Python source: 'invalid syntax' at line 1
# Name Stmts Miss Cover
# ----------------------------
- # xxxx NotPython: Couldn't parse '...' as Python source: 'invalid syntax' at line 1
# No data to report.
- last = self.squeezed_lines(report)[-2]
+ errmsg = self.squeezed_lines(report)[0]
# The actual file name varies run to run.
- last = re.sub(r"parse '.*(accented.*?\.py)", r"parse '\1", last)
+ errmsg = re.sub(r"parse '.*(accented.*?\.py)", r"parse '\1", errmsg)
# The actual error message varies version to version
- last = re.sub(r": '.*' at", ": 'error' at", last)
+ errmsg = re.sub(r": '.*' at", ": 'error' at", errmsg)
expected = (
u"accented\xe2.py NotPython: "
u"Couldn't parse 'accented\xe2.py' as Python source: 'error' at line 1"
)
if env.PY2:
- # pylint: disable=redefined-variable-type
expected = expected.encode(output_encoding())
- self.assertEqual(last, expected)
+ self.assertEqual(errmsg, expected)
def test_dotpy_not_python_ignored(self):
# We run a .py file, and when reporting, we can't parse it as Python,
@@ -564,7 +587,7 @@ class SummaryTest(CoverageTest):
# Python 3 puts the .pyc files in a __pycache__ directory, and will
# not import from there without source. It will import a .pyc from
# the source location though.
- if not os.path.exists("mod.pyc"):
+ if env.PY3 and not env.JYTHON:
pycs = glob.glob("__pycache__/mod.*.pyc")
self.assertEqual(len(pycs), 1)
os.rename(pycs[0], "mod.pyc")
@@ -656,7 +679,7 @@ class TestSummaryReporterConfiguration(CoverageTest):
HERE = os.path.dirname(__file__)
LINES_1 = {
- os.path.join(HERE, "test_api.py"): dict.fromkeys(range(300)),
+ os.path.join(HERE, "test_api.py"): dict.fromkeys(range(400)),
os.path.join(HERE, "test_backward.py"): dict.fromkeys(range(20)),
os.path.join(HERE, "test_coverage.py"): dict.fromkeys(range(15)),
}
@@ -738,6 +761,14 @@ class TestSummaryReporterConfiguration(CoverageTest):
report = self.get_summary_text(data, opts)
self.assert_ordering(report, "test_backward.py", "test_coverage.py", "test_api.py")
+ def test_sort_report_by_missing(self):
+ # Sort the text report by the Missing column.
+ data = self.get_coverage_data(self.LINES_1)
+ opts = CoverageConfig()
+ opts.from_args(sort='Miss')
+ report = self.get_summary_text(data, opts)
+ self.assert_ordering(report, "test_backward.py", "test_api.py", "test_coverage.py")
+
def test_sort_report_by_cover(self):
# Sort the text report by the Cover column.
data = self.get_coverage_data(self.LINES_1)
diff --git a/tests/test_templite.py b/tests/test_templite.py
index 1df942e..bcc65f9 100644
--- a/tests/test_templite.py
+++ b/tests/test_templite.py
@@ -1,4 +1,4 @@
-# -*- coding: utf8 -*-
+# coding: utf-8
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
diff --git a/tests/test_testing.py b/tests/test_testing.py
index c5858bf..05bf0c9 100644
--- a/tests/test_testing.py
+++ b/tests/test_testing.py
@@ -8,11 +8,15 @@ import datetime
import os
import sys
+import pytest
+
import coverage
-from coverage.backunittest import TestCase
+from coverage.backunittest import TestCase, unittest
from coverage.files import actual_path
+from coverage.misc import StopEverything
-from tests.coveragetest import CoverageTest
+from tests.coveragetest import CoverageTest, convert_skip_exceptions
+from tests.helpers import CheckUniqueFilenames, re_lines, re_line
class TestingTest(TestCase):
@@ -110,14 +114,38 @@ class CoverageTestTest(CoverageTest):
with self.assert_warnings(cov, ["Not me"]):
cov._warn("Hello there!")
+ # Try checking a warning that shouldn't appear: happy case.
+ with self.assert_warnings(cov, ["Hi"], not_warnings=["Bye"]):
+ cov._warn("Hi")
+
+ # But it should fail if the unexpected warning does appear.
+ warn_regex = r"Found warning 'Bye' in \['Hi', 'Bye'\]"
+ with self.assertRaisesRegex(AssertionError, warn_regex):
+ with self.assert_warnings(cov, ["Hi"], not_warnings=["Bye"]):
+ cov._warn("Hi")
+ cov._warn("Bye")
+
# assert_warnings shouldn't hide a real exception.
with self.assertRaises(ZeroDivisionError):
with self.assert_warnings(cov, ["Hello there!"]):
raise ZeroDivisionError("oops")
+ def test_assert_no_warnings(self):
+ cov = coverage.Coverage()
+
+ # Happy path: no warnings.
+ with self.assert_warnings(cov, []):
+ pass
+
+ # If you said there would be no warnings, and there were, fail!
+ warn_regex = r"Unexpected warnings: \['Watch out!'\]"
+ with self.assertRaisesRegex(AssertionError, warn_regex):
+ with self.assert_warnings(cov, []):
+ cov._warn("Watch out!")
+
def test_sub_python_is_this_python(self):
# Try it with a Python command.
- os.environ['COV_FOOBAR'] = 'XYZZY'
+ self.set_environ('COV_FOOBAR', 'XYZZY')
self.make_file("showme.py", """\
import os, sys
print(sys.executable)
@@ -130,17 +158,97 @@ class CoverageTestTest(CoverageTest):
self.assertEqual(out[2], 'XYZZY')
# Try it with a "coverage debug sys" command.
- out = self.run_command("coverage debug sys").splitlines()
- # "environment: COV_FOOBAR = XYZZY" or "COV_FOOBAR = XYZZY"
- executable = next(l for l in out if "executable:" in l) # pragma: part covered
+ out = self.run_command("coverage debug sys")
+
+ executable = re_line(out, "executable:")
executable = executable.split(":", 1)[1].strip()
- self.assertTrue(same_python_executable(executable, sys.executable))
- environ = next(l for l in out if "COV_FOOBAR" in l) # pragma: part covered
+ self.assertTrue(_same_python_executable(executable, sys.executable))
+
+ # "environment: COV_FOOBAR = XYZZY" or "COV_FOOBAR = XYZZY"
+ environ = re_line(out, "COV_FOOBAR")
_, _, environ = environ.rpartition(":")
self.assertEqual(environ.strip(), "COV_FOOBAR = XYZZY")
-def same_python_executable(e1, e2):
+class CheckUniqueFilenamesTest(CoverageTest):
+ """Tests of CheckUniqueFilenames."""
+
+ run_in_temp_dir = False
+
+ class Stub(object):
+ """A stand-in for the class we're checking."""
+ def __init__(self, x):
+ self.x = x
+
+ def method(self, filename, a=17, b="hello"):
+ """The method we'll wrap, with args to be sure args work."""
+ return (self.x, filename, a, b)
+
+ def test_detect_duplicate(self):
+ stub = self.Stub(23)
+ CheckUniqueFilenames.hook(stub, "method")
+
+ # Two method calls with different names are fine.
+ assert stub.method("file1") == (23, "file1", 17, "hello")
+ assert stub.method("file2", 1723, b="what") == (23, "file2", 1723, "what")
+
+ # A duplicate file name trips an assertion.
+ with self.assertRaises(AssertionError):
+ stub.method("file1")
+
+
+@pytest.mark.parametrize("text, pat, result", [
+ ("line1\nline2\nline3\n", "line", "line1\nline2\nline3\n"),
+ ("line1\nline2\nline3\n", "[13]", "line1\nline3\n"),
+ ("line1\nline2\nline3\n", "X", ""),
+])
+def test_re_lines(text, pat, result):
+ assert re_lines(text, pat) == result
+
+@pytest.mark.parametrize("text, pat, result", [
+ ("line1\nline2\nline3\n", "line", ""),
+ ("line1\nline2\nline3\n", "[13]", "line2\n"),
+ ("line1\nline2\nline3\n", "X", "line1\nline2\nline3\n"),
+])
+def test_re_lines_inverted(text, pat, result):
+ assert re_lines(text, pat, match=False) == result
+
+@pytest.mark.parametrize("text, pat, result", [
+ ("line1\nline2\nline3\n", "2", "line2"),
+])
+def test_re_line(text, pat, result):
+ assert re_line(text, pat) == result
+
+@pytest.mark.parametrize("text, pat", [
+ ("line1\nline2\nline3\n", "line"), # too many matches
+ ("line1\nline2\nline3\n", "X"), # no matches
+])
+def test_re_line_bad(text, pat):
+ with pytest.raises(AssertionError):
+ re_line(text, pat)
+
+
+def test_convert_skip_exceptions():
+ @convert_skip_exceptions
+ def some_method(ret=None, exc=None):
+ """Be like a test case."""
+ if exc:
+ raise exc("yikes!")
+ return ret
+
+ # Normal flow is normal.
+ assert some_method(ret=[17, 23]) == [17, 23]
+
+ # Exceptions are raised normally.
+ with pytest.raises(ValueError):
+ some_method(exc=ValueError)
+
+ # But a StopEverything becomes a SkipTest.
+ with pytest.raises(unittest.SkipTest):
+ some_method(exc=StopEverything)
+
+
+def _same_python_executable(e1, e2):
"""Determine if `e1` and `e2` refer to the same Python executable.
Either path could include symbolic links. The two paths might not refer
diff --git a/tests/test_version.py b/tests/test_version.py
new file mode 100644
index 0000000..eb8de87
--- /dev/null
+++ b/tests/test_version.py
@@ -0,0 +1,39 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+"""Tests of version.py."""
+
+import coverage
+from coverage.version import _make_url, _make_version
+
+from tests.coveragetest import CoverageTest
+
+
+class VersionTest(CoverageTest):
+ """Tests of version.py"""
+
+ run_in_temp_dir = False
+
+ def test_version_info(self):
+ # Make sure we didn't screw up the version_info tuple.
+ self.assertIsInstance(coverage.version_info, tuple)
+ self.assertEqual([type(d) for d in coverage.version_info], [int, int, int, str, int])
+ self.assertIn(coverage.version_info[3], ['alpha', 'beta', 'candidate', 'final'])
+
+ def test_make_version(self):
+ self.assertEqual(_make_version(4, 0, 0, 'alpha', 0), "4.0a0")
+ self.assertEqual(_make_version(4, 0, 0, 'alpha', 1), "4.0a1")
+ self.assertEqual(_make_version(4, 0, 0, 'final', 0), "4.0")
+ self.assertEqual(_make_version(4, 1, 2, 'beta', 3), "4.1.2b3")
+ self.assertEqual(_make_version(4, 1, 2, 'final', 0), "4.1.2")
+ self.assertEqual(_make_version(5, 10, 2, 'candidate', 7), "5.10.2rc7")
+
+ def test_make_url(self):
+ self.assertEqual(
+ _make_url(4, 0, 0, 'final', 0),
+ "https://coverage.readthedocs.io"
+ )
+ self.assertEqual(
+ _make_url(4, 1, 2, 'beta', 3),
+ "https://coverage.readthedocs.io/en/coverage-4.1.2b3"
+ )
diff --git a/tests/test_xml.py b/tests/test_xml.py
index dd14b92..c3493e7 100644
--- a/tests/test_xml.py
+++ b/tests/test_xml.py
@@ -1,3 +1,4 @@
+# coding: utf-8
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
@@ -8,11 +9,13 @@ import os.path
import re
import coverage
+from coverage.backward import import_local_file
from coverage.files import abs_file
from tests.coveragetest import CoverageTest
from tests.goldtest import CoverageGoldTest
from tests.goldtest import change_dir, compare
+from tests.helpers import re_line, re_lines
class XmlTestHelpers(CoverageTest):
@@ -165,8 +168,8 @@ class XmlReportTest(XmlTestHelpers, CoverageTest):
self.make_file("also/over/there/bar.py", "b = 2")
cov = coverage.Coverage(source=["src/main", "also/over/there", "not/really"])
cov.start()
- mod_foo = self.import_local_file("foo", "src/main/foo.py") # pragma: nested
- mod_bar = self.import_local_file("bar", "also/over/there/bar.py") # pragma: nested
+ mod_foo = import_local_file("foo", "src/main/foo.py") # pragma: nested
+ mod_bar = import_local_file("bar", "also/over/there/bar.py") # pragma: nested
cov.stop() # pragma: nested
cov.xml_report([mod_foo, mod_bar], outfile="-")
xml = self.stdout()
@@ -184,6 +187,14 @@ class XmlReportTest(XmlTestHelpers, CoverageTest):
xml
)
+ def test_nonascii_directory(self):
+ # https://bitbucket.org/ned/coveragepy/issues/573/cant-generate-xml-report-if-some-source
+ self.make_file("테스트/program.py", "a = 1")
+ with change_dir("테스트"):
+ cov = coverage.Coverage()
+ self.start_import_stop(cov, "program")
+ cov.xml_report()
+
class XmlPackageStructureTest(XmlTestHelpers, CoverageTest):
"""Tests about the package structure reported in the coverage.xml file."""
@@ -194,7 +205,7 @@ class XmlPackageStructureTest(XmlTestHelpers, CoverageTest):
cov.xml_report(outfile="-")
packages_and_classes = re_lines(self.stdout(), r"<package |<class ")
scrubs = r' branch-rate="0"| complexity="0"| line-rate="[\d.]+"'
- return clean("".join(packages_and_classes), scrubs)
+ return clean(packages_and_classes, scrubs)
def assert_package_and_class_tags(self, cov, result):
"""Check the XML package and class tags from `cov` match `result`."""
@@ -282,19 +293,6 @@ class XmlPackageStructureTest(XmlTestHelpers, CoverageTest):
""")
-def re_lines(text, pat):
- """Return a list of lines that match `pat` in the string `text`."""
- lines = [l for l in text.splitlines(True) if re.search(pat, l)]
- return lines
-
-
-def re_line(text, pat):
- """Return the one line in `text` that matches regex `pat`."""
- lines = re_lines(text, pat)
- assert len(lines) == 1
- return lines[0]
-
-
def clean(text, scrub=None):
"""Clean text to prepare it for comparison.