From 66ff997caee4185205ccacf0f43fa5d1034ccca3 Mon Sep 17 00:00:00 2001 From: deepanker13 Date: Thu, 26 Oct 2023 18:44:10 +0530 Subject: [PATCH 1/6] adding bug fix in gen-sdk.sh and unit test for create_job function in training client --- hack/python-sdk/gen-sdk.sh | 2 +- sdk/python/setup.py | 1 + sdk/python/test/test_training_client.py | 125 ++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 sdk/python/test/test_training_client.py diff --git a/hack/python-sdk/gen-sdk.sh b/hack/python-sdk/gen-sdk.sh index 3d1be52023..45b45dd5a8 100755 --- a/hack/python-sdk/gen-sdk.sh +++ b/hack/python-sdk/gen-sdk.sh @@ -43,7 +43,7 @@ echo "Generating swagger file ..." go run "${repo_root}"/hack/swagger/main.go ${VERSION} >"${SWAGGER_CODEGEN_FILE}" echo "Removing previously generated files ..." -rm -rf "${SDK_OUTPUT_PATH}"/docs/V1*.md "${SDK_OUTPUT_PATH}"/kubeflow/training/models "${SDK_OUTPUT_PATH}"/kubeflow/training/*.py "${SDK_OUTPUT_PATH}"/test/test_*.py +rm -rf "${SDK_OUTPUT_PATH}"/docs/KubeflowOrgV1*.md "${SDK_OUTPUT_PATH}"/kubeflow/training/models "${SDK_OUTPUT_PATH}"/kubeflow/training/*.py "${SDK_OUTPUT_PATH}"/test/test_*.py echo "Generating Python SDK for Training Operator ..." java -jar "${SWAGGER_CODEGEN_JAR}" generate -i "${repo_root}"/hack/python-sdk/swagger.json -g python -o "${SDK_OUTPUT_PATH}" -c "${SWAGGER_CODEGEN_CONF}" diff --git a/sdk/python/setup.py b/sdk/python/setup.py index fc93bac67d..510facac98 100644 --- a/sdk/python/setup.py +++ b/sdk/python/setup.py @@ -29,6 +29,7 @@ "urllib3>=1.15.1", "kubernetes>=23.6.0", "retrying>=1.3.3", + "parameterized>=0.9.0" ] setuptools.setup( diff --git a/sdk/python/test/test_training_client.py b/sdk/python/test/test_training_client.py new file mode 100644 index 0000000000..6e89604030 --- /dev/null +++ b/sdk/python/test/test_training_client.py @@ -0,0 +1,125 @@ +import multiprocessing +import unittest +from unittest.mock import patch, Mock +from parameterized import parameterized + +from typing import Optional +from kubeflow.training import TrainingClient +from kubeflow.training import KubeflowOrgV1ReplicaSpec +from kubeflow.training import KubeflowOrgV1PyTorchJob +from kubeflow.training import KubeflowOrgV1PyTorchJobSpec +from kubeflow.training import KubeflowOrgV1RunPolicy +from kubeflow.training import KubeflowOrgV1SchedulingPolicy +from kubeflow.training import constants + +from kubernetes.client import V1PodTemplateSpec +from kubernetes.client import V1ObjectMeta +from kubernetes.client import V1PodSpec +from kubernetes.client import V1Container +from kubernetes.client import V1ResourceRequirements + +CONTAINER_NAME = "pytorch" +JOB_NAME = "pytorchjob-mnist-ci-test" + +def create_namespaced_custom_object_response(*args, **kwargs): + if args[2] == 'timeout': + raise multiprocessing.TimeoutError() + elif args[2] == 'runtime': + raise RuntimeError() + +def generate_container() -> V1Container: + return V1Container( + name=CONTAINER_NAME, + image="gcr.io/kubeflow-ci/pytorch-dist-mnist-test:v1.0", + args=["--backend", "gloo"], + resources=V1ResourceRequirements(limits={"memory": '1Gi', "cpu": "0.4"}), + ) + +def generate_pytorchjob( + job_namespace: str, + master: KubeflowOrgV1ReplicaSpec, + worker: KubeflowOrgV1ReplicaSpec, + scheduling_policy: Optional[KubeflowOrgV1SchedulingPolicy] = None, +) -> KubeflowOrgV1PyTorchJob: + return KubeflowOrgV1PyTorchJob( + api_version=constants.API_VERSION, + kind=constants.PYTORCHJOB_KIND, + metadata=V1ObjectMeta(name=JOB_NAME, namespace=job_namespace), + spec=KubeflowOrgV1PyTorchJobSpec( + run_policy=KubeflowOrgV1RunPolicy( + clean_pod_policy="None", + scheduling_policy=scheduling_policy, + ), + pytorch_replica_specs={"Master": master, "Worker": worker}, + ), + ) + +def create_job(): + job_namespace = "test" + container = generate_container() + master = KubeflowOrgV1ReplicaSpec( + replicas=1, + restart_policy="OnFailure", + template=V1PodTemplateSpec( + metadata=V1ObjectMeta( + annotations={constants.ISTIO_SIDECAR_INJECTION: "false"} + ), + spec=V1PodSpec(containers=[container]), + ), + ) + + worker = KubeflowOrgV1ReplicaSpec( + replicas=1, + restart_policy="OnFailure", + template=V1PodTemplateSpec( + metadata=V1ObjectMeta( + annotations={constants.ISTIO_SIDECAR_INJECTION: "false"} + ), + spec=V1PodSpec(containers=[container]), + ), + ) + pytorchjob = generate_pytorchjob(job_namespace, master, worker) + return pytorchjob + +class DummyJobClass: + def __init__(self,kind) -> None: + self.kind = kind + +class TestTrainingClient(unittest.TestCase): + + @patch('kubernetes.client.CustomObjectsApi', return_value=Mock(create_namespaced_custom_object=Mock(side_effect=create_namespaced_custom_object_response))) + @patch('kubernetes.client.CoreV1Api', return_value=Mock()) + @patch('kubernetes.config.load_kube_config', return_value=Mock()) + def setUp(self, mock_custom_api, mock_core_api, mock_load_kube_config) -> None: + self.training_client = TrainingClient(job_kind=constants.PYTORCHJOB_KIND) + + + @parameterized.expand([ + ("invalid extra parameter", {"job":create_job(), "namespace": "test", "base_image":"test_image" },ValueError), + ("invalid job kind", {"job_kind": "invalid_job_kind" },ValueError), + ("job name missing ", {"train_func": lambda: "test train function"}, ValueError), + ("job name missing", {"base_image":"test_image"}, ValueError), + ("uncallable train function", {"name": "test job", "train_func":"uncallable train function"}, ValueError), + ("invalid TFJob replica", {"name": "test job", "train_func": lambda: "test train function", "job_kind": constants.TFJOB_KIND }, ValueError ), + ("invalid PyTorchJob replica", {"name": "test job", "train_func": lambda: "test train function","job_kind": constants.PYTORCHJOB_KIND }, ValueError ), + ("invalid pod template spec parameters", {"name": "test job", "train_func": lambda: "test train function","job_kind": constants.MXJOB_KIND }, KeyError ), + ("paddle job can't be created using function", {"name": "test job", "train_func": lambda: "test train function","job_kind": constants.PADDLEJOB_KIND }, ValueError ), + ("invalid job object", {"job": DummyJobClass(constants.TFJOB_KIND)}, ValueError), + ("create_namespaced_custom_object timeout error", {"job":create_job(), "namespace": "timeout" },TimeoutError), + ("create_namespaced_custom_object runtime error", {"job":create_job(), "namespace": "runtime" },RuntimeError), + + ]) + def test_create_job(self,test_name, kwargs, expected_output ): + """ + test create_job function of training client + """ + print("Executing test:", test_name) + try: + self.training_client.create_job(**kwargs) + except Exception as e: + self.assertEqual(type(e),expected_output) + print("test execution complete") + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 6ce1c68b86a0a7cb6e7cc2aaed66cae34fab6a5d Mon Sep 17 00:00:00 2001 From: deepanker13 Date: Fri, 27 Oct 2023 16:49:03 +0530 Subject: [PATCH 2/6] code reformatted using black --- sdk/python/setup.py | 2 +- sdk/python/test/test_training_client.py | 124 ++++++++++++++++++------ 2 files changed, 97 insertions(+), 29 deletions(-) diff --git a/sdk/python/setup.py b/sdk/python/setup.py index 510facac98..dbd23e6ac4 100644 --- a/sdk/python/setup.py +++ b/sdk/python/setup.py @@ -29,7 +29,7 @@ "urllib3>=1.15.1", "kubernetes>=23.6.0", "retrying>=1.3.3", - "parameterized>=0.9.0" + "parameterized>=0.9.0", ] setuptools.setup( diff --git a/sdk/python/test/test_training_client.py b/sdk/python/test/test_training_client.py index 6e89604030..5dabc8f5d2 100644 --- a/sdk/python/test/test_training_client.py +++ b/sdk/python/test/test_training_client.py @@ -21,20 +21,23 @@ CONTAINER_NAME = "pytorch" JOB_NAME = "pytorchjob-mnist-ci-test" + def create_namespaced_custom_object_response(*args, **kwargs): - if args[2] == 'timeout': + if args[2] == "timeout": raise multiprocessing.TimeoutError() - elif args[2] == 'runtime': + elif args[2] == "runtime": raise RuntimeError() + def generate_container() -> V1Container: return V1Container( name=CONTAINER_NAME, image="gcr.io/kubeflow-ci/pytorch-dist-mnist-test:v1.0", args=["--backend", "gloo"], - resources=V1ResourceRequirements(limits={"memory": '1Gi', "cpu": "0.4"}), + resources=V1ResourceRequirements(limits={"memory": "1Gi", "cpu": "0.4"}), ) + def generate_pytorchjob( job_namespace: str, master: KubeflowOrgV1ReplicaSpec, @@ -54,6 +57,7 @@ def generate_pytorchjob( ), ) + def create_job(): job_namespace = "test" container = generate_container() @@ -81,35 +85,99 @@ def create_job(): pytorchjob = generate_pytorchjob(job_namespace, master, worker) return pytorchjob + class DummyJobClass: - def __init__(self,kind) -> None: + def __init__(self, kind) -> None: self.kind = kind -class TestTrainingClient(unittest.TestCase): - @patch('kubernetes.client.CustomObjectsApi', return_value=Mock(create_namespaced_custom_object=Mock(side_effect=create_namespaced_custom_object_response))) - @patch('kubernetes.client.CoreV1Api', return_value=Mock()) - @patch('kubernetes.config.load_kube_config', return_value=Mock()) +class TestTrainingClient(unittest.TestCase): + @patch( + "kubernetes.client.CustomObjectsApi", + return_value=Mock( + create_namespaced_custom_object=Mock( + side_effect=create_namespaced_custom_object_response + ) + ), + ) + @patch("kubernetes.client.CoreV1Api", return_value=Mock()) + @patch("kubernetes.config.load_kube_config", return_value=Mock()) def setUp(self, mock_custom_api, mock_core_api, mock_load_kube_config) -> None: self.training_client = TrainingClient(job_kind=constants.PYTORCHJOB_KIND) - - @parameterized.expand([ - ("invalid extra parameter", {"job":create_job(), "namespace": "test", "base_image":"test_image" },ValueError), - ("invalid job kind", {"job_kind": "invalid_job_kind" },ValueError), - ("job name missing ", {"train_func": lambda: "test train function"}, ValueError), - ("job name missing", {"base_image":"test_image"}, ValueError), - ("uncallable train function", {"name": "test job", "train_func":"uncallable train function"}, ValueError), - ("invalid TFJob replica", {"name": "test job", "train_func": lambda: "test train function", "job_kind": constants.TFJOB_KIND }, ValueError ), - ("invalid PyTorchJob replica", {"name": "test job", "train_func": lambda: "test train function","job_kind": constants.PYTORCHJOB_KIND }, ValueError ), - ("invalid pod template spec parameters", {"name": "test job", "train_func": lambda: "test train function","job_kind": constants.MXJOB_KIND }, KeyError ), - ("paddle job can't be created using function", {"name": "test job", "train_func": lambda: "test train function","job_kind": constants.PADDLEJOB_KIND }, ValueError ), - ("invalid job object", {"job": DummyJobClass(constants.TFJOB_KIND)}, ValueError), - ("create_namespaced_custom_object timeout error", {"job":create_job(), "namespace": "timeout" },TimeoutError), - ("create_namespaced_custom_object runtime error", {"job":create_job(), "namespace": "runtime" },RuntimeError), - - ]) - def test_create_job(self,test_name, kwargs, expected_output ): + @parameterized.expand( + [ + ( + "invalid extra parameter", + {"job": create_job(), "namespace": "test", "base_image": "test_image"}, + ValueError, + ), + ("invalid job kind", {"job_kind": "invalid_job_kind"}, ValueError), + ( + "job name missing ", + {"train_func": lambda: "test train function"}, + ValueError, + ), + ("job name missing", {"base_image": "test_image"}, ValueError), + ( + "uncallable train function", + {"name": "test job", "train_func": "uncallable train function"}, + ValueError, + ), + ( + "invalid TFJob replica", + { + "name": "test job", + "train_func": lambda: "test train function", + "job_kind": constants.TFJOB_KIND, + }, + ValueError, + ), + ( + "invalid PyTorchJob replica", + { + "name": "test job", + "train_func": lambda: "test train function", + "job_kind": constants.PYTORCHJOB_KIND, + }, + ValueError, + ), + ( + "invalid pod template spec parameters", + { + "name": "test job", + "train_func": lambda: "test train function", + "job_kind": constants.MXJOB_KIND, + }, + KeyError, + ), + ( + "paddle job can't be created using function", + { + "name": "test job", + "train_func": lambda: "test train function", + "job_kind": constants.PADDLEJOB_KIND, + }, + ValueError, + ), + ( + "invalid job object", + {"job": DummyJobClass(constants.TFJOB_KIND)}, + ValueError, + ), + ( + "create_namespaced_custom_object timeout error", + {"job": create_job(), "namespace": "timeout"}, + TimeoutError, + ), + ( + "create_namespaced_custom_object runtime error", + {"job": create_job(), "namespace": "runtime"}, + RuntimeError, + ), + ] + ) + def test_create_job(self, test_name, kwargs, expected_output): """ test create_job function of training client """ @@ -117,9 +185,9 @@ def test_create_job(self,test_name, kwargs, expected_output ): try: self.training_client.create_job(**kwargs) except Exception as e: - self.assertEqual(type(e),expected_output) + self.assertEqual(type(e), expected_output) print("test execution complete") -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() From 53c784037ebcb7bbd3179c0f0c213b0e2dbc22f6 Mon Sep 17 00:00:00 2001 From: deepanker13 Date: Fri, 27 Oct 2023 18:13:43 +0530 Subject: [PATCH 3/6] move unittest to a different folder --- sdk/python/test/{ => sdk_unittest}/test_training_client.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sdk/python/test/{ => sdk_unittest}/test_training_client.py (100%) diff --git a/sdk/python/test/test_training_client.py b/sdk/python/test/sdk_unittest/test_training_client.py similarity index 100% rename from sdk/python/test/test_training_client.py rename to sdk/python/test/sdk_unittest/test_training_client.py From 29fc3dc055195d28cab0d020586502d33b4c34b0 Mon Sep 17 00:00:00 2001 From: deepanker13 Date: Thu, 2 Nov 2023 04:04:49 +0530 Subject: [PATCH 4/6] code review comments changes --- .../api/test_training_client_create_job.py | 194 ++++++++++++++++++ sdk/python/setup.py | 1 - .../test/sdk_unittest/test_training_client.py | 193 ----------------- 3 files changed, 194 insertions(+), 194 deletions(-) create mode 100644 sdk/python/kubeflow/training/api/test_training_client_create_job.py delete mode 100644 sdk/python/test/sdk_unittest/test_training_client.py diff --git a/sdk/python/kubeflow/training/api/test_training_client_create_job.py b/sdk/python/kubeflow/training/api/test_training_client_create_job.py new file mode 100644 index 0000000000..5210104f7b --- /dev/null +++ b/sdk/python/kubeflow/training/api/test_training_client_create_job.py @@ -0,0 +1,194 @@ +import multiprocessing +import pytest +from unittest.mock import patch, Mock + +from typing import Optional +from kubeflow.training import TrainingClient +from kubeflow.training import KubeflowOrgV1ReplicaSpec +from kubeflow.training import KubeflowOrgV1PyTorchJob +from kubeflow.training import KubeflowOrgV1PyTorchJobSpec +from kubeflow.training import KubeflowOrgV1RunPolicy +from kubeflow.training import KubeflowOrgV1SchedulingPolicy +from kubeflow.training import constants + +from kubernetes.client import V1PodTemplateSpec +from kubernetes.client import V1ObjectMeta +from kubernetes.client import V1PodSpec +from kubernetes.client import V1Container +from kubernetes.client import V1ResourceRequirements + + +def create_namespaced_custom_object_response(*args, **kwargs): + if args[2] == "timeout": + raise multiprocessing.TimeoutError() + elif args[2] == "runtime": + raise RuntimeError() + + +def generate_container() -> V1Container: + return V1Container( + name="pytorch", + image="gcr.io/kubeflow-ci/pytorch-dist-mnist-test:v1.0", + args=["--backend", "gloo"], + resources=V1ResourceRequirements(limits={"memory": "1Gi", "cpu": "0.4"}), + ) + + +def generate_pytorchjob( + job_namespace: str, + master: KubeflowOrgV1ReplicaSpec, + worker: KubeflowOrgV1ReplicaSpec, + scheduling_policy: Optional[KubeflowOrgV1SchedulingPolicy] = None, +) -> KubeflowOrgV1PyTorchJob: + return KubeflowOrgV1PyTorchJob( + api_version=constants.API_VERSION, + kind=constants.PYTORCHJOB_KIND, + metadata=V1ObjectMeta(name="pytorchjob-mnist-ci-test", namespace=job_namespace), + spec=KubeflowOrgV1PyTorchJobSpec( + run_policy=KubeflowOrgV1RunPolicy( + clean_pod_policy="None", + scheduling_policy=scheduling_policy, + ), + pytorch_replica_specs={"Master": master, "Worker": worker}, + ), + ) + + +def create_job(): + job_namespace = "test" + container = generate_container() + master = KubeflowOrgV1ReplicaSpec( + replicas=1, + restart_policy="OnFailure", + template=V1PodTemplateSpec( + metadata=V1ObjectMeta( + annotations={constants.ISTIO_SIDECAR_INJECTION: "false"} + ), + spec=V1PodSpec(containers=[container]), + ), + ) + + worker = KubeflowOrgV1ReplicaSpec( + replicas=1, + restart_policy="OnFailure", + template=V1PodTemplateSpec( + metadata=V1ObjectMeta( + annotations={constants.ISTIO_SIDECAR_INJECTION: "false"} + ), + spec=V1PodSpec(containers=[container]), + ), + ) + pytorchjob = generate_pytorchjob(job_namespace, master, worker) + return pytorchjob + + +class DummyJobClass: + def __init__(self, kind) -> None: + self.kind = kind + + +test_data = [ + ( + "invalid extra parameter", + {"job": create_job(), "namespace": "test", "base_image": "test_image"}, + ValueError, + ), + ("invalid job kind", {"job_kind": "invalid_job_kind"}, ValueError), + ( + "job name missing ", + {"train_func": lambda: "test train function"}, + ValueError, + ), + ("job name missing", {"base_image": "test_image"}, ValueError), + ( + "uncallable train function", + {"name": "test job", "train_func": "uncallable train function"}, + ValueError, + ), + ( + "invalid TFJob replica", + { + "name": "test job", + "train_func": lambda: "test train function", + "job_kind": constants.TFJOB_KIND, + }, + ValueError, + ), + ( + "invalid PyTorchJob replica", + { + "name": "test job", + "train_func": lambda: "test train function", + "job_kind": constants.PYTORCHJOB_KIND, + }, + ValueError, + ), + ( + "invalid pod template spec parameters", + { + "name": "test job", + "train_func": lambda: "test train function", + "job_kind": constants.MXJOB_KIND, + }, + KeyError, + ), + ( + "paddle job can't be created using function", + { + "name": "test job", + "train_func": lambda: "test train function", + "job_kind": constants.PADDLEJOB_KIND, + }, + ValueError, + ), + ( + "invalid job object", + {"job": DummyJobClass(constants.TFJOB_KIND)}, + ValueError, + ), + ( + "create_namespaced_custom_object timeout error", + {"job": create_job(), "namespace": "timeout"}, + TimeoutError, + ), + ( + "create_namespaced_custom_object runtime error", + {"job": create_job(), "namespace": "runtime"}, + RuntimeError, + ), + ( + "valid flow", + {"job": create_job(), "namespace": "test"}, + "success", + ), +] + + +@pytest.fixture +def training_client(): + with patch( + "kubernetes.client.CustomObjectsApi", + return_value=Mock( + create_namespaced_custom_object=Mock( + side_effect=create_namespaced_custom_object_response + ) + ), + ), patch("kubernetes.client.CoreV1Api", return_value=Mock()), patch( + "kubernetes.config.load_kube_config", return_value=Mock() + ): + client = TrainingClient(job_kind=constants.PYTORCHJOB_KIND) + yield client + + +@pytest.mark.parametrize("test_name,kwargs,expected_output", test_data) +def test_create_job(training_client, test_name, kwargs, expected_output): + """ + test create_job function of training client + """ + print("Executing test:", test_name) + try: + training_client.create_job(**kwargs) + assert expected_output == "success" + except Exception as e: + assert type(e) == expected_output + print("test execution complete") diff --git a/sdk/python/setup.py b/sdk/python/setup.py index dbd23e6ac4..fc93bac67d 100644 --- a/sdk/python/setup.py +++ b/sdk/python/setup.py @@ -29,7 +29,6 @@ "urllib3>=1.15.1", "kubernetes>=23.6.0", "retrying>=1.3.3", - "parameterized>=0.9.0", ] setuptools.setup( diff --git a/sdk/python/test/sdk_unittest/test_training_client.py b/sdk/python/test/sdk_unittest/test_training_client.py deleted file mode 100644 index 5dabc8f5d2..0000000000 --- a/sdk/python/test/sdk_unittest/test_training_client.py +++ /dev/null @@ -1,193 +0,0 @@ -import multiprocessing -import unittest -from unittest.mock import patch, Mock -from parameterized import parameterized - -from typing import Optional -from kubeflow.training import TrainingClient -from kubeflow.training import KubeflowOrgV1ReplicaSpec -from kubeflow.training import KubeflowOrgV1PyTorchJob -from kubeflow.training import KubeflowOrgV1PyTorchJobSpec -from kubeflow.training import KubeflowOrgV1RunPolicy -from kubeflow.training import KubeflowOrgV1SchedulingPolicy -from kubeflow.training import constants - -from kubernetes.client import V1PodTemplateSpec -from kubernetes.client import V1ObjectMeta -from kubernetes.client import V1PodSpec -from kubernetes.client import V1Container -from kubernetes.client import V1ResourceRequirements - -CONTAINER_NAME = "pytorch" -JOB_NAME = "pytorchjob-mnist-ci-test" - - -def create_namespaced_custom_object_response(*args, **kwargs): - if args[2] == "timeout": - raise multiprocessing.TimeoutError() - elif args[2] == "runtime": - raise RuntimeError() - - -def generate_container() -> V1Container: - return V1Container( - name=CONTAINER_NAME, - image="gcr.io/kubeflow-ci/pytorch-dist-mnist-test:v1.0", - args=["--backend", "gloo"], - resources=V1ResourceRequirements(limits={"memory": "1Gi", "cpu": "0.4"}), - ) - - -def generate_pytorchjob( - job_namespace: str, - master: KubeflowOrgV1ReplicaSpec, - worker: KubeflowOrgV1ReplicaSpec, - scheduling_policy: Optional[KubeflowOrgV1SchedulingPolicy] = None, -) -> KubeflowOrgV1PyTorchJob: - return KubeflowOrgV1PyTorchJob( - api_version=constants.API_VERSION, - kind=constants.PYTORCHJOB_KIND, - metadata=V1ObjectMeta(name=JOB_NAME, namespace=job_namespace), - spec=KubeflowOrgV1PyTorchJobSpec( - run_policy=KubeflowOrgV1RunPolicy( - clean_pod_policy="None", - scheduling_policy=scheduling_policy, - ), - pytorch_replica_specs={"Master": master, "Worker": worker}, - ), - ) - - -def create_job(): - job_namespace = "test" - container = generate_container() - master = KubeflowOrgV1ReplicaSpec( - replicas=1, - restart_policy="OnFailure", - template=V1PodTemplateSpec( - metadata=V1ObjectMeta( - annotations={constants.ISTIO_SIDECAR_INJECTION: "false"} - ), - spec=V1PodSpec(containers=[container]), - ), - ) - - worker = KubeflowOrgV1ReplicaSpec( - replicas=1, - restart_policy="OnFailure", - template=V1PodTemplateSpec( - metadata=V1ObjectMeta( - annotations={constants.ISTIO_SIDECAR_INJECTION: "false"} - ), - spec=V1PodSpec(containers=[container]), - ), - ) - pytorchjob = generate_pytorchjob(job_namespace, master, worker) - return pytorchjob - - -class DummyJobClass: - def __init__(self, kind) -> None: - self.kind = kind - - -class TestTrainingClient(unittest.TestCase): - @patch( - "kubernetes.client.CustomObjectsApi", - return_value=Mock( - create_namespaced_custom_object=Mock( - side_effect=create_namespaced_custom_object_response - ) - ), - ) - @patch("kubernetes.client.CoreV1Api", return_value=Mock()) - @patch("kubernetes.config.load_kube_config", return_value=Mock()) - def setUp(self, mock_custom_api, mock_core_api, mock_load_kube_config) -> None: - self.training_client = TrainingClient(job_kind=constants.PYTORCHJOB_KIND) - - @parameterized.expand( - [ - ( - "invalid extra parameter", - {"job": create_job(), "namespace": "test", "base_image": "test_image"}, - ValueError, - ), - ("invalid job kind", {"job_kind": "invalid_job_kind"}, ValueError), - ( - "job name missing ", - {"train_func": lambda: "test train function"}, - ValueError, - ), - ("job name missing", {"base_image": "test_image"}, ValueError), - ( - "uncallable train function", - {"name": "test job", "train_func": "uncallable train function"}, - ValueError, - ), - ( - "invalid TFJob replica", - { - "name": "test job", - "train_func": lambda: "test train function", - "job_kind": constants.TFJOB_KIND, - }, - ValueError, - ), - ( - "invalid PyTorchJob replica", - { - "name": "test job", - "train_func": lambda: "test train function", - "job_kind": constants.PYTORCHJOB_KIND, - }, - ValueError, - ), - ( - "invalid pod template spec parameters", - { - "name": "test job", - "train_func": lambda: "test train function", - "job_kind": constants.MXJOB_KIND, - }, - KeyError, - ), - ( - "paddle job can't be created using function", - { - "name": "test job", - "train_func": lambda: "test train function", - "job_kind": constants.PADDLEJOB_KIND, - }, - ValueError, - ), - ( - "invalid job object", - {"job": DummyJobClass(constants.TFJOB_KIND)}, - ValueError, - ), - ( - "create_namespaced_custom_object timeout error", - {"job": create_job(), "namespace": "timeout"}, - TimeoutError, - ), - ( - "create_namespaced_custom_object runtime error", - {"job": create_job(), "namespace": "runtime"}, - RuntimeError, - ), - ] - ) - def test_create_job(self, test_name, kwargs, expected_output): - """ - test create_job function of training client - """ - print("Executing test:", test_name) - try: - self.training_client.create_job(**kwargs) - except Exception as e: - self.assertEqual(type(e), expected_output) - print("test execution complete") - - -if __name__ == "__main__": - unittest.main() From ba71a23028a193e94c95f3ca71327d6159372c29 Mon Sep 17 00:00:00 2001 From: deepanker13 Date: Mon, 20 Nov 2023 19:42:57 +0530 Subject: [PATCH 5/6] adding pytest github action --- .github/workflows/test-python.yaml | 15 ++++++++++++++- ...ient_create_job.py => training_client_test.py} | 0 2 files changed, 14 insertions(+), 1 deletion(-) rename sdk/python/kubeflow/training/api/{test_training_client_create_job.py => training_client_test.py} (100%) diff --git a/.github/workflows/test-python.yaml b/.github/workflows/test-python.yaml index 09dc39d9cb..a3dfe39c65 100644 --- a/.github/workflows/test-python.yaml +++ b/.github/workflows/test-python.yaml @@ -5,7 +5,7 @@ on: - pull_request jobs: - test: + python-test: name: Test runs-on: ubuntu-latest @@ -21,3 +21,16 @@ jobs: version: 23.9.1 options: --check --exclude '/*kubeflow_org_v1*|__init__.py|api_client.py|configuration.py|exceptions.py|rest.py' src: sdk/ + + - name: Install dependencies + run: pip install pytest pytest-md pytest-emoji python-dateutil urllib3 kubernetes + + - name: Run unit test for training sdk + uses: pavelzw/pytest-action@v2 + with: + verbose: true + emoji: true + job-summary: true + click-to-expand: true + report-title: 'Test Report' + custom-arguments: ./sdk/python/kubeflow/training/api/training_client_test.py \ No newline at end of file diff --git a/sdk/python/kubeflow/training/api/test_training_client_create_job.py b/sdk/python/kubeflow/training/api/training_client_test.py similarity index 100% rename from sdk/python/kubeflow/training/api/test_training_client_create_job.py rename to sdk/python/kubeflow/training/api/training_client_test.py From 9452cdee7f586770186a68264153570cd367f092 Mon Sep 17 00:00:00 2001 From: deepanker13 Date: Tue, 21 Nov 2023 12:07:09 +0530 Subject: [PATCH 6/6] removing 3rd party github pytest action --- .github/workflows/test-python.yaml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-python.yaml b/.github/workflows/test-python.yaml index a3dfe39c65..61f6c82f45 100644 --- a/.github/workflows/test-python.yaml +++ b/.github/workflows/test-python.yaml @@ -23,14 +23,7 @@ jobs: src: sdk/ - name: Install dependencies - run: pip install pytest pytest-md pytest-emoji python-dateutil urllib3 kubernetes + run: pip install pytest python-dateutil urllib3 kubernetes - name: Run unit test for training sdk - uses: pavelzw/pytest-action@v2 - with: - verbose: true - emoji: true - job-summary: true - click-to-expand: true - report-title: 'Test Report' - custom-arguments: ./sdk/python/kubeflow/training/api/training_client_test.py \ No newline at end of file + run: pytest ./sdk/python/kubeflow/training/api/training_client_test.py \ No newline at end of file