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 11 import mox 12 import shutil 13 import tempfile 14 import time 15 import unittest 16 import os 17 import common 18 19 from autotest_lib.server import frontend 20 from autotest_lib.server.cros.dynamic_suite import host_spec 21 from autotest_lib.server.cros.dynamic_suite import job_status 22 from autotest_lib.server.cros.dynamic_suite.fakes import FakeJob 23 from autotest_lib.server.cros.dynamic_suite.fakes import FakeStatus 24 25 26 DEFAULT_WAITTIMEOUT_MINS = 60 * 4 27 28 29 class StatusTest(mox.MoxTestBase): 30 """Unit tests for job_status.Status. 31 """ 32 33 34 def setUp(self): 35 super(StatusTest, self).setUp() 36 self.afe = self.mox.CreateMock(frontend.AFE) 37 self.tko = self.mox.CreateMock(frontend.TKO) 38 39 self.tmpdir = tempfile.mkdtemp(suffix=type(self).__name__) 40 41 42 def tearDown(self): 43 super(StatusTest, self).tearDown() 44 shutil.rmtree(self.tmpdir, ignore_errors=True) 45 46 47 def expect_result_gathering(self, job): 48 self.afe.get_jobs(id=job.id, finished=True).AndReturn(job) 49 self.expect_yield_job_entries(job) 50 51 52 def expect_yield_job_entries(self, job): 53 entries = [s.entry for s in job.statuses] 54 self.afe.run('get_host_queue_entries', 55 job=job.id).AndReturn(entries) 56 if True not in map(lambda e: 'aborted' in e and e['aborted'], entries): 57 self.tko.get_job_test_statuses_from_db(job.id).AndReturn( 58 job.statuses) 59 60 61 def testWaitForResults(self): 62 """Should gather status and return records for job summaries.""" 63 jobs = [FakeJob(0, [FakeStatus('GOOD', 'T0', ''), 64 FakeStatus('GOOD', 'T1', '')]), 65 FakeJob(1, [FakeStatus('ERROR', 'T0', 'err', False), 66 FakeStatus('GOOD', 'T1', '')]), 67 FakeJob(2, [FakeStatus('TEST_NA', 'T0', 'no')]), 68 FakeJob(3, [FakeStatus('FAIL', 'T0', 'broken')]), 69 FakeJob(4, [FakeStatus('ERROR', 'SERVER_JOB', 'server error'), 70 FakeStatus('GOOD', 'T0', '')]),] 71 72 # TODO: Write a better test for the case where we yield 73 # results for aborts vs cannot yield results because of 74 # a premature abort. Currently almost all client aborts 75 # have been converted to failures, and when aborts do happen 76 # they result in server job failures for which we always 77 # want results. 78 # FakeJob(5, [FakeStatus('ERROR', 'T0', 'gah', True)]), 79 # The next job shouldn't be recorded in the results. 80 # FakeJob(6, [FakeStatus('GOOD', 'SERVER_JOB', '')])] 81 82 for status in jobs[4].statuses: 83 status.entry['job'] = {'name': 'broken_infra_job'} 84 85 # To simulate a job that isn't ready the first time we check. 86 self.afe.get_jobs(id=jobs[0].id, finished=True).AndReturn([]) 87 # Expect all the rest of the jobs to be good to go the first time. 88 for job in jobs[1:]: 89 self.expect_result_gathering(job) 90 # Then, expect job[0] to be ready. 91 self.expect_result_gathering(jobs[0]) 92 # Expect us to poll twice. 93 self.mox.StubOutWithMock(time, 'sleep') 94 time.sleep(mox.IgnoreArg()) 95 time.sleep(mox.IgnoreArg()) 96 self.mox.ReplayAll() 97 98 results = [result for result in job_status.wait_for_results(self.afe, 99 self.tko, 100 jobs)] 101 for job in jobs[:6]: # the 'GOOD' SERVER_JOB shouldn't be there. 102 for status in job.statuses: 103 self.assertTrue(True in map(status.equals_record, results)) 104 105 106 def testWaitForChildResults(self): 107 """Should gather status and return records for job summaries.""" 108 parent_job_id = 54321 109 jobs = [FakeJob(0, [FakeStatus('GOOD', 'T0', ''), 110 FakeStatus('GOOD', 'T1', '')], 111 parent_job_id=parent_job_id), 112 FakeJob(1, [FakeStatus('ERROR', 'T0', 'err', False), 113 FakeStatus('GOOD', 'T1', '')], 114 parent_job_id=parent_job_id), 115 FakeJob(2, [FakeStatus('TEST_NA', 'T0', 'no')], 116 parent_job_id=parent_job_id), 117 FakeJob(3, [FakeStatus('FAIL', 'T0', 'broken')], 118 parent_job_id=parent_job_id), 119 FakeJob(4, [FakeStatus('ERROR', 'SERVER_JOB', 'server error'), 120 FakeStatus('GOOD', 'T0', '')], 121 parent_job_id=parent_job_id),] 122 123 # TODO: Write a better test for the case where we yield 124 # results for aborts vs cannot yield results because of 125 # a premature abort. Currently almost all client aborts 126 # have been converted to failures and when aborts do happen 127 # they result in server job failures for which we always 128 # want results. 129 #FakeJob(5, [FakeStatus('ERROR', 'T0', 'gah', True)], 130 # parent_job_id=parent_job_id), 131 # The next job shouldn't be recorded in the results. 132 #FakeJob(6, [FakeStatus('GOOD', 'SERVER_JOB', '')], 133 # parent_job_id=12345)] 134 for status in jobs[4].statuses: 135 status.entry['job'] = {'name': 'broken_infra_job'} 136 137 # Expect one call to get a list of all child jobs. 138 self.afe.get_jobs(parent_job_id=parent_job_id).AndReturn(jobs[:6]) 139 140 job_id_set = set([job.id for job in jobs]) 141 yield_values = [ 142 [jobs[1]], 143 [jobs[0], jobs[2]], 144 jobs[3:6] 145 ] 146 self.mox.StubOutWithMock(time, 'sleep') 147 for yield_this in yield_values: 148 self.afe.get_jobs(id__in=list(job_id_set), 149 finished=True).AndReturn(yield_this) 150 for job in yield_this: 151 self.expect_yield_job_entries(job) 152 job_id_set.remove(job.id) 153 time.sleep(mox.IgnoreArg()) 154 self.mox.ReplayAll() 155 156 results = [result for result in job_status.wait_for_child_results( 157 self.afe, 158 self.tko, 159 parent_job_id)] 160 for job in jobs[:6]: # the 'GOOD' SERVER_JOB shouldn't be there. 161 for status in job.statuses: 162 self.assertTrue(True in map(status.equals_record, results)) 163 164 165 def testYieldSubdir(self): 166 """Make sure subdir are properly set for test and non-test status.""" 167 job_tag = '0-owner/172.33.44.55' 168 job_name = 'broken_infra_job' 169 job = FakeJob(0, [FakeStatus('ERROR', 'SERVER_JOB', 'server error', 170 subdir='---', job_tag=job_tag), 171 FakeStatus('GOOD', 'T0', '', 172 subdir='T0.subdir', job_tag=job_tag)], 173 parent_job_id=54321) 174 for status in job.statuses: 175 status.entry['job'] = {'name': job_name} 176 self.expect_yield_job_entries(job) 177 self.mox.ReplayAll() 178 results = list(job_status._yield_job_results(self.afe, self.tko, job)) 179 for i in range(len(results)): 180 result = results[i] 181 if result.test_name.endswith('SERVER_JOB'): 182 expected_name = '%s_%s' % (job_name, job.statuses[i].test_name) 183 expected_subdir = job_tag 184 else: 185 expected_name = job.statuses[i].test_name 186 expected_subdir = os.path.join(job_tag, job.statuses[i].subdir) 187 self.assertEqual(results[i].test_name, expected_name) 188 self.assertEqual(results[i].subdir, expected_subdir) 189 190 191 def _prepareForReporting(self, results): 192 def callable(x): 193 pass 194 195 record_entity = self.mox.CreateMock(callable) 196 group = self.mox.CreateMock(host_spec.HostGroup) 197 198 statuses = {} 199 all_bad = True not in results.itervalues() 200 for hostname, result in results.iteritems(): 201 status = self.mox.CreateMock(job_status.Status) 202 status.record_all(record_entity).InAnyOrder('recording') 203 status.is_good().InAnyOrder('recording').AndReturn(result) 204 if not result: 205 status.test_name = 'test' 206 if not all_bad: 207 status.override_status('WARN').InAnyOrder('recording') 208 else: 209 group.mark_host_success(hostname).InAnyOrder('recording') 210 statuses[hostname] = status 211 212 return (statuses, group, record_entity) 213 214 215 if __name__ == '__main__': 216 unittest.main() 217