1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3 4""" 5Copyright (c) 2024 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""" 18 19from grammar_test import Runner, parse_args, Task 20import os 21import json 22import copy 23import shutil 24import stat 25 26combination_config_path = os.path.join(os.path.dirname(__file__), 'combination_config.json') 27config_temp_dir = os.path.join(os.path.dirname(__file__), '../test/local/temp_configs') 28INDENTATION = 2 29TEST_TYPE = 'combinations' 30 31DEFAULT_CONFIG = { 32 'mCompact': False, 33 'mRemoveComments': False, 34 'mOutputDir': '', 35 'mDisableConsole': False, 36 'mSimplify': False, 37 'mNameObfuscation': { 38 'mEnable': True, 39 'mNameGeneratorType': 1, 40 'mDictionaryList': [], 41 'mRenameProperties': False, 42 'mKeepStringProperty': True, 43 'mTopLevel': False, 44 'mKeepParameterNames': False 45 }, 46 'mExportObfuscation': False, 47 'mEnableSourceMap': False, 48 'mEnableNameCache': False, 49 'mKeepFileSourceCode': { 50 'mKeepSourceOfPaths': [], 51 'mkeepFilesAndDependencies': [] 52 }, 53 'mRenameFileName': { 54 'mEnable': False, 55 'mNameGeneratorType': 1, 56 'mReservedFileNames': [], 57 'mOhmUrlUseNormalized': False 58 } 59} 60 61# This alias is used as the output directory for the current obfuscation options. 62CONFIG_ALIAS = { 63 'mCompact': 'comp', 64 'mRemoveComments': 'rmComments', 65 'mDisableConsole': 'con', 66 'mNameObfuscation': { 67 'mEnable': 'localVar', 68 'mRenameProperties': 'prop', 69 'mKeepStringProperty': 'strProp', 70 'mTopLevel': 'top', 71 'mKeepParameterNames': 'keepPara' 72 }, 73 'mExportObfuscation': 'export', 74 'mEnableSourceMap': 'sourcemap', 75 'mEnableNameCache': 'namecache' 76} 77 78 79class CombinationRunner(Runner): 80 def __init__(self, test_filter, root_dir, test_type): 81 super().__init__(test_filter, root_dir, test_type) 82 self.configs = [] 83 self.combinations = [] 84 self.temp_files = [] 85 self.obfscated_cache_root_dir = os.path.normpath(root_dir) 86 if not os.path.exists(config_temp_dir): 87 os.makedirs(config_temp_dir) 88 89 def prepare_task(self, input_dirs, config_path): 90 for input_dir in input_dirs: 91 input_abs_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), input_dir)) 92 for one_case_path in os.listdir(input_abs_dir): 93 input_path = os.path.join(input_abs_dir, one_case_path) 94 task = Task(input_path, config_path, TEST_TYPE) 95 self.obfuscation_tasks.append(task) 96 97 def recursive_search(self, config_part, input_part, result): 98 for key in input_part: 99 if key not in config_part: 100 continue 101 input_val = input_part[key] 102 config_val = config_part[key] 103 104 if isinstance(input_val, dict): 105 self.recursive_search(config_val, input_val, result) 106 else: 107 result.append(config_val) 108 109 def get_alias_from_config(self, config, input_data): 110 result = [] 111 self.recursive_search(config, input_data, result) 112 return '+'.join(result) if result else 'default' 113 114 def merge_deep(self, target, source): 115 for key, value in source.items(): 116 if isinstance(value, dict) and key in target: 117 target[key] = self.merge_deep(target.get(key, {}), value) 118 else: 119 target[key] = value 120 return target 121 122 def merge_config(self, options, options_alias, whitelist, output_dir): 123 whitelist_config = whitelist.get(options_alias, {}) 124 output_config = {'mOutputDir': output_dir} 125 option_config = self.merge_deep(copy.deepcopy(whitelist_config), copy.deepcopy(options)) 126 option_config = self.merge_deep(option_config, output_config) 127 merged_config = self.merge_deep(copy.deepcopy(DEFAULT_CONFIG), option_config) 128 return merged_config 129 130 def generate_config(self, combinations, input_dirs, output_dir, whitelist): 131 output_abs_dir = os.path.join(os.path.dirname(combination_config_path), output_dir) 132 for combination in combinations: 133 alias_str = self.get_alias_from_config(CONFIG_ALIAS, combination) 134 output_dir_for_current_option = os.path.normpath(os.path.join(output_abs_dir, alias_str)) 135 temp_config_path = os.path.normpath(os.path.join(config_temp_dir, alias_str + '_config.json')) 136 merged_config = self.merge_config(combination, alias_str, whitelist, str(output_dir_for_current_option)) 137 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC 138 modes = stat.S_IRUSR | stat.S_IWUSR 139 # write temp config file 140 with os.fdopen(os.open(temp_config_path, flags, modes), 'w') as config_file: 141 json.dump(merged_config, config_file, indent=INDENTATION) 142 self.temp_files.append(temp_config_path) 143 self.prepare_task(input_dirs, temp_config_path) 144 145 146 def delete_temp_config_files(self): 147 for path in self.temp_files: 148 os.remove(path) 149 self.temp_files.clear() 150 151 152 def combine(self, current, remaining_keys, configs, result): 153 if not remaining_keys: 154 return 155 156 key = remaining_keys[0] 157 value = configs[key] 158 159 if isinstance(value, dict): 160 sub_keys = value.keys() 161 sub_combinations = [] 162 163 for sub_key in sub_keys: 164 sub_value = value[sub_key] 165 current_sub_combinations = sub_combinations[0:] 166 for comb in current_sub_combinations: 167 new_comb = {**comb, sub_key: sub_value} 168 sub_combinations.append(new_comb) 169 sub_combinations.append({sub_key: sub_value}) 170 171 for sub_comb in sub_combinations: 172 new_comb = {**current, key: sub_comb} 173 result.append(new_comb) 174 self.combine(new_comb, remaining_keys[1:], configs, result) 175 else: 176 new_comb = {**current, key: value} 177 result.append(new_comb) 178 self.combine(new_comb, remaining_keys[1:], configs, result) 179 180 self.combine(current, remaining_keys[1:], configs, result) 181 182 def generate_combinations(self, configs): 183 result = [{}] # Initialize with the empty object 184 keys = list(configs.keys()) 185 self.combine({}, keys, configs, result) 186 return result 187 188 def parse_configs_and_execute(self): 189 with open(combination_config_path, 'r', encoding='utf-8') as file: 190 configs = json.load(file) 191 # Parse each configuration in combination_config.json and then execute the test. 192 for key, config in configs.items(): 193 enable_options = config.get('enableOptions', {}) 194 input_dirs = config.get('inputDirs', []) 195 output_dir = config.get('outputDir', '') 196 whitelist = config.get('whitelist', {}) 197 combinations = self.generate_combinations(enable_options) 198 print('Obfuscation option combinations count:', len(combinations)) 199 self.generate_config(combinations, input_dirs, output_dir, whitelist) 200 self.obfuscate() 201 self.delete_temp_config_files() 202 203 def try_remove_cache(self): 204 if not self.has_failed_cases(): 205 shutil.rmtree(self.obfscated_cache_root_dir) 206 207 208def main(): 209 args = parse_args() 210 local_root_dir = os.path.join(os.path.dirname(__file__), "../test/local/combinations") 211 runner = CombinationRunner(args.test_filter, local_root_dir, TEST_TYPE) 212 runner.parse_configs_and_execute() 213 runner.run_with_node() 214 runner.content_compare() 215 runner.print_summary() 216 217 runner.try_remove_cache() 218 219 220if __name__ == '__main__': 221 main() 222