blob: c52bd5620ca465a401c6eac0a6ea7717f8559f39 [file] [log] [blame]
# Copyright 2019 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.
from google.appengine.ext import ndb
class GitilesCommit(ndb.Model):
"""A class for a gitiles commit."""
@classmethod
def _CreateKey(cls, gitiles_host, gitiles_project, gitiles_ref, gitiles_id):
return ndb.Key(
cls.__name__, '{}/{}/{}/{}'.format(gitiles_host, gitiles_project,
gitiles_ref, gitiles_id))
@classmethod
def Get(cls, gitiles_host, gitiles_project, gitiles_ref, gitiles_id):
return cls._CreateKey(gitiles_host, gitiles_project, gitiles_ref,
gitiles_id).get()
@classmethod
def Create(cls, gitiles_host, gitiles_project, gitiles_ref, gitiles_id,
commit_position):
"""Creates an entity for a commit."""
return cls(
key=cls._CreateKey(gitiles_host, gitiles_project, gitiles_ref,
gitiles_id),
gitiles_host=gitiles_host,
gitiles_project=gitiles_project,
gitiles_ref=gitiles_ref,
gitiles_id=gitiles_id,
commit_position=commit_position)
# Gitiles hostname, e.g. 'chromium.googlesource.com'.
gitiles_host = ndb.StringProperty(required=True)
# Project name the commit belongs to, e.g. 'chromium/src'.
gitiles_project = ndb.StringProperty(required=True)
# Associated git ref of the commit, e.g. 'refs/heads/master'.
# NOT a branch name: must start with 'refs/'.
# If not specified when query build, default to 'refs/heads/master'.
gitiles_ref = ndb.StringProperty(required=True)
# SHA1 of the change.
# May be called as 'revision' or 'git_hash' somewhere else.
gitiles_id = ndb.StringProperty(required=True)
# Integer identifier of a commit, required to sort commits.
commit_position = ndb.IntegerProperty(required=True)
class Hint(ndb.Model):
"""Describes a hint produced by heuristic analysis and its score.
I.e. A fact about a commit that makes it a suspect.
"""
# A short, human-readable string that concisely describes a fact about the
# suspect. e.g. 'add a/b/x.cc'
content = ndb.StringProperty(indexed=False)
# A score value for the hint, where a higher score means a stronger signal
# that the suspect is indeed responsible for a given failure.
score = ndb.IntegerProperty(indexed=False)
class Suspect(GitilesCommit):
"""Identifies a commit that is suspected of being a culprit for a failure."""
# Facts about this commit that make it a suspect.
hints = ndb.StructuredProperty(Hint, repeated=True)
@property
def score(self):
return sum(h.score for h in self.hints)
@classmethod
def GetOrCreate(cls,
gitiles_host,
gitiles_project,
gitiles_ref,
gitiles_id,
commit_position=None,
hints=None):
"""Gets or Creates a Suspect entity.
This also updates the entity if needed.
"""
updated = False
suspect = cls.Get(gitiles_host, gitiles_project, gitiles_ref, gitiles_id)
if not suspect:
suspect = cls.Create(gitiles_host, gitiles_project, gitiles_ref,
gitiles_id, commit_position)
updated = True
if hints:
suspect.hints = suspect.hints or []
for content, score in hints.iteritems():
new_hint = Hint(content=content, score=score)
if new_hint not in suspect.hints:
suspect.hints.append(new_hint)
updated = True
if updated:
suspect.put()
return suspect
class Culprit(GitilesCommit):
"""Base class for a suspected or culprit commit."""
# Urlsafe_keys to atom failures this culprit is responsible for.
# Uses urlsafe_keys so that it can accept both compile and test failures.
failure_urlsafe_keys = ndb.StringProperty(repeated=True)
@classmethod
# pylint: disable=arguments-differ
def Create(cls,
gitiles_host,
gitiles_project,
gitiles_ref,
gitiles_id,
commit_position,
failure_urlsafe_keys=None):
"""Creates an entity for a culprit."""
return cls(
key=cls._CreateKey(gitiles_host, gitiles_project, gitiles_ref,
gitiles_id),
gitiles_host=gitiles_host,
gitiles_project=gitiles_project,
gitiles_ref=gitiles_ref,
gitiles_id=gitiles_id,
commit_position=commit_position,
failure_urlsafe_keys=failure_urlsafe_keys or [])
@classmethod
def GetOrCreate(cls,
gitiles_host,
gitiles_project,
gitiles_ref,
gitiles_id,
commit_position=None,
failure_urlsafe_keys=None):
"""Gets or Creates a Culprit entity.
If failure_urlsafe_keys provided, update the culprit as well.
"""
updated = False
culprit = cls.Get(gitiles_host, gitiles_project, gitiles_ref, gitiles_id)
if not culprit:
culprit = cls.Create(gitiles_host, gitiles_project, gitiles_ref,
gitiles_id, commit_position)
updated = True
if failure_urlsafe_keys:
culprit.failure_urlsafe_keys = list(
set(culprit.failure_urlsafe_keys) | set(failure_urlsafe_keys))
updated = True
if updated:
culprit.put()
return culprit