diff options
Diffstat (limited to 'tests/mixins.py')
-rw-r--r-- | tests/mixins.py | 131 |
1 files changed, 122 insertions, 9 deletions
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): |