@@ -60,3 +60,91 @@ func GenerateCheckRecoveryScript(opts ScriptOptions, replicaPorts []int) string
6060 }
6161 return b .String ()
6262}
63+
64+ // GenerateTestReplicationScript emits a topology-level test_replication
65+ // script that verifies replication is actually working: it creates a
66+ // table on the primary, inserts rows, captures the primary's WAL LSN,
67+ // then queries each replica to confirm the LSN matches and the row
68+ // count is in sync. Modeled on the MySQL `test_replication` template
69+ // (sandbox/templates/replication/test_replication.gotxt) but adapted to
70+ // PostgreSQL primitives (pg_current_wal_lsn / pg_last_wal_replay_lsn).
71+ //
72+ // numReplicas is the count of replica sub-sandboxes laid down by
73+ // `deploy replication --provider=postgresql` (i.e. one less than --nodes).
74+ // The generated script invokes ./primary/use and ./replicaN/use, which
75+ // the PostgreSQL provider already creates as per-sandbox `use` scripts.
76+ func GenerateTestReplicationScript (opts ScriptOptions , numReplicas int ) string {
77+ preamble := fmt .Sprintf (envPreamble , opts .LibDir )
78+ var b strings.Builder
79+ b .WriteString (preamble )
80+ b .WriteString (fmt .Sprintf (`SBDIR=%s
81+ cd "$SBDIR"
82+
83+ if [ ! -x ./primary/use ]; then
84+ echo "# No primary found at ./primary/use"
85+ exit 1
86+ fi
87+ PRIMARY=./primary/use
88+
89+ FAILED=0
90+ PASSED=0
91+
92+ function ok_equal {
93+ fact="$1"
94+ expected="$2"
95+ msg="$3"
96+ if [ "$fact" = "$expected" ]; then
97+ echo "ok - (expected: <$expected>) - $msg"
98+ PASSED=$((PASSED+1))
99+ else
100+ echo "not ok - (expected: <$expected> found: <$fact>) - $msg"
101+ FAILED=$((FAILED+1))
102+ fi
103+ }
104+
105+ function test_summary {
106+ TESTS=$((PASSED+FAILED))
107+ printf "# Tests : %%5d\n" $TESTS
108+ printf "# Passed: %%5d\n" $PASSED
109+ printf "# Failed: %%5d\n" $FAILED
110+ if [ "$FAILED" != "0" ]; then exit 1; fi
111+ exit 0
112+ }
113+
114+ YYYY_MM=$(date +%%Y-%%m)
115+ echo "# Creating and populating table on primary..."
116+ $PRIMARY -qc 'DROP TABLE IF EXISTS dbdeployer_test_replication'
117+ $PRIMARY -qc 'CREATE TABLE dbdeployer_test_replication (i INT NOT NULL PRIMARY KEY, msg VARCHAR(50), d DATE, t TIME, dt TIMESTAMP)'
118+ for N in $(seq -f '%%02.0f' 1 20); do
119+ $PRIMARY -qc "INSERT INTO dbdeployer_test_replication VALUES ($N, 'test sandbox $N', '${YYYY_MM}-$N', '11:23:$N', '${YYYY_MM}-01 12:34:$N')"
120+ done
121+
122+ PRIMARY_RECS=$($PRIMARY -Atqc 'SELECT count(*) FROM dbdeployer_test_replication')
123+ PRIMARY_LSN=$($PRIMARY -Atqc 'SELECT pg_current_wal_lsn()')
124+ echo "# Primary: rows=$PRIMARY_RECS LSN=$PRIMARY_LSN"
125+
126+ # Allow a brief window for streaming replication to catch up
127+ sleep 1
128+
129+ ` , opts .SandboxDir ))
130+
131+ for n := 1 ; n <= numReplicas ; n ++ {
132+ b .WriteString (fmt .Sprintf (`echo "# Testing replica %d"
133+ if [ -x ./replica%d/use ]; then
134+ REPLICA_LSN=$(./replica%d/use -Atqc 'SELECT pg_last_wal_replay_lsn()')
135+ REPLICA_RECS=$(./replica%d/use -Atqc 'SELECT count(*) FROM dbdeployer_test_replication')
136+ REPLICA_IS_REPLICA=$(./replica%d/use -Atqc 'SELECT pg_is_in_recovery()')
137+ ok_equal "$REPLICA_IS_REPLICA" "t" "replica %d is in recovery mode"
138+ ok_equal "$REPLICA_LSN" "$PRIMARY_LSN" "replica %d LSN matches primary"
139+ ok_equal "$REPLICA_RECS" "$PRIMARY_RECS" "replica %d row count matches primary"
140+ else
141+ echo "not ok - replica %d/use not found"
142+ FAILED=$((FAILED+1))
143+ fi
144+
145+ ` , n , n , n , n , n , n , n , n , n ))
146+ }
147+
148+ b .WriteString ("test_summary\n " )
149+ return b .String ()
150+ }
0 commit comments