#!/usr/bin/env bash # 跑全仓库的单元测试(纯逻辑:不需要硬件、不需要 Gazebo、不发运动指令)。 # # 为什么要有这个脚本,而不是让人手敲 pytest # ---------------------------------------- # 测试都按**包名**导入(arm_application.* / arm_sim_gazebo.* / arm_kinematics.*), # 所以 PYTHONPATH 必须同时包含 src/ 下所有包目录。手敲很容易漏一个: # 漏了 src/arm_kinematics,`from arm_application.pipeline_node import ...` 会在 # 收集阶段就 ModuleNotFoundError —— 现象是"测试跑了 0 个 / 1 个 error", # 很容易被读成"测试环境坏了",而不是"少了一个路径"。 # # 用法: # bash scripts/run_unit_tests.sh # 全部 # bash scripts/run_unit_tests.sh -k gauge # 只跑名字带 gauge 的 # bash scripts/run_unit_tests.sh src/arm_sim_gazebo/test/test_sim_scene.py -x # # 环境:真机 conda 环境或仿真环境都能跑;缺可选依赖的用例自己会 skip #(例如 onnx/ultralytics 只在真机环境有 —— 见各测试里的 pytest.importorskip)。 set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PY="${PYTHON:-python3}" # 把所有 src/* 目录挂到 PYTHONPATH 最前面(包名不冲突,顺序无关)。 paths=() for d in "${ROOT}"/src/*/; do paths+=("${d%/}") done export PYTHONPATH="$(IFS=:; echo "${paths[*]}")${PYTHONPATH:+:${PYTHONPATH}}" test_dirs=() for d in "${ROOT}"/src/*/test; do [ -d "$d" ] && test_dirs+=("$d") done if [ "${#test_dirs[@]}" -eq 0 ]; then echo "没有找到任何测试目录(src/*/test)—— 仓库不完整?" >&2 exit 1 fi cd "${ROOT}" # 位置参数里**存在的路径**当作"只跑这些",其余(-k / -x / --tb=… 及其取值) # 原样透传给 pytest。 # # 为什么要分开:以前是 `pytest "${test_dirs[@]}" "$@"` —— 传进来的路径只是被 # **追加**到"全部测试目录"后面,于是 `run_unit_tests.sh src/x/test` 实际跑的是 # **整仓库**。脚本头部的用法示例看起来像"只跑这个文件",实际不是,很容易被读成 # "我只改了 x,怎么蹦出别的包的失败"。用法示例就是承诺,得让它成立。 paths=() extra=() for a in "$@"; do if [[ "${a}" != -* && -e "${a}" ]]; then paths+=("${a}") else extra+=("${a}") fi done if [ "${#paths[@]}" -gt 0 ]; then exec "${PY}" -m pytest "${paths[@]}" -q "${extra[@]}" fi exec "${PY}" -m pytest "${test_dirs[@]}" -q "${extra[@]}"