chiark / gitweb /
tmpdir: undo rename in build - we actually want TMPDIR there
[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 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()
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):
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,bin) in t.act.binaries:
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         if 'needs-root' not in t.restriction_names and opts.user is not None:
1157                 tfl = ['su',opts.user,'-c',tf]
1158                 testtmp = '%s%s-testtmp' % (testbed.scratch.read(True), t.what)
1159                 script = 'rm -rf -- "$1"; mkdir -- "$1"'
1160                 if opts.user:
1161                         script += '; chown %s "$1"' % opts.user
1162                         if 'rw-build-tree' in t.restriction_names:
1163                                 script += '; chown -R %s "$2"' % opts.user
1164                 xenv = ['TMPDIR=%s' % testtmp]
1165                 rc = testbed.execute('mktmpdir-'+t.what,
1166                         ['sh','-xec',script,'x',xenv=xenv,tree.read(True)])
1167                 if rc: bomb("could not create test tmp `%s', exit code %d"
1168                                 % (testtmp, rc))
1169         else:
1170                 tfl = [tf]
1171
1172         rc = testbed.execute('test-'+t.what, tfl,
1173                 so=so.write(True), se=se.write(True), cwd=tree.read(True),
1174                 xenv=xenv, kind='test')
1175
1176         so_read = so.read()
1177         se_read = se.read()
1178
1179         t._debug(' - - - - - - - - - - results - - - - - - - - - -')
1180         stab = os.stat(se_read)
1181         if stab.st_size != 0:
1182                 l = open(se_read).readline()
1183                 l = l.rstrip('\n \t\r')
1184                 if len(l) > 35: l = l[:35] + '...'
1185                 t.reportfail('status: %d, stderr: %s' % (rc, l))
1186                 t._debug(' - - - - - - - - - - stderr - - - - - - - - - -')
1187                 debug_file(se_read)
1188         elif rc != 0:
1189                 t.reportfail('non-zero exit status %d' % rc)
1190         else:
1191                 t.report('PASS')
1192
1193         stab = os.stat(so_read)
1194         if stab.st_size != 0:
1195                 t._debug(' - - - - - - - - - - stdout - - - - - - - - - -')
1196                 debug_file(so_read)
1197
1198         t._debug('----------------------------------------]')
1199
1200 def read_control(act, tree, control_override):
1201         stanzas = [ ]
1202
1203         if control_override is not None:
1204                 control_af = control_override
1205                 testbed.blame('arg:'+control_override.spec)
1206         else:
1207                 if act.missing_tests_control:
1208                         return ()
1209                 control_af = RelativeInputFile(act.what+'-testcontrol',
1210                         tree, 'debian/tests/control')
1211         try:
1212                 control = open(control_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         def testbadctrl(stz, lno, m):
1257                 report_badctrl(lno, m)
1258                 stz[' errs'] += 1
1259
1260         for stz in stanzas:
1261                 try:
1262                         try: tnames = stz['Tests']
1263                         except KeyError:
1264                                 tnames = ['*']
1265                                 raise Unsupported(stz[' lno'],
1266                                         'no Tests field')
1267                         tnames = map((lambda lt: lt[1]), tnames)
1268                         tnames = string.join(tnames).split()
1269                         base = {
1270                                 'restriction_names': [],
1271                                 'restrictions': [],
1272                                 'feature_names': [],
1273                                 'features': [],
1274                                 'testsdir': 'debian/tests',
1275                                 'depends' : '@'
1276                         }
1277                         for fname in stz.keys():
1278                                 if fname.startswith(' '): continue
1279                                 vl = stz[fname]
1280                                 try: fclass = globals()['Field_'+
1281                                         fname.replace('-','_')]
1282                                 except KeyError: raise Unsupported(vl[0][0],
1283                                         'unknown metadata field %s' % fname)
1284                                 f = fclass(stz, fname, base, tnames, vl)
1285                                 f.parse()
1286                         for tname in tnames:
1287                                 t = Test(tname, base, act)
1288                                 stz[' tests'].append(t)
1289                 except Unsupported, u:
1290                         for tname in tnames: u.report(tname)
1291                         continue
1292
1293         return stanzas
1294
1295 def print_exception(ei, msgprefix=''):
1296         if msgprefix: pstderr(msgprefix)
1297         (et, q, tb) = ei
1298         if et is Quit:
1299                 pstderr('adt-run: ' + q.m)
1300                 psummary('quitting: '+q.m)
1301                 return q.ec
1302         else:
1303                 pstderr("adt-run: unexpected, exceptional, error:")
1304                 psummary('quitting: unexpected error, consult transcript')
1305                 traceback.print_exc(None, sys.stderr)
1306                 if trace_stream is not None:
1307                         traceback.print_exc(None, trace_stream)
1308                 return 20
1309
1310 def cleanup():
1311         global trace_stream
1312         try:
1313                 if testbed is not None:
1314                         testbed.reset_apt()
1315                         testbed.stop()
1316                 if opts.tmp is None and tmp is not None:
1317                         rmtree('tmp', tmp)
1318                 if trace_stream is not None:
1319                         trace_stream.close()
1320                         trace_stream = None
1321         except:
1322                 print_exception(sys.exc_info(),
1323                         '\nadt-run: error cleaning up:\n')
1324                 os._exit(20)
1325
1326 #---------- registration, installation etc. of .deb's: Binaries
1327
1328 def determine_package(act):
1329         cmd = 'dpkg-deb --info --'.split(' ')+[act.af.read(),'control']
1330         (rc, output) = subprocess_cooked(cmd, stdout=subprocess.PIPE)
1331         if rc: badpkg('failed to parse binary package, code %d' % rc)
1332         re = regexp.compile('^\s*Package\s*:\s*([0-9a-z][-+.0-9a-z]*)\s*$')
1333         act.pkg = None
1334         for l in output.split('\n'):
1335                 m = re.match(l)
1336                 if not m: continue
1337                 if act.pkg: badpkg('two Package: lines in control file')
1338                 act.pkg = m.groups()[0]
1339         if not act.pkg: badpkg('no good Package: line in control file')
1340
1341 class Binaries:
1342  def __init__(b, tb):
1343         b.dir = TemporaryDir('binaries')
1344         b.dir.write()
1345         ok = False
1346
1347         if opts.gnupghome is None:
1348                 opts.gnupghome = tmp+'/gnupg'
1349
1350         b._debug('initialising')
1351         try:
1352                 for x in ['pubring','secring']:
1353                         os.stat(opts.gnupghome + '/' + x + '.gpg')
1354                 ok = True
1355         except (IOError,OSError), oe:
1356                 if oe.errno != errno.ENOENT: raise
1357
1358         if ok: b._debug('no key generation needed')
1359         else: b.genkey()
1360
1361  def _debug(b, s):
1362         debug('* '+s)
1363
1364  def genkey(b):
1365         b._debug('preparing for key generation')
1366
1367         mkdir_okexist(os.path.dirname(opts.gnupghome), 02755)
1368         mkdir_okexist(opts.gnupghome, 0700)
1369
1370         script = '''
1371   exec >&2
1372   cd "$1"
1373   cat <<"END" >key-gen-params
1374 Key-Type: DSA
1375 Key-Length: 1024
1376 Key-Usage: sign
1377 Name-Real: autopkgtest per-run key
1378 Name-Comment: do not trust this key
1379 Name-Email: autopkgtest@example.com
1380 END
1381   set -x
1382   gpg --homedir="$1" --batch --gen-key key-gen-params
1383                 '''
1384         cmdl = ['sh','-ec',script,'x',opts.gnupghome]
1385         rc = subprocess_cooked(cmdl, dbg=('genkey',script))[0]
1386         if rc: bomb('key generation failed, code %d' % rc)
1387
1388  def apt_configs(b):
1389         return {
1390                 "Debug::pkgProblemResolver": "true",
1391                 "APT::Get::force-yes" : "true",
1392                 "APT::Get::Assume-Yes" : "true",
1393                 "quiet" : "true",
1394         }
1395
1396  def _configure_apt(b, tb):
1397         config = OutputFile('apt-config','/etc/apt/apt.conf.d/90autopkgtest',
1398                         True)
1399         f = open(config.write(),'w')
1400         for (k,v) in b.apt_configs().iteritems():
1401                 print >>f, '%s { "%s"; };' % (k, v)
1402         f.close()
1403         config.read(True)
1404
1405         prefs = OutputFile('apt-prefs','/etc/apt/preferences.d/90autopkgtest',
1406                         True)
1407         print >>open(prefs.write(),'w'), '''
1408 Package: *
1409 Pin: origin ""
1410 Pin-Priority: 1002
1411 '''
1412         prefs.read(True)
1413         
1414  def _apt_get(b):
1415         ag = ['apt-get','-q']
1416         for kv in b.apt_configs().iteritems():
1417                 ag += ['-o', '%s=%s' % kv]
1418         return ' '.join(ag)
1419
1420  def reset(b):
1421         b._debug('reset')
1422         rmtree('binaries', b.dir.read())
1423         b.dir.invalidate()
1424         b.dir.write()
1425         b.install = []
1426         b.blamed = []
1427         b.registered = set()
1428
1429  def register(b, act, pkg, af, forwhat, blamed):
1430         b._debug('register what=%s deb_%s=%s pkg=%s af=%s'
1431                 % (act.what, forwhat, act.ah['deb_'+forwhat], pkg, str(af)))
1432
1433         if act.ah['deb_'+forwhat] == 'ignore': return
1434
1435         b.blamed += testbed.blamed
1436
1437         leafname = pkg+'.deb'
1438         dest = RelativeOutputFile('binaries--'+leafname, b.dir, leafname)
1439
1440         try: os.remove(dest.write())
1441         except (IOError,OSError), oe:
1442                 if oe.errno != errno.ENOENT: raise e
1443
1444         try: os.link(af.read(), dest.write())
1445         except (IOError,OSError), oe:
1446                 if oe.errno != errno.EXDEV: raise e
1447                 shutil.copy(af.read(), dest)
1448
1449         if act.ah['deb_'+forwhat] == 'install':
1450                 b.install.append(pkg)
1451
1452         b.registered.add(pkg)
1453
1454  def publish(b):
1455         b._debug('publish')
1456
1457         b._configure_apt(testbed)
1458
1459         script = '''
1460   exec >&2
1461   cd "$1"
1462   apt-ftparchive packages . >Packages
1463   gzip <Packages >Packages.gz
1464   apt-ftparchive release . >Release
1465   rm -f Release.gpg
1466   gpg --homedir="$2" --batch --detach-sign --armour -o Release.gpg Release
1467   gpg --homedir="$2" --batch --export >archive-key.pgp
1468                 '''
1469         cmdl = ['sh','-ec',script,'x',b.dir.write(),opts.gnupghome]
1470         rc = subprocess_cooked(cmdl, dbg=('ftparchive',script))[0]
1471         if rc: bomb('apt-ftparchive or signature failed, code %d' % rc)
1472
1473         b.dir.invalidate(True)
1474         apt_source = b.dir.read(True)
1475
1476         so = TemporaryFile('vlds')
1477         script = '''
1478   exec 3>&1 >&2
1479   apt-key add archive-key.pgp
1480   echo "deb file://'''+apt_source+''' /" >/etc/apt/sources.list.d/autopkgtest.list
1481   if [ "x`ls /var/lib/dpkg/updates`" != x ]; then
1482     echo >&2 "/var/lib/dpkg/updates contains some files, aargh"; exit 1
1483   fi
1484   '''+ b._apt_get() +''' update >&2
1485   cat /var/lib/dpkg/status >&3
1486                 '''
1487         testbed.mungeing_apt()
1488         rc = testbed.execute('apt-key', ['sh','-ec',script],
1489                                 so=so.write(True), cwd=b.dir.write(True),
1490                                 script=script, kind='install')
1491         if rc: bomb('apt setup failed with exit code %d' % rc)
1492
1493         testbed.blamed += b.blamed
1494
1495         b._debug('publish reinstall checking...')
1496         pkgs_reinstall = set()
1497         pkg = None
1498         for l in open(so.read()):
1499                 if l.startswith('Package: '):
1500                         pkg = l[9:].rstrip()
1501                 elif l.startswith('Status: install '):
1502                         if pkg in b.registered:
1503                                 pkgs_reinstall.add(pkg)
1504                                 b._debug(' publish reinstall needs '+pkg)
1505
1506         if pkgs_reinstall:
1507                 for pkg in pkgs_reinstall: testbed.blame(pkg)
1508                 what = 'apt-get-reinstall'
1509                 cmdl = (b._apt_get() + ' --reinstall install '+
1510                         ' '.join([pkg for pkg in pkgs_reinstall])+' >&2')
1511                 cmdl = ['sh','-c',cmdl]
1512                 rc = testbed.execute(what, cmdl, script=None, kind='install')
1513                 if rc: badpkg("installation of basic binarries failed,"
1514                                 " exit code %d" % rc)
1515
1516         b._debug('publish install...')
1517         for pkg in b.install:
1518                 what = 'apt-get-install-%s' % pkg
1519                 testbed.blame(pkg)
1520                 cmdl = b._apt_get() + ' install ' + pkg + ' >&2'
1521                 cmdl = ['sh','-c',cmdl]
1522                 rc = testbed.execute(what, cmdl, script=None, kind='install')
1523                 if rc: badpkg("installation of %s failed, exit code %d"
1524                                 % (pkg, rc))
1525
1526         b._debug('publish done')
1527
1528 #---------- processing of sources (building)
1529
1530 def source_rules_command(act,script,what,which,work,cwd,
1531                                 results_lines=0,xargs=[]):
1532         script = [      "exec 3>&1 >&2",
1533                         "set -x"        ] + script
1534         script = '\n'.join(script)
1535         so = TemporaryFile('%s-%s-results' % (what,which))
1536         rc = testbed.execute('%s-%s' % (what,which),
1537                         ['sh','-ec',script]+xargs, script=script,
1538                         so=so.write(True), cwd=cwd, kind='build')
1539         results = open(so.read()).read().rstrip('\n')
1540         if len(results): results = results.split("\n")
1541         else: results = []
1542         if rc: badpkg("rules %s failed with exit code %d" % (which,rc))
1543         if results_lines is not None and len(results) != results_lines:
1544                 badpkg("got %d lines of results from %s where %d expected"
1545                         % (len(results), which, results_lines))
1546         if results_lines==1: return results[0]
1547         return results
1548
1549 def build_source(act, control_override):
1550         act.blame = 'arg:'+act.af.spec
1551         testbed.blame(act.blame)
1552         testbed.prepare1([])
1553         testbed.needs_reset()
1554
1555         what = act.what
1556         basename = act.af.spec
1557         debiancontrol = None
1558         act.binaries = []
1559
1560         def debug_b(m): debug('* <%s:%s> %s' % (act.kind, act.what, m))
1561
1562         if act.kind == 'dsc':
1563                 dsc = act.af
1564                 dsc_file = open(dsc.read())
1565                 in_files = False
1566                 fre = regexp.compile('^\s+[0-9a-f]+\s+\d+\s+([^/.][^/]*)$')
1567                 for l in dsc_file:
1568                         l = l.rstrip('\n')
1569                         if l.startswith('Files:'): in_files = True; continue
1570                         elif l.startswith('#'): pass
1571                         elif not l.startswith(' '):
1572                                 in_files = False
1573                                 if l.startswith('Source:'):
1574                                         act.blame = 'dsc:'+l[7:].strip()
1575                                         testbed.blame(act.blame)
1576                         if not in_files: continue
1577
1578                         m = fre.match(l)
1579                         if not m: badpkg(".dsc contains unparseable line"
1580                                         " in Files: `%s'" % l)
1581                         leaf = m.groups(0)[0]
1582                         subfile = RelativeInputFile(what+'/'+leaf, dsc, leaf,
1583                                         sibling=True)
1584                         subfile.read(True)
1585                 dsc.read(True)
1586
1587         if act.kind == 'ubtree':
1588                 debiancontrol = RelativeInputFile(what+'-debiancontrol',
1589                         act.af, 'debian/control')
1590                 dsc = TemporaryFile(what+'-fakedsc')
1591                 dsc_w = open(dsc.write(), 'w')
1592                 for l in open(debiancontrol.read()):
1593                         l = l.rstrip('\n')
1594                         if not len(l): break
1595                         print >>dsc_w, l
1596                 print >>dsc_w, 'Binary: none-so-this-is-not-a-package-name'
1597                 dsc_w.close()
1598
1599         if act.kind == 'dsc':
1600                 testbed.prepare2([])
1601                 testbed.satisfy_dependencies_string('dpkg-dev',
1602                                                 'install dpkg-dev')
1603
1604         work = TemporaryDir(what+'-build')
1605         act.work = work
1606
1607         tmpdir = work.write(True)+'/tmpdir'
1608         tmpdir_script = [
1609                         'TMPDIR="$1"',
1610                         'rm -rf -- "$TMPDIR"',
1611                         'export TMPDIR',
1612                         opts.user_wrap('mkdir -- "$TMPDIR"'),
1613                 ]
1614
1615         if act.kind == 'ubtree':
1616                 spec = '%s/real-tree' % work.write(True)
1617                 create_command = '''
1618                         rm -rf "$spec"
1619                         mkdir "$spec"
1620                         cp -rP --preserve=timestamps,links -- "$origpwd"/. "$spec"/.
1621                         '''
1622                 initcwd = act.af.read(True)
1623
1624         if act.kind == 'dsc':
1625                 spec = dsc.read(True)
1626                 create_command = '''
1627                         dpkg-source -x $spec
1628                         '''
1629                 initcwd = work.write(True)
1630
1631         script = [
1632                 'spec="$2"',
1633                 'origpwd=`pwd`',
1634                 'cd '+work.write(True)
1635         ]
1636
1637         if opts.user:
1638                 script += ([ 'chown '+opts.user+' .' ] +
1639                         tmpdir_script +
1640                         [ 'spec="$spec" origpwd="$origpwd" '
1641                                 +opts.user_wrap(create_command) ])
1642         else:
1643                 script += (tmpdir_script +
1644                         [ create_command ])
1645
1646         script += [
1647                         'cd [a-z0-9]*-*/.',
1648                         'pwd >&3',
1649                         'set +e; test -f debian/tests/control; echo $? >&3'
1650                 ]
1651         (result_pwd, control_test_rc) = source_rules_command(
1652                         act,script,what,'extract',work,
1653                         cwd=initcwd, results_lines=2, xargs=['x',tmpdir,spec])
1654
1655         filter = act.ah['dsc_filter']
1656
1657         if control_test_rc == '1': act.missing_tests_control = True
1658
1659         # For optional builds:
1660         #
1661         # We might need to build the package because:
1662         #   - we want its binaries (filter isn't _ and at least one of the
1663         #       deb_... isn't ignore)
1664         #   - the test control file says so
1665         #       (assuming we have any tests)
1666
1667         class NeedBuildException: pass
1668         def build_needed(m):
1669                 debug_b('build needed for %s' % m)
1670                 raise NeedBuildException()
1671
1672         try:
1673                 if filter != '_' and (act.ah['deb_forbuilds'] != 'ignore' or
1674                                       act.ah['deb_fortests'] != 'ignore'):
1675                         build_needed('binaries')
1676
1677                 result_pwd_af = InputDir(what+'-treeforcontrol',
1678                         result_pwd, True)
1679                 stanzas = read_control(act, result_pwd_af, control_override)
1680                 for stanza in stanzas:
1681                         for t in stanza[' tests']:
1682                                 if 'build-needed' in t.restriction_names:
1683                                         build_needed('test %s' % t.tname)
1684
1685                 debug_b('build not needed')
1686                 built = False
1687
1688         except NeedBuildException:
1689
1690                 if act.kind != 'dsc':
1691                         testbed.prepare2([])
1692
1693                 testbed.satisfy_dependencies_string('build-essential',
1694                                 'install build-essential')
1695                 testbed.satisfy_dependencies_dsc(dsc, 'build dependencies')
1696
1697                 script = tmpdir_script + [
1698                         'cd "$2"',
1699                         'dpkg-checkbuilddeps',
1700                         opts.user_wrap('debian/rules build'),
1701                         ]
1702                 source_rules_command(act,script,what,'build',work,
1703                                 cwd=initcwd, xargs=['x',tmpdir,result_pwd])
1704
1705                 if os.path.dirname(result_pwd)+'/' != work.read(True):
1706                         badpkg("results dir `%s' is not in expected parent"
1707                                " dir `%s'" % (result_pwd, work.read(True)))
1708
1709                 built = True
1710
1711         act.tests_tree = RelativeInputDir(what+'-tests-tree',
1712                                 work, os.path.basename(result_pwd),
1713                                 True)
1714
1715         if not built:
1716                 act.blamed = []
1717                 return
1718
1719         act.blamed = copy.copy(testbed.blamed)
1720
1721         debug_b('filter=%s' % filter)
1722         if filter != '_':
1723                 script = tmpdir_script + [
1724                         'cd '+work.write(True)+'/[a-z0-9]*-*/.',
1725                         opts.user_wrap(opts.gainroot+' debian/rules binary'),
1726                         'cd ..',
1727                         'echo *.deb >&3',
1728                         ]
1729                 result_debs = source_rules_command(act,script,what,
1730                                 'binary',work,work.write(True),
1731                                 results_lines=1, xargs=['x',tmpdir])
1732                 if result_debs == '*.deb': debs = []
1733                 else: debs = result_debs.split(' ')
1734                 debug_b('debs='+`debs`)
1735                 re = regexp.compile('^([-+.0-9a-z]+)_[^_/]+(?:_[^_/]+)\.deb$')
1736                 for deb in debs:
1737                         m = re.match(deb)
1738                         if not m: badpkg("badly-named binary `%s'" % deb)
1739                         pkg = m.groups()[0]
1740                         debug_b(' deb=%s, pkg=%s' % (deb,pkg))
1741                         for pat in filter.split(','):
1742                                 debug_b('  pat=%s' % pat)
1743                                 if not fnmatch.fnmatchcase(pkg,pat):
1744                                         debug_b('   no match')
1745                                         continue
1746                                 deb_what = pkg+'_'+what+'.deb'
1747                                 bin = RelativeInputFile(deb_what,work,deb,True)
1748                                 debug_b('  deb_what=%s, bin=%s' %
1749                                         (deb_what, str(bin)))
1750                                 binaries.register(act,pkg,bin,
1751                                         'forbuilds',testbed.blamed)
1752                                 act.binaries.append((pkg,bin))
1753                                 break
1754                 debug_b('all done.')
1755
1756 #---------- main processing loop and main program
1757
1758 def process_actions():
1759         global binaries
1760
1761         def debug_a1(m): debug('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ '+m)
1762         def debug_a2(m): debug('@@@@@@@@@@@@@@@@@@@@ '+m)
1763         def debug_a3(m): debug('@@@@@@@@@@ '+m)
1764
1765         debug_a1('starting')
1766         testbed.open()
1767         binaries = Binaries(testbed)
1768
1769         binaries.reset()
1770         control_override = None
1771
1772         debug_a1('builds ...')
1773         for act in opts.actions:
1774                 debug_a2('%s %s' %
1775                         (act.kind, act.what))
1776
1777                 if act.kind == 'control':
1778                         control_override = act.af
1779                 if act.kind == 'deb':
1780                         testbed.blame('arg:'+act.af.spec)
1781                         determine_package(act)
1782                         testbed.blame('deb:'+act.pkg)
1783                         binaries.register(act,act.pkg,act.af,
1784                                 'forbuilds',testbed.blamed)
1785                 if act.kind == 'dsc' or act.kind == 'ubtree':
1786                         build_source(act, control_override)
1787                 if act.kind == 'tree':
1788                         act.binaries = []
1789                 if act.kind.endswith('tree') or act.kind == 'dsc':
1790                         control_override = None
1791                 if act.kind == 'instantiate':
1792                         pass
1793
1794         debug_a1('builds done.')
1795
1796         binaries.reset()
1797         control_override = None
1798
1799         debug_a1('tests ...')
1800         for act in opts.actions:
1801                 debug_a2('test %s %s' % (act.kind, act.what))
1802
1803                 testbed.needs_reset()
1804                 if act.kind == 'control':
1805                         control_override = act.af
1806                 if act.kind == 'deb':
1807                         binaries.register(act,act.pkg,act.af,'fortests',
1808                                 ['deb:'+act.pkg])
1809                 if act.kind == 'dsc' or act.kind == 'ubtree':
1810                         for (pkg,bin) in act.binaries:
1811                                 binaries.register(act,pkg,bin,'fortests',
1812                                         act.blamed)
1813                 if act.kind == 'dsc':
1814                         if act.ah['dsc_tests']:
1815                                 debug_a3('read control ...')
1816                                 stanzas = read_control(act, act.tests_tree,
1817                                                 control_override)
1818                                 testbed.blamed += act.blamed
1819                                 debug_a3('run_tests ...')
1820                                 run_tests(stanzas, act.tests_tree)
1821                         control_override = None
1822                 if act.kind == 'tree' or act.kind == 'ubtree':
1823                         testbed.blame('arg:'+act.af.spec)
1824                         stanzas = read_control(act, act.af, control_override)
1825                         debug_a3('run_tests ...')
1826                         run_tests(stanzas, act.af)
1827                         control_override = None
1828                 if act.kind == 'instantiate':
1829                         testbed.prepare([])
1830         debug_a1('tests done.')
1831
1832 def main():
1833         global testbed
1834         global tmp
1835         try:
1836                 parse_args()
1837         except SystemExit, se:
1838                 os._exit(20)
1839         try:
1840                 setup_trace()
1841                 testbed = Testbed()
1842                 testbed.start()
1843                 finalise_options()
1844                 process_actions()
1845         except:
1846                 ec = print_exception(sys.exc_info(), '')
1847                 cleanup()
1848                 os._exit(ec)
1849         cleanup()
1850         os._exit(errorcode)
1851
1852 main()