summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/tools/lint
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /testing/web-platform/tests/tools/lint
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-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')
-rw-r--r--testing/web-platform/tests/tools/lint/__init__.py1
-rw-r--r--testing/web-platform/tests/tools/lint/lint.py426
-rw-r--r--testing/web-platform/tests/tools/lint/tests/__init__.py0
-rw-r--r--testing/web-platform/tests/tools/lint/tests/dummy/broken.html1
-rw-r--r--testing/web-platform/tests/tools/lint/tests/dummy/broken_ignored.html1
-rw-r--r--testing/web-platform/tests/tools/lint/tests/dummy/lint.whitelist1
-rw-r--r--testing/web-platform/tests/tools/lint/tests/dummy/okay.html1
-rw-r--r--testing/web-platform/tests/tools/lint/tests/test_file_lints.py356
-rw-r--r--testing/web-platform/tests/tools/lint/tests/test_lint.py138
-rw-r--r--testing/web-platform/tests/tools/lint/tests/test_path_lints.py25
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)]