blob: 2b6bbc33023e194c633b995de3e2e618514d7c63 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2014 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.
"""Unit tests for gather_builder_stats."""
import datetime
import itertools
import os
import random
import sys
import unittest
sys.path.insert(0, os.path.abspath('%s/../..' % os.path.dirname(__file__)))
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.scripts import gather_builder_stats
from chromite.buildbot import cbuildbot_metadata
from chromite.buildbot import constants
import mock
REASON_BAD_CL = gather_builder_stats.CLStats.REASON_BAD_CL
CQ = constants.CQ
class TestCLActionLogic(unittest.TestCase):
"""Ensures that CL action analysis logic is correct."""
def _getTestBuildData(self):
"""Generate a return test data.
Returns:
A list of cbuildbot_metadata.BuildData objects to use as
test data for CL action summary logic.
"""
# Mock patches for test data.
c1p1 = cbuildbot_metadata.GerritPatchTuple(1, 1, False)
c2p1 = cbuildbot_metadata.GerritPatchTuple(2, 1, True)
c2p2 = cbuildbot_metadata.GerritPatchTuple(2, 2, True)
# Mock builder status dictionaries
passed_status = {'status' : constants.FINAL_STATUS_PASSED}
failed_status = {'status' : constants.FINAL_STATUS_FAILED}
t = itertools.count()
TEST_METADATA = [
# Build 1 picks up no patches.
cbuildbot_metadata.CBuildbotMetadata(
).UpdateWithDict({'build-number' : 1,
'status' : passed_status}),
# Build 2 picks up c1p1 and does nothing.
cbuildbot_metadata.CBuildbotMetadata(
).UpdateWithDict({'build-number' : 2,
'status' : failed_status}
).RecordCLAction(c1p1, constants.CL_ACTION_PICKED_UP, t.next()),
# Build 3 picks up c1p1 and c2p1 and rejects both.
cbuildbot_metadata.CBuildbotMetadata(
).UpdateWithDict({'build-number' : 3,
'status' : failed_status}
).RecordCLAction(c1p1, constants.CL_ACTION_PICKED_UP, t.next()
).RecordCLAction(c2p1, constants.CL_ACTION_PICKED_UP, t.next()
).RecordCLAction(c1p1, constants.CL_ACTION_KICKED_OUT, t.next()
).RecordCLAction(c2p1, constants.CL_ACTION_KICKED_OUT, t.next()),
# Build 4 picks up c1p1 and c2p2 and submits both.
# So c1p1 should be detected as a 1-time rejected good patch,
# and c2p1 should be detected as a possibly bad patch.
cbuildbot_metadata.CBuildbotMetadata(
).UpdateWithDict({'build-number' : 4,
'status' : passed_status}
).RecordCLAction(c1p1, constants.CL_ACTION_PICKED_UP, t.next()
).RecordCLAction(c2p2, constants.CL_ACTION_PICKED_UP, t.next()
).RecordCLAction(c1p1, constants.CL_ACTION_SUBMITTED, t.next()
).RecordCLAction(c2p2, constants.CL_ACTION_SUBMITTED, t.next())
]
# TEST_METADATA should not be guaranteed to be ordered by build number
# so shuffle it, but use the same seed each time so that unit test is
# deterministic.
random.seed(0)
random.shuffle(TEST_METADATA)
# Wrap the test metadata into BuildData objects.
TEST_BUILDDATA = [cbuildbot_metadata.BuildData('', d.GetDict())
for d in TEST_METADATA]
return TEST_BUILDDATA
def testCLStatsSummary(self):
with cros_build_lib.ContextManagerStack() as stack:
test_builddata = self._getTestBuildData()
stack.Add(mock.patch.object, gather_builder_stats.StatsManager,
'_FetchBuildData', side_effect=[test_builddata, []])
stack.Add(mock.patch.object, gather_builder_stats, '_PrepareCreds')
stack.Add(mock.patch.object, gather_builder_stats.CLStats,
'GatherFailureReasons')
cl_stats = gather_builder_stats.CLStats('foo@bar.com')
cl_stats.Gather(datetime.date.today())
cl_stats.reasons = {1: '', 2: '', 3: REASON_BAD_CL, 4: REASON_BAD_CL}
summary = cl_stats.Summarize()
expected = {
'mean_good_patch_rejections': 0.5,
'unique_patches': 3,
'total_cl_actions': 9,
'good_patch_rejection_breakdown': [(0, 1), (1, 1)],
'good_patches_rejected': 1,
'submitted_patches': 2,
'submit_fails': 0,
'unique_cls': 2,
'median_handling_time': -1, # This will be ignored in comparison
'bad_cl_candidates': {CQ: [
cbuildbot_metadata.GerritChangeTuple(gerrit_number=2,
internal=True)]},
'rejections': 2}
# Ignore handling time in comparison.
summary['median_handling_time'] = expected['median_handling_time']
self.maxDiff = None
self.assertEqual(summary, expected)
if __name__ == '__main__':
cros_test_lib.main()