Skip to content

Commit 649c875

Browse files
authored
Issue 1554: add progress reporting (#10178)
1 parent 80f2946 commit 649c875

File tree

5 files changed

+123
-10
lines changed

5 files changed

+123
-10
lines changed

pylint/config/config_initialization.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from pylint.lint import PyLinter
2424

2525

26-
def _config_initialization(
26+
def _config_initialization( # pylint: disable=too-many-statements
2727
linter: PyLinter,
2828
args_list: list[str],
2929
reporter: reporters.BaseReporter | reporters.MultiReporter | None = None,
@@ -33,6 +33,7 @@ def _config_initialization(
3333
"""Parse all available options, read config files and command line arguments and
3434
set options accordingly.
3535
"""
36+
linter.verbose = verbose_mode
3637
config_file = Path(config_file) if config_file else None
3738

3839
# Set the current module to the configuration file

pylint/lint/pylinter.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
)
5555
from pylint.message import Message, MessageDefinition, MessageDefinitionStore
5656
from pylint.reporters.base_reporter import BaseReporter
57+
from pylint.reporters.progress_reporters import ProgressReporter
5758
from pylint.reporters.text import TextReporter
5859
from pylint.reporters.ureports import nodes as report_nodes
5960
from pylint.typing import (
@@ -323,8 +324,7 @@ def __init__(
323324
self.option_groups_descs[opt_group[0]] = opt_group[1]
324325
self._option_groups: tuple[tuple[str, str], ...] = (
325326
*option_groups,
326-
("Messages control", "Options controlling analysis messages"),
327-
("Reports", "Options related to output formatting and reporting"),
327+
*PyLinter.option_groups_descs.items(),
328328
)
329329
self.fail_on_symbols: list[str] = []
330330
"""List of message symbols on which pylint should fail, set by --fail-on."""
@@ -354,6 +354,7 @@ def __init__(
354354
self.current_file: str | None = None
355355
self._ignore_file = False
356356
self._ignore_paths: list[Pattern[str]] = []
357+
self.verbose = False
357358

358359
self.register_checker(self)
359360

@@ -685,6 +686,8 @@ def check(self, files_or_modules: Sequence[str]) -> None:
685686
sys.path = original_sys_path
686687
return
687688

689+
progress_reporter = ProgressReporter(self.verbose)
690+
688691
# 1) Get all FileItems
689692
with augmented_sys_path(extra_packages_paths):
690693
if self.config.from_stdin:
@@ -698,18 +701,26 @@ def check(self, files_or_modules: Sequence[str]) -> None:
698701
with augmented_sys_path(extra_packages_paths):
699702
with self._astroid_module_checker() as check_astroid_module:
700703
# 2) Get the AST for each FileItem
701-
ast_per_fileitem = self._get_asts(fileitems, data)
704+
ast_per_fileitem = self._get_asts(fileitems, data, progress_reporter)
702705

703706
# 3) Lint each ast
704-
self._lint_files(ast_per_fileitem, check_astroid_module)
707+
self._lint_files(
708+
ast_per_fileitem, check_astroid_module, progress_reporter
709+
)
705710

706711
def _get_asts(
707-
self, fileitems: Iterator[FileItem], data: str | None
712+
self,
713+
fileitems: Iterator[FileItem],
714+
data: str | None,
715+
progress_reporter: ProgressReporter,
708716
) -> dict[FileItem, nodes.Module | None]:
709717
"""Get the AST for all given FileItems."""
710718
ast_per_fileitem: dict[FileItem, nodes.Module | None] = {}
711719

720+
progress_reporter.start_get_asts()
721+
712722
for fileitem in fileitems:
723+
progress_reporter.get_ast_for_file(fileitem.filepath)
713724
self.set_current_module(fileitem.name, fileitem.filepath)
714725

715726
try:
@@ -743,9 +754,12 @@ def _lint_files(
743754
self,
744755
ast_mapping: dict[FileItem, nodes.Module | None],
745756
check_astroid_module: Callable[[nodes.Module], bool | None],
757+
progress_reporter: ProgressReporter,
746758
) -> None:
747759
"""Lint all AST modules from a mapping.."""
760+
progress_reporter.start_linting()
748761
for fileitem, module in ast_mapping.items():
762+
progress_reporter.lint_file(fileitem.filepath)
749763
if module is None:
750764
continue
751765
try:
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
6+
class ProgressReporter:
7+
"""Progress reporter."""
8+
9+
def __init__(self, is_verbose: bool = True) -> None:
10+
self._is_verbose = is_verbose
11+
self._ast_count = 0
12+
self._lint_counter = 0
13+
14+
def start_get_asts(self) -> None:
15+
self._print_message("Get ASTs.")
16+
17+
def get_ast_for_file(self, filename: str) -> None:
18+
self._ast_count += 1
19+
self._print_message(f"AST for {filename}")
20+
21+
def start_linting(self) -> None:
22+
self._print_message(f"Linting {self._ast_count} modules.")
23+
24+
def lint_file(self, filename: str) -> None:
25+
self._lint_counter += 1
26+
self._print_message(f"{filename} ({self._lint_counter} of {self._ast_count})")
27+
28+
def _print_message(self, msg: str) -> None:
29+
"""Display progress message."""
30+
if self._is_verbose:
31+
print(msg, flush=True)

tests/test_check_parallel.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from pylint.lint.parallel import _worker_check_single_file as worker_check_single_file
3030
from pylint.lint.parallel import _worker_initialize as worker_initialize
3131
from pylint.lint.parallel import check_parallel
32+
from pylint.reporters.progress_reporters import ProgressReporter
3233
from pylint.testutils import GenericTestReporter as Reporter
3334
from pylint.testutils.utils import _test_cwd
3435
from pylint.typing import FileItem
@@ -506,6 +507,8 @@ def test_compare_workers_to_single_proc(
506507

507508
file_infos = _gen_file_datas(num_files)
508509

510+
progress_reporter = ProgressReporter(is_verbose=False)
511+
509512
# Loop for single-proc and mult-proc, so we can ensure the same linter-config
510513
for do_single_proc in range(2):
511514
linter = PyLinter(reporter=Reporter())
@@ -523,9 +526,13 @@ def test_compare_workers_to_single_proc(
523526
assert (
524527
linter.config.jobs == 1
525528
), "jobs>1 are ignored when calling _lint_files"
526-
ast_mapping = linter._get_asts(iter(file_infos), None)
529+
ast_mapping = linter._get_asts(
530+
iter(file_infos), None, progress_reporter
531+
)
527532
with linter._astroid_module_checker() as check_astroid_module:
528-
linter._lint_files(ast_mapping, check_astroid_module)
533+
linter._lint_files(
534+
ast_mapping, check_astroid_module, progress_reporter
535+
)
529536
assert linter.msg_status == 0, "We should not fail the lint"
530537
stats_single_proc = linter.stats
531538
else:
@@ -585,14 +592,20 @@ def test_map_reduce(self, num_files: int, num_jobs: int, num_checkers: int) -> N
585592
if num_checkers > 2:
586593
linter.register_checker(ThirdParallelTestChecker(linter))
587594

595+
progress_reporter = ProgressReporter(is_verbose=False)
596+
588597
if do_single_proc:
589598
# establish the baseline
590599
assert (
591600
linter.config.jobs == 1
592601
), "jobs>1 are ignored when calling _lint_files"
593-
ast_mapping = linter._get_asts(iter(file_infos), None)
602+
ast_mapping = linter._get_asts(
603+
iter(file_infos), None, progress_reporter
604+
)
594605
with linter._astroid_module_checker() as check_astroid_module:
595-
linter._lint_files(ast_mapping, check_astroid_module)
606+
linter._lint_files(
607+
ast_mapping, check_astroid_module, progress_reporter
608+
)
596609
stats_single_proc = linter.stats
597610
else:
598611
check_parallel(

tests/test_self.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,60 @@ def test_wrong_import_position_when_others_disabled(self) -> None:
341341
actual_output = actual_output[actual_output.find("\n") :]
342342
assert self._clean_paths(expected_output.strip()) == actual_output.strip()
343343

344+
def test_progress_reporting(self) -> None:
345+
module1 = join(HERE, "regrtest_data", "import_something.py")
346+
module2 = join(HERE, "regrtest_data", "wrong_import_position.py")
347+
args = [
348+
module2,
349+
module1,
350+
"--disable=all",
351+
"--enable=wrong-import-position",
352+
"--verbose",
353+
"-rn",
354+
"-sn",
355+
]
356+
out = StringIO()
357+
self._run_pylint(args, out=out)
358+
actual_output = self._clean_paths(out.getvalue().strip())
359+
360+
expected_output = textwrap.dedent(
361+
f"""
362+
Using config file pylint/testutils/testing_pylintrc
363+
Get ASTs.
364+
AST for {module2}
365+
AST for {module1}
366+
Linting 2 modules.
367+
{module2} (1 of 2)
368+
************* Module wrong_import_position
369+
{module2}:11:0: C0413: Import "import os" should be placed at the top of the module (wrong-import-position)
370+
{module1} (2 of 2)
371+
"""
372+
)
373+
assert self._clean_paths(expected_output.strip()) == actual_output.strip()
374+
375+
def test_progress_reporting_not_shown_if_not_verbose(self) -> None:
376+
module1 = join(HERE, "regrtest_data", "import_something.py")
377+
module2 = join(HERE, "regrtest_data", "wrong_import_position.py")
378+
args = [
379+
module2,
380+
module1,
381+
"--disable=all",
382+
"--enable=wrong-import-position",
383+
"-rn",
384+
"-sn",
385+
]
386+
out = StringIO()
387+
self._run_pylint(args, out=out)
388+
actual_output = self._clean_paths(out.getvalue().strip())
389+
390+
expected_output = textwrap.dedent(
391+
f"""
392+
************* Module wrong_import_position
393+
{module2}:11:0: C0413: Import "import os" should be placed at the top of the module (wrong-import-position)
394+
"""
395+
)
396+
assert self._clean_paths(expected_output.strip()) == actual_output.strip()
397+
344398
def test_type_annotation_names(self) -> None:
345399
"""Test resetting the `_type_annotation_names` list to `[]` when leaving a module.
346400

0 commit comments

Comments
 (0)