summaryrefslogtreecommitdiffstats
path: root/tools/lint/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/lint/test-disable.yml16
-rw-r--r--tools/lint/test/conftest.py228
-rw-r--r--tools/lint/test/files/black/bad.py6
-rw-r--r--tools/lint/test/files/black/invalid.py4
-rw-r--r--tools/lint/test/files/clang-format/bad/bad.cpp6
-rw-r--r--tools/lint/test/files/clang-format/bad/bad2.c8
-rw-r--r--tools/lint/test/files/clang-format/bad/bad2.h1
-rw-r--r--tools/lint/test/files/clang-format/bad/good.cpp1
-rw-r--r--tools/lint/test/files/clang-format/good/foo.cpp1
-rw-r--r--tools/lint/test/files/clippy/test1/Cargo.toml17
-rw-r--r--tools/lint/test/files/clippy/test1/bad.rs14
-rw-r--r--tools/lint/test/files/clippy/test1/bad2.rs17
-rw-r--r--tools/lint/test/files/clippy/test1/good.rs6
-rw-r--r--tools/lint/test/files/clippy/test2/Cargo.lock5
-rw-r--r--tools/lint/test/files/clippy/test2/Cargo.toml8
-rw-r--r--tools/lint/test/files/clippy/test2/src/bad_1.rs15
-rw-r--r--tools/lint/test/files/clippy/test2/src/bad_2.rs17
-rw-r--r--tools/lint/test/files/codespell/ignore.rst5
-rw-r--r--tools/lint/test/files/eslint/good.js0
-rw-r--r--tools/lint/test/files/eslint/import/bad_import.js1
-rw-r--r--tools/lint/test/files/eslint/nolint/foo.txt0
-rw-r--r--tools/lint/test/files/eslint/subdir/bad.js2
-rwxr-xr-xtools/lint/test/files/file-perm/maybe-shebang/bad.js2
-rwxr-xr-xtools/lint/test/files/file-perm/maybe-shebang/good.js5
-rwxr-xr-xtools/lint/test/files/file-perm/no-shebang/bad-shebang.c2
-rwxr-xr-xtools/lint/test/files/file-perm/no-shebang/bad.c1
-rwxr-xr-xtools/lint/test/files/file-perm/no-shebang/bad.pngbin0 -> 49 bytes
-rw-r--r--tools/lint/test/files/file-perm/no-shebang/good.c1
-rw-r--r--tools/lint/test/files/file-whitespace/bad-newline.c3
-rw-r--r--tools/lint/test/files/file-whitespace/bad-windows.c3
-rw-r--r--tools/lint/test/files/file-whitespace/bad.c3
-rwxr-xr-xtools/lint/test/files/file-whitespace/bad.js3
-rw-r--r--tools/lint/test/files/file-whitespace/good.c1
-rwxr-xr-xtools/lint/test/files/file-whitespace/good.js5
-rw-r--r--tools/lint/test/files/flake8/.flake84
-rw-r--r--tools/lint/test/files/flake8/bad.py5
-rw-r--r--tools/lint/test/files/flake8/custom/.flake84
-rw-r--r--tools/lint/test/files/flake8/custom/good.py5
-rw-r--r--tools/lint/test/files/flake8/ext/bad.configure2
-rw-r--r--tools/lint/test/files/flake8/subdir/exclude/bad.py5
-rw-r--r--tools/lint/test/files/flake8/subdir/exclude/exclude_subdir/bad.py5
-rw-r--r--tools/lint/test/files/license/.eslintrc.js5
-rw-r--r--tools/lint/test/files/license/bad.c1
-rw-r--r--tools/lint/test/files/license/bad.js6
-rw-r--r--tools/lint/test/files/license/good-other.h9
-rw-r--r--tools/lint/test/files/license/good.c8
-rw-r--r--tools/lint/test/files/license/good.js7
-rw-r--r--tools/lint/test/files/lintpref/bad.js2
-rw-r--r--tools/lint/test/files/lintpref/good.js6
-rw-r--r--tools/lint/test/files/pylint/bad.py5
-rw-r--r--tools/lint/test/files/pylint/good.py3
-rw-r--r--tools/lint/test/files/rst/.dotfile.rst11
-rw-r--r--tools/lint/test/files/rst/bad.rst20
-rw-r--r--tools/lint/test/files/rst/bad2.rst4
-rw-r--r--tools/lint/test/files/rst/good.rst11
-rw-r--r--tools/lint/test/files/rustfmt/subdir/bad.rs16
-rw-r--r--tools/lint/test/files/rustfmt/subdir/bad2.rs17
-rw-r--r--tools/lint/test/files/rustfmt/subdir/good.rs6
-rw-r--r--tools/lint/test/files/shellcheck/bad.sh3
-rw-r--r--tools/lint/test/files/shellcheck/good.sh2
-rw-r--r--tools/lint/test/files/yaml/.yamllint6
-rw-r--r--tools/lint/test/files/yaml/bad.yml8
-rw-r--r--tools/lint/test/files/yaml/good.yml6
-rw-r--r--tools/lint/test/python.ini34
-rw-r--r--tools/lint/test/test_black.py26
-rw-r--r--tools/lint/test/test_clang_format.py48
-rw-r--r--tools/lint/test/test_clippy.py103
-rw-r--r--tools/lint/test/test_codespell.py24
-rw-r--r--tools/lint/test/test_eslint.py30
-rw-r--r--tools/lint/test/test_file_license.py25
-rw-r--r--tools/lint/test/test_file_perm.py39
-rw-r--r--tools/lint/test/test_file_whitespace.py37
-rw-r--r--tools/lint/test/test_flake8.py109
-rw-r--r--tools/lint/test/test_lintpref.py18
-rw-r--r--tools/lint/test/test_perfdocs.py423
-rw-r--r--tools/lint/test/test_pylint.py24
-rw-r--r--tools/lint/test/test_rst.py25
-rw-r--r--tools/lint/test/test_rustfmt.py44
-rw-r--r--tools/lint/test/test_shellcheck.py26
-rw-r--r--tools/lint/test/test_yaml.py28
80 files changed, 1662 insertions, 0 deletions
diff --git a/tools/lint/test-disable.yml b/tools/lint/test-disable.yml
new file mode 100644
index 0000000000..7d93889704
--- /dev/null
+++ b/tools/lint/test-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/conftest.py b/tools/lint/test/conftest.py
new file mode 100644
index 0000000000..e9a6b01a2d
--- /dev/null
+++ b/tools/lint/test/conftest.py
@@ -0,0 +1,228 @@
+from __future__ import absolute_import, print_function
+
+import logging
+import os
+import sys
+from collections import defaultdict
+
+from mozbuild.base import MozbuildObject
+from mozlint.pathutils import findobject
+from mozlint.parser import Parser
+from mozlint.result import ResultSummary
+from mozlog.structuredlog import StructuredLogger
+from mozpack import path
+
+import pytest
+
+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
+
+ func = findobject(config["setup"])
+ func(build.topsrcdir, virtualenv_manager=build.virtualenv_manager)
+
+
+@pytest.fixture
+def lint(config, root):
+ """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.
+ """
+ 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 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 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 (
+ SAMPLE_TEST,
+ SAMPLE_CONFIG,
+ temp_dir,
+ temp_file,
+ )
+
+ with temp_dir() as tmpdir:
+ suitedir = os.path.join(tmpdir, "suite")
+ perfdocs_dir = os.path.join(tmpdir, "perfdocs")
+ os.mkdir(perfdocs_dir)
+ os.mkdir(suitedir)
+
+ with temp_file(
+ "perftest.ini", tempdir=suitedir, content="[perftest_sample.js]"
+ ) as tmpmanifest, temp_file(
+ "perftest_sample.js", tempdir=suitedir, content=SAMPLE_TEST
+ ) as tmptest, temp_file(
+ "config.yml", tempdir=perfdocs_dir, content=SAMPLE_CONFIG
+ ) as tmpconfig, temp_file(
+ "index.rst", tempdir=perfdocs_dir, content="{documentation}"
+ ) as tmpindex:
+ yield {
+ "top_dir": tmpdir.replace("\\", "\\\\"),
+ "manifest": tmpmanifest,
+ "test": tmptest,
+ "config": tmpconfig,
+ "index": tmpindex,
+ }
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..a4236a2de7
--- /dev/null
+++ b/tools/lint/test/files/clippy/test1/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/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/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
new file mode 100755
index 0000000000..db3a5fda7e
--- /dev/null
+++ b/tools/lint/test/files/file-perm/no-shebang/bad.png
Binary files differ
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 100755
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 100755
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/flake8/.flake8 b/tools/lint/test/files/flake8/.flake8
new file mode 100644
index 0000000000..1933432319
--- /dev/null
+++ b/tools/lint/test/files/flake8/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+max-line-length = 100
+exclude =
+ subdir/exclude,
diff --git a/tools/lint/test/files/flake8/bad.py b/tools/lint/test/files/flake8/bad.py
new file mode 100644
index 0000000000..9d9751c7eb
--- /dev/null
+++ b/tools/lint/test/files/flake8/bad.py
@@ -0,0 +1,5 @@
+# Unused import
+import distutils
+
+print("This is a line that is over 80 characters but under 100. It shouldn't fail.")
+print("This is a line that is over not only 80, but 100 characters. It should most certainly cause a failure.")
diff --git a/tools/lint/test/files/flake8/custom/.flake8 b/tools/lint/test/files/flake8/custom/.flake8
new file mode 100644
index 0000000000..cfe68833f2
--- /dev/null
+++ b/tools/lint/test/files/flake8/custom/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+max-line-length=110
+ignore=
+ F401
diff --git a/tools/lint/test/files/flake8/custom/good.py b/tools/lint/test/files/flake8/custom/good.py
new file mode 100644
index 0000000000..7f9121a2ba
--- /dev/null
+++ b/tools/lint/test/files/flake8/custom/good.py
@@ -0,0 +1,5 @@
+# Unused import
+import distutils
+
+print("This is a line that is over 80 characters but under 100. It shouldn't fail.")
+print("This is a line that is over not only 80, but 100 characters. It should also not cause a failure.")
diff --git a/tools/lint/test/files/flake8/ext/bad.configure b/tools/lint/test/files/flake8/ext/bad.configure
new file mode 100644
index 0000000000..8214ebb3c0
--- /dev/null
+++ b/tools/lint/test/files/flake8/ext/bad.configure
@@ -0,0 +1,2 @@
+# unused import
+import os
diff --git a/tools/lint/test/files/flake8/subdir/exclude/bad.py b/tools/lint/test/files/flake8/subdir/exclude/bad.py
new file mode 100644
index 0000000000..9d9751c7eb
--- /dev/null
+++ b/tools/lint/test/files/flake8/subdir/exclude/bad.py
@@ -0,0 +1,5 @@
+# Unused import
+import distutils
+
+print("This is a line that is over 80 characters but under 100. It shouldn't fail.")
+print("This is a line that is over not only 80, but 100 characters. It should most certainly cause a failure.")
diff --git a/tools/lint/test/files/flake8/subdir/exclude/exclude_subdir/bad.py b/tools/lint/test/files/flake8/subdir/exclude/exclude_subdir/bad.py
new file mode 100644
index 0000000000..9d9751c7eb
--- /dev/null
+++ b/tools/lint/test/files/flake8/subdir/exclude/exclude_subdir/bad.py
@@ -0,0 +1,5 @@
+# Unused import
+import distutils
+
+print("This is a line that is over 80 characters but under 100. It shouldn't fail.")
+print("This is a line that is over not only 80, but 100 characters. It should most certainly cause a failure.")
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/pylint/bad.py b/tools/lint/test/files/pylint/bad.py
new file mode 100644
index 0000000000..61b69a49cf
--- /dev/null
+++ b/tools/lint/test/files/pylint/bad.py
@@ -0,0 +1,5 @@
+def foo():
+ useless_var = 1
+ useless_var = true
+ return "true"
+ print("unreachable") \ No newline at end of file
diff --git a/tools/lint/test/files/pylint/good.py b/tools/lint/test/files/pylint/good.py
new file mode 100644
index 0000000000..c867dc66ec
--- /dev/null
+++ b/tools/lint/test/files/pylint/good.py
@@ -0,0 +1,3 @@
+def foo():
+ a = 1 + 1
+ return a
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/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/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/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..5acd0a1e81
--- /dev/null
+++ b/tools/lint/test/python.ini
@@ -0,0 +1,34 @@
+[DEFAULT]
+subsuite = mozlint
+skip-if = python == 2
+
+[test_black.py]
+requirements = tools/lint/python/black_requirements.txt
+skip-if = os == "mac" # pip unable to find black
+[test_eslint.py]
+[test_flake8.py]
+requirements = tools/lint/python/flake8_requirements.txt
+skip-if = os == "mac" # pip unable to find 'flake8==3.5.0'
+[test_file_perm.py]
+skip-if = os == "win"
+[test_file_whitespace.py]
+[test_file_license.py]
+[test_lintpref.py]
+[test_shellcheck.py]
+[test_rst.py]
+requirements = tools/lint/rst/requirements.txt
+skip-if = os == "mac" # pip unable to install
+[test_codespell.py]
+skip-if = os == "win" || os == "mac" # codespell installed on Linux
+[test_yaml.py]
+[test_clippy.py]
+skip-if = os == "win" || os == "mac" # only installed on Linux
+[test_rustfmt.py]
+skip-if = os == "win" || os == "mac" # only installed on Linux
+[test_clang_format.py]
+skip-if = os == "win" || os == "mac" # only installed on Linux
+[test_perfdocs.py]
+[test_pylint.py]
+skip-if = os == "win" || os == "mac" # only installed on linux
+requirements = tools/lint/python/pylint_requirements.txt
+
diff --git a/tools/lint/test/test_black.py b/tools/lint/test/test_black.py
new file mode 100644
index 0000000000..a5482c6e11
--- /dev/null
+++ b/tools/lint/test/test_black.py
@@ -0,0 +1,26 @@
+# -*- 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"
+
+
+def test_lint_black(lint, paths):
+ results = lint(paths())
+ assert len(results) == 2
+
+ print(results)
+ 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..a04fde6267
--- /dev/null
+++ b/tools/lint/test/test_clang_format.py
@@ -0,0 +1,48 @@
+import mozunit
+
+from conftest import build
+
+LINTER = "clang-format"
+
+
+def test_good(lint, config, paths):
+ results = lint(paths("good/"), root=build.topsrcdir, use_filters=False)
+ print(results)
+ assert len(results) == 0
+
+
+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 == 4
+ assert "bad.cpp" in results[0].path
+ assert "int main ( ) {" in results[0].diff
+
+
+def test_dir(lint, config, paths):
+ results = lint(paths("bad/"), root=build.topsrcdir, use_filters=False)
+ print(results)
+ assert len(results) >= 4
+
+ assert "Reformat C/C++" in results[0].message
+ assert results[0].level == "warning"
+ assert results[0].lineno == 1
+ assert results[0].column == 4
+ assert "bad.cpp" in results[0].path
+ assert "int main ( ) {" in results[0].diff
+
+ assert "Reformat C/C++" in results[5].message
+ assert results[5].level == "warning"
+ assert results[5].lineno == 1
+ assert results[5].column == 18
+ assert "bad2.c" in results[5].path
+ assert "#include" in results[5].diff
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_clippy.py b/tools/lint/test/test_clippy.py
new file mode 100644
index 0000000000..553aba8876
--- /dev/null
+++ b/tools/lint/test/test_clippy.py
@@ -0,0 +1,103 @@
+import mozunit
+import os
+
+LINTER = "clippy"
+
+
+def test_basic(lint, config, paths):
+ results = lint(paths("test1/"))
+ print(results)
+ assert len(results) > 7
+
+ assert (
+ "is never read" in results[0].message or "but never used" in results[0].message
+ )
+ assert results[0].level == "warning"
+ assert results[0].lineno == 7
+ assert results[0].column == 9
+ assert results[0].rule == "unused_assignments"
+ assert results[0].relpath == "test1/bad.rs"
+ assert "tools/lint/test/files/clippy/test1/bad.rs" in results[0].path
+
+ assert "this looks like you are trying to swap `a` and `b`" in results[1].message
+ assert results[1].level == "error"
+ assert results[1].relpath == "test1/bad.rs"
+ assert results[1].rule == "clippy::almost_swapped"
+ assert "or maybe you should use `std::mem::replace`" in results[1].hint
+
+ assert "value assigned to `b` is never read" in results[2].message
+ assert results[2].level == "warning"
+ assert results[2].relpath == "test1/bad.rs"
+
+ assert "variable does not need to be mutable" in results[5].message
+ assert results[5].relpath == "test1/bad2.rs"
+ assert results[5].rule == "unused_mut"
+
+ assert "this range is empty so" in results[6].message
+ assert results[6].level == "error"
+ assert results[6].relpath == "test1/bad2.rs"
+ assert results[6].rule == "clippy::reversed_empty_ranges"
+
+
+def test_error(lint, config, paths):
+ results = lint(paths("test1/Cargo.toml"))
+ # Should fail. We don't accept Cargo.toml as input
+ assert results == 1
+
+
+def test_file_and_path_provided(lint, config, paths):
+ results = lint(paths("./test2/src/bad_1.rs", "test1/"))
+ # even if clippy analyzed it
+ # we should not have anything from bad_2.rs
+ # as mozlint is filtering out the file
+ print(results)
+ assert len(results) > 16
+ assert "value assigned to `a` is never read" in results[0].message
+ assert results[0].level == "warning"
+ assert results[0].lineno == 7
+ assert results[0].column == 9
+ assert results[0].rule == "unused_assignments"
+ assert results[0].relpath == "test1/bad.rs"
+ assert "tools/lint/test/files/clippy/test1/bad.rs" in results[0].path
+ assert "value assigned to `a` is never read" in results[0].message
+ assert results[8].level == "warning"
+ assert results[8].lineno == 1
+ assert results[8].column == 4
+ assert results[8].rule == "dead_code"
+ assert results[8].relpath == "test2/src/bad_1.rs"
+ assert "tools/lint/test/files/clippy/test2/src/bad_1.rs" in results[8].path
+ for r in results:
+ assert "bad_2.rs" not in r.relpath
+
+
+def test_file_provided(lint, config, paths):
+ results = lint(paths("./test2/src/bad_1.rs"))
+ # even if clippy analyzed it
+ # we should not have anything from bad_2.rs
+ # as mozlint is filtering out the file
+ print(results)
+ assert len(results) > 8
+ assert results[0].level == "warning"
+ assert results[0].lineno == 1
+ assert results[0].column == 4
+ assert results[0].rule == "dead_code"
+ assert results[0].relpath == "test2/src/bad_1.rs"
+ assert "tools/lint/test/files/clippy/test2/src/bad_1.rs" in results[0].path
+ for r in results:
+ assert "bad_2.rs" not in r.relpath
+
+
+def test_cleanup(lint, paths, root):
+ # If Cargo.lock does not exist before clippy run, delete it
+ lint(paths("test1/"))
+ assert not os.path.exists(os.path.join(root, "test1/target/"))
+ assert not os.path.exists(os.path.join(root, "test1/Cargo.lock"))
+
+ # If Cargo.lock exists before clippy run, keep it after cleanup
+ lint(paths("test2/"))
+ assert not os.path.exists(os.path.join(root, "test2/target/"))
+ assert os.path.exists(os.path.join(root, "test2/Cargo.lock"))
+
+
+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..41912e01a7
--- /dev/null
+++ b/tools/lint/test/test_codespell.py
@@ -0,0 +1,24 @@
+from __future__ import absolute_import, print_function
+
+import mozunit
+
+LINTER = "codespell"
+
+
+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..01456a4063
--- /dev/null
+++ b/tools/lint/test/test_eslint.py
@@ -0,0 +1,30 @@
+import mozunit
+
+from conftest import build
+
+LINTER = "eslint"
+
+
+def test_lint_with_global_exclude(lint, config, paths):
+ config["exclude"] = ["subdir", "import"]
+ results = lint(paths(), config=config, root=build.topsrcdir)
+ assert len(results) == 0
+
+
+def test_no_files_to_lint(lint, config, paths):
+ # A directory with no files to lint.
+ results = lint(paths("nolint"), root=build.topsrcdir)
+ assert results == []
+
+ # Errors still show up even when a directory with no files is passed in.
+ results = lint(paths("nolint", "subdir/bad.js"), root=build.topsrcdir)
+ assert len(results) == 1
+
+
+def test_bad_import(lint, config, paths):
+ results = lint(paths("import"), config=config, root=build.topsrcdir)
+ assert results == 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..b7f0efac60
--- /dev/null
+++ b/tools/lint/test/test_file_license.py
@@ -0,0 +1,25 @@
+from __future__ import absolute_import, print_function
+
+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..df5de9adb8
--- /dev/null
+++ b/tools/lint/test/test_file_perm.py
@@ -0,0 +1,39 @@
+from __future__ import absolute_import, print_function
+
+import pytest
+
+import mozunit
+
+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)
+ print(results)
+
+ 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"))
+ print(results)
+ 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..b6fa79799b
--- /dev/null
+++ b/tools/lint/test/test_file_whitespace.py
@@ -0,0 +1,37 @@
+from __future__ import absolute_import, print_function
+
+import mozunit
+
+LINTER = "file-whitespace"
+
+
+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
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_flake8.py b/tools/lint/test/test_flake8.py
new file mode 100644
index 0000000000..66befab4e7
--- /dev/null
+++ b/tools/lint/test/test_flake8.py
@@ -0,0 +1,109 @@
+import os
+
+import mozunit
+
+LINTER = "flake8"
+
+
+def test_lint_single_file(lint, paths):
+ results = lint(paths("bad.py"))
+ assert len(results) == 2
+ assert results[0].rule == "F401"
+ assert results[1].rule == "E501"
+ assert results[1].lineno == 5
+
+ # run lint again to make sure the previous results aren't counted twice
+ results = lint(paths("bad.py"))
+ assert len(results) == 2
+
+
+def test_lint_custom_config_ignored(lint, paths):
+ results = lint(paths("custom"))
+ assert len(results) == 2
+
+ results = lint(paths("custom/good.py"))
+ assert len(results) == 2
+
+
+def test_lint_fix(lint, create_temp_file):
+ contents = """
+import distutils
+
+def foobar():
+ pass
+""".lstrip()
+
+ path = create_temp_file(contents, name="bad.py")
+ results = lint([path])
+ assert len(results) == 2
+
+ # Make sure the missing blank line is fixed, but the unused import isn't.
+ results = lint([path], fix=True)
+ assert len(results) == 1
+
+ # Also test with a directory
+ path = os.path.dirname(create_temp_file(contents, name="bad2.py"))
+ results = lint([path], fix=True)
+ # There should now be two files with 2 combined errors
+ assert len(results) == 2
+ assert all(r.rule != "E501" for r in results)
+
+
+def test_lint_fix_uses_config(lint, create_temp_file):
+ contents = """
+foo = ['A list of strings', 'that go over 80 characters', 'to test if autopep8 fixes it']
+""".lstrip()
+
+ path = create_temp_file(contents, name="line_length.py")
+ lint([path], fix=True)
+
+ # Make sure autopep8 reads the global config under lintargs['root']. If it
+ # didn't, then the line-length over 80 would get fixed.
+ with open(path, "r") as fh:
+ assert fh.read() == contents
+
+
+def test_lint_excluded_file(lint, paths, config):
+ # First file is globally excluded, second one is from .flake8 config.
+ files = paths("bad.py", "subdir/exclude/bad.py", "subdir/exclude/exclude_subdir")
+ config["exclude"] = paths("bad.py")
+ results = lint(files, config)
+ print(results)
+ assert len(results) == 0
+
+ # Make sure excludes also apply when running from a different cwd.
+ cwd = paths("subdir")[0]
+ os.chdir(cwd)
+
+ results = lint(paths("subdir/exclude"))
+ print(results)
+ assert len(results) == 0
+
+
+def test_lint_excluded_file_with_glob(lint, paths, config):
+ config["exclude"] = paths("ext/*.configure")
+
+ files = paths("ext")
+ results = lint(files, config)
+ print(results)
+ assert len(results) == 0
+
+ files = paths("ext/bad.configure")
+ results = lint(files, config)
+ print(results)
+ assert len(results) == 0
+
+
+def test_lint_excluded_file_with_no_filter(lint, paths, config):
+ results = lint(paths("subdir/exclude"), use_filters=False)
+ print(results)
+ assert len(results) == 4
+
+
+def test_lint_uses_custom_extensions(lint, paths):
+ assert len(lint(paths("ext"))) == 1
+ assert len(lint(paths("ext/bad.configure"))) == 1
+
+
+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..4fb3b8d23c
--- /dev/null
+++ b/tools/lint/test/test_lintpref.py
@@ -0,0 +1,18 @@
+from __future__ import absolute_import, print_function
+
+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_perfdocs.py b/tools/lint/test/test_perfdocs.py
new file mode 100644
index 0000000000..f977330fda
--- /dev/null
+++ b/tools/lint/test/test_perfdocs.py
@@ -0,0 +1,423 @@
+import contextlib
+import mock
+import os
+import pytest
+import shutil
+import tempfile
+
+import mozunit
+
+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: ""
+"""
+
+
+@contextlib.contextmanager
+def temp_file(name="temp", tempdir=None, content=None):
+ if tempdir is None:
+ tempdir = tempfile.mkdtemp()
+ path = os.path.join(tempdir, name)
+ if content is not None:
+ with open(path, "w") as f:
+ f.write(content)
+ try:
+ yield path
+ finally:
+ try:
+ shutil.rmtree(tempdir)
+ except FileNotFoundError:
+ pass
+
+
+@contextlib.contextmanager
+def temp_dir():
+ tempdir = tempfile.mkdtemp()
+ try:
+ yield tempdir
+ finally:
+ try:
+ shutil.rmtree(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.verifier as vf
+ import perfdocs.gatherer as gt
+ import perfdocs.generator as gn
+
+ gt.logger = logger
+ vf.logger = logger
+ gn.logger = logger
+
+
+@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, 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=[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("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=[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_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(yaml_path)
+
+ assert valid
+
+
+@mock.patch("perfdocs.verifier.jsonschema")
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_invalid_yaml(
+ logger, jsonschema, 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
+
+ jsonschema.validate = mock.Mock(side_effect=Exception("Schema/ValidationError"))
+ verifier = Verifier("top_dir")
+ valid = verifier.validate_yaml(yaml_path)
+
+ expected = ("YAML ValidationError: Schema/ValidationError", yaml_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_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(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") 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"]
+ 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") 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_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
+
+ Verifier.validate_yaml = mock.Mock(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
+
+
+@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") 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"]
+ fg._get_subtests_from_ini = mock.Mock()
+ fg._get_subtests_from_ini.return_value = {
+ "Example": perfdocs_sample["manifest"]
+ }
+
+ for suite, suitetests in fg.get_test_list().items():
+ assert suite == "suite"
+ for test, manifest in suitetests.items():
+ assert test == "Example"
+ assert manifest == perfdocs_sample["manifest"]
+
+
+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_pylint.py b/tools/lint/test/test_pylint.py
new file mode 100644
index 0000000000..6ee2217089
--- /dev/null
+++ b/tools/lint/test/test_pylint.py
@@ -0,0 +1,24 @@
+import mozunit
+
+LINTER = "pylint"
+
+
+def test_lint_single_file(lint, paths):
+ results = lint(paths("bad.py"))
+ assert len(results) == 3
+ assert results[1].rule == "E0602"
+ assert results[2].rule == "W0101"
+ assert results[2].lineno == 5
+
+ # run lint again to make sure the previous results aren't counted twice
+ results = lint(paths("bad.py"))
+ assert len(results) == 3
+
+
+def test_lint_single_file_good(lint, paths):
+ results = lint(paths("good.py"))
+ assert len(results) == 0
+
+
+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..532ac332d6
--- /dev/null
+++ b/tools/lint/test/test_rst.py
@@ -0,0 +1,25 @@
+import pytest
+import mozunit
+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_rustfmt.py b/tools/lint/test/test_rustfmt.py
new file mode 100644
index 0000000000..4eaf333825
--- /dev/null
+++ b/tools/lint/test/test_rustfmt.py
@@ -0,0 +1,44 @@
+import mozunit
+
+
+LINTER = "rustfmt"
+
+
+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
+
+
+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..aa6fdc4494
--- /dev/null
+++ b/tools/lint/test/test_shellcheck.py
@@ -0,0 +1,26 @@
+import pytest
+import mozunit
+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_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()