#!/usr/bin/python2.4 # # 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 import tempfile import sys 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 #---------- global variables 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): preport('blame: ' + ' '.join(testbed.blamed)) preport('badpkg: ' + m) raise Quit(12, "erroneous package: %s" % 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 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 = '