test(lifetime): 新增生命期负向运行器;NewTest 补 memgr 用例
run_lifetime_negative_tests.py:只用 TransPyV 编译并断言编译失败且输出含 LifetimeError(TPC 解析不了 new、也产不出 LifetimeError,故与 NegativeTest 的 TPC 运行器分开);默认跑 EscapeTest / EscapeMethodTest / EscapeXmodTest,支持 --only/--rebuild-includes。 NewTest:按 MemhubTest 的写法补上 new memgr(m) hlife Point(5,6) 段(memhub.MemBuddy(arena, 65536))。README 同步负向运行器用法。
This commit is contained in:
1 parent
42ca901b28
commit
ed204852c4
3 files changed
+130
-5
No files matched your search
+8
-5
@@ -3,6 +3,8 @@ import w32.win32console
|
||||
import t, c
|
||||
from t import CInt, CExport
|
||||
import stdio
|
||||
import stdlib
|
||||
import memhub
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -32,11 +34,12 @@ def main() -> CInt | CExport:
|
||||
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)
|
||||
# (3) memgr(x):从 memhub 管理器取存储(管理器构造方式见 Test/MemhubTest)
|
||||
arena: bytes = stdlib.malloc(65536)
|
||||
m: memhub.MemBuddy = memhub.MemBuddy(arena, 65536)
|
||||
p3: Point* = new memgr(m) hlife Point(5, 6)
|
||||
stdio.printf("memgr: %d %d\n", p3.x, p3.y)
|
||||
stdlib.free(arena)
|
||||
|
||||
# 非法组合(应报 LifetimeError)——要验证时打开其中一条:
|
||||
# 1) 语义矛盾:bad: Point* = new stack hlife Point(7, 8)
|
||||
|
||||
@@ -142,6 +142,15 @@ python Test/SequentialRegressionTest.py --escape --exe <TransPyV.exe> --rebuild-
|
||||
|
||||
> `--escape` / `--only` 会在 `Test/<名>` 与**仓库根** `<名>` 两处查找 `project.json` / `project.vpj`,所以根级 escape 项目也能直接跑;`--escape` 会忽略 README 的难度顺序。
|
||||
|
||||
**负向断言**("应报 `LifetimeError`")需要 TransPyV 编译——`NegativeTest/run_negative_tests.py` 用的是 TPC,而 `new` 描述符与 `LifetimeError` 只在 TPV 侧实现,故单独提供:
|
||||
|
||||
```bash
|
||||
python Test/run_lifetime_negative_tests.py --exe .\TransPyV\output\TransPyV.exe
|
||||
python Test/run_lifetime_negative_tests.py --exe .\TransPyV\output\TransPyV.exe --only EscapeMethodTest
|
||||
```
|
||||
|
||||
> 默认跑 `EscapeTest` / `EscapeMethodTest` / `EscapeXmodTest`:断言**编译失败且输出含 `LifetimeError`**;编译成功 = 漏检(FAIL),报错但方向不符 = SKIP。
|
||||
|
||||
---
|
||||
|
||||
## 二、建议实现顺序(难度升序,共 68 步)
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""生命期/逃逸负向测试运行器(TransPyV 专用)
|
||||
|
||||
与 NegativeTest/run_negative_tests.py 的区别:
|
||||
· 那个脚本用 **TPC**(Python 编译器,`TransPyC.py -f`)编译并断言通用错误;
|
||||
· `new` 描述符与 `LifetimeError` 只在 **TransPyV** 侧实现——TPC 的解析器不认识 `new`,
|
||||
也产不出 LifetimeError,所以本脚本统一用 TransPyV.exe 编译,
|
||||
断言"**编译失败且输出含 LifetimeError**"。
|
||||
|
||||
用法:
|
||||
python Test/run_lifetime_negative_tests.py --exe .\\TransPyV\\output\\TransPyV.exe
|
||||
python Test/run_lifetime_negative_tests.py --exe ... --only EscapeMethodTest
|
||||
python Test/run_lifetime_negative_tests.py --exe ... --rebuild-includes
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
# 强制 stdout/stderr 使用 UTF-8,避免 Windows GBK 编码崩溃
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
PROJECT_ROOT = os.path.dirname(TEST_DIR)
|
||||
|
||||
# 期望"编译失败 + 报 LifetimeError"的项目(名字;目录优先 Test/<名>,其次仓库根 <名>)。
|
||||
# 注意:EscapeMethodTest / EscapeXmodTest 内部有多条互斥用例(编译在第一个错误处终止),
|
||||
# 每个项目只保证"至少触发一条 LifetimeError"。
|
||||
NEGATIVE_PROJECTS = ["EscapeTest", "EscapeMethodTest", "EscapeXmodTest"]
|
||||
EXPECT_KEYWORD = "LifetimeError"
|
||||
COMPILE_TIMEOUT = 300
|
||||
|
||||
|
||||
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 resolve(name):
|
||||
"""名字 → (项目目录, 项目文件路径);找不到目录返回 (dir, None)"""
|
||||
d = os.path.join(TEST_DIR, name)
|
||||
if not os.path.isdir(d):
|
||||
d = os.path.join(PROJECT_ROOT, name)
|
||||
return d, find_project_file(d)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
description="生命期/逃逸负向测试(只用 TransPyV 编译,断言 LifetimeError)")
|
||||
ap.add_argument("--exe", required=True,
|
||||
help="TransPyV 可执行文件(如 .\\TransPyV\\output\\TransPyV.exe)")
|
||||
ap.add_argument("--only", default=None,
|
||||
help="只跑指定项目(逗号分隔,名字即目录名)")
|
||||
ap.add_argument("--rebuild-includes", action="store_true",
|
||||
help="每次编译追加 --rebuild-includes(全量重编 includes)")
|
||||
args = ap.parse_args()
|
||||
|
||||
names = [n.strip() for n in args.only.split(",") if n.strip()] if args.only else NEGATIVE_PROJECTS
|
||||
suffix = " --rebuild-includes" if args.rebuild_includes else ""
|
||||
|
||||
passed, failed, skipped = [], [], []
|
||||
print("=" * 70)
|
||||
print("生命期/逃逸负向测试(期望编译失败 + LifetimeError)")
|
||||
print("=" * 70)
|
||||
for name in names:
|
||||
proj_dir, proj_file = resolve(name)
|
||||
if proj_file is None:
|
||||
skipped.append((name, "找不到 project.json / project.vpj"))
|
||||
print(f"[SKIP] {name}: 找不到 project.json / project.vpj")
|
||||
continue
|
||||
cmd = f'"{args.exe}" --project "{proj_file}" --clean{suffix}'
|
||||
try:
|
||||
r = subprocess.run(cmd, shell=True, capture_output=True, text=True,
|
||||
encoding='utf-8', errors='replace',
|
||||
cwd=proj_dir, timeout=COMPILE_TIMEOUT)
|
||||
except subprocess.TimeoutExpired:
|
||||
failed.append((name, "TIMEOUT", ""))
|
||||
print(f"[FAIL] {name}: 编译超时")
|
||||
continue
|
||||
out = (r.stdout or "") + (r.stderr or "")
|
||||
if r.returncode == 0:
|
||||
failed.append((name, "COMPILED_OK(漏检)", out.strip()[-400:]))
|
||||
print(f"[FAIL] {name}: 编译成功(漏检,应报 LifetimeError)")
|
||||
elif EXPECT_KEYWORD.lower() in out.lower():
|
||||
passed.append((name, out.strip()[-200:]))
|
||||
print(f"[PASS] {name}: 已按预期拒绝(含 {EXPECT_KEYWORD})")
|
||||
else:
|
||||
skipped.append((name, f"报错但未见 {EXPECT_KEYWORD}", out.strip()[-300:]))
|
||||
print(f"[SKIP] {name}: 报错但未见 {EXPECT_KEYWORD}")
|
||||
|
||||
print()
|
||||
print("=" * 70)
|
||||
print(f"PASS {len(passed)} FAIL {len(failed)} SKIP {len(skipped)}")
|
||||
for name, tail in skipped:
|
||||
print(f" [SKIP] {name}: {tail if isinstance(tail, str) else ''}")
|
||||
if tail: print(f" {tail}")
|
||||
for name, tag, tail in failed:
|
||||
print(f" [FAIL] {name}: {tag}")
|
||||
if tail: print(f" {tail}")
|
||||
print("=" * 70)
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user