2
0

test_backup_and_restore.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. #!/usr/bin/env bash
  2. # Test script for backup_and_restore.sh
  3. # Tests backward compatibility with .tar.gz and new .tar.zst format
  4. set -e
  5. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
  6. BACKUP_IMAGE="${BACKUP_IMAGE:-ghcr.io/mailcow/backup:latest}"
  7. TEST_DIR="/tmp/mailcow_backup_test_$$"
  8. THREADS=2
  9. echo "=== Mailcow Backup & Restore Test Suite ==="
  10. echo "Test directory: ${TEST_DIR}"
  11. echo "Backup image: ${BACKUP_IMAGE}"
  12. echo ""
  13. # Cleanup function
  14. cleanup() {
  15. echo "Cleaning up test files..."
  16. rm -rf "${TEST_DIR}"
  17. docker rmi mailcow-backup-test 2>/dev/null || true
  18. }
  19. trap cleanup EXIT
  20. # Create test directory structure
  21. mkdir -p "${TEST_DIR}"/{test_data,backup_zst,backup_gz,restore_zst,restore_gz,backup_large_zst,backup_large_gz}
  22. echo "Test data for mailcow backup compatibility test" > "${TEST_DIR}/test_data/test.txt"
  23. echo "Additional file to verify complete restore" > "${TEST_DIR}/test_data/test2.txt"
  24. # Build test backup image with zstd support
  25. echo "=== Building backup image with zstd support ==="
  26. docker build -t mailcow-backup-test "${SCRIPT_DIR}/../data/Dockerfiles/backup/" || {
  27. echo "ERROR: Failed to build backup image"
  28. exit 1
  29. }
  30. # Test 1: Create .tar.zst backup
  31. echo ""
  32. echo "=== Test 1: Creating .tar.zst backup ==="
  33. docker run --rm \
  34. -w /data \
  35. -v "${TEST_DIR}/test_data:/data:ro" \
  36. -v "${TEST_DIR}/backup_zst:/backup" \
  37. mailcow-backup-test \
  38. /bin/tar --use-compress-program="zstd --rsyncable -T${THREADS}" \
  39. -cvpf /backup/backup_test.tar.zst . \
  40. > /dev/null
  41. echo "✓ .tar.zst backup created: $(ls -lh ${TEST_DIR}/backup_zst/backup_test.tar.zst | awk '{print $5}')"
  42. # Test 2: Create .tar.gz backup
  43. echo ""
  44. echo "=== Test 2: Creating .tar.gz backup (legacy) ==="
  45. docker run --rm \
  46. -w /data \
  47. -v "${TEST_DIR}/test_data:/data:ro" \
  48. -v "${TEST_DIR}/backup_gz:/backup" \
  49. mailcow-backup-test \
  50. /bin/tar --use-compress-program="pigz --rsyncable -p ${THREADS}" \
  51. -cvpf /backup/backup_test.tar.gz . \
  52. > /dev/null
  53. echo "✓ .tar.gz backup created: $(ls -lh ${TEST_DIR}/backup_gz/backup_test.tar.gz | awk '{print $5}')"
  54. # Test 3: Test get_archive_info function
  55. echo ""
  56. echo "=== Test 3: Testing get_archive_info function ==="
  57. # Extract and test the function directly
  58. get_archive_info() {
  59. local backup_name="$1"
  60. local location="$2"
  61. if [[ -f "${location}/${backup_name}.tar.zst" ]]; then
  62. echo "${backup_name}.tar.zst|zstd -d -T${THREADS}"
  63. elif [[ -f "${location}/${backup_name}.tar.gz" ]]; then
  64. echo "${backup_name}.tar.gz|pigz -d -p ${THREADS}"
  65. else
  66. echo ""
  67. fi
  68. }
  69. # Test with .tar.zst
  70. result=$(get_archive_info "backup_test" "${TEST_DIR}/backup_zst")
  71. if [[ "${result}" =~ "zstd" ]]; then
  72. echo "✓ Correctly detects .tar.zst and returns zstd decompressor"
  73. else
  74. echo "✗ Failed to detect .tar.zst"
  75. exit 1
  76. fi
  77. # Test with .tar.gz
  78. result=$(get_archive_info "backup_test" "${TEST_DIR}/backup_gz")
  79. if [[ "${result}" =~ "pigz" ]]; then
  80. echo "✓ Correctly detects .tar.gz and returns pigz decompressor"
  81. else
  82. echo "✗ Failed to detect .tar.gz"
  83. exit 1
  84. fi
  85. # Test with no file
  86. result=$(get_archive_info "backup_test" "${TEST_DIR}")
  87. if [[ -z "${result}" ]]; then
  88. echo "✓ Correctly returns empty when no backup file found"
  89. else
  90. echo "✗ Should return empty but got: ${result}"
  91. exit 1
  92. fi
  93. # Test 4: Restore from .tar.zst
  94. echo ""
  95. echo "=== Test 4: Restoring from .tar.zst ==="
  96. docker run --rm \
  97. -w /restore \
  98. -v "${TEST_DIR}/backup_zst:/backup:ro" \
  99. -v "${TEST_DIR}/restore_zst:/restore" \
  100. mailcow-backup-test \
  101. /bin/tar --use-compress-program="zstd -d -T${THREADS}" -xvpf /backup/backup_test.tar.zst \
  102. > /dev/null 2>&1
  103. if [[ -f "${TEST_DIR}/restore_zst/test.txt" ]] && \
  104. [[ -f "${TEST_DIR}/restore_zst/test2.txt" ]]; then
  105. echo "✓ Successfully restored from .tar.zst"
  106. else
  107. echo "✗ Failed to restore from .tar.zst"
  108. ls -la "${TEST_DIR}/restore_zst/" || true
  109. exit 1
  110. fi
  111. # Test 5: Restore from .tar.gz
  112. echo ""
  113. echo "=== Test 5: Restoring from .tar.gz (backward compatibility) ==="
  114. docker run --rm \
  115. -w /restore \
  116. -v "${TEST_DIR}/backup_gz:/backup:ro" \
  117. -v "${TEST_DIR}/restore_gz:/restore" \
  118. mailcow-backup-test \
  119. /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -xvpf /backup/backup_test.tar.gz \
  120. > /dev/null 2>&1
  121. if [[ -f "${TEST_DIR}/restore_gz/test.txt" ]] && \
  122. [[ -f "${TEST_DIR}/restore_gz/test2.txt" ]]; then
  123. echo "✓ Successfully restored from .tar.gz (backward compatible)"
  124. else
  125. echo "✗ Failed to restore from .tar.gz"
  126. ls -la "${TEST_DIR}/restore_gz/" || true
  127. exit 1
  128. fi
  129. # Test 6: Verify content integrity
  130. echo ""
  131. echo "=== Test 6: Verifying content integrity ==="
  132. original_content=$(cat "${TEST_DIR}/test_data/test.txt")
  133. zst_content=$(cat "${TEST_DIR}/restore_zst/test.txt")
  134. gz_content=$(cat "${TEST_DIR}/restore_gz/test.txt")
  135. if [[ "${original_content}" == "${zst_content}" ]] && \
  136. [[ "${original_content}" == "${gz_content}" ]]; then
  137. echo "✓ Content integrity verified for both formats"
  138. else
  139. echo "✗ Content mismatch detected"
  140. exit 1
  141. fi
  142. # Test 7: Compare compression ratios
  143. echo ""
  144. echo "=== Test 7: Compression comparison ==="
  145. 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")
  146. 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")
  147. improvement=$(echo "scale=2; (${gz_size} - ${zst_size}) * 100 / ${gz_size}" | bc)
  148. echo " Small files - .tar.gz size: ${gz_size} bytes"
  149. echo " Small files - .tar.zst size: ${zst_size} bytes"
  150. echo " Small files - Improvement: ${improvement}% smaller with zstd"
  151. # Test 8: Error handling - missing backup file
  152. echo ""
  153. echo "=== Test 8: Error handling - Missing backup file ==="
  154. result=$(get_archive_info "nonexistent_backup" "${TEST_DIR}/backup_zst")
  155. if [[ -z "${result}" ]]; then
  156. echo "✓ Correctly handles missing backup files"
  157. else
  158. echo "✗ Should return empty for missing files"
  159. exit 1
  160. fi
  161. # Test 9: Error handling - Empty directory
  162. echo ""
  163. echo "=== Test 9: Error handling - Empty directory ==="
  164. mkdir -p "${TEST_DIR}/empty_dir"
  165. result=$(get_archive_info "backup_test" "${TEST_DIR}/empty_dir")
  166. if [[ -z "${result}" ]]; then
  167. echo "✓ Correctly handles empty directories"
  168. else
  169. echo "✗ Should return empty for empty directories"
  170. exit 1
  171. fi
  172. # Test 10: Priority test - .tar.zst preferred over .tar.gz
  173. echo ""
  174. echo "=== Test 10: Format priority - .tar.zst preferred ==="
  175. mkdir -p "${TEST_DIR}/both_formats"
  176. touch "${TEST_DIR}/both_formats/backup_test.tar.gz"
  177. touch "${TEST_DIR}/both_formats/backup_test.tar.zst"
  178. result=$(get_archive_info "backup_test" "${TEST_DIR}/both_formats")
  179. if [[ "${result}" =~ "zstd" ]]; then
  180. echo "✓ Correctly prefers .tar.zst when both formats exist"
  181. else
  182. echo "✗ Should prefer .tar.zst over .tar.gz"
  183. exit 1
  184. fi
  185. # Test 11: Large file compression test
  186. echo ""
  187. echo "=== Test 11: Large file compression test ==="
  188. mkdir -p "${TEST_DIR}/large_data"
  189. # Create ~10MB of compressible data (log-like content)
  190. for i in {1..50000}; do
  191. 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"
  192. echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: SMTP connection established from 192.168.1.$((i % 255))" >> "${TEST_DIR}/large_data/maillog.txt"
  193. done 2>/dev/null
  194. # Get size (portable: works on Linux and macOS)
  195. if du --version 2>/dev/null | grep -q GNU; then
  196. original_size=$(du -sb "${TEST_DIR}/large_data" | cut -f1)
  197. else
  198. # macOS
  199. original_size=$(find "${TEST_DIR}/large_data" -type f -exec stat -f%z {} \; | awk '{sum+=$1} END {print sum}')
  200. fi
  201. echo " Original data size: $(echo "scale=2; ${original_size} / 1024 / 1024" | bc) MB"
  202. # Backup with zstd
  203. docker run --rm \
  204. -w /data \
  205. -v "${TEST_DIR}/large_data:/data:ro" \
  206. -v "${TEST_DIR}/backup_large_zst:/backup" \
  207. mailcow-backup-test \
  208. /bin/tar --use-compress-program="zstd --rsyncable -T${THREADS}" \
  209. -cvpf /backup/backup_large.tar.zst . \
  210. > /dev/null 2>&1
  211. # Backup with pigz
  212. docker run --rm \
  213. -w /data \
  214. -v "${TEST_DIR}/large_data:/data:ro" \
  215. -v "${TEST_DIR}/backup_large_gz:/backup" \
  216. mailcow-backup-test \
  217. /bin/tar --use-compress-program="pigz --rsyncable -p ${THREADS}" \
  218. -cvpf /backup/backup_large.tar.gz . \
  219. > /dev/null 2>&1
  220. 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")
  221. 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")
  222. if [[ ${zst_large_size} -gt 0 ]] && [[ ${gz_large_size} -gt 0 ]]; then
  223. large_improvement=$(echo "scale=2; (${gz_large_size} - ${zst_large_size}) * 100 / ${gz_large_size}" | bc)
  224. echo " .tar.gz compressed: $(echo "scale=2; ${gz_large_size} / 1024 / 1024" | bc) MB"
  225. echo " .tar.zst compressed: $(echo "scale=2; ${zst_large_size} / 1024 / 1024" | bc) MB"
  226. echo " Improvement: ${large_improvement}% smaller with zstd"
  227. else
  228. echo " ✗ Failed to get file sizes"
  229. exit 1
  230. fi
  231. if [[ $(echo "${large_improvement} > 0" | bc) -eq 1 ]]; then
  232. echo "✓ zstd provides better compression on realistic data"
  233. else
  234. echo "⚠ zstd compression similar or worse than gzip (unusual but not critical)"
  235. fi
  236. # Test 12: Thread scaling test
  237. echo ""
  238. echo "=== Test 12: Multi-threading verification ==="
  239. # This test verifies that different thread counts work (not measuring speed difference)
  240. for thread_count in 1 4; do
  241. THREADS=${thread_count}
  242. result=$(get_archive_info "backup_test" "${TEST_DIR}/backup_zst")
  243. if [[ "${result}" =~ "-T${thread_count}" ]]; then
  244. echo "✓ Thread count ${thread_count} correctly configured"
  245. else
  246. echo "✗ Thread count not properly applied"
  247. exit 1
  248. fi
  249. done
  250. echo ""
  251. echo "=== All tests passed! ==="
  252. echo ""
  253. echo "Summary:"
  254. echo " ✓ zstd compression working"
  255. echo " ✓ pigz compression working (legacy)"
  256. echo " ✓ zstd decompression working"
  257. echo " ✓ pigz decompression working (backward compatible)"
  258. echo " ✓ Archive detection working"
  259. echo " ✓ Content integrity verified"
  260. echo " ✓ Format priority correct (.tar.zst preferred)"
  261. echo " ✓ Error handling for missing files"
  262. echo " ✓ Error handling for empty directories"
  263. echo " ✓ Multi-threading configuration verified"
  264. echo " ✓ Large file compression: ${large_improvement}% improvement"
  265. echo " ✓ Small file compression: ${improvement}% improvement"