已合并
fix: pre-commit code #199
RuiWang_创建于 10 天前
fix: pre-commit code #199
已合并
RuiWang_创建于 10 天前
254 个文件变更+9968-7606
@@ -37,10 +37,10 @@ body:
37 label: Related log / screenshot / 日志 / 截图 (Mandatory / 必填)37 label: Related log / screenshot / 日志 / 截图 (Mandatory / 必填)
38 description: 提供尽可能多的日志/结果信息38 description: 提供尽可能多的日志/结果信息
39 validations:39 validations:
40- required: true 40+ required: true
41 - type: textarea41 - type: textarea
42 attributes:42 attributes:
43 label: Special notes for this issue/备注 (Optional / 选填)43 label: Special notes for this issue/备注 (Optional / 选填)
44 description: 补充下其他您认为需要提供的信息44 description: 补充下其他您认为需要提供的信息
45 validations:45 validations:
46- required: false46+ required: false
@@ -24,4 +24,4 @@ body:
24 label: Existing Issues(存在的问题)24 label: Existing Issues(存在的问题)
25 description: 请描述下存在的问题25 description: 请描述下存在的问题
26 validations:26 validations:
27- required: false27+ required: false
@@ -9,7 +9,7 @@ body:
9 Thanks for sending an requirement! Please fill in the following template to help quickly solve your problem.9 Thanks for sending an requirement! Please fill in the following template to help quickly solve your problem.
10 - type: textarea10 - type: textarea
11 attributes:11 attributes:
12- label: Backgroud(背景信息)12+ label: Background(背景信息)
13 description: 请描述下新需求的功能是什么,解决了什么问题13 description: 请描述下新需求的功能是什么,解决了什么问题
14 validations:14 validations:
15 required: true15 required: true
@@ -30,4 +30,4 @@ body:
30 label: Design(设计方案)30 label: Design(设计方案)
31 description: 请描述/解释设计的总体思路(可以使用伪代码)31 description: 请描述/解释设计的总体思路(可以使用伪代码)
32 validations:32 validations:
33- required: false33+ required: false
@@ -12,4 +12,4 @@ body:
12 label: 问题描述12 label: 问题描述
13 description: 请描述下您的问题13 description: 请描述下您的问题
14 validations:14 validations:
15- required: true15+ required: true
@@ -1,15 +1,15 @@
1<!-- 感谢您的合入申请! -->1<!-- 感谢您的合入申请! -->
2 2 
3-### 当前PR是否有AI参与: 3+### 当前PR是否有AI参与:
4-[x] 否 4+[x] 否
5-[ ] 是 5+[ ] 是
6-__1. AI Agent 平台: 6+__1. AI Agent 平台:
7-__2. AI 模型: 7+__2. AI 模型:
8-__3. Prompt上下文 : 8+__3. Prompt上下文 :
9 9 
10-### PR功能描述 / 为什么需要这个合入**: 10+### PR功能描述 / 为什么需要这个合入**:
11- 11+ 
12-### 该PR关联的issue 12+### 该PR关联的issue
13*(格式为fixes #<issue号>, 或者resolves #<issue号>)*: fixes #13*(格式为fixes #<issue号>, 或者resolves #<issue号>)*: fixes #
14 14 
15### 希望检视人员了解:15### 希望检视人员了解:
@@ -100,7 +100,7 @@ stages:
100 pre:100 pre:
101 - type: auto101 - type: auto
102 fail-fast: false102 fail-fast: false
103- 103+ 
104 104 
105post:105post:
106 jobs:106 jobs:
@@ -1,4 +1,4 @@
1on:1on:
2 pr_comment:2 pr_comment:
3 types: [ created ]3 types: [ created ]
4- keyword: '^(?:\/)?compile*'4+ keyword: '^(?:\/)?compile*'
@@ -36,7 +36,7 @@ repos:
36 - id: codespell36 - id: codespell
37 args: [37 args: [
38 "-L",38 "-L",
39- "CANN,cann,NNAL,nnal,ASCEND,ascend,EnQue,CopyIn,ArchType,AND,ND,tbe,copyin,alog",39+ "CANN,cann,NNAL,nnal,ASCEND,ascend,EnQue,CopyIn,ArchType,AND,ND,tbe,copyin,alog,dOut",
40 "--skip",40 "--skip",
41 "*.py,*.cpp,*.hpp,*.c,*.h",41 "*.py,*.cpp,*.hpp,*.c,*.h",
42 ]42 ]
@@ -51,4 +51,4 @@ repos:
51 pass_filenames: true51 pass_filenames: true
52 types: [file]52 types: [file]
53 stages: [commit]53 stages: [commit]
54- verbose: true54+ verbose: true
@@ -108,4 +108,3 @@ msopprof --aic-metrics=pipetimeline/instrtimeline python3 -m ttk kernel -i cases
108 108 
109- `task duration` 一般大于 `aiv time`,因为 `task duration` 包含硬件调度时间,而 `aiv time` 是纯核执行时间。109- `task duration` 一般大于 `aiv time`,因为 `task duration` 包含硬件调度时间,而 `aiv time` 是纯核执行时间。
110- 从数据来源看,`task duration` 由 stars 采集,`aiv time` 由 ffts 采集。110- 从数据来源看,`task duration` 由 stars 采集,`aiv time` 由 ffts 采集。
111- 
@@ -17,11 +17,11 @@ from unittest.mock import MagicMock
17def _make_args(**kwargs):17def _make_args(**kwargs):
18 """构造 args mock,只填 args_to_switches 实际读取的字段。"""18 """构造 args mock,只填 args_to_switches 实际读取的字段。"""
19 args = MagicMock()19 args = MagicMock()
20- args.provider = kwargs.get('provider', None)20+ args.provider = kwargs.get("provider", None)
21- args.config = kwargs.get('config', None)21+ args.config = kwargs.get("config", None)
22- args.input = kwargs.get('input', "x.csv")22+ args.input = kwargs.get("input", "x.csv")
23- args.output = kwargs.get('output', None)23+ args.output = kwargs.get("output", None)
24- args.run = kwargs.get('run', 1)24+ args.run = kwargs.get("run", 1)
25 return args25 return args
26 26 
27 27 
@@ -38,9 +38,11 @@ remote:
38 monkeypatch.chdir(tmp_path)38 monkeypatch.chdir(tmp_path)
39 39 
40 import ttk.config.loader as loader40 import ttk.config.loader as loader
41+ 
41 loader._config = None42 loader._config = None
42 43 
43 from ttk.config.loader import get_remote_config, load_config44 from ttk.config.loader import get_remote_config, load_config
45+ 
44 load_config()46 load_config()
45 47 
46 config = get_remote_config()48 config = get_remote_config()
@@ -61,9 +63,11 @@ remote:
61 monkeypatch.chdir(tmp_path)63 monkeypatch.chdir(tmp_path)
62 64 
63 import ttk.config.loader as loader65 import ttk.config.loader as loader
66+ 
64 loader._config = None67 loader._config = None
65 68 
66 from ttk.config.loader import get_remote_config, load_config69 from ttk.config.loader import get_remote_config, load_config
70+ 
67 load_config()71 load_config()
68 72 
69 assert get_remote_config() is None73 assert get_remote_config() is None
@@ -79,11 +83,13 @@ remote:
79 monkeypatch.chdir(tmp_path)83 monkeypatch.chdir(tmp_path)
80 84 
81 import ttk.config.loader as loader85 import ttk.config.loader as loader
86+ 
82 loader._config = None87 loader._config = None
83 88 
84 args = _make_args(provider="torch")89 args = _make_args(provider="torch")
85 90 
86 from ttk.cli.bridge import args_to_switches91 from ttk.cli.bridge import args_to_switches
92+ 
87 sw = args_to_switches(args)93 sw = args_to_switches(args)
88 94 
89 assert sw.provider_filter == "torch"95 assert sw.provider_filter == "torch"
@@ -58,19 +58,41 @@ def _prepare_switches(file_format="bin"):
58 58 
59# -- 参数暴露 ----------------------------------------------------------------59# -- 参数暴露 ----------------------------------------------------------------
60 60 
61+ 
61def test_manual_data_dirs_exposed_on_three_commands():62def test_manual_data_dirs_exposed_on_three_commands():
62 """e2e/aclnn/kernel 三个子命令都暴露 --manual-data-dirs 和 --no-prof。"""63 """e2e/aclnn/kernel 三个子命令都暴露 --manual-data-dirs 和 --no-prof。"""
63 parser = _parser()64 parser = _parser()
64- e2e = parser.parse_args([65+ e2e = parser.parse_args(
65- "e2e", "-i", "case.csv", "--no-prof", "--dump", "in,golden",66+ [
66- "--manual-data-dirs", "prepared",67+ "e2e",
67- ])68+ "-i",
68- aclnn = parser.parse_args([69+ "case.csv",
69- "aclnn", "-i", "case.csv", "--manual-data-dirs", "first", "second",70+ "--no-prof",
70- ])71+ "--dump",
71- kernel = parser.parse_args([72+ "in,golden",
72- "kernel", "-i", "case.csv", "--manual-data-dirs", "prepared",73+ "--manual-data-dirs",
73- ])74+ "prepared",
75+ ]
76+ )
77+ aclnn = parser.parse_args(
78+ [
79+ "aclnn",
80+ "-i",
81+ "case.csv",
82+ "--manual-data-dirs",
83+ "first",
84+ "second",
85+ ]
86+ )
87+ kernel = parser.parse_args(
88+ [
89+ "kernel",
90+ "-i",
91+ "case.csv",
92+ "--manual-data-dirs",
93+ "prepared",
94+ ]
95+ )
74 96 
75 assert e2e.no_prof is True97 assert e2e.no_prof is True
76 assert e2e.manual_data_dirs == ["prepared"]98 assert e2e.manual_data_dirs == ["prepared"]
@@ -80,6 +102,7 @@ def test_manual_data_dirs_exposed_on_three_commands():
80 102 
81# -- prepare 模式 ------------------------------------------------------------103# -- prepare 模式 ------------------------------------------------------------
82 104 
105+ 
83def test_prepare_defaults_to_plugin_manual_data_dir(tmp_path):106def test_prepare_defaults_to_plugin_manual_data_dir(tmp_path):
84 """prepare 默认输出目录 = <plugin_path>/manual_data。"""107 """prepare 默认输出目录 = <plugin_path>/manual_data。"""
85 switches = _prepare_switches()108 switches = _prepare_switches()
@@ -168,6 +191,7 @@ def test_e2e_prepare_accepts_input_only_dump():
168 191 
169# -- replay 模式 -------------------------------------------------------------192# -- replay 模式 -------------------------------------------------------------
170 193 
194+ 
171def test_replay_preserves_directory_order(tmp_path):195def test_replay_preserves_directory_order(tmp_path):
172 """replay 模式按 --manual-data-dirs 指定顺序搜索(顺序敏感)。"""196 """replay 模式按 --manual-data-dirs 指定顺序搜索(顺序敏感)。"""
173 switches = SWITCHES()197 switches = SWITCHES()
@@ -176,9 +200,7 @@ def test_replay_preserves_directory_order(tmp_path):
176 configure_manual_data(switches, _args(manual_data_dirs=directories), "aclnn")200 configure_manual_data(switches, _args(manual_data_dirs=directories), "aclnn")
177 201 
178 assert switches.manual_data_mode == "replay"202 assert switches.manual_data_mode == "replay"
179- assert switches.manual_data_dirs == tuple(203+ assert switches.manual_data_dirs == tuple(str((tmp_path / name).resolve()) for name in ("one", "two"))
180- str((tmp_path / name).resolve()) for name in ("one", "two")
181- )
182 204 
183 205 
184def test_e2e_replay_rejects_cpu_backend(tmp_path):206def test_e2e_replay_rejects_cpu_backend(tmp_path):
@@ -192,6 +214,7 @@ def test_e2e_replay_rejects_cpu_backend(tmp_path):
192 214 
193# -- kernel 特有语义 ---------------------------------------------------------215# -- kernel 特有语义 ---------------------------------------------------------
194 216 
217+ 
195def test_kernel_no_prof_without_dump_pair_keeps_legacy():218def test_kernel_no_prof_without_dump_pair_keeps_legacy():
196 """kernel --no-prof 但无 --dump in,golden → 不进入 prepare,保持旧 dry-run 语义。"""219 """kernel --no-prof 但无 --dump in,golden → 不进入 prepare,保持旧 dry-run 语义。"""
197 switches = SWITCHES()220 switches = SWITCHES()
@@ -239,6 +262,7 @@ def test_kernel_manual_data_rejects_compile_only(tmp_path):
239 262 
240# -- pickle 往返 -------------------------------------------------------------263# -- pickle 往返 -------------------------------------------------------------
241 264 
265+ 
242def test_manual_data_fields_survive_worker_pickle(tmp_path):266def test_manual_data_fields_survive_worker_pickle(tmp_path):
243 """manual_data_mode / manual_data_dirs 经 pickle 往返不丢(worker 传递保障)。"""267 """manual_data_mode / manual_data_dirs 经 pickle 往返不丢(worker 传递保障)。"""
244 switches = SWITCHES()268 switches = SWITCHES()
@@ -253,6 +277,7 @@ def test_manual_data_fields_survive_worker_pickle(tmp_path):
253 277 
254# -- --clear-ub / --clear-l1 数值解析 ----------------------------------------278# -- --clear-ub / --clear-l1 数值解析 ----------------------------------------
255 279 
280+ 
256@pytest.mark.parametrize(281@pytest.mark.parametrize(
257 "value, expected_type, expected",282 "value, expected_type, expected",
258 [283 [
@@ -51,6 +51,7 @@ def test_xpu_perf_mapped_to_switches():
51 51 
52# -- _xpu_inputs 数据选择 ----------------------------------------------------52# -- _xpu_inputs 数据选择 ----------------------------------------------------
53 53 
54+ 
54def test_xpu_inputs_prefers_original_shape_arrays():55def test_xpu_inputs_prefers_original_shape_arrays():
55 """XPU 优先喂 logical ori-shape 数组,而非 NPU run-format 数组(如 NC1HWC0)。"""56 """XPU 优先喂 logical ori-shape 数组,而非 NPU run-format 数组(如 NC1HWC0)。"""
56 from ttk.core_modules.npu.op.profiling import _xpu_inputs57 from ttk.core_modules.npu.op.profiling import _xpu_inputs
@@ -69,6 +70,7 @@ def test_xpu_inputs_falls_back_to_input_arrays():
69 70 
70# -- _xpu_mode 位运算 --------------------------------------------------------71# -- _xpu_mode 位运算 --------------------------------------------------------
71 72 
73+ 
72def test_xpu_mode_bitwise_or():74def test_xpu_mode_bitwise_or():
73 """_xpu_mode = xpu_perf(PERF) | need_data(DATA);全 False 返回 0。"""75 """_xpu_mode = xpu_perf(PERF) | need_data(DATA);全 False 返回 0。"""
74 from ttk.core_modules.npu.op import profiling as prof76 from ttk.core_modules.npu.op import profiling as prof
@@ -87,6 +89,7 @@ def test_xpu_mode_bitwise_or():
87 89 
88# -- validate_xpu_perf_precondition 前置校验 ---------------------------------90# -- validate_xpu_perf_precondition 前置校验 ---------------------------------
89 91 
92+ 
90def test_validate_xpu_perf_precondition_three_branches(monkeypatch):93def test_validate_xpu_perf_precondition_three_branches(monkeypatch):
91 """前置校验三分支:无远端+开启→抛错;有远端+开启→不抛;未开启→跳过。"""94 """前置校验三分支:无远端+开启→抛错;有远端+开启→不抛;未开启→跳过。"""
92 from ttk.cli import common as common_mod95 from ttk.cli import common as common_mod
@@ -111,6 +114,7 @@ def test_validate_xpu_perf_precondition_three_branches(monkeypatch):
111 114 
112# -- _extract_third_party fail-closed ---------------------------------------115# -- _extract_third_party fail-closed ---------------------------------------
113 116 
117+ 
114def test_extract_third_party_fail_closed():118def test_extract_third_party_fail_closed():
115 """_extract_third_party fail-closed:非 PASS / 无 outputs / 无 priority → None。"""119 """_extract_third_party fail-closed:非 PASS / 无 outputs / 无 priority → None。"""
116 from ttk.core_modules.npu.op import profiling as prof120 from ttk.core_modules.npu.op import profiling as prof
@@ -129,6 +133,7 @@ def test_extract_third_party_fail_closed():
129 133 
130# -- profile_process XPU gate 回归看护 ---------------------------------------134# -- profile_process XPU gate 回归看护 ---------------------------------------
131 135 
136+ 
132# _fake_do_xpu 被调用时抛此异常,短路 profile_process 证明 gate 已开。137# _fake_do_xpu 被调用时抛此异常,短路 profile_process 证明 gate 已开。
133class _XpuCalled(Exception):138class _XpuCalled(Exception):
134 pass139 pass
@@ -167,9 +172,14 @@ def test_profile_process_gate_open_and_shut(monkeypatch):
167 172 
168 # 隔离 gate 上游:所有 parse/gen 步骤为 no-op173 # 隔离 gate 上游:所有 parse/gen 步骤为 no-op
169 monkeypatch.setattr(prof, "get_global_storage", lambda: sw)174 monkeypatch.setattr(prof, "get_global_storage", lambda: sw)
170- monkeypatch.setattr(prof, "get_process_context", lambda: SimpleNamespace(175+ monkeypatch.setattr(
171- change_name=lambda _name: None, notify_status=lambda _s: None,176+ prof,
172- ))177+ "get_process_context",
178+ lambda: SimpleNamespace(
179+ change_name=lambda _name: None,
180+ notify_status=lambda _s: None,
181+ ),
182+ )
173 monkeypatch.setattr(prof, "__parse_manual_params", lambda _ctx: None)183 monkeypatch.setattr(prof, "__parse_manual_params", lambda _ctx: None)
174 monkeypatch.setattr(prof, "__parse_dynamic_tiling_data", lambda _ctx: None)184 monkeypatch.setattr(prof, "__parse_dynamic_tiling_data", lambda _ctx: None)
175 monkeypatch.setattr(prof, "__parse_binary_tiling_data", lambda _ctx: None)185 monkeypatch.setattr(prof, "__parse_binary_tiling_data", lambda _ctx: None)
@@ -178,9 +188,11 @@ def test_profile_process_gate_open_and_shut(monkeypatch):
178 # resolve 路径:tolerance None → resolve 返回 [] → need_3party=False188 # resolve 路径:tolerance None → resolve 返回 [] → need_3party=False
179 monkeypatch.setattr(prof, "get_spec_attr", lambda *_a, **_k: None)189 monkeypatch.setattr(prof, "get_spec_attr", lambda *_a, **_k: None)
180 import ttk.core_modules.comparison.resolve as resolve_mod190 import ttk.core_modules.comparison.resolve as resolve_mod
191+ 
181 monkeypatch.setattr(resolve_mod, "resolve_tolerance", lambda *_a, **_k: [])192 monkeypatch.setattr(resolve_mod, "resolve_tolerance", lambda *_a, **_k: [])
182 # clear_error_manager 在 profile_process 内 lazy import193 # clear_error_manager 在 profile_process 内 lazy import
183 import ttk.core_modules.npu.error_cleaner as ec194 import ttk.core_modules.npu.error_cleaner as ec
195+ 
184 monkeypatch.setattr(ec, "clear_error_manager", lambda: None)196 monkeypatch.setattr(ec, "clear_error_manager", lambda: None)
185 197 
186 # 记录 _do_xpu_profiling 调用;OPEN 时抛异常短路198 # 记录 _do_xpu_profiling 调用;OPEN 时抛异常短路
@@ -8,12 +8,14 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""Tests for get_config() raising RuntimeError before load_config()."""10"""Tests for get_config() raising RuntimeError before load_config()."""
11+ 
11import pytest12import pytest
12 13 
13 14 
14def test_get_config_raises_before_load():15def test_get_config_raises_before_load():
15 """get_config() 在 load_config() 之前调 → raise RuntimeError(不再 lazy fallback)。"""16 """get_config() 在 load_config() 之前调 → raise RuntimeError(不再 lazy fallback)。"""
16 import ttk.config.loader as loader17 import ttk.config.loader as loader
18+ 
17 saved = loader._config19 saved = loader._config
18 loader._config = None20 loader._config = None
19 try:21 try:
@@ -21,17 +21,27 @@ def _cfg(**overrides):
21 return RemoteConfig.from_dict(base)21 return RemoteConfig.from_dict(base)
22 22 
23 23 
24-@pytest.mark.parametrize("fields, expected", [24+@pytest.mark.parametrize(
25- # backoff 三字段25+ "fields, expected",
26- ({"backoff_base_s": 0.5, "backoff_max_s": 60.0, "backoff_jitter": 0.1},26+ [
27- {"backoff_base_s": 0.5, "backoff_max_s": 60.0, "backoff_jitter": 0.1}),27+ # backoff 三字段
28- # retry 三字段28+ (
29- ({"max_503_retries": 15, "max_conn_retries": 8, "dispatch_deadline_s": 600},29+ {"backoff_base_s": 0.5, "backoff_max_s": 60.0, "backoff_jitter": 0.1},
30- {"max_503_retries": 15, "max_conn_retries": 8, "dispatch_deadline_s": 600}),30+ {"backoff_base_s": 0.5, "backoff_max_s": 60.0, "backoff_jitter": 0.1},
31- # TLS 三字段31+ ),
32- ({"tls_ca": "/path/to/ca.pem", "tls_cert": "/path/to/cert.pem", "tls_key": "/path/to/key.pem"},32+ # retry 三字段
33- {"tls_ca": "/path/to/ca.pem", "tls_cert": "/path/to/cert.pem", "tls_key": "/path/to/key.pem"}),33+ (
34-], ids=["backoff", "retry", "tls"])34+ {"max_503_retries": 15, "max_conn_retries": 8, "dispatch_deadline_s": 600},
35+ {"max_503_retries": 15, "max_conn_retries": 8, "dispatch_deadline_s": 600},
36+ ),
37+ # TLS 三字段
38+ (
39+ {"tls_ca": "/path/to/ca.pem", "tls_cert": "/path/to/cert.pem", "tls_key": "/path/to/key.pem"},
40+ {"tls_ca": "/path/to/ca.pem", "tls_cert": "/path/to/cert.pem", "tls_key": "/path/to/key.pem"},
41+ ),
42+ ],
43+ ids=["backoff", "retry", "tls"],
44+)
35def test_remote_config_field_groups(fields, expected):45def test_remote_config_field_groups(fields, expected):
36 """backoff / retry / TLS 三组字段从 yaml dict 正确解析到 RemoteConfig。"""46 """backoff / retry / TLS 三组字段从 yaml dict 正确解析到 RemoteConfig。"""
37 config = _cfg(**fields)47 config = _cfg(**fields)
@@ -55,15 +65,23 @@ def test_remote_config_defaults():
55 65 
56def test_remote_config_all_fields_together():66def test_remote_config_all_fields_together():
57 """全字段 + 多 endpoint 组合配置。"""67 """全字段 + 多 endpoint 组合配置。"""
58- config = RemoteConfig.from_dict({68+ config = RemoteConfig.from_dict(
59- "endpoints": [69+ {
60- {"host": "127.0.0.1", "port": 9090},70+ "endpoints": [
61- {"host": "192.168.1.1", "port": 8080},71+ {"host": "127.0.0.1", "port": 9090},
62- ],72+ {"host": "192.168.1.1", "port": 8080},
63- "backoff_base_s": 1.0, "backoff_max_s": 30.0, "backoff_jitter": 0.05,73+ ],
64- "max_503_retries": 20, "max_conn_retries": 10, "dispatch_deadline_s": 900,74+ "backoff_base_s": 1.0,
65- "tls_ca": "/etc/ssl/ca.crt", "tls_cert": "/etc/ssl/client.crt", "tls_key": "/etc/ssl/client.key",75+ "backoff_max_s": 30.0,
66- })76+ "backoff_jitter": 0.05,
77+ "max_503_retries": 20,
78+ "max_conn_retries": 10,
79+ "dispatch_deadline_s": 900,
80+ "tls_ca": "/etc/ssl/ca.crt",
81+ "tls_cert": "/etc/ssl/client.crt",
82+ "tls_key": "/etc/ssl/client.key",
83+ }
84+ )
67 assert len(config.endpoints) == 285 assert len(config.endpoints) == 2
68 assert config.backoff_base_s == 1.0 and config.backoff_max_s == 30.0 and config.backoff_jitter == 0.0586 assert config.backoff_base_s == 1.0 and config.backoff_max_s == 30.0 and config.backoff_jitter == 0.05
69 assert config.max_503_retries == 20 and config.max_conn_retries == 10 and config.dispatch_deadline_s == 90087 assert config.max_503_retries == 20 and config.max_conn_retries == 10 and config.dispatch_deadline_s == 900
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""Shared pytest fixtures for the test suite."""10"""Shared pytest fixtures for the test suite."""
11+ 
11from pathlib import Path12from pathlib import Path
12 13 
13import pytest14import pytest
@@ -24,6 +25,7 @@ def isolate_ttk_environment(monkeypatch):
24 # endpoints),使断言"未配 endpoints"的测试随环境红。只保留 cwd 相对路径,25 # endpoints),使断言"未配 endpoints"的测试随环境红。只保留 cwd 相对路径,
25 # 测试用 tmp_path + chdir 自己铺配置。26 # 测试用 tmp_path + chdir 自己铺配置。
26 import ttk.config.loader as _loader27 import ttk.config.loader as _loader
28+ 
27 monkeypatch.setattr(_loader, "_CONFIG_PATHS", [Path("ttk.conf.yaml")])29 monkeypatch.setattr(_loader, "_CONFIG_PATHS", [Path("ttk.conf.yaml")])
28 30 
29 31 
@@ -31,8 +33,10 @@ def isolate_ttk_environment(monkeypatch):
31def make_testcase():33def make_testcase():
32 """Factory: TestcaseAclnn instances (shared global; test_testcase_e2e.py34 """Factory: TestcaseAclnn instances (shared global; test_testcase_e2e.py
33 overrides locally with TestcaseE2e)."""35 overrides locally with TestcaseE2e)."""
36+ 
34 def _make(api_name="aclnnDummy", **kwargs):37 def _make(api_name="aclnnDummy", **kwargs):
35 from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn38 from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn
39+ 
36 case = TestcaseAclnn()40 case = TestcaseAclnn()
37 case.api_name = api_name41 case.api_name = api_name
38 case.is_valid = True42 case.is_valid = True
@@ -41,6 +45,7 @@ def make_testcase():
41 for k, v in kwargs.items():45 for k, v in kwargs.items():
42 setattr(case, k, v)46 setattr(case, k, v)
43 return case47 return case
48+ 
44 return _make49 return _make
45 50 
46 51 
@@ -54,5 +59,6 @@ def _load_default_config():
54 load_config(yaml),测完 load_config() 恢复默认。59 load_config(yaml),测完 load_config() 恢复默认。
55 """60 """
56 from ttk.config.loader import load_config61 from ttk.config.loader import load_config
62+ 
57 load_config()63 load_config()
58 yield64 yield
@@ -89,6 +89,7 @@ def test_acl_init_tracks_external_runtime(monkeypatch):
89 device = AclInterface.__new__(AclInterface)89 device = AclInterface.__new__(AclInterface)
90 device._acl_inited = False90 device._acl_inited = False
91 device._owns_acl_runtime = False91 device._owns_acl_runtime = False
92+ 
92 def _noop():93 def _noop():
93 pass94 pass
94 95 
@@ -96,13 +97,15 @@ def test_acl_init_tracks_external_runtime(monkeypatch):
96 call = {}97 call = {}
97 98 
98 def record_call(kind, api_name, extra_log, *args, accepted_errors=()):99 def record_call(kind, api_name, extra_log, *args, accepted_errors=()):
99- call.update({100+ call.update(
100- "kind": kind,101+ {
101- "api_name": api_name,102+ "kind": kind,
102- "extra_log": extra_log,103+ "api_name": api_name,
103- "args": args,104+ "extra_log": extra_log,
104- "accepted_errors": accepted_errors,105+ "args": args,
105- })106+ "accepted_errors": accepted_errors,
107+ }
108+ )
106 return ACL_ERROR_REPEAT_INITIALIZE109 return ACL_ERROR_REPEAT_INITIALIZE
107 110 
108 monkeypatch.setattr(device, "_api_call", record_call)111 monkeypatch.setattr(device, "_api_call", record_call)
@@ -121,8 +124,7 @@ def test_acl_init_tracks_external_runtime(monkeypatch):
121 124 
122 125 
123@pytest.mark.parametrize("owns_runtime, expected_reset_calls", [(False, 0), (True, 1)])126@pytest.mark.parametrize("owns_runtime, expected_reset_calls", [(False, 0), (True, 1)])
124-def test_reset_only_resets_owned_runtime_device(127+def test_reset_only_resets_owned_runtime_device(monkeypatch, owns_runtime, expected_reset_calls):
125- monkeypatch, owns_runtime, expected_reset_calls):
126 device = AclInterface.__new__(AclInterface)128 device = AclInterface.__new__(AclInterface)
127 device._owns_acl_runtime = owns_runtime129 device._owns_acl_runtime = owns_runtime
128 device._device_id = 0130 device._device_id = 0
@@ -145,8 +147,7 @@ def test_reset_only_resets_owned_runtime_device(
145 147 
146 148 
147@pytest.mark.parametrize("owns_runtime, expected_finalize_calls", [(False, 0), (True, 1)])149@pytest.mark.parametrize("owns_runtime, expected_finalize_calls", [(False, 0), (True, 1)])
148-def test_finalize_only_finalizes_owned_runtime(150+def test_finalize_only_finalizes_owned_runtime(monkeypatch, owns_runtime, expected_finalize_calls):
149- monkeypatch, owns_runtime, expected_finalize_calls):
150 device = AclInterface.__new__(AclInterface)151 device = AclInterface.__new__(AclInterface)
151 device._acl_inited = True152 device._acl_inited = True
152 device._owns_acl_runtime = owns_runtime153 device._owns_acl_runtime = owns_runtime
@@ -168,9 +169,7 @@ def test_finalize_only_finalizes_owned_runtime(
168 169 
169def test_create_acl_tensor_from_numpy_copies_complete_storage_with_offset():170def test_create_acl_tensor_from_numpy_copies_complete_storage_with_offset():
170 storage = numpy.arange(64, dtype=numpy.uint8).reshape(4, 16)171 storage = numpy.arange(64, dtype=numpy.uint8).reshape(4, 16)
171- view = np_as_strided_safe(172+ view = np_as_strided_safe(storage.ravel()[3:], shape=(2, 2, 4), strides=(16, 4, 1))
172- storage.ravel()[3:], shape=(2, 2, 4), strides=(16, 4, 1)
173- )
174 device, captured = make_numpy_tensor_device()173 device, captured = make_numpy_tensor_device()
175 174 
176 device.create_acl_tensor(view, "ND", storage.shape)175 device.create_acl_tensor(view, "ND", storage.shape)
@@ -187,9 +186,7 @@ def test_create_acl_tensor_from_numpy_rejects_ambiguous_parent_storage():
187 parent = numpy.arange(128, dtype=numpy.uint8)186 parent = numpy.arange(128, dtype=numpy.uint8)
188 storage_shape = (4, 16)187 storage_shape = (4, 16)
189 # The 93-byte tail and 128-byte parent are both larger than the declared storage.188 # The 93-byte tail and 128-byte parent are both larger than the declared storage.
190- view = np_as_strided_safe(189+ view = np_as_strided_safe(parent[35:], shape=(2, 2, 4), strides=(16, 4, 1))
191- parent[35:], shape=(2, 2, 4), strides=(16, 4, 1)
192- )
193 device, _ = make_numpy_tensor_device()190 device, _ = make_numpy_tensor_device()
194 191 
195 with pytest.raises(ValueError, match="exact contiguous numpy storage"):192 with pytest.raises(ValueError, match="exact contiguous numpy storage"):
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""binary_equal 跨 dtype 比对测试:整数跨宽度相等、bool/int 互认、int4 二进制一致、浮点跨 dtype 拒绝、空数组。"""10"""binary_equal 跨 dtype 比对测试:整数跨宽度相等、bool/int 互认、int4 二进制一致、浮点跨 dtype 拒绝、空数组。"""
11+ 
11import numpy as np12import numpy as np
12import pytest13import pytest
13 14 
@@ -42,10 +43,13 @@ def test_isclose_alias_registered():
42 assert _cls("isclose") is _cls("close")43 assert _cls("isclose") is _cls("close")
43 44 
44 45 
45-@pytest.mark.parametrize("actual_vals, actual_dtype, golden_vals, golden_dtype, check_metrics", [46+@pytest.mark.parametrize(
46- pytest.param([1, 2, 3], "int32", [1, 2, 3], "int64", True, id="int32_int64_equal"),47+ "actual_vals, actual_dtype, golden_vals, golden_dtype, check_metrics",
47- pytest.param([False, True], "bool", [0, 1], "int32", False, id="bool_vs_int"),48+ [
48-])49+ pytest.param([1, 2, 3], "int32", [1, 2, 3], "int64", True, id="int32_int64_equal"),
50+ pytest.param([False, True], "bool", [0, 1], "int32", False, id="bool_vs_int"),
51+ ],
52+)
49def test_cross_dtype_pass(actual_vals, actual_dtype, golden_vals, golden_dtype, check_metrics):53def test_cross_dtype_pass(actual_vals, actual_dtype, golden_vals, golden_dtype, check_metrics):
50 """跨 dtype 一致 → PASS(int32/int64、bool/int32、int4/int4 二进制一致)。"""54 """跨 dtype 一致 → PASS(int32/int64、bool/int32、int4/int4 二进制一致)。"""
51 actual = _arr(actual_vals, actual_dtype)55 actual = _arr(actual_vals, actual_dtype)
@@ -58,13 +62,16 @@ def test_cross_dtype_pass(actual_vals, actual_dtype, golden_vals, golden_dtype,
58 assert metrics["standard"] == "binary_equal" and metrics["pass"] is True62 assert metrics["standard"] == "binary_equal" and metrics["pass"] is True
59 63 
60 64 
61-@pytest.mark.parametrize("actual_vals, actual_dtype, golden_vals, golden_dtype", [65+@pytest.mark.parametrize(
62- pytest.param([1, 2], "int32", [1, 3], "int64", id="int32_int64_different"),66+ "actual_vals, actual_dtype, golden_vals, golden_dtype",
63- pytest.param([1, 2, 3], "int4", [1, 9, 3], "int4", id="int4_different"),67+ [
64- pytest.param([1.0], "float32", [1.0], "float64", id="float_cross_dtype"),68+ pytest.param([1, 2], "int32", [1, 3], "int64", id="int32_int64_different"),
65- pytest.param([1], "int32", [1.0], "float32", id="int_vs_float"),69+ pytest.param([1, 2, 3], "int4", [1, 9, 3], "int4", id="int4_different"),
66- pytest.param([1], "uint64", [1], "int32", id="uint64_int32_precision_loss"),70+ pytest.param([1.0], "float32", [1.0], "float64", id="float_cross_dtype"),
67-])71+ pytest.param([1], "int32", [1.0], "float32", id="int_vs_float"),
72+ pytest.param([1], "uint64", [1], "int32", id="uint64_int32_precision_loss"),
73+ ],
74+)
68def test_cross_dtype_fail(actual_vals, actual_dtype, golden_vals, golden_dtype):75def test_cross_dtype_fail(actual_vals, actual_dtype, golden_vals, golden_dtype):
69 """跨 dtype 不一致/不可比 → FAIL(值不同、浮点跨 dtype、int/float 混合、uint64/int32 精度损失)。"""76 """跨 dtype 不一致/不可比 → FAIL(值不同、浮点跨 dtype、int/float 混合、uint64/int32 精度损失)。"""
70 actual = _arr(actual_vals, actual_dtype)77 actual = _arr(actual_vals, actual_dtype)
@@ -74,10 +81,13 @@ def test_cross_dtype_fail(actual_vals, actual_dtype, golden_vals, golden_dtype):
74 assert is_pass is False81 assert is_pass is False
75 82 
76 83 
77-@pytest.mark.parametrize("actual, golden, dtype, expected_pass", [84+@pytest.mark.parametrize(
78- pytest.param(np.array([]), np.array([]), "int32", True, id="empty_both"),85+ "actual, golden, dtype, expected_pass",
79- pytest.param(np.array([1], np.int32), np.array([], np.int32), "int32", False, id="empty_one"),86+ [
80-])87+ pytest.param(np.array([]), np.array([]), "int32", True, id="empty_both"),
88+ pytest.param(np.array([1], np.int32), np.array([], np.int32), "int32", False, id="empty_one"),
89+ ],
90+)
81def test_empty_cases(actual, golden, dtype, expected_pass):91def test_empty_cases(actual, golden, dtype, expected_pass):
82 """空数组场景:双方空 → PASS,一方空 → FAIL。"""92 """空数组场景:双方空 → PASS,一方空 → FAIL。"""
83 c = _cls("binary_equal")(actual, golden, 0, dtype, {})93 c = _cls("binary_equal")(actual, golden, 0, dtype, {})
@@ -87,8 +97,9 @@ def test_empty_cases(actual, golden, dtype, expected_pass):
87 97 
88def test_isclose_populates_metrics():98def test_isclose_populates_metrics():
89 """isclose 比对填充 standard/pass/precision metrics。"""99 """isclose 比对填充 standard/pass/precision metrics。"""
90- c = _cls("isclose")(np.array([1.0, 2.0]), np.array([1.0, 2.0]), 0, "float32",100+ c = _cls("isclose")(
91- {"rtol": [1e-3], "atol": [1e-8], "ptol": [1e-3]})101+ np.array([1.0, 2.0]), np.array([1.0, 2.0]), 0, "float32", {"rtol": [1e-3], "atol": [1e-8], "ptol": [1e-3]}
102+ )
92 _p, _l, is_pass, metrics = c.compare()103 _p, _l, is_pass, metrics = c.compare()
93 assert metrics["standard"] == "isclose"104 assert metrics["standard"] == "isclose"
94 assert metrics["pass"] is True105 assert metrics["pass"] is True
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""UT for compare() 接口(comparison.py 核心分发逻辑)。"""10"""UT for compare() 接口(comparison.py 核心分发逻辑)。"""
11+ 
11from unittest.mock import MagicMock12from unittest.mock import MagicMock
12 13 
13import numpy as np14import numpy as np
@@ -21,10 +22,13 @@ def _stds(n):
21 22 
22 23 
23class TestOutputSentinels:24class TestOutputSentinels:
24- @pytest.mark.parametrize("sentinel, check_precision", [25+ @pytest.mark.parametrize(
25- pytest.param("DYN_OFF", True, id="dyn_off"),26+ "sentinel, check_precision",
26- pytest.param("BIN_OFF", False, id="bin_off"),27+ [
27- ])28+ pytest.param("DYN_OFF", True, id="dyn_off"),
29+ pytest.param("BIN_OFF", False, id="bin_off"),
30+ ],
31+ )
28 def test_pass_sentinels(self, sentinel, check_precision):32 def test_pass_sentinels(self, sentinel, check_precision):
29 """各 fake-pass 哨兵 output → is_pass=True。"""33 """各 fake-pass 哨兵 output → is_pass=True。"""
30 r = compare([sentinel], [np.array([1.0])], ("float32",), standards=_stds(1))34 r = compare([sentinel], [np.array([1.0])], ("float32",), standards=_stds(1))
@@ -32,10 +36,13 @@ class TestOutputSentinels:
32 if check_precision:36 if check_precision:
33 assert sentinel in r[0]37 assert sentinel in r[0]
34 38 
35- @pytest.mark.parametrize("output, expected_precision_substr", [39+ @pytest.mark.parametrize(
36- pytest.param(None, "NO_OUTPUT", id="none_output"),40+ "output, expected_precision_substr",
37- pytest.param("SOMETHING_ELSE", None, id="non_fake_string"),41+ [
38- ])42+ pytest.param(None, "NO_OUTPUT", id="none_output"),
43+ pytest.param("SOMETHING_ELSE", None, id="non_fake_string"),
44+ ],
45+ )
39 def test_fail_sentinels(self, output, expected_precision_substr):46 def test_fail_sentinels(self, output, expected_precision_substr):
40 """非 fake-pass output → is_pass=False。"""47 """非 fake-pass output → is_pass=False。"""
41 r = compare([output], [np.array([1.0])], ("float32",), standards=_stds(1))48 r = compare([output], [np.array([1.0])], ("float32",), standards=_stds(1))
@@ -47,24 +54,25 @@ class TestOutputSentinels:
47class TestMultiOutput:54class TestMultiOutput:
48 def test_mix_dyn_off_and_none_fails(self):55 def test_mix_dyn_off_and_none_fails(self):
49 """多输出混合 fake-pass 与 None → 整体 FAIL。"""56 """多输出混合 fake-pass 与 None → 整体 FAIL。"""
50- r = compare(["DYN_OFF", None], [np.array([1.0]), np.array([2.0])],57+ r = compare(["DYN_OFF", None], [np.array([1.0]), np.array([2.0])], ("float32", "float32"), standards=_stds(2))
51- ("float32", "float32"), standards=_stds(2))
52 assert r[2] is False58 assert r[2] is False
53 59 
54 60 
55class TestThirdPartyCount:61class TestThirdPartyCount:
56 def test_none_third_parties_ok(self):62 def test_none_third_parties_ok(self):
57 """third_parties=None → 正常流程(不报错)。"""63 """third_parties=None → 正常流程(不报错)。"""
58- r = compare(["DYN_OFF"], [np.array([1.0])],64+ r = compare(["DYN_OFF"], [np.array([1.0])], ("float32",), standards=_stds(1))
59- ("float32",), standards=_stds(1))
60 assert r[2] is True65 assert r[2] is True
61 66 
62 67 
63class TestFilterFakeFail:68class TestFilterFakeFail:
64- @pytest.mark.parametrize("token, expected", [69+ @pytest.mark.parametrize(
65- pytest.param("DYN_OFF", True, id="DYN_OFF"),70+ "token, expected",
66- pytest.param("PASS", False, id="PASS"),71+ [
67- ])72+ pytest.param("DYN_OFF", True, id="DYN_OFF"),
73+ pytest.param("PASS", False, id="PASS"),
74+ ],
75+ )
68 def test_filter_fake_fail(self, token, expected):76 def test_filter_fake_fail(self, token, expected):
69 """_filter_fake_fail: fake-pass token → True,其余 → False。"""77 """_filter_fake_fail: fake-pass token → True,其余 → False。"""
70 assert _filter_fake_fail(token) is expected78 assert _filter_fake_fail(token) is expected
@@ -20,8 +20,7 @@ def test_compare_returns_4tuple_with_metrics():
20 outputs = [np.array([1.0, 2.0], np.float32)]20 outputs = [np.array([1.0, 2.0], np.float32)]
21 goldens = [np.array([1.0, 2.0], np.float32)]21 goldens = [np.array([1.0, 2.0], np.float32)]
22 standards = resolve_tolerance(None, None, None, ["float32"], None) # -> stat_rel_err22 standards = resolve_tolerance(None, None, None, ["float32"], None) # -> stat_rel_err
23- precision, log, is_pass, metrics = compare(outputs, goldens, ("float32",),23+ precision, log, is_pass, metrics = compare(outputs, goldens, ("float32",), standards=standards)
24- standards=standards)
25 assert is_pass is True24 assert is_pass is True
26 assert 0 in metrics25 assert 0 in metrics
27 assert metrics[0]["standard"] == "stat_rel_err"26 assert metrics[0]["standard"] == "stat_rel_err"
@@ -29,6 +28,7 @@ def test_compare_returns_4tuple_with_metrics():
29 28 
30# —— 端到端:Spec.tolerance → resolve → compare → metrics → structure(CR5-I2)——29# —— 端到端:Spec.tolerance → resolve → compare → metrics → structure(CR5-I2)——
31 30 
31+ 
32def test_threshold_override_flows_to_metrics():32def test_threshold_override_flows_to_metrics():
33 """Spec.tolerance threshold override 经 resolve → compare → stat_rel_err metrics。"""33 """Spec.tolerance threshold override 经 resolve → compare → stat_rel_err metrics。"""
34 tolerance = {"float32": {"standard": "stat_rel_err", "threshold": 1e-3}}34 tolerance = {"float32": {"standard": "stat_rel_err", "threshold": 1e-3}}
@@ -79,7 +79,7 @@ def test_metrics_flow_to_api_structure():
79def test_output_none_fails():79def test_output_none_fails():
80 """output=None + golden 非 None → NO_OUTPUT / FAIL。"""80 """output=None + golden 非 None → NO_OUTPUT / FAIL。"""
81 precision, _log, is_pass, _m = compare(81 precision, _log, is_pass, _m = compare(
82- [None], [np.array([1.0])], ("float32",),82+ [None], [np.array([1.0])], ("float32",), standards=[ResolvedStandard("stat_rel_err")]
83- standards=[ResolvedStandard("stat_rel_err")])83+ )
84 assert precision == "NO_OUTPUT"84 assert precision == "NO_OUTPUT"
85 assert is_pass is False85 assert is_pass is False
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""Tests for CrossCheckComparison: pass/fail/ratio/small_value/nan + safe_div."""10"""Tests for CrossCheckComparison: pass/fail/ratio/small_value/nan + safe_div."""
11+ 
11import numpy as np12import numpy as np
12 13 
13from ttk.core_modules.comparison.cross_check import CrossCheckComparison, safe_div14from ttk.core_modules.comparison.cross_check import CrossCheckComparison, safe_div
@@ -19,22 +20,34 @@ def _make(output, golden, third_party, params):
19 20 
20def test_pass_when_all_good():21def test_pass_when_all_good():
21 """三方误差一致 → PASS + metrics 结构。"""22 """三方误差一致 → PASS + metrics 结构。"""
22- params = {"level": "L1", "mare_ratio": 5.0, "mere_ratio": 1.5, "rmse_ratio": 1.5,23+ params = {
23- "small_value": 2**-14, "small_value_atol": 2**-30,24+ "level": "L1",
24- "legacy": {"rtol": None, "ptol": None, "atol": 1e-8}}25+ "mare_ratio": 5.0,
26+ "mere_ratio": 1.5,
27+ "rmse_ratio": 1.5,
28+ "small_value": 2**-14,
29+ "small_value_atol": 2**-30,
30+ "legacy": {"rtol": None, "ptol": None, "atol": 1e-8},
31+ }
25 g = np.array([1.0, 2.0, 3.0], dtype=np.float32)32 g = np.array([1.0, 2.0, 3.0], dtype=np.float32)
26 c = _make(g.copy(), g.copy(), g.copy(), params)33 c = _make(g.copy(), g.copy(), g.copy(), params)
27 precision, log, is_pass, metrics = c.compare()34 precision, log, is_pass, metrics = c.compare()
28 assert is_pass is True35 assert is_pass is True
29 assert metrics["standard"] == "cross_check"36 assert metrics["standard"] == "cross_check"
30- assert "config" in metrics and "result" in metrics # 单元测试定位 metrics 形状(不只靠 Task 9 E2E)37+ assert "config" in metrics and "result" in metrics # 单元测试定位 metrics 形状(不只靠 Task 9 E2E)
31 38 
32 39 
33def test_ratio_exceeded():40def test_ratio_exceeded():
34 """NPU 误差远大于 third_party → ratio_exceeded FAIL。"""41 """NPU 误差远大于 third_party → ratio_exceeded FAIL。"""
35- params = {"level": "L1", "mare_ratio": 5.0, "mere_ratio": 1.5, "rmse_ratio": 1.5,42+ params = {
36- "small_value": 2**-14, "small_value_atol": 2**-30,43+ "level": "L1",
37- "legacy": {"rtol": None, "ptol": None, "atol": 1e-8}}44+ "mare_ratio": 5.0,
45+ "mere_ratio": 1.5,
46+ "rmse_ratio": 1.5,
47+ "small_value": 2**-14,
48+ "small_value_atol": 2**-30,
49+ "legacy": {"rtol": None, "ptol": None, "atol": 1e-8},
50+ }
38 g = np.array([1.0, 2.0, 3.0], dtype=np.float32)51 g = np.array([1.0, 2.0, 3.0], dtype=np.float32)
39 out = g + 1.0 # NPU 误差大52 out = g + 1.0 # NPU 误差大
40 third = g + 0.001 # third_party 误差小53 third = g + 0.001 # third_party 误差小
@@ -46,37 +59,49 @@ def test_ratio_exceeded():
46 59 
47def test_safe_div_branches():60def test_safe_div_branches():
48 """safe_div: 分母夹按 dtype 取的小值域阈值 err(精度标准);nan/inf 分母 -> inf。"""61 """safe_div: 分母夹按 dtype 取的小值域阈值 err(精度标准);nan/inf 分母 -> inf。"""
49- err = 2 ** -14 # fp32 的 err62+ err = 2**-14 # fp32 的 err
50- assert safe_div(0, 0, err) == 1.0 # 0/0 -> 1(两边都完美,一致)63+ assert safe_div(0, 0, err) == 1.0 # 0/0 -> 1(两边都完美,一致)
51- assert safe_div(1.0, 0, err) == 1.0 / err # 分母为 0 -> 夹到 err(非 inf)64+ assert safe_div(1.0, 0, err) == 1.0 / err # 分母为 0 -> 夹到 err(非 inf)
52- assert safe_div(6, 3, err) == 2.0 # 分母 > err,照常相除65+ assert safe_div(6, 3, err) == 2.0 # 分母 > err,照常相除
53 assert safe_div(0.1, float("nan"), err) == float("inf") # nan 分母 -> inf66 assert safe_div(0.1, float("nan"), err) == float("inf") # nan 分母 -> inf
54- assert isinstance(safe_div(6, 3, err), float) # 守护 float() 强转67+ assert isinstance(safe_div(6, 3, err), float) # 守护 float() 强转
55 68 
56 69 
57def test_small_value_partition_pass():70def test_small_value_partition_pass():
58 """防回归(spec §9):golden 全小(<small_value)+ third_party 误差大 + NPU 精确 → small_ratio 小 → PASS。"""71 """防回归(spec §9):golden 全小(<small_value)+ third_party 误差大 + NPU 精确 → small_ratio 小 → PASS。"""
59- params = {"level": "L1", "mare_ratio": 5.0, "mere_ratio": 1.5, "rmse_ratio": 1.5,72+ params = {
60- "small_value": 2**-14, "small_value_atol": 2**-30,73+ "level": "L1",
61- "legacy": {"rtol": None, "ptol": None, "atol": 1e-8}}74+ "mare_ratio": 5.0,
62- g = np.full(100, 1e-15, dtype=np.float32) # 全 < small_value(2**-14≈6e-5)→ 全 small 分区,large 空75+ "mere_ratio": 1.5,
63- out = g.copy() # NPU 精确 → err_target=076+ "rmse_ratio": 1.5,
77+ "small_value": 2**-14,
78+ "small_value_atol": 2**-30,
79+ "legacy": {"rtol": None, "ptol": None, "atol": 1e-8},
80+ }
81+ g = np.full(100, 1e-15, dtype=np.float32) # 全 < small_value(2**-14≈6e-5)→ 全 small 分区,large 空
82+ out = g.copy() # NPU 精确 → err_target=0
64 third = g + 1e-8 # third_party 误差大(>>small_value_atol=2**-30≈9.3e-10)→err_third=10083 third = g + 1e-8 # third_party 误差大(>>small_value_atol=2**-30≈9.3e-10)→err_third=100
65 c = _make(out, g, third, params)84 c = _make(out, g, third, params)
66 precision, log, is_pass, metrics = c.compare()85 precision, log, is_pass, metrics = c.compare()
67 assert is_pass86 assert is_pass
68 assert metrics["result"]["small_err_cnt_target"] == 087 assert metrics["result"]["small_err_cnt_target"] == 0
69 assert metrics["result"]["small_err_cnt_third"] > 088 assert metrics["result"]["small_err_cnt_third"] > 0
70- assert metrics["result"]["mare"] is None # 全小值域 large-empty → mare N/A(非 0.0 误导)89+ assert metrics["result"]["mare"] is None # 全小值域 large-empty → mare N/A(非 0.0 误导)
71 90 
72 91 
73def test_nan_inf_mismatch():92def test_nan_inf_mismatch():
74 """NaN/Inf 特殊位 mismatch → reason=NaN/Inf mismatch(special_ok 短路优先于 ratio)。"""93 """NaN/Inf 特殊位 mismatch → reason=NaN/Inf mismatch(special_ok 短路优先于 ratio)。"""
75- params = {"level": "L1", "mare_ratio": 5.0, "mere_ratio": 1.5, "rmse_ratio": 1.5,94+ params = {
76- "small_value": 2**-14, "small_value_atol": 2**-30,95+ "level": "L1",
77- "legacy": {"rtol": None, "ptol": None, "atol": 1e-8}}96+ "mare_ratio": 5.0,
97+ "mere_ratio": 1.5,
98+ "rmse_ratio": 1.5,
99+ "small_value": 2**-14,
100+ "small_value_atol": 2**-30,
101+ "legacy": {"rtol": None, "ptol": None, "atol": 1e-8},
102+ }
78 g = np.array([1.0, np.nan], dtype=np.float32)103 g = np.array([1.0, np.nan], dtype=np.float32)
79- out = np.array([1.0, 1.0], dtype=np.float32) # golden[1]=nan 但 NPU 非 nan → special 位 mismatch104+ out = np.array([1.0, 1.0], dtype=np.float32) # golden[1]=nan 但 NPU 非 nan → special 位 mismatch
80 third = np.array([1.0, np.nan], dtype=np.float32)105 third = np.array([1.0, np.nan], dtype=np.float32)
81 c = _make(out, g, third, params)106 c = _make(out, g, third, params)
82 precision, log, is_pass, metrics = c.compare()107 precision, log, is_pass, metrics = c.compare()
@@ -9,6 +9,7 @@
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10# tests/test_cross_check_integration.py10# tests/test_cross_check_integration.py
11"""端到端:spec.tolerance → resolve → compare(third_parties=) → metrics。"""11"""端到端:spec.tolerance → resolve → compare(third_parties=) → metrics。"""
12+ 
12import numpy as np13import numpy as np
13 14 
14from ttk.core_modules.comparison import compare15from ttk.core_modules.comparison import compare
@@ -23,7 +24,8 @@ def test_cross_check_e2e_pass():
23 goldens = [g.copy()]24 goldens = [g.copy()]
24 third_parties = [g.copy()]25 third_parties = [g.copy()]
25 precision, log, is_pass, metrics = compare(26 precision, log, is_pass, metrics = compare(
26- outputs, goldens, ("float32",), standards=standards, third_parties=third_parties)27+ outputs, goldens, ("float32",), standards=standards, third_parties=third_parties
28+ )
27 assert is_pass29 assert is_pass
28 assert metrics[0]["standard"] == "cross_check"30 assert metrics[0]["standard"] == "cross_check"
29 assert "config" in metrics[0] and "result" in metrics[0]31 assert "config" in metrics[0] and "result" in metrics[0]
@@ -36,10 +38,12 @@ def test_cross_check_e2e_count_mismatch():
36 tolerance = {"float32": {"standard": "cross_check", "level": "L1"}}38 tolerance = {"float32": {"standard": "cross_check", "level": "L1"}}
37 standards = resolve_tolerance(tolerance, None, 1e-8, ["float32", "float32"], None)39 standards = resolve_tolerance(tolerance, None, 1e-8, ["float32", "float32"], None)
38 precision, log, is_pass, metrics = compare(40 precision, log, is_pass, metrics = compare(
39- [np.array([1.0]), np.array([2.0])], [np.array([1.0]), np.array([2.0])],41+ [np.array([1.0]), np.array([2.0])],
42+ [np.array([1.0]), np.array([2.0])],
40 ("float32", "float32"),43 ("float32", "float32"),
41 standards=standards,44 standards=standards,
42- third_parties=[np.array([1.0])]) # 1 < 245+ third_parties=[np.array([1.0])],
46+ ) # 1 < 2
43 assert precision == "COMPARE_FAILURE"47 assert precision == "COMPARE_FAILURE"
44 assert not is_pass48 assert not is_pass
45 assert metrics["reason"] == "third_party count != outputs"49 assert metrics["reason"] == "third_party count != outputs"
@@ -49,7 +53,6 @@ def test_cross_check_e2e_no_third_party():
49 """cross_check + third_parties=None → GOLDEN_FAILURE。"""53 """cross_check + third_parties=None → GOLDEN_FAILURE。"""
50 tolerance = {"float32": {"standard": "cross_check", "level": "L1"}}54 tolerance = {"float32": {"standard": "cross_check", "level": "L1"}}
51 standards = resolve_tolerance(tolerance, None, 1e-8, ["float32"], None)55 standards = resolve_tolerance(tolerance, None, 1e-8, ["float32"], None)
52- precision, log, is_pass, metrics = compare(56+ precision, log, is_pass, metrics = compare([np.array([1.0])], [np.array([1.0])], ("float32",), standards=standards)
53- [np.array([1.0])], [np.array([1.0])], ("float32",), standards=standards)
54 assert precision == "GOLDEN_FAILURE"57 assert precision == "GOLDEN_FAILURE"
55 assert not is_pass58 assert not is_pass
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""Tests for FAIL_REASONS registry keys and stat_rel_err fail reason."""10"""Tests for FAIL_REASONS registry keys and stat_rel_err fail reason."""
11+ 
11import numpy as np12import numpy as np
12 13 
13# 触发各比对类的注册(装饰器在 import 时执行)14# 触发各比对类的注册(装饰器在 import 时执行)
@@ -22,11 +23,17 @@ from ttk.core_modules.comparison.registry import FAIL_REASONS
22def test_fail_reasons_keys_exist():23def test_fail_reasons_keys_exist():
23 """FAIL_REASONS 覆盖所有 standard 的失败模式。"""24 """FAIL_REASONS 覆盖所有 standard 的失败模式。"""
24 expected_keys = {25 expected_keys = {
25- "nan_inf_mismatch", "threshold_exceeded",26+ "nan_inf_mismatch",
26- "bitwise_mismatch", "cross_dtype_uncomparable",27+ "threshold_exceeded",
27- "tolerance_exceeded", "similarity_below_threshold", "precision_exceeded",28+ "bitwise_mismatch",
28- "third_party_count_mismatch", "third_party_unavailable",29+ "cross_dtype_uncomparable",
29- "ratio_exceeded", "small_value_exceeded",30+ "tolerance_exceeded",
31+ "similarity_below_threshold",
32+ "precision_exceeded",
33+ "third_party_count_mismatch",
34+ "third_party_unavailable",
35+ "ratio_exceeded",
36+ "small_value_exceeded",
30 }37 }
31 assert expected_keys <= set(FAIL_REASONS.keys())38 assert expected_keys <= set(FAIL_REASONS.keys())
32 39 
@@ -35,9 +42,10 @@ def test_stat_rel_err_fail_has_reason():
35 """stat_rel_err 失败 metrics 有 reason(threshold_exceeded 带 mere/mare 值)。"""42 """stat_rel_err 失败 metrics 有 reason(threshold_exceeded 带 mere/mare 值)。"""
36 from ttk.core_modules.comparison.registry import ComparisonRegister43 from ttk.core_modules.comparison.registry import ComparisonRegister
37 from ttk.core_modules.comparison.resolve import resolve_tolerance44 from ttk.core_modules.comparison.resolve import resolve_tolerance
45+ 
38 standards = resolve_tolerance(46 standards = resolve_tolerance(
39- {"float32": {"standard": "stat_rel_err", "threshold": 1e-10}},47+ {"float32": {"standard": "stat_rel_err", "threshold": 1e-10}}, None, 1e-8, ["float32"], None
40- None, 1e-8, ["float32"], None)48+ )
41 cls = ComparisonRegister.registry["stat_rel_err"]49 cls = ComparisonRegister.registry["stat_rel_err"]
42 c = cls(np.array([1.0, 2.0]), np.array([1.1, 2.1]), 0, "float32", standards[0].params)50 c = cls(np.array([1.0, 2.0]), np.array([1.1, 2.1]), 0, "float32", standards[0].params)
43 r = c.compare()51 r = c.compare()
@@ -9,6 +9,7 @@
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""registry 基础设施单元测试:EachCompareResult 默认值、_to_numpy 转换、10"""registry 基础设施单元测试:EachCompareResult 默认值、_to_numpy 转换、
11ComparisonBase.compare() 4-tuple + _check_empty。"""11ComparisonBase.compare() 4-tuple + _check_empty。"""
12+ 
12import numpy as np13import numpy as np
13import pytest14import pytest
14 15 
@@ -51,13 +52,15 @@ class _Dummy(ComparisonBase):
51 STANDARD_NAME = "dummy"52 STANDARD_NAME = "dummy"
52 53 
53 def compare_impl(self):54 def compare_impl(self):
54- return EachCompareResult(0.5, is_pass=True, standard="dummy",55+ return EachCompareResult(0.5, is_pass=True, standard="dummy", metrics={"k": 1})
55- metrics={"k": 1})
56 56 
57 57 
58-@pytest.mark.parametrize("actual, golden, expected", [58+@pytest.mark.parametrize(
59- pytest.param(np.array([]), np.array([]), (True, 1), id="both_empty"),59+ "actual, golden, expected",
60-])60+ [
61+ pytest.param(np.array([]), np.array([]), (True, 1), id="both_empty"),
62+ ],
63+)
61def test_check_empty(actual, golden, expected):64def test_check_empty(actual, golden, expected):
62 """_check_empty: 双方空 → PASS(precision=1)。"""65 """_check_empty: 双方空 → PASS(precision=1)。"""
63 c = _Dummy(actual, golden, 0, "float32", {})66 c = _Dummy(actual, golden, 0, "float32", {})
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""resolve_tolerance 收敛测试:legacy 注入、cross_check level 预设/override、isclose/cosine legacy 读取。"""10"""resolve_tolerance 收敛测试:legacy 注入、cross_check level 预设/override、isclose/cosine legacy 读取。"""
11+ 
11import numpy as np12import numpy as np
12import pytest13import pytest
13 14 
@@ -22,10 +23,13 @@ def test_legacy_injection():
22 assert standards[0].params["legacy"]["atol"] == 1e-823 assert standards[0].params["legacy"]["atol"] == 1e-8
23 24 
24 25 
25-@pytest.mark.parametrize("tol_spec, expected_level, expected_mare, expected_mere, expected_rmse, check_extras", [26+@pytest.mark.parametrize(
26- pytest.param({"level": "L1"}, "L1", 5.0, 1.5, 1.5, True, id="L1_preset"),27+ "tol_spec, expected_level, expected_mare, expected_mere, expected_rmse, check_extras",
27- pytest.param({"level": "L1", "mare_ratio": 3.0}, "L1", 3.0, 1.5, 1.5, False, id="explicit_ratio_override"),28+ [
28-])29+ pytest.param({"level": "L1"}, "L1", 5.0, 1.5, 1.5, True, id="L1_preset"),
30+ pytest.param({"level": "L1", "mare_ratio": 3.0}, "L1", 3.0, 1.5, 1.5, False, id="explicit_ratio_override"),
31+ ],
32+)
29def test_cross_check_level_preset(tol_spec, expected_level, expected_mare, expected_mere, expected_rmse, check_extras):33def test_cross_check_level_preset(tol_spec, expected_level, expected_mare, expected_mere, expected_rmse, check_extras):
30 """cross_check level → ratio 预设 + override 组合(spec §9 level 矩阵):34 """cross_check level → ratio 预设 + override 组合(spec §9 level 矩阵):
31 L0/L1/L2 预设、显式 ratio 覆盖、无 level 全 ratio。"""35 L0/L1/L2 预设、显式 ratio 覆盖、无 level 全 ratio。"""
@@ -46,6 +50,7 @@ def test_isclose_reads_legacy_rtol():
46 import ttk.core_modules.comparison.is_close # noqa: F401 — 触发 @register_comparison50 import ttk.core_modules.comparison.is_close # noqa: F401 — 触发 @register_comparison
47 from ttk.core_modules.comparison.registry import ComparisonRegister51 from ttk.core_modules.comparison.registry import ComparisonRegister
48 from ttk.core_modules.comparison.resolve import resolve_tolerance52 from ttk.core_modules.comparison.resolve import resolve_tolerance
53+ 
49 standards = resolve_tolerance(None, [(0.001, 0.001)], 1e-9, ["float32"], None)54 standards = resolve_tolerance(None, [(0.001, 0.001)], 1e-9, ["float32"], None)
50 cls = ComparisonRegister.registry["isclose"]55 cls = ComparisonRegister.registry["isclose"]
51 out = np.array([1.0, 2.0])56 out = np.array([1.0, 2.0])
@@ -61,6 +66,7 @@ def test_cosine_reads_legacy_rtol():
61 import ttk.core_modules.comparison.cosine_similarity # noqa: F401 — 触发 @register_comparison66 import ttk.core_modules.comparison.cosine_similarity # noqa: F401 — 触发 @register_comparison
62 from ttk.core_modules.comparison.registry import ComparisonRegister67 from ttk.core_modules.comparison.registry import ComparisonRegister
63 from ttk.core_modules.comparison.resolve import resolve_tolerance68 from ttk.core_modules.comparison.resolve import resolve_tolerance
69+ 
64 standards = resolve_tolerance(None, [(0.01, 0.001)], 1e-9, ["float32"], "cosine")70 standards = resolve_tolerance(None, [(0.01, 0.001)], 1e-9, ["float32"], "cosine")
65 cls = ComparisonRegister.registry["cosine"]71 cls = ComparisonRegister.registry["cosine"]
66 out = np.array([1.0, 2.0])72 out = np.array([1.0, 2.0])
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""resolve_tolerance 单元测试:dtype→standard 路由 + threshold 解析(表默认/spec override/override 优先级)。"""10"""resolve_tolerance 单元测试:dtype→standard 路由 + threshold 解析(表默认/spec override/override 优先级)。"""
11+ 
11from ttk.core_modules.comparison.resolve import resolve_tolerance12from ttk.core_modules.comparison.resolve import resolve_tolerance
12 13 
13 14 
@@ -39,14 +40,16 @@ def test_normal_float_defaults_stat_rel_err():
39 40 
40def test_normal_float_cli_wins():41def test_normal_float_cli_wins():
41 """CLI 指定优先于 Spec.tolerance。"""42 """CLI 指定优先于 Spec.tolerance。"""
42- assert _tokens(resolve_tolerance(43+ assert _tokens(resolve_tolerance({"float32": {"standard": "binary_equal"}}, None, None, ["float32"], "close")) == [
43- {"float32": {"standard": "binary_equal"}}, None, None, ["float32"], "close")) == ["close"]44+ "close"
45+ ]
44 46 
45 47 
46def test_normal_float_spec_binary_equal():48def test_normal_float_spec_binary_equal():
47 """Spec.tolerance 可将 float16 改为 binary_equal。"""49 """Spec.tolerance 可将 float16 改为 binary_equal。"""
48- assert _tokens(resolve_tolerance(50+ assert _tokens(resolve_tolerance({"float16": {"standard": "binary_equal"}}, None, None, ["float16"], None)) == [
49- {"float16": {"standard": "binary_equal"}}, None, None, ["float16"], None)) == ["binary_equal"]51+ "binary_equal"
52+ ]
50 53 
51 54 
52def test_multi_output_mixed():55def test_multi_output_mixed():
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""StatRelErrComparison 单元测试:mismatch 真值表、diff_idx 分流、mere/mare 公式、边界场景。"""10"""StatRelErrComparison 单元测试:mismatch 真值表、diff_idx 分流、mere/mare 公式、边界场景。"""
11+ 
11import numpy as np12import numpy as np
12import pytest13import pytest
13 14 
@@ -27,14 +28,17 @@ def _run(actual, golden, dtype, threshold):
27 28 
28 29 
29# —— 防线 1:mismatch 真值表全覆盖(13 cell)——30# —— 防线 1:mismatch 真值表全覆盖(13 cell)——
30-@pytest.mark.parametrize("a,g,expect_pass,mere_none", [31+@pytest.mark.parametrize(
31- # match:全非有限且一致 → PASS,mere=None32+ "a,g,expect_pass,mere_none",
32- (np.nan, np.nan, True, True),33+ [
33- # finite/finitemere 路径(mere 算出来None34+ # match:全非有限且一致PASSmere=None
34- (1.0, 2.0, False, False), # mere≈0.5 >> th → FAIL35+ (np.nan, np.nan, True, True),
35- # mismatchFAIL,mere=None36+ # finite/finite → mere 路径(mere 算出来,非 None
36- (np.nan, 1.0, False, True),37+ (1.0, 2.0, False, False), # mere≈0.5 >> th → FAIL
37-])38+ # mismatch → FAIL,mere=None
39+ (np.nan, 1.0, False, True),
40+ ],
41+)
38def test_mismatch_truth_table(a, g, expect_pass, mere_none):42def test_mismatch_truth_table(a, g, expect_pass, mere_none):
39 """防线 1:mismatch 真值表全覆盖(13 cell)—非有限值一致/不一致的 PASS/FAIL 与 mere 是否为 None。"""43 """防线 1:mismatch 真值表全覆盖(13 cell)—非有限值一致/不一致的 PASS/FAIL 与 mere 是否为 None。"""
40 r = _impl([a], [g], "float32", 2**-13)44 r = _impl([a], [g], "float32", 2**-13)
@@ -80,9 +84,12 @@ def test_mere_mare_values():
80 84 
81 85 
82# —— 结构 / 边界 ——86# —— 结构 / 边界 ——
83-@pytest.mark.parametrize("actual, golden, check", [87+@pytest.mark.parametrize(
84- pytest.param([], [], "empty_precision", id="empty_both"),88+ "actual, golden, check",
85-])89+ [
90+ pytest.param([], [], "empty_precision", id="empty_both"),
91+ ],
92+)
86def test_pass_cases(actual, golden, check):93def test_pass_cases(actual, golden, check):
87 """空数组 precision=100%。"""94 """空数组 precision=100%。"""
88 precision, _l, is_pass, _m = _run(actual, golden, "float32", 2**-13)95 precision, _l, is_pass, _m = _run(actual, golden, "float32", 2**-13)
@@ -12,6 +12,7 @@
12Task 7: device_name() 返回硬件 MODEL(torch.<lib>.get_device_name);12Task 7: device_name() 返回硬件 MODEL(torch.<lib>.get_device_name);
13alias() 携带段名;soc_version 已移除(合并入 device_name)。13alias() 携带段名;soc_version 已移除(合并入 device_name)。
14"""14"""
15+ 
15from __future__ import annotations16from __future__ import annotations
16 17 
17import pytest18import pytest
@@ -54,13 +55,19 @@ def test_npu_backend_is_npu_device_type():
54 "segment, torch_lib, profiler_config, expected_cls, check_torch_lib",55 "segment, torch_lib, profiler_config, expected_cls, check_torch_lib",
55 [56 [
56 pytest.param(57 pytest.param(
57- "gpu", "cuda", {"activities": ["CPU", "CUDA"]},58+ "gpu",
58- XpuTorchBackend, "cuda",59+ "cuda",
60+ {"activities": ["CPU", "CUDA"]},
61+ XpuTorchBackend,
62+ "cuda",
59 id="segment_name_not_hardcoded",63 id="segment_name_not_hardcoded",
60 ),64 ),
61 pytest.param(65 pytest.param(
62- "ascend", "npu", "builtin",66+ "ascend",
63- NpuTorchBackend, None,67+ "npu",
68+ "builtin",
69+ NpuTorchBackend,
70+ None,
64 id="npu_torch_lib",71 id="npu_torch_lib",
65 ),72 ),
66 ],73 ],
@@ -19,6 +19,7 @@ After Task 7:
19 - get_profiler uses is_npu() + profile.get('profiler') instead of19 - get_profiler uses is_npu() + profile.get('profiler') instead of
20 device_name() string compares.20 device_name() string compares.
21"""21"""
22+ 
22from __future__ import annotations23from __future__ import annotations
23 24 
24import subprocess25import subprocess
@@ -132,5 +132,5 @@ def test_maybe_promote_sets_promote_then_restores():
132 out = generate_golden_maybe_promote(testcase, [], _Switches(), "cpu", [np.ones(2)])132 out = generate_golden_maybe_promote(testcase, [], _Switches(), "cpu", [np.ones(2)])
133 133 
134 assert out == ["golden"]134 assert out == ["golden"]
135- assert seen["mode"] == "Promote" # 生成期间已抬升135+ assert seen["mode"] == "Promote" # 生成期间已抬升
136 assert testcase.golden_mode_override == "Enable" # 结束后恢复原值136 assert testcase.golden_mode_override == "Enable" # 结束后恢复原值
@@ -22,7 +22,6 @@ def _keeper():
22 22 
23 23 
24class TestFrameworkApiInfoKeeperCache:24class TestFrameworkApiInfoKeeperCache:
25- 
26 def setup_method(self):25 def setup_method(self):
27 _keeper().clear_cache()26 _keeper().clear_cache()
28 _MANUAL_OVERRIDES.clear()27 _MANUAL_OVERRIDES.clear()
@@ -35,10 +35,10 @@ def _register(api_name, params, source="test"):
35 FrameworkApiInfoKeeper().register(api_name, params, source=source)35 FrameworkApiInfoKeeper().register(api_name, params, source=source)
36 36 
37 37 
38-def _make_testcase(api_name, shapes, dtypes, attrs=None,38+def _make_testcase(api_name, shapes, dtypes, attrs=None, output_tensor_indexes=(), golden_api=None):
39- output_tensor_indexes=(), golden_api=None):
40 """构造一个 TestcaseE2e 测试用例。"""39 """构造一个 TestcaseE2e 测试用例。"""
41 from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e40 from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e
41+ 
42 case = TestcaseE2e()42 case = TestcaseE2e()
43 case.testcase_name = f"test_{api_name}"43 case.testcase_name = f"test_{api_name}"
44 case.api_name = api_name44 case.api_name = api_name
@@ -57,15 +57,16 @@ class TestGenerateGolden:
57 57 
58 def test_priority1_golden_api(self):58 def test_priority1_golden_api(self):
59 """优先级 1:指定 golden_api 时走 golden_api 路径。"""59 """优先级 1:指定 golden_api 时走 golden_api 路径。"""
60- _register("torch.golden_target", [60+ _register(
61- ParamInfo(name="input", type="Tensor"),61+ "torch.golden_target",
62- ])62+ [
63- case = _make_testcase(63+ ParamInfo(name="input", type="Tensor"),
64- "torch.base_api", ((3,),), ('float32',),64+ ],
65- golden_api="torch.golden_target")65+ )
66+ case = _make_testcase("torch.base_api", ((3,),), ("float32",), golden_api="torch.golden_target")
66 raw = [np.array([1.0, 2.0, 3.0], dtype=np.float32)]67 raw = [np.array([1.0, 2.0, 3.0], dtype=np.float32)]
67 68 
68- with patch('ttk.core_modules.framework_api.golden_generation.resolve_api') as mock_resolve:69+ with patch("ttk.core_modules.framework_api.golden_generation.resolve_api") as mock_resolve:
69 mock_fn = MagicMock(return_value=torch.tensor([10.0, 20.0, 30.0]))70 mock_fn = MagicMock(return_value=torch.tensor([10.0, 20.0, 30.0]))
70 mock_resolve.return_value = (mock_fn, False)71 mock_resolve.return_value = (mock_fn, False)
71 result = generate_golden(case, raw)72 result = generate_golden(case, raw)
@@ -74,15 +75,18 @@ class TestGenerateGolden:
74 75 
75 def test_priority2_custom_plugin(self):76 def test_priority2_custom_plugin(self):
76 """优先级 2:有自定义插件时走插件路径。"""77 """优先级 2:有自定义插件时走插件路径。"""
77- _register("torch.relu", [78+ _register(
78- ParamInfo(name="input", type="Tensor"),79+ "torch.relu",
79- ])80+ [
80- case = _make_testcase("torch.relu", ((3,),), ('float32',))81+ ParamInfo(name="input", type="Tensor"),
82+ ],
83+ )
84+ case = _make_testcase("torch.relu", ((3,),), ("float32",))
81 raw = [np.array([1.0, 2.0, 3.0], dtype=np.float32)]85 raw = [np.array([1.0, 2.0, 3.0], dtype=np.float32)]
82 case.tensors = raw86 case.tensors = raw
83 87 
84 fake_func = MagicMock(return_value=np.array([4.0, 5.0, 6.0]))88 fake_func = MagicMock(return_value=np.array([4.0, 5.0, 6.0]))
85- with patch('ttk.core_modules.framework_api.golden_generation.get_plugin_function') as mock_get:89+ with patch("ttk.core_modules.framework_api.golden_generation.get_plugin_function") as mock_get:
86 mock_get.return_value = fake_func90 mock_get.return_value = fake_func
87 result = generate_golden(case, raw)91 result = generate_golden(case, raw)
88 assert len(result) == 192 assert len(result) == 1
@@ -90,29 +94,35 @@ class TestGenerateGolden:
90 94 
91 def test_priority3_cpu_fallback(self):95 def test_priority3_cpu_fallback(self):
92 """优先级 3:无插件时回退到 CPU 执行。"""96 """优先级 3:无插件时回退到 CPU 执行。"""
93- _register("torch.relu", [97+ _register(
94- ParamInfo(name="input", type="Tensor"),98+ "torch.relu",
95- ])99+ [
96- case = _make_testcase("torch.relu", ((4,),), ('float32',))100+ ParamInfo(name="input", type="Tensor"),
101+ ],
102+ )
103+ case = _make_testcase("torch.relu", ((4,),), ("float32",))
97 raw = [np.array([-1.0, 0.0, 1.0, 2.0], dtype=np.float32)]104 raw = [np.array([-1.0, 0.0, 1.0, 2.0], dtype=np.float32)]
98 105 
99- with patch('ttk.core_modules.framework_api.golden_generation.get_plugin_function') as mock_get:106+ with patch("ttk.core_modules.framework_api.golden_generation.get_plugin_function") as mock_get:
100 mock_get.return_value = None107 mock_get.return_value = None
101 result = generate_golden(case, raw)108 result = generate_golden(case, raw)
102 assert len(result) == 1109 assert len(result) == 1
103- np.testing.assert_array_almost_equal(110+ np.testing.assert_array_almost_equal(result[0], [0.0, 0.0, 1.0, 2.0])
104- result[0], [0.0, 0.0, 1.0, 2.0])
105 111 
106 def test_priority3_cpu_fails_raises(self):112 def test_priority3_cpu_fails_raises(self):
107 """CPU 回退失败时抛出 RuntimeError。"""113 """CPU 回退失败时抛出 RuntimeError。"""
108- _register("torch.noexist_api", [114+ _register(
109- ParamInfo(name="input", type="Tensor"),115+ "torch.noexist_api",
110- ])116+ [
111- case = _make_testcase("torch.noexist_api", ((3,),), ('float32',))117+ ParamInfo(name="input", type="Tensor"),
118+ ],
119+ )
120+ case = _make_testcase("torch.noexist_api", ((3,),), ("float32",))
112 raw = [np.array([1.0], dtype=np.float32)]121 raw = [np.array([1.0], dtype=np.float32)]
113 122 
114- with patch('ttk.core_modules.framework_api.golden_generation.get_plugin_function') as mock_get, \123+ with patch("ttk.core_modules.framework_api.golden_generation.get_plugin_function") as mock_get, patch(
115- patch('ttk.core_modules.framework_api.golden_generation._run_api_on_cpu') as mock_cpu:124+ "ttk.core_modules.framework_api.golden_generation._run_api_on_cpu"
125+ ) as mock_cpu:
116 mock_get.return_value = None126 mock_get.return_value = None
117 mock_cpu.side_effect = RuntimeError("no CPU impl")127 mock_cpu.side_effect = RuntimeError("no CPU impl")
118 with pytest.raises(RuntimeError, match="cannot run on CPU"):128 with pytest.raises(RuntimeError, match="cannot run on CPU"):
@@ -120,18 +130,18 @@ class TestGenerateGolden:
120 130 
121 def test_with_nested_distribution(self):131 def test_with_nested_distribution(self):
122 """嵌套分布(tuple of Tensors)正确传递。"""132 """嵌套分布(tuple of Tensors)正确传递。"""
123- _register("torch.stack_test", [133+ _register(
124- ParamInfo(name="tensors", type="tuple of Tensors"),
125- ])
126- case = _make_testcase(
127 "torch.stack_test",134 "torch.stack_test",
128- (((3,), (3,)),),135+ [
129- (('float32', 'float32'),))136+ ParamInfo(name="tensors", type="tuple of Tensors"),
137+ ],
138+ )
139+ case = _make_testcase("torch.stack_test", (((3,), (3,)),), (("float32", "float32"),))
130 a = np.array([1.0, 2.0, 3.0], dtype=np.float32)140 a = np.array([1.0, 2.0, 3.0], dtype=np.float32)
131 b = np.array([4.0, 5.0, 6.0], dtype=np.float32)141 b = np.array([4.0, 5.0, 6.0], dtype=np.float32)
132 raw = [a, b]142 raw = [a, b]
133 143 
134- with patch('ttk.core_modules.framework_api.golden_generation.resolve_api') as mock_resolve:144+ with patch("ttk.core_modules.framework_api.golden_generation.resolve_api") as mock_resolve:
135 mock_fn = MagicMock(return_value=torch.zeros((2, 3)))145 mock_fn = MagicMock(return_value=torch.zeros((2, 3)))
136 mock_resolve.return_value = (mock_fn, False)146 mock_resolve.return_value = (mock_fn, False)
137 generate_golden(case, raw)147 generate_golden(case, raw)
@@ -15,6 +15,7 @@
15- _build(fw, name, profile) fail-fast validates torch_lib + profiler before15- _build(fw, name, profile) fail-fast validates torch_lib + profiler before
16 instantiating/injecting the backend.16 instantiating/injecting the backend.
17"""17"""
18+ 
18from __future__ import annotations19from __future__ import annotations
19 20 
20from ttk.config.loader import get_hardware_config, load_config21from ttk.config.loader import get_hardware_config, load_config
@@ -15,6 +15,7 @@ ImportError / RuntimeError / AttributeError) returning False + warning.
15get_backend(force_cpu=True) -> CpuTorchBackend; else iterate _hw_profiles15get_backend(force_cpu=True) -> CpuTorchBackend; else iterate _hw_profiles
16in order, _probe each non-cpu profile, build first hit; cpu fallback.16in order, _probe each non-cpu profile, build first hit; cpu fallback.
17"""17"""
18+ 
18from __future__ import annotations19from __future__ import annotations
19 20 
20from ttk.core_modules.framework_api.backends import _probe, get_backend21from ttk.core_modules.framework_api.backends import _probe, get_backend
@@ -41,8 +41,7 @@ def test_parse_stdout_complex32():
41 41 
42 42 
43def test_parse_stdout_complex64():43def test_parse_stdout_complex64():
44- arr = np.array([[1 + 2j, 3 + 4j, 5 + 6j], [7 + 8j, 9 + 10j, 11 + 12j]],44+ arr = np.array([[1 + 2j, 3 + 4j, 5 + 6j], [7 + 8j, 9 + 10j, 11 + 12j]], dtype=np.complex64)
45- dtype=np.complex64)
46 data = _make_geir_output([arr])45 data = _make_geir_output([arr])
47 result = _parse_stdout(data, ["complex64"], [[2, 3]])46 result = _parse_stdout(data, ["complex64"], [[2, 3]])
48 assert len(result) == 147 assert len(result) == 1
@@ -52,8 +51,7 @@ def test_parse_stdout_complex64():
52 51 
53 52 
54def test_parse_stdout_complex128():53def test_parse_stdout_complex128():
55- arr = np.array([[1 + 2j, 3 + 4j, 5 + 6j], [7 + 8j, 9 + 10j, 11 + 12j]],54+ arr = np.array([[1 + 2j, 3 + 4j, 5 + 6j], [7 + 8j, 9 + 10j, 11 + 12j]], dtype=np.complex128)
56- dtype=np.complex128)
57 data = _make_geir_output([arr])55 data = _make_geir_output([arr])
58 result = _parse_stdout(data, ["complex128"], [[2, 3]])56 result = _parse_stdout(data, ["complex128"], [[2, 3]])
59 assert len(result) == 157 assert len(result) == 1
@@ -29,17 +29,27 @@ from ttk.core_modules.infershape.format_transformation import (
29)29)
30 30 
31 31 
32-@pytest.mark.parametrize("dtype,expected", [32+@pytest.mark.parametrize(
33- ("float16", 16), ("int8", 32), ("int64", 4), ("bogus", 16),33+ "dtype,expected",
34-])34+ [
35+ ("float16", 16),
36+ ("int8", 32),
37+ ("int64", 4),
38+ ("bogus", 16),
39+ ],
40+)
35def test_align_factor(dtype, expected):41def test_align_factor(dtype, expected):
36 assert align_factor(dtype) == expected42 assert align_factor(dtype) == expected
37 43 
38 44 
39-@pytest.mark.parametrize("dtype,target,expected", [45+@pytest.mark.parametrize(
40- ("float16", [1, 2, 8, 8, 24], 24),46+ "dtype,target,expected",
41- ("int8", None, 32),47+ [
42-], ids=["target-shape", "fallback"])48+ ("float16", [1, 2, 8, 8, 24], 24),
49+ ("int8", None, 32),
50+ ],
51+ ids=["target-shape", "fallback"],
52+)
43def test_determine_c0(dtype, target, expected):53def test_determine_c0(dtype, target, expected):
44 assert determine_c0(dtype, target) == expected54 assert determine_c0(dtype, target) == expected
45 55 
@@ -48,26 +58,37 @@ def test_determine_c0_accepts_numpy_dtype():
48 assert determine_c0(np.dtype("float16")) == 1658 assert determine_c0(np.dtype("float16")) == 16
49 59 
50 60 
51-@pytest.mark.parametrize("shape,fmt,expected", [61+@pytest.mark.parametrize(
52- ((1, 32, 8, 8), "NCHW", True),62+ "shape,fmt,expected",
53- ((1, 32, 8), "NCHW", False),63+ [
54-], ids=["ok", "bad-rank"])64+ ((1, 32, 8, 8), "NCHW", True),
65+ ((1, 32, 8), "NCHW", False),
66+ ],
67+ ids=["ok", "bad-rank"],
68+)
55def test_is_nchw_like(shape, fmt, expected):69def test_is_nchw_like(shape, fmt, expected):
56 assert is_nchw_like(shape, fmt) is expected70 assert is_nchw_like(shape, fmt) is expected
57 71 
58 72 
59-@pytest.mark.parametrize("shape,fmt,expected", [73+@pytest.mark.parametrize(
60- ((1, 2, 32, 8, 8), "NCDHW", True),74+ "shape,fmt,expected",
61- ((1, 32, 8, 8), "NCDHW", False),75+ [
62-], ids=["ok", "bad-rank"])76+ ((1, 2, 32, 8, 8), "NCDHW", True),
77+ ((1, 32, 8, 8), "NCDHW", False),
78+ ],
79+ ids=["ok", "bad-rank"],
80+)
63def test_is_ndchw_like(shape, fmt, expected):81def test_is_ndchw_like(shape, fmt, expected):
64 assert is_ndchw_like(shape, fmt) is expected82 assert is_ndchw_like(shape, fmt) is expected
65 83 
66 84 
67-@pytest.mark.parametrize("shape,fmt,expected", [85+@pytest.mark.parametrize(
68- ((1, 32, 8, 8), "NCHW", (1, 2, 8, 8, 16)),86+ "shape,fmt,expected",
69- ((1, 8, 8, 32), "NHWC", (1, 2, 8, 8, 16)),87+ [
70-])88+ ((1, 32, 8, 8), "NCHW", (1, 2, 8, 8, 16)),
89+ ((1, 8, 8, 32), "NHWC", (1, 2, 8, 8, 16)),
90+ ],
91+)
71def test_nd_shape2fhd_shape(shape, fmt, expected):92def test_nd_shape2fhd_shape(shape, fmt, expected):
72 assert nd_shape2fhd_shape(shape, fmt) == expected93 assert nd_shape2fhd_shape(shape, fmt) == expected
73 94 
@@ -101,12 +122,15 @@ def test_nd_to_fractal_nz_4d_and_back():
101 np.testing.assert_array_equal(nz2nd(fractal, (2, 5, 4, 7)), data)122 np.testing.assert_array_equal(nz2nd(fractal, (2, 5, 4, 7)), data)
102 123 
103 124 
104-@pytest.mark.parametrize("ori,target,expected", [125+@pytest.mark.parametrize(
105- ("NCHW", "NC1HWC0", True),126+ "ori,target,expected",
106- ("ND", "FRACTAL_NZ", True),127+ [
107- ("BOGUS", "NC1HWC0", False),128+ ("NCHW", "NC1HWC0", True),
108- ("NCHW", "BOGUS", False),129+ ("ND", "FRACTAL_NZ", True),
109-])130+ ("BOGUS", "NC1HWC0", False),
131+ ("NCHW", "BOGUS", False),
132+ ],
133+)
110def test_is_transformable(ori, target, expected):134def test_is_transformable(ori, target, expected):
111 assert is_transformable(ori, target) is expected135 assert is_transformable(ori, target) is expected
112 136 
@@ -55,29 +55,38 @@ def test_elewise_invalid_shape_type():
55 elewise(("x", "y"))55 elewise(("x", "y"))
56 56 
57 57 
58-@pytest.mark.parametrize("shape,axes,expected", [58+@pytest.mark.parametrize(
59- ((2, 3, 4), (1,), (2, 1, 4)),59+ "shape,axes,expected",
60- ((2, 3, 4), None, (-1, -1, -1)),60+ [
61- ((1, 3, 4), None, (1, -1, -1)),61+ ((2, 3, 4), (1,), (2, 1, 4)),
62-])62+ ((2, 3, 4), None, (-1, -1, -1)),
63+ ((1, 3, 4), None, (1, -1, -1)),
64+ ],
65+)
63def test_reduce(shape, axes, expected):66def test_reduce(shape, axes, expected):
64 assert reduce(shape, axes) == expected67 assert reduce(shape, axes) == expected
65 68 
66 69 
67-@pytest.mark.parametrize("shape,expected", [70+@pytest.mark.parametrize(
68- ((4, 8), ((4, 4), (8, 8))),71+ "shape,expected",
69- ((-1, 8, -2), ((1, None), (8, 8), (1, None))),72+ [
70- (None, None),73+ ((4, 8), ((4, 4), (8, 8))),
71-])74+ ((-1, 8, -2), ((1, None), (8, 8), (1, None))),
75+ (None, None),
76+ ],
77+)
72def test_range_inference(shape, expected):78def test_range_inference(shape, expected):
73 assert range_inference(shape) == expected79 assert range_inference(shape) == expected
74 80 
75 81 
76-@pytest.mark.parametrize("mode,shapes,args,expected", [82+@pytest.mark.parametrize(
77- ("ELEWISE", ((4, 8), (4, 8)), (1, None), ((4, 8),)),83+ "mode,shapes,args,expected",
78- ("REDUCE", ((2, 3, 4),), ((1,), 1, None), ((2, 1, 4),)),84+ [
79- ("RANGE", ((4, -1),), (None,), (((4, 4), (1, None)),)),85+ ("ELEWISE", ((4, 8), (4, 8)), (1, None), ((4, 8),)),
80-])86+ ("REDUCE", ((2, 3, 4),), ((1,), 1, None), ((2, 1, 4),)),
87+ ("RANGE", ((4, -1),), (None,), (((4, 4), (1, None)),)),
88+ ],
89+)
81def test_shape_inference_dispatch(mode, shapes, args, expected):90def test_shape_inference_dispatch(mode, shapes, args, expected):
82 assert shape_inference(shapes, args, mode) == expected91 assert shape_inference(shapes, args, mode) == expected
83 92 
@@ -87,14 +96,17 @@ def test_shape_inference_unknown_mode():
87 shape_inference(((4,),), (None,), "BOGUS")96 shape_inference(((4,),), (None,), "BOGUS")
88 97 
89 98 
90-@pytest.mark.parametrize("shape,cur,target,expected", [99+@pytest.mark.parametrize(
91- ((1, 8, 8, 32), "NHWC", "NC1HWC0", (1, 2, 8, 8, 16)),100+ "shape,cur,target,expected",
92- ((1, 32, 8, 8), "NCHW", "NC1HWC0", (1, 2, 8, 8, 16)),101+ [
93- ((1, 8, 32), "NWC", "NC1HWC0", (1, 2, 1, 8, 16)),102+ ((1, 8, 8, 32), "NHWC", "NC1HWC0", (1, 2, 8, 8, 16)),
94- ((1, 2, 8, 8, 32), "NDHWC", "NDC1HWC0", (1, 2, 2, 8, 8, 16)),103+ ((1, 32, 8, 8), "NCHW", "NC1HWC0", (1, 2, 8, 8, 16)),
95- ((2, 3), "NHWC", "FRACTAL_NZ", (1, 1, 16, 16)),104+ ((1, 8, 32), "NWC", "NC1HWC0", (1, 2, 1, 8, 16)),
96- ((1, 5, 2, 3), "NCHW", "FRACTAL_NZ", (1, 5, 1, 1, 16, 16)),105+ ((1, 2, 8, 8, 32), "NDHWC", "NDC1HWC0", (1, 2, 2, 8, 8, 16)),
97-])106+ ((2, 3), "NHWC", "FRACTAL_NZ", (1, 1, 16, 16)),
107+ ((1, 5, 2, 3), "NCHW", "FRACTAL_NZ", (1, 5, 1, 1, 16, 16)),
108+ ],
109+)
98def test_transform_formats(shape, cur, target, expected):110def test_transform_formats(shape, cur, target, expected):
99 assert transform(shape, cur, target) == expected111 assert transform(shape, cur, target) == expected
100 112 
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""kernel 算子 manual_data prepare/replay 与存储输入/golden 的单元测试。"""10"""kernel 算子 manual_data prepare/replay 与存储输入/golden 的单元测试。"""
11+ 
11from types import SimpleNamespace12from types import SimpleNamespace
12from unittest.mock import MagicMock13from unittest.mock import MagicMock
13 14 
@@ -134,9 +135,7 @@ def test_kernel_prepare_snapshots_input_and_stops_before_device(monkeypatch, tmp
134 assert result is prepared135 assert result is prepared
135 assert not device_execution.called136 assert not device_execution.called
136 loaded = ManualDataStore(tmp_path).load_case(case, "kernel")137 loaded = ManualDataStore(tmp_path).load_case(case, "kernel")
137- np.testing.assert_array_equal(138+ np.testing.assert_array_equal(loaded.inputs[0], np.array([1.0, 2.0], np.float32))
138- loaded.inputs[0], np.array([1.0, 2.0], np.float32)
139- )
140 np.testing.assert_array_equal(139 np.testing.assert_array_equal(
141 loaded.load_goldens(140 loaded.load_goldens(
142 shapes=case.flat_output_shapes,141 shapes=case.flat_output_shapes,
@@ -15,8 +15,10 @@ class AddDictImplSpec:
15 15 
16 class AddTorchImpl:16 class AddTorchImpl:
17 """torch impl — called on the XPU server (spec mode)."""17 """torch impl — called on the XPU server (spec mode)."""
18+ 
18 def __call__(self, x, y, **kwargs):19 def __call__(self, x, y, **kwargs):
19 import torch20 import torch
21+ 
20 return [torch.add(x, y)]22 return [torch.add(x, y)]
21 23 
22 third_party = {"torch": AddTorchImpl}24 third_party = {"torch": AddTorchImpl}
@@ -15,8 +15,10 @@ class AddTfImplSpec:
15 15 
16 class AddTfImpl:16 class AddTfImpl:
17 """tf impl — called on the XPU server (spec mode)."""17 """tf impl — called on the XPU server (spec mode)."""
18+ 
18 def __call__(self, x, y, **kwargs):19 def __call__(self, x, y, **kwargs):
19 import tensorflow as tf20 import tensorflow as tf
21+ 
20 return [tf.raw_ops.Add(x=x, y=y)]22 return [tf.raw_ops.Add(x=x, y=y)]
21 23 
22 third_party = {"tf": AddTfImpl}24 third_party = {"tf": AddTfImpl}
@@ -45,16 +45,18 @@ from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn
45# --------------------------------------------------------------------------- #45# --------------------------------------------------------------------------- #
46class _AclnnNegScale:46class _AclnnNegScale:
47 """Partial split: ``__init__`` takes a config attr, ``__call__`` takes the input tensor."""47 """Partial split: ``__init__`` takes a config attr, ``__call__`` takes the input tensor."""
48+ 
48 def __init__(self, *, scale):49 def __init__(self, *, scale):
49 self.scale = scale50 self.scale = scale
50 51 
51- def __call__(self, x): # noqa: N803 (short param mirrors real plugins)52+ def __call__(self, x): # noqa: N803 (short param mirrors real plugins)
52 return [x * self.scale]53 return [x * self.scale]
53 54 
54 55 
55class _AclnnDouble:56class _AclnnDouble:
56 """No custom ``__init__`` — exercises the ``cls()`` guard in ``_invoke_class``."""57 """No custom ``__init__`` — exercises the ``cls()`` guard in ``_invoke_class``."""
57- def __call__(self, x): # noqa: N80358+ 
59+ def __call__(self, x): # noqa: N803
58 return [x * 2]60 return [x * 2]
59 61 
60 62 
@@ -65,7 +67,7 @@ def _mock_switches():
65 sw = MagicMock()67 sw = MagicMock()
66 sw.dev_plat = "Ascend910B2"68 sw.dev_plat = "Ascend910B2"
67 sw.short_soc_version = "Ascend910B"69 sw.short_soc_version = "Ascend910B"
68- sw.golden_mode = "Enable" # not Promote → _golden_mode is a no-op70+ sw.golden_mode = "Enable" # not Promote → _golden_mode is a no-op
69 sw.plugin_path = None71 sw.plugin_path = None
70 sw.overflow_mode = 072 sw.overflow_mode = 0
71 return sw73 return sw
@@ -108,60 +110,50 @@ def _mock_env(monkeypatch):
108 monkeypatch.delenv("ASCEND_OPP_PATH", raising=False)110 monkeypatch.delenv("ASCEND_OPP_PATH", raising=False)
109 111 
110 112 
111-@patch('ttk.core_modules.npu.op_api.golden_generation.OpApiInfoKeeper')113+@patch("ttk.core_modules.npu.op_api.golden_generation.OpApiInfoKeeper")
112-@patch('ttk.core_modules.npu.op_api.golden_generation.get_global_storage')114+@patch("ttk.core_modules.npu.op_api.golden_generation.get_global_storage")
113-@patch('ttk.core_modules.npu.op_api.golden_generation.get_plugin_function')115+@patch("ttk.core_modules.npu.op_api.golden_generation.get_plugin_function")
114class TestAclnnClassGoldenDispatch:116class TestAclnnClassGoldenDispatch:
115 """class-form golden (isinstance(golden_func, type)) → _invoke_class end-to-end."""117 """class-form golden (isinstance(golden_func, type)) → _invoke_class end-to-end."""
116 118 
117- def test_partial_split_init_takes_attr_call_takes_input(self, mock_get_plugin, mock_sw,119+ def test_partial_split_init_takes_attr_call_takes_input(self, mock_get_plugin, mock_sw, mock_op_info):
118- mock_op_info):
119 """``_AclnnNegScale(scale=3)`` instantiated; ``__call__(x)`` returns ``x*3``.120 """``_AclnnNegScale(scale=3)`` instantiated; ``__call__(x)`` returns ``x*3``.
120 121 
121 ``scale`` here flows in as a *scalar* (aclnn convention), proving the122 ``scale`` here flows in as a *scalar* (aclnn convention), proving the
122 ``_named_values`` scalar-name merge feeds ``__init__``'s keyword-only ``scale``.123 ``_named_values`` scalar-name merge feeds ``__init__``'s keyword-only ``scale``.
123 """124 """
124- mock_get_plugin.return_value = _AclnnNegScale # → isinstance(type) branch125+ mock_get_plugin.return_value = _AclnnNegScale # → isinstance(type) branch
125 mock_sw.return_value = _mock_switches()126 mock_sw.return_value = _mock_switches()
126 # OpApiInfo: one input tensor named "x", one scalar named "scale".127 # OpApiInfo: one input tensor named "x", one scalar named "scale".
127- mock_op_info.return_value.info_of.return_value = _make_op_api_info(128+ mock_op_info.return_value.info_of.return_value = _make_op_api_info(tensor_names=["x"], scalar_names=["scale"])
128- tensor_names=["x"], scalar_names=["scale"])
129 129 
130 inp = np.array([1.0, 2.0, 3.0], dtype="float32")130 inp = np.array([1.0, 2.0, 3.0], dtype="float32")
131- case = _make_testcase("aclnnNegScale",131+ case = _make_testcase("aclnnNegScale", tensors=[inp], attributes={}, scalars=[3])
132- tensors=[inp],
133- attributes={},
134- scalars=[3])
135 132 
136 golden = GoldenGenerator(case)._generate_golden()133 golden = GoldenGenerator(case)._generate_golden()
137 134 
138 assert len(golden) >= 1135 assert len(golden) >= 1
139- assert isinstance(golden[0], np.ndarray), \136+ assert isinstance(golden[0], np.ndarray), f"expected ndarray, got sentinel/string: {golden[0]!r}"
140- f"expected ndarray, got sentinel/string: {golden[0]!r}"
141 np.testing.assert_array_equal(golden[0], np.array([3.0, 6.0, 9.0], dtype="float32"))137 np.testing.assert_array_equal(golden[0], np.array([3.0, 6.0, 9.0], dtype="float32"))
142 mock_get_plugin.assert_called_once()138 mock_get_plugin.assert_called_once()
143 139 
144- def test_dispatch_routes_class_not_callable_path(self, mock_get_plugin, mock_sw,140+ def test_dispatch_routes_class_not_callable_path(self, mock_get_plugin, mock_sw, mock_op_info):
145- mock_op_info):
146 """A class is ``Callable`` but MUST route to ``_invoke_class`` (not the custom-callable141 """A class is ``Callable`` but MUST route to ``_invoke_class`` (not the custom-callable
147 branch that would do ``golden_func(*args, **kwargs)`` via the param plan, which needs a142 branch that would do ``golden_func(*args, **kwargs)`` via the param plan, which needs a
148 real OpApiInfo + tensor plan). Guards the ``isinstance(type)`` ordering in143 real OpApiInfo + tensor plan). Guards the ``isinstance(type)`` ordering in
149 ``_invoke_golden``: a class leaking to the custom branch would fail building the plan."""144 ``_invoke_golden``: a class leaking to the custom branch would fail building the plan."""
150 mock_get_plugin.return_value = _AclnnNegScale145 mock_get_plugin.return_value = _AclnnNegScale
151 mock_sw.return_value = _mock_switches()146 mock_sw.return_value = _mock_switches()
152- mock_op_info.return_value.info_of.return_value = _make_op_api_info(147+ mock_op_info.return_value.info_of.return_value = _make_op_api_info(tensor_names=["x"], scalar_names=["scale"])
153- tensor_names=["x"], scalar_names=["scale"])
154 148 
155 inp = np.array([1.0, 2.0, 3.0], dtype="float32")149 inp = np.array([1.0, 2.0, 3.0], dtype="float32")
156- case = _make_testcase("aclnnNegScale",150+ case = _make_testcase("aclnnNegScale", tensors=[inp], attributes={}, scalars=[4])
157- tensors=[inp],
158- attributes={},
159- scalars=[4])
160 151 
161 golden = GoldenGenerator(case)._generate_golden()152 golden = GoldenGenerator(case)._generate_golden()
162 153 
163 # If mis-routed to the custom-callable branch, golden[0] would be the154 # If mis-routed to the custom-callable branch, golden[0] would be the
164 # string "GOLDEN_FAILURE"; the class path yields a numeric ndarray.155 # string "GOLDEN_FAILURE"; the class path yields a numeric ndarray.
165- assert isinstance(golden[0], np.ndarray), \156+ assert isinstance(golden[0], np.ndarray), (
166 f"class golden must hit _invoke_class, not the custom-callable branch: {golden[0]!r}"157 f"class golden must hit _invoke_class, not the custom-callable branch: {golden[0]!r}"
158+ )
167 np.testing.assert_array_equal(golden[0], np.array([4.0, 8.0, 12.0], dtype="float32"))159 np.testing.assert_array_equal(golden[0], np.array([4.0, 8.0, 12.0], dtype="float32"))
@@ -45,11 +45,11 @@ def _run(context, spec_attrs, builtin_compare=None):
45 return spec_attrs.get(attr_name)45 return spec_attrs.get(attr_name)
46 46 
47 storage = SimpleNamespace(compare_method=None, plugin_path="/tmp/test-plugin")47 storage = SimpleNamespace(compare_method=None, plugin_path="/tmp/test-plugin")
48- with patch("ttk.core_modules.npu.op_api.comparison.get_global_storage", return_value=storage), \48+ with patch("ttk.core_modules.npu.op_api.comparison.get_global_storage", return_value=storage), patch(
49- patch("ttk.core_modules.npu.op_api.comparison.get_spec_attr", side_effect=get_attr), \49+ "ttk.core_modules.npu.op_api.comparison.get_spec_attr", side_effect=get_attr
50- patch.object(Comparator, "_output_bytes_to_tensors"), \50+ ), patch.object(Comparator, "_output_bytes_to_tensors"), patch(
51- patch("ttk.core_modules.npu.op_api.comparison.compare",51+ "ttk.core_modules.npu.op_api.comparison.compare", side_effect=builtin_compare
52- side_effect=builtin_compare) as fallback:52+ ) as fallback:
53 result = Comparator(context).compare()53 result = Comparator(context).compare()
54 return result, fallback54 return result, fallback
55 55 
@@ -128,9 +128,9 @@ __spec__ = {
128 )128 )
129 storage = SimpleNamespace(compare_method=None, plugin_path=str(tmp_path))129 storage = SimpleNamespace(compare_method=None, plugin_path=str(tmp_path))
130 130 
131- with patch("ttk.core_modules.npu.op_api.comparison.get_global_storage", return_value=storage), \131+ with patch("ttk.core_modules.npu.op_api.comparison.get_global_storage", return_value=storage), patch.object(
132- patch.object(Comparator, "_output_bytes_to_tensors"), \132+ Comparator, "_output_bytes_to_tensors"
133- patch("ttk.core_modules.npu.op_api.comparison.compare") as fallback:133+ ), patch("ttk.core_modules.npu.op_api.comparison.compare") as fallback:
134 result = Comparator(context).compare()134 result = Comparator(context).compare()
135 135 
136 assert result.precision == "SPEC_LOADED"136 assert result.precision == "SPEC_LOADED"
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""Tests for TestcaseAclnn flatten_tensors/scalars derived properties."""10"""Tests for TestcaseAclnn flatten_tensors/scalars derived properties."""
11+ 
11import torch12import torch
12 13 
13from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn14from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn
@@ -17,8 +18,8 @@ def test_flatten_tensors_derives_from_tensors():
17 """flatten_tensors 是 deep_flatten(tensors) 的派生缓存,不是独立赋值的 plain attr。"""18 """flatten_tensors 是 deep_flatten(tensors) 的派生缓存,不是独立赋值的 plain attr。"""
18 case = TestcaseAclnn()19 case = TestcaseAclnn()
19 t1, t2, t3 = torch.tensor([1.0]), torch.tensor([2.0]), torch.tensor([3.0])20 t1, t2, t3 = torch.tensor([1.0]), torch.tensor([2.0]), torch.tensor([3.0])
20- case.tensors = ((t1, t2), t3) # 嵌套:TensorList (t1,t2) + 单 t321+ case.tensors = ((t1, t2), t3) # 嵌套:TensorList (t1,t2) + 单 t3
21 flat = case.flatten_tensors22 flat = case.flatten_tensors
22- assert list(flat) == [t1, t2, t3] # deep_flatten 展平,顺序保持23+ assert list(flat) == [t1, t2, t3] # deep_flatten 展平,顺序保持
23 # 缓存生效:再读同一对象24 # 缓存生效:再读同一对象
24 assert case.flatten_tensors is flat25 assert case.flatten_tensors is flat
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""Tests for ACLNN golden promote dtype context."""10"""Tests for ACLNN golden promote dtype context."""
11+ 
11from collections import OrderedDict12from collections import OrderedDict
12from unittest.mock import MagicMock, patch13from unittest.mock import MagicMock, patch
13 14 
@@ -54,6 +55,7 @@ def _make_testcase(api_name, tensors, tensor_dtypes):
54 55 
55class _RecordDtype:56class _RecordDtype:
56 """class-form golden:无自定义 __init__(走 cls() 守卫);__call__ 记录收到的 dtype。"""57 """class-form golden:无自定义 __init__(走 cls() 守卫);__call__ 记录收到的 dtype。"""
58+ 
57 received = {}59 received = {}
58 60 
59 def __call__(self, x):61 def __call__(self, x):
@@ -67,9 +69,9 @@ def _mock_env(monkeypatch):
67 monkeypatch.delenv(k, raising=False)69 monkeypatch.delenv(k, raising=False)
68 70 
69 71 
70-@patch('ttk.core_modules.npu.op_api.golden_generation.OpApiInfoKeeper')72+@patch("ttk.core_modules.npu.op_api.golden_generation.OpApiInfoKeeper")
71-@patch('ttk.core_modules.npu.op_api.golden_generation.get_global_storage')73+@patch("ttk.core_modules.npu.op_api.golden_generation.get_global_storage")
72-@patch('ttk.core_modules.npu.op_api.golden_generation.get_plugin_function')74+@patch("ttk.core_modules.npu.op_api.golden_generation.get_plugin_function")
73class TestAclnnPromote:75class TestAclnnPromote:
74 def test_promote_float16_to_float32_and_restore(self, mock_get_plugin, mock_sw, mock_op_info):76 def test_promote_float16_to_float32_and_restore(self, mock_get_plugin, mock_sw, mock_op_info):
75 """Promote: float16 输入 → golden(class __call__)收 float32;退出后 ctx 还原 float16。"""77 """Promote: float16 输入 → golden(class __call__)收 float32;退出后 ctx 还原 float16。"""
@@ -21,16 +21,17 @@ from ttk.core_modules.npu.op.comparison import comparing
21 21 
22@pytest.fixture(autouse=True)22@pytest.fixture(autouse=True)
23def _mock_outputs_to_numpy():23def _mock_outputs_to_numpy():
24- with patch('ttk.core_modules.npu.op.comparison.__outputs_to_numpy_arrays',24+ with patch("ttk.core_modules.npu.op.comparison.__outputs_to_numpy_arrays", lambda outputs, dtypes: None):
25- lambda outputs, dtypes: None):
26 yield25 yield
27 26 
28 27 
29def _call_comparing(mock_compare_side_effect):28def _call_comparing(mock_compare_side_effect):
30- with patch('ttk.core_modules.comparison.custom.compare') as mock_compare:29+ with patch("ttk.core_modules.comparison.custom.compare") as mock_compare:
31 mock_compare.side_effect = mock_compare_side_effect30 mock_compare.side_effect = mock_compare_side_effect
32 result = comparing(31 result = comparing(
33- "dyn_k", "cst_k", "bin_k",32+ "dyn_k",
33+ "cst_k",
34+ "bin_k",
34 (np.array([1.0]),),35 (np.array([1.0]),),
35 (np.array([1.0]),),36 (np.array([1.0]),),
36 (np.array([1.0]),),37 (np.array([1.0]),),
@@ -42,33 +43,38 @@ def _call_comparing(mock_compare_side_effect):
42 43 
43 44 
44class TestComparingPass:45class TestComparingPass:
45- 
46 def test_all_pass(self):46 def test_all_pass(self):
47- result, _ = _call_comparing([47+ result, _ = _call_comparing(
48- ("1.0", "", True, {}),48+ [
49- ("1.0", "", True, {}),49+ ("1.0", "", True, {}),
50- ("1.0", "", True, {}),50+ ("1.0", "", True, {}),
51- ])51+ ("1.0", "", True, {}),
52+ ]
53+ )
52 assert result.passed == "PASS"54 assert result.passed == "PASS"
53 assert result.dyn_precision == "1.0"55 assert result.dyn_precision == "1.0"
54 assert result.cst_precision == "1.0"56 assert result.cst_precision == "1.0"
55 assert result.bin_precision == "1.0"57 assert result.bin_precision == "1.0"
56 58 
57 def test_dyn_fail_means_overall_fail(self):59 def test_dyn_fail_means_overall_fail(self):
58- result, _ = _call_comparing([60+ result, _ = _call_comparing(
59- ("0.5", "", False, {}),61+ [
60- ("1.0", "", True, {}),62+ ("0.5", "", False, {}),
61- ("1.0", "", True, {}),63+ ("1.0", "", True, {}),
62- ])64+ ("1.0", "", True, {}),
65+ ]
66+ )
63 assert result.passed == "FAIL"67 assert result.passed == "FAIL"
64 assert result.dyn_precision == "0.5"68 assert result.dyn_precision == "0.5"
65 69 
66 def test_3_comparisons_made(self):70 def test_3_comparisons_made(self):
67- result, mock_compare = _call_comparing([71+ result, mock_compare = _call_comparing(
68- ("1.0", "", True, {}),72+ [
69- ("1.0", "", True, {}),73+ ("1.0", "", True, {}),
70- ("1.0", "", True, {}),74+ ("1.0", "", True, {}),
71- ])75+ ("1.0", "", True, {}),
76+ ]
77+ )
72 assert mock_compare.call_count == 378 assert mock_compare.call_count == 3
73 79 
74 def test_comparison_order(self):80 def test_comparison_order(self):
@@ -89,10 +95,18 @@ class TestComparingPass:
89 bin_out = np.array([1.0])95 bin_out = np.array([1.0])
90 golden = np.array([1.0])96 golden = np.array([1.0])
91 97 
92- with patch('ttk.core_modules.comparison.custom.compare', side_effect=track_compare):98+ with patch("ttk.core_modules.comparison.custom.compare", side_effect=track_compare):
93- comparing("dyn_k", "cst_k", "bin_k",99+ comparing(
94- (dyn_out,), (cst_out,), (bin_out,), (golden,),100+ "dyn_k",
95- ("float32",), standards=[MagicMock()])101+ "cst_k",
102+ "bin_k",
103+ (dyn_out,),
104+ (cst_out,),
105+ (bin_out,),
106+ (golden,),
107+ ("float32",),
108+ standards=[MagicMock()],
109+ )
96 110 
97 assert call_log == [111 assert call_log == [
98 "dyn_vs_golden",112 "dyn_vs_golden",
@@ -101,9 +115,11 @@ class TestComparingPass:
101 ]115 ]
102 116 
103 def test_exception_returns_compare_failure(self):117 def test_exception_returns_compare_failure(self):
104- with patch('ttk.core_modules.comparison.custom.compare', side_effect=RuntimeError("boom")):118+ with patch("ttk.core_modules.comparison.custom.compare", side_effect=RuntimeError("boom")):
105 result = comparing(119 result = comparing(
106- "dyn_k", "cst_k", "bin_k",120+ "dyn_k",
121+ "cst_k",
122+ "bin_k",
107 (np.array([1.0]),),123 (np.array([1.0]),),
108 (np.array([1.0]),),124 (np.array([1.0]),),
109 (np.array([1.0]),),125 (np.array([1.0]),),
@@ -32,7 +32,7 @@ from ttk.core_modules.npu.op import input_generation as _in_gen
32from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp32from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
33from ttk.utilities.classes import SWITCHES33from ttk.utilities.classes import SWITCHES
34 34 
35-_gen_input = getattr(_in_gen, '__gen_input')35+_gen_input = getattr(_in_gen, "__gen_input")
36 36 
37 37 
38def _kernel_case(name="custom_input_sync"):38def _kernel_case(name="custom_input_sync"):
@@ -29,11 +29,12 @@ import pytest
29from ttk.core_modules.npu.op import output_generation as _mod29from ttk.core_modules.npu.op import output_generation as _mod
30from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp30from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
31 31 
32-_generate_golden = getattr(_mod, '__generate_golden')32+_generate_golden = getattr(_mod, "__generate_golden")
33 33 
34 34 
35def _make_custom_golden(seen_dtypes):35def _make_custom_golden(seen_dtypes):
36 """构造 custom golden 函数,捕获实际接收的 dtype。"""36 """构造 custom golden 函数,捕获实际接收的 dtype。"""
37+ 
37 def golden(x, **kwargs):38 def golden(x, **kwargs):
38 if isinstance(x, np.ndarray):39 if isinstance(x, np.ndarray):
39 seen_dtypes.append(x.dtype)40 seen_dtypes.append(x.dtype)
@@ -42,13 +43,17 @@ def _make_custom_golden(seen_dtypes):
42 if isinstance(t, np.ndarray):43 if isinstance(t, np.ndarray):
43 seen_dtypes.append(t.dtype)44 seen_dtypes.append(t.dtype)
44 return x45 return x
46+ 
45 return golden47 return golden
46 48 
47 49 
48-def _make_testcase(op_name="custom_promote_op", input_shapes=((4,),),50+def _make_testcase(
49- input_dtypes=("float16",),51+ op_name="custom_promote_op",
50- output_shapes=((4,),),52+ input_shapes=((4,),),
51- output_dtypes=("float16",)):53+ input_dtypes=("float16",),
54+ output_shapes=((4,),),
55+ output_dtypes=("float16",),
56+):
52 case = TestcaseOp()57 case = TestcaseOp()
53 case.testcase_name = f"test_{op_name}_promote_wrap"58 case.testcase_name = f"test_{op_name}_promote_wrap"
54 case.op_name = op_name59 case.op_name = op_name
@@ -75,7 +80,7 @@ def _mock_switches_promote():
75 sw = MagicMock()80 sw = MagicMock()
76 sw.dev_plat = "Ascend910B2"81 sw.dev_plat = "Ascend910B2"
77 sw.short_soc_version = "Ascend910B"82 sw.short_soc_version = "Ascend910B"
78- sw.golden_mode = "Promote" # <-- the mode under test83+ sw.golden_mode = "Promote" # <-- the mode under test
79 sw.plugin_path = None84 sw.plugin_path = None
80 sw.overflow_mode = 085 sw.overflow_mode = 0
81 return sw86 return sw
@@ -88,9 +93,9 @@ def _mock_env(monkeypatch):
88 monkeypatch.delenv("ASCEND_OPP_PATH", raising=False)93 monkeypatch.delenv("ASCEND_OPP_PATH", raising=False)
89 94 
90 95 
91-@patch('ttk.core_modules.npu.op.output_generation.OpInfoKeeper')96+@patch("ttk.core_modules.npu.op.output_generation.OpInfoKeeper")
92-@patch('ttk.core_modules.npu.op.output_generation.get_global_storage')97+@patch("ttk.core_modules.npu.op.output_generation.get_global_storage")
93-@patch('ttk.core_modules.npu.op.output_generation.get_plugin_function')98+@patch("ttk.core_modules.npu.op.output_generation.get_plugin_function")
94class TestKernelPromoteWrapCoversAllForms:99class TestKernelPromoteWrapCoversAllForms:
95 """Under golden_mode=Promote, ALL dispatch forms must see promoted inputs."""100 """Under golden_mode=Promote, ALL dispatch forms must see promoted inputs."""
96 101 
@@ -102,15 +107,13 @@ class TestKernelPromoteWrapCoversAllForms:
102 mock_sw.return_value = _mock_switches_promote()107 mock_sw.return_value = _mock_switches_promote()
103 mock_op_info.return_value.info_of.return_value = {"inputs": []}108 mock_op_info.return_value.info_of.return_value = {"inputs": []}
104 109 
105- case = _make_testcase(op_name="custom_promote_op",110+ case = _make_testcase(op_name="custom_promote_op", input_dtypes=("float16",), output_dtypes=("float16",))
106- input_dtypes=("float16",),
107- output_dtypes=("float16",))
108 111 
109 _generate_golden(case, ["float16"])112 _generate_golden(case, ["float16"])
110 113 
111 assert len(seen_dtypes) >= 1, "golden was not invoked"114 assert len(seen_dtypes) >= 1, "golden was not invoked"
112 for d in seen_dtypes:115 for d in seen_dtypes:
113 # float16 ∈ DTYPE_PROMOTE_MAP → must be promoted to float32116 # float16 ∈ DTYPE_PROMOTE_MAP → must be promoted to float32
114- assert d == np.dtype("float32"), \117+ assert d == np.dtype("float32"), (
115- f"golden received UN-promoted dtype {d!r}; " \118+ f"golden received UN-promoted dtype {d!r}; expected float32 (promoted from float16 under Promote mode)"
116- f"expected float32 (promoted from float16 under Promote mode)"119+ )
@@ -18,11 +18,10 @@ import numpy as np
18 18 
19from ttk.core_modules.npu.op import output_generation as _mod19from ttk.core_modules.npu.op import output_generation as _mod
20 20 
21-_golden_reshape = getattr(_mod, '__golden_flatten')21+_golden_reshape = getattr(_mod, "__golden_flatten")
22 22 
23 23 
24class TestGoldenReshapeFlat:24class TestGoldenReshapeFlat:
25- 
26 def test_flat_ndarrays(self):25 def test_flat_ndarrays(self):
27 a = np.ones((3,), dtype="float16")26 a = np.ones((3,), dtype="float16")
28 b = np.ones((4,), dtype="float32")27 b = np.ones((4,), dtype="float32")
@@ -33,7 +32,6 @@ class TestGoldenReshapeFlat:
33 32 
34 33 
35class TestGoldenReshapeNested:34class TestGoldenReshapeNested:
36- 
37 def test_nested_tensorlist(self):35 def test_nested_tensorlist(self):
38 a1 = np.ones((3,), dtype="float16")36 a1 = np.ones((3,), dtype="float16")
39 a2 = np.ones((4,), dtype="float16")37 a2 = np.ones((4,), dtype="float16")
@@ -46,7 +44,6 @@ class TestGoldenReshapeNested:
46 44 
47 45 
48class TestGoldenReshapeEdgeCases:46class TestGoldenReshapeEdgeCases:
49- 
50 def test_empty_tuple(self):47 def test_empty_tuple(self):
51 result = _golden_reshape(())48 result = _golden_reshape(())
52 assert result == []49 assert result == []
@@ -12,6 +12,7 @@ InputGenerator.is_broadcast 的参数化测试。
12 12 
13覆盖广播场景:expand 产生 stride=0 的张量应返回 True13覆盖广播场景:expand 产生 stride=0 的张量应返回 True
14"""14"""
15+ 
15import pytest16import pytest
16import torch17import torch
17 18 
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""Tests for NpuInstance heartbeat lifecycle."""10"""Tests for NpuInstance heartbeat lifecycle."""
11+ 
11from unittest.mock import MagicMock12from unittest.mock import MagicMock
12 13 
13 14 
@@ -31,14 +31,13 @@ from ttk.core_modules.npu.op import output_generation as _mod
31from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp31from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
32 32 
33# Access module-level private functions via getattr to dodge class-style name mangling.33# Access module-level private functions via getattr to dodge class-style name mangling.
34-_generate_golden = getattr(_mod, '__generate_golden')34+_generate_golden = getattr(_mod, "__generate_golden")
35KERNEL_GOLDEN = _mod.KERNEL_GOLDEN35KERNEL_GOLDEN = _mod.KERNEL_GOLDEN
36 36 
37 37 
38-def _make_testcase(op_name="neg", input_shapes=((4,),),38+def _make_testcase(
39- input_dtypes=("float32",),39+ op_name="neg", input_shapes=((4,),), input_dtypes=("float32",), output_shapes=((4,),), output_dtypes=("float32",)
40- output_shapes=((4,),),40+):
41- output_dtypes=("float32",)):
42 case = TestcaseOp()41 case = TestcaseOp()
43 case.testcase_name = f"test_{op_name}_builtin_golden"42 case.testcase_name = f"test_{op_name}_builtin_golden"
44 case.op_name = op_name43 case.op_name = op_name
@@ -65,7 +64,7 @@ def _mock_switches():
65 sw = MagicMock()64 sw = MagicMock()
66 sw.dev_plat = "Ascend910B2"65 sw.dev_plat = "Ascend910B2"
67 sw.short_soc_version = "Ascend910B"66 sw.short_soc_version = "Ascend910B"
68- sw.golden_mode = "Enable" # not Promote → __golden_mode is a no-op67+ sw.golden_mode = "Enable" # not Promote → __golden_mode is a no-op
69 sw.plugin_path = None68 sw.plugin_path = None
70 sw.overflow_mode = 069 sw.overflow_mode = 0
71 return sw70 return sw
@@ -79,9 +78,9 @@ def _mock_env(monkeypatch):
79 monkeypatch.delenv("ASCEND_OPP_PATH", raising=False)78 monkeypatch.delenv("ASCEND_OPP_PATH", raising=False)
80 79 
81 80 
82-@patch('ttk.core_modules.npu.op.output_generation.OpInfoKeeper')81+@patch("ttk.core_modules.npu.op.output_generation.OpInfoKeeper")
83-@patch('ttk.core_modules.npu.op.output_generation.get_global_storage')82+@patch("ttk.core_modules.npu.op.output_generation.get_global_storage")
84-@patch('ttk.core_modules.npu.op.output_generation.get_plugin_function')83+@patch("ttk.core_modules.npu.op.output_generation.get_plugin_function")
85class TestKernelBuiltinGoldenDispatch:84class TestKernelBuiltinGoldenDispatch:
86 """KERNEL_GOLDEN[op] → numpy ufunc → numeric result (not a sentinel)."""85 """KERNEL_GOLDEN[op] → numpy ufunc → numeric result (not a sentinel)."""
87 86 
@@ -92,10 +91,13 @@ class TestKernelBuiltinGoldenDispatch:
92 mock_sw.return_value = _mock_switches()91 mock_sw.return_value = _mock_switches()
93 mock_op_info.return_value.info_of.return_value = {"inputs": []}92 mock_op_info.return_value.info_of.return_value = {"inputs": []}
94 93 
95- case = _make_testcase(op_name="neg", input_shapes=((4,),),94+ case = _make_testcase(
96- input_dtypes=("float32",),95+ op_name="neg",
97- output_shapes=((4,),),96+ input_shapes=((4,),),
98- output_dtypes=("float32",))97+ input_dtypes=("float32",),
98+ output_shapes=((4,),),
99+ output_dtypes=("float32",),
100+ )
99 # Sanity: pre-conditions for the path under test.101 # Sanity: pre-conditions for the path under test.
100 assert "neg" in KERNEL_GOLDEN102 assert "neg" in KERNEL_GOLDEN
101 assert KERNEL_GOLDEN["neg"] is np.negative103 assert KERNEL_GOLDEN["neg"] is np.negative
@@ -108,15 +110,13 @@ class TestKernelBuiltinGoldenDispatch:
108 # The path must succeed (not "GOLDEN_FAILURE" / "UNSUPPORTED") and110 # The path must succeed (not "GOLDEN_FAILURE" / "UNSUPPORTED") and
109 # produce a numeric numpy array equal to -input.111 # produce a numeric numpy array equal to -input.
110 assert len(golden) >= 1112 assert len(golden) >= 1
111- assert isinstance(golden[0], np.ndarray), \113+ assert isinstance(golden[0], np.ndarray), f"expected ndarray, got sentinel/string: {golden[0]!r}"
112- f"expected ndarray, got sentinel/string: {golden[0]!r}"114+ np.testing.assert_array_equal(golden[0], np.array([-1.0, 2.0, -3.0, 4.0], dtype="float32"))
113- np.testing.assert_array_equal(golden[0], np.array([-1.0, 2.0, -3.0, 4.0],
114- dtype="float32"))
115 # Confirm get_plugin_function was consulted (proves the None→KERNEL_GOLDEN branch ran).115 # Confirm get_plugin_function was consulted (proves the None→KERNEL_GOLDEN branch ran).
116 mock_get_plugin.assert_called_once()116 mock_get_plugin.assert_called_once()
117 117 
118 118 
119-@patch('ttk.core_modules.npu.op.output_generation.get_plugin_function')119+@patch("ttk.core_modules.npu.op.output_generation.get_plugin_function")
120class TestKernelGoldenFallbackBranchCoverage:120class TestKernelGoldenFallbackBranchCoverage:
121 """Lighter-weight unit on the fallback decision itself.121 """Lighter-weight unit on the fallback decision itself.
122 122 
@@ -130,6 +130,6 @@ class TestKernelGoldenFallbackBranchCoverage:
130 # framework_of classification is what steers numpy ufuncs into130 # framework_of classification is what steers numpy ufuncs into
131 # __call_numpy_api inside __invoke_golden.131 # __call_numpy_api inside __invoke_golden.
132 from ttk.utilities import framework_of132 from ttk.utilities import framework_of
133+ 
133 for op in ("neg", "acos", "floor_div"):134 for op in ("neg", "acos", "floor_div"):
134- assert framework_of(KERNEL_GOLDEN[op]) == "numpy", \135+ assert framework_of(KERNEL_GOLDEN[op]) == "numpy", f"{op} must classify as 'numpy' to hit the builtin path"
135- f"{op} must classify as 'numpy' to hit the builtin path"
@@ -24,7 +24,7 @@ from ttk.core_modules.npu.op import output_generation as _mod
24from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp24from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
25 25 
26# Access module-level private functions via getattr to dodge class-style name mangling.26# Access module-level private functions via getattr to dodge class-style name mangling.
27-_generate_golden = getattr(_mod, '__generate_golden')27+_generate_golden = getattr(_mod, "__generate_golden")
28 28 
29 29 
30# --------------------------------------------------------------------------- #30# --------------------------------------------------------------------------- #
@@ -32,18 +32,18 @@ _generate_golden = getattr(_mod, '__generate_golden')
32# --------------------------------------------------------------------------- #32# --------------------------------------------------------------------------- #
33class _NegScale:33class _NegScale:
34 """Partial split: ``__init__`` takes a config attr, ``__call__`` takes the input."""34 """Partial split: ``__init__`` takes a config attr, ``__call__`` takes the input."""
35+ 
35 def __init__(self, *, scale):36 def __init__(self, *, scale):
36 self.scale = scale37 self.scale = scale
37 38 
38- def __call__(self, x): # noqa: N803 (short param mirrors real plugins)39+ def __call__(self, x): # noqa: N803 (short param mirrors real plugins)
39 return [x * self.scale]40 return [x * self.scale]
40 41 
41 42 
42# --------------------------------------------------------------------------- #43# --------------------------------------------------------------------------- #
43# Helpers — mirror tests/test_kernel_builtin_golden.py::_make_testcase.44# Helpers — mirror tests/test_kernel_builtin_golden.py::_make_testcase.
44# --------------------------------------------------------------------------- #45# --------------------------------------------------------------------------- #
45-def _make_testcase(op_name, input_shapes, input_dtypes, output_shapes, output_dtypes,46+def _make_testcase(op_name, input_shapes, input_dtypes, output_shapes, output_dtypes, attributes):
46- attributes):
47 case = TestcaseOp()47 case = TestcaseOp()
48 case.testcase_name = f"test_{op_name}_class_golden"48 case.testcase_name = f"test_{op_name}_class_golden"
49 case.op_name = op_name49 case.op_name = op_name
@@ -70,7 +70,7 @@ def _mock_switches():
70 sw = MagicMock()70 sw = MagicMock()
71 sw.dev_plat = "Ascend910B2"71 sw.dev_plat = "Ascend910B2"
72 sw.short_soc_version = "Ascend910B"72 sw.short_soc_version = "Ascend910B"
73- sw.golden_mode = "Enable" # not Promote → __golden_mode is a no-op73+ sw.golden_mode = "Enable" # not Promote → __golden_mode is a no-op
74 sw.plugin_path = None74 sw.plugin_path = None
75 sw.overflow_mode = 075 sw.overflow_mode = 0
76 return sw76 return sw
@@ -91,14 +91,13 @@ def _mock_env(monkeypatch):
91 monkeypatch.delenv("ASCEND_OPP_PATH", raising=False)91 monkeypatch.delenv("ASCEND_OPP_PATH", raising=False)
92 92 
93 93 
94-@patch('ttk.core_modules.npu.op.output_generation.OpInfoKeeper')94+@patch("ttk.core_modules.npu.op.output_generation.OpInfoKeeper")
95-@patch('ttk.core_modules.npu.op.output_generation.get_global_storage')95+@patch("ttk.core_modules.npu.op.output_generation.get_global_storage")
96-@patch('ttk.core_modules.npu.op.output_generation.get_plugin_function')96+@patch("ttk.core_modules.npu.op.output_generation.get_plugin_function")
97class TestKernelClassGoldenDispatch:97class TestKernelClassGoldenDispatch:
98 """class-form golden (isinstance(golden_func, type)) → __invoke_class end-to-end."""98 """class-form golden (isinstance(golden_func, type)) → __invoke_class end-to-end."""
99 99 
100- def test_dispatch_routes_class_to_invoke_class_not_callable_path(self, mock_get_plugin,100+ def test_dispatch_routes_class_to_invoke_class_not_callable_path(self, mock_get_plugin, mock_sw, mock_op_info):
101- mock_sw, mock_op_info):
102 """A class is a ``Callable`` but MUST route to ``__invoke_class`` (not the custom-callable101 """A class is a ``Callable`` but MUST route to ``__invoke_class`` (not the custom-callable
103 path that would do ``golden_func(*input_arrays)``). Guards the ``isinstance(type)`` check102 path that would do ``golden_func(*input_arrays)``). Guards the ``isinstance(type)`` check
104 ordering in ``__invoke_golden``: if a class leaked to the custom branch it would be called103 ordering in ``__invoke_golden``: if a class leaked to the custom branch it would be called
@@ -108,16 +107,21 @@ class TestKernelClassGoldenDispatch:
108 mock_op_info.return_value.info_of.return_value = {"inputs": [{"name": "x"}]}107 mock_op_info.return_value.info_of.return_value = {"inputs": [{"name": "x"}]}
109 108 
110 inp = np.array([1.0, 2.0, 3.0], dtype="float32")109 inp = np.array([1.0, 2.0, 3.0], dtype="float32")
111- case = _make_testcase(op_name="neg_scale",110+ case = _make_testcase(
112- input_shapes=((3,),), input_dtypes=("float32",),111+ op_name="neg_scale",
113- output_shapes=((3,),), output_dtypes=("float32",),112+ input_shapes=((3,),),
114- attributes={"scale": 5})113+ input_dtypes=("float32",),
114+ output_shapes=((3,),),
115+ output_dtypes=("float32",),
116+ attributes={"scale": 5},
117+ )
115 case.input_arrays = (inp,)118 case.input_arrays = (inp,)
116 119 
117 golden = _generate_golden(case, ["float32"])120 golden = _generate_golden(case, ["float32"])
118 121 
119 # If mis-routed to the custom-callable branch, golden[0] would be the122 # If mis-routed to the custom-callable branch, golden[0] would be the
120 # string "GOLDEN_FAILURE"; the class path yields a numeric ndarray.123 # string "GOLDEN_FAILURE"; the class path yields a numeric ndarray.
121- assert isinstance(golden[0], np.ndarray), \124+ assert isinstance(golden[0], np.ndarray), (
122 f"class golden must hit __invoke_class, not the custom-callable branch: {golden[0]!r}"125 f"class golden must hit __invoke_class, not the custom-callable branch: {golden[0]!r}"
126+ )
123 np.testing.assert_array_equal(golden[0], np.array([5.0, 10.0, 15.0], dtype="float32"))127 np.testing.assert_array_equal(golden[0], np.array([5.0, 10.0, 15.0], dtype="float32"))
@@ -24,6 +24,7 @@ Covers:
24 24 
25No NPU needed — this tests the XPU dispatch chain, not the kernel compile.25No NPU needed — this tests the XPU dispatch chain, not the kernel compile.
26"""26"""
27+ 
27import http.client28import http.client
28 29 
29# Dynamic capability check (find_spec = no import, no TF flood).30# Dynamic capability check (find_spec = no import, no TF flood).
@@ -51,8 +52,9 @@ has_tf = _ilu.find_spec("tensorflow") is not None
51if has_tf:52if has_tf:
52 # find_spec 只看包存在;CI 的 tf 可能装了但 import 崩溃(protobuf 不兼容 / C 扩展 segfault)53 # find_spec 只看包存在;CI 的 tf 可能装了但 import 崩溃(protobuf 不兼容 / C 扩展 segfault)
53 try:54 try:
54- has_tf = subprocess.run([sys.executable, "-c", "import tensorflow"],55+ has_tf = (
55- capture_output=True, timeout=90).returncode == 056+ subprocess.run([sys.executable, "-c", "import tensorflow"], capture_output=True, timeout=90).returncode == 0
57+ )
56 except Exception:58 except Exception:
57 has_tf = False59 has_tf = False
58 60 
@@ -78,14 +80,14 @@ def xpu_server():
78 """Start a standalone XPU-Server (CPU stub, same as production deploy)."""80 """Start a standalone XPU-Server (CPU stub, same as production deploy)."""
79 port = _free_port()81 port = _free_port()
80 env = dict(os.environ)82 env = dict(os.environ)
81- ttk_remote = os.path.abspath(83+ ttk_remote = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "ttk", "remote"))
82- os.path.join(os.path.dirname(__file__), "..", "..", "..", "ttk", "remote"))84+ env["PYTHONPATH"] = os.pathsep.join(p for p in (ttk_remote, env.get("PYTHONPATH", "")) if p)
83- env["PYTHONPATH"] = os.pathsep.join(
84- p for p in (ttk_remote, env.get("PYTHONPATH", "")) if p)
85 proc = subprocess.Popen(85 proc = subprocess.Popen(
86- [sys.executable, "-m", "server.xpu_server",86+ [sys.executable, "-m", "server.xpu_server", "--port", str(port), "--devices", "cpu"],
87- "--port", str(port), "--devices", "cpu"],87+ env=env,
88- env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)88+ stdout=subprocess.PIPE,
89+ stderr=subprocess.PIPE,
90+ )
89 # Task 6 removed /health; readiness now probes GET /v1/heartbeat (merged91 # Task 6 removed /health; readiness now probes GET /v1/heartbeat (merged
90 # health+detect+register). tenant_id=e2e_xpu matches collect_xpu_results.92 # health+detect+register). tenant_id=e2e_xpu matches collect_xpu_results.
91 for _ in range(20):93 for _ in range(20):
@@ -138,9 +140,9 @@ def _dispatch(xpu_port, tmp_path, monkeypatch, spec_subdir=None, cli_providers=N
138 ep_key = f"127.0.0.1:{xpu_port}"140 ep_key = f"127.0.0.1:{xpu_port}"
139 os.makedirs(os.path.dirname(health_path), exist_ok=True)141 os.makedirs(os.path.dirname(health_path), exist_ok=True)
140 with open(health_path, "w") as f:142 with open(health_path, "w") as f:
141- json.dump({"endpoints": {143+ json.dump(
142- ep_key: {"alive": True, "providers": providers,144+ {"endpoints": {ep_key: {"alive": True, "providers": providers, "hardware": hardware, "ts": time.time()}}}, f
143- "hardware": hardware, "ts": time.time()}}}, f)145+ )
144 146 
145 # 3. Wire env + RemoteConfig + clear Singleton EVERY call so the147 # 3. Wire env + RemoteConfig + clear Singleton EVERY call so the
146 # EndpointView constructed inside collect_xpu_results binds to THIS148 # EndpointView constructed inside collect_xpu_results binds to THIS
@@ -150,9 +152,9 @@ def _dispatch(xpu_port, tmp_path, monkeypatch, spec_subdir=None, cli_providers=N
150 # set_remote_config override (both removed in Task 6/7). Write a temp yaml152 # set_remote_config override (both removed in Task 6/7). Write a temp yaml
151 # with this endpoint and load it so get_remote_config() sees it.153 # with this endpoint and load it so get_remote_config() sees it.
152 yaml_path = tmp_path / "ttk.conf.yaml"154 yaml_path = tmp_path / "ttk.conf.yaml"
153- yaml_path.write_text(155+ yaml_path.write_text(f"remote:\n endpoints:\n - host: 127.0.0.1\n port: {xpu_port}\n")
154- f"remote:\n endpoints:\n - host: 127.0.0.1\n port: {xpu_port}\n")
155 from ttk.config.loader import load_config156 from ttk.config.loader import load_config
157+ 
156 load_config(str(yaml_path))158 load_config(str(yaml_path))
157 # cli_providers is passed directly to resolve_providers below (and into159 # cli_providers is passed directly to resolve_providers below (and into
158 # collect_xpu_results); TTK_XPU_PROVIDER env is no longer read by production160 # collect_xpu_results); TTK_XPU_PROVIDER env is no longer read by production
@@ -179,9 +181,13 @@ def _dispatch(xpu_port, tmp_path, monkeypatch, spec_subdir=None, cli_providers=N
179 x = np.random.randn(4, 8).astype(np.float32)181 x = np.random.randn(4, 8).astype(np.float32)
180 y = np.random.randn(4, 8).astype(np.float32)182 y = np.random.randn(4, 8).astype(np.float32)
181 results = collect_xpu_results(183 results = collect_xpu_results(
182- specs, inputs=[x, y], input_names=["x", "y"],184+ specs,
183- mode=DATA | PERF, tenant_id=_TENANT_ID,185+ inputs=[x, y],
184- op_name="add", op_type="Add",186+ input_names=["x", "y"],
187+ mode=DATA | PERF,
188+ tenant_id=_TENANT_ID,
189+ op_name="add",
190+ op_type="Add",
185 )191 )
186 # priority = first resolved provider (spec order when a spec is given); DATA goes to it.192 # priority = first resolved provider (spec order when a spec is given); DATA goes to it.
187 return results, (x, y), (resolved[0] if resolved else None)193 return results, (x, y), (resolved[0] if resolved else None)
@@ -203,12 +209,12 @@ def _assert_priority_output(results, x, y, priority):
203 assert priority is not None, "no provider resolved (priority unknown)"209 assert priority is not None, "no provider resolved (priority unknown)"
204 outs = results[priority].get("outputs")210 outs = results[priority].get("outputs")
205 assert outs, f"priority {priority} has no outputs (expected DATA for priority)"211 assert outs, f"priority {priority} has no outputs (expected DATA for priority)"
206- np.testing.assert_allclose(outs[0], np.add(x, y), rtol=1e-5,212+ np.testing.assert_allclose(outs[0], np.add(x, y), rtol=1e-5, err_msg=f"priority {priority} output mismatch")
207- err_msg=f"priority {priority} output mismatch")
208 213 
209 214 
210# ---- No-spec scenarios ----215# ---- No-spec scenarios ----
211 216 
217+ 
212@needs_both218@needs_both
213def test_no_spec_discovery_both_providers(xpu_server, tmp_path, monkeypatch):219def test_no_spec_discovery_both_providers(xpu_server, tmp_path, monkeypatch):
214 """No spec, no --provider: both torch+tf dispatched + priority output verified."""220 """No spec, no --provider: both torch+tf dispatched + priority output verified."""
@@ -220,10 +226,10 @@ def test_no_spec_discovery_both_providers(xpu_server, tmp_path, monkeypatch):
220 226 
221# ---- Spec scenarios: all third_party styles ----227# ---- Spec scenarios: all third_party styles ----
222 228 
229+ 
223@needs_torch230@needs_torch
224def test_spec_tp_str(xpu_server, tmp_path, monkeypatch):231def test_spec_tp_str(xpu_server, tmp_path, monkeypatch):
225 """third_party = 'torch.add' (single API string); torch is priority."""232 """third_party = 'torch.add' (single API string); torch is priority."""
226- results, (x, y), priority = _dispatch(xpu_server, tmp_path, monkeypatch,233+ results, (x, y), priority = _dispatch(xpu_server, tmp_path, monkeypatch, spec_subdir="tp_str")
227- spec_subdir="tp_str")
228 _assert_pass(results, "torch")234 _assert_pass(results, "torch")
229 _assert_priority_output(results, x, y, priority)235 _assert_priority_output(results, x, y, priority)
@@ -26,15 +26,18 @@ from ttk.core_modules.npu.op import input_generation as _in_gen_mod
26from ttk.core_modules.npu.op import output_generation as _out_gen_mod26from ttk.core_modules.npu.op import output_generation as _out_gen_mod
27from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp27from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
28 28 
29-_collect_golden_kwargs = getattr(_out_gen_mod, '__collect_dynamic_golden_kwargs')29+_collect_golden_kwargs = getattr(_out_gen_mod, "__collect_dynamic_golden_kwargs")
30-_collect_input_kwargs = getattr(_in_gen_mod, '__collect_dynamic_kwargs')30+_collect_input_kwargs = getattr(_in_gen_mod, "__collect_dynamic_kwargs")
31 31 
32 32 
33-def _make_testcase(op_name="Add", input_shapes=((8,), (8,)),33+def _make_testcase(
34- input_dtypes=("float16", "float16"),34+ op_name="Add",
35- output_shapes=((8,),),35+ input_shapes=((8,), (8,)),
36- output_dtypes=("float16",),36+ input_dtypes=("float16", "float16"),
37- **kwargs):37+ output_shapes=((8,),),
38+ output_dtypes=("float16",),
39+ **kwargs,
40+):
38 case = TestcaseOp()41 case = TestcaseOp()
39 case.testcase_name = f"test_{op_name or 'None'}"42 case.testcase_name = f"test_{op_name or 'None'}"
40 case.op_name = op_name43 case.op_name = op_name
@@ -60,7 +63,7 @@ def _make_testcase(op_name="Add", input_shapes=((8,), (8,)),
60def _validate(case):63def _validate(case):
61 n_in = len(case.input_shapes) if case.input_shapes else 064 n_in = len(case.input_shapes) if case.input_shapes else 0
62 n_out = len(case.output_shapes) if case.output_shapes and not isinstance(case.output_shapes, str) else 065 n_out = len(case.output_shapes) if case.output_shapes and not isinstance(case.output_shapes, str) else 0
63- with patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper') as mock:66+ with patch("ttk.core_modules.operator.op_info_keeper.OpInfoKeeper") as mock:
64 mock.return_value.info_of.return_value = {67 mock.return_value.info_of.return_value = {
65 "coreType.value": "AiCore",68 "coreType.value": "AiCore",
66 "inputs": [{"name": f"i{i}"} for i in range(n_in)],69 "inputs": [{"name": f"i{i}"} for i in range(n_in)],
@@ -99,10 +102,10 @@ def _mock_env(monkeypatch):
99# Tests for output_generation.__collect_dynamic_golden_kwargs102# Tests for output_generation.__collect_dynamic_golden_kwargs
100# =====================================================================103# =====================================================================
101 104 
102-class TestGoldenKwargsNonTensorList:
103 105 
104- @patch('ttk.core_modules.npu.op.output_generation.OpInfoKeeper')106+class TestGoldenKwargsNonTensorList:
105- @patch('ttk.core_modules.npu.op.output_generation.get_global_storage')107+ @patch("ttk.core_modules.npu.op.output_generation.OpInfoKeeper")
108+ @patch("ttk.core_modules.npu.op.output_generation.get_global_storage")
106 def test_input_dtypes_matches_stc(self, mock_sw, mock_op_info):109 def test_input_dtypes_matches_stc(self, mock_sw, mock_op_info):
107 mock_sw.return_value = _mock_switches()110 mock_sw.return_value = _mock_switches()
108 mock_op_info.return_value.info_of.return_value = {"inputs": []}111 mock_op_info.return_value.info_of.return_value = {"inputs": []}
@@ -114,15 +117,14 @@ class TestGoldenKwargsNonTensorList:
114 output_dtypes=("float16",),117 output_dtypes=("float16",),
115 )118 )
116 _validate(case)119 _validate(case)
117- case.input_arrays = tuple(_make_arrays(120+ case.input_arrays = tuple(_make_arrays(case.flat_input_shapes, case.flat_input_dtypes))
118- case.flat_input_shapes, case.flat_input_dtypes))
119 121 
120 kwargs = _collect_golden_kwargs(case)122 kwargs = _collect_golden_kwargs(case)
121 assert kwargs["input_dtypes"] == case.input_dtypes123 assert kwargs["input_dtypes"] == case.input_dtypes
122 assert kwargs["input_dtypes"] == ("float16", "float32", "int32")124 assert kwargs["input_dtypes"] == ("float16", "float32", "int32")
123 125 
124- @patch('ttk.core_modules.npu.op.output_generation.OpInfoKeeper')126+ @patch("ttk.core_modules.npu.op.output_generation.OpInfoKeeper")
125- @patch('ttk.core_modules.npu.op.output_generation.get_global_storage')127+ @patch("ttk.core_modules.npu.op.output_generation.get_global_storage")
126 def test_all_format_fields_match(self, mock_sw, mock_op_info):128 def test_all_format_fields_match(self, mock_sw, mock_op_info):
127 mock_sw.return_value = _mock_switches()129 mock_sw.return_value = _mock_switches()
128 mock_op_info.return_value.info_of.return_value = {"inputs": []}130 mock_op_info.return_value.info_of.return_value = {"inputs": []}
@@ -138,8 +140,7 @@ class TestGoldenKwargsNonTensorList:
138 output_ori_formats=("ND",),140 output_ori_formats=("ND",),
139 )141 )
140 _validate(case)142 _validate(case)
141- case.input_arrays = tuple(_make_arrays(143+ case.input_arrays = tuple(_make_arrays(case.flat_input_shapes, case.flat_input_dtypes))
142- case.flat_input_shapes, case.flat_input_dtypes))
143 144 
144 kwargs = _collect_golden_kwargs(case)145 kwargs = _collect_golden_kwargs(case)
145 assert kwargs["input_formats"] == case.input_formats146 assert kwargs["input_formats"] == case.input_formats
@@ -152,10 +153,10 @@ class TestGoldenKwargsNonTensorList:
152# Tests for input_generation.__collect_dynamic_kwargs153# Tests for input_generation.__collect_dynamic_kwargs
153# =====================================================================154# =====================================================================
154 155 
155-class TestInputKwargsNonTensorList:
156 156 
157- @patch('ttk.core_modules.npu.op.input_generation.OpInfoKeeper')157+class TestInputKwargsNonTensorList:
158- @patch('ttk.core_modules.npu.op.input_generation.get_global_storage')158+ @patch("ttk.core_modules.npu.op.input_generation.OpInfoKeeper")
159+ @patch("ttk.core_modules.npu.op.input_generation.get_global_storage")
159 def test_input_ranges_matches_stc(self, mock_sw, mock_op_info):160 def test_input_ranges_matches_stc(self, mock_sw, mock_op_info):
160 mock_sw.return_value = _mock_switches()161 mock_sw.return_value = _mock_switches()
161 mock_op_info.return_value.info_of.return_value = {"inputs": []}162 mock_op_info.return_value.info_of.return_value = {"inputs": []}
@@ -8,6 +8,7 @@
8# See LICENSE in the root of the software repository for the full text of the License.8# See LICENSE in the root of the software repository for the full text of the License.
9# ----------------------------------------------------------------------------9# ----------------------------------------------------------------------------
10"""Tests for manual-data mode NpuInstance."""10"""Tests for manual-data mode NpuInstance."""
11+ 
11from types import SimpleNamespace12from types import SimpleNamespace
12from unittest.mock import MagicMock13from unittest.mock import MagicMock
13 14 
@@ -31,9 +32,7 @@ def _instance():
31def test_prepare_uses_one_logical_worker_without_querying_devices(monkeypatch):32def test_prepare_uses_one_logical_worker_without_querying_devices(monkeypatch):
32 instance = _instance()33 instance = _instance()
33 dsmi = MagicMock(side_effect=AssertionError("DSMI must not be queried"))34 dsmi = MagicMock(side_effect=AssertionError("DSMI must not be queried"))
34- monkeypatch.setattr(35+ monkeypatch.setattr("ttk.core_modules.npu.instance_refactor.DSMIInterface", dsmi)
35- "ttk.core_modules.npu.instance_refactor.DSMIInterface", dsmi
36- )
37 36 
38 instance.get_device_count()37 instance.get_device_count()
39 38 
@@ -12,20 +12,29 @@
12覆盖字段存在性:各结构(ComparisonResult / ProfilingReturnStructure /12覆盖字段存在性:各结构(ComparisonResult / ProfilingReturnStructure /
13GeirReturnStructure)均应含 precision_metrics(或 ComparisonResult 的 metrics)字段。13GeirReturnStructure)均应含 precision_metrics(或 ComparisonResult 的 metrics)字段。
14"""14"""
15+ 
15import pytest16import pytest
16 17 
17from ttk.core_modules.geir.geir_struct import GeirReturnStructure18from ttk.core_modules.geir.geir_struct import GeirReturnStructure
18from ttk.core_modules.npu.op.profiling_structure import ComparisonResult, ProfilingReturnStructure19from ttk.core_modules.npu.op.profiling_structure import ComparisonResult, ProfilingReturnStructure
19 20 
20 21 
21-@pytest.mark.parametrize("cls, factory, field_name, is_geir", [22+@pytest.mark.parametrize(
22- pytest.param(ComparisonResult, lambda: ComparisonResult(None), "metrics", False,23+ "cls, factory, field_name, is_geir",
23- id="comparison_result"),24+ [
24- pytest.param(ProfilingReturnStructure, lambda: ProfilingReturnStructure(), "precision_metrics",25+ pytest.param(ComparisonResult, lambda: ComparisonResult(None), "metrics", False, id="comparison_result"),
25- False, id="profiling_structure"),26+ pytest.param(
26- pytest.param(GeirReturnStructure, lambda: GeirReturnStructure(), "precision_metrics", True,27+ ProfilingReturnStructure,
27- id="geir_structure"),28+ lambda: ProfilingReturnStructure(),
28-])29+ "precision_metrics",
30+ False,
31+ id="profiling_structure",
32+ ),
33+ pytest.param(
34+ GeirReturnStructure, lambda: GeirReturnStructure(), "precision_metrics", True, id="geir_structure"
35+ ),
36+ ],
37+)
29def test_precision_metrics_slot_exists(cls, factory, field_name, is_geir):38def test_precision_metrics_slot_exists(cls, factory, field_name, is_geir):
30 """各结构均应含 precision_metrics(或 ComparisonResult 的 metrics)字段。39 """各结构均应含 precision_metrics(或 ComparisonResult 的 metrics)字段。
31 40 
@@ -10,6 +10,7 @@
10"""_extract_spec_providers (dict/str/None) + EndpointView.resolve_providers10"""_extract_spec_providers (dict/str/None) + EndpointView.resolve_providers
11+ _do_xpu_profiling fail-loud (Task 9). No server fixture; EV constructed11+ _do_xpu_profiling fail-loud (Task 9). No server fixture; EV constructed
12in-process with a health file (ref Task 3 test pattern)."""12in-process with a health file (ref Task 3 test pattern)."""
13+ 
13import json14import json
14import logging15import logging
15import os16import os
@@ -26,13 +27,13 @@ def _write_health(path, endpoints_data):
26 json.dump({"endpoints": endpoints_data}, f)27 json.dump({"endpoints": endpoints_data}, f)
27 28 
28 29 
29-def _make_ev(monkeypatch, tmp_path, health_data, endpoints_list,30+def _make_ev(monkeypatch, tmp_path, health_data, endpoints_list, health_name="h.json"):
30- health_name="h.json"):
31 """Wire TTK_XPU_HEALTH_PATH env + write health file + load remote endpoints31 """Wire TTK_XPU_HEALTH_PATH env + write health file + load remote endpoints
32 from yaml (load_config), then construct a FRESH EndpointView (Singleton cleared)."""32 from yaml (load_config), then construct a FRESH EndpointView (Singleton cleared)."""
33 from ttk.config.loader import load_config33 from ttk.config.loader import load_config
34 from ttk.remote.endpoint_view import EndpointView34 from ttk.remote.endpoint_view import EndpointView
35 from ttk.utilities.singleton import Singleton35 from ttk.utilities.singleton import Singleton
36+ 
36 Singleton._instances.clear()37 Singleton._instances.clear()
37 health_path = str(tmp_path / health_name)38 health_path = str(tmp_path / health_name)
38 monkeypatch.setenv("TTK_XPU_HEALTH_PATH", health_path)39 monkeypatch.setenv("TTK_XPU_HEALTH_PATH", health_path)
@@ -42,8 +43,7 @@ def _make_ev(monkeypatch, tmp_path, health_data, endpoints_list,
42 lines.append(f" - host: {e['host']}")43 lines.append(f" - host: {e['host']}")
43 lines.append(f" port: {e['port']}")44 lines.append(f" port: {e['port']}")
44 if e.get("providers"):45 if e.get("providers"):
45- lines.append(" providers: ["46+ lines.append(" providers: [" + ", ".join(repr(p) for p in e["providers"]) + "]")
46- + ", ".join(repr(p) for p in e["providers"]) + "]")
47 yaml_path = tmp_path / "ttk.conf.yaml"47 yaml_path = tmp_path / "ttk.conf.yaml"
48 yaml_path.write_text("\n".join(lines) + "\n", encoding="utf-8")48 yaml_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
49 load_config(str(yaml_path))49 load_config(str(yaml_path))
@@ -53,6 +53,7 @@ def _make_ev(monkeypatch, tmp_path, health_data, endpoints_list,
53 53 
54# ---- _extract_spec_providers ----54# ---- _extract_spec_providers ----
55 55 
56+ 
56def test_extract_spec_providers_dict_returns_keys():57def test_extract_spec_providers_dict_returns_keys():
57 tp = {"torch": "torch.add", "tf": "tf.raw_ops.Add"}58 tp = {"torch": "torch.add", "tf": "tf.raw_ops.Add"}
58 assert prof._extract_spec_providers(tp) == ["torch", "tf"]59 assert prof._extract_spec_providers(tp) == ["torch", "tf"]
@@ -60,32 +61,36 @@ def test_extract_spec_providers_dict_returns_keys():
60 61 
61# ---- resolve_providers via real EndpointView ----62# ---- resolve_providers via real EndpointView ----
62 63 
64+ 
63def test_resolve_spec_intersects_effective(monkeypatch, tmp_path):65def test_resolve_spec_intersects_effective(monkeypatch, tmp_path):
64- ev = _make_ev(monkeypatch, tmp_path,66+ ev = _make_ev(
65- {"127.0.0.1:9090": {"alive": True, "providers": ["torch", "tf"]}},67+ monkeypatch,
66- [{"host": "127.0.0.1", "port": 9090}])68+ tmp_path,
69+ {"127.0.0.1:9090": {"alive": True, "providers": ["torch", "tf"]}},
70+ [{"host": "127.0.0.1", "port": 9090}],
71+ )
67 assert ev.resolve_providers(spec_providers=["torch"]) == ["torch"]72 assert ev.resolve_providers(spec_providers=["torch"]) == ["torch"]
68 73 
69 74 
70# ---- fail-loud: _do_xpu_profiling survives resolve failure ----75# ---- fail-loud: _do_xpu_profiling survives resolve failure ----
71 76 
77+ 
72def test_do_xpu_profiling_resolve_failure_sets_empty_and_survives(monkeypatch, tmp_path, caplog):78def test_do_xpu_profiling_resolve_failure_sets_empty_and_survives(monkeypatch, tmp_path, caplog):
73 """resolve_providers RuntimeError -> context.xpu_results = {} + error log,79 """resolve_providers RuntimeError -> context.xpu_results = {} + error log,
74 no exception propagated (worker survives)."""80 no exception propagated (worker survives)."""
75 from ttk.utilities.singleton import Singleton81 from ttk.utilities.singleton import Singleton
82+ 
76 Singleton._instances.clear()83 Singleton._instances.clear()
77 monkeypatch.setenv("TTK_XPU_HEALTH_PATH", str(tmp_path / "h.json"))84 monkeypatch.setenv("TTK_XPU_HEALTH_PATH", str(tmp_path / "h.json"))
78 # Remote endpoints come from yaml (load_config), not TTK_XPU_ENDPOINTS env.85 # Remote endpoints come from yaml (load_config), not TTK_XPU_ENDPOINTS env.
79 # Load a yaml with the dead endpoint so the resolve-failure path is driven86 # Load a yaml with the dead endpoint so the resolve-failure path is driven
80 # by "alive=False" rather than "no endpoints configured".87 # by "alive=False" rather than "no endpoints configured".
81 yaml_path = tmp_path / "ttk.conf.yaml"88 yaml_path = tmp_path / "ttk.conf.yaml"
82- yaml_path.write_text(89+ yaml_path.write_text("remote:\n endpoints:\n - host: 127.0.0.1\n port: 9090\n", encoding="utf-8")
83- "remote:\n endpoints:\n - host: 127.0.0.1\n port: 9090\n",
84- encoding="utf-8")
85 from ttk.config.loader import load_config90 from ttk.config.loader import load_config
91+ 
86 load_config(str(yaml_path))92 load_config(str(yaml_path))
87- _write_health(str(tmp_path / "h.json"),93+ _write_health(str(tmp_path / "h.json"), {"127.0.0.1:9090": {"alive": False, "providers": ["torch"]}})
88- {"127.0.0.1:9090": {"alive": False, "providers": ["torch"]}})
89 94 
90 # Stub OpInfoKeeper so the NPU-env-dependent lookups (ASCEND_OPP_PATH etc.)95 # Stub OpInfoKeeper so the NPU-env-dependent lookups (ASCEND_OPP_PATH etc.)
91 # don't blow up before we reach the resolve. fail-loud path must run in any env.96 # don't blow up before we reach the resolve. fail-loud path must run in any env.
@@ -95,19 +100,18 @@ def test_do_xpu_profiling_resolve_failure_sets_empty_and_survives(monkeypatch, t
95 100 
96 def op_type_of(self, op_name):101 def op_type_of(self, op_name):
97 return None102 return None
103+ 
98 monkeypatch.setattr(prof, "OpInfoKeeper", _FakeKeeper)104 monkeypatch.setattr(prof, "OpInfoKeeper", _FakeKeeper)
99 105 
100 # Minimal fake context — _do_xpu_profiling reads op_name, testcase_name,106 # Minimal fake context — _do_xpu_profiling reads op_name, testcase_name,
101 # input_arrays, attributes; resolve fails before any of the collector path.107 # input_arrays, attributes; resolve fails before any of the collector path.
102- context = types.SimpleNamespace(108+ context = types.SimpleNamespace(op_name="add", testcase_name="add_case_0", input_arrays=(), attributes={})
103- op_name="add", testcase_name="add_case_0",
104- input_arrays=(), attributes={})
105 109 
106 # OpInfoKeeper / TestSpecManager lookups must not break the fail-loud path;110 # OpInfoKeeper / TestSpecManager lookups must not break the fail-loud path;
107 # the resolve failure happens after them, before collect_xpu_results.111 # the resolve failure happens after them, before collect_xpu_results.
108 with caplog.at_level(logging.ERROR):112 with caplog.at_level(logging.ERROR):
109 priority = prof._do_xpu_profiling(context, xpu_mode=0b11) # must NOT raise113 priority = prof._do_xpu_profiling(context, xpu_mode=0b11) # must NOT raise
110 114 
111- assert context.xpu_results == {} # empty: no provider dispatched115+ assert context.xpu_results == {} # empty: no provider dispatched
112- assert priority is None # resolve failed -> None priority116+ assert priority is None # resolve failed -> None priority
113 assert any("XPU resolve failed" in r.message for r in caplog.records)117 assert any("XPU resolve failed" in r.message for r in caplog.records)
@@ -20,7 +20,6 @@ from ttk.core_modules.npu.op.profiling_structure import (
20 20 
21 21 
22class TestComparisonResult:22class TestComparisonResult:
23- 
24 def test_set_and_get(self):23 def test_set_and_get(self):
25 cr = ComparisonResult(None)24 cr = ComparisonResult(None)
26 result = cr.set("d", "c", "b", "PASS")25 result = cr.set("d", "c", "b", "PASS")
@@ -32,13 +31,15 @@ class TestComparisonResult:
32 31 
33 32 
34class TestRTSProfilingResult:33class TestRTSProfilingResult:
35- 34+ @pytest.mark.parametrize(
36- @pytest.mark.parametrize("oob_value, expected_status", [35+ "oob_value, expected_status",
37- pytest.param("", "PASS", id="empty"),36+ [
38- pytest.param(None, "PASS", id="none"),37+ pytest.param("", "PASS", id="empty"),
39- pytest.param("PASS,SOMETHING", "PASS", id="pass_normal"),38+ pytest.param(None, "PASS", id="none"),
40- pytest.param("FAIL,SOMETHING", "FAIL", id="fail"),39+ pytest.param("PASS,SOMETHING", "PASS", id="pass_normal"),
41- ])40+ pytest.param("FAIL,SOMETHING", "FAIL", id="fail"),
41+ ],
42+ )
42 def test_oob_status(self, oob_value, expected_status):43 def test_oob_status(self, oob_value, expected_status):
43 """oob_status 属性:空/None/PASS 开头返回 PASS,FAIL 开头返回 FAIL。"""44 """oob_status 属性:空/None/PASS 开头返回 PASS,FAIL 开头返回 FAIL。"""
44 r = RTSProfilingResult(oob=oob_value)45 r = RTSProfilingResult(oob=oob_value)
@@ -12,6 +12,7 @@
12Covers _build_spec (Task 7). The _extract_spec_providers test is covered by12Covers _build_spec (Task 7). The _extract_spec_providers test is covered by
13test_profiling_config.py. No server fixture.13test_profiling_config.py. No server fixture.
14"""14"""
15+ 
15from ttk.core_modules.npu.op import profiling as prof16from ttk.core_modules.npu.op import profiling as prof
16 17 
17# ---- _build_spec (signature: provider, tp, spec_file, spec_class, op_name, op_type) ----18# ---- _build_spec (signature: provider, tp, spec_file, spec_class, op_name, op_type) ----
@@ -19,9 +20,14 @@ from ttk.core_modules.npu.op import profiling as prof
19 20 
20def test_build_spec_dict_api_string():21def test_build_spec_dict_api_string():
21 """third_party 为 dict 且值为 API 字符串时,spec.type 标记为 api。"""22 """third_party 为 dict 且值为 API 字符串时,spec.type 标记为 api。"""
22- spec = prof._build_spec("torch", {"torch": "torch.add", "tf": "tf.raw.ops.Add"},23+ spec = prof._build_spec(
23- spec_file=None, spec_class=None,24+ "torch",
24- op_name="add", op_type="Add")25+ {"torch": "torch.add", "tf": "tf.raw.ops.Add"},
26+ spec_file=None,
27+ spec_class=None,
28+ op_name="add",
29+ op_type="Add",
30+ )
25 assert spec.provider == "torch" and spec.type == "api" and spec.api == "torch.add"31 assert spec.provider == "torch" and spec.type == "api" and spec.api == "torch.add"
26 32 
27 33 
@@ -31,8 +37,8 @@ def test_build_spec_dict_impl_class_marks_spec_mode():
31 class _Dummy:37 class _Dummy:
32 pass38 pass
33 39 
34- spec = prof._build_spec("torch", {"torch": _Dummy},40+ spec = prof._build_spec(
35- spec_file="/tmp/s.py", spec_class="_SpecCls",41+ "torch", {"torch": _Dummy}, spec_file="/tmp/s.py", spec_class="_SpecCls", op_name="add", op_type="Add"
36- op_name="add", op_type="Add")42+ )
37 assert spec.provider == "torch" and spec.type == "spec"43 assert spec.provider == "torch" and spec.type == "spec"
38 assert spec.spec_file == "/tmp/s.py" and spec.spec_class == "_SpecCls"44 assert spec.spec_file == "/tmp/s.py" and spec.spec_class == "_SpecCls"
@@ -20,15 +20,19 @@ from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
20_tbe_mod.Opc = MagicMock20_tbe_mod.Opc = MagicMock
21 21 
22from ttk.core_modules.operator.op_interface import ( # noqa: E402, I00122from ttk.core_modules.operator.op_interface import ( # noqa: E402, I001
23- OperatorInterface, OperatorNotFoundError,23+ OperatorInterface,
24+ OperatorNotFoundError,
24)25)
25 26 
26 27 
27-def _make_testcase(op_name="Add", input_shapes=((8,), (8,)),28+def _make_testcase(
28- input_dtypes=("float16", "float16"),29+ op_name="Add",
29- output_shapes=((8,),),30+ input_shapes=((8,), (8,)),
30- output_dtypes=("float16",),31+ input_dtypes=("float16", "float16"),
31- **kwargs):32+ output_shapes=((8,),),
33+ output_dtypes=("float16",),
34+ **kwargs,
35+):
32 """构造一个填好默认字段的 TestcaseOp,便于各用例快速定制。"""36 """构造一个填好默认字段的 TestcaseOp,便于各用例快速定制。"""
33 case = TestcaseOp()37 case = TestcaseOp()
34 case.testcase_name = f"test_{op_name or 'None'}"38 case.testcase_name = f"test_{op_name or 'None'}"
@@ -81,10 +85,10 @@ def _mock_env(monkeypatch):
81@pytest.fixture(autouse=True)85@pytest.fixture(autouse=True)
82def _mock_singleton():86def _mock_singleton():
83 """提供一个装配好 mock Opc 的 OperatorInterface 单例。"""87 """提供一个装配好 mock Opc 的 OperatorInterface 单例。"""
84- p = patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper')88+ p = patch("ttk.core_modules.operator.op_info_keeper.OpInfoKeeper")
85 mock_cls = p.start()89 mock_cls = p.start()
86 mock_cls.return_value.info_of.return_value = None90 mock_cls.return_value.info_of.return_value = None
87- with patch('ttk.core_modules.operator.op_interface.Opc', return_value=_mock_opc()):91+ with patch("ttk.core_modules.operator.op_interface.Opc", return_value=_mock_opc()):
88 oi = OperatorInterface()92 oi = OperatorInterface()
89 oi._opc = _mock_opc()93 oi._opc = _mock_opc()
90 yield oi94 yield oi
@@ -156,12 +160,12 @@ class TestGetOpTypeFromSourceCode:
156 160 
157 def test_with_register_operator(self):161 def test_with_register_operator(self):
158 func = MagicMock()162 func = MagicMock()
159- with patch('inspect.getsource', return_value='@register_operator("AddOp")\ndef add(x, y): pass'):163+ with patch("inspect.getsource", return_value='@register_operator("AddOp")\ndef add(x, y): pass'):
160 assert OperatorInterface.get_op_type_from_source_code(func) == "AddOp"164 assert OperatorInterface.get_op_type_from_source_code(func) == "AddOp"
161 165 
162 def test_os_error(self):166 def test_os_error(self):
163 func = MagicMock()167 func = MagicMock()
164- with patch('inspect.getsource', side_effect=OSError):168+ with patch("inspect.getsource", side_effect=OSError):
165 assert OperatorInterface.get_op_type_from_source_code(func) is None169 assert OperatorInterface.get_op_type_from_source_code(func) is None
166 170 
167 171 
@@ -169,7 +173,7 @@ class TestGetOpFuncParams:
169 """get_op_func_params 在 OpInfo 缺失时抛 RuntimeError。"""173 """get_op_func_params 在 OpInfo 缺失时抛 RuntimeError。"""
170 174 
171 def test_runtime_error(self):175 def test_runtime_error(self):
172- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as m:176+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as m:
173 m.return_value.info_of.return_value = None177 m.return_value.info_of.return_value = None
174 with pytest.raises(RuntimeError, match="not configured"):178 with pytest.raises(RuntimeError, match="not configured"):
175 OperatorInterface.get_op_func_params(op_name="MissingOp")179 OperatorInterface.get_op_func_params(op_name="MissingOp")
@@ -180,15 +184,16 @@ class TestGetOpFuncParameterDict:
180 184 
181 def test_from_op_info(self):185 def test_from_op_info(self):
182 op_info = {186 op_info = {
183- "inputs": [{"name": "x", "paramType": "required"},187+ "inputs": [{"name": "x", "paramType": "required"}, {"name": "y", "paramType": "optional"}],
184- {"name": "y", "paramType": "optional"}],
185 "outputs": [{"name": "z"}],188 "outputs": [{"name": "z"}],
186- "attr": [{"name": "axis", "type": "int", "defaultValue": 1},189+ "attr": [
187- {"name": "keep_dims", "type": "bool", "defaultValue": None}],190+ {"name": "axis", "type": "int", "defaultValue": 1},
191+ {"name": "keep_dims", "type": "bool", "defaultValue": None},
192+ ],
188 }193 }
189 mock_oik = MagicMock()194 mock_oik = MagicMock()
190 mock_oik.info_of.return_value = op_info195 mock_oik.info_of.return_value = op_info
191- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper', return_value=mock_oik):196+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper", return_value=mock_oik):
192 result = OperatorInterface.get_op_func_parameter_dict(op_name="FakeOp_param")197 result = OperatorInterface.get_op_func_parameter_dict(op_name="FakeOp_param")
193 assert len(result) == 5198 assert len(result) == 5
194 assert result["y"].default is None199 assert result["y"].default is None
@@ -218,8 +223,7 @@ class TestAddPrivateAttrToOpInfo:
218 223 
219 def test_with_private_attrs(self):224 def test_with_private_attrs(self):
220 op_info = MagicMock()225 op_info = MagicMock()
221- OperatorInterface.add_private_attr_to_op_info(226+ OperatorInterface.add_private_attr_to_op_info(({"name": "axis"}, {"name": "scale"}), {"@scale": 0.5}, op_info)
222- ({"name": "axis"}, {"name": "scale"}), {"@scale": 0.5}, op_info)
223 op_info.private_attrs = {"name": "scale"}227 op_info.private_attrs = {"name": "scale"}
224 228 
225 229 
@@ -228,15 +232,16 @@ class TestConstructCompileContextOpInfo:
228 232 
229 def test_with_op_type(self, _mock_singleton):233 def test_with_op_type(self, _mock_singleton):
230 oi = _mock_singleton234 oi = _mock_singleton
231- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as m:235+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as m:
232 m.return_value.op_type_of.return_value = "AddOp"236 m.return_value.op_type_of.return_value = "AddOp"
233 oi._construct_compile_context_op_info(lambda: None, "Add", "kernel", {})237 oi._construct_compile_context_op_info(lambda: None, "Add", "kernel", {})
234 oi._opc.op_info.OpInfo.assert_called_with("AddOp", "AddOp")238 oi._opc.op_info.OpInfo.assert_called_with("AddOp", "AddOp")
235 239 
236 def test_unknown_op_type(self, _mock_singleton):240 def test_unknown_op_type(self, _mock_singleton):
237 oi = _mock_singleton241 oi = _mock_singleton
238- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as m, \242+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as m, patch.object(
239- patch.object(OperatorInterface, 'get_op_type_from_source_code', return_value=None):243+ OperatorInterface, "get_op_type_from_source_code", return_value=None
244+ ):
240 m.return_value.op_type_of.return_value = None245 m.return_value.op_type_of.return_value = None
241 oi._construct_compile_context_op_info(lambda: None, "Add", "kernel", {})246 oi._construct_compile_context_op_info(lambda: None, "Add", "kernel", {})
242 oi._opc.op_info.OpInfo.assert_called_with("UNKNOWN", "UNKNOWN")247 oi._opc.op_info.OpInfo.assert_called_with("UNKNOWN", "UNKNOWN")
@@ -245,7 +250,7 @@ class TestConstructCompileContextOpInfo:
245 oi = _mock_singleton250 oi = _mock_singleton
246 mock_oi = MagicMock()251 mock_oi = MagicMock()
247 oi._opc.op_info.OpInfo.return_value = mock_oi252 oi._opc.op_info.OpInfo.return_value = mock_oi
248- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as m:253+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as m:
249 m.return_value.op_type_of.return_value = "AddOp"254 m.return_value.op_type_of.return_value = "AddOp"
250 oi._construct_compile_context_op_info(lambda: None, "Add", "kernel", {"impl_mode": "hp"})255 oi._construct_compile_context_op_info(lambda: None, "Add", "kernel", {"impl_mode": "hp"})
251 assert mock_oi.precision_mode == "hp"256 assert mock_oi.precision_mode == "hp"
@@ -265,7 +270,7 @@ class TestCompileOp:
265 def test_compile_op(self, _mock_singleton, mode, func, expect_raises, match):270 def test_compile_op(self, _mock_singleton, mode, func, expect_raises, match):
266 oi = _mock_singleton271 oi = _mock_singleton
267 mock_gs = MagicMock(kernel_compile_options=[])272 mock_gs = MagicMock(kernel_compile_options=[])
268- with patch('ttk.core_modules.operator.op_interface.get_global_storage', return_value=mock_gs):273+ with patch("ttk.core_modules.operator.op_interface.get_global_storage", return_value=mock_gs):
269 if expect_raises:274 if expect_raises:
270 with pytest.raises(RuntimeError, match=match):275 with pytest.raises(RuntimeError, match=match):
271 oi._compile_op(mode, "Add", func, ("x",), [], {})276 oi._compile_op(mode, "Add", func, ("x",), [], {})
@@ -283,7 +288,7 @@ class TestSetCommonCompileContext:
283 cxt.get_op_mode.return_value = "dynamic"288 cxt.get_op_mode.return_value = "dynamic"
284 case = _make_testcase()289 case = _make_testcase()
285 case.kb_pid = 42290 case.kb_pid = 42
286- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as m:291+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as m:
287 m.return_value.op_type_of.return_value = "AddOp"292 m.return_value.op_type_of.return_value = "AddOp"
288 oi.set_common_compile_context(cxt, case, MagicMock(), "kernel")293 oi.set_common_compile_context(cxt, case, MagicMock(), "kernel")
289 cxt.add_addition.assert_any_call("master_pid", 42)294 cxt.add_addition.assert_any_call("master_pid", 42)
@@ -300,8 +305,9 @@ class TestSetDynamicCompileContext:
300 cxt.get_op_info.return_value = [MagicMock(), mock_op_info]305 cxt.get_op_info.return_value = [MagicMock(), mock_op_info]
301 case = _make_testcase(attributes={"axis": 1})306 case = _make_testcase(attributes={"axis": 1})
302 dyn_params = ({"shape": (-1,)},) * 3307 dyn_params = ({"shape": (-1,)},) * 3
303- with patch.object(oi, 'set_common_compile_context'), \308+ with patch.object(oi, "set_common_compile_context"), patch(
304- patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as m:309+ "ttk.core_modules.operator.op_interface.OpInfoKeeper"
310+ ) as m:
305 m.return_value.info_of.return_value = None311 m.return_value.info_of.return_value = None
306 oi.set_dynamic_compile_context(cxt, case, MagicMock(), "kernel", dyn_params)312 oi.set_dynamic_compile_context(cxt, case, MagicMock(), "kernel", dyn_params)
307 assert mock_op_info.inputs is not None313 assert mock_op_info.inputs is not None
@@ -312,7 +318,7 @@ class TestGetDynOperator:
312 318 
313 def test_not_found(self, _mock_singleton):319 def test_not_found(self, _mock_singleton):
314 oi = _mock_singleton320 oi = _mock_singleton
315- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as m:321+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as m:
316 m.return_value.get_operator_function.return_value = None322 m.return_value.get_operator_function.return_value = None
317 assert oi.get_dyn_operator(_make_testcase()) is None323 assert oi.get_dyn_operator(_make_testcase()) is None
318 324 
@@ -322,23 +328,27 @@ class TestCompileDynamicShape:
322 328 
323 def test_operator_not_found(self, _mock_singleton):329 def test_operator_not_found(self, _mock_singleton):
324 oi = _mock_singleton330 oi = _mock_singleton
325- with patch.object(oi, 'get_dyn_operator', return_value=None):331+ with patch.object(oi, "get_dyn_operator", return_value=None):
326 assert oi.compile_dynamic_shape((), _make_testcase(), "k") is None332 assert oi.compile_dynamic_shape((), _make_testcase(), "k") is None
327 333 
328 def test_compile_success(self, _mock_singleton):334 def test_compile_success(self, _mock_singleton):
329 oi = _mock_singleton335 oi = _mock_singleton
330 case = _make_testcase()336 case = _make_testcase()
331 mock_gs = MagicMock(auto_switch=False, kernel_compile_options=[])337 mock_gs = MagicMock(auto_switch=False, kernel_compile_options=[])
332- dyn_params = ({"shape": (-1,), "dtype": "float16"}, {"shape": (-1,), "dtype": "float16"},338+ dyn_params = (
333- {"shape": (-1,), "dtype": "float16"})339+ {"shape": (-1,), "dtype": "float16"},
334- with patch.object(oi, 'get_dyn_operator', return_value=MagicMock()), \340+ {"shape": (-1,), "dtype": "float16"},
335- patch.object(OperatorInterface, 'get_op_func_params', return_value=("x",)), \341+ {"shape": (-1,), "dtype": "float16"},
336- patch('ttk.core_modules.operator.op_interface.get_global_storage', return_value=mock_gs), \342+ )
337- patch('ttk.core_modules.operator.op_interface.OpInfoKeeper'), \343+ with patch.object(oi, "get_dyn_operator", return_value=MagicMock()), patch.object(
338- patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper') as mock_oi2, \344+ OperatorInterface, "get_op_func_params", return_value=("x",)
339- patch.object(oi, 'set_dynamic_compile_context'), \345+ ), patch("ttk.core_modules.operator.op_interface.get_global_storage", return_value=mock_gs), patch(
340- patch.object(oi, '_compile_op', return_value=0.1), \346+ "ttk.core_modules.operator.op_interface.OpInfoKeeper"
341- patch.object(OperatorInterface, 'construct_optiling_attrs', return_value=()):347+ ), patch("ttk.core_modules.operator.op_info_keeper.OpInfoKeeper") as mock_oi2, patch.object(
348+ oi, "set_dynamic_compile_context"
349+ ), patch.object(oi, "_compile_op", return_value=0.1), patch.object(
350+ OperatorInterface, "construct_optiling_attrs", return_value=()
351+ ):
342 mock_oi2.return_value.info_of.return_value = None352 mock_oi2.return_value.info_of.return_value = None
343 result = oi.compile_dynamic_shape(dyn_params, case, "k")353 result = oi.compile_dynamic_shape(dyn_params, case, "k")
344 assert result is not None354 assert result is not None
@@ -347,19 +357,18 @@ class TestCompileDynamicShape:
347class TestPrepareOperatorParametersConst:357class TestPrepareOperatorParametersConst:
348 """prepare_operator_parameters_const 覆盖普通/None/常量/TensorList 输入与输出组合。"""358 """prepare_operator_parameters_const 覆盖普通/None/常量/TensorList 输入与输出组合。"""
349 359 
350- def _run(self, oi, case, op_info=None, op_output_defined=False,360+ def _run(self, oi, case, op_info=None, op_output_defined=False, func_params=("x", "y"), param_transform=None):
351- func_params=("x", "y"), param_transform=None):
352 """Helper to run prepare_operator_parameters_const with mocks."""361 """Helper to run prepare_operator_parameters_const with mocks."""
353 mocks = [362 mocks = [
354- patch.object(oi, 'get_dyn_operator', return_value=MagicMock()),363+ patch.object(oi, "get_dyn_operator", return_value=MagicMock()),
355- patch.object(OperatorInterface, 'get_op_func_params', return_value=func_params),364+ patch.object(OperatorInterface, "get_op_func_params", return_value=func_params),
356- patch('ttk.core_modules.operator.op_interface.OpInfoKeeper'),365+ patch("ttk.core_modules.operator.op_interface.OpInfoKeeper"),
357- patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper'),366+ patch("ttk.core_modules.operator.op_info_keeper.OpInfoKeeper"),
358 ]367 ]
359 if param_transform is not None:368 if param_transform is not None:
360 mocks.append(369 mocks.append(
361- patch('ttk.core_modules.operator.op_interface.param_transformation',370+ patch("ttk.core_modules.operator.op_interface.param_transformation", return_value=param_transform)
362- return_value=param_transform))371+ )
363 with mocks[0], mocks[1], mocks[2] as m_oi, mocks[3] as m_oi2:372 with mocks[0], mocks[1], mocks[2] as m_oi, mocks[3] as m_oi2:
364 m_oi.return_value.op_output_defined.return_value = op_output_defined373 m_oi.return_value.op_output_defined.return_value = op_output_defined
365 m_oi2.return_value.info_of.return_value = op_info374 m_oi2.return_value.info_of.return_value = op_info
@@ -370,7 +379,7 @@ class TestPrepareOperatorParametersConst:
370 379 
371 def test_operator_not_found(self, _mock_singleton):380 def test_operator_not_found(self, _mock_singleton):
372 oi = _mock_singleton381 oi = _mock_singleton
373- with patch.object(oi, 'get_dyn_operator', return_value=None):382+ with patch.object(oi, "get_dyn_operator", return_value=None):
374 with pytest.raises(OperatorNotFoundError):383 with pytest.raises(OperatorNotFoundError):
375 oi.prepare_operator_parameters_const(_make_testcase())384 oi.prepare_operator_parameters_const(_make_testcase())
376 385 
@@ -411,11 +420,8 @@ class TestPrepareOperatorParametersConst:
411 "inputs": [{"name": "x"}, {"name": "y", "valueDepend": "required"}],420 "inputs": [{"name": "x"}, {"name": "y", "valueDepend": "required"}],
412 "outputs": [{"name": "z"}],421 "outputs": [{"name": "z"}],
413 }422 }
414- case = _make_testcase(423+ case = _make_testcase(input_shapes=((3, 4), (2,)), attributes={"y": [1, 2]})
415- input_shapes=((3, 4), (2,)),424+ ipt, opt = self._run(oi, case, op_info=op_info, op_output_defined=False, param_transform={"y": [1, 2]})
416- attributes={"y": [1, 2]})
417- ipt, opt = self._run(oi, case, op_info=op_info, op_output_defined=False,
418- param_transform={"y": [1, 2]})
419 assert "const_value" in ipt[1]425 assert "const_value" in ipt[1]
420 assert ipt[1]["name"] == "y"426 assert ipt[1]["name"] == "y"
421 assert ipt[1]["dtype"] == "float16"427 assert ipt[1]["dtype"] == "float16"
@@ -428,8 +434,7 @@ class TestPrepareOperatorParametersConst:
428 input_dtypes=(("float16", "float16"), "float16"),434 input_dtypes=(("float16", "float16"), "float16"),
429 output_shapes=((7, 6),),435 output_shapes=((7, 6),),
430 )436 )
431- ipt, opt = self._run(oi, case, op_output_defined=True,437+ ipt, opt = self._run(oi, case, op_output_defined=True, func_params=("x", "y"))
432- func_params=("x", "y"))
433 assert isinstance(ipt[0], tuple)438 assert isinstance(ipt[0], tuple)
434 assert len(ipt[0]) == 2439 assert len(ipt[0]) == 2
435 assert ipt[0][0]["shape"] == (3, 4)440 assert ipt[0][0]["shape"] == (3, 4)
@@ -446,8 +451,7 @@ class TestPrepareOperatorParametersConst:
446 output_shapes=(((2, 3), (4, 5)),),451 output_shapes=(((2, 3), (4, 5)),),
447 output_dtypes=(("float16", "float16"),),452 output_dtypes=(("float16", "float16"),),
448 )453 )
449- ipt, opt = self._run(oi, case, op_output_defined=True,454+ ipt, opt = self._run(oi, case, op_output_defined=True, func_params=("x",))
450- func_params=("x",))
451 assert isinstance(opt[0], tuple)455 assert isinstance(opt[0], tuple)
452 assert len(opt[0]) == 2456 assert len(opt[0]) == 2
453 assert opt[0][0]["shape"] == (2, 3)457 assert opt[0][0]["shape"] == (2, 3)
@@ -456,9 +460,7 @@ class TestPrepareOperatorParametersConst:
456 def test_const_plus_tl_mixed(self, _mock_singleton):460 def test_const_plus_tl_mixed(self, _mock_singleton):
457 oi = _mock_singleton461 oi = _mock_singleton
458 op_info = {462 op_info = {
459- "inputs": [{"name": "x", "valueDepend": "required"},463+ "inputs": [{"name": "x", "valueDepend": "required"}, {"name": "y"}, {"name": "z"}],
460- {"name": "y"},
461- {"name": "z"}],
462 "outputs": [{"name": "out"}],464 "outputs": [{"name": "out"}],
463 }465 }
464 case = _make_testcase(466 case = _make_testcase(
@@ -467,9 +469,14 @@ class TestPrepareOperatorParametersConst:
467 output_shapes=((7, 6),),469 output_shapes=((7, 6),),
468 attributes={"x": [[1, 2, 3], [4, 5, 6]]},470 attributes={"x": [[1, 2, 3], [4, 5, 6]]},
469 )471 )
470- ipt, opt = self._run(oi, case, op_info=op_info, op_output_defined=False,472+ ipt, opt = self._run(
471- func_params=("x", "y", "z"),473+ oi,
472- param_transform={"x": [[1, 2, 3], [4, 5, 6]]})474+ case,
475+ op_info=op_info,
476+ op_output_defined=False,
477+ func_params=("x", "y", "z"),
478+ param_transform={"x": [[1, 2, 3], [4, 5, 6]]},
479+ )
473 # pos0: const480 # pos0: const
474 assert "const_value" in ipt[0]481 assert "const_value" in ipt[0]
475 assert ipt[0]["name"] == "x"482 assert ipt[0]["name"] == "x"
@@ -489,8 +496,7 @@ class TestPrepareOperatorParametersConst:
489 output_shapes=(((2, 3), (4, 5)),),496 output_shapes=(((2, 3), (4, 5)),),
490 output_dtypes=(("float16", "float16"),),497 output_dtypes=(("float16", "float16"),),
491 )498 )
492- ipt, opt = self._run(oi, case, op_output_defined=True,499+ ipt, opt = self._run(oi, case, op_output_defined=True, func_params=("x", "y"))
493- func_params=("x", "y"))
494 assert isinstance(ipt[0], tuple)500 assert isinstance(ipt[0], tuple)
495 assert len(ipt[0]) == 2501 assert len(ipt[0]) == 2
496 assert isinstance(opt[0], tuple)502 assert isinstance(opt[0], tuple)
@@ -505,9 +511,11 @@ class TestPrepareTilingParams:
505 def test_basic(self, _mock_singleton):511 def test_basic(self, _mock_singleton):
506 oi = _mock_singleton512 oi = _mock_singleton
507 case = _make_testcase()513 case = _make_testcase()
508- with patch.object(OperatorInterface, 'construct_optiling_attrs', return_value=()), \514+ with patch.object(OperatorInterface, "construct_optiling_attrs", return_value=()), patch.object(
509- patch.object(oi, 'prepare_operator_parameters_const',515+ oi,
510- return_value=(({"shape": (8,), "range": (None, None)},), ({"shape": (8,)},))):516+ "prepare_operator_parameters_const",
517+ return_value=(({"shape": (8,), "range": (None, None)},), ({"shape": (8,)},)),
518+ ):
511 inputs, outputs, attrs = oi.prepare_tiling_params(case)519 inputs, outputs, attrs = oi.prepare_tiling_params(case)
512 assert len(attrs) == 0520 assert len(attrs) == 0
513 521 
@@ -531,12 +539,11 @@ class TestCallConstOpTiling:
531 else:539 else:
532 oi._opc.do_op_tiling.return_value = tiling_side_effect540 oi._opc.do_op_tiling.return_value = tiling_side_effect
533 mock_gs = MagicMock(tiling_run_time=1)541 mock_gs = MagicMock(tiling_run_time=1)
534- with patch('ttk.core_modules.operator.op_interface.get_global_storage', return_value=mock_gs), \542+ with patch("ttk.core_modules.operator.op_interface.get_global_storage", return_value=mock_gs), patch.object(
535- patch.object(oi, 'prepare_tiling_params',543+ oi, "prepare_tiling_params", return_value=(({"shape": (8,)},), ({"shape": (8,)},), ())
536- return_value=(({"shape": (8,)},), ({"shape": (8,)},), ())), \544+ ), patch("ttk.core_modules.operator.op_interface.adapter_before_tiling"), patch(
537- patch('ttk.core_modules.operator.op_interface.adapter_before_tiling'), \545+ "os.path.expanduser", return_value="/tmp/plog"
538- patch('os.path.expanduser', return_value="/tmp/plog"), \546+ ), patch("glob.glob", return_value=[]):
539- patch('glob.glob', return_value=[]):
540 if expected is None:547 if expected is None:
541 result = oi.call_const_op_tiling(mock_cr, _make_testcase())548 result = oi.call_const_op_tiling(mock_cr, _make_testcase())
542 assert "tiling_time" in result549 assert "tiling_time" in result
@@ -558,6 +565,7 @@ class TestAdapterBeforeTiling:
558 )565 )
559 def test_adapter_before_tiling(self, op_type_list, attributes, fi, expect_transform):566 def test_adapter_before_tiling(self, op_type_list, attributes, fi, expect_transform):
560 from ttk.core_modules.operator.op_interface import adapter_before_tiling567 from ttk.core_modules.operator.op_interface import adapter_before_tiling
568+ 
561 case = _make_testcase(attributes=attributes)569 case = _make_testcase(attributes=attributes)
562 cr = MagicMock(compile_info={"tiling_type": "binary", "op_type_list": op_type_list})570 cr = MagicMock(compile_info={"tiling_type": "binary", "op_type_list": op_type_list})
563 fo = [None] if fi else ()571 fo = [None] if fi else ()
@@ -5,6 +5,7 @@
5# CANN Open Software License Agreement Version 2.0 (the "License").5# CANN Open Software License Agreement Version 2.0 (the "License").
6# Please refer to the License for details. You may not use this file except in compliance with the License.6# Please refer to the License for details. You may not use this file except in compliance with the License.
7"""Unit tests for the NPUSim simulator backend (no device / simulation needed)."""7"""Unit tests for the NPUSim simulator backend (no device / simulation needed)."""
8+ 
8import json9import json
9import os10import os
10import pickle11import pickle
@@ -20,6 +21,7 @@ from ttk.utilities.classes import MODE, SWITCHES
20 21 
21class _DummyRTSProfilingParam:22class _DummyRTSProfilingParam:
22 """Minimal stand-in for RTSProfilingParam (module-level: picklable)."""23 """Minimal stand-in for RTSProfilingParam (module-level: picklable)."""
24+ 
23 switch = True25 switch = True
24 compile_result = "SUCC"26 compile_result = "SUCC"
25 is_valid = True27 is_valid = True
@@ -172,8 +174,7 @@ class TestResultLoading:
172 mdir = tmp_path / "dyn"174 mdir = tmp_path / "dyn"
173 if scenario == "ok":175 if scenario == "ok":
174 mdir.mkdir()176 mdir.mkdir()
175- (mdir / "result.json").write_text(177+ (mdir / "result.json").write_text(json.dumps({"ok": True, "cycle": "UNKNOWN", "oob": "OK,OK"}))
176- json.dumps({"ok": True, "cycle": "UNKNOWN", "oob": "OK,OK"}))
177 (mdir / "output_0.bin").write_bytes(b"\x00\x01")178 (mdir / "output_0.bin").write_bytes(b"\x00\x01")
178 (mdir / "output_1.bin").write_bytes(b"\x02\x03")179 (mdir / "output_1.bin").write_bytes(b"\x02\x03")
179 res = _load_mode_result(tmp_path, "dyn")180 res = _load_mode_result(tmp_path, "dyn")
@@ -182,8 +183,7 @@ class TestResultLoading:
182 assert res.oob == "OK,OK"183 assert res.oob == "OK,OK"
183 elif scenario == "failure":184 elif scenario == "failure":
184 mdir.mkdir()185 mdir.mkdir()
185- (mdir / "result.json").write_text(186+ (mdir / "result.json").write_text(json.dumps({"ok": False, "error": "SIM_EXECUTION_FAILED"}))
186- json.dumps({"ok": False, "error": "SIM_EXECUTION_FAILED"}))
187 res = _load_mode_result(tmp_path, "dyn")187 res = _load_mode_result(tmp_path, "dyn")
188 assert isinstance(res, RTSProfilingResult)188 assert isinstance(res, RTSProfilingResult)
189 assert res.cycle == "SIM_EXECUTION_FAILED"189 assert res.cycle == "SIM_EXECUTION_FAILED"
@@ -197,9 +197,8 @@ class TestResultLoading:
197 # A wrapper crash must surface its traceback, not a bare MISSING.197 # A wrapper crash must surface its traceback, not a bare MISSING.
198 (tmp_path / "dyn").mkdir()198 (tmp_path / "dyn").mkdir()
199 (tmp_path / "wrapper_error.json").write_text(199 (tmp_path / "wrapper_error.json").write_text(
200- 'Traceback (most recent call last):\n'200+ 'Traceback (most recent call last):\n File "wrapper.py", line 12, in main\nRuntimeError: boom'
201- ' File "wrapper.py", line 12, in main\n'201+ )
202- 'RuntimeError: boom')
203 res = _load_mode_result(tmp_path, "dyn")202 res = _load_mode_result(tmp_path, "dyn")
204 assert res.cycle.startswith("SIM_RESULT_MISSING")203 assert res.cycle.startswith("SIM_RESULT_MISSING")
205 assert "RuntimeError: boom" in res.cycle204 assert "RuntimeError: boom" in res.cycle
@@ -214,9 +213,17 @@ class TestResultLoading:
214 from ttk.core_modules.simulator.sim_profiling import _load_aclnn_result213 from ttk.core_modules.simulator.sim_profiling import _load_aclnn_result
215 214 
216 if scenario == "ok":215 if scenario == "ok":
217- (tmp_path / "final_result.json").write_text(json.dumps(216+ (tmp_path / "final_result.json").write_text(
218- {"ok": True, "api_prof": "UNKNOWN", "op_prof": "TOTAL_CYCLE_TODO",217+ json.dumps(
219- "oob": "UNKNOWN", "deterministic_status": None}))218+ {
219+ "ok": True,
220+ "api_prof": "UNKNOWN",
221+ "op_prof": "TOTAL_CYCLE_TODO",
222+ "oob": "UNKNOWN",
223+ "deterministic_status": None,
224+ }
225+ )
226+ )
220 (tmp_path / "output_0.bin").write_bytes(b"\x00\x01")227 (tmp_path / "output_0.bin").write_bytes(b"\x00\x01")
221 with (tmp_path / "output_view_shapes.pkl").open("wb") as f:228 with (tmp_path / "output_view_shapes.pkl").open("wb") as f:
222 pickle.dump([("float32", [1, 2])], f)229 pickle.dump([("float32", [1, 2])], f)
@@ -443,8 +450,7 @@ class TestCannsimBackend:
443 from ttk.core_modules.simulator import npusim_runner450 from ttk.core_modules.simulator import npusim_runner
444 451 
445 fake_script = Path("/opt/ascend/bin/cannsim")452 fake_script = Path("/opt/ascend/bin/cannsim")
446- monkeypatch.setattr(npusim_runner, "locate_cannsim_executable",453+ monkeypatch.setattr(npusim_runner, "locate_cannsim_executable", lambda: fake_script)
447- lambda: fake_script)
448 cmd = npusim_runner._cannsim_cmd()454 cmd = npusim_runner._cannsim_cmd()
449 assert cmd == [sys.executable, str(fake_script)]455 assert cmd == [sys.executable, str(fake_script)]
450 456 
@@ -484,8 +490,7 @@ class TestSimRecordFailure:
484 sw = SWITCHES()490 sw = SWITCHES()
485 sw.sim_output_dir = str(tmp_path)491 sw.sim_output_dir = str(tmp_path)
486 monkeypatch.setattr(sim_mod, "get_global_storage", lambda: sw)492 monkeypatch.setattr(sim_mod, "get_global_storage", lambda: sw)
487- monkeypatch.setattr(sim_mod, "_construct_param",493+ monkeypatch.setattr(sim_mod, "_construct_param", lambda *a, **k: _DummyRTSProfilingParam())
488- lambda *a, **k: _DummyRTSProfilingParam())
489 monkeypatch.setattr(sim_mod, "_validate_param", lambda p: None)494 monkeypatch.setattr(sim_mod, "_validate_param", lambda p: None)
490 495 
491 def _boom(*a, **k):496 def _boom(*a, **k):
@@ -22,6 +22,7 @@ Tests cover:
227. No device: use_device=False, no lock acquired, all run concurrently227. No device: use_device=False, no lock acquired, all run concurrently
238. No device + kill: use_device=False with child kill238. No device + kill: use_device=False with child kill
24"""24"""
25+ 
25import multiprocessing as mp26import multiprocessing as mp
26import time27import time
27 28 
@@ -43,13 +44,11 @@ class MockProcessContext:
43 self.pipe_w.send(("RELEASED", device_id))44 self.pipe_w.send(("RELEASED", device_id))
44 45 
45 46 
46-def child_work(pipe_w, idx, device_id, use_device=True,47+def child_work(pipe_w, idx, device_id, use_device=True, grant_event=None, granted_idx=None, work_time=2):
47- grant_event=None, granted_idx=None, work_time=2):
48 """Acquire lock via DeviceLock, work, release."""48 """Acquire lock via DeviceLock, work, release."""
49 try:49 try:
50 mock_ctx = MockProcessContext(pipe_w)50 mock_ctx = MockProcessContext(pipe_w)
51- lock = DeviceLock(mock_ctx, device_id, use_device=use_device,51+ lock = DeviceLock(mock_ctx, device_id, use_device=use_device, grant_event=grant_event, granted_idx=granted_idx)
52- grant_event=grant_event, granted_idx=granted_idx)
53 with lock:52 with lock:
54 pipe_w.send(("ACQUIRED", idx, device_id))53 pipe_w.send(("ACQUIRED", idx, device_id))
55 time.sleep(work_time)54 time.sleep(work_time)
@@ -57,13 +56,11 @@ def child_work(pipe_w, idx, device_id, use_device=True,
57 pipe_w.send(("ERROR", idx, str(e)))56 pipe_w.send(("ERROR", idx, str(e)))
58 57 
59 58 
60-def run_test(test_name, num_children, num_devices, kill_plan,59+def run_test(test_name, num_children, num_devices, kill_plan, use_device=True, timeout=15):
61- use_device=True, timeout=15):
62 """启动若干子进程并按 kill_plan 杀进程,验证 DeviceLockManager 的获取/释放/回收语义。"""60 """启动若干子进程并按 kill_plan 杀进程,验证 DeviceLockManager 的获取/释放/回收语义。"""
63 print(f"{'=' * 60}")61 print(f"{'=' * 60}")
64 print(f"Test: {test_name}")62 print(f"Test: {test_name}")
65- print(f" children={num_children}, devices={num_devices}, "63+ print(f" children={num_children}, devices={num_devices}, use_device={use_device}, kill_plan={kill_plan}")
66- f"use_device={use_device}, kill_plan={kill_plan}")
67 print(f"{'=' * 60}")64 print(f"{'=' * 60}")
68 65 
69 DeviceLockManager.lock_holders.clear()66 DeviceLockManager.lock_holders.clear()
@@ -87,13 +84,12 @@ def run_test(test_name, num_children, num_devices, kill_plan,
87 for i in range(num_children):84 for i in range(num_children):
88 dev = i % max(num_devices, 1)85 dev = i % max(num_devices, 1)
89 pipe_r, pipe_w = mp.Pipe()86 pipe_r, pipe_w = mp.Pipe()
90- p = mp.Process(target=child_work, args=(87+ p = mp.Process(
91- pipe_w, i, dev, use_device,88+ target=child_work, args=(pipe_w, i, dev, use_device, grant_events.get(dev), granted_indices.get(dev))
92- grant_events.get(dev), granted_indices.get(dev)))89+ )
93 p.start()90 p.start()
94 pipes.append((pipe_r, pipe_w))91 pipes.append((pipe_r, pipe_w))
95- procs.append({"proc": p, "dev": dev, "idx": i, "dead_processed": False,92+ procs.append({"proc": p, "dev": dev, "idx": i, "dead_processed": False, "lock_id": None})
96- "lock_id": None})
97 93 
98 acquired_order = []94 acquired_order = []
99 released_order = []95 released_order = []
@@ -109,8 +105,7 @@ def run_test(test_name, num_children, num_devices, kill_plan,
109 if kill_plan:105 if kill_plan:
110 for delay, child_idx in kill_plan:106 for delay, child_idx in kill_plan:
111 if child_idx not in killed_plan and elapsed >= delay:107 if child_idx not in killed_plan and elapsed >= delay:
112- print(f" [{elapsed:.1f}s] >>> KILL child {child_idx} "108+ print(f" [{elapsed:.1f}s] >>> KILL child {child_idx} (pid={procs[child_idx]['proc'].pid}) <<<")
113- f"(pid={procs[child_idx]['proc'].pid}) <<<")
114 procs[child_idx]["proc"].kill()109 procs[child_idx]["proc"].kill()
115 killed_plan.add(child_idx)110 killed_plan.add(child_idx)
116 111 
@@ -121,8 +116,7 @@ def run_test(test_name, num_children, num_devices, kill_plan,
121 if i not in killed:116 if i not in killed:
122 killed.add(i)117 killed.add(i)
123 if use_device:118 if use_device:
124- DeviceLockManager.on_process_dead(119+ DeviceLockManager.on_process_dead(procs[i], grant_events, granted_indices)
125- procs[i], grant_events, granted_indices)
126 continue120 continue
127 121 
128 if procs[i]["proc"].exitcode is not None:122 if procs[i]["proc"].exitcode is not None:
@@ -134,8 +128,8 @@ def run_test(test_name, num_children, num_devices, kill_plan,
134 _, lock_id, dev_id = msg128 _, lock_id, dev_id = msg
135 procs[i]["lock_id"] = lock_id129 procs[i]["lock_id"] = lock_id
136 DeviceLockManager.try_grant(130 DeviceLockManager.try_grant(
137- procs[i], dev_id, lock_id,131+ procs[i], dev_id, lock_id, grant_events[dev_id], granted_indices[dev_id]
138- grant_events[dev_id], granted_indices[dev_id])132+ )
139 elif msg[0] == "ACQUIRED":133 elif msg[0] == "ACQUIRED":
140 acquired_order.append(msg[1])134 acquired_order.append(msg[1])
141 print(f" [{elapsed:.1f}s] child {msg[1]} acquired dev {msg[2]}")135 print(f" [{elapsed:.1f}s] child {msg[1]} acquired dev {msg[2]}")
@@ -143,9 +137,7 @@ def run_test(test_name, num_children, num_devices, kill_plan,
143 dev_id = msg[1]137 dev_id = msg[1]
144 released_order.append(dev_id)138 released_order.append(dev_id)
145 print(f" [{elapsed:.1f}s] child {i} released dev {dev_id}")139 print(f" [{elapsed:.1f}s] child {i} released dev {dev_id}")
146- DeviceLockManager.release(140+ DeviceLockManager.release(procs[i], dev_id, grant_events[dev_id], granted_indices[dev_id])
147- procs[i], dev_id,
148- grant_events[dev_id], granted_indices[dev_id])
149 elif msg[0] == "ERROR":141 elif msg[0] == "ERROR":
150 print(f" [{elapsed:.1f}s] child {msg[1]} ERROR: {msg[2]}")142 print(f" [{elapsed:.1f}s] child {msg[1]} ERROR: {msg[2]}")
151 143 
@@ -153,8 +145,7 @@ def run_test(test_name, num_children, num_devices, kill_plan,
153 alive = sum(1 for p in procs if not p["dead_processed"])145 alive = sum(1 for p in procs if not p["dead_processed"])
154 if alive == 0:146 if alive == 0:
155 break147 break
156- if use_device and len(acquired_order) >= num_children and \148+ if use_device and len(acquired_order) >= num_children and len(released_order) + len(killed) >= num_children:
157- len(released_order) + len(killed) >= num_children:
158 break149 break
159 150 
160 # Collect results151 # Collect results
@@ -179,8 +170,7 @@ def run_test(test_name, num_children, num_devices, kill_plan,
179 print(f"\n Acquired order: {acquired_order}")170 print(f"\n Acquired order: {acquired_order}")
180 print(f" Released: {released_order}")171 print(f" Released: {released_order}")
181 print(f" Killed: {sorted(killed_plan)}")172 print(f" Killed: {sorted(killed_plan)}")
182- print(f" Non-killed acquisitions: {len(set(non_killed_acquired))} "173+ print(f" Non-killed acquisitions: {len(set(non_killed_acquired))} (need >= {num_children - len(killed_plan)})")
183- f"(need >= {num_children - len(killed_plan)})")
184 print(f" Result: {'PASS' if success else 'FAIL'}")174 print(f" Result: {'PASS' if success else 'FAIL'}")
185 print()175 print()
186 return success176 return success
@@ -190,7 +180,8 @@ def test_1_basic():
190 """基础:杀 1 个持锁子进程后,下一个等待者能获取锁。"""180 """基础:杀 1 个持锁子进程后,下一个等待者能获取锁。"""
191 assert run_test(181 assert run_test(
192 "Basic: kill 1 child, verify next acquires",182 "Basic: kill 1 child, verify next acquires",
193- num_children=4, num_devices=1,183+ num_children=4,
184+ num_devices=1,
194 kill_plan=[(1.5, 0)],185 kill_plan=[(1.5, 0)],
195 )186 )
196 187 
@@ -199,7 +190,8 @@ def test_2_sequential_kills():
199 """连续杀 2 个持锁子进程,锁逐个回收给后续等待者。"""190 """连续杀 2 个持锁子进程,锁逐个回收给后续等待者。"""
200 assert run_test(191 assert run_test(
201 "Sequential kills: kill child 0 at 1s, child 1 at 3s",192 "Sequential kills: kill child 0 at 1s, child 1 at 3s",
202- num_children=4, num_devices=1,193+ num_children=4,
194+ num_devices=1,
203 kill_plan=[(1.0, 0), (3.0, 1)],195 kill_plan=[(1.0, 0), (3.0, 1)],
204 )196 )
205 197 
@@ -208,7 +200,8 @@ def test_3_multi_device():
208 """多设备:2 个设备各自独立锁状态,互不干扰。"""200 """多设备:2 个设备各自独立锁状态,互不干扰。"""
209 assert run_test(201 assert run_test(
210 "Multi-device: 2 devices, kill 1 on each",202 "Multi-device: 2 devices, kill 1 on each",
211- num_children=4, num_devices=2,203+ num_children=4,
204+ num_devices=2,
212 kill_plan=[(1.0, 0), (1.0, 1)],205 kill_plan=[(1.0, 0), (1.0, 1)],
213 )206 )
214 207 
@@ -217,7 +210,8 @@ def test_4_stress():
217 """压力:8 个子进程 1 设备,连续杀前 4 个,剩余均能获取锁。"""210 """压力:8 个子进程 1 设备,连续杀前 4 个,剩余均能获取锁。"""
218 assert run_test(211 assert run_test(
219 "Stress: 8 children, 1 device, kill first 4 sequentially",212 "Stress: 8 children, 1 device, kill first 4 sequentially",
220- num_children=8, num_devices=1,213+ num_children=8,
214+ num_devices=1,
221 kill_plan=[(1.0, 0), (2.0, 1), (3.0, 2), (4.0, 3)],215 kill_plan=[(1.0, 0), (2.0, 1), (3.0, 2), (4.0, 3)],
222 timeout=20,216 timeout=20,
223 )217 )
@@ -227,7 +221,8 @@ def test_5_normal():
227 """正常路径:无杀进程,所有子进程依次获取并释放锁。"""221 """正常路径:无杀进程,所有子进程依次获取并释放锁。"""
228 assert run_test(222 assert run_test(
229 "Normal: no kills, all acquire and release",223 "Normal: no kills, all acquire and release",
230- num_children=4, num_devices=1,224+ num_children=4,
225+ num_devices=1,
231 kill_plan=None,226 kill_plan=None,
232 )227 )
233 228 
@@ -236,7 +231,8 @@ def test_6_rapid_kill():
236 """快速杀:持锁后立即(0.3s)杀进程,锁及时回收。"""231 """快速杀:持锁后立即(0.3s)杀进程,锁及时回收。"""
237 assert run_test(232 assert run_test(
238 "Rapid kill: kill child 0 at 0.3s (immediately after acquire)",233 "Rapid kill: kill child 0 at 0.3s (immediately after acquire)",
239- num_children=4, num_devices=1,234+ num_children=4,
235+ num_devices=1,
240 kill_plan=[(0.3, 0)],236 kill_plan=[(0.3, 0)],
241 )237 )
242 238 
@@ -245,7 +241,8 @@ def test_7_no_device():
245 """无设备模式:use_device=False,所有子进程并发运行无需锁。"""241 """无设备模式:use_device=False,所有子进程并发运行无需锁。"""
246 assert run_test(242 assert run_test(
247 "No device: use_device=False, all children run concurrently without lock",243 "No device: use_device=False, all children run concurrently without lock",
248- num_children=4, num_devices=1,244+ num_children=4,
245+ num_devices=1,
249 kill_plan=None,246 kill_plan=None,
250 use_device=False,247 use_device=False,
251 )248 )
@@ -255,7 +252,8 @@ def test_8_no_device_with_kill():
255 """无设备 + 杀进程:use_device=False 下杀 1 个子进程,其余正常完成。"""252 """无设备 + 杀进程:use_device=False 下杀 1 个子进程,其余正常完成。"""
256 assert run_test(253 assert run_test(
257 "No device + kill: use_device=False, kill child 0, others complete",254 "No device + kill: use_device=False, kill child 0, others complete",
258- num_children=4, num_devices=1,255+ num_children=4,
256+ num_devices=1,
259 kill_plan=[(1.0, 0)],257 kill_plan=[(1.0, 0)],
260 use_device=False,258 use_device=False,
261 )259 )
@@ -19,16 +19,11 @@ def test_worker_bootstrap_loads_config_from_switches(tmp_path, monkeypatch):
19 # 测试。worker_bootstrap 的核心是 load_config,logging 非本测试关注点。19 # 测试。worker_bootstrap 的核心是 load_config,logging 非本测试关注点。
20 import ttk.core_modules.tbe_multiprocessing.pool as pool20 import ttk.core_modules.tbe_multiprocessing.pool as pool
21 from ttk.utilities.classes import SWITCHES21 from ttk.utilities.classes import SWITCHES
22+ 
22 monkeypatch.setattr(pool, "default_logging_config", lambda **kw: None)23 monkeypatch.setattr(pool, "default_logging_config", lambda **kw: None)
23 24 
24 yaml_path = tmp_path / "worker-test.yaml"25 yaml_path = tmp_path / "worker-test.yaml"
25- yaml_path.write_text(26+ yaml_path.write_text('remote:\n tls_ca: "/fake/ca.crt"\n endpoints:\n - host: 10.0.0.1\n port: 9090\n')
26- 'remote:\n'
27- ' tls_ca: "/fake/ca.crt"\n'
28- ' endpoints:\n'
29- ' - host: 10.0.0.1\n'
30- ' port: 9090\n'
31- )
32 saved = loader._config27 saved = loader._config
33 loader._config = None # 模拟 forkserver worker:干净的 _config28 loader._config = None # 模拟 forkserver worker:干净的 _config
34 try:29 try:
@@ -37,6 +32,7 @@ def test_worker_bootstrap_loads_config_from_switches(tmp_path, monkeypatch):
37 sw.logging_to_file = False32 sw.logging_to_file = False
38 33 
39 from ttk.core_modules.tbe_multiprocessing.pool import worker_bootstrap34 from ttk.core_modules.tbe_multiprocessing.pool import worker_bootstrap
35+ 
40 worker_bootstrap(sw)36 worker_bootstrap(sw)
41 37 
42 rc = loader.get_remote_config()38 rc = loader.get_remote_config()
@@ -53,6 +49,7 @@ def test_worker_bootstrap_without_config_path_loads_defaults(monkeypatch):
53 import ttk.config.loader as loader49 import ttk.config.loader as loader
54 import ttk.core_modules.tbe_multiprocessing.pool as pool50 import ttk.core_modules.tbe_multiprocessing.pool as pool
55 from ttk.utilities.classes import SWITCHES51 from ttk.utilities.classes import SWITCHES
52+ 
56 monkeypatch.setattr(pool, "default_logging_config", lambda **kw: None)53 monkeypatch.setattr(pool, "default_logging_config", lambda **kw: None)
57 saved = loader._config54 saved = loader._config
58 loader._config = None55 loader._config = None
@@ -61,6 +58,7 @@ def test_worker_bootstrap_without_config_path_loads_defaults(monkeypatch):
61 sw.config_path = None58 sw.config_path = None
62 sw.logging_to_file = False59 sw.logging_to_file = False
63 from ttk.core_modules.tbe_multiprocessing.pool import worker_bootstrap60 from ttk.core_modules.tbe_multiprocessing.pool import worker_bootstrap
61+ 
64 worker_bootstrap(sw)62 worker_bootstrap(sw)
65 cfg = loader.get_config()63 cfg = loader.get_config()
66 assert "remote" in cfg # default.yaml 有 remote 段64 assert "remote" in cfg # default.yaml 有 remote 段
@@ -9,6 +9,7 @@ Everything built afterwards inherited them; the GEIR graph then declared
9DT_DOUBLE for a float32 testcase and the operator rejected it, masking the real9DT_DOUBLE for a float32 testcase and the operator rejected it, masking the real
10error (an out-of-range `dim`) behind "data type DT_DOUBLE is not supported".10error (an out-of-range `dim`) behind "data type DT_DOUBLE is not supported".
11"""11"""
12+ 
12import numpy13import numpy
13import pytest14import pytest
14 15 
@@ -38,8 +39,7 @@ class _Ctx:
38def test_promote_restores_dtypes(monkeypatch, raises):39def test_promote_restores_dtypes(monkeypatch, raises):
39 ctx = _Ctx()40 ctx = _Ctx()
40 before_in, before_out = ctx.input_dtypes, ctx.output_dtypes41 before_in, before_out = ctx.input_dtypes, ctx.output_dtypes
41- monkeypatch.setattr(output_generation, "input_apply_as_list",42+ monkeypatch.setattr(output_generation, "input_apply_as_list", lambda values, _dist: list(values), raising=False)
42- lambda values, _dist: list(values), raising=False)
43 43 
44 if raises:44 if raises:
45 with pytest.raises(RuntimeError):45 with pytest.raises(RuntimeError):
@@ -13,6 +13,7 @@
13canndev op_proto/inc 中确有这样写的算子(FfnWorkerBatching)。解析器不认这种写法时,13canndev op_proto/inc 中确有这样写的算子(FfnWorkerBatching)。解析器不认这种写法时,
14表现为 "GEIR op source generation failed",会被误判成算子缺陷。14表现为 "GEIR op source generation failed",会被误判成算子缺陷。
15"""15"""
16+ 
16import pytest17import pytest
17 18 
18from ttk.core_modules.geir.proto_loader import ProtoLoader19from ttk.core_modules.geir.proto_loader import ProtoLoader
@@ -45,10 +46,10 @@ def _parse(tmp_path, reg_sp="", end_sp=""):
45@pytest.mark.parametrize(46@pytest.mark.parametrize(
46 "reg_sp, end_sp",47 "reg_sp, end_sp",
47 [48 [
48- ("", ""), # 常规写法49+ ("", ""), # 常规写法
49- (" ", ""), # FfnWorkerBatching 的实际写法:仅 REG_OP 后有空格50+ (" ", ""), # FfnWorkerBatching 的实际写法:仅 REG_OP 后有空格
50- (" ", " "), # 两处都有空格51+ (" ", " "), # 两处都有空格
51- ("\t", "\t"), # 制表符52+ ("\t", "\t"), # 制表符
52 ],53 ],
53)54)
54def test_reg_op_accepts_whitespace_before_paren(tmp_path, reg_sp, end_sp):55def test_reg_op_accepts_whitespace_before_paren(tmp_path, reg_sp, end_sp):
@@ -24,78 +24,90 @@ class TestAclnnParamPlanInit:
24 24 
25 def test_tensor_only(self):25 def test_tensor_only(self):
26 """纯 aclTensor* 参数 → tensor_count == len(params),scalar_count == 0。"""26 """纯 aclTensor* 参数 → tensor_count == len(params),scalar_count == 0。"""
27- info = _make_op_api_info([27+ info = _make_op_api_info(
28- ("x", {"type": "aclTensor*", "default": None}),28+ [
29- ("y", {"type": "aclTensor*", "default": None}),29+ ("x", {"type": "aclTensor*", "default": None}),
30- ])30+ ("y", {"type": "aclTensor*", "default": None}),
31+ ]
32+ )
31 plan = AclnnParamPlan("aclnnAdd", info)33 plan = AclnnParamPlan("aclnnAdd", info)
32 assert plan.tensor_count == 234 assert plan.tensor_count == 2
33 assert plan.scalar_count == 035 assert plan.scalar_count == 0
34 assert len(plan.param_layout) == 236 assert len(plan.param_layout) == 2
35- assert plan.param_layout[0][0] == 'tensor'37+ assert plan.param_layout[0][0] == "tensor"
36- assert plan.param_layout[1][0] == 'tensor'38+ assert plan.param_layout[1][0] == "tensor"
37 39 
38 def test_tensor_and_scalar(self):40 def test_tensor_and_scalar(self):
39 """aclTensor* 与 aclScalar* 交错 → layout 保留声明顺序。"""41 """aclTensor* 与 aclScalar* 交错 → layout 保留声明顺序。"""
40- info = _make_op_api_info([42+ info = _make_op_api_info(
41- ("self", {"type": "aclTensor*", "default": None}),43+ [
42- ("dim", {"type": "aclScalar*", "default": None}),44+ ("self", {"type": "aclTensor*", "default": None}),
43- ("out", {"type": "aclTensor*", "default": None}),45+ ("dim", {"type": "aclScalar*", "default": None}),
44- ])46+ ("out", {"type": "aclTensor*", "default": None}),
47+ ]
48+ )
45 plan = AclnnParamPlan("aclnnSoftmax", info)49 plan = AclnnParamPlan("aclnnSoftmax", info)
46 assert plan.tensor_count == 250 assert plan.tensor_count == 2
47 assert plan.scalar_count == 151 assert plan.scalar_count == 1
48- assert plan.param_layout[0] == ('tensor', 'self', 'aclTensor*', None)52+ assert plan.param_layout[0] == ("tensor", "self", "aclTensor*", None)
49- assert plan.param_layout[1] == ('scalar', 'dim', 'aclScalar*', None)53+ assert plan.param_layout[1] == ("scalar", "dim", "aclScalar*", None)
50- assert plan.param_layout[2] == ('tensor', 'out', 'aclTensor*', None)54+ assert plan.param_layout[2] == ("tensor", "out", "aclTensor*", None)
51 55 
52 def test_with_attributes(self):56 def test_with_attributes(self):
53 """非 Tensor/Scalar 类型(如 float/aclDataType)→ 归入 'other' (attribute) 桶。"""57 """非 Tensor/Scalar 类型(如 float/aclDataType)→ 归入 'other' (attribute) 桶。"""
54- info = _make_op_api_info([58+ info = _make_op_api_info(
55- ("x", {"type": "aclTensor*", "default": None}),59+ [
56- ("alpha", {"type": "float", "default": "1.0"}),60+ ("x", {"type": "aclTensor*", "default": None}),
57- ("dtype", {"type": "aclDataType", "default": None}),61+ ("alpha", {"type": "float", "default": "1.0"}),
58- ])62+ ("dtype", {"type": "aclDataType", "default": None}),
63+ ]
64+ )
59 plan = AclnnParamPlan("aclnnCast", info)65 plan = AclnnParamPlan("aclnnCast", info)
60 assert plan.tensor_count == 166 assert plan.tensor_count == 1
61 assert plan.scalar_count == 067 assert plan.scalar_count == 0
62 assert len(plan.param_layout) == 368 assert len(plan.param_layout) == 3
63- assert plan.param_layout[1][0] == 'other'69+ assert plan.param_layout[1][0] == "other"
64- assert plan.param_layout[1][1] == 'alpha'70+ assert plan.param_layout[1][1] == "alpha"
65- assert plan.param_layout[2][0] == 'other'71+ assert plan.param_layout[2][0] == "other"
66- assert plan.param_layout[2][1] == 'dtype'72+ assert plan.param_layout[2][1] == "dtype"
67 73 
68 def test_tensor_list(self):74 def test_tensor_list(self):
69 """aclTensorList* → 计入 tensor_count,layout 保留 TensorList 类型。"""75 """aclTensorList* → 计入 tensor_count,layout 保留 TensorList 类型。"""
70- info = _make_op_api_info([76+ info = _make_op_api_info(
71- ("tensors", {"type": "aclTensorList*", "default": None}),77+ [
72- ("dim", {"type": "int64_t", "default": "0"}),78+ ("tensors", {"type": "aclTensorList*", "default": None}),
73- ])79+ ("dim", {"type": "int64_t", "default": "0"}),
80+ ]
81+ )
74 plan = AclnnParamPlan("aclnnCat", info)82 plan = AclnnParamPlan("aclnnCat", info)
75 assert plan.tensor_count == 183 assert plan.tensor_count == 1
76- assert plan.param_layout[0] == ('tensor', 'tensors', 'aclTensorList*', None)84+ assert plan.param_layout[0] == ("tensor", "tensors", "aclTensorList*", None)
77 85 
78 def test_scalar_list(self):86 def test_scalar_list(self):
79 """aclScalarList* → 计入 scalar_count。"""87 """aclScalarList* → 计入 scalar_count。"""
80- info = _make_op_api_info([88+ info = _make_op_api_info(
81- ("self", {"type": "aclTensor*", "default": None}),89+ [
82- ("scalarList", {"type": "aclScalarList*", "default": None}),90+ ("self", {"type": "aclTensor*", "default": None}),
83- ])91+ ("scalarList", {"type": "aclScalarList*", "default": None}),
92+ ]
93+ )
84 plan = AclnnParamPlan("aclnnAddScalarList", info)94 plan = AclnnParamPlan("aclnnAddScalarList", info)
85 assert plan.tensor_count == 195 assert plan.tensor_count == 1
86 assert plan.scalar_count == 196 assert plan.scalar_count == 1
87- assert plan.param_layout[1][0] == 'scalar'97+ assert plan.param_layout[1][0] == "scalar"
88 98 
89 def test_full_signature(self):99 def test_full_signature(self):
90 """混合 Tensor/Scalar/attribute 的完整 Conv2d-like 签名。"""100 """混合 Tensor/Scalar/attribute 的完整 Conv2d-like 签名。"""
91- info = _make_op_api_info([101+ info = _make_op_api_info(
92- ("x", {"type": "aclTensor*", "default": None}),102+ [
93- ("weight", {"type": "aclTensor*", "default": None}),103+ ("x", {"type": "aclTensor*", "default": None}),
94- ("bias", {"type": "aclTensor*", "default": None}),104+ ("weight", {"type": "aclTensor*", "default": None}),
95- ("scale", {"type": "aclScalar*", "default": None}),105+ ("bias", {"type": "aclTensor*", "default": None}),
96- ("groups", {"type": "int64_t", "default": "1"}),106+ ("scale", {"type": "aclScalar*", "default": None}),
97- ("format", {"type": "aclDataType", "default": None}),107+ ("groups", {"type": "int64_t", "default": "1"}),
98- ])108+ ("format", {"type": "aclDataType", "default": None}),
109+ ]
110+ )
99 plan = AclnnParamPlan("aclnnConv2d", info)111 plan = AclnnParamPlan("aclnnConv2d", info)
100 assert plan.tensor_count == 3112 assert plan.tensor_count == 3
101 assert plan.scalar_count == 1113 assert plan.scalar_count == 1
@@ -107,10 +119,12 @@ class TestAclnnParamPlanBuildArgs:
107 119 
108 def test_tensor_only_args(self):120 def test_tensor_only_args(self):
109 """纯 Tensor 参数 → args 即输入 tensors,extra 为空。"""121 """纯 Tensor 参数 → args 即输入 tensors,extra 为空。"""
110- info = _make_op_api_info([122+ info = _make_op_api_info(
111- ("x", {"type": "aclTensor*", "default": None}),123+ [
112- ("y", {"type": "aclTensor*", "default": None}),124+ ("x", {"type": "aclTensor*", "default": None}),
113- ])125+ ("y", {"type": "aclTensor*", "default": None}),
126+ ]
127+ )
114 plan = AclnnParamPlan("aclnnAdd", info)128 plan = AclnnParamPlan("aclnnAdd", info)
115 tensors = ["T_X", "T_Y"]129 tensors = ["T_X", "T_Y"]
116 args, extra = plan.build_args(tensors, [], {})130 args, extra = plan.build_args(tensors, [], {})
@@ -119,11 +133,13 @@ class TestAclnnParamPlanBuildArgs:
119 133 
120 def test_interleaved_tensor_scalar_attr(self):134 def test_interleaved_tensor_scalar_attr(self):
121 """Tensor 与 Scalar 交错 → args 按声明顺序插入 scalar。"""135 """Tensor 与 Scalar 交错 → args 按声明顺序插入 scalar。"""
122- info = _make_op_api_info([136+ info = _make_op_api_info(
123- ("self", {"type": "aclTensor*", "default": None}),137+ [
124- ("dim", {"type": "aclScalar*", "default": None}),138+ ("self", {"type": "aclTensor*", "default": None}),
125- ("out", {"type": "aclTensor*", "default": None}),139+ ("dim", {"type": "aclScalar*", "default": None}),
126- ])140+ ("out", {"type": "aclTensor*", "default": None}),
141+ ]
142+ )
127 plan = AclnnParamPlan("aclnnSoftmax", info)143 plan = AclnnParamPlan("aclnnSoftmax", info)
128 tensors = ["T_SELF", "T_OUT"]144 tensors = ["T_SELF", "T_OUT"]
129 scalars = ["S_DIM"]145 scalars = ["S_DIM"]
@@ -133,10 +149,12 @@ class TestAclnnParamPlanBuildArgs:
133 149 
134 def test_attribute_from_dict(self):150 def test_attribute_from_dict(self):
135 """attribute 从 attrs dict 取值,按声明顺序追加到 args。"""151 """attribute 从 attrs dict 取值,按声明顺序追加到 args。"""
136- info = _make_op_api_info([152+ info = _make_op_api_info(
137- ("x", {"type": "aclTensor*", "default": None}),153+ [
138- ("alpha", {"type": "float", "default": "1.0"}),154+ ("x", {"type": "aclTensor*", "default": None}),
139- ])155+ ("alpha", {"type": "float", "default": "1.0"}),
156+ ]
157+ )
140 plan = AclnnParamPlan("aclnnAddAlpha", info)158 plan = AclnnParamPlan("aclnnAddAlpha", info)
141 args, extra = plan.build_args(["T_X"], [], {"alpha": 2.5})159 args, extra = plan.build_args(["T_X"], [], {"alpha": 2.5})
142 assert args == ["T_X", 2.5]160 assert args == ["T_X", 2.5]
@@ -144,10 +162,12 @@ class TestAclnnParamPlanBuildArgs:
144 162 
145 def test_attribute_default_fallback(self):163 def test_attribute_default_fallback(self):
146 """attribute 未在 attrs 提供 → 使用 default。"""164 """attribute 未在 attrs 提供 → 使用 default。"""
147- info = _make_op_api_info([165+ info = _make_op_api_info(
148- ("x", {"type": "aclTensor*", "default": None}),166+ [
149- ("alpha", {"type": "float", "default": "1.0"}),167+ ("x", {"type": "aclTensor*", "default": None}),
150- ])168+ ("alpha", {"type": "float", "default": "1.0"}),
169+ ]
170+ )
151 plan = AclnnParamPlan("aclnnAddAlpha", info)171 plan = AclnnParamPlan("aclnnAddAlpha", info)
152 args, extra = plan.build_args(["T_X"], [], {})172 args, extra = plan.build_args(["T_X"], [], {})
153 assert args == ["T_X", "1.0"]173 assert args == ["T_X", "1.0"]
@@ -155,10 +175,12 @@ class TestAclnnParamPlanBuildArgs:
155 175 
156 def test_attribute_missing_no_default(self):176 def test_attribute_missing_no_default(self):
157 """attribute 无 default 且未提供 → None。"""177 """attribute 无 default 且未提供 → None。"""
158- info = _make_op_api_info([178+ info = _make_op_api_info(
159- ("x", {"type": "aclTensor*", "default": None}),179+ [
160- ("dtype", {"type": "aclDataType", "default": None}),180+ ("x", {"type": "aclTensor*", "default": None}),
161- ])181+ ("dtype", {"type": "aclDataType", "default": None}),
182+ ]
183+ )
162 plan = AclnnParamPlan("aclnnCast", info)184 plan = AclnnParamPlan("aclnnCast", info)
163 args, extra = plan.build_args(["T_X"], [], {})185 args, extra = plan.build_args(["T_X"], [], {})
164 assert args == ["T_X", None]186 assert args == ["T_X", None]
@@ -166,13 +188,15 @@ class TestAclnnParamPlanBuildArgs:
166 188 
167 def test_full_conv2d_like(self):189 def test_full_conv2d_like(self):
168 """完整 Conv2d-like 签名:Tensor + Scalar + attribute 混合。"""190 """完整 Conv2d-like 签名:Tensor + Scalar + attribute 混合。"""
169- info = _make_op_api_info([191+ info = _make_op_api_info(
170- ("x", {"type": "aclTensor*", "default": None}),192+ [
171- ("weight", {"type": "aclTensor*", "default": None}),193+ ("x", {"type": "aclTensor*", "default": None}),
172- ("bias", {"type": "aclTensor*", "default": None}),194+ ("weight", {"type": "aclTensor*", "default": None}),
173- ("scale", {"type": "aclScalar*", "default": None}),195+ ("bias", {"type": "aclTensor*", "default": None}),
174- ("groups", {"type": "int64_t", "default": "1"}),196+ ("scale", {"type": "aclScalar*", "default": None}),
175- ])197+ ("groups", {"type": "int64_t", "default": "1"}),
198+ ]
199+ )
176 plan = AclnnParamPlan("aclnnConv2d", info)200 plan = AclnnParamPlan("aclnnConv2d", info)
177 tensors = ["T_X", "T_W", "T_B"]201 tensors = ["T_X", "T_W", "T_B"]
178 scalars = ["S_SCALE"]202 scalars = ["S_SCALE"]
@@ -183,10 +207,12 @@ class TestAclnnParamPlanBuildArgs:
183 207 
184 def test_tensor_list_preserved(self):208 def test_tensor_list_preserved(self):
185 """TensorList 作为单一位置参数保留列表结构。"""209 """TensorList 作为单一位置参数保留列表结构。"""
186- info = _make_op_api_info([210+ info = _make_op_api_info(
187- ("tensors", {"type": "aclTensorList*", "default": None}),211+ [
188- ("dim", {"type": "int64_t", "default": "0"}),212+ ("tensors", {"type": "aclTensorList*", "default": None}),
189- ])213+ ("dim", {"type": "int64_t", "default": "0"}),
214+ ]
215+ )
190 plan = AclnnParamPlan("aclnnCat", info)216 plan = AclnnParamPlan("aclnnCat", info)
191 tensor_list = [["T_A", "T_B", "T_C"]]217 tensor_list = [["T_A", "T_B", "T_C"]]
192 args, extra = plan.build_args(tensor_list, [], {"dim": 1})218 args, extra = plan.build_args(tensor_list, [], {"dim": 1})
@@ -200,10 +226,13 @@ class TestGetParamPlan:
200 226 
201 def test_plan_cached(self):227 def test_plan_cached(self):
202 """已缓存的 _param_plan_cache → 直接返回,不重新构建。"""228 """已缓存的 _param_plan_cache → 直接返回,不重新构建。"""
203- info = _make_op_api_info([229+ info = _make_op_api_info(
204- ("x", {"type": "aclTensor*", "default": None}),230+ [
205- ])231+ ("x", {"type": "aclTensor*", "default": None}),
232+ ]
233+ )
206 from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn234 from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn
235+ 
207 case = TestcaseAclnn()236 case = TestcaseAclnn()
208 case.api_name = "aclnnAdd"237 case.api_name = "aclnnAdd"
209 plan = AclnnParamPlan("aclnnAdd", info)238 plan = AclnnParamPlan("aclnnAdd", info)
@@ -214,17 +243,21 @@ class TestGetParamPlan:
214 def test_plan_none_for_no_api(self):243 def test_plan_none_for_no_api(self):
215 """api_name 为 None → get_param_plan 返回 None。"""244 """api_name 为 None → get_param_plan 返回 None。"""
216 from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn245 from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn
246+ 
217 case = TestcaseAclnn()247 case = TestcaseAclnn()
218 case.api_name = None248 case.api_name = None
219 assert case.get_param_plan() is None249 assert case.get_param_plan() is None
220 250 
221 def test_plan_build_args_integration(self):251 def test_plan_build_args_integration(self):
222 """get_param_plan + build_args 端到端集成。"""252 """get_param_plan + build_args 端到端集成。"""
223- info = _make_op_api_info([253+ info = _make_op_api_info(
224- ("x", {"type": "aclTensor*", "default": None}),254+ [
225- ("alpha", {"type": "float", "default": "1.0"}),255+ ("x", {"type": "aclTensor*", "default": None}),
226- ])256+ ("alpha", {"type": "float", "default": "1.0"}),
257+ ]
258+ )
227 from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn259 from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn
260+ 
228 case = TestcaseAclnn()261 case = TestcaseAclnn()
229 case.api_name = "aclnnAddAlpha"262 case.api_name = "aclnnAddAlpha"
230 plan = AclnnParamPlan("aclnnAddAlpha", info)263 plan = AclnnParamPlan("aclnnAddAlpha", info)
@@ -21,11 +21,14 @@ from ttk.core_modules.operator.op_interface import OperatorInterface
21from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp21from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
22 22 
23 23 
24-def _make_testcase(op_name="Add", input_shapes=((8,), (8,)),24+def _make_testcase(
25- input_dtypes=("float16", "float16"),25+ op_name="Add",
26- output_shapes=((8,),),26+ input_shapes=((8,), (8,)),
27- output_dtypes=("float16",),27+ input_dtypes=("float16", "float16"),
28- **kwargs):28+ output_shapes=((8,),),
29+ output_dtypes=("float16",),
30+ **kwargs,
31+):
29 case = TestcaseOp()32 case = TestcaseOp()
30 case.testcase_name = f"test_{op_name or 'None'}"33 case.testcase_name = f"test_{op_name or 'None'}"
31 case.op_name = op_name34 case.op_name = op_name
@@ -68,8 +71,9 @@ def _validate(case, op_info=None):
68 mock_gs.op_impl_mode = None71 mock_gs.op_impl_mode = None
69 mock_gs.kernel_meta = "/tmp"72 mock_gs.kernel_meta = "/tmp"
70 mock_gs.short_soc_version = "Ascend910B2"73 mock_gs.short_soc_version = "Ascend910B2"
71- with patch('ttk.core_modules.testcase_manager.testcase_op.get_global_storage', return_value=mock_gs), \74+ with patch("ttk.core_modules.testcase_manager.testcase_op.get_global_storage", return_value=mock_gs), patch(
72- patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper') as mock:75+ "ttk.core_modules.operator.op_info_keeper.OpInfoKeeper"
76+ ) as mock:
73 mock.return_value.info_of.return_value = op_info77 mock.return_value.info_of.return_value = op_info
74 case.validate()78 case.validate()
75 79 
@@ -103,7 +107,6 @@ def _mock_compile_result(has_kernel_json=True, clear_atomic=False):
103 107 
104 108 
105class TestClearAtomicProperties:109class TestClearAtomicProperties:
106- 
107 def test_dyn_clear_atomic_from_compile_result(self):110 def test_dyn_clear_atomic_from_compile_result(self):
108 case = _make_testcase()111 case = _make_testcase()
109 case.dyn_compile_result = _mock_compile_result(clear_atomic=True)112 case.dyn_compile_result = _mock_compile_result(clear_atomic=True)
@@ -132,43 +135,49 @@ class TestShapeInference:
132 135 
133 _EXACT = object() # sentinel: 精确匹配 expected;否则仅断言非 None136 _EXACT = object() # sentinel: 精确匹配 expected;否则仅断言非 None
134 137 
135- @pytest.mark.parametrize("input_shapes, output_shapes, attributes, expected", [138+ @pytest.mark.parametrize(
136- (((8,), (8,)), "ELEWISE", {}, ((8,),)),139+ "input_shapes, output_shapes, attributes, expected",
137- (((8, 16),), "REDUCE", {"axis": (1,)}, _EXACT),140+ [
138- (((8, 16, 32),), "REDUCE", {"axes": (1, 2)}, _EXACT),141+ (((8,), (8,)), "ELEWISE", {}, ((8,),)),
139- (((8, 16),), "REDUCE", {}, _EXACT),142+ (((8, 16),), "REDUCE", {"axis": (1,)}, _EXACT),
140- (((8,), (8,)), "ELEWISE(1, None)", {}, _EXACT),143+ (((8, 16, 32),), "REDUCE", {"axes": (1, 2)}, _EXACT),
141- ], ids=["elewise", "reduce-with-axis", "reduce-with-axes",144+ (((8, 16),), "REDUCE", {}, _EXACT),
142- "reduce-no-axis", "elewise-with-args"])145+ (((8,), (8,)), "ELEWISE(1, None)", {}, _EXACT),
143- def test_inference_returns_value(self, input_shapes, output_shapes,146+ ],
144- attributes, expected):147+ ids=["elewise", "reduce-with-axis", "reduce-with-axes", "reduce-no-axis", "elewise-with-args"],
148+ )
149+ def test_inference_returns_value(self, input_shapes, output_shapes, attributes, expected):
145 """验证 ELEWISE/REDUCE 各场景推导出非 None 结果(ELEWISE 还检查精确值)。"""150 """验证 ELEWISE/REDUCE 各场景推导出非 None 结果(ELEWISE 还检查精确值)。"""
146- result = TestcaseOp._do_shape_inference(151+ result = TestcaseOp._do_shape_inference(input_shapes, output_shapes, attributes)
147- input_shapes, output_shapes, attributes)
148 if expected is self._EXACT:152 if expected is self._EXACT:
149 assert result is not None153 assert result is not None
150 else:154 else:
151 assert result == expected155 assert result == expected
152 156 
153- @pytest.mark.parametrize("input_shapes, output_shapes, match", [157+ @pytest.mark.parametrize(
154- ((None,), "ELEWISE", "None input"),158+ "input_shapes, output_shapes, match",
155- (((8,),), "INVALID_TYPE", "Invalid"),159+ [
156- ], ids=["none-in-inputs-raises", "invalid-inference-raises"])160+ ((None,), "ELEWISE", "None input"),
161+ (((8,),), "INVALID_TYPE", "Invalid"),
162+ ],
163+ ids=["none-in-inputs-raises", "invalid-inference-raises"],
164+ )
157 def test_inference_raises(self, input_shapes, output_shapes, match):165 def test_inference_raises(self, input_shapes, output_shapes, match):
158 """验证 None 输入或非法类型 → ValueError。"""166 """验证 None 输入或非法类型 → ValueError。"""
159 with pytest.raises(ValueError, match=match):167 with pytest.raises(ValueError, match=match):
160- TestcaseOp._do_shape_inference(168+ TestcaseOp._do_shape_inference(input_shapes, output_shapes, {})
161- input_shapes, output_shapes, {})
162 169 
163 170 
164class TestExpandIndices:171class TestExpandIndices:
165- 172+ @pytest.mark.parametrize(
166- @pytest.mark.parametrize("count, distribution, indices, expected", [173+ "count, distribution, indices, expected",
167- (3, (), [0, 1, 2], [0, 1, 2]),174+ [
168- (3, (), [0, None, 2], [0, None, 2]),175+ (3, (), [0, 1, 2], [0, 1, 2]),
169- ], ids=["simple-expand", "with-none"])176+ (3, (), [0, None, 2], [0, None, 2]),
170- def test_expand_indices_returns_list(self, count, distribution,177+ ],
171- indices, expected):178+ ids=["simple-expand", "with-none"],
179+ )
180+ def test_expand_indices_returns_list(self, count, distribution, indices, expected):
172 """验证无 distribution 时 _expand_indices 原样返回列表。"""181 """验证无 distribution 时 _expand_indices 原样返回列表。"""
173 assert TestcaseOp._expand_indices(count, distribution, indices) == expected182 assert TestcaseOp._expand_indices(count, distribution, indices) == expected
174 183 
@@ -179,7 +188,6 @@ class TestExpandIndices:
179 188 
180 189 
181class TestAutoSetInplaceIndexes:190class TestAutoSetInplaceIndexes:
182- 
183 def test_no_inplace(self):191 def test_no_inplace(self):
184 op_info = {192 op_info = {
185 "coreType.value": "AiCore",193 "coreType.value": "AiCore",
@@ -193,8 +201,7 @@ class TestAutoSetInplaceIndexes:
193 def test_with_inplace(self):201 def test_with_inplace(self):
194 op_info = {202 op_info = {
195 "coreType.value": "AiCore",203 "coreType.value": "AiCore",
196- "inputs": [{"name": "x", "paramType": "default"},204+ "inputs": [{"name": "x", "paramType": "default"}, {"name": "y", "paramType": "default"}],
197- {"name": "y", "paramType": "default"}],
198 "outputs": [{"name": "x", "paramType": "default"}],205 "outputs": [{"name": "x", "paramType": "default"}],
199 }206 }
200 case = _make_testcase()207 case = _make_testcase()
@@ -204,8 +211,7 @@ class TestAutoSetInplaceIndexes:
204 def test_param_type_mismatch(self):211 def test_param_type_mismatch(self):
205 op_info = {212 op_info = {
206 "coreType.value": "AiCore",213 "coreType.value": "AiCore",
207- "inputs": [{"name": "x", "paramType": "dynamic"},214+ "inputs": [{"name": "x", "paramType": "dynamic"}, {"name": "y", "paramType": "default"}],
208- {"name": "y", "paramType": "default"}],
209 "outputs": [{"name": "x", "paramType": "default"}],215 "outputs": [{"name": "x", "paramType": "default"}],
210 }216 }
211 case = _make_testcase()217 case = _make_testcase()
@@ -214,36 +220,32 @@ class TestAutoSetInplaceIndexes:
214 220 
215 221 
216class TestConstInputIndexesOpInfo:222class TestConstInputIndexesOpInfo:
217- 
218 def test_value_depend_required(self):223 def test_value_depend_required(self):
219 op_info = {224 op_info = {
220 "coreType.value": "AiCore",225 "coreType.value": "AiCore",
221- "inputs": [{"name": "x", "valueDepend": "required"},226+ "inputs": [{"name": "x", "valueDepend": "required"}, {"name": "y"}],
222- {"name": "y"}],
223 "outputs": [{"name": "z"}],227 "outputs": [{"name": "z"}],
224 }228 }
225 case = _make_testcase()229 case = _make_testcase()
226 _validate(case, op_info=op_info)230 _validate(case, op_info=op_info)
227- with patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper') as m:231+ with patch("ttk.core_modules.operator.op_info_keeper.OpInfoKeeper") as m:
228 m.return_value.info_of.return_value = op_info232 m.return_value.info_of.return_value = op_info
229 assert case.const_input_indexes == (0,)233 assert case.const_input_indexes == (0,)
230 234 
231 def test_no_value_depend(self):235 def test_no_value_depend(self):
232 op_info = {236 op_info = {
233 "coreType.value": "AiCore",237 "coreType.value": "AiCore",
234- "inputs": [{"name": "x", "valueDepend": "ignore"},238+ "inputs": [{"name": "x", "valueDepend": "ignore"}, {"name": "y"}],
235- {"name": "y"}],
236 "outputs": [{"name": "z"}],239 "outputs": [{"name": "z"}],
237 }240 }
238 case = _make_testcase()241 case = _make_testcase()
239 _validate(case, op_info=op_info)242 _validate(case, op_info=op_info)
240- with patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper') as m:243+ with patch("ttk.core_modules.operator.op_info_keeper.OpInfoKeeper") as m:
241 m.return_value.info_of.return_value = op_info244 m.return_value.info_of.return_value = op_info
242 assert case.const_input_indexes == ()245 assert case.const_input_indexes == ()
243 246 
244 247 
245class TestSupportedRerunTitle:248class TestSupportedRerunTitle:
246- 
247 def test_returns_expected_titles(self):249 def test_returns_expected_titles(self):
248 titles = TestcaseOp.supported_rerun_title()250 titles = TestcaseOp.supported_rerun_title()
249 assert "dyn_perf_us" in titles251 assert "dyn_perf_us" in titles
@@ -251,7 +253,6 @@ class TestSupportedRerunTitle:
251 253 
252 254 
253class TestSetCaseCoreType:255class TestSetCaseCoreType:
254- 
255 def test_core_type_from_global_storage(self):256 def test_core_type_from_global_storage(self):
256 case = _make_testcase()257 case = _make_testcase()
257 _validate(case)258 _validate(case)
@@ -274,7 +275,6 @@ class TestSetCaseCoreType:
274 275 
275 276 
276class TestStcShapeSizeCheck:277class TestStcShapeSizeCheck:
277- 
278 def test_shape_out_of_bound(self):278 def test_shape_out_of_bound(self):
279 case = _make_testcase(input_shapes=((999999999,),), input_dtypes=("float32",))279 case = _make_testcase(input_shapes=((999999999,),), input_dtypes=("float32",))
280 mock_gs = MagicMock()280 mock_gs = MagicMock()
@@ -283,8 +283,9 @@ class TestStcShapeSizeCheck:
283 mock_gs.op_impl_mode = None283 mock_gs.op_impl_mode = None
284 mock_gs.kernel_meta = "/tmp"284 mock_gs.kernel_meta = "/tmp"
285 mock_gs.short_soc_version = "Ascend910B2"285 mock_gs.short_soc_version = "Ascend910B2"
286- with patch('ttk.core_modules.testcase_manager.testcase_op.get_global_storage', return_value=mock_gs), \286+ with patch("ttk.core_modules.testcase_manager.testcase_op.get_global_storage", return_value=mock_gs), patch(
287- patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper') as mock_oi:287+ "ttk.core_modules.operator.op_info_keeper.OpInfoKeeper"
288+ ) as mock_oi:
288 mock_oi.return_value.info_of.return_value = {289 mock_oi.return_value.info_of.return_value = {
289 "coreType.value": "AiCore",290 "coreType.value": "AiCore",
290 "inputs": [{"name": "x"}],291 "inputs": [{"name": "x"}],
@@ -296,7 +297,6 @@ class TestStcShapeSizeCheck:
296 297 
297 298 
298class TestGetDynFuncParamName:299class TestGetDynFuncParamName:
299- 
300 def test_basic(self):300 def test_basic(self):
301 case = _make_testcase()301 case = _make_testcase()
302 case.dyn_func_params = ("x", "y", "axis")302 case.dyn_func_params = ("x", "y", "axis")
@@ -323,11 +323,10 @@ def _clear_caches():
323 323 
324 324 
325class TestPrepareOperatorParameters:325class TestPrepareOperatorParameters:
326- 
327 def test_dyn_mode(self):326 def test_dyn_mode(self):
328 case = _make_testcase()327 case = _make_testcase()
329 _validate(case)328 _validate(case)
330- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as mock_oi:329+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as mock_oi:
331 mock_oi.return_value.op_output_defined.return_value = True330 mock_oi.return_value.op_output_defined.return_value = True
332 ipt, opt = OperatorInterface.prepare_operator_parameters(case, "dyn")331 ipt, opt = OperatorInterface.prepare_operator_parameters(case, "dyn")
333 assert len(ipt) == 2332 assert len(ipt) == 2
@@ -336,8 +335,9 @@ class TestPrepareOperatorParameters:
336 def test_bin_mode(self):335 def test_bin_mode(self):
337 case = _make_testcase()336 case = _make_testcase()
338 _validate(case)337 _validate(case)
339- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as mock_oi, \338+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as mock_oi, patch(
340- patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper') as mock_oi2:339+ "ttk.core_modules.operator.op_info_keeper.OpInfoKeeper"
340+ ) as mock_oi2:
341 mock_oi.return_value.op_output_defined.return_value = True341 mock_oi.return_value.op_output_defined.return_value = True
342 mock_oi2.return_value.info_of.return_value = None342 mock_oi2.return_value.info_of.return_value = None
343 ipt, opt = OperatorInterface.prepare_operator_parameters(case, "bin")343 ipt, opt = OperatorInterface.prepare_operator_parameters(case, "bin")
@@ -351,16 +351,15 @@ class TestPrepareOperatorParameters:
351 def test_op_output_not_defined(self):351 def test_op_output_not_defined(self):
352 case = _make_testcase()352 case = _make_testcase()
353 _validate(case)353 _validate(case)
354- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as mock_oi:354+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as mock_oi:
355 mock_oi.return_value.op_output_defined.return_value = False355 mock_oi.return_value.op_output_defined.return_value = False
356 ipt, opt = OperatorInterface.prepare_operator_parameters(case, "dyn")356 ipt, opt = OperatorInterface.prepare_operator_parameters(case, "dyn")
357 assert opt == ()357 assert opt == ()
358 358 
359 359 
360class TestConstructOptilingAttrs:360class TestConstructOptilingAttrs:
361- 
362 def test_from_attr_dictionary_no_op_info(self):361 def test_from_attr_dictionary_no_op_info(self):
363- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as mock_oi:362+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as mock_oi:
364 mock_oi.return_value.info_of.return_value = None363 mock_oi.return_value.info_of.return_value = None
365 result = OperatorInterface.construct_optiling_attrs("Add", {"axis": 1, "keep_dims": True})364 result = OperatorInterface.construct_optiling_attrs("Add", {"axis": 1, "keep_dims": True})
366 assert len(result) == 2365 assert len(result) == 2
@@ -374,7 +373,7 @@ class TestConstructOptilingAttrs:
374 {"name": "keep_dims", "type": "Bool", "defaultValue": False},373 {"name": "keep_dims", "type": "Bool", "defaultValue": False},
375 ]374 ]
376 }375 }
377- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as mock_oi:376+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as mock_oi:
378 mock_oi.return_value.info_of.return_value = op_info377 mock_oi.return_value.info_of.return_value = op_info
379 result = OperatorInterface.construct_optiling_attrs("Add", {"axis": 1})378 result = OperatorInterface.construct_optiling_attrs("Add", {"axis": 1})
380 assert len(result) == 2379 assert len(result) == 2
@@ -386,19 +385,19 @@ class TestConstructOptilingAttrs:
386 {"name": "axis", "type": "Int", "defaultValue": None},385 {"name": "axis", "type": "Int", "defaultValue": None},
387 ]386 ]
388 }387 }
389- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as mock_oi:388+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as mock_oi:
390 mock_oi.return_value.info_of.return_value = op_info389 mock_oi.return_value.info_of.return_value = op_info
391 with pytest.raises(RuntimeError, match="Required attribute"):390 with pytest.raises(RuntimeError, match="Required attribute"):
392 OperatorInterface.construct_optiling_attrs("Add", {})391 OperatorInterface.construct_optiling_attrs("Add", {})
393 392 
394 def test_private_attrs(self):393 def test_private_attrs(self):
395- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as mock_oi:394+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as mock_oi:
396 mock_oi.return_value.info_of.return_value = None395 mock_oi.return_value.info_of.return_value = None
397 result = OperatorInterface.construct_optiling_attrs("Add", {"@private_key": 42})396 result = OperatorInterface.construct_optiling_attrs("Add", {"@private_key": 42})
398 assert any(r["name"] == "private_key" for r in result)397 assert any(r["name"] == "private_key" for r in result)
399 398 
400 def test_skip_special_prefixes(self):399 def test_skip_special_prefixes(self):
401- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as mock_oi:400+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as mock_oi:
402 mock_oi.return_value.info_of.return_value = None401 mock_oi.return_value.info_of.return_value = None
403 result = OperatorInterface.construct_optiling_attrs("Add", {"!skip": 1, "#skip": 2, "keep": 3})402 result = OperatorInterface.construct_optiling_attrs("Add", {"!skip": 1, "#skip": 2, "keep": 3})
404 assert len(result) == 1403 assert len(result) == 1
@@ -406,10 +405,10 @@ class TestConstructOptilingAttrs:
406 405 
407 406 
408class TestGetOpFuncParams:407class TestGetOpFuncParams:
409- 
410 def test_from_callable(self):408 def test_from_callable(self):
411 def my_op(x, y, axis):409 def my_op(x, y, axis):
412 pass410 pass
411+ 
413 result = OperatorInterface.get_op_func_params(operator_func=my_op)412 result = OperatorInterface.get_op_func_params(operator_func=my_op)
414 assert result == ("x", "y", "axis")413 assert result == ("x", "y", "axis")
415 414 
@@ -419,7 +418,7 @@ class TestGetOpFuncParams:
419 "outputs": [{"name": "z"}],418 "outputs": [{"name": "z"}],
420 "attr": [{"name": "axis"}],419 "attr": [{"name": "axis"}],
421 }420 }
422- with patch('ttk.core_modules.operator.op_interface.OpInfoKeeper') as mock_oi:421+ with patch("ttk.core_modules.operator.op_interface.OpInfoKeeper") as mock_oi:
423 mock_oi.return_value.info_of.return_value = op_info422 mock_oi.return_value.info_of.return_value = op_info
424 result = OperatorInterface.get_op_func_params(op_name="Add")423 result = OperatorInterface.get_op_func_params(op_name="Add")
425 assert result == ("x", "y", "z", "axis")424 assert result == ("x", "y", "z", "axis")
@@ -430,7 +429,6 @@ class TestGetOpFuncParams:
430 429 
431 430 
432class TestOpTypeFromSourceCode:431class TestOpTypeFromSourceCode:
433- 
434 def test_not_callable(self):432 def test_not_callable(self):
435 assert OperatorInterface.get_op_type_from_source_code("not_a_func") is None433 assert OperatorInterface.get_op_type_from_source_code("not_a_func") is None
436 434 
@@ -438,18 +436,21 @@ class TestOpTypeFromSourceCode:
438class TestEnableShapeInt64:436class TestEnableShapeInt64:
439 """Tests for _enable_shape_int64 — 短 shape / None / 嵌套列表均返回 False。"""437 """Tests for _enable_shape_int64 — 短 shape / None / 嵌套列表均返回 False。"""
440 438 
441- @pytest.mark.parametrize("tensors", [439+ @pytest.mark.parametrize(
442- [{"shape": (8,), "dtype": "float16"}],440+ "tensors",
443- [None],441+ [
444- [[{"shape": (8,), "dtype": "float16"}]],442+ [{"shape": (8,), "dtype": "float16"}],
445- ], ids=["small-shape", "none-tensor", "list-of-tensors"])443+ [None],
444+ [[{"shape": (8,), "dtype": "float16"}]],
445+ ],
446+ ids=["small-shape", "none-tensor", "list-of-tensors"],
447+ )
446 def test_enable_shape_int64_returns_false(self, tensors):448 def test_enable_shape_int64_returns_false(self, tensors):
447 """验证短 shape / None / 嵌套列表场景下 _enable_shape_int64 返回 False。"""449 """验证短 shape / None / 嵌套列表场景下 _enable_shape_int64 返回 False。"""
448 assert OperatorInterface._enable_shape_int64(tensors) is False450 assert OperatorInterface._enable_shape_int64(tensors) is False
449 451 
450 452 
451class TestPrintFuncParams:453class TestPrintFuncParams:
452- 
453 def test_basic(self):454 def test_basic(self):
454 result = OperatorInterface.print_func_params(("x", "y"), {"axis": 1}, [{"shape": (8,)}])455 result = OperatorInterface.print_func_params(("x", "y"), {"axis": 1}, [{"shape": (8,)}])
455 assert len(result) > 2456 assert len(result) > 2
@@ -457,7 +458,6 @@ class TestPrintFuncParams:
457 458 
458 459 
459class TestAddCompileInfoToOpContext:460class TestAddCompileInfoToOpContext:
460- 
461 def test_dict_context(self):461 def test_dict_context(self):
462 cxt = {}462 cxt = {}
463 case = _make_testcase()463 case = _make_testcase()
@@ -480,11 +480,11 @@ class TestAddCompileInfoToOpContext:
480 480 
481# ============= static_compilation.py tests =============481# ============= static_compilation.py tests =============
482 482 
483-class TestStaticCompilation:
484 483 
485- @patch('ttk.core_modules.npu.op.compilation.static_compilation.get_process_context')484+class TestStaticCompilation:
486- @patch('ttk.core_modules.npu.op.compilation.static_compilation.get_global_storage')485+ @patch("ttk.core_modules.npu.op.compilation.static_compilation.get_process_context")
487- @patch('ttk.core_modules.npu.op.compilation.static_compilation.OperatorInterface')486+ @patch("ttk.core_modules.npu.op.compilation.static_compilation.get_global_storage")
487+ @patch("ttk.core_modules.npu.op.compilation.static_compilation.OperatorInterface")
488 def test_switch_disabled(self, mock_oi_cls, mock_gs, mock_pc):488 def test_switch_disabled(self, mock_oi_cls, mock_gs, mock_pc):
489 mock_gs.return_value.kernel_meta = "/tmp/test_kernel_meta"489 mock_gs.return_value.kernel_meta = "/tmp/test_kernel_meta"
490 mock_sw = MagicMock()490 mock_sw = MagicMock()
@@ -493,13 +493,14 @@ class TestStaticCompilation:
493 mock_pc.return_value.notify_status = MagicMock()493 mock_pc.return_value.notify_status = MagicMock()
494 494 
495 from ttk.core_modules.npu.op.compilation.static_compilation import static_compilation495 from ttk.core_modules.npu.op.compilation.static_compilation import static_compilation
496+ 
496 case = _make_testcase()497 case = _make_testcase()
497 result = static_compilation(case, "Cst")498 result = static_compilation(case, "Cst")
498 assert result.compile_result == "CST_OFF"499 assert result.compile_result == "CST_OFF"
499 500 
500- @patch('ttk.core_modules.npu.op.compilation.static_compilation.get_process_context')501+ @patch("ttk.core_modules.npu.op.compilation.static_compilation.get_process_context")
501- @patch('ttk.core_modules.npu.op.compilation.static_compilation.get_global_storage')502+ @patch("ttk.core_modules.npu.op.compilation.static_compilation.get_global_storage")
502- @patch('ttk.core_modules.npu.op.compilation.static_compilation.OperatorInterface')503+ @patch("ttk.core_modules.npu.op.compilation.static_compilation.OperatorInterface")
503 def test_cst_dyn_invalid(self, mock_oi_cls, mock_gs, mock_pc):504 def test_cst_dyn_invalid(self, mock_oi_cls, mock_gs, mock_pc):
504 mock_gs.return_value.kernel_meta = "/tmp/test_kernel_meta"505 mock_gs.return_value.kernel_meta = "/tmp/test_kernel_meta"
505 mock_sw = MagicMock()506 mock_sw = MagicMock()
@@ -509,6 +510,7 @@ class TestStaticCompilation:
509 mock_pc.return_value.notify_status = MagicMock()510 mock_pc.return_value.notify_status = MagicMock()
510 511 
511 from ttk.core_modules.npu.op.compilation.static_compilation import static_compilation512 from ttk.core_modules.npu.op.compilation.static_compilation import static_compilation
513+ 
512 case = _make_testcase()514 case = _make_testcase()
513 case.is_valid = False515 case.is_valid = False
514 case.fail_reason = "DYN_INPUT_MISSING"516 case.fail_reason = "DYN_INPUT_MISSING"
@@ -519,17 +521,23 @@ class TestStaticCompilation:
519class TestNormalizeMode:521class TestNormalizeMode:
520 """Tests for normalize_mode — 大小写归一化为 'Cst',非法模式抛 NotImplementedError。"""522 """Tests for normalize_mode — 大小写归一化为 'Cst',非法模式抛 NotImplementedError。"""
521 523 
522- @pytest.mark.parametrize("mode, expected", [524+ @pytest.mark.parametrize(
523- ("cst", "Cst"),525+ "mode, expected",
524- ("CST", "Cst"),526+ [
525- ], ids=["lowercase", "uppercase"])527+ ("cst", "Cst"),
528+ ("CST", "Cst"),
529+ ],
530+ ids=["lowercase", "uppercase"],
531+ )
526 def test_normalize_mode(self, mode, expected):532 def test_normalize_mode(self, mode, expected):
527 """验证 cst/CST 均归一化为 'Cst'。"""533 """验证 cst/CST 均归一化为 'Cst'。"""
528 from ttk.core_modules.npu.op.compilation.common import normalize_mode534 from ttk.core_modules.npu.op.compilation.common import normalize_mode
535+ 
529 assert normalize_mode(mode) == expected536 assert normalize_mode(mode) == expected
530 537 
531 def test_invalid_raises(self):538 def test_invalid_raises(self):
532 """非法 mode → NotImplementedError。"""539 """非法 mode → NotImplementedError。"""
533 from ttk.core_modules.npu.op.compilation.common import normalize_mode540 from ttk.core_modules.npu.op.compilation.common import normalize_mode
541+ 
534 with pytest.raises(NotImplementedError):542 with pytest.raises(NotImplementedError):
535 normalize_mode("invalid_long")543 normalize_mode("invalid_long")
@@ -20,12 +20,15 @@ import pytest
20from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp20from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
21 21 
22 22 
23-def _make_testcase(op_name="Add", input_shapes=((8,), (8,)),23+def _make_testcase(
24- input_dtypes=("float16", "float16"),24+ op_name="Add",
25- output_shapes=((8,),),25+ input_shapes=((8,), (8,)),
26- output_dtypes=("float16",),26+ input_dtypes=("float16", "float16"),
27- const_input_indexes=None,27+ output_shapes=((8,),),
28- **kwargs):28+ output_dtypes=("float16",),
29+ const_input_indexes=None,
30+ **kwargs,
31+):
29 case = TestcaseOp()32 case = TestcaseOp()
30 case.testcase_name = f"test_{op_name or 'None'}"33 case.testcase_name = f"test_{op_name or 'None'}"
31 case.op_name = op_name34 case.op_name = op_name
@@ -58,7 +61,7 @@ def _mock_op_info(monkeypatch):
58 61 
59 62 
60def _validate(case):63def _validate(case):
61- with patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper') as mock:64+ with patch("ttk.core_modules.operator.op_info_keeper.OpInfoKeeper") as mock:
62 mock.return_value.info_of.return_value = None65 mock.return_value.info_of.return_value = None
63 case.validate()66 case.validate()
64 67 
@@ -72,11 +75,15 @@ class TestDynInputsNested:
72 特殊断言(None 元素 / 直接调用 classmethod),保留独立测试75 特殊断言(None 元素 / 直接调用 classmethod),保留独立测试
73 """76 """
74 77 
75- @pytest.mark.parametrize("input_shapes, expected", [78+ @pytest.mark.parametrize(
76- (((8,), (8,)), ((-1,), (-1,))),79+ "input_shapes, expected",
77- ((((3, 4), (5, 4)), (8,)), (((-1, -1), (-1, -1)), (-1,))),80+ [
78- ((), ()),81+ (((8,), (8,)), ((-1,), (-1,))),
79- ], ids=["flat-no-tensorlist", "nested-with-tensorlist", "empty"])82+ ((((3, 4), (5, 4)), (8,)), (((-1, -1), (-1, -1)), (-1,))),
83+ ((), ()),
84+ ],
85+ ids=["flat-no-tensorlist", "nested-with-tensorlist", "empty"],
86+ )
80 def test_dyn_inputs(self, input_shapes, expected):87 def test_dyn_inputs(self, input_shapes, expected):
81 """验证 dyn_inputs 在 flat/nested/empty 下的返回值。"""88 """验证 dyn_inputs 在 flat/nested/empty 下的返回值。"""
82 case = _make_testcase(input_shapes=input_shapes)89 case = _make_testcase(input_shapes=input_shapes)
@@ -103,27 +110,29 @@ class TestDynInputDtypesNested:
103 expected 为期望返回值。110 expected 为期望返回值。
104 """111 """
105 112 
106- @pytest.mark.parametrize("property_name, kwargs, expected", [113+ @pytest.mark.parametrize(
107- ("dyn_input_dtypes",114+ "property_name, kwargs, expected",
108- {"input_dtypes": ("float16", "float32")},115+ [
109- ("float16", "float32")),116+ ("dyn_input_dtypes", {"input_dtypes": ("float16", "float32")}, ("float16", "float32")),
110- ("dyn_input_dtypes",117+ (
111- {"input_shapes": (((3, 4), (5, 4)), (8,)),118+ "dyn_input_dtypes",
112- "input_dtypes": (("float16", "float16"), "float32")},119+ {"input_shapes": (((3, 4), (5, 4)), (8,)), "input_dtypes": (("float16", "float16"), "float32")},
113- (("float16", "float16"), "float32")),120+ (("float16", "float16"), "float32"),
114- ("dyn_input_formats",121+ ),
115- {"input_shapes": (((3, 4), (5, 4)), (8,)),122+ (
116- "input_formats": (("ND", "ND"), "NCHW")},123+ "dyn_input_formats",
117- (("ND", "ND"), "NCHW")),124+ {"input_shapes": (((3, 4), (5, 4)), (8,)), "input_formats": (("ND", "ND"), "NCHW")},
118- ("dyn_outputs",125+ (("ND", "ND"), "NCHW"),
119- {"output_shapes": (((8,), (8,)),)},126+ ),
120- (((-1,), (-1,)),)),127+ ("dyn_outputs", {"output_shapes": (((8,), (8,)),)}, (((-1,), (-1,)),)),
121- ("dyn_ori_inputs",128+ (
122- {"input_shapes": (((3, 4), (5, 4)), (8,)),129+ "dyn_ori_inputs",
123- "input_ori_shapes": (((3, 4), (5, 4)), (8,))},130+ {"input_shapes": (((3, 4), (5, 4)), (8,)), "input_ori_shapes": (((3, 4), (5, 4)), (8,))},
124- (((-1, -1), (-1, -1)), (-1,))),131+ (((-1, -1), (-1, -1)), (-1,)),
125- ], ids=["dtypes-flat", "dtypes-nested", "formats-nested",132+ ),
126- "outputs-nested", "ori-inputs-nested"])133+ ],
134+ ids=["dtypes-flat", "dtypes-nested", "formats-nested", "outputs-nested", "ori-inputs-nested"],
135+ )
127 def test_dyn_property_nested(self, property_name, kwargs, expected):136 def test_dyn_property_nested(self, property_name, kwargs, expected):
128 """验证各 dyn_* 属性在嵌套结构下返回与 stc 同构的结果。"""137 """验证各 dyn_* 属性在嵌套结构下返回与 stc 同构的结果。"""
129 case = _make_testcase(**kwargs)138 case = _make_testcase(**kwargs)
@@ -138,26 +147,27 @@ class TestFlatDynProperties:
138 expected 为期望扁平化结果。147 expected 为期望扁平化结果。
139 """148 """
140 149 
141- @pytest.mark.parametrize("property_name, kwargs, expected", [150+ @pytest.mark.parametrize(
142- ("flat_dyn_inputs",151+ "property_name, kwargs, expected",
143- {"input_shapes": ((8,), (8,))},152+ [
144- ((-1,), (-1,))),153+ ("flat_dyn_inputs", {"input_shapes": ((8,), (8,))}, ((-1,), (-1,))),
145- ("flat_dyn_inputs",154+ ("flat_dyn_inputs", {"input_shapes": (((3, 4), (5, 4)), (8,))}, ((-1, -1), (-1, -1), (-1,))),
146- {"input_shapes": (((3, 4), (5, 4)), (8,))},155+ (
147- ((-1, -1), (-1, -1), (-1,))),156+ "flat_dyn_input_dtypes",
148- ("flat_dyn_input_dtypes",157+ {"input_shapes": (((3, 4), (5, 4)), (8,)), "input_dtypes": (("float16", "float16"), "float32")},
149- {"input_shapes": (((3, 4), (5, 4)), (8,)),158+ ("float16", "float16", "float32"),
150- "input_dtypes": (("float16", "float16"), "float32")},159+ ),
151- ("float16", "float16", "float32")),160+ ("flat_dyn_outputs", {"output_shapes": (((8,), (8,)),)}, ((-1,), (-1,))),
152- ("flat_dyn_outputs",161+ ("flat_dyn_inputs", {"input_shapes": (), "input_dtypes": ()}, ()),
153- {"output_shapes": (((8,), (8,)),)},162+ ],
154- ((-1,), (-1,))),163+ ids=[
155- ("flat_dyn_inputs",164+ "inputs-no-tensorlist",
156- {"input_shapes": (), "input_dtypes": ()},165+ "inputs-with-tensorlist",
157- ()),166+ "input_dtypes-with-tensorlist",
158- ], ids=["inputs-no-tensorlist", "inputs-with-tensorlist",167+ "outputs-with-tensorlist",
159- "input_dtypes-with-tensorlist", "outputs-with-tensorlist",168+ "inputs-empty",
160- "inputs-empty"])169+ ],
170+ )
161 def test_flat_dyn_property(self, property_name, kwargs, expected):171 def test_flat_dyn_property(self, property_name, kwargs, expected):
162 """验证 flat_dyn_* 属性返回扁平化结果。"""172 """验证 flat_dyn_* 属性返回扁平化结果。"""
163 case = _make_testcase(**kwargs)173 case = _make_testcase(**kwargs)
@@ -45,53 +45,67 @@ def _match_overload(api_name, input_tensor_count, attributes=None, tensor_distri
45def _build_positional_args(api_name, nested_tensors, attributes, output_tensor_indexes, tensor_distribution=None):45def _build_positional_args(api_name, nested_tensors, attributes, output_tensor_indexes, tensor_distribution=None):
46 api_info = FrameworkApiInfoKeeper().get(api_name)46 api_info = FrameworkApiInfoKeeper().get(api_name)
47 return _real_build_positional_args(47 return _real_build_positional_args(
48- api_name, nested_tensors, attributes,48+ api_name, nested_tensors, attributes, output_tensor_indexes, tensor_distribution, api_info
49- output_tensor_indexes, tensor_distribution, api_info)49+ )
50 50 
51 51 
52class TestMatchOverload:52class TestMatchOverload:
53- 
54 def test_single_overload_match(self):53 def test_single_overload_match(self):
55- _register("torch.t1", [54+ _register(
56- ParamInfo(name="input", type="Tensor"),55+ "torch.t1",
57- ParamInfo(name="dim", type="int"),56+ [
58- ])57+ ParamInfo(name="input", type="Tensor"),
58+ ParamInfo(name="dim", type="int"),
59+ ],
60+ )
59 params, oidx = _match_overload("torch.t1", 1, {"dim": "0"})61 params, oidx = _match_overload("torch.t1", 1, {"dim": "0"})
60 assert params is not None62 assert params is not None
61 assert oidx == 063 assert oidx == 0
62 64 
63 def test_multi_overload_select_by_tensor_count(self):65 def test_multi_overload_select_by_tensor_count(self):
64- _register("torch.t2", [66+ _register(
65- [ParamInfo(name="input", type="Tensor"), ParamInfo(name="other", type="Tensor")],67+ "torch.t2",
66- [ParamInfo(name="input", type="Tensor")],68+ [
67- ])69+ [ParamInfo(name="input", type="Tensor"), ParamInfo(name="other", type="Tensor")],
70+ [ParamInfo(name="input", type="Tensor")],
71+ ],
72+ )
68 params1, oidx1 = _match_overload("torch.t2", 1)73 params1, oidx1 = _match_overload("torch.t2", 1)
69 assert oidx1 == 174 assert oidx1 == 1
70 params2, oidx2 = _match_overload("torch.t2", 2)75 params2, oidx2 = _match_overload("torch.t2", 2)
71 assert oidx2 == 076 assert oidx2 == 0
72 77 
73 def test_multi_overload_select_by_attribute_score(self):78 def test_multi_overload_select_by_attribute_score(self):
74- _register("torch.t3", [79+ _register(
75- [ParamInfo(name="input", type="Tensor"), ParamInfo(name="min", type="Tensor")],80+ "torch.t3",
76- [ParamInfo(name="input", type="Tensor"), ParamInfo(name="min", type="Number")],81+ [
77- ])82+ [ParamInfo(name="input", type="Tensor"), ParamInfo(name="min", type="Tensor")],
83+ [ParamInfo(name="input", type="Tensor"), ParamInfo(name="min", type="Number")],
84+ ],
85+ )
78 params, oidx = _match_overload("torch.t3", 1, {"min": "0.5"})86 params, oidx = _match_overload("torch.t3", 1, {"min": "0.5"})
79 assert oidx == 187 assert oidx == 1
80 88 
81 def test_no_match_returns_none(self):89 def test_no_match_returns_none(self):
82- _register("torch.t4", [90+ _register(
83- ParamInfo(name="input", type="Tensor"),91+ "torch.t4",
84- ParamInfo(name="other", type="Tensor"),92+ [
85- ])93+ ParamInfo(name="input", type="Tensor"),
94+ ParamInfo(name="other", type="Tensor"),
95+ ],
96+ )
86 params, oidx = _match_overload("torch.t4", 3)97 params, oidx = _match_overload("torch.t4", 3)
87 assert params is None98 assert params is None
88 assert oidx == -199 assert oidx == -1
89 100 
90 def test_tensor_distribution_filter(self):101 def test_tensor_distribution_filter(self):
91- _register("torch.t5", [102+ _register(
92- [ParamInfo(name="tensors", type="tuple of Tensors")],103+ "torch.t5",
93- [ParamInfo(name="input", type="Tensor"), ParamInfo(name="other", type="Tensor")],104+ [
94- ])105+ [ParamInfo(name="tensors", type="tuple of Tensors")],
106+ [ParamInfo(name="input", type="Tensor"), ParamInfo(name="other", type="Tensor")],
107+ ],
108+ )
95 params_nested, oidx_nested = _match_overload("torch.t5", 1, tensor_distribution=[True])109 params_nested, oidx_nested = _match_overload("torch.t5", 1, tensor_distribution=[True])
96 assert oidx_nested == 0110 assert oidx_nested == 0
97 params_flat, oidx_flat = _match_overload("torch.t5", 2, tensor_distribution=[False, False])111 params_flat, oidx_flat = _match_overload("torch.t5", 2, tensor_distribution=[False, False])
@@ -103,77 +117,113 @@ class TestMatchOverload:
103 117 
104 118 
105class TestScoreAttrTypeCompatibility:119class TestScoreAttrTypeCompatibility:
106- 120+ @pytest.mark.parametrize(
107- @pytest.mark.parametrize("api_suffix, attr_name, attr_value, expected_oidx", [121+ "api_suffix, attr_name, attr_value, expected_oidx",
108- ("str", "ord", "fro", 1),122+ [
109- ("int", "ord", 2, 0),123+ ("str", "ord", "fro", 1),
110- ("bool", "flag", True, 1),124+ ("int", "ord", 2, 0),
111- ("tuple", "size", (2, 3), 1),125+ ("bool", "flag", True, 1),
112- ], ids=["str-prefers-str-overload", "int-prefers-number-overload",126+ ("tuple", "size", (2, 3), 1),
113- "bool-prefers-bool-overload", "tuple-prefers-tuple-overload"])127+ ],
114- def test_attr_type_prefers_matching_overload(self, api_suffix, attr_name,128+ ids=[
115- attr_value, expected_oidx):129+ "str-prefers-str-overload",
130+ "int-prefers-number-overload",
131+ "bool-prefers-bool-overload",
132+ "tuple-prefers-tuple-overload",
133+ ],
134+ )
135+ def test_attr_type_prefers_matching_overload(self, api_suffix, attr_name, attr_value, expected_oidx):
116 if api_suffix == "str":136 if api_suffix == "str":
117- _register("torch.norm_like_str", [137+ _register(
118- [ParamInfo(name="input", type="Tensor"),138+ "torch.norm_like_str",
119- ParamInfo(name=attr_name, type="Number"),139+ [
120- ParamInfo(name="dim", type="tuple of ints", default="(-2,-1)", is_optional=True),140+ [
121- ParamInfo(name="keepdim", type="bool", default="False", is_optional=True)],141+ ParamInfo(name="input", type="Tensor"),
122- [ParamInfo(name="input", type="Tensor"),142+ ParamInfo(name=attr_name, type="Number"),
123- ParamInfo(name=attr_name, type="str", default="fro", is_optional=True),143+ ParamInfo(name="dim", type="tuple of ints", default="(-2,-1)", is_optional=True),
124- ParamInfo(name="dim", type="tuple of ints", default="(-2,-1)", is_optional=True),144+ ParamInfo(name="keepdim", type="bool", default="False", is_optional=True),
125- ParamInfo(name="keepdim", type="bool", default="False", is_optional=True)],145+ ],
126- ])146+ [
127- params, oidx = _match_overload("torch.norm_like_str", 1,147+ ParamInfo(name="input", type="Tensor"),
128- {attr_name: attr_value, "dim": (-2, -1), "keepdim": False})148+ ParamInfo(name=attr_name, type="str", default="fro", is_optional=True),
149+ ParamInfo(name="dim", type="tuple of ints", default="(-2,-1)", is_optional=True),
150+ ParamInfo(name="keepdim", type="bool", default="False", is_optional=True),
151+ ],
152+ ],
153+ )
154+ params, oidx = _match_overload(
155+ "torch.norm_like_str", 1, {attr_name: attr_value, "dim": (-2, -1), "keepdim": False}
156+ )
129 elif api_suffix == "int":157 elif api_suffix == "int":
130- _register("torch.norm_like_int", [158+ _register(
131- [ParamInfo(name="input", type="Tensor"),159+ "torch.norm_like_int",
132- ParamInfo(name=attr_name, type="Number"),160+ [
133- ParamInfo(name="dim", type="tuple of ints", default="(-2,-1)", is_optional=True),161+ [
134- ParamInfo(name="keepdim", type="bool", default="False", is_optional=True)],162+ ParamInfo(name="input", type="Tensor"),
135- [ParamInfo(name="input", type="Tensor"),163+ ParamInfo(name=attr_name, type="Number"),
136- ParamInfo(name=attr_name, type="str", default="fro", is_optional=True),164+ ParamInfo(name="dim", type="tuple of ints", default="(-2,-1)", is_optional=True),
137- ParamInfo(name="dim", type="tuple of ints", default="(-2,-1)", is_optional=True),165+ ParamInfo(name="keepdim", type="bool", default="False", is_optional=True),
138- ParamInfo(name="keepdim", type="bool", default="False", is_optional=True)],166+ ],
139- ])167+ [
140- params, oidx = _match_overload("torch.norm_like_int", 1,168+ ParamInfo(name="input", type="Tensor"),
141- {attr_name: attr_value, "dim": (-2, -1), "keepdim": False})169+ ParamInfo(name=attr_name, type="str", default="fro", is_optional=True),
170+ ParamInfo(name="dim", type="tuple of ints", default="(-2,-1)", is_optional=True),
171+ ParamInfo(name="keepdim", type="bool", default="False", is_optional=True),
172+ ],
173+ ],
174+ )
175+ params, oidx = _match_overload(
176+ "torch.norm_like_int", 1, {attr_name: attr_value, "dim": (-2, -1), "keepdim": False}
177+ )
142 elif api_suffix == "bool":178 elif api_suffix == "bool":
143- _register("torch.bool_overload_test", [179+ _register(
144- [ParamInfo(name="input", type="Tensor"),180+ "torch.bool_overload_test",
145- ParamInfo(name=attr_name, type="str"),181+ [
146- ParamInfo(name="dim", type="int")],182+ [
147- [ParamInfo(name="input", type="Tensor"),183+ ParamInfo(name="input", type="Tensor"),
148- ParamInfo(name=attr_name, type="bool"),184+ ParamInfo(name=attr_name, type="str"),
149- ParamInfo(name="dim", type="int")],185+ ParamInfo(name="dim", type="int"),
150- ])186+ ],
151- params, oidx = _match_overload("torch.bool_overload_test", 1,187+ [
152- {attr_name: attr_value, "dim": 0})188+ ParamInfo(name="input", type="Tensor"),
189+ ParamInfo(name=attr_name, type="bool"),
190+ ParamInfo(name="dim", type="int"),
191+ ],
192+ ],
193+ )
194+ params, oidx = _match_overload("torch.bool_overload_test", 1, {attr_name: attr_value, "dim": 0})
153 else:195 else:
154- _register("torch.tuple_overload_test", [196+ _register(
155- [ParamInfo(name="input", type="Tensor"),197+ "torch.tuple_overload_test",
156- ParamInfo(name=attr_name, type="int"),198+ [
157- ParamInfo(name="mode", type="str")],199+ [
158- [ParamInfo(name="input", type="Tensor"),200+ ParamInfo(name="input", type="Tensor"),
159- ParamInfo(name=attr_name, type="tuple of ints"),201+ ParamInfo(name=attr_name, type="int"),
160- ParamInfo(name="mode", type="str")],202+ ParamInfo(name="mode", type="str"),
161- ])203+ ],
162- params, oidx = _match_overload("torch.tuple_overload_test", 1,204+ [
163- {attr_name: attr_value, "mode": "bilinear"})205+ ParamInfo(name="input", type="Tensor"),
206+ ParamInfo(name=attr_name, type="tuple of ints"),
207+ ParamInfo(name="mode", type="str"),
208+ ],
209+ ],
210+ )
211+ params, oidx = _match_overload("torch.tuple_overload_test", 1, {attr_name: attr_value, "mode": "bilinear"})
164 assert oidx == expected_oidx212 assert oidx == expected_oidx
165 213 
166 def test_key_score_takes_priority_over_value_score(self):214 def test_key_score_takes_priority_over_value_score(self):
167- _register("torch.key_priority_test", [215+ _register(
168- [ParamInfo(name="input", type="Tensor"),216+ "torch.key_priority_test",
169- ParamInfo(name="alpha", type="Number")],217+ [
170- [ParamInfo(name="input", type="Tensor"),218+ [ParamInfo(name="input", type="Tensor"), ParamInfo(name="alpha", type="Number")],
171- ParamInfo(name="alpha", type="Number"),219+ [
172- ParamInfo(name="extra", type="str")],220+ ParamInfo(name="input", type="Tensor"),
173- ])221+ ParamInfo(name="alpha", type="Number"),
174- params, oidx = _match_overload(222+ ParamInfo(name="extra", type="str"),
175- "torch.key_priority_test", 1,223+ ],
176- {"alpha": 0.5})224+ ],
225+ )
226+ params, oidx = _match_overload("torch.key_priority_test", 1, {"alpha": 0.5})
177 assert oidx == 0227 assert oidx == 0
178 228 
179 def test_live_matrix_norm_overload_selection(self):229 def test_live_matrix_norm_overload_selection(self):
@@ -181,9 +231,7 @@ class TestScoreAttrTypeCompatibility:
181 import torch # noqa: F401231 import torch # noqa: F401
182 except ImportError:232 except ImportError:
183 pytest.skip("torch not available")233 pytest.skip("torch not available")
184- params, oidx = _match_overload(234+ params, oidx = _match_overload("torch.linalg.matrix_norm", 1, {"ord": "fro", "dim": (-2, -1), "keepdim": False})
185- "torch.linalg.matrix_norm", 1,
186- {"ord": "fro", "dim": (-2, -1), "keepdim": False})
187 assert oidx == 1235 assert oidx == 1
188 ord_param = next(p for p in params if p.name == "ord")236 ord_param = next(p for p in params if p.name == "ord")
189 assert ord_param.type == "str"237 assert ord_param.type == "str"
@@ -200,32 +248,45 @@ class TestCoerceValue:
200 - test_unknown_type_raises: 未知目标类型 → ValueError248 - test_unknown_type_raises: 未知目标类型 → ValueError
201 """249 """
202 250 
203- @pytest.mark.parametrize("value, target_type, expected", [251+ @pytest.mark.parametrize(
204- (None, "int", None),252+ "value, target_type, expected",
205- ("None", "int", None),253+ [
206- ("3", "int", 3),254+ (None, "int", None),
207- ("3.5", "float", 3.5),255+ ("None", "int", None),
208- ("2.0", "Number", 2.0),256+ ("3", "int", 3),
209- ("hello", "str", "hello"),257+ ("3.5", "float", 3.5),
210- ((2, 2), "int", (2, 2)),258+ ("2.0", "Number", 2.0),
211- ([2, 3], "int", (2, 3)),259+ ("hello", "str", "hello"),
212- ((1.0, 2.0), "float", (1.0, 2.0)),260+ ((2, 2), "int", (2, 2)),
213- ([1, 2], "float", (1.0, 2.0)),261+ ([2, 3], "int", (2, 3)),
214- ("true", "bool", True),262+ ((1.0, 2.0), "float", (1.0, 2.0)),
215- ("false", "bool", False),263+ ([1, 2], "float", (1.0, 2.0)),
216- (True, "bool", True),264+ ("true", "bool", True),
217- (False, "bool", False),265+ ("false", "bool", False),
218- (2, "int", 2),266+ (True, "bool", True),
219- (1.0, "float", 1.0),267+ (False, "bool", False),
220- ], ids=[268+ (2, "int", 2),
221- "none-passthrough", "string-none",269+ (1.0, "float", 1.0),
222- "int-string", "float-string", "number-coerces-float",270+ ],
223- "str-passthrough", "int-tuple-fallback", "int-list-fallback",271+ ids=[
224- "float-tuple-fallback", "float-list-fallback",272+ "none-passthrough",
225- "bool-string-true", "bool-string-false",273+ "string-none",
226- "bool-native-true", "bool-native-false",274+ "int-string",
227- "int-native", "float-native",275+ "float-string",
228- ])276+ "number-coerces-float",
277+ "str-passthrough",
278+ "int-tuple-fallback",
279+ "int-list-fallback",
280+ "float-tuple-fallback",
281+ "float-list-fallback",
282+ "bool-string-true",
283+ "bool-string-false",
284+ "bool-native-true",
285+ "bool-native-false",
286+ "int-native",
287+ "float-native",
288+ ],
289+ )
229 def test_coerce_value(self, value, target_type, expected):290 def test_coerce_value(self, value, target_type, expected):
230 assert coerce_value(value, target_type) == expected291 assert coerce_value(value, target_type) == expected
231 292 
@@ -235,16 +296,16 @@ class TestCoerceValue:
235 296 
236 297 
237class TestBuildPositionalArgs:298class TestBuildPositionalArgs:
238- 
239 def test_simple_two_tensors(self):299 def test_simple_two_tensors(self):
240- _register("torch.add", [300+ _register(
241- ParamInfo(name="input", type="Tensor"),301+ "torch.add",
242- ParamInfo(name="other", type="Tensor"),302+ [
243- ParamInfo(name="alpha", type="Number", default="1",303+ ParamInfo(name="input", type="Tensor"),
244- is_optional=True, is_keyword_only=True),304+ ParamInfo(name="other", type="Tensor"),
245- ParamInfo(name="out", type="Tensor", default="None",305+ ParamInfo(name="alpha", type="Number", default="1", is_optional=True, is_keyword_only=True),
246- is_optional=True, is_keyword_only=True),306+ ParamInfo(name="out", type="Tensor", default="None", is_optional=True, is_keyword_only=True),
247- ])307+ ],
308+ )
248 t1, t2 = "T1", "T2"309 t1, t2 = "T1", "T2"
249 args, kwargs, oidx = _build_positional_args("torch.add", [t1, t2], {"alpha": "2.0"}, ())310 args, kwargs, oidx = _build_positional_args("torch.add", [t1, t2], {"alpha": "2.0"}, ())
250 assert args == ["T1", "T2"]311 assert args == ["T1", "T2"]
@@ -252,71 +313,79 @@ class TestBuildPositionalArgs:
252 assert oidx == 0313 assert oidx == 0
253 314 
254 def test_interleaved_tensor_scalar(self):315 def test_interleaved_tensor_scalar(self):
255- _register("torch.gather", [316+ _register(
256- ParamInfo(name="input", type="Tensor"),317+ "torch.gather",
257- ParamInfo(name="dim", type="int"),318+ [
258- ParamInfo(name="index", type="Tensor"),319+ ParamInfo(name="input", type="Tensor"),
259- ParamInfo(name="out", type="Tensor", default="None",320+ ParamInfo(name="dim", type="int"),
260- is_optional=True, is_keyword_only=True),321+ ParamInfo(name="index", type="Tensor"),
261- ParamInfo(name="sparse_grad", type="bool", default="False",322+ ParamInfo(name="out", type="Tensor", default="None", is_optional=True, is_keyword_only=True),
262- is_optional=True, is_keyword_only=True),323+ ParamInfo(name="sparse_grad", type="bool", default="False", is_optional=True, is_keyword_only=True),
263- ])324+ ],
325+ )
264 t1, t2 = "T_INPUT", "T_INDEX"326 t1, t2 = "T_INPUT", "T_INDEX"
265- args, kwargs, oidx = _build_positional_args(327+ args, kwargs, oidx = _build_positional_args("torch.gather", [t1, t2], {"dim": "1"}, ())
266- "torch.gather", [t1, t2], {"dim": "1"}, ())
267 assert args == ["T_INPUT", 1, "T_INDEX"]328 assert args == ["T_INPUT", 1, "T_INDEX"]
268 assert kwargs == {"sparse_grad": False}329 assert kwargs == {"sparse_grad": False}
269 330 
270 def test_out_tensor_from_output_indexes(self):331 def test_out_tensor_from_output_indexes(self):
271- _register("torch.abs_out", [332+ _register(
272- ParamInfo(name="input", type="Tensor"),333+ "torch.abs_out",
273- ParamInfo(name="out", type="Tensor", default="None",334+ [
274- is_optional=True, is_keyword_only=True),335+ ParamInfo(name="input", type="Tensor"),
275- ])336+ ParamInfo(name="out", type="Tensor", default="None", is_optional=True, is_keyword_only=True),
337+ ],
338+ )
276 t_in, t_out = "T_IN", "T_OUT"339 t_in, t_out = "T_IN", "T_OUT"
277- args, kwargs, oidx = _build_positional_args(340+ args, kwargs, oidx = _build_positional_args("torch.abs_out", [t_in, t_out], {}, (1,))
278- "torch.abs_out", [t_in, t_out], {}, (1,))
279 assert args == ["T_IN"]341 assert args == ["T_IN"]
280 assert kwargs == {"out": "T_OUT"}342 assert kwargs == {"out": "T_OUT"}
281 343 
282 def test_tensorlist_param(self):344 def test_tensorlist_param(self):
283- _register("torch.cat", [345+ _register(
284- ParamInfo(name="tensors", type="tuple of Tensors"),346+ "torch.cat",
285- ParamInfo(name="dim", type="int", default="0"),347+ [
286- ])348+ ParamInfo(name="tensors", type="tuple of Tensors"),
349+ ParamInfo(name="dim", type="int", default="0"),
350+ ],
351+ )
287 tl = ["T_A", "T_B"]352 tl = ["T_A", "T_B"]
288- args, kwargs, oidx = _build_positional_args(353+ args, kwargs, oidx = _build_positional_args("torch.cat", [tl], {"dim": "0"}, ())
289- "torch.cat", [tl], {"dim": "0"}, ())
290 assert args == [["T_A", "T_B"], 0]354 assert args == [["T_A", "T_B"], 0]
291 assert kwargs == {}355 assert kwargs == {}
292 356 
293 def test_multi_overload_tensor_scalar(self):357 def test_multi_overload_tensor_scalar(self):
294- _register("torch.div", [358+ _register(
295- [ParamInfo(name="input", type="Tensor"),359+ "torch.div",
296- ParamInfo(name="other", type="Tensor"),360+ [
297- ParamInfo(name="rounding_mode", type="str", default="None",361+ [
298- is_optional=True, is_keyword_only=True),362+ ParamInfo(name="input", type="Tensor"),
299- ParamInfo(name="out", type="Tensor", default="None",363+ ParamInfo(name="other", type="Tensor"),
300- is_optional=True, is_keyword_only=True)],364+ ParamInfo(name="rounding_mode", type="str", default="None", is_optional=True, is_keyword_only=True),
301- [ParamInfo(name="input", type="Tensor"),365+ ParamInfo(name="out", type="Tensor", default="None", is_optional=True, is_keyword_only=True),
302- ParamInfo(name="other", type="Number"),366+ ],
303- ParamInfo(name="rounding_mode", type="str", default="None",367+ [
304- is_optional=True, is_keyword_only=True),368+ ParamInfo(name="input", type="Tensor"),
305- ParamInfo(name="out", type="Tensor", default="None",369+ ParamInfo(name="other", type="Number"),
306- is_optional=True, is_keyword_only=True)],370+ ParamInfo(name="rounding_mode", type="str", default="None", is_optional=True, is_keyword_only=True),
307- ])371+ ParamInfo(name="out", type="Tensor", default="None", is_optional=True, is_keyword_only=True),
372+ ],
373+ ],
374+ )
308 t1 = "T_IN"375 t1 = "T_IN"
309- args, kwargs, oidx = _build_positional_args(376+ args, kwargs, oidx = _build_positional_args("torch.div", [t1], {"other": "3.0"}, ())
310- "torch.div", [t1], {"other": "3.0"}, ())
311 assert oidx == 1377 assert oidx == 1
312 assert args == ["T_IN", 3.0]378 assert args == ["T_IN", 3.0]
313 379 
314 def test_raises_when_overload_not_matched(self):380 def test_raises_when_overload_not_matched(self):
315- _register("torch.raise_test", [381+ _register(
316- ParamInfo(name="input", type="Tensor"),382+ "torch.raise_test",
317- ParamInfo(name="dim", type="int", default="0"),383+ [
318- ParamInfo(name="index", type="Tensor"),384+ ParamInfo(name="input", type="Tensor"),
319- ])385+ ParamInfo(name="dim", type="int", default="0"),
386+ ParamInfo(name="index", type="Tensor"),
387+ ],
388+ )
320 with pytest.raises(ValueError, match="Cannot match overload"):389 with pytest.raises(ValueError, match="Cannot match overload"):
321 _build_positional_args("torch.raise_test", ["T1"], {}, ())390 _build_positional_args("torch.raise_test", ["T1"], {}, ())
322 391 
@@ -325,19 +394,24 @@ class TestVarPositional:
325 """Tests for *args tensor parameter support (is_var_positional=True)."""394 """Tests for *args tensor parameter support (is_var_positional=True)."""
326 395 
327 def test_var_positional_consumes_all_remaining_tensors(self):396 def test_var_positional_consumes_all_remaining_tensors(self):
328- _register("torch.block_diag", [397+ _register(
329- ParamInfo(name="tensors", type="Tensor", is_var_positional=True),398+ "torch.block_diag",
330- ])399+ [
331- args, kwargs, oidx = _build_positional_args(400+ ParamInfo(name="tensors", type="Tensor", is_var_positional=True),
332- "torch.block_diag", ["T1", "T2", "T3"], {}, ())401+ ],
402+ )
403+ args, kwargs, oidx = _build_positional_args("torch.block_diag", ["T1", "T2", "T3"], {}, ())
333 assert args == ["T1", "T2", "T3"]404 assert args == ["T1", "T2", "T3"]
334 assert kwargs == {}405 assert kwargs == {}
335 assert oidx == 0406 assert oidx == 0
336 407 
337 def test_match_overload_var_positional_accepts_any_count(self):408 def test_match_overload_var_positional_accepts_any_count(self):
338- _register("torch.vp_match", [409+ _register(
339- [ParamInfo(name="tensors", type="Tensor", is_var_positional=True)],410+ "torch.vp_match",
340- ])411+ [
412+ [ParamInfo(name="tensors", type="Tensor", is_var_positional=True)],
413+ ],
414+ )
341 params1, oidx1 = _match_overload("torch.vp_match", 2)415 params1, oidx1 = _match_overload("torch.vp_match", 2)
342 assert params1 is not None416 assert params1 is not None
343 assert oidx1 == 0417 assert oidx1 == 0
@@ -346,18 +420,24 @@ class TestVarPositional:
346 assert oidx2 == 0420 assert oidx2 == 0
347 421 
348 def test_match_overload_non_var_still_enforces_upper_bound(self):422 def test_match_overload_non_var_still_enforces_upper_bound(self):
349- _register("torch.vp_no_var", [423+ _register(
350- [ParamInfo(name="input", type="Tensor"),424+ "torch.vp_no_var",
351- ParamInfo(name="other", type="Tensor")],425+ [
352- ])426+ [ParamInfo(name="input", type="Tensor"), ParamInfo(name="other", type="Tensor")],
427+ ],
428+ )
353 params, oidx = _match_overload("torch.vp_no_var", 3)429 params, oidx = _match_overload("torch.vp_no_var", 3)
354 assert params is None430 assert params is None
355 431 
356 def test_param_plan_var_positional(self):432 def test_param_plan_var_positional(self):
357- _register("torch.vp_plan", [433+ _register(
358- ParamInfo(name="tensors", type="Tensor", is_var_positional=True),434+ "torch.vp_plan",
359- ])435+ [
436+ ParamInfo(name="tensors", type="Tensor", is_var_positional=True),
437+ ],
438+ )
360 from ttk.core_modules.testcase_manager.param_plan import ParamPlan439 from ttk.core_modules.testcase_manager.param_plan import ParamPlan
440+ 
361 params, oidx = _match_overload("torch.vp_plan", 3)441 params, oidx = _match_overload("torch.vp_plan", 3)
362 plan = ParamPlan("torch.vp_plan", params, oidx, (), {})442 plan = ParamPlan("torch.vp_plan", params, oidx, (), {})
363 args, kwargs, _ = plan.build_args(["A", "B", "C"])443 args, kwargs, _ = plan.build_args(["A", "B", "C"])
@@ -369,11 +449,15 @@ class TestParamPlanBuildArgs:
369 """Tests for ParamPlan.build_args (reusable plan)."""449 """Tests for ParamPlan.build_args (reusable plan)."""
370 450 
371 def test_plan_reused_produces_same_result(self):451 def test_plan_reused_produces_same_result(self):
372- _register("torch.plan_reuse", [452+ _register(
373- ParamInfo(name="input", type="Tensor"),453+ "torch.plan_reuse",
374- ParamInfo(name="dim", type="int", default="0"),454+ [
375- ])455+ ParamInfo(name="input", type="Tensor"),
456+ ParamInfo(name="dim", type="int", default="0"),
457+ ],
458+ )
376 from ttk.core_modules.testcase_manager.param_plan import ParamPlan459 from ttk.core_modules.testcase_manager.param_plan import ParamPlan
460+ 
377 params, oidx = _match_overload("torch.plan_reuse", 1)461 params, oidx = _match_overload("torch.plan_reuse", 1)
378 plan = ParamPlan("torch.plan_reuse", params, oidx, (), {})462 plan = ParamPlan("torch.plan_reuse", params, oidx, (), {})
379 463 
@@ -385,11 +469,15 @@ class TestParamPlanBuildArgs:
385 assert kwargs2 == {}469 assert kwargs2 == {}
386 470 
387 def test_plan_with_out_tensor(self):471 def test_plan_with_out_tensor(self):
388- _register("torch.plan_out", [472+ _register(
389- ParamInfo(name="input", type="Tensor"),473+ "torch.plan_out",
390- ParamInfo(name="out", type="Tensor", is_keyword_only=True, is_optional=True),474+ [
391- ])475+ ParamInfo(name="input", type="Tensor"),
476+ ParamInfo(name="out", type="Tensor", is_keyword_only=True, is_optional=True),
477+ ],
478+ )
392 from ttk.core_modules.testcase_manager.param_plan import ParamPlan479 from ttk.core_modules.testcase_manager.param_plan import ParamPlan
480+ 
393 params, oidx = _match_overload("torch.plan_out", 1)481 params, oidx = _match_overload("torch.plan_out", 1)
394 plan = ParamPlan("torch.plan_out", params, oidx, (0,), {})482 plan = ParamPlan("torch.plan_out", params, oidx, (0,), {})
395 483 
@@ -404,6 +492,7 @@ class TestGetParamPlan:
404 492 
405 def _make_testcase(self, api_name, shapes, dtypes, attrs=None, output_indexes=()):493 def _make_testcase(self, api_name, shapes, dtypes, attrs=None, output_indexes=()):
406 from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e494 from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e
495+ 
407 case = TestcaseE2e()496 case = TestcaseE2e()
408 case.api_name = api_name497 case.api_name = api_name
409 case.tensor_view_shapes = shapes498 case.tensor_view_shapes = shapes
@@ -414,33 +503,27 @@ class TestGetParamPlan:
414 return case503 return case
415 504 
416 def test_plan_cached(self):505 def test_plan_cached(self):
417- case = self._make_testcase(506+ case = self._make_testcase("torch.abs", ((2, 3), (2, 3)), ("float32", "float32"), output_indexes=(1,))
418- 'torch.abs',
419- ((2, 3), (2, 3)),
420- ('float32', 'float32'),
421- output_indexes=(1,))
422 plan1 = case.get_param_plan()507 plan1 = case.get_param_plan()
423 plan2 = case.get_param_plan()508 plan2 = case.get_param_plan()
424 assert plan1 is plan2509 assert plan1 is plan2
425 510 
426 def test_plan_none_for_no_api(self):511 def test_plan_none_for_no_api(self):
427- case = self._make_testcase(512+ case = self._make_testcase("nonexistent.api", ((2, 3),), ("float32",))
428- 'nonexistent.api',
429- ((2, 3),),
430- ('float32',))
431 plan = case.get_param_plan()513 plan = case.get_param_plan()
432 assert plan is None514 assert plan is None
433 515 
434 def test_plan_build_args_integration(self):516 def test_plan_build_args_integration(self):
435 import numpy as np517 import numpy as np
436- _register("torch.np_test", [518+ 
437- ParamInfo(name="input", type="Tensor"),519+ _register(
438- ParamInfo(name="other", type="Tensor"),520+ "torch.np_test",
439- ])521+ [
440- case = self._make_testcase(522+ ParamInfo(name="input", type="Tensor"),
441- 'torch.np_test',523+ ParamInfo(name="other", type="Tensor"),
442- ((2, 3), (2, 3)),524+ ],
443- ('float32', 'float32'))525+ )
526+ case = self._make_testcase("torch.np_test", ((2, 3), (2, 3)), ("float32", "float32"))
444 plan = case.get_param_plan()527 plan = case.get_param_plan()
445 assert plan is not None528 assert plan is not None
446 tensors = [np.zeros((2, 3), np.float32), np.zeros((2, 3), np.float32)]529 tensors = [np.zeros((2, 3), np.float32), np.zeros((2, 3), np.float32)]
@@ -455,8 +538,9 @@ class TestNpuWeightQuantBatchmatmul:
455 @staticmethod538 @staticmethod
456 def _make_case(variant, shapes, dtypes, attrs=None):539 def _make_case(variant, shapes, dtypes, attrs=None):
457 from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e540 from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e
541+ 
458 case = TestcaseE2e()542 case = TestcaseE2e()
459- case.api_name = 'torch_npu.npu_weight_quant_batchmatmul'543+ case.api_name = "torch_npu.npu_weight_quant_batchmatmul"
460 case.tensor_view_shapes = shapes544 case.tensor_view_shapes = shapes
461 case.tensor_dtypes = dtypes545 case.tensor_dtypes = dtypes
462 case.attributes = attrs or {}546 case.attributes = attrs or {}
@@ -465,9 +549,12 @@ class TestNpuWeightQuantBatchmatmul:
465 549 
466 def test_per_channel_4_tensors_3_none(self):550 def test_per_channel_4_tensors_3_none(self):
467 import numpy as np551 import numpy as np
468- case = self._make_case('per_channel',552+ 
553+ case = self._make_case(
554+ "per_channel",
469 ((4, 16), (16, 8), (1, 8), (1, 8), None, None, None),555 ((4, 16), (16, 8), (1, 8), (1, 8), None, None, None),
470- ('float16', 'int8', 'float16', 'float16', None, None, None))556+ ("float16", "int8", "float16", "float16", None, None, None),
557+ )
471 assert case.is_valid558 assert case.is_valid
472 559 
473 plan = case.get_param_plan()560 plan = case.get_param_plan()
@@ -477,7 +564,9 @@ class TestNpuWeightQuantBatchmatmul:
477 np.zeros((16, 8), np.int8),564 np.zeros((16, 8), np.int8),
478 np.zeros((1, 8), np.float16),565 np.zeros((1, 8), np.float16),
479 np.zeros((1, 8), np.float16),566 np.zeros((1, 8), np.float16),
480- None, None, None,567+ None,
568+ None,
569+ None,
481 ]570 ]
482 args, kwargs, _ = plan.build_args(tensors)571 args, kwargs, _ = plan.build_args(tensors)
483 572 
@@ -494,9 +583,12 @@ class TestNpuWeightQuantBatchmatmul:
494 583 
495 def test_with_bias_5_tensors_2_none(self):584 def test_with_bias_5_tensors_2_none(self):
496 import numpy as np585 import numpy as np
497- case = self._make_case('with_bias',586+ 
587+ case = self._make_case(
588+ "with_bias",
498 ((4, 16), (16, 8), (1, 8), (1, 8), None, None, (4, 8)),589 ((4, 16), (16, 8), (1, 8), (1, 8), None, None, (4, 8)),
499- ('float16', 'int8', 'float16', 'float16', None, None, 'float16'))590+ ("float16", "int8", "float16", "float16", None, None, "float16"),
591+ )
500 assert case.is_valid592 assert case.is_valid
501 593 
502 plan = case.get_param_plan()594 plan = case.get_param_plan()
@@ -505,7 +597,8 @@ class TestNpuWeightQuantBatchmatmul:
505 np.zeros((16, 8), np.int8),597 np.zeros((16, 8), np.int8),
506 np.zeros((1, 8), np.float16),598 np.zeros((1, 8), np.float16),
507 np.zeros((1, 8), np.float16),599 np.zeros((1, 8), np.float16),
508- None, None,600+ None,
601+ None,
509 np.zeros((4, 8), np.float16),602 np.zeros((4, 8), np.float16),
510 ]603 ]
511 args, kwargs, _ = plan.build_args(tensors)604 args, kwargs, _ = plan.build_args(tensors)
@@ -523,10 +616,13 @@ class TestNpuWeightQuantBatchmatmul:
523 616 
524 def test_per_group_with_attr(self):617 def test_per_group_with_attr(self):
525 import numpy as np618 import numpy as np
526- case = self._make_case('per_group',619+ 
620+ case = self._make_case(
621+ "per_group",
527 ((4, 16), (16, 8), (2, 8), (2, 8), None, None, None),622 ((4, 16), (16, 8), (2, 8), (2, 8), None, None, None),
528- ('float16', 'int8', 'float16', 'float16', None, None, None),623+ ("float16", "int8", "float16", "float16", None, None, None),
529- attrs={'antiquant_group_size': 8})624+ attrs={"antiquant_group_size": 8},
625+ )
530 assert case.is_valid626 assert case.is_valid
531 627 
532 plan = case.get_param_plan()628 plan = case.get_param_plan()
@@ -535,7 +631,9 @@ class TestNpuWeightQuantBatchmatmul:
535 np.zeros((16, 8), np.int8),631 np.zeros((16, 8), np.int8),
536 np.zeros((2, 8), np.float16),632 np.zeros((2, 8), np.float16),
537 np.zeros((2, 8), np.float16),633 np.zeros((2, 8), np.float16),
538- None, None, None,634+ None,
635+ None,
636+ None,
539 ]637 ]
540 args, kwargs, _ = plan.build_args(tensors)638 args, kwargs, _ = plan.build_args(tensors)
541 639 
@@ -552,22 +650,26 @@ class TestNpuWeightQuantBatchmatmul:
552 650 
553 651 
554class TestInplaceTensorMethodBuildArgs:652class TestInplaceTensorMethodBuildArgs:
555- 
556 def test_inplace_build_args_includes_self(self):653 def test_inplace_build_args_includes_self(self):
557 import numpy as np654 import numpy as np
558- _register("torch.Tensor.fake_add_", [655+ 
559- ParamInfo(name="self", type="Tensor"),656+ _register(
560- ParamInfo(name="other", type="Tensor"),657+ "torch.Tensor.fake_add_",
561- ParamInfo(name="alpha", type="Number", default="1", is_optional=True,658+ [
562- is_keyword_only=True),659+ ParamInfo(name="self", type="Tensor"),
563- ])660+ ParamInfo(name="other", type="Tensor"),
661+ ParamInfo(name="alpha", type="Number", default="1", is_optional=True, is_keyword_only=True),
662+ ],
663+ )
564 from ttk.core_modules.testcase_manager.param_plan import ParamPlan664 from ttk.core_modules.testcase_manager.param_plan import ParamPlan
665+ 
565 plan = ParamPlan(666 plan = ParamPlan(
566 api_name="torch.Tensor.fake_add_",667 api_name="torch.Tensor.fake_add_",
567 overload_params=_MANUAL_OVERRIDES["torch.Tensor.fake_add_"].params,668 overload_params=_MANUAL_OVERRIDES["torch.Tensor.fake_add_"].params,
568 overload_index=0,669 overload_index=0,
569 output_tensor_indexes=(0,),670 output_tensor_indexes=(0,),
570- attributes={"alpha": "2"})671+ attributes={"alpha": "2"},
672+ )
571 tensors = [673 tensors = [
572 np.zeros((2, 3), np.float32),674 np.zeros((2, 3), np.float32),
573 np.ones((2, 3), np.float32),675 np.ones((2, 3), np.float32),
@@ -584,6 +686,7 @@ class TestCheckInputCountExceededVarPos:
584 686 
585 def _make_testcase(self, api_name, shapes, dtypes, attrs=None, output_indexes=()):687 def _make_testcase(self, api_name, shapes, dtypes, attrs=None, output_indexes=()):
586 from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e688 from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e
689+ 
587 case = TestcaseE2e()690 case = TestcaseE2e()
588 case.api_name = api_name691 case.api_name = api_name
589 case.tensor_view_shapes = shapes692 case.tensor_view_shapes = shapes
@@ -594,25 +697,27 @@ class TestCheckInputCountExceededVarPos:
594 return case697 return case
595 698 
596 def test_var_pos_overload_skips_input_count_check(self):699 def test_var_pos_overload_skips_input_count_check(self):
597- _register("torch.block_diag", [700+ _register(
598- ParamInfo(name="tensors", type="Tensor", is_var_positional=True),
599- ])
600- case = self._make_testcase(
601 "torch.block_diag",701 "torch.block_diag",
602- ((4, 4), (4, 4)),702+ [
603- ("float32", "float32"))703+ ParamInfo(name="tensors", type="Tensor", is_var_positional=True),
704+ ],
705+ )
706+ case = self._make_testcase("torch.block_diag", ((4, 4), (4, 4)), ("float32", "float32"))
604 assert case.is_valid707 assert case.is_valid
605 assert case.fail_reason != "INPUT_COUNT_EXCEEDED"708 assert case.fail_reason != "INPUT_COUNT_EXCEEDED"
606 709 
607 def test_non_var_pos_overload_still_checked(self):710 def test_non_var_pos_overload_still_checked(self):
608- _register("torch.strict_two_tensors", [711+ _register(
609- ParamInfo(name="input", type="Tensor"),
610- ParamInfo(name="other", type="Tensor"),
611- ])
612- case = self._make_testcase(
613 "torch.strict_two_tensors",712 "torch.strict_two_tensors",
614- ((4, 4), (4, 4), (4, 4)),713+ [
615- ("float32", "float32", "float32"))714+ ParamInfo(name="input", type="Tensor"),
715+ ParamInfo(name="other", type="Tensor"),
716+ ],
717+ )
718+ case = self._make_testcase(
719+ "torch.strict_two_tensors", ((4, 4), (4, 4), (4, 4)), ("float32", "float32", "float32")
720+ )
616 assert not case.is_valid721 assert not case.is_valid
617 assert case.fail_reason == "INPUT_COUNT_EXCEEDED"722 assert case.fail_reason == "INPUT_COUNT_EXCEEDED"
618 723 
@@ -621,19 +726,16 @@ class TestCheckInputCountExceededVarPos:
621 import torch # noqa: F401726 import torch # noqa: F401
622 except ImportError:727 except ImportError:
623 pytest.skip("torch not available")728 pytest.skip("torch not available")
624- case = self._make_testcase(729+ case = self._make_testcase("torch.block_diag", ((4, 4), (4, 4)), ("float32", "float32"))
625- "torch.block_diag",
626- ((4, 4), (4, 4)),
627- ("float32", "float32"))
628 assert case.is_valid730 assert case.is_valid
629 731 
630 732 
631class TestTensorListOutBuildArgs:733class TestTensorListOutBuildArgs:
632- 
633 def test_tensor_list_out_assembles_tuple(self):734 def test_tensor_list_out_assembles_tuple(self):
634 import numpy as np735 import numpy as np
635 736 
636 from ttk.core_modules.testcase_manager.param_plan import ParamPlan737 from ttk.core_modules.testcase_manager.param_plan import ParamPlan
738+ 
637 params = [739 params = [
638 ParamInfo(name="input", type="Tensor"),740 ParamInfo(name="input", type="Tensor"),
639 ParamInfo(name="out", type="Tensor[]", is_keyword_only=True),741 ParamInfo(name="out", type="Tensor[]", is_keyword_only=True),
@@ -652,15 +754,16 @@ class TestTensorListOutBuildArgs:
652 )754 )
653 args, kwargs, extra = plan.build_args([t0, t1, t2, t3, t4])755 args, kwargs, extra = plan.build_args([t0, t1, t2, t3, t4])
654 assert len(args) == 1756 assert len(args) == 1
655- assert 'out' in kwargs757+ assert "out" in kwargs
656- assert len(kwargs['out']) == 4758+ assert len(kwargs["out"]) == 4
657- assert kwargs['out'][0] is t1759+ assert kwargs["out"][0] is t1
658- assert kwargs['out'][3] is t4760+ assert kwargs["out"][3] is t4
659 761 
660 def test_single_out_still_works(self):762 def test_single_out_still_works(self):
661 import numpy as np763 import numpy as np
662 764 
663 from ttk.core_modules.testcase_manager.param_plan import ParamPlan765 from ttk.core_modules.testcase_manager.param_plan import ParamPlan
766+ 
664 params = [767 params = [
665 ParamInfo(name="input", type="Tensor"),768 ParamInfo(name="input", type="Tensor"),
666 ParamInfo(name="out", type="Tensor", is_keyword_only=True),769 ParamInfo(name="out", type="Tensor", is_keyword_only=True),
@@ -676,12 +779,13 @@ class TestTensorListOutBuildArgs:
676 )779 )
677 args, kwargs, extra = plan.build_args([t0, t1])780 args, kwargs, extra = plan.build_args([t0, t1])
678 assert len(args) == 1781 assert len(args) == 1
679- assert kwargs['out'] is t1782+ assert kwargs["out"] is t1
680 783 
681 def test_tensor_list_out_with_attrs(self):784 def test_tensor_list_out_with_attrs(self):
682 import numpy as np785 import numpy as np
683 786 
684 from ttk.core_modules.testcase_manager.param_plan import ParamPlan787 from ttk.core_modules.testcase_manager.param_plan import ParamPlan
788+ 
685 params = [789 params = [
686 ParamInfo(name="input", type="Tensor"),790 ParamInfo(name="input", type="Tensor"),
687 ParamInfo(name="statistic", type="bool", default=False, is_keyword_only=True),791 ParamInfo(name="statistic", type="bool", default=False, is_keyword_only=True),
@@ -697,6 +801,6 @@ class TestTensorListOutBuildArgs:
697 )801 )
698 args, kwargs, extra = plan.build_args(tensors)802 args, kwargs, extra = plan.build_args(tensors)
699 assert len(args) == 1803 assert len(args) == 1
700- assert kwargs['statistic'] is True804+ assert kwargs["statistic"] is True
701- assert 'out' in kwargs805+ assert "out" in kwargs
702- assert len(kwargs['out']) == 4806+ assert len(kwargs["out"]) == 4
@@ -13,6 +13,7 @@
13"""13"""
14Tests for attributes1~attributes9 extension columns merged into attributes.14Tests for attributes1~attributes9 extension columns merged into attributes.
15"""15"""
16+ 
16import pytest17import pytest
17 18 
18from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn19from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn
@@ -72,8 +73,7 @@ def test_override_across_extended_columns(cls):
72@pytest.mark.parametrize("cls", CLS_LIST)73@pytest.mark.parametrize("cls", CLS_LIST)
73def test_override_chain_last_wins(cls):74def test_override_chain_last_wins(cls):
74 """多列同键链式覆盖 → 最后一列(attributes9)胜出。"""75 """多列同键链式覆盖 → 最后一列(attributes9)胜出。"""
75- case = _make(cls, attributes={"k": 0}, attributes1={"k": 1},76+ case = _make(cls, attributes={"k": 0}, attributes1={"k": 1}, attributes5={"k": 5}, attributes9={"k": 9})
76- attributes5={"k": 5}, attributes9={"k": 9})
77 case._merge_extended_attributes()77 case._merge_extended_attributes()
78 assert case.attributes == {"k": 9}78 assert case.attributes == {"k": 9}
79 assert case.is_valid is True79 assert case.is_valid is True
@@ -82,8 +82,7 @@ def test_override_chain_last_wins(cls):
82@pytest.mark.parametrize("cls", CLS_LIST)82@pytest.mark.parametrize("cls", CLS_LIST)
83def test_multi_column_merge(cls):83def test_multi_column_merge(cls):
84 """多列无冲突键 → 全部合并。"""84 """多列无冲突键 → 全部合并。"""
85- case = _make(cls, attributes={"a": 1}, attributes1={"b": 2},85+ case = _make(cls, attributes={"a": 1}, attributes1={"b": 2}, attributes2={"c": 3}, attributes3={"d": 4})
86- attributes2={"c": 3}, attributes3={"d": 4})
87 case._merge_extended_attributes()86 case._merge_extended_attributes()
88 assert case.attributes == {"a": 1, "b": 2, "c": 3, "d": 4}87 assert case.attributes == {"a": 1, "b": 2, "c": 3, "d": 4}
89 88 
@@ -117,6 +116,7 @@ def test_skip_when_invalid(cls):
117def test_validate_invokes_merge():116def test_validate_invokes_merge():
118 """validate() 内部应触发 _merge_extended_attributes。"""117 """validate() 内部应触发 _merge_extended_attributes。"""
119 from ttk.core_modules.testcase_manager.testcase_base import TestcaseBase118 from ttk.core_modules.testcase_manager.testcase_base import TestcaseBase
119+ 
120 case = TestcaseOp()120 case = TestcaseOp()
121 case.is_valid = True121 case.is_valid = True
122 case.attributes = {"a": 1}122 case.attributes = {"a": 1}
@@ -21,11 +21,14 @@ import pytest
21from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp21from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp
22 22 
23 23 
24-def _make_testcase(op_name="Add", input_shapes=((8,), (8,)),24+def _make_testcase(
25- input_dtypes=("float16", "float16"),25+ op_name="Add",
26- output_shapes=((8,),),26+ input_shapes=((8,), (8,)),
27- output_dtypes=("float16",),27+ input_dtypes=("float16", "float16"),
28- **kwargs):28+ output_shapes=((8,),),
29+ output_dtypes=("float16",),
30+ **kwargs,
31+):
29 case = TestcaseOp()32 case = TestcaseOp()
30 case.testcase_name = f"test_{op_name or 'None'}"33 case.testcase_name = f"test_{op_name or 'None'}"
31 case.op_name = op_name34 case.op_name = op_name
@@ -58,7 +61,7 @@ def _mock_env(monkeypatch):
58def _validate(case):61def _validate(case):
59 n_in = len(case.input_shapes) if case.input_shapes else 062 n_in = len(case.input_shapes) if case.input_shapes else 0
60 n_out = len(case.output_shapes) if case.output_shapes and not isinstance(case.output_shapes, str) else 063 n_out = len(case.output_shapes) if case.output_shapes and not isinstance(case.output_shapes, str) else 0
61- with patch('ttk.core_modules.operator.op_info_keeper.OpInfoKeeper') as mock:64+ with patch("ttk.core_modules.operator.op_info_keeper.OpInfoKeeper") as mock:
62 mock.return_value.info_of.return_value = {65 mock.return_value.info_of.return_value = {
63 "coreType.value": "AiCore",66 "coreType.value": "AiCore",
64 "inputs": [{"name": f"i{i}"} for i in range(n_in)],67 "inputs": [{"name": f"i{i}"} for i in range(n_in)],
@@ -71,6 +74,7 @@ def _validate(case):
71# Normalize: basic type conversions74# Normalize: basic type conversions
72# =====================================================================75# =====================================================================
73 76 
77+ 
74class TestNormalizeManualOutputBinaries:78class TestNormalizeManualOutputBinaries:
75 """Tests for _normalize_manual_binaries on output side.79 """Tests for _normalize_manual_binaries on output side.
76 80 
@@ -80,35 +84,44 @@ class TestNormalizeManualOutputBinaries:
80 - test_invalid_type_rejected: 非法类型 → MANUAL_OUTPUT_BINARIES_INVALID84 - test_invalid_type_rejected: 非法类型 → MANUAL_OUTPUT_BINARIES_INVALID
81 """85 """
82 86 
83- @pytest.mark.parametrize("output_shapes, output_dtypes, value, expected", [87+ @pytest.mark.parametrize(
84- (((8,), (8,)), ("float16", "float16"), (), ()),88+ "output_shapes, output_dtypes, value, expected",
85- (((8,), (8,)), ("float16", "float16"), None, None),89+ [
86- (((8,),), ("float16",), 'out.bin', ('out.bin',)),90+ (((8,), (8,)), ("float16", "float16"), (), ()),
87- (((8,), (8,), (8,)),91+ (((8,), (8,)), ("float16", "float16"), None, None),
88- ("float16", "float16", "float16"),92+ (((8,),), ("float16",), "out.bin", ("out.bin",)),
89- ('o1.bin', 'o2.bin', 'o3.bin'),93+ (
90- ('o1.bin', 'o2.bin', 'o3.bin')),94+ ((8,), (8,), (8,)),
91- (((8,), None, (8,)),95+ ("float16", "float16", "float16"),
92- ("float16", "float16", "float32"),96+ ("o1.bin", "o2.bin", "o3.bin"),
93- ('o1.bin', 'None', 'o3.bin'),97+ ("o1.bin", "o2.bin", "o3.bin"),
94- ('o1.bin', None, 'o3.bin')),98+ ),
95- ], ids=["empty-not-modified", "none-not-modified",99+ (
96- "single-string-wrapped", "flat-tuple-preserved",100+ ((8,), None, (8,)),
97- "none-quoted-converted"])101+ ("float16", "float16", "float32"),
98- def test_normalize_preserves_value(self, output_shapes, output_dtypes,102+ ("o1.bin", "None", "o3.bin"),
99- value, expected):103+ ("o1.bin", None, "o3.bin"),
104+ ),
105+ ],
106+ ids=[
107+ "empty-not-modified",
108+ "none-not-modified",
109+ "single-string-wrapped",
110+ "flat-tuple-preserved",
111+ "none-quoted-converted",
112+ ],
113+ )
114+ def test_normalize_preserves_value(self, output_shapes, output_dtypes, value, expected):
100 """验证 validate 后 manual_golden_binaries 等于 expected。"""115 """验证 validate 后 manual_golden_binaries 等于 expected。"""
101- case = _make_testcase(output_shapes=output_shapes,116+ case = _make_testcase(output_shapes=output_shapes, output_dtypes=output_dtypes)
102- output_dtypes=output_dtypes)
103 case.manual_golden_binaries = value117 case.manual_golden_binaries = value
104 _validate(case)118 _validate(case)
105 assert case.manual_golden_binaries == expected119 assert case.manual_golden_binaries == expected
106 120 
107 def test_list_converted_to_tuple(self):121 def test_list_converted_to_tuple(self):
108 """list 被 normalize 转换为 tuple,保留 isinstance 断言。"""122 """list 被 normalize 转换为 tuple,保留 isinstance 断言。"""
109- case = _make_testcase(output_shapes=((8,), (8,)),123+ case = _make_testcase(output_shapes=((8,), (8,)), output_dtypes=("float16", "float16"))
110- output_dtypes=("float16", "float16"))124+ case.manual_golden_binaries = ["o1.bin", "o2.bin"]
111- case.manual_golden_binaries = ['o1.bin', 'o2.bin']
112 _validate(case)125 _validate(case)
113 assert isinstance(case.manual_golden_binaries, tuple)126 assert isinstance(case.manual_golden_binaries, tuple)
114 127 
@@ -125,6 +138,7 @@ class TestNormalizeManualOutputBinaries:
125# Validation: flat outputs138# Validation: flat outputs
126# =====================================================================139# =====================================================================
127 140 
141+ 
128class TestValidateOutputBinariesFlat:142class TestValidateOutputBinariesFlat:
129 """Tests for flat output binaries validation.143 """Tests for flat output binaries validation.
130 144 
@@ -135,21 +149,18 @@ class TestValidateOutputBinariesFlat:
135 (check_reason 为 True 表示同时检查 fail_reason)149 (check_reason 为 True 表示同时检查 fail_reason)
136 """150 """
137 151 
138- @pytest.mark.parametrize("output_shapes, output_dtypes, value, expected", [152+ @pytest.mark.parametrize(
139- (((8,), (8,)), ("float16", "float16"),153+ "output_shapes, output_dtypes, value, expected",
140- ('o1.bin', 'o2.bin'), True),154+ [
141- (((8,), None, (8,)),155+ (((8,), (8,)), ("float16", "float16"), ("o1.bin", "o2.bin"), True),
142- ("float16", "float16", "float32"),156+ (((8,), None, (8,)), ("float16", "float16", "float32"), ("o1.bin", None, "o3.bin"), True),
143- ('o1.bin', None, 'o3.bin'), True),157+ (((8,), None, None), ("float16", "float16", "float16"), ("o1.bin",), ("o1.bin", None, None)),
144- (((8,), None, None),158+ ],
145- ("float16", "float16", "float16"),159+ ids=["flat-count-matches", "flat-with-none-output", "flat-trailing-none-padded"],
146- ('o1.bin',), ('o1.bin', None, None)),160+ )
147- ], ids=["flat-count-matches", "flat-with-none-output",
148- "flat-trailing-none-padded"])
149 def test_flat_valid(self, output_shapes, output_dtypes, value, expected):161 def test_flat_valid(self, output_shapes, output_dtypes, value, expected):
150 """验证 flat binaries 与 outputs 匹配时 is_valid 为真(可选检查字段值)。"""162 """验证 flat binaries 与 outputs 匹配时 is_valid 为真(可选检查字段值)。"""
151- case = _make_testcase(output_shapes=output_shapes,163+ case = _make_testcase(output_shapes=output_shapes, output_dtypes=output_dtypes)
152- output_dtypes=output_dtypes)
153 case.manual_golden_binaries = value164 case.manual_golden_binaries = value
154 _validate(case)165 _validate(case)
155 if expected is True:166 if expected is True:
@@ -157,23 +168,18 @@ class TestValidateOutputBinariesFlat:
157 else:168 else:
158 assert case.manual_golden_binaries == expected169 assert case.manual_golden_binaries == expected
159 170 
160- @pytest.mark.parametrize("output_shapes, output_dtypes, value, check_reason", [171+ @pytest.mark.parametrize(
161- (((8,), (8,)), ("float16", "float16"),172+ "output_shapes, output_dtypes, value, check_reason",
162- ('o1.bin', 'o2.bin', 'o3.bin'), True),173+ [
163- (((8,), None, (8,)),174+ (((8,), (8,)), ("float16", "float16"), ("o1.bin", "o2.bin", "o3.bin"), True),
164- ("float16", "float16", "float32"),175+ (((8,), None, (8,)), ("float16", "float16", "float32"), ("o1.bin", "unexpected.bin", "o3.bin"), False),
165- ('o1.bin', 'unexpected.bin', 'o3.bin'), False),176+ (((8,), (8,), (8,)), ("float16", "float16", "float16"), ("o1.bin", None, "o3.bin"), False),
166- (((8,), (8,), (8,)),177+ ],
167- ("float16", "float16", "float16"),178+ ids=["flat-exceeds-outputs-rejected", "file-for-none-output-rejected", "missing-file-for-non-none-rejected"],
168- ('o1.bin', None, 'o3.bin'), False),179+ )
169- ], ids=["flat-exceeds-outputs-rejected",180+ def test_flat_rejected(self, output_shapes, output_dtypes, value, check_reason):
170- "file-for-none-output-rejected",
171- "missing-file-for-non-none-rejected"])
172- def test_flat_rejected(self, output_shapes, output_dtypes,
173- value, check_reason):
174 """验证 flat binaries 与 outputs 不匹配时 is_valid 为 False。"""181 """验证 flat binaries 与 outputs 不匹配时 is_valid 为 False。"""
175- case = _make_testcase(output_shapes=output_shapes,182+ case = _make_testcase(output_shapes=output_shapes, output_dtypes=output_dtypes)
176- output_dtypes=output_dtypes)
177 case.manual_golden_binaries = value183 case.manual_golden_binaries = value
178 _validate(case)184 _validate(case)
179 assert case.is_valid is False185 assert case.is_valid is False
@@ -185,6 +191,7 @@ class TestValidateOutputBinariesFlat:
185# Validation: nested (TensorList) outputs191# Validation: nested (TensorList) outputs
186# =====================================================================192# =====================================================================
187 193 
194+ 
188class TestValidateOutputBinariesNested:195class TestValidateOutputBinariesNested:
189 """Tests for nested (TensorList) output binaries validation.196 """Tests for nested (TensorList) output binaries validation.
190 197 
@@ -193,53 +200,43 @@ class TestValidateOutputBinariesNested:
193 - test_nested_rejected: 嵌套结构不匹配 → is_valid 为 False200 - test_nested_rejected: 嵌套结构不匹配 → is_valid 为 False
194 """201 """
195 202 
196- @pytest.mark.parametrize("output_shapes, output_dtypes, value, expected", [203+ @pytest.mark.parametrize(
197- ((((8,), (8,)),),204+ "output_shapes, output_dtypes, value, expected",
198- (("float16", "float16"),),205+ [
199- (('o1.bin', 'o2.bin'),),206+ ((((8,), (8,)),), (("float16", "float16"),), (("o1.bin", "o2.bin"),), (("o1.bin", "o2.bin"),)),
200- (('o1.bin', 'o2.bin'),)),207+ ((((8,), None),), (("float16", "float16"),), (("o1.bin", None),), (("o1.bin", None),)),
201- ((((8,), None),),208+ ],
202- (("float16", "float16"),),209+ ids=["nested-preserved", "nested-with-none-in-tensorlist"],
203- (('o1.bin', None),),210+ )
204- (('o1.bin', None),)),211+ def test_nested_preserved(self, output_shapes, output_dtypes, value, expected):
205- ], ids=["nested-preserved", "nested-with-none-in-tensorlist"])
206- def test_nested_preserved(self, output_shapes, output_dtypes,
207- value, expected):
208 """验证嵌套 binaries 与 TensorList output_shapes 匹配时被保留。"""212 """验证嵌套 binaries 与 TensorList output_shapes 匹配时被保留。"""
209- case = _make_testcase(output_shapes=output_shapes,213+ case = _make_testcase(output_shapes=output_shapes, output_dtypes=output_dtypes)
210- output_dtypes=output_dtypes)
211 case.manual_golden_binaries = value214 case.manual_golden_binaries = value
212 _validate(case)215 _validate(case)
213 assert case.manual_golden_binaries == expected216 assert case.manual_golden_binaries == expected
214 217 
215- @pytest.mark.parametrize("output_shapes, output_dtypes, value", [218+ @pytest.mark.parametrize(
216- (((8,), (8,)), ("float16", "float16"),219+ "output_shapes, output_dtypes, value",
217- (('o1.bin', 'o2.bin'),)),220+ [
218- ((((8,), (8,)), (4,)), ("float16", "float32"),221+ (((8,), (8,)), ("float16", "float16"), (("o1.bin", "o2.bin"),)),
219- (('o1.bin', 'o2.bin'),)),222+ ((((8,), (8,)), (4,)), ("float16", "float32"), (("o1.bin", "o2.bin"),)),
220- ((((8,), (8,)),),223+ ((((8,), (8,)),), (("float16", "float16"),), ("o1.bin",)),
221- (("float16", "float16"),),224+ ((((8,), (8,)), (4,)), ("float16", "float32"), (("o1.bin", "o2.bin"), ("o3.bin",))),
222- ('o1.bin',)),225+ ((((8,), None),), (("float16", "float16"),), (("o1.bin", "unexpected.bin"),)),
223- ((((8,), (8,)), (4,)), ("float16", "float32"),226+ ((((8,), (8,)),), (("float16", "float16"),), (("o1.bin", None),)),
224- (('o1.bin', 'o2.bin'), ('o3.bin',))),227+ ],
225- ((((8,), None),),228+ ids=[
226- (("float16", "float16"),),229+ "nested-rejected-without-tensorlist",
227- (('o1.bin', 'unexpected.bin'),)),230+ "nested-top-level-count-mismatch",
228- ((((8,), (8,)),),231+ "nested-tensorlist-position-is-str-rejected",
229- (("float16", "float16"),),232+ "nested-non-tensorlist-position-is-tuple-rejected",
230- (('o1.bin', None),)),233+ "file-for-none-in-tensorlist-rejected",
231- ], ids=[234+ "missing-file-for-non-none-tensorlist-rejected",
232- "nested-rejected-without-tensorlist",235+ ],
233- "nested-top-level-count-mismatch",236+ )
234- "nested-tensorlist-position-is-str-rejected",
235- "nested-non-tensorlist-position-is-tuple-rejected",
236- "file-for-none-in-tensorlist-rejected",
237- "missing-file-for-non-none-tensorlist-rejected",
238- ])
239 def test_nested_rejected(self, output_shapes, output_dtypes, value):237 def test_nested_rejected(self, output_shapes, output_dtypes, value):
240 """验证嵌套结构不匹配时 is_valid 为 False。"""238 """验证嵌套结构不匹配时 is_valid 为 False。"""
241- case = _make_testcase(output_shapes=output_shapes,239+ case = _make_testcase(output_shapes=output_shapes, output_dtypes=output_dtypes)
242- output_dtypes=output_dtypes)
243 case.manual_golden_binaries = value240 case.manual_golden_binaries = value
244 _validate(case)241 _validate(case)
245 assert case.is_valid is False242 assert case.is_valid is False
@@ -249,35 +246,31 @@ class TestValidateOutputBinariesNested:
249# Reshape: flat → nested246# Reshape: flat → nested
250# =====================================================================247# =====================================================================
251 248 
249+ 
252class TestReshapeOutputBinaries:250class TestReshapeOutputBinaries:
253 """Tests for reshape: flat → nested binaries.251 """Tests for reshape: flat → nested binaries.
254 252 
255 每行参数:output_shapes/output_dtypes/value/expected,验证 flat binaries 被正确 reshape。253 每行参数:output_shapes/output_dtypes/value/expected,验证 flat binaries 被正确 reshape。
256 """254 """
257 255 
258- @pytest.mark.parametrize("output_shapes, output_dtypes, value, expected", [256+ @pytest.mark.parametrize(
259- ((((8,), (8,)),),257+ "output_shapes, output_dtypes, value, expected",
260- (("float16", "float16"),),258+ [
261- ('o1.bin', 'o2.bin'),259+ ((((8,), (8,)),), (("float16", "float16"),), ("o1.bin", "o2.bin"), (("o1.bin", "o2.bin"),)),
262- (('o1.bin', 'o2.bin'),)),260+ ((((8,), None),), (("float16", "float16"),), ("o1.bin", None), (("o1.bin", None),)),
263- ((((8,), None),),261+ (
264- (("float16", "float16"),),262+ (((8,), (8,)), (4,)),
265- ('o1.bin', None),263+ ("float16", "float32"),
266- (('o1.bin', None),)),264+ ("o1.bin", "o2.bin", "o3.bin"),
267- ((((8,), (8,)), (4,)),265+ (("o1.bin", "o2.bin"), "o3.bin"),
268- ("float16", "float32"),266+ ),
269- ('o1.bin', 'o2.bin', 'o3.bin'),267+ (((8,), (8,)), ("float16", "float16"), ("o1.bin", "o2.bin"), ("o1.bin", "o2.bin")),
270- (('o1.bin', 'o2.bin'), 'o3.bin')),268+ ],
271- (((8,), (8,)),269+ ids=["flat-to-nested", "flat-with-none-to-nested", "mixed-tensorlist-and-flat", "no-tensorlist-stays-flat"],
272- ("float16", "float16"),270+ )
273- ('o1.bin', 'o2.bin'),
274- ('o1.bin', 'o2.bin')),
275- ], ids=["flat-to-nested", "flat-with-none-to-nested",
276- "mixed-tensorlist-and-flat", "no-tensorlist-stays-flat"])
277 def test_reshape(self, output_shapes, output_dtypes, value, expected):271 def test_reshape(self, output_shapes, output_dtypes, value, expected):
278 """验证 flat binaries 被 reshape 为匹配 output_shapes 的结构。"""272 """验证 flat binaries 被 reshape 为匹配 output_shapes 的结构。"""
279- case = _make_testcase(output_shapes=output_shapes,273+ case = _make_testcase(output_shapes=output_shapes, output_dtypes=output_dtypes)
280- output_dtypes=output_dtypes)
281 case.manual_golden_binaries = value274 case.manual_golden_binaries = value
282 _validate(case)275 _validate(case)
283 assert case.manual_golden_binaries == expected276 assert case.manual_golden_binaries == expected
@@ -287,6 +280,7 @@ class TestReshapeOutputBinaries:
287# Flat properties280# Flat properties
288# =====================================================================281# =====================================================================
289 282 
283+ 
290class TestFlatOutputBinariesProperties:284class TestFlatOutputBinariesProperties:
291 """Tests for flat_manual_golden_binaries property.285 """Tests for flat_manual_golden_binaries property.
292 286 
@@ -295,25 +289,23 @@ class TestFlatOutputBinariesProperties:
295 - test_flat_output_none_when_not_set: 未设置时 flat 属性为 None289 - test_flat_output_none_when_not_set: 未设置时 flat 属性为 None
296 """290 """
297 291 
298- @pytest.mark.parametrize("output_shapes, output_dtypes, value, expected", [292+ @pytest.mark.parametrize(
299- (((8,), (8,)),293+ "output_shapes, output_dtypes, value, expected",
300- ("float16", "float16"),294+ [
301- ('o1.bin', 'o2.bin'),295+ (((8,), (8,)), ("float16", "float16"), ("o1.bin", "o2.bin"), ("o1.bin", "o2.bin")),
302- ('o1.bin', 'o2.bin')),296+ ((((8,), (8,)),), (("float16", "float16"),), (("o1.bin", "o2.bin"),), ("o1.bin", "o2.bin")),
303- ((((8,), (8,)),),297+ (
304- (("float16", "float16"),),298+ (((8,), (8,)), (4,)),
305- (('o1.bin', 'o2.bin'),),299+ ("float16", "float32"),
306- ('o1.bin', 'o2.bin')),300+ (("o1.bin", "o2.bin"), "o3.bin"),
307- ((((8,), (8,)), (4,)),301+ ("o1.bin", "o2.bin", "o3.bin"),
308- ("float16", "float32"),302+ ),
309- (('o1.bin', 'o2.bin'), 'o3.bin'),303+ ],
310- ('o1.bin', 'o2.bin', 'o3.bin')),304+ ids=["flat", "nested", "mixed"],
311- ], ids=["flat", "nested", "mixed"])305+ )
312- def test_flat_property(self, output_shapes, output_dtypes,306+ def test_flat_property(self, output_shapes, output_dtypes, value, expected):
313- value, expected):
314 """验证 flat_manual_golden_binaries 在 flat/nested/mixed 下的返回值。"""307 """验证 flat_manual_golden_binaries 在 flat/nested/mixed 下的返回值。"""
315- case = _make_testcase(output_shapes=output_shapes,308+ case = _make_testcase(output_shapes=output_shapes, output_dtypes=output_dtypes)
316- output_dtypes=output_dtypes)
317 case.manual_golden_binaries = value309 case.manual_golden_binaries = value
318 _validate(case)310 _validate(case)
319 assert case.flat_manual_golden_binaries == expected311 assert case.flat_manual_golden_binaries == expected