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
5 """Helper for building, testing, and linting coverage.py.
7 To get portability, all these operations are written in Python here instead
8 of in shell scripts, batch files, or Makefiles.
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")
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")
37 # Functions named do_* are executable from the command line: do_blah is run
38 # by "python igor.py blah".
42 """Show the environment variables."""
44 for env
in sorted(os
.environ
):
45 print(" %s = %r" % (env
, os
.environ
[env
]))
48 def do_remove_extension():
49 """Remove the compiled C extension, no matter what its name."""
58 for pattern
in so_patterns
:
59 pattern
= os
.path
.join("coverage", pattern
)
60 for filename
in glob
.glob(pattern
):
67 def label_for_tracer(tracer
):
68 """Get the label for these tests."""
70 label
= "with Python tracer"
72 label
= "with C tracer"
77 def should_skip(tracer
):
78 """Is there a reason to skip these tests?"""
80 skipper
= os
.environ
.get("COVERAGE_NO_PYTRACER")
83 os
.environ
.get("COVERAGE_NO_EXTENSION") or
84 os
.environ
.get("COVERAGE_NO_CTRACER")
88 msg
= "Skipping tests " + label_for_tracer(tracer
)
97 def run_tests(tracer
, *nose_args
):
98 """The actual running of tests."""
99 with
ignore_warnings():
102 if 'COVERAGE_TESTING' not in os
.environ
:
103 os
.environ
['COVERAGE_TESTING'] = "True"
104 print_banner(label_for_tracer(tracer
))
105 nose_args
= ["nosetests"] + list(nose_args
)
106 nose
.core
.main(argv
=nose_args
)
109 def run_tests_with_coverage(tracer
, *nose_args
):
110 """Run tests, but with coverage."""
112 # Need to define this early enough that the first import of env.py sees it.
113 os
.environ
['COVERAGE_TESTING'] = "True"
114 os
.environ
['COVERAGE_PROCESS_START'] = os
.path
.abspath('metacov.ini')
115 os
.environ
['COVERAGE_HOME'] = os
.getcwd()
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?
121 pth_dir
= os
.path
.dirname(os
.path
.dirname(nose
.__file
__))
122 pth_path
= os
.path
.join(pth_dir
, "zzz_metacov.pth")
123 with
open(pth_path
, "w") as pth_file
:
124 pth_file
.write("import coverage; coverage.process_startup()\n")
126 # Make names for the data files that keep all the test runs distinct.
127 impl
= platform
.python_implementation().lower()
128 version
= "%s%s" % sys
.version_info
[:2]
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())
133 os
.environ
['COVERAGE_METAFILE'] = os
.path
.abspath(".metacov."+suffix
)
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.
140 cov
.cover_prefix
= "Please measure coverage.py!"
141 cov
._warn
_unimported
_source
= False
145 # Re-import coverage to get it coverage tested! I don't understand all
146 # the mechanics here, but if I don't carry over the imported modules
147 # (in covmods), then things go haywire (os == None, eventually).
149 covdir
= os
.path
.split(coverage
.__file
__)[0]
150 # We have to make a list since we'll be deleting in the loop.
151 modules
= list(sys
.modules
.items())
152 for name
, mod
in modules
:
153 if name
.startswith('coverage'):
154 if getattr(mod
, '__file__', "??").startswith(covdir
):
156 del sys
.modules
[name
]
157 import coverage
# pylint: disable=reimported
158 sys
.modules
.update(covmods
)
160 # Run nosetests, with the arguments from our command line.
162 run_tests(tracer
, *nose_args
)
164 # nose3 seems to raise SystemExit, not sure why?
174 def do_combine_html():
175 """Combine data from a meta-coverage run, and make the HTML and XML reports."""
177 os
.environ
['COVERAGE_HOME'] = os
.getcwd()
178 os
.environ
['COVERAGE_METAFILE'] = os
.path
.abspath(".metacov")
179 cov
= coverage
.Coverage(config_file
="metacov.ini")
187 def do_test_with_tracer(tracer
, *noseargs
):
188 """Run nosetests with a particular tracer."""
189 # If we should skip these tests, skip them.
190 skip_msg
= should_skip(tracer
)
195 os
.environ
["COVERAGE_TEST_TRACER"] = tracer
196 if os
.environ
.get("COVERAGE_COVERAGE", ""):
197 return run_tests_with_coverage(tracer
, *noseargs
)
199 return run_tests(tracer
, *noseargs
)
203 """Build the zipmods.zip file."""
204 zf
= zipfile
.ZipFile("tests/zipmods.zip", "w")
206 # Take one file from disk.
207 zf
.write("tests/covmodzip1.py", "covmodzip1.py")
209 # The others will be various encodings.
210 source
= textwrap
.dedent(u
"""\
214 assert [ord(c) for c in text] == ords
215 print(u"All OK with {encoding}")
217 # These encodings should match the list in tests/test_python.py
219 (u
'utf8', u
'ⓗⓔⓛⓛⓞ, ⓦⓞⓡⓛⓓ'),
220 (u
'gb2312', u
'你好,世界'),
221 (u
'hebrew', u
'שלום, עולם'),
222 (u
'shift_jis', u
'こんにちは世界'),
223 (u
'cp1252', u
'“hi”'),
225 for encoding
, text
in details
:
226 filename
= 'encoded_{0}.py'.format(encoding
)
227 ords
= [ord(c
) for c
in text
]
228 source_text
= source
.format(encoding
=encoding
, text
=text
, ords
=ords
)
229 zf
.writestr(filename
, source_text
.encode(encoding
))
234 def do_install_egg():
235 """Install the egg1 egg for tests."""
236 # I am pretty certain there are easier ways to install eggs...
237 # pylint: disable=import-error,no-name-in-module
238 cur_dir
= os
.getcwd()
239 os
.chdir("tests/eggsrc")
240 with
ignore_warnings():
241 import distutils
.core
242 distutils
.core
.run_setup("setup.py", ["--quiet", "bdist_egg"])
243 egg
= glob
.glob("dist/*.egg")[0]
244 distutils
.core
.run_setup(
245 "setup.py", ["--quiet", "easy_install", "--no-deps", "--zip-ok", egg
]
251 """Check files for incorrect newlines and trailing whitespace."""
254 '.svn', '.hg', '.git',
261 def check_file(fname
, crlf
=True, trail_white
=True):
262 """Check a single file for whitespace abuse."""
263 fname
= os
.path
.relpath(fname
)
269 with
open(fname
, "rb") as f
:
270 for n
, line
in enumerate(f
, start
=1):
273 print("%s@%d: CR found" % (fname
, n
))
278 line
= line
.rstrip('\r')
279 if line
.rstrip() != line
:
280 print("%s@%d: trailing whitespace found" % (fname
, n
))
283 if line
is not None and not line
.strip():
284 print("%s: final blank line" % (fname
,))
286 def check_files(root
, patterns
, **kwargs
):
287 """Check a number of files for whitespace abuse."""
288 for root
, dirs
, files
in os
.walk(root
):
290 fname
= os
.path
.join(root
, f
)
292 if fnmatch
.fnmatch(fname
, p
):
293 check_file(fname
, **kwargs
)
295 for ignore_dir
in ignore_dirs
:
297 for dir_name
in dirs
:
298 if fnmatch
.fnmatch(dir_name
, ignore_dir
):
299 ignored
.append(dir_name
)
300 for dir_name
in ignored
:
301 dirs
.remove(dir_name
)
303 check_files("coverage", ["*.py"])
304 check_files("coverage/ctracer", ["*.c", "*.h"])
305 check_files("coverage/htmlfiles", ["*.html", "*.css", "*.js"])
306 check_file("tests/farm/html/src/bom.py", crlf
=False)
307 check_files("tests", ["*.py"])
308 check_files("tests", ["*,cover"], trail_white
=False)
309 check_files("tests/js", ["*.js", "*.html"])
310 check_file("setup.py")
311 check_file("igor.py")
312 check_file("Makefile")
313 check_file(".hgignore")
314 check_file(".travis.yml")
315 check_files(".", ["*.rst", "*.txt"])
316 check_files(".", ["*.pip"])
319 def print_banner(label
):
320 """Print the version of Python."""
322 impl
= platform
.python_implementation()
323 except AttributeError:
326 version
= platform
.python_version()
328 if '__pypy__' in sys
.builtin_module_names
:
329 version
+= " (pypy %s)" % ".".join(str(v
) for v
in sys
.pypy_version_info
)
332 which_python
= os
.path
.relpath(sys
.executable
)
334 # On Windows having a python executable on a different drives
335 # than the sources cannot be relative.
336 which_python
= sys
.executable
337 print('=== %s %s %s (%s) ===' % (impl
, version
, label
, which_python
))
342 """List the available commands"""
343 items
= list(globals().items())
345 for name
, value
in items
:
346 if name
.startswith('do_'):
347 print("%-20s%s" % (name
[3:], value
.__doc
__))
350 def analyze_args(function
):
351 """What kind of args does `function` expect?
355 star(boolean): Does `function` accept *args?
356 num_args(int): How many positional arguments does `function` have?
359 getargspec
= inspect
.getfullargspec
360 except AttributeError:
361 getargspec
= inspect
.getargspec
362 argspec
= getargspec(function
)
363 return bool(argspec
[1]), len(argspec
[0])
367 """Main command-line execution for igor.
369 Verbs are taken from the command line, and extra words taken as directed
370 by the arguments needed by the handler.
375 handler
= globals().get('do_'+verb
)
377 print("*** No handler for %r" % verb
)
379 star
, num_args
= analyze_args(handler
)
381 # Handler has *args, give it all the rest of the command line.
385 # Handler has specific arguments, give it only what it needs.
386 handler_args
= args
[:num_args
]
387 args
= args
[num_args
:]
388 ret
= handler(*handler_args
)
389 # If a handler returns a failure-like value, stop.
394 if __name__
== '__main__':
395 sys
.exit(main(sys
.argv
[1:]))