1# Copyright 2012 the V8 project authors. All rights reserved. 2# Redistribution and use in source and binary forms, with or without 3# modification, are permitted provided that the following conditions are 4# met: 5# 6# * Redistributions of source code must retain the above copyright 7# notice, this list of conditions and the following disclaimer. 8# * Redistributions in binary form must reproduce the above 9# copyright notice, this list of conditions and the following 10# disclaimer in the documentation and/or other materials provided 11# with the distribution. 12# * Neither the name of Google Inc. nor the names of its 13# contributors may be used to endorse or promote products derived 14# from this software without specific prior written permission. 15# 16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 29import fnmatch 30import imp 31import itertools 32import os 33from contextlib import contextmanager 34 35from . import command 36from . import statusfile 37from . import utils 38from ..objects.testcase import TestCase 39from .variants import ALL_VARIANTS, ALL_VARIANT_FLAGS 40 41 42STANDARD_VARIANT = set(["default"]) 43 44 45class VariantsGenerator(object): 46 def __init__(self, variants): 47 self._all_variants = [v for v in variants if v in ALL_VARIANTS] 48 self._standard_variant = [v for v in variants if v in STANDARD_VARIANT] 49 50 def gen(self, test): 51 """Generator producing (variant, flags, procid suffix) tuples.""" 52 flags_set = self._get_flags_set(test) 53 for n, variant in enumerate(self._get_variants(test)): 54 yield (variant, flags_set[variant][0], n) 55 56 def _get_flags_set(self, test): 57 return ALL_VARIANT_FLAGS 58 59 def _get_variants(self, test): 60 if test.only_standard_variant: 61 return self._standard_variant 62 return self._all_variants 63 64 65class TestCombiner(object): 66 def get_group_key(self, test): 67 """To indicate what tests can be combined with each other we define a group 68 key for each test. Tests with the same group key can be combined. Test 69 without a group key (None) is not combinable with any other test. 70 """ 71 raise NotImplementedError() 72 73 def combine(self, name, tests): 74 """Returns test combined from `tests`. Since we identify tests by their 75 suite and name, `name` parameter should be unique within one suite. 76 """ 77 return self._combined_test_class()(name, tests) 78 79 def _combined_test_class(self): 80 raise NotImplementedError() 81 82 83class TestLoader(object): 84 """Base class for loading TestSuite tests after applying test suite 85 transformations.""" 86 87 def __init__(self, suite, test_class, test_config, test_root): 88 self.suite = suite 89 self.test_class = test_class 90 self.test_config = test_config 91 self.test_root = test_root 92 self.test_count_estimation = len(list(self._list_test_filenames())) 93 94 def _list_test_filenames(self): 95 """Implemented by the subclassed TestLoaders to list filenames. 96 97 Filenames are expected to be sorted and are deterministic.""" 98 raise NotImplementedError 99 100 def _should_filter_by_name(self, name): 101 return False 102 103 def _should_filter_by_test(self, test): 104 return False 105 106 def _filename_to_testname(self, filename): 107 """Hook for subclasses to write their own filename transformation 108 logic before the test creation.""" 109 return filename 110 111 # TODO: not needed for every TestLoader, extract it into a subclass. 112 def _path_to_name(self, path): 113 if utils.IsWindows(): 114 return path.replace(os.path.sep, "/") 115 116 return path 117 118 def _create_test(self, path, suite, **kwargs): 119 """Converts paths into test objects using the given options""" 120 return self.test_class( 121 suite, path, self._path_to_name(path), self.test_config, **kwargs) 122 123 def list_tests(self): 124 """Loads and returns the test objects for a TestSuite""" 125 # TODO: detect duplicate tests. 126 for filename in self._list_test_filenames(): 127 if self._should_filter_by_name(filename): 128 continue 129 130 testname = self._filename_to_testname(filename) 131 case = self._create_test(testname, self.suite) 132 if self._should_filter_by_test(case): 133 continue 134 135 yield case 136 137 138class GenericTestLoader(TestLoader): 139 """Generic TestLoader implementing the logic for listing filenames""" 140 @property 141 def excluded_files(self): 142 return set() 143 144 @property 145 def excluded_dirs(self): 146 return set() 147 148 @property 149 def excluded_suffixes(self): 150 return set() 151 152 @property 153 def test_dirs(self): 154 return [self.test_root] 155 156 @property 157 def extensions(self): 158 return [] 159 160 def __find_extension(self, filename): 161 for extension in self.extensions: 162 if filename.endswith(extension): 163 return extension 164 165 return False 166 167 def _should_filter_by_name(self, filename): 168 if not self.__find_extension(filename): 169 return True 170 171 for suffix in self.excluded_suffixes: 172 if filename.endswith(suffix): 173 return True 174 175 if os.path.basename(filename) in self.excluded_files: 176 return True 177 178 return False 179 180 def _filename_to_testname(self, filename): 181 extension = self.__find_extension(filename) 182 if not extension: 183 return filename 184 185 return filename[:-len(extension)] 186 187 def _to_relpath(self, abspath, test_root): 188 return os.path.relpath(abspath, test_root) 189 190 def _list_test_filenames(self): 191 for test_dir in sorted(self.test_dirs): 192 test_root = os.path.join(self.test_root, test_dir) 193 for dirname, dirs, files in os.walk(test_root, followlinks=True): 194 dirs.sort() 195 for dir in dirs: 196 if dir in self.excluded_dirs or dir.startswith('.'): 197 dirs.remove(dir) 198 199 files.sort() 200 for filename in files: 201 abspath = os.path.join(dirname, filename) 202 203 yield self._to_relpath(abspath, test_root) 204 205 206class JSTestLoader(GenericTestLoader): 207 @property 208 def extensions(self): 209 return [".js", ".mjs"] 210 211 212class TestGenerator(object): 213 def __init__(self, test_count_estimate, slow_tests, fast_tests): 214 self.test_count_estimate = test_count_estimate 215 self.slow_tests = slow_tests 216 self.fast_tests = fast_tests 217 self._rebuild_iterator() 218 219 def _rebuild_iterator(self): 220 self._iterator = itertools.chain(self.slow_tests, self.fast_tests) 221 222 def __iter__(self): 223 return self 224 225 def __next__(self): 226 return self.next() 227 228 def next(self): 229 return next(self._iterator) 230 231 def merge(self, test_generator): 232 self.test_count_estimate += test_generator.test_count_estimate 233 self.slow_tests = itertools.chain( 234 self.slow_tests, test_generator.slow_tests) 235 self.fast_tests = itertools.chain( 236 self.fast_tests, test_generator.fast_tests) 237 self._rebuild_iterator() 238 239 240@contextmanager 241def _load_testsuite_module(name, root): 242 f = None 243 try: 244 (f, pathname, description) = imp.find_module("testcfg", [root]) 245 yield imp.load_module(name + "_testcfg", f, pathname, description) 246 finally: 247 if f: 248 f.close() 249 250class TestSuite(object): 251 @staticmethod 252 def Load(root, test_config, framework_name): 253 name = root.split(os.path.sep)[-1] 254 with _load_testsuite_module(name, root) as module: 255 return module.GetSuite(name, root, test_config, framework_name) 256 257 def __init__(self, name, root, test_config, framework_name): 258 self.name = name # string 259 self.root = root # string containing path 260 self.test_config = test_config 261 self.framework_name = framework_name # name of the test runner impl 262 self.tests = None # list of TestCase objects 263 self.statusfile = None 264 265 self._test_loader = self._test_loader_class()( 266 self, self._test_class(), self.test_config, self.root) 267 268 def status_file(self): 269 return "%s/%s.status" % (self.root, self.name) 270 271 @property 272 def _test_loader_class(self): 273 raise NotImplementedError 274 275 def ListTests(self): 276 return self._test_loader.list_tests() 277 278 def __initialize_test_count_estimation(self): 279 # Retrieves a single test to initialize the test generator. 280 next(iter(self.ListTests()), None) 281 282 def __calculate_test_count(self): 283 self.__initialize_test_count_estimation() 284 return self._test_loader.test_count_estimation 285 286 def load_tests_from_disk(self, statusfile_variables): 287 self.statusfile = statusfile.StatusFile( 288 self.status_file(), statusfile_variables) 289 290 test_count = self.__calculate_test_count() 291 slow_tests = (test for test in self.ListTests() if test.is_slow) 292 fast_tests = (test for test in self.ListTests() if not test.is_slow) 293 return TestGenerator(test_count, slow_tests, fast_tests) 294 295 def get_variants_gen(self, variants): 296 return self._variants_gen_class()(variants) 297 298 def _variants_gen_class(self): 299 return VariantsGenerator 300 301 def test_combiner_available(self): 302 return bool(self._test_combiner_class()) 303 304 def get_test_combiner(self): 305 cls = self._test_combiner_class() 306 if cls: 307 return cls() 308 return None 309 310 def _test_combiner_class(self): 311 """Returns Combiner subclass. None if suite doesn't support combining 312 tests. 313 """ 314 return None 315 316 def _test_class(self): 317 raise NotImplementedError 318