gecko-dev/python/mozlint/test/test_roller.py

141 строка
3.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/.
from __future__ import absolute_import
import os
Bug 1430825 - [mozlint] Split work up by paths instead of by linters, r=standard8 The initial motivation for this patch, was to prevent command lines that are too long on Windows. To that end, there is a cap to the number of paths that can be run per job. For now that cap is set to 50. This will allow for an average path length of 160 characters, which should be sufficient with room to spare. But another big benefit of this patch is that we are running more things in parallel. Previously, mozlint ran each linter in its own subprocess, but that's it. If running eslint is 90% of the work, it'll still only get a single process. This means we are wasting cores as soon as the other linters are finished. This patch chunks the number of specified paths such that there will be N*L jobs where 'N' is the number of cores and 'L' is the number of linters. This means even when there's a dominant linter, we'll be making better use of our resources. This isn't perfect of course, as some paths might contain a small number of files, and some will contain a very large number of files. But it's a start A limitation to this approach is that if there are fewer paths specified than there are cores, we won't schedule enough jobs per linter to use those extra cores. One idea might be to expand specified directories and individually list all the paths under the directory. But this has some hairy edge cases that would be tough to catch. Doing this in a non-hacky way would also require a medium scale refactor. So I propose further parallelization efforts be destined for follow-ups. MozReview-Commit-ID: JRRu13AFaii --HG-- extra : rebase_source : 242fb71fe0af8bd2a981bd10a7216bb897fe00ac
2018-01-17 00:01:20 +03:00
import platform
import sys
import mozunit
import pytest
from mozlint import ResultContainer
from mozlint.errors import LintersNotConfigured, LintException
here = os.path.abspath(os.path.dirname(__file__))
Bug 1288432 - [mozlint] Use yaml for lint definitions and separate implementation of external linters, r=bc Rather than using .lint.py files that contain a LINTER object, linter definitions are now in standalone .yml files. In the case of external linters that need to run python code, the payload is now of the form: <module path>:<object path> The <module path> is the import path to the module, and <object path> is the callable object to use within that module. It is up to the consumer of mozlint to ensure the <module path> lives on sys.path. For example, if an external lint's function lives in package 'foo', file 'bar.py' and function 'lint', the payload would read: foo.bar:lint This mechanism was borrowed from taskcluster. MozReview-Commit-ID: AIsfbVmozy4 --HG-- rename : python/mozlint/test/linters/badreturncode.lint.py => python/mozlint/test/linters/badreturncode.yml rename : python/mozlint/test/linters/explicit_path.lint.py => python/mozlint/test/linters/explicit_path.yml rename : python/mozlint/test/linters/external.lint.py => python/mozlint/test/linters/external.yml rename : python/mozlint/test/linters/invalid_exclude.lint.py => python/mozlint/test/linters/invalid_exclude.yml rename : python/mozlint/test/linters/invalid_extension.lnt => python/mozlint/test/linters/invalid_extension.ym rename : python/mozlint/test/linters/invalid_include.lint.py => python/mozlint/test/linters/invalid_include.yml rename : python/mozlint/test/linters/invalid_type.lint.py => python/mozlint/test/linters/invalid_type.yml rename : python/mozlint/test/linters/missing_attrs.lint.py => python/mozlint/test/linters/missing_attrs.yml rename : python/mozlint/test/linters/missing_definition.lint.py => python/mozlint/test/linters/missing_definition.yml rename : python/mozlint/test/linters/raises.lint.py => python/mozlint/test/linters/raises.yml rename : python/mozlint/test/linters/regex.lint.py => python/mozlint/test/linters/regex.yml rename : python/mozlint/test/linters/string.lint.py => python/mozlint/test/linters/string.yml rename : python/mozlint/test/linters/structured.lint.py => python/mozlint/test/linters/structured.yml extra : rebase_source : bda3926712234123355c5af71c6453ce869b19fc
2017-06-02 16:49:26 +03:00
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)
result = lint.roll(files)
assert len(result) == 1
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):
Bug 1288432 - [mozlint] Use yaml for lint definitions and separate implementation of external linters, r=bc Rather than using .lint.py files that contain a LINTER object, linter definitions are now in standalone .yml files. In the case of external linters that need to run python code, the payload is now of the form: <module path>:<object path> The <module path> is the import path to the module, and <object path> is the callable object to use within that module. It is up to the consumer of mozlint to ensure the <module path> lives on sys.path. For example, if an external lint's function lives in package 'foo', file 'bar.py' and function 'lint', the payload would read: foo.bar:lint This mechanism was borrowed from taskcluster. MozReview-Commit-ID: AIsfbVmozy4 --HG-- rename : python/mozlint/test/linters/badreturncode.lint.py => python/mozlint/test/linters/badreturncode.yml rename : python/mozlint/test/linters/explicit_path.lint.py => python/mozlint/test/linters/explicit_path.yml rename : python/mozlint/test/linters/external.lint.py => python/mozlint/test/linters/external.yml rename : python/mozlint/test/linters/invalid_exclude.lint.py => python/mozlint/test/linters/invalid_exclude.yml rename : python/mozlint/test/linters/invalid_extension.lnt => python/mozlint/test/linters/invalid_extension.ym rename : python/mozlint/test/linters/invalid_include.lint.py => python/mozlint/test/linters/invalid_include.yml rename : python/mozlint/test/linters/invalid_type.lint.py => python/mozlint/test/linters/invalid_type.yml rename : python/mozlint/test/linters/missing_attrs.lint.py => python/mozlint/test/linters/missing_attrs.yml rename : python/mozlint/test/linters/missing_definition.lint.py => python/mozlint/test/linters/missing_definition.yml rename : python/mozlint/test/linters/raises.lint.py => python/mozlint/test/linters/raises.yml rename : python/mozlint/test/linters/regex.lint.py => python/mozlint/test/linters/regex.yml rename : python/mozlint/test/linters/string.lint.py => python/mozlint/test/linters/string.yml rename : python/mozlint/test/linters/structured.lint.py => python/mozlint/test/linters/structured.yml extra : rebase_source : bda3926712234123355c5af71c6453ce869b19fc
2017-06-02 16:49:26 +03:00
lint.read(os.path.join(lintdir, 'raises.yml'))
# suppress printed traceback from test output
old_stderr = sys.stderr
sys.stderr = open(os.devnull, 'w')
with pytest.raises(LintException):
lint.roll(files)
sys.stderr = old_stderr
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):
Bug 1288432 - [mozlint] Use yaml for lint definitions and separate implementation of external linters, r=bc Rather than using .lint.py files that contain a LINTER object, linter definitions are now in standalone .yml files. In the case of external linters that need to run python code, the payload is now of the form: <module path>:<object path> The <module path> is the import path to the module, and <object path> is the callable object to use within that module. It is up to the consumer of mozlint to ensure the <module path> lives on sys.path. For example, if an external lint's function lives in package 'foo', file 'bar.py' and function 'lint', the payload would read: foo.bar:lint This mechanism was borrowed from taskcluster. MozReview-Commit-ID: AIsfbVmozy4 --HG-- rename : python/mozlint/test/linters/badreturncode.lint.py => python/mozlint/test/linters/badreturncode.yml rename : python/mozlint/test/linters/explicit_path.lint.py => python/mozlint/test/linters/explicit_path.yml rename : python/mozlint/test/linters/external.lint.py => python/mozlint/test/linters/external.yml rename : python/mozlint/test/linters/invalid_exclude.lint.py => python/mozlint/test/linters/invalid_exclude.yml rename : python/mozlint/test/linters/invalid_extension.lnt => python/mozlint/test/linters/invalid_extension.ym rename : python/mozlint/test/linters/invalid_include.lint.py => python/mozlint/test/linters/invalid_include.yml rename : python/mozlint/test/linters/invalid_type.lint.py => python/mozlint/test/linters/invalid_type.yml rename : python/mozlint/test/linters/missing_attrs.lint.py => python/mozlint/test/linters/missing_attrs.yml rename : python/mozlint/test/linters/missing_definition.lint.py => python/mozlint/test/linters/missing_definition.yml rename : python/mozlint/test/linters/raises.lint.py => python/mozlint/test/linters/raises.yml rename : python/mozlint/test/linters/regex.lint.py => python/mozlint/test/linters/regex.yml rename : python/mozlint/test/linters/string.lint.py => python/mozlint/test/linters/string.yml rename : python/mozlint/test/linters/structured.lint.py => python/mozlint/test/linters/structured.yml extra : rebase_source : bda3926712234123355c5af71c6453ce869b19fc
2017-06-02 16:49:26 +03:00
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):
Bug 1288432 - [mozlint] Use yaml for lint definitions and separate implementation of external linters, r=bc Rather than using .lint.py files that contain a LINTER object, linter definitions are now in standalone .yml files. In the case of external linters that need to run python code, the payload is now of the form: <module path>:<object path> The <module path> is the import path to the module, and <object path> is the callable object to use within that module. It is up to the consumer of mozlint to ensure the <module path> lives on sys.path. For example, if an external lint's function lives in package 'foo', file 'bar.py' and function 'lint', the payload would read: foo.bar:lint This mechanism was borrowed from taskcluster. MozReview-Commit-ID: AIsfbVmozy4 --HG-- rename : python/mozlint/test/linters/badreturncode.lint.py => python/mozlint/test/linters/badreturncode.yml rename : python/mozlint/test/linters/explicit_path.lint.py => python/mozlint/test/linters/explicit_path.yml rename : python/mozlint/test/linters/external.lint.py => python/mozlint/test/linters/external.yml rename : python/mozlint/test/linters/invalid_exclude.lint.py => python/mozlint/test/linters/invalid_exclude.yml rename : python/mozlint/test/linters/invalid_extension.lnt => python/mozlint/test/linters/invalid_extension.ym rename : python/mozlint/test/linters/invalid_include.lint.py => python/mozlint/test/linters/invalid_include.yml rename : python/mozlint/test/linters/invalid_type.lint.py => python/mozlint/test/linters/invalid_type.yml rename : python/mozlint/test/linters/missing_attrs.lint.py => python/mozlint/test/linters/missing_attrs.yml rename : python/mozlint/test/linters/missing_definition.lint.py => python/mozlint/test/linters/missing_definition.yml rename : python/mozlint/test/linters/raises.lint.py => python/mozlint/test/linters/raises.yml rename : python/mozlint/test/linters/regex.lint.py => python/mozlint/test/linters/regex.yml rename : python/mozlint/test/linters/string.lint.py => python/mozlint/test/linters/string.yml rename : python/mozlint/test/linters/structured.lint.py => python/mozlint/test/linters/structured.yml extra : rebase_source : bda3926712234123355c5af71c6453ce869b19fc
2017-06-02 16:49:26 +03:00
lint.read(os.path.join(lintdir, 'badreturncode.yml'))
assert lint.failed == set([])
Bug 1430825 - [mozlint] Split work up by paths instead of by linters, r=standard8 The initial motivation for this patch, was to prevent command lines that are too long on Windows. To that end, there is a cap to the number of paths that can be run per job. For now that cap is set to 50. This will allow for an average path length of 160 characters, which should be sufficient with room to spare. But another big benefit of this patch is that we are running more things in parallel. Previously, mozlint ran each linter in its own subprocess, but that's it. If running eslint is 90% of the work, it'll still only get a single process. This means we are wasting cores as soon as the other linters are finished. This patch chunks the number of specified paths such that there will be N*L jobs where 'N' is the number of cores and 'L' is the number of linters. This means even when there's a dominant linter, we'll be making better use of our resources. This isn't perfect of course, as some paths might contain a small number of files, and some will contain a very large number of files. But it's a start A limitation to this approach is that if there are fewer paths specified than there are cores, we won't schedule enough jobs per linter to use those extra cores. One idea might be to expand specified directories and individually list all the paths under the directory. But this has some hairy edge cases that would be tough to catch. Doing this in a non-hacky way would also require a medium scale refactor. So I propose further parallelization efforts be destined for follow-ups. MozReview-Commit-ID: JRRu13AFaii --HG-- extra : rebase_source : 242fb71fe0af8bd2a981bd10a7216bb897fe00ac
2018-01-17 00:01:20 +03:00
result = lint.roll(files, num_procs=1)
assert len(result) == 0
assert lint.failed == set(['BadReturnCodeLinter'])
Bug 1430825 - [mozlint] Split work up by paths instead of by linters, r=standard8 The initial motivation for this patch, was to prevent command lines that are too long on Windows. To that end, there is a cap to the number of paths that can be run per job. For now that cap is set to 50. This will allow for an average path length of 160 characters, which should be sufficient with room to spare. But another big benefit of this patch is that we are running more things in parallel. Previously, mozlint ran each linter in its own subprocess, but that's it. If running eslint is 90% of the work, it'll still only get a single process. This means we are wasting cores as soon as the other linters are finished. This patch chunks the number of specified paths such that there will be N*L jobs where 'N' is the number of cores and 'L' is the number of linters. This means even when there's a dominant linter, we'll be making better use of our resources. This isn't perfect of course, as some paths might contain a small number of files, and some will contain a very large number of files. But it's a start A limitation to this approach is that if there are fewer paths specified than there are cores, we won't schedule enough jobs per linter to use those extra cores. One idea might be to expand specified directories and individually list all the paths under the directory. But this has some hairy edge cases that would be tough to catch. Doing this in a non-hacky way would also require a medium scale refactor. So I propose further parallelization efforts be destined for follow-ups. MozReview-Commit-ID: JRRu13AFaii --HG-- extra : rebase_source : 242fb71fe0af8bd2a981bd10a7216bb897fe00ac
2018-01-17 00:01:20 +03:00
def fake_run_linters(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_linters', fake_run_linters)
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_linters', fake_run_linters)
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
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 == set(['SetupFailedLinter', 'SetupRaisedLinter'])
if __name__ == '__main__':
mozunit.main()