# HG changeset patch # User osimons # Date 1251159418 0 # Node ID de04ce69da53bcac4bf987ea3c2113f23d4c0ed5 # Parent 44a862c1e559fe66d6a935af49539135df732732 0.6dev: Removing code and updated docs related to Trac < 0.11 and Python < 2.4 (base requirements for Bitten 0.6). diff --git a/README.txt b/README.txt --- a/README.txt +++ b/README.txt @@ -6,21 +6,16 @@ metrics generated by builds, to enable feedback and reporting about the progress of a software project. -The Bitten software consists of three separate parts: - * The build slave, which executes builds on behalf of a local or remote - build master - * The build master, which orchestrates builds for a project across all - connected slaves, and stores the build status and results to the - database +The Bitten software consists of two separate parts: + * The build slave, which executes builds on behalf of a build master. * The web interface, which is implemented as an add-on to Trac (http://trac.edgewall.com/) and provides a build management interface as well as presentation of build results. -Both the build master and the web interface depend on Trac 0.10, and need -to be installed on the same machine, together with the Subversion -repository. The build slave only requires Python (>= 2.3), setuptools -(>= 0.6a2), as well as any tools required by the build process itself. A -build slave may be run on any machine that can connect to the server +The build master is a plugin for Trac 0.11, while the build slave can be +installed without other dependencies than Python >= 2.4 and +setuptools >= 0.6a2 in addition to any tools required by the build process +itself. A build slave may be run on any machine that can connect to the server running the Bitten build master. diff --git a/bitten/build/api.py b/bitten/build/api.py --- a/bitten/build/api.py +++ b/bitten/build/api.py @@ -15,12 +15,9 @@ import os import shlex import time +import subprocess import sys -try: - import subprocess -except ImportError: - subprocess = None log = logging.getLogger('bitten.build.api') @@ -87,236 +84,99 @@ assert os.path.isdir(self.cwd) self.returncode = None - if subprocess: - - def execute(self, timeout=None): - """Execute the command, and return a generator for iterating over - the output written to the standard output and error streams. - - :param timeout: number of seconds before the external process - should be aborted (not supported on Windows without - ``subprocess`` module / Python 2.4+) - """ - from threading import Thread - from Queue import Queue, Empty - - class ReadThread(Thread): - def __init__(self, pipe, pipe_name, queue): - self.pipe = pipe - self.pipe_name = pipe_name - self.queue = queue - Thread.__init__(self) - def run(self): - while self.pipe and not self.pipe.closed: - line = self.pipe.readline() - if line == '': - break - self.queue.put((self.pipe_name, line)) - if not self.pipe.closed: - self.pipe.close() - - class WriteThread(Thread): - def __init__(self, pipe, data): - self.pipe = pipe - self.data = data - Thread.__init__(self) - def run(self): - if self.data and self.pipe and not self.pipe.closed: - self.pipe.write(self.data) - if not self.pipe.closed: - self.pipe.close() - - args = [self.executable] + self.arguments - try: - p = subprocess.Popen(args, bufsize=1, # Line buffered - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=(self.cwd or None), - shell=(os.name == 'nt' and True or False), - universal_newlines=True, - env=None) - except Exception, e: - # NT executes through shell and will not raise BuildError - raise BuildError('Error executing %s: %s %s' % (args, - e.__class__.__name__, str(e))) - - log.debug('Executing %s, (pid = %s)', args, p.pid) - - if self.input: - if isinstance(self.input, basestring): - in_data = self.input - else: - in_data = self.input.read() - else: - in_data = None - - queue = Queue() - limit = timeout and timeout + time.time() or 0 - - pipe_in = WriteThread(p.stdin, in_data) - pipe_out = ReadThread(p.stdout, 'stdout', queue) - pipe_err = ReadThread(p.stderr, 'stderr', queue) - pipe_err.start(); pipe_out.start(); pipe_in.start() - - while True: - if limit and limit < time.time(): - if hasattr(subprocess, 'kill'): # Python 2.6+ - p.kill() - raise TimeoutError('Command %s timed out' % self.executable) - if p.poll() != None and self.returncode == None: - self.returncode = p.returncode - try: - name, line = queue.get(block=True, timeout=.01) - line = line and _decode(line.rstrip().replace('\x00', '')) - if name == 'stderr': - yield (None, line) - else: - yield (line, None) - except Empty: - if self.returncode != None: - break - - pipe_out.join(); pipe_in.join(); pipe_err.join() - - log.debug('%s exited with code %s', self.executable, - self.returncode) - - elif os.name == 'nt': # windows - - def execute(self, timeout=None): - """Execute the command, and return a generator for iterating over - the output written to the standard output and error streams. - - :param timeout: number of seconds before the external process - should be aborted (not supported on Windows without - ``subprocess`` module / Python 2.4+) - """ - args = [self.executable] + self.arguments - for idx, arg in enumerate(args): - if arg.find(' ') >= 0: - args[idx] = '"%s"' % arg - log.debug('Executing %s', args) - - if self.cwd: - old_cwd = os.getcwd() - os.chdir(self.cwd) - import tempfile - in_name = None - if self.input: - if isinstance(self.input, basestring): - in_file, in_name = tempfile.mkstemp(prefix='bitten_', - suffix='.pipe') - os.write(in_file, self.input) - os.close(in_file) - in_redirect = '< "%s" ' % in_name - else: - in_redirect = '< "%s" ' % self.input.name - else: - in_redirect = '' - - out_file, out_name = tempfile.mkstemp(prefix='bitten_', - suffix='.pipe') - os.close(out_file) - - try: - # NT without subprocess joins output from stdout & stderr - cmd = '( %s ) > "%s" %s 2>&1' % (' '.join(args), out_name, - in_redirect) - log.info("running: %s", cmd) - self.returncode = os.system(cmd) - log.debug('Exited with code %s', self.returncode) - - out_file = file(out_name, 'r') - out_lines = out_file.readlines() - err_lines = [] - out_file.close() - finally: - if in_name: - os.unlink(in_name) - if out_name: - os.unlink(out_name) - if self.cwd: - os.chdir(old_cwd) - - for out_line, err_line in _combine(out_lines, err_lines): - yield out_line and _decode( - out_line.rstrip().replace('\x00', '')), \ - err_line and _decode( - err_line.rstrip().replace('\x00', '')) - - if self.cwd: - os.chdir(old_cwd) - - else: # posix + def execute(self, timeout=None): + """Execute the command, and return a generator for iterating over + the output written to the standard output and error streams. + + :param timeout: number of seconds before the external process + should be aborted (not supported on Windows without + ``subprocess`` module / Python 2.4+) + """ + from threading import Thread + from Queue import Queue, Empty - def execute(self, timeout=None): - """Execute the command, and return a generator for iterating over - the output written to the standard output and error streams. - - :param timeout: number of seconds before the external process - should be aborted (not supported on Windows without - ``subprocess`` module / Python 2.4+) - """ - import popen2, select - if self.cwd: - old_cwd = os.getcwd() - os.chdir(self.cwd) - - log.debug('Executing %s', [self.executable] + self.arguments) - pipe = popen2.Popen3([self.executable] + self.arguments, - capturestderr=True) - if self.input: - if isinstance(self.input, basestring): - in_data = self.input - else: - in_data = self.input.read() - else: - pipe.tochild.close() - in_data = '' + class ReadThread(Thread): + def __init__(self, pipe, pipe_name, queue): + self.pipe = pipe + self.pipe_name = pipe_name + self.queue = queue + Thread.__init__(self) + def run(self): + while self.pipe and not self.pipe.closed: + line = self.pipe.readline() + if line == '': + break + self.queue.put((self.pipe_name, line)) + if not self.pipe.closed: + self.pipe.close() - out_data, err_data = [], [] - in_eof = out_eof = err_eof = False - if not in_data: - in_eof = True - while not out_eof or not err_eof: - readable = [pipe.fromchild] * (not out_eof) + \ - [pipe.childerr] * (not err_eof) - writable = [pipe.tochild] * (not in_eof) - ready = select.select(readable, writable, [], timeout) - if not (ready[0] or ready[1]): - raise TimeoutError('Command %s timed out' % self.executable) - if pipe.tochild in ready[1]: - sent = os.write(pipe.tochild.fileno(), in_data) - in_data = in_data[sent:] - if not in_data: - pipe.tochild.close() - in_eof = True - if pipe.fromchild in ready[0]: - data = os.read(pipe.fromchild.fileno(), 1024) - if data: - out_data.append(data) - else: - out_eof = True - if pipe.childerr in ready[0]: - data = os.read(pipe.childerr.fileno(), 1024) - if data: - err_data.append(data) - else: - err_eof = True - out_lines = self._extract_lines(out_data) - err_lines = self._extract_lines(err_data) - for out_line, err_line in _combine(out_lines, err_lines): - yield out_line and _decode(out_line), \ - err_line and _decode(err_line) - time.sleep(.1) - self.returncode = pipe.wait() - log.debug('%s exited with code %s', self.executable, - self.returncode) + class WriteThread(Thread): + def __init__(self, pipe, data): + self.pipe = pipe + self.data = data + Thread.__init__(self) + def run(self): + if self.data and self.pipe and not self.pipe.closed: + self.pipe.write(self.data) + if not self.pipe.closed: + self.pipe.close() - if self.cwd: - os.chdir(old_cwd) + args = [self.executable] + self.arguments + try: + p = subprocess.Popen(args, bufsize=1, # Line buffered + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=(self.cwd or None), + shell=(os.name == 'nt' and True or False), + universal_newlines=True, + env=None) + except Exception, e: + # NT executes through shell and will not raise BuildError + raise BuildError('Error executing %s: %s %s' % (args, + e.__class__.__name__, str(e))) + + log.debug('Executing %s, (pid = %s)', args, p.pid) + + if self.input: + if isinstance(self.input, basestring): + in_data = self.input + else: + in_data = self.input.read() + else: + in_data = None + + queue = Queue() + limit = timeout and timeout + time.time() or 0 + + pipe_in = WriteThread(p.stdin, in_data) + pipe_out = ReadThread(p.stdout, 'stdout', queue) + pipe_err = ReadThread(p.stderr, 'stderr', queue) + pipe_err.start(); pipe_out.start(); pipe_in.start() + + while True: + if limit and limit < time.time(): + if hasattr(subprocess, 'kill'): # Python 2.6+ + p.kill() + raise TimeoutError('Command %s timed out' % self.executable) + if p.poll() != None and self.returncode == None: + self.returncode = p.returncode + try: + name, line = queue.get(block=True, timeout=.01) + line = line and _decode(line.rstrip().replace('\x00', '')) + if name == 'stderr': + yield (None, line) + else: + yield (line, None) + except Empty: + if self.returncode != None: + break + + pipe_out.join(); pipe_in.join(); pipe_err.join() + + log.debug('%s exited with code %s', self.executable, + self.returncode) def _extract_lines(self, data): extracted = [] diff --git a/bitten/report/coverage.py b/bitten/report/coverage.py --- a/bitten/report/coverage.py +++ b/bitten/report/coverage.py @@ -127,113 +127,111 @@ } -# Coverage annotation requires the new interface from 0.11 -if hasattr(IHTMLPreviewAnnotator, 'get_annotation_data'): - class TestCoverageAnnotator(Component): - """ - >>> from genshi.builder import tag - >>> from trac.test import Mock, MockPerm - >>> from trac.mimeview import Context - >>> from trac.web.href import Href - >>> from bitten.model import BuildConfig, Build, Report - >>> from bitten.report.tests.coverage import env_stub_with_tables - >>> env = env_stub_with_tables() - - >>> BuildConfig(env, name='trunk', path='trunk').insert() - >>> Build(env, rev=123, config='trunk', rev_time=12345, platform=1).insert() - >>> rpt = Report(env, build=1, step='test', category='coverage') - >>> rpt.items.append({'file': 'foo.py', 'line_hits': '5 - 0'}) - >>> rpt.insert() - - >>> ann = TestCoverageAnnotator(env) - >>> req = Mock(href=Href('/'), perm=MockPerm(), chrome={}) - - Version in the branch should not match: - >>> context = Context.from_request(req, 'source', '/branches/blah/foo.py', 123) - >>> ann.get_annotation_data(context) - [] - - Version in the trunk should match: - >>> context = Context.from_request(req, 'source', '/trunk/foo.py', 123) - >>> data = ann.get_annotation_data(context) - >>> print data - [u'5', u'-', u'0'] - - >>> def annotate_row(lineno, line): - ... row = tag.tr() - ... ann.annotate_row(context, row, lineno, line, data) - ... return row.generate().render('html') - - >>> annotate_row(1, 'x = 1') - '