OLD | NEW |
| 1 # coding: utf-8 |
| 2 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| 3 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
| 4 |
1 """Helper for building, testing, and linting coverage.py. | 5 """Helper for building, testing, and linting coverage.py. |
2 | 6 |
3 To get portability, all these operations are written in Python here instead | 7 To get portability, all these operations are written in Python here instead |
4 of in shell scripts, batch files, or Makefiles. | 8 of in shell scripts, batch files, or Makefiles. |
5 | 9 |
6 """ | 10 """ |
7 | 11 |
| 12 import contextlib |
8 import fnmatch | 13 import fnmatch |
9 import glob | 14 import glob |
10 import inspect | 15 import inspect |
11 import os | 16 import os |
12 import platform | 17 import platform |
13 import socket | |
14 import sys | 18 import sys |
| 19 import textwrap |
| 20 import warnings |
15 import zipfile | 21 import zipfile |
16 | 22 |
17 | 23 |
| 24 # We want to see all warnings while we are running tests. But we also need to |
| 25 # disable warnings for some of the more complex setting up of tests. |
| 26 warnings.simplefilter("default") |
| 27 |
| 28 |
| 29 @contextlib.contextmanager |
| 30 def ignore_warnings(): |
| 31 """Context manager to ignore warning within the with statement.""" |
| 32 with warnings.catch_warnings(): |
| 33 warnings.simplefilter("ignore") |
| 34 yield |
| 35 |
| 36 |
18 # Functions named do_* are executable from the command line: do_blah is run | 37 # Functions named do_* are executable from the command line: do_blah is run |
19 # by "python igor.py blah". | 38 # by "python igor.py blah". |
20 | 39 |
21 | 40 |
| 41 def do_show_env(): |
| 42 """Show the environment variables.""" |
| 43 print("Environment:") |
| 44 for env in sorted(os.environ): |
| 45 print(" %s = %r" % (env, os.environ[env])) |
| 46 |
| 47 |
22 def do_remove_extension(): | 48 def do_remove_extension(): |
23 """Remove the compiled C extension, no matter what its name.""" | 49 """Remove the compiled C extension, no matter what its name.""" |
24 | 50 |
25 so_patterns = """ | 51 so_patterns = """ |
26 tracer.so | 52 tracer.so |
27 tracer.*.so | 53 tracer.*.so |
28 tracer.pyd | 54 tracer.pyd |
| 55 tracer.*.pyd |
29 """.split() | 56 """.split() |
30 | 57 |
31 for pattern in so_patterns: | 58 for pattern in so_patterns: |
32 pattern = os.path.join("coverage", pattern) | 59 pattern = os.path.join("coverage", pattern) |
33 for filename in glob.glob(pattern): | 60 for filename in glob.glob(pattern): |
34 try: | 61 try: |
35 os.remove(filename) | 62 os.remove(filename) |
36 except OSError: | 63 except OSError: |
37 pass | 64 pass |
38 | 65 |
39 def run_tests(tracer, *nose_args): | 66 |
40 """The actual running of tests.""" | 67 def label_for_tracer(tracer): |
41 import nose.core | 68 """Get the label for these tests.""" |
42 if tracer == "py": | 69 if tracer == "py": |
43 label = "with Python tracer" | 70 label = "with Python tracer" |
44 else: | 71 else: |
45 label = "with C tracer" | 72 label = "with C tracer" |
46 if os.environ.get("COVERAGE_NO_EXTENSION"): | 73 |
47 print("Skipping tests, no C extension in this environment") | 74 return label |
48 return | 75 |
49 print_banner(label) | 76 |
50 os.environ["COVERAGE_TEST_TRACER"] = tracer | 77 def should_skip(tracer): |
| 78 """Is there a reason to skip these tests?""" |
| 79 if tracer == "py": |
| 80 skipper = os.environ.get("COVERAGE_NO_PYTRACER") |
| 81 else: |
| 82 skipper = ( |
| 83 os.environ.get("COVERAGE_NO_EXTENSION") or |
| 84 os.environ.get("COVERAGE_NO_CTRACER") |
| 85 ) |
| 86 |
| 87 if skipper: |
| 88 msg = "Skipping tests " + label_for_tracer(tracer) |
| 89 if len(skipper) > 1: |
| 90 msg += ": " + skipper |
| 91 else: |
| 92 msg = "" |
| 93 |
| 94 return msg |
| 95 |
| 96 |
| 97 def run_tests(tracer, *nose_args): |
| 98 """The actual running of tests.""" |
| 99 with ignore_warnings(): |
| 100 import nose.core |
| 101 |
| 102 if 'COVERAGE_TESTING' not in os.environ: |
| 103 os.environ['COVERAGE_TESTING'] = "True" |
| 104 print_banner(label_for_tracer(tracer)) |
51 nose_args = ["nosetests"] + list(nose_args) | 105 nose_args = ["nosetests"] + list(nose_args) |
52 nose.core.main(argv=nose_args) | 106 nose.core.main(argv=nose_args) |
53 | 107 |
| 108 |
54 def run_tests_with_coverage(tracer, *nose_args): | 109 def run_tests_with_coverage(tracer, *nose_args): |
55 """Run tests, but with coverage.""" | 110 """Run tests, but with coverage.""" |
56 import coverage | |
57 | 111 |
| 112 # Need to define this early enough that the first import of env.py sees it. |
| 113 os.environ['COVERAGE_TESTING'] = "True" |
58 os.environ['COVERAGE_PROCESS_START'] = os.path.abspath('metacov.ini') | 114 os.environ['COVERAGE_PROCESS_START'] = os.path.abspath('metacov.ini') |
59 os.environ['COVERAGE_HOME'] = os.getcwd() | 115 os.environ['COVERAGE_HOME'] = os.getcwd() |
60 | 116 |
61 # Create the .pth file that will let us measure coverage in sub-processes. | 117 # Create the .pth file that will let us measure coverage in sub-processes. |
| 118 # The .pth file seems to have to be alphabetically after easy-install.pth |
| 119 # or the sys.path entries aren't created right? |
62 import nose | 120 import nose |
63 pth_dir = os.path.dirname(os.path.dirname(nose.__file__)) | 121 pth_dir = os.path.dirname(os.path.dirname(nose.__file__)) |
64 pth_path = os.path.join(pth_dir, "covcov.pth") | 122 pth_path = os.path.join(pth_dir, "zzz_metacov.pth") |
65 pth_file = open(pth_path, "w") | 123 with open(pth_path, "w") as pth_file: |
66 try: | |
67 pth_file.write("import coverage; coverage.process_startup()\n") | 124 pth_file.write("import coverage; coverage.process_startup()\n") |
68 finally: | |
69 pth_file.close() | |
70 | 125 |
| 126 # Make names for the data files that keep all the test runs distinct. |
| 127 impl = platform.python_implementation().lower() |
71 version = "%s%s" % sys.version_info[:2] | 128 version = "%s%s" % sys.version_info[:2] |
72 suffix = "%s_%s_%s" % (version, tracer, socket.gethostname()) | 129 if '__pypy__' in sys.builtin_module_names: |
| 130 version += "_%s%s" % sys.pypy_version_info[:2] |
| 131 suffix = "%s%s_%s_%s" % (impl, version, tracer, platform.platform()) |
73 | 132 |
74 cov = coverage.coverage(config_file="metacov.ini", data_suffix=suffix) | 133 os.environ['COVERAGE_METAFILE'] = os.path.abspath(".metacov."+suffix) |
75 # Cheap trick: the coverage code itself is excluded from measurement, but | 134 |
76 # if we clobber the cover_prefix in the coverage object, we can defeat the | 135 import coverage |
77 # self-detection. | 136 cov = coverage.Coverage(config_file="metacov.ini", data_suffix=False) |
| 137 # Cheap trick: the coverage.py code itself is excluded from measurement, |
| 138 # but if we clobber the cover_prefix in the coverage object, we can defeat |
| 139 # the self-detection. |
78 cov.cover_prefix = "Please measure coverage.py!" | 140 cov.cover_prefix = "Please measure coverage.py!" |
79 cov.erase() | 141 cov._warn_unimported_source = False |
80 cov.start() | 142 cov.start() |
81 | 143 |
82 try: | 144 try: |
83 # Re-import coverage to get it coverage tested! I don't understand all | 145 # 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 | 146 # the mechanics here, but if I don't carry over the imported modules |
85 # (in covmods), then things go haywire (os == None, eventually). | 147 # (in covmods), then things go haywire (os == None, eventually). |
86 covmods = {} | 148 covmods = {} |
87 covdir = os.path.split(coverage.__file__)[0] | 149 covdir = os.path.split(coverage.__file__)[0] |
88 # We have to make a list since we'll be deleting in the loop. | 150 # We have to make a list since we'll be deleting in the loop. |
89 modules = list(sys.modules.items()) | 151 modules = list(sys.modules.items()) |
90 for name, mod in modules: | 152 for name, mod in modules: |
91 if name.startswith('coverage'): | 153 if name.startswith('coverage'): |
92 if getattr(mod, '__file__', "??").startswith(covdir): | 154 if getattr(mod, '__file__', "??").startswith(covdir): |
93 covmods[name] = mod | 155 covmods[name] = mod |
94 del sys.modules[name] | 156 del sys.modules[name] |
95 import coverage # don't warn about re-import: pylint: disable=W0404 | 157 import coverage # pylint: disable=reimported |
96 sys.modules.update(covmods) | 158 sys.modules.update(covmods) |
97 | 159 |
98 # Run nosetests, with the arguments from our command line. | 160 # Run nosetests, with the arguments from our command line. |
99 try: | 161 try: |
100 run_tests(tracer, *nose_args) | 162 run_tests(tracer, *nose_args) |
101 except SystemExit: | 163 except SystemExit: |
102 # nose3 seems to raise SystemExit, not sure why? | 164 # nose3 seems to raise SystemExit, not sure why? |
103 pass | 165 pass |
104 finally: | 166 finally: |
105 cov.stop() | 167 cov.stop() |
106 os.remove(pth_path) | 168 os.remove(pth_path) |
107 | 169 |
| 170 cov.combine() |
108 cov.save() | 171 cov.save() |
109 | 172 |
| 173 |
110 def do_combine_html(): | 174 def do_combine_html(): |
111 """Combine data from a meta-coverage run, and make the HTML report.""" | 175 """Combine data from a meta-coverage run, and make the HTML and XML reports.
""" |
112 import coverage | 176 import coverage |
113 os.environ['COVERAGE_HOME'] = os.getcwd() | 177 os.environ['COVERAGE_HOME'] = os.getcwd() |
114 cov = coverage.coverage(config_file="metacov.ini") | 178 os.environ['COVERAGE_METAFILE'] = os.path.abspath(".metacov") |
| 179 cov = coverage.Coverage(config_file="metacov.ini") |
115 cov.load() | 180 cov.load() |
116 cov.combine() | 181 cov.combine() |
117 cov.save() | 182 cov.save() |
118 cov.html_report() | 183 cov.html_report() |
| 184 cov.xml_report() |
| 185 |
119 | 186 |
120 def do_test_with_tracer(tracer, *noseargs): | 187 def do_test_with_tracer(tracer, *noseargs): |
121 """Run nosetests with a particular tracer.""" | 188 """Run nosetests with a particular tracer.""" |
| 189 # If we should skip these tests, skip them. |
| 190 skip_msg = should_skip(tracer) |
| 191 if skip_msg: |
| 192 print(skip_msg) |
| 193 return |
| 194 |
| 195 os.environ["COVERAGE_TEST_TRACER"] = tracer |
122 if os.environ.get("COVERAGE_COVERAGE", ""): | 196 if os.environ.get("COVERAGE_COVERAGE", ""): |
123 return run_tests_with_coverage(tracer, *noseargs) | 197 return run_tests_with_coverage(tracer, *noseargs) |
124 else: | 198 else: |
125 return run_tests(tracer, *noseargs) | 199 return run_tests(tracer, *noseargs) |
126 | 200 |
| 201 |
127 def do_zip_mods(): | 202 def do_zip_mods(): |
128 """Build the zipmods.zip file.""" | 203 """Build the zipmods.zip file.""" |
129 zf = zipfile.ZipFile("tests/zipmods.zip", "w") | 204 zf = zipfile.ZipFile("tests/zipmods.zip", "w") |
| 205 |
| 206 # Take one file from disk. |
130 zf.write("tests/covmodzip1.py", "covmodzip1.py") | 207 zf.write("tests/covmodzip1.py", "covmodzip1.py") |
| 208 |
| 209 # The others will be various encodings. |
| 210 source = textwrap.dedent(u"""\ |
| 211 # coding: {encoding} |
| 212 text = u"{text}" |
| 213 ords = {ords} |
| 214 assert [ord(c) for c in text] == ords |
| 215 print(u"All OK with {encoding}") |
| 216 """) |
| 217 details = [ |
| 218 (u'utf8', u'ⓗⓔⓛⓛⓞ, ⓦⓞⓡⓛⓓ'), |
| 219 (u'gb2312', u'你好,世界'), |
| 220 (u'hebrew', u'שלום, עולם'), |
| 221 (u'shift_jis', u'こんにちは世界'), |
| 222 ] |
| 223 for encoding, text in details: |
| 224 filename = 'encoded_{0}.py'.format(encoding) |
| 225 ords = [ord(c) for c in text] |
| 226 source_text = source.format(encoding=encoding, text=text, ords=ords) |
| 227 zf.writestr(filename, source_text.encode(encoding)) |
| 228 |
131 zf.close() | 229 zf.close() |
132 | 230 |
| 231 |
133 def do_install_egg(): | 232 def do_install_egg(): |
134 """Install the egg1 egg for tests.""" | 233 """Install the egg1 egg for tests.""" |
135 # I am pretty certain there are easier ways to install eggs... | 234 # I am pretty certain there are easier ways to install eggs... |
136 # pylint: disable=F0401,E0611,E1101 | 235 # pylint: disable=import-error,no-name-in-module |
137 import distutils.core | |
138 cur_dir = os.getcwd() | 236 cur_dir = os.getcwd() |
139 os.chdir("tests/eggsrc") | 237 os.chdir("tests/eggsrc") |
140 distutils.core.run_setup("setup.py", ["--quiet", "bdist_egg"]) | 238 with ignore_warnings(): |
141 egg = glob.glob("dist/*.egg")[0] | 239 import distutils.core |
142 distutils.core.run_setup( | 240 distutils.core.run_setup("setup.py", ["--quiet", "bdist_egg"]) |
143 "setup.py", ["--quiet", "easy_install", "--no-deps", "--zip-ok", egg] | 241 egg = glob.glob("dist/*.egg")[0] |
144 ) | 242 distutils.core.run_setup( |
| 243 "setup.py", ["--quiet", "easy_install", "--no-deps", "--zip-ok", egg
] |
| 244 ) |
145 os.chdir(cur_dir) | 245 os.chdir(cur_dir) |
146 | 246 |
| 247 |
147 def do_check_eol(): | 248 def do_check_eol(): |
148 """Check files for incorrect newlines and trailing whitespace.""" | 249 """Check files for incorrect newlines and trailing whitespace.""" |
149 | 250 |
150 ignore_dirs = [ | 251 ignore_dirs = [ |
151 '.svn', '.hg', '.tox', '.tox_kits', 'coverage.egg-info', | 252 '.svn', '.hg', '.git', |
152 '_build', 'covtestegg1.egg-info', | 253 '.tox*', |
| 254 '*.egg-info', |
| 255 '_build', |
153 ] | 256 ] |
154 checked = set([]) | 257 checked = set() |
155 | 258 |
156 def check_file(fname, crlf=True, trail_white=True): | 259 def check_file(fname, crlf=True, trail_white=True): |
157 """Check a single file for whitespace abuse.""" | 260 """Check a single file for whitespace abuse.""" |
158 fname = os.path.relpath(fname) | 261 fname = os.path.relpath(fname) |
159 if fname in checked: | 262 if fname in checked: |
160 return | 263 return |
161 checked.add(fname) | 264 checked.add(fname) |
162 | 265 |
163 line = None | 266 line = None |
164 for n, line in enumerate(open(fname, "rb")): | 267 with open(fname, "rb") as f: |
165 if crlf: | 268 for n, line in enumerate(f, start=1): |
166 if "\r" in line: | 269 if crlf: |
167 print("%s@%d: CR found" % (fname, n+1)) | 270 if "\r" in line: |
168 return | 271 print("%s@%d: CR found" % (fname, n)) |
169 if trail_white: | 272 return |
170 line = line[:-1] | 273 if trail_white: |
171 if not crlf: | 274 line = line[:-1] |
172 line = line.rstrip('\r') | 275 if not crlf: |
173 if line.rstrip() != line: | 276 line = line.rstrip('\r') |
174 print("%s@%d: trailing whitespace found" % (fname, n+1)) | 277 if line.rstrip() != line: |
175 return | 278 print("%s@%d: trailing whitespace found" % (fname, n)) |
| 279 return |
176 | 280 |
177 if line is not None and not line.strip(): | 281 if line is not None and not line.strip(): |
178 print("%s: final blank line" % (fname,)) | 282 print("%s: final blank line" % (fname,)) |
179 | 283 |
180 def check_files(root, patterns, **kwargs): | 284 def check_files(root, patterns, **kwargs): |
181 """Check a number of files for whitespace abuse.""" | 285 """Check a number of files for whitespace abuse.""" |
182 for root, dirs, files in os.walk(root): | 286 for root, dirs, files in os.walk(root): |
183 for f in files: | 287 for f in files: |
184 fname = os.path.join(root, f) | 288 fname = os.path.join(root, f) |
185 for p in patterns: | 289 for p in patterns: |
186 if fnmatch.fnmatch(fname, p): | 290 if fnmatch.fnmatch(fname, p): |
187 check_file(fname, **kwargs) | 291 check_file(fname, **kwargs) |
188 break | 292 break |
189 for dir_name in ignore_dirs: | 293 for ignore_dir in ignore_dirs: |
190 if dir_name in dirs: | 294 ignored = [] |
| 295 for dir_name in dirs: |
| 296 if fnmatch.fnmatch(dir_name, ignore_dir): |
| 297 ignored.append(dir_name) |
| 298 for dir_name in ignored: |
191 dirs.remove(dir_name) | 299 dirs.remove(dir_name) |
192 | 300 |
193 check_files("coverage", ["*.py", "*.c"]) | 301 check_files("coverage", ["*.py"]) |
| 302 check_files("coverage/ctracer", ["*.c", "*.h"]) |
194 check_files("coverage/htmlfiles", ["*.html", "*.css", "*.js"]) | 303 check_files("coverage/htmlfiles", ["*.html", "*.css", "*.js"]) |
195 check_file("tests/farm/html/src/bom.py", crlf=False) | 304 check_file("tests/farm/html/src/bom.py", crlf=False) |
196 check_files("tests", ["*.py"]) | 305 check_files("tests", ["*.py"]) |
197 check_files("tests", ["*,cover"], trail_white=False) | 306 check_files("tests", ["*,cover"], trail_white=False) |
198 check_files("tests/js", ["*.js", "*.html"]) | 307 check_files("tests/js", ["*.js", "*.html"]) |
199 check_file("setup.py") | 308 check_file("setup.py") |
200 check_file("igor.py") | 309 check_file("igor.py") |
201 check_file("Makefile") | 310 check_file("Makefile") |
202 check_file(".hgignore") | 311 check_file(".hgignore") |
203 check_file(".travis.yml") | 312 check_file(".travis.yml") |
204 check_files("doc", ["*.rst"]) | 313 check_files(".", ["*.rst", "*.txt"]) |
205 check_files(".", ["*.txt"]) | 314 check_files(".", ["*.pip"]) |
206 | 315 |
207 | 316 |
208 def print_banner(label): | 317 def print_banner(label): |
209 """Print the version of Python.""" | 318 """Print the version of Python.""" |
210 try: | 319 try: |
211 impl = platform.python_implementation() | 320 impl = platform.python_implementation() |
212 except AttributeError: | 321 except AttributeError: |
213 impl = "Python" | 322 impl = "Python" |
214 | 323 |
215 version = platform.python_version() | 324 version = platform.python_version() |
216 | 325 |
217 if '__pypy__' in sys.builtin_module_names: | 326 if '__pypy__' in sys.builtin_module_names: |
218 pypy_version = sys.pypy_version_info # pylint: disable=E1101 | 327 version += " (pypy %s)" % ".".join(str(v) for v in sys.pypy_version_info
) |
219 version += " (pypy %s)" % ".".join([str(v) for v in pypy_version]) | |
220 | 328 |
221 print('=== %s %s %s (%s) ===' % (impl, version, label, sys.executable)) | 329 which_python = os.path.relpath(sys.executable) |
| 330 print('=== %s %s %s (%s) ===' % (impl, version, label, which_python)) |
| 331 sys.stdout.flush() |
222 | 332 |
223 | 333 |
224 def do_help(): | 334 def do_help(): |
225 """List the available commands""" | 335 """List the available commands""" |
226 items = list(globals().items()) | 336 items = list(globals().items()) |
227 items.sort() | 337 items.sort() |
228 for name, value in items: | 338 for name, value in items: |
229 if name.startswith('do_'): | 339 if name.startswith('do_'): |
230 print("%-20s%s" % (name[3:], value.__doc__)) | 340 print("%-20s%s" % (name[3:], value.__doc__)) |
231 | 341 |
232 | 342 |
| 343 def analyze_args(function): |
| 344 """What kind of args does `function` expect? |
| 345 |
| 346 Returns: |
| 347 star, num_pos: |
| 348 star(boolean): Does `function` accept *args? |
| 349 num_args(int): How many positional arguments does `function` have? |
| 350 """ |
| 351 try: |
| 352 getargspec = inspect.getfullargspec |
| 353 except AttributeError: |
| 354 getargspec = inspect.getargspec |
| 355 argspec = getargspec(function) |
| 356 return bool(argspec[1]), len(argspec[0]) |
| 357 |
| 358 |
233 def main(args): | 359 def main(args): |
234 """Main command-line execution for igor. | 360 """Main command-line execution for igor. |
235 | 361 |
236 Verbs are taken from the command line, and extra words taken as directed | 362 Verbs are taken from the command line, and extra words taken as directed |
237 by the arguments needed by the handler. | 363 by the arguments needed by the handler. |
238 | 364 |
239 """ | 365 """ |
240 while args: | 366 while args: |
241 verb = args.pop(0) | 367 verb = args.pop(0) |
242 handler = globals().get('do_'+verb) | 368 handler = globals().get('do_'+verb) |
243 if handler is None: | 369 if handler is None: |
244 print("*** No handler for %r" % verb) | 370 print("*** No handler for %r" % verb) |
245 return 1 | 371 return 1 |
246 argspec = inspect.getargspec(handler) | 372 star, num_args = analyze_args(handler) |
247 if argspec[1]: | 373 if star: |
248 # Handler has *args, give it all the rest of the command line. | 374 # Handler has *args, give it all the rest of the command line. |
249 handler_args = args | 375 handler_args = args |
250 args = [] | 376 args = [] |
251 else: | 377 else: |
252 # Handler has specific arguments, give it only what it needs. | 378 # Handler has specific arguments, give it only what it needs. |
253 num_args = len(argspec[0]) | |
254 handler_args = args[:num_args] | 379 handler_args = args[:num_args] |
255 args = args[num_args:] | 380 args = args[num_args:] |
256 ret = handler(*handler_args) | 381 ret = handler(*handler_args) |
257 # If a handler returns a failure-like value, stop. | 382 # If a handler returns a failure-like value, stop. |
258 if ret: | 383 if ret: |
259 return ret | 384 return ret |
260 | 385 |
| 386 |
261 if __name__ == '__main__': | 387 if __name__ == '__main__': |
262 sys.exit(main(sys.argv[1:])) | 388 sys.exit(main(sys.argv[1:])) |
OLD | NEW |