blob: c0fdb61d4bd554d0d5ea906e5da4e9fa413571ed [file] [log] [blame]
# Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
# Copyright (c) 2014-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2014 Calin Don <calin.don@gmail.com>
# Copyright (c) 2014 Google, Inc.
# Copyright (c) 2014 Arun Persaud <arun@nubati.net>
# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
# Copyright (c) 2016-2017 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
# Copyright (c) 2019-2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Copyright (c) 2020 hippo91 <guillaume.peillex@gmail.com>
# Copyright (c) 2021 Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>
# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
# Copyright (c) 2021 ruro <ruro.ruro@ya.ru>
# 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
# pylint: disable=redefined-outer-name
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.interfaces import IReporter
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.set_output(output)
linter.set_option("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_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.set_output(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):
__implements__ = IReporter
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.set_option("persistent", False)
linter.set_option("output-format", formats)
linter.set_option("reports", True)
linter.set_option("score", True)
linter.load_default_plugins()
assert linter.reporter.linter is linter
with pytest.raises(NotImplementedError):
linter.reporter.set_output(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'
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'
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"
"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 -10.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)