1 """Core control stuff for Coverage.""" |
1 """Core control stuff for Coverage.""" |
2 |
2 |
3 import atexit, os, socket |
3 import atexit, os, random, socket, sys |
4 |
4 |
5 from .annotate import AnnotateReporter |
5 from .annotate import AnnotateReporter |
6 from .backward import string_class # pylint: disable-msg=W0622 |
6 from .backward import string_class, iitems, sorted # pylint: disable=W0622 |
7 from .codeunit import code_unit_factory, CodeUnit |
7 from .codeunit import code_unit_factory, CodeUnit |
8 from .collector import Collector |
8 from .collector import Collector |
|
9 from .config import CoverageConfig |
9 from .data import CoverageData |
10 from .data import CoverageData |
10 from .files import FileLocator |
11 from .debug import DebugControl |
11 from .html import HtmlReporter |
12 from .files import FileLocator, TreeMatcher, FnmatchMatcher |
12 from .results import Analysis |
13 from .files import PathAliases, find_python_files, prep_patterns |
|
14 #from .html import HtmlReporter # Comment for eric5 |
|
15 from .misc import CoverageException, bool_or_none, join_regex |
|
16 from .misc import file_be_gone |
|
17 from .results import Analysis, Numbers |
13 from .summary import SummaryReporter |
18 from .summary import SummaryReporter |
14 from .xmlreport import XmlReporter |
19 from .xmlreport import XmlReporter |
15 |
20 |
|
21 # Pypy has some unusual stuff in the "stdlib". Consider those locations |
|
22 # when deciding where the stdlib is. |
|
23 try: |
|
24 import _structseq # pylint: disable=F0401 |
|
25 except ImportError: |
|
26 _structseq = None |
|
27 |
|
28 |
16 class coverage(object): |
29 class coverage(object): |
17 """Programmatic access to Coverage. |
30 """Programmatic access to coverage.py. |
18 |
31 |
19 To use:: |
32 To use:: |
20 |
33 |
21 from . import coverage |
34 from . import coverage |
22 |
35 |
23 cov = coverage() |
36 cov = coverage() |
24 cov.start() |
37 cov.start() |
25 #.. blah blah (run your code) blah blah .. |
38 #.. call your code .. |
26 cov.stop() |
39 cov.stop() |
27 cov.html_report(directory='covhtml') |
40 cov.html_report(directory='covhtml') |
28 |
41 |
29 """ |
42 """ |
30 |
43 def __init__(self, data_file=None, data_suffix=None, cover_pylib=None, |
31 def __init__(self, data_file=None, data_suffix=False, cover_pylib=False, |
44 auto_data=False, timid=None, branch=None, config_file=True, |
32 auto_data=False, timid=False, branch=False): |
45 source=None, omit=None, include=None, debug=None, |
|
46 debug_file=None): |
33 """ |
47 """ |
34 `data_file` is the base name of the data file to use, defaulting to |
48 `data_file` is the base name of the data file to use, defaulting to |
35 ".coverage". `data_suffix` is appended to `data_file` to create the |
49 ".coverage". `data_suffix` is appended (with a dot) to `data_file` to |
36 final file name. If `data_suffix` is simply True, then a suffix is |
50 create the final file name. If `data_suffix` is simply True, then a |
37 created with the machine and process identity included. |
51 suffix is created with the machine and process identity included. |
38 |
52 |
39 `cover_pylib` is a boolean determining whether Python code installed |
53 `cover_pylib` is a boolean determining whether Python code installed |
40 with the Python interpreter is measured. This includes the Python |
54 with the Python interpreter is measured. This includes the Python |
41 standard library and any packages installed with the interpreter. |
55 standard library and any packages installed with the interpreter. |
42 |
56 |
49 tracing functions breaks the faster trace function. |
63 tracing functions breaks the faster trace function. |
50 |
64 |
51 If `branch` is true, then branch coverage will be measured in addition |
65 If `branch` is true, then branch coverage will be measured in addition |
52 to the usual statement coverage. |
66 to the usual statement coverage. |
53 |
67 |
|
68 `config_file` determines what config file to read. If it is a string, |
|
69 it is the name of the config file to read. If it is True, then a |
|
70 standard file is read (".coveragerc"). If it is False, then no file is |
|
71 read. |
|
72 |
|
73 `source` is a list of file paths or package names. Only code located |
|
74 in the trees indicated by the file paths or package names will be |
|
75 measured. |
|
76 |
|
77 `include` and `omit` are lists of filename patterns. Files that match |
|
78 `include` will be measured, files that match `omit` will not. Each |
|
79 will also accept a single string argument. |
|
80 |
|
81 `debug` is a list of strings indicating what debugging information is |
|
82 desired. `debug_file` is the file to write debug messages to, |
|
83 defaulting to stderr. |
|
84 |
54 """ |
85 """ |
55 from . import __version__ |
86 from . import __version__ |
56 |
87 |
57 self.cover_pylib = cover_pylib |
88 # A record of all the warnings that have been issued. |
|
89 self._warnings = [] |
|
90 |
|
91 # Build our configuration from a number of sources: |
|
92 # 1: defaults: |
|
93 self.config = CoverageConfig() |
|
94 |
|
95 # 2: from the coveragerc file: |
|
96 if config_file: |
|
97 if config_file is True: |
|
98 config_file = ".coveragerc" |
|
99 try: |
|
100 self.config.from_file(config_file) |
|
101 except ValueError: |
|
102 _, err, _ = sys.exc_info() |
|
103 raise CoverageException( |
|
104 "Couldn't read config file %s: %s" % (config_file, err) |
|
105 ) |
|
106 |
|
107 # 3: from environment variables: |
|
108 self.config.from_environment('COVERAGE_OPTIONS') |
|
109 env_data_file = os.environ.get('COVERAGE_FILE') |
|
110 if env_data_file: |
|
111 self.config.data_file = env_data_file |
|
112 |
|
113 # 4: from constructor arguments: |
|
114 self.config.from_args( |
|
115 data_file=data_file, cover_pylib=cover_pylib, timid=timid, |
|
116 branch=branch, parallel=bool_or_none(data_suffix), |
|
117 source=source, omit=omit, include=include, debug=debug, |
|
118 ) |
|
119 |
|
120 # Create and configure the debugging controller. |
|
121 self.debug = DebugControl(self.config.debug, debug_file or sys.stderr) |
|
122 |
58 self.auto_data = auto_data |
123 self.auto_data = auto_data |
59 self.atexit_registered = False |
124 |
60 |
125 # _exclude_re is a dict mapping exclusion list names to compiled |
61 self.exclude_re = "" |
126 # regexes. |
62 self.exclude_list = [] |
127 self._exclude_re = {} |
|
128 self._exclude_regex_stale() |
63 |
129 |
64 self.file_locator = FileLocator() |
130 self.file_locator = FileLocator() |
65 |
131 |
66 # Timidity: for nose users, read an environment variable. This is a |
132 # The source argument can be directories or package names. |
67 # cheap hack, since the rest of the command line arguments aren't |
133 self.source = [] |
68 # recognized, but it solves some users' problems. |
134 self.source_pkgs = [] |
69 timid = timid or ('--timid' in os.environ.get('COVERAGE_OPTIONS', '')) |
135 for src in self.config.source or []: |
|
136 if os.path.exists(src): |
|
137 self.source.append(self.file_locator.canonical_filename(src)) |
|
138 else: |
|
139 self.source_pkgs.append(src) |
|
140 |
|
141 self.omit = prep_patterns(self.config.omit) |
|
142 self.include = prep_patterns(self.config.include) |
|
143 |
70 self.collector = Collector( |
144 self.collector = Collector( |
71 self._should_trace, timid=timid, branch=branch |
145 self._should_trace, timid=self.config.timid, |
|
146 branch=self.config.branch, warn=self._warn |
72 ) |
147 ) |
73 |
148 |
74 # Create the data file. |
149 # Suffixes are a bit tricky. We want to use the data suffix only when |
75 if data_suffix: |
150 # collecting data, not when combining data. So we save it as |
|
151 # `self.run_suffix` now, and promote it to `self.data_suffix` if we |
|
152 # find that we are collecting data later. |
|
153 if data_suffix or self.config.parallel: |
76 if not isinstance(data_suffix, string_class): |
154 if not isinstance(data_suffix, string_class): |
77 # if data_suffix=True, use .machinename.pid |
155 # if data_suffix=True, use .machinename.pid.random |
78 data_suffix = ".%s.%s" % (socket.gethostname(), os.getpid()) |
156 data_suffix = True |
79 else: |
157 else: |
80 data_suffix = None |
158 data_suffix = None |
81 |
159 self.data_suffix = None |
|
160 self.run_suffix = data_suffix |
|
161 |
|
162 # Create the data file. We do this at construction time so that the |
|
163 # data file will be written into the directory where the process |
|
164 # started rather than wherever the process eventually chdir'd to. |
82 self.data = CoverageData( |
165 self.data = CoverageData( |
83 basename=data_file, suffix=data_suffix, |
166 basename=self.config.data_file, |
84 collector="coverage v%s" % __version__ |
167 collector="coverage v%s" % __version__, |
|
168 debug=self.debug, |
85 ) |
169 ) |
86 |
170 |
87 # The default exclude pattern. |
171 # The dirs for files considered "installed with the interpreter". |
88 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]') |
172 self.pylib_dirs = [] |
89 |
173 if not self.config.cover_pylib: |
90 # The prefix for files considered "installed with the interpreter". |
174 # Look at where some standard modules are located. That's the |
91 if not self.cover_pylib: |
175 # indication for "installed with the interpreter". In some |
92 # Look at where the "os" module is located. That's the indication |
176 # environments (virtualenv, for example), these modules may be |
93 # for "installed with the interpreter". |
177 # spread across a few locations. Look at all the candidate modules |
94 os_file = self.file_locator.canonical_filename(os.__file__) |
178 # we've imported, and take all the different ones. |
95 self.pylib_prefix = os.path.split(os_file)[0] |
179 for m in (atexit, os, random, socket, _structseq): |
|
180 if m is not None and hasattr(m, "__file__"): |
|
181 m_dir = self._canonical_dir(m) |
|
182 if m_dir not in self.pylib_dirs: |
|
183 self.pylib_dirs.append(m_dir) |
96 |
184 |
97 # To avoid tracing the coverage code itself, we skip anything located |
185 # To avoid tracing the coverage code itself, we skip anything located |
98 # where we are. |
186 # where we are. |
99 here = self.file_locator.canonical_filename(__file__) |
187 self.cover_dir = self._canonical_dir(__file__) |
100 self.cover_prefix = os.path.split(here)[0] |
188 |
101 |
189 # The matchers for _should_trace. |
102 def _should_trace(self, filename, frame): |
190 self.source_match = None |
103 """Decide whether to trace execution in `filename` |
191 self.pylib_match = self.cover_match = None |
|
192 self.include_match = self.omit_match = None |
|
193 |
|
194 # Set the reporting precision. |
|
195 Numbers.set_precision(self.config.precision) |
|
196 |
|
197 # Is it ok for no data to be collected? |
|
198 self._warn_no_data = True |
|
199 self._warn_unimported_source = True |
|
200 |
|
201 # State machine variables: |
|
202 # Have we started collecting and not stopped it? |
|
203 self._started = False |
|
204 # Have we measured some data and not harvested it? |
|
205 self._measured = False |
|
206 |
|
207 atexit.register(self._atexit) |
|
208 |
|
209 def _canonical_dir(self, morf): |
|
210 """Return the canonical directory of the module or file `morf`.""" |
|
211 return os.path.split(CodeUnit(morf, self.file_locator).filename)[0] |
|
212 |
|
213 def _source_for_file(self, filename): |
|
214 """Return the source file for `filename`.""" |
|
215 if not filename.endswith(".py"): |
|
216 if filename.endswith((".pyc", ".pyo")): |
|
217 filename = filename[:-1] |
|
218 elif filename.endswith("$py.class"): # jython |
|
219 filename = filename[:-9] + ".py" |
|
220 return filename |
|
221 |
|
222 def _should_trace_with_reason(self, filename, frame): |
|
223 """Decide whether to trace execution in `filename`, with a reason. |
104 |
224 |
105 This function is called from the trace function. As each new file name |
225 This function is called from the trace function. As each new file name |
106 is encountered, this function determines whether it is traced or not. |
226 is encountered, this function determines whether it is traced or not. |
107 |
227 |
108 Returns a canonicalized filename if it should be traced, False if it |
228 Returns a pair of values: the first indicates whether the file should |
109 should not. |
229 be traced: it's a canonicalized filename if it should be traced, None |
110 |
230 if it should not. The second value is a string, the resason for the |
111 """ |
231 decision. |
112 if filename == '<string>': |
232 |
113 # There's no point in ever tracing string executions, we can't do |
233 """ |
114 # anything with the data later anyway. |
234 if not filename: |
115 return False |
235 # Empty string is pretty useless |
|
236 return None, "empty string isn't a filename" |
|
237 |
|
238 if filename.startswith('<'): |
|
239 # Lots of non-file execution is represented with artificial |
|
240 # filenames like "<string>", "<doctest readme.txt[0]>", or |
|
241 # "<exec_function>". Don't ever trace these executions, since we |
|
242 # can't do anything with the data later anyway. |
|
243 return None, "not a real filename" |
|
244 |
|
245 self._check_for_packages() |
116 |
246 |
117 # Compiled Python files have two filenames: frame.f_code.co_filename is |
247 # Compiled Python files have two filenames: frame.f_code.co_filename is |
118 # the filename at the time the .pyc was compiled. The second name |
248 # the filename at the time the .pyc was compiled. The second name is |
119 # is __file__, which is where the .pyc was actually loaded from. Since |
249 # __file__, which is where the .pyc was actually loaded from. Since |
120 # .pyc files can be moved after compilation (for example, by being |
250 # .pyc files can be moved after compilation (for example, by being |
121 # installed), we look for __file__ in the frame and prefer it to the |
251 # installed), we look for __file__ in the frame and prefer it to the |
122 # co_filename value. |
252 # co_filename value. |
123 dunder_file = frame.f_globals.get('__file__') |
253 dunder_file = frame.f_globals.get('__file__') |
124 if dunder_file: |
254 if dunder_file: |
125 if not dunder_file.endswith(".py"): |
255 filename = self._source_for_file(dunder_file) |
126 if dunder_file[-4:-1] == ".py": |
256 |
127 dunder_file = dunder_file[:-1] |
257 # Jython reports the .class file to the tracer, use the source file. |
128 filename = dunder_file |
258 if filename.endswith("$py.class"): |
|
259 filename = filename[:-9] + ".py" |
129 |
260 |
130 canonical = self.file_locator.canonical_filename(filename) |
261 canonical = self.file_locator.canonical_filename(filename) |
131 |
262 |
132 # If we aren't supposed to trace installed code, then check if this is |
263 # If the user specified source or include, then that's authoritative |
133 # near the Python standard library and skip it if so. |
264 # about the outer bound of what to measure and we don't have to apply |
134 if not self.cover_pylib: |
265 # any canned exclusions. If they didn't, then we have to exclude the |
135 if canonical.startswith(self.pylib_prefix): |
266 # stdlib and coverage.py directories. |
136 return False |
267 if self.source_match: |
137 |
268 if not self.source_match.match(canonical): |
138 # We exclude the coverage code itself, since a little of it will be |
269 return None, "falls outside the --source trees" |
139 # measured otherwise. |
270 elif self.include_match: |
140 if canonical.startswith(self.cover_prefix): |
271 if not self.include_match.match(canonical): |
141 return False |
272 return None, "falls outside the --include trees" |
142 |
273 else: |
|
274 # If we aren't supposed to trace installed code, then check if this |
|
275 # is near the Python standard library and skip it if so. |
|
276 if self.pylib_match and self.pylib_match.match(canonical): |
|
277 return None, "is in the stdlib" |
|
278 |
|
279 # We exclude the coverage code itself, since a little of it will be |
|
280 # measured otherwise. |
|
281 if self.cover_match and self.cover_match.match(canonical): |
|
282 return None, "is part of coverage.py" |
|
283 |
|
284 # Check the file against the omit pattern. |
|
285 if self.omit_match and self.omit_match.match(canonical): |
|
286 return None, "is inside an --omit pattern" |
|
287 |
|
288 return canonical, "because we love you" |
|
289 |
|
290 def _should_trace(self, filename, frame): |
|
291 """Decide whether to trace execution in `filename`. |
|
292 |
|
293 Calls `_should_trace_with_reason`, and returns just the decision. |
|
294 |
|
295 """ |
|
296 canonical, reason = self._should_trace_with_reason(filename, frame) |
|
297 if self.debug.should('trace'): |
|
298 if not canonical: |
|
299 msg = "Not tracing %r: %s" % (filename, reason) |
|
300 else: |
|
301 msg = "Tracing %r" % (filename,) |
|
302 self.debug.write(msg) |
143 return canonical |
303 return canonical |
144 |
304 |
145 # To log what should_trace returns, change this to "if 1:" |
305 def _warn(self, msg): |
146 if 0: |
306 """Use `msg` as a warning.""" |
147 _real_should_trace = _should_trace |
307 self._warnings.append(msg) |
148 def _should_trace(self, filename, frame): # pylint: disable-msg=E0102 |
308 sys.stderr.write("Coverage.py warning: %s\n" % msg) |
149 """A logging decorator around the real _should_trace function.""" |
309 |
150 ret = self._real_should_trace(filename, frame) |
310 def _check_for_packages(self): |
151 print("should_trace: %r -> %r" % (filename, ret)) |
311 """Update the source_match matcher with latest imported packages.""" |
152 return ret |
312 # Our self.source_pkgs attribute is a list of package names we want to |
|
313 # measure. Each time through here, we see if we've imported any of |
|
314 # them yet. If so, we add its file to source_match, and we don't have |
|
315 # to look for that package any more. |
|
316 if self.source_pkgs: |
|
317 found = [] |
|
318 for pkg in self.source_pkgs: |
|
319 try: |
|
320 mod = sys.modules[pkg] |
|
321 except KeyError: |
|
322 continue |
|
323 |
|
324 found.append(pkg) |
|
325 |
|
326 try: |
|
327 pkg_file = mod.__file__ |
|
328 except AttributeError: |
|
329 pkg_file = None |
|
330 else: |
|
331 d, f = os.path.split(pkg_file) |
|
332 if f.startswith('__init__'): |
|
333 # This is actually a package, return the directory. |
|
334 pkg_file = d |
|
335 else: |
|
336 pkg_file = self._source_for_file(pkg_file) |
|
337 pkg_file = self.file_locator.canonical_filename(pkg_file) |
|
338 if not os.path.exists(pkg_file): |
|
339 pkg_file = None |
|
340 |
|
341 if pkg_file: |
|
342 self.source.append(pkg_file) |
|
343 self.source_match.add(pkg_file) |
|
344 else: |
|
345 self._warn("Module %s has no Python source." % pkg) |
|
346 |
|
347 for pkg in found: |
|
348 self.source_pkgs.remove(pkg) |
153 |
349 |
154 def use_cache(self, usecache): |
350 def use_cache(self, usecache): |
155 """Control the use of a data file (incorrectly called a cache). |
351 """Control the use of a data file (incorrectly called a cache). |
156 |
352 |
157 `usecache` is true or false, whether to read and write data on disk. |
353 `usecache` is true or false, whether to read and write data on disk. |
187 |
424 |
188 """ |
425 """ |
189 self.collector.reset() |
426 self.collector.reset() |
190 self.data.erase() |
427 self.data.erase() |
191 |
428 |
192 def clear_exclude(self): |
429 def clear_exclude(self, which='exclude'): |
193 """Clear the exclude list.""" |
430 """Clear the exclude list.""" |
194 self.exclude_list = [] |
431 setattr(self.config, which + "_list", []) |
195 self.exclude_re = "" |
432 self._exclude_regex_stale() |
196 |
433 |
197 def exclude(self, regex): |
434 def exclude(self, regex, which='exclude'): |
198 """Exclude source lines from execution consideration. |
435 """Exclude source lines from execution consideration. |
199 |
436 |
200 `regex` is a regular expression. Lines matching this expression are |
437 A number of lists of regular expressions are maintained. Each list |
201 not considered executable when reporting code coverage. A list of |
438 selects lines that are treated differently during reporting. |
202 regexes is maintained; this function adds a new regex to the list. |
439 |
203 Matching any of the regexes excludes a source line. |
440 `which` determines which list is modified. The "exclude" list selects |
204 |
441 lines that are not considered executable at all. The "partial" list |
205 """ |
442 indicates lines with branches that are not taken. |
206 self.exclude_list.append(regex) |
443 |
207 self.exclude_re = "(" + ")|(".join(self.exclude_list) + ")" |
444 `regex` is a regular expression. The regex is added to the specified |
208 |
445 list. If any of the regexes in the list is found in a line, the line |
209 def get_exclude_list(self): |
446 is marked for special treatment during reporting. |
210 """Return the list of excluded regex patterns.""" |
447 |
211 return self.exclude_list |
448 """ |
|
449 excl_list = getattr(self.config, which + "_list") |
|
450 excl_list.append(regex) |
|
451 self._exclude_regex_stale() |
|
452 |
|
453 def _exclude_regex_stale(self): |
|
454 """Drop all the compiled exclusion regexes, a list was modified.""" |
|
455 self._exclude_re.clear() |
|
456 |
|
457 def _exclude_regex(self, which): |
|
458 """Return a compiled regex for the given exclusion list.""" |
|
459 if which not in self._exclude_re: |
|
460 excl_list = getattr(self.config, which + "_list") |
|
461 self._exclude_re[which] = join_regex(excl_list) |
|
462 return self._exclude_re[which] |
|
463 |
|
464 def get_exclude_list(self, which='exclude'): |
|
465 """Return a list of excluded regex patterns. |
|
466 |
|
467 `which` indicates which list is desired. See `exclude` for the lists |
|
468 that are available, and their meaning. |
|
469 |
|
470 """ |
|
471 return getattr(self.config, which + "_list") |
212 |
472 |
213 def save(self): |
473 def save(self): |
214 """Save the collected coverage data to the data file.""" |
474 """Save the collected coverage data to the data file.""" |
|
475 data_suffix = self.data_suffix |
|
476 if data_suffix is True: |
|
477 # If data_suffix was a simple true value, then make a suffix with |
|
478 # plenty of distinguishing information. We do this here in |
|
479 # `save()` at the last minute so that the pid will be correct even |
|
480 # if the process forks. |
|
481 extra = "" |
|
482 if _TEST_NAME_FILE: |
|
483 f = open(_TEST_NAME_FILE) |
|
484 test_name = f.read() |
|
485 f.close() |
|
486 extra = "." + test_name |
|
487 data_suffix = "%s%s.%s.%06d" % ( |
|
488 socket.gethostname(), extra, os.getpid(), |
|
489 random.randint(0, 999999) |
|
490 ) |
|
491 |
215 self._harvest_data() |
492 self._harvest_data() |
216 self.data.write() |
493 self.data.write(suffix=data_suffix) |
217 |
494 |
218 def combine(self): |
495 def combine(self): |
219 """Combine together a number of similarly-named coverage data files. |
496 """Combine together a number of similarly-named coverage data files. |
220 |
497 |
221 All coverage data files whose name starts with `data_file` (from the |
498 All coverage data files whose name starts with `data_file` (from the |
222 coverage() constructor) will be read, and combined together into the |
499 coverage() constructor) will be read, and combined together into the |
223 current measurements. |
500 current measurements. |
224 |
501 |
225 """ |
502 """ |
226 self.data.combine_parallel_data() |
503 aliases = None |
|
504 if self.config.paths: |
|
505 aliases = PathAliases(self.file_locator) |
|
506 for paths in self.config.paths.values(): |
|
507 result = paths[0] |
|
508 for pattern in paths[1:]: |
|
509 aliases.add(pattern, result) |
|
510 self.data.combine_parallel_data(aliases=aliases) |
227 |
511 |
228 def _harvest_data(self): |
512 def _harvest_data(self): |
229 """Get the collected data and reset the collector.""" |
513 """Get the collected data and reset the collector. |
|
514 |
|
515 Also warn about various problems collecting data. |
|
516 |
|
517 """ |
|
518 if not self._measured: |
|
519 return |
|
520 |
230 self.data.add_line_data(self.collector.get_line_data()) |
521 self.data.add_line_data(self.collector.get_line_data()) |
231 self.data.add_arc_data(self.collector.get_arc_data()) |
522 self.data.add_arc_data(self.collector.get_arc_data()) |
232 self.collector.reset() |
523 self.collector.reset() |
|
524 |
|
525 # If there are still entries in the source_pkgs list, then we never |
|
526 # encountered those packages. |
|
527 if self._warn_unimported_source: |
|
528 for pkg in self.source_pkgs: |
|
529 self._warn("Module %s was never imported." % pkg) |
|
530 |
|
531 # Find out if we got any data. |
|
532 summary = self.data.summary() |
|
533 if not summary and self._warn_no_data: |
|
534 self._warn("No data was collected.") |
|
535 |
|
536 # Find files that were never executed at all. |
|
537 for src in self.source: |
|
538 for py_file in find_python_files(src): |
|
539 py_file = self.file_locator.canonical_filename(py_file) |
|
540 |
|
541 if self.omit_match and self.omit_match.match(py_file): |
|
542 # Turns out this file was omitted, so don't pull it back |
|
543 # in as unexecuted. |
|
544 continue |
|
545 |
|
546 self.data.touch_file(py_file) |
|
547 |
|
548 self._measured = False |
233 |
549 |
234 # Backward compatibility with version 1. |
550 # Backward compatibility with version 1. |
235 def analysis(self, morf): |
551 def analysis(self, morf): |
236 """Like `analysis2` but doesn't return excluded line numbers.""" |
552 """Like `analysis2` but doesn't return excluded line numbers.""" |
237 f, s, _, m, mf = self.analysis2(morf) |
553 f, s, _, m, mf = self.analysis2(morf) |
254 coverage data. |
570 coverage data. |
255 |
571 |
256 """ |
572 """ |
257 analysis = self._analyze(morf) |
573 analysis = self._analyze(morf) |
258 return ( |
574 return ( |
259 analysis.filename, analysis.statements, analysis.excluded, |
575 analysis.filename, |
260 analysis.missing, analysis.missing_formatted() |
576 sorted(analysis.statements), |
|
577 sorted(analysis.excluded), |
|
578 sorted(analysis.missing), |
|
579 analysis.missing_formatted(), |
261 ) |
580 ) |
262 |
581 |
263 def _analyze(self, it): |
582 def _analyze(self, it): |
264 """Analyze a single morf or code unit. |
583 """Analyze a single morf or code unit. |
265 |
584 |
266 Returns an `Analysis` object. |
585 Returns an `Analysis` object. |
267 |
586 |
268 """ |
587 """ |
|
588 self._harvest_data() |
269 if not isinstance(it, CodeUnit): |
589 if not isinstance(it, CodeUnit): |
270 it = code_unit_factory(it, self.file_locator)[0] |
590 it = code_unit_factory(it, self.file_locator)[0] |
271 |
591 |
272 return Analysis(self, it) |
592 return Analysis(self, it) |
273 |
593 |
274 def report(self, morfs=None, show_missing=True, ignore_errors=False, |
594 def report(self, morfs=None, show_missing=True, ignore_errors=None, |
275 file=None, omit_prefixes=None): # pylint: disable-msg=W0622 |
595 file=None, # pylint: disable=W0622 |
|
596 omit=None, include=None |
|
597 ): |
276 """Write a summary report to `file`. |
598 """Write a summary report to `file`. |
277 |
599 |
278 Each module in `morfs` is listed, with counts of statements, executed |
600 Each module in `morfs` is listed, with counts of statements, executed |
279 statements, missing statements, and a list of lines missed. |
601 statements, missing statements, and a list of lines missed. |
280 |
602 |
281 """ |
603 `include` is a list of filename patterns. Modules whose filenames |
282 reporter = SummaryReporter(self, show_missing, ignore_errors) |
604 match those patterns will be included in the report. Modules matching |
283 reporter.report(morfs, outfile=file, omit_prefixes=omit_prefixes) |
605 `omit` will not be included in the report. |
284 |
606 |
285 def annotate(self, morfs=None, directory=None, ignore_errors=False, |
607 Returns a float, the total percentage covered. |
286 omit_prefixes=None): |
608 |
|
609 """ |
|
610 self._harvest_data() |
|
611 self.config.from_args( |
|
612 ignore_errors=ignore_errors, omit=omit, include=include, |
|
613 show_missing=show_missing, |
|
614 ) |
|
615 reporter = SummaryReporter(self, self.config) |
|
616 return reporter.report(morfs, outfile=file) |
|
617 |
|
618 def annotate(self, morfs=None, directory=None, ignore_errors=None, |
|
619 omit=None, include=None): |
287 """Annotate a list of modules. |
620 """Annotate a list of modules. |
288 |
621 |
289 Each module in `morfs` is annotated. The source is written to a new |
622 Each module in `morfs` is annotated. The source is written to a new |
290 file, named with a ",cover" suffix, with each line prefixed with a |
623 file, named with a ",cover" suffix, with each line prefixed with a |
291 marker to indicate the coverage of the line. Covered lines have ">", |
624 marker to indicate the coverage of the line. Covered lines have ">", |
292 excluded lines have "-", and missing lines have "!". |
625 excluded lines have "-", and missing lines have "!". |
293 |
626 |
294 """ |
627 See `coverage.report()` for other arguments. |
295 reporter = AnnotateReporter(self, ignore_errors) |
628 |
296 reporter.report( |
629 """ |
297 morfs, directory=directory, omit_prefixes=omit_prefixes) |
630 self._harvest_data() |
298 |
631 self.config.from_args( |
299 def html_report(self, morfs=None, directory=None, ignore_errors=False, |
632 ignore_errors=ignore_errors, omit=omit, include=include |
300 omit_prefixes=None): |
633 ) |
|
634 reporter = AnnotateReporter(self, self.config) |
|
635 reporter.report(morfs, directory=directory) |
|
636 |
|
637 def html_report(self, morfs=None, directory=None, ignore_errors=None, |
|
638 omit=None, include=None, extra_css=None, title=None): |
301 """Generate an HTML report. |
639 """Generate an HTML report. |
302 |
640 |
303 """ |
641 The HTML is written to `directory`. The file "index.html" is the |
304 reporter = HtmlReporter(self, ignore_errors) |
642 overview starting point, with links to more detailed pages for |
305 reporter.report( |
643 individual modules. |
306 morfs, directory=directory, omit_prefixes=omit_prefixes) |
644 |
307 |
645 `extra_css` is a path to a file of other CSS to apply on the page. |
308 def xml_report(self, morfs=None, outfile=None, ignore_errors=False, |
646 It will be copied into the HTML directory. |
309 omit_prefixes=None): |
647 |
|
648 `title` is a text string (not HTML) to use as the title of the HTML |
|
649 report. |
|
650 |
|
651 See `coverage.report()` for other arguments. |
|
652 |
|
653 Returns a float, the total percentage covered. |
|
654 |
|
655 """ |
|
656 self._harvest_data() |
|
657 self.config.from_args( |
|
658 ignore_errors=ignore_errors, omit=omit, include=include, |
|
659 html_dir=directory, extra_css=extra_css, html_title=title, |
|
660 ) |
|
661 reporter = HtmlReporter(self, self.config) |
|
662 return reporter.report(morfs) |
|
663 |
|
664 def xml_report(self, morfs=None, outfile=None, ignore_errors=None, |
|
665 omit=None, include=None): |
310 """Generate an XML report of coverage results. |
666 """Generate an XML report of coverage results. |
311 |
667 |
312 The report is compatible with Cobertura reports. |
668 The report is compatible with Cobertura reports. |
313 |
669 |
314 """ |
670 Each module in `morfs` is included in the report. `outfile` is the |
315 if outfile: |
671 path to write the file to, "-" will write to stdout. |
316 outfile = open(outfile, "w") |
672 |
|
673 See `coverage.report()` for other arguments. |
|
674 |
|
675 Returns a float, the total percentage covered. |
|
676 |
|
677 """ |
|
678 self._harvest_data() |
|
679 self.config.from_args( |
|
680 ignore_errors=ignore_errors, omit=omit, include=include, |
|
681 xml_output=outfile, |
|
682 ) |
|
683 file_to_close = None |
|
684 delete_file = False |
|
685 if self.config.xml_output: |
|
686 if self.config.xml_output == '-': |
|
687 outfile = sys.stdout |
|
688 else: |
|
689 outfile = open(self.config.xml_output, "w") |
|
690 file_to_close = outfile |
317 try: |
691 try: |
318 reporter = XmlReporter(self, ignore_errors) |
692 try: |
319 reporter.report( |
693 reporter = XmlReporter(self, self.config) |
320 morfs, omit_prefixes=omit_prefixes, outfile=outfile) |
694 return reporter.report(morfs, outfile=outfile) |
|
695 except CoverageException: |
|
696 delete_file = True |
|
697 raise |
321 finally: |
698 finally: |
322 outfile.close() |
699 if file_to_close: |
|
700 file_to_close.close() |
|
701 if delete_file: |
|
702 file_be_gone(self.config.xml_output) |
323 |
703 |
324 def sysinfo(self): |
704 def sysinfo(self): |
325 """Return a list of key,value pairs showing internal information.""" |
705 """Return a list of (key, value) pairs showing internal information.""" |
326 |
706 |
327 import coverage as covmod |
707 import coverage as covmod |
328 import platform, re, sys |
708 import platform, re |
|
709 |
|
710 try: |
|
711 implementation = platform.python_implementation() |
|
712 except AttributeError: |
|
713 implementation = "unknown" |
329 |
714 |
330 info = [ |
715 info = [ |
331 ('version', covmod.__version__), |
716 ('version', covmod.__version__), |
332 ('coverage', covmod.__file__), |
717 ('coverage', covmod.__file__), |
333 ('cover_prefix', self.cover_prefix), |
718 ('cover_dir', self.cover_dir), |
334 ('pylib_prefix', self.pylib_prefix), |
719 ('pylib_dirs', self.pylib_dirs), |
335 ('tracer', self.collector.tracer_name()), |
720 ('tracer', self.collector.tracer_name()), |
|
721 ('config_files', self.config.attempted_config_files), |
|
722 ('configs_read', self.config.config_files), |
336 ('data_path', self.data.filename), |
723 ('data_path', self.data.filename), |
337 ('python', sys.version.replace('\n', '')), |
724 ('python', sys.version.replace('\n', '')), |
338 ('platform', platform.platform()), |
725 ('platform', platform.platform()), |
|
726 ('implementation', implementation), |
|
727 ('executable', sys.executable), |
339 ('cwd', os.getcwd()), |
728 ('cwd', os.getcwd()), |
340 ('path', sys.path), |
729 ('path', sys.path), |
341 ('environment', [ |
730 ('environment', sorted([ |
342 ("%s = %s" % (k, v)) for k, v in os.environ.items() |
731 ("%s = %s" % (k, v)) for k, v in iitems(os.environ) |
343 if re.search("^COV|^PY", k) |
732 if re.search(r"^COV|^PY", k) |
344 ]), |
733 ])), |
|
734 ('command_line', " ".join(getattr(sys, 'argv', ['???']))), |
345 ] |
735 ] |
|
736 if self.source_match: |
|
737 info.append(('source_match', self.source_match.info())) |
|
738 if self.include_match: |
|
739 info.append(('include_match', self.include_match.info())) |
|
740 if self.omit_match: |
|
741 info.append(('omit_match', self.omit_match.info())) |
|
742 if self.cover_match: |
|
743 info.append(('cover_match', self.cover_match.info())) |
|
744 if self.pylib_match: |
|
745 info.append(('pylib_match', self.pylib_match.info())) |
|
746 |
346 return info |
747 return info |
|
748 |
|
749 |
|
750 def process_startup(): |
|
751 """Call this at Python startup to perhaps measure coverage. |
|
752 |
|
753 If the environment variable COVERAGE_PROCESS_START is defined, coverage |
|
754 measurement is started. The value of the variable is the config file |
|
755 to use. |
|
756 |
|
757 There are two ways to configure your Python installation to invoke this |
|
758 function when Python starts: |
|
759 |
|
760 #. Create or append to sitecustomize.py to add these lines:: |
|
761 |
|
762 import coverage |
|
763 coverage.process_startup() |
|
764 |
|
765 #. Create a .pth file in your Python installation containing:: |
|
766 |
|
767 import coverage; coverage.process_startup() |
|
768 |
|
769 """ |
|
770 cps = os.environ.get("COVERAGE_PROCESS_START") |
|
771 if cps: |
|
772 cov = coverage(config_file=cps, auto_data=True) |
|
773 cov.start() |
|
774 cov._warn_no_data = False |
|
775 cov._warn_unimported_source = False |
|
776 |
|
777 |
|
778 # A hack for debugging testing in subprocesses. |
|
779 _TEST_NAME_FILE = "" #"/tmp/covtest.txt" |