summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2016-06-01 17:36:01 -0400
committerNed Batchelder <ned@nedbatchelder.com>2016-06-01 17:36:01 -0400
commitc904c13921cff61aea7832df693a03b4fe546bf3 (patch)
tree7bd3e2ca30bc5fc5f2fd104baff6e697db0f1e96
parent177b9ba4f35a0cb3b77f440039a349104fac045a (diff)
downloadpython-coveragepy-c904c13921cff61aea7832df693a03b4fe546bf3.tar.gz
Remove the test helpers into their own repo
-rw-r--r--coverage/test_helpers.py394
-rw-r--r--requirements/dev.pip1
-rw-r--r--tests/coveragetest.py9
-rw-r--r--tests/goldtest.py2
-rw-r--r--tests/test_farm.py2
-rw-r--r--tests/test_testing.py111
-rw-r--r--tox.ini1
7 files changed, 9 insertions, 511 deletions
diff --git a/coverage/test_helpers.py b/coverage/test_helpers.py
deleted file mode 100644
index 9649ed5..0000000
--- a/coverage/test_helpers.py
+++ /dev/null
@@ -1,394 +0,0 @@
-# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
-# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
-
-"""Mixin classes to help make good tests."""
-
-import atexit
-import collections
-import contextlib
-import os
-import random
-import shutil
-import sys
-import tempfile
-import textwrap
-
-from coverage.backunittest import TestCase
-from coverage.backward import StringIO, to_bytes
-
-
-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)
-
- def flush(self):
- """Flush the data on all the files."""
- for f in self._files:
- f.flush()
-
- def getvalue(self):
- """StringIO file-likes have .getvalue()"""
- return self._files[0].getvalue()
-
- 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)
-
-
-@contextlib.contextmanager
-def change_dir(new_dir):
- """Change directory, and then change back.
-
- Use as a context manager, it will give you the new directory, and later
- restore the old one.
-
- """
- old_dir = os.getcwd()
- os.chdir(new_dir)
- try:
- yield os.getcwd()
- finally:
- os.chdir(old_dir)
-
-
-@contextlib.contextmanager
-def saved_sys_path():
- """Save sys.path, and restore it later."""
- old_syspath = sys.path[:]
- try:
- yield
- finally:
- sys.path = old_syspath
-
-
-def setup_with_context_manager(testcase, cm):
- """Use a contextmanager to setUp a test case.
-
- If you have a context manager you like::
-
- with ctxmgr(a, b, c) as v:
- # do something with v
-
- and you want to have that effect for a test case, call this function from
- your setUp, and it will start the context manager for your test, and end it
- when the test is done::
-
- def setUp(self):
- self.v = setup_with_context_manager(self, ctxmgr(a, b, c))
-
- def test_foo(self):
- # do something with self.v
-
- """
- val = cm.__enter__()
- testcase.addCleanup(cm.__exit__, None, None, None)
- return val
-
-
-class ModuleAwareMixin(TestCase):
- """A test case mixin that isolates changes to sys.modules."""
-
- def setUp(self):
- super(ModuleAwareMixin, self).setUp()
-
- # Record sys.modules here so we can restore it in cleanup_modules.
- self.old_modules = list(sys.modules)
- self.addCleanup(self.cleanup_modules)
-
- def cleanup_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]
-
-
-class SysPathAwareMixin(TestCase):
- """A test case mixin that isolates changes to sys.path."""
-
- def setUp(self):
- super(SysPathAwareMixin, self).setUp()
- setup_with_context_manager(self, saved_sys_path())
-
-
-class EnvironmentAwareMixin(TestCase):
- """A test case mixin that isolates changes to the environment."""
-
- def setUp(self):
- super(EnvironmentAwareMixin, self).setUp()
-
- # Record environment variables that we changed with set_environ.
- self.environ_undos = {}
-
- self.addCleanup(self.cleanup_environ)
-
- 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 `cleanup_environ` 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 cleanup_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
-
-
-class StdStreamCapturingMixin(TestCase):
- """A test case mixin that captures stdout and stderr."""
-
- def setUp(self):
- super(StdStreamCapturingMixin, self).setUp()
-
- # 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.
- old_stdout = sys.stdout
- self.captured_stdout = StringIO()
- sys.stdout = Tee(sys.stdout, self.captured_stdout)
-
- old_stderr = sys.stderr
- self.captured_stderr = StringIO()
- sys.stderr = self.captured_stderr
-
- self.addCleanup(self.cleanup_std_streams, old_stdout, old_stderr)
-
- def cleanup_std_streams(self, old_stdout, old_stderr):
- """Restore stdout and stderr."""
- sys.stdout = old_stdout
- sys.stderr = old_stderr
-
- 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()
-
-
-class DelayedAssertionMixin(TestCase):
- """A test case mixin that provides a `delayed_assertions` context manager.
-
- Use it like this::
-
- with self.delayed_assertions():
- self.assertEqual(x, y)
- self.assertEqual(z, w)
-
- All of the assertions will run. The failures will be displayed at the end
- of the with-statement.
-
- NOTE: this only works with some assertions. These are known to work:
-
- - `assertEqual(str, str)`
-
- - `assertMultilineEqual(str, str)`
-
- """
- def __init__(self, *args, **kwargs):
- super(DelayedAssertionMixin, self).__init__(*args, **kwargs)
- # This mixin only works with assert methods that call `self.fail`. In
- # Python 2.7, `assertEqual` didn't, but we can do what Python 3 does,
- # and use `assertMultiLineEqual` for comparing strings.
- self.addTypeEqualityFunc(str, 'assertMultiLineEqual')
- self._delayed_assertions = None
-
- @contextlib.contextmanager
- def delayed_assertions(self):
- """The context manager: assert that we didn't collect any assertions."""
- self._delayed_assertions = []
- old_fail = self.fail
- self.fail = self._delayed_fail
- try:
- yield
- finally:
- self.fail = old_fail
- if self._delayed_assertions:
- if len(self._delayed_assertions) == 1:
- self.fail(self._delayed_assertions[0])
- else:
- self.fail(
- "{0} failed assertions:\n{1}".format(
- len(self._delayed_assertions),
- "\n".join(self._delayed_assertions),
- )
- )
-
- def _delayed_fail(self, msg=None):
- """The stand-in for TestCase.fail during delayed_assertions."""
- self._delayed_assertions.append(msg)
-
-
-class TempDirMixin(SysPathAwareMixin, ModuleAwareMixin, TestCase):
- """A test case mixin that creates a temp directory and files in it.
-
- Includes SysPathAwareMixin and ModuleAwareMixin, because making and using
- temp directories like this will also need that kind of isolation.
-
- """
-
- # 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
-
- def setUp(self):
- super(TempDirMixin, self).setUp()
-
- if self.run_in_temp_dir:
- # Create a temporary directory.
- self.temp_dir = self.make_temp_dir("test_cover")
- self.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())
-
- class_behavior = self.class_behavior()
- class_behavior.tests += 1
- class_behavior.temp_dir = self.run_in_temp_dir
- class_behavior.no_files_ok = self.no_files_in_temp_dir
-
- self.addCleanup(self.check_behavior)
-
- def make_temp_dir(self, slug="test_cover"):
- """Make a temp directory that is cleaned up when the test is done."""
- name = "%s_%08d" % (slug, random.randint(0, 99999999))
- temp_dir = os.path.join(tempfile.gettempdir(), name)
- os.makedirs(temp_dir)
- self.addCleanup(shutil.rmtree, temp_dir)
- return temp_dir
-
- def chdir(self, new_dir):
- """Change directory, and change back when the test is done."""
- old_dir = os.getcwd()
- os.chdir(new_dir)
- self.addCleanup(os.chdir, old_dir)
-
- def check_behavior(self):
- """Check that we did the right things."""
-
- class_behavior = self.class_behavior()
- if class_behavior.test_method_made_any_files:
- class_behavior.tests_making_files += 1
-
- def make_file(self, filename, text="", 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).
-
- 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`.
-
- """
- # 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
-
- # 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 directory 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."""
- def __init__(self):
- self.tests = 0
- self.skipped = 0
- self.temp_dir = True
- self.no_files_ok = False
- 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():
- bad = ""
- if behavior.tests <= behavior.skipped:
- bad = ""
- elif behavior.temp_dir and behavior.tests_making_files == 0:
- if not behavior.no_files_ok:
- bad = "Inefficient"
- elif not behavior.temp_dir and behavior.tests_making_files > 0:
- bad = "Unsafe"
-
- 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(TempDirMixin.report_on_class_behavior)
diff --git a/requirements/dev.pip b/requirements/dev.pip
index dbbcfc5..eab9acb 100644
--- a/requirements/dev.pip
+++ b/requirements/dev.pip
@@ -14,6 +14,7 @@ mock==2.0.0
PyContracts==1.7.9
pyenchant==1.6.6
pylint==1.4.5
+git+https://github.com/nedbat/unittest-mixins@master#egg=unittest_mixins==0.0
# for kitting.
requests==2.10.0
diff --git a/tests/coveragetest.py b/tests/coveragetest.py
index 7625ce6..b38e0a5 100644
--- a/tests/coveragetest.py
+++ b/tests/coveragetest.py
@@ -13,15 +13,16 @@ import shlex
import shutil
import sys
+from unittest_mixins import (
+ EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin,
+ DelayedAssertionMixin,
+)
+
import coverage
from coverage.backunittest import TestCase
from coverage.backward import StringIO, import_local_file, string_class, shlex_quote
from coverage.cmdline import CoverageScript
from coverage.debug import _TEST_NAME_FILE, DebugControl
-from coverage.test_helpers import (
- EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin,
- DelayedAssertionMixin,
-)
from nose.plugins.skip import SkipTest
diff --git a/tests/goldtest.py b/tests/goldtest.py
index 39f2bfd..8ebbd59 100644
--- a/tests/goldtest.py
+++ b/tests/goldtest.py
@@ -8,7 +8,7 @@ import sys
from tests.coveragetest import CoverageTest
-from coverage.test_helpers import change_dir # pylint: disable=unused-import
+from unittest_mixins import change_dir # pylint: disable=unused-import
from tests.test_farm import clean
# Import helpers, eventually test_farm.py will go away.
from tests.test_farm import ( # pylint: disable=unused-import
diff --git a/tests/test_farm.py b/tests/test_farm.py
index 0db0af8..ae9e915 100644
--- a/tests/test_farm.py
+++ b/tests/test_farm.py
@@ -15,7 +15,7 @@ import unittest
from nose.plugins.skip import SkipTest
-from coverage.test_helpers import ModuleAwareMixin, SysPathAwareMixin, change_dir, saved_sys_path
+from unittest_mixins import ModuleAwareMixin, SysPathAwareMixin, change_dir, saved_sys_path
from tests.helpers import run_command
from tests.backtest import execfile # pylint: disable=redefined-builtin
diff --git a/tests/test_testing.py b/tests/test_testing.py
index 1dafdd0..c5858bf 100644
--- a/tests/test_testing.py
+++ b/tests/test_testing.py
@@ -6,15 +6,11 @@
import datetime
import os
-import re
import sys
-import textwrap
import coverage
from coverage.backunittest import TestCase
-from coverage.backward import to_bytes
from coverage.files import actual_path
-from coverage.test_helpers import EnvironmentAwareMixin, TempDirMixin, DelayedAssertionMixin
from tests.coveragetest import CoverageTest
@@ -31,113 +27,6 @@ class TestingTest(TestCase):
self.assertCountEqual(set([1,2,3]), set([4,5,6]))
-class TempDirMixinTest(TempDirMixin, TestCase):
- """Test the methods in TempDirMixin."""
-
- def file_text(self, fname):
- """Return the text read from a file."""
- 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(self.file_text("fooey.boo"), "Hello there")
- # A file in a sub-directory
- self.make_file("sub/another.txt", "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(self.file_text("sub/second.txt"), "Second")
- # A deeper directory
- self.make_file("sub/deeper/evenmore/third.txt")
- self.assertEqual(self.file_text("sub/deeper/evenmore/third.txt"), "")
-
- def test_make_file_newline(self):
- self.make_file("unix.txt", "Hello\n")
- self.assertEqual(self.file_text("unix.txt"), "Hello\n")
- self.make_file("dos.txt", "Hello\n", newline="\r\n")
- self.assertEqual(self.file_text("dos.txt"), "Hello\r\n")
- self.make_file("mac.txt", "Hello\n", newline="\r")
- self.assertEqual(self.file_text("mac.txt"), "Hello\r")
-
- def test_make_file_non_ascii(self):
- self.make_file("unicode.txt", "tabblo: «ταБЬℓσ»")
- with open("unicode.txt", "rb") as f:
- text = f.read()
- self.assertEqual(text, to_bytes("tabblo: «ταБЬℓσ»"))
-
-
-class EnvironmentAwareMixinTest(EnvironmentAwareMixin, TestCase):
- """Tests of test_helpers.EnvironmentAwareMixin."""
-
- def test_setting_and_cleaning_env_vars(self):
- # The before state.
- # Not sure what environment variables are available in all of our
- # different testing environments, so try a bunch.
- for envvar in ["HOME", "HOMEDIR", "USER", "SYSTEMDRIVE", "TEMP"]: # pragma: part covered
- if envvar in os.environ:
- original_text = os.environ[envvar]
- new_text = "Some Strange Text"
- break
- # pylint: disable=undefined-loop-variable
- self.assertNotEqual(original_text, new_text)
- self.assertNotIn("XYZZY_PLUGH", os.environ)
-
- # Change the environment.
- self.set_environ(envvar, new_text)
- self.set_environ("XYZZY_PLUGH", "Vogon")
-
- self.assertEqual(os.environ[envvar], new_text)
- self.assertEqual(os.environ["XYZZY_PLUGH"], "Vogon")
-
- # Do the clean ups early.
- self.doCleanups()
-
- # The environment should be restored.
- self.assertEqual(os.environ[envvar], original_text)
- self.assertNotIn("XYZZY_PLUGH", os.environ)
-
-
-class DelayedAssertionMixinTest(DelayedAssertionMixin, TestCase):
- """Test the `delayed_assertions` method."""
-
- def test_delayed_assertions(self):
- # Two assertions can be shown at once:
- msg = re.escape(textwrap.dedent("""\
- 2 failed assertions:
- 'x' != 'y'
- - x
- + y
-
- 'w' != 'z'
- - w
- + z
- """))
- with self.assertRaisesRegex(AssertionError, msg):
- with self.delayed_assertions():
- self.assertEqual("x", "y")
- self.assertEqual("w", "z")
-
- # It's also OK if only one fails:
- msg = re.escape(textwrap.dedent("""\
- 'w' != 'z'
- - w
- + z
- """))
- with self.assertRaisesRegex(AssertionError, msg):
- with self.delayed_assertions():
- self.assertEqual("x", "x")
- self.assertEqual("w", "z")
-
- # If an error happens, it gets reported immediately, no special
- # handling:
- with self.assertRaises(ZeroDivisionError):
- with self.delayed_assertions():
- self.assertEqual("x", "y")
- self.assertEqual("w", 1/0)
-
-
class CoverageTestTest(CoverageTest):
"""Test the methods in `CoverageTest`."""
diff --git a/tox.ini b/tox.ini
index 9d6c499..8020146 100644
--- a/tox.ini
+++ b/tox.ini
@@ -14,6 +14,7 @@ deps =
nose==1.3.7
mock==2.0.0
PyContracts==1.7.9
+ git+https://github.com/nedbat/unittest-mixins@master#egg=unittest_mixins==0.0
py26: unittest2==1.1.0
py{27,33,34,35,36}: gevent==1.1.1
py{26,27,33,34,35,36}: eventlet==0.19.0