From: Ian Jackson Date: Thu, 22 Feb 2007 20:08:59 +0000 (+0000) Subject: can avoid builds sometimes (no-build-needed feature); many bugfixes X-Git-Tag: converted-from-bzr~32^3~25 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=0f14566a3042262d1d99c21d101b1c34fb632285;p=autopkgtest.git can avoid builds sometimes (no-build-needed feature); many bugfixes --- diff --git a/doc/README.package-tests b/doc/README.package-tests index 97b2b84..83a3b61 100644 --- a/doc/README.package-tests +++ b/doc/README.package-tests @@ -53,14 +53,14 @@ The fields which may appear in the RFC822-style stanza are: this stanza. Depending on the test environment capabilities, user requests, and so on, restrictions can cause tests to be skipped or can cause the test to be run in a different manner. Tests which - declare unknown restrictions will be skipped. + declare unknown restrictions will be skipped. See below for the + defined restrictions. Features: [ ...] Declares some additional capabilities or good properties of the tests defined in this stanza. Any unknown features declared will - be completely ignored. Currently there are no defined features - but this field is recognised and ignored for future expansion. + be completely ignored. See below for the defined features. Depends: @@ -118,3 +118,9 @@ The defined Restrictions are: The test script must be run as root. + +The currently defined Features are: + + no-build-needed + + The tests can run in an unbuilt tree. diff --git a/runner/adt-run b/runner/adt-run index c5f9dc2..9822d16 100755 --- a/runner/adt-run +++ b/runner/adt-run @@ -384,6 +384,7 @@ 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`) @@ -716,8 +717,8 @@ 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]): @@ -725,9 +726,14 @@ class Testbed: 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) def _install_deps(tb, deps_new): @@ -810,7 +816,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): # Options for script: # False - do not call debug_subprocess, no synch. reporting required # None or string - call debug_subprocess with that value, @@ -838,7 +844,10 @@ class Testbed: cmdl = [None, ','.join(map(urllib.quote, cmdl)), si, so, se_use, cwd] + if xdump is not None and 'execute-debug' in tb.caps: cmdl += [xdump] + if tmpdir is not None: cmdl.append('env=TMPDIR=%s' % tmpdir) + rc = tb.commandr1('execute', cmdl) try: rc = int(rc) except ValueError: bomb("execute for %s gave invalid response `%s'" @@ -902,17 +911,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,10 +948,12 @@ class Field_Tests_directory(FieldBase): def run_tests(stanzas, tree): global errorcode, testbed + if stanzas == (): + report('*', 'SKIP no tests in this package') 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() @@ -989,11 +1011,23 @@ class Test: af = RelativeInputFile(t.what, tree, t.path) so = stdouterr('stdout') se = stdouterr('stderr') + tf = af.read(True) + tmpdir = None + if 'needs-root' not in t.restrictions: tf = opts.user_wrap(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 + rc = testbed.execute('mktmpdir-'+t.what, + ['sh','-xec',script,'x',tmpdir]) + if rc: bomb("could not create test tmpdir `%s', exit code %d" + % (tmpdir, rc)) + rc = testbed.execute('test-'+t.what, [tf], - so=so.write(True), se=se.write(True), cwd=tree.read(True)) + so=so.write(True), se=se.write(True), cwd=tree.read(True), + tmpdir=tmpdir) so_read = so.read() se_read = se.read() @@ -1026,6 +1060,8 @@ 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: @@ -1084,7 +1120,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' : '@' } @@ -1356,7 +1395,9 @@ def source_rules_command(act,script,what,which,work,cwd, 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") + results = open(so.read()).read().rstrip('\n') + if len(results): results = results.split("\n") + else: results = [] if rc: badpkg("%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" @@ -1364,13 +1405,18 @@ def source_rules_command(act,script,what,which,work,cwd, 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,13 +1455,28 @@ def build_source(act): print >>dsc_w, 'Binary: none-so-this-is-not-a-package-name' dsc_w.close() + if act.kind == 'dsc': + testbed.prepare2([]) + script = binaries.apt_pkg_gdebi_script('', [[ + 'from GDebi.DebPackage import DebPackage', + 'd = DebPackage(cache)', + 'res = d.satisfyDependsStr("dpkg-source")', + ]]) + cmdl = ['python','-c',script] + whatp = what+'-dpkgsource' + rc = testbed.execute(what, cmdl, script=script) + if rc: badpkg('dpkg-source install failed, exit code %d' % rc) + work = TemporaryDir(what+'-build') + act.work = work - script = [ - 'spec="$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) @@ -1433,50 +1494,103 @@ def build_source(act): ''' initcwd = work.write(True) - if opts.user: script += [ - 'chown '+opts.user+' .', - 'spec="$spec" origpwd="$origpwd" '+opts.user_wrap(create_command) - ] - else: script += [ - 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 */.', 'pwd >&3', + 'set +e; test -f debian/tests/control; echo $? >&3' ] - result_pwd = source_rules_command(act,script,what,'extract',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]) - 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")', - ]]) + filter = act.ah['dsc_filter'] - 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 control_test_rc == '1': act.missing_tests_control = True - script = [ - 'cd "$1"', + # 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.name) + for d in t.depends: + if '@' in d: + build_needed('test %s ' + 'dependency %s' % (t.name,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("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) + + 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',result_pwd]) + ] + 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))) + 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) @@ -1485,21 +1599,21 @@ def build_source(act): testbed.register_ephemeral(act.work) testbed.register_ephemeral(act.tests_tree) + if not built: 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 = [ + script = tmpdir_script + [ 'cd '+work.write(True)+'/*/.', 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) + 'binary',work,work.write(True), + results_lines=1, xargs=['x',tmpdir]) if result_debs == '*': debs = [] else: debs = result_debs.split(' ') debug_b('debs='+`debs`) @@ -1541,13 +1655,15 @@ def process_actions(): 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) @@ -1555,9 +1671,11 @@ 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 debug_a1('builds done.')