ae-shell 0.3.17__tar.gz → 0.3.19__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ae_shell-0.3.17 → ae_shell-0.3.19}/LICENSE.md +1 -1
- {ae_shell-0.3.17/ae_shell.egg-info → ae_shell-0.3.19}/PKG-INFO +4 -4
- {ae_shell-0.3.17 → ae_shell-0.3.19}/README.md +3 -3
- {ae_shell-0.3.17 → ae_shell-0.3.19}/ae/shell.py +126 -115
- {ae_shell-0.3.17 → ae_shell-0.3.19/ae_shell.egg-info}/PKG-INFO +4 -4
- {ae_shell-0.3.17 → ae_shell-0.3.19}/pyproject.toml +1 -1
- {ae_shell-0.3.17 → ae_shell-0.3.19}/setup.py +2 -2
- {ae_shell-0.3.17 → ae_shell-0.3.19}/tests/test_shell.py +68 -93
- {ae_shell-0.3.17 → ae_shell-0.3.19}/ae_shell.egg-info/SOURCES.txt +0 -0
- {ae_shell-0.3.17 → ae_shell-0.3.19}/ae_shell.egg-info/dependency_links.txt +0 -0
- {ae_shell-0.3.17 → ae_shell-0.3.19}/ae_shell.egg-info/requires.txt +0 -0
- {ae_shell-0.3.17 → ae_shell-0.3.19}/ae_shell.egg-info/top_level.txt +0 -0
- {ae_shell-0.3.17 → ae_shell-0.3.19}/ae_shell.egg-info/zip-safe +0 -0
- {ae_shell-0.3.17 → ae_shell-0.3.19}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ae_shell
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.19
|
|
4
4
|
Summary: ae namespace module portion shell: shell execution and environment helpers
|
|
5
5
|
Home-page: https://gitlab.com/ae-group/ae_shell
|
|
6
6
|
Author: AndiEcker
|
|
@@ -64,13 +64,13 @@ Dynamic: summary
|
|
|
64
64
|
|
|
65
65
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.110 -->
|
|
66
66
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.33 -->
|
|
67
|
-
# shell 0.3.
|
|
67
|
+
# shell 0.3.19
|
|
68
68
|
|
|
69
69
|
[](
|
|
70
70
|
https://gitlab.com/ae-group/ae_shell)
|
|
71
71
|
[](
|
|
73
|
+
https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.19)
|
|
74
74
|
[](
|
|
75
75
|
https://pypi.org/project/ae-shell/#history)
|
|
76
76
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.110 -->
|
|
2
2
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.33 -->
|
|
3
|
-
# shell 0.3.
|
|
3
|
+
# shell 0.3.19
|
|
4
4
|
|
|
5
5
|
[](
|
|
6
6
|
https://gitlab.com/ae-group/ae_shell)
|
|
7
7
|
[](
|
|
9
|
+
https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.19)
|
|
10
10
|
[](
|
|
11
11
|
https://pypi.org/project/ae-shell/#history)
|
|
12
12
|
|
|
@@ -12,32 +12,31 @@ to manage shell printouts, OS environment variables and to execute shell command
|
|
|
12
12
|
* :func:`mask_token`: hide/mask tokens in a text block, to prevent to show them in logs and printouts.
|
|
13
13
|
* :func:`output_line_split`: decode and split the specified shell/console output streams into line chunks.
|
|
14
14
|
* :func:`output_zero_split`: decode and split the specified shell/console output streams separated by `NUL` (\\0) chars.
|
|
15
|
-
* :func:`
|
|
16
|
-
* :func:`
|
|
15
|
+
* :func:`run_cmd`: execute command in the current working directory of the OS console/shell.
|
|
16
|
+
* :func:`run_logged_cmd`: extended version of :func:`run_cmd` with logging and error checking.
|
|
17
17
|
after a command is executed and handles application shutdown/termination gracefully.
|
|
18
18
|
|
|
19
|
-
* :data:`STDERR_BEG_MARKER`: marker used in the console/shell printouts for the beginning of merged-in stderr output.
|
|
20
|
-
* :data:`STDERR_END_MARKER`: marker used in the console/shell printouts for the end of merged-in stderr output.
|
|
19
|
+
* :data:`STDERR_BEG_MARKER`: marker used in the console/shell printouts for the beginning of merged-in `stderr` output.
|
|
20
|
+
* :data:`STDERR_END_MARKER`: marker used in the console/shell printouts for the end of merged-in `stderr` output.
|
|
21
21
|
"""
|
|
22
22
|
import os
|
|
23
|
-
import shlex
|
|
24
23
|
import subprocess
|
|
25
24
|
|
|
26
|
-
from collections.abc import Callable,
|
|
25
|
+
from collections.abc import Callable, Iterator, MutableMapping
|
|
27
26
|
from contextlib import contextmanager
|
|
28
|
-
from typing import Any, cast, overload
|
|
27
|
+
from typing import Any, Sequence, cast, overload
|
|
29
28
|
|
|
30
|
-
from ae.base import UNSET, UnsetType, dummy_function, env_str, norm_name
|
|
29
|
+
from ae.base import UNSET, URI_SVC_SEP, UnsetType, dummy_function, env_str, norm_name # type: ignore
|
|
31
30
|
from ae.system import load_env_var_defaults, os_env_venv # type: ignore
|
|
32
31
|
from ae.core import main_app_instance, AppBase # type: ignore
|
|
33
32
|
from ae.console import MAIN_SECTION_NAME, ConsoleApp # type: ignore
|
|
34
33
|
|
|
35
34
|
|
|
36
|
-
__version__ = '0.3.
|
|
35
|
+
__version__ = '0.3.19'
|
|
37
36
|
|
|
38
37
|
|
|
39
|
-
STDERR_BEG_MARKER = 'vvv STDERR vvv' #: :paramref:`ae.shell.
|
|
40
|
-
STDERR_END_MARKER = '^^^ STDERR ^^^' #: end stderr lines marker in :paramref:`ae.shell.
|
|
38
|
+
STDERR_BEG_MARKER = 'vvv STDERR vvv' #: :paramref:`ae.shell.run_cmd.output_lines` begin `stderr` lines marker
|
|
39
|
+
STDERR_END_MARKER = '^^^ STDERR ^^^' #: end `stderr` lines marker in :paramref:`ae.shell.run_cmd.output_lines`
|
|
41
40
|
|
|
42
41
|
|
|
43
42
|
def debug_or_verbose(app_obj: ConsoleApp | UnsetType | None = None) -> bool:
|
|
@@ -119,18 +118,14 @@ def in_os_env(start_dir: str = "") -> Iterator[MutableMapping[str, str]]:
|
|
|
119
118
|
|
|
120
119
|
|
|
121
120
|
@overload
|
|
122
|
-
def mask_token(text: str) -> str: ...
|
|
121
|
+
def mask_token(text: str) -> str: ... # type: ignore[overload-overlap]
|
|
123
122
|
|
|
124
123
|
|
|
125
124
|
@overload
|
|
126
|
-
def mask_token(text:
|
|
125
|
+
def mask_token(text: Sequence[str]) -> list[str]: ...
|
|
127
126
|
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
def mask_token(text: str | list[str]) -> str | list[str]: ...
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def mask_token(text: str | list[str]) -> str | list[str]:
|
|
128
|
+
def mask_token(text: str | Sequence[str]) -> str | list[str]:
|
|
134
129
|
""" hide most parts of any Codeberg/GitHub/GitHub URL tokens found in the specified text/-lines.
|
|
135
130
|
|
|
136
131
|
:param text: text, specified either as str object or as a list of str objects (lines),
|
|
@@ -144,7 +139,7 @@ def mask_token(text: str | list[str]) -> str | list[str]:
|
|
|
144
139
|
else:
|
|
145
140
|
lines = list(text) # copy to not change text list content
|
|
146
141
|
|
|
147
|
-
url_beg =
|
|
142
|
+
url_beg = f"https{URI_SVC_SEP}" # PDV_REPO_HOST_PROTOCOL
|
|
148
143
|
url_beg_len = len(url_beg)
|
|
149
144
|
for tok_beg, tok_end in ((':', '@codeberg.org'), ('glpat-', '@gitlab.com'), ('ghp_', '@github.com')):
|
|
150
145
|
for idx, line in enumerate(lines):
|
|
@@ -161,7 +156,7 @@ def mask_token(text: str | list[str]) -> str | list[str]:
|
|
|
161
156
|
def output_line_split(output: bytes) -> list[str]:
|
|
162
157
|
""" decode and split the specified shell/console output streams into line chunks.
|
|
163
158
|
|
|
164
|
-
:param output: captured stdout
|
|
159
|
+
:param output: captured `stdout`/`stderr` output from an executed OS shell command.
|
|
165
160
|
:return: list of non-empty shell/console output lines, decoded into string.
|
|
166
161
|
"""
|
|
167
162
|
return [line for line in output.decode().splitlines() if line.strip()]
|
|
@@ -170,83 +165,84 @@ def output_line_split(output: bytes) -> list[str]:
|
|
|
170
165
|
def output_zero_split(output: bytes) -> list[str]:
|
|
171
166
|
""" decode and split the specified shell/console output streams separated by `NUL` (\\0) characters.
|
|
172
167
|
|
|
173
|
-
:param output: captured stdout
|
|
168
|
+
:param output: captured `stdout`/`stderr` output from an executed OS shell command (e.g. `env -0`).
|
|
174
169
|
:return: list of non-empty shell/console output chunks, decoded into string.
|
|
175
170
|
"""
|
|
176
171
|
return [line for line in output.decode().split('\0') if line.strip()]
|
|
177
172
|
|
|
178
173
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
"""
|
|
185
|
-
|
|
186
|
-
:param
|
|
187
|
-
|
|
188
|
-
:param
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
174
|
+
def run_cmd(*cmd_args: str,
|
|
175
|
+
output_lines: list[str] | None = None,
|
|
176
|
+
app_obj: AppBase | UnsetType | None = None,
|
|
177
|
+
decoder_splitter: Callable[[bytes], list[str]] = output_line_split,
|
|
178
|
+
**run_kwargs) -> int:
|
|
179
|
+
""" run command in the current working directory, capturing/logging errors and optionally returning console output.
|
|
180
|
+
|
|
181
|
+
:param cmd_args: command line string or args sequence of command name and arguments
|
|
182
|
+
to be run/executed on the console/shell via :func:`subprocess.run`.
|
|
183
|
+
:param output_lines: specify a list to be extended with the lines printed on the console/shell `stdout`
|
|
184
|
+
stream, and to also hide this output on the console. if and how the `stderr` stream get
|
|
185
|
+
also either hidden, captured or to be redirected to this list can be controlled via the
|
|
186
|
+
value passed in the kwarg :paramref:`~subprocess.Popen.stderr`: if this argument is a
|
|
187
|
+
list, and you specified the argument value :data:`subprocess.PIPE`, then the `stderr`
|
|
188
|
+
output will get added at the end of this list (enclosed between the list items
|
|
189
|
+
:data:`STDERR_BEG_MARKER` and :data:`STDERR_END_MARKER`). if this argument is a list,
|
|
190
|
+
and you specified the argument value :data:`subprocess.STDOUT` then the `stderr` outputs
|
|
191
|
+
will get merged without any markers into this list in the order they get printed.
|
|
192
|
+
specify :data:`subprocess.DEVNULL` in :paramref:`~subprocess.Popen.stderr` to hide any
|
|
193
|
+
`stderr` output onto the console/shell as well as in this list argument.
|
|
194
|
+
specifying `None` as :paramref:`~subprocess.Popen.stderr` argument value
|
|
195
|
+
(the default argument) then the `stderr` output will be printed only on the console.
|
|
194
196
|
:param app_obj: optional :class:`~ae.core.AppBase`/:class:`~ae.console.ConsoleApp` instance, used for
|
|
195
197
|
logging. if not specified or None and if :func:`~ae.core.main_app_instance()` returns
|
|
196
198
|
None then the Python :func:`print` function is used.
|
|
197
199
|
specify :data:`~ae.base.UNSET` to suppress any printing/logging output.
|
|
198
|
-
:param
|
|
199
|
-
documentation of the parameter :paramref:`~subprocess.run.shell` of the
|
|
200
|
-
:func:`subprocess.run` function).
|
|
201
|
-
:param env_vars: OS shell environment variables to be used instead of the console/bash defaults.
|
|
202
|
-
if not specified or `UNSET` then an isolated dict copy of :attr:`os.environ` will get
|
|
203
|
-
passed onto :func:`subprocess.run` in order to avoid potential runtime errors, if the
|
|
204
|
-
parent process (or a concurrent thread) modifies :attr:`os.environ` during the creation
|
|
205
|
-
of the subprocess. Additionally, with the convertion of the special :class:`_Environ`
|
|
206
|
-
object into a standard dict, the execution will result slightly faster and avoids
|
|
207
|
-
any special method overrides interfering with the child process creation. it also is
|
|
208
|
-
preventing rare errors with multithread-processes that try to change OS env variable
|
|
209
|
-
(e.g. Conda in relation with pip could lead to raise a `RuntimeError: dictionary
|
|
210
|
-
changed size during iteration` exception).
|
|
211
|
-
specify `None` in order to use the original/unisolated :attr:`os.environ` object.
|
|
212
|
-
:param err_redirect: this argument controls if and how the output of the executed command onto the console
|
|
213
|
-
stderr stream gets captured/redirected. it gets passed directly onto the
|
|
214
|
-
:paramref:`~subprocess.run.stderr` argument of :func:`subprocess.run` function.
|
|
215
|
-
if the argument of :paramref:`.output_lines` is a list, and you specified the
|
|
216
|
-
argument value :data:`subprocess.PIPE`, then the stderr output will get added at the
|
|
217
|
-
end of this list (enclosed between the list items :data:`STDERR_BEG_MARKER` and
|
|
218
|
-
:data:`STDERR_END_MARKER`). if the argument of :paramref:`.output_lines` is a
|
|
219
|
-
list, and you specified the argument value :data:`subprocess.STDOUT` then the stderr
|
|
220
|
-
output will get merged without any markers into this list in the order they get printed.
|
|
221
|
-
specify :data:`subprocess.DEVNULL` to hide any stderr output onto the console/shell
|
|
222
|
-
as well as in the :paramref:`.output_lines` list. if you specify `None`
|
|
223
|
-
(the default argument) then the stderr output will be printed only on the console.
|
|
224
|
-
:param decoder_splitter: callable to decode and split the output from the stdout/stderr streams into a list
|
|
200
|
+
:param decoder_splitter: callable to decode and split the output from the `stdout`/`stderr` streams into a list
|
|
225
201
|
of string chunks/lines, to be added to and returned by :paramref:`.output_lines`.
|
|
202
|
+
:param run_kwargs: kwargs to be passed onto :func:`subprocess.run`, most of them unchanged, like e.g.
|
|
203
|
+
:paramref:`~subprocess.run.input`. some of them, will get adopted/changed before
|
|
204
|
+
they get passed onto :func:`subprocess.run`:
|
|
205
|
+
|
|
206
|
+
* :paramref:`~subprocess.run.check`: will get passed onto :func:`subprocess.run`
|
|
207
|
+
as `True` if not specified in this kwarg (in order to catch and log the
|
|
208
|
+
:class:`subprocess.CalledProcessError` exception in debug mode).
|
|
209
|
+
* :paramref:`~subprocess.Popen.env`: shell environment variables to be used instead of
|
|
210
|
+
the currently set OS shell variable values.
|
|
211
|
+
only if the value of this argument does not get specified or has the value `UNSET`,
|
|
212
|
+
then an isolated dict copy of :attr:`os.environ` will get passed onto
|
|
213
|
+
:func:`subprocess.run` in order to avoid potential runtime errors, caused by a parent
|
|
214
|
+
process (or a concurrent thread) if it modifies :attr:`os.environ` during the creation
|
|
215
|
+
of this subprocess. additionally, with the convertion of the special :class:`_Environ`
|
|
216
|
+
object into a standard dict, the execution will result slightly faster and avoids
|
|
217
|
+
any special method overrides interfering with the child process creation. it also is
|
|
218
|
+
preventing rare errors with multithread-processes that try to change OS env variable
|
|
219
|
+
(e.g. Conda in relation with pip could lead to raise a `RuntimeError: dictionary
|
|
220
|
+
changed size during iteration` exception). specify `None` as the :paramref:`.env`
|
|
221
|
+
value in order to use the original/unisolated :attr:`os.environ` object.
|
|
222
|
+
* :paramref:`~subprocess.Popen.stdout`: will be passed to :func:`subprocess.run`
|
|
223
|
+
as :data:`subprocess.PIPE` (instead of None) if a list instance got specified
|
|
224
|
+
as the :paramref:`.output_lines` argument.
|
|
225
|
+
|
|
226
226
|
:return: return code of the executed command or 126 if execution raised any other exception.
|
|
227
227
|
"""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
else:
|
|
231
|
-
all_args = shlex.split(command_line) + list(extra_args)
|
|
232
|
-
masked_args = mask_token(all_args)
|
|
228
|
+
masked_args = mask_token(cmd_args)
|
|
229
|
+
|
|
233
230
|
if app_obj is None:
|
|
234
231
|
app_obj = main_app_instance()
|
|
235
232
|
print_out = dummy_function if app_obj is UNSET else app_obj.print_out if isinstance(app_obj, AppBase) else print
|
|
236
233
|
debug_out = dummy_function if app_obj is UNSET else app_obj.debug_out if isinstance(app_obj, AppBase) else print
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
|
|
235
|
+
run_kwargs.setdefault('check', True)
|
|
236
|
+
if run_kwargs.get('env', UNSET) is UNSET:
|
|
237
|
+
run_kwargs['env'] = os.environ.copy()
|
|
238
|
+
if isinstance(output_lines, list):
|
|
239
|
+
run_kwargs.setdefault('stdout', subprocess.PIPE)
|
|
239
240
|
|
|
240
241
|
debug_out(f" . executing {masked_args} at {os.getcwd()=} in {os_env_venv()=}")
|
|
241
|
-
|
|
242
|
+
|
|
243
|
+
result: subprocess.CompletedProcess | subprocess.CalledProcessError # having: stdout/stderr/returncode
|
|
242
244
|
try:
|
|
243
|
-
result = subprocess.run(
|
|
244
|
-
stdout=subprocess.PIPE if isinstance(output_lines, list) else None,
|
|
245
|
-
stderr=err_redirect,
|
|
246
|
-
input=console_input.encode(),
|
|
247
|
-
check=True,
|
|
248
|
-
shell=shell,
|
|
249
|
-
env=env_vars)
|
|
245
|
+
result = subprocess.run(cmd_args, **run_kwargs) # pylint: disable=subprocess-run-check
|
|
250
246
|
except subprocess.CalledProcessError as exc:
|
|
251
247
|
debug_out(f"**** subprocess.run({masked_args}) returned non-zero exit code {exc.returncode}; {exc=}")
|
|
252
248
|
result = exc
|
|
@@ -257,7 +253,7 @@ def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: st
|
|
|
257
253
|
if isinstance(output_lines, list):
|
|
258
254
|
if result.stdout:
|
|
259
255
|
output_lines.extend(decoder_splitter(result.stdout))
|
|
260
|
-
if
|
|
256
|
+
if result.stderr and run_kwargs.get('stderr', None) == subprocess.PIPE:
|
|
261
257
|
output_lines.append(STDERR_BEG_MARKER)
|
|
262
258
|
output_lines.extend(decoder_splitter(result.stderr))
|
|
263
259
|
output_lines.append(STDERR_END_MARKER)
|
|
@@ -265,36 +261,48 @@ def sh_exec(command_line: str, extra_args: Iterable[str] = (), console_input: st
|
|
|
265
261
|
return result.returncode
|
|
266
262
|
|
|
267
263
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
:paramref
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
:param
|
|
287
|
-
to
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
:param
|
|
292
|
-
:paramref:`sh_exec.err_redirect` for more details to the supported argument values.
|
|
293
|
-
if this argument is not specified or has the value :data:`subprocess.DEVNULL` then the
|
|
294
|
-
stderr output will get suppressed on the console and will also not get added to the
|
|
295
|
-
list argument in :paramref:`.output_lines`.
|
|
296
|
-
:param decoder_splitter: callable to decode and split the output from the stdout/stderr streams into a list
|
|
264
|
+
def run_logged_cmd(err_code: int, *cmd_args: str,
|
|
265
|
+
output_lines: list[str] | None = None,
|
|
266
|
+
exit_on_err: bool | str = True,
|
|
267
|
+
app_obj: ConsoleApp | UnsetType | None = None,
|
|
268
|
+
decoder_splitter: Callable[[bytes], list[str]] = output_line_split,
|
|
269
|
+
**run_kwargs) -> int:
|
|
270
|
+
""" run command in the current working directory, optionally capturing/logging console output and exiting on error.
|
|
271
|
+
|
|
272
|
+
:param err_code: error code to be passed onto the console as exit code if the command set an error code
|
|
273
|
+
and the value of the :paramref:`.exit_on_err` argument is `True` or a nonempty string.
|
|
274
|
+
:param cmd_args: command line string or a sequence of command name and separate line arguments
|
|
275
|
+
to be run/executed on the console/shell.
|
|
276
|
+
:param output_lines: optional list extended with the lines printed to `stdout`/`stderr` on execution.
|
|
277
|
+
see the :paramref:`~run_cmd.output_lines` argument of :func:`run_cmd` for more details.
|
|
278
|
+
:param exit_on_err: specifying `True` (the default) or a nonempty string will shut-down/quit/exit the
|
|
279
|
+
currently running app (the Python interpreter) if the executed command set an
|
|
280
|
+
error code. a nonempty string will get printed/logged to `stdout` before the exit.
|
|
281
|
+
pass `False` or an empty string to not exit the app if a command error occurred.
|
|
282
|
+
:param app_obj: optional :class:`~ae.console.ConsoleApp` instance, used for logging and error checking
|
|
283
|
+
(with the option to ignore errors if the app got started with the `--force` option).
|
|
284
|
+
if not specified or None and if :func:`~ae.core.main_app_instance()` returns
|
|
285
|
+
None then the Python :func:`print` function is used for logging.
|
|
286
|
+
specify :data:`~ae.base.UNSET` to suppress any printing/logging output.
|
|
287
|
+
:param decoder_splitter: callable to decode and split the output from the `stdout`/`stderr` streams into a list
|
|
297
288
|
of string chunks/lines, to be added to and returned by :paramref:`.output_lines`.
|
|
289
|
+
:param run_kwargs: extra kwargs to be passed onto :func:`run_cmd` and :func:`subprocess.run`. some kwargs
|
|
290
|
+
like e.g. :paramref:`~subprocess.run.input` and :paramref:`~subprocess.Popen.shell`
|
|
291
|
+
will get passed unchanged onto :func:`run_cmd`, others like
|
|
292
|
+
:paramref:`~run_cmd.env` or :paramref:`~run_cmd.stderr`, will be first processed by
|
|
293
|
+
this function and/or :func:`run_cmd` before they get passed onto :func:`subprocess.run`:
|
|
294
|
+
|
|
295
|
+
* :paramref:`~subprocess.Popen.stderr`: controls how the output onto `stderr` will get
|
|
296
|
+
captured, redirected and/or returned. if this argument is not specified then the value
|
|
297
|
+
:data:`subprocess.DEVNULL` will get passed onto :func:`run_cmd` and
|
|
298
|
+
:func:`subprocess.run`, which is suppressing any output sent onto `stderr` on the
|
|
299
|
+
console as well as any addition of it to the list argument specified in
|
|
300
|
+
:paramref:`.output_lines`. if you specify the value :data:`subprocess.PIPE` or
|
|
301
|
+
:data:`subprocess.STDOUT` together with a list in the :paramref:`.output_lines`
|
|
302
|
+
argument, then any output onto `stderr` will get added to this list.
|
|
303
|
+
see also the description of the :paramref:`~run_cmd.output_lines` parameter of
|
|
304
|
+
:func:`run_cmd` for more details on the supported values of this parameter.
|
|
305
|
+
|
|
298
306
|
:return: 0 on success, or if an error occurred the error number set by the executed command.
|
|
299
307
|
"""
|
|
300
308
|
if output_lines is None:
|
|
@@ -302,22 +310,25 @@ def sh_exit_if_exec_err(err_code: int, command_line: str,
|
|
|
302
310
|
output_len = 0
|
|
303
311
|
else:
|
|
304
312
|
output_len = len(output_lines)
|
|
313
|
+
|
|
305
314
|
if app_obj is None:
|
|
306
|
-
app_obj = cast(ConsoleApp, main_app_instance()) # calls app_obj
|
|
315
|
+
app_obj = cast(ConsoleApp, main_app_instance()) # calls ConsoleApp./app_obj.chk() method
|
|
316
|
+
|
|
317
|
+
run_kwargs.setdefault('stderr', subprocess.DEVNULL)
|
|
307
318
|
|
|
308
|
-
sh_err =
|
|
309
|
-
|
|
319
|
+
sh_err = run_cmd(*cmd_args, output_lines=output_lines, app_obj=app_obj, decoder_splitter=decoder_splitter,
|
|
320
|
+
**run_kwargs)
|
|
310
321
|
|
|
311
322
|
if isinstance(app_obj, ConsoleApp) and (app_obj.debug or sh_err and exit_on_err):
|
|
312
323
|
for line in output_lines[output_len:]:
|
|
313
324
|
if app_obj.verbose or not line.startswith("LOG: "): # if verbose show mypy's endless (stderr) log entries
|
|
314
325
|
app_obj.po(" " * 6 + line)
|
|
315
|
-
command = mask_token(
|
|
326
|
+
command = mask_token(cmd_args)
|
|
316
327
|
if sh_err == 0:
|
|
317
328
|
app_obj.dpo(f" = successfully executed {command=}")
|
|
318
329
|
else:
|
|
319
|
-
if
|
|
320
|
-
app_obj.po(f" {
|
|
321
|
-
app_obj.chk(err_code, not exit_on_err, f"
|
|
330
|
+
if isinstance(exit_on_err, str):
|
|
331
|
+
app_obj.po(f" {exit_on_err}")
|
|
332
|
+
app_obj.chk(err_code, not bool(exit_on_err), f"run_logged_cmd({err_code}, {command!r}) error {sh_err}")
|
|
322
333
|
|
|
323
334
|
return sh_err
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ae_shell
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.19
|
|
4
4
|
Summary: ae namespace module portion shell: shell execution and environment helpers
|
|
5
5
|
Home-page: https://gitlab.com/ae-group/ae_shell
|
|
6
6
|
Author: AndiEcker
|
|
@@ -64,13 +64,13 @@ Dynamic: summary
|
|
|
64
64
|
|
|
65
65
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project ae.ae v0.3.110 -->
|
|
66
66
|
<!-- THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.namespace_root_tpls v0.3.33 -->
|
|
67
|
-
# shell 0.3.
|
|
67
|
+
# shell 0.3.19
|
|
68
68
|
|
|
69
69
|
[](
|
|
70
70
|
https://gitlab.com/ae-group/ae_shell)
|
|
71
71
|
[](
|
|
73
|
+
https://gitlab.com/ae-group/ae_shell/-/tree/release0.3.19)
|
|
74
74
|
[](
|
|
75
75
|
https://pypi.org/project/ae-shell/#history)
|
|
76
76
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.
|
|
1
|
+
# THIS FILE IS EXCLUSIVELY MAINTAINED by the project aedev.project_tpls v0.3.104
|
|
2
2
|
""" setup of ae namespace module portion shell: shell execution and environment helpers. """
|
|
3
3
|
import pathlib
|
|
4
4
|
import sys
|
|
@@ -77,7 +77,7 @@ setup_kwargs: dict[str, Any] = {
|
|
|
77
77
|
},
|
|
78
78
|
'python_requires': '>=3.12',
|
|
79
79
|
'url': 'https://gitlab.com/ae-group/ae_shell',
|
|
80
|
-
'version': '0.3.
|
|
80
|
+
'version': '0.3.19',
|
|
81
81
|
'zip_safe': True,
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -5,7 +5,7 @@ import sys
|
|
|
5
5
|
import subprocess
|
|
6
6
|
from unittest.mock import PropertyMock, patch
|
|
7
7
|
|
|
8
|
-
from ae.base import UNSET, camel_to_snake, norm_name, os_path_join, write_file
|
|
8
|
+
from ae.base import UNSET, URI_SVC_SEP, camel_to_snake, norm_name, os_path_join, write_file
|
|
9
9
|
from ae.system import load_env_var_defaults, os_env_venv
|
|
10
10
|
from ae.core import DEBUG_LEVEL_DISABLED, DEBUG_LEVEL_ENABLED, DEBUG_LEVEL_VERBOSE
|
|
11
11
|
from ae.console import MAIN_SECTION_NAME, ConsoleApp
|
|
@@ -14,7 +14,7 @@ from ae.console import MAIN_SECTION_NAME, ConsoleApp
|
|
|
14
14
|
from ae.shell import (
|
|
15
15
|
STDERR_BEG_MARKER, STDERR_END_MARKER,
|
|
16
16
|
debug_or_verbose, get_domain_user_var, hint, in_os_env, mask_token,
|
|
17
|
-
output_line_split, output_zero_split,
|
|
17
|
+
output_line_split, output_zero_split, run_cmd, run_logged_cmd)
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class TestHelpers:
|
|
@@ -221,7 +221,7 @@ class TestHelpers:
|
|
|
221
221
|
assert os.environ == os_env
|
|
222
222
|
|
|
223
223
|
def test_mask_token(self):
|
|
224
|
-
url_prefix_str = "https
|
|
224
|
+
url_prefix_str = f"https{URI_SVC_SEP}" # PDV_REPO_HOST_PROTOCOL
|
|
225
225
|
|
|
226
226
|
token = "codeberg token does not have a prefix and only contains hex-digits followed by @ and codeberg.org"
|
|
227
227
|
text = f"a text block containing a codeberg URL with a token: {url_prefix_str}UsaNäm:{token}@codeberg.org"
|
|
@@ -282,68 +282,68 @@ class TestShellExecutions:
|
|
|
282
282
|
assert output_zero_split(STDOUT_LINE + b"\n" + STDERR_LINE + b"\r\n\r\0\r") == [
|
|
283
283
|
STDOUT_LINE.decode() + "\n" + STDERR_LINE.decode() + "\r\n\r"]
|
|
284
284
|
|
|
285
|
-
def
|
|
285
|
+
def test_run_cmd_catch_any_exception(self, capsys):
|
|
286
286
|
with patch("subprocess.run", side_effect=Exception('broad tst exception')):
|
|
287
|
-
assert
|
|
287
|
+
assert run_cmd('any_cmd') == (126, )[0]
|
|
288
288
|
|
|
289
289
|
output = capsys.readouterr().out
|
|
290
290
|
assert 'any_cmd' in output
|
|
291
291
|
assert " raised exception " in output
|
|
292
292
|
assert 'broad tst exception' in output
|
|
293
293
|
|
|
294
|
-
def
|
|
295
|
-
assert
|
|
294
|
+
def test_run_cmd_catch_exit_code_exception(self, capsys):
|
|
295
|
+
assert run_cmd("exit 69", shell=True) == 69
|
|
296
296
|
|
|
297
297
|
output = capsys.readouterr().out
|
|
298
298
|
assert " returned non-zero exit code " in output
|
|
299
299
|
|
|
300
|
-
assert
|
|
300
|
+
assert run_cmd(sys.executable, "-c", "import sys; sys.exit(96)") == 96
|
|
301
301
|
|
|
302
302
|
output = capsys.readouterr().out
|
|
303
303
|
assert " returned non-zero exit code 96" in output
|
|
304
304
|
|
|
305
|
-
def
|
|
306
|
-
|
|
305
|
+
def test_run_cmd_console_output(self, capsys, cons_app):
|
|
306
|
+
run_cmd('any command')
|
|
307
307
|
|
|
308
308
|
out, err = capsys.readouterr()
|
|
309
|
-
assert " . executing ['any
|
|
309
|
+
assert " . executing ['any command']" in out
|
|
310
310
|
assert os.getcwd() in out
|
|
311
311
|
assert os_env_venv() in out
|
|
312
|
-
assert "**** subprocess.run(['any
|
|
312
|
+
assert "**** subprocess.run(['any command']) raised exception" in out
|
|
313
313
|
assert err == ""
|
|
314
314
|
|
|
315
315
|
with patch('ae.console.ConsoleApp.debug_level', new_callable=PropertyMock, return_value=DEBUG_LEVEL_DISABLED):
|
|
316
|
-
|
|
316
|
+
run_cmd('any command')
|
|
317
317
|
|
|
318
318
|
out, err = capsys.readouterr()
|
|
319
319
|
assert " . executing" not in out
|
|
320
|
-
assert "**** subprocess.run(['any
|
|
320
|
+
assert "**** subprocess.run(['any command']) raised exception" in out
|
|
321
321
|
assert err == ""
|
|
322
322
|
|
|
323
323
|
with patch('ae.console.ConsoleApp.debug_level', new_callable=PropertyMock, return_value=DEBUG_LEVEL_DISABLED):
|
|
324
|
-
|
|
324
|
+
run_cmd("any", "command", app_obj=cons_app) # strange: patch('ae.core.AppBase.debug_level') does not work
|
|
325
325
|
|
|
326
326
|
out, err = capsys.readouterr()
|
|
327
327
|
assert " . executing" not in out
|
|
328
328
|
assert "**** subprocess.run(['any', 'command']) raised exception" in out
|
|
329
329
|
assert err == ""
|
|
330
330
|
|
|
331
|
-
|
|
331
|
+
run_cmd('any command', app_obj=UNSET)
|
|
332
332
|
|
|
333
333
|
out, err = capsys.readouterr()
|
|
334
334
|
assert out == ""
|
|
335
335
|
assert err == ""
|
|
336
336
|
|
|
337
|
-
def
|
|
338
|
-
ret =
|
|
337
|
+
def test_run_cmd_console_output_shell(self, capsys):
|
|
338
|
+
ret = run_cmd("echo hello world", shell=True)
|
|
339
339
|
|
|
340
340
|
out, err = capsys.readouterr()
|
|
341
341
|
assert ret == 0
|
|
342
|
-
assert " . executing echo hello world at " in out
|
|
342
|
+
assert " . executing ['echo hello world'] at " in out
|
|
343
343
|
assert out.count("hello world") == 1 # strange: capsys does not get the echo command output w/ shell=True arg
|
|
344
344
|
assert err == ""
|
|
345
345
|
|
|
346
|
-
ret =
|
|
346
|
+
ret = run_cmd("echo hello world", app_obj=UNSET, shell=True)
|
|
347
347
|
|
|
348
348
|
out, err = capsys.readouterr()
|
|
349
349
|
assert ret == 0
|
|
@@ -351,60 +351,43 @@ class TestShellExecutions:
|
|
|
351
351
|
assert err == ""
|
|
352
352
|
|
|
353
353
|
@patch.object(subprocess, 'run', autospec=True)
|
|
354
|
-
def
|
|
354
|
+
def test_run_cmd_run_args(self, mock_method):
|
|
355
355
|
cmd_line = "cmd arg1 arg2"
|
|
356
356
|
extra_args = ['extra_arg1', 'extra_arg2']
|
|
357
357
|
env = os.environ.copy()
|
|
358
358
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
mock_method.assert_called_with(
|
|
362
|
-
shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'', check=True, shell=False, env=env)
|
|
363
|
-
|
|
364
|
-
sh_exec(cmd_line, shell=True)
|
|
359
|
+
run_cmd(*(shlex.split(cmd_line) + extra_args))
|
|
365
360
|
|
|
366
|
-
mock_method.assert_called_with(
|
|
367
|
-
cmd_line, stdout=None, stderr=None, input=b'', check=True, shell=True, env=env)
|
|
361
|
+
mock_method.assert_called_with(tuple(shlex.split(cmd_line) + extra_args), check=True, env=env)
|
|
368
362
|
|
|
369
|
-
|
|
363
|
+
run_cmd(cmd_line, shell=True)
|
|
370
364
|
|
|
371
|
-
mock_method.assert_called_with(
|
|
372
|
-
cmd_line + " " + " ".join(extra_args), stdout=None, stderr=subprocess.DEVNULL, input=b'', check=True,
|
|
373
|
-
shell=True, env=env)
|
|
365
|
+
mock_method.assert_called_with((cmd_line, ), check=True, shell=True, env=env)
|
|
374
366
|
|
|
375
|
-
|
|
367
|
+
run_cmd(cmd_line, *extra_args, input='con_inp')
|
|
376
368
|
|
|
377
|
-
mock_method.assert_called_with(
|
|
378
|
-
shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'con_inp',
|
|
379
|
-
check=True, shell=False, env=env)
|
|
369
|
+
mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), input='con_inp', check=True, env=env)
|
|
380
370
|
|
|
381
|
-
|
|
371
|
+
run_cmd(cmd_line, *extra_args, output_lines=[])
|
|
382
372
|
|
|
383
|
-
mock_method.assert_called_with(
|
|
384
|
-
shlex.split(cmd_line) + extra_args, stdout=subprocess.PIPE, stderr=None, input=b'',
|
|
385
|
-
check=True, shell=False, env=env)
|
|
373
|
+
mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), check=True, stdout=subprocess.PIPE, env=env)
|
|
386
374
|
|
|
387
|
-
|
|
375
|
+
run_cmd(cmd_line, *extra_args, input='con_inp', output_lines=[], stderr=subprocess.STDOUT)
|
|
388
376
|
|
|
389
|
-
mock_method.assert_called_with(
|
|
390
|
-
|
|
391
|
-
check=True, shell=False, env=env)
|
|
377
|
+
mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), input='con_inp',
|
|
378
|
+
stderr=subprocess.STDOUT, stdout=subprocess.PIPE, check=True, env=env)
|
|
392
379
|
|
|
393
380
|
env_vars = {'A': "1", 'C': "tst_string value"}
|
|
394
381
|
|
|
395
|
-
|
|
382
|
+
run_cmd(cmd_line, *extra_args, env=env_vars)
|
|
396
383
|
|
|
397
|
-
mock_method.assert_called_with(
|
|
398
|
-
shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'',
|
|
399
|
-
check=True, shell=False, env=env_vars)
|
|
384
|
+
mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), env=env_vars, check=True)
|
|
400
385
|
|
|
401
|
-
|
|
386
|
+
run_cmd(cmd_line, *extra_args, env=None)
|
|
402
387
|
|
|
403
|
-
mock_method.assert_called_with(
|
|
404
|
-
shlex.split(cmd_line) + extra_args, stdout=None, stderr=None, input=b'',
|
|
405
|
-
check=True, shell=False, env=None)
|
|
388
|
+
mock_method.assert_called_with((cmd_line, ) + tuple(extra_args), check=True, env=None)
|
|
406
389
|
|
|
407
|
-
def
|
|
390
|
+
def test_run_cmd_run_returned_values(self):
|
|
408
391
|
def _run_return(*_args, **_kwargs):
|
|
409
392
|
""" mock to simulate subprocess.run return object. """
|
|
410
393
|
class _Return:
|
|
@@ -416,7 +399,7 @@ class TestShellExecutions:
|
|
|
416
399
|
with patch('ae.shell.subprocess.run', new=_run_return): # @patch.object(subprocess, 'run', new=_run_return)
|
|
417
400
|
output_lines = []
|
|
418
401
|
|
|
419
|
-
assert
|
|
402
|
+
assert run_cmd("cmd_line", output_lines=output_lines, stderr=subprocess.PIPE) == RETURN_CODE
|
|
420
403
|
|
|
421
404
|
assert output_lines[0] == STDOUT_LINE.decode()
|
|
422
405
|
assert output_lines[1] == STDERR_BEG_MARKER
|
|
@@ -425,7 +408,7 @@ class TestShellExecutions:
|
|
|
425
408
|
|
|
426
409
|
output_lines = []
|
|
427
410
|
|
|
428
|
-
assert
|
|
411
|
+
assert run_cmd("cmd_line", output_lines=output_lines) == RETURN_CODE
|
|
429
412
|
|
|
430
413
|
assert len(output_lines) == 1
|
|
431
414
|
assert output_lines[0] == STDOUT_LINE.decode()
|
|
@@ -435,7 +418,7 @@ class TestShellExecutions:
|
|
|
435
418
|
|
|
436
419
|
output_lines = ['first line', 'second line']
|
|
437
420
|
|
|
438
|
-
assert
|
|
421
|
+
assert run_cmd("cmd_line", output_lines=output_lines, err_redirect=subprocess.STDOUT) == RETURN_CODE
|
|
439
422
|
|
|
440
423
|
assert len(output_lines) == 3
|
|
441
424
|
assert output_lines[0] == 'first line'
|
|
@@ -445,11 +428,10 @@ class TestShellExecutions:
|
|
|
445
428
|
assert STDERR_BEG_MARKER not in output_lines
|
|
446
429
|
assert STDERR_END_MARKER not in output_lines
|
|
447
430
|
|
|
448
|
-
def
|
|
449
|
-
args =
|
|
450
|
-
'extra_args': ["-c", "import sys; print('tst_std_err', file=sys.stderr); print('tst_std_out')"]}
|
|
431
|
+
def test_run_cmd_stderr_redirection(self, capfd, cons_app): # pytest/capsys replaces sys.stdout/.stderr
|
|
432
|
+
args = [sys.executable, "-c", "import sys; print('tst_std_err', file=sys.stderr); print('tst_std_out')"]
|
|
451
433
|
|
|
452
|
-
assert
|
|
434
|
+
assert run_cmd(*args) == 0
|
|
453
435
|
|
|
454
436
|
out, err = capfd.readouterr()
|
|
455
437
|
assert out.count('tst_std_out') == 2
|
|
@@ -458,7 +440,7 @@ class TestShellExecutions:
|
|
|
458
440
|
|
|
459
441
|
redirected = []
|
|
460
442
|
|
|
461
|
-
assert
|
|
443
|
+
assert run_cmd(*args, output_lines=redirected) == 0
|
|
462
444
|
|
|
463
445
|
out, err = capfd.readouterr()
|
|
464
446
|
assert out.count('tst_std_out') == 1
|
|
@@ -468,7 +450,7 @@ class TestShellExecutions:
|
|
|
468
450
|
|
|
469
451
|
redirected = []
|
|
470
452
|
|
|
471
|
-
assert
|
|
453
|
+
assert run_cmd(*args, output_lines=redirected, stderr=subprocess.DEVNULL) == 0
|
|
472
454
|
|
|
473
455
|
out, err = capfd.readouterr()
|
|
474
456
|
assert err == ''
|
|
@@ -476,7 +458,7 @@ class TestShellExecutions:
|
|
|
476
458
|
|
|
477
459
|
redirected = []
|
|
478
460
|
|
|
479
|
-
assert
|
|
461
|
+
assert run_cmd(*args, output_lines=redirected, stderr=subprocess.PIPE) == 0
|
|
480
462
|
|
|
481
463
|
out, err = capfd.readouterr()
|
|
482
464
|
assert err == ''
|
|
@@ -484,18 +466,17 @@ class TestShellExecutions:
|
|
|
484
466
|
|
|
485
467
|
redirected = []
|
|
486
468
|
|
|
487
|
-
assert
|
|
469
|
+
assert run_cmd(*args, output_lines=redirected, stderr=subprocess.STDOUT) == 0
|
|
488
470
|
|
|
489
471
|
out, err = capfd.readouterr()
|
|
490
472
|
assert err == ''
|
|
491
473
|
assert redirected == ['tst_std_err', 'tst_std_out']
|
|
492
474
|
|
|
493
|
-
def
|
|
475
|
+
def test_run_logged_cmd_any_command(self, capsys, cons_app):
|
|
494
476
|
output = ['old output']
|
|
495
477
|
|
|
496
478
|
with patch('ae.console.ConsoleApp.debug', new_callable=PropertyMock, return_value=True): # with app_obj kwarg
|
|
497
|
-
ret =
|
|
498
|
-
exit_on_err=False, exit_msg='tst exit message', app_obj=cons_app)
|
|
479
|
+
ret = run_logged_cmd(0, "git", "--version", output_lines=output, exit_on_err=False, app_obj=cons_app)
|
|
499
480
|
|
|
500
481
|
assert ret == 0
|
|
501
482
|
assert len(output) >= 2
|
|
@@ -503,49 +484,46 @@ class TestShellExecutions:
|
|
|
503
484
|
assert isinstance(output[1], str) # e.g. == 'git version 2.55.0'
|
|
504
485
|
out, err = capsys.readouterr()
|
|
505
486
|
assert 'old output' not in out
|
|
506
|
-
assert 'git --version' in out
|
|
507
|
-
assert 'tst exit message' not in out
|
|
487
|
+
assert "\n . executing ['git', '--version'] at os.getcwd()='" in out
|
|
508
488
|
assert err == ""
|
|
509
489
|
|
|
510
|
-
def
|
|
511
|
-
ret =
|
|
490
|
+
def test_run_logged_cmd_any_invalid_command(self, capsys, cons_app, patched_shutdown_wrapper):
|
|
491
|
+
ret = run_logged_cmd(693, 'tst_command_line', exit_on_err=False)
|
|
512
492
|
|
|
513
493
|
assert ret == (126, )[0]
|
|
514
494
|
out, err = capsys.readouterr()
|
|
515
495
|
assert out.count('tst_command_line') == 3
|
|
516
|
-
assert 'tst exit message' in out
|
|
517
496
|
assert err == ""
|
|
518
497
|
|
|
519
|
-
def
|
|
520
|
-
ret = patched_shutdown_wrapper(
|
|
498
|
+
def test_run_logged_cmd_caught_shutdown_exception(self, capsys, cons_app, patched_shutdown_wrapper):
|
|
499
|
+
ret = patched_shutdown_wrapper(run_logged_cmd, 693, 'tst_command_line', exit_on_err='tst exit message')
|
|
521
500
|
|
|
522
501
|
assert len(ret) == 1
|
|
523
502
|
assert ret[0]['exit_code'] == 693 # 1st arg == error code
|
|
524
503
|
assert 'tst_command_line' in ret[0]['error_message']
|
|
525
|
-
assert '
|
|
504
|
+
assert 'run_logged_cmd(' in ret[0]['error_message']
|
|
526
505
|
assert "(693, " in ret[0]['error_message']
|
|
527
506
|
out, err = capsys.readouterr()
|
|
528
507
|
assert out.count('tst_command_line') == 3
|
|
529
508
|
assert 'tst exit message' in out
|
|
530
509
|
assert err == ""
|
|
531
510
|
|
|
532
|
-
def
|
|
511
|
+
def test_run_logged_cmd_exception(self, capsys, cons_app):
|
|
533
512
|
output = ['old output']
|
|
534
513
|
|
|
535
|
-
ret =
|
|
514
|
+
ret = run_logged_cmd(693, "", output_lines=output, exit_on_err=False)
|
|
536
515
|
|
|
537
516
|
assert ret == (126, )[0]
|
|
538
517
|
assert output == ['old output']
|
|
539
518
|
out, err = capsys.readouterr()
|
|
540
|
-
assert f". executing [] at os.getcwd()='{os.getcwd()}' in os_env_venv()='{os_env_venv()}'" in out
|
|
541
|
-
assert 'tst exit message' in out
|
|
519
|
+
assert f". executing [''] at os.getcwd()='{os.getcwd()}' in os_env_venv()='{os_env_venv()}'" in out
|
|
542
520
|
assert err == ""
|
|
543
521
|
|
|
544
|
-
def
|
|
522
|
+
def test_run_logged_cmd_with_app(self, capsys, cons_app):
|
|
545
523
|
output = []
|
|
546
524
|
|
|
547
525
|
with patch('ae.console.ConsoleApp.debug', new_callable=PropertyMock, return_value=False):
|
|
548
|
-
ret =
|
|
526
|
+
ret = run_logged_cmd(0, "_err", output_lines=output, exit_on_err=False, stderr=subprocess.STDOUT)
|
|
549
527
|
|
|
550
528
|
assert ret == (126, )[0]
|
|
551
529
|
assert output == []
|
|
@@ -553,26 +531,24 @@ class TestShellExecutions:
|
|
|
553
531
|
assert out.count('_err') == 3
|
|
554
532
|
assert err == ""
|
|
555
533
|
|
|
556
|
-
def
|
|
534
|
+
def test_run_logged_cmd_with_app_kwarg_and_empty_exit_on_err_msg(self, capsys, cons_app):
|
|
557
535
|
output = []
|
|
558
536
|
|
|
559
537
|
with patch('ae.console.ConsoleApp.debug', new_callable=PropertyMock, return_value=True): # with app_obj kwarg
|
|
560
|
-
ret =
|
|
561
|
-
exit_on_err=False, exit_msg='tst exit message', app_obj=cons_app)
|
|
538
|
+
ret = run_logged_cmd(0, 'error-command', output_lines=output, exit_on_err="", app_obj=cons_app)
|
|
562
539
|
|
|
563
540
|
assert ret == (126, )[0]
|
|
564
541
|
assert output == []
|
|
565
542
|
out, err = capsys.readouterr()
|
|
566
|
-
assert out.count('error-command') == 3
|
|
567
|
-
assert 'tst exit message' in out
|
|
543
|
+
assert out.count('error-command') == 3 # extra empty line
|
|
568
544
|
assert err == ""
|
|
569
545
|
|
|
570
|
-
def
|
|
546
|
+
def test_run_logged_cmd_with_app_extending_output(self, capsys, cons_app):
|
|
571
547
|
output = ['any old line output']
|
|
572
548
|
|
|
573
|
-
with (patch('ae.shell.
|
|
549
|
+
with (patch('ae.shell.run_cmd', new=lambda *_, **kwargs: kwargs['output_lines'].append('new out line') or 0),
|
|
574
550
|
patch('ae.console.ConsoleApp.debug', new_callable=PropertyMock, return_value=True)):
|
|
575
|
-
ret =
|
|
551
|
+
ret = run_logged_cmd(369, "any_cmd", output_lines=output) # extended output_lines and ret==0
|
|
576
552
|
|
|
577
553
|
assert ret == 0
|
|
578
554
|
assert output == ['any old line output', 'new out line']
|
|
@@ -583,14 +559,13 @@ class TestShellExecutions:
|
|
|
583
559
|
assert out.count('any_cmd') == 1
|
|
584
560
|
assert err == ""
|
|
585
561
|
|
|
586
|
-
def
|
|
562
|
+
def test_run_logged_cmd_without_app(self, capsys):
|
|
587
563
|
output = []
|
|
588
564
|
|
|
589
|
-
ret =
|
|
565
|
+
ret = run_logged_cmd(0, "_err_cmd", output_lines=output, exit_on_err=False)
|
|
590
566
|
|
|
591
567
|
assert ret == (126, )[0]
|
|
592
568
|
assert output == []
|
|
593
569
|
out, err = capsys.readouterr()
|
|
594
570
|
assert out.count('_err_cmd') == 3
|
|
595
|
-
assert 'tst exit message' not in out
|
|
596
571
|
assert err == ""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|