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