From: Ian Jackson Date: Fri, 12 Jan 2007 19:59:05 +0000 (+0000) Subject: working on shiny new adt-run X-Git-Tag: converted-from-bzr~32^3~68 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=553ac3ad72dfb0ff42f9e5fd9889b94bcf73ea27;hp=a3b5936c930104d5098ce1eacdd0a78a71126e6a;p=autopkgtest.git working on shiny new adt-run --- diff --git a/runner/adt-run b/runner/adt-run index 26cd18d..e452e2e 100755 --- a/runner/adt-run +++ b/runner/adt-run @@ -33,6 +33,7 @@ import string import re as regexp import os import errno +import fnmatch from optparse import OptionParser @@ -67,7 +68,58 @@ def flatten(l): return reduce((lambda a,b: a + b), l, []) class Path: - def __init__(p, tb, path, what, dir=False, tbscratch=None, xfmap=None + # p.path[tb] None or path not None => path known + # p.file[tb] None or path not None => file exists + # p.what + # p.spec_tb + # p.tb_scratch + + def ensure_path(p, tb=False): + if tb and not p.spec_tb: + if not testbed.scratch: + error "called ensure_path for `%s' when testbed closed" + % what + if not p.tb_scratch or p.tb_scratch is not testbed.scratch: + + + if p.path[tb] is not None: return + if tb: p.path[tb] = p.tb_tmpdir + else: p.path[tb] = tmpdir + p.path[tb] += '/'+p.what + + def ensure_file(p, tb=False): + if p.file[tb] is not None: return + p.ensure_path(tb) + testbed.open() + + + def write(p, tb=False): + p.ensure_path(tb) + return p.path[tb] + def read(p, tb=False): + p.ensure_file(tb) + +class InputPath: +class OutputPath: +class OutputPath: + + def __init__(p, path, spec_tb, what, dir=False): + if p.tb: + if p.p[:1] != '/': + bomb("path %s specified as being in testbed but" + " not absolute: `%s'" % (what, p.p)) + p.path[spec_tb] = p.file[spec_tb] = path + p.what = what + + + if spec_tb: + p. +tb_path = path + p.tb_onpath = path + p.tb_onhost = None + + +4 def __init__(p, tb, path, what, dir=False, tbscratch=None, xfmap=None lpath=None): p.tb = tb p.p = path @@ -157,6 +209,13 @@ class Path: p.xfmapcopy('copydown') return p.down +def Action: + def __init__(a, kind, path, arghandling, ix): + a.kind = kind + a.path = path + a.ah = arghandling + a.what = '%s%s' % (kind,ix); ix++ + def parse_args(): global opts usage = "%prog -- ..." @@ -164,52 +223,128 @@ def parse_args(): pa = parser.add_option pe = parser.add_option + arghandling = { + 'dsc_tests': True, + 'dsc_filter': '*', + 'deb_forbuilds': 'auto', + 'deb_fortests': 'auto', + 'tb': False, + 'override_control': None + } + initial_arghandling = arghandling.copy() + n_actions = 0 + + #---------- + # actions (ie, test sets to run, sources to build, binaries to use): + + def cb_action(op,optstr,value,parser, long,kindpath,is_act): + parser.largs.append((value,kindpath)) + n_actions += is_act + + def pa_action(long, metavar, kindpath, help, is_act=True): + pa('','--'+long, action='callback', callback=cb_action, + nargs=1, type='string', + callback_args=(long,kindpath,is_act), help=help) + + pa_action('build-tree', 'TREE', '@/', + help='run tests from build tree TREE') + + pa_action('source', 'DSC', '@.dsc', + help='build DSC and use its tests and/or' + ' generated binary packages') + + pa_action('binary', 'DEB', '@.deb', + help='use binary package DEB according' + ' to most recent --binaries-* settings') + + pa_action('override-control', 'CONTROL', ('control',), is_act=0, + help='run tests from control file CONTROL instead, + ' (applies to next test suite only)') + + #---------- + # argument handling settings (what ways to use action + # arguments, and pathname processing): + + def cb_setah(option, opt_str, value, parser, toset,setval): + if type(setval) == list: + if not value in setval: + parser.error('value for %s option (%s) is not ' + 'one of the permitted values (%s)' % + (value, opt_str, setval.join(' '))) + elif setval is not None: + value = setval + for v in toset: + arghandling[v] = value + parser.largs.append(arghandling.copy()) + + def pa_setah(long, affected,effect, **kwargs): + type = metavar; if type: type = 'string' + pa('',long, action='callback', callback=cb_setah, + callback_args=(affected,effect), **kwargs) + ' according to most recent --binaries-* settings') + + #---- paths: host or testbed: + # + pa_setah('--paths-testbed', ['tb'],True, + help='subsequent path specifications refer to the testbed') + pa_setah('--paths-host', ['tb'],False, + help='subsequent path specifications refer to the host') + + #---- source processing settings: + + pa_setah('--sources-tests', ['dsc_tests'],True, + help='run tests from builds of subsequent sources') + pa_setah('--sources-no-tests', ['dsc_tests'],False, + help='do not run tests from builds of subsequent sources') + + pa_setah('--built-binaries-filter', ['dsc_filter'],None, + type=string, metavar='PATTERN-LIST', + help='from subsequent sources, use binaries matching' + ' PATTERN-LIST (comma-separated glob patterns)' + ' 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): + pa_setah(long, toset,['ignore','auto','install'], + type=string, metavar='IGNORE|AUTO|INSTALL', default='auto', + help=how+' ignore binaries, install them as needed' + ' for dependencies, or unconditionally install' + ' them, respectively') + pa_setahbins('--binaries', ['deb_forbuilds','deb_fortests'], '') + pa_setahbins('--binaries-forbuilds', ['deb_forbuilds'], 'for builds, ') + pa_setahbins('--binaries-fortests', ['deb_fortests'], 'for tests, ') + + #---------- + # general options: + def cb_vserv(op,optstr,value,parser): parser.values.vserver = list(parser.rargs) del parser.rargs[:] - def cb_path(op,optstr,value,parser, long,tb,dir,xfmap): + def cb_path(op,optstr,value,parser, long,dir,xfmap): name = long.replace('-','_') - path = Path(tb, value, long, dir, xfmap=xfmap) + path = Path(arghandling['tb'], value, long, dir, xfmap=xfmap) setattr(parser.values, name, path) def pa_path(long, help, dir=False, xfmap=None): - def papa_tb(long, ca, pahelp): - pa('', long, action='callback', callback=cb_path, - nargs=1, type='string', callback_args=ca, - help=(help % pahelp), metavar='PATH') - papa_tb('--'+long, (long, False, dir, xfmap), 'host') - papa_tb('--'+long+'-tb',(long, True, dir, xfmap), 'testbed') - - pa_path('tests-tree', 'TREE', 'use tests in build tree TREE (found on %s)', dir=True) - - pa_pkg('source', 'DSC', 'build DSC (on %s) on testbed and use its tests and packages', testsrc=True, pkgsrc=True) - pa_pkg('source-tests', 'DSC', 'use tests in DSC (found on %s), building it on testbed', testsrc=True, pkgsrc=False) - pa_pkg('source-testsinstall', 'DSC', 'build DSC (found on %s), use its tests, and install its' - ' binaries (even when apparently not required)', testsrc=True, pkgsrc='*') - - pa_pkg('source-binaries', 'DSC', 'build DSC (on %s) on testbed and use its binary packages' - ' as test targets and to satisfy dependencies', testsrc=False, pkgsrc=True) - pa_pkg('source-installbinaries','DSC', 'build DSC (on %s) on testbed and install its binary' - ' packages (even when apparently not required)', testsrc=False, pkgsrc='*') - - pa_pkg('install-binary', 'DEB', 'install package DEB (found on %s) on testbed', testsrc=None, pkgsrc=None) - - - #nyi pa_path('install-from-source', 'build and install package found'+ - # ' in PATH on %s', xfmap=xfmap_dsc) - #nyi these install-* options need cb_path to be able to make a list - # nyi: without-depends,with-depends-only,with-depends,with-recommends - # nyi: package-filter-dependency - # nyi: package-filter-from-source - pa_path('install-binary', 'build source package PATH on %s') - pa_path('control', 'read control file PATH on %s') - pa_path('output-dir', 'write stderr/out files in PATH on %s', dir=True) - - pa('','--user', help= 'run tests as USER (needs root on testbed)', type='string', dest='user') - pa('','--fakeroot', help= 'prefix debian/rules build with FAKEROOT' type='string', dest='fakeroot') + pa('','--'+long, action='callback', callback=cb_path, + nargs=1, type='string', callback_args=(long,dir,xfmap), + help=, metavar='PATH') + + pa_path('output-dir', 'write stderr/out files in PATH', dir=True) + + pa('','--user', type='string', dest='user', + help='run tests as USER (needs root on testbed)') + pa('','--fakeroot', type='string', dest='fakeroot', + help='prefix debian/rules build with FAKEROOT') pa('-d', '--debug', action='store_true', dest='debug'); + #---------- + # actual meat: + class SpecialOption(optparse.Option): pass vs_op = SpecialOption('','--VSERVER-DUMMY') vs_op.action = 'callback' @@ -221,7 +356,6 @@ def parse_args(): vs_op.callback_kwargs = { } vs_op.help = 'introduces virtualisation server and args' vs_op._short_opts = [] - #vs_op._long_opts = ['--DUMMY'] vs_op._long_opts = ['---'] pa(vs_op) @@ -229,21 +363,38 @@ def parse_args(): (opts,args) = parser.parse_args() if not hasattr(opts,'vserver'): parser.error('you must specifiy --- ...') + if not n_actions: + parser.error('nothing to do specified') + + arghandling = initial_arghandling + opts.actions = [] + ix = 0 + for act in args: + if type(act) == dict: + arghandling = act + continue + elif type(act) == tuple: + pass + elif type(act) == string: + act = (act,act) + else: + error "unknown action in list `%s' having" + "type `%s' % (act, type(act)) + (path, kindpath) = act - if opts.tests_tree is not None and opts.build_source is not None: - parser.error('do not specify both --build-tree and' - ' --build-source') + if type(kindpath) is tuple: kind = kindpath[0] + elif kindpath.endswith('/'): kind = 'tree' + elif kindpath.endswith('.deb'): kind = 'deb' + elif kindpath.endswith('.dsc'): kind = 'dsc' + else: parser.error("do not know how to handle filename \`%s';" + " specify --source --binary or --build-tree") - if opts.control is None: - opts.control = opts.tests_tree.append( - 'debian/tests/control', 'control') + opts.actions.append(Action(kind, path, arghandling, ix)) + ix++ def finalise_options(): global opts, testbed - if opts.tests_tree is None and opts.build_source is None: - opts.tests_tree = Path(False, '.', 'build-tree', dir=True) - if opts.user is None and 'root-on-testbed' not in caps: opts.user = '' @@ -405,36 +556,6 @@ class FieldBase: 'only one %s field allowed' % fn) return f.v -def build_some_source(keyletter, dsc, binaries=False): - idstr = 'build'+keyletter - testbed.open() - bd = testbed.scratch.append(idstr, idstr) - script = [ - 'exec 3>&1 >&2' - 'mkdir '+bd.ontb(), - 'cd '+bd.ontb(), - 'dpkg-source -x '+dsc.ontb()+' >&2', - 'cd */.', - 'pwd >&3', - opts.user_wrap('debian/rules build'), - ] - if binaries: - script = script + [ - opts.user_wrap(opts.fakeroot+' debian/rules binary'), - 'cd ..', - 'echo *.deb >&3', - ] - - script = '\n'.join(script) - so = testbed.scratch.append(idstr+'-tree-path') - se = logpath('log-'+idstr) - rc = testbed.commandr1(['execute', - ','.join(map(urllib.quote, ['sh','-xec',script]))], - '/dev/null',so.ontb(),se.ontb(), testbed.scratch.ontb()) - sod = file(so.onhost()).read().split("\n") - tests_tree = Path(True, sod[0], idstr+'-tree', dir=True) - se.maybe_onhost() - return (tests_tree,) def acquire_built_source(): global opts @@ -630,6 +751,124 @@ def cleanup(): '\nadt-run: error cleaning up:\n') os._exit(20) +def source_rules_command(act,script,which,work,results_lines=0): + script = "exec 3>&1 >&2\n" + '\n'.join(script) + so = TemporaryPath('%s-%s-results' % (what,which)) + se = TemporaryPath('%s-%s-log' & (what,which)) + rc = testbed.commandr1(['execute', + ','.join(map(urllib.quote, ['sh','-xec',script]))], + '/dev/null', so.write(True), se.write(True), work.write(True)) + results = file(so.read()).read().split("\n") + if rc: + act.bomb("%s failed with exit code %d" % (which,rc), se) + if results_lines is not None and len(results) != results_lines: + act.bomb("got %d lines of results from %s where %d expected" + % (len(results), which, results_lines), se) + if results_lines==1: return results[0] + return results + +def build_source(act,ah): + prepare_testbed_for_action() + + what = act.ah['what'] + dsc_what = what+'/'+os.path.basename(act.path) + dsc = InputPath(dsc_what, act.path, arghandling['tb']) + + if not dsc.spec_tb: + dsc_file = open(dsc.read()) + in_files = False + re = regexp.compile('^\s+[0-9a-f]+\s+\d+\s+([^/.][^/]*)$') + for l in dsc_file(): + if l.startswith('Files:'): in_files = True + elif l.startswith('#'): pass + elif not l.startswith(' '): in_files = False + elif not in_files: pass + + m = re.match(l) + if not m: act.bomb(".dsc contains unparseable line" + " in Files: `%s'" % (`dsc`,l)) + + subfile = dsc.enclosingdir().append('/'+m.groups(0)) + subfile.ensure_file(True) + dsc.ensure_file(True) + + work = AccumulationPath(what+'/build', dir=True) + + script = [ + 'cd '+work.write(True), + 'gdebi '+dsc.read(True), + 'dpkg-source -x '+dsc.read(True), + 'cd */.', + 'pwd >&3', + opts.user_wrap('debian/rules build'), + ] + result_pwd = source_rules_command(act,script,what,'build',work,1) + + if os.path.dirname(result_pwd) != work.read(True): + act.bomb("results dir `%s' is not in expected parent dir `%s'" + % (results[0], work.read(True)), se) + + ah.tests_tree = work.append('/'+os.path.basename(results[0])) + if ah['dsc_tests']: + ah.tests_tree.preserve_now() + + ah.binaries = [] + if ah['dsc_filter'] != '_': + script = [ + 'cd '+work.write(True)+'/*/.', + opts.user_wrap(opts.fakeroot+' debian/rules binary'), + 'cd ..', + 'echo *.deb >&3', + ] + result_debs = source_rules_command(act,script,what, + 'debian/rules binary',work,1) + if result_debs == '*': debs = [] + else: debs = debs.split(' ') + re = regexp.compile('^([-+.0-9a-z]+)_[^_/]+(?:_[^_/]+)\.deb$') + for deb in debs: + m = re.match(deb) + if not m: act.bomb("badly-named binary `%s'" % deb, se) + package = m.groups(deb) + for pat in ah['dsc_filter'].split(','): + if fnmatch.fnmatchcase(package,pat): + deb_path = work.read()+'/'+deb + deb_what = package+'_'+what+'.deb' + bin = InputPath(deb_what,deb_path,True) + bin.preserve_now() + record_binary(bin,'builds') + ah.binaries.append(bin) + break + +def record_binary( + +def process_actions(): + global binaries + + binaries = {} + ix = 0 + for act opts.actions: + if act.kind == 'deb': + record_binary(act,'builds') + if act.kind == 'dsc': + build_source(act) + # build_source records tree location in ah + + binaries = {} + control_override = None + for (kind,path,ah) in opts.actions: + if kind == 'control': + control_override = act + if kind == 'deb': + record_binary(act,'tests') + if kind == 'dsc': + for bin in act.binaries: record_binary(bin,'tests') + if not act.ah['dsc_tests']: continue + run_tests(act,control_override) + control_override = None + if kind == 'tree': + run_tests(act,control_override) + control_override = None + def main(): global testbed global tmpdir @@ -642,10 +881,8 @@ def main(): testbed = Testbed() testbed.start() testbed.open() - testbed.close() finalise_options() - read_control() - run_tests() + process_actions() except: ec = print_exception(sys.exc_info(), '') cleanup() diff --git a/runner/adt-run.1 b/runner/adt-run.1 index 1654674..3050df8 100644 --- a/runner/adt-run.1 +++ b/runner/adt-run.1 @@ -35,81 +35,35 @@ Specifies that the built source tree can be found in .BR --source [ -tb ] " " \fIdsc\fR Builds \fIdsc\fR, and then uses the tests in that tree, as well as testing the packages from this build and using them to satisfy -dependencies. Equivalent to \fB--source-tests\fI and -\fB--source-binaries\fI (except that it only does the build once). +dependencies. Equivalent to \fB--source-binaries-tests\fR. Order is +significant: each \fB--source\fR* option only affects the packages +used pursuant to \fIsubsequent\fR \fB--source\fR or \fB--binary\fR options. .TP -.BR --source-tests [ -tb ] " " \fIdsc\fR +.BR --source [[ -forbuilds | -installforbuilds ][ -fortests | installfortests ]| -binaries | -install ][ -tests ][ -tb "] \fIdsc\fR" Builds \fIdsc\fR (installing dependencies as appropriate), and then -uses the tests in that tree. The packages tested will be those -installed on the testbed, not the ones from this build. -.TP -.BR --source-testsinstall [ -tb ] " " \fIdsc\fR -Builds \fIdsc\fR (installing dependencies as appropriate), and then -installs the packages which result (subject to -\fB--package-filter-source-install\fR). The packages tested will be -those installed on the testbed, not the ones from this build. This is -like \fB--source-tests\fR except that the packages from the build are -installed even if they are not required as a dependency for any test. -.TP -.BR --source-binaries [ -tb ] " " \fIdsc\fR -Builds \fIdsc\fR (installing dependencies as appropriate), and then -use the resulting binary packages to satisfy dependencies (instead of -packages from the default archive). - - -Only dependencies mentioned by -tests, or binary packages built by or provided directly to -\fBadt-run\fR, are affected: dependencies mentioned by binary packages -installed implicitly - - The dependencies affected are - - -which are themselves installed deliberately by the test -. Binary packages -installed implicitly by - - -direct \Test-Depends lines, - -installs the packages which result (subject to -\fB--package-filter-source-install\fR). The packages tested will be -those installed on the testbed, not the ones from this build. - - - - -Equivalent to \fB--source-tests\fR but - -; also uses the tests in the -resulting build tree. - - -and then -uses the tests in that tree. The packages tested will be those -installed on the testbed, not the ones from this build. -.TP - - - -, as well as -testing the packages from this build and using them to satisfy -dependencies. Equivalent to \fB--source-tests\fI and -\fB--source-binaries\fI (except that it only does the build once). - - - -Specifies that the source to use for the tests can be found in -.IR filename -(which should be a \fB.dsc\fR). The source will be unpacked -and built, and the tests from this build tree will be run. -.TP -.BR --use-source [ -tb ] " " \fIfilename\fR -Specifies that the source to use for the tests can be found in -.IR filename -(which should be a \fB.dsc\fR), and that the packages from this -build should be the ones tested, and used to satisfy any dependencies; -this option is like \fB--build-source\fR and \fB--from-source\fR combined. +uses the resulting binary packages to satisfy dependencies (not +\fBinstall\fR, or just \fBbinaries\fR), installs the resulting +binaries unconditionally (subject to +\fB--package-filter-source-install\fR) (\fBinstall\fR*), or uses the +resulting build tree's test suite (\fBtests\fR). The scope of the use +of the generated binary packages can be limited to building other +source packages (\fBforbuilds\fR, only available if the virtualisation used +supports filesystem rollback) or running the tests +(\fBforbinaries\fR). The subcomponents of the \fB--source-\fR* option +must be specified in the order shown. +.TP +.BR --binary [ -tb ] " " \fIfilename\fR +Specifies that \fIfilename\fR (which should be a \fB.deb\fR) should +be used to satisfy dependencies, both during building and testing, +(which will include using it as the specific package under test). +Equivalent to \fB--binary-forbuilds-fortests\fR. +.TP +.BR --binary [ -installforbuilds | -forbuilds ][ -installfortests | -fortests ] -tb "] \fIpkg\fR" +Uses the specified binary package to satisfy dependencies, or +unconditionally installs it (\fBinstall\fR), during building +(\fBforbuilds\fR) or during testing (\fBfortests\fR), as specified. +Order is significant: each \fB--source\fR* option only affects the +packages installed pursuant to \fIsubsequent\fR \fB--source\fR or \fB--binary\fR options. .TP .BR --user " " \fIusername\fR Specifies that commands on the testbed should generally be run as @@ -133,18 +87,6 @@ a normal user according to a specified or default value of The default is to run without any special measures if the test -Specifies that commands on the testbed should generally be run as -\fIusername\fR; this option does not make much sense unless the -virtualisation environment provides root on the testbed. Commands -are run via \fBsu -c\fR; affected commands are tests (other than those -which declare that they need root), source package builds and source -package binary generation (except if \fB--fakeroot\fR is specified - -see below). - -Specifies that \fIfilename\fR (which should be a \fB.deb\fR) should be -unconditionally installed on the testbed before any tests are run (but -after any necessary building). This can be used to test a specific -binary package. .TP .BR --install-binary [ -tb ] " " \fIfilename\fR Specifies that \fIfilename\fR (which should be a \fB.deb\fR) should be