diff options
Diffstat (limited to '')
119 files changed, 3837 insertions, 0 deletions
diff --git a/tools/lint/test-manifest-alpha.yml b/tools/lint/test-manifest-alpha.yml new file mode 100644 index 0000000000..cd4761e4b6 --- /dev/null +++ b/tools/lint/test-manifest-alpha.yml @@ -0,0 +1,19 @@ +--- +test-manifest-alpha: + description: Mochitest manifest tests should be in alphabetical order. + exclude: + - "**/application.ini" + - "**/l10n.ini" + - "**/xpcshell.ini" + - "**/python.ini" + - "**/manifest.ini" + - dom/canvas/test/webgl-conf/mochitest-errata.ini + - python/mozbuild/mozbuild/test/backend/data + - testing/mozbase/manifestparser/tests + - testing/web-platform + - xpcom/tests/unit/data + extensions: ['ini'] + type: external + payload: test-manifest-alpha:lint + support-files: + - 'tools/lint/test-manifest-alpha/error-level-manifests.yml' diff --git a/tools/lint/test-manifest-alpha/__init__.py b/tools/lint/test-manifest-alpha/__init__.py new file mode 100644 index 0000000000..87d6ce5c5d --- /dev/null +++ b/tools/lint/test-manifest-alpha/__init__.py @@ -0,0 +1,77 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import difflib +import os +import sys + +import yaml +from manifestparser import TestManifest +from mozlint import result +from mozlint.pathutils import expand_exclusions +from mozpack import path as mozpath + +# Since this linter matches on all files with .ini extensions, we can omit +# those extensions from this allowlist, which makes it easier to match +# against variants like "mochitest-serviceworker.ini". +FILENAME_ALLOWLIST = ["mochitest", "browser", "chrome", "a11y"] + +here = os.path.abspath(os.path.dirname(__file__)) +ERROR_LEVEL_MANIFESTS_PATH = os.path.join(here, "error-level-manifests.yml") + + +def lint(paths, config, fix=None, **lintargs): + try: + with open(ERROR_LEVEL_MANIFESTS_PATH) as f: + error_level_manifests = yaml.safe_load(f) + except (IOError, ValueError) as e: + print("{}: error:\n {}".format(ERROR_LEVEL_MANIFESTS_PATH, e), file=sys.stderr) + sys.exit(1) + + topsrcdir = lintargs["root"] + + results = [] + file_names = list(expand_exclusions(paths, config, lintargs["root"])) + + for file_name in file_names: + name = os.path.basename(file_name) + if not any(name.startswith(allowed) for allowed in FILENAME_ALLOWLIST): + continue + + manifest = TestManifest(manifests=(file_name,), strict=False) + + test_names = [test["name"] for test in manifest.tests] + sorted_test_names = sorted(test_names) + + if test_names != sorted_test_names: + rel_file_path = mozpath.relpath(file_name, topsrcdir) + level = "warning" + + if (mozpath.normsep(rel_file_path) in error_level_manifests) or ( + any( + mozpath.match(rel_file_path, e) + for e in error_level_manifests + if "*" in e + ) + ): + level = "error" + + diff_instance = difflib.Differ() + diff_result = diff_instance.compare(test_names, sorted_test_names) + diff_list = list(diff_result) + + res = { + "path": rel_file_path, + "lineno": 0, + "column": 0, + "message": ( + "The mochitest test manifest is not in alphabetical order. " + "Expected ordering: \n\n%s\n\n" % "\n".join(sorted_test_names) + ), + "level": level, + "diff": "\n".join(diff_list), + } + results.append(result.from_config(config, **res)) + + return {"results": results, "fixed": 0} diff --git a/tools/lint/test-manifest-alpha/error-level-manifests.yml b/tools/lint/test-manifest-alpha/error-level-manifests.yml new file mode 100644 index 0000000000..90c091a3b8 --- /dev/null +++ b/tools/lint/test-manifest-alpha/error-level-manifests.yml @@ -0,0 +1,7 @@ +--- +# This file contains a list of manifest files or directory patterns that have +# have been put into alphabetical order already. Items in this list will +# cause the test-manifest-alpha linter to use the Error level rather than +# the Warning level. + +- browser/** diff --git a/tools/lint/test-manifest-disable.yml b/tools/lint/test-manifest-disable.yml new file mode 100644 index 0000000000..7d93889704 --- /dev/null +++ b/tools/lint/test-manifest-disable.yml @@ -0,0 +1,16 @@ +--- +no-comment-disable: + description: > + "Use 'disabled=<reason>' to disable a test instead of a + comment" + include: ['.'] + exclude: + - "**/application.ini" + - "**/l10n.ini" + - dom/canvas/test/webgl-conf/mochitest-errata.ini + - testing/mozbase/manifestparser/tests + - testing/web-platform + - xpcom/tests/unit/data + extensions: ['ini'] + type: regex + payload: ^[ \t]*(#|;)[ \t]*\[ diff --git a/tools/lint/test-manifest-skip-if.yml b/tools/lint/test-manifest-skip-if.yml new file mode 100644 index 0000000000..7ad1bd717c --- /dev/null +++ b/tools/lint/test-manifest-skip-if.yml @@ -0,0 +1,18 @@ +--- +multiline-skip-if: + description: Conditions joined by || should be on separate lines + hint: | + skip-if = + <condition one> # reason + <condition two> # reason + exclude: + - "**/application.ini" + - "**/l10n.ini" + - dom/canvas/test/webgl-conf/mochitest-errata.ini + - testing/mozbase/manifestparser/tests + - testing/web-platform + - xpcom/tests/unit/data + extensions: ['ini'] + level: warning + type: regex + payload: '^\s*(skip|fail)-if\s*=[^(]*\|\|' diff --git a/tools/lint/test/conftest.py b/tools/lint/test/conftest.py new file mode 100644 index 0000000000..f8cf8a30b6 --- /dev/null +++ b/tools/lint/test/conftest.py @@ -0,0 +1,305 @@ +import logging +import os +import pathlib +import sys +from collections import defaultdict + +import pytest +from mozbuild.base import MozbuildObject +from mozlint.parser import Parser +from mozlint.pathutils import findobject +from mozlint.result import ResultSummary +from mozlog.structuredlog import StructuredLogger +from mozpack import path + +here = path.abspath(path.dirname(__file__)) +build = MozbuildObject.from_environment(cwd=here, virtualenv_name="python-test") + +lintdir = path.dirname(here) +sys.path.insert(0, lintdir) +logger = logging.getLogger("mozlint") + + +def pytest_generate_tests(metafunc): + """Finds, loads and returns the config for the linter name specified by the + LINTER global variable in the calling module. + + This implies that each test file (that uses this fixture) should only be + used to test a single linter. If no LINTER variable is defined, the test + will fail. + """ + if "config" in metafunc.fixturenames: + if not hasattr(metafunc.module, "LINTER"): + pytest.fail( + "'config' fixture used from a module that didn't set the LINTER variable" + ) + + name = metafunc.module.LINTER + config_path = path.join(lintdir, "{}.yml".format(name)) + parser = Parser(build.topsrcdir) + configs = parser.parse(config_path) + config_names = {config["name"] for config in configs} + + marker = metafunc.definition.get_closest_marker("lint_config") + if marker: + config_name = marker.kwargs["name"] + if config_name not in config_names: + pytest.fail(f"lint config {config_name} not present in {name}.yml") + configs = [ + config for config in configs if config["name"] == marker.kwargs["name"] + ] + + ids = [config["name"] for config in configs] + metafunc.parametrize("config", configs, ids=ids) + + +@pytest.fixture(scope="module") +def root(request): + """Return the root directory for the files of the linter under test. + + For example, with LINTER=flake8 this would be tools/lint/test/files/flake8. + """ + if not hasattr(request.module, "LINTER"): + pytest.fail( + "'root' fixture used from a module that didn't set the LINTER variable" + ) + return path.join(here, "files", request.module.LINTER) + + +@pytest.fixture(scope="module") +def paths(root): + """Return a function that can resolve file paths relative to the linter + under test. + + Can be used like `paths('foo.py', 'bar/baz')`. This will return a list of + absolute paths under the `root` files directory. + """ + + def _inner(*paths): + if not paths: + return [root] + return [path.normpath(path.join(root, p)) for p in paths] + + return _inner + + +@pytest.fixture(autouse=True) +def run_setup(config): + """Make sure that if the linter named in the LINTER global variable has a + setup function, it gets called before running the tests. + """ + if "setup" not in config: + return + + if config["name"] == "clang-format": + # Skip the setup for the clang-format linter, as it requires a Mach context + # (which we may not have if pytest is invoked directly). + return + + log = logging.LoggerAdapter( + logger, {"lintname": config.get("name"), "pid": os.getpid()} + ) + + func = findobject(config["setup"]) + func( + build.topsrcdir, + virtualenv_manager=build.virtualenv_manager, + virtualenv_bin_path=build.virtualenv_manager.bin_path, + log=log, + ) + + +@pytest.fixture +def lint(config, root, request): + """Find and return the 'lint' function for the external linter named in the + LINTER global variable. + + This will automatically pass in the 'config' and 'root' arguments if not + specified. + """ + + if hasattr(request.module, "fixed"): + request.module.fixed = 0 + + try: + func = findobject(config["payload"]) + except (ImportError, ValueError): + pytest.fail( + "could not resolve a lint function from '{}'".format(config["payload"]) + ) + + ResultSummary.root = root + + def wrapper(paths, config=config, root=root, collapse_results=False, **lintargs): + logger.setLevel(logging.DEBUG) + lintargs["log"] = logging.LoggerAdapter( + logger, {"lintname": config.get("name"), "pid": os.getpid()} + ) + + results = func(paths, config, root=root, **lintargs) + if hasattr(request.module, "fixed") and isinstance(results, dict): + request.module.fixed += results["fixed"] + + if isinstance(results, dict): + results = results["results"] + + if isinstance(results, (list, tuple)): + results = sorted(results) + + if not collapse_results: + return results + + ret = defaultdict(list) + for r in results: + ret[r.relpath].append(r) + return ret + + return wrapper + + +@pytest.fixture +def structuredlog_lint(config, root, logger=None): + """Find and return the 'lint' function for the external linter named in the + LINTER global variable. This variant of the lint function is for linters that + use the 'structuredlog' type. + + This will automatically pass in the 'config' and 'root' arguments if not + specified. + """ + try: + func = findobject(config["payload"]) + except (ImportError, ValueError): + pytest.fail( + "could not resolve a lint function from '{}'".format(config["payload"]) + ) + + ResultSummary.root = root + + if not logger: + logger = structured_logger() + + def wrapper( + paths, + config=config, + root=root, + logger=logger, + collapse_results=False, + **lintargs, + ): + lintargs["log"] = logging.LoggerAdapter( + logger, {"lintname": config.get("name"), "pid": os.getpid()} + ) + results = func(paths, config, root=root, logger=logger, **lintargs) + if not collapse_results: + return results + + ret = defaultdict(list) + for r in results: + ret[r.path].append(r) + return ret + + return wrapper + + +@pytest.fixture +def global_lint(config, root, request): + try: + func = findobject(config["payload"]) + except (ImportError, ValueError): + pytest.fail( + "could not resolve a lint function from '{}'".format(config["payload"]) + ) + + ResultSummary.root = root + + def wrapper(config=config, root=root, collapse_results=False, **lintargs): + logger.setLevel(logging.DEBUG) + lintargs["log"] = logging.LoggerAdapter( + logger, {"lintname": config.get("name"), "pid": os.getpid()} + ) + results = func(config, root=root, **lintargs) + if hasattr(request.module, "fixed") and isinstance(results, dict): + request.module.fixed += results["fixed"] + + if isinstance(results, dict): + results = results["results"] + + if isinstance(results, (list, tuple)): + results = sorted(results) + + if not collapse_results: + return results + + ret = defaultdict(list) + for r in results: + ret[r.relpath].append(r) + return ret + + return wrapper + + +@pytest.fixture +def create_temp_file(tmpdir): + def inner(contents, name=None): + name = name or "temp.py" + path = tmpdir.join(name) + path.write(contents) + return path.strpath + + return inner + + +@pytest.fixture +def structured_logger(): + return StructuredLogger("logger") + + +@pytest.fixture +def perfdocs_sample(): + from test_perfdocs import ( + DYNAMIC_SAMPLE_CONFIG, + SAMPLE_CONFIG, + SAMPLE_INI, + SAMPLE_TEST, + temp_dir, + temp_file, + ) + + with temp_dir() as tmpdir: + suite_dir = pathlib.Path(tmpdir, "suite") + raptor_dir = pathlib.Path(tmpdir, "raptor") + raptor_suitedir = pathlib.Path(tmpdir, "raptor", "suite") + raptor_another_suitedir = pathlib.Path(tmpdir, "raptor", "another_suite") + perfdocs_dir = pathlib.Path(tmpdir, "perfdocs") + + perfdocs_dir.mkdir(parents=True, exist_ok=True) + suite_dir.mkdir(parents=True, exist_ok=True) + raptor_dir.mkdir(parents=True, exist_ok=True) + raptor_suitedir.mkdir(parents=True, exist_ok=True) + raptor_another_suitedir.mkdir(parents=True, exist_ok=True) + + with temp_file( + "perftest.ini", tempdir=suite_dir, content="[perftest_sample.js]" + ) as tmpmanifest, temp_file( + "raptor_example1.ini", tempdir=raptor_suitedir, content=SAMPLE_INI + ) as tmpexample1manifest, temp_file( + "raptor_example2.ini", tempdir=raptor_another_suitedir, content=SAMPLE_INI + ) as tmpexample2manifest, temp_file( + "perftest_sample.js", tempdir=suite_dir, content=SAMPLE_TEST + ) as tmptest, temp_file( + "config.yml", tempdir=perfdocs_dir, content=SAMPLE_CONFIG + ) as tmpconfig, temp_file( + "config_2.yml", tempdir=perfdocs_dir, content=DYNAMIC_SAMPLE_CONFIG + ) as tmpconfig_2, temp_file( + "index.rst", tempdir=perfdocs_dir, content="{documentation}" + ) as tmpindex: + yield { + "top_dir": tmpdir, + "manifest": {"path": tmpmanifest}, + "example1_manifest": tmpexample1manifest, + "example2_manifest": tmpexample2manifest, + "test": tmptest, + "config": tmpconfig, + "config_2": tmpconfig_2, + "index": tmpindex, + } diff --git a/tools/lint/test/files/android-format/Bad.java b/tools/lint/test/files/android-format/Bad.java new file mode 100644 index 0000000000..13aa5d49d5 --- /dev/null +++ b/tools/lint/test/files/android-format/Bad.java @@ -0,0 +1,8 @@ +package org.mozilla.geckoview; + +import java.util.Arrays; + +public class Bad { + public static void main() { + } +} diff --git a/tools/lint/test/files/android-format/Main.kt b/tools/lint/test/files/android-format/Main.kt new file mode 100644 index 0000000000..a172cf71ee --- /dev/null +++ b/tools/lint/test/files/android-format/Main.kt @@ -0,0 +1,7 @@ +package org.mozilla.geckoview + +import java.util.Arrays; + +fun main() { +println("Hello") +} diff --git a/tools/lint/test/files/android-format/build.gradle b/tools/lint/test/files/android-format/build.gradle new file mode 100644 index 0000000000..6d2aff6d60 --- /dev/null +++ b/tools/lint/test/files/android-format/build.gradle @@ -0,0 +1 @@ +buildDir "${topobjdir}/gradle/build/tools/lint/test/files/android-format" diff --git a/tools/lint/test/files/black/bad.py b/tools/lint/test/files/black/bad.py new file mode 100644 index 0000000000..0a50df4dd9 --- /dev/null +++ b/tools/lint/test/files/black/bad.py @@ -0,0 +1,6 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +print ( + "test" + ) diff --git a/tools/lint/test/files/black/invalid.py b/tools/lint/test/files/black/invalid.py new file mode 100644 index 0000000000..079ecbad30 --- /dev/null +++ b/tools/lint/test/files/black/invalid.py @@ -0,0 +1,4 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +print( diff --git a/tools/lint/test/files/clang-format/bad/bad.cpp b/tools/lint/test/files/clang-format/bad/bad.cpp new file mode 100644 index 0000000000..f08a83f795 --- /dev/null +++ b/tools/lint/test/files/clang-format/bad/bad.cpp @@ -0,0 +1,6 @@ +int main ( ) { + +return 0; + + +} diff --git a/tools/lint/test/files/clang-format/bad/bad2.c b/tools/lint/test/files/clang-format/bad/bad2.c new file mode 100644 index 0000000000..9792e85071 --- /dev/null +++ b/tools/lint/test/files/clang-format/bad/bad2.c @@ -0,0 +1,8 @@ +#include "bad2.h" + + +int bad2() { + int a =2; + return a; + +} diff --git a/tools/lint/test/files/clang-format/bad/bad2.h b/tools/lint/test/files/clang-format/bad/bad2.h new file mode 100644 index 0000000000..a35d49d7e7 --- /dev/null +++ b/tools/lint/test/files/clang-format/bad/bad2.h @@ -0,0 +1 @@ +int bad2(void ); diff --git a/tools/lint/test/files/clang-format/bad/good.cpp b/tools/lint/test/files/clang-format/bad/good.cpp new file mode 100644 index 0000000000..76e8197013 --- /dev/null +++ b/tools/lint/test/files/clang-format/bad/good.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tools/lint/test/files/clang-format/good/foo.cpp b/tools/lint/test/files/clang-format/good/foo.cpp new file mode 100644 index 0000000000..76e8197013 --- /dev/null +++ b/tools/lint/test/files/clang-format/good/foo.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tools/lint/test/files/clippy/test1/Cargo.toml b/tools/lint/test/files/clippy/test1/Cargo.toml new file mode 100644 index 0000000000..92d5072eca --- /dev/null +++ b/tools/lint/test/files/clippy/test1/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "hello_world" # the name of the package +version = "0.1.0" # the current version, obeying semver +authors = ["Alice <a@example.com>", "Bob <b@example.com>"] + +[[bin]] +name ="good" +path = "good.rs" + +[[bin]] +name ="bad" +path = "bad.rs" + +[[bin]] +name ="bad2" +path = "bad2.rs" + diff --git a/tools/lint/test/files/clippy/test1/bad.rs b/tools/lint/test/files/clippy/test1/bad.rs new file mode 100644 index 0000000000..c403fba603 --- /dev/null +++ b/tools/lint/test/files/clippy/test1/bad.rs @@ -0,0 +1,14 @@ +fn main() { + // Statements here are executed when the compiled binary is called + + // Print text to the console + println!("Hello World!"); + // Clippy detects this as a swap and considers this as an error + let mut a=1; + let mut b=1; + + a = b; + b = a; + + +} diff --git a/tools/lint/test/files/clippy/test1/bad2.rs b/tools/lint/test/files/clippy/test1/bad2.rs new file mode 100644 index 0000000000..bf488bbe72 --- /dev/null +++ b/tools/lint/test/files/clippy/test1/bad2.rs @@ -0,0 +1,14 @@ +fn main() { + // Statements here are executed when the compiled binary is called + + // Print text to the console + println!("Hello World!"); + let mut a; + let mut b=1; + let mut vec = vec![1, 2]; + + for x in 5..10 - 5 { + a = x; + } + + } diff --git a/tools/lint/test/files/clippy/test1/good.rs b/tools/lint/test/files/clippy/test1/good.rs new file mode 100644 index 0000000000..9bcaee67b7 --- /dev/null +++ b/tools/lint/test/files/clippy/test1/good.rs @@ -0,0 +1,6 @@ +fn main() { + // Statements here are executed when the compiled binary is called + + // Print text to the console + println!("Hello World!"); +} diff --git a/tools/lint/test/files/clippy/test2/Cargo.lock b/tools/lint/test/files/clippy/test2/Cargo.lock new file mode 100644 index 0000000000..6b2bc69eeb --- /dev/null +++ b/tools/lint/test/files/clippy/test2/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "hello_world_2" +version = "0.2.0" diff --git a/tools/lint/test/files/clippy/test2/Cargo.toml b/tools/lint/test/files/clippy/test2/Cargo.toml new file mode 100644 index 0000000000..b0ac992088 --- /dev/null +++ b/tools/lint/test/files/clippy/test2/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "hello_world_2" # the name of the package +version = "0.2.0" # the current version, obeying semver +authors = ["Alice <a@example.com>", "Bob <b@example.com>"] + +[[bin]] +name = "fake_lib1" +path = "src/bad_1.rs" diff --git a/tools/lint/test/files/clippy/test2/src/bad_1.rs b/tools/lint/test/files/clippy/test2/src/bad_1.rs new file mode 100644 index 0000000000..2fe0630202 --- /dev/null +++ b/tools/lint/test/files/clippy/test2/src/bad_1.rs @@ -0,0 +1,15 @@ +mod bad_2; + +fn main() { + // Statements here are executed when the compiled binary is called + + // Print text to the console + println!("Hello World!"); + // Clippy detects this as a swap and considers this as an error + let mut a=1; + let mut b=1; + + a = b; + b = a; + +} diff --git a/tools/lint/test/files/clippy/test2/src/bad_2.rs b/tools/lint/test/files/clippy/test2/src/bad_2.rs new file mode 100644 index 0000000000..f77de330b4 --- /dev/null +++ b/tools/lint/test/files/clippy/test2/src/bad_2.rs @@ -0,0 +1,17 @@ +fn foo() { + // Statements here are executed when the compiled binary is called + + // Print text to the console + println!("Hello World!"); + let mut a; + let mut b=1; + let mut vec = Vec::new(); + vec.push(1); + vec.push(2); + + + for x in 5..10 - 5 { + a = x; + } + + } diff --git a/tools/lint/test/files/codespell/ignore.rst b/tools/lint/test/files/codespell/ignore.rst new file mode 100644 index 0000000000..1371d07054 --- /dev/null +++ b/tools/lint/test/files/codespell/ignore.rst @@ -0,0 +1,5 @@ +This is a file with some typos and informations. +But also testing false positive like optin (because this isn't always option) +or stuff related to our coding style like: +aparent (aParent). +but detects mistakes like mozila diff --git a/tools/lint/test/files/eslint/good.js b/tools/lint/test/files/eslint/good.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/lint/test/files/eslint/good.js diff --git a/tools/lint/test/files/eslint/import/bad_import.js b/tools/lint/test/files/eslint/import/bad_import.js new file mode 100644 index 0000000000..e2a8ec8de1 --- /dev/null +++ b/tools/lint/test/files/eslint/import/bad_import.js @@ -0,0 +1 @@ +/* import-globals-from notpresent/notpresent.js */ diff --git a/tools/lint/test/files/eslint/nolint/foo.txt b/tools/lint/test/files/eslint/nolint/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/lint/test/files/eslint/nolint/foo.txt diff --git a/tools/lint/test/files/eslint/subdir/bad.js b/tools/lint/test/files/eslint/subdir/bad.js new file mode 100644 index 0000000000..9d2dd18f39 --- /dev/null +++ b/tools/lint/test/files/eslint/subdir/bad.js @@ -0,0 +1,2 @@ +// Missing semicolon +let foo = "bar" diff --git a/tools/lint/test/files/eslint/testprettierignore b/tools/lint/test/files/eslint/testprettierignore new file mode 100644 index 0000000000..c2df665174 --- /dev/null +++ b/tools/lint/test/files/eslint/testprettierignore @@ -0,0 +1 @@ +# Intentionally empty file. diff --git a/tools/lint/test/files/file-perm/maybe-shebang/bad.js b/tools/lint/test/files/file-perm/maybe-shebang/bad.js new file mode 100755 index 0000000000..1a0b4c5fd6 --- /dev/null +++ b/tools/lint/test/files/file-perm/maybe-shebang/bad.js @@ -0,0 +1,2 @@ +# Nothing too + diff --git a/tools/lint/test/files/file-perm/maybe-shebang/good.js b/tools/lint/test/files/file-perm/maybe-shebang/good.js new file mode 100755 index 0000000000..8149c0d4f3 --- /dev/null +++ b/tools/lint/test/files/file-perm/maybe-shebang/good.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + + +# Nothing + diff --git a/tools/lint/test/files/file-perm/no-shebang/bad-shebang.c b/tools/lint/test/files/file-perm/no-shebang/bad-shebang.c new file mode 100755 index 0000000000..7151678efa --- /dev/null +++ b/tools/lint/test/files/file-perm/no-shebang/bad-shebang.c @@ -0,0 +1,2 @@ +#!/bin/bash +int main() { return 0; } diff --git a/tools/lint/test/files/file-perm/no-shebang/bad.c b/tools/lint/test/files/file-perm/no-shebang/bad.c new file mode 100755 index 0000000000..76e8197013 --- /dev/null +++ b/tools/lint/test/files/file-perm/no-shebang/bad.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tools/lint/test/files/file-perm/no-shebang/bad.png b/tools/lint/test/files/file-perm/no-shebang/bad.png Binary files differnew file mode 100755 index 0000000000..db3a5fda7e --- /dev/null +++ b/tools/lint/test/files/file-perm/no-shebang/bad.png diff --git a/tools/lint/test/files/file-perm/no-shebang/good.c b/tools/lint/test/files/file-perm/no-shebang/good.c new file mode 100644 index 0000000000..76e8197013 --- /dev/null +++ b/tools/lint/test/files/file-perm/no-shebang/good.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tools/lint/test/files/file-whitespace/bad-newline.c b/tools/lint/test/files/file-whitespace/bad-newline.c new file mode 100644 index 0000000000..3746a0add3 --- /dev/null +++ b/tools/lint/test/files/file-whitespace/bad-newline.c @@ -0,0 +1,3 @@ +int main() { return 0; } + +
\ No newline at end of file diff --git a/tools/lint/test/files/file-whitespace/bad-windows.c b/tools/lint/test/files/file-whitespace/bad-windows.c new file mode 100644 index 0000000000..70d4c697b9 --- /dev/null +++ b/tools/lint/test/files/file-whitespace/bad-windows.c @@ -0,0 +1,3 @@ +int main(){
+ return 42;
+}
diff --git a/tools/lint/test/files/file-whitespace/bad.c b/tools/lint/test/files/file-whitespace/bad.c new file mode 100644 index 0000000000..4309b1f55d --- /dev/null +++ b/tools/lint/test/files/file-whitespace/bad.c @@ -0,0 +1,3 @@ +int main() { +return 0; +} diff --git a/tools/lint/test/files/file-whitespace/bad.js b/tools/lint/test/files/file-whitespace/bad.js new file mode 100644 index 0000000000..3441696ef1 --- /dev/null +++ b/tools/lint/test/files/file-whitespace/bad.js @@ -0,0 +1,3 @@ +# Nothing too + + diff --git a/tools/lint/test/files/file-whitespace/good.c b/tools/lint/test/files/file-whitespace/good.c new file mode 100644 index 0000000000..76e8197013 --- /dev/null +++ b/tools/lint/test/files/file-whitespace/good.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tools/lint/test/files/file-whitespace/good.js b/tools/lint/test/files/file-whitespace/good.js new file mode 100644 index 0000000000..8149c0d4f3 --- /dev/null +++ b/tools/lint/test/files/file-whitespace/good.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + + +# Nothing + diff --git a/tools/lint/test/files/fluent-lint/bad.ftl b/tools/lint/test/files/fluent-lint/bad.ftl new file mode 100644 index 0000000000..ebc0e1a602 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/bad.ftl @@ -0,0 +1,44 @@ +Blah-blah = Uppercase letters in identifiers are not permitted. +blah-blah = This is a legal identifier. +blah_blah = Underscores in identifiers are not permitted. + +bad-apostrophe-1 = The bee's knees +bad-apostrophe-end-1 = The bees' knees +bad-apostrophe-2 = The bee‘s knees +bad-single-quote = 'The bee’s knees' +ok-apostrophe = The bee’s knees +ok-single-quote = ‘The bee’s knees’ +bad-double-quote = "The bee’s knees" +good-double-quote = “The bee’s knees” +bad-ellipsis = The bee’s knees... +good-ellipsis = The bee’s knees… + +embedded-tag = Read more about <a data-l10n-name="privacy-policy"> our privacy policy </a>. +bad-embedded-tag = Read more about <a data-l10n-name="privacy-policy"> our 'privacy' policy </a>. + +Invalid_Id = This identifier is in the exclusions file and will not cause an error. + +good-has-attributes = + .accessKey = Attribute identifiers are not checked. + +bad-has-attributes = + .accessKey = Attribute 'values' are checked. + +good-function-call = Last modified: { DATETIME($timeChanged, day: "numeric", month: "long", year: "numeric") } + +# $engineName (String) - The engine name that will currently be used for the private window. +good-variable-identifier = { $engineName } is your default search engine in Private Windows + +short-id = I am too short + +identifiers-in-selectors-should-be-ignored = + .label = { $tabCount -> + [1] Send Tab to Device + [UPPERCASE] Send Tab to Device + *[other] Send { $tabCount } Tabs to Device + } + +this-message-reference-is-ignored = + .label = { menu-quit.label } + +ok-message-with-html-and-var = This is a <a href="{ $url }">link</a> diff --git a/tools/lint/test/files/fluent-lint/brand-names-excluded.ftl b/tools/lint/test/files/fluent-lint/brand-names-excluded.ftl new file mode 100644 index 0000000000..9f3afa28b8 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/brand-names-excluded.ftl @@ -0,0 +1,2 @@ +# Comment +bad-firefox1 = Welcome to Firefox diff --git a/tools/lint/test/files/fluent-lint/brand-names.ftl b/tools/lint/test/files/fluent-lint/brand-names.ftl new file mode 100644 index 0000000000..c338d920ca --- /dev/null +++ b/tools/lint/test/files/fluent-lint/brand-names.ftl @@ -0,0 +1,30 @@ +bad-firefox1 = Welcome to Firefox + +# Comment should be ignored when displaying the offset of the error +bad-firefox2 = Welcome to Firefox again +bad-firefox2b = <span>Welcome to Firefox<span> again +bad-firefox3 = <b>Firefox</b> +bad-firefox-excluded = <b>Firefox</b> + +bad-mozilla1 = Welcome to Mozilla +bad-mozilla2 = Welcome to Mozilla again +bad-mozilla2b = <span>Welcome to Mozilla</span> again +bad-mozilla3 = <b>Mozilla</b> + +bad-thunderbird1 = Welcome to Thunderbird +bad-thunderbird2 = <span>Welcome to Thunderbird</span> again +bad-thunderbird3 = <b>Thunderbird</b> + +good-firefox1 = Welcome to { -brand-firefox } +good-firefox2 = Welcome to { firefox-message } + +good-mozilla1 = Welcome to { -brand-mozilla } +good-mozilla2 = Welcome to { mozilla-message } + +good-thunderbird1 = Welcome to { -brand-thunderbird } +good-thunderbird2 = Welcome to { thunderbird-message } + +# There are no brand checks on terms +-brand-firefox = Firefox + +bland-message = No brands here. diff --git a/tools/lint/test/files/fluent-lint/comment-group1.ftl b/tools/lint/test/files/fluent-lint/comment-group1.ftl new file mode 100644 index 0000000000..32c19dc441 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-group1.ftl @@ -0,0 +1,35 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +### Test group comments. + +fake-identifier-1 = Fake text + +## Pass: This group comment has proper spacing. + +fake-identifier-2 = Fake text +## Fail: (GC03) Group comments should have an empty line before them. + +fake-identifier-3 = Fake text + +## Fail: (GC02) Group comments should have an empty line after them. +fake-identifier-4 = Fake text + +## Pass: A single comment is fine. + +## Fail: (GC04) A group comment must be followed by at least one message. + +fake-identifier-5 = Fake text + + +## Fail: (GC03) Only allow 1 line above. + +fake-identifier-6 = Fake text + +## Fail: (GC02) Only allow 1 line below. + + +fake-identifier-6 = Fake text + +## Fail: (GC01) Group comments should not be at the end of a file. diff --git a/tools/lint/test/files/fluent-lint/comment-group2.ftl b/tools/lint/test/files/fluent-lint/comment-group2.ftl new file mode 100644 index 0000000000..47d29fa211 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-group2.ftl @@ -0,0 +1,15 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +### Test group comments. + +## Pass: This group comment is followed by a term + +-fake-term = Fake text + +## Pass: The last group comment is allowed to be an empty ## + +fake-identifier-1 = Fake text + +## diff --git a/tools/lint/test/files/fluent-lint/comment-resource1.ftl b/tools/lint/test/files/fluent-lint/comment-resource1.ftl new file mode 100644 index 0000000000..f5d5e53d59 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-resource1.ftl @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +### Pass: This is a resource comment with proper spacing. + +fake-identifier-1 = Fake text + +### Fail: (RC01) There should not be more than one resource comment + +fake-identifier-2 = Fake text diff --git a/tools/lint/test/files/fluent-lint/comment-resource2.ftl b/tools/lint/test/files/fluent-lint/comment-resource2.ftl new file mode 100644 index 0000000000..44a77f4e73 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-resource2.ftl @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +### Fail: (RC03) There should be an empty line preceeding. + +fake-identifier-1 = Fake text diff --git a/tools/lint/test/files/fluent-lint/comment-resource3.ftl b/tools/lint/test/files/fluent-lint/comment-resource3.ftl new file mode 100644 index 0000000000..b261404380 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-resource3.ftl @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +### Fail: (RC02) There should be an empty line following. +fake-identifier-1 = Fake text diff --git a/tools/lint/test/files/fluent-lint/comment-resource4.ftl b/tools/lint/test/files/fluent-lint/comment-resource4.ftl new file mode 100644 index 0000000000..c24e8887f8 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-resource4.ftl @@ -0,0 +1,8 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +### Fail: (RC03) There should be only one space above. + +fake-identifier-1 = Fake text diff --git a/tools/lint/test/files/fluent-lint/comment-resource5.ftl b/tools/lint/test/files/fluent-lint/comment-resource5.ftl new file mode 100644 index 0000000000..60d8e8c264 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-resource5.ftl @@ -0,0 +1,8 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +### Fail: (RC02) There should be only one space below. + + +fake-identifier-1 = Fake text diff --git a/tools/lint/test/files/fluent-lint/comment-resource6.ftl b/tools/lint/test/files/fluent-lint/comment-resource6.ftl new file mode 100644 index 0000000000..a2ca9abfe7 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-resource6.ftl @@ -0,0 +1,4 @@ +### Pass: Check two conditions. +### 1. This is an edge case, but we shouldn't error if there is only a resource comment. +### 2. Make sure this linter does not error if there is no license header. The license is +### checked with `mach lint license`. diff --git a/tools/lint/test/files/fluent-lint/comment-variables1.ftl b/tools/lint/test/files/fluent-lint/comment-variables1.ftl new file mode 100644 index 0000000000..10de9de195 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-variables1.ftl @@ -0,0 +1,60 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +### Test group comments. +### $var doesn't count towards commented placeables + +message-without-comment = This string has a { $var } + +## Variables: +## $foo-group (String): group level comment + +# Variables: +# $foo1 (String): just text +message-with-comment = This string has a { $foo1 } + +message-with-group-comment = This string has a { $foo-group } + +select-without-comment1 = { + $select1 -> + [t] Foo + *[s] Bar +} + +select-without-comment2 = { + $select2 -> + [t] Foo { $select2 } + *[s] Bar +} + +## Variables: +## $select4 (Integer): a number + +# Variables: +# $select3 (Integer): a number +select-with-comment1 = { + $select3 -> + [t] Foo + *[s] Bar +} + +select-with-group-comment1 = { + $select4 -> + [t] Foo { $select4 } + *[s] Bar +} + +message-attribute-without-comment = + .label = This string as { $attr } + +# Variables: +# $attr2 (String): just text +message-attribute-with-comment = + .label = This string as { $attr2 } + +message-selection-function = + { PLATFORM() -> + [macos] foo + *[other] bar + } diff --git a/tools/lint/test/files/fluent-lint/comment-variables2.ftl b/tools/lint/test/files/fluent-lint/comment-variables2.ftl new file mode 100644 index 0000000000..2e9ae8e684 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/comment-variables2.ftl @@ -0,0 +1,27 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +## Terms are not checked for variables, no need for comments. + +-term-without-comment1 = { $term1 } +-term-without-comment2 = { + $select2 -> + [t] Foo { $term2 } + *[s] Bar +} + +# Testing that variable references from terms are not kept around when analyzing +# standard messages (see bug 1812568). +# +# Variables: +# $message1 (String) - Just text +message-with-comment = This string has a { $message1 } + +# This comment is not necessary, just making sure it doesn't get carried over to +# the following message which uses the same variable. +# +# Variables: +# $term-message (string) - Text +-term-with-variable = { $term-message } +message-without-comment = This string has a { $term-message } diff --git a/tools/lint/test/files/fluent-lint/excluded.ftl b/tools/lint/test/files/fluent-lint/excluded.ftl new file mode 100644 index 0000000000..79fe509ad6 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/excluded.ftl @@ -0,0 +1,6 @@ +# This file is used to test excluding paths from tests. +Blah-blah = Uppercase letters in identifiers are not permitted. +blah-blah = This is a legal identifier. +blah_blah = Underscores in identifiers are not permitted. + +bad-apostrophe-1 = The bee's knees diff --git a/tools/lint/test/files/fluent-lint/test-brands.ftl b/tools/lint/test/files/fluent-lint/test-brands.ftl new file mode 100644 index 0000000000..1aa6e9d0e8 --- /dev/null +++ b/tools/lint/test/files/fluent-lint/test-brands.ftl @@ -0,0 +1,5 @@ +# These are brands used in the fluent-lint test + +-brand-first = Firefox +-brand-second = Thunderbird +-brand-third = Mozilla diff --git a/tools/lint/test/files/fluent-lint/tools/lint/fluent-lint/exclusions.yml b/tools/lint/test/files/fluent-lint/tools/lint/fluent-lint/exclusions.yml new file mode 100644 index 0000000000..1aecf8cedd --- /dev/null +++ b/tools/lint/test/files/fluent-lint/tools/lint/fluent-lint/exclusions.yml @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +--- +ID01: + messages: + - Invalid_Id + files: + - excluded.ftl +ID02: + messages: [] + files: [] +CO01: + messages: + - bad-firefox-excluded + files: + - brand-names-excluded.ftl diff --git a/tools/lint/test/files/license/.eslintrc.js b/tools/lint/test/files/license/.eslintrc.js new file mode 100644 index 0000000000..0449fdfa33 --- /dev/null +++ b/tools/lint/test/files/license/.eslintrc.js @@ -0,0 +1,5 @@ + +// Dot file to verify that it works +// without license + +"use strict"; diff --git a/tools/lint/test/files/license/bad.c b/tools/lint/test/files/license/bad.c new file mode 100644 index 0000000000..76e8197013 --- /dev/null +++ b/tools/lint/test/files/license/bad.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tools/lint/test/files/license/bad.js b/tools/lint/test/files/license/bad.js new file mode 100644 index 0000000000..5de1a72f1f --- /dev/null +++ b/tools/lint/test/files/license/bad.js @@ -0,0 +1,6 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Pulic Unknown License as published by + * the Free Software Foundation, version 3. + * + */ diff --git a/tools/lint/test/files/license/good-other.h b/tools/lint/test/files/license/good-other.h new file mode 100644 index 0000000000..fb915e9b26 --- /dev/null +++ b/tools/lint/test/files/license/good-other.h @@ -0,0 +1,9 @@ +/* +Permission to use, copy, modify, distribute and sell this software +and its documentation for any purpose is hereby granted without fee, +provided that the above copyright notice appear in all copies and +that both that copyright notice and this permission notice appear +in supporting documentation. Samphan Raruenrom makes no +representations about the suitability of this software for any +purpose. It is provided "as is" without express or implied warranty. +*/ diff --git a/tools/lint/test/files/license/good.c b/tools/lint/test/files/license/good.c new file mode 100644 index 0000000000..d1a6827fb1 --- /dev/null +++ b/tools/lint/test/files/license/good.c @@ -0,0 +1,8 @@ + +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +int main() { return 0; } diff --git a/tools/lint/test/files/license/good.js b/tools/lint/test/files/license/good.js new file mode 100644 index 0000000000..d10ae3a8d5 --- /dev/null +++ b/tools/lint/test/files/license/good.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +# Nothing + diff --git a/tools/lint/test/files/lintpref/bad.js b/tools/lint/test/files/lintpref/bad.js new file mode 100644 index 0000000000..ab55ba5dad --- /dev/null +++ b/tools/lint/test/files/lintpref/bad.js @@ -0,0 +1,2 @@ +// Real test pref, matching value. +pref("dom.webidl.test1", true); diff --git a/tools/lint/test/files/lintpref/good.js b/tools/lint/test/files/lintpref/good.js new file mode 100644 index 0000000000..0bf81c8f58 --- /dev/null +++ b/tools/lint/test/files/lintpref/good.js @@ -0,0 +1,6 @@ +// Fake prefs. +pref("foo.bar", 1); +pref("foo.baz", "1.234"); + +// Real test pref, different value. +pref("dom.webidl.test1", false); diff --git a/tools/lint/test/files/rst/.dotfile.rst b/tools/lint/test/files/rst/.dotfile.rst new file mode 100644 index 0000000000..be24e1d161 --- /dev/null +++ b/tools/lint/test/files/rst/.dotfile.rst @@ -0,0 +1,11 @@ +============ +Coding style +========== + +foo bar +~~~~~ + + +This file has error but should not be there +as we don't analyze dot files + diff --git a/tools/lint/test/files/rst/bad.rst b/tools/lint/test/files/rst/bad.rst new file mode 100644 index 0000000000..c9b60f613e --- /dev/null +++ b/tools/lint/test/files/rst/bad.rst @@ -0,0 +1,20 @@ +============ +Coding style +============ + + +This document attempts to explain the basic styles and patterns used in +the Mozilla codebase. New code should try to conform to these standards, +so it is as easy to maintain as existing code. There are exceptions, but +it's still important to know the rules! + + +Whitespace +~~~~~~~~ + +Line length +~~~~~~~~~~~ + +Line length +~~~~~~~~~~~ + diff --git a/tools/lint/test/files/rst/bad2.rst b/tools/lint/test/files/rst/bad2.rst new file mode 100644 index 0000000000..81c35dde06 --- /dev/null +++ b/tools/lint/test/files/rst/bad2.rst @@ -0,0 +1,4 @@ +==== +Test +=== + diff --git a/tools/lint/test/files/rst/bad3.rst b/tools/lint/test/files/rst/bad3.rst new file mode 100644 index 0000000000..b7e66e5c92 --- /dev/null +++ b/tools/lint/test/files/rst/bad3.rst @@ -0,0 +1,6 @@ + +.. _When_Should_I_Use_a_Hashtable.3F: + +When Should I Use a Hashtable? +------------------------------ + diff --git a/tools/lint/test/files/rst/good.rst b/tools/lint/test/files/rst/good.rst new file mode 100644 index 0000000000..fd12da85d3 --- /dev/null +++ b/tools/lint/test/files/rst/good.rst @@ -0,0 +1,11 @@ +============ +Coding style +============ + + +This document attempts to explain the basic styles and patterns used in +the Mozilla codebase. New code should try to conform to these standards, +so it is as easy to maintain as existing code. There are exceptions, but +it's still important to know the rules! + + diff --git a/tools/lint/test/files/ruff/bad.py b/tools/lint/test/files/ruff/bad.py new file mode 100644 index 0000000000..0015d7e7f9 --- /dev/null +++ b/tools/lint/test/files/ruff/bad.py @@ -0,0 +1,4 @@ +import distutils + +if not "foo" in "foobar": + print("oh no!") diff --git a/tools/lint/test/files/ruff/ruff.toml b/tools/lint/test/files/ruff/ruff.toml new file mode 100644 index 0000000000..34f5ca74a4 --- /dev/null +++ b/tools/lint/test/files/ruff/ruff.toml @@ -0,0 +1 @@ +# Empty config to force ruff to ignore the global one. diff --git a/tools/lint/test/files/rustfmt/subdir/bad.rs b/tools/lint/test/files/rustfmt/subdir/bad.rs new file mode 100644 index 0000000000..fb1746fafd --- /dev/null +++ b/tools/lint/test/files/rustfmt/subdir/bad.rs @@ -0,0 +1,16 @@ +fn main() { + // Statements here are executed when the compiled binary is called + + // Print text to the console + println!("Hello World!"); + // Clippy detects this as a swap and considers this as an error + let mut a = + 1; + let mut b=1; + + a = + b; + b = a; + + +} diff --git a/tools/lint/test/files/rustfmt/subdir/bad2.rs b/tools/lint/test/files/rustfmt/subdir/bad2.rs new file mode 100644 index 0000000000..a4236a2de7 --- /dev/null +++ b/tools/lint/test/files/rustfmt/subdir/bad2.rs @@ -0,0 +1,17 @@ +fn main() { + // Statements here are executed when the compiled binary is called + + // Print text to the console + println!("Hello World!"); + let mut a; + let mut b=1; + let mut vec = Vec::new(); + vec.push(1); + vec.push(2); + + + for x in 5..10 - 5 { + a = x; + } + + } diff --git a/tools/lint/test/files/rustfmt/subdir/good.rs b/tools/lint/test/files/rustfmt/subdir/good.rs new file mode 100644 index 0000000000..9bcaee67b7 --- /dev/null +++ b/tools/lint/test/files/rustfmt/subdir/good.rs @@ -0,0 +1,6 @@ +fn main() { + // Statements here are executed when the compiled binary is called + + // Print text to the console + println!("Hello World!"); +} diff --git a/tools/lint/test/files/shellcheck/bad.sh b/tools/lint/test/files/shellcheck/bad.sh new file mode 100644 index 0000000000..b2eb195558 --- /dev/null +++ b/tools/lint/test/files/shellcheck/bad.sh @@ -0,0 +1,3 @@ +#!/bin/sh +hello="Hello world" +echo $1 diff --git a/tools/lint/test/files/shellcheck/good.sh b/tools/lint/test/files/shellcheck/good.sh new file mode 100644 index 0000000000..e61d501955 --- /dev/null +++ b/tools/lint/test/files/shellcheck/good.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo "Hello world" diff --git a/tools/lint/test/files/stylelint/nolint/foo.txt b/tools/lint/test/files/stylelint/nolint/foo.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/lint/test/files/stylelint/nolint/foo.txt diff --git a/tools/lint/test/files/stylelint/subdir/bad.css b/tools/lint/test/files/stylelint/subdir/bad.css new file mode 100644 index 0000000000..70004c1fb2 --- /dev/null +++ b/tools/lint/test/files/stylelint/subdir/bad.css @@ -0,0 +1,5 @@ +#foo { + /* Duplicate property: */ + font-size: 12px; + font-size: 12px; +} diff --git a/tools/lint/test/files/test-manifest-alpha/mochitest-in-order.ini b/tools/lint/test/files/test-manifest-alpha/mochitest-in-order.ini new file mode 100644 index 0000000000..9de566702a --- /dev/null +++ b/tools/lint/test/files/test-manifest-alpha/mochitest-in-order.ini @@ -0,0 +1,29 @@ +[DEFAULT] +support-files = + click-event-helper.js + head.js + test-button-overlay.html + ../../../../dom/media/test/gizmo.mp4 + ../../../../dom/media/test/owl.mp3 + +prefs = + media.videocontrols.picture-in-picture.display-text-tracks.enabled=false + +[browser_AAA_run_first_firstTimePiPToggleEvents.js] +[browser_aaa_run_first_firstTimePiPToggleEvents.js] +[browser_backgroundTab.js] +[browser_cannotTriggerFromContent.js] +[browser_closePipPause.js] +skip-if = os == "linux" && bits == 64 && os_version == "18.04" # Bug 1569205 +[browser_closePip_pageNavigationChanges.js] +[browser_closePlayer.js] +[browser_closeTab.js] +[browser_close_unpip_focus.js] +[browser_contextMenu.js] +[browser_cornerSnapping.js] +run-if = os == "mac" +[browser_dblclickFullscreen.js] +[browser_flipIconWithRTL.js] +skip-if = + os == "linux" && ccov # Bug 1678091 + tsan # Bug 1678091 diff --git a/tools/lint/test/files/test-manifest-alpha/mochitest-mostly-in-order.ini b/tools/lint/test/files/test-manifest-alpha/mochitest-mostly-in-order.ini new file mode 100644 index 0000000000..01f2551bd4 --- /dev/null +++ b/tools/lint/test/files/test-manifest-alpha/mochitest-mostly-in-order.ini @@ -0,0 +1,30 @@ +[DEFAULT] +support-files = + click-event-helper.js + head.js + test-button-overlay.html + ../../../../dom/media/test/gizmo.mp4 + ../../../../dom/media/test/owl.mp3 + +prefs = + media.videocontrols.picture-in-picture.display-text-tracks.enabled=false + +[browser_AAA_run_first_firstTimePiPToggleEvents.js] +[browser_aaa_run_first_firstTimePiPToggleEvents.js] +[browser_backgroundTab.js] +[browser_cannotTriggerFromContent.js] +[browser_closePipPause.js] +skip-if = os == "linux" && bits == 64 && os_version == "18.04" # Bug 1569205 +[browser_closePip_pageNavigationChanges.js] +[browser_closePlayer.js] +[browser_closeTab.js] +[browser_close_unpip_focus.js] +[browser_contextMenu.js] +[browser_cornerSnapping.js] +run-if = os == "mac" +[browser_dblclickFullscreen.js] +[browser_flipIconWithRTL.js] +skip-if = + os == "linux" && ccov # Bug 1678091 + tsan # Bug 1678091 +[browser_a_new_test.js] diff --git a/tools/lint/test/files/test-manifest-alpha/mochitest-very-out-of-order.ini b/tools/lint/test/files/test-manifest-alpha/mochitest-very-out-of-order.ini new file mode 100644 index 0000000000..45bfdd776b --- /dev/null +++ b/tools/lint/test/files/test-manifest-alpha/mochitest-very-out-of-order.ini @@ -0,0 +1,29 @@ +[DEFAULT] +support-files = + click-event-helper.js + head.js + test-button-overlay.html + ../../../../dom/media/test/gizmo.mp4 + ../../../../dom/media/test/owl.mp3 + +prefs = + media.videocontrols.picture-in-picture.display-text-tracks.enabled=false + +[browser_contextMenu.js] +[browser_aaa_run_first_firstTimePiPToggleEvents.js] +[browser_AAA_run_first_firstTimePiPToggleEvents.js] +[browser_backgroundTab.js] +[browser_cannotTriggerFromContent.js] +[browser_close_unpip_focus.js] +[browser_closePip_pageNavigationChanges.js] +[browser_closePipPause.js] +skip-if = os == "linux" && bits == 64 && os_version == "18.04" # Bug 1569205 +[browser_cornerSnapping.js] +run-if = os == "mac" +[browser_closePlayer.js] +[browser_closeTab.js] +[browser_dblclickFullscreen.js] +[browser_flipIconWithRTL.js] +skip-if = + os == "linux" && ccov # Bug 1678091 + tsan # Bug 1678091 diff --git a/tools/lint/test/files/test-manifest-alpha/other-ini-very-out-of-order.ini b/tools/lint/test/files/test-manifest-alpha/other-ini-very-out-of-order.ini new file mode 100644 index 0000000000..45bfdd776b --- /dev/null +++ b/tools/lint/test/files/test-manifest-alpha/other-ini-very-out-of-order.ini @@ -0,0 +1,29 @@ +[DEFAULT] +support-files = + click-event-helper.js + head.js + test-button-overlay.html + ../../../../dom/media/test/gizmo.mp4 + ../../../../dom/media/test/owl.mp3 + +prefs = + media.videocontrols.picture-in-picture.display-text-tracks.enabled=false + +[browser_contextMenu.js] +[browser_aaa_run_first_firstTimePiPToggleEvents.js] +[browser_AAA_run_first_firstTimePiPToggleEvents.js] +[browser_backgroundTab.js] +[browser_cannotTriggerFromContent.js] +[browser_close_unpip_focus.js] +[browser_closePip_pageNavigationChanges.js] +[browser_closePipPause.js] +skip-if = os == "linux" && bits == 64 && os_version == "18.04" # Bug 1569205 +[browser_cornerSnapping.js] +run-if = os == "mac" +[browser_closePlayer.js] +[browser_closeTab.js] +[browser_dblclickFullscreen.js] +[browser_flipIconWithRTL.js] +skip-if = + os == "linux" && ccov # Bug 1678091 + tsan # Bug 1678091 diff --git a/tools/lint/test/files/trojan-source/README b/tools/lint/test/files/trojan-source/README new file mode 100644 index 0000000000..343a9d0c3c --- /dev/null +++ b/tools/lint/test/files/trojan-source/README @@ -0,0 +1,5 @@ +These examples are taken from trojan source: +https://github.com/nickboucher/trojan-source + +The examples are published under the MIT license. + diff --git a/tools/lint/test/files/trojan-source/commenting-out.cpp b/tools/lint/test/files/trojan-source/commenting-out.cpp new file mode 100644 index 0000000000..d67df70ce1 --- /dev/null +++ b/tools/lint/test/files/trojan-source/commenting-out.cpp @@ -0,0 +1,9 @@ +#include <iostream> + +int main() { + bool isAdmin = false; + /* } if (isAdmin) begin admins only */ + std::cout << "You are an admin.\n"; + /* end admins only { */ + return 0; +}
\ No newline at end of file diff --git a/tools/lint/test/files/trojan-source/early-return.py b/tools/lint/test/files/trojan-source/early-return.py new file mode 100644 index 0000000000..2797d8ae9f --- /dev/null +++ b/tools/lint/test/files/trojan-source/early-return.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +bank = { 'alice': 100 } + +def subtract_funds(account: str, amount: int): + ''' Subtract funds from bank account then ''' ;return + bank[account] -= amount + return + +subtract_funds('alice', 50)
\ No newline at end of file diff --git a/tools/lint/test/files/trojan-source/invisible-function.rs b/tools/lint/test/files/trojan-source/invisible-function.rs new file mode 100644 index 0000000000..b32efb0372 --- /dev/null +++ b/tools/lint/test/files/trojan-source/invisible-function.rs @@ -0,0 +1,15 @@ +fn isAdmin() { + return false; +} + +fn isAdmin() { + return true; +} + +fn main() { + if isAdmin() { + printf("You are an admin\n"); + } else { + printf("You are NOT an admin.\n"); + } +}
\ No newline at end of file diff --git a/tools/lint/test/files/updatebot/.yamllint b/tools/lint/test/files/updatebot/.yamllint new file mode 100644 index 0000000000..4f11bbd6c5 --- /dev/null +++ b/tools/lint/test/files/updatebot/.yamllint @@ -0,0 +1,6 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +# Explicity default .yamllint to isolate tests from tree-wide yamlint config. +--- +extends: default diff --git a/tools/lint/test/files/updatebot/cargo-mismatch.yaml b/tools/lint/test/files/updatebot/cargo-mismatch.yaml new file mode 100644 index 0000000000..ac18d2b87c --- /dev/null +++ b/tools/lint/test/files/updatebot/cargo-mismatch.yaml @@ -0,0 +1,44 @@ +--- +# Version of this schema +schema: 1 + +bugzilla: + # Bugzilla product and component for this directory and subdirectories + product: Core + component: "Graphics: WebGPU" + +# Document the source of externally hosted code +origin: + + # Short name of the package/library + name: wgpu + + description: A cross-platform pure-Rust graphics API + + # Full URL for the package's homepage/etc + # Usually different from repository url + url: https://github.com/gfx-rs/wgpu + + # Human-readable identifier for this version/release + # Generally "version NNN", "tag SSS", "bookmark SSS" + release: commit 32af4f56 + + # Revision to pull in + # Must be a long or short commit SHA (long preferred) + revision: idontmatchanything + + license: ['MIT', 'Apache-2.0'] + +updatebot: + maintainer-phab: jimb + maintainer-bz: jimb@mozilla.com + tasks: + - type: vendoring + enabled: true + frequency: 1 week + +vendoring: + url: https://github.com/gfx-rs/wgpu + source-hosting: github + vendor-directory: gfx/wgpu_bindings/ + flavor: rust diff --git a/tools/lint/test/files/updatebot/good1.yaml b/tools/lint/test/files/updatebot/good1.yaml new file mode 100644 index 0000000000..f57d2c5b4c --- /dev/null +++ b/tools/lint/test/files/updatebot/good1.yaml @@ -0,0 +1,44 @@ +--- +schema: 1 + +bugzilla: + product: Core + component: Graphics + +origin: + name: angle + + description: ANGLE - Almost Native Graphics Layer Engine + + url: https://chromium.googlesource.com/angle/angle + + # Note that while the vendoring information here, including revision, + # release, and upstream repo locations refer to the third party upstream, + # Angle is vendored from a mozilla git repository that pulls from + # upstream and mainntains local patches there. + release: commit 018f85dea11fd5e41725750c6958695a6b8e8409 + revision: 018f85dea11fd5e41725750c6958695a6b8e8409 + + license: BSD-3-Clause + +updatebot: + maintainer-phab: jgilbert + maintainer-bz: jgilbert@mozilla.com + tasks: + - type: commit-alert + enabled: true + branch: chromium/4515 + needinfo: ["jgilbert@mozilla.com"] + +vendoring: + url: https://chromium.googlesource.com/angle/angle + tracking: tag + source-hosting: angle + vendor-directory: gfx/angle/checkout + skip-vendoring-steps: ["fetch", "update-moz-build"] + + update-actions: + - action: run-script + script: '{yaml_dir}/auto-update-angle.sh' + args: ['{revision}'] + cwd: '{cwd}' diff --git a/tools/lint/test/files/updatebot/good2.yaml b/tools/lint/test/files/updatebot/good2.yaml new file mode 100644 index 0000000000..0161d28b11 --- /dev/null +++ b/tools/lint/test/files/updatebot/good2.yaml @@ -0,0 +1,74 @@ +--- +# Version of this schema +schema: 1 + +bugzilla: + # Bugzilla product and component for this directory and subdirectories + product: Core + component: "Audio/Video: Playback" + +# Document the source of externally hosted code +origin: + + # Short name of the package/library + name: dav1d + + description: dav1d, a fast AV1 decoder + + # Full URL for the package's homepage/etc + # Usually different from repository url + url: https://code.videolan.org/videolan/dav1d + + # Human-readable identifier for this version/release + # Generally "version NNN", "tag SSS", "bookmark SSS" + release: ffb59680356fd210816cf9e46d9d023ade1f4d5a + + # Revision to pull in + # Must be a long or short commit SHA (long preferred) + revision: ffb59680356fd210816cf9e46d9d023ade1f4d5a + + # The package's license, where possible using the mnemonic from + # https://spdx.org/licenses/ + # Multiple licenses can be specified (as a YAML list) + # A "LICENSE" file must exist containing the full license text + license: BSD-2-Clause + + license-file: COPYING + +updatebot: + maintainer-phab: chunmin + maintainer-bz: cchang@mozilla.com + tasks: + - type: vendoring + enabled: true + frequency: release + +vendoring: + url: https://code.videolan.org/videolan/dav1d + source-hosting: gitlab + vendor-directory: third_party/dav1d + + exclude: + - build/.gitattributes + - build/.gitignore + - doc + - examples + - package + - tools + + generated: + - '{yaml_dir}/vcs_version.h' + - '{yaml_dir}/version.h' + + update-actions: + - action: copy-file + from: include/vcs_version.h.in + to: '{yaml_dir}/vcs_version.h' + - action: replace-in-file + pattern: '@VCS_TAG@' + with: '{revision}' + file: '{yaml_dir}/vcs_version.h' + - action: run-script + script: '{yaml_dir}/update-version.sh' + cwd: '{vendor_dir}' + args: ['{yaml_dir}/version.h'] diff --git a/tools/lint/test/files/updatebot/no-revision.yaml b/tools/lint/test/files/updatebot/no-revision.yaml new file mode 100644 index 0000000000..4d581508d8 --- /dev/null +++ b/tools/lint/test/files/updatebot/no-revision.yaml @@ -0,0 +1,43 @@ +--- +schema: 1 + +bugzilla: + product: Core + component: Graphics + +origin: + name: angle + + description: ANGLE - Almost Native Graphics Layer Engine + + url: https://chromium.googlesource.com/angle/angle + + # Note that while the vendoring information here, including revision, + # release, and upstream repo locations refer to the third party upstream, + # Angle is vendored from a mozilla git repository that pulls from + # upstream and mainntains local patches there. + release: commit 018f85dea11fd5e41725750c6958695a6b8e8409 + + license: BSD-3-Clause + +updatebot: + maintainer-phab: jgilbert + maintainer-bz: jgilbert@mozilla.com + tasks: + - type: commit-alert + enabled: true + branch: chromium/4515 + needinfo: ["jgilbert@mozilla.com"] + +vendoring: + url: https://chromium.googlesource.com/angle/angle + tracking: tag + source-hosting: angle + vendor-directory: gfx/angle/checkout + skip-vendoring-steps: ["fetch", "update-moz-build"] + + update-actions: + - action: run-script + script: '{yaml_dir}/auto-update-angle.sh' + args: ['{revision}'] + cwd: '{cwd}' diff --git a/tools/lint/test/files/yaml/.yamllint b/tools/lint/test/files/yaml/.yamllint new file mode 100644 index 0000000000..4f11bbd6c5 --- /dev/null +++ b/tools/lint/test/files/yaml/.yamllint @@ -0,0 +1,6 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +# Explicity default .yamllint to isolate tests from tree-wide yamlint config. +--- +extends: default diff --git a/tools/lint/test/files/yaml/bad.yml b/tools/lint/test/files/yaml/bad.yml new file mode 100644 index 0000000000..195ac7b030 --- /dev/null +++ b/tools/lint/test/files/yaml/bad.yml @@ -0,0 +1,8 @@ +--- +yamllint: + description: YAML linteraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax + include: + - .cron.yml + - browser/config/ + - wrong + application:bar diff --git a/tools/lint/test/files/yaml/good.yml b/tools/lint/test/files/yaml/good.yml new file mode 100644 index 0000000000..b30941b797 --- /dev/null +++ b/tools/lint/test/files/yaml/good.yml @@ -0,0 +1,6 @@ +--- +yamllint: + description: YAML linter + include: + - .cron.yml + - browser/config/ diff --git a/tools/lint/test/python.ini b/tools/lint/test/python.ini new file mode 100644 index 0000000000..acbd032423 --- /dev/null +++ b/tools/lint/test/python.ini @@ -0,0 +1,35 @@ +[DEFAULT] +subsuite = mozlint + +[test_android_format.py] +[test_black.py] +requirements = tools/lint/python/black_requirements.txt +[test_clang_format.py] +[test_codespell.py] +[test_eslint.py] +skip-if = os == "win" # busts the tree for subsequent tasks on the same worker (bug 1708591) +# Setup conflicts with stylelint setup so this should run sequentially. +sequential = true +[test_file_license.py] +[test_file_perm.py] +skip-if = os == "win" +[test_file_whitespace.py] +[test_fluent_lint.py] +[test_lintpref.py] +[test_manifest_alpha.py] +[test_perfdocs.py] +[test_perfdocs_generation.py] +[test_updatebot.py] +[test_perfdocs_helpers.py] +[test_rst.py] +requirements = tools/lint/rst/requirements.txt +[test_ruff.py] +requirements = tools/lint/python/ruff_requirements.txt +[test_rustfmt.py] +[test_shellcheck.py] +[test_stylelint.py] +skip-if = os == "win" # busts the tree for subsequent tasks on the same worker (bug 1708591) +# Setup conflicts with eslint setup so this should run sequentially. +sequential = true +[test_trojan_source.py] +[test_yaml.py] diff --git a/tools/lint/test/test_android_format.py b/tools/lint/test/test_android_format.py new file mode 100644 index 0000000000..70cd1ea02e --- /dev/null +++ b/tools/lint/test/test_android_format.py @@ -0,0 +1,38 @@ +import mozunit +from conftest import build + +LINTER = "android-format" + + +def test_basic(global_lint, config): + substs = { + "GRADLE_ANDROID_FORMAT_LINT_CHECK_TASKS": [ + "spotlessJavaCheck", + "spotlessKotlinCheck", + ], + "GRADLE_ANDROID_FORMAT_LINT_FIX_TASKS": [ + "spotlessJavaApply", + "spotlessKotlinApply", + ], + "GRADLE_ANDROID_FORMAT_LINT_FOLDERS": ["tools/lint/test/files/android-format"], + } + results = global_lint( + config=config, + topobjdir=build.topobjdir, + root=build.topsrcdir, + substs=substs, + extra_args=["-PandroidFormatLintTest"], + ) + print(results) + + # When first task (spotlessJavaCheck) hits error, we won't check next Kotlin error. + # So results length will be 1. + assert len(results) == 1 + assert results[0].level == "error" + + # Since android-format is global lint, fix=True overrides repository files directly. + # No way to add this test. + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_black.py b/tools/lint/test/test_black.py new file mode 100644 index 0000000000..9027670665 --- /dev/null +++ b/tools/lint/test/test_black.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import mozunit + +LINTER = "black" +fixed = 0 + + +def test_lint_fix(lint, create_temp_file): + + contents = """def is_unique( + s + ): + s = list(s + ) + s.sort() + + + for i in range(len(s) - 1): + if s[i] == s[i + 1]: + return 0 + else: + return 1 + + +if __name__ == "__main__": + print( + is_unique(input()) + ) """ + + path = create_temp_file(contents, "bad.py") + lint([path], fix=True) + assert fixed == 1 + + +def test_lint_black(lint, paths): + results = lint(paths()) + assert len(results) == 2 + + assert results[0].level == "error" + assert results[0].relpath == "bad.py" + + assert "EOF" in results[1].message + assert results[1].level == "error" + assert results[1].relpath == "invalid.py" + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_clang_format.py b/tools/lint/test/test_clang_format.py new file mode 100644 index 0000000000..809280601d --- /dev/null +++ b/tools/lint/test/test_clang_format.py @@ -0,0 +1,139 @@ +import mozunit +from conftest import build + +LINTER = "clang-format" +fixed = 0 + + +def test_good(lint, config, paths): + results = lint(paths("good/"), root=build.topsrcdir, use_filters=False) + print(results) + assert len(results) == 0 + + results = lint(paths("good/"), root=build.topsrcdir, use_filters=False, fix=True) + assert fixed == len(results) + + +def test_basic(lint, config, paths): + results = lint(paths("bad/bad.cpp"), root=build.topsrcdir, use_filters=False) + print(results) + assert len(results) == 1 + + assert "Reformat C/C++" in results[0].message + assert results[0].level == "warning" + assert results[0].lineno == 1 + assert results[0].column == 0 + assert "bad.cpp" in results[0].path + assert ( + results[0].diff + == """\ +-int main ( ) { +- +-return 0; +- +- +-} ++int main() { return 0; } +""" # noqa + ) + + +def test_dir(lint, config, paths): + results = lint(paths("bad/"), root=build.topsrcdir, use_filters=False) + print(results) + assert len(results) == 5 + + assert "Reformat C/C++" in results[0].message + assert results[0].level == "warning" + assert results[0].lineno == 1 + assert results[0].column == 0 + assert "bad.cpp" in results[0].path + assert ( + results[0].diff + == """\ +-int main ( ) { +- +-return 0; +- +- +-} ++int main() { return 0; } +""" # noqa + ) + + assert "Reformat C/C++" in results[1].message + assert results[1].level == "warning" + assert results[1].lineno == 1 + assert results[1].column == 0 + assert "bad2.c" in results[1].path + assert ( + results[1].diff + == """\ +-#include "bad2.h" +- +- +-int bad2() { ++#include "bad2.h" ++ ++int bad2() { +""" + ) + + assert "Reformat C/C++" in results[2].message + assert results[2].level == "warning" + assert results[2].lineno == 5 + assert results[2].column == 0 + assert "bad2.c" in results[2].path + assert ( + results[2].diff + == """\ +- int a =2; ++ int a = 2; +""" + ) + + assert "Reformat C/C++" in results[3].message + assert results[3].level == "warning" + assert results[3].lineno == 6 + assert results[3].column == 0 + assert "bad2.c" in results[3].path + assert ( + results[3].diff + == """\ +- return a; +- +-} ++ return a; ++} +""" + ) + + assert "Reformat C/C++" in results[4].message + assert results[4].level == "warning" + assert results[4].lineno == 1 + assert results[4].column == 0 + assert "bad2.h" in results[4].path + assert ( + results[4].diff + == """\ +-int bad2(void ); ++int bad2(void); +""" + ) + + +def test_fixed(lint, create_temp_file): + + contents = """int main ( ) { \n +return 0; \n + +}""" + + path = create_temp_file(contents, "ignore.cpp") + lint([path], use_filters=False, fix=True) + + assert fixed == 1 + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_codespell.py b/tools/lint/test/test_codespell.py new file mode 100644 index 0000000000..8baae66b41 --- /dev/null +++ b/tools/lint/test/test_codespell.py @@ -0,0 +1,37 @@ +import mozunit + +LINTER = "codespell" +fixed = 0 + + +def test_lint_codespell_fix(lint, create_temp_file): + contents = """This is a file with some typos and informations. +But also testing false positive like optin (because this isn't always option) +or stuff related to our coding style like: +aparent (aParent). +but detects mistakes like mozila +""".lstrip() + + path = create_temp_file(contents, "ignore.rst") + lint([path], fix=True) + + assert fixed == 2 + + +def test_lint_codespell(lint, paths): + results = lint(paths()) + assert len(results) == 2 + + assert results[0].message == "informations ==> information" + assert results[0].level == "error" + assert results[0].lineno == 1 + assert results[0].relpath == "ignore.rst" + + assert results[1].message == "mozila ==> mozilla" + assert results[1].level == "error" + assert results[1].lineno == 5 + assert results[1].relpath == "ignore.rst" + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_eslint.py b/tools/lint/test/test_eslint.py new file mode 100644 index 0000000000..b4fda2fb35 --- /dev/null +++ b/tools/lint/test/test_eslint.py @@ -0,0 +1,99 @@ +import mozunit +import pytest +from conftest import build + +LINTER = "eslint" +fixed = 0 + + +@pytest.fixture +def eslint(lint): + def inner(*args, **kwargs): + # --no-ignore is for ESLint to avoid the .eslintignore file. + # --ignore-path is because Prettier doesn't have the --no-ignore option + # and therefore needs to be given an empty file for the tests to work. + kwargs["extra_args"] = [ + "--no-ignore", + "--ignore-path=tools/lint/test/files/eslint/testprettierignore", + ] + return lint(*args, **kwargs) + + return inner + + +def test_lint_with_global_exclude(lint, config, paths): + config["exclude"] = ["subdir", "import"] + # This uses lint directly as we need to not ignore the excludes. + results = lint(paths(), config=config, root=build.topsrcdir) + assert len(results) == 0 + + +def test_no_files_to_lint(eslint, config, paths): + # A directory with no files to lint. + results = eslint(paths("nolint"), root=build.topsrcdir) + assert results == [] + + # Errors still show up even when a directory with no files is passed in. + results = eslint(paths("nolint", "subdir/bad.js"), root=build.topsrcdir) + assert len(results) == 1 + + +def test_bad_import(eslint, config, paths): + results = eslint(paths("import"), config=config, root=build.topsrcdir) + assert results == 1 + + +def test_eslint_rule(eslint, config, create_temp_file): + contents = """var re = /foo bar/; +var re = new RegExp("foo bar"); +""" + path = create_temp_file(contents, "bad.js") + results = eslint( + [path], config=config, root=build.topsrcdir, rules=["no-regex-spaces: error"] + ) + + assert len(results) == 2 + + +def test_eslint_fix(eslint, config, create_temp_file): + contents = """/*eslint no-regex-spaces: "error"*/ + +var re = /foo bar/; +var re = new RegExp("foo bar"); + +var re = /foo bar/; +var re = new RegExp("foo bar"); + +var re = /foo bar/; +var re = new RegExp("foo bar"); +""" + path = create_temp_file(contents, "bad.js") + eslint([path], config=config, root=build.topsrcdir, fix=True) + + # ESLint returns counts of files fixed, not errors fixed. + assert fixed == 1 + + +def test_prettier_rule(eslint, config, create_temp_file): + contents = """var re = /foobar/; + var re = "foo"; +""" + path = create_temp_file(contents, "bad.js") + results = eslint([path], config=config, root=build.topsrcdir) + + assert len(results) == 1 + + +def test_prettier_fix(eslint, config, create_temp_file): + contents = """var re = /foobar/; + var re = "foo"; +""" + path = create_temp_file(contents, "bad.js") + eslint([path], config=config, root=build.topsrcdir, fix=True) + + # Prettier returns counts of files fixed, not errors fixed. + assert fixed == 1 + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_file_license.py b/tools/lint/test/test_file_license.py new file mode 100644 index 0000000000..b00b023bae --- /dev/null +++ b/tools/lint/test/test_file_license.py @@ -0,0 +1,23 @@ +import mozunit + +LINTER = "license" + + +def test_lint_license(lint, paths): + results = lint(paths()) + print(results) + assert len(results) == 3 + + assert ".eslintrc.js" in results[0].relpath + + assert "No matching license strings" in results[1].message + assert results[1].level == "error" + assert "bad.c" in results[1].relpath + + assert "No matching license strings" in results[2].message + assert results[2].level == "error" + assert "bad.js" in results[2].relpath + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_file_perm.py b/tools/lint/test/test_file_perm.py new file mode 100644 index 0000000000..08d6a20eef --- /dev/null +++ b/tools/lint/test/test_file_perm.py @@ -0,0 +1,35 @@ +import mozunit +import pytest + +LINTER = "file-perm" + + +@pytest.mark.lint_config(name="file-perm") +def test_lint_file_perm(lint, paths): + results = lint(paths("no-shebang"), collapse_results=True) + + assert results.keys() == { + "no-shebang/bad.c", + "no-shebang/bad-shebang.c", + "no-shebang/bad.png", + } + + for path, issues in results.items(): + for issue in issues: + assert "permissions on a source" in issue.message + assert issue.level == "error" + + +@pytest.mark.lint_config(name="maybe-shebang-file-perm") +def test_lint_shebang_file_perm(config, lint, paths): + results = lint(paths("maybe-shebang")) + + assert len(results) == 1 + + assert "permissions on a source" in results[0].message + assert results[0].level == "error" + assert results[0].relpath == "maybe-shebang/bad.js" + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_file_whitespace.py b/tools/lint/test/test_file_whitespace.py new file mode 100644 index 0000000000..51b6fc4795 --- /dev/null +++ b/tools/lint/test/test_file_whitespace.py @@ -0,0 +1,51 @@ +import mozunit + +LINTER = "file-whitespace" +fixed = 0 + + +def test_lint_file_whitespace(lint, paths): + results = lint(paths()) + print(results) + assert len(results) == 5 + + assert "File does not end with newline character" in results[1].message + assert results[1].level == "error" + assert "bad-newline.c" in results[1].relpath + + assert "Empty Lines at end of file" in results[0].message + assert results[0].level == "error" + assert "bad-newline.c" in results[0].relpath + + assert "Windows line return" in results[2].message + assert results[2].level == "error" + assert "bad-windows.c" in results[2].relpath + + assert "Trailing whitespace" in results[3].message + assert results[3].level == "error" + assert "bad.c" in results[3].relpath + assert results[3].lineno == 1 + + assert "Trailing whitespace" in results[4].message + assert results[4].level == "error" + assert "bad.c" in results[4].relpath + assert results[4].lineno == 2 + + +def test_lint_file_whitespace_fix(lint, paths, create_temp_file): + + contents = """int main() { \n + return 0; \n +} + + +""" + + path = create_temp_file(contents, "bad.cpp") + lint([path], fix=True) + # Gives a different answer on Windows. Probably because of Windows CR + assert fixed == 3 or fixed == 2 + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_fluent_lint.py b/tools/lint/test/test_fluent_lint.py new file mode 100644 index 0000000000..d7867a8269 --- /dev/null +++ b/tools/lint/test/test_fluent_lint.py @@ -0,0 +1,156 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +import mozunit + +LINTER = "fluent-lint" + + +def test_lint_exclusions(lint, paths): + results = lint(paths("excluded.ftl")) + assert len(results) == 1 + assert results[0].rule == "TE01" + assert results[0].lineno == 6 + assert results[0].column == 20 + + +def test_lint_single_file(lint, paths): + results = lint(paths("bad.ftl")) + assert len(results) == 13 + assert results[0].rule == "ID01" + assert results[0].lineno == 1 + assert results[0].column == 1 + assert results[1].rule == "ID01" + assert results[1].lineno == 3 + assert results[1].column == 1 + assert results[2].rule == "TE01" + assert results[2].lineno == 5 + assert results[2].column == 20 + assert results[3].rule == "TE01" + assert results[3].lineno == 6 + assert results[3].column == 24 + assert results[4].rule == "TE02" + assert results[4].lineno == 7 + assert results[4].column == 20 + assert results[5].rule == "TE03" + assert results[5].lineno == 8 + assert results[5].column == 20 + assert results[6].rule == "TE04" + assert results[6].lineno == 11 + assert results[6].column == 20 + assert results[7].rule == "TE05" + assert results[7].lineno == 13 + assert results[7].column == 16 + assert results[8].rule == "TE03" + assert results[8].lineno == 17 + assert results[8].column == 20 + assert results[9].rule == "TE03" + assert results[9].lineno == 25 + assert results[9].column == 18 + assert results[10].rule == "ID02" + assert results[10].lineno == 32 + assert results[10].column == 1 + assert results[11].rule == "VC01" + assert "$tabCount" in results[11].message + assert results[12].rule == "VC01" + assert "$url" in results[12].message + + +def test_comment_group(lint, paths): + results = lint(paths("comment-group1.ftl")) + assert len(results) == 6 + assert results[0].rule == "GC03" + assert results[0].lineno == 12 + assert results[0].column == 1 + assert results[1].rule == "GC02" + assert results[1].lineno == 16 + assert results[1].column == 1 + assert results[2].rule == "GC04" + assert results[2].lineno == 21 + assert results[2].column == 1 + assert results[3].rule == "GC03" + assert results[3].lineno == 26 + assert results[3].column == 1 + assert results[4].rule == "GC02" + assert results[4].lineno == 30 + assert results[4].column == 1 + assert results[5].rule == "GC01" + assert results[5].lineno == 35 + assert results[5].column == 1 + + results = lint(paths("comment-group2.ftl")) + assert (len(results)) == 0 + + +def test_comment_resource(lint, paths): + results = lint(paths("comment-resource1.ftl")) + assert len(results) == 1 + assert results[0].rule == "RC01" + assert results[0].lineno == 9 + assert results[0].column == 1 + + results = lint(paths("comment-resource2.ftl")) + assert len(results) == 1 + assert results[0].rule == "RC03" + assert results[0].lineno == 4 + assert results[0].column == 1 + + results = lint(paths("comment-resource3.ftl")) + assert len(results) == 1 + assert results[0].rule == "RC02" + assert results[0].lineno == 5 + assert results[0].column == 1 + + results = lint(paths("comment-resource4.ftl")) + assert len(results) == 1 + assert results[0].rule == "RC03" + assert results[0].lineno == 6 + assert results[0].column == 1 + + results = lint(paths("comment-resource5.ftl")) + assert len(results) == 1 + assert results[0].rule == "RC02" + assert results[0].lineno == 5 + assert results[0].column == 1 + + results = lint(paths("comment-resource6.ftl")) + assert len(results) == 0 + + +def test_brand_names(lint, paths): + results = lint(paths("brand-names.ftl"), {"brand-files": ["test-brands.ftl"]}) + assert len(results) == 11 + assert results[0].rule == "CO01" + assert results[0].lineno == 1 + assert results[0].column == 16 + assert "Firefox" in results[0].message + assert "Mozilla" not in results[0].message + assert "Thunderbird" not in results[0].message + assert results[1].rule == "CO01" + assert results[1].lineno == 4 + assert results[1].column == 16 + + results = lint(paths("brand-names-excluded.ftl")) + assert len(results) == 0 + + +def test_comment_variables(lint, paths): + results = lint(paths("comment-variables1.ftl")) + assert len(results) == 4 + assert results[0].rule == "VC01" + assert "$var" in results[0].message + assert results[1].rule == "VC01" + assert "$select1" in results[1].message + assert results[2].rule == "VC01" + assert "$select2" in results[2].message + assert results[3].rule == "VC01" + assert "$attr" in results[3].message + + results = lint(paths("comment-variables2.ftl")) + assert len(results) == 1 + assert results[0].rule == "VC01" + assert "$term-message" in results[0].message + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_lintpref.py b/tools/lint/test/test_lintpref.py new file mode 100644 index 0000000000..3e75b1675e --- /dev/null +++ b/tools/lint/test/test_lintpref.py @@ -0,0 +1,16 @@ +import mozunit + +LINTER = "lintpref" + + +def test_lintpref(lint, paths): + results = lint(paths()) + assert len(results) == 1 + assert results[0].level == "error" + assert 'pref("dom.webidl.test1", true);' in results[0].message + assert "bad.js" in results[0].relpath + assert results[0].lineno == 2 + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_manifest_alpha.py b/tools/lint/test/test_manifest_alpha.py new file mode 100644 index 0000000000..2e8e1a6c77 --- /dev/null +++ b/tools/lint/test/test_manifest_alpha.py @@ -0,0 +1,33 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +import mozunit + +LINTER = "test-manifest-alpha" + + +def test_very_out_of_order(lint, paths): + results = lint(paths("mochitest-very-out-of-order.ini")) + assert len(results) == 1 + assert results[0].diff + + +def test_in_order(lint, paths): + results = lint(paths("mochitest-in-order.ini")) + assert len(results) == 0 + + +def test_mostly_in_order(lint, paths): + results = lint(paths("mochitest-mostly-in-order.ini")) + assert len(results) == 1 + assert results[0].diff + + +def test_other_ini_very_out_of_order(lint, paths): + """Test that an .ini file outside of the allowlist is ignored.""" + results = lint(paths("other-ini-very-out-of-order.ini")) + assert len(results) == 0 + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_perfdocs.py b/tools/lint/test/test_perfdocs.py new file mode 100644 index 0000000000..3e52e86271 --- /dev/null +++ b/tools/lint/test/test_perfdocs.py @@ -0,0 +1,858 @@ +import contextlib +import os +import pathlib +import shutil +import tempfile +from unittest import mock + +import mozunit +import pytest + +LINTER = "perfdocs" + + +class PerfDocsLoggerMock: + LOGGER = None + PATHS = [] + FAILED = True + + +""" +This is a sample mozperftest test that we use for testing +the verification process. +""" +SAMPLE_TEST = """ +"use strict"; + +async function setUp(context) { + context.log.info("setUp example!"); +} + +async function test(context, commands) { + context.log.info("Test with setUp/tearDown example!"); + await commands.measure.start("https://www.sitespeed.io/"); + await commands.measure.start("https://www.mozilla.org/en-US/"); +} + +async function tearDown(context) { + context.log.info("tearDown example!"); +} + +module.noexport = {}; + +module.exports = { + setUp, + tearDown, + test, + owner: "Performance Testing Team", + name: "Example", + description: "The description of the example test.", + longDescription: ` + This is a longer description of the test perhaps including information + about how it should be run locally or links to relevant information. + ` +}; +""" + + +SAMPLE_CONFIG = """ +name: mozperftest +manifest: None +static-only: False +suites: + suite: + description: "Performance tests from the 'suite' folder." + tests: + Example: "" +""" + + +DYNAMIC_SAMPLE_CONFIG = """ +name: {} +manifest: None +static-only: False +suites: + suite: + description: "Performance tests from the 'suite' folder." + tests: + Example: "Performance test Example from suite." + another_suite: + description: "Performance tests from the 'another_suite' folder." + tests: + Example: "Performance test Example from another_suite." +""" + + +SAMPLE_METRICS_CONFIG = """ +name: raptor +manifest: "None"{} +static-only: False +suites: + suite: + description: "Performance tests from the 'suite' folder."{} + tests: + Example: "Performance test Example from another_suite." + another_suite: + description: "Performance tests from the 'another_suite' folder." + tests: + Example: "Performance test Example from another_suite." +""" + + +SAMPLE_INI = """ +[Example] +test_url = Example_url +alert_on = fcp +""" + +SAMPLE_METRICS_INI = """ +[Example] +test_url = Example_url +alert_on = fcp,SpeedIndex +""" + + +@contextlib.contextmanager +def temp_file(name="temp", tempdir=None, content=None): + if tempdir is None: + tempdir = tempfile.mkdtemp() + path = pathlib.Path(tempdir, name) + if content is not None: + with path.open("w", newline="\n") as f: + f.write(content) + try: + yield path + finally: + try: + shutil.rmtree(str(tempdir)) + except FileNotFoundError: + pass + + +@contextlib.contextmanager +def temp_dir(): + tempdir = pathlib.Path(tempfile.mkdtemp()) + try: + yield tempdir + finally: + try: + shutil.rmtree(str(tempdir)) + except FileNotFoundError: + pass + + +def setup_sample_logger(logger, structured_logger, top_dir): + from perfdocs.logger import PerfDocLogger + + PerfDocLogger.LOGGER = structured_logger + PerfDocLogger.PATHS = ["perfdocs"] + PerfDocLogger.TOP_DIR = top_dir + + import perfdocs.gatherer as gt + import perfdocs.generator as gn + import perfdocs.verifier as vf + + gt.logger = logger + vf.logger = logger + gn.logger = logger + + +@mock.patch("taskgraph.util.taskcluster.get_artifact") +@mock.patch("tryselect.tasks.generate_tasks") +@mock.patch("perfdocs.generator.Generator") +@mock.patch("perfdocs.verifier.Verifier") +@mock.patch("perfdocs.logger.PerfDocLogger", new=PerfDocsLoggerMock) +def test_perfdocs_start_and_fail( + verifier, + generator, + get_artifact_mock, + gen_tasks_mock, + structured_logger, + config, + paths, +): + from perfdocs.perfdocs import run_perfdocs + + with temp_file("bad", content="foo") as temp: + run_perfdocs( + config, logger=structured_logger, paths=[str(temp)], generate=False + ) + assert PerfDocsLoggerMock.LOGGER == structured_logger + assert PerfDocsLoggerMock.PATHS == [temp] + assert PerfDocsLoggerMock.FAILED + + assert verifier.call_count == 1 + assert mock.call().validate_tree() in verifier.mock_calls + assert generator.call_count == 0 + + +@mock.patch("taskgraph.util.taskcluster.get_artifact") +@mock.patch("tryselect.tasks.generate_tasks") +@mock.patch("perfdocs.generator.Generator") +@mock.patch("perfdocs.verifier.Verifier") +@mock.patch("perfdocs.logger.PerfDocLogger", new=PerfDocsLoggerMock) +def test_perfdocs_start_and_pass(verifier, generator, structured_logger, config, paths): + from perfdocs.perfdocs import run_perfdocs + + PerfDocsLoggerMock.FAILED = False + with temp_file("bad", content="foo") as temp: + run_perfdocs( + config, logger=structured_logger, paths=[str(temp)], generate=False + ) + assert PerfDocsLoggerMock.LOGGER == structured_logger + assert PerfDocsLoggerMock.PATHS == [temp] + assert not PerfDocsLoggerMock.FAILED + + assert verifier.call_count == 1 + assert mock.call().validate_tree() in verifier.mock_calls + assert generator.call_count == 1 + assert mock.call().generate_perfdocs() in generator.mock_calls + + +@mock.patch("perfdocs.logger.PerfDocLogger", new=PerfDocsLoggerMock) +def test_perfdocs_bad_paths(structured_logger, config, paths): + from perfdocs.perfdocs import run_perfdocs + + with pytest.raises(Exception): + run_perfdocs(config, logger=structured_logger, paths=["bad"], generate=False) + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_gatherer_fetch_perfdocs_tree( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.gatherer import Gatherer + + gatherer = Gatherer(top_dir) + assert not gatherer._perfdocs_tree + + gatherer.fetch_perfdocs_tree() + + expected = "Found 1 perfdocs directories" + args, _ = logger.log.call_args + + assert expected in args[0] + assert logger.log.call_count == 1 + assert gatherer._perfdocs_tree + + expected = ["path", "yml", "rst", "static"] + for i, key in enumerate(gatherer._perfdocs_tree[0].keys()): + assert key == expected[i] + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_gatherer_get_test_list(logger, structured_logger, perfdocs_sample): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.gatherer import Gatherer + + gatherer = Gatherer(top_dir) + gatherer.fetch_perfdocs_tree() + framework = gatherer.get_test_list(gatherer._perfdocs_tree[0]) + + expected = ["name", "test_list", "yml_content", "yml_path"] + for i, key in enumerate(sorted(framework.keys())): + assert key == expected[i] + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verification(logger, structured_logger, perfdocs_sample): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier.validate_tree() + + # Make sure that we had no warnings + assert logger.warning.call_count == 0 + assert logger.log.call_count == 1 + assert len(logger.mock_calls) == 1 + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_validate_yaml_pass( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + yaml_path = perfdocs_sample["config"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.verifier import Verifier + + valid = Verifier(top_dir).validate_yaml(pathlib.Path(yaml_path)) + + assert valid + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_invalid_yaml(logger, structured_logger, perfdocs_sample): + top_dir = perfdocs_sample["top_dir"] + yaml_path = perfdocs_sample["config"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.verifier import Verifier + + verifier = Verifier("top_dir") + with open(yaml_path, "r", newline="\n") as f: + lines = f.readlines() + print(lines) + with open(yaml_path, "w", newline="\n") as f: + f.write("\n".join(lines[2:])) + valid = verifier.validate_yaml(yaml_path) + + expected = ("YAML ValidationError: 'name' is a required property\n", yaml_path) + args, _ = logger.warning.call_args + + assert logger.warning.call_count == 1 + assert expected[0] in args[0] + assert not valid + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_validate_rst_pass( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + rst_path = perfdocs_sample["index"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.verifier import Verifier + + valid = Verifier(top_dir).validate_rst_content(pathlib.Path(rst_path)) + + assert valid + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_invalid_rst(logger, structured_logger, perfdocs_sample): + top_dir = perfdocs_sample["top_dir"] + rst_path = perfdocs_sample["index"] + setup_sample_logger(logger, structured_logger, top_dir) + + # Replace the target string to invalid Keyword for test + with open(rst_path, "r") as file: + filedata = file.read() + + filedata = filedata.replace("documentation", "Invalid Keyword") + + with open(rst_path, "w", newline="\n") as file: + file.write(filedata) + + from perfdocs.verifier import Verifier + + verifier = Verifier("top_dir") + valid = verifier.validate_rst_content(rst_path) + + expected = ( + "Cannot find a '{documentation}' entry in the given index file", + rst_path, + ) + args, _ = logger.warning.call_args + + assert logger.warning.call_count == 1 + assert args == expected + assert not valid + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_validate_descriptions_pass( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier._check_framework_descriptions(verifier._gatherer.perfdocs_tree[0]) + + assert logger.warning.call_count == 0 + assert logger.log.call_count == 1 + assert len(logger.mock_calls) == 1 + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_not_existing_suite_in_test_list( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + manifest_path = perfdocs_sample["manifest"]["path"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + os.remove(manifest_path) + verifier._check_framework_descriptions(verifier._gatherer.perfdocs_tree[0]) + + expected = ( + "Could not find an existing suite for suite - bad suite name?", + perfdocs_sample["config"], + ) + args, _ = logger.warning.call_args + + assert logger.warning.call_count == 1 + assert args == expected + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_not_existing_tests_in_suites( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + with open(perfdocs_sample["config"], "r") as file: + filedata = file.read() + filedata = filedata.replace("Example", "DifferentName") + with open(perfdocs_sample["config"], "w", newline="\n") as file: + file.write(filedata) + + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier._check_framework_descriptions(verifier._gatherer.perfdocs_tree[0]) + + expected = [ + "Could not find an existing test for DifferentName - bad test name?", + "Could not find a test description for Example", + ] + + assert logger.warning.call_count == 2 + for i, call in enumerate(logger.warning.call_args_list): + args, _ = call + assert args[0] == expected[i] + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_missing_contents_in_suite( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + with open(perfdocs_sample["config"], "r") as file: + filedata = file.read() + filedata = filedata.replace("suite:", "InvalidSuite:") + with open(perfdocs_sample["config"], "w", newline="\n") as file: + file.write(filedata) + + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier._check_framework_descriptions(verifier._gatherer.perfdocs_tree[0]) + + expected = ( + "Could not find an existing suite for InvalidSuite - bad suite name?", + "Missing suite description for suite", + ) + + assert logger.warning.call_count == 2 + for i, call in enumerate(logger.warning.call_args_list): + args, _ = call + assert args[0] == expected[i] + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_invalid_dir(logger, structured_logger, perfdocs_sample): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.verifier import Verifier + + verifier = Verifier("invalid_path") + with pytest.raises(Exception) as exceinfo: + verifier.validate_tree() + + assert str(exceinfo.value) == "No valid perfdocs directories found" + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_file_invalidation( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.verifier import Verifier + + with mock.patch("perfdocs.verifier.Verifier.validate_yaml", return_value=False): + verifier = Verifier(top_dir) + with pytest.raises(Exception): + verifier.validate_tree() + + # Check if "File validation error" log is called + # and Called with a log inside perfdocs_tree(). + assert logger.log.call_count == 2 + assert len(logger.mock_calls) == 2 + + +@pytest.mark.parametrize( + "manifest, metric_definitions, expected", + [ + [ + SAMPLE_INI, + """ +metrics: + "FirstPaint": + aliases: + - fcp + description: "Example" """, + 1, + ], + [ + SAMPLE_METRICS_INI, + """ +metrics: + FirstPaint: + aliases: + - fcp + description: Example + SpeedIndex: + aliases: + - speedindex + - si + description: Example + """, + 2, + ], + ], +) +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_nonexistent_documented_metrics( + logger, structured_logger, perfdocs_sample, manifest, metric_definitions, expected +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + with open(perfdocs_sample["config"], "w", newline="\n") as f: + f.write(SAMPLE_METRICS_CONFIG.format(metric_definitions, "")) + with open(perfdocs_sample["manifest"]["path"], "w", newline="\n") as f: + f.write(manifest) + + sample_gatherer_result = { + "suite": {"Example": {}}, + "another_suite": {"Example": {}}, + } + + from perfdocs.verifier import Verifier + + with mock.patch("perfdocs.framework_gatherers.RaptorGatherer.get_test_list") as m: + m.return_value = sample_gatherer_result + verifier = Verifier(top_dir) + verifier.validate_tree() + + assert len(logger.warning.call_args_list) == expected + for (args, _) in logger.warning.call_args_list: + assert "Cannot find documented metric" in args[0] + assert "being used" in args[0] + + +@pytest.mark.parametrize( + "manifest, metric_definitions", + [ + [ + SAMPLE_INI, + """ +metrics: + "FirstPaint": + aliases: + - fcp + description: "Example" """, + ], + [ + SAMPLE_METRICS_INI, + """ +metrics: + SpeedIndex: + aliases: + - speedindex + - si + description: Example + """, + ], + ], +) +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_undocumented_metrics( + logger, structured_logger, perfdocs_sample, manifest, metric_definitions +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + with open(perfdocs_sample["config"], "w", newline="\n") as f: + f.write(SAMPLE_METRICS_CONFIG.format(metric_definitions, "")) + with open(perfdocs_sample["manifest"]["path"], "w", newline="\n") as f: + f.write(manifest) + + sample_gatherer_result = { + "suite": {"Example": {"metrics": ["fcp", "SpeedIndex"]}}, + "another_suite": {"Example": {}}, + } + + from perfdocs.verifier import Verifier + + with mock.patch("perfdocs.framework_gatherers.RaptorGatherer.get_test_list") as m: + m.return_value = sample_gatherer_result + verifier = Verifier(top_dir) + verifier.validate_tree() + + assert len(logger.warning.call_args_list) == 1 + for (args, _) in logger.warning.call_args_list: + assert "Missing description for the metric" in args[0] + + +@pytest.mark.parametrize( + "manifest, metric_definitions, expected", + [ + [ + SAMPLE_INI, + """ +metrics: + "FirstPaint": + aliases: + - fcp + - SpeedIndex + description: "Example" """, + 3, + ], + [ + SAMPLE_METRICS_INI, + """ +metrics: + FirstPaint: + aliases: + - fcp + description: Example + SpeedIndex: + aliases: + - speedindex + - si + description: Example + """, + 5, + ], + ], +) +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_duplicate_metrics( + logger, structured_logger, perfdocs_sample, manifest, metric_definitions, expected +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + with open(perfdocs_sample["config"], "w", newline="\n") as f: + indented_defs = "\n".join( + [(" " * 8) + metric_line for metric_line in metric_definitions.split("\n")] + ) + f.write(SAMPLE_METRICS_CONFIG.format(metric_definitions, indented_defs)) + with open(perfdocs_sample["manifest"]["path"], "w", newline="\n") as f: + f.write(manifest) + + sample_gatherer_result = { + "suite": {"Example": {"metrics": ["fcp", "SpeedIndex"]}}, + "another_suite": {"Example": {}}, + } + + from perfdocs.verifier import Verifier + + with mock.patch("perfdocs.framework_gatherers.RaptorGatherer.get_test_list") as m: + m.return_value = sample_gatherer_result + verifier = Verifier(top_dir) + verifier.validate_tree() + + assert len(logger.warning.call_args_list) == expected + for (args, _) in logger.warning.call_args_list: + assert "Duplicate definitions found for " in args[0] + + +@pytest.mark.parametrize( + "manifest, metric_definitions", + [ + [ + SAMPLE_INI, + """ +metrics: + "FirstPaint": + aliases: + - fcp + - SpeedIndex + description: "Example" """, + ], + [ + SAMPLE_METRICS_INI, + """ +metrics: + FirstPaint: + aliases: + - fcp + description: Example + SpeedIndex: + aliases: + - speedindex + - si + description: Example + """, + ], + ], +) +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_verifier_valid_metrics( + logger, structured_logger, perfdocs_sample, manifest, metric_definitions +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + with open(perfdocs_sample["config"], "w", newline="\n") as f: + f.write(SAMPLE_METRICS_CONFIG.format(metric_definitions, "")) + with open(perfdocs_sample["manifest"]["path"], "w", newline="\n") as f: + f.write(manifest) + + sample_gatherer_result = { + "suite": {"Example": {"metrics": ["fcp", "SpeedIndex"]}}, + "another_suite": {"Example": {}}, + } + + from perfdocs.verifier import Verifier + + with mock.patch("perfdocs.framework_gatherers.RaptorGatherer.get_test_list") as m: + m.return_value = sample_gatherer_result + verifier = Verifier(top_dir) + verifier.validate_tree() + + assert len(logger.warning.call_args_list) == 0 + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_framework_gatherers(logger, structured_logger, perfdocs_sample): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + # Check to make sure that every single framework + # gatherer that has been implemented produces a test list + # in every suite that contains a test with an associated + # manifest. + from perfdocs.gatherer import frameworks + + for framework, gatherer in frameworks.items(): + with open(perfdocs_sample["config"], "w", newline="\n") as f: + f.write(DYNAMIC_SAMPLE_CONFIG.format(framework)) + + fg = gatherer(perfdocs_sample["config"], top_dir) + if getattr(fg, "get_test_list", None) is None: + # Skip framework gatherers that have not + # implemented a method to build a test list. + continue + + # Setup some framework-specific things here if needed + if framework == "raptor": + fg._manifest_path = perfdocs_sample["manifest"]["path"] + fg._get_subtests_from_ini = mock.Mock() + fg._get_subtests_from_ini.return_value = { + "Example": perfdocs_sample["manifest"], + } + + if framework == "talos": + fg._get_ci_tasks = mock.Mock() + for suite, suitetests in fg.get_test_list().items(): + assert suite == "Talos Tests" + assert suitetests + continue + + if framework == "awsy": + for suite, suitetests in fg.get_test_list().items(): + assert suite == "Awsy tests" + assert suitetests + continue + + for suite, suitetests in fg.get_test_list().items(): + assert suite == "suite" + for test, manifest in suitetests.items(): + assert test == "Example" + assert ( + pathlib.Path(manifest["path"]) + == perfdocs_sample["manifest"]["path"] + ) + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_framework_gatherers_urls(logger, structured_logger, perfdocs_sample): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.gatherer import frameworks + from perfdocs.generator import Generator + from perfdocs.utils import read_yaml + from perfdocs.verifier import Verifier + + # This test is only for raptor + gatherer = frameworks["raptor"] + with open(perfdocs_sample["config"], "w", newline="\n") as f: + f.write(DYNAMIC_SAMPLE_CONFIG.format("raptor")) + + fg = gatherer(perfdocs_sample["config_2"], top_dir) + fg.get_suite_list = mock.Mock() + fg.get_suite_list.return_value = { + "suite": [perfdocs_sample["example1_manifest"]], + "another_suite": [perfdocs_sample["example2_manifest"]], + } + + v = Verifier(top_dir) + gn = Generator(v, generate=True, workspace=top_dir) + + # Check to make sure that if a test is present under multiple + # suties the urls are generated correctly for the test under + # every suite + for suite, suitetests in fg.get_test_list().items(): + url = fg._descriptions.get(suite) + assert url is not None + assert url[0]["name"] == "Example" + assert url[0]["test_url"] == "Example_url" + + perfdocs_tree = gn._perfdocs_tree[0] + yaml_content = read_yaml( + pathlib.Path( + os.path.join(os.path.join(perfdocs_tree["path"], perfdocs_tree["yml"])) + ) + ) + suites = yaml_content["suites"] + + # Check that the sections for each suite are generated correctly + for suite_name, suite_details in suites.items(): + gn._verifier._gatherer = mock.Mock(framework_gatherers={"raptor": gatherer}) + section = gn._verifier._gatherer.framework_gatherers[ + "raptor" + ].build_suite_section(fg, suite_name, suites.get(suite_name)["description"]) + assert suite_name.capitalize() == section[0] + assert suite_name in section[2] + + tests = suites.get(suite_name).get("tests", {}) + for test_name in tests.keys(): + desc = gn._verifier._gatherer.framework_gatherers[ + "raptor" + ].build_test_description(fg, test_name, tests[test_name], suite_name) + assert f"**test url**: `<{url[0]['test_url']}>`__" in desc[0] + assert f"**expected**: {url[0]['expected']}" in desc[0] + assert test_name in desc[0] + + +def test_perfdocs_logger_failure(config, paths): + from perfdocs.logger import PerfDocLogger + + PerfDocLogger.LOGGER = None + with pytest.raises(Exception): + PerfDocLogger() + + PerfDocLogger.PATHS = [] + with pytest.raises(Exception): + PerfDocLogger() + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_perfdocs_generation.py b/tools/lint/test/test_perfdocs_generation.py new file mode 100644 index 0000000000..29b1555467 --- /dev/null +++ b/tools/lint/test/test_perfdocs_generation.py @@ -0,0 +1,297 @@ +import os +import pathlib +from unittest import mock + +import mozunit + +LINTER = "perfdocs" + + +def setup_sample_logger(logger, structured_logger, top_dir): + from perfdocs.logger import PerfDocLogger + + PerfDocLogger.LOGGER = structured_logger + PerfDocLogger.PATHS = ["perfdocs"] + PerfDocLogger.TOP_DIR = top_dir + + import perfdocs.gatherer as gt + import perfdocs.generator as gn + import perfdocs.utils as utils + import perfdocs.verifier as vf + + gt.logger = logger + vf.logger = logger + gn.logger = logger + utils.logger = logger + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_generator_generate_perfdocs_pass( + logger, structured_logger, perfdocs_sample +): + from test_perfdocs import temp_file + + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates") + templates_dir.mkdir(parents=True, exist_ok=True) + + from perfdocs.generator import Generator + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier.validate_tree() + + generator = Generator(verifier, generate=True, workspace=top_dir) + with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"): + generator.generate_perfdocs() + + assert logger.warning.call_count == 0 + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_generator_needed_regeneration( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.generator import Generator + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier.validate_tree() + + generator = Generator(verifier, generate=False, workspace=top_dir) + generator.generate_perfdocs() + + expected = "PerfDocs need to be regenerated." + args, _ = logger.warning.call_args + + assert logger.warning.call_count == 1 + assert args[0] == expected + + +@mock.patch("perfdocs.generator.get_changed_files", new=lambda x: []) +@mock.patch("perfdocs.generator.ON_TRY", new=True) +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_generator_needed_update(logger, structured_logger, perfdocs_sample): + from test_perfdocs import temp_file + + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates") + templates_dir.mkdir(parents=True, exist_ok=True) + + from perfdocs.generator import Generator + from perfdocs.verifier import Verifier + + # Initializing perfdocs + verifier = Verifier(top_dir) + verifier.validate_tree() + + generator = Generator(verifier, generate=True, workspace=top_dir) + with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"): + generator.generate_perfdocs() + + # Removed file for testing and run again + generator._generate = False + files = [f for f in os.listdir(generator.perfdocs_path)] + for f in files: + os.remove(str(pathlib.Path(generator.perfdocs_path, f))) + + generator.generate_perfdocs() + + expected = ( + "PerfDocs are outdated, run ./mach lint -l perfdocs --fix .` to update them. " + "You can also apply the perfdocs.diff patch file produced from this " + "reviewbot test to fix the issue." + ) + args, _ = logger.warning.call_args + + assert logger.warning.call_count == 1 + assert args[0] == expected + + # Check to ensure a diff was produced + assert logger.log.call_count == 6 + + logs = [v[0][0] for v in logger.log.call_args_list] + for failure_log in ( + "Some files are missing or are funny.", + "Missing in existing docs: index.rst", + "Missing in existing docs: mozperftest.rst", + ): + assert failure_log in logs + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_generator_created_perfdocs( + logger, structured_logger, perfdocs_sample +): + from test_perfdocs import temp_file + + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates") + templates_dir.mkdir(parents=True, exist_ok=True) + + from perfdocs.generator import Generator + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier.validate_tree() + + generator = Generator(verifier, generate=True, workspace=top_dir) + with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"): + perfdocs_tmpdir = generator._create_perfdocs() + + files = [f for f in os.listdir(perfdocs_tmpdir)] + files.sort() + expected_files = ["index.rst", "mozperftest.rst"] + + for i, file in enumerate(files): + assert file == expected_files[i] + + with pathlib.Path(perfdocs_tmpdir, expected_files[0]).open() as f: + filedata = f.readlines() + assert "".join(filedata) == " * :doc:`mozperftest`" + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_generator_build_perfdocs(logger, structured_logger, perfdocs_sample): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.generator import Generator + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier.validate_tree() + + generator = Generator(verifier, generate=True, workspace=top_dir) + frameworks_info = generator.build_perfdocs_from_tree() + + expected = ["dynamic", "static"] + + for framework in sorted(frameworks_info.keys()): + for i, framework_info in enumerate(frameworks_info[framework]): + assert framework_info == expected[i] + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_generator_create_temp_dir(logger, structured_logger, perfdocs_sample): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.generator import Generator + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier.validate_tree() + + generator = Generator(verifier, generate=True, workspace=top_dir) + tmpdir = generator._create_temp_dir() + + assert pathlib.Path(tmpdir).is_dir() + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_generator_create_temp_dir_fail( + logger, structured_logger, perfdocs_sample +): + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + from perfdocs.generator import Generator + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier.validate_tree() + + generator = Generator(verifier, generate=True, workspace=top_dir) + with mock.patch("perfdocs.generator.pathlib") as path_mock: + path_mock.Path().mkdir.side_effect = OSError() + path_mock.Path().is_dir.return_value = False + tmpdir = generator._create_temp_dir() + + expected = "Error creating temp file: " + args, _ = logger.critical.call_args + + assert not tmpdir + assert logger.critical.call_count == 1 + assert args[0] == expected + + +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_generator_save_perfdocs_pass( + logger, structured_logger, perfdocs_sample +): + from test_perfdocs import temp_file + + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates") + templates_dir.mkdir(parents=True, exist_ok=True) + + from perfdocs.generator import Generator + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier.validate_tree() + + generator = Generator(verifier, generate=True, workspace=top_dir) + + assert not generator.perfdocs_path.is_dir() + + with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"): + perfdocs_tmpdir = generator._create_perfdocs() + + generator._save_perfdocs(perfdocs_tmpdir) + + expected = ["index.rst", "mozperftest.rst"] + files = [f for f in os.listdir(generator.perfdocs_path)] + files.sort() + + for i, file in enumerate(files): + assert file == expected[i] + + +@mock.patch("perfdocs.generator.shutil") +@mock.patch("perfdocs.logger.PerfDocLogger") +def test_perfdocs_generator_save_perfdocs_fail( + logger, shutil, structured_logger, perfdocs_sample +): + from test_perfdocs import temp_file + + top_dir = perfdocs_sample["top_dir"] + setup_sample_logger(logger, structured_logger, top_dir) + + templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates") + templates_dir.mkdir(parents=True, exist_ok=True) + + from perfdocs.generator import Generator + from perfdocs.verifier import Verifier + + verifier = Verifier(top_dir) + verifier.validate_tree() + + generator = Generator(verifier, generate=True, workspace=top_dir) + with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"): + perfdocs_tmpdir = generator._create_perfdocs() + + shutil.copytree = mock.Mock(side_effect=Exception()) + generator._save_perfdocs(perfdocs_tmpdir) + + expected = "There was an error while saving the documentation: " + args, _ = logger.critical.call_args + + assert logger.critical.call_count == 1 + assert args[0] == expected + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_perfdocs_helpers.py b/tools/lint/test/test_perfdocs_helpers.py new file mode 100644 index 0000000000..02c1abfecc --- /dev/null +++ b/tools/lint/test/test_perfdocs_helpers.py @@ -0,0 +1,206 @@ +import mozunit +import pytest + +LINTER = "perfdocs" + +testdata = [ + { + "table_specifications": { + "title": ["not a string"], + "widths": [10, 10, 10, 10], + "header_rows": 1, + "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]], + "indent": 2, + }, + "error_msg": "TableBuilder attribute title must be a string.", + }, + { + "table_specifications": { + "title": "I've got a lovely bunch of coconuts", + "widths": ("not", "a", "list"), + "header_rows": 1, + "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]], + "indent": 2, + }, + "error_msg": "TableBuilder attribute widths must be a list of integers.", + }, + { + "table_specifications": { + "title": "There they are all standing in a row", + "widths": ["not an integer"], + "header_rows": 1, + "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]], + "indent": 2, + }, + "error_msg": "TableBuilder attribute widths must be a list of integers.", + }, + { + "table_specifications": { + "title": "Big ones, small ones", + "widths": [10, 10, 10, 10], + "header_rows": "not an integer", + "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]], + "indent": 2, + }, + "error_msg": "TableBuilder attribute header_rows must be an integer.", + }, + { + "table_specifications": { + "title": "Some as big as your head!", + "widths": [10, 10, 10, 10], + "header_rows": 1, + "headers": ("not", "a", "list"), + "indent": 2, + }, + "error_msg": "TableBuilder attribute headers must be a two-dimensional list of strings.", + }, + { + "table_specifications": { + "title": "(And bigger)", + "widths": [10, 10, 10, 10], + "header_rows": 1, + "headers": ["not", "two", "dimensional"], + "indent": 2, + }, + "error_msg": "TableBuilder attribute headers must be a two-dimensional list of strings.", + }, + { + "table_specifications": { + "title": "Give 'em a twist, a flick of the wrist'", + "widths": [10, 10, 10, 10], + "header_rows": 1, + "headers": [[1, 2, 3]], + "indent": 2, + }, + "error_msg": "TableBuilder attribute headers must be a two-dimensional list of strings.", + }, + { + "table_specifications": { + "title": "That's what the showman said!", + "widths": [10, 10, 10, 10], + "header_rows": 1, + "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]], + "indent": "not an integer", + }, + "error_msg": "TableBuilder attribute indent must be an integer.", + }, +] + +table_specifications = { + "title": "I've got a lovely bunch of coconuts", + "widths": [10, 10, 10], + "header_rows": 1, + "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]], + "indent": 2, +} + + +@pytest.mark.parametrize("testdata", testdata) +def test_table_builder_invalid_attributes(testdata): + from perfdocs.doc_helpers import TableBuilder + + table_specifications = testdata["table_specifications"] + error_msg = testdata["error_msg"] + + with pytest.raises(TypeError) as error: + TableBuilder( + table_specifications["title"], + table_specifications["widths"], + table_specifications["header_rows"], + table_specifications["headers"], + table_specifications["indent"], + ) + + assert str(error.value) == error_msg + + +def test_table_builder_mismatched_columns(): + from perfdocs.doc_helpers import MismatchedRowLengthsException, TableBuilder + + table_specifications = { + "title": "I've got a lovely bunch of coconuts", + "widths": [10, 10, 10, 42], + "header_rows": 1, + "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]], + "indent": 2, + } + + with pytest.raises(MismatchedRowLengthsException) as error: + TableBuilder( + table_specifications["title"], + table_specifications["widths"], + table_specifications["header_rows"], + table_specifications["headers"], + table_specifications["indent"], + ) + assert ( + str(error.value) + == "Number of table headers must match number of column widths." + ) + + +def test_table_builder_add_row_too_long(): + from perfdocs.doc_helpers import MismatchedRowLengthsException, TableBuilder + + table = TableBuilder( + table_specifications["title"], + table_specifications["widths"], + table_specifications["header_rows"], + table_specifications["headers"], + table_specifications["indent"], + ) + with pytest.raises(MismatchedRowLengthsException) as error: + table.add_row( + ["big ones", "small ones", "some as big as your head!", "(and bigger)"] + ) + assert ( + str(error.value) + == "Number of items in a row must must number of columns defined." + ) + + +def test_table_builder_add_rows_type_error(): + from perfdocs.doc_helpers import TableBuilder + + table = TableBuilder( + table_specifications["title"], + table_specifications["widths"], + table_specifications["header_rows"], + table_specifications["headers"], + table_specifications["indent"], + ) + with pytest.raises(TypeError) as error: + table.add_rows( + ["big ones", "small ones", "some as big as your head!", "(and bigger)"] + ) + assert str(error.value) == "add_rows() requires a two-dimensional list of strings." + + +def test_table_builder_validate(): + from perfdocs.doc_helpers import TableBuilder + + table = TableBuilder( + table_specifications["title"], + table_specifications["widths"], + table_specifications["header_rows"], + table_specifications["headers"], + table_specifications["indent"], + ) + table.add_row(["big ones", "small ones", "some as big as your head!"]) + table.add_row( + ["Give 'em a twist", "A flick of the wrist", "That's what the showman said!"] + ) + table = table.finish_table() + print(table) + assert ( + table == " .. list-table:: **I've got a lovely bunch of coconuts**\n" + " :widths: 10 10 10\n :header-rows: 1\n\n" + " * - **Coconut 1**\n - Coconut 2\n - Coconut 3\n" + " * - **big ones**\n - small ones\n - some as big as your head!\n" + " * - **Give 'em a twist**\n - A flick of the wrist\n" + " - That's what the showman said!\n\n" + ) + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_rst.py b/tools/lint/test/test_rst.py new file mode 100644 index 0000000000..e540081a94 --- /dev/null +++ b/tools/lint/test/test_rst.py @@ -0,0 +1,25 @@ +import mozunit +import pytest +from mozfile import which + +LINTER = "rst" +pytestmark = pytest.mark.skipif( + not which("rstcheck"), reason="rstcheck is not installed" +) + + +def test_basic(lint, paths): + results = lint(paths()) + assert len(results) == 2 + + assert "Title underline too short" in results[0].message + assert results[0].level == "error" + assert results[0].relpath == "bad.rst" + + assert "Title overline & underline mismatch" in results[1].message + assert results[1].level == "error" + assert results[1].relpath == "bad2.rst" + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_ruff.py b/tools/lint/test/test_ruff.py new file mode 100644 index 0000000000..fbb483780e --- /dev/null +++ b/tools/lint/test/test_ruff.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from pprint import pprint +from textwrap import dedent + +import mozunit + +LINTER = "ruff" +fixed = 0 + + +def test_lint_fix(lint, create_temp_file): + contents = dedent( + """ + import distutils + print("hello!") + """ + ) + + path = create_temp_file(contents, "bad.py") + lint([path], fix=True) + assert fixed == 1 + + +def test_lint_ruff(lint, paths): + results = lint(paths()) + pprint(results, indent=2) + assert len(results) == 2 + assert results[0].level == "error" + assert results[0].relpath == "bad.py" + assert "`distutils` imported but unused" in results[0].message + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_rustfmt.py b/tools/lint/test/test_rustfmt.py new file mode 100644 index 0000000000..c176a2695d --- /dev/null +++ b/tools/lint/test/test_rustfmt.py @@ -0,0 +1,70 @@ +import mozunit + +LINTER = "rustfmt" +fixed = 0 + + +def test_good(lint, config, paths): + results = lint(paths("subdir/good.rs")) + print(results) + assert len(results) == 0 + + +def test_basic(lint, config, paths): + results = lint(paths("subdir/bad.rs")) + print(results) + assert len(results) >= 1 + + assert "Reformat rust" in results[0].message + assert results[0].level == "warning" + assert results[0].lineno == 4 + assert "bad.rs" in results[0].path + assert "Print text to the console" in results[0].diff + + +def test_dir(lint, config, paths): + results = lint(paths("subdir/")) + print(results) + assert len(results) >= 4 + + assert "Reformat rust" in results[0].message + assert results[0].level == "warning" + assert results[0].lineno == 4 + assert "bad.rs" in results[0].path + assert "Print text to the console" in results[0].diff + + assert "Reformat rust" in results[1].message + assert results[1].level == "warning" + assert results[1].lineno == 4 + assert "bad2.rs" in results[1].path + assert "Print text to the console" in results[1].diff + + +def test_fix(lint, create_temp_file): + + contents = """fn main() { + // Statements here are executed when the compiled binary is called + + // Print text to the console + println!("Hello World!"); + let mut a; + let mut b=1; + let mut vec = Vec::new(); + vec.push(1); + vec.push(2); + + + for x in 5..10 - 5 { + a = x; + } + + } +""" + + path = create_temp_file(contents, "bad.rs") + lint([path], fix=True) + assert fixed == 3 + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_shellcheck.py b/tools/lint/test/test_shellcheck.py new file mode 100644 index 0000000000..1b41e298bd --- /dev/null +++ b/tools/lint/test/test_shellcheck.py @@ -0,0 +1,26 @@ +import mozunit +import pytest +from mozfile import which + +LINTER = "shellcheck" +pytestmark = pytest.mark.skipif( + not which("shellcheck"), reason="shellcheck is not installed" +) + + +def test_basic(lint, paths): + results = lint(paths()) + print(results) + assert len(results) == 2 + + assert "hello appears unused" in results[0].message + assert results[0].level == "error" + assert results[0].relpath == "bad.sh" + + assert "Double quote to prevent" in results[1].message + assert results[1].level == "error" + assert results[1].relpath == "bad.sh" + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_stylelint.py b/tools/lint/test/test_stylelint.py new file mode 100644 index 0000000000..5d758ad318 --- /dev/null +++ b/tools/lint/test/test_stylelint.py @@ -0,0 +1,65 @@ +import mozunit +import pytest +from conftest import build + +LINTER = "stylelint" +fixed = 0 + + +@pytest.fixture +def stylelint(lint): + def inner(*args, **kwargs): + # --ignore-path is because stylelint doesn't have the --no-ignore option + # and therefore needs to be given an empty file for the tests to work. + kwargs["extra_args"] = [ + "--ignore-path=tools/lint/test/files/eslint/testprettierignore", + ] + return lint(*args, **kwargs) + + return inner + + +def test_lint_with_global_exclude(lint, config, paths): + config["exclude"] = ["subdir", "import"] + # This uses lint directly as we need to not ignore the excludes. + results = lint(paths(), config=config, root=build.topsrcdir) + assert len(results) == 0 + + +def test_no_files_to_lint(stylelint, config, paths): + # A directory with no files to lint. + results = stylelint(paths("nolint"), root=build.topsrcdir) + assert results == [] + + # Errors still show up even when a directory with no files is passed in. + results = stylelint(paths("nolint", "subdir/bad.css"), root=build.topsrcdir) + assert len(results) == 1 + + +def test_stylelint(stylelint, config, create_temp_file): + contents = """#foo { + font-size: 12px; + font-size: 12px; +} +""" + path = create_temp_file(contents, "bad.css") + results = stylelint([path], config=config, root=build.topsrcdir) + + assert len(results) == 1 + + +def test_stylelint_fix(stylelint, config, create_temp_file): + contents = """#foo { + font-size: 12px; + font-size: 12px; +} +""" + path = create_temp_file(contents, "bad.css") + stylelint([path], config=config, root=build.topsrcdir, fix=True) + + # stylelint returns counts of files fixed, not errors fixed. + assert fixed == 1 + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_trojan_source.py b/tools/lint/test/test_trojan_source.py new file mode 100644 index 0000000000..64a3789c37 --- /dev/null +++ b/tools/lint/test/test_trojan_source.py @@ -0,0 +1,25 @@ +import mozunit + +LINTER = "trojan-source" + + +def test_lint_trojan_source(lint, paths): + results = lint(paths()) + print(results) + assert len(results) == 3 + + assert "disallowed characters" in results[0].message + assert results[0].level == "error" + assert "commenting-out.cpp" in results[0].relpath + + assert "disallowed characters" in results[1].message + assert results[1].level == "error" + assert "early-return.py" in results[1].relpath + + assert "disallowed characters" in results[2].message + assert results[2].level == "error" + assert "invisible-function.rs" in results[2].relpath + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_updatebot.py b/tools/lint/test/test_updatebot.py new file mode 100644 index 0000000000..55842e99c0 --- /dev/null +++ b/tools/lint/test/test_updatebot.py @@ -0,0 +1,44 @@ +import os + +import mozunit + +LINTER = "updatebot" + + +def test_basic(lint, paths): + results = [] + + for p in paths(): + for (root, dirs, files) in os.walk(p): + for f in files: + if f == ".yamllint": + continue + + filepath = os.path.join(root, f) + result = lint(filepath, testing=True) + if result: + results.append(result) + + assert len(results) == 2 + + expected_results = 0 + + for r in results: + if "no-revision.yaml" in r[0].path: + expected_results += 1 + assert "no-revision.yaml" in r[0].path + assert ( + 'If "vendoring" is present, "revision" must be present in "origin"' + in r[0].message + ) + + if "cargo-mismatch.yaml" in r[0].path: + expected_results += 1 + assert "cargo-mismatch.yaml" in r[0].path + assert "wasn't found in Cargo.lock" in r[0].message + + assert expected_results == 2 + + +if __name__ == "__main__": + mozunit.main() diff --git a/tools/lint/test/test_yaml.py b/tools/lint/test/test_yaml.py new file mode 100644 index 0000000000..63b678d152 --- /dev/null +++ b/tools/lint/test/test_yaml.py @@ -0,0 +1,28 @@ +import mozunit + +LINTER = "yaml" + + +def test_basic(lint, paths): + results = lint(paths()) + + assert len(results) == 3 + + assert "line too long (122 > 80 characters)" in results[0].message + assert results[0].level == "error" + assert "bad.yml" in results[0].relpath + assert results[0].lineno == 3 + + assert "wrong indentation: expected 4 but found 8" in results[1].message + assert results[1].level == "error" + assert "bad.yml" in results[1].relpath + assert results[0].lineno == 3 + + assert "could not find expected" in results[2].message + assert results[2].level == "error" + assert "bad.yml" in results[2].relpath + assert results[2].lineno == 9 + + +if __name__ == "__main__": + mozunit.main() |