• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2021-2024 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 subprocess
18import sys
19import uuid
20import os
21import logging
22
23from pathlib import Path
24from os import getenv, path, remove
25from multiprocessing import current_process
26from typing import Optional, List, Any, TextIO, Union
27
28from runner.logger import Log
29from runner.plugins.work_dir import WorkDir
30
31_LOGGER = logging.getLogger("runner.code_coverage.coverage")
32
33LLVM_PROFDATA_VERSION: Optional[str] = getenv('LLVM_PROFDATA_VERSION')
34LLVM_COV_VERSION: Optional[str] = getenv('LLVM_COV_VERSION')
35
36IGNORE_REGEX: Optional[str] = getenv('IGNORE_REGEX')
37
38LLVM_PROFDATA_BINARY = path.join('llvm-profdata-14')
39LLVM_COV_PATH_BINARY = path.join('llvm-cov-14')
40
41GENHTML_BINARY = 'genhtml'
42
43if LLVM_PROFDATA_VERSION is not None:
44    LLVM_PROFDATA_BINARY = f"{LLVM_PROFDATA_BINARY}-{LLVM_PROFDATA_VERSION}"
45
46if LLVM_COV_VERSION is not None:
47    LLVM_COV_PATH_BINARY = f"{LLVM_COV_PATH_BINARY}-{LLVM_COV_VERSION}"
48
49
50class LinuxCommands:
51    def run_command(self, command: List[str], stdout: Union[TextIO, int] = subprocess.PIPE) -> Union[TextIO, int]:
52        with self._popen(
53                args=command,
54                stdout=stdout,
55                stderr=subprocess.PIPE,
56        ) as proc:
57            try:
58                stdout = proc.communicate(timeout=5400)
59            except subprocess.TimeoutExpired:
60                Log.all(_LOGGER, f"Timeout when try execute {command}")
61        return stdout
62
63    def do_genhtml(self, args: List[str]) -> Union[TextIO, int]:
64        command = ['genhtml'] + args
65        return self.run_command(command)
66
67    def _popen(self, **kwargs: Any) -> Any:
68        if sys.version_info.major == 3 and sys.version_info.minor >= 6:
69            return subprocess.Popen(encoding=sys.stdout.encoding, **kwargs)
70        return subprocess.Popen(**kwargs)
71
72
73class LlvmCovCommands:
74    def llvm_prof_merge_command(self, args: List[str]) -> List[str]:
75        return [LLVM_PROFDATA_BINARY, 'merge'] + args
76
77    def llvm_cov_export_command(self, args: List[str]) -> List[str]:
78        return [LLVM_COV_PATH_BINARY, 'export'] + args
79
80
81class LlvmCov:
82    def __init__(self, build_dir: str, work_dir: WorkDir) -> None:
83        self.build_dir = build_dir
84        self.coverage_dir = work_dir.coverage_dir
85        self.llvm_cov_commands = LlvmCovCommands()
86        self.linux_commands = LinuxCommands()
87
88    def do_find(self, search_directory: Path, extension: str) -> List[Path]:
89        return list(Path(search_directory).rglob(extension))
90
91    def get_uniq_profraw_profdata_file_paths(self) -> List[str]:
92        pid = current_process().pid
93        hash_code = uuid.uuid4()
94        file_path = f"{self.coverage_dir.profdata_dir}/{pid}-{hash_code}"
95        profraw_file = os.extsep.join([file_path, 'profraw'])
96        profdata_file = os.extsep.join([file_path, 'profdata'])
97        return [profraw_file, profdata_file]
98
99    def llvm_profdata_merge(self, args: List[str]) -> None:
100        prof_merge_command = self.llvm_cov_commands.llvm_prof_merge_command(args)
101        self.linux_commands.run_command(prof_merge_command)
102
103    def merge_and_delete_prowraw_files(self, profraw_file: str, profdata_file: str) -> None:
104        file = Path(profraw_file)
105        if file.is_file():
106            self.llvm_profdata_merge(['--sparse', profraw_file, '-o', profdata_file])
107            remove(profraw_file)
108        else:
109            Log.all(_LOGGER, f"File with name {profraw_file} not exist")
110
111    def make_profdata_list_file(self) -> None:
112        results = self.do_find(self.coverage_dir.profdata_dir, '*.profdata')
113        with os.fdopen(os.open(self.coverage_dir.profdata_files_list_file, os.O_APPEND | os.O_CREAT, 0o755),
114                "a", encoding="utf-8") as the_file:
115            for i in results:
116                the_file.write(str(i) + '\n')
117
118    def merge_all_profdata_files(self) -> None:
119        input_files = f"--input-files={self.coverage_dir.profdata_files_list_file}"
120        self.llvm_profdata_merge(['--sparse', input_files, '-o', str(self.coverage_dir.profdata_merged_file)])
121
122    def llvm_cov_export_to_info_file(self) -> None:
123        instr_profile = f"-instr-profile={self.coverage_dir.profdata_merged_file}"
124
125        bin_dir = f"{self.build_dir}/bin"
126
127        bins = ['ark', 'ark_aot', 'ark_asm', 'ark_disasm', 'c2abc',
128                'c2p', 'es2panda', 'irtoc_ecmascript_fastpath_exec',
129                'irtoc_fastpath_exec', 'irtoc_interpreter_exec',
130                'panda', 'pandasm', 'paoc', 'verifier'
131                ]
132
133        args = ['-format=lcov', '-Xdemangler', 'c++filt', instr_profile]
134
135        for bin_n in bins:
136            args.append(f"--object={bin_dir}/{bin_n}")
137
138        for so_path in Path(self.build_dir).rglob('*.so'):
139            args.append(f"--object={so_path}")
140
141        if IGNORE_REGEX is not None:
142            ignore_filename_regex = f"--ignore-filename-regex=\"{IGNORE_REGEX}\""
143            args.append(ignore_filename_regex)
144
145        command = self.llvm_cov_commands.llvm_cov_export_command(args)
146
147        with os.fdopen(os.open(self.coverage_dir.info_file, os.O_WRONLY | os.O_CREAT, 0o755), \
148                       "w", encoding="utf-8") as file_dot_info:
149            self.linux_commands.run_command(command, stdout=file_dot_info)
150
151    def genhtml(self) -> None:
152        output_directory = f"--output-directory={self.coverage_dir.html_report_dir}"
153        self.linux_commands.do_genhtml([output_directory, str(self.coverage_dir.info_file)])
154