The pipelint tool was not working correctly on Windows for three reasons: 1. The module was being imported into every child process spawned by the lint roller's multiprocessing executor. Because the import of pipelint has side-effects (the actual running of mach lint on the input), each child process would end up recursively triggering these side-effects and causing exponential process growth until they all eventually crashed. To address this we now have the logic of the pipelint script behind a `if __name__ == "__main__" guard. 2. We were keeping the file handle open before we called into mach, which resulted in a permissions error trying to read from the file handle after mach had finished. To address this we now immediately close the file before invoking `mach lint` and re-open it for reading after. 3. We were using default newline handling inside `pipelint` and `mozlint`, which resulted in LFs being translated to CRLFs automatically in all the intermediate temporary files. To address this, we open files for reading and writing in binary mode so that we deal directly with the underlying bytes instead of particular interpretations of bytes (e.g., newlines, CP252 vs UTF-8), including `stdin` (when reading file input from `jj`) and `stdout` (when outputting the final formatted files). Differential Revision: https://phabricator.services.mozilla.com/D244513
166 lines
4.9 KiB
Python
166 lines
4.9 KiB
Python
# 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 io
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from shutil import which
|
|
|
|
import mozunit
|
|
import pytest
|
|
|
|
from mozlint import cli
|
|
|
|
here = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
|
|
@pytest.fixture
|
|
def parser():
|
|
return cli.MozlintParser()
|
|
|
|
|
|
@pytest.fixture
|
|
def run(parser, files):
|
|
def inner(args=None):
|
|
args = args or []
|
|
if not any("--stdin-filename" in a for a in args):
|
|
args.extend(files)
|
|
lintargs = vars(parser.parse_args(args))
|
|
lintargs["root"] = here
|
|
lintargs["config_paths"] = [os.path.join(here, "linters")]
|
|
return cli.run(**lintargs)
|
|
|
|
return inner
|
|
|
|
|
|
def test_cli_with_ascii_encoding(run, monkeypatch, capfd):
|
|
cmd = [sys.executable, "runcli.py", "-l=string", "-f=stylish", "files/foobar.js"]
|
|
env = os.environ.copy()
|
|
env["PYTHONPATH"] = os.pathsep.join(sys.path)
|
|
env["PYTHONIOENCODING"] = "ascii"
|
|
proc = subprocess.Popen(
|
|
cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
cwd=here,
|
|
env=env,
|
|
universal_newlines=True,
|
|
)
|
|
out = proc.communicate()[0]
|
|
assert "Traceback" not in out
|
|
|
|
|
|
def test_cli_run_with_fix(run, capfd):
|
|
ret = run(["-f", "json", "--fix", "--linter", "external"])
|
|
out, err = capfd.readouterr()
|
|
assert ret == 0
|
|
assert out.endswith("{}\n")
|
|
|
|
|
|
@pytest.mark.skipif(not which("echo"), reason="No `echo` executable found.")
|
|
def test_cli_run_with_edit(run, parser, capfd):
|
|
os.environ["EDITOR"] = "echo"
|
|
|
|
ret = run(["-f", "compact", "--edit", "--linter", "external"])
|
|
out, err = capfd.readouterr()
|
|
out = out.splitlines()
|
|
assert ret == 1
|
|
assert out[0].endswith("foobar.js") # from the `echo` editor
|
|
assert "foobar.js: line 1, col 1, Error" in out[1]
|
|
assert "foobar.js: line 2, col 1, Error" in out[2]
|
|
assert "2 problems" in out[-1]
|
|
assert len(out) == 5
|
|
|
|
del os.environ["EDITOR"]
|
|
with pytest.raises(SystemExit):
|
|
parser.parse_args(["--edit"])
|
|
|
|
|
|
def test_cli_run_with_setup(run, capfd):
|
|
# implicitly call setup
|
|
ret = run(["-l", "setup", "-l", "setupfailed", "-l", "setupraised"])
|
|
out, err = capfd.readouterr()
|
|
assert "setup passed" in out
|
|
assert "setup failed" in out
|
|
assert "setup raised" in out
|
|
assert ret == 1
|
|
|
|
# explicitly call setup
|
|
ret = run(["-l", "setup", "-l", "setupfailed", "-l", "setupraised", "--setup"])
|
|
out, err = capfd.readouterr()
|
|
assert "setup passed" in out
|
|
assert "setup failed" in out
|
|
assert "setup raised" in out
|
|
assert ret == 1
|
|
|
|
|
|
def test_cli_for_exclude_list(run, monkeypatch, capfd):
|
|
ret = run(["-l", "excludes", "--check-exclude-list"])
|
|
out, err = capfd.readouterr()
|
|
|
|
assert "**/foobar.js" in out
|
|
assert (
|
|
"The following list of paths are now green and can be removed from the exclude list:"
|
|
in out
|
|
)
|
|
|
|
ret = run(["-l", "excludes_empty", "--check-exclude-list"])
|
|
out, err = capfd.readouterr()
|
|
|
|
assert "No path in the exclude list is green." in out
|
|
assert ret == 1
|
|
|
|
|
|
def test_cli_run_with_wrong_linters(run, capfd):
|
|
run(["-l", "external", "-l", "foobar"])
|
|
out, err = capfd.readouterr()
|
|
|
|
# Check if it identifies foobar as invalid linter
|
|
assert "A failure occurred in the foobar linter." in out
|
|
|
|
# Check for exception message
|
|
assert "Invalid linters given, run again using valid linters or no linters" in out
|
|
|
|
|
|
def test_cli_run_with_stdin_filename(run, filedir, capfd, monkeypatch, tmp_path):
|
|
for arg in ("bar.txt", "--workdir", "--outgoing", "--rev=abc"):
|
|
with pytest.raises(SystemExit):
|
|
run(["--stdin-filename=foo.txt", arg])
|
|
|
|
capfd.readouterr()
|
|
monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"foobar\n")))
|
|
run(["-l", "string", f"--stdin-filename={filedir}/foobar.py"])
|
|
out, err = capfd.readouterr()
|
|
assert out == "✖ 0 problems (0 errors, 0 warnings, 0 fixed)\n"
|
|
|
|
monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"foobar\n")))
|
|
run(["-l", "string", f"--stdin-filename={filedir}/foobar.py", "--dump-stdin-file"])
|
|
out, err = capfd.readouterr()
|
|
assert out == "foobar\n"
|
|
|
|
monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"foobar\n")))
|
|
run(["-l", "string", f"--stdin-filename={filedir}/foobar.py", "--fix"])
|
|
out, err = capfd.readouterr()
|
|
assert out == "foobar\n"
|
|
|
|
monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"foobar\n")))
|
|
tmpfile = tmp_path / "temp"
|
|
run(
|
|
[
|
|
"-l",
|
|
"string",
|
|
f"--stdin-filename={filedir}/foobar.py",
|
|
"--dump-stdin-file",
|
|
str(tmpfile),
|
|
]
|
|
)
|
|
out, err = capfd.readouterr()
|
|
assert out == ""
|
|
assert tmpfile.read_text() == "foobar\n"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
mozunit.main()
|