1 # Copyright (C) 2012 Google Inc. All rights reserved. 2 # 3 # Redistribution and use in source and binary forms, with or without 4 # modification, are permitted provided that the following conditions are 5 # met: 6 # 7 # * Redistributions of source code must retain the above copyright 8 # notice, this list of conditions and the following disclaimer. 9 # * Redistributions in binary form must reproduce the above 10 # copyright notice, this list of conditions and the following disclaimer 11 # in the documentation and/or other materials provided with the 12 # distribution. 13 # * Neither the name of Google Inc. nor the names of its 14 # contributors may be used to endorse or promote products derived from 15 # this software without specific prior written permission. 16 # 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 import errno 30 import logging 31 import re 32 33 from webkitpy.layout_tests.models import test_expectations 34 35 36 _log = logging.getLogger(__name__) 37 38 39 class LayoutTestFinder(object): 40 def __init__(self, port, options): 41 self._port = port 42 self._options = options 43 self._filesystem = self._port.host.filesystem 44 self.LAYOUT_TESTS_DIRECTORY = 'LayoutTests' 45 46 def find_tests(self, options, args): 47 paths = self._strip_test_dir_prefixes(args) 48 if options.test_list: 49 paths += self._strip_test_dir_prefixes(self._read_test_names_from_file(options.test_list, self._port.TEST_PATH_SEPARATOR)) 50 test_files = self._port.tests(paths) 51 return (paths, test_files) 52 53 def _strip_test_dir_prefixes(self, paths): 54 return [self._strip_test_dir_prefix(path) for path in paths if path] 55 56 def _strip_test_dir_prefix(self, path): 57 # Handle both "LayoutTests/foo/bar.html" and "LayoutTests\foo\bar.html" if 58 # the filesystem uses '\\' as a directory separator. 59 if path.startswith(self.LAYOUT_TESTS_DIRECTORY + self._port.TEST_PATH_SEPARATOR): 60 return path[len(self.LAYOUT_TESTS_DIRECTORY + self._port.TEST_PATH_SEPARATOR):] 61 if path.startswith(self.LAYOUT_TESTS_DIRECTORY + self._filesystem.sep): 62 return path[len(self.LAYOUT_TESTS_DIRECTORY + self._filesystem.sep):] 63 return path 64 65 def _read_test_names_from_file(self, filenames, test_path_separator): 66 fs = self._filesystem 67 tests = [] 68 for filename in filenames: 69 try: 70 if test_path_separator != fs.sep: 71 filename = filename.replace(test_path_separator, fs.sep) 72 file_contents = fs.read_text_file(filename).split('\n') 73 for line in file_contents: 74 line = self._strip_comments(line) 75 if line: 76 tests.append(line) 77 except IOError, e: 78 if e.errno == errno.ENOENT: 79 _log.critical('') 80 _log.critical('--test-list file "%s" not found' % file) 81 raise 82 return tests 83 84 @staticmethod 85 def _strip_comments(line): 86 commentIndex = line.find('//') 87 if commentIndex is -1: 88 commentIndex = len(line) 89 90 line = re.sub(r'\s+', ' ', line[:commentIndex].strip()) 91 if line == '': 92 return None 93 else: 94 return line 95 96 def skip_tests(self, paths, all_tests_list, expectations, http_tests): 97 all_tests = set(all_tests_list) 98 99 tests_to_skip = expectations.get_tests_with_result_type(test_expectations.SKIP) 100 if self._options.skip_failing_tests: 101 tests_to_skip.update(expectations.get_tests_with_result_type(test_expectations.FAIL)) 102 tests_to_skip.update(expectations.get_tests_with_result_type(test_expectations.FLAKY)) 103 104 if self._options.skipped == 'only': 105 tests_to_skip = all_tests - tests_to_skip 106 elif self._options.skipped == 'ignore': 107 tests_to_skip = set() 108 elif self._options.skipped != 'always': 109 # make sure we're explicitly running any tests passed on the command line; equivalent to 'default'. 110 tests_to_skip -= set(paths) 111 112 return tests_to_skip 113 114 def split_into_chunks(self, test_names): 115 """split into a list to run and a set to skip, based on --run-chunk and --run-part.""" 116 if not self._options.run_chunk and not self._options.run_part: 117 return test_names, set() 118 119 # If the user specifies they just want to run a subset of the tests, 120 # just grab a subset of the non-skipped tests. 121 chunk_value = self._options.run_chunk or self._options.run_part 122 try: 123 (chunk_num, chunk_len) = chunk_value.split(":") 124 chunk_num = int(chunk_num) 125 assert(chunk_num >= 0) 126 test_size = int(chunk_len) 127 assert(test_size > 0) 128 except AssertionError: 129 _log.critical("invalid chunk '%s'" % chunk_value) 130 return (None, None) 131 132 # Get the number of tests 133 num_tests = len(test_names) 134 135 # Get the start offset of the slice. 136 if self._options.run_chunk: 137 chunk_len = test_size 138 # In this case chunk_num can be really large. We need 139 # to make the slave fit in the current number of tests. 140 slice_start = (chunk_num * chunk_len) % num_tests 141 else: 142 # Validate the data. 143 assert(test_size <= num_tests) 144 assert(chunk_num <= test_size) 145 146 # To count the chunk_len, and make sure we don't skip 147 # some tests, we round to the next value that fits exactly 148 # all the parts. 149 rounded_tests = num_tests 150 if rounded_tests % test_size != 0: 151 rounded_tests = (num_tests + test_size - (num_tests % test_size)) 152 153 chunk_len = rounded_tests / test_size 154 slice_start = chunk_len * (chunk_num - 1) 155 # It does not mind if we go over test_size. 156 157 # Get the end offset of the slice. 158 slice_end = min(num_tests, slice_start + chunk_len) 159 160 tests_to_run = test_names[slice_start:slice_end] 161 162 _log.debug('chunk slice [%d:%d] of %d is %d tests' % (slice_start, slice_end, num_tests, (slice_end - slice_start))) 163 164 # If we reached the end and we don't have enough tests, we run some 165 # from the beginning. 166 if slice_end - slice_start < chunk_len: 167 extra = chunk_len - (slice_end - slice_start) 168 _log.debug(' last chunk is partial, appending [0:%d]' % extra) 169 tests_to_run.extend(test_names[0:extra]) 170 171 return (tests_to_run, set(test_names) - set(tests_to_run)) 172