Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 985f09f

Browse files
committed
Compute the maximum command length more accurately
1 parent 518a72d commit 985f09f

2 files changed

Lines changed: 45 additions & 15 deletions

File tree

pre_commit/xargs.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import concurrent.futures
66
import contextlib
77
import math
8+
import os
89
import sys
910

1011
import six
@@ -13,10 +14,24 @@
1314
from pre_commit.util import cmd_output
1415

1516

16-
# TODO: properly compute max_length value
17-
def _get_platform_max_length():
18-
# posix minimum
19-
return 4 * 1024
17+
def _environ_size(_env=None):
18+
environ = _env if _env is not None else getattr(os, 'environb', os.environ)
19+
size = 8 * len(environ) # number of pointers in `envp`
20+
for k, v in environ.items():
21+
size += len(k) + len(v) + 2 # c strings in `envp`
22+
return size
23+
24+
25+
def _get_platform_max_length(): # pragma: no cover (platform specific)
26+
if os.name == 'posix':
27+
maximum = os.sysconf(str('SC_ARG_MAX')) - 2048 - _environ_size()
28+
maximum = min(maximum, 2 ** 17)
29+
return maximum
30+
elif os.name == 'nt':
31+
return 2 ** 15 - 2048 # UNICODE_STRING max - headroom
32+
else:
33+
# posix minimum
34+
return 2 ** 12
2035

2136

2237
def _command_length(*cmd):
@@ -52,7 +67,7 @@ def partition(cmd, varargs, target_concurrency, _max_length=None):
5267
# Reversed so arguments are in order
5368
varargs = list(reversed(varargs))
5469

55-
total_length = _command_length(*cmd)
70+
total_length = _command_length(*cmd) + 1
5671
while varargs:
5772
arg = varargs.pop()
5873

@@ -69,7 +84,7 @@ def partition(cmd, varargs, target_concurrency, _max_length=None):
6984
# We've exceeded the length, yield a command
7085
ret.append(cmd + tuple(ret_cmd))
7186
ret_cmd = []
72-
total_length = _command_length(*cmd)
87+
total_length = _command_length(*cmd) + 1
7388
varargs.append(arg)
7489

7590
ret.append(cmd + tuple(ret_cmd))
@@ -99,7 +114,7 @@ def xargs(cmd, varargs, **kwargs):
99114
stderr = b''
100115

101116
try:
102-
parse_shebang.normexe(cmd[0])
117+
cmd = parse_shebang.normalize_cmd(cmd)
103118
except parse_shebang.ExecutableNotFoundError as e:
104119
return e.to_output()
105120

tests/xargs_test.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,24 @@
1010
import pytest
1111
import six
1212

13+
from pre_commit import parse_shebang
1314
from pre_commit import xargs
1415

1516

17+
@pytest.mark.parametrize(
18+
('env', 'expected'),
19+
(
20+
({}, 0),
21+
({b'x': b'1'}, 12),
22+
({b'x': b'12'}, 13),
23+
({b'x': b'1', b'y': b'2'}, 24),
24+
),
25+
)
26+
def test_environ_size(env, expected):
27+
# normalize integer sizing
28+
assert xargs._environ_size(_env=env) == expected
29+
30+
1631
@pytest.fixture
1732
def win32_py2_mock():
1833
with mock.patch.object(sys, 'getfilesystemencoding', return_value='utf-8'):
@@ -56,7 +71,7 @@ def test_partition_limits():
5671
'.' * 6,
5772
),
5873
1,
59-
_max_length=20,
74+
_max_length=21,
6075
)
6176
assert ret == (
6277
('ninechars', '.' * 5, '.' * 4),
@@ -70,21 +85,21 @@ def test_partition_limit_win32_py3(win32_py3_mock):
7085
cmd = ('ninechars',)
7186
# counted as half because of utf-16 encode
7287
varargs = ('😑' * 5,)
73-
ret = xargs.partition(cmd, varargs, 1, _max_length=20)
88+
ret = xargs.partition(cmd, varargs, 1, _max_length=21)
7489
assert ret == (cmd + varargs,)
7590

7691

7792
def test_partition_limit_win32_py2(win32_py2_mock):
7893
cmd = ('ninechars',)
7994
varargs = ('😑' * 5,) # 4 bytes * 5
80-
ret = xargs.partition(cmd, varargs, 1, _max_length=30)
95+
ret = xargs.partition(cmd, varargs, 1, _max_length=31)
8196
assert ret == (cmd + varargs,)
8297

8398

8499
def test_partition_limit_linux(linux_mock):
85100
cmd = ('ninechars',)
86101
varargs = ('😑' * 5,)
87-
ret = xargs.partition(cmd, varargs, 1, _max_length=30)
102+
ret = xargs.partition(cmd, varargs, 1, _max_length=31)
88103
assert ret == (cmd + varargs,)
89104

90105

@@ -134,9 +149,9 @@ def test_xargs_smoke():
134149
assert err == b''
135150

136151

137-
exit_cmd = ('bash', '-c', 'exit $1', '--')
152+
exit_cmd = parse_shebang.normalize_cmd(('bash', '-c', 'exit $1', '--'))
138153
# Abuse max_length to control the exit code
139-
max_length = len(' '.join(exit_cmd)) + 2
154+
max_length = len(' '.join(exit_cmd)) + 3
140155

141156

142157
def test_xargs_negate():
@@ -165,14 +180,14 @@ def test_xargs_retcode_normal():
165180

166181

167182
def test_xargs_concurrency():
168-
bash_cmd = ('bash', '-c')
183+
bash_cmd = parse_shebang.normalize_cmd(('bash', '-c'))
169184
print_pid = ('sleep 0.5 && echo $$',)
170185

171186
start = time.time()
172187
ret, stdout, _ = xargs.xargs(
173188
bash_cmd, print_pid * 5,
174189
target_concurrency=5,
175-
_max_length=len(' '.join(bash_cmd + print_pid)),
190+
_max_length=len(' '.join(bash_cmd + print_pid)) + 1,
176191
)
177192
elapsed = time.time() - start
178193
assert ret == 0

0 commit comments

Comments
 (0)