diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /testing/web-platform/tests/tools/lint | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'testing/web-platform/tests/tools/lint')
10 files changed, 950 insertions, 0 deletions
diff --git a/testing/web-platform/tests/tools/lint/__init__.py b/testing/web-platform/tests/tools/lint/__init__.py new file mode 100644 index 000000000..e5eb5e12d --- /dev/null +++ b/testing/web-platform/tests/tools/lint/__init__.py @@ -0,0 +1 @@ +from . import lint diff --git a/testing/web-platform/tests/tools/lint/lint.py b/testing/web-platform/tests/tools/lint/lint.py new file mode 100644 index 000000000..2aee3da1c --- /dev/null +++ b/testing/web-platform/tests/tools/lint/lint.py @@ -0,0 +1,426 @@ +from __future__ import print_function, unicode_literals + +import abc +import argparse +import ast +import fnmatch +import json +import os +import re +import subprocess +import sys + +from collections import defaultdict + +from ..localpaths import repo_root + +from manifest.sourcefile import SourceFile +from six import iteritems, itervalues +from six.moves import range + +here = os.path.abspath(os.path.split(__file__)[0]) + +ERROR_MSG = """You must fix all errors; for details on how to fix them, see +https://github.com/w3c/web-platform-tests/blob/master/docs/lint-tool.md + +However, instead of fixing a particular error, it's sometimes +OK to add a line to the lint.whitelist file in the root of the +web-platform-tests directory to make the lint tool ignore it. + +For example, to make the lint tool ignore all '%s' +errors in the %s file, +you could add the following line to the lint.whitelist file. + +%s:%s""" + +def all_git_paths(repo_root): + command_line = ["git", "ls-tree", "-r", "--name-only", "HEAD"] + output = subprocess.check_output(command_line, cwd=repo_root) + for item in output.split("\n"): + yield item + + +def check_path_length(repo_root, path): + if len(path) + 1 > 150: + return [("PATH LENGTH", "/%s longer than maximum path length (%d > 150)" % (path, len(path) + 1), None)] + return [] + + +def parse_whitelist(f): + """ + Parse the whitelist file given by `f`, and return the parsed structure. + """ + + data = defaultdict(lambda:defaultdict(set)) + ignored_files = set() + + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + parts = [item.strip() for item in line.split(":")] + if len(parts) == 2: + parts.append(None) + else: + parts[-1] = int(parts[-1]) + + error_type, file_match, line_number = parts + + if error_type == "*": + ignored_files.add(file_match) + else: + data[file_match][error_type].add(line_number) + + return data, ignored_files + + +def filter_whitelist_errors(data, path, errors): + """ + Filter out those errors that are whitelisted in `data`. + """ + + whitelisted = [False for item in range(len(errors))] + + for file_match, whitelist_errors in iteritems(data): + if fnmatch.fnmatch(path, file_match): + for i, (error_type, msg, path, line) in enumerate(errors): + if error_type in whitelist_errors: + allowed_lines = whitelist_errors[error_type] + if None in allowed_lines or line in allowed_lines: + whitelisted[i] = True + + return [item for i, item in enumerate(errors) if not whitelisted[i]] + +class Regexp(object): + pattern = None + file_extensions = None + error = None + _re = None + + def __init__(self): + self._re = re.compile(self.pattern) + + def applies(self, path): + return (self.file_extensions is None or + os.path.splitext(path)[1] in self.file_extensions) + + def search(self, line): + return self._re.search(line) + +class TrailingWhitespaceRegexp(Regexp): + pattern = b"[ \t\f\v]$" + error = "TRAILING WHITESPACE" + description = "Whitespace at EOL" + +class TabsRegexp(Regexp): + pattern = b"^\t" + error = "INDENT TABS" + description = "Tabs used for indentation" + +class CRRegexp(Regexp): + pattern = b"\r$" + error = "CR AT EOL" + description = "CR character in line separator" + +class W3CTestOrgRegexp(Regexp): + pattern = b"w3c\-test\.org" + error = "W3C-TEST.ORG" + description = "External w3c-test.org domain used" + +class Webidl2Regexp(Regexp): + pattern = b"webidl2\.js" + error = "WEBIDL2.JS" + description = "Legacy webidl2.js script used" + +class ConsoleRegexp(Regexp): + pattern = b"console\.[a-zA-Z]+\s*\(" + error = "CONSOLE" + file_extensions = [".html", ".htm", ".js", ".xht", ".xhtml", ".svg"] + description = "Console logging API used" + +class PrintRegexp(Regexp): + pattern = b"print(?:\s|\s*\()" + error = "PRINT STATEMENT" + file_extensions = [".py"] + description = "Print function used" + +regexps = [item() for item in + [TrailingWhitespaceRegexp, + TabsRegexp, + CRRegexp, + W3CTestOrgRegexp, + Webidl2Regexp, + ConsoleRegexp, + PrintRegexp]] + +def check_regexp_line(repo_root, path, f): + errors = [] + + applicable_regexps = [regexp for regexp in regexps if regexp.applies(path)] + + for i, line in enumerate(f): + for regexp in applicable_regexps: + if regexp.search(line): + errors.append((regexp.error, regexp.description, path, i+1)) + + return errors + +def check_parsed(repo_root, path, f): + source_file = SourceFile(repo_root, path, "/", contents=f.read()) + + errors = [] + + if source_file.name_is_non_test or source_file.name_is_manual: + return [] + + if source_file.markup_type is None: + return [] + + if source_file.root is None: + return [("PARSE-FAILED", "Unable to parse file", path, None)] + + if len(source_file.timeout_nodes) > 1: + errors.append(("MULTIPLE-TIMEOUT", "More than one meta name='timeout'", path, None)) + + for timeout_node in source_file.timeout_nodes: + timeout_value = timeout_node.attrib.get("content", "").lower() + if timeout_value != "long": + errors.append(("INVALID-TIMEOUT", "Invalid timeout value %s" % timeout_value, path, None)) + + if source_file.testharness_nodes: + if len(source_file.testharness_nodes) > 1: + errors.append(("MULTIPLE-TESTHARNESS", + "More than one <script src='/resources/testharness.js'>", path, None)) + + testharnessreport_nodes = source_file.root.findall(".//{http://www.w3.org/1999/xhtml}script[@src='/resources/testharnessreport.js']") + if not testharnessreport_nodes: + errors.append(("MISSING-TESTHARNESSREPORT", + "Missing <script src='/resources/testharnessreport.js'>", path, None)) + else: + if len(testharnessreport_nodes) > 1: + errors.append(("MULTIPLE-TESTHARNESSREPORT", + "More than one <script src='/resources/testharnessreport.js'>", path, None)) + + testharnesscss_nodes = source_file.root.findall(".//{http://www.w3.org/1999/xhtml}link[@href='/resources/testharness.css']") + if testharnesscss_nodes: + errors.append(("PRESENT-TESTHARNESSCSS", + "Explicit link to testharness.css present", path, None)) + + for element in source_file.variant_nodes: + if "content" not in element.attrib: + errors.append(("VARIANT-MISSING", + "<meta name=variant> missing 'content' attribute", path, None)) + else: + variant = element.attrib["content"] + if variant != "" and variant[0] not in ("?", "#"): + errors.append(("MALFORMED-VARIANT", + "%s <meta name=variant> 'content' attribute must be the empty string or start with '?' or '#'" % path, None)) + + seen_elements = {"timeout": False, + "testharness": False, + "testharnessreport": False} + required_elements = [key for key, value in {"testharness": True, + "testharnessreport": len(testharnessreport_nodes) > 0, + "timeout": len(source_file.timeout_nodes) > 0}.items() + if value] + + for elem in source_file.root.iter(): + if source_file.timeout_nodes and elem == source_file.timeout_nodes[0]: + seen_elements["timeout"] = True + if seen_elements["testharness"]: + errors.append(("LATE-TIMEOUT", + "<meta name=timeout> seen after testharness.js script", path, None)) + + elif elem == source_file.testharness_nodes[0]: + seen_elements["testharness"] = True + + elif testharnessreport_nodes and elem == testharnessreport_nodes[0]: + seen_elements["testharnessreport"] = True + if not seen_elements["testharness"]: + errors.append(("EARLY-TESTHARNESSREPORT", + "testharnessreport.js script seen before testharness.js script", path, None)) + + if all(seen_elements[name] for name in required_elements): + break + + + for element in source_file.root.findall(".//{http://www.w3.org/1999/xhtml}script[@src]"): + src = element.attrib["src"] + for name in ["testharness", "testharnessreport"]: + if "%s.js" % name == src or ("/%s.js" % name in src and src != "/resources/%s.js" % name): + errors.append(("%s-PATH" % name.upper(), "%s.js script seen with incorrect path" % name, path, None)) + + + return errors + +class ASTCheck(object): + __metaclass__ = abc.ABCMeta + error = None + description = None + + @abc.abstractmethod + def check(self, root): + pass + +class OpenModeCheck(ASTCheck): + error = "OPEN-NO-MODE" + description = "File opened without providing an explicit mode (note: binary files must be read with 'b' in the mode flags)" + + def check(self, root): + errors = [] + for node in ast.walk(root): + if isinstance(node, ast.Call): + if hasattr(node.func, "id") and node.func.id in ("open", "file"): + if (len(node.args) < 2 and + all(item.arg != "mode" for item in node.keywords)): + errors.append(node.lineno) + return errors + +ast_checkers = [item() for item in [OpenModeCheck]] + +def check_python_ast(repo_root, path, f): + if not path.endswith(".py"): + return [] + + try: + root = ast.parse(f.read()) + except SyntaxError as e: + return [("PARSE-FAILED", "Unable to parse file", path, e.lineno)] + + errors = [] + for checker in ast_checkers: + for lineno in checker.check(root): + errors.append((checker.error, checker.description, path, lineno)) + return errors + + +def check_path(repo_root, path): + """ + Runs lints that check the file path. + + :param repo_root: the repository root + :param path: the path of the file within the repository + :returns: a list of errors found in ``path`` + """ + + errors = [] + for path_fn in path_lints: + errors.extend(path_fn(repo_root, path)) + return errors + + +def check_file_contents(repo_root, path, f): + """ + Runs lints that check the file contents. + + :param repo_root: the repository root + :param path: the path of the file within the repository + :param f: a file-like object with the file contents + :returns: a list of errors found in ``f`` + """ + + errors = [] + for file_fn in file_lints: + errors.extend(file_fn(repo_root, path, f)) + f.seek(0) + return errors + + +def output_errors_text(errors): + for error_type, description, path, line_number in errors: + pos_string = path + if line_number: + pos_string += " %s" % line_number + print("%s: %s %s" % (error_type, pos_string, description)) + +def output_errors_json(errors): + for error_type, error, path, line_number in errors: + print(json.dumps({"path": path, "lineno": line_number, + "rule": error_type, "message": error})) + +def output_error_count(error_count): + if not error_count: + return + + by_type = " ".join("%s: %d" % item for item in error_count.items()) + count = sum(error_count.values()) + if count == 1: + print("There was 1 error (%s)" % (by_type,)) + else: + print("There were %d errors (%s)" % (count, by_type)) + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("paths", nargs="*", + help="List of paths to lint") + parser.add_argument("--json", action="store_true", + help="Output machine-readable JSON format") + return parser.parse_args() + +def main(): + args = parse_args() + paths = args.paths if args.paths else all_git_paths(repo_root) + return lint(repo_root, paths, args.json) + +def lint(repo_root, paths, output_json): + error_count = defaultdict(int) + last = None + + with open(os.path.join(repo_root, "lint.whitelist")) as f: + whitelist, ignored_files = parse_whitelist(f) + + if output_json: + output_errors = output_errors_json + else: + output_errors = output_errors_text + + def process_errors(path, errors): + """ + Filters and prints the errors, and updates the ``error_count`` object. + + :param path: the path of the file that contains the errors + :param errors: a list of error tuples (error type, message, path, line number) + :returns: ``None`` if there were no errors, or + a tuple of the error type and the path otherwise + """ + + errors = filter_whitelist_errors(whitelist, path, errors) + + if not errors: + return None + + output_errors(errors) + for error_type, error, path, line in errors: + error_count[error_type] += 1 + + return (errors[-1][0], path) + + for path in paths: + abs_path = os.path.join(repo_root, path) + if not os.path.exists(abs_path): + continue + + if any(fnmatch.fnmatch(path, file_match) for file_match in ignored_files): + continue + + errors = check_path(repo_root, path) + last = process_errors(path, errors) or last + + if not os.path.isdir(abs_path): + with open(abs_path, 'rb') as f: + errors = check_file_contents(repo_root, path, f) + last = process_errors(path, errors) or last + + if not output_json: + output_error_count(error_count) + if error_count: + print(ERROR_MSG % (last[0], last[1], last[0], last[1])) + return sum(itervalues(error_count)) + +path_lints = [check_path_length] +file_lints = [check_regexp_line, check_parsed, check_python_ast] + +if __name__ == "__main__": + error_count = main() + if error_count > 0: + sys.exit(1) diff --git a/testing/web-platform/tests/tools/lint/tests/__init__.py b/testing/web-platform/tests/tools/lint/tests/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/testing/web-platform/tests/tools/lint/tests/__init__.py diff --git a/testing/web-platform/tests/tools/lint/tests/dummy/broken.html b/testing/web-platform/tests/tools/lint/tests/dummy/broken.html new file mode 100644 index 000000000..74793c43c --- /dev/null +++ b/testing/web-platform/tests/tools/lint/tests/dummy/broken.html @@ -0,0 +1 @@ +THIS LINE HAS TRAILING WHITESPACE diff --git a/testing/web-platform/tests/tools/lint/tests/dummy/broken_ignored.html b/testing/web-platform/tests/tools/lint/tests/dummy/broken_ignored.html new file mode 100644 index 000000000..74793c43c --- /dev/null +++ b/testing/web-platform/tests/tools/lint/tests/dummy/broken_ignored.html @@ -0,0 +1 @@ +THIS LINE HAS TRAILING WHITESPACE diff --git a/testing/web-platform/tests/tools/lint/tests/dummy/lint.whitelist b/testing/web-platform/tests/tools/lint/tests/dummy/lint.whitelist new file mode 100644 index 000000000..a763e4432 --- /dev/null +++ b/testing/web-platform/tests/tools/lint/tests/dummy/lint.whitelist @@ -0,0 +1 @@ +*:broken_ignored.html diff --git a/testing/web-platform/tests/tools/lint/tests/dummy/okay.html b/testing/web-platform/tests/tools/lint/tests/dummy/okay.html new file mode 100644 index 000000000..a3178a3c8 --- /dev/null +++ b/testing/web-platform/tests/tools/lint/tests/dummy/okay.html @@ -0,0 +1 @@ +THIS LINE HAS NO TRAILING WHITESPACE diff --git a/testing/web-platform/tests/tools/lint/tests/test_file_lints.py b/testing/web-platform/tests/tools/lint/tests/test_file_lints.py new file mode 100644 index 000000000..3e3e359b0 --- /dev/null +++ b/testing/web-platform/tests/tools/lint/tests/test_file_lints.py @@ -0,0 +1,356 @@ +from __future__ import unicode_literals + +from ..lint import check_file_contents +import os +import pytest +import six + +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, six.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(): + 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(): + expected = [("INDENT TABS", "Tabs used for indentation", 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(): + expected = [("CR AT EOL", "CR character in line separator", 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(): + expected = [ + ("CR AT EOL", "CR character in line separator", filename, 1), + ("CR AT EOL", "CR character in line separator", 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(): + expected = [("W3C-TEST.ORG", "External w3c-test.org 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(): + 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(): + if kind in ["web-lax", "web-strict", "js"]: + assert errors == [ + ("CONSOLE", "Console logging API used", filename, 2), + ("CONSOLE", "Console logging API used", filename, 3), + ] + else: + assert errors == [("PARSE-FAILED", "Unable to parse file", 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(): + if kind in ["web-lax", "web-strict"]: + assert errors == [ + ("MULTIPLE-TIMEOUT", "More than one meta name='timeout'", filename, None), + ("INVALID-TIMEOUT", "Invalid timeout value ", filename, None), + ("INVALID-TIMEOUT", "Invalid timeout value 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(): + if kind in ["web-lax", "web-strict"]: + assert errors == [ + ("EARLY-TESTHARNESSREPORT", "testharnessreport.js script seen before testharness.js script", 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(): + 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(): + 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_present_testharnesscss(): + code = b""" +<html xmlns="http://www.w3.org/1999/xhtml"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="stylesheet" href="/resources/testharness.css"/> +</html> +""" + error_map = check_with_files(code) + + for (filename, (errors, kind)) in error_map.items(): + if kind in ["web-lax", "web-strict"]: + assert errors == [ + ("PRESENT-TESTHARNESSCSS", "Explicit link to testharness.css present", 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(): + expected = [("W3C-TEST.ORG", "External w3c-test.org domain used", 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(): + expected = [("W3C-TEST.ORG", "External w3c-test.org domain used", 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_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> +</html> +""" + error_map = check_with_files(code) + + for (filename, (errors, kind)) in error_map.items(): + if kind == "python": + assert errors == [ + ("PARSE-FAILED", "Unable to parse file", filename, 1), + ] + else: + assert errors == [] + + +@pytest.mark.skipif(six.PY3, reason="Cannot parse print statements from python 3") +def test_print_statement(): + error_map = check_with_files(b"def foo():\n print 'statement'\n print\n") + + for (filename, (errors, kind)) in error_map.items(): + if kind == "python": + assert errors == [ + ("PRINT STATEMENT", "Print function used", filename, 2), + ("PRINT STATEMENT", "Print function used", filename, 3), + ] + elif kind == "web-strict": + assert errors == [ + ("PARSE-FAILED", "Unable to parse file", filename, None), + ] + else: + assert errors == [] + + +def test_print_function(): + error_map = check_with_files(b"def foo():\n print('function')\n") + + for (filename, (errors, kind)) in error_map.items(): + if kind == "python": + assert errors == [ + ("PRINT STATEMENT", "Print function used", filename, 2), + ] + elif kind == "web-strict": + assert errors == [ + ("PARSE-FAILED", "Unable to parse file", filename, None), + ] + else: + 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", six.BytesIO(code)) + + 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), + ] diff --git a/testing/web-platform/tests/tools/lint/tests/test_lint.py b/testing/web-platform/tests/tools/lint/tests/test_lint.py new file mode 100644 index 000000000..ebca206c8 --- /dev/null +++ b/testing/web-platform/tests/tools/lint/tests/test_lint.py @@ -0,0 +1,138 @@ +from __future__ import unicode_literals + +import os + +import mock +import pytest +import six + +from .. import lint as lint_mod +from ..lint import filter_whitelist_errors, parse_whitelist, lint + +_dummy_repo = os.path.join(os.path.dirname(__file__), "dummy") + + +def _mock_lint(name): + wrapped = getattr(lint_mod, name) + return mock.patch(lint_mod.__name__ + "." + name, wraps=wrapped) + + +def test_filter_whitelist_errors(): + filtered = filter_whitelist_errors({}, '', []) + assert filtered == [] + + +def test_parse_whitelist(): + input_buffer = six.StringIO(""" +# Comment +CR AT EOL: svg/import/* +CR AT EOL: streams/resources/test-utils.js + +INDENT TABS: .gitmodules +INDENT TABS: app-uri/* +INDENT TABS: svg/* + +TRAILING WHITESPACE: app-uri/* + +CONSOLE:streams/resources/test-utils.js: 12 + +*:*.pdf +*:resources/* +""") + + expected_data = { + '.gitmodules': { + 'INDENT TABS': {None}, + }, + 'app-uri/*': { + 'TRAILING WHITESPACE': {None}, + 'INDENT TABS': {None}, + }, + 'streams/resources/test-utils.js': { + 'CONSOLE': {12}, + 'CR AT EOL': {None}, + }, + 'svg/*': { + 'INDENT TABS': {None}, + }, + 'svg/import/*': { + 'CR AT EOL': {None}, + }, + } + expected_ignored = {"*.pdf", "resources/*"} + data, ignored = parse_whitelist(input_buffer) + assert data == expected_data + assert ignored == expected_ignored + + +def test_lint_no_files(capsys): + rv = lint(_dummy_repo, [], False) + assert rv == 0 + out, err = capsys.readouterr() + assert out == "" + assert err == "" + + +def test_lint_ignored_file(capsys): + with _mock_lint("check_path") as mocked_check_path: + with _mock_lint("check_file_contents") as mocked_check_file_contents: + rv = lint(_dummy_repo, ["broken_ignored.html"], False) + assert rv == 0 + assert not mocked_check_path.called + assert not mocked_check_file_contents.called + out, err = capsys.readouterr() + assert out == "" + assert err == "" + + +def test_lint_not_existing_file(capsys): + with _mock_lint("check_path") as mocked_check_path: + with _mock_lint("check_file_contents") as mocked_check_file_contents: + # really long path-linted filename + name = "a" * 256 + ".html" + rv = lint(_dummy_repo, [name], False) + assert rv == 0 + assert not mocked_check_path.called + assert not mocked_check_file_contents.called + out, err = capsys.readouterr() + assert out == "" + assert err == "" + + +def test_lint_passing(capsys): + with _mock_lint("check_path") as mocked_check_path: + with _mock_lint("check_file_contents") as mocked_check_file_contents: + rv = lint(_dummy_repo, ["okay.html"], False) + assert rv == 0 + assert mocked_check_path.call_count == 1 + assert mocked_check_file_contents.call_count == 1 + out, err = capsys.readouterr() + assert out == "" + assert err == "" + + +def test_lint_failing(capsys): + with _mock_lint("check_path") as mocked_check_path: + with _mock_lint("check_file_contents") as mocked_check_file_contents: + rv = lint(_dummy_repo, ["broken.html"], False) + assert rv == 1 + assert mocked_check_path.call_count == 1 + assert mocked_check_file_contents.call_count == 1 + out, err = capsys.readouterr() + assert "TRAILING WHITESPACE" in out + assert "broken.html 1 " in out + assert err == "" + + +def test_lint_passing_and_failing(capsys): + with _mock_lint("check_path") as mocked_check_path: + with _mock_lint("check_file_contents") as mocked_check_file_contents: + rv = lint(_dummy_repo, ["broken.html", "okay.html"], False) + assert rv == 1 + assert mocked_check_path.call_count == 2 + assert mocked_check_file_contents.call_count == 2 + out, err = capsys.readouterr() + assert "TRAILING WHITESPACE" in out + assert "broken.html 1 " in out + assert "okay.html" not in out + assert err == "" diff --git a/testing/web-platform/tests/tools/lint/tests/test_path_lints.py b/testing/web-platform/tests/tools/lint/tests/test_path_lints.py new file mode 100644 index 000000000..83cb8aaa6 --- /dev/null +++ b/testing/web-platform/tests/tools/lint/tests/test_path_lints.py @@ -0,0 +1,25 @@ +from __future__ import unicode_literals + +from ..lint import check_path +import pytest +import six + +def test_allowed_path_length(): + basename = 29 * "test/" + + for idx in range(5): + filename = basename + idx * "a" + + errors = check_path("/foo/", filename) + assert errors == [] + + +def test_forbidden_path_length(): + basename = 29 * "test/" + + for idx in range(5, 10): + filename = basename + idx * "a" + message = "/%s longer than maximum path length (%s > 150)" % (filename, 146 + idx) + + errors = check_path("/foo/", filename) + assert errors == [("PATH LENGTH", message, None)] |