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