Skip to content

Commit a157e1a

Browse files
committed
xargs returns nonzero for negate + not found exe (fixes pcre + not found pre-commit#447)
1 parent 0e2c3c1 commit a157e1a

7 files changed

Lines changed: 49 additions & 11 deletions

File tree

pre_commit/languages/pcre.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from __future__ import unicode_literals
22

3-
from sys import platform
3+
import sys
44

55
from pre_commit.xargs import xargs
66

77

88
ENVIRONMENT_DIR = None
9+
GREP = 'ggrep' if sys.platform == 'darwin' else 'grep'
910

1011

1112
def install_environment(
@@ -19,10 +20,7 @@ def install_environment(
1920

2021
def run_hook(repo_cmd_runner, hook, file_args):
2122
# For PCRE the entry is the regular expression to match
22-
cmd = (
23-
'ggrep' if platform == 'darwin' else 'grep',
24-
'-H', '-n', '-P',
25-
) + tuple(hook['args']) + (hook['entry'],)
23+
cmd = (GREP, '-H', '-n', '-P') + tuple(hook['args']) + (hook['entry'],)
2624

2725
# Grep usually returns 0 for matches, and nonzero for non-matches so we
2826
# negate it here.

pre_commit/parse_shebang.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212

1313
class ExecutableNotFoundError(OSError):
14-
pass
14+
def to_output(self):
15+
return (1, self.args[0].encode('UTF-8'), b'')
1516

1617

1718
def parse_bytesio(bytesio):

pre_commit/util.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,16 +172,16 @@ def cmd_output(*cmd, **kwargs):
172172
try:
173173
cmd = parse_shebang.normalize_cmd(cmd)
174174
except parse_shebang.ExecutableNotFoundError as e:
175-
returncode, stdout, stderr = (-1, e.args[0].encode('UTF-8'), b'')
175+
returncode, stdout, stderr = e.to_output()
176176
else:
177177
popen_kwargs.update(kwargs)
178178
proc = __popen(cmd, **popen_kwargs)
179179
stdout, stderr = proc.communicate()
180-
if encoding is not None and stdout is not None:
181-
stdout = stdout.decode(encoding)
182-
if encoding is not None and stderr is not None:
183-
stderr = stderr.decode(encoding)
184180
returncode = proc.returncode
181+
if encoding is not None and stdout is not None:
182+
stdout = stdout.decode(encoding)
183+
if encoding is not None and stderr is not None:
184+
stderr = stderr.decode(encoding)
185185

186186
if retcode is not None and retcode != returncode:
187187
raise CalledProcessError(

pre_commit/xargs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import absolute_import
22
from __future__ import unicode_literals
33

4+
from pre_commit import parse_shebang
45
from pre_commit.util import cmd_output
56

67

@@ -52,6 +53,11 @@ def xargs(cmd, varargs, **kwargs):
5253
stdout = b''
5354
stderr = b''
5455

56+
try:
57+
parse_shebang.normexe(cmd[0])
58+
except parse_shebang.ExecutableNotFoundError as e:
59+
return e.to_output()
60+
5561
for run_cmd in partition(cmd, varargs, **kwargs):
5662
proc_retcode, proc_out, proc_err = cmd_output(
5763
*run_cmd, encoding=None, retcode=None

tests/repository_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
import pytest
1313

1414
from pre_commit import five
15+
from pre_commit import parse_shebang
1516
from pre_commit.clientlib.validate_config import CONFIG_JSON_SCHEMA
1617
from pre_commit.clientlib.validate_config import validate_config_extra
1718
from pre_commit.jsonschema_extensions import apply_defaults
1819
from pre_commit.languages import helpers
1920
from pre_commit.languages import node
21+
from pre_commit.languages import pcre
2022
from pre_commit.languages import python
2123
from pre_commit.languages import ruby
2224
from pre_commit.repository import Repository
@@ -187,6 +189,25 @@ def test_missing_executable(tempdir_factory, store):
187189
)
188190

189191

192+
@pytest.mark.integration
193+
def test_missing_pcre_support(tempdir_factory, store):
194+
orig_find_executable = parse_shebang.find_executable
195+
196+
def no_grep(exe, **kwargs):
197+
if exe == pcre.GREP:
198+
return None
199+
else:
200+
return orig_find_executable(exe, **kwargs)
201+
202+
with mock.patch.object(parse_shebang, 'find_executable', no_grep):
203+
_test_hook_repo(
204+
tempdir_factory, store, 'pcre_hooks_repo',
205+
'regex-with-quotes', ['/dev/null'],
206+
'Executable `{}` not found'.format(pcre.GREP).encode('UTF-8'),
207+
expected_return_code=1,
208+
)
209+
210+
190211
@pytest.mark.integration
191212
def test_run_a_script_hook(tempdir_factory, store):
192213
_test_hook_repo(

tests/util_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pytest
77

88
from pre_commit.util import clean_path_on_failure
9+
from pre_commit.util import cmd_output
910
from pre_commit.util import cwd
1011
from pre_commit.util import memoize_by_cwd
1112
from pre_commit.util import tmpdir
@@ -81,3 +82,9 @@ def test_tmpdir():
8182
with tmpdir() as tempdir:
8283
assert os.path.exists(tempdir)
8384
assert not os.path.exists(tempdir)
85+
86+
87+
def test_cmd_output_exe_not_found():
88+
ret, out, _ = cmd_output('i-dont-exist', retcode=None)
89+
assert ret == 1
90+
assert out == 'Executable `i-dont-exist` not found'

tests/xargs_test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ def test_xargs_negate():
6464
assert ret == 1
6565

6666

67+
def test_xargs_negate_command_not_found():
68+
ret, _, _ = xargs.xargs(('cmd-not-found',), ('1',), negate=True)
69+
assert ret != 0
70+
71+
6772
def test_xargs_retcode_normal():
6873
ret, _, _ = xargs.xargs(exit_cmd, ('0',), _max_length=max_length)
6974
assert ret == 0

0 commit comments

Comments
 (0)