已合并
rdv-support #931
sujunwei3创建于 6月1日
rdv-support #931
已合并
sujunwei3创建于 6月1日
4 个文件变更+1509-0
@@ -57,6 +57,7 @@ option(OP_GRAPH_UT "Enable graph ut" OFF)
57option(OP_KERNEL_UT "Enable kernel ut" OFF)57option(OP_KERNEL_UT "Enable kernel ut" OFF)
58option(OP_KERNEL_AICPU_UT "Enable aicpu kernel ut" OFF)58option(OP_KERNEL_AICPU_UT "Enable aicpu kernel ut" OFF)
59option(UT_TEST_ALL "Enable all ut" OFF)59option(UT_TEST_ALL "Enable all ut" OFF)
60+option(DOWNLOAD_OPS_TEST_KIT "Download ops-test-kit repository" OFF)
60set(BISHENG_FLAGS "" CACHE STRING "bisheng compiler flags")61set(BISHENG_FLAGS "" CACHE STRING "bisheng compiler flags")
61set(KERNEL_TEMPLATE_INPUT "" CACHE STRING " kernel template input")62set(KERNEL_TEMPLATE_INPUT "" CACHE STRING " kernel template input")
62set(BUILD_MODE "" CACHE STRING "build mode -O0/O1/O2/O3")63set(BUILD_MODE "" CACHE STRING "build mode -O0/O1/O2/O3")
@@ -110,6 +111,9 @@ endif()
110# Cmake Compile配置111# Cmake Compile配置
111set(CMAKE_CXX_STANDARD 17 CACHE STRING "c++17 is needed for this project")112set(CMAKE_CXX_STANDARD 17 CACHE STRING "c++17 is needed for this project")
112include(cmake/third_party/opbase.cmake) # 放在dependencies.cmake前面113include(cmake/third_party/opbase.cmake) # 放在dependencies.cmake前面
114+if(DOWNLOAD_OPS_TEST_KIT)
115+ include(cmake/third_party/ops_test_kit.cmake)
116+endif()
113include(cmake/dependencies.cmake)117include(cmake/dependencies.cmake)
114include(cmake/variables.cmake)118include(cmake/variables.cmake)
115include(cmake/opbuild.cmake)119include(cmake/opbuild.cmake)
@@ -0,0 +1,57 @@
1+# ----------------------------------------------------------------------------
2+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+# CANN Open Software License Agreement Version 2.0 (the "License").
5+# Please refer to the License for details. You may not use this file except in compliance with the License.
6+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+# See LICENSE in the root of the software repository for the full text of the License.
9+# ----------------------------------------------------------------------------
10+set(OPS_TEST_KIT_TAG_ID master)
11+set(OPS_TEST_KIT_PKG ops-test-kit.tar.gz)
12+ 
13+if(EXISTS "${PROJECT_SOURCE_DIR}/build/third_party/ops-test-kit")
14+ get_filename_component(OPS_TEST_KIT_SOURCE_PATH
15+ ${PROJECT_SOURCE_DIR}/build/third_party/ops-test-kit REALPATH)
16+ message(STATUS "Find ops-test-kit source dir: ${OPS_TEST_KIT_SOURCE_PATH}")
17+elseif(EXISTS "${CANN_3RD_LIB_PATH}/ops-test-kit")
18+ get_filename_component(OPS_TEST_KIT_SOURCE_PATH
19+ ${CANN_3RD_LIB_PATH}/ops-test-kit REALPATH)
20+ message(STATUS "Find ops-test-kit source dir: ${OPS_TEST_KIT_SOURCE_PATH}")
21+else()
22+ if(EXISTS "${CANN_3RD_LIB_PATH}/pkg/${OPS_TEST_KIT_PKG}")
23+ set(OPS_TEST_KIT_URL "file://${CANN_3RD_LIB_PATH}/pkg/${OPS_TEST_KIT_PKG}")
24+ message(STATUS "[ThirdPartyLib][ops-test-kit] found in ${OPS_TEST_KIT_URL}.")
25+ include(FetchContent)
26+ FetchContent_Declare(
27+ ops_test_kit
28+ URL ${OPS_TEST_KIT_URL}
29+ SOURCE_DIR ${PROJECT_SOURCE_DIR}/build/third_party/ops-test-kit
30+ )
31+ else()
32+ execute_process(
33+ COMMAND git remote get-url origin
34+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
35+ OUTPUT_VARIABLE GIT_REMOTE_URL
36+ OUTPUT_STRIP_TRAILING_WHITESPACE
37+ ERROR_QUIET
38+ )
39+ if(GIT_REMOTE_URL MATCHES "^git@|^ssh://")
40+ set(OPS_TEST_KIT_GIT_URL "git@gitcode.com:cann/ops-test-kit.git")
41+ message(STATUS "[ThirdPartyLib][ops-test-kit] using SSH protocol: ${OPS_TEST_KIT_GIT_URL}")
42+ else()
43+ set(OPS_TEST_KIT_GIT_URL "https://gitcode.com/cann/ops-test-kit.git")
44+ message(STATUS "[ThirdPartyLib][ops-test-kit] using HTTPS protocol: ${OPS_TEST_KIT_GIT_URL}")
45+ endif()
46+ include(FetchContent)
47+ FetchContent_Declare(
48+ ops_test_kit
49+ GIT_REPOSITORY ${OPS_TEST_KIT_GIT_URL}
50+ GIT_TAG ${OPS_TEST_KIT_TAG_ID}
51+ GIT_PROGRESS TRUE
52+ SOURCE_DIR ${PROJECT_SOURCE_DIR}/build/third_party/ops-test-kit
53+ )
54+ endif()
55+ FetchContent_Populate(ops_test_kit)
56+ set(OPS_TEST_KIT_SOURCE_PATH ${PROJECT_SOURCE_DIR}/build/third_party/ops-test-kit)
57+endif()
@@ -0,0 +1,819 @@
1+#!/bin/bash
2+# -----------------------------------------------------------------------------------------------------------
3+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
4+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
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.
7+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
8+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
9+# See LICENSE in the root of the software repository for the full text of the License.
10+# -----------------------------------------------------------------------------------------------------------
11+ 
12+set -euo pipefail
13+ 
14+declare -A SOC_TO_ST_ARCH
15+SOC_TO_ST_ARCH=(["ascend910b"]="arch22" ["ascend950"]="arch35")
16+ 
17+dotted_line="----------------------------------------------------------------"
18+print_msg() {
19+ local msg="$1"
20+ local date_time
21+ date_time=$(date +%Y-%m-%d/%H.%M.%S)
22+ echo "[INFO]${date_time}: ${msg}" >&2
23+}
24+ 
25+print_error() {
26+ echo >&2
27+ echo "${dotted_line}" >&2
28+ local msg="$1"
29+ echo -e "\033[31m[ERROR] ${msg}\033[0m" >&2
30+ echo "${dotted_line}" >&2
31+ echo >&2
32+}
33+ 
34+print_success() {
35+ echo >&2
36+ echo "${dotted_line}" >&2
37+ local msg="$1"
38+ echo -e "\033[32m[SUCCESS] ${msg}\033[0m" >&2
39+ echo "${dotted_line}" >&2
40+ echo >&2
41+}
42+ 
43+print_warning() {
44+ echo >&2
45+ echo "${dotted_line}" >&2
46+ local msg="$1"
47+ echo -e "\033[33m[WARNING] ${msg}\033[0m" >&2
48+ echo "${dotted_line}" >&2
49+ echo >&2
50+}
51+ 
52+get_op_categories() {
53+ local cmake_file="${framework_path}/cmake/variables.cmake"
54+ local categories=""
55+
56+ if [[ -f "${cmake_file}" ]]; then
57+ categories=$(grep "OP_CATEGORY_LIST" "${cmake_file}" | \
58+ sed -n 's/set(OP_CATEGORY_LIST "\(.*\)")/\1/p' | \
59+ tr -d '"')
60+ else
61+ categories="image objdetect"
62+ fi
63+
64+ categories="${categories} common"
65+ echo "${categories}"
66+}
67+ 
68+extract_op_from_path() {
69+ local file_path="$1"
70+ local op_categories="$2"
71+ local op_name=""
72+
73+ local rel_path="${file_path#${framework_path}/}"
74+ local parts=(${rel_path//\// })
75+
76+ if [[ ${#parts[@]} -ge 2 ]]; then
77+ local first_dir="${parts[0]}"
78+ local second_dir="${parts[1]}"
79+
80+ local is_category=0
81+ for cat in ${op_categories}; do
82+ if [[ "${first_dir}" == "${cat}" ]]; then
83+ is_category=1
84+ break
85+ fi
86+ done
87+
88+ if [[ ${is_category} -eq 1 ]]; then
89+ if [[ "${second_dir}" == "common" ]]; then
90+ op_name="${first_dir}.common"
91+ else
92+ op_name="${second_dir}"
93+ fi
94+ elif [[ "${first_dir}" == "experimental" && ${#parts[@]} -ge 3 ]]; then
95+ local exp_type="${parts[1]}"
96+ local exp_name="${parts[2]}"
97+ for cat in ${op_categories}; do
98+ if [[ "${exp_type}" == "${cat}" ]]; then
99+ if [[ "${exp_name}" == "common" ]]; then
100+ op_name="${exp_type}.common"
101+ else
102+ op_name="${exp_name}"
103+ fi
104+ break
105+ fi
106+ done
107+ fi
108+ fi
109+
110+ if [[ -n "${op_name}" ]]; then
111+ echo "${op_name}"
112+ fi
113+}
114+ 
115+parse_ops_from_filelist() {
116+ local pr_filelist="$1"
117+
118+ if [[ ! -f "${pr_filelist}" ]]; then
119+ print_error "pr_filelist not found: ${pr_filelist}"
120+ return 1
121+ fi
122+
123+ local op_categories=$(get_op_categories)
124+ local changed_files=$(cat "${pr_filelist}" | grep -v '^$' | grep -v '^#' || echo "")
125+
126+ if [[ -z "${changed_files}" ]]; then
127+ print_msg "No changed files in pr_filelist"
128+ return 0
129+ fi
130+
131+ local ops_set=""
132+ while IFS= read -r file_line; do
133+ [[ -z "${file_line}" ]] && continue
134+ file_line=$(echo "${file_line}" | sed 's/^[MADRC]\t//')
135+ local op_name=$(extract_op_from_path "${file_line}" "${op_categories}")
136+ if [[ -n "${op_name}" ]]; then
137+ if [[ -z "${ops_set}" ]]; then
138+ ops_set="${op_name}"
139+ elif [[ ",${ops_set}," != *",${op_name},"* ]]; then
140+ ops_set="${ops_set},${op_name}"
141+ fi
142+ fi
143+ done <<< "${changed_files}"
144+
145+ echo "${ops_set}"
146+}
147+ 
148+merge_ops_lists() {
149+ local list1="$1"
150+ local list2="$2"
151+ local merged=""
152+
153+ for op in ${list1//,/ }; do
154+ if [[ -z "${merged}" ]]; then
155+ merged="${op}"
156+ elif [[ ",${merged}," != *",${op},"* ]]; then
157+ merged="${merged},${op}"
158+ fi
159+ done
160+
161+ for op in ${list2//,/ }; do
162+ if [[ -z "${merged}" ]]; then
163+ merged="${op}"
164+ elif [[ ",${merged}," != *",${op},"* ]]; then
165+ merged="${merged},${op}"
166+ fi
167+ done
168+
169+ echo "${merged}"
170+}
171+ 
172+usage() {
173+ echo "Usage: bash ops_st_test.sh [--soc_version=ascend950] [--ops=op1,op2,op3] [--test_type=kernel,aclnn,e2e] [--pr_filelist=pr_filelist.txt]"
174+ echo " bash ops_st_test.sh pr_filelist.txt"
175+ echo "Options:"
176+ echo " --soc_version (Optional) Specify soc version. Supported: ascend910b, ascend950. If not specified, auto-detect via 'asys info -r=status'."
177+ echo " --ops (Optional) Specify operators to test (comma-separated). If not specified, extract from git diff."
178+ echo " --test_type (Optional) Specify test types to run (comma-separated). Supported: kernel, aclnn, e2e. Default: all types."
179+ echo " --pr_filelist (Optional) Path to file containing list of changed files (one per line). If not specified, extract from git diff."
180+ echo " --case_path (Optional) Custom base path for test cases. If specified, st_path will be {case_path}/${op_type}/${op_name}"
181+ echo "Examples:"
182+ echo " bash ops_st_test.sh"
183+ echo " bash ops_st_test.sh pr_filelist.txt"
184+ echo " bash ops_st_test.sh --soc_version=ascend950"
185+ echo " bash ops_st_test.sh --pr_filelist=pr_filelist.txt"
186+ echo " bash ops_st_test.sh --soc_version=ascend910b --ops=resize_bilinear_v2,roi_align"
187+ echo " bash ops_st_test.sh --soc_version=ascend950 --test_type=kernel"
188+ echo " bash ops_st_test.sh --soc_version=ascend950 --test_type=kernel,e2e"
189+}
190+ 
191+get_changed_ops() {
192+ local base_branch="master"
193+ local changed_files
194+ 
195+ changed_files=$(git diff --name-only "${base_branch}...HEAD" 2>/dev/null || git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
196+ 
197+ if [[ -z "${changed_files}" ]]; then
198+ print_msg "No changed files detected"
199+ return 0
200+ fi
201+ 
202+ local tmp_file="${build_path}/tmp/git_diff_filelist.txt"
203+ mkdir -p "${build_path}/tmp"
204+ echo "${changed_files}" > "${tmp_file}"
205+ local result=$(parse_ops_from_filelist "${tmp_file}")
206+ rm -f "${tmp_file}"
207+ 
208+ if [[ -z "${result}" ]]; then
209+ print_msg "No ops detected from changed files"
210+ return 0
211+ fi
212+ 
213+ echo "${result}"
214+}
215+ 
216+download_ops_test_kit() {
217+ print_msg "Preparing build environment..."
218+ 
219+ mkdir -p "${build_path}"
220+ 
221+ if [[ ! -d "${ttk_path}" ]]; then
222+ print_msg "Downloading ops-test-kit via cmake..."
223+ print_msg "ASCEND_HOME_PATH: ${ASCEND_HOME_PATH}"
224+ (cd "${build_path}" && cmake -DDOWNLOAD_OPS_TEST_KIT=ON -DASCEND_INSTALL_PATH="${ASCEND_HOME_PATH}" "${framework_path}") || {
225+ print_error "Failed to download ops-test-kit via cmake"
226+ exit 1
227+ }
228+ print_msg "ops-test-kit downloaded successfully"
229+ else
230+ print_msg "ops-test-kit already exists, skipping download"
231+ fi
232+ 
233+ if [[ ! -d "${ttk_path}" ]]; then
234+ print_error "ttk_path does not exist after download"
235+ exit 1
236+ fi
237+}
238+ 
239+find_op_code_path() {
240+ local op_name="$1"
241+ local code_path=$(find "${framework_path}" -type d -name "${op_name}" -not -path "*/build/*" -not -path "*/.git/*" -not -path "*/build_out/*" | head -1)
242+
243+ if [[ -z "${code_path}" ]]; then
244+ return 1
245+ fi
246+
247+ echo "${code_path}"
248+}
249+ 
250+get_op_type() {
251+ local code_path="$1"
252+ local subdir_path=$(realpath "${code_path}")
253+ local op_type=$(basename "$(dirname "${subdir_path}")")
254+ echo "${op_type}"
255+}
256+ 
257+find_test_cases() {
258+ local op_name="$1"
259+ local op_type="$2"
260+ local arch="$3"
261+ local test_case_files=()
262+
263+ local st_path
264+ if [[ -n "${case_path}" ]]; then
265+ st_path="${case_path}/${op_type}/${op_name}/"
266+ else
267+ st_path="${framework_path}/${op_type}/${op_name}/tests/st"
268+ fi
269+
270+ if [[ ! -d "${st_path}" ]]; then
271+ [[ "${DEBUG_DEPENDENCIES:-}" == "TRUE" ]] && print_msg "No st test directory found for ${op_name} at ${st_path}"
272+ return 0
273+ fi
274+
275+ local all_prefixes=("ttk_kernel" "ttk_aclnn" "ttk_e2e")
276+ local search_prefixes=()
277+
278+ if [[ -n "${test_type_list}" ]]; then
279+ IFS=',' read -r -a input_types <<< "${test_type_list}"
280+ for input_type in "${input_types[@]}"; do
281+ # 兼容旧参数 pta,转换为 e2e
282+ if [[ "${input_type}" == "pta" ]]; then
283+ search_prefixes+=("ttk_e2e")
284+ else
285+ search_prefixes+=("ttk_${input_type}")
286+ fi
287+ done
288+ else
289+ search_prefixes=("${all_prefixes[@]}")
290+ fi
291+
292+ # 第一步:查找通用用例(st/ttk_kernel_*.csv 等)
293+ for prefix in "${search_prefixes[@]}"; do
294+ local csv_files=$(find "${st_path}" -maxdepth 1 -name "${prefix}_*.csv" -type f 2>/dev/null)
295+ for csv_file in ${csv_files}; do
296+ local test_type="${prefix#ttk_}"
297+ test_case_files+=("${test_type}:${csv_file}")
298+ done
299+ done
300+
301+ # 第二步:查找架构专用用例(st/arch35/ 或 st/arch22/)
302+ if [[ -n "${arch}" ]]; then
303+ local arch_path="${st_path}/${arch}"
304+ if [[ -d "${arch_path}" ]]; then
305+ for prefix in "${search_prefixes[@]}"; do
306+ local csv_files=$(find "${arch_path}" -maxdepth 1 -name "${prefix}_*.csv" -type f 2>/dev/null)
307+ for csv_file in ${csv_files}; do
308+ local test_type="${prefix#ttk_}"
309+ test_case_files+=("${test_type}:${csv_file}")
310+ done
311+ done
312+ fi
313+ fi
314+
315+ echo "${test_case_files[*]}"
316+}
317+ 
318+get_ops_test_path() {
319+ local op_name="$1"
320+ local op_type="$2"
321+ local ops_test_path="${framework_path}/${op_type}/${op_name}/tests"
322+
323+ if [[ ! -d "${ops_test_path}" ]]; then
324+ print_msg "No tests directory found for ${op_name} at ${op_type}/${op_name}/tests"
325+ return 1
326+ fi
327+
328+ echo "${ops_test_path}"
329+}
330+ 
331+check_precision_status() {
332+ local result_csv="$1"
333+ local op_name="$2"
334+ local testcase_name="$3"
335+
336+ if [[ ! -f "${result_csv}" ]]; then
337+ print_warning "Result csv file not found: ${result_csv}"
338+ return 1
339+ fi
340+
341+ python3 "${framework_path}/scripts/ci/ops_test_util.py" \
342+ --action=check_precision \
343+ --result_csv="${result_csv}" \
344+ --op_name="${op_name}" \
345+ --testcase_name="${testcase_name}"
346+
347+ return $?
348+}
349+ 
350+check_plugin_assets() {
351+ local plugin_path="$1"
352+ local op_name="$2"
353+
354+ local assets_path="${plugin_path}/assets"
355+
356+ if [[ ! -d "${assets_path}" ]]; then
357+ print_warning "assets directory not found for ${op_name}: ${assets_path}"
358+ return 1
359+ fi
360+
361+ local py_files=$(find "${assets_path}" -maxdepth 1 -name "*.py" -type f 2>/dev/null | head -1)
362+ if [[ -z "${py_files}" ]]; then
363+ print_warning "No .py files found in assets directory for ${op_name}: ${assets_path}"
364+ return 1
365+ fi
366+
367+ return 0
368+}
369+ 
370+run_kernel_test() {
371+ local op_name="$1"
372+ local test_csv="$2"
373+ local ops_test_path="$3"
374+
375+ if [[ ! -f "${test_csv}" ]]; then
376+ print_warning "Test csv file not found: ${test_csv}, skipping this test case"
377+ return 0
378+ fi
379+
380+ if [[ ! -d "${ops_test_path}" ]]; then
381+ print_warning "Plugin directory not found: ${ops_test_path}, skipping this test case"
382+ return 0
383+ fi
384+
385+ if ! check_plugin_assets "${ops_test_path}" "${op_name}"; then
386+ return 0
387+ fi
388+
389+ local testcase_name=$(basename "${test_csv}" .csv)
390+ if [[ "${test_csv}" == */arch3[0-9]/* || "${test_csv}" == */arch2[0-9]/* ]]; then
391+ testcase_name="$(basename "$(dirname "${test_csv}")")_${testcase_name}"
392+ fi
393+ local log_op_dir="${log_path}/${op_name}"
394+ mkdir -p "${log_op_dir}"
395+
396+ print_msg "Running kernel test for ${op_name}, testcase: ${testcase_name}"
397+
398+ cd "${ttk_path}"
399+
400+ local cmd="python3 -m ttk kernel -i ${test_csv} -o ${log_op_dir}/${testcase_name}_result.csv --plugin ${ops_test_path} -c --pc=8 --warmup=false"
401+ print_msg "Executing: ${cmd}"
402+
403+ local start_time=$(date +%s)
404+ set +e
405+ ${cmd} 2>&1 | tee "${log_op_dir}/${testcase_name}_run.log" > /dev/null
406+ local test_failed=${PIPESTATUS[0]}
407+ set -e
408+ local end_time=$(date +%s)
409+ local elapsed=$((end_time - start_time))
410+
411+ if [[ ${test_failed} -ne 0 ]]; then
412+ print_error "kernel test failed for ${op_name}, testcase: ${testcase_name}, elapsed: ${elapsed}s"
413+ else
414+ print_msg "kernel test completed for ${op_name}, testcase: ${testcase_name}, elapsed: ${elapsed}s"
415+ fi
416+
417+ local result_csv="${log_op_dir}/${testcase_name}_result.csv"
418+ echo "${result_csv}"
419+
420+ if [[ ${test_failed} -ne 0 ]]; then
421+ return 1
422+ fi
423+}
424+ 
425+run_aclnn_test() {
426+ local op_name="$1"
427+ local test_csv="$2"
428+ local ops_test_path="$3"
429+
430+ local start_time=$(date +%s)
431+ print_warning "aclnn test not implemented yet for ${op_name}"
432+ # TODO: 待确定aclnn测试命令后实现
433+ local end_time=$(date +%s)
434+ local elapsed=$((end_time - start_time))
435+ print_msg "aclnn test elapsed: ${elapsed}s"
436+ return 0
437+}
438+ 
439+run_e2e_test() {
440+ local op_name="$1"
441+ local test_csv="$2"
442+ local ops_test_path="$3"
443+
444+ local start_time=$(date +%s)
445+ print_warning "e2e test not implemented yet for ${op_name}"
446+ # TODO: 待确定e2e测试命令后实现
447+ local end_time=$(date +%s)
448+ local elapsed=$((end_time - start_time))
449+ print_msg "e2e test elapsed: ${elapsed}s"
450+ return 0
451+}
452+ 
453+summarize_op_results() {
454+ local op_name="$1"
455+ local test_type="$2"
456+ local result_csvs="$3"
457+
458+ local summary_file="${log_path}/${test_type}_summary.csv"
459+ local summary_header="op_name,testcase_name,test_type,result_csv,status,precision"
460+
461+ if [[ ! -f "${summary_file}" ]]; then
462+ echo "${summary_header}" > "${summary_file}"
463+ fi
464+
465+ if [[ -z "${result_csvs}" ]]; then
466+ return 0
467+ fi
468+
469+ for result_csv in ${result_csvs}; do
470+ if [[ ! -f "${result_csv}" ]]; then
471+ continue
472+ fi
473+
474+ python3 "${framework_path}/scripts/ci/ops_test_util.py" \
475+ --action=summarize \
476+ --result_csv="${result_csv}" \
477+ --op_name="${op_name}" \
478+ --test_type="${test_type}" \
479+ --summary_file="${summary_file}"
480+ done
481+}
482+ 
483+print_summary_table() {
484+ python3 "${framework_path}/scripts/ci/ops_test_util.py" \
485+ --action=print_table \
486+ --log_path="${log_path}"
487+}
488+ 
489+run_single_op_test() {
490+ local op_name="$1"
491+
492+ print_msg "=== Testing op: ${op_name} ==="
493+
494+ local code_path
495+ code_path=$(find_op_code_path "${op_name}") || true
496+ if [[ -z "${code_path}" ]]; then
497+ print_warning "Cannot find op directory for ${op_name}, skipping"
498+ return 0
499+ fi
500+
501+ local op_type=$(get_op_type "${code_path}")
502+ print_msg "op_type: ${op_type}, op_name: ${op_name}"
503+
504+ local arch="${SOC_TO_ST_ARCH[${soc_version}]:-}"
505+ if [[ -n "${arch}" ]]; then
506+ print_msg "soc_version: ${soc_version}, arch: ${arch}"
507+ fi
508+
509+ local ops_test_path
510+ ops_test_path=$(get_ops_test_path "${op_name}" "${op_type}") || true
511+ if [[ -z "${ops_test_path}" ]]; then
512+ print_msg "No tests directory found, skipping ${op_name}"
513+ return 0
514+ fi
515+
516+ local test_cases=$(find_test_cases "${op_name}" "${op_type}" "${arch}")
517+
518+ if [[ -z "${test_cases}" ]]; then
519+ print_msg "No test cases found for ${op_name}"
520+ return 0
521+ fi
522+
523+ local result_csvs=()
524+ local kernel_csvs=()
525+ local aclnn_csvs=()
526+ local e2e_csvs=()
527+ local test_case_array=(${test_cases})
528+ local result_csv
529+ local testcase_name
530+ local op_error_flag=0
531+
532+ for test_item in "${test_case_array[@]}"; do
533+ local test_type=$(echo "${test_item}" | cut -d':' -f1)
534+ local test_csv=$(echo "${test_item}" | cut -d':' -f2-)
535+ local test_ret=0
536+
537+ case "${test_type}" in
538+ kernel)
539+ result_csv=$(run_kernel_test "${op_name}" "${test_csv}" "${ops_test_path}") || test_ret=$?
540+ if [[ ${test_ret} -ne 0 ]]; then
541+ op_error_flag=1
542+ fi
543+ if [[ -n "${result_csv}" ]]; then
544+ kernel_csvs+=("${result_csv}")
545+ fi
546+ ;;
547+ aclnn)
548+ result_csv=$(run_aclnn_test "${op_name}" "${test_csv}" "${ops_test_path}") || test_ret=$?
549+ if [[ ${test_ret} -ne 0 ]]; then
550+ op_error_flag=1
551+ fi
552+ if [[ -n "${result_csv}" ]]; then
553+ aclnn_csvs+=("${result_csv}")
554+ fi
555+ ;;
556+ e2e)
557+ result_csv=$(run_e2e_test "${op_name}" "${test_csv}" "${ops_test_path}") || test_ret=$?
558+ if [[ ${test_ret} -ne 0 ]]; then
559+ op_error_flag=1
560+ fi
561+ if [[ -n "${result_csv}" ]]; then
562+ e2e_csvs+=("${result_csv}")
563+ fi
564+ ;;
565+ *)
566+ print_warning "Unknown test type: ${test_type}, skipping"
567+ continue
568+ ;;
569+ esac
570+
571+ if [[ -n "${result_csv}" ]]; then
572+ result_csvs+=("${result_csv}")
573+ fi
574+ done
575+
576+ summarize_op_results "${op_name}" "kernel" "${kernel_csvs[*]}"
577+ summarize_op_results "${op_name}" "aclnn" "${aclnn_csvs[*]}"
578+ summarize_op_results "${op_name}" "e2e" "${e2e_csvs[*]}"
579+
580+ local arch="${SOC_TO_ST_ARCH[${soc_version}]:-}"
581+ if [[ -n "${arch}" ]]; then
582+ local merged_dir="${log_path}/${op_name}/${arch}"
583+ mkdir -p "${merged_dir}"
584+ 
585+ local -A type_csvs_map=(
586+ ["kernel"]="${kernel_csvs[*]}"
587+ ["aclnn"]="${aclnn_csvs[*]}"
588+ ["e2e"]="${e2e_csvs[*]}"
589+ )
590+ 
591+ for test_type_name in kernel aclnn e2e; do
592+ local type_csvs_str="${type_csvs_map[${test_type_name}]}"
593+ if [[ -z "${type_csvs_str}" ]]; then
594+ continue
595+ fi
596+ local -a type_csvs=(${type_csvs_str})
597+ if [[ ${#type_csvs[@]} -eq 0 ]]; then
598+ continue
599+ fi
600+ 
601+ local merged_result="${merged_dir}/ttk_${test_type_name}_${op_name}_st_result.csv"
602+ local merged_log="${merged_dir}/ttk_${test_type_name}_${op_name}_st_run.log"
603+ 
604+ local first_csv=1
605+ for csv in "${type_csvs[@]}"; do
606+ if [[ -f "${csv}" ]]; then
607+ if [[ ${first_csv} -eq 1 ]]; then
608+ head -1 "${csv}" > "${merged_result}"
609+ first_csv=0
610+ fi
611+ tail -n +2 "${csv}" >> "${merged_result}"
612+ fi
613+ done
614+ 
615+ : > "${merged_log}"
616+ for csv in "${type_csvs[@]}"; do
617+ local run_log="${csv%_result.csv}_run.log"
618+ if [[ -f "${run_log}" ]]; then
619+ cat "${run_log}" >> "${merged_log}"
620+ rm -f "${run_log}"
621+ fi
622+ done
623+ 
624+ for csv in "${type_csvs[@]}"; do
625+ rm -f "${csv}"
626+ done
627+ 
628+ echo "${op_name}:ttk_${test_type_name}_${op_name}_st:${merged_result}"
629+ done
630+ else
631+ for csv in "${result_csvs[@]}"; do
632+ testcase_name=$(basename "${csv}" _result.csv)
633+ echo "${op_name}:${testcase_name}:${csv}"
634+ done
635+ fi
636+
637+ if [[ ${op_error_flag} -ne 0 ]]; then
638+ return 1
639+ fi
640+}
641+ 
642+parse_args() {
643+ ops_list=""
644+ soc_version=""
645+ test_type_list=""
646+ pr_filelist=""
647+ case_path=""
648+
649+ for arg in "$@"; do
650+ case "${arg}" in
651+ --ops=*)
652+ ops_list="${arg#*=}"
653+ ;;
654+ --soc_version=*)
655+ soc_version="${arg#*=}"
656+ ;;
657+ --test_type=*)
658+ test_type_list="${arg#*=}"
659+ ;;
660+ --pr_filelist=*)
661+ pr_filelist="${arg#*=}"
662+ ;;
663+ --case_path=*)
664+ case_path="${arg#*=}"
665+ ;;
666+ -h|--help)
667+ usage
668+ exit 0
669+ ;;
670+ -*)
671+ print_error "Unknown argument: ${arg}"
672+ usage
673+ exit 1
674+ ;;
675+ *)
676+ if [[ -z "${pr_filelist}" ]]; then
677+ pr_filelist="${arg}"
678+ else
679+ print_error "Multiple pr_filelist arguments: ${pr_filelist} and ${arg}"
680+ usage
681+ exit 1
682+ fi
683+ ;;
684+ esac
685+ done
686+
687+ if [[ -n "${pr_filelist}" && ! -f "${pr_filelist}" ]]; then
688+ print_error "pr_filelist not found: ${pr_filelist}"
689+ exit 1
690+ fi
691+
692+ if [[ -n "${test_type_list}" ]]; then
693+ IFS=',' read -r -a valid_types <<< "kernel,aclnn,e2e"
694+ IFS=',' read -r -a input_types <<< "${test_type_list}"
695+ for input_type in "${input_types[@]}"; do
696+ local found=0
697+ for valid_type in "${valid_types[@]}"; do
698+ if [[ "${input_type}" == "${valid_type}" ]]; then
699+ found=1
700+ break
701+ fi
702+ done
703+ if [[ ${found} -eq 0 ]]; then
704+ print_error "Unsupported test_type: ${input_type}. Supported: kernel, aclnn, e2e"
705+ exit 1
706+ fi
707+ done
708+ fi
709+
710+ local chip_info=$(asys info -r=status 2>/dev/null || echo "")
711+ local detected_soc=""
712+ if echo "${chip_info}" | grep -q "Ascend 950"; then
713+ detected_soc="ascend950"
714+ elif echo "${chip_info}" | grep -q "Ascend 910"; then
715+ detected_soc="ascend910b"
716+ fi
717+
718+ if [[ -z "${soc_version}" ]]; then
719+ if [[ -z "${detected_soc}" ]]; then
720+ print_error "Failed to detect SOC version via 'asys info -r=status'. Current environment does not support auto-detection. Please specify --soc_version manually."
721+ exit 1
722+ fi
723+ soc_version="${detected_soc}"
724+ print_msg "Auto-detected soc_version: ${soc_version}"
725+ else
726+ if [[ "${soc_version}" != "ascend910b" && "${soc_version}" != "ascend950" ]]; then
727+ print_error "Unsupported soc_version: ${soc_version}. Supported: ascend910b, ascend950"
728+ exit 1
729+ fi
730+
731+ if [[ -n "${detected_soc}" && "${detected_soc}" != "${soc_version}" ]]; then
732+ print_error "SOC version mismatch: specified '${soc_version}' but detected '${detected_soc}' from 'asys info -r=status'"
733+ exit 1
734+ fi
735+ fi
736+
737+ print_msg "soc_version: ${soc_version}"
738+ print_msg "ops_list: ${ops_list:-'auto detect from git diff or pr_filelist'}"
739+ print_msg "test_type_list: ${test_type_list:-'all types'}"
740+ if [[ -n "${pr_filelist}" ]]; then
741+ print_msg "pr_filelist: ${pr_filelist}"
742+ fi
743+ if [[ -n "${case_path}" ]]; then
744+ print_msg "case_path: ${case_path}"
745+ fi
746+}
747+ 
748+framework_path="$(cd "$(dirname "$0")/../.." && pwd)"
749+build_path="${framework_path}/build"
750+log_path="${framework_path}/st/log"
751+ttk_path="${build_path}/third_party/ops-test-kit"
752+ 
753+parse_args "$@"
754+ 
755+rm -rf "${log_path:?}"/*
756+mkdir -p "${log_path}"
757+ 
758+download_ops_test_kit
759+ 
760+if [[ -n "${ops_list}" && -z "${pr_filelist}" ]]; then
761+ print_msg "Using ops from --ops parameter: ${ops_list}"
762+elif [[ -z "${ops_list}" && -n "${pr_filelist}" ]]; then
763+ print_msg "Extracting ops from pr_filelist..."
764+ print_msg "pr_filelist content:"
765+ cat "${pr_filelist}" | grep -v '^$' | grep -v '^#' | sed 's/^[MADRC]\t//' >&2
766+ ops_list=$(parse_ops_from_filelist "${pr_filelist}")
767+elif [[ -n "${ops_list}" && -n "${pr_filelist}" ]]; then
768+ print_msg "Merging ops from pr_filelist and --ops parameter..."
769+ print_msg "--ops input: ${ops_list}"
770+ print_msg "pr_filelist content:"
771+ cat "${pr_filelist}" | grep -v '^$' | grep -v '^#' | sed 's/^[MADRC]\t//' >&2
772+ ops_from_filelist=$(parse_ops_from_filelist "${pr_filelist}")
773+ ops_list=$(merge_ops_lists "${ops_from_filelist}" "${ops_list}")
774+else
775+ print_msg "Extracting ops from git diff..."
776+ ops_list=$(get_changed_ops)
777+fi
778+ 
779+if [[ -z "${ops_list}" ]]; then
780+ print_msg "No ops to test"
781+ exit 0
782+fi
783+ 
784+print_msg "Ops to test: ${ops_list}"
785+ 
786+IFS=',' read -r -a op_name_array <<< "${ops_list}"
787+ 
788+all_result_csvs=()
789+result_flag=0
790+for op_name in "${op_name_array[@]}"; do
791+ op_results=$(run_single_op_test "${op_name}") || result_flag=1
792+ if [[ -n "${op_results}" ]]; then
793+ while IFS= read -r line; do
794+ all_result_csvs+=("${line}")
795+ done <<< "${op_results}"
796+ fi
797+done
798+ 
799+print_msg "=== Starting precision check for all test cases ==="
800+precision_flag=0
801+for result_info in "${all_result_csvs[@]}"; do
802+ op_name=$(echo "${result_info}" | cut -d':' -f1)
803+ testcase_name=$(echo "${result_info}" | cut -d':' -f2)
804+ result_csv=$(echo "${result_info}" | cut -d':' -f3)
805+ check_precision_status "${result_csv}" "${op_name}" "${testcase_name}" || precision_flag=1
806+done
807+ 
808+print_summary_table
809+ 
810+if [[ ${result_flag} -ne 0 ]]; then
811+ print_error "Some tests failed, please check the log for details."
812+ exit 1
813+elif [[ ${precision_flag} -ne 0 ]]; then
814+ print_error "Some precision checks failed, please check the details above."
815+ exit 1
816+else
817+ print_success "All tests and precision checks passed."
818+ exit 0
819+fi
@@ -0,0 +1,629 @@
1+#!/usr/bin/env python3
2+# -*- coding: utf-8 -*-
3+# ----------------------------------------------------------------------------
4+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
5+# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
6+# CANN Open Software License Agreement Version 2.0 (the "License").
7+# Please refer to the License for details. You may not use this file except in compliance with the License.
8+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
9+# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
10+# See LICENSE in the root of the software repository for the full text of the License.
11+# ----------------------------------------------------------------------------
12+import argparse
13+import csv
14+import logging
15+import os
16+import re
17+import sys
18+from dataclasses import dataclass
19+ 
20+# 日志配置:错误/警告输出到stderr
21+logging.basicConfig(
22+ level=logging.INFO,
23+ format='[%(levelname)s] %(message)s',
24+ stream=sys.stderr
25+)
26+logger = logging.getLogger(__name__)
27+ 
28+# 表格专用logger:输出到stdout,保持表格格式
29+table_logger = logging.getLogger('table_output')
30+table_handler = logging.StreamHandler(sys.stdout)
31+table_handler.setFormatter(logging.Formatter('%(message)s'))
32+table_logger.addHandler(table_handler)
33+table_logger.setLevel(logging.INFO)
34+table_logger.propagate = False
35+ 
36+ 
37+@dataclass
38+class SummaryRowData:
39+ """汇总行数据封装
40+
41+ 用于封装写入汇总时所需的参数,避免函数参数过多
42+ """
43+ rows: list
44+ op_name: str
45+ test_type: str
46+ result_csv: str
47+ summary_file: str
48+ precision_idx: int
49+ dyn_idx: int
50+ cst_idx: int
51+ bin_idx: int
52+ 
53+ 
54+@dataclass
55+class SingleRowData:
56+ """单行数据封装
57+
58+ 用于封装写入单行时所需的参数
59+ """
60+ out_f: object
61+ row: list
62+ op_name: str
63+ test_type: str
64+ result_csv: str
65+ precision_idx: int
66+ dyn_idx: int
67+ cst_idx: int
68+ bin_idx: int
69+ 
70+ 
71+@dataclass
72+class TableRowData:
73+ """表格行数据封装
74+
75+ 用于封装打印表格行时所需的参数
76+ """
77+ op: str
78+ testcase: str
79+ test_type: str
80+ status: str
81+ dyn_prec: str
82+ cst_prec: str
83+ bin_prec: str
84+ 
85+ 
86+class OpTestUtil:
87+ """OPS测试工具主类
88+
89+ 整合了精度检查、结果汇总和表格打印功能
90+
91+ 日志设计:
92+ - logger: 用于错误/警告信息,输出到stderr
93+ - table_logger: 用于表格可视化输出,输出到stdout
94+ """
95+
96+ col_widths = {
97+ 'op': 20,
98+ 'testcase': 70,
99+ 'type': 8,
100+ 'status': 8,
101+ 'dyn_prec': 9,
102+ 'cst_prec': 9,
103+ 'bin_prec': 9
104+ }
105+
106+ def __init__(self):
107+ pass
108+
109+ @staticmethod
110+ def check(result_csv, op_name, testcase_name):
111+ """检查精度状态
112+
113+ Args:
114+ result_csv: 结果CSV文件路径
115+ op_name: 算子名称
116+ testcase_name: 测试用例名称
117+
118+ Returns:
119+ int: 0表示全部通过,1表示有失败用例
120+ """
121+ if not OpTestUtil._validate_file(result_csv):
122+ return 1
123+
124+ try:
125+ with open(result_csv, 'r') as f:
126+ reader = csv.reader(f)
127+ headers = next(reader)
128+ precision_idx = OpTestUtil._find_precision_column(headers)
129+
130+ if precision_idx == -1:
131+ logger.warning("precision_status column not found in result csv")
132+ return 1
133+
134+ total_cases, passed_cases = OpTestUtil._count_results(reader, precision_idx)
135+
136+ if total_cases - passed_cases > 0:
137+ return 1
138+ return 0
139+ except Exception as e:
140+ logger.error(f"Failed to parse result csv: {e}")
141+ return 1
142+
143+ @staticmethod
144+ def summarize(result_csv, op_name, test_type, summary_file):
145+ """汇总测试结果
146+
147+ Args:
148+ result_csv: 结果CSV文件路径
149+ op_name: 算子名称
150+ test_type: 测试类型 (kernel/aclnn/e2e)
151+ summary_file: 汇总CSV文件路径
152+ """
153+ if not os.path.exists(result_csv):
154+ return
155+
156+ OpTestUtil._ensure_summary_file(summary_file)
157+ OpTestUtil._process_csv(result_csv, op_name, test_type, summary_file)
158+
159+ @staticmethod
160+ def print_table(log_path):
161+ """打印可视化表格
162+
163+ Args:
164+ log_path: 日志目录路径
165+ """
166+ summary_files = ["kernel_summary.csv", "aclnn_summary.csv", "e2e_summary.csv"]
167+
168+ all_rows = OpTestUtil._load_summary_data(log_path, summary_files)
169+
170+ if not all_rows:
171+ logger.warning("No summary data found")
172+ return
173+
174+ total = len(all_rows)
175+ passed = sum(1 for r in all_rows if r.get('status', '').upper() == 'PASS')
176+ failed = total - passed
177+
178+ OpTestUtil._print_title_section()
179+
180+ if failed > 0:
181+ OpTestUtil._print_failed_rows(all_rows)
182+
183+ OpTestUtil._print_summary(total, passed, failed)
184+
185+ @staticmethod
186+ def check_precision(result_csv, op_name, testcase_name):
187+ """检查精度状态
188+
189+ Args:
190+ result_csv: 结果CSV文件路径
191+ op_name: 算子名称
192+ testcase_name: 测试用例名称
193+
194+ Returns:
195+ int: 0表示全部通过,1表示有失败用例
196+ """
197+ return OpTestUtil.check(result_csv, op_name, testcase_name)
198+
199+ @staticmethod
200+ def summarize_results(result_csv, op_name, test_type, summary_file):
201+ """汇总测试结果
202+
203+ Args:
204+ result_csv: 结果CSV文件路径
205+ op_name: 算子名称
206+ test_type: 测试类型 (kernel/aclnn/e2e)
207+ summary_file: 汇总CSV文件路径
208+ """
209+ OpTestUtil.summarize(result_csv, op_name, test_type, summary_file)
210+
211+ @staticmethod
212+ def print_summary_table(log_path):
213+ """打印可视化表格
214+
215+ Args:
216+ log_path: 日志目录路径
217+ """
218+ OpTestUtil.print_table(log_path)
219+
220+ @staticmethod
221+ def _validate_file(result_csv):
222+ """验证文件是否存在
223+
224+ Args:
225+ result_csv: 结果CSV文件路径
226+
227+ Returns:
228+ bool: 文件存在返回True
229+ """
230+ if not os.path.exists(result_csv):
231+ logger.warning(f"Result csv file not found: {result_csv}")
232+ return False
233+ return True
234+
235+ @staticmethod
236+ def _find_precision_column(headers):
237+ """查找precision_status列索引
238+
239+ Args:
240+ headers: CSV表头列表
241+
242+ Returns:
243+ int: 列索引,未找到返回-1
244+ """
245+ try:
246+ return headers.index('precision_status')
247+ except ValueError:
248+ return -1
249+
250+ @staticmethod
251+ def _count_results(reader, precision_idx):
252+ """统计测试结果
253+
254+ Args:
255+ reader: CSV reader对象
256+ precision_idx: precision_status列索引
257+
258+ Returns:
259+ tuple: (总数, 通过数)
260+ """
261+ total_cases = 0
262+ passed_cases = 0
263+
264+ for row in reader:
265+ if len(row) <= precision_idx:
266+ continue
267+ total_cases += 1
268+ if row[precision_idx] == "PASS":
269+ passed_cases += 1
270+
271+ return total_cases, passed_cases
272+
273+ @staticmethod
274+ def _ensure_summary_file(summary_file):
275+ """确保汇总文件存在并写入表头
276+
277+ Args:
278+ summary_file: 汇总文件路径
279+ """
280+ if not os.path.exists(summary_file):
281+ summary_header = "op_name,testcase_name,test_type,result_csv,status,dyn_prec,cst_prec,bin_prec"
282+ with open(summary_file, 'w') as f:
283+ f.write(summary_header + '\n')
284+
285+ @staticmethod
286+ def _read_csv_rows(result_csv):
287+ """读取CSV文件
288+
289+ Args:
290+ result_csv: CSV文件路径
291+
292+ Returns:
293+ tuple: (headers, rows) 或 (None, None)
294+ """
295+ try:
296+ with open(result_csv, 'r') as f:
297+ reader = csv.reader(f)
298+ headers = next(reader)
299+ rows = list(reader)
300+ return headers, rows
301+ except Exception as e:
302+ logger.error(f"Failed to read {result_csv}: {e}")
303+ return None, None
304+
305+ @staticmethod
306+ def _find_column_indices(headers):
307+ """查找关键列索引
308+
309+ Args:
310+ headers: CSV表头列表
311+
312+ Returns:
313+ tuple: (precision_status索引, dyn_precision索引, cst_precision索引, bin_precision索引)
314+ """
315+ precision_idx = -1
316+ dyn_idx = -1
317+ cst_idx = -1
318+ bin_idx = -1
319+
320+ for i, h in enumerate(headers):
321+ if h == 'precision_status':
322+ precision_idx = i
323+ elif h == 'dyn_precision':
324+ dyn_idx = i
325+ elif h == 'cst_precision':
326+ cst_idx = i
327+ elif h == 'bin_precision':
328+ bin_idx = i
329+
330+ return precision_idx, dyn_idx, cst_idx, bin_idx
331+
332+ @staticmethod
333+ def _get_status(row, precision_idx):
334+ """获取状态值
335+
336+ Args:
337+ row: 数据行
338+ precision_idx: precision_status列索引
339+
340+ Returns:
341+ str: 状态值
342+ """
343+ if precision_idx == -1:
344+ return "PASS"
345+ if precision_idx >= 0 and len(row) > precision_idx:
346+ return row[precision_idx]
347+ return "FAIL"
348+
349+ @staticmethod
350+ def _get_precision(row, idx):
351+ """获取精度值
352+
353+ Args:
354+ row: 数据行
355+ idx: 精度列索引
356+
357+ Returns:
358+ str: 精度值
359+ """
360+ if idx >= 0 and len(row) > idx:
361+ return OpTestUtil._parse_precision(row[idx])
362+ return "N/A"
363+
364+ @staticmethod
365+ def _get_all_precisions(row, dyn_idx, cst_idx, bin_idx):
366+ """获取三个精度值
367+
368+ Args:
369+ row: 数据行
370+ dyn_idx: dyn_precision列索引
371+ cst_idx: cst_precision列索引
372+ bin_idx: bin_precision列索引
373+
374+ Returns:
375+ tuple: (dyn_prec, cst_prec, bin_prec)
376+ """
377+ dyn_prec = OpTestUtil._get_precision(row, dyn_idx)
378+ cst_prec = OpTestUtil._get_precision(row, cst_idx)
379+ bin_prec = OpTestUtil._get_precision(row, bin_idx)
380+ return dyn_prec, cst_prec, bin_prec
381+
382+ @staticmethod
383+ def _parse_precision(value):
384+ """解析精度值
385+
386+ Args:
387+ value: 精度值字符串
388+
389+ Returns:
390+ str: 格式化后的精度值
391+ """
392+ if not value:
393+ return 'N/A'
394+ match = re.search(r'([\d.]+)%', str(value))
395+ if match:
396+ return f"{float(match.group(1)):.2f}%"
397+ return str(value)[:30]
398+
399+ @staticmethod
400+ def _write_single_row(single_data):
401+ """写入单行数据
402+
403+ Args:
404+ single_data: SingleRowData数据封装对象
405+ """
406+ tc_name = single_data.row[0]
407+ status = OpTestUtil._get_status(single_data.row, single_data.precision_idx)
408+ dyn_prec, cst_prec, bin_prec = OpTestUtil._get_all_precisions(
409+ single_data.row, single_data.dyn_idx, single_data.cst_idx, single_data.bin_idx)
410+
411+ single_data.out_f.write(f"{single_data.op_name},{tc_name},{single_data.test_type},"
412+ f"{single_data.result_csv},{status},{dyn_prec},{cst_prec},{bin_prec}\n")
413+
414+ @staticmethod
415+ def _process_csv(result_csv, op_name, test_type, summary_file):
416+ """处理CSV文件并写入汇总
417+
418+ Args:
419+ result_csv: 结果CSV文件路径
420+ op_name: 算子名称
421+ test_type: 测试类型
422+ summary_file: 汇总文件路径
423+ """
424+ try:
425+ headers, rows = OpTestUtil._read_csv_rows(result_csv)
426+ if headers is None:
427+ return
428+
429+ precision_idx, dyn_idx, cst_idx, bin_idx = OpTestUtil._find_column_indices(headers)
430+
431+ row_data = SummaryRowData(
432+ rows=rows,
433+ op_name=op_name,
434+ test_type=test_type,
435+ result_csv=result_csv,
436+ summary_file=summary_file,
437+ precision_idx=precision_idx,
438+ dyn_idx=dyn_idx,
439+ cst_idx=cst_idx,
440+ bin_idx=bin_idx
441+ )
442+ OpTestUtil._write_summary_rows(row_data)
443+ except Exception as e:
444+ logger.error(f"Failed to process {result_csv}: {e}")
445+
446+ @staticmethod
447+ def _write_summary_rows(row_data):
448+ """写入汇总行数据
449+
450+ Args:
451+ row_data: SummaryRowData数据封装对象
452+ """
453+ with open(row_data.summary_file, 'a') as out_f:
454+ for row in row_data.rows:
455+ if len(row) == 0:
456+ continue
457+
458+ single_data = SingleRowData(
459+ out_f=out_f,
460+ row=row,
461+ op_name=row_data.op_name,
462+ test_type=row_data.test_type,
463+ result_csv=row_data.result_csv,
464+ precision_idx=row_data.precision_idx,
465+ dyn_idx=row_data.dyn_idx,
466+ cst_idx=row_data.cst_idx,
467+ bin_idx=row_data.bin_idx
468+ )
469+ OpTestUtil._write_single_row(single_data)
470+
471+ @staticmethod
472+ def _read_summary_file(filepath):
473+ """读取单个汇总文件
474+
475+ Args:
476+ filepath: 文件路径
477+
478+ Returns:
479+ list: 数据行列表
480+ """
481+ try:
482+ with open(filepath, 'r') as f:
483+ reader = csv.DictReader(f)
484+ return list(reader)
485+ except Exception as e:
486+ logger.error(f"Failed to read {filepath}: {e}")
487+ return []
488+
489+ @staticmethod
490+ def _print_title_section():
491+ """打印标题区域
492+
493+ 使用table_logger输出到stdout,保持表格格式
494+ """
495+ table_logger.info('')
496+ table_logger.info('=' * 131)
497+ table_logger.info('{:^129}'.format('PRECISION TEST RESULTS SUMMARY'))
498+ table_logger.info('=' * 131)
499+
500+ @staticmethod
501+ def _load_summary_data(log_path, summary_files):
502+ """加载汇总数据
503+
504+ Args:
505+ log_path: 日志目录路径
506+ summary_files: 汇总文件列表
507+
508+ Returns:
509+ list: 所有数据行
510+ """
511+ all_rows = []
512+ for sf in summary_files:
513+ filepath = os.path.join(log_path, sf)
514+ if os.path.exists(filepath):
515+ rows = OpTestUtil._read_summary_file(filepath)
516+ all_rows.extend(rows)
517+ return all_rows
518+
519+ @staticmethod
520+ def _print_separator():
521+ """打印分隔线
522+
523+ 使用table_logger输出到stdout
524+ """
525+ line = '+' + '-' * OpTestUtil.col_widths['op'] + '+' + '-' * OpTestUtil.col_widths['testcase'] + \
526+ '+' + '-' * OpTestUtil.col_widths['type'] + '+' + '-' * OpTestUtil.col_widths['status'] + \
527+ '+' + '-' * OpTestUtil.col_widths['dyn_prec'] + '+' + '-' * OpTestUtil.col_widths['cst_prec'] + \
528+ '+' + '-' * OpTestUtil.col_widths['bin_prec'] + '+'
529+ table_logger.info(line)
530+
531+ @staticmethod
532+ def _print_header():
533+ """打印表头
534+
535+ 使用table_logger输出到stdout
536+ """
537+ OpTestUtil._print_separator()
538+ header = '| {:^18} | {:^68} | {:^6} | {:^6} | {:^7} | {:^7} | {:^7} |'.format(
539+ 'Op Name', 'Testcase Name', 'Type', 'Status', 'DynPrec', 'CstPrec', 'BinPrec')
540+ table_logger.info(header)
541+ OpTestUtil._print_separator()
542+
543+ @staticmethod
544+ def _print_row(row_data):
545+ """打印单行数据
546+
547+ Args:
548+ row_data: TableRowData数据封装对象
549+
550+ 使用table_logger输出到stdout
551+ """
552+ status_display = '\033[31mFAIL\033[0m'
553+
554+ tc_display = row_data.testcase if len(row_data.testcase) <= OpTestUtil.col_widths['testcase'] \
555+ else row_data.testcase[:35] + '...' + row_data.testcase[-32:]
556+
557+ row = '| {:<18} | {:<68} | {:^6} | {:^6} | {:^7} | {:^7} | {:^7} |'.format(
558+ row_data.op, tc_display, row_data.test_type, status_display,
559+ row_data.dyn_prec or 'N/A', row_data.cst_prec or 'N/A', row_data.bin_prec or 'N/A')
560+ table_logger.info(row)
561+
562+ @staticmethod
563+ def _print_failed_rows(all_rows):
564+ """打印失败的行数据
565+
566+ Args:
567+ all_rows: 所有数据行列表
568+
569+ 使用table_logger输出到stdout
570+ """
571+ OpTestUtil._print_header()
572+
573+ failed_rows = [r for r in all_rows if r.get('status', '').upper() != 'PASS']
574+ for row in failed_rows:
575+ row_data = TableRowData(
576+ op=row.get('op_name', ''),
577+ testcase=row.get('testcase_name', ''),
578+ test_type=row.get('test_type', ''),
579+ status=row.get('status', ''),
580+ dyn_prec=row.get('dyn_prec', ''),
581+ cst_prec=row.get('cst_prec', ''),
582+ bin_prec=row.get('bin_prec', '')
583+ )
584+ OpTestUtil._print_row(row_data)
585+
586+ @staticmethod
587+ def _print_summary(total, passed, failed):
588+ """打印汇总统计
589+
590+ Args:
591+ total: 总数
592+ passed: 通过数
593+ failed: 失败数
594+
595+ 使用table_logger输出到stdout
596+ """
597+ OpTestUtil._print_separator()
598+ pass_rate = (passed / total * 100) if total > 0 else 0.0
599+ summary_line = '| TOTAL: {:^5} | PASSED: {:^4} | FAILED: {:^4} | PASS RATE: {:.2f}%{} |'.format(
600+ total, passed, failed, pass_rate, ' ' * 57)
601+ table_logger.info(summary_line)
602+ OpTestUtil._print_separator()
603+ 
604+ 
605+def main():
606+ parser = argparse.ArgumentParser(description='OPS Test Utilities')
607+ parser.add_argument('--action', required=True,
608+ choices=['check_precision', 'summarize', 'print_table'],
609+ help='Action to perform')
610+ parser.add_argument('--result_csv', help='Result CSV file path')
611+ parser.add_argument('--op_name', help='Operator name')
612+ parser.add_argument('--testcase_name', help='Testcase name')
613+ parser.add_argument('--test_type', help='Test type (kernel/aclnn/e2e)')
614+ parser.add_argument('--summary_file', help='Summary CSV file path')
615+ parser.add_argument('--log_path', help='Log directory path')
616+
617+ args = parser.parse_args()
618+
619+ if args.action == 'check_precision':
620+ ret = OpTestUtil.check_precision(args.result_csv, args.op_name, args.testcase_name)
621+ sys.exit(ret)
622+ elif args.action == 'summarize':
623+ OpTestUtil.summarize_results(args.result_csv, args.op_name, args.test_type, args.summary_file)
624+ elif args.action == 'print_table':
625+ OpTestUtil.print_summary_table(args.log_path)
626+ 
627+ 
628+if __name__ == '__main__':
629+ main()