]> begriffs open source - pg_scribe/blob - tests/test_status.sh
Add --status, fix --start
[pg_scribe] / tests / test_status.sh
1 #!/usr/bin/env bash
2 #
3 # Test suite for pg_scribe --status command
4 #
5 # This test suite:
6 # - Creates temporary test databases
7 # - Tests various --status scenarios
8 # - Verifies status reporting and health checks
9 # - Cleans up all resources
10 #
11
12 set -euo pipefail
13
14 # Colors for test output
15 RED='\033[0;31m'
16 GREEN='\033[0;32m'
17 YELLOW='\033[0;33m'
18 BLUE='\033[0;34m'
19 NC='\033[0m' # No Color
20
21 # Test configuration
22 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23 PG_SCRIBE="$SCRIPT_DIR/scripts/pg_scribe"
24 TEST_DIR="/tmp/pg_scribe_test_status_$$"
25 TEST_DB_PREFIX="pg_scribe_test_status_$$"
26 PGUSER="${PGUSER:-postgres}"
27
28 # Test counters
29 TESTS_RUN=0
30 TESTS_PASSED=0
31 TESTS_FAILED=0
32
33 # Cleanup tracking
34 DATABASES_TO_CLEANUP=()
35 PIDS_TO_CLEANUP=()
36
37 #
38 # Logging functions
39 #
40
41 log_test() {
42     echo -e "${BLUE}TEST:${NC} $*"
43 }
44
45 log_pass() {
46     echo -e "${GREEN}PASS:${NC} $*"
47     ((TESTS_PASSED++))
48 }
49
50 log_fail() {
51     echo -e "${RED}FAIL:${NC} $*"
52     ((TESTS_FAILED++))
53 }
54
55 log_info() {
56     echo -e "${YELLOW}INFO:${NC} $*"
57 }
58
59 #
60 # Helper functions
61 #
62
63 run_psql() {
64     local dbname="$1"
65     shift
66     psql -U "$PGUSER" -d "$dbname" -tAq "$@"
67 }
68
69 query_db() {
70     local dbname="$1"
71     local query="$2"
72     run_psql "$dbname" -c "$query" 2>/dev/null || true
73 }
74
75 create_test_db() {
76     local dbname="$1"
77     log_info "Creating test database: $dbname"
78
79     # Drop if exists
80     psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
81
82     # Create database
83     psql -U "$PGUSER" -d postgres -c "CREATE DATABASE $dbname;" &>/dev/null
84
85     DATABASES_TO_CLEANUP+=("$dbname")
86 }
87
88 # shellcheck disable=SC2317  # Function called from cleanup trap handler
89 drop_test_db() {
90     local dbname="$1"
91     log_info "Dropping test database: $dbname"
92
93     # Terminate connections
94     psql -U "$PGUSER" -d postgres -c "
95         SELECT pg_terminate_backend(pid)
96         FROM pg_stat_activity
97         WHERE datname = '$dbname' AND pid <> pg_backend_pid();
98     " &>/dev/null || true
99
100     # Drop database
101     psql -U "$PGUSER" -d postgres -c "DROP DATABASE IF EXISTS $dbname;" &>/dev/null || true
102 }
103
104 # shellcheck disable=SC2317  # Function called from cleanup trap handler
105 drop_replication_slot() {
106     local dbname="$1"
107     local slot="$2"
108     log_info "Dropping replication slot: $slot"
109
110     # Check if slot exists
111     local exists
112     exists=$(query_db "$dbname" "
113         SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
114     ")
115
116     if [[ -n "$exists" ]]; then
117         # Drop slot
118         query_db "$dbname" "SELECT pg_drop_replication_slot('$slot');" || true
119     fi
120 }
121
122 check_slot_exists() {
123     local dbname="$1"
124     local slot="$2"
125     local exists
126     exists=$(query_db "$dbname" "
127         SELECT 1 FROM pg_replication_slots WHERE slot_name = '$slot';
128     ")
129     [[ -n "$exists" ]]
130 }
131
132 create_table_with_pk() {
133     local dbname="$1"
134     local table="$2"
135     query_db "$dbname" "
136         CREATE TABLE $table (
137             id SERIAL PRIMARY KEY,
138             name TEXT,
139             created_at TIMESTAMP DEFAULT now()
140         );
141     "
142 }
143
144 initialize_backup_system() {
145     local dbname="$1"
146     local slot="$2"
147     local backup_dir="$3"
148
149     # Create backup directory
150     mkdir -p "$backup_dir"
151
152     # Initialize (exit on failure - this is critical for tests)
153     local init_output
154     init_output=$("$PG_SCRIBE" --init -d "$dbname" -f "$backup_dir" -S "$slot" -U "$PGUSER" 2>&1)
155     local init_exit=$?
156
157     if [[ $init_exit -ne 0 && $init_exit -ne 10 ]]; then
158         log_info "ERROR: Failed to initialize backup system for $dbname (exit=$init_exit)"
159         echo "$init_output" >&2
160         return 1
161     fi
162     return 0
163 }
164
165 #
166 # Test cases
167 #
168
169 test_status_without_slot() {
170     ((TESTS_RUN++))
171     log_test "Status check when slot doesn't exist (should fail)"
172
173     local dbname="${TEST_DB_PREFIX}_noslot"
174     local slot="test_slot_noslot"
175
176     # Setup - create db but DON'T initialize
177     create_test_db "$dbname"
178     create_table_with_pk "$dbname" "users"
179
180     # Try to check status - should fail with exit code 3 (slot error)
181     local exit_code=0
182     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>/dev/null || exit_code=$?
183
184     if [[ $exit_code -eq 3 ]]; then
185         log_pass "Correctly failed with slot error"
186         return 0
187     else
188         log_fail "Expected exit code 3, got $exit_code"
189         return 1
190     fi
191 }
192
193 test_status_basic_healthy() {
194     ((TESTS_RUN++))
195     log_test "Basic status check (without backup directory)"
196
197     local dbname="${TEST_DB_PREFIX}_healthy"
198     local slot="test_slot_healthy"
199     local backup_dir="$TEST_DIR/healthy"
200
201     # Setup
202     create_test_db "$dbname"
203     create_table_with_pk "$dbname" "users"
204     initialize_backup_system "$dbname" "$slot" "$backup_dir"
205
206     # Check status WITHOUT backup directory - should succeed
207     # (warnings about inactive slot, but exit code 10 is acceptable)
208     local output_file="$TEST_DIR/healthy_output.txt"
209     local exit_code=0
210     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$output_file" || exit_code=$?
211
212     # Exit code 0 or 10 (warning about inactive slot) are acceptable
213     if [[ $exit_code -ne 0 && $exit_code -ne 10 ]]; then
214         log_fail "Status check failed with unexpected exit code $exit_code"
215         return 1
216     fi
217
218     # Verify output contains expected information
219     if ! grep -q "Slot Name:" "$output_file"; then
220         log_fail "Output missing slot name"
221         return 1
222     fi
223
224     if ! grep -q "Active:" "$output_file"; then
225         log_fail "Output missing active status"
226         return 1
227     fi
228
229     if ! grep -q "Restart Lag:" "$output_file"; then
230         log_fail "Output missing lag information"
231         return 1
232     fi
233
234     log_pass "Status check successful"
235     return 0
236 }
237
238 test_status_with_backup_directory() {
239     ((TESTS_RUN++))
240     log_test "Status check with backup directory analysis"
241
242     local dbname="${TEST_DB_PREFIX}_with_dir"
243     local slot="test_slot_with_dir"
244     local backup_dir="$TEST_DIR/with_dir"
245
246     # Setup
247     create_test_db "$dbname"
248     create_table_with_pk "$dbname" "users"
249     initialize_backup_system "$dbname" "$slot" "$backup_dir"
250
251     # Check status with backup directory
252     local output_file="$TEST_DIR/with_dir_output.txt"
253     local exit_code=0
254     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$output_file" || exit_code=$?
255
256     # Exit code 0 or 10 (warning) are both acceptable here
257     # (warning because no incremental backups exist yet)
258     if [[ $exit_code -ne 0 && $exit_code -ne 10 ]]; then
259         log_fail "Status check failed with unexpected exit code $exit_code"
260         return 1
261     fi
262
263     # Verify backup directory analysis appears in output
264     if ! grep -q "Backup Directory Analysis" "$output_file"; then
265         log_fail "Output missing backup directory analysis"
266         return 1
267     fi
268
269     if ! grep -q "Base Backups:" "$output_file"; then
270         log_fail "Output missing base backup count"
271         return 1
272     fi
273
274     if ! grep -q "Metadata File:" "$output_file"; then
275         log_fail "Output missing metadata file status"
276         return 1
277     fi
278
279     log_pass "Backup directory analysis works correctly"
280     return 0
281 }
282
283 test_status_inactive_slot() {
284     ((TESTS_RUN++))
285     log_test "Status check with inactive slot (should warn)"
286
287     local dbname="${TEST_DB_PREFIX}_inactive"
288     local slot="test_slot_inactive"
289     local backup_dir="$TEST_DIR/inactive"
290
291     # Setup
292     create_test_db "$dbname"
293     create_table_with_pk "$dbname" "users"
294     initialize_backup_system "$dbname" "$slot" "$backup_dir"
295
296     # Slot exists but is inactive (no pg_recvlogical running)
297     # Check status - should succeed but with warning
298     local output_file="$TEST_DIR/inactive_output.txt"
299     local exit_code=0
300     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$output_file" || exit_code=$?
301
302     # Should exit with warning code (10)
303     if [[ $exit_code -ne 10 ]]; then
304         log_fail "Expected exit code 10 (warning), got $exit_code"
305         return 1
306     fi
307
308     # Should mention inactive status
309     if ! grep -qi "not active" "$output_file"; then
310         log_fail "Output should mention inactive slot"
311         return 1
312     fi
313
314     log_pass "Inactive slot warning works correctly"
315     return 0
316 }
317
318 test_status_with_active_streaming() {
319     ((TESTS_RUN++))
320     log_test "Status check with active streaming"
321
322     local dbname="${TEST_DB_PREFIX}_streaming"
323     local slot="test_slot_streaming"
324     local backup_dir="$TEST_DIR/streaming"
325     local output_file="$TEST_DIR/streaming_incremental.sql"
326
327     # Setup
328     create_test_db "$dbname"
329     create_table_with_pk "$dbname" "users"
330     initialize_backup_system "$dbname" "$slot" "$backup_dir"
331
332     # Start streaming in background
333     "$PG_SCRIBE" --start -d "$dbname" -f "$output_file" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
334     local pg_scribe_pid=$!
335     PIDS_TO_CLEANUP+=("$pg_scribe_pid")
336
337     # Give it a moment to activate
338     sleep 2
339
340     # Check status - slot should now be active
341     local status_output="$TEST_DIR/streaming_status.txt"
342     local exit_code=0
343     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$status_output" || exit_code=$?
344
345     # Stop streaming
346     kill -INT "$pg_scribe_pid" 2>/dev/null || true
347     sleep 1
348
349     # Force kill if still running
350     if kill -0 "$pg_scribe_pid" 2>/dev/null; then
351         kill -9 "$pg_scribe_pid" 2>/dev/null || true
352     fi
353     wait "$pg_scribe_pid" 2>/dev/null || true
354
355     # Verify status showed active slot
356     if ! grep -q "Active:.*Yes" "$status_output"; then
357         log_fail "Slot should be shown as active"
358         return 1
359     fi
360
361     log_pass "Active slot detected correctly"
362     return 0
363 }
364
365 test_status_with_incremental_backups() {
366     ((TESTS_RUN++))
367     log_test "Status check with incremental backup files"
368
369     local dbname="${TEST_DB_PREFIX}_with_inc"
370     local slot="test_slot_with_inc"
371     local backup_dir="$TEST_DIR/with_inc"
372     local output_file="$backup_dir/incremental.sql"
373
374     # Setup
375     create_test_db "$dbname"
376     create_table_with_pk "$dbname" "users"
377     initialize_backup_system "$dbname" "$slot" "$backup_dir"
378
379     # Start streaming to create incremental backup
380     "$PG_SCRIBE" --start -d "$dbname" -f "$output_file" -S "$slot" -U "$PGUSER" -s 1 -F 1 &>/dev/null &
381     local pg_scribe_pid=$!
382     PIDS_TO_CLEANUP+=("$pg_scribe_pid")
383
384     # Give it a moment to start
385     sleep 1
386
387     # Make some changes to generate incremental data
388     query_db "$dbname" "INSERT INTO users (name) VALUES ('Test User');"
389
390     # Give it time to flush
391     sleep 2
392
393     # Stop streaming
394     kill -INT "$pg_scribe_pid" 2>/dev/null || true
395     sleep 1
396
397     # Force kill if still running
398     if kill -0 "$pg_scribe_pid" 2>/dev/null; then
399         kill -9 "$pg_scribe_pid" 2>/dev/null || true
400     fi
401     wait "$pg_scribe_pid" 2>/dev/null || true
402
403     # Check status with backup directory
404     local status_output="$TEST_DIR/with_inc_status.txt"
405     local exit_code=0
406     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$status_output" || exit_code=$?
407
408     # Should succeed (exit code 0) since we have backups now
409     if [[ $exit_code -ne 0 ]]; then
410         log_fail "Status check failed with exit code $exit_code"
411         return 1
412     fi
413
414     # Verify incremental files are reported
415     if ! grep -q "Incremental Files:" "$status_output"; then
416         log_fail "Output missing incremental file count"
417         return 1
418     fi
419
420     # Should show at least 1 incremental file
421     if grep -q "Incremental Files:.*0" "$status_output"; then
422         log_fail "Should report at least 1 incremental file"
423         return 1
424     fi
425
426     log_pass "Incremental backup files reported correctly"
427     return 0
428 }
429
430 test_status_missing_metadata() {
431     ((TESTS_RUN++))
432     log_test "Status check with missing metadata file (should warn)"
433
434     local dbname="${TEST_DB_PREFIX}_no_meta"
435     local slot="test_slot_no_meta"
436     local backup_dir="$TEST_DIR/no_meta"
437
438     # Setup
439     create_test_db "$dbname"
440     create_table_with_pk "$dbname" "users"
441     initialize_backup_system "$dbname" "$slot" "$backup_dir"
442
443     # Remove metadata file
444     rm -f "$backup_dir/pg_scribe_metadata.txt"
445
446     # Check status
447     local output_file="$TEST_DIR/no_meta_output.txt"
448     local exit_code=0
449     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$output_file" || exit_code=$?
450
451     # Should exit with warning (10)
452     if [[ $exit_code -ne 10 ]]; then
453         log_fail "Expected exit code 10 (warning), got $exit_code"
454         return 1
455     fi
456
457     # Should warn about missing metadata
458     if ! grep -qi "metadata.*not found" "$output_file"; then
459         log_fail "Should warn about missing metadata file"
460         return 1
461     fi
462
463     log_pass "Missing metadata warning works correctly"
464     return 0
465 }
466
467 test_status_nonexistent_backup_dir() {
468     ((TESTS_RUN++))
469     log_test "Status check with nonexistent backup directory"
470
471     local dbname="${TEST_DB_PREFIX}_no_dir"
472     local slot="test_slot_no_dir"
473     local backup_dir="$TEST_DIR/no_dir"
474     local fake_dir="$TEST_DIR/does_not_exist"
475
476     # Setup - initialize but check status with different directory
477     create_test_db "$dbname"
478     create_table_with_pk "$dbname" "users"
479     initialize_backup_system "$dbname" "$slot" "$backup_dir"
480
481     # Check status with nonexistent directory
482     local output_file="$TEST_DIR/no_dir_output.txt"
483     local exit_code=0
484     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$fake_dir" -U "$PGUSER" &>"$output_file" || exit_code=$?
485
486     # Should exit with warning (10)
487     if [[ $exit_code -ne 10 ]]; then
488         log_fail "Expected exit code 10 (warning), got $exit_code"
489         return 1
490     fi
491
492     # Should warn about nonexistent directory
493     if ! grep -qi "does not exist" "$output_file"; then
494         log_fail "Should warn about nonexistent directory"
495         return 1
496     fi
497
498     log_pass "Nonexistent directory warning works correctly"
499     return 0
500 }
501
502 test_status_lag_display() {
503     ((TESTS_RUN++))
504     log_test "Status displays lag information correctly"
505
506     local dbname="${TEST_DB_PREFIX}_lag"
507     local slot="test_slot_lag"
508     local backup_dir="$TEST_DIR/lag"
509
510     # Setup
511     create_test_db "$dbname"
512     create_table_with_pk "$dbname" "users"
513
514     if ! initialize_backup_system "$dbname" "$slot" "$backup_dir"; then
515         log_fail "Failed to initialize backup system"
516         return 1
517     fi
518
519     # Check status
520     local output_file="$TEST_DIR/lag_output.txt"
521     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -U "$PGUSER" &>"$output_file" || true
522
523     # Verify lag is displayed - use case-insensitive grep for ANSI color codes
524     if ! grep -i "restart.*lag" "$output_file"; then
525         log_fail "Output missing restart lag"
526         cat "$output_file" >&2
527         return 1
528     fi
529
530     if ! grep -i "confirmed.*lag" "$output_file"; then
531         log_fail "Output missing confirmed lag"
532         cat "$output_file" >&2
533         return 1
534     fi
535
536     # Verify lag values are in MB
537     if ! grep "MB" "$output_file"; then
538         log_fail "Lag should be displayed in MB"
539         cat "$output_file" >&2
540         return 1
541     fi
542
543     log_pass "Lag information displayed correctly"
544     return 0
545 }
546
547 test_status_multiple_base_backups() {
548     ((TESTS_RUN++))
549     log_test "Status with multiple base backups shows latest"
550
551     local dbname="${TEST_DB_PREFIX}_multi_base"
552     local slot="test_slot_multi_base"
553     local backup_dir="$TEST_DIR/multi_base"
554
555     # Setup
556     create_test_db "$dbname"
557     create_table_with_pk "$dbname" "users"
558
559     if ! initialize_backup_system "$dbname" "$slot" "$backup_dir"; then
560         log_fail "Failed to initialize backup system"
561         return 1
562     fi
563
564     # Create additional base backup files manually
565     sleep 1
566     touch "$backup_dir/base-20250101-000000.sql"
567     sleep 1
568     touch "$backup_dir/base-20250102-000000.sql"
569
570     # Check status
571     local output_file="$TEST_DIR/multi_base_output.txt"
572     "$PG_SCRIBE" --status -d "$dbname" -S "$slot" -f "$backup_dir" -U "$PGUSER" &>"$output_file" || true
573
574     # Count base backups - should be 3
575     local count
576     count=$(find "$backup_dir" -maxdepth 1 -name 'base-*.sql' 2>/dev/null | wc -l)
577
578     # Should report multiple base backups (at least 3)
579     if ! grep -q "Base Backups:" "$output_file"; then
580         log_fail "Output missing base backup count"
581         cat "$output_file" >&2
582         return 1
583     fi
584
585     # Check that we have at least 3 base backups
586     if [[ $count -lt 3 ]]; then
587         log_fail "Expected at least 3 base backups, found $count"
588         ls -la "$backup_dir" >&2
589         return 1
590     fi
591
592     # Should show latest base backup
593     if ! grep -q "Latest Base:" "$output_file"; then
594         log_fail "Should show latest base backup"
595         cat "$output_file" >&2
596         return 1
597     fi
598
599     log_pass "Multiple base backups handled correctly"
600     return 0
601 }
602
603 #
604 # Cleanup
605 #
606
607 # shellcheck disable=SC2317  # Function called via trap handler
608 cleanup() {
609     log_info "Cleaning up test resources..."
610
611     # Kill any running pg_scribe processes
612     for pid in "${PIDS_TO_CLEANUP[@]}"; do
613         if kill -0 "$pid" 2>/dev/null; then
614             log_info "Stopping pg_scribe process $pid"
615             # Try graceful shutdown first (allows signal forwarding to child processes)
616             kill -TERM "$pid" 2>/dev/null || true
617
618             # Wait briefly for graceful shutdown
619             local timeout=2
620             local count=0
621             while kill -0 "$pid" 2>/dev/null && [[ $count -lt $timeout ]]; do
622                 sleep 0.5
623                 ((count++))
624             done
625
626             # Force kill if still running
627             if kill -0 "$pid" 2>/dev/null; then
628                 log_info "Force killing pg_scribe process $pid"
629                 kill -9 "$pid" 2>/dev/null || true
630             fi
631         fi
632     done
633
634     # Wait for child pg_recvlogical processes to fully terminate
635     # (They may take a moment to shut down after parent terminates)
636     sleep 1
637
638     # Drop replication slots
639     for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
640         for slot in test_slot_noslot test_slot_healthy test_slot_with_dir test_slot_inactive test_slot_streaming test_slot_with_inc test_slot_no_meta test_slot_no_dir test_slot_lag test_slot_multi_base; do
641             drop_replication_slot "$dbname" "$slot" 2>/dev/null || true
642         done
643     done
644
645     # Drop databases
646     for dbname in "${DATABASES_TO_CLEANUP[@]}"; do
647         drop_test_db "$dbname"
648     done
649
650     # Remove test directory
651     if [[ -d "$TEST_DIR" ]]; then
652         rm -rf "$TEST_DIR"
653     fi
654
655     log_info "Cleanup complete"
656 }
657
658 #
659 # Main test runner
660 #
661
662 main() {
663     echo "========================================"
664     echo "pg_scribe --status Test Suite"
665     echo "========================================"
666     echo ""
667
668     # Verify pg_scribe exists
669     if [[ ! -x "$PG_SCRIBE" ]]; then
670         echo "ERROR: pg_scribe not found or not executable: $PG_SCRIBE"
671         exit 1
672     fi
673
674     # Verify PostgreSQL is running
675     if ! psql -U "$PGUSER" -d postgres -c "SELECT 1;" &>/dev/null; then
676         echo "ERROR: Cannot connect to PostgreSQL"
677         exit 1
678     fi
679
680     # Verify wal_level is logical
681     local wal_level
682     wal_level=$(psql -U "$PGUSER" -d postgres -tAq -c "SHOW wal_level;")
683     if [[ "$wal_level" != "logical" ]]; then
684         echo "ERROR: wal_level must be 'logical', currently: $wal_level"
685         echo "Update ~/.pgenv/pgsql/data/postgresql.conf and restart PostgreSQL"
686         exit 1
687     fi
688
689     # Create test directory
690     mkdir -p "$TEST_DIR"
691
692     # Set up cleanup trap
693     trap cleanup EXIT INT TERM
694
695     echo "Running tests..."
696     echo ""
697
698     # Run all tests (use || true to prevent set -e from exiting)
699     test_status_without_slot || true
700     test_status_basic_healthy || true
701     test_status_with_backup_directory || true
702     test_status_inactive_slot || true
703     test_status_with_active_streaming || true
704     test_status_with_incremental_backups || true
705     test_status_missing_metadata || true
706     test_status_nonexistent_backup_dir || true
707     test_status_lag_display || true
708     test_status_multiple_base_backups || true
709
710     # Summary
711     echo ""
712     echo "========================================"
713     echo "Test Results"
714     echo "========================================"
715     echo "Tests run:    $TESTS_RUN"
716     echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
717     echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
718     echo ""
719
720     if [[ $TESTS_FAILED -eq 0 ]]; then
721         echo -e "${GREEN}All tests passed!${NC}"
722         exit 0
723     else
724         echo -e "${RED}Some tests failed!${NC}"
725         exit 1
726     fi
727 }
728
729 main "$@"