Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / third_party / pycoverage / igor.py
blob12ec6c8fec4afe1fe482cbcbcda18759135ff60e
1 """Helper for building, testing, and linting coverage.py.
3 To get portability, all these operations are written in Python here instead
4 of in shell scripts, batch files, or Makefiles.
6 """
8 import fnmatch
9 import glob
10 import inspect
11 import os
12 import platform
13 import socket
14 import sys
15 import zipfile
18 # Functions named do_* are executable from the command line: do_blah is run
19 # by "python igor.py blah".
22 def do_remove_extension():
23 """Remove the compiled C extension, no matter what its name."""
25 so_patterns = """
26 tracer.so
27 tracer.*.so
28 tracer.pyd
29 """.split()
31 for pattern in so_patterns:
32 pattern = os.path.join("coverage", pattern)
33 for filename in glob.glob(pattern):
34 try:
35 os.remove(filename)
36 except OSError:
37 pass
39 def run_tests(tracer, *nose_args):
40 """The actual running of tests."""
41 import nose.core
42 if tracer == "py":
43 label = "with Python tracer"
44 else:
45 label = "with C tracer"
46 if os.environ.get("COVERAGE_NO_EXTENSION"):
47 print("Skipping tests, no C extension in this environment")
48 return
49 print_banner(label)
50 os.environ["COVERAGE_TEST_TRACER"] = tracer
51 nose_args = ["nosetests"] + list(nose_args)
52 nose.core.main(argv=nose_args)
54 def run_tests_with_coverage(tracer, *nose_args):
55 """Run tests, but with coverage."""
56 import coverage
58 os.environ['COVERAGE_PROCESS_START'] = os.path.abspath('metacov.ini')
59 os.environ['COVERAGE_HOME'] = os.getcwd()
61 # Create the .pth file that will let us measure coverage in sub-processes.
62 import nose
63 pth_dir = os.path.dirname(os.path.dirname(nose.__file__))
64 pth_path = os.path.join(pth_dir, "covcov.pth")
65 pth_file = open(pth_path, "w")
66 try:
67 pth_file.write("import coverage; coverage.process_startup()\n")
68 finally:
69 pth_file.close()
71 version = "%s%s" % sys.version_info[:2]
72 suffix = "%s_%s_%s" % (version, tracer, socket.gethostname())
74 cov = coverage.coverage(config_file="metacov.ini", data_suffix=suffix)
75 # Cheap trick: the coverage code itself is excluded from measurement, but
76 # if we clobber the cover_prefix in the coverage object, we can defeat the
77 # self-detection.
78 cov.cover_prefix = "Please measure coverage.py!"
79 cov.erase()
80 cov.start()
82 try:
83 # Re-import coverage to get it coverage tested! I don't understand all
84 # the mechanics here, but if I don't carry over the imported modules
85 # (in covmods), then things go haywire (os == None, eventually).
86 covmods = {}
87 covdir = os.path.split(coverage.__file__)[0]
88 # We have to make a list since we'll be deleting in the loop.
89 modules = list(sys.modules.items())
90 for name, mod in modules:
91 if name.startswith('coverage'):
92 if getattr(mod, '__file__', "??").startswith(covdir):
93 covmods[name] = mod
94 del sys.modules[name]
95 import coverage # don't warn about re-import: pylint: disable=W0404
96 sys.modules.update(covmods)
98 # Run nosetests, with the arguments from our command line.
99 try:
100 run_tests(tracer, *nose_args)
101 except SystemExit:
102 # nose3 seems to raise SystemExit, not sure why?
103 pass
104 finally:
105 cov.stop()
106 os.remove(pth_path)
108 cov.save()
110 def do_combine_html():
111 """Combine data from a meta-coverage run, and make the HTML report."""
112 import coverage
113 os.environ['COVERAGE_HOME'] = os.getcwd()
114 cov = coverage.coverage(config_file="metacov.ini")
115 cov.load()
116 cov.combine()
117 cov.save()
118 cov.html_report()
120 def do_test_with_tracer(tracer, *noseargs):
121 """Run nosetests with a particular tracer."""
122 if os.environ.get("COVERAGE_COVERAGE", ""):
123 return run_tests_with_coverage(tracer, *noseargs)
124 else:
125 return run_tests(tracer, *noseargs)
127 def do_zip_mods():
128 """Build the zipmods.zip file."""
129 zf = zipfile.ZipFile("tests/zipmods.zip", "w")
130 zf.write("tests/covmodzip1.py", "covmodzip1.py")
131 zf.close()
133 def do_install_egg():
134 """Install the egg1 egg for tests."""
135 # I am pretty certain there are easier ways to install eggs...
136 # pylint: disable=F0401,E0611,E1101
137 import distutils.core
138 cur_dir = os.getcwd()
139 os.chdir("tests/eggsrc")
140 distutils.core.run_setup("setup.py", ["--quiet", "bdist_egg"])
141 egg = glob.glob("dist/*.egg")[0]
142 distutils.core.run_setup(
143 "setup.py", ["--quiet", "easy_install", "--no-deps", "--zip-ok", egg]
145 os.chdir(cur_dir)
147 def do_check_eol():
148 """Check files for incorrect newlines and trailing whitespace."""
150 ignore_dirs = [
151 '.svn', '.hg', '.tox', '.tox_kits', 'coverage.egg-info',
152 '_build', 'covtestegg1.egg-info',
154 checked = set([])
156 def check_file(fname, crlf=True, trail_white=True):
157 """Check a single file for whitespace abuse."""
158 fname = os.path.relpath(fname)
159 if fname in checked:
160 return
161 checked.add(fname)
163 line = None
164 for n, line in enumerate(open(fname, "rb")):
165 if crlf:
166 if "\r" in line:
167 print("%s@%d: CR found" % (fname, n+1))
168 return
169 if trail_white:
170 line = line[:-1]
171 if not crlf:
172 line = line.rstrip('\r')
173 if line.rstrip() != line:
174 print("%s@%d: trailing whitespace found" % (fname, n+1))
175 return
177 if line is not None and not line.strip():
178 print("%s: final blank line" % (fname,))
180 def check_files(root, patterns, **kwargs):
181 """Check a number of files for whitespace abuse."""
182 for root, dirs, files in os.walk(root):
183 for f in files:
184 fname = os.path.join(root, f)
185 for p in patterns:
186 if fnmatch.fnmatch(fname, p):
187 check_file(fname, **kwargs)
188 break
189 for dir_name in ignore_dirs:
190 if dir_name in dirs:
191 dirs.remove(dir_name)
193 check_files("coverage", ["*.py", "*.c"])
194 check_files("coverage/htmlfiles", ["*.html", "*.css", "*.js"])
195 check_file("tests/farm/html/src/bom.py", crlf=False)
196 check_files("tests", ["*.py"])
197 check_files("tests", ["*,cover"], trail_white=False)
198 check_files("tests/js", ["*.js", "*.html"])
199 check_file("setup.py")
200 check_file("igor.py")
201 check_file("Makefile")
202 check_file(".hgignore")
203 check_file(".travis.yml")
204 check_files("doc", ["*.rst"])
205 check_files(".", ["*.txt"])
208 def print_banner(label):
209 """Print the version of Python."""
210 try:
211 impl = platform.python_implementation()
212 except AttributeError:
213 impl = "Python"
215 version = platform.python_version()
217 if '__pypy__' in sys.builtin_module_names:
218 pypy_version = sys.pypy_version_info # pylint: disable=E1101
219 version += " (pypy %s)" % ".".join([str(v) for v in pypy_version])
221 print('=== %s %s %s (%s) ===' % (impl, version, label, sys.executable))
224 def do_help():
225 """List the available commands"""
226 items = list(globals().items())
227 items.sort()
228 for name, value in items:
229 if name.startswith('do_'):
230 print("%-20s%s" % (name[3:], value.__doc__))
233 def main(args):
234 """Main command-line execution for igor.
236 Verbs are taken from the command line, and extra words taken as directed
237 by the arguments needed by the handler.
240 while args:
241 verb = args.pop(0)
242 handler = globals().get('do_'+verb)
243 if handler is None:
244 print("*** No handler for %r" % verb)
245 return 1
246 argspec = inspect.getargspec(handler)
247 if argspec[1]:
248 # Handler has *args, give it all the rest of the command line.
249 handler_args = args
250 args = []
251 else:
252 # Handler has specific arguments, give it only what it needs.
253 num_args = len(argspec[0])
254 handler_args = args[:num_args]
255 args = args[num_args:]
256 ret = handler(*handler_args)
257 # If a handler returns a failure-like value, stop.
258 if ret:
259 return ret
261 if __name__ == '__main__':
262 sys.exit(main(sys.argv[1:]))