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 subprocess 27import sys 28import time 29import zipfile 30from io import StringIO 31 32 33def get_env_path_from_rsp(script_file: str) -> list: 34 """get env path from response file recursively.""" 35 rsp_file = "{0}{1}".format(script_file, ".rsp") 36 if not os.path.exists(rsp_file): 37 print( 38 "File \"{}\" does not exist!\n" \ 39 "This indicates that its related shared_library is not compiled by this project, but there is an " \ 40 "executable or shared_library depend on its related shared_library!".format(rsp_file)) 41 sys.exit(1) 42 43 rsp_info_list = [] 44 with open(rsp_file, "r") as fi: 45 rsp_info_str = fi.read() 46 rsp_info_list = rsp_info_str.split(" ") 47 48 env_path_list = [] 49 for element in rsp_info_list: 50 if element.endswith(".so") or element.endswith(".dll"): 51 env_path_list.extend(get_env_path_from_rsp(element)) 52 env_path_list.append(os.path.dirname(element)) 53 return env_path_list 54 55 56def get_command_and_env_path(args: object) -> [str, str]: 57 """get command and environment path from args for running excutable.""" 58 env_path_list = list(set(get_env_path_from_rsp(args.script_file))) 59 env_path_list.append(args.clang_lib_path) 60 env_path = ":".join(env_path_list) 61 if args.qemu_binary_path: 62 if not os.path.exists(args.qemu_binary_path): 63 print("Have you set up environment for running executables with qemu?\n" \ 64 "If not, get set-up steps from https://gitee.com/ark_standalone_build/docs ," \ 65 " append your build command of ark.py with option \"--clean-continue\"," \ 66 " and execute the appended command after setting up the environment.\n" \ 67 "If yes, the environment settings for qemu on your host machine may be different from what the link" \ 68 " above shows, it is suggested to match your local environment settings with what the link shows.") 69 sys.exit(1) 70 cmd = \ 71 "{}".format(args.qemu_binary_path) + \ 72 " -L {}".format(args.qemu_ld_prefix) + \ 73 " -E LD_LIBRARY_PATH={}".format(env_path) + \ 74 " {}".format(args.script_file) 75 else: 76 cmd = "{}".format(args.script_file) 77 cmd += " {}".format(args.script_options) if args.script_options else "" 78 cmd += " {}".format(args.script_args) if args.script_args else "" 79 return [cmd, env_path] 80 81 82def parse_args() -> object: 83 """parse arguments.""" 84 parser = argparse.ArgumentParser() 85 parser.add_argument('--script-file', help='execute script file') 86 parser.add_argument('--script-options', help='execute script options') 87 parser.add_argument('--script-args', help='args of script') 88 parser.add_argument('--expect-output', help='expect output') 89 parser.add_argument('--expect-sub-output', help='expect sub output') 90 parser.add_argument('--expect-file', help='expect file') 91 parser.add_argument('--env-path', help='LD_LIBRARY_PATH env') 92 parser.add_argument('--timeout-limit', help='timeout limit') 93 parser.add_argument('--clang-lib-path', help='part for LD_LIBRARY_PATH, it is not in .rsp file') 94 parser.add_argument('--qemu-binary-path', help='path to qemu binary, run executable with qemu if assigned') 95 parser.add_argument('--qemu-ld-prefix', help='elf interpreter prefix') 96 parser.add_argument('--expect-sub-error', help='use error output to compare result') 97 args = parser.parse_args() 98 return args 99 100 101def process_open(args: object) -> [str, object]: 102 """get command and open subprocess.""" 103 if args.env_path: 104 # use the given env-path 105 cmd = args.script_file 106 cmd += " {}".format(args.script_options) if args.script_options else "" 107 cmd += " {}".format(args.script_args) if args.script_args else "" 108 # process for running executable directly 109 subp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 110 env={'LD_LIBRARY_PATH': str(args.env_path)}) 111 else: 112 # get env-path from response file recursively 113 [cmd, env_path] = get_command_and_env_path(args) 114 if args.qemu_binary_path: 115 # process for running executable with qemu 116 subp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 117 else: 118 # process for running executable directly 119 subp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 120 env={'LD_LIBRARY_PATH': str(env_path)}) 121 return [cmd, subp] 122 123 124def generate_stub_code_comment(out_str:str): 125 dir_path = './gen/arkcompiler/ets_runtime/' 126 if not os.path.exists(dir_path): 127 return 128 zip_path = dir_path + 'stub_code_comment.zip' 129 if out_str != '': 130 memory_file = StringIO() 131 memory_file.write(out_str) 132 memory_file.seek(0) 133 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file: 134 zip_file.writestr('stub_code_comment.txt', memory_file.getvalue()) 135 136 137def check_expect_output(cmd: str, returncode: str, out_str: str, err_str: str, expect_output: str): 138 """Check if return code matches expected output.""" 139 if returncode != expect_output: 140 print(">>>>> ret <<<<<") 141 print(returncode) 142 print(">>>>> out <<<<<") 143 print(out_str) 144 print(">>>>> err <<<<<") 145 print(err_str) 146 print(">>>>> Expect return: [" + expect_output \ 147 + "]\n>>>>> But got: [" + returncode + "]") 148 raise RuntimeError("Run [" + cmd + "] failed!") 149 150 151def check_expect_sub_output(cmd: str, returncode: str, out_str: str, err_str: str, expect_sub_output: str): 152 """Check if output contains expected substring.""" 153 if out_str.find(expect_sub_output) == -1 or returncode != "0": 154 print(">>>>> ret <<<<<") 155 print(returncode) 156 print(">>>>> err <<<<<") 157 print(err_str) 158 print(">>>>> Expect contain: [" + expect_sub_output \ 159 + "]\n>>>>> But got: [" + out_str + "]") 160 raise RuntimeError("Run [" + cmd + "] failed!") 161 162 163def check_expect_sub_error(cmd: str, returncode: str, out_str: str, err_str: str, expect_sub_error: str): 164 """Check if error output contains expected substring.""" 165 if err_str.find(expect_sub_error) == -1 or returncode == '0': 166 print(">>>>> returnCode <<<<<") 167 print(returncode) 168 print(">>>>> expect err <<<<<") 169 print(err_str) 170 if returncode == '0': 171 print(">>>>> Expect err : [" + expect_sub_error \ 172 + "]\n>>>>> But got corret: [" + out_str + "]") 173 else: 174 print(">>>>> Expect err : [" + expect_sub_error \ 175 + "]\n>>>>> But got wrong err: [" + err_str + "]") 176 raise RuntimeError("Run [" + cmd + "] failed!") 177 178 179def check_expect_file(cmd: str, returncode: str, out_str: str, err_str: str, expect_file: str): 180 """Check if output matches expected file content.""" 181 with open(expect_file, mode='r') as file: 182 # skip license header 183 expect_output = ''.join(file.readlines()[13:]) 184 file.close() 185 result_cmp = compare_line_by_line(expect_output, out_str) 186 if result_cmp or returncode != "0": 187 print(">>>>> ret <<<<<") 188 print(returncode) 189 print(">>>>> err <<<<<") 190 print(err_str) 191 print(">>>>> Expect {} lines: [{}]\n>>>>> But got {} lines: [{}]".format( 192 expect_output.count('\n'), expect_output, out_str.count('\n'), out_str 193 )) 194 raise RuntimeError("Run [" + cmd + "] failed!") 195 196 197def judge_output(args: object): 198 """Run executable and judge if success or not.""" 199 start_time = time.time() 200 [cmd, subp] = process_open(args) 201 timeout_limit = int(args.timeout_limit) if args.timeout_limit else 1200 # units: s 202 203 try: 204 out, err = subp.communicate(timeout=timeout_limit) 205 except subprocess.TimeoutExpired: 206 raise RuntimeError('Run [', cmd, '] timeout, timeout_limit = ', timeout_limit, 's') 207 208 out_str = out.decode('UTF-8', errors="ignore") 209 err_str = err.decode('UTF-8', errors="ignore") 210 generate_stub_code_comment(out_str) 211 returncode = str(subp.returncode) 212 213 if args.expect_output: 214 check_expect_output(cmd, returncode, out_str, err_str, args.expect_output) 215 elif args.expect_sub_output: 216 check_expect_sub_output(cmd, returncode, out_str, err_str, args.expect_sub_output) 217 elif args.expect_sub_error: 218 check_expect_sub_error(cmd, returncode, out_str, err_str, args.expect_sub_error) 219 elif args.expect_file: 220 check_expect_file(cmd, returncode, out_str, err_str, args.expect_file) 221 else: 222 raise RuntimeError("Run [" + cmd + "] with no expect !") 223 224 print("Run [" + cmd + "] success!") 225 print("used: %.5f seconds" % (time.time() - start_time)) 226 227def compare_line_by_line(expect_output:str, got_output:str): 228 expect_output_list = expect_output.split("\n") 229 got_output_list = got_output.split("\n") 230 for index, (expect_line, got_line) in enumerate(zip(expect_output_list, got_output_list)): 231 if expect_line == got_line: 232 continue 233 error_msg = "" 234 235 if "__INT_MORE_PREV__" in expect_line: 236 prev_got_value = reverse_find_first_not_trace_line(got_output_list, index-1) 237 if got_line.isdigit() and prev_got_value.isdigit() and int(prev_got_value) < int(got_line): 238 continue 239 error_msg = "Got integer result is not more than previous integer result" 240 241 if "__INT__" in expect_line: 242 if got_line.isdigit(): 243 continue 244 error_msg = "Got not integer" 245 246 print(">>>>> diff <<<<<") 247 if error_msg: 248 print(error_msg) 249 print("Difference in line {}:\nExcepted: [{}]\nBut got: [{}]".format(index+1, expect_line, got_line)) 250 return True 251 return False 252 253def reverse_find_first_not_trace_line(output_list: list, init_index: int) -> str: 254 for i in range(init_index, -1, -1): 255 if "[trace]" not in output_list[i]: 256 return output_list[i] 257 return "" 258 259if __name__ == '__main__': 260 input_args = parse_args() 261 judge_output(input_args) 262