Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python | |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Layout tests module that is necessary for tha layout analyzer. | |
| 7 | |
| 8 Layout tests are stored in Webkit SVN and LayoutTestCaseManager collects these | |
| 9 layout test cases (including description). | |
| 10 """ | |
| 11 | |
| 12 import copy | |
| 13 import csv | |
| 14 import locale | |
| 15 import pysvn | |
| 16 import re | |
| 17 import sys | |
| 18 import urllib2 | |
| 19 | |
| 20 | |
| 21 # Webkit SVN root location. | |
| 22 DEFAULT_LAYOUTTEST_LOCATION = ( | |
| 23 'http://svn.webkit.org/repository/webkit/trunk/LayoutTests/') | |
| 24 | |
| 25 # When parsing the test HTML file and finding the test description, | |
| 26 # this script tries to find the test description using sentences | |
| 27 # starting with these keywords. This is adhoc but it is the only way | |
| 28 # since there is no standard for writing test description. | |
| 29 KEYWORDS_FOR_TEST_DESCRIPTION = ['This test', 'Tests that', 'Test '] | |
| 30 | |
| 31 # If cannot find the keywords, this script tries to find test case | |
| 32 # description by the following tags. | |
| 33 TAGS_FOR_TEST_DESCRIPTION = ['title', 'p', 'div'] | |
| 34 | |
| 35 # If cannot find the tags, this script tries to find the test case | |
| 36 # description in the sentence containing following words. | |
| 37 KEYWORD_FOR_TEST_DESCRIPTION_FAIL_SAFE = ['PASSED ', 'PASS:'] | |
| 38 | |
| 39 | |
| 40 class LayoutTests(object): | |
| 41 """A class to store test names in layout tests. | |
| 42 | |
| 43 The test names (including regular expression patterns) are read from a CSV | |
| 44 file and used for getting layout test names from Webkit SVN. | |
| 45 """ | |
| 46 | |
| 47 def __init__(self, layouttest_root_path=DEFAULT_LAYOUTTEST_LOCATION, | |
| 48 csv_file_path=None): | |
| 49 """Initialize LayoutTests using root and CSV file. | |
| 50 | |
| 51 Args: | |
| 52 layouttest_root_path: A location string where Webkit layout tests are | |
| 53 stored. | |
| 54 csv_file_path: CSV file path. The file contains a list of test names | |
| 55 in CSV format. When this parameter is not specified, the names of | |
| 56 all layout tests are retrieved under the root directory. | |
| 57 """ | |
| 58 filter_names = [] | |
| 59 if csv_file_path: | |
| 60 filter_names = LayoutTests.GetLayoutTestNamesFromCSV(csv_file_path) | |
| 61 parent_location_list = LayoutTests.GetParentDirectoryList(filter_names) | |
| 62 if layouttest_root_path.startswith('http://'): | |
| 63 name_map = self.GetLayoutTestNamesFromSVN(parent_location_list, | |
| 64 layouttest_root_path) | |
| 65 else: | |
| 66 # TODO(imasaki): support other forms such as CSV for reading test names. | |
| 67 pass | |
| 68 self.name_map = copy.copy(name_map) | |
| 69 # Filter names. | |
| 70 for lt_name in name_map.keys(): | |
| 71 match = False | |
| 72 for filter_name in filter_names: | |
| 73 if re.search(filter_name, lt_name): | |
| 74 match = True | |
| 75 break | |
| 76 if not match: | |
| 77 del self.name_map[lt_name] | |
| 78 # We get description only for the filtered names. | |
| 79 for lt_name in self.name_map.keys(): | |
| 80 self.name_map[lt_name] = LayoutTests.GetTestDescriptionFromSVN(lt_name) | |
| 81 | |
| 82 @staticmethod | |
| 83 def ExtractTestDescription(txt): | |
| 84 """Extract the description description from test code in HTML. | |
| 85 | |
| 86 Currently, we have 4 rules described in the code below. | |
| 87 (This example falls into rule 1): | |
| 88 <p> | |
| 89 This tests the intrinsic size of a video element is the default | |
| 90 300,150 before metadata is loaded, and 0,0 after | |
| 91 metadata is loaded for an audio-only file. | |
| 92 </p> | |
| 93 The strategy is very adhoc since the original test case files | |
| 94 (in HTML format) do not have standard way to store test description. | |
| 95 | |
| 96 Args: | |
| 97 txt: A HTML text which may or may not contain test description. | |
| 98 | |
| 99 Returns: | |
| 100 A string that contains test description. Returns 'UNKNOWN' if the | |
| 101 test description is not found. | |
| 102 """ | |
| 103 # (1) Try to find test description that contains keywords such as | |
| 104 # 'test that' and surrounded by p tag. | |
| 105 # This is the most common case. | |
| 106 for keyword in KEYWORDS_FOR_TEST_DESCRIPTION: | |
| 107 # Try to find <p> and </p>. | |
| 108 pattern = r'<p>(.*' + keyword + '.*)</p>' | |
| 109 matches = re.search(pattern, txt) | |
| 110 if matches is not None: | |
| 111 return matches.group(1).strip() | |
| 112 | |
| 113 # (2) Try to find it by using more generic keywords such as 'PASS' etc. | |
| 114 for keyword in KEYWORD_FOR_TEST_DESCRIPTION_FAIL_SAFE: | |
| 115 # Try to find new lines. | |
| 116 pattern = r'\n(.*' + keyword + '.*)\n' | |
| 117 matches = re.search(pattern, txt) | |
| 118 if matches is not None: | |
| 119 # Remove 'p' tag. | |
| 120 text = matches.group(1).strip() | |
| 121 return text.replace('<p>', '').replace('</p>', '') | |
| 122 | |
| 123 # (3) Try to find it by using HTML tag such as title. | |
| 124 for tag in TAGS_FOR_TEST_DESCRIPTION: | |
| 125 pattern = r'<' + tag + '>(.*)</' + tag + '>' | |
| 126 matches = re.search(pattern, txt) | |
| 127 if matches is not None: | |
| 128 return matches.group(1).strip() | |
| 129 | |
| 130 # (4) Try to find it by using test description and remove 'p' tag. | |
| 131 for keyword in KEYWORDS_FOR_TEST_DESCRIPTION: | |
| 132 # Try to find <p> and </p>. | |
| 133 pattern = r'\n(.*' + keyword + '.*)\n' | |
| 134 matches = re.search(pattern, txt) | |
| 135 if matches is not None: | |
| 136 # Remove 'p' tag. | |
| 137 text = matches.group(1).strip() | |
| 138 return text.replace('<p>', '').replace('</p>', '') | |
| 139 | |
| 140 # (5) cannot find test description using existing rules. | |
| 141 return 'UNKNOWN' | |
| 142 | |
| 143 @staticmethod | |
| 144 def GetLayoutTestNamesFromSVN(parent_location_list, | |
| 145 layouttest_root_path): | |
| 146 """Get LayoutTest names from Webkit SVN. | |
| 147 | |
| 148 Args: | |
| 149 parent_location_list: a list of locations of parent directory. This is | |
|
dennis_jeffrey
2011/08/26 19:01:26
nit: 'directory' --> 'directories'
imasaki1
2011/08/26 22:28:44
Done.
| |
| 150 used when getting layout tests using PySVN.list(). | |
| 151 layouttest_root_path: the root path of the Webkit SVN directory. | |
| 152 | |
| 153 Returns: | |
| 154 a map containing test names as keys for de-dupe. | |
| 155 """ | |
| 156 client = pysvn.Client() | |
| 157 # Get directory structure in the Webkit SVN. | |
| 158 if parent_location_list is None: | |
| 159 # Try to get all the layout tests by enabling recursion option. | |
| 160 parent_location_list = ['/\S+\.html$'] | |
| 161 recursion = True | |
| 162 else: | |
| 163 recursion = False | |
| 164 name_map = {} | |
| 165 for parent_location in parent_location_list: | |
| 166 if parent_location.endswith('/'): | |
| 167 file_list = client.list(layouttest_root_path + parent_location, | |
| 168 recurse=recursion) | |
| 169 for file_name in file_list: | |
| 170 if sys.stdout.isatty(): | |
| 171 default_encoding = sys.stdout.encoding | |
| 172 else: | |
| 173 default_encoding = locale.getpreferredencoding() | |
| 174 file_name = file_name[0].repos_path.encode(default_encoding) | |
| 175 # Remove the word '/truck/LayoutTests'. | |
| 176 file_name = file_name.replace('/trunk/LayoutTests/', '') | |
| 177 if file_name.endswith('.html'): | |
| 178 name_map[file_name] = True | |
| 179 return name_map | |
| 180 | |
| 181 @staticmethod | |
| 182 def GetLayoutTestNamesFromCSV(csv_file_path): | |
| 183 """Get layout test names from CSV file. | |
| 184 | |
| 185 Args: | |
| 186 csv_file_path: the path for the CSV file containing test names (including | |
| 187 regular expression patterns). The CSV file content has one column and | |
| 188 each row contains a test name. | |
| 189 """ | |
| 190 file_object = file(csv_file_path, 'r') | |
| 191 reader = csv.reader(file_object) | |
| 192 names = [row[0] for row in reader] | |
| 193 file_object.close() | |
| 194 return names | |
| 195 | |
| 196 @staticmethod | |
| 197 def GetParentDirectoryList(names): | |
| 198 """Get parent directory list from test names. | |
| 199 | |
| 200 Args: | |
| 201 names: a list of test names. The test names also have path information as | |
| 202 well (e.g., media/video-zoom.html). | |
| 203 """ | |
| 204 pd_map = {} | |
| 205 for name in names: | |
| 206 p_dir = name[0:name.rfind('/') + 1] | |
| 207 pd_map[p_dir] = True | |
| 208 return list(pd_map.iterkeys()) | |
| 209 | |
| 210 def JoinWithTestExpectation(self, test_expectations): | |
| 211 """Join layout tests with the test expectation file using test name as key. | |
| 212 | |
| 213 Args: | |
| 214 test_expectations: a test expectations object. | |
| 215 | |
| 216 Returns: | |
| 217 test_info_map contains test name as key and another map as value. The | |
| 218 other map contains test description and the test expectation | |
| 219 information which contains keyword (e.g., 'GPU') as key (we do | |
| 220 not care about values). The map data structure is used since we | |
| 221 have to look up these keywords several times. | |
| 222 """ | |
| 223 test_info_map = {} | |
| 224 for (lt_name, desc) in self.name_map.items(): | |
| 225 test_info_map[lt_name] = {} | |
| 226 test_info_map[lt_name]['desc'] = desc | |
| 227 for (te_name, te_info) in ( | |
| 228 test_expectations.all_test_expectation_info.items()): | |
| 229 if te_name == lt_name or ( | |
| 230 te_name in lt_name and te_name.endswith('/')): | |
| 231 # Only keep the first match when found. | |
| 232 test_info_map[lt_name]['te_info'] = te_info | |
| 233 break | |
| 234 return test_info_map | |
| 235 | |
| 236 @staticmethod | |
| 237 def GetTestDescriptionFromSVN(test_location, | |
| 238 root_path=DEFAULT_LAYOUTTEST_LOCATION): | |
| 239 """Get test description of a layout test from SVN. | |
| 240 | |
| 241 Using urllib2.urlopen(), this method gets the entire HTML and extracts its | |
| 242 test description using |ExtractTestDescription()|. | |
| 243 | |
| 244 Args: | |
| 245 test_location: the location of the layout test. | |
| 246 root_path: the root path of the Webkit SVN directory. | |
| 247 | |
| 248 Returns: | |
| 249 A test description string. | |
| 250 | |
| 251 Raises: | |
| 252 A URLError when the layout test is not available. | |
| 253 """ | |
| 254 if test_location.endswith('.html'): | |
| 255 url = root_path + test_location | |
| 256 resp = urllib2.urlopen(url) | |
| 257 if resp.code == 200: | |
| 258 return LayoutTests.ExtractTestDescription(resp.read()) | |
| 259 raise URLError('Fail to get layout test HTML file from %s.' % url) | |
| OLD | NEW |