# mypy: allow-untyped-defs

from ..lint import check_file_contents
from .base import check_errors
import io
import os
import pytest

INTERESTING_FILE_NAMES = {
    "python": [
        "test.py",
    ],
    "js": [
        "test.js",
    ],
    "web-lax": [
        "test.htm",
        "test.html",
    ],
    "web-strict": [
        "test.svg",
        "test.xht",
        "test.xhtml",
    ],
}

def check_with_files(input_bytes):
    return {
        filename: (check_file_contents("", filename, io.BytesIO(input_bytes)), kind)
        for (filename, kind) in
        (
            (os.path.join("html", filename), kind)
            for (kind, filenames) in INTERESTING_FILE_NAMES.items()
            for filename in filenames
        )
    }


def test_trailing_whitespace():
    error_map = check_with_files(b"test; ")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = [("TRAILING WHITESPACE", "Whitespace at EOL", filename, 1)]
        if kind == "web-strict":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, None))
        assert errors == expected


def test_indent_tabs():
    error_map = check_with_files(b"def foo():\n\x09pass")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = [("INDENT TABS", "Test-file line starts with one or more tab characters", filename, 2)]
        if kind == "web-strict":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, None))
        assert errors == expected


def test_cr_not_at_eol():
    error_map = check_with_files(b"line1\rline2\r")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = [("CR AT EOL", "Test-file line ends with CR (U+000D) character", filename, 1)]
        if kind == "web-strict":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, None))
        assert errors == expected


def test_cr_at_eol():
    error_map = check_with_files(b"line1\r\nline2\r\n")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = [
            ("CR AT EOL", "Test-file line ends with CR (U+000D) character", filename, 1),
            ("CR AT EOL", "Test-file line ends with CR (U+000D) character", filename, 2),
        ]
        if kind == "web-strict":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, None))
        assert errors == expected


def test_w3c_test_org():
    error_map = check_with_files(b"import('http://www.w3c-test.org/')")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = [("W3C-TEST.ORG", "Test-file line has the string `w3c-test.org`", filename, 1)]
        if kind == "python":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, 1))
        elif kind == "web-strict":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, None))
        assert errors == expected

def test_web_platform_test():
    error_map = check_with_files(b"import('http://web-platform.test/')")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = [("WEB-PLATFORM.TEST", "Internal web-platform.test domain used", filename, 1)]
        if kind == "python":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, 1))
        elif kind == "web-strict":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, None))
        assert errors == expected


def test_webidl2_js():
    error_map = check_with_files(b"<script src=/resources/webidl2.js>")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = [("WEBIDL2.JS", "Legacy webidl2.js script used", filename, 1)]
        if kind == "python":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, 1))
        elif kind == "web-strict":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, None))
        assert errors == expected


def test_console():
    error_map = check_with_files(b"<script>\nconsole.log('error');\nconsole.error ('log')\n</script>")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict", "js"]:
            assert errors == [
                ("CONSOLE", "Test-file line has a `console.*(...)` call", filename, 2),
                ("CONSOLE", "Test-file line has a `console.*(...)` call", filename, 3),
            ]
        else:
            assert errors == [("PARSE-FAILED", "Unable to parse file", filename, 1)]


def test_setTimeout():
    error_map = check_with_files(b"<script>setTimeout(() => 1, 10)</script>")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [("PARSE-FAILED", "Unable to parse file", filename, 1)]
        else:
            assert errors == [('SET TIMEOUT',
                               'setTimeout used',
                               filename,
                               1)]


def test_eventSender():
    error_map = check_with_files(b"<script>eventSender.mouseDown()</script>")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [("PARSE-FAILED", "Unable to parse file", filename, 1)]
        else:
            assert errors == [('LAYOUTTESTS APIS',
                               'eventSender/testRunner/internals used; these are LayoutTests-specific APIs (WebKit/Blink)',
                               filename,
                               1)]


def test_testRunner():
    error_map = check_with_files(b"<script>if (window.testRunner) { testRunner.waitUntilDone(); }</script>")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [("PARSE-FAILED", "Unable to parse file", filename, 1)]
        else:
            assert errors == [('LAYOUTTESTS APIS',
                               'eventSender/testRunner/internals used; these are LayoutTests-specific APIs (WebKit/Blink)',
                               filename,
                               1)]


def test_internals():
    error_map = check_with_files(b"<script>if (window.internals) { internals.doAThing(); }</script>")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [("PARSE-FAILED", "Unable to parse file", filename, 1)]
        else:
            assert errors == [('LAYOUTTESTS APIS',
                               'eventSender/testRunner/internals used; these are LayoutTests-specific APIs (WebKit/Blink)',
                               filename,
                               1)]


def test_missing_deps():
    error_map = check_with_files(b"<script src='/gen/foo.js'></script>")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [("PARSE-FAILED", "Unable to parse file", filename, 1)]
        else:
            assert errors == [('MISSING DEPENDENCY',
                               'Chromium-specific content referenced',
                               filename,
                               1)]


def test_no_missing_deps():
    error_map = check_with_files(b"""<head>
<script src='/foo/gen/foo.js'></script>
<script src='/gens/foo.js'></script>
</head>""")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [("PARSE-FAILED", "Unable to parse file", filename, 1)]
        else:
            assert errors == []


def test_html_invalid_syntax():
    error_map = check_with_files(b"<!doctype html><div/>")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "web-lax":
            assert errors == [("HTML INVALID SYNTAX", "Test-file line has a non-void HTML tag with /> syntax", filename, 1)]


def test_meta_timeout():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<meta name="timeout" />
<meta name="timeout" content="short" />
<meta name="timeout" content="long" />
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("MULTIPLE-TIMEOUT", "More than one meta name='timeout'", filename, None),
                ("INVALID-TIMEOUT",
                    "Test file with `<meta name='timeout'...>` element that has a `content` attribute whose value is not `long`: ",
                    filename,
                    None),
                ("INVALID-TIMEOUT",
                    "Test file with `<meta name='timeout'...>` element that has a `content` attribute whose value is not `long`: short",
                    filename,
                    None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_early_testharnessreport():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testharness.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("EARLY-TESTHARNESSREPORT",
                    "Test file has an instance of "
                    "`<script src='/resources/testharnessreport.js'>` "
                    "prior to `<script src='/resources/testharness.js'>`",
                    filename,
                    None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_multiple_testharness():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharness.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("MULTIPLE-TESTHARNESS", "More than one `<script src='/resources/testharness.js'>`", filename, None),
                ("MISSING-TESTHARNESSREPORT", "Missing `<script src='/resources/testharnessreport.js'>`", filename, None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_multiple_testharnessreport():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testharnessreport.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("MULTIPLE-TESTHARNESSREPORT", "More than one `<script src='/resources/testharnessreport.js'>`", filename, None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_testdriver_in_unsupported():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""

    filename = os.path.join("html", "test-manual.html")
    errors = check_file_contents("", filename, io.BytesIO(code))
    check_errors(errors)
    assert errors == [(
        "TESTDRIVER-IN-UNSUPPORTED-TYPE",
        "testdriver.js included in a manual test, which doesn't support testdriver.js",
        filename,
        None,
    )]


def test_early_testdriver_vendor():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("EARLY-TESTDRIVER-VENDOR",
                    "Test file has an instance of "
                    "`<script src='/resources/testdriver-vendor.js'>` "
                    "prior to `<script src='/resources/testdriver.js'>`",
                    filename,
                    None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_testdriver_allowed():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == []
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_testdriver_feature_bidi():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js?feature=bidi"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == []
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_multiple_testdriver():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("MULTIPLE-TESTDRIVER", "More than one `<script src='/resources/testdriver.js'>`", filename, None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_multiple_testdriver_with_query_param():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver.js?feature=bidi"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("MULTIPLE-TESTDRIVER",
                 "More than one `<script src='/resources/testdriver.js'>`",
                 filename, None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_testdriver_unsupported_query_param():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js?foo"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("TESTDRIVER-UNSUPPORTED-QUERY-PARAMETER",
                 "testdriver.js script seen with unsupported query parameters",
                 filename, None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_testdriver_vendor_query_param():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js?vendor:param=foo"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ('TESTDRIVER-UNSUPPORTED-QUERY-PARAMETER',
                 'testdriver.js script seen with unsupported query parameters',
                 filename, None)
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_testdriver_unsupported_feature():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js?feature=unsupported_feature"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("TESTDRIVER-UNSUPPORTED-QUERY-PARAMETER",
                 "testdriver.js script seen with unsupported query parameters",
                 filename, None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_testdriver_multiple_with_unsupported_features():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js?feature=bidi&feature=unsupported_feature"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "web-lax":
            assert errors == [
                ('TESTDRIVER-UNSUPPORTED-QUERY-PARAMETER',
                 'testdriver.js script seen with unsupported query parameters',
                 filename, None)
            ]
        elif kind == "python":
            assert errors == [
                ('PARSE-FAILED', 'Unable to parse file', filename, 2),
            ]
        elif kind == "web-strict":
            assert errors == [
                ('PARSE-FAILED', 'Unable to parse file', filename, None),
            ]


def test_testdriver_multiple_vendor_features():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js?feature=bidi&feature=vendor:feature"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "web-lax":
            assert errors == []
        elif kind == "python":
            assert errors == [
                ('PARSE-FAILED', 'Unable to parse file', filename, 2),
            ]
        elif kind == "web-strict":
            assert errors == [
                ('PARSE-FAILED', 'Unable to parse file', filename, None),
            ]


def test_testdriver_vendor_feature():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js?feature=vendor:feature"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == []
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_testdriver_unsupported_empty_feature():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js?feature="></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("TESTDRIVER-UNSUPPORTED-QUERY-PARAMETER",
                 "testdriver.js script seen with unsupported query parameters",
                 filename, None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_multiple_testdriver_vendor():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("MULTIPLE-TESTDRIVER-VENDOR", "More than one `<script src='/resources/testdriver-vendor.js'>`", filename, None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_missing_testdriver_vendor():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                ("MISSING-TESTDRIVER-VENDOR", "Missing `<script src='/resources/testdriver-vendor.js'>`", filename, None),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_testharness_path():
    code = b"""\
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="testharness.js"></script>
<script src="resources/testharness.js"></script>
<script src="../resources/testharness.js"></script>
<script src="http://w3c-test.org/resources/testharness.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = [("W3C-TEST.ORG", "Test-file line has the string `w3c-test.org`", filename, 5)]
        if kind == "python":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, 1))
        elif kind in ["web-lax", "web-strict"]:
            expected.extend([
                ("TESTHARNESS-PATH", "testharness.js script seen with incorrect path", filename, None),
                ("TESTHARNESS-PATH", "testharness.js script seen with incorrect path", filename, None),
                ("TESTHARNESS-PATH", "testharness.js script seen with incorrect path", filename, None),
                ("TESTHARNESS-PATH", "testharness.js script seen with incorrect path", filename, None),
            ])
        assert errors == expected


def test_testharnessreport_path():
    code = b"""\
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="testharnessreport.js"></script>
<script src="resources/testharnessreport.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="http://w3c-test.org/resources/testharnessreport.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = [("W3C-TEST.ORG", "Test-file line has the string `w3c-test.org`", filename, 5)]
        if kind == "python":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, 1))
        elif kind in ["web-lax", "web-strict"]:
            expected.extend([
                ("TESTHARNESSREPORT-PATH", "testharnessreport.js script seen with incorrect path", filename, None),
                ("TESTHARNESSREPORT-PATH", "testharnessreport.js script seen with incorrect path", filename, None),
                ("TESTHARNESSREPORT-PATH", "testharnessreport.js script seen with incorrect path", filename, None),
                ("TESTHARNESSREPORT-PATH", "testharnessreport.js script seen with incorrect path", filename, None),
            ])
        assert errors == expected


def test_testdriver_path():
    code = b"""\
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="testdriver.js"></script>
<script src="/elsewhere/testdriver.js"></script>
<script src="/elsewhere/resources/testdriver.js"></script>
<script src="/resources/elsewhere/testdriver.js"></script>
<script src="../resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        expected = []
        if kind == "python":
            expected.append(("PARSE-FAILED", "Unable to parse file", filename, 1))
        elif kind in ["web-lax", "web-strict"]:
            expected.extend([
                ("TESTDRIVER-PATH", "testdriver.js script seen with incorrect path", filename, None),
                ("TESTDRIVER-PATH", "testdriver.js script seen with incorrect path", filename, None),
                ("TESTDRIVER-PATH", "testdriver.js script seen with incorrect path", filename, None),
                ("TESTDRIVER-PATH", "testdriver.js script seen with incorrect path", filename, None),
                ("TESTDRIVER-PATH", "testdriver.js script seen with incorrect path", filename, None)
            ])
        assert errors == expected


def test_testdriver_vendor_path():
    code = b"""\
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="testdriver-vendor.js"></script>
<script src="/elsewhere/testdriver-vendor.js"></script>
<script src="/elsewhere/resources/testdriver-vendor.js"></script>
<script src="/resources/elsewhere/testdriver-vendor.js"></script>
<script src="../resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            expected = {("PARSE-FAILED", "Unable to parse file", filename, 1)}
        elif kind in ["web-lax", "web-strict"]:
            expected = {
                ("MISSING-TESTDRIVER-VENDOR", "Missing `<script src='/resources/testdriver-vendor.js'>`", filename, None),
                ("TESTDRIVER-VENDOR-PATH", "testdriver-vendor.js script seen with incorrect path", filename, None),
                ("TESTDRIVER-VENDOR-PATH", "testdriver-vendor.js script seen with incorrect path", filename, None),
                ("TESTDRIVER-VENDOR-PATH", "testdriver-vendor.js script seen with incorrect path", filename, None),
                ("TESTDRIVER-VENDOR-PATH", "testdriver-vendor.js script seen with incorrect path", filename, None),
                ("TESTDRIVER-VENDOR-PATH", "testdriver-vendor.js script seen with incorrect path", filename, None)
            }
        else:
            expected = set()

        assert set(errors) == expected


def test_not_testharness_path():
    code = b"""\
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/webperftestharness.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 1),
            ]
        else:
            assert errors == []


def test_variant_missing():
    code = b"""\
<html xmlns="http://www.w3.org/1999/xhtml">
<meta name="variant">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 1),
            ]
        elif kind == "web-lax":
            assert errors == [
                ("VARIANT-MISSING",
                    "Test file with a `<meta name='variant'...>` element that's missing a `content` attribute",
                    filename,
                    None)
            ]


# A corresponding "positive" test cannot be written because the manifest
# SourceFile implementation raises a runtime exception for the condition this
# linting rule describes
@pytest.mark.parametrize("content", ["?foo"
                                     "#bar"])
def test_variant_malformed_negative(content):
    code = """\
<html xmlns="http://www.w3.org/1999/xhtml">
<meta name="variant" content="{}">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</html>
""".format(content).encode("utf-8")
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 1),
            ]
        elif kind == "web-lax":
            assert errors == []


def test_late_timeout():
    code = b"""\
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<meta name="timeout" content="long">
<script src="/resources/testharnessreport.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 1),
            ]
        elif kind == "web-lax":
            assert errors == [
                ("LATE-TIMEOUT",
                    "Test file with `<meta name='timeout'...>` element after `<script src='/resources/testharnessreport.js'>` element",
                    filename,
                    None)
            ]


def test_reference_in_non_reference():
    code = b"""
<html xmlns="http://www.w3.org/1999/xhtml">
<link rel="match" href="test-ref.html"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</html>
"""
    error_map = check_with_files(code)

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind in ["web-lax", "web-strict"]:
            assert errors == [
                (
                    "NON-EXISTENT-REF",
                    "Reference test with a non-existent 'match' relationship reference: "
                    "'test-ref.html'",
                    filename,
                    None,
                ),
                (
                    "REFERENCE-IN-OTHER-TYPE",
                    "Reference link included in a testharness test",
                    filename,
                    None,
                ),
            ]
        elif kind == "python":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, 2),
            ]


def test_print_function():
    error_map = check_with_files(b"def foo():\n  print('function')\n")

    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if kind == "python":
            assert errors == [
                ("PRINT STATEMENT", "A server-side python support file contains a `print` statement", filename, 2),
            ]
        elif kind == "web-strict":
            assert errors == [
                ("PARSE-FAILED", "Unable to parse file", filename, None),
            ]
        else:
            assert errors == []


def test_ahem_system_font():
    code = b"""\
<html>
<style>
body {
  font-family: aHEm, sans-serif;
}
</style>
</html>
"""
    error_map = check_with_files(code)
    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if filename.endswith((".htm", ".html", ".xht", ".xhtml")):
            assert errors == [
                ("AHEM SYSTEM FONT", "Don't use Ahem as a system font, use /fonts/ahem.css", filename, None)
            ]


def test_ahem_web_font():
    code = b"""\
<html>
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
body {
  font-family: aHEm, sans-serif;
}
</style>
</html>
"""
    error_map = check_with_files(code)
    for (filename, (errors, kind)) in error_map.items():
        check_errors(errors)

        if filename.endswith((".htm", ".html", ".xht", ".xhtml")):
            assert errors == []


open_mode_code = """
def first():
    return {0}("test.png")

def second():
    return {0}("test.png", "r")

def third():
    return {0}("test.png", "rb")

def fourth():
    return {0}("test.png", encoding="utf-8")

def fifth():
    return {0}("test.png", mode="rb")
"""


def test_open_mode():
    for method in ["open", "file"]:
        code = open_mode_code.format(method).encode("utf-8")
        errors = check_file_contents("", "test.py", io.BytesIO(code))
        check_errors(errors)

        message = ("File opened without providing an explicit mode (note: " +
                   "binary files must be read with 'b' in the mode flags)")

        assert errors == [
            ("OPEN-NO-MODE", message, "test.py", 3),
            ("OPEN-NO-MODE", message, "test.py", 12),
        ]


def test_css_missing_file_in_css():
    code = b"""\
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</html>
"""
    errors = check_file_contents("", "css/foo/bar.html", io.BytesIO(code))
    check_errors(errors)

    assert errors == [
        ('MISSING-LINK',
         'Testcase file must have a link to a spec',
         "css/foo/bar.html",
         None),
    ]

def test_valid_meta_file():
    code = b"""\
spec: url
suggested_reviewers:
  - username
"""
    errors = check_file_contents("", "css/META.yml", io.BytesIO(code))
    check_errors(errors)

    assert errors == []

def test_invalid_meta_file():
    code = b"""\
- test
"""
    # Check when the file is named correctly. It should find the error.
    errors = check_file_contents("", "css/META.yml", io.BytesIO(code))
    check_errors(errors)

    assert errors == [
        ('INVALID-META-FILE',
         'The META.yml is not a YAML file with the expected structure',
         "css/META.yml",
         None),
    ]

    # Check when the file is named incorrectly. It should not find the error.
    errors = check_file_contents("", "css/OTHER_META.yml", io.BytesIO(code))
    check_errors(errors)

    assert errors == []


@pytest.mark.parametrize("files,yml,expected_errors", [
    (
        ["file1.txt", "file2.txt", "file3.txt"],
        b"""\
features:
- name: feature1
  files:
  - file1.txt
""",
        []
    ),
    (
        ["file1.txt", "file2.txt", "file3.txt"],
        b"""\
features:
- name: feature1
  files:
  - file*.txt
""",
        []
    ),
    (
        ["file1.txt", "file2.txt", "file3.txt"],
        b"""\
features:
- name: feature1
  files:
  - file*.txt
  - foo.txt
""",
        [
            ("MISSING-WEB-FEATURES-FILE",
             "The WEB_FEATURES.yml file references a test that does not exist: 'foo.txt'",
             "css/WEB_FEATURES.yml",
             None),
        ]
    ),
    (
        ["bar1.txt", "bar2.txt", "bar3.txt"],
        b"""\
features:
- name: feature1
  files:
  - file*.txt
  - bar*.txt
""",
        [
            ("MISSING-WEB-FEATURES-FILE",
             "The WEB_FEATURES.yml file references a test that does not exist: 'file*.txt'",
             "css/WEB_FEATURES.yml",
             None),
        ]
    ),
    (
        ["file1.txt", "file2.txt", "file3.txt"],
        b"""\
features:
- name: feature1
  files:
  - foo.txt
""",
        [
            ("MISSING-WEB-FEATURES-FILE",
             "The WEB_FEATURES.yml file references a test that does not exist: 'foo.txt'",
             "css/WEB_FEATURES.yml",
             None),
        ]
    ),
    (
        ["file1.txt", "file2.txt", "file3.txt"],
        b"""\
features:
- name: feature1
  files: "**"
""",
        []
    ),
])
def test_valid_web_features_file(monkeypatch, files, yml, expected_errors):
    def listdir(dir):
        if dir.endswith("css"):
            return files

    def is_file(file):
        if os.path.basename(file) in files:
            return True
        return False

    monkeypatch.setattr(os.path, "isfile", is_file)
    monkeypatch.setattr(os, "listdir", listdir)
    errors = check_file_contents("", "css/WEB_FEATURES.yml", io.BytesIO(yml))
    check_errors(errors)

    assert errors == expected_errors


def test_invalid_web_features_file():
    code = b"""\
- test
"""
    # Check when the value is named correctly. It should find the error.
    errors = check_file_contents("", "css/WEB_FEATURES.yml", io.BytesIO(code))
    check_errors(errors)

    assert errors == [
        ('INVALID-WEB-FEATURES-FILE',
         'The WEB_FEATURES.yml file contains an invalid structure',
         "css/WEB_FEATURES.yml",
         None),
    ]

    # Check when the value is named incorrectly. It should not find the error.
    errors = check_file_contents("", "css/OTHER_WEB_FEATURES.yml", io.BytesIO(code))
    check_errors(errors)

    assert errors == []


def test_css_missing_file_manual():
    errors = check_file_contents("", "css/foo/bar-manual.html", io.BytesIO(b""))
    check_errors(errors)

    assert errors == [
        ('MISSING-LINK',
         'Testcase file must have a link to a spec',
         "css/foo/bar-manual.html",
         None),
    ]


def test_css_missing_file_tentative():
    code = b"""\
<html xmlns="http://www.w3.org/1999/xhtml">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</html>
"""

    # The tentative flag covers tests that make assertions 'not yet required by
    # any specification', so they need not have a specification link.
    errors = check_file_contents("", "css/foo/bar.tentative.html", io.BytesIO(code))
    assert not errors


@pytest.mark.parametrize("filename", [
    "foo.worker.js",
    "foo.any.js",
])
@pytest.mark.parametrize("input,error", [
    (b"""//META: title=foo\n""", None),
    (b"""//META: timeout=long\n""", None),
    (b"""// META: timeout=long\n""", None),
    (b"""//  META: timeout=long\n""", None),
    (b"""// META: script=foo.js\n""", None),
    (b"""// META: variant=\n""", (1, "MALFORMED-VARIANT")),
    (b"""// META: variant=?wss\n""", None),
    (b"""# META:\n""", None),
    (b"""\n// META: timeout=long\n""", (2, "STRAY-METADATA")),
    (b""" // META: timeout=long\n""", (1, "INDENTED-METADATA")),
    (b"""// META: timeout=long\n// META: timeout=long\n""", None),
    (b"""// META: timeout=long\n\n// META: timeout=long\n""", (3, "STRAY-METADATA")),
    (b"""// META: timeout=long\n// Start of the test\n// META: timeout=long\n""", (3, "STRAY-METADATA")),
    (b"""// META:\n""", (1, "BROKEN-METADATA")),
    (b"""// META: foobar\n""", (1, "BROKEN-METADATA")),
    (b"""// META: foo=bar\n""", (1, "UNKNOWN-METADATA")),
    (b"""// META: timeout=bar\n""", (1, "UNKNOWN-TIMEOUT-METADATA")),
    (b"""// META: script=/resources/testharness.js""", (1, "MULTIPLE-TESTHARNESS")),
    (b"""// META: script=/resources/testharnessreport.js""", (1, "MULTIPLE-TESTHARNESSREPORT")),
])
def test_script_metadata(filename, input, error):
    errors = check_file_contents("", filename, io.BytesIO(input))
    check_errors(errors)

    if error is not None:
        line, kind = error
        messages = {
            "STRAY-METADATA": "Metadata comments should start the file",
            "INDENTED-METADATA": "Metadata comments should start the line",
            "BROKEN-METADATA": "Metadata comment is not formatted correctly",
            "UNKNOWN-TIMEOUT-METADATA": "Unexpected value for timeout metadata",
            "UNKNOWN-METADATA": "Unexpected kind of metadata",
            "MALFORMED-VARIANT": (
                f"{filename} `META: variant=...` value must be a non empty "
                "string and start with '?' or '#'"),
            "MULTIPLE-TESTHARNESS": (
                "More than one `<script src='/resources/testharness.js'>`"),
            "MULTIPLE-TESTHARNESSREPORT": (
                "More than one `<script src='/resources/testharnessreport.js'>`"),
        }
        assert errors == [
            (kind,
             messages[kind],
             filename,
             line),
        ]
    else:
        assert errors == []


@pytest.mark.parametrize("globals,error", [
    (b"", None),
    (b"default", "UNKNOWN-GLOBAL-METADATA"),
    (b"!default", "UNKNOWN-GLOBAL-METADATA"),
    (b"window", None),
    (b"!window", "UNKNOWN-GLOBAL-METADATA"),
    (b"!dedicatedworker", "UNKNOWN-GLOBAL-METADATA"),
    (b"window, !window", "UNKNOWN-GLOBAL-METADATA"),
    (b"!serviceworker", "UNKNOWN-GLOBAL-METADATA"),
    (b"serviceworker, !serviceworker", "UNKNOWN-GLOBAL-METADATA"),
    (b"worker, !dedicatedworker", "UNKNOWN-GLOBAL-METADATA"),
    (b"worker, !serviceworker", "UNKNOWN-GLOBAL-METADATA"),
    (b"!worker", "UNKNOWN-GLOBAL-METADATA"),
    (b"foo", "UNKNOWN-GLOBAL-METADATA"),
    (b"!foo", "UNKNOWN-GLOBAL-METADATA"),
])
def test_script_globals_metadata(globals, error):
    filename = "foo.any.js"
    input = b"""// META: global=%s\n""" % globals
    errors = check_file_contents("", filename, io.BytesIO(input))
    check_errors(errors)

    if error is not None:
        errors = [(k, f, l) for (k, _, f, l) in errors]
        assert errors == [
            (error,
             filename,
             1),
        ]
    else:
        assert errors == []


@pytest.mark.parametrize("input,error", [
    (b"""#META: timeout=long\n""", None),
    (b"""# META: timeout=long\n""", None),
    (b"""#  META: timeout=long\n""", None),
    (b""""// META:"\n""", None),
    (b"""\n# META: timeout=long\n""", (2, "STRAY-METADATA")),
    (b""" # META: timeout=long\n""", (1, "INDENTED-METADATA")),
    (b"""# META: timeout=long\n# META: timeout=long\n""", None),
    (b"""# META: timeout=long\n\n# META: timeout=long\n""", (3, "STRAY-METADATA")),
    (b"""# META: timeout=long\n# Start of the test\n# META: timeout=long\n""", (3, "STRAY-METADATA")),
    (b"""# META:\n""", (1, "BROKEN-METADATA")),
    (b"""# META: foobar\n""", (1, "BROKEN-METADATA")),
    (b"""# META: foo=bar\n""", (1, "UNKNOWN-METADATA")),
    (b"""# META: timeout=bar\n""", (1, "UNKNOWN-TIMEOUT-METADATA")),
])
def test_python_metadata(input, error):
    filename = "test.py"
    errors = check_file_contents("", filename, io.BytesIO(input))
    check_errors(errors)

    if error is not None:
        line, kind = error
        messages = {
            "STRAY-METADATA": "Metadata comments should start the file",
            "INDENTED-METADATA": "Metadata comments should start the line",
            "BROKEN-METADATA": "Metadata comment is not formatted correctly",
            "UNKNOWN-TIMEOUT-METADATA": "Unexpected value for timeout metadata",
            "UNKNOWN-METADATA": "Unexpected kind of metadata",
        }
        assert errors == [
            (kind,
             messages[kind],
             filename,
             line),
        ]
    else:
        assert errors == []
