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 与旧扁平模块
This commit is contained in:
1 parent
d978a13a83
commit
0df07ad0ae
48 files changed
+5813
-6812
No files matched your search
Executable
+111
@@ -0,0 +1,111 @@
|
||||
#!/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
|
||||
Reference in new issue
Block a user