test(new): 新增 NewTest 工程;两个回归脚本支持 --escape/--only 单独跑
NewTest:M2.4 new 描述符冒烟(stack→alloca、heap→malloc;memgr 段待确认管理器 API,非法组合以注释列出)。 regression_test.py / SequentialRegressionTest.py 新增 --escape(只跑生命期/逃逸 + new 相关项目)与 --only(逗号分隔指定项目);两者都会在 Test/<名> 与仓库根 <名> 查找 project.json / project.vpj,故根级 Escape* 项目也能直接跑;--escape 忽略 README 难度顺序。 README.md:新增「一·附 生命期/逃逸相关测试」一节,列出 5 个项目的期望结果与单独运行命令。
This commit is contained in:
1 parent
f5544eed80
commit
42ca901b28
5 files changed
+195
-3
No files matched your search
@@ -0,0 +1,46 @@
|
||||
from stdint import *
|
||||
import w32.win32console
|
||||
import t, c
|
||||
from t import CInt, CExport
|
||||
import stdio
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# M2.4 `new` 描述符分配:三来源各自落地不同(IR 差异见下方注释)
|
||||
# stack → 帧内 alloca(无 malloc 调用,随帧失效)
|
||||
# heap → call i8* @malloc(i64 sizeof) + bitcast 到 T*
|
||||
# memgr → call i8* @<Mgr>.alloc(i64 sizeof) + bitcast 到 T*
|
||||
# 本工程做冒烟验证:能编译、能跑出正确字段值。
|
||||
# 描述符顺序无关;省略来源 = heap(D3),省略生命期由来源决定。
|
||||
# =============================================================================
|
||||
|
||||
|
||||
novirtual class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
def main() -> CInt | CExport:
|
||||
w32.win32console.SetConsoleCP(65001)
|
||||
w32.win32console.SetConsoleOutputCP(65001)
|
||||
|
||||
# (1) stack:等价既有写法 &Point(1, 2),生命期随帧
|
||||
p1: Point* = new stack slife Point(1, 2)
|
||||
stdio.printf("stack: %d %d\n", p1.x, p1.y)
|
||||
|
||||
# (2) heap:malloc(sizeof(Point)) + 构造(生命期默认 hlife)
|
||||
p2: Point* = new heap Point(3, 4)
|
||||
stdio.printf("heap : %d %d\n", p2.x, p2.y)
|
||||
|
||||
# (3) memgr(x):从 memhub 管理器取存储
|
||||
# 待确认 memhub 管理器的构造 API(MemBuddy 建立方式)后打开本段:
|
||||
# m: memhub.MemBuddy* = <构造管理器>
|
||||
# p3: Point* = new memgr(m) hlife Point(5, 6)
|
||||
# stdio.printf("memgr: %d %d\n", p3.x, p3.y)
|
||||
|
||||
# 非法组合(应报 LifetimeError)——要验证时打开其中一条:
|
||||
# 1) 语义矛盾:bad: Point* = new stack hlife Point(7, 8)
|
||||
# 2) 多来源 :new stack heap Point(7, 8) → 解析期报错
|
||||
# 3) 多生命期:new heap slife hlife Point(7, 8) → 解析期报错
|
||||
|
||||
return 0
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json",
|
||||
"name": "NewTest",
|
||||
"version": "1.0.0",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": ["-filetype=obj", "-relocation-model=pic"]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "clang++",
|
||||
"flags": ["-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32"],
|
||||
"output": "NewTest.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"sha1_slice_level": 1,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,31 @@
|
||||
|
||||
---
|
||||
|
||||
## 一·附、生命期 / 逃逸(escape)相关测试
|
||||
|
||||
验证 M1–M2.4 的生命期检查(栈地址逃逸、借用传播、函数逃逸摘要)与 `new` 描述符分配。
|
||||
因历史原因前 4 个位于**仓库根**(不在 `Test/` 下),`NewTest` 位于 `Test/`:
|
||||
|
||||
| 项目 | 位置 | 期望结果 |
|
||||
|---|---|---|
|
||||
| EscapeTest | 仓库根 `EscapeTest/` | **编译失败**:`return Mid(&l)` → `LifetimeError`(v2 传递闭包) |
|
||||
| EscapeOkTest | 仓库根 `EscapeOkTest/` | 编译通过并输出 `Hi/hello/AB/x/aa/loop`(值拷贝/静态/帧内使用等豁免不得误报) |
|
||||
| EscapeMethodTest | 仓库根 `EscapeMethodTest/` | **编译失败**:方法 / `__init__` / 嵌套函数交出栈地址 → `LifetimeError`(三用例互斥,一次开一条) |
|
||||
| EscapeXmodTest | 仓库根 `EscapeXmodTest/` | **编译失败**:跨模块摘要(`.vpi` 落盘 `# esc=`)路径 → `LifetimeError` |
|
||||
| NewTest | [Test/NewTest/](NewTest/App/main.vp) | 编译通过并输出 `stack` / `heap` 的字段值(`memgr(x)` 段待确认管理器构造 API) |
|
||||
|
||||
单独运行(`regression_test.py` 与 `SequentialRegressionTest.py` 都支持):
|
||||
|
||||
```bash
|
||||
python Test/regression_test.py --escape # 只跑上表全部项目
|
||||
python Test/regression_test.py --only NewTest # 只跑指定项目(逗号分隔可多个)
|
||||
python Test/SequentialRegressionTest.py --escape --exe <TransPyV.exe> --rebuild-includes
|
||||
```
|
||||
|
||||
> `--escape` / `--only` 会在 `Test/<名>` 与**仓库根** `<名>` 两处查找 `project.json` / `project.vpj`,所以根级 escape 项目也能直接跑;`--escape` 会忽略 README 的难度顺序。
|
||||
|
||||
---
|
||||
|
||||
## 二、建议实现顺序(难度升序,共 68 步)
|
||||
|
||||
按"先基础语法 → 再类型系统 → 再容器/OOP → 再泛型/闭包难点 → 最后编译器自身库与外部集成"推进,每步按依赖关系排序。
|
||||
|
||||
@@ -33,6 +33,37 @@ PROJECTRANS = os.path.join(PROJECT_ROOT, "TransPyC", "Projectrans.py")
|
||||
# README.md 路径(顺序来源,原 TestsOverview.md)
|
||||
OVERVIEW = os.path.join(TEST_DIR, "README.md")
|
||||
|
||||
# ============================================================
|
||||
# 单独运行支持:--escape / --only
|
||||
# ============================================================
|
||||
# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符):
|
||||
# Test/ 下:NewTest;仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest
|
||||
ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest"]
|
||||
|
||||
|
||||
def find_project_file(proj_dir):
|
||||
"""项目目录下找 project.json(Test/ 约定)或 project.vpj(仓库根约定)"""
|
||||
for fn in ("project.json", "project.vpj"):
|
||||
p = os.path.join(proj_dir, fn)
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def select_named_projects(names):
|
||||
"""按名字解析项目:优先 Test/<name>,其次仓库根 <name>(根级 escape 测试)"""
|
||||
sel = []
|
||||
for n in names:
|
||||
d = os.path.join(TEST_DIR, n)
|
||||
if not os.path.isdir(d):
|
||||
d = os.path.join(PROJECT_ROOT, n)
|
||||
pj = find_project_file(d)
|
||||
if pj is None:
|
||||
print(f" 跳过 {n}: 找不到 project.json / project.vpj")
|
||||
continue
|
||||
sel.append((n, d, pj))
|
||||
return sel
|
||||
|
||||
# 编译超时(秒)
|
||||
COMPILE_TIMEOUT = 300
|
||||
# 运行超时(秒)
|
||||
@@ -178,6 +209,10 @@ def main():
|
||||
help="每次编译追加 --rebuild-includes:清空 includes.binary 全量重编(修复缓存不一致)")
|
||||
parser.add_argument("--number", type=int, default=1,
|
||||
help="从第几个案例开始(1 开始编号),编译该编号及之后的项目;默认 1")
|
||||
parser.add_argument("--escape", action="store_true",
|
||||
help="只跑 escape 相关项目(生命期/逃逸检查 + new 描述符),忽略 README 顺序")
|
||||
parser.add_argument("--only", default=None,
|
||||
help="只跑指定项目(逗号分隔,名字即目录名),忽略 README 顺序")
|
||||
args = parser.parse_args()
|
||||
compile_cmd = build_compile_cmd(args)
|
||||
|
||||
@@ -186,7 +221,14 @@ def main():
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
order = read_test_order()
|
||||
if args.escape:
|
||||
order = [n for (n, d, pj) in select_named_projects(ESCAPE_PROJECTS)]
|
||||
args.number = 1
|
||||
elif args.only:
|
||||
order = [n for (n, d, pj) in select_named_projects([x.strip() for x in args.only.split(",") if x.strip()])]
|
||||
args.number = 1
|
||||
else:
|
||||
order = read_test_order()
|
||||
total = len(order)
|
||||
|
||||
start = max(1, args.number)
|
||||
@@ -200,8 +242,13 @@ def main():
|
||||
for idx, name in enumerate(order, 1):
|
||||
if idx < start:
|
||||
continue
|
||||
# 项目目录:优先 Test/<name>,其次仓库根 <name>(根级 escape 项目)
|
||||
proj_dir = os.path.join(TEST_DIR, name)
|
||||
proj_json = os.path.join(proj_dir, "project.json")
|
||||
if not os.path.isdir(proj_dir): proj_dir = os.path.join(PROJECT_ROOT, name)
|
||||
proj_json = find_project_file(proj_dir)
|
||||
if proj_json is None:
|
||||
print(f" [SKIP] {name}: 找不到 project.json / project.vpj")
|
||||
continue
|
||||
|
||||
if not os.path.isfile(proj_json):
|
||||
print(f"[{idx}/{total}] {name}: 缺少 project.json,跳过")
|
||||
|
||||
+46
-1
@@ -82,6 +82,38 @@ def find_test_projects():
|
||||
return projects
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 单独运行支持:--escape / --only
|
||||
# ============================================================
|
||||
# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符):
|
||||
# Test/ 下:NewTest;仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest
|
||||
ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest"]
|
||||
|
||||
|
||||
def find_project_file(proj_dir):
|
||||
"""项目目录下找 project.json(Test/ 约定)或 project.vpj(仓库根约定)"""
|
||||
for fn in ("project.json", "project.vpj"):
|
||||
p = os.path.join(proj_dir, fn)
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def select_named_projects(names):
|
||||
"""按名字解析项目:优先 Test/<name>,其次仓库根 <name>(根级 escape 测试)"""
|
||||
sel = []
|
||||
for n in names:
|
||||
d = os.path.join(TEST_DIR, n)
|
||||
if not os.path.isdir(d):
|
||||
d = os.path.join(PROJECT_ROOT, n)
|
||||
pj = find_project_file(d)
|
||||
if pj is None:
|
||||
print(f" 跳过 {n}: 找不到 project.json / project.vpj")
|
||||
continue
|
||||
sel.append((n, d, pj))
|
||||
return sel
|
||||
|
||||
|
||||
def compile_project(name, proj_dir, proj_json_path):
|
||||
"""编译单个测试项目,返回 (success, elapsed, output)"""
|
||||
cmd = f'D:\\Python312\\python.exe -u "{PROJECTRANS}" --project "{proj_json_path}" --clean'
|
||||
@@ -172,12 +204,25 @@ def extract_error_context(stdout, stderr, context_lines=5):
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
ap = argparse.ArgumentParser(description="TransPyC 回归测试(可用 --escape/--only 单独跑子集)")
|
||||
ap.add_argument("--escape", action="store_true",
|
||||
help="只跑 escape 相关项目(生命期/逃逸检查 + new 描述符)")
|
||||
ap.add_argument("--only", default=None,
|
||||
help="只跑指定项目(逗号分隔,名字即目录名)")
|
||||
args = ap.parse_args()
|
||||
|
||||
print("=" * 70)
|
||||
print("TransPyC 回归测试")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
projects = find_test_projects()
|
||||
if args.escape:
|
||||
projects = select_named_projects(ESCAPE_PROJECTS)
|
||||
elif args.only:
|
||||
projects = select_named_projects([n.strip() for n in args.only.split(",") if n.strip()])
|
||||
else:
|
||||
projects = find_test_projects()
|
||||
total = len(projects)
|
||||
print(f"发现 {total} 个测试项目\n")
|
||||
|
||||
|
||||
Reference in new issue
Block a user