import re as regexp
import os
import errno
+import fnmatch
from optparse import OptionParser
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
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 <options> -- <virt-server>..."
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'
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)
(opts,args) = parser.parse_args()
if not hasattr(opts,'vserver'):
parser.error('you must specifiy --- <virt-server>...')
+ 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 = ''
'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
'\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
testbed = Testbed()
testbed.start()
testbed.open()
- testbed.close()
finalise_options()
- read_control()
- run_tests()
+ process_actions()
except:
ec = print_exception(sys.exc_info(), '')
cleanup()
.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
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