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, 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 from __future__ import print_function
60 import sys
61 
62 
65 
66 import re
67 import os
68 import dis
69 
70 running_coverage = 0
71 
72 
73 # Given a code string, return the SET_LINENO information
74 # This works up to python 2.2
76  """return all of the SET_LINENO information from a code block"""
77  co_code = code.co_code
78  linenos = {}
79 
80  # This code was filched from the `dis' module then modified
81  n = len(co_code)
82  i = 0
83  prev_op = None
84  prev_lineno = 0
85  while i < n:
86  c = co_code[i]
87  op = ord(c)
88  if op == dis.SET_LINENO:
89  if prev_op == op:
90  # two SET_LINENO in a row, so the previous didn't
91  # indicate anything. This occurs with triple
92  # quoted strings (?). Remove the old one.
93  del linenos[prev_lineno]
94  prev_lineno = ord(co_code[i+1]) + ord(co_code[i+2])*256
95  linenos[prev_lineno] = 1
96  if op >= dis.HAVE_ARGUMENT:
97  i = i + 3
98  else:
99  i = i + 1
100  prev_op = op
101  return linenos
102 
103 
104 # This works from python 2.3 on.
106  linenos = {}
107  for (o, l) in dis.findlinestarts (code):
108  linenos[l] = 1
109  return linenos
110 
111 # Choose the appropriate version.
112 if 'SET_LINENO' in dis.__dict__:
113  _find_LINENO_from_code = _find_LINENO_from_code_22
114 else:
115  _find_LINENO_from_code = _find_LINENO_from_code_23
116 
117 def _find_LINENO(code):
118  """return all of the SET_LINENO information from a code object"""
119  import types
120 
121  # get all of the lineno information from the code of this scope level
122  #linenos = _find_LINENO_from_string(code.co_code)
123  linenos = _find_LINENO_from_code(code)
124 
125  # and check the constants for references to other code objects
126  for c in code.co_consts:
127  if isinstance(c, types.CodeType):
128  # find another code object, so recurse into it
129  linenos.update(_find_LINENO(c))
130  return linenos
131 
133  """return a dict of the line numbers from executable statements in a file
134 
135  Works by finding all of the code-like objects in the module then searching
136  the byte code for 'SET_LINENO' terms (so this won't work one -O files).
137 
138  """
139 
140  prog = open(filename).read()
141  code = compile (prog, filename, 'exec')
142 
143  # The only way I know to find line numbers is to look for the
144  # SET_LINENO instructions. Isn't there some way to get it from
145  # the AST?
146 
147  return _find_LINENO(code)
148 
149 class Coverage:
150  def __init__(self, modname, toplev_name = 'main'):
151  global running_coverage
152  running_coverage = 1
153  self.modname = modname
154  self.toplev_name = toplev_name
155  self.counts = {} # keys are linenumber
156  self.mutex = None
157  if 'NOCOVER' not in os.environ:
158  #self.save_import = __builtin__.__import__
159  #__builtin__.__import__ = self.import_hook
160  sys.settrace (self.trace)
161  return
162 
163 
164  #def import_hook (self, name, globals={}, locals={}, fromlist=[]):
165  # loaded = sys.modules.has_key (name)
166  # mod = self.save_import (name, globals, locals, fromlist)
167  # if not loaded:
168  # if name == 'thread_util':
169  # self.set_thread_hook (mod)
170  # elif name == 'itc' or name == 'd0me':
171  # self.set_itc_hook (mod)
172  # return mod
173 
174 
175  #def set_thread_hook (self, thread_util):
176  # if not self.mutex:
177  # self.mutex = thread_util.ACE_Thread_Mutex ()
178  # thread_util.Pythread._save_run = thread_util.Pythread._run
179  # def _run (self, coverage=self):
180  # sys.settrace (coverage.trace)
181  # return self._save_run ()
182  # thread_util.Pythread._run = _run
183  # return
184 
185  #def set_itc_hook (self, itc):
186  # if not self.mutex:
187  # import thread_util
188  # assert self.mutex != None
189  # self.old_itc_hook = itc._callback_hook
190  # def itc_hook (self=self):
191  # sys.settrace (self.trace)
192  # if self.old_itc_hook: self.old_itc_hook ()
193  # return
194  # itc._callback_hook = itc_hook
195  # return
196 
197 
198  def trace(self, frame, why, arg):
199  # something is fishy about getting the file name
200  modulename = frame.f_globals.get ("__name__")
201  if why == 'call':
202  # Don't bother tracing if we're not in one of these modules.
203  if not (modulename == self.modname or
204  (modulename == '__main__' and
205  frame.f_code.co_name == self.toplev_name)):
206  return None
207  if why == 'line':
208  if modulename == self.modname:
209  lineno = frame.f_lineno
210 
211  # record the file name and line number of every trace
212  if self.mutex: self.mutex.acquire ()
213  self.counts[lineno] = self.counts.get(lineno, 0) + 1
214  if self.mutex: self.mutex.release ()
215 
216  elif why == 'return':
217  if frame.f_code.co_name == self.toplev_name:
218  sys.settrace (None)
219  if self.mutex: self.mutex.acquire ()
220  self.analyze ()
221  m = self.mutex
222  self.mutex = None
223  if m: m.release ()
224 
225  return self.trace
226 
227  def analyze (self):
228  filename = sys.modules[self.modname].__file__
229  if filename[-4:] == ".pyc" or filename[-4:] == ".pyo":
230  orig_filename = filename[:-4] + '.py'
231  else:
232  orig_filename = filename
233 
234  # Get the original lines from the .py file
235  try:
236  lines = open(orig_filename, 'r').readlines()
237  except IOError as err:
238  sys.stderr.write(
239  "%s: Could not open %s for reading because: %s - skipping\n" %
240 \
241  ("trace", repr(filename), err.strerror))
242  return
243 
244  # there are many places where this is insufficient, like a blank
245  # line embedded in a multiline string.
246  blank = re.compile(r'^\s*(#.*)?$')
247 
248  executable_linenos = find_executable_linenos(orig_filename)
249 
250  lines_hit = self.counts
251  uncovered = 0
252  outlines = []
253  for i in range(len(lines)):
254  line = lines[i]
255 
256  # do the blank/comment match to try to mark more lines
257  # (help the reader find stuff that hasn't been covered)
258  if (i+1) in lines_hit:
259  # count precedes the lines that we captured
260  prefix = '%5d: ' % lines_hit[i+1]
261  elif blank.match(line):
262  # blank lines and comments are preceded by dots
263  prefix = ' . '
264  else:
265  # lines preceded by no marks weren't hit
266  # Highlight them if so indicated, unless the line contains
267  # '#pragma: NO COVER' (it is possible to embed this into
268  # the text as a non-comment; no easy fix)
269  if (i+1) in executable_linenos and \
270  lines[i].find('#pragma: NO COVER') == -1:
271  prefix = '>>>>>> '
272  uncovered = uncovered + 1
273  else:
274  prefix = ' '*7
275  outlines.append (prefix + line.expandtabs(8))
276 
277  if uncovered:
278  print("*** There were %d uncovered lines." % uncovered)
279  else:
280  return
281 
282  # build list file name by appending a ".cover" to the module name
283  # and sticking it into the specified directory
284  listfilename = self.modname + ".cover"
285  try:
286  outfile = open(listfilename, 'w')
287  except IOError as err:
288  sys.stderr.write(
289  '%s: Could not open %s for writing because: %s - skipping\n' %
290  ("trace", repr(listfilename), err.strerror))
291  return
292 
293  for l in outlines:
294  outfile.write (l)
295 
296  outfile.close()
297 
298  return
299 
300 
301  def doctest_cover (self, *args, **kw):
302  import doctest
303  m = __import__ (self.modname)
304 
305  # Note a peculiarity of __import__: if modname is like A.B,
306  # then it returns A not B...
307  mm = self.modname.split ('.')
308  if len(mm) > 1:
309  m = getattr (m, mm[-1])
310 
311  oldrun = doctest.DocTestRunner.run
312  def xrun (xself, *args, **kw):
313  sys.settrace (self.trace)
314  return oldrun (xself, *args, **kw)
315  doctest.DocTestRunner.run = xrun
316 
317  import bdb
318  old_set_continue = bdb.Bdb.set_continue
319  def xcontinue (xself):
320  old_set_continue (xself)
321  sys.settrace (self.trace)
322  return
323  bdb.Bdb.set_continue = xcontinue
324 
325  doctest.testmod (m, *args, **kw)
326 
327  main = sys.modules['__main__']
328  if m != main:
329  doctest.testmod (main, *args, **kw)
330 
331  self.analyze()
332  return
python.coverage.Coverage
Definition: coverage.py:149
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:227
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:150
python.coverage._find_LINENO_from_code
_find_LINENO_from_code
Definition: coverage.py:113
python.coverage.Coverage.trace
def trace(self, frame, why, arg)
Definition: coverage.py:198
python.coverage.Coverage.modname
modname
Definition: coverage.py:153
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:195
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
Trk::open
@ open
Definition: BinningType.h:40
python.coverage._find_LINENO
def _find_LINENO(code)
Definition: coverage.py:117
python.coverage.Coverage.toplev_name
toplev_name
Definition: coverage.py:154
python.coverage.Coverage.doctest_cover
def doctest_cover(self, *args, **kw)
Definition: coverage.py:301
Muon::print
std::string print(const MuPatSegment &)
Definition: MuonTrackSteering.cxx:28
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:132
python.coverage._find_LINENO_from_code_23
def _find_LINENO_from_code_23(code)
Definition: coverage.py:105
python.coverage.Coverage.mutex
mutex
Definition: coverage.py:156
python.coverage._find_LINENO_from_code_22
def _find_LINENO_from_code_22(code)
Definition: coverage.py:75
python.coverage.Coverage.counts
counts
Definition: coverage.py:155