|  | # Copyright 2008 the V8 project authors. All rights reserved. | 
|  | # Redistribution and use in source and binary forms, with or without | 
|  | # modification, are permitted provided that the following conditions are | 
|  | # met: | 
|  | # | 
|  | #     * Redistributions of source code must retain the above copyright | 
|  | #       notice, this list of conditions and the following disclaimer. | 
|  | #     * Redistributions in binary form must reproduce the above | 
|  | #       copyright notice, this list of conditions and the following | 
|  | #       disclaimer in the documentation and/or other materials provided | 
|  | #       with the distribution. | 
|  | #     * Neither the name of Google Inc. nor the names of its | 
|  | #       contributors may be used to endorse or promote products derived | 
|  | #       from this software without specific prior written permission. | 
|  | # | 
|  | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
|  | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
|  | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
|  | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
|  | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
|  | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
|  | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
|  | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
|  | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | from collections import OrderedDict | 
|  | import itertools | 
|  | import os | 
|  | import re | 
|  |  | 
|  | from testrunner.local import statusfile | 
|  | from testrunner.local import testsuite | 
|  | from testrunner.objects import testcase | 
|  | from testrunner.outproc import base as outproc | 
|  |  | 
|  | try: | 
|  | basestring       # Python 2 | 
|  | except NameError:  # Python 3 | 
|  | basestring = str | 
|  |  | 
|  | FILES_PATTERN = re.compile(r"//\s+Files:(.*)") | 
|  | ENV_PATTERN = re.compile(r"//\s+Environment Variables:(.*)") | 
|  | SELF_SCRIPT_PATTERN = re.compile(r"//\s+Env: TEST_FILE_NAME") | 
|  | NO_HARNESS_PATTERN = re.compile(r"^// NO HARNESS$", flags=re.MULTILINE) | 
|  |  | 
|  |  | 
|  | # Flags known to misbehave when combining arbitrary mjsunit tests. Can also | 
|  | # be compiled regular expressions. | 
|  | MISBEHAVING_COMBINED_TESTS_FLAGS= [ | 
|  | '--check-handle-count', | 
|  | '--enable-tracing', | 
|  | re.compile('--experimental.*'), | 
|  | '--expose-trigger-failure', | 
|  | re.compile('--harmony.*'), | 
|  | '--mock-arraybuffer-allocator', | 
|  | '--print-ast', | 
|  | re.compile('--trace.*'), | 
|  | '--wasm-lazy-compilation', | 
|  | ] | 
|  |  | 
|  |  | 
|  | class TestLoader(testsuite.JSTestLoader): | 
|  | @property | 
|  | def excluded_files(self): | 
|  | return { | 
|  | "mjsunit.js", | 
|  | "mjsunit_numfuzz.js", | 
|  | } | 
|  |  | 
|  |  | 
|  | class TestSuite(testsuite.TestSuite): | 
|  | def _test_loader_class(self): | 
|  | return TestLoader | 
|  |  | 
|  | def _test_combiner_class(self): | 
|  | return TestCombiner | 
|  |  | 
|  | def _test_class(self): | 
|  | return TestCase | 
|  |  | 
|  |  | 
|  | class TestCase(testcase.D8TestCase): | 
|  | def __init__(self, *args, **kwargs): | 
|  | super(TestCase, self).__init__(*args, **kwargs) | 
|  |  | 
|  | source = self.get_source() | 
|  |  | 
|  | files_list = []  # List of file names to append to command arguments. | 
|  | files_match = FILES_PATTERN.search(source); | 
|  | # Accept several lines of 'Files:'. | 
|  | while True: | 
|  | if files_match: | 
|  | files_list += files_match.group(1).strip().split() | 
|  | files_match = FILES_PATTERN.search(source, files_match.end()) | 
|  | else: | 
|  | break | 
|  | files = [ os.path.normpath(os.path.join(self.suite.root, '..', '..', f)) | 
|  | for f in files_list ] | 
|  | testfilename = self._get_source_path() | 
|  | if SELF_SCRIPT_PATTERN.search(source): | 
|  | files = ( | 
|  | ["-e", "TEST_FILE_NAME=\"%s\"" % testfilename.replace("\\", "\\\\")] + | 
|  | files) | 
|  |  | 
|  | if NO_HARNESS_PATTERN.search(source): | 
|  | mjsunit_files = [] | 
|  | else: | 
|  | mjsunit_files = [os.path.join(self.suite.root, "mjsunit.js")] | 
|  |  | 
|  | if self.suite.framework_name == 'num_fuzzer': | 
|  | mjsunit_files.append(os.path.join(self.suite.root, "mjsunit_numfuzz.js")) | 
|  |  | 
|  | self._source_files = files | 
|  | self._source_flags = self._parse_source_flags(source) | 
|  | self._mjsunit_files = mjsunit_files | 
|  | self._files_suffix = [testfilename] | 
|  | self._env = self._parse_source_env(source) | 
|  |  | 
|  | def _parse_source_env(self, source): | 
|  | env_match = ENV_PATTERN.search(source) | 
|  | env = {} | 
|  | if env_match: | 
|  | for env_pair in env_match.group(1).strip().split(): | 
|  | var, value = env_pair.split('=') | 
|  | env[var] = value | 
|  | return env | 
|  |  | 
|  | def _get_source_flags(self): | 
|  | return self._source_flags | 
|  |  | 
|  | def _get_files_params(self): | 
|  | files = list(self._source_files) | 
|  | if not self._test_config.no_harness: | 
|  | files += self._mjsunit_files | 
|  | files += self._files_suffix | 
|  | if self._test_config.isolates: | 
|  | files += ['--isolate'] + files | 
|  |  | 
|  | return files | 
|  |  | 
|  | def _get_cmd_env(self): | 
|  | return self._env | 
|  |  | 
|  | def _get_source_path(self): | 
|  | base_path = os.path.join(self.suite.root, self.path) | 
|  | # Try .js first, and fall back to .mjs. | 
|  | # TODO(v8:9406): clean this up by never separating the path from | 
|  | # the extension in the first place. | 
|  | if os.path.exists(base_path + self._get_suffix()): | 
|  | return base_path + self._get_suffix() | 
|  | return base_path + '.mjs' | 
|  |  | 
|  |  | 
|  | class TestCombiner(testsuite.TestCombiner): | 
|  | def get_group_key(self, test): | 
|  | """Combine tests with the same set of flags. | 
|  | Ignore: | 
|  | 1. Some special cases where it's not obvious what to pass in the command. | 
|  | 2. Tests with flags that can cause failure even inside try-catch wrapper. | 
|  | 3. Tests that use async functions. Async functions can be scheduled after | 
|  | exiting from try-catch wrapper and cause failure. | 
|  | """ | 
|  | if (len(test._files_suffix) > 1 or | 
|  | test._env or | 
|  | not test._mjsunit_files or | 
|  | test._source_files): | 
|  | return None | 
|  |  | 
|  | source_flags = test._get_source_flags() | 
|  | if ('--expose-trigger-failure' in source_flags or | 
|  | '--throws' in source_flags): | 
|  | return None | 
|  |  | 
|  | source_code = test.get_source() | 
|  | # Maybe we could just update the tests to await all async functions they | 
|  | # call? | 
|  | if 'async' in source_code: | 
|  | return None | 
|  |  | 
|  | # TODO(machenbach): Remove grouping if combining tests in a flag-independent | 
|  | # way works well. | 
|  | return 1 | 
|  |  | 
|  | def _combined_test_class(self): | 
|  | return CombinedTest | 
|  |  | 
|  |  | 
|  | class CombinedTest(testcase.D8TestCase): | 
|  | """Behaves like normal mjsunit tests except: | 
|  | 1. Expected outcome is always PASS | 
|  | 2. Instead of one file there is a try-catch wrapper with all combined tests | 
|  | passed as arguments. | 
|  | """ | 
|  | def __init__(self, name, tests): | 
|  | super(CombinedTest, self).__init__(tests[0].suite, '', name, | 
|  | tests[0]._test_config) | 
|  | self._tests = tests | 
|  |  | 
|  | def _prepare_outcomes(self, force_update=True): | 
|  | self._statusfile_outcomes = outproc.OUTCOMES_PASS_OR_TIMEOUT | 
|  | self.expected_outcomes = outproc.OUTCOMES_PASS_OR_TIMEOUT | 
|  |  | 
|  | def _get_shell_flags(self): | 
|  | """In addition to standard set of shell flags it appends: | 
|  | --disable-abortjs: %AbortJS can abort the test even inside | 
|  | trycatch-wrapper, so we disable it. | 
|  | --es-staging: We skip all harmony flags due to false positives, | 
|  | but always pass the staging flag to cover the mature features. | 
|  | --omit-quit: Calling quit() in JS would otherwise early terminate. | 
|  | --quiet-load: suppress any stdout from load() function used by | 
|  | trycatch-wrapper. | 
|  | """ | 
|  | return [ | 
|  | '--test', | 
|  | '--disable-abortjs', | 
|  | '--es-staging', | 
|  | '--omit-quit', | 
|  | '--quiet-load', | 
|  | ] | 
|  |  | 
|  | def _get_cmd_params(self): | 
|  | return ( | 
|  | super(CombinedTest, self)._get_cmd_params() + | 
|  | ['tools/testrunner/trycatch_loader.js', '--'] + | 
|  | self._tests[0]._mjsunit_files + | 
|  | ['--'] + | 
|  | [t._files_suffix[0] for t in self._tests] | 
|  | ) | 
|  |  | 
|  | def _merge_flags(self, flags): | 
|  | """Merges flags from a list of flags. | 
|  |  | 
|  | Flag values not starting with '-' are merged with the preceeding flag, | 
|  | e.g. --foo 1 will become --foo=1. All other flags remain the same. | 
|  |  | 
|  | Returns: A generator of flags. | 
|  | """ | 
|  | if not flags: | 
|  | return | 
|  | # Iterate over flag pairs. ['-'] is a sentinel value for the last iteration. | 
|  | for flag1, flag2 in itertools.izip(flags, flags[1:] + ['-']): | 
|  | if not flag2.startswith('-'): | 
|  | assert '=' not in flag1 | 
|  | yield flag1 + '=' + flag2 | 
|  | elif flag1.startswith('-'): | 
|  | yield flag1 | 
|  |  | 
|  | def _is_flag_blocked(self, flag): | 
|  | for item in MISBEHAVING_COMBINED_TESTS_FLAGS: | 
|  | if isinstance(item, basestring): | 
|  | if item == flag: | 
|  | return True | 
|  | elif item.match(flag): | 
|  | return True | 
|  | return False | 
|  |  | 
|  | def _get_combined_flags(self, flags_gen): | 
|  | """Combines all flags - dedupes, keeps order and filters some flags. | 
|  |  | 
|  | Args: | 
|  | flags_gen: Generator for flag lists. | 
|  | Returns: A list of flags. | 
|  | """ | 
|  | merged_flags = self._merge_flags(list(itertools.chain(*flags_gen))) | 
|  | unique_flags = OrderedDict((flag, True) for flag in merged_flags).keys() | 
|  | return [ | 
|  | flag for flag in unique_flags | 
|  | if not self._is_flag_blocked(flag) | 
|  | ] | 
|  |  | 
|  | def _get_source_flags(self): | 
|  | # Combine flags from all source files. | 
|  | return self._get_combined_flags( | 
|  | test._get_source_flags() for test in self._tests) | 
|  |  | 
|  | def _get_statusfile_flags(self): | 
|  | # Combine flags from all status file entries. | 
|  | return self._get_combined_flags( | 
|  | test._get_statusfile_flags() for test in self._tests) | 
|  |  | 
|  |  | 
|  | def GetSuite(*args, **kwargs): | 
|  | return TestSuite(*args, **kwargs) |