• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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