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