forked from mirrors/gecko-dev
395 lines
12 KiB
Python
395 lines
12 KiB
Python
# coding: utf-8
|
|
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
# For details: https://github.com/nedbat/coveragepy/blob/master/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
|
|
|
|
import pytest
|
|
|
|
|
|
@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":
|
|
# $set_env.py: COVERAGE_NO_PYTRACER - Don't run the tests under the Python tracer.
|
|
skipper = os.environ.get("COVERAGE_NO_PYTRACER")
|
|
else:
|
|
# $set_env.py: COVERAGE_NO_CTRACER - Don't run the tests under the C tracer.
|
|
skipper = 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 make_env_id(tracer):
|
|
"""An environment id that will 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]
|
|
env_id = "%s%s_%s" % (impl, version, tracer)
|
|
return env_id
|
|
|
|
|
|
def run_tests(tracer, *runner_args):
|
|
"""The actual running of tests."""
|
|
if 'COVERAGE_TESTING' not in os.environ:
|
|
os.environ['COVERAGE_TESTING'] = "True"
|
|
# $set_env.py: COVERAGE_ENV_ID - Use environment-specific test directories.
|
|
if 'COVERAGE_ENV_ID' in os.environ:
|
|
os.environ['COVERAGE_ENV_ID'] = make_env_id(tracer)
|
|
print_banner(label_for_tracer(tracer))
|
|
return pytest.main(list(runner_args))
|
|
|
|
|
|
def run_tests_with_coverage(tracer, *runner_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?
|
|
# There's an entry in "make clean" to get rid of this file.
|
|
pth_dir = os.path.dirname(pytest.__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")
|
|
|
|
suffix = "%s_%s" % (make_env_id(tracer), platform.platform())
|
|
os.environ['COVERAGE_METAFILE'] = os.path.abspath(".metacov."+suffix)
|
|
|
|
import coverage
|
|
cov = coverage.Coverage(config_file="metacov.ini")
|
|
cov._warn_unimported_source = False
|
|
cov._warn_preimported_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 tests, with the arguments from our command line.
|
|
status = run_tests(tracer, *runner_args)
|
|
|
|
finally:
|
|
cov.stop()
|
|
os.remove(pth_path)
|
|
|
|
cov.combine()
|
|
cov.save()
|
|
|
|
return status
|
|
|
|
|
|
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()
|
|
show_contexts = bool(os.environ.get('COVERAGE_CONTEXT'))
|
|
cov.html_report(show_contexts=show_contexts)
|
|
cov.xml_report()
|
|
|
|
|
|
def do_test_with_tracer(tracer, *runner_args):
|
|
"""Run tests with a particular tracer."""
|
|
# If we should skip these tests, skip them.
|
|
skip_msg = should_skip(tracer)
|
|
if skip_msg:
|
|
print(skip_msg)
|
|
return None
|
|
|
|
os.environ["COVERAGE_TEST_TRACER"] = tracer
|
|
if os.environ.get("COVERAGE_COVERAGE", "no") == "yes":
|
|
return run_tests_with_coverage(tracer, *runner_args)
|
|
else:
|
|
return run_tests(tracer, *runner_args)
|
|
|
|
|
|
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_{}.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()
|
|
|
|
zf = zipfile.ZipFile("tests/covmain.zip", "w")
|
|
zf.write("coverage/__main__.py", "__main__.py")
|
|
zf.close()
|
|
|
|
|
|
def do_install_egg():
|
|
"""Install the egg1 egg for tests."""
|
|
# I am pretty certain there are easier ways to install eggs...
|
|
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',
|
|
'_spell',
|
|
]
|
|
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 b"\r" in line:
|
|
print("%s@%d: CR found" % (fname, n))
|
|
return
|
|
if trail_white:
|
|
line = line[:-1]
|
|
if not crlf:
|
|
line = line.rstrip(b'\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 where, dirs, files in os.walk(root):
|
|
for f in files:
|
|
fname = os.path.join(where, 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", "*.scss", "*.css", "*.js"])
|
|
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(".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 drive
|
|
# 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
|
|
with ignore_warnings():
|
|
# DeprecationWarning: Use inspect.signature() instead of inspect.getfullargspec()
|
|
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
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|