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 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 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 testsuite in xmlio.parse(fileobj).children('testsuite'): total += int(testsuite.attr['tests']) failed += int(testsuite.attr['failures']) + \ int(testsuite.attr['errors']) _process_testsuite(testsuite, results) 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)
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 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 figleaf(ctxt, summary=None, include=None, exclude=None): """Extract data from a ``Figleaf`` run. :param ctxt: the build context :type ctxt: `Context` :param summary: path to the file containing the coverage summary :param include: patterns of files or directories to include in the report :param exclude: patterns of files or directories to exclude from the report """ from figleaf import get_lines coverage = xmlio.Fragment() try: fileobj = open(ctxt.resolve(summary)) except IOError, e: log.warning('Error opening coverage summary file (%s)', e) return
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 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)
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, file_=None): """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: 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) except IOError, e: ctxt.log('Error opening coverage summary file (%s)' % e)
def ant(ctxt, file_=None, target=None, keep_going=False, args=None): """Run an Ant build. :param ctxt: the build context :type ctxt: `Context` :param file\_: name of the Ant build file :param target: name of the target that should be executed (optional) :param keep_going: whether Ant should keep going when errors are encountered :param args: additional arguments to pass to Ant """ executable = 'ant' ant_home = ctxt.config.get_dirpath('ant.home') if ant_home: executable = os.path.join(ant_home, 'bin', 'ant') java_home = ctxt.config.get_dirpath('java.home') if java_home: os.environ['JAVA_HOME'] = java_home logfile = tempfile.NamedTemporaryFile(prefix='ant_log', suffix='.xml') logfile.close() if args: args = shlex.split(args) else: args = [] args += [ '-noinput', '-listener', 'org.apache.tools.ant.XmlLogger', '-Dant.XmlLogger.stylesheet.uri', '""', '-DXmlLogger.file', logfile.name ] if file_: args += ['-buildfile', ctxt.resolve(file_)] if keep_going: args.append('-keep-going') if target: args.append(target) shell = False if os.name == 'nt': # Need to execute ant.bat through a shell on Windows shell = True cmdline = CommandLine(executable, args, cwd=ctxt.basedir, shell=shell) for out, err in cmdline.execute(): if out is not None: log.info(out) if err is not None: log.error(err) error_logged = False log_elem = xmlio.Fragment() try: xml_log = xmlio.parse(file(logfile.name, 'r')) 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) collect_log_messages(xml_log) if 'error' in xml_log.attr: ctxt.error(xml_log.attr['error']) error_logged = True except xmlio.ParseError, e: log.warning('Error parsing Ant XML log 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)
def execute(ctxt, executable=None, file_=None, input_=None, output=None, args=None, dir_=None, filter_=None): """Generic external program execution. This function is not itself bound to a recipe command, but rather used from other commands. :param ctxt: the build context :type ctxt: `Context` :param executable: name of the executable to run :param file\_: name of the script file, relative to the project directory, that should be run :param input\_: name of the file containing the data that should be passed to the shell script on its standard input stream :param output: name of the file to which the output of the script should be written :param args: command-line arguments to pass to the script :param filter\_: function to filter out messages from the executable stdout """ if args: if isinstance(args, basestring): args = shlex.split(args) else: args = [] if dir_: def resolve(*args): return ctxt.resolve(dir_, *args) else: resolve = ctxt.resolve if file_ and os.path.isfile(resolve(file_)): file_ = resolve(file_) if executable is None: executable = file_ elif file_: args[:0] = [file_] # Support important Windows CMD.EXE built-ins (and it does its own quoting) if os.name == 'nt' and executable.upper() in [ 'COPY', 'DIR', 'ECHO', 'ERASE', 'DEL', 'MKDIR', 'MD', 'MOVE', 'RMDIR', 'RD', 'TYPE' ]: args = ['/C', executable] + [arg.strip('"') for arg in args] executable = os.environ['COMSPEC'] if input_: input_file = file(resolve(input_), 'r') else: input_file = None if output: output_file = file(resolve(output), 'w') else: output_file = None if dir_ and os.path.isdir(ctxt.resolve(dir_)): dir_ = ctxt.resolve(dir_) else: dir_ = ctxt.basedir if not filter_: filter_ = lambda s: s try: cmdline = CommandLine(executable, args, input=input_file, cwd=dir_) log_elem = xmlio.Fragment() for out, err in cmdline.execute(): if out is not None: log.info(out) info = filter_(out) if info: log_elem.append( xmlio.Element('message', level='info')[info.replace( ctxt.basedir + os.sep, '').replace(ctxt.basedir, '')]) if output: output_file.write(out + os.linesep) if err is not None: log.error(err) log_elem.append( xmlio.Element('message', level='error')[err.replace( ctxt.basedir + os.sep, '').replace(ctxt.basedir, '')]) if output: output_file.write(err + os.linesep) ctxt.log(log_elem) finally: if input_: input_file.close() if output: output_file.close() return cmdline.returncode
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 cppunit(ctxt, file_=None, srcdir=None): """Collect CppUnit XML data. :param ctxt: the build context :type ctxt: `Context` :param file\_: path of the file containing the CppUnit results; may contain globbing wildcards to match multiple files :param srcdir: name of the directory containing the source files, used to link the test results to the corresponding files """ assert file_, 'Missing required attribute "file"' try: fileobj = file(ctxt.resolve(file_), 'r') try: total, failed = 0, 0 results = xmlio.Fragment() for group in xmlio.parse(fileobj): if group.name not in ('FailedTests', 'SuccessfulTests'): continue for child in group.children(): test = xmlio.Element('test') name = child.children('Name').next().gettext() if '::' in name: parts = name.split('::') test.attr['fixture'] = '::'.join(parts[:-1]) name = parts[-1] test.attr['name'] = name for location in child.children('Location'): for file_elem in location.children('File'): filepath = file_elem.gettext() if srcdir is not None: filepath = posixpath.join(srcdir, filepath) test.attr['file'] = filepath break for line_elem in location.children('Line'): test.attr['line'] = line_elem.gettext() break break if child.name == 'FailedTest': for message in child.children('Message'): test.append(xmlio.Element('traceback')[ message.gettext() ]) 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) finally: fileobj.close() except IOError, e: log.warning('Error opening CppUnit results file (%s)', e)
def cunit (ctxt, file_=None, srcdir=None): """Collect CUnit XML data. :param ctxt: the build context :type ctxt: `Context` :param file\_: path of the file containing the CUnit results; may contain globbing wildcards to match multiple files :param srcdir: name of the directory containing the source files, used to link the test results to the corresponding files """ assert file_, 'Missing required attribute "file"' try: fileobj = file(ctxt.resolve(file_), 'r') try: total, failed = 0, 0 results = 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 node in xmlio.parse(fileobj): if node.name != 'CUNIT_RESULT_LISTING': continue for suiteRun in node.children ('CUNIT_RUN_SUITE'): for suite in suiteRun.children(): if suite.name not in ('CUNIT_RUN_SUITE_SUCCESS', 'CUNIT_RUN_SUITE_FAILURE'): warning ("Unknown node: %s" % suite.name) continue suiteName = suite.children ('SUITE_NAME').next().gettext() info ("%s [%s]" % ("*" * (57 - len (suiteName)), suiteName)) for record in suite.children ('CUNIT_RUN_TEST_RECORD'): for result in record.children(): if result.name not in ('CUNIT_RUN_TEST_SUCCESS', 'CUNIT_RUN_TEST_FAILURE'): continue testName = result.children ('TEST_NAME').next().gettext() info ("Running %s..." % testName); test = xmlio.Element('test') test.attr['fixture'] = suiteName test.attr['name'] = testName if result.name == 'CUNIT_RUN_TEST_FAILURE': error ("%s(%d): %s" % (result.children ('FILE_NAME').next().gettext(), int (result.children ('LINE_NUMBER').next().gettext()), result.children ('CONDITION').next().gettext())) 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) ctxt.log (log_elem) finally: fileobj.close() except IOError, e: log.warning('Error opening CUnit results file (%s)', e)
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) 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