diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2018-11-18 11:18:39 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2018-11-18 12:23:05 -0500 |
commit | 2fb113e60c2733e640f92d737278431a1f670052 (patch) | |
tree | 3b197a57ddd85846b3791f55facb58373a144721 | |
parent | 701f4ed56639325dcb9fa0aecf06df303ee6a761 (diff) | |
download | python-coveragepy-git-2fb113e60c2733e640f92d737278431a1f670052.tar.gz |
Remove test_farm.py
-rw-r--r-- | MANIFEST.in | 1 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | tests/goldtest.py | 158 | ||||
-rw-r--r-- | tests/test_farm.py | 383 |
4 files changed, 154 insertions, 390 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index ebc8116e..195980cb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -36,7 +36,6 @@ recursive-include requirements *.pip recursive-include tests *.py *.tok recursive-include tests/farm */gold*/*.* */gold*/*/*.* -recursive-include tests/farm/*/src * *.* recursive-include tests js/*.* qunit/*.* prune tests/eggsrc/build @@ -10,8 +10,6 @@ clean: -pip uninstall -y coverage -rm -f *.pyd */*.pyd -rm -f *.so */*.so - -PYTHONPATH=. python tests/test_farm.py clean - -rm -rf tests/farm/*/out -rm -rf build coverage.egg-info dist htmlcov -rm -rf __pycache__ */__pycache__ */*/__pycache__ */*/*/__pycache__ */*/*/*/__pycache__ */*/*/*/*/__pycache__ -rm -f *.pyc */*.pyc */*/*.pyc */*/*/*.pyc */*/*/*/*.pyc */*/*/*/*/*.pyc diff --git a/tests/goldtest.py b/tests/goldtest.py index 48842f0c..975db615 100644 --- a/tests/goldtest.py +++ b/tests/goldtest.py @@ -3,16 +3,166 @@ """A test base class for tests based on gold file comparison.""" +import difflib +import filecmp +import fnmatch import os +import os.path +import re +import sys from unittest_mixins import change_dir # pylint: disable=unused-import +from coverage import env + from tests.coveragetest import TESTS_DIR -# Import helpers, eventually test_farm.py will go away. -from tests.test_farm import ( # pylint: disable=unused-import - compare, contains, doesnt_contain, contains_any, -) + def gold_path(path): """Get a path to a gold file for comparison.""" return os.path.join(TESTS_DIR, "farm", path) + + +# "rU" was deprecated in 3.4 +READ_MODE = "rU" if env.PYVERSION < (3, 4) else "r" + + +def versioned_directory(d): + """Find a subdirectory of d specific to the Python version. + For example, on Python 3.6.4 rc 1, it returns the first of these + directories that exists:: + d/3.6.4.candidate.1 + d/3.6.4.candidate + d/3.6.4 + d/3.6 + d/3 + d + Returns: a string, the path to an existing directory. + """ + ver_parts = list(map(str, sys.version_info)) + for nparts in range(len(ver_parts), -1, -1): + version = ".".join(ver_parts[:nparts]) + subdir = os.path.join(d, version) + if os.path.exists(subdir): + return subdir + raise Exception("Directory missing: {}".format(d)) # pragma: only failure + + +def compare( + expected_dir, actual_dir, file_pattern=None, + actual_extra=False, scrubs=None, + ): + """Compare files matching `file_pattern` in `expected_dir` and `actual_dir`. + + A version-specific subdirectory of `expected_dir` will be used if + it exists. + + `actual_extra` true means `actual_dir` can have extra files in it + without triggering an assertion. + + `scrubs` is a list of pairs: regexes to find and replace to scrub the + files of unimportant differences. + + An assertion will be raised if the directories fail one of their + matches. + + """ + expected_dir = versioned_directory(expected_dir) + + dc = filecmp.dircmp(expected_dir, actual_dir) + diff_files = fnmatch_list(dc.diff_files, file_pattern) + expected_only = fnmatch_list(dc.left_only, file_pattern) + actual_only = fnmatch_list(dc.right_only, file_pattern) + + # filecmp only compares in binary mode, but we want text mode. So + # look through the list of different files, and compare them + # ourselves. + text_diff = [] + for f in diff_files: + expected_file = os.path.join(expected_dir, f) + actual_file = os.path.join(actual_dir, f) + with open(expected_file, READ_MODE) as fobj: + expected = fobj.read() + with open(actual_file, READ_MODE) as fobj: + actual = fobj.read() + if scrubs: + expected = scrub(expected, scrubs) + actual = scrub(actual, scrubs) + if expected != actual: # pragma: only failure + text_diff.append('%s != %s' % (expected_file, actual_file)) + expected = expected.splitlines() + actual = actual.splitlines() + print(":::: diff {!r} and {!r}".format(expected_file, actual_file)) + print("\n".join(difflib.Differ().compare(expected, actual))) + print(":::: end diff {!r} and {!r}".format(expected_file, actual_file)) + assert not text_diff, "Files differ: %s" % '\n'.join(text_diff) + + assert not expected_only, "Files in %s only: %s" % (expected_dir, expected_only) + if not actual_extra: + assert not actual_only, "Files in %s only: %s" % (actual_dir, actual_only) + + +def contains(filename, *strlist): + """Check that the file contains all of a list of strings. + + An assert will be raised if one of the arguments in `strlist` is + missing in `filename`. + + """ + with open(filename, "r") as fobj: + text = fobj.read() + for s in strlist: + assert s in text, "Missing content in %s: %r" % (filename, s) + + +def contains_any(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, ( # pragma: only failure + "Missing content in %s: %r [1 of %d]" % (filename, strlist[0], len(strlist),) + ) + + +def doesnt_contain(filename, *strlist): + """Check that the file contains none of a list of strings. + + An assert will be raised if any of the strings in `strlist` appears in + `filename`. + + """ + with open(filename, "r") as fobj: + text = fobj.read() + for s in strlist: + assert s not in text, "Forbidden content in %s: %r" % (filename, s) + + +# Helpers + +def fnmatch_list(files, file_pattern): + """Filter the list of `files` to only those that match `file_pattern`. + If `file_pattern` is None, then return the entire list of files. + Returns a list of the filtered files. + """ + if file_pattern: + files = [f for f in files if fnmatch.fnmatch(f, file_pattern)] + return files + + +def scrub(strdata, scrubs): + """Scrub uninteresting data from the payload in `strdata`. + `scrubs` is a list of (find, replace) pairs of regexes that are used on + `strdata`. A string is returned. + """ + for rgx_find, rgx_replace in scrubs: + strdata = re.sub(rgx_find, rgx_replace, strdata) + return strdata diff --git a/tests/test_farm.py b/tests/test_farm.py deleted file mode 100644 index 95654ef4..00000000 --- a/tests/test_farm.py +++ /dev/null @@ -1,383 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt - -"""Run tests in the farm sub-directory. Designed for pytest.""" - -import difflib -import filecmp -import fnmatch -import glob -import os -import re -import shutil -import sys - -import pytest - -from unittest_mixins import ModuleAwareMixin, SysPathAwareMixin, change_dir -from tests.helpers import run_command -from tests.backtest import execfile # pylint: disable=redefined-builtin - -from coverage import env -from coverage.backunittest import unittest - - -# Look for files that become tests. -TEST_FILES = glob.glob("tests/farm/*/*.py") - - -@pytest.mark.parametrize("filename", TEST_FILES) -def test_farm(filename): - if env.JYTHON: - # All of the farm tests use reporting, so skip them all. - skip("Farm tests don't run on Jython") - FarmTestCase(filename).run_fully() - - -# "rU" was deprecated in 3.4 -READ_MODE = "rU" if env.PYVERSION < (3, 4) else "r" - - -class FarmTestCase(ModuleAwareMixin, SysPathAwareMixin, unittest.TestCase): - """A test case from the farm tree. - - Tests are short Python script files, often called run.py: - - copy("src", "out") - run(''' - coverage run white.py - coverage annotate white.py - ''', rundir="out") - compare("gold", "out", "*,cover") - clean("out") - - Verbs (copy, run, compare, clean) are methods in this class. FarmTestCase - has options to allow various uses of the test cases (normal execution, - cleaning-only, or run and leave the results for debugging). - - This class is a unittest.TestCase so that we can use behavior-modifying - mixins, but it's only useful as a test function. Yes, this is confusing. - - """ - - # We don't want test runners finding this and instantiating it themselves. - __test__ = False - - def __init__(self, runpy, clean_only=False, dont_clean=False): - """Create a test case from a run.py file. - - `clean_only` means that only the clean() action is executed. - `dont_clean` means that the clean() action is not executed. - - """ - super(FarmTestCase, self).__init__() - - self.description = runpy - self.dir, self.runpy = os.path.split(runpy) - self.clean_only = clean_only - self.dont_clean = dont_clean - self.ok = True - - def setUp(self): - """Test set up, run by the test runner before __call__.""" - super(FarmTestCase, self).setUp() - # Modules should be importable from the current directory. - sys.path.insert(0, '') - - def tearDown(self): - """Test tear down, run by the test runner after __call__.""" - # Make sure the test is cleaned up, unless we never want to, or if the - # test failed. - if not self.dont_clean and self.ok: # pragma: part covered - self.clean_only = True - self() - - super(FarmTestCase, self).tearDown() - - # This object will be run via the __call__ method, and test runners - # don't do cleanups in that case. Do them now. - self.doCleanups() - - def runTest(self): # pragma: not covered - """Here to make unittest.TestCase happy, but will never be invoked.""" - raise Exception("runTest isn't used in this class!") - - def __call__(self): # pylint: disable=arguments-differ - """Execute the test from the runpy file.""" - # Prepare a dictionary of globals for the run.py files to use. - fns = """ - copy run clean skip - compare contains contains_any doesnt_contain - """.split() - if self.clean_only: - glo = dict((fn, noop) for fn in fns) - glo['clean'] = clean - else: - glo = dict((fn, globals()[fn]) for fn in fns) - if self.dont_clean: # pragma: debugging - glo['clean'] = noop - - with change_dir(self.dir): - try: - execfile(self.runpy, glo) - except Exception: - self.ok = False - raise - - def run_fully(self): - """Run as a full test case, with setUp and tearDown.""" - self.setUp() - try: - self() - finally: - self.tearDown() - - -# Functions usable inside farm run.py files - -def noop(*args_unused, **kwargs_unused): - """A no-op function to stub out run, copy, etc, when only cleaning.""" - pass - - -def copy(src, dst): - """Copy a directory.""" - if os.path.exists(dst): - pytest.fail('%s already exists.' % os.path.join(os.getcwd(), dst)) # pragma: only failure - shutil.copytree(src, dst) - - -def run(cmds, rundir="src", outfile=None): - """Run a list of commands. - - `cmds` is a string, commands separated by newlines. - `rundir` is the directory in which to run the commands. - `outfile` is a file name to redirect stdout to. - - """ - with change_dir(rundir): - if outfile: - fout = open(outfile, "a+") - try: - for cmd in cmds.split("\n"): - cmd = cmd.strip() - if not cmd: - continue - retcode, output = run_command(cmd) - print(output.rstrip()) - if outfile: - fout.write(output) - if retcode: - raise Exception("command exited abnormally") # pragma: only failure - finally: - if outfile: - fout.close() - - -def versioned_directory(d): - """Find a subdirectory of d specific to the Python version. - - For example, on Python 3.6.4 rc 1, it returns the first of these - directories that exists:: - - d/3.6.4.candidate.1 - d/3.6.4.candidate - d/3.6.4 - d/3.6 - d/3 - d - - Returns: a string, the path to an existing directory. - - """ - ver_parts = list(map(str, sys.version_info)) - for nparts in range(len(ver_parts), -1, -1): - version = ".".join(ver_parts[:nparts]) - subdir = os.path.join(d, version) - if os.path.exists(subdir): - return subdir - raise Exception("Directory missing: {}".format(d)) # pragma: only failure - - -def compare( - expected_dir, actual_dir, file_pattern=None, - actual_extra=False, scrubs=None, - ): - """Compare files matching `file_pattern` in `expected_dir` and `actual_dir`. - - A version-specific subdirectory of `expected_dir` will be used if - it exists. - - `actual_extra` true means `actual_dir` can have extra files in it - without triggering an assertion. - - `scrubs` is a list of pairs: regexes to find and replace to scrub the - files of unimportant differences. - - An assertion will be raised if the directories fail one of their - matches. - - """ - expected_dir = versioned_directory(expected_dir) - - dc = filecmp.dircmp(expected_dir, actual_dir) - diff_files = fnmatch_list(dc.diff_files, file_pattern) - expected_only = fnmatch_list(dc.left_only, file_pattern) - actual_only = fnmatch_list(dc.right_only, file_pattern) - - # filecmp only compares in binary mode, but we want text mode. So - # look through the list of different files, and compare them - # ourselves. - text_diff = [] - for f in diff_files: - expected_file = os.path.join(expected_dir, f) - actual_file = os.path.join(actual_dir, f) - with open(expected_file, READ_MODE) as fobj: - expected = fobj.read() - with open(actual_file, READ_MODE) as fobj: - actual = fobj.read() - if scrubs: - expected = scrub(expected, scrubs) - actual = scrub(actual, scrubs) - if expected != actual: # pragma: only failure - text_diff.append('%s != %s' % (expected_file, actual_file)) - expected = expected.splitlines() - actual = actual.splitlines() - print(":::: diff {!r} and {!r}".format(expected_file, actual_file)) - print("\n".join(difflib.Differ().compare(expected, actual))) - print(":::: end diff {!r} and {!r}".format(expected_file, actual_file)) - assert not text_diff, "Files differ: %s" % '\n'.join(text_diff) - - assert not expected_only, "Files in %s only: %s" % (expected_dir, expected_only) - if not actual_extra: - assert not actual_only, "Files in %s only: %s" % (actual_dir, actual_only) - - -def contains(filename, *strlist): - """Check that the file contains all of a list of strings. - - An assert will be raised if one of the arguments in `strlist` is - missing in `filename`. - - """ - with open(filename, "r") as fobj: - text = fobj.read() - for s in strlist: - assert s in text, "Missing content in %s: %r" % (filename, s) - - -def contains_any(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, ( # pragma: only failure - "Missing content in %s: %r [1 of %d]" % (filename, strlist[0], len(strlist),) - ) - - -def doesnt_contain(filename, *strlist): - """Check that the file contains none of a list of strings. - - An assert will be raised if any of the strings in `strlist` appears in - `filename`. - - """ - with open(filename, "r") as fobj: - text = fobj.read() - for s in strlist: - assert s not in text, "Forbidden content in %s: %r" % (filename, s) - - -def clean(cleandir): - """Clean `cleandir` by removing it and all its children completely.""" - # rmtree gives mysterious failures on Win7, so retry a "few" times. - # I've seen it take over 100 tries, so, 1000! This is probably the - # most unpleasant hack I've written in a long time... - tries = 1000 - while tries: # pragma: part covered - if os.path.exists(cleandir): - try: - shutil.rmtree(cleandir) - except OSError: # pragma: cant happen - if tries == 1: - raise - else: - tries -= 1 - continue - break - - -def skip(msg=None): - """Skip the current test.""" - raise unittest.SkipTest(msg) - - -# Helpers - -def fnmatch_list(files, file_pattern): - """Filter the list of `files` to only those that match `file_pattern`. - - If `file_pattern` is None, then return the entire list of files. - - Returns a list of the filtered files. - - """ - if file_pattern: - files = [f for f in files if fnmatch.fnmatch(f, file_pattern)] - return files - - -def scrub(strdata, scrubs): - """Scrub uninteresting data from the payload in `strdata`. - - `scrubs` is a list of (find, replace) pairs of regexes that are used on - `strdata`. A string is returned. - - """ - for rgx_find, rgx_replace in scrubs: - strdata = re.sub(rgx_find, rgx_replace, strdata) - return strdata - - -def main(): # pragma: debugging - """Command-line access to farm tests. - - Commands: - - run testcase ... - Run specific test case(s) - out testcase ... - Run test cases, but don't clean up, leaving output. - clean - Clean all the output for all tests. - - """ - try: - op = sys.argv[1] - except IndexError: - op = 'help' - - if op == 'run': - # Run the test for real. - for filename in sys.argv[2:]: - FarmTestCase(filename).run_fully() - elif op == 'out': - # Run the test, but don't clean up, so we can examine the output. - for filename in sys.argv[2:]: - FarmTestCase(filename, dont_clean=True).run_fully() - elif op == 'clean': - # Run all the tests, but just clean. - for filename in TEST_FILES: - FarmTestCase(filename, clean_only=True).run_fully() - else: - print(main.__doc__) - -# So that we can run just one farm run.py at a time. -if __name__ == '__main__': # pragma: debugging - main() |