Skip to content

Commit a1b3388

Browse files
committed
test: extend awaitDataSync to also compare row content digest
Counts alone miss UPDATE-only drift: subtests that modify field values without changing row counts leave the two-node table content temporarily divergent while their cleanup repair replicates. The old gate returned immediately in that window, letting the next subtest start against drifted state. Add a hashtext-sum row digest alongside the count check. The digest is order-independent and PK-agnostic, so it generalises across tables and catches UPDATE-induced drift that counts cannot see.
1 parent 15e0dfd commit a1b3388

1 file changed

Lines changed: 18 additions & 8 deletions

File tree

tests/integration/test_env_test.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -392,22 +392,32 @@ func (e *testEnv) pairKey() string {
392392
return e.ServiceN1 + "/" + e.ServiceN2
393393
}
394394

395-
// awaitDataSync waits until n1 and n2 have the same row count for the given
396-
// table. This prevents inter-test bleed when a previous subtest's cleanup
397-
// repair is still replicating.
395+
// awaitDataSync waits until n1 and n2 have the same row count AND the same
396+
// content digest for the given table. Counts alone miss UPDATE-only drift
397+
// (repair replays after UPDATE-based subtests change field values without
398+
// changing row counts); the hashtext-sum digest is order-independent and
399+
// PK-agnostic, so it catches that case.
398400
func (e *testEnv) awaitDataSync(t *testing.T, qualifiedTableName string) {
399401
t.Helper()
400402
ctx := context.Background()
403+
query := fmt.Sprintf(
404+
"SELECT count(*), COALESCE(sum(hashtext(t::text)::bigint), 0) FROM %s t",
405+
qualifiedTableName,
406+
)
401407
assertEventually(t, 30*time.Second, func() error {
402408
var n1Count, n2Count int
403-
if err := e.N1Pool.QueryRow(ctx, fmt.Sprintf("SELECT count(*) FROM %s", qualifiedTableName)).Scan(&n1Count); err != nil {
404-
return fmt.Errorf("counting rows on n1: %w", err)
409+
var n1Digest, n2Digest int64
410+
if err := e.N1Pool.QueryRow(ctx, query).Scan(&n1Count, &n1Digest); err != nil {
411+
return fmt.Errorf("digesting rows on n1: %w", err)
405412
}
406-
if err := e.N2Pool.QueryRow(ctx, fmt.Sprintf("SELECT count(*) FROM %s", qualifiedTableName)).Scan(&n2Count); err != nil {
407-
return fmt.Errorf("counting rows on n2: %w", err)
413+
if err := e.N2Pool.QueryRow(ctx, query).Scan(&n2Count, &n2Digest); err != nil {
414+
return fmt.Errorf("digesting rows on n2: %w", err)
408415
}
409416
if n1Count != n2Count {
410-
return fmt.Errorf("nodes not in sync for %s: n1=%d n2=%d", qualifiedTableName, n1Count, n2Count)
417+
return fmt.Errorf("nodes not in sync for %s: n1 count=%d n2 count=%d", qualifiedTableName, n1Count, n2Count)
418+
}
419+
if n1Digest != n2Digest {
420+
return fmt.Errorf("nodes not in sync for %s: count=%d but digest n1=%d n2=%d", qualifiedTableName, n1Count, n1Digest, n2Digest)
411421
}
412422
return nil
413423
})

0 commit comments

Comments
 (0)