diff options
-rw-r--r-- | pylintrc | 2 | ||||
-rw-r--r-- | tests/coveragetest.py | 196 |
2 files changed, 117 insertions, 81 deletions
@@ -245,7 +245,7 @@ max-branches=50 max-statements=150 # Maximum number of parents for a class (see R0901). -max-parents=7 +max-parents=12 # Maximum number of attributes for a class (see R0902). max-attributes=40 diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 1eedad39..19a3a604 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -34,101 +34,50 @@ class Tee(object): # Status returns for the command line. OK, ERR = 0, 1 -class CoverageTest(TestCase): - """A base class for Coverage test cases.""" - - # Our own setting: most CoverageTests run in their own temp directory. - run_in_temp_dir = True - - # Standard unittest setting: show me diffs even if they are very long. - maxDiff = None +class ModuleAwareMixin(TestCase): + """A test case mixin that isolates changes to sys.modules.""" def setUp(self): - super(CoverageTest, self).setUp() + super(ModuleAwareMixin, self).setUp() - if _TEST_NAME_FILE: - f = open(_TEST_NAME_FILE, "w") - f.write("%s_%s" % (self.__class__.__name__, self._testMethodName)) - f.close() - - # Tell newer unittest implementations to print long helpful messages. - self.longMessage = True - - # tearDown will restore the original sys.path - self.old_syspath = sys.path[:] - - if self.run_in_temp_dir: - # Create a temporary directory. - self.noise = str(random.random())[2:] - self.temp_root = os.path.join(tempfile.gettempdir(), 'test_cover') - self.temp_dir = os.path.join(self.temp_root, self.noise) - os.makedirs(self.temp_dir) - self.old_dir = os.getcwd() - os.chdir(self.temp_dir) + # Record sys.modules here so we can restore it in tearDown. + self.old_modules = dict(sys.modules) + self.addCleanup(self.cleanup_modules) - # 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()) + def cleanup_modules(self): + """Remove any new modules imported during the test run. - # Keep a counter to make every call to check_coverage unique. - self.n = 0 + This lets us import the same source files for more than one test. - # Record environment variables that we changed with set_environ. - self.environ_undos = {} + """ + for m in [m for m in sys.modules if m not in self.old_modules]: + del sys.modules[m] - # 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 - # Record sys.modules here so we can restore it in tearDown. - self.old_modules = dict(sys.modules) +class SysPathAwareMixin(TestCase): + """A test case mixin that isolates changes to sys.path.""" - class_behavior = self.class_behavior() - class_behavior.tests += 1 - class_behavior.test_method_made_any_files = False - class_behavior.temp_dir = self.run_in_temp_dir + def setUp(self): + super(SysPathAwareMixin, self).setUp() - def tearDown(self): - super(CoverageTest, self).tearDown() + self.old_syspath = sys.path[:] + self.addCleanup(self.cleanup_syspath) - # Restore the original sys.path. + def cleanup_syspath(self): + """Restore the original sys.path.""" sys.path = self.old_syspath - if self.run_in_temp_dir: - # Get rid of the temporary directory. - os.chdir(self.old_dir) - shutil.rmtree(self.temp_root) - - # Restore the environment. - self.undo_environ() - # Restore stdout and stderr - sys.stdout = self.old_stdout - sys.stderr = self.old_stderr +class EnvironmentAwareMixin(TestCase): + """A test case mixin that isolates changes to the environment.""" - self.clean_modules() - - class_behavior = self.class_behavior() - if class_behavior.test_method_made_any_files: - class_behavior.tests_making_files += 1 - - def clean_modules(self): - """Remove any new modules imported during the test run. + def setUp(self): + super(EnvironmentAwareMixin, self).setUp() - This lets us import the same source files for more than one test. + # Record environment variables that we changed with set_environ. + self.environ_undos = {} - """ - for m in [m for m in sys.modules if m not in self.old_modules]: - del sys.modules[m] + self.addCleanup(self.cleanup_environ) def set_environ(self, name, value): """Set an environment variable `name` to be `value`. @@ -151,7 +100,7 @@ class CoverageTest(TestCase): ret = if_missing return ret - def undo_environ(self): + def cleanup_environ(self): """Undo all the changes made by `set_environ`.""" for name, value in self.environ_undos.items(): if value is None: @@ -159,6 +108,31 @@ class CoverageTest(TestCase): 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. + 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 + + self.addCleanup(self.cleanup_std_streams) + + def cleanup_std_streams(self): + """Restore stdout and stderr.""" + sys.stdout = self.old_stdout + sys.stderr = self.old_stderr + def stdout(self): """Return the data written to stdout during the test.""" return self.captured_stdout.getvalue() @@ -167,6 +141,68 @@ class CoverageTest(TestCase): """Return the data written to stderr during the test.""" return self.captured_stderr.getvalue() + +class CoverageTest( + ModuleAwareMixin, + SysPathAwareMixin, + EnvironmentAwareMixin, + StdStreamCapturingMixin, + TestCase +): + """A base class for Coverage test cases.""" + + # Our own setting: most CoverageTests run in their own temp directory. + run_in_temp_dir = True + + # Standard unittest setting: show me diffs even if they are very long. + maxDiff = None + + def setUp(self): + super(CoverageTest, self).setUp() + + if _TEST_NAME_FILE: + f = open(_TEST_NAME_FILE, "w") + f.write("%s_%s" % (self.__class__.__name__, self._testMethodName)) + f.close() + + # Tell newer unittest implementations to print long helpful messages. + self.longMessage = True + + # Keep a counter to make every call to check_coverage unique. + self.n = 0 + + if self.run_in_temp_dir: + # Create a temporary directory. + self.noise = str(random.random())[2:] + self.temp_root = os.path.join(tempfile.gettempdir(), 'test_cover') + self.temp_dir = os.path.join(self.temp_root, self.noise) + os.makedirs(self.temp_dir) + self.old_dir = os.getcwd() + os.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.test_method_made_any_files = False + class_behavior.temp_dir = self.run_in_temp_dir + + def tearDown(self): + super(CoverageTest, self).tearDown() + + if self.run_in_temp_dir: + # Get rid of the temporary directory. + os.chdir(self.old_dir) + shutil.rmtree(self.temp_root) + + 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. @@ -206,7 +242,7 @@ class CoverageTest(TestCase): """ # So that we can re-import files, clean them out first. - self.clean_modules() + self.cleanup_modules() # Also have to clean out the .pyc file, since the timestamp # resolution is only one second, a changed file might not be # picked up. |