chiark / gitweb /
adt-run: Fix for fix for #678359: when reading package list, do so using the host...
[autopkgtest.git] / runner / adt-run
1 #!/usr/bin/python
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 tmp = 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] = tmp+'/'+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='tmp',
580                 help='write temporary files to TMP, 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, tmp, summary_stream
678
679         if opts.tmp is not None:
680                 rmtree('tmp(specified)',opts.tmp)
681                 mkdir_okexist(opts.tmp, 0700)
682                 tmp = opts.tmp
683         else:
684                 assert(tmp is None)
685                 tmp = 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.tmp is not None:
691                         opts.logfile = opts.tmp + '/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         what = 'aptconf-reset'
820         cmdl = ['rm','-f','/etc/apt/apt.conf.d/90autopkgtest',
821                 '/etc/apt/sources.list.d/autopkgtest.list',
822                 '/etc/apt/preferences.d/90autopkgtest']
823         rc = tb.execute(what, cmdl, kind='install')
824         if rc:
825                 pstderr("\n" "warning: failed to reset changes"
826                         " made to testbed apt configuration, exit code %d" % rc)
827         tb._need_reset_apt = False
828  def close(tb):
829         tb._debug('close, scratch=%s' % tb.scratch)
830         if tb.scratch is None: return
831         tb.scratch = None
832         if tb.sp is None: return
833         tb.command('close')
834  def prepare1(tb, deps_new):
835         tb._debug('prepare1, modified=%s, deps_processed=%s, deps_new=%s' %
836                 (tb.modified, tb.deps_processed, deps_new), 1)
837         if 'revert' in tb.caps and (tb.modified or
838             [d for d in tb.deps_processed if d not in deps_new]):
839                 for af in paths: af.read(False)
840                 tb._debug('reset **')
841                 pl = tb.commandr('revert')
842                 tb._opened(pl)
843         tb.modified = False
844  def prepare2(tb, deps_new):
845         tb._debug('prepare2, deps_new=%s' % deps_new, 1)
846         binaries.publish()
847         tb._install_deps(deps_new)
848  def prepare(tb, deps_new):
849         tb.prepare1(deps_new)
850         tb.prepare2(deps_new)
851  def _install_deps(tb, deps_new):
852         tb._debug(' installing dependencies '+`deps_new`, 1)
853         tb.deps_processed = deps_new
854         if not deps_new: return
855         tb.satisfy_dependencies_string(', '.join(deps_new), 'install-deps')
856  def needs_reset(tb):
857         tb._debug('needs_reset, previously=%s' % tb.modified, 1)
858         tb.modified = True
859  def blame(tb, m):
860         tb._debug('blame += %s' % m, 1)
861         tb.blamed.append(m)
862  def bomb(tb, m):
863         tb._debug('bomb %s' % m)
864         if tb.sp is not None:
865                 tb.sp.stdout.close()
866                 tb.sp.stdin.close()
867                 ec = tb.sp.wait()
868                 if ec: pstderr('adt-run: testbed failing,'
869                                 ' exit status %d' % ec)
870         tb.sp = None
871         raise Quit(16, 'testbed failed: %s' % m)
872  def send(tb, string):
873         tb.sp.stdin
874         try:
875                 debug('>> '+string, 2)
876                 print >>tb.sp.stdin, string
877                 tb.sp.stdin.flush()
878                 tb.lastsend = string
879         except:
880                 (type, value, dummy) = sys.exc_info()
881                 tb.bomb('cannot send to testbed: %s' % traceback.
882                         format_exception_only(type, value))
883  def expect(tb, keyword, nresults=None):
884         l = tb.sp.stdout.readline()
885         if not l: tb.bomb('unexpected eof from the testbed')
886         if not l.endswith('\n'): tb.bomb('unterminated line from the testbed')
887         l = l.rstrip('\n')
888         debug('<< '+l, 2)
889         ll = l.split()
890         if not ll: tb.bomb('unexpected whitespace-only line from the testbed')
891         if ll[0] != keyword:
892                 if tb.lastsend is None:
893                         tb.bomb("got banner `%s', expected `%s...'" %
894                                 (l, keyword))
895                 else:
896                         tb.bomb("sent `%s', got `%s', expected `%s...'" %
897                                 (tb.lastsend, l, keyword))
898         ll = ll[1:]
899         if nresults is not None and len(ll) != nresults:
900                 tb.bomb("sent `%s', got `%s' (%d result parameters),"
901                         " expected %d result parameters" %
902                         (tb.lastsend, l, len(ll), nresults))
903         return ll
904  def commandr(tb, cmd, args=(), nresults=None, unquote=True):
905         # pass args=[None,...] or =(None,...) to avoid more url quoting
906         if type(cmd) is str: cmd = [cmd]
907         if len(args) and args[0] is None: args = args[1:]
908         else: args = map(urllib.quote, args)
909         al = cmd + args
910         tb.send(string.join(al))
911         ll = tb.expect('ok', nresults)
912         if unquote: ll = map(urllib.unquote, ll)
913         return ll
914  def command(tb, cmd, args=()):
915         tb.commandr(cmd, args, 0)
916  def commandr1(tb, cmd, args=()):
917         rl = tb.commandr(cmd, args, 1)
918         return rl[0]
919  def execute(tb, what, cmdl,
920                 si='/dev/null', so='/dev/null', se=None, cwd=None,
921                 script=False, xenv=[], kind='short'):
922         # Options for script:
923         #   False - do not call debug_subprocess, no synch. reporting required
924         #   None or string - call debug_subprocess with that value,
925         #                       plumb stderr through synchronously if possible
926         # Options for se:
927         #   None - usual Errplumb (output is of kind 2c)
928         #   string - path on testbed (output is of kind 2d)
929
930         timeout = timeouts[kind]
931
932         if script is not False: debug_subprocess(what, cmdl, script=script)
933         if cwd is None: cwd = tb.scratch.write(True)
934
935         xdump = None
936         if se is None:
937                 ep = Errplumb()
938                 se_catch = TemporaryFile(what+'-xerr')
939                 se_use = se_catch.write(True)
940                 if not opts.quiet: xdump = 'debug=2-2'
941                 elif trace_stream is not None:
942                         xdump = 'debug=2-%d' % trace_stream.fileno()
943         else:
944                 ep = None
945                 se_catch = None
946                 se_use = se
947
948         cmdl = [None,
949                 ','.join(map(urllib.quote, cmdl)),
950                 si, so, se_use, cwd]
951
952         if timeout is not None and timeout > 0:
953                 cmdl.append('timeout=%d' % timeout)
954
955         if xdump is not None and 'execute-debug' in tb.caps: cmdl += [xdump]
956         for e in xenv: cmdl.append('env=%s' % e)
957         if kind=='install': cmdl.append('env=DEBIAN_FRONTEND=noninteractive')
958         if opts.set_lang is not False:
959                 cmdl.append('env=LANG=%s' % opts.set_lang)
960
961         rc = tb.commandr1('execute', cmdl)
962         try: rc = int(rc)
963         except ValueError: bomb("execute for %s gave invalid response `%s'"
964                                         % (what,rc))
965
966         if se_catch is not None:
967                 debug_file(se_catch.read())
968         if ep is not None:
969                 ep.wait()
970
971         return rc
972
973  def satisfy_dependencies_string(tb, deps, what):
974         # Must have called Binaries.configure_apt
975         debug('dependencies: %s: satisfying %s' % (what,deps))
976         dsc = TemporaryFile('deps.dsc')
977         print >>open(dsc.write(),'w'), 'Build-Depends: ', deps, '\n\n'
978         # pbuilder-satisfydepends has a bug where it ignores the
979         #  Build-Depends if it's the last line in the dsc (#635696)
980         tb.satisfy_dependencies_dsc(dsc, what)
981
982  def satisfy_dependencies_dsc(tb, dsc, what):
983         # Must have called Binaries.configure_apt
984         cmdl = [ '/usr/lib/pbuilder/pbuilder-satisfydepends-classic',
985                 '--binary-all', # --check-key
986                 '--internal-chrootexec',tb.ec_auxverbscript.read(),
987                 '-c',dsc.read()
988         ]
989         # The --internal-chrootexec option is really handy but
990         #  perhaps we are not supposed to use it ?  See also #635697.
991         debug('dependencies: %s: running %s' % (what,`cmdl`))
992         rc = subprocess.call(cmdl, stdout=None, stderr=None)
993         if rc: badpkg('dependency install failed, exit code %d' % rc)
994
995 #---------- representation of test control files: Field*, Test, etc.
996
997 class FieldBase:
998  def __init__(f, fname, stz, base, tnames, vl):
999         assert(vl)
1000         f.stz = stz
1001         f.base = base
1002         f.tnames = tnames
1003         f.vl = vl
1004  def words(f):
1005         def distribute(vle):
1006                 (lno, v) = vle
1007                 r = v.split()
1008                 r = map((lambda w: (lno, w)), r)
1009                 return r
1010         return flatten(map(distribute, f.vl))
1011  def atmostone(f):
1012         if len(vl) == 1:
1013                 (f.lno, f.v) = vl[0]
1014         else:
1015                 raise Unsupported(f.vl[1][0],
1016                         'only one %s field allowed' % fn)
1017         return f.v
1018
1019 class FieldIgnore(FieldBase):
1020  def parse(f): pass
1021
1022 class Restriction:
1023  def __init__(r,rname,base): pass
1024
1025 class Restriction_rw_build_tree(Restriction): pass
1026 class Restriction_build_needed(Restriction): pass
1027 class Restriction_breaks_testbed(Restriction):
1028  def __init__(r, rname, base):
1029         if 'revert-full-system' not in testbed.caps:
1030                 raise Unsupported(-1,
1031         'Test breaks testbed but testbed does not advertise revert-full-system')
1032 class Restriction_needs_root(Restriction):
1033  def __init__(r, rname, base):
1034         if 'root-on-testbed' not in testbed.caps:
1035                 raise Unsupported(-1,
1036                         'Test needs root on testbed which is not available')
1037
1038 class Field_Restrictions(FieldBase):
1039  def parse(f):
1040         for wle in f.words():
1041                 (lno, rname) = wle
1042                 nrname = rname.replace('-','_')
1043                 try: rclass = globals()['Restriction_'+nrname]
1044                 except KeyError: raise Unsupported(lno,
1045                         'unknown restriction %s' % rname)
1046                 r = rclass(nrname, f.base)
1047                 f.base['restriction_names'].append(rname)
1048                 f.base['restrictions'].append(r)
1049
1050 class Field_Features(FieldIgnore):
1051  def parse(f):
1052         for wle in f.words():
1053                 (lno, fname) = wle
1054                 f.base['feature_names'].append(fname)
1055                 nfname = fname.replace('-','_')
1056                 try: fclass = globals()['Feature_'+nfname]
1057                 except KeyError: continue
1058                 ft = fclass(nfname, f.base)
1059                 f.base['features'].append(ft)
1060
1061 class Field_Tests(FieldIgnore): pass
1062
1063 class Field_Depends(FieldBase):
1064  def parse(f):
1065         debug("Field_DependS: %s %s %s %s" % (f.stz, f.base, f.tnames, f.vl), 2)
1066         dl = map(lambda x: x.strip(),
1067                 flatten(map(lambda (lno, v): v.split(','), f.vl)))
1068         re = regexp.compile('[^-.+:~0-9a-z()<>=*@]')
1069         for d in dl:
1070                 if re.search(d):
1071                         badpkg("Test Depends field contains dependency"
1072                                " `%s' with invalid characters" % d)
1073         f.base['depends'] = dl
1074
1075 class Field_Tests_directory(FieldBase):
1076  def parse(f):
1077         td = atmostone(f)
1078         if td.startswith('/'): raise Unspported(f.lno,
1079                 'Tests-Directory may not be absolute')
1080         f.base['testsdir'] = td
1081
1082 def run_tests(stanzas, tree):
1083         global errorcode, testbed
1084         if stanzas == ():
1085                 report('*', 'SKIP no tests in this package')
1086                 errorcode |= 8
1087         for stanza in stanzas:
1088                 tests = stanza[' tests']
1089                 if not tests:
1090                         report('*', 'SKIP package has metadata but no tests')
1091                         errorcode |= 8
1092                 for t in tests:
1093                         t.prepare(tree)
1094                         t.run(tree)
1095                         if 'breaks-testbed' in t.restriction_names:
1096                                 testbed.needs_reset()
1097                 testbed.needs_reset()
1098
1099 class Test:
1100  def __init__(t, tname, base, act):
1101         if '/' in tname: raise Unsupported(base[' lno'],
1102                 'test name may not contain / character')
1103         for k in base: setattr(t,k,base[k])
1104         t.tname = tname
1105         t.act = act
1106         t.what = act.what+'t-'+tname
1107         if len(base['testsdir']): t.path = base['testsdir'] + '/' + tname
1108         else: t.path = tname
1109         t._debug('constructed; path=%s' % t.path)
1110         t._debug(' .depends=%s' % t.depends)
1111  def _debug(t, m):
1112         debug('& %s: %s' % (t.what, m))
1113  def report(t, m):
1114         report(t.what, m)
1115  def reportfail(t, m):
1116         global errorcode
1117         errorcode |= 4
1118         report(t.what, 'FAIL ' + m)
1119  def prepare(t, tree):
1120         t._debug('preparing')
1121         dn = []
1122         for d in t.depends:
1123                 t._debug(' processing dependency '+d)
1124                 if not '@' in d:
1125                         t._debug('  literal dependency '+d)
1126                         dn.append(d)
1127                 else:
1128                         for pkg in packages_from_source(t.act, tree):
1129                                 dp = d.replace('@',pkg)
1130                                 t._debug('  synthesised dependency '+dp)
1131                                 dn.append(dp)
1132         testbed.prepare(dn)
1133  def run(t, tree):
1134         t._debug('[----------------------------------------')
1135         def stdouterr(oe):
1136                 idstr = t.what + '-' + oe
1137                 if opts.output_dir is not None and opts.output_dir.spec_tbp:
1138                         use_dir = opts.output_dir
1139                 else:
1140                         use_dir = testbed.scratch
1141                 return RelativeOutputFile(idstr, use_dir, idstr)
1142
1143         if hasattr(t.act,'work'): t.act.work.write(True)
1144         tree.read(True)
1145
1146         af = RelativeInputFile(t.what, tree, t.path)
1147         so = stdouterr('stdout')
1148         se = stdouterr('stderr')
1149
1150         tf = af.read(True)
1151         xenv = []
1152
1153         rc = testbed.execute('testchmod-'+t.what, ['chmod','+x','--',tf])
1154         if rc: bomb('failed to chmod +x %s' % tf)
1155
1156         testtmp = '%s%s-testtmp' % (testbed.scratch.read(True), t.what)
1157
1158         script = 'buildtree="$1"; shift\n'
1159         script += 'rm -rf -- "$@"; mkdir -- "$@"\n'
1160
1161         if 'needs-root' not in t.restriction_names and opts.user is not None:
1162                 tfl = ['su',opts.user,'-c',tf]
1163                 if opts.user:
1164                         script += 'chown %s "$@"\n' % opts.user
1165                         if 'rw-build-tree' in t.restriction_names:
1166                                 script += ('chown -R %s "$buildtree"\n'
1167                                                  % opts.user)
1168         else:
1169                 tfl = [tf]
1170
1171         test_tmpdir = testtmp+'/tmpdir'; xenv.append('TMPDIR=%s' % test_tmpdir)
1172         test_adttmp = testtmp+'/adttmp'; xenv.append('ADTTMP=%s' % test_adttmp)
1173         rc = testbed.execute('mktmpdir-'+t.what,
1174                 ['sh','-xec',script,'x', tree.read(True),
1175                         testtmp, test_tmpdir, test_adttmp, 
1176                         ])
1177         if rc: bomb("could not create test tmp dirs in `%s', exit code %d"
1178                         % (testtmp, rc))
1179
1180         rc = testbed.execute('test-'+t.what, tfl,
1181                 so=so.write(True), se=se.write(True), cwd=tree.read(True),
1182                 xenv=xenv, kind='test')
1183
1184         so_read = so.read()
1185         se_read = se.read()
1186
1187         t._debug(' - - - - - - - - - - results - - - - - - - - - -')
1188         stab = os.stat(se_read)
1189         if stab.st_size != 0:
1190                 l = open(se_read).readline()
1191                 l = l.rstrip('\n \t\r')
1192                 if len(l) > 35: l = l[:35] + '...'
1193                 t.reportfail('status: %d, stderr: %s' % (rc, l))
1194                 t._debug(' - - - - - - - - - - stderr - - - - - - - - - -')
1195                 debug_file(se_read)
1196         elif rc != 0:
1197                 t.reportfail('non-zero exit status %d' % rc)
1198         else:
1199                 t.report('PASS')
1200
1201         stab = os.stat(so_read)
1202         if stab.st_size != 0:
1203                 t._debug(' - - - - - - - - - - stdout - - - - - - - - - -')
1204                 debug_file(so_read)
1205
1206         t._debug('----------------------------------------]')
1207
1208 def read_stanzas(af):
1209         stanzas = [ ]
1210
1211         try:
1212                 control = open(af.read(), 'r')
1213         except (IOError,OSError), oe:
1214                 if oe[0] != errno.ENOENT: raise
1215                 return []
1216
1217         lno = 0
1218         def badctrl(m): testbed.bomb('tests/control line %d: %s' % (lno, m))
1219         stz = { }       # stz[field_name][index] = (lno, value)
1220                         # special field names:
1221                         # stz[' lno'] = number
1222                         # stz[' tests'] = list of Test objects
1223                         # empty dictionary means we're between stanzas
1224         def in_stanza(stz):
1225                 return stz.has_key(' lno')
1226         def end_stanza(stz):
1227                 if not in_stanza(stz): return
1228                 stz[' errs'] = 0
1229                 stanzas.append(stz.copy())
1230                 stz.clear()
1231                 hcurrent = None
1232
1233         initre = regexp.compile('([A-Z][-0-9a-zA-Z]*)\s*\:\s*(.*)$')
1234         while 1:
1235                 l = control.readline()
1236                 if not l: break
1237                 lno += 1
1238                 if not l.endswith('\n'): badctrl('unterminated line')
1239                 if regexp.compile('\s*\#').match(l): continue
1240                 if not regexp.compile('\S').match(l): end_stanza(stz); continue
1241                 initmat = initre.match(l)
1242                 if initmat:
1243                         (fname, l) = initmat.groups()
1244                         fname = string.capwords(fname)
1245                         if not in_stanza(stz):
1246                                 stz = { ' lno': lno, ' tests': [] }
1247                         if not stz.has_key(fname): stz[fname] = [ ]
1248                         hcurrent = stz[fname]
1249                 elif regexp.compile('\s').match(l):
1250                         if not hcurrent: badctrl('unexpected continuation')
1251                 else:
1252                         badctrl('syntax error')
1253                 hcurrent.append((lno, l))
1254         end_stanza(stz)
1255
1256         return stanzas
1257
1258 def read_control(act, tree, control_override):
1259
1260         if control_override is not None:
1261                 control_af = control_override
1262                 testbed.blame('arg:'+control_override.spec)
1263         else:
1264                 if act.missing_tests_control:
1265                         return ()
1266                 control_af = RelativeInputFile(act.what+'-testcontrol',
1267                         tree, 'debian/tests/control')
1268
1269         stanzas = read_stanzas(control_af)
1270
1271         def testbadctrl(stz, lno, m):
1272                 report_badctrl(lno, m)
1273                 stz[' errs'] += 1
1274
1275         for stz in stanzas:
1276                 try:
1277                         try: tnames = stz['Tests']
1278                         except KeyError:
1279                                 tnames = ['*']
1280                                 raise Unsupported(stz[' lno'],
1281                                         'no Tests field')
1282                         tnames = map((lambda lt: lt[1]), tnames)
1283                         tnames = string.join(tnames).split()
1284                         base = {
1285                                 'restriction_names': [],
1286                                 'restrictions': [],
1287                                 'feature_names': [],
1288                                 'features': [],
1289                                 'testsdir': 'debian/tests',
1290                                 'depends' : '@'
1291                         }
1292                         for fname in stz.keys():
1293                                 if fname.startswith(' '): continue
1294                                 vl = stz[fname]
1295                                 try: fclass = globals()['Field_'+
1296                                         fname.replace('-','_')]
1297                                 except KeyError: raise Unsupported(vl[0][0],
1298                                         'unknown metadata field %s' % fname)
1299                                 f = fclass(stz, fname, base, tnames, vl)
1300                                 f.parse()
1301                         for tname in tnames:
1302                                 t = Test(tname, base, act)
1303                                 stz[' tests'].append(t)
1304                 except Unsupported, u:
1305                         for tname in tnames: u.report(tname)
1306                         continue
1307
1308         return stanzas
1309
1310 def print_exception(ei, msgprefix=''):
1311         if msgprefix: pstderr(msgprefix)
1312         (et, q, tb) = ei
1313         if et is Quit:
1314                 pstderr('adt-run: ' + q.m)
1315                 psummary('quitting: '+q.m)
1316                 return q.ec
1317         else:
1318                 pstderr("adt-run: unexpected, exceptional, error:")
1319                 psummary('quitting: unexpected error, consult transcript')
1320                 traceback.print_exc(None, sys.stderr)
1321                 if trace_stream is not None:
1322                         traceback.print_exc(None, trace_stream)
1323                 return 20
1324
1325 def cleanup():
1326         global trace_stream
1327         try:
1328                 if testbed is not None:
1329                         testbed.reset_apt()
1330                         testbed.stop()
1331                 if opts.tmp is None and tmp is not None:
1332                         rmtree('tmp', tmp)
1333                 if trace_stream is not None:
1334                         trace_stream.close()
1335                         trace_stream = None
1336         except:
1337                 print_exception(sys.exc_info(),
1338                         '\nadt-run: error cleaning up:\n')
1339                 os._exit(20)
1340
1341 #---------- registration, installation etc. of .deb's: Binaries
1342
1343 def determine_package(act):
1344         cmd = 'dpkg-deb --info --'.split(' ')+[act.af.read(),'control']
1345         (rc, output) = subprocess_cooked(cmd, stdout=subprocess.PIPE)
1346         if rc: badpkg('failed to parse binary package, code %d' % rc)
1347         re = regexp.compile('^\s*Package\s*:\s*([0-9a-z][-+.0-9a-z]*)\s*$')
1348         act.pkg = None
1349         for l in output.split('\n'):
1350                 m = re.match(l)
1351                 if not m: continue
1352                 if act.pkg: badpkg('two Package: lines in control file')
1353                 act.pkg = m.groups()[0]
1354         if not act.pkg: badpkg('no good Package: line in control file')
1355
1356 def packages_from_source(act, tree):
1357     (rc, output) = subprocess_cooked(['dh_listpackages'],
1358             stdout=subprocess.PIPE, cwd=tree.read())
1359     if rc: badpkg('failed to parse packages built from source, code %d' % rc)
1360
1361     # filter out empty lines
1362     packages = [p for p in output.split() if p] 
1363
1364     # filter out udebs
1365     control_af = RelativeInputFile(act.what+'-control',
1366             tree, 'debian/control')
1367     for st in read_stanzas(control_af):
1368             if 'Package' not in st:
1369                     # source stanza
1370                     continue
1371             if 'Xc-package-type' in st:
1372                     try:
1373                             packages.remove(st['Package'][0][1])
1374                     except ValueError:
1375                             pass
1376
1377     return packages
1378
1379 class Binaries:
1380  def __init__(b, tb):
1381         b.dir = TemporaryDir('binaries')
1382         b.dir.write()
1383         ok = False
1384
1385         if opts.gnupghome is None:
1386                 opts.gnupghome = tmp+'/gnupg'
1387
1388         b._debug('initialising')
1389         try:
1390                 for x in ['pubring','secring']:
1391                         os.stat(opts.gnupghome + '/' + x + '.gpg')
1392                 ok = True
1393         except (IOError,OSError), oe:
1394                 if oe.errno != errno.ENOENT: raise
1395
1396         if ok: b._debug('no key generation needed')
1397         else: b.genkey()
1398
1399  def _debug(b, s):
1400         debug('* '+s)
1401
1402  def genkey(b):
1403         b._debug('preparing for key generation')
1404
1405         mkdir_okexist(os.path.dirname(opts.gnupghome), 02755)
1406         mkdir_okexist(opts.gnupghome, 0700)
1407
1408         script = '''
1409   exec >&2
1410   cd "$1"
1411   cat <<"END" >key-gen-params
1412 Key-Type: DSA
1413 Key-Length: 1024
1414 Key-Usage: sign
1415 Name-Real: autopkgtest per-run key
1416 Name-Comment: do not trust this key
1417 Name-Email: autopkgtest@example.com
1418 END
1419   set -x
1420   gpg --homedir="$1" --batch --gen-key key-gen-params
1421                 '''
1422         cmdl = ['sh','-ec',script,'x',opts.gnupghome]
1423         rc = subprocess_cooked(cmdl, dbg=('genkey',script))[0]
1424         if rc: bomb('key generation failed, code %d' % rc)
1425
1426  def apt_configs(b):
1427         return {
1428                 "Debug::pkgProblemResolver": "true",
1429                 "APT::Get::force-yes" : "true",
1430                 "APT::Get::Assume-Yes" : "true",
1431                 "quiet" : "true",
1432         }
1433
1434  def _configure_apt(b, tb):
1435         config = OutputFile('apt-config','/etc/apt/apt.conf.d/90autopkgtest',
1436                         True)
1437         f = open(config.write(),'w')
1438         for (k,v) in b.apt_configs().iteritems():
1439                 print >>f, '%s { "%s"; };' % (k, v)
1440         f.close()
1441         config.read(True)
1442
1443         prefs = OutputFile('apt-prefs','/etc/apt/preferences.d/90autopkgtest',
1444                         True)
1445         print >>open(prefs.write(),'w'), '''
1446 Package: *
1447 Pin: origin ""
1448 Pin-Priority: 1002
1449 '''
1450         prefs.read(True)
1451         
1452  def _apt_get(b):
1453         ag = ['apt-get','-q']
1454         for kv in b.apt_configs().iteritems():
1455                 ag += ['-o', '%s=%s' % kv]
1456         return ' '.join(ag)
1457
1458  def reset(b):
1459         b._debug('reset')
1460         rmtree('binaries', b.dir.read())
1461         b.dir.invalidate()
1462         b.dir.write()
1463         b.install = []
1464         b.blamed = []
1465         b.registered = set()
1466
1467  def register(b, act, pkg, af, forwhat, blamed):
1468         b._debug('register what=%s deb_%s=%s pkg=%s af=%s'
1469                 % (act.what, forwhat, act.ah['deb_'+forwhat], pkg, str(af)))
1470
1471         if act.ah['deb_'+forwhat] == 'ignore': return
1472
1473         b.blamed += testbed.blamed
1474
1475         leafname = pkg+'.deb'
1476         dest = RelativeOutputFile('binaries--'+leafname, b.dir, leafname)
1477
1478         try: os.remove(dest.write())
1479         except (IOError,OSError), oe:
1480                 if oe.errno != errno.ENOENT: raise e
1481
1482         try: os.link(af.read(), dest.write())
1483         except (IOError,OSError), oe:
1484                 if oe.errno != errno.EXDEV: raise e
1485                 shutil.copy(af.read(), dest)
1486
1487         if act.ah['deb_'+forwhat] == 'install':
1488                 b.install.append(pkg)
1489
1490         b.registered.add(pkg)
1491
1492  def publish(b):
1493         b._debug('publish')
1494
1495         b._configure_apt(testbed)
1496
1497         script = '''
1498   exec >&2
1499   cd "$1"
1500   apt-ftparchive packages . >Packages
1501   gzip <Packages >Packages.gz
1502   apt-ftparchive release . >Release
1503   rm -f Release.gpg
1504   gpg --homedir="$2" --batch --detach-sign --armour -o Release.gpg Release
1505   gpg --homedir="$2" --batch --export >archive-key.pgp
1506                 '''
1507         cmdl = ['sh','-ec',script,'x',b.dir.write(),opts.gnupghome]
1508         rc = subprocess_cooked(cmdl, dbg=('ftparchive',script))[0]
1509         if rc: bomb('apt-ftparchive or signature failed, code %d' % rc)
1510
1511         b.dir.invalidate(True)
1512         apt_source = b.dir.read(True)
1513
1514         so = TemporaryFile('vlds')
1515         script = '''
1516   exec 3>&1 >&2
1517   apt-key add archive-key.pgp
1518   echo "deb file://'''+apt_source+''' /" >/etc/apt/sources.list.d/autopkgtest.list
1519   if [ "x`ls /var/lib/dpkg/updates`" != x ]; then
1520     echo >&2 "/var/lib/dpkg/updates contains some files, aargh"; exit 1
1521   fi
1522   '''+ b._apt_get() +''' update >&2
1523   cat /var/lib/dpkg/status >&3
1524                 '''
1525         testbed.mungeing_apt()
1526         rc = testbed.execute('apt-key', ['sh','-ec',script],
1527                                 so=so.write(True), cwd=b.dir.write(True),
1528                                 script=script, kind='install')
1529         if rc: bomb('apt setup failed with exit code %d' % rc)
1530
1531         testbed.blamed += b.blamed
1532
1533         b._debug('publish reinstall checking...')
1534         pkgs_reinstall = set()
1535         pkg = None
1536         for l in open(so.read()):
1537                 if l.startswith('Package: '):
1538                         pkg = l[9:].rstrip()
1539                 elif l.startswith('Status: install '):
1540                         if pkg in b.registered:
1541                                 pkgs_reinstall.add(pkg)
1542                                 b._debug(' publish reinstall needs '+pkg)
1543
1544         if pkgs_reinstall:
1545                 for pkg in pkgs_reinstall: testbed.blame(pkg)
1546                 what = 'apt-get-reinstall'
1547                 cmdl = (b._apt_get() + ' --reinstall install '+
1548                         ' '.join([pkg for pkg in pkgs_reinstall])+' >&2')
1549                 cmdl = ['sh','-c',cmdl]
1550                 rc = testbed.execute(what, cmdl, script=None, kind='install')
1551                 if rc: badpkg("installation of basic binarries failed,"
1552                                 " exit code %d" % rc)
1553
1554         b._debug('publish install...')
1555         for pkg in b.install:
1556                 what = 'apt-get-install-%s' % pkg
1557                 testbed.blame(pkg)
1558                 cmdl = b._apt_get() + ' install ' + pkg + ' >&2'
1559                 cmdl = ['sh','-c',cmdl]
1560                 rc = testbed.execute(what, cmdl, script=None, kind='install')
1561                 if rc: badpkg("installation of %s failed, exit code %d"
1562                                 % (pkg, rc))
1563
1564         b._debug('publish done')
1565
1566 #---------- processing of sources (building)
1567
1568 def source_rules_command(act,script,what,which,work,cwd,
1569                                 results_lines=0,xargs=[]):
1570         script = [      "exec 3>&1 >&2",
1571                         "set -x"        ] + script
1572         script = '\n'.join(script)
1573         so = TemporaryFile('%s-%s-results' % (what,which))
1574         rc = testbed.execute('%s-%s' % (what,which),
1575                         ['sh','-ec',script]+xargs, script=script,
1576                         so=so.write(True), cwd=cwd, kind='build')
1577         results = open(so.read()).read().rstrip('\n')
1578         if len(results): results = results.split("\n")
1579         else: results = []
1580         if rc: badpkg("rules %s failed with exit code %d" % (which,rc))
1581         if results_lines is not None and len(results) != results_lines:
1582                 badpkg("got %d lines of results from %s where %d expected"
1583                         % (len(results), which, results_lines))
1584         if results_lines==1: return results[0]
1585         return results
1586
1587 def build_source(act, control_override):
1588         act.blame = 'arg:'+act.af.spec
1589         testbed.blame(act.blame)
1590         testbed.prepare1([])
1591         testbed.needs_reset()
1592
1593         what = act.what
1594         basename = act.af.spec
1595         debiancontrol = None
1596         act.binaries = []
1597
1598         def debug_b(m): debug('* <%s:%s> %s' % (act.kind, act.what, m))
1599
1600         if act.kind == 'dsc':
1601                 dsc = act.af
1602                 dsc_file = open(dsc.read())
1603                 in_files = False
1604                 fre = regexp.compile('^\s+[0-9a-f]+\s+\d+\s+([^/.][^/]*)$')
1605                 for l in dsc_file:
1606                         l = l.rstrip('\n')
1607                         if l.startswith('Files:'): in_files = True; continue
1608                         elif l.startswith('#'): pass
1609                         elif not l.startswith(' '):
1610                                 in_files = False
1611                                 if l.startswith('Source:'):
1612                                         act.blame = 'dsc:'+l[7:].strip()
1613                                         testbed.blame(act.blame)
1614                         if not in_files: continue
1615
1616                         m = fre.match(l)
1617                         if not m: badpkg(".dsc contains unparseable line"
1618                                         " in Files: `%s'" % l)
1619                         leaf = m.groups(0)[0]
1620                         subfile = RelativeInputFile(what+'/'+leaf, dsc, leaf,
1621                                         sibling=True)
1622                         subfile.read(True)
1623                 dsc.read(True)
1624
1625         if act.kind == 'ubtree':
1626                 debiancontrol = RelativeInputFile(what+'-debiancontrol',
1627                         act.af, 'debian/control')
1628                 dsc = TemporaryFile(what+'-fakedsc')
1629                 dsc_w = open(dsc.write(), 'w')
1630                 for l in open(debiancontrol.read()):
1631                         l = l.rstrip('\n')
1632                         if not len(l): break
1633                         print >>dsc_w, l
1634                 print >>dsc_w, 'Binary: none-so-this-is-not-a-package-name'
1635                 dsc_w.close()
1636
1637         if act.kind == 'dsc':
1638                 testbed.prepare2([])
1639                 testbed.satisfy_dependencies_string('dpkg-dev',
1640                                                 'install dpkg-dev')
1641
1642         work = TemporaryDir(what+'-build')
1643         act.work = work
1644
1645         tmpdir = work.write(True)+'/tmpdir'
1646         tmpdir_script = [
1647                         'TMPDIR="$1"',
1648                         'rm -rf -- "$TMPDIR"',
1649                         'export TMPDIR',
1650                         opts.user_wrap('mkdir -- "$TMPDIR"'),
1651                 ]
1652
1653         if act.kind == 'ubtree':
1654                 spec = '%s/real-tree' % work.write(True)
1655                 create_command = '''
1656                         rm -rf "$spec"
1657                         mkdir "$spec"
1658                         cp -rP --preserve=timestamps,links -- "$origpwd"/. "$spec"/.
1659                         '''
1660                 initcwd = act.af.read(True)
1661
1662         if act.kind == 'dsc':
1663                 spec = dsc.read(True)
1664                 create_command = '''
1665                         dpkg-source -x $spec
1666                         '''
1667                 initcwd = work.write(True)
1668
1669         script = [
1670                 'spec="$2"',
1671                 'origpwd=`pwd`',
1672                 'cd '+work.write(True)
1673         ]
1674
1675         if opts.user:
1676                 script += ([ 'chown '+opts.user+' .' ] +
1677                         tmpdir_script +
1678                         [ 'spec="$spec" origpwd="$origpwd" '
1679                                 +opts.user_wrap(create_command) ])
1680         else:
1681                 script += (tmpdir_script +
1682                         [ create_command ])
1683
1684         script += [
1685                         'cd [a-z0-9]*-*/.',
1686                         'pwd >&3',
1687                         'set +e; test -f debian/tests/control; echo $? >&3'
1688                 ]
1689         (result_pwd, control_test_rc) = source_rules_command(
1690                         act,script,what,'extract',work,
1691                         cwd=initcwd, results_lines=2, xargs=['x',tmpdir,spec])
1692
1693         filter = act.ah['dsc_filter']
1694
1695         if control_test_rc == '1': act.missing_tests_control = True
1696
1697         # For optional builds:
1698         #
1699         # We might need to build the package because:
1700         #   - we want its binaries (filter isn't _ and at least one of the
1701         #       deb_... isn't ignore)
1702         #   - the test control file says so
1703         #       (assuming we have any tests)
1704
1705         class NeedBuildException: pass
1706         def build_needed(m):
1707                 debug_b('build needed for %s' % m)
1708                 raise NeedBuildException()
1709
1710         try:
1711                 if filter != '_' and (act.ah['deb_forbuilds'] != 'ignore' or
1712                                       act.ah['deb_fortests'] != 'ignore'):
1713                         build_needed('binaries')
1714
1715                 result_pwd_af = InputDir(what+'-treeforcontrol',
1716                         result_pwd, True)
1717                 stanzas = read_control(act, result_pwd_af, control_override)
1718                 for stanza in stanzas:
1719                         for t in stanza[' tests']:
1720                                 if 'build-needed' in t.restriction_names:
1721                                         build_needed('test %s' % t.tname)
1722
1723                 debug_b('build not needed')
1724                 built = False
1725
1726         except NeedBuildException:
1727
1728                 if act.kind != 'dsc':
1729                         testbed.prepare2([])
1730
1731                 testbed.satisfy_dependencies_string('build-essential',
1732                                 'install build-essential')
1733                 testbed.satisfy_dependencies_dsc(dsc, 'build dependencies')
1734
1735                 script = tmpdir_script + [
1736                         'cd "$2"',
1737                         'dpkg-checkbuilddeps',
1738                         opts.user_wrap('debian/rules build'),
1739                         ]
1740                 source_rules_command(act,script,what,'build',work,
1741                                 cwd=initcwd, xargs=['x',tmpdir,result_pwd])
1742
1743                 if os.path.dirname(result_pwd)+'/' != work.read(True):
1744                         badpkg("results dir `%s' is not in expected parent"
1745                                " dir `%s'" % (result_pwd, work.read(True)))
1746
1747                 built = True
1748
1749         act.tests_tree = RelativeInputDir(what+'-tests-tree',
1750                                 work, os.path.basename(result_pwd),
1751                                 True)
1752
1753         if not built:
1754                 act.blamed = []
1755                 return
1756
1757         act.blamed = copy.copy(testbed.blamed)
1758
1759         debug_b('filter=%s' % filter)
1760         if filter != '_':
1761                 script = tmpdir_script + [
1762                         'cd '+work.write(True)+'/[a-z0-9]*-*/.',
1763                         opts.user_wrap(opts.gainroot+' debian/rules binary'),
1764                         'cd ..',
1765                         'echo *.deb >&3',
1766                         ]
1767                 result_debs = source_rules_command(act,script,what,
1768                                 'binary',work,work.write(True),
1769                                 results_lines=1, xargs=['x',tmpdir])
1770                 if result_debs == '*.deb': debs = []
1771                 else: debs = result_debs.split(' ')
1772                 debug_b('debs='+`debs`)
1773                 re = regexp.compile('^([-+.0-9a-z]+)_[^_/]+(?:_[^_/]+)\.deb$')
1774                 for deb in debs:
1775                         m = re.match(deb)
1776                         if not m: badpkg("badly-named binary `%s'" % deb)
1777                         pkg = m.groups()[0]
1778                         debug_b(' deb=%s, pkg=%s' % (deb,pkg))
1779                         for pat in filter.split(','):
1780                                 debug_b('  pat=%s' % pat)
1781                                 if not fnmatch.fnmatchcase(pkg,pat):
1782                                         debug_b('   no match')
1783                                         continue
1784                                 deb_what = pkg+'_'+what+'.deb'
1785                                 bin = RelativeInputFile(deb_what,work,deb,True)
1786                                 debug_b('  deb_what=%s, bin=%s' %
1787                                         (deb_what, str(bin)))
1788                                 binaries.register(act,pkg,bin,
1789                                         'forbuilds',testbed.blamed)
1790                                 act.binaries.append((pkg,bin))
1791                                 break
1792                 debug_b('all done.')
1793
1794 #---------- main processing loop and main program
1795
1796 def process_actions():
1797         global binaries
1798
1799         def debug_a1(m): debug('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ '+m)
1800         def debug_a2(m): debug('@@@@@@@@@@@@@@@@@@@@ '+m)
1801         def debug_a3(m): debug('@@@@@@@@@@ '+m)
1802
1803         debug_a1('starting')
1804         testbed.open()
1805         binaries = Binaries(testbed)
1806
1807         binaries.reset()
1808         control_override = None
1809
1810         debug_a1('builds ...')
1811         for act in opts.actions:
1812                 debug_a2('%s %s' %
1813                         (act.kind, act.what))
1814
1815                 if act.kind == 'control':
1816                         control_override = act.af
1817                 if act.kind == 'deb':
1818                         testbed.blame('arg:'+act.af.spec)
1819                         determine_package(act)
1820                         testbed.blame('deb:'+act.pkg)
1821                         binaries.register(act,act.pkg,act.af,
1822                                 'forbuilds',testbed.blamed)
1823                 if act.kind == 'dsc' or act.kind == 'ubtree':
1824                         build_source(act, control_override)
1825                 if act.kind == 'tree':
1826                         act.binaries = []
1827                 if act.kind.endswith('tree') or act.kind == 'dsc':
1828                         control_override = None
1829                 if act.kind == 'instantiate':
1830                         pass
1831
1832         debug_a1('builds done.')
1833
1834         binaries.reset()
1835         control_override = None
1836
1837         debug_a1('tests ...')
1838         for act in opts.actions:
1839                 debug_a2('test %s %s' % (act.kind, act.what))
1840
1841                 testbed.needs_reset()
1842                 if act.kind == 'control':
1843                         control_override = act.af
1844                 if act.kind == 'deb':
1845                         binaries.register(act,act.pkg,act.af,'fortests',
1846                                 ['deb:'+act.pkg])
1847                 if act.kind == 'dsc' or act.kind == 'ubtree':
1848                         for (pkg,bin) in act.binaries:
1849                                 binaries.register(act,pkg,bin,'fortests',
1850                                         act.blamed)
1851                 if act.kind == 'dsc':
1852                         if act.ah['dsc_tests']:
1853                                 debug_a3('read control ...')
1854                                 stanzas = read_control(act, act.tests_tree,
1855                                                 control_override)
1856                                 testbed.blamed += act.blamed
1857                                 debug_a3('run_tests ...')
1858                                 run_tests(stanzas, act.tests_tree)
1859                         control_override = None
1860                 if act.kind == 'tree' or act.kind == 'ubtree':
1861                         testbed.blame('arg:'+act.af.spec)
1862                         stanzas = read_control(act, act.af, control_override)
1863                         debug_a3('run_tests ...')
1864                         run_tests(stanzas, act.af)
1865                         control_override = None
1866                 if act.kind == 'instantiate':
1867                         testbed.prepare([])
1868         debug_a1('tests done.')
1869
1870 def main():
1871         global testbed
1872         global tmp
1873         try:
1874                 parse_args()
1875         except SystemExit, se:
1876                 os._exit(20)
1877         try:
1878                 setup_trace()
1879                 testbed = Testbed()
1880                 testbed.start()
1881                 finalise_options()
1882                 process_actions()
1883         except:
1884                 ec = print_exception(sys.exc_info(), '')
1885                 cleanup()
1886                 os._exit(ec)
1887         cleanup()
1888         os._exit(errorcode)
1889
1890 main()