# 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 extension(self): return "" def _should_filter_by_name(self, filename): if not filename.endswith(self.extension): 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): if not self.extension: return filename return filename[:-len(self.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 extension(self): return ".js" 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 self.next() 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): name = root.split(os.path.sep)[-1] with _load_testsuite_module(name, root) as module: return module.GetSuite(name, root, test_config) def __init__(self, name, root, test_config): self.name = name # string self.root = root # string containing path self.test_config = test_config 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