1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 r"""Usage:
26
27 coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...]
28 Execute module, passing the given command-line arguments, collecting
29 coverage data. With the -p option, write to a temporary file containing
30 the machine name and process ID.
31
32 coverage.py -e
33 Erase collected coverage data.
34
35 coverage.py -c
36 Collect data from multiple coverage files (as created by -p option above)
37 and store it into a single file representing the union of the coverage.
38
39 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
40 Report on the statement coverage for the given files. With the -m
41 option, show line numbers of the statements that weren't executed.
42
43 coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
44 Make annotated copies of the given files, marking statements that
45 are executed with > and statements that are missed with !. With
46 the -d option, make the copies in that directory. Without the -d
47 option, make each copy in the same directory as the original.
48
49 -o dir,dir2,...
50 Omit reporting or annotating files when their filename path starts with
51 a directory listed in the omit list.
52 e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
53
54 Coverage data is saved in the file .coverage by default. Set the
55 COVERAGE_FILE environment variable to save it somewhere else."""
56
57 __docformat__ = 'plaintext en'
58 __version__ = "2.77.20070729"
59
60 import compiler
61 import compiler.visitor
62 import glob
63 import os
64 import re
65 import string
66 import symbol
67 import sys
68 import threading
69 import token
70 import types
71 from socket import gethostname
72
73
74 try:
75 strclass = basestring
76 except:
77 strclass = str
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
100 FUNC = 'func'
101 CLASS = 'class'
102 - def __init__(self, node, defstart, codestart, end, coverage=None):
109
111 return ('DefInfo(%s, %s, %s, %s, %s, %s)' %
112 (self.typ, self.name, self.defstart, self.codestart,
113 self.end, self.coverage))
114
115 @staticmethod
121
123 """ A visitor for a parsed Abstract Syntax Tree which finds executable
124 statements.
125 """
126 - def __init__(self, statements, excluded, suite_spots, definfo):
127 compiler.visitor.ASTVisitor.__init__(self)
128 self.statements = statements
129 self.excluded = excluded
130 self.suite_spots = suite_spots
131 self.excluding_suite = 0
132 self.definfo = definfo
133
135 for n in node.getChildNodes():
136 self.dispatch(n)
137
138 visitStmt = visitModule = doRecursive
139
158
159 visitFunction = visitClass = doCode
160
162
163 lineno = node.lineno
164 for n in node.getChildNodes():
165 f = self.getFirstLine(n)
166 if lineno and f:
167 lineno = min(lineno, f)
168 else:
169 lineno = lineno or f
170 return lineno
171
173
174 lineno = node.lineno
175 for n in node.getChildNodes():
176 lineno = max(lineno, self.getLastLine(n))
177 return lineno
178
181
182 visitAssert = visitAssign = visitAssTuple = visitPrint = \
183 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
184 doStatement
185
187
188
189
190 l = node.lineno
191 if l:
192 lines = self.suite_spots.get(l, [l,l])
193 self.statements[lines[1]] = 1
194
202
211
213
214 if lineno:
215
216
217 if lineno in self.suite_spots:
218 lineno = self.suite_spots[lineno][0]
219
220
221 if self.excluding_suite:
222 self.excluded[lineno] = 1
223 return 0
224
225
226 elif self.excluded.has_key(lineno) or \
227 self.suite_spots.has_key(lineno) and \
228 self.excluded.has_key(self.suite_spots[lineno][1]):
229 return 0
230
231 else:
232 self.statements[lineno] = 1
233 return 1
234 return 0
235
236 default = recordNodeLine
237
241
242 - def doSuite(self, intro, body, exclude=0):
243 exsuite = self.excluding_suite
244 if exclude or (intro and not self.recordNodeLine(intro)):
245 self.excluding_suite = 1
246 self.recordAndDispatch(body)
247 self.excluding_suite = exsuite
248
250
251
252
253
254 lastprev = self.getLastLine(prevsuite)
255 firstelse = self.getFirstLine(suite)
256 for l in range(lastprev+1, firstelse):
257 if self.suite_spots.has_key(l):
258 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
259 break
260 else:
261 self.doSuite(None, suite)
262
263 - def doElse(self, prevsuite, node):
266
270
271 visitWhile = visitFor
272
281
296
300
303
308
309 the_coverage = None
310
312
314
315 cache_default = ".coverage"
316
317
318 cache_env = "COVERAGE_FILE"
319
320
321
322 c = {}
323
324
325
326
327 cexecuted = {}
328
329
330
331 analysis_cache = {}
332
333
334
335 canonical_filename_cache = {}
336
338 global the_coverage
339 if the_coverage:
340 raise CoverageException, "Only one coverage object allowed."
341 self.usecache = 1
342 self.cache = None
343 self.parallel_mode = False
344 self.exclude_re = ''
345 self.nesting = 0
346 self.cstack = []
347 self.xstack = []
348 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.sep)
349 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
350
351
352
353
354
355
356
357 - def t(self, f, w, unused):
358 if w == 'line':
359
360 self.c[(f.f_code.co_filename, f.f_lineno)] = 1
361 for c in self.cstack:
362 c[(f.f_code.co_filename, f.f_lineno)] = 1
363 return self.t
364
365 - def help(self, error=None):
366 if error:
367 print error
368 print
369 print __doc__
370 sys.exit(1)
371
373 import getopt
374 help_fn = help_fn or self.help
375 settings = {}
376 optmap = {
377 '-a': 'annotate',
378 '-c': 'collect',
379 '-d:': 'directory=',
380 '-e': 'erase',
381 '-h': 'help',
382 '-i': 'ignore-errors',
383 '-m': 'show-missing',
384 '-p': 'parallel-mode',
385 '-r': 'report',
386 '-x': 'execute',
387 '-o:': 'omit=',
388 }
389 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
390 long_opts = optmap.values()
391 options, args = getopt.getopt(argv, short_opts, long_opts)
392 for o, a in options:
393 if optmap.has_key(o):
394 settings[optmap[o]] = 1
395 elif optmap.has_key(o + ':'):
396 settings[optmap[o + ':']] = a
397 elif o[2:] in long_opts:
398 settings[o[2:]] = 1
399 elif o[2:] + '=' in long_opts:
400 settings[o[2:]+'='] = a
401 else:
402 pass
403
404 if settings.get('help'):
405 help_fn()
406
407 for i in ['erase', 'execute']:
408 for j in ['annotate', 'report', 'collect']:
409 if settings.get(i) and settings.get(j):
410 help_fn("You can't specify the '%s' and '%s' "
411 "options at the same time." % (i, j))
412
413 args_needed = (settings.get('execute')
414 or settings.get('annotate')
415 or settings.get('report'))
416 action = (settings.get('erase')
417 or settings.get('collect')
418 or args_needed)
419 if not action:
420 help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
421 if not args_needed and args:
422 help_fn("Unexpected arguments: %s" % " ".join(args))
423
424 self.parallel_mode = settings.get('parallel-mode')
425 self.get_ready()
426
427 if settings.get('erase'):
428 self.erase()
429 if settings.get('execute'):
430 if not args:
431 help_fn("Nothing to do.")
432 sys.argv = args
433 self.start()
434 import __main__
435 sys.path[0] = os.path.dirname(sys.argv[0])
436 execfile(sys.argv[0], __main__.__dict__)
437 if settings.get('collect'):
438 self.collect()
439 if not args:
440 args = self.cexecuted.keys()
441
442 ignore_errors = settings.get('ignore-errors')
443 show_missing = settings.get('show-missing')
444 directory = settings.get('directory=')
445
446 omit = settings.get('omit=')
447 if omit is not None:
448 omit = omit.split(',')
449 else:
450 omit = []
451
452 if settings.get('report'):
453 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
454 if settings.get('annotate'):
455 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
456
457 - def use_cache(self, usecache, cache_file=None):
458 self.usecache = usecache
459 if cache_file and not self.cache:
460 self.cache_default = cache_file
461
469
470 - def start(self, parallel_mode=False):
471 self.get_ready()
472 if self.nesting == 0:
473 sys.settrace(self.t)
474 if hasattr(threading, 'settrace'):
475 threading.settrace(self.t)
476 self.nesting += 1
477
479 self.nesting -= 1
480 if self.nesting == 0:
481 sys.settrace(None)
482 if hasattr(threading, 'settrace'):
483 threading.settrace(None)
484
492
494 if self.exclude_re:
495 self.exclude_re += "|"
496 self.exclude_re += "(" + re + ")"
497
499 self.cstack.append(self.c)
500 self.xstack.append(self.exclude_re)
501
503 self.c = self.cstack.pop()
504 self.exclude_re = self.xstack.pop()
505
506
507
515
516
517
524
537
538
539
549
556
558 for line_number in new_data.keys():
559 if not cache_data.has_key(line_number):
560 cache_data[line_number] = new_data[line_number]
561
562
563
564
565
580
581
582
583
594
595
596
598 if isinstance(morf, types.ModuleType):
599 if not hasattr(morf, '__file__'):
600 raise CoverageException, "Module has no __file__ attribute."
601 f = morf.__file__
602 else:
603 f = morf
604 return self.canonical_filename(f)
605
606
607
608
609
610
611
612
613
616
618 if self.analysis_cache.has_key(morf):
619 return self.analysis_cache[morf]
620 filename = self.morf_filename(morf)
621 ext = os.path.splitext(filename)[1]
622 if ext == '.pyc':
623 if not os.path.exists(filename[0:-1]):
624 raise CoverageException, ("No source for compiled code '%s'."
625 % filename)
626 filename = filename[0:-1]
627 elif ext != '.py':
628 raise CoverageException, "File '%s' not Python source." % filename
629 source = open(filename, 'rU')
630 lines, excluded_lines, line_map, definfo = \
631 self.find_executable_statements2(source.read(),
632 exclude=self.exclude_re)
633 source.close()
634 result = filename, lines, excluded_lines, line_map, definfo
635 self.analysis_cache[morf] = result
636 return result
637
643
649
656
658 try:
659 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
660 except:
661 return False
662
664 try:
665 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
666 except:
667 return False
668
670 for l in range(i, j+1):
671 spots[l] = (i, j)
672
721
724
726
727 excluded = {}
728 suite_spots = {}
729 if exclude:
730 reExclude = re.compile(exclude)
731 lines = text.split('\n')
732 for i in range(len(lines)):
733 if reExclude.search(lines[i]):
734 excluded[i+1] = 1
735
736
737
738 import parser
739 tree = parser.suite(text+'\n\n').totuple(1)
740 self.get_suite_spots(tree, suite_spots)
741
742
743
744 definfo = []
745
746
747
748 statements = {}
749 ast = compiler.parse(text+'\n\n')
750 visitor = StatementFindingAstVisitor(statements, excluded,
751 suite_spots, definfo)
752 compiler.walk(ast, visitor, walker=visitor)
753
754 lines = statements.keys()
755 lines.sort()
756 excluded_lines = excluded.keys()
757 excluded_lines.sort()
758 return lines, excluded_lines, suite_spots, definfo
759
760
761
762
763
764
765
766
791 ret = string.join(map(stringify, pairs), ", ")
792 return ret
793
794
796 f, s, _, m, mf = self.analysis2(morf)
797 return f, s, m, mf
798
800 f, s, e, m, mf, _ = self.analysis3(morf)
801 return f, s, e, m, mf
802
804 filename, statements, excluded, line_map, definfo = \
805 self.analyze_morf2(morf)
806 self.canonicalize_filenames()
807 if not self.cexecuted.has_key(filename):
808 self.cexecuted[filename] = {}
809 missing = []
810 for line in statements:
811 lines = line_map.get(line, [line, line])
812 for l in range(lines[0], lines[1]+1):
813 if self.cexecuted[filename].has_key(l):
814 break
815 else:
816 missing.append(line)
817 self.find_def_coverage(morf, statements, missing, definfo)
818 return (filename, statements, excluded, missing,
819 self.format_lines(statements, missing), definfo)
820
822 """Return mapping from function name to coverage.
823 """
824 def_coverage = {}
825 root = self.morf_name(morf)
826 statements = set(statements)
827 missing = set(missing)
828 for info in definfo:
829 if info.codestart is None:
830 info.coverage = 1
831 else:
832 lines = set(range(info.codestart, info.end+1))
833 stmt = len(lines.intersection(statements))
834 miss = len(lines.intersection(missing))
835 if miss == 0: info.cover = 1
836 else: info.coverage = (1.0 - float(miss)/float(stmt))
837
839 """ Convert filename to relative filename from self.relative_dir.
840 """
841 return filename.replace(self.relative_dir, "")
842
844 """ Return the name of morf as used in report.
845 """
846 if isinstance(morf, types.ModuleType):
847 return morf.__name__
848 else:
849 return self.relative_filename(os.path.splitext(morf)[0])
850
852 """ Return list of morfs where the morf name does not begin
853 with any one of the omit_prefixes.
854 """
855 filtered_morfs = []
856 for morf in morfs:
857 for prefix in omit_prefixes:
858 if self.morf_name(morf).startswith(prefix):
859 break
860 else:
861 filtered_morfs.append(morf)
862
863 return filtered_morfs
864
867
868 - def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
869 if not isinstance(morfs, types.ListType):
870 morfs = [morfs]
871
872 globbed = []
873 for morf in morfs:
874 if isinstance(morf, strclass):
875 globbed.extend(glob.glob(morf))
876 else:
877 globbed.append(morf)
878 morfs = globbed
879
880 morfs = self.filter_by_prefix(morfs, omit_prefixes)
881 morfs.sort(self.morf_name_compare)
882
883 max_name = max([5,] + map(len, map(self.morf_name, morfs)))
884 fmt_name = "%%- %ds " % max_name
885 fmt_err = fmt_name + "%s: %s"
886 header = fmt_name % "Name" + " Stmts Exec Cover"
887 fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
888 if show_missing:
889 header = header + " Missing"
890 fmt_coverage = fmt_coverage + " %s"
891 if not file:
892 file = sys.stdout
893 print >>file, header
894 print >>file, "-" * len(header)
895 total_statements = 0
896 total_executed = 0
897 for morf in morfs:
898 name = self.morf_name(morf)
899 try:
900 _, statements, _, missing, readable = self.analysis2(morf)
901 n = len(statements)
902 m = n - len(missing)
903 if n > 0:
904 pc = 100.0 * m / n
905 else:
906 pc = 100.0
907 args = (name, n, m, pc)
908 if show_missing:
909 args = args + (readable,)
910 print >>file, fmt_coverage % args
911 total_statements = total_statements + n
912 total_executed = total_executed + m
913 except KeyboardInterrupt:
914 raise
915 except:
916 if not ignore_errors:
917 typ, msg = sys.exc_info()[0:2]
918 print >>file, fmt_err % (name, typ, msg)
919 if len(morfs) > 1:
920 print >>file, "-" * len(header)
921 if total_statements > 0:
922 pc = 100.0 * total_executed / total_statements
923 else:
924 pc = 100.0
925 args = ("TOTAL", total_statements, total_executed, pc)
926 if show_missing:
927 args = args + ("",)
928 print >>file, fmt_coverage % args
929
930
931
932 blank_re = re.compile(r"\s*(#|$)")
933 else_re = re.compile(r"\s*else\s*:\s*(#|$)")
934
935 - def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
936 morfs = self.filter_by_prefix(morfs, omit_prefixes)
937 for morf in morfs:
938 try:
939 filename, statements, excluded, missing, _ = self.analysis2(morf)
940 self.annotate_file(filename, statements, excluded, missing, directory)
941 except KeyboardInterrupt:
942 raise
943 except:
944 if not ignore_errors:
945 raise
946
947 - def annotate_file(self, filename, statements, excluded, missing, directory=None):
948 source = open(filename, 'rU')
949 if directory:
950 dest_file = os.path.join(directory,
951 os.path.basename(filename)
952 + ',cover')
953 else:
954 dest_file = filename + ',cover'
955 dest = open(dest_file, 'w')
956 lineno = 0
957 i = 0
958 j = 0
959 covered = 1
960 while 1:
961 line = source.readline()
962 if line == '':
963 break
964 lineno = lineno + 1
965 while i < len(statements) and statements[i] < lineno:
966 i = i + 1
967 while j < len(missing) and missing[j] < lineno:
968 j = j + 1
969 if i < len(statements) and statements[i] == lineno:
970 covered = j >= len(missing) or missing[j] > lineno
971 if self.blank_re.match(line):
972 dest.write(' ')
973 elif self.else_re.match(line):
974
975
976 if i >= len(statements) and j >= len(missing):
977 dest.write('! ')
978 elif i >= len(statements) or j >= len(missing):
979 dest.write('> ')
980 elif statements[i] == missing[j]:
981 dest.write('! ')
982 else:
983 dest.write('> ')
984 elif lineno in excluded:
985 dest.write('- ')
986 elif covered:
987 dest.write('> ')
988 else:
989 dest.write('! ')
990 dest.write(line)
991 source.close()
992 dest.close()
993
994
995 the_coverage = coverage()
996
997
999 return the_coverage.use_cache(*args, **kw)
1000
1002 return the_coverage.start(*args, **kw)
1003
1004 -def stop(*args, **kw):
1005 return the_coverage.stop(*args, **kw)
1006
1008 return the_coverage.erase(*args, **kw)
1009
1011 return the_coverage.begin_recursive(*args, **kw)
1012
1014 return the_coverage.end_recursive(*args, **kw)
1015
1017 return the_coverage.exclude(*args, **kw)
1018
1020 return the_coverage.analysis(*args, **kw)
1021
1023 return the_coverage.analysis2(*args, **kw)
1024
1026 return the_coverage.analysis3(*args, **kw)
1027
1029 return the_coverage.report(*args, **kw)
1030
1032 return the_coverage.annotate(*args, **kw)
1033
1035 return the_coverage.annotate_file(*args, **kw)
1036
1037
1038
1039
1040 try:
1041 import atexit
1042 atexit.register(the_coverage.save)
1043 except ImportError:
1044 sys.exitfunc = the_coverage.save
1045
1046
1047 if __name__ == '__main__':
1048 the_coverage.command_line(sys.argv[1:])
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189