userver: en/testsuite/environment/shell.py Source File
Loading...
Searching...
No Matches
shell.py
1import contextlib
2import logging
3import subprocess
4import threading
5
6from testsuite.utils import traceback
7
8logger = logging.getLogger(__name__)
9
10
11class BaseError(Exception):
12 pass
13
14
16 pass
17
18
19__tracebackhide__ = traceback.hide(BaseError)
20
21
22def execute(args, *, env=None, verbose: int, command_alias: str) -> None:
23 buffer: list[str] = []
24 lock_process_completion = threading.Lock()
25 process_completed = False
26
27 def _capture_output(stream):
28 for line in stream:
29 try:
30 decoded = line.decode('utf-8')
31 except UnicodeDecodeError as err:
32 logger.error(
33 'Failed to decode subprocess output',
34 exc_info=err,
35 )
36 continue
37 decoded = decoded.rstrip('\r\n')
38 with lock_process_completion:
39 if process_completed:
40 # Treat postmortem output from pipe as error.
41 # For example pg_ctl does not close pipe on exit so we may
42 # get output later from a started process.
43 logger.warning('%s: %s', ident, decoded)
44 else:
45 if verbose > 1:
46 logger.info('%s: %s', ident, decoded)
47 else:
48 buffer.append(decoded)
49
50 def _do_capture_output(stream):
51 with contextlib.closing(stream):
52 _capture_output(stream)
53
54 process = subprocess.Popen(
55 args,
56 env=env,
57 stdout=subprocess.PIPE,
58 stderr=subprocess.STDOUT,
59 )
60 ident = f'{command_alias}[{process.pid}]'
61
62 thread = threading.Thread(target=_do_capture_output, args=(process.stdout,))
63 thread.daemon = True
64 thread.start()
65 exit_code = process.wait()
66 with lock_process_completion:
67 process_completed = True
68 if exit_code != 0:
69 for msg in buffer:
70 logger.error('%s: %s', ident, msg)
71 logger.error(
72 '%s: subprocess %s exited with code %d',
73 ident,
74 process.args,
75 exit_code,
76 )
77
78 if exit_code != 0:
79 raise SubprocessFailed(
80 f'Subprocess {ident} exited with code {exit_code}\n'
81 f'... args={process.args!r}'
82 )