Skip to content

Commit c264fb8

Browse files
Use dedicated exit code 3 when dead code is found (#319)
This addresses issue #308 by introducing a new class vulture.utils.ExitCode, which will encode the different exit codes Vulture can return. --------- Co-authored-by: Jendrik Seipp <[email protected]>
1 parent 0ba4ee7 commit c264fb8

File tree

9 files changed

+74
-30
lines changed

9 files changed

+74
-30
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 2.9 (unreleased)
2+
* Use exit code 3 when dead code is found (whosayn, #319).
3+
14
# 2.8 (2023-08-10)
25

36
* Add `UnicodeEncodeError` exception handling to `core.py` (milanbalazs, #299).

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,9 +316,9 @@ codes.
316316
| Exit code | Description |
317317
| --------- | ------------------------------------------------------------- |
318318
| 0 | No dead code found |
319-
| 1 | Dead code found |
320319
| 1 | Invalid input (file missing, syntax error, wrong encoding) |
321320
| 2 | Invalid command line arguments |
321+
| 3 | Dead code found |
322322

323323
## Similar programs
324324

tests/test_encoding.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import codecs
22

33
from . import v
4+
from vulture.utils import ExitCode
45

56
assert v # Silence pyflakes.
67

@@ -12,7 +13,7 @@ def test_encoding1(v):
1213
pass
1314
"""
1415
)
15-
assert not v.found_dead_code_or_error
16+
assert v.exit_code == ExitCode.NoDeadCode
1617

1718

1819
def test_encoding2(v):
@@ -23,7 +24,7 @@ def test_encoding2(v):
2324
pass
2425
"""
2526
)
26-
assert not v.found_dead_code_or_error
27+
assert v.exit_code == ExitCode.NoDeadCode
2728

2829

2930
def test_non_utf8_encoding(v, tmp_path):
@@ -34,7 +35,7 @@ def test_non_utf8_encoding(v, tmp_path):
3435
f.write(codecs.BOM_UTF16_LE)
3536
f.write(code.encode("utf_16_le"))
3637
v.scavenge([non_utf_8_file])
37-
assert v.found_dead_code_or_error
38+
assert v.exit_code == ExitCode.InvalidInput
3839

3940

4041
def test_utf8_with_bom(v, tmp_path):
@@ -43,4 +44,4 @@ def test_utf8_with_bom(v, tmp_path):
4344
# utf8_sig prepends the BOM to the file.
4445
filepath.write_text("", encoding="utf-8-sig")
4546
v.scavenge([filepath])
46-
assert not v.found_dead_code_or_error
47+
assert v.exit_code == ExitCode.NoDeadCode

tests/test_errors.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import pytest
22

3-
from . import v
3+
from . import v, call_vulture
4+
from vulture.utils import ExitCode
45

56
assert v # Silence pyflakes.
67

78

89
def test_syntax_error(v):
910
v.scan("foo bar")
10-
assert int(v.report()) == 1
11+
assert int(v.report()) == ExitCode.InvalidInput
1112

1213

1314
def test_null_byte(v):
1415
v.scan("\x00")
15-
assert int(v.report()) == 1
16+
assert int(v.report()) == ExitCode.InvalidInput
1617

1718

1819
def test_confidence_range(v):
@@ -24,3 +25,10 @@ def foo():
2425
)
2526
with pytest.raises(ValueError):
2627
v.get_unused_code(min_confidence=150)
28+
29+
30+
def test_invalid_cmdline_args():
31+
assert (
32+
call_vulture(["vulture/", "--invalid-argument"])
33+
== ExitCode.InvalidCmdlineArguments
34+
)

tests/test_scavenging.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44

55
from . import check, v
6+
from vulture.utils import ExitCode
67

78
assert v # Silence pyflakes.
89

@@ -765,7 +766,7 @@ def __init__(self):
765766
check(v.unused_imports, ["Any", "Dict", "List", "Text", "Tuple"])
766767
else:
767768
check(v.unused_imports, [])
768-
assert not v.found_dead_code_or_error
769+
assert v.exit_code == ExitCode.NoDeadCode
769770

770771

771772
def test_invalid_type_comment(v):
@@ -779,9 +780,9 @@ def bad():
779780
)
780781

781782
if sys.version_info < (3, 8):
782-
assert not v.found_dead_code_or_error
783+
assert v.exit_code == ExitCode.NoDeadCode
783784
else:
784-
assert v.found_dead_code_or_error
785+
assert v.exit_code == ExitCode.InvalidInput
785786

786787

787788
def test_unused_args_with_del(v):

tests/test_script.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,49 @@
44
import sys
55

66
from . import call_vulture, REPO, WHITELISTS
7+
from vulture.utils import ExitCode
78

89

910
def test_module_with_explicit_whitelists():
10-
assert call_vulture(["vulture/"] + WHITELISTS) == 0
11+
assert call_vulture(["vulture/"] + WHITELISTS) == ExitCode.NoDeadCode
1112

1213

1314
def test_module_with_implicit_whitelists():
14-
assert call_vulture(["vulture/"]) == 0
15+
assert call_vulture(["vulture/"]) == ExitCode.NoDeadCode
1516

1617

1718
def test_module_without_whitelists():
18-
assert call_vulture(["vulture/", "--exclude", "whitelists"]) == 1
19+
assert (
20+
call_vulture(["vulture/", "--exclude", "whitelists"])
21+
== ExitCode.DeadCode
22+
)
1923

2024

2125
def test_missing_file():
22-
assert call_vulture(["missing.py"]) == 1
26+
assert call_vulture(["missing.py"]) == ExitCode.InvalidInput
2327

2428

2529
def test_tests():
26-
assert call_vulture(["tests/"]) == 0
30+
assert call_vulture(["tests/"]) == ExitCode.NoDeadCode
2731

2832

2933
def test_whitelists_with_python():
3034
for whitelist in WHITELISTS:
31-
assert subprocess.call([sys.executable, whitelist], cwd=REPO) == 0
35+
assert (
36+
subprocess.call([sys.executable, whitelist], cwd=REPO)
37+
== ExitCode.NoDeadCode
38+
)
3239

3340

3441
def test_pyc():
3542
assert call_vulture(["missing.pyc"]) == 1
3643

3744

3845
def test_sort_by_size():
39-
assert call_vulture(["vulture/utils.py", "--sort-by-size"]) == 1
46+
assert (
47+
call_vulture(["vulture/utils.py", "--sort-by-size"])
48+
== ExitCode.DeadCode
49+
)
4050

4151

4252
def test_min_confidence():
@@ -50,7 +60,7 @@ def test_min_confidence():
5060
"100",
5161
]
5262
)
53-
== 0
63+
== ExitCode.NoDeadCode
5464
)
5565

5666

@@ -61,15 +71,23 @@ def get_csv(paths):
6171
def call_vulture_with_excludes(excludes):
6272
return call_vulture(["vulture/", "--exclude", get_csv(excludes)])
6373

64-
assert call_vulture_with_excludes(["core.py", "utils.py"]) == 1
65-
assert call_vulture_with_excludes(glob.glob("vulture/*.py")) == 0
74+
assert (
75+
call_vulture_with_excludes(["core.py", "utils.py"])
76+
== ExitCode.DeadCode
77+
)
78+
assert (
79+
call_vulture_with_excludes(glob.glob("vulture/*.py"))
80+
== ExitCode.NoDeadCode
81+
)
6682

6783

6884
def test_make_whitelist():
6985
assert (
7086
call_vulture(
7187
["vulture/", "--make-whitelist", "--exclude", "whitelists"]
7288
)
73-
== 1
89+
== ExitCode.DeadCode
90+
)
91+
assert (
92+
call_vulture(["vulture/", "--make-whitelist"]) == ExitCode.NoDeadCode
7493
)
75-
assert call_vulture(["vulture/", "--make-whitelist"]) == 0

vulture/config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import toml
1010

1111
from .version import __version__
12+
from vulture.utils import ExitCode
1213

1314
#: Possible configuration options and their respective defaults
1415
DEFAULTS = {
@@ -192,7 +193,10 @@ def make_config(argv=None, tomlfile=None):
192193
else:
193194
config = {}
194195

195-
cli_config = _parse_args(argv)
196+
try:
197+
cli_config = _parse_args(argv)
198+
except SystemExit as e:
199+
raise SystemExit(ExitCode.InvalidCmdlineArguments) from e
196200

197201
# Overwrite TOML options with CLI options, if given.
198202
config.update(cli_config)

vulture/core.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from vulture import noqa
1111
from vulture import utils
1212
from vulture.config import make_config
13+
from vulture.utils import ExitCode
1314

1415

1516
DEFAULT_CONFIDENCE = 60
@@ -217,7 +218,7 @@ def get_list(typ):
217218

218219
self.filename = Path()
219220
self.code = []
220-
self.found_dead_code_or_error = False
221+
self.exit_code = ExitCode.NoDeadCode
221222

222223
def scan(self, code, filename=""):
223224
filename = Path(filename)
@@ -232,7 +233,7 @@ def handle_syntax_error(e):
232233
file=sys.stderr,
233234
force=True,
234235
)
235-
self.found_dead_code_or_error = True
236+
self.exit_code = ExitCode.InvalidInput
236237

237238
try:
238239
node = (
@@ -251,7 +252,7 @@ def handle_syntax_error(e):
251252
file=sys.stderr,
252253
force=True,
253254
)
254-
self.found_dead_code_or_error = True
255+
self.exit_code = ExitCode.InvalidInput
255256
else:
256257
# When parsing type comments, visiting can throw SyntaxError.
257258
try:
@@ -287,7 +288,7 @@ def exclude_path(path):
287288
file=sys.stderr,
288289
force=True,
289290
)
290-
self.found_dead_code_or_error = True
291+
self.exit_code = ExitCode.InvalidInput
291292
else:
292293
self.scan(module_string, filename=module)
293294

@@ -353,8 +354,8 @@ def report(
353354
else item.get_report(add_size=sort_by_size),
354355
force=True,
355356
)
356-
self.found_dead_code_or_error = True
357-
return self.found_dead_code_or_error
357+
self.exit_code = ExitCode.DeadCode
358+
return self.exit_code
358359

359360
@property
360361
def unused_classes(self):

vulture/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ast
2+
from enum import IntEnum
23
import pathlib
34
import sys
45
import tokenize
@@ -8,6 +9,13 @@ class VultureInputException(Exception):
89
pass
910

1011

12+
class ExitCode(IntEnum):
13+
NoDeadCode = 0
14+
InvalidInput = 1
15+
InvalidCmdlineArguments = 2
16+
DeadCode = 3
17+
18+
1119
def _safe_eval(node, default):
1220
"""
1321
Safely evaluate the Boolean expression under the given AST node.

0 commit comments

Comments
 (0)