From 5a6b6e81e90d071b9c1cfd9ade7ec3b0b0c39b61 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 20 May 2016 13:22:13 -0700 Subject: [PATCH] Don't crash when an executable is not found --- pre_commit/parse_shebang.py | 8 ++++++- pre_commit/util.py | 25 ++++++++++++---------- testing/resources/not_found_exe/hooks.yaml | 5 +++++ tests/parse_shebang_test.py | 2 +- tests/repository_test.py | 10 +++++++++ 5 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 testing/resources/not_found_exe/hooks.yaml diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py index df10c6d3..13a1a722 100644 --- a/pre_commit/parse_shebang.py +++ b/pre_commit/parse_shebang.py @@ -12,6 +12,10 @@ from pre_commit import five printable = frozenset(string.printable) +class ExecutableNotFoundError(OSError): + pass + + def parse_bytesio(bytesio): """Parse the shebang from a file opened for reading binary.""" if bytesio.read(2) != b'#!': @@ -70,7 +74,9 @@ def normexe(orig_exe): if os.sep not in orig_exe: exe = find_executable(orig_exe) if exe is None: - raise OSError('Executable {0} not found'.format(orig_exe)) + raise ExecutableNotFoundError( + 'Executable `{0}` not found'.format(orig_exe), + ) return exe else: return orig_exe diff --git a/pre_commit/util.py b/pre_commit/util.py index 57303f56..4b478563 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -181,23 +181,26 @@ def cmd_output(*cmd, **kwargs): for key, value in kwargs.pop('env', {}).items() ) or None - cmd = parse_shebang.normalize_cmd(cmd) - - popen_kwargs.update(kwargs) - proc = __popen(cmd, **popen_kwargs) - stdout, stderr = proc.communicate() - if encoding is not None and stdout is not None: - stdout = stdout.decode(encoding) - if encoding is not None and stderr is not None: - stderr = stderr.decode(encoding) - returncode = proc.returncode + try: + cmd = parse_shebang.normalize_cmd(cmd) + except parse_shebang.ExecutableNotFoundError as e: + returncode, stdout, stderr = (-1, e.args[0].encode('UTF-8'), b'') + else: + popen_kwargs.update(kwargs) + proc = __popen(cmd, **popen_kwargs) + stdout, stderr = proc.communicate() + if encoding is not None and stdout is not None: + stdout = stdout.decode(encoding) + if encoding is not None and stderr is not None: + stderr = stderr.decode(encoding) + returncode = proc.returncode if retcode is not None and retcode != returncode: raise CalledProcessError( returncode, cmd, retcode, output=(stdout, stderr), ) - return proc.returncode, stdout, stderr + return returncode, stdout, stderr def rmtree(path): diff --git a/testing/resources/not_found_exe/hooks.yaml b/testing/resources/not_found_exe/hooks.yaml new file mode 100644 index 00000000..566f3c1f --- /dev/null +++ b/testing/resources/not_found_exe/hooks.yaml @@ -0,0 +1,5 @@ +- id: not-found-exe + name: Not found exe + entry: i-dont-exist-lol + language: system + files: '' diff --git a/tests/parse_shebang_test.py b/tests/parse_shebang_test.py index c26ff73f..95a0fcef 100644 --- a/tests/parse_shebang_test.py +++ b/tests/parse_shebang_test.py @@ -108,7 +108,7 @@ def test_find_executable_path_ext(in_tmpdir): def test_normexe_does_not_exist(): with pytest.raises(OSError) as excinfo: parse_shebang.normexe('i-dont-exist-lol') - assert excinfo.value.args == ('Executable i-dont-exist-lol not found',) + assert excinfo.value.args == ('Executable `i-dont-exist-lol` not found',) def test_normexe_already_full_path(): diff --git a/tests/repository_test.py b/tests/repository_test.py index 978b42ce..28ecc275 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -164,6 +164,16 @@ def test_system_hook_with_spaces(tempdir_factory, store): ) +@pytest.mark.integration +def test_missing_executable(tempdir_factory, store): + _test_hook_repo( + tempdir_factory, store, 'not_found_exe', + 'not-found-exe', ['/dev/null'], + b'Executable `i-dont-exist-lol` not found', + expected_return_code=1, + ) + + @pytest.mark.integration def test_run_a_script_hook(tempdir_factory, store): _test_hook_repo(