blob: 0a40b33106f4e01682ed69a39e6efccda84b4df6 [file] [log] [blame]
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""End-to-end test of Exonerator using fake / mocked services."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import datetime
import json
from chromite.lib import clactions
from chromite.lib import fake_cidb
from chromite.lib import cidb
from chromite.lib import constants
from chromite.lib import gerrit
from google.cloud import datastore # pylint: disable=E0611,import-error
import pytest
import mock
from exonerator import gerrit_cq_test
from testing_utils import fake_datastore
import main
_DEFAULT_BUILD_VALUES = {
'builder_name': 'my builder',
'waterfall': 'chromiumos',
'build_config': 'a build config',
'bot_hostname': 'bot hostname',
}
GerritPatchTuple = clactions.GerritPatchTuple
# pylint: disable=redefined-outer-name
@pytest.fixture
def client():
return main.app.test_client()
@pytest.fixture
def cidb_conn():
return fake_cidb.FakeCIDBConnection()
@pytest.fixture
def datastore_client():
# The Datastore database is a global variable. Call reset_store between tests
# to reset its state.
fake_datastore.FakeDatastoreClient.reset_store()
return fake_datastore.FakeDatastoreClient()
def _GerritHelper(change_details_map):
helper = mock.Mock()
helper.GetChangeDetail = change_details_map.get
return helper
def _CronTest(client, cidb_conn, datastore_client, change_details_map,
endpoint='cq'):
"""Patches services with side-effects.
Args:
client: A Flask TestClient object
cidb_conn: A fake chromite.lib.cidb.CIDBConnection
datastore_client: A fake datastore.Client
change_details_map: A Dict[int, object] mapping change numbers to change
details responses from the gerrit API.
endpoint: /cron/exonerate/<endpoint> to call, defaults to 'cq'
Returns:
The JSON response from |endpoint|
"""
with mock.patch.object(datastore, 'Client', return_value=datastore_client),\
mock.patch.object(cidb, 'CIDBConnection', return_value=cidb_conn),\
mock.patch.object(gerrit, 'GetGerritHelper',
return_value=_GerritHelper(change_details_map)),\
_PatchDatetime():
response = client.get(
'/cron/exonerate/' + endpoint,
headers={main.APPENGINE_CRON_HEADER: 'true'})
return json.loads(response.get_data(as_text=True))
def _PatchDatetime():
"""Mocks out datetime.datetime to make time-dependent code consistent."""
orig_datetime = datetime.datetime
fake_datetime = mock.Mock(
wraps=orig_datetime,
now=mock.Mock(return_value=orig_datetime(year=2017, month=2, day=2)))
return mock.patch.object(datetime, 'datetime', fake_datetime)
def TestEmptyDatabase(client, cidb_conn, datastore_client):
"""Endpoints should succeed, but do nothing when the databases are empty."""
change_details_map = {}
response = _CronTest(client, cidb_conn, datastore_client, change_details_map)
assert response['exonerated'] == []
response = _CronTest(
client, cidb_conn, datastore_client, change_details_map,
endpoint='pre-cq')
assert response['exonerated'] == []
def TestBadCLNotExonerated(client, cidb_conn, datastore_client):
"""Tests that a CL which was blamed as bad is not exonerated."""
change_details = next(gerrit_cq_test.GlobChangeDetails(
gerrit_cq_test.READY_CASES))
change_number = change_details['_number']
change_details_map = {change_number: change_details}
build_id = cidb_conn.InsertBuild(
start_time=datetime.datetime(year=2017, month=1, day=1),
build_number=1,
**_DEFAULT_BUILD_VALUES)
action = clactions.CLAction.FromGerritPatchAndAction(
GerritPatchTuple(change_number, 1, False),
constants.CL_ACTION_KICKED_OUT)
cidb_conn.InsertCLActions(build_id, [action])
# pylint: disable=protected-access
cidb_conn._Insert('annotationsTable', {
'build_id': build_id,
'failure_category': constants.FAILURE_CATEGORY_BAD_CL,
'last_annotator': 'bert',
'failure_message': 'whoops!',
'blame_url': 'crrev.com/c/%d' % change_number,
})
cidb_conn._Insert('buildMessageTable', {
'build_id': build_id,
'message_type': constants.MESSAGE_TYPE_ANNOTATIONS_FINALIZED,
'timestamp': '2017-01-01 00:00:00'
})
response = _CronTest(client, cidb_conn, datastore_client, change_details_map)
assert response['exonerated'] == []
response = _CronTest(client, cidb_conn, datastore_client, change_details_map,
endpoint='pre-cq')
assert response['exonerated'] == []
def TestGoodCLExonerated(client, cidb_conn, datastore_client):
"""Tests that a CL which was not blamed is exonerated."""
change_details = next(gerrit_cq_test.GlobChangeDetails(
gerrit_cq_test.READY_CASES))
change_number = change_details['_number']
change_details_map = {change_number: change_details}
build_id = cidb_conn.InsertBuild(
start_time=datetime.datetime(year=2017, month=1, day=1),
build_number=1,
**_DEFAULT_BUILD_VALUES)
patch = GerritPatchTuple(change_number, 1, False)
action = clactions.CLAction.FromGerritPatchAndAction(
patch, constants.CL_ACTION_KICKED_OUT)
cidb_conn.InsertCLActions(build_id, [action])
# pylint: disable=protected-access
cidb_conn._Insert('annotationsTable', {
'build_id': build_id,
'failure_category': constants.FAILURE_CATEGORY_BAD_CL,
'last_annotator': 'bert',
'failure_message': 'whoops!',
'blame_url': 'crrev.com/c/%d' % (change_number + 1),
})
cidb_conn._Insert('buildMessageTable', {
'build_id': build_id,
'message_type': constants.MESSAGE_TYPE_ANNOTATIONS_FINALIZED,
'timestamp': '2017-01-01 00:00:00'
})
response = _CronTest(client, cidb_conn, datastore_client, change_details_map)
assert response['exonerated'] == [[list(patch), build_id]]
response = _CronTest(client, cidb_conn, datastore_client, change_details_map,
endpoint='pre-cq')
assert response['exonerated'] == []