#!/usr/bin/python3

# This file is part of Cockpit.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import unittest
import subprocess
import os
import re

import parent
from testlib import *

dirname = os.path.dirname(__file__)
run_tests = os.path.join(TEST_DIR, "common", "run-tests")
VERIFY_DIR = os.path.join(TEST_DIR, "verify")

class TestRunTestListing(unittest.TestCase):

    def testBasic(self):
        # nondestructive tests are sorted alphabetically; ascending or descending depending on $TEST_OS 1-bit hash
        forward_sort_env = {"TEST_OS": "evenstring", "PATH": os.environ["PATH"]}
        rev_sort_env = {"TEST_OS": "oddstring1", "PATH": os.environ["PATH"]}

        # Listing on check-* file
        self.assertEqual(subprocess.check_output([os.path.join(dirname, "check-example"), "-l", "TestNondestructiveExample"]).strip().decode(),
                         "TestNondestructiveExample.testOne\nTestNondestructiveExample.testTwo")
        # Filter on class
        p = subprocess.run([run_tests, "--test-dir", VERIFY_DIR, "-l", "TestNondestructiveExample"], env=forward_sort_env, capture_output=True, check=True)
        self.assertIn(b"1..2\nTestNondestructiveExample.testOne\nTestNondestructiveExample.testTwo", p.stdout.strip())
        # Filter a specific test
        self.assertIn("1..1\nTestNondestructiveExample.testOne",
                      subprocess.check_output([run_tests, "--test-dir", VERIFY_DIR, "-l", "TestNondestructiveExample.testOne"]).strip().decode())
        # Exclude test patterns
        self.assertIn("1..1\nTestNondestructiveExample.testOne",
                      subprocess.check_output([run_tests, "--test-dir", VERIFY_DIR, "-l",
                                               "--exclude", "bogus", "--exclude", "TestNondestructiveExample.testTwo",
                                               "TestNondestructiveExample"]).strip().decode())

        ndtests = subprocess.run([run_tests, "--test-dir", VERIFY_DIR, "-n", "-l"], env=forward_sort_env, check=True, capture_output=True)
        self.assertIn(b"TestExample.testNondestructive\n", ndtests.stdout)
        self.assertIn(b"TestNondestructiveExample.testOne\nTestNondestructiveExample.testTwo", ndtests.stdout)

        # nondestructive tests are sorted alphabetically; ascending or descending depending on $TEST_OS 1-bit hash
        self.assertRegex(ndtests.stdout, re.compile(b".*TestAccounts.*TestFirewall.*TestLogin.*TestServices.*TestTerminal.*", re.S))

        # reverse direction
        revtests = subprocess.run([run_tests, "--test-dir", VERIFY_DIR, "-n", "-l"], env=rev_sort_env, check=True, capture_output=True)
        self.assertRegex(revtests.stdout, re.compile(b".*TestTerminal.*TestServices.*TestLogin.*TestFirewall.*TestAccounts.*", re.S))

    def testNonDestructive(self):
        self.assertEqual(subprocess.check_output([run_tests, "--test-dir", VERIFY_DIR, "--nondestructive", "-l", "TestExample"]).strip().decode(),
                         "1..1\nTestExample.testNondestructive")

        # with short option and substring
        self.assertEqual(subprocess.check_output([run_tests, "--test-dir", VERIFY_DIR, "-nl", "TestExamp"]).strip().decode(),
                         "1..1\nTestExample.testNondestructive")


# This can't be @nondestructive, as we run our own @nondestructive test nested inside this test. This will already call the
# cleanup handlers, and the outside cleanup would then fail to do that again.
class TestRunTest(MachineCase):
    def testExistingMachine(self):
        env = os.environ.copy()
        try:
            del env["TEST_JOBS"]
        except KeyError:
            pass
        out = subprocess.check_output([run_tests, "--test-dir", VERIFY_DIR, "-vt",
                                       "--machine", self.machine.ssh_address + ":" + self.machine.ssh_port,
                                       "--browser", self.machine.web_address + ":" + self.machine.web_port,
                                       "TestNondestructiveExample"], env=env)
        self.assertRegex(out, b"\nok .* TestNondestructiveExample.testOne")
        self.assertRegex(out, b"\nok .* TestNondestructiveExample.testTwo")
        self.assertIn(b"TESTS PASSED", out)

        # can't be called with concurrency
        p = subprocess.Popen([run_tests, "--test-dir", VERIFY_DIR, "--machine", "1.2.3.4:56", "--browser", "1.2.3.4:67", "-j2"],
                             stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
        (out, err) = p.communicate()
        self.assertGreaterEqual(p.returncode, 1)
        self.assertIn(b"--machine cannot be used with concurrent jobs", err)

        # implies --nondestructive
        out = subprocess.check_output([run_tests, "--test-dir", VERIFY_DIR, "--machine", "1.2.3.4:56", "--browser", "1.2.3.4:67",
                                       "TestExample.testBasic"], env=env)
        self.assertIn(b"1..0", out)
        self.assertNotIn(b"TestExample", out)


class TestTestlib(MachineCase):
    @nondestructive
    def testRestoreAPI(self):
        m = self.machine

        self.assertEqual(m.execute("whoami").strip(), "root")
        # existing file
        m.execute("echo original > /etc/someconfig")
        self.restore_file("/etc/someconfig")
        m.execute("echo changed > /etc/someconfig")
        # nonexisting file
        self.restore_file("/var/lib/cockpittest.txt")
        m.execute("echo data > /var/lib/cockpittest.txt")

        # existing dir
        m.execute("mkdir -p /var/lib/existing_dir && echo hello > /var/lib/existing_dir/original")
        self.restore_dir("/var/lib/existing_dir")
        m.execute("rm /var/lib/existing_dir/original && echo pwned > /var/lib/existing_dir/new")
        # nonexisting dir
        self.restore_dir("/var/lib/cockpittestnew")
        m.execute("mkdir -p /var/lib/cockpittestnew && echo hello > /var/lib/cockpittestnew/cruft")

        # NSS is backed up by default
        m.execute("useradd cockpittest")

        # now pretend the test ends here
        self.doCleanups()

        # correctly restored existing file
        self.assertEqual("original", m.execute("cat /etc/someconfig").strip())
        m.execute("rm /etc/someconfig")
        # correctly cleaned up nonexisting file
        m.execute("test ! -e /var/lib/cockpittest.txt")

        # correctly restored existing dir
        self.assertEqual("original", m.execute("ls /var/lib/existing_dir").strip())
        self.assertEqual("hello\n", m.execute("cat /var/lib/existing_dir/original"))
        m.execute("rm -r /var/lib/existing_dir")
        # correctly removed nonexisting dir
        m.execute("test ! -e /var/lib/cockpittestnew")

        # NSS/home got restored
        self.assertNotIn("cockpittest", self.machine.execute("cat /etc/passwd"))
        self.machine.execute("test ! -e /home/cockpittest")


if __name__ == '__main__':
    test_main()
