def test_nonexisting_command(self): cmdline = CommandLine('doesnotexist', []) iterable = iter(cmdline.execute()) try: out, err = iterable.next() self.fail("Expected BuildError") except BuildError, e: self.failUnless("Error executing ['doesnotexist']" in str(e))
def test_timeout(self): script_file = self._create_file('test.py', content=""" import time time.sleep(2.0) print 'Done' """) cmdline = CommandLine('python', [script_file]) iterable = iter(cmdline.execute(timeout=.5)) self.assertRaises(TimeoutError, iterable.next)
def test_timeout(self): script_file = self._create_file('test.py', content=""" import time time.sleep(2.0) print 'Done' """) cmdline = CommandLine('python', [script_file]) iterable = iter(cmdline.execute(timeout=.5)) if os.name != "nt": # commandline timeout not implemented on windows. See #257 self.assertRaises(TimeoutError, iterable.next)
def test_single_argument(self): cmdline = CommandLine(sys.executable, ['-V']) stdout = [] stderr = [] for out, err in cmdline.execute(timeout=5.0): if out is not None: stdout.append(out) if err is not None: stderr.append(err) py_version = '.'.join([str(v) for (v) in sys.version_info[:3]]) self.assertEqual(['Python %s' % py_version], stderr) self.assertEqual([], stdout) self.assertEqual(0, cmdline.returncode)
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 test_single_argument(self): cmdline = CommandLine(sys.executable, ['-V']) stdout = [] stderr = [] for out, err in cmdline.execute(timeout=5.0): if out is not None: stdout.append(out) if err is not None: stderr.append(err) py_version = '.'.join([str(v) for (v) in sys.version_info[:3]] ).rstrip('.0') self.assertEqual(['Python %s' % py_version], stderr) self.assertEqual([], stdout) self.assertEqual(0, cmdline.returncode)
def test_multiple_arguments(self): script_file = self._create_file('test.py', content=""" import sys for arg in sys.argv[1:]: print arg """) cmdline = CommandLine('python', [script_file, 'foo', 'bar', 'baz']) stdout = [] stderr = [] for out, err in cmdline.execute(timeout=5.0): stdout.append(out) stderr.append(err) self.assertEqual(['foo', 'bar', 'baz'], stdout) self.assertEqual([None, None, None], stderr) self.assertEqual(0, cmdline.returncode)
def test_input_stream_as_string(self): script_file = self._create_file('test.py', content=""" import sys data = sys.stdin.read() if data == 'abcd': print>>sys.stdout, 'Thanks' """) cmdline = CommandLine('python', [script_file], input='abcd') stdout = [] stderr = [] for out, err in cmdline.execute(timeout=5.0): stdout.append(out) stderr.append(err) py_version = '.'.join([str(v) for (v) in sys.version_info[:3]]) self.assertEqual(['Thanks'], stdout) self.assertEqual([None], stderr) self.assertEqual(0, cmdline.returncode)
def test_output_error_streams(self): script_file = self._create_file('test.py', content=""" import sys, time print>>sys.stdout, 'Hello' print>>sys.stdout, 'world!' sys.stdout.flush() time.sleep(.1) print>>sys.stderr, 'Oops' sys.stderr.flush() """) cmdline = CommandLine('python', [script_file]) stdout = [] stderr = [] for out, err in cmdline.execute(timeout=5.0): stdout.append(out) stderr.append(err) py_version = '.'.join([str(v) for (v) in sys.version_info[:3]]) self.assertEqual(['Hello', 'world!', None], stdout) self.assertEqual(0, cmdline.returncode)
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 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 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) cmdline = CommandLine(executable, args, cwd=ctxt.basedir) 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 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 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 gcov(ctxt, include=None, exclude=None, prefix=None, root="", dir_=None): """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 :param dir_: optional base dir path, usefull when you have source in a subdirectory of your repository """ 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*$") # basedir will be a path relative to the context # resolve will be a function for resolving file's paths if dir_: # The base dir should finish in / # this is used to create the path of the files basedir = dir_ if basedir.rfind("/") != len(basedir) - 1: basedir += "/" def resolve(*args): return ctxt.resolve(dir_, *args) else: basedir = "" resolve = ctxt.resolve files = [] if dir_ and os.path.isdir(ctxt.resolve(dir_)): dir_ = ctxt.resolve(dir_) else: dir_ = ctxt.basedir for filename in FileSet(dir_, 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(resolve(objfile)): warning("No object file found for %s at %s" % (srcfile, objfile)) continue if not os.path.isfile(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(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=dir_) for out, err in cmd.execute(): info(out) 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 the file is inside our ctxt.resolve(dir_)... if match.group("file").find(dir_) != 0: 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=(basedir + 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 execute( ctxt, executable=None, file_=None, input_=None, output=None, args=None, dir_=None, filter_=None, timeout=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 dir\_: directory to change to before executing the command :param filter\_: function to filter out messages from the executable stdout :param timeout: the number of seconds before the external process should be aborted (has same constraints as CommandLine) """ 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_) shell = False if file_ and os.name == "nt": # Need to execute script files through a shell on Windows shell = True 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", ]: shell = True if input_: input_file = codecs.open(resolve(input_), "r", "utf-8") else: input_file = None if output: output_file = codecs.open(resolve(output), "w", "utf-8") 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 if timeout: timeout = int(timeout) try: cmdline = CommandLine(executable, args, input=input_file, cwd=dir_, shell=shell) log_elem = xmlio.Fragment() for out, err in cmdline.execute(timeout=timeout): 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