1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2021-2022 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, MIN_SUPPORT_BC_VERSION, MIX_COMPILE_ENTRY_POINT
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        else:
601            glob_expression = path.join(
602                self.test_root, directory, "**/*.%s" % (extension))
603            files = glob(glob_expression, recursive=True)
604            files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
605            self.tests += list(map(lambda f: CompilerTest(f, flags), files))
606
607    def test_path(self, src):
608        return src
609
610
611class CompilerTest(Test):
612    def __init__(self, test_path, flags):
613        Test.__init__(self, test_path, flags)
614
615    def execute_arkguard(self, runner):
616        input_file_path = self.path
617        arkguard_root_dir = os.path.join(runner.test_root, "../../arkguard")
618        arkgurad_entry_path = os.path.join(arkguard_root_dir, "lib/cli/SecHarmony.js")
619        config_path = os.path.join(arkguard_root_dir, "test/compilerTestConfig.json")
620        arkguard_cmd = [
621            'node',
622            '--no-warnings',
623            arkgurad_entry_path,
624            input_file_path,
625            '--config-path',
626            config_path,
627            '--inplace'
628        ]
629        self.log_cmd(arkguard_cmd)
630        process = subprocess.Popen(arkguard_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
631        out, err = process.communicate()
632        process.wait()
633        success = True
634        if err or process.returncode != 0:
635            success = False
636            self.passed = False
637            self.error = err.decode("utf-8", errors="ignore")
638        return success
639
640    def run(self, runner):
641        test_abc_name = ("%s.abc" % (path.splitext(self.path)[0])).replace("/", "_")
642        test_abc_path = path.join(runner.build_dir, test_abc_name)
643        es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
644        es2abc_cmd.extend(self.flags)
645        es2abc_cmd.extend(["--output=" + test_abc_path])
646        es2abc_cmd.append(self.path)
647        enable_arkguard = runner.args.enable_arkguard
648        if enable_arkguard:
649            success = self.execute_arkguard(runner)
650            if not success:
651                return self
652
653        process = run_subprocess_with_beta3(self, es2abc_cmd)
654        out, err = process.communicate()
655        if "--dump-assembly" in self.flags:
656            pa_expected_path = "".join([self.get_path_to_expected()[:self.get_path_to_expected().rfind(".txt")],
657                                       ".pa.txt"])
658            self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
659            try:
660                with open(pa_expected_path, 'r') as fp:
661                    expected = fp.read()
662                self.passed = expected == self.output and process.returncode in [0, 1]
663            except Exception:
664                self.passed = False
665            if not self.passed:
666                self.error = err.decode("utf-8", errors="ignore")
667                if os.path.exists(test_abc_path):
668                    os.remove(test_abc_path)
669                return self
670        if "--dump-debug-info" in self.flags:
671            self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
672            try:
673                with open(self.get_path_to_expected(), 'r') as fp:
674                    expected = fp.read()
675                self.passed = expected == self.output and process.returncode in [0, 1]
676                if os.path.exists(test_abc_path):
677                    os.remove(test_abc_path)
678                return self
679            except Exception:
680                self.passed = False
681            if not self.passed:
682                self.error = err.decode("utf-8", errors="ignore")
683                if os.path.exists(test_abc_path):
684                    os.remove(test_abc_path)
685                return self
686        if err:
687            self.passed = False
688            self.error = err.decode("utf-8", errors="ignore")
689            return self
690
691        ld_library_path = runner.ld_library_path
692        os.environ.setdefault("LD_LIBRARY_PATH", ld_library_path)
693        run_abc_cmd = [runner.ark_js_vm, '--enable-force-gc=false', test_abc_path]
694        self.log_cmd(run_abc_cmd)
695
696        process = subprocess.Popen(run_abc_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
697        out, err = process.communicate()
698        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
699        expected_path = self.get_path_to_expected()
700        try:
701            with open(expected_path, 'r') as fp:
702                expected = fp.read()
703            self.passed = expected == self.output and process.returncode in [0, 1]
704        except Exception:
705            self.passed = False
706
707        if not self.passed:
708            self.error = err.decode("utf-8", errors="ignore")
709
710        os.remove(test_abc_path)
711
712        return self
713
714
715class CompilerProjectTest(Test):
716    def __init__(self, projects_path, project, test_paths, flags):
717        Test.__init__(self, "", flags)
718        self.projects_path = projects_path
719        self.project = project
720        self.test_paths = test_paths
721        self.files_info_path = os.path.join(os.path.join(self.projects_path, self.project), 'filesInfo.txt')
722        # Skip execution if --dump-assembly exists in flags
723        self.requires_execution = "--dump-assembly" not in self.flags
724        self.file_record_mapping = None
725        self.generated_abc_inputs_path = os.path.join(os.path.join(self.projects_path, self.project), "abcinputs_gen")
726        self.abc_input_filenames = None
727        self.record_names_path = os.path.join(os.path.join(self.projects_path, self.project), 'recordnames.txt')
728        self.abc_inputs_path = os.path.join(os.path.join(self.projects_path, self.project), 'abcinputs')
729
730    def remove_project(self, runner):
731        project_path = runner.build_dir + "/" + self.project
732        if path.exists(project_path):
733            shutil.rmtree(project_path)
734        if path.exists(self.files_info_path):
735            os.remove(self.files_info_path)
736        if path.exists(self.generated_abc_inputs_path):
737            shutil.rmtree(self.generated_abc_inputs_path)
738
739    def get_file_absolute_path_and_name(self, runner):
740        sub_path = self.path[len(self.projects_path):]
741        file_relative_path = path.split(sub_path)[0]
742        file_name = path.split(sub_path)[1]
743        file_absolute_path = runner.build_dir + "/" + file_relative_path
744        return [file_absolute_path, file_name]
745
746    def gen_single_abc(self, runner):
747        for test_path in self.test_paths:
748            self.path = test_path
749            [file_absolute_path, file_name] = self.get_file_absolute_path_and_name(runner)
750            if not path.exists(file_absolute_path):
751                os.makedirs(file_absolute_path)
752
753            test_abc_name = ("%s.abc" % (path.splitext(file_name)[0]))
754            test_abc_path = path.join(file_absolute_path, test_abc_name)
755            es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
756            es2abc_cmd.extend(self.flags)
757            es2abc_cmd.extend(['%s%s' % ("--output=", test_abc_path)])
758            es2abc_cmd.append(self.path)
759            process = run_subprocess_with_beta3(self, es2abc_cmd)
760            out, err = process.communicate()
761            if err:
762                self.passed = False
763                self.error = err.decode("utf-8", errors="ignore")
764                self.remove_project(runner)
765                return self
766
767    def collect_record_mapping(self):
768        # Collect record mappings from recordnames.txt, file format:
769        # 'source_file_name:record_name\n' * n
770        if path.exists(self.record_names_path):
771            with open(self.record_names_path) as mapping_fp:
772                mapping_lines = mapping_fp.readlines()
773                self.file_record_mapping = {}
774                for mapping_line in mapping_lines:
775                    cur_mapping = mapping_line[:-1].split(":")
776                    self.file_record_mapping[cur_mapping[0]] = cur_mapping[1]
777
778    def get_record_name(self, test_path):
779        record_name = os.path.relpath(test_path, os.path.dirname(self.files_info_path)).split('.')[0]
780        if (self.file_record_mapping is not None and record_name in self.file_record_mapping):
781            record_name = self.file_record_mapping[record_name]
782        return record_name
783
784    def collect_abc_inputs(self, runner):
785        # Collect abc input information from the 'abcinputs' directory. Each txt file in the directory
786        # will generate a merged abc file with the same filename and serve as the final abc input.
787        # file format: 'source_file_name.ts\n' * n
788        if not path.exists(self.abc_inputs_path):
789            return
790        if not path.exists(self.generated_abc_inputs_path):
791            os.makedirs(self.generated_abc_inputs_path)
792        self.abc_input_filenames = {}
793        filenames = os.listdir(self.abc_inputs_path)
794        for filename in filenames:
795            if not filename.endswith('.txt'):
796                self.remove_project(runner)
797                raise Exception("Invalid abc input file: %s, only txt files are allowed in abcinputs directory: %s"
798                                % (filename, self.abc_inputs_path))
799            with open(path.join(self.abc_inputs_path, filename)) as abc_inputs_fp:
800                abc_inputs_lines = abc_inputs_fp.readlines()
801                for abc_input_line in abc_inputs_lines:
802                    # filename is 'xxx.txt', remove '.txt' here
803                    self.abc_input_filenames[abc_input_line[:-1]] = filename[:-len('.txt')]
804
805    def get_belonging_abc_input(self, test_path):
806        filename = os.path.relpath(test_path, os.path.dirname(self.files_info_path))
807        if (self.abc_input_filenames is not None and filename in self.abc_input_filenames):
808            return self.abc_input_filenames[filename]
809        return None
810
811    def gen_abc_input_files_infos(self, runner, abc_files_infos, final_file_info_f):
812        for abc_files_info_name in abc_files_infos:
813            abc_files_info = abc_files_infos[abc_files_info_name]
814            if len(abc_files_info) != 0:
815                abc_input_path = path.join(self.generated_abc_inputs_path, abc_files_info_name)
816                abc_files_info_path = ("%s-filesInfo.txt" % (abc_input_path))
817                abc_files_info_fd = os.open(abc_files_info_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC)
818                abc_files_info_f = os.fdopen(abc_files_info_fd, 'w')
819                abc_files_info_f.writelines(abc_files_info)
820                final_file_info_f.writelines('%s-abcinput.abc;;;;%s;\n' % (abc_input_path, abc_files_info_name))
821
822    def gen_files_info(self, runner):
823        # After collect_record_mapping, self.file_record_mapping stores {'source file name' : 'source file record name'}
824        self.collect_record_mapping()
825        # After collect_abc_inputs, self.abc_input_filenames stores {'source file name' : 'belonging abc input name'}
826        self.collect_abc_inputs(runner)
827
828        fd = os.open(self.files_info_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC)
829        f = os.fdopen(fd, 'w')
830        abc_files_infos = {}
831        for test_path in self.test_paths:
832            record_name = self.get_record_name(test_path)
833            module_kind = 'esm'
834            if (os.path.basename(test_path).startswith("commonjs")):
835                module_kind = 'commonjs'
836            is_shared_module = 'false'
837            if (os.path.basename(test_path).startswith("sharedmodule")):
838                is_shared_module = 'true'
839            file_info = ('%s;%s;%s;%s;%s;%s\n' % (test_path, record_name, module_kind,
840                                               os.path.relpath(test_path, self.projects_path), record_name,
841                                               is_shared_module))
842            belonging_abc_input = self.get_belonging_abc_input(test_path)
843            if belonging_abc_input is not None:
844                if not belonging_abc_input in abc_files_infos:
845                    abc_files_infos[belonging_abc_input] = []
846                abc_files_infos[belonging_abc_input].append(file_info)
847            else:
848                f.writelines(file_info)
849        self.gen_abc_input_files_infos(runner, abc_files_infos, f)
850        f.close()
851
852    def gen_es2abc_cmd(self, runner, input_file, output_file):
853        es2abc_cmd = runner.cmd_prefix + [runner.es2panda]
854        es2abc_cmd.extend(self.flags)
855        es2abc_cmd.extend(['%s%s' % ("--output=", output_file)])
856        es2abc_cmd.append(input_file)
857        return es2abc_cmd
858
859    def gen_merged_abc_for_abc_input(self, runner, files_info_name):
860        self.passed = True
861        if not files_info_name.endswith(".txt"):
862            return
863        abc_input_files_info_path = path.join(self.generated_abc_inputs_path, files_info_name)
864        abc_input_merged_abc_path = path.join(self.generated_abc_inputs_path,
865                                              '%s-abcinput.abc' % (files_info_name[:-len('-filesInfo.txt')]))
866
867        abc_input_file_path = '@' + abc_input_files_info_path
868        if "unmerged_abc_input" in self.generated_abc_inputs_path:
869            self.flags.remove("--merge-abc")
870            with open(abc_input_files_info_path, 'r') as fp:
871                abc_input_file_path = fp.read().split(';')[0]
872
873        es2abc_cmd = self.gen_es2abc_cmd(runner, abc_input_file_path, abc_input_merged_abc_path)
874        process = run_subprocess_with_beta3(self, es2abc_cmd)
875        out, err = process.communicate()
876        if err:
877            self.passed = False
878            self.error = err.decode("utf-8", errors="ignore")
879
880    def gen_merged_abc(self, runner):
881        # Generate abc inputs
882        if (os.path.exists(self.generated_abc_inputs_path)):
883            files_info_names = os.listdir(self.generated_abc_inputs_path)
884            for filename in files_info_names:
885                self.gen_merged_abc_for_abc_input(runner, filename)
886                if (not self.passed):
887                    self.remove_project(runner)
888                    return self
889        # Generate the abc to be tested
890        for test_path in self.test_paths:
891            self.path = test_path
892            if (self.path.endswith("-exec.ts")) or (self.path.endswith("-exec.js")):
893                exec_file_path = self.path
894                [file_absolute_path, file_name] = self.get_file_absolute_path_and_name(runner)
895                if not path.exists(file_absolute_path):
896                    os.makedirs(file_absolute_path)
897                test_abc_name = ("%s.abc" % (path.splitext(file_name)[0]))
898                output_abc_name = path.join(file_absolute_path, test_abc_name)
899
900        # reverse merge-abc flag
901        if "merge_abc_consistence_check" in self.path:
902            if "--merge-abc" in self.flags:
903                self.flags.remove("--merge-abc")
904            else:
905                self.flags.append("--merge-abc")
906
907        es2abc_cmd = self.gen_es2abc_cmd(runner, '@' + self.files_info_path, output_abc_name)
908        compile_context_info_path = path.join(path.join(self.projects_path, self.project), "compileContextInfo.json")
909        if path.exists(compile_context_info_path):
910            es2abc_cmd.append("%s%s" % ("--compile-context-info=", compile_context_info_path))
911        process = run_subprocess_with_beta3(self, es2abc_cmd)
912        self.path = exec_file_path
913        out, err = process.communicate()
914
915        # restore merge-abc flag
916        if "merge_abc_consistence_check" in self.path and "--merge-abc" not in self.flags:
917            self.flags.append("--merge-abc")
918
919        # Check dump-assembly outputs when required
920        if "--dump-assembly" in self.flags:
921            pa_expected_path = "".join([self.get_path_to_expected()[:self.get_path_to_expected().rfind(".txt")],
922                                        ".pa.txt"])
923            self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
924            if "merge_abc_consistence_check" in self.path:
925                self.output = self.output.split('.')[0]
926            try:
927                with open(pa_expected_path, 'r') as fp:
928                    expected = fp.read()
929                self.passed = expected == self.output and process.returncode in [0, 1]
930            except Exception:
931                self.passed = False
932            if not self.passed:
933                self.error = err.decode("utf-8", errors="ignore")
934                self.remove_project(runner)
935                return self
936            else:
937                return self
938
939        if err:
940            self.passed = False
941            self.error = err.decode("utf-8", errors="ignore")
942            self.remove_project(runner)
943            return self
944
945    def run(self, runner):
946        # Compile all ts source files in the project to abc files.
947        if ("--merge-abc" in self.flags):
948            self.gen_files_info(runner)
949            self.gen_merged_abc(runner)
950        else:
951            self.gen_single_abc(runner)
952
953        if (not self.requires_execution):
954            self.remove_project(runner)
955            return self
956
957        # Run test files that need to be executed in the project.
958        for test_path in self.test_paths:
959            self.path = test_path
960            if self.path.endswith("-exec.ts"):
961                [file_absolute_path, file_name] = self.get_file_absolute_path_and_name(runner)
962
963                entry_point_name = path.splitext(file_name)[0]
964                test_abc_name = ("%s.abc" % entry_point_name)
965                test_abc_path = path.join(file_absolute_path, test_abc_name)
966
967                ld_library_path = runner.ld_library_path
968                os.environ.setdefault("LD_LIBRARY_PATH", ld_library_path)
969                run_abc_cmd = [runner.ark_js_vm]
970                if ("--merge-abc" in self.flags):
971                    run_abc_cmd.extend(["--entry-point", entry_point_name])
972                run_abc_cmd.extend([test_abc_path])
973                self.log_cmd(run_abc_cmd)
974
975                process = subprocess.Popen(run_abc_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
976                out, err = process.communicate()
977                self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
978                expected_path = self.get_path_to_expected()
979                try:
980                    with open(expected_path, 'r') as fp:
981                        expected = fp.read()
982                    self.passed = expected == self.output and process.returncode in [0, 1]
983                except Exception:
984                    self.passed = False
985
986                if not self.passed:
987                    self.error = err.decode("utf-8", errors="ignore")
988                    self.remove_project(runner)
989                    return self
990
991            self.passed = True
992
993        self.remove_project(runner)
994        return self
995
996
997class TSDeclarationTest(Test):
998    def get_path_to_expected(self):
999        file_name = self.path[:self.path.find(".d.ts")]
1000        return "%s-expected.txt" % file_name
1001
1002
1003class BcVersionRunner(Runner):
1004    def __init__(self, args):
1005        Runner.__init__(self, args, "Target bc version")
1006        self.ts2abc = path.join(self.test_root, '..', 'scripts', 'ts2abc.js')
1007
1008    def add_cmd(self):
1009        api_sub_version_list = ["beta1", "beta2", "beta3"]
1010        for api_version in range(8, 17):
1011            cmd = self.cmd_prefix + [self.es2panda]
1012            cmd += ["--target-bc-version"]
1013            cmd += ["--target-api-version"]
1014            cmd += [str(api_version)]
1015            self.tests += [BcVersionTest(cmd, api_version)]
1016            node_cmd = ["node"] + [self.ts2abc]
1017            node_cmd += ["".join(["es2abc=", self.es2panda])]
1018            node_cmd += ["--target-api-version"]
1019            node_cmd += [str(api_version)]
1020            self.tests += [BcVersionTest(node_cmd, api_version)]
1021
1022            # Add tests for "--target-api-sub-version" option
1023            if api_version == 12:
1024                for api_sub_version in api_sub_version_list:
1025                    new_cmd = cmd.copy()
1026                    new_cmd += ["--target-api-sub-version", api_sub_version]
1027                    self.tests += [BcVersionTest(new_cmd, str(api_version) + '_' + api_sub_version)]
1028                    new_node_cmd = node_cmd.copy()
1029                    new_node_cmd += ["--target-api-sub-version", api_sub_version]
1030                    self.tests += [BcVersionTest(new_node_cmd, str(api_version) + '_' + api_sub_version)]
1031
1032    def run(self):
1033        for test in self.tests:
1034            test.run()
1035
1036
1037class BcVersionTest(Test):
1038    def __init__(self, cmd, api_version):
1039        Test.__init__(self, "", 0)
1040        self.cmd = cmd
1041        self.api_version = api_version
1042        # To avoid problems when api version is upgraded abruptly,
1043        # the corresponding bytecode version of the api version not written in isa.yaml is alaways the newest version.
1044        self.bc_version_expect = {
1045            8: "13.0.0.0",
1046            9: "9.0.0.0",
1047            10: "9.0.0.0",
1048            11: "11.0.2.0",
1049            12: "12.0.2.0",
1050            "12_beta1": "12.0.2.0",
1051            "12_beta2": "12.0.2.0",
1052            "12_beta3": "12.0.6.0",
1053            13: "12.0.6.0",
1054            14: "12.0.6.0",
1055            15: "13.0.0.0",
1056            16: "13.0.0.0"
1057        }
1058        self.es2abc_script_expect = {
1059            8: "0.0.0.2",
1060            9: "9.0.0.0",
1061            10: "9.0.0.0",
1062            11: "11.0.2.0",
1063            12: "12.0.2.0",
1064            "12_beta1": "12.0.2.0",
1065            "12_beta2": "12.0.2.0",
1066            "12_beta3": "12.0.6.0",
1067            13: "12.0.6.0",
1068            14: "12.0.6.0",
1069            15: "13.0.0.0",
1070            16: "13.0.0.0"
1071        }
1072
1073    def run(self):
1074        process = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1075        out, err = process.communicate()
1076        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
1077        if self.cmd[0] == "node":
1078            self.passed = self.es2abc_script_expect.get(self.api_version) == self.output and process.returncode in [0, 1]
1079        else:
1080            self.passed = self.bc_version_expect.get(self.api_version) == self.output and process.returncode in [0, 1]
1081        if not self.passed:
1082            self.error = err.decode("utf-8", errors="ignore")
1083        return self
1084
1085
1086class TransformerRunner(Runner):
1087    def __init__(self, args):
1088        Runner.__init__(self, args, "Transformer")
1089
1090    def add_directory(self, directory, extension, flags):
1091        glob_expression = path.join(
1092            self.test_root, directory, "**/*.%s" % (extension))
1093        files = glob(glob_expression, recursive=True)
1094        files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
1095
1096        self.tests += list(map(lambda f: TransformerTest(f, flags), files))
1097
1098    def test_path(self, src):
1099        return src
1100
1101
1102class TransformerInTargetApiVersion10Runner(Runner):
1103    def __init__(self, args):
1104        Runner.__init__(self, args, "TransformerInTargetApiVersion10")
1105
1106    def add_directory(self, directory, extension, flags):
1107        glob_expression = path.join(
1108            self.test_root, directory, "**/*.%s" % (extension))
1109        files = glob(glob_expression, recursive=True)
1110        files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
1111
1112        self.tests += list(map(lambda f: TransformerTest(f, flags), files))
1113
1114    def test_path(self, src):
1115        return src
1116
1117
1118class TransformerTest(Test):
1119    def __init__(self, test_path, flags):
1120        Test.__init__(self, test_path, flags)
1121
1122    def get_path_to_expected(self):
1123        return "%s-transformed-expected.txt" % (path.splitext(self.path)[0])
1124
1125    def run(self, runner):
1126        cmd = runner.cmd_prefix + [runner.es2panda]
1127        cmd.extend(self.flags)
1128        cmd.append(self.path)
1129        process = run_subprocess_with_beta3(self, cmd)
1130        out, err = process.communicate()
1131        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
1132
1133        expected_path = self.get_path_to_expected()
1134        try:
1135            with open(expected_path, 'r') as fp:
1136                expected = fp.read()
1137            self.passed = expected == self.output and process.returncode in [0, 1]
1138        except Exception:
1139            self.passed = False
1140
1141        if not self.passed:
1142            self.error = err.decode("utf-8", errors="ignore")
1143
1144        return self
1145
1146
1147class PatchTest(Test):
1148    def __init__(self, test_path, mode_arg, target_version, preserve_files):
1149        Test.__init__(self, test_path, "")
1150        self.mode = mode_arg
1151        self.target_version = target_version
1152        self.preserve_files = preserve_files
1153
1154    def gen_cmd(self, runner):
1155        symbol_table_file = os.path.join(self.path, 'base.map')
1156        origin_input_file = 'base.js'
1157        origin_output_abc = os.path.join(self.path, 'base.abc')
1158        modified_input_file = 'base_mod.js'
1159        modified_output_abc = os.path.join(self.path, 'patch.abc')
1160        target_version_cmd = ""
1161        if self.target_version > 0:
1162            target_version_cmd = "--target-api-version=" + str(self.target_version)
1163
1164        gen_base_cmd = runner.cmd_prefix + [runner.es2panda, '--module', target_version_cmd]
1165        if 'record-name-with-dots' in os.path.basename(self.path):
1166            gen_base_cmd.extend(['--merge-abc', '--record-name=record.name.with.dots'])
1167        gen_base_cmd.extend(['--dump-symbol-table', symbol_table_file])
1168        gen_base_cmd.extend(['--output', origin_output_abc])
1169        gen_base_cmd.extend([os.path.join(self.path, origin_input_file)])
1170        self.log_cmd(gen_base_cmd)
1171
1172        if self.mode == 'hotfix':
1173            mode_arg = ["--generate-patch"]
1174        elif self.mode == 'hotreload':
1175            mode_arg = ["--hot-reload"]
1176        elif self.mode == 'coldfix':
1177            mode_arg = ["--generate-patch", "--cold-fix"]
1178        elif self.mode == 'coldreload':
1179            mode_arg = ["--cold-reload"]
1180
1181        patch_test_cmd = runner.cmd_prefix + [runner.es2panda, '--module', target_version_cmd]
1182        patch_test_cmd.extend(mode_arg)
1183        patch_test_cmd.extend(['--input-symbol-table', symbol_table_file])
1184        patch_test_cmd.extend(['--output', modified_output_abc])
1185        patch_test_cmd.extend([os.path.join(self.path, modified_input_file)])
1186        if 'record-name-with-dots' in os.path.basename(self.path):
1187            patch_test_cmd.extend(['--merge-abc', '--record-name=record.name.with.dots'])
1188        dump_assembly_testname = [
1189            'modify-anon-content-keep-origin-name',
1190            'modify-class-memeber-function',
1191            'exist-lexenv-3',
1192            'lexenv-reduce',
1193            'lexenv-increase']
1194        for name in dump_assembly_testname:
1195            if name in os.path.basename(self.path):
1196                patch_test_cmd.extend(['--dump-assembly'])
1197        self.log_cmd(patch_test_cmd)
1198
1199        return gen_base_cmd, patch_test_cmd, symbol_table_file, origin_output_abc, modified_output_abc
1200
1201    def run(self, runner):
1202        gen_base_cmd, patch_test_cmd, symbol_table_file, origin_output_abc, modified_output_abc = self.gen_cmd(runner)
1203
1204        process_base = run_subprocess_with_beta3(None, gen_base_cmd)
1205        stdout_base, stderr_base = process_base.communicate(timeout=runner.args.es2panda_timeout)
1206        if stderr_base:
1207            self.passed = False
1208            self.error = stderr_base.decode("utf-8", errors="ignore")
1209            self.output = stdout_base.decode("utf-8", errors="ignore") + stderr_base.decode("utf-8", errors="ignore")
1210        else:
1211            process_patch = run_subprocess_with_beta3(None, patch_test_cmd)
1212            process_patch = subprocess.Popen(patch_test_cmd, stdout=subprocess.PIPE,
1213                stderr=subprocess.PIPE)
1214            stdout_patch, stderr_patch = process_patch.communicate(timeout=runner.args.es2panda_timeout)
1215            if stderr_patch:
1216                self.passed = False
1217                self.error = stderr_patch.decode("utf-8", errors="ignore")
1218            self.output = stdout_patch.decode("utf-8", errors="ignore") + stderr_patch.decode("utf-8", errors="ignore")
1219
1220        expected_path = os.path.join(self.path, 'expected.txt')
1221        try:
1222            with open(expected_path, 'r') as fp:
1223                # ignore license description lines and skip leading blank lines
1224                expected = (''.join((fp.readlines()[12:]))).lstrip()
1225            self.passed = expected == self.output
1226        except Exception:
1227            self.passed = False
1228
1229        if not self.passed:
1230            self.error = "expected output:" + os.linesep + expected + os.linesep + "actual output:" + os.linesep +\
1231                self.output
1232        if not self.preserve_files:
1233            os.remove(symbol_table_file)
1234            os.remove(origin_output_abc)
1235            if (os.path.exists(modified_output_abc)):
1236                os.remove(modified_output_abc)
1237        return self
1238
1239
1240class PatchRunner(Runner):
1241    def __init__(self, args, name):
1242        Runner.__init__(self, args, name)
1243        self.preserve_files = args.error
1244        self.tests_in_dirs = []
1245        dirs = os.listdir(path.join(self.test_root, "patch"))
1246        for target_version_path in dirs:
1247            self.add_tests(target_version_path, name)
1248
1249    def add_tests(self, target_version_path, name):
1250        name_dir = os.path.join(self.test_root, "patch", target_version_path, name)
1251        if not os.path.exists(name_dir):
1252            return
1253        target_version = 0
1254        if target_version_path.isdigit():
1255            target_version = int(target_version_path)
1256        for sub_path in os.listdir(name_dir):
1257            test_base_path = os.path.join(name_dir, sub_path)
1258            if name != "coldreload":
1259                for test_dir in os.listdir(test_base_path):
1260                    test_path = os.path.join(test_base_path, test_dir)
1261                    self.tests_in_dirs.append(test_path)
1262                    self.tests.append(PatchTest(test_path, name, target_version, self.preserve_files))
1263            else:
1264                self.tests_in_dirs.append(test_base_path)
1265                self.tests.append(PatchTest(test_base_path, name, target_version, self.preserve_files))
1266
1267    def test_path(self, src):
1268        return os.path.basename(src)
1269
1270
1271class HotfixRunner(PatchRunner):
1272    def __init__(self, args):
1273        PatchRunner.__init__(self, args, "hotfix")
1274
1275
1276class HotreloadRunner(PatchRunner):
1277    def __init__(self, args):
1278        PatchRunner.__init__(self, args, "hotreload")
1279
1280
1281class ColdfixRunner(PatchRunner):
1282    def __init__(self, args):
1283        PatchRunner.__init__(self, args, "coldfix")
1284
1285
1286class ColdreloadRunner(PatchRunner):
1287    def __init__(self, args):
1288        PatchRunner.__init__(self, args, "coldreload")
1289
1290
1291class DebuggerTest(Test):
1292    def __init__(self, test_path, mode):
1293        Test.__init__(self, test_path, "")
1294        self.mode = mode
1295
1296    def run(self, runner):
1297        cmd = runner.cmd_prefix + [runner.es2panda, "--module"]
1298        input_file_name = 'base.js'
1299        if self.mode == "debug-mode":
1300            cmd.extend(['--debug-info'])
1301        cmd.extend([os.path.join(self.path, input_file_name)])
1302        cmd.extend(['--dump-assembly'])
1303        process = run_subprocess_with_beta3(self, cmd)
1304        stdout, stderr = process.communicate(timeout=runner.args.es2panda_timeout)
1305        if stderr:
1306            self.passed = False
1307            self.error = stderr.decode("utf-8", errors="ignore")
1308            return self
1309
1310        self.output = stdout.decode("utf-8", errors="ignore")
1311
1312        expected_path = os.path.join(self.path, 'expected.txt')
1313        try:
1314            with open(expected_path, 'r') as fp:
1315                expected = (''.join((fp.readlines()[12:]))).lstrip()
1316            self.passed = expected == self.output
1317        except Exception:
1318            self.passed = False
1319
1320        if not self.passed:
1321            self.error = "expected output:" + os.linesep + expected + os.linesep + "actual output:" + os.linesep +\
1322                self.output
1323
1324        if os.path.exists("base.abc"):
1325            os.remove("base.abc")
1326
1327        return self
1328
1329
1330class DebuggerRunner(Runner):
1331    def __init__(self, args):
1332        Runner.__init__(self, args, "debugger")
1333        self.test_directory = path.join(self.test_root, "debugger")
1334        self.add_test()
1335
1336    def add_test(self):
1337        self.tests = []
1338        self.tests.append(DebuggerTest(os.path.join(self.test_directory, "debugger-in-debug"), "debug-mode"))
1339        self.tests.append(DebuggerTest(os.path.join(self.test_directory, "debugger-in-release"), "release-mode"))
1340
1341
1342class Base64Test(Test):
1343    def __init__(self, test_path, input_type):
1344        Test.__init__(self, test_path, "")
1345        self.input_type = input_type
1346
1347    def run(self, runner):
1348        cmd = runner.cmd_prefix + [runner.es2panda, "--base64Output"]
1349        if self.input_type == "file":
1350            input_file_name = 'input.js'
1351            cmd.extend(['--source-file', input_file_name])
1352            cmd.extend([os.path.join(self.path, input_file_name)])
1353        elif self.input_type == "string":
1354            input_file = os.path.join(self.path, "input.txt")
1355            try:
1356                with open(input_file, 'r') as fp:
1357                    base64_input = (''.join((fp.readlines()[12:]))).lstrip()  # ignore license description lines
1358                    cmd.extend(["--base64Input", base64_input])
1359            except Exception:
1360                self.passed = False
1361        elif self.input_type == "targetApiVersion":
1362            # base64 test for all available target api version.
1363            version = os.path.basename(self.path)
1364            cmd.extend(['--target-api-version', version])
1365            if version == "12":
1366                cmd.append("--target-api-sub-version=beta3")
1367            input_file = os.path.join(self.path, "input.txt")
1368            try:
1369                with open(input_file, 'r') as fp:
1370                    base64_input = (''.join((fp.readlines()[12:]))).lstrip()  # ignore license description lines
1371                    cmd.extend(["--base64Input", base64_input])
1372            except Exception:
1373                self.passed = False
1374        else:
1375            self.error = "Unsupported base64 input type"
1376            self.passed = False
1377            return self
1378
1379        version = os.path.basename(self.path)
1380        if not self.input_type == "targetApiVersion":
1381            cmd.append("--target-api-sub-version=beta3")
1382
1383        self.log_cmd(cmd)
1384
1385        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1386        stdout, stderr = process.communicate(timeout=runner.args.es2panda_timeout)
1387        if stderr:
1388            self.passed = False
1389            self.error = stderr.decode("utf-8", errors="ignore")
1390            return self
1391
1392        self.output = stdout.decode("utf-8", errors="ignore")
1393
1394        expected_path = os.path.join(self.path, 'expected.txt')
1395        try:
1396            with open(expected_path, 'r') as fp:
1397                expected = (''.join((fp.readlines()[12:]))).lstrip()
1398            self.passed = expected == self.output
1399        except Exception:
1400            self.passed = False
1401
1402        if not self.passed:
1403            self.error = "expected output:" + os.linesep + expected + os.linesep + "actual output:" + os.linesep +\
1404                self.output
1405
1406        return self
1407
1408
1409class Base64Runner(Runner):
1410    def __init__(self, args):
1411        Runner.__init__(self, args, "Base64")
1412        self.test_directory = path.join(self.test_root, "base64")
1413        self.add_test()
1414
1415    def add_test(self):
1416        self.tests = []
1417        self.tests.append(Base64Test(os.path.join(self.test_directory, "inputFile"), "file"))
1418        self.tests.append(Base64Test(os.path.join(self.test_directory, "inputString"), "string"))
1419        # current target api version is 12, once a new version is addded, a new testcase should be added here.
1420        current_version = 12
1421        available_target_api_versions = [9, 10, 11, current_version]
1422        for version in available_target_api_versions:
1423            self.tests.append(Base64Test(os.path.join(self.test_directory, "availableTargetApiVersion", str(version)),
1424                "targetApiVersion"))
1425
1426    def test_path(self, src):
1427        return os.path.basename(src)
1428
1429
1430class BytecodeRunner(Runner):
1431    def __init__(self, args):
1432        Runner.__init__(self, args, "Bytecode")
1433
1434    def add_directory(self, directory, extension, flags, func=Test):
1435        glob_expression = path.join(
1436            self.test_root, directory, "**/*.%s" % (extension))
1437        files = glob(glob_expression, recursive=True)
1438        files = fnmatch.filter(files, self.test_root + '**' + self.args.filter)
1439        self.tests += list(map(lambda f: func(f, flags), files))
1440
1441    def test_path(self, src):
1442        return src
1443
1444
1445class ArkJsVmDownload:  # Obtain different versions of ark_js_vm and their dependent libraries
1446    def __init__(self, args):
1447        self.build_dir = args.build_dir
1448        self.url = "https://gitee.com/zhongmingwei123123/ark_js_vm_version.git"
1449        self.local_path = path.join(self.build_dir, "ark_js_vm_version")
1450        self.max_retries = 3
1451
1452    def run_cmd_cwd(self, cmd):
1453        try:
1454            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1455            _, _ = proc.communicate()
1456            return proc.wait()
1457        except Exception as e:
1458            print(f"Error executing command: {e}")
1459            return -1
1460
1461    def git_clone(self, git_url, code_dir):
1462        cmd = ["git", "clone", git_url, code_dir]
1463        retries = 1
1464        while retries <= self.max_retries:
1465            ret = self.run_cmd_cwd(cmd)
1466            if ret == 0:
1467                break
1468            else:
1469                print(f"\n warning: Atempt: #{retries} to clone '{git_url}' failed. Try cloining again")
1470                retries += 1
1471        assert not ret, f"\n error: Cloning '{git_url}' failed."
1472
1473    def run(self):
1474        if not os.path.exists(self.local_path):
1475            print("\nstart downLoad ark_js_vm_version ...\n")
1476            self.git_clone(self.url, self.local_path)
1477            print("\ndownload finish.\n")
1478
1479
1480class AbcTestCasesPrepare:
1481    def __init__(self, args):
1482        self.test_root = path.dirname(path.abspath(__file__))
1483        self.es2panda = path.join(args.build_dir, "es2abc")
1484        self.args = args
1485        self.valid_mode_list = ["non_merge_mode", "merge_mode"]
1486        self.test_abc_path_list = set()
1487
1488    @staticmethod
1489    def split_api_version(version_str):
1490        parts = version_str.split("API")[1].split("beta")
1491        main_part = parts[0]
1492        beta_part = "beta%s" % parts[1] if len(parts) > 1 else ""
1493        return (main_part, beta_part)
1494
1495    def add_abc_directory(self, directory, extension):
1496        test_directory = path.join(self.test_root, directory)
1497        glob_expression = path.join(test_directory, "*.%s" % (extension))
1498        files = glob(glob_expression)
1499        files = fnmatch.filter(files, self.test_root + "**" + self.args.filter)
1500        return files
1501
1502    def gen_abc_versions(self, flags, source_path):
1503        for api_version in API_VERSION_MAP:
1504            main_version, beta_version = AbcTestCasesPrepare.split_api_version(api_version)
1505            output_path = "%s_version_API%s%s.abc" % (
1506                path.splitext(source_path)[0],
1507                main_version,
1508                beta_version,
1509            )
1510            self.test_abc_path_list.add(output_path)
1511            _, stderr = self.compile_for_target_version(flags, source_path, output_path, main_version, beta_version)
1512            if stderr:
1513                raise RuntimeError(f"abc generate error: " % (stderr.decode("utf-8", errors="ignore")))
1514
1515    def gen_abc_tests(self, directory, extension, flags, abc_mode):
1516        if abc_mode not in self.valid_mode_list:
1517            raise ValueError(f"Invalid abc_mode value: {abc_mode}")
1518        test_source_list = self.add_abc_directory(directory, extension)
1519        for input_path in test_source_list:
1520            self.gen_abc_versions(flags, input_path)
1521
1522    def compile_for_target_version(self, flags, input_path, output_path, target_api_version, target_api_sub_version=""):
1523        cmd = []
1524        cmd.append(self.es2panda)
1525        cmd.append(input_path)
1526        cmd.extend(flags)
1527        cmd.append("--target-api-version=%s" % (target_api_version))
1528        cmd.extend(["--output=%s" % (output_path)])
1529        if target_api_version != "":
1530            cmd.append("--target-api-sub-version=%s" % (target_api_sub_version))
1531        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1532        stdout, stderr = process.communicate(timeout=10)
1533        if stderr:
1534            stderr = "Error executing command: %s\n%s" % (cmd, stderr)
1535        return stdout, stderr
1536
1537    def remove_abc_tests(self):
1538        for abc_path in self.test_abc_path_list:
1539            if path.exists(abc_path):
1540                os.remove(abc_path)
1541
1542
1543class AbcVersionControlRunner(Runner):
1544    def __init__(self, args):
1545        super().__init__(args, "AbcVersionControl")
1546        self.valid_mode_list = ["non_merge_mode", "merge_mode", "mix_compile_mode"]
1547
1548    def add_directory(self, directory, extension, flags, abc_mode, is_discard=False):
1549        if abc_mode not in self.valid_mode_list:
1550            raise ValueError(f"Invalid abc_mode value: {abc_mode}")
1551        glob_expression = path.join(self.test_root, directory, "*.%s" % (extension))
1552        files = glob(glob_expression)
1553        files = fnmatch.filter(files, self.test_root + "**" + self.args.filter)
1554        if abc_mode == "mix_compile_mode":
1555            files = [f for f in files if not f.endswith("-expected.txt")]
1556        self.tests += list(map(lambda f: TestAbcVersionControl(f, flags, abc_mode, is_discard), files))
1557
1558    def test_path(self, src):
1559        return src
1560
1561    def run(self):
1562        for test in self.tests:
1563            test.run(self)
1564        self.args.abc_tests_prepare.remove_abc_tests()
1565
1566
1567class VersionControlRunner(Runner):
1568    def __init__(self, args):
1569        Runner.__init__(self, args, "VersionControl")
1570
1571    def add_directory(self, directory, extension, flags, test_version, feature_type, module_dir=None, func=Test):
1572        glob_expression = path.join(self.test_root, directory, "*.%s" % (extension))
1573        files = glob(glob_expression)
1574        files = fnmatch.filter(files, self.test_root + "**" + self.args.filter)
1575        module_path_list = []
1576        if module_dir is not None:
1577            module_path_list = self.add_module_path(module_dir)
1578        self.tests += list(
1579            map(lambda f: TestVersionControl(f, flags, test_version, feature_type, module_path_list), files)
1580        )
1581
1582    def add_module_path(self, module_dir):
1583        module_path_list = []
1584        glob_expression_ts = path.join(self.test_root, module_dir, "*.%s" % ("ts"))
1585        glob_expression_js = path.join(self.test_root, module_dir, "*.%s" % ("js"))
1586        module_path_list = glob(glob_expression_ts)
1587        module_path_list.extend(glob(glob_expression_js))
1588        module_path_list = fnmatch.filter(module_path_list, self.test_root + "**" + self.args.filter)
1589        return module_path_list
1590
1591    def test_path(self, src):
1592        return src
1593
1594    def run(self):
1595        for test in self.tests:
1596            test.run(self)
1597
1598
1599class TestAbcVersionControl(Test):
1600    def __init__(self, test_path, flags, abc_mode, is_discard):
1601        super().__init__(test_path, flags)
1602        self.min_support_version_number = API_VERSION_MAP.get(MIN_SUPPORT_BC_VERSION)
1603        self.abc_mode = abc_mode
1604        self.is_discard = is_discard
1605        self.output = None
1606        self.process = None
1607        self.is_support = False
1608        self.test_abc_list = list()
1609        self.test_input = None
1610        self.target_abc_path = None
1611        self.entry_point = self.get_entry_point()
1612
1613    @staticmethod
1614    def compare_version_number(version1, version2):
1615        v1 = TestAbcVersionControl.version_number_to_tuple(version1)
1616        v2 = TestAbcVersionControl.version_number_to_tuple(version2)
1617        for num1, num2 in zip(v1, v2):
1618            if num1 > num2:
1619                return 1
1620            elif num1 < num2:
1621                return -1
1622        return 0
1623
1624    @staticmethod
1625    def version_number_to_tuple(version):
1626        return tuple(int(part) for part in version.split("."))
1627
1628    def get_entry_point(self):
1629        if self.abc_mode == "merge_mode":
1630            base_name = os.path.basename(self.path)
1631            return os.path.splitext(base_name)[0]
1632        elif self.abc_mode == "mix_compile_mode":
1633            return MIX_COMPILE_ENTRY_POINT
1634        return ""
1635
1636    def get_path_to_expected(self, is_support=False, test_stage=""):
1637        support_name = "supported_" if is_support else "unsupported_"
1638        if self.abc_mode == "mix_compile_mode" and test_stage != "runtime":
1639            support_name = ""
1640        expected_name = path.splitext(self.path)[0].split("_version_API")[0]
1641        expected_path = "%s_%s%s-expected.txt" % (expected_name, support_name, test_stage)
1642        return expected_path
1643
1644    def run_process(self, cmd):
1645        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1646        stdout, stderr = self.process.communicate(timeout=10)
1647        self.output = stdout.decode("utf-8", errors="ignore") + stderr.decode("utf-8", errors="ignore").split("\n")[0]
1648        if stderr:
1649            stderr = "Error executing command: %s\n%s" % (cmd, stderr)
1650        return stdout, stderr
1651
1652    def compile_for_target_version(
1653        self, runner, input_path, output_path, target_api_version, target_api_sub_version=""
1654    ):
1655        cmd = []
1656        cmd.append(runner.es2panda)
1657        if self.abc_mode == "mix_compile_mode":
1658            input_path = "@%s" % (input_path)
1659        cmd.append(input_path)
1660        cmd.extend(self.flags)
1661        cmd.append("--target-api-version=%s" % (target_api_version))
1662        cmd.extend(["--output=%s" % (output_path)])
1663        if target_api_version != "":
1664            cmd.append("--target-api-sub-version=%s" % (target_api_sub_version))
1665        stdout, stderr = self.run_process(cmd)
1666        return stdout, stderr
1667
1668    def generate_abc(self, runner, target_api_version, target_api_sub_version=""):
1669        compile_expected_path = None
1670        target_abc_name = (
1671            "%s_target_%s%s.abc" % (path.splitext(self.path)[0], target_api_version, target_api_sub_version)
1672        ).replace("/", "_")
1673        self.target_abc_path = path.join(runner.build_dir, target_abc_name)
1674        _, stderr = self.compile_for_target_version(
1675            runner, self.path, self.target_abc_path, target_api_version, target_api_sub_version
1676        )
1677        format_content = ""
1678        self.is_support = False
1679
1680        # Extract the API versions of the input abc files from the file name of the test case.
1681        input_api_versions = self.extract_api_versions(path.split(self.path)[1])
1682        input_version_numbers = [API_VERSION_MAP.get(api) for api in input_api_versions]
1683        sorted(input_version_numbers, key=TestAbcVersionControl.version_number_to_tuple)
1684        min_input_version_number = input_version_numbers[0]
1685        max_input_version_number = input_version_numbers[-1]
1686        target_version = "API" + target_api_version + target_api_sub_version
1687        target_version_number = API_VERSION_MAP.get(target_version)
1688
1689        if TestAbcVersionControl.compare_version_number(target_version_number, self.min_support_version_number) < 0:
1690            compile_expected_path = self.get_path_to_expected(
1691                self.is_support, "compile_target_version_below_min_support"
1692            )
1693            format_content = target_api_version
1694        elif (
1695            TestAbcVersionControl.compare_version_number(min_input_version_number, self.min_support_version_number) < 0
1696        ):
1697            compile_expected_path = self.get_path_to_expected(self.is_support, "compile_cur_version_below_min_support")
1698            format_content = self.path
1699        elif TestAbcVersionControl.compare_version_number(target_version_number, max_input_version_number) < 0:
1700            compile_expected_path = self.get_path_to_expected(self.is_support, "compile_target_version_below_cur")
1701            format_content = self.path
1702        elif self.is_discard:
1703            compile_expected_path = self.get_path_to_expected(self.is_support, "compile_discard")
1704        else:
1705            self.is_support = True
1706            if stderr:
1707                self.passed = False
1708            return stderr
1709
1710        try:
1711            with open(compile_expected_path, "r") as fp:
1712                expected = fp.read()
1713                self.passed = expected.format(format_content) in self.output and self.process.returncode in [0, 1]
1714        except Exception:
1715            self.passed = False
1716        return stderr
1717
1718    def execute_abc(self, runner, vm_api_version, vm_api_sub_version="", entry_point=""):
1719        cmd = []
1720        if vm_api_version != "12":
1721            vm_api_sub_version = ""
1722        # there is no virtual machine with version api12beta2 available.
1723        # chosen api12beta1 as a replacement.
1724        elif vm_api_version == "12" and vm_api_sub_version == "beta2":
1725            vm_api_sub_version = "beta1"
1726        ark_js_vm_dir = os.path.join(
1727            runner.build_dir,
1728            "ark_js_vm_version",
1729            "API%s%s" % (vm_api_version, vm_api_sub_version),
1730        )
1731        ld_library_path = os.path.join(ark_js_vm_dir, "lib")
1732        os.environ["LD_LIBRARY_PATH"] = ld_library_path
1733        ark_js_vm_path = os.path.join(ark_js_vm_dir, "ark_js_vm")
1734        cmd.append(ark_js_vm_path)
1735        if entry_point != "":
1736            cmd.append("--entry-point=%s" % entry_point)
1737        cmd.append(self.target_abc_path)
1738        stdout, stderr = self.run_process(cmd)
1739        return stdout, stderr
1740
1741    def test_abc_execution(self, runner, target_api_version, target_api_sub_version=""):
1742        stderr = None
1743        target_version = "API" + target_api_version + target_api_sub_version
1744        target_version_number = API_VERSION_MAP.get(target_version)
1745        for api_version in API_VERSION_MAP:
1746            vm_api_version, vm_api_sub_version = AbcTestCasesPrepare.split_api_version(api_version)
1747            vm_version = "API" + vm_api_version + vm_api_sub_version
1748            vm_version_number = API_VERSION_MAP.get(vm_version)
1749            _, stderr = self.execute_abc(runner, vm_api_version, vm_api_sub_version, self.entry_point)
1750            self.is_support = (
1751                TestAbcVersionControl.compare_version_number(vm_version_number, target_version_number) >= 0
1752            )
1753            runtime_expect_path = self.get_path_to_expected(self.is_support, "runtime")
1754            try:
1755                with open(runtime_expect_path, "r") as fp:
1756                    expected = fp.read()
1757                    if self.is_support and self.abc_mode != "merge_mode":
1758                        self.passed = expected == self.output and self.process.returncode in [0, 1, 255]
1759                    else:
1760                        self.passed = expected in self.output
1761                    pass
1762            except Exception:
1763                self.passed = False
1764            if not self.passed:
1765                return stderr
1766        return stderr
1767
1768    def extract_api_versions(self, file_name):
1769        pattern = r"(API\d+)(beta\d+)?"
1770        matches = re.findall(pattern, file_name)
1771        api_versions = [f"{api}{f'{beta}' if beta else ''}" for api, beta in matches]
1772        return api_versions
1773
1774    def remove_abc(self, abc_path):
1775        if path.exists(abc_path):
1776            os.remove(abc_path)
1777
1778    def run(self, runner):
1779        for api_version in API_VERSION_MAP:
1780            target_api_version, target_api_sub_version = AbcTestCasesPrepare.split_api_version(api_version)
1781            stderr = self.generate_abc(runner, target_api_version, target_api_sub_version)
1782            if not self.passed:
1783                self.error = stderr.decode("utf-8", errors="ignore")
1784                return self
1785            if stderr:
1786                continue
1787            stderr = self.test_abc_execution(runner, target_api_version, target_api_sub_version)
1788            self.remove_abc(self.target_abc_path)
1789            if not self.passed:
1790                self.error = stderr.decode("utf-8", errors="ignore")
1791                return self
1792        return self
1793
1794
1795class TestVersionControl(Test):
1796    def __init__(self, test_path, flags, test_version, feature_type, module_path_list):
1797        Test.__init__(self, test_path, flags)
1798        self.beta_version_default = 3
1799        self.version_with_sub_version_list = [12]
1800        self.target_api_version_list = ["9", "10", "11", "12"]
1801        self.target_api_sub_version_list = ["beta1", "beta2", "beta3"]
1802        self.specific_api_version_list = ["API11", "API12beta3"]
1803        self.output = None
1804        self.process = None
1805        self.test_version = test_version
1806        self.test_abc_path = None
1807        self.feature_type = feature_type
1808        self.module_path_list = module_path_list
1809        self.module_abc_path_set = set()
1810
1811    def split_version(self, version_str):
1812        parts = version_str.split("API")[1].split("beta")
1813        main_part = int(parts[0])
1814        beta_part = int(parts[1]) if len(parts) > 1 else self.beta_version_default
1815        return (main_part, beta_part)
1816
1817    def compare_two_versions(self, version1, version2):
1818        version1_parsed = self.split_version(version1)
1819        version2_parsed = self.split_version(version2)
1820
1821        if version1_parsed < version2_parsed:
1822            return -1
1823        elif version1_parsed > version2_parsed:
1824            return 1
1825        else:
1826            return 0
1827
1828    def get_relative_path(self, from_dir, to_dir):
1829        from_dir = os.path.normpath(from_dir)
1830        to_dir = os.path.normpath(to_dir)
1831        from_dir = os.path.abspath(from_dir)
1832        to_dir = os.path.abspath(to_dir)
1833        from_parts = from_dir.split(os.sep)
1834        to_parts = to_dir.split(os.sep)
1835        common_prefix_length = 0
1836        for part1, part2 in zip(from_parts, to_parts):
1837            if part1 == part2:
1838                common_prefix_length += 1
1839            else:
1840                break
1841        relative_parts = [".."] * (len(from_parts) - common_prefix_length) + to_parts[common_prefix_length:]
1842        relative_path = os.path.join(*relative_parts)
1843        return relative_path
1844
1845    def generate_single_module_abc(self, runner, module_path, target_version):
1846        cmd = []
1847        cmd.append(runner.es2panda)
1848        cmd.append(module_path)
1849        cmd.append("--module")
1850        main_version, sub_version = self.split_version(target_version)
1851        cmd.append("--target-api-version=%s" % (main_version))
1852        if main_version == 12:
1853            cmd.append("--target-api-sub-version=beta%s" % (sub_version))
1854
1855        basename = os.path.basename(module_path)
1856        module_abc_name = "%s.abc" % (path.splitext(basename)[0])
1857        relative_path = self.get_relative_path(path.split(self.path)[0], path.split(module_path)[0])
1858        module_abc_dir = path.join(runner.build_dir, relative_path)
1859        if not os.path.exists(module_abc_dir):
1860            os.makedirs(module_abc_dir)
1861        module_abc_path = path.join(module_abc_dir, module_abc_name)
1862        self.module_abc_path_set.add(module_abc_path)
1863        cmd.extend(["--output=%s" % (module_abc_path)])
1864
1865        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1866        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1867        _, stderr = proc.communicate()
1868        proc.wait()
1869        if stderr:
1870            print(stderr.decode("utf-8", errors="ignore"))
1871
1872    def generate_module_abc(self, runner, target_version):
1873        for module_path in self.module_path_list:
1874            self.generate_single_module_abc(runner, module_path, target_version)
1875
1876    def remove_module_abc(self):
1877        for module_abc_path in self.module_abc_path_set:
1878            if path.exists(module_abc_path):
1879                os.remove(module_abc_path)
1880
1881    def get_path_to_expected(
1882        self, is_support, expected_stage, target_api_version="", specific_api_version="", dump_type=""
1883    ):
1884        support_name = "supported" if is_support else "unsupported"
1885        api_name = ""
1886        # Higher than the specific API version, expected results may differ
1887        if target_api_version != "" and specific_api_version != "":
1888            if self.compare_two_versions(target_api_version, specific_api_version) >= 0:
1889                api_name = "for_higher_or_equal_to_%s_" % (specific_api_version)
1890            else:
1891                api_name = "for_below_%s_" % (specific_api_version)
1892        if dump_type == "ast":
1893            dump_type = ""
1894        elif dump_type == "asm":
1895            dump_type = "asm_"
1896        expected_path = "%s_%s_%s_%s%sversion-expected.txt" % (
1897            path.splitext(self.path)[0],
1898            support_name,
1899            expected_stage,
1900            api_name,
1901            dump_type,
1902        )
1903        return expected_path
1904
1905    def get_path_to_runtime_output_below_version_expected(self):
1906        expected_path = "%s_runtime_below_abc_api_version-expected.txt" % (
1907            path.splitext(self.path)[0])
1908        return expected_path
1909
1910    def get_path_to_runtime_output_expected(self, is_support, target_api_version, is_below_abc_api_version):
1911        path_expected = None
1912        if is_below_abc_api_version:
1913            path_expected = self.get_path_to_runtime_output_below_version_expected()
1914            return path_expected
1915        for specific_api_version in self.specific_api_version_list:
1916            if self.compare_two_versions(target_api_version, specific_api_version) > 0:
1917                continue
1918            path_expected = self.get_path_to_expected(is_support, "runtime", target_api_version, specific_api_version)
1919            if path.exists(path_expected):
1920                return path_expected
1921        return self.get_path_to_expected(is_support, "runtime", target_api_version)
1922
1923    def get_path_to_compile_ast_output_expected(self, is_support):
1924        return self.get_path_to_expected(is_support, "compile")
1925
1926    def get_path_to_compile_asm_output_expected(self, is_support, target_api_version):
1927        path_expected = None
1928        for specific_api_version in self.specific_api_version_list:
1929            if self.compare_two_versions(target_api_version, specific_api_version) > 0:
1930                continue
1931            path_expected = self.get_path_to_expected(
1932                is_support, "compile", target_api_version, specific_api_version, "asm"
1933            )
1934            if path.exists(path_expected):
1935                return path_expected
1936        return self.get_path_to_expected(is_support, "compile", "", "", "asm")
1937
1938    def run_process(self, cmd):
1939        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1940        stdout, stderr = self.process.communicate()
1941        self.output = stdout.decode("utf-8", errors="ignore") + stderr.decode("utf-8", errors="ignore").split("\n")[0]
1942        return stdout, stderr
1943
1944    def run_process_compile(self, runner, target_api_version, target_api_sub_version="bata3", dump_type=""):
1945        cmd = []
1946        cmd.append(runner.es2panda)
1947        cmd.append(self.path)
1948        cmd.extend(self.flags)
1949        cmd.append("--target-api-version=%s" % (target_api_version))
1950        test_abc_name = ("%s.abc" % (path.splitext(self.path)[0])).replace("/", "_")
1951        self.test_abc_path = path.join(runner.build_dir, test_abc_name)
1952        cmd.extend(["--output=%s" % (self.test_abc_path)])
1953        if target_api_version == "12":
1954            cmd.append("--target-api-sub-version=%s" % (target_api_sub_version))
1955        if dump_type == "ast":
1956            cmd.append("--dump-ast")
1957        elif dump_type == "assembly":
1958            cmd.append("--dump-assembly")
1959        stdout, stderr = self.run_process(cmd)
1960        return stdout, stderr
1961
1962    def generate_ast_of_target_version(self, runner, target_api_version, target_api_sub_version="bata3"):
1963        return self.run_process_compile(runner, target_api_version, target_api_sub_version, dump_type="ast")
1964
1965    def generate_asm_of_target_version(self, runner, target_api_version, target_api_sub_version="bata3"):
1966        return self.run_process_compile(runner, target_api_version, target_api_sub_version, dump_type="assembly")
1967
1968    def runtime_for_target_version(self, runner, target_api_version, target_api_sub_version="bata3"):
1969        cmd = []
1970        if target_api_version != "12":
1971            target_api_sub_version = ""
1972        # there is no virtual machine with version api12beta2 available.
1973        # We have chosen api12beta1 as a replacement.
1974        if target_api_version == "12" and target_api_sub_version == "beta2":
1975            target_api_sub_version = "beta1"
1976        ark_js_vm_dir = os.path.join(
1977            runner.build_dir,
1978            "ark_js_vm_version",
1979            "API%s%s" % (target_api_version, target_api_sub_version),
1980        )
1981        ld_library_path = os.path.join(ark_js_vm_dir, "lib")
1982        os.environ["LD_LIBRARY_PATH"] = ld_library_path
1983        ark_js_vm_path = os.path.join(ark_js_vm_dir, "ark_js_vm")
1984        cmd.append(ark_js_vm_path)
1985        cmd.append(self.test_abc_path)
1986        self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1987        stdout, stderr = self.process.communicate()
1988        self.output = stdout.decode("utf-8", errors="ignore") + stderr.decode("utf-8", errors="ignore").split("\n")[0]
1989        return stdout, stderr
1990
1991    def run_for_single_version(self, runner, target_api_version, target_api_sub_version="beta3"):
1992        cur_api_version = "API" + target_api_version + target_api_sub_version
1993        is_support = True if self.compare_two_versions(cur_api_version, self.test_version) >= 0 else False
1994        compile_expected_path = None
1995        stderr = None
1996        if self.feature_type == "syntax_feature":
1997            compile_expected_path = self.get_path_to_compile_ast_output_expected(is_support)
1998            _, stderr = self.generate_ast_of_target_version(runner, target_api_version, target_api_sub_version)
1999        elif self.feature_type == "bytecode_feature":
2000            compile_expected_path = self.get_path_to_compile_asm_output_expected(is_support, cur_api_version)
2001            _, stderr = self.generate_asm_of_target_version(
2002                runner, target_api_version, target_api_sub_version
2003            )
2004        try:
2005            with open(compile_expected_path, "r") as fp:
2006                expected = fp.read()
2007                self.passed = expected == self.output and self.process.returncode in [0, 1]
2008        except Exception:
2009            self.passed = False
2010        if not self.passed or (stderr and self.passed):
2011            return stderr
2012        for api_version in self.target_api_version_list:
2013            # The interception capability of API9 version of ark_js_vm has not yet been launched.
2014            if api_version == "9":
2015                continue
2016            for api_sub_version in self.target_api_sub_version_list:
2017                if not api_version in self.version_with_sub_version_list and api_sub_version != "beta3":
2018                    continue
2019                cur_runtime_api_version = "API" + api_version + api_sub_version
2020                is_below_abc_version = (
2021                    False if self.compare_two_versions(cur_runtime_api_version, cur_api_version) >= 0 else True
2022                )
2023                self.generate_module_abc(runner, cur_runtime_api_version)
2024                _, stderr = self.runtime_for_target_version(runner, api_version, api_sub_version)
2025                runtime_expected_path = self.get_path_to_runtime_output_expected(
2026                    is_support, cur_api_version, is_below_abc_version
2027                )
2028                self.remove_module_abc()
2029                try:
2030                    with open(runtime_expected_path, "r") as fp:
2031                        expected = fp.read()
2032                    if is_below_abc_version:
2033                        self.passed = expected in self.output
2034                    else:
2035                        self.passed = expected == self.output
2036                except Exception:
2037                    self.passed = False
2038                if not self.passed:
2039                    return stderr
2040        return stderr
2041
2042    def run(self, runner):
2043        for target_api_version in self.target_api_version_list:
2044            stderr = None
2045            if target_api_version == "12":
2046                for target_api_sub_version in self.target_api_sub_version_list:
2047                    stderr = self.run_for_single_version(runner, target_api_version, target_api_sub_version)
2048                    if path.exists(self.test_abc_path):
2049                        os.remove(self.test_abc_path)
2050                    if not self.passed:
2051                        self.error = stderr.decode("utf-8", errors="ignore")
2052                        return self
2053            else:
2054                stderr = self.run_for_single_version(runner, target_api_version)
2055                if not self.passed:
2056                    self.error = stderr.decode("utf-8", errors="ignore")
2057                    return self
2058        return self
2059
2060
2061class CompilerTestInfo(object):
2062    def __init__(self, directory, extension, flags):
2063        self.directory = directory
2064        self.extension = extension
2065        self.flags = flags
2066
2067    def update_dir(self, prefiex_dir):
2068        self.directory = os.path.sep.join([prefiex_dir, self.directory])
2069
2070
2071# Copy compiler directory to test/.local directory, and do inplace obfuscation.
2072def prepare_for_obfuscation(compiler_test_infos, test_root):
2073    tmp_dir_name = ".local"
2074    tmp_path = os.path.join(test_root, tmp_dir_name)
2075    if not os.path.exists(tmp_path):
2076        os.mkdir(tmp_path)
2077
2078    test_root_dirs = set()
2079    for info in compiler_test_infos:
2080        root_dir = info.directory.split("/")[0]
2081        test_root_dirs.add(root_dir)
2082
2083    for test_dir in test_root_dirs:
2084        src_dir = os.path.join(test_root, test_dir)
2085        target_dir = os.path.join(tmp_path, test_dir)
2086        if os.path.exists(target_dir):
2087            shutil.rmtree(target_dir)
2088        shutil.copytree(src_dir, target_dir)
2089
2090    for info in compiler_test_infos:
2091        info.update_dir(tmp_dir_name)
2092
2093
2094def add_directory_for_version_control(runners, args):
2095    ark_js_vm_prepared = ArkJsVmDownload(args)
2096    ark_js_vm_prepared.run()
2097    runner = VersionControlRunner(args)
2098    runner.add_directory(
2099        "version_control/API11/syntax_feature",
2100        "js",
2101        ["--module"],
2102        "API11",
2103        "syntax_feature",
2104    )
2105    runner.add_directory(
2106        "version_control/API11/syntax_feature",
2107        "ts",
2108        ["--module"],
2109        "API11",
2110        "syntax_feature",
2111    )
2112    runner.add_directory(
2113        "version_control/API12beta1_and_beta2/syntax_feature",
2114        "ts", ["--module"],
2115        "API12beta1",
2116        "syntax_feature",
2117    )
2118    runner.add_directory(
2119        "version_control/API12beta1_and_beta2/syntax_feature",
2120        "js",
2121        ["--module"],
2122        "API12beta1",
2123        "syntax_feature",
2124    )
2125    runner.add_directory(
2126        "version_control/API12beta3/syntax_feature",
2127        "ts",
2128        ["--module"],
2129        "API12beta3",
2130        "syntax_feature",
2131        "version_control/API12beta3/syntax_feature/import_target",
2132    )
2133    runner.add_directory(
2134        "version_control/API12beta3/syntax_feature",
2135        "js",
2136        ["--module"],
2137        "API12beta3",
2138        "syntax_feature",
2139        "version_control/API12beta3/syntax_feature/import_target",
2140    )
2141    runner.add_directory(
2142        "version_control/API11/bytecode_feature",
2143        "ts",
2144        ["--module"],
2145        "API11",
2146        "bytecode_feature",
2147    )
2148    runner.add_directory(
2149        "version_control/API11/bytecode_feature",
2150        "js",
2151        ["--module"],
2152        "API11",
2153        "bytecode_feature",
2154    )
2155    runner.add_directory(
2156        "version_control/API12beta1_and_beta2/bytecode_feature",
2157        "ts",
2158        ["--module"],
2159        "API12beta1",
2160        "bytecode_feature",
2161        "version_control/API12beta1_and_beta2/bytecode_feature/import_target",
2162    )
2163    runner.add_directory(
2164        "version_control/API12beta1_and_beta2/bytecode_feature",
2165        "js",
2166        ["--module"],
2167        "API12beta1",
2168        "bytecode_feature",
2169        "version_control/API12beta1_and_beta2/bytecode_feature/import_target",
2170    )
2171    runner.add_directory(
2172        "version_control/API12beta3/bytecode_feature",
2173        "ts",
2174        ["--module"],
2175        "API12beta3",
2176        "bytecode_feature",
2177        "version_control/API12beta3/bytecode_feature/import_target",
2178    )
2179    runner.add_directory(
2180        "version_control/API12beta3/bytecode_feature",
2181        "js",
2182        ["--module"],
2183        "API12beta3",
2184        "bytecode_feature",
2185        "version_control/API12beta3/bytecode_feature/import_target",
2186    )
2187    runners.append(runner)
2188
2189    abc_tests_prepare = AbcTestCasesPrepare(args)
2190    abc_tests_prepare.gen_abc_tests(
2191        "version_control/bytecode_version_control/non_merge_mode",
2192        "js",
2193        ["--module"],
2194        "non_merge_mode",
2195    )
2196    abc_tests_prepare.gen_abc_tests(
2197        "version_control/bytecode_version_control/merge_mode",
2198        "js",
2199        ["--module", "--merge-abc"],
2200        "merge_mode",
2201    )
2202    abc_tests_prepare.gen_abc_tests(
2203        "version_control/bytecode_version_control/mixed_compile",
2204        "js",
2205        ["--module", "--merge-abc"],
2206        "merge_mode",
2207    )
2208
2209    args.abc_tests_prepare = abc_tests_prepare
2210    abc_version_control_runner = AbcVersionControlRunner(args)
2211    abc_version_control_runner.add_directory(
2212        "version_control/bytecode_version_control/non_merge_mode",
2213        "abc",
2214        ["--module", "--enable-abc-input"],
2215        "non_merge_mode",
2216    )
2217    abc_version_control_runner.add_directory(
2218        "version_control/bytecode_version_control/merge_mode",
2219        "abc",
2220        ["--module", "--enable-abc-input", "--merge-abc"],
2221        "merge_mode",
2222    )
2223    abc_version_control_runner.add_directory(
2224        "version_control/bytecode_version_control/mixed_compile",
2225        "txt",
2226        ["--module", "--enable-abc-input", "--merge-abc"],
2227        "mix_compile_mode",
2228    )
2229    runners.append(abc_version_control_runner)
2230
2231def add_directory_for_regression(runners, args):
2232    runner = RegressionRunner(args)
2233    runner.add_directory("parser/concurrent", "js", ["--module", "--dump-ast"])
2234    runner.add_directory("parser/js", "js", ["--parse-only", "--dump-ast"])
2235    runner.add_directory("parser/script", "ts", ["--parse-only", "--dump-ast"])
2236    runner.add_directory("parser/ts", "ts",
2237                         ["--parse-only", "--module", "--dump-ast"])
2238    runner.add_directory("parser/ts/type_checker", "ts",
2239                         ["--parse-only", "--enable-type-check", "--module", "--dump-ast"])
2240    runner.add_directory("parser/ts/cases/declaration", "d.ts",
2241                         ["--parse-only", "--module", "--dump-ast"], TSDeclarationTest)
2242    runner.add_directory("parser/commonjs", "js", ["--commonjs", "--parse-only", "--dump-ast"])
2243    runner.add_directory("parser/binder", "js", ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-sub-version=beta3"])
2244    runner.add_directory("parser/binder", "ts", ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-sub-version=beta3"])
2245    runner.add_directory("parser/binder/noModule", "ts", ["--dump-assembly", "--dump-literal-buffer", "--target-api-sub-version=beta3"])
2246    runner.add_directory("parser/binder/api12beta2", "js", ["--dump-assembly", "--target-api-version=12", "--target-api-sub-version=beta2"])
2247    runner.add_directory("parser/js/emptySource", "js", ["--dump-assembly"])
2248    runner.add_directory("parser/js/language/arguments-object", "js", ["--parse-only"])
2249    runner.add_directory("parser/js/language/statements/for-statement", "js", ["--parse-only", "--dump-ast"])
2250    runner.add_directory("parser/js/language/expressions/optional-chain", "js", ["--parse-only", "--dump-ast"])
2251    runner.add_directory("parser/js/language/import/syntax", "js",
2252                         ["--parse-only", "--module", "--target-api-sub-version=beta3"])
2253    runner.add_directory("parser/js/language/import/syntax/beta2", "js",
2254                         ["--parse-only", "--module", "--target-api-version=12", "--target-api-sub-version=beta2"])
2255    runner.add_directory("parser/js/language/import", "ts",
2256                         ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-sub-version=beta3"])
2257    runner.add_directory("parser/sendable_class", "ts",
2258                         ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-sub-version=beta3"])
2259    runner.add_directory("parser/sendable_class/api12beta2", "ts",
2260                         ["--dump-assembly", "--dump-literal-buffer", "--module", "--target-api-version=12", "--target-api-sub-version=beta2"])
2261    runner.add_directory("parser/unicode", "js", ["--parse-only"])
2262    runner.add_directory("parser/ts/stack_overflow", "ts", ["--parse-only", "--dump-ast"])
2263    runner.add_directory("parser/js/module-record/module-record-field-name-option.js", "js",
2264                         ["--module-record-field-name=abc", "--source-file=abc", "--module", "--dump-normalized-asm-program"])
2265    runner.add_directory("parser/annotations", "ts", ["--module", "--dump-ast", "--enable-annotations"])
2266
2267    runners.append(runner)
2268
2269    transformer_runner = TransformerRunner(args)
2270    transformer_runner.add_directory("parser/ts/transformed_cases", "ts",
2271                                     ["--parse-only", "--module", "--dump-transformed-ast",
2272                                     "--check-transformed-ast-structure"])
2273
2274    runners.append(transformer_runner)
2275
2276    bc_version_runner = BcVersionRunner(args)
2277    bc_version_runner.add_cmd()
2278
2279    runners.append(bc_version_runner)
2280
2281    transformer_api_version_10_runner = TransformerInTargetApiVersion10Runner(args)
2282    transformer_api_version_10_runner.add_directory("parser/ts/transformed_cases_api_version_10", "ts",
2283                                                    ["--parse-only", "--module", "--target-api-version=10",
2284                                                    "--dump-transformed-ast"])
2285
2286    runners.append(transformer_api_version_10_runner)
2287
2288def add_directory_for_asm(runners, args, mode=""):
2289    runner = AbcToAsmRunner(args, True if mode == "debug" else False)
2290    runner.add_directory("abc2asm/js", "js", [])
2291    runner.add_directory("abc2asm/ts", "ts", [])
2292    runner.add_directory("compiler/js", "js", [])
2293    runner.add_directory("compiler/ts/cases/compiler", "ts", [])
2294    runner.add_directory("compiler/ts/projects", "ts", ["--module"])
2295    runner.add_directory("compiler/ts/projects", "ts", ["--module", "--merge-abc"])
2296    runner.add_directory("compiler/dts", "d.ts", ["--module", "--opt-level=0"])
2297    runner.add_directory("compiler/commonjs", "js", ["--commonjs"])
2298    runner.add_directory("parser/concurrent", "js", ["--module"])
2299    runner.add_directory("parser/js", "js", [])
2300    runner.add_directory("parser/script", "ts", [])
2301    runner.add_directory("parser/ts", "ts", ["--module"])
2302    runner.add_directory("parser/ts/type_checker", "ts", ["--enable-type-check", "--module"])
2303    runner.add_directory("parser/commonjs", "js", ["--commonjs"])
2304    runner.add_directory("parser/binder", "js", ["--dump-assembly", "--dump-literal-buffer", "--module"])
2305    runner.add_directory("parser/binder", "ts", ["--dump-assembly", "--dump-literal-buffer", "--module"])
2306    runner.add_directory("parser/binder/noModule", "ts", ["--dump-assembly", "--dump-literal-buffer"])
2307    runner.add_directory("parser/js/emptySource", "js", [])
2308    runner.add_directory("parser/js/language/arguments-object", "js", [])
2309    runner.add_directory("parser/js/language/statements/for-statement", "js", [])
2310    runner.add_directory("parser/js/language/expressions/optional-chain", "js", [])
2311    runner.add_directory("parser/sendable_class", "ts", ["--module"])
2312    runner.add_directory("parser/unicode", "js", [])
2313    runner.add_directory("parser/ts/stack_overflow", "ts", [])
2314
2315    runners.append(runner)
2316
2317
2318def add_directory_for_compiler(runners, args):
2319    runner = CompilerRunner(args)
2320    compiler_test_infos = []
2321    compiler_test_infos.append(CompilerTestInfo("compiler/js", "js", ["--module"]))
2322    compiler_test_infos.append(CompilerTestInfo("compiler/ts/cases", "ts", []))
2323    compiler_test_infos.append(CompilerTestInfo("compiler/ts/projects", "ts", ["--module"]))
2324    compiler_test_infos.append(CompilerTestInfo("compiler/ts/projects", "ts", ["--module", "--merge-abc"]))
2325    compiler_test_infos.append(CompilerTestInfo("compiler/annotations-projects", "ts", ["--module", "--enable-annotations", "--merge-abc"]))
2326    compiler_test_infos.append(CompilerTestInfo("compiler/dts", "d.ts", ["--module", "--opt-level=0"]))
2327    compiler_test_infos.append(CompilerTestInfo("compiler/commonjs", "js", ["--commonjs"]))
2328    compiler_test_infos.append(CompilerTestInfo("compiler/interpreter/lexicalEnv", "js", []))
2329    compiler_test_infos.append(CompilerTestInfo("compiler/sendable", "ts", ["--module", "--target-api-sub-version=beta3"]))
2330    compiler_test_infos.append(CompilerTestInfo("optimizer/js/branch-elimination", "js",
2331                                                ["--module", "--branch-elimination", "--dump-assembly"]))
2332    compiler_test_infos.append(CompilerTestInfo("optimizer/js/opt-try-catch-func", "js",
2333                                                ["--module", "--dump-assembly"]))
2334    compiler_test_infos.append(CompilerTestInfo("compiler/debugInfo/", "js",
2335                                                ["--debug-info", "--dump-debug-info", "--source-file", "debug-info.js"]))
2336    compiler_test_infos.append(CompilerTestInfo("compiler/js/module-record-field-name-option.js", "js",
2337                                                ["--module", "--module-record-field-name=abc"]))
2338    compiler_test_infos.append(CompilerTestInfo("compiler/annotations", "ts", ["--module", "--enable-annotations"]))
2339    # Following directories of test cases are for dump-assembly comparison only, and is not executed.
2340    # Check CompilerProjectTest for more details.
2341    compiler_test_infos.append(CompilerTestInfo("optimizer/ts/branch-elimination/projects", "ts",
2342                                                ["--module", "--branch-elimination", "--merge-abc", "--dump-assembly",
2343                                                "--file-threads=8"]))
2344    compiler_test_infos.append(CompilerTestInfo("compiler/bytecodehar/projects", "ts",
2345                                                ["--merge-abc", "--dump-assembly", "--enable-abc-input",
2346                                                 "--dump-deps-info", "--remove-redundant-file",
2347                                                 "--dump-literal-buffer", "--dump-string", "--abc-class-threads=4"]))
2348    compiler_test_infos.append(CompilerTestInfo("compiler/bytecodehar/js/projects", "js",
2349                                                ["--merge-abc", "--dump-assembly", "--enable-abc-input",
2350                                                 "--dump-deps-info", "--remove-redundant-file",
2351                                                 "--dump-literal-buffer", "--dump-string", "--abc-class-threads=4"]))
2352    compiler_test_infos.append(CompilerTestInfo("compiler/bytecodehar/merge_abc_consistence_check/projects", "js",
2353                                                ["--merge-abc", "--dump-assembly", "--enable-abc-input",
2354                                                 "--abc-class-threads=4"]))
2355
2356    compiler_test_infos.append(CompilerTestInfo("compiler/ts/shared_module/projects", "ts",
2357                                                ["--module", "--merge-abc", "--dump-assembly"]))
2358
2359    if args.enable_arkguard:
2360        prepare_for_obfuscation(compiler_test_infos, runner.test_root)
2361
2362    for info in compiler_test_infos:
2363        runner.add_directory(info.directory, info.extension, info.flags)
2364
2365    runners.append(runner)
2366
2367
2368def add_directory_for_bytecode(runners, args):
2369    runner = BytecodeRunner(args)
2370    runner.add_directory("bytecode/commonjs", "js", ["--commonjs", "--dump-assembly"])
2371    runner.add_directory("bytecode/js", "js", ["--dump-assembly"])
2372    runner.add_directory("bytecode/ts/cases", "ts", ["--dump-assembly"])
2373    runner.add_directory("bytecode/ts/ic", "ts", ["--dump-assembly"])
2374    runner.add_directory("bytecode/ts/api11", "ts", ["--dump-assembly", "--module", "--target-api-version=11"])
2375    runner.add_directory("bytecode/ts/api12", "ts", ["--dump-assembly", "--module", "--target-api-version=12"])
2376    runner.add_directory("bytecode/watch-expression", "js", ["--debugger-evaluate-expression", "--dump-assembly"])
2377
2378    runners.append(runner)
2379
2380
2381def add_directory_for_debug(runners, args):
2382    runner = RegressionRunner(args)
2383    runner.add_directory("debug/parser", "js", ["--parse-only", "--dump-ast"])
2384
2385    runners.append(runner)
2386
2387
2388def add_cmd_for_aop_transform(runners, args):
2389    runner = AopTransform(args)
2390
2391    aop_file_path = path.join(runner.test_root, "aop")
2392    lib_suffix = '.so'
2393    #cpp src, deal type, result compare str, abc compare str
2394    msg_list = [
2395        ["correct_modify.cpp", "compile", "aop_transform_start", "new_abc_content"],
2396        ["correct_no_modify.cpp", "compile", "aop_transform_start", ""],
2397        ["exec_error.cpp", "compile", "Transform exec fail", ""],
2398        ["no_func_transform.cpp", "compile", "os::library_loader::ResolveSymbol get func Transform error", ""],
2399        ["error_format.cpp", "copy_lib", "os::library_loader::Load error", ""],
2400        ["".join(["no_exist", lib_suffix]), "dirct_use", "Failed to find file", ""],
2401        ["error_suffix.xxx", "direct_use", "aop transform file suffix support", ""]
2402    ]
2403    for msg in msg_list:
2404        cpp_file = path.join(aop_file_path, msg[0])
2405        if msg[1] == 'compile':
2406            lib_file = cpp_file.replace('.cpp', lib_suffix)
2407            remove_file = lib_file
2408            runner.add_cmd(["g++", "--share", "-o", lib_file, cpp_file], "", "", "")
2409        elif msg[1] == 'copy_lib':
2410            lib_file = cpp_file.replace('.cpp', lib_suffix)
2411            remove_file = lib_file
2412            if not os.path.exists(lib_file):
2413                with open(cpp_file, "r") as source_file:
2414                    fd = os.open(lib_file, os.O_RDWR | os.O_CREAT | os.O_TRUNC)
2415                    target_file = os.fdopen(fd, 'w')
2416                    target_file.write(source_file.read())
2417        elif msg[1] == 'direct_use':
2418            lib_file = cpp_file
2419            remove_file = ""
2420
2421        js_file = path.join(aop_file_path, "test_aop.js")
2422        runner.add_cmd([runner.es2panda, "--merge-abc", "--transform-lib", lib_file, js_file], msg[2], msg[3], remove_file)
2423
2424    runners.append(runner)
2425
2426
2427class AopTransform(Runner):
2428    def __init__(self, args):
2429        Runner.__init__(self, args, "AopTransform")
2430
2431    def add_cmd(self, cmd, compare_str, compare_abc_str, remove_file, func=TestAop):
2432        self.tests += [func(cmd, compare_str, compare_abc_str, remove_file)]
2433
2434    def test_path(self, src):
2435        return src
2436
2437
2438def main():
2439    args = get_args()
2440
2441    runners = []
2442
2443    if args.regression:
2444        add_directory_for_regression(runners, args)
2445
2446    if args.abc_to_asm:
2447        add_directory_for_asm(runners, args)
2448        add_directory_for_asm(runners, args, "debug")
2449
2450    if args.tsc:
2451        runners.append(TSCRunner(args))
2452
2453    if args.compiler:
2454        add_directory_for_compiler(runners, args)
2455
2456    if args.hotfix:
2457        runners.append(HotfixRunner(args))
2458
2459    if args.hotreload:
2460        runners.append(HotreloadRunner(args))
2461
2462    if args.coldfix:
2463        runners.append(ColdfixRunner(args))
2464
2465    if args.coldreload:
2466        runners.append(ColdreloadRunner(args))
2467
2468    if args.debugger:
2469        runners.append(DebuggerRunner(args))
2470
2471    if args.base64:
2472        runners.append(Base64Runner(args))
2473
2474    if args.bytecode:
2475        add_directory_for_bytecode(runners, args)
2476
2477    if args.aop_transform:
2478        add_cmd_for_aop_transform(runners, args)
2479
2480    if args.debug:
2481        add_directory_for_debug(runners, args)
2482
2483    if args.version_control:
2484        add_directory_for_version_control(runners, args)
2485
2486    failed_tests = 0
2487
2488    for runner in runners:
2489        runner.run()
2490        failed_tests += runner.summarize()
2491
2492    # TODO: exit 1 when we have failed tests after all tests are fixed
2493    exit(0)
2494
2495
2496if __name__ == "__main__":
2497    main()
2498