X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=runner%2Fadt-run;h=7787e5761971fb566993e8e87bd40058ed5d01b4;hb=aa43f2a81adb120c1da99919c5b3523ea952d170;hp=aed221946e7af7fcbfc76aac69e0736b4ad88a86;hpb=411e1043b3fc508d1243f28787fa4f7c2a0f85a3;p=autopkgtest.git diff --git a/runner/adt-run b/runner/adt-run index aed2219..7787e57 100755 --- a/runner/adt-run +++ b/runner/adt-run @@ -45,7 +45,9 @@ signal.signal(signal.SIGINT, signal.SIG_DFL) # undo stupid Python SIGINT thing tmpdir = None # pathstring on host testbed = None # Testbed errorcode = 0 # exit status that we are going to use +timeouts = { 'short':100, 'install':3000, 'test':10000, 'build':100000 } binaries = None # Binaries (.debs we have registered) +build_essential = ["build-essential"] #---------- output handling # @@ -73,7 +75,12 @@ binaries = None # Binaries (.debs we have registered) # is done by forking off a copy of ourselves to do plumbing, # which copy we wait for at the appropriate point. +class DummyOpts: + def __init__(do): do.debuglevel = 0 + +opts = DummyOpts() trace_stream = None +summary_stream = None def pstderr(m): print >>sys.stderr, m @@ -82,7 +89,7 @@ def pstderr(m): def debug(m, minlevel=0): if opts.debuglevel < minlevel: return if opts.quiet and trace_stream is None: return - p = 'adt-run: debug' + p = 'adt-run: trace' if minlevel: p += `minlevel` p += ': ' for l in m.rstrip('\n').split('\n'): @@ -107,16 +114,19 @@ class Errplumb: ep.stream = open('/dev/null','w') ep._sp = None elif count == 1: - if to_stderr: ep.stream = sys.stderr + if to_stderr: ep.stream = os.dup(2) else: ep.stream = trace_stream ep._sp = None else: - ep._sp = subprocess.Popen(['tee','/dev/stderr'], + ep._sp = subprocess.Popen(['tee','-a','/dev/stderr'], stdin=subprocess.PIPE, stdout=trace_stream, close_fds=True) ep.stream = ep._sp.stdin def wait(ep): if ep._sp is None: return + if type(ep.stream) == type(2): + os.close(ep.stream) + ep.stream = () ep._sp.stdin.close() rc = ep._sp.wait() if rc: bomb('stderr plumbing tee(1) failed, exit code %d' % rc) @@ -134,9 +144,14 @@ def subprocess_cooked(cmdl, critical=False, dbg=None, **kwargs): ep.wait() return (rc, output) +def psummary(m): + if summary_stream is not None: print >>summary_stream, m + def preport(m): print m + sys.stdout.flush() if trace_stream is not None: print >>trace_stream, m + psummary(m) def report(tname, result): preport('%-20s %s' % (tname, result)) @@ -168,12 +183,14 @@ class Unsupported: def mkdir_okexist(pathname, mode=02755): try: os.mkdir(pathname, mode) - except OSError, oe: + except (IOError,OSError), oe: if oe.errno != errno.EEXIST: raise def rmtree(what, pathname): debug('/ %s rmtree %s' % (what, pathname), 2) - shutil.rmtree(pathname) + try: shutil.rmtree(pathname) + except (IOError,OSError), oe: + if oe.errno != errno.EEXIST: raise def debug_subprocess(what, cmdl=None, script=None): o = '$ '+what+':' @@ -220,7 +237,8 @@ class AutoFile: else: return p.path[tbp]+p.dir+'!' out = p.what if p.spec is not None: - if p.spec_tbp: out += '#' + if not hasattr(p,'spec_tb'): out += '~' + elif p.spec_tbp: out += '#' else: out += '=' out += p.spec out += ':' @@ -231,7 +249,8 @@ class AutoFile: def _wrong(p, how): xtra = '' - if p.spec is not None: xtra = ' spec[%s]=%s' % (p.spec, p.spec_tb) + if p.spec is not None: + xtra = ' spec[%s]=%s' % (p.spec, getattr(p,'spec_tb',None)) raise ("internal error: %s (%s)" % (how, str(p))) def _ensure_path(p, tbp): @@ -384,11 +403,13 @@ class Action: a.af = af a.ah = arghandling a.what = what + a.missing_tests_control = False def __repr__(a): return "" % (a.kind, a.what, `a.af`) def parse_args(): - global opts + global opts, timeouts + global n_non_actions # argh, stupid python scoping rules usage = "%prog --- ..." parser = OptionParser(usage=usage) pa = parser.add_option @@ -400,7 +421,8 @@ def parse_args(): 'deb_forbuilds': 'auto', 'deb_fortests': 'auto', 'tb': False, - 'override_control': None + 'override_control': None, + 'set_lang': 'C' } initial_arghandling = arghandling.copy() n_non_actions = 0 @@ -409,6 +431,7 @@ def parse_args(): # actions (ie, test sets to run, sources to build, binaries to use): def cb_action(op,optstr,value,parser, long,kindpath,is_act): + global n_non_actions parser.largs.append((value,kindpath)) n_non_actions += not(is_act) @@ -431,6 +454,15 @@ def parse_args(): help='use binary package DEB according' ' to most recent --binaries-* settings') + def cb_actnoarg(op,optstr,value,parser, largsentry): + parser.largs.append(largsentry) + pa('','--instantiate', action='callback', callback=cb_actnoarg, + callback_args=((None, ('instantiate',)),), + help='instantiate testbed now (during testing phase)' + ' and install packages' + ' selected for automatic installation, even' + ' if this might apparently not be required otherwise') + pa_action('override-control', 'CONTROL', ('control',), is_act=0, help='run tests from control file CONTROL instead,' ' (applies to next test suite only)') @@ -478,7 +510,6 @@ def parse_args(): ' according to most recent --binaries-* settings') pa_setah('--no-built-binaries', ['dsc_filter'], '_', help='from subsequent sources, do not use any binaries') - #---- binary package processing settings: def pa_setahbins(long,toset,how): @@ -500,7 +531,7 @@ def parse_args(): def cb_path(op,optstr,value,parser, constructor,long,dir): name = long.replace('-','_') - af = constructor(arghandling['tb'], value, long, dir) + af = constructor(long, value,arghandling['tb']) setattr(parser.values, name, af) def pa_path(long, constructor, help, dir=False): @@ -512,12 +543,27 @@ def parse_args(): pa_path('output-dir', OutputDir, dir=True, help='write stderr/out files in PATH') + pa('--leave-lang', dest='set_lang', action='store_false', + help="leave LANG on testbed set to testbed's default") + pa('--set-lang', dest='set_lang', action='store', metavar='LANGVAL', + help='set LANG on testbed to LANGVAL', default='C') + pa('','--tmp-dir', type='string', dest='tmpdir', help='write temporary files to TMPDIR, emptying it' ' beforehand and leaving it behind at the end') pa('','--log-file', type='string', dest='logfile', help='write the log LOGFILE, emptying it beforehand,' ' instead of using OUTPUT-DIR/log or TMPDIR/log') + pa('','--summary-file', type='string', dest='summary', + help='write a summary report to SUMMARY,' + ' emptying it beforehand') + + for k in timeouts.keys(): + pa('','--timeout-'+k, type='int', dest='timeout_'+k, + metavar='T', help='set %s timeout to T') + pa('','--timeout-factor', type='float', dest='timeout_factor', + metavar='FACTOR', default=1.0, + help='multiply all default timeouts by FACTOR') pa('','--user', type='string', dest='user', help='run tests as USER (needs root on testbed)') @@ -555,6 +601,11 @@ def parse_args(): if n_non_actions >= len(parser.largs): parser.error('nothing to do specified') + for k in timeouts.keys(): + t = getattr(opts,'timeout_'+k) + if t is None: t = timeouts[k] * opts.timeout_factor + timeouts[k] = int(t) + arghandling = initial_arghandling opts.actions = [] ix = 0 @@ -581,19 +632,21 @@ def parse_args(): elif kindpath.endswith('/'): kind = 'tree' constructor = InputDir - else: parser.error("do not know how to handle filename \`%s';" - " specify --source --binary or --build-tree") + else: parser.error("do not know how to handle filename `%s';" + " specify --source --binary or --build-tree" % + kindpath) what = '%s%s' % (kind,ix); ix += 1 if kind == 'dsc': fwhatx = '/' + os.path.basename(pathstr) else: fwhatx = '-'+kind - af = constructor(what+fwhatx, pathstr, arghandling['tb']) + if pathstr is None: af = None + else: af = constructor(what+fwhatx, pathstr, arghandling['tb']) opts.actions.append(Action(kind, af, arghandling, what)) def setup_trace(): - global trace_stream, tmpdir + global trace_stream, tmpdir, summary_stream if opts.tmpdir is not None: rmtree('tmpdir(specified)',opts.tmpdir) @@ -610,11 +663,13 @@ def setup_trace(): opts.logfile = opts.tmpdir + '/log' if opts.logfile is not None: trace_stream = open(opts.logfile, 'w', 0) + if opts.summary is not None: + summary_stream = open(opts.summary, 'w', 0) - debug('options: '+`opts`, 1) + debug('options: '+`opts`+'; timeouts: '+`timeouts`, 1) def finalise_options(): - global opts, tb + global opts, tb, build_essential if opts.user is None and 'root-on-testbed' not in testbed.caps: opts.user = '' @@ -645,6 +700,7 @@ def finalise_options(): if (opts.user or 'root-on-testbed' not in testbed.caps): opts.gainroot = 'fakeroot' + build_essential += ['fakeroot'] if opts.gnupghome.startswith('~/'): try: home = os.environ['HOME'] @@ -704,8 +760,8 @@ class Testbed: def reset_apt(tb): if not tb._need_reset_apt: return what = 'aptget-update-reset' - cmdl = ['apt-get','-qy','update'] - rc = tb.execute(what, cmdl) + cmdl = ['sh','-c','apt-get -qy update 2>&1'] + rc = tb.execute(what, cmdl, kind='install') if rc: pstderr("\n" "warning: failed to restore" " testbed apt cache, exit code %d" % rc) @@ -716,20 +772,26 @@ class Testbed: tb.scratch = None if tb.sp is None: return tb.command('close') - def prepare(tb, deps_new): - tb._debug('prepare, modified=%s, deps_processed=%s, deps_new=%s' % + def prepare1(tb, deps_new): + tb._debug('prepare1, modified=%s, deps_processed=%s, deps_new=%s' % (tb.modified, tb.deps_processed, deps_new), 1) if 'revert' in tb.caps and (tb.modified or [d for d in tb.deps_processed if d not in deps_new]): + for af in tb._ephemeral: af.read(False) tb._debug('reset **') tb.command('revert') tb.blamed = [] for af in tb._ephemeral: af.invalidate(True) - binaries.publish() tb.modified = False + def prepare2(tb, deps_new): + tb._debug('prepare2, deps_new=%s' % deps_new, 1) + binaries.publish() tb._install_deps(deps_new) + def prepare(tb, deps_new): + tb.prepare1(deps_new) + tb.prepare2(deps_new) def register_ephemeral(tb, af): - if not getattr(af,'spec_tbp',False): tb._ephemeral.append(af) + tb._ephemeral.append(af) def _install_deps(tb, deps_new): tb._debug(' installing dependencies '+`deps_new`, 1) tb.deps_processed = deps_new @@ -743,7 +805,8 @@ class Testbed: ]]) cmdl = ['python','-c',script] what = 'install-deps' - rc = testbed.execute(what+'-debinstall', cmdl, script=script) + rc = testbed.execute(what+'-debinstall', cmdl, script=script, + kind='install') if rc: badpkg('dependency install failed, exit code %d' % rc) def needs_reset(tb): tb._debug('needs_reset, previously=%s' % tb.modified, 1) @@ -810,7 +873,7 @@ class Testbed: return rl[0] def execute(tb, what, cmdl, si='/dev/null', so='/dev/null', se=None, cwd=None, - script=False): + script=False, tmpdir=None, kind='short'): # Options for script: # False - do not call debug_subprocess, no synch. reporting required # None or string - call debug_subprocess with that value, @@ -819,6 +882,8 @@ class Testbed: # None - usual Errplumb (output is of kind 2c) # string - path on testbed (output is of kind 2d) + timeout = timeouts[kind] + if script is not False: debug_subprocess(what, cmdl, script=script) if cwd is None: cwd = tb.scratch.write(True) @@ -838,7 +903,16 @@ class Testbed: cmdl = [None, ','.join(map(urllib.quote, cmdl)), si, so, se_use, cwd] + + if timeout is not None and timeout > 0: + cmdl.append('timeout=%d' % timeout) + if xdump is not None and 'execute-debug' in tb.caps: cmdl += [xdump] + if tmpdir is not None: cmdl.append('env=TMPDIR=%s' % tmpdir) + if kind=='install': cmdl.append('env=DEBIAN_FRONTEND=noninteractive') + if opts.set_lang is not False: + cmdl.append('env=LANG=%s' % opts.set_lang) + rc = tb.commandr1('execute', cmdl) try: rc = int(rc) except ValueError: bomb("execute for %s gave invalid response `%s'" @@ -902,17 +976,28 @@ class Field_Restrictions(FieldBase): except KeyError: raise Unsupported(lno, 'unknown restriction %s' % rname) r = rclass(nrname, f.base) + f.base['restriction_names'].append(rname) f.base['restrictions'].append(r) +class Field_Features(FieldIgnore): + def parse(f): + for wle in f.words(): + (lno, fname) = wle + f.base['feature_names'].append(fname) + nfname = fname.replace('-','_') + try: fclass = globals()['Feature_'+nfname] + except KeyError: continue + ft = fclass(nfname, f.base) + f.base['features'].append(ft) + class Field_Tests(FieldIgnore): pass -class Field_Features(FieldIgnore): pass class Field_Depends(FieldBase): def parse(f): print >>sys.stderr, "Field_Depends:", `f.stz`, `f.base`, `f.tnames`, `f.vl` dl = map(lambda x: x.strip(), flatten(map(lambda (lno, v): v.split(','), f.vl))) - re = regexp.compile('[^-.+:~0-9a-z()<>=*]') + re = regexp.compile('[^-.+:~0-9a-z()<>=*@]') for d in dl: if re.search(d): badpkg("Test Depends field contains dependency" @@ -928,15 +1013,18 @@ class Field_Tests_directory(FieldBase): def run_tests(stanzas, tree): global errorcode, testbed + if stanzas == (): + report('*', 'SKIP no tests in this package') + errorcode |= 8 for stanza in stanzas: tests = stanza[' tests'] if not tests: - report('*', 'SKIP no tests in this package') + report('*', 'SKIP package has metadata but no tests') errorcode |= 8 for t in tests: t.prepare() t.run(tree) - if 'breaks-testbed' in t.restrictions: + if 'breaks-testbed' in t.restriction_names: testbed.needs_reset() testbed.needs_reset() @@ -978,22 +1066,42 @@ class Test: t._debug('[----------------------------------------') def stdouterr(oe): idstr = t.what + '-' + oe - if opts.output_dir is not None and opts.output_dir.tb: + if opts.output_dir is not None and opts.output_dir.spec_tbp: use_dir = opts.output_dir else: use_dir = testbed.scratch return RelativeOutputFile(idstr, use_dir, idstr) - t.act.work.write(True) + if hasattr(t.act,'work'): t.act.work.write(True) + tree.read(True) af = RelativeInputFile(t.what, tree, t.path) so = stdouterr('stdout') se = stdouterr('stderr') + tf = af.read(True) - if 'needs-root' not in t.restrictions: - tf = opts.user_wrap(tf) - rc = testbed.execute('test-'+t.what, [tf], - so=so.write(True), se=se.write(True), cwd=tree.read(True)) + tmpdir = None + + rc = testbed.execute('testchmod-'+t.what, ['chmod','+x','--',tf]) + if rc: bomb('failed to chmod +x %s' % tf) + + if 'needs-root' not in t.restriction_names and opts.user is not None: + tfl = ['su',opts.user,'-c',tf] + tmpdir = '%s%s-tmpdir' % (testbed.scratch.read(True), t.what) + script = 'rm -rf -- "$1"; mkdir -- "$1"' + if opts.user: script += '; chown %s "$1"' % opts.user + if 'rw-build-tree' in t.restriction_names: + script += '; chown -R %s "$2"' % opts.user + rc = testbed.execute('mktmpdir-'+t.what, + ['sh','-xec',script,'x',tmpdir,tree.read(True)]) + if rc: bomb("could not create test tmpdir `%s', exit code %d" + % (tmpdir, rc)) + else: + tfl = [tf] + + rc = testbed.execute('test-'+t.what, tfl, + so=so.write(True), se=se.write(True), cwd=tree.read(True), + tmpdir=tmpdir, kind='test') so_read = so.read() se_read = se.read() @@ -1003,7 +1111,7 @@ class Test: if stab.st_size != 0: l = open(se_read).readline() l = l.rstrip('\n \t\r') - if len(l) > 40: l = l[:40] + '...' + if len(l) > 35: l = l[:35] + '...' t.reportfail('status: %d, stderr: %s' % (rc, l)) t._debug(' - - - - - - - - - - stderr - - - - - - - - - -') debug_file(se_read) @@ -1026,11 +1134,13 @@ def read_control(act, tree, control_override): control_af = control_override testbed.blame('arg:'+control_override.spec) else: + if act.missing_tests_control: + return () control_af = RelativeInputFile(act.what+'-testcontrol', tree, 'debian/tests/control') try: control = open(control_af.read(), 'r') - except OSError, oe: + except (IOError,OSError), oe: if oe[0] != errno.ENOENT: raise return [] @@ -1084,7 +1194,10 @@ def read_control(act, tree, control_override): tnames = map((lambda lt: lt[1]), tnames) tnames = string.join(tnames).split() base = { + 'restriction_names': [], 'restrictions': [], + 'feature_names': [], + 'features': [], 'testsdir': 'debian/tests', 'depends' : '@' } @@ -1111,9 +1224,11 @@ def print_exception(ei, msgprefix=''): (et, q, tb) = ei if et is Quit: pstderr('adt-run: ' + q.m) + psummary('quitting: '+q.m) return q.ec else: pstderr("adt-run: unexpected, exceptional, error:") + psummary('quitting: unexpected error, consult transcript') traceback.print_exc(None, sys.stderr) if trace_stream is not None: traceback.print_exc(None, trace_stream) @@ -1122,13 +1237,11 @@ def print_exception(ei, msgprefix=''): def cleanup(): global trace_stream try: - rm_ec = 0 - if opts.tmpdir is None and tmpdir is not None: - rmtree('tmpdir', tmpdir) if testbed is not None: testbed.reset_apt() testbed.stop() - if rm_ec: bomb('rm -rf -- %s failed, code %d' % (tmpdir, ec)) + if opts.tmpdir is None and tmpdir is not None: + rmtree('tmpdir', tmpdir) if trace_stream is not None: trace_stream.close() trace_stream = None @@ -1166,7 +1279,7 @@ class Binaries: for x in ['pubring','secring']: os.stat(opts.gnupghome + '/' + x + '.gpg') ok = True - except OSError, oe: + except (IOError,OSError), oe: if oe.errno != errno.ENOENT: raise if ok: b._debug('no key generation needed') @@ -1196,18 +1309,13 @@ END gpg --homedir="$1" --batch --gen-key key-gen-params ''' cmdl = ['sh','-ec',script,'x',opts.gnupghome] - rc = subprocess_cooked(cmdl, dbg=(genkey,script))[0] - if rc: - try: - f = open(opts.gnupghome+'/key-gen-log') - tp = file.read() - except OSError, e: tp = e - pstderr(tp) - bomb('key generation failed, code %d' % rc) + rc = subprocess_cooked(cmdl, dbg=('genkey',script))[0] + if rc: bomb('key generation failed, code %d' % rc) def apt_configs(b): return { "Dir::Etc::sourcelist": b.dir.read(True)+'sources.list', + "Debug::pkgProblemResolver": "true", } def apt_pkg_gdebi_script(b, arg, middle): @@ -1229,7 +1337,9 @@ END 'print res', 'print d.missingDeps', 'print d.requiredChanges', - 'assert(res)', + 'if not res: raise "gdebi failed (%s, %s, %s): %s" % '+ + ' (`res`, `d.missingDeps`, `d.requiredChanges`, '+ + 'd._failureString)', 'cache.commit()', '' ] @@ -1238,7 +1348,7 @@ END ag = ['apt-get','-qy'] for kv in b.apt_configs().iteritems(): ag += ['-o', '%s=%s' % kv] - return ag + return ' '.join(ag) def reset(b): b._debug('reset') @@ -1261,11 +1371,11 @@ END dest = RelativeOutputFile('binaries--'+leafname, b.dir, leafname) try: os.remove(dest.write()) - except OSError, oe: + except (IOError,OSError), oe: if oe.errno != errno.ENOENT: raise e try: os.link(af.read(), dest.write()) - except OSError, oe: + except (IOError,OSError), oe: if oe.errno != errno.EXDEV: raise e shutil.copy(af.read(), dest) @@ -1303,13 +1413,13 @@ END if [ "x`ls /var/lib/dpkg/updates`" != x ]; then echo >&2 "/var/lib/dpkg/updates contains some files, aargh"; exit 1 fi - '''+ ' '.join(b.apt_get()) +''' update >&2 + '''+ b.apt_get() +''' update >&2 cat /var/lib/dpkg/status >&3 ''' testbed.mungeing_apt() rc = testbed.execute('apt-key', ['sh','-ec',script], so=so.write(True), cwd=b.dir.write(True), - script=script) + script=script, kind='install') if rc: bomb('apt setup failed with exit code %d' % rc) testbed.blamed += b.blamed @@ -1328,9 +1438,10 @@ END if pkgs_reinstall: for pkg in pkgs_reinstall: testbed.blame(pkg) what = 'apt-get-reinstall' - cmdl = (b.apt_get() + ['--reinstall','install'] + - [pkg for pkg in pkgs_reinstall]) - rc = testbed.execute(what, cmdl, script=None) + cmdl = (b.apt_get() + ' --reinstall install '+ + ' '.join([pkg for pkg in pkgs_reinstall])+' >&2') + cmdl = ['sh','-c',cmdl] + rc = testbed.execute(what, cmdl, script=None, kind='install') if rc: badpkg("installation of basic binarries failed," " exit code %d" % rc) @@ -1338,8 +1449,9 @@ END for pkg in b.install: what = 'apt-get-install-%s' % pkg testbed.blame(pkg) - cmdl = b.apt_get() + ['install',pkg] - rc = testbed.execute(what, cmdl, script=None) + cmdl = b.apt_get() + ' install ' + pkg + ' >&2' + cmdl = ['sh','-c',cmdl] + rc = testbed.execute(what, cmdl, script=None, kind='install') if rc: badpkg("installation of %s failed, exit code %d" % (pkg, rc)) @@ -1355,22 +1467,29 @@ def source_rules_command(act,script,what,which,work,cwd, so = TemporaryFile('%s-%s-results' % (what,which)) rc = testbed.execute('%s-%s' % (what,which), ['sh','-ec',script]+xargs, script=script, - so=so.write(True), cwd=cwd) - results = open(so.read()).read().rstrip('\n').split("\n") - if rc: badpkg("%s failed with exit code %d" % (which,rc)) + so=so.write(True), cwd=cwd, kind='build') + results = open(so.read()).read().rstrip('\n') + if len(results): results = results.split("\n") + else: results = [] + if rc: badpkg("rules %s failed with exit code %d" % (which,rc)) if results_lines is not None and len(results) != results_lines: badpkg("got %d lines of results from %s where %d expected" % (len(results), which, results_lines)) if results_lines==1: return results[0] return results -def build_source(act): +def build_source(act, control_override): act.blame = 'arg:'+act.af.spec testbed.blame(act.blame) + testbed.prepare1([]) testbed.needs_reset() what = act.what basename = act.af.spec + debiancontrol = None + act.binaries = [] + + def debug_b(m): debug('* <%s:%s> %s' % (act.kind, act.what, m)) if act.kind == 'dsc': dsc = act.af @@ -1409,29 +1528,28 @@ def build_source(act): print >>dsc_w, 'Binary: none-so-this-is-not-a-package-name' dsc_w.close() - script = binaries.apt_pkg_gdebi_script( - dsc.read(True), [[ - 'from GDebi.DscSrcPackage import DscSrcPackage', - 'd = DscSrcPackage(cache, arg)', - 'res = d.checkDeb()', - ],[ - 'from GDebi.DebPackage import DebPackage', - 'd = DebPackage(cache)', - 'res = d.satisfyDependsStr("build-essential")', - ]]) - - cmdl = ['python','-c',script] - whatp = what+'-builddeps' - rc = testbed.execute(what, cmdl, script=script) - if rc: badpkg('build-depends install failed, exit code %d' % rc) + if act.kind == 'dsc': + testbed.prepare2([]) + script = binaries.apt_pkg_gdebi_script('', [[ + 'from GDebi.DebPackage import DebPackage', + 'd = DebPackage(cache)', + 'res = d.satisfyDependsStr("dpkg-dev")', + ]]) + cmdl = ['python','-c',script] + whatp = what+'-dpkgsource' + rc = testbed.execute(what, cmdl, script=script, kind='install') + if rc: badpkg('dpkg-source install failed, exit code %d' % rc) work = TemporaryDir(what+'-build') + act.work = work - script = [ - 'arg="$1"', - 'origpwd=`pwd`', - 'cd '+work.write(True) - ] + tmpdir = work.write(True)+'/tmpdir' + tmpdir_script = [ + 'TMPDIR="$1"', + 'rm -rf -- "$TMPDIR"', + 'export TMPDIR', + opts.user_wrap('mkdir -- "$TMPDIR"'), + ] if act.kind == 'ubtree': spec = '%s/real-tree' % work.write(True) @@ -1449,53 +1567,130 @@ def build_source(act): ''' initcwd = work.write(True) - if opts.user: script += [ - 'chown '+opts.user+' .', - 'spec="$arg" '+opts.user_wrap(create_command) - ] - else: script += [ - 'spec="$arg"', - create_command - ] + script = [ + 'spec="$2"', + 'origpwd=`pwd`', + 'cd '+work.write(True) + ] + + if opts.user: + script += ([ 'chown '+opts.user+' .' ] + + tmpdir_script + + [ 'spec="$spec" origpwd="$origpwd" ' + +opts.user_wrap(create_command) ]) + else: + script += (tmpdir_script + + [ create_command ]) script += [ - 'cd */.', - 'dpkg-checkbuilddeps', + 'cd [a-z0-9]*-*/.', 'pwd >&3', - opts.user_wrap('debian/rules build'), + 'set +e; test -f debian/tests/control; echo $? >&3' ] - result_pwd = source_rules_command(act,script,what,'build',work, - cwd=initcwd, results_lines=1, xargs=['x',spec]) + (result_pwd, control_test_rc) = source_rules_command( + act,script,what,'extract',work, + cwd=initcwd, results_lines=2, xargs=['x',tmpdir,spec]) - if os.path.dirname(result_pwd)+'/' != work.read(True): - badpkg("results dir `%s' is not in expected parent dir `%s'" - % (result_pwd, work.read(True))) + filter = act.ah['dsc_filter'] + + if control_test_rc == '1': act.missing_tests_control = True + + # For optional builds: + # + # We might need to build the package because: + # - we want its binaries (filter isn't _ and at least one of the + # deb_... isn't ignore) + # - the test control file says so + # (assuming we have any tests) + + class NeedBuildException: pass + def build_needed(m): + debug_b('build needed for %s' % m) + raise NeedBuildException() + + try: + if filter != '_' and (act.ah['deb_forbuilds'] != 'ignore' or + act.ah['deb_fortests'] != 'ignore'): + build_needed('binaries') + + result_pwd_af = InputDir(what+'-treeforcontrol', + result_pwd, True) + stanzas = read_control(act, result_pwd_af, control_override) + for stanza in stanzas: + for t in stanza[' tests']: + if 'no-build-needed' not in t.feature_names: + build_needed('test %s' % t.tname) + for d in t.depends: + if '@' in d: + build_needed('test %s ' + 'dependency %s' % (t.tname,d)) + + debug_b('build not needed') + built = False + + except NeedBuildException: + + if act.kind != 'dsc': + testbed.prepare2([]) + + script = binaries.apt_pkg_gdebi_script( + dsc.read(True), [[ + 'from GDebi.DscSrcPackage import DscSrcPackage', + 'd = DscSrcPackage(cache, arg)', + 'res = d.checkDeb()', + ],[ + 'from GDebi.DebPackage import DebPackage', + 'd = DebPackage(cache)', + 'res = d.satisfyDependsStr("'+ + ','.join(build_essential)+ + '")', + ]]) + + cmdl = ['python','-c',script] + whatp = what+'-builddeps' + rc = testbed.execute(what, cmdl, script=script, kind='install') + if rc: badpkg('build-depends install failed,' + ' exit code %d' % rc) + + script = tmpdir_script + [ + 'cd "$2"', + 'dpkg-checkbuilddeps', + opts.user_wrap('debian/rules build'), + ] + source_rules_command(act,script,what,'build',work, + cwd=initcwd, xargs=['x',tmpdir,result_pwd]) + + if os.path.dirname(result_pwd)+'/' != work.read(True): + badpkg("results dir `%s' is not in expected parent" + " dir `%s'" % (result_pwd, work.read(True))) + + built = True - act.work = work act.tests_tree = InputDir(what+'-tests-tree', work.read(True)+os.path.basename(result_pwd), True) if act.ah['dsc_tests']: - act.tests_tree.read() testbed.register_ephemeral(act.work) testbed.register_ephemeral(act.tests_tree) + if not built: + act.blamed = [] + return + act.blamed = copy.copy(testbed.blamed) - def debug_b(m): debug('* <%s:%s> %s' % (act.kind, act.what, m)) - act.binaries = [] - filter = act.ah['dsc_filter'] debug_b('filter=%s' % filter) if filter != '_': - script = [ - 'cd '+work.write(True)+'/*/.', + script = tmpdir_script + [ + 'cd '+work.write(True)+'/[a-z0-9]*-*/.', opts.user_wrap(opts.gainroot+' debian/rules binary'), 'cd ..', 'echo *.deb >&3', ] result_debs = source_rules_command(act,script,what, - 'binary',work,work.write(True),results_lines=1) - if result_debs == '*': debs = [] + 'binary',work,work.write(True), + results_lines=1, xargs=['x',tmpdir]) + if result_debs == '*.deb': debs = [] else: debs = result_debs.split(' ') debug_b('debs='+`debs`) re = regexp.compile('^([-+.0-9a-z]+)_[^_/]+(?:_[^_/]+)\.deb$') @@ -1533,16 +1728,19 @@ def process_actions(): binaries = Binaries() for act in opts.actions: - testbed.register_ephemeral(act.af) + if act.af is not None and not act.af.spec_tbp: + testbed.register_ephemeral(act.af) binaries.reset() + control_override = None debug_a1('builds ...') for act in opts.actions: debug_a2('%s %s' % (act.kind, act.what)) - testbed.prepare([]) + if act.kind == 'control': + control_override = act.af if act.kind == 'deb': testbed.blame('arg:'+act.af.spec) determine_package(act) @@ -1550,9 +1748,13 @@ def process_actions(): binaries.register(act,act.pkg,act.af, 'forbuilds',testbed.blamed) if act.kind == 'dsc' or act.kind == 'ubtree': - build_source(act) + build_source(act, control_override) if act.kind == 'tree': act.binaries = [] + if act.kind.endswith('tree') or act.kind == 'dsc': + control_override = None + if act.kind == 'instantiate': + pass debug_a1('builds done.') @@ -1588,6 +1790,8 @@ def process_actions(): debug_a3('run_tests ...') run_tests(stanzas, act.af) control_override = None + if act.kind == 'instantiate': + testbed.prepare([]) debug_a1('tests done.') def main():