Skip to content

Commit efda3cd

Browse files
committed
Merge pull request pre-commit#372 from pre-commit/dont_crash_on_not_found_exe
Don't crash when an executable is not found
2 parents cd03f78 + 5a6b6e8 commit efda3cd

File tree

5 files changed

+37
-13
lines changed

5 files changed

+37
-13
lines changed

pre_commit/parse_shebang.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
printable = frozenset(string.printable)
1313

1414

15+
class ExecutableNotFoundError(OSError):
16+
pass
17+
18+
1519
def parse_bytesio(bytesio):
1620
"""Parse the shebang from a file opened for reading binary."""
1721
if bytesio.read(2) != b'#!':
@@ -70,7 +74,9 @@ def normexe(orig_exe):
7074
if os.sep not in orig_exe:
7175
exe = find_executable(orig_exe)
7276
if exe is None:
73-
raise OSError('Executable {0} not found'.format(orig_exe))
77+
raise ExecutableNotFoundError(
78+
'Executable `{0}` not found'.format(orig_exe),
79+
)
7480
return exe
7581
else:
7682
return orig_exe

pre_commit/util.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -181,23 +181,26 @@ def cmd_output(*cmd, **kwargs):
181181
for key, value in kwargs.pop('env', {}).items()
182182
) or None
183183

184-
cmd = parse_shebang.normalize_cmd(cmd)
185-
186-
popen_kwargs.update(kwargs)
187-
proc = __popen(cmd, **popen_kwargs)
188-
stdout, stderr = proc.communicate()
189-
if encoding is not None and stdout is not None:
190-
stdout = stdout.decode(encoding)
191-
if encoding is not None and stderr is not None:
192-
stderr = stderr.decode(encoding)
193-
returncode = proc.returncode
184+
try:
185+
cmd = parse_shebang.normalize_cmd(cmd)
186+
except parse_shebang.ExecutableNotFoundError as e:
187+
returncode, stdout, stderr = (-1, e.args[0].encode('UTF-8'), b'')
188+
else:
189+
popen_kwargs.update(kwargs)
190+
proc = __popen(cmd, **popen_kwargs)
191+
stdout, stderr = proc.communicate()
192+
if encoding is not None and stdout is not None:
193+
stdout = stdout.decode(encoding)
194+
if encoding is not None and stderr is not None:
195+
stderr = stderr.decode(encoding)
196+
returncode = proc.returncode
194197

195198
if retcode is not None and retcode != returncode:
196199
raise CalledProcessError(
197200
returncode, cmd, retcode, output=(stdout, stderr),
198201
)
199202

200-
return proc.returncode, stdout, stderr
203+
return returncode, stdout, stderr
201204

202205

203206
def rmtree(path):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- id: not-found-exe
2+
name: Not found exe
3+
entry: i-dont-exist-lol
4+
language: system
5+
files: ''

tests/parse_shebang_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def test_find_executable_path_ext(in_tmpdir):
108108
def test_normexe_does_not_exist():
109109
with pytest.raises(OSError) as excinfo:
110110
parse_shebang.normexe('i-dont-exist-lol')
111-
assert excinfo.value.args == ('Executable i-dont-exist-lol not found',)
111+
assert excinfo.value.args == ('Executable `i-dont-exist-lol` not found',)
112112

113113

114114
def test_normexe_already_full_path():

tests/repository_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,16 @@ def test_system_hook_with_spaces(tempdir_factory, store):
164164
)
165165

166166

167+
@pytest.mark.integration
168+
def test_missing_executable(tempdir_factory, store):
169+
_test_hook_repo(
170+
tempdir_factory, store, 'not_found_exe',
171+
'not-found-exe', ['/dev/null'],
172+
b'Executable `i-dont-exist-lol` not found',
173+
expected_return_code=1,
174+
)
175+
176+
167177
@pytest.mark.integration
168178
def test_run_a_script_hook(tempdir_factory, store):
169179
_test_hook_repo(

0 commit comments

Comments
 (0)