--- a/DebugClients/Python/coverage/test_helpers.py Fri Sep 02 19:08:02 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,393 +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() - - 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) - -# -# eflag: FileType = Python2