szehon-ho commented on code in PR #58055:
URL: https://github.com/apache/spark/pull/58055#discussion_r3867923981


##########
sql/pipelines/src/test/scala/org/apache/spark/sql/pipelines/graph/AutoCdcCrossScdConvergenceSuite.scala:
##########
@@ -0,0 +1,109 @@
+/*
+ * 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.sql.pipelines.graph
+
+import scala.util.Random
+
+import org.apache.spark.sql.functions
+import org.apache.spark.sql.pipelines.autocdc.{Scd2BatchProcessor, ScdType}
+import org.apache.spark.sql.pipelines.utils.ExecutionTest
+import org.apache.spark.sql.test.SharedSparkSession
+
+/**
+ * Differential test for cross-SCD current-state agreement: given the same 
randomly-generated
+ * CDF, every live key's SCD Type 1 target row must equal (in user data 
columns) the current
+ * open SCD Type 2 row for that key - this is by definitions of an SCD1 and 
SCD2 transformation.
+ *
+ * By asserting the final outcome of SCD1 equals the final live rows of SCD2, 
each implementation
+ * is an effective verifier of the other, and catches regressions or behavior 
changes made to one
+ * implementation but not the other.
+ *
+ * CDC metadata and SCD2 interval bounds are not compared.
+ */
+class AutoCdcCrossScdConvergenceSuite
+    extends ExecutionTest
+    with SharedSparkSession
+    with AutoCdcGraphExecutionTestMixin
+    with AutoCdcRandomCdcTestMixin {
+
+  /**
+   * Assert SCD1 live rows equal SCD2 current open rows (`__END_AT IS NULL`) 
on user data
+   * columns only.
+   */
+  private def assertCrossScdAgreement(
+      scd1Table: String,
+      scd2Table: String,
+      expectedLiveKeyCount: Int): Unit = {
+    val scd1Data = spark.table(s"$catalog.$namespace.$scd1Table").select(
+      dataColumnNames.map(functions.col): _*
+    )
+    val scd2CurrentData = spark.table(s"$catalog.$namespace.$scd2Table")
+      .where(functions.col(Scd2BatchProcessor.endAtColName).isNull)
+      .select(dataColumnNames.map(functions.col): _*)
+
+    // Verify the number of live keys (i.e rows that haven't been fully 
deleted) are the same in
+    // both SCD1 and SCD2, after all events are applied.
+    val scd1LiveKeyCount = scd1Data.count()
+    val scd2LiveKeyCount = scd2CurrentData.count()
+    assert(
+      scd1LiveKeyCount == expectedLiveKeyCount,
+      s"Expected $expectedLiveKeyCount live SCD1 keys, found 
$scd1LiveKeyCount")
+    assert(
+      scd2LiveKeyCount == expectedLiveKeyCount,
+      s"Expected $expectedLiveKeyCount live SCD2 keys, found 
$scd2LiveKeyCount")
+
+    checkAnswer(scd1Data, scd2CurrentData)
+  }
+
+  private val crossScdConvergenceTestName =
+    "SCD1 current rows match SCD2 open rows for the same shuffled CDC stream"
+
+  test(crossScdConvergenceTestName) {
+    val numDistinctKeys = resolveNumDistinctKeys()
+    val maxUniqueEventsPerKey = resolveMaxUniqueEventsPerKey()
+    val numBatches = resolveNumBatches()
+
+    forEachConvergenceSeed(crossScdConvergenceTestName) { (seed, seedIndex) =>
+      val rand = new Random(seed)
+      val sortedEventStream = generateRandomCdcEventStream(rand)
+      val shuffledEventStream = rand.shuffle(sortedEventStream)
+      val expectedLiveKeyCount = sortedEventStream
+        .groupBy(_.key)
+        .values
+        .count(events => !events.maxBy(_.sequence).isDelete)
+
+      // Avoid dumping thousands of events into every clue string (ScalaTest 
evaluates clues
+      // eagerly).
+      withClue(
+        s"\ncross-SCD convergence testName=$crossScdConvergenceTestName " +
+        s"seedIndex=$seedIndex iterationSeed=$seed " +
+        s"(rerun this test with -D$baseSeedSystemProperty=$configuredBaseSeed 
" +
+        s"-D$numSeedsSystemProperty=1 to reproduce)\n" +

Review Comment:
   Suggest hinting the same `numSeeds` used (so replay hits the same index), or 
add a property to pin `iterationSeed` directly.
   
   `numSeeds=1` only reproduces `seedIndex=0`; a multi-seed failure at 
`seedIndex>0` reruns a different seed and won't reproduce.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to