• 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_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