|
1 """Command-line support for Coverage.""" |
|
2 |
|
3 import getopt, sys |
|
4 |
|
5 from .execfile import run_python_file |
|
6 |
|
7 USAGE = r""" |
|
8 Coverage version %(__version__)s |
|
9 Measure, collect, and report on code coverage in Python programs. |
|
10 |
|
11 Usage: |
|
12 |
|
13 coverage -x [-p] [-L] MODULE.py [ARG1 ARG2 ...] |
|
14 Execute the module, passing the given command-line arguments, collecting |
|
15 coverage data. With the -p option, include the machine name and process |
|
16 ID in the .coverage file name. With -L, measure coverage even inside the |
|
17 Python installed library, which isn't done by default. |
|
18 |
|
19 coverage -e |
|
20 Erase collected coverage data. |
|
21 |
|
22 coverage -c |
|
23 Combine data from multiple coverage files (as created by -p option above) |
|
24 and store it into a single file representing the union of the coverage. |
|
25 |
|
26 coverage -r [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...] |
|
27 Report on the statement coverage for the given files. With the -m |
|
28 option, show line numbers of the statements that weren't executed. |
|
29 |
|
30 coverage -b -d DIR [-i] [-o DIR,...] [FILE1 FILE2 ...] |
|
31 Create an HTML report of the coverage of the given files. Each file gets |
|
32 its own page, with the file listing decorated to show executed, excluded, |
|
33 and missed lines. |
|
34 |
|
35 coverage -a [-d DIR] [-i] [-o DIR,...] [FILE1 FILE2 ...] |
|
36 Make annotated copies of the given files, marking statements that |
|
37 are executed with > and statements that are missed with !. |
|
38 |
|
39 -d DIR |
|
40 Write output files for -b or -a to this directory. |
|
41 |
|
42 -i Ignore errors while reporting or annotating. |
|
43 |
|
44 -o DIR,... |
|
45 Omit reporting or annotating files when their filename path starts with |
|
46 a directory listed in the omit list. |
|
47 e.g. coverage -i -r -o c:\python25,lib\enthought\traits |
|
48 |
|
49 -h Print this help. |
|
50 |
|
51 Coverage data is saved in the file .coverage by default. Set the |
|
52 COVERAGE_FILE environment variable to save it somewhere else. |
|
53 """.strip() |
|
54 |
|
55 |
|
56 class CoverageScript: |
|
57 """The command-line interface to Coverage.""" |
|
58 |
|
59 def __init__(self): |
|
60 import coverage |
|
61 self.covpkg = coverage |
|
62 self.coverage = None |
|
63 |
|
64 def help(self, error=None): |
|
65 """Display an error message, or the usage for Coverage.""" |
|
66 if error: |
|
67 print(error) |
|
68 print("Use -h for help.") |
|
69 else: |
|
70 print((USAGE % self.covpkg.__dict__)) |
|
71 |
|
72 def command_line(self, argv, help_fn=None): |
|
73 """The bulk of the command line interface to Coverage. |
|
74 |
|
75 `argv` is the argument list to process. |
|
76 `help_fn` is the help function to use when something goes wrong. |
|
77 |
|
78 """ |
|
79 # Collect the command-line options. |
|
80 help_fn = help_fn or self.help |
|
81 OK, ERR = 0, 1 |
|
82 settings = {} |
|
83 optmap = { |
|
84 '-a': 'annotate', |
|
85 '-b': 'html', |
|
86 '-c': 'combine', |
|
87 '-d:': 'directory=', |
|
88 '-e': 'erase', |
|
89 '-h': 'help', |
|
90 '-i': 'ignore-errors', |
|
91 '-L': 'pylib', |
|
92 '-m': 'show-missing', |
|
93 '-p': 'parallel-mode', |
|
94 '-r': 'report', |
|
95 '-x': 'execute', |
|
96 '-o:': 'omit=', |
|
97 } |
|
98 short_opts = ''.join([o[1:] for o in list(optmap.keys())]) |
|
99 long_opts = list(optmap.values()) |
|
100 options, args = getopt.getopt(argv, short_opts, long_opts) |
|
101 for o, a in options: |
|
102 if o in optmap: |
|
103 settings[optmap[o]] = True |
|
104 elif o + ':' in optmap: |
|
105 settings[optmap[o + ':']] = a |
|
106 elif o[2:] in long_opts: |
|
107 settings[o[2:]] = True |
|
108 elif o[2:] + '=' in long_opts: |
|
109 settings[o[2:]+'='] = a |
|
110 |
|
111 if settings.get('help'): |
|
112 help_fn() |
|
113 return OK |
|
114 |
|
115 # Check for conflicts and problems in the options. |
|
116 for i in ['erase', 'execute']: |
|
117 for j in ['annotate', 'html', 'report', 'combine']: |
|
118 if settings.get(i) and settings.get(j): |
|
119 help_fn("You can't specify the '%s' and '%s' " |
|
120 "options at the same time." % (i, j)) |
|
121 return ERR |
|
122 |
|
123 args_needed = (settings.get('execute') |
|
124 or settings.get('annotate') |
|
125 or settings.get('html') |
|
126 or settings.get('report')) |
|
127 action = (settings.get('erase') |
|
128 or settings.get('combine') |
|
129 or args_needed) |
|
130 if not action: |
|
131 help_fn( |
|
132 "You must specify at least one of -e, -x, -c, -r, -a, or -b." |
|
133 ) |
|
134 return ERR |
|
135 if not args_needed and args: |
|
136 help_fn("Unexpected arguments: %s" % " ".join(args)) |
|
137 return ERR |
|
138 |
|
139 # Do something. |
|
140 self.coverage = self.covpkg.coverage( |
|
141 data_suffix = bool(settings.get('parallel-mode')), |
|
142 cover_pylib = settings.get('pylib') |
|
143 ) |
|
144 |
|
145 if settings.get('erase'): |
|
146 self.coverage.erase() |
|
147 else: |
|
148 self.coverage.load() |
|
149 |
|
150 if settings.get('execute'): |
|
151 if not args: |
|
152 help_fn("Nothing to do.") |
|
153 return ERR |
|
154 |
|
155 # Run the script. |
|
156 self.coverage.start() |
|
157 try: |
|
158 run_python_file(args[0], args) |
|
159 finally: |
|
160 self.coverage.stop() |
|
161 self.coverage.save() |
|
162 |
|
163 if settings.get('combine'): |
|
164 self.coverage.combine() |
|
165 self.coverage.save() |
|
166 |
|
167 # Remaining actions are reporting, with some common options. |
|
168 show_missing = settings.get('show-missing') |
|
169 directory = settings.get('directory=') |
|
170 report_args = { |
|
171 'morfs': args, |
|
172 'ignore_errors': settings.get('ignore-errors'), |
|
173 } |
|
174 |
|
175 omit = settings.get('omit=') |
|
176 if omit: |
|
177 omit = omit.split(',') |
|
178 report_args['omit_prefixes'] = omit |
|
179 |
|
180 if settings.get('report'): |
|
181 self.coverage.report(show_missing=show_missing, **report_args) |
|
182 if settings.get('annotate'): |
|
183 self.coverage.annotate(directory=directory, **report_args) |
|
184 if settings.get('html'): |
|
185 self.coverage.html_report(directory=directory, **report_args) |
|
186 |
|
187 return OK |
|
188 |
|
189 |
|
190 def main(): |
|
191 """The main entrypoint to Coverage. |
|
192 |
|
193 This is installed as the script entrypoint. |
|
194 |
|
195 """ |
|
196 return CoverageScript().command_line(sys.argv[1:]) |