• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2021-2025 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
18from os import path
19from enum import Enum
20import argparse
21import fnmatch
22import multiprocessing
23import os
24import re
25import shutil
26import subprocess
27import sys
28from config import API_VERSION_MAP, ARK_JS_VM_LIST, MIN_SUPPORT_BC_VERSION, MIX_COMPILE_ENTRY_POINT, ES2ABC_API_SUPPORT
29
30
31def is_directory(parser, arg):
32    if not path.isdir(arg):
33        parser.error("The directory '%s' does not exist" % arg)
34
35    return path.abspath(arg)
36
37
38def is_file(parser, arg):
39    if not path.isfile(arg):
40        parser.error("The file '%s' does not exist" % arg)
41
42    return path.abspath(arg)
43
44def prepare_tsc_testcases(test_root):
45    third_party_tsc = path.join(test_root, "TypeScript")
46    ohos_third_party_tsc = path.join(test_root, "../../../../third_party/typescript")
47
48    if not path.isdir(third_party_tsc):
49        if (path.isdir(ohos_third_party_tsc)):
50            return path.abspath(ohos_third_party_tsc)
51        subprocess.run(
52            f"git clone https://gitee.com/openharmony/third_party_typescript.git {third_party_tsc}",
53            shell=True,
54            stdout=subprocess.DEVNULL,
55        )
56    else:
57        subprocess.run(
58            f"cd {third_party_tsc} && git clean -f > /dev/null 2>&1",
59            shell=True,
60            stdout=subprocess.DEVNULL,
61        )
62    return third_party_tsc
63
64def check_timeout(value):
65    ivalue = int(value)
66    if ivalue <= 0:
67        raise argparse.ArgumentTypeError(
68            "%s is an invalid timeout value" % value)
69    return ivalue
70
71
72def get_args():
73    parser = argparse.ArgumentParser(description="Regression test runner")
74    parser.add_argument(
75        'build_dir', type=lambda arg: is_directory(parser, arg),
76        help='panda build directory')
77    parser.add_argument(
78        '--error', action='store_true', dest='error', default=False,
79        help='capture stderr')
80    parser.add_argument(
81        '--abc-to-asm', action='store_true', dest='abc_to_asm',
82        default=False, help='run abc2asm tests')
83    parser.add_argument(
84        '--regression', '-r', action='store_true', dest='regression',
85        default=False, help='run regression tests')
86    parser.add_argument(
87        '--compiler', '-c', action='store_true', dest='compiler',
88        default=False, help='run compiler tests')
89    parser.add_argument(
90        '--tsc', action='store_true', dest='tsc',
91        default=False, help='run tsc tests')
92    parser.add_argument(
93        '--no-progress', action='store_false', dest='progress', default=True,
94        help='don\'t show progress bar')
95    parser.add_argument(
96        '--no-skip', action='store_false', dest='skip', default=True,
97        help='don\'t use skiplists')
98    parser.add_argument(
99        '--update', action='store_true', dest='update', default=False,
100        help='update skiplist')
101    parser.add_argument(
102        '--no-run-gc-in-place', action='store_true', dest='no_gip', default=False,
103        help='enable --run-gc-in-place mode')
104    parser.add_argument(
105        '--filter', '-f', action='store', dest='filter',
106        default="*", help='test filter regexp')
107    parser.add_argument(
108        '--es2panda-timeout', type=check_timeout,
109        dest='es2panda_timeout', default=60, help='es2panda translator timeout')
110    parser.add_argument(
111        '--paoc-timeout', type=check_timeout,
112        dest='paoc_timeout', default=600, help='paoc compiler timeout')
113    parser.add_argument(
114        '--timeout', type=check_timeout,
115        dest='timeout', default=10, help='JS runtime timeout')
116    parser.add_argument(
117        '--gc-type', dest='gc_type', default="g1-gc", help='Type of garbage collector')
118    parser.add_argument(
119        '--aot', action='store_true', dest='aot', default=False,
120        help='use AOT compilation')
121    parser.add_argument(
122        '--no-bco', action='store_false', dest='bco', default=True,
123        help='disable bytecodeopt')
124    parser.add_argument(
125        '--jit', action='store_true', dest='jit', default=False,
126        help='use JIT in interpreter')
127    parser.add_argument(
128        '--arm64-compiler-skip', action='store_true', dest='arm64_compiler_skip', default=False,
129        help='use skiplist for tests failing on aarch64 in AOT or JIT mode')
130    parser.add_argument(
131        '--arm64-qemu', action='store_true', dest='arm64_qemu', default=False,
132        help='launch all binaries in qemu aarch64')
133    parser.add_argument(
134        '--arm32-qemu', action='store_true', dest='arm32_qemu', default=False,
135        help='launch all binaries in qemu arm')
136    parser.add_argument(
137        '--test-list', dest='test_list', default=None, type=lambda arg: is_file(parser, arg),
138        help='run tests listed in file')
139    parser.add_argument(
140        '--aot-args', action='append', dest='aot_args', default=[],
141        help='Additional arguments that will passed to ark_aot')
142    parser.add_argument(
143        '--verbose', '-v', action='store_true', dest='verbose', default=False,
144        help='Enable verbose output')
145    parser.add_argument(
146        '--js-runtime', dest='js_runtime_path', default=None, type=lambda arg: is_directory(parser, arg),
147        help='the path of js vm runtime')
148    parser.add_argument(
149        '--LD_LIBRARY_PATH', dest='ld_library_path', default=None, help='LD_LIBRARY_PATH')
150    parser.add_argument(
151        '--tsc-path', dest='tsc_path', default=None, type=lambda arg: is_directory(parser, arg),
152        help='the path of tsc')
153    parser.add_argument('--hotfix', dest='hotfix', action='store_true', default=False,
154        help='run hotfix tests')
155    parser.add_argument('--hotreload', dest='hotreload', action='store_true', default=False,
156        help='run hotreload tests')
157    parser.add_argument('--coldfix', dest='coldfix', action='store_true', default=False,
158        help='run coldfix tests')
159    parser.add_argument('--coldreload', dest='coldreload', action='store_true', default=False,
160        help='run coldreload tests')
161    parser.add_argument('--base64', dest='base64', action='store_true', default=False,
162        help='run base64 tests')
163    parser.add_argument('--bytecode', dest='bytecode', action='store_true', default=False,
164        help='run bytecode tests')
165    parser.add_argument('--debugger', dest='debugger', action='store_true', default=False,
166        help='run debugger tests')
167    parser.add_argument('--debug', dest='debug', action='store_true', default=False,
168        help='run debug tests')
169    parser.add_argument('--enable-arkguard', action='store_true', dest='enable_arkguard', default=False,
170        help='enable arkguard for compiler tests')
171    parser.add_argument('--aop-transform', dest='aop_transform', action='store_true', default=False,
172        help='run debug tests')
173    parser.add_argument('--version-control', action='store_true', dest='version_control', default=False,
174        help='run api version control tests')
175
176    return parser.parse_args()
177
178
179def run_subprocess_with_beta3(test_obj, cmd):
180    has_target_api = False
181    has_version_12 = False
182    has_sub_version = False
183    is_es2abc_cmd = False
184
185    for param in cmd:
186        if "es2abc" in param:
187            is_es2abc_cmd = True
188        if "--target-api-sub-version" in param:
189            has_sub_version = True
190        if "--target-api-version" in param:
191            has_target_api = True
192        if "12" in param:
193            has_version_12 = True
194    if is_es2abc_cmd and (not has_target_api or (has_version_12 and not has_sub_version)):
195        cmd.append("--target-api-sub-version=beta3")
196    if test_obj:
197        test_obj.log_cmd(cmd)
198    return subprocess.Popen(
199        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
200
201
202class Test:
203    def __init__(self, test_path, flags):
204        self.path = test_path
205        self.flags = flags
206        self.output = None
207        self.error = None
208        self.passed = None
209        self.skipped = None
210        self.reproduce = ""
211
212    def log_cmd(self, cmd):
213        self.reproduce += "\n" + ' '.join(cmd)
214
215    def get_path_to_expected(self):
216        if self.path.find(".d.ts") == -1:
217            return "%s-expected.txt" % (path.splitext(self.path)[0])
218        return "%s-expected.txt" % (self.path[:self.path.find(".d.ts")])
219
220    def run(self, runner):
221        test_abc_name = ("%s.abc" % (path.splitext(self.path)[0])).replace("/", "_")
222        test_abc_path = path.join(runner.build_dir, test_abc_name)
223        cmd = runner.cmd_prefix + [runner.es2panda]
224        cmd.extend(self.flags)
225        cmd.extend(["--output=" + test_abc_path])
226        cmd.append(self.path)
227        process = run_subprocess_with_beta3(self, cmd)
228        out, err = process.communicate()
229        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
230
231        expected_path = self.get_path_to_expected()
232        try:
233            with open(expected_path, 'r') as fp:
234                expected = fp.read()
235            self.passed = expected == self.output and process.returncode in [
236                0, 1]
237        except Exception:
238            self.passed = False
239
240        if not self.passed:
241            self.error = err.decode("utf-8", errors="ignore")
242
243        if os.path.exists(test_abc_path):
244            os.remove(test_abc_path)
245
246        return self
247
248
249class TSCTest(Test):
250    def __init__(self, test_path, flags):
251        Test.__init__(self, test_path, flags)
252        self.options = self.parse_options()
253
254    def parse_options(self):
255        test_options = {}
256
257        with open(self.path, "r", encoding="latin1") as f:
258            lines = f.read()
259            options = re.findall(r"//\s?@\w+:.*\n", lines)
260
261            for option in options:
262                separated = option.split(":")
263                opt = re.findall(r"\w+", separated[0])[0].lower()
264                value = separated[1].strip().lower()
265
266                if opt == "filename":
267                    if opt in options:
268                        test_options[opt].append(value)
269                    else:
270                        test_options[opt] = [value]
271
272                elif opt == "lib" or opt == "module":
273                    test_options[opt] = [each.strip()
274                                         for each in value.split(",")]
275                elif value == "true" or value == "false":
276                    test_options[opt] = value.lower() == "true"
277                else:
278                    test_options[opt] = value
279
280            # TODO: Possibility of error: all exports will be catched, even the commented ones
281            if 'module' not in test_options and re.search(r"export ", lines):
282                test_options['module'] = []
283
284        return test_options
285
286    def run(self, runner):
287        cmd = runner.cmd_prefix + [runner.es2panda, '--parse-only']
288        cmd.extend(self.flags)
289        if "module" in self.options:
290            cmd.append('--module')
291        cmd.append(self.path)
292        process = run_subprocess_with_beta3(self, cmd)
293        out, err = process.communicate()
294        self.output = out.decode("utf-8", errors="ignore")
295
296        self.passed = True if process.returncode == 0 else False
297
298        if not self.passed:
299            self.error = err.decode("utf-8", errors="ignore")
300
301        return self
302
303
304class TestAop:
305    def __init__(self, cmd, compare_str, compare_abc_str, remove_file):
306        self.cmd = cmd
307        self.compare_str = compare_str
308        self.compare_abc_str = compare_abc_str
309        self.remove_file = remove_file
310        self.path = ''
311        self.output = None
312        self.error = None
313        self.passed = None
314        self.skipped = None
315        self.reproduce = ""
316
317    def log_cmd(self, cmd):
318        self.reproduce += ''.join(["\n", ' '.join(cmd)])
319
320    def run(self, runner):
321        cmd = self.cmd
322        process = run_subprocess_with_beta3(self, cmd)
323        out, err = process.communicate()
324        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
325
326        if self.compare_str == '':
327            self.passed = True
328        else :
329            self.passed = self.output.startswith(self.compare_str) and process.returncode in [0, 1]
330            if self.remove_file != '' and os.path.exists(self.remove_file):
331                os.remove(self.remove_file)
332
333        if not self.passed:
334            self.error = err.decode("utf-8", errors="ignore")
335
336        abc_path = path.join(os.getcwd(), 'test_aop.abc')
337        if os.path.exists(abc_path):
338            if self.compare_abc_str != '':
339                with open(abc_path, "r") as abc_file:
340                    self.passed = self.passed and abc_file.read() == self.compare_abc_str
341            os.remove(abc_path)
342
343        return self
344
345
346class Runner:
347    def __init__(self, args, name):
348        self.test_root = path.dirname(path.abspath(__file__))
349        self.args = args
350        self.name = name
351        self.tests = []
352        self.failed = 0
353        self.passed = 0
354        self.es2panda = path.join(args.build_dir, 'es2abc')
355        self.build_dir = args.build_dir
356        self.cmd_prefix = []
357        self.ark_js_vm = ""
358        self.ark_aot_compiler = ""
359        self.ld_library_path = ""
360
361        if args.js_runtime_path:
362            self.ark_js_vm = path.join(args.js_runtime_path, 'ark_js_vm')
363            self.ark_aot_compiler = path.join(args.js_runtime_path, 'ark_aot_compiler')
364
365        if args.ld_library_path:
366            self.ld_library_path = args.ld_library_path
367
368        if args.arm64_qemu:
369            self.cmd_prefix = ["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu/"]
370
371        if args.arm32_qemu:
372            self.cmd_prefix = ["qemu-arm", "-L", "/usr/arm-linux-gnueabi"]
373
374        if not path.isfile(self.es2panda):
375            raise Exception("Cannot find es2panda binary: %s" % self.es2panda)
376
377    def add_directory(self, directory, extension, flags):
378        pass
379
380    def test_path(self, src):
381        pass
382
383    def run_test(self, test):
384        return test.run(self)
385
386    def run(self):
387        pool = multiprocessing.Pool()
388        result_iter = pool.imap_unordered(
389            self.run_test, self.tests, chunksize=32)
390        pool.close()
391
392        if self.args.progress:
393            from tqdm import tqdm
394            result_iter = tqdm(result_iter, total=len(self.tests))
395
396        results = []
397        for res in result_iter:
398            results.append(res)
399
400        self.tests = results
401        pool.join()
402
403    def deal_error(self, test):
404        path_str = test.path
405        err_col = {}
406        if test.error:
407            err_str = test.error.split('[')[0] if "patchfix" not in test.path else " patchfix throw error failed"
408            err_col = {"path" : [path_str], "status": ["fail"], "error" : [test.error], "type" : [err_str]}
409        else:
410            err_col = {"path" : [path_str], "status": ["fail"], "error" : ["Segmentation fault"],
411                        "type" : ["Segmentation fault"]}
412        return err_col
413
414    def summarize(self):
415        print("")
416        fail_list = []
417        success_list = []
418
419        for test in self.tests:
420            assert(test.passed is not None)
421            if not test.passed:
422                fail_list.append(test)
423            else:
424                success_list.append(test)
425
426        if len(fail_list):
427            if self.args.error:
428                import pandas as pd
429                test_list = pd.DataFrame(columns=["path", "status", "error", "type"])
430            for test in success_list:
431                suc_col = {"path" : [test.path], "status": ["success"], "error" : ["success"], "type" : ["success"]}
432                if self.args.error:
433                    test_list = pd.concat([test_list, pd.DataFrame(suc_col)])
434            print("Failed tests:")
435            for test in fail_list:
436                print(self.test_path(test.path))
437
438                if self.args.error:
439                    print("steps:", test.reproduce)
440                    print("error:")
441                    print(test.error)
442                    print("\n")
443                    err_col = self.deal_error(test)
444                    test_list = pd.concat([test_list, pd.DataFrame(err_col)])
445
446            if self.args.error:
447                test_list.to_csv('test_statistics.csv', index=False)
448                test_list["type"].value_counts().to_csv('type_statistics.csv', index_label="error")
449                print("Type statistics:\n", test_list["type"].value_counts())
450            print("")
451
452        print("Summary(%s):" % self.name)
453        print("\033[37mTotal:   %5d" % (len(self.tests)))
454        print("\033[92mPassed:  %5d" % (len(self.tests) - len(fail_list)))
455        print("\033[91mFailed:  %5d" % (len(fail_list)))
456        print("\033[0m")
457
458        return len(fail_list)
459
460
461class RegressionRunner(Runner):
462    def __init__(self, args):
463        Runner.__init__(self, args, "Regression")
464
465    def add_directory(self, directory, extension, flags, func=Test):
466        glob_expression = path.join(
467            self.test_root, directory, "*.%s" % (extension))
468        files = glob(glob_expression)
469        files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
470
471        self.tests += list(map(lambda f: func(f, flags), files))
472
473    def test_path(self, src):
474        return src
475
476
477class AbcToAsmRunner(Runner):
478    def __init__(self, args, is_debug):
479        Runner.__init__(self, args, "Abc2asm" if not is_debug else "Abc2asmDebug")
480        self.is_debug = is_debug
481
482    def add_directory(self, directory, extension, flags, func=Test):
483        glob_expression = path.join(
484            self.test_root, directory, "*.%s" % (extension))
485        files = glob(glob_expression)
486        files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
487
488        self.tests += list(map(lambda f: AbcToAsmTest(f, flags, self.is_debug), files))
489
490    def test_path(self, src):
491        return os.path.basename(src)
492
493
494class AbcToAsmTest(Test):
495    def __init__(self, test_path, flags, is_debug):
496        Test.__init__(self, test_path, flags)
497        self.is_debug = is_debug
498
499    def run(self, runner):
500        output_abc_file = ("%s.abc" % (path.splitext(self.path)[0])).replace("/", "_")
501        # source code compilation, generate an abc file
502        gen_abc_cmd = runner.cmd_prefix + [runner.es2panda]
503        if (self.is_debug):
504            gen_abc_cmd.extend(["--debug-info"])
505        gen_abc_cmd.extend(["--module", "--dump-normalized-asm-program", "--output=" + output_abc_file])
506        gen_abc_cmd.append(self.path)
507        process_gen_abc = run_subprocess_with_beta3(self, gen_abc_cmd)
508        gen_abc_out, gen_abc_err = process_gen_abc.communicate()
509        gen_abc_output = gen_abc_out.decode("utf-8", errors="ignore")
510
511        # If no abc file is generated, an error occurs during parser, but abc2asm function is normal.
512        if not os.path.exists(output_abc_file):
513            self.passed = True
514            return self
515
516        # abc file compilation
517        abc_to_asm_cmd = runner.cmd_prefix + [runner.es2panda]
518        if (self.is_debug):
519            abc_to_asm_cmd.extend(["--debug-info"])
520        abc_to_asm_cmd.extend(["--module", "--dump-normalized-asm-program", "--enable-abc-input"])
521        abc_to_asm_cmd.append(output_abc_file)
522        process_abc_to_asm = run_subprocess_with_beta3(self, abc_to_asm_cmd)
523        abc_to_asm_out, abc_to_asm_err = process_abc_to_asm.communicate()
524        abc_to_asm_output = abc_to_asm_out.decode("utf-8", errors="ignore")
525
526        self.passed = gen_abc_output == abc_to_asm_output and process_abc_to_asm.returncode in [0, 1]
527        if not self.passed:
528            self.error = "Comparison of dump results between source code compilation and abc file compilation failed."
529            if gen_abc_err:
530                self.error += "\n" + gen_abc_err.decode("utf-8", errors="ignore")
531            if abc_to_asm_err:
532                self.error += "\n" + abc_to_asm_err.decode("utf-8", errors="ignore")
533
534        os.remove(output_abc_file)
535        return self
536
537
538class TSCRunner(Runner):
539    def __init__(self, args):
540        Runner.__init__(self, args, "TSC")
541
542        if self.args.tsc_path:
543            self.tsc_path = self.args.tsc_path
544        else :
545            self.tsc_path = prepare_tsc_testcases(self.test_root)
546
547        self.add_directory("conformance", [])
548        self.add_directory("compiler", [])
549
550    def add_directory(self, directory, flags):
551        ts_suite_dir = path.join(self.tsc_path, 'tests/cases')
552
553        glob_expression = path.join(
554            ts_suite_dir, directory, "**/*.ts")
555        files = glob(glob_expression, recursive=True)
556        files = fnmatch.filter(files, ts_suite_dir + '**' + self.args.filter)
557
558        for f in files:
559            test_name = path.basename(f.split(".ts")[0])
560            negative_references = path.join(
561                self.tsc_path, 'tests/baselines/reference')
562            is_negative = path.isfile(path.join(negative_references,
563                                                test_name + ".errors.txt"))
564            test = TSCTest(f, flags)
565
566            if 'target' in test.options:
567                targets = test.options['target'].replace(" ", "").split(',')
568                for target in targets:
569                    if path.isfile(path.join(negative_references,
570                                             test_name + "(target=%s).errors.txt" % (target))):
571                        is_negative = True
572                        break
573
574            if is_negative or "filename" in test.options:
575                continue
576
577            with open(path.join(self.test_root, 'test_tsc_ignore_list.txt'), 'r') as failed_references:
578                if self.args.skip:
579                    if path.relpath(f, self.tsc_path) in failed_references.read():
580                        continue
581
582            self.tests.append(test)
583
584    def test_path(self, src):
585        return src
586
587
588class CompilerRunner(Runner):
589    def __init__(self, args):
590        Runner.__init__(self, args, "Compiler")
591
592    def add_directory(self, directory, extension, flags):
593        if directory.endswith("projects"):
594            projects_path = path.join(self.test_root, directory)
595            for project in os.listdir(projects_path):
596                glob_expression = path.join(projects_path, project, "**/*.%s" % (extension))
597                files = glob(glob_expression, recursive=True)
598                files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
599                self.tests.append(CompilerProjectTest(projects_path, project, files, flags))
600        elif directory.endswith("protobin"):
601            test_path = path.join(self.test_root, directory)
602            for project in os.listdir(test_path):
603                self.tests.append(CompilerProtobinTest(path.join(test_path, project), flags))
604        elif directory.endswith("abc2program"):
605            test_path = path.join(self.test_root, directory)
606            for project in os.listdir(test_path):
607                self.tests.append(CompilerAbcFileTest(path.join(test_path, project), flags))
608        else:
609            glob_expression = path.join(
610                self.test_root, directory, "**/*.%s" % (extension))
611            files = glob(glob_expression, recursive=True)
612            files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
613            self.tests += list(map(lambda f: CompilerTest(f, flags), files))
614
615    def test_path(self, src):
616        return src
617
618
619class CompilerTest(Test):
620    def __init__(self, test_path, flags):
621        Test.__init__(self, test_path, flags)
622
623    def execute_arkguard(self, runner):
624        input_file_path = self.path
625        arkguard_root_dir = os.path.join(runner.test_root, "../../arkguard")
626        arkgurad_entry_path = os.path.join(arkguard_root_dir, "lib/cli/SecHarmony.js")
627        config_path = os.path.join(arkguard_root_dir, "test/compilerTestConfig.json")
628        arkguard_cmd = [
629            'node',
630            '--no-warnings',
631            arkgurad_entry_path,
632            input_file_path,
633            '--config-path',
634            config_path,
635            '--inplace'
636        ]
637        self.log_cmd(arkguard_cmd)
638        process = subprocess.Popen(arkguard_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
639        out, err = process.communicate()
640        process.wait()
641        success = True
642        if err or process.returncode != 0:
643            success = False
644            self.passed = False
645            self.error = err.decode("utf-8", errors="ignore")
646        return success
647
648    def run(self, runner):
649        test_abc_name = ("%s.abc" % (path.splitext(self.path)[0])).replace("/", "_")
650        test_abc_path = path.join(runner.build_dir, test_abc_name)
651        es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
652        es2abc_cmd.extend(self.flags)
653        es2abc_cmd.extend(["--output=" + test_abc_path])
654        relative_path = os.path.relpath(self.path)
655        es2abc_cmd.append(relative_path)
656        enable_arkguard = runner.args.enable_arkguard
657        if enable_arkguard:
658            success = self.execute_arkguard(runner)
659            if not success:
660                return self
661
662        process = run_subprocess_with_beta3(self, es2abc_cmd)
663        out, err = process.communicate()
664        if "--dump-assembly" in self.flags:
665            pa_expected_path = "".join([self.get_path_to_expected()[:self.get_path_to_expected().rfind(".txt")],
666                                       ".pa.txt"])
667            self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
668            try:
669                with open(pa_expected_path, 'r') as fp:
670                    expected = fp.read()
671                self.passed = expected == self.output and process.returncode in [0, 1]
672            except Exception:
673                self.passed = False
674            if not self.passed:
675                self.error = err.decode("utf-8", errors="ignore")
676                if os.path.exists(test_abc_path):
677                    os.remove(test_abc_path)
678                return self
679        if "--dump-debug-info" in self.flags:
680            self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
681            try:
682                with open(self.get_path_to_expected(), 'r') as fp:
683                    expected = fp.read()
684                self.passed = expected == self.output and process.returncode in [0, 1]
685                if os.path.exists(test_abc_path):
686                    os.remove(test_abc_path)
687                return self
688            except Exception:
689                self.passed = False
690            if not self.passed:
691                self.error = err.decode("utf-8", errors="ignore")
692                if os.path.exists(test_abc_path):
693                    os.remove(test_abc_path)
694                return self
695        if err:
696            self.passed = False
697            self.error = err.decode("utf-8", errors="ignore")
698            return self
699
700        ld_library_path = runner.ld_library_path
701        os.environ.setdefault("LD_LIBRARY_PATH", ld_library_path)
702        run_abc_cmd = [runner.ark_js_vm, '--enable-force-gc=false', test_abc_path]
703        self.log_cmd(run_abc_cmd)
704
705        process = subprocess.Popen(run_abc_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
706        out, err = process.communicate()
707        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
708        expected_path = self.get_path_to_expected()
709        try:
710            with open(expected_path, 'r') as fp:
711                expected = fp.read()
712            self.passed = expected == self.output and process.returncode in [0, 1]
713        except Exception:
714            self.passed = False
715
716        if not self.passed:
717            self.error = err.decode("utf-8", errors="ignore")
718
719        os.remove(test_abc_path)
720
721        return self
722
723
724class CompilerAbcFileTest(Test):
725    def __init__(self, test_dir, flags):
726        Test.__init__(self, test_dir, flags)
727        self.test_dir = test_dir
728        self.generated_path = os.path.join(self.test_dir, "gen")
729        if not path.exists(self.generated_path):
730            os.makedirs(self.generated_path)
731        self.original_abc_path = os.path.join(self.generated_path, "original.abc")
732        self.output_path = os.path.join(self.generated_path, "result.abc")
733        self.original_test = os.path.join(self.test_dir, "base.ts")
734        self.expected_path = os.path.join(self.test_dir, "expected.txt")
735
736    def remove_test_build(self, runner):
737        if path.exists(self.generated_path):
738            shutil.rmtree(self.generated_path)
739
740    def gen_abc(self, runner, test_path, output_path, flags):
741        es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
742        es2abc_cmd.extend(['%s%s' % ("--output=", output_path)])
743        es2abc_cmd.extend(flags)
744        es2abc_cmd.append(test_path)
745        process = run_subprocess_with_beta3(self, es2abc_cmd)
746        out, err = process.communicate()
747        if err:
748            self.passed = False
749            self.error = err.decode("utf-8", errors="ignore")
750            self.remove_test_build(runner)
751            return self
752
753    def run(self, runner):
754        new_flags = self.flags
755        # Generate 'abc' from the source file
756        self.gen_abc(runner, self.original_test, self.original_abc_path, new_flags)
757        # Generate 'abc' from the abc file
758        new_flags = self.flags
759        compile_context_info_path = path.join(self.test_dir, "compileContextInfo.json")
760        if path.exists(compile_context_info_path):
761            new_flags.append("%s%s" % ("--compile-context-info=", compile_context_info_path))
762        es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
763        es2abc_cmd.append('%s%s' % ("--output=", self.output_path))
764        es2abc_cmd.append(self.original_abc_path)
765        es2abc_cmd.extend(new_flags)
766        process = run_subprocess_with_beta3(self, es2abc_cmd)
767        out, err = process.communicate()
768        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
769        try:
770            with open(self.expected_path, 'r') as fp:
771                expected = fp.read()
772            self.passed = expected == self.output and process.returncode in [0, 1]
773        except Exception:
774            self.passed = False
775        if not self.passed:
776            self.remove_test_build(runner)
777            return self
778        self.remove_test_build(runner)
779        return self
780
781
782class CompilerProtobinTest(Test):
783    def __init__(self, test_dir, flags):
784        Test.__init__(self, test_dir, flags)
785        self.test_dir = test_dir
786        self.generated_path = os.path.join(self.test_dir, "gen")
787        if not path.exists(self.generated_path):
788            os.makedirs(self.generated_path)
789        self.protobin_path = os.path.join(self.generated_path, "cache.protobin")
790        self.original_abc_path = os.path.join(self.generated_path, "base.abc")
791        self.output_path = os.path.join(self.generated_path, "module.abc")
792        self.original_test = os.path.join(self.test_dir, "base.ts")
793        self.modify_test = os.path.join(self.test_dir, "base_mod.ts")
794        self.expected_path = os.path.join(self.test_dir, "expected.txt")
795
796    def remove_test_build(self, runner):
797        if path.exists(self.generated_path):
798            shutil.rmtree(self.generated_path)
799
800    def gen_merge_abc(self, runner, test_path, need_cache, output_path):
801        es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
802        es2abc_cmd.extend(["--merge-abc"])
803        if need_cache:
804           es2abc_cmd.extend(["--enable-abc-input", '%s%s' % ("--cache-file=", self.protobin_path)])
805        es2abc_cmd.extend(['%s%s' % ("--output=", output_path)])
806        es2abc_cmd.append(test_path)
807        process = run_subprocess_with_beta3(self, es2abc_cmd)
808        out, err = process.communicate()
809        if err:
810            self.passed = False
811            self.error = err.decode("utf-8", errors="ignore")
812            self.remove_test_build(runner)
813            return self
814
815    def run(self, runner):
816        # Generate 'abc' from the source file before modifying it
817        self.gen_merge_abc(runner, self.original_test, False, self.original_abc_path)
818        # Generate protobin from the abc file before modifying it
819        self.gen_merge_abc(runner, self.original_abc_path, True, self.output_path)
820        # Modify the original abc file
821        self.gen_merge_abc(runner, self.modify_test, False, self.original_abc_path)
822        # Compile based on the modified abc file
823        self.gen_merge_abc(runner, self.original_abc_path, True, self.output_path)
824        ld_library_path = runner.ld_library_path
825        os.environ.setdefault("LD_LIBRARY_PATH", ld_library_path)
826        run_abc_cmd = [runner.ark_js_vm, '--entry-point=base', self.output_path]
827        self.log_cmd(run_abc_cmd)
828
829        process = subprocess.Popen(run_abc_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
830        out, err = process.communicate()
831        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
832        try:
833            with open(self.expected_path, 'r') as fp:
834                expected = fp.read()
835            self.passed = expected == self.output and process.returncode in [0, 1]
836        except Exception:
837            self.passed = False
838        if not self.passed:
839            self.remove_test_build(runner)
840            return self
841        self.remove_test_build(runner)
842        return self
843
844
845class CompilerProjectTest(Test):
846    def __init__(self, projects_path, project, test_paths, flags):
847        Test.__init__(self, "", flags)
848        self.projects_path = projects_path
849        self.project = project
850        self.test_paths = test_paths
851        self.files_info_path = os.path.join(os.path.join(self.projects_path, self.project), 'filesInfo.txt')
852        self.files_info_mod_path = os.path.join(os.path.join(self.projects_path, self.project), 'filesInfoModify.txt')
853        # Skip execution if --dump-assembly exists in flags
854        self.requires_execution = "--dump-assembly" not in self.flags
855        self.file_record_mapping = None
856        self.generated_abc_inputs_path = os.path.join(os.path.join(self.projects_path, self.project), "abcinputs_gen")
857        self.abc_input_filenames = None
858        self.protoBin_file_path = ""
859        self.record_names_path = os.path.join(os.path.join(self.projects_path, self.project), 'recordnames.txt')
860        self.abc_inputs_path = os.path.join(os.path.join(self.projects_path, self.project), 'abcinputs')
861        # Modify the hap file path
862        self.project_mod_path = os.path.join(os.path.join(self.projects_path, self.project), 'mod')
863        self.modules_cache_path = os.path.join(os.path.join(self.projects_path, self.project), 'modulescache.cache')
864        self.deps_json_path = os.path.join(os.path.join(self.projects_path, self.project), 'deps-json.json')
865        # Merge hap need to modify package name
866        self.modifyPkgNamePath = os.path.join(os.path.join(self.projects_path, self.project), 'modify_pkg_name.txt')
867
868    def remove_project(self, runner):
869        project_path = runner.build_dir + "/" + self.project
870        if path.exists(project_path):
871            shutil.rmtree(project_path)
872        if path.exists(self.files_info_path):
873            os.remove(self.files_info_path)
874        if path.exists(self.files_info_mod_path):
875            os.remove(self.files_info_mod_path)
876        if path.exists(self.generated_abc_inputs_path):
877            shutil.rmtree(self.generated_abc_inputs_path)
878        if path.exists(self.protoBin_file_path):
879            os.remove(self.protoBin_file_path)
880        if path.exists(self.modules_cache_path):
881            self.remove_cache_files()
882
883    def remove_cache_files(self):
884        if path.exists(self.modules_cache_path):
885            with open(self.modules_cache_path) as cache_fp:
886                cache_lines = cache_fp.readlines()
887                for cache_line in cache_lines:
888                    cache_file_path = cache_line[:-1].split(";")[1]
889                    if path.exists(cache_file_path):
890                        os.remove(cache_file_path)
891            os.remove(self.modules_cache_path)
892
893    def get_file_absolute_path_and_name(self, runner):
894        sub_path = self.path[len(self.projects_path):]
895        file_relative_path = path.split(sub_path)[0]
896        file_name = path.split(sub_path)[1]
897        file_absolute_path = runner.build_dir + "/" + file_relative_path
898        return [file_absolute_path, file_name]
899
900    def gen_single_abc(self, runner):
901        for test_path in self.test_paths:
902            self.path = test_path
903            [file_absolute_path, file_name] = self.get_file_absolute_path_and_name(runner)
904            if not path.exists(file_absolute_path):
905                os.makedirs(file_absolute_path)
906
907            test_abc_name = ("%s.abc" % (path.splitext(file_name)[0]))
908            test_abc_path = path.join(file_absolute_path, test_abc_name)
909            es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
910            es2abc_cmd.extend(self.flags)
911            es2abc_cmd.extend(['%s%s' % ("--output=", test_abc_path)])
912            es2abc_cmd.append(self.path)
913            process = run_subprocess_with_beta3(self, es2abc_cmd)
914            out, err = process.communicate()
915            if err:
916                self.passed = False
917                self.error = err.decode("utf-8", errors="ignore")
918                self.remove_project(runner)
919                return self
920
921    def collect_record_mapping(self):
922        # Collect record mappings from recordnames.txt, file format:
923        # 'source_file_name:record_name\n' * n
924        if path.exists(self.record_names_path):
925            with open(self.record_names_path) as mapping_fp:
926                mapping_lines = mapping_fp.readlines()
927                self.file_record_mapping = {}
928                for mapping_line in mapping_lines:
929                    cur_mapping = mapping_line[:-1].split(":")
930                    self.file_record_mapping[cur_mapping[0]] = cur_mapping[1]
931
932    def get_record_name(self, test_path):
933        record_name = os.path.relpath(test_path, os.path.dirname(self.files_info_path)).split('.')[0]
934        if (self.file_record_mapping is not None and record_name in self.file_record_mapping):
935            record_name = self.file_record_mapping[record_name]
936        return record_name
937
938    def collect_abc_inputs(self, runner):
939        # Collect abc input information from the 'abcinputs' directory. Each txt file in the directory
940        # will generate a merged abc file with the same filename and serve as the final abc input.
941        # file format: 'source_file_name.ts\n' * n
942        if not path.exists(self.abc_inputs_path):
943            return
944        if not path.exists(self.generated_abc_inputs_path):
945            os.makedirs(self.generated_abc_inputs_path)
946        self.abc_input_filenames = {}
947        filenames = os.listdir(self.abc_inputs_path)
948        for filename in filenames:
949            if not filename.endswith('.txt'):
950                self.remove_project(runner)
951                raise Exception("Invalid abc input file: %s, only txt files are allowed in abcinputs directory: %s"
952                                % (filename, self.abc_inputs_path))
953            with open(path.join(self.abc_inputs_path, filename)) as abc_inputs_fp:
954                abc_inputs_lines = abc_inputs_fp.readlines()
955                for abc_input_line in abc_inputs_lines:
956                    # filename is 'xxx.txt', remove '.txt' here
957                    self.abc_input_filenames[abc_input_line[:-1]] = filename[:-len('.txt')]
958
959    def get_belonging_abc_input(self, test_path):
960        filename = os.path.relpath(test_path, os.path.dirname(self.files_info_path))
961        if (self.abc_input_filenames is not None and filename in self.abc_input_filenames):
962            return self.abc_input_filenames[filename]
963        return None
964
965    def gen_abc_input_files_infos(self, runner, abc_files_infos, final_file_info_f, mod_files_info):
966        for abc_files_info_name in abc_files_infos:
967            abc_files_info = abc_files_infos[abc_files_info_name]
968            if len(abc_files_info) != 0:
969                abc_input_path = path.join(self.generated_abc_inputs_path, abc_files_info_name)
970                abc_files_info_path = ("%s-filesInfo.txt" % (abc_input_path))
971                abc_files_info_fd = os.open(abc_files_info_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC)
972                abc_files_info_f = os.fdopen(abc_files_info_fd, 'w')
973                abc_files_info_f.writelines(abc_files_info)
974                abc_line = '%s-abcinput.abc;;;;%s;\n' % (abc_input_path, abc_files_info_name)
975                mod_files_info.append(abc_line)
976                final_file_info_f.writelines(abc_line)
977
978    def gen_files_info(self, runner):
979        # After collect_record_mapping, self.file_record_mapping stores {'source file name' : 'source file record name'}
980        self.collect_record_mapping()
981        # After collect_abc_inputs, self.abc_input_filenames stores {'source file name' : 'belonging abc input name'}
982        self.collect_abc_inputs(runner)
983
984        fd = os.open(self.files_info_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC)
985        f = os.fdopen(fd, 'w')
986        mod_files_info = []
987        abc_files_infos = {}
988        for test_path in self.test_paths:
989            record_name = self.get_record_name(test_path)
990            module_kind = 'esm'
991            if (os.path.basename(test_path).startswith("commonjs")):
992                module_kind = 'commonjs'
993            is_shared_module = 'false'
994            if (os.path.basename(test_path).startswith("sharedmodule")):
995                is_shared_module = 'true'
996            file_info = ('%s;%s;%s;%s;%s;%s\n' % (test_path, record_name, module_kind,
997                                               os.path.relpath(test_path, self.projects_path), record_name,
998                                               is_shared_module))
999            belonging_abc_input = self.get_belonging_abc_input(test_path)
1000            if belonging_abc_input is not None:
1001                if not belonging_abc_input in abc_files_infos:
1002                    abc_files_infos[belonging_abc_input] = []
1003                abc_files_infos[belonging_abc_input].append(file_info)
1004            elif test_path.startswith(self.project_mod_path):
1005                mod_files_info.append(file_info)
1006            else:
1007                mod_files_info.append(file_info)
1008                f.writelines(file_info)
1009        if (os.path.exists(self.deps_json_path)):
1010            record_name = self.get_record_name(self.deps_json_path)
1011            file_info = ('%s;%s;%s;%s;%s;%s\n' % (self.deps_json_path, record_name, 'esm',
1012                                               os.path.relpath(self.deps_json_path, self.projects_path), record_name,
1013                                               'false'))
1014            f.writelines(file_info)
1015        self.gen_abc_input_files_infos(runner, abc_files_infos, f, mod_files_info)
1016        f.close()
1017        if (os.path.exists(self.project_mod_path)):
1018            mod_fd = os.open(self.files_info_mod_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC)
1019            mod_f = os.fdopen(mod_fd, 'w')
1020            for file_line in mod_files_info:
1021                mod_f.writelines(file_line)
1022            mod_f.close()
1023
1024    def gen_modules_cache(self, runner):
1025        if "--cache-file" not in self.flags or "--file-threads=0" in self.flags:
1026            return
1027        fd = os.open(self.modules_cache_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC)
1028        f = os.fdopen(fd, 'w')
1029        abc_files = set()
1030        for test_path in self.test_paths:
1031            cache_info = ('%s;%s\n' % (test_path, f"{test_path.rsplit('.', 1)[0]}.protobin"))
1032            belonging_abc_input = self.get_belonging_abc_input(test_path)
1033            if belonging_abc_input is not None:
1034                abc_files.add(belonging_abc_input)
1035            else:
1036                f.writelines(cache_info)
1037        for abc_path in abc_files:
1038            abc_input_path = f"{path.join(self.generated_abc_inputs_path, abc_path)}-abcinput.abc"
1039            cache_info = ('%s;%s\n' % (abc_input_path, f"{abc_input_path.rsplit('.', 1)[0]}.protobin"))
1040            f.writelines(cache_info)
1041        f.close()
1042
1043    def gen_es2abc_cmd(self, runner, input_file, output_file):
1044        es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
1045
1046        new_flags = self.flags
1047        if "--cache-file" in new_flags and len(self.test_paths) == 1:
1048            # Generate cache-file test case in single thread
1049            new_flags.remove("--cache-file")
1050            protobin_path = f"{self.test_paths[0].rsplit('.', 1)[0]}.protobin"
1051            self.protoBin_file_path = protobin_path
1052            es2abc_cmd.append('--cache-file=%s' % (protobin_path))
1053        elif "--cache-file" in self.flags and output_file.endswith("-abcinput.abc"):
1054            # Generate abc for bytecode har
1055            new_flags = list(filter(lambda x: x != "--cache-file", new_flags))
1056        elif "--cache-file" in self.flags:
1057            new_flags = list(filter(lambda x: x != "--cache-file", new_flags))
1058            es2abc_cmd.append('--cache-file')
1059            es2abc_cmd.append('@%s' % (self.modules_cache_path))
1060
1061        es2abc_cmd.extend(new_flags)
1062        es2abc_cmd.extend(['%s%s' % ("--output=", output_file)])
1063        es2abc_cmd.append(input_file)
1064        return es2abc_cmd
1065
1066    def gen_merged_abc_for_abc_input(self, runner, files_info_name):
1067        self.passed = True
1068        if not files_info_name.endswith(".txt"):
1069            return
1070        abc_input_files_info_path = path.join(self.generated_abc_inputs_path, files_info_name)
1071        abc_input_merged_abc_path = path.join(self.generated_abc_inputs_path,
1072                                              '%s-abcinput.abc' % (files_info_name[:-len('-filesInfo.txt')]))
1073
1074        abc_input_file_path = '@' + abc_input_files_info_path
1075        if "unmerged_abc_input" in self.generated_abc_inputs_path:
1076            self.flags.remove("--merge-abc")
1077            with open(abc_input_files_info_path, 'r') as fp:
1078                abc_input_file_path = fp.read().split(';')[0]
1079
1080        es2abc_cmd = self.gen_es2abc_cmd(runner, abc_input_file_path, abc_input_merged_abc_path)
1081        process = run_subprocess_with_beta3(self, es2abc_cmd)
1082        out, err = process.communicate()
1083        if err:
1084            self.passed = False
1085            self.error = err.decode("utf-8", errors="ignore")
1086
1087    def gen_merged_abc(self, runner):
1088        output_abc_name = ""
1089        exec_file_path = ""
1090        # Generate abc inputs
1091        if (os.path.exists(self.generated_abc_inputs_path)):
1092            files_info_names = os.listdir(self.generated_abc_inputs_path)
1093            for filename in files_info_names:
1094                self.gen_merged_abc_for_abc_input(runner, filename)
1095                if (not self.passed):
1096                    self.remove_project(runner)
1097                    return self
1098        # Generate the abc to be tested
1099        for test_path in self.test_paths:
1100            self.path = test_path
1101            if (self.path.endswith("-exec.ts")) or (self.path.endswith("-exec.js")):
1102                exec_file_path = self.path
1103                [file_absolute_path, file_name] = self.get_file_absolute_path_and_name(runner)
1104                if not path.exists(file_absolute_path):
1105                    os.makedirs(file_absolute_path)
1106                test_abc_name = ("%s.abc" % (path.splitext(file_name)[0]))
1107                output_abc_name = path.join(file_absolute_path, test_abc_name)
1108        if "merge_hap" in self.projects_path:
1109            exec_file_path = os.path.join(self.projects_path, self.project)
1110            exec_file_path = os.path.join(exec_file_path, "main_hap")
1111            [file_absolute_path, file_name] = self.get_file_absolute_path_and_name(runner)
1112            if not path.exists(file_absolute_path):
1113                os.makedirs(file_absolute_path)
1114            output_abc_name = path.join(file_absolute_path, "merge_hap.abc")
1115        # reverse merge-abc flag
1116        if "merge_abc_consistence_check" in self.path:
1117            if "--merge-abc" in self.flags:
1118                self.flags.remove("--merge-abc")
1119            else:
1120                self.flags.append("--merge-abc")
1121
1122        es2abc_cmd = self.gen_es2abc_cmd(runner, '@' + self.files_info_path, output_abc_name)
1123        if "--cache-file" in self.flags and len(self.test_paths) == 1:
1124            es2abc_cmd = self.gen_es2abc_cmd(runner, self.test_paths[0], output_abc_name)
1125        else:
1126            es2abc_cmd = self.gen_es2abc_cmd(runner, '@' + self.files_info_path, output_abc_name)
1127        compile_context_info_path = path.join(path.join(self.projects_path, self.project), "compileContextInfo.json")
1128        if path.exists(compile_context_info_path):
1129            es2abc_cmd.append("%s%s" % ("--compile-context-info=", compile_context_info_path))
1130        if path.exists(self.modifyPkgNamePath):
1131            with open(self.modifyPkgNamePath, 'r') as file:
1132                modifyPkgName = file.readline().rstrip('\n')
1133                pkgNames = modifyPkgName.split(":")
1134                es2abc_cmd.append("--src-package-name=%s" % pkgNames[0])
1135                es2abc_cmd.append("--dst-package-name=%s" % pkgNames[1])
1136        process = run_subprocess_with_beta3(self, es2abc_cmd)
1137        self.path = exec_file_path
1138        out, err = [None, None]
1139        # Check single-thread execution timeout when required
1140        if "--file-threads=0" in self.flags:
1141            try:
1142                out, err = process.communicate(timeout=60)
1143            except:
1144                process.kill()
1145                print("Generating the abc file timed out.")
1146        else:
1147            out, err = process.communicate()
1148
1149        if "non-merged-abc"  in self.path and "--merge-abc" in es2abc_cmd:
1150            es2abc_cmd.remove("--merge-abc")
1151
1152        if "--cache-file" in self.flags:
1153            # Firstly generate cache file, and generate abc from cache file
1154            if (os.path.exists(self.project_mod_path)):
1155                es2abc_cmd = self.gen_es2abc_cmd(runner, '@' + self.files_info_mod_path, output_abc_name)
1156                compile_context_info_path = path.join(path.join(self.projects_path, self.project), "compileContextInfo.json")
1157                if path.exists(compile_context_info_path):
1158                    es2abc_cmd.append("%s%s" % ("--compile-context-info=", compile_context_info_path))
1159                process = run_subprocess_with_beta3(self, es2abc_cmd)
1160                out, err = process.communicate()
1161            else:
1162                process = run_subprocess_with_beta3(self, es2abc_cmd)
1163                out, err = process.communicate()
1164
1165        # restore merge-abc flag
1166        if "merge_abc_consistence_check" in self.path and "--merge-abc" not in self.flags:
1167            self.flags.append("--merge-abc")
1168
1169        # Check dump-assembly outputs when required
1170        if "--dump-assembly" in self.flags:
1171            pa_expected_path = "".join([self.get_path_to_expected()[:self.get_path_to_expected().rfind(".txt")],
1172                                        ".pa.txt"])
1173            self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
1174            if "merge_abc_consistence_check" in self.path:
1175                self.output = self.output.split('.')[0]
1176            try:
1177                with open(pa_expected_path, 'r') as fp:
1178                    expected = fp.read()
1179                self.passed = expected == self.output and process.returncode in [0, 1]
1180            except Exception:
1181                self.passed = False
1182            if not self.passed:
1183                self.error = err.decode("utf-8", errors="ignore")
1184                self.remove_project(runner)
1185                return self
1186            else:
1187                return self
1188
1189        if err:
1190            self.passed = False
1191            self.error = err.decode("utf-8", errors="ignore")
1192            self.remove_project(runner)
1193            return self
1194
1195    def run(self, runner):
1196        # Compile all ts source files in the project to abc files.
1197        if ("--merge-abc" in self.flags):
1198            self.gen_files_info(runner)
1199            self.gen_modules_cache(runner)
1200            self.gen_merged_abc(runner)
1201        else:
1202            self.gen_single_abc(runner)
1203
1204        if (not self.requires_execution):
1205            self.remove_project(runner)
1206            return self
1207
1208        # Run test files that need to be executed in the project.
1209        for test_path in self.test_paths:
1210            self.path = test_path
1211            if self.path.endswith("-exec.ts"):
1212                [file_absolute_path, file_name] = self.get_file_absolute_path_and_name(runner)
1213
1214                entry_point_name = path.splitext(file_name)[0]
1215                test_abc_name = ("%s.abc" % entry_point_name)
1216                test_abc_path = path.join(file_absolute_path, test_abc_name)
1217
1218                ld_library_path = runner.ld_library_path
1219                os.environ.setdefault("LD_LIBRARY_PATH", ld_library_path)
1220                run_abc_cmd = [runner.ark_js_vm]
1221                if ("--merge-abc" in self.flags):
1222                    run_abc_cmd.extend(["--entry-point", entry_point_name])
1223                run_abc_cmd.extend([test_abc_path])
1224                self.log_cmd(run_abc_cmd)
1225
1226                process = subprocess.Popen(run_abc_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1227                out, err = process.communicate()
1228                self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
1229                expected_path = self.get_path_to_expected()
1230                try:
1231                    with open(expected_path, 'r') as fp:
1232                        expected = fp.read()
1233                    self.passed = expected == self.output and process.returncode in [0, 1]
1234                except Exception:
1235                    self.passed = False
1236
1237                if not self.passed:
1238                    self.error = err.decode("utf-8", errors="ignore")
1239                    self.remove_project(runner)
1240                    return self
1241
1242            self.passed = True
1243
1244        self.remove_project(runner)
1245        return self
1246
1247
1248class FilesInfoRunner(Runner):
1249    def __init__(self, args):
1250        Runner.__init__(self, args, "FilesInfo")
1251
1252    def add_directory(self, directory, extension, flags):
1253        projects_path = path.join(self.test_root, directory)
1254        test_projects = ["base", "mod"]
1255        for project in test_projects:
1256            project_dir = path.join(projects_path, project)
1257            if not path.isdir(project_dir):
1258                continue
1259            filesinfo_path = path.join(project_dir, "filesInfo.txt")
1260            self.tests.append(FilesInfoTest(projects_path, project, filesinfo_path, flags))
1261
1262    def test_path(self, src):
1263        return src
1264
1265
1266class FilesInfoTest(Test):
1267    LONG_PATH_MARKER = "long_path_filesinfo"
1268
1269    def __init__(self, projects_path, project, filesinfo_path, flags):
1270        Test.__init__(self, "", flags)
1271        self.projects_path = projects_path
1272        self.output_path = path.join(projects_path, "output")
1273        self.project = project
1274        self.origin_filesinfo_path = filesinfo_path
1275        self.files_info_path = path.join(self.output_path, self.project + "filesInfo.txt")
1276        self.path = path.join(self.projects_path, self.project)
1277        self.symbol_table_file = os.path.join(self.output_path, 'base.map')
1278        self.output_abc_name = path.join(self.output_path, self.project + ".abc")
1279        self.output_abc_name_of_input_abc = path.join(self.output_path, self.project + "_input.abc")
1280        self.is_long_path_case = self.LONG_PATH_MARKER in self.path
1281
1282        if not path.exists(self.output_path):
1283            os.makedirs(self.output_path)
1284
1285    def gen_files_info(self):
1286        with open(self.origin_filesinfo_path, 'r') as src, open(self.files_info_path, 'w') as dst:
1287            for line in src:
1288                dst.write(f"{path.join(self.projects_path, self.project)}/{line}")
1289
1290    def remove_output(self):
1291        shutil.rmtree(self.output_path)
1292
1293    def remove_project(self, runner):
1294        if self.project == "mod": # clear after all tests
1295            self.remove_output()
1296
1297    def gen_es2abc_cmd(self, runner, input_file, output_file):
1298        es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
1299        es2abc_cmd.extend(self.flags)
1300        es2abc_cmd.extend(['%s%s' % ("--output=", output_file)])
1301        es2abc_cmd.append(input_file)
1302        if ("base" == self.project):
1303            es2abc_cmd.extend(['--dump-symbol-table', self.symbol_table_file])
1304        else:
1305            es2abc_cmd.extend(['--input-symbol-table', self.symbol_table_file])
1306        return es2abc_cmd
1307
1308    def gen_es2abc_cmd_input_abc(self, runner, input_file, output_file):
1309        es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
1310        es2abc_cmd.extend(self.flags)
1311        es2abc_cmd.extend(['%s%s' % ("--output=", output_file), "--enable-abc-input"])
1312        es2abc_cmd.append(input_file)
1313        return es2abc_cmd
1314
1315    def get_long_path_filesinfo_path(self):
1316        long_dir = "a" * 4096
1317        return path.join(self.projects_path, self.project, long_dir, "filesInfo.txt")
1318
1319    def replace_expected_file_root(self, expected):
1320        full_root_path = os.path.abspath(path.join(self.projects_path, self.project))
1321        return expected.replace("{{EXPECTED_FILE_ROOT}}", full_root_path)
1322
1323    def gen_merged_abc(self, runner):
1324        # Generate long path filesinfo if it is path contains long path
1325        if self.is_long_path_case:
1326            self.files_info_path = self.get_long_path_filesinfo_path()
1327
1328        # Generate the abc to be tested
1329        es2abc_cmd = self.gen_es2abc_cmd(runner, '@' + self.files_info_path, self.output_abc_name)
1330        process = run_subprocess_with_beta3(self, es2abc_cmd)
1331        out, err = process.communicate()
1332
1333        # Gen abc and verify it
1334        pa_expected_path = "".join([self.get_path_to_expected()[:self.get_path_to_expected().rfind(".txt")],
1335                                    ".pa.txt"])
1336        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
1337        try:
1338            with open(pa_expected_path, 'r') as fp:
1339                expected = fp.read()
1340            if self.is_long_path_case:
1341                expected = self.replace_expected_file_root(expected)
1342            self.passed = expected == self.output and process.returncode in [0, 1]
1343        except Exception:
1344            self.passed = False
1345        if not self.passed:
1346            self.error = err.decode("utf-8", errors="ignore")
1347            return self
1348
1349        # Input abc and verify it when it is base.
1350        if self.project == "base":
1351            self.input_abc(runner)
1352        return self
1353
1354    def input_abc(self, runner):
1355        es2abc_cmd = self.gen_es2abc_cmd_input_abc(runner, self.output_abc_name, self.output_abc_name_of_input_abc)
1356        process = run_subprocess_with_beta3(self, es2abc_cmd)
1357        out, err = process.communicate()
1358        pa_expected_path = "".join([self.path, "input_base-expected.pa.txt"])
1359        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
1360        try:
1361            with open(pa_expected_path, 'r') as fp:
1362                expected = fp.read()
1363            self.passed = expected == self.output and process.returncode in [0, 1]
1364        except Exception:
1365            self.passed = False
1366        if not self.passed:
1367            self.error = err.decode("utf-8", errors="ignore")
1368            return self
1369
1370
1371    def run(self, runner):
1372        self.gen_files_info()
1373        self.gen_merged_abc(runner)
1374        self.remove_project(runner)
1375        return self
1376
1377
1378class TSDeclarationTest(Test):
1379    def get_path_to_expected(self):
1380        file_name = self.path[:self.path.find(".d.ts")]
1381        return "%s-expected.txt" % file_name
1382
1383
1384class BcVersionRunner(Runner):
1385    def __init__(self, args):
1386        Runner.__init__(self, args, "Target bc version")
1387        self.ts2abc = path.join(self.test_root, '..', 'scripts', 'ts2abc.js')
1388
1389    def add_cmd(self):
1390        api_sub_version_list = ["beta1", "beta2", "beta3"]
1391        for api_version in range(8, 17):
1392            cmd = self.cmd_prefix + [self.es2panda]
1393            cmd += ["--target-bc-version"]
1394            cmd += ["--target-api-version"]
1395            cmd += [str(api_version)]
1396            self.tests += [BcVersionTest(cmd, api_version)]
1397            node_cmd = ["node"] + [self.ts2abc]
1398            node_cmd += ["".join(["es2abc=", self.es2panda])]
1399            node_cmd += ["--target-api-version"]
1400            node_cmd += [str(api_version)]
1401            self.tests += [BcVersionTest(node_cmd, api_version)]
1402
1403            # Add tests for "--target-api-sub-version" option
1404            if api_version == 12:
1405                for api_sub_version in api_sub_version_list:
1406                    new_cmd = cmd.copy()
1407                    new_cmd += ["--target-api-sub-version", api_sub_version]
1408                    self.tests += [BcVersionTest(new_cmd, str(api_version) + '_' + api_sub_version)]
1409                    new_node_cmd = node_cmd.copy()
1410                    new_node_cmd += ["--target-api-sub-version", api_sub_version]
1411                    self.tests += [BcVersionTest(new_node_cmd, str(api_version) + '_' + api_sub_version)]
1412
1413    def run(self):
1414        for test in self.tests:
1415            test.run()
1416
1417
1418class BcVersionTest(Test):
1419    def __init__(self, cmd, api_version):
1420        Test.__init__(self, "", 0)
1421        self.cmd = cmd
1422        self.api_version = api_version
1423        # To avoid problems when api version is upgraded abruptly,
1424        # the corresponding bytecode version of the api version not written in isa.yaml is alaways the newest version.
1425        self.bc_version_expect = {
1426            8: "13.0.1.0",
1427            9: "9.0.0.0",
1428            10: "9.0.0.0",
1429            11: "11.0.2.0",
1430            12: "12.0.2.0",
1431            "12_beta1": "12.0.2.0",
1432            "12_beta2": "12.0.2.0",
1433            "12_beta3": "12.0.6.0",
1434            13: "12.0.6.0",
1435            14: "12.0.6.0",
1436            15: "12.0.6.0",
1437            16: "12.0.6.0",
1438            17: "12.0.6.0",
1439            18: "13.0.1.0",
1440            19: "13.0.1.0",
1441            20: "13.0.1.0",
1442        }
1443        self.es2abc_script_expect = {
1444            8: "0.0.0.2",
1445            9: "9.0.0.0",
1446            10: "9.0.0.0",
1447            11: "11.0.2.0",
1448            12: "12.0.2.0",
1449            "12_beta1": "12.0.2.0",
1450            "12_beta2": "12.0.2.0",
1451            "12_beta3": "12.0.6.0",
1452            13: "12.0.6.0",
1453            14: "12.0.6.0",
1454            15: "12.0.6.0",
1455            16: "12.0.6.0",
1456            17: "12.0.6.0",
1457            18: "13.0.1.0",
1458            19: "13.0.1.0",
1459            20: "13.0.1.0",
1460        }
1461
1462    def run(self):
1463        process = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1464        out, err = process.communicate()
1465        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
1466        if self.cmd[0] == "node":
1467            self.passed = self.es2abc_script_expect.get(self.api_version) == self.output and process.returncode in [0, 1]
1468        else:
1469            self.passed = self.bc_version_expect.get(self.api_version) == self.output and process.returncode in [0, 1]
1470        if not self.passed:
1471            self.error = err.decode("utf-8", errors="ignore")
1472        return self
1473
1474
1475class TransformerRunner(Runner):
1476    def __init__(self, args):
1477        Runner.__init__(self, args, "Transformer")
1478
1479    def add_directory(self, directory, extension, flags):
1480        glob_expression = path.join(
1481            self.test_root, directory, "**/*.%s" % (extension))
1482        files = glob(glob_expression, recursive=True)
1483        files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
1484
1485        self.tests += list(map(lambda f: TransformerTest(f, flags), files))
1486
1487    def test_path(self, src):
1488        return src
1489
1490
1491class TransformerInTargetApiVersion10Runner(Runner):
1492    def __init__(self, args):
1493        Runner.__init__(self, args, "TransformerInTargetApiVersion10")
1494
1495    def add_directory(self, directory, extension, flags):
1496        glob_expression = path.join(
1497            self.test_root, directory, "**/*.%s" % (extension))
1498        files = glob(glob_expression, recursive=True)
1499        files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
1500
1501        self.tests += list(map(lambda f: TransformerTest(f, flags), files))
1502
1503    def test_path(self, src):
1504        return src
1505
1506
1507class TransformerTest(Test):
1508    def __init__(self, test_path, flags):
1509        Test.__init__(self, test_path, flags)
1510
1511    def get_path_to_expected(self):
1512        return "%s-transformed-expected.txt" % (path.splitext(self.path)[0])
1513
1514    def run(self, runner):
1515        cmd = runner.cmd_prefix + [runner.es2panda]
1516        cmd.extend(self.flags)
1517        cmd.append(self.path)
1518        process = run_subprocess_with_beta3(self, cmd)
1519        out, err = process.communicate()
1520        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
1521
1522        expected_path = self.get_path_to_expected()
1523        try:
1524            with open(expected_path, 'r') as fp:
1525                expected = fp.read()
1526            self.passed = expected == self.output and process.returncode in [0, 1]
1527        except Exception:
1528            self.passed = False
1529
1530        if not self.passed:
1531            self.error = err.decode("utf-8", errors="ignore")
1532
1533        return self
1534
1535
1536class PatchTest(Test):
1537    def __init__(self, test_path, mode_arg, target_version, preserve_files):
1538        Test.__init__(self, test_path, "")
1539        self.mode = mode_arg
1540        self.target_version = target_version
1541        self.preserve_files = preserve_files
1542
1543    def is_ts_test_cases(self):
1544        return 'ts_test_cases' in os.path.normpath(self.path).split(os.sep)
1545
1546    def need_to_merge(self):
1547        # If the test case is in the 'export and import' directory, it needs to be merged.
1548        if os.path.split(self.path)[-2] == 'export-and-import':
1549            return True
1550        else:
1551            return False
1552
1553    def gen_files_infos(self, modified=False):
1554        if not self.need_to_merge():
1555            return
1556
1557        file_suffix = '.ts' if self.is_ts_test_cases() else '.js'
1558        file_name_list = []
1559        for file_name in os.listdir(self.path):
1560            if file_name.endswith(file_suffix):
1561                if '_mod' in file_name and modified:
1562                    file_name_list.append(file_name)
1563                elif not '_mod' in file_name and not modified:
1564                    file_name_list.append(file_name)
1565
1566        files_info_txt = os.path.join(self.path, 'filesInfo.txt')
1567        with open(files_info_txt, 'w', encoding='utf-8') as file:
1568            for file_name in file_name_list:
1569                file_path = os.path.join(self.path, file_name)
1570                file_prev, file_extension = os.path.splitext(file_name)
1571                abc_file_path = os.path.join(self.path, f"{file_prev}.abc")
1572                file.write(f'{file_path};{file_name};{file_name};esm;{file_name};false\n')
1573
1574    def gen_cmd(self, runner):
1575        symbol_table_file = os.path.join(self.path, 'base.map')
1576        origin_input_file = 'base.js' if not self.is_ts_test_cases() else 'base.ts'
1577        origin_output_abc = os.path.join(self.path, 'base.abc')
1578        modified_input_file = 'base_mod.js' if not self.is_ts_test_cases() else 'base_mod.ts'
1579        modified_output_abc = os.path.join(self.path, 'patch.abc')
1580        files_info_txt = os.path.join(self.path, 'filesInfo.txt')
1581        target_version_cmd = ""
1582        if self.target_version > 0:
1583            target_version_cmd = "--target-api-version=" + str(self.target_version)
1584
1585        gen_base_cmd = runner.cmd_prefix + [runner.es2panda, '--module', target_version_cmd]
1586        if self.need_to_merge():
1587            gen_base_cmd = gen_base_cmd.append('--merge-abc')
1588        if 'record-name-with-dots' in os.path.basename(self.path):
1589            gen_base_cmd.extend(['--merge-abc', '--record-name=record.name.with.dots'])
1590        gen_base_cmd.extend(['--dump-symbol-table', symbol_table_file])
1591        gen_base_cmd.extend(['--output', origin_output_abc])
1592        if not self.need_to_merge():
1593            gen_base_cmd.extend([os.path.join(self.path, origin_input_file)])
1594        else:
1595            gen_base_cmd.extend(['--debug-info', f'@{files_info_txt}'])
1596        self.log_cmd(gen_base_cmd)
1597
1598        if self.mode == 'hotfix':
1599            mode_arg = ["--generate-patch"]
1600        elif self.mode == 'hotreload':
1601            mode_arg = ["--hot-reload"]
1602        elif self.mode == 'coldfix':
1603            mode_arg = ["--generate-patch", "--cold-fix"]
1604        elif self.mode == 'coldreload':
1605            mode_arg = ["--cold-reload"]
1606
1607        patch_test_cmd = runner.cmd_prefix + [runner.es2panda, '--module', target_version_cmd]
1608        if self.need_to_merge():
1609            patch_test_cmd = patch_test_cmd.append('--merge-abc')
1610        patch_test_cmd.extend(mode_arg)
1611        patch_test_cmd.extend(['--input-symbol-table', symbol_table_file])
1612        patch_test_cmd.extend(['--output', modified_output_abc])
1613        if not self.need_to_merge():
1614            patch_test_cmd.extend([os.path.join(self.path, modified_input_file)])
1615        else:
1616            patch_test_cmd.extend(['--debug-info', f'@{files_info_txt}'])
1617        if 'record-name-with-dots' in os.path.basename(self.path):
1618            patch_test_cmd.extend(['--merge-abc', '--record-name=record.name.with.dots'])
1619        dump_assembly_testname = [
1620            'modify-anon-content-keep-origin-name',
1621            'modify-class-memeber-function',
1622            'exist-lexenv-3',
1623            'lexenv-reduce',
1624            'lexenv-increase']
1625        for name in dump_assembly_testname:
1626            if name in os.path.basename(self.path):
1627                patch_test_cmd.extend(['--dump-assembly'])
1628        self.log_cmd(patch_test_cmd)
1629
1630        return gen_base_cmd, patch_test_cmd, symbol_table_file, origin_output_abc, modified_output_abc
1631
1632    def run(self, runner):
1633        gen_base_cmd, patch_test_cmd, symbol_table_file, origin_output_abc, modified_output_abc = self.gen_cmd(runner)
1634
1635        self.gen_files_infos(False)
1636        process_base = run_subprocess_with_beta3(None, gen_base_cmd)
1637        stdout_base, stderr_base = process_base.communicate(timeout=runner.args.es2panda_timeout)
1638        if stderr_base:
1639            self.passed = False
1640            self.error = stderr_base.decode("utf-8", errors="ignore")
1641            self.output = stdout_base.decode("utf-8", errors="ignore") + stderr_base.decode("utf-8", errors="ignore")
1642        else:
1643            self.gen_files_infos(True)
1644            process_patch = run_subprocess_with_beta3(None, patch_test_cmd)
1645            process_patch = subprocess.Popen(patch_test_cmd, stdout=subprocess.PIPE,
1646                stderr=subprocess.PIPE)
1647            stdout_patch, stderr_patch = process_patch.communicate(timeout=runner.args.es2panda_timeout)
1648            if stderr_patch:
1649                self.passed = False
1650                self.error = stderr_patch.decode("utf-8", errors="ignore")
1651            self.output = stdout_patch.decode("utf-8", errors="ignore") + stderr_patch.decode("utf-8", errors="ignore")
1652
1653        expected_path = os.path.join(self.path, 'expected.txt')
1654        try:
1655            with open(expected_path, 'r') as fp:
1656                # ignore license description lines and skip leading blank lines
1657                expected = (''.join((fp.readlines()[12:]))).lstrip()
1658            self.passed = expected == self.output
1659        except Exception:
1660            self.passed = False
1661
1662        if not self.passed:
1663            self.error = "expected output:" + os.linesep + expected + os.linesep + "actual output:" + os.linesep +\
1664                self.output
1665        files_info_txt = os.path.join(self.path, 'filesInfo.txt')
1666        if not self.preserve_files:
1667            os.remove(symbol_table_file)
1668            os.remove(origin_output_abc)
1669            if (os.path.exists(modified_output_abc)):
1670                os.remove(modified_output_abc)
1671            if (os.path.exists(files_info_txt)):
1672                os.remove(files_info_txt)
1673        return self
1674
1675
1676class PatchRunner(Runner):
1677    def __init__(self, args, name):
1678        Runner.__init__(self, args, name)
1679        self.preserve_files = args.error
1680        self.tests_in_dirs = []
1681        dirs = os.listdir(path.join(self.test_root, "patch"))
1682        for target_version_path in dirs:
1683            self.add_tests(target_version_path, name)
1684
1685    def add_tests(self, target_version_path, name):
1686        name_dir = os.path.join(self.test_root, "patch", target_version_path, name)
1687        if not os.path.exists(name_dir):
1688            return
1689        target_version = 0
1690        if target_version_path.isdigit():
1691            target_version = int(target_version_path)
1692        for sub_path in os.listdir(name_dir):
1693            test_base_path = os.path.join(name_dir, sub_path)
1694            if name != "coldreload":
1695                for dirpath, dirnames, filenames in os.walk(test_base_path):
1696                    if not dirnames:
1697                        self.tests_in_dirs.append(dirpath)
1698                        self.tests.append(PatchTest(dirpath, name, target_version, self.preserve_files))
1699            else:
1700                self.tests_in_dirs.append(test_base_path)
1701                self.tests.append(PatchTest(test_base_path, name, target_version, self.preserve_files))
1702
1703    def test_path(self, src):
1704        return os.path.basename(src)
1705
1706
1707class HotfixRunner(PatchRunner):
1708    def __init__(self, args):
1709        PatchRunner.__init__(self, args, "hotfix")
1710
1711
1712class HotreloadRunner(PatchRunner):
1713    def __init__(self, args):
1714        PatchRunner.__init__(self, args, "hotreload")
1715
1716
1717class ColdfixRunner(PatchRunner):
1718    def __init__(self, args):
1719        PatchRunner.__init__(self, args, "coldfix")
1720
1721
1722class ColdreloadRunner(PatchRunner):
1723    def __init__(self, args):
1724        PatchRunner.__init__(self, args, "coldreload")
1725
1726
1727class DebuggerTest(Test):
1728    def __init__(self, test_path, mode):
1729        Test.__init__(self, test_path, "")
1730        self.mode = mode
1731
1732    def run(self, runner):
1733        cmd = runner.cmd_prefix + [runner.es2panda, "--module"]
1734        input_file_name = 'base.js'
1735        if self.mode == "debug-mode":
1736            cmd.extend(['--debug-info'])
1737        cmd.extend([os.path.join(self.path, input_file_name)])
1738        cmd.extend(['--dump-assembly'])
1739        process = run_subprocess_with_beta3(self, cmd)
1740        stdout, stderr = process.communicate(timeout=runner.args.es2panda_timeout)
1741        if stderr:
1742            self.passed = False
1743            self.error = stderr.decode("utf-8", errors="ignore")
1744            return self
1745
1746        self.output = stdout.decode("utf-8", errors="ignore")
1747
1748        expected_path = os.path.join(self.path, 'expected.txt')
1749        try:
1750            with open(expected_path, 'r') as fp:
1751                expected = (''.join((fp.readlines()[12:]))).lstrip()
1752            self.passed = expected == self.output
1753        except Exception:
1754            self.passed = False
1755
1756        if not self.passed:
1757            self.error = "expected output:" + os.linesep + expected + os.linesep + "actual output:" + os.linesep +\
1758                self.output
1759
1760        if os.path.exists("base.abc"):
1761            os.remove("base.abc")
1762
1763        return self
1764
1765
1766class DebuggerRunner(Runner):
1767    def __init__(self, args):
1768        Runner.__init__(self, args, "debugger")
1769        self.test_directory = path.join(self.test_root, "debugger")
1770        self.add_test()
1771
1772    def add_test(self):
1773        self.tests = []
1774        self.tests.append(DebuggerTest(os.path.join(self.test_directory, "debugger-in-debug"), "debug-mode"))
1775        self.tests.append(DebuggerTest(os.path.join(self.test_directory, "debugger-in-release"), "release-mode"))
1776
1777
1778class Base64Test(Test):
1779    def __init__(self, test_path, input_type):
1780        Test.__init__(self, test_path, "")
1781        self.input_type = input_type
1782
1783    def run(self, runner):
1784        cmd = runner.cmd_prefix + [runner.es2panda, "--base64Output"]
1785        if self.input_type == "file":
1786            input_file_name = 'input.js'
1787            cmd.extend(['--source-file', input_file_name])
1788            cmd.extend([os.path.join(self.path, input_file_name)])
1789        elif self.input_type == "string":
1790            input_file = os.path.join(self.path, "input.txt")
1791            try:
1792                with open(input_file, 'r') as fp:
1793                    base64_input = (''.join((fp.readlines()[12:]))).lstrip()  # ignore license description lines
1794                    cmd.extend(["--base64Input", base64_input])
1795            except Exception:
1796                self.passed = False
1797        elif self.input_type == "targetApiVersion":
1798            # base64 test for all available target api version.
1799            version = os.path.basename(self.path)
1800            cmd.extend(['--target-api-version', version])
1801            if version == "12":
1802                cmd.append("--target-api-sub-version=beta3")
1803            input_file = os.path.join(self.path, "input.txt")
1804            try:
1805                with open(input_file, 'r') as fp:
1806                    base64_input = (''.join((fp.readlines()[12:]))).lstrip()  # ignore license description lines
1807                    cmd.extend(["--base64Input", base64_input])
1808            except Exception:
1809                self.passed = False
1810        else:
1811            self.error = "Unsupported base64 input type"
1812            self.passed = False
1813            return self
1814
1815        version = os.path.basename(self.path)
1816        if not self.input_type == "targetApiVersion":
1817            cmd.append("--target-api-sub-version=beta3")
1818
1819        self.log_cmd(cmd)
1820
1821        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1822        stdout, stderr = process.communicate(timeout=runner.args.es2panda_timeout)
1823        if stderr:
1824            self.passed = False
1825            self.error = stderr.decode("utf-8", errors="ignore")
1826            return self
1827
1828        self.output = stdout.decode("utf-8", errors="ignore")
1829
1830        expected_path = os.path.join(self.path, 'expected.txt')
1831        try:
1832            with open(expected_path, 'r') as fp:
1833                expected = (''.join((fp.readlines()[12:]))).lstrip()
1834            self.passed = expected == self.output
1835        except Exception:
1836            self.passed = False
1837
1838        if not self.passed:
1839            self.error = "expected output:" + os.linesep + expected + os.linesep + "actual output:" + os.linesep +\
1840                self.output
1841
1842        return self
1843
1844
1845class Base64Runner(Runner):
1846    def __init__(self, args):
1847        Runner.__init__(self, args, "Base64")
1848        self.test_directory = path.join(self.test_root, "base64")
1849        self.add_test()
1850
1851    def add_test(self):
1852        self.tests = []
1853        self.tests.append(Base64Test(os.path.join(self.test_directory, "inputFile"), "file"))
1854        self.tests.append(Base64Test(os.path.join(self.test_directory, "inputString"), "string"))
1855        # current target api version is 12, once a new version is addded, a new testcase should be added here.
1856        current_version = 12
1857        available_target_api_versions = [9, 10, 11, current_version]
1858        for version in available_target_api_versions:
1859            self.tests.append(Base64Test(os.path.join(self.test_directory, "availableTargetApiVersion", str(version)),
1860                "targetApiVersion"))
1861
1862    def test_path(self, src):
1863        return os.path.basename(src)
1864
1865
1866class BytecodeRunner(Runner):
1867    def __init__(self, args):
1868        Runner.__init__(self, args, "Bytecode")
1869
1870    def add_directory(self, directory, extension, flags, func=Test):
1871        glob_expression = path.join(
1872            self.test_root, directory, "**/*.%s" % (extension))
1873        files = glob(glob_expression, recursive=True)
1874        files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
1875        self.tests += list(map(lambda f: func(f, flags), files))
1876
1877    def test_path(self, src):
1878        return src
1879
1880
1881class ArkJsVmDownload:  # Obtain different versions of ark_js_vm and their dependent libraries
1882    def __init__(self, args):
1883        self.build_dir = args.build_dir
1884        self.url = "https://gitee.com/zhongmingwei123123/ark_js_vm_version.git"
1885        self.local_path = path.join(self.build_dir, "ark_js_vm_version")
1886        self.max_retries = 3
1887
1888    def run_cmd_cwd(self, cmd):
1889        try:
1890            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1891            _, _ = proc.communicate()
1892            return proc.wait()
1893        except Exception as e:
1894            print(f"Error executing command: {e}")
1895            return -1
1896
1897    def git_clone(self, git_url, code_dir):
1898        cmd = ["git", "clone", git_url, code_dir, "--depth=1"]
1899        retries = 1
1900        while retries <= self.max_retries:
1901            ret = self.run_cmd_cwd(cmd)
1902            if ret == 0:
1903                break
1904            else:
1905                print(f"\n warning: Atempt: #{retries} to clone '{git_url}' failed. Try cloining again")
1906                retries += 1
1907        assert not ret, f"\n error: Cloning '{git_url}' failed."
1908
1909    def run(self):
1910        if not os.path.exists(self.local_path):
1911            print("\nstart downLoad ark_js_vm_version ...\n")
1912            self.git_clone(self.url, self.local_path)
1913            print("\ndownload finish.\n")
1914
1915class CodeDownloader:
1916    def __init__(self, args, url, components_name, max_retries=3):
1917        self.build_dir = args.build_dir
1918        self.url = url
1919        self.local_path = path.join(self.build_dir, components_name)
1920        self.max_retries = max_retries
1921
1922    def run_cmd_cwd(self, cmd):
1923        try:
1924            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1925            _, _ = proc.communicate()
1926            return proc.wait()
1927        except Exception as e:
1928            print(f"Error executing command: {e}")
1929            return -1
1930
1931    def git_clone(self, git_url, code_dir):
1932        cmd = ["git", "clone", git_url, code_dir, "--depth=1"]
1933        retries = 1
1934        while retries <= self.max_retries:
1935            ret = self.run_cmd_cwd(cmd)
1936            if ret == 0:
1937                break
1938            else:
1939                print(f"\nWarning: Attempt #{retries} to clone '{git_url}' failed. Retrying...")
1940                retries += 1
1941        assert ret == 0, f"\nError: Cloning '{git_url}' failed."
1942
1943    def run(self):
1944        if not os.path.exists(self.local_path):
1945            print(f"\nStart downloading {self.url}...\n")
1946            self.git_clone(self.url, self.local_path)
1947            print("\nDownload finished.\n")
1948            print(self.local_path)
1949
1950class AbcTestCasesPrepare:
1951    def __init__(self, args):
1952        self.test_root = path.dirname(path.abspath(__file__))
1953        self.es2panda = path.join(args.build_dir, "es2abc")
1954        self.args = args
1955        self.valid_mode_list = ["non_merge_mode", "merge_mode"]
1956        self.test_abc_path_list = set()
1957
1958    @staticmethod
1959    def split_api_version(version_str):
1960        parts = version_str.split("API")[1].split("beta")
1961        main_part = parts[0]
1962        beta_part = "beta%s" % parts[1] if len(parts) > 1 else ""
1963        return (main_part, beta_part)
1964
1965    def add_abc_directory(self, directory, extension):
1966        test_directory = path.join(self.test_root, directory)
1967        glob_expression = path.join(test_directory, "*.%s" % (extension))
1968        files = glob(glob_expression)
1969        files = fnmatch.filter(files, self.test_root + "**" + self.args.filter)
1970        return files
1971
1972    def get_output_path(self, source_path, main_version, beta_version, es2abc_version):
1973        base = path.splitext(source_path)[0]
1974        suffix = f"version_API{main_version}{beta_version}"
1975        return f"{base}_{es2abc_version}_{suffix}.abc" if es2abc_version != "default" else f"{base}_{suffix}.abc"
1976
1977    def gen_abc_versions(self, flags, source_path, es2abc_version):
1978        supported_apis = ES2ABC_API_SUPPORT.get(es2abc_version)
1979        for api_version in supported_apis:
1980            main_version, beta_version = AbcTestCasesPrepare.split_api_version(api_version)
1981            output_path = self.get_output_path(source_path, main_version, beta_version, es2abc_version)
1982            self.test_abc_path_list.add(output_path)
1983            _, stderr = self.compile_for_target_version(flags, source_path, output_path, main_version, beta_version, es2abc_version)
1984            if stderr:
1985                raise RuntimeError(f"abc generate error: {stderr}")
1986
1987    def gen_abc_tests(self, directory, extension, flags, abc_mode, es2abc_versions=["default"]):
1988        if abc_mode not in self.valid_mode_list:
1989            raise ValueError(f"Invalid abc_mode value: {abc_mode}")
1990        test_source_list = self.add_abc_directory(directory, extension)
1991
1992        for es2abc_version in es2abc_versions:
1993            for input_path in test_source_list:
1994                self.gen_abc_versions(flags, input_path, es2abc_version)
1995
1996    def compile_for_target_version(self, flags, input_path, output_path, target_api_version, target_api_sub_version="", es2abc_version="default"):
1997        es2panda_path = (
1998            self.es2panda if es2abc_version == "default"
1999            else os.path.join(self.args.build_dir, "es2abc_version", es2abc_version, "es2abc")
2000        )
2001        cmd = []
2002        cmd.append(es2panda_path)
2003        cmd.append(input_path)
2004        cmd.extend(flags)
2005        cmd.append("--target-api-version=%s" % (target_api_version))
2006        cmd.extend(["--output=%s" % (output_path)])
2007        if target_api_sub_version != "":
2008            cmd.append("--target-api-sub-version=%s" % (target_api_sub_version))
2009        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2010        stdout, stderr = process.communicate(timeout=10)
2011        if stderr:
2012            stderr = "Error executing command: %s\n%s" % (cmd, stderr)
2013        return stdout, stderr
2014
2015    def remove_abc_tests(self):
2016        for abc_path in self.test_abc_path_list:
2017            if path.exists(abc_path):
2018                os.remove(abc_path)
2019
2020
2021class AbcVersionControlRunner(Runner):
2022    def __init__(self, args):
2023        super().__init__(args, "AbcVersionControl")
2024        self.valid_mode_list = ["non_merge_mode", "merge_mode", "mix_compile_mode"]
2025
2026    def add_directory(self, directory, extension, flags, abc_mode, is_discard=False):
2027        if abc_mode not in self.valid_mode_list:
2028            raise ValueError(f"Invalid abc_mode value: {abc_mode}")
2029        glob_expression = path.join(self.test_root, directory, "*.%s" % (extension))
2030        files = glob(glob_expression)
2031        files = fnmatch.filter(files, self.test_root + "**" + self.args.filter)
2032        if abc_mode == "mix_compile_mode":
2033            files = [f for f in files if not f.endswith("-expected.txt")]
2034        self.tests += list(map(lambda f: TestAbcVersionControl(f, flags, abc_mode, is_discard), files))
2035
2036    def test_path(self, src):
2037        return src
2038
2039    def run(self):
2040        for test in self.tests:
2041            test.run(self)
2042
2043
2044class VersionControlRunner(Runner):
2045    def __init__(self, args):
2046        Runner.__init__(self, args, "VersionControl")
2047
2048    def add_directory(self, directory, extension, flags, test_version, feature_type, module_dir=None, func=Test):
2049        glob_expression = path.join(self.test_root, directory, "*.%s" % (extension))
2050        files = glob(glob_expression)
2051        files = fnmatch.filter(files, self.test_root + "**" + self.args.filter)
2052        module_path_list = []
2053        if module_dir is not None:
2054            module_path_list = self.add_module_path(module_dir)
2055        self.tests += list(
2056            map(lambda f: TestVersionControl(f, flags, test_version, feature_type, module_path_list), files)
2057        )
2058
2059    def add_module_path(self, module_dir):
2060        module_path_list = []
2061        glob_expression_ts = path.join(self.test_root, module_dir, "*.%s" % ("ts"))
2062        glob_expression_js = path.join(self.test_root, module_dir, "*.%s" % ("js"))
2063        module_path_list = glob(glob_expression_ts)
2064        module_path_list.extend(glob(glob_expression_js))
2065        module_path_list = fnmatch.filter(module_path_list, self.test_root + "**" + self.args.filter)
2066        return module_path_list
2067
2068    def test_path(self, src):
2069        return src
2070
2071    def run(self):
2072        for test in self.tests:
2073            test.run(self)
2074
2075
2076class TestAbcVersionControl(Test):
2077    def __init__(self, test_path, flags, abc_mode, is_discard):
2078        super().__init__(test_path, flags)
2079        self.min_support_version_number = API_VERSION_MAP.get(MIN_SUPPORT_BC_VERSION)
2080        self.abc_mode = abc_mode
2081        self.is_discard = is_discard
2082        self.output = None
2083        self.process = None
2084        self.is_support = False
2085        self.test_abc_list = list()
2086        self.test_input = None
2087        self.target_abc_path = None
2088        self.entry_point = self.get_entry_point()
2089
2090    @staticmethod
2091    def compare_version_number(version1, version2):
2092        v1 = TestAbcVersionControl.version_number_to_tuple(version1)
2093        v2 = TestAbcVersionControl.version_number_to_tuple(version2)
2094        for num1, num2 in zip(v1, v2):
2095            if num1 > num2:
2096                return 1
2097            elif num1 < num2:
2098                return -1
2099        return 0
2100
2101    @staticmethod
2102    def version_number_to_tuple(version):
2103        return tuple(int(part) for part in version.split("."))
2104
2105    def get_entry_point(self):
2106        if self.abc_mode == "merge_mode":
2107            base_name = os.path.basename(self.path)
2108            return os.path.splitext(base_name)[0]
2109        elif self.abc_mode == "mix_compile_mode":
2110            return MIX_COMPILE_ENTRY_POINT
2111        return ""
2112
2113    def get_path_to_expected(self, is_support=False, test_stage=""):
2114        support_name = "supported_" if is_support else "unsupported_"
2115        if self.abc_mode == "mix_compile_mode" and test_stage != "runtime":
2116            support_name = ""
2117        expected_name = path.splitext(self.path)[0].split("_version_API")[0]
2118        expected_name = re.sub(r"_(\d+\.\d+)(?=(_|$))", "", expected_name)
2119        expected_path = "%s_%s%s-expected.txt" % (expected_name, support_name, test_stage)
2120        return expected_path
2121
2122    def run_process(self, cmd):
2123        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2124        stdout, stderr = self.process.communicate()
2125        self.output = stdout.decode("utf-8", errors="ignore") + stderr.decode("utf-8", errors="ignore").split("\n")[0]
2126        if stderr:
2127            stderr = "Error executing command: %s\n%s" % (cmd, stderr)
2128        return stdout, stderr
2129
2130    def compile_for_target_version(
2131        self, runner, input_path, output_path, target_api_version, target_api_sub_version=""
2132    ):
2133        cmd = []
2134        cmd.append(runner.es2panda)
2135        if self.abc_mode == "mix_compile_mode":
2136            input_path = "@%s" % (input_path)
2137        cmd.append(input_path)
2138        cmd.extend(self.flags)
2139        cmd.append("--target-api-version=%s" % (target_api_version))
2140        cmd.extend(["--output=%s" % (output_path)])
2141        if target_api_sub_version != "":
2142            cmd.append("--target-api-sub-version=%s" % (target_api_sub_version))
2143        self.log_cmd(cmd)
2144        stdout, stderr = self.run_process(cmd)
2145        return stdout, stderr
2146
2147    def build_abc_filename(self, target_api_version, target_api_sub_version):
2148        prefix = f"{path.splitext(self.path)[0]}"
2149        abc_name = f"{prefix}_target_{target_api_version}{target_api_sub_version}.abc"
2150        return abc_name.replace("/", "_")
2151
2152    def extract_input_versions(self):
2153        input_api_versions = self.extract_api_versions(path.split(self.path)[1])
2154        input_version_numbers = [API_VERSION_MAP.get(api) for api in input_api_versions]
2155        return sorted(input_version_numbers, key=TestAbcVersionControl.version_number_to_tuple)
2156
2157    def determine_expected_behavior(self, target_api_version, target_api_sub_version, input_version_numbers):
2158        target_version = "API" + target_api_version + target_api_sub_version
2159        target_version_number = API_VERSION_MAP.get(target_version)
2160
2161        min_input_version_number = input_version_numbers[0]
2162        max_input_version_number = input_version_numbers[-1]
2163
2164        if TestAbcVersionControl.compare_version_number(target_version_number, self.min_support_version_number) < 0:
2165            compile_expected_path = self.get_path_to_expected(
2166                False, "compile_target_version_below_min_support"
2167            )
2168            return False, compile_expected_path, target_api_version
2169
2170        elif (
2171            TestAbcVersionControl.compare_version_number(min_input_version_number, self.min_support_version_number) < 0
2172        ):
2173            compile_expected_path = self.get_path_to_expected(False, "compile_cur_version_below_min_support")
2174            return False, compile_expected_path, self.path
2175
2176        elif TestAbcVersionControl.compare_version_number(target_version_number, max_input_version_number) < 0:
2177            compile_expected_path = self.get_path_to_expected(False, "compile_target_version_below_cur")
2178            return False, compile_expected_path, self.path
2179
2180        elif self.is_discard:
2181            compile_expected_path = self.get_path_to_expected(False, "compile_discard")
2182            return False, compile_expected_path, ""
2183
2184        return True, None, None
2185
2186    def generate_abc(self, runner, target_api_version, target_api_sub_version=""):
2187        compile_expected_path = None
2188        target_abc_name = self.build_abc_filename(target_api_version, target_api_sub_version)
2189        self.target_abc_path = path.join(runner.build_dir, target_abc_name)
2190
2191        _, stderr = self.compile_for_target_version(
2192            runner, self.path, self.target_abc_path, target_api_version, target_api_sub_version
2193        )
2194        format_content = ""
2195        self.is_support = False
2196
2197        # Extract the API versions of the input abc files from the file name of the test case.
2198        input_version_numbers = self.extract_input_versions()
2199
2200        is_support, compile_expected_path, format_content = self.determine_expected_behavior(
2201            target_api_version, target_api_sub_version, input_version_numbers
2202        )
2203        self.is_support = is_support
2204        if self.is_support:
2205            if stderr:
2206                self.passed = False
2207            else:
2208                self.passed = True
2209            return stderr
2210
2211        try:
2212            with open(compile_expected_path, "r") as fp:
2213                expected = fp.read()
2214                self.passed = expected.format(format_content) in self.output and self.process.returncode in [0, 1]
2215        except Exception:
2216            self.passed = False
2217        return stderr
2218
2219    def execute_abc(self, runner, vm_version, entry_point=""):
2220        cmd = []
2221        ark_js_vm_dir = os.path.join(
2222            runner.build_dir,
2223            "ark_js_vm_version",
2224            vm_version,
2225        )
2226        ld_library_path = os.path.join(ark_js_vm_dir, "lib")
2227        os.environ["LD_LIBRARY_PATH"] = ld_library_path
2228        ark_js_vm_path = os.path.join(ark_js_vm_dir, "ark_js_vm")
2229        cmd.append(ark_js_vm_path)
2230        if entry_point != "":
2231            cmd.append("--entry-point=%s" % entry_point)
2232        cmd.append(self.target_abc_path)
2233        self.log_cmd(cmd)
2234        stdout, stderr = self.run_process(cmd)
2235        return stdout, stderr
2236
2237    def test_abc_execution(self, runner, target_api_version, target_api_sub_version=""):
2238        stderr = None
2239        target_version = "API" + target_api_version + target_api_sub_version
2240        target_version_number = API_VERSION_MAP.get(target_version)
2241        for vm_version in ARK_JS_VM_LIST:
2242            vm_version_number = API_VERSION_MAP.get(vm_version)
2243            _, stderr = self.execute_abc(runner, vm_version, self.entry_point)
2244            self.is_support = (
2245                TestAbcVersionControl.compare_version_number(vm_version_number, target_version_number) >= 0
2246            )
2247            runtime_expect_path = self.get_path_to_expected(self.is_support, "runtime")
2248            try:
2249                with open(runtime_expect_path, "r") as fp:
2250                    expected = fp.read()
2251                    if self.is_support and self.abc_mode != "merge_mode":
2252                        self.passed = expected == self.output and self.process.returncode in [0, 1, 255]
2253                    else:
2254                        self.passed = expected in self.output
2255                    pass
2256            except Exception:
2257                self.passed = False
2258            if not self.passed:
2259                return stderr
2260        return stderr
2261
2262    def extract_api_versions(self, file_name):
2263        pattern = r"(API\d+)(beta\d+)?"
2264        matches = re.findall(pattern, file_name)
2265        api_versions = [f"{api}{f'{beta}' if beta else ''}" for api, beta in matches]
2266        return api_versions
2267
2268    def remove_abc(self, abc_path):
2269        if path.exists(abc_path):
2270            os.remove(abc_path)
2271
2272    def run(self, runner):
2273        for api_version in API_VERSION_MAP:
2274            target_api_version, target_api_sub_version = AbcTestCasesPrepare.split_api_version(api_version)
2275            stderr = self.generate_abc(runner, target_api_version, target_api_sub_version)
2276            if not self.passed:
2277                self.error = stderr
2278                return self
2279            if stderr:
2280                continue
2281            stderr = self.test_abc_execution(runner, target_api_version, target_api_sub_version)
2282            self.remove_abc(self.target_abc_path)
2283            if not self.passed:
2284                self.error = stderr
2285                return self
2286        return self
2287
2288class Es2abcVersionControlRunner(Runner):
2289    def __init__(self, args):
2290        super().__init__(args, "Es2abcVersionControl")
2291        self.valid_mode_list = ["non_merge_mode", "merge_mode"]
2292
2293    def add_directory(self, directory, extension, flags, abc_mode, is_discard=False):
2294        if abc_mode not in self.valid_mode_list:
2295            raise ValueError(f"Invalid abc_mode value: {abc_mode}")
2296        glob_expression = path.join(self.test_root, directory, "*.%s" % (extension))
2297        files = glob(glob_expression)
2298        files = fnmatch.filter(files, self.test_root + "**" + self.args.filter)
2299        self.tests += list(map(lambda f: TestEs2abcVersionControl(f, flags, abc_mode, is_discard), files))
2300
2301    def test_path(self, src):
2302        return src
2303
2304    def run(self):
2305        for test in self.tests:
2306            test.run(self)
2307        self.args.abc_tests_prepare.remove_abc_tests()
2308
2309class TestEs2abcVersionControl(TestAbcVersionControl):
2310    def __init__(self, test_path, flags, abc_mode, is_discard, es2abc_versions=None):
2311        super().__init__(test_path, flags, abc_mode, is_discard)
2312        self.es2abc_versions = es2abc_versions or list(ES2ABC_API_SUPPORT.keys())
2313
2314    @staticmethod
2315    def version_str_to_tuple(version: str):
2316        if isinstance(version, list):
2317            version = ".".join(version)
2318        return tuple(int(x) for x in version.split("."))
2319
2320    @classmethod
2321    def get_max_supported_version(cls, es2abc_version: str) -> str:
2322        supported_apis = ES2ABC_API_SUPPORT.get(es2abc_version, ES2ABC_API_SUPPORT["default"])
2323        max_api = max(supported_apis, key=lambda api: cls.version_str_to_tuple(API_VERSION_MAP[api]))
2324        return API_VERSION_MAP[max_api]
2325
2326    @classmethod
2327    def should_skip_abc_file(cls, file_version: str, es2abc_version: str) -> bool:
2328        max_supported_version = cls.get_max_supported_version(es2abc_version)
2329        return cls.version_str_to_tuple(file_version) > cls.version_str_to_tuple(max_supported_version)
2330
2331    def build_abc_filename(self, target_api_version, target_api_sub_version, es2abc_version):
2332        prefix = f"{path.splitext(self.path)[0]}"
2333        if es2abc_version == "default" or es2abc_version is None:
2334            abc_name = f"{prefix}_target_{target_api_version}{target_api_sub_version}.abc"
2335        else:
2336            abc_name = f"{prefix}_{es2abc_version}_target_{target_api_version}{target_api_sub_version}.abc"
2337        return abc_name.replace("/", "_")
2338
2339    def compile_for_target_version(
2340        self, runner, input_path, output_path, target_api_version, target_api_sub_version="", es2abc_version="default"
2341    ):
2342        cmd = []
2343        if es2abc_version != "default":
2344            runner.es2panda = os.path.join(runner.build_dir, "es2abc_version", es2abc_version, "es2abc")
2345        cmd.append(runner.es2panda)
2346        cmd.append(input_path)
2347        cmd.extend(self.flags)
2348        cmd.append("--target-api-version=%s" % target_api_version)
2349        cmd.extend(["--output=%s" % output_path])
2350        if target_api_sub_version:
2351            cmd.append("--target-api-sub-version=%s" % target_api_sub_version)
2352        self.es2abc_cmd = ' '.join(cmd)
2353        stdout, stderr = self.run_process(cmd)
2354        return stdout, stderr
2355
2356    def generate_abc(self, runner, target_api_version, target_api_sub_version=""):
2357        compile_expected_path = None
2358        input_version_numbers = self.extract_input_versions()
2359
2360        for es2abc_version in self.es2abc_versions:
2361            target_abc_name = self.build_abc_filename(target_api_version, target_api_sub_version, es2abc_version)
2362            self.target_abc_path = path.join(runner.build_dir, target_abc_name)
2363
2364            _, stderr = self.compile_for_target_version(
2365                runner, self.path, self.target_abc_path, target_api_version, target_api_sub_version, es2abc_version
2366            )
2367
2368            format_content = ""
2369            self.is_support = False
2370
2371            # The version of the abc file exceeds the maximum supported range of es2abc
2372            if self.should_skip_abc_file(input_version_numbers, es2abc_version):
2373                continue
2374
2375            is_support, compile_expected_path, format_content = self.determine_expected_behavior(
2376                target_api_version, target_api_sub_version, input_version_numbers
2377            )
2378            self.is_support = is_support
2379            if self.is_support:
2380                if stderr:
2381                    self.passed = False
2382                else:
2383                    self.passed = True
2384                return stderr
2385
2386            try:
2387                with open(compile_expected_path, "r") as fp:
2388                    expected = fp.read()
2389                    self.passed = expected.format(format_content) in self.output and self.process.returncode in [0, 1]
2390            except Exception:
2391                self.passed = False
2392            return stderr
2393        return None
2394
2395    def run(self, runner):
2396        for api_version in API_VERSION_MAP:
2397            target_api_version, target_api_sub_version = AbcTestCasesPrepare.split_api_version(api_version)
2398            stderr = self.generate_abc(runner, target_api_version, target_api_sub_version)
2399            if not self.passed:
2400                self.error = stderr
2401                return self
2402        return self
2403
2404class TestVersionControl(Test):
2405    def __init__(self, test_path, flags, test_version, feature_type, module_path_list):
2406        Test.__init__(self, test_path, flags)
2407        self.beta_version_default = 3
2408        self.version_with_sub_version_list = ["12"]
2409        self.target_api_version_list = ["9", "10", "11", "12", "18", "20"]
2410        self.target_api_sub_version_list = ["beta1", "beta2", "beta3"]
2411        self.specific_api_version_list = ["API18", "API12beta3", "API11"]
2412        self.output = None
2413        self.process = None
2414        self.test_version = test_version
2415        self.test_abc_path = None
2416        self.feature_type = feature_type
2417        self.module_path_list = module_path_list
2418        self.module_abc_path_set = set()
2419
2420    def split_version(self, version_str):
2421        parts = version_str.split("API")[1].split("beta")
2422        main_part = int(parts[0])
2423        beta_part = int(parts[1]) if len(parts) > 1 else self.beta_version_default
2424        return (main_part, beta_part)
2425
2426    def compare_two_versions(self, version1, version2):
2427        version1_parsed = self.split_version(version1)
2428        version2_parsed = self.split_version(version2)
2429
2430        if version1_parsed < version2_parsed:
2431            return -1
2432        elif version1_parsed > version2_parsed:
2433            return 1
2434        else:
2435            return 0
2436
2437    def get_relative_path(self, from_dir, to_dir):
2438        from_dir = os.path.normpath(from_dir)
2439        to_dir = os.path.normpath(to_dir)
2440        from_dir = os.path.abspath(from_dir)
2441        to_dir = os.path.abspath(to_dir)
2442        from_parts = from_dir.split(os.sep)
2443        to_parts = to_dir.split(os.sep)
2444        common_prefix_length = 0
2445        for part1, part2 in zip(from_parts, to_parts):
2446            if part1 == part2:
2447                common_prefix_length += 1
2448            else:
2449                break
2450        relative_parts = [".."] * (len(from_parts) - common_prefix_length) + to_parts[common_prefix_length:]
2451        relative_path = os.path.join(*relative_parts)
2452        return relative_path
2453
2454    def generate_single_module_abc(self, runner, module_path, target_version):
2455        cmd = []
2456        cmd.append(runner.es2panda)
2457        cmd.append(module_path)
2458        cmd.append("--module")
2459        main_version, sub_version = self.split_version(target_version)
2460        cmd.append("--target-api-version=%s" % (main_version))
2461        if main_version == 12:
2462            cmd.append("--target-api-sub-version=beta%s" % (sub_version))
2463
2464        basename = os.path.basename(module_path)
2465        module_abc_name = "%s.abc" % (path.splitext(basename)[0])
2466        relative_path = self.get_relative_path(path.split(self.path)[0], path.split(module_path)[0])
2467        module_abc_dir = path.join(runner.build_dir, relative_path)
2468        if not os.path.exists(module_abc_dir):
2469            os.makedirs(module_abc_dir)
2470        module_abc_path = path.join(module_abc_dir, module_abc_name)
2471        self.module_abc_path_set.add(module_abc_path)
2472        cmd.extend(["--output=%s" % (module_abc_path)])
2473
2474        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2475        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2476        _, stderr = proc.communicate()
2477        proc.wait()
2478        if stderr:
2479            print(stderr.decode("utf-8", errors="ignore"))
2480
2481    def generate_module_abc(self, runner, target_version):
2482        for module_path in self.module_path_list:
2483            self.generate_single_module_abc(runner, module_path, target_version)
2484
2485    def remove_module_abc(self):
2486        for module_abc_path in self.module_abc_path_set:
2487            if path.exists(module_abc_path):
2488                os.remove(module_abc_path)
2489
2490    def get_path_to_expected(
2491        self, is_support, expected_stage, target_api_version="", specific_api_version="", dump_type=""
2492    ):
2493        support_name = "supported" if is_support else "unsupported"
2494        api_name = ""
2495        # Higher than the specific API version, expected results may differ
2496        if target_api_version != "" and specific_api_version != "":
2497            if self.compare_two_versions(target_api_version, specific_api_version) >= 0:
2498                api_name = "for_higher_or_equal_to_%s_" % (specific_api_version)
2499            else:
2500                api_name = "for_below_%s_" % (specific_api_version)
2501        if dump_type == "ast":
2502            dump_type = ""
2503        elif dump_type == "asm":
2504            dump_type = "asm_"
2505        expected_path = "%s_%s_%s_%s%sversion-expected.txt" % (
2506            path.splitext(self.path)[0],
2507            support_name,
2508            expected_stage,
2509            api_name,
2510            dump_type,
2511        )
2512        return expected_path
2513
2514    def get_path_to_runtime_output_below_version_expected(self):
2515        expected_path = "%s_runtime_below_abc_api_version-expected.txt" % (
2516            path.splitext(self.path)[0])
2517        return expected_path
2518
2519    def get_path_to_runtime_output_expected(self, is_support, target_api_version, is_below_abc_api_version):
2520        path_expected = None
2521        if is_below_abc_api_version:
2522            path_expected = self.get_path_to_runtime_output_below_version_expected()
2523            return path_expected
2524        for specific_api_version in self.specific_api_version_list:
2525            if self.compare_two_versions(target_api_version, specific_api_version) > 0:
2526                continue
2527            path_expected = self.get_path_to_expected(is_support, "runtime", target_api_version, specific_api_version)
2528            if path.exists(path_expected):
2529                return path_expected
2530        return self.get_path_to_expected(is_support, "runtime", target_api_version)
2531
2532    def get_path_to_compile_ast_output_expected(self, is_support):
2533        return self.get_path_to_expected(is_support, "compile")
2534
2535    def get_path_to_compile_asm_output_expected(self, is_support, target_api_version):
2536        path_expected = None
2537        for specific_api_version in self.specific_api_version_list:
2538            path_expected = self.get_path_to_expected(
2539                is_support, "compile", target_api_version, specific_api_version, "asm"
2540            )
2541            if path.exists(path_expected):
2542                return path_expected
2543        return self.get_path_to_expected(is_support, "compile", "", "", "asm")
2544
2545    def run_process(self, cmd):
2546        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2547        stdout, stderr = self.process.communicate()
2548        self.output = stdout.decode("utf-8", errors="ignore") + stderr.decode("utf-8", errors="ignore")
2549        return stdout, stderr
2550
2551    def run_process_compile(self, runner, target_api_version, target_api_sub_version="bata3", dump_type=""):
2552        cmd = []
2553        cmd.append(runner.es2panda)
2554        cmd.append(self.path)
2555        cmd.extend(self.flags)
2556        cmd.append("--target-api-version=%s" % (target_api_version))
2557        test_abc_name = ("%s.abc" % (path.splitext(self.path)[0])).replace("/", "_")
2558        self.test_abc_path = path.join(runner.build_dir, test_abc_name)
2559        cmd.extend(["--output=%s" % (self.test_abc_path)])
2560        if target_api_version == "12":
2561            cmd.append("--target-api-sub-version=%s" % (target_api_sub_version))
2562        if dump_type == "ast":
2563            cmd.append("--dump-ast")
2564        elif dump_type == "assembly":
2565            cmd.append("--dump-assembly")
2566        self.log_cmd(cmd)
2567        stdout, stderr = self.run_process(cmd)
2568        return stdout, stderr
2569
2570    def generate_ast_of_target_version(self, runner, target_api_version, target_api_sub_version="bata3"):
2571        return self.run_process_compile(runner, target_api_version, target_api_sub_version, dump_type="ast")
2572
2573    def generate_asm_of_target_version(self, runner, target_api_version, target_api_sub_version="bata3"):
2574        return self.run_process_compile(runner, target_api_version, target_api_sub_version, dump_type="assembly")
2575
2576    def runtime_for_target_version(self, runner, target_api_version, target_api_sub_version="bata3"):
2577        cmd = []
2578        if target_api_version != "12":
2579            target_api_sub_version = ""
2580        # there is no virtual machine with version api12beta2 available.
2581        # We have chosen api12beta1 as a replacement.
2582        if target_api_version == "12" and target_api_sub_version == "beta2":
2583            target_api_sub_version = "beta1"
2584        ark_js_vm_dir = os.path.join(
2585            runner.build_dir,
2586            "ark_js_vm_version",
2587            "API%s%s" % (target_api_version, target_api_sub_version),
2588        )
2589        ld_library_path = os.path.join(ark_js_vm_dir, "lib")
2590        os.environ["LD_LIBRARY_PATH"] = ld_library_path
2591        ark_js_vm_path = os.path.join(ark_js_vm_dir, "ark_js_vm")
2592        cmd.append(ark_js_vm_path)
2593        cmd.append(self.test_abc_path)
2594        self.log_cmd(cmd)
2595        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2596        stdout, stderr = self.process.communicate()
2597        self.output = stdout.decode("utf-8", errors="ignore") + stderr.decode("utf-8", errors="ignore").split("\n")[0]
2598        return stdout, stderr
2599
2600    def run_for_single_version(self, runner, target_api_version, target_api_sub_version=""):
2601        cur_api_version = "API" + target_api_version + target_api_sub_version
2602        is_support = True if self.compare_two_versions(cur_api_version, self.test_version) >= 0 else False
2603        compile_expected_path = None
2604        stderr = None
2605        if self.feature_type == "syntax_feature":
2606            compile_expected_path = self.get_path_to_compile_ast_output_expected(is_support)
2607            _, stderr = self.generate_ast_of_target_version(runner, target_api_version, target_api_sub_version)
2608        elif self.feature_type == "bytecode_feature":
2609            compile_expected_path = self.get_path_to_compile_asm_output_expected(is_support, cur_api_version)
2610            _, stderr = self.generate_asm_of_target_version(
2611                runner, target_api_version, target_api_sub_version
2612            )
2613        try:
2614            with open(compile_expected_path, "r") as fp:
2615                expected = fp.read()
2616                self.passed = expected == self.output and self.process.returncode in [0, 1]
2617        except Exception:
2618            self.passed = False
2619        if not self.passed or (stderr and self.passed):
2620            return stderr
2621        cur_api_version_number = API_VERSION_MAP.get(cur_api_version)
2622        for api_version in self.target_api_version_list:
2623            # The interception capability of API9 version of ark_js_vm has not yet been launched.
2624            if api_version == "9":
2625                continue
2626            for api_sub_version in self.target_api_sub_version_list:
2627                if not api_version in self.version_with_sub_version_list and api_sub_version != "beta3":
2628                    continue
2629                elif not api_version in self.version_with_sub_version_list:
2630                    api_sub_version = ""
2631                cur_runtime_api_version = "API" + api_version + api_sub_version
2632                cur_runtime_api_version_number = API_VERSION_MAP.get(cur_runtime_api_version)
2633                is_below_abc_version = (
2634                    False
2635                    if TestAbcVersionControl.compare_version_number(
2636                        cur_runtime_api_version_number,
2637                        cur_api_version_number,
2638                    )
2639                    >= 0
2640                    else True
2641                )
2642                self.generate_module_abc(runner, cur_runtime_api_version)
2643                _, stderr = self.runtime_for_target_version(runner, api_version, api_sub_version)
2644                runtime_expected_path = self.get_path_to_runtime_output_expected(
2645                    is_support, cur_api_version, is_below_abc_version
2646                )
2647                self.remove_module_abc()
2648                try:
2649                    with open(runtime_expected_path, "r") as fp:
2650                        expected = fp.read()
2651                    if is_below_abc_version:
2652                        self.passed = expected in self.output
2653                    else:
2654                        self.passed = expected == self.output
2655                except Exception:
2656                    self.passed = False
2657                if not self.passed:
2658                    return stderr
2659        return stderr
2660
2661    def run(self, runner):
2662        for target_api_version in self.target_api_version_list:
2663            stderr = None
2664            if target_api_version == "12":
2665                for target_api_sub_version in self.target_api_sub_version_list:
2666                    stderr = self.run_for_single_version(runner, target_api_version, target_api_sub_version)
2667                    if path.exists(self.test_abc_path):
2668                        os.remove(self.test_abc_path)
2669                    if not self.passed:
2670                        self.error = stderr.decode("utf-8", errors="ignore")
2671                        return self
2672            else:
2673                stderr = self.run_for_single_version(runner, target_api_version)
2674                if not self.passed:
2675                    self.error = stderr.decode("utf-8", errors="ignore")
2676                    return self
2677        return self
2678
2679
2680class CompilerTestInfo(object):
2681    def __init__(self, directory, extension, flags):
2682        self.directory = directory
2683        self.extension = extension
2684        self.flags = flags
2685
2686    def update_dir(self, prefiex_dir):
2687        self.directory = os.path.sep.join([prefiex_dir, self.directory])
2688
2689
2690# Copy compiler directory to test/.local directory, and do inplace obfuscation.
2691def prepare_for_obfuscation(compiler_test_infos, test_root):
2692    tmp_dir_name = ".local"
2693    tmp_path = os.path.join(test_root, tmp_dir_name)
2694    if not os.path.exists(tmp_path):
2695        os.mkdir(tmp_path)
2696
2697    test_root_dirs = set()
2698    for info in compiler_test_infos:
2699        root_dir = info.directory.split("/")[0]
2700        test_root_dirs.add(root_dir)
2701
2702    for test_dir in test_root_dirs:
2703        src_dir = os.path.join(test_root, test_dir)
2704        target_dir = os.path.join(tmp_path, test_dir)
2705        if os.path.exists(target_dir):
2706            shutil.rmtree(target_dir)
2707        shutil.copytree(src_dir, target_dir)
2708
2709    for info in compiler_test_infos:
2710        info.update_dir(tmp_dir_name)
2711
2712
2713def add_directory_for_version_control(runners, args):
2714    tools = [
2715        {
2716            "url": "https://gitee.com/zhongmingwei123123/ark_js_vm_version.git",
2717            "components_name": "ark_js_vm_version"
2718        },
2719        {
2720            "url": "https://gitee.com/li_yue1999/es2abc_version.git",
2721            "components_name": "es2abc_version"
2722        }
2723    ]
2724
2725    for tool in tools:
2726        downloader = CodeDownloader(args, tool['url'], tool['components_name'])
2727        downloader.run()
2728
2729    runner = VersionControlRunner(args)
2730    runner.add_directory(
2731        "version_control/API11/syntax_feature",
2732        "js",
2733        ["--module"],
2734        "API11",
2735        "syntax_feature",
2736    )
2737    runner.add_directory(
2738        "version_control/API11/syntax_feature",
2739        "ts",
2740        ["--module"],
2741        "API11",
2742        "syntax_feature",
2743    )
2744    runner.add_directory(
2745        "version_control/API12beta1_and_beta2/syntax_feature",
2746        "ts", ["--module"],
2747        "API12beta1",
2748        "syntax_feature",
2749    )
2750    runner.add_directory(
2751        "version_control/API12beta1_and_beta2/syntax_feature",
2752        "js",
2753        ["--module"],
2754        "API12beta1",
2755        "syntax_feature",
2756    )
2757    runner.add_directory(
2758        "version_control/API12beta3/syntax_feature",
2759        "ts",
2760        ["--module"],
2761        "API12beta3",
2762        "syntax_feature",
2763        "version_control/API12beta3/syntax_feature/import_target",
2764    )
2765    runner.add_directory(
2766        "version_control/API12beta3/syntax_feature",
2767        "js",
2768        ["--module"],
2769        "API12beta3",
2770        "syntax_feature",
2771        "version_control/API12beta3/syntax_feature/import_target",
2772    )
2773    runner.add_directory(
2774        "version_control/API11/bytecode_feature",
2775        "ts",
2776        ["--module"],
2777        "API11",
2778        "bytecode_feature",
2779    )
2780    runner.add_directory(
2781        "version_control/API11/bytecode_feature",
2782        "js",
2783        ["--module"],
2784        "API11",
2785        "bytecode_feature",
2786    )
2787    runner.add_directory(
2788        "version_control/API12beta1_and_beta2/bytecode_feature",
2789        "ts",
2790        ["--module"],
2791        "API12beta1",
2792        "bytecode_feature",
2793        "version_control/API12beta1_and_beta2/bytecode_feature/import_target",
2794    )
2795    runner.add_directory(
2796        "version_control/API12beta1_and_beta2/bytecode_feature",
2797        "js",
2798        ["--module"],
2799        "API12beta1",
2800        "bytecode_feature",
2801        "version_control/API12beta1_and_beta2/bytecode_feature/import_target",
2802    )
2803    runner.add_directory(
2804        "version_control/API12beta3/bytecode_feature",
2805        "ts",
2806        ["--module"],
2807        "API12beta3",
2808        "bytecode_feature",
2809        "version_control/API12beta3/bytecode_feature/import_target",
2810    )
2811    runner.add_directory(
2812        "version_control/API12beta3/bytecode_feature",
2813        "js",
2814        ["--module"],
2815        "API12beta3",
2816        "bytecode_feature",
2817        "version_control/API12beta3/bytecode_feature/import_target",
2818    )
2819    runner.add_directory(
2820        "version_control/API18/bytecode_feature",
2821        "js",
2822        [],
2823        "API18",
2824        "bytecode_feature",
2825    )
2826    runner.add_directory(
2827        "version_control/API18/bytecode_feature",
2828        "ts",
2829        ["--module"],
2830        "API18",
2831        "bytecode_feature",
2832    )
2833    runner.add_directory(
2834        "version_control/API20/bytecode_feature",
2835        "ts",
2836        ["--module", "--enable-annotations"],
2837        "API20",
2838        "bytecode_feature",
2839    )
2840    runners.append(runner)
2841
2842    abc_tests_prepare = AbcTestCasesPrepare(args)
2843    es2abc_versions = list(ES2ABC_API_SUPPORT.keys())
2844
2845    abc_tests_prepare.gen_abc_tests(
2846        "version_control/bytecode_version_control/non_merge_mode",
2847        "js",
2848        ["--module"],
2849        "non_merge_mode",
2850        es2abc_versions
2851    )
2852    abc_tests_prepare.gen_abc_tests(
2853        "version_control/bytecode_version_control/merge_mode",
2854        "js",
2855        ["--module", "--merge-abc"],
2856        "merge_mode",
2857        es2abc_versions
2858    )
2859    abc_tests_prepare.gen_abc_tests(
2860        "version_control/bytecode_version_control/mixed_compile",
2861        "js",
2862        ["--module", "--merge-abc"],
2863        "merge_mode",
2864    )
2865
2866    args.abc_tests_prepare = abc_tests_prepare
2867    abc_version_control_runner = AbcVersionControlRunner(args)
2868    abc_version_control_runner.add_directory(
2869        "version_control/bytecode_version_control/non_merge_mode",
2870        "abc",
2871        ["--module", "--enable-abc-input"],
2872        "non_merge_mode",
2873    )
2874    abc_version_control_runner.add_directory(
2875        "version_control/bytecode_version_control/merge_mode",
2876        "abc",
2877        ["--module", "--enable-abc-input", "--merge-abc"],
2878        "merge_mode",
2879    )
2880    abc_version_control_runner.add_directory(
2881        "version_control/bytecode_version_control/mixed_compile",
2882        "txt",
2883        ["--module", "--enable-abc-input", "--merge-abc"],
2884        "mix_compile_mode",
2885    )
2886    runners.append(abc_version_control_runner)
2887
2888    es2abc_version_control_runner = Es2abcVersionControlRunner(args)
2889    es2abc_version_control_runner.add_directory(
2890        "version_control/bytecode_version_control/non_merge_mode",
2891        "abc",
2892        ["--module", "--enable-abc-input"],
2893        "non_merge_mode",
2894    )
2895    es2abc_version_control_runner.add_directory(
2896        "version_control/bytecode_version_control/merge_mode",
2897        "abc",
2898        ["--module", "--enable-abc-input", "--merge-abc"],
2899        "merge_mode",
2900    )
2901    runners.append(es2abc_version_control_runner)
2902
2903def add_directory_for_regression(runners, args):
2904    runner = RegressionRunner(args)
2905    runner.add_directory("parser/concurrent", "js", ["--module", "--dump-ast"])
2906    runner.add_directory("parser/js", "js", ["--parse-only", "--dump-ast"])
2907    runner.add_directory("parser/script", "ts", ["--parse-only", "--dump-ast"])
2908    runner.add_directory("parser/ts", "ts",
2909                         ["--parse-only", "--module", "--dump-ast"])
2910    runner.add_directory("parser/ts/type_checker", "ts",
2911                         ["--parse-only", "--enable-type-check", "--module", "--dump-ast"])
2912    runner.add_directory("parser/ts/cases/declaration", "d.ts",
2913                         ["--parse-only", "--module", "--dump-ast"], TSDeclarationTest)
2914    runner.add_directory("parser/commonjs", "js", ["--commonjs", "--parse-only", "--dump-ast"])
2915    runner.add_directory("parser/binder", "js", ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-sub-version=beta3"])
2916    runner.add_directory("parser/binder", "ts", ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-sub-version=beta3"])
2917    runner.add_directory("parser/binder/noModule", "ts", ["--dump-assembly", "--dump-literal-buffer", "--target-api-sub-version=beta3"])
2918    runner.add_directory("parser/binder/api12beta2", "js", ["--dump-assembly", "--target-api-version=12", "--target-api-sub-version=beta2"])
2919    runner.add_directory("parser/binder/debugInfo", "ts", ["--dump-assembly", "--dump-literal-buffer", "--debug-info", "--module"])
2920    runner.add_directory("parser/js/emptySource", "js", ["--dump-assembly"])
2921    runner.add_directory("parser/js/language/arguments-object", "js", ["--parse-only"])
2922    runner.add_directory("parser/js/language/statements/for-statement", "js", ["--parse-only", "--dump-ast"])
2923    runner.add_directory("parser/js/language/expressions/optional-chain", "js", ["--parse-only", "--dump-ast"])
2924    runner.add_directory("parser/js/language/import/syntax/api18", "js",
2925                         ["--parse-only", "--module", "--target-api-version=18"])
2926    runner.add_directory("parser/js/language/import/syntax/api12/beta3", "js",
2927                         ["--parse-only", "--module", "--target-api-version=12", "--target-api-sub-version=beta3"])
2928    runner.add_directory("parser/js/language/import/syntax/api12/beta2", "js",
2929                         ["--parse-only", "--module", "--target-api-version=12", "--target-api-sub-version=beta2"])
2930    runner.add_directory("parser/js/language/import", "ts",
2931                         ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-version=12",
2932                          "--target-api-sub-version=beta3"])
2933    runner.add_directory("parser/sendable_class", "ts",
2934                         ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-sub-version=beta3"])
2935    runner.add_directory("parser/sendable_class/api12beta2", "ts",
2936                         ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-version=12", "--target-api-sub-version=beta2"])
2937    runner.add_directory("parser/unicode", "js", ["--parse-only"])
2938    runner.add_directory("parser/ts/stack_overflow", "ts", ["--parse-only", "--dump-ast"])
2939    runner.add_directory("parser/js/module-record/module-record-field-name-option.js", "js",
2940                         ["--module-record-field-name=abc", "--source-file=abc", "--module", "--dump-normalized-asm-program"])
2941    runner.add_directory("parser/annotations", "ts", ["--module", "--dump-ast", "--enable-annotations"])
2942    runner.add_directory("parser/ts/inline-property", "ts", ["--dump-assembly", "--module"])
2943
2944    runners.append(runner)
2945
2946    transformer_runner = TransformerRunner(args)
2947    transformer_runner.add_directory("parser/ts/transformed_cases", "ts",
2948                                     ["--parse-only", "--module", "--dump-transformed-ast",
2949                                     "--check-transformed-ast-structure"])
2950
2951    runners.append(transformer_runner)
2952
2953    bc_version_runner = BcVersionRunner(args)
2954    bc_version_runner.add_cmd()
2955
2956    runners.append(bc_version_runner)
2957
2958    transformer_api_version_10_runner = TransformerInTargetApiVersion10Runner(args)
2959    transformer_api_version_10_runner.add_directory("parser/ts/transformed_cases_api_version_10", "ts",
2960                                                    ["--parse-only", "--module", "--target-api-version=10",
2961                                                    "--dump-transformed-ast"])
2962
2963    runners.append(transformer_api_version_10_runner)
2964
2965def add_directory_for_asm(runners, args, mode=""):
2966    runner = AbcToAsmRunner(args, True if mode == "debug" else False)
2967    runner.add_directory("abc2asm/js", "js", [])
2968    runner.add_directory("abc2asm/ts", "ts", [])
2969    runner.add_directory("compiler/js", "js", [])
2970    runner.add_directory("compiler/ts/cases/compiler", "ts", [])
2971    runner.add_directory("compiler/ts/projects", "ts", ["--module"])
2972    runner.add_directory("compiler/ts/projects", "ts", ["--module", "--merge-abc"])
2973    runner.add_directory("compiler/dts", "d.ts", ["--module", "--opt-level=0"])
2974    runner.add_directory("compiler/commonjs", "js", ["--commonjs"])
2975    runner.add_directory("parser/concurrent", "js", ["--module"])
2976    runner.add_directory("parser/js", "js", [])
2977    runner.add_directory("parser/script", "ts", [])
2978    runner.add_directory("parser/ts", "ts", ["--module"])
2979    runner.add_directory("parser/ts/type_checker", "ts", ["--enable-type-check", "--module"])
2980    runner.add_directory("parser/commonjs", "js", ["--commonjs"])
2981    runner.add_directory("parser/binder", "js", ["--dump-assembly", "--dump-literal-buffer", "--module"])
2982    runner.add_directory("parser/binder", "ts", ["--dump-assembly", "--dump-literal-buffer", "--module"])
2983    runner.add_directory("parser/binder/noModule", "ts", ["--dump-assembly", "--dump-literal-buffer"])
2984    runner.add_directory("parser/js/emptySource", "js", [])
2985    runner.add_directory("parser/js/language/arguments-object", "js", [])
2986    runner.add_directory("parser/js/language/statements/for-statement", "js", [])
2987    runner.add_directory("parser/js/language/expressions/optional-chain", "js", [])
2988    runner.add_directory("parser/sendable_class", "ts", ["--module"])
2989    runner.add_directory("parser/unicode", "js", [])
2990    runner.add_directory("parser/ts/stack_overflow", "ts", [])
2991
2992    runners.append(runner)
2993
2994
2995def add_directory_for_compiler(runners, args):
2996    runner = CompilerRunner(args)
2997    compiler_test_infos = []
2998    compiler_test_infos.append(CompilerTestInfo("compiler/crashStack/enableColumn/js", "js", ["--enable-release-column"]))
2999    compiler_test_infos.append(CompilerTestInfo("compiler/crashStack/enableColumn/ts", "ts", ["--enable-release-column"]))
3000    compiler_test_infos.append(CompilerTestInfo("compiler/crashStack/offColumn/js", "js", []))
3001    compiler_test_infos.append(CompilerTestInfo("compiler/crashStack/offColumn/ts", "ts", []))
3002    compiler_test_infos.append(CompilerTestInfo("compiler/js", "js", ["--module"]))
3003    compiler_test_infos.append(CompilerTestInfo("compiler/ts/cases", "ts", []))
3004    compiler_test_infos.append(CompilerTestInfo("compiler/ts/projects", "ts", ["--module"]))
3005    compiler_test_infos.append(CompilerTestInfo("compiler/ts/projects", "ts", ["--module", "--merge-abc"]))
3006    compiler_test_infos.append(CompilerTestInfo("compiler/annotations-projects", "ts", ["--module", "--enable-annotations", "--merge-abc"]))
3007    compiler_test_infos.append(CompilerTestInfo("compiler/dts", "d.ts", ["--module", "--opt-level=0"]))
3008    compiler_test_infos.append(CompilerTestInfo("compiler/commonjs", "js", ["--commonjs"]))
3009    compiler_test_infos.append(CompilerTestInfo("compiler/interpreter/lexicalEnv", "js", []))
3010    compiler_test_infos.append(CompilerTestInfo("compiler/sendable", "ts", ["--module", "--target-api-sub-version=beta3"]))
3011    compiler_test_infos.append(CompilerTestInfo("optimizer/js/branch-elimination", "js",
3012                                                ["--module", "--branch-elimination", "--dump-assembly"]))
3013    compiler_test_infos.append(CompilerTestInfo("optimizer/js/opt-try-catch-func", "js",
3014                                                ["--module", "--dump-assembly"]))
3015    compiler_test_infos.append(CompilerTestInfo("optimizer/js/unused-inst-opt", "js",
3016                                                ["--module", "--dump-assembly"]))
3017    compiler_test_infos.append(CompilerTestInfo("compiler/debugInfo/", "js",
3018                                                ["--debug-info", "--dump-debug-info", "--source-file", "debug-info.js"]))
3019    compiler_test_infos.append(CompilerTestInfo("compiler/js/module-record-field-name-option.js", "js",
3020                                                ["--module", "--module-record-field-name=abc"]))
3021    compiler_test_infos.append(CompilerTestInfo("compiler/annotations", "ts", ["--module", "--enable-annotations"]))
3022    compiler_test_infos.append(CompilerTestInfo("compiler/generateCache-projects", "ts",
3023                                                ["--merge-abc", "--file-threads=0", "--cache-file"]))
3024    # Following directories of test cases are for dump-assembly comparison only, and is not executed.
3025    # Check CompilerProjectTest for more details.
3026    compiler_test_infos.append(CompilerTestInfo("optimizer/ts/branch-elimination/projects", "ts",
3027                                                ["--module", "--branch-elimination", "--merge-abc", "--dump-assembly",
3028                                                "--file-threads=8"]))
3029    compiler_test_infos.append(CompilerTestInfo("compiler/bytecodehar/projects", "ts",
3030                                                ["--merge-abc", "--dump-assembly", "--enable-abc-input",
3031                                                 "--dump-deps-info", "--remove-redundant-file", "--enable-annotations",
3032                                                 "--dump-literal-buffer", "--dump-string", "--abc-class-threads=4"]))
3033    compiler_test_infos.append(CompilerTestInfo("compiler/bytecodehar/js/projects", "js",
3034                                                ["--merge-abc", "--dump-assembly", "--enable-abc-input",
3035                                                 "--dump-deps-info", "--remove-redundant-file",
3036                                                 "--dump-literal-buffer", "--dump-string", "--abc-class-threads=4"]))
3037    compiler_test_infos.append(CompilerTestInfo("compiler/bytecodehar/merge_abc_consistence_check/projects", "js",
3038                                                ["--merge-abc", "--dump-assembly", "--enable-abc-input",
3039                                                 "--abc-class-threads=4"]))
3040    compiler_test_infos.append(CompilerTestInfo("compiler/cache_projects", "ts",
3041                                                ["--merge-abc", "--dump-assembly", "--enable-abc-input",
3042                                                 "--dump-deps-info", "--remove-redundant-file", "--enable-annotations",
3043                                                 "--dump-literal-buffer", "--dump-string", "--abc-class-threads=4",
3044                                                 "--cache-file"]))
3045
3046    compiler_test_infos.append(CompilerTestInfo("compiler/ts/shared_module/projects", "ts",
3047                                                ["--module", "--merge-abc", "--dump-assembly"]))
3048    compiler_test_infos.append(CompilerTestInfo("compiler/protobin", "ts", []))
3049    compiler_test_infos.append(CompilerTestInfo("compiler/merge_hap/projects", "ts",
3050                                                ["--merge-abc", "--dump-assembly", "--enable-abc-input",
3051                                                 "--dump-literal-buffer", "--dump-string", "--abc-class-threads=4"]))
3052    compiler_test_infos.append(CompilerTestInfo("compiler/abc2program", "ts",
3053                                                ["--merge-abc", "--module", "--dump-assembly", "--enable-abc-input",
3054                                                 "--dump-literal-buffer", "--dump-string", "--source-file=source.ts",
3055                                                 "--module-record-field-name=source"]))
3056
3057    if args.enable_arkguard:
3058        prepare_for_obfuscation(compiler_test_infos, runner.test_root)
3059
3060    for info in compiler_test_infos:
3061        runner.add_directory(info.directory, info.extension, info.flags)
3062
3063    filesinfo_compiler_infos  = []
3064    filesinfo_runner = FilesInfoRunner(args)
3065    filesinfo_compiler_infos.append(CompilerTestInfo("compiler/filesInfoTest/sourceLang", "txt",
3066                                                ["--module", "--merge-abc", "--dump-assembly"]))
3067    filesinfo_compiler_infos.append(CompilerTestInfo("compiler/filesInfoTest/long_path_filesinfo", "txt",
3068                                                ["--module", "--merge-abc"]))
3069
3070    for info in filesinfo_compiler_infos:
3071        filesinfo_runner.add_directory(info.directory, info.extension, info.flags)
3072
3073    runners.append(runner)
3074    runners.append(filesinfo_runner)
3075
3076
3077def add_directory_for_bytecode(runners, args):
3078    runner = BytecodeRunner(args)
3079    runner.add_directory("bytecode/commonjs", "js", ["--commonjs", "--dump-assembly"])
3080    runner.add_directory("bytecode/js", "js", ["--dump-assembly"])
3081    runner.add_directory("bytecode/ts/cases", "ts", ["--dump-assembly"])
3082    runner.add_directory("bytecode/ts/ic", "ts", ["--dump-assembly"])
3083    runner.add_directory("bytecode/ts/api11", "ts", ["--dump-assembly", "--module", "--target-api-version=11"])
3084    runner.add_directory("bytecode/ts/api12", "ts", ["--dump-assembly", "--module", "--target-api-version=12"])
3085    runner.add_directory("bytecode/ts/api18", "ts", ["--dump-assembly", "--module", "--target-api-version=18"])
3086    runner.add_directory("bytecode/watch-expression", "js", ["--debugger-evaluate-expression", "--dump-assembly"])
3087
3088    runners.append(runner)
3089
3090
3091def add_directory_for_debug(runners, args):
3092    runner = RegressionRunner(args)
3093    runner.add_directory("debug/parser", "js", ["--parse-only", "--dump-ast"])
3094
3095    runners.append(runner)
3096
3097
3098def add_cmd_for_aop_transform(runners, args):
3099    runner = AopTransform(args)
3100
3101    aop_file_path = path.join(runner.test_root, "aop")
3102    lib_suffix = '.so'
3103    #cpp src, deal type, result compare str, abc compare str
3104    msg_list = [
3105        ["correct_modify.cpp", "compile", "aop_transform_start", "new_abc_content"],
3106        ["correct_no_modify.cpp", "compile", "aop_transform_start", ""],
3107        ["exec_error.cpp", "compile", "Transform exec fail", ""],
3108        ["no_func_transform.cpp", "compile", "os::library_loader::ResolveSymbol get func Transform error", ""],
3109        ["error_format.cpp", "copy_lib", "os::library_loader::Load error", ""],
3110        ["".join(["no_exist", lib_suffix]), "dirct_use", "Failed to find file", ""],
3111        ["error_suffix.xxx", "direct_use", "aop transform file suffix support", ""]
3112    ]
3113    for msg in msg_list:
3114        cpp_file = path.join(aop_file_path, msg[0])
3115        if msg[1] == 'compile':
3116            lib_file = cpp_file.replace('.cpp', lib_suffix)
3117            remove_file = lib_file
3118            runner.add_cmd(["g++", "--share", "-o", lib_file, cpp_file], "", "", "")
3119        elif msg[1] == 'copy_lib':
3120            lib_file = cpp_file.replace('.cpp', lib_suffix)
3121            remove_file = lib_file
3122            if not os.path.exists(lib_file):
3123                with open(cpp_file, "r") as source_file:
3124                    fd = os.open(lib_file, os.O_RDWR | os.O_CREAT | os.O_TRUNC)
3125                    target_file = os.fdopen(fd, 'w')
3126                    target_file.write(source_file.read())
3127        elif msg[1] == 'direct_use':
3128            lib_file = cpp_file
3129            remove_file = ""
3130
3131        js_file = path.join(aop_file_path, "test_aop.js")
3132        runner.add_cmd([runner.es2panda, "--merge-abc", "--transform-lib", lib_file, js_file], msg[2], msg[3], remove_file)
3133
3134    runners.append(runner)
3135
3136
3137class AopTransform(Runner):
3138    def __init__(self, args):
3139        Runner.__init__(self, args, "AopTransform")
3140
3141    def add_cmd(self, cmd, compare_str, compare_abc_str, remove_file, func=TestAop):
3142        self.tests += [func(cmd, compare_str, compare_abc_str, remove_file)]
3143
3144    def test_path(self, src):
3145        return src
3146
3147
3148def main():
3149    args = get_args()
3150
3151    runners = []
3152
3153    if args.regression:
3154        add_directory_for_regression(runners, args)
3155
3156    if args.abc_to_asm:
3157        add_directory_for_asm(runners, args)
3158        add_directory_for_asm(runners, args, "debug")
3159
3160    if args.tsc:
3161        runners.append(TSCRunner(args))
3162
3163    if args.compiler:
3164        add_directory_for_compiler(runners, args)
3165
3166    if args.hotfix:
3167        runners.append(HotfixRunner(args))
3168
3169    if args.hotreload:
3170        runners.append(HotreloadRunner(args))
3171
3172    if args.coldfix:
3173        runners.append(ColdfixRunner(args))
3174
3175    if args.coldreload:
3176        runners.append(ColdreloadRunner(args))
3177
3178    if args.debugger:
3179        runners.append(DebuggerRunner(args))
3180
3181    if args.base64:
3182        runners.append(Base64Runner(args))
3183
3184    if args.bytecode:
3185        add_directory_for_bytecode(runners, args)
3186
3187    if args.aop_transform:
3188        add_cmd_for_aop_transform(runners, args)
3189
3190    if args.debug:
3191        add_directory_for_debug(runners, args)
3192
3193    if args.version_control:
3194        add_directory_for_version_control(runners, args)
3195
3196    failed_tests = 0
3197
3198    for runner in runners:
3199        runner.run()
3200        failed_tests += runner.summarize()
3201
3202    if failed_tests > 0:
3203        exit(1)
3204    exit(0)
3205
3206
3207if __name__ == "__main__":
3208    main()
3209