test(lifetime): 新增 NewLifetimeTest/NewLifetimeFreeTest 与 IR 断言,escape 跑法纳入新项目
- NewLifetimeTest(正向,9 用例自检 + 退出码判定):出口释放 / 循环内覆盖释放 / 别名只释放一次 / 嵌套函数登记区间隔离 / early return / void 裸 return / 仅分支里 new 时 free(NULL) / stack slife 不释放。 - NewLifetimeFreeTest(负向):对 slife 对象手动 stdlib.free → LifetimeError。 - SequentialRegressionTest 增 IR_EXPECT:编译成功后断言生成的 IR 出现 llvm.call @free(漏释放=纯泄漏,退出码看不出来);--escape/--only 纳入两个新项目,run_lifetime_negative_tests.py 同步。
This commit is contained in:
1 parent
9eb037ee8f
commit
7451adf523
8 files changed
+301
-11
No files matched your search
@@ -0,0 +1,28 @@
|
||||
from stdint import *
|
||||
import w32.win32console
|
||||
import t, c
|
||||
from t import CInt, CExport
|
||||
import stdlib
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 负向工程(**应当编译失败**,对照工程:NewLifetimeTest)
|
||||
#
|
||||
# §4.3 判据 5:`slife` 对象由编译器独占释放 —— 禁止手动 free。
|
||||
# 理由:出口已经会自动 free 一次,再手动 free 就是 double-free。
|
||||
# 期望报错:LifetimeError。
|
||||
# =============================================================================
|
||||
|
||||
|
||||
novirtual class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
def main() -> CInt | CExport:
|
||||
w32.win32console.SetConsoleCP(65001)
|
||||
w32.win32console.SetConsoleOutputCP(65001)
|
||||
|
||||
p: Point* = new heap slife Point(1, 2)
|
||||
stdlib.free(p) # ← 必须被拒:slife 由编译器在函数出口释放
|
||||
return 0
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json",
|
||||
"name": "NewLifetimeFreeTest",
|
||||
"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": "NewLifetimeFreeTest.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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
from stdint import *
|
||||
import w32.win32console
|
||||
import t, c
|
||||
from t import CInt, CExport
|
||||
import stdio
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# M3 owned slife:`new heap slife …` 的 RAII 自动释放(函数级出口统一 free)
|
||||
# 设计见 Debugger/ownership-and-new.md §4.2(释放点)/ §4.3(硬约束)。
|
||||
#
|
||||
# 语义:`new heap slife` 得到 malloc 的对象,**由编译器独占释放**——在引入该绑定的
|
||||
# 最内层作用域出口(函数的所有 return / 隐式收尾)`free` 它;赋值处若已有旧对象则
|
||||
# 先"覆盖释放"(循环里反复 new 因此不累积);手动 free 被编译期拒绝(见 NewLifetimeFreeTest)。
|
||||
#
|
||||
# 本工程是**正向**工程:编译必须成功、运行必须退出码 0。每个 Case 返回 0 才算通过,
|
||||
# main 累加失败数并在末尾打印结论。
|
||||
#
|
||||
# IR 断言:本工程**刻意不写任何用户级 free**(否则断言会被用户代码蒙混过关)。
|
||||
# SequentialRegressionTest 的 IR_EXPECT 会检查生成的 IR 里出现 `llvm.call @free`
|
||||
# (temp/**.ll 是 LLVM dialect 文本,调用写作 `llvm.call @free(%9) : (!llvm.ptr) -> ()`),
|
||||
# 那只能来自 M3 的出口释放——缺释放(泄漏)是运行期看不出来的,必须直接断言 IR。
|
||||
# =============================================================================
|
||||
|
||||
|
||||
novirtual class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
# (1) 普通出口:字段在释放前可读,函数返回时 free(p)
|
||||
def CaseBasic() -> int:
|
||||
p: Point* = new heap slife Point(1, 2)
|
||||
if p.x != 1: return 1
|
||||
if p.y != 2: return 2
|
||||
return 0
|
||||
|
||||
|
||||
# (2) 循环内反复 new:靠"覆盖释放"不累积。
|
||||
# 第二次赋值前必须先 free 旧值(缺了=泄漏,多了/漏判=double-free 崩)
|
||||
def CaseLoop(n: int) -> int:
|
||||
i: int = 0
|
||||
while i < n:
|
||||
p: Point* = new heap slife Point(i, i + 1)
|
||||
if p.x != i: return 1
|
||||
i += 1
|
||||
return 0
|
||||
|
||||
|
||||
# (3) 别名:q 只是借用 p 的对象,出口必须只释放一次(释放两次会 abort)
|
||||
def CaseAlias() -> int:
|
||||
p: Point* = new heap slife Point(3, 4)
|
||||
q: Point* = p
|
||||
if q.x != 3: return 1
|
||||
return 0
|
||||
|
||||
|
||||
# (4) 真·嵌套函数:内层在编译期与外层共享同一张登记表,必须各自隔离——
|
||||
# 内层出口只能释放内层对象(否则外层对象被提前/重复释放)
|
||||
def CaseNestedDef() -> int:
|
||||
a: Point* = new heap slife Point(5, 6)
|
||||
|
||||
def Inner() -> int:
|
||||
b: Point* = new heap slife Point(7, 8)
|
||||
return b.y
|
||||
|
||||
r: int = Inner()
|
||||
if r != 8: return 1
|
||||
if a.x != 5: return 2
|
||||
return 0
|
||||
|
||||
|
||||
# (5) 多出口:early return 的两条路径都要释放。
|
||||
# 注意返回**错误码**而不是字段值:成功分支若 `return p.x`(=9),main 里的 `fails +=`
|
||||
# 会把它当成失败数累加(现场踩过:19 = 9 + 10,看着像 19 个用例错了,其实全过)。
|
||||
def CaseEarlyReturn(flag: int) -> int:
|
||||
p: Point* = new heap slife Point(9, 10)
|
||||
if flag != 0:
|
||||
if p.x != 9: return 1
|
||||
return 0
|
||||
if p.y != 10: return 2
|
||||
return 0
|
||||
|
||||
|
||||
# (6) void 函数 + 裸 return:裸 return 与隐式收尾两条出口都要释放
|
||||
def CaseVoidBare(flag: int) -> None:
|
||||
p: Point* = new heap slife Point(11, 12)
|
||||
if flag != 0:
|
||||
return
|
||||
stdio.printf("void-slife x=%d\n", p.x)
|
||||
|
||||
|
||||
# (7) 只有分支里才 new:flag=0 时该语句运行期不执行,出口必须 free(NULL)。
|
||||
# 缺"入口块置空"就会 free 栈垃圾 → 堆损坏/abort
|
||||
def CaseBranchOnly(flag: int) -> int:
|
||||
p: Point* = None
|
||||
if flag != 0:
|
||||
p = new heap slife Point(13, 14)
|
||||
if p.x != 13: return 1
|
||||
return 0
|
||||
|
||||
|
||||
# (8) stack slife:帧内 alloca,随帧消失,**不得**释放(对栈地址 free 会 abort)
|
||||
def CaseStack() -> int:
|
||||
p: Point* = new stack slife Point(15, 16)
|
||||
if p.x != 15: return 1
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> CInt | CExport:
|
||||
w32.win32console.SetConsoleCP(65001)
|
||||
w32.win32console.SetConsoleOutputCP(65001)
|
||||
|
||||
fails: int = 0
|
||||
fails += CaseBasic()
|
||||
fails += CaseLoop(200)
|
||||
fails += CaseAlias()
|
||||
fails += CaseNestedDef()
|
||||
fails += CaseEarlyReturn(1)
|
||||
fails += CaseEarlyReturn(0)
|
||||
fails += CaseBranchOnly(0)
|
||||
fails += CaseBranchOnly(1)
|
||||
fails += CaseStack()
|
||||
CaseVoidBare(1)
|
||||
CaseVoidBare(0)
|
||||
|
||||
if fails != 0:
|
||||
stdio.printf("NewLifetimeTest FAILED, %d case(s) wrong\n", fails)
|
||||
return 1
|
||||
stdio.printf("NewLifetimeTest OK: owned-slife RAII cases passed\n")
|
||||
return 0
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json",
|
||||
"name": "NewLifetimeTest",
|
||||
"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": "NewLifetimeTest.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
|
||||
}
|
||||
}
|
||||
@@ -121,8 +121,8 @@
|
||||
|
||||
## 一·附、生命期 / 逃逸(escape)相关测试
|
||||
|
||||
验证 M1–M2.4 的生命期检查(栈地址逃逸、借用传播、函数逃逸摘要)与 `new` 描述符分配。
|
||||
因历史原因前 4 个位于**仓库根**(不在 `Test/` 下),`NewTest` 位于 `Test/`:
|
||||
验证 M1–M3 的生命期检查(栈地址逃逸、借用传播、函数逃逸摘要)、`new` 描述符分配,
|
||||
以及 `new heap slife` 的 RAII 自动释放(M3)。因历史原因前 4 个位于**仓库根**(不在 `Test/` 下):
|
||||
|
||||
| 项目 | 位置 | 期望结果 |
|
||||
|---|---|---|
|
||||
@@ -131,6 +131,8 @@
|
||||
| EscapeMethodTest | 仓库根 `EscapeMethodTest/` | **编译失败**:方法 / `__init__` / 嵌套函数交出栈地址 → `LifetimeError`(三用例互斥,一次开一条) |
|
||||
| EscapeXmodTest | 仓库根 `EscapeXmodTest/` | **编译失败**:跨模块摘要(`.vpi` 落盘 `# esc=`)路径 → `LifetimeError` |
|
||||
| NewTest | [Test/NewTest/](NewTest/App/main.vp) | 编译通过并输出 4 行:`stack`(显式 slife) / `heap` / `stack默认slife`(验证**缺省规则**:省略生命期时 stack→slife、heap/memgr→hlife)/ `memgr` |
|
||||
| NewLifetimeTest | [Test/NewLifetimeTest/](NewLifetimeTest/App/main.vp) | 编译通过、退出码 0(**M3 owned slife RAII**:出口释放 / 循环内覆盖释放 / 别名只释放一次 / 嵌套函数登记隔离 / early return / void 裸 return / 仅分支里 new 时 `free(NULL)` / stack slife 不释放)。**另含 IR 断言**:IR 中必须出现 `llvm.call @free` |
|
||||
| NewLifetimeFreeTest | [Test/NewLifetimeFreeTest/](NewLifetimeFreeTest/App/main.vp) | **编译失败**:对 `slife` 对象手动 `stdlib.free(p)` → `LifetimeError`(§4.3 判据5,编译器独占释放) |
|
||||
|
||||
单独运行(`regression_test.py` 与 `SequentialRegressionTest.py` 都支持):
|
||||
|
||||
@@ -138,10 +140,16 @@
|
||||
python Test/regression_test.py --escape # 只跑上表全部项目
|
||||
python Test/regression_test.py --only NewTest # 只跑指定项目(逗号分隔可多个)
|
||||
python Test/SequentialRegressionTest.py --escape --exe <TransPyV.exe> --rebuild-includes
|
||||
python Test/SequentialRegressionTest.py --only NewLifetimeTest --exe <TransPyV.exe>
|
||||
```
|
||||
|
||||
> `--escape` / `--only` 会在 `Test/<名>` 与**仓库根** `<名>` 两处查找 `project.json` / `project.vpj`,所以根级 escape 项目也能直接跑;`--escape` 会忽略 README 的难度顺序。
|
||||
|
||||
**IR 断言**:`SequentialRegressionTest.py` 的 `IR_EXPECT`(项目名 → 生成 IR 中必须出现的子串)在编译成功后、运行前,把 `{build_dir}` 下所有 `.ll` 拼起来做子串检查;找不到 `.ll` 视为断言失败(fail-fast,不静默跳过)。
|
||||
> `{build_dir}/temp/**.ll` 里是 **LLVM dialect 文本**(mlir-opt 的输入),调用形如 `llvm.call @free(%9) : (!llvm.ptr) -> ()`(不是经典 IR 的 `call void @free`),所以断言串用 `llvm.call @free`(`llvm.func @free` 是声明行,不会被匹配)。
|
||||
> 之所以加 IR 断言:M3 的"出口释放"只靠"运行没崩"证明不了——**漏了释放是纯泄漏,进程照样退出码 0**;必须直接断言释放调用出现过。
|
||||
> 因此纳入该断言的项目**不得自己写 free**(否则用户代码的 `free` 会蒙混过关):`NewLifetimeTest` 正是刻意不含任何用户级 `free`。
|
||||
|
||||
**负向断言**("应报 `LifetimeError`")需要 TransPyV 编译——`NegativeTest/run_negative_tests.py` 用的是 TPC,而 `new` 描述符与 `LifetimeError` 只在 TPV 侧实现,故单独提供:
|
||||
|
||||
```bash
|
||||
@@ -149,7 +157,7 @@ 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。
|
||||
> 默认跑 `EscapeTest` / `EscapeMethodTest` / `EscapeXmodTest` / `NewLifetimeFreeTest`:断言**编译失败且输出含 `LifetimeError`**;编译成功 = 漏检(FAIL),报错但方向不符 = SKIP。
|
||||
|
||||
负向项目在 `SequentialRegressionTest.py` 里是**反向判定**(`NEGATIVE_PROJECTS`):编译失败且输出含 `LifetimeError` 才算通过,且**不再进入运行阶段**;编译成功 = 漏检,直接终止。`regression_test.py --escape` 会**剔除**这些项目(该脚本要求"编译成功 + 能跑"),负向断言请用 `SequentialRegressionTest.py --escape` 或上面的 `run_lifetime_negative_tests.py`。
|
||||
|
||||
|
||||
@@ -36,9 +36,10 @@ 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"]
|
||||
# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII):
|
||||
# Test/ 下:NewTest、NewLifetimeTest(正向)、NewLifetimeFreeTest(负向);
|
||||
# 仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest
|
||||
ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeFreeTest"]
|
||||
|
||||
# 负向项目:**期望编译失败**且输出含指定关键字(反向判定,且不运行可执行文件)。
|
||||
# 这些项目里的用例故意交出本帧栈地址(或跨模块摘要路径),必须被 M1–M2.3 的
|
||||
@@ -49,6 +50,20 @@ NEGATIVE_PROJECTS = {
|
||||
"EscapeTest": "LifetimeError",
|
||||
"EscapeMethodTest": "LifetimeError",
|
||||
"EscapeXmodTest": "LifetimeError",
|
||||
# M3 §4.3 判据5:对 slife 对象手动 free → LifetimeError
|
||||
"NewLifetimeFreeTest": "LifetimeError",
|
||||
}
|
||||
|
||||
# IR 断言:项目名 → 生成 IR 中**必须出现**的子串。
|
||||
# 动机:M3 owned slife 的"出口释放"只靠"运行不崩"是证明不了的(漏了释放 = 纯泄漏,
|
||||
# 进程照样退出码 0),必须直接断言生成的 IR 里出现了释放调用。
|
||||
# 形式注意:`{build_dir}/temp/**.ll` 里是 **LLVM dialect 文本**(mlir-opt 的输入),
|
||||
# 调用长这样 `llvm.call @free(%9) : (!llvm.ptr) -> ()`,不是经典 IR 的 `call void @free`;
|
||||
# 用 `llvm.call @free` 才能匹配到调用(`llvm.func @free` 是声明那一行,不会被匹配)。
|
||||
# 约定:纳入 IR 断言的项目**不得**自己写 free,否则用户代码的 free 会把断言蒙混过关
|
||||
# (NewLifetimeTest 正是因此不含任何用户级 free)。
|
||||
IR_EXPECT = {
|
||||
"NewLifetimeTest": ["llvm.call @free"],
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +193,45 @@ def find_output_dirs(proj_dir, proj_config):
|
||||
return uniq
|
||||
|
||||
|
||||
def find_ll_files(proj_dir, proj_config):
|
||||
"""收集项目构建目录下的所有 .ll(TPV 布局:{build_dir}/temp/<xx>/<sha1>.ll)"""
|
||||
bd = proj_config.get('build_dir') or '.tpv_build'
|
||||
root = os.path.abspath(os.path.join(proj_dir, bd))
|
||||
if not os.path.isdir(root):
|
||||
return []
|
||||
out = []
|
||||
for dp, _dn, fns in os.walk(root):
|
||||
for fn in fns:
|
||||
if fn.endswith('.ll'):
|
||||
out.append(os.path.join(dp, fn))
|
||||
return out
|
||||
|
||||
|
||||
def check_ir_expect(proj_dir, proj_config, name):
|
||||
"""IR 断言:在生成的 .ll 里检查 IR_EXPECT[name] 的每个子串都出现。
|
||||
|
||||
返回 (ok, 说明)。未配置 IR_EXPECT 的项目恒 (True, "")。
|
||||
找不到任何 .ll 视为**失败**(fail-fast:静默跳过等于没断言)。
|
||||
"""
|
||||
wants = IR_EXPECT.get(name)
|
||||
if not wants:
|
||||
return True, ""
|
||||
lls = find_ll_files(proj_dir, proj_config)
|
||||
if not lls:
|
||||
return False, f"IR 断言失败:{name} 的构建目录下找不到任何 .ll(检查 build_dir 配置)"
|
||||
text = ""
|
||||
for p in lls:
|
||||
try:
|
||||
with open(p, 'r', encoding='utf-8', errors='replace') as f:
|
||||
text += f.read()
|
||||
except Exception as e:
|
||||
return False, f"IR 断言失败:读取 {p} 出错: {e}"
|
||||
missing = [w for w in wants if w not in text]
|
||||
if missing:
|
||||
return False, f"IR 断言失败:{name} 生成的 .ll 中未见 {missing}(共 {len(lls)} 个 .ll)"
|
||||
return True, f"IR 断言通过({len(wants)} 项 / {len(lls)} 个 .ll)"
|
||||
|
||||
|
||||
def run_executable(exe_path):
|
||||
"""运行单个可执行文件,返回 (returncode, stdout, stderr, timed_out)
|
||||
|
||||
@@ -326,6 +380,15 @@ def main():
|
||||
print(f"\n在 {name} 读取 project.json 失败,立即终止(请修正该文件后重跑)。")
|
||||
return 1
|
||||
|
||||
# ---------- IR 断言(可选,见 IR_EXPECT) ----------
|
||||
ir_ok, ir_msg = check_ir_expect(proj_dir, proj_config, name)
|
||||
if not ir_ok:
|
||||
print(f" [IR] {ir_msg}")
|
||||
print(f"\n在 {name} IR 断言失败,立即终止。")
|
||||
return 1
|
||||
if ir_msg:
|
||||
print(f" [IR] {ir_msg}")
|
||||
|
||||
output_dirs = find_output_dirs(proj_dir, proj_config)
|
||||
exes = []
|
||||
for od in output_dirs:
|
||||
|
||||
+5
-4
@@ -85,14 +85,15 @@ def find_test_projects():
|
||||
# ============================================================
|
||||
# 单独运行支持:--escape / --only
|
||||
# ============================================================
|
||||
# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符):
|
||||
# Test/ 下:NewTest;仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest
|
||||
ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest"]
|
||||
# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符 + M3 owned slife RAII):
|
||||
# Test/ 下:NewTest、NewLifetimeTest(正向)、NewLifetimeFreeTest(负向);
|
||||
# 仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest
|
||||
ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest", "NewLifetimeTest", "NewLifetimeFreeTest"]
|
||||
|
||||
# 负向项目(**期望编译失败**,见 SequentialRegressionTest.NEGATIVE_PROJECTS):
|
||||
# 本脚本按"编译必须成功 + 能跑"判定,纳入会误报,故 --escape 时剔除;
|
||||
# 负向断言请用 SequentialRegressionTest(反向判定)或 run_lifetime_negative_tests.py。
|
||||
NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest"}
|
||||
NEGATIVE_PROJECT_NAMES = {"EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest"}
|
||||
|
||||
|
||||
def find_project_file(proj_dir):
|
||||
|
||||
@@ -30,7 +30,8 @@ PROJECT_ROOT = os.path.dirname(TEST_DIR)
|
||||
# 期望"编译失败 + 报 LifetimeError"的项目(名字;目录优先 Test/<名>,其次仓库根 <名>)。
|
||||
# 注意:EscapeMethodTest / EscapeXmodTest 内部有多条互斥用例(编译在第一个错误处终止),
|
||||
# 每个项目只保证"至少触发一条 LifetimeError"。
|
||||
NEGATIVE_PROJECTS = ["EscapeTest", "EscapeMethodTest", "EscapeXmodTest"]
|
||||
# NewLifetimeFreeTest:M3 §4.3 判据5(对 slife 对象手动 free)。
|
||||
NEGATIVE_PROJECTS = ["EscapeTest", "EscapeMethodTest", "EscapeXmodTest", "NewLifetimeFreeTest"]
|
||||
EXPECT_KEYWORD = "LifetimeError"
|
||||
COMPILE_TIMEOUT = 300
|
||||
|
||||
|
||||
Reference in new issue
Block a user