✅ test: add automated tests and ci workflow
This commit is contained in:
parent
99bef309b7
commit
da0ef2b7a2
|
|
@ -0,0 +1,826 @@
|
||||||
|
name: 🧪 Playbook 测试套件
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch: # 允许手动触发
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: test-${{ github.repository }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 🔧 配置区域 - 测试参数
|
||||||
|
# ==========================================
|
||||||
|
env:
|
||||||
|
# ===== 测试环境配置 =====
|
||||||
|
# 测试工作目录
|
||||||
|
WORKSPACE_DIR: "/home/workspace"
|
||||||
|
TEST_WORKSPACE: "/home/workspace/playbook-test"
|
||||||
|
|
||||||
|
# ===== 测试覆盖率目标 =====
|
||||||
|
# Shell 脚本测试覆盖率目标(百分比)
|
||||||
|
SHELL_COVERAGE_TARGET: "80"
|
||||||
|
# 模板验证通过率目标(百分比)
|
||||||
|
TEMPLATE_VALIDATION_TARGET: "100"
|
||||||
|
|
||||||
|
# ===== 测试输出配置 =====
|
||||||
|
# 是否生成详细测试报告
|
||||||
|
VERBOSE_OUTPUT: "true"
|
||||||
|
# 测试报告格式(tap/junit/markdown)
|
||||||
|
REPORT_FORMAT: "markdown"
|
||||||
|
|
||||||
|
# ===== 颜色输出配置 =====
|
||||||
|
# 启用彩色输出
|
||||||
|
FORCE_COLOR: "1"
|
||||||
|
TERM: "xterm-256color"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ==========================================
|
||||||
|
# Job 1: 环境检查与准备
|
||||||
|
# ==========================================
|
||||||
|
setup:
|
||||||
|
name: 🔍 环境检查
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
scripts-changed: ${{ steps.changes.outputs.scripts }}
|
||||||
|
templates-changed: ${{ steps.changes.outputs.templates }}
|
||||||
|
docs-changed: ${{ steps.changes.outputs.docs }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📥 准备仓库
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "📥 准备仓库到 WORKSPACE_DIR"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
|
||||||
|
TOKEN="${{ secrets.WORKFLOW }}"
|
||||||
|
if [ -n "$TOKEN" ]; then
|
||||||
|
REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
|
||||||
|
else
|
||||||
|
REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$REPO_DIR" ]; then
|
||||||
|
if [ -d "$REPO_DIR/.git" ]; then
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
git clean -fdx
|
||||||
|
git reset --hard
|
||||||
|
git fetch --all --tags --force --prune --prune-tags
|
||||||
|
else
|
||||||
|
rm -rf "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$REPO_DIR/.git" ]; then
|
||||||
|
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
||||||
|
git clone "$REPO_URL" "$REPO_DIR"
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_SHA="${{ github.sha }}"
|
||||||
|
TARGET_REF="${{ github.ref }}"
|
||||||
|
if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
|
||||||
|
git checkout -f "$TARGET_SHA"
|
||||||
|
else
|
||||||
|
if [ -n "$TARGET_REF" ]; then
|
||||||
|
git fetch origin "$TARGET_REF"
|
||||||
|
git checkout -f FETCH_HEAD
|
||||||
|
else
|
||||||
|
git checkout -f "${{ github.ref_name }}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
git config --global --add safe.directory "$REPO_DIR"
|
||||||
|
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: 🔍 检测变更文件
|
||||||
|
id: changes
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔍 检测变更文件"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
|
||||||
|
# 如果是手动触发或主分支push,测试所有内容
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ] || [ "${{ github.event_name }}" = "push" ]; then
|
||||||
|
echo "scripts=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "templates=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "docs=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "✓ 手动触发或主分支push,测试所有内容"
|
||||||
|
else
|
||||||
|
# PR:只测试变更的部分
|
||||||
|
SCRIPTS_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '^scripts/' || echo "")
|
||||||
|
TEMPLATES_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '^templates/' || echo "")
|
||||||
|
DOCS_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '^(docs/|.agents/)' || echo "")
|
||||||
|
|
||||||
|
if [ -n "$SCRIPTS_CHANGED" ]; then
|
||||||
|
echo "scripts=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "✓ 检测到脚本变更"
|
||||||
|
else
|
||||||
|
echo "scripts=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$TEMPLATES_CHANGED" ]; then
|
||||||
|
echo "templates=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "✓ 检测到模板变更"
|
||||||
|
else
|
||||||
|
echo "templates=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$DOCS_CHANGED" ]; then
|
||||||
|
echo "docs=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "✓ 检测到文档变更"
|
||||||
|
else
|
||||||
|
echo "docs=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 📊 显示测试环境信息
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "📊 测试环境信息"
|
||||||
|
echo "========================================"
|
||||||
|
echo "🖥️ 操作系统: $(lsb_release -ds)"
|
||||||
|
echo "🐚 Shell: $SHELL"
|
||||||
|
echo "🐍 Python: $(python3 --version)"
|
||||||
|
echo "📦 Git: $(git --version)"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# Job 2: Shell 脚本测试
|
||||||
|
# ==========================================
|
||||||
|
test-scripts:
|
||||||
|
name: 🐚 Shell 脚本测试
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: setup
|
||||||
|
if: needs.setup.outputs.scripts-changed == 'true'
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
script-group:
|
||||||
|
- name: sync_standards
|
||||||
|
scripts: "sync_standards.sh"
|
||||||
|
- name: vendor_playbook
|
||||||
|
scripts: "vendor_playbook.sh"
|
||||||
|
- name: install_codex_skills
|
||||||
|
scripts: "install_codex_skills.sh"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📥 准备仓库
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "📥 准备仓库到 WORKSPACE_DIR"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
|
||||||
|
TOKEN="${{ secrets.WORKFLOW }}"
|
||||||
|
if [ -n "$TOKEN" ]; then
|
||||||
|
REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
|
||||||
|
else
|
||||||
|
REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$REPO_DIR" ]; then
|
||||||
|
if [ -d "$REPO_DIR/.git" ]; then
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
git clean -fdx
|
||||||
|
git reset --hard
|
||||||
|
git fetch --all --tags --force --prune --prune-tags
|
||||||
|
else
|
||||||
|
rm -rf "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$REPO_DIR/.git" ]; then
|
||||||
|
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
||||||
|
git clone "$REPO_URL" "$REPO_DIR"
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_SHA="${{ github.sha }}"
|
||||||
|
TARGET_REF="${{ github.ref }}"
|
||||||
|
if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
|
||||||
|
git checkout -f "$TARGET_SHA"
|
||||||
|
else
|
||||||
|
if [ -n "$TARGET_REF" ]; then
|
||||||
|
git fetch origin "$TARGET_REF"
|
||||||
|
git checkout -f FETCH_HEAD
|
||||||
|
else
|
||||||
|
git checkout -f "${{ github.ref_name }}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
git config --global --add safe.directory "$REPO_DIR"
|
||||||
|
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: 🔧 安装 bats-core
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔧 安装 bats-core 测试框架"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y bats
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✓ bats 版本: $(bats --version)"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 🧪 运行 ${{ matrix.script-group.name }} 测试
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🧪 测试脚本组: ${{ matrix.script-group.name }}"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cd "$REPO_DIR/tests/scripts"
|
||||||
|
|
||||||
|
# 运行对应的测试文件
|
||||||
|
if [ -f "test_${{ matrix.script-group.name }}.bats" ]; then
|
||||||
|
bats --formatter tap "test_${{ matrix.script-group.name }}.bats" | tee "${{ matrix.script-group.name }}_test_results.tap"
|
||||||
|
TEST_EXIT_CODE=${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [ $TEST_EXIT_CODE -eq 0 ]; then
|
||||||
|
echo "✅ ${{ matrix.script-group.name }} 测试通过"
|
||||||
|
else
|
||||||
|
echo "❌ ${{ matrix.script-group.name }} 测试失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ 未找到测试文件: test_${{ matrix.script-group.name }}.bats"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 📊 上传测试结果
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: script-test-results-${{ matrix.script-group.name }}
|
||||||
|
path: ${{ env.REPO_DIR }}/tests/scripts/*_test_results.tap
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# Job 3: 模板验证测试
|
||||||
|
# ==========================================
|
||||||
|
test-templates:
|
||||||
|
name: 📄 模板验证测试
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: setup
|
||||||
|
if: needs.setup.outputs.templates-changed == 'true'
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
template-type:
|
||||||
|
- name: python
|
||||||
|
validator: validate_python_templates.sh
|
||||||
|
- name: cpp
|
||||||
|
validator: validate_cpp_templates.sh
|
||||||
|
- name: ci
|
||||||
|
validator: validate_ci_templates.sh
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📥 准备仓库
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "📥 准备仓库到 WORKSPACE_DIR"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
|
||||||
|
TOKEN="${{ secrets.WORKFLOW }}"
|
||||||
|
if [ -n "$TOKEN" ]; then
|
||||||
|
REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
|
||||||
|
else
|
||||||
|
REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$REPO_DIR" ]; then
|
||||||
|
if [ -d "$REPO_DIR/.git" ]; then
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
git clean -fdx
|
||||||
|
git reset --hard
|
||||||
|
git fetch --all --tags --force --prune --prune-tags
|
||||||
|
else
|
||||||
|
rm -rf "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$REPO_DIR/.git" ]; then
|
||||||
|
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
||||||
|
git clone "$REPO_URL" "$REPO_DIR"
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_SHA="${{ github.sha }}"
|
||||||
|
TARGET_REF="${{ github.ref }}"
|
||||||
|
if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
|
||||||
|
git checkout -f "$TARGET_SHA"
|
||||||
|
else
|
||||||
|
if [ -n "$TARGET_REF" ]; then
|
||||||
|
git fetch origin "$TARGET_REF"
|
||||||
|
git checkout -f FETCH_HEAD
|
||||||
|
else
|
||||||
|
git checkout -f "${{ github.ref_name }}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
git config --global --add safe.directory "$REPO_DIR"
|
||||||
|
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: 🔧 安装验证工具
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔧 安装 ${{ matrix.template-type.name }} 模板验证工具"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
case "${{ matrix.template-type.name }}" in
|
||||||
|
python)
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
pip install toml tomli jsonschema yamllint
|
||||||
|
echo "✓ Python 验证工具已安装"
|
||||||
|
;;
|
||||||
|
cpp)
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y cmake clang-format
|
||||||
|
echo "✓ C++ 验证工具已安装"
|
||||||
|
;;
|
||||||
|
ci)
|
||||||
|
pip install yamllint
|
||||||
|
echo "✓ CI 验证工具已安装"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 🧪 验证 ${{ matrix.template-type.name }} 模板
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🧪 验证模板类型: ${{ matrix.template-type.name }}"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cd "$REPO_DIR/tests/templates"
|
||||||
|
|
||||||
|
if [ -f "${{ matrix.template-type.validator }}" ]; then
|
||||||
|
chmod +x "${{ matrix.template-type.validator }}"
|
||||||
|
./"${{ matrix.template-type.validator }}"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ ${{ matrix.template-type.name }} 模板验证通过"
|
||||||
|
else
|
||||||
|
echo "❌ ${{ matrix.template-type.name }} 模板验证失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ 未找到验证脚本: ${{ matrix.template-type.validator }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 📊 上传验证报告
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: template-validation-${{ matrix.template-type.name }}
|
||||||
|
path: ${{ env.REPO_DIR }}/tests/templates/*_validation_report.txt
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# Job 4: 集成测试
|
||||||
|
# ==========================================
|
||||||
|
test-integration:
|
||||||
|
name: 🔗 集成测试
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: [setup, test-scripts, test-templates]
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
(needs.test-scripts.result == 'success' || needs.test-scripts.result == 'skipped') &&
|
||||||
|
(needs.test-templates.result == 'success' || needs.test-templates.result == 'skipped')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📥 准备仓库
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "📥 准备仓库到 WORKSPACE_DIR"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
|
||||||
|
TOKEN="${{ secrets.WORKFLOW }}"
|
||||||
|
if [ -n "$TOKEN" ]; then
|
||||||
|
REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
|
||||||
|
else
|
||||||
|
REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$REPO_DIR" ]; then
|
||||||
|
if [ -d "$REPO_DIR/.git" ]; then
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
git clean -fdx
|
||||||
|
git reset --hard
|
||||||
|
git fetch --all --tags --force --prune --prune-tags
|
||||||
|
else
|
||||||
|
rm -rf "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$REPO_DIR/.git" ]; then
|
||||||
|
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
||||||
|
git clone "$REPO_URL" "$REPO_DIR"
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_SHA="${{ github.sha }}"
|
||||||
|
TARGET_REF="${{ github.ref }}"
|
||||||
|
if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
|
||||||
|
git checkout -f "$TARGET_SHA"
|
||||||
|
else
|
||||||
|
if [ -n "$TARGET_REF" ]; then
|
||||||
|
git fetch origin "$TARGET_REF"
|
||||||
|
git checkout -f FETCH_HEAD
|
||||||
|
else
|
||||||
|
git checkout -f "${{ github.ref_name }}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
git config --global --add safe.directory "$REPO_DIR"
|
||||||
|
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: 🔧 准备测试环境
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔧 准备集成测试环境"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
mkdir -p "${{ env.TEST_WORKSPACE }}"
|
||||||
|
cd "${{ env.TEST_WORKSPACE }}"
|
||||||
|
|
||||||
|
# 创建测试项目目录
|
||||||
|
mkdir -p test-project-tsl
|
||||||
|
mkdir -p test-project-cpp
|
||||||
|
mkdir -p test-project-multi
|
||||||
|
|
||||||
|
echo "✓ 测试环境已准备"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 🧪 测试场景1: TSL 项目标准同步
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🧪 测试场景1: TSL 项目标准同步"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cd "${{ env.TEST_WORKSPACE }}/test-project-tsl"
|
||||||
|
|
||||||
|
# 初始化 git 仓库
|
||||||
|
git init
|
||||||
|
git config user.name "Test User"
|
||||||
|
git config user.email "test@example.com"
|
||||||
|
|
||||||
|
# 模拟 subtree add(包含 .agents 等点目录,排除 .git)
|
||||||
|
mkdir -p docs/standards/playbook
|
||||||
|
tar -C "$REPO_DIR" --exclude .git -cf - . | tar -C docs/standards/playbook -xf -
|
||||||
|
|
||||||
|
# 运行同步脚本
|
||||||
|
echo "▶ 运行 sync_standards.sh tsl"
|
||||||
|
sh docs/standards/playbook/scripts/sync_standards.sh tsl
|
||||||
|
|
||||||
|
# 验证结果
|
||||||
|
if [ -d ".agents/tsl" ] && [ -f ".agents/tsl/index.md" ]; then
|
||||||
|
echo "✅ TSL 规则集同步成功"
|
||||||
|
else
|
||||||
|
echo "❌ TSL 规则集同步失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "# BEGIN playbook .gitattributes" .gitattributes 2>/dev/null; then
|
||||||
|
echo "✅ .gitattributes 更新成功"
|
||||||
|
else
|
||||||
|
echo "❌ .gitattributes 更新失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 🧪 测试场景2: C++ 项目标准同步
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🧪 测试场景2: C++ 项目标准同步"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cd "${{ env.TEST_WORKSPACE }}/test-project-cpp"
|
||||||
|
|
||||||
|
git init
|
||||||
|
git config user.name "Test User"
|
||||||
|
git config user.email "test@example.com"
|
||||||
|
|
||||||
|
mkdir -p docs/standards/playbook
|
||||||
|
tar -C "$REPO_DIR" --exclude .git -cf - . | tar -C docs/standards/playbook -xf -
|
||||||
|
|
||||||
|
echo "▶ 运行 sync_standards.sh cpp"
|
||||||
|
sh docs/standards/playbook/scripts/sync_standards.sh cpp
|
||||||
|
|
||||||
|
if [ -d ".agents/cpp" ] && [ -f ".agents/cpp/index.md" ]; then
|
||||||
|
echo "✅ C++ 规则集同步成功"
|
||||||
|
else
|
||||||
|
echo "❌ C++ 规则集同步失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 🧪 测试场景3: 多语言项目标准同步
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🧪 测试场景3: 多语言项目标准同步"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cd "${{ env.TEST_WORKSPACE }}/test-project-multi"
|
||||||
|
|
||||||
|
git init
|
||||||
|
git config user.name "Test User"
|
||||||
|
git config user.email "test@example.com"
|
||||||
|
|
||||||
|
mkdir -p docs/standards/playbook
|
||||||
|
tar -C "$REPO_DIR" --exclude .git -cf - . | tar -C docs/standards/playbook -xf -
|
||||||
|
|
||||||
|
echo "▶ 运行 sync_standards.sh tsl cpp"
|
||||||
|
sh docs/standards/playbook/scripts/sync_standards.sh tsl cpp
|
||||||
|
|
||||||
|
if [ -d ".agents/tsl" ] && [ -d ".agents/cpp" ] && [ -f ".agents/index.md" ]; then
|
||||||
|
echo "✅ 多语言规则集同步成功"
|
||||||
|
else
|
||||||
|
echo "❌ 多语言规则集同步失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 🧪 测试场景4: vendor_playbook 脚本
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🧪 测试场景4: vendor_playbook 脚本"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cd "${{ env.TEST_WORKSPACE }}"
|
||||||
|
mkdir -p test-project-vendor
|
||||||
|
cd test-project-vendor
|
||||||
|
|
||||||
|
git init
|
||||||
|
git config user.name "Test User"
|
||||||
|
git config user.email "test@example.com"
|
||||||
|
|
||||||
|
echo "▶ 运行 vendor_playbook.sh"
|
||||||
|
sh "$REPO_DIR/scripts/vendor_playbook.sh" . tsl
|
||||||
|
|
||||||
|
if [ -d "docs/standards/playbook" ] && [ -d ".agents/tsl" ]; then
|
||||||
|
echo "✅ vendor_playbook 脚本执行成功"
|
||||||
|
else
|
||||||
|
echo "❌ vendor_playbook 脚本执行失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 🧹 清理测试环境
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "🧹 清理测试环境..."
|
||||||
|
chmod -R u+w "${{ env.TEST_WORKSPACE }}" 2>/dev/null || true
|
||||||
|
rm -rf "${{ env.TEST_WORKSPACE }}"
|
||||||
|
echo "✓ 清理完成"
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# Job 5: 文档一致性检查
|
||||||
|
# ==========================================
|
||||||
|
test-docs:
|
||||||
|
name: 📚 文档一致性检查
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: setup
|
||||||
|
if: needs.setup.outputs.docs-changed == 'true'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📥 准备仓库
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "📥 准备仓库到 WORKSPACE_DIR"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
|
||||||
|
TOKEN="${{ secrets.WORKFLOW }}"
|
||||||
|
if [ -n "$TOKEN" ]; then
|
||||||
|
REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git"
|
||||||
|
else
|
||||||
|
REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$REPO_DIR" ]; then
|
||||||
|
if [ -d "$REPO_DIR/.git" ]; then
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
git clean -fdx
|
||||||
|
git reset --hard
|
||||||
|
git fetch --all --tags --force --prune --prune-tags
|
||||||
|
else
|
||||||
|
rm -rf "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$REPO_DIR/.git" ]; then
|
||||||
|
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
||||||
|
git clone "$REPO_URL" "$REPO_DIR"
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_SHA="${{ github.sha }}"
|
||||||
|
TARGET_REF="${{ github.ref }}"
|
||||||
|
if git cat-file -e "$TARGET_SHA^{commit}" 2>/dev/null; then
|
||||||
|
git checkout -f "$TARGET_SHA"
|
||||||
|
else
|
||||||
|
if [ -n "$TARGET_REF" ]; then
|
||||||
|
git fetch origin "$TARGET_REF"
|
||||||
|
git checkout -f FETCH_HEAD
|
||||||
|
else
|
||||||
|
git checkout -f "${{ github.ref_name }}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
git config --global --add safe.directory "$REPO_DIR"
|
||||||
|
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: 🔍 检查文档链接有效性
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔍 检查文档链接有效性"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cd "$REPO_DIR/tests/integration"
|
||||||
|
|
||||||
|
if [ -f "check_doc_links.sh" ]; then
|
||||||
|
chmod +x check_doc_links.sh
|
||||||
|
./check_doc_links.sh
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ 文档链接检查通过"
|
||||||
|
else
|
||||||
|
echo "❌ 发现无效链接"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ 未找到链接检查脚本,跳过"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
- name: 🔍 检查代理规则一致性
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔍 检查代理规则一致性"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
|
||||||
|
# 检查 .agents/ 和 docs/ 中的规则是否一致
|
||||||
|
python3 << 'EOF'
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# 检查各语言的 .agents/ 目录
|
||||||
|
agents_base = Path(".agents")
|
||||||
|
for lang_dir in ["tsl", "cpp", "python"]:
|
||||||
|
agents_lang = agents_base / lang_dir
|
||||||
|
if not agents_lang.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查必须存在的文件
|
||||||
|
required_files = ["index.md", "auth.md", "code_quality.md"]
|
||||||
|
for req_file in required_files:
|
||||||
|
file_path = agents_lang / req_file
|
||||||
|
if not file_path.exists():
|
||||||
|
errors.append(f"❌ 缺少文件: {file_path}")
|
||||||
|
elif file_path.stat().st_size == 0:
|
||||||
|
errors.append(f"❌ 文件为空: {file_path}")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
print("\n".join(errors))
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print("✅ 代理规则一致性检查通过")
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# Job 6: 生成测试报告
|
||||||
|
# ==========================================
|
||||||
|
report:
|
||||||
|
name: 📊 生成测试报告
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: [setup, test-scripts, test-templates, test-integration, test-docs]
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📁 准备报告目录
|
||||||
|
run: |
|
||||||
|
mkdir -p "${{ env.WORKSPACE_DIR }}/test-results"
|
||||||
|
|
||||||
|
- name: 📥 下载所有测试结果
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.WORKSPACE_DIR }}/test-results
|
||||||
|
|
||||||
|
- name: 📊 生成综合报告
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "========================================"
|
||||||
|
echo "📊 生成测试综合报告"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUMMARY'
|
||||||
|
# 🧪 Playbook 测试报告
|
||||||
|
|
||||||
|
## 📋 测试执行摘要
|
||||||
|
|
||||||
|
| 测试类型 | 状态 |
|
||||||
|
|---------|------|
|
||||||
|
EOFSUMMARY
|
||||||
|
|
||||||
|
# Shell 脚本测试
|
||||||
|
if [ "${{ needs.test-scripts.result }}" = "success" ]; then
|
||||||
|
echo "| 🐚 Shell 脚本测试 | ✅ 通过 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ needs.test-scripts.result }}" = "failure" ]; then
|
||||||
|
echo "| 🐚 Shell 脚本测试 | ❌ 失败 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ needs.test-scripts.result }}" = "skipped" ]; then
|
||||||
|
echo "| 🐚 Shell 脚本测试 | ⏭️ 跳过 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 模板验证测试
|
||||||
|
if [ "${{ needs.test-templates.result }}" = "success" ]; then
|
||||||
|
echo "| 📄 模板验证测试 | ✅ 通过 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ needs.test-templates.result }}" = "failure" ]; then
|
||||||
|
echo "| 📄 模板验证测试 | ❌ 失败 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ needs.test-templates.result }}" = "skipped" ]; then
|
||||||
|
echo "| 📄 模板验证测试 | ⏭️ 跳过 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 集成测试
|
||||||
|
if [ "${{ needs.test-integration.result }}" = "success" ]; then
|
||||||
|
echo "| 🔗 集成测试 | ✅ 通过 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ needs.test-integration.result }}" = "failure" ]; then
|
||||||
|
echo "| 🔗 集成测试 | ❌ 失败 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ needs.test-integration.result }}" = "skipped" ]; then
|
||||||
|
echo "| 🔗 集成测试 | ⏭️ 跳过 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 文档检查
|
||||||
|
if [ "${{ needs.test-docs.result }}" = "success" ]; then
|
||||||
|
echo "| 📚 文档一致性检查 | ✅ 通过 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ needs.test-docs.result }}" = "failure" ]; then
|
||||||
|
echo "| 📚 文档一致性检查 | ❌ 失败 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ needs.test-docs.result }}" = "skipped" ]; then
|
||||||
|
echo "| 📚 文档一致性检查 | ⏭️ 跳过 |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << 'EOFEND'
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 相关链接
|
||||||
|
|
||||||
|
- 📝 [测试文档](tests/README.md)
|
||||||
|
- 🐛 [问题反馈](../../issues)
|
||||||
|
- 📖 [开发指南](docs/index.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
*🤖 由 [Gitea Actions](../../actions) 自动生成*
|
||||||
|
|
||||||
|
EOFEND
|
||||||
|
|
||||||
|
echo "*📅 生成时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "</div>" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "✅ 测试报告已生成"
|
||||||
|
echo "========================================"
|
||||||
|
|
@ -63,9 +63,7 @@ function/
|
||||||
|
|
||||||
### 方法 3:查看历史备份
|
### 方法 3:查看历史备份
|
||||||
|
|
||||||
如果需要查看原始完整文件:
|
如需查看原始完整文件,请从历史版本获取(当前仓库不保留备份文件)。
|
||||||
|
|
||||||
- 备份文件:[function.md.backup](./function.md.backup)(保留完整内容)
|
|
||||||
|
|
||||||
## 💡 拆分的好处
|
## 💡 拆分的好处
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,5 +77,4 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**原始文档**:本文档由 221,389 行的 `function.md` 拆分而来,原文件已保留为
|
**原始文档**:本文档由 221,389 行的 `function.md` 拆分而来,原始大文件不在当前仓库保留;如需查看,请从历史版本获取。
|
||||||
[`../function.md.backup`](../function.md.backup)
|
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ sh docs/standards/playbook/scripts/install_codex_skills.sh
|
||||||
|
|
||||||
## CI templates(可选)
|
## CI templates(可选)
|
||||||
|
|
||||||
目标项目可复制启用的 CI 示例模板(如 Gitea Actions):`templates/ci/`。
|
目标项目可复制启用的 CI 示例模板(如 Gitea Actions):\`templates/ci/\`。
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat >"$DEST_PREFIX/SOURCE.md" <<EOF
|
cat >"$DEST_PREFIX/SOURCE.md" <<EOF
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,435 @@
|
||||||
|
# 🧪 Playbook 测试套件
|
||||||
|
|
||||||
|
本目录包含 Playbook 项目的完整测试框架,用于验证脚本、模板和文档的正确性。
|
||||||
|
|
||||||
|
## 📋 目录结构
|
||||||
|
|
||||||
|
```txt
|
||||||
|
tests/
|
||||||
|
├── README.md # 本文件:测试文档
|
||||||
|
├── scripts/ # Shell 脚本测试(bats)
|
||||||
|
│ ├── test_sync_standards.bats # sync_standards.sh 测试
|
||||||
|
│ ├── test_vendor_playbook.bats # vendor_playbook.sh 测试
|
||||||
|
│ └── test_install_codex_skills.bats # install_codex_skills.sh 测试
|
||||||
|
├── templates/ # 模板验证测试
|
||||||
|
│ ├── validate_python_templates.sh # Python 模板验证
|
||||||
|
│ ├── validate_cpp_templates.sh # C++ 模板验证
|
||||||
|
│ └── validate_ci_templates.sh # CI 模板验证
|
||||||
|
└── integration/ # 集成测试
|
||||||
|
└── check_doc_links.sh # 文档链接有效性检查
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 本地运行所有测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入 playbook 根目录
|
||||||
|
cd /path/to/playbook
|
||||||
|
|
||||||
|
# 1. 运行 Shell 脚本测试(需要 bats)
|
||||||
|
sudo apt-get install bats # Ubuntu/Debian
|
||||||
|
cd tests/scripts
|
||||||
|
bats test_sync_standards.bats
|
||||||
|
bats test_vendor_playbook.bats
|
||||||
|
bats test_install_codex_skills.bats
|
||||||
|
|
||||||
|
# 2. 运行模板验证测试
|
||||||
|
cd tests/templates
|
||||||
|
sh validate_python_templates.sh
|
||||||
|
sh validate_cpp_templates.sh
|
||||||
|
sh validate_ci_templates.sh
|
||||||
|
|
||||||
|
# 3. 运行集成测试
|
||||||
|
cd tests/integration
|
||||||
|
sh check_doc_links.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI 自动化测试
|
||||||
|
|
||||||
|
测试套件通过 Gitea Actions 自动运行(见 `.gitea/workflows/test.yml`):
|
||||||
|
|
||||||
|
- **触发时机**:
|
||||||
|
- 推送到 `main` 分支
|
||||||
|
- Pull Request 到 `main` 分支
|
||||||
|
- 手动触发(workflow_dispatch)
|
||||||
|
- **运行平台**:ubuntu-22.04
|
||||||
|
- **并行策略**:使用 matrix 策略并行运行多个测试组
|
||||||
|
|
||||||
|
## 📚 测试详解
|
||||||
|
|
||||||
|
### 1. Shell 脚本测试 (scripts/)
|
||||||
|
|
||||||
|
使用 [bats-core](https://github.com/bats-core/bats-core) 框架测试 shell 脚本。
|
||||||
|
|
||||||
|
#### test_sync_standards.bats
|
||||||
|
|
||||||
|
测试 `scripts/sync_standards.sh` 脚本的功能:
|
||||||
|
|
||||||
|
- **基础功能**:
|
||||||
|
- 脚本存在且可执行
|
||||||
|
- 无参数时默认同步 tsl 规则集
|
||||||
|
- 单语言同步(tsl/cpp)
|
||||||
|
- 多语言同步(tsl cpp)
|
||||||
|
- **.gitattributes 同步**:
|
||||||
|
- 默认模式创建 managed block
|
||||||
|
- 保留现有内容
|
||||||
|
- 更新已存在的 managed block
|
||||||
|
- **AGENTS.md 处理**:
|
||||||
|
- 不存在时自动创建
|
||||||
|
- 已存在时不覆盖
|
||||||
|
- **备份功能**:
|
||||||
|
- 更新前创建备份
|
||||||
|
- **错误处理**:
|
||||||
|
- 未找到 playbook 快照时报错
|
||||||
|
- 无效语言参数时报错
|
||||||
|
- **环境变量**:
|
||||||
|
- `SYNC_GITATTR_MODE` 配置
|
||||||
|
- **幂等性**:
|
||||||
|
- 多次执行结果一致
|
||||||
|
|
||||||
|
#### test_vendor_playbook.bats
|
||||||
|
|
||||||
|
测试 `scripts/vendor_playbook.sh` 脚本的功能:
|
||||||
|
|
||||||
|
- **基础功能**:
|
||||||
|
- 单语言 vendoring
|
||||||
|
- 多语言 vendoring
|
||||||
|
- **自动同步**:
|
||||||
|
- 自动执行 sync_standards
|
||||||
|
- **SOURCE.md 生成**:
|
||||||
|
- 包含来源信息
|
||||||
|
- 包含 commit hash
|
||||||
|
- 包含时间戳
|
||||||
|
- **裁剪功能**:
|
||||||
|
- 仅包含指定语言
|
||||||
|
- 始终包含 common 目录
|
||||||
|
- 包含对应模板文件
|
||||||
|
- **目标目录处理**:
|
||||||
|
- 已存在时覆盖更新
|
||||||
|
- 创建必要的父目录
|
||||||
|
- **错误处理**:
|
||||||
|
- 目标目录不存在时报错
|
||||||
|
- 无效语言参数时报错
|
||||||
|
- **完整性验证**:
|
||||||
|
- 所有必要文件已复制
|
||||||
|
- 脚本可执行
|
||||||
|
- **幂等性**:
|
||||||
|
- 多次 vendor 结果一致
|
||||||
|
|
||||||
|
#### test_install_codex_skills.bats
|
||||||
|
|
||||||
|
测试 `scripts/install_codex_skills.sh` 脚本的功能:
|
||||||
|
|
||||||
|
- **基础功能**:
|
||||||
|
- 脚本存在且可执行
|
||||||
|
- **安装功能**:
|
||||||
|
- 创建 skills 目录
|
||||||
|
- 复制 skill 目录(包含 `SKILL.md`)
|
||||||
|
- 支持指定单个 skill 安装
|
||||||
|
- 同名目录安装前创建备份
|
||||||
|
- **错误处理**:
|
||||||
|
- 指定不存在的 skill 报错
|
||||||
|
- **幂等性**:
|
||||||
|
- 多次安装结果一致
|
||||||
|
|
||||||
|
### 2. 模板验证测试 (templates/)
|
||||||
|
|
||||||
|
验证项目模板文件的正确性和完整性。
|
||||||
|
|
||||||
|
#### validate_python_templates.sh
|
||||||
|
|
||||||
|
验证 `templates/python/` 目录下的 Python 模板:
|
||||||
|
|
||||||
|
- **pyproject.toml**:
|
||||||
|
- 文件存在
|
||||||
|
- TOML 语法正确
|
||||||
|
- 包含必要配置节(tool.black, tool.isort, tool.pytest.ini_options)
|
||||||
|
- black line-length 配置正确
|
||||||
|
- isort profile 配置正确
|
||||||
|
- **.flake8**:
|
||||||
|
- 文件存在
|
||||||
|
- 包含 [flake8] 配置
|
||||||
|
- 配置了 max-line-length
|
||||||
|
- 配置了错误忽略规则
|
||||||
|
- **.pylintrc**:
|
||||||
|
- 文件存在
|
||||||
|
- 包含必要配置节(MASTER, MESSAGES CONTROL, FORMAT)
|
||||||
|
- 配置了 max-line-length
|
||||||
|
- **.pre-commit-config.yaml**:
|
||||||
|
- 文件存在
|
||||||
|
- YAML 语法正确
|
||||||
|
- 包含 repos 配置
|
||||||
|
- 配置了常用 hooks(black, isort, flake8)
|
||||||
|
- **.editorconfig**:
|
||||||
|
- 文件存在
|
||||||
|
- 包含 root = true
|
||||||
|
- 包含 Python 文件配置
|
||||||
|
- **.vscode/settings.json**:
|
||||||
|
- 文件存在
|
||||||
|
- JSON 语法正确
|
||||||
|
- 包含 Python 配置
|
||||||
|
|
||||||
|
#### validate_cpp_templates.sh
|
||||||
|
|
||||||
|
验证 `templates/cpp/` 目录下的 C++ 模板:
|
||||||
|
|
||||||
|
- **CMakeLists.txt**:
|
||||||
|
- 文件存在
|
||||||
|
- CMake 基础语法正确
|
||||||
|
- 配置了 C++23 标准
|
||||||
|
- 启用了 C++ Modules 扫描(可选)
|
||||||
|
- 启用了 import std; 支持(可选)
|
||||||
|
- 启用了编译命令导出(用于 clangd)
|
||||||
|
- **.clang-format**:
|
||||||
|
- 文件存在
|
||||||
|
- YAML 语法正确
|
||||||
|
- 配置了 Language: Cpp
|
||||||
|
- 基于某个风格(BasedOnStyle)
|
||||||
|
- 配置了 C++ 标准
|
||||||
|
- 配置了缩进宽度
|
||||||
|
- **.clangd**:
|
||||||
|
- 文件存在
|
||||||
|
- YAML 语法正确
|
||||||
|
- 包含 CompileFlags 配置
|
||||||
|
- 配置了 -std=c++23
|
||||||
|
- 配置了 CompilationDatabase 路径
|
||||||
|
- 配置了 Index 选项
|
||||||
|
- **conanfile.txt**:
|
||||||
|
- 文件存在
|
||||||
|
- 包含 [requires] 配置(可选)
|
||||||
|
- 包含 [generators] 配置
|
||||||
|
- 配置了 CMakeDeps 生成器
|
||||||
|
- 配置了 CMakeToolchain 生成器
|
||||||
|
- 包含 [options] 配置
|
||||||
|
- **CMakeUserPresets.json**:
|
||||||
|
- 文件存在
|
||||||
|
- JSON 语法正确
|
||||||
|
- 包含 version 字段
|
||||||
|
- 包含 configurePresets
|
||||||
|
- 包含 buildPresets
|
||||||
|
|
||||||
|
#### validate_ci_templates.sh
|
||||||
|
|
||||||
|
验证 `templates/ci/` 目录下的 CI 模板:
|
||||||
|
|
||||||
|
- **Gitea/GitHub workflow 文件**:
|
||||||
|
- 文件存在
|
||||||
|
- YAML 语法正确
|
||||||
|
- 包含必要字段(name, on, jobs, runs-on, steps)
|
||||||
|
- 包含中文注释(符合项目风格)
|
||||||
|
- 包含配置区域标记
|
||||||
|
- 包含环境变量配置
|
||||||
|
- **特定 workflow 模板**:
|
||||||
|
- standards-check.yml:包含格式化检查和 lint 检查
|
||||||
|
- test.yml:包含测试步骤和测试矩阵
|
||||||
|
- **文档**:
|
||||||
|
- 包含 README 说明文档
|
||||||
|
- 包含使用说明
|
||||||
|
|
||||||
|
### 3. 集成测试 (integration/)
|
||||||
|
|
||||||
|
端到端测试,验证整体功能。
|
||||||
|
|
||||||
|
#### check_doc_links.sh
|
||||||
|
|
||||||
|
检查所有 Markdown 文档中的链接有效性:
|
||||||
|
|
||||||
|
- **扫描范围**:
|
||||||
|
- 所有 `*.md` 文件
|
||||||
|
- 排除 node_modules, .git, build, dist 等目录
|
||||||
|
- **链接类型**:
|
||||||
|
- Markdown 链接:`[text](link)`
|
||||||
|
- 引用链接:`[text]: link`
|
||||||
|
- **验证逻辑**:
|
||||||
|
- 检查相对路径链接是否指向存在的文件
|
||||||
|
- 跳过外部链接(http/https)
|
||||||
|
- 跳过 mailto 链接
|
||||||
|
- 跳过代码块与行内代码中的链接样式文本
|
||||||
|
- 支持锚点(但只验证文件存在性)
|
||||||
|
- **报告内容**:
|
||||||
|
- 总链接数
|
||||||
|
- 有效链接数
|
||||||
|
- 跳过链接数(外部/mailto)
|
||||||
|
- 断开链接详情(文件、行号、链接、目标路径)
|
||||||
|
|
||||||
|
## 🔧 本地开发
|
||||||
|
|
||||||
|
### 安装测试依赖
|
||||||
|
|
||||||
|
**Ubuntu/Debian**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# bats-core (Shell 脚本测试)
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install bats
|
||||||
|
|
||||||
|
# Python 工具(模板验证)
|
||||||
|
pip install toml tomli jsonschema yamllint
|
||||||
|
|
||||||
|
# C++ 工具(模板验证,可选)
|
||||||
|
sudo apt-get install cmake clang-format
|
||||||
|
|
||||||
|
# YAML 验证
|
||||||
|
pip install yamllint
|
||||||
|
```
|
||||||
|
|
||||||
|
**macOS**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# bats-core
|
||||||
|
brew install bats-core
|
||||||
|
|
||||||
|
# Python 工具
|
||||||
|
pip3 install toml tomli jsonschema yamllint
|
||||||
|
|
||||||
|
# C++ 工具(可选)
|
||||||
|
brew install cmake clang-format
|
||||||
|
|
||||||
|
# YAML 验证
|
||||||
|
pip3 install yamllint
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行单个测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Shell 脚本测试
|
||||||
|
cd tests/scripts
|
||||||
|
bats test_sync_standards.bats --tap # TAP 格式输出
|
||||||
|
bats test_vendor_playbook.bats --formatter junit # JUnit 格式
|
||||||
|
|
||||||
|
# 模板验证测试
|
||||||
|
cd tests/templates
|
||||||
|
sh validate_python_templates.sh
|
||||||
|
sh validate_cpp_templates.sh
|
||||||
|
sh validate_ci_templates.sh
|
||||||
|
|
||||||
|
# 集成测试
|
||||||
|
cd tests/integration
|
||||||
|
sh check_doc_links.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加新测试
|
||||||
|
|
||||||
|
#### 添加新的 bats 测试
|
||||||
|
|
||||||
|
在 `tests/scripts/` 创建新文件 `test_<script_name>.bats`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
# 测试前准备
|
||||||
|
export TEST_DIR="$(mktemp -d)"
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
# 测试后清理
|
||||||
|
rm -rf "$TEST_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "描述测试内容" {
|
||||||
|
# 测试代码
|
||||||
|
[ -f "some_file" ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 添加新的模板验证
|
||||||
|
|
||||||
|
在 `tests/templates/` 创建新文件 `validate_<template_type>_templates.sh`,参考现有脚本结构。
|
||||||
|
|
||||||
|
#### 添加新的集成测试
|
||||||
|
|
||||||
|
在 `tests/integration/` 创建新脚本,确保:
|
||||||
|
|
||||||
|
1. 使用 `set -eu` 启用错误检测
|
||||||
|
2. 输出清晰的测试进度
|
||||||
|
3. 生成详细的报告文件
|
||||||
|
4. 返回正确的退出码(0 = 成功,非 0 = 失败)
|
||||||
|
|
||||||
|
## 📊 测试覆盖率目标
|
||||||
|
|
||||||
|
- **Shell 脚本测试**:目标覆盖率 ≥ 80%
|
||||||
|
- **模板验证测试**:目标通过率 = 100%
|
||||||
|
- **集成测试**:目标通过率 = 100%
|
||||||
|
- **文档链接有效性**:目标有效率 = 100%
|
||||||
|
|
||||||
|
## 🐛 故障排查
|
||||||
|
|
||||||
|
### bats 测试失败
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 --verbose 查看详细输出
|
||||||
|
bats --verbose test_sync_standards.bats
|
||||||
|
|
||||||
|
# 使用 --trace 查看执行跟踪
|
||||||
|
bats --trace test_sync_standards.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模板验证失败
|
||||||
|
|
||||||
|
验证脚本会生成详细报告文件:
|
||||||
|
|
||||||
|
- `tests/templates/python_validation_report.txt`
|
||||||
|
- `tests/templates/cpp_validation_report.txt`
|
||||||
|
- `tests/templates/ci_validation_report.txt`
|
||||||
|
|
||||||
|
### 文档链接检查失败
|
||||||
|
|
||||||
|
查看详细报告:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /tmp/doc_links_report.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤝 贡献指南
|
||||||
|
|
||||||
|
添加新功能时,请同步更新相应的测试:
|
||||||
|
|
||||||
|
1. **修改脚本**(`scripts/`)→ 更新对应的 `.bats` 测试
|
||||||
|
2. **修改模板**(`templates/`)→ 更新对应的验证脚本
|
||||||
|
3. **修改文档**(`docs/`, `.agents/`)→ 运行文档链接检查
|
||||||
|
4. **修改 CI workflow**(`.gitea/workflows/`)→ 验证 YAML 语法
|
||||||
|
|
||||||
|
## 📖 相关文档
|
||||||
|
|
||||||
|
- [开发规范索引](../docs/index.md)
|
||||||
|
- [提交信息规范](../docs/common/commit_message.md)
|
||||||
|
- [Gitea Actions 文档](https://docs.gitea.com/usage/actions/overview)
|
||||||
|
- [bats-core 文档](https://bats-core.readthedocs.io/)
|
||||||
|
|
||||||
|
## ❓ 常见问题
|
||||||
|
|
||||||
|
### Q: 为什么测试在 CI 通过,但本地失败?
|
||||||
|
|
||||||
|
A: 可能原因:
|
||||||
|
|
||||||
|
- 环境差异(工具版本、路径)
|
||||||
|
- 权限问题
|
||||||
|
- Git 配置差异
|
||||||
|
|
||||||
|
建议使用 Docker 容器复现 CI 环境。
|
||||||
|
|
||||||
|
### Q: 如何跳过某些测试?
|
||||||
|
|
||||||
|
A: bats 支持 `skip` 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
@test "某个测试" {
|
||||||
|
skip "原因说明"
|
||||||
|
# 测试代码
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q: 测试运行很慢,如何加速?
|
||||||
|
|
||||||
|
A: 建议:
|
||||||
|
|
||||||
|
1. 使用 bats 的 `--jobs` 参数并行运行
|
||||||
|
2. 只运行变更相关的测试
|
||||||
|
3. 使用 CI 的缓存机制
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**测试套件维护者**: Playbook 团队
|
||||||
|
**最后更新**: 2026-01-07
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
# 文档链接有效性检查脚本
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔗 文档链接有效性检查"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
TOTAL_LINKS=0
|
||||||
|
VALID_LINKS=0
|
||||||
|
BROKEN_LINKS=0
|
||||||
|
SKIPPED_LINKS=0
|
||||||
|
|
||||||
|
BROKEN_LINKS_FILE="/tmp/broken_links.txt"
|
||||||
|
REPORT_FILE="/tmp/doc_links_report.txt"
|
||||||
|
|
||||||
|
> "$BROKEN_LINKS_FILE"
|
||||||
|
> "$REPORT_FILE"
|
||||||
|
|
||||||
|
echo "📁 Playbook 根目录: $PLAYBOOK_ROOT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 辅助函数
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
check_file_link() {
|
||||||
|
local source_file="$1"
|
||||||
|
local link_path="$2"
|
||||||
|
local link_line="$3"
|
||||||
|
|
||||||
|
TOTAL_LINKS=$((TOTAL_LINKS + 1))
|
||||||
|
|
||||||
|
# 处理相对路径
|
||||||
|
local source_dir
|
||||||
|
source_dir="$(dirname "$source_file")"
|
||||||
|
|
||||||
|
# 解析链接路径
|
||||||
|
local target_path="$link_path"
|
||||||
|
|
||||||
|
# 移除锚点
|
||||||
|
target_path="${target_path%%#*}"
|
||||||
|
|
||||||
|
# 跳过空链接
|
||||||
|
if [ -z "$target_path" ]; then
|
||||||
|
SKIPPED_LINKS=$((SKIPPED_LINKS + 1))
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 跳过外部链接(http/https)
|
||||||
|
if echo "$target_path" | grep -qE "^https?://"; then
|
||||||
|
SKIPPED_LINKS=$((SKIPPED_LINKS + 1))
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 跳过 mailto 链接
|
||||||
|
if echo "$target_path" | grep -q "^mailto:"; then
|
||||||
|
SKIPPED_LINKS=$((SKIPPED_LINKS + 1))
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建绝对路径
|
||||||
|
local absolute_path
|
||||||
|
if echo "$target_path" | grep -q "^/"; then
|
||||||
|
# 绝对路径(从仓库根)
|
||||||
|
absolute_path="$PLAYBOOK_ROOT$target_path"
|
||||||
|
else
|
||||||
|
# 相对路径
|
||||||
|
absolute_path="$source_dir/$target_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 规范化路径
|
||||||
|
absolute_path="$(cd "$(dirname "$absolute_path")" 2>/dev/null && pwd)/$(basename "$absolute_path")" || absolute_path=""
|
||||||
|
|
||||||
|
# 检查文件是否存在
|
||||||
|
if [ -n "$absolute_path" ] && [ -e "$absolute_path" ]; then
|
||||||
|
VALID_LINKS=$((VALID_LINKS + 1))
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
BROKEN_LINKS=$((BROKEN_LINKS + 1))
|
||||||
|
echo "❌ 断链: $source_file:$link_line" >> "$BROKEN_LINKS_FILE"
|
||||||
|
echo " 链接: $link_path" >> "$BROKEN_LINKS_FILE"
|
||||||
|
echo " 目标: $absolute_path" >> "$BROKEN_LINKS_FILE"
|
||||||
|
echo "" >> "$BROKEN_LINKS_FILE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_links() {
|
||||||
|
awk '
|
||||||
|
BEGIN { in_code = 0 }
|
||||||
|
{
|
||||||
|
line = $0
|
||||||
|
if (line ~ /^```/) { in_code = !in_code; next }
|
||||||
|
if (in_code) next
|
||||||
|
|
||||||
|
gsub(/`[^`]*`/, "", line)
|
||||||
|
|
||||||
|
while (match(line, /\[[^]]+\]\(([^)]+)\)/, m)) {
|
||||||
|
print NR "\t" m[1]
|
||||||
|
line = substr(line, RSTART + RLENGTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(line, /^\[[^]]+\]:[[:space:]]*(.+)/, ref)) {
|
||||||
|
print NR "\t" ref[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 查找并检查所有 Markdown 文件
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 扫描 Markdown 文件..."
|
||||||
|
|
||||||
|
cd "$PLAYBOOK_ROOT"
|
||||||
|
|
||||||
|
MD_FILES=$(find . -name "*.md" \
|
||||||
|
-not -path "*/node_modules/*" \
|
||||||
|
-not -path "*/.git/*" \
|
||||||
|
-not -path "*/build/*" \
|
||||||
|
-not -path "*/dist/*" \
|
||||||
|
2>/dev/null || true)
|
||||||
|
|
||||||
|
FILE_COUNT=$(echo "$MD_FILES" | grep -c "^" || echo 0)
|
||||||
|
echo "📄 找到 $FILE_COUNT 个 Markdown 文件"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
CURRENT_FILE_NUM=0
|
||||||
|
|
||||||
|
for md_file in $MD_FILES; do
|
||||||
|
CURRENT_FILE_NUM=$((CURRENT_FILE_NUM + 1))
|
||||||
|
|
||||||
|
# 显示进度
|
||||||
|
if [ "$CURRENT_FILE_NUM" -eq 1 ] || [ $((CURRENT_FILE_NUM % 10)) -eq 0 ] || [ "$CURRENT_FILE_NUM" -eq "$FILE_COUNT" ]; then
|
||||||
|
echo "📖 处理中... [$CURRENT_FILE_NUM/$FILE_COUNT] $md_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
links_file="$(mktemp)"
|
||||||
|
extract_links "$md_file" > "$links_file"
|
||||||
|
while IFS="$(printf '\t')" read -r line_num link; do
|
||||||
|
check_file_link "$md_file" "$link" "$line_num" || true
|
||||||
|
done < "$links_file"
|
||||||
|
rm -f "$links_file"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ 扫描完成"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 生成检查报告
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "📊 链接检查结果统计"
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔗 总链接数: $TOTAL_LINKS"
|
||||||
|
echo "✅ 有效链接: $VALID_LINKS"
|
||||||
|
echo "⏭️ 跳过链接: $SKIPPED_LINKS (外部/mailto)"
|
||||||
|
echo "❌ 断开链接: $BROKEN_LINKS"
|
||||||
|
|
||||||
|
if [ "$TOTAL_LINKS" -gt 0 ]; then
|
||||||
|
CHECKED_LINKS=$((TOTAL_LINKS - SKIPPED_LINKS))
|
||||||
|
if [ "$CHECKED_LINKS" -gt 0 ]; then
|
||||||
|
SUCCESS_RATE=$(awk "BEGIN {printf \"%.1f\", ($VALID_LINKS * 100.0) / $CHECKED_LINKS}")
|
||||||
|
echo "📈 有效率: $SUCCESS_RATE%"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 写入报告
|
||||||
|
{
|
||||||
|
echo "文档链接有效性检查报告"
|
||||||
|
echo "========================"
|
||||||
|
echo ""
|
||||||
|
echo "检查时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo "检查目录: $PLAYBOOK_ROOT"
|
||||||
|
echo ""
|
||||||
|
echo "统计结果:"
|
||||||
|
echo " 总链接数: $TOTAL_LINKS"
|
||||||
|
echo " 有效链接: $VALID_LINKS"
|
||||||
|
echo " 跳过链接: $SKIPPED_LINKS"
|
||||||
|
echo " 断开链接: $BROKEN_LINKS"
|
||||||
|
echo ""
|
||||||
|
if [ "$BROKEN_LINKS" -gt 0 ]; then
|
||||||
|
echo "断开链接详情:"
|
||||||
|
echo "=============="
|
||||||
|
cat "$BROKEN_LINKS_FILE"
|
||||||
|
fi
|
||||||
|
} > "$REPORT_FILE"
|
||||||
|
|
||||||
|
if [ "$BROKEN_LINKS" -gt 0 ]; then
|
||||||
|
echo "❌ 发现 $BROKEN_LINKS 个断开的链接"
|
||||||
|
echo ""
|
||||||
|
echo "断开链接详情:"
|
||||||
|
cat "$BROKEN_LINKS_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "📄 详细报告: $REPORT_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# 清理临时文件(保留报告用于 CI)
|
||||||
|
# rm -f "$BROKEN_LINKS_FILE"
|
||||||
|
|
||||||
|
# 返回结果
|
||||||
|
if [ "$BROKEN_LINKS" -eq 0 ]; then
|
||||||
|
echo "✅ 所有文档链接检查通过"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ 文档链接检查失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
# install_codex_skills.sh 测试套件
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
export TEST_DIR="$(mktemp -d)"
|
||||||
|
export PLAYBOOK_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
|
||||||
|
export SCRIPT_PATH="$PLAYBOOK_ROOT/scripts/install_codex_skills.sh"
|
||||||
|
|
||||||
|
# 创建临时 HOME 避免污染真实环境
|
||||||
|
export ORIGINAL_HOME="$HOME"
|
||||||
|
export HOME="$TEST_DIR/home"
|
||||||
|
mkdir -p "$HOME"
|
||||||
|
|
||||||
|
export CODEX_HOME="$HOME/.codex"
|
||||||
|
export SKILLS_DST_ROOT="$CODEX_HOME/skills"
|
||||||
|
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
export HOME="$ORIGINAL_HOME"
|
||||||
|
|
||||||
|
if [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ]; then
|
||||||
|
chmod -R u+w "$TEST_DIR" 2>/dev/null || true
|
||||||
|
rm -rf "$TEST_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_if_no_skills() {
|
||||||
|
if [ ! -d "$PLAYBOOK_ROOT/codex/skills" ]; then
|
||||||
|
skip "No codex/skills directory found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
first_skill_name() {
|
||||||
|
ls -1 "$PLAYBOOK_ROOT/codex/skills" 2>/dev/null | head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 基础功能测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "install_codex_skills.sh 脚本存在且可执行" {
|
||||||
|
[ -f "$SCRIPT_PATH" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "安装 - 创建 skills 目录" {
|
||||||
|
skip_if_no_skills
|
||||||
|
|
||||||
|
[ ! -d "$SKILLS_DST_ROOT" ]
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH"
|
||||||
|
|
||||||
|
[ -d "$SKILLS_DST_ROOT" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "安装 - 复制 skill 目录" {
|
||||||
|
skip_if_no_skills
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH"
|
||||||
|
|
||||||
|
SKILL_DIRS=$(find "$SKILLS_DST_ROOT" -mindepth 1 -maxdepth 1 -type d)
|
||||||
|
[ -n "$SKILL_DIRS" ]
|
||||||
|
|
||||||
|
for dir in $SKILL_DIRS; do
|
||||||
|
[ -f "$dir/SKILL.md" ]
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "安装 - 指定单个 skill" {
|
||||||
|
skip_if_no_skills
|
||||||
|
|
||||||
|
SKILL_NAME="$(first_skill_name)"
|
||||||
|
[ -n "$SKILL_NAME" ]
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$SKILL_NAME"
|
||||||
|
|
||||||
|
[ -d "$SKILLS_DST_ROOT/$SKILL_NAME" ]
|
||||||
|
COUNT=$(find "$SKILLS_DST_ROOT" -mindepth 1 -maxdepth 1 -type d | wc -l)
|
||||||
|
[ "$COUNT" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "备份 - 同名 skill 目录会创建备份" {
|
||||||
|
skip_if_no_skills
|
||||||
|
|
||||||
|
SKILL_NAME="$(first_skill_name)"
|
||||||
|
[ -n "$SKILL_NAME" ]
|
||||||
|
|
||||||
|
mkdir -p "$SKILLS_DST_ROOT/$SKILL_NAME"
|
||||||
|
echo "marker" > "$SKILLS_DST_ROOT/$SKILL_NAME/marker.txt"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$SKILL_NAME"
|
||||||
|
|
||||||
|
BACKUP_DIRS=$(find "$SKILLS_DST_ROOT" -maxdepth 1 -type d -name "$SKILL_NAME.bak.*")
|
||||||
|
[ -n "$BACKUP_DIRS" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "错误处理 - 指定不存在 skill 报错" {
|
||||||
|
skip_if_no_skills
|
||||||
|
|
||||||
|
run sh "$SCRIPT_PATH" nonexistent-skill
|
||||||
|
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "幂等性 - 多次安装结果一致" {
|
||||||
|
skip_if_no_skills
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH"
|
||||||
|
CHECKSUM1=$(find "$SKILLS_DST_ROOT" -type f -name "SKILL.md" ! -path "$SKILLS_DST_ROOT"'/*.bak.*/*' -exec md5sum {} \; | sort | md5sum)
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH"
|
||||||
|
CHECKSUM2=$(find "$SKILLS_DST_ROOT" -type f -name "SKILL.md" ! -path "$SKILLS_DST_ROOT"'/*.bak.*/*' -exec md5sum {} \; | sort | md5sum)
|
||||||
|
|
||||||
|
[ "$CHECKSUM1" = "$CHECKSUM2" ]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
# sync_standards.sh 测试套件
|
||||||
|
|
||||||
|
# 测试辅助函数
|
||||||
|
setup() {
|
||||||
|
# 创建临时测试目录
|
||||||
|
export TEST_DIR="$(mktemp -d)"
|
||||||
|
export PLAYBOOK_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
|
||||||
|
# 初始化测试项目
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
git init
|
||||||
|
git config user.name "Test User"
|
||||||
|
git config user.email "test@example.com"
|
||||||
|
|
||||||
|
# 模拟 playbook 快照目录
|
||||||
|
mkdir -p docs/standards/playbook
|
||||||
|
cp -r "$PLAYBOOK_ROOT"/{.agents,.gitattributes,docs,scripts} docs/standards/playbook/ 2>/dev/null || true
|
||||||
|
|
||||||
|
export SCRIPT_PATH="$TEST_DIR/docs/standards/playbook/scripts/sync_standards.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
# 清理测试目录
|
||||||
|
if [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ]; then
|
||||||
|
rm -rf "$TEST_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 基础功能测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "sync_standards.sh 脚本存在且可执行" {
|
||||||
|
[ -f "$SCRIPT_PATH" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "sync_standards.sh 无参数时同步 tsl 规则集" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
sh "$SCRIPT_PATH"
|
||||||
|
|
||||||
|
# 验证输出目录
|
||||||
|
[ -d ".agents/tsl" ]
|
||||||
|
[ -f ".agents/tsl/index.md" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "sync_standards.sh tsl - 同步 TSL 规则集" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
# 验证必须文件
|
||||||
|
[ -d ".agents/tsl" ]
|
||||||
|
[ -f ".agents/tsl/index.md" ]
|
||||||
|
[ -f ".agents/tsl/auth.md" ]
|
||||||
|
[ -f ".agents/tsl/code_quality.md" ]
|
||||||
|
[ -f ".agents/tsl/performance.md" ]
|
||||||
|
[ -f ".agents/tsl/testing.md" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "sync_standards.sh cpp - 同步 C++ 规则集" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
sh "$SCRIPT_PATH" cpp
|
||||||
|
|
||||||
|
# 验证必须文件
|
||||||
|
[ -d ".agents/cpp" ]
|
||||||
|
[ -f ".agents/cpp/index.md" ]
|
||||||
|
[ -f ".agents/cpp/auth.md" ]
|
||||||
|
[ -f ".agents/cpp/code_quality.md" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "sync_standards.sh tsl cpp - 同步多个规则集" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
sh "$SCRIPT_PATH" tsl cpp
|
||||||
|
|
||||||
|
# 验证两个规则集都存在
|
||||||
|
[ -d ".agents/tsl" ]
|
||||||
|
[ -d ".agents/cpp" ]
|
||||||
|
[ -f ".agents/index.md" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# .gitattributes 同步测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test ".gitattributes - 默认模式创建 managed block" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
[ ! -f ".gitattributes" ]
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
[ -f ".gitattributes" ]
|
||||||
|
grep -q "# BEGIN playbook .gitattributes" .gitattributes
|
||||||
|
grep -q "# END playbook .gitattributes" .gitattributes
|
||||||
|
}
|
||||||
|
|
||||||
|
@test ".gitattributes - 保留现有内容" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
echo "# My custom rules" > .gitattributes
|
||||||
|
echo "*.custom binary" >> .gitattributes
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
grep -q "# My custom rules" .gitattributes
|
||||||
|
grep -q "*.custom binary" .gitattributes
|
||||||
|
}
|
||||||
|
|
||||||
|
@test ".gitattributes - 更新已存在的 managed block" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
cat > .gitattributes << 'EOF'
|
||||||
|
# BEGIN playbook .gitattributes
|
||||||
|
# Old content
|
||||||
|
# END playbook .gitattributes
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
# 验证 block 已更新(不再包含 "Old content")
|
||||||
|
! grep -q "Old content" .gitattributes
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# AGENTS.md 自动生成测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "AGENTS.md - 不存在时自动创建" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
[ ! -f "AGENTS.md" ]
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
[ -f "AGENTS.md" ]
|
||||||
|
grep -q ".agents/" AGENTS.md
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "AGENTS.md - 已存在时不覆盖" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
echo "# My custom AGENTS.md" > AGENTS.md
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
grep -q "# My custom AGENTS.md" AGENTS.md
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 备份功能测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "备份 - .gitattributes 更新前创建备份" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
echo "# Original content" > .gitattributes
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
# 验证备份文件存在
|
||||||
|
[ -f ".gitattributes.bak."* ] || [ -f ".gitattributes.bak" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "备份 - .agents/ 更新前创建备份" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
mkdir -p .agents/tsl
|
||||||
|
echo "# Old index" > .agents/tsl/index.md
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
# 验证备份目录存在
|
||||||
|
[ -d ".agents/tsl.bak."* ] || [ -d ".agents/tsl.bak" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 多语言项目测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "多语言 - TSL + C++ + Python 规则集共存" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
|
||||||
|
# 复制 Python 规则集(如果存在)
|
||||||
|
if [ -d "$PLAYBOOK_ROOT/.agents/python" ]; then
|
||||||
|
cp -r "$PLAYBOOK_ROOT/.agents/python" docs/standards/playbook/.agents/
|
||||||
|
fi
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl cpp
|
||||||
|
|
||||||
|
# 验证规则集不互相覆盖
|
||||||
|
[ -d ".agents/tsl" ]
|
||||||
|
[ -d ".agents/cpp" ]
|
||||||
|
|
||||||
|
# 验证索引文件正确引用
|
||||||
|
[ -f ".agents/index.md" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 错误处理测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "错误处理 - 未找到 playbook 快照时报错" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
rm -rf docs/standards/playbook/.agents
|
||||||
|
|
||||||
|
run sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "错误处理 - 无效语言参数时报错" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
|
||||||
|
run sh "$SCRIPT_PATH" invalid_lang
|
||||||
|
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 环境变量配置测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "环境变量 - SYNC_GITATTR_MODE=skip 跳过 .gitattributes" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
export SYNC_GITATTR_MODE=skip
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
[ ! -f ".gitattributes" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "环境变量 - SYNC_GITATTR_MODE=overwrite 覆盖 .gitattributes" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
echo "# Custom content" > .gitattributes
|
||||||
|
export SYNC_GITATTR_MODE=overwrite
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
|
||||||
|
# 验证自定义内容被覆盖
|
||||||
|
! grep -q "# Custom content" .gitattributes
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 幂等性测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "幂等性 - 多次执行结果一致" {
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
|
||||||
|
# 第一次同步
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
CHECKSUM1=$(find .agents/tsl -type f -exec md5sum {} \; | sort | md5sum)
|
||||||
|
|
||||||
|
# 第二次同步
|
||||||
|
sh "$SCRIPT_PATH" tsl
|
||||||
|
CHECKSUM2=$(find .agents/tsl -type f -exec md5sum {} \; | sort | md5sum)
|
||||||
|
|
||||||
|
[ "$CHECKSUM1" = "$CHECKSUM2" ]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,284 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
# vendor_playbook.sh 测试套件
|
||||||
|
|
||||||
|
# 测试辅助函数
|
||||||
|
setup() {
|
||||||
|
# 创建临时测试目录
|
||||||
|
export TEST_DIR="$(mktemp -d)"
|
||||||
|
export PLAYBOOK_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
|
||||||
|
export SCRIPT_PATH="$PLAYBOOK_ROOT/scripts/vendor_playbook.sh"
|
||||||
|
|
||||||
|
# 创建目标项目目录
|
||||||
|
export TARGET_DIR="$(mktemp -d)"
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
git init
|
||||||
|
git config user.name "Test User"
|
||||||
|
git config user.email "test@example.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
# 清理测试目录
|
||||||
|
if [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ]; then
|
||||||
|
chmod -R u+w "$TEST_DIR" 2>/dev/null || true
|
||||||
|
rm -rf "$TEST_DIR"
|
||||||
|
fi
|
||||||
|
if [ -n "$TARGET_DIR" ] && [ -d "$TARGET_DIR" ]; then
|
||||||
|
chmod -R u+w "$TARGET_DIR" 2>/dev/null || true
|
||||||
|
rm -rf "$TARGET_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 基础功能测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "vendor_playbook.sh 脚本存在且可执行" {
|
||||||
|
[ -f "$SCRIPT_PATH" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "vendor_playbook.sh - 无参数时显示用法" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
run sh "$SCRIPT_PATH"
|
||||||
|
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "vendor_playbook.sh - 单语言 vendoring (tsl)" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 验证快照目录结构
|
||||||
|
[ -d "docs/standards/playbook" ]
|
||||||
|
[ -d "docs/standards/playbook/docs/common" ]
|
||||||
|
[ -d "docs/standards/playbook/docs/tsl" ]
|
||||||
|
[ -d "docs/standards/playbook/.agents/tsl" ]
|
||||||
|
[ -f "docs/standards/playbook/scripts/sync_standards.sh" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "vendor_playbook.sh - 多语言 vendoring (tsl cpp)" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl cpp
|
||||||
|
|
||||||
|
# 验证包含两种语言的文档
|
||||||
|
[ -d "docs/standards/playbook/docs/tsl" ]
|
||||||
|
[ -d "docs/standards/playbook/docs/cpp" ]
|
||||||
|
[ -d "docs/standards/playbook/.agents/tsl" ]
|
||||||
|
[ -d "docs/standards/playbook/.agents/cpp" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 自动同步测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "vendor_playbook.sh - 自动执行 sync_standards" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 验证根目录已同步规则集
|
||||||
|
[ -d ".agents/tsl" ]
|
||||||
|
[ -f ".agents/tsl/index.md" ]
|
||||||
|
[ -f ".gitattributes" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "vendor_playbook.sh - 多语言自动同步" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl cpp
|
||||||
|
|
||||||
|
# 验证两个规则集都已同步
|
||||||
|
[ -d ".agents/tsl" ]
|
||||||
|
[ -d ".agents/cpp" ]
|
||||||
|
[ -f ".agents/index.md" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# SOURCE.md 生成测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "vendor_playbook.sh - 生成 SOURCE.md" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
[ -f "docs/standards/playbook/SOURCE.md" ]
|
||||||
|
grep -q "Source:" docs/standards/playbook/SOURCE.md
|
||||||
|
grep -q "Commit:" docs/standards/playbook/SOURCE.md
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "vendor_playbook.sh - SOURCE.md 包含 commit hash" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 验证包含 commit hash(40个十六进制字符)
|
||||||
|
grep -E "[0-9a-f]{40}" docs/standards/playbook/SOURCE.md
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "vendor_playbook.sh - SOURCE.md 包含时间戳" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 验证包含日期格式 YYYY-MM-DD
|
||||||
|
grep -E "[0-9]{4}-[0-9]{2}-[0-9]{2}" docs/standards/playbook/SOURCE.md
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 裁剪功能测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "裁剪 - 仅包含指定语言的文档" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 验证包含 TSL 文档
|
||||||
|
[ -d "docs/standards/playbook/docs/tsl" ]
|
||||||
|
|
||||||
|
# 验证不包含其他语言(如果原本有 Python)
|
||||||
|
if [ -d "$PLAYBOOK_ROOT/docs/python" ]; then
|
||||||
|
[ ! -d "docs/standards/playbook/docs/python" ]
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "裁剪 - 始终包含 common 目录" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
[ -d "docs/standards/playbook/docs/common" ]
|
||||||
|
[ -f "docs/standards/playbook/docs/common/commit_message.md" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "裁剪 - 包含对应的模板文件" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" cpp
|
||||||
|
|
||||||
|
# 验证包含 C++ 模板
|
||||||
|
[ -d "docs/standards/playbook/templates/cpp" ]
|
||||||
|
|
||||||
|
# 验证不包含 Python 模板(如果指定了 cpp)
|
||||||
|
if [ -d "$PLAYBOOK_ROOT/templates/python" ]; then
|
||||||
|
[ ! -d "docs/standards/playbook/templates/python" ]
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "裁剪 - 包含通用 CI 模板" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
[ -d "docs/standards/playbook/templates/ci" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 目标目录处理测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "目标目录 - 已存在时覆盖更新" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
# 首次 vendor
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 在快照中添加标记文件
|
||||||
|
touch docs/standards/playbook/OLD_MARKER
|
||||||
|
|
||||||
|
# 再次 vendor
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 验证旧标记不存在(已被覆盖)
|
||||||
|
[ ! -f "docs/standards/playbook/OLD_MARKER" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "目标目录 - 创建必要的父目录" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
# 确保 docs/standards 不存在
|
||||||
|
[ ! -d "docs/standards" ]
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
[ -d "docs/standards/playbook" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 错误处理测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "错误处理 - 目标目录不存在时报错" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
run sh "$SCRIPT_PATH" /nonexistent/path tsl
|
||||||
|
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "错误处理 - 无效语言参数时报错" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
run sh "$SCRIPT_PATH" "$TARGET_DIR" invalid_lang
|
||||||
|
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "错误处理 - 目标目录不是 git 仓库时警告" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
rm -rf .git
|
||||||
|
|
||||||
|
run sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 应该给出警告但不失败
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 完整性测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "完整性 - 验证所有必要文件已复制" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 验证关键文件
|
||||||
|
[ -f "docs/standards/playbook/README.md" ]
|
||||||
|
[ -f "docs/standards/playbook/docs/index.md" ]
|
||||||
|
[ -f "docs/standards/playbook/.gitattributes" ]
|
||||||
|
[ -f "docs/standards/playbook/scripts/sync_standards.sh" ]
|
||||||
|
[ -f "docs/standards/playbook/SOURCE.md" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "完整性 - 验证脚本可执行性" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
|
||||||
|
# 验证同步脚本可执行
|
||||||
|
run sh docs/standards/playbook/scripts/sync_standards.sh tsl
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# 多次执行测试
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@test "幂等性 - 多次 vendor 结果一致" {
|
||||||
|
cd "$TARGET_DIR"
|
||||||
|
|
||||||
|
# 第一次 vendor
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
CHECKSUM1=$(find docs/standards/playbook -type f -name "*.md" ! -name "SOURCE.md" -exec md5sum {} \; | sort | md5sum)
|
||||||
|
|
||||||
|
# 第二次 vendor
|
||||||
|
sh "$SCRIPT_PATH" "$TARGET_DIR" tsl
|
||||||
|
CHECKSUM2=$(find docs/standards/playbook -type f -name "*.md" ! -name "SOURCE.md" -exec md5sum {} \; | sort | md5sum)
|
||||||
|
|
||||||
|
[ "$CHECKSUM1" = "$CHECKSUM2" ]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,332 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
# CI 模板验证脚本
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔧 CI 模板验证"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
TEMPLATES_DIR="$PLAYBOOK_ROOT/templates/ci"
|
||||||
|
|
||||||
|
VALIDATION_PASSED=0
|
||||||
|
VALIDATION_FAILED=0
|
||||||
|
ERRORS_FILE="/tmp/ci_template_validation_errors.txt"
|
||||||
|
REPORT_FILE="$SCRIPT_DIR/ci_validation_report.txt"
|
||||||
|
|
||||||
|
> "$ERRORS_FILE"
|
||||||
|
> "$REPORT_FILE"
|
||||||
|
|
||||||
|
echo "📁 模板目录: $TEMPLATES_DIR"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 辅助函数
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
validate_file_exists() {
|
||||||
|
local file="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
echo " ✅ $description: $(basename "$file")"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ❌ $description: $(basename "$file") - 文件不存在"
|
||||||
|
echo "文件不存在: $file" >> "$ERRORS_FILE"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_yaml_syntax() {
|
||||||
|
local file="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
if ! command -v yamllint >/dev/null 2>&1; then
|
||||||
|
echo " ⚠️ $description: 跳过(yamllint 未安装)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if yamllint -d relaxed "$file" >/dev/null 2>&1; then
|
||||||
|
echo " ✅ $description: YAML 语法正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ❌ $description: YAML 语法错误"
|
||||||
|
echo "YAML 语法错误: $file" >> "$ERRORS_FILE"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_workflow_structure() {
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
echo " 📋 检查 workflow 结构:"
|
||||||
|
|
||||||
|
# 检查必要字段
|
||||||
|
if grep -q "^name:" "$file"; then
|
||||||
|
echo " ✓ 包含 name 字段"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ 缺少 name 字段"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "^on:" "$file"; then
|
||||||
|
echo " ✓ 包含 on 触发器"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ 缺少 on 触发器"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "^jobs:" "$file"; then
|
||||||
|
echo " ✓ 包含 jobs 定义"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ 缺少 jobs 定义"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 runs-on
|
||||||
|
if grep -q "runs-on:" "$file"; then
|
||||||
|
runner=$(grep "runs-on:" "$file" | head -1 | awk '{print $2}')
|
||||||
|
echo " ✓ 配置了 runner: $runner"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ 缺少 runs-on 配置"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 steps
|
||||||
|
if grep -q "steps:" "$file"; then
|
||||||
|
step_count=$(grep -c "^ - name:" "$file" || echo 0)
|
||||||
|
echo " ✓ 包含 $step_count 个步骤"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ 缺少 steps 定义"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 查找并验证 Gitea workflow 文件
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 查找 Gitea workflow 文件"
|
||||||
|
GITEA_WORKFLOWS_DIR="$TEMPLATES_DIR/gitea/.gitea/workflows"
|
||||||
|
|
||||||
|
if [ ! -d "$GITEA_WORKFLOWS_DIR" ]; then
|
||||||
|
echo "⚠️ Gitea workflows 目录不存在: $GITEA_WORKFLOWS_DIR"
|
||||||
|
echo "跳过 Gitea workflow 验证"
|
||||||
|
else
|
||||||
|
WORKFLOW_FILES=$(find "$GITEA_WORKFLOWS_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -z "$WORKFLOW_FILES" ]; then
|
||||||
|
echo "⚠️ 未找到 Gitea workflow 文件"
|
||||||
|
else
|
||||||
|
for workflow in $WORKFLOW_FILES; do
|
||||||
|
echo ""
|
||||||
|
echo "🔍 验证 $(basename "$workflow")"
|
||||||
|
|
||||||
|
if validate_file_exists "$workflow" "$(basename "$workflow")"; then
|
||||||
|
# 验证 YAML 语法
|
||||||
|
validate_yaml_syntax "$workflow" "$(basename "$workflow")"
|
||||||
|
|
||||||
|
# 验证 workflow 结构
|
||||||
|
validate_workflow_structure "$workflow"
|
||||||
|
|
||||||
|
# 检查是否包含中文注释(符合项目风格)
|
||||||
|
if grep -q "# .*[\u4e00-\u9fa5]" "$workflow" 2>/dev/null || grep -qP "[\x{4e00}-\x{9fa5}]" "$workflow" 2>/dev/null; then
|
||||||
|
echo " ✓ 包含中文注释(符合项目风格)"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查配置区域标记
|
||||||
|
if grep -q "# ====.*配置.*====" "$workflow"; then
|
||||||
|
echo " ✓ 包含配置区域标记"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查环境变量配置
|
||||||
|
if grep -q "^env:" "$workflow"; then
|
||||||
|
echo " ✓ 包含环境变量配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 查找并验证 GitHub Actions workflow 文件
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 查找 GitHub Actions workflow 文件"
|
||||||
|
GITHUB_WORKFLOWS_DIR="$TEMPLATES_DIR/github/.github/workflows"
|
||||||
|
|
||||||
|
if [ -d "$GITHUB_WORKFLOWS_DIR" ]; then
|
||||||
|
WORKFLOW_FILES=$(find "$GITHUB_WORKFLOWS_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$WORKFLOW_FILES" ]; then
|
||||||
|
for workflow in $WORKFLOW_FILES; do
|
||||||
|
echo ""
|
||||||
|
echo "🔍 验证 $(basename "$workflow")"
|
||||||
|
|
||||||
|
if validate_file_exists "$workflow" "$(basename "$workflow")"; then
|
||||||
|
# 验证 YAML 语法
|
||||||
|
validate_yaml_syntax "$workflow" "$(basename "$workflow")"
|
||||||
|
|
||||||
|
# 验证 workflow 结构
|
||||||
|
validate_workflow_structure "$workflow"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ℹ️ GitHub Actions workflows 目录不存在(可选)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证特定 workflow 模板
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证特定 workflow 模板"
|
||||||
|
|
||||||
|
# 检查 standards-check workflow
|
||||||
|
STANDARDS_CHECK="$GITEA_WORKFLOWS_DIR/standards-check.yml"
|
||||||
|
if [ -f "$STANDARDS_CHECK" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "📋 验证 standards-check.yml:"
|
||||||
|
|
||||||
|
# 检查是否包含格式化检查
|
||||||
|
if grep -q "格式化\|format" "$STANDARDS_CHECK"; then
|
||||||
|
echo " ✓ 包含格式化检查"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否包含 lint 检查
|
||||||
|
if grep -q "lint\|检查" "$STANDARDS_CHECK"; then
|
||||||
|
echo " ✓ 包含 lint 检查"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ℹ️ 未找到 standards-check.yml(可选)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 test workflow
|
||||||
|
TEST_WORKFLOW="$GITEA_WORKFLOWS_DIR/test.yml"
|
||||||
|
if [ -f "$TEST_WORKFLOW" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "📋 验证 test.yml:"
|
||||||
|
|
||||||
|
# 检查是否包含测试步骤
|
||||||
|
if grep -q "测试\|test" "$TEST_WORKFLOW"; then
|
||||||
|
echo " ✓ 包含测试步骤"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否配置了测试矩阵
|
||||||
|
if grep -q "strategy:" "$TEST_WORKFLOW" && grep -q "matrix:" "$TEST_WORKFLOW"; then
|
||||||
|
echo " ✓ 配置了测试矩阵"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ℹ️ 未找到 test.yml(可选)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 README 文档
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 CI 模板文档"
|
||||||
|
|
||||||
|
if [ -d "$TEMPLATES_DIR" ]; then
|
||||||
|
# 查找 README
|
||||||
|
README_FILE=""
|
||||||
|
for name in README.md readme.md README README.txt; do
|
||||||
|
if [ -f "$TEMPLATES_DIR/$name" ]; then
|
||||||
|
README_FILE="$TEMPLATES_DIR/$name"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$README_FILE" ]; then
|
||||||
|
echo " ✅ 找到说明文档: $(basename "$README_FILE")"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
|
||||||
|
# 检查文档内容
|
||||||
|
if grep -qi "使用\|usage\|how to" "$README_FILE"; then
|
||||||
|
echo " ✓ 包含使用说明"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ⚠️ 未找到 README 文档(建议添加)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 生成验证报告
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "📊 验证结果统计"
|
||||||
|
echo "========================================"
|
||||||
|
echo "✅ 通过: $VALIDATION_PASSED"
|
||||||
|
echo "❌ 失败: $VALIDATION_FAILED"
|
||||||
|
|
||||||
|
if [ $((VALIDATION_PASSED + VALIDATION_FAILED)) -gt 0 ]; then
|
||||||
|
echo "📈 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
|
||||||
|
else
|
||||||
|
echo "📈 通过率: N/A (无测试项)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 写入报告文件
|
||||||
|
{
|
||||||
|
echo "CI 模板验证报告"
|
||||||
|
echo "===================="
|
||||||
|
echo ""
|
||||||
|
echo "验证时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo "模板目录: $TEMPLATES_DIR"
|
||||||
|
echo ""
|
||||||
|
echo "统计结果:"
|
||||||
|
echo " 通过: $VALIDATION_PASSED"
|
||||||
|
echo " 失败: $VALIDATION_FAILED"
|
||||||
|
if [ $((VALIDATION_PASSED + VALIDATION_FAILED)) -gt 0 ]; then
|
||||||
|
echo " 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
if [ -s "$ERRORS_FILE" ]; then
|
||||||
|
echo "错误详情:"
|
||||||
|
cat "$ERRORS_FILE"
|
||||||
|
fi
|
||||||
|
} > "$REPORT_FILE"
|
||||||
|
|
||||||
|
echo "📄 详细报告: $REPORT_FILE"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -f "$ERRORS_FILE"
|
||||||
|
|
||||||
|
# 返回结果
|
||||||
|
if [ "$VALIDATION_FAILED" -eq 0 ]; then
|
||||||
|
echo "✅ 所有 CI 模板验证通过"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ CI 模板验证失败 ($VALIDATION_FAILED 个错误)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,353 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
# C++ 模板验证脚本
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "⚙️ C++ 模板验证"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
TEMPLATES_DIR="$PLAYBOOK_ROOT/templates/cpp"
|
||||||
|
|
||||||
|
VALIDATION_PASSED=0
|
||||||
|
VALIDATION_FAILED=0
|
||||||
|
ERRORS_FILE="/tmp/cpp_template_validation_errors.txt"
|
||||||
|
REPORT_FILE="$SCRIPT_DIR/cpp_validation_report.txt"
|
||||||
|
|
||||||
|
> "$ERRORS_FILE"
|
||||||
|
> "$REPORT_FILE"
|
||||||
|
|
||||||
|
echo "📁 模板目录: $TEMPLATES_DIR"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 辅助函数
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
validate_file_exists() {
|
||||||
|
local file="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
echo " ✅ $description: $(basename "$file")"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ❌ $description: $(basename "$file") - 文件不存在"
|
||||||
|
echo "文件不存在: $file" >> "$ERRORS_FILE"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_cmake_syntax() {
|
||||||
|
local file="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
# 基础语法检查
|
||||||
|
if [ ! -s "$file" ]; then
|
||||||
|
echo " ❌ $description: 文件为空"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查必要的 CMake 命令
|
||||||
|
local has_errors=0
|
||||||
|
|
||||||
|
if ! grep -q "cmake_minimum_required" "$file"; then
|
||||||
|
echo " ✗ 缺少 cmake_minimum_required"
|
||||||
|
has_errors=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "project(" "$file"; then
|
||||||
|
echo " ✗ 缺少 project()"
|
||||||
|
has_errors=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $has_errors -eq 0 ]; then
|
||||||
|
echo " ✅ $description: CMake 基础语法正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ❌ $description: CMake 语法检查失败"
|
||||||
|
echo "CMake 语法错误: $file" >> "$ERRORS_FILE"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 CMakeLists.txt
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 CMakeLists.txt"
|
||||||
|
CMAKE_FILE="$TEMPLATES_DIR/CMakeLists.txt"
|
||||||
|
|
||||||
|
if validate_file_exists "$CMAKE_FILE" "CMakeLists.txt"; then
|
||||||
|
# 验证 CMake 语法
|
||||||
|
validate_cmake_syntax "$CMAKE_FILE" "CMakeLists.txt"
|
||||||
|
|
||||||
|
echo " 📋 检查 C++23 配置:"
|
||||||
|
|
||||||
|
# 检查 C++23 标准
|
||||||
|
if grep -q "CMAKE_CXX_STANDARD 23" "$CMAKE_FILE"; then
|
||||||
|
echo " ✓ 配置了 C++23 标准"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ 未配置 C++23 标准"
|
||||||
|
echo "未配置 C++23: $CMAKE_FILE" >> "$ERRORS_FILE"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 Modules 支持
|
||||||
|
if grep -q "CMAKE_CXX_SCAN_FOR_MODULES" "$CMAKE_FILE"; then
|
||||||
|
echo " ✓ 启用了 C++ Modules 扫描"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ⚠️ 未启用 C++ Modules 扫描(可选)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 import std; 支持
|
||||||
|
if grep -q "CMAKE_EXPERIMENTAL_CXX_IMPORT_STD" "$CMAKE_FILE"; then
|
||||||
|
echo " ✓ 启用了 import std; 支持"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ⚠️ 未启用 import std; 支持(可选)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查编译命令导出
|
||||||
|
if grep -q "CMAKE_EXPORT_COMPILE_COMMANDS" "$CMAKE_FILE"; then
|
||||||
|
echo " ✓ 启用了编译命令导出(用于 clangd)"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ⚠️ 未启用编译命令导出"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 .clang-format
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 .clang-format"
|
||||||
|
CLANG_FORMAT="$TEMPLATES_DIR/.clang-format"
|
||||||
|
|
||||||
|
if validate_file_exists "$CLANG_FORMAT" ".clang-format"; then
|
||||||
|
# 验证 YAML 语法
|
||||||
|
if command -v yamllint >/dev/null 2>&1; then
|
||||||
|
if yamllint -d relaxed "$CLANG_FORMAT" >/dev/null 2>&1; then
|
||||||
|
echo " ✓ YAML 语法正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ YAML 语法错误"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证关键配置
|
||||||
|
echo " 📋 检查格式化配置:"
|
||||||
|
|
||||||
|
if grep -q "^Language:" "$CLANG_FORMAT" && grep -q "Cpp" "$CLANG_FORMAT"; then
|
||||||
|
echo " ✓ 配置了 Language: Cpp"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "^BasedOnStyle:" "$CLANG_FORMAT"; then
|
||||||
|
style=$(grep "^BasedOnStyle:" "$CLANG_FORMAT" | awk '{print $2}')
|
||||||
|
echo " ✓ 基于风格: $style"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "^Standard:" "$CLANG_FORMAT"; then
|
||||||
|
std=$(grep "^Standard:" "$CLANG_FORMAT" | awk '{print $2}')
|
||||||
|
echo " ✓ C++ 标准: $std"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查缩进配置
|
||||||
|
if grep -q "^IndentWidth:" "$CLANG_FORMAT"; then
|
||||||
|
echo " ✓ 配置了缩进宽度"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 .clangd
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 .clangd"
|
||||||
|
CLANGD="$TEMPLATES_DIR/.clangd"
|
||||||
|
|
||||||
|
if validate_file_exists "$CLANGD" ".clangd"; then
|
||||||
|
# 验证 YAML 语法
|
||||||
|
if command -v yamllint >/dev/null 2>&1; then
|
||||||
|
if yamllint -d relaxed "$CLANGD" >/dev/null 2>&1; then
|
||||||
|
echo " ✓ YAML 语法正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ YAML 语法错误"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证 CompileFlags
|
||||||
|
echo " 📋 检查 clangd 配置:"
|
||||||
|
|
||||||
|
if grep -q "CompileFlags:" "$CLANGD"; then
|
||||||
|
echo " ✓ 包含 CompileFlags 配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
|
||||||
|
# 检查 C++23 标准
|
||||||
|
if grep -q "std=c++23" "$CLANGD"; then
|
||||||
|
echo " ✓ 配置了 -std=c++23"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证 CompilationDatabase
|
||||||
|
if grep -q "CompilationDatabase:" "$CLANGD"; then
|
||||||
|
echo " ✓ 配置了 CompilationDatabase 路径"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证 Index 配置
|
||||||
|
if grep -q "Index:" "$CLANGD"; then
|
||||||
|
echo " ✓ 配置了索引选项"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 conanfile.txt
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 conanfile.txt"
|
||||||
|
CONANFILE="$TEMPLATES_DIR/conanfile.txt"
|
||||||
|
|
||||||
|
if validate_file_exists "$CONANFILE" "conanfile.txt"; then
|
||||||
|
echo " 📋 检查 Conan 配置:"
|
||||||
|
|
||||||
|
# 验证必要的节
|
||||||
|
if grep -q "^\[requires\]" "$CONANFILE"; then
|
||||||
|
echo " ✓ 包含 [requires] 配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ⚠️ 缺少 [requires] 配置(可选)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "^\[generators\]" "$CONANFILE"; then
|
||||||
|
echo " ✓ 包含 [generators] 配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
|
||||||
|
# 检查 CMakeDeps 和 CMakeToolchain
|
||||||
|
if grep -q "CMakeDeps" "$CONANFILE"; then
|
||||||
|
echo " ✓ 配置了 CMakeDeps 生成器"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "CMakeToolchain" "$CONANFILE"; then
|
||||||
|
echo " ✓ 配置了 CMakeToolchain 生成器"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "^\[options\]" "$CONANFILE"; then
|
||||||
|
echo " ✓ 包含 [options] 配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 CMakeUserPresets.json
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 CMakeUserPresets.json"
|
||||||
|
CMAKE_PRESETS="$TEMPLATES_DIR/CMakeUserPresets.json"
|
||||||
|
|
||||||
|
if validate_file_exists "$CMAKE_PRESETS" "CMakeUserPresets.json"; then
|
||||||
|
# 验证 JSON 语法
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
if python3 -m json.tool "$CMAKE_PRESETS" >/dev/null 2>&1; then
|
||||||
|
echo " ✓ JSON 语法正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ JSON 语法错误"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证配置项
|
||||||
|
echo " 📋 检查 CMake Presets:"
|
||||||
|
|
||||||
|
if grep -q "\"version\"" "$CMAKE_PRESETS"; then
|
||||||
|
echo " ✓ 包含 version 字段"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "\"configurePresets\"" "$CMAKE_PRESETS"; then
|
||||||
|
echo " ✓ 包含 configurePresets"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "\"buildPresets\"" "$CMAKE_PRESETS"; then
|
||||||
|
echo " ✓ 包含 buildPresets"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 生成验证报告
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "📊 验证结果统计"
|
||||||
|
echo "========================================"
|
||||||
|
echo "✅ 通过: $VALIDATION_PASSED"
|
||||||
|
echo "❌ 失败: $VALIDATION_FAILED"
|
||||||
|
echo "📈 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 写入报告文件
|
||||||
|
{
|
||||||
|
echo "C++ 模板验证报告"
|
||||||
|
echo "===================="
|
||||||
|
echo ""
|
||||||
|
echo "验证时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo "模板目录: $TEMPLATES_DIR"
|
||||||
|
echo ""
|
||||||
|
echo "统计结果:"
|
||||||
|
echo " 通过: $VALIDATION_PASSED"
|
||||||
|
echo " 失败: $VALIDATION_FAILED"
|
||||||
|
echo " 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
|
||||||
|
echo ""
|
||||||
|
if [ -s "$ERRORS_FILE" ]; then
|
||||||
|
echo "错误详情:"
|
||||||
|
cat "$ERRORS_FILE"
|
||||||
|
fi
|
||||||
|
} > "$REPORT_FILE"
|
||||||
|
|
||||||
|
echo "📄 详细报告: $REPORT_FILE"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -f "$ERRORS_FILE"
|
||||||
|
|
||||||
|
# 返回结果
|
||||||
|
if [ "$VALIDATION_FAILED" -eq 0 ]; then
|
||||||
|
echo "✅ 所有 C++ 模板验证通过"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ C++ 模板验证失败 ($VALIDATION_FAILED 个错误)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,329 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
# Python 模板验证脚本
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "🐍 Python 模板验证"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
TEMPLATES_DIR="$PLAYBOOK_ROOT/templates/python"
|
||||||
|
|
||||||
|
VALIDATION_PASSED=0
|
||||||
|
VALIDATION_FAILED=0
|
||||||
|
ERRORS_FILE="/tmp/python_template_validation_errors.txt"
|
||||||
|
REPORT_FILE="$SCRIPT_DIR/python_validation_report.txt"
|
||||||
|
|
||||||
|
> "$ERRORS_FILE"
|
||||||
|
> "$REPORT_FILE"
|
||||||
|
|
||||||
|
echo "📁 模板目录: $TEMPLATES_DIR"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 辅助函数
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
validate_file_exists() {
|
||||||
|
local file="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
echo " ✅ $description: $(basename "$file")"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ❌ $description: $(basename "$file") - 文件不存在"
|
||||||
|
echo "文件不存在: $file" >> "$ERRORS_FILE"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_toml_syntax() {
|
||||||
|
local file="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
echo " ⚠️ $description: 跳过(Python3 未安装)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if python3 << EOF
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
import tomli
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import tomllib as tomli
|
||||||
|
except ImportError:
|
||||||
|
import toml as tomli
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open("$file", "rb") as f:
|
||||||
|
tomli.load(f)
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"TOML 语法错误: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
EOF
|
||||||
|
then
|
||||||
|
echo " ✅ $description: TOML 语法正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo " ❌ $description: TOML 语法错误"
|
||||||
|
echo "TOML 语法错误: $file" >> "$ERRORS_FILE"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_required_sections() {
|
||||||
|
local file="$1"
|
||||||
|
local sections="$2"
|
||||||
|
|
||||||
|
for section in $sections; do
|
||||||
|
if grep -q "^\[$section\]" "$file"; then
|
||||||
|
echo " ✓ 包含 [$section] 配置"
|
||||||
|
else
|
||||||
|
echo " ✗ 缺少 [$section] 配置"
|
||||||
|
echo "缺少配置节: $section in $file" >> "$ERRORS_FILE"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 pyproject.toml
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 pyproject.toml"
|
||||||
|
PYPROJECT="$TEMPLATES_DIR/pyproject.toml"
|
||||||
|
|
||||||
|
if validate_file_exists "$PYPROJECT" "pyproject.toml"; then
|
||||||
|
# 验证 TOML 语法
|
||||||
|
validate_toml_syntax "$PYPROJECT" "pyproject.toml"
|
||||||
|
|
||||||
|
# 验证必要的配置节
|
||||||
|
echo " 📋 检查必要配置节:"
|
||||||
|
validate_required_sections "$PYPROJECT" "tool.black tool.isort tool.pytest.ini_options"
|
||||||
|
|
||||||
|
# 验证 black 配置
|
||||||
|
if grep -q "line-length = 80" "$PYPROJECT"; then
|
||||||
|
echo " ✓ black line-length 配置正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ black line-length 配置缺失或不正确"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证 isort 配置
|
||||||
|
if grep -q "profile = \"google\"" "$PYPROJECT"; then
|
||||||
|
echo " ✓ isort profile 配置正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ isort profile 配置缺失或不正确"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 .flake8
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 .flake8"
|
||||||
|
FLAKE8="$TEMPLATES_DIR/.flake8"
|
||||||
|
|
||||||
|
if validate_file_exists "$FLAKE8" ".flake8"; then
|
||||||
|
# 验证 [flake8] 节
|
||||||
|
if grep -q "^\[flake8\]" "$FLAKE8"; then
|
||||||
|
echo " ✓ 包含 [flake8] 配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
|
||||||
|
# 验证 max-line-length
|
||||||
|
if grep -q "^max-line-length" "$FLAKE8"; then
|
||||||
|
echo " ✓ 配置了 max-line-length"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ 缺少 max-line-length 配置"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证 extend-ignore
|
||||||
|
if grep -q "^extend-ignore" "$FLAKE8" || grep -q "^ignore" "$FLAKE8"; then
|
||||||
|
echo " ✓ 配置了错误忽略规则"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ 缺少 [flake8] 配置节"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 .pylintrc
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 .pylintrc"
|
||||||
|
PYLINTRC="$TEMPLATES_DIR/.pylintrc"
|
||||||
|
|
||||||
|
if validate_file_exists "$PYLINTRC" ".pylintrc"; then
|
||||||
|
# 验证关键配置节
|
||||||
|
for section in "MASTER" "MESSAGES CONTROL" "FORMAT"; do
|
||||||
|
if grep -qi "^\[$section\]" "$PYLINTRC"; then
|
||||||
|
echo " ✓ 包含 [$section] 配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ 缺少 [$section] 配置"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 验证 max-line-length
|
||||||
|
if grep -q "^max-line-length" "$PYLINTRC"; then
|
||||||
|
echo " ✓ 配置了 max-line-length"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 .pre-commit-config.yaml
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 .pre-commit-config.yaml"
|
||||||
|
PRECOMMIT="$TEMPLATES_DIR/.pre-commit-config.yaml"
|
||||||
|
|
||||||
|
if validate_file_exists "$PRECOMMIT" ".pre-commit-config.yaml"; then
|
||||||
|
# 验证 YAML 语法
|
||||||
|
if command -v yamllint >/dev/null 2>&1; then
|
||||||
|
if yamllint -d relaxed "$PRECOMMIT" >/dev/null 2>&1; then
|
||||||
|
echo " ✓ YAML 语法正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ YAML 语法错误"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证包含 pre-commit hooks
|
||||||
|
if grep -q "^repos:" "$PRECOMMIT"; then
|
||||||
|
echo " ✓ 包含 repos 配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
|
||||||
|
# 检查常用 hooks
|
||||||
|
for hook in "black" "isort" "flake8"; do
|
||||||
|
if grep -q "$hook" "$PRECOMMIT"; then
|
||||||
|
echo " ✓ 配置了 $hook hook"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 .editorconfig
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 .editorconfig"
|
||||||
|
EDITORCONFIG="$TEMPLATES_DIR/.editorconfig"
|
||||||
|
|
||||||
|
if validate_file_exists "$EDITORCONFIG" ".editorconfig"; then
|
||||||
|
# 验证包含 root 标记
|
||||||
|
if grep -q "^root = true" "$EDITORCONFIG"; then
|
||||||
|
echo " ✓ 包含 root = true"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证 Python 配置节
|
||||||
|
if grep -q "^\[\*.py\]" "$EDITORCONFIG" || grep -q "^\[*.py\]" "$EDITORCONFIG"; then
|
||||||
|
echo " ✓ 包含 Python 文件配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 验证 VSCode 配置
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "🔍 验证 .vscode/settings.json"
|
||||||
|
VSCODE_SETTINGS="$TEMPLATES_DIR/.vscode/settings.json"
|
||||||
|
|
||||||
|
if validate_file_exists "$VSCODE_SETTINGS" "settings.json"; then
|
||||||
|
# 验证 JSON 语法
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
if python3 -m json.tool "$VSCODE_SETTINGS" >/dev/null 2>&1; then
|
||||||
|
echo " ✓ JSON 语法正确"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " ✗ JSON 语法错误"
|
||||||
|
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证 Python 相关配置
|
||||||
|
if grep -q "python" "$VSCODE_SETTINGS"; then
|
||||||
|
echo " ✓ 包含 Python 配置"
|
||||||
|
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 生成验证报告
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "📊 验证结果统计"
|
||||||
|
echo "========================================"
|
||||||
|
echo "✅ 通过: $VALIDATION_PASSED"
|
||||||
|
echo "❌ 失败: $VALIDATION_FAILED"
|
||||||
|
echo "📈 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 写入报告文件
|
||||||
|
{
|
||||||
|
echo "Python 模板验证报告"
|
||||||
|
echo "===================="
|
||||||
|
echo ""
|
||||||
|
echo "验证时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo "模板目录: $TEMPLATES_DIR"
|
||||||
|
echo ""
|
||||||
|
echo "统计结果:"
|
||||||
|
echo " 通过: $VALIDATION_PASSED"
|
||||||
|
echo " 失败: $VALIDATION_FAILED"
|
||||||
|
echo " 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
|
||||||
|
echo ""
|
||||||
|
if [ -s "$ERRORS_FILE" ]; then
|
||||||
|
echo "错误详情:"
|
||||||
|
cat "$ERRORS_FILE"
|
||||||
|
fi
|
||||||
|
} > "$REPORT_FILE"
|
||||||
|
|
||||||
|
echo "📄 详细报告: $REPORT_FILE"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -f "$ERRORS_FILE"
|
||||||
|
|
||||||
|
# 返回结果
|
||||||
|
if [ "$VALIDATION_FAILED" -eq 0 ]; then
|
||||||
|
echo "✅ 所有 Python 模板验证通过"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ Python 模板验证失败 ($VALIDATION_FAILED 个错误)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Loading…
Reference in New Issue