#!/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__))
root_dir = os.path.dirname(test_dir)
if os.path.exists(os.path.join(root_dir, 'lib', 'adt_run_args.py')):
    our_base = os.path.join(root_dir, 'lib')
else:
    our_base = '/usr/share/autopkgtest/python'
sys.path.insert(1, our_base)

import adt_run_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()

    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 adt_run_args.parse_args(args)

    def test_explicit_actions(self):
        (args, acts, virt) = self.parse(
            ['--source', 'd/foo.dsc', '--binary', 'foodeb',
             '--click-source', '.', '--click', 'x.click'])
        self.assertTrue(isinstance(args, argparse.Namespace))
        self.assertEqual(acts, [('source', 'd/foo.dsc', True),
                                ('binary', 'foodeb', None),
                                ('click-source', '.', None),
                                ('click', 'x.click', None)])
        self.assertEqual(virt, ['null'])

    def test_explicit_actions_eq(self):
        (args, acts, virt) = self.parse(
            ['--source=d/foo.dsc', '--binary=foodeb',
             '--click-source=.', '--click=x.click'])
        self.assertTrue(isinstance(args, argparse.Namespace))
        self.assertEqual(acts, [('source', 'd/foo.dsc', True),
                                ('binary', 'foodeb', None),
                                ('click-source', '.', None),
                                ('click', 'x.click', None)])
        self.assertEqual(virt, ['null'])

    def test_implicit_built_tree(self):
        (args, acts, virt) = self.parse(['/proc/', './'])
        self.assertTrue(isinstance(args, argparse.Namespace))
        self.assertEqual(acts, [('built-tree', '/proc/', False),
                                ('built-tree', './', False)])
        self.assertEqual(virt, ['null'])

    def test_implicit_unbuilt_tree(self):
        acts = self.parse(['/proc//', './/'])[1]
        self.assertEqual(acts, [('unbuilt-tree', '/proc//', True),
                                ('unbuilt-tree', './/', True)])

    def test_implicit_deb(self):
        acts = self.parse(['mypkg.deb'])[1]
        self.assertEqual(acts, [('binary', 'mypkg.deb', None)])

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

    def test_git_source(self):
        acts = self.parse(['--git-source', 'git://dev.org/proj1.git',
                           '--no-built-binaries',
                           '--git-source=http://dev.org/proj2.git stable'])[1]
        self.assertEqual(acts, [('git-source', 'git://dev.org/proj1.git', True),
                                ('git-source', 'http://dev.org/proj2.git stable', False)])

    def test_implicit_click_source(self):
        os.makedirs('mypkg/click')
        acts = self.parse(['mypkg'])[1]
        self.assertEqual(acts, [('click-source', 'mypkg', None)])

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

    def test_built_binaries(self):
        acts = self.parse(
            ['--source', 'dsc1y', './', './/', 'myapt1',
             '--no-built-binaries',
             '--source', 'dsc2n', './', './/', 'myapt2',
             '--built-binaries',
             '--source', 'dsc3y', './', './/', 'myapt3'])[1]
        self.assertEqual(acts, [('source', 'dsc1y', True),
                                ('built-tree', './', False),
                                ('unbuilt-tree', './/', True),
                                ('apt-source', 'myapt1', False),
                                ('source', 'dsc2n', False),
                                ('built-tree', './', False),
                                ('unbuilt-tree', './/', False),
                                ('apt-source', 'myapt2', False),
                                ('source', 'dsc3y', True),
                                ('built-tree', './', False),
                                ('unbuilt-tree', './/', True),
                                ('apt-source', 'myapt3', False)])

    def test_testname(self):
        acts = self.parse(
            ['--source', 'dsc1',
             '--testname', 'foo', '--source', 'dsc2',
             '--source', 'dsc3'])[1]
        self.assertEqual(acts, [('source', 'dsc1', True),
                                ('testname', 'foo', None),
                                ('source', 'dsc2', True),
                                ('source', 'dsc3', True)])

    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 mypkg_1_all.deb
 deadbeef 100 utils optional mypkg_1.dsc
 deadbeef 20000 utils optional mypkg_1.tar.gz
 deadbeef 10000 utils optional libmypkg_1_all.deb
''')

        acts = self.parse(['--source', 'dsc1y', ch, '--apt-source', 'apt1'])[1]
        self.assertEqual(
            acts,
            [('source', 'dsc1y', True),
             ('binary', os.path.join(self.workdir.name, 'mypkg_1_all.deb'), None),
             ('binary', os.path.join(self.workdir.name, 'libmypkg_1_all.deb'), None),
             ('source', os.path.join(self.workdir.name, 'mypkg_1.dsc'), False),
             ('apt-source', 'apt1', False)])

    def test_default_options(self):
        args = self.parse(['./'])[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)

    def test_options(self):
        (args, acts, virt) = self.parse(
            ['-q', '--shell-fail', '--timeout-copy=5', '--set-lang',
             'en_US.UTF-8', './',
             '---', 'adt-virt-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, [('built-tree', './', False)])
        self.assertEqual(virt, ['adt-virt-foo', '-d', '-s', '--', '-d'])

    def test_mix_opts_actions(self):
        (args, acts, virt) = self.parse(
            ['-q', '--built-tree=.', '--shell-fail', '--source', 'mysrc_1.dsc', '-B',
             'x.click', '--timeout-copy=5', './/'])

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

        self.assertEqual(acts, [('built-tree', '.', False),
                                ('source', 'mysrc_1.dsc', True),
                                ('click', 'x.click', None),
                                ('unbuilt-tree', './/', False)])

    def test_timeouts(self):
        (args, acts, virt) = self.parse(
            ['--timeout-short=37', '--timeout-factor=0.5',
             '--timeout-build=1337', '--source', 'x.dsc', '---', '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('--source=mysrc_1.dsc\n-B\n--timeout-copy=5')

        (args, acts, virt) = self.parse(
            ['-q', './', '--shell-fail', '@' + argfile, 'x.click',
             '--built-binaries', './/'])

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

        self.assertEqual(acts, [('built-tree', './', False),
                                ('source', 'mysrc_1.dsc', True),
                                ('click', 'x.click', None),
                                ('unbuilt-tree', './/', True)])

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

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

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

        self.assertEqual(acts, [('built-tree', './', False),
                                ('source', 'mysrc_1.dsc', True),
                                ('click', 'x.click', None),
                                ('unbuilt-tree', './/', True)])

    def test_read_file_with_runner(self):
        argfile = os.path.join(self.workdir.name, 'myopts')
        with open(argfile, 'w') as f:
            f.write('--source=mysrc_1.dsc\n--timeout-copy=5\n---\nadt-virt-foo\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', 'mysrc_1.dsc', True)])
        self.assertEqual(virt, ['adt-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', './'])[0]
        self.assertEqual(args.setup_commands,
                         ['apt update', './setup.py install', '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, './'])[0]
        self.assertEqual(args.copy, [(stuff, '/setup/stuff.txt')])

    def test_env(self):
        args = self.parse(['--env', 'ADT_X=one', '--env=ADT_Y=two', './'])[0]
        self.assertEqual(args.env, ['ADT_X=one', 'ADT_Y=two'])

    def test_wrong_env(self):
        try:
            self.parse(['--env', 'ADT_X', './'])[0]
            self.fail('expected "--env ADT_X" to fail')
        except SystemExit:
            pass

    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('--source', 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', './', '---', 'adt-virt-foo'])
        self.assertEqual(args.auto_control, False)
        self.assertEqual(acts, [('built-tree', './', False)])
        self.assertEqual(virt, ['adt-virt-foo'])

    def test_build_parallel(self):
        (args, acts, virt) = self.parse(
            ['--build-parallel=17', './', '---', 'adt-virt-foo'])
        self.assertEqual(args.build_parallel, '17')

        (args, acts, virt) = self.parse(
            ['./', '---', 'adt-virt-foo'])
        self.assertEqual(args.build_parallel, None)

    #
    # Errors
    #

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

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

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

    def test_no_actions(self):
        self.err([], 'at least one action')

    def test_unknown_file(self):
        self.err(['./something.foo'], './something.foo: unsupported')

    def test_unknown_dir(self):
        os.makedirs('src/mypkg')
        self.err(['src/mypkg'], 'src/mypkg: unsupported')

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


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