Mercurial > bitten > bitten-test
changeset 302:fe966b950424
* Add a `<c:gcov>` command based on patch by Chandler Carruth. Closes #72.
* Enable more extensive unit testing of recipe commands by providing a simple ''dummy'' implementation of the `CommandLine` class.
author | cmlenz |
---|---|
date | Mon, 07 Nov 2005 17:58:22 +0000 |
parents | d486e34084af |
children | 3d58d9dd11c8 |
files | bitten/build/api.py bitten/build/ctools.py bitten/build/tests/ctools.py bitten/build/tests/dummy.py setup.py |
diffstat | 5 files changed, 178 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/bitten/build/api.py +++ b/bitten/build/api.py @@ -24,6 +24,23 @@ """Exception raised when the execution of a command times out.""" +def _combine(*iterables): + iterables = [iter(iterable) for iterable in iterables] + size = len(iterables) + while True: + to_yield = [None] * size + for idx, iterable in enumerate(iterables): + if iterable is None: + continue + try: + to_yield[idx] = iterable.next() + except StopIteration: + iterables[idx] = None + if not [iterable for iterable in iterables if iterable is not None]: + break + yield tuple(to_yield) + + class CommandLine(object): """Simple helper for executing subprocesses.""" # TODO: Use 'subprocess' module if available (Python >= 2.4) @@ -102,7 +119,7 @@ if self.cwd: os.chdir(old_cwd) - for out_line, err_line in self._combine(out_lines, err_lines): + for out_line, err_line in _combine(out_lines, err_lines): yield out_line and out_line.rstrip(), \ err_line and err_line.rstrip() @@ -157,7 +174,7 @@ err_eof = True out_lines = self._extract_lines(out_data) err_lines = self._extract_lines(err_data) - for out_line, err_line in self._combine(out_lines, err_lines): + for out_line, err_line in _combine(out_lines, err_lines): yield out_line, err_line time.sleep(.1) self.returncode = pipe.wait() @@ -167,22 +184,6 @@ if self.cwd: os.chdir(old_cwd) - def _combine(self, *iterables): - iterables = [iter(iterable) for iterable in iterables] - size = len(iterables) - while True: - to_yield = [None] * size - for idx, iterable in enumerate(iterables): - if iterable is None: - continue - try: - to_yield[idx] = iterable.next() - except StopIteration: - iterables[idx] = None - if not [iterable for iterable in iterables if iterable is not None]: - break - yield tuple(to_yield) - def _extract_lines(self, data): extracted = [] def _endswith_linesep(string):
--- a/bitten/build/ctools.py +++ b/bitten/build/ctools.py @@ -8,8 +8,14 @@ # are also available at http://bitten.cmlenz.net/wiki/License. import logging +import re +import os +try: + set +except NameError: + from sets import Set as set -from bitten.build import CommandLine +from bitten.build import CommandLine, FileSet from bitten.util import xmlio log = logging.getLogger('bitten.build.ctools') @@ -118,3 +124,69 @@ except xmlio.ParseError, e: print e log.warning('Error parsing CppUnit results file (%s)', e) + +def gcov(ctxt, include=None, exclude=None, prefix=None): + """Run `gcov` to extract coverage data where available.""" + file_re = re.compile(r'^File \`(?P<file>[^\']+)\'\s*$') + lines_re = re.compile(r'^Lines executed:(?P<cov>\d+\.\d+)\% of (?P<num>\d+)\s*$') + + files = [] + for filename in FileSet(ctxt.basedir, include, exclude): + if os.path.splitext(filename)[1] in ('.c', '.cpp', '.cc', '.cxx'): + files.append(filename) + + coverage = xmlio.Fragment() + + for srcfile in files: + # Determine the coverage for each source file by looking for a .gcno + # and .gcda pair + filepath, filename = os.path.split(srcfile) + stem = os.path.splitext(filename)[0] + if prefix is not None: + stem = prefix + '-' + stem + + objfile = os.path.join(filepath, stem + '.o') + if not os.path.isfile(ctxt.resolve(objfile)): + log.warn('No object file found for %s at %s', srcfile, objfile) + continue + if not os.path.isfile(ctxt.resolve(stem + '.gcno')): + log.warn('No .gcno file found for %s', srcfile) + continue + if not os.path.isfile(ctxt.resolve(stem + '.gcda')): + log.warn('No .gcda file found for %s', srcfile) + continue + + num_lines, num_covered = 0, 0 + skip_block = False + cmd = CommandLine('gcov', ['-b', '-n', '-o', objfile, srcfile], + cwd=ctxt.basedir) + for out, err in cmd.execute(): + if out == '': # catch blank lines, reset the block state... + skip_block = False + elif out and not skip_block: + # Check for a file name + match = file_re.match(out) + if match: + if os.path.isabs(match.group('file')): + skip_block = True + continue + else: + # check for a "Lines executed" message + match = lines_re.match(out) + if match: + lines = float(match.group('num')) + cov = float(match.group('cov')) + num_covered += int(lines * cov / 100) + num_lines += int(lines) + if cmd.returncode != 0: + continue + + module = xmlio.Element('coverage', name=os.path.basename(srcfile), + file=srcfile.replace(os.sep, '/'), + lines=num_lines, percentage=0) + if num_lines: + percent = int(round(num_covered * 100 / num_lines)) + module.attr['percentage'] = percent + coverage.append(module) + + ctxt.report('coverage', coverage)
--- a/bitten/build/tests/ctools.py +++ b/bitten/build/tests/ctools.py @@ -13,6 +13,7 @@ import unittest from bitten.build import ctools +from bitten.build.tests import dummy from bitten.recipe import Context, Recipe @@ -83,9 +84,67 @@ self.assertEqual('success', tests[2].attr['status']) +class GCovTestCase(unittest.TestCase): + + def setUp(self): + self.basedir = os.path.realpath(tempfile.mkdtemp()) + self.ctxt = Context(self.basedir) + + def tearDown(self): + shutil.rmtree(self.basedir) + + def _create_file(self, *path): + filename = os.path.join(self.basedir, *path) + dirname = os.path.dirname(filename) + if not os.path.isdir(dirname): + os.makedirs(dirname) + fd = file(filename, 'w') + fd.close() + return filename[len(self.basedir) + 1:] + + def test_no_file(self): + ctools.CommandLine = dummy.CommandLine() + ctools.gcov(self.ctxt) + type, category, generator, xml = self.ctxt.output.pop() + self.assertEqual('report', type) + self.assertEqual('coverage', category) + self.assertEqual(0, len(xml.children)) + + def test_single_file(self): + self._create_file('foo.c') + self._create_file('foo.o') + self._create_file('foo.gcno') + self._create_file('foo.gcda') + + ctools.CommandLine = dummy.CommandLine(stdout=""" +File `foo.c' +Lines executed:45.81% of 884 +Branches executed:54.27% of 398 +Taken at least once:36.68% of 398 +Calls executed:48.19% of 249 + +File `foo.h' +Lines executed:50.00% of 4 +No branches +Calls executed:100.00% of 1 +""") + ctools.gcov(self.ctxt) + type, category, generator, xml = self.ctxt.output.pop() + self.assertEqual('report', type) + self.assertEqual('coverage', category) + self.assertEqual(1, len(xml.children)) + elem = xml.children[0] + self.assertEqual('coverage', elem.name) + self.assertEqual('foo.c', elem.attr['file']) + self.assertEqual('foo.c', elem.attr['name']) + self.assertEqual(888, elem.attr['lines']) + self.assertEqual(45, elem.attr['percentage']) + + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(CppUnitTestCase, 'test')) + suite.addTest(unittest.makeSuite(GCovTestCase, 'test')) return suite if __name__ == '__main__':
new file mode 100644 --- /dev/null +++ b/bitten/build/tests/dummy.py @@ -0,0 +1,26 @@ +# -*- coding: iso8859-1 -*- +# +# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de> +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://bitten.cmlenz.net/wiki/License. + +from StringIO import StringIO + +from bitten.build import api + + +class CommandLine(api.CommandLine): + + def __init__(self, returncode=0, stdout='', stderr=''): + self.returncode = returncode + self.stdout = StringIO(stdout) + self.stderr = StringIO(stderr) + + def __call__(self, executable, args, input=None, cwd=None): + return self + + def execute(self): + return api._combine(self.stdout.readlines(), self.stderr.readlines())
--- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ NS + 'sh#pipe = bitten.build.shtools:pipe', NS + 'c#configure = bitten.build.ctools:configure', NS + 'c#cppunit = bitten.build.ctools:cppunit', + NS + 'c#gcov = bitten.build.ctools:gcov', NS + 'c#make = bitten.build.ctools:make', NS + 'java#ant = bitten.build.javatools:ant', NS + 'java#junit = bitten.build.javatools:junit',