#!/usr/bin/python3

import os
import sys
import io
import argparse
import unittest
import tempfile

try:
    # Python >= 3.3
    from unittest.mock import patch
    patch  # pyflakes
except ImportError:
    # fall back to separate package
    from mock import patch

test_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(1, os.path.join(os.path.dirname(test_dir), 'lib'))

import autopkgtest_args
import adt_testbed


class T(unittest.TestCase):
    def setUp(self):
        self.workdir = tempfile.TemporaryDirectory(prefix='run_args.')
        os.chdir(self.workdir.name)
        self.orig_timeouts = adt_testbed.timeouts.copy()
        self.testdsc = 'testsrc_1.dsc'
        with open(self.testdsc, 'w'):
            pass
        self.testdeb1 = 'testbin1_0.1_all.deb'
        with open(self.testdeb1, 'w'):
            pass
        self.testdeb2 = 'testbin2_0.1_all.deb'
        with open(self.testdeb2, 'w'):
            pass
        self.testtree = './testsrc-1'
        os.makedirs(os.path.join(self.testtree, 'debian'))
        with open(os.path.join(self.testtree, 'debian', 'control'), 'w'):
            pass
        self.testclick = os.path.join(test_dir, 'testclick_0.1_all.click')
        self.testclicksrc = os.path.join(test_dir, 'testclick')

    def tearDown(self):
        adt_testbed.timeouts = self.orig_timeouts

    def parse(self, args, default_virt=True):
        if default_virt and '--' not in args:
            # append a default virt server for convenience
            args += ['--', 'null']
        return autopkgtest_args.parse_args(args)

    @patch('argparse.ArgumentParser.error')
    def err(self, arguments, err_re, *args, default_virt=True, **kwargs):
        acts = self.parse(arguments, default_virt)[1]
        self.assertGreaterEqual(argparse.ArgumentParser.error.call_count, 1)
        self.assertRegex(argparse.ArgumentParser.error.call_args_list[0][0][0],
                         err_re)

    #
    # Test actions
    #

    def test_dsc(self):
        (args, acts, virt) = self.parse([self.testdsc])
        self.assertTrue(isinstance(args, argparse.Namespace))
        self.assertEqual(acts, [('source', self.testdsc, True)])
        self.assertEqual(virt, ['autopkgtest-virt-null'])

    def test_dsc_with_debs(self):
        (args, acts, virt) = self.parse(
            [self.testdeb1, self.testdsc, self.testdeb2])
        # debs should come first, and no build
        self.assertEqual(acts, [('binary', self.testdeb1, None),
                                ('binary', self.testdeb2, None),
                                ('source', self.testdsc, False)])

    def test_dsc_notexist(self):
        self.err(['bogus_1.dsc'], 'not a valid test package')

    def test_unbuilt_tree(self):
        (args, acts, virt) = self.parse([self.testtree])
        self.assertEqual(acts, [('unbuilt-tree', self.testtree, True)])
        self.assertEqual(virt, ['autopkgtest-virt-null'])

    def test_unbuilt_tree_with_debs(self):
        (args, acts, virt) = self.parse([self.testtree, self.testdeb1])
        self.assertEqual(acts, [('binary', self.testdeb1, None),
                                ('unbuilt-tree', self.testtree, False)])

    def test_unbuilt_tree_notexist(self):
        self.err(['/tmp/bogus-1'], 'not a valid test package')

    def test_built_tree(self):
        with open(os.path.join(self.testtree, 'debian', 'files'), 'w'):
            pass
        (args, acts, virt) = self.parse([self.testtree])
        self.assertEqual(acts, [('built-tree', self.testtree, False)])
        self.assertEqual(virt, ['autopkgtest-virt-null'])

    def test_apt_source(self):
        acts = self.parse(['mypkg'])[1]
        self.assertEqual(acts, [('apt-source', 'mypkg', False)])

    def test_apt_source_trumps_dir(self):
        os.makedirs('mypkg/debian')
        with open(os.path.join('mypkg', 'debian', 'control'), 'w'):
            pass
        acts = self.parse(['mypkg'])[1]
        self.assertEqual(acts, [('apt-source', 'mypkg', False)])

    def test_git_source(self):
        acts = self.parse(['git://dev.org/proj1.git#stable'])[1]
        self.assertEqual(acts, [('git-source', 'git://dev.org/proj1.git#stable', True)])

    def test_git_source_nobuild(self):
        acts = self.parse(['-B', 'git://dev.org/proj1.git#stable'])[1]
        self.assertEqual(acts, [('git-source', 'git://dev.org/proj1.git#stable', False)])

    def test_click_source(self):
        acts = self.parse([self.testclicksrc])[1]
        self.assertEqual(acts, [('click-source', self.testclicksrc, True)])

    def test_click_binary(self):
        acts = self.parse([self.testclick])[1]
        self.assertEqual(acts, [('click', self.testclick, None)])

    def test_click_source_with_binary(self):
        acts = self.parse([self.testclicksrc, self.testclick])[1]
        self.assertEqual(acts, [('click-source', self.testclicksrc, False),
                                ('click', self.testclick, None)])

        # other way around
        acts = self.parse([self.testclick, self.testclicksrc])[1]
        self.assertEqual(acts, [('click-source', self.testclicksrc, False),
                                ('click', self.testclick, None)])

    def test_installed_click_source(self):
        acts = self.parse([self.testclicksrc, '--installed-click', 'com.example.click'])[1]
        self.assertEqual(acts, [('click-source', self.testclicksrc, False),
                                ('click', 'com.example.click', None)])

    def test_installed_click_nosource(self):
        acts = self.parse(['--installed-click', 'com.example.click'])[1]
        self.assertEqual(acts, [('click', 'com.example.click', None)])

    def test_changes(self):
        ch = os.path.join(self.workdir.name, 'foo.changes')
        with open(ch, 'w') as f:
            f.write('''Format: 1.8
Source: testpkg
Binary: testpkg
Files:
 deadbeef 10000 utils optional testbin1_0.1_all.deb
 deadbeef 100 utils optional testsrc_1.dsc
 deadbeef 20000 utils optional testsrc_1.tar.gz
 deadbeef 10000 utils optional testbin2_0.1_all.deb
''')

        acts = self.parse([ch])[1]
        self.assertEqual(
            acts,
            [('binary', os.path.join(self.workdir.name, 'testbin1_0.1_all.deb'), None),
             ('binary', os.path.join(self.workdir.name, 'testbin2_0.1_all.deb'), None),
             ('source', os.path.join(self.workdir.name, 'testsrc_1.dsc'), False),
            ])

    def test_changes_no_source(self):
        ch = os.path.join(self.workdir.name, 'foo.changes')
        with open(ch, 'w') as f:
            f.write('''Format: 1.8
Source: testpkg
Binary: testpkg
Files:
 deadbeef 10000 utils optional testbin1_0.1_all.deb
 deadbeef 10000 utils optional testbin2_0.1_all.deb
''')

        self.err([ch], 'must specify .*source.* to test')

    def test_cwd_unbuilt(self):
        os.chdir(self.testtree)
        (args, acts, virt) = self.parse([])
        self.assertEqual(acts, [('unbuilt-tree', '.', True)])
        self.assertEqual(virt, ['autopkgtest-virt-null'])

    def test_cwd_built(self):
        os.chdir(self.testtree)
        with open(os.path.join('debian', 'files'), 'w'):
            pass
        (args, acts, virt) = self.parse([])
        self.assertEqual(acts, [('built-tree', '.', False)])
        self.assertEqual(virt, ['autopkgtest-virt-null'])

    def test_cwd_notest(self):
        self.err([], 'must specify .*source.* to test')

    def test_multiple_tests(self):
        self.err([self.testdsc, self.testtree], 'must specify only one source')

    def test_only_debs(self):
        self.err([self.testdeb1], 'must specify .*source.* to test')


    #
    # Test options
    #

    def test_testname(self):
        # --testname is deprecated; use --test-name
        (args, acts, virt) = self.parse(['--testname', 'foo', 'mypkg'])
        self.assertEqual(acts, [('apt-source', 'mypkg', False)])
        self.assertEqual(args.testname, 'foo')

    def test_test_name(self):
        # --testname is deprecated; use --test-name
        (args, acts, virt) = self.parse(['--test-name', 'foo', 'mypkg'])
        self.assertEqual(acts, [('apt-source', 'mypkg', False)])
        self.assertEqual(args.testname, 'foo')

    def test_default_options(self):
        args = self.parse(['mypkg'])[0]
        self.assertEqual(args.verbosity, 1)
        self.assertEqual(args.shell_fail, False)
        self.assertEqual(adt_testbed.timeouts['test'], 10000)
        self.assertEqual(adt_testbed.timeouts['copy'], 300)
        self.assertEqual(args.testname, None)

    def test_options(self):
        (args, acts, virt) = self.parse(
            ['-q', '--shell-fail', '--timeout-copy=5', '--set-lang',
             'en_US.UTF-8', 'mypkg',
             '--', 'foo', '-d', '-s', '--', '-d'])
        self.assertEqual(args.verbosity, 0)
        self.assertEqual(args.shell_fail, True)
        self.assertEqual(adt_testbed.timeouts['copy'], 5)
        self.assertEqual(args.env, ['LANG=en_US.UTF-8'])
        self.assertEqual(args.auto_control, True)

        self.assertEqual(acts, [('apt-source', 'mypkg', False)])
        self.assertEqual(virt, ['autopkgtest-virt-foo', '-d', '-s', '--', '-d'])

    def test_timeouts(self):
        (args, acts, virt) = self.parse(
            ['--timeout-short=37', '--timeout-factor=0.5',
             '--timeout-build=1337', 'mypkg', '--', 'null'])
        # explicit, not affected by factor
        self.assertEqual(adt_testbed.timeouts['short'], 37)
        self.assertEqual(adt_testbed.timeouts['build'], 1337)
        # default, with factor
        self.assertEqual(adt_testbed.timeouts['copy'], 150)
        self.assertEqual(adt_testbed.timeouts['test'], 5000)

    def test_read_file(self):
        argfile = os.path.join(self.workdir.name, 'myopts')
        with open(argfile, 'w') as f:
            f.write('testsrc_1.dsc\n--test-name=foo\n--timeout-copy=5')

        autopkgtest_args.adtlog.verbosity = 2
        (args, acts, virt) = self.parse(
            ['-q', '--shell-fail', self.testdeb1, '@' + argfile])

        self.assertEqual(args.verbosity, 0)
        self.assertEqual(args.timeout_copy, 5)
        self.assertEqual(args.testname, 'foo')

        self.assertEqual(acts, [('binary', self.testdeb1, None),
                                ('source', 'testsrc_1.dsc', False),
                               ])

    def test_read_file_spaces(self):
        argfile = os.path.join(self.workdir.name, 'myopts')
        with open(argfile, 'w') as f:
            f.write(' testsrc_1.dsc \n -q \n -B \n --timeout-copy=5 ')

        (args, acts, virt) = self.parse(
            ['-d', '--shell-fail', '@' + argfile, '--no-built-binaries'])

        self.assertEqual(args.verbosity, 0)
        self.assertEqual(args.timeout_copy, 5)

        self.assertEqual(acts, [('source', 'testsrc_1.dsc', False)])

    def test_read_file_with_runner(self):
        argfile = os.path.join(self.workdir.name, 'myopts')
        with open(argfile, 'w') as f:
            f.write('testsrc_1.dsc\n--timeout-copy=5\n--\nfoo\n-d')

        (args, acts, virt) = self.parse(['-q', '@' + argfile, '-x'],
                                        default_virt=False)

        self.assertEqual(args.verbosity, 0)
        self.assertEqual(args.timeout_copy, 5)

        self.assertEqual(acts, [('source', 'testsrc_1.dsc', True)])
        self.assertEqual(virt, ['autopkgtest-virt-foo', '-d', '-x'])

    def test_setup_commands(self):
        cmd = os.path.join(self.workdir.name, 'cmd')
        with open(cmd, 'w') as f:
            f.write('./setup.py install\n')

        args = self.parse(
            ['--setup-commands', 'apt update', '--setup-commands', cmd,
             '--setup-commands=cleanup', 'mypkg'])[0]
        self.assertEqual(args.setup_commands,
                         ['apt update', './setup.py install', 'cleanup'])

    def test_setup_commands_boot(self):
        cmd = os.path.join(self.workdir.name, 'cmd')
        with open(cmd, 'w') as f:
            f.write('touch /run/foo/bar\n')

        args = self.parse(
            ['--setup-commands-boot', 'mkdir /run/foo',
             '--setup-commands-boot', cmd,
             '--setup-commands-boot=cleanup', 'mypkg'])[0]
        self.assertEqual(args.setup_commands_boot,
                         ['mkdir /run/foo', 'touch /run/foo/bar', 'cleanup'])

    def test_copy(self):
        stuff = os.path.join(self.workdir.name, 'stuff')
        with open(stuff, 'w') as f:
            f.write('stuff\n')
        args = self.parse(['--copy', '%s:/setup/stuff.txt' % stuff, 'mypkg'])[0]
        self.assertEqual(args.copy, [(stuff, '/setup/stuff.txt')])

    def test_copy_nonexisting(self):
        self.err(['--copy', '/non/existing:/setup/stuff.txt', 'mypkg'],
                 '--copy.*non/existing.*not exist')

    def test_env(self):
        args = self.parse(['--env', 'AUTOPKGTEST_X=one', '--env=AUTOPKGTEST_Y=two', 'mypkg'])[0]
        self.assertEqual(args.env, ['AUTOPKGTEST_X=one', 'AUTOPKGTEST_Y=two'])

    def test_wrong_env(self):
        self.err(['--env', 'AUTOPKGTEST_X', 'mypkg'], 'must be KEY=value')

    def test_help(self):
        try:
            out = io.StringIO()
            sys.stdout = out
            try:
                self.parse(['--help'], default_virt=False)
            finally:
                sys.stdout = sys.__stdout__
            self.fail('expected --help to exit')
        except SystemExit:
            pass
        out = out.getvalue()
        # has description
        self.assertIn('Test installed bin', out)
        # has actions
        self.assertIn('--test-name', out)
        # has options
        self.assertIn('--no-built-binaries', out)
        # has virt server
        self.assertIn('--', out)

    def test_no_auto_control(self):
        (args, acts, virt) = self.parse(['--no-auto-control', 'mypkg', '--', 'foo'])
        self.assertEqual(args.auto_control, False)
        self.assertEqual(acts, [('apt-source', 'mypkg', False)])
        self.assertEqual(virt, ['autopkgtest-virt-foo'])

    def test_build_parallel(self):
        args = self.parse(['--build-parallel=17', 'mypkg'])[0]
        self.assertEqual(args.build_parallel, '17')

        args = self.parse(['mypkg'])[0]
        self.assertEqual(args.build_parallel, None)

    def test_no_virt_server(self):
        self.err(['mypkg'], 'must specify.*--.*virt-server', default_virt=False)

    def test_empty_virt_server(self):
        self.err(['mypkg', '--'], 'must specify.*--.*virt-server',
                 default_virt=False)

    def test_triple_dash_virt_server(self):
        (args, acts, virt) = self.parse([self.testdsc, '---', 'foo', '--fooarg'],
                                       default_virt=False)
        self.assertEqual(acts, [('source', self.testdsc, True)])
        self.assertEqual(virt, ['autopkgtest-virt-foo', '--fooarg'])


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