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 directories. This is |
| 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 |