diff --git a/leapp/libraries/stdlib/__init__.py b/leapp/libraries/stdlib/__init__.py index bf31b6b41..8b51520b6 100644 --- a/leapp/libraries/stdlib/__init__.py +++ b/leapp/libraries/stdlib/__init__.py @@ -146,7 +146,7 @@ def _restore_level(handlers, levels): def run(args, split=False, callback_raw=_console_logging_handler, callback_linebuffered=_logfile_logging_handler, - env=None, checked=True): + env=None, checked=True, stdin=None): """ Run a command and return its result as a dict. @@ -162,6 +162,8 @@ def run(args, split=False, callback_raw=_console_logging_handler, callback_lineb :type env: dict :param checked: Raise an exception on a non-zero exit code, default True :type checked: bool + :param stdin: String or a file descriptor that will be written to stdin of the child process + :type stdin: int, str :return: {'stdout' : stdout, 'stderr': stderr, 'signal': signal, 'exit_code': exit_code, 'pid': pid} :rtype: dict """ @@ -174,7 +176,8 @@ def run(args, split=False, callback_raw=_console_logging_handler, callback_lineb result = None try: create_audit_entry('process-start', {'id': _id, 'parameters': args, 'env': env}) - result = _call(args, callback_raw=callback_raw, callback_linebuffered=callback_linebuffered, env=env) + result = _call(args, callback_raw=callback_raw, callback_linebuffered=callback_linebuffered, + stdin=stdin, env=env) if checked and result['exit_code'] != 0: message = 'Command {0} failed with exit code {1}.'.format(str(args), result.get('exit_code')) api.current_logger().debug(message) diff --git a/tests/scripts/test_stdlib.py b/tests/scripts/test_stdlib.py index 48f6f3ada..fce04b982 100644 --- a/tests/scripts/test_stdlib.py +++ b/tests/scripts/test_stdlib.py @@ -5,35 +5,60 @@ from leapp.libraries.stdlib.config import is_debug, is_verbose +_STDIN = [[], {}, 0.123, lambda: None] + + def test_check_single_line_output(): - a_command = ['echo', 'This a single line test!'] - assert run(a_command, split=True)['stdout'] == [u'This a single line test!'] + cmd = ['echo', 'This a single line test!'] + assert run(cmd, split=True)['stdout'] == [u'This a single line test!'] def test_check_single_line_output_no_split(): - a_command = ['echo', 'This a single line No Split test!'] - assert run(a_command, split=False)['stdout'] == u'This a single line No Split test!\n' + cmd = ['echo', 'This a single line No Split test!'] + assert run(cmd, split=False)['stdout'] == u'This a single line No Split test!\n' def test_check_multiline_output(): - a_command = ['echo', 'This a multi-\nline test!'] - assert run(a_command, split=True)['stdout'] == [u'This a multi-', u'line test!'] + cmd = ['echo', 'This a multi-\nline test!'] + assert run(cmd, split=True)['stdout'] == [u'This a multi-', u'line test!'] def test_check_multiline_output_no_split(): - a_command = ['echo', 'This a multi-\nline No Split test!'] - assert run(a_command, split=False)['stdout'] == u'This a multi-\nline No Split test!\n' + cmd = ['echo', 'This a multi-\nline No Split test!'] + assert run(cmd, split=False)['stdout'] == u'This a multi-\nline No Split test!\n' def test_check_error(): - a_command = ['false'] + cmd = ['false'] with pytest.raises(CalledProcessError): - run(a_command, checked=True) + run(cmd, checked=True) def test_check_error_no_checked(): - a_command = ['false'] - assert run(a_command, checked=False)['exit_code'] == 1 + cmd = ['false'] + assert run(cmd, checked=False)['exit_code'] == 1 + + +def test_stdin_string(): + ret = run(('bash', '-c', 'read MSG; echo "<$MSG>"'), stdin='LOREM IPSUM') + assert ret['stdout'] == '\n' + + +def test_stdin_fd(): + r, w = os.pipe() + # The string we write here should not exceed `/proc/sys/fs/pipe-max-size` + # which represents the size of the kernel buffer backing the pipe + os.write(w, b'LOREM IPSUM') + os.close(w) + ret = run(('bash', '-c', 'read MSG; echo "<$MSG>"'), stdin=r) + os.close(r) + assert ret['stdout'] == '\n' + + +@pytest.mark.parametrize('p', _STDIN) +def test_stdin_check(p): + with pytest.raises(TypeError): + run(('true',), stdin=p) def test_is_verbose(monkeypatch):