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 """A Module for LayoutTests. | |
|
dennis_jeffrey
2011/08/25 00:36:46
We can see this from the file name; is there any o
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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, | |
|
dennis_jeffrey
2011/08/25 00:36:46
'find' --> 'finding the'
imasaki1
2011/08/25 23:57:03
Done.
| |
| 26 # this script tries to find test description using sentences | |
|
dennis_jeffrey
2011/08/25 00:36:46
'find test' --> 'find the test'
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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 | |
|
dennis_jeffrey
2011/08/25 00:36:46
Remove this blank line; the comment above is assoc
imasaki1
2011/08/25 23:57:03
Done.
| |
| 30 KEYWORDS_FOR_TEST_DESCRIPTION = ( | |
| 31 ['This tests', 'This test', 'Test that', 'Tests that', 'Test ']) | |
|
dennis_jeffrey
2011/08/25 00:36:46
I think this list can be shortened.
'This test' i
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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']) | |
|
dennis_jeffrey
2011/08/25 00:36:46
Can fit on the previous line?
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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 | |
|
dennis_jeffrey
2011/08/25 00:36:46
Add period at end of line
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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. | |
|
dennis_jeffrey
2011/08/25 00:36:46
Describe what happens when this parameter is None
imasaki1
2011/08/25 23:57:03
Done.
| |
| 60 """ | |
| 61 if csv_file_path: | |
| 62 filter_names = LayoutTests.GetLayoutTestNamesFromCSV(csv_file_path) | |
| 63 else: | |
| 64 filter_names = [] | |
|
dennis_jeffrey
2011/08/25 00:36:46
for the above 4 lines:
filter_names = []
if csv_f
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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) | |
|
dennis_jeffrey
2011/08/25 00:36:46
indent this line by 5 fewer spaces.
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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): | |
|
dennis_jeffrey
2011/08/25 00:36:46
(This example falls into rule 1):
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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> | |
|
dennis_jeffrey
2011/08/25 00:36:46
Indent lines 92-96 a bit since this is an example
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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 | |
|
dennis_jeffrey
2011/08/25 00:36:46
'contains' --> 'that contains'
imasaki1
2011/08/25 23:57:03
Done.
| |
| 108 # 'test that' and surrounded by p tag. | |
| 109 # We found this is the most common case. | |
|
dennis_jeffrey
2011/08/25 00:36:46
'This is the most common case'.
imasaki1
2011/08/25 23:57:03
Done.
| |
| 110 for keyword in KEYWORDS_FOR_TEST_DESCRIPTION: | |
| 111 # Try to find <p> and </p>. | |
|
dennis_jeffrey
2011/08/25 00:36:46
Only indent by 2 spaces, not 4, within this functi
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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. | |
|
dennis_jeffrey
2011/08/25 00:36:46
'THML' --> 'HTML'
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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' | |
|
dennis_jeffrey
2011/08/25 00:36:46
Remove this line.
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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 | |
|
dennis_jeffrey
2011/08/25 00:36:46
'parents directory' --> 'parent directories'
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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 for de-dupe. | |
| 160 """ | |
| 161 client = pysvn.Client() | |
| 162 # Get directory structure in the Webkit SVN. | |
| 163 if parent_location_list is None: | |
| 164 # Try to get all the layout tests by enabling recursion option. | |
| 165 parent_location_list = ['/\S+\.html$'] | |
| 166 recursion = True | |
| 167 else: | |
| 168 recursion = False | |
| 169 name_map = {} | |
| 170 for parent_location in parent_location_list: | |
| 171 if parent_location.endswith('/'): | |
| 172 file_list = client.list(layouttest_root_path + parent_location, | |
| 173 recurse=recursion) | |
| 174 for file_name in file_list: | |
| 175 if sys.stdout.isatty(): | |
| 176 default_encoding = sys.stdout.encoding | |
| 177 else: | |
| 178 default_encoding = locale.getpreferredencoding() | |
| 179 file_name = file_name[0].repos_path.encode(default_encoding) | |
| 180 # Remove the word '/truck/LayoutTests'. | |
| 181 file_name = file_name.replace('/trunk/LayoutTests/', '') | |
| 182 if file_name.endswith('.html'): | |
| 183 name_map[file_name] = True | |
| 184 return name_map | |
| 185 | |
| 186 @staticmethod | |
| 187 def GetLayoutTestNamesFromCSV(csv_file_path): | |
| 188 """Get layout test names from CSV file. | |
| 189 | |
| 190 Args: | |
| 191 csv_file_path: the path for the CSV file containing test names (including | |
| 192 regular expression patterns). The CSV file content has one column and | |
| 193 each row contains a test name. | |
| 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]) | |
|
dennis_jeffrey
2011/08/25 00:36:46
For the above 3 lines:
names = [row[0] for row in
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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 | |
|
dennis_jeffrey
2011/08/25 00:36:46
remove the space before the ':'
dennis_jeffrey
2011/08/25 00:36:46
'test name also' -->
'test names also'
imasaki1
2011/08/25 23:57:03
Done.
imasaki1
2011/08/25 23:57:03
Done.
| |
| 209 well (e.g., 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): | |
|
dennis_jeffrey
2011/08/25 00:36:46
'test_expections' -->
'test_expectations'
imasaki1
2011/08/25 23:57:03
Done.
| |
| 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 other map contains test description and the test expectation | |
| 226 information which contains keyword (e.g., 'GPU') as key (we do | |
| 227 not care about values). | |
|
dennis_jeffrey
2011/08/25 00:36:46
If you don't care about values, why not use a list
imasaki1
2011/08/25 23:57:03
But, I have to look the value up several times so
| |
| 228 """ | |
| 229 test_info_map = {} | |
| 230 for (lt_name, desc) in self.name_map.items(): | |
| 231 test_info_map[lt_name] = {} | |
| 232 test_info_map[lt_name]['desc'] = desc | |
| 233 for (te_name, te_info) in ( | |
| 234 test_expections.all_test_expectation_info.items()): | |
| 235 if te_name == lt_name or ( | |
| 236 te_name in lt_name and te_name.endswith('/')): | |
| 237 # Only 1 match (if exist) | |
|
dennis_jeffrey
2011/08/25 00:36:46
# Only keep the first match when found.
imasaki1
2011/08/25 23:57:03
Done.
| |
| 238 test_info_map[lt_name]['te_info'] = te_info | |
| 239 break | |
| 240 return test_info_map | |
| 241 | |
| 242 @staticmethod | |
| 243 def GetTestDescriptionFromSVN(test_location, | |
| 244 layouttest_root_path=DEFAULT_LAYOUTTEST_LOCATION): | |
|
dennis_jeffrey
2011/08/25 00:36:46
indent this line so it lines up underneath the fir
imasaki1
2011/08/25 23:57:03
Done.
| |
| 245 """Get test description of a layout test from SVN. | |
| 246 | |
| 247 Using urllib2.urlopen(), this method gets the entire HTML and extract its | |
|
dennis_jeffrey
2011/08/25 00:36:46
'extract' --> 'extracts'
imasaki1
2011/08/25 23:57:03
Done.
| |
| 248 test description using |ExtractTestDescription()|. | |
| 249 | |
| 250 Args: | |
| 251 test_location: the location of the layout test | |
|
dennis_jeffrey
2011/08/25 00:36:46
add period at end of line
imasaki1
2011/08/25 23:57:03
Done.
| |
| 252 layouttest_root_path: the root path of the Webkit SVN directory. | |
| 253 | |
| 254 Returns: | |
| 255 A test description string. | |
| 256 | |
| 257 raises: | |
|
dennis_jeffrey
2011/08/25 00:36:46
'raises:' --> 'Raises:'
imasaki1
2011/08/25 23:57:03
Done.
| |
| 258 A URLError when the layout test is not available. | |
| 259 """ | |
| 260 if test_location.endswith('.html'): | |
| 261 resp = urllib2.urlopen(layouttest_root_path + test_location) | |
| 262 if resp.code == 200: | |
| 263 return LayoutTests.ExtractTestDescription(resp.read()) | |
| 264 raise URLError | |
|
dennis_jeffrey
2011/08/25 00:36:46
Maybe add an informative message to go along with
imasaki1
2011/08/25 23:57:03
Done.
| |
| OLD | NEW |