123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- # coding: utf-8
- # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
- # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
- """Helper for building, testing, and linting coverage.py.
- To get portability, all these operations are written in Python here instead
- of in shell scripts, batch files, or Makefiles.
- """
- import contextlib
- import fnmatch
- import glob
- import inspect
- import os
- import platform
- import sys
- import textwrap
- import warnings
- import zipfile
- # We want to see all warnings while we are running tests. But we also need to
- # disable warnings for some of the more complex setting up of tests.
- warnings.simplefilter("default")
- @contextlib.contextmanager
- def ignore_warnings():
- """Context manager to ignore warning within the with statement."""
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- yield
- # Functions named do_* are executable from the command line: do_blah is run
- # by "python igor.py blah".
- def do_show_env():
- """Show the environment variables."""
- print("Environment:")
- for env in sorted(os.environ):
- print(" %s = %r" % (env, os.environ[env]))
- def do_remove_extension():
- """Remove the compiled C extension, no matter what its name."""
- so_patterns = """
- tracer.so
- tracer.*.so
- tracer.pyd
- tracer.*.pyd
- """.split()
- for pattern in so_patterns:
- pattern = os.path.join("coverage", pattern)
- for filename in glob.glob(pattern):
- try:
- os.remove(filename)
- except OSError:
- pass
- def label_for_tracer(tracer):
- """Get the label for these tests."""
- if tracer == "py":
- label = "with Python tracer"
- else:
- label = "with C tracer"
- return label
- def should_skip(tracer):
- """Is there a reason to skip these tests?"""
- if tracer == "py":
- skipper = os.environ.get("COVERAGE_NO_PYTRACER")
- else:
- skipper = (
- os.environ.get("COVERAGE_NO_EXTENSION") or
- os.environ.get("COVERAGE_NO_CTRACER")
- )
- if skipper:
- msg = "Skipping tests " + label_for_tracer(tracer)
- if len(skipper) > 1:
- msg += ": " + skipper
- else:
- msg = ""
- return msg
- def run_tests(tracer, *nose_args):
- """The actual running of tests."""
- with ignore_warnings():
- import nose.core
- if 'COVERAGE_TESTING' not in os.environ:
- os.environ['COVERAGE_TESTING'] = "True"
- print_banner(label_for_tracer(tracer))
- nose_args = ["nosetests"] + list(nose_args)
- nose.core.main(argv=nose_args)
- def run_tests_with_coverage(tracer, *nose_args):
- """Run tests, but with coverage."""
- # Need to define this early enough that the first import of env.py sees it.
- os.environ['COVERAGE_TESTING'] = "True"
- os.environ['COVERAGE_PROCESS_START'] = os.path.abspath('metacov.ini')
- os.environ['COVERAGE_HOME'] = os.getcwd()
- # Create the .pth file that will let us measure coverage in sub-processes.
- # The .pth file seems to have to be alphabetically after easy-install.pth
- # or the sys.path entries aren't created right?
- import nose
- pth_dir = os.path.dirname(os.path.dirname(nose.__file__))
- pth_path = os.path.join(pth_dir, "zzz_metacov.pth")
- with open(pth_path, "w") as pth_file:
- pth_file.write("import coverage; coverage.process_startup()\n")
- # Make names for the data files that keep all the test runs distinct.
- impl = platform.python_implementation().lower()
- version = "%s%s" % sys.version_info[:2]
- if '__pypy__' in sys.builtin_module_names:
- version += "_%s%s" % sys.pypy_version_info[:2]
- suffix = "%s%s_%s_%s" % (impl, version, tracer, platform.platform())
- os.environ['COVERAGE_METAFILE'] = os.path.abspath(".metacov."+suffix)
- import coverage
- cov = coverage.Coverage(config_file="metacov.ini", data_suffix=False)
- # Cheap trick: the coverage.py code itself is excluded from measurement,
- # but if we clobber the cover_prefix in the coverage object, we can defeat
- # the self-detection.
- cov.cover_prefix = "Please measure coverage.py!"
- cov._warn_unimported_source = False
- cov.start()
- try:
- # Re-import coverage to get it coverage tested! I don't understand all
- # the mechanics here, but if I don't carry over the imported modules
- # (in covmods), then things go haywire (os == None, eventually).
- covmods = {}
- covdir = os.path.split(coverage.__file__)[0]
- # We have to make a list since we'll be deleting in the loop.
- modules = list(sys.modules.items())
- for name, mod in modules:
- if name.startswith('coverage'):
- if getattr(mod, '__file__', "??").startswith(covdir):
- covmods[name] = mod
- del sys.modules[name]
- import coverage # pylint: disable=reimported
- sys.modules.update(covmods)
- # Run nosetests, with the arguments from our command line.
- try:
- run_tests(tracer, *nose_args)
- except SystemExit:
- # nose3 seems to raise SystemExit, not sure why?
- pass
- finally:
- cov.stop()
- os.remove(pth_path)
- cov.combine()
- cov.save()
- def do_combine_html():
- """Combine data from a meta-coverage run, and make the HTML and XML reports."""
- import coverage
- os.environ['COVERAGE_HOME'] = os.getcwd()
- os.environ['COVERAGE_METAFILE'] = os.path.abspath(".metacov")
- cov = coverage.Coverage(config_file="metacov.ini")
- cov.load()
- cov.combine()
- cov.save()
- cov.html_report()
- cov.xml_report()
- def do_test_with_tracer(tracer, *noseargs):
- """Run nosetests with a particular tracer."""
- # If we should skip these tests, skip them.
- skip_msg = should_skip(tracer)
- if skip_msg:
- print(skip_msg)
- return
- os.environ["COVERAGE_TEST_TRACER"] = tracer
- if os.environ.get("COVERAGE_COVERAGE", ""):
- return run_tests_with_coverage(tracer, *noseargs)
- else:
- return run_tests(tracer, *noseargs)
- def do_zip_mods():
- """Build the zipmods.zip file."""
- zf = zipfile.ZipFile("tests/zipmods.zip", "w")
- # Take one file from disk.
- zf.write("tests/covmodzip1.py", "covmodzip1.py")
- # The others will be various encodings.
- source = textwrap.dedent(u"""\
- # coding: {encoding}
- text = u"{text}"
- ords = {ords}
- assert [ord(c) for c in text] == ords
- print(u"All OK with {encoding}")
- """)
- # These encodings should match the list in tests/test_python.py
- details = [
- (u'utf8', u'ⓗⓔⓛⓛⓞ, ⓦⓞⓡⓛⓓ'),
- (u'gb2312', u'你好,世界'),
- (u'hebrew', u'שלום, עולם'),
- (u'shift_jis', u'こんにちは世界'),
- (u'cp1252', u'“hi”'),
- ]
- for encoding, text in details:
- filename = 'encoded_{0}.py'.format(encoding)
- ords = [ord(c) for c in text]
- source_text = source.format(encoding=encoding, text=text, ords=ords)
- zf.writestr(filename, source_text.encode(encoding))
- zf.close()
- def do_install_egg():
- """Install the egg1 egg for tests."""
- # I am pretty certain there are easier ways to install eggs...
- # pylint: disable=import-error,no-name-in-module
- cur_dir = os.getcwd()
- os.chdir("tests/eggsrc")
- with ignore_warnings():
- import distutils.core
- distutils.core.run_setup("setup.py", ["--quiet", "bdist_egg"])
- egg = glob.glob("dist/*.egg")[0]
- distutils.core.run_setup(
- "setup.py", ["--quiet", "easy_install", "--no-deps", "--zip-ok", egg]
- )
- os.chdir(cur_dir)
- def do_check_eol():
- """Check files for incorrect newlines and trailing whitespace."""
- ignore_dirs = [
- '.svn', '.hg', '.git',
- '.tox*',
- '*.egg-info',
- '_build',
- ]
- checked = set()
- def check_file(fname, crlf=True, trail_white=True):
- """Check a single file for whitespace abuse."""
- fname = os.path.relpath(fname)
- if fname in checked:
- return
- checked.add(fname)
- line = None
- with open(fname, "rb") as f:
- for n, line in enumerate(f, start=1):
- if crlf:
- if "\r" in line:
- print("%s@%d: CR found" % (fname, n))
- return
- if trail_white:
- line = line[:-1]
- if not crlf:
- line = line.rstrip('\r')
- if line.rstrip() != line:
- print("%s@%d: trailing whitespace found" % (fname, n))
- return
- if line is not None and not line.strip():
- print("%s: final blank line" % (fname,))
- def check_files(root, patterns, **kwargs):
- """Check a number of files for whitespace abuse."""
- for root, dirs, files in os.walk(root):
- for f in files:
- fname = os.path.join(root, f)
- for p in patterns:
- if fnmatch.fnmatch(fname, p):
- check_file(fname, **kwargs)
- break
- for ignore_dir in ignore_dirs:
- ignored = []
- for dir_name in dirs:
- if fnmatch.fnmatch(dir_name, ignore_dir):
- ignored.append(dir_name)
- for dir_name in ignored:
- dirs.remove(dir_name)
- check_files("coverage", ["*.py"])
- check_files("coverage/ctracer", ["*.c", "*.h"])
- check_files("coverage/htmlfiles", ["*.html", "*.css", "*.js"])
- check_file("tests/farm/html/src/bom.py", crlf=False)
- check_files("tests", ["*.py"])
- check_files("tests", ["*,cover"], trail_white=False)
- check_files("tests/js", ["*.js", "*.html"])
- check_file("setup.py")
- check_file("igor.py")
- check_file("Makefile")
- check_file(".hgignore")
- check_file(".travis.yml")
- check_files(".", ["*.rst", "*.txt"])
- check_files(".", ["*.pip"])
- def print_banner(label):
- """Print the version of Python."""
- try:
- impl = platform.python_implementation()
- except AttributeError:
- impl = "Python"
- version = platform.python_version()
- if '__pypy__' in sys.builtin_module_names:
- version += " (pypy %s)" % ".".join(str(v) for v in sys.pypy_version_info)
- try:
- which_python = os.path.relpath(sys.executable)
- except ValueError:
- # On Windows having a python executable on a different drives
- # than the sources cannot be relative.
- which_python = sys.executable
- print('=== %s %s %s (%s) ===' % (impl, version, label, which_python))
- sys.stdout.flush()
- def do_help():
- """List the available commands"""
- items = list(globals().items())
- items.sort()
- for name, value in items:
- if name.startswith('do_'):
- print("%-20s%s" % (name[3:], value.__doc__))
- def analyze_args(function):
- """What kind of args does `function` expect?
- Returns:
- star, num_pos:
- star(boolean): Does `function` accept *args?
- num_args(int): How many positional arguments does `function` have?
- """
- try:
- getargspec = inspect.getfullargspec
- except AttributeError:
- getargspec = inspect.getargspec
- argspec = getargspec(function)
- return bool(argspec[1]), len(argspec[0])
- def main(args):
- """Main command-line execution for igor.
- Verbs are taken from the command line, and extra words taken as directed
- by the arguments needed by the handler.
- """
- while args:
- verb = args.pop(0)
- handler = globals().get('do_'+verb)
- if handler is None:
- print("*** No handler for %r" % verb)
- return 1
- star, num_args = analyze_args(handler)
- if star:
- # Handler has *args, give it all the rest of the command line.
- handler_args = args
- args = []
- else:
- # Handler has specific arguments, give it only what it needs.
- handler_args = args[:num_args]
- args = args[num_args:]
- ret = handler(*handler_args)
- # If a handler returns a failure-like value, stop.
- if ret:
- return ret
- if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
|