CI:Gitea Actions 落地 L0+L0.5(补 T-6)
新增 .gitea/workflows/ci.yml:push/PR 触发两个 Windows job—— l0-gates 跑 pre-commit --all-files(无 Keil .map 时自适应跳过 mem-numbers), l05-host-unit-tests 遍历 test/host/**/run_unit_test.py(缺 MSVC 则告警降级跳过)。 刻意不跑 @build(需 Keil 授权);L1/L2/L3 需真机+ST-LINK,不上 CI。 新增 scripts/ci_local.py:本地跑与 CI 等价的门禁(自动选用装了 pre-commit 的解释器;.map 自适应;退出码 0/1/2 与 CI 对齐),供提交前自查与 runner 就绪前过渡。 文档同步:测试开发标准流程 §9(CI 章节)/§1/§4.1、待处理内容 T-6 销项、 项目部署环境要求 §2.11(runner 前置)、pre-commit 使用说明 §1、scripts/README.md、AGENTS.md。 验证:scripts/ci_local.py rc=0(L0 14 钩子全过 + L0.5 39/39)。
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
# Gitea Actions CI —— STM32F4-Base 持续集成
|
||||
#
|
||||
# 覆盖范围(**只做不依赖硬件的层**,见 docs/测试说明/测试开发标准流程.md §1/§9):
|
||||
# L0 静态门禁 = pre-commit 全部钩子(等价本地 `pre-commit run --all-files`)
|
||||
# L0.5 主机侧纯逻辑单测 = test/host/**(MSVC 离线编译,零硬件)
|
||||
#
|
||||
# ⛔ L1 板端套件(test/board/,需真机 + ST-LINK)/ L2 @test 闭环 / L3 人工目检
|
||||
# **不上 CI** —— 需要开发板与烧录器,只能在本地跑。
|
||||
#
|
||||
# 明确不做的两件事(都是刻意为之):
|
||||
# 1. `@build`(Keil UV4)—— 需本机 Keil 授权,runner 上不具备;且 L0 的 cppcheck +
|
||||
# 代码规范已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(AGENTS.md)。
|
||||
# 2. `mem-numbers` 钩子 —— 它的事实源是 `MDK-ARM/**/*.map`(Keil 构建产物、已 gitignore),
|
||||
# 无 `@build` 即无 `.map`。故本 workflow 在**检测不到 .map 时自动跳过该钩子**
|
||||
# (见下方 "决定跳过哪些钩子" 步骤),有 .map 的 runner(如自建带 Keil 的机器)则照常跑。
|
||||
#
|
||||
# 触发:push(除纯文档路径外)与 pull_request。
|
||||
#
|
||||
# ⚠️ 前置(部署到 Gitea 时需确认):
|
||||
# - 该 Gitea 实例已启用 Actions:`[actions] ENABLED = true`(site admin 配置)
|
||||
# - 已注册一个 **Windows** runner(labels 含 `windows`),且装有:
|
||||
# Python 3.x、cppcheck、pre-commit、MSVC(VS2019/2022 的 C++ 工具链 + Windows SDK)
|
||||
# - 缺 MSVC 时 L0.5 步骤会**降级为提示并跳过**(不硬失败),但会打印醒目 WARNING
|
||||
#
|
||||
# 本地等价命令(与 CI 同一套判定,见 scripts/ci_local.py):
|
||||
# & '.venv\Scripts\python.exe' scripts/ci_local.py
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '**/*.md'
|
||||
- '.workbuddy-ai/**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '**/*.md'
|
||||
- '.workbuddy-ai/**'
|
||||
workflow_dispatch:
|
||||
|
||||
# 同一分支的旧运行自动取消,省 runner
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# ------------------------------------------------------------------
|
||||
# L0:静态门禁(pre-commit 钩子全集)
|
||||
# ------------------------------------------------------------------
|
||||
l0-gates:
|
||||
name: L0 静态门禁 (pre-commit)
|
||||
runs-on: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# 钩子要对比基线与历史(doc-links 等读工作区即可,但 gitlint/cubemx 需完整历史)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 环境自检(Python / cppcheck / pre-commit)
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$missing = @()
|
||||
foreach ($item in @(
|
||||
@{ name = 'Python'; cmd = 'python' },
|
||||
@{ name = 'cppcheck'; cmd = 'cppcheck' }
|
||||
)) {
|
||||
if (-not (Get-Command $item.cmd -ErrorAction SilentlyContinue)) {
|
||||
$missing += $item.name
|
||||
}
|
||||
}
|
||||
if ($missing.Count -gt 0) {
|
||||
Write-Host "::error::runner 缺少:$($missing -join ', ') —— 请安装后重跑"
|
||||
exit 1
|
||||
}
|
||||
python --version
|
||||
cppcheck --version
|
||||
|
||||
# pre-commit:优先用 `python -m pre_commit`(不依赖 PATH 里的 console script)
|
||||
python -m pre_commit --version
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "::error::runner 缺少 pre-commit(pip install pre-commit / uv tool install pre-commit)"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "runner 环境自检通过"
|
||||
|
||||
- name: 安装 pre-commit 钩子到本仓库
|
||||
shell: pwsh
|
||||
run: python -m pre_commit install --install-hooks
|
||||
|
||||
- name: 决定跳过哪些钩子(Keil 依赖)
|
||||
id: skip
|
||||
shell: pwsh
|
||||
run: |
|
||||
# mem-numbers 的事实源是 Keil 构建产物 .map(已 gitignore)。
|
||||
# 无 .map => 本 runner 不可能跑 L0 的该钩子 => 跳过(不是"通过",是"不适用")。
|
||||
$map = Get-ChildItem -Path 'MDK-ARM' -Recurse -Filter '*.map' -ErrorAction SilentlyContinue
|
||||
if ($map) {
|
||||
Write-Host "找到 .map:$($map[0].FullName) —— mem-numbers 照常运行"
|
||||
"value=" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
} else {
|
||||
Write-Host "::notice::未找到 .map(未做 Keil 构建)—— 跳过 mem-numbers 钩子"
|
||||
"value=mem-numbers" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
}
|
||||
|
||||
- name: 运行 L0 门禁(--all-files)
|
||||
shell: pwsh
|
||||
env:
|
||||
SKIP: ${{ steps.skip.outputs.value }}
|
||||
run: |
|
||||
if ($env:SKIP) { Write-Host "SKIP=$env:SKIP" }
|
||||
python -m pre_commit run --all-files --show-diff-on-failure
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "::error::L0 静态门禁未通过(详见上方输出)"
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# L0.5:主机侧纯逻辑单测(零硬件;缺 MSVC 则跳过)
|
||||
# ------------------------------------------------------------------
|
||||
l05-host-unit-tests:
|
||||
name: L0.5 主机侧单测 (MSVC)
|
||||
runs-on: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: 探测 MSVC(VS BuildTools / Community 等)
|
||||
id: msvc
|
||||
shell: pwsh
|
||||
run: |
|
||||
# 与 test/host/common/unit_test_harness.py 的 VS_ROOTS 保持一致
|
||||
$roots = @(
|
||||
'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools',
|
||||
'C:\Program Files\Microsoft Visual Studio\2022\BuildTools',
|
||||
'C:\Program Files\Microsoft Visual Studio\2022\Community',
|
||||
'C:\Program Files\Microsoft Visual Studio\2022\Professional'
|
||||
)
|
||||
$found = $false
|
||||
foreach ($r in $roots) {
|
||||
if (Test-Path $r) {
|
||||
$cl = Get-ChildItem -Path $r -Recurse -Filter 'cl.exe' -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -like '*Hostx64\x64*' } | Select-Object -First 1
|
||||
if ($cl) { $found = $true; Write-Host "找到 cl.exe: $($cl.FullName)"; break }
|
||||
}
|
||||
}
|
||||
if ($found) {
|
||||
"value=yes" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
} else {
|
||||
Write-Host "::warning::runner 未安装 MSVC(VS C++ 工具链)—— 跳过 L0.5 单测。请安装 VS BuildTools 后重跑。"
|
||||
"value=no" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
}
|
||||
|
||||
- name: 运行主机侧单测
|
||||
if: steps.msvc.outputs.value == 'yes'
|
||||
shell: pwsh
|
||||
run: |
|
||||
# 遍历 test/host/*/run_unit_test.py(各模块自带运行器,统一走 common harness)
|
||||
$runners = Get-ChildItem -Path 'test/host' -Recurse -Filter 'run_unit_test.py' |
|
||||
Sort-Object FullName
|
||||
if (-not $runners) {
|
||||
Write-Host "::warning::未找到 test/host/**/run_unit_test.py —— 跳过"
|
||||
exit 0
|
||||
}
|
||||
$fail = 0
|
||||
foreach ($r in $runners) {
|
||||
Write-Host "================================"
|
||||
Write-Host "运行:$($r.FullName)"
|
||||
Write-Host "================================"
|
||||
python $r.FullName
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "::error::单测失败:$($r.FullName)(退出码 $LASTEXITCODE)"
|
||||
$fail = 1
|
||||
}
|
||||
}
|
||||
exit $fail
|
||||
|
||||
- name: L0.5 跳过说明
|
||||
if: steps.msvc.outputs.value == 'no'
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "L0.5 未执行:runner 缺 MSVC。"
|
||||
Write-Host "本地等价命令:& '.venv\Scripts\python.exe' scripts/ci_local.py"
|
||||
exit 0
|
||||
@@ -34,6 +34,7 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
|
||||
- 提交信息:**单行中文标题 <100 字符**(gitlint),UTF-8 无 BOM + `git commit -F <文件>`(防终端转码破坏)
|
||||
- 代码规范 V1.0:小写下划线命名(`g_`/`s_`/`p_`/`a_` 前缀)、头文件保护宏 `XXX_H`、常量在右、文件头模块注释块 → `docs/代码规范/`
|
||||
- 全库扫描 `pre-commit run --all-files`;详见 `docs/代码规范/pre-commit使用说明.md`
|
||||
- **CI(Gitea Actions,2026-10-01 T-6)**:`.gitea/workflows/ci.yml` 在 push/PR 跑 **L0 静态门禁 + L0.5 主机侧单测**(Windows runner);**不含 L1/L2/L3**(需真机+ST-LINK)、**刻意不跑 `@build`**(需 Keil 授权)、无 `.map` 时自适应跳过 `mem-numbers`。本地等价自查:`& '.venv\Scripts\python.exe' scripts/ci_local.py`(**runner 未就绪时这是 L0 的唯一保障**,不能因"已配 CI"放宽本地门禁)。详见 `docs/测试说明/测试开发标准流程.md` §9
|
||||
|
||||
## 平台约束
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ git commit
|
||||
|
||||
**两阶段钩子**:`pre-commit` 阶段拦截代码问题,`commit-msg` 阶段拦截格式问题。任一失败 → commit 中止。
|
||||
|
||||
> **CI 侧的同一套门禁(2026-10-01 T-6)**:Gitea Actions(`.gitea/workflows/ci.yml`)在 push / PR 时
|
||||
> 以 `pre-commit run --all-files` 复跑**全部上述钩子**(`mem-numbers` 在无 Keil `.map` 的 runner 上自适应跳过)。
|
||||
> 本地提交前想跑同一套判定:`& '.venv\Scripts\python.exe' scripts/ci_local.py`。详见 `../测试说明/测试开发标准流程.md` §9。
|
||||
|
||||
### 依赖清单
|
||||
|
||||
| 依赖 组件 | 安装方式 | 用途 |
|
||||
|
||||
+10
-1
@@ -53,7 +53,16 @@
|
||||
> 看门狗运行器 170 行 → 55 行声明式。
|
||||
> - **T-4(P2)**:`test/board/test_common.h` 集中断言内核,4 套件绑定自身统计变量。
|
||||
> - **T-5(P2)**:`test/run_all_suites.py` 多套件编排(`try/finally` 无条件还原开关)。
|
||||
> - **T-6(P3)**:**Gitea Actions CI 落地(补办,2026-10-01)**。远程为 Gitea 1.25.4(`gitea.bitnasdaq.vip/wandering1/STM32F4-Base.git`),已启用 Actions →
|
||||
> 新增 `.gitea/workflows/ci.yml`:**两个 Windows job**,`l0-gates`(`pre-commit run --all-files`,检测不到 Keil `.map` 时自适应跳过 `mem-numbers`)+
|
||||
> `l05-host-unit-tests`(遍历 `test/host/**/run_unit_test.py`,缺 MSVC 则打 WARNING 降级跳过)。**刻意不跑 `@build`**(需 Keil 授权)。
|
||||
> 另新增 **`scripts/ci_local.py`**(本地跑与 CI 等价的门禁,自动选用装了 pre-commit 的解释器;退出码 0/1/2 与 CI 对齐)。
|
||||
> 登记依据见 [`测试说明/测试开发标准流程.md`](测试说明/测试开发标准流程.md) §9;覆盖边界与"刻意不含项"见该节与 `../.gitea/workflows/ci.yml` 文件头。
|
||||
|
||||
> ⚠️ **部署前置(未完成,需人工确认)**:CI 仅在 runner 就绪后才真正生效——
|
||||
> ① Gitea 实例 `[actions] ENABLED = true`;② 注册一个 **Windows runner(labels 含 `windows`)**,装 Python 3.x / cppcheck / pre-commit / MSVC(VS C++ 工具链 + Windows SDK)。
|
||||
> runner 未就绪前,**提交前仍须在本地跑 `scripts/ci_local.py` 或 `pre-commit run --all-files` 自查**(这是 L0 的唯一保障,不能因"已配 CI"而放宽)。
|
||||
|
||||
| # | 事项 | 优先级 | 问题与证据 | 处置方向 | 出处 |
|
||||
|---|---|---|---|---|---|
|
||||
| T-6 | **无 CI——L0/L0.5 已具备上 CI 条件** | P3 | L0(pre-commit 门禁,纯 Python + cppcheck)与 L0.5(MSVC 单测,纯标准库运行器)**均不依赖硬件**,可直接在 CI runner 跑;L1/L2 需真机与 ST-LINK,只能本地。当前全靠开发者本地 `pre-commit install`,新机器漏装即裸奔 | 待有 CI 平台时:挂 L0(`pre-commit run --all-files`)+ L0.5;L1/L2 保留本地。前置:CI runner 需装 cppcheck + VS BuildTools(或自建带工具链镜像) | 2026-10-01 评审;`测试说明/测试开发标准流程.md` §1 |
|
||||
| (暂无待办) | — | — | — | — | — |
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
> (无窗口、无硬件、秒级),专测"判定逻辑"。目前仅看门狗模块使用,见
|
||||
> [`看门狗单元测试说明.md`](看门狗单元测试说明.md)。适格对象:算法 / 状态机 / 协议编解码 /
|
||||
> 参数校验等**无副作用**的模块。
|
||||
>
|
||||
> **CI**:**L0 + L0.5 已上 CI**(Gitea Actions,`.gitea/workflows/ci.yml`,2026-10-01 T-6);
|
||||
> L1/L2/L3 需硬件,只在本地。本地等价命令 `scripts/ci_local.py`。详见 §9。
|
||||
|
||||
### 1.1 门禁决策表(改动类型 → 必跑门禁)
|
||||
|
||||
@@ -235,6 +238,7 @@ static void phaseN_run(void) {
|
||||
| `test/host/verify_log.py` | `test/host/` | 日志标记校验(Required / Forbidden → PASS / FAIL)+ 可选套件汇总行校验(T-2) |
|
||||
| **`test/host/common/unit_test_harness.py`** | `test/host/common/` | **L0.5 通用单测 harness**:工具链探测 / `INCLUDE`-`LIB` 拼接 / 编译运行 / 退出码契约。各模块运行器只做声明式配置(T-3) |
|
||||
| **`test/host/watchdog/run_unit_test.py`** | `test/host/watchdog/` | **L0.5 主机侧单测**:声明 `TestBuild` 后调 harness,穷举看门狗判定状态机(零硬件、秒级)。**需本机装有 VS(`cl.exe`)**,见 §4.2 环境要求 |
|
||||
| **`scripts/ci_local.py`** | `scripts/` | **本地跑与 CI 等价的门禁**(L0 + L0.5):自动选用装了 pre-commit 的解释器;`.map` 自适应跳过 `mem-numbers`;退出码 0/1/2 与 CI 对齐。见 §9.3 |
|
||||
| `.opencode/serial_config.json` | 仓库 | **串口端口单一配置源**(当前 COM6 / 115200);换端口只改这个文件 |
|
||||
| `test/host/ftp_*.py`(4 个)/ `ch395f_socket_test.py` / `time_sync_server.py` | `test/host/` | PC 端交互测试工具(FTP 客户端 / socket 回显 / 对时推送) |
|
||||
| `simulator/tests/*.py`(4 个) | `simulator/tests/` | LVGL 模拟器回归脚本(见 §5) |
|
||||
@@ -432,6 +436,68 @@ flowchart LR
|
||||
|
||||
---
|
||||
|
||||
## 9. CI 持续集成(Gitea Actions,2026-10-01 加 T-6)
|
||||
|
||||
远程仓库为 **Gitea 1.25.4**(`gitea.bitnasdaq.vip/wandering1/STM32F4-Base.git`),支持 Gitea Actions
|
||||
(兼容 GitHub Actions 语法,workflow 置于 `.gitea/workflows/`)。落地文件 **`.gitea/workflows/ci.yml`**。
|
||||
|
||||
### 9.1 覆盖范围:只做"不依赖硬件"的层
|
||||
|
||||
| 层 | 上 CI? | 载体 | 理由 |
|
||||
|---|---|---|---|
|
||||
| **L0** 静态门禁 | ✅ | `pre-commit run --all-files`(全部钩子) | 纯 Python + cppcheck,零硬件 |
|
||||
| **L0.5** 主机侧纯逻辑单测 | ✅ | `test/host/**/run_unit_test.py`(MSVC 离线编译) | 纯标准库 + `cl.exe`,零硬件 |
|
||||
| L1 板端套件 | ⛔ | `test/board/` | 需真机 + 串口 + ST-LINK |
|
||||
| L2 `@test` 闭环 | ⛔ | 烧录 + 串口采集 | 同上 |
|
||||
| L3 人工目检 | ⛔ | 上机屏幕 | 人的判断 |
|
||||
|
||||
**明确不做的两件事(都是刻意为之)**:
|
||||
|
||||
1. **不跑 `@build`(Keil UV4)**——需本机 Keil 授权,runner 上不具备。L0 的 cppcheck + 代码规范
|
||||
已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(`AGENTS.md`)。
|
||||
2. **强制跑 `mem-numbers` 钩子**——它的事实源是 `MDK-ARM/**/*.map`(Keil 构建产物,已 `.gitignore`),
|
||||
无 `@build` 即无 `.map`。故 workflow 在**检测不到 `.map` 时自动跳过该钩子**
|
||||
(`SKIP=mem-numbers`,钩子显示 `Skipped` 而非 `Passed`——是"不适用",不是"通过");
|
||||
有 `.map` 的 runner(如自建带 Keil 的机器)则照常跑。
|
||||
|
||||
### 9.2 两个 job
|
||||
|
||||
| job | `runs-on` | 步骤要点 |
|
||||
|---|---|---|
|
||||
| `l0-gates` | `windows` | checkout(`fetch-depth: 0`,gitlint/cubemx 需完整历史)→ 环境自检(Python / cppcheck / `python -m pre_commit`,缺则 `::error::` + exit 1)→ `pre_commit install --install-hooks` → **决定跳过哪些钩子**(探测 `.map`,写入 `GITHUB_OUTPUT`)→ `pre-commit run --all-files --show-diff-on-failure`(`env: SKIP`) |
|
||||
| `l05-host-unit-tests` | `windows` | checkout → **探测 MSVC**(按 VS_ROOTS 找 `Hostx64\x64\cl.exe`)→ 遍历 `test/host/**/run_unit_test.py` 逐个跑(失败 `::error::` 并置退出码);**未探测到 MSVC 时打印醒目 WARNING 并降级跳过**(不硬失败,避免"没装 VS 的 runner 全红") |
|
||||
|
||||
触发:`push`(`paths-ignore: docs/** / **/*.md / .workbuddy-ai/**`)与 `pull_request`,另留 `workflow_dispatch` 手动触发。
|
||||
`concurrency: group: ci-${{ github.ref }}` + `cancel-in-progress: true`(同分支旧运行自动取消,省 runner)。
|
||||
|
||||
### 9.3 本地等价:`scripts/ci_local.py`
|
||||
|
||||
CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runner 还没配好时**,用同一套判定在本地跑:
|
||||
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py # L0 + L0.5(与 CI 等价)
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py --l0-only # 只跑 L0
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py --l05-only # 只跑 L0.5
|
||||
```
|
||||
|
||||
- **自动选用装了 pre-commit 的解释器**(本机 `pre-commit` 只装在 anaconda3,`.venv` 没有);
|
||||
仍不可用时退出码 2 并给出修复提示。
|
||||
- **`.map` 自适应与 CI 完全一致**:本地有 `.map` 就照常跑 `mem-numbers`,没有就跳过。
|
||||
- 退出码:`0` 通过 / `1` 门禁或单测失败 / `2` 环境错误。
|
||||
|
||||
> ⚠️ **CI 不能替代本地门禁**:`.map` 类体积门禁、`@build`、L1/L2 台架项都只存在于本地。
|
||||
> 提交前跑一遍 `ci_local.py`(或 `pre-commit run --all-files`)仍是**强制**动作,见 §7.1。
|
||||
|
||||
### 9.4 部署前置(需人工确认)
|
||||
|
||||
| 项 | 要求 |
|
||||
|---|---|
|
||||
| Gitea Actions | 实例开启 `[actions] ENABLED = true`(site admin 配置) |
|
||||
| Windows runner | 已注册且 **labels 含 `windows`**;装有 Python 3.x、cppcheck、pre-commit、MSVC(VS2019/2022 C++ 工具链 + Windows SDK) |
|
||||
| 缺 MSVC 时 | L0.5 降级为 WARNING 跳过(不硬失败),但**等于该层裸奔**,须尽快补装 |
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:test/ 文件清单(现状)
|
||||
|
||||
| 文件 | 作用 |
|
||||
@@ -464,6 +530,7 @@ flowchart LR
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-01 | 新增 **§9 CI 持续集成(Gitea Actions,T-6)**:`.gitea/workflows/ci.yml`(`l0-gates` + `l05-host-unit-tests`,Windows runner);**L0 + L0.5 上 CI、L1/L2/L3 留本地**;刻意不跑 `@build`、`.map` 缺失时自适应跳过 `mem-numbers`;新增 `scripts/ci_local.py` 本地等价命令(§4.1 登记);§1 补 CI 说明;`docs/待处理内容.md` T-6 销项 |
|
||||
| 2026-10-01 | §3.6 新增 **`test/run_all_suites.py`** 多套件回归编排(`try/finally` 无条件还原套件开关;T-5);`test/README.md` 同步 |
|
||||
| 2026-10-01 | §3.2 断言宏改为**集中内核**:新增 `test/board/test_common.h`(`test_stats_t` + `TEST_CHECK_IMPL`/`TEST_REPORT_IMPL`),4 套件各自用一行宏绑定自身统计变量(T-4;零行为变化、零 RAM 增量、`@build` 0/0,四套件均 0/0 验证)。附 `test_common.h` 不 include `dbg_log.h` 的原因(`DBG_TAG` 冻结 → `#47-D`) |
|
||||
| 2026-10-01 | 新增 §7.1 钩子 **`test-suite-switch`**(测试套件开关=产品构建,防测试固件静默提交)+ §3.1 铁律(跑完套件回归务必改回产品构建);依据 `docs/待处理内容.md` T-1(测试架构评审 P0)。同步更新 `../代码规范/pre-commit使用说明.md` §2.13 |
|
||||
|
||||
@@ -376,6 +376,26 @@ pre-commit run --all-files
|
||||
> 获取渠道优先级:① `winget`(自动校验、免手动)→ ② 上表官方页 → ③ 网络受限时自行选可信镜像;
|
||||
> Keil / ST 官网访问受限时可走代理。
|
||||
|
||||
### 2.11 CI runner 环境(Gitea Actions,2026-10-01 T-6)
|
||||
|
||||
CI 跑 **L0 静态门禁 + L0.5 主机侧单测**(**不含 L1/L2/L3**——需真机 + ST-LINK,只在本地)。
|
||||
workflow 见 `.gitea/workflows/ci.yml`;覆盖边界与判定细节见 [`../测试说明/测试开发标准流程.md`](../测试说明/测试开发标准流程.md) §9。
|
||||
|
||||
| 项 | 要求 | 说明 |
|
||||
|---|---|---|
|
||||
| Gitea 版本 | ≥ 1.21(实测远程 **1.25.4**) | workflow 置于 `.gitea/workflows/`,兼容 GitHub Actions 语法 |
|
||||
| Actions 开关 | 实例 `[actions] ENABLED = true` | site admin 配置;未开启则 push 不触发任何 job |
|
||||
| runner OS | **Windows**(`runs-on: windows`) | labels 须含 `windows`;与开发机同平台,避免脚本语法分叉 |
|
||||
| runner 软件 | Python 3.x、cppcheck、pre-commit、**MSVC**(VS C++ 工具链 + Windows SDK) | 前三个缺 → `l0-gates` **硬失败**(`::error::`);MSVC 缺 → `l05-host-unit-tests` **降级跳过并告警**(不硬失败,但等于 L0.5 裸奔) |
|
||||
| Keil | **不需要** | 刻意不跑 `@build`(需授权);`.map` 缺失时 `mem-numbers` 钩子自动跳过 |
|
||||
| `mem-numbers` | 有 `.map` 就跑,无则 `SKIP` | Keil 构建产物 `.map` 已 gitignore;自建带 Keil 的 runner 会照常跑该钩子 |
|
||||
|
||||
**runner 就绪前的过渡**:CI 只是"多一道网",**不能替代本地门禁**。提交前仍须在本地跑:
|
||||
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py # 与 CI 同一套判定(L0 + L0.5)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、目标板环境(DTU 硬件)
|
||||
@@ -552,6 +572,7 @@ pre-commit run --all-files
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-01 | **新增 §2.11 CI runner 环境(Gitea Actions,T-6)**:Gitea ≥1.21(实测 1.25.4)+ `[actions] ENABLED` + Windows runner(装 Python/cppcheck/pre-commit/MSVC);**不需要 Keil**(刻意不跑 `@build`,无 `.map` 时 `mem-numbers` 自动跳过);补"CI 不能替代本地门禁"与 `scripts/ci_local.py` 过渡命令 |
|
||||
| 2026-10-01 | **补主机侧单元测试(L0.5)环境要求**:§2.3 由"PC 模拟器环境"扩为"PC 侧编译环境(模拟器 + 主机侧单元测试)"——明确**同一套 VS 的 `cl.exe` 同时服务模拟器与单测**,MSVC 从"仅模拟器需要"升级为"提交门禁需要";§2.7 pre-commit 表补 `watchdog-unit-test` 钩子及"未装 VS 会被拦截";第 3 步 / 第 10 步冒烟 / 搭建清单 / §2.10 下载表(补 VS2022/2026 行)/ 速查表(补 `cl.exe` 缺失与 `C1083 stdio.h` 两条)/ 相关文档同步;总览拓扑图加 `MSVC cl.exe` 节点 |
|
||||
| 2026-09-21 | 更正 SDL2 描述:`simulator/SDL2/` 并不入库(gitignore,fresh clone 缺失),原"**随仓库提供**/不用装"表述有误;第 3 步补全官方预编译包下载(直链 + 内网中转)、解压、`include/SDL2/` 头文件复制、5 文件校验步骤;§2.3 表格与速查表同步修正 |
|
||||
| 2026-09-21 | 模拟器构建改为自适应:新增 `simulator/build_sim.bat/.ps1`(vswhere 自动探测本机 VS 大版本 → 生成器 16=2019 / 17=2022 / 18=2026;`build/` 生成器不符自动删除重建,删除带重试抗文件锁);VS 版本不再固定,2019/2022/2026 兼容:总览图、§2.3、第 3 步、第 10 步冒烟、搭建清单、速查表同步更新 |
|
||||
|
||||
@@ -23,12 +23,23 @@
|
||||
| `ram_report.py` | 从 `.map` 生成**静态 RAM/Flash 占用**报告(→ `docs/内存管理/data/`) |
|
||||
| `stack_report.py` | 从 Keil 静态调用图 + 串口 `[STACK]` 水位生成**每任务最坏链**报告 |
|
||||
|
||||
### CI 与本地等价(2026-10-01 T-6)
|
||||
|
||||
| 脚本 / 文件 | 作用 |
|
||||
|---|---|
|
||||
| `ci_local.py` | **本地跑与 CI 等价的门禁**(L0 pre-commit + L0.5 主机侧单测):自动选用装了 pre-commit 的解释器;无 `.map` 时自适应跳过 `mem-numbers`;退出码 0/1/2 与 CI 对齐。CI 定义见 `../.gitea/workflows/ci.yml`,说明见 `docs/测试说明/测试开发标准流程.md` §9 |
|
||||
|
||||
## 使用方式
|
||||
|
||||
```bash
|
||||
# 全库跑门禁
|
||||
& 'C:\ProgramData\anaconda3\python.exe' -m pre_commit run --all-files
|
||||
|
||||
# 本地跑与 CI 等价的门禁(L0 + L0.5)
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py --l0-only # 只跑 L0
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py --l05-only # 只跑 L0.5
|
||||
|
||||
# 单个脚本
|
||||
& '.venv\Scripts\python.exe' scripts/check_doc_links.py --all
|
||||
& '.venv\Scripts\python.exe' scripts/gen_task_table.py --check # 或 --write / --diff
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""ci_local.py - 本地一键跑"与 CI 等价"的门禁(纯标准库)。
|
||||
|
||||
为什么需要它(2026-10-01 T-6):
|
||||
CI(`.gitea/workflows/ci.yml`)只在 push / PR 时跑,且需要 Gitea Actions +
|
||||
Windows runner 就绪。**提交前想自查、或 runner 还没配好时**,用本脚本在本地
|
||||
跑同一套判定,命令与退出码与 CI 一一对应。
|
||||
|
||||
覆盖范围(与 CI 完全一致):
|
||||
L0 静态门禁 = `pre-commit run --all-files`
|
||||
L0.5 主机侧纯逻辑单测 = `test/host/**/run_unit_test.py`
|
||||
|
||||
刻意**不含**(与 CI 同理由,见 workflow 文件头):
|
||||
- `@build`(需 Keil 授权;CI 也不跑,真编译是开发者本地红线)
|
||||
- `mem-numbers` 钩子(事实源 `.map` 是 Keil 产物——**本地若存在 .map 就照常跑**,
|
||||
与 CI 的自适应逻辑一致)
|
||||
|
||||
退出码:
|
||||
0 = 全部通过(或不适用项已按规则跳过)
|
||||
1 = 有门禁/单测失败
|
||||
2 = 环境错误(缺 pre-commit / python 模块等)
|
||||
|
||||
用法(仓库根目录):
|
||||
& '.venv\\Scripts\\python.exe' scripts/ci_local.py
|
||||
& '.venv\\Scripts\\python.exe' scripts/ci_local.py --l0-only
|
||||
& '.venv\\Scripts\\python.exe' scripts/ci_local.py --l05-only
|
||||
& '.venv\\Scripts\\python.exe' scripts/ci_local.py --keep-going # 失败也继续跑完
|
||||
|
||||
设计依据:`.gitea/workflows/ci.yml`;docs/测试说明/测试开发标准流程.md §9
|
||||
创建日期:2026-10-01
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def _resolve_python():
|
||||
"""挑一个真正装了 pre-commit 的解释器。
|
||||
|
||||
本机可能有多个 python(.venv / anaconda3 / 系统),而 **pre-commit 只装在其中一个**。
|
||||
按优先级探测:当前解释器 → `.venv` → anaconda3 → PATH 上的 `pre-commit` 所在环境。
|
||||
找到即返回 (python_path, note);都找不到则返回 (当前解释器, None) 交由调用方报错。
|
||||
"""
|
||||
candidates = [sys.executable]
|
||||
venv = REPO_ROOT / ".venv" / "Scripts" / "python.exe"
|
||||
if venv.is_file():
|
||||
candidates.append(str(venv))
|
||||
candidates.append(r"C:\ProgramData\anaconda3\python.exe")
|
||||
|
||||
seen = set()
|
||||
for py in candidates:
|
||||
if not py or py in seen or not os.path.isfile(py):
|
||||
continue
|
||||
seen.add(py)
|
||||
try:
|
||||
cp = subprocess.run([py, "-m", "pre_commit", "--version"],
|
||||
capture_output=True, timeout=60)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
continue
|
||||
if cp.returncode == 0:
|
||||
note = None if py == sys.executable else "(自动选用,本解释器无 pre-commit)"
|
||||
return py, note
|
||||
return sys.executable, None
|
||||
|
||||
|
||||
PY, PY_NOTE = _resolve_python()
|
||||
|
||||
|
||||
def _find_map():
|
||||
"""返回 Keil 构建产物 .map 的路径(不存在则 None)。"""
|
||||
hits = sorted((REPO_ROOT / "MDK-ARM").rglob("*.map"))
|
||||
return hits[0] if hits else None
|
||||
|
||||
|
||||
def _find_unit_runners():
|
||||
"""test/host 下所有模块运行器,按路径排序。"""
|
||||
return sorted((REPO_ROOT / "test" / "host").rglob("run_unit_test.py"))
|
||||
|
||||
|
||||
def _run(cmd, env=None):
|
||||
"""跑子进程,输出直通终端。返回退出码。"""
|
||||
print("$ " + " ".join(str(c) for c in cmd))
|
||||
cp = subprocess.run(cmd, cwd=str(REPO_ROOT), env=env)
|
||||
return cp.returncode
|
||||
|
||||
|
||||
def run_l0():
|
||||
"""L0:pre-commit 全部钩子。返回退出码(0/1/2)。"""
|
||||
print("")
|
||||
print("=" * 64)
|
||||
print(" L0 静态门禁(pre-commit --all-files)")
|
||||
print("=" * 64)
|
||||
|
||||
env = dict(os.environ)
|
||||
# 与 CI 相同的自适应:无 .map(未做 Keil 构建)=> mem-numbers 不适用 => 跳过
|
||||
map_path = _find_map()
|
||||
if map_path:
|
||||
print("[INFO] 找到 .map:{}".format(map_path.relative_to(REPO_ROOT)))
|
||||
print("[INFO] mem-numbers 照常运行")
|
||||
else:
|
||||
print("[INFO] 未找到 .map(未做 Keil 构建)-> 跳过 mem-numbers(与 CI 一致)")
|
||||
skip = env.get("SKIP", "")
|
||||
env["SKIP"] = (skip + "," if skip else "") + "mem-numbers"
|
||||
|
||||
return _run([PY, "-m", "pre_commit", "run", "--all-files"], env=env)
|
||||
|
||||
|
||||
def run_l05(args):
|
||||
"""L0.5:主机侧单测。返回退出码(0/1/2)。"""
|
||||
print("")
|
||||
print("=" * 64)
|
||||
print(" L0.5 主机侧纯逻辑单测(MSVC,零硬件)")
|
||||
print("=" * 64)
|
||||
|
||||
runners = _find_unit_runners()
|
||||
if not runners:
|
||||
print("[WARN] 未找到 test/host/**/run_unit_test.py —— 跳过")
|
||||
return 0
|
||||
|
||||
rc_all = 0
|
||||
for r in runners:
|
||||
rel = r.relative_to(REPO_ROOT)
|
||||
print("")
|
||||
print("--- 运行:{} ---".format(rel))
|
||||
rc = _run([PY, str(r)])
|
||||
if rc != 0:
|
||||
print("[FAIL] {}(退出码 {})".format(rel, rc))
|
||||
if rc == 2:
|
||||
print(" (退出码 2 = 环境错误:多半是本机未装 MSVC,见 "
|
||||
"docs/测试说明/测试开发标准流程.md §4.2)")
|
||||
rc_all = 1
|
||||
if not args.keep_going:
|
||||
break
|
||||
else:
|
||||
print("[OK] {}".format(rel))
|
||||
return rc_all
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ap = argparse.ArgumentParser(
|
||||
description="本地跑与 CI 等价的门禁(L0 + L0.5,纯标准库)")
|
||||
g = ap.add_mutually_exclusive_group()
|
||||
g.add_argument("--l0-only", action="store_true", help="只跑 L0 静态门禁")
|
||||
g.add_argument("--l05-only", action="store_true", help="只跑 L0.5 主机侧单测")
|
||||
ap.add_argument("--keep-going", action="store_true",
|
||||
help="某步失败也继续跑后续步骤(默认首错即停)")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
print("=== 本地 CI(等价 .gitea/workflows/ci.yml)===")
|
||||
print("仓库:{}".format(REPO_ROOT))
|
||||
print("Python:{}".format(PY))
|
||||
if PY_NOTE:
|
||||
print(" {}".format(PY_NOTE))
|
||||
|
||||
# 环境预检:pre-commit 必须可用(CI 里由"环境自检"步骤拦)
|
||||
try:
|
||||
probe = subprocess.run([PY, "-m", "pre_commit", "--version"],
|
||||
capture_output=True, timeout=60)
|
||||
except (OSError, subprocess.TimeoutExpired) as e:
|
||||
print("[ERROR] 无法调用 pre-commit:{}".format(e))
|
||||
return 2
|
||||
if probe.returncode != 0:
|
||||
print("[ERROR] 该解释器没有 pre-commit 模块。请任选其一:")
|
||||
print(" - 用装了 pre-commit 的解释器跑本脚本(如 "
|
||||
r"& 'C:\ProgramData\anaconda3\python.exe' scripts/ci_local.py)")
|
||||
print(" - 或在当前环境安装:pip install pre-commit")
|
||||
return 2
|
||||
|
||||
rc = 0
|
||||
try:
|
||||
if not args.l05_only:
|
||||
rc = run_l0()
|
||||
if rc != 0 and not args.keep_going:
|
||||
print("")
|
||||
print("[FAIL] L0 未通过 —— 中止(--keep-going 可继续跑 L0.5)")
|
||||
return 1
|
||||
if not args.l0_only:
|
||||
rc05 = run_l05(args)
|
||||
rc = rc or rc05
|
||||
except FileNotFoundError as e:
|
||||
print("[ERROR] 环境错误:{}".format(e))
|
||||
return 2
|
||||
|
||||
print("")
|
||||
print("=" * 64)
|
||||
if rc == 0:
|
||||
print(" [OK] 本地 CI 全部通过(L0 + L0.5)")
|
||||
else:
|
||||
print(" [FAIL] 本地 CI 有未通过项(见上方输出)")
|
||||
print("=" * 64)
|
||||
return rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user