diff options
Diffstat (limited to 'tests')
41 files changed, 1184 insertions, 637 deletions
diff --git a/tests/backtest.py b/tests/backtest.py index 89a25536..439493d1 100644 --- a/tests/backtest.py +++ b/tests/backtest.py @@ -4,41 +4,31 @@ # (Redefining built-in blah) # The whole point of this file is to redefine built-ins, so shut up about it. -import os +import subprocess -# Py2 and Py3 don't agree on how to run commands in a subprocess. -try: - import subprocess -except ImportError: - def run_command(cmd, status=0): - """Run a command in a subprocess. - - Returns the exit status code and the combined stdout and stderr. - """ - _, stdouterr = os.popen4(cmd) - return status, stdouterr.read() +# This isn't really a backward compatibility thing, should be moved into a +# helpers file or something. +def run_command(cmd): + """Run a command in a subprocess. -else: - def run_command(cmd, status=0): - """Run a command in a subprocess. + Returns the exit status code and the combined stdout and stderr. - Returns the exit status code and the combined stdout and stderr. + """ + proc = subprocess.Popen(cmd, shell=True, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + output, _ = proc.communicate() + status = proc.returncode # pylint: disable=E1101 - """ - proc = subprocess.Popen(cmd, shell=True, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT - ) - output, _ = proc.communicate() - status = proc.returncode # pylint: disable=E1101 + # Get the output, and canonicalize it to strings with newlines. + if not isinstance(output, str): + output = output.decode('utf-8') + output = output.replace('\r', '') - # Get the output, and canonicalize it to strings with newlines. - if not isinstance(output, str): - output = output.decode('utf-8') - output = output.replace('\r', '') + return status, output - return status, output # No more execfile in Py3 try: @@ -46,4 +36,6 @@ try: except NameError: def execfile(filename, globs): """A Python 3 implementation of execfile.""" - exec(compile(open(filename).read(), filename, 'exec'), globs) + with open(filename) as fobj: + code = fobj.read() + exec(compile(code, filename, 'exec'), globs) diff --git a/tests/backunittest.py b/tests/backunittest.py deleted file mode 100644 index ca741d37..00000000 --- a/tests/backunittest.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Implementations of unittest features from the future.""" - -# Use unittest2 if it's available, otherwise unittest. This gives us -# backported features for 2.6. -try: - import unittest2 as unittest # pylint: disable=F0401 -except ImportError: - import unittest - - -def _need(method): - """Do we need to define our own `method` method?""" - return not hasattr(unittest.TestCase, method) - - -class TestCase(unittest.TestCase): - """Just like unittest.TestCase, but with assert methods added. - - Designed to be compatible with 3.1 unittest. Methods are only defined if - `unittest` doesn't have them. - - """ - if _need('assertSameElements'): - def assertSameElements(self, s1, s2): - """Assert that the two arguments are equal as sets.""" - self.assertEqual(set(s1), set(s2)) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 0467d808..4053059f 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -1,47 +1,39 @@ """Base test case class for coverage testing.""" -import glob, imp, os, random, shlex, shutil, sys, tempfile, textwrap -import atexit, collections +import glob, os, random, re, shlex, shutil, sys import coverage -from coverage.backward import StringIO, to_bytes +from coverage.backunittest import TestCase +from coverage.backward import StringIO, import_local_file +from coverage.backward import importlib # pylint: disable=unused-import from coverage.control import _TEST_NAME_FILE -from tests.backtest import run_command -from tests.backunittest import TestCase - -class Tee(object): - """A file-like that writes to all the file-likes it has.""" - - def __init__(self, *files): - """Make a Tee that writes to all the files in `files.`""" - self._files = files - if hasattr(files[0], "encoding"): - self.encoding = files[0].encoding - - def write(self, data): - """Write `data` to all the files.""" - for f in self._files: - f.write(data) +from coverage.test_helpers import ( + ModuleAwareMixin, SysPathAwareMixin, EnvironmentAwareMixin, + StdStreamCapturingMixin, TempDirMixin, +) - if 0: - # Use this if you need to use a debugger, though it makes some tests - # fail, I'm not sure why... - def __getattr__(self, name): - return getattr(self._files[0], name) +from tests.backtest import run_command # Status returns for the command line. OK, ERR = 0, 1 -class CoverageTest(TestCase): +class CoverageTest( + ModuleAwareMixin, + SysPathAwareMixin, + EnvironmentAwareMixin, + StdStreamCapturingMixin, + TempDirMixin, + TestCase +): """A base class for Coverage test cases.""" - # Our own setting: most CoverageTests run in their own temp directory. - run_in_temp_dir = True - # Standard unittest setting: show me diffs even if they are very long. maxDiff = None + # Tell newer unittest implementations to print long helpful messages. + longMessage = True + def setUp(self): super(CoverageTest, self).setUp() @@ -50,151 +42,6 @@ class CoverageTest(TestCase): f.write("%s_%s" % (self.__class__.__name__, self._testMethodName)) f.close() - # Tell newer unittest implementations to print long helpful messages. - self.longMessage = True - - # tearDown will restore the original sys.path - self.old_syspath = sys.path[:] - - if self.run_in_temp_dir: - # Create a temporary directory. - self.noise = str(random.random())[2:] - self.temp_root = os.path.join(tempfile.gettempdir(), 'test_cover') - self.temp_dir = os.path.join(self.temp_root, self.noise) - os.makedirs(self.temp_dir) - self.old_dir = os.getcwd() - os.chdir(self.temp_dir) - - # Modules should be importable from this temp directory. We don't - # use '' because we make lots of different temp directories and - # nose's caching importer can get confused. The full path prevents - # problems. - sys.path.insert(0, os.getcwd()) - - # Keep a counter to make every call to check_coverage unique. - self.n = 0 - - # Record environment variables that we changed with set_environ. - self.environ_undos = {} - - # Capture stdout and stderr so we can examine them in tests. - # nose keeps stdout from littering the screen, so we can safely Tee it, - # but it doesn't capture stderr, so we don't want to Tee stderr to the - # real stderr, since it will interfere with our nice field of dots. - self.old_stdout = sys.stdout - self.captured_stdout = StringIO() - sys.stdout = Tee(sys.stdout, self.captured_stdout) - self.old_stderr = sys.stderr - self.captured_stderr = StringIO() - sys.stderr = self.captured_stderr - - # Record sys.modules here so we can restore it in tearDown. - self.old_modules = dict(sys.modules) - - class_behavior = self.class_behavior() - class_behavior.tests += 1 - class_behavior.test_method_made_any_files = False - class_behavior.temp_dir = self.run_in_temp_dir - - def tearDown(self): - super(CoverageTest, self).tearDown() - - # Restore the original sys.path. - sys.path = self.old_syspath - - if self.run_in_temp_dir: - # Get rid of the temporary directory. - os.chdir(self.old_dir) - shutil.rmtree(self.temp_root) - - # Restore the environment. - self.undo_environ() - - # Restore stdout and stderr - sys.stdout = self.old_stdout - sys.stderr = self.old_stderr - - self.clean_modules() - - class_behavior = self.class_behavior() - if class_behavior.test_method_made_any_files: - class_behavior.tests_making_files += 1 - - def clean_modules(self): - """Remove any new modules imported during the test run. - - This lets us import the same source files for more than one test. - - """ - for m in [m for m in sys.modules if m not in self.old_modules]: - del sys.modules[m] - - def set_environ(self, name, value): - """Set an environment variable `name` to be `value`. - - The environment variable is set, and record is kept that it was set, - so that `tearDown` can restore its original value. - - """ - if name not in self.environ_undos: - self.environ_undos[name] = os.environ.get(name) - os.environ[name] = value - - def original_environ(self, name, if_missing=None): - """The environment variable `name` from when the test started.""" - if name in self.environ_undos: - ret = self.environ_undos[name] - else: - ret = os.environ.get(name) - if ret is None: - ret = if_missing - return ret - - def undo_environ(self): - """Undo all the changes made by `set_environ`.""" - for name, value in self.environ_undos.items(): - if value is None: - del os.environ[name] - else: - os.environ[name] = value - - def stdout(self): - """Return the data written to stdout during the test.""" - return self.captured_stdout.getvalue() - - def stderr(self): - """Return the data written to stderr during the test.""" - return self.captured_stderr.getvalue() - - def make_file(self, filename, text="", newline=None): - """Create a temp file. - - `filename` is the path to the file, including directories if desired, - and `text` is the content. If `newline` is provided, it is a string - that will be used as the line endings in the created file. - - Returns the path to the file. - - """ - # Tests that call `make_file` should be run in a temp environment. - assert self.run_in_temp_dir - self.class_behavior().test_method_made_any_files = True - - text = textwrap.dedent(text) - if newline: - text = text.replace("\n", newline) - - # Make sure the directories are available. - dirs, _ = os.path.split(filename) - if dirs and not os.path.exists(dirs): - os.makedirs(dirs) - - # Create the file. - with open(filename, 'wb') as f: - f.write(to_bytes(text)) - - return filename - def clean_local_file_imports(self): """Clean up the results of calls to `import_local_file`. @@ -203,7 +50,7 @@ class CoverageTest(TestCase): """ # So that we can re-import files, clean them out first. - self.clean_modules() + 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. @@ -219,18 +66,7 @@ class CoverageTest(TestCase): as `modname`, and returns the module object. """ - modfile = modname + '.py' - - for suff in imp.get_suffixes(): - if suff[0] == '.py': - break - - with open(modfile, 'r') as f: - # pylint: disable=W0631 - # (Using possibly undefined loop variable 'suff') - mod = imp.load_module(modname, f, modfile, suff) - - return mod + return import_local_file(modname) def start_import_stop(self, cov, modname): """Start coverage, import a file, then stop coverage. @@ -252,13 +88,7 @@ class CoverageTest(TestCase): def get_module_name(self): """Return the module name to use for this test run.""" - # We append self.n because otherwise two calls in one test will use the - # same filename and whether the test works or not depends on the - # timestamps in the .pyc file, so it becomes random whether the second - # call will use the compiled version of the first call's code or not! - modname = 'coverage_test_' + self.noise + str(self.n) - self.n += 1 - return modname + return 'coverage_test_' + str(random.random())[2:] # Map chars to numbers for arcz_to_arcs _arcz_map = {'.': -1} @@ -363,19 +193,21 @@ class CoverageTest(TestCase): if statements == line_list: break else: - self.fail("None of the lines choices matched %r" % - statements + self.fail( + "None of the lines choices matched %r" % statements ) + missing_formatted = analysis.missing_formatted() if type(missing) == type(""): - self.assertEqual(analysis.missing_formatted(), missing) + self.assertEqual(missing_formatted, missing) else: for missing_list in missing: - if analysis.missing_formatted() == missing_list: + if missing_formatted == missing_list: break else: - self.fail("None of the missing choices matched %r" % - analysis.missing_formatted() + self.fail( + "None of the missing choices matched %r" % + missing_formatted ) if arcs is not None: @@ -410,17 +242,17 @@ class CoverageTest(TestCase): """Assert that `flist1` and `flist2` are the same set of file names.""" flist1_nice = [self.nice_file(f) for f in flist1] flist2_nice = [self.nice_file(f) for f in flist2] - self.assertSameElements(flist1_nice, flist2_nice) + self.assertCountEqual(flist1_nice, flist2_nice) def assert_exists(self, fname): """Assert that `fname` is a file that exists.""" msg = "File %r should exist" % fname - self.assert_(os.path.exists(fname), msg) + self.assertTrue(os.path.exists(fname), msg) def assert_doesnt_exist(self, fname): """Assert that `fname` is a file that doesn't exist.""" msg = "File %r shouldn't exist" % fname - self.assert_(not os.path.exists(fname), msg) + self.assertTrue(not os.path.exists(fname), msg) def assert_starts_with(self, s, prefix, msg=None): """Assert that `s` starts with `prefix`.""" @@ -464,7 +296,7 @@ class CoverageTest(TestCase): _, output = self.run_command_status(cmd) return output - def run_command_status(self, cmd, status=0): + def run_command_status(self, cmd): """Run the command-line `cmd` in a subprocess, and print its output. Use this when you need to test the process behavior of coverage. @@ -473,9 +305,6 @@ class CoverageTest(TestCase): Returns a pair: the process' exit status and stdout text. - The `status` argument is returned as the status on older Pythons where - we can't get the actual exit status of the process. - """ # Add our test modules directory to PYTHONPATH. I'm sure there's too # much path munging here, but... @@ -488,58 +317,31 @@ class CoverageTest(TestCase): pypath += testmods + os.pathsep + zipfile self.set_environ('PYTHONPATH', pypath) - status, output = run_command(cmd, status=status) + status, output = run_command(cmd) print(output) return status, output - # We run some tests in temporary directories, because they may need to make - # files for the tests. But this is expensive, so we can change per-class - # whether a temp dir is used or not. It's easy to forget to set that - # option properly, so we track information about what the tests did, and - # then report at the end of the process on test classes that were set - # wrong. - - class ClassBehavior(object): - """A value object to store per-class in CoverageTest.""" - def __init__(self): - self.tests = 0 - self.temp_dir = True - self.tests_making_files = 0 - self.test_method_made_any_files = False - - # Map from class to info about how it ran. - class_behaviors = collections.defaultdict(ClassBehavior) - - @classmethod - def report_on_class_behavior(cls): - """Called at process exit to report on class behavior.""" - for test_class, behavior in cls.class_behaviors.items(): - if behavior.temp_dir and behavior.tests_making_files == 0: - bad = "Inefficient" - elif not behavior.temp_dir and behavior.tests_making_files > 0: - bad = "Unsafe" - else: - bad = "" - - if bad: - if behavior.temp_dir: - where = "in a temp directory" - else: - where = "without a temp directory" - print( - "%s: %s ran %d tests, %d made files %s" % ( - bad, - test_class.__name__, - behavior.tests, - behavior.tests_making_files, - where, - ) - ) - - def class_behavior(self): - """Get the ClassBehavior instance for this test.""" - return self.class_behaviors[self.__class__] - - -# When the process ends, find out about bad classes. -atexit.register(CoverageTest.report_on_class_behavior) + def report_from_command(self, cmd): + """Return the report from the `cmd`, with some convenience added.""" + report = self.run_command(cmd).replace('\\', '/') + self.assertNotIn("error", report.lower()) + return report + + def report_lines(self, report): + """Return the lines of the report, as a list.""" + lines = report.split('\n') + self.assertEqual(lines[-1], "") + return lines[:-1] + + def line_count(self, report): + """How many lines are in `report`?""" + return len(self.report_lines(report)) + + def squeezed_lines(self, report): + """Return a list of the lines in report, with the spaces squeezed.""" + lines = self.report_lines(report) + return [re.sub(r"\s+", " ", l.strip()) for l in lines] + + def last_line_squeezed(self, report): + """Return the last line of `report` with the spaces squeezed down.""" + return self.squeezed_lines(report)[-1] diff --git a/tests/farm/annotate/annotate_dir.py b/tests/farm/annotate/annotate_dir.py index 3e37f9ed..86c18cab 100644 --- a/tests/farm/annotate/annotate_dir.py +++ b/tests/farm/annotate/annotate_dir.py @@ -1,7 +1,7 @@ copy("src", "run") run(""" - coverage -e -x multi.py - coverage -a -d out_anno_dir + coverage run multi.py + coverage annotate -d out_anno_dir """, rundir="run") compare("run/out_anno_dir", "gold_anno_dir", "*,cover", left_extra=True) clean("run") diff --git a/tests/farm/annotate/run.py b/tests/farm/annotate/run.py index c645f21c..236f401f 100644 --- a/tests/farm/annotate/run.py +++ b/tests/farm/annotate/run.py @@ -1,7 +1,7 @@ copy("src", "out") run(""" - coverage -e -x white.py - coverage -a white.py + coverage run white.py + coverage annotate white.py """, rundir="out") compare("out", "gold", "*,cover") clean("out") diff --git a/tests/farm/annotate/run_multi.py b/tests/farm/annotate/run_multi.py index 4e8252ed..ef1e8238 100644 --- a/tests/farm/annotate/run_multi.py +++ b/tests/farm/annotate/run_multi.py @@ -1,7 +1,7 @@ copy("src", "out_multi") run(""" - coverage -e -x multi.py - coverage -a + coverage run multi.py + coverage annotate """, rundir="out_multi") compare("out_multi", "gold_multi", "*,cover") clean("out_multi") diff --git a/tests/farm/html/gold_x_xml/coverage.xml b/tests/farm/html/gold_x_xml/coverage.xml index 912112f2..d5a8c442 100644 --- a/tests/farm/html/gold_x_xml/coverage.xml +++ b/tests/farm/html/gold_x_xml/coverage.xml @@ -3,6 +3,9 @@ SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
<coverage branch-rate="0" line-rate="0.6667" timestamp="1253972570431" version="3.1b1">
<!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage/VER -->
+ <sources>
+ <source></source>
+ </sources>
<packages>
<package branch-rate="0" complexity="0" line-rate="0.6667" name="">
<classes>
diff --git a/tests/farm/html/gold_y_xml_branch/coverage.xml b/tests/farm/html/gold_y_xml_branch/coverage.xml index ecbe0073..86e9e73c 100644 --- a/tests/farm/html/gold_y_xml_branch/coverage.xml +++ b/tests/farm/html/gold_y_xml_branch/coverage.xml @@ -3,6 +3,9 @@ SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
<coverage branch-rate="0.5" line-rate="0.8" timestamp="1259288252325" version="3.2b4">
<!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage/VER -->
+ <sources>
+ <source></source>
+ </sources>
<packages>
<package branch-rate="0.5" complexity="0" line-rate="0.8" name="">
<classes>
diff --git a/tests/farm/html/run_a_xml_1.py b/tests/farm/html/run_a_xml_1.py index 3d187023..83f8c86d 100644 --- a/tests/farm/html/run_a_xml_1.py +++ b/tests/farm/html/run_a_xml_1.py @@ -1,3 +1,5 @@ +source_path = None + def html_it(): """Run coverage and make an XML report for a.""" import coverage @@ -6,6 +8,8 @@ def html_it(): import a # pragma: nested cov.stop() # pragma: nested cov.xml_report(a, outfile="../xml_1/coverage.xml") + global source_path + source_path = cov.file_locator.relative_dir.rstrip('/') import os if not os.path.exists("xml_1"): @@ -16,6 +20,7 @@ runfunc(html_it, rundir="src") compare("gold_x_xml", "xml_1", scrubs=[ (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'), (r' version="[-.\w]+"', ' version="VERSION"'), + (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % source_path), (r'/code/coverage/?[-.\w]*', '/code/coverage/VER'), ]) clean("xml_1") diff --git a/tests/farm/html/run_a_xml_2.py b/tests/farm/html/run_a_xml_2.py index 53691ead..6dd44225 100644 --- a/tests/farm/html/run_a_xml_2.py +++ b/tests/farm/html/run_a_xml_2.py @@ -1,3 +1,5 @@ +source_path = None + def html_it(): """Run coverage and make an XML report for a.""" import coverage @@ -6,6 +8,8 @@ def html_it(): import a # pragma: nested cov.stop() # pragma: nested cov.xml_report(a) + global source_path + source_path = cov.file_locator.relative_dir.rstrip('/') import os if not os.path.exists("xml_2"): @@ -16,6 +20,7 @@ runfunc(html_it, rundir="src") compare("gold_x_xml", "xml_2", scrubs=[ (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'), (r' version="[-.\w]+"', ' version="VERSION"'), + (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % source_path), (r'/code/coverage/?[-.\w]*', '/code/coverage/VER'), ]) clean("xml_2") diff --git a/tests/farm/html/run_unicode.py b/tests/farm/html/run_unicode.py index cef26ee5..c8cb6c50 100644 --- a/tests/farm/html/run_unicode.py +++ b/tests/farm/html/run_unicode.py @@ -1,5 +1,3 @@ -import sys - def html_it(): """Run coverage and make an HTML report for unicode.py.""" import coverage @@ -18,13 +16,9 @@ contains("html_unicode/unicode.html", "<span class='str'>"ʎd˙ǝbɐɹǝʌoɔ"</span>", ) -if sys.maxunicode == 65535: - contains("html_unicode/unicode.html", - "<span class='str'>"db40,dd00: x��"</span>", - ) -else: - contains("html_unicode/unicode.html", - "<span class='str'>"db40,dd00: x󠄀"</span>", - ) +contains_any("html_unicode/unicode.html", + "<span class='str'>"db40,dd00: x��"</span>", + "<span class='str'>"db40,dd00: x󠄀"</span>", + ) clean("html_unicode") diff --git a/tests/farm/html/run_y_xml_branch.py b/tests/farm/html/run_y_xml_branch.py index 88a2e44e..9ae9a9f0 100644 --- a/tests/farm/html/run_y_xml_branch.py +++ b/tests/farm/html/run_y_xml_branch.py @@ -1,3 +1,5 @@ +source_path = None + def xml_it(): """Run coverage and make an XML report for y.""" import coverage @@ -6,6 +8,8 @@ def xml_it(): import y # pragma: nested cov.stop() # pragma: nested cov.xml_report(y, outfile="../xml_branch/coverage.xml") + global source_path + source_path = cov.file_locator.relative_dir.rstrip('/') import os if not os.path.exists("xml_branch"): @@ -16,6 +20,7 @@ runfunc(xml_it, rundir="src") compare("gold_y_xml_branch", "xml_branch", scrubs=[ (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'), (r' version="[-.\w]+"', ' version="VERSION"'), + (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % source_path), (r'/code/coverage/?[-.\w]*', '/code/coverage/VER'), ]) clean("xml_branch") diff --git a/tests/farm/html/src/coverage.xml b/tests/farm/html/src/coverage.xml index 128cf750..e20cdaec 100644 --- a/tests/farm/html/src/coverage.xml +++ b/tests/farm/html/src/coverage.xml @@ -3,6 +3,9 @@ SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
<coverage branch-rate="0.0" line-rate="0.666666666667" timestamp="1263087779313" version="3.3a1">
<!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage -->
+ <sources>
+ <source></source>
+ </sources>
<packages>
<package branch-rate="0.0" complexity="0.0" line-rate="0.666666666667" name="">
<classes>
diff --git a/tests/farm/run/run_chdir.py b/tests/farm/run/run_chdir.py index f459f500..367cd0ad 100644 --- a/tests/farm/run/run_chdir.py +++ b/tests/farm/run/run_chdir.py @@ -1,7 +1,7 @@ copy("src", "out") run(""" coverage run chdir.py - coverage -r + coverage report """, rundir="out", outfile="stdout.txt") contains("out/stdout.txt", "Line One", diff --git a/tests/farm/run/run_timid.py b/tests/farm/run/run_timid.py index ce78fff1..d4e69a46 100644 --- a/tests/farm/run/run_timid.py +++ b/tests/farm/run/run_timid.py @@ -17,8 +17,8 @@ if os.environ.get('COVERAGE_COVERAGE', ''): copy("src", "out") run(""" python showtrace.py none - coverage -e -x showtrace.py regular - coverage -e -x --timid showtrace.py timid + coverage run showtrace.py regular + coverage run --timid showtrace.py timid """, rundir="out", outfile="showtraceout.txt") # When running without coverage, no trace function @@ -42,8 +42,8 @@ old_opts = os.environ.get('COVERAGE_OPTIONS') os.environ['COVERAGE_OPTIONS'] = '--timid' run(""" - coverage -e -x showtrace.py regular - coverage -e -x --timid showtrace.py timid + coverage run showtrace.py regular + coverage run --timid showtrace.py timid """, rundir="out", outfile="showtraceout.txt") contains("out/showtraceout.txt", diff --git a/tests/farm/run/run_xxx.py b/tests/farm/run/run_xxx.py index 19e94a42..6fedc934 100644 --- a/tests/farm/run/run_xxx.py +++ b/tests/farm/run/run_xxx.py @@ -1,7 +1,7 @@ copy("src", "out") run(""" - coverage -e -x xxx - coverage -r + coverage run xxx + coverage report """, rundir="out", outfile="stdout.txt") contains("out/stdout.txt", "xxx: 3 4 0 7", diff --git a/tests/modules/pkg1/p1a.py b/tests/modules/pkg1/p1a.py index be5fcdd3..337add49 100644 --- a/tests/modules/pkg1/p1a.py +++ b/tests/modules/pkg1/p1a.py @@ -1,5 +1,5 @@ import os, sys # Invoke functions in os and sys so we can see if we measure code there. -x = sys.getcheckinterval() +x = sys.getfilesystemencoding() y = os.getcwd() diff --git a/tests/modules/plugins/__init__.py b/tests/modules/plugins/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/modules/plugins/__init__.py diff --git a/tests/modules/plugins/a_plugin.py b/tests/modules/plugins/a_plugin.py new file mode 100644 index 00000000..2ff84dac --- /dev/null +++ b/tests/modules/plugins/a_plugin.py @@ -0,0 +1,6 @@ +"""A plugin for tests to reference.""" + +from coverage import CoveragePlugin + +class Plugin(CoveragePlugin): + pass diff --git a/tests/modules/plugins/another.py b/tests/modules/plugins/another.py new file mode 100644 index 00000000..2ff84dac --- /dev/null +++ b/tests/modules/plugins/another.py @@ -0,0 +1,6 @@ +"""A plugin for tests to reference.""" + +from coverage import CoveragePlugin + +class Plugin(CoveragePlugin): + pass diff --git a/tests/test_api.py b/tests/test_api.py index 097947d2..31bfc57f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -100,7 +100,7 @@ class ApiTest(CoverageTest): """Assert that the files here are `files`, ignoring the usual junk.""" here = os.listdir(".") here = self.clean_files(here, ["*.pyc", "__pycache__"]) - self.assertSameElements(here, files) + self.assertCountEqual(here, files) def test_unexecuted_file(self): cov = coverage.coverage() @@ -221,7 +221,7 @@ class ApiTest(CoverageTest): self.assertEqual(cov.get_exclude_list(), ["foo"]) cov.exclude("bar") self.assertEqual(cov.get_exclude_list(), ["foo", "bar"]) - self.assertEqual(cov._exclude_regex('exclude'), "(foo)|(bar)") + self.assertEqual(cov._exclude_regex('exclude'), "(?:foo)|(?:bar)") cov.clear_exclude() self.assertEqual(cov.get_exclude_list(), []) @@ -233,7 +233,9 @@ class ApiTest(CoverageTest): self.assertEqual(cov.get_exclude_list(which='partial'), ["foo"]) cov.exclude("bar", which='partial') self.assertEqual(cov.get_exclude_list(which='partial'), ["foo", "bar"]) - self.assertEqual(cov._exclude_regex(which='partial'), "(foo)|(bar)") + self.assertEqual( + cov._exclude_regex(which='partial'), "(?:foo)|(?:bar)" + ) cov.clear_exclude(which='partial') self.assertEqual(cov.get_exclude_list(which='partial'), []) diff --git a/tests/test_backward.py b/tests/test_backward.py index e98017ae..09803ba7 100644 --- a/tests/test_backward.py +++ b/tests/test_backward.py @@ -1,18 +1,16 @@ """Tests that our version shims in backward.py are working.""" +from coverage.backunittest import TestCase from coverage.backward import iitems, binary_bytes, byte_to_int, bytes_to_ints -from tests.backunittest import TestCase class BackwardTest(TestCase): """Tests of things from backward.py.""" - run_in_temp_dir = False - def test_iitems(self): d = {'a': 1, 'b': 2, 'c': 3} items = [('a', 1), ('b', 2), ('c', 3)] - self.assertSameElements(list(iitems(d)), items) + self.assertCountEqual(list(iitems(d)), items) def test_binary_bytes(self): byte_values = [0, 255, 17, 23, 42, 57] diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 99bae516..08f7937a 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -72,7 +72,7 @@ class CmdLineTest(CoverageTest): code = re.sub(r"(?m)^\.", "m2.", code) m2 = self.model_object() code_obj = compile(code, "<code>", "exec") - eval(code_obj, globals(), { 'm2': m2 }) + eval(code_obj, globals(), { 'm2': m2 }) # pylint: disable=eval-used # Many of our functions take a lot of arguments, and cmdline.py # calls them with many. But most of them are just the defaults, which @@ -754,7 +754,7 @@ class CmdMainTest(CoverageTest): self.assertEqual(err[-2], 'Exception: oh noes!') def test_internalraise(self): - with self.assertRaisesRegexp(ValueError, "coverage is broken"): + with self.assertRaisesRegex(ValueError, "coverage is broken"): coverage.cmdline.main(['internalraise']) def test_exit(self): diff --git a/tests/test_codeunit.py b/tests/test_codeunit.py index e4912e11..fe82ea1c 100644 --- a/tests/test_codeunit.py +++ b/tests/test_codeunit.py @@ -31,9 +31,9 @@ class CodeUnitTest(CoverageTest): self.assertEqual(acu[0].flat_rootname(), "aa_afile") self.assertEqual(bcu[0].flat_rootname(), "aa_bb_bfile") self.assertEqual(ccu[0].flat_rootname(), "aa_bb_cc_cfile") - self.assertEqual(acu[0].source_file().read(), "# afile.py\n") - self.assertEqual(bcu[0].source_file().read(), "# bfile.py\n") - self.assertEqual(ccu[0].source_file().read(), "# cfile.py\n") + self.assertEqual(acu[0].source(), "# afile.py\n") + self.assertEqual(bcu[0].source(), "# bfile.py\n") + self.assertEqual(ccu[0].source(), "# cfile.py\n") def test_odd_filenames(self): acu = code_unit_factory("aa/afile.odd.py", FileLocator()) @@ -45,9 +45,9 @@ class CodeUnitTest(CoverageTest): self.assertEqual(acu[0].flat_rootname(), "aa_afile_odd") self.assertEqual(bcu[0].flat_rootname(), "aa_bb_bfile_odd") self.assertEqual(b2cu[0].flat_rootname(), "aa_bb_odd_bfile") - self.assertEqual(acu[0].source_file().read(), "# afile.odd.py\n") - self.assertEqual(bcu[0].source_file().read(), "# bfile.odd.py\n") - self.assertEqual(b2cu[0].source_file().read(), "# bfile.py\n") + self.assertEqual(acu[0].source(), "# afile.odd.py\n") + self.assertEqual(bcu[0].source(), "# bfile.odd.py\n") + self.assertEqual(b2cu[0].source(), "# bfile.py\n") def test_modules(self): import aa, aa.bb, aa.bb.cc @@ -58,9 +58,9 @@ class CodeUnitTest(CoverageTest): self.assertEqual(cu[0].flat_rootname(), "aa") self.assertEqual(cu[1].flat_rootname(), "aa_bb") self.assertEqual(cu[2].flat_rootname(), "aa_bb_cc") - self.assertEqual(cu[0].source_file().read(), "# aa\n") - self.assertEqual(cu[1].source_file().read(), "# bb\n") - self.assertEqual(cu[2].source_file().read(), "") # yes, empty + self.assertEqual(cu[0].source(), "# aa\n") + self.assertEqual(cu[1].source(), "# bb\n") + self.assertEqual(cu[2].source(), "") # yes, empty def test_module_files(self): import aa.afile, aa.bb.bfile, aa.bb.cc.cfile @@ -72,9 +72,9 @@ class CodeUnitTest(CoverageTest): self.assertEqual(cu[0].flat_rootname(), "aa_afile") self.assertEqual(cu[1].flat_rootname(), "aa_bb_bfile") self.assertEqual(cu[2].flat_rootname(), "aa_bb_cc_cfile") - self.assertEqual(cu[0].source_file().read(), "# afile.py\n") - self.assertEqual(cu[1].source_file().read(), "# bfile.py\n") - self.assertEqual(cu[2].source_file().read(), "# cfile.py\n") + self.assertEqual(cu[0].source(), "# afile.py\n") + self.assertEqual(cu[1].source(), "# bfile.py\n") + self.assertEqual(cu[2].source(), "# cfile.py\n") def test_comparison(self): acu = code_unit_factory("aa/afile.py", FileLocator())[0] @@ -97,7 +97,7 @@ class CodeUnitTest(CoverageTest): self.assert_doesnt_exist(egg1.__file__) cu = code_unit_factory([egg1, egg1.egg1], FileLocator()) - self.assertEqual(cu[0].source_file().read(), "") - self.assertEqual(cu[1].source_file().read().split("\n")[0], + self.assertEqual(cu[0].source(), "") + self.assertEqual(cu[1].source().split("\n")[0], "# My egg file!" ) diff --git a/tests/test_config.py b/tests/test_config.py index 7fa31208..bf84423d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- """Test the config file handling for coverage.py""" +import sys, os + import coverage from coverage.misc import CoverageException @@ -125,58 +127,84 @@ class ConfigTest(CoverageTest): class ConfigFileTest(CoverageTest): """Tests of the config file settings in particular.""" - def test_config_file_settings(self): - # This sample file tries to use lots of variation of syntax... - self.make_file(".coveragerc", """\ - # This is a settings file for coverage.py - [run] - timid = yes - data_file = something_or_other.dat - branch = 1 - cover_pylib = TRUE - parallel = on - include = a/ , b/ - - [report] - ; these settings affect reporting. - exclude_lines = - if 0: - - pragma:?\\s+no cover - another_tab - - ignore_errors = TRUE - omit = - one, another, some_more, - yet_more - precision = 3 - - partial_branches = - pragma:?\\s+no branch - partial_branches_always = - if 0: - while True: - - show_missing= TruE - - [html] - - directory = c:\\tricky\\dir.somewhere - extra_css=something/extra.css - title = Title & nums # nums! - [xml] - output=mycov.xml - - [paths] - source = - . - /home/ned/src/ - - other = other, /home/ned/other, c:\\Ned\\etc - - """) - cov = coverage.coverage() - + def setUp(self): + super(ConfigFileTest, self).setUp() + # Parent class saves and restores sys.path, we can just modify it. + # Add modules to the path so we can import plugins. + sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules')) + + # This sample file tries to use lots of variation of syntax... + # The {section} placeholder lets us nest these settings in another file. + LOTSA_SETTINGS = """\ + # This is a settings file for coverage.py + [{section}run] + timid = yes + data_file = something_or_other.dat + branch = 1 + cover_pylib = TRUE + parallel = on + include = a/ , b/ + plugins = + plugins.a_plugin + plugins.another + + [{section}report] + ; these settings affect reporting. + exclude_lines = + if 0: + + pragma:?\\s+no cover + another_tab + + ignore_errors = TRUE + omit = + one, another, some_more, + yet_more + precision = 3 + + partial_branches = + pragma:?\\s+no branch + partial_branches_always = + if 0: + while True: + + show_missing= TruE + + [{section}html] + + directory = c:\\tricky\\dir.somewhere + extra_css=something/extra.css + title = Title & nums # nums! + [{section}xml] + output=mycov.xml + + [{section}paths] + source = + . + /home/ned/src/ + + other = other, /home/ned/other, c:\\Ned\\etc + + [{section}plugins.a_plugin] + hello = world + ; comments still work. + names = Jane/John/Jenny + """ + + # Just some sample setup.cfg text from the docs. + SETUP_CFG = """\ + [bdist_rpm] + release = 1 + packager = Jane Packager <janep@pysoft.com> + doc_files = CHANGES.txt + README.txt + USAGE.txt + doc/ + examples/ + """ + + 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) @@ -199,6 +227,9 @@ class ConfigFileTest(CoverageTest): self.assertEqual(cov.config.partial_always_list, ["if 0:", "while True:"] ) + self.assertEqual(cov.config.plugins, + ["plugins.a_plugin", "plugins.another"] + ) self.assertTrue(cov.config.show_missing) self.assertEqual(cov.config.html_dir, r"c:\tricky\dir.somewhere") self.assertEqual(cov.config.extra_css, "something/extra.css") @@ -211,8 +242,39 @@ class ConfigFileTest(CoverageTest): 'other': ['other', '/home/ned/other', 'c:\\Ned\\etc'] }) + self.assertEqual(cov.config.get_plugin_options("plugins.a_plugin"), { + 'hello': 'world', + 'names': 'Jane/John/Jenny', + }) + self.assertEqual(cov.config.get_plugin_options("plugins.another"), {}) + + def test_config_file_settings(self): + self.make_file(".coveragerc", self.LOTSA_SETTINGS.format(section="")) + cov = coverage.coverage() + self.assert_config_settings_are_correct(cov) + + def test_config_file_settings_in_setupcfg(self): + nested = self.LOTSA_SETTINGS.format(section="coverage:") + self.make_file("setup.cfg", nested + "\n" + self.SETUP_CFG) + cov = coverage.coverage() + self.assert_config_settings_are_correct(cov) + + def test_setupcfg_only_if_not_coveragerc(self): + self.make_file(".coveragerc", """\ + [run] + include = foo + """) + self.make_file("setup.cfg", """\ + [run] + omit = bar + branch = true + """) + cov = coverage.coverage() + self.assertEqual(cov.config.include, ["foo"]) + self.assertEqual(cov.config.omit, None) + self.assertEqual(cov.config.branch, False) + def test_one(self): - # This sample file tries to use lots of variation of syntax... self.make_file(".coveragerc", """\ [html] title = tabblo & «ταБЬℓσ» # numbers diff --git a/tests/test_coroutine.py b/tests/test_coroutine.py new file mode 100644 index 00000000..4abdd6f6 --- /dev/null +++ b/tests/test_coroutine.py @@ -0,0 +1,208 @@ +"""Tests for coroutining.""" + +import os, os.path, sys, threading + +import coverage + +from tests.coveragetest import CoverageTest + + +# These libraries aren't always available, we'll skip tests if they aren't. + +try: + import eventlet # pylint: disable=import-error +except ImportError: + eventlet = None + +try: + import gevent # pylint: disable=import-error +except ImportError: + gevent = None + +try: + import greenlet # pylint: disable=import-error +except ImportError: + greenlet = None + +# Are we running with the C tracer or not? +C_TRACER = os.getenv('COVERAGE_TEST_TRACER', 'c') == 'c' + + +def line_count(s): + """How many non-blank non-comment lines are in `s`?""" + def code_line(l): + """Is this a code line? Not blank, and not a full-line comment.""" + return l.strip() and not l.strip().startswith('#') + return sum(1 for l in s.splitlines() if code_line(l)) + + +class CoroutineTest(CoverageTest): + """Tests of the coroutine support in coverage.py.""" + + LIMIT = 1000 + + # The code common to all the concurrency models. + COMMON = """ + class Producer(threading.Thread): + def __init__(self, q): + threading.Thread.__init__(self) + self.q = q + + def run(self): + for i in range({LIMIT}): + self.q.put(i) + self.q.put(None) + + class Consumer(threading.Thread): + def __init__(self, q): + threading.Thread.__init__(self) + self.q = q + + def run(self): + sum = 0 + while True: + i = self.q.get() + if i is None: + print(sum) + break + sum += i + + q = queue.Queue() + c = Consumer(q) + p = Producer(q) + c.start() + p.start() + + p.join() + c.join() + """.format(LIMIT=LIMIT) + + # Import the things to use threads. + if sys.version_info < (3, 0): + THREAD = """\ + import threading + import Queue as queue + """ + COMMON + else: + THREAD = """\ + import threading + import queue + """ + COMMON + + # Import the things to use eventlet. + EVENTLET = """\ + import eventlet.green.threading as threading + import eventlet.queue as queue + """ + COMMON + + # Import the things to use gevent. + GEVENT = """\ + from gevent import monkey + monkey.patch_thread() + import threading + import gevent.queue as queue + """ + COMMON + + # Uncomplicated code that doesn't use any of the coroutining stuff, to test + # the simple case under each of the regimes. + SIMPLE = """\ + total = 0 + for i in range({LIMIT}): + total += i + print(total) + """.format(LIMIT=LIMIT) + + def try_some_code(self, code, coroutine, the_module, expected_out=None): + """Run some coroutine testing code and see that it was all covered. + + `code` is the Python code to execute. `coroutine` is the name of the + coroutine regime to test it under. `the_module` is the imported module + that must be available for this to work at all. `expected_out` is the + text we expect the code to produce. + + """ + + self.make_file("try_it.py", code) + + cmd = "coverage run --coroutine=%s try_it.py" % coroutine + out = self.run_command(cmd) + + if not the_module: + # We don't even have the underlying module installed, we expect + # coverage to alert us to this fact. + expected_out = ( + "Couldn't trace with coroutine=%s, " + "the module isn't installed.\n" % coroutine + ) + self.assertEqual(out, expected_out) + elif C_TRACER or coroutine == "thread": + # We can fully measure the code if we are using the C tracer, which + # can support all the coroutining, or if we are using threads. + if expected_out is None: + expected_out = "%d\n" % (sum(range(self.LIMIT))) + self.assertEqual(out, expected_out) + + # Read the coverage file and see that try_it.py has all its lines + # executed. + data = coverage.CoverageData() + data.read_file(".coverage") + + # If the test fails, it's helpful to see this info: + fname = os.path.abspath("try_it.py") + linenos = data.executed_lines(fname).keys() + print("{0}: {1}".format(len(linenos), linenos)) + print_simple_annotation(code, linenos) + + lines = line_count(code) + self.assertEqual(data.summary()['try_it.py'], lines) + else: + expected_out = ( + "Can't support coroutine=%s with PyTracer, " + "only threads are supported\n" % coroutine + ) + self.assertEqual(out, expected_out) + + def test_threads(self): + self.try_some_code(self.THREAD, "thread", threading) + + def test_threads_simple_code(self): + self.try_some_code(self.SIMPLE, "thread", threading) + + def test_eventlet(self): + self.try_some_code(self.EVENTLET, "eventlet", eventlet) + + def test_eventlet_simple_code(self): + self.try_some_code(self.SIMPLE, "eventlet", eventlet) + + def test_gevent(self): + self.try_some_code(self.GEVENT, "gevent", gevent) + + def test_gevent_simple_code(self): + self.try_some_code(self.SIMPLE, "gevent", gevent) + + def test_greenlet(self): + GREENLET = """\ + from greenlet import greenlet + + def test1(x, y): + z = gr2.switch(x+y) + print(z) + + def test2(u): + print(u) + gr1.switch(42) + + gr1 = greenlet(test1) + gr2 = greenlet(test2) + gr1.switch("hello", " world") + """ + self.try_some_code(GREENLET, "greenlet", greenlet, "hello world\n42\n") + + def test_greenlet_simple_code(self): + self.try_some_code(self.SIMPLE, "greenlet", greenlet) + + +def print_simple_annotation(code, linenos): + """Print the lines in `code` with X for each line number in `linenos`.""" + for lineno, line in enumerate(code.splitlines(), start=1): + print(" {0} {1}".format("X" if lineno in linenos else " ", line)) diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 33f644fa..565fa4e1 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -46,7 +46,7 @@ class TestCoverageTest(CoverageTest): def test_failed_coverage(self): # If the lines are wrong, the message shows right and wrong. - with self.assertRaisesRegexp(AssertionError, r"\[1, 2] != \[1]"): + with self.assertRaisesRegex(AssertionError, r"\[1, 2] != \[1]"): self.check_coverage("""\ a = 1 b = 2 @@ -55,7 +55,7 @@ class TestCoverageTest(CoverageTest): ) # If the list of lines possibilities is wrong, the msg shows right. msg = r"None of the lines choices matched \[1, 2]" - with self.assertRaisesRegexp(AssertionError, msg): + with self.assertRaisesRegex(AssertionError, msg): self.check_coverage("""\ a = 1 b = 2 @@ -63,7 +63,7 @@ class TestCoverageTest(CoverageTest): ([1], [2]) ) # If the missing lines are wrong, the message shows right and wrong. - with self.assertRaisesRegexp(AssertionError, r"'3' != '37'"): + with self.assertRaisesRegex(AssertionError, r"'3' != '37'"): self.check_coverage("""\ a = 1 if a == 2: @@ -74,7 +74,7 @@ class TestCoverageTest(CoverageTest): ) # If the missing lines possibilities are wrong, the msg shows right. msg = r"None of the missing choices matched '3'" - with self.assertRaisesRegexp(AssertionError, msg): + with self.assertRaisesRegex(AssertionError, msg): self.check_coverage("""\ a = 1 if a == 2: @@ -1671,7 +1671,7 @@ class ReportingTest(CoverageTest): def test_no_data_to_report_on_annotate(self): # Reporting with no data produces a nice message and no output dir. - with self.assertRaisesRegexp(CoverageException, "No data to report."): + with self.assertRaisesRegex(CoverageException, "No data to report."): self.command_line("annotate -d ann") self.assert_doesnt_exist("ann") @@ -1681,12 +1681,12 @@ class ReportingTest(CoverageTest): def test_no_data_to_report_on_html(self): # Reporting with no data produces a nice message and no output dir. - with self.assertRaisesRegexp(CoverageException, "No data to report."): + with self.assertRaisesRegex(CoverageException, "No data to report."): self.command_line("html -d htmlcov") self.assert_doesnt_exist("htmlcov") def test_no_data_to_report_on_xml(self): # Reporting with no data produces a nice message. - with self.assertRaisesRegexp(CoverageException, "No data to report."): + with self.assertRaisesRegex(CoverageException, "No data to report."): self.command_line("xml") self.assert_doesnt_exist("coverage.xml") diff --git a/tests/test_data.py b/tests/test_data.py index 31578f26..b048fd18 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -33,7 +33,7 @@ class DataTest(CoverageTest): def assert_measured_files(self, covdata, measured): """Check that `covdata`'s measured files are `measured`.""" - self.assertSameElements(covdata.measured_files(), measured) + self.assertCountEqual(covdata.measured_files(), measured) def test_reading_empty(self): covdata = CoverageData() @@ -96,9 +96,9 @@ class DataTest(CoverageTest): data = pickle.load(fdata) lines = data['lines'] - self.assertSameElements(lines.keys(), MEASURED_FILES_1) - self.assertSameElements(lines['a.py'], A_PY_LINES_1) - self.assertSameElements(lines['b.py'], B_PY_LINES_1) + self.assertCountEqual(lines.keys(), MEASURED_FILES_1) + self.assertCountEqual(lines['a.py'], A_PY_LINES_1) + self.assertCountEqual(lines['b.py'], B_PY_LINES_1) # If not measuring branches, there's no arcs entry. self.assertEqual(data.get('arcs', 'not there'), 'not there') @@ -111,10 +111,10 @@ class DataTest(CoverageTest): with open(".coverage", 'rb') as fdata: data = pickle.load(fdata) - self.assertSameElements(data['lines'].keys(), []) + self.assertCountEqual(data['lines'].keys(), []) arcs = data['arcs'] - self.assertSameElements(arcs['x.py'], X_PY_ARCS_3) - self.assertSameElements(arcs['y.py'], Y_PY_ARCS_3) + self.assertCountEqual(arcs['x.py'], X_PY_ARCS_3) + self.assertCountEqual(arcs['y.py'], Y_PY_ARCS_3) def test_combining_with_aliases(self): covdata1 = CoverageData() diff --git a/tests/test_execfile.py b/tests/test_execfile.py index 7cd8ac4e..69616e84 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -16,7 +16,7 @@ class RunFileTest(CoverageTest): def test_run_python_file(self): tryfile = os.path.join(here, "try_execfile.py") run_python_file(tryfile, [tryfile, "arg1", "arg2"]) - mod_globs = eval(self.stdout()) + mod_globs = eval(self.stdout()) # pylint: disable=eval-used # The file should think it is __main__ self.assertEqual(mod_globs['__name__'], "__main__") @@ -118,11 +118,11 @@ class RunPycFileTest(CoverageTest): fpyc.write(binary_bytes([0x2a, 0xeb, 0x0d, 0x0a])) fpyc.close() - with self.assertRaisesRegexp(NoCode, "Bad magic number in .pyc file"): + with self.assertRaisesRegex(NoCode, "Bad magic number in .pyc file"): run_python_file(pycfile, [pycfile]) def test_no_such_pyc_file(self): - with self.assertRaisesRegexp(NoCode, "No file to run: 'xyzzy.pyc'"): + with self.assertRaisesRegex(NoCode, "No file to run: 'xyzzy.pyc'"): run_python_file("xyzzy.pyc", []) @@ -138,22 +138,27 @@ class RunModuleTest(CoverageTest): def test_runmod1(self): run_python_module("runmod1", ["runmod1", "hello"]) + self.assertEqual(self.stderr(), "") self.assertEqual(self.stdout(), "runmod1: passed hello\n") def test_runmod2(self): run_python_module("pkg1.runmod2", ["runmod2", "hello"]) + self.assertEqual(self.stderr(), "") self.assertEqual(self.stdout(), "runmod2: passed hello\n") def test_runmod3(self): run_python_module("pkg1.sub.runmod3", ["runmod3", "hello"]) + self.assertEqual(self.stderr(), "") self.assertEqual(self.stdout(), "runmod3: passed hello\n") def test_pkg1_main(self): run_python_module("pkg1", ["pkg1", "hello"]) + self.assertEqual(self.stderr(), "") self.assertEqual(self.stdout(), "pkg1.__main__: passed hello\n") def test_pkg1_sub_main(self): run_python_module("pkg1.sub", ["pkg1.sub", "hello"]) + self.assertEqual(self.stderr(), "") self.assertEqual(self.stdout(), "pkg1.sub.__main__: passed hello\n") def test_no_such_module(self): diff --git a/tests/test_farm.py b/tests/test_farm.py index c86983e5..47f9b7b7 100644 --- a/tests/test_farm.py +++ b/tests/test_farm.py @@ -15,6 +15,10 @@ def test_farm(clean_only=False): yield (case,) +# "rU" was deprecated in 3.4 +READ_MODE = "rU" if sys.version_info < (3, 4) else "r" + + class FarmTestCase(object): """A test case from the farm tree. @@ -22,8 +26,8 @@ class FarmTestCase(object): copy("src", "out") run(''' - coverage -x white.py - coverage -a white.py + coverage run white.py + coverage annotate white.py ''', rundir="out") compare("out", "gold", "*,cover") clean("out") @@ -75,7 +79,8 @@ class FarmTestCase(object): # Prepare a dictionary of globals for the run.py files to use. fns = """ - copy run runfunc compare contains doesnt_contain clean skip + copy run runfunc clean skip + compare contains contains_any doesnt_contain """.split() if self.clean_only: glo = dict((fn, self.noop) for fn in fns) @@ -238,8 +243,10 @@ class FarmTestCase(object): # guide for size comparison. wrong_size = [] for f in diff_files: - left = open(os.path.join(dir1, f), "rb").read() - right = open(os.path.join(dir2, f), "rb").read() + with open(os.path.join(dir1, f), "rb") as fobj: + left = fobj.read() + with open(os.path.join(dir2, f), "rb") as fobj: + right = fobj.read() size_l, size_r = len(left), len(right) big, little = max(size_l, size_r), min(size_l, size_r) if (big - little) / float(little) > size_within/100.0: @@ -256,14 +263,18 @@ class FarmTestCase(object): # ourselves. text_diff = [] for f in diff_files: - left = open(os.path.join(dir1, f), "rU").readlines() - right = open(os.path.join(dir2, f), "rU").readlines() + with open(os.path.join(dir1, f), READ_MODE) as fobj: + left = fobj.read() + with open(os.path.join(dir2, f), READ_MODE) as fobj: + right = fobj.read() if scrubs: left = self._scrub(left, scrubs) right = self._scrub(right, scrubs) if left != right: text_diff.append(f) - print("".join(list(difflib.Differ().compare(left, right)))) + left = left.splitlines() + right = right.splitlines() + print("\n".join(difflib.Differ().compare(left, right))) assert not text_diff, "Files differ: %s" % text_diff if not left_extra: @@ -271,19 +282,16 @@ class FarmTestCase(object): if not right_extra: assert not right_only, "Files in %s only: %s" % (dir2, right_only) - def _scrub(self, strlist, scrubs): - """Scrub uninteresting data from the strings in `strlist`. + def _scrub(self, strdata, scrubs): + """Scrub uninteresting data from the payload in `strdata`. - `scrubs is a list of (find, replace) pairs of regexes that are used on - each string in `strlist`. A list of scrubbed strings is returned. + `scrubs` is a list of (find, replace) pairs of regexes that are used on + `strdata`. A string is returned. """ - scrubbed = [] - for s in strlist: - for rgx_find, rgx_replace in scrubs: - s = re.sub(rgx_find, rgx_replace, s) - scrubbed.append(s) - return scrubbed + for rgx_find, rgx_replace in scrubs: + strdata = re.sub(rgx_find, rgx_replace, strdata) + return strdata def contains(self, filename, *strlist): """Check that the file contains all of a list of strings. @@ -292,10 +300,27 @@ class FarmTestCase(object): missing in `filename`. """ - text = open(filename, "r").read() + with open(filename, "r") as fobj: + text = fobj.read() for s in strlist: assert s in text, "Missing content in %s: %r" % (filename, s) + def contains_any(self, filename, *strlist): + """Check that the file contains at least one of a list of strings. + + An assert will be raised if none of the arguments in `strlist` is in + `filename`. + + """ + with open(filename, "r") as fobj: + text = fobj.read() + for s in strlist: + if s in text: + return + assert False, "Missing content in %s: %r [1 of %d]" % ( + filename, strlist[0], len(strlist), + ) + def doesnt_contain(self, filename, *strlist): """Check that the file contains none of a list of strings. @@ -303,7 +328,8 @@ class FarmTestCase(object): `filename`. """ - text = open(filename, "r").read() + with open(filename, "r") as fobj: + text = fobj.read() for s in strlist: assert s not in text, "Forbidden content in %s: %r" % (filename, s) diff --git a/tests/test_files.py b/tests/test_files.py index 85c0ac7b..648c76a9 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -50,41 +50,62 @@ class FileLocatorTest(CoverageTest): class MatcherTest(CoverageTest): """Tests of file matchers.""" + def setUp(self): + super(MatcherTest, self).setUp() + self.fl = FileLocator() + + def assertMatches(self, matcher, filepath, matches): + """The `matcher` should agree with `matches` about `filepath`.""" + canonical = self.fl.canonical_filename(filepath) + self.assertEqual( + matcher.match(canonical), matches, + "File %s should have matched as %s" % (filepath, matches) + ) + def test_tree_matcher(self): - file1 = self.make_file("sub/file1.py") - file2 = self.make_file("sub/file2.c") - file3 = self.make_file("sub2/file3.h") - file4 = self.make_file("sub3/file4.py") - file5 = self.make_file("sub3/file5.c") + matches_to_try = [ + (self.make_file("sub/file1.py"), True), + (self.make_file("sub/file2.c"), True), + (self.make_file("sub2/file3.h"), False), + (self.make_file("sub3/file4.py"), True), + (self.make_file("sub3/file5.c"), False), + ] fl = FileLocator() trees = [ fl.canonical_filename("sub"), - fl.canonical_filename(file4), + fl.canonical_filename("sub3/file4.py"), ] tm = TreeMatcher(trees) - self.assertTrue(tm.match(fl.canonical_filename(file1))) - self.assertTrue(tm.match(fl.canonical_filename(file2))) - self.assertFalse(tm.match(fl.canonical_filename(file3))) - self.assertTrue(tm.match(fl.canonical_filename(file4))) - self.assertFalse(tm.match(fl.canonical_filename(file5))) - self.assertEqual(tm.info(), trees) + for filepath, matches in matches_to_try: + self.assertMatches(tm, filepath, matches) def test_fnmatch_matcher(self): - file1 = self.make_file("sub/file1.py") - file2 = self.make_file("sub/file2.c") - file3 = self.make_file("sub2/file3.h") - file4 = self.make_file("sub3/file4.py") - file5 = self.make_file("sub3/file5.c") - fl = FileLocator() + matches_to_try = [ + (self.make_file("sub/file1.py"), True), + (self.make_file("sub/file2.c"), False), + (self.make_file("sub2/file3.h"), True), + (self.make_file("sub3/file4.py"), True), + (self.make_file("sub3/file5.c"), False), + ] fnm = FnmatchMatcher(["*.py", "*/sub2/*"]) - self.assertTrue(fnm.match(fl.canonical_filename(file1))) - self.assertFalse(fnm.match(fl.canonical_filename(file2))) - self.assertTrue(fnm.match(fl.canonical_filename(file3))) - self.assertTrue(fnm.match(fl.canonical_filename(file4))) - self.assertFalse(fnm.match(fl.canonical_filename(file5))) - self.assertEqual(fnm.info(), ["*.py", "*/sub2/*"]) + for filepath, matches in matches_to_try: + self.assertMatches(fnm, filepath, matches) + + def test_fnmatch_matcher_overload(self): + fnm = FnmatchMatcher(["*x%03d*.txt" % i for i in range(500)]) + self.assertMatches(fnm, "x007foo.txt", True) + self.assertMatches(fnm, "x123foo.txt", True) + self.assertMatches(fnm, "x798bar.txt", False) + + def test_fnmatch_windows_paths(self): + # We should be able to match Windows paths even if we are running on + # a non-Windows OS. + fnm = FnmatchMatcher(["*/foo.py"]) + self.assertMatches(fnm, r"dir\foo.py", True) + fnm = FnmatchMatcher([r"*\foo.py"]) + self.assertMatches(fnm, r"dir\foo.py", True) class PathAliasesTest(CoverageTest): @@ -124,11 +145,11 @@ class PathAliasesTest(CoverageTest): def test_cant_have_wildcard_at_end(self): aliases = PathAliases() msg = "Pattern must not end with wildcards." - with self.assertRaisesRegexp(CoverageException, msg): + with self.assertRaisesRegex(CoverageException, msg): aliases.add("/ned/home/*", "fooey") - with self.assertRaisesRegexp(CoverageException, msg): + with self.assertRaisesRegex(CoverageException, msg): aliases.add("/ned/home/*/", "fooey") - with self.assertRaisesRegexp(CoverageException, msg): + with self.assertRaisesRegex(CoverageException, msg): aliases.add("/ned/home/*/*/", "fooey") def test_no_accidental_munging(self): @@ -170,7 +191,7 @@ class RelativePathAliasesTest(CoverageTest): aliases.add(d, '/the/source') the_file = os.path.join(d, 'a.py') the_file = os.path.expanduser(the_file) - the_file = os.path.abspath(the_file) + the_file = os.path.abspath(os.path.realpath(the_file)) assert '~' not in the_file # to be sure the test is pure. self.assertEqual(aliases.map(the_file), '/the/source/a.py') diff --git a/tests/test_html.py b/tests/test_html.py index 41859382..8e43e7cf 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Tests that HTML generation is awesome.""" -import os.path, re +import os.path, re, sys import coverage import coverage.html from coverage.misc import CoverageException, NotPython, NoSource @@ -42,6 +42,13 @@ class HtmlTestHelpers(CoverageTest): os.remove("htmlcov/helper1.html") os.remove("htmlcov/helper2.html") + def get_html_report_content(self, module): + """Return the content of the HTML report for `module`.""" + filename = module.replace(".py", ".html").replace("/", "_") + filename = os.path.join("htmlcov", filename) + with open(filename) as f: + return f.read() + class HtmlDeltaTest(HtmlTestHelpers, CoverageTest): """Tests of the HTML delta speed-ups.""" @@ -208,7 +215,7 @@ class HtmlTitleTest(HtmlTestHelpers, CoverageTest): ) -class HtmlWithUnparsableFilesTest(CoverageTest): +class HtmlWithUnparsableFilesTest(HtmlTestHelpers, CoverageTest): """Test the behavior when measuring unparsable files.""" def test_dotpy_not_python(self): @@ -217,7 +224,7 @@ class HtmlWithUnparsableFilesTest(CoverageTest): self.start_import_stop(cov, "innocuous") 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.assertRaisesRegexp(NotPython, msg): + with self.assertRaisesRegex(NotPython, msg): cov.html_report() def test_dotpy_not_python_ignored(self): @@ -267,6 +274,31 @@ class HtmlWithUnparsableFilesTest(CoverageTest): cov.html_report() self.assert_exists("htmlcov/index.html") + def test_decode_error(self): + # imp.load_module won't load a file with an undecodable character + # in a comment, though Python will run them. So we'll change the + # file after running. + self.make_file("main.py", "import sub.not_ascii") + self.make_file("sub/__init__.py") + self.make_file("sub/not_ascii.py", """\ + a = 1 # Isn't this great?! + """) + cov = coverage.coverage() + self.start_import_stop(cov, "main") + + # Create the undecodable version of the file. + self.make_file("sub/not_ascii.py", """\ + a = 1 # Isn't this great?\xcb! + """) + cov.html_report() + + html_report = self.get_html_report_content("sub/not_ascii.py") + if sys.version_info < (3, 0): + expected = "# Isn't this great?�!" + else: + expected = "# Isn't this great?Ë!" + self.assertIn(expected, html_report) + class HtmlTest(CoverageTest): """Moar HTML tests.""" @@ -283,7 +315,7 @@ class HtmlTest(CoverageTest): missing_file = os.path.join(self.temp_dir, "sub", "another.py") missing_file = os.path.realpath(missing_file) msg = "(?i)No source for code: '%s'" % re.escape(missing_file) - with self.assertRaisesRegexp(NoSource, msg): + with self.assertRaisesRegex(NoSource, msg): cov.html_report() class HtmlStaticFileTest(CoverageTest): @@ -340,5 +372,5 @@ class HtmlStaticFileTest(CoverageTest): cov = coverage.coverage() self.start_import_stop(cov, "main") msg = "Couldn't find static file '.*'" - with self.assertRaisesRegexp(CoverageException, msg): + with self.assertRaisesRegex(CoverageException, msg): cov.html_report() diff --git a/tests/test_oddball.py b/tests/test_oddball.py index 786ede94..47f492f6 100644 --- a/tests/test_oddball.py +++ b/tests/test_oddball.py @@ -116,9 +116,8 @@ class RecursionTest(CoverageTest): self.assertEqual(statements, [1,2,3,5,7,8,9,10,11]) self.assertEqual(missing, expected_missing) - # We can get a warning about the stackoverflow effect on the tracing - # function only if we have sys.gettrace - if pytrace and hasattr(sys, "gettrace"): + # Get a warning about the stackoverflow effect on the tracing function. + if pytrace: self.assertEqual(cov._warnings, ["Trace function changed, measurement is likely wrong: None"] ) @@ -368,35 +367,34 @@ class DoctestTest(CoverageTest): [1,11,12,14,16,17], "") -if hasattr(sys, 'gettrace'): - 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) +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('''\ + import sys + def level1(): + a = 3 + level2() + b = 5 + def level2(): + c = 7 sys.settrace(sys.gettrace()) - a = bar(8) - ''', - [1,2,3,4,5,6,7,8], "") - - def test_multi_layers(self): - self.check_coverage('''\ - import sys - def level1(): - a = 3 - level2() - b = 5 - def level2(): - c = 7 - sys.settrace(sys.gettrace()) - d = 9 - e = 10 - level1() - f = 12 - ''', - [1,2,3,4,5,6,7,8,9,10,11,12], "") + d = 9 + e = 10 + level1() + f = 12 + ''', + [1,2,3,4,5,6,7,8,9,10,11,12], "") diff --git a/tests/test_parser.py b/tests/test_parser.py index 80773c74..a392ea03 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -2,23 +2,23 @@ import textwrap from tests.coveragetest import CoverageTest -from coverage.parser import CodeParser +from coverage.parser import PythonParser -class ParserTest(CoverageTest): - """Tests for Coverage.py's code parsing.""" +class PythonParserTest(CoverageTest): + """Tests for Coverage.py's Python code parsing.""" run_in_temp_dir = False def parse_source(self, text): - """Parse `text` as source, and return the `CodeParser` used.""" + """Parse `text` as source, and return the `PythonParser` used.""" text = textwrap.dedent(text) - cp = CodeParser(text=text, exclude="nocover") - cp.parse_source() - return cp + parser = PythonParser(text=text, exclude="nocover") + parser.parse_source() + return parser def test_exit_counts(self): - cp = self.parse_source("""\ + parser = self.parse_source("""\ # check some basic branch counting class Foo: def foo(self, a): @@ -30,12 +30,12 @@ class ParserTest(CoverageTest): class Bar: pass """) - self.assertEqual(cp.exit_counts(), { + self.assertEqual(parser.exit_counts(), { 2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1 }) def test_try_except(self): - cp = self.parse_source("""\ + parser = self.parse_source("""\ try: a = 2 except ValueError: @@ -46,12 +46,12 @@ class ParserTest(CoverageTest): a = 8 b = 9 """) - self.assertEqual(cp.exit_counts(), { + self.assertEqual(parser.exit_counts(), { 1: 1, 2:1, 3:1, 4:1, 5:1, 6:1, 7:1, 8:1, 9:1 }) def test_excluded_classes(self): - cp = self.parse_source("""\ + parser = self.parse_source("""\ class Foo: def __init__(self): pass @@ -60,20 +60,20 @@ class ParserTest(CoverageTest): class Bar: pass """) - self.assertEqual(cp.exit_counts(), { + self.assertEqual(parser.exit_counts(), { 1:0, 2:1, 3:1 }) def test_missing_branch_to_excluded_code(self): - cp = self.parse_source("""\ + parser = self.parse_source("""\ if fooey: a = 2 else: # nocover a = 4 b = 5 """) - self.assertEqual(cp.exit_counts(), { 1:1, 2:1, 5:1 }) - cp = self.parse_source("""\ + self.assertEqual(parser.exit_counts(), { 1:1, 2:1, 5:1 }) + parser = self.parse_source("""\ def foo(): if fooey: a = 3 @@ -81,8 +81,8 @@ class ParserTest(CoverageTest): a = 5 b = 6 """) - self.assertEqual(cp.exit_counts(), { 1:1, 2:2, 3:1, 5:1, 6:1 }) - cp = self.parse_source("""\ + self.assertEqual(parser.exit_counts(), { 1:1, 2:2, 3:1, 5:1, 6:1 }) + parser = self.parse_source("""\ def foo(): if fooey: a = 3 @@ -90,17 +90,17 @@ class ParserTest(CoverageTest): a = 5 b = 6 """) - self.assertEqual(cp.exit_counts(), { 1:1, 2:1, 3:1, 6:1 }) + self.assertEqual(parser.exit_counts(), { 1:1, 2:1, 3:1, 6:1 }) class ParserFileTest(CoverageTest): """Tests for Coverage.py's code parsing from files.""" def parse_file(self, filename): - """Parse `text` as source, and return the `CodeParser` used.""" - cp = CodeParser(filename=filename, exclude="nocover") - cp.parse_source() - return cp + """Parse `text` as source, and return the `PythonParser` used.""" + parser = PythonParser(filename=filename, exclude="nocover") + parser.parse_source() + return parser def test_line_endings(self): text = """\ @@ -120,12 +120,12 @@ class ParserFileTest(CoverageTest): for fname, newline in name_endings: fname = fname + ".py" self.make_file(fname, text, newline=newline) - cp = self.parse_file(fname) - self.assertEqual(cp.exit_counts(), counts) + parser = self.parse_file(fname) + self.assertEqual(parser.exit_counts(), counts) def test_encoding(self): self.make_file("encoded.py", """\ coverage = "\xe7\xf6v\xear\xe3g\xe9" """) - cp = self.parse_file("encoded.py") - cp.exit_counts() + parser = self.parse_file("encoded.py") + parser.exit_counts() # TODO: This value should be tested! diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py index e15400b6..4755c167 100644 --- a/tests/test_phystokens.py +++ b/tests/test_phystokens.py @@ -97,6 +97,11 @@ if sys.version_info < (3, 0): source = "# This Python file uses this encoding: utf-8\n" self.assertEqual(source_encoding(source), 'utf-8') + def test_detect_source_encoding_not_in_comment(self): + # Should not detect anything here + source = 'def parse(src, encoding=None):\n pass' + self.assertEqual(source_encoding(source), 'ascii') + def test_detect_source_encoding_on_second_line(self): # A coding declaration should be found despite a first blank line. source = "\n# coding=cp850\n\n" diff --git a/tests/test_plugins.py b/tests/test_plugins.py new file mode 100644 index 00000000..9c5a037d --- /dev/null +++ b/tests/test_plugins.py @@ -0,0 +1,217 @@ +"""Tests for plugins.""" + +import os.path + +import coverage +from coverage.codeunit import CodeUnit +from coverage.parser import CodeParser +from coverage.plugin import Plugins, overrides + +from tests.coveragetest import CoverageTest + + +class FakeConfig(object): + """A fake config for use in tests.""" + + def __init__(self, plugin, options): + self.plugin = plugin + self.options = options + self.asked_for = [] + + def get_plugin_options(self, module): + """Just return the options for `module` if this is the right module.""" + self.asked_for.append(module) + if module == self.plugin: + return self.options + else: + return {} + + +class PluginUnitTest(CoverageTest): + """Test Plugins.load_plugins directly.""" + + def test_importing_and_configuring(self): + self.make_file("plugin1.py", """\ + from coverage import CoveragePlugin + + class Plugin(CoveragePlugin): + def __init__(self, options): + super(Plugin, self).__init__(options) + self.this_is = "me" + """) + + config = FakeConfig("plugin1", {'a':'hello'}) + plugins = list(Plugins.load_plugins(["plugin1"], config)) + + self.assertEqual(len(plugins), 1) + self.assertEqual(plugins[0].this_is, "me") + self.assertEqual(plugins[0].options, {'a':'hello'}) + self.assertEqual(config.asked_for, ['plugin1']) + + def test_importing_and_configuring_more_than_one(self): + self.make_file("plugin1.py", """\ + from coverage import CoveragePlugin + + class Plugin(CoveragePlugin): + def __init__(self, options): + super(Plugin, self).__init__(options) + self.this_is = "me" + """) + self.make_file("plugin2.py", """\ + from coverage import CoveragePlugin + + class Plugin(CoveragePlugin): + pass + """) + + config = FakeConfig("plugin1", {'a':'hello'}) + plugins = list(Plugins.load_plugins(["plugin1", "plugin2"], config)) + + self.assertEqual(len(plugins), 2) + self.assertEqual(plugins[0].this_is, "me") + self.assertEqual(plugins[0].options, {'a':'hello'}) + self.assertEqual(plugins[1].options, {}) + self.assertEqual(config.asked_for, ['plugin1', 'plugin2']) + + def test_cant_import(self): + with self.assertRaises(ImportError): + _ = Plugins.load_plugins(["plugin_not_there"], None) + + def test_ok_to_not_define_plugin(self): + self.make_file("plugin2.py", """\ + from coverage import CoveragePlugin + + Nothing = 0 + """) + plugins = list(Plugins.load_plugins(["plugin2"], None)) + self.assertEqual(plugins, []) + + +class PluginTest(CoverageTest): + """Test plugins through the Coverage class.""" + + def test_plugin_imported(self): + # Prove that a plugin will be imported. + self.make_file("my_plugin.py", """\ + with open("evidence.out", "w") as f: + f.write("we are here!") + """) + + self.assert_doesnt_exist("evidence.out") + _ = coverage.Coverage(plugins=["my_plugin"]) + + with open("evidence.out") as f: + self.assertEqual(f.read(), "we are here!") + + def test_missing_plugin_raises_import_error(self): + # Prove that a missing plugin will raise an ImportError. + with self.assertRaises(ImportError): + cov = coverage.Coverage(plugins=["does_not_exist_woijwoicweo"]) + cov.start() + + def test_bad_plugin_isnt_hidden(self): + # Prove that a plugin with an error in it will raise the error. + self.make_file("plugin_over_zero.py", """\ + 1/0 + """) + with self.assertRaises(ZeroDivisionError): + _ = coverage.Coverage(plugins=["plugin_over_zero"]) + + def test_importing_myself(self): + self.make_file("simple.py", """\ + import try_xyz + a = 1 + b = 2 + """) + self.make_file("try_xyz.py", """\ + c = 3 + d = 4 + """) + + cov = coverage.Coverage(plugins=["tests.test_plugins"]) + + # Import the python file, executing it. + self.start_import_stop(cov, "simple") + + _, statements, missing, _ = cov.analysis("simple.py") + self.assertEqual(statements, [1,2,3]) + self.assertEqual(missing, []) + _, statements, _, _ = cov.analysis("/src/try_ABC.zz") + self.assertEqual(statements, [105, 106, 107, 205, 206, 207]) + + +class Plugin(coverage.CoveragePlugin): + def trace_judge(self, disp): + if "xyz.py" in disp.original_filename: + disp.trace = True + disp.source_filename = os.path.join( + "/src", + os.path.basename( + disp.original_filename.replace("xyz.py", "ABC.zz") + ) + ) + + def line_number_range(self, frame): + lineno = frame.f_lineno + return lineno*100+5, lineno*100+7 + + def code_unit_class(self, filename): + return PluginCodeUnit + +class PluginCodeUnit(CodeUnit): + def get_parser(self, exclude=None): + return PluginParser() + +class PluginParser(CodeParser): + def parse_source(self): + return set([105, 106, 107, 205, 206, 207]), set([]) + + +class OverridesTest(CoverageTest): + """Test plugins.py:overrides.""" + + run_in_temp_dir = False + + def test_overrides(self): + class SomeBase(object): + """Base class, two base methods.""" + def method1(self): + pass + + def method2(self): + pass + + class Derived1(SomeBase): + """Simple single inheritance.""" + def method1(self): + pass + + self.assertTrue(overrides(Derived1(), "method1", SomeBase)) + self.assertFalse(overrides(Derived1(), "method2", SomeBase)) + + class FurtherDerived1(Derived1): + """Derive again from Derived1, inherit its method1.""" + pass + + self.assertTrue(overrides(FurtherDerived1(), "method1", SomeBase)) + self.assertFalse(overrides(FurtherDerived1(), "method2", SomeBase)) + + class FurtherDerived2(Derived1): + """Override the overridden method.""" + def method1(self): + pass + + self.assertTrue(overrides(FurtherDerived2(), "method1", SomeBase)) + self.assertFalse(overrides(FurtherDerived2(), "method2", SomeBase)) + + class Mixin(object): + """A mixin that overrides method1.""" + def method1(self): + pass + + class Derived2(Mixin, SomeBase): + """A class that gets the method from the mixin.""" + pass + + self.assertTrue(overrides(Derived2(), "method1", SomeBase)) + self.assertFalse(overrides(Derived2(), "method2", SomeBase)) diff --git a/tests/test_process.py b/tests/test_process.py index fa4759a8..3a0980dc 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -26,7 +26,7 @@ class ProcessTest(CoverageTest): """) self.assert_doesnt_exist(".coverage") - self.run_command("coverage -x mycode.py") + self.run_command("coverage run mycode.py") self.assert_exists(".coverage") def test_environment(self): @@ -39,7 +39,7 @@ class ProcessTest(CoverageTest): """) self.assert_doesnt_exist(".coverage") - out = self.run_command("coverage -x mycode.py") + out = self.run_command("coverage run mycode.py") self.assert_exists(".coverage") self.assertEqual(out, 'done\n') @@ -55,11 +55,11 @@ class ProcessTest(CoverageTest): print('done') """) - out = self.run_command("coverage -x -p b_or_c.py b") + out = self.run_command("coverage run -p b_or_c.py b") self.assertEqual(out, 'done\n') self.assert_doesnt_exist(".coverage") - out = self.run_command("coverage -x -p b_or_c.py c") + out = self.run_command("coverage run -p b_or_c.py c") self.assertEqual(out, 'done\n') self.assert_doesnt_exist(".coverage") @@ -67,7 +67,7 @@ class ProcessTest(CoverageTest): self.assertEqual(self.number_of_data_files(), 2) # Combine the parallel coverage data files into .coverage . - self.run_command("coverage -c") + self.run_command("coverage combine") self.assert_exists(".coverage") # After combining, there should be only the .coverage file. @@ -91,23 +91,23 @@ class ProcessTest(CoverageTest): print('done') """) - out = self.run_command("coverage -x -p b_or_c.py b") + out = self.run_command("coverage run -p b_or_c.py b") self.assertEqual(out, 'done\n') self.assert_doesnt_exist(".coverage") self.assertEqual(self.number_of_data_files(), 1) # Combine the (one) parallel coverage data file into .coverage . - self.run_command("coverage -c") + self.run_command("coverage combine") self.assert_exists(".coverage") self.assertEqual(self.number_of_data_files(), 1) - out = self.run_command("coverage -x -p b_or_c.py c") + out = self.run_command("coverage run --append -p b_or_c.py c") self.assertEqual(out, 'done\n') self.assert_exists(".coverage") self.assertEqual(self.number_of_data_files(), 2) # Combine the parallel coverage data files into .coverage . - self.run_command("coverage -c") + self.run_command("coverage combine") self.assert_exists(".coverage") # After combining, there should be only the .coverage file. @@ -229,7 +229,7 @@ class ProcessTest(CoverageTest): self.run_command("coverage run fleeting.py") os.remove("fleeting.py") out = self.run_command("coverage html -d htmlcov") - self.assertRegexpMatches(out, "No source for code: '.*fleeting.py'") + self.assertRegex(out, "No source for code: '.*fleeting.py'") self.assertNotIn("Traceback", out) # It happens that the code paths are different for *.py and other @@ -240,14 +240,14 @@ class ProcessTest(CoverageTest): self.run_command("coverage run fleeting") os.remove("fleeting") - status, out = self.run_command_status("coverage html -d htmlcov", 1) - self.assertRegexpMatches(out, "No source for code: '.*fleeting'") + status, out = self.run_command_status("coverage html -d htmlcov") + self.assertRegex(out, "No source for code: '.*fleeting'") self.assertNotIn("Traceback", out) self.assertEqual(status, 1) def test_running_missing_file(self): - status, out = self.run_command_status("coverage run xyzzy.py", 1) - self.assertRegexpMatches(out, "No file to run: .*xyzzy.py") + status, out = self.run_command_status("coverage run xyzzy.py") + self.assertRegex(out, "No file to run: .*xyzzy.py") self.assertNotIn("raceback", out) self.assertNotIn("rror", out) self.assertEqual(status, 1) @@ -265,7 +265,7 @@ class ProcessTest(CoverageTest): # The important thing is for "coverage run" and "python" to report the # same traceback. - status, out = self.run_command_status("coverage run throw.py", 1) + status, out = self.run_command_status("coverage run throw.py") out2 = self.run_command("python throw.py") if '__pypy__' in sys.builtin_module_names: # Pypy has an extra frame in the traceback for some reason @@ -294,8 +294,8 @@ class ProcessTest(CoverageTest): # The important thing is for "coverage run" and "python" to have the # same output. No traceback. - status, out = self.run_command_status("coverage run exit.py", 17) - status2, out2 = self.run_command_status("python exit.py", 17) + status, out = self.run_command_status("coverage run exit.py") + status2, out2 = self.run_command_status("python exit.py") self.assertMultiLineEqual(out, out2) self.assertMultiLineEqual(out, "about to exit..\n") self.assertEqual(status, status2) @@ -310,8 +310,8 @@ class ProcessTest(CoverageTest): f1() """) - status, out = self.run_command_status("coverage run exit_none.py", 0) - status2, out2 = self.run_command_status("python exit_none.py", 0) + status, out = self.run_command_status("coverage run exit_none.py") + status2, out2 = self.run_command_status("python exit_none.py") self.assertMultiLineEqual(out, out2) self.assertMultiLineEqual(out, "about to exit quietly..\n") self.assertEqual(status, status2) @@ -378,7 +378,7 @@ class ProcessTest(CoverageTest): self.assertEqual(self.number_of_data_files(), 2) # Combine the parallel coverage data files into .coverage . - self.run_command("coverage -c") + self.run_command("coverage combine") self.assert_exists(".coverage") # After combining, there should be only the .coverage file. @@ -470,8 +470,7 @@ class ProcessTest(CoverageTest): self.assertIn("Hello\n", out) self.assertIn("Goodbye\n", out) - if hasattr(sys, "gettrace"): - self.assertIn("Trace function changed", out) + self.assertIn("Trace function changed", out) if sys.version_info >= (3, 0): # This only works on 3.x for now. # It only works with the C tracer, @@ -502,6 +501,18 @@ class ProcessTest(CoverageTest): # about 5. self.assertGreater(data.summary()['os.py'], 50) + def test_deprecation_warnings(self): + # Test that coverage doesn't trigger deprecation warnings. + # https://bitbucket.org/ned/coveragepy/issue/305/pendingdeprecationwarning-the-imp-module + self.make_file("allok.py", """\ + import warnings + warnings.simplefilter('default') + import coverage + print("No warnings!") + """) + out = self.run_command("python allok.py") + self.assertEqual(out, "No warnings!\n") + class AliasedCommandTest(CoverageTest): """Tests of the version-specific command aliases.""" @@ -556,32 +567,47 @@ class FailUnderTest(CoverageTest): def setUp(self): super(FailUnderTest, self).setUp() - self.make_file("fifty.py", """\ - # I have 50% coverage! + self.make_file("forty_two_plus.py", """\ + # I have 42.857% (3/7) coverage! a = 1 - if a > 2: - b = 3 - c = 4 + b = 2 + if a > 3: + b = 4 + c = 5 + d = 6 + e = 7 """) - st, _ = self.run_command_status("coverage run fifty.py", 0) + st, _ = self.run_command_status("coverage run forty_two_plus.py") + self.assertEqual(st, 0) + st, out = self.run_command_status("coverage report") self.assertEqual(st, 0) + self.assertEqual( + self.last_line_squeezed(out), + "forty_two_plus 7 4 43%" + ) def test_report(self): - st, _ = self.run_command_status("coverage report --fail-under=50", 0) + 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=51", 2) + 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=50", 0) + st, _ = self.run_command_status("coverage html --fail-under=42") self.assertEqual(st, 0) - st, _ = self.run_command_status("coverage html --fail-under=51", 2) + 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=50", 0) + 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=51", 2) + st, _ = self.run_command_status("coverage xml --fail-under=44") self.assertEqual(st, 2) diff --git a/tests/test_summary.py b/tests/test_summary.py index 29167bf8..7bd1c496 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -21,26 +21,10 @@ class SummaryTest(CoverageTest): # Parent class saves and restores sys.path, we can just modify it. sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules')) - def report_from_command(self, cmd): - """Return the report from the `cmd`, with some convenience added.""" - report = self.run_command(cmd).replace('\\', '/') - self.assertNotIn("error", report.lower()) - return report - - def line_count(self, report): - """How many lines are in `report`?""" - self.assertEqual(report.split('\n')[-1], "") - return len(report.split('\n')) - 1 - - def last_line_squeezed(self, report): - """Return the last line of `report` with the spaces squeezed down.""" - last_line = report.split('\n')[-2] - return re.sub(r"\s+", " ", last_line) - def test_report(self): - out = self.run_command("coverage -x mycode.py") + out = self.run_command("coverage run mycode.py") self.assertEqual(out, 'done\n') - report = self.report_from_command("coverage -r") + report = self.report_from_command("coverage report") # Name Stmts Miss Cover # --------------------------------------------------------------------- @@ -58,8 +42,24 @@ class SummaryTest(CoverageTest): def test_report_just_one(self): # Try reporting just one module - self.run_command("coverage -x mycode.py") - report = self.report_from_command("coverage -r mycode.py") + self.run_command("coverage run mycode.py") + report = self.report_from_command("coverage report mycode.py") + + # Name Stmts Miss Cover + # ---------------------------- + # mycode 4 0 100% + + self.assertEqual(self.line_count(report), 3) + self.assertNotIn("/coverage/", report) + self.assertNotIn("/tests/modules/covmod1 ", report) + self.assertNotIn("/tests/zipmods.zip/covmodzip1 ", report) + self.assertIn("mycode ", report) + self.assertEqual(self.last_line_squeezed(report), "mycode 4 0 100%") + + def test_report_wildcard(self): + # Try reporting using wildcards to get the modules. + self.run_command("coverage run mycode.py") + report = self.report_from_command("coverage report my*.py") # Name Stmts Miss Cover # ---------------------------- @@ -75,8 +75,10 @@ class SummaryTest(CoverageTest): def test_report_omitting(self): # Try reporting while omitting some modules prefix = os.path.split(__file__)[0] - self.run_command("coverage -x mycode.py") - report = self.report_from_command("coverage -r -o '%s/*'" % prefix) + self.run_command("coverage run mycode.py") + report = self.report_from_command( + "coverage report --omit '%s/*'" % prefix + ) # Name Stmts Miss Cover # ---------------------------- @@ -126,13 +128,109 @@ class SummaryTest(CoverageTest): self.assertEqual(self.last_line_squeezed(report), "mybranch 5 0 2 1 86%") + def test_report_show_missing(self): + self.make_file("mymissing.py", """\ + def missing(x, y): + if x: + print("x") + return x + if y: + print("y") + try: + print("z") + 1/0 + print("Never!") + except ZeroDivisionError: + pass + return x + missing(0, 1) + """) + out = self.run_command("coverage run mymissing.py") + self.assertEqual(out, 'y\nz\n') + report = self.report_from_command("coverage report --show-missing") + + # Name Stmts Miss Cover Missing + # ----------------------------------------- + # mymissing 14 3 79% 3-4, 10 + + self.assertEqual(self.line_count(report), 3) + self.assertIn("mymissing ", report) + self.assertEqual(self.last_line_squeezed(report), + "mymissing 14 3 79% 3-4, 10") + + def test_report_show_missing_branches(self): + self.make_file("mybranch.py", """\ + def branch(x, y): + if x: + print("x") + if y: + print("y") + return x + branch(1, 1) + """) + out = self.run_command("coverage run --branch mybranch.py") + self.assertEqual(out, 'x\ny\n') + report = self.report_from_command("coverage report --show-missing") + + # Name Stmts Miss Branch BrMiss Cover Missing + # ------------------------------------------------------- + # mybranch 7 0 4 2 82% 2->4, 4->6 + + self.assertEqual(self.line_count(report), 3) + self.assertIn("mybranch ", report) + self.assertEqual(self.last_line_squeezed(report), + "mybranch 7 0 4 2 82% 2->4, 4->6") + + def test_report_show_missing_branches_and_lines(self): + self.make_file("main.py", """\ + import mybranch + """) + self.make_file("mybranch.py", """\ + def branch(x, y, z): + if x: + print("x") + if y: + print("y") + if z: + if x and y: + print("z") + return x + branch(1, 1, 0) + """) + out = self.run_command("coverage run --branch main.py") + self.assertEqual(out, 'x\ny\n') + report = self.report_from_command("coverage report --show-missing") + + # pylint: disable=C0301 + # Name Stmts Miss Branch BrMiss Cover Missing + # ------------------------------------------------------- + # main 1 0 0 0 100% + # mybranch 10 2 8 5 61% 7-8, 2->4, 4->6 + # ------------------------------------------------------- + # TOTAL 11 2 8 5 63% + + self.assertEqual(self.line_count(report), 6) + squeezed = self.squeezed_lines(report) + self.assertEqual( + squeezed[2], + "main 1 0 0 0 100%" + ) + self.assertEqual( + squeezed[3], + "mybranch 10 2 8 5 61% 7-8, 2->4, 4->6" + ) + self.assertEqual( + squeezed[5], + "TOTAL 11 2 8 5 63%" + ) + def test_dotpy_not_python(self): # We run a .py file, and when reporting, we can't parse it as Python. # We should get an error message in the report. self.run_command("coverage run mycode.py") self.make_file("mycode.py", "This isn't python at all!") - report = self.report_from_command("coverage -r mycode.py") + report = self.report_from_command("coverage report mycode.py") # pylint: disable=C0301 # Name Stmts Miss Cover @@ -155,7 +253,7 @@ class SummaryTest(CoverageTest): # but we've said to ignore errors, so there's no error reported. self.run_command("coverage run mycode.py") self.make_file("mycode.py", "This isn't python at all!") - report = self.report_from_command("coverage -r -i mycode.py") + report = self.report_from_command("coverage report -i mycode.py") # Name Stmts Miss Cover # ---------------------------- @@ -171,7 +269,7 @@ class SummaryTest(CoverageTest): self.run_command("coverage run mycode.html") # Before reporting, change it to be an HTML file. self.make_file("mycode.html", "<h1>This isn't python at all!</h1>") - report = self.report_from_command("coverage -r mycode.html") + report = self.report_from_command("coverage report mycode.html") # Name Stmts Miss Cover # ---------------------------- diff --git a/tests/test_templite.py b/tests/test_templite.py index 48e53ab4..a4667a62 100644 --- a/tests/test_templite.py +++ b/tests/test_templite.py @@ -1,6 +1,7 @@ """Tests for coverage.templite.""" -from coverage.templite import Templite +import re +from coverage.templite import Templite, TempliteSyntaxError from tests.coveragetest import CoverageTest # pylint: disable=W0612,E1101 @@ -23,9 +24,23 @@ class TempliteTest(CoverageTest): run_in_temp_dir = False - def try_render(self, text, ctx, result): - """Render `text` through `ctx`, and it had better be `result`.""" - self.assertEqual(Templite(text).render(ctx), result) + def try_render(self, text, ctx=None, result=None): + """Render `text` through `ctx`, and it had better be `result`. + + Result defaults to None so we can shorten the calls where we expect + an exception and never get to the result comparison. + """ + actual = Templite(text).render(ctx or {}) + if result: + self.assertEqual(actual, result) + + def assertSynErr(self, msg): + """Assert that a `TempliteSyntaxError` will happen. + + A context manager, and the message should be `msg`. + """ + pat = "^" + re.escape(msg) + "$" + return self.assertRaisesRegex(TempliteSyntaxError, pat) def test_passthrough(self): # Strings without variables are passed through unchanged. @@ -42,7 +57,7 @@ class TempliteTest(CoverageTest): def test_undefined_variables(self): # Using undefined names is an error. with self.assertRaises(Exception): - self.try_render("Hi, {{name}}!", {}, "xyz") + self.try_render("Hi, {{name}}!") def test_pipes(self): # Variables can be filtered with pipes. @@ -225,15 +240,42 @@ class TempliteTest(CoverageTest): "Hey {{foo.bar.baz}} there", {'foo': None}, "Hey ??? there" ) + def test_bad_names(self): + with self.assertSynErr("Not a valid name: 'var%&!@'"): + self.try_render("Wat: {{ var%&!@ }}") + with self.assertSynErr("Not a valid name: 'filter%&!@'"): + self.try_render("Wat: {{ foo|filter%&!@ }}") + with self.assertSynErr("Not a valid name: '@'"): + self.try_render("Wat: {% for @ in x %}{% endfor %}") + def test_bogus_tag_syntax(self): - msg = "Don't understand tag: 'bogus'" - with self.assertRaisesRegexp(SyntaxError, msg): - self.try_render("Huh: {% bogus %}!!{% endbogus %}??", {}, "") + with self.assertSynErr("Don't understand tag: 'bogus'"): + self.try_render("Huh: {% bogus %}!!{% endbogus %}??") + + def test_malformed_if(self): + with self.assertSynErr("Don't understand if: '{% if %}'"): + self.try_render("Buh? {% if %}hi!{% endif %}") + with self.assertSynErr("Don't understand if: '{% if this or that %}'"): + self.try_render("Buh? {% if this or that %}hi!{% endif %}") + + def test_malformed_for(self): + with self.assertSynErr("Don't understand for: '{% for %}'"): + self.try_render("Weird: {% for %}loop{% endfor %}") + with self.assertSynErr("Don't understand for: '{% for x from y %}'"): + self.try_render("Weird: {% for x from y %}loop{% endfor %}") + with self.assertSynErr("Don't understand for: '{% for x, y in z %}'"): + self.try_render("Weird: {% for x, y in z %}loop{% endfor %}") def test_bad_nesting(self): - msg = "Unmatched action tag: 'if'" - with self.assertRaisesRegexp(SyntaxError, msg): - self.try_render("{% if x %}X", {}, "") - msg = "Mismatched end tag: 'for'" - with self.assertRaisesRegexp(SyntaxError, msg): - self.try_render("{% if x %}X{% endfor %}", {}, "") + with self.assertSynErr("Unmatched action tag: 'if'"): + self.try_render("{% if x %}X") + with self.assertSynErr("Mismatched end tag: 'for'"): + self.try_render("{% if x %}X{% endfor %}") + with self.assertSynErr("Too many ends: '{% endif %}'"): + self.try_render("{% if x %}{% endif %}{% endif %}") + + def test_malformed_end(self): + with self.assertSynErr("Don't understand end: '{% end if %}'"): + self.try_render("{% if x %}X{% end if %}") + with self.assertSynErr("Don't understand end: '{% endif now %}'"): + self.try_render("{% if x %}X{% endif now %}") diff --git a/tests/test_testing.py b/tests/test_testing.py index a89a59a9..4a19098f 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -2,45 +2,44 @@ """Tests that our test infrastructure is really working!""" import os, sys +from coverage.backunittest import TestCase from coverage.backward import to_bytes -from tests.backunittest import TestCase -from tests.coveragetest import CoverageTest +from tests.coveragetest import TempDirMixin, CoverageTest class TestingTest(TestCase): """Tests of helper methods on `backunittest.TestCase`.""" - run_in_temp_dir = False - - def test_assert_same_elements(self): - self.assertSameElements(set(), set()) - self.assertSameElements(set([1,2,3]), set([3,1,2])) + def test_assert_count_equal(self): + self.assertCountEqual(set(), set()) + self.assertCountEqual(set([1,2,3]), set([3,1,2])) with self.assertRaises(AssertionError): - self.assertSameElements(set([1,2,3]), set()) + self.assertCountEqual(set([1,2,3]), set()) with self.assertRaises(AssertionError): - self.assertSameElements(set([1,2,3]), set([4,5,6])) + self.assertCountEqual(set([1,2,3]), set([4,5,6])) -class CoverageTestTest(CoverageTest): - """Test the methods in `CoverageTest`.""" +class TempDirMixinTest(TempDirMixin, TestCase): + """Test the methods in TempDirMixin.""" def file_text(self, fname): """Return the text read from a file.""" - return open(fname, "rb").read().decode('ascii') + with open(fname, "rb") as f: + return f.read().decode('ascii') def test_make_file(self): # A simple file. self.make_file("fooey.boo", "Hello there") - self.assertEqual(open("fooey.boo").read(), "Hello there") + self.assertEqual(self.file_text("fooey.boo"), "Hello there") # A file in a sub-directory self.make_file("sub/another.txt", "Another") - self.assertEqual(open("sub/another.txt").read(), "Another") + self.assertEqual(self.file_text("sub/another.txt"), "Another") # A second file in that sub-directory self.make_file("sub/second.txt", "Second") - self.assertEqual(open("sub/second.txt").read(), "Second") + self.assertEqual(self.file_text("sub/second.txt"), "Second") # A deeper directory self.make_file("sub/deeper/evenmore/third.txt") - self.assertEqual(open("sub/deeper/evenmore/third.txt").read(), "") + self.assertEqual(self.file_text("sub/deeper/evenmore/third.txt"), "") def test_make_file_newline(self): self.make_file("unix.txt", "Hello\n") @@ -52,10 +51,13 @@ class CoverageTestTest(CoverageTest): def test_make_file_non_ascii(self): self.make_file("unicode.txt", "tabblo: «ταБЬℓσ»") - self.assertEqual( - open("unicode.txt", "rb").read(), - to_bytes("tabblo: «ταБЬℓσ»") - ) + with open("unicode.txt", "rb") as f: + text = f.read() + self.assertEqual(text, to_bytes("tabblo: «ταБЬℓσ»")) + + +class CoverageTestTest(CoverageTest): + """Test the methods in `CoverageTest`.""" def test_file_exists(self): self.make_file("whoville.txt", "We are here!") diff --git a/tests/test_xml.py b/tests/test_xml.py index 0801bad3..37ada3cb 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -26,6 +26,13 @@ class XmlReportTest(CoverageTest): self.assert_doesnt_exist("coverage.xml") self.assert_exists("put_it_there.xml") + def test_config_file_directory_does_not_exist(self): + self.run_mycode() + self.run_command("coverage xml -o nonexistent/put_it_there.xml") + self.assert_doesnt_exist("coverage.xml") + self.assert_doesnt_exist("put_it_there.xml") + self.assert_exists("nonexistent/put_it_there.xml") + def test_config_affects_xml_placement(self): self.run_mycode() self.make_file(".coveragerc", "[xml]\noutput = xml.out\n") |