| # 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', |
| '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'] == [] |