chiark / gitweb /
Huge changes to virt-server and shell quoting
[autopkgtest.git] / runner / adt-run
index 7787e5761971fb566993e8e87bd40058ed5d01b4..4b64ce87f23b1ee194bcf4004a1b7f4a585e6ebd 100755 (executable)
@@ -48,6 +48,7 @@ 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"]
+paths = []
 
 #---------- output handling
 #
@@ -217,6 +218,7 @@ class AutoFile:
        # p.what
        # p.path[tb]    None or path    not None => path known
        # p.file[tb]    None or path    not None => file exists
+       # p.ephem[tb]   boolean         True => destroyed by tb reset
        # p.spec        string or None
        # p.spec_tbp    True or False, or not set if spec is None
        # p.dir         '' or '/'
@@ -225,7 +227,9 @@ class AutoFile:
        p.what = what
        p.path = [None,None]
        p.file = [None,None]
+       p.ephem = [False,False]
        p.spec = None
+       p.spec_tbp = None
        p.dir = ''
 
  def __repr__(p):
@@ -261,6 +265,7 @@ class AutoFile:
                if not tbp:
                        p.path[tbp] = tmpdir+'/'+p.what
                else:
+                       p.ephem[tbp] = True
                        p.path[tbp] = testbed.scratch.path[True]+'/'+p.what
 
  def write(p, tbp=False):
@@ -299,8 +304,15 @@ class AutoFile:
        return p.file[tbp] + p.dir
 
  def invalidate(p, tbp=False):
+       if p.path[tbp] is not None:
+               p._debug('invalidating %s' % 'HT'[tbp])
        p.file[tbp] = None
-       p._debug('invalidated %s' % 'HT'[tbp])
+       if p.spec_tbp != tbp or p.spec is None:
+               p.path[tbp] = None
+
+ def invalidate_ephem(p, tbp=False):
+       if p.ephem[tbp]:
+               p.invalidate(tbp)
 
  def _debug(p, m):
        debug('/ %s#%x: %s' % (p.what, id(p), m), 3)
@@ -308,6 +320,7 @@ class AutoFile:
  def _constructed(p):
        p._debug('constructed: '+str(p))
        p._check()
+       paths.append(p)
 
  def _check(p):
        for tbp in [False,True]:
@@ -333,6 +346,7 @@ class AutoFile:
        if sibling: trim = os.path.dirname
        else: trim = lambda x: x
        for tbp in [False,True]:
+               p.ephem[tbp] = parent.ephem[tbp]
                if parent.path[tbp] is None: continue
                trimmed = trim(parent.path[tbp])
                if trimmed: trimmed += '/'
@@ -373,6 +387,12 @@ class OutputDir(OutputFile):
        p.dir = '/'
        p._constructed()
 
+class RelativeInputDir(AutoFile):
+ def __init__(p, what, parent, leaf, onlyon_tbp=None, sibling=False):
+       p._relative_init(what, parent, leaf, onlyon_tbp, True, sibling)
+       p.dir = '/'
+       p._constructed()
+
 class RelativeInputFile(AutoFile):
  def __init__(p, what, parent, leaf, onlyon_tbp=None, sibling=False):
        p._relative_init(what, parent, leaf, onlyon_tbp, True, sibling)
@@ -721,7 +741,6 @@ class Testbed:
        tb.scratch = None
        tb.modified = False
        tb.blamed = []
-       tb._ephemeral = []
        tb._debug('init')
        tb._need_reset_apt = False
  def _debug(tb, m, minlevel=0):
@@ -752,8 +771,37 @@ class Testbed:
        tb._debug('open, scratch=%s' % tb.scratch)
        if tb.scratch is not None: return
        pl = tb.commandr('open')
+       tb._opened(pl)
+ def _opened(tb, pl):
        tb.scratch = InputDir('tb-scratch', pl[0], True)
        tb.deps_processed = []
+       for af in paths: af.invalidate_ephem(True)
+       tb._auxverbscript_make()
+ def _auxverbscript_make(tb):
+       pec = tb.commandr('print-auxverb-command')
+       if len(pec) < 1: tb.bomb('too few results from print-execute-command')
+       cmdl = map(urllib.unquote, pec[0].split(','))
+
+       shellquote_re = regexp.compile('"')
+       def shellquote_arg(s): return "'" + shellquote_re.sub(r"'\''", s) + "'"
+       def shellquote_cmdl(l): return ' '.join(map(shellquote_arg,l))
+
+       tb._debug('cmdl = %s' % (`cmdl`))
+
+       tb.ec_auxverbscript = TemporaryFile('satdep-auxverb')
+       print >>open(tb.ec_auxverbscript.write(),'w'), (
+'''#!/bin/sh
+set -ex
+echo >&2 ": $*"
+if [ $# = 2 ] && [ "x$1" = xdpkg-architecture ] && [ "x$2" = x-qDEB_HOST_ARCH ]; then
+       # This is a pretty nasty hack.  Hopefully it can go away
+       #  eventually.  See #635763.
+       set -- dpkg --print-architecture
+fi
+exec '''+shellquote_cmdl(cmdl)+' "$@"'+"\n"
+               )
+       os.chmod(tb.ec_auxverbscript.write(), 0755)
+
  def mungeing_apt(tb):
        if not 'revert' in tb.caps:
                tb._need_reset_apt = True
@@ -777,11 +825,10 @@ class Testbed:
                (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)
+               for af in paths: af.read(False)
                tb._debug('reset **')
-               tb.command('revert')
-               tb.blamed = []
-               for af in tb._ephemeral: af.invalidate(True)
+               pl = tb.commandr('revert')
+               tb._opened(pl)
        tb.modified = False
  def prepare2(tb, deps_new):
        tb._debug('prepare2, deps_new=%s' % deps_new, 1)
@@ -790,24 +837,11 @@ class Testbed:
  def prepare(tb, deps_new):
        tb.prepare1(deps_new)
        tb.prepare2(deps_new)
- def register_ephemeral(tb, af):
-       tb._ephemeral.append(af)
  def _install_deps(tb, deps_new):
        tb._debug(' installing dependencies '+`deps_new`, 1)
        tb.deps_processed = deps_new
        if not deps_new: return
-       dstr = ', '.join(deps_new)
-       script = binaries.apt_pkg_gdebi_script(
-               dstr, [[
-               'from GDebi.DebPackage import DebPackage',
-               'd = DebPackage(cache)',
-               'res = d.satisfyDependsStr(arg)',
-               ]])
-       cmdl = ['python','-c',script]
-       what = 'install-deps'
-       rc = testbed.execute(what+'-debinstall', cmdl, script=script,
-                               kind='install')
-       if rc: badpkg('dependency install failed, exit code %d' % rc)
+       tb.satisfy_dependencies_string(', '.join(deps_new), 'install-deps')
  def needs_reset(tb):
        tb._debug('needs_reset, previously=%s' % tb.modified, 1)
        tb.modified = True
@@ -854,9 +888,9 @@ class Testbed:
        if nresults is not None and len(ll) != nresults:
                tb.bomb("sent `%s', got `%s' (%d result parameters),"
                        " expected %d result parameters" %
-                       (string, l, len(ll), nresults))
+                       (tb.lastsend, l, len(ll), nresults))
        return ll
- def commandr(tb, cmd, args=(), nresults=None):
+ def commandr(tb, cmd, args=(), nresults=None, unquote=True):
        # pass args=[None,...] or =(None,...) to avoid more url quoting
        if type(cmd) is str: cmd = [cmd]
        if len(args) and args[0] is None: args = args[1:]
@@ -864,8 +898,8 @@ class Testbed:
        al = cmd + args
        tb.send(string.join(al))
        ll = tb.expect('ok', nresults)
-       rl = map(urllib.unquote, ll)
-       return rl
+       if unquote: ll = map(urllib.unquote, ll)
+       return ll
  def command(tb, cmd, args=()):
        tb.commandr(cmd, args, 0)
  def commandr1(tb, cmd, args=()):
@@ -925,6 +959,28 @@ class Testbed:
 
        return rc
 
+ def satisfy_dependencies_string(tb, deps, what):
+       # Must have called Binaries.configure_apt
+       debug('dependencies: %s: satisfying %s' % (what,deps))
+       dsc = TemporaryFile('deps.dsc')
+       print >>open(dsc.write(),'w'), 'Build-Depends: ', deps, '\n\n'
+       # pbuilder-satisfydepends has a bug where it ignores the
+       #  Build-Depends if it's the last line in the dsc (#635696)
+       tb.satisfy_dependencies_dsc(dsc, what)
+
+ def satisfy_dependencies_dsc(tb, dsc, what):
+       # Must have called Binaries.configure_apt
+       cmdl = [ '/usr/lib/pbuilder/pbuilder-satisfydepends-classic',
+               '--binary-all', # --check-key
+               '--internal-chrootexec',tb.ec_auxverbscript.read(),
+               '-c',dsc.read()
+       ]
+       # The --internal-chrootexec option is really handy but
+       #  perhaps we are not supposed to use it ?  See also #635697.
+       debug('dependencies: %s: running %s' % (what,`cmdl`))
+       rc = subprocess.call(cmdl, stdout=None, stderr=None)
+       if rc: badpkg('dependency install failed, exit code %d' % rc)
+
 #---------- representation of test control files: Field*, Test, etc.
 
 class FieldBase:
@@ -1089,9 +1145,10 @@ class Test:
                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
+               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"
@@ -1266,7 +1323,7 @@ def determine_package(act):
        if not act.pkg: badpkg('no good Package: line in control file')
 
 class Binaries:
- def __init__(b):
+ def __init__(b, tb):
        b.dir = TemporaryDir('binaries')
        b.dir.write()
        ok = False
@@ -1314,38 +1371,32 @@ END
 
  def apt_configs(b):
        return {
-               "Dir::Etc::sourcelist": b.dir.read(True)+'sources.list',
                "Debug::pkgProblemResolver": "true",
+               "APT::Get::force-yes" : "true",
+               "APT::Get::Assume-Yes" : "true",
+               "quiet" : "true",
        }
 
- def apt_pkg_gdebi_script(b, arg, middle):
-       script = [
-               'import apt_pkg',
-               'import urllib',
-               'arg = urllib.unquote("%s")' % urllib.quote(arg),
-               ]
+ def _configure_apt(b, tb):
+       config = OutputFile('apt-config','/etc/apt/apt.conf.d/90autopkgtest',
+                       True)
+       f = open(config.write(),'w')
        for (k,v) in b.apt_configs().iteritems():
-               v = urllib.quote(v)
-               script.append('apt_pkg.Config.Set("%s",urllib.unquote("%s"))'
-                               % (k, v))
-       script += [
-               'from GDebi.Cache import Cache',
-               'cache = Cache()',
-               ]
-       for m in middle:
-               script += m + [
-               'print res',
-               'print d.missingDeps',
-               'print d.requiredChanges',
-               'if not res: raise "gdebi failed (%s, %s, %s): %s" % '+
-                       ' (`res`, `d.missingDeps`, `d.requiredChanges`, '+
-                         'd._failureString)',
-               'cache.commit()',
-               ''
-               ]
-       return '\n'.join(script)
- def apt_get(b):
-       ag = ['apt-get','-qy']
+               print >>f, '%s { "%s"; };' % (k, v)
+       f.close()
+       config.read(True)
+
+       prefs = OutputFile('apt-prefs','/etc/apt/preferences.d/90autopkgtest',
+                       True)
+       print >>open(prefs.write(),'w'), '''
+Package: *
+Pin: origin ""
+Pin-Priority: 1002
+'''
+       prefs.read(True)
+       
+ def _apt_get(b):
+       ag = ['apt-get','-q']
        for kv in b.apt_configs().iteritems():
                ag += ['-o', '%s=%s' % kv]
        return ' '.join(ag)
@@ -1387,6 +1438,8 @@ END
  def publish(b):
        b._debug('publish')
 
+       b._configure_apt(testbed)
+
        script = '''
   exec >&2
   cd "$1"
@@ -1408,12 +1461,11 @@ END
        script = '''
   exec 3>&1 >&2
   apt-key add archive-key.pgp
-  echo "deb file://'''+apt_source+''' /" >sources.list
-  cat /etc/apt/sources.list >>sources.list
+  echo "deb file://'''+apt_source+''' /" >/etc/apt/sources.list.d/autopkgtest.list
   if [ "x`ls /var/lib/dpkg/updates`" != x ]; then
     echo >&2 "/var/lib/dpkg/updates contains some files, aargh"; exit 1
   fi
-  '''+ b.apt_get() +''' update >&2
+  '''+ b._apt_get() +''' update >&2
   cat /var/lib/dpkg/status >&3
                '''
        testbed.mungeing_apt()
@@ -1438,7 +1490,7 @@ END
        if pkgs_reinstall:
                for pkg in pkgs_reinstall: testbed.blame(pkg)
                what = 'apt-get-reinstall'
-               cmdl = (b.apt_get() + ' --reinstall install '+
+               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')
@@ -1449,7 +1501,7 @@ END
        for pkg in b.install:
                what = 'apt-get-install-%s' % pkg
                testbed.blame(pkg)
-               cmdl = b.apt_get() + ' install ' + pkg + ' >&2'
+               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"
@@ -1530,15 +1582,8 @@ def build_source(act, control_override):
 
        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)
+               testbed.satisfy_dependencies_string('dpkg-dev',
+                                               'install dpkg-dev')
 
        work = TemporaryDir(what+'-build')
        act.work = work
@@ -1633,24 +1678,9 @@ def build_source(act, control_override):
                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)
+               testbed.satisfy_dependencies_string('build-essential',
+                               'install build-essential')
+               testbed.satisfy_dependencies_dsc(dsc, 'build dependencies')
 
                script = tmpdir_script + [
                        'cd "$2"',
@@ -1666,12 +1696,9 @@ def build_source(act, control_override):
 
                built = True
 
-       act.tests_tree = InputDir(what+'-tests-tree',
-                               work.read(True)+os.path.basename(result_pwd),
+       act.tests_tree = RelativeInputDir(what+'-tests-tree',
+                               workos.path.basename(result_pwd),
                                True)
-       if act.ah['dsc_tests']:
-               testbed.register_ephemeral(act.work)
-               testbed.register_ephemeral(act.tests_tree)
 
        if not built:
                act.blamed = []
@@ -1725,11 +1752,7 @@ def process_actions():
 
        debug_a1('starting')
        testbed.open()
-       binaries = Binaries()
-
-       for act in opts.actions:
-               if act.af is not None and not act.af.spec_tbp:
-                       testbed.register_ephemeral(act.af)
+       binaries = Binaries(testbed)
 
        binaries.reset()
        control_override = None