ATLAS Offline Software
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 #
59 import sys
60 
61 import re
62 import os
63 import dis
64 
65 running_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.
107 if 'SET_LINENO' in dis.__dict__:
108  _find_LINENO_from_code = _find_LINENO_from_code_22
109 else:
110  _find_LINENO_from_code = _find_LINENO_from_code_23
111 
112 def _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 
144 class Coverage:
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
python.coverage.Coverage
Definition: coverage.py:144
read
IovVectorMap_t read(const Folder &theFolder, const SelectionCriterion &choice, const unsigned int limit=10)
Definition: openCoraCool.cxx:569
python.coverage.Coverage.analyze
def analyze(self)
Definition: coverage.py:222
find
std::string find(const std::string &s)
return a remapped string
Definition: hcg.cxx:135
python.coverage.Coverage.__init__
def __init__(self, modname, toplev_name='main')
Definition: coverage.py:145
python.coverage._find_LINENO_from_code
_find_LINENO_from_code
Definition: coverage.py:108
python.coverage.Coverage.trace
def trace(self, frame, why, arg)
Definition: coverage.py:193
python.coverage.Coverage.modname
modname
Definition: coverage.py:148
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:194
PyAthena::repr
std::string repr(PyObject *o)
returns the string representation of a python object equivalent of calling repr(o) in python
Definition: PyAthenaUtils.cxx:106
print
void print(char *figname, TCanvas *c1)
Definition: TRTCalib_StrawStatusPlots.cxx:26
Trk::open
@ open
Definition: BinningType.h:40
python.coverage._find_LINENO
def _find_LINENO(code)
Definition: coverage.py:112
python.coverage.Coverage.toplev_name
toplev_name
Definition: coverage.py:149
python.coverage.Coverage.doctest_cover
def doctest_cover(self, *args, **kw)
Definition: coverage.py:296
get
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition: hcg.cxx:127
python.coverage.find_executable_linenos
def find_executable_linenos(filename)
Definition: coverage.py:127
python.coverage._find_LINENO_from_code_23
def _find_LINENO_from_code_23(code)
Definition: coverage.py:100
python.coverage.Coverage.mutex
mutex
Definition: coverage.py:151
python.coverage._find_LINENO_from_code_22
def _find_LINENO_from_code_22(code)
Definition: coverage.py:70
python.coverage.Coverage.counts
counts
Definition: coverage.py:150