1#!/usr/bin/env python3
2#coding: utf-8
3
4"""
5Copyright (c) 2021-2022 Huawei Device Co., Ltd.
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17
18Description: run script
19    expect_output will get run result,
20    expect_sub_output will catch pivotal sub output,
21    expect_file will get print string
22"""
23
24import argparse
25import os
26import stat
27import subprocess
28import sys
29import time
30
31
32def get_env_path_from_rsp(script_file: str) -> list:
33    """get env path from response file recursively."""
34    rsp_file = "{0}{1}".format(script_file, ".rsp")
35    if not os.path.exists(rsp_file):
36        print(
37            "File \"{}\" does not exist!\n" \
38            "This indicates that its related shared_library is not compiled by this project, but there is an " \
39            "executable or shared_library depend on its related shared_library!".format(rsp_file))
40        sys.exit(1)
41
42    rsp_info_list = []
43    with open(rsp_file, "r") as fi:
44        rsp_info_str = fi.read()
45        rsp_info_list = rsp_info_str.split(" ")
46
47    env_path_list = []
48    for element in rsp_info_list:
49        if element.endswith(".so") or element.endswith(".dll"):
50            env_path_list.extend(get_env_path_from_rsp(element))
51            env_path_list.append(os.path.dirname(element))
52    return env_path_list
53
54
55def get_command_and_env_path(args: object) -> [str, str]:
56    """get command and environment path from args for running excutable."""
57    env_path_list = list(set(get_env_path_from_rsp(args.script_file)))
58    env_path_list.append(args.clang_lib_path)
59    env_path = ":".join(env_path_list)
60    if args.qemu_binary_path:
61        if not os.path.exists(args.qemu_binary_path):
62            print("Have you set up environment for running executables with qemu?\n" \
63                "If not, get set-up steps from https://gitee.com/ark_standalone_build/docs ," \
64                " append your build command of ark.py with option \"--clean-continue\"," \
65                " and execute the appended command after setting up the environment.\n" \
66                "If yes, the environment settings for qemu on your host machine may be different from what the link" \
67                " above shows, it is suggested to match your local environment settings with what the link shows.")
68            sys.exit(1)
69        cmd = \
70            "{}".format(args.qemu_binary_path) + \
71            " -L {}".format(args.qemu_ld_prefix) + \
72            " -E LD_LIBRARY_PATH={}".format(env_path) + \
73            " {}".format(args.script_file)
74    else:
75        cmd = "{}".format(args.script_file)
76    cmd += " {}".format(args.script_options) if args.script_options else ""
77    cmd += " {}".format(args.script_args) if args.script_args else ""
78    return [cmd, env_path]
79
80
81def parse_args() -> object:
82    """parse arguments."""
83    parser = argparse.ArgumentParser()
84    parser.add_argument('--script-file', help='execute script file')
85    parser.add_argument('--script-options', help='execute script options')
86    parser.add_argument('--script-args', help='args of script')
87    parser.add_argument('--expect-output', help='expect output')
88    parser.add_argument('--expect-sub-output', help='expect sub output')
89    parser.add_argument('--expect-file', help='expect file')
90    parser.add_argument('--env-path', help='LD_LIBRARY_PATH env')
91    parser.add_argument('--timeout-limit', help='timeout limit')
92    parser.add_argument('--clang-lib-path', help='part for LD_LIBRARY_PATH, it is not in .rsp file')
93    parser.add_argument('--qemu-binary-path', help='path to qemu binary, run executable with qemu if assigned')
94    parser.add_argument('--qemu-ld-prefix', help='elf interpreter prefix')
95    args = parser.parse_args()
96    return args
97
98
99def process_open(args: object) -> [str, object]:
100    """get command and open subprocess."""
101    if args.env_path:
102        # use the given env-path
103        cmd = args.script_file
104        cmd += " {}".format(args.script_options) if args.script_options else ""
105        cmd += " {}".format(args.script_args) if args.script_args else ""
106        # process for running executable directly
107        subp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
108            env={'LD_LIBRARY_PATH': str(args.env_path)})
109    else:
110        # get env-path from response file recursively
111        [cmd, env_path] = get_command_and_env_path(args)
112        if args.qemu_binary_path:
113            # process for running executable with qemu
114            subp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
115        else:
116            # process for running executable directly
117            subp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
118                env={'LD_LIBRARY_PATH': str(env_path)})
119    return [cmd, subp]
120
121
122def generate_stub_code_comment(out_str:str):
123    flags = os.O_WRONLY | os.O_CREAT
124    mode = stat.S_IWUSR | stat.S_IRUSR
125    dir_path = './gen/arkcompiler/ets_runtime/'
126    if not os.path.exists(dir_path):
127        return
128    file_path = dir_path + 'stub_code_comment.txt'
129    fd = os.open(file_path, flags, mode)
130    with os.fdopen(fd, "w") as code_comment_file:
131        code_comment_file.write(out_str)
132
133
134def judge_output(args: object):
135    """run executable and judge is success or not."""
136    start_time = time.time()
137    [cmd, subp] = process_open(args)
138    timeout_limit = int(args.timeout_limit) if args.timeout_limit else 1200  # units: s
139
140    try:
141        out, err = subp.communicate(timeout=timeout_limit)
142    except subprocess.TimeoutExpired:
143        raise RuntimeError('Run [', cmd, '] timeout, timeout_limit = ', timeout_limit, 's')
144
145    out_str = out.decode('UTF-8', errors="ignore")
146    err_str = err.decode('UTF-8', errors="ignore")
147    generate_stub_code_comment(out_str)
148    returncode = str(subp.returncode)
149    if args.expect_output:
150        if returncode != args.expect_output:
151            print(">>>>> ret <<<<<")
152            print(returncode)
153            print(">>>>> out <<<<<")
154            print(out_str)
155            print(">>>>> err <<<<<")
156            print(err_str)
157            print(">>>>> Expect return: [" + args.expect_output \
158                + "]\n>>>>> But got: [" + returncode + "]")
159            raise RuntimeError("Run [" + cmd + "] failed!")
160    elif args.expect_sub_output:
161        if out_str.find(args.expect_sub_output) == -1 or returncode != "0":
162            print(">>>>> ret <<<<<")
163            print(returncode)
164            print(">>>>> err <<<<<")
165            print(err_str)
166            print(">>>>> Expect contain: [" + args.expect_sub_output \
167                + "]\n>>>>> But got: [" + out_str + "]")
168            raise RuntimeError("Run [" + cmd + "] failed!")
169    elif args.expect_file:
170        with open(args.expect_file, mode='r') as file:
171            # skip license header
172            expect_output = ''.join(file.readlines()[13:])
173            file.close()
174            result_cmp = compare_line_by_line(expect_output, out_str)
175            if result_cmp or returncode != "0":
176                print(">>>>> ret <<<<<")
177                print(returncode)
178                print(">>>>> err <<<<<")
179                print(err_str)
180                print(">>>>> Expect {} lines: [{}]\n>>>>> But got {} lines: [{}]".format(
181                    expect_output.count('\n'), expect_output, out_str.count('\n'), out_str
182                ))
183                raise RuntimeError("Run [" + cmd + "] failed!")
184    else:
185        raise RuntimeError("Run [" + cmd + "] with no expect !")
186
187    print("Run [" + cmd + "] success!")
188    print("used: %.5f seconds" % (time.time() - start_time))
189
190def compare_line_by_line(expect_output:str, got_output:str):
191    expect_output_list = expect_output.split("\n")
192    got_output_list = got_output.split("\n")
193    for index, (expect_line, got_line) in enumerate(zip(expect_output_list, got_output_list)):
194        if expect_line == got_line:
195            continue
196        error_msg = ""
197
198        if "__INT_MORE_PREV__" in expect_line:
199            prev_got_value = reverse_find_first_not_trace_line(got_output_list, index-1)
200            if got_line.isdigit() and prev_got_value.isdigit() and int(prev_got_value) < int(got_line):
201                continue
202            error_msg = "Got integer result is not more than previous integer result"
203
204        if "__INT__" in expect_line:
205            if got_line.isdigit():
206                continue
207            error_msg = "Got not integer"
208
209        print(">>>>> diff <<<<<")
210        if error_msg:
211            print(error_msg)
212        print("Difference in line {}:\nExcepted: [{}]\nBut got:  [{}]".format(index+1, expect_line, got_line))
213        return True
214    return False
215
216def reverse_find_first_not_trace_line(output_list: list, init_index: int) -> str:
217    for i in range(init_index, -1, -1):
218        if "[trace]" not in output_list[i]:
219            return output_list[i]
220    return ""
221
222if __name__ == '__main__':
223    input_args = parse_args()
224    judge_output(input_args)
225