ATLAS Offline Software
Loading...
Searching...
No Matches
coverage.py
Go to the documentation of this file.
1# noqa: ATL902
2#
3# File: coverage.py
4# Purpose: A coverage utility for component tests.
5# Created: Dec 2008, sss from D0 code circa Jan, 2000.
6#
7# Derived from trace.py by Andrew Dalke and Skip Montanaro, mainly by
8# cutting out a whole bunch of stuff.
9#
10# To do coverage testing for some component 'Foo', add the following
11# to the start of Foo's test module:
12#
13# import python_util.coverage
14# python_util.coverage.Coverage ('Foo')
15#
16# If all executable lines in Foo are covered by the test, there will be
17# no output. Otherwise, a summary of the number of uncovered lines
18# will be printed to stdout, and a file Foo.cover will be created
19# with an annotated source listing for Foo, showing coverage counts
20# and uncovered lines.
21#
22# You can also run coverage testing in conjunction with doctest-based
23# regression tests. In general, doctests can be both in the source file
24# itself, or in a separate test file. If you have a separate test file,
25# you can run all the tests for a module DIR.MOD like this:
26#
27# from PyUtils import coverage
28# c = coverage.Coverage ('DIR.MOD')
29# c.doctest_cover ()
30#
31# This will run any doctests in the DIR.MOD source file, plus any
32# additional doctests in the test file. If any lines in the DIR.MOD
33# source file are uncovered, a coverage report will be generated.
34#
35# The coverage testing may be inhibited by setting the environment
36# variable NOCOVER.
37#
38# Coverage testing for an individual line may be suppressed by
39# adding '#pragma: NO COVER' to the end of the line.
40#
41# Original permission notice:
42# Copyright 1999, 2025, Bioreason, Inc., all rights reserved.
43# Author: Andrew Dalke
44#
45# Copyright 1995-1997, Automatrix, Inc., all rights reserved.
46# Author: Skip Montanaro
47#
48# Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
49#
50# Permission to use, copy, modify, and distribute this Python software
51# and its associated documentation for any purpose without fee is
52# hereby granted, provided that the above copyright notice appears in
53# all copies, and that both that copyright notice and this permission
54# notice appear in supporting documentation, and that the name of
55# neither Automatrix nor Bioreason be used in advertising or publicity
56# pertaining to distribution of the software without specific, written
57# prior permission.
58#
59import sys
60
61import re
62import os
63import dis
64
65running_coverage = 0
66
67
68# Given a code string, return the SET_LINENO information
69# This works up to python 2.2
71 """return all of the SET_LINENO information from a code block"""
72 co_code = code.co_code
73 linenos = {}
74
75 # This code was filched from the `dis' module then modified
76 n = len(co_code)
77 i = 0
78 prev_op = None
79 prev_lineno = 0
80 while i < n:
81 c = co_code[i]
82 op = ord(c)
83 if op == dis.SET_LINENO:
84 if prev_op == op:
85 # two SET_LINENO in a row, so the previous didn't
86 # indicate anything. This occurs with triple
87 # quoted strings (?). Remove the old one.
88 del linenos[prev_lineno]
89 prev_lineno = ord(co_code[i+1]) + ord(co_code[i+2])*256
90 linenos[prev_lineno] = 1
91 if op >= dis.HAVE_ARGUMENT:
92 i = i + 3
93 else:
94 i = i + 1
95 prev_op = op
96 return linenos
97
98
99# This works from python 2.3 on.
101 linenos = {}
102 for (o, l) in dis.findlinestarts (code):
103 linenos[l] = 1
104 return linenos
105
106# Choose the appropriate version.
107if 'SET_LINENO' in dis.__dict__:
108 _find_LINENO_from_code = _find_LINENO_from_code_22
109else:
110 _find_LINENO_from_code = _find_LINENO_from_code_23
111
112def _find_LINENO(code):
113 """return all of the SET_LINENO information from a code object"""
114 import types
115
116 # get all of the lineno information from the code of this scope level
117 #linenos = _find_LINENO_from_string(code.co_code)
118 linenos = _find_LINENO_from_code(code)
119
120 # and check the constants for references to other code objects
121 for c in code.co_consts:
122 if isinstance(c, types.CodeType):
123 # find another code object, so recurse into it
124 linenos.update(_find_LINENO(c))
125 return linenos
126
128 """return a dict of the line numbers from executable statements in a file
129
130 Works by finding all of the code-like objects in the module then searching
131 the byte code for 'SET_LINENO' terms (so this won't work one -O files).
132
133 """
134
135 prog = open(filename).read()
136 code = compile (prog, filename, 'exec')
137
138 # The only way I know to find line numbers is to look for the
139 # SET_LINENO instructions. Isn't there some way to get it from
140 # the AST?
141
142 return _find_LINENO(code)
143
145 def __init__(self, modname, toplev_name = 'main'):
146 global running_coverage
147 running_coverage = 1
148 self.modname = modname
149 self.toplev_name = toplev_name
150 self.counts = {} # keys are linenumber
151 self.mutex = None
152 if 'NOCOVER' not in os.environ:
153 #self.save_import = __builtin__.__import__
154 #__builtin__.__import__ = self.import_hook
155 sys.settrace (self.trace)
156 return
157
158
159 #def import_hook (self, name, globals={}, locals={}, fromlist=[]):
160 # loaded = sys.modules.has_key (name)
161 # mod = self.save_import (name, globals, locals, fromlist)
162 # if not loaded:
163 # if name == 'thread_util':
164 # self.set_thread_hook (mod)
165 # elif name == 'itc' or name == 'd0me':
166 # self.set_itc_hook (mod)
167 # return mod
168
169
170 #def set_thread_hook (self, thread_util):
171 # if not self.mutex:
172 # self.mutex = thread_util.ACE_Thread_Mutex ()
173 # thread_util.Pythread._save_run = thread_util.Pythread._run
174 # def _run (self, coverage=self):
175 # sys.settrace (coverage.trace)
176 # return self._save_run ()
177 # thread_util.Pythread._run = _run
178 # return
179
180 #def set_itc_hook (self, itc):
181 # if not self.mutex:
182 # import thread_util
183 # assert self.mutex != None
184 # self.old_itc_hook = itc._callback_hook
185 # def itc_hook (self=self):
186 # sys.settrace (self.trace)
187 # if self.old_itc_hook: self.old_itc_hook ()
188 # return
189 # itc._callback_hook = itc_hook
190 # return
191
192
193 def trace(self, frame, why, arg):
194 # something is fishy about getting the file name
195 modulename = frame.f_globals.get ("__name__")
196 if why == 'call':
197 # Don't bother tracing if we're not in one of these modules.
198 if not (modulename == self.modname or
199 (modulename == '__main__' and
200 frame.f_code.co_name == self.toplev_name)):
201 return None
202 if why == 'line':
203 if modulename == self.modname:
204 lineno = frame.f_lineno
205
206 # record the file name and line number of every trace
207 if self.mutex: self.mutex.acquire ()
208 self.counts[lineno] = self.counts.get(lineno, 0) + 1
209 if self.mutex: self.mutex.release ()
210
211 elif why == 'return':
212 if frame.f_code.co_name == self.toplev_name:
213 sys.settrace (None)
214 if self.mutex: self.mutex.acquire ()
215 self.analyze ()
216 m = self.mutex
217 self.mutex = None
218 if m: m.release ()
219
220 return self.trace
221
222 def analyze (self):
223 filename = sys.modules[self.modname].__file__
224 if filename[-4:] == ".pyc" or filename[-4:] == ".pyo":
225 orig_filename = filename[:-4] + '.py'
226 else:
227 orig_filename = filename
228
229 # Get the original lines from the .py file
230 try:
231 lines = open(orig_filename, 'r').readlines()
232 except IOError as err:
233 sys.stderr.write(
234 "%s: Could not open %s for reading because: %s - skipping\n" %
235\
236 ("trace", repr(filename), err.strerror))
237 return
238
239 # there are many places where this is insufficient, like a blank
240 # line embedded in a multiline string.
241 blank = re.compile(r'^\s*(#.*)?$')
242
243 executable_linenos = find_executable_linenos(orig_filename)
244
245 lines_hit = self.counts
246 uncovered = 0
247 outlines = []
248 for i in range(len(lines)):
249 line = lines[i]
250
251 # do the blank/comment match to try to mark more lines
252 # (help the reader find stuff that hasn't been covered)
253 if (i+1) in lines_hit:
254 # count precedes the lines that we captured
255 prefix = '%5d: ' % lines_hit[i+1]
256 elif blank.match(line):
257 # blank lines and comments are preceded by dots
258 prefix = ' . '
259 else:
260 # lines preceded by no marks weren't hit
261 # Highlight them if so indicated, unless the line contains
262 # '#pragma: NO COVER' (it is possible to embed this into
263 # the text as a non-comment; no easy fix)
264 if (i+1) in executable_linenos and \
265 lines[i].find('#pragma: NO COVER') == -1:
266 prefix = '>>>>>> '
267 uncovered = uncovered + 1
268 else:
269 prefix = ' '*7
270 outlines.append (prefix + line.expandtabs(8))
271
272 if uncovered:
273 print("*** There were %d uncovered lines." % uncovered)
274 else:
275 return
276
277 # build list file name by appending a ".cover" to the module name
278 # and sticking it into the specified directory
279 listfilename = self.modname + ".cover"
280 try:
281 outfile = open(listfilename, 'w')
282 except IOError as err:
283 sys.stderr.write(
284 '%s: Could not open %s for writing because: %s - skipping\n' %
285 ("trace", repr(listfilename), err.strerror))
286 return
287
288 for l in outlines:
289 outfile.write (l)
290
291 outfile.close()
292
293 return
294
295
296 def doctest_cover (self, *args, **kw):
297 import doctest
298 m = __import__ (self.modname)
299
300 # Note a peculiarity of __import__: if modname is like A.B,
301 # then it returns A not B...
302 mm = self.modname.split ('.')
303 if len(mm) > 1:
304 m = getattr (m, mm[-1])
305
306 oldrun = doctest.DocTestRunner.run
307 def xrun (xself, *args, **kw):
308 sys.settrace (self.trace)
309 return oldrun (xself, *args, **kw)
310 doctest.DocTestRunner.run = xrun
311
312 import bdb
313 old_set_continue = bdb.Bdb.set_continue
314 def xcontinue (xself):
315 old_set_continue (xself)
316 sys.settrace (self.trace)
317 return
318 bdb.Bdb.set_continue = xcontinue
319
320 doctest.testmod (m, *args, **kw)
321
322 main = sys.modules['__main__']
323 if m != main:
324 doctest.testmod (main, *args, **kw)
325
326 self.analyze()
327 return
void print(char *figname, TCanvas *c1)
doctest_cover(self, *args, **kw)
Definition coverage.py:296
self.mutex.acquire() mutex
Definition coverage.py:151
__init__(self, modname, toplev_name='main')
Definition coverage.py:145
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:130
std::string find(const std::string &s)
return a remapped string
Definition hcg.cxx:138
_find_LINENO_from_code_23(code)
Definition coverage.py:100
_find_LINENO(code)
Definition coverage.py:112
find_executable_linenos(filename)
Definition coverage.py:127
_find_LINENO_from_code_22(code)
Definition coverage.py:70
IovVectorMap_t read(const Folder &theFolder, const SelectionCriterion &choice, const unsigned int limit=10)