From 6214a7cbd6c90c8c4c3ac7e36f940e3a7360feff Mon Sep 17 00:00:00 2001 From: Electronic-Waste <2690692950@qq.com> Date: Tue, 6 Aug 2024 14:00:17 +0000 Subject: [PATCH 1/6] test(sdk): add unit test for wait_for_job_conditions. Signed-off-by: Electronic-Waste <2690692950@qq.com> --- .../training/api/training_client_test.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/sdk/python/kubeflow/training/api/training_client_test.py b/sdk/python/kubeflow/training/api/training_client_test.py index 8d0ab6310d..0d94e1ebf1 100644 --- a/sdk/python/kubeflow/training/api/training_client_test.py +++ b/sdk/python/kubeflow/training/api/training_client_test.py @@ -70,6 +70,13 @@ def get(self, timeout): return MockResponse() +def get_job_response(*args, **kwargs): + if kwargs.get("namespace") == "runtime": + return generate_job_with_status(create_job(), constants.JOB_CONDITION_FAILED) + else: + return generate_job_with_status(create_job()) + + def generate_container() -> V1Container: return V1Container( name="pytorch", @@ -127,6 +134,21 @@ def create_job(): return pytorchjob +def generate_job_with_status( + job: constants.JOB_MODELS_TYPE, + condition_type: str = constants.JOB_CONDITION_SUCCEEDED +) -> constants.JOB_MODELS_TYPE: + job.status = KubeflowOrgV1JobStatus( + conditions=[ + KubeflowOrgV1JobCondition( + type=condition_type, + status=constants.CONDITION_STATUS_TRUE + ) + ] + ) + return job + + class DummyJobClass: def __init__(self, kind) -> None: self.kind = kind @@ -279,6 +301,61 @@ def __init__(self, kind) -> None: ), ] +test_data_wait_for_job_conditions = [ + ( + "timeout waiting for succeeded condition", + { + "name": TEST_NAME, + "namespace": "timeout", + "wait_timeout": 0 + }, + TimeoutError + ), + ( + "invalid expected condition", + { + "name": TEST_NAME, + "namespace": "value", + "expected_conditions": {"invalid"} + }, + ValueError + ), + ( + "invalid expected condition(lowercase)", + { + "name": TEST_NAME, + "namespace": "value", + "expected_conditions": {"succeeded"} + }, + ValueError + ), + ( + "job failed unexpectedly", + { + "name": TEST_NAME, + "namespace": "runtime" + }, + RuntimeError + ), + ( + "valid case", + { + "name": TEST_NAME, + "namespace": "test-namespace" + }, + generate_job_with_status(create_job()) + ), + ( + "valid case with specified callback", + { + "name": TEST_NAME, + "namespace": "test-namespace", + "callback": lambda job: "test train function" + }, + generate_job_with_status(create_job()) + ) +] + test_data_get_job_pod_names = [ ( @@ -359,6 +436,17 @@ def training_client(): yield client +@pytest.fixture +def training_client_wait_for_job_conditions(): + with patch.object( + TrainingClient, + "get_job", + side_effect=get_job_response + ): + client = TrainingClient(job_kind=constants.PYTORCHJOB_KIND) + yield client + + @pytest.mark.parametrize("test_name,kwargs,expected_output", test_data_create_job) def test_create_job(training_client, test_name, kwargs, expected_output): """ @@ -434,3 +522,19 @@ def test_update_job(training_client, test_name, kwargs, expected_output): except Exception as e: assert type(e) is expected_output print("test execution complete") + + +@pytest.mark.parametrize("test_name,kwargs,expected_output", test_data_wait_for_job_conditions) +def test_wait_for_job_conditions( + training_client_wait_for_job_conditions, test_name, kwargs, expected_output +): + """ + test wait_for_job_conditions function of training client + """ + print("Executing test:", test_name) + try: + out = training_client_wait_for_job_conditions.wait_for_job_conditions(**kwargs) + assert out == expected_output + except Exception as e: + assert type(e) is expected_output + print("test execution complete") From 1ff6ebc8f8d42505fe5b117bc0d2961e18acbb40 Mon Sep 17 00:00:00 2001 From: Electronic-Waste <2690692950@qq.com> Date: Tue, 6 Aug 2024 14:07:27 +0000 Subject: [PATCH 2/6] test(sdk): fix lint error. Signed-off-by: Electronic-Waste <2690692950@qq.com> --- .../training/api/training_client_test.py | 54 ++++++------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/sdk/python/kubeflow/training/api/training_client_test.py b/sdk/python/kubeflow/training/api/training_client_test.py index 0d94e1ebf1..4eace6ac84 100644 --- a/sdk/python/kubeflow/training/api/training_client_test.py +++ b/sdk/python/kubeflow/training/api/training_client_test.py @@ -136,13 +136,13 @@ def create_job(): def generate_job_with_status( job: constants.JOB_MODELS_TYPE, - condition_type: str = constants.JOB_CONDITION_SUCCEEDED + condition_type: str = constants.JOB_CONDITION_SUCCEEDED, ) -> constants.JOB_MODELS_TYPE: job.status = KubeflowOrgV1JobStatus( conditions=[ KubeflowOrgV1JobCondition( type=condition_type, - status=constants.CONDITION_STATUS_TRUE + status=constants.CONDITION_STATUS_TRUE, ) ] ) @@ -304,56 +304,38 @@ def __init__(self, kind) -> None: test_data_wait_for_job_conditions = [ ( "timeout waiting for succeeded condition", - { - "name": TEST_NAME, - "namespace": "timeout", - "wait_timeout": 0 - }, - TimeoutError + {"name": TEST_NAME, "namespace": "timeout", "wait_timeout": 0}, + TimeoutError, ), ( "invalid expected condition", - { - "name": TEST_NAME, - "namespace": "value", - "expected_conditions": {"invalid"} - }, - ValueError + {"name": TEST_NAME, "namespace": "value", "expected_conditions": {"invalid"}}, + ValueError, ), ( "invalid expected condition(lowercase)", - { - "name": TEST_NAME, - "namespace": "value", - "expected_conditions": {"succeeded"} - }, - ValueError + {"name": TEST_NAME, "namespace": "value", "expected_conditions": {"succeeded"}}, + ValueError, ), ( "job failed unexpectedly", - { - "name": TEST_NAME, - "namespace": "runtime" - }, - RuntimeError + {"name": TEST_NAME, "namespace": "runtime"}, + RuntimeError, ), ( "valid case", - { - "name": TEST_NAME, - "namespace": "test-namespace" - }, - generate_job_with_status(create_job()) + {"name": TEST_NAME, "namespace": "test-namespace"}, + generate_job_with_status(create_job()), ), ( "valid case with specified callback", { "name": TEST_NAME, "namespace": "test-namespace", - "callback": lambda job: "test train function" + "callback": lambda job: "test train function", }, - generate_job_with_status(create_job()) - ) + generate_job_with_status(create_job()), + ), ] @@ -438,11 +420,7 @@ def training_client(): @pytest.fixture def training_client_wait_for_job_conditions(): - with patch.object( - TrainingClient, - "get_job", - side_effect=get_job_response - ): + with patch.object(TrainingClient, "get_job", side_effect=get_job_response): client = TrainingClient(job_kind=constants.PYTORCHJOB_KIND) yield client From 52a4406da6dff5b2bb7833d1eccf93593ce95ecf Mon Sep 17 00:00:00 2001 From: Electronic-Waste <2690692950@qq.com> Date: Tue, 6 Aug 2024 15:15:54 +0000 Subject: [PATCH 3/6] test(sdk): add patch for load_kube_config. Signed-off-by: Electronic-Waste <2690692950@qq.com> --- sdk/python/kubeflow/training/api/training_client_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/python/kubeflow/training/api/training_client_test.py b/sdk/python/kubeflow/training/api/training_client_test.py index 4eace6ac84..36987d5d7d 100644 --- a/sdk/python/kubeflow/training/api/training_client_test.py +++ b/sdk/python/kubeflow/training/api/training_client_test.py @@ -420,7 +420,9 @@ def training_client(): @pytest.fixture def training_client_wait_for_job_conditions(): - with patch.object(TrainingClient, "get_job", side_effect=get_job_response): + with patch.object(TrainingClient, "get_job", side_effect=get_job_response), patch( + "kubernetes.config.load_kube_config", return_value=Mock() + ): client = TrainingClient(job_kind=constants.PYTORCHJOB_KIND) yield client From 53c8e8315b966ace4f372fdd5cb7905a54f865ba Mon Sep 17 00:00:00 2001 From: Electronic-Waste <2690692950@qq.com> Date: Thu, 29 Aug 2024 14:21:17 +0000 Subject: [PATCH 4/6] test(trial): fix lint error with black. Signed-off-by: Electronic-Waste <2690692950@qq.com> --- sdk/python/kubeflow/training/api/training_client_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/python/kubeflow/training/api/training_client_test.py b/sdk/python/kubeflow/training/api/training_client_test.py index 36987d5d7d..a5b1f8215a 100644 --- a/sdk/python/kubeflow/training/api/training_client_test.py +++ b/sdk/python/kubeflow/training/api/training_client_test.py @@ -504,7 +504,9 @@ def test_update_job(training_client, test_name, kwargs, expected_output): print("test execution complete") -@pytest.mark.parametrize("test_name,kwargs,expected_output", test_data_wait_for_job_conditions) +@pytest.mark.parametrize( + "test_name,kwargs,expected_output", test_data_wait_for_job_conditions +) def test_wait_for_job_conditions( training_client_wait_for_job_conditions, test_name, kwargs, expected_output ): From db41d3f05090387e9bf7a57375bbc20303bcf5b2 Mon Sep 17 00:00:00 2001 From: Electronic-Waste <2690692950@qq.com> Date: Thu, 29 Aug 2024 14:24:36 +0000 Subject: [PATCH 5/6] test(trial): add package dependency. Signed-off-by: Electronic-Waste <2690692950@qq.com> --- sdk/python/kubeflow/training/api/training_client_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/python/kubeflow/training/api/training_client_test.py b/sdk/python/kubeflow/training/api/training_client_test.py index a5b1f8215a..bc4daa313f 100644 --- a/sdk/python/kubeflow/training/api/training_client_test.py +++ b/sdk/python/kubeflow/training/api/training_client_test.py @@ -4,6 +4,8 @@ import pytest from kubeflow.training import ( + KubeflowOrgV1JobCondition, + KubeflowOrgV1JobStatus, KubeflowOrgV1PyTorchJob, KubeflowOrgV1PyTorchJobSpec, KubeflowOrgV1ReplicaSpec, From 2e756d2a0f02dafcdc31e9eedf49e432024dadb6 Mon Sep 17 00:00:00 2001 From: Electronic-Waste <2690692950@qq.com> Date: Fri, 30 Aug 2024 12:23:48 +0000 Subject: [PATCH 6/6] test(sdk): reuse exisiting fixture. Signed-off-by: Electronic-Waste <2690692950@qq.com> --- .../training/api/training_client_test.py | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/sdk/python/kubeflow/training/api/training_client_test.py b/sdk/python/kubeflow/training/api/training_client_test.py index bc4daa313f..344ee76e2e 100644 --- a/sdk/python/kubeflow/training/api/training_client_test.py +++ b/sdk/python/kubeflow/training/api/training_client_test.py @@ -73,7 +73,7 @@ def get(self, timeout): def get_job_response(*args, **kwargs): - if kwargs.get("namespace") == "runtime": + if kwargs.get("namespace") == RUNTIME: return generate_job_with_status(create_job(), constants.JOB_CONDITION_FAILED) else: return generate_job_with_status(create_job()) @@ -306,27 +306,45 @@ def __init__(self, kind) -> None: test_data_wait_for_job_conditions = [ ( "timeout waiting for succeeded condition", - {"name": TEST_NAME, "namespace": "timeout", "wait_timeout": 0}, + { + "name": TEST_NAME, + "namespace": TIMEOUT, + "wait_timeout": 0, + }, TimeoutError, ), ( "invalid expected condition", - {"name": TEST_NAME, "namespace": "value", "expected_conditions": {"invalid"}}, + { + "name": TEST_NAME, + "namespace": "value", + "expected_conditions": {"invalid"}, + }, ValueError, ), ( "invalid expected condition(lowercase)", - {"name": TEST_NAME, "namespace": "value", "expected_conditions": {"succeeded"}}, + { + "name": TEST_NAME, + "namespace": "value", + "expected_conditions": {"succeeded"}, + }, ValueError, ), ( "job failed unexpectedly", - {"name": TEST_NAME, "namespace": "runtime"}, + { + "name": TEST_NAME, + "namespace": RUNTIME, + }, RuntimeError, ), ( "valid case", - {"name": TEST_NAME, "namespace": "test-namespace"}, + { + "name": TEST_NAME, + "namespace": "test-namespace", + }, generate_job_with_status(create_job()), ), ( @@ -415,15 +433,8 @@ def training_client(): ), ), patch( "kubernetes.config.load_kube_config", return_value=Mock() - ): - client = TrainingClient(job_kind=constants.PYTORCHJOB_KIND) - yield client - - -@pytest.fixture -def training_client_wait_for_job_conditions(): - with patch.object(TrainingClient, "get_job", side_effect=get_job_response), patch( - "kubernetes.config.load_kube_config", return_value=Mock() + ), patch.object( + TrainingClient, "get_job", side_effect=get_job_response ): client = TrainingClient(job_kind=constants.PYTORCHJOB_KIND) yield client @@ -509,15 +520,13 @@ def test_update_job(training_client, test_name, kwargs, expected_output): @pytest.mark.parametrize( "test_name,kwargs,expected_output", test_data_wait_for_job_conditions ) -def test_wait_for_job_conditions( - training_client_wait_for_job_conditions, test_name, kwargs, expected_output -): +def test_wait_for_job_conditions(training_client, test_name, kwargs, expected_output): """ test wait_for_job_conditions function of training client """ print("Executing test:", test_name) try: - out = training_client_wait_for_job_conditions.wait_for_job_conditions(**kwargs) + out = training_client.wait_for_job_conditions(**kwargs) assert out == expected_output except Exception as e: assert type(e) is expected_output