• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_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
215if __name__ == '__main__':
216    unittest.main()
217