Mobile polish, release validation script, bump to v4.1.2
Mobile-responsive CSS improvements:
- Larger touch targets for drop zones and buttons (56px min)
- Touch feedback with active states for touch devices
- Camera hint text on mobile ("Tap to take photo or choose file")
- Mode buttons stack vertically on small screens
- Full-width download buttons on mobile
- Navbar doesn't stick on mobile to save screen space
Release validation script (scripts/validate-release.sh):
- Automated pre-release checks: ruff, imports, encode/decode sanity
- Optional Docker build/test (--docker flag)
- Optional Pi smoke test via SSH (--pi flag)
- Pass/fail summary with exit codes
Other:
- Version bump to 4.1.2 (pyproject.toml, constants.py, __init__.py)
- Fixed ruff import sorting in cli.py
- Updated PLAN-4.1.2.md (all 9 features complete)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
307
scripts/validate-release.sh
Executable file
307
scripts/validate-release.sh
Executable file
@@ -0,0 +1,307 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Stegasoo Release Validation Script
|
||||
# =============================================================================
|
||||
# Automated pre-release validation to catch issues before tagging a release.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/validate-release.sh # Local validation only
|
||||
# ./scripts/validate-release.sh --pi # Include Pi smoke test
|
||||
# PI_IP=192.168.0.4 ./scripts/validate-release.sh --pi
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 = All tests passed
|
||||
# 1 = One or more tests failed
|
||||
# =============================================================================
|
||||
|
||||
# Don't use set -e as we need to handle test failures gracefully
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Default Pi IP (can be overridden via environment)
|
||||
PI_IP="${PI_IP:-192.168.0.4}"
|
||||
PI_USER="${PI_USER:-alee}"
|
||||
INCLUDE_PI=false
|
||||
INCLUDE_DOCKER=true
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--pi)
|
||||
INCLUDE_PI=true
|
||||
shift
|
||||
;;
|
||||
--no-docker)
|
||||
INCLUDE_DOCKER=false
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--pi] [--no-docker]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --pi Include Pi smoke test (requires SSH access)"
|
||||
echo " --no-docker Skip Docker build/test"
|
||||
echo ""
|
||||
echo "Environment:"
|
||||
echo " PI_IP Pi IP address (default: 192.168.0.4)"
|
||||
echo " PI_USER Pi SSH user (default: alee)"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Track results
|
||||
TESTS_RUN=0
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
FAILED_TESTS=()
|
||||
|
||||
# Helper functions
|
||||
pass() {
|
||||
echo -e "${GREEN}[PASS]${NC} $1"
|
||||
((TESTS_PASSED++))
|
||||
((TESTS_RUN++))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo -e "${RED}[FAIL]${NC} $1"
|
||||
FAILED_TESTS+=("$1")
|
||||
((TESTS_FAILED++))
|
||||
((TESTS_RUN++))
|
||||
}
|
||||
|
||||
skip() {
|
||||
echo -e "${YELLOW}[SKIP]${NC} $1"
|
||||
}
|
||||
|
||||
section() {
|
||||
echo ""
|
||||
echo -e "${CYAN}━━━ $1 ━━━${NC}"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Header
|
||||
# =============================================================================
|
||||
echo -e "${CYAN}╔═══════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ Stegasoo Release Validation ║${NC}"
|
||||
echo -e "${CYAN}╚═══════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# Get version from pyproject.toml
|
||||
VERSION=$(grep '^version = ' pyproject.toml | head -1 | cut -d'"' -f2)
|
||||
echo -e "Version: ${YELLOW}${VERSION}${NC}"
|
||||
echo -e "Branch: ${YELLOW}$(git branch --show-current)${NC}"
|
||||
echo ""
|
||||
|
||||
# =============================================================================
|
||||
# 1. Code Quality Checks
|
||||
# =============================================================================
|
||||
section "Code Quality"
|
||||
|
||||
# Ruff linting
|
||||
if command -v ./venv/bin/ruff &> /dev/null; then
|
||||
echo -n "Running ruff check... "
|
||||
if ./venv/bin/ruff check src/ frontends/ --quiet 2>/dev/null; then
|
||||
pass "Ruff linting"
|
||||
else
|
||||
fail "Ruff linting (run: ./venv/bin/ruff check src/ frontends/)"
|
||||
fi
|
||||
else
|
||||
skip "Ruff not installed"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 2. Unit Tests (if they exist)
|
||||
# =============================================================================
|
||||
section "Unit Tests"
|
||||
|
||||
if ls tests/test_*.py 1> /dev/null 2>&1; then
|
||||
echo -n "Running pytest... "
|
||||
if ./venv/bin/pytest tests/ -q --tb=no 2>/dev/null; then
|
||||
pass "Pytest unit tests"
|
||||
else
|
||||
fail "Pytest unit tests"
|
||||
fi
|
||||
else
|
||||
skip "No unit tests found (tests/test_*.py)"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 3. Import Tests
|
||||
# =============================================================================
|
||||
section "Import Tests"
|
||||
|
||||
# Test core library import
|
||||
echo -n "Testing stegasoo import... "
|
||||
if ./venv/bin/python -c "from stegasoo import encode, decode; print('OK')" 2>/dev/null | grep -q OK; then
|
||||
pass "Core library import"
|
||||
else
|
||||
fail "Core library import"
|
||||
fi
|
||||
|
||||
# Test DCT support
|
||||
echo -n "Testing DCT support... "
|
||||
if ./venv/bin/python -c "from stegasoo import has_dct_support; assert has_dct_support(), 'No DCT'; print('OK')" 2>/dev/null | grep -q OK; then
|
||||
pass "DCT support available"
|
||||
else
|
||||
fail "DCT support (scipy/jpegio missing?)"
|
||||
fi
|
||||
|
||||
# Test CLI import
|
||||
echo -n "Testing CLI import... "
|
||||
if ./venv/bin/python -c "from stegasoo.cli import main; print('OK')" 2>/dev/null | grep -q OK; then
|
||||
pass "CLI module import"
|
||||
else
|
||||
fail "CLI module import"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 4. Encode/Decode Sanity Test
|
||||
# =============================================================================
|
||||
section "Encode/Decode Test"
|
||||
|
||||
echo -n "Running encode/decode sanity check... "
|
||||
SANITY_RESULT=$(./venv/bin/python << 'EOF' 2>&1
|
||||
import sys
|
||||
sys.path.insert(0, 'src')
|
||||
from stegasoo import encode, decode
|
||||
|
||||
with open('test_data/carrier.jpg', 'rb') as f:
|
||||
carrier = f.read()
|
||||
with open('test_data/ref.jpg', 'rb') as f:
|
||||
ref = f.read()
|
||||
|
||||
# LSB test
|
||||
result = encode(message="sanity test", reference_photo=ref, carrier_image=carrier,
|
||||
passphrase="test", pin="123456", embed_mode="lsb")
|
||||
decoded = decode(stego_image=result.stego_image, reference_photo=ref,
|
||||
passphrase="test", pin="123456", embed_mode="lsb")
|
||||
assert decoded.message == "sanity test", f"LSB mismatch: {decoded.message}"
|
||||
|
||||
# DCT test
|
||||
result = encode(message="dct sanity", reference_photo=ref, carrier_image=carrier,
|
||||
passphrase="dct", pin="654321", embed_mode="dct")
|
||||
decoded = decode(stego_image=result.stego_image, reference_photo=ref,
|
||||
passphrase="dct", pin="654321", embed_mode="dct")
|
||||
assert decoded.message == "dct sanity", f"DCT mismatch: {decoded.message}"
|
||||
|
||||
print("OK")
|
||||
EOF
|
||||
)
|
||||
|
||||
if echo "$SANITY_RESULT" | grep -q "OK"; then
|
||||
pass "Encode/decode sanity (LSB + DCT)"
|
||||
else
|
||||
fail "Encode/decode sanity: $SANITY_RESULT"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 5. Docker Build & Test (optional)
|
||||
# =============================================================================
|
||||
if $INCLUDE_DOCKER; then
|
||||
section "Docker"
|
||||
|
||||
if command -v docker &> /dev/null || command -v sudo &> /dev/null; then
|
||||
DOCKER_CMD="docker"
|
||||
if ! docker info &>/dev/null 2>&1; then
|
||||
DOCKER_CMD="sudo docker"
|
||||
fi
|
||||
|
||||
echo -n "Building Docker image... "
|
||||
if $DOCKER_CMD build -t stegasoo:validate -q . >/dev/null 2>&1; then
|
||||
pass "Docker build"
|
||||
|
||||
# Test container starts
|
||||
echo -n "Testing container startup... "
|
||||
CONTAINER_ID=$($DOCKER_CMD run -d -p 15000:5000 stegasoo:validate 2>/dev/null)
|
||||
sleep 3
|
||||
|
||||
if curl -s -o /dev/null -w "%{http_code}" http://localhost:15000/ 2>/dev/null | grep -qE "200|302"; then
|
||||
pass "Container responds to HTTP"
|
||||
else
|
||||
fail "Container HTTP response"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
$DOCKER_CMD stop "$CONTAINER_ID" >/dev/null 2>&1 || true
|
||||
$DOCKER_CMD rm "$CONTAINER_ID" >/dev/null 2>&1 || true
|
||||
else
|
||||
fail "Docker build"
|
||||
fi
|
||||
|
||||
# Cleanup test image
|
||||
$DOCKER_CMD rmi stegasoo:validate >/dev/null 2>&1 || true
|
||||
else
|
||||
skip "Docker not available"
|
||||
fi
|
||||
else
|
||||
skip "Docker tests (use --docker to enable)"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 6. Pi Smoke Test (optional)
|
||||
# =============================================================================
|
||||
if $INCLUDE_PI; then
|
||||
section "Pi Smoke Test"
|
||||
|
||||
echo -n "Testing SSH connectivity to $PI_USER@$PI_IP... "
|
||||
if ssh -o ConnectTimeout=5 -o BatchMode=yes "$PI_USER@$PI_IP" "echo OK" 2>/dev/null | grep -q OK; then
|
||||
pass "SSH connectivity"
|
||||
|
||||
echo -n "Checking stegasoo service status... "
|
||||
if ssh "$PI_USER@$PI_IP" "systemctl is-active stegasoo" 2>/dev/null | grep -q active; then
|
||||
pass "Stegasoo service running"
|
||||
|
||||
echo -n "Running smoke test on Pi... "
|
||||
SMOKE_RESULT=$(ssh "$PI_USER@$PI_IP" "cd /home/$PI_USER/stegasoo && bash tests/smoke-test.sh --quick 2>&1" || echo "FAILED")
|
||||
if echo "$SMOKE_RESULT" | grep -qE "All tests passed|PASS"; then
|
||||
pass "Pi smoke test"
|
||||
else
|
||||
fail "Pi smoke test"
|
||||
fi
|
||||
else
|
||||
fail "Stegasoo service not running"
|
||||
fi
|
||||
else
|
||||
fail "SSH connectivity to Pi"
|
||||
fi
|
||||
else
|
||||
skip "Pi smoke test (use --pi to enable)"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Summary
|
||||
# =============================================================================
|
||||
echo ""
|
||||
echo -e "${CYAN}━━━ Summary ━━━${NC}"
|
||||
echo ""
|
||||
echo -e "Tests run: ${TESTS_RUN}"
|
||||
echo -e "Passed: ${GREEN}${TESTS_PASSED}${NC}"
|
||||
echo -e "Failed: ${RED}${TESTS_FAILED}${NC}"
|
||||
|
||||
if [ ${#FAILED_TESTS[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
echo -e "${RED}Failed tests:${NC}"
|
||||
for test in "${FAILED_TESTS[@]}"; do
|
||||
echo -e " - $test"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ $TESTS_FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ All validation checks passed!${NC}"
|
||||
echo -e " Ready to tag release ${VERSION}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}✗ Validation failed - fix issues before release${NC}"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user