1#!/usr/bin/env python2 2# 3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7# pylint: disable-msg=C0111 8 9"""Unit tests for server/cros/dynamic_suite/job_status.py.""" 10 11from __future__ import absolute_import 12from __future__ import division 13from __future__ import print_function 14 15import mox 16import os 17import shutil 18import six 19from six.moves import map 20from six.moves import range 21import tempfile 22import time 23import unittest 24 25import common 26 27from autotest_lib.server import frontend 28from autotest_lib.server.cros.dynamic_suite import host_spec 29from autotest_lib.server.cros.dynamic_suite import job_status 30from autotest_lib.server.cros.dynamic_suite.fakes import FakeJob 31from autotest_lib.server.cros.dynamic_suite.fakes import FakeStatus 32 33 34DEFAULT_WAITTIMEOUT_MINS = 60 * 4 35 36 37class StatusTest(mox.MoxTestBase): 38 """Unit tests for job_status.Status. 39 """ 40 41 42 def setUp(self): 43 super(StatusTest, self).setUp() 44 self.afe = self.mox.CreateMock(frontend.AFE) 45 self.tko = self.mox.CreateMock(frontend.TKO) 46 47 self.tmpdir = tempfile.mkdtemp(suffix=type(self).__name__) 48 49 50 def tearDown(self): 51 super(StatusTest, self).tearDown() 52 shutil.rmtree(self.tmpdir, ignore_errors=True) 53 54 55 def expect_yield_job_entries(self, job): 56 entries = [s.entry for s in job.statuses] 57 self.afe.run('get_host_queue_entries', 58 job=job.id).AndReturn(entries) 59 if True not in ['aborted' in e and e['aborted'] for e in entries]: 60 self.tko.get_job_test_statuses_from_db(job.id).AndReturn( 61 job.statuses) 62 63 64 def testJobResultWaiter(self): 65 """Should gather status and return records for job summaries.""" 66 jobs = [FakeJob(0, [FakeStatus('GOOD', 'T0', ''), 67 FakeStatus('GOOD', 'T1', '')]), 68 FakeJob(1, [FakeStatus('ERROR', 'T0', 'err', False), 69 FakeStatus('GOOD', 'T1', '')]), 70 FakeJob(2, [FakeStatus('TEST_NA', 'T0', 'no')]), 71 FakeJob(3, [FakeStatus('FAIL', 'T0', 'broken')]), 72 FakeJob(4, [FakeStatus('ERROR', 'SERVER_JOB', 'server error'), 73 FakeStatus('GOOD', 'T0', '')]),] 74 # TODO: Write a better test for the case where we yield 75 # results for aborts vs cannot yield results because of 76 # a premature abort. Currently almost all client aborts 77 # have been converted to failures, and when aborts do happen 78 # they result in server job failures for which we always 79 # want results. 80 # FakeJob(5, [FakeStatus('ERROR', 'T0', 'gah', True)]), 81 # The next job shouldn't be recorded in the results. 82 # FakeJob(6, [FakeStatus('GOOD', 'SERVER_JOB', '')])] 83 for status in jobs[4].statuses: 84 status.entry['job'] = {'name': 'broken_infra_job'} 85 86 job_id_set = set([job.id for job in jobs]) 87 yield_values = [ 88 [jobs[1]], 89 [jobs[0], jobs[2]], 90 jobs[3:6] 91 ] 92 self.mox.StubOutWithMock(time, 'sleep') 93 for yield_this in yield_values: 94 self.afe.get_jobs(id__in=list(job_id_set), 95 finished=True).AndReturn(yield_this) 96 for job in yield_this: 97 self.expect_yield_job_entries(job) 98 job_id_set.remove(job.id) 99 time.sleep(mox.IgnoreArg()) 100 self.mox.ReplayAll() 101 102 waiter = job_status.JobResultWaiter(self.afe, self.tko) 103 waiter.add_jobs(jobs) 104 results = [result for result in waiter.wait_for_results()] 105 for job in jobs[:6]: # the 'GOOD' SERVER_JOB shouldn't be there. 106 for status in job.statuses: 107 self.assertTrue(True in list(map(status.equals_record, results))) 108 109 110 def testYieldSubdir(self): 111 """Make sure subdir are properly set for test and non-test status.""" 112 job_tag = '0-owner/172.33.44.55' 113 job_name = 'broken_infra_job' 114 job = FakeJob(0, [FakeStatus('ERROR', 'SERVER_JOB', 'server error', 115 subdir='---', job_tag=job_tag), 116 FakeStatus('GOOD', 'T0', '', 117 subdir='T0.subdir', job_tag=job_tag)], 118 parent_job_id=54321) 119 for status in job.statuses: 120 status.entry['job'] = {'name': job_name} 121 self.expect_yield_job_entries(job) 122 self.mox.ReplayAll() 123 results = list(job_status._yield_job_results(self.afe, self.tko, job)) 124 for i in range(len(results)): 125 result = results[i] 126 if result.test_name.endswith('SERVER_JOB'): 127 expected_name = '%s_%s' % (job_name, job.statuses[i].test_name) 128 expected_subdir = job_tag 129 else: 130 expected_name = job.statuses[i].test_name 131 expected_subdir = os.path.join(job_tag, job.statuses[i].subdir) 132 self.assertEqual(results[i].test_name, expected_name) 133 self.assertEqual(results[i].subdir, expected_subdir) 134 135 136 def _prepareForReporting(self, results): 137 def callable(x): 138 pass 139 140 record_entity = self.mox.CreateMock(callable) 141 group = self.mox.CreateMock(host_spec.HostGroup) 142 143 statuses = {} 144 all_bad = True not in six.itervalues(results) 145 for hostname, result in six.iteritems(results): 146 status = self.mox.CreateMock(job_status.Status) 147 status.record_all(record_entity).InAnyOrder('recording') 148 status.is_good().InAnyOrder('recording').AndReturn(result) 149 if not result: 150 status.test_name = 'test' 151 if not all_bad: 152 status.override_status('WARN').InAnyOrder('recording') 153 else: 154 group.mark_host_success(hostname).InAnyOrder('recording') 155 statuses[hostname] = status 156 157 return (statuses, group, record_entity) 158 159 160if __name__ == '__main__': 161 unittest.main() 162