Repository navigation
chore(spark): expand unit test coverage for SparkClient job methods - #750
Neha-Nimsatkar wants to merge 5 commits into
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
🎉 Welcome to the Kubeflow SDK! 🎉 Thanks for opening your first PR! We're happy to have you as part of our community 🚀 Here's what happens next:
Join the community:
Feel free to ask questions in the comments if you need any help or clarification! |
| ) | ||
|
|
||
|
|
||
| def test_get_job(): |
There was a problem hiding this comment.
The new tests should use the @pytest.mark.parametrize marker with the TestCase dataclass from kubeflow.spark.test.common - similar to backend_test.py and utils_test.py.
sdk/kubeflow/spark/test/common.py
Lines 33 to 42 in 3febcd7
There was a problem hiding this comment.
Thanks for the pointer @tariq-hasan! I've refactored all new job management test methods (get_job, list_jobs, delete_job, wait_for_job_status, and get_job_logs) to use @pytest.mark.parametrize with the TestCase dataclass from kubeflow.spark.test.common.
All unit tests, ruff linting, and pre-commit checks are passing cleanly. Please have a look!
|
/ok-to-test |
|
Hi @tariq-hasan, I've updated the tests to use @pytest.mark.parametrize with the TestCase dataclass pattern from kubeflow.spark.test.common, and all unit tests and pre-commit hooks are passing locally. The E2E Test matrix hit an infrastructure disconnect across the self-hosted runners ("self-hosted runner lost communication with the server"). Could you please click "Re-run failed jobs" when you get a chance? Thanks! |
|
/retest |
|
/ok-to-test |
|
/retitle chore(spark): expand unit test coverage for SparkClient job methods |
| @@ -173,3 +173,158 @@ def test_submit_job_success(job, options): | |||
| spark_conf=None, | |||
| options=options, | |||
| ) | |||
There was a problem hiding this comment.
Please combine test_submit_job_validation and test_submit_job_success into a single test_submit_job with the pytest.mark.parametrize marker and the TestCase dataclass usage.
There was a problem hiding this comment.
Let's address this as well.
In addition please add a case with all of num_executors, resources_per_executor and spark_conf set, asserting each reaches the backend.
| ), | ||
| ], | ||
| ) | ||
| def test_wait_for_job_status(test_case: TestCase): |
There was a problem hiding this comment.
For test_wait_for_job_status please test this behavior as well.
sdk/kubeflow/spark/api/spark_client.py
Lines 285 to 288 in 3febcd7
| TestCase( | ||
| name="get job by name", | ||
| expected_status=SUCCESS, | ||
| config={"job_name": "spark-job-123"}, |
There was a problem hiding this comment.
Please ensure name and expected_status get used as well - otherwise they would be redundant. The same applies for other tests as well.
Also unhappy paths where errors are raised should be tested in addition to happy paths.
| TestCase( | ||
| name="list jobs filtered by status", | ||
| expected_status=SUCCESS, | ||
| config={"status": "Running"}, |
There was a problem hiding this comment.
We should use SparkJobStatus.RUNNING.
| result = client.wait_for_job_status( | ||
| name=test_case.config["job_name"], | ||
| status=test_case.config["status"], | ||
| timeout=test_case.config["timeout"], |
There was a problem hiding this comment.
We're not really using polling interval here but the test case has polling interval so the test passes by accident. We need to test both default and overrides.
| expected_status=SUCCESS, | ||
| config={ | ||
| "job_name": "spark-job-123", | ||
| "status": "Completed", |
There was a problem hiding this comment.
We should use enum here - SparkJobStatus.COMPLETED.
|
Thanks for the detailed review, @tariq-hasan! I have addressed all the feedback and updated the test suite:
|
tariq-hasan
left a comment
There was a problem hiding this comment.
Hi @Neha-Nimsatkar! I have added a few comments.
| @@ -173,3 +173,158 @@ def test_submit_job_success(job, options): | |||
| spark_conf=None, | |||
| options=options, | |||
| ) | |||
There was a problem hiding this comment.
Let's address this as well.
In addition please add a case with all of num_executors, resources_per_executor and spark_conf set, asserting each reaches the backend.
| TestCase( | ||
| name="list jobs filtered by status", | ||
| expected_status=SUCCESS, | ||
| config={"status": SparkJobStatus.RUNNING}, |
There was a problem hiding this comment.
Let's fix the type contracts everywhere in the tests. For example this one should be
| config={"status": SparkJobStatus.RUNNING}, | |
| config={"status": {SparkJobStatus.RUNNING}}, |
| """Test wait_for_job_status delegation and validation.""" | ||
| with patch("kubeflow.spark.api.spark_client.KubernetesBackend") as mock_backend: | ||
| backend = mock_backend.return_value | ||
| backend.wait_for_job_status.return_value = True |
There was a problem hiding this comment.
backend.wait_for_job_status.return_value should return a mock SparkJob not a boolean.
| expected_error=ValueError, | ||
| ), | ||
| ], | ||
| ) |
There was a problem hiding this comment.
Let's also add cases for polling_interval <= 0, polling_interval >= timeout and timeout=0.
And cases for default and custom timeout as well.
The ideal test case matrix would cover
- default status invocation
- default timeout invocation
- default polling interval
- explicit polling interval
- single status {RUNNING}
- multiple statuses {COMPLETED, FAILED}
- invalid timeout < 0
- timeout = 0
- polling_interval <= 0
- polling_interval >= timeout
- successful wait
- backend timeout
| ], | ||
| ) | ||
| def test_wait_for_job_status(test_case: TestCase): | ||
| """Test wait_for_job_status delegation and validation.""" |
There was a problem hiding this comment.
For invalid inputs let's add something along the lines of
with pytest.raises(ValueError):
client.wait_for_job_status(...)
backend.wait_for_job_status.assert_not_called()
The idea is that validation must occur before delegation to the polling function and this invariant needs to be asserted in unit tests.
| backend.get_job_logs.assert_called_once_with( | ||
| name=test_case.config["job_name"], | ||
| follow=test_case.config["follow"], | ||
| ) |
There was a problem hiding this comment.
Let's print test name throughout all the tests. You can check kubeflow/spark/backends/kubernetes/backend_test.py for the general pattern.
In addition let's use expected_output from the TestCase dataclass more comprehensively in the tests.
| expected_output=["log stream 1", "log stream 2"], | ||
| ), | ||
| ], | ||
| ) |
There was a problem hiding this comment.
Let's have a default follow invocation that does not use the follow parameter in its input.
ebc20a8 to
38faf0d
Compare
|
Hi @tariq-hasan ,
|
RobuRishabh
left a comment
There was a problem hiding this comment.
@Neha-Nimsatkar Thanks for working on this, the validation-before-delegation assertions and the full matrix look great. One remaining gap: the "default status/timeout/polling interval" cases skip the assertion exactly when the default is in play (if "status" in test_case.config), so they'd pass even if the wrong default were forwarded. Could you assert the complete expected call (e.g., assert_called_once_with(name=..., status={SparkJobStatus.COMPLETED}, timeout=600, polling_interval=2) with .get() fallbacks)? Also, the test_submit_job failure cases currently inject the error via side_effect, so they test propagation rather than validation its worth renaming to reflect that.
Signed-off-by: Neha-Nimsatkar <1492neha@gmail.com>
4743f92 to
8d4eee1
Compare
Thanks @RobuRishabh, I have addressed both points:
Please take another look when you get time. |
Co-authored-by: Tariq Hasan <mmtariquehsn@gmail.com> Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
| name="single status {RUNNING}", | ||
| expected_status=SUCCESS, | ||
| config={ | ||
| "job_name": "spark-job-123", | ||
| "status": {SparkJobStatus.RUNNING}, | ||
| "timeout": 60, | ||
| }, | ||
| expected_output=SparkJob(name="spark-job-123", namespace="default"), | ||
| ), | ||
| TestCase( | ||
| name="multiple statuses {COMPLETED, FAILED}", | ||
| expected_status=SUCCESS, | ||
| config={ | ||
| "job_name": "spark-job-123", | ||
| "status": {SparkJobStatus.COMPLETED, SparkJobStatus.FAILED}, | ||
| "timeout": 60, | ||
| }, | ||
| expected_output=SparkJob(name="spark-job-123", namespace="default"), | ||
| ), | ||
| TestCase( |
There was a problem hiding this comment.
single status {RUNNING} and multiple statuses {COMPLETED, FAILED} duplicate the path of default polling interval (346), same kwargs shape, assertion just echoes config back; set contents don't change forwarding behavior. So either drop both or merge into one non-default-status case
There was a problem hiding this comment.
Done . Merged both cases into a single non-default status test case to eliminate redundant coverage.
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
| # If we got an exception but expected success, fail | ||
| assert test_case.expected_status == FAILED, f"Unexpected exception in {test_case.name}: {e}" | ||
| # Validate the exception type if specified | ||
| if test_case.expected_error: |
There was a problem hiding this comment.
Removing these explanatory comments from test_create_and_connect is unrelated to the job-methods coverage this PR describes. Mind reverting to keep the diff surgical?
There was a problem hiding this comment.
Restored both explanatory comments ...
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
b22255f to
6bd8e96
Compare
What this PR does / why we need it
Adds proper unit test coverage for the
SparkClientjob management methods inkubeflow/spark/api/spark_client_test.py—submit_job,get_job,list_jobs,delete_job,wait_for_job_status, andget_job_logs.Tests now follow the same
@pytest.mark.parametrize+TestCasedataclass pattern already used inbackend_test.py, so we get consistent coverage of backend delegation and input validation across the client.Motivation
A few of the public
SparkClientmethods didn't have solid tests around argument forwarding, type contracts, or validation before hitting the backend. This was making it easy for delegation bugs to slip through unnoticed. This PR closes that gap and brings the test style in line with the rest of the codebase.What changed
@pytest.mark.parametrizewith theTestCasedataclass — prints test names and checks againstexpected_output.test_submit_job: Combined validation and submission scenarios into one parametrized test. Added checks thatnum_executors,resources_per_executor, andspark_confget forwarded correctly, plusbackend.submit_job.assert_not_called()for invalid input.test_get_job: Covers job lookup delegation and theRuntimeErrorraised when a job isn't found.test_list_jobs: Fixed status filtering to expect sets ({SparkJobStatus.RUNNING},{SparkJobStatus.COMPLETED, SparkJobStatus.FAILED}), plus a failure case test.test_delete_job: Covers deletion delegation and the nonexistent-job error path.test_wait_for_job_status:SparkJobinstead of a bool.backend.wait_for_job_status.assert_not_called()check for validation failures.< 0,= 0), polling interval edge cases (<= 0,>= timeout).test_get_job_logs: Added cases for defaultfollow, plus explicitfollow=True/follow=False.Verification
uv run pytest kubeflow/spark/api/spark_client_test.py -v -s— all passinguv run ruff format kubeflow/spark/api/spark_client_test.pyuv run ruff check --fix kubeflow/spark/api/spark_client_test.pyuv run pre-commit run --all-filesmake verifyFixes
Fixes #658
Checklist
make verify