1# Copyright (c) 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""A module for the history of the test expectation file.""" 6 7from datetime import datetime 8from datetime import timedelta 9 10import os 11import re 12import sys 13import tempfile 14import time 15import pysvn 16 17TEST_EXPECTATIONS_ROOT = 'http://src.chromium.org/blink/trunk/' 18# A map from earliest revision to path. 19# TODO(imasaki): support multiple test expectation files. 20TEST_EXPECTATIONS_LOCATIONS = { 21 148348: 'LayoutTests/TestExpectations', 22 119317: 'LayoutTests/platform/chromium/TestExpectations', 23 0: 'LayoutTests/platform/chromium/test_expectations.txt'} 24TEST_EXPECTATIONS_DEFAULT_PATH = ( 25 TEST_EXPECTATIONS_ROOT + TEST_EXPECTATIONS_LOCATIONS[148348]) 26 27class TestExpectationsHistory(object): 28 """A class to represent history of the test expectation file. 29 30 The history is obtained by calling PySVN.log()/diff() APIs. 31 32 TODO(imasaki): Add more functionalities here like getting some statistics 33 about the test expectation file. 34 """ 35 36 @staticmethod 37 def GetTestExpectationsPathForRevision(revision): 38 for i in sorted(TEST_EXPECTATIONS_LOCATIONS.keys(), reverse=True): 39 if revision >= i: 40 return TEST_EXPECTATIONS_ROOT + TEST_EXPECTATIONS_LOCATIONS[i] 41 42 @staticmethod 43 def GetDiffBetweenTimes(start, end, testname_list, 44 te_location=TEST_EXPECTATIONS_DEFAULT_PATH): 45 """Get difference between time period for the specified test names. 46 47 Given the time period, this method first gets the revision number. Then, 48 it gets the diff for each revision. Finally, it keeps the diff relating to 49 the test names and returns them along with other information about 50 revision. 51 52 Args: 53 start: A timestamp specifying start of the time period to be 54 looked at. 55 end: A timestamp object specifying end of the time period to be 56 looked at. 57 testname_list: A list of strings representing test names of interest. 58 te_location: A location of the test expectation file. 59 60 Returns: 61 A list of tuples (old_rev, new_rev, author, date, message, lines). The 62 |lines| contains the diff of the tests of interest. 63 """ 64 temp_directory = tempfile.mkdtemp() 65 test_expectations_path = os.path.join(temp_directory, 'TestExpectations') 66 # Get directory name which is necesary to call PySVN.checkout(). 67 te_location_dir = te_location[0:te_location.rindex('/')] 68 client = pysvn.Client() 69 client.checkout(te_location_dir, temp_directory, recurse=False) 70 # PySVN.log() (http://pysvn.tigris.org/docs/pysvn_prog_ref.html 71 # #pysvn_client_log) returns the log messages (including revision 72 # number in chronological order). 73 logs = client.log(test_expectations_path, 74 revision_start=pysvn.Revision( 75 pysvn.opt_revision_kind.date, start), 76 revision_end=pysvn.Revision( 77 pysvn.opt_revision_kind.date, end)) 78 result_list = [] 79 gobackdays = 1 80 while gobackdays < sys.maxint: 81 goback_start = time.mktime( 82 (datetime.fromtimestamp(start) - ( 83 timedelta(days=gobackdays))).timetuple()) 84 logs_before_time_period = ( 85 client.log(test_expectations_path, 86 revision_start=pysvn.Revision( 87 pysvn.opt_revision_kind.date, goback_start), 88 revision_end=pysvn.Revision( 89 pysvn.opt_revision_kind.date, start))) 90 if logs_before_time_period: 91 # Prepend at the beginning of logs. 92 logs.insert(0, logs_before_time_period[len(logs_before_time_period)-1]) 93 break 94 gobackdays *= 2 95 96 for i in xrange(len(logs) - 1): 97 old_rev = logs[i].revision.number 98 new_rev = logs[i + 1].revision.number 99 # Parsing the actual diff. 100 101 new_path = TestExpectationsHistory.GetTestExpectationsPathForRevision( 102 new_rev); 103 old_path = TestExpectationsHistory.GetTestExpectationsPathForRevision( 104 old_rev); 105 106 text = client.diff(temp_directory, 107 url_or_path=old_path, 108 revision1=pysvn.Revision( 109 pysvn.opt_revision_kind.number, old_rev), 110 url_or_path2=new_path, 111 revision2=pysvn.Revision( 112 pysvn.opt_revision_kind.number, new_rev)) 113 lines = text.split('\n') 114 target_lines = [] 115 for line in lines: 116 for testname in testname_list: 117 matches = re.findall(testname, line) 118 if matches: 119 if line[0] == '+' or line[0] == '-': 120 target_lines.append(line) 121 if target_lines: 122 # Needs to convert to normal date string for presentation. 123 result_list.append(( 124 old_rev, new_rev, logs[i + 1].author, 125 datetime.fromtimestamp( 126 logs[i + 1].date).strftime('%Y-%m-%d %H:%M:%S'), 127 logs[i + 1].message, target_lines)) 128 return result_list 129