chiark / gitweb /
adt-run: make sure apt-listbugs + apt-listchanges do not show up
[autopkgtest.git] / runner / adt-run
1 #!/usr/bin/python2.6
2 #
3 # adt-run is part of autopkgtest
4 # autopkgtest is a tool for testing Debian binary packages
5 #
6 # autopkgtest is Copyright (C) 2006-2007 Canonical Ltd.
7 #
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.
12 #
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.
17 #
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.
21 #
22 # See the file CREDITS for a full list of credits information (often
23 # installed as /usr/share/doc/autopkgtest/CREDITS).
24
25 import signal
26 import optparse
27 import tempfile
28 import sys
29 import subprocess
30 import traceback
31 import urllib
32 import string
33 import re as regexp
34 import os
35 import errno
36 import fnmatch
37 import shutil
38 import copy
39
40 from optparse import OptionParser
41 signal.signal(signal.SIGINT, signal.SIG_DFL) # undo stupid Python SIGINT thing
42
43 try: our_base = os.environ['AUTOPKGTEST_BASE']+'/lib'
44 except KeyError: our_base = '/usr/share/autopkgtest/python';
45 sys.path.insert(1, our_base)
46
47 from Autopkgtest import *
48
49 #---------- global variables
50
51 tmpdir = None           # pathstring on host
52 testbed = None          # Testbed
53 errorcode = 0           # exit status that we are going to use
54 timeouts = { 'short':100, 'install':3000, 'test':10000, 'build':100000 }
55 binaries = None         # Binaries (.debs we have registered)
56 build_essential = ["build-essential"]
57 paths = []
58 os.putenv("APT_LISTBUGS_FRONTEND", "none")    # do not consider using apt-listbugs
59 os.putenv("APT_LISTCHANGES_FRONTEND", "none") # do not consider using apt-listchanges
60
61 #---------- output handling
62 #
63 #  There are at least the following kinds of output:
64 #
65 #   1. stderr output which consists of
66 #   1a. our errors
67 #   1b. stuff printed to stderr by the virtualisation server
68 #   1c. stuff printed to stderr by our short-lived subprocesseses
69 #           which we don't expect to fail
70 #
71 #   2. trace information, which consists of
72 #   2a. our trace information from calls to debug()
73 #   2b. progress information and stderr output from
74 #        general scripts we run on the host
75 #   2c. progress information and stderr output from things
76 #        we run on the testbed including builds
77 #   2d. stderr and stdout output from actual tests
78 #
79 #   xxxx
80 #   3. actual test results (printed to our stdout)
81 #
82 # Cloning of 1a and 2a, where necessary, is done by us writing
83 # the data twice.  Cloning of 1[bc] and 2[bc], where necessary,
84 # is done by forking off a copy of ourselves to do plumbing,
85 # which copy we wait for at the appropriate point.
86
87 class DummyOpts:
88  def __init__(do): do.debuglevel = 0
89
90 opts = DummyOpts()
91 trace_stream = None
92 summary_stream = None
93
94 def pstderr(m):
95         print >>sys.stderr, m
96         if trace_stream is not None: print >>trace_stream, m
97
98 def debug(m, minlevel=0):
99         if opts.debuglevel < minlevel: return
100         if opts.quiet and trace_stream is None: return
101         p = 'adt-run: trace'
102         if minlevel: p += `minlevel`
103         p += ': '
104         for l in m.rstrip('\n').split('\n'):
105                 s = p + l
106                 if not opts.quiet: print >>sys.stderr, s
107                 if trace_stream is not None: print >>trace_stream, s
108
109 def debug_file(hp, minlevel=0):
110         if opts.debuglevel < minlevel: return
111         def do_copy(stream, what):
112                 rc = subprocess.call(['cat',hp], stdout=stream)
113                 if rc: bomb('cat failed copying data from %s'
114                             ' to %s, exit code %d' % (hp, what, rc))
115         if not opts.quiet: do_copy(sys.stderr, 'stderr')
116         if trace_stream is not None: do_copy(trace_stream, 'trace log')
117
118 class Errplumb:
119  def __init__(ep, critical=False):
120         to_stderr = critical or not opts.quiet
121         count = to_stderr + (trace_stream is not None)
122         if count == 0:
123                 ep.stream = open('/dev/null','w')
124                 ep._sp = None
125         elif count == 1:
126                 if to_stderr: ep.stream = os.dup(2)
127                 else: ep.stream = trace_stream
128                 ep._sp = None
129         else:
130                 ep._sp = subprocess.Popen(['tee','-a','/dev/stderr'],
131                         stdin=subprocess.PIPE, stdout=trace_stream,
132                         close_fds=True)
133                 ep.stream = ep._sp.stdin
134  def wait(ep):
135         if ep._sp is None: return
136         if type(ep.stream) == type(2):
137                 os.close(ep.stream)
138                 ep.stream = ()
139         ep._sp.stdin.close()
140         rc = ep._sp.wait()
141         if rc: bomb('stderr plumbing tee(1) failed, exit code %d' % rc)
142         ep._sp = None
143
144 def subprocess_cooked(cmdl, critical=False, dbg=None, **kwargs):
145         if dbg is not None:
146                 if isinstance(dbg,tuple): (what,script) = dbg
147                 else: (what,script) = (dbg,None)
148                 debug_subprocess(what, cmdl, script=script)
149         ep = Errplumb(critical)
150         running = subprocess.Popen(cmdl, stderr=ep.stream, **kwargs)
151         output = running.communicate()[0]
152         rc = running.wait()
153         ep.wait()
154         return (rc, output)
155
156 def psummary(m):
157         if summary_stream is not None: print >>summary_stream, m
158
159 def preport(m):
160         print m
161         sys.stdout.flush()
162         if trace_stream is not None: print >>trace_stream, m
163         psummary(m)
164
165 def report(tname, result):
166         preport('%-20s %s' % (tname, result))
167
168 #---------- errors we define
169
170 class Quit:
171         def __init__(q,ec,m): q.ec = ec; q.m = m
172
173 def bomb(m):
174         raise Quit(20, "unexpected error: %s" % m)
175
176 def badpkg(m):
177         preport('blame: ' + ' '.join(testbed.blamed))
178         preport('badpkg: ' + m)
179         raise Quit(12, "erroneous package: %s" % m)
180
181 class Unsupported:
182  def __init__(u, lno, m):
183         if lno >= 0: u.m = '%s (control line %d)' % (m, lno)
184         else: u.m = m
185  def report(u, tname):
186         global errorcode
187         errorcode != 2
188         report(tname, 'SKIP %s' % u.m)
189
190 #---------- convenience function
191
192 def mkdir_okexist(pathname, mode=02755):
193         try:
194                 os.mkdir(pathname, mode)
195         except (IOError,OSError), oe:
196                 if oe.errno != errno.EEXIST: raise
197
198 def rmtree(what, pathname):
199         debug('/ %s rmtree %s' % (what, pathname), 2)
200         try: shutil.rmtree(pathname)
201         except (IOError,OSError), oe:
202                 if oe.errno != errno.EEXIST: raise
203
204 def debug_subprocess(what, cmdl=None, script=None):
205         o = '$ '+what+':'
206         if cmdl is not None:
207                 ol = []
208                 for x in cmdl:
209                         if x is script: x = '<SCRIPT>'
210                         ol.append(x.    replace('\\','\\\\').
211                                         replace(' ','\\ ')      )
212                 o += ' '+ ' '.join(ol)
213         debug(o)
214         if script is not None and opts.debuglevel >= 1:
215                 o = ''
216                 for l in script.rstrip('\n').split('\n'):
217                         o += '$     '+l+'\n'
218                 debug(o,1)
219
220 def flatten(l):
221         return reduce((lambda a,b: a + b), l, []) 
222
223 #---------- fancy automatic file-copying class
224
225 class AutoFile:
226         # p.what
227         # p.path[tb]    None or path    not None => path known
228         # p.file[tb]    None or path    not None => file exists
229         # p.ephem[tb]   boolean         True => destroyed by tb reset
230         # p.spec        string or None
231         # p.spec_tbp    True or False, or not set if spec is None
232         # p.dir         '' or '/'
233
234  def __init__(p, what):
235         p.what = what
236         p.path = [None,None]
237         p.file = [None,None]
238         p.ephem = [False,False]
239         p.spec = None
240         p.spec_tbp = None
241         p.dir = ''
242
243  def __repr__(p):
244         return "<AF@%s>" % p.__str__()
245  def __str__(p):
246         def ptbp(tbp):
247                 if p.path[tbp] is None: return '-'+p.dir
248                 elif p.file[tbp] is None: return p.path[tbp]+p.dir+'?'
249                 else: return p.path[tbp]+p.dir+'!'
250         out = p.what
251         if p.spec is not None:
252                 if not hasattr(p,'spec_tb'): out += '~'
253                 elif p.spec_tbp: out += '#'
254                 else: out += '='
255                 out += p.spec
256         out += ':'
257         out += ptbp(False)
258         out += '|'
259         out += ptbp(True)
260         return out
261
262  def _wrong(p, how):
263         xtra = ''
264         if p.spec is not None:
265                 xtra = ' spec[%s]=%s' % (p.spec, getattr(p,'spec_tb',None))
266         raise Exception("internal error: %s (%s)" % (how, str(p)))
267
268  def _ensure_path(p, tbp):
269         if p.path[tbp] is None:
270                 if '/' in p.what:
271                         p._debug('tmp-parent %s...' % 'HT'[tbp])
272                         TemporaryDir(os.path.dirname(p.what)).write(tbp)
273                 if not tbp:
274                         p.path[tbp] = tmpdir+'/'+p.what
275                 else:
276                         p.ephem[tbp] = True
277                         p.path[tbp] = testbed.scratch.path[True]+'/'+p.what
278
279  def write(p, tbp=False):
280         p._debug('write %s...' % 'HT'[tbp])
281         p._ensure_path(tbp)
282
283         if p.dir and not p.file[tbp]:
284                 if not tbp:
285                         p._debug('mkdir H')
286                         mkdir_okexist(p.path[tbp])
287                 else:
288                         cmdl = ['sh','-ec',
289                                 'test -d "$1" || mkdir -p "$1"',
290                                 'x', p.path[tbp]]
291                         tf_what = urllib.quote(p.what).replace('/','%2F')
292                         rc = testbed.execute('mkdir-'+tf_what, cmdl)
293                         if rc: bomb('failed to create directory %s' %
294                                 p.path[tbp])
295
296         p.file[tbp] = p.path[tbp]
297         return p.path[tbp]
298
299  def read(p, tbp=False):
300         p._debug('read %s...' % 'HT'[tbp])
301         p._ensure_path(tbp)
302
303         if p.file[tbp] is None:
304                 if p.file[not tbp] is None:
305                         p._wrong("requesting read but nonexistent")
306                 cud = ['copyup','copydown'][tbp]
307                 src = p.file[not tbp] + p.dir
308                 dst = p.path[tbp] + p.dir
309                 testbed.command(cud, (src, dst))
310                 p.file[tbp] = p.path[tbp]
311
312         return p.file[tbp] + p.dir
313
314  def invalidate(p, tbp=False):
315         if p.path[tbp] is not None:
316                 p._debug('invalidating %s' % 'HT'[tbp])
317         p.file[tbp] = None
318         if p.spec_tbp != tbp or p.spec is None:
319                 p.path[tbp] = None
320
321  def invalidate_ephem(p, tbp=False):
322         if p.ephem[tbp]:
323                 p.invalidate(tbp)
324
325  def _debug(p, m):
326         debug('/ %s#%x: %s' % (p.what, id(p), m), 3)
327
328  def _constructed(p):
329         p._debug('constructed: '+str(p))
330         p._check()
331         paths.append(p)
332
333  def _check(p):
334         for tbp in [False,True]:
335          for pf in [p.path, p.file]:
336                 if pf[tbp] is None: continue
337                 if not pf[tbp]: bomb('empty path specified for %s' % p.what)
338                 if p.dir and pf[tbp].endswith('/'):
339                         pf[tbp] = pf[tbp].rstrip('/')
340                         if not pf[tbp]: pf[tbp] = '/'
341                 if not p.dir and pf[tbp].endswith('/'):
342                         bomb("directory `%s' specified for "
343                              "non-directory %s" % (pf[tbp], p.what))
344
345  def _relative_init(p, what, parent, leaf, onlyon_tbp, setfiles, sibling):
346         AutoFile.__init__(p,what)
347         sh_on = ''; sh_sibl = ''
348         if onlyon_tbp is not None: sh_on = ' (on %s)' % ('HT'[onlyon_tbp])
349         if sibling: sh_sibl=' (sibling)'
350         parent._debug('using as base: %s: %s%s%s' %
351                         (str(parent), leaf, sh_on, sh_sibl))
352         if not sibling and not parent.dir:
353                 parent._wrong('asked for non-sibling relative path of non-dir')
354         if sibling: trim = os.path.dirname
355         else: trim = lambda x: x
356         for tbp in [False,True]:
357                 p.ephem[tbp] = parent.ephem[tbp]
358                 if parent.path[tbp] is None: continue
359                 trimmed = trim(parent.path[tbp])
360                 if trimmed: trimmed += '/'
361                 p.path[tbp] = trimmed + leaf
362                 if setfiles and (onlyon_tbp is None or onlyon_tbp == tbp):
363                         p.file[tbp] = p.path[tbp]
364
365 class InputFile(AutoFile):
366  def _init(p, what, spec, spec_tbp=False):
367         AutoFile.__init__(p, what)
368         p.spec = spec
369         p.spec_tbp = spec_tbp
370         p.path[spec_tbp] = spec
371         p.file[p.spec_tbp] = p.path[p.spec_tbp]
372  def __init__(p, what, spec, spec_tbp=False):
373         p._init(what,spec,spec_tbp)
374         p._constructed()
375
376 class InputDir(InputFile):
377  def __init__(p, what, spec, spec_tbp=False):
378         InputFile._init(p,what,spec,spec_tbp)
379         p.dir = '/'
380         p._constructed()
381
382 class OutputFile(AutoFile):
383  def _init(p, what, spec, spec_tbp=False):
384         AutoFile.__init__(p, what)
385         p.spec = spec
386         p.spec_tbp = spec_tbp
387         p.path[spec_tbp] = spec
388  def __init__(p, what, spec, spec_tbp=False):
389         p._init(what,spec,spec_tbp)
390         p._constructed()
391
392 class OutputDir(OutputFile):
393  def __init__(p, what, spec, spec_tbp=False):
394         OutputFile._init(p,what,spec,spec_tbp)
395         p.dir = '/'
396         p._constructed()
397
398 class RelativeInputDir(AutoFile):
399  def __init__(p, what, parent, leaf, onlyon_tbp=None, sibling=False):
400         p._relative_init(what, parent, leaf, onlyon_tbp, True, sibling)
401         p.dir = '/'
402         p._constructed()
403
404 class RelativeInputFile(AutoFile):
405  def __init__(p, what, parent, leaf, onlyon_tbp=None, sibling=False):
406         p._relative_init(what, parent, leaf, onlyon_tbp, True, sibling)
407         p._constructed()
408
409 class RelativeOutputFile(AutoFile):
410  def __init__(p, what, parent, leaf, sibling=False):
411         p._relative_init(what, parent, leaf, None, False, sibling)
412         p._constructed()
413
414 class TemporaryFile(AutoFile):
415  def __init__(p, what):
416         AutoFile.__init__(p, what)
417         p._constructed()
418
419 class TemporaryDir(AutoFile):
420  def __init__(p, what):
421         AutoFile.__init__(p, what)
422         p.dir = '/'
423         p._constructed()
424
425 #---------- parsing and representation of the arguments
426
427 class Action:
428  def __init__(a, kind, af, arghandling, what):
429         # extra attributes get added during processing
430         a.kind = kind
431         a.af = af
432         a.ah = arghandling
433         a.what = what
434         a.missing_tests_control = False
435  def __repr__(a):
436         return "<Action %s %s %s>" % (a.kind, a.what, `a.af`)
437
438 def parse_args():
439         global opts, timeouts
440         global n_non_actions # argh, stupid python scoping rules
441         usage = "%prog <options> --- <virt-server>..."
442         parser = OptionParser(usage=usage)
443         pa = parser.add_option
444         pe = parser.add_option
445
446         arghandling = {
447                 'dsc_tests': True,
448                 'dsc_filter': '*',
449                 'deb_forbuilds': 'auto',
450                 'deb_fortests': 'auto',
451                 'tb': False,
452                 'override_control': None,
453                 'set_lang': 'C'
454         }
455         initial_arghandling = arghandling.copy()
456         n_non_actions = 0
457
458         #----------
459         # actions (ie, test sets to run, sources to build, binaries to use):
460
461         def cb_action(op,optstr,value,parser, long,kindpath,is_act):
462                 global n_non_actions
463                 parser.largs.append((value,kindpath))
464                 n_non_actions += not(is_act)
465
466         def pa_action(long, metavar, kindpath, help, is_act=True):
467                 pa('','--'+long, action='callback', callback=cb_action,
468                         nargs=1, type='string',
469                         callback_args=(long,kindpath,is_act), help=help)
470
471         pa_action('built-tree',         'TREE', '@/',
472                 help='run tests from build tree TREE')
473
474         pa_action('unbuilt-tree',       'TREE', '@//',
475                 help='run tests from build tree TREE')
476
477         pa_action('source',             'DSC', '@.dsc',
478                 help='build DSC and use its tests and/or'
479                     ' generated binary packages')
480
481         pa_action('binary',             'DEB', '@.deb',
482                help='use binary package DEB according'
483                     ' to most recent --binaries-* settings')
484
485         def cb_actnoarg(op,optstr,value,parser, largsentry):
486                 parser.largs.append(largsentry)
487         pa('','--instantiate', action='callback', callback=cb_actnoarg,
488                 callback_args=((None, ('instantiate',)),),
489                 help='instantiate testbed now (during testing phase)'
490                      ' and install packages'
491                      ' selected for automatic installation, even'
492                      ' if this might apparently not be required otherwise')
493
494         pa_action('override-control',   'CONTROL', ('control',), is_act=0,
495                help='run tests from control file CONTROL instead,'
496                     ' (applies to next test suite only)')
497
498         #----------
499         # argument handling settings (what ways to use action
500         #  arguments, and pathname processing):
501
502         def cb_setah(option, opt_str, value, parser, toset,setval):
503                 if type(setval) == list:
504                         if not value in setval:
505                                 parser.error('value for %s option (%s) is not '
506                                  'one of the permitted values (%s)' %
507                                  (value, opt_str, setval.join(' ')))
508                 elif setval is not None:
509                         value = setval
510                 for v in toset:
511                         arghandling[v] = value
512                 parser.largs.append(arghandling.copy())
513
514         def pa_setah(long, affected,effect, metavar=None, **kwargs):
515                 type = metavar
516                 if type is not None: type = 'string'
517                 pa('',long, action='callback', callback=cb_setah,
518                    callback_args=(affected,effect), **kwargs)
519
520         #---- paths: host or testbed:
521         #
522         pa_setah('--paths-testbed', ['tb'],True,
523                 help='subsequent path specifications refer to the testbed')
524         pa_setah('--paths-host', ['tb'],False,
525                 help='subsequent path specifications refer to the host')
526
527         #---- source processing settings:
528
529         pa_setah('--sources-tests', ['dsc_tests'],True,
530                 help='run tests from builds of subsequent sources')
531         pa_setah('--sources-no-tests', ['dsc_tests'],False,
532                 help='do not run tests from builds of subsequent sources')
533
534         pa_setah('--built-binaries-filter', ['dsc_filter'],None,
535                 type='string', metavar='PATTERN-LIST',
536                 help='from subsequent sources, use binaries matching'
537                      ' PATTERN-LIST (comma-separated glob patterns)'
538                      ' according to most recent --binaries-* settings')
539         pa_setah('--no-built-binaries', ['dsc_filter'], '_',
540                 help='from subsequent sources, do not use any binaries')
541         #---- binary package processing settings:
542
543         def pa_setahbins(long,toset,how):
544          pa_setah(long, toset,['ignore','auto','install'],
545                 type='string', metavar='IGNORE|AUTO|INSTALL', default='auto',
546                 help=how+' ignore binaries, install them as needed'
547                         ' for dependencies, or unconditionally install'
548                         ' them, respectively')
549         pa_setahbins('--binaries', ['deb_forbuilds','deb_fortests'], '')
550         pa_setahbins('--binaries-forbuilds', ['deb_forbuilds'], 'for builds, ')
551         pa_setahbins('--binaries-fortests', ['deb_fortests'], 'for tests, ')
552
553         #----------
554         # general options:
555
556         def cb_vserv(op,optstr,value,parser):
557                 parser.values.vserver = list(parser.rargs)
558                 del parser.rargs[:]
559
560         def cb_path(op,optstr,value,parser, constructor,long,dir):
561                 name = long.replace('-','_')
562                 af = constructor(long, value,arghandling['tb'])
563                 setattr(parser.values, name, af)
564
565         def pa_path(long, constructor, help, dir=False):
566                 pa('','--'+long, action='callback', callback=cb_path,
567                         callback_args=(constructor,long,dir),
568                         nargs=1, type='string',
569                         help=help, metavar='PATH')
570
571         pa_path('output-dir', OutputDir, dir=True,
572                 help='write stderr/out files in PATH')
573
574         pa('--leave-lang', dest='set_lang', action='store_false',
575                 help="leave LANG on testbed set to testbed's default")
576         pa('--set-lang', dest='set_lang', action='store', metavar='LANGVAL',
577                 help='set LANG on testbed to LANGVAL', default='C')
578
579         pa('','--tmp-dir',              type='string', dest='tmpdir',
580                 help='write temporary files to TMPDIR, emptying it'
581                      ' beforehand and leaving it behind at the end')
582         pa('','--log-file',             type='string', dest='logfile',
583                 help='write the log LOGFILE, emptying it beforehand,'
584                      ' instead of using OUTPUT-DIR/log or TMPDIR/log')
585         pa('','--summary-file',         type='string', dest='summary',
586                 help='write a summary report to SUMMARY,'
587                      ' emptying it beforehand')
588
589         for k in timeouts.keys():
590                 pa('','--timeout-'+k,   type='int', dest='timeout_'+k,
591                         metavar='T', help='set %s timeout to T')
592         pa('','--timeout-factor',       type='float', dest='timeout_factor',
593                         metavar='FACTOR', default=1.0,
594                         help='multiply all default timeouts by FACTOR')
595
596         pa('','--user',                 type='string', dest='user',
597                 help='run tests as USER (needs root on testbed)')
598         pa('','--gain-root',            type='string', dest='gainroot',
599                 help='prefix debian/rules binary with GAINROOT')
600         pa('-q', '--quiet', action='store_true', dest='quiet', default=False);
601         pa('-d', '--debug', action='count', dest='debuglevel', default=0);
602         pa('','--gnupg-home',           type='string', dest='gnupghome',
603                 default='~/.autopkgtest/gpg',
604                 help='use GNUPGHOME rather than ~/.autopkgtest (for'
605                         " signing private apt archive);"
606                         " `fresh' means generate new key each time.")
607
608         #----------
609         # actual meat:
610
611         class SpecialOption(optparse.Option): pass
612         vs_op = SpecialOption('','--VSERVER-DUMMY')
613         vs_op.action = 'callback'
614         vs_op.type = None
615         vs_op.default = None
616         vs_op.nargs = 0
617         vs_op.callback = cb_vserv
618         vs_op.callback_args = ( )
619         vs_op.callback_kwargs = { }
620         vs_op.help = 'introduces virtualisation server and args'
621         vs_op._short_opts = []
622         vs_op._long_opts = ['---']
623
624         pa(vs_op)
625
626         (opts,args) = parser.parse_args()
627         if not hasattr(opts,'vserver'):
628                 parser.error('you must specifiy --- <virt-server>...')
629         if n_non_actions >= len(parser.largs):
630                 parser.error('nothing to do specified')
631
632         for k in timeouts.keys():
633                 t = getattr(opts,'timeout_'+k)
634                 if t is None: t = timeouts[k] * opts.timeout_factor
635                 timeouts[k] = int(t)
636
637         arghandling = initial_arghandling
638         opts.actions = []
639         ix = 0
640         for act in args:
641                 if type(act) == dict:
642                         arghandling = act
643                         continue
644                 elif type(act) == tuple:
645                         pass
646                 elif type(act) == str:
647                         act = (act,act)
648                 else:
649                         raise Exception("unknown action in list `%s' having"
650                               " type `%s'" % (act, type(act)))
651                 (pathstr, kindpath) = act
652
653                 constructor = InputFile
654                 if type(kindpath) is tuple:             kind = kindpath[0]
655                 elif kindpath.endswith('.deb'):         kind = 'deb'
656                 elif kindpath.endswith('.dsc'):         kind = 'dsc'
657                 elif kindpath.endswith('//'):
658                         kind = 'ubtree'
659                         constructor = InputDir
660                 elif kindpath.endswith('/'):
661                         kind = 'tree'
662                         constructor = InputDir
663                 else: parser.error("do not know how to handle filename `%s';"
664                         " specify --source --binary or --build-tree" %
665                         kindpath)
666
667                 what = '%s%s' % (kind,ix); ix += 1
668
669                 if kind == 'dsc': fwhatx = '/' + os.path.basename(pathstr)
670                 else: fwhatx = '-'+kind
671
672                 if pathstr is None: af = None
673                 else: af = constructor(what+fwhatx, pathstr, arghandling['tb'])
674                 opts.actions.append(Action(kind, af, arghandling, what))
675
676 def setup_trace():
677         global trace_stream, tmpdir, summary_stream
678
679         if opts.tmpdir is not None:
680                 rmtree('tmpdir(specified)',opts.tmpdir)
681                 mkdir_okexist(opts.tmpdir, 0700)
682                 tmpdir = opts.tmpdir
683         else:
684                 assert(tmpdir is None)
685                 tmpdir = tempfile.mkdtemp()
686
687         if opts.logfile is None:
688                 if opts.output_dir is not None and opts.output_dir.spec_tbp:
689                         opts.logfile = opts.output_dir.spec + '/log'
690                 elif opts.tmpdir is not None:
691                         opts.logfile = opts.tmpdir + '/log'
692         if opts.logfile is not None:
693                 trace_stream = open(opts.logfile, 'w', 0)
694         if opts.summary is not None:
695                 summary_stream = open(opts.summary, 'w', 0)
696
697         debug('options: '+`opts`+'; timeouts: '+`timeouts`, 1)
698
699 def finalise_options():
700         global opts, tb, build_essential
701
702         if opts.user is None and 'root-on-testbed' not in testbed.caps:
703                 opts.user = ''
704
705         if opts.user is None:
706                 su = 'suggested-normal-user='
707                 ul = [
708                         e[len(su):]
709                         for e in testbed.caps
710                         if e.startswith(su)
711                         ]
712                 if ul:
713                         opts.user = ul[0]
714                 else:
715                         opts.user = ''
716
717         if opts.user:
718                 if 'root-on-testbed' not in testbed.caps:
719                         pstderr("warning: virtualisation"
720                                 " system does not offer root on testbed,"
721                                 " but --user option specified: failure likely")
722                 opts.user_wrap = lambda x: "su %s -c '%s'" % (opts.user, x)
723         else:
724                 opts.user_wrap = lambda x: x
725
726         if opts.gainroot is None:
727                 opts.gainroot = ''
728                 if (opts.user or
729                     'root-on-testbed' not in testbed.caps):
730                         opts.gainroot = 'fakeroot'
731                         build_essential += ['fakeroot']
732
733         if opts.gnupghome.startswith('~/'):
734                 try: home = os.environ['HOME']
735                 except KeyError:
736                         parser.error("HOME environment variable"
737                                 " not set, needed for --gnupghome=`%s"
738                                 % opts.gnupghome)
739                 opts.gnupghome = home + opts.gnupghome[1:]
740         elif opts.gnupghome == 'fresh':
741                 opts.gnupghome = None
742
743 #---------- testbed management - the Testbed class
744
745 class Testbed:
746  def __init__(tb):
747         tb.sp = None
748         tb.lastsend = None
749         tb.scratch = None
750         tb.modified = False
751         tb.blamed = []
752         tb._debug('init')
753         tb._need_reset_apt = False
754  def _debug(tb, m, minlevel=0):
755         debug('** '+m, minlevel)
756  def start(tb):
757         tb._debug('start')
758         p = subprocess.PIPE
759         debug_subprocess('vserver', opts.vserver)
760         tb._errplumb = Errplumb(True)
761         tb.sp = subprocess.Popen(opts.vserver,
762                 stdin=p, stdout=p, stderr=tb._errplumb.stream)
763         tb.expect('ok')
764         tb.caps = tb.commandr('capabilities')
765  def stop(tb):
766         tb._debug('stop')
767         tb.close()
768         if tb.sp is None: return
769         ec = tb.sp.returncode
770         if ec is None:
771                 tb.sp.stdout.close()
772                 tb.send('quit')
773                 tb.sp.stdin.close()
774                 ec = tb.sp.wait()
775         if ec:
776                 tb.bomb('testbed gave exit status %d after quit' % ec)
777         tb._errplumb.wait()
778  def open(tb):
779         tb._debug('open, scratch=%s' % tb.scratch)
780         if tb.scratch is not None: return
781         pl = tb.commandr('open')
782         tb._opened(pl)
783  def _opened(tb, pl):
784         tb.scratch = InputDir('tb-scratch', pl[0], True)
785         tb.deps_processed = []
786         for af in paths: af.invalidate_ephem(True)
787         tb._auxverbscript_make()
788  def _auxverbscript_make(tb):
789         pec = tb.commandr('print-auxverb-command')
790         if len(pec) < 1: tb.bomb('too few results from print-execute-command')
791         cmdl = map(urllib.unquote, pec[0].split(','))
792
793         tb._debug('cmdl = %s' % (`cmdl`))
794
795         tb.ec_auxverbscript = TemporaryFile('satdep-auxverb')
796         print >>open(tb.ec_auxverbscript.write(),'w'), (
797 '''#!/bin/sh
798 set -e
799 if [ $# = 2 ] && [ "x$1" = xdpkg-architecture ] && [ "x$2" = x-qDEB_HOST_ARCH ]; then
800         # This is a pretty nasty hack.  Hopefully it can go away
801         #  eventually.  See #635763.
802         set -- dpkg --print-architecture
803 fi
804 exec '''+shellquote_cmdl(cmdl)+' "$@"'+"\n"
805                 )
806         os.chmod(tb.ec_auxverbscript.write(), 0755)
807
808  def mungeing_apt(tb):
809         if not 'revert' in tb.caps:
810                 tb._need_reset_apt = True
811  def reset_apt(tb):
812         if not tb._need_reset_apt: return
813         what = 'aptget-update-reset'
814         cmdl = ['sh','-c','apt-get -qy update 2>&1']
815         rc = tb.execute(what, cmdl, kind='install')
816         if rc:
817                 pstderr("\n" "warning: failed to restore"
818                         " testbed apt cache, exit code %d" % rc)
819         tb._need_reset_apt = False
820  def close(tb):
821         tb._debug('close, scratch=%s' % tb.scratch)
822         if tb.scratch is None: return
823         tb.scratch = None
824         if tb.sp is None: return
825         tb.command('close')
826  def prepare1(tb, deps_new):
827         tb._debug('prepare1, modified=%s, deps_processed=%s, deps_new=%s' %
828                 (tb.modified, tb.deps_processed, deps_new), 1)
829         if 'revert' in tb.caps and (tb.modified or
830             [d for d in tb.deps_processed if d not in deps_new]):
831                 for af in paths: af.read(False)
832                 tb._debug('reset **')
833                 pl = tb.commandr('revert')
834                 tb._opened(pl)
835         tb.modified = False
836  def prepare2(tb, deps_new):
837         tb._debug('prepare2, deps_new=%s' % deps_new, 1)
838         binaries.publish()
839         tb._install_deps(deps_new)
840  def prepare(tb, deps_new):
841         tb.prepare1(deps_new)
842         tb.prepare2(deps_new)
843  def _install_deps(tb, deps_new):
844         tb._debug(' installing dependencies '+`deps_new`, 1)
845         tb.deps_processed = deps_new
846         if not deps_new: return
847         tb.satisfy_dependencies_string(', '.join(deps_new), 'install-deps')
848  def needs_reset(tb):
849         tb._debug('needs_reset, previously=%s' % tb.modified, 1)
850         tb.modified = True
851  def blame(tb, m):
852         tb._debug('blame += %s' % m, 1)
853         tb.blamed.append(m)
854  def bomb(tb, m):
855         tb._debug('bomb %s' % m)
856         if tb.sp is not None:
857                 tb.sp.stdout.close()
858                 tb.sp.stdin.close()
859                 ec = tb.sp.wait()
860                 if ec: pstderr('adt-run: testbed failing,'
861                                 ' exit status %d' % ec)
862         tb.sp = None
863         raise Quit(16, 'testbed failed: %s' % m)
864  def send(tb, string):
865         tb.sp.stdin
866         try:
867                 debug('>> '+string, 2)
868                 print >>tb.sp.stdin, string
869                 tb.sp.stdin.flush()
870                 tb.lastsend = string
871         except:
872                 (type, value, dummy) = sys.exc_info()
873                 tb.bomb('cannot send to testbed: %s' % traceback.
874                         format_exception_only(type, value))
875  def expect(tb, keyword, nresults=None):
876         l = tb.sp.stdout.readline()
877         if not l: tb.bomb('unexpected eof from the testbed')
878         if not l.endswith('\n'): tb.bomb('unterminated line from the testbed')
879         l = l.rstrip('\n')
880         debug('<< '+l, 2)
881         ll = l.split()
882         if not ll: tb.bomb('unexpected whitespace-only line from the testbed')
883         if ll[0] != keyword:
884                 if tb.lastsend is None:
885                         tb.bomb("got banner `%s', expected `%s...'" %
886                                 (l, keyword))
887                 else:
888                         tb.bomb("sent `%s', got `%s', expected `%s...'" %
889                                 (tb.lastsend, l, keyword))
890         ll = ll[1:]
891         if nresults is not None and len(ll) != nresults:
892                 tb.bomb("sent `%s', got `%s' (%d result parameters),"
893                         " expected %d result parameters" %
894                         (tb.lastsend, l, len(ll), nresults))
895         return ll
896  def commandr(tb, cmd, args=(), nresults=None, unquote=True):
897         # pass args=[None,...] or =(None,...) to avoid more url quoting
898         if type(cmd) is str: cmd = [cmd]
899         if len(args) and args[0] is None: args = args[1:]
900         else: args = map(urllib.quote, args)
901         al = cmd + args
902         tb.send(string.join(al))
903         ll = tb.expect('ok', nresults)
904         if unquote: ll = map(urllib.unquote, ll)
905         return ll
906  def command(tb, cmd, args=()):
907         tb.commandr(cmd, args, 0)
908  def commandr1(tb, cmd, args=()):
909         rl = tb.commandr(cmd, args, 1)
910         return rl[0]
911  def execute(tb, what, cmdl,
912                 si='/dev/null', so='/dev/null', se=None, cwd=None,
913                 script=False, tmpdir=None, kind='short'):
914         # Options for script:
915         #   False - do not call debug_subprocess, no synch. reporting required
916         #   None or string - call debug_subprocess with that value,
917         #                       plumb stderr through synchronously if possible
918         # Options for se:
919         #   None - usual Errplumb (output is of kind 2c)
920         #   string - path on testbed (output is of kind 2d)
921
922         timeout = timeouts[kind]
923
924         if script is not False: debug_subprocess(what, cmdl, script=script)
925         if cwd is None: cwd = tb.scratch.write(True)
926
927         xdump = None
928         if se is None:
929                 ep = Errplumb()
930                 se_catch = TemporaryFile(what+'-xerr')
931                 se_use = se_catch.write(True)
932                 if not opts.quiet: xdump = 'debug=2-2'
933                 elif trace_stream is not None:
934                         xdump = 'debug=2-%d' % trace_stream.fileno()
935         else:
936                 ep = None
937                 se_catch = None
938                 se_use = se
939
940         cmdl = [None,
941                 ','.join(map(urllib.quote, cmdl)),
942                 si, so, se_use, cwd]
943
944         if timeout is not None and timeout > 0:
945                 cmdl.append('timeout=%d' % timeout)
946
947         if xdump is not None and 'execute-debug' in tb.caps: cmdl += [xdump]
948         if tmpdir is not None: cmdl.append('env=TMPDIR=%s' % tmpdir)
949         if kind=='install': cmdl.append('env=DEBIAN_FRONTEND=noninteractive')
950         if opts.set_lang is not False:
951                 cmdl.append('env=LANG=%s' % opts.set_lang)
952
953         rc = tb.commandr1('execute', cmdl)
954         try: rc = int(rc)
955         except ValueError: bomb("execute for %s gave invalid response `%s'"
956                                         % (what,rc))
957
958         if se_catch is not None:
959                 debug_file(se_catch.read())
960         if ep is not None:
961                 ep.wait()
962
963         return rc
964
965  def satisfy_dependencies_string(tb, deps, what):
966         # Must have called Binaries.configure_apt
967         debug('dependencies: %s: satisfying %s' % (what,deps))
968         dsc = TemporaryFile('deps.dsc')
969         print >>open(dsc.write(),'w'), 'Build-Depends: ', deps, '\n\n'
970         # pbuilder-satisfydepends has a bug where it ignores the
971         #  Build-Depends if it's the last line in the dsc (#635696)
972         tb.satisfy_dependencies_dsc(dsc, what)
973
974  def satisfy_dependencies_dsc(tb, dsc, what):
975         # Must have called Binaries.configure_apt
976         cmdl = [ '/usr/lib/pbuilder/pbuilder-satisfydepends-classic',
977                 '--binary-all', # --check-key
978                 '--internal-chrootexec',tb.ec_auxverbscript.read(),
979                 '-c',dsc.read()
980         ]
981         # The --internal-chrootexec option is really handy but
982         #  perhaps we are not supposed to use it ?  See also #635697.
983         debug('dependencies: %s: running %s' % (what,`cmdl`))
984         rc = subprocess.call(cmdl, stdout=None, stderr=None)
985         if rc: badpkg('dependency install failed, exit code %d' % rc)
986
987 #---------- representation of test control files: Field*, Test, etc.
988
989 class FieldBase:
990  def __init__(f, fname, stz, base, tnames, vl):
991         assert(vl)
992         f.stz = stz
993         f.base = base
994         f.tnames = tnames
995         f.vl = vl
996  def words(f):
997         def distribute(vle):
998                 (lno, v) = vle
999                 r = v.split()
1000                 r = map((lambda w: (lno, w)), r)
1001                 return r
1002         return flatten(map(distribute, f.vl))
1003  def atmostone(f):
1004         if len(vl) == 1:
1005                 (f.lno, f.v) = vl[0]
1006         else:
1007                 raise Unsupported(f.vl[1][0],
1008                         'only one %s field allowed' % fn)
1009         return f.v
1010
1011 class FieldIgnore(FieldBase):
1012  def parse(f): pass
1013
1014 class Restriction:
1015  def __init__(r,rname,base): pass
1016
1017 class Restriction_rw_build_tree(Restriction): pass
1018 class Restriction_build_needed(Restriction): pass
1019 class Restriction_breaks_testbed(Restriction):
1020  def __init__(r, rname, base):
1021         if 'revert-full-system' not in testbed.caps:
1022                 raise Unsupported(f.lno,
1023         'Test breaks testbed but testbed does not advertise revert-full-system')
1024 class Restriction_needs_root(Restriction):
1025  def __init__(r, rname, base):
1026         if 'root-on-testbed' not in testbed.caps:
1027                 raise Unsupported(f.lno,
1028                         'Test needs root on testbed which is not available')
1029
1030 class Field_Restrictions(FieldBase):
1031  def parse(f):
1032         for wle in f.words():
1033                 (lno, rname) = wle
1034                 nrname = rname.replace('-','_')
1035                 try: rclass = globals()['Restriction_'+nrname]
1036                 except KeyError: raise Unsupported(lno,
1037                         'unknown restriction %s' % rname)
1038                 r = rclass(nrname, f.base)
1039                 f.base['restriction_names'].append(rname)
1040                 f.base['restrictions'].append(r)
1041
1042 class Field_Features(FieldIgnore):
1043  def parse(f):
1044         for wle in f.words():
1045                 (lno, fname) = wle
1046                 f.base['feature_names'].append(fname)
1047                 nfname = fname.replace('-','_')
1048                 try: fclass = globals()['Feature_'+nfname]
1049                 except KeyError: continue
1050                 ft = fclass(nfname, f.base)
1051                 f.base['features'].append(ft)
1052
1053 class Field_Tests(FieldIgnore): pass
1054
1055 class Field_Depends(FieldBase):
1056  def parse(f):
1057         debug("Field_DependS: %s %s %s %s" % (f.stz, f.base, f.tnames, f.vl), 2)
1058         dl = map(lambda x: x.strip(),
1059                 flatten(map(lambda (lno, v): v.split(','), f.vl)))
1060         re = regexp.compile('[^-.+:~0-9a-z()<>=*@]')
1061         for d in dl:
1062                 if re.search(d):
1063                         badpkg("Test Depends field contains dependency"
1064                                " `%s' with invalid characters" % d)
1065         f.base['depends'] = dl
1066
1067 class Field_Tests_directory(FieldBase):
1068  def parse(f):
1069         td = atmostone(f)
1070         if td.startswith('/'): raise Unspported(f.lno,
1071                 'Tests-Directory may not be absolute')
1072         f.base['testsdir'] = td
1073
1074 def run_tests(stanzas, tree):
1075         global errorcode, testbed
1076         if stanzas == ():
1077                 report('*', 'SKIP no tests in this package')
1078                 errorcode |= 8
1079         for stanza in stanzas:
1080                 tests = stanza[' tests']
1081                 if not tests:
1082                         report('*', 'SKIP package has metadata but no tests')
1083                         errorcode |= 8
1084                 for t in tests:
1085                         t.prepare()
1086                         t.run(tree)
1087                         if 'breaks-testbed' in t.restriction_names:
1088                                 testbed.needs_reset()
1089                 testbed.needs_reset()
1090
1091 class Test:
1092  def __init__(t, tname, base, act):
1093         if '/' in tname: raise Unsupported(base[' lno'],
1094                 'test name may not contain / character')
1095         for k in base: setattr(t,k,base[k])
1096         t.tname = tname
1097         t.act = act
1098         t.what = act.what+'t-'+tname
1099         if len(base['testsdir']): t.path = base['testsdir'] + '/' + tname
1100         else: t.path = tname
1101         t._debug('constructed; path=%s' % t.path)
1102         t._debug(' .depends=%s' % t.depends)
1103  def _debug(t, m):
1104         debug('& %s: %s' % (t.what, m))
1105  def report(t, m):
1106         report(t.what, m)
1107  def reportfail(t, m):
1108         global errorcode
1109         errorcode |= 4
1110         report(t.what, 'FAIL ' + m)
1111  def prepare(t):
1112         t._debug('preparing')
1113         dn = []
1114         for d in t.depends:
1115                 t._debug(' processing dependency '+d)
1116                 if not '@' in d:
1117                         t._debug('  literal dependency '+d)
1118                         dn.append(d)
1119                 else:
1120                         for (pkg,bin) in t.act.binaries:
1121                                 d = d.replace('@',pkg)
1122                                 t._debug('  synthesised dependency '+d)
1123                                 dn.append(d)
1124         testbed.prepare(dn)
1125  def run(t, tree):
1126         t._debug('[----------------------------------------')
1127         def stdouterr(oe):
1128                 idstr = t.what + '-' + oe
1129                 if opts.output_dir is not None and opts.output_dir.spec_tbp:
1130                         use_dir = opts.output_dir
1131                 else:
1132                         use_dir = testbed.scratch
1133                 return RelativeOutputFile(idstr, use_dir, idstr)
1134
1135         if hasattr(t.act,'work'): t.act.work.write(True)
1136         tree.read(True)
1137
1138         af = RelativeInputFile(t.what, tree, t.path)
1139         so = stdouterr('stdout')
1140         se = stdouterr('stderr')
1141
1142         tf = af.read(True)
1143         tmpdir = None
1144
1145         rc = testbed.execute('testchmod-'+t.what, ['chmod','+x','--',tf])
1146         if rc: bomb('failed to chmod +x %s' % tf)
1147
1148         if 'needs-root' not in t.restriction_names and opts.user is not None:
1149                 tfl = ['su',opts.user,'-c',tf]
1150                 tmpdir = '%s%s-tmpdir' % (testbed.scratch.read(True), t.what)
1151                 script = 'rm -rf -- "$1"; mkdir -- "$1"'
1152                 if opts.user:
1153                         script += '; chown %s "$1"' % opts.user
1154                         if 'rw-build-tree' in t.restriction_names:
1155                                 script += '; chown -R %s "$2"' % opts.user
1156                 rc = testbed.execute('mktmpdir-'+t.what,
1157                         ['sh','-xec',script,'x',tmpdir,tree.read(True)])
1158                 if rc: bomb("could not create test tmpdir `%s', exit code %d"
1159                                 % (tmpdir, rc))
1160         else:
1161                 tfl = [tf]
1162
1163         rc = testbed.execute('test-'+t.what, tfl,
1164                 so=so.write(True), se=se.write(True), cwd=tree.read(True),
1165                 tmpdir=tmpdir, kind='test')
1166
1167         so_read = so.read()
1168         se_read = se.read()
1169
1170         t._debug(' - - - - - - - - - - results - - - - - - - - - -')
1171         stab = os.stat(se_read)
1172         if stab.st_size != 0:
1173                 l = open(se_read).readline()
1174                 l = l.rstrip('\n \t\r')
1175                 if len(l) > 35: l = l[:35] + '...'
1176                 t.reportfail('status: %d, stderr: %s' % (rc, l))
1177                 t._debug(' - - - - - - - - - - stderr - - - - - - - - - -')
1178                 debug_file(se_read)
1179         elif rc != 0:
1180                 t.reportfail('non-zero exit status %d' % rc)
1181         else:
1182                 t.report('PASS')
1183
1184         stab = os.stat(so_read)
1185         if stab.st_size != 0:
1186                 t._debug(' - - - - - - - - - - stdout - - - - - - - - - -')
1187                 debug_file(so_read)
1188
1189         t._debug('----------------------------------------]')
1190
1191 def read_control(act, tree, control_override):
1192         stanzas = [ ]
1193
1194         if control_override is not None:
1195                 control_af = control_override
1196                 testbed.blame('arg:'+control_override.spec)
1197         else:
1198                 if act.missing_tests_control:
1199                         return ()
1200                 control_af = RelativeInputFile(act.what+'-testcontrol',
1201                         tree, 'debian/tests/control')
1202         try:
1203                 control = open(control_af.read(), 'r')
1204         except (IOError,OSError), oe:
1205                 if oe[0] != errno.ENOENT: raise
1206                 return []
1207
1208         lno = 0
1209         def badctrl(m): testbed.bomb('tests/control line %d: %s' % (lno, m))
1210         stz = { }       # stz[field_name][index] = (lno, value)
1211                         # special field names:
1212                         # stz[' lno'] = number
1213                         # stz[' tests'] = list of Test objects
1214                         # empty dictionary means we're between stanzas
1215         def in_stanza(stz):
1216                 return stz.has_key(' lno')
1217         def end_stanza(stz):
1218                 if not in_stanza(stz): return
1219                 stz[' errs'] = 0
1220                 stanzas.append(stz.copy())
1221                 stz.clear()
1222                 hcurrent = None
1223
1224         initre = regexp.compile('([A-Z][-0-9a-zA-Z]*)\s*\:\s*(.*)$')
1225         while 1:
1226                 l = control.readline()
1227                 if not l: break
1228                 lno += 1
1229                 if not l.endswith('\n'): badctrl('unterminated line')
1230                 if regexp.compile('\s*\#').match(l): continue
1231                 if not regexp.compile('\S').match(l): end_stanza(stz); continue
1232                 initmat = initre.match(l)
1233                 if initmat:
1234                         (fname, l) = initmat.groups()
1235                         fname = string.capwords(fname)
1236                         if not in_stanza(stz):
1237                                 stz = { ' lno': lno, ' tests': [] }
1238                         if not stz.has_key(fname): stz[fname] = [ ]
1239                         hcurrent = stz[fname]
1240                 elif regexp.compile('\s').match(l):
1241                         if not hcurrent: badctrl('unexpected continuation')
1242                 else:
1243                         badctrl('syntax error')
1244                 hcurrent.append((lno, l))
1245         end_stanza(stz)
1246
1247         def testbadctrl(stz, lno, m):
1248                 report_badctrl(lno, m)
1249                 stz[' errs'] += 1
1250
1251         for stz in stanzas:
1252                 try:
1253                         try: tnames = stz['Tests']
1254                         except KeyError:
1255                                 tnames = ['*']
1256                                 raise Unsupported(stz[' lno'],
1257                                         'no Tests field')
1258                         tnames = map((lambda lt: lt[1]), tnames)
1259                         tnames = string.join(tnames).split()
1260                         base = {
1261                                 'restriction_names': [],
1262                                 'restrictions': [],
1263                                 'feature_names': [],
1264                                 'features': [],
1265                                 'testsdir': 'debian/tests',
1266                                 'depends' : '@'
1267                         }
1268                         for fname in stz.keys():
1269                                 if fname.startswith(' '): continue
1270                                 vl = stz[fname]
1271                                 try: fclass = globals()['Field_'+
1272                                         fname.replace('-','_')]
1273                                 except KeyError: raise Unsupported(vl[0][0],
1274                                         'unknown metadata field %s' % fname)
1275                                 f = fclass(stz, fname, base, tnames, vl)
1276                                 f.parse()
1277                         for tname in tnames:
1278                                 t = Test(tname, base, act)
1279                                 stz[' tests'].append(t)
1280                 except Unsupported, u:
1281                         for tname in tnames: u.report(tname)
1282                         continue
1283
1284         return stanzas
1285
1286 def print_exception(ei, msgprefix=''):
1287         if msgprefix: pstderr(msgprefix)
1288         (et, q, tb) = ei
1289         if et is Quit:
1290                 pstderr('adt-run: ' + q.m)
1291                 psummary('quitting: '+q.m)
1292                 return q.ec
1293         else:
1294                 pstderr("adt-run: unexpected, exceptional, error:")
1295                 psummary('quitting: unexpected error, consult transcript')
1296                 traceback.print_exc(None, sys.stderr)
1297                 if trace_stream is not None:
1298                         traceback.print_exc(None, trace_stream)
1299                 return 20
1300
1301 def cleanup():
1302         global trace_stream
1303         try:
1304                 if testbed is not None:
1305                         testbed.reset_apt()
1306                         testbed.stop()
1307                 if opts.tmpdir is None and tmpdir is not None:
1308                         rmtree('tmpdir', tmpdir)
1309                 if trace_stream is not None:
1310                         trace_stream.close()
1311                         trace_stream = None
1312         except:
1313                 print_exception(sys.exc_info(),
1314                         '\nadt-run: error cleaning up:\n')
1315                 os._exit(20)
1316
1317 #---------- registration, installation etc. of .deb's: Binaries
1318
1319 def determine_package(act):
1320         cmd = 'dpkg-deb --info --'.split(' ')+[act.af.read(),'control']
1321         (rc, output) = subprocess_cooked(cmd, stdout=subprocess.PIPE)
1322         if rc: badpkg('failed to parse binary package, code %d' % rc)
1323         re = regexp.compile('^\s*Package\s*:\s*([0-9a-z][-+.0-9a-z]*)\s*$')
1324         act.pkg = None
1325         for l in output.split('\n'):
1326                 m = re.match(l)
1327                 if not m: continue
1328                 if act.pkg: badpkg('two Package: lines in control file')
1329                 act.pkg = m.groups()[0]
1330         if not act.pkg: badpkg('no good Package: line in control file')
1331
1332 class Binaries:
1333  def __init__(b, tb):
1334         b.dir = TemporaryDir('binaries')
1335         b.dir.write()
1336         ok = False
1337
1338         if opts.gnupghome is None:
1339                 opts.gnupghome = tmpdir+'/gnupg'
1340
1341         b._debug('initialising')
1342         try:
1343                 for x in ['pubring','secring']:
1344                         os.stat(opts.gnupghome + '/' + x + '.gpg')
1345                 ok = True
1346         except (IOError,OSError), oe:
1347                 if oe.errno != errno.ENOENT: raise
1348
1349         if ok: b._debug('no key generation needed')
1350         else: b.genkey()
1351
1352  def _debug(b, s):
1353         debug('* '+s)
1354
1355  def genkey(b):
1356         b._debug('preparing for key generation')
1357
1358         mkdir_okexist(os.path.dirname(opts.gnupghome), 02755)
1359         mkdir_okexist(opts.gnupghome, 0700)
1360
1361         script = '''
1362   exec >&2
1363   cd "$1"
1364   cat <<"END" >key-gen-params
1365 Key-Type: DSA
1366 Key-Length: 1024
1367 Key-Usage: sign
1368 Name-Real: autopkgtest per-run key
1369 Name-Comment: do not trust this key
1370 Name-Email: autopkgtest@example.com
1371 END
1372   set -x
1373   gpg --homedir="$1" --batch --gen-key key-gen-params
1374                 '''
1375         cmdl = ['sh','-ec',script,'x',opts.gnupghome]
1376         rc = subprocess_cooked(cmdl, dbg=('genkey',script))[0]
1377         if rc: bomb('key generation failed, code %d' % rc)
1378
1379  def apt_configs(b):
1380         return {
1381                 "Debug::pkgProblemResolver": "true",
1382                 "APT::Get::force-yes" : "true",
1383                 "APT::Get::Assume-Yes" : "true",
1384                 "quiet" : "true",
1385         }
1386
1387  def _configure_apt(b, tb):
1388         config = OutputFile('apt-config','/etc/apt/apt.conf.d/90autopkgtest',
1389                         True)
1390         f = open(config.write(),'w')
1391         for (k,v) in b.apt_configs().iteritems():
1392                 print >>f, '%s { "%s"; };' % (k, v)
1393         f.close()
1394         config.read(True)
1395
1396         prefs = OutputFile('apt-prefs','/etc/apt/preferences.d/90autopkgtest',
1397                         True)
1398         print >>open(prefs.write(),'w'), '''
1399 Package: *
1400 Pin: origin ""
1401 Pin-Priority: 1002
1402 '''
1403         prefs.read(True)
1404         
1405  def _apt_get(b):
1406         ag = ['apt-get','-q']
1407         for kv in b.apt_configs().iteritems():
1408                 ag += ['-o', '%s=%s' % kv]
1409         return ' '.join(ag)
1410
1411  def reset(b):
1412         b._debug('reset')
1413         rmtree('binaries', b.dir.read())
1414         b.dir.invalidate()
1415         b.dir.write()
1416         b.install = []
1417         b.blamed = []
1418         b.registered = set()
1419
1420  def register(b, act, pkg, af, forwhat, blamed):
1421         b._debug('register what=%s deb_%s=%s pkg=%s af=%s'
1422                 % (act.what, forwhat, act.ah['deb_'+forwhat], pkg, str(af)))
1423
1424         if act.ah['deb_'+forwhat] == 'ignore': return
1425
1426         b.blamed += testbed.blamed
1427
1428         leafname = pkg+'.deb'
1429         dest = RelativeOutputFile('binaries--'+leafname, b.dir, leafname)
1430
1431         try: os.remove(dest.write())
1432         except (IOError,OSError), oe:
1433                 if oe.errno != errno.ENOENT: raise e
1434
1435         try: os.link(af.read(), dest.write())
1436         except (IOError,OSError), oe:
1437                 if oe.errno != errno.EXDEV: raise e
1438                 shutil.copy(af.read(), dest)
1439
1440         if act.ah['deb_'+forwhat] == 'install':
1441                 b.install.append(pkg)
1442
1443         b.registered.add(pkg)
1444
1445  def publish(b):
1446         b._debug('publish')
1447
1448         b._configure_apt(testbed)
1449
1450         script = '''
1451   exec >&2
1452   cd "$1"
1453   apt-ftparchive packages . >Packages
1454   gzip <Packages >Packages.gz
1455   apt-ftparchive release . >Release
1456   rm -f Release.gpg
1457   gpg --homedir="$2" --batch --detach-sign --armour -o Release.gpg Release
1458   gpg --homedir="$2" --batch --export >archive-key.pgp
1459                 '''
1460         cmdl = ['sh','-ec',script,'x',b.dir.write(),opts.gnupghome]
1461         rc = subprocess_cooked(cmdl, dbg=('ftparchive',script))[0]
1462         if rc: bomb('apt-ftparchive or signature failed, code %d' % rc)
1463
1464         b.dir.invalidate(True)
1465         apt_source = b.dir.read(True)
1466
1467         so = TemporaryFile('vlds')
1468         script = '''
1469   exec 3>&1 >&2
1470   apt-key add archive-key.pgp
1471   echo "deb file://'''+apt_source+''' /" >/etc/apt/sources.list.d/autopkgtest.list
1472   if [ "x`ls /var/lib/dpkg/updates`" != x ]; then
1473     echo >&2 "/var/lib/dpkg/updates contains some files, aargh"; exit 1
1474   fi
1475   '''+ b._apt_get() +''' update >&2
1476   cat /var/lib/dpkg/status >&3
1477                 '''
1478         testbed.mungeing_apt()
1479         rc = testbed.execute('apt-key', ['sh','-ec',script],
1480                                 so=so.write(True), cwd=b.dir.write(True),
1481                                 script=script, kind='install')
1482         if rc: bomb('apt setup failed with exit code %d' % rc)
1483
1484         testbed.blamed += b.blamed
1485
1486         b._debug('publish reinstall checking...')
1487         pkgs_reinstall = set()
1488         pkg = None
1489         for l in open(so.read()):
1490                 if l.startswith('Package: '):
1491                         pkg = l[9:].rstrip()
1492                 elif l.startswith('Status: install '):
1493                         if pkg in b.registered:
1494                                 pkgs_reinstall.add(pkg)
1495                                 b._debug(' publish reinstall needs '+pkg)
1496
1497         if pkgs_reinstall:
1498                 for pkg in pkgs_reinstall: testbed.blame(pkg)
1499                 what = 'apt-get-reinstall'
1500                 cmdl = (b._apt_get() + ' --reinstall install '+
1501                         ' '.join([pkg for pkg in pkgs_reinstall])+' >&2')
1502                 cmdl = ['sh','-c',cmdl]
1503                 rc = testbed.execute(what, cmdl, script=None, kind='install')
1504                 if rc: badpkg("installation of basic binarries failed,"
1505                                 " exit code %d" % rc)
1506
1507         b._debug('publish install...')
1508         for pkg in b.install:
1509                 what = 'apt-get-install-%s' % pkg
1510                 testbed.blame(pkg)
1511                 cmdl = b._apt_get() + ' install ' + pkg + ' >&2'
1512                 cmdl = ['sh','-c',cmdl]
1513                 rc = testbed.execute(what, cmdl, script=None, kind='install')
1514                 if rc: badpkg("installation of %s failed, exit code %d"
1515                                 % (pkg, rc))
1516
1517         b._debug('publish done')
1518
1519 #---------- processing of sources (building)
1520
1521 def source_rules_command(act,script,what,which,work,cwd,
1522                                 results_lines=0,xargs=[]):
1523         script = [      "exec 3>&1 >&2",
1524                         "set -x"        ] + script
1525         script = '\n'.join(script)
1526         so = TemporaryFile('%s-%s-results' % (what,which))
1527         rc = testbed.execute('%s-%s' % (what,which),
1528                         ['sh','-ec',script]+xargs, script=script,
1529                         so=so.write(True), cwd=cwd, kind='build')
1530         results = open(so.read()).read().rstrip('\n')
1531         if len(results): results = results.split("\n")
1532         else: results = []
1533         if rc: badpkg("rules %s failed with exit code %d" % (which,rc))
1534         if results_lines is not None and len(results) != results_lines:
1535                 badpkg("got %d lines of results from %s where %d expected"
1536                         % (len(results), which, results_lines))
1537         if results_lines==1: return results[0]
1538         return results
1539
1540 def build_source(act, control_override):
1541         act.blame = 'arg:'+act.af.spec
1542         testbed.blame(act.blame)
1543         testbed.prepare1([])
1544         testbed.needs_reset()
1545
1546         what = act.what
1547         basename = act.af.spec
1548         debiancontrol = None
1549         act.binaries = []
1550
1551         def debug_b(m): debug('* <%s:%s> %s' % (act.kind, act.what, m))
1552
1553         if act.kind == 'dsc':
1554                 dsc = act.af
1555                 dsc_file = open(dsc.read())
1556                 in_files = False
1557                 fre = regexp.compile('^\s+[0-9a-f]+\s+\d+\s+([^/.][^/]*)$')
1558                 for l in dsc_file:
1559                         l = l.rstrip('\n')
1560                         if l.startswith('Files:'): in_files = True; continue
1561                         elif l.startswith('#'): pass
1562                         elif not l.startswith(' '):
1563                                 in_files = False
1564                                 if l.startswith('Source:'):
1565                                         act.blame = 'dsc:'+l[7:].strip()
1566                                         testbed.blame(act.blame)
1567                         if not in_files: continue
1568
1569                         m = fre.match(l)
1570                         if not m: badpkg(".dsc contains unparseable line"
1571                                         " in Files: `%s'" % l)
1572                         leaf = m.groups(0)[0]
1573                         subfile = RelativeInputFile(what+'/'+leaf, dsc, leaf,
1574                                         sibling=True)
1575                         subfile.read(True)
1576                 dsc.read(True)
1577
1578         if act.kind == 'ubtree':
1579                 debiancontrol = RelativeInputFile(what+'-debiancontrol',
1580                         act.af, 'debian/control')
1581                 dsc = TemporaryFile(what+'-fakedsc')
1582                 dsc_w = open(dsc.write(), 'w')
1583                 for l in open(debiancontrol.read()):
1584                         l = l.rstrip('\n')
1585                         if not len(l): break
1586                         print >>dsc_w, l
1587                 print >>dsc_w, 'Binary: none-so-this-is-not-a-package-name'
1588                 dsc_w.close()
1589
1590         if act.kind == 'dsc':
1591                 testbed.prepare2([])
1592                 testbed.satisfy_dependencies_string('dpkg-dev',
1593                                                 'install dpkg-dev')
1594
1595         work = TemporaryDir(what+'-build')
1596         act.work = work
1597
1598         tmpdir = work.write(True)+'/tmpdir'
1599         tmpdir_script = [
1600                         'TMPDIR="$1"',
1601                         'rm -rf -- "$TMPDIR"',
1602                         'export TMPDIR',
1603                         opts.user_wrap('mkdir -- "$TMPDIR"'),
1604                 ]
1605
1606         if act.kind == 'ubtree':
1607                 spec = '%s/real-tree' % work.write(True)
1608                 create_command = '''
1609                         rm -rf "$spec"
1610                         mkdir "$spec"
1611                         cp -rP --preserve=timestamps,links -- "$origpwd"/. "$spec"/.
1612                         '''
1613                 initcwd = act.af.read(True)
1614
1615         if act.kind == 'dsc':
1616                 spec = dsc.read(True)
1617                 create_command = '''
1618                         dpkg-source -x $spec
1619                         '''
1620                 initcwd = work.write(True)
1621
1622         script = [
1623                 'spec="$2"',
1624                 'origpwd=`pwd`',
1625                 'cd '+work.write(True)
1626         ]
1627
1628         if opts.user:
1629                 script += ([ 'chown '+opts.user+' .' ] +
1630                         tmpdir_script +
1631                         [ 'spec="$spec" origpwd="$origpwd" '
1632                                 +opts.user_wrap(create_command) ])
1633         else:
1634                 script += (tmpdir_script +
1635                         [ create_command ])
1636
1637         script += [
1638                         'cd [a-z0-9]*-*/.',
1639                         'pwd >&3',
1640                         'set +e; test -f debian/tests/control; echo $? >&3'
1641                 ]
1642         (result_pwd, control_test_rc) = source_rules_command(
1643                         act,script,what,'extract',work,
1644                         cwd=initcwd, results_lines=2, xargs=['x',tmpdir,spec])
1645
1646         filter = act.ah['dsc_filter']
1647
1648         if control_test_rc == '1': act.missing_tests_control = True
1649
1650         # For optional builds:
1651         #
1652         # We might need to build the package because:
1653         #   - we want its binaries (filter isn't _ and at least one of the
1654         #       deb_... isn't ignore)
1655         #   - the test control file says so
1656         #       (assuming we have any tests)
1657
1658         class NeedBuildException: pass
1659         def build_needed(m):
1660                 debug_b('build needed for %s' % m)
1661                 raise NeedBuildException()
1662
1663         try:
1664                 if filter != '_' and (act.ah['deb_forbuilds'] != 'ignore' or
1665                                       act.ah['deb_fortests'] != 'ignore'):
1666                         build_needed('binaries')
1667
1668                 result_pwd_af = InputDir(what+'-treeforcontrol',
1669                         result_pwd, True)
1670                 stanzas = read_control(act, result_pwd_af, control_override)
1671                 for stanza in stanzas:
1672                         for t in stanza[' tests']:
1673                                 if 'build-needed' in t.restriction_names:
1674                                         build_needed('test %s' % t.tname)
1675
1676                 debug_b('build not needed')
1677                 built = False
1678
1679         except NeedBuildException:
1680
1681                 if act.kind != 'dsc':
1682                         testbed.prepare2([])
1683
1684                 testbed.satisfy_dependencies_string('build-essential',
1685                                 'install build-essential')
1686                 testbed.satisfy_dependencies_dsc(dsc, 'build dependencies')
1687
1688                 script = tmpdir_script + [
1689                         'cd "$2"',
1690                         'dpkg-checkbuilddeps',
1691                         opts.user_wrap('debian/rules build'),
1692                         ]
1693                 source_rules_command(act,script,what,'build',work,
1694                                 cwd=initcwd, xargs=['x',tmpdir,result_pwd])
1695
1696                 if os.path.dirname(result_pwd)+'/' != work.read(True):
1697                         badpkg("results dir `%s' is not in expected parent"
1698                                " dir `%s'" % (result_pwd, work.read(True)))
1699
1700                 built = True
1701
1702         act.tests_tree = RelativeInputDir(what+'-tests-tree',
1703                                 work, os.path.basename(result_pwd),
1704                                 True)
1705
1706         if not built:
1707                 act.blamed = []
1708                 return
1709
1710         act.blamed = copy.copy(testbed.blamed)
1711
1712         debug_b('filter=%s' % filter)
1713         if filter != '_':
1714                 script = tmpdir_script + [
1715                         'cd '+work.write(True)+'/[a-z0-9]*-*/.',
1716                         opts.user_wrap(opts.gainroot+' debian/rules binary'),
1717                         'cd ..',
1718                         'echo *.deb >&3',
1719                         ]
1720                 result_debs = source_rules_command(act,script,what,
1721                                 'binary',work,work.write(True),
1722                                 results_lines=1, xargs=['x',tmpdir])
1723                 if result_debs == '*.deb': debs = []
1724                 else: debs = result_debs.split(' ')
1725                 debug_b('debs='+`debs`)
1726                 re = regexp.compile('^([-+.0-9a-z]+)_[^_/]+(?:_[^_/]+)\.deb$')
1727                 for deb in debs:
1728                         m = re.match(deb)
1729                         if not m: badpkg("badly-named binary `%s'" % deb)
1730                         pkg = m.groups()[0]
1731                         debug_b(' deb=%s, pkg=%s' % (deb,pkg))
1732                         for pat in filter.split(','):
1733                                 debug_b('  pat=%s' % pat)
1734                                 if not fnmatch.fnmatchcase(pkg,pat):
1735                                         debug_b('   no match')
1736                                         continue
1737                                 deb_what = pkg+'_'+what+'.deb'
1738                                 bin = RelativeInputFile(deb_what,work,deb,True)
1739                                 debug_b('  deb_what=%s, bin=%s' %
1740                                         (deb_what, str(bin)))
1741                                 binaries.register(act,pkg,bin,
1742                                         'forbuilds',testbed.blamed)
1743                                 act.binaries.append((pkg,bin))
1744                                 break
1745                 debug_b('all done.')
1746
1747 #---------- main processing loop and main program
1748
1749 def process_actions():
1750         global binaries
1751
1752         def debug_a1(m): debug('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ '+m)
1753         def debug_a2(m): debug('@@@@@@@@@@@@@@@@@@@@ '+m)
1754         def debug_a3(m): debug('@@@@@@@@@@ '+m)
1755
1756         debug_a1('starting')
1757         testbed.open()
1758         binaries = Binaries(testbed)
1759
1760         binaries.reset()
1761         control_override = None
1762
1763         debug_a1('builds ...')
1764         for act in opts.actions:
1765                 debug_a2('%s %s' %
1766                         (act.kind, act.what))
1767
1768                 if act.kind == 'control':
1769                         control_override = act.af
1770                 if act.kind == 'deb':
1771                         testbed.blame('arg:'+act.af.spec)
1772                         determine_package(act)
1773                         testbed.blame('deb:'+act.pkg)
1774                         binaries.register(act,act.pkg,act.af,
1775                                 'forbuilds',testbed.blamed)
1776                 if act.kind == 'dsc' or act.kind == 'ubtree':
1777                         build_source(act, control_override)
1778                 if act.kind == 'tree':
1779                         act.binaries = []
1780                 if act.kind.endswith('tree') or act.kind == 'dsc':
1781                         control_override = None
1782                 if act.kind == 'instantiate':
1783                         pass
1784
1785         debug_a1('builds done.')
1786
1787         binaries.reset()
1788         control_override = None
1789
1790         debug_a1('tests ...')
1791         for act in opts.actions:
1792                 debug_a2('test %s %s' % (act.kind, act.what))
1793
1794                 testbed.needs_reset()
1795                 if act.kind == 'control':
1796                         control_override = act.af
1797                 if act.kind == 'deb':
1798                         binaries.register(act,act.pkg,act.af,'fortests',
1799                                 ['deb:'+act.pkg])
1800                 if act.kind == 'dsc' or act.kind == 'ubtree':
1801                         for (pkg,bin) in act.binaries:
1802                                 binaries.register(act,pkg,bin,'fortests',
1803                                         act.blamed)
1804                 if act.kind == 'dsc':
1805                         if act.ah['dsc_tests']:
1806                                 debug_a3('read control ...')
1807                                 stanzas = read_control(act, act.tests_tree,
1808                                                 control_override)
1809                                 testbed.blamed += act.blamed
1810                                 debug_a3('run_tests ...')
1811                                 run_tests(stanzas, act.tests_tree)
1812                         control_override = None
1813                 if act.kind == 'tree' or act.kind == 'ubtree':
1814                         testbed.blame('arg:'+act.af.spec)
1815                         stanzas = read_control(act, act.af, control_override)
1816                         debug_a3('run_tests ...')
1817                         run_tests(stanzas, act.af)
1818                         control_override = None
1819                 if act.kind == 'instantiate':
1820                         testbed.prepare([])
1821         debug_a1('tests done.')
1822
1823 def main():
1824         global testbed
1825         global tmpdir
1826         try:
1827                 parse_args()
1828         except SystemExit, se:
1829                 os._exit(20)
1830         try:
1831                 setup_trace()
1832                 testbed = Testbed()
1833                 testbed.start()
1834                 finalise_options()
1835                 process_actions()
1836         except:
1837                 ec = print_exception(sys.exc_info(), '')
1838                 cleanup()
1839                 os._exit(ec)
1840         cleanup()
1841         os._exit(errorcode)
1842
1843 main()