From b13729ed944bd9ab324c358d8b8caf169962fa3f Mon Sep 17 00:00:00 2001 From: Lucian Petrut Date: Wed, 1 Apr 2026 09:19:47 +0000 Subject: [PATCH 1/4] Fix unit tests mock.has_calls is not a valid assertion, Python 3.12 now rejects it. We'll use the proper "assert_has_calls" method. Note that a few "assert_has_calls" assertion fail because the magic mocks returned by "mock.patch.object" are also recording accessed attributes of returned values, e.g.: a = mock.Mock() b = a() b.id # Recorded as a().id call unless we use mock.Mock --- .../v1/test_endpoint_destination_minion_pool_options.py | 2 +- coriolis/tests/api/v1/test_endpoint_instances.py | 2 +- .../api/v1/test_endpoint_source_minion_pool_options.py | 2 +- coriolis/tests/api/v1/test_endpoint_source_options.py | 2 +- coriolis/tests/conductor/rpc/test_server.py | 8 ++++---- coriolis/tests/tasks/test_replica_tasks.py | 7 ++++--- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/coriolis/tests/api/v1/test_endpoint_destination_minion_pool_options.py b/coriolis/tests/api/v1/test_endpoint_destination_minion_pool_options.py index c39bb357e..598cc0323 100644 --- a/coriolis/tests/api/v1/test_endpoint_destination_minion_pool_options.py +++ b/coriolis/tests/api/v1/test_endpoint_destination_minion_pool_options.py @@ -57,7 +57,7 @@ def test_index( mock_context.can.assert_called_once_with( 'migration:endpoints:list_destination_minion_pool_options') - mock_decode_base64_param.has_calls(expected_calls) + mock_decode_base64_param.assert_has_calls(expected_calls) (mock_get_endpoint_destination_minion_pool_options. assert_called_once_with)( mock_context, endpoint_id, diff --git a/coriolis/tests/api/v1/test_endpoint_instances.py b/coriolis/tests/api/v1/test_endpoint_instances.py index 181384e64..e374b9a24 100644 --- a/coriolis/tests/api/v1/test_endpoint_instances.py +++ b/coriolis/tests/api/v1/test_endpoint_instances.py @@ -120,7 +120,7 @@ def test_show( mock_context.can.assert_called_once_with( 'migration:endpoints:get_instance') - mock_decode_base64_param.has_calls(expected_calls) + mock_decode_base64_param.assert_has_calls(expected_calls) mock_get_endpoint_instance.assert_called_once_with( mock_context, endpoint_id, env, diff --git a/coriolis/tests/api/v1/test_endpoint_source_minion_pool_options.py b/coriolis/tests/api/v1/test_endpoint_source_minion_pool_options.py index b9b8b82c3..7d43c4825 100644 --- a/coriolis/tests/api/v1/test_endpoint_source_minion_pool_options.py +++ b/coriolis/tests/api/v1/test_endpoint_source_minion_pool_options.py @@ -52,7 +52,7 @@ def test_index( mock_context.can.assert_called_once_with( 'migration:endpoints:list_source_minion_pool_options') - mock_decode_base64_param.has_calls(expected_calls) + mock_decode_base64_param.assert_has_calls(expected_calls) (mock_get_endpoint_source_minion_pool_options. assert_called_once_with)( mock_context, endpoint_id, diff --git a/coriolis/tests/api/v1/test_endpoint_source_options.py b/coriolis/tests/api/v1/test_endpoint_source_options.py index 30e543ce3..d8f4ce5f0 100644 --- a/coriolis/tests/api/v1/test_endpoint_source_options.py +++ b/coriolis/tests/api/v1/test_endpoint_source_options.py @@ -48,7 +48,7 @@ def test_index( mock_context.can.assert_called_once_with( 'migration:endpoints:list_source_options') - mock_decode_base64_param.has_calls(expected_calls) + mock_decode_base64_param.assert_has_calls(expected_calls) mock_get_endpoint_source_options.assert_called_once_with( mock_context, endpoint_id, env=env, diff --git a/coriolis/tests/conductor/rpc/test_server.py b/coriolis/tests/conductor/rpc/test_server.py index fb6dac0c0..262cd8e94 100644 --- a/coriolis/tests/conductor/rpc/test_server.py +++ b/coriolis/tests/conductor/rpc/test_server.py @@ -5357,7 +5357,7 @@ def test_check_service_registered_no_service(self, mock_find_service): @mock.patch.object(db_api, "update_service") @mock.patch.object(rpc_worker_client.WorkerClient, "get_service_status") - @mock.patch.object(db_api, "get_service") + @mock.patch.object(db_api, "get_service", new_callable=mock.Mock) def test_refresh_service_status( self, mock_get_service, @@ -5374,12 +5374,12 @@ def test_refresh_service_status( mock_get_service.return_value, result ) - mock_get_service.has_calls([ + mock_get_service.assert_has_calls([ mock.call( mock.sentinel.context, mock.sentinel.service_id - ) * 2 - ]) + ) + ] * 2) mock_get_service_status.assert_called_once_with( mock.sentinel.context) mock_update_service.assert_called_once_with( diff --git a/coriolis/tests/tasks/test_replica_tasks.py b/coriolis/tests/tasks/test_replica_tasks.py index 1e3207d3e..f31e04bec 100644 --- a/coriolis/tests/tasks/test_replica_tasks.py +++ b/coriolis/tests/tasks/test_replica_tasks.py @@ -38,9 +38,10 @@ def test__get_volumes_info(self, data): def test__check_ensure_volumes_info_ordering( self, mock_sanitize, export_info, volumes_info, exception_expected, expected_result): + mock_sanitize.side_effect = lambda x: x expected_calls = [ - mock.call.mock_sanitize({"volumes_info": volumes_info}), - mock.call.mock_sanitize({"volumes_info": expected_result}), + mock.call({"volumes_info": volumes_info}), + mock.call({"volumes_info": expected_result}), ] if exception_expected: self.assertRaises( @@ -50,7 +51,7 @@ def test__check_ensure_volumes_info_ordering( else: result = replica_tasks._check_ensure_volumes_info_ordering( export_info, volumes_info) - mock_sanitize.has_calls(expected_calls) + mock_sanitize.assert_has_calls(expected_calls) self.assertEqual(result, expected_result) @ddt.file_data("data/test_nic_ips_update.yml") From ee7fba042b1553cde7b1df74567e18685ae247d5 Mon Sep 17 00:00:00 2001 From: Lucian Petrut Date: Wed, 1 Apr 2026 10:50:46 +0000 Subject: [PATCH 2/4] Avoid flake8 false positives around f strings Recent flake8 versions cannot handle f strings properly, treating the string contents as Python code. We'll add "noqa" comments where applicable. --- coriolis/api/v1/transfers.py | 8 ++++---- coriolis/conductor/rpc/server.py | 4 ++-- coriolis/db/api.py | 4 ++-- coriolis/osmorphing/windows.py | 6 +++--- .../tests/api/v1/test_endpoint_destination_options.py | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/coriolis/api/v1/transfers.py b/coriolis/api/v1/transfers.py index 357acd72b..013ac0d4c 100644 --- a/coriolis/api/v1/transfers.py +++ b/coriolis/api/v1/transfers.py @@ -70,8 +70,8 @@ def _validate_create_body(self, context, body): if scenario not in SUPPORTED_TRANSFER_SCENARIOS: raise exc.HTTPBadRequest( explanation=f"Unsupported Transfer creation scenario " - f"'{scenario}', must be one of: " - f"{SUPPORTED_TRANSFER_SCENARIOS}") + f"'{scenario}', must be one of: " # noqa + f"{SUPPORTED_TRANSFER_SCENARIOS}") # noqa else: scenario = constants.TRANSFER_SCENARIO_REPLICA LOG.warn( @@ -317,8 +317,8 @@ def _validate_update_body(self, id, context, body): if scenario and scenario != transfer["scenario"]: raise exc.HTTPBadRequest( explanation=f"Changing Transfer creation scenario is not " - f"supported (original scenario is " - f"{transfer['scenario']}, received '{scenario}')") + f"supported (original scenario is " # noqa + f"{transfer['scenario']}, received '{scenario}')") # noqa transfer_body = body['transfer'] origin_endpoint_id = transfer_body.get('origin_endpoint_id', None) diff --git a/coriolis/conductor/rpc/server.py b/coriolis/conductor/rpc/server.py index fa70e524d..226946447 100644 --- a/coriolis/conductor/rpc/server.py +++ b/coriolis/conductor/rpc/server.py @@ -302,7 +302,7 @@ def _create_reservation_for_transfer(self, transfer): if not reservation_type: raise exception.LicensingException( message="Could not determine reservation type for transfer " - f"'{action_id}' with scenario '{transfer.scenario}'.") + f"'{action_id}' with scenario '{transfer.scenario}'.") # noqa if not self._licensing_client: LOG.warn( "Licensing client not instantiated. Skipping creation of " @@ -1300,7 +1300,7 @@ def create_instances_transfer(self, ctxt, transfer_scenario, if transfer_scenario not in supported_scenarios: raise exception.InvalidInput( message=f"Unsupported Transfer scenario '{transfer_scenario}'." - f" Must be one of: {supported_scenarios}") + f" Must be one of: {supported_scenarios}") # noqa origin_endpoint = self.get_endpoint(ctxt, origin_endpoint_id) destination_endpoint = self.get_endpoint( diff --git a/coriolis/db/api.py b/coriolis/db/api.py index e250255b4..816031a42 100644 --- a/coriolis/db/api.py +++ b/coriolis/db/api.py @@ -902,7 +902,7 @@ def add_task_progress_update( f"Progress message for task '{task_id}' with ID '{task_event_id}'" f"is too long. Truncating before insertion. " f"Original message was: '{message}'") - message = f"{message[:max_msg_len-len('...')]}..." + message = f"{message[:max_msg_len-len('...')]}..." # noqa task_progress_update.message = message task_progress_update.index = 0 @@ -936,7 +936,7 @@ def update_task_progress_update( f"Progress message for task '{task_id}' with ID " f"'{task_event_id}' is too long. Truncating before insertion." f" Original message was: '{new_message}'") - new_message = f"{new_message[:max_msg_len-len('...')]}..." + new_message = f"{new_message[:max_msg_len-len('...')]}..." # noqa task_progress_update.message = new_message diff --git a/coriolis/osmorphing/windows.py b/coriolis/osmorphing/windows.py index e87d565db..016c5c161 100644 --- a/coriolis/osmorphing/windows.py +++ b/coriolis/osmorphing/windows.py @@ -341,7 +341,7 @@ def _set_services_start_mode(self, key_name, service_names, start_mode): @({paths_string}) | ForEach-Object {{ Set-ItemProperty -Path $_ -Name 'Start' -Value {start_mode} }} - """, + """, # noqa ignore_stdout=True) def _create_service(self, key_name, service_name, image_path, @@ -667,8 +667,8 @@ def _get_static_nics_info(self, nics_info, ips_info): if diff: LOG.warning( f"The IP addresses {list(diff)} found on the source " - f"VM's NIC were not found in the registry. These IPs will " - f"be skipped in the static IP configuration process") + "VM's NIC were not found in the registry. These IPs will " + "be skipped in the static IP configuration process") ip_matches = list( set(reg_ip_addresses).intersection(set(nic_ips))) if not ip_matches: diff --git a/coriolis/tests/api/v1/test_endpoint_destination_options.py b/coriolis/tests/api/v1/test_endpoint_destination_options.py index 89e877b7e..ec6b31636 100644 --- a/coriolis/tests/api/v1/test_endpoint_destination_options.py +++ b/coriolis/tests/api/v1/test_endpoint_destination_options.py @@ -48,7 +48,7 @@ def test_index( mock_context.can.assert_called_once_with( 'migration:endpoints:list_destination_options') - mock_decode_base64_param.has_calls(expected_calls) + mock_decode_base64_param.assert_has_calls(expected_calls) mock_get_endpoint_destination_options.assert_called_once_with( mock_context, endpoint_id, env=env, From e3323666daf7966b3dde338daecb874eb39125ba Mon Sep 17 00:00:00 2001 From: Lucian Petrut Date: Wed, 1 Apr 2026 09:03:21 +0000 Subject: [PATCH 3/4] CI: update Python version matrix Old Python versions do not support the modern type annotation syntax. It's time to update the test matrix. We'll drop 3.8 and 3.9, adding 3.12 and 3.14 instead. --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 19e52e214..1f878c2cc 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12", "3.14"] architecture: ["x64"] steps: From 87f5afb77b1098bfe4235c55d56d00373c39845d Mon Sep 17 00:00:00 2001 From: Lucian Petrut Date: Wed, 1 Apr 2026 11:08:53 +0000 Subject: [PATCH 4/4] Bump flake8 version Older flake8 versions are not compatible with Python 3.14. "hacking", a package that contains flake8 plugins used by Openstack, also needs to be updated. --- test-requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 32d0a87c4..5e7d13007 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,8 +3,10 @@ # process, which may cause wedges in the gate later. setuptools>=65.0.0,<82 # pkg_resources removed in 82; required by sqlalchemy-migrate -hacking>=6.0.1,<=6.0.1 # Apache-2.0 +hacking>=7.0.0,<7.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 ddt>=1.2.1 # MIT oslotest>=3.8.0 # Apache-2.0 stestr>=4.2.1 # Apache-2.0 +flake8>=7.0.0 +pyflakes>=3.2.0