diff options
-rw-r--r-- | pylintrc | 4 | ||||
-rw-r--r-- | tests/coveragetest.py | 30 | ||||
-rw-r--r-- | tests/mixins.py | 131 | ||||
-rw-r--r-- | tests/test_api.py | 8 | ||||
-rw-r--r-- | tests/test_cmdline.py | 4 | ||||
-rw-r--r-- | tests/test_files.py | 8 | ||||
-rw-r--r-- | tests/test_html.py | 8 | ||||
-rw-r--r-- | tests/test_numbits.py | 4 | ||||
-rw-r--r-- | tests/test_oddball.py | 2 | ||||
-rw-r--r-- | tests/test_process.py | 15 | ||||
-rw-r--r-- | tests/test_setup.py | 4 |
11 files changed, 166 insertions, 52 deletions
@@ -144,7 +144,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # TestCase overrides don't: setUp, tearDown # Nested decorator implementations: _decorator, _wrapper # Dispatched methods don't: _xxx__Xxxx -no-docstring-rgx=__.*__|test[A-Z_].*|setUp|tearDown|_decorator|_wrapper|_.*__.* +no-docstring-rgx=__.*__|test[A-Z_].*|setup_test|_decorator|_wrapper|_.*__.* # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ @@ -232,7 +232,7 @@ additional-builtins= [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp,reset +defining-attr-methods=__init__,__new__,setup_test,reset # checks for sign of poor/misdesign: diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 0bfc7123..8427f4ad 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -13,10 +13,8 @@ import random import re import shlex import sys -import unittest import pytest -from unittest_mixins import TempDirMixin import coverage from coverage import env @@ -25,7 +23,10 @@ from coverage.cmdline import CoverageScript from tests.helpers import arcs_to_arcz_repr, arcz_to_arcs, assert_count_equal from tests.helpers import run_command, SuperModuleCleaner -from tests.mixins import EnvironmentAwareMixin, StdStreamCapturingMixin, StopEverythingMixin +from tests.mixins import ( + StdStreamCapturingMixin, StopEverythingMixin, + TempDirMixin, PytestBase, +) # Status returns for the command line. @@ -36,11 +37,10 @@ TESTS_DIR = os.path.dirname(__file__) class CoverageTest( - EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin, StopEverythingMixin, - unittest.TestCase, + PytestBase, ): """A base class for coverage.py test cases.""" @@ -62,8 +62,8 @@ class CoverageTest( # $set_env.py: COVERAGE_KEEP_TMP - Keep the temp directories made by tests. keep_temp_dir = bool(int(os.getenv("COVERAGE_KEEP_TMP", "0"))) - def setUp(self): - super(CoverageTest, self).setUp() + def setup_test(self): + super(CoverageTest, self).setup_test() self.module_cleaner = SuperModuleCleaner() @@ -187,7 +187,7 @@ class CoverageTest( if statements == line_list: break else: - self.fail("None of the lines choices matched %r" % statements) + assert False, "None of the lines choices matched %r" % (statements,) missing_formatted = analysis.missing_formatted() if isinstance(missing, string_class): @@ -198,7 +198,7 @@ class CoverageTest( if missing_formatted == missing_list: break else: - self.fail("None of the missing choices matched %r" % missing_formatted) + assert False, "None of the missing choices matched %r" % (missing_formatted,) if arcs is not None: # print("Possible arcs:") @@ -262,15 +262,17 @@ class CoverageTest( if re.search(warning_regex, saved): break else: - self.fail("Didn't find warning %r in %r" % (warning_regex, saved_warnings)) + msg = "Didn't find warning %r in %r" % (warning_regex, saved_warnings) + assert False, msg for warning_regex in not_warnings: for saved in saved_warnings: if re.search(warning_regex, saved): - self.fail("Found warning %r in %r" % (warning_regex, saved_warnings)) + msg = "Found warning %r in %r" % (warning_regex, saved_warnings) + assert False, msg else: # No warnings expected. Raise if any warnings happened. if saved_warnings: - self.fail("Unexpected warnings: %r" % (saved_warnings,)) + assert False, "Unexpected warnings: %r" % (saved_warnings,) finally: cov._warn = original_warn @@ -459,8 +461,8 @@ class CoverageTest( class UsingModulesMixin(object): """A mixin for importing modules from tests/modules and tests/moremodules.""" - def setUp(self): - super(UsingModulesMixin, self).setUp() + def setup_test(self): + super(UsingModulesMixin, self).setup_test() # Parent class saves and restores sys.path, we can just modify it. sys.path.append(self.nice_file(TESTS_DIR, 'modules')) diff --git a/tests/mixins.py b/tests/mixins.py index 5abaa759..8fe0690b 100644 --- a/tests/mixins.py +++ b/tests/mixins.py @@ -8,30 +8,143 @@ Some of these are transitional while working toward pure-pytest style. """ import functools +import os +import os.path +import sys import types +import textwrap import unittest import pytest +from coverage import env from coverage.misc import StopEverything -class EnvironmentAwareMixin: - """ - Adapter from pytst monkeypatch fixture to our environment variable methods. - """ +class PytestBase(object): + """A base class to connect to pytest in a test class hierarchy.""" + @pytest.fixture(autouse=True) - def _monkeypatch(self, monkeypatch): - """Get the monkeypatch fixture for our methods to use.""" - self._envpatcher = monkeypatch + def connect_to_pytest(self, request, monkeypatch): + """Captures pytest facilities for use by other test helpers.""" + # pylint: disable=attribute-defined-outside-init + self._pytest_request = request + self._monkeypatch = monkeypatch + self.setup_test() + + # Can't call this setUp or setup because pytest sniffs out unittest and + # nosetest special names, and does things with them. + # https://github.com/pytest-dev/pytest/issues/8424 + def setup_test(self): + """Per-test initialization. Override this as you wish.""" + pass + + def addCleanup(self, fn, *args): + """Like unittest's addCleanup: code to call when the test is done.""" + self._pytest_request.addfinalizer(lambda: fn(*args)) def set_environ(self, name, value): """Set an environment variable `name` to be `value`.""" - self._envpatcher.setenv(name, value) + self._monkeypatch.setenv(name, value) def del_environ(self, name): """Delete an environment variable, unless we set it.""" - self._envpatcher.delenv(name) + self._monkeypatch.delenv(name) + + +class TempDirMixin(object): + """Provides temp dir and data file helpers for tests.""" + + # Our own setting: most of these tests run in their own temp directory. + # Set this to False in your subclass if you don't want a temp directory + # created. + run_in_temp_dir = True + + # Set this if you aren't creating any files with make_file, but still want + # the temp directory. This will stop the test behavior checker from + # complaining. + no_files_in_temp_dir = False + + @pytest.fixture(autouse=True) + def _temp_dir(self, tmpdir_factory): + """Create a temp dir for the tests, if they want it.""" + old_dir = None + if self.run_in_temp_dir: + tmpdir = tmpdir_factory.mktemp("") + self.temp_dir = str(tmpdir) + old_dir = os.getcwd() + tmpdir.chdir() + + # 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()) + + try: + yield None + finally: + if old_dir is not None: + os.chdir(old_dir) + + @pytest.fixture(autouse=True) + def _save_sys_path(self): + """Restore sys.path at the end of each test.""" + old_syspath = sys.path[:] + try: + yield + finally: + sys.path = old_syspath + + @pytest.fixture(autouse=True) + def _module_saving(self): + """Remove modules we imported during the test.""" + old_modules = list(sys.modules) + try: + yield + finally: + added_modules = [m for m in sys.modules if m not in old_modules] + for m in added_modules: + del sys.modules[m] + + def make_file(self, filename, text="", bytes=b"", newline=None): + """Create a file for testing. + + `filename` is the relative path to the file, including directories if + desired, which will be created if need be. + + `text` is the content to create in the file, a native string (bytes in + Python 2, unicode in Python 3), or `bytes` are the bytes to write. + + If `newline` is provided, it is a string that will be used as the line + endings in the created file, otherwise the line endings are as provided + in `text`. + + Returns `filename`. + + """ + # pylint: disable=redefined-builtin # bytes + if bytes: + data = bytes + else: + text = textwrap.dedent(text) + if newline: + text = text.replace("\n", newline) + if env.PY3: + data = text.encode('utf8') + else: + data = text + + # 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(data) + + return filename def convert_skip_exceptions(method): diff --git a/tests/test_api.py b/tests/test_api.py index 391a52e0..a865c24c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -884,7 +884,7 @@ class SourceIncludeOmitTest(IncludeOmitTestsMixin, CoverageTest): assert lines['p1c'] == 0 def test_source_package_as_dir(self): - self.chdir(self.nice_file(TESTS_DIR, 'modules')) + os.chdir(self.nice_file(TESTS_DIR, 'modules')) assert os.path.isdir("pkg1") lines = self.coverage_usepkgs(source=["pkg1"]) self.filenames_in(lines, "p1a p1b") @@ -910,7 +910,7 @@ class SourceIncludeOmitTest(IncludeOmitTestsMixin, CoverageTest): # the search for unexecuted files, and given a score of 0%. # The omit arg is by path, so need to be in the modules directory. - self.chdir(self.nice_file(TESTS_DIR, 'modules')) + os.chdir(self.nice_file(TESTS_DIR, 'modules')) lines = self.coverage_usepkgs(source=["pkg1"], omit=["pkg1/p1b.py"]) self.filenames_in(lines, "p1a") self.filenames_not_in(lines, "p1b") @@ -925,7 +925,7 @@ class SourceIncludeOmitTest(IncludeOmitTestsMixin, CoverageTest): def test_ambiguous_source_package_as_dir(self): # pkg1 is a directory and a pkg, since we cd into tests/modules/ambiguous - self.chdir(self.nice_file(TESTS_DIR, 'modules', "ambiguous")) + os.chdir(self.nice_file(TESTS_DIR, 'modules', "ambiguous")) # pkg1 defaults to directory because tests/modules/ambiguous/pkg1 exists lines = self.coverage_usepkgs(source=["pkg1"]) self.filenames_in(lines, "ambiguous") @@ -933,7 +933,7 @@ class SourceIncludeOmitTest(IncludeOmitTestsMixin, CoverageTest): def test_ambiguous_source_package_as_package(self): # pkg1 is a directory and a pkg, since we cd into tests/modules/ambiguous - self.chdir(self.nice_file(TESTS_DIR, 'modules', "ambiguous")) + os.chdir(self.nice_file(TESTS_DIR, 'modules', "ambiguous")) lines = self.coverage_usepkgs(source_pkgs=["pkg1"]) self.filenames_in(lines, "p1a p1b") self.filenames_not_in(lines, "p2a p2b othera otherb osa osb ambiguous") diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index d5141028..5c5ea0ef 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -900,8 +900,8 @@ class CmdMainTest(CoverageTest): raise AssertionError("Bad CoverageScriptStub: %r" % (argv,)) return 0 - def setUp(self): - super(CmdMainTest, self).setUp() + def setup_test(self): + super(CmdMainTest, self).setup_test() old_CoverageScript = coverage.cmdline.CoverageScript coverage.cmdline.CoverageScript = self.CoverageScriptStub self.addCleanup(setattr, coverage.cmdline, 'CoverageScript', old_CoverageScript) diff --git a/tests/test_files.py b/tests/test_files.py index 6040b889..512e4294 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -41,7 +41,7 @@ class FilesTest(CoverageTest): a1 = self.abs_path("sub/proj1/file1.py") a2 = self.abs_path("sub/proj2/file2.py") d = os.path.normpath("sub/proj1") - self.chdir(d) + os.chdir(d) files.set_relative_directory() assert files.relative_filename(a1) == "file1.py" assert files.relative_filename(a2) == a2 @@ -60,7 +60,7 @@ class FilesTest(CoverageTest): def test_canonical_filename_ensure_cache_hit(self): self.make_file("sub/proj1/file1.py") d = actual_path(self.abs_path("sub/proj1")) - self.chdir(d) + os.chdir(d) files.set_relative_directory() canonical_path = files.canonical_filename('sub/proj1/file1.py') assert canonical_path == self.abs_path('file1.py') @@ -140,8 +140,8 @@ def test_fnmatches_to_regex(patterns, case_insensitive, partial, matches, nomatc class MatcherTest(CoverageTest): """Tests of file matchers.""" - def setUp(self): - super(MatcherTest, self).setUp() + def setup_test(self): + super(MatcherTest, self).setup_test() files.set_relative_directory() def assertMatches(self, matcher, filepath, matches): diff --git a/tests/test_html.py b/tests/test_html.py index 79d14d26..1015b7d6 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -113,8 +113,8 @@ class FileWriteTracker(object): class HtmlDeltaTest(HtmlTestHelpers, CoverageTest): """Tests of the HTML delta speed-ups.""" - def setUp(self): - super(HtmlDeltaTest, self).setUp() + def setup_test(self): + super(HtmlDeltaTest, self).setup_test() # At least one of our tests monkey-patches the version of coverage.py, # so grab it here to restore it later. @@ -555,8 +555,8 @@ class HtmlTest(HtmlTestHelpers, CoverageTest): class HtmlStaticFileTest(CoverageTest): """Tests of the static file copying for the HTML report.""" - def setUp(self): - super(HtmlStaticFileTest, self).setUp() + def setup_test(self): + super(HtmlStaticFileTest, self).setup_test() original_path = list(coverage.html.STATIC_PATH) self.addCleanup(setattr, coverage.html, 'STATIC_PATH', original_path) diff --git a/tests/test_numbits.py b/tests/test_numbits.py index fc27a093..946f8fcb 100644 --- a/tests/test_numbits.py +++ b/tests/test_numbits.py @@ -99,8 +99,8 @@ class NumbitsSqliteFunctionTest(CoverageTest): run_in_temp_dir = False - def setUp(self): - super(NumbitsSqliteFunctionTest, self).setUp() + def setup_test(self): + super(NumbitsSqliteFunctionTest, self).setup_test() conn = sqlite3.connect(":memory:") register_sqlite_functions(conn) self.cursor = conn.cursor() diff --git a/tests/test_oddball.py b/tests/test_oddball.py index b7307887..da0531f1 100644 --- a/tests/test_oddball.py +++ b/tests/test_oddball.py @@ -186,7 +186,7 @@ class MemoryLeakTest(CoverageTest): fails += 1 # pragma: only failure if fails > 8: - self.fail("RAM grew by %d" % (ram_growth)) # pragma: only failure + pytest.fail("RAM grew by %d" % (ram_growth)) # pragma: only failure class MemoryFumblingTest(CoverageTest): diff --git a/tests/test_process.py b/tests/test_process.py index 548f3dd7..0743e14e 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -1225,8 +1225,8 @@ class PydocTest(CoverageTest): class FailUnderTest(CoverageTest): """Tests of the --fail-under switch.""" - def setUp(self): - super(FailUnderTest, self).setUp() + def setup_test(self): + super(FailUnderTest, self).setup_test() self.make_file("forty_two_plus.py", """\ # I have 42.857% (3/7) coverage! a = 1 @@ -1448,8 +1448,8 @@ def persistent_remove(path): class ProcessCoverageMixin(object): """Set up a .pth file to coverage-measure all sub-processes.""" - def setUp(self): - super(ProcessCoverageMixin, self).setUp() + def setup_test(self): + super(ProcessCoverageMixin, self).setup_test() # Create the .pth file. assert PTH_DIR @@ -1457,17 +1457,16 @@ class ProcessCoverageMixin(object): pth_path = os.path.join(PTH_DIR, "subcover_{}.pth".format(WORKER)) with open(pth_path, "w") as pth: pth.write(pth_contents) - self.pth_path = pth_path - self.addCleanup(persistent_remove, self.pth_path) + self.addCleanup(persistent_remove, pth_path) @pytest.mark.skipif(env.METACOV, reason="Can't test sub-process pth file during metacoverage") class ProcessStartupTest(ProcessCoverageMixin, CoverageTest): """Test that we can measure coverage in sub-processes.""" - def setUp(self): - super(ProcessStartupTest, self).setUp() + def setup_test(self): + super(ProcessStartupTest, self).setup_test() # Main will run sub.py self.make_file("main.py", """\ diff --git a/tests/test_setup.py b/tests/test_setup.py index 30456191..b2ccd67c 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -15,8 +15,8 @@ class SetupPyTest(CoverageTest): run_in_temp_dir = False - def setUp(self): - super(SetupPyTest, self).setUp() + def setup_test(self): + super(SetupPyTest, self).setup_test() # Force the most restrictive interpretation. self.set_environ('LC_ALL', 'C') |