gecko-dev/python/mozlint/test/test_roller.py
Andrew Halberstadt 84797ec831 Bug 1373368 - [mozlint] Lint whole tree if using --workdir/--outgoing and support-file was modified, r=standard8
Previously, using --workdir or --outgoing could miss errors when modifying a
support file since those could affect unmodified files.

This patch allows linters to define support-files in their .yml configuration.
If using --outgoing or --workdir and a file matching one of those patterns was
modified, we'll lint the entire tree.

MozReview-Commit-ID: CuGLYwQwiWr

--HG--
extra : rebase_source : 00d4107c41404f5e6ab05e0106d5cd377e25652f
2018-02-16 17:46:04 -05:00

198 lines
5.8 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/.
from __future__ import absolute_import
import os
import platform
import signal
import subprocess
import sys
import time
import mozunit
import pytest
from mozlint import ResultContainer
from mozlint.errors import LintersNotConfigured
here = os.path.abspath(os.path.dirname(__file__))
linters = ('string.yml', 'regex.yml', 'external.yml')
def test_roll_no_linters_configured(lint, files):
with pytest.raises(LintersNotConfigured):
lint.roll(files)
def test_roll_successful(lint, linters, files):
lint.read(linters)
assert lint.results is None
result = lint.roll(files)
assert len(result) == 1
assert lint.results == result
assert lint.failed == set([])
path = result.keys()[0]
assert os.path.basename(path) == 'foobar.js'
errors = result[path]
assert isinstance(errors, list)
assert len(errors) == 6
container = errors[0]
assert isinstance(container, ResultContainer)
assert container.rule == 'no-foobar'
def test_roll_catch_exception(lint, lintdir, files, capfd):
lint.read(os.path.join(lintdir, 'raises.yml'))
lint.roll(files) # assert not raises
out, err = capfd.readouterr()
assert 'LintException' in err
def test_roll_with_excluded_path(lint, linters, files):
lint.lintargs.update({'exclude': ['**/foobar.js']})
lint.read(linters)
result = lint.roll(files)
assert len(result) == 0
assert lint.failed == set([])
def test_roll_with_invalid_extension(lint, lintdir, filedir):
lint.read(os.path.join(lintdir, 'external.yml'))
result = lint.roll(os.path.join(filedir, 'foobar.py'))
assert len(result) == 0
assert lint.failed == set([])
def test_roll_with_failure_code(lint, lintdir, files):
lint.read(os.path.join(lintdir, 'badreturncode.yml'))
assert lint.failed is None
result = lint.roll(files, num_procs=1)
assert len(result) == 0
assert lint.failed == set(['BadReturnCodeLinter'])
def fake_run_worker(config, paths, **lintargs):
return {'count': [1]}, []
@pytest.mark.skipif(platform.system() == 'Windows',
reason="monkeypatch issues with multiprocessing on Windows")
@pytest.mark.parametrize('num_procs', [1, 4, 8, 16])
def test_number_of_jobs(monkeypatch, lint, linters, files, num_procs):
monkeypatch.setattr(sys.modules[lint.__module__], '_run_worker', fake_run_worker)
lint.read(linters)
num_jobs = len(lint.roll(files, num_procs=num_procs)['count'])
if len(files) >= num_procs:
assert num_jobs == num_procs * len(linters)
else:
assert num_jobs == len(files) * len(linters)
@pytest.mark.skipif(platform.system() == 'Windows',
reason="monkeypatch issues with multiprocessing on Windows")
@pytest.mark.parametrize('max_paths,expected_jobs', [(1, 12), (4, 6), (16, 6)])
def test_max_paths_per_job(monkeypatch, lint, linters, files, max_paths, expected_jobs):
monkeypatch.setattr(sys.modules[lint.__module__], '_run_worker', fake_run_worker)
files = files[:4]
assert len(files) == 4
linters = linters[:3]
assert len(linters) == 3
lint.MAX_PATHS_PER_JOB = max_paths
lint.read(linters)
num_jobs = len(lint.roll(files, num_procs=2)['count'])
assert num_jobs == expected_jobs
@pytest.mark.skipif(platform.system() == 'Windows',
reason="signal.CTRL_C_EVENT isn't causing a KeyboardInterrupt on Windows")
def test_keyboard_interrupt():
# We use two linters so we'll have two jobs. One (string.yml) will complete
# quickly. The other (slow.yml) will run slowly. This way the first worker
# will be be stuck blocking on the ProcessPoolExecutor._call_queue when the
# signal arrives and the other still be doing work.
cmd = [sys.executable, 'runcli.py', '-l=string', '-l=slow']
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=here)
time.sleep(1)
proc.send_signal(signal.SIGINT)
out = proc.communicate()[0]
assert 'warning: not all files were linted' in out
assert 'Traceback' not in out
def test_support_files(lint, lintdir, filedir, monkeypatch):
jobs = []
# Replace the original _generate_jobs with a new one that simply
# adds jobs to a list (and then doesn't return anything).
orig_generate_jobs = lint._generate_jobs
def fake_generate_jobs(*args, **kwargs):
jobs.extend([job[1] for job in orig_generate_jobs(*args, **kwargs)])
return []
monkeypatch.setattr(lint, '_generate_jobs', fake_generate_jobs)
linter_path = os.path.join(lintdir, 'support_files.yml')
lint.read(linter_path)
# Modified support files only lint entire root if --outgoing or --workdir
# are used.
path = os.path.join(filedir, 'foobar.js')
lint.mock_vcs([os.path.join(filedir, 'foobar.py')])
lint.roll(path)
assert jobs[0] == [path]
jobs = []
lint.roll(path, workdir=True)
assert jobs[0] == [lint.root]
jobs = []
lint.roll(path, outgoing=True)
assert jobs[0] == [lint.root]
# Lint config file is implicitly added as a support file
lint.mock_vcs([linter_path])
jobs = []
lint.roll(path, outgoing=True, workdir=True)
assert jobs[0] == [lint.root]
linters = ('setup.yml', 'setupfailed.yml', 'setupraised.yml')
def test_setup(lint, linters, filedir, capfd):
with pytest.raises(LintersNotConfigured):
lint.setup()
lint.read(linters)
lint.setup()
out, err = capfd.readouterr()
assert 'setup passed' in out
assert 'setup failed' in out
assert 'setup raised' in out
assert 'error: problem with lint setup, skipping' in out
assert lint.failed_setup == set(['SetupFailedLinter', 'SetupRaisedLinter'])
if __name__ == '__main__':
mozunit.main()