diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/backunittest.py | 41 | ||||
-rw-r--r-- | tests/coveragetest.py | 256 | ||||
-rw-r--r-- | tests/farm/html/run_unicode.py | 14 | ||||
-rw-r--r-- | tests/modules/plugins/__init__.py | 0 | ||||
-rw-r--r-- | tests/modules/plugins/a_plugin.py | 6 | ||||
-rw-r--r-- | tests/modules/plugins/another.py | 6 | ||||
-rw-r--r-- | tests/test_backward.py | 4 | ||||
-rw-r--r-- | tests/test_cmdline.py | 2 | ||||
-rw-r--r-- | tests/test_config.py | 24 | ||||
-rw-r--r-- | tests/test_coroutine.py | 121 | ||||
-rw-r--r-- | tests/test_execfile.py | 2 | ||||
-rw-r--r-- | tests/test_farm.py | 19 | ||||
-rw-r--r-- | tests/test_files.py | 8 | ||||
-rw-r--r-- | tests/test_oddball.py | 66 | ||||
-rw-r--r-- | tests/test_plugins.py | 217 | ||||
-rw-r--r-- | tests/test_process.py | 3 | ||||
-rw-r--r-- | tests/test_testing.py | 32 |
17 files changed, 450 insertions, 371 deletions
diff --git a/tests/backunittest.py b/tests/backunittest.py deleted file mode 100644 index 6498397f..00000000 --- a/tests/backunittest.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Implementations of unittest features from the future.""" - -# Use unittest2 if it's available, otherwise unittest. This gives us -# backported features for 2.6. -try: - import unittest2 as unittest # pylint: disable=F0401 -except ImportError: - import unittest - - -def unittest_has(method): - """Does `unitttest.TestCase` have `method` defined?""" - return hasattr(unittest.TestCase, method) - - -class TestCase(unittest.TestCase): - """Just like unittest.TestCase, but with assert methods added. - - Designed to be compatible with 3.1 unittest. Methods are only defined if - `unittest` doesn't have them. - - """ - # pylint: disable=missing-docstring - - if not unittest_has('assertCountEqual'): - if unittest_has('assertSameElements'): - def assertCountEqual(self, *args, **kwargs): - # pylint: disable=no-member - return self.assertSameElements(*args, **kwargs) - else: - def assertCountEqual(self, s1, s2): - """Assert these have the same elements, regardless of order.""" - self.assertEqual(set(s1), set(s2)) - - if not unittest_has('assertRaisesRegex'): - def assertRaisesRegex(self, *args, **kwargs): - return self.assertRaisesRegexp(*args, **kwargs) - - if not unittest_has('assertRegex'): - def assertRegex(self, *args, **kwargs): - return self.assertRegexpMatches(*args, **kwargs) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 1eedad39..4053059f 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -1,48 +1,39 @@ """Base test case class for coverage testing.""" -import glob, os, random, re, shlex, shutil, sys, tempfile, textwrap -import atexit, collections +import glob, os, random, re, shlex, shutil, sys import coverage -from coverage.backward import StringIO, to_bytes, import_local_file +from coverage.backunittest import TestCase +from coverage.backward import StringIO, import_local_file from coverage.backward import importlib # pylint: disable=unused-import from coverage.control import _TEST_NAME_FILE -from tests.backtest import run_command -from tests.backunittest import TestCase - -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) +from coverage.test_helpers import ( + ModuleAwareMixin, SysPathAwareMixin, EnvironmentAwareMixin, + StdStreamCapturingMixin, TempDirMixin, +) - 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) +from tests.backtest import run_command # Status returns for the command line. OK, ERR = 0, 1 -class CoverageTest(TestCase): +class CoverageTest( + ModuleAwareMixin, + SysPathAwareMixin, + EnvironmentAwareMixin, + StdStreamCapturingMixin, + TempDirMixin, + 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 + # Tell newer unittest implementations to print long helpful messages. + longMessage = True + def setUp(self): super(CoverageTest, self).setUp() @@ -51,153 +42,6 @@ class CoverageTest(TestCase): 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) - - # 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()) - - # Keep a counter to make every call to check_coverage unique. - self.n = 0 - - # Record environment variables that we changed with set_environ. - self.environ_undos = {} - - # 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_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() - - # 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 - - 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. - - 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] - - 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 `tearDown` 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 original_environ(self, name, if_missing=None): - """The environment variable `name` from when the test started.""" - if name in self.environ_undos: - ret = self.environ_undos[name] - else: - ret = os.environ.get(name) - if ret is None: - ret = if_missing - return ret - - def undo_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 - - 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() - - 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. 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 - def clean_local_file_imports(self): """Clean up the results of calls to `import_local_file`. @@ -206,7 +50,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. @@ -244,13 +88,7 @@ class CoverageTest(TestCase): def get_module_name(self): """Return the module name to use for this test run.""" - # We append self.n because otherwise two calls in one test will use the - # same filename and whether the test works or not depends on the - # timestamps in the .pyc file, so it becomes random whether the second - # call will use the compiled version of the first call's code or not! - modname = 'coverage_test_' + self.noise + str(self.n) - self.n += 1 - return modname + return 'coverage_test_' + str(random.random())[2:] # Map chars to numbers for arcz_to_arcs _arcz_map = {'.': -1} @@ -507,55 +345,3 @@ class CoverageTest(TestCase): def last_line_squeezed(self, report): """Return the last line of `report` with the spaces squeezed down.""" return self.squeezed_lines(report)[-1] - - # 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 dir 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 in CoverageTest.""" - def __init__(self): - self.tests = 0 - self.temp_dir = True - 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(): - if behavior.temp_dir and behavior.tests_making_files == 0: - bad = "Inefficient" - elif not behavior.temp_dir and behavior.tests_making_files > 0: - bad = "Unsafe" - else: - bad = "" - - 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(CoverageTest.report_on_class_behavior) diff --git a/tests/farm/html/run_unicode.py b/tests/farm/html/run_unicode.py index cef26ee5..c8cb6c50 100644 --- a/tests/farm/html/run_unicode.py +++ b/tests/farm/html/run_unicode.py @@ -1,5 +1,3 @@ -import sys - def html_it(): """Run coverage and make an HTML report for unicode.py.""" import coverage @@ -18,13 +16,9 @@ contains("html_unicode/unicode.html", "<span class='str'>"ʎd˙ǝbɐɹǝʌoɔ"</span>", ) -if sys.maxunicode == 65535: - contains("html_unicode/unicode.html", - "<span class='str'>"db40,dd00: x��"</span>", - ) -else: - contains("html_unicode/unicode.html", - "<span class='str'>"db40,dd00: x󠄀"</span>", - ) +contains_any("html_unicode/unicode.html", + "<span class='str'>"db40,dd00: x��"</span>", + "<span class='str'>"db40,dd00: x󠄀"</span>", + ) clean("html_unicode") diff --git a/tests/modules/plugins/__init__.py b/tests/modules/plugins/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/modules/plugins/__init__.py diff --git a/tests/modules/plugins/a_plugin.py b/tests/modules/plugins/a_plugin.py new file mode 100644 index 00000000..2ff84dac --- /dev/null +++ b/tests/modules/plugins/a_plugin.py @@ -0,0 +1,6 @@ +"""A plugin for tests to reference.""" + +from coverage import CoveragePlugin + +class Plugin(CoveragePlugin): + pass diff --git a/tests/modules/plugins/another.py b/tests/modules/plugins/another.py new file mode 100644 index 00000000..2ff84dac --- /dev/null +++ b/tests/modules/plugins/another.py @@ -0,0 +1,6 @@ +"""A plugin for tests to reference.""" + +from coverage import CoveragePlugin + +class Plugin(CoveragePlugin): + pass diff --git a/tests/test_backward.py b/tests/test_backward.py index 2c688edd..09803ba7 100644 --- a/tests/test_backward.py +++ b/tests/test_backward.py @@ -1,14 +1,12 @@ """Tests that our version shims in backward.py are working.""" +from coverage.backunittest import TestCase from coverage.backward import iitems, binary_bytes, byte_to_int, bytes_to_ints -from tests.backunittest import TestCase class BackwardTest(TestCase): """Tests of things from backward.py.""" - run_in_temp_dir = False - def test_iitems(self): d = {'a': 1, 'b': 2, 'c': 3} items = [('a', 1), ('b', 2), ('c', 3)] diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 038e9214..08f7937a 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -72,7 +72,7 @@ class CmdLineTest(CoverageTest): code = re.sub(r"(?m)^\.", "m2.", code) m2 = self.model_object() code_obj = compile(code, "<code>", "exec") - eval(code_obj, globals(), { 'm2': m2 }) + eval(code_obj, globals(), { 'm2': m2 }) # pylint: disable=eval-used # Many of our functions take a lot of arguments, and cmdline.py # calls them with many. But most of them are just the defaults, which diff --git a/tests/test_config.py b/tests/test_config.py index 7409f4aa..bf84423d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- """Test the config file handling for coverage.py""" +import sys, os + import coverage from coverage.misc import CoverageException @@ -125,6 +127,12 @@ class ConfigTest(CoverageTest): class ConfigFileTest(CoverageTest): """Tests of the config file settings in particular.""" + def setUp(self): + super(ConfigFileTest, self).setUp() + # Parent class saves and restores sys.path, we can just modify it. + # Add modules to the path so we can import plugins. + sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules')) + # This sample file tries to use lots of variation of syntax... # The {section} placeholder lets us nest these settings in another file. LOTSA_SETTINGS = """\ @@ -136,6 +144,9 @@ class ConfigFileTest(CoverageTest): cover_pylib = TRUE parallel = on include = a/ , b/ + plugins = + plugins.a_plugin + plugins.another [{section}report] ; these settings affect reporting. @@ -174,6 +185,10 @@ class ConfigFileTest(CoverageTest): other = other, /home/ned/other, c:\\Ned\\etc + [{section}plugins.a_plugin] + hello = world + ; comments still work. + names = Jane/John/Jenny """ # Just some sample setup.cfg text from the docs. @@ -212,6 +227,9 @@ class ConfigFileTest(CoverageTest): self.assertEqual(cov.config.partial_always_list, ["if 0:", "while True:"] ) + self.assertEqual(cov.config.plugins, + ["plugins.a_plugin", "plugins.another"] + ) self.assertTrue(cov.config.show_missing) self.assertEqual(cov.config.html_dir, r"c:\tricky\dir.somewhere") self.assertEqual(cov.config.extra_css, "something/extra.css") @@ -224,6 +242,12 @@ class ConfigFileTest(CoverageTest): 'other': ['other', '/home/ned/other', 'c:\\Ned\\etc'] }) + self.assertEqual(cov.config.get_plugin_options("plugins.a_plugin"), { + 'hello': 'world', + 'names': 'Jane/John/Jenny', + }) + self.assertEqual(cov.config.get_plugin_options("plugins.another"), {}) + def test_config_file_settings(self): self.make_file(".coveragerc", self.LOTSA_SETTINGS.format(section="")) cov = coverage.coverage() diff --git a/tests/test_coroutine.py b/tests/test_coroutine.py index fe6c8326..4abdd6f6 100644 --- a/tests/test_coroutine.py +++ b/tests/test_coroutine.py @@ -1,8 +1,7 @@ """Tests for coroutining.""" -import os.path, sys +import os, os.path, sys, threading -from nose.plugins.skip import SkipTest import coverage from tests.coveragetest import CoverageTest @@ -20,6 +19,14 @@ try: except ImportError: gevent = None +try: + import greenlet # pylint: disable=import-error +except ImportError: + greenlet = None + +# Are we running with the C tracer or not? +C_TRACER = os.getenv('COVERAGE_TEST_TRACER', 'c') == 'c' + def line_count(s): """How many non-blank non-comment lines are in `s`?""" @@ -96,48 +103,106 @@ class CoroutineTest(CoverageTest): import gevent.queue as queue """ + COMMON - def try_some_code(self, code, args): - """Run some coroutine testing code and see that it was all covered.""" + # Uncomplicated code that doesn't use any of the coroutining stuff, to test + # the simple case under each of the regimes. + SIMPLE = """\ + total = 0 + for i in range({LIMIT}): + total += i + print(total) + """.format(LIMIT=LIMIT) - self.make_file("try_it.py", code) + def try_some_code(self, code, coroutine, the_module, expected_out=None): + """Run some coroutine testing code and see that it was all covered. - out = self.run_command("coverage run --timid %s try_it.py" % args) - expected_out = "%d\n" % (sum(range(self.LIMIT))) - self.assertEqual(out, expected_out) + `code` is the Python code to execute. `coroutine` is the name of the + coroutine regime to test it under. `the_module` is the imported module + that must be available for this to work at all. `expected_out` is the + text we expect the code to produce. - # Read the coverage file and see that try_it.py has all its lines - # executed. - data = coverage.CoverageData() - data.read_file(".coverage") + """ - # If the test fails, it's helpful to see this info: - fname = os.path.abspath("try_it.py") - linenos = data.executed_lines(fname).keys() - print("{0}: {1}".format(len(linenos), linenos)) - print_simple_annotation(code, linenos) + self.make_file("try_it.py", code) - lines = line_count(code) - self.assertEqual(data.summary()['try_it.py'], lines) + cmd = "coverage run --coroutine=%s try_it.py" % coroutine + out = self.run_command(cmd) + + if not the_module: + # We don't even have the underlying module installed, we expect + # coverage to alert us to this fact. + expected_out = ( + "Couldn't trace with coroutine=%s, " + "the module isn't installed.\n" % coroutine + ) + self.assertEqual(out, expected_out) + elif C_TRACER or coroutine == "thread": + # We can fully measure the code if we are using the C tracer, which + # can support all the coroutining, or if we are using threads. + if expected_out is None: + expected_out = "%d\n" % (sum(range(self.LIMIT))) + self.assertEqual(out, expected_out) + + # Read the coverage file and see that try_it.py has all its lines + # executed. + data = coverage.CoverageData() + data.read_file(".coverage") + + # If the test fails, it's helpful to see this info: + fname = os.path.abspath("try_it.py") + linenos = data.executed_lines(fname).keys() + print("{0}: {1}".format(len(linenos), linenos)) + print_simple_annotation(code, linenos) + + lines = line_count(code) + self.assertEqual(data.summary()['try_it.py'], lines) + else: + expected_out = ( + "Can't support coroutine=%s with PyTracer, " + "only threads are supported\n" % coroutine + ) + self.assertEqual(out, expected_out) def test_threads(self): - self.try_some_code(self.THREAD, "") + self.try_some_code(self.THREAD, "thread", threading) + + def test_threads_simple_code(self): + self.try_some_code(self.SIMPLE, "thread", threading) def test_eventlet(self): - if eventlet is None: - raise SkipTest("No eventlet available") + self.try_some_code(self.EVENTLET, "eventlet", eventlet) - self.try_some_code(self.EVENTLET, "--coroutine=eventlet") + def test_eventlet_simple_code(self): + self.try_some_code(self.SIMPLE, "eventlet", eventlet) def test_gevent(self): - raise SkipTest("Still not sure why gevent isn't working...") + self.try_some_code(self.GEVENT, "gevent", gevent) + + def test_gevent_simple_code(self): + self.try_some_code(self.SIMPLE, "gevent", gevent) + + def test_greenlet(self): + GREENLET = """\ + from greenlet import greenlet + + def test1(x, y): + z = gr2.switch(x+y) + print(z) + + def test2(u): + print(u) + gr1.switch(42) - if gevent is None: - raise SkipTest("No gevent available") + gr1 = greenlet(test1) + gr2 = greenlet(test2) + gr1.switch("hello", " world") + """ + self.try_some_code(GREENLET, "greenlet", greenlet, "hello world\n42\n") - self.try_some_code(self.GEVENT, "--coroutine=gevent") + def test_greenlet_simple_code(self): + self.try_some_code(self.SIMPLE, "greenlet", greenlet) def print_simple_annotation(code, linenos): """Print the lines in `code` with X for each line number in `linenos`.""" for lineno, line in enumerate(code.splitlines(), start=1): - print(" {0:s} {1}".format("X" if lineno in linenos else " ", line)) + print(" {0} {1}".format("X" if lineno in linenos else " ", line)) diff --git a/tests/test_execfile.py b/tests/test_execfile.py index 2427847e..69616e84 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -16,7 +16,7 @@ class RunFileTest(CoverageTest): def test_run_python_file(self): tryfile = os.path.join(here, "try_execfile.py") run_python_file(tryfile, [tryfile, "arg1", "arg2"]) - mod_globs = eval(self.stdout()) + mod_globs = eval(self.stdout()) # pylint: disable=eval-used # The file should think it is __main__ self.assertEqual(mod_globs['__name__'], "__main__") diff --git a/tests/test_farm.py b/tests/test_farm.py index b2ea3697..47f9b7b7 100644 --- a/tests/test_farm.py +++ b/tests/test_farm.py @@ -79,7 +79,8 @@ class FarmTestCase(object): # Prepare a dictionary of globals for the run.py files to use. fns = """ - copy run runfunc compare contains doesnt_contain clean skip + copy run runfunc clean skip + compare contains contains_any doesnt_contain """.split() if self.clean_only: glo = dict((fn, self.noop) for fn in fns) @@ -304,6 +305,22 @@ class FarmTestCase(object): for s in strlist: assert s in text, "Missing content in %s: %r" % (filename, s) + def contains_any(self, filename, *strlist): + """Check that the file contains at least one of a list of strings. + + An assert will be raised if none of the arguments in `strlist` is in + `filename`. + + """ + with open(filename, "r") as fobj: + text = fobj.read() + for s in strlist: + if s in text: + return + assert False, "Missing content in %s: %r [1 of %d]" % ( + filename, strlist[0], len(strlist), + ) + def doesnt_contain(self, filename, *strlist): """Check that the file contains none of a list of strings. diff --git a/tests/test_files.py b/tests/test_files.py index 070430ff..648c76a9 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -99,6 +99,14 @@ class MatcherTest(CoverageTest): self.assertMatches(fnm, "x123foo.txt", True) self.assertMatches(fnm, "x798bar.txt", False) + def test_fnmatch_windows_paths(self): + # We should be able to match Windows paths even if we are running on + # a non-Windows OS. + fnm = FnmatchMatcher(["*/foo.py"]) + self.assertMatches(fnm, r"dir\foo.py", True) + fnm = FnmatchMatcher([r"*\foo.py"]) + self.assertMatches(fnm, r"dir\foo.py", True) + class PathAliasesTest(CoverageTest): """Tests for coverage/files.py:PathAliases""" diff --git a/tests/test_oddball.py b/tests/test_oddball.py index 786ede94..47f492f6 100644 --- a/tests/test_oddball.py +++ b/tests/test_oddball.py @@ -116,9 +116,8 @@ class RecursionTest(CoverageTest): self.assertEqual(statements, [1,2,3,5,7,8,9,10,11]) self.assertEqual(missing, expected_missing) - # We can get a warning about the stackoverflow effect on the tracing - # function only if we have sys.gettrace - if pytrace and hasattr(sys, "gettrace"): + # Get a warning about the stackoverflow effect on the tracing function. + if pytrace: self.assertEqual(cov._warnings, ["Trace function changed, measurement is likely wrong: None"] ) @@ -368,35 +367,34 @@ class DoctestTest(CoverageTest): [1,11,12,14,16,17], "") -if hasattr(sys, 'gettrace'): - class GettraceTest(CoverageTest): - """Tests that we work properly with `sys.gettrace()`.""" - def test_round_trip(self): - self.check_coverage('''\ - import sys - def foo(n): - return 3*n - def bar(n): - return 5*n - a = foo(6) +class GettraceTest(CoverageTest): + """Tests that we work properly with `sys.gettrace()`.""" + def test_round_trip(self): + self.check_coverage('''\ + import sys + def foo(n): + return 3*n + def bar(n): + return 5*n + a = foo(6) + sys.settrace(sys.gettrace()) + a = bar(8) + ''', + [1,2,3,4,5,6,7,8], "") + + def test_multi_layers(self): + self.check_coverage('''\ + import sys + def level1(): + a = 3 + level2() + b = 5 + def level2(): + c = 7 sys.settrace(sys.gettrace()) - a = bar(8) - ''', - [1,2,3,4,5,6,7,8], "") - - def test_multi_layers(self): - self.check_coverage('''\ - import sys - def level1(): - a = 3 - level2() - b = 5 - def level2(): - c = 7 - sys.settrace(sys.gettrace()) - d = 9 - e = 10 - level1() - f = 12 - ''', - [1,2,3,4,5,6,7,8,9,10,11,12], "") + d = 9 + e = 10 + level1() + f = 12 + ''', + [1,2,3,4,5,6,7,8,9,10,11,12], "") diff --git a/tests/test_plugins.py b/tests/test_plugins.py new file mode 100644 index 00000000..9c5a037d --- /dev/null +++ b/tests/test_plugins.py @@ -0,0 +1,217 @@ +"""Tests for plugins.""" + +import os.path + +import coverage +from coverage.codeunit import CodeUnit +from coverage.parser import CodeParser +from coverage.plugin import Plugins, overrides + +from tests.coveragetest import CoverageTest + + +class FakeConfig(object): + """A fake config for use in tests.""" + + def __init__(self, plugin, options): + self.plugin = plugin + self.options = options + self.asked_for = [] + + def get_plugin_options(self, module): + """Just return the options for `module` if this is the right module.""" + self.asked_for.append(module) + if module == self.plugin: + return self.options + else: + return {} + + +class PluginUnitTest(CoverageTest): + """Test Plugins.load_plugins directly.""" + + def test_importing_and_configuring(self): + self.make_file("plugin1.py", """\ + from coverage import CoveragePlugin + + class Plugin(CoveragePlugin): + def __init__(self, options): + super(Plugin, self).__init__(options) + self.this_is = "me" + """) + + config = FakeConfig("plugin1", {'a':'hello'}) + plugins = list(Plugins.load_plugins(["plugin1"], config)) + + self.assertEqual(len(plugins), 1) + self.assertEqual(plugins[0].this_is, "me") + self.assertEqual(plugins[0].options, {'a':'hello'}) + self.assertEqual(config.asked_for, ['plugin1']) + + def test_importing_and_configuring_more_than_one(self): + self.make_file("plugin1.py", """\ + from coverage import CoveragePlugin + + class Plugin(CoveragePlugin): + def __init__(self, options): + super(Plugin, self).__init__(options) + self.this_is = "me" + """) + self.make_file("plugin2.py", """\ + from coverage import CoveragePlugin + + class Plugin(CoveragePlugin): + pass + """) + + config = FakeConfig("plugin1", {'a':'hello'}) + plugins = list(Plugins.load_plugins(["plugin1", "plugin2"], config)) + + self.assertEqual(len(plugins), 2) + self.assertEqual(plugins[0].this_is, "me") + self.assertEqual(plugins[0].options, {'a':'hello'}) + self.assertEqual(plugins[1].options, {}) + self.assertEqual(config.asked_for, ['plugin1', 'plugin2']) + + def test_cant_import(self): + with self.assertRaises(ImportError): + _ = Plugins.load_plugins(["plugin_not_there"], None) + + def test_ok_to_not_define_plugin(self): + self.make_file("plugin2.py", """\ + from coverage import CoveragePlugin + + Nothing = 0 + """) + plugins = list(Plugins.load_plugins(["plugin2"], None)) + self.assertEqual(plugins, []) + + +class PluginTest(CoverageTest): + """Test plugins through the Coverage class.""" + + def test_plugin_imported(self): + # Prove that a plugin will be imported. + self.make_file("my_plugin.py", """\ + with open("evidence.out", "w") as f: + f.write("we are here!") + """) + + self.assert_doesnt_exist("evidence.out") + _ = coverage.Coverage(plugins=["my_plugin"]) + + with open("evidence.out") as f: + self.assertEqual(f.read(), "we are here!") + + def test_missing_plugin_raises_import_error(self): + # Prove that a missing plugin will raise an ImportError. + with self.assertRaises(ImportError): + cov = coverage.Coverage(plugins=["does_not_exist_woijwoicweo"]) + cov.start() + + def test_bad_plugin_isnt_hidden(self): + # Prove that a plugin with an error in it will raise the error. + self.make_file("plugin_over_zero.py", """\ + 1/0 + """) + with self.assertRaises(ZeroDivisionError): + _ = coverage.Coverage(plugins=["plugin_over_zero"]) + + def test_importing_myself(self): + self.make_file("simple.py", """\ + import try_xyz + a = 1 + b = 2 + """) + self.make_file("try_xyz.py", """\ + c = 3 + d = 4 + """) + + cov = coverage.Coverage(plugins=["tests.test_plugins"]) + + # Import the python file, executing it. + self.start_import_stop(cov, "simple") + + _, statements, missing, _ = cov.analysis("simple.py") + self.assertEqual(statements, [1,2,3]) + self.assertEqual(missing, []) + _, statements, _, _ = cov.analysis("/src/try_ABC.zz") + self.assertEqual(statements, [105, 106, 107, 205, 206, 207]) + + +class Plugin(coverage.CoveragePlugin): + def trace_judge(self, disp): + if "xyz.py" in disp.original_filename: + disp.trace = True + disp.source_filename = os.path.join( + "/src", + os.path.basename( + disp.original_filename.replace("xyz.py", "ABC.zz") + ) + ) + + def line_number_range(self, frame): + lineno = frame.f_lineno + return lineno*100+5, lineno*100+7 + + def code_unit_class(self, filename): + return PluginCodeUnit + +class PluginCodeUnit(CodeUnit): + def get_parser(self, exclude=None): + return PluginParser() + +class PluginParser(CodeParser): + def parse_source(self): + return set([105, 106, 107, 205, 206, 207]), set([]) + + +class OverridesTest(CoverageTest): + """Test plugins.py:overrides.""" + + run_in_temp_dir = False + + def test_overrides(self): + class SomeBase(object): + """Base class, two base methods.""" + def method1(self): + pass + + def method2(self): + pass + + class Derived1(SomeBase): + """Simple single inheritance.""" + def method1(self): + pass + + self.assertTrue(overrides(Derived1(), "method1", SomeBase)) + self.assertFalse(overrides(Derived1(), "method2", SomeBase)) + + class FurtherDerived1(Derived1): + """Derive again from Derived1, inherit its method1.""" + pass + + self.assertTrue(overrides(FurtherDerived1(), "method1", SomeBase)) + self.assertFalse(overrides(FurtherDerived1(), "method2", SomeBase)) + + class FurtherDerived2(Derived1): + """Override the overridden method.""" + def method1(self): + pass + + self.assertTrue(overrides(FurtherDerived2(), "method1", SomeBase)) + self.assertFalse(overrides(FurtherDerived2(), "method2", SomeBase)) + + class Mixin(object): + """A mixin that overrides method1.""" + def method1(self): + pass + + class Derived2(Mixin, SomeBase): + """A class that gets the method from the mixin.""" + pass + + self.assertTrue(overrides(Derived2(), "method1", SomeBase)) + self.assertFalse(overrides(Derived2(), "method2", SomeBase)) diff --git a/tests/test_process.py b/tests/test_process.py index d8314982..3a0980dc 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -470,8 +470,7 @@ class ProcessTest(CoverageTest): self.assertIn("Hello\n", out) self.assertIn("Goodbye\n", out) - if hasattr(sys, "gettrace"): - self.assertIn("Trace function changed", out) + self.assertIn("Trace function changed", out) if sys.version_info >= (3, 0): # This only works on 3.x for now. # It only works with the C tracer, diff --git a/tests/test_testing.py b/tests/test_testing.py index 049a1982..4a19098f 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -2,16 +2,14 @@ """Tests that our test infrastructure is really working!""" import os, sys +from coverage.backunittest import TestCase from coverage.backward import to_bytes -from tests.backunittest import TestCase -from tests.coveragetest import CoverageTest +from tests.coveragetest import TempDirMixin, CoverageTest class TestingTest(TestCase): """Tests of helper methods on `backunittest.TestCase`.""" - run_in_temp_dir = False - def test_assert_count_equal(self): self.assertCountEqual(set(), set()) self.assertCountEqual(set([1,2,3]), set([3,1,2])) @@ -21,26 +19,27 @@ class TestingTest(TestCase): self.assertCountEqual(set([1,2,3]), set([4,5,6])) -class CoverageTestTest(CoverageTest): - """Test the methods in `CoverageTest`.""" +class TempDirMixinTest(TempDirMixin, TestCase): + """Test the methods in TempDirMixin.""" def file_text(self, fname): """Return the text read from a file.""" - return open(fname, "rb").read().decode('ascii') + 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(open("fooey.boo").read(), "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(open("sub/another.txt").read(), "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(open("sub/second.txt").read(), "Second") + self.assertEqual(self.file_text("sub/second.txt"), "Second") # A deeper directory self.make_file("sub/deeper/evenmore/third.txt") - self.assertEqual(open("sub/deeper/evenmore/third.txt").read(), "") + self.assertEqual(self.file_text("sub/deeper/evenmore/third.txt"), "") def test_make_file_newline(self): self.make_file("unix.txt", "Hello\n") @@ -52,10 +51,13 @@ class CoverageTestTest(CoverageTest): def test_make_file_non_ascii(self): self.make_file("unicode.txt", "tabblo: «ταБЬℓσ»") - self.assertEqual( - open("unicode.txt", "rb").read(), - to_bytes("tabblo: «ταБЬℓσ»") - ) + with open("unicode.txt", "rb") as f: + text = f.read() + self.assertEqual(text, to_bytes("tabblo: «ταБЬℓσ»")) + + +class CoverageTestTest(CoverageTest): + """Test the methods in `CoverageTest`.""" def test_file_exists(self): self.make_file("whoville.txt", "We are here!") |