Mercurial > bitten > bitten-test
changeset 597:4c3d43adaa48
0.6dev: Fixed `php:phpunit` parsing including support for nested tests. Extended `php:coverage` to also parse PHPUnit coverage-clover format. Closes #199 and #316.
Thanks to all those that have helped develop the patch, and special thanks to Roland Wilczek for contributing, testing and helping me get working php builds for development.
author | osimons |
---|---|
date | Wed, 29 Jul 2009 10:43:48 +0000 |
parents | b1c90136f84a |
children | 5f3e66e5b451 |
files | bitten/build/phptools.py bitten/build/tests/phptools.py doc/commands.txt |
diffstat | 3 files changed, 216 insertions(+), 51 deletions(-) [+] |
line wrap: on
line diff
--- a/bitten/build/phptools.py +++ b/bitten/build/phptools.py @@ -35,36 +35,45 @@ def phpunit(ctxt, file_=None): """Extract test results from a PHPUnit XML report.""" assert file_, 'Missing required attribute "file"' + + def _process_testsuite(testsuite, results, parent_file=''): + for testcase in testsuite.children(): + if testcase.name == 'testsuite': + _process_testsuite(testcase, results, + parent_file=testcase.attr.get('file', parent_file)) + continue + test = xmlio.Element('test') + test.attr['fixture'] = testsuite.attr['name'] + test.attr['name'] = testcase.attr['name'] + test.attr['duration'] = testcase.attr['time'] + result = list(testcase.children()) + if result: + test.append(xmlio.Element('traceback')[ + result[0].gettext() + ]) + test.attr['status'] = result[0].name + else: + test.attr['status'] = 'success' + if 'file' in testsuite.attr or parent_file: + testfile = os.path.realpath( + testsuite.attr.get('file', parent_file)) + if testfile.startswith(ctxt.basedir): + testfile = testfile[len(ctxt.basedir) + 1:] + testfile = testfile.replace(os.sep, '/') + test.attr['file'] = testfile + results.append(test) + try: total, failed = 0, 0 results = xmlio.Fragment() fileobj = file(ctxt.resolve(file_), 'r') try: - for testsuit in xmlio.parse(fileobj).children('testsuite'): - total += int(testsuit.attr['tests']) - failed += int(testsuit.attr['failures']) + \ - int(testsuit.attr['errors']) + for testsuite in xmlio.parse(fileobj).children('testsuite'): + total += int(testsuite.attr['tests']) + failed += int(testsuite.attr['failures']) + \ + int(testsuite.attr['errors']) - for testcase in testsuit.children(): - test = xmlio.Element('test') - test.attr['fixture'] = testcase.attr['class'] - test.attr['name'] = testcase.attr['name'] - test.attr['duration'] = testcase.attr['time'] - result = list(testcase.children()) - if result: - test.append(xmlio.Element('traceback')[ - result[0].gettext() - ]) - test.attr['status'] = result[0].name - else: - test.attr['status'] = 'success' - if 'file' in testsuit.attr: - testfile = os.path.realpath(testsuit.attr['file']) - if testfile.startswith(ctxt.basedir): - testfile = testfile[len(ctxt.basedir) + 1:] - testfile = testfile.replace(os.sep, '/') - test.attr['file'] = testfile - results.append(test) + _process_testsuite(testsuite, results) finally: fileobj.close() if failed: @@ -77,33 +86,67 @@ ctxt.log('Error parsing PHPUnit results file (%s)' % e) def coverage(ctxt, file_=None): - """Extract data from a Phing code coverage report.""" + """Extract data from Phing or PHPUnit code coverage report.""" assert file_, 'Missing required attribute "file"' + + def _process_phing_coverage(ctxt, element, coverage): + for cls in element.children('class'): + statements = float(cls.attr['statementcount']) + covered = float(cls.attr['statementscovered']) + if statements: + percentage = covered / statements * 100 + else: + percentage = 100 + class_coverage = xmlio.Element('coverage', + name=cls.attr['name'], + lines=int(statements), + percentage=percentage + ) + source = list(cls.children())[0] + if 'sourcefile' in source.attr: + sourcefile = os.path.realpath(source.attr['sourcefile']) + if sourcefile.startswith(ctxt.basedir): + sourcefile = sourcefile[len(ctxt.basedir) + 1:] + sourcefile = sourcefile.replace(os.sep, '/') + class_coverage.attr['file'] = sourcefile + coverage.append(class_coverage) + + def _process_phpunit_coverage(ctxt, element, coverage): + for cls in element._node.getElementsByTagName('class'): + sourcefile = cls.parentNode.getAttribute('name') + if not os.path.isabs(sourcefile): + sourcefile = os.path.join(ctxt.basedir, sourcefile) + if sourcefile.startswith(ctxt.basedir): + loc, ncloc = 0, 0.0 + for line in cls.parentNode.getElementsByTagName('line'): + if str(line.getAttribute('type')) == 'stmt': + loc += 1 + if int(line.getAttribute('count')) == 0: + ncloc += 1 + if loc > 0: + percentage = 100 - (ncloc / loc * 100) + else: + percentage = 100 + + if sourcefile.startswith(ctxt.basedir): + sourcefile = sourcefile[len(ctxt.basedir) + 1:] + class_coverage = xmlio.Element('coverage', + name=cls.getAttribute('name'), + lines=int(loc), + percentage=int(percentage), + file=sourcefile.replace(os.sep, '/')) + coverage.append(class_coverage) + try: summary_file = file(ctxt.resolve(file_), 'r') + summary = xmlio.parse(summary_file) + coverage = xmlio.Fragment() try: - coverage = xmlio.Fragment() - for package in xmlio.parse(summary_file).children('package'): - for cls in package.children('class'): - statements = float(cls.attr['statementcount']) - covered = float(cls.attr['statementscovered']) - if statements: - percentage = covered / statements * 100 - else: - percentage = 100 - class_coverage = xmlio.Element('coverage', - name=cls.attr['name'], - lines=int(statements), - percentage=percentage - ) - source = list(cls.children())[0] - if 'sourcefile' in source.attr: - sourcefile = os.path.realpath(source.attr['sourcefile']) - if sourcefile.startswith(ctxt.basedir): - sourcefile = sourcefile[len(ctxt.basedir) + 1:] - sourcefile = sourcefile.replace(os.sep, '/') - class_coverage.attr['file'] = sourcefile - coverage.append(class_coverage) + for element in summary.children(): + if element.name == 'package': + _process_phing_coverage(ctxt, element, coverage) + elif element.name == 'project': + _process_phpunit_coverage(ctxt, element, coverage) finally: summary_file.close() ctxt.report('coverage', coverage)
--- a/bitten/build/tests/phptools.py +++ b/bitten/build/tests/phptools.py @@ -77,14 +77,14 @@ def test_missing_param_file(self): self.assertRaises(AssertionError, phptools.coverage, self.ctxt) - def test_sample_code_coverage(self): + def test_sample_phing_code_coverage(self): coverage_xml = file(self.ctxt.resolve('phpcoverage.xml'), 'w') coverage_xml.write("""<?xml version="1.0" encoding="UTF-8"?> <snapshot methodcount="4" methodscovered="2" statementcount="11" statementscovered="5" totalcount="15" totalcovered="7"> <package name="default" methodcount="4" methodscovered="2" statementcount="11" statementscovered="5" totalcount="15" totalcovered="7"> <class name="Foo" methodcount="1" methodscovered="1" statementcount="7" statementscovered="3" totalcount="8" totalcovered="4"> <sourcefile name="Foo.php" sourcefile="xxxx/Foo.php"> - ... + ... </sourcefile> </class> <class name="Foo2" methodcount="2" methodscovered="1" statementcount="4" statementscovered="2" totalcount="6" totalcovered="3"> @@ -121,6 +121,128 @@ self.assertEqual('Bar', coverage[2].attr['name']) self.assert_('xxxx/Bar.php' in coverage[2].attr['file']) + def test_sample_phpunit_code_coverage(self): + coverage_xml = file(self.ctxt.resolve('phpcoverage.xml'), 'w') + coverage_xml.write("""<?xml version="1.0" encoding="UTF-8"?> +<coverage generated="1248813201" phpunit="3.3.17"> + <project name="All Tests" timestamp="1248813201"> + <file name="%s/Foo/classes/Foo.php"> + <class name="Foo" namespace="global"> + <metrics methods="0" coveredmethods="0" statements="0" + coveredstatements="0" elements="0" coveredelements="0"/> + </class> + <line num="3" type="stmt" count="1"/> + <line num="6" type="stmt" count="1"/> + <metrics loc="5" ncloc="3" classes="1" methods="0" coveredmethods="0" + statements="2" coveredstatements="2" elements="2" coveredelements="2"/> + </file> + <file name="%s/Foo/tests/environment.config.php"> + <line num="0" type="stmt" count="2"/> + <line num="4" type="stmt" count="2"/> + <line num="5" type="stmt" count="2"/> + <metrics loc="6" ncloc="6" classes="0" methods="0" coveredmethods="0" + statements="3" coveredstatements="3" elements="3" coveredelements="3"/> + </file> + <file name="%s/Foo/tests/Foo/AllTests.php"> + <class name="All_Foo_Tests" namespace="global" fullPackage="All.Foo"> + <metrics methods="2" coveredmethods="0" statements="4" + coveredstatements="0" elements="6" coveredelements="0"/> + </class> + <line num="7" type="method" count="0"/> + <line num="9" type="stmt" count="0"/> + <line num="10" type="stmt" count="0"/> + <line num="12" type="method" count="0"/> + <line num="14" type="stmt" count="0"/> + <line num="15" type="stmt" count="0"/> + <line num="16" type="stmt" count="0"/> + <metrics loc="19" ncloc="19" classes="1" methods="2" coveredmethods="0" + statements="5" coveredstatements="0" elements="7" coveredelements="0"/> + </file> + <file name="%s/Foo/tests/AllTests.php"> + <class name="AllTests" namespace="global"> + <metrics methods="2" coveredmethods="0" statements="5" + coveredstatements="0" elements="7" coveredelements="0"/> + </class> + <line num="8" type="method" count="0"/> + <line num="10" type="stmt" count="0"/> + <line num="11" type="stmt" count="0"/> + <line num="13" type="method" count="0"/> + <line num="15" type="stmt" count="0"/> + <line num="16" type="stmt" count="0"/> + <line num="17" type="stmt" count="0"/> + <line num="18" type="stmt" count="0"/> + <metrics loc="22" ncloc="22" classes="1" methods="2" coveredmethods="0" + statements="6" coveredstatements="0" elements="8" coveredelements="0"/> + </file> + <file name="%s/Foo/tests/Bar/AllTests.php"> + <class name="All_Bar_Tests" namespace="global" fullPackage="All.Bar"> + <metrics methods="2" coveredmethods="0" statements="5" + coveredstatements="0" elements="7" coveredelements="0"/> + </class> + <line num="8" type="method" count="0"/> + <line num="10" type="stmt" count="0"/> + <line num="11" type="stmt" count="0"/> + <line num="13" type="method" count="0"/> + <line num="15" type="stmt" count="0"/> + <line num="16" type="stmt" count="0"/> + <line num="17" type="stmt" count="0"/> + <line num="18" type="stmt" count="0"/> + <metrics loc="20" ncloc="20" classes="1" methods="2" coveredmethods="0" + statements="6" coveredstatements="0" elements="8" coveredelements="0"/> + </file> + <file name="%s/Foo/tests/Bar/Nested/AllTests.php"> + <class name="All_Bar_Nested_Tests" namespace="global" fullPackage="All.Bar.Nested"> + <metrics methods="2" coveredmethods="0" statements="5" + coveredstatements="0" elements="7" coveredelements="0"/> + </class> + <line num="8" type="method" count="0"/> + <line num="10" type="stmt" count="0"/> + <line num="11" type="stmt" count="0"/> + <line num="13" type="method" count="0"/> + <line num="15" type="stmt" count="0"/> + <line num="16" type="stmt" count="0"/> + <line num="17" type="stmt" count="0"/> + <line num="18" type="stmt" count="0"/> + <metrics loc="21" ncloc="21" classes="1" methods="2" coveredmethods="0" + statements="6" coveredstatements="0" elements="8" coveredelements="0"/> + </file> + <file name="Foo/classes/Bar.php"> + <class name="Bar" namespace="global"> + <metrics methods="0" coveredmethods="0" statements="0" + coveredstatements="0" elements="0" coveredelements="0"/> + </class> + <line num="3" type="stmt" count="1"/> + <line num="6" type="stmt" count="1"/> + <metrics loc="5" ncloc="3" classes="1" methods="0" coveredmethods="0" + statements="2" coveredstatements="2" elements="2" coveredelements="2"/> + </file> + <metrics files="7" loc="98" ncloc="94" classes="6" methods="8" coveredmethods="0" + statements="30" coveredstatements="7" elements="38" coveredelements="7"/> + </project> +</coverage>""" % ((self.basedir,)*6)) # One relative path, remaining is absolute + coverage_xml.close() + phptools.coverage(self.ctxt, file_='phpcoverage.xml') + type, category, generator, xml = self.ctxt.output.pop() + self.assertEqual(Recipe.REPORT, type) + self.assertEqual('coverage', category) + + coverage = list(xml.children) + self.assertEqual(6, len(coverage)) + + self.assertEqual(27, sum([int(c.attr['lines']) for c in coverage])) + self.assertEqual(['Foo', 'All_Foo_Tests', 'AllTests', 'All_Bar_Tests', + 'All_Bar_Nested_Tests', 'Bar'], + [c.attr['name'] for c in coverage]) + self.assertEqual(['Foo/classes/Foo.php', + 'Foo/tests/Foo/AllTests.php', + 'Foo/tests/AllTests.php', + 'Foo/tests/Bar/AllTests.php', + 'Foo/tests/Bar/Nested/AllTests.php', + 'Foo/classes/Bar.php'], + [c.attr['file'] for c in coverage]) + self.assertEqual([100, 0, 0, 0, 0, 100], + [c.attr['percentage'] for c in coverage]) + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(PhpUnitTestCase, 'test'))
--- a/doc/commands.txt +++ b/doc/commands.txt @@ -545,8 +545,8 @@ ``<php:coverage>`` ------------------ -Extracts coverage information Phing_'s code coverage task recorded in an XML -file. +Extracts coverage information from Phing_'s code coverage task XML file or +from PHPUnit_ coverage-clover XML file. Parameters ----------