summaryrefslogtreecommitdiff
path: root/tests/reporters
diff options
context:
space:
mode:
authorPierre Sassoulas <pierre.sassoulas@gmail.com>2022-06-27 15:31:30 +0200
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2022-06-29 10:19:55 +0200
commit32985f75fa912823d08710db78ced5387c7c08a6 (patch)
tree31448314715d72c9290d9e35f7400dbcd0a1e87c /tests/reporters
parent480eae64d23931082850acd3a177b6d308e90916 (diff)
downloadpylint-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.py100
-rw-r--r--tests/reporters/unittest_reporting.py341
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)