You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

345 lines
12 KiB

"""Tests for job_directories."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import contextlib
import datetime
import mox
import os
import shutil
import tempfile
import unittest
import common
from autotest_lib.site_utils import job_directories
from autotest_lib.client.common_lib import time_utils
class SwarmingJobDirectoryTestCase(unittest.TestCase):
"""Tests SwarmingJobDirectory."""
def test_get_job_directories_legacy(self):
with _change_to_tempdir():
os.makedirs("swarming-3e4391423c3a4311/b")
os.mkdir("not-a-swarming-dir")
results = job_directories.SwarmingJobDirectory.get_job_directories()
self.assertEqual(set(results), {"swarming-3e4391423c3a4311"})
def test_get_job_directories(self):
with _change_to_tempdir():
os.makedirs("swarming-3e4391423c3a4310/1")
os.makedirs("swarming-3e4391423c3a4310/0")
open("swarming-3e4391423c3a4310/1/.ready_for_offload",
'w+').close()
os.makedirs("swarming-3e4391423c3a4310/a")
open("swarming-3e4391423c3a4310/a/.ready_for_offload",
'w+').close()
os.makedirs("swarming-34391423c3a4310/1/test_id")
os.makedirs("swarming-34391423c3a4310/1/test_id2")
open("swarming-34391423c3a4310/1/test_id/.ready_for_offload",
'w+').close()
open("swarming-34391423c3a4310/1/test_id2/.ready_for_offload",
'w+').close()
os.mkdir("not-a-swarming-dir")
results = job_directories.SwarmingJobDirectory.get_job_directories()
self.assertEqual(
set(results), {
"swarming-3e4391423c3a4310/1",
"swarming-3e4391423c3a4310/a",
"swarming-34391423c3a4310/1/test_id",
"swarming-34391423c3a4310/1/test_id2"
})
class GetJobIDOrTaskID(unittest.TestCase):
"""Tests get_job_id_or_task_id."""
def test_legacy_swarming_path(self):
self.assertEqual(
"3e4391423c3a4311",
job_directories.get_job_id_or_task_id(
"/autotest/results/swarming-3e4391423c3a4311"),
)
self.assertEqual(
"3e4391423c3a4311",
job_directories.get_job_id_or_task_id(
"swarming-3e4391423c3a4311"),
)
def test_swarming_path(self):
self.assertEqual(
"3e4391423c3a4311",
job_directories.get_job_id_or_task_id(
"/autotest/results/swarming-3e4391423c3a4310/1"),
)
self.assertEqual(
"3e4391423c3a431f",
job_directories.get_job_id_or_task_id(
"swarming-3e4391423c3a4310/f"),
)
class JobDirectorySubclassTests(mox.MoxTestBase):
"""Test specific to RegularJobDirectory and SpecialJobDirectory.
This provides coverage for the implementation in both
RegularJobDirectory and SpecialJobDirectory.
"""
def setUp(self):
super(JobDirectorySubclassTests, self).setUp()
self.mox.StubOutWithMock(job_directories, '_AFE')
def test_regular_job_fields(self):
"""Test the constructor for `RegularJobDirectory`.
Construct a regular job, and assert that the `dirname`
and `_id` attributes are set as expected.
"""
resultsdir = '118-fubar'
job = job_directories.RegularJobDirectory(resultsdir)
self.assertEqual(job.dirname, resultsdir)
self.assertEqual(job._id, '118')
def test_special_job_fields(self):
"""Test the constructor for `SpecialJobDirectory`.
Construct a special job, and assert that the `dirname`
and `_id` attributes are set as expected.
"""
destdir = 'hosts/host1'
resultsdir = destdir + '/118-reset'
job = job_directories.SpecialJobDirectory(resultsdir)
self.assertEqual(job.dirname, resultsdir)
self.assertEqual(job._id, '118')
def _check_finished_job(self, jobtime, hqetimes, expected):
"""Mock and test behavior of a finished job.
Initialize the mocks for a call to
`get_timestamp_if_finished()`, then simulate one call.
Assert that the returned timestamp matches the passed
in expected value.
@param jobtime Time used to construct a _MockJob object.
@param hqetimes List of times used to construct
_MockHostQueueEntry objects.
@param expected Expected time to be returned by
get_timestamp_if_finished
"""
job = job_directories.RegularJobDirectory('118-fubar')
job_directories._AFE.get_jobs(
id=job._id, finished=True).AndReturn(
[_MockJob(jobtime)])
job_directories._AFE.get_host_queue_entries(
finished_on__isnull=False,
job_id=job._id).AndReturn(
[_MockHostQueueEntry(t) for t in hqetimes])
self.mox.ReplayAll()
self.assertEqual(expected, job.get_timestamp_if_finished())
self.mox.VerifyAll()
def test_finished_regular_job(self):
"""Test getting the timestamp for a finished regular job.
Tests the return value for
`RegularJobDirectory.get_timestamp_if_finished()` when
the AFE indicates the job is finished.
"""
created_timestamp = make_timestamp(1, True)
hqe_timestamp = make_timestamp(0, True)
self._check_finished_job(created_timestamp,
[hqe_timestamp],
hqe_timestamp)
def test_finished_regular_job_multiple_hqes(self):
"""Test getting the timestamp for a regular job with multiple hqes.
Tests the return value for
`RegularJobDirectory.get_timestamp_if_finished()` when
the AFE indicates the job is finished and the job has multiple host
queue entries.
Tests that the returned timestamp is the latest timestamp in
the list of HQEs, regardless of the returned order.
"""
created_timestamp = make_timestamp(2, True)
older_hqe_timestamp = make_timestamp(1, True)
newer_hqe_timestamp = make_timestamp(0, True)
hqe_list = [older_hqe_timestamp,
newer_hqe_timestamp]
self._check_finished_job(created_timestamp,
hqe_list,
newer_hqe_timestamp)
self.mox.ResetAll()
hqe_list.reverse()
self._check_finished_job(created_timestamp,
hqe_list,
newer_hqe_timestamp)
def test_finished_regular_job_null_finished_times(self):
"""Test getting the timestamp for an aborted regular job.
Tests the return value for
`RegularJobDirectory.get_timestamp_if_finished()` when
the AFE indicates the job is finished and the job has aborted host
queue entries.
"""
timestamp = make_timestamp(0, True)
self._check_finished_job(timestamp, [], timestamp)
def test_unfinished_regular_job(self):
"""Test getting the timestamp for an unfinished regular job.
Tests the return value for
`RegularJobDirectory.get_timestamp_if_finished()` when
the AFE indicates the job is not finished.
"""
job = job_directories.RegularJobDirectory('118-fubar')
job_directories._AFE.get_jobs(
id=job._id, finished=True).AndReturn([])
self.mox.ReplayAll()
self.assertIsNone(job.get_timestamp_if_finished())
self.mox.VerifyAll()
def test_finished_special_job(self):
"""Test getting the timestamp for a finished special job.
Tests the return value for
`SpecialJobDirectory.get_timestamp_if_finished()` when
the AFE indicates the job is finished.
"""
job = job_directories.SpecialJobDirectory(
'hosts/host1/118-reset')
timestamp = make_timestamp(0, True)
job_directories._AFE.get_special_tasks(
id=job._id, is_complete=True).AndReturn(
[_MockSpecialTask(timestamp)])
self.mox.ReplayAll()
self.assertEqual(timestamp,
job.get_timestamp_if_finished())
self.mox.VerifyAll()
def test_unfinished_special_job(self):
"""Test getting the timestamp for an unfinished special job.
Tests the return value for
`SpecialJobDirectory.get_timestamp_if_finished()` when
the AFE indicates the job is not finished.
"""
job = job_directories.SpecialJobDirectory(
'hosts/host1/118-reset')
job_directories._AFE.get_special_tasks(
id=job._id, is_complete=True).AndReturn([])
self.mox.ReplayAll()
self.assertIsNone(job.get_timestamp_if_finished())
self.mox.VerifyAll()
class JobExpirationTests(unittest.TestCase):
"""Tests to exercise `job_directories.is_job_expired()`."""
def test_expired(self):
"""Test detection of an expired job."""
timestamp = make_timestamp(_TEST_EXPIRATION_AGE, True)
self.assertTrue(
job_directories.is_job_expired(
_TEST_EXPIRATION_AGE, timestamp))
def test_alive(self):
"""Test detection of a job that's not expired."""
# N.B. This test may fail if its run time exceeds more than
# about _MARGIN_SECS seconds.
timestamp = make_timestamp(_TEST_EXPIRATION_AGE, False)
self.assertFalse(
job_directories.is_job_expired(
_TEST_EXPIRATION_AGE, timestamp))
# When constructing sample time values for testing expiration,
# allow this many seconds between the expiration time and the
# current time.
_MARGIN_SECS = 10.0
# Test value to use for `days_old`, if nothing else is required.
_TEST_EXPIRATION_AGE = 7
class _MockJob(object):
"""Class to mock the return value of `AFE.get_jobs()`."""
def __init__(self, created):
self.created_on = created
class _MockHostQueueEntry(object):
"""Class to mock the return value of `AFE.get_host_queue_entries()`."""
def __init__(self, finished):
self.finished_on = finished
class _MockSpecialTask(object):
"""Class to mock the return value of `AFE.get_special_tasks()`."""
def __init__(self, finished):
self.time_finished = finished
@contextlib.contextmanager
def _change_to_tempdir():
old_dir = os.getcwd()
tempdir = tempfile.mkdtemp('job_directories_unittest')
try:
os.chdir(tempdir)
yield
finally:
os.chdir(old_dir)
shutil.rmtree(tempdir)
def make_timestamp(age_limit, is_expired):
"""Create a timestamp for use by `job_directories.is_job_expired()`.
The timestamp will meet the syntactic requirements for
timestamps used as input to `is_job_expired()`. If
`is_expired` is true, the timestamp will be older than
`age_limit` days before the current time; otherwise, the
date will be younger.
@param age_limit The number of days before expiration of the
target timestamp.
@param is_expired Whether the timestamp should be expired
relative to `age_limit`.
"""
seconds = -_MARGIN_SECS
if is_expired:
seconds = -seconds
delta = datetime.timedelta(days=age_limit, seconds=seconds)
reference_time = datetime.datetime.now() - delta
return reference_time.strftime(time_utils.TIME_FMT)
if __name__ == '__main__':
unittest.main()