Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """A module for TestExpectionsManager. | 6 """A Module for the Webkit layout test test expectation related class.""" |
| 7 | 7 |
| 8 TestExpectaionManager manages data for Webkit layout tests. | 8 import re |
| 9 """ | 9 import urllib2 |
| 10 | 10 |
| 11 import csv | 11 # Default Webkit SVN location for chromium test expectation file. |
| 12 import re | 12 # TODO(imasaki) : support multiple test expectations files. |
| 13 import time | 13 DEFAULT_TEST_EXPECTATION_LOCATION = ( |
| 14 import urllib | 14 'http://svn.webkit.org/repository/webkit/trunk/' |
| 15 import pysvn | 15 'LayoutTests/platform/chromium/test_expectations.txt') |
| 16 | 16 |
| 17 from csv_utils import CsvUtils | 17 # The following is from test expectation syntax. The detail can be found in |
| 18 from layout_test_test_case import LayoutTestCaseManager | 18 # http://www.chromium.org/developers/testing/ |
| 19 from test_case_patterns import TestCasePatterns | 19 # webkit-layout-tests#TOC-Test-Expectations |
| 20 # <decision> ::== [SKIP] [WONTFIX] [SLOW] | |
| 21 DECISION_NAMES = ['SKIP', 'WONTFIX', 'SLOW'] | |
| 22 # <platform> ::== [GPU] [CPU] [WIN] [LINUX] [MAC] | |
| 23 PLATFORM_NAMES = ['GPU', 'CPU', 'WIN', 'LINUX', 'MAC'] | |
| 24 # <config> ::== RELEASE | DEBUG | |
| 25 CONFIG_NAMES = ['RELEASE', 'DEBUG'] | |
| 26 # <EXPECTATION_NAMES> ::== \ | |
| 27 # [FAIL] [PASS] [CRASH] [TIMEOUT] [IMAGE] [TEXT] [IMAGE+TEXT] | |
| 28 EXPECTATION_NAMES = ['FAIL', 'PASS', 'CRASH', | |
| 29 'TIMEOUT', 'IMAGE', 'TEXT', | |
| 30 'IMAGE+TEXT'] | |
| 20 | 31 |
| 21 | 32 |
| 22 class TestExpectationsManager(object): | 33 class TestExpectations(object): |
| 23 """This class manages test expectations data for Webkit layout tests. | 34 """A class to model the content of test expectation file for analysis. |
| 24 | 35 |
| 25 The detail of the test expectation file can be found in | 36 The raw test expectation file can be found in |
| 26 http://trac.webkit.org/wiki/TestExpectations. | 37 |DEFAULT_TEST_EXPECTATION_LOCATION|. |
| 38 It is necessary to parse this file and store meaningful information for | |
| 39 the analysis (joining with existing layout tests using a test name). | |
| 40 Instance variable |all_test_expectation_info| is used. | |
| 41 A test name such as 'media/video-source-type.html' is used for the key | |
| 42 to store information. However, a test name can appear multiple times in | |
| 43 the test expectation file. So, the map should keep all the occurrences | |
| 44 information. For example, the current test expectation file has the following | |
| 45 two entries: | |
| 46 BUGWK58587 LINUX DEBUG GPU : media/video-zoom.html = IMAGE | |
| 47 BUGCR86714 MAC GPU : media/video-zoom.html = CRASH IMAGE | |
| 48 In this case, all_test_expectation_info['media/video-zoom.html'] will have | |
| 49 a list with two elements, each of which is the map of the test expectation | |
| 50 information. In other words,this is the generated map after parsing both | |
| 51 lines. | |
| 52 {'media/video-zoom.html': [{'LINUX':True, 'DEBUG':True ....}, | |
|
Ami GONE FROM CHROMIUM
2011/08/20 04:16:50
Are the values always True/False? If so ISTM you'
imasaki1
2011/08/20 08:25:36
This map also contains 'Bugs' which holds a list o
| |
| 53 {'MAC':True, 'GPU':True ....}] | |
| 54 """ | |
| 27 | 55 |
| 28 This class does the following: | 56 @staticmethod |
| 29 (1) get test expectation file from WebKit subversion source | 57 def GetAllDataNames(): |
| 30 repository using pysvn. | 58 """get all names relating to test expectation data. |
|
Ami GONE FROM CHROMIUM
2011/08/20 04:16:50
All comments should start with capital letter (ple
imasaki1
2011/08/20 08:25:36
Done.
| |
| 31 (2) parse the file and generate CSV entries. | 59 |
| 32 (3) create hyperlinks where appropriate (e.g., test case name). | 60 Returns: |
| 61 a list of all names (keywords) used in the test expectation files. | |
| 33 """ | 62 """ |
| 63 return DECISION_NAMES + PLATFORM_NAMES + CONFIG_NAMES + EXPECTATION_NAMES | |
| 34 | 64 |
| 35 DEFAULT_TEST_EXPECTATION_DIR = ( | 65 def __init__(self, url=DEFAULT_TEST_EXPECTATION_LOCATION): |
| 36 'http://svn.webkit.org/repository/webkit/trunk/' | 66 """read the test expectation file from the specified URL and parse it. |
| 37 'LayoutTests/platform/chromium/') | |
| 38 | 67 |
| 39 DEFAULT_TEST_EXPECTATION_LOCATION = ( | 68 All the parsed information is stored in the instance variable |
| 40 'http://svn.webkit.org/repository/webkit/trunk/' | 69 |all_test_expectation_info|, which is the map where its key is test name |
| 41 'LayoutTests/platform/chromium/test_expectations.txt') | 70 and its value is a list of the test expectation entries information map |
| 71 such as 'SKIP' or 'Comments' or 'Bugs'. | |
| 42 | 72 |
| 43 CHROME_BUG_URL = 'http://code.google.com/p/chromium/issues/detail?id=' | 73 Args: |
| 74 url: A URL string for the test expectation file. | |
| 75 """ | |
| 76 self.all_test_expectation_info = {} | |
| 77 resp = urllib2.urlopen(url) | |
| 78 if resp.code != 200: | |
| 79 raise NameError('Test expectation file does not exist in %s' % source) | |
| 80 # Start parsing each line. | |
| 81 lines = resp.read().split('\n') | |
| 82 comments = '' | |
| 83 for line in lines: | |
| 84 if line.startswith('//'): | |
| 85 # Comments can be Multiple lines. | |
| 86 comments += line.replace('//', '') | |
| 87 elif not line: | |
| 88 comments = '' | |
| 89 else: | |
| 90 test_expectation_info = self.ParseLine(line, comments) | |
| 91 testname = TestExpectations.ExtractTestName(line) | |
| 92 if not testname in self.all_test_expectation_info: | |
| 93 self.all_test_expectation_info[testname] = [] | |
| 94 # This is a list for multiple entries. | |
| 95 self.all_test_expectation_info[testname].append(test_expectation_info) | |
| 44 | 96 |
| 45 WEBKIT_BUG_URL = 'https://bugs.webkit.org/show_bug.cgi?id=' | 97 @staticmethod |
| 98 def ExtractTestName(line): | |
| 99 """extract either a test name or a directory name from each line. | |
| 46 | 100 |
| 47 FLAKINESS_DASHBOARD_LINK = ( | 101 Args: |
| 48 'http://test-results.appspot.com/dashboards/' | 102 line: each line in the test expectation file. |
| 49 'flakiness_dashboard.html#tests=') | |
| 50 | 103 |
| 51 TEST_CASE_PATTERNS = TestCasePatterns() | 104 Returns: |
| 105 a test name or directory name string. Returns '' if no matching. | |
| 106 """ | |
| 107 # First try to find test case where ending with .html. | |
| 108 matches = re.search(r':\s+(\S+.html)', line) | |
| 109 # Next try to find directory | |
| 110 if matches: | |
| 111 return matches.group(1) | |
| 112 matches = re.search(r':\s+(\S+)', line) | |
| 113 if matches: | |
| 114 return matches.group(1) | |
| 115 else: | |
| 116 return '' | |
|
Ami GONE FROM CHROMIUM
2011/08/20 04:16:50
raise?
imasaki1
2011/08/20 08:25:36
Done.
Ami GONE FROM CHROMIUM
2011/08/20 17:52:23
I meant raise a sensible exception. Doesn't this
imasaki1
2011/08/20 18:07:38
Done. Also added unit test for this case.
| |
| 52 | 117 |
| 53 OTHER_FIELD_NAMES = ['TestCase', 'Media', 'Flaky', 'WebKitD', 'Bug', | 118 @staticmethod |
| 54 'Test'] | 119 def ParseLine(line, comments): |
|
Ami GONE FROM CHROMIUM
2011/08/20 04:16:50
|comments| should be |comment_prefix| or something
imasaki1
2011/08/20 08:25:36
Done.
| |
| 120 """Parse each line in test expectation and update test expectation info. | |
| 55 | 121 |
| 56 # The following is from test expectation syntax. | 122 This looks for each name in |GetAllDataNames()| in line and store in the |
| 57 # BUG[0-9]+ [SKIP] [WONTFIX] [SLOW] | 123 map and returns it. Also, store comments and bugs in the map. |
| 58 DECISION_COLUMN_NAMES = ['SKIP', 'SLOW'] | |
| 59 # <platform> ::== [GPU] [CPU] [WIN] [LINUX] [MAC] | |
| 60 PLATFORM_COLUMN_NAMES = ['GPU', 'CPU', 'WIN', 'LINUX', 'MAC'] | |
| 61 #<config> ::== RELEASE | DEBUG | |
| 62 CONFIG_COLUMN_NAMES = ['RELEASE', 'DEBUG'] | |
| 63 # <EXPECTATION_COLUMN_NAMES> ::== \ | |
| 64 # [FAIL] [PASS] [CRASH] [TIMEOUT] [IMAGE] [TEXT] [IMAGE+TEXT] | |
| 65 EXPECTATION_COLUMN_NAMES = ['FAIL', 'PASS', 'CRASH', | |
| 66 'TIMEOUT', 'IMAGE', 'TEXT', | |
| 67 'IMAGE+TEXT'] | |
| 68 | 124 |
| 69 # These are coming from metadata in comments in the test expectation file. | 125 Args: |
| 70 COMMENT_COLUMN_NAMES = ['UNIMPLEMENTED', 'KNOWNISSUE', 'TESTISSUE', | 126 line: each line in the test expectation. For example, |
| 71 'WONTFIX'] | 127 "BUGCR86714 MAC GPU : media/video-zoom.html = CRASH IMAGE" |
| 128 comments: comments in the test expectation. Usually, it exist just before | |
| 129 the entry. | |
| 72 | 130 |
| 73 RAW_COMMENT_COLUMN_NAME = 'COMMENTS' | 131 Returns: |
| 74 | 132 a map containing the test expectation entries an and comments and bugs. |
| 75 def __init__(self): | 133 """ |
| 76 """Initialize the test cases.""" | 134 test_expectation_info = {} |
| 77 self.testcases = [] | 135 for name in TestExpectations.GetAllDataNames(): |
| 78 | 136 if not line: |
|
Ami GONE FROM CHROMIUM
2011/08/20 04:16:50
Isn't this guarded against before calling this fun
imasaki1
2011/08/20 08:25:36
Deleted
| |
| 79 def get_column_indexes(self, column_names): | 137 # Clear the comments if there is space (this is typical the case). |
|
Ami GONE FROM CHROMIUM
2011/08/20 04:16:50
s/typical/typically/
imasaki1
2011/08/20 08:25:36
Done.
| |
| 80 """Get column indexes for given column names. | 138 comments = '' |
| 81 | 139 if name in line: |
| 82 Args: | 140 test_expectation_info[name] = True |
|
Ami GONE FROM CHROMIUM
2011/08/20 04:16:50
This is going to incorrectly count names appearing
imasaki1
2011/08/20 08:25:36
Comments are dealt in __init__ function. So, the l
Ami GONE FROM CHROMIUM
2011/08/20 17:52:23
I was referring to line-ending comments, not multi
imasaki1
2011/08/20 18:07:38
Modified... Added unit test for this.
| |
| 83 column_names: a list of column names. | 141 # Store comments. |
| 84 | 142 test_expectation_info['Comments'] = comments |
| 85 Returns: | 143 # Store bugs. |
| 86 a list of indexes for the given column names. | 144 bugs = re.findall(r'BUG\w+', line) |
| 87 """ | 145 if len(bugs) > 0: |
| 88 all_field_names = self.get_all_column_names(True, True) | 146 test_expectation_info['Bugs'] = bugs |
| 89 return [all_field_names.index( | 147 return test_expectation_info |
| 90 field_name) for field_name in column_names] | |
| 91 | |
| 92 def get_all_column_names(self, include_other_fields, include_comments): | |
| 93 """Get all column names that are used in CSV file. | |
| 94 | |
| 95 Args: | |
| 96 include_other_fields: a boolean that indicates the result should | |
| 97 include other column names (defined in OTHER_COLUMNS_NAMES). | |
| 98 include_comments: a boolean that indicates the result should | |
| 99 include comment-related names. | |
| 100 | |
| 101 Returns: | |
| 102 a list that contains column names. | |
| 103 """ | |
| 104 return_list = ( | |
| 105 self.DECISION_COLUMN_NAMES + self.PLATFORM_COLUMN_NAMES + | |
| 106 self.CONFIG_COLUMN_NAMES + self.EXPECTATION_COLUMN_NAMES) | |
| 107 if include_other_fields: | |
| 108 return_list = self.OTHER_FIELD_NAMES + return_list | |
| 109 if include_comments: | |
| 110 return_list.extend(self.COMMENT_COLUMN_NAMES) | |
| 111 return_list.append(self.RAW_COMMENT_COLUMN_NAME) | |
| 112 return return_list | |
| 113 | |
| 114 def get_test_case_element(self, test_case, column_names): | |
| 115 """Get test case elements. | |
| 116 | |
| 117 A test case is a collection of test case elements. | |
| 118 | |
| 119 Args: | |
| 120 test_case: test case data that contains all test case elements. | |
| 121 column_names: column names for specifying test case elements. | |
| 122 | |
| 123 Returns: | |
| 124 A list of test case elements for the given column names. | |
| 125 """ | |
| 126 field_indexes = self.get_column_indexes(column_names) | |
| 127 test_case = self.get_test_case_by_name(test_case) | |
| 128 if test_case is None: | |
| 129 return [] | |
| 130 return [test_case[fid] for fid in field_indexes] | |
| 131 | |
| 132 def get_test_case_by_name(self, target): | |
| 133 """Get test case object by test case name. | |
| 134 | |
| 135 Args: | |
| 136 target: test case name. | |
| 137 | |
| 138 Returns: | |
| 139 A test case with the test case name or None if the test case | |
| 140 cannot be found. | |
| 141 """ | |
| 142 for testcase in self.testcases: | |
| 143 # Test case name is stored in the first column. | |
| 144 if testcase[0] == target: | |
| 145 return testcase | |
| 146 return None | |
| 147 | |
| 148 def get_all_test_case_names(self): | |
| 149 """Get all test case names. | |
| 150 | |
| 151 Returns: | |
| 152 A list of test case names. | |
| 153 """ | |
| 154 return [testcase[0] for testcase in self.testcases] | |
| 155 | |
| 156 def generate_link_for_bug(self, bug): | |
| 157 """Generate link for a bug. | |
| 158 | |
| 159 Parse the bug description accordingly. The bug description can be like | |
| 160 the following: BUGWK1234, BUGCR1234, BUGFOO. | |
| 161 | |
| 162 Args: | |
| 163 bug: A string bug description | |
| 164 | |
| 165 Returns: | |
| 166 A string that represents a bug link. Returns an empty | |
| 167 string if match is not found. | |
| 168 """ | |
| 169 pattern_for_webkit_bug = 'BUGWK(\d+)' | |
| 170 match = re.search(pattern_for_webkit_bug, bug) | |
| 171 if match is not None: | |
| 172 return self.WEBKIT_BUG_URL + match.group(1) | |
| 173 pattern_for_chrome_bug = 'BUGCR(\d+)' | |
| 174 match = re.search(pattern_for_chrome_bug, bug) | |
| 175 if match is not None: | |
| 176 return self.CHROME_BUG_URL + match.group(1) | |
| 177 pattern_for_other_bug = 'BUG(\S+)' | |
| 178 match = re.search(pattern_for_other_bug, bug) | |
| 179 if match is not None: | |
| 180 return 'mailto:%s@chromium.org' % match.group(1).lower() | |
| 181 return '' | |
| 182 | |
| 183 def generate_link_for_dashboard(self, testcase_name, webkit): | |
| 184 """Generate link for flakiness dashboard. | |
| 185 | |
| 186 Args: | |
| 187 testcase_name: a string test case name. | |
| 188 webkit: a boolean indicating whether to use the webkit dashboard. | |
| 189 | |
| 190 Returns: | |
| 191 A string link to the flakiness dashboard. | |
| 192 """ | |
| 193 url = self.FLAKINESS_DASHBOARD_LINK + urllib.quote(testcase_name) | |
| 194 if webkit: | |
| 195 url += '&master=webkit.org' | |
| 196 return url | |
| 197 | |
| 198 def parse_line(self, line, previous_comment_info_list, test_case_patterns, | |
| 199 media_test_cases_only, writer): | |
| 200 """Parse each line in test_expectations.txt. | |
| 201 | |
| 202 The format of each line is as follows: | |
| 203 BUG[0-9]+ [SKIP] [WONTFIX] [SLOW] [<platform>] [<config>] | |
| 204 : <url> = <EXPCTATION> | |
| 205 The example is: | |
| 206 BUG123 BUG345 MAC : media/hoge.html | |
| 207 WONTFIX SKIP BUG19635 : media/restore-from-page-cache.html | |
| 208 = TIMEOUT | |
| 209 | |
| 210 Args: | |
| 211 line: a text for each line in the test expectation file. | |
| 212 previous_comment_info_list: a list of comments in previous lines. | |
| 213 test_case_patterns: a string for test case pattern. | |
| 214 media_test_cases_only: a boolean for media test case only. | |
| 215 writer: writer for intermediate results. | |
| 216 """ | |
| 217 bugs = re.findall(r'BUG\w+', line) | |
| 218 testcases = re.search(r':\s+(\S+.[html|xml|svg|js])', line) | |
| 219 if testcases is not None: | |
| 220 testcase = testcases.group(1) | |
| 221 data = [] | |
| 222 data.append(testcase) | |
| 223 | |
| 224 if LayoutTestCaseManager.check_test_case_matches_pattern( | |
| 225 testcase, test_case_patterns): | |
| 226 data.append('Y') | |
| 227 media_test_case = True | |
| 228 else: | |
| 229 data.append('N') | |
| 230 media_test_case = False | |
| 231 | |
| 232 dlink = self.generate_link_for_dashboard(testcase, False) | |
| 233 # Generate dashboard link. | |
| 234 data.append( | |
| 235 CsvUtils.generate_hyperlink_in_csv(dlink, 'Y', '')) | |
| 236 dlink = self.generate_link_for_dashboard(testcase, True) | |
| 237 # Generate dashboard link. | |
| 238 data.append( | |
| 239 CsvUtils.generate_hyperlink_in_csv(dlink, 'Y', '')) | |
| 240 for bug in bugs: | |
| 241 blink = self.generate_link_for_bug(bug) | |
| 242 data.append( | |
| 243 CsvUtils.generate_hyperlink_in_csv(blink, bug, '')) | |
| 244 # Fill the gap for test case with less bug/test. | |
| 245 for i in range(2 - len(bugs)): | |
| 246 data.append('') | |
| 247 | |
| 248 # Fill all data with 'N' (default). | |
| 249 for field_name in self.get_all_column_names(False, False): | |
| 250 if field_name in line: | |
| 251 data.append('Y') | |
| 252 else: | |
| 253 data.append('N') | |
| 254 # Comments will be accumulated in previous comments. | |
| 255 # This is for multiple-line comment. | |
| 256 for previous_comment_info in previous_comment_info_list: | |
| 257 data.append(previous_comment_info) | |
| 258 # Write only media related test case if specified. | |
| 259 if (media_test_case and media_test_cases_only | |
| 260 or (not media_test_cases_only)): | |
| 261 writer.writerow(data) | |
| 262 self.testcases.append(data) | |
| 263 | |
| 264 def process_comments(self, comments): | |
| 265 """Process comments in the test expectations file. | |
| 266 | |
| 267 Comments may contain special keywords such as UNIMPLEMENTED, | |
| 268 KNOWNISSUE, TESTISSUE (in TestExpectationsManager.COMMENT_COLUMN_NAMES) | |
| 269 and may be multiple lines. | |
| 270 | |
| 271 Args: | |
| 272 comments: A raw comment from the test expectation file. | |
| 273 It is above test case name. | |
| 274 | |
| 275 Returns: | |
| 276 A list of 'Y' or 'N' whether the comments contain | |
| 277 each column name in TestExpectationsManager.COMMENT_COLUMN_NAMES | |
| 278 in the comments. | |
| 279 """ | |
| 280 return_list = [] | |
| 281 for ccn in TestExpectationsManager.COMMENT_COLUMN_NAMES: | |
| 282 if ccn in comments: | |
| 283 return_list.append('Y') | |
| 284 else: | |
| 285 return_list.append('N') | |
| 286 return_list.append(comments) | |
| 287 return return_list | |
| 288 | |
| 289 def get_and_save_content(self, location, output): | |
| 290 """Simply get test expectation from the specified location and save it. | |
| 291 | |
| 292 Args: | |
| 293 location: SVN location of the test expectation file. | |
| 294 output: an output file path including file name. | |
| 295 """ | |
| 296 client = pysvn.Client() | |
| 297 file_object = open(output, 'w') | |
| 298 file_object.write(client.cat(location)) | |
| 299 file_object.close() | |
| 300 | |
| 301 def get_and_save_content_media_only(self, location, output): | |
| 302 """Simply get test expectation from the specified location. | |
| 303 | |
| 304 It also saves it (media only). | |
| 305 | |
| 306 Args: | |
| 307 location: SVN location of the test expectation file. | |
| 308 output: an output file path including file name. | |
| 309 """ | |
| 310 file_object = file(location, 'r') | |
| 311 text_list = list(file_object) | |
| 312 output_text = '' | |
| 313 for txt in text_list: | |
| 314 if txt.startswith('//'): | |
| 315 output_text += txt | |
| 316 else: | |
| 317 for pattern in ( | |
| 318 self.TEST_CASE_PATTERNS.get_test_case_pattern( | |
| 319 'media')): | |
| 320 if re.search(pattern, txt) is not None: | |
| 321 output_text += txt | |
| 322 break | |
| 323 file_object.close() | |
| 324 # Save output. | |
| 325 file_object = open(output, 'w') | |
| 326 file_object.write(output_text) | |
| 327 file_object.close() | |
| 328 | |
| 329 def get_and_parse_content(self, location, output, media_test_case_only): | |
| 330 """Get test_expectations.txt and parse the content. | |
| 331 | |
| 332 The comments are parsed as well since it contains keywords. | |
| 333 | |
| 334 Args: | |
| 335 location: SVN location of the test expectation file. | |
| 336 output: an output file path including file name. | |
| 337 media_test_case_only: A boolean indicating whether media | |
| 338 test cases should only be processed. | |
| 339 """ | |
| 340 if location.startswith('http'): | |
| 341 # Get from SVN. | |
| 342 client = pysvn.Client() | |
| 343 # Check out the current version of the pysvn project. | |
| 344 txt = client.cat(location) | |
| 345 else: | |
| 346 if location.endswith('.csv'): | |
| 347 # No parsing in the case of CSV file. | |
| 348 # Direct reading. | |
| 349 file_object = file(location, 'r') | |
| 350 self.testcases = list(csv.reader(file_object)) | |
| 351 file_object.close() | |
| 352 return | |
| 353 file_object = open(location, 'r') | |
| 354 txt = file_object.read() | |
| 355 file_object.close() | |
| 356 | |
| 357 file_object = file(output, 'wb') | |
| 358 writer = csv.writer(file_object) | |
| 359 writer.writerow(self.get_all_column_names(True, True)) | |
| 360 lines = txt.split('\n') | |
| 361 process = False | |
| 362 previous_comment = '' | |
| 363 previous_comment_list = [] | |
| 364 for line in lines: | |
| 365 if line.isspace() or str is '': | |
| 366 continue | |
| 367 line.strip() | |
| 368 if line.startswith('//'): | |
| 369 # There are comments. | |
| 370 line = line.replace('//', '') | |
| 371 if process is True: | |
| 372 previous_comment = line | |
| 373 else: | |
| 374 previous_comment = previous_comment + '\n' + line | |
| 375 previous_comment_list = self.process_comments(previous_comment) | |
| 376 process = False | |
| 377 else: | |
| 378 self.parse_line( | |
| 379 line, previous_comment_list, | |
| 380 self.TEST_CASE_PATTERNS.get_test_case_pattern('media'), | |
| 381 media_test_case_only, writer) | |
| 382 process = True | |
| 383 file_object.close() | |
| 384 | |
| 385 def get_te_diff_between_times(self, te_location, start, end, patterns, | |
| 386 change, checkchange): | |
| 387 """Get test expectation diff output for a given time period. | |
| 388 | |
| 389 Args: | |
| 390 te_location: SVN location of the test expectation file. | |
| 391 start: a date object for the start of the time period. | |
| 392 end: a date object for the end of the time period. | |
| 393 patterns: test case name patterns of the test cases that | |
| 394 we are interested in. This could be test case name | |
| 395 as it is in the case of exact matching. | |
| 396 change: the number of change in occurrence of test case in | |
| 397 test expectation. | |
| 398 checkchange: a boolean to indicate if we want to check the | |
| 399 change in occurrence as specified in |change| argument. | |
| 400 | |
| 401 Returns: | |
| 402 a list of tuples (old_revision, new_revision, | |
| 403 diff_line, author, date, commit_message) that matches | |
| 404 the condition. | |
| 405 """ | |
| 406 client = pysvn.Client() | |
| 407 client.checkout(te_location, 'tmp', recurse=False) | |
| 408 logs = client.log('tmp/test_expectations.txt', | |
| 409 revision_start=pysvn.Revision( | |
| 410 pysvn.opt_revision_kind.date, start), | |
| 411 revision_end=pysvn.Revision( | |
| 412 pysvn.opt_revision_kind.date, end)) | |
| 413 result_list = [] | |
| 414 for i in xrange(len(logs) - 1): | |
| 415 # PySVN.log returns logs in reverse chronological order. | |
| 416 new_rev = logs[i].revision.number | |
| 417 old_rev = logs[i + 1].revision.number | |
| 418 # Getting information about new revision. | |
| 419 author = logs[i].author | |
| 420 date = logs[i].date | |
| 421 message = logs[i].message | |
| 422 text = client.diff('/tmp', 'tmp/test_expectations.txt', | |
| 423 revision1=pysvn.Revision( | |
| 424 pysvn.opt_revision_kind.number, old_rev), | |
| 425 revision2=pysvn.Revision( | |
| 426 pysvn.opt_revision_kind.number, new_rev)) | |
| 427 lines = text.split('\n') | |
| 428 for line in lines: | |
| 429 for pattern in patterns: | |
| 430 matches = re.findall(pattern, line) | |
| 431 if matches: | |
| 432 if checkchange: | |
| 433 if ((line[0] == '+' and change > 0) or | |
| 434 (line[0] == '-' and change < 0)): | |
| 435 result_list.append((old_rev, new_rev, line, | |
| 436 author, date, message)) | |
| 437 else: | |
| 438 if line[0] == '+' or line[0] == '-': | |
| 439 result_list.append((old_rev, new_rev, line, | |
| 440 author, date, message)) | |
| 441 return result_list | |
| OLD | NEW |