From daebf7552d009c7e1226601bb6b6d01ecc0901b6 Mon Sep 17 00:00:00 2001 From: Fabs Date: Tue, 18 Dec 2018 18:28:39 +0100 Subject: [PATCH] feature: add 'exec' returning a process and deprecates 'exec_command' --- dcos_test_utils/dcos_cli.py | 62 +++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + tests/test_dcos_cli.py | 41 +++++++++++++++++++++++- 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/dcos_test_utils/dcos_cli.py b/dcos_test_utils/dcos_cli.py index 2cbeff9..aafb21f 100644 --- a/dcos_test_utils/dcos_cli.py +++ b/dcos_test_utils/dcos_cli.py @@ -14,6 +14,7 @@ import tempfile from typing import Optional +import deprecation import requests log = logging.getLogger(__name__) @@ -92,6 +93,67 @@ def clear_cli_dir(): if os.path.exists(path): shutil.rmtree(path) + def exec(self, cmd: str, stdin=None, check=True) -> subprocess.CompletedProcess: + """Execute CLI command and processes result. + + This method expects that process won't block. + + :param cmd: Program and arguments + :type cmd: str + :param stdin: File to use for stdin + :param check: Does it check for raised errors + :type stdin: typing.optional[File] + :returns: A tuple with stdout and stderr + :rtype: subprocess.CompletedProcess + + :raises subprocess.CalledProcessError: When check=True if the returncode of \ + cmd is not 0 + exception description. + """ + + log.info('CMD: {!r}'.format(cmd)) + + # Borrowed from dcos-e2e + # https://github.com/dcos/dcos-e2e/blob/8d4916780ade8caf41dae376fdf47f4253eb52c7/src/dcos_e2e/_common.py#L46-L59 + def safe_decode(output_bytes: bytes) -> str: + """ + Decode a bytestring to Unicode with a safe fallback. + """ + try: + return output_bytes.decode( + encoding='utf-8', + errors='strict') + except UnicodeDecodeError: + return output_bytes.decode( + encoding='ascii', + errors='backslashreplace') + + try: + process = subprocess.run( + cmd, + stdin=stdin, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=self.env, + check=check) + except subprocess.CalledProcessError as e: + stderr = safe_decode(e.stderr) + log.error('STDERR: {}'.format(stderr)) + + stdout = safe_decode(e.stdout) + log.error('STDOUT: {}'.format(stdout)) + + raise + + stdout = safe_decode(process.stdout) + log.info('STDOUT: {}'.format(stdout)) + + stderr = safe_decode(process.stdout) + log.info('STDERR: {}'.format(stderr)) + + return process + + @deprecation.deprecated(details="Deprecated in favor of the `exec` function. DCOS-44823") def exec_command(self, cmd: str, stdin=None) -> tuple: """Execute CLI command and processes result. diff --git a/requirements.txt b/requirements.txt index 224c3d3..ec3abf1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,6 @@ pytest requests retrying tox +deprecation sphinx diff --git a/tests/test_dcos_cli.py b/tests/test_dcos_cli.py index f6440b9..cdba77f 100644 --- a/tests/test_dcos_cli.py +++ b/tests/test_dcos_cli.py @@ -1,9 +1,11 @@ -import pytest import subprocess +import deprecation +import pytest from dcos_test_utils import dcos_cli +@deprecation.deprecated(details="Deprecated in favor of the `exec` function. DCOS-44823") def test_exec_command(caplog): cli = dcos_cli.DcosCli('') stdout, stderr = cli.exec_command( @@ -16,9 +18,46 @@ def test_exec_command(caplog): assert any(rec.message.startswith('STDERR:') for rec in caplog.records) +@deprecation.deprecated(details="Deprecated in favor of the `exec` function. DCOS-44823") def test_exec_command_fail(caplog): cli = dcos_cli.DcosCli('') with pytest.raises(subprocess.CalledProcessError): cli.exec_command(['/bin/sh', '-c', 'does-not-exist']) assert any(rec.message.startswith('CMD:') for rec in caplog.records) assert any(rec.message.startswith('STDERR:') for rec in caplog.records) + + +def test_exec(caplog): + cli = dcos_cli.DcosCli('') + process = cli.exec( + ['/bin/sh', '-c', 'echo "hello, world!"'] + ) + assert process.stdout == b'hello, world!\n' + assert process.stderr == b'' + assert process.returncode == 0 + assert any(rec.message.startswith('CMD:') for rec in caplog.records) + assert any(rec.message.startswith('STDOUT:') for rec in caplog.records) + assert any(rec.message.startswith('STDERR:') for rec in caplog.records) + + +def test_exec_fail(caplog): + cli = dcos_cli.DcosCli('') + with pytest.raises(subprocess.CalledProcessError): + cli.exec(['/bin/sh', '-c', 'does-not-exist']) + assert any(rec.message.startswith('CMD:') for rec in caplog.records) + assert any(rec.message.startswith('STDERR:') for rec in caplog.records) + assert any(rec.message.startswith('STDOUT:') for rec in caplog.records) + + +def test_exec_fail_without_check(caplog): + cli = dcos_cli.DcosCli('') + process = cli.exec( + ['/bin/sh', '-c', 'does-not-exist'], + check=False + ) + assert process.stdout == b'' + assert b'does-not-exist' in process.stderr + assert process.returncode == 127 + assert any(rec.message.startswith('CMD:') for rec in caplog.records) + assert any(rec.message.startswith('STDOUT:') for rec in caplog.records) + assert any(rec.message.startswith('STDERR:') for rec in caplog.records)