Home | History | Annotate | Download | only in layout_tests
      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 
      7 from datetime import datetime
      8 from datetime import timedelta
      9 
     10 import os
     11 import re
     12 import sys
     13 import tempfile
     14 import time
     15 import pysvn
     16 
     17 TEST_EXPECTATIONS_ROOT = 'http://src.chromium.org/blink/trunk/'
     18 # A map from earliest revision to path.
     19 # TODO(imasaki): support multiple test expectation files.
     20 TEST_EXPECTATIONS_LOCATIONS = {
     21     148348: 'LayoutTests/TestExpectations',
     22     119317: 'LayoutTests/platform/chromium/TestExpectations',
     23     0: 'LayoutTests/platform/chromium/test_expectations.txt'}
     24 TEST_EXPECTATIONS_DEFAULT_PATH = (
     25     TEST_EXPECTATIONS_ROOT + TEST_EXPECTATIONS_LOCATIONS[148348])
     26 
     27 class 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