|  | # Copyright 2012 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. | 
|  |  | 
|  |  | 
|  | import fnmatch | 
|  | import imp | 
|  | import itertools | 
|  | import os | 
|  | from contextlib import contextmanager | 
|  |  | 
|  | from . import command | 
|  | from . import statusfile | 
|  | from . import utils | 
|  | from ..objects.testcase import TestCase | 
|  | from .variants import ALL_VARIANTS, ALL_VARIANT_FLAGS | 
|  |  | 
|  |  | 
|  | STANDARD_VARIANT = set(["default"]) | 
|  |  | 
|  |  | 
|  | class VariantsGenerator(object): | 
|  | def __init__(self, variants): | 
|  | self._all_variants = [v for v in variants if v in ALL_VARIANTS] | 
|  | self._standard_variant = [v for v in variants if v in STANDARD_VARIANT] | 
|  |  | 
|  | def gen(self, test): | 
|  | """Generator producing (variant, flags, procid suffix) tuples.""" | 
|  | flags_set = self._get_flags_set(test) | 
|  | for n, variant in enumerate(self._get_variants(test)): | 
|  | yield (variant, flags_set[variant][0], n) | 
|  |  | 
|  | def _get_flags_set(self, test): | 
|  | return ALL_VARIANT_FLAGS | 
|  |  | 
|  | def _get_variants(self, test): | 
|  | if test.only_standard_variant: | 
|  | return self._standard_variant | 
|  | return self._all_variants | 
|  |  | 
|  |  | 
|  | class TestCombiner(object): | 
|  | def get_group_key(self, test): | 
|  | """To indicate what tests can be combined with each other we define a group | 
|  | key for each test. Tests with the same group key can be combined. Test | 
|  | without a group key (None) is not combinable with any other test. | 
|  | """ | 
|  | raise NotImplementedError() | 
|  |  | 
|  | def combine(self, name, tests): | 
|  | """Returns test combined from `tests`. Since we identify tests by their | 
|  | suite and name, `name` parameter should be unique within one suite. | 
|  | """ | 
|  | return self._combined_test_class()(name, tests) | 
|  |  | 
|  | def _combined_test_class(self): | 
|  | raise NotImplementedError() | 
|  |  | 
|  |  | 
|  | class TestLoader(object): | 
|  | """Base class for loading TestSuite tests after applying test suite | 
|  | transformations.""" | 
|  |  | 
|  | def __init__(self, suite, test_class, test_config, test_root): | 
|  | self.suite = suite | 
|  | self.test_class = test_class | 
|  | self.test_config = test_config | 
|  | self.test_root = test_root | 
|  | self.test_count_estimation = len(list(self._list_test_filenames())) | 
|  |  | 
|  | def _list_test_filenames(self): | 
|  | """Implemented by the subclassed TestLoaders to list filenames. | 
|  |  | 
|  | Filenames are expected to be sorted and are deterministic.""" | 
|  | raise NotImplementedError | 
|  |  | 
|  | def _should_filter_by_name(self, name): | 
|  | return False | 
|  |  | 
|  | def _should_filter_by_test(self, test): | 
|  | return False | 
|  |  | 
|  | def _filename_to_testname(self, filename): | 
|  | """Hook for subclasses to write their own filename transformation | 
|  | logic before the test creation.""" | 
|  | return filename | 
|  |  | 
|  | # TODO: not needed for every TestLoader, extract it into a subclass. | 
|  | def _path_to_name(self, path): | 
|  | if utils.IsWindows(): | 
|  | return path.replace(os.path.sep, "/") | 
|  |  | 
|  | return path | 
|  |  | 
|  | def _create_test(self, path, suite, **kwargs): | 
|  | """Converts paths into test objects using the given options""" | 
|  | return self.test_class( | 
|  | suite, path, self._path_to_name(path), self.test_config, **kwargs) | 
|  |  | 
|  | def list_tests(self): | 
|  | """Loads and returns the test objects for a TestSuite""" | 
|  | # TODO: detect duplicate tests. | 
|  | for filename in self._list_test_filenames(): | 
|  | if self._should_filter_by_name(filename): | 
|  | continue | 
|  |  | 
|  | testname = self._filename_to_testname(filename) | 
|  | case = self._create_test(testname, self.suite) | 
|  | if self._should_filter_by_test(case): | 
|  | continue | 
|  |  | 
|  | yield case | 
|  |  | 
|  |  | 
|  | class GenericTestLoader(TestLoader): | 
|  | """Generic TestLoader implementing the logic for listing filenames""" | 
|  | @property | 
|  | def excluded_files(self): | 
|  | return set() | 
|  |  | 
|  | @property | 
|  | def excluded_dirs(self): | 
|  | return set() | 
|  |  | 
|  | @property | 
|  | def excluded_suffixes(self): | 
|  | return set() | 
|  |  | 
|  | @property | 
|  | def test_dirs(self): | 
|  | return [self.test_root] | 
|  |  | 
|  | @property | 
|  | def extensions(self): | 
|  | return [] | 
|  |  | 
|  | def __find_extension(self, filename): | 
|  | for extension in self.extensions: | 
|  | if filename.endswith(extension): | 
|  | return extension | 
|  |  | 
|  | return False | 
|  |  | 
|  | def _should_filter_by_name(self, filename): | 
|  | if not self.__find_extension(filename): | 
|  | return True | 
|  |  | 
|  | for suffix in self.excluded_suffixes: | 
|  | if filename.endswith(suffix): | 
|  | return True | 
|  |  | 
|  | if os.path.basename(filename) in self.excluded_files: | 
|  | return True | 
|  |  | 
|  | return False | 
|  |  | 
|  | def _filename_to_testname(self, filename): | 
|  | extension = self.__find_extension(filename) | 
|  | if not extension: | 
|  | return filename | 
|  |  | 
|  | return filename[:-len(extension)] | 
|  |  | 
|  | def _to_relpath(self, abspath, test_root): | 
|  | return os.path.relpath(abspath, test_root) | 
|  |  | 
|  | def _list_test_filenames(self): | 
|  | for test_dir in sorted(self.test_dirs): | 
|  | test_root = os.path.join(self.test_root, test_dir) | 
|  | for dirname, dirs, files in os.walk(test_root, followlinks=True): | 
|  | dirs.sort() | 
|  | for dir in dirs: | 
|  | if dir in self.excluded_dirs or dir.startswith('.'): | 
|  | dirs.remove(dir) | 
|  |  | 
|  | files.sort() | 
|  | for filename in files: | 
|  | abspath = os.path.join(dirname, filename) | 
|  |  | 
|  | yield self._to_relpath(abspath, test_root) | 
|  |  | 
|  |  | 
|  | class JSTestLoader(GenericTestLoader): | 
|  | @property | 
|  | def extensions(self): | 
|  | return [".js", ".mjs"] | 
|  |  | 
|  |  | 
|  | class TestGenerator(object): | 
|  | def __init__(self, test_count_estimate, slow_tests, fast_tests): | 
|  | self.test_count_estimate = test_count_estimate | 
|  | self.slow_tests = slow_tests | 
|  | self.fast_tests = fast_tests | 
|  | self._rebuild_iterator() | 
|  |  | 
|  | def _rebuild_iterator(self): | 
|  | self._iterator = itertools.chain(self.slow_tests, self.fast_tests) | 
|  |  | 
|  | def __iter__(self): | 
|  | return self | 
|  |  | 
|  | def __next__(self): | 
|  | return next(self) | 
|  |  | 
|  | def next(self): | 
|  | return next(self._iterator) | 
|  |  | 
|  | def merge(self, test_generator): | 
|  | self.test_count_estimate += test_generator.test_count_estimate | 
|  | self.slow_tests = itertools.chain( | 
|  | self.slow_tests, test_generator.slow_tests) | 
|  | self.fast_tests = itertools.chain( | 
|  | self.fast_tests, test_generator.fast_tests) | 
|  | self._rebuild_iterator() | 
|  |  | 
|  |  | 
|  | @contextmanager | 
|  | def _load_testsuite_module(name, root): | 
|  | f = None | 
|  | try: | 
|  | (f, pathname, description) = imp.find_module("testcfg", [root]) | 
|  | yield imp.load_module(name + "_testcfg", f, pathname, description) | 
|  | finally: | 
|  | if f: | 
|  | f.close() | 
|  |  | 
|  | class TestSuite(object): | 
|  | @staticmethod | 
|  | def Load(root, test_config, framework_name): | 
|  | name = root.split(os.path.sep)[-1] | 
|  | with _load_testsuite_module(name, root) as module: | 
|  | return module.GetSuite(name, root, test_config, framework_name) | 
|  |  | 
|  | def __init__(self, name, root, test_config, framework_name): | 
|  | self.name = name  # string | 
|  | self.root = root  # string containing path | 
|  | self.test_config = test_config | 
|  | self.framework_name = framework_name  # name of the test runner impl | 
|  | self.tests = None  # list of TestCase objects | 
|  | self.statusfile = None | 
|  |  | 
|  | self._test_loader = self._test_loader_class()( | 
|  | self, self._test_class(), self.test_config, self.root) | 
|  |  | 
|  | def status_file(self): | 
|  | return "%s/%s.status" % (self.root, self.name) | 
|  |  | 
|  | @property | 
|  | def _test_loader_class(self): | 
|  | raise NotImplementedError | 
|  |  | 
|  | def ListTests(self): | 
|  | return self._test_loader.list_tests() | 
|  |  | 
|  | def __initialize_test_count_estimation(self): | 
|  | # Retrieves a single test to initialize the test generator. | 
|  | next(iter(self.ListTests()), None) | 
|  |  | 
|  | def __calculate_test_count(self): | 
|  | self.__initialize_test_count_estimation() | 
|  | return self._test_loader.test_count_estimation | 
|  |  | 
|  | def load_tests_from_disk(self, statusfile_variables): | 
|  | self.statusfile = statusfile.StatusFile( | 
|  | self.status_file(), statusfile_variables) | 
|  |  | 
|  | test_count = self.__calculate_test_count() | 
|  | slow_tests = (test for test in self.ListTests() if test.is_slow) | 
|  | fast_tests = (test for test in self.ListTests() if not test.is_slow) | 
|  | return TestGenerator(test_count, slow_tests, fast_tests) | 
|  |  | 
|  | def get_variants_gen(self, variants): | 
|  | return self._variants_gen_class()(variants) | 
|  |  | 
|  | def _variants_gen_class(self): | 
|  | return VariantsGenerator | 
|  |  | 
|  | def test_combiner_available(self): | 
|  | return bool(self._test_combiner_class()) | 
|  |  | 
|  | def get_test_combiner(self): | 
|  | cls = self._test_combiner_class() | 
|  | if cls: | 
|  | return cls() | 
|  | return None | 
|  |  | 
|  | def _test_combiner_class(self): | 
|  | """Returns Combiner subclass. None if suite doesn't support combining | 
|  | tests. | 
|  | """ | 
|  | return None | 
|  |  | 
|  | def _test_class(self): | 
|  | raise NotImplementedError |