[CI] Fail lint when a registered file's TestCase classes never run (#32735)

This commit is contained in:
Liangsheng Yin
2026-07-29 00:45:01 -07:00
committed by GitHub
parent f05c92fb6d
commit cce5873513
2 changed files with 68 additions and 3 deletions
+52
View File
@@ -21,6 +21,7 @@ Reuses ut_parse_one_file() from ci_register.py (AST-based parsing)
to match the same logic used by run_suite.py's collect_tests().
"""
import ast
import glob
import importlib.util
import os
@@ -39,6 +40,37 @@ _MODERN_SHAPE = re.compile(r"^(.+)-test-(.+)$")
_LEGACY_CUDA_PREFIXES = ("nightly", "stress", "weekly")
def _defines_testcase(tree: ast.AST) -> bool:
"""True if the file defines unittest classes, statically or via type()."""
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
if any("TestCase" in ast.unparse(b) for b in node.bases):
return True
elif isinstance(node, ast.Call):
if (
isinstance(node.func, ast.Name)
and node.func.id == "type"
and len(node.args) >= 2
and isinstance(node.args[1], ast.Tuple)
and any("TestCase" in ast.unparse(e) for e in node.args[1].elts)
):
return True
return False
def _main_runs_tests(tree: ast.Module) -> bool:
for stmt in tree.body:
if not (
isinstance(stmt, ast.If)
and ast.unparse(stmt.test).replace("'", '"') == '__name__ == "__main__"'
):
continue
body = ast.unparse(ast.Module(body=stmt.body, type_ignores=[]))
if "unittest.main" in body or "pytest.main" in body:
return True
return False
def main() -> int:
# Import ci_register directly to avoid pulling in all of sglang
spec = importlib.util.spec_from_file_location(
@@ -61,6 +93,7 @@ def main() -> int:
missing = []
legacy_shape = [] # (file, suite, stage, runner_config) -- has a -test- split
non_dispatchable = [] # (file, suite) -- legacy CUDA suite no workflow invokes
dead_tests = [] # (file) -- TestCase classes that `python3 file.py` never runs
for f in files:
try:
registries, _has_main_entry = ci_register.ut_parse_one_file(f)
@@ -70,6 +103,12 @@ def main() -> int:
if len(registries) == 0:
missing.append(f)
continue
# TestCase classes are dead unless __main__ runs them (CI does
# `python3 file.py`); the ERROR text below explains the fix.
with open(f, "r", encoding="utf-8") as fh:
tree = ast.parse(fh.read(), filename=f)
if _defines_testcase(tree) and not _main_runs_tests(tree):
dead_tests.append(f)
for r in registries:
# Pure legacy form on a CUDA registry: suite set, stage/runner unset.
if not (
@@ -127,6 +166,19 @@ def main() -> int:
)
print()
exit_code = 1
if dead_tests:
print(
"ERROR: Test file(s) define TestCase classes that CI never runs: "
"the registered file is executed as `python3 file.py`, but its "
'`if __name__ == "__main__"` block does not call unittest.main() '
"or pytest.main(), so the classes are silently skipped while the "
"file reports success. Make __main__ run the tests (put any CLI "
"entry point behind an explicit flag):\n"
)
for f in dead_tests:
print(f" {f}")
print()
exit_code = 1
return exit_code
@@ -1,7 +1,7 @@
"""Large-scale benchmark + fuzz correctness tests for UnifiedRadixCache.
Usage (standalone):
bench: python3 test/registered/unit/mem_cache/test_unified_radix_cache_bench.py --num-seqs 5000 --verify --components mamba legacy-mamba swa legacy-swa
bench: python3 test/registered/unit/mem_cache/test_unified_radix_cache_bench.py --bench --num-seqs 5000 --verify --components mamba legacy-mamba swa legacy-swa
CI Test: python -m pytest test/registered/unit/mem_cache/test_unified_radix_cache_bench.py -v -s
"""
@@ -10,6 +10,7 @@ import gc
import logging
import random
import statistics
import sys
import time
import unittest
from array import array
@@ -239,7 +240,7 @@ def create_bench_cache(
_rid = [0]
def make_req():
from sglang.srt.managers.schedule_batch import Req
from sglang.srt.managers.schedule_batch import Req, ReqKvInfo
from sglang.srt.sampling.sampling_params import SamplingParams
req = Req(
@@ -250,6 +251,8 @@ def create_bench_cache(
)
_rid[0] += 1
req_to_token_pool.alloc([req])
# fabricated reqs bypass alloc_for_extend, the normal creator of req.kv
req.kv = ReqKvInfo(kv_allocated_len=0, swa_evicted_seqlen=0)
return req
return tree, allocator, req_to_token_pool, make_req
@@ -821,7 +824,8 @@ _TREE_CONFIGS = {
"legacy-swa": ((ComponentType.FULL, ComponentType.SWA), SWARadixCache),
}
if __name__ == "__main__":
def _run_bench_cli():
parser = argparse.ArgumentParser(description="UnifiedRadixCache benchmark")
parser.add_argument("--num-seqs", type=int, default=5000)
parser.add_argument("--chunk-len", type=int, default=256)
@@ -857,3 +861,12 @@ if __name__ == "__main__":
tree_cls=tree_cls,
page_size=args.page_size,
)
if __name__ == "__main__":
# CI runs `python3 file.py`; it must execute the TestBench_* classes
if "--bench" in sys.argv:
sys.argv.remove("--bench")
_run_bench_cli()
else:
unittest.main()