# Copyright (c) 2014 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import re from threading import Lock import crash_utils REVIEW_URL_PATTERN = re.compile(r'Review URL:( *)(.*?)/(\d+)') class Match(object): """Represents a match entry. A match is a CL that is suspected to have caused the crash. A match object contains information about files it changes, their authors, etc. Attributes: is_revert: True if this CL is reverted by other CL. revert_of: If this CL is a revert of some other CL, a revision number/ git hash of that CL. crashed_line_numbers: The list of lines that caused crash for this CL. function_list: The list of functions that caused the crash. min_distance: The minimum distance between the lines that CL changed and lines that caused the crash. changed_files: The list of files that the CL changed. changed_file_urls: The list of URLs for the file. author: The author of the CL. component_name: The name of the component that this CL belongs to. stack_frame_indices: For files that caused crash, list of where in the stackframe they occur. priorities: A list of priorities for each of the changed file. A priority is 1 if the file changes a crashed line, and 2 if it changes the file but not the crashed line. reivision_url: The revision URL of the CL. review_url: The codereview URL that reviews this CL. reviewers: The list of people that reviewed this CL. reason: The reason why this CL is suspected. """ REVERT_PATTERN = re.compile(r'(revert\w*) r?(\d+)', re.I) def __init__(self, revision, component_name): self.is_revert = False self.revert_of = None self.message = None self.crashed_line_numbers = [] self.function_list = [] self.min_distance = crash_utils.INFINITY self.min_distance_info = None self.changed_files = [] self.changed_file_urls = [] self.author = revision['author'] self.component_name = component_name self.stack_frame_indices = [] self.priorities = [] self.revision_url = revision['url'] self.review_url = '' self.reviewers = [] self.reason = None def ParseMessage(self, message, codereview_api_url): """Parses the message. It checks the message to extract the code review website and list of reviewers, and it also checks if the CL is a revert of another CL. Args: message: The message to parse. codereview_api_url: URL to retrieve codereview data from. """ self.message = message for line in message.splitlines(): line = line.strip() review_url_line_match = REVIEW_URL_PATTERN.match(line) # Check if the line has the code review information. if review_url_line_match: # Get review number for the code review site from the line. issue_number = review_url_line_match.group(3) # Get JSON from the code review site, ignore the line if it fails. url = codereview_api_url % issue_number json_string = crash_utils.GetDataFromURL(url) if not json_string: continue # Load the JSON from the string, and get the list of reviewers. code_review = crash_utils.LoadJSON(json_string) if code_review: self.reviewers = code_review['reviewers'] # Check if this CL is a revert of other CL. if line.lower().startswith('revert'): self.is_revert = True # Check if the line says what CL this CL is a revert of. revert = self.REVERT_PATTERN.match(line) if revert: self.revert_of = revert.group(2) return class MatchSet(object): """Represents a set of matches. Attributes: matches: A map from CL to a match object. cls_to_ignore: A set of CLs to ignore. matches_lock: A lock guarding matches dictionary. """ def __init__(self, codereview_api_url): self.codereview_api_url = codereview_api_url self.matches = {} self.cls_to_ignore = set() self.matches_lock = Lock() def RemoveRevertedCLs(self): """Removes CLs that are revert.""" for cl in self.matches: if cl in self.cls_to_ignore: del self.matches[cl]