You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
10 KiB
Python
265 lines
10 KiB
Python
"""
|
|
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
its licensors.
|
|
|
|
For complete copyright and license terms please see the LICENSE at the root of this
|
|
distribution (the "License"). All use of this software is governed by the License,
|
|
or, if provided, by the license below or the license accompanying this file. Do not
|
|
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
Unit Tests for watchdog.py
|
|
"""
|
|
import unittest
|
|
import unittest.mock as mock
|
|
import pytest
|
|
|
|
import ly_test_tools.environment.watchdog as watchdog
|
|
|
|
pytestmark = pytest.mark.SUITE_smoke
|
|
|
|
|
|
def mock_bool_fn():
|
|
return
|
|
|
|
|
|
class TestWatchdog(unittest.TestCase):
|
|
|
|
@mock.patch('threading.Event')
|
|
@mock.patch('threading.Thread')
|
|
def test_Watchdog_Instantiated_CreatesEventAndThread(self, mock_thread, mock_event):
|
|
mock_watchdog = watchdog.Watchdog(mock_bool_fn)
|
|
|
|
mock_event.assert_called_once()
|
|
mock_thread.assert_called_once_with(target=mock_watchdog._watchdog, name=mock_watchdog.name)
|
|
|
|
|
|
@mock.patch('threading.Event', mock.MagicMock())
|
|
@mock.patch('threading.Thread', mock.MagicMock())
|
|
class TestWatchdogMethods(unittest.TestCase):
|
|
def setUp(self):
|
|
self.mock_watchdog = watchdog.Watchdog(mock_bool_fn)
|
|
|
|
@mock.patch('threading.Thread.start')
|
|
@mock.patch('threading.Event.clear')
|
|
def test_Start_Called_ClearsEventAndStartsThread(self, mock_clear, mock_start):
|
|
self.mock_watchdog.start()
|
|
|
|
mock_clear.assert_called_once()
|
|
mock_start.assert_called_once()
|
|
|
|
@mock.patch('threading.Thread.join', mock.MagicMock())
|
|
@mock.patch('threading.Event.set')
|
|
def test_Stop_Called_CallsEventSet(self, under_test):
|
|
self.mock_watchdog.stop()
|
|
|
|
under_test.assert_called_once()
|
|
|
|
@mock.patch('threading.Thread.join', mock.MagicMock())
|
|
@mock.patch('threading.Event.set', mock.MagicMock())
|
|
@mock.patch('ly_test_tools.environment.watchdog.logging.Logger.error')
|
|
def test_Stop_NoCaughtFailures_NoRaiseOrError(self, mock_error_log):
|
|
self.mock_watchdog.caught_failure = False
|
|
|
|
try:
|
|
self.mock_watchdog.stop()
|
|
except watchdog.WatchdogError as e:
|
|
self.fail(f"Unexpected WatchdogError called. Error: {e}")
|
|
mock_error_log.assert_not_called()
|
|
|
|
@mock.patch('threading.Thread.join', mock.MagicMock())
|
|
@mock.patch('threading.Event.set', mock.MagicMock())
|
|
@mock.patch('ly_test_tools.environment.watchdog.logging.Logger.error')
|
|
def test_Stop_CaughtFailuresAndRaisesOnCondition_RaisesWatchdogError(self, mock_error_log):
|
|
self.mock_watchdog.caught_failure = True
|
|
self.mock_watchdog._raise_on_condition = True
|
|
|
|
with pytest.raises(watchdog.WatchdogError):
|
|
self.mock_watchdog.stop()
|
|
mock_error_log.assert_not_called()
|
|
|
|
@mock.patch('threading.Thread.join', mock.MagicMock())
|
|
@mock.patch('threading.Event.set', mock.MagicMock())
|
|
@mock.patch('ly_test_tools.environment.watchdog.logging.Logger.error')
|
|
def test_Stop_CaughtFailuresAndNotRaisesOnCondition_LogsError(self, mock_error_log):
|
|
self.mock_watchdog.caught_failure = True
|
|
self.mock_watchdog._raise_on_condition = False
|
|
|
|
try:
|
|
self.mock_watchdog.stop()
|
|
except watchdog.WatchdogError as e:
|
|
self.fail(f"Unexpected WatchdogError called. Error: {e}")
|
|
mock_error_log.assert_called_once()
|
|
|
|
@mock.patch('threading.Thread.join')
|
|
def test_Stop_Called_CallsJoin(self, under_test):
|
|
self.mock_watchdog.caught_failure = False
|
|
self.mock_watchdog.stop()
|
|
|
|
under_test.assert_called_once()
|
|
|
|
@mock.patch('threading.Thread.join', mock.MagicMock())
|
|
@mock.patch('ly_test_tools.environment.watchdog.Watchdog.is_alive')
|
|
@mock.patch('ly_test_tools.environment.watchdog.logging.Logger.error')
|
|
def test_Stop_ThreadIsAlive_LogsError(self, under_test, mock_is_alive):
|
|
mock_is_alive.return_value = True
|
|
self.mock_watchdog.stop()
|
|
|
|
under_test.assert_called_once()
|
|
|
|
@mock.patch('threading.Thread.join', mock.MagicMock())
|
|
@mock.patch('ly_test_tools.environment.watchdog.Watchdog.is_alive')
|
|
@mock.patch('ly_test_tools.environment.watchdog.logging.Logger.error')
|
|
def test_Stop_ThreadNotAlive_NoLogsError(self, under_test, mock_is_alive):
|
|
mock_is_alive.return_value = False
|
|
self.mock_watchdog.stop()
|
|
|
|
under_test.assert_not_called()
|
|
|
|
@mock.patch('threading.Thread.is_alive')
|
|
def test_IsAlive_Called_CallsIsAlive(self, under_test):
|
|
self.mock_watchdog.is_alive()
|
|
|
|
under_test.assert_called_once()
|
|
|
|
@mock.patch('threading.Event.wait')
|
|
def test_WatchdogRunner_ShutdownEventNotSet_CallsBoolFn(self, mock_event_wait):
|
|
mock_event_wait.side_effect = [False, True]
|
|
mock_bool_fn = mock.MagicMock()
|
|
mock_bool_fn.return_value = False
|
|
self.mock_watchdog._bool_fn = mock_bool_fn
|
|
self.mock_watchdog._watchdog()
|
|
|
|
self.mock_watchdog._bool_fn.assert_called_once()
|
|
assert self.mock_watchdog.caught_failure == False
|
|
|
|
def test_WatchdogRunner_ShutdownEventSet_NoCallsBoolFn(self):
|
|
self.mock_watchdog._shutdown.set()
|
|
mock_bool_fn = mock.MagicMock()
|
|
self.mock_watchdog._bool_fn = mock_bool_fn
|
|
under_test = self.mock_watchdog._watchdog()
|
|
|
|
assert under_test is None
|
|
self.mock_watchdog._bool_fn.assert_not_called()
|
|
|
|
@mock.patch('threading.Event.wait')
|
|
def test_WatchdogRunner_BoolFnReturnsTrue_SetsCaughtFailureToTrue(self, mock_event_wait):
|
|
mock_event_wait.side_effect = [False, True]
|
|
mock_bool_fn = mock.MagicMock()
|
|
mock_bool_fn.return_value = True
|
|
self.mock_watchdog._bool_fn = mock_bool_fn
|
|
self.mock_watchdog._watchdog()
|
|
|
|
assert self.mock_watchdog.caught_failure == True
|
|
|
|
|
|
class TestProcessUnresponsiveWatchdog(unittest.TestCase):
|
|
mock_process_id = 11111
|
|
mock_name = 'foo.exe'
|
|
mock_process_not_resp_call = \
|
|
'\r\n " \
|
|
"Image Name PID Session Name Session# Mem Usage\r\n" \
|
|
"========================= ======== ================ =========== ============\r\n" \
|
|
"foo.exe %d Console 1 0 K\r\n"' % mock_process_id
|
|
mock_process_resp_call = "INFO: No tasks are running which match the specified criteria.\r\n"
|
|
|
|
@mock.patch('psutil.Process', mock.MagicMock())
|
|
def setUp(self):
|
|
self.mock_watchdog = watchdog.ProcessUnresponsiveWatchdog(self.mock_process_id)
|
|
self.mock_watchdog._process_name = self.mock_name
|
|
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
def test_ProcessNotResponding_ProcessResponsive_ReturnsFalse(self, mock_check_output):
|
|
mock_check_output.return_value = self.mock_process_resp_call
|
|
self.mock_watchdog._pid = self.mock_process_id
|
|
under_test = self.mock_watchdog._process_not_responding()
|
|
|
|
assert not under_test
|
|
assert self.mock_watchdog._calculated_timeout_point is None
|
|
|
|
@mock.patch('time.time')
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
def test_ProcessNotResponding_ProcessUnresponsiveNoTimeout_ReturnsFalse(self, mock_check_output, mock_time):
|
|
mock_time.return_value = 1
|
|
mock_check_output.return_value = self.mock_process_not_resp_call
|
|
self.mock_watchdog._pid = self.mock_process_id
|
|
under_test = self.mock_watchdog._process_not_responding()
|
|
timeout_under_test = mock_time.return_value + self.mock_watchdog._unresponsive_timeout
|
|
|
|
assert not under_test
|
|
assert self.mock_watchdog._calculated_timeout_point == timeout_under_test
|
|
|
|
@mock.patch('time.time')
|
|
@mock.patch('ly_test_tools.environment.process_utils.check_output')
|
|
def test_ProcessNotResponding_ProcessUnresponsiveReachesTimeout_ReturnsTrue(self, mock_check_output, mock_time):
|
|
mock_time.return_value = 3
|
|
mock_check_output.return_value = self.mock_process_not_resp_call
|
|
self.mock_watchdog._pid = self.mock_process_id
|
|
self.mock_watchdog._calculated_timeout_point = 2
|
|
under_test = self.mock_watchdog._process_not_responding()
|
|
|
|
assert under_test
|
|
|
|
def test_GetPid_Called_ReturnsAttribute(self):
|
|
self.mock_watchdog._pid = self.mock_process_id
|
|
|
|
assert self.mock_watchdog.get_pid() == self.mock_watchdog._pid
|
|
|
|
|
|
class TestCrashLogWatchdog(unittest.TestCase):
|
|
|
|
mock_log_path = 'C:/foo'
|
|
|
|
@mock.patch('os.path.exists')
|
|
def test_CrashExists_Called_CallsOsPathExists(self, under_test):
|
|
under_test.side_effect = [False, mock.DEFAULT]
|
|
mock_watchdog = watchdog.CrashLogWatchdog(self.mock_log_path)
|
|
mock_watchdog._bool_fn()
|
|
|
|
under_test.assert_called_with(self.mock_log_path)
|
|
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.remove')
|
|
def test_CrashLogWatchdog_LogsExist_ClearsExistingLogs(self, under_test, mock_exists):
|
|
mock_exists.return_value = True
|
|
mock_watchdog = watchdog.CrashLogWatchdog(self.mock_log_path)
|
|
|
|
under_test.assert_called_once_with(self.mock_log_path)
|
|
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch('os.remove')
|
|
def test_CrashLogWatchdog_LogsNotExist_NoClearsExistingLogs(self, under_test, mock_exists):
|
|
mock_exists.return_value = False
|
|
mock_watchdog = watchdog.CrashLogWatchdog(self.mock_log_path)
|
|
|
|
under_test.assert_not_called()
|
|
|
|
@mock.patch('threading.Thread.join', mock.MagicMock())
|
|
@mock.patch('builtins.print')
|
|
@mock.patch('builtins.open')
|
|
@mock.patch('os.path.exists')
|
|
def test_CrashLogWatchdogStop_LogsExists_OpensLogAndPrints(self, mock_exists, mock_open, mock_print):
|
|
mock_exists.side_effect = [False, True]
|
|
mock_watchdog = watchdog.CrashLogWatchdog(self.mock_log_path)
|
|
mock_watchdog.caught_failure = True
|
|
mock_watchdog._raise_on_condition = False
|
|
|
|
mock_watchdog.stop()
|
|
|
|
mock_open.assert_called_once_with(mock_watchdog._log_path, "r")
|
|
assert mock_print.called
|
|
|
|
@mock.patch('threading.Thread.join', mock.MagicMock())
|
|
@mock.patch('builtins.print')
|
|
@mock.patch('builtins.open')
|
|
@mock.patch('os.path.exists')
|
|
def test_CrashLogWatchdogStop_LogsNoExists_NoOpensAndNoPrintsLog(self, mock_exists, mock_open, mock_print):
|
|
mock_exists.return_value = False
|
|
mock_watchdog = watchdog.CrashLogWatchdog(self.mock_log_path)
|
|
mock_watchdog.caught_failure = False
|
|
mock_watchdog._raise_on_condition = False
|
|
|
|
mock_watchdog.stop()
|
|
assert not mock_open.called
|
|
assert not mock_print.called
|