DebugClients/Python3/coverage/cmdline.py

changeset 4489
d0d6e4ad31bd
parent 3495
fac17a82b431
child 5051
3586ebd9fac8
equal deleted inserted replaced
4481:456c58fc64b0 4489:d0d6e4ad31bd
1 """Command-line support for Coverage.""" 1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3 import optparse, os, sys, time, traceback 3
4 4 """Command-line support for coverage.py."""
5 from .backward import sorted # pylint: disable=W0622 5
6 from .execfile import run_python_file, run_python_module 6 import glob
7 from .misc import CoverageException, ExceptionDuringRun, NoSource 7 import optparse
8 from .debug import info_formatter 8 import os.path
9 import sys
10 import traceback
11
12 from coverage import env
13 from coverage.execfile import run_python_file, run_python_module
14 from coverage.misc import CoverageException, ExceptionDuringRun, NoSource
15 from coverage.debug import info_formatter, info_header
9 16
10 17
11 class Opts(object): 18 class Opts(object):
12 """A namespace class for individual options we'll build parsers from.""" 19 """A namespace class for individual options we'll build parsers from."""
13 20
14 append = optparse.make_option( 21 append = optparse.make_option(
15 '-a', '--append', action='store_false', dest="erase_first", 22 '-a', '--append', action='store_true',
16 help="Append coverage data to .coverage, otherwise it is started " 23 help="Append coverage data to .coverage, otherwise it is started "
17 "clean with each run." 24 "clean with each run."
18 ) 25 )
19 branch = optparse.make_option( 26 branch = optparse.make_option(
20 '', '--branch', action='store_true', 27 '', '--branch', action='store_true',
21 help="Measure branch coverage in addition to statement coverage." 28 help="Measure branch coverage in addition to statement coverage."
22 ) 29 )
30 CONCURRENCY_CHOICES = [
31 "thread", "gevent", "greenlet", "eventlet", "multiprocessing",
32 ]
33 concurrency = optparse.make_option(
34 '', '--concurrency', action='store', metavar="LIB",
35 choices=CONCURRENCY_CHOICES,
36 help="Properly measure code using a concurrency library. "
37 "Valid values are: %s." % ", ".join(CONCURRENCY_CHOICES)
38 )
23 debug = optparse.make_option( 39 debug = optparse.make_option(
24 '', '--debug', action='store', metavar="OPTS", 40 '', '--debug', action='store', metavar="OPTS",
25 help="Debug options, separated by commas" 41 help="Debug options, separated by commas"
26 ) 42 )
27 directory = optparse.make_option( 43 directory = optparse.make_option(
41 help="Ignore errors while reading source files." 57 help="Ignore errors while reading source files."
42 ) 58 )
43 include = optparse.make_option( 59 include = optparse.make_option(
44 '', '--include', action='store', 60 '', '--include', action='store',
45 metavar="PAT1,PAT2,...", 61 metavar="PAT1,PAT2,...",
46 help="Include files only when their filename path matches one of " 62 help="Include only files whose paths match one of these patterns. "
47 "these patterns. Usually needs quoting on the command line." 63 "Accepts shell-style wildcards, which must be quoted."
48 ) 64 )
49 pylib = optparse.make_option( 65 pylib = optparse.make_option(
50 '-L', '--pylib', action='store_true', 66 '-L', '--pylib', action='store_true',
51 help="Measure coverage even inside the Python installed library, " 67 help="Measure coverage even inside the Python installed library, "
52 "which isn't done by default." 68 "which isn't done by default."
54 show_missing = optparse.make_option( 70 show_missing = optparse.make_option(
55 '-m', '--show-missing', action='store_true', 71 '-m', '--show-missing', action='store_true',
56 help="Show line numbers of statements in each module that weren't " 72 help="Show line numbers of statements in each module that weren't "
57 "executed." 73 "executed."
58 ) 74 )
59 old_omit = optparse.make_option( 75 skip_covered = optparse.make_option(
60 '-o', '--omit', action='store', 76 '--skip-covered', action='store_true',
61 metavar="PAT1,PAT2,...", 77 help="Skip files with 100% coverage."
62 help="Omit files when their filename matches one of these patterns. "
63 "Usually needs quoting on the command line."
64 ) 78 )
65 omit = optparse.make_option( 79 omit = optparse.make_option(
66 '', '--omit', action='store', 80 '', '--omit', action='store',
67 metavar="PAT1,PAT2,...", 81 metavar="PAT1,PAT2,...",
68 help="Omit files when their filename matches one of these patterns. " 82 help="Omit files whose paths match one of these patterns. "
69 "Usually needs quoting on the command line." 83 "Accepts shell-style wildcards, which must be quoted."
70 ) 84 )
71 output_xml = optparse.make_option( 85 output_xml = optparse.make_option(
72 '-o', '', action='store', dest="outfile", 86 '-o', '', action='store', dest="outfile",
73 metavar="OUTFILE", 87 metavar="OUTFILE",
74 help="Write the XML report to this file. Defaults to 'coverage.xml'" 88 help="Write the XML report to this file. Defaults to 'coverage.xml'"
106 help="Display version information and exit." 120 help="Display version information and exit."
107 ) 121 )
108 122
109 123
110 class CoverageOptionParser(optparse.OptionParser, object): 124 class CoverageOptionParser(optparse.OptionParser, object):
111 """Base OptionParser for coverage. 125 """Base OptionParser for coverage.py.
112 126
113 Problems don't exit the program. 127 Problems don't exit the program.
114 Defaults are initialized for all options. 128 Defaults are initialized for all options.
115 129
116 """ 130 """
118 def __init__(self, *args, **kwargs): 132 def __init__(self, *args, **kwargs):
119 super(CoverageOptionParser, self).__init__( 133 super(CoverageOptionParser, self).__init__(
120 add_help_option=False, *args, **kwargs 134 add_help_option=False, *args, **kwargs
121 ) 135 )
122 self.set_defaults( 136 self.set_defaults(
123 actions=[], 137 action=None,
138 append=None,
124 branch=None, 139 branch=None,
140 concurrency=None,
125 debug=None, 141 debug=None,
126 directory=None, 142 directory=None,
127 fail_under=None, 143 fail_under=None,
128 help=None, 144 help=None,
129 ignore_errors=None, 145 ignore_errors=None,
130 include=None, 146 include=None,
147 module=None,
131 omit=None, 148 omit=None,
132 parallel_mode=None, 149 parallel_mode=None,
133 module=None,
134 pylib=None, 150 pylib=None,
135 rcfile=True, 151 rcfile=True,
136 show_missing=None, 152 show_missing=None,
153 skip_covered=None,
137 source=None, 154 source=None,
138 timid=None, 155 timid=None,
139 title=None, 156 title=None,
140 erase_first=None,
141 version=None, 157 version=None,
142 ) 158 )
143 159
144 self.disable_interspersed_args() 160 self.disable_interspersed_args()
145 self.help_fn = self.help_noop 161 self.help_fn = self.help_noop
150 166
151 class OptionParserError(Exception): 167 class OptionParserError(Exception):
152 """Used to stop the optparse error handler ending the process.""" 168 """Used to stop the optparse error handler ending the process."""
153 pass 169 pass
154 170
155 def parse_args(self, args=None, options=None): 171 def parse_args_ok(self, args=None, options=None):
156 """Call optparse.parse_args, but return a triple: 172 """Call optparse.parse_args, but return a triple:
157 173
158 (ok, options, args) 174 (ok, options, args)
159 175
160 """ 176 """
169 """Override optparse.error so sys.exit doesn't get called.""" 185 """Override optparse.error so sys.exit doesn't get called."""
170 self.help_fn(msg) 186 self.help_fn(msg)
171 raise self.OptionParserError 187 raise self.OptionParserError
172 188
173 189
174 class ClassicOptionParser(CoverageOptionParser): 190 class GlobalOptionParser(CoverageOptionParser):
175 """Command-line parser for coverage.py classic arguments.""" 191 """Command-line parser for coverage.py global option arguments."""
176 192
177 def __init__(self): 193 def __init__(self):
178 super(ClassicOptionParser, self).__init__() 194 super(GlobalOptionParser, self).__init__()
179
180 self.add_action('-a', '--annotate', 'annotate')
181 self.add_action('-b', '--html', 'html')
182 self.add_action('-c', '--combine', 'combine')
183 self.add_action('-e', '--erase', 'erase')
184 self.add_action('-r', '--report', 'report')
185 self.add_action('-x', '--execute', 'execute')
186 195
187 self.add_options([ 196 self.add_options([
188 Opts.directory,
189 Opts.help, 197 Opts.help,
190 Opts.ignore_errors,
191 Opts.pylib,
192 Opts.show_missing,
193 Opts.old_omit,
194 Opts.parallel_mode,
195 Opts.timid,
196 Opts.version, 198 Opts.version,
197 ]) 199 ])
198 200
199 def add_action(self, dash, dashdash, action_code):
200 """Add a specialized option that is the action to execute."""
201 option = self.add_option(dash, dashdash, action='callback',
202 callback=self._append_action
203 )
204 option.action_code = action_code
205
206 def _append_action(self, option, opt_unused, value_unused, parser):
207 """Callback for an option that adds to the `actions` list."""
208 parser.values.actions.append(option.action_code)
209
210 201
211 class CmdOptionParser(CoverageOptionParser): 202 class CmdOptionParser(CoverageOptionParser):
212 """Parse one of the new-style commands for coverage.py.""" 203 """Parse one of the new-style commands for coverage.py."""
213 204
214 def __init__(self, action, options=None, defaults=None, usage=None, 205 def __init__(self, action, options=None, defaults=None, usage=None,
215 cmd=None, description=None 206 description=None
216 ): 207 ):
217 """Create an OptionParser for a coverage command. 208 """Create an OptionParser for a coverage.py command.
218 209
219 `action` is the slug to put into `options.actions`. 210 `action` is the slug to put into `options.action`.
220 `options` is a list of Option's for the command. 211 `options` is a list of Option's for the command.
221 `defaults` is a dict of default value for options. 212 `defaults` is a dict of default value for options.
222 `usage` is the usage string to display in help. 213 `usage` is the usage string to display in help.
223 `cmd` is the command name, if different than `action`.
224 `description` is the description of the command, for the help text. 214 `description` is the description of the command, for the help text.
225 215
226 """ 216 """
227 if usage: 217 if usage:
228 usage = "%prog " + usage 218 usage = "%prog " + usage
229 super(CmdOptionParser, self).__init__( 219 super(CmdOptionParser, self).__init__(
230 prog="coverage %s" % (cmd or action), 220 prog="coverage %s" % action,
231 usage=usage, 221 usage=usage,
232 description=description, 222 description=description,
233 ) 223 )
234 self.set_defaults(actions=[action], **(defaults or {})) 224 self.set_defaults(action=action, **(defaults or {}))
235 if options: 225 if options:
236 self.add_options(options) 226 self.add_options(options)
237 self.cmd = cmd or action 227 self.cmd = action
238 228
239 def __eq__(self, other): 229 def __eq__(self, other):
240 # A convenience equality, so that I can put strings in unit test 230 # A convenience equality, so that I can put strings in unit test
241 # results, and they will compare equal to objects. 231 # results, and they will compare equal to objects.
242 return (other == "<CmdOptionParser:%s>" % self.cmd) 232 return (other == "<CmdOptionParser:%s>" % self.cmd)
243 233
244 GLOBAL_ARGS = [ 234 GLOBAL_ARGS = [
235 Opts.debug,
236 Opts.help,
245 Opts.rcfile, 237 Opts.rcfile,
246 Opts.help,
247 ] 238 ]
248 239
249 CMDS = { 240 CMDS = {
250 'annotate': CmdOptionParser("annotate", 241 'annotate': CmdOptionParser("annotate",
251 [ 242 [
252 Opts.directory, 243 Opts.directory,
253 Opts.ignore_errors, 244 Opts.ignore_errors,
245 Opts.include,
254 Opts.omit, 246 Opts.omit,
255 Opts.include,
256 ] + GLOBAL_ARGS, 247 ] + GLOBAL_ARGS,
257 usage = "[options] [modules]", 248 usage = "[options] [modules]",
258 description = "Make annotated copies of the given files, marking " 249 description = "Make annotated copies of the given files, marking "
259 "statements that are executed with > and statements that are " 250 "statements that are executed with > and statements that are "
260 "missed with !." 251 "missed with !."
261 ), 252 ),
262 253
263 'combine': CmdOptionParser("combine", GLOBAL_ARGS, 254 'combine': CmdOptionParser("combine", GLOBAL_ARGS,
264 usage = " ", 255 usage = "<path1> <path2> ... <pathN>",
265 description = "Combine data from multiple coverage files collected " 256 description = "Combine data from multiple coverage files collected "
266 "with 'run -p'. The combined results are written to a single " 257 "with 'run -p'. The combined results are written to a single "
267 "file representing the union of the data." 258 "file representing the union of the data. The positional "
259 "arguments are data files or directories containing data files. "
260 "If no paths are provided, data files in the default data file's "
261 "directory are combined."
268 ), 262 ),
269 263
270 'debug': CmdOptionParser("debug", GLOBAL_ARGS, 264 'debug': CmdOptionParser("debug", GLOBAL_ARGS,
271 usage = "<topic>", 265 usage = "<topic>",
272 description = "Display information on the internals of coverage.py, " 266 description = "Display information on the internals of coverage.py, "
288 'html': CmdOptionParser("html", 282 'html': CmdOptionParser("html",
289 [ 283 [
290 Opts.directory, 284 Opts.directory,
291 Opts.fail_under, 285 Opts.fail_under,
292 Opts.ignore_errors, 286 Opts.ignore_errors,
287 Opts.include,
293 Opts.omit, 288 Opts.omit,
294 Opts.include,
295 Opts.title, 289 Opts.title,
296 ] + GLOBAL_ARGS, 290 ] + GLOBAL_ARGS,
297 usage = "[options] [modules]", 291 usage = "[options] [modules]",
298 description = "Create an HTML report of the coverage of the files. " 292 description = "Create an HTML report of the coverage of the files. "
299 "Each file gets its own page, with the source decorated to show " 293 "Each file gets its own page, with the source decorated to show "
302 296
303 'report': CmdOptionParser("report", 297 'report': CmdOptionParser("report",
304 [ 298 [
305 Opts.fail_under, 299 Opts.fail_under,
306 Opts.ignore_errors, 300 Opts.ignore_errors,
301 Opts.include,
307 Opts.omit, 302 Opts.omit,
308 Opts.include,
309 Opts.show_missing, 303 Opts.show_missing,
304 Opts.skip_covered,
310 ] + GLOBAL_ARGS, 305 ] + GLOBAL_ARGS,
311 usage = "[options] [modules]", 306 usage = "[options] [modules]",
312 description = "Report coverage statistics on modules." 307 description = "Report coverage statistics on modules."
313 ), 308 ),
314 309
315 'run': CmdOptionParser("execute", 310 'run': CmdOptionParser("run",
316 [ 311 [
317 Opts.append, 312 Opts.append,
318 Opts.branch, 313 Opts.branch,
319 Opts.debug, 314 Opts.concurrency,
315 Opts.include,
316 Opts.module,
317 Opts.omit,
320 Opts.pylib, 318 Opts.pylib,
321 Opts.parallel_mode, 319 Opts.parallel_mode,
322 Opts.module, 320 Opts.source,
323 Opts.timid, 321 Opts.timid,
324 Opts.source,
325 Opts.omit,
326 Opts.include,
327 ] + GLOBAL_ARGS, 322 ] + GLOBAL_ARGS,
328 defaults = {'erase_first': True},
329 cmd = "run",
330 usage = "[options] <pyfile> [program options]", 323 usage = "[options] <pyfile> [program options]",
331 description = "Run a Python program, measuring code execution." 324 description = "Run a Python program, measuring code execution."
332 ), 325 ),
333 326
334 'xml': CmdOptionParser("xml", 327 'xml': CmdOptionParser("xml",
335 [ 328 [
336 Opts.fail_under, 329 Opts.fail_under,
337 Opts.ignore_errors, 330 Opts.ignore_errors,
331 Opts.include,
338 Opts.omit, 332 Opts.omit,
339 Opts.include,
340 Opts.output_xml, 333 Opts.output_xml,
341 ] + GLOBAL_ARGS, 334 ] + GLOBAL_ARGS,
342 cmd = "xml",
343 usage = "[options] [modules]", 335 usage = "[options] [modules]",
344 description = "Generate an XML report of coverage results." 336 description = "Generate an XML report of coverage results."
345 ), 337 ),
346 } 338 }
347 339
348 340
349 OK, ERR, FAIL_UNDER = 0, 1, 2 341 OK, ERR, FAIL_UNDER = 0, 1, 2
350 342
351 343
352 class CoverageScript(object): 344 class CoverageScript(object):
353 """The command-line interface to Coverage.""" 345 """The command-line interface to coverage.py."""
354 346
355 def __init__(self, _covpkg=None, _run_python_file=None, 347 def __init__(self, _covpkg=None, _run_python_file=None,
356 _run_python_module=None, _help_fn=None): 348 _run_python_module=None, _help_fn=None, _path_exists=None):
357 # _covpkg is for dependency injection, so we can test this code. 349 # _covpkg is for dependency injection, so we can test this code.
358 if _covpkg: 350 if _covpkg:
359 self.covpkg = _covpkg 351 self.covpkg = _covpkg
360 else: 352 else:
361 from . import coverage 353 import coverage
362 self.covpkg = coverage 354 self.covpkg = coverage
363 355
364 # For dependency injection: 356 # For dependency injection:
365 self.run_python_file = _run_python_file or run_python_file 357 self.run_python_file = _run_python_file or run_python_file
366 self.run_python_module = _run_python_module or run_python_module 358 self.run_python_module = _run_python_module or run_python_module
367 self.help_fn = _help_fn or self.help 359 self.help_fn = _help_fn or self.help
368 self.classic = False 360 self.path_exists = _path_exists or os.path.exists
361 self.global_option = False
369 362
370 self.coverage = None 363 self.coverage = None
371 364
372 def command_line(self, argv): 365 def command_line(self, argv):
373 """The bulk of the command line interface to Coverage. 366 """The bulk of the command line interface to coverage.py.
374 367
375 `argv` is the argument list to process. 368 `argv` is the argument list to process.
376 369
377 Returns 0 if all is well, 1 if something went wrong. 370 Returns 0 if all is well, 1 if something went wrong.
378 371
380 # Collect the command-line options. 373 # Collect the command-line options.
381 if not argv: 374 if not argv:
382 self.help_fn(topic='minimum_help') 375 self.help_fn(topic='minimum_help')
383 return OK 376 return OK
384 377
385 # The command syntax we parse depends on the first argument. Classic 378 # The command syntax we parse depends on the first argument. Global
386 # syntax always starts with an option. 379 # switch syntax always starts with an option.
387 self.classic = argv[0].startswith('-') 380 self.global_option = argv[0].startswith('-')
388 if self.classic: 381 if self.global_option:
389 parser = ClassicOptionParser() 382 parser = GlobalOptionParser()
390 else: 383 else:
391 parser = CMDS.get(argv[0]) 384 parser = CMDS.get(argv[0])
392 if not parser: 385 if not parser:
393 self.help_fn("Unknown command: '%s'" % argv[0]) 386 self.help_fn("Unknown command: '%s'" % argv[0])
394 return ERR 387 return ERR
395 argv = argv[1:] 388 argv = argv[1:]
396 389
397 parser.help_fn = self.help_fn 390 parser.help_fn = self.help_fn
398 ok, options, args = parser.parse_args(argv) 391 ok, options, args = parser.parse_args_ok(argv)
399 if not ok: 392 if not ok:
400 return ERR 393 return ERR
401 394
402 # Handle help and version. 395 # Handle help and version.
403 if self.do_help(options, args, parser): 396 if self.do_help(options, args, parser):
404 return OK 397 return OK
405 398
406 # Check for conflicts and problems in the options. 399 # Check for conflicts and problems in the options.
407 if not self.args_ok(options, args): 400 if not self.args_ok(options, args):
408 return ERR 401 return ERR
402
403 # We need to be able to import from the current directory, because
404 # plugins may try to, for example, to read Django settings.
405 sys.path[0] = ''
409 406
410 # Listify the list options. 407 # Listify the list options.
411 source = unshell_list(options.source) 408 source = unshell_list(options.source)
412 omit = unshell_list(options.omit) 409 omit = unshell_list(options.omit)
413 include = unshell_list(options.include) 410 include = unshell_list(options.include)
422 config_file = options.rcfile, 419 config_file = options.rcfile,
423 source = source, 420 source = source,
424 omit = omit, 421 omit = omit,
425 include = include, 422 include = include,
426 debug = debug, 423 debug = debug,
424 concurrency = options.concurrency,
427 ) 425 )
428 426
429 if 'debug' in options.actions: 427 if options.action == "debug":
430 return self.do_debug(args) 428 return self.do_debug(args)
431 429
432 if 'erase' in options.actions or options.erase_first: 430 elif options.action == "erase":
433 self.coverage.erase() 431 self.coverage.erase()
434 else: 432 return OK
433
434 elif options.action == "run":
435 return self.do_run(options, args)
436
437 elif options.action == "combine":
435 self.coverage.load() 438 self.coverage.load()
436 439 data_dirs = args or None
437 if 'execute' in options.actions: 440 self.coverage.combine(data_dirs)
438 self.do_execute(options, args)
439
440 if 'combine' in options.actions:
441 self.coverage.combine()
442 self.coverage.save() 441 self.coverage.save()
442 return OK
443 443
444 # Remaining actions are reporting, with some common options. 444 # Remaining actions are reporting, with some common options.
445 report_args = dict( 445 report_args = dict(
446 morfs = args, 446 morfs = unglob_args(args),
447 ignore_errors = options.ignore_errors, 447 ignore_errors = options.ignore_errors,
448 omit = omit, 448 omit = omit,
449 include = include, 449 include = include,
450 ) 450 )
451 451
452 if 'report' in options.actions: 452 self.coverage.load()
453
454 total = None
455 if options.action == "report":
453 total = self.coverage.report( 456 total = self.coverage.report(
454 show_missing=options.show_missing, **report_args) 457 show_missing=options.show_missing,
455 if 'annotate' in options.actions: 458 skip_covered=options.skip_covered, **report_args)
459 elif options.action == "annotate":
456 self.coverage.annotate( 460 self.coverage.annotate(
457 directory=options.directory, **report_args) 461 directory=options.directory, **report_args)
458 if 'html' in options.actions: 462 elif options.action == "html":
459 total = self.coverage.html_report( 463 total = self.coverage.html_report(
460 directory=options.directory, title=options.title, 464 directory=options.directory, title=options.title,
461 **report_args) 465 **report_args)
462 if 'xml' in options.actions: 466 elif options.action == "xml":
463 outfile = options.outfile 467 outfile = options.outfile
464 total = self.coverage.xml_report(outfile=outfile, **report_args) 468 total = self.coverage.xml_report(outfile=outfile, **report_args)
465 469
466 if options.fail_under is not None: 470 if total is not None:
467 if total >= options.fail_under: 471 # Apply the command line fail-under options, and then use the config
468 return OK 472 # value, so we can get fail_under from the config file.
469 else: 473 if options.fail_under is not None:
470 return FAIL_UNDER 474 self.coverage.set_option("report:fail_under", options.fail_under)
471 else: 475
472 return OK 476 if self.coverage.get_option("report:fail_under"):
477
478 # Total needs to be rounded, but be careful of 0 and 100.
479 if 0 < total < 1:
480 total = 1
481 elif 99 < total < 100:
482 total = 99
483 else:
484 total = round(total)
485
486 if total >= self.coverage.get_option("report:fail_under"):
487 return OK
488 else:
489 return FAIL_UNDER
490
491 return OK
473 492
474 def help(self, error=None, topic=None, parser=None): 493 def help(self, error=None, topic=None, parser=None):
475 """Display an error message, or the named topic.""" 494 """Display an error message, or the named topic."""
476 assert error or topic or parser 495 assert error or topic or parser
477 if error: 496 if error:
492 Return True if it handled the request, False if not. 511 Return True if it handled the request, False if not.
493 512
494 """ 513 """
495 # Handle help. 514 # Handle help.
496 if options.help: 515 if options.help:
497 if self.classic: 516 if self.global_option:
498 self.help_fn(topic='help') 517 self.help_fn(topic='help')
499 else: 518 else:
500 self.help_fn(parser=parser) 519 self.help_fn(parser=parser)
501 return True 520 return True
502 521
503 if "help" in options.actions: 522 if options.action == "help":
504 if args: 523 if args:
505 for a in args: 524 for a in args:
506 parser = CMDS.get(a) 525 parser = CMDS.get(a)
507 if parser: 526 if parser:
508 self.help_fn(parser=parser) 527 self.help_fn(parser=parser)
520 return False 539 return False
521 540
522 def args_ok(self, options, args): 541 def args_ok(self, options, args):
523 """Check for conflicts and problems in the options. 542 """Check for conflicts and problems in the options.
524 543
525 Returns True if everything is ok, or False if not. 544 Returns True if everything is OK, or False if not.
526 545
527 """ 546 """
528 for i in ['erase', 'execute']: 547 if options.action == "run" and not args:
529 for j in ['annotate', 'html', 'report', 'combine']:
530 if (i in options.actions) and (j in options.actions):
531 self.help_fn("You can't specify the '%s' and '%s' "
532 "options at the same time." % (i, j))
533 return False
534
535 if not options.actions:
536 self.help_fn(
537 "You must specify at least one of -e, -x, -c, -r, -a, or -b."
538 )
539 return False
540 args_allowed = (
541 'execute' in options.actions or
542 'annotate' in options.actions or
543 'html' in options.actions or
544 'debug' in options.actions or
545 'report' in options.actions or
546 'xml' in options.actions
547 )
548 if not args_allowed and args:
549 self.help_fn("Unexpected arguments: %s" % " ".join(args))
550 return False
551
552 if 'execute' in options.actions and not args:
553 self.help_fn("Nothing to do.") 548 self.help_fn("Nothing to do.")
554 return False 549 return False
555 550
556 return True 551 return True
557 552
558 def do_execute(self, options, args): 553 def do_run(self, options, args):
559 """Implementation of 'coverage run'.""" 554 """Implementation of 'coverage run'."""
560 555
561 # Set the first path element properly. 556 if options.append and self.coverage.get_option("run:parallel"):
562 old_path0 = sys.path[0] 557 self.help_fn("Can't append to data files in parallel mode.")
558 return ERR
559
560 if not self.coverage.get_option("run:parallel"):
561 if not options.append:
562 self.coverage.erase()
563 563
564 # Run the script. 564 # Run the script.
565 self.coverage.start() 565 self.coverage.start()
566 code_ran = True 566 code_ran = True
567 try: 567 try:
568 try: 568 if options.module:
569 if options.module: 569 self.run_python_module(args[0], args)
570 sys.path[0] = '' 570 else:
571 self.run_python_module(args[0], args) 571 filename = args[0]
572 else: 572 self.run_python_file(filename, args)
573 filename = args[0] 573 except NoSource:
574 sys.path[0] = os.path.abspath(os.path.dirname(filename)) 574 code_ran = False
575 self.run_python_file(filename, args) 575 raise
576 except NoSource:
577 code_ran = False
578 raise
579 finally: 576 finally:
580 self.coverage.stop() 577 self.coverage.stop()
581 if code_ran: 578 if code_ran:
579 if options.append:
580 data_file = self.coverage.get_option("run:data_file")
581 if self.path_exists(data_file):
582 self.coverage.combine(data_paths=[data_file])
582 self.coverage.save() 583 self.coverage.save()
583 584
584 # Restore the old path 585 return OK
585 sys.path[0] = old_path0
586 586
587 def do_debug(self, args): 587 def do_debug(self, args):
588 """Implementation of 'coverage debug'.""" 588 """Implementation of 'coverage debug'."""
589 589
590 if not args: 590 if not args:
591 self.help_fn("What information would you like: data, sys?") 591 self.help_fn("What information would you like: data, sys?")
592 return ERR 592 return ERR
593
593 for info in args: 594 for info in args:
594 if info == 'sys': 595 if info == 'sys':
595 print("-- sys ----------------------------------------") 596 sys_info = self.coverage.sys_info()
596 for line in info_formatter(self.coverage.sysinfo()): 597 print(info_header("sys"))
598 for line in info_formatter(sys_info):
597 print(" %s" % line) 599 print(" %s" % line)
598 elif info == 'data': 600 elif info == 'data':
599 print("-- data ---------------------------------------")
600 self.coverage.load() 601 self.coverage.load()
601 print("path: %s" % self.coverage.data.filename) 602 data = self.coverage.data
602 print("has_arcs: %r" % self.coverage.data.has_arcs()) 603 print(info_header("data"))
603 summary = self.coverage.data.summary(fullpath=True) 604 print("path: %s" % self.coverage.data_files.filename)
604 if summary: 605 if data:
606 print("has_arcs: %r" % data.has_arcs())
607 summary = data.line_counts(fullpath=True)
605 filenames = sorted(summary.keys()) 608 filenames = sorted(summary.keys())
606 print("\n%d files:" % len(filenames)) 609 print("\n%d files:" % len(filenames))
607 for f in filenames: 610 for f in filenames:
608 print("%s: %d lines" % (f, summary[f])) 611 line = "%s: %d lines" % (f, summary[f])
612 plugin = data.file_tracer(f)
613 if plugin:
614 line += " [%s]" % plugin
615 print(line)
609 else: 616 else:
610 print("No data collected") 617 print("No data collected")
611 else: 618 else:
612 self.help_fn("Don't know what you mean by %r" % info) 619 self.help_fn("Don't know what you mean by %r" % info)
613 return ERR 620 return ERR
621
614 return OK 622 return OK
615 623
616 624
617 def unshell_list(s): 625 def unshell_list(s):
618 """Turn a command-line argument into a list.""" 626 """Turn a command-line argument into a list."""
619 if not s: 627 if not s:
620 return None 628 return None
621 if sys.platform == 'win32': 629 if env.WINDOWS:
622 # When running coverage as coverage.exe, some of the behavior 630 # When running coverage.py as coverage.exe, some of the behavior
623 # of the shell is emulated: wildcards are expanded into a list of 631 # of the shell is emulated: wildcards are expanded into a list of
624 # filenames. So you have to single-quote patterns on the command 632 # file names. So you have to single-quote patterns on the command
625 # line, but (not) helpfully, the single quotes are included in the 633 # line, but (not) helpfully, the single quotes are included in the
626 # argument, so we have to strip them off here. 634 # argument, so we have to strip them off here.
627 s = s.strip("'") 635 s = s.strip("'")
628 return s.split(',') 636 return s.split(',')
629 637
630 638
639 def unglob_args(args):
640 """Interpret shell wildcards for platforms that need it."""
641 if env.WINDOWS:
642 globbed = []
643 for arg in args:
644 if '?' in arg or '*' in arg:
645 globbed.extend(glob.glob(arg))
646 else:
647 globbed.append(arg)
648 args = globbed
649 return args
650
651
631 HELP_TOPICS = { 652 HELP_TOPICS = {
632 # -------------------------
633 'classic':
634 r"""Coverage.py version %(__version__)s
635 Measure, collect, and report on code coverage in Python programs.
636
637 Usage:
638
639 coverage -x [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...]
640 Execute the module, passing the given command-line arguments, collecting
641 coverage data. With the -p option, include the machine name and process
642 id in the .coverage file name. With -L, measure coverage even inside the
643 Python installed library, which isn't done by default. With --timid, use a
644 simpler but slower trace method.
645
646 coverage -e
647 Erase collected coverage data.
648
649 coverage -c
650 Combine data from multiple coverage files (as created by -p option above)
651 and store it into a single file representing the union of the coverage.
652
653 coverage -r [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
654 Report on the statement coverage for the given files. With the -m
655 option, show line numbers of the statements that weren't executed.
656
657 coverage -b -d DIR [-i] [-o DIR,...] [FILE1 FILE2 ...]
658 Create an HTML report of the coverage of the given files. Each file gets
659 its own page, with the file listing decorated to show executed, excluded,
660 and missed lines.
661
662 coverage -a [-d DIR] [-i] [-o DIR,...] [FILE1 FILE2 ...]
663 Make annotated copies of the given files, marking statements that
664 are executed with > and statements that are missed with !.
665
666 -d DIR
667 Write output files for -b or -a to this directory.
668
669 -i Ignore errors while reporting or annotating.
670
671 -o DIR,...
672 Omit reporting or annotating files when their filename path starts with
673 a directory listed in the omit list.
674 e.g. coverage -i -r -o c:\python25,lib\enthought\traits
675
676 Coverage data is saved in the file .coverage by default. Set the
677 COVERAGE_FILE environment variable to save it somewhere else.
678 """,
679 # ------------------------- 653 # -------------------------
680 'help': """\ 654 'help': """\
681 Coverage.py, version %(__version__)s 655 Coverage.py, version %(__version__)s
682 Measure, collect, and report on code coverage in Python programs. 656 Measure, collect, and report on code coverage in Python programs.
683 657
692 report Report coverage stats on modules. 666 report Report coverage stats on modules.
693 run Run a Python program and measure code execution. 667 run Run a Python program and measure code execution.
694 xml Create an XML report of coverage results. 668 xml Create an XML report of coverage results.
695 669
696 Use "coverage help <command>" for detailed help on any command. 670 Use "coverage help <command>" for detailed help on any command.
697 Use "coverage help classic" for help on older command syntax. 671 For full documentation, see %(__url__)s
698 For more information, see %(__url__)s
699 """, 672 """,
700 # ------------------------- 673 # -------------------------
701 'minimum_help': """\ 674 'minimum_help': """\
702 Code coverage for Python. Use 'coverage help' for help. 675 Code coverage for Python. Use 'coverage help' for help.
703 """, 676 """,
704 # ------------------------- 677 # -------------------------
705 'version': """\ 678 'version': """\
706 Coverage.py, version %(__version__)s. %(__url__)s 679 Coverage.py, version %(__version__)s.
680 Documentation at %(__url__)s
707 """, 681 """,
708 } 682 }
709 683
710 684
711 def main(argv=None): 685 def main(argv=None):
712 """The main entry point to Coverage. 686 """The main entry point to coverage.py.
713 687
714 This is installed as the script entry point. 688 This is installed as the script entry point.
715 689
716 """ 690 """
717 if argv is None: 691 if argv is None:
718 argv = sys.argv[1:] 692 argv = sys.argv[1:]
719 try: 693 try:
720 start = time.clock()
721 status = CoverageScript().command_line(argv) 694 status = CoverageScript().command_line(argv)
722 end = time.clock() 695 except ExceptionDuringRun as err:
723 if 0:
724 print("time: %.3fs" % (end - start))
725 except ExceptionDuringRun:
726 # An exception was caught while running the product code. The 696 # An exception was caught while running the product code. The
727 # sys.exc_info() return tuple is packed into an ExceptionDuringRun 697 # sys.exc_info() return tuple is packed into an ExceptionDuringRun
728 # exception. 698 # exception.
729 _, err, _ = sys.exc_info()
730 traceback.print_exception(*err.args) 699 traceback.print_exception(*err.args)
731 status = ERR 700 status = ERR
732 except CoverageException: 701 except CoverageException as err:
733 # A controlled error inside coverage.py: print the message to the user. 702 # A controlled error inside coverage.py: print the message to the user.
734 _, err, _ = sys.exc_info()
735 print(err) 703 print(err)
736 status = ERR 704 status = ERR
737 except SystemExit: 705 except SystemExit as err:
738 # The user called `sys.exit()`. Exit with their argument, if any. 706 # The user called `sys.exit()`. Exit with their argument, if any.
739 _, err, _ = sys.exc_info()
740 if err.args: 707 if err.args:
741 status = err.args[0] 708 status = err.args[0]
742 else: 709 else:
743 status = None 710 status = None
744 return status 711 return status

eric ide

mercurial