519 lines
18 KiB
YAML
519 lines
18 KiB
YAML
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"
|
||
|
||
jobs:
|
||
# ==========================================
|
||
# Job: 全量测试
|
||
# ==========================================
|
||
test:
|
||
name: 🧪 全量测试
|
||
runs-on: ubuntu-22.04
|
||
|
||
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 "========================================"
|
||
|
||
apt-get update
|
||
apt-get install -y bats cmake clang-format python3-pip
|
||
|
||
python3 -m pip install --upgrade pip
|
||
python3 -m pip install toml tomli jsonschema yamllint
|
||
|
||
echo ""
|
||
echo "✓ bats 版本: $(bats --version)"
|
||
echo "✓ Python 版本: $(python3 --version)"
|
||
echo "========================================"
|
||
|
||
- name: 🧪 运行全量测试并生成报告
|
||
shell: bash
|
||
run: |
|
||
set +e
|
||
set -o pipefail
|
||
|
||
overall_fail=0
|
||
scripts_status="success"
|
||
templates_status="success"
|
||
integration_status="success"
|
||
docs_status="success"
|
||
|
||
echo "========================================"
|
||
echo "🐚 Shell 脚本测试"
|
||
echo "========================================"
|
||
|
||
cd "$REPO_DIR/tests/scripts"
|
||
|
||
run_bats() {
|
||
local name="$1"
|
||
local file="$2"
|
||
local output="${name}_test_results.tap"
|
||
|
||
if [ ! -f "$file" ]; then
|
||
echo "⚠️ 未找到测试文件: $file"
|
||
scripts_status="failure"
|
||
overall_fail=1
|
||
return
|
||
fi
|
||
|
||
bats --formatter tap "$file" | tee "$output"
|
||
if [ $? -ne 0 ]; then
|
||
echo "❌ $name 测试失败"
|
||
scripts_status="failure"
|
||
overall_fail=1
|
||
else
|
||
echo "✅ $name 测试通过"
|
||
fi
|
||
}
|
||
|
||
run_bats "sync_standards" "test_sync_standards.bats"
|
||
run_bats "sync_templates" "test_sync_templates.bats"
|
||
run_bats "vendor_playbook" "test_vendor_playbook.bats"
|
||
run_bats "install_codex_skills" "test_install_codex_skills.bats"
|
||
|
||
echo "========================================"
|
||
echo "📄 模板验证测试"
|
||
echo "========================================"
|
||
|
||
cd "$REPO_DIR/tests/templates"
|
||
|
||
run_validator() {
|
||
local name="$1"
|
||
local script="$2"
|
||
|
||
if [ ! -f "$script" ]; then
|
||
echo "⚠️ 未找到验证脚本: $script"
|
||
templates_status="failure"
|
||
overall_fail=1
|
||
return
|
||
fi
|
||
|
||
chmod +x "$script"
|
||
"./$script"
|
||
if [ $? -ne 0 ]; then
|
||
echo "❌ $name 模板验证失败"
|
||
templates_status="failure"
|
||
overall_fail=1
|
||
else
|
||
echo "✅ $name 模板验证通过"
|
||
fi
|
||
}
|
||
|
||
run_validator "python" "validate_python_templates.sh"
|
||
run_validator "cpp" "validate_cpp_templates.sh"
|
||
run_validator "ci" "validate_ci_templates.sh"
|
||
run_validator "project_templates" "validate_project_templates.sh"
|
||
|
||
echo "========================================"
|
||
echo "🔗 集成测试"
|
||
echo "========================================"
|
||
|
||
mkdir -p "${TEST_WORKSPACE}"
|
||
cd "${TEST_WORKSPACE}"
|
||
|
||
# 创建测试项目目录
|
||
mkdir -p test-project-tsl
|
||
mkdir -p test-project-cpp
|
||
mkdir -p test-project-multi
|
||
|
||
echo "========================================"
|
||
echo "🧪 测试场景1: TSL 项目标准同步"
|
||
echo "========================================"
|
||
|
||
cd "${TEST_WORKSPACE}/test-project-tsl"
|
||
|
||
# 初始化 git 仓库
|
||
git init
|
||
git config user.name "Test User"
|
||
git config user.email "test@example.com"
|
||
|
||
# 模拟 subtree add(包含 rulesets 等点目录,排除 .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 规则集同步失败"
|
||
integration_status="failure"
|
||
overall_fail=1
|
||
fi
|
||
|
||
if grep -q "# BEGIN playbook .gitattributes" .gitattributes 2>/dev/null \
|
||
|| grep -q "# Added from playbook .gitattributes" .gitattributes 2>/dev/null \
|
||
|| grep -q "^\\* text=auto eol=lf" .gitattributes 2>/dev/null; then
|
||
echo "✅ .gitattributes 更新成功"
|
||
else
|
||
echo "❌ .gitattributes 更新失败"
|
||
integration_status="failure"
|
||
overall_fail=1
|
||
fi
|
||
|
||
echo "========================================"
|
||
echo "🧪 测试场景2: C++ 项目标准同步"
|
||
echo "========================================"
|
||
|
||
cd "${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++ 规则集同步失败"
|
||
integration_status="failure"
|
||
overall_fail=1
|
||
fi
|
||
|
||
echo "========================================"
|
||
echo "🧪 测试场景3: 多语言项目标准同步"
|
||
echo "========================================"
|
||
|
||
cd "${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 "❌ 多语言规则集同步失败"
|
||
integration_status="failure"
|
||
overall_fail=1
|
||
fi
|
||
|
||
echo "========================================"
|
||
echo "🧪 测试场景4: vendor_playbook 脚本"
|
||
echo "========================================"
|
||
|
||
cd "${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 "docs/standards/playbook/rulesets/tsl" ] && [ -d ".agents/tsl" ]; then
|
||
echo "✅ vendor_playbook 脚本执行成功"
|
||
else
|
||
echo "❌ vendor_playbook 脚本执行失败"
|
||
integration_status="failure"
|
||
overall_fail=1
|
||
fi
|
||
|
||
echo "========================================"
|
||
echo "🧹 清理测试环境..."
|
||
chmod -R u+w "${TEST_WORKSPACE}" 2>/dev/null || true
|
||
rm -rf "${TEST_WORKSPACE}"
|
||
echo "✓ 清理完成"
|
||
|
||
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 "❌ 发现无效链接"
|
||
docs_status="failure"
|
||
overall_fail=1
|
||
fi
|
||
else
|
||
echo "⚠️ 未找到链接检查脚本,跳过"
|
||
fi
|
||
|
||
echo "========================================"
|
||
echo "🔍 检查代理规则一致性(三层架构)"
|
||
echo "========================================"
|
||
|
||
cd "$REPO_DIR"
|
||
|
||
# 检查 rulesets/ 三层架构完整性
|
||
python3 << 'EOF'
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
errors = []
|
||
warnings = []
|
||
|
||
print("检查 Layer 1: rulesets/ (极简铁律)")
|
||
print("────────────────────────────────────────")
|
||
|
||
# 检查各语言的 rulesets/ 目录(只需 index.md)
|
||
agents_base = Path("rulesets")
|
||
|
||
# 检查 rulesets/index.md
|
||
agents_index = agents_base / "index.md"
|
||
if not agents_index.exists():
|
||
errors.append(f"❌ 缺少文件: {agents_index}")
|
||
elif agents_index.stat().st_size == 0:
|
||
errors.append(f"❌ 文件为空: {agents_index}")
|
||
else:
|
||
print(f"✅ {agents_index}")
|
||
|
||
for lang_dir in ["tsl", "cpp", "python"]:
|
||
agents_lang = agents_base / lang_dir
|
||
if not agents_lang.exists():
|
||
errors.append(f"❌ 缺少目录: {agents_lang}")
|
||
continue
|
||
|
||
# 只检查 index.md(≤50 行)
|
||
index_file = agents_lang / "index.md"
|
||
if not index_file.exists():
|
||
errors.append(f"❌ 缺少文件: {index_file}")
|
||
elif index_file.stat().st_size == 0:
|
||
errors.append(f"❌ 文件为空: {index_file}")
|
||
else:
|
||
# 检查规模(≤50 行)
|
||
line_count = len(index_file.read_text(encoding='utf-8').splitlines())
|
||
if line_count > 50:
|
||
warnings.append(f"⚠️ {index_file}: {line_count} 行 (目标: ≤50)")
|
||
else:
|
||
print(f"✅ {index_file} ({line_count} 行)")
|
||
|
||
# 检查是否有残留的旧文件
|
||
old_files = ["auth.md", "code_quality.md", "performance.md", "testing.md"]
|
||
for old_file in old_files:
|
||
old_path = agents_lang / old_file
|
||
if old_path.exists():
|
||
warnings.append(f"⚠️ 残留旧文件: {old_path} (应删除)")
|
||
|
||
print("")
|
||
print("检查 Layer 2: codex/skills/ (按需加载)")
|
||
print("────────────────────────────────────────")
|
||
|
||
# 检查关键 skills
|
||
skills_base = Path("codex/skills")
|
||
required_skills = [
|
||
("tsl-guide", ["SKILL.md", "references/primer.md", "references/advanced.md"]),
|
||
("testing-workflow", ["SKILL.md"]),
|
||
]
|
||
|
||
for skill_name, required_files in required_skills:
|
||
skill_dir = skills_base / skill_name
|
||
if not skill_dir.exists():
|
||
errors.append(f"❌ 缺少 skill: {skill_dir}")
|
||
continue
|
||
|
||
for req_file in required_files:
|
||
file_path = skill_dir / req_file
|
||
if not file_path.exists():
|
||
errors.append(f"❌ 缺少文件: {file_path}")
|
||
elif file_path.stat().st_size == 0:
|
||
errors.append(f"❌ 文件为空: {file_path}")
|
||
else:
|
||
print(f"✅ {file_path}")
|
||
|
||
print("")
|
||
print("检查 Layer 3: docs/ (权威文档)")
|
||
print("────────────────────────────────────────")
|
||
|
||
# 检查关键文档路径
|
||
docs_paths = [
|
||
"docs/tsl/syntax_book/index.md",
|
||
"docs/tsl/code_style.md",
|
||
"docs/tsl/naming.md",
|
||
"docs/cpp/code_style.md",
|
||
"docs/python/style_guide.md",
|
||
]
|
||
|
||
for doc_path in docs_paths:
|
||
path = Path(doc_path)
|
||
if not path.exists():
|
||
errors.append(f"❌ 缺少文档: {doc_path}")
|
||
else:
|
||
print(f"✅ {doc_path}")
|
||
|
||
print("")
|
||
print("检查架构文档")
|
||
print("────────────────────────────────────────")
|
||
|
||
# 检查新增的架构文档
|
||
arch_docs = ["AGENTS.md", "SKILLS.md", "README.md"]
|
||
for doc in arch_docs:
|
||
path = Path(doc)
|
||
if not path.exists():
|
||
errors.append(f"❌ 缺少文档: {doc}")
|
||
else:
|
||
print(f"✅ {doc}")
|
||
|
||
print("")
|
||
print("────────────────────────────────────────")
|
||
|
||
if warnings:
|
||
print("\n⚠️ 警告:")
|
||
for warning in warnings:
|
||
print(f" {warning}")
|
||
print("")
|
||
|
||
if errors:
|
||
print("\n❌ 发现错误:")
|
||
for error in errors:
|
||
print(f" {error}")
|
||
sys.exit(1)
|
||
else:
|
||
print("✅ 三层架构完整性检查通过")
|
||
EOF
|
||
|
||
if [ $? -ne 0 ]; then
|
||
docs_status="failure"
|
||
overall_fail=1
|
||
fi
|
||
|
||
echo "========================================"
|
||
echo "📊 生成测试综合报告"
|
||
echo "========================================"
|
||
|
||
format_status() {
|
||
case "$1" in
|
||
success)
|
||
echo "✅ 通过"
|
||
;;
|
||
failure)
|
||
echo "❌ 失败"
|
||
;;
|
||
*)
|
||
echo "❔ 未知"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
cat >> $GITHUB_STEP_SUMMARY << EOFSUMMARY
|
||
# 🧪 Playbook 测试报告
|
||
|
||
## 📋 测试执行摘要
|
||
|
||
| 测试类型 | 状态 |
|
||
|---------|------|
|
||
| 🐚 Shell 脚本测试 | $(format_status "$scripts_status") |
|
||
| 📄 模板验证测试 | $(format_status "$templates_status") |
|
||
| 🔗 集成测试 | $(format_status "$integration_status") |
|
||
| 📚 文档一致性检查 | $(format_status "$docs_status") |
|
||
|
||
---
|
||
|
||
## 🔗 相关链接
|
||
|
||
- 📝 [测试文档](tests/README.md)
|
||
- 🐛 [问题反馈](../../issues)
|
||
- 📖 [开发指南](docs/index.md)
|
||
|
||
---
|
||
|
||
<div align="center">
|
||
|
||
*🤖 由 [Gitea Actions](../../actions) 自动生成*
|
||
|
||
EOFSUMMARY
|
||
|
||
echo "*📅 生成时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "</div>" >> $GITHUB_STEP_SUMMARY
|
||
|
||
echo "========================================"
|
||
if [ "$overall_fail" -ne 0 ]; then
|
||
echo "❌ 测试失败"
|
||
exit 1
|
||
fi
|
||
echo "✅ 全量测试通过"
|