• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# coding=utf-8
3
4#
5# Copyright (c) 2023 Huawei Device Co., Ltd.
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19
20import os
21import sys
22import json
23import shutil
24import subprocess
25import stat
26import CppHeaderParser
27import get_innerkits_json
28import make_report
29
30FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
31MODES = stat.S_IWUSR | stat.S_IRUSR
32
33filter_file_name_list = [
34    "appexecfwk/libjnikit/include/jni.h",
35]
36FILTER_CLASS_ATTRIBUTE_LIST = ["ACE_EXPORT", "OHOS_NWEB_EXPORT"]
37
38
39def _init_sys_config():
40    sys.localcoverage_path = os.path.join(current_path, "..")
41    sys.path.insert(0, sys.localcoverage_path)
42
43
44def create_coverage_result_outpath(filepath):
45    if os.path.exists(filepath):
46        shutil.rmtree(filepath)
47    os.makedirs(filepath)
48
49
50def get_subsystem_part_list(project_rootpath):
51    subsystme_part_dict = {}
52    subsystem_part_config_filepath = os.path.join(
53        project_rootpath, "out", product_name, "build_configs/infos_for_testfwk.json")
54    if os.path.exists(subsystem_part_config_filepath):
55        try:
56            with open(subsystem_part_config_filepath, 'r') as f:
57                data = json.load(f)
58        except IOError:
59            print("Error for open subsystem config file. ")
60        if not data:
61            print("subsystem_part config file error.")
62        else:
63            subsystme_part_dict = data.get("phone", "").get("subsystem_infos", "")
64        return subsystme_part_dict
65    else:
66        print("subsystem_part_config_filepath not exists.")
67        return {}
68
69
70def load_json_data():
71    json_file_path = os.path.join(CODEPATH, KIT_MODULES_INFO)
72    json_data_dic = {}
73    if os.path.isfile(json_file_path):
74        try:
75            with open(json_file_path, 'r') as f:
76                json_data_dic = json.load(f)
77            if not json_data_dic:
78                print("Loadind file \"%s\" error" % json_file_path)
79                return {}
80        except(IOError, ValueError):
81            print("Error for load_json_data: \"%s\"" % json_file_path)
82    else:
83        print("Info: \"%s\" not exist." % json_file_path)
84    return json_data_dic
85
86
87def get_file_list(find_path, postfix):
88    file_names = os.listdir(find_path)
89    file_list = []
90    if len(file_names) > 0:
91        for fn in file_names:
92            if fn.find(postfix) != -1 and fn[-len(postfix):] == postfix:
93                file_list.append(fn)
94    return file_list
95
96
97def get_file_list_by_postfix(path, postfix, filter_jar=""):
98    file_list = []
99    for dirs in os.walk(path):
100        files = get_file_list(find_path=dirs[0], postfix=postfix)
101        for file_path in files:
102            if "" != file_path and -1 == file_path.find(__file__):
103                pos = file_path.rfind(os.sep)
104                file_name = file_path[pos + 1:]
105                file_path = os.path.join(dirs[0], file_path)
106                if filter_jar != "" and file_name == filter_jar:
107                    print("Skipped %s" % file_path)
108                    continue
109                file_list.append(file_path)
110    return file_list
111
112
113def is_need_to_be_parsed(filepath):
114    for item in filter_file_name_list:
115        if -1 != filepath.find(item):
116            return False
117    return True
118
119
120def get_pubilc_func_list_from_headfile(cxx_header_filepath):
121    pubilc_func_list = []
122    try:
123        cpp_header = CppHeaderParser.CppHeader(cxx_header_filepath)
124        for classname in cpp_header.classes:
125            class_name = classname
126            curr_class = cpp_header.classes[classname]
127            for func in curr_class["methods"]["public"]:
128                func_returntype = func["rtnType"]
129                func_name = func["name"]
130                if class_name in FILTER_CLASS_ATTRIBUTE_LIST:
131                    class_name = func_name
132                if func_returntype.find("KVSTORE_API") != -1:
133                    func_returntype = func_returntype.replace("KVSTORE_API", "").strip()
134                if func_name.isupper():
135                    continue
136                if class_name == func_name:
137                    destructor = func["destructor"]
138                    if destructor:
139                        func_name = f"~{func_name}"
140                    func_returntype = ""
141                debug = func["debug"].replace("KVSTORE_API", "")
142                debug = debug.replace(" ", "")
143                debug = debug.strip("{")
144                if debug.endswith("=delete;"):
145                    continue
146                if debug.endswith("=default;"):
147                    continue
148                if debug.startswith("inline"):
149                    continue
150                if debug.startswith("constexpr"):
151                    continue
152                if debug.startswith("virtual"):
153                    continue
154                template = func["template"]
155                if template:
156                    continue
157                param_type_list = [t["type"] for t in func["parameters"]]
158                pubilc_func_list.append((cxx_header_filepath, class_name,
159                                         func_name, param_type_list, func_returntype))
160        for func in cpp_header.functions:
161            func_returntype = func["rtnType"]
162            func_name = func["name"]
163            if func_returntype.find("KVSTORE_API") != -1:
164                func_returntype = func_returntype.replace("KVSTORE_API", "").strip()
165            if func_name.isupper():
166                continue
167            template = func["template"]
168            if template:
169                continue
170            debug = func["debug"].replace("KVSTORE_API", "")
171            debug = debug.replace(" ", "")
172            debug = debug.strip("{")
173            if debug.startswith("inline"):
174                continue
175            if debug.startswith("constexpr"):
176                continue
177            param_type_list = [t["type"] for t in func["parameters"]]
178            pubilc_func_list.append(
179                (cxx_header_filepath, "", func_name, param_type_list,
180                 func_returntype)
181            )
182    except CppHeaderParser.CppParseError:
183        print("")
184    return pubilc_func_list
185
186
187def get_sdk_interface_func_list(part_name):
188    interface_func_list = []
189    sub_path = load_json_data().get(part_name, "")
190    if sub_path == "":
191        return interface_func_list
192
193    sdk_path = os.path.join(CODEPATH, "out", product_name, sub_path)
194    if os.path.exists(sdk_path):
195        file_list = get_file_list_by_postfix(sdk_path, ".h")
196        for file in file_list:
197            try:
198                if is_need_to_be_parsed(file):
199                    interface_func_list += get_pubilc_func_list_from_headfile(file)
200            except Exception:
201                print("get interface error ", sdk_path)
202    else:
203        print("Error: %s is not exist." % sdk_path)
204    return interface_func_list
205
206
207def get_function_info_string(func_string):
208    function_info = ""
209    cxxfilt_filepath = "/usr/bin/c++filt"
210    if os.path.exists(cxxfilt_filepath):
211        command = ["c++filt", func_string]
212        function_info = subprocess.check_output(command, shell=False)
213    else:
214        print("/usr/bin/c++filt is not exist.")
215    return function_info
216
217
218def get_covered_function_list(part_name):
219    covered_function_list = []
220    file_name = f"{part_name}_strip.info"
221    file_path = os.path.join(SUB_SYSTEM_INFO_PATH, file_name)
222    if os.path.exists(file_path):
223        with open(file_path, "r") as fd:
224            for line in fd:
225                if line.startswith("FNDA:"):
226                    sub_line_string = line[len("FNDA:"):].replace("\n", "").strip()
227                    temp_list = sub_line_string.split(",")
228                    if len(temp_list) == 2 and int(temp_list[0]) != 0:
229                        func_info = get_function_info_string(temp_list[1])
230                        after_func_info = func_info.decode("utf-8")
231                        if "" == after_func_info:
232                            continue
233                        after_func_info = after_func_info.replace("\n", "")
234                        if after_func_info == temp_list[1] and after_func_info.startswith("_"):
235                            continue
236                        covered_function_list.append(after_func_info)
237    else:
238        pass
239    return covered_function_list
240
241
242def get_para_sub_string(content):
243    start_index = -1
244    ended_index = -1
245    parentheses_list_left = []
246    parentheses_list_right = []
247
248    for index in range(len(content)):
249        char = content[index]
250        if "<" == char:
251            if 0 == len(parentheses_list_left):
252                start_index = index
253            parentheses_list_left.append(char)
254            continue
255        if ">" == char:
256            parentheses_list_right.append(char)
257            if len(parentheses_list_left) == len(parentheses_list_right):
258                ended_index = index
259                break
260            continue
261
262    if -1 == start_index:
263        substring = content
264    else:
265        if -1 != ended_index:
266            substring = content[start_index:ended_index + 1]
267        else:
268            substring = content[start_index:]
269
270    return substring
271
272
273def filter_para_sub_string(source):
274    content = source
275    if content != "":
276        while True:
277            pos = content.find("<")
278            if -1 != pos:
279                substring = get_para_sub_string(content[pos:])
280                content = content.replace(substring, "")
281            else:
282                break
283    return content
284
285
286def get_function_para_count(func_info):
287    pos_start = func_info.find("(")
288    pos_end = func_info.rfind(")")
289    content = func_info[pos_start + 1: pos_end]
290    if "" == content:
291        return 0
292    content = filter_para_sub_string(content)
293    para_list = content.split(",")
294    return len(para_list)
295
296
297def get_covered_result_data(public_interface_func_list, covered_func_list):
298    coverage_result_list = []
299    for item in public_interface_func_list:
300        data_list = list(item)
301        class_name = data_list[1]
302        func_name = data_list[2]
303        para_list = data_list[3]
304        return_val = data_list[4]
305        para_string = ""
306        for index in range(len(para_list)):
307            if para_list[index].strip() == "":
308                continue
309            curr_para = para_list[index]
310            para_string += curr_para
311            if index < len(para_list) - 1:
312                para_string += ", "
313        fun_string = f"{return_val}' '{func_name}({para_string.strip().strip(',')})"
314        fun_string = fun_string.strip()
315        fun_string = filter_para_sub_string(fun_string)
316
317        if class_name != "":
318            find_string = f"::{class_name}::{func_name}("
319        else:
320            find_string = func_name
321        func_info_list = []
322        for line in covered_func_list:
323            if -1 != line.find(find_string):
324                func_info_list.append(line)
325        curr_list = [class_name, fun_string]
326        if len(func_info_list) == 0:
327            curr_list.append("N")
328        elif len(func_info_list) == 1:
329            curr_list.append("Y")
330        else:
331            interface_para_count = len(para_list)
332            find_flag = False
333            for funcinfo in func_info_list:
334                if find_string == funcinfo:
335                    curr_list.append("Y")
336                    break
337                para_count = get_function_para_count(funcinfo)
338                if interface_para_count == para_count:
339                    curr_list.append("Y")
340                    find_flag = True
341                    break
342            if not find_flag:
343                curr_list.append("N")
344        coverage_result_list.append(curr_list)
345    return coverage_result_list
346
347
348def get_interface_coverage_result_list(part_name):
349    public_interface_func_list = []
350    try:
351        interface_func_list = get_sdk_interface_func_list(part_name)
352        public_interface_func_list.extend(interface_func_list)
353    except Exception:
354        print("####")
355    covered_func_list = get_covered_function_list(part_name)
356    interface_coverage_result_list = get_covered_result_data(
357        public_interface_func_list, covered_func_list)
358    return interface_coverage_result_list
359
360
361def get_coverage_data(data_list):
362    covered_count = 0
363    total_count = len(data_list)
364    if 0 != total_count:
365        for item in data_list:
366            if "Y" == item[2] or "Recorded" == item[2]:
367                covered_count += 1
368        coverage = str("%.2f" % (covered_count * 100 / total_count)) + "%"
369    else:
370        coverage = "0%"
371    return covered_count, coverage
372
373
374def get_summary_data(interface_data_list):
375    summary_list = []
376    total_count = 0
377    covered_count = 0
378
379    for item in interface_data_list:
380        subsystem_name = item[0]
381        data_list = item[1]
382        if 0 != len(data_list):
383            count, coverage = get_coverage_data(data_list)
384            summary_list.append([subsystem_name, len(data_list), count, coverage])
385            total_count += len(data_list)
386            covered_count += count
387    if 0 != total_count:
388        total_coverage = str("%.2f" % (covered_count * 100 / total_count)) + "%"
389        summary_list.append(["Summary", total_count, covered_count, total_coverage])
390    return summary_list
391
392
393def make_summary_file(summary_list, output_path):
394    report_path = os.path.join(output_path, "coverage_summary_file.xml")
395    try:
396        if os.path.exists(report_path):
397            os.remove(report_path)
398        with os.fdopen(os.open(report_path, FLAGS, MODES), 'w') as fd:
399            fd.write('<?xml version="1.0" encoding="UTF-8"?>\n')
400            fd.write('<coverage>\n')
401            for item in summary_list:
402                fd.write("    <item subsystem_name=\"%s\" "
403                         "function_count=\"%s\" coverage_value=\"%s\" />\n" % (
404                    item[0], str(item[1]), item[3]))
405            fd.write('</coverage>\n')
406    except(IOError, ValueError):
407        print("Error for make coverage result:",)
408
409
410def make_result_file(interface_data_list, summary_list, output_path, title_name):
411    report_path = os.path.join(output_path, "ohos_interfaceCoverage.html")
412    make_report.create_html_start(report_path)
413    make_report.create_title(report_path, title_name, summary_list)
414    make_report.create_summary(report_path, summary_list)
415    for item in interface_data_list:
416        subsystem_name = item[0]
417        data_list = item[1]
418        if 0 == len(data_list):
419            continue
420        count, coverage = get_coverage_data(data_list)
421        make_report.create_table_test(
422            report_path, subsystem_name, data_list, len(data_list), count)
423    make_report.create_html_ended(report_path)
424
425
426def make_coverage_result_file(interface_data_list, output_path, title_name):
427    summary_list = get_summary_data(interface_data_list)
428    make_summary_file(summary_list, output_path)
429    make_result_file(interface_data_list, summary_list, output_path, title_name)
430
431
432def make_interface_coverage_result(part_list):
433    interface_data_list = []
434    for part_name in part_list:
435        coverage_result_list = get_interface_coverage_result_list(
436            part_name)
437        interface_data_list.append([part_name, coverage_result_list])
438    make_coverage_result_file(interface_data_list, OUTPUT_REPORT_PATH,
439                              "Inner Interface")
440
441
442if __name__ == "__main__":
443    current_path = os.getcwd()
444    CODEPATH = current_path.split("/test/testfwk/developer_test")[0]
445    SUB_SYSTEM_INFO_PATH = os.path.join(
446        CODEPATH, "test/testfwk/developer_test/localCoverage/codeCoverage/results/coverage/reports/cxx")
447    OUTPUT_REPORT_PATH = os.path.join(
448        CODEPATH, "test/testfwk/developer_test/localCoverage/interfaceCoverage/results/coverage/interface_kits"
449    )
450    _init_sys_config()
451    from localCoverage.utils import get_product_name, get_target_cpu
452    product_name = get_product_name(CODEPATH)
453    cpu_type = get_target_cpu(CODEPATH)
454    PATH_INFO_PATH = "out/{}/innerkits/ohos-{}".format(product_name, cpu_type)
455    OUTPUT_JSON_PATH = "out/{}/packages/phone/innerkits/ohos-{}".format(
456        product_name, cpu_type)
457    KIT_MODULES_INFO = "out/{}/packages/phone/innerkits/ohos-{}/kits_modules_info.json".format(
458        product_name, cpu_type)
459
460    part_args = sys.argv[1]
461    part_name_list = part_args.split("testpart=")[1].split(",")
462    get_innerkits_json.gen_parts_info_json(
463        get_innerkits_json.get_parts_list(os.path.join(CODEPATH, PATH_INFO_PATH)),
464        os.path.join(CODEPATH, OUTPUT_JSON_PATH), cpu_type
465    )
466    if len(part_name_list) > 0:
467        make_interface_coverage_result(part_name_list)
468    else:
469        print("subsystem_name not exists!")
470