• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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