2 """A Trial IReporter plugin that gathers figleaf code-coverage information.
4 Once this plugin is installed, trial can be invoked with one of two new
7 trial --reporter=verbose-figleaf ARGS
8 trial --reporter-bwverbose-figleaf ARGS
10 Once such a test run has finished, there will be a .figleaf file in the
11 top-level directory. This file can be turned into a directory of .html files
12 (with index.html as the starting point) by running:
14 figleaf2html -d OUTPUTDIR [-x EXCLUDEFILE]
16 Figleaf thinks of everyting in terms of absolute filenames rather than
17 modules. The EXCLUDEFILE may be necessary to keep it from providing reports
18 on non-Code-Under-Test files that live in unusual locations. In particular,
19 if you use extra PYTHONPATH arguments to point at some alternate version of
20 an upstream library (like Twisted), or if something like debian's
21 python-support puts symlinks to .py files in sys.path but not the .py files
22 themselves, figleaf will present coverage information on both of these. The
23 EXCLUDEFILE option might help to inhibit these.
25 Other figleaf problems:
27 the annotated code files are written to BASENAME(file).html, which results
28 in collisions between similarly-named source files.
30 The line-wise coverage information isn't quite right. Blank lines are
31 counted as unreached code, lambdas aren't quite right, and some multiline
32 comments (docstrings?) aren't quite right.
36 # TODO: pull some of figleaf into our tree so we can customize it more
39 from twisted.trial.reporter import TreeReporter, VerboseTextReporter
41 # These plugins are registered via twisted/plugins/allmydata_trial.py . See
42 # the notes there for an explanation of how that works.
46 # Reporters don't really get told about the suite starting and stopping.
48 # The Reporter class is imported before the test classes are.
50 # The test classes are imported before the Reporter is created. To get
51 # control earlier than that requires modifying twisted/scripts/trial.py .
53 # Then Reporter.__init__ is called.
55 # Then tests run, calling things like write() and addSuccess(). Each test is
56 # framed by a startTest/stopTest call.
58 # Then the results are emitted, calling things like printErrors,
59 # printSummary, and wasSuccessful.
61 # So for code-coverage (not including import), start in __init__ and finish
62 # in printSummary. To include import, we have to start in our own import and
63 # finish in printSummary.
68 class FigleafReporter(TreeReporter):
69 def __init__(self, *args, **kwargs):
70 TreeReporter.__init__(self, *args, **kwargs)
72 def printSummary(self):
74 figleaf.write_coverage(".figleaf")
75 print "Figleaf results written to .figleaf"
76 return TreeReporter.printSummary(self)
78 class FigleafTextReporter(VerboseTextReporter):
79 def __init__(self, *args, **kwargs):
80 VerboseTextReporter.__init__(self, *args, **kwargs)
82 def printSummary(self):
84 figleaf.write_coverage(".figleaf")
85 print "Figleaf results written to .figleaf"
86 return VerboseTextReporter.printSummary(self)
88 class not_FigleafReporter(object):
89 # this class, used as a reporter on a fully-passing test suite, doesn't
90 # trigger exceptions. So it is a guide to what methods are invoked on a
92 def __init__(self, *args, **kwargs):
94 self.r = TreeReporter(*args, **kwargs)
95 self.shouldStop = self.r.shouldStop
96 self.separator = self.r.separator
97 self.testsRun = self.r.testsRun
98 self._starting2 = False
100 def write(self, *args):
101 if not self._starting2:
102 self._starting2 = True
104 return self.r.write(*args)
106 def startTest(self, *args, **kwargs):
107 return self.r.startTest(*args, **kwargs)
109 def stopTest(self, *args, **kwargs):
110 return self.r.stopTest(*args, **kwargs)
112 def addSuccess(self, *args, **kwargs):
113 return self.r.addSuccess(*args, **kwargs)
115 def printErrors(self, *args, **kwargs):
116 return self.r.printErrors(*args, **kwargs)
118 def writeln(self, *args, **kwargs):
119 return self.r.writeln(*args, **kwargs)
121 def printSummary(self, *args, **kwargs):
122 print "PRINT SUMMARY"
123 return self.r.printSummary(*args, **kwargs)
125 def wasSuccessful(self, *args, **kwargs):
126 return self.r.wasSuccessful(*args, **kwargs)