X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=runner%2Fadt-run;h=7787e5761971fb566993e8e87bd40058ed5d01b4;hb=aa43f2a81adb120c1da99919c5b3523ea952d170;hp=2c8c807d8bc3400f90e15b58686e90c634760d04;hpb=574ca9aff1adc45c9eebe8a531dd43982cdcce0e;p=autopkgtest.git diff --git a/runner/adt-run b/runner/adt-run index 2c8c807..7787e57 100755 --- a/runner/adt-run +++ b/runner/adt-run @@ -1,17 +1,26 @@ #!/usr/bin/python2.4 -# usage: -# adt-run ... --- [...] # -# invoke in toplevel of package (not necessarily built) -# with package installed - -# exit status: -# 0 all tests passed -# 4 at least one test failed -# 8 no tests in this package -# 12 erroneous package -# 16 testbed failure -# 20 other unexpected failures including bad usage +# adt-run is part of autopkgtest +# autopkgtest is a tool for testing Debian binary packages +# +# autopkgtest is Copyright (C) 2006-2007 Canonical Ltd. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# See the file CREDITS for a full list of credits information (often +# installed as /usr/share/doc/autopkgtest/CREDITS). import signal import optparse @@ -21,86 +30,555 @@ import subprocess import traceback import urllib import string +import re as regexp +import os +import errno +import fnmatch +import shutil +import copy from optparse import OptionParser +signal.signal(signal.SIGINT, signal.SIG_DFL) # undo stupid Python SIGINT thing -tmpdir = None -testbed = None +#---------- global variables -signal.signal(signal.SIGINT, signal.SIG_DFL) # undo stupid Python SIGINT thing +tmpdir = None # pathstring on host +testbed = None # Testbed +errorcode = 0 # exit status that we are going to use +timeouts = { 'short':100, 'install':3000, 'test':10000, 'build':100000 } +binaries = None # Binaries (.debs we have registered) +build_essential = ["build-essential"] + +#---------- output handling +# +# There are at least the following kinds of output: +# +# 1. stderr output which consists of +# 1a. our errors +# 1b. stuff printed to stderr by the virtualisation server +# 1c. stuff printed to stderr by our short-lived subprocesseses +# which we don't expect to fail +# +# 2. trace information, which consists of +# 2a. our trace information from calls to debug() +# 2b. progress information and stderr output from +# general scripts we run on the host +# 2c. progress information and stderr output from things +# we run on the testbed including builds +# 2d. stderr and stdout output from actual tests +# +# xxxx +# 3. actual test results (printed to our stdout) +# +# Cloning of 1a and 2a, where necessary, is done by us writing +# the data twice. Cloning of 1[bc] and 2[bc], where necessary, +# is done by forking off a copy of ourselves to do plumbing, +# which copy we wait for at the appropriate point. + +class DummyOpts: + def __init__(do): do.debuglevel = 0 + +opts = DummyOpts() +trace_stream = None +summary_stream = None + +def pstderr(m): + print >>sys.stderr, m + if trace_stream is not None: print >>trace_stream, m + +def debug(m, minlevel=0): + if opts.debuglevel < minlevel: return + if opts.quiet and trace_stream is None: return + p = 'adt-run: trace' + if minlevel: p += `minlevel` + p += ': ' + for l in m.rstrip('\n').split('\n'): + s = p + l + if not opts.quiet: print >>sys.stderr, s + if trace_stream is not None: print >>trace_stream, s + +def debug_file(hp, minlevel=0): + if opts.debuglevel < minlevel: return + def do_copy(stream, what): + rc = subprocess.call(['cat',hp], stdout=stream) + if rc: bomb('cat failed copying data from %s' + ' to %s, exit code %d' % (hp, what, rc)) + if not opts.quiet: do_copy(sys.stderr, 'stderr') + if trace_stream is not None: do_copy(trace_stream, 'trace log') + +class Errplumb: + def __init__(ep, critical=False): + to_stderr = critical or not opts.quiet + count = to_stderr + (trace_stream is not None) + if count == 0: + ep.stream = open('/dev/null','w') + ep._sp = None + elif count == 1: + if to_stderr: ep.stream = os.dup(2) + else: ep.stream = trace_stream + ep._sp = None + else: + ep._sp = subprocess.Popen(['tee','-a','/dev/stderr'], + stdin=subprocess.PIPE, stdout=trace_stream, + close_fds=True) + ep.stream = ep._sp.stdin + def wait(ep): + if ep._sp is None: return + if type(ep.stream) == type(2): + os.close(ep.stream) + ep.stream = () + ep._sp.stdin.close() + rc = ep._sp.wait() + if rc: bomb('stderr plumbing tee(1) failed, exit code %d' % rc) + ep._sp = None + +def subprocess_cooked(cmdl, critical=False, dbg=None, **kwargs): + if dbg is not None: + if isinstance(dbg,tuple): (what,script) = dbg + else: (what,script) = (dbg,None) + debug_subprocess(what, cmdl, script=script) + ep = Errplumb(critical) + running = subprocess.Popen(cmdl, stderr=ep.stream, **kwargs) + output = running.communicate()[0] + rc = running.wait() + ep.wait() + return (rc, output) + +def psummary(m): + if summary_stream is not None: print >>summary_stream, m + +def preport(m): + print m + sys.stdout.flush() + if trace_stream is not None: print >>trace_stream, m + psummary(m) + +def report(tname, result): + preport('%-20s %s' % (tname, result)) + +#---------- errors we define class Quit: def __init__(q,ec,m): q.ec = ec; q.m = m -def bomb(m): raise Quit(20, "unexpected error: %s" % m) -def badpkg(m): raise Quit(12, "erroneous package: %s" % m) +def bomb(m): + raise Quit(20, "unexpected error: %s" % m) + +def badpkg(m): + preport('blame: ' + ' '.join(testbed.blamed)) + preport('badpkg: ' + m) + raise Quit(12, "erroneous package: %s" % m) -def debug(m): - global opts - if not opts.debug: return - print >>sys.stderr, 'atd-run: debug:', m +class Unsupported: + def __init__(u, lno, m): + if lno >= 0: u.m = '%s (control line %d)' % (m, lno) + else: u.m = m + def report(u, tname): + global errorcode + errorcode != 2 + report(tname, 'SKIP %s' % u.m) + +#---------- convenience function + +def mkdir_okexist(pathname, mode=02755): + try: + os.mkdir(pathname, mode) + except (IOError,OSError), oe: + if oe.errno != errno.EEXIST: raise -class Path: - def __init__(p, tb, path, what, dir=False): - p.tb = tb - p.p = path +def rmtree(what, pathname): + debug('/ %s rmtree %s' % (what, pathname), 2) + try: shutil.rmtree(pathname) + except (IOError,OSError), oe: + if oe.errno != errno.EEXIST: raise + +def debug_subprocess(what, cmdl=None, script=None): + o = '$ '+what+':' + if cmdl is not None: + ol = [] + for x in cmdl: + if x is script: x = '