diff options
author | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2022-06-27 15:31:30 +0200 |
---|---|---|
committer | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2022-06-29 10:19:55 +0200 |
commit | 32985f75fa912823d08710db78ced5387c7c08a6 (patch) | |
tree | 31448314715d72c9290d9e35f7400dbcd0a1e87c /tests/reporters | |
parent | 480eae64d23931082850acd3a177b6d308e90916 (diff) | |
download | pylint-git-32985f75fa912823d08710db78ced5387c7c08a6.tar.gz |
[reporters] Move the reporter tests in 'tests.reporter'
Diffstat (limited to 'tests/reporters')
-rw-r--r-- | tests/reporters/unittest_json_reporter.py | 100 | ||||
-rw-r--r-- | tests/reporters/unittest_reporting.py | 341 |
2 files changed, 441 insertions, 0 deletions
diff --git a/tests/reporters/unittest_json_reporter.py b/tests/reporters/unittest_json_reporter.py new file mode 100644 index 000000000..2a0843e7f --- /dev/null +++ b/tests/reporters/unittest_json_reporter.py @@ -0,0 +1,100 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + +"""Test for the JSON reporter.""" + +from __future__ import annotations + +import json +from io import StringIO +from typing import Any + +from pylint import checkers +from pylint.lint import PyLinter +from pylint.reporters import JSONReporter +from pylint.reporters.ureports.nodes import EvaluationSection + +expected_score_message = "Expected score message" + + +def test_simple_json_output_no_score() -> None: + """Test JSON reporter with no score.""" + message = { + "msg": "line-too-long", + "line": 1, + "args": (1, 2), + "end_line": None, + "end_column": None, + } + expected = [ + { + "type": "convention", + "module": "0123", + "obj": "", + "line": 1, + "column": 0, + "endLine": None, + "endColumn": None, + "path": "0123", + "symbol": "line-too-long", + "message": "Line too long (1/2)", + "message-id": "C0301", + } + ] + report = get_linter_result(score=False, message=message) + assert len(report) == 1 + assert json.dumps(report) == json.dumps(expected) + + +def test_simple_json_output_no_score_with_end_line() -> None: + """Test JSON reporter with no score with end_line and end_column.""" + message = { + "msg": "line-too-long", + "line": 1, + "args": (1, 2), + "end_line": 1, + "end_column": 4, + } + expected = [ + { + "type": "convention", + "module": "0123", + "obj": "", + "line": 1, + "column": 0, + "endLine": 1, + "endColumn": 4, + "path": "0123", + "symbol": "line-too-long", + "message": "Line too long (1/2)", + "message-id": "C0301", + } + ] + report = get_linter_result(score=False, message=message) + assert len(report) == 1 + assert json.dumps(report) == json.dumps(expected) + + +def get_linter_result(score: bool, message: dict[str, Any]) -> list[dict[str, Any]]: + output = StringIO() + reporter = JSONReporter(output) + linter = PyLinter(reporter=reporter) + checkers.initialize(linter) + linter.config.persistent = 0 + linter.config.score = score + linter.open() + linter.set_current_module("0123") + linter.add_message( + message["msg"], + line=message["line"], + args=message["args"], + end_lineno=message["end_line"], + end_col_offset=message["end_column"], + ) + # we call those methods because we didn't actually run the checkers + if score: + reporter.display_reports(EvaluationSection(expected_score_message)) + reporter.display_messages(None) + report_result = json.loads(output.getvalue()) + return report_result diff --git a/tests/reporters/unittest_reporting.py b/tests/reporters/unittest_reporting.py new file mode 100644 index 000000000..ebc4a225f --- /dev/null +++ b/tests/reporters/unittest_reporting.py @@ -0,0 +1,341 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + +# pylint: disable=redefined-outer-name + +from __future__ import annotations + +import sys +import warnings +from contextlib import redirect_stdout +from io import StringIO +from json import dumps +from typing import TYPE_CHECKING + +import pytest + +from pylint import checkers +from pylint.lint import PyLinter +from pylint.reporters import BaseReporter +from pylint.reporters.text import ParseableTextReporter, TextReporter +from pylint.typing import FileItem + +if TYPE_CHECKING: + from pylint.reporters.ureports.nodes import Section + + +@pytest.fixture(scope="module") +def reporter(): + return TextReporter + + +@pytest.fixture(scope="module") +def disable(): + return ["I"] + + +def test_template_option(linter): + output = StringIO() + linter.reporter.out = output + linter.config.msg_template = "{msg_id}:{line:03d}" + linter.open() + linter.set_current_module("0123") + linter.add_message("C0301", line=1, args=(1, 2)) + linter.add_message("line-too-long", line=2, args=(3, 4)) + assert output.getvalue() == "************* Module 0123\nC0301:001\nC0301:002\n" + + +def test_template_option_default(linter) -> None: + """Test the default msg-template setting.""" + output = StringIO() + linter.reporter.out = output + linter.open() + linter.set_current_module("my_module") + linter.add_message("C0301", line=1, args=(1, 2)) + linter.add_message("line-too-long", line=2, args=(3, 4)) + + out_lines = output.getvalue().split("\n") + assert out_lines[1] == "my_module:1:0: C0301: Line too long (1/2) (line-too-long)" + assert out_lines[2] == "my_module:2:0: C0301: Line too long (3/4) (line-too-long)" + + +def test_template_option_end_line(linter) -> None: + """Test the msg-template option with end_line and end_column.""" + output = StringIO() + linter.reporter.out = output + linter.config.msg_template = ( + "{path}:{line}:{column}:{end_line}:{end_column}: {msg_id}: {msg} ({symbol})" + ) + linter.open() + linter.set_current_module("my_mod") + linter.add_message("C0301", line=1, args=(1, 2)) + linter.add_message( + "line-too-long", line=2, end_lineno=2, end_col_offset=4, args=(3, 4) + ) + + out_lines = output.getvalue().split("\n") + assert out_lines[1] == "my_mod:1:0::: C0301: Line too long (1/2) (line-too-long)" + assert out_lines[2] == "my_mod:2:0:2:4: C0301: Line too long (3/4) (line-too-long)" + + +def test_template_option_non_existing(linter) -> None: + """Test the msg-template option with non-existent options. + This makes sure that this option remains backwards compatible as new + parameters do not break on previous versions + """ + output = StringIO() + linter.reporter.out = output + linter.config.msg_template = ( + "{path}:{line}:{a_new_option}:({a_second_new_option:03d})" + ) + linter.open() + with pytest.warns(UserWarning) as records: + linter.set_current_module("my_mod") + assert len(records) == 2 + assert ( + "Don't recognize the argument 'a_new_option'" in records[0].message.args[0] + ) + assert ( + "Don't recognize the argument 'a_second_new_option'" + in records[1].message.args[0] + ) + + linter.add_message("C0301", line=1, args=(1, 2)) + linter.add_message( + "line-too-long", line=2, end_lineno=2, end_col_offset=4, args=(3, 4) + ) + + out_lines = output.getvalue().split("\n") + assert out_lines[1] == "my_mod:1::()" + assert out_lines[2] == "my_mod:2::()" + + +def test_deprecation_set_output(recwarn): + """TODO remove in 3.0.""" + reporter = BaseReporter() + # noinspection PyDeprecation + reporter.set_output(sys.stdout) + warning = recwarn.pop() + assert "set_output' will be removed in 3.0" in str(warning) + assert reporter.out == sys.stdout + + +def test_parseable_output_deprecated(): + with warnings.catch_warnings(record=True) as cm: + warnings.simplefilter("always") + ParseableTextReporter() + + assert len(cm) == 1 + assert isinstance(cm[0].message, DeprecationWarning) + + +def test_parseable_output_regression(): + output = StringIO() + with warnings.catch_warnings(record=True): + linter = PyLinter(reporter=ParseableTextReporter()) + + checkers.initialize(linter) + linter.config.persistent = 0 + linter.reporter.out = output + linter.set_option("output-format", "parseable") + linter.open() + linter.set_current_module("0123") + linter.add_message("line-too-long", line=1, args=(1, 2)) + assert ( + output.getvalue() == "************* Module 0123\n" + "0123:1: [C0301(line-too-long), ] " + "Line too long (1/2)\n" + ) + + +class NopReporter(BaseReporter): + name = "nop-reporter" + extension = "" + + def __init__(self, output=None): + super().__init__(output) + print("A NopReporter was initialized.", file=self.out) + + def writeln(self, string=""): + pass + + def _display(self, layout: Section) -> None: + pass + + +def test_multi_format_output(tmp_path): + text = StringIO(newline=None) + json = tmp_path / "somefile.json" + + source_file = tmp_path / "somemodule.py" + source_file.write_text('NOT_EMPTY = "This module is not empty"\n') + escaped_source_file = dumps(str(source_file)) + + nop_format = NopReporter.__module__ + "." + NopReporter.__name__ + formats = ",".join(["json:" + str(json), "text", nop_format]) + + with redirect_stdout(text): + linter = PyLinter() + linter.load_default_plugins() + linter.set_option("persistent", False) + linter.set_option("reports", True) + linter.set_option("score", True) + linter.set_option("score", True) + linter.set_option("output-format", formats) + + assert linter.reporter.linter is linter + with pytest.raises(NotImplementedError): + linter.reporter.out = text + + linter.open() + linter.check_single_file_item(FileItem("somemodule", source_file, "somemodule")) + linter.add_message("line-too-long", line=1, args=(1, 2)) + linter.generate_reports() + linter.reporter.writeln("direct output") + + # Ensure the output files are flushed and closed + linter.reporter.close_output_files() + del linter.reporter + + with open(json, encoding="utf-8") as f: + assert ( + f.read() == "[\n" + " {\n" + ' "type": "convention",\n' + ' "module": "somemodule",\n' + ' "obj": "",\n' + ' "line": 1,\n' + ' "column": 0,\n' + ' "endLine": null,\n' + ' "endColumn": null,\n' + f' "path": {escaped_source_file},\n' + ' "symbol": "missing-module-docstring",\n' + ' "message": "Missing module docstring",\n' + ' "message-id": "C0114"\n' + " },\n" + " {\n" + ' "type": "convention",\n' + ' "module": "somemodule",\n' + ' "obj": "",\n' + ' "line": 1,\n' + ' "column": 0,\n' + ' "endLine": null,\n' + ' "endColumn": null,\n' + f' "path": {escaped_source_file},\n' + ' "symbol": "line-too-long",\n' + ' "message": "Line too long (1/2)",\n' + ' "message-id": "C0301"\n' + " }\n" + "]\n" + "direct output\n" + ) + + assert ( + text.getvalue() == "A NopReporter was initialized.\n" + "************* Module somemodule\n" + f"{source_file}:1:0: C0114: Missing module docstring (missing-module-docstring)\n" + f"{source_file}:1:0: C0301: Line too long (1/2) (line-too-long)\n" + "\n" + "\n" + "Report\n" + "======\n" + "1 statements analysed.\n" + "\n" + "Statistics by type\n" + "------------------\n" + "\n" + "+---------+-------+-----------+-----------+------------+---------+\n" + "|type |number |old number |difference |%documented |%badname |\n" + "+=========+=======+===========+===========+============+=========+\n" + "|module |1 |NC |NC |0.00 |0.00 |\n" + "+---------+-------+-----------+-----------+------------+---------+\n" + "|class |0 |NC |NC |0 |0 |\n" + "+---------+-------+-----------+-----------+------------+---------+\n" + "|method |0 |NC |NC |0 |0 |\n" + "+---------+-------+-----------+-----------+------------+---------+\n" + "|function |0 |NC |NC |0 |0 |\n" + "+---------+-------+-----------+-----------+------------+---------+\n" + "\n" + "\n" + "\n" + "3 lines have been analyzed\n" + "\n" + "Raw metrics\n" + "-----------\n" + "\n" + "+----------+-------+------+---------+-----------+\n" + "|type |number |% |previous |difference |\n" + "+==========+=======+======+=========+===========+\n" + "|code |2 |66.67 |NC |NC |\n" + "+----------+-------+------+---------+-----------+\n" + "|docstring |0 |0.00 |NC |NC |\n" + "+----------+-------+------+---------+-----------+\n" + "|comment |0 |0.00 |NC |NC |\n" + "+----------+-------+------+---------+-----------+\n" + "|empty |1 |33.33 |NC |NC |\n" + "+----------+-------+------+---------+-----------+\n" + "\n" + "\n" + "\n" + "Duplication\n" + "-----------\n" + "\n" + "+-------------------------+------+---------+-----------+\n" + "| |now |previous |difference |\n" + "+=========================+======+=========+===========+\n" + "|nb duplicated lines |0 |NC |NC |\n" + "+-------------------------+------+---------+-----------+\n" + "|percent duplicated lines |0.000 |NC |NC |\n" + "+-------------------------+------+---------+-----------+\n" + "\n" + "\n" + "\n" + "Messages by category\n" + "--------------------\n" + "\n" + "+-----------+-------+---------+-----------+\n" + "|type |number |previous |difference |\n" + "+===========+=======+=========+===========+\n" + "|convention |2 |NC |NC |\n" + "+-----------+-------+---------+-----------+\n" + "|refactor |0 |NC |NC |\n" + "+-----------+-------+---------+-----------+\n" + "|warning |0 |NC |NC |\n" + "+-----------+-------+---------+-----------+\n" + "|error |0 |NC |NC |\n" + "+-----------+-------+---------+-----------+\n" + "\n" + "\n" + "\n" + "Messages\n" + "--------\n" + "\n" + "+-------------------------+------------+\n" + "|message id |occurrences |\n" + "+=========================+============+\n" + "|missing-module-docstring |1 |\n" + "+-------------------------+------------+\n" + "|line-too-long |1 |\n" + "+-------------------------+------------+\n" + "\n" + "\n" + "\n" + "\n" + "-----------------------------------\n" + "Your code has been rated at 0.00/10\n" + "\n" + "direct output\n" + ) + + +def test_display_results_is_renamed(): + class CustomReporter(TextReporter): + def _display(self, layout: Section) -> None: + return None + + reporter = CustomReporter() + with pytest.raises(AttributeError) as exc: + # pylint: disable=no-member + reporter.display_results() + assert "no attribute 'display_results'" in str(exc) |