| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- #!/usr/bin/env bash
- # Test script for backup_and_restore.sh
- # Tests backward compatibility with .tar.gz and new .tar.zst format
- set -e
- SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
- BACKUP_IMAGE="${BACKUP_IMAGE:-ghcr.io/mailcow/backup:latest}"
- TEST_DIR="/tmp/mailcow_backup_test_$$"
- THREADS=2
- echo "=== Mailcow Backup & Restore Test Suite ==="
- echo "Test directory: ${TEST_DIR}"
- echo "Backup image: ${BACKUP_IMAGE}"
- echo ""
- # Cleanup function
- cleanup() {
- echo "Cleaning up test files..."
- rm -rf "${TEST_DIR}"
- docker rmi mailcow-backup-test 2>/dev/null || true
- }
- trap cleanup EXIT
- # Create test directory structure
- mkdir -p "${TEST_DIR}"/{test_data,backup_zst,backup_gz,restore_zst,restore_gz,backup_large_zst,backup_large_gz}
- echo "Test data for mailcow backup compatibility test" > "${TEST_DIR}/test_data/test.txt"
- echo "Additional file to verify complete restore" > "${TEST_DIR}/test_data/test2.txt"
- # Build test backup image with zstd support
- echo "=== Building backup image with zstd support ==="
- docker build -t mailcow-backup-test "${SCRIPT_DIR}/../data/Dockerfiles/backup/" || {
- echo "ERROR: Failed to build backup image"
- exit 1
- }
- # Test 1: Create .tar.zst backup
- echo ""
- echo "=== Test 1: Creating .tar.zst backup ==="
- docker run --rm \
- -w /data \
- -v "${TEST_DIR}/test_data:/data:ro" \
- -v "${TEST_DIR}/backup_zst:/backup" \
- mailcow-backup-test \
- /bin/tar --use-compress-program="zstd --rsyncable -T${THREADS}" \
- -cvpf /backup/backup_test.tar.zst . \
- > /dev/null
- echo "✓ .tar.zst backup created: $(ls -lh ${TEST_DIR}/backup_zst/backup_test.tar.zst | awk '{print $5}')"
- # Test 2: Create .tar.gz backup
- echo ""
- echo "=== Test 2: Creating .tar.gz backup (legacy) ==="
- docker run --rm \
- -w /data \
- -v "${TEST_DIR}/test_data:/data:ro" \
- -v "${TEST_DIR}/backup_gz:/backup" \
- mailcow-backup-test \
- /bin/tar --use-compress-program="pigz --rsyncable -p ${THREADS}" \
- -cvpf /backup/backup_test.tar.gz . \
- > /dev/null
- echo "✓ .tar.gz backup created: $(ls -lh ${TEST_DIR}/backup_gz/backup_test.tar.gz | awk '{print $5}')"
- # Test 3: Test get_archive_info function
- echo ""
- echo "=== Test 3: Testing get_archive_info function ==="
- # Extract and test the function directly
- get_archive_info() {
- local backup_name="$1"
- local location="$2"
- if [[ -f "${location}/${backup_name}.tar.zst" ]]; then
- echo "${backup_name}.tar.zst|zstd -d -T${THREADS}"
- elif [[ -f "${location}/${backup_name}.tar.gz" ]]; then
- echo "${backup_name}.tar.gz|pigz -d -p ${THREADS}"
- else
- echo ""
- fi
- }
- # Test with .tar.zst
- result=$(get_archive_info "backup_test" "${TEST_DIR}/backup_zst")
- if [[ "${result}" =~ "zstd" ]]; then
- echo "✓ Correctly detects .tar.zst and returns zstd decompressor"
- else
- echo "✗ Failed to detect .tar.zst"
- exit 1
- fi
- # Test with .tar.gz
- result=$(get_archive_info "backup_test" "${TEST_DIR}/backup_gz")
- if [[ "${result}" =~ "pigz" ]]; then
- echo "✓ Correctly detects .tar.gz and returns pigz decompressor"
- else
- echo "✗ Failed to detect .tar.gz"
- exit 1
- fi
- # Test with no file
- result=$(get_archive_info "backup_test" "${TEST_DIR}")
- if [[ -z "${result}" ]]; then
- echo "✓ Correctly returns empty when no backup file found"
- else
- echo "✗ Should return empty but got: ${result}"
- exit 1
- fi
- # Test 4: Restore from .tar.zst
- echo ""
- echo "=== Test 4: Restoring from .tar.zst ==="
- docker run --rm \
- -w /restore \
- -v "${TEST_DIR}/backup_zst:/backup:ro" \
- -v "${TEST_DIR}/restore_zst:/restore" \
- mailcow-backup-test \
- /bin/tar --use-compress-program="zstd -d -T${THREADS}" -xvpf /backup/backup_test.tar.zst \
- > /dev/null 2>&1
- if [[ -f "${TEST_DIR}/restore_zst/test.txt" ]] && \
- [[ -f "${TEST_DIR}/restore_zst/test2.txt" ]]; then
- echo "✓ Successfully restored from .tar.zst"
- else
- echo "✗ Failed to restore from .tar.zst"
- ls -la "${TEST_DIR}/restore_zst/" || true
- exit 1
- fi
- # Test 5: Restore from .tar.gz
- echo ""
- echo "=== Test 5: Restoring from .tar.gz (backward compatibility) ==="
- docker run --rm \
- -w /restore \
- -v "${TEST_DIR}/backup_gz:/backup:ro" \
- -v "${TEST_DIR}/restore_gz:/restore" \
- mailcow-backup-test \
- /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -xvpf /backup/backup_test.tar.gz \
- > /dev/null 2>&1
- if [[ -f "${TEST_DIR}/restore_gz/test.txt" ]] && \
- [[ -f "${TEST_DIR}/restore_gz/test2.txt" ]]; then
- echo "✓ Successfully restored from .tar.gz (backward compatible)"
- else
- echo "✗ Failed to restore from .tar.gz"
- ls -la "${TEST_DIR}/restore_gz/" || true
- exit 1
- fi
- # Test 6: Verify content integrity
- echo ""
- echo "=== Test 6: Verifying content integrity ==="
- original_content=$(cat "${TEST_DIR}/test_data/test.txt")
- zst_content=$(cat "${TEST_DIR}/restore_zst/test.txt")
- gz_content=$(cat "${TEST_DIR}/restore_gz/test.txt")
- if [[ "${original_content}" == "${zst_content}" ]] && \
- [[ "${original_content}" == "${gz_content}" ]]; then
- echo "✓ Content integrity verified for both formats"
- else
- echo "✗ Content mismatch detected"
- exit 1
- fi
- # Test 7: Compare compression ratios
- echo ""
- echo "=== Test 7: Compression comparison ==="
- zst_size=$(stat -f%z "${TEST_DIR}/backup_zst/backup_test.tar.zst" 2>/dev/null || stat -c%s "${TEST_DIR}/backup_zst/backup_test.tar.zst")
- gz_size=$(stat -f%z "${TEST_DIR}/backup_gz/backup_test.tar.gz" 2>/dev/null || stat -c%s "${TEST_DIR}/backup_gz/backup_test.tar.gz")
- improvement=$(echo "scale=2; (${gz_size} - ${zst_size}) * 100 / ${gz_size}" | bc)
- echo " Small files - .tar.gz size: ${gz_size} bytes"
- echo " Small files - .tar.zst size: ${zst_size} bytes"
- echo " Small files - Improvement: ${improvement}% smaller with zstd"
- # Test 8: Error handling - missing backup file
- echo ""
- echo "=== Test 8: Error handling - Missing backup file ==="
- result=$(get_archive_info "nonexistent_backup" "${TEST_DIR}/backup_zst")
- if [[ -z "${result}" ]]; then
- echo "✓ Correctly handles missing backup files"
- else
- echo "✗ Should return empty for missing files"
- exit 1
- fi
- # Test 9: Error handling - Empty directory
- echo ""
- echo "=== Test 9: Error handling - Empty directory ==="
- mkdir -p "${TEST_DIR}/empty_dir"
- result=$(get_archive_info "backup_test" "${TEST_DIR}/empty_dir")
- if [[ -z "${result}" ]]; then
- echo "✓ Correctly handles empty directories"
- else
- echo "✗ Should return empty for empty directories"
- exit 1
- fi
- # Test 10: Priority test - .tar.zst preferred over .tar.gz
- echo ""
- echo "=== Test 10: Format priority - .tar.zst preferred ==="
- mkdir -p "${TEST_DIR}/both_formats"
- touch "${TEST_DIR}/both_formats/backup_test.tar.gz"
- touch "${TEST_DIR}/both_formats/backup_test.tar.zst"
- result=$(get_archive_info "backup_test" "${TEST_DIR}/both_formats")
- if [[ "${result}" =~ "zstd" ]]; then
- echo "✓ Correctly prefers .tar.zst when both formats exist"
- else
- echo "✗ Should prefer .tar.zst over .tar.gz"
- exit 1
- fi
- # Test 11: Large file compression test
- echo ""
- echo "=== Test 11: Large file compression test ==="
- mkdir -p "${TEST_DIR}/large_data"
- # Create ~10MB of compressible data (log-like content)
- for i in {1..50000}; do
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: Processing email message $i from user@example.com to recipient@domain.com" >> "${TEST_DIR}/large_data/maillog.txt"
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: SMTP connection established from 192.168.1.$((i % 255))" >> "${TEST_DIR}/large_data/maillog.txt"
- done 2>/dev/null
- # Get size (portable: works on Linux and macOS)
- if du --version 2>/dev/null | grep -q GNU; then
- original_size=$(du -sb "${TEST_DIR}/large_data" | cut -f1)
- else
- # macOS
- original_size=$(find "${TEST_DIR}/large_data" -type f -exec stat -f%z {} \; | awk '{sum+=$1} END {print sum}')
- fi
- echo " Original data size: $(echo "scale=2; ${original_size} / 1024 / 1024" | bc) MB"
- # Backup with zstd
- docker run --rm \
- -w /data \
- -v "${TEST_DIR}/large_data:/data:ro" \
- -v "${TEST_DIR}/backup_large_zst:/backup" \
- mailcow-backup-test \
- /bin/tar --use-compress-program="zstd --rsyncable -T${THREADS}" \
- -cvpf /backup/backup_large.tar.zst . \
- > /dev/null 2>&1
- # Backup with pigz
- docker run --rm \
- -w /data \
- -v "${TEST_DIR}/large_data:/data:ro" \
- -v "${TEST_DIR}/backup_large_gz:/backup" \
- mailcow-backup-test \
- /bin/tar --use-compress-program="pigz --rsyncable -p ${THREADS}" \
- -cvpf /backup/backup_large.tar.gz . \
- > /dev/null 2>&1
- zst_large_size=$(stat -f%z "${TEST_DIR}/backup_large_zst/backup_large.tar.zst" 2>/dev/null || stat -c%s "${TEST_DIR}/backup_large_zst/backup_large.tar.zst" 2>/dev/null || echo "0")
- gz_large_size=$(stat -f%z "${TEST_DIR}/backup_large_gz/backup_large.tar.gz" 2>/dev/null || stat -c%s "${TEST_DIR}/backup_large_gz/backup_large.tar.gz" 2>/dev/null || echo "0")
- if [[ ${zst_large_size} -gt 0 ]] && [[ ${gz_large_size} -gt 0 ]]; then
- large_improvement=$(echo "scale=2; (${gz_large_size} - ${zst_large_size}) * 100 / ${gz_large_size}" | bc)
- echo " .tar.gz compressed: $(echo "scale=2; ${gz_large_size} / 1024 / 1024" | bc) MB"
- echo " .tar.zst compressed: $(echo "scale=2; ${zst_large_size} / 1024 / 1024" | bc) MB"
- echo " Improvement: ${large_improvement}% smaller with zstd"
- else
- echo " ✗ Failed to get file sizes"
- exit 1
- fi
- if [[ $(echo "${large_improvement} > 0" | bc) -eq 1 ]]; then
- echo "✓ zstd provides better compression on realistic data"
- else
- echo "⚠ zstd compression similar or worse than gzip (unusual but not critical)"
- fi
- # Test 12: Thread scaling test
- echo ""
- echo "=== Test 12: Multi-threading verification ==="
- # This test verifies that different thread counts work (not measuring speed difference)
- for thread_count in 1 4; do
- THREADS=${thread_count}
- result=$(get_archive_info "backup_test" "${TEST_DIR}/backup_zst")
- if [[ "${result}" =~ "-T${thread_count}" ]]; then
- echo "✓ Thread count ${thread_count} correctly configured"
- else
- echo "✗ Thread count not properly applied"
- exit 1
- fi
- done
- echo ""
- echo "=== All tests passed! ==="
- echo ""
- echo "Summary:"
- echo " ✓ zstd compression working"
- echo " ✓ pigz compression working (legacy)"
- echo " ✓ zstd decompression working"
- echo " ✓ pigz decompression working (backward compatible)"
- echo " ✓ Archive detection working"
- echo " ✓ Content integrity verified"
- echo " ✓ Format priority correct (.tar.zst preferred)"
- echo " ✓ Error handling for missing files"
- echo " ✓ Error handling for empty directories"
- echo " ✓ Multi-threading configuration verified"
- echo " ✓ Large file compression: ${large_improvement}% improvement"
- echo " ✓ Small file compression: ${improvement}% improvement"
|