Skip to content

Commit 2633d38

Browse files
committed
Fix ordering of mixed stdout / stderr printing
1 parent 183c8cb commit 2633d38

File tree

6 files changed

+55
-32
lines changed

6 files changed

+55
-32
lines changed

pre_commit/commands/run.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,7 @@ def _run_single_hook(classifier, hook, args, skips, cols):
118118
sys.stdout.flush()
119119

120120
diff_before = cmd_output_b('git', 'diff', '--no-ext-diff', retcode=None)
121-
retcode, stdout, stderr = hook.run(
122-
tuple(filenames) if hook.pass_filenames else (),
123-
)
121+
retcode, out = hook.run(tuple(filenames) if hook.pass_filenames else ())
124122
diff_after = cmd_output_b('git', 'diff', '--no-ext-diff', retcode=None)
125123

126124
file_modifications = diff_before != diff_after
@@ -141,7 +139,7 @@ def _run_single_hook(classifier, hook, args, skips, cols):
141139
output.write_line(color.format_color(pass_fail, print_color, args.color))
142140

143141
if (
144-
(stdout or stderr or file_modifications) and
142+
(out or file_modifications) and
145143
(retcode or args.verbose or hook.verbose)
146144
):
147145
output.write_line('hookid: {}\n'.format(hook.id))
@@ -150,15 +148,13 @@ def _run_single_hook(classifier, hook, args, skips, cols):
150148
if file_modifications:
151149
output.write('Files were modified by this hook.')
152150

153-
if stdout or stderr:
151+
if out:
154152
output.write_line(' Additional output:')
155153

156154
output.write_line()
157155

158-
for out in (stdout, stderr):
159-
assert type(out) is bytes, type(out)
160-
if out.strip():
161-
output.write_line(out.strip(), logfile_name=hook.log_file)
156+
if out.strip():
157+
output.write_line(out.strip(), logfile_name=hook.log_file)
162158
output.write_line()
163159

164160
return retcode

pre_commit/xargs.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import contextlib
77
import math
88
import os
9+
import subprocess
910
import sys
1011

1112
import six
@@ -112,23 +113,24 @@ def xargs(cmd, varargs, **kwargs):
112113
max_length = kwargs.pop('_max_length', _get_platform_max_length())
113114
retcode = 0
114115
stdout = b''
115-
stderr = b''
116116

117117
try:
118118
cmd = parse_shebang.normalize_cmd(cmd)
119119
except parse_shebang.ExecutableNotFoundError as e:
120-
return e.to_output()
120+
return e.to_output()[:2]
121121

122122
partitions = partition(cmd, varargs, target_concurrency, max_length)
123123

124124
def run_cmd_partition(run_cmd):
125-
return cmd_output_b(*run_cmd, retcode=None, **kwargs)
125+
return cmd_output_b(
126+
*run_cmd, retcode=None, stderr=subprocess.STDOUT, **kwargs
127+
)
126128

127129
threads = min(len(partitions), target_concurrency)
128130
with _thread_mapper(threads) as thread_map:
129131
results = thread_map(run_cmd_partition, partitions)
130132

131-
for proc_retcode, proc_out, proc_err in results:
133+
for proc_retcode, proc_out, _ in results:
132134
# This is *slightly* too clever so I'll explain it.
133135
# First the xor boolean table:
134136
# T | F |
@@ -141,6 +143,5 @@ def run_cmd_partition(run_cmd):
141143
# code. Otherwise, the returncode is unchanged.
142144
retcode |= bool(proc_retcode) ^ negate
143145
stdout += proc_out
144-
stderr += proc_err
145146

146-
return retcode, stdout, stderr
147+
return retcode, stdout
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- id: stdout-stderr
2+
name: stdout-stderr
3+
language: script
4+
entry: ./entry
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python
2+
import sys
3+
4+
5+
def main():
6+
for i in range(6):
7+
f = sys.stdout if i % 2 == 0 else sys.stderr
8+
f.write('{}\n'.format(i))
9+
f.flush()
10+
11+
12+
if __name__ == '__main__':
13+
exit(main())

tests/repository_test.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ def test_run_a_failing_docker_hook(tempdir_factory, store):
177177
_test_hook_repo(
178178
tempdir_factory, store, 'docker_hooks_repo',
179179
'docker-hook-failing',
180-
['Hello World from docker'], b'',
180+
['Hello World from docker'],
181+
mock.ANY, # an error message about `bork` not existing
181182
expected_return_code=1,
182183
)
183184

@@ -363,6 +364,15 @@ def test_run_hook_with_curly_braced_arguments(tempdir_factory, store):
363364
)
364365

365366

367+
def test_intermixed_stdout_stderr(tempdir_factory, store):
368+
_test_hook_repo(
369+
tempdir_factory, store, 'stdout_stderr_repo',
370+
'stdout-stderr',
371+
[],
372+
b'0\n1\n2\n3\n4\n5\n',
373+
)
374+
375+
366376
def _make_grep_repo(language, entry, store, args=()):
367377
config = {
368378
'repo': 'local',
@@ -393,20 +403,20 @@ class TestPygrep(object):
393403

394404
def test_grep_hook_matching(self, greppable_files, store):
395405
hook = _make_grep_repo(self.language, 'ello', store)
396-
ret, out, _ = hook.run(('f1', 'f2', 'f3'))
406+
ret, out = hook.run(('f1', 'f2', 'f3'))
397407
assert ret == 1
398408
assert _norm_out(out) == b"f1:1:hello'hi\n"
399409

400410
def test_grep_hook_case_insensitive(self, greppable_files, store):
401411
hook = _make_grep_repo(self.language, 'ELLO', store, args=['-i'])
402-
ret, out, _ = hook.run(('f1', 'f2', 'f3'))
412+
ret, out = hook.run(('f1', 'f2', 'f3'))
403413
assert ret == 1
404414
assert _norm_out(out) == b"f1:1:hello'hi\n"
405415

406416
@pytest.mark.parametrize('regex', ('nope', "foo'bar", r'^\[INFO\]'))
407417
def test_grep_hook_not_matching(self, regex, greppable_files, store):
408418
hook = _make_grep_repo(self.language, regex, store)
409-
ret, out, _ = hook.run(('f1', 'f2', 'f3'))
419+
ret, out = hook.run(('f1', 'f2', 'f3'))
410420
assert (ret, out) == (0, b'')
411421

412422

@@ -420,7 +430,7 @@ def test_pcre_hook_many_files(self, greppable_files, store):
420430
# file to make sure it still fails. This is not the case when naively
421431
# using a system hook with `grep -H -n '...'`
422432
hook = _make_grep_repo('pcre', 'ello', store)
423-
ret, out, _ = hook.run((os.devnull,) * 15000 + ('f1',))
433+
ret, out = hook.run((os.devnull,) * 15000 + ('f1',))
424434
assert ret == 1
425435
assert _norm_out(out) == b"f1:1:hello'hi\n"
426436

@@ -431,7 +441,7 @@ def no_grep(exe, **kwargs):
431441

432442
with mock.patch.object(parse_shebang, 'find_executable', no_grep):
433443
hook = _make_grep_repo('pcre', 'ello', store)
434-
ret, out, _ = hook.run(('f1', 'f2', 'f3'))
444+
ret, out = hook.run(('f1', 'f2', 'f3'))
435445
assert ret == 1
436446
expected = 'Executable `{}` not found'.format(pcre.GREP).encode()
437447
assert out == expected
@@ -635,7 +645,7 @@ class MyKeyboardInterrupt(KeyboardInterrupt):
635645
# However, it should be perfectly runnable (reinstall after botched
636646
# install)
637647
install_hook_envs(hooks, store)
638-
retv, stdout, stderr = hook.run(())
648+
retv, stdout = hook.run(())
639649
assert retv == 0
640650

641651

@@ -657,7 +667,7 @@ def test_invalidated_virtualenv(tempdir_factory, store):
657667
cmd_output_b('rm', '-rf', *paths)
658668

659669
# pre-commit should rebuild the virtualenv and it should be runnable
660-
retv, stdout, stderr = _get_hook(config, store, 'foo').run(())
670+
retv, stdout = _get_hook(config, store, 'foo').run(())
661671
assert retv == 0
662672

663673

tests/xargs_test.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,9 @@ def test_argument_too_long():
143143

144144

145145
def test_xargs_smoke():
146-
ret, out, err = xargs.xargs(('echo',), ('hello', 'world'))
146+
ret, out = xargs.xargs(('echo',), ('hello', 'world'))
147147
assert ret == 0
148148
assert out.replace(b'\r\n', b'\n') == b'hello world\n'
149-
assert err == b''
150149

151150

152151
exit_cmd = parse_shebang.normalize_cmd(('bash', '-c', 'exit $1', '--'))
@@ -155,27 +154,27 @@ def test_xargs_smoke():
155154

156155

157156
def test_xargs_negate():
158-
ret, _, _ = xargs.xargs(
157+
ret, _ = xargs.xargs(
159158
exit_cmd, ('1',), negate=True, _max_length=max_length,
160159
)
161160
assert ret == 0
162161

163-
ret, _, _ = xargs.xargs(
162+
ret, _ = xargs.xargs(
164163
exit_cmd, ('1', '0'), negate=True, _max_length=max_length,
165164
)
166165
assert ret == 1
167166

168167

169168
def test_xargs_negate_command_not_found():
170-
ret, _, _ = xargs.xargs(('cmd-not-found',), ('1',), negate=True)
169+
ret, _ = xargs.xargs(('cmd-not-found',), ('1',), negate=True)
171170
assert ret != 0
172171

173172

174173
def test_xargs_retcode_normal():
175-
ret, _, _ = xargs.xargs(exit_cmd, ('0',), _max_length=max_length)
174+
ret, _ = xargs.xargs(exit_cmd, ('0',), _max_length=max_length)
176175
assert ret == 0
177176

178-
ret, _, _ = xargs.xargs(exit_cmd, ('0', '1'), _max_length=max_length)
177+
ret, _ = xargs.xargs(exit_cmd, ('0', '1'), _max_length=max_length)
179178
assert ret == 1
180179

181180

@@ -184,7 +183,7 @@ def test_xargs_concurrency():
184183
print_pid = ('sleep 0.5 && echo $$',)
185184

186185
start = time.time()
187-
ret, stdout, _ = xargs.xargs(
186+
ret, stdout = xargs.xargs(
188187
bash_cmd, print_pid * 5,
189188
target_concurrency=5,
190189
_max_length=len(' '.join(bash_cmd + print_pid)) + 1,
@@ -215,6 +214,6 @@ def test_xargs_propagate_kwargs_to_cmd():
215214
cmd = ('bash', '-c', 'echo $PRE_COMMIT_TEST_VAR', '--')
216215
cmd = parse_shebang.normalize_cmd(cmd)
217216

218-
ret, stdout, _ = xargs.xargs(cmd, ('1',), env=env)
217+
ret, stdout = xargs.xargs(cmd, ('1',), env=env)
219218
assert ret == 0
220219
assert b'Pre commit is awesome' in stdout

0 commit comments

Comments
 (0)