chiark / gitweb /
working on adt-run
[autopkgtest.git] / runner / adt-run
1 #!/usr/bin/python2.4
2 # usage:
3 #       adt-run <options>... --- <virt-server> [<virt-server-arg>...]
4 #
5 # invoke in toplevel of package (not necessarily built)
6 # with package installed
7
8 # exit status:
9 #  0 all tests passed
10 #  4 at least one test failed
11 #  8 no tests in this package
12 # 12 erroneous package
13 # 16 testbed failure
14 # 20 other unexpected failures including bad usage
15
16 import signal
17 import optparse
18 import tempfile
19 import sys
20 import subprocess
21 import traceback
22
23 from optparse import OptionParser
24
25 tmpdir = None
26 testbed = None
27
28 signal.signal(signal.SIGINT, signal.SIG_DFL) # undo stupid Python SIGINT thing
29
30 class Quit:
31         def __init__(q,ec,m): q.ec = ec; q.m = m
32
33 def bomb(m): raise Quit(20, "unexpected error: %s" % m)
34 def badpkg(m): raise Quit(12, "erroneous package: %s" % m)
35
36 class Path:
37  def __init__(p, tb, path, dir=False):
38         p.tb = tb
39         p.p = path
40         p.dir = dir
41         if p.tb and p.p[:1] != '/':
42                 bomb("path specified as being in testbed but"
43                         " not absolute: `%s'" % p.p)
44  def path(p):
45         if p.dir: return p.p + '/'
46         else: return p.p
47  def append(p, suffix, dir=False):
48         return Path(p.path() + suffix, dir)
49  def __str__(p):
50         if p.tb: pfx = '/VIRT'
51         else if p.p[:1] == '/': pfx = '/HOST'
52         else pfx = './'
53         return pfx + p.p
54  def onhost(p):
55         if not p.tb: return p.p
56         testbed.open()
57         fixme testbed.command()
58
59 def parse_args():
60         global opts
61         usage = "%prog <options> -- <virt-server>..."
62         parser = OptionParser(usage=usage)
63         pa = parser.add_option
64         pe = parser.add_option
65
66         def cb_vserv(op,optstr,value,parser):
67                 parser.values.vserver = list(parser.rargs)
68                 del parser.rargs[:]
69
70         def cb_path(op,optstr,value,parser, long,tb,dir):
71                 name = long.replace('-','_')
72                 parser.values.__dict__[name] = Path(tb, value, dir)
73
74         def pa_path(long, dir, help):
75                 def papa_tb(long, ca, pahelp):
76                         pa('', long, action='callback', callback=cb_path,
77                                 nargs=1, type='string', callback_args=ca,
78                                 help=(help % pahelp), metavar='PATH')
79                 papa_tb('--'+long,      (long, False, dir), 'host')
80                 papa_tb('--'+long+'-tb',(long, True, dir), 'testbed')
81
82         pa_path('build-tree',   True, 'use build tree from PATH on %s')
83         pa_path('control',      False, 'read control file PATH on %s')
84
85         pa('-d', '--debug', action='store_true', dest='debug');
86         pa('','--user', type='string',
87                 help='run tests as USER (needs root on testbed)')
88
89         class SpecialOption(optparse.Option): pass
90         vs_op = SpecialOption('','--VSERVER-DUMMY')
91         vs_op.action = 'callback'
92         vs_op.type = None
93         vs_op.default = None
94         vs_op.nargs = 0
95         vs_op.callback = cb_vserv
96         vs_op.callback_args = ( )
97         vs_op.callback_kwargs = { }
98         vs_op.help = 'introduces virtualisation server and args'
99         vs_op._short_opts = []
100         #vs_op._long_opts = ['--DUMMY']
101         vs_op._long_opts = ['---']
102
103         pa(vs_op)
104
105         (opts,args) = parser.parse_args()
106         if not hasattr(opts,'vserver'):
107                 parser.error('you must specifiy --- <virt-server>...')
108
109         if opts.build_tree is None:
110                 opts.build_tree = Path('.', tb=False, dir=True)
111         if opts.control is None:
112                 opts.control = opts.build_tree.append('debian/tests/control')
113
114 class Testbed:
115  def __init__(tb): tb.sp = None
116  def start(tb):
117         p = subprocess.PIPE
118         tb.lastsend = None
119         tb.sp = subprocess.Popen(opts.vserver,
120                 stdin=p, stdout=p, stderr=None)
121         tb.expect('ok')
122  def stop(tb):
123         tb.close(tb)
124         if tb.sp is None: return
125         ec = tb.sp.returncode
126         if ec is None:
127                 tb.sp.stdout.close()
128                 tb.send('quit')
129                 tb.sp.stdin.close()
130                 ec = tb.sp.wait()
131         if ec:
132                 tb.bomb('testbed gave exit status %d after quit' % ec)
133  def open(tb):
134         fixme
135  def close(tb):
136         fixme
137  def bomb(tb, m):
138         if tb.sp is not None:
139                 tb.sp.stdout.close()
140                 tb.sp.stdin.close()
141                 ec = tb.sp.wait()
142                 if ec: print >>sys.stderr, ('testbed failing,'
143                         ' exit status %d' % ec)
144         tb.sp = None
145         raise Quit(16, 'testbed failed: %s' % m)
146  def send(tb, string):
147         try:
148                 print >>tb.sp.stdin, string
149                 tb.sp.stdin.flush()
150                 tb.lastsend = string
151         except:
152                 tb.bomb('cannot send to testbed: %s' %
153                         formatexception_only(sys.last_type, sys.last_value))
154  def expect(tb, keyword, nresults=-1):
155         l = tb.sp.stdout.readline()
156         if not l: tb.bomb('unexpected eof from the testbed')
157         ll = ll.split()
158         if not ll: tb.bomb('unexpected whitespace-only line from the testbed')
159         if ll[0] != keyword:
160                 if tb.lastsend is None:
161                         tb.bomb("got banner `%s', expected `%s...'" %
162                                 (l, keyword))
163                 else:
164                         tb.bomb("sent `%s', got `%s', expected `%s...'" %
165                                 (tb.lastsend, l, keyword))
166         ll = ll[1:]
167         if nresults >= 0 and len(ll) != nresults:
168                 tb.bomb("sent `%s', got `%s' (%d result parameters),"
169                         " expected %d result parameters" %
170                         (string, l, len(ll), nresults))
171         return ll
172  def commandr(tb, cmd, nresults, args=()):
173         al = [cmd] + map(urllib.quote, args)
174         tb.send(string.join(cmd))
175         ll = tb.expect('ok')
176         rl = map(urllib.unquote, ll)
177         return rl
178  def command(tb, cmd, args=()):
179         commandr(rb, cmd, 0, args)
180
181 def read_control():
182         control = file(hostpath(opts.control), 'r')
183         testbed.close()
184
185 def print_exception(ei, msgprefix=''):
186         print >>sys.stderr, msgprefix
187         (et, q, tb) = ei
188         if et is Quit:
189                 print >>sys.stderr, 'adt-run: ', q.m
190                 return q.ec
191         else:
192                 print >>sys.stderr, "adt-run: unexpected, exceptional, error:"
193                 traceback.print_exc()
194                 return 20
195
196 def cleanup():
197         try:
198                 if tmpdir is not None:
199                         rm_ec = subprocess.call(['rm','-rf','--',tmpdir])
200                 testbed.stop()
201                 if rm_ec: bomb('rm -rf -- %s failed, code %d' % (tmpdir, ec))
202         except:
203                 print_exception(sys.exc_info(),
204                         '\nadt-run: error cleaning up:\n')
205                 sys.exit(20)
206
207 def main():
208         try:
209                 parse_args()
210                 tmpdir = tempfile.mkdtemp()
211                 testbed = Testbed()
212                 testbed.start()
213                 read_control()
214         except:
215                 ec = print_exception(sys.exc_info(), '')
216                 cleanup()
217                 sys.exit(ec)
218         cleanup()
219
220 main()