• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2021-2024 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#
17
18from __future__ import annotations
19
20from unittest import TestCase
21from os import path, makedirs
22from pathlib import Path
23from typing import Tuple, Optional, Sequence, List
24from runner.utils import compare_files
25
26from runner.plugins.ets.ets_templates.test_metadata import get_metadata, TestMetadata
27from runner.enum_types.configuration_kind import ConfigurationKind
28from runner.enum_types.fail_kind import FailKind
29from runner.enum_types.params import TestEnv, TestReport, Params
30from runner.test_file_based import TestFileBased
31
32
33class TestETS(TestFileBased):
34    def __init__(self, test_env: TestEnv, test_path: str, flags: List[str], test_id: str) -> None:
35        TestFileBased.__init__(self, test_env, test_path, flags, test_id)
36
37        self.main_entry_point = "ETSGLOBAL::main"
38        self.metadata: TestMetadata = get_metadata(Path(test_path))
39        if self.metadata.package is not None:
40            self.main_entry_point = f"{self.metadata.package}.{self.main_entry_point}"
41
42        # If test fails it contains reason (of FailKind enum) of first failed step
43        # It's supposed if the first step is failed then no step is executed further
44        self.fail_kind = None
45
46        self.bytecode_path = test_env.work_dir.intermediate
47        makedirs(self.bytecode_path, exist_ok=True)
48        self.test_abc = path.join(self.bytecode_path, f"{self.test_id}.abc")
49        self.test_an = path.join(self.bytecode_path, f"{self.test_id}.an")
50        makedirs(path.dirname(self.test_abc), exist_ok=True)
51
52    @property
53    def is_negative_runtime(self) -> bool:
54        """ True if a test is expected to fail on ark """
55        negative_runtime_metadata = self.metadata.tags.negative and not self.metadata.tags.compile_only
56        return negative_runtime_metadata or path.basename(self.path).startswith("n.")
57
58    @property
59    def is_negative_compile(self) -> bool:
60        """ True if a test is expected to fail on es2panda """
61        return self.metadata.tags.negative and self.metadata.tags.compile_only
62
63    @property
64    def is_compile_only(self) -> bool:
65        """ True if a test should be run only on es2panda """
66        return self.metadata.tags.compile_only
67
68    @property
69    def is_valid_test(self) -> bool:
70        """ True if a test is valid """
71        return not self.metadata.tags.not_a_test
72
73    @property
74    def ark_extra_options(self) -> List[str]:
75        return self.metadata.ark_options
76
77    @property
78    def ark_timeout(self) -> int:
79        return self.metadata.timeout if self.metadata.timeout else super().ark_timeout
80
81    @property
82    def dependent_files(self) -> Sequence[TestETS]:
83        if not self.metadata.files:
84            return []
85
86        tests = []
87        for file in self.metadata.files:
88            test_path = Path(self.path).parent / Path(file)
89            current_test_id = Path(self.test_id)
90            test = self.__class__(self.test_env, str(test_path), self.flags, str(current_test_id.parent / Path(file)))
91
92            test_abc_name = f'{current_test_id.stem}_{Path(test.test_abc).name}'
93            test_an_name = f'{current_test_id.stem}_{Path(test.test_an).name}'
94
95            test.test_abc = str(Path(test.test_abc).parent / Path(test_abc_name))
96            test.test_an = str(Path(test.test_abc).parent / Path(test_an_name))
97
98            tests.append(test)
99        return tests
100
101    @property
102    def runtime_args(self) -> List[str]:
103        if not self.dependent_files:
104            return super().runtime_args
105        return self.add_boot_panda_files(super().runtime_args)
106
107    @property
108    def verifier_args(self) -> List[str]:
109        if not self.dependent_files:
110            return super().verifier_args
111        return self.add_boot_panda_files(super().verifier_args)
112
113    def add_boot_panda_files(self, args: List[str]) -> List[str]:
114        dep_files_args = []
115        for arg in args:
116            name = '--boot-panda-files'
117            if name in arg:
118                _, value = arg.split('=')
119                dep_files_args.append(f'{name}={":".join([value] + [dt.test_abc for dt in self.dependent_files])}')
120            else:
121                dep_files_args.append(arg)
122        return dep_files_args
123
124    # pylint: disable=too-many-return-statements
125    def do_run(self) -> TestETS:
126        for test in self.dependent_files:
127            test.do_run()
128
129        if not self.is_valid_test and not self.is_compile_only:
130            return self
131
132        if not self.is_valid_test and self.is_compile_only:
133            self._run_compiler(self.test_abc)
134            return self
135
136        if self.test_env.config.ets.compare_files:
137            return self._run_compare_mode()
138        self.passed, self.report, self.fail_kind = self._run_compiler(self.test_abc)
139
140        if not self.passed or (self.passed and self.is_compile_only):
141            return self
142
143        # Run verifier if required
144        if self.test_env.config.verifier.enable:
145            self.passed, self.report, self.fail_kind = self._run_verifier(self.test_abc)
146            if not self.passed:
147                return self
148
149        # Run aot if required
150        if self.test_env.conf_kind in [ConfigurationKind.AOT, ConfigurationKind.AOT_FULL]:
151            self.passed, self.report, self.fail_kind = self.run_aot(
152                self.test_an,
153                [test.test_abc for test in list(self.dependent_files) + [self]],
154                lambda o, e, rc: rc == 0 and path.exists(self.test_an) and path.getsize(self.test_an) > 0
155            )
156
157            if not self.passed:
158                return self
159
160        self.passed, self.report, self.fail_kind = self.run_runtime(
161            self.test_an,
162            self.test_abc,
163            lambda _, _2, rc: self._runtime_result_validator(rc))
164        return self
165
166    def _run_compare_mode(self) -> TestETS:
167        files = []
168        iterations = self.test_env.config.ets.compare_files_iterations
169        for i in range(iterations):
170            if i != 0:
171                test_ets = self.bytecode_path / self.test_id
172                stem, suffix_ets = test_ets.stem, test_ets.suffix
173                suffix_abc = ".abc"
174                test_abc = test_ets.parent / Path(f'{stem}_{str(i + 1)}{suffix_ets}{suffix_abc}')
175            else:
176                test_abc = Path(self.test_abc)
177
178            files.append(test_abc)
179            self.passed, self.report, self.fail_kind = self._run_compiler(str(test_abc))
180            if not self.passed:
181                return self
182
183        self.passed = compare_files(files)
184        if not self.passed:
185            self.fail_kind = FailKind.COMPARE_FAIL
186        return self
187
188    def _runtime_result_validator(self, return_code: int) -> bool:
189        """
190        :return: True if test is successful, False if failed
191        """
192        if self.is_negative_runtime:
193            return return_code != 0
194
195        return return_code == 0
196
197    def _run_compiler(self, test_abc: str) -> Tuple[bool, TestReport, Optional[FailKind]]:
198        es2panda_flags = []
199        if not self.is_valid_test:
200            es2panda_flags.append('--ets-module')
201        es2panda_flags.extend(self.test_env.es2panda_args)
202        es2panda_flags.append(f"--output={test_abc}")
203        es2panda_flags.append(self.path)
204
205        params = Params(
206            executor=self.test_env.es2panda,
207            flags=es2panda_flags,
208            env=self.test_env.cmd_env,
209            timeout=self.test_env.config.es2panda.timeout,
210            fail_kind_fail=FailKind.ES2PANDA_FAIL,
211            fail_kind_timeout=FailKind.ES2PANDA_TIMEOUT,
212            fail_kind_other=FailKind.ES2PANDA_OTHER,
213        )
214
215        passed, report, fail_kind = self.run_one_step(
216            name="es2panda",
217            params=params,
218            result_validator=lambda _, _2, rc: self._validate_compiler(rc, test_abc)
219        )
220        return passed, report, fail_kind
221
222    def _validate_compiler(self, return_code: int, output_path: str) -> bool:
223        if self.is_negative_compile:
224            return return_code != 0
225        return return_code == 0 and path.exists(output_path) and path.getsize(output_path) > 0
226
227    def _run_verifier(self, test_abc: str) -> Tuple[bool, TestReport, Optional[FailKind]]:
228        TestCase().assertTrue(path.exists(self.test_env.verifier), \
229                              f"Verifier binary '{self.test_env.verifier}' is absent or not set")
230        config_path = self.test_env.config.verifier.config
231        if config_path is None:
232            config_path = path.join(path.dirname(__file__), 'ets-verifier.config')
233
234        verifier_flags = list(self.verifier_args)
235        verifier_flags.append(f"--config-file={config_path}")
236        verifier_flags.append(test_abc)
237
238        params = Params(
239            executor=self.test_env.verifier,
240            flags=verifier_flags,
241            env=self.test_env.cmd_env,
242            timeout=self.test_env.config.verifier.timeout,
243            fail_kind_fail=FailKind.VERIFIER_FAIL,
244            fail_kind_timeout=FailKind.VERIFIER_TIMEOUT,
245            fail_kind_other=FailKind.VERIFIER_OTHER,
246        )
247        return self.run_one_step(
248            name="verifier",
249            params=params,
250            result_validator=lambda _, _2, rc: rc == 0,
251        )
252