Skip to content

Commit

Permalink
Refactor crash handling in pylinter and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Sassoulas committed Aug 7, 2021
1 parent 6b36245 commit fdcc9d2
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 55 deletions.
72 changes: 17 additions & 55 deletions pylint/lint/pylinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import collections
import contextlib
import datetime
import functools
import operator
import os
Expand All @@ -12,7 +11,6 @@
import traceback
import warnings
from io import TextIOWrapper
from pathlib import Path

import astroid
from astroid import AstroidError
Expand All @@ -26,7 +24,11 @@
report_messages_stats,
report_total_messages_stats,
)
from pylint.lint.utils import fix_import_path
from pylint.lint.utils import (
fix_import_path,
get_fatal_error_message,
prepare_crash_report,
)
from pylint.message import MessageDefinitionStore, MessagesHandlerMixIn
from pylint.reporters.ureports import nodes as report_nodes
from pylint.utils import ASTWalker, FileState, utils
Expand Down Expand Up @@ -1006,51 +1008,6 @@ def check_single_file(self, name, filepath, modname):
self.get_ast, check_astroid_module, name, filepath, modname
)

@staticmethod
def _prepare_crash_report(ex: Exception, filepath: str) -> str:
now = datetime.datetime.now().strftime("%Y-%m-%d-%H")
issue_template_path = Path(f"pylint-crash-{now}.txt")
with open(filepath, encoding="utf8") as f:
file_content = f.read()
template = ""
if not issue_template_path.exists():
template = """\
First, please verify that the bug is not already filled:
https://github.com/PyCQA/pylint/issues/
Then create a new crash issue:
https://github.com/PyCQA/pylint/issues/new?assignees=&labels=crash%2Cneeds+triage&template=BUG-REPORT.yml
"""
template += f"""\
Issue title:
Crash ``{ex}`` (if possible, be more specific about what made pylint crash)
Content:
When parsing the following file:
<!--
If sharing the code is not an option, please state so,
but providing only the stacktrace would still be helpful.
-->
```python
{file_content}
```
pylint crashed with a {ex.__class__.__name__} with the following stacktrace:
```
"""
with open(issue_template_path, "a", encoding="utf8") as f:
f.write(template)
traceback.print_exc(file=f)
f.write("\n```\n")
return (
f"Fatal error while checking '{filepath}'. "
f"Please open an issue in our bug tracker so we address this. "
f"There is a pre-filled template that you can use in '{issue_template_path}'."
)

def _check_files(self, get_ast, file_descrs):
"""Check all files from file_descrs
Expand All @@ -1062,19 +1019,24 @@ def _check_files(self, get_ast, file_descrs):
"""
with self._astroid_module_checker() as check_astroid_module:
for name, filepath, modname in file_descrs:
error = None
try:
self._check_file(
get_ast, check_astroid_module, name, filepath, modname
)
except AstroidError as ex:
self.add_message(
"astroid-error",
args=(filepath, self._prepare_crash_report(ex, filepath)),
)
except Exception as ex: # pylint: disable=broad-except
self.add_message(
"fatal", args=self._prepare_crash_report(ex, filepath)
error = ex
template_path = prepare_crash_report(
error, filepath, "pylint-crash-"
)
if error is not None:
msg = get_fatal_error_message(filepath, template_path)
if isinstance(error, AstroidError):
symbol = "astroid-error"
msg = (filepath, msg)
else:
symbol = "fatal"
self.add_message(symbol, args=msg)

def _check_file(self, get_ast, check_astroid_module, name, filepath, modname):
"""Check a file using the passed utility functions (get_ast and check_astroid_module)
Expand Down
52 changes: 52 additions & 0 deletions pylint/lint/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

import contextlib
import sys
import traceback
from datetime import datetime
from pathlib import Path

from pylint.lint.expand_modules import get_python_path

Expand All @@ -11,6 +14,55 @@ class ArgumentPreprocessingError(Exception):
"""Raised if an error occurs during argument preprocessing."""


def prepare_crash_report(ex: Exception, filepath: str, crash_file_prefix: str) -> Path:
now = datetime.now().strftime("%Y-%m-%d-%H")
issue_template_path = Path(f"{crash_file_prefix}{now}.txt").resolve()
with open(filepath, encoding="utf8") as f:
file_content = f.read()
template = ""
if not issue_template_path.exists():
template = """\
First, please verify that the bug is not already filled:
https://github.com/PyCQA/pylint/issues/
Then create a new crash issue:
https://github.com/PyCQA/pylint/issues/new?assignees=&labels=crash%2Cneeds+triage&template=BUG-REPORT.yml
"""
template += f"""\
Issue title:
Crash ``{ex}`` (if possible, be more specific about what made pylint crash)
Content:
When parsing the following file:
<!--
If sharing the code is not an option, please state so,
but providing only the stacktrace would still be helpful.
-->
```python
{file_content}
```
pylint crashed with a ``{ex.__class__.__name__}`` and with the following stacktrace:
```
"""
with open(issue_template_path, "a", encoding="utf8") as f:
f.write(template)
traceback.print_exc(file=f)
f.write("```\n")
return issue_template_path


def get_fatal_error_message(filepath: str, issue_template_path: Path) -> str:
return (
f"Fatal error while checking '{filepath}'. "
f"Please open an issue in our bug tracker so we address this. "
f"There is a pre-filled template that you can use in '{issue_template_path}'."
)


def preprocess_options(args, search_for):
"""look for some options (keys of <search_for>) which have to be processed
before others
Expand Down
29 changes: 29 additions & 0 deletions tests/lint/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pylint.lint.utils import get_fatal_error_message, prepare_crash_report


def test_prepare_crash_report(tmp_path):
exception_content = "Exmessage"
python_file = tmp_path / "myfile.py"
python_content = "from shadok import MagicFaucet"
with open(python_file, "w", encoding="utf8") as f:
f.write(python_content)
try:
raise Exception(exception_content)
except Exception as ex: # pylint: disable=broad-except
template_path = prepare_crash_report(ex, python_file, tmp_path)
assert str(tmp_path) in str(template_path)
with open(template_path, encoding="utf8") as f:
template_content = f.read()
assert python_content in template_content
assert exception_content in template_content
assert "in test_prepare_crash_report" in template_content
assert "raise Exception(exception_content)" in template_content


def test_get_fatal_error_message():
python_path = "mypath.py"
crash_path = "crash.txt"
msg = get_fatal_error_message(python_path, crash_path)
assert python_path in msg
assert crash_path in msg
assert "open an issue" in msg

0 comments on commit fdcc9d2

Please sign in to comment.