Files
actions-template/tests/template_defaults_test.sh
T
2026-07-16 09:17:05 +08:00

328 lines
21 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
fail() {
echo "FAIL: $*" >&2
exit 1
}
test_preset_compose_uses_env_for_instance() {
local file
for file in \
"${REPO_ROOT}/docker-runner/presets/standard-ubuntu-22/docker-compose.yml" \
"${REPO_ROOT}/docker-runner/presets/buildx-ubuntu-22/docker-compose.yml" \
"${REPO_ROOT}/docker-runner/presets/buildx-archlinux/docker-compose.yml"; do
grep -q 'GITEA_INSTANCE=${GITEA_INSTANCE}' "${file}" || fail "${file} should read GITEA_INSTANCE from env"
grep -q 'GITEA_TOKEN=${GITEA_TOKEN}' "${file}" || fail "${file} should read GITEA_TOKEN from env"
done
}
test_workflows_do_not_hardcode_company_server() {
! rg -q 'https://git\.mytsl\.cn' "${REPO_ROOT}/.gitea/workflows/changelog_and_release.yml" || fail "changelog workflow should not hardcode company server"
! rg -q 'https://git\.mytsl\.cn' "${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml" || fail "stats workflow should not hardcode company server"
}
test_stats_workflow_uses_workflow_secret_consistently() {
local file
file="${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml"
grep -q 'ACCESS_TOKEN: ${{ secrets.WORKFLOW }}' "${file}" || fail "stats workflow should read ACCESS_TOKEN from WORKFLOW secret"
! rg -q 'STATS_TOKEN' "${file}" || fail "stats workflow should not mention legacy STATS_TOKEN secret"
! rg -q 'GITHUB_TOKEN' "${file}" || fail "stats workflow should not mention GITHUB_TOKEN in token guidance"
}
test_release_workflow_uses_inline_release_slot() {
local file
file="${REPO_ROOT}/.gitea/workflows/changelog_and_release.yml"
grep -Fq 'group: release-${{ github.repository }}' "${file}" || fail "release workflow should serialize its slot per repository"
grep -q '^[[:space:]]*cancel-in-progress: false$' "${file}" || fail "release slot should queue instead of canceling"
grep -Fq 'WORKSPACE_DIR: "/data/workspace"' "${file}" || fail "release workflow should use /data/workspace"
grep -Fq 'WORKSPACE_SLOT: "release"' "${file}" || fail "release workflow should own the release slot"
grep -Fq 'MAIN_BRANCH: "main"' "${file}" || fail "release workflow should keep an explicit MAIN_BRANCH"
grep -Fq '# 必须明确配置主分支(例如 main 或 master' "${file}" || fail "release workflow should document MAIN_BRANCH as required"
! rg -q 'CHANGELOG\.md (已作为附件上传|已上传为附件)' "${file}" || fail "release workflow should not claim CHANGELOG.md was uploaded as a Release asset"
grep -q 'ADDITIONAL_RELEASE_FILES' "${file}" || fail "release workflow should retain optional Release asset configuration"
grep -Fq 'attachment=@${file}' "${file}" || fail "release workflow should retain optional Release asset uploads"
grep -q 'GIT_CONFIG_COUNT: "3"' "${file}" || fail "release workflow should configure transient Git authentication"
grep -Fq 'case "$REMOTE_URL" in' "${file}" || fail "release workflow should reject token use on non-HTTPS remotes"
grep -Fq 'REPO_DIR="${WORKSPACE_DIR}/${REPO_NAME}/${WORKSPACE_SLOT}"' "${file}" || fail "release workflow should build its clone-root slot inline"
grep -Fq 'git clone --no-checkout "$REMOTE_URL" "$REPO_DIR"' "${file}" || fail "release workflow should clone directly into its slot"
grep -Fq 'git -C "$REPO_DIR" checkout --force -B "$MAIN_BRANCH" "$REMOTE_REF"' "${file}" || fail "release workflow should reset its branch from origin"
grep -Fq 'git -C "$REPO_DIR" clean -ffdx' "${file}" || fail "release workflow should clean ignored output before reuse"
grep -Fq 'git -C "${{ env.REPO_DIR }}" push origin "${{ env.MAIN_BRANCH }}"' "${file}" || fail "release workflow should push directly through transient authentication"
! rg -q 'BOOTSTRAP_SCRIPT|prepare-slot-workspace|git-auth|bootstrap_workspace\.sh' "${file}" || fail "release workflow should not use a shared bootstrap helper"
! rg -q 'MIRROR_ROOT|JOB_WORKSPACE_ROOT|MIRROR_PATH|JOB_WORKSPACE|https://oauth2:' "${file}" || fail "release workflow should not use retired paths or credential URLs"
}
test_stats_workflow_uses_inline_stats_slot() {
local file
file="${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml"
grep -Fq 'group: stats-${{ github.repository }}' "${file}" || fail "stats workflow should serialize the stats slot per repository"
grep -q '^[[:space:]]*cancel-in-progress: false$' "${file}" || fail "stats slot should queue instead of canceling"
grep -Fq 'WORKSPACE_DIR: "/data/workspace"' "${file}" || fail "stats workflow should use /data/workspace"
grep -Fq 'WORKSPACE_SLOT: "stats"' "${file}" || fail "stats workflow should own the stats slot"
grep -q 'GIT_CONFIG_COUNT: "3"' "${file}" || fail "stats workflow should configure transient Git authentication"
grep -Fq 'case "$REMOTE_URL" in' "${file}" || fail "stats workflow should reject token use on non-HTTPS remotes"
grep -Fq 'REPO_DIR="${WORKSPACE_DIR}/${REPO_NAME}/${WORKSPACE_SLOT}"' "${file}" || fail "stats workflow should build its clone-root slot inline"
grep -Fq 'git clone --no-checkout "$REMOTE_URL" "$REPO_DIR"' "${file}" || fail "stats workflow should clone directly into its slot"
grep -Fq 'git -C "$REPO_DIR" checkout --force -B "$TARGET_BRANCH" "$REMOTE_REF"' "${file}" || fail "stats workflow should reset its source branch from origin"
grep -Fq 'git -C "$REPO_DIR" clean -ffdx' "${file}" || fail "stats workflow should clean ignored output before reuse"
grep -Fq 'git checkout --force -B "${{ env.BADGE_BRANCH }}" "refs/remotes/origin/${{ env.BADGE_BRANCH }}"' "${file}" || fail "stats workflow should reset a reused local stats branch to origin"
grep -Fq 'git branch -D "${{ env.BADGE_BRANCH }}"' "${file}" || fail "stats workflow should remove a stale local stats branch when origin is absent"
grep -Fq 'git -C "${{ env.REPO_DIR }}" push origin "${{ env.BADGE_BRANCH }}"' "${file}" || fail "stats workflow should push directly through transient authentication"
! rg -q 'BOOTSTRAP_SCRIPT|prepare-slot-workspace|git-auth|bootstrap_workspace\.sh' "${file}" || fail "stats workflow should not use a shared bootstrap helper"
! rg -q 'MIRROR_ROOT|JOB_WORKSPACE_ROOT|MIRROR_PATH|JOB_WORKSPACE|https://oauth2:' "${file}" || fail "stats workflow should not use retired paths or credential URLs"
}
test_shared_workspace_helper_is_removed() {
test ! -e "${REPO_ROOT}/.gitea/ci/bootstrap_workspace.sh" || fail "shared workspace helper should be removed"
}
test_register_default_capacity_is_four() {
grep -q "config\['runner'\]\['capacity'\] = 4" "${REPO_ROOT}/docker-runner/common/register.sh" || fail "register.sh should default new runner capacity to 4"
}
test_presets_do_not_mount_workspace_helper() {
! rg -q 'workspace\.sh:/data/workspace\.sh' "${REPO_ROOT}/docker-runner/presets" || fail "presets should not mount a shared workspace helper"
}
test_workflow_docs_and_links_use_actual_paths() {
local workflow_doc stats_workflow release_workflow
workflow_doc="${REPO_ROOT}/WORKFLOW.md"
stats_workflow="${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml"
release_workflow="${REPO_ROOT}/.gitea/workflows/changelog_and_release.yml"
grep -q 'changelog_and_release.yml' "${workflow_doc}" || fail "WORKFLOW.md should mention changelog_and_release.yml"
grep -q 'update_stats_badge.yaml' "${workflow_doc}" || fail "WORKFLOW.md should mention update_stats_badge.yaml"
! rg -q 'changelog-and-release\.yml' "${workflow_doc}" || fail "WORKFLOW.md should not mention stale changelog-and-release.yml filename"
! rg -q 'update-stats-badge\.yml' "${workflow_doc}" || fail "WORKFLOW.md should not mention stale update-stats-badge.yml filename"
! rg -q 'update_stats_badge\.yml' "${workflow_doc}" || fail "WORKFLOW.md should not mention stale update_stats_badge.yml filename"
grep -q '/src/branch/${{ github.ref_name }}/.gitea/workflows/update_stats_badge.yaml' "${stats_workflow}" || fail "stats workflow summary should link to .gitea workflow path"
grep -q '/src/branch/${{ env.MAIN_BRANCH }}/.gitea/workflows/changelog_and_release.yml' "${release_workflow}" || fail "release workflow summary should link to .gitea workflow path"
! rg -q '/\\.github/workflows/' "${stats_workflow}" || fail "stats workflow should not link to .github/workflows"
! rg -q '/\\.github/workflows/' "${release_workflow}" || fail "release workflow should not link to .github/workflows"
}
test_workflow_doc_describes_workspace_architecture() {
local file
file="${REPO_ROOT}/WORKFLOW.md"
grep -Eq '^## .*运行模型' "${file}" || fail "WORKFLOW.md should include a run model section"
grep -Eq '固定.*slot|slot.*固定' "${file}" || fail "WORKFLOW.md should describe fixed workflow slots"
grep -Eq '职责|责任' "${file}" || fail "WORKFLOW.md should describe responsibility ownership"
grep -Eq '并发|串行' "${file}" || fail "WORKFLOW.md should describe slot serialization"
grep -Eq 'reset|重置' "${file}" || fail "WORKFLOW.md should describe reset before reuse"
grep -Eq '保留|复用' "${file}" || fail "WORKFLOW.md should describe persistent reuse"
! rg -q 'bare|MIRROR_ROOT|JOB_WORKSPACE_ROOT|/data/git-mirrors|/home/workspace/jobs' "${file}" || fail "WORKFLOW.md should not describe the retired mirror model"
! rg -q 'bootstrap_workspace\.sh' "${file}" || fail "WORKFLOW.md should describe architecture rather than helper implementation"
}
test_presets_do_not_mount_check_crlf_helper() {
! rg -q 'check_crlf\.sh:/data/check_crlf\.sh:ro' "${REPO_ROOT}/docker-runner/presets" || fail "preset compose files should not mount check_crlf helper into containers"
}
test_runner_data_is_gitignored() {
local path
for path in \
"docker-runner/presets/standard-ubuntu-22/runner-data/config.yaml" \
"docker-runner/presets/buildx-ubuntu-22/runner-data/config.yaml" \
"docker-runner/presets/buildx-archlinux/runner-data/config.yaml"; do
git -C "${REPO_ROOT}" check-ignore -q "${path}" || fail "${path} should be ignored as runtime runner data"
done
}
test_readme_has_project_intro_and_navigation() {
local file
file="${REPO_ROOT}/README.md"
grep -Eq '^## .*项目简介' "${file}" || fail "README.md should provide a project intro section"
grep -Eq '^## .*文档导航' "${file}" || fail "README.md should provide a document navigation section"
grep -q '\[DEPLOYMENT.md\](\./DEPLOYMENT.md)' "${file}" || fail "README.md should link to DEPLOYMENT.md"
grep -q '\[WORKFLOW.md\](\./WORKFLOW.md)' "${file}" || fail "README.md should link to WORKFLOW.md"
! rg -q '^## 🚀 快速提示$' "${file}" || fail "README.md should not carry concrete deployment tip sections"
! rg -q '^## ⚙️ 当前默认行为$' "${file}" || fail "README.md should not carry default behavior details"
! rg -q 'docker compose (build|up|exec)' "${file}" || fail "README.md should stay high-level and leave compose commands to deployment docs"
}
test_deployment_doc_stays_runner_focused() {
local file
file="${REPO_ROOT}/DEPLOYMENT.md"
! rg -q '^## ⚙️ 默认行为$' "${file}" || fail "DEPLOYMENT.md should not carry generic default behavior sections"
! rg -q '/data/git-mirrors/<owner>/<repo>\.git' "${file}" || fail "DEPLOYMENT.md should not document workflow mirror paths"
! rg -q '/home/workspace/jobs/' "${file}" || fail "DEPLOYMENT.md should not document workflow temp workspace paths"
! rg -q 'bootstrap_workspace\.sh' "${file}" || fail "DEPLOYMENT.md should stay focused on runner deployment, not workflow helpers"
}
test_presets_define_expected_hostname() {
grep -q '^ hostname: ubuntu$' "${REPO_ROOT}/docker-runner/presets/standard-ubuntu-22/docker-compose.yml" || fail "standard preset should set hostname to ubuntu"
grep -q '^ hostname: ubuntu$' "${REPO_ROOT}/docker-runner/presets/buildx-ubuntu-22/docker-compose.yml" || fail "buildx ubuntu preset should set hostname to ubuntu"
grep -q '^ hostname: arch$' "${REPO_ROOT}/docker-runner/presets/buildx-archlinux/docker-compose.yml" || fail "buildx arch preset should set hostname to arch"
}
test_preset_env_examples_exist() {
local file
for file in \
"${REPO_ROOT}/docker-runner/presets/standard-ubuntu-22/.env.example" \
"${REPO_ROOT}/docker-runner/presets/buildx-ubuntu-22/.env.example" \
"${REPO_ROOT}/docker-runner/presets/buildx-archlinux/.env.example"; do
test -f "${file}" || fail "missing env example: ${file}"
grep -q '^GITEA_INSTANCE=https://git.mytsl.cn$' "${file}" || fail "${file} should include company default instance"
grep -q '^GITEA_TOKEN=$' "${file}" || fail "${file} should include empty token placeholder"
grep -q '^DEFAULT_RUNNER_NAME=' "${file}" || fail "${file} should define DEFAULT_RUNNER_NAME override example"
grep -q '^DEFAULT_RUNNER_LABEL=' "${file}" || fail "${file} should define DEFAULT_RUNNER_LABEL override example"
done
}
test_preset_compose_supports_runner_identity_overrides() {
grep -q 'DEFAULT_RUNNER_NAME=${DEFAULT_RUNNER_NAME:-standard-ubuntu-22}' \
"${REPO_ROOT}/docker-runner/presets/standard-ubuntu-22/docker-compose.yml" || \
fail "standard preset should allow DEFAULT_RUNNER_NAME override via env"
grep -q 'DEFAULT_RUNNER_LABEL=${DEFAULT_RUNNER_LABEL:-ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,standard-ubuntu-22:host://ubuntu:22.04}' \
"${REPO_ROOT}/docker-runner/presets/standard-ubuntu-22/docker-compose.yml" || \
fail "standard preset should allow DEFAULT_RUNNER_LABEL override via env"
grep -q 'DEFAULT_RUNNER_NAME=${DEFAULT_RUNNER_NAME:-buildx-ubuntu-22}' \
"${REPO_ROOT}/docker-runner/presets/buildx-ubuntu-22/docker-compose.yml" || \
fail "buildx ubuntu preset should allow DEFAULT_RUNNER_NAME override via env"
grep -q 'DEFAULT_RUNNER_LABEL=${DEFAULT_RUNNER_LABEL:-ubuntu-22.04:host://ubuntu:22.04,company-server:host://ubuntu:22.04,buildx-ubuntu-22:host://ubuntu:22.04}' \
"${REPO_ROOT}/docker-runner/presets/buildx-ubuntu-22/docker-compose.yml" || \
fail "buildx ubuntu preset should allow DEFAULT_RUNNER_LABEL override via env"
grep -q 'DEFAULT_RUNNER_NAME=${DEFAULT_RUNNER_NAME:-buildx-archlinux}' \
"${REPO_ROOT}/docker-runner/presets/buildx-archlinux/docker-compose.yml" || \
fail "buildx arch preset should allow DEFAULT_RUNNER_NAME override via env"
grep -q 'DEFAULT_RUNNER_LABEL=${DEFAULT_RUNNER_LABEL:-archlinux:host://archlinux:latest,company-server:host://archlinux:latest,buildx-archlinux:host://archlinux:latest}' \
"${REPO_ROOT}/docker-runner/presets/buildx-archlinux/docker-compose.yml" || \
fail "buildx arch preset should allow DEFAULT_RUNNER_LABEL override via env"
}
test_register_requires_python_yaml_path() {
local file
file="${REPO_ROOT}/docker-runner/common/register.sh"
! rg -q 'Python configuration failed, using basic sed' "${file}" || fail "register.sh should not fall back to basic sed when Python config fails"
! rg -q 'Python3 not found, applying basic configuration' "${file}" || fail "register.sh should not continue with basic configuration when Python3 is missing"
! rg -q "sed -i 's/capacity: 1/capacity: 4/g' config.yaml" "${file}" || fail "register.sh should not mutate config.yaml via sed fallback"
grep -q "python3 << PYEOF" "${file}" || fail "register.sh should keep Python-based config generation"
}
test_setup_requires_upgrade_helper() {
local file
file="${REPO_ROOT}/docker-runner/common/setup.sh"
grep -q '^# shellcheck source=/dev/null$' "${file}" || fail "setup.sh should source upgrade.sh directly"
grep -q '^source "\${UPGRADE_HELPER}"$' "${file}" || fail "setup.sh should require sourcing upgrade.sh"
! grep -Fq 'if [ -f "${UPGRADE_HELPER}" ]; then' "${file}" || fail "setup.sh should not treat upgrade helper as optional"
! rg -q 'declare -F resolve_latest_version_or_fallback' "${file}" || fail "setup.sh should not guard helper functions with declare -F"
! rg -q 'declare -F validate_version' "${file}" || fail "setup.sh should not guard validate_version with declare -F"
! rg -q 'declare -F validate_binary_arch_or_fail' "${file}" || fail "setup.sh should not guard binary validation with declare -F"
}
test_entrypoint_uses_shared_buildx_builder_creation() {
local file
file="${REPO_ROOT}/docker-runner/common/entrypoint.sh"
grep -q '^create_buildx_builder() {$' "${file}" || fail "entrypoint.sh should extract Buildx builder creation into a helper"
grep -q '^ensure_buildx_builder() {$' "${file}" || fail "entrypoint.sh should extract Buildx builder selection into a helper"
grep -q '^[[:space:]]*create_buildx_builder$' "${file}" || fail "entrypoint.sh should use shared builder creation during initial setup"
grep -q '^[[:space:]]*ensure_buildx_builder$' "${file}" || fail "entrypoint.sh should use shared builder selection during reuse"
}
test_stats_workflow_avoids_eval_find() {
local file
file="${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml"
! rg -q 'eval "find ' "${file}" || fail "stats workflow should avoid eval when building find commands"
grep -q 'EXCLUDE_FIND_ARGS=()' "${file}" || fail "stats workflow should build reusable find exclusion arrays"
grep -q 'LANG_FIND_ARGS=()' "${file}" || fail "stats workflow should build language-specific find arguments via arrays"
}
test_stats_workflow_uses_svg_badges() {
local file workflow_doc
file="${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml"
workflow_doc="${REPO_ROOT}/WORKFLOW.md"
grep -q 'total-lines.svg' "${file}" || fail "stats workflow should generate total-lines.svg"
grep -q 'total-files.svg' "${file}" || fail "stats workflow should generate total-files.svg"
grep -q 'language-count.svg' "${file}" || fail "stats workflow should generate language-count.svg"
grep -Fq '{language}-lines.svg' "${file}" || fail "stats workflow should describe per-language svg badges"
grep -q '/src/branch/${{ env.BADGE_BRANCH }}/README.md' "${file}" || fail "stats workflow summary should link to stats branch root README"
! rg -q '/src/branch/\$\{\{ env.BADGE_BRANCH \}\}/\$\{\{ env.BADGE_DIR \}\}/README\.md' "${file}" || fail "stats workflow should not place README.md under the badge directory"
! rg -q 'img\.shields\.io/endpoint' "${file}" || fail "stats workflow should not depend on Shields endpoint badges"
! rg -q 'total-lines\.json|total-files\.json|\$\{lang_id\}-lines\.json|\$\{lang_id\}-files\.json' "${file}" || fail "stats workflow should not generate json badge payloads"
! rg -q 'GitHub Actions' "${file}" || fail "stats workflow should not mention GitHub Actions"
grep -q 'Gitea Actions' "${file}" || fail "stats workflow should mention Gitea Actions in generated content"
grep -q 'badges/cpp-lines.svg' "${workflow_doc}" || fail "WORKFLOW.md should show svg badge usage examples"
! rg -q 'img\.shields\.io/endpoint|cpp-lines\.json' "${workflow_doc}" || fail "WORKFLOW.md should not document json endpoint badge usage"
}
test_stats_workflow_uses_emoji_commit_messages() {
local file
file="${REPO_ROOT}/.gitea/workflows/update_stats_badge.yaml"
grep -q 'git commit -m ":bar_chart: chore(stats): 初始化统计分支"' "${file}" || fail "stats workflow should initialize stats branch with an emoji commit message"
grep -q 'COMMIT_MSG=":bar_chart: chore(stats): 更新代码统计 \[' "${file}" || fail "stats workflow should use an emoji commit prefix for future stats updates"
}
test_entrypoint_uses_platform_aware_multiarch_verification() {
local file
file="${REPO_ROOT}/docker-runner/common/entrypoint.sh"
grep -q 'docker run --rm --platform linux/arm64 alpine uname -m' "${file}" || fail "entrypoint.sh should verify arm64 support with an explicit platform"
grep -q 'docker run --rm --platform linux/amd64 alpine uname -m' "${file}" || fail "entrypoint.sh should verify amd64 support with an explicit platform"
! rg -q 'docker run --rm arm64v8/alpine uname -m' "${file}" || fail "entrypoint.sh should not use arm64v8/alpine without an explicit platform"
! rg -q 'docker run --rm amd64/alpine uname -m' "${file}" || fail "entrypoint.sh should not use amd64/alpine without an explicit platform"
}
test_preset_compose_uses_env_for_instance
test_workflows_do_not_hardcode_company_server
test_stats_workflow_uses_workflow_secret_consistently
test_release_workflow_uses_inline_release_slot
test_stats_workflow_uses_inline_stats_slot
test_shared_workspace_helper_is_removed
test_register_default_capacity_is_four
test_presets_do_not_mount_workspace_helper
test_workflow_docs_and_links_use_actual_paths
test_workflow_doc_describes_workspace_architecture
test_presets_do_not_mount_check_crlf_helper
test_runner_data_is_gitignored
test_readme_has_project_intro_and_navigation
test_deployment_doc_stays_runner_focused
test_presets_define_expected_hostname
test_preset_env_examples_exist
test_preset_compose_supports_runner_identity_overrides
test_register_requires_python_yaml_path
test_setup_requires_upgrade_helper
test_entrypoint_uses_shared_buildx_builder_creation
test_stats_workflow_avoids_eval_find
test_stats_workflow_uses_svg_badges
test_stats_workflow_uses_emoji_commit_messages
test_entrypoint_uses_platform_aware_multiarch_verification
echo "template_defaults_test.sh: PASS"