• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Tests for job_directories."""
2
3from __future__ import absolute_import
4from __future__ import division
5from __future__ import print_function
6
7import contextlib
8import datetime
9import mox
10import os
11import shutil
12import tempfile
13import unittest
14
15import common
16from autotest_lib.site_utils import job_directories
17from autotest_lib.client.common_lib import time_utils
18
19
20class SwarmingJobDirectoryTestCase(unittest.TestCase):
21    """Tests SwarmingJobDirectory."""
22
23    def test_get_job_directories_legacy(self):
24        with _change_to_tempdir():
25            os.makedirs("swarming-3e4391423c3a4311/b")
26            os.mkdir("not-a-swarming-dir")
27            results = job_directories.SwarmingJobDirectory.get_job_directories()
28            self.assertEqual(set(results), {"swarming-3e4391423c3a4311"})
29
30    def test_get_job_directories(self):
31        with _change_to_tempdir():
32            os.makedirs("swarming-3e4391423c3a4310/1")
33            os.makedirs("swarming-3e4391423c3a4310/0")
34            os.makedirs("swarming-3e4391423c3a4310/a")
35            os.mkdir("not-a-swarming-dir")
36            results = job_directories.SwarmingJobDirectory.get_job_directories()
37            self.assertEqual(set(results),
38                             {"swarming-3e4391423c3a4310/1",
39                              "swarming-3e4391423c3a4310/a"})
40
41
42class GetJobIDOrTaskID(unittest.TestCase):
43    """Tests get_job_id_or_task_id."""
44
45    def test_legacy_swarming_path(self):
46        self.assertEqual(
47                "3e4391423c3a4311",
48                job_directories.get_job_id_or_task_id(
49                        "/autotest/results/swarming-3e4391423c3a4311"),
50        )
51        self.assertEqual(
52                "3e4391423c3a4311",
53                job_directories.get_job_id_or_task_id(
54                        "swarming-3e4391423c3a4311"),
55        )
56
57    def test_swarming_path(self):
58        self.assertEqual(
59                "3e4391423c3a4311",
60                job_directories.get_job_id_or_task_id(
61                        "/autotest/results/swarming-3e4391423c3a4310/1"),
62        )
63        self.assertEqual(
64                "3e4391423c3a431f",
65                job_directories.get_job_id_or_task_id(
66                        "swarming-3e4391423c3a4310/f"),
67        )
68
69
70
71class JobDirectorySubclassTests(mox.MoxTestBase):
72    """Test specific to RegularJobDirectory and SpecialJobDirectory.
73
74    This provides coverage for the implementation in both
75    RegularJobDirectory and SpecialJobDirectory.
76
77    """
78
79    def setUp(self):
80        super(JobDirectorySubclassTests, self).setUp()
81        self.mox.StubOutWithMock(job_directories, '_AFE')
82
83
84    def test_regular_job_fields(self):
85        """Test the constructor for `RegularJobDirectory`.
86
87        Construct a regular job, and assert that the `dirname`
88        and `_id` attributes are set as expected.
89
90        """
91        resultsdir = '118-fubar'
92        job = job_directories.RegularJobDirectory(resultsdir)
93        self.assertEqual(job.dirname, resultsdir)
94        self.assertEqual(job._id, '118')
95
96
97    def test_special_job_fields(self):
98        """Test the constructor for `SpecialJobDirectory`.
99
100        Construct a special job, and assert that the `dirname`
101        and `_id` attributes are set as expected.
102
103        """
104        destdir = 'hosts/host1'
105        resultsdir = destdir + '/118-reset'
106        job = job_directories.SpecialJobDirectory(resultsdir)
107        self.assertEqual(job.dirname, resultsdir)
108        self.assertEqual(job._id, '118')
109
110
111    def _check_finished_job(self, jobtime, hqetimes, expected):
112        """Mock and test behavior of a finished job.
113
114        Initialize the mocks for a call to
115        `get_timestamp_if_finished()`, then simulate one call.
116        Assert that the returned timestamp matches the passed
117        in expected value.
118
119        @param jobtime Time used to construct a _MockJob object.
120        @param hqetimes List of times used to construct
121                        _MockHostQueueEntry objects.
122        @param expected Expected time to be returned by
123                        get_timestamp_if_finished
124
125        """
126        job = job_directories.RegularJobDirectory('118-fubar')
127        job_directories._AFE.get_jobs(
128                id=job._id, finished=True).AndReturn(
129                        [_MockJob(jobtime)])
130        job_directories._AFE.get_host_queue_entries(
131                finished_on__isnull=False,
132                job_id=job._id).AndReturn(
133                        [_MockHostQueueEntry(t) for t in hqetimes])
134        self.mox.ReplayAll()
135        self.assertEqual(expected, job.get_timestamp_if_finished())
136        self.mox.VerifyAll()
137
138
139    def test_finished_regular_job(self):
140        """Test getting the timestamp for a finished regular job.
141
142        Tests the return value for
143        `RegularJobDirectory.get_timestamp_if_finished()` when
144        the AFE indicates the job is finished.
145
146        """
147        created_timestamp = make_timestamp(1, True)
148        hqe_timestamp = make_timestamp(0, True)
149        self._check_finished_job(created_timestamp,
150                                 [hqe_timestamp],
151                                 hqe_timestamp)
152
153
154    def test_finished_regular_job_multiple_hqes(self):
155        """Test getting the timestamp for a regular job with multiple hqes.
156
157        Tests the return value for
158        `RegularJobDirectory.get_timestamp_if_finished()` when
159        the AFE indicates the job is finished and the job has multiple host
160        queue entries.
161
162        Tests that the returned timestamp is the latest timestamp in
163        the list of HQEs, regardless of the returned order.
164
165        """
166        created_timestamp = make_timestamp(2, True)
167        older_hqe_timestamp = make_timestamp(1, True)
168        newer_hqe_timestamp = make_timestamp(0, True)
169        hqe_list = [older_hqe_timestamp,
170                    newer_hqe_timestamp]
171        self._check_finished_job(created_timestamp,
172                                 hqe_list,
173                                 newer_hqe_timestamp)
174        self.mox.ResetAll()
175        hqe_list.reverse()
176        self._check_finished_job(created_timestamp,
177                                 hqe_list,
178                                 newer_hqe_timestamp)
179
180
181    def test_finished_regular_job_null_finished_times(self):
182        """Test getting the timestamp for an aborted regular job.
183
184        Tests the return value for
185        `RegularJobDirectory.get_timestamp_if_finished()` when
186        the AFE indicates the job is finished and the job has aborted host
187        queue entries.
188
189        """
190        timestamp = make_timestamp(0, True)
191        self._check_finished_job(timestamp, [], timestamp)
192
193
194    def test_unfinished_regular_job(self):
195        """Test getting the timestamp for an unfinished regular job.
196
197        Tests the return value for
198        `RegularJobDirectory.get_timestamp_if_finished()` when
199        the AFE indicates the job is not finished.
200
201        """
202        job = job_directories.RegularJobDirectory('118-fubar')
203        job_directories._AFE.get_jobs(
204                id=job._id, finished=True).AndReturn([])
205        self.mox.ReplayAll()
206        self.assertIsNone(job.get_timestamp_if_finished())
207        self.mox.VerifyAll()
208
209
210    def test_finished_special_job(self):
211        """Test getting the timestamp for a finished special job.
212
213        Tests the return value for
214        `SpecialJobDirectory.get_timestamp_if_finished()` when
215        the AFE indicates the job is finished.
216
217        """
218        job = job_directories.SpecialJobDirectory(
219                'hosts/host1/118-reset')
220        timestamp = make_timestamp(0, True)
221        job_directories._AFE.get_special_tasks(
222                id=job._id, is_complete=True).AndReturn(
223                    [_MockSpecialTask(timestamp)])
224        self.mox.ReplayAll()
225        self.assertEqual(timestamp,
226                         job.get_timestamp_if_finished())
227        self.mox.VerifyAll()
228
229
230    def test_unfinished_special_job(self):
231        """Test getting the timestamp for an unfinished special job.
232
233        Tests the return value for
234        `SpecialJobDirectory.get_timestamp_if_finished()` when
235        the AFE indicates the job is not finished.
236
237        """
238        job = job_directories.SpecialJobDirectory(
239                'hosts/host1/118-reset')
240        job_directories._AFE.get_special_tasks(
241                id=job._id, is_complete=True).AndReturn([])
242        self.mox.ReplayAll()
243        self.assertIsNone(job.get_timestamp_if_finished())
244        self.mox.VerifyAll()
245
246
247class JobExpirationTests(unittest.TestCase):
248    """Tests to exercise `job_directories.is_job_expired()`."""
249
250    def test_expired(self):
251        """Test detection of an expired job."""
252        timestamp = make_timestamp(_TEST_EXPIRATION_AGE, True)
253        self.assertTrue(
254            job_directories.is_job_expired(
255                _TEST_EXPIRATION_AGE, timestamp))
256
257
258    def test_alive(self):
259        """Test detection of a job that's not expired."""
260        # N.B.  This test may fail if its run time exceeds more than
261        # about _MARGIN_SECS seconds.
262        timestamp = make_timestamp(_TEST_EXPIRATION_AGE, False)
263        self.assertFalse(
264            job_directories.is_job_expired(
265                _TEST_EXPIRATION_AGE, timestamp))
266
267
268# When constructing sample time values for testing expiration,
269# allow this many seconds between the expiration time and the
270# current time.
271_MARGIN_SECS = 10.0
272# Test value to use for `days_old`, if nothing else is required.
273_TEST_EXPIRATION_AGE = 7
274
275
276class _MockJob(object):
277    """Class to mock the return value of `AFE.get_jobs()`."""
278    def __init__(self, created):
279        self.created_on = created
280
281
282class _MockHostQueueEntry(object):
283    """Class to mock the return value of `AFE.get_host_queue_entries()`."""
284    def __init__(self, finished):
285        self.finished_on = finished
286
287
288class _MockSpecialTask(object):
289    """Class to mock the return value of `AFE.get_special_tasks()`."""
290    def __init__(self, finished):
291        self.time_finished = finished
292
293
294@contextlib.contextmanager
295def _change_to_tempdir():
296    old_dir = os.getcwd()
297    tempdir = tempfile.mkdtemp('job_directories_unittest')
298    try:
299        os.chdir(tempdir)
300        yield
301    finally:
302        os.chdir(old_dir)
303        shutil.rmtree(tempdir)
304
305
306def make_timestamp(age_limit, is_expired):
307    """Create a timestamp for use by `job_directories.is_job_expired()`.
308
309    The timestamp will meet the syntactic requirements for
310    timestamps used as input to `is_job_expired()`.  If
311    `is_expired` is true, the timestamp will be older than
312    `age_limit` days before the current time; otherwise, the
313    date will be younger.
314
315    @param age_limit    The number of days before expiration of the
316                        target timestamp.
317    @param is_expired   Whether the timestamp should be expired
318                        relative to `age_limit`.
319
320    """
321    seconds = -_MARGIN_SECS
322    if is_expired:
323        seconds = -seconds
324    delta = datetime.timedelta(days=age_limit, seconds=seconds)
325    reference_time = datetime.datetime.now() - delta
326    return reference_time.strftime(time_utils.TIME_FMT)
327
328
329if __name__ == '__main__':
330    unittest.main()
331