| 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 |
|---|