1 | """This plugin will run tests using the hotshot profiler, which is part |
---|
2 | of the standard library. To turn it on, use the ``--with-profile`` option |
---|
3 | or set the NOSE_WITH_PROFILE environment variable. Profiler output can be |
---|
4 | controlled with the ``--profile-sort`` and ``--profile-restrict`` options, |
---|
5 | and the profiler output file may be changed with ``--profile-stats-file``. |
---|
6 | |
---|
7 | See the `hotshot documentation`_ in the standard library documentation for |
---|
8 | more details on the various output options. |
---|
9 | |
---|
10 | .. _hotshot documentation: http://docs.python.org/library/hotshot.html |
---|
11 | """ |
---|
12 | |
---|
13 | try: |
---|
14 | import hotshot |
---|
15 | from hotshot import stats |
---|
16 | except ImportError: |
---|
17 | hotshot, stats = None, None |
---|
18 | import logging |
---|
19 | import os |
---|
20 | import sys |
---|
21 | import tempfile |
---|
22 | from nose.plugins.base import Plugin |
---|
23 | from nose.util import tolist |
---|
24 | |
---|
25 | log = logging.getLogger('nose.plugins') |
---|
26 | |
---|
27 | class Profile(Plugin): |
---|
28 | """ |
---|
29 | Use this plugin to run tests using the hotshot profiler. |
---|
30 | """ |
---|
31 | pfile = None |
---|
32 | clean_stats_file = False |
---|
33 | def options(self, parser, env): |
---|
34 | """Register commandline options. |
---|
35 | """ |
---|
36 | if not self.available(): |
---|
37 | return |
---|
38 | Plugin.options(self, parser, env) |
---|
39 | parser.add_option('--profile-sort', action='store', dest='profile_sort', |
---|
40 | default=env.get('NOSE_PROFILE_SORT', 'cumulative'), |
---|
41 | metavar="SORT", |
---|
42 | help="Set sort order for profiler output") |
---|
43 | parser.add_option('--profile-stats-file', action='store', |
---|
44 | dest='profile_stats_file', |
---|
45 | metavar="FILE", |
---|
46 | default=env.get('NOSE_PROFILE_STATS_FILE'), |
---|
47 | help='Profiler stats file; default is a new ' |
---|
48 | 'temp file on each run') |
---|
49 | parser.add_option('--profile-restrict', action='append', |
---|
50 | dest='profile_restrict', |
---|
51 | metavar="RESTRICT", |
---|
52 | default=env.get('NOSE_PROFILE_RESTRICT'), |
---|
53 | help="Restrict profiler output. See help for " |
---|
54 | "pstats.Stats for details") |
---|
55 | |
---|
56 | def available(cls): |
---|
57 | return hotshot is not None |
---|
58 | available = classmethod(available) |
---|
59 | |
---|
60 | def begin(self): |
---|
61 | """Create profile stats file and load profiler. |
---|
62 | """ |
---|
63 | if not self.available(): |
---|
64 | return |
---|
65 | self._create_pfile() |
---|
66 | self.prof = hotshot.Profile(self.pfile) |
---|
67 | |
---|
68 | def configure(self, options, conf): |
---|
69 | """Configure plugin. |
---|
70 | """ |
---|
71 | if not self.available(): |
---|
72 | self.enabled = False |
---|
73 | return |
---|
74 | Plugin.configure(self, options, conf) |
---|
75 | self.conf = conf |
---|
76 | if options.profile_stats_file: |
---|
77 | self.pfile = options.profile_stats_file |
---|
78 | self.clean_stats_file = False |
---|
79 | else: |
---|
80 | self.pfile = None |
---|
81 | self.clean_stats_file = True |
---|
82 | self.fileno = None |
---|
83 | self.sort = options.profile_sort |
---|
84 | self.restrict = tolist(options.profile_restrict) |
---|
85 | |
---|
86 | def prepareTest(self, test): |
---|
87 | """Wrap entire test run in :func:`prof.runcall`. |
---|
88 | """ |
---|
89 | if not self.available(): |
---|
90 | return |
---|
91 | log.debug('preparing test %s' % test) |
---|
92 | def run_and_profile(result, prof=self.prof, test=test): |
---|
93 | self._create_pfile() |
---|
94 | prof.runcall(test, result) |
---|
95 | return run_and_profile |
---|
96 | |
---|
97 | def report(self, stream): |
---|
98 | """Output profiler report. |
---|
99 | """ |
---|
100 | log.debug('printing profiler report') |
---|
101 | self.prof.close() |
---|
102 | prof_stats = stats.load(self.pfile) |
---|
103 | prof_stats.sort_stats(self.sort) |
---|
104 | |
---|
105 | # 2.5 has completely different stream handling from 2.4 and earlier. |
---|
106 | # Before 2.5, stats objects have no stream attribute; in 2.5 and later |
---|
107 | # a reference sys.stdout is stored before we can tweak it. |
---|
108 | compat_25 = hasattr(stats, 'stream') |
---|
109 | if compat_25: |
---|
110 | tmp = prof_stats.stream |
---|
111 | stats.stream = stream |
---|
112 | else: |
---|
113 | tmp = sys.stdout |
---|
114 | sys.stdout = stream |
---|
115 | try: |
---|
116 | if self.restrict: |
---|
117 | log.debug('setting profiler restriction to %s', self.restrict) |
---|
118 | prof_stats.print_stats(*self.restrict) |
---|
119 | else: |
---|
120 | prof_stats.print_stats() |
---|
121 | finally: |
---|
122 | if compat_25: |
---|
123 | stats.stream = tmp |
---|
124 | else: |
---|
125 | sys.stdout = tmp |
---|
126 | |
---|
127 | def finalize(self, result): |
---|
128 | """Clean up stats file, if configured to do so. |
---|
129 | """ |
---|
130 | if not self.available(): |
---|
131 | return |
---|
132 | try: |
---|
133 | self.prof.close() |
---|
134 | except AttributeError: |
---|
135 | # TODO: is this trying to catch just the case where not |
---|
136 | # hasattr(self.prof, "close")? If so, the function call should be |
---|
137 | # moved out of the try: suite. |
---|
138 | pass |
---|
139 | if self.clean_stats_file: |
---|
140 | if self.fileno: |
---|
141 | try: |
---|
142 | os.close(self.fileno) |
---|
143 | except OSError: |
---|
144 | pass |
---|
145 | try: |
---|
146 | os.unlink(self.pfile) |
---|
147 | except OSError: |
---|
148 | pass |
---|
149 | return None |
---|
150 | |
---|
151 | def _create_pfile(self): |
---|
152 | if not self.pfile: |
---|
153 | self.fileno, self.pfile = tempfile.mkstemp() |
---|
154 | self.clean_stats_file = True |
---|