Test the changes that have been made

This commit is contained in:
Bishoy Ehab
2025-10-18 22:53:52 +03:00
parent 0facd08fa9
commit 1fd328f90a
13 changed files with 383 additions and 260 deletions
+187 -192
View File
@@ -1,15 +1,13 @@
#!/usr/bin/env bash
#
# exp-update-tester.sh - Test suite for update.sh
# exp-update-tester.sh - Test suite for update.sh (sourced subcommand)
#
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
TESTS_PASSED=0
@@ -32,22 +30,22 @@ log_fail() {
((TESTS_FAILED++))
}
log_info() {
echo -e "${YELLOW}[INFO]${NC} $1"
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Setup test environment
setup_test_env() {
local temp_dir
temp_dir=$(mktemp -d -t dotfiles-test.XXXXXX)
cd "$temp_dir" || { echo "Failed to cd to test directory"; return 1; }
git init -q
git config user.email "test@example.com"
git config user.name "Test User"
git commit --allow-empty -m "Initial commit" -q
echo "$temp_dir"
}
@@ -63,10 +61,10 @@ cleanup_test_env() {
run_test() {
local test_name="$1"
local test_func="$2"
# Cleanup before test
cleanup_test_env
# Run the test
if $test_func; then
echo "$test_name passed"
@@ -79,18 +77,18 @@ run_test() {
# Test 1: Script exists and is executable
test_script_exists() {
log_test "Checking if update.sh exists and is executable"
if [[ ! -f "update.sh" ]]; then
log_fail "update.sh not found"
log_test "Checking if install.sh exists and is executable"
if [[ ! -f "install.sh" ]]; then
log_fail "install.sh not found"
return 1
fi
if [[ ! -x "update.sh" ]]; then
log_fail "update.sh is not executable"
if [[ ! -x "install.sh" ]]; then
log_fail "install.sh is not executable"
return 1
fi
log_pass "Script exists and is executable"
return 0
}
@@ -98,8 +96,8 @@ test_script_exists() {
# Test 2: Script has no syntax errors
test_syntax() {
log_test "Checking script syntax"
if bash -n update.sh; then
if bash -n install.sh; then
log_pass "No syntax errors found"
return 0
else
@@ -111,8 +109,8 @@ test_syntax() {
# Test 3: Help option works
test_help_option() {
log_test "Testing --help option"
if ./update.sh --help 2>&1 | grep -q "Usage:"; then
if ./install.sh exp-update --help 2>&1 | grep -qiE "(Usage|Options|exp-update)"; then
log_pass "Help option works"
return 0
else
@@ -124,52 +122,39 @@ test_help_option() {
# Test 4: Test repository structure detection (dots/ prefix)
test_dots_structure() {
log_test "Testing dots/ prefix structure detection"
local test_repo
test_repo=$(setup_test_env)
TEST_DIR="$test_repo"
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
mkdir -p dots/.config/test-app
mkdir -p dots/.local/bin
echo "test config" > dots/.config/test-app/config.conf
git add .
git commit -m "Add dots structure" -q
cat > test_detection.sh << 'EOF'
cat > test_detection.sh << EOF
#!/bin/bash
# Mock logging and style functions/variables
log_info() { :; }
log_warning() { :; }
log_error() { :; }
log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; }
STY_CYAN="" STY_RST="" STY_YELLOW=""
REPO_ROOT="$1"
detect_repo_structure() {
local found_dirs=()
if [[ -d "${REPO_ROOT}/dots/.config" ]]; then
found_dirs+=("dots/.config")
[[ -d "${REPO_ROOT}/dots/.local/bin" ]] && found_dirs+=("dots/.local/bin")
elif [[ -d "${REPO_ROOT}/.config" ]]; then
found_dirs+=(".config")
[[ -d "${REPO_ROOT}/.local/bin" ]] && found_dirs+=(".local/bin")
else
for candidate in "dots/.config" ".config" "dots/.local/bin" ".local/bin"; do
if [[ -d "${REPO_ROOT}/${candidate}" ]]; then
if [[ ! " ${found_dirs[*]} " =~ " ${candidate} " ]]; then
found_dirs+=("${candidate}")
fi
fi
done
fi
if [[ ${#found_dirs[@]} -eq 0 ]]; then
echo "ERROR" >&2
return 1
fi
echo "${found_dirs[@]}"
}
source "$ORIGINAL_DIR/sdata/step/exp-update.sh"
detect_repo_structure
EOF
chmod +x test_detection.sh
result=$(./test_detection.sh "$test_repo")
if [[ "$result" == *"dots/.config"* ]]; then
log_pass "Dots structure detected correctly"
cd "$ORIGINAL_DIR"
@@ -184,52 +169,39 @@ EOF
# Test 5: Test flat structure detection
test_flat_structure() {
log_test "Testing flat structure detection"
local test_repo
test_repo=$(setup_test_env)
TEST_DIR="$test_repo"
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
mkdir -p .config/test-app
mkdir -p .local/bin
echo "test config" > .config/test-app/config.conf
git add .
git commit -m "Add flat structure" -q
cat > test_detection.sh << 'EOF'
cat > test_detection.sh << EOF
#!/bin/bash
# Mock logging and style functions/variables
log_info() { :; }
log_warning() { :; }
log_error() { :; }
log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; }
STY_CYAN="" STY_RST="" STY_YELLOW=""
REPO_ROOT="$1"
detect_repo_structure() {
local found_dirs=()
if [[ -d "${REPO_ROOT}/dots/.config" ]]; then
found_dirs+=("dots/.config")
[[ -d "${REPO_ROOT}/dots/.local/bin" ]] && found_dirs+=("dots/.local/bin")
elif [[ -d "${REPO_ROOT}/.config" ]]; then
found_dirs+=(".config")
[[ -d "${REPO_ROOT}/.local/bin" ]] && found_dirs+=(".local/bin")
else
for candidate in "dots/.config" ".config" "dots/.local/bin" ".local/bin"; do
if [[ -d "${REPO_ROOT}/${candidate}" ]]; then
if [[ ! " ${found_dirs[*]} " =~ " ${candidate} " ]]; then
found_dirs+=("${candidate}")
fi
fi
done
fi
if [[ ${#found_dirs[@]} -eq 0 ]]; then
echo "ERROR" >&2
return 1
fi
echo "${found_dirs[@]}"
}
source "$ORIGINAL_DIR/sdata/step/exp-update.sh"
detect_repo_structure
EOF
chmod +x test_detection.sh
result=$(./test_detection.sh "$test_repo")
if [[ "$result" == *".config"* ]] && [[ "$result" != *"dots/"* ]]; then
log_pass "Flat structure detected correctly"
cd "$ORIGINAL_DIR"
@@ -283,47 +255,22 @@ EOF
mkdir -p .config
mkdir -p secrets
cat > test_ignore.sh << 'EOF'
cat > test_ignore.sh << EOF
#!/bin/bash
# Mock logging and style functions/variables
log_info() { :; }
log_warning() { :; }
log_error() { :; }
log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; }
STY_CYAN="" STY_RST="" STY_YELLOW=""
REPO_ROOT="$1"
UPDATE_IGNORE_FILE="${REPO_ROOT}/.updateignore"
HOME_UPDATE_IGNORE_FILE="/dev/null"
should_ignore() {
local file_path="$1"
local relative_path="${file_path#$HOME/}"
local repo_relative=""
if [[ "$file_path" == "$REPO_ROOT"* ]]; then
repo_relative="${file_path#$REPO_ROOT/}"
fi
for ignore_file in "$UPDATE_IGNORE_FILE" "$HOME_UPDATE_IGNORE_FILE"; do
if [[ -f "$ignore_file" ]]; then
while IFS= read -r pattern || [[ -n "$pattern" ]]; do
[[ -z "$pattern" || "$pattern" =~ ^[[:space:]]*# ]] && continue
pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -z "$pattern" ]] && continue
if [[ "$relative_path" == "$pattern" ]] || [[ "$repo_relative" == "$pattern" ]]; then
return 0
fi
if [[ "$relative_path" == $pattern ]] || [[ "$repo_relative" == $pattern ]]; then
return 0
fi
if [[ "$pattern" == */ ]]; then
local dir_pattern="${pattern%/}"
if [[ "$relative_path" == "$dir_pattern"/* ]] || [[ "$repo_relative" == "$dir_pattern"/* ]]; then
return 0
fi
fi
if [[ "$file_path" == *"$pattern"* ]] || [[ "$relative_path" == *"$pattern"* ]]; then
return 0
fi
done <"$ignore_file"
fi
done
return 1
}
# Source the production script to use the real should_ignore function
source "$ORIGINAL_DIR/sdata/step/exp-update.sh"
test_cases=(
"$REPO_ROOT/app.log:0"
@@ -335,7 +282,7 @@ test_cases=(
all_passed=true
for test_case in "${test_cases[@]}"; do
IFS=':' read -r file expected <<< "$test_case"
IFS=":" read -r file expected <<< "$test_case"
mkdir -p "$(dirname "$file")"
touch "$file"
@@ -376,16 +323,22 @@ EOF
# Test 8: Test safe_read security - COMPLETELY NON-INTERACTIVE
test_safe_read_security() {
log_test "Testing safe_read uses secure assignment (printf -v)"
# Check that safe_read uses printf -v and not eval
if grep -A 10 "safe_read()" update.sh | grep -q "printf -v.*varname"; then
log_pass "safe_read uses secure printf -v assignment"
return 0
elif grep -A 10 "safe_read()" update.sh | grep -q "eval.*varname"; then
log_fail "safe_read uses vulnerable eval assignment"
local awk_script='/^safe_read() \{/,/^\}/'
local safe_read_function
safe_read_function=$(awk "$awk_script" "$ORIGINAL_DIR/sdata/step/exp-update.sh")
if [[ -z "$safe_read_function" ]]; then
log_fail "Could not find safe_read function"
return 1
fi
# Check for secure printf -v assignment and absence of eval
if echo "$safe_read_function" | grep -q "printf -v" && ! echo "$safe_read_function" | grep -q "eval"; then
log_pass "safe_read uses secure printf -v assignment and no eval"
return 0
else
log_fail "Cannot determine safe_read assignment method"
log_fail "safe_read does not use secure assignment or contains eval"
return 1
fi
}
@@ -393,25 +346,25 @@ test_safe_read_security() {
# Test 9: Test dry-run mode
test_dry_run() {
log_test "Testing dry-run mode"
local test_repo
test_repo=$(setup_test_env)
TEST_DIR="$test_repo"
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
mkdir -p dots/.config/test-app
echo "repo config" > dots/.config/test-app/config.conf
# Copy necessary files for install.sh to run
cp "$ORIGINAL_DIR/install.sh" .
cp -r "$ORIGINAL_DIR/sdata" .
cp -r "$ORIGINAL_DIR/dots" .
chmod +x install.sh
git add .
git commit -m "Add test config" -q
cp "$ORIGINAL_DIR/update.sh" .
chmod +x update.sh
# Use printf to pipe responses automatically
printf "y\ny\n" | ./update.sh -n --skip-notice 2>&1 | tee dry_run_output.txt
# Use non-interactive mode and check for DRY-RUN marker
./install.sh exp-update -n --skip-notice --non-interactive 2>&1 | tee dry_run_output.txt
if grep -q "DRY-RUN" dry_run_output.txt; then
log_pass "Dry-run mode detected in output"
else
@@ -419,7 +372,7 @@ test_dry_run() {
cd "$ORIGINAL_DIR"
return 1
fi
if [[ ! -f "${HOME}/.config/test-app/config.conf" ]]; then
log_pass "No files created in home during dry-run"
else
@@ -428,7 +381,7 @@ test_dry_run() {
cd "$ORIGINAL_DIR"
return 1
fi
cd "$ORIGINAL_DIR"
return 0
}
@@ -436,20 +389,20 @@ test_dry_run() {
# Test 10: Test command-line flags
test_flags() {
log_test "Testing command-line flags"
# Only test non-interactive flags
local flags=("-h" "--help")
local all_passed=true
for flag in "${flags[@]}"; do
if ./update.sh "$flag" 2>&1 | grep -q -E "(Usage|help)"; then
log_info "$flag recognized"
if ./install.sh exp-update "$flag" 2>&1 | grep -qiE "(Usage|Options|exp-update)"; then
log_test "$flag recognized"
else
log_info "$flag not recognized"
log_test "$flag not recognized"
all_passed=false
fi
done
if [[ "$all_passed" == true ]]; then
log_pass "Help flags recognized correctly"
return 0
@@ -464,11 +417,11 @@ test_shellcheck() {
log_test "Running shellcheck (if available)"
if ! command -v shellcheck &>/dev/null; then
log_info "shellcheck not found, skipping static analysis"
log_test "shellcheck not found, skipping static analysis"
return 0
fi
if shellcheck -e SC1090,SC1091,SC2148,SC2034,SC2155,SC2164 update.sh; then
if shellcheck -e SC1090,SC1091,SC2148,SC2034,SC2155,SC2164 install.sh; then
log_pass "shellcheck passed"
return 0
else
@@ -477,45 +430,87 @@ test_shellcheck() {
fi
}
# Test 12: Test fresh clone scenario
test_fresh_clone() {
log_test "Testing fresh clone scenario"
# Test 13: Test ** substring ignore patterns
test_substring_ignore_patterns() {
log_test "Testing ** substring ignore pattern matching"
local test_repo
test_repo=$(setup_test_env)
TEST_DIR="$test_repo"
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
mkdir -p .config/test-app
echo "config" > .config/test-app/settings.conf
cat > test_fresh_clone.sh << 'EOF'
#!/bin/bash
has_new_commits() {
if git rev-parse --verify HEAD@{1} &>/dev/null; then
[[ "$(git rev-parse HEAD)" != "$(git rev-parse HEAD@{1})" ]]
else
return 0
fi
}
if has_new_commits; then
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
cat > .updateignore << 'EOF'
**temp**
**backup**
**test**
EOF
mkdir -p .config/test-app
mkdir -p temp-backup-dir
mkdir -p .local/share/test-temp
mkdir -p .config/temp-file
cat > test_substring_ignore.sh << EOF
#!/bin/bash
# Mock logging and style functions/variables
log_info() { :; }
log_warning() { :; }
log_error() { :; }
log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; }
STY_CYAN="" STY_RST="" STY_YELLOW=""
REPO_ROOT="$1"
UPDATE_IGNORE_FILE="${REPO_ROOT}/.updateignore"
HOME_UPDATE_IGNORE_FILE="/dev/null"
# Source the production script to use the real should_ignore function
source "$ORIGINAL_DIR/sdata/step/exp-update.sh"
test_cases=(
"$REPO_ROOT/temp-backup-dir/file:0"
"$REPO_ROOT/.config/test-app/temp.conf:0"
"$REPO_ROOT/.local/share/test-temp/data:0"
"$REPO_ROOT/.config/temp-file/config:0"
"$REPO_ROOT/normal-config:1"
)
all_passed=true
for test_case in "${test_cases[@]}"; do
IFS=":" read -r file expected <<< "$test_case"
mkdir -p "$(dirname "$file")"
touch "$file"
if should_ignore "$file"; then
result=0
else
result=1
fi
if [[ $result -ne $expected ]]; then
echo "FAIL: $file (expected: $expected, got: $result)"
all_passed=false
fi
done
if [[ "$all_passed" == true ]]; then
echo "PASS"
else
echo "FAIL"
fi
EOF
chmod +x test_fresh_clone.sh
result=$(./test_fresh_clone.sh)
chmod +x test_substring_ignore.sh
result=$(./test_substring_ignore.sh "$test_repo")
if [[ "$result" == "PASS" ]]; then
log_pass "Fresh clone scenario handled correctly"
log_pass "** substring ignore patterns work correctly"
cd "$ORIGINAL_DIR"
return 0
else
log_fail "Fresh clone scenario not handled properly"
log_fail "** substring ignore patterns failed"
echo "$result"
cd "$ORIGINAL_DIR"
return 1
fi
@@ -524,32 +519,32 @@ EOF
# Main test runner
main() {
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE} Update.sh Test Suite${NC}"
echo -e "${BLUE} Update.sh Test Suite (Sourced Subcommand)${NC}"
echo -e "${BLUE}================================${NC}\n"
if [[ ! -f "update.sh" ]]; then
log_error "Please run this test from the directory containing update.sh"
if [[ ! -f "install.sh" ]]; then
log_error "Please run this test from the directory containing install.sh"
exit 1
fi
chmod +x update.sh 2>/dev/null || true
chmod +x install.sh 2>/dev/null || true
# Define tests
tests=(
"test_script_exists"
"test_syntax"
"test_syntax"
"test_help_option"
"test_dots_structure"
"test_flat_structure"
"test_dots_mapping"
"test_ignore_patterns"
"test_substring_ignore_patterns"
"test_safe_read_security"
"test_dry_run"
"test_flags"
"test_shellcheck"
"test_fresh_clone"
)
# Run tests
for test in "${tests[@]}"; do
if $test; then
@@ -559,7 +554,7 @@ main() {
fi
echo
done
# Summary
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE} Test Summary${NC}"
@@ -567,7 +562,7 @@ main() {
echo -e "${GREEN}Passed: $TESTS_PASSED${NC}"
echo -e "${RED}Failed: $TESTS_FAILED${NC}"
echo -e "${BLUE}Total: ${#tests[@]}${NC}\n"
if [[ $TESTS_FAILED -eq 0 ]]; then
echo -e "${GREEN}All tests passed! 🎉${NC}\n"
exit 0
@@ -581,7 +576,7 @@ main() {
cleanup() {
echo "Cleaning up test files..."
cleanup_test_env
rm -f test_detection.sh test_ignore.sh test_safe_read.sh test_fresh_clone.sh dry_run_output.txt 2>/dev/null || true
rm -f test_detection.sh test_ignore.sh test_safe_read.sh test_fresh_clone.sh test_substring_ignore.sh dry_run_output.txt 2>/dev/null || true
rm -rf "${HOME}/.config/test-app" 2>/dev/null || true
}