已合并
fix: pre-commit code #199
RuiWang_创建于 10 天前
fix: pre-commit code #199
已合并
共 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: textarea | 41 | - 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: false | 46 | + 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: false | 27 | + 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: textarea | 10 | - type: textarea |
| 11 | attributes: | 11 | attributes: |
| 12 | - label: Backgroud(背景信息) | 12 | + label: Background(背景信息) |
| 13 | description: 请描述下新需求的功能是什么,解决了什么问题 | 13 | description: 请描述下新需求的功能是什么,解决了什么问题 |
| 14 | validations: | 14 | validations: |
| 15 | required: true | 15 | required: true |
| @@ -30,4 +30,4 @@ body: | |||
| 30 | label: Design(设计方案) | 30 | label: Design(设计方案) |
| 31 | description: 请描述/解释设计的总体思路(可以使用伪代码) | 31 | description: 请描述/解释设计的总体思路(可以使用伪代码) |
| 32 | validations: | 32 | validations: |
| 33 | - required: false | 33 | + required: false |
| @@ -12,4 +12,4 @@ body: | |||
| 12 | label: 问题描述 | 12 | label: 问题描述 |
| 13 | description: 请描述下您的问题 | 13 | description: 请描述下您的问题 |
| 14 | validations: | 14 | validations: |
| 15 | - required: true | 15 | + 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: auto | 101 | - type: auto |
| 102 | fail-fast: false | 102 | fail-fast: false |
| 103 | - | 103 | + |
| 104 | 104 | ||
| 105 | post: | 105 | post: |
| 106 | jobs: | 106 | jobs: |
| @@ -1,4 +1,4 @@ | |||
| 1 | on: | 1 | on: |
| 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: codespell | 36 | - 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: true | 51 | pass_filenames: true |
| 52 | types: [file] | 52 | types: [file] |
| 53 | stages: [commit] | 53 | stages: [commit] |
| 54 | - verbose: true | 54 | + 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 | |||
| 17 | def _make_args(**kwargs): | 17 | def _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 args | 25 | 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 loader | 40 | import ttk.config.loader as loader |
| 41 | + | ||
| 41 | loader._config = None | 42 | loader._config = None |
| 42 | 43 | ||
| 43 | from ttk.config.loader import get_remote_config, load_config | 44 | 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 loader | 65 | import ttk.config.loader as loader |
| 66 | + | ||
| 64 | loader._config = None | 67 | loader._config = None |
| 65 | 68 | ||
| 66 | from ttk.config.loader import get_remote_config, load_config | 69 | 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 None | 73 | 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 loader | 85 | import ttk.config.loader as loader |
| 86 | + | ||
| 82 | loader._config = None | 87 | 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_switches | 91 | 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 | + | ||
| 61 | def test_manual_data_dirs_exposed_on_three_commands(): | 62 | def 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 True | 97 | 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 | + | ||
| 83 | def test_prepare_defaults_to_plugin_manual_data_dir(tmp_path): | 106 | def 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 | + | ||
| 171 | def test_replay_preserves_directory_order(tmp_path): | 195 | def 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 | ||
| 184 | def test_e2e_replay_rejects_cpu_backend(tmp_path): | 206 | def 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 | + | ||
| 195 | def test_kernel_no_prof_without_dump_pair_keeps_legacy(): | 218 | def 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 | + | ||
| 242 | def test_manual_data_fields_survive_worker_pickle(tmp_path): | 266 | def 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 | 281 | ||
| 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 | + | ||
| 54 | def test_xpu_inputs_prefers_original_shape_arrays(): | 55 | def 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_inputs | 57 | 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 | + | ||
| 72 | def test_xpu_mode_bitwise_or(): | 74 | def 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 prof | 76 | 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 | + | ||
| 90 | def test_validate_xpu_perf_precondition_three_branches(monkeypatch): | 93 | def test_validate_xpu_perf_precondition_three_branches(monkeypatch): |
| 91 | """前置校验三分支:无远端+开启→抛错;有远端+开启→不抛;未开启→跳过。""" | 94 | """前置校验三分支:无远端+开启→抛错;有远端+开启→不抛;未开启→跳过。""" |
| 92 | from ttk.cli import common as common_mod | 95 | 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 | + | ||
| 114 | def test_extract_third_party_fail_closed(): | 118 | def 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 prof | 120 | 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 已开。 |
| 133 | class _XpuCalled(Exception): | 138 | class _XpuCalled(Exception): |
| 134 | pass | 139 | pass |
| @@ -167,9 +172,14 @@ def test_profile_process_gate_open_and_shut(monkeypatch): | |||
| 167 | 172 | ||
| 168 | # 隔离 gate 上游:所有 parse/gen 步骤为 no-op | 173 | # 隔离 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=False | 188 | # 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_mod | 190 | 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 import | 193 | # clear_error_manager 在 profile_process 内 lazy import |
| 183 | import ttk.core_modules.npu.error_cleaner as ec | 194 | 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 | + | ||
| 11 | import pytest | 12 | import pytest |
| 12 | 13 | ||
| 13 | 14 | ||
| 14 | def test_get_config_raises_before_load(): | 15 | def 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 loader | 17 | import ttk.config.loader as loader |
| 18 | + | ||
| 17 | saved = loader._config | 19 | saved = loader._config |
| 18 | loader._config = None | 20 | 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 | +) | ||
| 35 | def test_remote_config_field_groups(fields, expected): | 45 | def 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 | ||
| 56 | def test_remote_config_all_fields_together(): | 66 | def 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) == 2 | 85 | 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.05 | 86 | 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 == 900 | 87 | 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 | + | ||
| 11 | from pathlib import Path | 12 | from pathlib import Path |
| 12 | 13 | ||
| 13 | import pytest | 14 | import 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 _loader | 27 | 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): | |||
| 31 | def make_testcase(): | 33 | def make_testcase(): |
| 32 | """Factory: TestcaseAclnn instances (shared global; test_testcase_e2e.py | 34 | """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 TestcaseAclnn | 38 | from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn |
| 39 | + | ||
| 36 | case = TestcaseAclnn() | 40 | case = TestcaseAclnn() |
| 37 | case.api_name = api_name | 41 | case.api_name = api_name |
| 38 | case.is_valid = True | 42 | 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 case | 47 | return case |
| 48 | + | ||
| 44 | return _make | 49 | 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_config | 61 | from ttk.config.loader import load_config |
| 62 | + | ||
| 57 | load_config() | 63 | load_config() |
| 58 | yield | 64 | 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 = False | 90 | device._acl_inited = False |
| 91 | device._owns_acl_runtime = False | 91 | device._owns_acl_runtime = False |
| 92 | + | ||
| 92 | def _noop(): | 93 | def _noop(): |
| 93 | pass | 94 | 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_INITIALIZE | 109 | 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 | 126 | ||
| 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_runtime | 129 | device._owns_acl_runtime = owns_runtime |
| 128 | device._device_id = 0 | 130 | device._device_id = 0 |
| @@ -145,8 +147,7 @@ def test_reset_only_resets_owned_runtime_device( | |||
| 145 | 147 | ||
| 146 | 148 | ||
| 147 | 149 | ||
| 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 = True | 152 | device._acl_inited = True |
| 152 | device._owns_acl_runtime = owns_runtime | 153 | device._owns_acl_runtime = owns_runtime |
| @@ -168,9 +169,7 @@ def test_finalize_only_finalizes_owned_runtime( | |||
| 168 | 169 | ||
| 169 | def test_create_acl_tensor_from_numpy_copies_complete_storage_with_offset(): | 170 | def 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 | + | ||
| 11 | import numpy as np | 12 | import numpy as np |
| 12 | import pytest | 13 | import 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 | +) | ||
| 49 | def test_cross_dtype_pass(actual_vals, actual_dtype, golden_vals, golden_dtype, check_metrics): | 53 | def 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 True | 62 | 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 | +) | ||
| 68 | def test_cross_dtype_fail(actual_vals, actual_dtype, golden_vals, golden_dtype): | 75 | def 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 False | 81 | 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 | +) | ||
| 81 | def test_empty_cases(actual, golden, dtype, expected_pass): | 91 | def 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 | ||
| 88 | def test_isclose_populates_metrics(): | 98 | def 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 True | 105 | 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 | + | ||
| 11 | from unittest.mock import MagicMock | 12 | from unittest.mock import MagicMock |
| 12 | 13 | ||
| 13 | import numpy as np | 14 | import numpy as np |
| @@ -21,10 +22,13 @@ def _stds(n): | |||
| 21 | 22 | ||
| 22 | 23 | ||
| 23 | class TestOutputSentinels: | 24 | class 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: | |||
| 47 | class TestMultiOutput: | 54 | class 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 False | 58 | assert r[2] is False |
| 53 | 59 | ||
| 54 | 60 | ||
| 55 | class TestThirdPartyCount: | 61 | class 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 True | 65 | assert r[2] is True |
| 61 | 66 | ||
| 62 | 67 | ||
| 63 | class TestFilterFakeFail: | 68 | class 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 expected | 78 | 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_err | 22 | 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 True | 24 | assert is_pass is True |
| 26 | assert 0 in metrics | 25 | 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 | + | ||
| 32 | def test_threshold_override_flows_to_metrics(): | 32 | def 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(): | |||
| 79 | def test_output_none_fails(): | 79 | def 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 False | 85 | 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 | + | ||
| 11 | import numpy as np | 12 | import numpy as np |
| 12 | 13 | ||
| 13 | from ttk.core_modules.comparison.cross_check import CrossCheckComparison, safe_div | 14 | from ttk.core_modules.comparison.cross_check import CrossCheckComparison, safe_div |
| @@ -19,22 +20,34 @@ def _make(output, golden, third_party, params): | |||
| 19 | 20 | ||
| 20 | def test_pass_when_all_good(): | 21 | def 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 True | 35 | 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 | ||
| 33 | def test_ratio_exceeded(): | 40 | def 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 | ||
| 47 | def test_safe_div_branches(): | 60 | def 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 的 err | 62 | + 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 分母 -> inf | 66 | 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 | ||
| 57 | def test_small_value_partition_pass(): | 70 | def 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=0 | 76 | + "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=100 | 83 | 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_pass | 86 | assert is_pass |
| 68 | assert metrics["result"]["small_err_cnt_target"] == 0 | 87 | assert metrics["result"]["small_err_cnt_target"] == 0 |
| 69 | assert metrics["result"]["small_err_cnt_third"] > 0 | 88 | 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 | ||
| 73 | def test_nan_inf_mismatch(): | 92 | def 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 位 mismatch | 104 | + 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.py | 10 | # tests/test_cross_check_integration.py |
| 11 | """端到端:spec.tolerance → resolve → compare(third_parties=) → metrics。""" | 11 | """端到端:spec.tolerance → resolve → compare(third_parties=) → metrics。""" |
| 12 | + | ||
| 12 | import numpy as np | 13 | import numpy as np |
| 13 | 14 | ||
| 14 | from ttk.core_modules.comparison import compare | 15 | from 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_pass | 29 | 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 < 2 | 45 | + third_parties=[np.array([1.0])], |
| 46 | + ) # 1 < 2 | ||
| 43 | assert precision == "COMPARE_FAILURE" | 47 | assert precision == "COMPARE_FAILURE" |
| 44 | assert not is_pass | 48 | 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_pass | 58 | 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 | + | ||
| 11 | import numpy as np | 12 | import numpy as np |
| 12 | 13 | ||
| 13 | # 触发各比对类的注册(装饰器在 import 时执行) | 14 | # 触发各比对类的注册(装饰器在 import 时执行) |
| @@ -22,11 +23,17 @@ from ttk.core_modules.comparison.registry import FAIL_REASONS | |||
| 22 | def test_fail_reasons_keys_exist(): | 23 | def 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 ComparisonRegister | 43 | from ttk.core_modules.comparison.registry import ComparisonRegister |
| 37 | from ttk.core_modules.comparison.resolve import resolve_tolerance | 44 | 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 转换、 |
| 11 | ComparisonBase.compare() 4-tuple + _check_empty。""" | 11 | ComparisonBase.compare() 4-tuple + _check_empty。""" |
| 12 | + | ||
| 12 | import numpy as np | 13 | import numpy as np |
| 13 | import pytest | 14 | import 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 | +) | ||
| 61 | def test_check_empty(actual, golden, expected): | 64 | def 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 | + | ||
| 11 | import numpy as np | 12 | import numpy as np |
| 12 | import pytest | 13 | import pytest |
| 13 | 14 | ||
| @@ -22,10 +23,13 @@ def test_legacy_injection(): | |||
| 22 | assert standards[0].params["legacy"]["atol"] == 1e-8 | 23 | 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 | +) | ||
| 29 | def test_cross_check_level_preset(tol_spec, expected_level, expected_mare, expected_mere, expected_rmse, check_extras): | 33 | def 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_comparison | 50 | import ttk.core_modules.comparison.is_close # noqa: F401 — 触发 @register_comparison |
| 47 | from ttk.core_modules.comparison.registry import ComparisonRegister | 51 | from ttk.core_modules.comparison.registry import ComparisonRegister |
| 48 | from ttk.core_modules.comparison.resolve import resolve_tolerance | 52 | 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_comparison | 66 | import ttk.core_modules.comparison.cosine_similarity # noqa: F401 — 触发 @register_comparison |
| 62 | from ttk.core_modules.comparison.registry import ComparisonRegister | 67 | from ttk.core_modules.comparison.registry import ComparisonRegister |
| 63 | from ttk.core_modules.comparison.resolve import resolve_tolerance | 68 | 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 | + | ||
| 11 | from ttk.core_modules.comparison.resolve import resolve_tolerance | 12 | from 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 | ||
| 40 | def test_normal_float_cli_wins(): | 41 | def 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 | ||
| 46 | def test_normal_float_spec_binary_equal(): | 48 | def 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 | ||
| 52 | def test_multi_output_mixed(): | 55 | def 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 | + | ||
| 11 | import numpy as np | 12 | import numpy as np |
| 12 | import pytest | 13 | import 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=None | 32 | + "a,g,expect_pass,mere_none", |
| 32 | - (np.nan, np.nan, True, True), | 33 | + [ |
| 33 | - # finite/finite → mere 路径(mere 算出来,非 None) | 34 | + # match:全非有限且一致 → PASS,mere=None |
| 34 | - (1.0, 2.0, False, False), # mere≈0.5 >> th → FAIL | 35 | + (np.nan, np.nan, True, True), |
| 35 | - # mismatch → FAIL,mere=None | 36 | + # 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 | +) | ||
| 38 | def test_mismatch_truth_table(a, g, expect_pass, mere_none): | 42 | def 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 | +) | ||
| 86 | def test_pass_cases(actual, golden, check): | 93 | def 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 @@ | |||
| 12 | Task 7: device_name() 返回硬件 MODEL(torch.<lib>.get_device_name); | 12 | Task 7: device_name() 返回硬件 MODEL(torch.<lib>.get_device_name); |
| 13 | alias() 携带段名;soc_version 已移除(合并入 device_name)。 | 13 | alias() 携带段名;soc_version 已移除(合并入 device_name)。 |
| 14 | """ | 14 | """ |
| 15 | + | ||
| 15 | from __future__ import annotations | 16 | from __future__ import annotations |
| 16 | 17 | ||
| 17 | import pytest | 18 | import 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 of | 19 | - get_profiler uses is_npu() + profile.get('profiler') instead of |
| 20 | device_name() string compares. | 20 | device_name() string compares. |
| 21 | """ | 21 | """ |
| 22 | + | ||
| 22 | from __future__ import annotations | 23 | from __future__ import annotations |
| 23 | 24 | ||
| 24 | import subprocess | 25 | import 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 | ||
| 24 | class TestFrameworkApiInfoKeeperCache: | 24 | class 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 TestcaseE2e | 40 | 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_name | 44 | 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 = raw | 86 | 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_func | 90 | mock_get.return_value = fake_func |
| 87 | result = generate_golden(case, raw) | 91 | result = generate_golden(case, raw) |
| 88 | assert len(result) == 1 | 92 | 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 = None | 107 | mock_get.return_value = None |
| 101 | result = generate_golden(case, raw) | 108 | result = generate_golden(case, raw) |
| 102 | assert len(result) == 1 | 109 | 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 = None | 126 | 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 before | 15 | - _build(fw, name, profile) fail-fast validates torch_lib + profiler before |
| 16 | instantiating/injecting the backend. | 16 | instantiating/injecting the backend. |
| 17 | """ | 17 | """ |
| 18 | + | ||
| 18 | from __future__ import annotations | 19 | from __future__ import annotations |
| 19 | 20 | ||
| 20 | from ttk.config.loader import get_hardware_config, load_config | 21 | from ttk.config.loader import get_hardware_config, load_config |
| @@ -15,6 +15,7 @@ ImportError / RuntimeError / AttributeError) returning False + warning. | |||
| 15 | get_backend(force_cpu=True) -> CpuTorchBackend; else iterate _hw_profiles | 15 | get_backend(force_cpu=True) -> CpuTorchBackend; else iterate _hw_profiles |
| 16 | in order, _probe each non-cpu profile, build first hit; cpu fallback. | 16 | in order, _probe each non-cpu profile, build first hit; cpu fallback. |
| 17 | """ | 17 | """ |
| 18 | + | ||
| 18 | from __future__ import annotations | 19 | from __future__ import annotations |
| 19 | 20 | ||
| 20 | from ttk.core_modules.framework_api.backends import _probe, get_backend | 21 | from ttk.core_modules.framework_api.backends import _probe, get_backend |
| @@ -41,8 +41,7 @@ def test_parse_stdout_complex32(): | |||
| 41 | 41 | ||
| 42 | 42 | ||
| 43 | def test_parse_stdout_complex64(): | 43 | def 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) == 1 | 47 | assert len(result) == 1 |
| @@ -52,8 +51,7 @@ def test_parse_stdout_complex64(): | |||
| 52 | 51 | ||
| 53 | 52 | ||
| 54 | def test_parse_stdout_complex128(): | 53 | def 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) == 1 | 57 | 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 | +) | ||
| 35 | def test_align_factor(dtype, expected): | 41 | def test_align_factor(dtype, expected): |
| 36 | assert align_factor(dtype) == expected | 42 | 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 | +) | ||
| 43 | def test_determine_c0(dtype, target, expected): | 53 | def test_determine_c0(dtype, target, expected): |
| 44 | assert determine_c0(dtype, target) == expected | 54 | 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")) == 16 | 58 | 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 | +) | ||
| 55 | def test_is_nchw_like(shape, fmt, expected): | 69 | def test_is_nchw_like(shape, fmt, expected): |
| 56 | assert is_nchw_like(shape, fmt) is expected | 70 | 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 | +) | ||
| 63 | def test_is_ndchw_like(shape, fmt, expected): | 81 | def test_is_ndchw_like(shape, fmt, expected): |
| 64 | assert is_ndchw_like(shape, fmt) is expected | 82 | 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 | +) | ||
| 71 | def test_nd_shape2fhd_shape(shape, fmt, expected): | 92 | def test_nd_shape2fhd_shape(shape, fmt, expected): |
| 72 | assert nd_shape2fhd_shape(shape, fmt) == expected | 93 | 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 | +) | ||
| 110 | def test_is_transformable(ori, target, expected): | 134 | def test_is_transformable(ori, target, expected): |
| 111 | assert is_transformable(ori, target) is expected | 135 | 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 | +) | ||
| 63 | def test_reduce(shape, axes, expected): | 66 | def test_reduce(shape, axes, expected): |
| 64 | assert reduce(shape, axes) == expected | 67 | 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 | +) | ||
| 72 | def test_range_inference(shape, expected): | 78 | def test_range_inference(shape, expected): |
| 73 | assert range_inference(shape) == expected | 79 | 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 | +) | ||
| 81 | def test_shape_inference_dispatch(mode, shapes, args, expected): | 90 | def test_shape_inference_dispatch(mode, shapes, args, expected): |
| 82 | assert shape_inference(shapes, args, mode) == expected | 91 | 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 | +) | ||
| 98 | def test_transform_formats(shape, cur, target, expected): | 110 | def test_transform_formats(shape, cur, target, expected): |
| 99 | assert transform(shape, cur, target) == expected | 111 | 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 | + | ||
| 11 | from types import SimpleNamespace | 12 | from types import SimpleNamespace |
| 12 | from unittest.mock import MagicMock | 13 | from 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 prepared | 135 | assert result is prepared |
| 135 | assert not device_execution.called | 136 | 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 torch | 20 | 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 tf | 20 | 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 | # --------------------------------------------------------------------------- # |
| 46 | class _AclnnNegScale: | 46 | class _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 = scale | 50 | 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 | ||
| 55 | class _AclnnDouble: | 56 | class _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: N803 | 58 | + |
| 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-op | 70 | + sw.golden_mode = "Enable" # not Promote → _golden_mode is a no-op |
| 69 | sw.plugin_path = None | 71 | sw.plugin_path = None |
| 70 | sw.overflow_mode = 0 | 72 | sw.overflow_mode = 0 |
| 71 | return sw | 73 | 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") |
| 114 | class TestAclnnClassGoldenDispatch: | 116 | class 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 the | 122 | ``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) branch | 125 | + 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) >= 1 | 135 | 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-callable | 141 | """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 a | 142 | 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 in | 143 | 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 = _AclnnNegScale | 145 | 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 the | 154 | # 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, fallback | 54 | 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 | + | ||
| 11 | import torch | 12 | import torch |
| 12 | 13 | ||
| 13 | from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn | 14 | from 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) + 单 t3 | 21 | + case.tensors = ((t1, t2), t3) # 嵌套:TensorList (t1,t2) + 单 t3 |
| 21 | flat = case.flatten_tensors | 22 | 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 flat | 25 | 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 | + | ||
| 11 | from collections import OrderedDict | 12 | from collections import OrderedDict |
| 12 | from unittest.mock import MagicMock, patch | 13 | from unittest.mock import MagicMock, patch |
| 13 | 14 | ||
| @@ -54,6 +55,7 @@ def _make_testcase(api_name, tensors, tensor_dtypes): | |||
| 54 | 55 | ||
| 55 | class _RecordDtype: | 56 | class _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") |
| 73 | class TestAclnnPromote: | 75 | class 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 | 22 | ||
| 23 | def _mock_outputs_to_numpy(): | 23 | def _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 | yield | 25 | yield |
| 27 | 26 | ||
| 28 | 27 | ||
| 29 | def _call_comparing(mock_compare_side_effect): | 28 | def _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_effect | 30 | 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 | ||
| 44 | class TestComparingPass: | 45 | class 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 == 3 | 78 | 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 | |||
| 32 | from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp | 32 | from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp |
| 33 | from ttk.utilities.classes import SWITCHES | 33 | from 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 | ||
| 38 | def _kernel_case(name="custom_input_sync"): | 38 | def _kernel_case(name="custom_input_sync"): |
| @@ -29,11 +29,12 @@ import pytest | |||
| 29 | from ttk.core_modules.npu.op import output_generation as _mod | 29 | from ttk.core_modules.npu.op import output_generation as _mod |
| 30 | from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp | 30 | from 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 | ||
| 35 | def _make_custom_golden(seen_dtypes): | 35 | def _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 x | 45 | return x |
| 46 | + | ||
| 45 | return golden | 47 | 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_name | 59 | 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 test | 83 | + sw.golden_mode = "Promote" # <-- the mode under test |
| 79 | sw.plugin_path = None | 84 | sw.plugin_path = None |
| 80 | sw.overflow_mode = 0 | 85 | sw.overflow_mode = 0 |
| 81 | return sw | 86 | 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") |
| 94 | class TestKernelPromoteWrapCoversAllForms: | 99 | class 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 float32 | 116 | # 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 | ||
| 19 | from ttk.core_modules.npu.op import output_generation as _mod | 19 | from 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 | ||
| 24 | class TestGoldenReshapeFlat: | 24 | class 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 | ||
| 35 | class TestGoldenReshapeNested: | 34 | class 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 | ||
| 48 | class TestGoldenReshapeEdgeCases: | 46 | class 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 的张量应返回 True。 | 13 | 覆盖广播场景:expand 产生 stride=0 的张量应返回 True。 |
| 14 | """ | 14 | """ |
| 15 | + | ||
| 15 | import pytest | 16 | import pytest |
| 16 | import torch | 17 | import 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 | + | ||
| 11 | from unittest.mock import MagicMock | 12 | from unittest.mock import MagicMock |
| 12 | 13 | ||
| 13 | 14 | ||
| @@ -31,14 +31,13 @@ from ttk.core_modules.npu.op import output_generation as _mod | |||
| 31 | from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp | 31 | from 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") |
| 35 | KERNEL_GOLDEN = _mod.KERNEL_GOLDEN | 35 | KERNEL_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_name | 43 | 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-op | 67 | + sw.golden_mode = "Enable" # not Promote → __golden_mode is a no-op |
| 69 | sw.plugin_path = None | 68 | sw.plugin_path = None |
| 70 | sw.overflow_mode = 0 | 69 | sw.overflow_mode = 0 |
| 71 | return sw | 70 | 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") |
| 85 | class TestKernelBuiltinGoldenDispatch: | 84 | class 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_GOLDEN | 102 | assert "neg" in KERNEL_GOLDEN |
| 101 | assert KERNEL_GOLDEN["neg"] is np.negative | 103 | assert KERNEL_GOLDEN["neg"] is np.negative |
| @@ -108,15 +110,13 @@ class TestKernelBuiltinGoldenDispatch: | |||
| 108 | # The path must succeed (not "GOLDEN_FAILURE" / "UNSUPPORTED") and | 110 | # 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) >= 1 | 112 | 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") |
| 120 | class TestKernelGoldenFallbackBranchCoverage: | 120 | class 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 into | 130 | # 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_of | 132 | 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 | |||
| 24 | from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp | 24 | from 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 | # --------------------------------------------------------------------------- # |
| 33 | class _NegScale: | 33 | class _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 = scale | 37 | 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_name | 49 | 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-op | 73 | + sw.golden_mode = "Enable" # not Promote → __golden_mode is a no-op |
| 74 | sw.plugin_path = None | 74 | sw.plugin_path = None |
| 75 | sw.overflow_mode = 0 | 75 | sw.overflow_mode = 0 |
| 76 | return sw | 76 | 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") |
| 97 | class TestKernelClassGoldenDispatch: | 97 | class 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-callable | 101 | """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)`` check | 102 | 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 called | 103 | 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 the | 122 | # 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 | ||
| 25 | No NPU needed — this tests the XPU dispatch chain, not the kernel compile. | 25 | No NPU needed — this tests the XPU dispatch chain, not the kernel compile. |
| 26 | """ | 26 | """ |
| 27 | + | ||
| 27 | import http.client | 28 | import 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 | |||
| 51 | if has_tf: | 52 | if 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 == 0 | 56 | + subprocess.run([sys.executable, "-c", "import tensorflow"], capture_output=True, timeout=90).returncode == 0 |
| 57 | + ) | ||
| 56 | except Exception: | 58 | except Exception: |
| 57 | has_tf = False | 59 | 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 (merged | 91 | # 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 the | 147 | # 3. Wire env + RemoteConfig + clear Singleton EVERY call so the |
| 146 | # EndpointView constructed inside collect_xpu_results binds to THIS | 148 | # 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 yaml | 152 | # 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_config | 156 | 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 into | 159 | # cli_providers is passed directly to resolve_providers below (and into |
| 158 | # collect_xpu_results); TTK_XPU_PROVIDER env is no longer read by production | 160 | # 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 | 218 | ||
| 213 | def test_no_spec_discovery_both_providers(xpu_server, tmp_path, monkeypatch): | 219 | def 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 | 230 | ||
| 224 | def test_spec_tp_str(xpu_server, tmp_path, monkeypatch): | 231 | def 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 | |||
| 26 | from ttk.core_modules.npu.op import output_generation as _out_gen_mod | 26 | from ttk.core_modules.npu.op import output_generation as _out_gen_mod |
| 27 | from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp | 27 | from 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_name | 43 | case.op_name = op_name |
| @@ -60,7 +63,7 @@ def _make_testcase(op_name="Add", input_shapes=((8,), (8,)), | |||
| 60 | def _validate(case): | 63 | def _validate(case): |
| 61 | n_in = len(case.input_shapes) if case.input_shapes else 0 | 64 | 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 0 | 65 | 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_kwargs | 102 | # 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 | + | ||
| 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_dtypes | 123 | 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_formats | 146 | assert kwargs["input_formats"] == case.input_formats |
| @@ -152,10 +153,10 @@ class TestGoldenKwargsNonTensorList: | |||
| 152 | # Tests for input_generation.__collect_dynamic_kwargs | 153 | # 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 | + | ||
| 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 | + | ||
| 11 | from types import SimpleNamespace | 12 | from types import SimpleNamespace |
| 12 | from unittest.mock import MagicMock | 13 | from unittest.mock import MagicMock |
| 13 | 14 | ||
| @@ -31,9 +32,7 @@ def _instance(): | |||
| 31 | def test_prepare_uses_one_logical_worker_without_querying_devices(monkeypatch): | 32 | def 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 / |
| 13 | GeirReturnStructure)均应含 precision_metrics(或 ComparisonResult 的 metrics)字段。 | 13 | GeirReturnStructure)均应含 precision_metrics(或 ComparisonResult 的 metrics)字段。 |
| 14 | """ | 14 | """ |
| 15 | + | ||
| 15 | import pytest | 16 | import pytest |
| 16 | 17 | ||
| 17 | from ttk.core_modules.geir.geir_struct import GeirReturnStructure | 18 | from ttk.core_modules.geir.geir_struct import GeirReturnStructure |
| 18 | from ttk.core_modules.npu.op.profiling_structure import ComparisonResult, ProfilingReturnStructure | 19 | from 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 | +) | ||
| 29 | def test_precision_metrics_slot_exists(cls, factory, field_name, is_geir): | 38 | def 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_providers | 10 | """_extract_spec_providers (dict/str/None) + EndpointView.resolve_providers |
| 11 | + _do_xpu_profiling fail-loud (Task 9). No server fixture; EV constructed | 11 | + _do_xpu_profiling fail-loud (Task 9). No server fixture; EV constructed |
| 12 | in-process with a health file (ref Task 3 test pattern).""" | 12 | in-process with a health file (ref Task 3 test pattern).""" |
| 13 | + | ||
| 13 | import json | 14 | import json |
| 14 | import logging | 15 | import logging |
| 15 | import os | 16 | import 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 endpoints | 31 | """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_config | 33 | from ttk.config.loader import load_config |
| 34 | from ttk.remote.endpoint_view import EndpointView | 34 | from ttk.remote.endpoint_view import EndpointView |
| 35 | from ttk.utilities.singleton import Singleton | 35 | 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 | + | ||
| 56 | def test_extract_spec_providers_dict_returns_keys(): | 57 | def 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 | + | ||
| 63 | def test_resolve_spec_intersects_effective(monkeypatch, tmp_path): | 65 | def 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 | + | ||
| 72 | def test_do_xpu_profiling_resolve_failure_sets_empty_and_survives(monkeypatch, tmp_path, caplog): | 78 | def 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 Singleton | 81 | 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 driven | 86 | # 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_config | 90 | 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 None | 102 | 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 raise | 113 | priority = prof._do_xpu_profiling(context, xpu_mode=0b11) # must NOT raise |
| 110 | 114 | ||
| 111 | - assert context.xpu_results == {} # empty: no provider dispatched | 115 | + assert context.xpu_results == {} # empty: no provider dispatched |
| 112 | - assert priority is None # resolve failed -> None priority | 116 | + 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 | ||
| 22 | class TestComparisonResult: | 22 | class 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 | ||
| 34 | class TestRTSProfilingResult: | 33 | class 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 @@ | |||
| 12 | Covers _build_spec (Task 7). The _extract_spec_providers test is covered by | 12 | Covers _build_spec (Task 7). The _extract_spec_providers test is covered by |
| 13 | test_profiling_config.py. No server fixture. | 13 | test_profiling_config.py. No server fixture. |
| 14 | """ | 14 | """ |
| 15 | + | ||
| 15 | from ttk.core_modules.npu.op import profiling as prof | 16 | from 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 | ||
| 20 | def test_build_spec_dict_api_string(): | 21 | def 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 | pass | 38 | 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 = MagicMock | 20 | _tbe_mod.Opc = MagicMock |
| 21 | 21 | ||
| 22 | from ttk.core_modules.operator.op_interface import ( # noqa: E402, I001 | 22 | from 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 | 85 | ||
| 82 | def _mock_singleton(): | 86 | def _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 = None | 90 | 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 oi | 94 | 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 None | 169 | 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 = None | 177 | 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_info | 195 | 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) == 5 | 198 | assert len(result) == 5 |
| 194 | assert result["y"].default is None | 199 | 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_singleton | 234 | 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_singleton | 241 | 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 = None | 245 | 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_singleton | 250 | oi = _mock_singleton |
| 246 | mock_oi = MagicMock() | 251 | mock_oi = MagicMock() |
| 247 | oi._opc.op_info.OpInfo.return_value = mock_oi | 252 | 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_singleton | 271 | 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 = 42 | 290 | 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,)},) * 3 | 307 | 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 = None | 311 | 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 None | 313 | 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_singleton | 320 | 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 = None | 322 | m.return_value.get_operator_function.return_value = None |
| 317 | assert oi.get_dyn_operator(_make_testcase()) is None | 323 | 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_singleton | 330 | 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 None | 332 | 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_singleton | 335 | 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 = None | 352 | 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 None | 354 | assert result is not None |
| @@ -347,19 +357,18 @@ class TestCompileDynamicShape: | |||
| 347 | class TestPrepareOperatorParametersConst: | 357 | class 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_defined | 373 | m_oi.return_value.op_output_defined.return_value = op_output_defined |
| 365 | m_oi2.return_value.info_of.return_value = op_info | 374 | 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_singleton | 381 | 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]) == 2 | 439 | 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]) == 2 | 456 | 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_singleton | 461 | 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: const | 480 | # 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]) == 2 | 501 | 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_singleton | 512 | 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) == 0 | 520 | 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_effect | 540 | 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 result | 549 | 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_tiling | 567 | 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 | + | ||
| 8 | import json | 9 | import json |
| 9 | import os | 10 | import os |
| 10 | import pickle | 11 | import pickle |
| @@ -20,6 +21,7 @@ from ttk.utilities.classes import MODE, SWITCHES | |||
| 20 | 21 | ||
| 21 | class _DummyRTSProfilingParam: | 22 | class _DummyRTSProfilingParam: |
| 22 | """Minimal stand-in for RTSProfilingParam (module-level: picklable).""" | 23 | """Minimal stand-in for RTSProfilingParam (module-level: picklable).""" |
| 24 | + | ||
| 23 | switch = True | 25 | switch = True |
| 24 | compile_result = "SUCC" | 26 | compile_result = "SUCC" |
| 25 | is_valid = True | 27 | 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.cycle | 204 | assert "RuntimeError: boom" in res.cycle |
| @@ -214,9 +213,17 @@ class TestResultLoading: | |||
| 214 | from ttk.core_modules.simulator.sim_profiling import _load_aclnn_result | 213 | 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_runner | 450 | 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: | |||
| 22 | 7. No device: use_device=False, no lock acquired, all run concurrently | 22 | 7. No device: use_device=False, no lock acquired, all run concurrently |
| 23 | 8. No device + kill: use_device=False with child kill | 23 | 8. No device + kill: use_device=False with child kill |
| 24 | """ | 24 | """ |
| 25 | + | ||
| 25 | import multiprocessing as mp | 26 | import multiprocessing as mp |
| 26 | import time | 27 | import 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 | continue | 120 | 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 = msg | 128 | _, lock_id, dev_id = msg |
| 135 | procs[i]["lock_id"] = lock_id | 129 | 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 | break | 147 | 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 | break | 149 | break |
| 159 | 150 | ||
| 160 | # Collect results | 151 | # 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 success | 176 | 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 pool | 20 | import ttk.core_modules.tbe_multiprocessing.pool as pool |
| 21 | from ttk.utilities.classes import SWITCHES | 21 | 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._config | 27 | saved = loader._config |
| 33 | loader._config = None # 模拟 forkserver worker:干净的 _config | 28 | 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 = False | 32 | sw.logging_to_file = False |
| 38 | 33 | ||
| 39 | from ttk.core_modules.tbe_multiprocessing.pool import worker_bootstrap | 34 | 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 loader | 49 | import ttk.config.loader as loader |
| 54 | import ttk.core_modules.tbe_multiprocessing.pool as pool | 50 | import ttk.core_modules.tbe_multiprocessing.pool as pool |
| 55 | from ttk.utilities.classes import SWITCHES | 51 | 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._config | 54 | saved = loader._config |
| 58 | loader._config = None | 55 | loader._config = None |
| @@ -61,6 +58,7 @@ def test_worker_bootstrap_without_config_path_loads_defaults(monkeypatch): | |||
| 61 | sw.config_path = None | 58 | sw.config_path = None |
| 62 | sw.logging_to_file = False | 59 | sw.logging_to_file = False |
| 63 | from ttk.core_modules.tbe_multiprocessing.pool import worker_bootstrap | 60 | 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 | |||
| 9 | DT_DOUBLE for a float32 testcase and the operator rejected it, masking the real | 9 | DT_DOUBLE for a float32 testcase and the operator rejected it, masking the real |
| 10 | error (an out-of-range `dim`) behind "data type DT_DOUBLE is not supported". | 10 | error (an out-of-range `dim`) behind "data type DT_DOUBLE is not supported". |
| 11 | """ | 11 | """ |
| 12 | + | ||
| 12 | import numpy | 13 | import numpy |
| 13 | import pytest | 14 | import pytest |
| 14 | 15 | ||
| @@ -38,8 +39,7 @@ class _Ctx: | |||
| 38 | def test_promote_restores_dtypes(monkeypatch, raises): | 39 | def test_promote_restores_dtypes(monkeypatch, raises): |
| 39 | ctx = _Ctx() | 40 | ctx = _Ctx() |
| 40 | before_in, before_out = ctx.input_dtypes, ctx.output_dtypes | 41 | 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 @@ | |||
| 13 | canndev op_proto/inc 中确有这样写的算子(FfnWorkerBatching)。解析器不认这种写法时, | 13 | canndev op_proto/inc 中确有这样写的算子(FfnWorkerBatching)。解析器不认这种写法时, |
| 14 | 表现为 "GEIR op source generation failed",会被误判成算子缺陷。 | 14 | 表现为 "GEIR op source generation failed",会被误判成算子缺陷。 |
| 15 | """ | 15 | """ |
| 16 | + | ||
| 16 | import pytest | 17 | import pytest |
| 17 | 18 | ||
| 18 | from ttk.core_modules.geir.proto_loader import ProtoLoader | 19 | from ttk.core_modules.geir.proto_loader import ProtoLoader |
| @@ -45,10 +46,10 @@ def _parse(tmp_path, reg_sp="", end_sp=""): | |||
| 45 | 46 | ||
| 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 | ) |
| 54 | def test_reg_op_accepts_whitespace_before_paren(tmp_path, reg_sp, end_sp): | 55 | def 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 == 2 | 34 | assert plan.tensor_count == 2 |
| 33 | assert plan.scalar_count == 0 | 35 | assert plan.scalar_count == 0 |
| 34 | assert len(plan.param_layout) == 2 | 36 | 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 == 2 | 50 | assert plan.tensor_count == 2 |
| 47 | assert plan.scalar_count == 1 | 51 | 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 == 1 | 66 | assert plan.tensor_count == 1 |
| 61 | assert plan.scalar_count == 0 | 67 | assert plan.scalar_count == 0 |
| 62 | assert len(plan.param_layout) == 3 | 68 | 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 == 1 | 83 | 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 == 1 | 95 | assert plan.tensor_count == 1 |
| 86 | assert plan.scalar_count == 1 | 96 | 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 == 3 | 112 | assert plan.tensor_count == 3 |
| 101 | assert plan.scalar_count == 1 | 113 | 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 TestcaseAclnn | 234 | 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 TestcaseAclnn | 245 | from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn |
| 246 | + | ||
| 217 | case = TestcaseAclnn() | 247 | case = TestcaseAclnn() |
| 218 | case.api_name = None | 248 | case.api_name = None |
| 219 | assert case.get_param_plan() is None | 249 | 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 TestcaseAclnn | 259 | 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 | |||
| 21 | from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp | 21 | from 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_name | 34 | case.op_name = op_name |
| @@ -68,8 +71,9 @@ def _validate(case, op_info=None): | |||
| 68 | mock_gs.op_impl_mode = None | 71 | 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_info | 77 | 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 | ||
| 105 | class TestClearAtomicProperties: | 109 | class 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;否则仅断言非 None | 136 | _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 None | 153 | assert result is not None |
| 150 | else: | 154 | else: |
| 151 | assert result == expected | 155 | 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 | ||
| 164 | class TestExpandIndices: | 171 | class 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) == expected | 182 | assert TestcaseOp._expand_indices(count, distribution, indices) == expected |
| 174 | 183 | ||
| @@ -179,7 +188,6 @@ class TestExpandIndices: | |||
| 179 | 188 | ||
| 180 | 189 | ||
| 181 | class TestAutoSetInplaceIndexes: | 190 | class 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 | ||
| 216 | class TestConstInputIndexesOpInfo: | 222 | class 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_info | 232 | 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_info | 244 | 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 | ||
| 245 | class TestSupportedRerunTitle: | 248 | class 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 titles | 251 | assert "dyn_perf_us" in titles |
| @@ -251,7 +253,6 @@ class TestSupportedRerunTitle: | |||
| 251 | 253 | ||
| 252 | 254 | ||
| 253 | class TestSetCaseCoreType: | 255 | class 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 | ||
| 276 | class TestStcShapeSizeCheck: | 277 | class 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 = None | 283 | 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 | ||
| 298 | class TestGetDynFuncParamName: | 299 | class 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 | ||
| 325 | class TestPrepareOperatorParameters: | 325 | class 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 = True | 330 | 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) == 2 | 332 | 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 = True | 341 | mock_oi.return_value.op_output_defined.return_value = True |
| 342 | mock_oi2.return_value.info_of.return_value = None | 342 | 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 = False | 355 | 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 | ||
| 360 | class TestConstructOptilingAttrs: | 360 | class 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 = None | 363 | 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) == 2 | 365 | 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_info | 377 | 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) == 2 | 379 | 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_info | 389 | 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 = None | 395 | 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 = None | 401 | 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) == 1 | 403 | assert len(result) == 1 |
| @@ -406,10 +405,10 @@ class TestConstructOptilingAttrs: | |||
| 406 | 405 | ||
| 407 | 406 | ||
| 408 | class TestGetOpFuncParams: | 407 | class 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 | pass | 410 | 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_info | 422 | 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 | ||
| 432 | class TestOpTypeFromSourceCode: | 431 | class 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 None | 433 | assert OperatorInterface.get_op_type_from_source_code("not_a_func") is None |
| 436 | 434 | ||
| @@ -438,18 +436,21 @@ class TestOpTypeFromSourceCode: | |||
| 438 | class TestEnableShapeInt64: | 436 | class 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 False | 450 | assert OperatorInterface._enable_shape_int64(tensors) is False |
| 449 | 451 | ||
| 450 | 452 | ||
| 451 | class TestPrintFuncParams: | 453 | class 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) > 2 | 456 | assert len(result) > 2 |
| @@ -457,7 +458,6 @@ class TestPrintFuncParams: | |||
| 457 | 458 | ||
| 458 | 459 | ||
| 459 | class TestAddCompileInfoToOpContext: | 460 | class 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 | + | ||
| 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_compilation | 495 | 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_compilation | 512 | 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 = False | 515 | case.is_valid = False |
| 514 | case.fail_reason = "DYN_INPUT_MISSING" | 516 | case.fail_reason = "DYN_INPUT_MISSING" |
| @@ -519,17 +521,23 @@ class TestStaticCompilation: | |||
| 519 | class TestNormalizeMode: | 521 | class 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_mode | 534 | from ttk.core_modules.npu.op.compilation.common import normalize_mode |
| 535 | + | ||
| 529 | assert normalize_mode(mode) == expected | 536 | 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_mode | 540 | 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 | |||
| 20 | from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp | 20 | from 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_name | 34 | case.op_name = op_name |
| @@ -58,7 +61,7 @@ def _mock_op_info(monkeypatch): | |||
| 58 | 61 | ||
| 59 | 62 | ||
| 60 | def _validate(case): | 63 | def _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 = None | 65 | 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 | |||
| 45 | def _build_positional_args(api_name, nested_tensors, attributes, output_tensor_indexes, tensor_distribution=None): | 45 | def _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 | ||
| 52 | class TestMatchOverload: | 52 | class 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 None | 62 | assert params is not None |
| 61 | assert oidx == 0 | 63 | 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 == 1 | 74 | assert oidx1 == 1 |
| 70 | params2, oidx2 = _match_overload("torch.t2", 2) | 75 | params2, oidx2 = _match_overload("torch.t2", 2) |
| 71 | assert oidx2 == 0 | 76 | 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 == 1 | 87 | 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 None | 98 | assert params is None |
| 88 | assert oidx == -1 | 99 | 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 == 0 | 110 | 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 | ||
| 105 | class TestScoreAttrTypeCompatibility: | 119 | class 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_oidx | 212 | 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 == 0 | 227 | 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: F401 | 231 | 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 == 1 | 235 | 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: 未知目标类型 → ValueError | 248 | - 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) == expected | 291 | assert coerce_value(value, target_type) == expected |
| 231 | 292 | ||
| @@ -235,16 +296,16 @@ class TestCoerceValue: | |||
| 235 | 296 | ||
| 236 | 297 | ||
| 237 | class TestBuildPositionalArgs: | 298 | class 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 == 0 | 313 | 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 == 1 | 377 | 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 == 0 | 406 | 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 None | 416 | assert params1 is not None |
| 343 | assert oidx1 == 0 | 417 | assert oidx1 == 0 |
| @@ -346,18 +420,24 @@ class TestVarPositional: | |||
| 346 | assert oidx2 == 0 | 420 | 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 None | 430 | 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 ParamPlan | 439 | 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 ParamPlan | 459 | 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 ParamPlan | 479 | 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 TestcaseE2e | 494 | from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e |
| 495 | + | ||
| 407 | case = TestcaseE2e() | 496 | case = TestcaseE2e() |
| 408 | case.api_name = api_name | 497 | case.api_name = api_name |
| 409 | case.tensor_view_shapes = shapes | 498 | case.tensor_view_shapes = shapes |
| @@ -414,33 +503,27 @@ class TestGetParamPlan: | |||
| 414 | return case | 503 | 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 plan2 | 509 | 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 None | 514 | 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 np | 517 | 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 None | 528 | 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 | 538 | ||
| 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 TestcaseE2e | 540 | 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 = shapes | 544 | case.tensor_view_shapes = shapes |
| 461 | case.tensor_dtypes = dtypes | 545 | 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 np | 551 | 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_valid | 558 | 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 np | 585 | 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_valid | 592 | 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 np | 618 | 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_valid | 626 | 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 | ||
| 554 | class TestInplaceTensorMethodBuildArgs: | 652 | class TestInplaceTensorMethodBuildArgs: |
| 555 | - | ||
| 556 | def test_inplace_build_args_includes_self(self): | 653 | def test_inplace_build_args_includes_self(self): |
| 557 | import numpy as np | 654 | 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 ParamPlan | 664 | 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 TestcaseE2e | 688 | from ttk.core_modules.testcase_manager.testcase_e2e import TestcaseE2e |
| 689 | + | ||
| 587 | case = TestcaseE2e() | 690 | case = TestcaseE2e() |
| 588 | case.api_name = api_name | 691 | case.api_name = api_name |
| 589 | case.tensor_view_shapes = shapes | 692 | case.tensor_view_shapes = shapes |
| @@ -594,25 +697,27 @@ class TestCheckInputCountExceededVarPos: | |||
| 594 | return case | 697 | 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_valid | 707 | 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_valid | 721 | 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: F401 | 726 | 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_valid | 730 | assert case.is_valid |
| 629 | 731 | ||
| 630 | 732 | ||
| 631 | class TestTensorListOutBuildArgs: | 733 | class TestTensorListOutBuildArgs: |
| 632 | - | ||
| 633 | def test_tensor_list_out_assembles_tuple(self): | 734 | def test_tensor_list_out_assembles_tuple(self): |
| 634 | import numpy as np | 735 | import numpy as np |
| 635 | 736 | ||
| 636 | from ttk.core_modules.testcase_manager.param_plan import ParamPlan | 737 | 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) == 1 | 756 | assert len(args) == 1 |
| 655 | - assert 'out' in kwargs | 757 | + assert "out" in kwargs |
| 656 | - assert len(kwargs['out']) == 4 | 758 | + assert len(kwargs["out"]) == 4 |
| 657 | - assert kwargs['out'][0] is t1 | 759 | + assert kwargs["out"][0] is t1 |
| 658 | - assert kwargs['out'][3] is t4 | 760 | + 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 np | 763 | import numpy as np |
| 662 | 764 | ||
| 663 | from ttk.core_modules.testcase_manager.param_plan import ParamPlan | 765 | 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) == 1 | 781 | assert len(args) == 1 |
| 679 | - assert kwargs['out'] is t1 | 782 | + 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 np | 785 | import numpy as np |
| 683 | 786 | ||
| 684 | from ttk.core_modules.testcase_manager.param_plan import ParamPlan | 787 | 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) == 1 | 803 | assert len(args) == 1 |
| 700 | - assert kwargs['statistic'] is True | 804 | + assert kwargs["statistic"] is True |
| 701 | - assert 'out' in kwargs | 805 | + assert "out" in kwargs |
| 702 | - assert len(kwargs['out']) == 4 | 806 | + assert len(kwargs["out"]) == 4 |
| @@ -13,6 +13,7 @@ | |||
| 13 | """ | 13 | """ |
| 14 | Tests for attributes1~attributes9 extension columns merged into attributes. | 14 | Tests for attributes1~attributes9 extension columns merged into attributes. |
| 15 | """ | 15 | """ |
| 16 | + | ||
| 16 | import pytest | 17 | import pytest |
| 17 | 18 | ||
| 18 | from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn | 19 | from ttk.core_modules.testcase_manager.testcase_aclnn import TestcaseAclnn |
| @@ -72,8 +73,7 @@ def test_override_across_extended_columns(cls): | |||
| 72 | 73 | ||
| 73 | def test_override_chain_last_wins(cls): | 74 | def 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 True | 79 | assert case.is_valid is True |
| @@ -82,8 +82,7 @@ def test_override_chain_last_wins(cls): | |||
| 82 | 82 | ||
| 83 | def test_multi_column_merge(cls): | 83 | def 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): | |||
| 117 | def test_validate_invokes_merge(): | 116 | def test_validate_invokes_merge(): |
| 118 | """validate() 内部应触发 _merge_extended_attributes。""" | 117 | """validate() 内部应触发 _merge_extended_attributes。""" |
| 119 | from ttk.core_modules.testcase_manager.testcase_base import TestcaseBase | 118 | from ttk.core_modules.testcase_manager.testcase_base import TestcaseBase |
| 119 | + | ||
| 120 | case = TestcaseOp() | 120 | case = TestcaseOp() |
| 121 | case.is_valid = True | 121 | case.is_valid = True |
| 122 | case.attributes = {"a": 1} | 122 | case.attributes = {"a": 1} |
| @@ -21,11 +21,14 @@ import pytest | |||
| 21 | from ttk.core_modules.testcase_manager.testcase_op import TestcaseOp | 21 | from 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_name | 34 | case.op_name = op_name |
| @@ -58,7 +61,7 @@ def _mock_env(monkeypatch): | |||
| 58 | def _validate(case): | 61 | def _validate(case): |
| 59 | n_in = len(case.input_shapes) if case.input_shapes else 0 | 62 | 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 0 | 63 | 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 conversions | 74 | # Normalize: basic type conversions |
| 72 | # ===================================================================== | 75 | # ===================================================================== |
| 73 | 76 | ||
| 77 | + | ||
| 74 | class TestNormalizeManualOutputBinaries: | 78 | class 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_INVALID | 84 | - 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 = value | 117 | case.manual_golden_binaries = value |
| 104 | _validate(case) | 118 | _validate(case) |
| 105 | assert case.manual_golden_binaries == expected | 119 | 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 outputs | 138 | # Validation: flat outputs |
| 126 | # ===================================================================== | 139 | # ===================================================================== |
| 127 | 140 | ||
| 141 | + | ||
| 128 | class TestValidateOutputBinariesFlat: | 142 | class 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 = value | 164 | 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 == expected | 169 | 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 = value | 183 | case.manual_golden_binaries = value |
| 178 | _validate(case) | 184 | _validate(case) |
| 179 | assert case.is_valid is False | 185 | assert case.is_valid is False |
| @@ -185,6 +191,7 @@ class TestValidateOutputBinariesFlat: | |||
| 185 | # Validation: nested (TensorList) outputs | 191 | # Validation: nested (TensorList) outputs |
| 186 | # ===================================================================== | 192 | # ===================================================================== |
| 187 | 193 | ||
| 194 | + | ||
| 188 | class TestValidateOutputBinariesNested: | 195 | class 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 为 False | 200 | - 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 = value | 214 | case.manual_golden_binaries = value |
| 212 | _validate(case) | 215 | _validate(case) |
| 213 | assert case.manual_golden_binaries == expected | 216 | 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 = value | 240 | case.manual_golden_binaries = value |
| 244 | _validate(case) | 241 | _validate(case) |
| 245 | assert case.is_valid is False | 242 | assert case.is_valid is False |
| @@ -249,35 +246,31 @@ class TestValidateOutputBinariesNested: | |||
| 249 | # Reshape: flat → nested | 246 | # Reshape: flat → nested |
| 250 | # ===================================================================== | 247 | # ===================================================================== |
| 251 | 248 | ||
| 249 | + | ||
| 252 | class TestReshapeOutputBinaries: | 250 | class 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 = value | 274 | case.manual_golden_binaries = value |
| 282 | _validate(case) | 275 | _validate(case) |
| 283 | assert case.manual_golden_binaries == expected | 276 | assert case.manual_golden_binaries == expected |
| @@ -287,6 +280,7 @@ class TestReshapeOutputBinaries: | |||
| 287 | # Flat properties | 280 | # Flat properties |
| 288 | # ===================================================================== | 281 | # ===================================================================== |
| 289 | 282 | ||
| 283 | + | ||
| 290 | class TestFlatOutputBinariesProperties: | 284 | class 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 属性为 None | 289 | - 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 = value | 309 | case.manual_golden_binaries = value |
| 318 | _validate(case) | 310 | _validate(case) |
| 319 | assert case.flat_manual_golden_binaries == expected | 311 | assert case.flat_manual_golden_binaries == expected |