summaryrefslogtreecommitdiff
path: root/test/coveragetest.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/coveragetest.py')
-rw-r--r--test/coveragetest.py134
1 files changed, 103 insertions, 31 deletions
diff --git a/test/coveragetest.py b/test/coveragetest.py
index 4471392..c82891e 100644
--- a/test/coveragetest.py
+++ b/test/coveragetest.py
@@ -38,8 +38,6 @@ class CoverageTest(TestCase):
self.old_dir = os.getcwd()
os.chdir(self.temp_dir)
- # Preserve changes to PYTHONPATH.
- self.old_pypath = os.environ.get('PYTHONPATH', '')
# Modules should be importable from this temp directory.
self.old_syspath = sys.path[:]
@@ -48,28 +46,70 @@ class CoverageTest(TestCase):
# Keep a counter to make every call to check_coverage unique.
self.n = 0
- # Use a Tee to capture stdout.
+ # 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
def tearDown(self):
if self.run_in_temp_dir:
- # Restore the original sys.path and PYTHONPATH
+ # Restore the original sys.path.
sys.path = self.old_syspath
- os.environ['PYTHONPATH'] = self.old_pypath
# Get rid of the temporary directory.
os.chdir(self.old_dir)
shutil.rmtree(self.temp_root)
- # Restore stdout.
+ # Restore the environment.
+ self.undo_environ()
+
+ # Restore stdout and stderr
sys.stdout = self.old_stdout
+ sys.stderr = self.old_stderr
+
+ 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):
+ """The environment variable `name` from when the test started."""
+ if name in self.environ_undos:
+ return self.environ_undos[name]
+ else:
+ return os.environ.get(name)
+
+ 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):
"""Create a temp file.
@@ -133,14 +173,22 @@ class CoverageTest(TestCase):
arcs.append((self._arcz_map[a], self._arcz_map[b]))
return sorted(arcs)
+ def assertEqualArcs(self, a1, a2):
+ """Assert that the arc lists `a1` and `a2` are equal."""
+ # Make them into multi-line strings so we can see what's going wrong.
+ s1 = "\n".join([repr(a) for a in a1]) + "\n"
+ s2 = "\n".join([repr(a) for a in a2]) + "\n"
+ self.assertMultiLineEqual(s1, s2)
+
def check_coverage(self, text, lines=None, missing="", excludes=None,
report="", arcz=None, arcz_missing="", arcz_unpredicted=""):
"""Check the coverage measurement of `text`.
The source `text` is run and measured. `lines` are the line numbers
- that are executable, `missing` are the lines not executed, `excludes`
- are regexes to match against for excluding lines, and `report` is
- the text of the measurement report.
+ that are executable, or a list of possible line numbers, any of which
+ could match. `missing` are the lines not executed, `excludes` are
+ regexes to match against for excluding lines, and `report` is the text
+ of the measurement report.
For arc measurement, `arcz` is a string that can be decoded into arcs
in the code (see `arcz_to_arcs` for the encoding scheme),
@@ -168,10 +216,10 @@ class CoverageTest(TestCase):
cov.exclude(exc)
cov.start()
- try:
+ try: # pragma: recursive coverage
# Import the python file, executing it.
mod = self.import_module(modname)
- finally:
+ finally: # pragma: recursive coverage
# Stop Coverage.
cov.stop()
@@ -182,8 +230,12 @@ class CoverageTest(TestCase):
analysis = cov._analyze(mod)
if lines is not None:
if type(lines[0]) == type(1):
+ # lines is just a list of numbers, it must match the statements
+ # found in the code.
self.assertEqual(analysis.statements, lines)
else:
+ # lines is a list of possible line number lists, one of them
+ # must match.
for line_list in lines:
if analysis.statements == line_list:
break
@@ -192,26 +244,27 @@ class CoverageTest(TestCase):
analysis.statements
)
- if missing is not None:
- if type(missing) == type(""):
- self.assertEqual(analysis.missing_formatted(), missing)
+ if type(missing) == type(""):
+ self.assertEqual(analysis.missing_formatted(), missing)
+ else:
+ for missing_list in missing:
+ if analysis.missing_formatted() == missing_list:
+ break
else:
- for missing_list in missing:
- if analysis.missing == 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" %
+ analysis.missing_formatted()
+ )
if arcs is not None:
- self.assertEqual(analysis.arc_possibilities(), arcs)
+ self.assertEqualArcs(analysis.arc_possibilities(), arcs)
if arcs_missing is not None:
- self.assertEqual(analysis.arcs_missing(), arcs_missing)
+ self.assertEqualArcs(analysis.arcs_missing(), arcs_missing)
if arcs_unpredicted is not None:
- self.assertEqual(analysis.arcs_unpredicted(), arcs_unpredicted)
+ self.assertEqualArcs(
+ analysis.arcs_unpredicted(), arcs_unpredicted
+ )
if report:
frep = StringIO()
@@ -224,7 +277,7 @@ class CoverageTest(TestCase):
fname = os.path.join(*fparts)
return os.path.normcase(os.path.abspath(os.path.realpath(fname)))
- def command_line(self, args, ret=OK):
+ def command_line(self, args, ret=OK, _covpkg=None):
"""Run `args` through the command line.
Use this when you want to run the full coverage machinery, but in the
@@ -236,11 +289,12 @@ class CoverageTest(TestCase):
Returns None.
"""
- ret_actual = coverage.CoverageScript().command_line(shlex.split(args))
+ script = coverage.CoverageScript(_covpkg=_covpkg)
+ ret_actual = script.command_line(shlex.split(args))
self.assertEqual(ret_actual, ret)
def run_command(self, cmd):
- """ Run the command-line `cmd` in a subprocess, and print its output.
+ """Run the command-line `cmd` in a subprocess, and print its output.
Use this when you need to test the process behavior of coverage.
@@ -249,17 +303,35 @@ class CoverageTest(TestCase):
Returns the process' stdout text.
"""
+ _, output = self.run_command_status(cmd)
+ return output
+
+ def run_command_status(self, cmd, status=0):
+ """Run the command-line `cmd` in a subprocess, and print its output.
+
+ Use this when you need to test the process behavior of coverage.
+
+ Compare with `command_line`.
+
+ 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...
here = os.path.dirname(self.nice_file(coverage.__file__, ".."))
testmods = self.nice_file(here, 'test/modules')
zipfile = self.nice_file(here, 'test/zipmods.zip')
- pypath = self.old_pypath
+ pypath = self.original_environ('PYTHONPATH')
if pypath:
pypath += os.pathsep
+ else:
+ pypath = ""
pypath += testmods + os.pathsep + zipfile
- os.environ['PYTHONPATH'] = pypath
+ self.set_environ('PYTHONPATH', pypath)
- _, output = run_command(cmd)
+ status, output = run_command(cmd, status=status)
print(output)
- return output
+ return status, output