Skip to content

Commit 9790791

Browse files
committed
[lint] Add a distinct exit code for uncaught exceptions
CPython exits with 1 when there's an uncaught exception; this is ambiguous with us exiting with 1 when there's lint errors. When processing the output (e.g., as JSON) it would be good to know if we just have lint errors or if the linter failed to run to completion.
1 parent edefd0e commit 9790791

2 files changed

Lines changed: 45 additions & 3 deletions

File tree

tools/lint/lint.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import subprocess
1010
import sys
1111
import tempfile
12+
import traceback
1213
from collections import defaultdict
1314
from typing import (Any, Callable, Dict, IO, Iterable, List, Optional, Sequence, Set, Text, Tuple,
1415
Type, TypeVar)
@@ -1146,8 +1147,17 @@ def all_paths_lints() -> Any:
11461147
return paths
11471148

11481149

1149-
if __name__ == "__main__":
1150-
args = create_parser().parse_args()
1151-
error_count = main(**vars(args))
1150+
def _run_main(argv: List[Text]) -> None:
1151+
try:
1152+
error_count = main(**vars(create_parser().parse_args(argv)))
1153+
except SystemExit:
1154+
raise
1155+
except Exception:
1156+
traceback.print_exc()
1157+
sys.exit(3)
11521158
if error_count > 0:
11531159
sys.exit(1)
1160+
1161+
1162+
if __name__ == "__main__":
1163+
_run_main(sys.argv[1:])

tools/lint/tests/test_lint.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import sys
66
from unittest import mock
77

8+
import pytest
9+
810
from ...localpaths import repo_root
911
from .. import lint as lint_mod
1012
from ..lint import filter_ignorelist_errors, parse_ignorelist, lint, create_parser
@@ -438,3 +440,33 @@ def test_main_all():
438440
m.assert_called_once_with(repo_root, ['foo', 'bar'], "normal", None, None, 0)
439441
finally:
440442
sys.argv = orig_argv
443+
444+
445+
446+
def test_run_main_no_errors_returns():
447+
with mock.patch.object(lint_mod, 'main', return_value=0):
448+
lint_mod._run_main(['--all']) # should not raise
449+
450+
451+
def test_run_main_errors_exits_1():
452+
with mock.patch.object(lint_mod, 'main', return_value=5):
453+
with pytest.raises(SystemExit) as exc_info:
454+
lint_mod._run_main(['--all'])
455+
assert exc_info.value.code == 1
456+
457+
458+
def test_run_main_argument_error_exits_2():
459+
with pytest.raises(SystemExit) as exc_info:
460+
lint_mod._run_main(['--json', '--markdown'])
461+
assert exc_info.value.code == 2
462+
463+
464+
def test_run_main_exception_exits_3(capsys):
465+
with mock.patch.object(lint_mod, 'main', side_effect=RuntimeError('simulated crash')):
466+
with pytest.raises(SystemExit) as exc_info:
467+
lint_mod._run_main(['--all'])
468+
assert exc_info.value.code == 3
469+
470+
captured = capsys.readouterr()
471+
assert 'Traceback (most recent call last):' in captured.err
472+
assert 'RuntimeError: simulated crash' in captured.err

0 commit comments

Comments
 (0)