Skip to content

Commit daebf75

Browse files
committed
feature: add 'exec' returning a process and deprecates 'exec_command'
1 parent 5c582b0 commit daebf75

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

dcos_test_utils/dcos_cli.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import tempfile
1515
from typing import Optional
1616

17+
import deprecation
1718
import requests
1819

1920
log = logging.getLogger(__name__)
@@ -92,6 +93,67 @@ def clear_cli_dir():
9293
if os.path.exists(path):
9394
shutil.rmtree(path)
9495

96+
def exec(self, cmd: str, stdin=None, check=True) -> subprocess.CompletedProcess:
97+
"""Execute CLI command and processes result.
98+
99+
This method expects that process won't block.
100+
101+
:param cmd: Program and arguments
102+
:type cmd: str
103+
:param stdin: File to use for stdin
104+
:param check: Does it check for raised errors
105+
:type stdin: typing.optional[File]
106+
:returns: A tuple with stdout and stderr
107+
:rtype: subprocess.CompletedProcess
108+
109+
:raises subprocess.CalledProcessError: When check=True if the returncode of \
110+
cmd is not 0
111+
exception description.
112+
"""
113+
114+
log.info('CMD: {!r}'.format(cmd))
115+
116+
# Borrowed from dcos-e2e
117+
# https://github.com/dcos/dcos-e2e/blob/8d4916780ade8caf41dae376fdf47f4253eb52c7/src/dcos_e2e/_common.py#L46-L59
118+
def safe_decode(output_bytes: bytes) -> str:
119+
"""
120+
Decode a bytestring to Unicode with a safe fallback.
121+
"""
122+
try:
123+
return output_bytes.decode(
124+
encoding='utf-8',
125+
errors='strict')
126+
except UnicodeDecodeError:
127+
return output_bytes.decode(
128+
encoding='ascii',
129+
errors='backslashreplace')
130+
131+
try:
132+
process = subprocess.run(
133+
cmd,
134+
stdin=stdin,
135+
stdout=subprocess.PIPE,
136+
stderr=subprocess.PIPE,
137+
env=self.env,
138+
check=check)
139+
except subprocess.CalledProcessError as e:
140+
stderr = safe_decode(e.stderr)
141+
log.error('STDERR: {}'.format(stderr))
142+
143+
stdout = safe_decode(e.stdout)
144+
log.error('STDOUT: {}'.format(stdout))
145+
146+
raise
147+
148+
stdout = safe_decode(process.stdout)
149+
log.info('STDOUT: {}'.format(stdout))
150+
151+
stderr = safe_decode(process.stdout)
152+
log.info('STDERR: {}'.format(stderr))
153+
154+
return process
155+
156+
@deprecation.deprecated(details="Deprecated in favor of the `exec` function. DCOS-44823")
95157
def exec_command(self, cmd: str, stdin=None) -> tuple:
96158
"""Execute CLI command and processes result.
97159

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ pytest
33
requests
44
retrying
55
tox
6+
deprecation
67

78
sphinx

tests/test_dcos_cli.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import pytest
21
import subprocess
32

3+
import deprecation
4+
import pytest
45
from dcos_test_utils import dcos_cli
56

67

8+
@deprecation.deprecated(details="Deprecated in favor of the `exec` function. DCOS-44823")
79
def test_exec_command(caplog):
810
cli = dcos_cli.DcosCli('')
911
stdout, stderr = cli.exec_command(
@@ -16,9 +18,46 @@ def test_exec_command(caplog):
1618
assert any(rec.message.startswith('STDERR:') for rec in caplog.records)
1719

1820

21+
@deprecation.deprecated(details="Deprecated in favor of the `exec` function. DCOS-44823")
1922
def test_exec_command_fail(caplog):
2023
cli = dcos_cli.DcosCli('')
2124
with pytest.raises(subprocess.CalledProcessError):
2225
cli.exec_command(['/bin/sh', '-c', 'does-not-exist'])
2326
assert any(rec.message.startswith('CMD:') for rec in caplog.records)
2427
assert any(rec.message.startswith('STDERR:') for rec in caplog.records)
28+
29+
30+
def test_exec(caplog):
31+
cli = dcos_cli.DcosCli('')
32+
process = cli.exec(
33+
['/bin/sh', '-c', 'echo "hello, world!"']
34+
)
35+
assert process.stdout == b'hello, world!\n'
36+
assert process.stderr == b''
37+
assert process.returncode == 0
38+
assert any(rec.message.startswith('CMD:') for rec in caplog.records)
39+
assert any(rec.message.startswith('STDOUT:') for rec in caplog.records)
40+
assert any(rec.message.startswith('STDERR:') for rec in caplog.records)
41+
42+
43+
def test_exec_fail(caplog):
44+
cli = dcos_cli.DcosCli('')
45+
with pytest.raises(subprocess.CalledProcessError):
46+
cli.exec(['/bin/sh', '-c', 'does-not-exist'])
47+
assert any(rec.message.startswith('CMD:') for rec in caplog.records)
48+
assert any(rec.message.startswith('STDERR:') for rec in caplog.records)
49+
assert any(rec.message.startswith('STDOUT:') for rec in caplog.records)
50+
51+
52+
def test_exec_fail_without_check(caplog):
53+
cli = dcos_cli.DcosCli('')
54+
process = cli.exec(
55+
['/bin/sh', '-c', 'does-not-exist'],
56+
check=False
57+
)
58+
assert process.stdout == b''
59+
assert b'does-not-exist' in process.stderr
60+
assert process.returncode == 127
61+
assert any(rec.message.startswith('CMD:') for rec in caplog.records)
62+
assert any(rec.message.startswith('STDOUT:') for rec in caplog.records)
63+
assert any(rec.message.startswith('STDERR:') for rec in caplog.records)

0 commit comments

Comments
 (0)