3 # Test suite for pg_scribe --start command
6 # - Creates temporary test databases
7 # - Tests various --start scenarios
8 # - Verifies SQL capture (DML + DDL)
9 # - Tests signal handling
10 # - Cleans up all resources
15 # Colors for test output
20 NC='\033[0m' # No Color
23 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
24 PG_SCRIBE="$SCRIPT_DIR/scripts/pg_scribe"
25 TEST_DIR="/tmp/pg_scribe_test_start_$$"
26 TEST_DB_PREFIX="pg_scribe_test_start_$$"
27 PGUSER="${PGUSER:-postgres}"
35 DATABASES_TO_CLEANUP=()
43 echo -e "${BLUE}TEST:${NC} $*"
47 echo -e "${GREEN}PASS:${NC} $*"
52 echo -e "${RED}FAIL:${NC} $*"
57 echo -e "${YELLOW}INFO:${NC} $*"
67 psql -U "$PGUSER" -d "$dbname" -tAq "$@"
73 run_psql "$dbname" -c "$query" 2>/dev/null || true
78 log_info "Creating test database: $dbname"
81 psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
84 psql -U "$PGUSER" -d postgres -c "CREATE DATABASE $dbname;" &>/dev/null
86 DATABASES_TO_CLEANUP+=("$dbname")
89 # shellcheck disable=SC2317 # Function called from cleanup trap handler
92 log_info "Dropping test database: $dbname"
94 # Terminate connections
95 psql -U "$PGUSER" -d postgres -c "
96 SELECT pg_terminate_backend(pid)
98 WHERE datname = '$dbname' AND pid <> pg_backend_pid();
102 psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
105 # shellcheck disable=SC2317 # Function called from cleanup trap handler
106 drop_replication_slot() {
109 log_info "Dropping replication slot: $slot"
111 # Check if slot exists
113 exists=$(query_db "$dbname" "
114 SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
117 if [[ -n "$exists" ]]; then
119 query_db "$dbname" "SELECT pg_drop_replication_slot('$slot');" || true
123 check_slot_exists() {
127 exists=$(query_db "$dbname" "
128 SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
133 create_table_with_pk() {
137 CREATE TABLE $table (
138 id SERIAL PRIMARY KEY,
140 created_at TIMESTAMP DEFAULT now()
145 initialize_backup_system() {
148 local backup_dir="$3"
150 # Create backup directory
151 mkdir -p "$backup_dir"
154 "$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null
161 test_start_without_init() {
163 log_test "Start without initialization (should fail)"
165 local dbname="${TEST_DB_PREFIX}_noinit"
166 local slot="test_slot_noinit"
167 local backup_dir="$TEST_DIR/noinit"
169 # Setup - create db and backup dir but DON'T initialize
170 create_test_db "$dbname"
171 create_table_with_pk "$dbname" "users"
172 mkdir -p "$backup_dir"
174 # Try to start - should fail with exit code 4 (no chains found)
176 "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" &>/dev/null || exit_code=$?
178 if [[ $exit_code -eq 4 ]]; then
179 log_pass "Correctly failed with backup error (no chains)"
182 log_fail "Expected exit code 4, got $exit_code"
187 test_start_basic_streaming() {
189 log_test "Basic streaming with DML capture"
191 local dbname="${TEST_DB_PREFIX}_basic"
192 local slot="test_slot_basic"
193 local backup_dir="$TEST_DIR/basic"
196 create_test_db "$dbname"
197 create_table_with_pk "$dbname" "users"
198 initialize_backup_system "$dbname" "$slot" "$backup_dir"
200 # Start streaming in background
201 "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
202 local pg_scribe_pid=$!
203 PIDS_TO_CLEANUP+=("$pg_scribe_pid")
205 # Give it a moment to start
209 query_db "$dbname" "INSERT INTO users (name) VALUES ('Alice');"
210 query_db "$dbname" "INSERT INTO users (name) VALUES ('Bob');"
211 query_db "$dbname" "UPDATE users SET name = 'Alice Smith' WHERE name = 'Alice';"
212 query_db "$dbname" "DELETE FROM users WHERE name = 'Bob';"
214 # Give it time to flush (status-interval=1, fsync-interval=1)
218 kill -INT "$pg_scribe_pid" 2>/dev/null || true
221 # Force kill if still running
222 if kill -0 "$pg_scribe_pid" 2>/dev/null; then
223 kill -9 "$pg_scribe_pid" 2>/dev/null || true
225 wait "$pg_scribe_pid" 2>/dev/null || true
227 # Find active.sql in chain directory
228 local chain_dirs=("$backup_dir"/chain-*)
229 local output_file="${chain_dirs[0]}/active.sql"
231 # Verify output file exists
232 if [[ ! -f "$output_file" ]]; then
233 log_fail "Output file not created: $output_file"
238 if ! grep -q "INSERT INTO public.users" "$output_file"; then
239 log_fail "INSERT not captured"
243 if ! grep -q "UPDATE public.users" "$output_file"; then
244 log_fail "UPDATE not captured"
248 if ! grep -q "DELETE FROM public.users" "$output_file"; then
249 log_fail "DELETE not captured"
253 # Verify transaction boundaries
254 if ! grep -q "BEGIN" "$output_file"; then
255 log_fail "BEGIN not captured"
259 if ! grep -q "COMMIT" "$output_file"; then
260 log_fail "COMMIT not captured"
264 log_pass "DML captured successfully"
268 test_start_ddl_capture() {
270 log_test "DDL capture via event triggers"
272 local dbname="${TEST_DB_PREFIX}_ddl"
273 local slot="test_slot_ddl"
274 local backup_dir="$TEST_DIR/ddl"
277 create_test_db "$dbname"
278 create_table_with_pk "$dbname" "users"
279 initialize_backup_system "$dbname" "$slot" "$backup_dir"
281 # Start streaming in background
282 "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
283 local pg_scribe_pid=$!
284 PIDS_TO_CLEANUP+=("$pg_scribe_pid")
286 # Give it a moment to start
290 query_db "$dbname" "CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT);"
291 query_db "$dbname" "ALTER TABLE products ADD COLUMN price NUMERIC(10,2);"
292 query_db "$dbname" "DROP TABLE products;"
294 # Give it time to flush
298 kill -INT "$pg_scribe_pid" 2>/dev/null || true
301 # Force kill if still running
302 if kill -0 "$pg_scribe_pid" 2>/dev/null; then
303 kill -9 "$pg_scribe_pid" 2>/dev/null || true
305 wait "$pg_scribe_pid" 2>/dev/null || true
307 # Find active.sql in chain directory
308 local chain_dirs=("$backup_dir"/chain-*)
309 local output_file="${chain_dirs[0]}/active.sql"
311 # Verify DDL captured
312 if ! grep -qi "CREATE TABLE products" "$output_file"; then
313 log_fail "CREATE TABLE not captured"
317 if ! grep -qi "ALTER TABLE products" "$output_file"; then
318 log_fail "ALTER TABLE not captured"
322 if ! grep -qi "DROP TABLE products" "$output_file"; then
323 log_fail "DROP TABLE not captured"
327 log_pass "DDL captured successfully"
331 test_start_truncate_capture() {
333 log_test "TRUNCATE capture"
335 local dbname="${TEST_DB_PREFIX}_truncate"
336 local slot="test_slot_truncate"
337 local backup_dir="$TEST_DIR/truncate"
340 create_test_db "$dbname"
341 create_table_with_pk "$dbname" "users"
342 initialize_backup_system "$dbname" "$slot" "$backup_dir"
344 # Start streaming in background
345 "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
346 local pg_scribe_pid=$!
347 PIDS_TO_CLEANUP+=("$pg_scribe_pid")
349 # Give it a moment to start
352 # Insert data and truncate
353 query_db "$dbname" "INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Charlie');"
354 query_db "$dbname" "TRUNCATE users;"
356 # Give it time to flush
360 kill -INT "$pg_scribe_pid" 2>/dev/null || true
363 # Force kill if still running
364 if kill -0 "$pg_scribe_pid" 2>/dev/null; then
365 kill -9 "$pg_scribe_pid" 2>/dev/null || true
367 wait "$pg_scribe_pid" 2>/dev/null || true
369 # Find active.sql in chain directory
370 local chain_dirs=("$backup_dir"/chain-*)
371 local output_file="${chain_dirs[0]}/active.sql"
373 # Verify TRUNCATE captured
374 if ! grep -qi "TRUNCATE.*users" "$output_file"; then
375 log_fail "TRUNCATE not captured"
379 log_pass "TRUNCATE captured successfully"
383 test_start_signal_handling() {
385 log_test "Signal handling (SIGTERM)"
387 local dbname="${TEST_DB_PREFIX}_signal"
388 local slot="test_slot_signal"
389 local backup_dir="$TEST_DIR/signal"
392 create_test_db "$dbname"
393 create_table_with_pk "$dbname" "users"
394 initialize_backup_system "$dbname" "$slot" "$backup_dir"
396 # Start streaming in background
397 "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
398 local pg_scribe_pid=$!
399 PIDS_TO_CLEANUP+=("$pg_scribe_pid")
401 # Give it a moment to start
405 query_db "$dbname" "INSERT INTO users (name) VALUES ('Test');"
407 # Give it time to flush
411 kill -TERM "$pg_scribe_pid" 2>/dev/null || true
413 # Wait for graceful shutdown (with timeout)
416 while kill -0 "$pg_scribe_pid" 2>/dev/null && [[ $count -lt $timeout ]]; do
421 # Check if process stopped
422 if kill -0 "$pg_scribe_pid" 2>/dev/null; then
423 log_fail "Process did not stop after SIGTERM"
424 kill -9 "$pg_scribe_pid" 2>/dev/null || true
428 # Find active.sql in chain directory
429 local chain_dirs=("$backup_dir"/chain-*)
430 local output_file="${chain_dirs[0]}/active.sql"
432 # Verify output file was created and flushed
433 if [[ ! -f "$output_file" ]]; then
434 log_fail "Output file not created"
438 if ! grep -q "INSERT INTO public.users" "$output_file"; then
439 log_fail "Data not flushed before shutdown"
443 log_pass "SIGTERM handled gracefully"
447 test_start_interleaved_ddl_dml() {
449 log_test "DDL and DML interleaving (chronological order)"
451 local dbname="${TEST_DB_PREFIX}_interleaved"
452 local slot="test_slot_interleaved"
453 local backup_dir="$TEST_DIR/interleaved"
456 create_test_db "$dbname"
457 create_table_with_pk "$dbname" "users"
458 initialize_backup_system "$dbname" "$slot" "$backup_dir"
460 # Start streaming in background
461 "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
462 local pg_scribe_pid=$!
463 PIDS_TO_CLEANUP+=("$pg_scribe_pid")
465 # Give it a moment to start
468 # Make interleaved changes
469 query_db "$dbname" "INSERT INTO users (name) VALUES ('Alice');"
470 query_db "$dbname" "ALTER TABLE users ADD COLUMN email TEXT;"
471 query_db "$dbname" "UPDATE users SET email = 'alice@example.com' WHERE name = 'Alice';"
472 query_db "$dbname" "ALTER TABLE users DROP COLUMN created_at;"
473 query_db "$dbname" "INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');"
475 # Give it time to flush
479 kill -INT "$pg_scribe_pid" 2>/dev/null || true
482 # Force kill if still running
483 if kill -0 "$pg_scribe_pid" 2>/dev/null; then
484 kill -9 "$pg_scribe_pid" 2>/dev/null || true
486 wait "$pg_scribe_pid" 2>/dev/null || true
488 # Find active.sql in chain directory
489 local chain_dirs=("$backup_dir"/chain-*)
490 local output_file="${chain_dirs[0]}/active.sql"
492 # Verify all operations captured
493 if ! grep -q "INSERT INTO public.users (id, name" "$output_file"; then
494 log_fail "First INSERT not captured"
498 if ! grep -qi "ALTER TABLE.*ADD COLUMN email" "$output_file"; then
499 log_fail "ALTER TABLE ADD COLUMN not captured"
503 if ! grep -q "UPDATE public.users.*email" "$output_file"; then
504 log_fail "UPDATE with new column not captured"
508 if ! grep -qi "ALTER TABLE.*DROP COLUMN" "$output_file"; then
509 log_fail "ALTER TABLE DROP COLUMN not captured"
513 # Verify chronological order by checking line numbers
515 insert1_line=$(grep -n "INSERT INTO public.users (id, name" "$output_file" | head -1 | cut -d: -f1)
517 alter_add_line=$(grep -ni "ALTER TABLE.*ADD COLUMN email" "$output_file" | cut -d: -f1)
519 update_line=$(grep -n "UPDATE public.users.*email" "$output_file" | cut -d: -f1)
521 if [[ "$insert1_line" -gt "$alter_add_line" ]]; then
522 log_fail "DDL/DML not in chronological order (INSERT after ALTER)"
526 if [[ "$alter_add_line" -gt "$update_line" ]]; then
527 log_fail "DDL/DML not in chronological order (ALTER after UPDATE)"
531 log_pass "DDL/DML interleaved in correct chronological order"
535 test_rotate_diff_basic() {
537 log_test "--rotate-diff basic functionality"
539 local dbname="${TEST_DB_PREFIX}_rotate_basic"
540 local slot="test_slot_rotate_basic"
541 local backup_dir="$TEST_DIR/rotate_basic"
544 create_test_db "$dbname"
545 create_table_with_pk "$dbname" "users"
546 initialize_backup_system "$dbname" "$slot" "$backup_dir"
548 # Start streaming in background
549 "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
550 local pg_scribe_pid=$!
551 PIDS_TO_CLEANUP+=("$pg_scribe_pid")
553 # Give it a moment to start
556 # Make changes before rotation
557 query_db "$dbname" "INSERT INTO users (name) VALUES ('Before Rotation');"
559 # Give it time to flush
562 # Find chain directory
563 local chain_dirs=("$backup_dir"/chain-*)
564 local chain_dir="${chain_dirs[0]}"
566 # Verify active.sql exists
567 if [[ ! -f "$chain_dir/active.sql" ]]; then
568 log_fail "active.sql not found before rotation"
573 "$PG_SCRIBE" --rotate-diff -f "$backup_dir" &>/dev/null
575 # Give pg_recvlogical time to reopen the file
578 # Verify sealed differential was created
579 local diff_files=("$chain_dir"/diff-*.sql)
580 if [[ ! -f "${diff_files[0]}" ]]; then
581 log_fail "Sealed differential not created"
585 # Verify new active.sql was created
586 if [[ ! -f "$chain_dir/active.sql" ]]; then
587 log_fail "New active.sql not created after rotation"
591 # Make changes after rotation
592 query_db "$dbname" "INSERT INTO users (name) VALUES ('After Rotation');"
594 # Give it time to flush
598 kill -INT "$pg_scribe_pid" 2>/dev/null || true
601 # Force kill if still running
602 if kill -0 "$pg_scribe_pid" 2>/dev/null; then
603 kill -9 "$pg_scribe_pid" 2>/dev/null || true
605 wait "$pg_scribe_pid" 2>/dev/null || true
607 # Verify sealed differential has "Before Rotation" but not "After Rotation"
608 if ! grep -q "Before Rotation" "${diff_files[0]}"; then
609 log_fail "Sealed differential missing data before rotation"
613 if grep -q "After Rotation" "${diff_files[0]}"; then
614 log_fail "Sealed differential should not contain data after rotation"
618 # Verify new active.sql has "After Rotation" but not "Before Rotation"
619 if ! grep -q "After Rotation" "$chain_dir/active.sql"; then
620 log_fail "New active.sql missing data after rotation"
624 if grep -q "Before Rotation" "$chain_dir/active.sql"; then
625 log_fail "New active.sql should not contain data before rotation"
629 log_pass "--rotate-diff works correctly"
633 test_rotate_diff_no_active_process() {
635 log_test "--rotate-diff without active process (should fail)"
637 local dbname="${TEST_DB_PREFIX}_rotate_noactive"
638 local slot="test_slot_rotate_noactive"
639 local backup_dir="$TEST_DIR/rotate_noactive"
641 # Setup - initialize but don't start streaming
642 create_test_db "$dbname"
643 create_table_with_pk "$dbname" "users"
644 initialize_backup_system "$dbname" "$slot" "$backup_dir"
646 # Try to rotate without active process - should fail
648 "$PG_SCRIBE" --rotate-diff -f "$backup_dir" &>/dev/null || exit_code=$?
650 if [[ $exit_code -ne 0 ]]; then
651 log_pass "Correctly failed when no active process"
654 log_fail "Should have failed with no active process"
659 test_rotate_diff_stale_pidfile() {
661 log_test "--rotate-diff with stale pidfile (should fail)"
663 local dbname="${TEST_DB_PREFIX}_rotate_stale"
664 local slot="test_slot_rotate_stale"
665 local backup_dir="$TEST_DIR/rotate_stale"
668 create_test_db "$dbname"
669 create_table_with_pk "$dbname" "users"
670 initialize_backup_system "$dbname" "$slot" "$backup_dir"
672 # Create a stale pidfile (process doesn't exist)
673 echo "99999" > "$backup_dir/.pg_scribe.pid"
675 # Try to rotate - should fail with stale pidfile
677 "$PG_SCRIBE" --rotate-diff -f "$backup_dir" &>/dev/null || exit_code=$?
679 if [[ $exit_code -ne 0 ]]; then
680 log_pass "Correctly failed with stale pidfile"
683 log_fail "Should have failed with stale pidfile"
688 test_rotate_diff_multiple() {
690 log_test "--rotate-diff multiple times"
692 local dbname="${TEST_DB_PREFIX}_rotate_multiple"
693 local slot="test_slot_rotate_multiple"
694 local backup_dir="$TEST_DIR/rotate_multiple"
697 create_test_db "$dbname"
698 create_table_with_pk "$dbname" "users"
699 initialize_backup_system "$dbname" "$slot" "$backup_dir"
701 # Start streaming in background
702 "$PG_SCRIBE" --start -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
703 local pg_scribe_pid=$!
704 PIDS_TO_CLEANUP+=("$pg_scribe_pid")
706 # Give it a moment to start
709 local chain_dirs=("$backup_dir"/chain-*)
710 local chain_dir="${chain_dirs[0]}"
713 query_db "$dbname" "INSERT INTO users (name) VALUES ('First');"
715 "$PG_SCRIBE" --rotate-diff -f "$backup_dir" &>/dev/null
719 query_db "$dbname" "INSERT INTO users (name) VALUES ('Second');"
721 "$PG_SCRIBE" --rotate-diff -f "$backup_dir" &>/dev/null
725 query_db "$dbname" "INSERT INTO users (name) VALUES ('Third');"
727 "$PG_SCRIBE" --rotate-diff -f "$backup_dir" &>/dev/null
731 kill -INT "$pg_scribe_pid" 2>/dev/null || true
734 # Force kill if still running
735 if kill -0 "$pg_scribe_pid" 2>/dev/null; then
736 kill -9 "$pg_scribe_pid" 2>/dev/null || true
738 wait "$pg_scribe_pid" 2>/dev/null || true
740 # Verify we have 3 sealed differentials
742 diff_count=$(ls -1 "$chain_dir"/diff-*.sql 2>/dev/null | wc -l)
744 if [[ $diff_count -ne 3 ]]; then
745 log_fail "Expected 3 differentials, found $diff_count"
749 # Verify each differential has the correct data
750 local diff_files=("$chain_dir"/diff-*.sql)
752 if ! grep -q "First" "${diff_files[0]}"; then
753 log_fail "First differential missing expected data"
757 if ! grep -q "Second" "${diff_files[1]}"; then
758 log_fail "Second differential missing expected data"
762 if ! grep -q "Third" "${diff_files[2]}"; then
763 log_fail "Third differential missing expected data"
767 # Verify active.sql exists (from streaming after last rotation)
768 if [[ ! -f "$chain_dir/active.sql" ]]; then
769 log_fail "active.sql not found after multiple rotations"
773 log_pass "Multiple rotations work correctly"
781 # shellcheck disable=SC2317 # Function called via trap handler
783 log_info "Cleaning up test resources..."
785 # Kill any running pg_scribe processes
786 for pid in "${PIDS_TO_CLEANUP[@]}"; do
787 if kill -0 "$pid" 2>/dev/null; then
788 log_info "Stopping pg_scribe process $pid"
789 # Try graceful shutdown first (allows signal forwarding to child processes)
790 kill -TERM "$pid" 2>/dev/null || true
792 # Wait briefly for graceful shutdown
795 while kill -0 "$pid" 2>/dev/null && [[ $count -lt $timeout ]]; do
800 # Force kill if still running
801 if kill -0 "$pid" 2>/dev/null; then
802 log_info "Force killing pg_scribe process $pid"
803 kill -9 "$pid" 2>/dev/null || true
808 # Wait for child pg_recvlogical processes to fully terminate
809 # (They may take a moment to shut down after parent terminates)
812 # Drop replication slots
813 for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
814 for slot in test_slot_noinit test_slot_basic test_slot_ddl test_slot_truncate test_slot_signal test_slot_interleaved test_slot_rotate_basic test_slot_rotate_noactive test_slot_rotate_stale test_slot_rotate_multiple; do
815 drop_replication_slot "$dbname" "$slot" 2>/dev/null || true
820 for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
821 drop_test_db "$dbname"
824 # Remove test directory
825 if [[ -d "$TEST_DIR" ]]; then
829 log_info "Cleanup complete"
837 echo "========================================"
838 echo "pg_scribe --start Test Suite"
839 echo "========================================"
842 # Verify pg_scribe exists
843 if [[ ! -x "$PG_SCRIBE" ]]; then
844 echo "ERROR: pg_scribe not found or not executable: $PG_SCRIBE"
848 # Verify PostgreSQL is running
849 if ! psql -U "$PGUSER" -d postgres -c "SELECT 1;" &>/dev/null; then
850 echo "ERROR: Cannot connect to PostgreSQL"
854 # Verify wal_level is logical
856 wal_level=$(psql -U "$PGUSER" -d postgres -tAq -c "SHOW wal_level;")
857 if [[ "$wal_level" != "logical" ]]; then
858 echo "ERROR: wal_level must be 'logical', currently: $wal_level"
859 echo "Update ~/.pgenv/pgsql/data/postgresql.conf and restart PostgreSQL"
863 # Verify wal2sql extension is available
864 if ! pg_config --pkglibdir &>/dev/null; then
865 echo "ERROR: pg_config not found"
870 wal2sql_path="$(pg_config --pkglibdir)/wal2sql.so"
871 if [[ ! -f "$wal2sql_path" ]]; then
872 echo "ERROR: wal2sql.so not found at $wal2sql_path"
873 echo "Build and install wal2sql: cd wal2sql && make && make install"
877 # Create test directory
880 # Set up cleanup trap
881 trap cleanup EXIT INT TERM
883 echo "Running tests..."
886 # Run all tests (use || true to prevent set -e from exiting)
887 test_start_without_init || true
888 test_start_basic_streaming || true
889 test_start_ddl_capture || true
890 test_start_truncate_capture || true
891 test_start_signal_handling || true
892 test_start_interleaved_ddl_dml || true
893 test_rotate_diff_basic || true
894 test_rotate_diff_no_active_process || true
895 test_rotate_diff_stale_pidfile || true
896 test_rotate_diff_multiple || true
900 echo "========================================"
902 echo "========================================"
903 echo "Tests run: $TESTS_RUN"
904 echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
905 echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
908 if [[ $TESTS_FAILED -eq 0 ]]; then
909 echo -e "${GREEN}All tests passed!${NC}"
912 echo -e "${RED}Some tests failed!${NC}"