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