| 1 | # -*- coding: UTF-8 -*- |
---|
| 2 | # |
---|
| 3 | # Copyright (C) 2007-2010 Edgewall Software |
---|
| 4 | # Copyright (C) 2007 Wei Zhuo <weizhuo@gmail.com> |
---|
| 5 | # All rights reserved. |
---|
| 6 | # |
---|
| 7 | # This software is licensed as described in the file COPYING, which |
---|
| 8 | # you should have received as part of this distribution. The terms |
---|
| 9 | # are also available at http://bitten.edgewall.org/wiki/License. |
---|
| 10 | |
---|
1 | 11 | "Recipe commands for tools commonly used in PHP projects." |
---|
| 12 | |
---|
1 | 13 | import logging |
---|
1 | 14 | import os |
---|
1 | 15 | import shlex |
---|
| 16 | |
---|
1 | 17 | from bitten.util import xmlio |
---|
1 | 18 | from bitten.build import shtools |
---|
| 19 | |
---|
1 | 20 | log = logging.getLogger('bitten.build.phptools') |
---|
| 21 | |
---|
1 | 22 | def phing(ctxt, file_=None, target=None, executable=None, args=None): |
---|
| 23 | """Run a phing build""" |
---|
0 | 24 | if args: |
---|
0 | 25 | args = shlex.split(args) |
---|
0 | 26 | else: |
---|
0 | 27 | args = [] |
---|
0 | 28 | args += ['-logger', 'phing.listener.DefaultLogger', |
---|
0 | 29 | '-buildfile', ctxt.resolve(file_ or 'build.xml')] |
---|
0 | 30 | if target: |
---|
0 | 31 | args.append(target) |
---|
| 32 | |
---|
0 | 33 | returncode = shtools.execute(ctxt, file_=executable or 'phing', args=args) |
---|
0 | 34 | if returncode != 0: |
---|
0 | 35 | ctxt.error('Phing failed (%s)' % returncode) |
---|
| 36 | |
---|
1 | 37 | def phpunit(ctxt, file_=None): |
---|
| 38 | """Extract test results from a PHPUnit XML report.""" |
---|
2 | 39 | assert file_, 'Missing required attribute "file"' |
---|
| 40 | |
---|
1 | 41 | def _process_testsuite(testsuite, results, parent_file=''): |
---|
5 | 42 | for testcase in testsuite.children(): |
---|
3 | 43 | if testcase.name == 'testsuite': |
---|
0 | 44 | _process_testsuite(testcase, results, |
---|
0 | 45 | parent_file=testcase.attr.get('file', parent_file)) |
---|
0 | 46 | continue |
---|
3 | 47 | test = xmlio.Element('test') |
---|
3 | 48 | test.attr['fixture'] = testsuite.attr['name'] |
---|
3 | 49 | test.attr['name'] = testcase.attr['name'] |
---|
3 | 50 | test.attr['duration'] = testcase.attr['time'] |
---|
3 | 51 | result = list(testcase.children()) |
---|
3 | 52 | if result: |
---|
1 | 53 | test.append(xmlio.Element('traceback')[ |
---|
1 | 54 | result[0].gettext() |
---|
1 | 55 | ]) |
---|
1 | 56 | test.attr['status'] = result[0].name |
---|
1 | 57 | else: |
---|
2 | 58 | test.attr['status'] = 'success' |
---|
3 | 59 | if 'file' in testsuite.attr or parent_file: |
---|
3 | 60 | testfile = os.path.realpath( |
---|
3 | 61 | testsuite.attr.get('file', parent_file)) |
---|
3 | 62 | if testfile.startswith(ctxt.basedir): |
---|
0 | 63 | testfile = testfile[len(ctxt.basedir) + 1:] |
---|
3 | 64 | testfile = testfile.replace(os.sep, '/') |
---|
3 | 65 | test.attr['file'] = testfile |
---|
3 | 66 | results.append(test) |
---|
| 67 | |
---|
1 | 68 | try: |
---|
1 | 69 | total, failed = 0, 0 |
---|
1 | 70 | results = xmlio.Fragment() |
---|
1 | 71 | fileobj = file(ctxt.resolve(file_), 'r') |
---|
1 | 72 | try: |
---|
3 | 73 | for testsuite in xmlio.parse(fileobj).children('testsuite'): |
---|
2 | 74 | total += int(testsuite.attr['tests']) |
---|
2 | 75 | failed += int(testsuite.attr['failures']) + \ |
---|
2 | 76 | int(testsuite.attr['errors']) |
---|
| 77 | |
---|
2 | 78 | _process_testsuite(testsuite, results) |
---|
2 | 79 | finally: |
---|
1 | 80 | fileobj.close() |
---|
1 | 81 | if failed: |
---|
1 | 82 | ctxt.error('%d of %d test%s failed' % (failed, total, |
---|
1 | 83 | total != 1 and 's' or '')) |
---|
1 | 84 | ctxt.report('test', results) |
---|
0 | 85 | except IOError, e: |
---|
0 | 86 | ctxt.log('Error opening PHPUnit results file (%s)' % e) |
---|
0 | 87 | except xmlio.ParseError, e: |
---|
0 | 88 | ctxt.log('Error parsing PHPUnit results file (%s)' % e) |
---|
| 89 | |
---|
1 | 90 | def coverage(ctxt, file_=None): |
---|
| 91 | """Extract data from Phing or PHPUnit code coverage report.""" |
---|
3 | 92 | assert file_, 'Missing required attribute "file"' |
---|
| 93 | |
---|
2 | 94 | def _process_phing_coverage(ctxt, element, coverage): |
---|
4 | 95 | for cls in element.children('class'): |
---|
3 | 96 | statements = float(cls.attr['statementcount']) |
---|
3 | 97 | covered = float(cls.attr['statementscovered']) |
---|
3 | 98 | if statements: |
---|
2 | 99 | percentage = covered / statements * 100 |
---|
2 | 100 | else: |
---|
1 | 101 | percentage = 100 |
---|
3 | 102 | class_coverage = xmlio.Element('coverage', |
---|
3 | 103 | name=cls.attr['name'], |
---|
3 | 104 | lines=int(statements), |
---|
3 | 105 | percentage=percentage |
---|
3 | 106 | ) |
---|
3 | 107 | source = list(cls.children())[0] |
---|
3 | 108 | if 'sourcefile' in source.attr: |
---|
3 | 109 | sourcefile = os.path.realpath(source.attr['sourcefile']) |
---|
3 | 110 | if sourcefile.startswith(ctxt.basedir): |
---|
0 | 111 | sourcefile = sourcefile[len(ctxt.basedir) + 1:] |
---|
3 | 112 | sourcefile = sourcefile.replace(os.sep, '/') |
---|
3 | 113 | class_coverage.attr['file'] = sourcefile |
---|
3 | 114 | coverage.append(class_coverage) |
---|
| 115 | |
---|
2 | 116 | def _process_phpunit_coverage(ctxt, element, coverage): |
---|
7 | 117 | for cls in element._node.getElementsByTagName('class'): |
---|
6 | 118 | sourcefile = cls.parentNode.getAttribute('name') |
---|
6 | 119 | if not os.path.isabs(sourcefile): |
---|
1 | 120 | sourcefile = os.path.join(ctxt.basedir, sourcefile) |
---|
6 | 121 | if sourcefile.startswith(ctxt.basedir): |
---|
6 | 122 | loc, ncloc = 0, 0.0 |
---|
41 | 123 | for line in cls.parentNode.getElementsByTagName('line'): |
---|
35 | 124 | if str(line.getAttribute('type')) == 'stmt': |
---|
27 | 125 | loc += 1 |
---|
27 | 126 | if int(line.getAttribute('count')) == 0: |
---|
23 | 127 | ncloc += 1 |
---|
6 | 128 | if loc > 0: |
---|
6 | 129 | percentage = 100 - (ncloc / loc * 100) |
---|
6 | 130 | else: |
---|
0 | 131 | percentage = 100 |
---|
| 132 | |
---|
6 | 133 | if sourcefile.startswith(ctxt.basedir): |
---|
6 | 134 | sourcefile = sourcefile[len(ctxt.basedir) + 1:] |
---|
6 | 135 | class_coverage = xmlio.Element('coverage', |
---|
6 | 136 | name=cls.getAttribute('name'), |
---|
6 | 137 | lines=int(loc), |
---|
6 | 138 | percentage=int(percentage), |
---|
6 | 139 | file=sourcefile.replace(os.sep, '/')) |
---|
6 | 140 | coverage.append(class_coverage) |
---|
| 141 | |
---|
2 | 142 | try: |
---|
2 | 143 | summary_file = file(ctxt.resolve(file_), 'r') |
---|
2 | 144 | summary = xmlio.parse(summary_file) |
---|
2 | 145 | coverage = xmlio.Fragment() |
---|
2 | 146 | try: |
---|
4 | 147 | for element in summary.children(): |
---|
2 | 148 | if element.name == 'package': |
---|
1 | 149 | _process_phing_coverage(ctxt, element, coverage) |
---|
1 | 150 | elif element.name == 'project': |
---|
1 | 151 | _process_phpunit_coverage(ctxt, element, coverage) |
---|
1 | 152 | finally: |
---|
2 | 153 | summary_file.close() |
---|
2 | 154 | ctxt.report('coverage', coverage) |
---|
0 | 155 | except IOError, e: |
---|
0 | 156 | ctxt.log('Error opening coverage summary file (%s)' % e) |
---|
0 | 157 | except xmlio.ParseError, e: |
---|
0 | 158 | ctxt.log('Error parsing coverage summary file (%s)' % e) |
---|