chiark / gitweb /
delete debug msgs; bomb surviving path better; get tbscratch default right
[autopkgtest.git] / runner / adt-run
index cb312c4b0c15ea51d01b8e994021e17eb2f66424..e663a2a76a8e0070ed1ff3f9db5e266f946dc855 100755 (executable)
@@ -1,17 +1,26 @@
 #!/usr/bin/python2.4
-# usage:
-#      adt-run <options>... --- <virt-server> [<virt-server-arg>...]
 #
-# 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 autodebtest
+# autodebtest is a tool for testing Debian binary packages
+#
+# autodebtest is Copyright (C) 2006 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/autodebtest/CREDITS).
 
 import signal
 import optparse
@@ -23,11 +32,13 @@ import urllib
 import string
 import re as regexp
 import os
+import errno
 
 from optparse import OptionParser
 
 tmpdir = None
 testbed = None
+errorcode = 0
 
 signal.signal(signal.SIGINT, signal.SIG_DFL) # undo stupid Python SIGINT thing
 
@@ -43,6 +54,8 @@ class Unsupported:
        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)
 
 def debug(m):
@@ -54,11 +67,12 @@ def flatten(l):
        return reduce((lambda a,b: a + b), l, []) 
 
 class Path:
- def __init__(p, tb, path, what, dir=False):
+ def __init__(p, tb, path, what, dir=False, tbscratch=None):
        p.tb = tb
        p.p = path
        p.what = what
        p.dir = dir
+       p.tbscratch = tbscratch
        if p.tb:
                if p.p[:1] != '/':
                        bomb("path %s specified as being in testbed but"
@@ -73,21 +87,32 @@ class Path:
  def path(p):
        return p.p + p.dirsfx
  def append(p, suffix, what, dir=False):
-       return Path(p.tb, p.path() + suffix, what=what, dir=dir)
+       return Path(p.tb, p.path() + suffix, what=what, dir=dir,
+                       tbscratch=p.tbscratch)
  def __str__(p):
        if p.tb: pfx = '/VIRT'
        elif p.p[:1] == '/': pfx = '/HOST'
        else: pfx = './'
        return pfx + p.p
- def onhost(p):
-       if p.local is not None: return p.local
+ def onhost(p, lpath = None):
+       if p.local is not None:
+               if lpath is not None: assert(p.local == lpath)
+               return p.local
        testbed.open()
-       p.local = tmpdir + '/tb-' + p.what
+       p.local = lpath
+       if p.local is None: p.local = tmpdir + '/tb-' + p.what
        testbed.command('copyup', (p.path(), p.local + p.dirsfx))
        return p.local
  def ontb(p):
+       testbed.open()
+       if p.tbscratch is not None:
+               if p.tbscratch != testbed.scratch:
+                       p.down = None
        if p.down is not None: return p.down
+       if p.tb:
+               bomb("testbed scratch path " + str(p) + " survived testbed")
        p.down = testbed.scratch.p + '/host-' + p.what
+       p.tbscratch = testbed.scratch
        testbed.command('copydown', (p.path(), p.down + p.dirsfx))
        return p.down
 
@@ -116,10 +141,12 @@ def parse_args():
 
        pa_path('build-tree',   True, 'use build tree from PATH on %s')
        pa_path('control',      False, 'read control file PATH on %s')
+       pa_path('output-dir',   True, 'write stderr/out files in PATH on %s')
 
        pa('-d', '--debug', action='store_true', dest='debug');
-       pa('','--user', type='string',
-               help='run tests as USER (needs root on testbed)')
+       # pa('','--user', type='string',
+       #       help='run tests as USER (needs root on testbed)')
+       # nyi
 
        class SpecialOption(optparse.Option): pass
        vs_op = SpecialOption('','--VSERVER-DUMMY')
@@ -172,9 +199,11 @@ class Testbed:
        if tb.scratch is not None: return
        p = tb.commandr1('open')
        tb.scratch = Path(True, p, 'tb-scratch', dir=True)
+       tb.scratch.tbscratch = tb.scratch
  def close(tb):
        if tb.scratch is None: return
        tb.scratch = None
+       if tb.sp is None: return
        tb.command('close')
  def bomb(tb, m):
        if tb.sp is not None:
@@ -186,14 +215,16 @@ class Testbed:
        tb.sp = None
        raise Quit(16, 'testbed failed: %s' % m)
  def send(tb, string):
+       tb.sp.stdin
        try:
                debug('>> '+string)
                print >>tb.sp.stdin, string
                tb.sp.stdin.flush()
                tb.lastsend = string
        except:
-               tb.bomb('cannot send to testbed: %s' %
-                       formatexception_only(sys.last_type, sys.last_value))
+               (type, value, dummy) = sys.exc_info()
+               tb.bomb('cannot send to testbed: %s' % traceback.
+                       format_exception_only(type, value))
  def expect(tb, keyword, nresults=-1):
        l = tb.sp.stdout.readline()
        if not l: tb.bomb('unexpected eof from the testbed')
@@ -281,6 +312,10 @@ def run_tests():
        testbed.close()
        for t in tests:
                t.run()
+       if not tests:
+               global errorcode
+               report('*', 'SKIP no tests in this package')
+               errorcode |= 8
 
 class Test:
  def __init__(t, tname, base):
@@ -288,18 +323,35 @@ class Test:
                'test name may not contain / character')
        for k in base: setattr(t,k,base[k])
        t.tname = tname
-       t.p = opts.build_tree.append(tname, 'test-'+tname)
-       t.p.ontb()
+       if len(base['testsdir']): tpath = base['testsdir'] + '/' + tname
+       else: tpath = tname
+       t.p = opts.build_tree.append(tpath, 'test-'+tname)
  def report(t, m):
        report(t.tname, m)
+ def reportfail(t, m):
+       global errorcode
+       errorcode |= 4
+       report(t.tname, 'FAIL ' + m)
  def run(t):
        testbed.open()
-       so = testbed.scratch.append('stdout-' + t.tname, 'stdout-' + t.tname)
-       se = testbed.scratch.append('stdout-' + t.tname, 'stdout-' + t.tname)
+       def stdouterr(oe):
+               idstr = oe + '-' + t.tname
+               if opts.output_dir is not None and opts.output_dir.tb:
+                       return opts.output_dir.append(idstr)
+               else:
+                       return testbed.scratch.append(idstr, idstr)
+       def stdouterrh(p, oe):
+               idstr = oe + '-' + t.tname
+               if opts.output_dir is None or opts.output_dir.tb:
+                       return p.onhost()
+               else:
+                       return p.onhost(opts.output_dir.onhost() + '/' + idstr)
+       so = stdouterr('stdout')
+       se = stdouterr('stderr')
        rc = testbed.commandr1('execute',(t.p.ontb(),
                '/dev/null', so.ontb(), se.ontb()))
-       soh = so.onhost()
-       seh = se.onhost()
+       soh = stdouterrh(so, 'stdout')
+       soe = stdouterrh(se, 'stderr')
        testbed.close()
        rc = int(rc)
        stab = os.stat(soh)
@@ -307,15 +359,20 @@ class Test:
                l = file(seh).readline()
                l = l.rstrip('\n \t\r')
                if len(l) > 40: l = l[:40] + '...'
-               t.report('FAIL stderr: %s' % l)
+               t.reportfail('stderr: %s' % l)
        elif rc != 0:
-               t.report('FAIL non-zero exit status %d' % rc)
+               t.reportfail('non-zero exit status %d' % rc)
        else:
                t.report('PASS')
 
 def read_control():
        global tests
-       control = file(opts.control.onhost(), 'r')
+       try:
+               control = file(opts.control.onhost(), 'r')
+       except IOError, oe:
+               if oe[0] != errno.ENOENT: raise
+               tests = []
+               return
        lno = 0
        def badctrl(m): testbed.badpkg('tests/control line %d: %s' % (lno, m))
        stz = None # stz[field_name][index] = (lno, value)
@@ -409,22 +466,28 @@ def cleanup():
        except:
                print_exception(sys.exc_info(),
                        '\nadt-run: error cleaning up:\n')
-               sys.exit(20)
+               os._exit(20)
 
 def main():
        global testbed
        global tmpdir
        try:
                parse_args()
+       except SystemExit, se:
+               os._exit(20)
+       try:
                tmpdir = tempfile.mkdtemp()
                testbed = Testbed()
                testbed.start()
+               testbed.open()
+               testbed.close()
                read_control()
                run_tests()
        except:
                ec = print_exception(sys.exc_info(), '')
                cleanup()
-               sys.exit(ec)
+               os._exit(ec)
        cleanup()
+       os._exit(errorcode)
 
 main()