Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 15 additions & 29 deletions mobly/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import signal
import string
import subprocess
import threading
import time
import traceback
from typing import Literal, Tuple, overload
Expand Down Expand Up @@ -474,36 +473,23 @@ def run_command(
shell=shell,
cwd=cwd,
env=env,
universal_newlines=universal_newlines,
text=universal_newlines, # "text" is introdcued in Python 3.7.
)
timer = None
timer_triggered = threading.Event()
if timeout and timeout > 0:
# The wait method on process will hang when used with PIPEs with large
# outputs, so use a timer thread instead.

def timeout_expired():
timer_triggered.set()
process.terminate()

timer = threading.Timer(timeout, timeout_expired)
timer.start()
# If the command takes longer than the timeout, then the timer thread
# will kill the subprocess, which will make it terminate.
out, err = process.communicate()
if timer is not None:
timer.cancel()
if timer_triggered.is_set():
raise subprocess.TimeoutExpired(
cmd=cmd, timeout=timeout, output=out, stderr=err
out, err = None, None
try:
out, err = process.communicate(timeout=timeout)
except subprocess.TimeoutExpired:
process.kill()
out, err = process.communicate()
raise
finally:
logging.debug(
'cmd: %s, stdout: %s, stderr: %s, ret: %s',
cli_cmd_to_string(cmd),
out,
err,
process.returncode,
)
logging.debug(
'cmd: %s, stdout: %s, stderr: %s, ret: %s',
cli_cmd_to_string(cmd),
out,
err,
process.returncode,
)
return process.returncode, out, err


Expand Down
12 changes: 4 additions & 8 deletions tests/mobly/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,8 @@ def test_run_command_with_timeout_expired(self):
with self.assertRaisesRegex(subprocess.TimeoutExpired, 'sleep'):
_ = utils.run_command(self.sleep_cmd(4), timeout=0.01)

@mock.patch('threading.Timer')
@mock.patch('subprocess.Popen')
def test_run_command_with_default_params(self, mock_popen, mock_timer):
def test_run_command_with_default_params(self, mock_popen):
mock_command = mock.MagicMock(spec=dict)
mock_proc = mock_popen.return_value
mock_proc.communicate.return_value = ('fake_out', 'fake_err')
Expand All @@ -316,13 +315,11 @@ def test_run_command_with_default_params(self, mock_popen, mock_timer):
shell=False,
cwd=None,
env=None,
universal_newlines=False,
text=False,
)
mock_timer.assert_not_called()

@mock.patch('threading.Timer')
@mock.patch('subprocess.Popen')
def test_run_command_with_custom_params(self, mock_popen, mock_timer):
def test_run_command_with_custom_params(self, mock_popen):
mock_command = mock.MagicMock(spec=dict)
mock_stdout = mock.MagicMock(spec=int)
mock_stderr = mock.MagicMock(spec=int)
Expand Down Expand Up @@ -352,9 +349,8 @@ def test_run_command_with_custom_params(self, mock_popen, mock_timer):
shell=mock_shell,
cwd=None,
env=mock_env,
universal_newlines=mock_universal_newlines,
text=mock_universal_newlines,
)
mock_timer.assert_called_with(1234, mock.ANY)

def test_run_command_with_universal_newlines_false(self):
_, out, _ = utils.run_command(
Expand Down
Loading