mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 18:34:46 +00:00
Automated test optimization increased to 132
This commit is contained in:
63
.github/workflows/quality.yml
vendored
Normal file
63
.github/workflows/quality.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Quality
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
shell-quality:
|
||||
name: Code Quality
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache Homebrew
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/Library/Caches/Homebrew
|
||||
/usr/local/Cellar/shfmt
|
||||
/usr/local/Cellar/shellcheck
|
||||
key: ${{ runner.os }}-brew-quality-${{ hashFiles('**/Brewfile') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-brew-quality-
|
||||
|
||||
- name: Install tools
|
||||
run: brew install shfmt shellcheck
|
||||
|
||||
- name: Format check
|
||||
run: |
|
||||
echo "Checking shell script formatting..."
|
||||
./scripts/format.sh
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "Code formatting issues found:"
|
||||
git diff
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ All scripts properly formatted"
|
||||
|
||||
- name: ShellCheck
|
||||
run: |
|
||||
echo "Running ShellCheck on all shell scripts..."
|
||||
shellcheck mole
|
||||
shellcheck bin/*.sh
|
||||
find lib -name "*.sh" -exec shellcheck {} +
|
||||
echo "✓ ShellCheck passed"
|
||||
|
||||
- name: Syntax check
|
||||
run: |
|
||||
echo "Checking Bash syntax..."
|
||||
bash -n mole
|
||||
for script in bin/*.sh; do
|
||||
bash -n "$script"
|
||||
done
|
||||
find lib -name "*.sh" | while read -r script; do
|
||||
bash -n "$script"
|
||||
done
|
||||
echo "✓ All scripts have valid syntax"
|
||||
55
.github/workflows/shell-quality-checks.yml
vendored
55
.github/workflows/shell-quality-checks.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: Shell Script Quality Checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
shell-quality-checks:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24.6"
|
||||
cache: true
|
||||
|
||||
- name: Cache Homebrew packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/Library/Caches/Homebrew
|
||||
/usr/local/Cellar/bats-core
|
||||
/usr/local/Cellar/shfmt
|
||||
/usr/local/Cellar/shellcheck
|
||||
key: ${{ runner.os }}-brew-v1
|
||||
restore-keys: |
|
||||
${{ runner.os }}-brew-
|
||||
|
||||
- name: Install shell linting and testing tools
|
||||
run: brew install bats-core shfmt shellcheck
|
||||
|
||||
- name: Auto-format shell scripts with shfmt
|
||||
run: ./scripts/format.sh
|
||||
|
||||
- name: Run shellcheck linter and bats tests
|
||||
run: |
|
||||
./scripts/check.sh
|
||||
echo "✓ All quality checks passed"
|
||||
|
||||
- name: Run all bats tests with summary
|
||||
run: |
|
||||
echo "Running all test suites..."
|
||||
bats tests/*.bats --formatter tap
|
||||
echo ""
|
||||
echo "Test summary:"
|
||||
echo " Total test files: $(ls tests/*.bats | wc -l | tr -d ' ')"
|
||||
echo " Total tests: $(grep -c "^@test" tests/*.bats | awk -F: '{sum+=$2} END {print sum}')"
|
||||
140
.github/workflows/tests.yml
vendored
Normal file
140
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
pull_request:
|
||||
branches: [main, dev]
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit Tests
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install bats
|
||||
run: brew install bats-core
|
||||
|
||||
- name: Run all test suites
|
||||
run: |
|
||||
echo "Running all test suites..."
|
||||
bats tests/*.bats --formatter tap
|
||||
echo ""
|
||||
echo "Test summary:"
|
||||
echo " Total test files: $(ls tests/*.bats | wc -l | tr -d ' ')"
|
||||
echo " Total tests: $(grep -c "^@test" tests/*.bats | awk -F: '{sum+=$2} END {print sum}')"
|
||||
echo "✓ All tests passed"
|
||||
|
||||
go-tests:
|
||||
name: Go Tests
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
|
||||
- name: Build Go binaries
|
||||
run: |
|
||||
echo "Building Go binaries..."
|
||||
go build ./...
|
||||
echo "✓ Build successful"
|
||||
|
||||
- name: Run go vet
|
||||
run: |
|
||||
echo "Running go vet..."
|
||||
go vet ./cmd/...
|
||||
echo "✓ Vet passed"
|
||||
|
||||
- name: Check formatting
|
||||
run: |
|
||||
echo "Checking Go formatting..."
|
||||
if [ -n "$(gofmt -l ./cmd)" ]; then
|
||||
echo "Go code is not formatted:"
|
||||
gofmt -d ./cmd
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Go code properly formatted"
|
||||
|
||||
integration-tests:
|
||||
name: Integration Tests
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install coreutils
|
||||
|
||||
- name: Test module loading
|
||||
run: |
|
||||
echo "Testing module loading..."
|
||||
bash -c 'source lib/core/common.sh && echo "✓ Modules loaded successfully"'
|
||||
|
||||
- name: Test clean --dry-run
|
||||
run: |
|
||||
echo "Testing clean --dry-run..."
|
||||
./bin/clean.sh --dry-run
|
||||
echo "✓ Clean dry-run completed"
|
||||
|
||||
- name: Test installation
|
||||
run: |
|
||||
echo "Testing installation script..."
|
||||
./install.sh --prefix /tmp/mole-test
|
||||
test -f /tmp/mole-test/mole
|
||||
echo "✓ Installation successful"
|
||||
|
||||
compatibility:
|
||||
name: macOS Compatibility
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-13, macos-14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Test on ${{ matrix.os }}
|
||||
run: |
|
||||
echo "Testing on ${{ matrix.os }}..."
|
||||
bash -n mole
|
||||
source lib/core/common.sh
|
||||
echo "✓ Successfully loaded on ${{ matrix.os }}"
|
||||
|
||||
security:
|
||||
name: Security Checks
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for unsafe rm usage
|
||||
run: |
|
||||
echo "Checking for unsafe rm patterns..."
|
||||
if grep -r "rm -rf" --include="*.sh" lib/ | grep -v "safe_remove\|validate_path\|# "; then
|
||||
echo "✗ Unsafe rm -rf usage found"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ No unsafe rm usage found"
|
||||
|
||||
- name: Verify app protection
|
||||
run: |
|
||||
echo "Verifying critical file protection..."
|
||||
bash -c '
|
||||
source lib/core/common.sh
|
||||
if should_protect_from_uninstall "com.apple.Safari"; then
|
||||
echo "✓ Safari is protected"
|
||||
else
|
||||
echo "✗ Safari protection failed"
|
||||
exit 1
|
||||
fi
|
||||
'
|
||||
|
||||
- name: Check for secrets
|
||||
run: |
|
||||
echo "Checking for hardcoded secrets..."
|
||||
if grep -r "password\|secret\|api_key" --include="*.sh" . | grep -v "# \|test"; then
|
||||
echo "✗ Potential secrets found"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ No secrets found"
|
||||
169
tests/regression_bugs.bats
Normal file
169
tests/regression_bugs.bats
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bats
|
||||
# Regression tests for previously fixed bugs
|
||||
# Ensures历史bug不再复现
|
||||
|
||||
setup() {
|
||||
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
|
||||
export PROJECT_ROOT
|
||||
export HOME="$BATS_TEST_TMPDIR/home"
|
||||
mkdir -p "$HOME/.config/mole"
|
||||
}
|
||||
|
||||
# =================================================================
|
||||
# 退出问题回归测试 (bb21bb1, 4b6c436, d75c34d)
|
||||
# =================================================================
|
||||
|
||||
@test "find with non-existent directory doesn't cause script exit (pipefail bug)" {
|
||||
# 这个模式曾导致 lib/clean/user.sh 在 pipefail 模式下意外退出
|
||||
result=$(bash -c '
|
||||
set -euo pipefail
|
||||
find /non/existent/dir -name "*.cache" 2>/dev/null || true
|
||||
echo "survived"
|
||||
')
|
||||
[[ "$result" == "survived" ]]
|
||||
}
|
||||
|
||||
@test "browser directory check pattern is safe when directories don't exist" {
|
||||
# 修复模式:先检查目录是否存在
|
||||
result=$(bash -c '
|
||||
set -euo pipefail
|
||||
search_dirs=()
|
||||
[[ -d "/non/existent/chrome" ]] && search_dirs+=("/non/existent/chrome")
|
||||
[[ -d "/tmp" ]] && search_dirs+=("/tmp")
|
||||
|
||||
if [[ ${#search_dirs[@]} -gt 0 ]]; then
|
||||
find "${search_dirs[@]}" -maxdepth 1 -type f 2>/dev/null || true
|
||||
fi
|
||||
echo "survived"
|
||||
')
|
||||
[[ "$result" == "survived" ]]
|
||||
}
|
||||
|
||||
@test "empty array doesn't cause unbound variable error" {
|
||||
result=$(bash -c '
|
||||
set -euo pipefail
|
||||
search_dirs=()
|
||||
|
||||
# 这不应该执行且不应该报错
|
||||
if [[ ${#search_dirs[@]} -gt 0 ]]; then
|
||||
echo "should not reach here"
|
||||
fi
|
||||
echo "survived"
|
||||
')
|
||||
[[ "$result" == "survived" ]]
|
||||
}
|
||||
|
||||
# ===============================================================
|
||||
# 更新检查回归测试 (260254f, b61b3f7, 2a64cae, 7a9c946)
|
||||
# ===============================================================
|
||||
|
||||
@test "version comparison works correctly" {
|
||||
result=$(bash -c '
|
||||
v1="1.11.8"
|
||||
v2="1.11.9"
|
||||
if [[ "$(printf "%s\n" "$v1" "$v2" | sort -V | head -1)" == "$v1" && "$v1" != "$v2" ]]; then
|
||||
echo "update_needed"
|
||||
fi
|
||||
')
|
||||
[[ "$result" == "update_needed" ]]
|
||||
}
|
||||
|
||||
@test "version comparison with same versions" {
|
||||
result=$(bash -c '
|
||||
v1="1.11.8"
|
||||
v2="1.11.8"
|
||||
if [[ "$(printf "%s\n" "$v1" "$v2" | sort -V | head -1)" == "$v1" && "$v1" != "$v2" ]]; then
|
||||
echo "update_needed"
|
||||
else
|
||||
echo "up_to_date"
|
||||
fi
|
||||
')
|
||||
[[ "$result" == "up_to_date" ]]
|
||||
}
|
||||
|
||||
@test "version prefix v/V is stripped correctly" {
|
||||
result=$(bash -c '
|
||||
version="v1.11.9"
|
||||
clean=${version#v}
|
||||
clean=${clean#V}
|
||||
echo "$clean"
|
||||
')
|
||||
[[ "$result" == "1.11.9" ]]
|
||||
}
|
||||
|
||||
@test "network timeout prevents hanging (simulated)" {
|
||||
# curl 超时参数应该生效
|
||||
# shellcheck disable=SC2016
|
||||
result=$(timeout 5 bash -c '
|
||||
result=$(curl -fsSL --connect-timeout 1 --max-time 2 "http://192.0.2.1:12345/test" 2>/dev/null || echo "failed")
|
||||
if [[ "$result" == "failed" ]]; then
|
||||
echo "timeout_works"
|
||||
fi
|
||||
')
|
||||
[[ "$result" == "timeout_works" ]]
|
||||
}
|
||||
|
||||
@test "empty version string is handled gracefully" {
|
||||
result=$(bash -c '
|
||||
latest=""
|
||||
if [[ -z "$latest" ]]; then
|
||||
echo "handled"
|
||||
fi
|
||||
')
|
||||
[[ "$result" == "handled" ]]
|
||||
}
|
||||
|
||||
# ===============================================================
|
||||
# Pipefail 模式安全模式测试
|
||||
# ===============================================================
|
||||
|
||||
@test "grep with no match doesn't cause exit in pipefail mode" {
|
||||
result=$(bash -c '
|
||||
set -euo pipefail
|
||||
echo "test" | grep "nonexistent" || true
|
||||
echo "survived"
|
||||
')
|
||||
[[ "$result" == "survived" ]]
|
||||
}
|
||||
|
||||
@test "command substitution failure is handled with || true" {
|
||||
result=$(bash -c '
|
||||
set -euo pipefail
|
||||
output=$(false) || true
|
||||
echo "survived"
|
||||
')
|
||||
[[ "$result" == "survived" ]]
|
||||
}
|
||||
|
||||
@test "arithmetic on zero doesn't cause exit" {
|
||||
result=$(bash -c '
|
||||
set -euo pipefail
|
||||
count=0
|
||||
((count++)) || true
|
||||
echo "$count"
|
||||
')
|
||||
[[ "$result" == "1" ]]
|
||||
}
|
||||
|
||||
# ===============================================================
|
||||
# 实际场景回归测试
|
||||
# ===============================================================
|
||||
|
||||
@test "safe_remove pattern doesn't fail on non-existent path" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
safe_remove '$HOME/non/existent/path' true > /dev/null 2>&1 || true
|
||||
echo 'survived'
|
||||
")
|
||||
[[ "$result" == "survived" ]]
|
||||
}
|
||||
|
||||
@test "module loading doesn't fail" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
echo 'loaded'
|
||||
")
|
||||
[[ "$result" == "loaded" ]]
|
||||
}
|
||||
230
tests/timeout_tests.bats
Normal file
230
tests/timeout_tests.bats
Normal file
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env bats
|
||||
# Timeout functionality tests
|
||||
# Tests for lib/core/timeout.sh
|
||||
|
||||
setup() {
|
||||
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
|
||||
export PROJECT_ROOT
|
||||
export MO_DEBUG=0 # Disable debug output for cleaner tests
|
||||
}
|
||||
|
||||
# =================================================================
|
||||
# Basic Timeout Functionality
|
||||
# =================================================================
|
||||
|
||||
@test "run_with_timeout: command completes before timeout" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 5 echo 'success'
|
||||
")
|
||||
[[ "$result" == "success" ]]
|
||||
}
|
||||
|
||||
@test "run_with_timeout: zero timeout runs command normally" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 0 echo 'no_timeout'
|
||||
")
|
||||
[[ "$result" == "no_timeout" ]]
|
||||
}
|
||||
|
||||
@test "run_with_timeout: invalid timeout runs command normally" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout invalid echo 'no_timeout'
|
||||
")
|
||||
[[ "$result" == "no_timeout" ]]
|
||||
}
|
||||
|
||||
@test "run_with_timeout: negative timeout runs command normally" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout -5 echo 'no_timeout'
|
||||
")
|
||||
[[ "$result" == "no_timeout" ]]
|
||||
}
|
||||
|
||||
# =================================================================
|
||||
# Exit Code Handling
|
||||
# =================================================================
|
||||
|
||||
@test "run_with_timeout: preserves command exit code on success" {
|
||||
bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 5 true
|
||||
"
|
||||
exit_code=$?
|
||||
[[ $exit_code -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "run_with_timeout: preserves command exit code on failure" {
|
||||
set +e
|
||||
bash -c "
|
||||
set +e # Don't exit on error
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 5 false
|
||||
exit \$?
|
||||
"
|
||||
exit_code=$?
|
||||
set -e
|
||||
[[ $exit_code -eq 1 ]]
|
||||
}
|
||||
|
||||
@test "run_with_timeout: returns 124 on timeout (if using gtimeout)" {
|
||||
# This test only passes if gtimeout/timeout is available
|
||||
# Skip if using shell fallback (can't guarantee exit code 124 in all cases)
|
||||
if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then
|
||||
skip "gtimeout/timeout not available"
|
||||
fi
|
||||
|
||||
set +e
|
||||
bash -c "
|
||||
set +e
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 1 sleep 10
|
||||
exit \$?
|
||||
"
|
||||
exit_code=$?
|
||||
set -e
|
||||
[[ $exit_code -eq 124 ]]
|
||||
}
|
||||
|
||||
# =================================================================
|
||||
# Timeout Behavior
|
||||
# =================================================================
|
||||
|
||||
@test "run_with_timeout: kills long-running command" {
|
||||
# Command should be killed after 2 seconds
|
||||
start_time=$(date +%s)
|
||||
set +e
|
||||
bash -c "
|
||||
set +e
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 2 sleep 30
|
||||
" >/dev/null 2>&1
|
||||
set -e
|
||||
end_time=$(date +%s)
|
||||
duration=$((end_time - start_time))
|
||||
|
||||
# Should complete in ~2 seconds, not 30
|
||||
# Allow some margin (up to 5 seconds for slow systems)
|
||||
[[ $duration -lt 10 ]]
|
||||
}
|
||||
|
||||
@test "run_with_timeout: handles fast-completing commands" {
|
||||
# Fast command should complete immediately
|
||||
start_time=$(date +%s)
|
||||
bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 10 echo 'fast'
|
||||
" >/dev/null 2>&1
|
||||
end_time=$(date +%s)
|
||||
duration=$((end_time - start_time))
|
||||
|
||||
# Should complete in ~0 seconds
|
||||
[[ $duration -lt 3 ]]
|
||||
}
|
||||
|
||||
# =================================================================
|
||||
# Pipefail Compatibility
|
||||
# =================================================================
|
||||
|
||||
@test "run_with_timeout: works in pipefail mode" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 5 echo 'pipefail_test'
|
||||
")
|
||||
[[ "$result" == "pipefail_test" ]]
|
||||
}
|
||||
|
||||
@test "run_with_timeout: doesn't cause unintended exits" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 5 true || true
|
||||
echo 'survived'
|
||||
")
|
||||
[[ "$result" == "survived" ]]
|
||||
}
|
||||
|
||||
# =================================================================
|
||||
# Command Arguments
|
||||
# =================================================================
|
||||
|
||||
@test "run_with_timeout: handles commands with arguments" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 5 echo 'arg1' 'arg2' 'arg3'
|
||||
")
|
||||
[[ "$result" == "arg1 arg2 arg3" ]]
|
||||
}
|
||||
|
||||
@test "run_with_timeout: handles commands with spaces in arguments" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 5 echo 'hello world'
|
||||
")
|
||||
[[ "$result" == "hello world" ]]
|
||||
}
|
||||
|
||||
# =================================================================
|
||||
# Debug Logging
|
||||
# =================================================================
|
||||
|
||||
@test "run_with_timeout: debug logging when MO_DEBUG=1" {
|
||||
output=$(bash -c "
|
||||
set -euo pipefail
|
||||
export MO_DEBUG=1
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
run_with_timeout 5 echo 'test' 2>&1
|
||||
")
|
||||
# Should contain debug output
|
||||
[[ "$output" =~ TIMEOUT ]]
|
||||
}
|
||||
|
||||
@test "run_with_timeout: no debug logging when MO_DEBUG=0" {
|
||||
# When MO_DEBUG=0, no debug messages should appear during function execution
|
||||
# (Initialization messages may appear if module is loaded for first time)
|
||||
output=$(bash -c "
|
||||
set -euo pipefail
|
||||
export MO_DEBUG=0
|
||||
unset MO_TIMEOUT_INITIALIZED # Force re-initialization
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
# Capture only the function call output, not initialization
|
||||
run_with_timeout 5 echo 'test'
|
||||
" 2>/dev/null) # Discard stderr (initialization messages)
|
||||
# Should only have command output
|
||||
[[ "$output" == "test" ]]
|
||||
}
|
||||
|
||||
# =================================================================
|
||||
# Module Loading
|
||||
# =================================================================
|
||||
|
||||
@test "timeout.sh: prevents multiple sourcing" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
echo 'loaded'
|
||||
")
|
||||
[[ "$result" == "loaded" ]]
|
||||
}
|
||||
|
||||
@test "timeout.sh: sets MOLE_TIMEOUT_LOADED flag" {
|
||||
result=$(bash -c "
|
||||
set -euo pipefail
|
||||
source '$PROJECT_ROOT/lib/core/timeout.sh'
|
||||
echo \"\$MOLE_TIMEOUT_LOADED\"
|
||||
")
|
||||
[[ "$result" == "1" ]]
|
||||
}
|
||||
@@ -4,7 +4,10 @@ setup_file() {
|
||||
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
|
||||
export PROJECT_ROOT
|
||||
|
||||
ORIGINAL_HOME="${HOME:-}"
|
||||
ORIGINAL_HOME="${BATS_TMPDIR:-}" # Use BATS_TMPDIR as original HOME if set by bats
|
||||
if [[ -z "$ORIGINAL_HOME" ]]; then
|
||||
ORIGINAL_HOME="${HOME:-}"
|
||||
fi
|
||||
export ORIGINAL_HOME
|
||||
|
||||
HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-uninstall-home.XXXXXX")"
|
||||
@@ -63,7 +66,9 @@ EOF
|
||||
HOME="$HOME" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
files="$(printf '%s\n%s\n' "$HOME/sized/file1" "$HOME/sized/file2")"
|
||||
files="$(printf '%s
|
||||
%s
|
||||
' "$HOME/sized/file1" "$HOME/sized/file2")"
|
||||
calculate_total_size "$files"
|
||||
EOF
|
||||
)"
|
||||
@@ -91,10 +96,10 @@ show_cursor() { :; }
|
||||
remove_apps_from_dock() { :; }
|
||||
pgrep() { return 1; }
|
||||
pkill() { return 0; }
|
||||
sudo() { return 0; }
|
||||
sudo() { return 0; } # Mock sudo command
|
||||
|
||||
app_bundle="$HOME/Applications/TestApp.app"
|
||||
mkdir -p "$app_bundle"
|
||||
mkdir -p "$app_bundle" # Ensure this is created in the temp HOME
|
||||
|
||||
related="$(find_app_files "com.example.TestApp" "TestApp")"
|
||||
encoded_related=$(printf '%s' "$related" | base64 | tr -d '\n')
|
||||
@@ -105,8 +110,10 @@ files_cleaned=0
|
||||
total_items=0
|
||||
total_size_cleaned=0
|
||||
|
||||
printf '\n' | batch_uninstall_applications >/dev/null
|
||||
# Use the actual bash function directly, don't pipe printf as that complicates stdin
|
||||
batch_uninstall_applications
|
||||
|
||||
# Verify cleanup
|
||||
[[ ! -d "$app_bundle" ]] || exit 1
|
||||
[[ ! -d "$HOME/Library/Application Support/TestApp" ]] || exit 1
|
||||
[[ ! -d "$HOME/Library/Caches/TestApp" ]] || exit 1
|
||||
@@ -116,6 +123,21 @@ EOF
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "safe_remove can remove a simple directory" {
|
||||
mkdir -p "$HOME/test_dir"
|
||||
touch "$HOME/test_dir/file.txt"
|
||||
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
|
||||
safe_remove "$HOME/test_dir"
|
||||
[[ ! -d "$HOME/test_dir" ]] || exit 1
|
||||
EOF
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
|
||||
@test "decode_file_list validates base64 encoding" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
@@ -123,7 +145,8 @@ source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall/batch.sh"
|
||||
|
||||
# Valid base64 encoded path list
|
||||
valid_data=$(printf '/path/one\n/path/two' | base64)
|
||||
valid_data=$(printf '/path/one
|
||||
/path/two' | base64)
|
||||
result=$(decode_file_list "$valid_data" "TestApp")
|
||||
[[ -n "$result" ]] || exit 1
|
||||
EOF
|
||||
@@ -184,4 +207,4 @@ fi
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user