已合并
adapt oversize dump name #498
adapt oversize dump name #498
已合并
starchen_创建于 27 天前
12 个文件变更+788-33
@@ -155,4 +155,4 @@ repos:
155 # 后续遇新误判词追加即可(codespell 按小写匹配)。155 # 后续遇新误判词追加即可(codespell 按小写匹配)。
156 args:156 args:
157 - --skip=*.bin,*.xlsx,*.png,*.svg,*.lock,*.whl,*.pyc,./build,./bundle,./submodule,./third_party,./node_modules157 - --skip=*.bin,*.xlsx,*.png,*.svg,*.lock,*.whl,*.pyc,./build,./bundle,./submodule,./third_party,./node_modules
158- - --ignore-words-list=cann,oam,msprof,aicerr,hccl,tbe,te,nin,fo,optin,alog,abl,OpEnd,mata158+ - --ignore-words-list=cann,oam,msprof,aicerr,hccl,tbe,te,nin,fo,optin,alog,abl,OpEnd,mata,VALU
@@ -33,6 +33,7 @@ class AicErrorInfo:
33 self.task_id = ""33 self.task_id = ""
34 self.stream_id = ""34 self.stream_id = ""
35 self.node_name = ""35 self.node_name = ""
36+ self.data_name = "" # plog解析出的原始data_name,可能超过NAME_MAX
36 self.kernel_name = ""37 self.kernel_name = ""
37 self.flip_num = ""38 self.flip_num = ""
38 self.instr = ""39 self.instr = ""
@@ -388,6 +388,8 @@ class AicoreErrorParser:
388 info.extra_info = self._get_extra_info(aic_err_ret.pop('extra_info'))388 info.extra_info = self._get_extra_info(aic_err_ret.pop('extra_info'))
389 info.aic_error_info = aic_err_ret389 info.aic_error_info = aic_err_ret
390 390 
391+ info.data_name = data_name
392+ 
391 # 此处附加判断是L0还是L1393 # 此处附加判断是L0还是L1
392 error_info = self._get_node_and_kernel_name(data_name)394 error_info = self._get_node_and_kernel_name(data_name)
393 # 访问具名元组的属性395 # 访问具名元组的属性
@@ -37,6 +37,9 @@ class Collection:
37 self.collect_level = 037 self.collect_level = 0
38 self.ffts_flag = False38 self.ffts_flag = False
39 self.is_sk = False39 self.is_sk = False
40+ # 超长文件名场景:dump文件被框架重命名为随机数字串
zhangjie
zhangjiezhangjie27 天前

级别:提示 问题:self.dump_name_mapping 在 _resolve_dump_file_rename 中被赋值,但在整个生产代码路径中从未被读取(仅在 UT 断言中使用),属于死状态。 影响:增加维护者理解成本,可能误以为该字段在下游逻辑中被消费。 修复建议:若仅用于调试可保留并加注释说明;否则移除该实例变量,_resolve_dump_file_rename 内部用局部变量即可。

likedislike
starchen_
27 天前 评论:
41+ self.dump_file_rename = "" # 映射后的实际文件名
42+ self._mapping_csv_path = "" # mapping.csv路径,首次解析后缓存,避免重复find
40 43 
41 @staticmethod44 @staticmethod
42 def get_sk_kernel_name(plog_dir) -> str:45 def get_sk_kernel_name(plog_dir) -> str:
@@ -156,6 +159,50 @@ class Collection:
156 err_time, device_id, data_name = dump_data_ret[0]159 err_time, device_id, data_name = dump_data_ret[0]
157 return err_time, device_id, data_name160 return err_time, device_id, data_name
158 161 
162+ @staticmethod
163+ def _is_oversize_name(data_name: str) -> bool:
164+ # 超过NAME_MAX的算子名,异常dump框架会将落盘文件重命名为随机数字串
165+ # NAME_MAX是字节上限,多字节文件名下需按编码后的字节数判断
166+ return len(os.fsencode(data_name)) > Constant.MAX_FILE_NAME_LEN
167+ 
168+ def _get_dump_mapping_csv_path(self, device_id) -> str:
169+ find_mapping_cmd = ['find', self.report_path, '-name', Constant.MAPPING_CSV_FILE]
170+ regexp = r"[_\.\-/0-9a-zA-Z.]{1,}"
171+ mapping_csv_list = utils.get_inquire_result(find_mapping_cmd, regexp)
172+ if not mapping_csv_list:
173+ return ""
174+ # mapping.csv与dump文件同级,只取报错device对应的那一份。
175+ # 此处匹配到目录分隔符与文件名,避免device_id为0时误命中data-dump/01/
176+ device_dump_suffix = os.path.join("data-dump", str(device_id), Constant.MAPPING_CSV_FILE)
177+ for mapping_csv in mapping_csv_list:
178+ if mapping_csv.endswith(device_dump_suffix):
179+ return mapping_csv
180+ # 不回退到其它device的映射表:各device的随机名互不相通,用错会静默收集到别的卡的dump
181+ utils.print_warn_log(
182+ f"{Constant.MAPPING_CSV_FILE} of device {device_id} cannot be found in {self.report_path}, "
183+ f"{len(mapping_csv_list)} {Constant.MAPPING_CSV_FILE} of other devices are ignored.")
184+ return ""
185+ 
186+ def _resolve_dump_file_rename(self, device_id, data_name) -> str:
187+ # 长度未超限时不查mapping.csv,避免大目录下不必要的全盘扫描
188+ if not self._is_oversize_name(data_name):
189+ return ""
190+ mapping_csv_path = self._get_dump_mapping_csv_path(device_id)
191+ self._mapping_csv_path = mapping_csv_path
192+ dump_name_mapping = utils.parse_name_mapping_csv(mapping_csv_path)
193+ rename = dump_name_mapping.get(data_name, "")
194+ if rename:
195+ utils.print_info_log(f"The dump file name exceeds {Constant.MAX_FILE_NAME_LEN}, "
196+ f"use mapped name {rename} instead of {data_name}.")
197+ elif not mapping_csv_path:
198+ utils.print_warn_log(f"The dump file name {data_name} exceeds {Constant.MAX_FILE_NAME_LEN}, "
199+ f"but {Constant.MAPPING_CSV_FILE} cannot be found in {self.report_path}.")
200+ else:
201+ utils.print_warn_log(f"The dump file name {data_name} exceeds {Constant.MAX_FILE_NAME_LEN}, "
202+ f"but it is not recorded in {mapping_csv_path}.")
203+ self.dump_file_rename = rename
204+ return rename
205+ 
159 def collect_plog_file(self):206 def collect_plog_file(self):
160 find_path_cmd = ['grep', r'\[Dump\]\[Exception\]', '-inrE', self.report_path]207 find_path_cmd = ['grep', r'\[Dump\]\[Exception\]', '-inrE', self.report_path]
161 find_path_regexp = r"(/[_\-/0-9a-zA-Z.]{1,}.[log|txt]):"208 find_path_regexp = r"(/[_\-/0-9a-zA-Z.]{1,}.[log|txt]):"
@@ -257,10 +304,11 @@ class Collection:
257 utils.copy_src_to_dest(original_files, dest_path)304 utils.copy_src_to_dest(original_files, dest_path)
258 return dest_path305 return dest_path
259 306 
260- def collect_data_dump(self, device_id, data_name):307+ def collect_data_dump(self, device_id, data_name, rename=""):
261 dest_path = os.path.join(self.output_path, "collection", "dump")308 dest_path = os.path.join(self.output_path, "collection", "dump")
309+ find_name = rename or data_name
262 find_path_cmd = ['find', self.report_path, '-name',310 find_path_cmd = ['find', self.report_path, '-name',
263- f"{data_name}"]311+ f"{find_name}"]
264 regexp = r"[_\.\-/0-9a-zA-Z.]{1,}"312 regexp = r"[_\.\-/0-9a-zA-Z.]{1,}"
265 original_files = utils.get_inquire_result(find_path_cmd, regexp)313 original_files = utils.get_inquire_result(find_path_cmd, regexp)
266 if not original_files:314 if not original_files:
@@ -269,24 +317,28 @@ class Collection:
269 )317 )
270 return ''318 return ''
271 319 
272- # 如果找到大于1个data, 则匹配日志中data_dump和device_id320+ # 如果找到大于1个data, 则按文件自身所在data-dump/<device_id>/目录取报错device的那一份。
321+ # 此处匹配完整目录段,避免device_id为0时误命中data-dump/01/
273 if len(original_files) > 1:322 if len(original_files) > 1:
274- plog_dir = os.path.join(self.output_path, 'collection', 'plog')323+ device_dump_dir = os.path.join("data-dump", str(device_id))
275- for file in original_files:324+ matched_files = [file for file in original_files
276- data_dump_cmd = ['grep', os.path.basename(file), '-nr', plog_dir]325+ if os.path.dirname(file).endswith(device_dump_dir)]
277- dump_data_regexp = r".*?extra-info\/data-dump\/(\d+)\/[\w.]+"326+ if matched_files:
278- data_dump_ret = utils.get_inquire_result(data_dump_cmd, dump_data_regexp)327+ utils.print_info_log(f"Find dump file {os.path.basename(matched_files[0])}.")
279- if (device_id != data_dump_ret[0]):328+ original_files = matched_files[:1]
280- continue
281- utils.print_info_log(f"Find dump file {os.path.basename(file)}.")
282- original_files = [file]
283 329 
284 utils.check_path_valid(dest_path, isdir=True, output=True)330 utils.check_path_valid(dest_path, isdir=True, output=True)
285 utils.copy_src_to_dest(original_files, dest_path)331 utils.copy_src_to_dest(original_files, dest_path)
332+ if rename:
333+ # 超长场景下dump文件保持随机名,需一并收集mapping.csv供解析侧还原
334+ # 复用_resolve_dump_file_rename中已解析的路径,避免重复find
335+ mapping_csv_path = self._mapping_csv_path or self._get_dump_mapping_csv_path(device_id)
336+ if mapping_csv_path:
337+ utils.copy_src_to_dest([mapping_csv_path], dest_path)
286 return dest_path338 return dest_path
zhangjie
zhangjiezhangjie27 天前

级别:一般 问题:collect_data_dump 中当 rename 非空时再次调用 _get_dump_mapping_csv_path,而该路径在 _resolve_dump_file_rename 中已经解析过一次,导致同一 find 命令在 collect 流程中重复执行。 影响:在 report_path 目录较大的场景下(如数十 GB dump 数据),重复全盘 find 可能显著增加收集耗时。 修复建议:将首次解析得到的 mapping_csv 路径缓存为实例变量(如 self._mapping_csv_path),collect_data_dump 中直接复用,避免二次搜索。

likedislike
starchen_
27 天前 评论:
287 339 
288- def check_dump_data_is_valid(self, err_time, data_name):340+ def check_dump_data_is_valid(self, err_time, data_name, rename=""):
289- find_dump_data_cmd = ['find', self.report_path, '-name', data_name]341+ find_dump_data_cmd = ['find', self.report_path, '-name', rename or data_name]
290 regexp = r".*?\/data-dump\/\d+\/([\w.]+)"342 regexp = r".*?\/data-dump\/\d+\/([\w.]+)"
291 dump_data_file_list = utils.get_inquire_result(find_dump_data_cmd, regexp)343 dump_data_file_list = utils.get_inquire_result(find_dump_data_cmd, regexp)
292 home = os.environ.get("HOME")344 home = os.environ.get("HOME")
@@ -305,13 +357,14 @@ class Collection:
305 )357 )
306 raise utils.AicErrException(Constant.MS_AICERR_INVALID_DUMP_DATA_ERROR)358 raise utils.AicErrException(Constant.MS_AICERR_INVALID_DUMP_DATA_ERROR)
307 359 
308- def check_host_and_device_kernel_name(self, data_name):360+ def check_host_and_device_kernel_name(self, data_name, rename=""):
309 if self.is_sk:361 if self.is_sk:
310 # SK场景下没有device .o,跳过host/device一致性检查362 # SK场景下没有device .o,跳过host/device一致性检查
311 return True363 return True
312- kernel_cmd = ['find', self.report_path, '-name', data_name]364+ find_name = rename or data_name
365+ kernel_cmd = ['find', self.report_path, '-name', find_name]
313 _, kernel_info = utils.execute_command(kernel_cmd)366 _, kernel_info = utils.execute_command(kernel_cmd)
314- kernel_path = kernel_info.split(data_name)[0]367+ kernel_path = kernel_info.split(find_name)[0]
315 res = os.listdir(kernel_path)368 res = os.listdir(kernel_path)
316 host_kernel_name = ''369 host_kernel_name = ''
317 device_kernel_name = ''370 device_kernel_name = ''
@@ -345,8 +398,9 @@ class Collection:
345 utils.print_info_log('Step 2. Obtain the name and path of the flushed data file from the log.')398 utils.print_info_log('Step 2. Obtain the name and path of the flushed data file from the log.')
346 try:399 try:
347 err_time, device_id, data_name = self.get_dump_data_info()400 err_time, device_id, data_name = self.get_dump_data_info()
348- self.check_dump_data_is_valid(err_time, data_name)401+ rename = self._resolve_dump_file_rename(device_id, data_name)
349- check_result = self.check_host_and_device_kernel_name(data_name)402+ self.check_dump_data_is_valid(err_time, data_name, rename)
403+ check_result = self.check_host_and_device_kernel_name(data_name, rename)
350 if not check_result:404 if not check_result:
351 utils.print_error_log(f"The kernel load on the host is different from the device.")405 utils.print_error_log(f"The kernel load on the host is different from the device.")
352 return False406 return False
@@ -355,7 +409,7 @@ class Collection:
355 409 
356 # collect dump410 # collect dump
357 utils.print_info_log('Step 3. Obtain the operator name from the log.')411 utils.print_info_log('Step 3. Obtain the operator name from the log.')
358- self.collect_data_dump(device_id, data_name)412+ self.collect_data_dump(device_id, data_name, rename)
359 413 
360 # get kernel_name414 # get kernel_name
361 utils.print_info_log('Step 4. Obtain the compilation file based on the operator name.')415 utils.print_info_log('Step 4. Obtain the compilation file based on the operator name.')
@@ -67,6 +67,9 @@ class Constant:
67 MAX_READ_FILE_BYTES = 1024 * 1024 # 1M67 MAX_READ_FILE_BYTES = 1024 * 1024 # 1M
68 MAX_TAR_SIZE = 1 * 1024 * 1024 * 1024 # 1G68 MAX_TAR_SIZE = 1 * 1024 * 1024 * 1024 # 1G
69 69 
70+ MAX_FILE_NAME_LEN = 255 # Linux NAME_MAX,单文件名上限
71+ MAPPING_CSV_FILE = "mapping.csv" # 超长文件名映射表,每行 {映射后},{映射前}
72+ 
70 DIR_PLOG = 'plog'73 DIR_PLOG = 'plog'
71 74 
72 AIC_ERROR_TUPLE_LEN = 975 AIC_ERROR_TUPLE_LEN = 9
@@ -21,8 +21,10 @@ DumpDataParser class. This class mainly involves the parser_dump_data function.
21Copyright Information:21Copyright Information:
22Huawei Technologies Co., Ltd. All Rights Reserved © 202022Huawei Technologies Co., Ltd. All Rights Reserved © 2020
23"""23"""
24+import csv
24import json25import json
25import os26import os
27+import random
26import struct28import struct
27import ctypes29import ctypes
28import traceback30import traceback
@@ -297,13 +299,46 @@ class DumpDataParser:
297 array = array.reshape(shape)299 array = array.reshape(shape)
298 return array, np_dtype300 return array, np_dtype
299 301 
302+ def _load_name_mapping(self):
303+ """
304+ Load the {original name: renamed name} mapping written by the dump framework.
305+ @return: the mapping, empty when there is no mapping.csv beside the dump files
306+ """
307+ return utils.parse_name_mapping_csv(os.path.join(self.dump_path, Constant.MAPPING_CSV_FILE))
308+ 
309+ @staticmethod
310+ def _gen_random_numeric_name(file_dir, suffix):
311+ # 与异常dump框架一致,用随机数字串命名,保留后缀以便下游按扩展名读取
312+ while True:
313+ name = str(random.randint(10 ** 15, 10 ** 16 - 1)) + suffix
314+ if not os.path.exists(os.path.join(file_dir, name)):
315+ return name
316+ 
317+ @staticmethod
318+ def _record_mapping(file_dir, renamed, original_name):
319+ # 追加一行 {映射后随机数字串},{映射前文件名} 到同级mapping.csv
320+ mapping_csv = os.path.join(file_dir, Constant.MAPPING_CSV_FILE)
321+ with open(mapping_csv, 'a', newline='') as csv_file:
322+ csv.writer(csv_file).writerow([renamed, original_name])
323+ 
324+ def _check_file_name_len(self, dst_file_name):
325+ # NAME_MAX是字节上限,多字节文件名下需按编码后的字节数判断
326+ if len(os.fsencode(os.path.basename(dst_file_name))) <= Constant.MAX_FILE_NAME_LEN:
327+ return dst_file_name
328+ file_dir, file_name = os.path.split(dst_file_name)
329+ _, suffix = os.path.splitext(file_name)
330+ renamed = self._gen_random_numeric_name(file_dir, suffix)
331+ self._record_mapping(file_dir, renamed, file_name)
332+ utils.print_warn_log(f"The output file name is too long, rename {file_name} to {renamed}.")
333+ return os.path.join(file_dir, renamed)
334+ 
300 def _build_dst_file_name(self, dump_file_path, parse_type, index, dtype, np_dtype):335 def _build_dst_file_name(self, dump_file_path, parse_type, index, dtype, np_dtype):
301 name_parts = [self.info.kernel_name, parse_type, str(index)]336 name_parts = [self.info.kernel_name, parse_type, str(index)]
302 if dtype:337 if dtype:
303 name_parts.append(dtype)338 name_parts.append(dtype)
304 # numpy supported dtype is saved as npy, others keep the raw bin format339 # numpy supported dtype is saved as npy, others keep the raw bin format
305 name_parts.append("npy" if np_dtype is not None else "bin")340 name_parts.append("npy" if np_dtype is not None else "bin")
306- return os.path.join(dump_file_path, ".".join(name_parts))341+ return self._check_file_name_len(os.path.join(dump_file_path, ".".join(name_parts)))
307 342 
308 def _save_array(self, array, dst_file_name, parse_type, np_dtype):343 def _save_array(self, array, dst_file_name, parse_type, np_dtype):
309 if np_dtype is not None:344 if np_dtype is not None:
@@ -469,8 +504,14 @@ class DumpDataParser:
469 else:504 else:
470 match_dump_list = []505 match_dump_list = []
471 match_name = self.info.node_name506 match_name = self.info.node_name
507+ name_mapping = self._load_name_mapping()
508+ if name_mapping:
509+ # 超长场景下落盘文件已被重命名,按原始data_name反查随机数字串
510+ match_name = name_mapping.get(self.info.data_name or self.info.node_name, match_name)
472 for top, _, files in os.walk(self.dump_path):511 for top, _, files in os.walk(self.dump_path):
473 for name in files:512 for name in files:
513+ if name == Constant.MAPPING_CSV_FILE:
514+ continue
474 if match_name in name:515 if match_name in name:
475 match_dump_list.append(os.path.join(top, name))516 match_dump_list.append(os.path.join(top, name))
476 517 
@@ -21,6 +21,7 @@ This file mainly involves the common function.
21Copyright Information:21Copyright Information:
22Huawei Technologies Co., Ltd. All Rights Reserved © 202022Huawei Technologies Co., Ltd. All Rights Reserved © 2020
23"""23"""
24+import csv
24import inspect25import inspect
25import os26import os
26import os.path27import os.path
@@ -297,6 +298,35 @@ def copy_src_to_dest(src_file_list: list, dest_path: str):
297 print_warn_log(f"Failed to copy {file} to {dest_file}. {error}.")298 print_warn_log(f"Failed to copy {file} to {dest_file}. {error}.")
298 299 
299 300 
301+def _iter_csv_rows(csv_path: str):
302+ """
303+ yield the rows of a csv file one by one
304+ :param csv_path: the csv path
305+ """
306+ # 显式指定utf-8,避免受locale影响;算子名可能含非utf-8字节,需兜住解码失败
307+ with open(csv_path, 'r', encoding='utf-8') as csv_file:
308+ yield from csv.reader(csv_file)
309+ 
310+ 
311+def parse_name_mapping_csv(csv_path: str) -> dict:
312+ """
313+ parse the mapping.csv generated when a file name exceeds NAME_MAX,
314+ each line of which is {renamed random digits},{original file name}
315+ :param csv_path: the mapping.csv path
316+ :return: {original file name: renamed random digits}, empty when the csv is absent
317+ """
318+ mapping = {}
319+ if not csv_path or not os.path.isfile(csv_path):
320+ return mapping
321+ try:
322+ for row in _iter_csv_rows(csv_path):
323+ if len(row) == 2:
324+ mapping[row[1].strip()] = row[0].strip()
325+ except (OSError, IOError, csv.Error, UnicodeDecodeError) as error:
326+ print_warn_log(f"Failed to read {csv_path}. {error}.")
327+ return mapping
328+ 
329+ 
300def write_file(output_path: str, file_content: str, write_mode="w") -> None:330def write_file(output_path: str, file_content: str, write_mode="w") -> None:
301 """331 """
302 write text to output file332 write text to output file
@@ -18,6 +18,7 @@
18 18 
19from ms_interface import utils19from ms_interface import utils
20from ms_interface.collection import Collection20from ms_interface.collection import Collection
21+from ms_interface.constant import Constant
21import os22import os
22import sys23import sys
23import pytest24import pytest
@@ -331,6 +332,57 @@ class TestUtilsMethods(CommonAssert):
331 self.assertIn(utils.ExceptionRootCause().format_causes(),332 self.assertIn(utils.ExceptionRootCause().format_causes(),
332 """Failed to get node name in plog. Cannot run L1 test""")333 """Failed to get node name in plog. Cannot run L1 test""")
333 334 
335+ def test_run_collect_oversize_data_name(self):
336+ """
337+ 测试超长算子名场景:dump文件被框架重命名为随机数字串,
338+ collect通过mapping.csv找到实际落盘文件,并把mapping.csv一并收集
339+ """
340+ data_name = "a" * 250 + ".42.1.1726159207469285"
341+ rename = "1234567890123456"
342+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
343+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
344+ input_path.mkdir(parents=True, exist_ok=True)
345+ dump_path = input_path.joinpath("extra-info/data-dump/0")
346+ dump_path.mkdir(parents=True, exist_ok=True)
347+ dump_path.joinpath(rename).touch()
348+ dump_path.joinpath("te_gatherv2.o").touch()
349+ dump_path.joinpath("te_gatherv2_host.o").touch()
350+ dump_path.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"{rename},{data_name}\n")
351+ write_log_keyword_to_file(input_path, [
352+ DUMP_EXCEPTION_STR, EXCEPTION_INFO_DUMP_ARGS_DATA, AICORE_KERNEL_EXECUTE_FAILED,
353+ '[ERROR] IDEDD(1592077,python3):2024-09-12-16:40:08.360.226 [dump_args.cpp:807]'
354+ '[tid:1592077] [1] dump exception to file, file: '
355+ f'./new/extra-info/data-dump/0/{data_name}'])
356+ collection = Collection(input_path, output_path)
357+ res = collection.collect()
358+ self.assertEqual(res, True)
359+ self.assertEqual(collection.dump_file_rename, rename)
360+ self.assertEqual(bool(list(output_path.rglob(f"collection/dump/{rename}"))), True)
361+ self.assertEqual(
362+ bool(list(output_path.rglob(f"collection/dump/{Constant.MAPPING_CSV_FILE}"))), True)
363+ self.assertIn(self.debug_info.read_text(encoding="utf-8"),
364+ f"use mapped name {rename} instead of")
365+ 
366+ def test_run_collect_oversize_data_name_no_mapping(self):
367+ """
368+ 测试超长算子名但缺失mapping.csv:退化为按原始名查找,报dump文件找不到
369+ """
370+ data_name = "a" * 250 + ".42.1.1726159207469285"
371+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
372+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
373+ input_path.mkdir(parents=True, exist_ok=True)
374+ write_log_keyword_to_file(input_path, [
375+ DUMP_EXCEPTION_STR, EXCEPTION_INFO_DUMP_ARGS_DATA,
376+ '[ERROR] IDEDD(1592077,python3):2024-09-12-16:40:08.360.226 [dump_args.cpp:807]'
377+ '[tid:1592077] [1] dump exception to file, file: '
378+ f'./new/extra-info/data-dump/0/{data_name}'])
379+ collection = Collection(input_path, output_path)
380+ res = collection.collect()
381+ self.assertEqual(res, False)
382+ self.assertEqual(collection.dump_file_rename, "")
383+ self.assertIn(self.debug_info.read_text(encoding="utf-8"),
384+ f"Cannot find dump file {data_name}")
385+ 
334 def test_get_node_and_kernel_name_l1_get_node_name_have_multiple_dump(self):386 def test_get_node_and_kernel_name_l1_get_node_name_have_multiple_dump(self):
335 """387 """
336 测试L1无法找到node_name失败报错388 测试L1无法找到node_name失败报错
@@ -29,6 +29,7 @@ import pytest
29sys.path.append(MSAICERR_PATH)29sys.path.append(MSAICERR_PATH)
30 30 
31from ms_interface.aic_error_info import AicErrorInfo31from ms_interface.aic_error_info import AicErrorInfo
32+from ms_interface.constant import Constant
32from ms_interface.dump_data_parser import DumpDataParser, BigDumpDataParser33from ms_interface.dump_data_parser import DumpDataParser, BigDumpDataParser
33 34 
34dump_file = "exception_info.2.1.20250609144925349"35dump_file = "exception_info.2.1.20250609144925349"
@@ -51,6 +52,18 @@ class Selfliberr():
51 return 152 return 1
52 53 
53 54 
55+class SelflibWriteJson():
56+ """在被调用时才生成json,模拟解析so的落盘时机"""
57+ 
58+ def __init__(self, dump_json):
59+ self.dump_json = dump_json
60+ 
61+ def ParseDumpProtoToJson(self, data_ptr, data_size, path_ptr):
62+ with open(path_ptr.decode('utf-8'), 'w') as json_file:
63+ json_file.write(json.dumps(self.dump_json))
64+ return 0
65+ 
66+ 
54class TestUtilsMethods(CommonAssert):67class TestUtilsMethods(CommonAssert):
55 @pytest.fixture(autouse=True)68 @pytest.fixture(autouse=True)
56 def change_test_dir(self, tmp_path):69 def change_test_dir(self, tmp_path):
@@ -242,6 +255,49 @@ class TestUtilsMethods(CommonAssert):
242 assert parser.get_bin_data()[0].endswith("input.0.hifloat8.bin")255 assert parser.get_bin_data()[0].endswith("input.0.hifloat8.bin")
243 self.assertIn(res, "If dtype is hifloat8, summary is: ")256 self.assertIn(res, "If dtype is hifloat8, summary is: ")
244 257 
258+ def test_parser_dump_file_by_mapped_name(self, mocker):
259+ """超长场景: dump目录下只有随机名文件和mapping.csv,按原始data_name反查映射名完成解析"""
260+ data_name = "a" * 250 + ".42.1.1726159207469285"
261+ rename = "1234567890123456"
262+ dump_dir = self.temp.joinpath("dump")
263+ dump_dir.mkdir(parents=True, exist_ok=True)
264+ dump_json = self._make_dump_json(output_data_type=1, output_size='8', output_dim=['2'])
265+ # 解析so会在dump文件同级生成json,此处在调用时写入,避免提前污染待匹配目录
266+ mocker.patch('ctypes.CDLL', return_value=SelflibWriteJson(dump_json))
267+ create_dump_file(str(dump_dir.joinpath(rename)), 10, 200)
268+ dump_dir.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"{rename},{data_name}\n")
269+ info = AicErrorInfo()
270+ info.node_name = "GatherV2" # L1场景下node_name是plog中的短名,与映射key不同
271+ info.data_name = data_name
272+ info.json_file = str(RES_PATH.joinpath("ori_data/collect_json/test.json"))
273+ parser = DumpDataParser(str(dump_dir), info)
274+ parser.parse()
275+ # 命中随机名文件,且mapping.csv没有被当成dump文件解析
276+ self.assertEqual(info.dump_file, [str(dump_dir.joinpath(rename))])
277+ self.assertIn(info.dump_info, f"{rename}.output.0.float32.npy")
278+ 
279+ def test_parser_dump_file_oversize_result_renamed(self, mocker):
280+ """超长场景: 解析结果文件名超过NAME_MAX时重命名为随机数字串,并记录到同级mapping.csv"""
281+ dump_json = self._make_dump_json(output_data_type=1, output_size='8', output_dim=['2'])
282+ self.common_mock(mocker, dump_json)
283+ create_dump_file(dump_file, 10, 200)
284+ info = AicErrorInfo()
285+ info.kernel_name = "a" * 260 # 拼装后的结果文件名必然超长
286+ info.json_file = str(RES_PATH.joinpath("ori_data/collect_json/test.json"))
287+ parser = DumpDataParser(dump_file, info)
288+ parser.parse()
289+ npy_files = [f for f in parser.get_bin_data() if f.endswith(".npy")]
290+ self.assertEqual(len(npy_files), 1)
291+ file_name = os.path.basename(npy_files[0])
292+ # 落盘成功,文件名为随机数字串 + 原后缀
293+ assert len(file_name) <= Constant.MAX_FILE_NAME_LEN
294+ assert file_name[:-len(".npy")].isdigit()
295+ self.assertEqual(os.path.isfile(npy_files[0]), True)
296+ self.assertEqual(str(np.load(npy_files[0]).dtype), "float32")
297+ # mapping.csv中记录了 {映射后},{映射前}
298+ mapping_text = self.temp.joinpath(Constant.MAPPING_CSV_FILE).read_text()
299+ self.assertIn(mapping_text, f"{file_name},{info.kernel_name}.output.0.float32.npy")
300+ 
245 def test_parser_dump_file_bfloat16_dtype_success(self, mocker):301 def test_parser_dump_file_bfloat16_dtype_success(self, mocker):
246 dump_json = {'version': '2.0', 'dump_time': '1749451765349986', 'output': [{'data_type': 27, 'format': 0, 'shape': {'dim': ['2', '2048']}, 'data': '', 'size': '10', 'sub_format': 0, 'address': '0', 'dim_range': [], 'offset': '3'}], 'input': [{'data_type': 0, 'format': 0, 'shape': {'dim': ['10240', '2048']}, 'data': '', 'size': '10', 'sub_format': 0, 'address': '0', 'offset': '0', 'arg_index': 0, 'input_type': 2}, {'data_type': 0, 'format': 0, 'shape': {'dim': ['2']}, 'data': '', 'size': '32', 'sub_format': 0, 'address': '0', 'offset': '0', 'arg_index': 1, 'input_type': 2}, {'data_type': 0, 'format': 0, 'shape': {'dim': ['1']}, 'data': '', 'size': '32', 'sub_format': 0, 'address': '0', 'offset': '0', 'arg_index': 2, 'input_type': 2}, {'data_type': 0, 'format': 0, 'data': '', 'size': '10', 'sub_format': 0, 'address': '0', 'offset': '0', 'arg_index': 5, 'input_type': 7}], 'buffer': [], 'op_name': '', 'attr': [], 'space': [{'type': 0, 'data': '', 'size': '10'}], 'dfx_message': '[AIC_INFO] args(0 to 20) after execute:0x12c200000000, 0x12d340000000, 0x12c1c0000518, 0x12d340000200, 0x12d340004400, 0x12c1c0000438, 0x12c100011000, 0x285a, 0x2, 0x1, 0, 0x2000, 0x8, 0x1, 0x1, 0x2800, 0x2, 0x800, 0x1, 0x1, \n[AIC_INFO] args(20 to 39) after execute:0x2, 0x1, 0x1, 0x1, 0x1, 0x800, 0x1, 0x1, 0x2, 0x1, 0x2, 0xa5a5a5a500000000, 0, 0, 0, 0, 0, 0, 0, \n[Dump][Exception] begin to load normal tensor, index:0\n[Dump][Exception] exception info dump args data, addr:0x12c200000000; size:83886080 bytes\n[Dump][Exception] end to load normal tensor, index:0\n[Dump][Exception] begin to load normal tensor, index:1\n[Dump][Exception] exception info dump args data, addr:0x12d340000000; size:32 bytes\n[Dump][Exception] end to load normal tensor, index:1\n[Dump][Exception] begin to load normal tensor, index:2\n[Dump][Exception] exception info dump args data, addr:0x12c1c0000518; size:32 bytes\n[Dump][Exception] end to load normal tensor, index:2\n[Dump][Exception] begin to load normal tensor, index:3\n[Dump][Exception] exception info dump args data, addr:0x12d340000200; size:16384 bytes\n[Dump][Exception] end to load normal tensor, index:3\n[Dump][Exception] exception info dump args data, addr:0x12d340004400; size:76832 bytes\n[Dump][Exception] exception info dump args data, addr:0x12c1c0000438; size:200 bytes\n'}302 dump_json = {'version': '2.0', 'dump_time': '1749451765349986', 'output': [{'data_type': 27, 'format': 0, 'shape': {'dim': ['2', '2048']}, 'data': '', 'size': '10', 'sub_format': 0, 'address': '0', 'dim_range': [], 'offset': '3'}], 'input': [{'data_type': 0, 'format': 0, 'shape': {'dim': ['10240', '2048']}, 'data': '', 'size': '10', 'sub_format': 0, 'address': '0', 'offset': '0', 'arg_index': 0, 'input_type': 2}, {'data_type': 0, 'format': 0, 'shape': {'dim': ['2']}, 'data': '', 'size': '32', 'sub_format': 0, 'address': '0', 'offset': '0', 'arg_index': 1, 'input_type': 2}, {'data_type': 0, 'format': 0, 'shape': {'dim': ['1']}, 'data': '', 'size': '32', 'sub_format': 0, 'address': '0', 'offset': '0', 'arg_index': 2, 'input_type': 2}, {'data_type': 0, 'format': 0, 'data': '', 'size': '10', 'sub_format': 0, 'address': '0', 'offset': '0', 'arg_index': 5, 'input_type': 7}], 'buffer': [], 'op_name': '', 'attr': [], 'space': [{'type': 0, 'data': '', 'size': '10'}], 'dfx_message': '[AIC_INFO] args(0 to 20) after execute:0x12c200000000, 0x12d340000000, 0x12c1c0000518, 0x12d340000200, 0x12d340004400, 0x12c1c0000438, 0x12c100011000, 0x285a, 0x2, 0x1, 0, 0x2000, 0x8, 0x1, 0x1, 0x2800, 0x2, 0x800, 0x1, 0x1, \n[AIC_INFO] args(20 to 39) after execute:0x2, 0x1, 0x1, 0x1, 0x1, 0x800, 0x1, 0x1, 0x2, 0x1, 0x2, 0xa5a5a5a500000000, 0, 0, 0, 0, 0, 0, 0, \n[Dump][Exception] begin to load normal tensor, index:0\n[Dump][Exception] exception info dump args data, addr:0x12c200000000; size:83886080 bytes\n[Dump][Exception] end to load normal tensor, index:0\n[Dump][Exception] begin to load normal tensor, index:1\n[Dump][Exception] exception info dump args data, addr:0x12d340000000; size:32 bytes\n[Dump][Exception] end to load normal tensor, index:1\n[Dump][Exception] begin to load normal tensor, index:2\n[Dump][Exception] exception info dump args data, addr:0x12c1c0000518; size:32 bytes\n[Dump][Exception] end to load normal tensor, index:2\n[Dump][Exception] begin to load normal tensor, index:3\n[Dump][Exception] exception info dump args data, addr:0x12d340000200; size:16384 bytes\n[Dump][Exception] end to load normal tensor, index:3\n[Dump][Exception] exception info dump args data, addr:0x12d340004400; size:76832 bytes\n[Dump][Exception] exception info dump args data, addr:0x12c1c0000438; size:200 bytes\n'}
247 self.common_mock(mocker, dump_json)303 self.common_mock(mocker, dump_json)
@@ -565,6 +565,375 @@ class TestUtilsMethods(CommonAssert):
565 res = collection.collect_kernel_file(kernel_name1)565 res = collection.collect_kernel_file(kernel_name1)
566 self.assertEqual('', res)566 self.assertEqual('', res)
567 567 
568+ @pytest.mark.parametrize(
569+ "name_len, expected",
570+ [
571+ (Constant.MAX_FILE_NAME_LEN, False), # 等于上限,不算超长
572+ (Constant.MAX_FILE_NAME_LEN + 1, True) # 超过上限
573+ ]
574+ )
575+ def test_is_oversize_name(self, name_len, expected):
576+ """
577+ 测试超长名字长度初判的边界
578+ """
579+ collection = Collection(self.temp, self.temp)
580+ self.assertEqual(collection._is_oversize_name("a" * name_len), expected)
581+ 
582+ def test_get_dump_mapping_csv_path(self):
583+ """
584+ 测试按device_id定位data-dump目录下的mapping.csv
585+ """
586+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
587+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
588+ for device_id in ("0", "1"):
589+ dump_path = input_path.joinpath(f"extra-info/data-dump/{device_id}")
590+ dump_path.mkdir(parents=True, exist_ok=True)
591+ dump_path.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"1234,{device_id}\n")
592+ collection = Collection(input_path, output_path)
593+ res = collection._get_dump_mapping_csv_path("1")
594+ self.assertIn(res, "data-dump/1/mapping.csv")
atomgit-bot
atomgit-botatomgit-bot27 天前

🟠 High Priority

unittest.assertIn(member, container) 断言 member in container。此处第一个参数 res_get_dump_mapping_csv_path 返回的完整路径,例如 /tmp/.../asys_output_XXX/extra-info/data-dump/1/mapping.csv)被当作 member,第二个参数 "data-dump/1/mapping.csv" 被当作 container。这断言的是"完整路径是短字符串的子串",逻辑反向。意图应为"返回路径中包含 data-dump/1/mapping.csv",正确写法是 self.assertIn("data-dump/1/mapping.csv", res)

同一文件中存在 6 处相同模式的错误:

后果:这些断言要么永远通过(若返回值为空串或碰巧是短串的子串),要么永远失败(若返回值是完整路径),无法验证实际意图。

建议:交换 assertIn / assertNotIn 的两个参数:将预期子串放在第一个参数,实际值放在第二个参数。修正所有 6 处(L594、L608、L609、L660、L670-671、L746)。

改动建议
594
- self.assertIn(res, "data-dump/1/mapping.csv")
594
+ self.assertIn("data-dump/1/mapping.csv", res)
应用建议
likedislike
不准确?
starchen_
27 天前 评论:
595+ 
596+ def test_get_dump_mapping_csv_path_device_id_prefix(self):
597+ """
598+ 测试device_id为0时不会误命中data-dump/01/下的mapping.csv
599+ """
600+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
601+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
602+ for device_id in ("01", "0"):
603+ dump_path = input_path.joinpath(f"extra-info/data-dump/{device_id}")
604+ dump_path.mkdir(parents=True, exist_ok=True)
605+ dump_path.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"1234,{device_id}\n")
606+ collection = Collection(input_path, output_path)
607+ res = collection._get_dump_mapping_csv_path("0")
608+ self.assertIn(res, "data-dump/0/mapping.csv")
609+ self.assertNotIn(res, "data-dump/01/mapping.csv")
610+ 
611+ def test_get_dump_mapping_csv_path_not_exist(self):
612+ """
613+ 测试report_path下没有mapping.csv时返回空
614+ """
615+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
616+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
617+ input_path.mkdir(parents=True, exist_ok=True)
618+ collection = Collection(input_path, output_path)
619+ self.assertEqual(collection._get_dump_mapping_csv_path("0"), "")
620+ 
621+ def test_get_dump_mapping_csv_path_other_device_only(self):
622+ """
623+ 测试只有其它device的mapping.csv时返回空,不回退使用别的卡的映射表
624+ """
625+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
626+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
627+ for device_id in ("1", "2"):
628+ dump_path = input_path.joinpath(f"extra-info/data-dump/{device_id}")
629+ dump_path.mkdir(parents=True, exist_ok=True)
630+ dump_path.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"1234,{device_id}\n")
631+ collection = Collection(input_path, output_path)
632+ self.assertEqual(collection._get_dump_mapping_csv_path("0"), "")
633+ self.assertIn(self.debug_info.read_text(),
634+ f"{Constant.MAPPING_CSV_FILE} of device 0 cannot be found in")
635+ 
636+ def test_collect_oversize_scene_other_device_mapping_only(self):
637+ """
638+ 测试报错device无mapping.csv、其它device有时,不会误用其映射名收集到别的卡的dump。
639+ 退化为按原始名查找,dump文件找不到时collect返回False
640+ """
641+ data_name = "a" * 250 + ".42.1.1726159207469285"
642+ rename = "1234567890123456"
643+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
644+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
645+ # 报错device为0,但只有device1落了随机名dump和mapping.csv
646+ dump_path = input_path.joinpath("extra-info/data-dump/1")
647+ dump_path.mkdir(parents=True, exist_ok=True)
648+ dump_path.joinpath(rename).touch()
649+ dump_path.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"{rename},{data_name}\n")
650+ input_path.joinpath("plog.txt").write_text(
651+ "[ERROR] IDEDD(1592077,python3):2024-09-12-16:40:08.360.226 [dump_args.cpp:807]"
652+ "[tid:1592077] [Dump][Exception] dump exception to file, file: "
653+ f"./new/extra-info/data-dump/0/{data_name}")
654+ collection = Collection(input_path, output_path)
655+ self.assertEqual(collection.collect(), False)
656+ self.assertEqual(collection.dump_file_rename, "")
657+ # device1的dump文件没有被误收集
658+ self.assertEqual(bool(list(output_path.rglob(f'collection/dump/{rename}'))), False)
659+ 
660+ def test_resolve_dump_file_rename_normal_name(self, mocker):
661+ """
662+ 测试普通名字走快路径,不触发find mapping.csv
663+ """
664+ collection = Collection(self.temp, self.temp)
665+ get_csv = mocker.patch.object(collection, "_get_dump_mapping_csv_path")
666+ self.assertEqual(collection._resolve_dump_file_rename("0", "short_name"), "")
667+ self.assertEqual(get_csv.called, False)
668+ 
669+ def test_resolve_dump_file_rename_oversize_matched(self, mocker):
670+ """
671+ 测试超长名字命中映射,返回映射后的随机数字串
672+ """
673+ data_name = "a" * 250 + ".42.1.1726159207469285"
674+ collection = Collection(self.temp, self.temp)
675+ mocker.patch.object(collection, "_get_dump_mapping_csv_path", return_value="mapping.csv")
676+ mocker.patch.object(utils, "parse_name_mapping_csv",
677+ return_value={data_name: "1234567890123456"})
678+ self.assertEqual(collection._resolve_dump_file_rename("0", data_name), "1234567890123456")
679+ self.assertEqual(collection.dump_file_rename, "1234567890123456")
680+ 
681+ def test_is_oversize_name_multi_byte(self):
682+ """
683+ 测试多字节文件名按字节数判定,字符数未超但字节数已超
684+ """
685+ collection = Collection(self.temp, self.temp)
686+ name = "算" * 100 # 100个字符,UTF-8编码为300字节
687+ self.assertEqual(len(name) > Constant.MAX_FILE_NAME_LEN, False)
688+ self.assertEqual(collection._is_oversize_name(name), True)
689+ 
690+ def test_resolve_dump_file_rename_oversize_not_matched(self, mocker):
691+ """
692+ 测试超长名字未命中映射,返回空串退化为原始名,并打印未命中原因
693+ """
694+ data_name = "a" * 250 + ".42.1.1726159207469285"
695+ collection = Collection(self.temp, self.temp)
696+ mocker.patch.object(collection, "_get_dump_mapping_csv_path", return_value="mapping.csv")
697+ mocker.patch.object(utils, "parse_name_mapping_csv", return_value={"other": "1234"})
698+ self.assertEqual(collection._resolve_dump_file_rename("0", data_name), "")
699+ self.assertIn(self.debug_info.read_text(), "it is not recorded in mapping.csv")
700+ 
701+ def test_resolve_dump_file_rename_oversize_no_mapping_csv(self, mocker):
702+ """
703+ 测试超长名字但mapping.csv缺失,日志可区分于"有mapping.csv但未命中"
704+ """
705+ data_name = "a" * 250 + ".42.1.1726159207469285"
706+ collection = Collection(self.temp, self.temp)
707+ mocker.patch.object(collection, "_get_dump_mapping_csv_path", return_value="")
708+ self.assertEqual(collection._resolve_dump_file_rename("0", data_name), "")
709+ self.assertIn(self.debug_info.read_text(),
710+ f"but {Constant.MAPPING_CSV_FILE} cannot be found in")
711+ 
712+ def test_check_dump_data_is_valid_with_rename(self):
713+ """
714+ 测试超长场景下按映射名校验dump文件存在性
715+ """
716+ data_name = "a" * 250 + ".42.1.1726159207469285"
717+ rename = "1234567890123456"
718+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
719+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
720+ dump_path = input_path.joinpath("extra-info/data-dump/0")
721+ dump_path.mkdir(parents=True, exist_ok=True)
722+ dump_path.joinpath(rename).touch()
723+ collection = Collection(input_path, output_path)
724+ # 按映射名可以找到,不抛异常
725+ collection.check_dump_data_is_valid("2024-09-12-16:40:08.360.226", data_name, rename)
726+ # 不传rename时按原始名查找,找不到
727+ with pytest.raises(utils.AicErrException) as e:
728+ collection.check_dump_data_is_valid("2024-09-12-16:40:08.360.226", data_name)
729+ self.assertEqual(str(e), str(Constant.MS_AICERR_INVALID_DUMP_DATA_ERROR))
atomgit-bot
atomgit-botatomgit-bot27 天前

🟠 High Priority

L688-690 的代码结构为: with pytest.raises(utils.AicErrException) as e: collection.check_dump_data_is_valid("2024-09-12-16:40:08.360.226", data_name) # L689 self.assertEqual(str(e), str(Constant.MS_AICERR_INVALID_DUMP_DATA_ERROR)) # L690

若 L689 抛出异常,控制流立即跳出 with 块,L690 永远不可达。若 L689 未抛异常,L690 执行时 eExceptionInfo 对象,str(e) 不等于异常消息——且此时该测试已经失败(本该抛异常却没抛)。

另外,即使把断言移到 with 块之外,str(e) 获取的是 ExceptionInfo 的字符串表示而非异常对象,应使用 str(e.value)

正确写法: with pytest.raises(utils.AicErrException) as e: collection.check_dump_data_is_valid("2024-09-12-16:40:08.360.226", data_name) self.assertEqual(str(e.value), str(Constant.MS_AICERR_INVALID_DUMP_DATA_ERROR))

建议:将 self.assertEqual 移到 with pytest.raises 块之外,并使用 str(e.value) 获取异常消息。

likedislike
不准确?
starchen_
27 天前 评论:
730+ 
731+ def test_check_host_and_device_kernel_name_with_rename(self):
732+ """
733+ 测试超长场景下用映射名定位dump文件所在目录,目录内.o校验逻辑不变
734+ """
735+ data_name = "a" * 250 + ".42.1.1726159207469285"
736+ rename = "1234567890123456"
737+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
738+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}/dump")
739+ input_path.mkdir(parents=True, exist_ok=True)
740+ input_path.joinpath(rename).touch()
741+ input_path.joinpath("te_gatherv2.o").touch()
742+ input_path.joinpath("te_gatherv2_host.o").touch()
743+ collection = Collection(input_path, output_path)
744+ self.assertEqual(collection.check_host_and_device_kernel_name(data_name, rename), True)
745+ 
746+ def test_collect_data_dump_with_rename(self):
747+ """
748+ 测试超长场景下按映射名收集dump文件,并一并收集mapping.csv
749+ """
750+ data_name = "a" * 250 + ".42.1.1726159207469285"
751+ rename = "1234567890123456"
752+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
753+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
754+ dump_path = input_path.joinpath("extra-info/data-dump/0")
755+ dump_path.mkdir(parents=True, exist_ok=True)
756+ dump_path.joinpath(rename).touch()
757+ dump_path.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"{rename},{data_name}\n")
758+ collection = Collection(input_path, output_path)
759+ collection.collect_data_dump("0", data_name, rename)
760+ self.assertEqual(
761+ bool(list(output_path.rglob(f'collection/dump/{rename}'))), True)
762+ self.assertEqual(
763+ bool(list(output_path.rglob(f'collection/dump/{Constant.MAPPING_CSV_FILE}'))), True)
764+ 
765+ def test_collect_data_dump_with_rename_multiple(self, mocker):
766+ """
767+ 测试超长场景下找到多个dump文件时,回查plog的grep关键字用原始名
768+ """
769+ data_name = "a" * 250 + ".42.1.1726159207469285"
770+ rename = "1234567890123456"
771+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
772+ collection_plog_path = output_path.joinpath('collection/plog')
773+ collection_plog_path.mkdir(parents=True, exist_ok=True)
774+ collection_plog_path.joinpath("dump.log").write_text(
775+ f"extra-info/data-dump/0/{data_name}")
776+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
777+ for device_id in ("0", "1"):
778+ dump_path = input_path.joinpath(f"extra-info/data-dump/{device_id}")
779+ dump_path.mkdir(parents=True, exist_ok=True)
780+ dump_path.joinpath(rename).touch()
781+ collection = Collection(input_path, output_path)
782+ collection.collect_data_dump("0", data_name, rename)
783+ self.assertEqual(
784+ bool(list(output_path.rglob(f'collection/dump/{rename}'))), True)
785+ self.assertIn(self.debug_info.read_text(), f"Find dump file {rename}.")
786+ 
787+ def test_collect_data_dump_multiple_no_plog_match(self):
788+ """
789+ 测试找到多个dump文件但plog中查不到data-dump记录时不抛IndexError
790+ """
791+ data_name = "GatherV2.GatherV21.1.1733469426252033"
792+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
793+ collection_plog_path = output_path.joinpath('collection/plog')
794+ collection_plog_path.mkdir(parents=True, exist_ok=True)
795+ collection_plog_path.joinpath("dump.log").write_text("no data-dump record here")
796+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
797+ for device_id in ("0", "1"):
798+ dump_path = input_path.joinpath(f"extra-info/data-dump/{device_id}")
799+ dump_path.mkdir(parents=True, exist_ok=True)
800+ dump_path.joinpath(data_name).touch()
801+ collection = Collection(input_path, output_path)
802+ res = collection.collect_data_dump("0", data_name)
803+ self.assertEqual(res, os.path.join(str(output_path), "collection", "dump"))
804+ self.assertEqual(
805+ bool(list(output_path.rglob(f'collection/dump/{data_name}'))), True)
806+ 
807+ @pytest.mark.parametrize("target_device", ["0", "1"])
808+ def test_collect_data_dump_multiple_picks_target_device(self, target_device):
809+ """
810+ 测试多个device目录下存在同名dump文件时,按报错device筛选。
811+ 两个device各跑一次,结果必须各自命中,不能受目录遍历顺序影响
812+ """
813+ data_name = "GatherV2.GatherV21.1.1733469426252033"
814+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}_{target_device}")
815+ collection_plog_path = output_path.joinpath('collection/plog')
816+ collection_plog_path.mkdir(parents=True, exist_ok=True)
817+ collection_plog_path.joinpath("dump.log").write_text(
818+ f"extra-info/data-dump/{target_device}/{data_name}")
819+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
820+ for device_id in ("0", "1"):
821+ dump_path = input_path.joinpath(f"extra-info/data-dump/{device_id}")
822+ dump_path.mkdir(parents=True, exist_ok=True)
823+ dump_path.joinpath(data_name).write_text(f"device{device_id}")
824+ collection = Collection(input_path, output_path)
825+ collection.collect_data_dump(target_device, data_name)
826+ collected = list(output_path.rglob(f'collection/dump/{data_name}'))
827+ # 只收集报错device的那一份
828+ self.assertEqual(len(collected), 1)
829+ self.assertEqual(collected[0].read_text(), f"device{target_device}")
830+ 
831+ def test_collect_data_dump_multiple_device_id_prefix(self):
832+ """
833+ 测试device_id为0时不会误命中data-dump/01/下的同名dump文件
834+ """
835+ data_name = "GatherV2.GatherV21.1.1733469426252033"
836+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
837+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
838+ for device_id in ("01", "0"):
839+ dump_path = input_path.joinpath(f"extra-info/data-dump/{device_id}")
840+ dump_path.mkdir(parents=True, exist_ok=True)
841+ dump_path.joinpath(data_name).write_text(f"device{device_id}")
842+ collection = Collection(input_path, output_path)
843+ collection.collect_data_dump("0", data_name)
844+ collected = list(output_path.rglob(f'collection/dump/{data_name}'))
845+ self.assertEqual(len(collected), 1)
846+ self.assertEqual(collected[0].read_text(), "device0")
847+ 
848+ @pytest.mark.parametrize("target_device", ["0", "1"])
849+ def test_collect_data_dump_with_rename_multiple_device(self, target_device):
850+ """
851+ 测试超长场景下多个device目录存在相同映射名时,只收集报错device的那一份。
852+ 映射名在plog中不存在,无法靠回查plog区分,必须按文件所在目录筛选
853+ """
854+ data_name = "a" * 250 + ".42.1.1726159207469285"
855+ rename = "1234567890123456"
856+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}_{target_device}")
857+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
858+ for device_id in ("0", "1"):
859+ dump_path = input_path.joinpath(f"extra-info/data-dump/{device_id}")
860+ dump_path.mkdir(parents=True, exist_ok=True)
861+ dump_path.joinpath(rename).write_text(f"device{device_id}")
862+ collection = Collection(input_path, output_path)
863+ collection.collect_data_dump(target_device, data_name, rename)
864+ collected = list(output_path.rglob(f'collection/dump/{rename}'))
865+ self.assertEqual(len(collected), 1)
866+ self.assertEqual(collected[0].read_text(), f"device{target_device}")
867+ 
868+ def test_collect_oversize_scene(self):
869+ """
870+ 测试超长名字场景走完整collect流程:按映射名收集dump文件并带上mapping.csv
871+ """
872+ data_name = "a" * 250 + ".42.1.1726159207469285"
873+ rename = "1234567890123456"
874+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
875+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
876+ dump_path = input_path.joinpath("extra-info/data-dump/0")
877+ dump_path.mkdir(parents=True, exist_ok=True)
878+ dump_path.joinpath(rename).touch()
879+ dump_path.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"{rename},{data_name}\n")
880+ input_path.joinpath("plog.txt").write_text(
881+ "[ERROR] IDEDD(1592077,python3):2024-09-12-16:40:08.360.226 [dump_args.cpp:807]"
882+ "[tid:1592077] [Dump][Exception] dump exception to file, file: "
883+ f"./new/extra-info/data-dump/0/{data_name}")
884+ collection = Collection(input_path, output_path)
885+ res = collection.collect()
886+ self.assertEqual(res, True)
887+ self.assertEqual(collection.dump_file_rename, rename)
888+ self.assertEqual(
889+ bool(list(output_path.rglob(f'collection/dump/{rename}'))), True)
890+ self.assertEqual(
891+ bool(list(output_path.rglob(f'collection/dump/{Constant.MAPPING_CSV_FILE}'))), True)
892+ 
893+ def test_collect_oversize_scene_mapping_csv_found_once(self, mocker):
894+ """
895+ 测试超长名字场景下mapping.csv只解析一次,collect_data_dump复用缓存不再重复find
896+ """
897+ data_name = "a" * 250 + ".42.1.1726159207469285"
898+ rename = "1234567890123456"
899+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
900+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
901+ dump_path = input_path.joinpath("extra-info/data-dump/0")
902+ dump_path.mkdir(parents=True, exist_ok=True)
903+ dump_path.joinpath(rename).touch()
904+ dump_path.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"{rename},{data_name}\n")
905+ input_path.joinpath("plog.txt").write_text(
906+ "[ERROR] IDEDD(1592077,python3):2024-09-12-16:40:08.360.226 [dump_args.cpp:807]"
907+ "[tid:1592077] [Dump][Exception] dump exception to file, file: "
908+ f"./new/extra-info/data-dump/0/{data_name}")
909+ collection = Collection(input_path, output_path)
910+ get_csv = mocker.spy(collection, "_get_dump_mapping_csv_path")
911+ self.assertEqual(collection.collect(), True)
912+ self.assertEqual(get_csv.call_count, 1)
913+ self.assertEqual(
914+ bool(list(output_path.rglob(f'collection/dump/{Constant.MAPPING_CSV_FILE}'))), True)
915+ 
916+ def test_collect_data_dump_with_rename_no_cache(self, mocker):
917+ """
918+ 测试直接调用collect_data_dump(未经_resolve_dump_file_rename)时缓存为空,
919+ 仍会按需解析mapping.csv
920+ """
921+ data_name = "a" * 250 + ".42.1.1726159207469285"
922+ rename = "1234567890123456"
923+ output_path = self.temp.joinpath(f"info_{CUR_TIME_STR}")
924+ input_path = self.temp.joinpath(f"asys_output_{CUR_TIME_STR}")
925+ dump_path = input_path.joinpath("extra-info/data-dump/0")
926+ dump_path.mkdir(parents=True, exist_ok=True)
927+ dump_path.joinpath(rename).touch()
928+ dump_path.joinpath(Constant.MAPPING_CSV_FILE).write_text(f"{rename},{data_name}\n")
929+ collection = Collection(input_path, output_path)
930+ self.assertEqual(collection._mapping_csv_path, "")
931+ get_csv = mocker.spy(collection, "_get_dump_mapping_csv_path")
932+ collection.collect_data_dump("0", data_name, rename)
933+ self.assertEqual(get_csv.call_count, 1)
934+ self.assertEqual(
935+ bool(list(output_path.rglob(f'collection/dump/{Constant.MAPPING_CSV_FILE}'))), True)
936+ 
568 @pytest.mark.parametrize(937 @pytest.mark.parametrize(
569 "graph_name, expected",938 "graph_name, expected",
570 [939 [
@@ -470,3 +470,107 @@ class TestUtilsMethods(CommonAssert):
470 dump_data_parser._save_data_to_bin_file({'input': [{'shape': {'dim':['1', '2']}, 'size':'2', 'data': struct.pack('Q', 10)}]}, 'input', {'input': {}}, dump_file)470 dump_data_parser._save_data_to_bin_file({'input': [{'shape': {'dim':['1', '2']}, 'size':'2', 'data': struct.pack('Q', 10)}]}, 'input', {'input': {}}, dump_file)
471 except Exception as e:471 except Exception as e:
472 self.assertEqual(str(e), '4')472 self.assertEqual(str(e), '4')
473+ 
474+ def test_build_dst_file_name_normal_len(self):
475+ """不超长的解析结果文件名原样返回,不生成mapping.csv"""
476+ info = AicErrorInfo()
477+ info.kernel_name = 'GatherV2'
478+ dump_data_parser = DumpDataParser(str(self.temp), info)
479+ res = dump_data_parser._build_dst_file_name(str(self.temp), 'input', 0, 'float32', np.dtype('float32'))
480+ self.assertEqual(res, os.path.join(str(self.temp), 'GatherV2.input.0.float32.npy'))
481+ self.assertEqual(self.temp.joinpath(Constant.MAPPING_CSV_FILE).exists(), False)
482+ 
483+ def test_build_dst_file_name_oversize_renamed(self):
484+ """超长的解析结果文件名被重命名为随机数字串,并记录到同级mapping.csv"""
485+ info = AicErrorInfo()
486+ info.kernel_name = 'a' * 260
487+ dump_data_parser = DumpDataParser(str(self.temp), info)
488+ res = dump_data_parser._build_dst_file_name(str(self.temp), 'input', 0, 'float32', np.dtype('float32'))
489+ file_name = os.path.basename(res)
490+ # 重命名后为随机数字串 + 原后缀,落盘不会超过NAME_MAX
491+ assert len(file_name) <= Constant.MAX_FILE_NAME_LEN
492+ assert file_name.endswith('.npy')
493+ assert file_name[:-len('.npy')].isdigit()
494+ # 回读mapping.csv,确认记录了 {映射后},{映射前}
495+ mapping_text = self.temp.joinpath(Constant.MAPPING_CSV_FILE).read_text()
496+ self.assertIn(mapping_text, f'{file_name},{info.kernel_name}.input.0.float32.npy')
atomgit-bot
atomgit-botatomgit-bot27 天前

🟠 High Priority

unittest.assertIn(member, container) 断言 member in container。此处第一个参数 mapping_text(mapping.csv 的完整内容,例如 "1234567890123456.npy,aaa...aaa.input.0.float32.npy\n")被当作 member,第二个参数 f'{file_name},{info.kernel_name}.input.0.float32.npy'(单条期望的 CSV 行)被当作 container。这断言的是"整个文件内容是单行字符串的子串",逻辑反向。意图应为"mapping.csv 中包含这条映射记录",正确写法是 self.assertIn(f'{file_name},{info.kernel_name}.input.0.float32.npy', mapping_text)

同一文件中 L535-536 存在相同错误:

后果:mapping_text(含换行符的完整内容)几乎不会是单行字符串的子串,断言预期会失败,无法验证实际意图。

建议:交换 assertIn 的两个参数:将预期的 CSV 行放在第一个参数,mapping.csv 的完整内容放在第二个参数。修正 L496、L535、L536 三处。

改动建议
496
- self.assertIn(mapping_text, f'{file_name},{info.kernel_name}.input.0.float32.npy')
496
+ self.assertIn(f'{file_name},{info.kernel_name}.input.0.float32.npy', mapping_text)
应用建议
likedislike
不准确?
starchen_
27 天前 评论:
497+ # 重命名后的文件名可以正常落盘
498+ np.save(res, np.zeros(2, dtype=np.float32))
499+ self.assertEqual(os.path.isfile(res), True)
500+ 
501+ def test_build_dst_file_name_oversize_multi_byte(self):
502+ """多字节kernel_name按字节数判超长,字符数未超但字节数已超时同样重命名"""
503+ info = AicErrorInfo()
504+ info.kernel_name = '算' * 100 # 100个字符,UTF-8编码为300字节
505+ dump_data_parser = DumpDataParser(str(self.temp), info)
506+ res = dump_data_parser._build_dst_file_name(str(self.temp), 'input', 0, 'float32', np.dtype('float32'))
507+ file_name = os.path.basename(res)
508+ assert len(os.fsencode(file_name)) <= Constant.MAX_FILE_NAME_LEN
509+ assert file_name[:-len('.npy')].isdigit()
510+ np.save(res, np.zeros(2, dtype=np.float32))
511+ self.assertEqual(os.path.isfile(res), True)
512+ 
513+ def test_build_dst_file_name_oversize_bin_keeps_suffix(self):
514+ """超长的bin结果同样保留.bin后缀,保证下游按裸字节读取"""
515+ info = AicErrorInfo()
516+ info.kernel_name = 'a' * 260
517+ dump_data_parser = DumpDataParser(str(self.temp), info)
518+ res = dump_data_parser._build_dst_file_name(str(self.temp), 'input', 0, 'hifloat8', None)
519+ assert os.path.basename(res).endswith('.bin')
520+ 
521+ def test_gen_random_numeric_name(self, mocker):
522+ """随机数字串与已有文件冲突时重新生成"""
523+ dump_data_parser = DumpDataParser(str(self.temp), AicErrorInfo())
524+ self.temp.joinpath('1111111111111111.npy').touch()
525+ mocker.patch('random.randint', side_effect=[1111111111111111, 2222222222222222])
526+ res = dump_data_parser._gen_random_numeric_name(str(self.temp), '.npy')
527+ self.assertEqual(res, '2222222222222222.npy')
528+ 
529+ def test_record_mapping_append(self):
530+ """多条映射追加写入同一份mapping.csv"""
531+ dump_data_parser = DumpDataParser(str(self.temp), AicErrorInfo())
532+ dump_data_parser._record_mapping(str(self.temp), '1111111111111111.npy', 'long_name_a.npy')
533+ dump_data_parser._record_mapping(str(self.temp), '2222222222222222.bin', 'long_name_b.bin')
534+ mapping_text = self.temp.joinpath(Constant.MAPPING_CSV_FILE).read_text()
535+ self.assertIn(mapping_text, '1111111111111111.npy,long_name_a.npy')
536+ self.assertIn(mapping_text, '2222222222222222.bin,long_name_b.bin')
537+ 
538+ def test_load_name_mapping(self):
539+ """读取dump目录下的mapping.csv,构造 原名 -> 映射名 的查找字典"""
540+ dump_data_parser = DumpDataParser(str(self.temp), AicErrorInfo())
541+ self.temp.joinpath(Constant.MAPPING_CSV_FILE).write_text(
542+ '1234567890123456,long_name_a\nbad_line\n')
543+ self.assertEqual(dump_data_parser._load_name_mapping(), {'long_name_a': '1234567890123456'})
544+ 
545+ def test_load_name_mapping_not_exist(self):
546+ dump_data_parser = DumpDataParser(str(self.temp), AicErrorInfo())
547+ self.assertEqual(dump_data_parser._load_name_mapping(), {})
548+ 
549+ def test_parse_matches_by_mapped_name(self, mocker):
550+ """dump目录下只有随机名文件 + mapping.csv时,按data_name反查映射名命中"""
551+ data_name = 'a' * 250 + '.42.1.1726159207469285'
552+ rename = '1234567890123456'
553+ dump_dir = self.temp.joinpath('dump')
554+ dump_dir.mkdir(parents=True, exist_ok=True)
555+ dump_dir.joinpath(rename).touch()
556+ dump_dir.joinpath(Constant.MAPPING_CSV_FILE).write_text(f'{rename},{data_name}\n')
557+ info = AicErrorInfo()
558+ info.node_name = 'GatherV2'
559+ info.data_name = data_name
560+ dump_data_parser = DumpDataParser(str(dump_dir), info)
561+ mocker.patch.object(dump_data_parser, 'parse_dump_data', return_value='')
562+ dump_data_parser.parse()
563+ self.assertEqual(info.dump_file, [str(dump_dir.joinpath(rename))])
564+ 
565+ def test_parse_skips_mapping_csv(self, mocker):
566+ """mapping.csv不会被当成dump文件解析"""
567+ dump_dir = self.temp.joinpath('dump')
568+ dump_dir.mkdir(parents=True, exist_ok=True)
569+ dump_dir.joinpath('GatherV2.1.1.123').touch()
570+ dump_dir.joinpath(Constant.MAPPING_CSV_FILE).write_text('1234,GatherV2.mapping\n')
571+ info = AicErrorInfo()
572+ info.node_name = 'GatherV2'
573+ dump_data_parser = DumpDataParser(str(dump_dir), info)
574+ mocker.patch.object(dump_data_parser, 'parse_dump_data', return_value='')
575+ dump_data_parser.parse()
576+ self.assertEqual(info.dump_file, [str(dump_dir.joinpath('GatherV2.1.1.123'))])
@@ -16,6 +16,16 @@
16# limitations under the License.16# limitations under the License.
17# ----------------------------------------------------------------------------17# ----------------------------------------------------------------------------
18 18 
19+import csv
20+import os
21+import pytest
22+import sys
23+import shutil
24+import subprocess
25+from unittest import mock
26+from unittest.mock import Mock
27+from pathlib import Path
28+ 
19from ms_interface import utils29from ms_interface import utils
20from ms_interface.constant import Constant30from ms_interface.constant import Constant
21from ms_interface.constant import ModeCustom31from ms_interface.constant import ModeCustom
@@ -26,17 +36,6 @@ from ms_interface.single_op_test_frame.common.ascend_tbe_op import AscendOpKerne
26from ms_interface.run_dirty_ub import run_dirty_ub_tik36from ms_interface.run_dirty_ub import run_dirty_ub_tik
27 37 
28from conftest import MSAICERR_PATH, CommonAssert38from conftest import MSAICERR_PATH, CommonAssert
29-import os
30-import pytest
31-import sys
32-import shutil
33-from unittest import mock
34-from unittest.mock import Mock
35-from pathlib import Path
36-import subprocess
37-te = Mock(name="te")
38-te.__name__ = "te"
39-sys.modules['te'] = te
40sys.path.append(MSAICERR_PATH)39sys.path.append(MSAICERR_PATH)
41 40 
42cur_abspath = os.path.dirname(__file__)41cur_abspath = os.path.dirname(__file__)
@@ -142,6 +141,50 @@ class TestUtilsMethods(CommonAssert):
142 res = utils.get_inquire_result(['xxx'], 'asfdd', match_dict=True)141 res = utils.get_inquire_result(['xxx'], 'asfdd', match_dict=True)
143 assert res == []142 assert res == []
144 143 
144+ def test_parse_name_mapping_csv(self, tmp_path):
145+ """
146+ 测试超长文件名的mapping.csv解析为 原名 -> 映射名,非法行被忽略
147+ """
148+ csv_path = tmp_path.joinpath(Constant.MAPPING_CSV_FILE)
149+ csv_path.write_text("1234567890123456,long_name_a\n"
150+ "6543210987654321,long_name_b\n"
151+ "invalid_line_without_comma\n")
152+ res = utils.parse_name_mapping_csv(str(csv_path))
153+ self.assertEqual(res, {"long_name_a": "1234567890123456",
154+ "long_name_b": "6543210987654321"})
155+ 
156+ def test_parse_name_mapping_csv_not_exist(self, tmp_path):
157+ self.assertEqual(utils.parse_name_mapping_csv(""), {})
158+ self.assertEqual(utils.parse_name_mapping_csv(
159+ str(tmp_path.joinpath("not_exist.csv"))), {})
160+ 
161+ def test_parse_name_mapping_csv_read_failed(self, tmp_path, mocker):
162+ """
163+ 测试mapping.csv读取异常时返回空字典而非向上抛异常
164+ """
165+ csv_path = tmp_path.joinpath(Constant.MAPPING_CSV_FILE)
166+ csv_path.write_text("1234567890123456,long_name_a\n")
167+ mocker.patch("csv.reader", side_effect=csv.Error("line contains NUL"))
168+ self.assertEqual(utils.parse_name_mapping_csv(str(csv_path)), {})
169+ 
170+ def test_parse_name_mapping_csv_invalid_encoding(self, tmp_path):
171+ """
172+ 测试mapping.csv含非utf-8字节时返回空字典而非抛UnicodeDecodeError
173+ """
174+ csv_path = tmp_path.joinpath(Constant.MAPPING_CSV_FILE)
175+ csv_path.write_bytes(b"1234567890123456,long_name_a\n"
176+ b"6543210987654321,long_name_\xff\xfe\n")
177+ self.assertEqual(utils.parse_name_mapping_csv(str(csv_path)), {})
178+ 
179+ def test_parse_name_mapping_csv_utf8_name(self, tmp_path):
180+ """
181+ 测试多字节算子名不受locale影响,按utf-8解析成功
182+ """
183+ csv_path = tmp_path.joinpath(Constant.MAPPING_CSV_FILE)
184+ csv_path.write_bytes("1234567890123456,算子名称\n".encode("utf-8"))
185+ self.assertEqual(utils.parse_name_mapping_csv(str(csv_path)),
186+ {"算子名称": "1234567890123456"})
187+ 
145 @pytest.mark.skip188 @pytest.mark.skip
146 def test_run_dirty_ub(self, mocker):189 def test_run_dirty_ub(self, mocker):
147 temp_dir = Path(cur_abspath).joinpath("../test_run_dirty_ub")190 temp_dir = Path(cur_abspath).joinpath("../test_run_dirty_ub")