[Local Agent] Sync LA feature from source: 1. Update host for EAP partners 2. Fix unit test flakiness Change-Id: I579a1ad95bc6b8de7bd65faab0ccc13d9e353655
diff --git a/local_agent/ams_client.py b/local_agent/ams_client.py index 3382a43..4d1fee6 100644 --- a/local_agent/ams_client.py +++ b/local_agent/ams_client.py
@@ -26,12 +26,6 @@ logger = logger_module.get_logger() -# TODO(b/194648316): Use production host when ready to launch. -# The default AMS server configuration. -_AMS_SCHEME = 'https' -_AMS_HOST = 'chip-testsuite-experimental.appspot.com' -_AMS_PORT = None - # _ENDPOINTS stores the endpoint definitions. # _TIMEOUTS stores the timeout config for the endpoints. _ENDPOINTS = immutabledict.immutabledict({ @@ -47,6 +41,7 @@ _DEFAULT_TIMEOUT = 5.0 _DEFAULT_RETRY = 3 _DEFAULT_RETRY_INTERVAL = 1.0 +_DEFAULT_SCHEME = 'https' _UNLINKED_ERR_MSG = 'Invalid agent id' @@ -92,13 +87,14 @@ APIs to AMS and receive responses. """ - def __init__(self, - host: Optional[str] = None, - port: Optional[int] = None, - scheme: Optional[str] = None): + def __init__( + self, + host: str, + port: Optional[int] = None, + scheme: str = _DEFAULT_SCHEME): """Initializes the AMS client. - The host, port, and scheme parameters are all optional. If not + The port and scheme parameters are optional. If not provided, we will use the defaults. Args: @@ -106,9 +102,6 @@ port: The port of AMS server. scheme: Either 'http' or 'https'. """ - host = host or _AMS_HOST - port = port or _AMS_PORT - scheme = scheme or _AMS_SCHEME if port is not None: self._base_url = f'{scheme}://{host}:{port}' else:
diff --git a/local_agent/local_agent.py b/local_agent/local_agent.py index b029faf..94b1541 100644 --- a/local_agent/local_agent.py +++ b/local_agent/local_agent.py
@@ -55,6 +55,9 @@ _USER_CONFIG_AMS_SCHEME = 'AMS_SCHEME' _USER_CONFIG_ARTIFACTS_DIR = 'ARTIFACTS_DIR' _USER_CONFIG_MATTER_DEVICE_CONTROLLERS = 'MatterDeviceControllerPackages' +_EAP_AMS_HOST = 'rainier-test-suite-eap.appspot.com' +_EAP_AMS_PORT = None +_EAP_AMS_SCHEME = 'https' # ======================================================================= # @@ -550,7 +553,7 @@ sys.argv[1:] = leftover if not os.path.exists(args.user_config): - return {} + return {}, {} config = configparser.ConfigParser(allow_no_value=True) config.read(args.user_config) @@ -584,9 +587,9 @@ """Main entry of Local Agent.""" user_config, matter_controllers_config = read_config() - ams_host = user_config.get(_USER_CONFIG_AMS_HOST) - ams_port = user_config.get(_USER_CONFIG_AMS_PORT) - ams_scheme = user_config.get(_USER_CONFIG_AMS_SCHEME) + ams_host = user_config.get(_USER_CONFIG_AMS_HOST, _EAP_AMS_HOST) + ams_port = user_config.get(_USER_CONFIG_AMS_PORT, _EAP_AMS_PORT) + ams_scheme = user_config.get(_USER_CONFIG_AMS_SCHEME, _EAP_AMS_SCHEME) artifacts_dir = ( user_config.get(_USER_CONFIG_ARTIFACTS_DIR, DEFAULT_ARTIFACTS_DIR))
diff --git a/local_agent/tests/unit_tests/test_ams_client.py b/local_agent/tests/unit_tests/test_ams_client.py index f99fc1c..ae71ac0 100644 --- a/local_agent/tests/unit_tests/test_ams_client.py +++ b/local_agent/tests/unit_tests/test_ams_client.py
@@ -23,11 +23,15 @@ from local_agent import ams_client from local_agent import errors +_FAKE_HOST = 'fake-host' +_FAKE_PORT = 8000 + class AmsClientTest(unittest.TestCase): def setUp(self): super().setUp() + self.sut = ams_client.AmsClient(host=_FAKE_HOST, port=_FAKE_PORT) sleep_patcher = mock.patch.object(time, 'sleep') sleep_patcher.start() self.addCleanup(sleep_patcher.stop) @@ -36,7 +40,6 @@ @mock.patch.object(requests.sessions.Session, 'request') def test_register_success(self, mock_request, mock_set_credentials): """Verifies register successful.""" - sut = ams_client.AmsClient() mock_response = mock_request.return_value mock_response.status_code = 200 mock_response.json.return_value = { @@ -44,7 +47,7 @@ 'agentId': 'the-id', 'agentSecret': 'the-secret'}} - sut.register(linking_code='the-linking-code') + self.sut.register(linking_code='the-linking-code') mock_set_credentials.assert_called_once_with( local_agent_id='the-id', @@ -53,18 +56,16 @@ @mock.patch.object(requests.sessions.Session, 'request') def test_register_api_timeout(self, mock_request): """Verifies register raises ApiTimeoutError when request timed out.""" - sut = ams_client.AmsClient() mock_request.side_effect = requests.exceptions.Timeout with self.assertRaises(errors.ApiTimeoutError): - sut.register(linking_code='the-linking-code') + self.sut.register(linking_code='the-linking-code') @mock.patch.object(requests.sessions.Session, 'request') def test_register_api_return_status_bad_request(self, mock_request): """Verifies register raises CredentialError when API response 400.""" - sut = ams_client.AmsClient() mock_request.return_value.status_code = http.HTTPStatus.BAD_REQUEST with self.assertRaises(errors.CredentialsError): - sut.register(linking_code='the-linking-code') + self.sut.register(linking_code='the-linking-code') @mock.patch.object(requests.sessions.Session, 'request') def test_register_api_error_message_included(self, mock_request): @@ -72,18 +73,16 @@ mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.BAD_REQUEST mock_response.json.return_value = {'errorMessage': 'the-message'} - sut = ams_client.AmsClient() with self.assertRaisesRegex(errors.CredentialsError, 'the-message'): - sut.register(linking_code='the-linking-code') + self.sut.register(linking_code='the-linking-code') @mock.patch.object(requests.sessions.Session, 'request') def test_register_api_return_status_not_ok(self, mock_request): """Verifies register raises ApiError when API fails.""" - sut = ams_client.AmsClient() mock_request.return_value.status_code = http.HTTPStatus.NOT_FOUND with self.assertRaises(errors.ApiError): - sut.register(linking_code='the-linking-code') + self.sut.register(linking_code='the-linking-code') @mock.patch.object(requests.sessions.Session, 'request') def test_set_local_agent_credentials_get_auth_token_success(self, mock_request): @@ -95,8 +94,7 @@ 'authToken': 'the-auth-token', } } - sut = ams_client.AmsClient() - sut.set_local_agent_credentials('agent-id', 'agent-secret') + self.sut.set_local_agent_credentials('agent-id', 'agent-secret') @mock.patch.object(ams_client.AmsClient, '_request_wrapper') def test_get_auth_token_not_refreshing_auth(self, mock_request_wrapper): @@ -104,9 +102,8 @@ mock_response = mock_request_wrapper.return_value mock_response.status_code = http.HTTPStatus.CREATED mock_response.json.return_value = {'result': {'authToken': 'abc'}} - sut = ams_client.AmsClient() - sut._get_auth_token() + self.sut._get_auth_token() self.assertFalse( mock_request_wrapper.call_args.kwargs['refresh_auth'], @@ -121,29 +118,26 @@ mock_response = mock_request_wrapper.return_value mock_response.status_code = http.HTTPStatus.BAD_REQUEST mock_extract_err_msg.return_value = 'Invalid agent id' - sut = ams_client.AmsClient() error_regex = 'Local agent is unlinked' with self.assertRaisesRegex(errors.UnlinkedError, error_regex): - sut._get_auth_token() + self.sut._get_auth_token() @mock.patch.object(requests.sessions.Session, 'request') def test_set_local_agent_credentials_raises_when_api_timeout(self, mock_request): """Verifies set_local_agent_credentials raises when API timed out.""" mock_request.side_effect = requests.exceptions.Timeout - sut = ams_client.AmsClient() with self.assertRaises(errors.ApiTimeoutError): - sut.set_local_agent_credentials('agent-id', 'agent-secret') + self.sut.set_local_agent_credentials('agent-id', 'agent-secret') @mock.patch.object(requests.sessions.Session, 'request') def test_set_local_agent_credentials_raises_when_api_error(self, mock_request): """Verifies set_local_agent_credentials raises when API has error.""" mock_request.return_value.status_code = http.HTTPStatus.NOT_FOUND - sut = ams_client.AmsClient() with self.assertRaises(errors.CredentialsError): - sut.set_local_agent_credentials('agent-id', 'agent-secret') + self.sut.set_local_agent_credentials('agent-id', 'agent-secret') @mock.patch.object(requests.sessions.Session, 'request') def test_set_local_agent_credentials_include_ams_error_message( @@ -153,28 +147,25 @@ mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.NOT_FOUND mock_response.json.return_value = {'errorMessage': 'the-message'} - sut = ams_client.AmsClient() with self.assertRaisesRegex(errors.CredentialsError, 'the-message'): - sut.set_local_agent_credentials('agent-id', 'agent-secret') + self.sut.set_local_agent_credentials('agent-id', 'agent-secret') @mock.patch.object(requests.sessions.Session, 'request', side_effect=requests.exceptions.Timeout) def test_report_info_api_timeout(self, mock_request): """Verifies report_info API timed out.""" - sut = ams_client.AmsClient() with self.assertRaises(errors.ApiTimeoutError): - sut.report_info({}) + self.sut.report_info({}) @mock.patch.object(requests.sessions.Session, 'request') def test_report_info_api_response_error(self, mock_request): """Verifies report_info raise exception when API response has error.""" mock_request.return_value.status_code = http.HTTPStatus.BAD_REQUEST - sut = ams_client.AmsClient() with self.assertRaisesRegex(errors.ApiError, 'Report info API failed: status 400'): - sut.report_info({}) + self.sut.report_info({}) @mock.patch.object(requests.sessions.Session, 'request') def test_report_info_api_response_error_include_ams_error_message( @@ -184,19 +175,17 @@ mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.BAD_REQUEST mock_response.json.return_value = {'errorMessage': 'the-message'} - sut = ams_client.AmsClient() with self.assertRaisesRegex(errors.ApiError, 'the-message'): - sut.report_info({}) + self.sut.report_info({}) @mock.patch.object(requests.sessions.Session, 'request') def test_report_info_successful(self, mock_request): """Verifies report_info succeeds and sends info dict to AMS.""" mock_request.return_value.status_code = http.HTTPStatus.OK local_agent_info = {'hi': 'hello'} - sut = ams_client.AmsClient() - sut.report_info(local_agent_info) + self.sut.report_info(local_agent_info) self.assertIn( 'json', @@ -211,9 +200,8 @@ mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.OK mock_response.json.return_value = {'result': {'hi': 'hello'}} - sut = ams_client.AmsClient() - self.assertEqual(sut.get_rpc_request_from_ams(), + self.assertEqual(self.sut.get_rpc_request_from_ams(), {'hi': 'hello'}) @mock.patch.object(requests.sessions.Session, 'request') @@ -222,19 +210,17 @@ """Verifies get_rpc_request_from_ams gets no request from AMS.""" mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.NO_CONTENT - sut = ams_client.AmsClient() - self.assertIsNone(sut.get_rpc_request_from_ams()) + self.assertIsNone(self.sut.get_rpc_request_from_ams()) @mock.patch.object(requests.sessions.Session, 'request') def test_get_rpc_request_from_ams_raises_when_api_timeout(self, mock_request): """Verifies get_rpc_request_from_ams raises when API timed out.""" mock_request.side_effect = requests.exceptions.Timeout - sut = ams_client.AmsClient() with self.assertRaises(errors.ApiTimeoutError): - sut.get_rpc_request_from_ams() + self.sut.get_rpc_request_from_ams() @mock.patch.object(requests.sessions.Session, 'request') def test_get_rpc_request_from_ams_raise_when_api_response_error( @@ -242,10 +228,9 @@ """Verifies get_rpc_request_from_ams raises when response has error.""" mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.INTERNAL_SERVER_ERROR - sut = ams_client.AmsClient() with self.assertRaises(errors.ApiError): - sut.get_rpc_request_from_ams() + self.sut.get_rpc_request_from_ams() @mock.patch.object(requests.sessions.Session, 'request') def test_get_rpc_request_from_ams_when_api_error_has_exception_message( @@ -254,27 +239,26 @@ mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.INTERNAL_SERVER_ERROR mock_response.json.return_value = {'errorMessage': 'message-from-ams'} - sut = ams_client.AmsClient() with self.assertRaisesRegex(errors.ApiError, r'500.*message-from-ams'): - sut.get_rpc_request_from_ams() + self.sut.get_rpc_request_from_ams() @mock.patch.object(requests.sessions.Session, 'request') def test_remove_rpc_request_from_ams_successful(self, mock_request): """Verifies remove_rpc_request_from_ams succeeds.""" mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.OK - sut = ams_client.AmsClient() - sut.remove_rpc_request_from_ams({'the': 'request'}) + + self.sut.remove_rpc_request_from_ams({'the': 'request'}) @mock.patch.object(requests.sessions.Session, 'request') def test_remove_rpc_request_from_ams_raises_when_api_timeout( self, mock_request): """Verifies remove_rpc_request_from_ams raises if API timed out.""" mock_request.side_effect = requests.exceptions.Timeout - sut = ams_client.AmsClient() + with self.assertRaises(errors.ApiTimeoutError): - sut.remove_rpc_request_from_ams({'the': 'request'}) + self.sut.remove_rpc_request_from_ams({'the': 'request'}) @mock.patch.object(requests.sessions.Session, 'request') def test_remove_rpc_request_from_ams_raises_when_api_fails( @@ -283,18 +267,17 @@ mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.INTERNAL_SERVER_ERROR mock_response.json.return_value = {'errorMessage': 'msg-from-ams'} - sut = ams_client.AmsClient() with self.assertRaisesRegex(errors.ApiError, r'500.*msg-from-ams'): - sut.remove_rpc_request_from_ams({'the': 'request'}) + self.sut.remove_rpc_request_from_ams({'the': 'request'}) @mock.patch.object(requests.sessions.Session, 'request') def test_send_rpc_response_successful(self, mock_request): """Verifies send_rpc_response sends RPC response to AMS successfully. """ mock_request.return_value.status_code = http.HTTPStatus.OK - sut = ams_client.AmsClient() - sut.send_rpc_response({'the': 'response'}) + + self.sut.send_rpc_response({'the': 'response'}) @mock.patch.object(requests.sessions.Session, 'request') def test_send_rpc_response_raise_api_error(self, mock_request): @@ -304,10 +287,9 @@ mock_response.status_code = ( http.HTTPStatus.INTERNAL_SERVER_ERROR) mock_response.json.return_value = {'errorMessage': 'the-ams-msg'} - sut = ams_client.AmsClient() with self.assertRaisesRegex(errors.ApiError, '500.*the-ams-msg'): - sut.send_rpc_response({'the': 'response'}) + self.sut.send_rpc_response({'the': 'response'}) # Verify we have retried. self.assertEqual(4, mock_request.call_count) @@ -316,10 +298,9 @@ """Verifies send_rpc_response raises ApiTimeoutError if API timed out. """ mock_request.side_effect = requests.exceptions.Timeout - sut = ams_client.AmsClient() with self.assertRaises(errors.ApiTimeoutError): - sut.send_rpc_response({'the': 'response'}) + self.sut.send_rpc_response({'the': 'response'}) # Verify we have retried. self.assertEqual(4, mock_request.call_count) @@ -341,10 +322,9 @@ requests.exceptions.Timeout, mock_500_response, ) - sut = ams_client.AmsClient() with self.assertRaisesRegex(errors.ApiError, '500'): - sut.send_rpc_response({'the': 'response'}) + self.sut.send_rpc_response({'the': 'response'}) # Verify we have refreshed the auth token, and that does not count as # a retry. self.assertEqual(1, mock_get_auth_token.call_count) @@ -355,8 +335,8 @@ def test_upload_artifact_successful(self, mock_open, mock_request): """Verifies upload_artifact succeeds.""" mock_request.return_value.status_code = http.HTTPStatus.OK - sut = ams_client.AmsClient() - sut.upload_artifact('the/file/path', 'the-test-result-id') + + self.sut.upload_artifact('the/file/path', 'the-test-result-id') @mock.patch.object(requests.sessions.Session, 'request') @mock.patch.object(ams_client, 'open', new_callable=mock.mock_open) @@ -364,9 +344,9 @@ """Verifies upload_artifact raises ApiTimeoutError if API timed out.""" mock_request.return_value.status_code = http.HTTPStatus.OK mock_request.side_effect = requests.exceptions.Timeout - sut = ams_client.AmsClient() + with self.assertRaises(errors.ApiTimeoutError): - sut.upload_artifact('the/file/path', 'the-test-result-id') + self.sut.upload_artifact('the/file/path', 'the-test-result-id') @mock.patch.object(requests.sessions.Session, 'request') @mock.patch.object(ams_client, 'open', new_callable=mock.mock_open) @@ -375,10 +355,9 @@ mock_response = mock_request.return_value mock_response.status_code = http.HTTPStatus.INTERNAL_SERVER_ERROR mock_response.json.return_value = {'errorMessage': 'the-ams-err-msg'} - sut = ams_client.AmsClient() with self.assertRaisesRegex(errors.ApiError, '500.*the-ams-err-msg'): - sut.upload_artifact('the/file/path', 'the-test-result-id') + self.sut.upload_artifact('the/file/path', 'the-test-result-id') @mock.patch.object(requests.sessions.Session, 'request') def test_request_wrapper_no_timeout_and_invalid_num_retries( @@ -386,9 +365,8 @@ """Verifies request_wrapper no timeout field and invalid num_retries.""" mock_response = mock.Mock(status_code=http.HTTPStatus.OK) mock_request.return_value = mock_response - sut = ams_client.AmsClient() - response = sut._request_wrapper(num_retries=-1) + response = self.sut._request_wrapper(num_retries=-1) self.assertEqual(mock_response, response)
diff --git a/local_agent/tests/unit_tests/test_local_agent.py b/local_agent/tests/unit_tests/test_local_agent.py index 3b796a3..3f52f66 100644 --- a/local_agent/tests/unit_tests/test_local_agent.py +++ b/local_agent/tests/unit_tests/test_local_agent.py
@@ -32,7 +32,6 @@ ####################### Fake data for unit test ############################# _FAKE_AMS_HOST = 'localhost' -_FAKE_AMS_PORT = 8000 _FAKE_AGENT_ID = 'fake-agent-id' _FAKE_AGENT_SECRET = 'fake-agent-secret' _FAKE_AUTH_TOKEN = 'fake-auth-token' @@ -53,7 +52,8 @@ def setUp(self): super().setUp() self.proc = local_agent.LocalAgentProcess( - client=ams_client.AmsClient(), artifacts_dir=_FAKE_ARTIFACTS_DIR) + client=ams_client.AmsClient(host=_FAKE_AMS_HOST), + artifacts_dir=_FAKE_ARTIFACTS_DIR) _, local_agent.AUTH_FILE = self._create_temp_file_with_clean_up() _, local_agent.DEFAULT_USER_CONFIG = ( @@ -154,7 +154,7 @@ def test_05_read_config_with_inexistent_file(self): """Verifies read_config returns {} when config file doesn't exist.""" local_agent.DEFAULT_USER_CONFIG = '' - self.assertEqual({}, local_agent.read_config()) + self.assertEqual(({}, {}), local_agent.read_config()) @mock.patch.object(configparser, 'ConfigParser') def test_05_read_config_missing_root_key(self, mock_parser): @@ -437,20 +437,20 @@ mock_logger.error.assert_called_once() @mock.patch.object(ams_client.AmsClient, 'upload_artifact') - @mock.patch.object(os, 'stat') + @mock.patch.object(local_agent, 'os') @mock.patch.object(shutil, 'rmtree') @mock.patch.object(shutil, 'make_archive') def test_11_compress_artifacts_and_upload_on_success( - self, mock_make, mock_rm, mock_stat, mock_ams_upload): + self, mock_make, mock_rm, mock_os, mock_ams_upload): """Verifies _compress_artifacts_and_upload on success.""" - mock_stat.return_value.st_size = 1 + mock_os.stat.return_value.st_size = 1 with mock.patch('builtins.open', new_callable=mock.mock_open): self.proc._compress_artifacts_and_upload('', '') - self.assertEqual(1, mock_make.call_count) - self.assertEqual(1, mock_rm.call_count) - self.assertEqual(1, mock_stat.call_count) - self.assertEqual(1, mock_ams_upload.call_count) + mock_make.assert_called_once() + mock_rm.assert_called_once() + mock_os.stat.assert_called_once() + mock_ams_upload.assert_called_once() @mock.patch.object(os, 'stat') @mock.patch.object(shutil, 'rmtree')
diff --git a/local_agent/tests/unit_tests/test_suite_session_manager.py b/local_agent/tests/unit_tests/test_suite_session_manager.py index 901e816..c9830cb 100644 --- a/local_agent/tests/unit_tests/test_suite_session_manager.py +++ b/local_agent/tests/unit_tests/test_suite_session_manager.py
@@ -33,6 +33,8 @@ _START_TEST_SUITE = 'startTestSuite' _END_TEST_SUITE = 'endTestSuite' _THREADING_MODULE_PATH = 'threading.Thread' +_FAKE_OUTDATED_ARTIFACTS = 'outdated_artifacts.zip' +_FAKE_NOT_OUTDATED_ARTIFACTS = 'not_outdated_artifacts.zip' ############################################################################## @@ -169,56 +171,46 @@ mock_add.assert_called_once() @mock.patch.object(logger_module, 'logger') - @mock.patch.object(os, 'listdir') - @mock.patch.object(os.path, 'isfile', return_value=True) - @mock.patch.object(os, 'stat') - @mock.patch.object(os, 'remove') - @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(suite_session_manager, 'os') def test_06_remove_outdated_artifacts_on_success( - self, - mock_exists, - mock_rm, - mock_stat, - mock_isfile, - mock_listdir, - mock_logger): + self, mock_os, mock_logger): """Verifies _remove_outdated_artifacts removes outdated files only.""" - mock_listdir.return_value = [ - 'outdated_artifacts.zip', - 'not_outdated_artifacts.zip', + mock_os.listdir.return_value = [ + _FAKE_OUTDATED_ARTIFACTS, + _FAKE_NOT_OUTDATED_ARTIFACTS ] - mock_stat.side_effect = [ + mock_os.stat.side_effect = [ mock.Mock(st_ctime=0), mock.Mock(st_ctime=time.time()), ] + mock_os.path.isfile.return_value = True + mock_os.path.exists.return_value = True + mock_os.path.join.return_value = _FAKE_OUTDATED_ARTIFACTS self.suite_mgr._remove_outdated_artifacts() - mock_exists.assert_called_once() - mock_rm.assert_called_once() - self.assertEqual(2, mock_isfile.call_count) - self.assertEqual('outdated_artifacts.zip', - os.path.basename(mock_rm.call_args.args[0])) + mock_os.path.exists.assert_called_once() + mock_os.remove.assert_called_once() + self.assertEqual(2, mock_os.path.isfile.call_count) + self.assertEqual(_FAKE_OUTDATED_ARTIFACTS, + mock_os.remove.call_args.args[0]) - @mock.patch.object(os, 'listdir') - @mock.patch.object(os.path, 'isfile', return_value=True) - @mock.patch.object(os, 'stat') - @mock.patch.object(os, 'remove') - @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(suite_session_manager, 'os') def test_06_remove_outdated_artifacts_will_suppress_oserror( - self, mock_exists, mock_rm, mock_stat, mock_isfile, mock_listdir): + self, mock_os): """ Verifies _remove_outdated_artifacts suppresses OSError when cannot remove. """ - mock_listdir.return_value = ['artifacts.zip'] - mock_stat.return_value.st_ctime = 0 - mock_rm.side_effect = OSError + mock_os.listdir.return_value = [_FAKE_OUTDATED_ARTIFACTS] + mock_os.path.isfile.return_value = True + mock_os.stat.return_value.st_ctime = 0 + mock_os.remove.side_effect = OSError self.suite_mgr._remove_outdated_artifacts() - mock_exists.assert_called_once() - mock_rm.assert_called_once() + mock_os.path.exists.assert_called_once() + mock_os.remove.assert_called_once() @mock.patch.object(os, 'remove') @mock.patch.object(os.path, 'exists', return_value=False)