3 # adt-run is part of autopkgtest
4 # autopkgtest is a tool for testing Debian binary packages
6 # autopkgtest is Copyright (C) 2006 Canonical Ltd.
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 # See the file CREDITS for a full list of credits information (often
23 # installed as /usr/share/doc/autopkgtest/CREDITS).
39 from optparse import OptionParser
40 signal.signal(signal.SIGINT, signal.SIG_DFL) # undo stupid Python SIGINT thing
42 #---------- global variables
44 tmpdir = None # pathstring on host
45 testbed = None # Testbed
46 errorcode = 0 # exit status that we are going to use
47 binaries = None # Binaries (.debs we have registered)
49 #---------- errors we define
52 def __init__(q,ec,m): q.ec = ec; q.m = m
54 def bomb(m): raise Quit(20, "unexpected error: %s" % m)
56 print 'blame: ', ' '.join(testbed.blamed)
57 raise Quit(12, "erroneous package: %s" % m)
59 def report(tname, result): print '%-20s %s' % (tname, result)
62 def __init__(u, lno, m):
63 if lno >= 0: u.m = '%s (control line %d)' % (m, lno)
68 report(tname, 'SKIP %s' % u.m)
72 if not opts.debug: return
73 print >>sys.stderr, 'atd-run: debug:', m
76 return reduce((lambda a,b: a + b), l, [])
78 #---------- fancy automatic file-copying class
82 # p.path[tb] None or path not None => path known
83 # p.file[tb] None or path not None => file exists
85 # p.spec_tb True or False, None iff p.spec is None
88 def __init__(p, what):
93 def subpath(p, what, leaf, constructor):
94 if not p.dir: error "creating subpath of non-directory"
95 return constructor(what, p.spec+'/'+leaf, p.spec_tb)
96 def invalidate(p, tb=False):
99 class InputFile(Path):
100 def __init__(p, what, spec, spec_tb=False):
101 AutoFile.__init__(p, what)
104 p.path[spec_tb] = p.file[spec_tb] = spec
106 class InputDir(Path):
107 def __init__(p, what, spec, spec_tb=False):
108 InputFile.__init__(p,what,spec,spec_tb)
111 class OutputFile(Path):
112 def __init__(p, what, spec, spec_tb=False):
113 AutoFile.__init__(p, what)
116 p.path[spec_tb] = spec
118 class OutputDir(Path):
119 def __init__(p, what, spec, spec_tb=False):
120 OutputFile.__init__(p,what,spec,spec_tb)
123 class TemporaryFile(Path):
124 def __init__(p, what):
126 OutputFile.__init__(p,what, testbed.scratch
128 def ensure_path(p, tb=False):
129 if tb and not p.spec_tb:
130 if not testbed.scratch:
131 error "called ensure_path for `%s' when testbed closed"
133 if not p.tb_scratch or p.tb_scratch is not testbed.scratch:
136 if p.path[tb] is not None: return
137 if tb: p.path[tb] = p.tb_tmpdir
138 else: p.path[tb] = tmpdir
139 p.path[tb] += '/'+p.what
141 def ensure_file(p, tb=False):
142 if p.file[tb] is not None: return
147 def write(p, tb=False):
150 def read(p, tb=False):
159 def __init__(p, path, spec_tb, what, dir=False):
162 bomb("path %s specified as being in testbed but"
163 " not absolute: `%s'" % (what, p.p))
164 p.path[spec_tb] = p.file[spec_tb] = path
175 4 def __init__(p, tb, path, what, dir=False, tbscratch=None, xfmap=None
181 p.tbscratch = tbscratch
185 bomb("path %s specified as being in testbed but"
186 " not absolute: `%s'" % (what, p.p))
192 if p.dir: p.dirsfx = '/'
195 return p.p + p.dirsfx
196 def append(p, suffix, what, dir=False):
197 return Path(p.tb, p.path() + suffix, what=what, dir=dir,
198 tbscratch=p.tbscratch)
200 if p.tb: pfx = '/VIRT'
201 elif p.p[:1] == '/': pfx = '/HOST'
205 def xfmapcopy(p, cud, dstdir):
206 if p.xfmap is None: return
207 srcdir = os.path.dirname(p.path()+'/')
208 dstdir = p.xfmapdstdir+'/'
209 for f in p.xfmap(file(p.local)):
210 if '/' in f: bomb("control file %s mentions other filename"
211 "containing slash" % p.what)
212 testbed.command(cud, (srcdir+f, dstdir+f))
214 def onhost(p, lpath = None):
215 if lpath is not None:
216 if p.lpath is not None: assert(p.lpath == lpath)
218 if p.local is not None:
219 if p.lpath is not None: assert(p.local == p.lpath)
224 if p.local is None: p.local = tmpdir + '/tb-' + p.what
226 assert(p.lpath is None)
228 p.xfmapdstdir = tmpdir + '/tbd-' + p.what
229 os.mkdir(p.xfmapdstdir)
230 p.local = p.xfmapdstdir + '/' + os.path.basename(p.down)
232 testbed.command('copyup', (p.path(), p.local + p.dirsfx))
233 p.xfmapcopy('copyup')
238 if p.lpath is None: return None
243 if p.tbscratch is not None:
244 if p.tbscratch != testbed.scratch:
246 if p.down is not None: return p.down
248 bomb("testbed scratch path " + str(p) + " survived testbed")
251 p.down = testbed.scratch.p + '/host-' + p.what
254 p.xfmapdstdir = testbed.scratch.p + '/hostd-' + p.what
255 testbed.command('mkdir '+p.xfmapdstdir)
256 p.down = p.xfmapdstdir + '/' + os.path.basename(p.local)
258 p.tbscratch = testbed.scratch
259 testbed.command('copydown', (p.path(), p.down + p.dirsfx))
260 p.xfmapcopy('copydown')
263 #---------- parsing and representation of the arguments
266 def __init__(a, kind, af, arghandling, what):
267 # extra attributes get added during processing
275 usage = "%prog <options> -- <virt-server>..."
276 parser = OptionParser(usage=usage)
277 pa = parser.add_option
278 pe = parser.add_option
283 'deb_forbuilds': 'auto',
284 'deb_fortests': 'auto',
286 'override_control': None
288 initial_arghandling = arghandling.copy()
292 # actions (ie, test sets to run, sources to build, binaries to use):
294 def cb_action(op,optstr,value,parser, long,kindpath,is_act):
295 parser.largs.append((value,kindpath))
298 def pa_action(long, metavar, kindpath, help, is_act=True):
299 pa('','--'+long, action='callback', callback=cb_action,
300 nargs=1, type='string',
301 callback_args=(long,kindpath,is_act), help=help)
303 pa_action('build-tree', 'TREE', '@/',
304 help='run tests from build tree TREE')
306 pa_action('source', 'DSC', '@.dsc',
307 help='build DSC and use its tests and/or'
308 ' generated binary packages')
310 pa_action('binary', 'DEB', '@.deb',
311 help='use binary package DEB according'
312 ' to most recent --binaries-* settings')
314 pa_action('override-control', 'CONTROL', ('control',), is_act=0,
315 help='run tests from control file CONTROL instead,
316 ' (applies to next test suite only)')
319 # argument handling settings (what ways to use action
320 # arguments, and pathname processing):
322 def cb_setah(option, opt_str, value, parser, toset,setval):
323 if type(setval) == list:
324 if not value in setval:
325 parser.error('value for %s option (%s) is not '
326 'one of the permitted values (%s)' %
327 (value, opt_str, setval.join(' ')))
328 elif setval is not None:
331 arghandling[v] = value
332 parser.largs.append(arghandling.copy())
334 def pa_setah(long, affected,effect, **kwargs):
335 type = metavar; if type: type = 'string'
336 pa('',long, action='callback', callback=cb_setah,
337 callback_args=(affected,effect), **kwargs)
338 ' according to most recent --binaries-* settings')
340 #---- paths: host or testbed:
342 pa_setah('--paths-testbed', ['tb'],True,
343 help='subsequent path specifications refer to the testbed')
344 pa_setah('--paths-host', ['tb'],False,
345 help='subsequent path specifications refer to the host')
347 #---- source processing settings:
349 pa_setah('--sources-tests', ['dsc_tests'],True,
350 help='run tests from builds of subsequent sources')
351 pa_setah('--sources-no-tests', ['dsc_tests'],False,
352 help='do not run tests from builds of subsequent sources')
354 pa_setah('--built-binaries-filter', ['dsc_filter'],None,
355 type=string, metavar='PATTERN-LIST',
356 help='from subsequent sources, use binaries matching'
357 ' PATTERN-LIST (comma-separated glob patterns)'
358 ' according to most recent --binaries-* settings')
359 pa_setah('--no-built-binaries', ['dsc_filter'], '_',
360 help='from subsequent sources, do not use any binaries')
362 #---- binary package processing settings:
364 def pa_setahbins(long,toset,how):
365 pa_setah(long, toset,['ignore','auto','install'],
366 type=string, metavar='IGNORE|AUTO|INSTALL', default='auto',
367 help=how+' ignore binaries, install them as needed'
368 ' for dependencies, or unconditionally install'
369 ' them, respectively')
370 pa_setahbins('--binaries', ['deb_forbuilds','deb_fortests'], '')
371 pa_setahbins('--binaries-forbuilds', ['deb_forbuilds'], 'for builds, ')
372 pa_setahbins('--binaries-fortests', ['deb_fortests'], 'for tests, ')
377 def cb_vserv(op,optstr,value,parser):
378 parser.values.vserver = list(parser.rargs)
381 def cb_path(op,optstr,value,parser, constructor,long,dir):
382 name = long.replace('-','_')
383 af = constructor(arghandling['tb'], value, long, dir)
384 setattr(parser.values, name, af)
386 def pa_path(long, constructor, help, dir=False):
387 pa('','--'+long, action='callback', callback=cb_path,
388 callback_args=(constructor,long,dir),
389 nargs=1, type='string',
390 help=help, metavar='PATH')
392 pa_path('output-dir', OutputDir, dir=True,
393 help='write stderr/out files in PATH')
394 pa_path('tmp-dir', OutputDir, dir=True,
395 help='write temporary files to PATH, emptying PATH'
396 ' beforehand and leaving it behind at the end')
398 pa('','--user', type='string', dest='user',
399 help='run tests as USER (needs root on testbed)')
400 pa('','--fakeroot', type='string', dest='fakeroot',
401 help='prefix debian/rules build with FAKEROOT')
402 pa('-d', '--debug', action='store_true', dest='debug');
403 pa('','--gnupg-home', type='string', dest='gnupghome',
404 default='~/.autopkgtest/gpg',
405 help='use GNUPGHOME rather than ~/.autopkgtest (for
406 " signing private apt archive);"
407 " `fresh' means generate new key each time.")
412 class SpecialOption(optparse.Option): pass
413 vs_op = SpecialOption('','--VSERVER-DUMMY')
414 vs_op.action = 'callback'
418 vs_op.callback = cb_vserv
419 vs_op.callback_args = ( )
420 vs_op.callback_kwargs = { }
421 vs_op.help = 'introduces virtualisation server and args'
422 vs_op._short_opts = []
423 vs_op._long_opts = ['---']
427 (opts,args) = parser.parse_args()
428 if not hasattr(opts,'vserver'):
429 parser.error('you must specifiy --- <virt-server>...')
431 parser.error('nothing to do specified')
433 arghandling = initial_arghandling
437 if type(act) == dict:
440 elif type(act) == tuple:
442 elif type(act) == string:
445 error "unknown action in list `%s' having"
446 "type `%s' % (act, type(act))
447 (pathstr, kindpath) = act
449 constructor = InputPath
450 if type(kindpath) is tuple: kind = kindpath[0]
451 elif kindpath.endswith('.deb'): kind = 'deb'
452 elif kindpath.endswith('.dsc'): kind = 'dsc'
453 elif kindpath.endswith('/'):
455 constructor = InputPathDir
456 else: parser.error("do not know how to handle filename \`%s';"
457 " specify --source --binary or --build-tree")
459 what = '%s%s' % (kind,ix); ix++
461 af = constructor(what+'-'+kind, pathstr, arghandling['tb'])
462 opts.actions.append(Action(kind, af, arghandling, ix))
464 def finalise_options():
467 if opts.user is None and 'root-on-testbed' not in caps:
470 if opts.user is None:
471 su = 'suggested-normal-user='
478 print >>sys.stderr, "warning: virtualisation"
479 " system offers several suggested-normal-user"
480 " values: "+('/'.join(ul))+", using "+ul[0]
487 if 'root-on-testbed' not in caps:
488 print >>sys.stderr, "warning: virtualisation"
489 " system does not offer root on testbed,"
490 " but --user option specified: failure likely"
491 opts.user_wrap = lambda x: 'su %s -c "%s"' % (opts.user, x)
493 opts.user_wrap = lambda x: x
495 if opts.fakeroot is None:
498 'root-on-testbed' not in testbed.caps:
499 opts.fakeroot = 'fakeroot'
501 if opts.gnupghome.startswith('~/'):
502 try: home = os.environ['HOME']
504 parser.error("HOME environment variable"
505 " not set, needed for --gnupghome=`%s"
507 opts.gnupghome = home + opts.gnupghome[1:]
508 elif opts.gnupghome == 'fresh':
509 opts.gnupghome = None
511 #---------- testbed management - the Testbed class
522 tb.sp = subprocess.Popen(opts.vserver,
523 stdin=p, stdout=p, stderr=None)
525 tb.caps = tb.command('capabilities')
528 if tb.sp is None: return
529 ec = tb.sp.returncode
536 tb.bomb('testbed gave exit status %d after quit' % ec)
538 if tb.scratch is not None: return
539 p = tb.commandr1('open')
540 tb.scratch = OutputDir('tb-scratch', p, True)
542 if tb.scratch is None: return
544 if tb.sp is None: return
547 if tb.modified and 'reset' in caps:
551 binaries.publish(act)
557 if tb.sp is not None:
561 if ec: print >>sys.stderr, ('adt-run: testbed failing,'
562 ' exit status %d' % ec)
564 raise Quit(16, 'testbed failed: %s' % m)
565 def send(tb, string):
569 print >>tb.sp.stdin, string
573 (type, value, dummy) = sys.exc_info()
574 tb.bomb('cannot send to testbed: %s' % traceback.
575 format_exception_only(type, value))
576 def expect(tb, keyword, nresults=-1):
577 l = tb.sp.stdout.readline()
578 if not l: tb.bomb('unexpected eof from the testbed')
579 if not l.endswith('\n'): tb.bomb('unterminated line from the testbed')
583 if not ll: tb.bomb('unexpected whitespace-only line from the testbed')
585 if tb.lastsend is None:
586 tb.bomb("got banner `%s', expected `%s...'" %
589 tb.bomb("sent `%s', got `%s', expected `%s...'" %
590 (tb.lastsend, l, keyword))
592 if nresults >= 0 and len(ll) != nresults:
593 tb.bomb("sent `%s', got `%s' (%d result parameters),"
594 " expected %d result parameters" %
595 (string, l, len(ll), nresults))
597 def commandr(tb, cmd, nresults, args=()):
598 if type(cmd) is str: cmd = [cmd]
599 al = cmd + map(urllib.quote, args)
600 tb.send(string.join(al))
602 rl = map(urllib.unquote, ll)
604 def command(tb, cmd, args=()):
605 tb.commandr(cmd, 0, args)
606 def commandr1(tb, cmd, args=()):
607 rl = tb.commandr(cmd, 1, args)
610 #---------- representation of test control files: Field*, Test, etc.
613 def __init__(f, fname, stz, base, tnames, vl):
623 r = map((lambda w: (lno, w)), r)
625 return flatten(map(distribute, f.vl))
630 raise Unsupported(f.vl[1][0],
631 'only one %s field allowed' % fn)
634 class FieldIgnore(FieldBase):
638 def __init__(r,rname,base): pass
640 class Restriction_rw_tests_tree(Restriction): pass
641 class Restriction_breaks_testbed(Restriction):
642 if 'reset' not in caps:
643 raise Unsupported(f.lno,
644 'Test breaks testbed but testbed cannot reset')
646 class Field_Restrictions(FieldBase):
648 for wle in f.words():
650 rname = rname.replace('-','_')
651 try: rclass = globals()['Restriction_'+rname]
652 except KeyError: raise Unsupported(lno,
653 'unknown restriction %s' % rname)
654 r = rclass(rname, f.base)
655 f.base['restrictions'].append(r)
657 class Field_Tests(FieldIgnore): pass
659 class Field_Tests_directory(FieldBase):
662 if td.startswith('/'): raise Unspported(f.lno,
663 'Tests-Directory may not be absolute')
664 base['testsdir'] = td
666 def run_tests(stanzas):
668 for stanza in stanzas:
669 tests = stanza[' tests']
671 report('*', 'SKIP no tests in this package')
676 if 'breaks-testbed' in t.restrictions:
677 testbed.needs_reset()
678 testbed.needs_reset()
681 def __init__(t, tname, base):
682 if '/' in tname: raise Unsupported(base[' lno'],
683 'test name may not contain / character')
684 for k in base: setattr(t,k,base[k])
686 if len(base['testsdir']): tpath = base['testsdir'] + '/' + tname
688 t.af = opts.tests_tree.subpath('test-'+tname, tpath, InputFile)
691 def reportfail(t, m):
694 report(t.tname, 'FAIL ' + m)
697 idstr = oe + '-' + t.tname
698 if opts.output_dir is not None and opts.output_dir.tb:
699 use_dir = opts.output_dir
701 use_dir = testbed.scratch
702 return use_dir.subpath(idstr, idstr, OutputFile)
703 def stdouterrh(p, oe):
704 idstr = oe + '-' + t.tname
705 if opts.output_dir is None or opts.output_dir.tb:
708 return p.onhost(opts.output_dir.onhost() + '/' + idstr)
709 so = stdouterr('stdout')
710 se = stdouterr('stderr')
711 rc = testbed.commandr1('execute',(t.af.ontb(),
712 '/dev/null', so.ontb(), se.ontb(), opts.tests_tree.ontb()))
713 soh = stdouterrh(so, 'stdout')
714 seh = stdouterrh(se, 'stderr')
717 if stab.st_size != 0:
718 l = file(seh).readline()
719 l = l.rstrip('\n \t\r')
720 if len(l) > 40: l = l[:40] + '...'
721 t.reportfail('stderr: %s' % l)
723 t.reportfail('non-zero exit status %d' % rc)
727 def read_control(act, tree, control_override):
730 if control_override is not None:
731 control_af = control_override
732 testbed.blame('arg:'+control_override.spec)
734 control_af = tree.subpath(act.what+'-testcontrol',
735 'debian/tests/control', InputFile)
736 testbed.blame('arg:'+tree.spec)
739 control = file(control_af.read(), 'r')
741 if oe[0] != errno.ENOENT: raise
745 def badctrl(m): act.bomb('tests/control line %d: %s' % (lno, m))
746 stz = None # stz[field_name][index] = (lno, value)
747 # special field names:
748 # stz[' lno'] = number
749 # stz[' tests'] = list of Test objects
751 if stz is None: return
757 initre = regexp.compile('([A-Z][-0-9a-z]*)\s*\:\s*(.*)$')
759 l = control.readline()
762 if not l.endswith('\n'): badctrl('unterminated line')
763 if regexp.compile('\s*\#').match(l): continue
764 if not regexp.compile('\S').match(l): end_stanza(stz); continue
765 initmat = initre.match(l)
767 (fname, l) = initmat.groups()
768 fname = string.capwords(fname)
770 stz = { ' lno': lno, ' tests': [] }
771 if not stz.has_key(fname): stz[fname] = [ ]
772 hcurrent = stz[fname]
773 elif regexp.compile('\s').match(l):
774 if not hcurrent: badctrl('unexpected continuation')
776 badctrl('syntax error')
777 hcurrent.append((lno, l))
780 def testbadctrl(stz, lno, m):
781 report_badctrl(lno, m)
786 try: tnames = stz['Tests']
789 raise Unsupported(stz[' lno'],
791 tnames = map((lambda lt: lt[1]), tnames)
792 tnames = string.join(tnames).split()
795 'testsdir': 'debian/tests'
797 for fname in stz.keys():
798 if fname.startswith(' '): continue
800 try: fclass = globals()['Field_'+
801 fname.replace('-','_')]
802 except KeyError: raise Unsupported(vl[0][0],
803 'unknown metadata field %s' % fname)
804 f = fclass(stz, fname, base, tnames, vl)
807 t = Test(tname, base)
808 stz[' tests'].append(t)
809 except Unsupported, u:
810 for tname in tnames: u.report(tname)
815 def print_exception(ei, msgprefix=''):
816 if msgprefix: print >>sys.stderr, msgprefix
819 print >>sys.stderr, 'adt-run:', q.m
822 print >>sys.stderr, "adt-run: unexpected, exceptional, error:"
823 traceback.print_exc()
829 if tmpdir is not None:
830 shutil.rmtree(tmpdir)
831 if testbed is not None:
833 if rm_ec: bomb('rm -rf -- %s failed, code %d' % (tmpdir, ec))
835 print_exception(sys.exc_info(),
836 '\nadt-run: error cleaning up:\n')
839 #---------- registration, installation etc. of .deb's: Binaries
841 def determine_package(act):
842 cmd = 'dpkg-deb --info --'.split(' ')+[act.af.read(),'control']
843 running = Popen(cmd, stdout=PIPE)
844 output = running.communicate()[0]
846 if rc: badpkg('failed to parse binary package, code %d' % rc)
847 re = regexp.compile('^\s*Package\s*:\s*([0-9a-z][-+.0-9a-z]*)\s*$')
849 for l in '\n'.split(output):
852 if act.pkg: badpkg('two Package: lines in control file')
854 if not act.pkg: badpkg('no good Package: line in control file')
858 b.dir = TemporaryDir('binaries')
860 if opts.gnupghome is None:
861 opts.gnupghome = tmpdir+'/gnupg'
864 for x in ['pubring','secring']:
865 os.stat(opts.gnupghome + '/' + x + '.gpg')
867 if oe.errno != errno.ENOENT: raise
869 try: os.mkdir(opts.gnupghome, 0700)
870 except IOError, oe: if oe.errno != errno.EEXIST: raise
873 exec >key-gen-log 2>&1
874 cat <<"END" >key-gen-params
878 Name-Real: autopkgtest per-run key
879 Name-Comment: do not trust this key
880 Name-Email: autopkgtest@example.com
883 gpg --homedir="$1" --batch --gen-key key-gen-params
885 cmdl = ['sh','-ec',script,'x',opts.gnupghome]
886 rc = subprocess.call(cmdl)
889 f = open(opts.gnupghome+'/key-gen-log')
891 except IOError, e: tp = e
892 print >>sys.stderr, tp
893 bomb('key generation failed, code %d' % rc)
896 shutil.rmtree(b.dir.read())
901 def register(b, act, pkg, af, forwhat, blamed):
902 if act.ah['deb_'+forwhat] == 'ignore': return
904 b.blamed += testbed.blamed
906 leafname = pkg+'.deb'
907 dest = b.dir.subpath('binaries--'+leafname, leafname, OutputFile)
909 try: os.remove(dest.write())
911 if oe.errno != errno.ENOENT: raise e
913 try: os.link(af.read(), dest.write())
915 if oe.errno != errno.EXDEV: raise e
916 shutil.copy(af.read(), dest)
918 if act.ah['deb_'+forwhat] == 'install':
919 b.install.append(pkg)
924 apt-ftparchive packages . >Packages
926 apt-ftparchive release . >Release
927 gpg --homedir="$2" --batch --detach-sign --armour -o Release.gpg Release
928 gpg --homedir="$2" --batch --export >archive-key.pgp
930 cmdl = ['sh','-ec',script,'x',b.dir.write(),opts.gnupghome]
931 rc = subprocess.call(cmd)
932 if rc: bomb('apt-ftparchive or signature failed, code %d' % rc)
934 b.dir.invalidate(True)
935 apt_source = b.dir.read(True)
937 se = TemporaryFile('%s-aptkey-stderr' % act.what)
939 apt-key add archive-key.pgp
940 echo "deb file:///'+apt_source+'/ /" >/etc/apt/sources.list.d/autopkgtest
942 rc = testbed.commandr1(['execute',
943 ','.join(map(urllib.quote, ['sh','-ec','script']))],
944 '/dev/null', '/dev/null', se.write(True), tbp)
945 if rc: bomb('apt setup failed with exit code %d' % rc, se)
947 testbed.blamed += b.blamed
949 for pkg in b.install:
951 se = TemporaryFile('%s-install-%s-stderr' % (act.what,pkg))
952 rc = testbed.commandr1('execute','apt-get,-qy,install,'+pkg,
953 '/dev/null','/dev/null',se.ontb(),
954 testbed.scratch.read(True))
956 badpkg("installation of %s failed, exit code %d"
959 #---------- processing of sources (building)
961 def source_rules_command(act,script,which,work,results_lines=0):
962 script = "exec 3>&1 >&2\n" + '\n'.join(script)
963 so = TemporaryFile('%s-%s-results' % (what,which))
964 se = TemporaryFile('%s-%s-log' & (what,which))
965 rc = testbed.commandr1(['execute',
966 ','.join(map(urllib.quote, ['sh','-xec',script]))],
967 '/dev/null', so.write(True), se.write(True), work.write(True))
968 results = file(so.read()).read().split("\n")
970 badpkg_se("%s failed with exit code %d" % (which,rc), se)
971 if results_lines is not None and len(results) != results_lines:
972 badpkg_se("got %d lines of results from %s where %d expected"
973 % (len(results), which, results_lines), se)
974 if results_lines==1: return results[0]
977 def build_source(act):
978 act.blame = 'arg:'+act.af.spec()
979 testbed.blame(act.blame)
980 testbed.needs_reset()
982 what = act.ah['what']
984 basename = dsc.spec; if basename is None: basename = 'source.dsc'
985 dsc_what = what+'/'+basename
987 dsc_file = open(dsc.read())
989 fre = regexp.compile('^\s+[0-9a-f]+\s+\d+\s+([^/.][^/]*)$')
991 if l.startswith('Files:'): in_files = True
992 elif l.startswith('#'): pass
993 elif not l.startswith(' '):
995 if l.startswith('Source:'):
996 act.blame = 'dsc:'+l[7:].strip()
997 testbed.blame(act.blame)
998 elif not in_files: pass
999 if not dsc.spec_tb: continue
1001 if not m: badpkg(".dsc contains unparseable line"
1002 " in Files: `%s'" % (`dsc`,l))
1003 subfile = dsc.enclosingdir().subpath(
1004 dsc_what+'/'+m.groups(0), m.groups(0),
1009 work = TemporaryDir(what+'-build')
1012 'cd '+work.write(True),
1013 'gdebi '+dsc.read(True),
1014 'dpkg-source -x '+dsc.read(True),
1017 opts.user_wrap('debian/rules build'),
1019 result_pwd = source_rules_command(act,script,what,'build',work,1)
1021 if os.path.dirname(result_pwd) != work.read(True):
1022 badpkg_se("results dir `%s' is not in expected parent dir `%s'"
1023 % (results[0], work.read(True)), se)
1025 act.tests_tree = InputDir(dsc_what+'tests-tree',
1026 work.read(True)+os.path.basename(results[0]),
1028 if act.ah['dsc_tests']:
1029 act.tests_tree.read()
1030 act.tests_tree.invalidate(True)
1032 act.blamed = testbed.blamed.copy()
1035 if act.ah['dsc_filter'] != '_':
1037 'cd '+work.write(True)+'/*/.',
1038 opts.user_wrap(opts.fakeroot+' debian/rules binary'),
1042 result_debs = source_rules_command(act,script,what,
1043 'debian/rules binary',work,1)
1044 if result_debs == '*': debs = []
1045 else: debs = debs.split(' ')
1046 re = regexp.compile('^([-+.0-9a-z]+)_[^_/]+(?:_[^_/]+)\.deb$')
1049 if not m: badpkg("badly-named binary `%s'" % deb)
1051 for pat in act.ah['dsc_filter'].split(','):
1052 if fnmatch.fnmatchcase(pkg,pat):
1053 deb_af = work.read()+'/'+deb
1054 deb_what = pkg+'_'+what+'.deb'
1055 bin = InputFile(deb_what,deb_af,True)
1057 binaries.register(act,pkg,bin,'builds',
1059 act.binaries.subpath((pkg,bin))
1062 #---------- main processing loop and main program
1064 def process_actions():
1066 binaries = Binaries()
1069 for act in opts.actions:
1071 if act.kind == 'deb':
1072 blame('arg:'+act.af.spec)
1073 determine_package(act)
1074 blame('deb:'+act.pkg)
1075 binaries.register(act,act.pkg,act.af,'builds',
1077 if act.kind == 'dsc':
1081 control_override = None
1082 for act in opts.actions:
1084 if act.kind == 'control':
1085 control_override = act.af
1086 if act.kind == 'deb':
1087 binaries.register(act,act.pkg,act.af,'tests',
1089 if act.kind == 'dsc':
1090 for (pkg,bin) in act.binaries:
1091 binaries.register(act,pkg,bin,'tests',
1093 if not act.ah['dsc_tests']: continue
1094 stanzas = read_control(act, act.tests_tree,
1096 testbed.blamed += act.blamed
1097 run_tests(act, stanzas)
1098 control_override = None
1099 if act.kind == 'tree':
1100 testbed.blame('arg:'+act.af.spec)
1101 stanzas = read_control(act, act.af,
1103 run_tests(act, stanzas)
1104 control_override = None
1111 except SystemExit, se:
1114 tmpdir = tempfile.mkdtemp()
1120 ec = print_exception(sys.exc_info(), '')