13af6ab5fSopenharmony_ci#!/usr/bin/env python3
23af6ab5fSopenharmony_ci# -*- coding: utf-8 -*-
33af6ab5fSopenharmony_ci
43af6ab5fSopenharmony_ci"""
53af6ab5fSopenharmony_ciCopyright (c) 2024 Huawei Device Co., Ltd.
63af6ab5fSopenharmony_ciLicensed under the Apache License, Version 2.0 (the "License");
73af6ab5fSopenharmony_ciyou may not use this file except in compliance with the License.
83af6ab5fSopenharmony_ciYou may obtain a copy of the License at
93af6ab5fSopenharmony_ci
103af6ab5fSopenharmony_ci    http://www.apache.org/licenses/LICENSE-2.0
113af6ab5fSopenharmony_ci
123af6ab5fSopenharmony_ciUnless required by applicable law or agreed to in writing, software
133af6ab5fSopenharmony_cidistributed under the License is distributed on an "AS IS" BASIS,
143af6ab5fSopenharmony_ciWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
153af6ab5fSopenharmony_ciSee the License for the specific language governing permissions and
163af6ab5fSopenharmony_cilimitations under the License.
173af6ab5fSopenharmony_ci"""
183af6ab5fSopenharmony_ci
193af6ab5fSopenharmony_cifrom grammar_test import Runner, parse_args, Task
203af6ab5fSopenharmony_ciimport os
213af6ab5fSopenharmony_ciimport json
223af6ab5fSopenharmony_ciimport copy
233af6ab5fSopenharmony_ciimport shutil
243af6ab5fSopenharmony_ciimport stat
253af6ab5fSopenharmony_ci
263af6ab5fSopenharmony_cicombination_config_path = os.path.join(os.path.dirname(__file__), 'combination_config.json')
273af6ab5fSopenharmony_ciconfig_temp_dir = os.path.join(os.path.dirname(__file__), '../test/local/temp_configs')
283af6ab5fSopenharmony_ciINDENTATION = 2
293af6ab5fSopenharmony_ciTEST_TYPE = 'combinations'
303af6ab5fSopenharmony_ci
313af6ab5fSopenharmony_ciDEFAULT_CONFIG = {
323af6ab5fSopenharmony_ci    'mCompact': False,
333af6ab5fSopenharmony_ci    'mRemoveComments': False,
343af6ab5fSopenharmony_ci    'mOutputDir': '',
353af6ab5fSopenharmony_ci    'mDisableConsole': False,
363af6ab5fSopenharmony_ci    'mSimplify': False,
373af6ab5fSopenharmony_ci    'mNameObfuscation': {
383af6ab5fSopenharmony_ci        'mEnable': True,
393af6ab5fSopenharmony_ci        'mNameGeneratorType': 1,
403af6ab5fSopenharmony_ci        'mDictionaryList': [],
413af6ab5fSopenharmony_ci        'mRenameProperties': False,
423af6ab5fSopenharmony_ci        'mKeepStringProperty': True,
433af6ab5fSopenharmony_ci        'mTopLevel': False
443af6ab5fSopenharmony_ci    },
453af6ab5fSopenharmony_ci    'mExportObfuscation': False,
463af6ab5fSopenharmony_ci    'mEnableSourceMap': False,
473af6ab5fSopenharmony_ci    'mEnableNameCache': False,
483af6ab5fSopenharmony_ci    'mKeepFileSourceCode': {
493af6ab5fSopenharmony_ci        'mKeepSourceOfPaths': [],
503af6ab5fSopenharmony_ci        'mkeepFilesAndDependencies': []
513af6ab5fSopenharmony_ci    },
523af6ab5fSopenharmony_ci    'mRenameFileName': {
533af6ab5fSopenharmony_ci        'mEnable': False,
543af6ab5fSopenharmony_ci        'mNameGeneratorType': 1,
553af6ab5fSopenharmony_ci        'mReservedFileNames': [],
563af6ab5fSopenharmony_ci        'mOhmUrlUseNormalized': False
573af6ab5fSopenharmony_ci    }
583af6ab5fSopenharmony_ci}
593af6ab5fSopenharmony_ci
603af6ab5fSopenharmony_ci# This alias is used as the output directory for the current obfuscation options.
613af6ab5fSopenharmony_ciCONFIG_ALIAS = {
623af6ab5fSopenharmony_ci    'mCompact': 'comp',
633af6ab5fSopenharmony_ci    'mRemoveComments': 'rmComments',
643af6ab5fSopenharmony_ci    'mDisableConsole': 'con',
653af6ab5fSopenharmony_ci    'mNameObfuscation': {
663af6ab5fSopenharmony_ci        'mEnable': 'localVar',
673af6ab5fSopenharmony_ci        'mRenameProperties': 'prop',
683af6ab5fSopenharmony_ci        'mKeepStringProperty': 'strProp',
693af6ab5fSopenharmony_ci        'mTopLevel': 'top'
703af6ab5fSopenharmony_ci    },
713af6ab5fSopenharmony_ci    'mExportObfuscation': 'export',
723af6ab5fSopenharmony_ci    'mEnableSourceMap': 'sourcemap',
733af6ab5fSopenharmony_ci    'mEnableNameCache': 'namecache'
743af6ab5fSopenharmony_ci}
753af6ab5fSopenharmony_ci
763af6ab5fSopenharmony_ci
773af6ab5fSopenharmony_ciclass CombinationRunner(Runner):
783af6ab5fSopenharmony_ci    def __init__(self, test_filter, root_dir, test_type):
793af6ab5fSopenharmony_ci        super().__init__(test_filter, root_dir, test_type)
803af6ab5fSopenharmony_ci        self.configs = []
813af6ab5fSopenharmony_ci        self.combinations = []
823af6ab5fSopenharmony_ci        self.temp_files = []
833af6ab5fSopenharmony_ci        self.obfscated_cache_root_dir = os.path.normpath(root_dir)
843af6ab5fSopenharmony_ci        if not os.path.exists(config_temp_dir):
853af6ab5fSopenharmony_ci            os.makedirs(config_temp_dir)
863af6ab5fSopenharmony_ci
873af6ab5fSopenharmony_ci    def prepare_task(self, input_dirs, config_path):
883af6ab5fSopenharmony_ci        for input_dir in input_dirs:
893af6ab5fSopenharmony_ci            input_abs_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), input_dir))
903af6ab5fSopenharmony_ci            for one_case_path in os.listdir(input_abs_dir):
913af6ab5fSopenharmony_ci                input_path = os.path.join(input_abs_dir, one_case_path)
923af6ab5fSopenharmony_ci                task = Task(input_path, config_path, TEST_TYPE)
933af6ab5fSopenharmony_ci                self.obfuscation_tasks.append(task)
943af6ab5fSopenharmony_ci
953af6ab5fSopenharmony_ci    def recursive_search(self, config_part, input_part, result):
963af6ab5fSopenharmony_ci        for key in input_part:
973af6ab5fSopenharmony_ci            if key not in config_part:
983af6ab5fSopenharmony_ci                continue
993af6ab5fSopenharmony_ci            input_val = input_part[key]
1003af6ab5fSopenharmony_ci            config_val = config_part[key]
1013af6ab5fSopenharmony_ci
1023af6ab5fSopenharmony_ci            if isinstance(input_val, dict):
1033af6ab5fSopenharmony_ci                self.recursive_search(config_val, input_val, result)
1043af6ab5fSopenharmony_ci            else:
1053af6ab5fSopenharmony_ci                result.append(config_val)
1063af6ab5fSopenharmony_ci
1073af6ab5fSopenharmony_ci    def get_alias_from_config(self, config, input_data):
1083af6ab5fSopenharmony_ci        result = []
1093af6ab5fSopenharmony_ci        self.recursive_search(config, input_data, result)
1103af6ab5fSopenharmony_ci        return '+'.join(result) if result else 'default'
1113af6ab5fSopenharmony_ci
1123af6ab5fSopenharmony_ci    def merge_deep(self, target, source):
1133af6ab5fSopenharmony_ci        for key, value in source.items():
1143af6ab5fSopenharmony_ci            if isinstance(value, dict) and key in target:
1153af6ab5fSopenharmony_ci                target[key] = self.merge_deep(target.get(key, {}), value)
1163af6ab5fSopenharmony_ci            else:
1173af6ab5fSopenharmony_ci                target[key] = value
1183af6ab5fSopenharmony_ci        return target
1193af6ab5fSopenharmony_ci
1203af6ab5fSopenharmony_ci    def merge_config(self, options, options_alias, whitelist, output_dir):
1213af6ab5fSopenharmony_ci        whitelist_config = whitelist.get(options_alias, {})
1223af6ab5fSopenharmony_ci        output_config = {'mOutputDir': output_dir}
1233af6ab5fSopenharmony_ci        option_config = self.merge_deep(copy.deepcopy(whitelist_config), copy.deepcopy(options))
1243af6ab5fSopenharmony_ci        option_config = self.merge_deep(option_config, output_config)
1253af6ab5fSopenharmony_ci        merged_config = self.merge_deep(copy.deepcopy(DEFAULT_CONFIG), option_config)
1263af6ab5fSopenharmony_ci        return merged_config
1273af6ab5fSopenharmony_ci
1283af6ab5fSopenharmony_ci    def generate_config(self, combinations, input_dirs, output_dir, whitelist):
1293af6ab5fSopenharmony_ci        output_abs_dir = os.path.join(os.path.dirname(combination_config_path), output_dir)
1303af6ab5fSopenharmony_ci        for combination in combinations:
1313af6ab5fSopenharmony_ci            alias_str = self.get_alias_from_config(CONFIG_ALIAS, combination)
1323af6ab5fSopenharmony_ci            output_dir_for_current_option = os.path.normpath(os.path.join(output_abs_dir, alias_str))
1333af6ab5fSopenharmony_ci            temp_config_path = os.path.normpath(os.path.join(config_temp_dir, alias_str + '_config.json'))
1343af6ab5fSopenharmony_ci            merged_config = self.merge_config(combination, alias_str, whitelist, str(output_dir_for_current_option))
1353af6ab5fSopenharmony_ci            flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
1363af6ab5fSopenharmony_ci            modes = stat.S_IRUSR | stat.S_IWUSR
1373af6ab5fSopenharmony_ci            # write temp config file
1383af6ab5fSopenharmony_ci            with os.fdopen(os.open(temp_config_path, flags, modes), 'w') as config_file:
1393af6ab5fSopenharmony_ci                json.dump(merged_config, config_file, indent=INDENTATION)
1403af6ab5fSopenharmony_ci            self.temp_files.append(temp_config_path)
1413af6ab5fSopenharmony_ci            self.prepare_task(input_dirs, temp_config_path)
1423af6ab5fSopenharmony_ci
1433af6ab5fSopenharmony_ci
1443af6ab5fSopenharmony_ci    def delete_temp_config_files(self):
1453af6ab5fSopenharmony_ci        for path in self.temp_files:
1463af6ab5fSopenharmony_ci            os.remove(path)
1473af6ab5fSopenharmony_ci        self.temp_files.clear()
1483af6ab5fSopenharmony_ci
1493af6ab5fSopenharmony_ci
1503af6ab5fSopenharmony_ci    def combine(self, current, remaining_keys, configs, result):
1513af6ab5fSopenharmony_ci        if not remaining_keys:
1523af6ab5fSopenharmony_ci            return
1533af6ab5fSopenharmony_ci
1543af6ab5fSopenharmony_ci        key = remaining_keys[0]
1553af6ab5fSopenharmony_ci        value = configs[key]
1563af6ab5fSopenharmony_ci
1573af6ab5fSopenharmony_ci        if isinstance(value, dict):
1583af6ab5fSopenharmony_ci            sub_keys = value.keys()
1593af6ab5fSopenharmony_ci            sub_combinations = []
1603af6ab5fSopenharmony_ci
1613af6ab5fSopenharmony_ci            for sub_key in sub_keys:
1623af6ab5fSopenharmony_ci                sub_value = value[sub_key]
1633af6ab5fSopenharmony_ci                current_sub_combinations = sub_combinations[0:]
1643af6ab5fSopenharmony_ci                for comb in current_sub_combinations:
1653af6ab5fSopenharmony_ci                    new_comb = {**comb, sub_key: sub_value}
1663af6ab5fSopenharmony_ci                    sub_combinations.append(new_comb)
1673af6ab5fSopenharmony_ci                sub_combinations.append({sub_key: sub_value})
1683af6ab5fSopenharmony_ci
1693af6ab5fSopenharmony_ci            for sub_comb in sub_combinations:
1703af6ab5fSopenharmony_ci                new_comb = {**current, key: sub_comb}
1713af6ab5fSopenharmony_ci                result.append(new_comb)
1723af6ab5fSopenharmony_ci                self.combine(new_comb, remaining_keys[1:], configs, result)
1733af6ab5fSopenharmony_ci        else:
1743af6ab5fSopenharmony_ci            new_comb = {**current, key: value}
1753af6ab5fSopenharmony_ci            result.append(new_comb)
1763af6ab5fSopenharmony_ci            self.combine(new_comb, remaining_keys[1:], configs, result)
1773af6ab5fSopenharmony_ci
1783af6ab5fSopenharmony_ci        self.combine(current, remaining_keys[1:], configs, result)
1793af6ab5fSopenharmony_ci
1803af6ab5fSopenharmony_ci    def generate_combinations(self, configs):
1813af6ab5fSopenharmony_ci        result = [{}]  # Initialize with the empty object
1823af6ab5fSopenharmony_ci        keys = list(configs.keys())
1833af6ab5fSopenharmony_ci        self.combine({}, keys, configs, result)
1843af6ab5fSopenharmony_ci        return result
1853af6ab5fSopenharmony_ci
1863af6ab5fSopenharmony_ci    def parse_configs_and_execute(self):
1873af6ab5fSopenharmony_ci        with open(combination_config_path, 'r', encoding='utf-8') as file:
1883af6ab5fSopenharmony_ci            configs = json.load(file)
1893af6ab5fSopenharmony_ci        # Parse each configuration in combination_config.json and then execute the test.
1903af6ab5fSopenharmony_ci        for key, config in configs.items():
1913af6ab5fSopenharmony_ci            enable_options = config.get('enableOptions', {})
1923af6ab5fSopenharmony_ci            input_dirs = config.get('inputDirs', [])
1933af6ab5fSopenharmony_ci            output_dir = config.get('outputDir', '')
1943af6ab5fSopenharmony_ci            whitelist = config.get('whitelist', {})
1953af6ab5fSopenharmony_ci            combinations = self.generate_combinations(enable_options)
1963af6ab5fSopenharmony_ci            print('Obfuscation option combinations count:', len(combinations))
1973af6ab5fSopenharmony_ci            self.generate_config(combinations, input_dirs, output_dir, whitelist)
1983af6ab5fSopenharmony_ci            self.obfuscate()
1993af6ab5fSopenharmony_ci            self.delete_temp_config_files()
2003af6ab5fSopenharmony_ci
2013af6ab5fSopenharmony_ci    def try_remove_cache(self):
2023af6ab5fSopenharmony_ci        if not self.has_failed_cases():
2033af6ab5fSopenharmony_ci            shutil.rmtree(self.obfscated_cache_root_dir)
2043af6ab5fSopenharmony_ci
2053af6ab5fSopenharmony_ci
2063af6ab5fSopenharmony_cidef main():
2073af6ab5fSopenharmony_ci    args = parse_args()
2083af6ab5fSopenharmony_ci    local_root_dir = os.path.join(os.path.dirname(__file__), "../test/local/combinations")
2093af6ab5fSopenharmony_ci    runner = CombinationRunner(args.test_filter, local_root_dir, TEST_TYPE)
2103af6ab5fSopenharmony_ci    runner.parse_configs_and_execute()
2113af6ab5fSopenharmony_ci    runner.run_with_node()
2123af6ab5fSopenharmony_ci    runner.content_compare()
2133af6ab5fSopenharmony_ci    runner.print_summary()
2143af6ab5fSopenharmony_ci
2153af6ab5fSopenharmony_ci    runner.try_remove_cache()
2163af6ab5fSopenharmony_ci
2173af6ab5fSopenharmony_ci
2183af6ab5fSopenharmony_ciif __name__ == '__main__':
2193af6ab5fSopenharmony_ci    main()
220