# HG changeset patch # User cmlenz # Date 1187714573 0 # Node ID a8787de4fbc3eedd12a05e088d9f25a307bf32e4 # Parent 641a73f078f507ad1f3a6336a41f60aa370696ee Improve the still experimental support for using `coverage.py`. diff --git a/bitten/build/pythontools.py b/bitten/build/pythontools.py --- a/bitten/build/pythontools.py +++ b/bitten/build/pythontools.py @@ -175,11 +175,11 @@ :param exclude: patterns of files or directories to exclude from the report """ assert summary, 'Missing required attribute "summary"' - assert coverdir, 'Missing required attribute "coverdir"' - summary_line_re = re.compile(r'^\s*(?P\d+)\s+(?P\d+)%\s+' - r'(?P.*?)\s+\((?P.*?)\)') - coverage_line_re = re.compile(r'\s*(?:(?P\d+): )?(?P.*)') + summary_line_re = re.compile(r'^(?P.*?)\s+(?P\d+)\s+' + r'(?P\d+)\s+(?P\d+)%\s+' + r'(?:(?P(?:\d+(?:-\d+)?(?:, )?)*)\s+)?' + r'(?P.+)$') fileset = FileSet(ctxt.basedir, include, exclude) missing_files = [] @@ -189,46 +189,6 @@ missing_files.append(filename) covered_modules = set() - def handle_file(elem, sourcefile, coverfile=None): - code_lines = set() - for lineno, linetype, line in loc.count(sourcefile): - if linetype == loc.CODE: - code_lines.add(lineno) - num_covered = 0 - lines = [] - - if coverfile: - prev_hits = '0' - for idx, coverline in enumerate(coverfile): - match = coverage_line_re.search(coverline) - if match: - hits = match.group(1) - if hits: # Line covered - if hits != '0': - num_covered += 1 - lines.append(hits) - prev_hits = hits - elif coverline.startswith('>'): # Line not covered - lines.append('0') - prev_hits = '0' - elif idx not in code_lines: # Not a code line - lines.append('-') - prev_hits = '0' - else: # A code line not flagged by trace.py - if prev_hits != '0': - num_covered += 1 - lines.append(prev_hits) - - elem.append(xmlio.Element('line_hits')[' '.join(lines)]) - - num_lines = len(code_lines) - if num_lines: - percentage = int(round(num_covered * 100 / num_lines)) - else: - percentage = 0 - elem.attr['percentage'] = percentage - elem.attr['lines'] = num_lines - try: summary_file = open(ctxt.resolve(summary), 'r') try: @@ -236,8 +196,8 @@ for summary_line in summary_file: match = summary_line_re.search(summary_line) if match: - modname = match.group(3) - filename = match.group(4) + modname = match.group(1) + filename = match.group(6) if not os.path.isabs(filename): filename = os.path.normpath(os.path.join(ctxt.basedir, filename)) @@ -249,26 +209,13 @@ if not filename in fileset: continue + percentage = int(match.group(4).rstrip('%')) + missing_files.remove(filename) covered_modules.add(modname) module = xmlio.Element('coverage', name=modname, - file=filename.replace(os.sep, '/')) - sourcefile = file(ctxt.resolve(filename)) - try: - coverpath = ctxt.resolve(coverdir, modname + '.cover') - if os.path.isfile(coverpath): - coverfile = file(coverpath, 'r') - else: - log.warning('No coverage file for module %s at %s', - modname, coverpath) - coverfile = None - try: - handle_file(module, sourcefile, coverfile) - finally: - if coverfile: - coverfile.close() - finally: - sourcefile.close() + file=filename.replace(os.sep, '/'), + percentage=percentage) coverage.append(module) for filename in missing_files: @@ -279,12 +226,6 @@ module = xmlio.Element('coverage', name=modname, file=filename.replace(os.sep, '/'), percentage=0) - filepath = ctxt.resolve(filename) - fileobj = file(filepath, 'r') - try: - handle_file(module, fileobj) - finally: - fileobj.close() coverage.append(module) ctxt.report('coverage', coverage) diff --git a/bitten/slave.py b/bitten/slave.py --- a/bitten/slave.py +++ b/bitten/slave.py @@ -58,7 +58,7 @@ def __init__(self, url, name=None, config=None, dry_run=False, work_dir=None, keep_files=False, single_build=False, - username=None, password=None): + username=None, password=None, dump_reports=False): """Create the build slave instance. :param url: the URL of the build master, or the path to a build recipe @@ -75,6 +75,9 @@ :param username: the username to use when authentication against the build master is requested :param password: the password to use when authentication is needed + :param dump_reports: whether report data should be written to the + standard output, in addition to being transmitted + to the build master """ self.url = url self.local = not url.startswith('http') and not url.startswith('https') @@ -90,6 +93,7 @@ self.work_dir = work_dir self.keep_files = keep_files self.single_build = single_build + self.dump_reports = dump_reports if not self.local: self.opener = urllib2.build_opener(SaneHTTPErrorProcessor) @@ -211,6 +215,8 @@ step.execute(recipe.ctxt): if type == Recipe.ERROR: failed = True + if type == Recipe.REPORT and self.dump_reports: + print output xml.append(xmlio.Element(type, category=category, generator=generator)[ output @@ -272,28 +278,36 @@ help='name of this slave (defaults to host name)') parser.add_option('-f', '--config', action='store', dest='config', metavar='FILE', help='path to configuration file') - parser.add_option('-d', '--work-dir', action='store', dest='work_dir', - metavar='DIR', help='working directory for builds') - parser.add_option('-k', '--keep-files', action='store_true', - dest='keep_files', - help='don\'t delete files after builds') - parser.add_option('-l', '--log', dest='logfile', metavar='FILENAME', - help='write log messages to FILENAME') - parser.add_option('-n', '--dry-run', action='store_true', dest='dry_run', - help='don\'t report results back to master') - parser.add_option('-v', '--verbose', action='store_const', dest='loglevel', - const=logging.DEBUG, help='print as much as possible') - parser.add_option('-q', '--quiet', action='store_const', dest='loglevel', - const=logging.WARN, help='print as little as possible') - parser.add_option('-s', '--single', action='store_true', - dest='single_build', - help='exit after completing a single build') parser.add_option('-u', '--user', dest='username', help='the username to use for authentication') parser.add_option('-p', '--password', dest='password', help='the password to use when authenticating') + + group = parser.add_option_group('building') + group.add_option('-d', '--work-dir', action='store', dest='work_dir', + metavar='DIR', help='working directory for builds') + group.add_option('-k', '--keep-files', action='store_true', + dest='keep_files', + help='don\'t delete files after builds') + group.add_option('-s', '--single', action='store_true', + dest='single_build', + help='exit after completing a single build') + group.add_option('-n', '--dry-run', action='store_true', dest='dry_run', + help='don\'t report results back to master') + + group = parser.add_option_group('logging') + group.add_option('-l', '--log', dest='logfile', metavar='FILENAME', + help='write log messages to FILENAME') + group.add_option('-v', '--verbose', action='store_const', dest='loglevel', + const=logging.DEBUG, help='print as much as possible') + group.add_option('-q', '--quiet', action='store_const', dest='loglevel', + const=logging.WARN, help='print as little as possible') + group.add_option('--dump-reports', action='store_true', dest='dump_reports', + help='whether report data should be printed') + parser.set_defaults(dry_run=False, keep_files=False, - loglevel=logging.INFO, single_build=False) + loglevel=logging.INFO, single_build=False, + dump_reports=False) options, args = parser.parse_args() if len(args) < 1: @@ -319,7 +333,8 @@ dry_run=options.dry_run, work_dir=options.work_dir, keep_files=options.keep_files, single_build=options.single_build, - username=options.username, password=options.password) + username=options.username, password=options.password, + dump_reports=options.dump_reports) try: try: slave.run() diff --git a/bitten/util/testrunner.py b/bitten/util/testrunner.py --- a/bitten/util/testrunner.py +++ b/bitten/util/testrunner.py @@ -31,8 +31,6 @@ def __init__(self, stream, descriptions, verbosity): _TextTestResult.__init__(self, stream, descriptions, verbosity) self.tests = [] - self.orig_stdout = self.orig_stderr = None - self.buf_stdout = self.buf_stderr = None def startTest(self, test): _TextTestResult.startTest(self, test) @@ -41,17 +39,8 @@ filename = filename[:-1] self.tests.append([test, filename, time.time(), None, None]) - # Record output by the test to stdout and stderr - self.old_stdout, self.buf_stdout = sys.stdout, StringIO() - self.old_stderr, self.buf_stderr = sys.stderr, StringIO() - sys.stdout, sys.stderr = self.buf_stdout, self.buf_stderr - def stopTest(self, test): self.tests[-1][2] = time.time() - self.tests[-1][2] - self.tests[-1][3] = self.buf_stdout.getvalue() - self.tests[-1][4] = self.buf_stderr.getvalue() - sys.stdout, sys.stderr = self.orig_stdout, self.orig_stderr - _TextTestResult.stopTest(self, test) @@ -159,7 +148,6 @@ import coverage coverage.use_cache(False) coverage.start() - log.info('running tests under coverage.py') try: self._run_tests() finally: @@ -185,7 +173,9 @@ fileobj.write(line) continue filename = os.path.normpath(sys.modules[name].__file__) - fileobj.write(line + ' ' + filename + '\n') + if filename.endswith('.pyc') or filename.endswith('.pyo'): + filename = filename[:-1] + fileobj.write(line.rstrip() + ' ' + filename + '\n') finally: fileobj.close() diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -69,6 +69,7 @@ NS + 'php#phing = bitten.build.phptools:phing', NS + 'php#phpunit = bitten.build.phptools:phpunit', NS + 'php#coverage = bitten.build.phptools:coverage', + NS + 'python#coverage = bitten.build.pythontools:coverage', NS + 'python#distutils = bitten.build.pythontools:distutils', NS + 'python#exec = bitten.build.pythontools:exec_', NS + 'python#pylint = bitten.build.pythontools:pylint',