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