#!/usr/bin/python3

# this testsuite is part of autopkgtest
# autopkgtest is a tool for testing Debian binary packages
#
# autopkgtest is Copyright (C) 2006-2013 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@ubuntu.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# See the file CREDITS for a full list of credits information (often
# installed as /usr/share/doc/autopkgtest/CREDITS).

import sys
import os
import re
import subprocess
import unittest
import tempfile
import shutil
import fnmatch

test_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(test_dir)

# backwards compat shim for Python 3.1
if not hasattr(unittest.TestCase, 'assertRegex'):
    unittest.TestCase.assertRegex = unittest.TestCase.assertRegexpMatches


class AdtTestCase(unittest.TestCase):
    '''Base class for adt-run tests'''

    def __init__(self, virt, *args, **kwargs):
        super(AdtTestCase, self).__init__(*args, **kwargs)
        self.adt_run_path = os.path.join(root_dir, 'run-from-checkout')
        self.virt = 'adt-virt-' + virt

    def setUp(self):
        self.workdir = tempfile.mkdtemp()
        self.addCleanup(shutil.rmtree, self.workdir)
        self.cwd = os.getcwd()
        self.addCleanup(os.chdir, self.cwd)

        temp_home = os.path.join(self.workdir, 'home')
        shutil.copytree(os.path.join(test_dir, 'home'), temp_home)
        os.environ['HOME'] = temp_home

    def build_src(self, test_control, test_scripts):
        '''Create source package tree with given tests.

         @test_control: contents of debian/tests/control
         @test_scripts: map of test name (in debian/tests/) to file contents

        Return path to the source tree.
        '''
        srcdir = os.path.join(self.workdir, 'testpkg')
        shutil.copytree(os.path.join(test_dir, 'testpkg'), srcdir, symlinks=True)
        if test_control:
            dtdir = os.path.join(srcdir, 'debian', 'tests')
            os.mkdir(dtdir)
            with open(os.path.join(dtdir, 'control'), 'w') as f:
                f.write(test_control)
            for name, contents in test_scripts.items():
                with open(os.path.join(dtdir, name), 'w', encoding='UTF-8') as f:
                    f.write(contents)

        return srcdir

    def build_dsc(self, test_control, test_scripts):
        '''Create source package dsc with given tests.

         @test_control: contents of debian/tests/control
         @test_scripts: map of test name (in debian/tests/) to file contents

        Return path to the dsc.
        '''
        srcdir = self.build_src(test_control, test_scripts)
        dbp = subprocess.Popen(['dpkg-buildpackage', '-S', '-us', '-uc'],
                               stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                               cwd=srcdir)
        out, err = dbp.communicate()
        self.assertEqual(dbp.returncode, 0, err)
        return os.path.join(os.path.dirname(srcdir), 'testpkg_1.dsc')

    def adt_run(self, args, virt_args=[]):
        '''Run adt-run with given arguments with configured virt runner.

         @args: command line args of adt-run, excluding "adt-run" itself; "---
                adt-virt-XXX" will be appended automatically (called from the
                source tree)

        Return a tuple (exit_code, stdout, stderr).
        '''
        # run adt command
        adt = subprocess.Popen([self.adt_run_path] + args +
                               ['---', self.virt] + virt_args,
                               stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        (out, err) = adt.communicate()
        return (adt.returncode, out.decode('UTF-8'), err.decode('UTF-8'))


class NullRunnerNoRoot(AdtTestCase):
    def __init__(self, *args, **kwargs):
        super(NullRunnerNoRoot, self).__init__('null', *args, **kwargs)

    def test_dev_stdouterr_access(self):
        '''write to /dev/stdout and /dev/stderr in a test'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: allow-stderr',
                           {'pass': '#!/bin/sh\necho I am fine >/dev/stdout\n'
                            'echo SomeDebug >/dev/stderr\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p])
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout/err
        self.assertIn('I am fine\n', out)
        # should show test stderr
        self.assertIn('\nSomeDebug\n', err)

    def test_summary(self):
        '''--summary option'''

        p = self.build_src('Tests: good bad\nDepends:\n',
                           {'good': '#!/bin/sh\necho happy\n',
                            'bad': '#!/bin/sh\nexit 1'})

        summary = os.path.join(self.workdir, 'summary.log')

        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--summary=' + summary])

        # test results
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'ubtree0t-good\s+PASS', out)
        self.assertRegex(out, 'ubtree0t-bad\s+FAIL non-zero exit status 1', out)

        # check summary file
        with open(summary) as f:
            self.assertEqual(f.read(), '''ubtree0t-good        PASS
ubtree0t-bad         FAIL non-zero exit status 1
''')

    def test_tree_norestrictions_nobuild_fail_on_stderr_and_exit(self):
        '''source tree, no build, no restrictions, test fails with stderr+exit'''

        p = self.build_src('Tests: senz\nDepends:\n',
                           {'senz': '#!/bin/sh\necho I am sick >&2\nexit 7\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p])
        # test should fail
        self.assertEqual(code, 4)
        self.assertRegex(out, 'tree0t-senz\s+FAIL non-zero exit status 7')

        # should show test stderr, but inline
        self.assertRegex(err, 'stderr [ -]+\nI am sick', err)
        self.assertNotIn('stdout', err)

    def test_tree_allow_stderr_nobuild_success(self):
        '''source tree, no build, allow-stderr, test success'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: allow-stderr',
                           {'pass': '#!/bin/sh\necho I am fine >&2\necho babble'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p])
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout/err
        self.assertIn('babble\n', out)
        self.assertRegex(err, '-+\nI am fine\nadt-run \[[0-9: -]+\]: & tree0t-pass: --')
        # but not complain about stderr
        self.assertNotIn('stderr', err)

    def test_unicode(self):
        '''Unicode test output'''

        p = self.build_src('Tests: se\nDepends:\n',
                           {'se': '#!/bin/sh\necho ‘a♩’; echo fancy ‴ʎɔuɐɟ″!>&2'})

        summary = os.path.join(self.workdir, 'summary.log')

        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--summary=' + summary])
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'ubtree0t-se\s+FAIL status: 0, stderr: fancy ‴ʎɔuɐɟ″!\n')

        # should show test stdout/stderr
        self.assertIn('‘a♩’\n', out)
        self.assertRegex(err, 'stderr [ -]+\nfancy ‴ʎɔuɐɟ″!\n')

        with open(summary, encoding='UTF-8') as f:
            self.assertEqual(f.read(), 'ubtree0t-se          FAIL status: 0, stderr: fancy ‴ʎɔuɐɟ″!\n')

    def test_isolation(self):
        '''isolation restrictions'''

        p = self.build_src('Tests: ic\nDepends:\nRestrictions: isolation-container\n\n'
                           'Tests: im\nDepends:\nRestrictions: isolation-machine\n',
                           {'ic': '#!/bin/sh\necho container ok',
                            'im': '#!/bin/sh\necho machine ok'})

        (code, out, err) = self.adt_run(['-B', '--built-tree=' + p])
        self.assertEqual(code, 0, out + err)
        self.assertRegex(out, 'tree0t-ic\s+PASS', out)
        self.assertRegex(out, 'tree0t-im\s+PASS', out)
        self.assertIn('container ok\n', out)
        self.assertIn('machine ok\n', out)

    def test_no_tests_dir(self):
        '''package without debian/tests/'''

        p = self.build_src(None, None)
        (code, out, err) = self.adt_run(['--unbuilt-tree=' + p])
        self.assertEqual(code, 8, err)
        self.assertRegex(out, 'SKIP no tests in this package', out)


@unittest.skipIf(os.getuid() > 0,
                 'null runner tests need to run as root')
class NullRunner(AdtTestCase):
    def __init__(self, *args, **kwargs):
        super(NullRunner, self).__init__('null', *args, **kwargs)

    def test_tree_norestrictions_nobuild_success(self):
        '''source tree, no build, no restrictions, test success'''

        p = self.build_src('Tests: pass\nDepends: coreutils\n',
                           {'pass': '#!/bin/sh\necho I am fine\n'})

        (code, out, err) = self.adt_run(['-B', '--unbuilt-tree=' + p])
        #print('----- out ----\n%s\n----- err ----\n%s\n----' % (out, err))
        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertIn('coreutils', out)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        self.assertIn('processing dependency coreutils', err)
        # should show test stdout
        self.assertIn('\nI am fine\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should not build package
        self.assertNotIn('dh build', err)

    def test_tree_norestrictions_nobuild_fail_on_exit(self):
        '''source tree, no build, no restrictions, test fails with non-zero'''

        p = self.build_src('Tests: nz\nDepends: coreutils\n',
                           {'nz': '#!/bin/sh\necho I am sick\nexit 7'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p])
        # test should fail
        self.assertEqual(code, 4)
        self.assertIn('coreutils', out)
        self.assertRegex(out, 'tree0t-nz\s+FAIL non-zero exit status 7')

        self.assertIn('processing dependency coreutils', err)
        # should show test stdout
        self.assertIn('\nI am sick\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

    def test_tree_norestrictions_nobuild_fail_on_stderr(self):
        '''source tree, no build, no restrictions, test fails with stderr'''

        p = self.build_src('Tests: se\nDepends: coreutils\n',
                           {'se': '#!/bin/sh\necho I am sick >&2\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p])
        # test should fail
        self.assertEqual(code, 4)
        self.assertIn('coreutils', out)
        self.assertRegex(out, '\ntree0t-se\s+FAIL status: 0, stderr: I am sick\n')

        self.assertIn('processing dependency coreutils', err)
        # should show test stderr
        self.assertRegex(err, 'stderr [ -]+\nI am sick', err)

    def test_tree_allow_stderr_nobuild_fail_on_exit(self):
        '''source tree, no build, allow-stderr, test fails with non-zero'''

        p = self.build_src('Tests: nz\nDepends: coreutils\nRestrictions: allow-stderr',
                           {'nz': '#!/bin/sh\necho I am fine >&2\necho babble\nexit 7'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p])
        # test should fail
        self.assertEqual(code, 4)
        self.assertIn('coreutils', out)
        self.assertRegex(out, 'tree0t-nz\s+FAIL non-zero exit status 7')

        # should show test stdout/err inline
        self.assertRegex(out, '\nbabble\n')
        self.assertRegex(err, '---+\nI am fine\nadt-run', err)
        # but not complain about stderr
        self.assertNotIn('stderr', err)

    def test_tree_build_needed_success(self):
        '''source tree, build-needed restriction, test success'''

        p = self.build_src('Tests: pass\nDepends: coreutils\nRestrictions: build-needed\n',
                           {'pass': '#!/bin/sh -e\n./test_built | grep -q "built script OK"\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p])
        # test should succeed
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should not have any test stdout/stderr
        self.assertNotIn('stderr', err)

        # should build package
        self.assertIn('dh build', err)

    def test_dsc_norestrictions_nobuild_success(self):
        '''dsc, no build, no restrictions, test success'''

        p = self.build_dsc('Tests: pass\nDepends: coreutils\n',
                           {'pass': '#!/bin/sh\necho I am fine\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', p])
        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertIn('coreutils', out)
        self.assertRegex(out, 'dsc0t-pass\s+PASS', out)

        self.assertIn('processing dependency coreutils', err)
        # should show test stdout
        self.assertRegex(out, '\nI am fine\n')
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should log package version
        self.assertIn('adt-run: testing package testpkg version 1\n', err)

    def test_dsc_build_needed_success(self):
        '''dsc, build-needed restriction, test success'''

        p = self.build_dsc('Tests: pass\nDepends: coreutils\nRestrictions: build-needed\n',
                           {'pass': '#!/bin/sh -e\n./test_built | grep -q "built script OK"\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', p])
        # test should succeed
        self.assertRegex(out, 'dsc0t-pass\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should not have any test stdout/stderr
        self.assertNotIn('stdout', err)
        self.assertNotIn('stderr', err)

        # should build package
        self.assertIn('dh build', err)

    def test_tmpdir_for_other_users(self):
        '''$TMPDIR is accessible to non-root users'''

        prev_mask = os.umask(0o077)
        self.addCleanup(os.umask, prev_mask)
        p = self.build_src('Tests: t\nDepends: coreutils\nRestrictions: needs-root\n',
                           {'t': '''#!/bin/sh -e
echo hello > ${TMPDIR:=/tmp}/rootowned.txt
su -s /bin/sh -c "echo hello > $TMPDIR/world.txt" nobody
if su -s /bin/sh -c "echo break > $TMPDIR/rootowned.txt" nobody 2>/dev/null; then
    exit 1
fi
rm $TMPDIR/rootowned.txt $TMPDIR/world.txt
'''})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p])
        #print('----- out ----\n%s\n----- err ----\n%s\n----' % (out, err))
        # test should succeed
        self.assertRegex(out, 'tree0t-t\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should not have any test stdout/stderr
        self.assertNotIn('stdout', err)
        self.assertNotIn('stderr', err)

    def test_user(self):
        '''--user option'''

        p = self.build_src('Tests: t\nDepends:\n',
                           {'t': '''#!/bin/sh -e
                                    echo world > ${TMPDIR:=/tmp}/hello.txt
                                    cat $TMPDIR/hello.txt
                                    rm $TMPDIR/hello.txt
                                    stat -c %U .
                                    stat -c %U debian
                                    stat -c %U Makefile
                                    whoami'''})

        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--user=nobody'])
        self.assertEqual(code, 0, err)

        # test should succeed
        self.assertRegex(out, 'tree0t-t\s+PASS', out)

        # has output from cat and whoami
        self.assertIn('world\nnobody\nnobody\nnobody\nnobody\n', out)

    def test_tree_output_dir(self):
        '''source tree, explicit --output-dir

        This also covers using upper-case lettes in test names.
        '''
        p = self.build_src('Tests: sP sF\nDepends: coreutils\n\n'
                           'Tests: bP\nDepends: coreutils\nRestrictions: build-needed',
                           {'sP': '#!/bin/sh\n./test_static\n',
                            'sF': '#!/bin/sh\necho kaputt >&2',
                            'bP': '#!/bin/sh\n./test_built'})

        outdir = os.path.join(self.workdir, 'out')
        os.mkdir(outdir)
        # put some cruft into it, to check that it gets cleaned up
        with open(os.path.join(outdir, 'cruft.txt'), 'w') as f:
            f.write('hello world')

        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--output-dir=' + outdir])

        # test results
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'ubtree0t-sP\s+PASS', out)
        self.assertRegex(out, 'ubtree0t-sF\s+FAIL status: 0, stderr: kaputt', out)
        self.assertRegex(out, 'ubtree0t-bP\s+PASS', out)

        # should show test stdout and stderr
        self.assertIn('\nstatic script OK\n', out)
        self.assertIn('\nbuilt script OK\n', out)
        self.assertRegex(err, 'stderr [ -]+\nkaputt', err)

        # should build package
        self.assertIn('dh build', err)

        # check outdir test stdout/err
        with open(os.path.join(outdir, 'ubtree0t-sP-stdout')) as f:
            self.assertEqual(f.read(), 'static script OK\n')
        self.assertFalse(os.path.exists(
            os.path.join(outdir, 'ubtree0t-sP-stderr')))
        with open(os.path.join(outdir, 'ubtree0t-bP-stdout')) as f:
            self.assertEqual(f.read(), 'built script OK\n')
        self.assertFalse(os.path.exists(
            os.path.join(outdir, 'ubtree0t-bP-stderr')))
        self.assertFalse(os.path.exists(
            os.path.join(outdir, 'ubtree0t-sF-stdout')))
        with open(os.path.join(outdir, 'ubtree0t-sF-stderr')) as f:
            self.assertEqual(f.read(), 'kaputt\n')

        # check outdir log
        with open(os.path.join(outdir, 'log')) as f:
            contents = f.read()
        self.assertIn('build needed for test bP', contents)
        self.assertIn('dh build', contents)
        self.assertRegex(contents, 'ubtree0t-sF\s+FAIL status: 0, stderr: kaputt')
        self.assertIn('@@@@ tests done', contents)
        self.assertIn('testing package testpkg version 1\n', contents)

        # check test package version
        with open(os.path.join(outdir, 'testpkg-version')) as f:
            contents = f.read()
        self.assertEqual(contents, 'testpkg 1\n')

        # check recorded package lists
        with open(os.path.join(outdir, 'testbed-packages')) as f:
            contents = f.read()
            lines = contents.splitlines()
            self.assertGreater(len(lines), 10)
            self.assertRegex(lines[0], '^[0-9a-z.-]+\t[0-9a-z.~-]+')
            self.assertRegex(lines[1], '^[0-9a-z.-]+\t[0-9a-z.~-]+')
            self.assertIn('bash\t', contents)

        # test don't pull in any additional dependencies
        for t in ['sP', 'sF', 'bP']:
            self.assertEqual(os.path.getsize(os.path.join(
                outdir, 'ubtree0t-%s-packages' % t)), 0)

        # check for cruft in outdir
        # --no-built-binaries, we don't expect any debs
        files = [i for i in os.listdir(outdir)
                 if not fnmatch.fnmatch(i, 'ubtree*-std*') and
                 not fnmatch.fnmatch(i, 'ubtree*-packages')]
        self.assertEqual(set(files), set(['log', 'testpkg-version', 'testbed-packages']))

    def test_timeout(self):
        '''handling test timeout'''

        p = self.build_dsc('Tests: to\nDepends:\n',
                           {'to': '#!/bin/sh\necho start\nsleep 10\necho after_sleep\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--timeout-test=3', p])
        # test should time out
        self.assertEqual(code, 16, err)
        self.assertIn("got `timeout', expected `ok...'", err)
        self.assertNotIn('dsc0t-to', out)

        # should show test stdout
        self.assertIn('start', out)
        # but not let the test finish
        self.assertNotIn('after_sleep', out)

    def test_timeout_no_output(self):
        '''handling test timeout for test without any output'''

        p = self.build_dsc('Tests: to\nDepends:\n',
                           {'to': '#!/bin/sh\nsleep 10\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--timeout-test=3', p])
        # test should time out
        self.assertEqual(code, 16, err)
        self.assertIn("got `timeout', expected `ok...'", err)
        self.assertNotIn('dsc0t-to', out)

    def test_timeout_long_test(self):
        '''long-running test with custom timeouts

        This verifies that the right timeout is being used for tests.
        '''
        p = self.build_dsc('Tests: p\nDepends:\nRestrictions: build-needed',
                           {'p': '#!/bin/sh\necho start\nsleep 5\n./test_built\n'})

        (code, out, err) = self.adt_run(['-B', '--timeout-test=6',
                                         '--timeout-build=20',
                                         '--timeout-install=3', p])
        # test should not time out
        self.assertEqual(code, 0, err)
        self.assertNotIn('timeout', err)

        # should show test stdout
        self.assertIn('start\n', out)
        self.assertIn('built script OK\n', out)

    def test_timeout_long_build(self):
        '''long-running build with custom timeouts

        This verifies that the right timeout is being used for builds.
        '''
        p = self.build_src('Tests: p\nDepends:\nRestrictions: build-needed',
                           {'p': '#!/bin/sh\necho start\n\n./test_built\n'})

        # make build take 4s
        subprocess.check_call(['sed', '-i', '/^build:/ s/$/\\n\\tsleep 4/',
                               os.path.join(p, 'Makefile')])

        (code, out, err) = self.adt_run(['--timeout-test=1', '--timeout-build=30',
                                         '--timeout-install=3', '-B',
                                         '--unbuilt-tree', p])
        # should build package
        self.assertIn('dh build', err)

        # test should not time out
        self.assertEqual(code, 0, err)
        self.assertNotIn('timeout', err)

        # should show test stdout
        self.assertIn('start\n', out)
        self.assertIn('built script OK\n', out)

    def test_timeout_long_build_fail(self):
        '''long-running build times out

        This verifies that the right timeout is being used for builds.
        '''
        p = self.build_src('Tests: p\nDepends:\nRestrictions: build-needed',
                           {'p': '#!/bin/sh\necho start\n\n./test_built\n'})

        # make build take 4s
        subprocess.check_call(['sed', '-i', '/^build:/ s/$/\\n\\tsleep 4/',
                               os.path.join(p, 'Makefile')])

        (code, out, err) = self.adt_run(['--timeout-test=1', '--timeout-build=6',
                                         '--timeout-install=3', '-B',
                                         '--unbuilt-tree', p])
        # should start building package
        self.assertIn('dh build', err)

        # build should time out
        self.assertEqual(code, 16, err)
        self.assertIn("got `timeout', expected `ok...'", err)

        # should not start tests
        self.assertNotIn('start\n', out)

    def test_logfile_success(self):
        '''--log-file option, success'''

        p = self.build_src('Tests: pass\nDepends: coreutils\n',
                           {'pass': '#!/bin/sh\necho I am fine\n'})

        logfile = os.path.join(self.workdir, 'adt.log')
        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--log-file=' + logfile])
        #print('----- out ----\n%s\n----- err ----\n%s\n----' % (out, err))
        # test should succeed
        self.assertEqual(code, 0, err)

        with open(logfile) as f:
            log = f.read()
        self.assertIn('coreutils', out)
        self.assertIn('coreutils', log)
        self.assertRegex(out, 'tree0t-pass\s+PASS')
        self.assertRegex(log, 'tree0t-pass\s+PASS')

        self.assertIn('processing dependency coreutils', err)
        self.assertIn('processing dependency coreutils', log)
        # should show test stdout
        self.assertIn('\nI am fine\n', out)
        self.assertIn('\nI am fine\n', log)
        # should not have any test stderr
        self.assertNotIn('stderr', err)
        self.assertNotIn('stderr', log)

        # should not build package
        self.assertNotIn('dh build', err)

    def test_logfile_failure(self):
        '''--log-file option, failure'''

        p = self.build_src('Tests: nz\nDepends: coreutils\nRestrictions: build-needed\n',
                           {'nz': '#!/bin/sh\n./test_built\necho I am sick >&2\nexit 7'})

        logfile = os.path.join(self.workdir, 'adt.log')
        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--log-file=' + logfile])
        with open(logfile) as f:
            log = f.read()

        # test should fail
        self.assertEqual(code, 4)
        self.assertRegex(out, 'tree0t-nz\s+FAIL non-zero exit status 7')
        self.assertRegex(log, 'tree0t-nz\s+FAIL non-zero exit status 7')

        self.assertIn('processing dependency coreutils', err)
        self.assertIn('processing dependency coreutils', log)

        # should build package
        self.assertIn('dh build', err)
        self.assertIn('dh build', log)

        # should show test stdout
        self.assertIn('\nbuilt script OK\n', out)
        self.assertIn('\nbuilt script OK\n', log)
        # should show test stderr
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')
        self.assertRegex(log, 'stderr [ -]+\nI am sick\n')

    def test_breaks_testbed(self):
        '''breaks-testbed restriction'''

        p = self.build_src('Tests: rambo\nDepends:\nRestrictions: needs-root breaks-testbed',
                           {'rambo': '#!/bin/sh\ntouch /var/tmp/zap\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p])
        # test should be skipped as null runner doesn't provide revert
        self.assertEqual(code, 2)
        self.assertRegex(out, 'rambo\s+SKIP Test breaks testbed')
        self.assertNotIn(out, 'no tests')
        self.assertFalse(os.path.exists('/var/tmp/zap'))

    def test_tree_apply_patches(self):
        '''source tree, 3.0 (quilt) patches get applied'''

        p = self.build_src('Tests: pass\nDepends: coreutils\nRestrictions: build-needed\n',
                           {'pass': '#!/bin/sh -e\ndebian/testpkg/usr/bin/test_built\n'
                            'debian/testpkg/usr/bin/test_static\n'})

        # add patch
        patchdir = os.path.join(p, 'debian', 'patches')
        os.mkdir(patchdir)
        with open(os.path.join(patchdir, '01_hack.patch'), 'w') as f:
            f.write('''--- testpkg.orig/test_static
+++ testpkg/test_static
@@ -1,2 +1,2 @@
 #!/bin/sh
-echo "static script OK"
+echo "static patched script OK"
''')
        with open(os.path.join(patchdir, 'series'), 'w') as f:
            f.write('01_hack.patch')

        # turn into 3.0 (quilt) source
        dsrcdir = os.path.join(p, 'debian', 'source')
        os.mkdir(dsrcdir)
        with open(os.path.join(dsrcdir, 'format'), 'w') as f:
            f.write('3.0 (quilt)\n')

        # run tests, should apply unapplied patches
        (code, out, err) = self.adt_run(['-B', '-d', '--unbuilt-tree=' + p])

        # test should succeed
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should have patched source
        self.assertIn('01_hack.patch\n', err)
        self.assertIn('built script OK\nstatic patched script OK\n', out)

        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should build package
        self.assertIn('dh build', err)


@unittest.skipIf('cowdancer' in os.environ.get('LD_PRELOAD', ''),
                 'chroot tests do not work under cowdancer')
@unittest.skipIf(os.getuid() > 0,
                 'chroot runner needs to run as root')
class ChrootRunner(AdtTestCase):
    def __init__(self, *args, **kwargs):
        super(ChrootRunner, self).__init__('chroot', *args, **kwargs)

    def setUp(self):
        super(ChrootRunner, self).setUp()

        def install_file(path):
            destdir = self.chroot + '/' + os.path.dirname(path)
            if not os.path.exists(destdir):
                os.makedirs(destdir)
            if os.path.isfile(path):
                shutil.copy(path, destdir)
            else:
                subprocess.check_call(['cp', '-a', path, destdir])

        def install_elf(path):
            install_file(path)
            out = subprocess.check_output(['ldd', path], universal_newlines=True)
            libs = set()
            for lib in re.finditer('/[^ ]+', out):
                libs.add(lib.group(0))
            for lib in libs:
                install_file(lib)

        # build a mini-chroot
        self.chroot = os.path.join(self.workdir, 'chroot')
        self.addCleanup(shutil.rmtree, self.chroot)
        install_file('/dev/null')
        install_elf('/bin/sh')
        install_elf('/bin/ls')
        install_elf('/bin/cat')
        install_elf('/bin/rm')
        install_elf('/bin/cp')
        install_elf('/bin/mkdir')
        install_elf('/bin/chmod')
        install_elf('/bin/chown')
        install_elf('/bin/mktemp')
        install_elf('/bin/tar')
        install_elf('/bin/sleep')
        install_elf('/usr/bin/test')
        install_elf('/usr/bin/awk')

        # some fakes
        for cmd in ['dpkg', 'dpkg-query', 'apt-get', 'apt-key', 'apt-cache']:
            p = os.path.join(self.chroot, 'usr', 'bin', cmd)
            with open(p, 'w') as f:
                f.write('#!/bin/sh\necho "fake-%s: $@" >&2\n' % cmd)
                if cmd == 'apt-get':
                    f.write('if [ "$1" = source ]; then cp -r /aptget-src $2-1; fi\n')
            os.chmod(p, 0o755)

        p = os.path.join(self.chroot, 'tmp')
        os.mkdir(p)
        os.chmod(p, 0o177)

    def test_tree_norestrictions_nobuild_success(self):
        '''source tree, no build, no restrictions, test success'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: needs-root\n',
                           {'pass': '#!/bin/sh\necho I am fine\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.chroot])
        #print('----- out ----\n%s\n----- err ----\n%s\n----' % (out, err))
        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout
        self.assertRegex(out, '^I am fine\n')
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should not build package
        self.assertNotIn('dh build', err)

        self.assertNotIn('@@@@@@ test bed setup', err)

        # should log package version
        self.assertIn('adt-run: testing package testpkg version 1\n', err)

    def test_tree_norestrictions_nobuild_fail_on_exit(self):
        '''source tree, no build, no restrictions, test fails with non-zero'''

        p = self.build_src('Tests: nz\nDepends: fancypkg\nRestrictions: needs-root\n',
                           {'nz': '#!/bin/sh\necho I am sick\nexit 7'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.chroot])
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'tree0t-nz\s+FAIL non-zero exit status 7')

        # should show test stdout
        self.assertIn('\nI am sick\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        self.assertIn('processing dependency fancypkg', err)
        self.assertRegex(err, 'fake-apt-get: .*install.*fancypkg')

        # should not build package
        self.assertNotIn('dh build', err)

    def test_tree_norestrictions_nobuild_fail_on_stderr(self):
        '''source tree, no build, no restrictions, test fails with stderr'''

        p = self.build_src('Tests: se\nDepends:\nRestrictions: needs-root\n',
                           {'se': '#!/bin/sh\necho I am sick >&2'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.chroot])
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'ubtree0t-se\s+FAIL status: 0, stderr: I am sick\n')

        # should show test stderr
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')
        # should not have any test stdout
        self.assertNotIn('stdout', err)

    def test_tree_norestrictions_nobuild_fail_on_stderr_and_exit(self):
        '''source tree, no build, no restrictions, test fails with stderr+exit'''

        p = self.build_src('Tests: senz\nDepends:\nRestrictions: needs-root\n',
                           {'senz': '#!/bin/sh\necho I am sick >&2\nexit 7\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p],
                                        [self.chroot])
        # test should fail
        self.assertEqual(code, 4)
        self.assertRegex(out, 'tree0t-senz\s+FAIL non-zero exit status 7')

        # should show test stderr separately (no real-time output for chroot)
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')
        self.assertNotIn('stdout', err)

    def test_tree_allow_stderr_nobuild_success(self):
        '''source tree, no build, allow-stderr, test success'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: allow-stderr needs-root',
                           {'pass': '#!/bin/sh\necho I am fine >&2\necho babble'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p],
                                        [self.chroot])
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS')

        # should show test stdout/err
        self.assertRegex(out, '^babble\n')
        self.assertIn('\nI am fine\n', err)

    def test_fancy_deps(self):
        '''wrapped and versioned test dependencies'''

        p = self.build_src('''Tests: pass
Depends: fancypkg,
         coreutils | vanilla (>= 10),
         chocolate,
Restrictions: needs-root
''', {'pass': '#!/bin/sh\ntrue'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.chroot])
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS')

        self.assertIn('processing dependency fancypkg\n', err)
        self.assertIn('processing dependency coreutils | vanilla (>= 10)\n', err)
        self.assertIn('processing dependency chocolate\n', err)
        self.assertRegex(err, 'fake-apt-get: .*install.*fancypkg coreutils chocolate')

    def test_build_deps(self):
        '''test depends on build dependencies'''

        p = self.build_src('''Tests: pass
Depends: @, testdep1, @builddeps@, testdep2,
# blabla
 testdep3
Restrictions: needs-root
''', {'pass': '#!/bin/sh\ntrue'})

        # add extra build dependencies to testpkg
        subprocess.check_call(['sed', '-i', '/^Build-Depends:/ s/:.*/: bdep1 , bdep2,\\n bdep3, # moo\\n#comment\\n bdep4\\n'
                                            'Build-Depends-Indep: bdep5/',
                               os.path.join(p, 'debian', 'control')])

        (code, out, err) = self.adt_run(['-dB', '--unbuilt-tree=' + p],
                                        [self.chroot])
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS')

        self.assertIn('synthesised dependency testpkg\n', err)
        self.assertIn('processing dependency testdep1\n', err)
        self.assertIn('synthesised dependency bdep1\n', err)
        self.assertIn('synthesised dependency bdep2\n', err)
        self.assertIn('synthesised dependency bdep3\n', err)
        self.assertIn('synthesised dependency bdep4\n', err)
        self.assertIn('synthesised dependency bdep5\n', err)
        self.assertIn('processing dependency testdep2\n', err)
        self.assertIn('processing dependency testdep3\n', err)
        self.assertRegex(err, 'fake-apt-get: .*install.*testpkg testdep1 bdep1 bdep2 bdep3 bdep4 bdep5 make testdep2 testdep3')

    def test_logfile(self):
        '''--log-file option'''

        p = self.build_src('Tests: nz\nDepends: coreutils\nRestrictions: needs-root\n',
                           {'nz': '#!/bin/sh\n./test_static\necho I am sick >&2\nexit 7'})

        logfile = os.path.join(self.workdir, 'adt.log')
        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--log-file=' + logfile],
                                        [self.chroot])
        with open(logfile) as f:
            log = f.read()

        # test should fail
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'tree0t-nz\s+FAIL non-zero exit status 7')
        self.assertRegex(log, 'tree0t-nz\s+FAIL non-zero exit status 7')

        self.assertIn('processing dependency coreutils', err)
        self.assertIn('processing dependency coreutils', log)

        # should show test stdout
        self.assertIn('\nstatic script OK\n', out)
        self.assertIn('\nstatic script OK\n', log)
        # should show test stderr
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')
        self.assertRegex(log, 'stderr [ -]+\nI am sick\n')

    def test_artifacts(self):
        '''tests producing additional artifacts'''

        p = self.build_src('Tests: a1 a2 a3 a4\nDepends:\nRestrictions: needs-root\n',
                           {'a1': '#!/bin/sh -e\n[ -d "$ADT_ARTIFACTS" ]\n'
                                  'echo old > $ADT_ARTIFACTS/health.txt\n',
                            'a2': '#!/bin/sh -e\n[ -d "$ADT_ARTIFACTS" ]\n'
                                  'echo I am fine > $ADT_ARTIFACTS/health.txt\n',
                            'a3': '#!/bin/sh -e\n[ -d "$ADT_ARTIFACTS" ]\n'
                                  'mkdir $ADT_ARTIFACTS/logs\n'
                                  'echo world > $ADT_ARTIFACTS/logs/hello.txt\n',
                            'a4': '#!/bin/sh -e\n[ -d "$ADT_ARTIFACTS" ]\n'
                                  'mkdir $ADT_ARTIFACTS/logs\n'
                                  'echo 42 > $ADT_ARTIFACTS/logs/answer.txt\n'})

        outdir = os.path.join(self.workdir, 'out')

        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--output-dir=' + outdir],
                                        [self.chroot])

        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-a1\s+PASS', out)
        self.assertRegex(out, 'tree0t-a2\s+PASS', out)
        self.assertRegex(out, 'tree0t-a3\s+PASS', out)
        self.assertRegex(out, 'tree0t-a4\s+PASS', out)

        # check for cruft in output dir
        files = [i for i in os.listdir(outdir)
                 if not fnmatch.fnmatch(i, 'ubtree*-std*') and
                 not fnmatch.fnmatch(i, 'ubtree*-packages')]
        self.assertEqual(set(files), set(['log', 'artifacts', 'testpkg-version', 'testbed-packages']))

        # check artifact; a2 should overwrite a1's health.txt
        with open(os.path.join(outdir, 'artifacts', 'health.txt')) as f:
            self.assertEqual(f.read(), 'I am fine\n')
        with open(os.path.join(outdir, 'artifacts', 'logs', 'hello.txt')) as f:
            self.assertEqual(f.read(), 'world\n')
        with open(os.path.join(outdir, 'artifacts', 'logs', 'answer.txt')) as f:
            self.assertEqual(f.read(), '42\n')

    def test_slash_in_test_name(self):
        '''test names must not contain /'''

        p = self.build_src('Tests: pass subdir/p\nDepends:\nRestrictions: needs-root',
                           {'pass': '#!/bin/sh\ntrue'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.chroot])
        # invalid test gets skipped
        self.assertEqual(code, 2, err)
        self.assertRegex(out, 'subdir/p\s+SKIP test name may not contain /.* line 1')

        # valid test still gets run
        self.assertFalse(re.match('pass\s+SKIP', out), out)
        self.assertRegex(out, 'ubtree0t-pass\s+PASS', out)

    def test_breaks_testbed(self):
        '''breaks-testbed restriction'''

        p = self.build_src('Tests: rambo\nDepends:\nRestrictions: needs-root breaks-testbed',
                           {'rambo': '#!/bin/sh\ntouch /zap\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.chroot])
        # test should be skipped as chroot runner doesn't provide revert
        self.assertEqual(code, 2)
        self.assertRegex(out, 'rambo\s+SKIP Test breaks testbed')
        self.assertFalse(os.path.exists(os.path.join(self.chroot, 'zap')))

    def test_unicode(self):
        '''Unicode test output'''

        p = self.build_src('Tests: se\nDepends:\nRestrictions: needs-root\n',
                           {'se': '#!/bin/sh\necho ‘a♩’; echo fancy ‴ʎɔuɐɟ″!>&2'})

        summary = os.path.join(self.workdir, 'summary.log')

        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--summary=' + summary],
                                        [self.chroot])
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'ubtree0t-se\s+FAIL status: 0, stderr: fancy ‴ʎɔuɐɟ″!\n')

        # should show test stdout/stderr
        self.assertRegex(out, '‘a♩’\n')
        self.assertRegex(err, '\nfancy ‴ʎɔuɐɟ″!\n')

        with open(summary, encoding='UTF-8') as f:
            self.assertEqual(f.read(), 'ubtree0t-se          FAIL status: 0, stderr: fancy ‴ʎɔuɐɟ″!\n')

    def test_apt_source_no_restrictions(self):
        '''apt source, no build, no restrictions'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: needs-root\n',
                           {'pass': '#!/bin/sh\necho I am fine\n'})
        # copy that into the chroot where fake apt-get source can find it
        shutil.copytree(p, os.path.join(self.chroot, 'aptget-src'))

        (code, out, err) = self.adt_run(['testpkg'], [self.chroot])
        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'apt0t-pass\s+PASS', out)

        # should show test stdout
        self.assertIn('\nI am fine\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should not build package
        self.assertNotIn('dh build', err)

        self.assertRegex(err, 'fake-apt-get: source testpkg')

    def test_setup_commands_string(self):
        '''--setup-commands with command string'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: needs-root\n',
                           {'pass': '#!/bin/sh -e\n[ -x /bin/cp_cp ]; cat /setup.log\n'})

        (code, out, err) = self.adt_run(['-B', '--unbuilt-tree=' + p,
                                         '--setup-commands', 'sleep 3; cp /bin/cp /bin/cp_cp; '
                                         'echo setup_success > /setup.log',
                                         '--setup-commands', 'cp /bin/cp /bin/cp_cp',
                                         '--timeout-short=1', '--timeout-copy=1'],
                                        [self.chroot])
        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout
        self.assertRegex(out, '^setup_success\n')
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        self.assertIn('@@@@@@ test bed setup', err)

    def test_setup_commands_file(self):
        '''--setup-commands with command file'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: needs-root\n',
                           {'pass': '#!/bin/sh -e\n[ -x /bin/cp_cp ]; cat /setup.log\ncat /s2.log'})

        cmds = os.path.join(self.workdir, 'setup.sh')
        with open(cmds, 'w') as f:
            f.write('cp /bin/cp /bin/cp_cp\necho setup_success > /setup.log\n')
            f.flush()
        cmds2 = os.path.join(self.workdir, 'setup2.sh')
        with open(cmds2, 'w') as f:
            f.write('echo setup2_success > /s2.log\n')
            f.flush()

        (code, out, err) = self.adt_run(['-B', '-d', '--unbuilt-tree=' + p,
                                         '--setup-commands', cmds,
                                         '--setup-commands', cmds2],
                                        [self.chroot])
        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout
        self.assertRegex(out, '^setup_success\n')
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        self.assertIn('@@@@@@ test bed setup', err)

    def test_apt_pocket(self):
        '''--apt-pocket'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: needs-root\n',
                           {'pass': '#!/bin/sh -e\ntest -e /etc/apt/sources.list.d/proposed.list'})

        apt_dir = os.path.join(self.chroot, 'etc', 'apt')
        os.makedirs(os.path.join(apt_dir, 'sources.list.d'))
        with open(os.path.join(apt_dir, 'sources.list'), 'w') as f:
            f.write('''# comment
deb http://my.distro/ fluffy-updates main non-free
deb-src http://my.distro/ fluffy-updates main non-free
deb http://my.distro/ fluffy main non-free
deb-src http://my.distro/ fluffy main non-free
''')

        (code, out, err) = self.adt_run(['-B', '-d', '--unbuilt-tree=' + p,
                                         '--apt-pocket', 'proposed'],
                                        [self.chroot])

        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # verify proposed.list
        with open(os.path.join(apt_dir, 'sources.list.d', 'proposed.list')) as f:
            self.assertEqual(f.read(), '''deb http://my.distro/ fluffy-proposed main non-free
deb-src http://my.distro/ fluffy-proposed main non-free
''')

    def test_isolation(self):
        '''isolation restrictions'''

        p = self.build_src('Tests: ic\nDepends:\nRestrictions: isolation-container\n\n'
                           'Tests: im\nDepends:\nRestrictions: isolation-machine\n',
                           {'ic': '#!/bin/sh\necho container ok',
                            'im': '#!/bin/sh\necho machine ok'})

        (code, out, err) = self.adt_run(['-B', '--built-tree=' + p], [self.chroot])
        self.assertEqual(code, 2, out + err)
        self.assertRegex(out, 'ic\s+SKIP .*container', out)
        self.assertRegex(out, 'im\s+SKIP .*machine', out)
        self.assertNotIn('ok', out)

    def test_tree_garbage(self):
        '''copied source tree contains only expected files'''

        p = self.build_src('Tests: g\nDepends:\nRestrictions: needs-root\n',
                           {'g': '#!/bin/sh\npwd\nLC_ALL=C ls .\n'})

        (code, out, err) = self.adt_run(['-B', '--unbuilt-tree=' + p],
                                        [self.chroot])
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-g\s+PASS', out)

        self.assertRegex(out, '^/tmp/adt-run.*/ubtree0-build/real-tree\n'
                         'Makefile\ndebian\ntest_static\n')
        # should not have any test stderr
        self.assertNotIn('stderr', err)

    def test_no_tests_dir(self):
        '''package without debian/tests/'''

        p = self.build_src(None, None)
        (code, out, err) = self.adt_run(['-B', '--unbuilt-tree=' + p], [self.chroot])
        self.assertEqual(code, 8, err)
        self.assertRegex(out, 'SKIP no tests in this package', out)


@unittest.skipUnless('ADT_TEST_SCHROOT' in os.environ,
                     'Set $ADT_TEST_SCHROOT to an existing schroot')
class SchrootRunner(AdtTestCase):
    def __init__(self, *args, **kwargs):
        super(SchrootRunner, self).__init__('schroot', *args, **kwargs)
        self.schroot_name = os.environ.get('ADT_TEST_SCHROOT')

    def test_tree_norestrictions_nobuild_success(self):
        '''source tree, no build, no restrictions, test success'''

        p = self.build_src('Tests: p1 p2\nDepends: coreutils\nRestrictions: needs-root\n',
                           {'p1': '#!/bin/sh\necho I am fine\n',
                            'p2': '#!/bin/sh\necho I am also fine\n'})

        (code, out, err) = self.adt_run(['-d', '--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.schroot_name])
        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertIn('coreutils', out)
        self.assertRegex(out, 'tree0t-p1\s+PASS', out)
        self.assertRegex(out, 'tree0t-p2\s+PASS', out)

        # should show test stdout
        self.assertIn('\nI am fine\n', out)
        self.assertIn('\nI am also fine\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should not build package
        self.assertNotIn('dh build', err)

    def test_tree_norestrictions_nobuild_fail_on_exit(self):
        '''source tree, no build, no restrictions, test fails with non-zero'''

        p = self.build_src('Tests: nz\nDepends:\nRestrictions: needs-root\n',
                           {'nz': '#!/bin/sh\necho I am sick\nexit 7'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.schroot_name])
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'tree0t-nz\s+FAIL non-zero exit status 7')

        # should show test stdout
        self.assertRegex(out, '^I am sick\n')
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should not build package
        self.assertNotIn('dh build', err)

    def test_tree_norestrictions_nobuild_fail_on_stderr(self):
        '''source tree, no build, no restrictions, test fails with stderr'''

        p = self.build_src('Tests: se\nDepends:\nRestrictions: needs-root\n',
                           {'se': '#!/bin/sh\necho I am sick >&2'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.schroot_name])
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'ubtree0t-se\s+FAIL status: 0, stderr: I am sick\n')

        # should show test stderr
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')
        # should not have any test stdout
        self.assertNotIn('stdout', err)

    def test_tree_norestrictions_nobuild_fail_on_stderr_and_exit(self):
        '''source tree, no build, no restrictions, test fails with stderr+exit'''

        p = self.build_src('Tests: senz\nDepends:\nRestrictions: needs-root\n',
                           {'senz': '#!/bin/sh\necho I am sick >&2\nexit 7\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p],
                                        [self.schroot_name])
        # test should fail
        self.assertEqual(code, 4)
        self.assertRegex(out, 'tree0t-senz\s+FAIL non-zero exit status 7')

        # should show test stderr separately
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')
        self.assertNotIn('stdout', err)

    def test_tree_allow_stderr_nobuild_success(self):
        '''source tree, no build, allow-stderr, test success'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: allow-stderr',
                           {'pass': '#!/bin/sh\necho I am fine >&2\necho babble'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p],
                                        [self.schroot_name])
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout/err
        self.assertRegex(out, '^babble\n')
        self.assertIn('\nI am fine\n', err)
        # stderr test output should be inline
        self.assertNotIn('stderr', err)

    def test_tree_build_needed_success(self):
        '''source tree, build-needed restriction, test success'''

        p = self.build_src('Tests: pass\nDepends: coreutils\nRestrictions: build-needed\n',
                           {'pass': '#!/bin/sh -e\n./test_built | grep -q "built script OK"\necho GOOD'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.schroot_name])
        # test should succeed
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)
        self.assertEqual(code, 0, out + err)

        # should build package
        self.assertIn('dh build', err)

        # should show test stdout
        self.assertIn('\nGOOD\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

    def test_dsc_build_needed_success(self):
        '''dsc, build-needed restriction, test success'''

        p = self.build_dsc('Tests: pass\nDepends: coreutils\nRestrictions: build-needed\n',
                           {'pass': '#!/bin/sh -e\n./test_built | grep -q "built script OK"\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', p],
                                        [self.schroot_name])
        # test should succeed
        self.assertRegex(out, 'dsc0t-pass\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should not have any test stdout/stderr
        self.assertNotIn('stdout', err)
        self.assertNotIn('stderr', err)

        # should build package
        self.assertIn('dh build', err)

    def test_tree_built_binaries(self):
        '''source tree, install built binaries'''

        # no "build-needed" restriction here, it should be built because we
        # need to install the package as a dependency (@)
        p = self.build_src('Tests: pass\n',
                           {'pass': '#!/bin/sh -e\n/usr/bin/test_built | grep -q "built script OK"\necho GOOD'})

        # add extra dependency to testpkg, to ensure autopkgtest does not stop
        # and ask for confirmation in apt-get
        subprocess.check_call(['sed', '-i', '/^Depends:/ s/$/, aspell-doc/',
                               os.path.join(p, 'debian', 'control')])

        (code, out, err) = self.adt_run(['--unbuilt-tree=' + p], [self.schroot_name])

        # should build and install package
        self.assertIn('dh build', err)
        self.assertRegex(out, 'ubtree0t-pass\s+PASS', out)
        self.assertIn('Unpacking testpkg', out)
        self.assertIn('binaries/./testpkg.deb', out)
        self.assertIn('Unpacking aspell-doc', out)
        # does not install Recommends by default
        self.assertNotIn('Unpacking cpp-doc', out)
        self.assertNotIn('cpp-doc', err)

        # test should succeed
        self.assertRegex(out, 'ubtree0t-pass\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should show test stdout
        self.assertIn('\nGOOD\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

    def test_binary_unbuilt_tree(self):
        '''--binary for test, unbuilt tree'''

        p = self.build_src('Tests: pass\n',
                           {'pass': '#!/bin/sh -e\n/usr/bin/test_built'})

        # build the package
        subprocess.check_call(['dpkg-buildpackage', '-b', '-us', '-uc', '-tc'],
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                              cwd=p)
        deb = os.path.join(os.path.dirname(p), 'testpkg_1_all.deb')
        self.assertTrue(os.path.exists(deb))
        # destroy debian/rules, to ensure it does not try to build the package
        # again
        with open(os.path.join(p, 'debian', 'rules'), 'w') as f:
            f.write('bwhahahahano!')

        (code, out, err) = self.adt_run(['-d', '-B', '--binary', deb, '--unbuilt-tree=' + p],
                                        [self.schroot_name])

        # should install package
        self.assertNotIn('dh build', err)
        self.assertIn('Unpacking testpkg', out)
        self.assertIn('binaries/./testpkg.deb', out)

        # test should succeed
        self.assertRegex(out, '-pass\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should show test stdout
        self.assertIn('\nbuilt script OK\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # our deb should still be there
        self.assertTrue(os.path.exists(deb))

    def test_binary_built_tree(self):
        '''--binary for test, built tree'''

        # we test both for the installed package as well as the built tree
        p = self.build_src('Tests: pass\n',
                           {'pass': '#!/bin/sh -e\n/usr/bin/test_built\n./test_built'})

        # build the package
        subprocess.check_call(['dpkg-buildpackage', '-b', '-us', '-uc'],
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                              cwd=p)
        deb = os.path.join(os.path.dirname(p), 'testpkg_1_all.deb')
        self.assertTrue(os.path.exists(deb))
        # destroy debian/rules, to ensure it does not try to build the package
        # again
        with open(os.path.join(p, 'debian', 'rules'), 'w') as f:
            f.write('bwhahahahano!')

        # --no-built-binaries should be implied here
        (code, out, err) = self.adt_run(['-d', deb, '--built-tree=' + p],
                                        [self.schroot_name])

        # should install package
        self.assertNotIn('dh build', err)
        self.assertIn('Unpacking testpkg', out)
        self.assertIn('binaries/./testpkg.deb', out)

        # test should succeed
        self.assertRegex(out, '-pass\s+PASS', out + err)
        self.assertEqual(code, 0, out + err)

        # should show test stdout
        self.assertIn('\nbuilt script OK\nbuilt script OK', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # our deb should still be there
        self.assertTrue(os.path.exists(deb))

    def test_logfile(self):
        '''--log-file option'''

        p = self.build_src('Tests: nz\nDepends: coreutils\nRestrictions: build-needed\n',
                           {'nz': '#!/bin/sh\n./test_built\necho I am sick >&2\nexit 7'})

        logfile = os.path.join(self.workdir, 'adt.log')
        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--log-file=' + logfile],
                                        [self.schroot_name])
        with open(logfile) as f:
            log = f.read()

        # test should fail
        self.assertEqual(code, 4)
        self.assertRegex(out, 'tree0t-nz\s+FAIL non-zero exit status 7')
        self.assertRegex(log, 'tree0t-nz\s+FAIL non-zero exit status 7')

        self.assertIn('processing dependency coreutils', err)
        self.assertIn('processing dependency coreutils', log)

        # should build package
        self.assertIn('dh build', err)
        self.assertIn('dh build', log)

        # should show test stdout
        self.assertIn('\nbuilt script OK\n', out)
        self.assertIn('\nbuilt script OK\n', log)
        # should show test stderr
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')
        self.assertRegex(log, 'stderr [ -]+\nI am sick\n')

    def test_tree_output_dir(self):
        '''source tree, explicit --output-dir'''

        p = self.build_src('Tests: ok\n\nTests: broken\nDepends: @, aspell-doc',
                           {'ok': '#!/bin/sh\n/usr/bin/test_built',
                            'broken': '#!/bin/sh\necho kaputt >&2'})

        outdir = os.path.join(self.workdir, 'out')

        (code, out, err) = self.adt_run(['--unbuilt-tree=' + p,
                                         '--output-dir=' + outdir],
                                        [self.schroot_name])

        # test results
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'ubtree0t-ok\s+PASS', out)
        self.assertRegex(out, 'ubtree0t-broken\s+FAIL status: 0, stderr: kaputt', out)

        # should show test stdout and stderr
        self.assertIn('\nbuilt script OK\n', out)
        self.assertRegex(err, 'stderr [ -]+\nkaputt', err)

        # should build package
        self.assertIn('dh build', err)

        # check outdir test stdout/err
        with open(os.path.join(outdir, 'ubtree0t-ok-stdout')) as f:
            self.assertEqual(f.read(), 'built script OK\n')
        self.assertFalse(os.path.exists(
            os.path.join(outdir, 'ubtree0t-ok-stderr')))
        self.assertFalse(os.path.exists(
            os.path.join(outdir, 'ubtree0t-broken-stdout')))
        with open(os.path.join(outdir, 'ubtree0t-broken-stderr')) as f:
            self.assertEqual(f.read(), 'kaputt\n')

        # check outdir log
        with open(os.path.join(outdir, 'log')) as f:
            contents = f.read()
        self.assertIn('dh build', contents)
        self.assertRegex(contents, 'ubtree0t-broken\s+FAIL status: 0, stderr: kaputt')
        self.assertIn('@@@@ tests done', contents)

        # check test package version
        with open(os.path.join(outdir, 'testpkg-version')) as f:
            contents = f.read()
        self.assertEqual(contents, 'testpkg 1\n')

        # check recorded package lists
        with open(os.path.join(outdir, 'testbed-packages')) as f:
            contents = f.read()
            lines = contents.splitlines()
            self.assertGreater(len(lines), 10)
            self.assertRegex(lines[0], '^[0-9a-z.-]+\t[0-9a-z.~-]+')
            self.assertRegex(lines[1], '^[0-9a-z.-]+\t[0-9a-z.~-]+')
            self.assertIn('bash\t', contents)

        # check recorded package list
        for t in ['ok', 'broken']:
            with open(os.path.join(outdir, 'ubtree0t-%s-packages' % t)) as f:
                contents = f.read()
                self.assertIn('testpkg\t1\n', contents)
                if t == 'broken':
                    self.assertIn('aspell-doc\t', contents)
                else:
                    self.assertNotIn('aspell-doc\t', contents)

        # check binaries
        bins = os.listdir(os.path.join(outdir, 'binaries'))
        self.assertEqual(set(bins),
                         set(['Release.gpg', 'archive-key.pgp', 'Release',
                              'Packages.gz', 'Packages', 'testpkg.deb']))

        # check for cruft in outdir
        files = [i for i in os.listdir(outdir)
                 if not fnmatch.fnmatch(i, 'ubtree*-std*') and
                 not fnmatch.fnmatch(i, 'ubtree*-packages')]
        self.assertEqual(set(files), set(['log', 'binaries', 'testpkg-version', 'testbed-packages']))

    def test_user(self):
        '''--user option'''

        p = self.build_src('Tests: t\nDepends: aspell-doc\n',
                           {'t': '''#!/bin/sh -e
                                    echo world > ${TMPDIR:=/tmp}/hello.txt
                                    cat $TMPDIR/hello.txt
                                    whoami'''})

        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--user=nobody'],
                                        [self.schroot_name])
        self.assertEqual(code, 0, err)

        # should install dependencies
        self.assertIn('processing dependency aspell-doc', err)
        self.assertIn('Unpacking aspell-doc', out)

        # test should succeed
        self.assertRegex(out, 'tree0t-t\s+PASS', out)

        # has output from cat and whoami
        self.assertIn('\nworld\nnobody\n', out)

    def test_user_needs_root(self):
        '''--user option with needs-root restriction'''

        p = self.build_src('Tests: t\nDepends: aspell-doc\nRestrictions: needs-root',
                           {'t': '''#!/bin/sh -e
                                    echo world > ${TMPDIR:=/tmp}/hello.txt
                                    cat $TMPDIR/hello.txt
                                    whoami'''})

        (code, out, err) = self.adt_run(['--no-built-binaries',
                                         '--unbuilt-tree=' + p,
                                         '--user=nobody'],
                                        [self.schroot_name])
        self.assertEqual(code, 0, err)

        # should install dependencies
        self.assertIn('processing dependency aspell-doc', err)
        self.assertIn('Unpacking aspell-doc', out)

        # test should succeed
        self.assertRegex(out, 'tree0t-t\s+PASS', out)

        # has output from cat and whoami
        self.assertIn('\nworld\nroot\n', out)

    def test_breaks_testbed(self):
        '''breaks-testbed restriction'''

        p = self.build_src('Tests: rambo\nDepends:\nRestrictions: needs-root breaks-testbed',
                           {'rambo': '#!/bin/sh\ntouch /zap\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.schroot_name])
        # test should be skipped as chroot runner doesn't provide revert
        self.assertEqual(code, 2)
        self.assertRegex(out, 'rambo\s+SKIP Test breaks testbed')

    def test_isolation(self):
        '''isolation restrictions'''

        p = self.build_src('Tests: ic\nDepends:\nRestrictions: isolation-container\n\n'
                           'Tests: im\nDepends:\nRestrictions: isolation-machine\n',
                           {'ic': '#!/bin/sh\necho container ok',
                            'im': '#!/bin/sh\necho machine ok'})

        (code, out, err) = self.adt_run(['-B', '--built-tree=' + p],
                                        [self.schroot_name])
        self.assertEqual(code, 2, out + err)
        self.assertRegex(out, 'ic\s+SKIP .*container', out)
        self.assertRegex(out, 'im\s+SKIP .*machine', out)
        self.assertNotIn('ok', out)


@unittest.skipUnless('ADT_TEST_LXC' in os.environ,
                     'Set $ADT_TEST_LXC to an existing container')
class LxcRunner(AdtTestCase):
    def __init__(self, *args, **kwargs):
        super(LxcRunner, self).__init__('lxc', *args, **kwargs)
        self.container_name = os.environ.get('ADT_TEST_LXC')
        self.virt_args = ['--ephemeral', self.container_name]
        if os.getuid() > 0:
            self.virt_args.insert(0, '--sudo')

    def test_tree_norestrictions_nobuild_success(self):
        '''source tree, no build, no restrictions, test success'''

        p = self.build_src('Tests: pass\nDepends: coreutils\n',
                           {'pass': '#!/bin/sh\necho I am fine\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        self.virt_args)
        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertIn('coreutils', out)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout
        self.assertIn('\nI am fine\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should not build package
        self.assertNotIn('dh build', err)

    def test_tree_norestrictions_nobuild_fail_on_exit(self):
        '''source tree, no build, no restrictions, test fails with non-zero'''

        p = self.build_src('Tests: nz\nDepends:\n',
                           {'nz': '#!/bin/sh\necho I am sick\nexit 7'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        self.virt_args)
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'tree0t-nz\s+FAIL non-zero exit status 7')

        # should show test stdout
        self.assertRegex(out, '^I am sick\n')
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should not build package
        self.assertNotIn('dh build', err)

    def test_tree_norestrictions_nobuild_fail_on_stderr(self):
        '''source tree, no build, no restrictions, test fails with stderr'''

        p = self.build_src('Tests: se\nDepends:\n',
                           {'se': '#!/bin/sh\necho I am sick >&2'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        self.virt_args)
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'ubtree0t-se\s+FAIL status: 0, stderr: I am sick\n')

        # should show test stderr
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')
        # should not have any test stdout
        self.assertNotIn('stdout', err)

    def test_tree_norestrictions_nobuild_fail_on_stderr_and_exit(self):
        '''source tree, no build, no restrictions, test fails with stderr+exit'''

        p = self.build_src('Tests: senz\nDepends:\n',
                           {'senz': '#!/bin/sh\necho I am sick >&2\nexit 7\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p],
                                        self.virt_args)
        # test should fail
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'tree0t-senz\s+FAIL non-zero exit status 7')

        # should show test stderr separately (no real-time output for chroot)
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')
        self.assertNotIn('stdout', err)

    def test_tree_allow_stderr_nobuild_success(self):
        '''source tree, no build, allow-stderr, test success'''

        p = self.build_src('Tests: pass\nDepends:\nRestrictions: allow-stderr',
                           {'pass': '#!/bin/sh\necho I am fine >&2\necho babble'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p],
                                        self.virt_args)
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout/err
        self.assertRegex(out, '^babble\n')
        # stderr test output should be inline
        self.assertIn('\nI am fine\n', err)
        self.assertNotIn('stderr', err)

    def test_tree_build_needed_success(self):
        '''source tree, build-needed restriction, test success'''

        p = self.build_src('Tests: pass\nDepends: coreutils\nRestrictions: build-needed\n',
                           {'pass': '#!/bin/sh -e\n./test_built | grep -q "built script OK"\necho GOOD'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        self.virt_args)
        # test should succeed
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should build package
        self.assertIn('dh build', err)

        # should show test stdout
        self.assertIn('\nGOOD\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

    def test_dsc_build_needed_success(self):
        '''dsc, build-needed restriction, test success'''

        p = self.build_dsc('Tests: pass\nDepends: coreutils\nRestrictions: build-needed\n',
                           {'pass': '#!/bin/sh -e\n./test_built | grep -q "built script OK"\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', p],
                                        self.virt_args)
        # test should succeed
        self.assertRegex(out, 'dsc0t-pass\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should not have any test stdout/stderr
        self.assertNotIn('stdout', err)
        self.assertNotIn('stderr', err)

        # should build package
        self.assertIn('dh build', err)

    def test_tree_built_binaries(self):
        '''source tree, install built binaries'''

        # no "build-needed" restriction here, it should be built because we
        # need to install the package as a dependency (@)
        p = self.build_src('Tests: pass\n',
                           {'pass': '#!/bin/sh -e\n/usr/bin/test_built | grep -q "built script OK"\necho GOOD'})

        # add extra dependency to testpkg, to ensure autopkgtest does not stop
        # and ask for confirmation in apt-get
        subprocess.check_call(['sed', '-i', '/^Depends:/ s/$/, aspell-doc/',
                               os.path.join(p, 'debian', 'control')])

        (code, out, err) = self.adt_run(['--unbuilt-tree=' + p],
                                        self.virt_args)

        # should build and install package
        self.assertIn('dh build', err)
        self.assertRegex(out, 'ubtree0t-pass\s+PASS', out)
        self.assertIn('binaries/./testpkg.deb', out)
        self.assertRegex(out, '\.\.\./aspell-doc_\d.*_all.deb')

        # test should succeed
        self.assertRegex(out, 'ubtree0t-pass\s+PASS', out)
        self.assertEqual(code, 0, err)

        # should show test stdout
        self.assertIn('\nGOOD\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

    def test_tree_clone(self):
        '''source tree, no --ephemeral option (using clone)'''

        p = self.build_src('Tests: senz\nDepends:\n',
                           {'senz': '#!/bin/sh\necho hello\necho I am sick >&2\nexit 7\n'})

        virt_args = [a for a in self.virt_args if a != '--ephemeral']
        (code, out, err) = self.adt_run(['--no-built-binaries', '--built-tree=' + p],
                                        virt_args)
        # test should fail
        self.assertEqual(code, 4, err)
        self.assertRegex(out, 'tree0t-senz\s+FAIL non-zero exit status 7')

        # should show test stdout inline
        self.assertRegex(out, '^hello\n')
        self.assertNotIn('stdout', err)

        # should show test stderr separately
        self.assertRegex(err, 'stderr [ -]+\nI am sick\n')

    def test_breaks_testbed(self):
        '''breaks-testbed restriction'''

        p = self.build_src('Tests: zap boom\nDepends:\nRestrictions: needs-root breaks-testbed',
                           {'zap': '#!/bin/sh\ntouch /zap\n',
                            'boom': '#!/bin/sh\n[ ! -e /zap ]; touch /boom'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p, '-d'],
                                        self.virt_args)
        # both tests should run; the second one (boom) should not see the
        # effect of the first one (/zap existing)
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'ubtree0t-zap\s+PASS')
        self.assertRegex(out, 'ubtree0t-boom\s+PASS')

    def test_setup_commands(self):
        '''--setup-commands'''

        p = self.build_src('Tests: t1 t2\nDepends:\n',
                           {'t1': '#!/bin/sh -e\ncat /setup.log; echo t1ok\n',
                            't2': '#!/bin/sh -e\ncat /setup.log; echo t2ok\n'})

        (code, out, err) = self.adt_run(['-B', '-d', p + '//',
                                         '--setup-commands', 'echo setupok >> /setup.log'],
                                        self.virt_args)

        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-t1\s+PASS', out)
        self.assertRegex(out, 'tree0t-t2\s+PASS', out)

        # should show test stdout
        self.assertRegex(out, '^setupok\nt1ok\n')
        self.assertIn('\nsetupok\nt2ok\n', out)
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        self.assertIn('@@@@@@ test bed setup', err)

    def test_isolation(self):
        '''isolation restrictions'''

        p = self.build_src('Tests: ic\nDepends:\nRestrictions: isolation-container\n\n'
                           'Tests: im\nDepends:\nRestrictions: isolation-machine\n',
                           {'ic': '#!/bin/sh\necho container ok',
                            'im': '#!/bin/sh\necho machine ok'})

        (code, out, err) = self.adt_run(['-B', '--built-tree=' + p], self.virt_args)
        self.assertEqual(code, 2, out + err)
        self.assertRegex(out, 'tree0t-ic\s+PASS', out)
        self.assertRegex(out, 'im\s+SKIP .*machine', out)
        self.assertIn('container ok\n', out)
        self.assertNotIn('machine ok', out)


@unittest.skipUnless('ADT_TEST_QEMU' in os.environ,
                     'Set $ADT_TEST_QEMU to an existing autopkgtest QEMU image')
class QemuRunner(AdtTestCase):
    def __init__(self, *args, **kwargs):
        super(QemuRunner, self).__init__('qemu', *args, **kwargs)
        self.image = os.environ.get('ADT_TEST_QEMU')

    def test_tree_norestrictions_nobuild_success(self):
        '''source tree, no build, no restrictions, test success'''

        p = self.build_src('Tests: pass\nDepends: coreutils\n',
                           {'pass': '#!/bin/sh\necho I am fine\n'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p],
                                        [self.image])
        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertIn('coreutils', out)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout
        self.assertRegex(err, 'stdout [ -]+\nI am fine\n')
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should not build package
        self.assertNotIn('dh build', err)

    def test_tree_build_needed_success(self):
        '''source tree, build-needed restriction, test success'''

        p = self.build_src('Tests: pass\nRestrictions: build-needed\n',
                           {'pass': '#!/bin/sh -e\ntest_built\n./test_built\n'})

        (code, out, err) = self.adt_run(['--unbuilt-tree=' + p],
                                        [self.image])

        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-pass\s+PASS', out)

        # should show test stdout
        self.assertRegex(err, 'stdout [ -]+\nbuilt script OK\nbuilt script OK\n')

        # should not have any test stderr
        self.assertNotIn('stderr', err)

        # should build package
        self.assertIn('dh build', err)

    def test_breaks_testbed(self):
        '''breaks-testbed restriction'''

        p = self.build_src('Tests: zap boom\nDepends:\nRestrictions: needs-root breaks-testbed',
                           {'zap': '#!/bin/sh\ntouch /zap\n',
                            'boom': '#!/bin/sh\n[ ! -e /zap ]; touch /boom'})

        (code, out, err) = self.adt_run(['--no-built-binaries', '--unbuilt-tree=' + p, '-d'],
                                        [self.image])
        # both tests should run; the second one (boom) should not see the
        # effect of the first one (/zap existing)
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'ubtree0t-zap\s+PASS')
        self.assertRegex(out, 'ubtree0t-boom\s+PASS')

    def test_setup_commands(self):
        '''--setup-commands'''

        p = self.build_src('Tests: t1 t2\nDepends:\n',
                           {'t1': '#!/bin/sh -e\ncat /setup.log; echo t1ok\n',
                            't2': '#!/bin/sh -e\ncat /setup.log; echo t2ok\n'})

        (code, out, err) = self.adt_run(['-B', '-d', p + '//',
                                         '--setup-commands', 'echo setupok >> /setup.log'],
                                        [self.image])

        # test should succeed
        self.assertEqual(code, 0, err)
        self.assertRegex(out, 'tree0t-t1\s+PASS', out)
        self.assertRegex(out, 'tree0t-t2\s+PASS', out)

        # should show test stdout
        self.assertRegex(err, 'stdout [ -]+\nsetupok\nt1ok\n')
        self.assertRegex(err, 'stdout [ -]+\nsetupok\nt2ok\n')
        # should not have any test stderr
        self.assertNotIn('stderr', err)

        self.assertIn('@@@@@@ test bed setup', err)

    def test_isolation(self):
        '''isolation restrictions'''

        p = self.build_src('Tests: ic\nDepends:\nRestrictions: isolation-container\n\n'
                           'Tests: im\nDepends:\nRestrictions: isolation-machine\n',
                           {'ic': '#!/bin/sh\necho container ok',
                            'im': '#!/bin/sh\necho machine ok'})

        (code, out, err) = self.adt_run(['-B', '--built-tree=' + p], [self.image])
        self.assertEqual(code, 0, out + err)
        self.assertRegex(out, 'tree0t-ic\s+PASS', out)
        self.assertRegex(out, 'tree0t-im\s+PASS', out)
        self.assertRegex(err, 'stdout [ -]+\ncontainer ok\n')
        self.assertRegex(err, 'stdout [ -]+\nmachine ok\n')


if __name__ == '__main__':
    unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
