def report_file(self, category=None, file_=None): """Read report data from a file and record it. :param category: the name of the category of the report :param file\_: the path to the file containing the report data, relative to the base directory """ filename = self.resolve(file_) try: fileobj = file(filename, 'r') try: xml_elem = xmlio.Fragment() for child in xmlio.parse(fileobj).children(): child_elem = xmlio.Element( child.name, **dict([(name, value) for name, value in child.attr.items() if value is not None])) xml_elem.append(child_elem[[ xmlio.Element(grandchild.name)[grandchild.gettext()] for grandchild in child.children() ]]) self.output.append((Recipe.REPORT, category, None, xml_elem)) finally: fileobj.close() except xmlio.ParseError, e: self.error('Failed to parse %s report at %s: %s' % (category, filename, e))
def _create_build(self, url): xml = xmlio.Element('slave', name=self.name, version=PROTOCOL_VERSION)[ xmlio.Element('platform', processor=self.config['processor'] )[self.config['machine']], xmlio.Element('os', family=self.config['family'], version=self.config['version'])[self.config['os']], ] log.debug('Configured packages: %s', self.config.packages) for package, properties in self.config.packages.items(): xml.append(xmlio.Element('package', name=package, **properties)) body = str(xml) log.debug('Sending slave configuration: %s', body) resp = self.request( 'POST', url, body, { 'Content-Length': str(len(body)), 'Content-Type': 'application/x-bitten+xml' }) if resp.code == 201: self._initiate_build(resp.info().get('location')) return True elif resp.code == 204: log.info('No pending builds') return False else: log.error('Unexpected response (%d %s)', resp.code, resp.msg) raise ExitSlave(EX_PROTOCOL)
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)
def junit(ctxt, file_=None, srcdir=None): """Extract test results from a JUnit XML report. :param ctxt: the build context :type ctxt: `Context` :param file\_: path to the JUnit XML test results; may contain globbing wildcards for matching multiple results files :param srcdir: name of the directory containing the test sources, used to link test results to the corresponding source files """ assert file_, 'Missing required attribute "file"' try: total, failed = 0, 0 results = xmlio.Fragment() for path in glob(ctxt.resolve(file_)): fileobj = file(path, 'r') try: for testcase in xmlio.parse(fileobj).children('testcase'): test = xmlio.Element('test') test.attr['fixture'] = testcase.attr['classname'] test.attr['name'] = testcase.attr['name'] if 'time' in testcase.attr: test.attr['duration'] = testcase.attr['time'] if srcdir is not None: cls = testcase.attr['classname'].split('.') test.attr['file'] = posixpath.join(srcdir, *cls) + \ '.java' result = list(testcase.children()) if result: test.attr['status'] = result[0].name # Sometimes the traceback isn't prefixed with the # exception type and message, so add it in if needed tracebackprefix = "%s: %s" % ( result[0].attr['type'], result[0].attr['message']) if result[0].gettext().startswith(tracebackprefix): test.append( xmlio.Element('traceback')[ result[0].gettext()]) else: test.append( xmlio.Element('traceback')["\n".join( (tracebackprefix, result[0].gettext()))]) failed += 1 else: test.attr['status'] = 'success' results.append(test) total += 1 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: log.warning('Error opening JUnit results file (%s)', e)
def distutils(ctxt, file_='setup.py', command='build', options=None, timeout=None): """Execute a ``distutils`` command. :param ctxt: the build context :type ctxt: `Context` :param file\_: name of the file defining the distutils setup :param command: the setup command to execute :param options: additional options to pass to the command :param timeout: the number of seconds before the external process should be aborted (has same constraints as CommandLine) """ if options: if isinstance(options, basestring): options = shlex.split(options) else: options = [] if timeout: timeout = int(timeout) cmdline = CommandLine(_python_path(ctxt), [ctxt.resolve(file_), command] + options, cwd=ctxt.basedir) log_elem = xmlio.Fragment() error_logged = False for out, err in cmdline.execute(timeout): if out is not None: log.info(out) log_elem.append(xmlio.Element('message', level='info')[out]) if err is not None: level = 'error' if err.startswith('warning: '): err = err[9:] level = 'warning' log.warning(err) elif err.startswith('error: '): ctxt.error(err[7:]) error_logged = True else: log.error(err) log_elem.append(xmlio.Element('message', level=level)[err]) ctxt.log(log_elem) if not error_logged and cmdline.returncode != 0: ctxt.error('distutils failed (%s)' % cmdline.returncode)
def cobertura(ctxt, file_=None): """Extract test coverage information from a Cobertura XML report. :param ctxt: the build context :type ctxt: `Context` :param file\_: path to the Cobertura XML output """ assert file_, 'Missing required attribute "file"' coverage = xmlio.Fragment() doc = xmlio.parse(open(ctxt.resolve(file_))) srcdir = [ s.gettext().strip() for ss in doc.children('sources') for s in ss.children('source') ][0] classes = [ cls for pkgs in doc.children('packages') for pkg in pkgs.children('package') for clss in pkg.children('classes') for cls in clss.children('class') ] counters = {} class_names = {} for cls in classes: filename = cls.attr['filename'].replace(os.sep, '/') name = cls.attr['name'] if not '$' in name: # ignore internal classes class_names[filename] = name counter = counters.get(filename) if counter is None: counter = counters[filename] = _LineCounter() lines = [ l for ls in cls.children('lines') for l in ls.children('line') ] for line in lines: counter[line.attr['number']] = line.attr['hits'] for filename, name in class_names.iteritems(): counter = counters[filename] module = xmlio.Element('coverage', name=name, file=posixpath.join(srcdir, filename), lines=counter.num_lines, percentage=counter.percentage) module.append(xmlio.Element('line_hits')[counter.line_hits]) coverage.append(module) ctxt.report('coverage', 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)
def nunit(ctxt, file_=None): """Extract test results from a NUnit XML report. :param ctxt: the build context :type ctxt: `Context` :param file\_: path to the NUnit XML test results; may contain globbing wildcards for matching multiple results files """ assert file_, 'Missing required attribute "file"' try: total, failed = 0, 0 results = xmlio.Fragment() for path in glob(ctxt.resolve(file_)): fileobj = file(path, 'r') try: for suite, testcases in _get_cases(fileobj): for testcase in testcases: test = xmlio.Element('test') test.attr['fixture'] = suite.attr['name'] if 'time' in testcase.attr: test.attr['duration'] = testcase.attr['time'] if testcase.attr['executed'] == 'True': if testcase.attr['success'] != 'True': test.attr['status'] = 'failure' failure = list(testcase.children('failure')) if failure: stacktraceNode = list(failure[0].children('stack-trace')) if stacktraceNode: test.append(xmlio.Element('traceback')[ stacktraceNode[0].gettext() ]) failed += 1 else: test.attr['status'] = 'success' else: test.attr['status'] = 'ignore' results.append(test) total += 1 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: log.warning('Error opening NUnit results file (%s)', e)
def pylint(ctxt, file_=None): """Extract data from a ``pylint`` run written to a file. :param ctxt: the build context :type ctxt: `Context` :param file\_: name of the file containing the Pylint output """ assert file_, 'Missing required attribute "file"' msg_re = re.compile(r'^(?P<file>.+):(?P<line>\d+): ' r'\[(?P<type>[A-Z]\d*)(?:, (?P<tag>[\w\.]+))?\] ' r'(?P<msg>.*)$') msg_categories = dict(W='warning', E='error', C='convention', R='refactor') problems = xmlio.Fragment() try: fd = open(ctxt.resolve(file_), 'r') try: for line in fd: match = msg_re.search(line) if match: msg_type = match.group('type') category = msg_categories.get(msg_type[0]) if len(msg_type) == 1: msg_type = None filename = match.group('file') if os.path.isabs(filename) \ and filename.startswith(ctxt.basedir): filename = filename[len(ctxt.basedir) + 1:] filename = filename.replace('\\', '/') lineno = int(match.group('line')) tag = match.group('tag') problems.append( xmlio.Element('problem', category=category, type=msg_type, tag=tag, line=lineno, file=filename)[xmlio.Element('msg')[ match.group('msg') or '']]) ctxt.report('lint', problems) finally: fd.close() except IOError, e: log.warning('Error opening pylint results file (%s)', e)
def phpcs(ctxt, file_=None): """Extract test results from a PHP Code Sniffer full report.""" assert file_, 'Missing required attribute "file"' msg_categories = dict(WARNING='warning', ERROR='error') problems = xmlio.Fragment() try: fd = open(ctxt.resolve(file_), 'r') try: filename = None for line in fd: line = line.strip() if (not line or line.startswith("-") or line.startswith("FOUND")): continue if line.startswith("FILE:"): filename = line.split(":", 1)[1].strip() if (os.path.isabs(filename) and filename.startswith(ctxt.basedir)): filename = filename[len(ctxt.basedir) + 1:] filename = filename.replace('\\', '/') continue parts = line.split("|") if len(parts) != 3 or not parts[0]: continue lineno = int(parts[0].strip()) type = msg_categories[parts[1].strip()] tag = type category = type msg = parts[2].strip() problems.append( xmlio.Element('problem', category=category, type=type, tag=tag, line=lineno, file=filename)[xmlio.Element('msg')[msg]]) ctxt.report('lint', problems) except Exception, e: log.warning('Error parsing phpcs report file (%s)', e) finally: fd.close()
def unittest(ctxt, file_=None): """Extract data from a unittest results file in XML format. :param ctxt: the build context :type ctxt: `Context` :param file\_: name of the file containing the test results """ assert file_, 'Missing required attribute "file"' try: fileobj = file(ctxt.resolve(file_), 'r') try: total, failed = 0, 0 results = xmlio.Fragment() for child in xmlio.parse(fileobj).children(): test = xmlio.Element('test') for name, value in child.attr.items(): if name == 'file': value = os.path.realpath(value) if value.startswith(ctxt.basedir): value = value[len(ctxt.basedir) + 1:] value = value.replace(os.sep, '/') else: continue test.attr[name] = value if name == 'status' and value in ('error', 'failure'): failed += 1 for grandchild in child.children(): test.append(xmlio.Element(grandchild.name)[ grandchild.gettext() ]) results.append(test) total += 1 if failed: ctxt.error('%d of %d test%s failed' % (failed, total, total != 1 and 's' or '')) ctxt.report('test', results) finally: fileobj.close() except IOError, e: log.warning('Error opening unittest results file (%s)', e)
def _execute_step(self, build_url, recipe, step): failed = False started = datetime.utcnow() xml = xmlio.Element('result', step=step.id, time=started.isoformat()) try: for type, category, generator, output in \ 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 ]) except KeyboardInterrupt: log.warning('Build interrupted') self._cancel_build(build_url) except BuildError, e: log.error('Build step %r failed', step.id) failed = True
def collect_log_messages(node): for child in node.children(): if child.name == 'message': if child.attr['priority'] == 'debug': continue log_elem.append( xmlio.Element('message', level=child.attr['priority']) [child.gettext().replace(ctxt.basedir + os.sep, '').replace(ctxt.basedir, '')]) else: collect_log_messages(child)
def _execute_step(self, build_url, recipe, step): failed = False started = int(time.time()) xml = xmlio.Element('result', step=step.id) try: for type, category, generator, output in \ step.execute(recipe.ctxt): if type == Recipe.ERROR: failed = True if type == Recipe.REPORT and self.dump_reports: print output if type == Recipe.ATTACH: # Attachments are added out-of-band due to major # performance issues with inlined base64 xml content self._attach_file(build_url, recipe, output) xml.append( xmlio.Element(type, category=category, generator=generator)[output]) except KeyboardInterrupt: log.warning('Build interrupted') self._cancel_build(build_url) except BuildError, e: log.error('Build step %r failed', step.id) failed = True
def run(self, test): """ Monkey patch improving the way bitten extracts test and fixture names from result of TestCase.__str__. It fixes the case when test name contains a dot as in failure in module load (unittest.loader.LoadTestsFailure) (not a test really, but should be reported any way. Works with Bitten 0.7dev-r1026. """ result = TextTestRunner.run(self, test) if not self.xml_stream: return result root = xmlio.Element('unittest-results') for testcase, filename, timetaken, stdout, stderr in result.tests: status = 'success' tb = None if testcase in [e[0] for e in result.errors]: status = 'error' tb = [e[1] for e in result.errors if e[0] is testcase][0] elif testcase in [f[0] for f in result.failures]: status = 'failure' tb = [f[1] for f in result.failures if f[0] is testcase][0] name = str(testcase) fixture = None description = testcase.shortDescription() or '' if description.startswith('doctest of '): name = 'doctest' fixture = description[11:] description = None else: match = re.match(r'(\S+)\s+\(([\w.]+)\)', name) # this regexp is changed from '(\w+)\s+\(([\w.]+)\)' if match: name = match.group(1) fixture = match.group(2) test_elem = xmlio.Element('test', file=filename, name=name, fixture=fixture, status=status, duration=timetaken) if description: test_elem.append(xmlio.Element('description')[description]) if stdout: test_elem.append(xmlio.Element('stdout')[stdout]) if stderr: test_elem.append(xmlio.Element('stdout')[stderr]) if tb: test_elem.append(xmlio.Element('traceback')[tb]) root.append(test_elem) root.write(self.xml_stream, newlines=True) return result
def attach(self, file_=None, description=None, resource=None): """Attach a file to the build or build configuration. :param file\_: the path to the file to attach, relative to base directory. :param description: description saved with attachment :param resource: which resource to attach the file to, either 'build' (default) or 'config' """ # Attachments are not added as inline xml, so only adding # the details for later processing. if not file_: self.error('No attachment file specified.') return xml_elem = xmlio.Element('file', filename=file_, description=description or '', resource=resource or 'build') self.output.append((Recipe.ATTACH, None, None, xml_elem))
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 run(self, test): result = TextTestRunner.run(self, test) if not self.xml_stream: return result root = xmlio.Element('unittest-results') for testcase, filename, timetaken, stdout, stderr in result.tests: status = 'success' tb = None if testcase in [e[0] for e in result.errors]: status = 'error' tb = [e[1] for e in result.errors if e[0] is testcase][0] elif testcase in [f[0] for f in result.failures]: status = 'failure' tb = [f[1] for f in result.failures if f[0] is testcase][0] name = str(testcase) fixture = None description = testcase.shortDescription() or '' if description.startswith('doctest of '): name = 'doctest' fixture = description[11:] description = None else: match = re.match('(\w+)\s+\(([\w.]+)\)', name) if match: name = match.group(1) fixture = match.group(2) test_elem = xmlio.Element('test', file=filename, name=name, fixture=fixture, status=status, duration=timetaken) if description: test_elem.append(xmlio.Element('description')[description]) if stdout: test_elem.append(xmlio.Element('stdout')[stdout]) if stderr: test_elem.append(xmlio.Element('stdout')[stderr]) if tb: test_elem.append(xmlio.Element('traceback')[tb]) root.append(test_elem) root.write(self.xml_stream, newlines=True) return result
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 = not lines and len(code_lines) or \ len([l for l in lines if l != '-']) if num_lines: percentage = int(round(num_covered * 100 / num_lines)) else: percentage = 0 elem.attr['percentage'] = percentage elem.attr['lines'] = num_lines
def handle_file(element, modname): if not coverdir: return fp = ctxt.resolve( os.path.join(coverdir, modname.replace(".", "_") + ".py,cover")) if not os.path.exists(fp): log.info("No line by line coverage available for %s", modname) return try: with open(fp) as f: lines = [] for line in f: if line.startswith(">"): lines.append("1") elif line.startswith("!"): lines.append("0") else: lines.append("-") element.append(xmlio.Element('line_hits')[' '.join(lines)]) except Exception, e: log.info("Error while processing line by line coverage: %s", e)
def attach(self, file_=None, description=None, resource=None): """Attach a file to the build or build configuration. :param file\_: the path to the file to attach, relative to base directory. :param description: description saved with attachment :param resource: which resource to attach the file to, either 'build' (default) or 'config' """ filename = self.resolve(file_) try: fileobj = open(filename, 'rb') try: xml_elem = xmlio.Element('file', filename=os.path.basename(filename), description=description, resource=resource or 'build') xml_elem.append(fileobj.read().encode('base64')) self.output.append((Recipe.ATTACH, None, None, xml_elem)) finally: fileobj.close() except IOError, e: self.error('Failed to read file %s as attachment' % file_)
def trace(ctxt, summary=None, coverdir=None, include=None, exclude=None): """Extract data from a ``trace.py`` run. :param ctxt: the build context :type ctxt: `Context` :param summary: path to the file containing the coverage summary :param coverdir: name of the directory containing the per-module coverage details :param include: patterns of files or directories to include in the report :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<lines>\d+)\s+(?P<cov>\d+)%\s+' r'(?P<module>.*?)\s+\((?P<filename>.*?)\)') coverage_line_re = re.compile(r'\s*(?:(?P<hits>\d+): )?(?P<line>.*)') fileset = FileSet(ctxt.basedir, include, exclude) missing_files = [] for filename in fileset: if os.path.splitext(filename)[1] != '.py': continue 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 = not lines and len(code_lines) or \ len([l for l in lines if l != '-']) 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: coverage = xmlio.Fragment() for summary_line in summary_file: match = summary_line_re.search(summary_line) if match: modname = match.group(3) filename = match.group(4) if not os.path.isabs(filename): filename = os.path.normpath(os.path.join(ctxt.basedir, filename)) else: filename = os.path.realpath(filename) if not filename.startswith(ctxt.basedir): continue filename = filename[len(ctxt.basedir) + 1:] if not filename in fileset: continue 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() coverage.append(module) for filename in missing_files: modname = os.path.splitext(filename.replace(os.sep, '.'))[0] if modname in covered_modules: continue covered_modules.add(modname) 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) finally: summary_file.close() except IOError, e: log.warning('Error opening coverage summary file (%s)', e)
def coverage(ctxt, summary=None, coverdir=None, include=None, exclude=None): """Extract data from a ``coverage.py`` run. :param ctxt: the build context :type ctxt: `Context` :param summary: path to the file containing the coverage summary :param coverdir: name of the directory containing the per-module coverage details :param include: patterns of files or directories to include in the report :param exclude: patterns of files or directories to exclude from the report """ assert summary, 'Missing required attribute "summary"' summary_line_re = re.compile(r'^(?P<module>.*?)\s+(?P<stmts>\d+)\s+' r'(?P<exec>\d+)\s+(?P<cov>\d+)%\s+' r'(?:(?P<missing>(?:\d+(?:-\d+)?(?:, )?)*)\s+)?' r'(?P<file>.+)$') fileset = FileSet(ctxt.basedir, include, exclude) missing_files = [] for filename in fileset: if os.path.splitext(filename)[1] != '.py': continue missing_files.append(filename) covered_modules = set() try: summary_file = open(ctxt.resolve(summary), 'r') try: coverage = xmlio.Fragment() for summary_line in summary_file: match = summary_line_re.search(summary_line) if match: modname = match.group(1) filename = match.group(6) if not os.path.isabs(filename): filename = os.path.normpath(os.path.join(ctxt.basedir, filename)) else: filename = os.path.realpath(filename) if not filename.startswith(ctxt.basedir): continue filename = filename[len(ctxt.basedir) + 1:] if not filename in fileset: continue percentage = int(match.group(4).rstrip('%')) num_lines = int(match.group(2)) missing_files.remove(filename) covered_modules.add(modname) module = xmlio.Element('coverage', name=modname, file=filename.replace(os.sep, '/'), percentage=percentage, lines=num_lines) coverage.append(module) for filename in missing_files: modname = os.path.splitext(filename.replace(os.sep, '.'))[0] if modname in covered_modules: continue covered_modules.add(modname) module = xmlio.Element('coverage', name=modname, file=filename.replace(os.sep, '/'), percentage=0) coverage.append(module) ctxt.report('coverage', coverage) finally: summary_file.close() except IOError, e: log.warning('Error opening coverage summary file (%s)', e)
modname = base.replace(os.path.sep, '.') realfilename = ctxt.resolve(filename) interesting_lines = get_lines(open(realfilename)) covered_lines = coverage_data.get(realfilename, set()) percentage = int(round(len(covered_lines) * 100 / len(interesting_lines))) line_hits = [] for lineno in xrange(1, max(interesting_lines)+1): if lineno not in interesting_lines: line_hits.append('-') elif lineno in covered_lines: line_hits.append('1') else: line_hits.append('0') module = xmlio.Element('coverage', name=modname, file=filename.replace(os.sep, '/'), percentage=percentage, lines=len(interesting_lines), line_hits=' '.join(line_hits)) coverage.append(module) ctxt.report('coverage', coverage) def _normalize_filenames(ctxt, filenames, fileset): for filename in filenames: if not os.path.isabs(filename): filename = os.path.normpath(os.path.join(ctxt.basedir, filename)) else: filename = os.path.realpath(filename) if not filename.startswith(ctxt.basedir): continue filename = filename[len(ctxt.basedir) + 1:]
def junit(ctxt, file_=None, srcdir=None): """Extract test results from a JUnit XML report. :param ctxt: the build context :type ctxt: `Context` :param file\_: path to the JUnit XML test results; may contain globbing wildcards for matching multiple results files :param srcdir: name of the directory containing the test sources, used to link test results to the corresponding source files """ assert file_, 'Missing required attribute "file"' try: total, failed = 0, 0 results = xmlio.Fragment() for path in glob(ctxt.resolve(file_)): fileobj = file(path, 'r') try: output = xmlio.parse(fileobj) finally: fileobj.close() if output.name == 'testsuites': # top level wrapper for testsuites testcases = [] for t_suite in output.children('testsuite'): testcases.extend( [t_case for t_case in t_suite.children('testcase')]) else: testcases = [t_case for t_case in output.children('testcase')] for testcase in testcases: test = xmlio.Element('test') test.attr['fixture'] = testcase.attr['classname'] test.attr['name'] = testcase.attr['name'] if 'time' in testcase.attr: test.attr['duration'] = testcase.attr['time'] if srcdir is not None: cls = testcase.attr['classname'].split('.') test.attr['file'] = posixpath.join(srcdir, *cls) + \ '.java' result = list(testcase.children()) if result: junit_status = result[0].name test.append( xmlio.Element('traceback')[_fix_traceback(result)]) if junit_status == 'skipped': test.attr['status'] = 'ignore' elif junit_status == 'error': test.attr['status'] = 'error' failed += 1 else: test.attr['status'] = 'failure' failed += 1 else: test.attr['status'] = 'success' results.append(test) total += 1 if failed: ctxt.error('%d of %d test%s failed' % (failed, total, total != 1 and 's' or '')) ctxt.report('test', results) except IOError, e: log.warning('Error opening JUnit results file (%s)', e)
def info (msg): log.info (msg) log_elem.append (xmlio.Element ('message', level='info')[msg])
def test_Element_encoding(self): self.assertEquals( '<\xc3\xb8\xc3\xbc arg="\xc3\xa9\xe2\x82\xac"/>', str(xmlio.Element(u'\xf8\xfc', arg=u'\xe9\u20ac'.encode('utf-8'))))
def warning (msg): log.warning (msg) log_elem.append (xmlio.Element ('message', level='warning')[msg])
def error (msg): log.error (msg) log_elem.append (xmlio.Element ('message', level='error')[msg])
def gcov(ctxt, include=None, exclude=None, prefix=None, root=""): """Run ``gcov`` to extract coverage data where available. :param ctxt: the build context :type ctxt: `Context` :param include: patterns of files and directories to include :param exclude: patterns of files and directories that should be excluded :param prefix: optional prefix name that is added to object files by the build system :param root: optional root path in which the build system puts the object files """ 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() log_elem = xmlio.Fragment() def info (msg): log.info (msg) log_elem.append (xmlio.Element ('message', level='info')[msg]) def warning (msg): log.warning (msg) log_elem.append (xmlio.Element ('message', level='warning')[msg]) def error (msg): log.error (msg) log_elem.append (xmlio.Element ('message', level='error')[msg]) for srcfile in files: # Determine the coverage for each source file by looking for a .gcno # and .gcda pair info ("Getting coverage info for %s" % srcfile) filepath, filename = os.path.split(srcfile) stem = os.path.splitext(filename)[0] if prefix is not None: stem = prefix + '-' + stem objfile = os.path.join (root, filepath, stem + '.o') if not os.path.isfile(ctxt.resolve(objfile)): warning ('No object file found for %s at %s' % (srcfile, objfile)) continue if not os.path.isfile (ctxt.resolve (os.path.join (root, filepath, stem + '.gcno'))): warning ('No .gcno file found for %s at %s' % (srcfile, os.path.join (root, filepath, stem + '.gcno'))) continue if not os.path.isfile (ctxt.resolve (os.path.join (root, filepath, stem + '.gcda'))): warning ('No .gcda file found for %s at %s' % (srcfile, os.path.join (root, filepath, stem + '.gcda'))) 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) ctxt.log (log_elem)