This is an automated email from the ASF dual-hosted git repository.

dongjoon pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/spark-kubernetes-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 7522641  [SPARK-55566] Add `SparkClusterReconcilerTest`
7522641 is described below

commit 7522641dc66c13ccfdb5439fedba6ef7427e0820
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Tue Feb 17 08:55:59 2026 -0800

    [SPARK-55566] Add `SparkClusterReconcilerTest`
    
    ### What changes were proposed in this pull request?
    
    This PR aims to add `SparkClusterReconcilerTest`.
    
    ### Why are the changes needed?
    
    To improve a unit test coverage for `SparkCluster` CRD.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Pass the CIs.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: `Opus 4.5` on `Claude Code`
    
    Closes #508 from dongjoon-hyun/SPARK-55566.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 .../reconciler/SparkClusterReconcilerTest.java     | 195 +++++++++++++++++++++
 1 file changed, 195 insertions(+)

diff --git 
a/spark-operator/src/test/java/org/apache/spark/k8s/operator/reconciler/SparkClusterReconcilerTest.java
 
b/spark-operator/src/test/java/org/apache/spark/k8s/operator/reconciler/SparkClusterReconcilerTest.java
new file mode 100644
index 0000000..2890382
--- /dev/null
+++ 
b/spark-operator/src/test/java/org/apache/spark/k8s/operator/reconciler/SparkClusterReconcilerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.spark.k8s.operator.reconciler;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
+
+import org.apache.spark.k8s.operator.SparkCluster;
+import org.apache.spark.k8s.operator.SparkClusterSubmissionWorker;
+import org.apache.spark.k8s.operator.context.SparkClusterContext;
+import org.apache.spark.k8s.operator.metrics.healthcheck.SentinelManager;
+import 
org.apache.spark.k8s.operator.reconciler.reconcilesteps.ClusterReconcileStep;
+import org.apache.spark.k8s.operator.status.ClusterState;
+import org.apache.spark.k8s.operator.status.ClusterStateSummary;
+import org.apache.spark.k8s.operator.status.ClusterStatus;
+import org.apache.spark.k8s.operator.utils.SparkClusterStatusRecorder;
+
+class SparkClusterReconcilerTest {
+  private final SparkClusterStatusRecorder mockRecorder = 
mock(SparkClusterStatusRecorder.class);
+  private final SentinelManager<SparkCluster> mockSentinelManager = 
mock(SentinelManager.class);
+  private final KubernetesClient mockClient = mock(KubernetesClient.class);
+  private final Context<SparkCluster> mockContext = mock(Context.class);
+  private final SparkClusterSubmissionWorker mockWorker = 
mock(SparkClusterSubmissionWorker.class);
+  SparkCluster cluster = new SparkCluster();
+  SparkClusterReconciler reconciler =
+      new SparkClusterReconciler(mockWorker, mockRecorder, 
mockSentinelManager);
+
+  @BeforeEach
+  public void beforeEach() {
+    when(mockContext.getClient()).thenReturn(mockClient);
+    doNothing().when(mockRecorder).removeCachedStatus(any(SparkCluster.class));
+    doAnswer(
+            invocation -> {
+              cluster.setStatus(invocation.getArgument(1));
+              return null;
+            })
+        .when(mockRecorder)
+        .persistStatus(any(SparkClusterContext.class), 
any(ClusterStatus.class));
+    doAnswer(
+            invocation -> {
+              ClusterStatus updatedStatus =
+                  
cluster.getStatus().appendNewState(invocation.getArgument(1));
+              cluster.setStatus(updatedStatus);
+              return null;
+            })
+        .when(mockRecorder)
+        .appendNewStateAndPersist(any(SparkClusterContext.class), 
any(ClusterState.class));
+  }
+
+  @SuppressWarnings("PMD.UnusedLocalVariable")
+  @Test
+  void testCleanupRunningCluster() {
+    try (MockedConstruction<SparkClusterContext> mockClusterContext =
+        mockConstruction(
+            SparkClusterContext.class,
+            (mock, context) -> {
+              when(mock.getResource()).thenReturn(cluster);
+              when(mock.getClient()).thenReturn(mockClient);
+            })) {
+      // delete running cluster
+      cluster.setStatus(cluster.getStatus().appendNewState(
+          new ClusterState(ClusterStateSummary.RunningHealthy, "")));
+      DeleteControl deleteControl = reconciler.cleanup(cluster, mockContext);
+      // Cluster in RunningHealthy state - cleanup completes and allows 
deletion
+      assertTrue(deleteControl.isRemoveFinalizer());
+    }
+  }
+
+  @SuppressWarnings("PMD.UnusedLocalVariable")
+  @Test
+  void testCleanupClusterResourceReleased() {
+    try (MockedConstruction<SparkClusterContext> mockClusterContext =
+        mockConstruction(
+            SparkClusterContext.class,
+            (mock, context) -> {
+              when(mock.getResource()).thenReturn(cluster);
+              when(mock.getClient()).thenReturn(mockClient);
+            })) {
+      // delete cluster that has already released resources
+      cluster.setStatus(cluster.getStatus().appendNewState(
+          new ClusterState(ClusterStateSummary.ResourceReleased, "")));
+      DeleteControl deleteControl = reconciler.cleanup(cluster, mockContext);
+      assertTrue(deleteControl.isRemoveFinalizer());
+    }
+  }
+
+  @SuppressWarnings("PMD.UnusedLocalVariable")
+  @Test
+  void testCleanupFailedCluster() {
+    try (MockedConstruction<SparkClusterContext> mockClusterContext =
+        mockConstruction(
+            SparkClusterContext.class,
+            (mock, context) -> {
+              when(mock.getResource()).thenReturn(cluster);
+              when(mock.getClient()).thenReturn(mockClient);
+            })) {
+      // delete failed cluster
+      cluster.setStatus(
+          cluster.getStatus().appendNewState(new 
ClusterState(ClusterStateSummary.Failed, "")));
+      DeleteControl deleteControl = reconciler.cleanup(cluster, mockContext);
+      // Failed cluster cleanup completes and allows deletion
+      assertTrue(deleteControl.isRemoveFinalizer());
+    }
+  }
+
+  @Test
+  void testGetReconcileStepsForSubmittedCluster() {
+    // Submitted state should include ClusterInitStep
+    cluster.setStatus(
+        cluster.getStatus().appendNewState(new 
ClusterState(ClusterStateSummary.Submitted, "")));
+    List<ClusterReconcileStep> steps = reconciler.getReconcileSteps(cluster);
+    assertEquals(3, steps.size());
+    assertEquals(
+        "ClusterValidateStep", steps.get(0).getClass().getSimpleName());
+    assertEquals(
+        "ClusterTerminatedStep", steps.get(1).getClass().getSimpleName());
+    assertEquals(
+        "ClusterInitStep", steps.get(2).getClass().getSimpleName());
+  }
+
+  @Test
+  void testGetReconcileStepsForRunningHealthyCluster() {
+    // RunningHealthy state should not have additional steps beyond validation 
and termination check
+    cluster.setStatus(cluster.getStatus().appendNewState(
+        new ClusterState(ClusterStateSummary.RunningHealthy, "")));
+    List<ClusterReconcileStep> steps = reconciler.getReconcileSteps(cluster);
+    assertEquals(2, steps.size());
+    assertEquals(
+        "ClusterValidateStep", steps.get(0).getClass().getSimpleName());
+    assertEquals(
+        "ClusterTerminatedStep", steps.get(1).getClass().getSimpleName());
+  }
+
+  @Test
+  void testGetReconcileStepsForFailedCluster() {
+    // Failed state should include ClusterUnknownStateStep
+    cluster.setStatus(
+        cluster.getStatus().appendNewState(new 
ClusterState(ClusterStateSummary.Failed, "")));
+    List<ClusterReconcileStep> steps = reconciler.getReconcileSteps(cluster);
+    assertEquals(3, steps.size());
+    assertEquals(
+        "ClusterValidateStep", steps.get(0).getClass().getSimpleName());
+    assertEquals(
+        "ClusterTerminatedStep", steps.get(1).getClass().getSimpleName());
+    assertEquals(
+        "ClusterUnknownStateStep", steps.get(2).getClass().getSimpleName());
+  }
+
+  @Test
+  void testGetReconcileStepsForResourceReleasedCluster() {
+    // ResourceReleased state should include ClusterUnknownStateStep
+    cluster.setStatus(cluster.getStatus().appendNewState(
+        new ClusterState(ClusterStateSummary.ResourceReleased, "")));
+    List<ClusterReconcileStep> steps = reconciler.getReconcileSteps(cluster);
+    assertEquals(3, steps.size());
+    assertEquals(
+        "ClusterValidateStep", steps.get(0).getClass().getSimpleName());
+    assertEquals(
+        "ClusterTerminatedStep", steps.get(1).getClass().getSimpleName());
+    assertEquals(
+        "ClusterUnknownStateStep", steps.get(2).getClass().getSimpleName());
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to