#!/usr/bin/python3

# ruff: noqa: E402

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

from unittest.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

    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):
        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_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_test_name(self):
        (args, acts, virt) = self.parse(["--test-name", "foo", "mypkg"])
        self.assertEqual(acts, [("apt-source", "mypkg", False)])
        self.assertEqual(args.only_tests, ["foo"])

    def test_skip_test(self):
        (args, acts, virt) = self.parse(
            ["--skip-test", "foo", "--skip-test", "bar", "mypkg"]
        )
        self.assertEqual(acts, [("apt-source", "mypkg", False)])
        self.assertEqual(args.skip_tests, ["foo", "bar"])

    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.only_tests, [])
        self.assertEqual(args.ignore_restrictions, [])

    def test_options(self):
        (args, acts, virt) = self.parse(
            [
                "-q",
                "--shell-fail",
                "--timeout-copy=5",
                "--set-lang",
                "en_US.UTF-8",
                "--ignore-restrictions=a,b",
                "--ignore-restrictions=c",
                "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(args.ignore_restrictions, ["a", "b", "c"])

        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.only_tests, ["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))
