1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 2021-2025 Huawei Device Co., Ltd. 5# Licensed under the Apache License, Version 2.0 (the 'License'); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an 'AS IS' BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import os 18import subprocess 19import sys 20import uuid 21from multiprocessing import current_process 22from os import getenv, path, remove 23from pathlib import Path 24from typing import Any, TextIO 25 26from runner.logger import Log 27from runner.suites.work_dir import WorkDir 28 29_LOGGER = Log.get_logger(__file__) 30 31LLVM_PROFDATA_VERSION: str | None = getenv('LLVM_PROFDATA_VERSION') 32LLVM_COV_VERSION: str | None = getenv('LLVM_COV_VERSION') 33 34IGNORE_REGEX: str | None = getenv('IGNORE_REGEX') 35 36LLVM_PROFDATA_BINARY = path.join('llvm-profdata-14') 37LLVM_COV_PATH_BINARY = path.join('llvm-cov-14') 38 39GENHTML_BINARY = 'genhtml' 40 41if LLVM_PROFDATA_VERSION is not None: 42 LLVM_PROFDATA_BINARY = f"{LLVM_PROFDATA_BINARY}-{LLVM_PROFDATA_VERSION}" 43 44if LLVM_COV_VERSION is not None: 45 LLVM_COV_PATH_BINARY = f"{LLVM_COV_PATH_BINARY}-{LLVM_COV_VERSION}" 46 47 48class LinuxCommands: 49 @staticmethod 50 def _popen(**kwargs: Any) -> Any: # type: ignore[explicit-any] 51 if sys.version_info.major == 3 and sys.version_info.minor >= 6: 52 return subprocess.Popen(encoding=sys.stdout.encoding, **kwargs) 53 return subprocess.Popen(**kwargs) 54 55 def run_command(self, command: list[str], stdout: TextIO | int = subprocess.PIPE) -> TextIO | int: 56 with self._popen( 57 args=command, 58 stdout=stdout, 59 stderr=subprocess.PIPE, 60 ) as proc: 61 try: 62 stdout = proc.communicate(timeout=5400) 63 except subprocess.TimeoutExpired: 64 _LOGGER.all(f"Timeout when try execute {command}") 65 return stdout 66 67 def do_genhtml(self, args: list[str]) -> TextIO | int: 68 command = ['genhtml', *args] 69 return self.run_command(command) 70 71 72class LlvmCovCommands: 73 @staticmethod 74 def llvm_prof_merge_command(args: list[str]) -> list[str]: 75 return [LLVM_PROFDATA_BINARY, 'merge', *args] 76 77 @staticmethod 78 def llvm_cov_export_command(args: list[str]) -> list[str]: 79 return [LLVM_COV_PATH_BINARY, 'export', *args] 80 81 82class LlvmCov: 83 def __init__(self, build_dir: Path, work_dir: WorkDir) -> None: 84 self.build_dir: Path = build_dir 85 self.coverage_dir = work_dir.coverage_dir 86 self.llvm_cov_commands = LlvmCovCommands() 87 self.linux_commands = LinuxCommands() 88 89 @staticmethod 90 def do_find(search_directory: Path, extension: str) -> list[Path]: 91 return list(Path(search_directory).rglob(extension)) 92 93 def get_uniq_profraw_profdata_file_paths(self) -> list[str]: 94 pid = current_process().pid 95 hash_code = uuid.uuid4() 96 file_path = f"{self.coverage_dir.profdata_dir}/{pid}-{hash_code}" 97 profraw_file = os.extsep.join([file_path, 'profraw']) 98 profdata_file = os.extsep.join([file_path, 'profdata']) 99 return [profraw_file, profdata_file] 100 101 def llvm_profdata_merge(self, args: list[str]) -> None: 102 prof_merge_command = self.llvm_cov_commands.llvm_prof_merge_command(args) 103 self.linux_commands.run_command(prof_merge_command) 104 105 def merge_and_delete_prowraw_files(self, profraw_file: str, profdata_file: str) -> None: 106 file = Path(profraw_file) 107 if file.is_file(): 108 self.llvm_profdata_merge(['--sparse', profraw_file, '-o', profdata_file]) 109 remove(profraw_file) 110 else: 111 _LOGGER.all(f"File with name {profraw_file} not exist") 112 113 def make_profdata_list_file(self) -> None: 114 results = self.do_find(self.coverage_dir.profdata_dir, '*.profdata') 115 with os.fdopen(os.open(self.coverage_dir.profdata_files_list_file, os.O_RDWR | os.O_APPEND | os.O_CREAT, 0o755), 116 "a", encoding="utf-8") as the_file: 117 for i in results: 118 the_file.write(str(i) + '\n') 119 120 def merge_all_profdata_files(self) -> None: 121 input_files = f"--input-files={self.coverage_dir.profdata_files_list_file}" 122 self.llvm_profdata_merge(['--sparse', input_files, '-o', str(self.coverage_dir.profdata_merged_file)]) 123 124 def llvm_cov_export_to_info_file(self) -> None: 125 instr_profile = f"-instr-profile={self.coverage_dir.profdata_merged_file}" 126 127 bin_dir = f"{self.build_dir}/bin" 128 129 bins = [ 130 'ark', 'ark_aot', 'ark_asm', 'ark_disasm', 'c2abc', 131 'c2p', 'es2panda', 'irtoc_ecmascript_fastpath_exec', 132 'irtoc_fastpath_exec', 'irtoc_interpreter_exec', 133 'panda', 'pandasm', 'paoc', 'verifier' 134 ] 135 136 args = ['-format=lcov', '-Xdemangler', 'c++filt', instr_profile] 137 138 for bin_n in bins: 139 args.append(f"--object={bin_dir}/{bin_n}") 140 141 for so_path in Path(self.build_dir).rglob('*.so'): 142 args.append(f"--object={so_path}") 143 144 if IGNORE_REGEX is not None: 145 ignore_filename_regex = f"--ignore-filename-regex=\"{IGNORE_REGEX}\"" 146 args.append(ignore_filename_regex) 147 148 command_info = self.llvm_cov_commands.llvm_cov_export_command(args) 149 args = list(map(lambda x: x.replace('-format=lcov', '-format=text'), args)) 150 args.append("-summary-only") 151 command_json = self.llvm_cov_commands.llvm_cov_export_command(args) 152 153 with os.fdopen(os.open(self.coverage_dir.info_file, os.O_WRONLY | os.O_CREAT, 0o755), 154 "w", encoding="utf-8") as file_dot_info: 155 self.linux_commands.run_command(command_info, stdout=file_dot_info) 156 157 with os.fdopen(os.open(self.coverage_dir.json_file, os.O_WRONLY | os.O_CREAT, 0o755), 158 "w", encoding="utf-8") as file_dot_json: 159 self.linux_commands.run_command(command_json, stdout=file_dot_json) 160 161 def genhtml(self) -> None: 162 output_directory = f"--output-directory={self.coverage_dir.html_report_dir}" 163 self.linux_commands.do_genhtml([output_directory, str(self.coverage_dir.info_file)]) 164