Skip to content

测试与 CI ​

Code CIN 的回归体系分三层: pytest 测试套件、两条一致性门禁 (gen_isa_docs.py --check 与 gen_native_isa.py --check)、以及 GitHub Actions CI (test / integration / lint / native / dist 五个作业)。

安装开发依赖 ​

bash
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt

requirements-dev.txt 的内容是 -r requirements.txt (只有 rich>=13,<15) 加上:

依赖用途
pytest>=7测试运行器
pytest-cov>=5覆盖率 (CI 的 integration 作业用, --cov-fail-under=70)
ruff>=0.5静态检查

用 pip install -e ".[dev]" 也可以 (见 pyproject.toml 的 [project.optional-dependencies] dev)。

运行测试 ​

bash
python -m pytest                                   # 全量
python -m pytest tests/test_cli.py                 # 单个文件
python -m pytest -k "crom or version" -v           # 按关键字
python -m pytest -rs                               # 显示 skip 原因 (CI integration 用)
python -m pytest --cov=codecin --cov-report=term-missing --cov-fail-under=70

pyproject.toml 已配置 testpaths = ["tests"]、addopts = "-ra"、 pythonpath = ["."], 所以在仓库根目录直接 python -m pytest 就是全量。

临时目录与 native skip

tests/conftest.py 提供会话级的 workdir 夹具, 落在仓库内的 .pytest_tmp/ (会话结束清理), 这样在沙箱环境里也能安全写文件。

不要用 pytest 的 tmp_path: 受限沙箱下子进程无法 scandir 系统临时目录, tmp_path 会在 setup 阶段就 PermissionError。仓库统一约定用 workdir; tests/test_keyboard.py、tests/test_p0_fixes.py 已从 tmp_path 迁到 workdir, tests/test_aot.py 的临时目录用例也用 workdir + monkeypatch 把 aot._NATIVE_DIR 指向假 native/, 而不是真去写 codecin/native/。

依赖原生库的用例 (test_cin_host.py、test_cin_system.py、 test_libs.py、test_native_hardening.py) 用 pytest.mark.skipif(native.get_engine() is None) 跳过。没编译原生库时它们会静默 skip, 所以 CI 的 integration 作业会先断言库真的能加载。

静态检查 (ruff) ​

bash
ruff check codecin cpu.py script tests
ruff check --fix codecin cpu.py script tests      # 自动修可修的
ruff format --check codecin cpu.py script tests   # 可选: 只检查格式

pyproject.toml 的 ruff 配置:

项值
target-versionpy38
line-length100
lint.selectE4, E5, E7, E9, F, I, UP, B, SIM, C4
lint.ignoreE501 (行宽交给 line-length 之外的风格判断)

注意 CI 只跑 ruff check, 不含 ruff format; 提交前至少让 ruff check 干净。

两条一致性门禁 ​

bash
python script/gen_isa_docs.py --check     # docs/ISA.md 与 docs/reference/isa.md 与 isa.py 一致
python script/gen_native_isa.py --check   # Go 常量生成物与 isa.py / __version__ 一致
text
$ python script/gen_isa_docs.py --check
ISA docs up to date.

$ python script/gen_native_isa.py --check
native isa_gen.go up to date.
compiler syscalls.go up to date.
native version_gen.go up to date.
脚本生成物失败提示
gen_isa_docs.pydocs/ISA.md、docs/reference/isa.mdISA doc OUT OF DATE: <path> / ISA doc missing
gen_native_isa.pyengine/isa_gen.go、compiler/syscalls.go、engine/version_gen.go<label> OUT OF DATE / 换行不是 LF (CRLF) / missing

修复方式永远是重新生成, 然后重新编译原生库:

bash
python script/gen_isa_docs.py
python script/gen_native_isa.py
cd codecin/native && sh build.sh && cd ../..
python -m pytest

生成物必须 gofmt 干净且是 LF

gen_native_isa.py --check 按字节比对, Windows 下写出的 CRLF 会被判为不一致。 CI 的 native 作业另有 gofmt -l . 门禁 —— 生成器已按 gofmt 规则对齐映射字面量, 所以正常情况下 gofmt -l 应该是空的。

Go 侧检查 ​

bash
cd codecin/native
go build ./...                # 全部包 (含 cmd/cincc 编译器入口)
go vet ./...
gofmt -l .                    # 必须无输出
go test ./... -race -count=1
go test ./mc -count=1         # cincc 原生编译器

交叉编译自检 (不需要 C 交叉工具链;cmd/cincc 是纯 Go,可一并检查):

bash
cd codecin/native
for t in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64; do
  GOOS="${t%/*}" GOARCH="${t#*/}" CGO_ENABLED=0 \
    go build ./engine ./ir ./compiler ./aot ./mc ./cmd/cincc
done

架构门禁 (CI 里以 shell 断言实现, 本地也有对应的 pytest):

bash
cd codecin/native
# 允许的 package main: c-shared 库入口 main.go + cincc 编译器 cmd/cincc/main.go
grep -rl '^package main' --include='*.go' . \
  | grep -v -E '^\./main\.go$|^\./cmd/cincc/main\.go$'   # 必须无输出
python -m pytest ../tests/test_no_go_cli.py

tests/ 逐文件覆盖点 ​

文件覆盖点
conftest.py会话级工作目录夹具 (workdir → 仓库内 .pytest_tmp/, 会话结束清理)
helpers.py测试辅助: reg/imm/mem 操作数构造、new_cpu、run_program、run_cin_source、run_cin_file、asm_program、snapshot
test_aot.pyAOT 产物可独立运行、C/native/VM 后端、交叉编译 x86-64/ARM64、静态 ELF 无 PT_INTERP、--build-debug、非法 --target 报 AotError; 临时目录生命周期 (陈旧 .aotbuild-*/.aotprobe-* 被 sweep、新鲜目录保留、越界不误删、删除失败告警不抛异常、build() 返回后不留临时目录)
test_sdgcc.py.sdgcc 容器: 头部/段表/压缩/CODE 段与 BIN v3 一致
test_cbackend.pyC 后端: 指令展开、syscall 白名单、目标映射、产物运行
test_bin_compat.pyBIN v3 与旧 BIN v2 的只读兼容
test_native_codegen.pynative 指令降低、基本块模式、native/VM 输出一致性、优化器 -O0..3
test_object_link.py.co 对象、CIN/ASM 跨对象链接、NAME±N/DW/DD/DQ 重定位、extern 函数/全局、struct 指针槽、.colib 归档选择、--gc-sections
test_assembler_ext.py汇编器 .equ 常量、立即数/偏移表达式、符号算术、数据段表达式
test_cin_host.pyCIN 宿主能力 (GUI 画布 / 联网音频 / 桌面集成)
test_cin_new_features.py位运算符、idiv、字符串单字符访问、min/max、floor/ceil/round、atoi、trim/ltrim/rtrim
test_cin_syntax.py新语法: break/continue、do-while、switch、复合赋值、自增自减、三目、类型别名与字面量、转换内建
test_cin_system.py系统原生交互 / Termux API 内建; 依赖原生库, 无则 skip
test_cli.pyargparse 参数解析、帮助文本、退出码
test_compiler_errors.py非法程序必须被 Go 与 Python 两侧同时拒绝; 合法程序两侧都接受
test_environment.py工具链检测与安装提示: find_tool/tool_available/install_hint/missing_tool_message
test_examples.pyexamples/ 每个示例在 Go 原生引擎上完整运行且结果确定
test_features_ab.py--bounds-check / CIN assert、--seed 确定性、--disasm 反汇编
test_features_import.py字符串内建 (substr/indexof/upper/lower) 与 import/lib 模块化
test_features_importloc.pyimport 行级源映射: 错误定位精确到模块 file:line
test_ffi_net.pyFFI 动态库调用与网络系统调用 (HTTP 扩展 / TCP / UDP / DNS)
test_isa_single_source.pyISA 单一真源守卫: 各手写映射表与 isa.py 不漂移
test_libs.py官方标准库 (codecin/lib/*/src/*.cin) 行为回归; 依赖原生库, 无则 skip
test_libs_ext.py新增标准库 bits/stat/hash/validate/matrix/queue 回归
test_literals_and_bom.py数值字面量与带 UTF-8 BOM 源码中 import 的双端一致性
test_native_hardening.py原生库加固: 步数用尽报错、不可信字节码、CROM 解压上限
test_native_lib_lookup.py原生库查找顺序: 架构专属文件名优先于通用名
test_no_go_cli.py架构门禁: 允许 c-shared 入口与 cmd/cincc 编译器, 无用户 CLI; 源码树无构建产物
test_packaging.py打包设计: package-data 含内置库不含二进制、MANIFEST 带 Go 源码、只发 sdist、BuildPyWithNative
test_sparse_memory.py稀疏分页内存: 页按需分配、resident_bytes、越界与负地址提示、保护检查
test_switch_semantics.pyswitch 语义回归 (Go 与 Python 双编译器对齐)
test_version.py版本设施: pyproject 无静态 version、Go BuildVersion 同步、semver、--version/--build-info; build_info() 字段齐全且永不抛异常 (原生库缺失 / dlopen 失败 / 平台探测异常都降级); bump_version.py 的 dry-run 不落盘、非法与不递增版本被拒、副本里真实跑通全链路; CHANGELOG 门禁: CHANGELOG.md 必须含当前版本小节
test_workflows.py工作流静态校验: YAML 可解析、表达式函数白名单 (无 replace)、matrix.* 已声明、job/step 形状、artifact 名唯一

CI 作业 ​

.github/workflows/ci.yml 在 push(master/main)、pull_request 与手动 触发时运行, 权限只有 contents: read, 并有 concurrency 取消同分支旧运行。 所有作业都 submodules: recursive 检出 (文档一致性检查依赖文档站子仓库)。

作业名称Runner / 超时做什么
testpytest (py${{ matrix.python-version }})ubuntu-latest / 30 minPython 3.9 / 3.11 / 3.13 矩阵: 装 requirements-dev.txt → gen_isa_docs.py --check → gen_native_isa.py --check → python -m pytest → 检查是否有源码被 .gitignore 意外吞掉
integration原生/Go 集成 (真实跑原生路径)ubuntu-latest / 45 minPython 3.11 + setup-go (版本取自 codecin/native/go.mod) → go build -buildmode=c-shared -o ../libcodecin_native.so . → 断言 native.get_engine() is not None → python -m pytest -rs --cov=codecin --cov-report=term-missing --cov-fail-under=70, 环境变量 CODECIN_AOT_TESTS=1
dist打包 (wheel/sdist 必须含内置标准库)ubuntu-latest / 30 minCODECIN_SKIP_NATIVE=1 python -m build → 断言 wheel/sdist 各含 ≥19 个 codecin/lib/*/src/*.cin 且不含 .so/.dylib/.dll → 装 wheel 后跑一个 import "str.cin" 的冒烟程序
lintruffubuntu-latest / 15 minruff check codecin cpu.py script tests
nativeGo native (build check)ubuntu-latest / 30 mingo build ./...、go vet ./...、gofmt -l . 门禁、go test ./... -race -count=1、断言 Go 侧无额外用户 CLI (package main 仅 main.go + cmd/cincc)、五次交叉编译自检、Linux c-shared 构建

test 作业的最后一步值得一提: 它用 git ls-files --others --ignored --exclude-standard -- '*.py' '*.go' '*.cin' '*.asm' 列出被忽略的源码文件, 防止新文件因为 .gitignore 规则而根本没被提交。

.github/workflows/release.yml 是发布流程 (tag v* 触发), 含 version-check / native / python-dist / release 四个作业, 见 打包与发布。

test_workflows.py 为什么存在

GitHub Actions 的表达式函数集里没有 replace() —— 用了它的工作流在 GitHub 上 会被判为 Invalid workflow file, 本地却看不出来。tests/test_workflows.py 因此在本地 就把这类错误拦住: 校验 YAML 可解析、表达式函数在官方白名单内、matrix.<key> 都在同 job 的 matrix 里声明过、每个 job 有 runs-on + steps、每个 step 有 uses 或 run。

本地复现 CI ​

bash
# 1) 与 test 作业等价 (任选一个 Python 版本)
python -m pip install -r requirements-dev.txt
python script/gen_isa_docs.py --check
python script/gen_native_isa.py --check
python -m pytest
git ls-files --others --ignored --exclude-standard -- '*.py' '*.go' '*.cin' '*.asm'

# 2) 与 lint 作业等价
ruff check codecin cpu.py script tests

# 3) 与 native 作业等价
cd codecin/native
go build ./... && go vet ./... && gofmt -l . && go test ./... -race -count=1
go test ./mc -count=1
grep -rl '^package main' --include='*.go' . \
  | grep -v -E '^\./main\.go$|^\./cmd/cincc/main\.go$'
for t in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64; do
  GOOS="${t%/*}" GOARCH="${t#*/}" CGO_ENABLED=0 go build ./engine ./ir ./compiler ./aot ./mc ./cmd/cincc
done
go build -buildmode=c-shared -o /tmp/libcodecin_native.so .
cd ../..

# 4) 与 integration 作业等价 (真实原生路径 + 覆盖率门槛)
cd codecin/native && go build -buildmode=c-shared -o ../libcodecin_native.so . && cd ../..
python -c "from codecin import native; assert native.get_engine() is not None"
CODECIN_AOT_TESTS=1 python -m pytest -rs \
  --cov=codecin --cov-report=term-missing --cov-fail-under=70

# 5) 与 dist 作业等价 (本地构建发布物)
CODECIN_SKIP_NATIVE=1 python -m build
python -m pytest tests/test_packaging.py tests/test_version.py

Windows PowerShell 下的等价写法 (Go 部分):

powershell
cd codecin\native
$env:CGO_ENABLED = '1'
go build ./...; go vet ./...; gofmt -l .
go test ./... -race -count=1
go build -buildmode=c-shared -o ..\libcodecin_native.so .
cd ..\..
python -c "from codecin import native; print(native.get_engine())"

覆盖率门槛只在 integration 作业里生效

--cov-fail-under=70 需要原生库就绪 (否则大量用例 skip, 覆盖率自然偏低)。 在没编译原生库的机器上跑带门槛的覆盖率命令会失败, 这是预期行为 —— 先按 编译 Go 原生库 建好库。

相关页面 ​

文档由 VitePress 构建