3 # adt-run is part of autodebtest
4 # autodebtest is a tool for testing Debian binary packages
6 # autodebtest is Copyright (C) 2006 Canonical Ltd.
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 # See the file CREDITS for a full list of credits information (often
23 # installed as /usr/share/doc/autodebtest/CREDITS).
37 from optparse import OptionParser
43 signal.signal(signal.SIGINT, signal.SIG_DFL) # undo stupid Python SIGINT thing
46 def __init__(q,ec,m): q.ec = ec; q.m = m
48 def bomb(m): raise Quit(20, "unexpected error: %s" % m)
49 def badpkg(m): raise Quit(12, "erroneous package: %s" % m)
50 def report(tname, result): print '%-20s %s' % (tname, result)
53 def __init__(u, lno, m):
54 if lno >= 0: u.m = '%s (control line %d)' % (m, lno)
59 report(tname, 'SKIP %s' % u.m)
63 if not opts.debug: return
64 print >>sys.stderr, 'atd-run: debug:', m
67 return reduce((lambda a,b: a + b), l, [])
70 def __init__(p, tb, path, what, dir=False, tbscratch=False):
75 p.tbscratch = tbscratch
78 bomb("path %s specified as being in testbed but"
79 " not absolute: `%s'" % (what, p.p))
85 if p.dir: p.dirsfx = '/'
89 def append(p, suffix, what, dir=False):
90 return Path(p.tb, p.path() + suffix, what=what, dir=dir,
91 tbscratch=p.tbscratch)
93 if p.tb: pfx = '/VIRT'
94 elif p.p[:1] == '/': pfx = '/HOST'
97 def onhost(p, lpath = None):
98 if p.local is not None:
99 if lpath is not None: assert(p.local == lpath)
103 if p.local is None: p.local = tmpdir + '/tb-' + p.what
104 testbed.command('copyup', (p.path(), p.local + p.dirsfx))
107 print 'ontb', p, testbed.scratch
109 if p.tbscratch is not None:
110 if p.tbscratch != testbed.scratch:
112 if p.down is not None: return p.down
114 p.down = testbed.scratch.p + '/host-' + p.what
115 p.tbscratch = testbed.scratch
116 testbed.command('copydown', (p.path(), p.down + p.dirsfx))
121 usage = "%prog <options> -- <virt-server>..."
122 parser = OptionParser(usage=usage)
123 pa = parser.add_option
124 pe = parser.add_option
126 def cb_vserv(op,optstr,value,parser):
127 parser.values.vserver = list(parser.rargs)
130 def cb_path(op,optstr,value,parser, long,tb,dir):
131 name = long.replace('-','_')
132 setattr(parser.values, name, Path(tb, value, long, dir))
134 def pa_path(long, dir, help):
135 def papa_tb(long, ca, pahelp):
136 pa('', long, action='callback', callback=cb_path,
137 nargs=1, type='string', callback_args=ca,
138 help=(help % pahelp), metavar='PATH')
139 papa_tb('--'+long, (long, False, dir), 'host')
140 papa_tb('--'+long+'-tb',(long, True, dir), 'testbed')
142 pa_path('build-tree', True, 'use build tree from PATH on %s')
143 pa_path('control', False, 'read control file PATH on %s')
144 pa_path('output-dir', True, 'write stderr/out files in PATH on %s')
146 pa('-d', '--debug', action='store_true', dest='debug');
147 # pa('','--user', type='string',
148 # help='run tests as USER (needs root on testbed)')
151 class SpecialOption(optparse.Option): pass
152 vs_op = SpecialOption('','--VSERVER-DUMMY')
153 vs_op.action = 'callback'
157 vs_op.callback = cb_vserv
158 vs_op.callback_args = ( )
159 vs_op.callback_kwargs = { }
160 vs_op.help = 'introduces virtualisation server and args'
161 vs_op._short_opts = []
162 #vs_op._long_opts = ['--DUMMY']
163 vs_op._long_opts = ['---']
167 (opts,args) = parser.parse_args()
168 if not hasattr(opts,'vserver'):
169 parser.error('you must specifiy --- <virt-server>...')
171 if opts.build_tree is None:
172 opts.build_tree = Path(False, '.', 'build-tree', dir=True)
173 if opts.control is None:
174 opts.control = opts.build_tree.append(
175 'debian/tests/control', 'control')
184 tb.sp = subprocess.Popen(opts.vserver,
185 stdin=p, stdout=p, stderr=None)
189 if tb.sp is None: return
190 ec = tb.sp.returncode
197 tb.bomb('testbed gave exit status %d after quit' % ec)
199 if tb.scratch is not None: return
200 p = tb.commandr1('open')
201 tb.scratch = Path(True, p, 'tb-scratch', dir=True)
202 tb.scratch.tbscratch = tb.scratch
204 if tb.scratch is None: return
206 if tb.sp is None: return
209 if tb.sp is not None:
213 if ec: print >>sys.stderr, ('adt-run: testbed failing,'
214 ' exit status %d' % ec)
216 raise Quit(16, 'testbed failed: %s' % m)
217 def send(tb, string):
221 print >>tb.sp.stdin, string
225 (type, value, dummy) = sys.exc_info()
226 tb.bomb('cannot send to testbed: %s' % traceback.
227 format_exception_only(type, value))
228 def expect(tb, keyword, nresults=-1):
229 l = tb.sp.stdout.readline()
230 if not l: tb.bomb('unexpected eof from the testbed')
231 if not l.endswith('\n'): tb.bomb('unterminated line from the testbed')
235 if not ll: tb.bomb('unexpected whitespace-only line from the testbed')
237 if tb.lastsend is None:
238 tb.bomb("got banner `%s', expected `%s...'" %
241 tb.bomb("sent `%s', got `%s', expected `%s...'" %
242 (tb.lastsend, l, keyword))
244 if nresults >= 0 and len(ll) != nresults:
245 tb.bomb("sent `%s', got `%s' (%d result parameters),"
246 " expected %d result parameters" %
247 (string, l, len(ll), nresults))
249 def commandr(tb, cmd, nresults, args=()):
250 al = [cmd] + map(urllib.quote, args)
251 tb.send(string.join(al))
253 rl = map(urllib.unquote, ll)
255 def command(tb, cmd, args=()):
256 tb.commandr(cmd, 0, args)
257 def commandr1(tb, cmd, args=()):
258 rl = tb.commandr(cmd, 1, args)
262 def __init__(f, fname, stz, base, tnames, vl):
272 r = map((lambda w: (lno, w)), r)
274 return flatten(map(distribute, f.vl))
279 raise Unsupported(f.vl[1][0],
280 'only one %s field allowed' % fn)
283 class FieldIgnore(FieldBase):
287 def __init__(r,rname,base): pass
289 class Restriction_rw_build_tree(Restriction): pass
291 class Field_Restrictions(FieldBase):
293 for wle in f.words():
295 rname = rname.replace('-','_')
296 try: rclass = globals()['Restriction_'+rname]
297 except KeyError: raise Unsupported(lno,
298 'unknown restriction %s' % rname)
299 r = rclass(rname, f.base)
300 f.base['restrictions'].append(r)
302 class Field_Tests(FieldIgnore): pass
304 class Field_Tests_directory(FieldBase):
307 if td.startswith('/'): raise Unspported(f.lno,
308 'Tests-Directory may not be absolute')
309 base['testsdir'] = td
317 report('*', 'SKIP no tests in this package')
321 def __init__(t, tname, base):
322 if '/' in tname: raise Unsupported(base[' lno'],
323 'test name may not contain / character')
324 for k in base: setattr(t,k,base[k])
326 if len(base['testsdir']): tpath = base['testsdir'] + '/' + tname
328 t.p = opts.build_tree.append(tpath, 'test-'+tname)
331 def reportfail(t, m):
334 report(t.tname, 'FAIL ' + m)
339 idstr = oe + '-' + t.tname
340 print 'stdouterr', oe, idstr, opts.output_dir
341 if opts.output_dir is not None and opts.output_dir.tb:
342 return opts.output_dir.append(idstr)
344 return testbed.scratch.append(idstr, idstr)
345 def stdouterrh(p, oe):
346 idstr = oe + '-' + t.tname
347 if opts.output_dir is None or opts.output_dir.tb:
350 return p.onhost(opts.output_dir.onhost() + '/' + idstr)
351 so = stdouterr('stdout')
353 se = stdouterr('stderr')
354 print 'commandr1', t.p, so, se
355 rc = testbed.commandr1('execute',(t.p.ontb(),
356 '/dev/null', so.ontb(), se.ontb()))
357 soh = stdouterrh(so, 'stdout')
358 soe = stdouterrh(se, 'stderr')
362 if stab.st_size != 0:
363 l = file(seh).readline()
364 l = l.rstrip('\n \t\r')
365 if len(l) > 40: l = l[:40] + '...'
366 t.reportfail('stderr: %s' % l)
368 t.reportfail('non-zero exit status %d' % rc)
375 control = file(opts.control.onhost(), 'r')
377 if oe[0] != errno.ENOENT: raise
381 def badctrl(m): testbed.badpkg('tests/control line %d: %s' % (lno, m))
382 stz = None # stz[field_name][index] = (lno, value)
388 if stz is None: return
394 initre = regexp.compile('([A-Z][-0-9a-z]*)\s*\:\s*(.*)$')
396 l = control.readline()
399 if not l.endswith('\n'): badctrl('unterminated line')
400 if regexp.compile('\s*\#').match(l): continue
401 if not regexp.compile('\S').match(l): end_stanza(stz); continue
402 initmat = initre.match(l)
404 (fname, l) = initmat.groups()
405 fname = string.capwords(fname)
407 stz = { ' lno': lno }
408 if not stz.has_key(fname): stz[fname] = [ ]
409 hcurrent = stz[fname]
410 elif regexp.compile('\s').match(l):
411 if not hcurrent: badctrl('unexpected continuation')
413 badctrl('syntax error')
414 hcurrent.append((lno, l))
417 def testbadctrl(stz, lno, m):
418 report_badctrl(lno, m)
423 try: tnames = stz['Tests']
426 raise Unsupported(stz[' lno'],
428 tnames = map((lambda lt: lt[1]), tnames)
429 tnames = string.join(tnames).split()
432 'testsdir': 'debian/tests'
434 for fname in stz.keys():
435 if fname.startswith(' '): continue
437 try: fclass = globals()['Field_'+
438 fname.replace('-','_')]
439 except KeyError: raise Unsupported(vl[0][0],
440 'unknown metadata field %s' % fname)
441 f = fclass(stz, fname, base, tnames, vl)
445 t = Test(tname, base)
447 except Unsupported, u:
448 for tname in tnames: u.report(tname)
451 def print_exception(ei, msgprefix=''):
452 if msgprefix: print >>sys.stderr, msgprefix
455 print >>sys.stderr, 'adt-run:', q.m
458 print >>sys.stderr, "adt-run: unexpected, exceptional, error:"
459 traceback.print_exc()
465 if tmpdir is not None:
466 rm_ec = subprocess.call(['rm','-rf','--',tmpdir])
467 if testbed is not None:
469 if rm_ec: bomb('rm -rf -- %s failed, code %d' % (tmpdir, ec))
471 print_exception(sys.exc_info(),
472 '\nadt-run: error cleaning up:\n')
480 except SystemExit, se:
483 tmpdir = tempfile.mkdtemp()
491 ec = print_exception(sys.exc_info(), '')