# Copyright (C) 2018 Igalia S.L.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import unittest

from webkitpy.common.config.ports_mock import MockPort
from webkitpy.common.host_mock import MockHost
from webkitpy.w3c.wpt_runner import WPTRunner, parse_args


TEST_EXPECTATIONS_JSON_CONTENT = """{
    "passing_test.html": { "expected": "PASS" },
    "failing_test.html": { "expected": "FAIL" },
    "disabled_test.html": { "disabled": "Test Name #1" },
    "custom_test_name.html": {
        "test_name": "Custom Test Name",
        "expected": "FAIL"
    },
    "test_with_subtests.html": {
        "subtests": {
            "Subtest #1": { "expected": "PASS" },
            "Subtest #2": { "expected": "TIMEOUT" }
        }
    },
    "nested/test_file.html": { "expected": "PASS" },
    "nested/nested/test_file.html": {
        "test_name": "Deeper-nested test case",
        "subtests": {
            "Only Subtest": { "expected": "PASS" }
        }
    }
}"""

EXPECTED_TEST_MANIFESTS = {
    "passing_test.html.ini":
"""[passing_test.html]
    expected: PASS
""",

    "failing_test.html.ini":
"""[failing_test.html]
    expected: FAIL
""",

    "disabled_test.html.ini":
"""[disabled_test.html]
    disabled: Test Name #1
""",

    "custom_test_name.html.ini":
"""[Custom Test Name]
    expected: FAIL
""",

    "test_with_subtests.html.ini":
"""[test_with_subtests.html]
    [Subtest #1]
        expected: PASS
    [Subtest #2]
        expected: TIMEOUT
""",

    "nested/test_file.html.ini":
"""[test_file.html]
    expected: PASS
""",

    "nested/nested/test_file.html.ini":
"""[Deeper-nested test case]
    [Only Subtest]
        expected: PASS
"""
}


class WPTRunnerTest(unittest.TestCase):

    class MockTestDownloader(object):
        @staticmethod
        def default_options():
            return {}

        def __init__(self, repository_directory, host, options):
            self._repository_directory = repository_directory
            self._host = host

        def clone_tests(self):
            self._host.filesystem.maybe_make_directory(self._repository_directory, "web-platform-tests")

    class MockWebDriver(object):
        @staticmethod
        def create(port):
            return WPTRunnerTest.MockWebDriver()

        def binary_path(self):
            return "/mock-webdriver/bin/webdriver"

        def browser_path(self):
            return "/mock-webdriver/bin/browser"

        def browser_args(self):
            return ["webdriver_arg1", "webdriver_arg2"]

    class MockSpawnWPT(object):
        def __init__(self, test_case, expected_wpt_checkout=None, expected_wpt_args=None):
            self._test_case = test_case
            self._expected_wpt_checkout = expected_wpt_checkout
            self._expected_wpt_args = expected_wpt_args

        def __call__(self, script_name, wpt_checkout, wpt_args):
            self._test_case.assertEqual(script_name, "wptrunner_unittest")
            self._test_case.assertEqual(wpt_checkout, self._expected_wpt_checkout)
            self._test_case.assertEqual(wpt_args, self._expected_wpt_args)

    class TestInstance(object):
        def __init__(self, options, spawn_wpt_func=None):
            self.port = MockPort()
            self.host = MockHost()

            # In non-test environments, this value is by default set to the WEBKIT_TEST_CHILD_PROCESSES
            # env value or, if that's not present, to the default number of child processes. Here we
            # manually set it to 4 for consistency, unless the test case specifies it.
            if not options.child_processes:
                options.child_processes = 4

            self.runner = WPTRunner(self.port, self.host, "wptrunner_unittest", options,
                WPTRunnerTest.MockTestDownloader, WPTRunnerTest.MockWebDriver.create, spawn_wpt_func)

    def test_prepare_wpt_checkout(self):
        # Tests the prepare_wpt_checkout() method with no WPT checkout specified in options.

        options, _ = parse_args([])
        instance = WPTRunnerTest.TestInstance(options)

        self.assertTrue(instance.runner.prepare_wpt_checkout())

        expected_wpt_checkout = "/mock-checkout/WebKitBuild/w3c-tests/web-platform-tests"
        self.assertEqual(instance.runner._options.wpt_checkout, expected_wpt_checkout)
        self.assertTrue(instance.host.filesystem.isdir(expected_wpt_checkout))

    def test_prepare_wpt_checkout_specified_path(self):
        # Tests the prepare_wpt_checkout() method with WPT checkout specified in options.

        specified_wpt_checkout = "/mock-path/web-platform-tests"
        options, _ = parse_args(["--wpt-checkout", specified_wpt_checkout])
        instance = WPTRunnerTest.TestInstance(options)
        instance.host.filesystem.maybe_make_directory(specified_wpt_checkout)

        self.assertTrue(instance.runner.prepare_wpt_checkout())
        self.assertEqual(instance.runner._options.wpt_checkout, specified_wpt_checkout)

    def test_run(self):
        # Tests the run() method. Files are mocked to the point that helper methods don't fail.
        # Goal of this test is for the WPT spawn command to match the desired WPT directory and
        # arguments. No options or arguments are used.

        spawn_wpt_func = WPTRunnerTest.MockSpawnWPT(self,
            "/mock-checkout/WebKitBuild/w3c-tests/web-platform-tests",
            ["run", "--webkit-port=MockPort", "--processes=4",
                "--webdriver-binary=/mock-webdriver/bin/webdriver",
                "--binary=/mock-webdriver/bin/browser",
                "--binary-arg=webdriver_arg1", "--binary-arg=webdriver_arg2", "webkit"])

        options, _ = parse_args([])
        instance = WPTRunnerTest.TestInstance(options, spawn_wpt_func)

        self.assertTrue(instance.runner.run([]))

    def test_run_with_specified_options(self):
        # Tests the run() method. Files are mocked to the point that helper methods don't fail.
        # Goal of this test is for the WPT spawn command to match the desired WPT directory and
        # arguments. Custom WPT checkout and child process count are specified. Note that the
        # WPT checkout doesn't have an impact on the resulting WPT argument list, as intended.
        specified_wpt_checkout = "/mock-path/web-platform-tests"
        specified_wpt_metadata = "/mock-path/wpt-metadata"
        specified_wpt_manifest = "/mock-path/wpt-manifest.json"
        specified_wpt_include_manifest = "/mock-path/wpt-include-manifest.ini"
        specified_child_processes = 16

        spawn_wpt_func = WPTRunnerTest.MockSpawnWPT(self, specified_wpt_checkout,
            ["run", "--webkit-port=MockPort", "--processes=16",
                "--metadata=/mock-path/wpt-metadata",
                "--manifest=/mock-path/wpt-manifest.json",
                "--include-manifest=/mock-path/wpt-include-manifest.ini",
                "--webdriver-binary=/mock-webdriver/bin/webdriver",
                "--binary=/mock-webdriver/bin/browser",
                "--binary-arg=webdriver_arg1", "--binary-arg=webdriver_arg2", "webkit"])

        options, _ = parse_args(["--wpt-checkout", specified_wpt_checkout,
            "--wpt-metadata", specified_wpt_metadata,
            "--wpt-manifest", specified_wpt_manifest,
            "--wpt-include-manifest", specified_wpt_include_manifest,
            "--child-processes", specified_child_processes])
        instance = WPTRunnerTest.TestInstance(options, spawn_wpt_func)

        # Also create the mock WPT checkout and metadata directories.
        instance.host.filesystem.maybe_make_directory(specified_wpt_checkout)
        instance.host.filesystem.maybe_make_directory(specified_wpt_metadata)
        instance.host.filesystem.write_text_file(specified_wpt_manifest, "{}")
        instance.host.filesystem.write_text_file(specified_wpt_include_manifest, "skip: true");

        self.assertTrue(instance.runner.run([]))

    def test_run_with_args(self):
        # Tests the run() method. Files are mocked to the point that helper methods don't fail.
        # Goal of this test is for the WPT spawn command to match the desired WPT directory and
        # arguments. A custom two-element argument list is used. It's expected to be appended
        # to the resulting WPT argument list.

        specified_args = ["test1.html", "test2.html"]

        spawn_wpt_func = WPTRunnerTest.MockSpawnWPT(self,
            "/mock-checkout/WebKitBuild/w3c-tests/web-platform-tests",
            ["run", "--webkit-port=MockPort", "--processes=4",
                "--webdriver-binary=/mock-webdriver/bin/webdriver",
                "--binary=/mock-webdriver/bin/browser",
                "--binary-arg=webdriver_arg1", "--binary-arg=webdriver_arg2", "webkit"] + specified_args)

        options, _ = parse_args([])
        instance = WPTRunnerTest.TestInstance(options, spawn_wpt_func)

        self.assertTrue(instance.runner.run(specified_args))
