Files
Shuery 0df07ad0ae refactor: 迁移至 bashly CLI 与功能域模块结构
- CLI 定义迁移至 src/bashly.yml(bashly 1.4 生成参数解析),入口收敛为 src/root_command.sh
- 源码按功能域重组:config/storage/integrate/media/pipeline,消除扁平散落
- 新增零依赖测试框架 tests/(123 用例)与 CI 流水线(lint + test + build + 产物一致性)
- build.sh 支持版本单一来源注入与 --check 产物校验;新增 lint.sh(bash -n + shellcheck 零容忍)
- 删除旧单文件 media_organizer.sh 与旧扁平模块
2026-08-14 22:41:28 +08:00

112 lines
3.9 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ============================================================================
# 单元测试运行器(零依赖,纯 bash)
#
# 用法:
# ./tests/run.sh 运行全部用例
# ./tests/run.sh cache 仅运行匹配名字的用例文件(如 cache、filename)
# ./tests/run.sh -v 显示每个用例名称(默认只显示失败与汇总)
#
# 设计:
# - 按 src/modules.list 顺序加载全部模块(跳过 lib/entry.sh,避免触发 main)
# - 每个用例文件在独立子 shell 中运行:全局状态自动隔离、互不污染
# - 用例文件可定义可选 setup()(每个测试前调用)与 teardown()(测试后调用)
# - 断言使用 tests/lib/assert.sh,失败返回非 0,不影响其余用例继续执行
# ============================================================================
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VERBOSE=false
FILTER=""
for a in "$@"; do
case "$a" in
-v) VERBOSE=true ;;
*) FILTER="$a" ;;
esac
done
# 加载全部模块(bashly 源码树):
# - src/lib/main.sh 常量与全局变量最先
# - src/lib/*.sh 纯函数定义库
# - 跳过 src/root_command.sh(bashly 命令文件为顶层代码,仅由 bashly 包装执行)
# 注意:必须在顶层加载(不能包在函数里)——模块中的 declare -A 在函数内
# 会变成函数局部变量,函数返回后关联数组全部丢失。
# shellcheck source=/dev/null
source "$ROOT/src/lib/main.sh"
while IFS= read -r -d '' m; do
# main.sh 已加载(readonly 常量不能重复 source)
[[ "$(basename "$m")" == "main.sh" ]] && continue
# shellcheck source=/dev/null
source "$m"
done < <(find "$ROOT/src/lib" -name '*.sh' -print0 | sort -z)
# 断言库
# shellcheck source=/dev/null
source "$ROOT/tests/lib/assert.sh"
# 测试环境约定:_log 仅作日志输出(不参与业务逻辑),覆盖为 no-op 保持测试输出干净;
# 如需断言日志行为,可在具体用例中临时重新定义。
_log() { return 0; }
pass=0
fail=0
failed_names=()
run_case_file() {
local case_file="$1"
local case_name
case_name="$(basename "$case_file" .sh)"
# 子 shell:全局状态隔离(每个用例文件一组干净状态)
(
set +e
# shellcheck source=/dev/null
source "$case_file"
local fn
for fn in $(compgen -A function | grep '^test_' | sort); do
if declare -F setup >/dev/null; then setup; fi
# 注意:不能在 if 条件中运行 ( set -e; "$fn" )——bash 的条件上下文会
# 抑制子 shell 内的 errexit(多断言测试会漏报)。先执行再取退出码。
# stdout 吞掉(_log 等噪声),stderr 透传(断言失败诊断可见)
(
set -e
"$fn"
) >/dev/null
rc=$?
if ((rc == 0)); then
printf 'PASS\t%s\t%s\n' "$case_name" "$fn"
else
printf 'FAIL\t%s\t%s\n' "$case_name" "$fn"
fi
if declare -F teardown >/dev/null; then teardown; fi
done
)
}
while IFS= read -r -d '' case_file; do
if [[ -n "$FILTER" ]] && [[ "$(basename "$case_file" .sh)" != *"$FILTER"* ]]; then
continue
fi
[[ $VERBOSE == true ]] && echo "── $case_file"
while IFS=$'\t' read -r kind case_name fn; do
if [[ "$kind" == "PASS" ]]; then
pass=$((pass + 1))
[[ $VERBOSE == true ]] && echo " ✅ $case_name/$fn"
elif [[ "$kind" == "FAIL" ]]; then
fail=$((fail + 1))
failed_names+=("$case_name/$fn")
echo " ❌ $case_name/$fn"
fi
done < <(run_case_file "$case_file")
done < <(find "$ROOT/tests/cases" -name '*.sh' -print0 | sort -z)
echo ""
echo "════════════════════════════════════"
echo "结果:$pass 通过,$fail 失败"
if ((fail > 0)); then
printf '失败用例:\n'
for n in "${failed_names[@]}"; do
echo " - $n"
done
exit 1
fi