Mercurial > bitten > bitten-test
changeset 416:ff35be7d2a5e
Add PHP recipe commands contributed by Wei Zhuo.
author | cmlenz |
---|---|
date | Wed, 08 Aug 2007 12:43:16 +0000 |
parents | b4ec24092b54 |
children | ab2557ff0d97 |
files | bitten/build/phptools.py bitten/build/tests/__init__.py bitten/build/tests/phptools.py doc/commands.txt setup.py |
diffstat | 5 files changed, 351 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/bitten/build/phptools.py @@ -0,0 +1,113 @@ +# -*- coding: UTF-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007 Wei Zhuo <weizhuo@gmail.com> +# 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. + +import logging +import os +import shlex + +from bitten.util import xmlio +from bitten.build import shtools + +log = logging.getLogger('bitten.build.phptools') + +def phing(ctxt, file_=None, target_=None, executable_=None, args=None): + """Run a phing build""" + if args: + args = shlex.split(args) + else: + args = [] + args += ['-logger', 'phing.listener.DefaultLogger', + '-buildfile', ctxt.resolve(file_ or 'build.xml')] + if target_: + args.append(target_) + + returncode = shtools.execute(ctxt, file_= executable_ or 'phing', args=args) + if returncode != 0: + ctxt.error('Phing failed (%s)' % returncode) + +def phpunit(ctxt, file_=None): + """Extract test results from a PHPUnit XML report.""" + assert file_, 'Missing required attribute "file"' + 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 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) + finally: + fileobj.close() + if failed: + ctxt.error('%d of %d test%s failed' % (failed, total, + total != 1 and 's' or '')) + ctxt.report('test', results) + except IOError, e: + ctxt.log('Error opening PHPUnit results file (%s)' % e) + except xmlio.ParseError, e: + ctxt.log('Error parsing PHPUnit results file (%s)' % e) + +def coverage(ctxt, file_=None): + """Extract data from a Phing code coverage report.""" + assert file_, 'Missing required attribute "file"' + try: + summary_file = file(ctxt.resolve(file_), 'r') + 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) + finally: + summary_file.close() + ctxt.report('coverage', coverage) + except IOError, e: + ctxt.log('Error opening coverage summary file (%s)' % e) + except xmlio.ParseError, e: + ctxt.log('Error parsing coverage summary file (%s)' % e)
--- a/bitten/build/tests/__init__.py +++ b/bitten/build/tests/__init__.py @@ -10,13 +10,15 @@ import unittest -from bitten.build.tests import api, config, ctools, pythontools, xmltools +from bitten.build.tests import api, config, ctools, phptools, pythontools, \ + xmltools def suite(): suite = unittest.TestSuite() suite.addTest(api.suite()) suite.addTest(config.suite()) suite.addTest(ctools.suite()) + suite.addTest(phptools.suite()) suite.addTest(pythontools.suite()) suite.addTest(xmltools.suite()) return suite
new file mode 100644 --- /dev/null +++ b/bitten/build/tests/phptools.py @@ -0,0 +1,131 @@ +# -*- coding: UTF-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007 Wei Zhuo <weizhuo@gmail.com> +# 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. + +import os +import shutil +import tempfile +import unittest + +from bitten.build import phptools +from bitten.recipe import Context, Recipe + +class PhpUnitTestCase(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 test_missing_param_file(self): + self.assertRaises(AssertionError, phptools.phpunit, self.ctxt) + + def test_sample_unit_test_result(self): + phpunit_xml = file(self.ctxt.resolve('phpunit.xml'), 'w') + phpunit_xml.write("""<?xml version="1.0" encoding="UTF-8"?> +<testsuites> + <testsuite name="FooTest" file="FooTest.php" tests="2" failures="1" errors="0" time="0.147397"> + <testcase name="testBar" class="FooTest" time="0.122265"> + <failure message="expected same: <1> was not: <2>" type="PHPUnit2_Framework_AssertionFailedError"> + ... +</failure> + </testcase> + <testcase name="testBar2" class="FooTest" time="0.025132"/> + </testsuite> + <testsuite name="BarTest" file="BarTest.php" tests="1" failures="0" errors="0" time="0.050713"> + <testcase name="testFoo" class="BarTest" time="0.026046"/> + </testsuite> +</testsuites>""") + phpunit_xml.close() + phptools.phpunit(self.ctxt, file_='phpunit.xml') + type, category, generator, xml = self.ctxt.output.pop() + self.assertEqual(Recipe.REPORT, type) + self.assertEqual('test', category) + + tests = list(xml.children) + self.assertEqual(3, len(tests)) + self.assertEqual('FooTest', tests[0].attr['fixture']) + self.assertEqual('testBar', tests[0].attr['name']) + self.assertEqual('failure', tests[0].attr['status']) + self.assert_('FooTest.php' in tests[0].attr['file']) + + self.assertEqual('FooTest', tests[1].attr['fixture']) + self.assertEqual('testBar2', tests[1].attr['name']) + self.assertEqual('success', tests[1].attr['status']) + + self.assertEqual('BarTest', tests[2].attr['fixture']) + self.assertEqual('testFoo', tests[2].attr['name']) + self.assertEqual('success', tests[2].attr['status']) + +class PhpCodeCoverageTestCase(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 test_missing_param_file(self): + self.assertRaises(AssertionError, phptools.coverage, self.ctxt) + + def test_sample_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"> + <sourcefile name="Foo.php" sourcefile="xxxx/Foo.php"> + ... + </sourcefile> + </class> + <class name="Bar" methodcount="1" methodscovered="0" statementcount="0" statementscovered="0" totalcount="1" totalcovered="0"> + <sourcefile name="Bar.php" sourcefile="xxxx/Bar.php"> + ... + </sourcefile> + </class> + </package> +</snapshot>""") + 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(3, len(coverage)) + self.assertEqual(7, coverage[0].attr['lines']) + self.assertEqual('Foo', coverage[0].attr['name']) + self.assert_('xxxx/Foo.php' in coverage[0].attr['file']) + + self.assertEqual(4, coverage[1].attr['lines']) + self.assertEqual(50.0, coverage[1].attr['percentage']) + self.assertEqual('Foo2', coverage[1].attr['name']) + self.assert_('xxxx/Foo.php' in coverage[1].attr['file']) + + self.assertEqual(0, coverage[2].attr['lines']) + self.assertEqual(100.0, coverage[2].attr['percentage']) + self.assertEqual('Bar', coverage[2].attr['name']) + self.assert_('xxxx/Bar.php' in coverage[2].attr['file']) + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(PhpUnitTestCase, 'test')) + suite.addTest(unittest.makeSuite(PhpCodeCoverageTestCase, 'test')) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite')
--- a/doc/commands.txt +++ b/doc/commands.txt @@ -366,6 +366,107 @@ files to Java source files in the directory `src/tests`. +PHP Tools +========= + +A bundle of recipe commands for PHP_ projects. + +:Namespace: ``http://bitten.cmlenz.net/tools/php`` +:Common prefix: ``php`` + +.. _php: http://php.net/ + +------------------ +``<python:phing>`` +------------------ + +Runs a Phing_ build. + +.. _phing: http://phing.info/ + +Parameters +---------- + ++-------------------+-------------------------------------------------------+ +| Name | Description | ++===================+=======================================================+ +| ``file`` | Path of the build file, relative to the project | +| | source directory (default is ``build.xml``). | ++-------------------+-------------------------------------------------------+ +| ``target`` | Name of the build target(s) to execute. | ++-------------------+-------------------------------------------------------+ +| ``args`` | Additional arguments to pass to Phing, separated by | +| | whitespace. | ++-------------------+-------------------------------------------------------+ +| ``executable`` | Phing executable program (default is ``phing``). | ++-------------------+-------------------------------------------------------+ + + +Examples +-------- + +.. code-block:: xml + + <php:phing target="compile" /> + +Executes the target ``compile`` of the ``build.xml`` buildfile at the top of the +project source directory. + + +----------------- +``<php:phpunit>`` +----------------- + +Extracts information from PHPUnit_ test results recorded in an XML file. + +.. _phpunit: http://www.phpunit.de/ + +Parameters +---------- + ++----------------+-----------------------------------------------------------+ +| Name | Description | ++================+===========================================================+ +| ``file`` | Path to the XML results file, relative to the project | +| | source directory. | ++----------------+-----------------------------------------------------------+ + +Examples +-------- + +.. code-block:: xml + + <php:phpunit file="build/test-results.xml"/> + +Extracts the test results from the XML file located at +``build/test-results.xml``. + + +------------------ +``<php:coverage>`` +------------------ + +Extracts coverage information Phing_'s code coverage task recorded in an XML +file. + +Parameters +---------- + ++---------------+-----------------------------------------------------------+ +| Name | Description | ++===============+===========================================================+ +| ``file`` | Path to the XML coverage file, relative to the project | +| | source directory. | ++---------------+-----------------------------------------------------------+ + +Examples +-------- + +.. code-block:: xml + + <php:coverage file="build/coverage.xml" /> + + Python Tools ============
--- a/setup.py +++ b/setup.py @@ -67,6 +67,9 @@ NS + 'java#ant = bitten.build.javatools:ant', NS + 'java#junit = bitten.build.javatools:junit', NS + 'java#cobertura = bitten.build.javatools:cobertura', + NS + 'php#phing = bitten.build.phptools:phing', + NS + 'php#phpunit = bitten.build.phptools:phpunit', + NS + 'php#coverage = bitten.build.phptools:coverage', NS + 'python#distutils = bitten.build.pythontools:distutils', NS + 'python#exec = bitten.build.pythontools:exec_', NS + 'python#pylint = bitten.build.pythontools:pylint',