1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 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 17from glob import glob 18import argparse 19import copy 20import json 21import os 22import stat 23import subprocess 24import shutil 25 26JAVA_CASES = 'java_cases' 27DEX_FILE = 'classes.dex' 28DEX_SIZE_DATA = 'dex_size.dat' 29SOURCE_SIZE_DATA = 'source_size.dat' 30R8_CLASS = 'com.android.tools.r8.R8' 31PG_CONF = 'pg.cfg' 32 33 34def is_file(parser, arg): 35 if not os.path.isfile(arg): 36 parser.error("The file '%s' does not exist" % arg) 37 return os.path.abspath(arg) 38 39def is_dir(parser, arg): 40 if not os.path.isdir(arg): 41 parser.error("The dir '%s' does not exist" % arg) 42 return os.path.abspath(arg) 43 44def check_timeout(value): 45 ivalue = int(value) 46 if ivalue <= 0: 47 raise argparse.ArgumentTypeError("%s is an invalid timeout value" % value) 48 return ivalue 49 50 51def get_args(): 52 description = "Execute Java test cases and generate a data file with the bytecode file size of the test cases." 53 parser = argparse.ArgumentParser(description=description) 54 parser.add_argument('--java-bin-path', dest='java_bin_path', type=lambda arg : is_dir(parser, arg), 55 help='Path to java root', required=True) 56 parser.add_argument('--r8-path', dest='r8_path', type=lambda arg : is_file(parser, arg), 57 help='Path to the executable program r8', required=True) 58 parser.add_argument('--rt-path', dest='rt_path', type=lambda arg : is_file(parser, arg), 59 help='Path to the jre/lib/rt.jar', required=True) 60 parser.add_argument('--timeout', type=check_timeout, dest='timeout', default=180, 61 help='Time limits for use case execution (In seconds)') 62 return parser.parse_args() 63 64 65class JavaR8Runner: 66 def __init__(self, args): 67 self.args = args 68 self.test_root = os.path.dirname(os.path.abspath(__file__)) 69 self.output = {} 70 self.source_output = {} 71 self.case_list = [] 72 self.cmd_list = [args.java_bin_path, args.r8_path, args.rt_path] # Contain [java_bin_path, r8_path, rt_path] 73 74 @staticmethod 75 def get_case_name(case_path): 76 filename = case_path.split('/')[-1] 77 case_name = filename[:filename.rfind('.')] 78 return case_name 79 80 def add_case(self, case_path, extension): 81 if not os.path.isabs(case_path): 82 case_path = os.path.join(self.test_root, case_path) 83 abs_case_path = os.path.abspath(case_path) 84 if abs_case_path not in self.case_list and abs_case_path.endswith(extension): 85 self.case_list.append(case_path) 86 87 def add_directory(self, directory, extension): 88 if not os.path.isabs(directory): 89 directory = os.path.join(self.test_root, directory) 90 glob_expression = os.path.join(os.path.abspath(directory), "**/*%s" % (extension)) 91 cases = glob(glob_expression, recursive=True) 92 for case in cases: 93 self.add_case(case, extension) 94 95 def save_output_to_file(self): 96 dex_size_data = os.path.join(self.test_root, DEX_SIZE_DATA) 97 source_size_data = os.path.join(self.test_root, SOURCE_SIZE_DATA) 98 flags = os.O_RDWR | os.O_CREAT 99 mode = stat.S_IWUSR | stat.S_IRUSR 100 with os.fdopen(os.open(dex_size_data, flags, mode), 'w') as f: 101 f.truncate() 102 json.dump(self.output, f) 103 with os.fdopen(os.open(source_size_data, flags, mode), 'w') as f: 104 f.truncate() 105 json.dump(self.source_output, f) 106 107 def run(self): 108 self.case_list.sort() 109 [java_bin_path, r8_path, rt_path] = copy.deepcopy(self.cmd_list) 110 javac_path = '/'.join([java_bin_path, 'javac']) 111 jar_path = '/'.join([java_bin_path, 'jar']) 112 java_path = '/'.join([java_bin_path, 'java']) 113 cmd_jar2dex_base = [java_path, '-cp', r8_path, R8_CLASS, '--release', '--pg-conf', PG_CONF, '--lib', rt_path] 114 for file_path in self.case_list: 115 child_path = file_path[:file_path.rfind('.')] 116 cmd_java2class = [javac_path, file_path, '-d', child_path] 117 case_name = self.get_case_name(file_path) 118 jar_name = ''.join([child_path, '/', case_name, '.jar']) 119 cmd_class2jar = [jar_path ,'cvf', jar_name, '-C', child_path, '.'] 120 cmd_jar2dex = copy.copy(cmd_jar2dex_base) 121 cmd_jar2dex.extend(['--output', child_path, jar_name]) 122 try: 123 subprocess.run(cmd_java2class, timeout=self.args.timeout) 124 subprocess.run(cmd_class2jar, timeout=self.args.timeout) 125 subprocess.run(cmd_jar2dex, timeout=self.args.timeout) 126 127 except subprocess.TimeoutExpired: 128 print(f'[WARNING]: Timeout! {file_path}') 129 except Exception as e: 130 print(f"[ERROR]: {e}") 131 finally: 132 dex_file = os.path.join(child_path, DEX_FILE) 133 dex_file_size = 0 134 if os.path.exists(dex_file): 135 dex_file_size = os.path.getsize(dex_file) 136 shutil.rmtree(child_path) 137 self.output[self.get_case_name(file_path)] = dex_file_size 138 self.source_output[self.get_case_name(file_path)] = os.path.getsize(file_path) 139 print(f'[INFO]: FINISH: {file_path}!') 140 141 self.save_output_to_file() 142 143 144def main(): 145 args = get_args() 146 java_runner = JavaR8Runner(args) 147 java_runner.add_directory(JAVA_CASES, '.java') 148 java_runner.run() 149 150 151if __name__ == "__main__": 152 main()