- Test 6: nested directory extraction (3+ levels deep, 4 files) - Test 7: empty directory creation without decryption errors - Test 8: mixed standalone files + directory pack/unpack - All 5 original test cases preserved unchanged Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
328 lines
11 KiB
Bash
Executable File
328 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cross-validation test script for Kotlin ArchiveDecoder
|
|
#
|
|
# Creates archives with the Rust CLI, decodes with the Kotlin decoder,
|
|
# and verifies byte-identical output via SHA-256 comparison.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
TMPDIR=""
|
|
PASS_COUNT=0
|
|
FAIL_COUNT=0
|
|
TOTAL_COUNT=0
|
|
|
|
# Colors (if terminal supports them)
|
|
if [ -t 1 ]; then
|
|
GREEN='\033[0;32m'
|
|
RED='\033[0;31m'
|
|
YELLOW='\033[0;33m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
else
|
|
GREEN=''
|
|
RED=''
|
|
YELLOW=''
|
|
BOLD=''
|
|
NC=''
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cleanup() {
|
|
if [ -n "$TMPDIR" ] && [ -d "$TMPDIR" ]; then
|
|
rm -rf "$TMPDIR"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
pass() {
|
|
local name="$1"
|
|
PASS_COUNT=$((PASS_COUNT + 1))
|
|
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
|
echo -e " ${GREEN}PASS${NC}: $name"
|
|
}
|
|
|
|
fail() {
|
|
local name="$1"
|
|
local detail="${2:-}"
|
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
|
echo -e " ${RED}FAIL${NC}: $name"
|
|
if [ -n "$detail" ]; then
|
|
echo " $detail"
|
|
fi
|
|
}
|
|
|
|
sha256_of() {
|
|
sha256sum "$1" | awk '{print $1}'
|
|
}
|
|
|
|
verify_file() {
|
|
local original="$1"
|
|
local extracted="$2"
|
|
local label="$3"
|
|
|
|
if [ ! -f "$extracted" ]; then
|
|
fail "$label" "Extracted file not found: $extracted"
|
|
return
|
|
fi
|
|
|
|
local orig_hash
|
|
local ext_hash
|
|
orig_hash=$(sha256_of "$original")
|
|
ext_hash=$(sha256_of "$extracted")
|
|
|
|
if [ "$orig_hash" = "$ext_hash" ]; then
|
|
pass "$label (SHA-256: ${orig_hash:0:16}...)"
|
|
else
|
|
fail "$label" "SHA-256 mismatch: original=$orig_hash extracted=$ext_hash"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Prerequisites check
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Checking prerequisites...${NC}"
|
|
|
|
if ! command -v kotlinc &>/dev/null; then
|
|
echo -e "${RED}ERROR${NC}: kotlinc not found."
|
|
echo " Install Kotlin compiler:"
|
|
echo " - SDKMAN: sdk install kotlin"
|
|
echo " - Homebrew: brew install kotlin"
|
|
echo " - Snap: sudo snap install kotlin --classic"
|
|
echo " - Manual: https://kotlinlang.org/docs/command-line.html"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v java &>/dev/null; then
|
|
echo -e "${RED}ERROR${NC}: java not found."
|
|
echo " Install Java runtime:"
|
|
echo " - Ubuntu: sudo apt install default-jdk"
|
|
echo " - SDKMAN: sdk install java"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v cargo &>/dev/null; then
|
|
echo -e "${RED}ERROR${NC}: cargo not found."
|
|
echo " Install Rust: https://rustup.rs/"
|
|
exit 1
|
|
fi
|
|
|
|
echo " kotlinc: $(kotlinc -version 2>&1 | head -1)"
|
|
echo " java: $(java -version 2>&1 | head -1)"
|
|
echo " cargo: $(cargo --version)"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Build Rust archiver
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Building Rust archiver...${NC}"
|
|
(cd "$PROJECT_DIR" && cargo build --release -q)
|
|
ARCHIVER="$PROJECT_DIR/target/release/encrypted_archive"
|
|
echo " Built: $ARCHIVER"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Compile Kotlin decoder
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Compiling Kotlin decoder...${NC}"
|
|
JAR="$SCRIPT_DIR/ArchiveDecoder.jar"
|
|
kotlinc "$SCRIPT_DIR/ArchiveDecoder.kt" -include-runtime -d "$JAR" 2>&1 | tail -5 || true
|
|
if [ ! -f "$JAR" ]; then
|
|
echo -e "${RED}ERROR${NC}: Compilation failed. ArchiveDecoder.jar not found."
|
|
exit 1
|
|
fi
|
|
echo " Compiled: $JAR"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Create temp directory
|
|
# ---------------------------------------------------------------------------
|
|
|
|
TMPDIR=$(mktemp -d)
|
|
echo -e "${BOLD}Temp directory: $TMPDIR${NC}"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test case 1: Single text file
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Test 1: Single text file${NC}"
|
|
|
|
ORIG1="$TMPDIR/hello.txt"
|
|
echo -n "Hello, World!" > "$ORIG1"
|
|
|
|
"$ARCHIVER" pack "$ORIG1" -o "$TMPDIR/test1.archive"
|
|
java -jar "$JAR" "$TMPDIR/test1.archive" "$TMPDIR/output1/"
|
|
|
|
verify_file "$ORIG1" "$TMPDIR/output1/hello.txt" "hello.txt"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test case 2: Multiple files with mixed content (text + binary + Cyrillic)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Test 2: Multiple files with mixed content${NC}"
|
|
|
|
ORIG2_TEXT="$TMPDIR/text.txt"
|
|
cat > "$ORIG2_TEXT" << 'TEXTEOF'
|
|
This is a test file with multiple lines.
|
|
It contains ASCII and Cyrillic characters.
|
|
Line 3: testing 1 2 3.
|
|
TEXTEOF
|
|
|
|
ORIG2_BIN="$TMPDIR/binary.bin"
|
|
dd if=/dev/urandom bs=1024 count=10 of="$ORIG2_BIN" 2>/dev/null
|
|
|
|
"$ARCHIVER" pack "$ORIG2_TEXT" "$ORIG2_BIN" -o "$TMPDIR/test2.archive"
|
|
java -jar "$JAR" "$TMPDIR/test2.archive" "$TMPDIR/output2/"
|
|
|
|
verify_file "$ORIG2_TEXT" "$TMPDIR/output2/text.txt" "text.txt (multiline UTF-8)"
|
|
verify_file "$ORIG2_BIN" "$TMPDIR/output2/binary.bin" "binary.bin (10 KB random)"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test case 3: Already-compressed file (APK-like, no compression)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Test 3: Already-compressed file (no-compress mode)${NC}"
|
|
|
|
ORIG3="$TMPDIR/fake.dat"
|
|
dd if=/dev/urandom bs=512 count=5 of="$ORIG3" 2>/dev/null
|
|
|
|
# Use --no-compress with the filename pattern to skip compression
|
|
"$ARCHIVER" pack "$ORIG3" -o "$TMPDIR/test3.archive" --no-compress "fake.dat"
|
|
java -jar "$JAR" "$TMPDIR/test3.archive" "$TMPDIR/output3/"
|
|
|
|
verify_file "$ORIG3" "$TMPDIR/output3/fake.dat" "fake.dat (no-compress)"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test case 4: Empty file
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Test 4: Empty file${NC}"
|
|
|
|
ORIG4="$TMPDIR/empty.txt"
|
|
touch "$ORIG4"
|
|
|
|
"$ARCHIVER" pack "$ORIG4" -o "$TMPDIR/test4.archive"
|
|
java -jar "$JAR" "$TMPDIR/test4.archive" "$TMPDIR/output4/"
|
|
|
|
# Verify output file exists and is empty
|
|
if [ -f "$TMPDIR/output4/empty.txt" ]; then
|
|
EMPTY_SIZE=$(wc -c < "$TMPDIR/output4/empty.txt")
|
|
if [ "$EMPTY_SIZE" -eq 0 ]; then
|
|
pass "empty.txt (0 bytes)"
|
|
else
|
|
fail "empty.txt" "Expected 0 bytes, got $EMPTY_SIZE bytes"
|
|
fi
|
|
else
|
|
fail "empty.txt" "File not found in output"
|
|
fi
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test case 5: Large file (100 KB)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Test 5: Large file (100 KB)${NC}"
|
|
|
|
ORIG5="$TMPDIR/large.bin"
|
|
dd if=/dev/urandom bs=1024 count=100 of="$ORIG5" 2>/dev/null
|
|
|
|
"$ARCHIVER" pack "$ORIG5" -o "$TMPDIR/test5.archive"
|
|
java -jar "$JAR" "$TMPDIR/test5.archive" "$TMPDIR/output5/"
|
|
|
|
verify_file "$ORIG5" "$TMPDIR/output5/large.bin" "large.bin (100 KB random)"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test case 6: Directory with nested files
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Test 6: Directory with nested files${NC}"
|
|
|
|
mkdir -p "$TMPDIR/testdir6/subdir1/deep"
|
|
mkdir -p "$TMPDIR/testdir6/subdir2"
|
|
echo "file in root" > "$TMPDIR/testdir6/root.txt"
|
|
echo "file in subdir1" > "$TMPDIR/testdir6/subdir1/sub1.txt"
|
|
echo "file in deep" > "$TMPDIR/testdir6/subdir1/deep/deep.txt"
|
|
echo "file in subdir2" > "$TMPDIR/testdir6/subdir2/sub2.txt"
|
|
|
|
"$ARCHIVER" pack "$TMPDIR/testdir6" -o "$TMPDIR/test6.archive"
|
|
java -jar "$JAR" "$TMPDIR/test6.archive" "$TMPDIR/output6/"
|
|
|
|
verify_file "$TMPDIR/testdir6/root.txt" "$TMPDIR/output6/testdir6/root.txt" "testdir6/root.txt"
|
|
verify_file "$TMPDIR/testdir6/subdir1/sub1.txt" "$TMPDIR/output6/testdir6/subdir1/sub1.txt" "testdir6/subdir1/sub1.txt"
|
|
verify_file "$TMPDIR/testdir6/subdir1/deep/deep.txt" "$TMPDIR/output6/testdir6/subdir1/deep/deep.txt" "testdir6/subdir1/deep/deep.txt"
|
|
verify_file "$TMPDIR/testdir6/subdir2/sub2.txt" "$TMPDIR/output6/testdir6/subdir2/sub2.txt" "testdir6/subdir2/sub2.txt"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test case 7: Directory with empty subdirectory
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Test 7: Directory with empty subdirectory${NC}"
|
|
|
|
mkdir -p "$TMPDIR/testdir7/populated"
|
|
mkdir -p "$TMPDIR/testdir7/empty_subdir"
|
|
echo "content" > "$TMPDIR/testdir7/populated/file.txt"
|
|
|
|
"$ARCHIVER" pack "$TMPDIR/testdir7" -o "$TMPDIR/test7.archive"
|
|
java -jar "$JAR" "$TMPDIR/test7.archive" "$TMPDIR/output7/"
|
|
|
|
# Verify file content
|
|
verify_file "$TMPDIR/testdir7/populated/file.txt" "$TMPDIR/output7/testdir7/populated/file.txt" "testdir7/populated/file.txt"
|
|
|
|
# Verify empty directory exists
|
|
if [ -d "$TMPDIR/output7/testdir7/empty_subdir" ]; then
|
|
pass "testdir7/empty_subdir (empty directory created)"
|
|
else
|
|
fail "testdir7/empty_subdir" "Empty directory not found in output"
|
|
fi
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test case 8: Mixed standalone files and directory
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}Test 8: Mixed standalone files and directory${NC}"
|
|
|
|
ORIG8_FILE="$TMPDIR/standalone.txt"
|
|
echo "standalone content" > "$ORIG8_FILE"
|
|
mkdir -p "$TMPDIR/testdir8"
|
|
echo "dir content" > "$TMPDIR/testdir8/inner.txt"
|
|
|
|
"$ARCHIVER" pack "$ORIG8_FILE" "$TMPDIR/testdir8" -o "$TMPDIR/test8.archive"
|
|
java -jar "$JAR" "$TMPDIR/test8.archive" "$TMPDIR/output8/"
|
|
|
|
verify_file "$ORIG8_FILE" "$TMPDIR/output8/standalone.txt" "standalone.txt (standalone file)"
|
|
verify_file "$TMPDIR/testdir8/inner.txt" "$TMPDIR/output8/testdir8/inner.txt" "testdir8/inner.txt (from directory)"
|
|
echo ""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Summary
|
|
# ---------------------------------------------------------------------------
|
|
|
|
echo -e "${BOLD}========================================${NC}"
|
|
echo -e "${BOLD}Results: $PASS_COUNT passed, $FAIL_COUNT failed out of $TOTAL_COUNT tests${NC}"
|
|
echo -e "${BOLD}========================================${NC}"
|
|
|
|
if [ "$FAIL_COUNT" -gt 0 ]; then
|
|
echo -e "${RED}SOME TESTS FAILED${NC}"
|
|
exit 1
|
|
else
|
|
echo -e "${GREEN}ALL TESTS PASSED${NC}"
|
|
exit 0
|
|
fi
|