blob: 9c985561ab0948bce71a2cfb1457bfbf89b2df6d [file] [log] [blame]
J. Richard Barnetteea785362014-03-17 16:00:53 -07001# Copyright 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import Queue
6import datetime
7import logging
8import os
9import shutil
J. Richard Barnette2e443ef2014-05-20 12:31:35 -070010import signal
J. Richard Barnetteea785362014-03-17 16:00:53 -070011import sys
12import tempfile
13import time
14import unittest
15
16import mox
17
18import common
19import gs_offloader
20import job_directories
21
Simran Basidd129972014-09-11 14:34:49 -070022from autotest_lib.client.common_lib import global_config
Dan Shi1b4c7c32015-10-05 10:38:57 -070023from autotest_lib.client.common_lib import time_utils
24from autotest_lib.client.common_lib import utils
J. Richard Barnetteea785362014-03-17 16:00:53 -070025from autotest_lib.scheduler import email_manager
26
Jakob Juelich24f22c22014-09-26 11:46:11 -070027
J. Richard Barnetteea785362014-03-17 16:00:53 -070028# Test value to use for `days_old`, if nothing else is required.
29_TEST_EXPIRATION_AGE = 7
30
31# When constructing sample time values for testing expiration,
32# allow this many seconds between the expiration time and the
33# current time.
34_MARGIN_SECS = 10.0
35
36
37def _get_options(argv):
38 """Helper function to exercise command line parsing.
39
40 @param argv Value of sys.argv to be parsed.
41
42 """
43 sys.argv = ['bogus.py'] + argv
44 return gs_offloader.parse_options()
45
46
Simran Basidd129972014-09-11 14:34:49 -070047class OffloaderOptionsTests(mox.MoxTestBase):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070048 """Tests for the `Offloader` constructor.
49
50 Tests that offloader instance fields are set as expected
51 for given command line options.
52
53 """
54
55 _REGULAR_ONLY = set([job_directories.RegularJobDirectory])
56 _SPECIAL_ONLY = set([job_directories.SpecialJobDirectory])
57 _BOTH = _REGULAR_ONLY | _SPECIAL_ONLY
J. Richard Barnetteea785362014-03-17 16:00:53 -070058
Jakob Juelich24f22c22014-09-26 11:46:11 -070059
Simran Basidd129972014-09-11 14:34:49 -070060 def setUp(self):
61 super(OffloaderOptionsTests, self).setUp()
62 self.mox.StubOutWithMock(utils, 'get_offload_gsuri')
Simran Basif3e305f2014-10-03 14:43:53 -070063 gs_offloader.GS_OFFLOADING_ENABLED = True
Simran Basidd129972014-09-11 14:34:49 -070064
Jakob Juelich24f22c22014-09-26 11:46:11 -070065
Simran Basidd129972014-09-11 14:34:49 -070066 def _mock_get_offload_func(self, is_moblab):
67 """Mock the process of getting the offload_dir function."""
68 if is_moblab:
69 expected_gsuri = '%sresults/%s/%s/' % (
70 global_config.global_config.get_config_value(
71 'CROS', 'image_storage_server'),
72 'Fa:ke:ma:c0:12:34', 'rand0m-uu1d')
73 else:
74 expected_gsuri = utils.DEFAULT_OFFLOAD_GSURI
75 utils.get_offload_gsuri().AndReturn(expected_gsuri)
MK Ryue93c8572015-08-11 11:53:00 -070076 offload_func = gs_offloader.get_offload_dir_func(expected_gsuri, False)
Simran Basidd129972014-09-11 14:34:49 -070077 self.mox.StubOutWithMock(gs_offloader, 'get_offload_dir_func')
MK Ryue93c8572015-08-11 11:53:00 -070078 gs_offloader.get_offload_dir_func(expected_gsuri, False).AndReturn(
Simran Basidd129972014-09-11 14:34:49 -070079 offload_func)
80 self.mox.ReplayAll()
81 return offload_func
82
Jakob Juelich24f22c22014-09-26 11:46:11 -070083
J. Richard Barnetteea785362014-03-17 16:00:53 -070084 def test_process_no_options(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070085 """Test default offloader options."""
Simran Basidd129972014-09-11 14:34:49 -070086 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070087 offloader = gs_offloader.Offloader(_get_options([]))
88 self.assertEqual(set(offloader._jobdir_classes),
89 self._REGULAR_ONLY)
90 self.assertEqual(offloader._processes, 1)
91 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -070092 offload_func)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070093 self.assertEqual(offloader._age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -070094
Jakob Juelich24f22c22014-09-26 11:46:11 -070095
J. Richard Barnetteea785362014-03-17 16:00:53 -070096 def test_process_all_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070097 """Test offloader handling for the --all option."""
Simran Basidd129972014-09-11 14:34:49 -070098 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070099 offloader = gs_offloader.Offloader(_get_options(['--all']))
100 self.assertEqual(set(offloader._jobdir_classes), self._BOTH)
101 self.assertEqual(offloader._processes, 1)
102 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700103 offload_func)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700104 self.assertEqual(offloader._age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700105
Jakob Juelich24f22c22014-09-26 11:46:11 -0700106
J. Richard Barnetteea785362014-03-17 16:00:53 -0700107 def test_process_hosts_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700108 """Test offloader handling for the --hosts option."""
Simran Basidd129972014-09-11 14:34:49 -0700109 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700110 offloader = gs_offloader.Offloader(
111 _get_options(['--hosts']))
112 self.assertEqual(set(offloader._jobdir_classes),
113 self._SPECIAL_ONLY)
114 self.assertEqual(offloader._processes, 1)
115 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700116 offload_func)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700117 self.assertEqual(offloader._age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700118
Jakob Juelich24f22c22014-09-26 11:46:11 -0700119
J. Richard Barnetteea785362014-03-17 16:00:53 -0700120 def test_parallelism_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700121 """Test offloader handling for the --parallelism option."""
Simran Basidd129972014-09-11 14:34:49 -0700122 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700123 offloader = gs_offloader.Offloader(
124 _get_options(['--parallelism', '2']))
125 self.assertEqual(set(offloader._jobdir_classes),
126 self._REGULAR_ONLY)
127 self.assertEqual(offloader._processes, 2)
128 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700129 offload_func)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700130 self.assertEqual(offloader._age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700131
Jakob Juelich24f22c22014-09-26 11:46:11 -0700132
J. Richard Barnetteea785362014-03-17 16:00:53 -0700133 def test_delete_only_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700134 """Test offloader handling for the --delete_only option."""
135 offloader = gs_offloader.Offloader(
136 _get_options(['--delete_only']))
137 self.assertEqual(set(offloader._jobdir_classes),
138 self._REGULAR_ONLY)
139 self.assertEqual(offloader._processes, 1)
140 self.assertEqual(offloader._offload_func,
141 gs_offloader.delete_files)
142 self.assertEqual(offloader._age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700143
Jakob Juelich24f22c22014-09-26 11:46:11 -0700144
Simran Basidf4751e2014-10-10 14:19:22 -0700145 def test_days_old_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700146 """Test offloader handling for the --days_old option."""
Simran Basidd129972014-09-11 14:34:49 -0700147 offload_func = self._mock_get_offload_func(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700148 offloader = gs_offloader.Offloader(
149 _get_options(['--days_old', '7']))
150 self.assertEqual(set(offloader._jobdir_classes),
151 self._REGULAR_ONLY)
152 self.assertEqual(offloader._processes, 1)
153 self.assertEqual(offloader._offload_func,
Simran Basidd129972014-09-11 14:34:49 -0700154 offload_func)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700155 self.assertEqual(offloader._age_limit, 7)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700156
Jakob Juelich24f22c22014-09-26 11:46:11 -0700157
Simran Basidd129972014-09-11 14:34:49 -0700158 def test_moblab_gsuri_generation(self):
159 """Test offloader construction for Moblab."""
160 offload_func = self._mock_get_offload_func(True)
161 offloader = gs_offloader.Offloader(_get_options([]))
162 self.assertEqual(set(offloader._jobdir_classes),
163 self._REGULAR_ONLY)
164 self.assertEqual(offloader._processes, 1)
165 self.assertEqual(offloader._offload_func,
166 offload_func)
167 self.assertEqual(offloader._age_limit, 0)
168
J. Richard Barnetteea785362014-03-17 16:00:53 -0700169
Simran Basif3e305f2014-10-03 14:43:53 -0700170 def test_globalconfig_offloading_flag(self):
171 """Test enabling of --delete_only via global_config."""
172 gs_offloader.GS_OFFLOADING_ENABLED = False
173 offloader = gs_offloader.Offloader(
174 _get_options([]))
175 self.assertEqual(offloader._offload_func,
176 gs_offloader.delete_files)
177
178
J. Richard Barnetteea785362014-03-17 16:00:53 -0700179def _make_timestamp(age_limit, is_expired):
180 """Create a timestamp for use by `job_directories._is_job_expired()`.
181
182 The timestamp will meet the syntactic requirements for
183 timestamps used as input to `_is_job_expired()`. If
184 `is_expired` is true, the timestamp will be older than
185 `age_limit` days before the current time; otherwise, the
186 date will be younger.
187
188 @param age_limit The number of days before expiration of the
189 target timestamp.
190 @param is_expired Whether the timestamp should be expired
191 relative to `age_limit`.
192
193 """
194 seconds = -_MARGIN_SECS
195 if is_expired:
196 seconds = -seconds
197 delta = datetime.timedelta(days=age_limit, seconds=seconds)
198 reference_time = datetime.datetime.now() - delta
Dan Shidfea3682014-08-10 23:38:40 -0700199 return reference_time.strftime(time_utils.TIME_FMT)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700200
201
202class JobExpirationTests(unittest.TestCase):
203 """Tests to exercise `job_directories._is_job_expired()`."""
204
205 def test_expired(self):
206 """Test detection of an expired job."""
207 timestamp = _make_timestamp(_TEST_EXPIRATION_AGE, True)
208 self.assertTrue(
209 job_directories._is_job_expired(
210 _TEST_EXPIRATION_AGE, timestamp))
211
212
213 def test_alive(self):
214 """Test detection of a job that's not expired."""
215 # N.B. This test may fail if its run time exceeds more than
216 # about _MARGIN_SECS seconds.
217 timestamp = _make_timestamp(_TEST_EXPIRATION_AGE, False)
218 self.assertFalse(
219 job_directories._is_job_expired(
220 _TEST_EXPIRATION_AGE, timestamp))
221
222
223class _MockJobDirectory(job_directories._JobDirectory):
224 """Subclass of `_JobDirectory` used as a helper for tests."""
225
226 GLOB_PATTERN = '[0-9]*-*'
227
Jakob Juelich24f22c22014-09-26 11:46:11 -0700228
J. Richard Barnetteea785362014-03-17 16:00:53 -0700229 def __init__(self, resultsdir):
230 """Create new job in initial state."""
231 super(_MockJobDirectory, self).__init__(resultsdir)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700232 self._timestamp = None
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700233 self.queue_args = [resultsdir, os.path.dirname(resultsdir)]
J. Richard Barnetteea785362014-03-17 16:00:53 -0700234
Jakob Juelich24f22c22014-09-26 11:46:11 -0700235
J. Richard Barnetteea785362014-03-17 16:00:53 -0700236 def get_timestamp_if_finished(self):
237 return self._timestamp
238
Jakob Juelich24f22c22014-09-26 11:46:11 -0700239
J. Richard Barnetteea785362014-03-17 16:00:53 -0700240 def set_finished(self, days_old):
241 """Make this job appear to be finished.
242
243 After calling this function, calls to `enqueue_offload()`
244 will find this job as finished, but not expired and ready
245 for offload. Note that when `days_old` is 0,
246 `enqueue_offload()` will treat a finished job as eligible
247 for offload.
248
249 @param days_old The value of the `days_old` parameter that
250 will be passed to `enqueue_offload()` for
251 testing.
252
253 """
254 self._timestamp = _make_timestamp(days_old, False)
255
Jakob Juelich24f22c22014-09-26 11:46:11 -0700256
J. Richard Barnetteea785362014-03-17 16:00:53 -0700257 def set_expired(self, days_old):
258 """Make this job eligible to be offloaded.
259
260 After calling this function, calls to `offload` will attempt
261 to offload this job.
262
263 @param days_old The value of the `days_old` parameter that
264 will be passed to `enqueue_offload()` for
265 testing.
266
267 """
268 self._timestamp = _make_timestamp(days_old, True)
269
Jakob Juelich24f22c22014-09-26 11:46:11 -0700270
J. Richard Barnetteea785362014-03-17 16:00:53 -0700271 def set_incomplete(self):
272 """Make this job appear to have failed offload just once."""
273 self._offload_count += 1
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700274 self._first_offload_start = time.time()
J. Richard Barnetteea785362014-03-17 16:00:53 -0700275 if not os.path.isdir(self._dirname):
276 os.mkdir(self._dirname)
277
Jakob Juelich24f22c22014-09-26 11:46:11 -0700278
J. Richard Barnetteea785362014-03-17 16:00:53 -0700279 def set_reportable(self):
280 """Make this job be reportable."""
J. Richard Barnetteea785362014-03-17 16:00:53 -0700281 self.set_incomplete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700282 self._offload_count += 1
J. Richard Barnetteea785362014-03-17 16:00:53 -0700283
Jakob Juelich24f22c22014-09-26 11:46:11 -0700284
J. Richard Barnetteea785362014-03-17 16:00:53 -0700285 def set_complete(self):
286 """Make this job be completed."""
287 self._offload_count += 1
288 if os.path.isdir(self._dirname):
289 os.rmdir(self._dirname)
290
291
Simran Basi1e10e922015-04-16 15:09:56 -0700292 def process_gs_instructions(self):
293 """Always still offload the job directory."""
294 return True
295
296
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700297class CommandListTests(unittest.TestCase):
298 """Tests for `get_cmd_list()`."""
299
MK Ryue93c8572015-08-11 11:53:00 -0700300 def _command_list_assertions(self, job, use_rsync=True, multi=False):
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700301 """Call `get_cmd_list()` and check the return value.
302
303 Check the following assertions:
304 * The command name (argv[0]) is 'gsutil'.
MK Ryue93c8572015-08-11 11:53:00 -0700305 * '-m' option (argv[1]) is on when the argument, multi, is True.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700306 * The arguments contain the 'cp' subcommand.
307 * The next-to-last argument (the source directory) is the
308 job's `queue_args[0]`.
Simran Basidd129972014-09-11 14:34:49 -0700309 * The last argument (the destination URL) is the job's
310 'queue_args[1]'.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700311
312 @param job A job with properly calculated arguments to
313 `get_cmd_list()`
MK Ryue93c8572015-08-11 11:53:00 -0700314 @param use_rsync True when using 'rsync'. False when using 'cp'.
315 @param multi True when using '-m' option for gsutil.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700316
317 """
Jakob Juelich24f22c22014-09-26 11:46:11 -0700318 test_bucket_uri = 'gs://a-test-bucket'
319
320 gs_offloader.USE_RSYNC_ENABLED = use_rsync
321
322 command = gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700323 multi, job.queue_args[0],
324 os.path.join(test_bucket_uri, job.queue_args[1]))
Jakob Juelich24f22c22014-09-26 11:46:11 -0700325
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700326 self.assertEqual(command[0], 'gsutil')
MK Ryue93c8572015-08-11 11:53:00 -0700327 if multi:
328 self.assertEqual(command[1], '-m')
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700329 self.assertEqual(command[-2], job.queue_args[0])
Jakob Juelich24f22c22014-09-26 11:46:11 -0700330
331 if use_rsync:
332 self.assertTrue('rsync' in command)
333 self.assertEqual(command[-1],
334 os.path.join(test_bucket_uri, job.queue_args[0]))
335 else:
336 self.assertTrue('cp' in command)
337 self.assertEqual(command[-1],
338 os.path.join(test_bucket_uri, job.queue_args[1]))
339
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700340
341 def test_get_cmd_list_regular(self):
342 """Test `get_cmd_list()` as for a regular job."""
343 job = _MockJobDirectory('118-debug')
344 self._command_list_assertions(job)
345
Jakob Juelich24f22c22014-09-26 11:46:11 -0700346
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700347 def test_get_cmd_list_special(self):
348 """Test `get_cmd_list()` as for a special job."""
349 job = _MockJobDirectory('hosts/host1/118-reset')
350 self._command_list_assertions(job)
351
352
Jakob Juelich24f22c22014-09-26 11:46:11 -0700353 def test_get_cmd_list_regular_no_rsync(self):
354 """Test `get_cmd_list()` as for a regular job."""
355 job = _MockJobDirectory('118-debug')
356 self._command_list_assertions(job, use_rsync=False)
357
358
359 def test_get_cmd_list_special_no_rsync(self):
360 """Test `get_cmd_list()` as for a special job."""
361 job = _MockJobDirectory('hosts/host1/118-reset')
362 self._command_list_assertions(job, use_rsync=False)
363
364
MK Ryue93c8572015-08-11 11:53:00 -0700365 def test_get_cmd_list_regular_multi(self):
366 """Test `get_cmd_list()` as for a regular job with True multi."""
367 job = _MockJobDirectory('118-debug')
368 self._command_list_assertions(job, multi=True)
369
370
371 def test_get_cmd_list_special_multi(self):
372 """Test `get_cmd_list()` as for a special job with True multi."""
373 job = _MockJobDirectory('hosts/host1/118-reset')
374 self._command_list_assertions(job, multi=True)
375
376
J. Richard Barnetteea785362014-03-17 16:00:53 -0700377# Below is partial sample of e-mail notification text. This text is
378# deliberately hard-coded and then parsed to create the test data;
379# the idea is to make sure the actual text format will be reviewed
380# by a human being.
381#
382# first offload count directory
383# --+----1----+---- ----+ ----+----1----+----2----+----3
384_SAMPLE_DIRECTORIES_REPORT = '''\
385=================== ====== ==============================
3862014-03-14 15:09:26 1 118-fubar
3872014-03-14 15:19:23 2 117-fubar
3882014-03-14 15:29:20 6 116-fubar
3892014-03-14 15:39:17 24 115-fubar
3902014-03-14 15:49:14 120 114-fubar
3912014-03-14 15:59:11 720 113-fubar
3922014-03-14 16:09:08 5040 112-fubar
3932014-03-14 16:19:05 40320 111-fubar
394'''
395
396
397class EmailTemplateTests(mox.MoxTestBase):
398 """Test the formatting of e-mail notifications."""
399
400 def setUp(self):
401 super(EmailTemplateTests, self).setUp()
402 self.mox.StubOutWithMock(email_manager.manager,
403 'send_email')
404 self._joblist = []
405 for line in _SAMPLE_DIRECTORIES_REPORT.split('\n')[1 : -1]:
406 date_, time_, count, dir_ = line.split()
407 job = _MockJobDirectory(dir_)
408 job._offload_count = int(count)
Dan Shidfea3682014-08-10 23:38:40 -0700409 timestruct = time.strptime("%s %s" % (date_, time_),
410 gs_offloader.ERROR_EMAIL_TIME_FORMAT)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700411 job._first_offload_start = time.mktime(timestruct)
412 # enter the jobs in reverse order, to make sure we
413 # test that the output will be sorted.
414 self._joblist.insert(0, job)
415
Jakob Juelich24f22c22014-09-26 11:46:11 -0700416
J. Richard Barnetteea785362014-03-17 16:00:53 -0700417 def test_email_template(self):
418 """Trigger an e-mail report and check its contents."""
419 # The last line of the report is a separator that we
420 # repeat in the first line of our expected result data.
421 # So, we remove that separator from the end of the of
422 # the e-mail report message.
423 #
424 # The last element in the list returned by split('\n')
425 # will be an empty string, so to remove the separator,
426 # we remove the next-to-last entry in the list.
427 report_lines = gs_offloader.ERROR_EMAIL_REPORT_FORMAT.split('\n')
428 expected_message = ('\n'.join(report_lines[: -2] +
429 report_lines[-1 :]) +
430 _SAMPLE_DIRECTORIES_REPORT)
431 email_manager.manager.send_email(
432 mox.IgnoreArg(), mox.IgnoreArg(), expected_message)
433 self.mox.ReplayAll()
434 gs_offloader.report_offload_failures(self._joblist)
435
436
Kevin Cheng686ae8c2015-09-09 11:56:38 -0700437 def test_email_url(self):
438 """Check that the expected helper url is in the email header."""
439 self.assertIn(gs_offloader.ERROR_EMAIL_HELPER_URL,
440 gs_offloader.ERROR_EMAIL_REPORT_FORMAT)
441
442
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700443class _MockJob(object):
444 """Class to mock the return value of `AFE.get_jobs()`."""
445 def __init__(self, created):
446 self.created_on = created
447
448
449class _MockHostQueueEntry(object):
450 """Class to mock the return value of `AFE.get_host_queue_entries()`."""
451 def __init__(self, finished):
452 self.finished_on = finished
453
454
455class _MockSpecialTask(object):
456 """Class to mock the return value of `AFE.get_special_tasks()`."""
457 def __init__(self, finished):
458 self.time_finished = finished
459
460
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700461class JobDirectorySubclassTests(mox.MoxTestBase):
462 """Test specific to RegularJobDirectory and SpecialJobDirectory.
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700463
464 This provides coverage for the implementation in both
465 RegularJobDirectory and SpecialJobDirectory.
466
467 """
468
469 def setUp(self):
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700470 super(JobDirectorySubclassTests, self).setUp()
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700471 self.mox.StubOutWithMock(job_directories._AFE, 'get_jobs')
472 self.mox.StubOutWithMock(job_directories._AFE,
473 'get_host_queue_entries')
474 self.mox.StubOutWithMock(job_directories._AFE,
475 'get_special_tasks')
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700476
Jakob Juelich24f22c22014-09-26 11:46:11 -0700477
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700478 def test_regular_job_fields(self):
479 """Test the constructor for `RegularJobDirectory`.
480
481 Construct a regular job, and assert that the `_dirname`
482 and `_id` attributes are set as expected.
483
484 """
485 resultsdir = '118-fubar'
486 job = job_directories.RegularJobDirectory(resultsdir)
487 self.assertEqual(job._dirname, resultsdir)
Dan Shicf4d2032015-03-12 15:04:21 -0700488 self.assertEqual(job._id, 118)
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700489
Jakob Juelich24f22c22014-09-26 11:46:11 -0700490
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700491 def test_special_job_fields(self):
492 """Test the constructor for `SpecialJobDirectory`.
493
494 Construct a special job, and assert that the `_dirname`
495 and `_id` attributes are set as expected.
496
497 """
498 destdir = 'hosts/host1'
499 resultsdir = destdir + '/118-reset'
500 job = job_directories.SpecialJobDirectory(resultsdir)
501 self.assertEqual(job._dirname, resultsdir)
Dan Shicf4d2032015-03-12 15:04:21 -0700502 self.assertEqual(job._id, 118)
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700503
Jakob Juelich24f22c22014-09-26 11:46:11 -0700504
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700505 def _check_finished_job(self, jobtime, hqetimes, expected):
506 """Mock and test behavior of a finished job.
507
508 Initialize the mocks for a call to
509 `get_timestamp_if_finished()`, then simulate one call.
510 Assert that the returned timestamp matches the passed
511 in expected value.
512
513 @param jobtime Time used to construct a _MockJob object.
514 @param hqetimes List of times used to construct
515 _MockHostQueueEntry objects.
516 @param expected Expected time to be returned by
517 get_timestamp_if_finished
518
519 """
520 job = job_directories.RegularJobDirectory('118-fubar')
521 job_directories._AFE.get_jobs(
522 id=job._id, finished=True).AndReturn(
523 [_MockJob(jobtime)])
524 job_directories._AFE.get_host_queue_entries(
525 finished_on__isnull=False,
526 job_id=job._id).AndReturn(
527 [_MockHostQueueEntry(t) for t in hqetimes])
528 self.mox.ReplayAll()
529 self.assertEqual(expected, job.get_timestamp_if_finished())
530 self.mox.VerifyAll()
531
532
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700533 def test_finished_regular_job(self):
534 """Test getting the timestamp for a finished regular job.
535
536 Tests the return value for
537 `RegularJobDirectory.get_timestamp_if_finished()` when
538 the AFE indicates the job is finished.
539
540 """
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700541 created_timestamp = _make_timestamp(1, True)
542 hqe_timestamp = _make_timestamp(0, True)
543 self._check_finished_job(created_timestamp,
544 [hqe_timestamp],
545 hqe_timestamp)
Simran Basifb98e462014-08-18 12:35:44 -0700546
Jakob Juelich24f22c22014-09-26 11:46:11 -0700547
Simran Basifb98e462014-08-18 12:35:44 -0700548 def test_finished_regular_job_multiple_hqes(self):
549 """Test getting the timestamp for a regular job with multiple hqes.
550
551 Tests the return value for
552 `RegularJobDirectory.get_timestamp_if_finished()` when
553 the AFE indicates the job is finished and the job has multiple host
554 queue entries.
555
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700556 Tests that the returned timestamp is the latest timestamp in
557 the list of HQEs, regardless of the returned order.
558
Simran Basifb98e462014-08-18 12:35:44 -0700559 """
Simran Basifb98e462014-08-18 12:35:44 -0700560 created_timestamp = _make_timestamp(2, True)
561 older_hqe_timestamp = _make_timestamp(1, True)
562 newer_hqe_timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700563 hqe_list = [older_hqe_timestamp,
564 newer_hqe_timestamp]
565 self._check_finished_job(created_timestamp,
566 hqe_list,
567 newer_hqe_timestamp)
568 self.mox.ResetAll()
569 hqe_list.reverse()
570 self._check_finished_job(created_timestamp,
571 hqe_list,
572 newer_hqe_timestamp)
Simran Basifb98e462014-08-18 12:35:44 -0700573
Jakob Juelich24f22c22014-09-26 11:46:11 -0700574
Simran Basifb98e462014-08-18 12:35:44 -0700575 def test_finished_regular_job_null_finished_times(self):
576 """Test getting the timestamp for an aborted regular job.
577
578 Tests the return value for
579 `RegularJobDirectory.get_timestamp_if_finished()` when
580 the AFE indicates the job is finished and the job has aborted host
581 queue entries.
582
583 """
Simran Basifb98e462014-08-18 12:35:44 -0700584 timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700585 self._check_finished_job(timestamp, [], timestamp)
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700586
Jakob Juelich24f22c22014-09-26 11:46:11 -0700587
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700588 def test_unfinished_regular_job(self):
589 """Test getting the timestamp for an unfinished regular job.
590
591 Tests the return value for
592 `RegularJobDirectory.get_timestamp_if_finished()` when
593 the AFE indicates the job is not finished.
594
595 """
596 job = job_directories.RegularJobDirectory('118-fubar')
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700597 job_directories._AFE.get_jobs(
598 id=job._id, finished=True).AndReturn([])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700599 self.mox.ReplayAll()
600 self.assertIsNone(job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700601 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700602
Jakob Juelich24f22c22014-09-26 11:46:11 -0700603
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700604 def test_finished_special_job(self):
605 """Test getting the timestamp for a finished special job.
606
607 Tests the return value for
608 `SpecialJobDirectory.get_timestamp_if_finished()` when
609 the AFE indicates the job is finished.
610
611 """
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700612 job = job_directories.SpecialJobDirectory(
613 'hosts/host1/118-reset')
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700614 timestamp = _make_timestamp(0, True)
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700615 job_directories._AFE.get_special_tasks(
616 id=job._id, is_complete=True).AndReturn(
617 [_MockSpecialTask(timestamp)])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700618 self.mox.ReplayAll()
619 self.assertEqual(timestamp,
620 job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700621 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700622
Jakob Juelich24f22c22014-09-26 11:46:11 -0700623
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700624 def test_unfinished_special_job(self):
625 """Test getting the timestamp for an unfinished special job.
626
627 Tests the return value for
628 `SpecialJobDirectory.get_timestamp_if_finished()` when
629 the AFE indicates the job is not finished.
630
631 """
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700632 job = job_directories.SpecialJobDirectory(
633 'hosts/host1/118-reset')
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700634 job_directories._AFE.get_special_tasks(
635 id=job._id, is_complete=True).AndReturn([])
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700636 self.mox.ReplayAll()
637 self.assertIsNone(job.get_timestamp_if_finished())
J. Richard Barnettedd0227d2015-04-10 15:18:48 -0700638 self.mox.VerifyAll()
J. Richard Barnette3e3ed6a2014-05-19 07:59:00 -0700639
640
J. Richard Barnetteea785362014-03-17 16:00:53 -0700641class _TempResultsDirTestBase(mox.MoxTestBase):
642 """Base class for tests using a temporary results directory."""
643
J. Richard Barnette08800322014-05-16 14:49:46 -0700644 REGULAR_JOBLIST = [
645 '111-fubar', '112-fubar', '113-fubar', '114-snafu']
646 HOST_LIST = ['host1', 'host2', 'host3']
647 SPECIAL_JOBLIST = [
648 'hosts/host1/333-reset', 'hosts/host1/334-reset',
649 'hosts/host2/444-reset', 'hosts/host3/555-reset']
650
Jakob Juelich24f22c22014-09-26 11:46:11 -0700651
J. Richard Barnetteea785362014-03-17 16:00:53 -0700652 def setUp(self):
653 super(_TempResultsDirTestBase, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -0700654 self._resultsroot = tempfile.mkdtemp()
655 self._cwd = os.getcwd()
656 os.chdir(self._resultsroot)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700657
Jakob Juelich24f22c22014-09-26 11:46:11 -0700658
J. Richard Barnetteea785362014-03-17 16:00:53 -0700659 def tearDown(self):
J. Richard Barnette08800322014-05-16 14:49:46 -0700660 os.chdir(self._cwd)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700661 shutil.rmtree(self._resultsroot)
662 super(_TempResultsDirTestBase, self).tearDown()
663
Jakob Juelich24f22c22014-09-26 11:46:11 -0700664
J. Richard Barnetteea785362014-03-17 16:00:53 -0700665 def make_job(self, jobdir):
666 """Create a job with results in `self._resultsroot`.
667
668 @param jobdir Name of the subdirectory to be created in
669 `self._resultsroot`.
670
671 """
J. Richard Barnette08800322014-05-16 14:49:46 -0700672 os.mkdir(jobdir)
673 return _MockJobDirectory(jobdir)
674
Jakob Juelich24f22c22014-09-26 11:46:11 -0700675
J. Richard Barnette08800322014-05-16 14:49:46 -0700676 def make_job_hierarchy(self):
677 """Create a sample hierarchy of job directories.
678
679 `self.REGULAR_JOBLIST` is a list of directories for regular
680 jobs to be created; `self.SPECIAL_JOBLIST` is a list of
681 directories for special jobs to be created.
682
683 """
684 for d in self.REGULAR_JOBLIST:
685 os.mkdir(d)
686 hostsdir = 'hosts'
687 os.mkdir(hostsdir)
688 for host in self.HOST_LIST:
689 os.mkdir(os.path.join(hostsdir, host))
690 for d in self.SPECIAL_JOBLIST:
691 os.mkdir(d)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700692
693
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700694class OffloadDirectoryTests(_TempResultsDirTestBase):
695 """Tests for `offload_dir()`."""
696
697 def setUp(self):
698 super(OffloadDirectoryTests, self).setUp()
699 # offload_dir() logs messages; silence them.
700 self._saved_loglevel = logging.getLogger().getEffectiveLevel()
701 logging.getLogger().setLevel(logging.CRITICAL+1)
702 self._job = self.make_job(self.REGULAR_JOBLIST[0])
703 self.mox.StubOutWithMock(gs_offloader, 'get_cmd_list')
704 self.mox.StubOutWithMock(signal, 'alarm')
705
Jakob Juelich24f22c22014-09-26 11:46:11 -0700706
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700707 def tearDown(self):
708 logging.getLogger().setLevel(self._saved_loglevel)
709 super(OffloadDirectoryTests, self).tearDown()
710
Jakob Juelich24f22c22014-09-26 11:46:11 -0700711
Simran Basidd129972014-09-11 14:34:49 -0700712 def _mock_offload_dir_calls(self, command, queue_args):
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700713 """Mock out the calls needed by `offload_dir()`.
714
715 This covers only the calls made when there is no timeout.
716
717 @param command Command list to be returned by the mocked
718 call to `get_cmd_list()`.
719
720 """
721 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS)
Simran Basidd129972014-09-11 14:34:49 -0700722 command.append(queue_args[0])
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700723 gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700724 False, queue_args[0],
725 '%s%s' % (utils.DEFAULT_OFFLOAD_GSURI,
726 queue_args[1])).AndReturn(command)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700727 signal.alarm(0)
728 signal.alarm(0)
729
Jakob Juelich24f22c22014-09-26 11:46:11 -0700730
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700731 def _run_offload_dir(self, should_succeed):
732 """Make one call to `offload_dir()`.
733
734 The caller ensures all mocks are set up already.
735
736 @param should_succeed True iff the call to `offload_dir()`
737 is expected to succeed and remove the
738 offloaded job directory.
739
740 """
741 self.mox.ReplayAll()
Simran Basidd129972014-09-11 14:34:49 -0700742 gs_offloader.get_offload_dir_func(
MK Ryue93c8572015-08-11 11:53:00 -0700743 utils.DEFAULT_OFFLOAD_GSURI, False)(
744 self._job.queue_args[0],
745 self._job.queue_args[1])
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700746 self.mox.VerifyAll()
747 self.assertEqual(not should_succeed,
748 os.path.isdir(self._job.queue_args[0]))
749
Jakob Juelich24f22c22014-09-26 11:46:11 -0700750
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700751 def test_offload_success(self):
752 """Test that `offload_dir()` can succeed correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700753 self._mock_offload_dir_calls(['test', '-d'],
754 self._job.queue_args)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700755 self._run_offload_dir(True)
756
Jakob Juelich24f22c22014-09-26 11:46:11 -0700757
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700758 def test_offload_failure(self):
759 """Test that `offload_dir()` can fail correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700760 self._mock_offload_dir_calls(['test', '!', '-d'],
761 self._job.queue_args)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700762 self._run_offload_dir(False)
763
Jakob Juelich24f22c22014-09-26 11:46:11 -0700764
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700765 def test_offload_timeout_early(self):
766 """Test that `offload_dir()` times out correctly.
767
768 This test triggers timeout at the earliest possible moment,
769 at the first call to set the timeout alarm.
770
771 """
772 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS).AndRaise(
773 gs_offloader.TimeoutException('fubar'))
774 signal.alarm(0)
775 self._run_offload_dir(False)
776
Jakob Juelich24f22c22014-09-26 11:46:11 -0700777
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700778 def test_offload_timeout_late(self):
779 """Test that `offload_dir()` times out correctly.
780
781 This test triggers timeout at the latest possible moment, at
782 the call to clear the timeout alarm.
783
784 """
785 signal.alarm(gs_offloader.OFFLOAD_TIMEOUT_SECS)
786 gs_offloader.get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700787 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700788 ['test', '-d', self._job.queue_args[0]])
789 signal.alarm(0).AndRaise(
790 gs_offloader.TimeoutException('fubar'))
791 signal.alarm(0)
792 self._run_offload_dir(False)
793
794
Dan Shiaffb9222015-04-15 17:05:47 -0700795 def test_sanitize_dir(self):
796 """Test that folder/file name with invalid character can be corrected.
797 """
798 results_folder = tempfile.mkdtemp()
799 invalid_chars = '_'.join(gs_offloader.INVALID_GS_CHARS)
800 invalid_files = []
801 invalid_folder = os.path.join(
802 results_folder,
803 'invalid_name_folder_%s' % invalid_chars)
804 invalid_files.append(os.path.join(
805 invalid_folder,
806 'invalid_name_file_%s' % invalid_chars))
807 for r in gs_offloader.INVALID_GS_CHAR_RANGE:
808 for c in range(r[0], r[1]+1):
809 # NULL cannot be in file name.
810 if c != 0:
811 invalid_files.append(os.path.join(
812 invalid_folder,
813 'invalid_name_file_%s' % chr(c)))
814 good_folder = os.path.join(results_folder, 'valid_name_folder')
815 good_file = os.path.join(good_folder, 'valid_name_file')
816 for folder in [invalid_folder, good_folder]:
817 os.makedirs(folder)
818 for f in invalid_files + [good_file]:
819 with open(f, 'w'):
820 pass
821 gs_offloader.sanitize_dir(results_folder)
822 for _, dirs, files in os.walk(results_folder):
823 for name in dirs + files:
824 self.assertEqual(name, gs_offloader.get_sanitized_name(name))
825 for c in name:
826 self.assertFalse(c in gs_offloader.INVALID_GS_CHARS)
827 for r in gs_offloader.INVALID_GS_CHAR_RANGE:
828 self.assertFalse(ord(c) >= r[0] and ord(c) <= r[1])
829 self.assertTrue(os.path.exists(good_file))
830 shutil.rmtree(results_folder)
831
832
Dan Shi1b4c7c32015-10-05 10:38:57 -0700833 def check_limit_file_count(self, is_test_job=True):
834 """Test that folder with too many files can be compressed.
835
836 @param is_test_job: True to check the method with test job result
837 folder. Set to False for special task folder.
838 """
839 results_folder = tempfile.mkdtemp()
840 host_folder = os.path.join(
841 results_folder,
842 'lab1-host1' if is_test_job else 'hosts/lab1-host1/1-repair')
843 debug_folder = os.path.join(host_folder, 'debug')
844 sysinfo_folder = os.path.join(host_folder, 'sysinfo')
845 for folder in [debug_folder, sysinfo_folder]:
846 os.makedirs(folder)
847 for i in range(10):
848 with open(os.path.join(folder, str(i)), 'w') as f:
849 f.write('test')
850
851 gs_offloader.MAX_FILE_COUNT = 100
852 gs_offloader.limit_file_count(
853 results_folder if is_test_job else host_folder)
854 self.assertTrue(os.path.exists(sysinfo_folder))
855
856 gs_offloader.MAX_FILE_COUNT = 10
857 gs_offloader.limit_file_count(
858 results_folder if is_test_job else host_folder)
859 self.assertFalse(os.path.exists(sysinfo_folder))
860 self.assertTrue(os.path.exists(sysinfo_folder + '.tgz'))
861 self.assertTrue(os.path.exists(debug_folder))
862
863 shutil.rmtree(results_folder)
864
865
866 def test_limit_file_count(self):
867 """Test that folder with too many files can be compressed.
868 """
869 self.check_limit_file_count(is_test_job=True)
870 self.check_limit_file_count(is_test_job=False)
871
872
J. Richard Barnetteea785362014-03-17 16:00:53 -0700873class JobDirectoryOffloadTests(_TempResultsDirTestBase):
874 """Tests for `_JobDirectory.enqueue_offload()`.
875
876 When testing with a `days_old` parameter of 0, we use
877 `set_finished()` instead of `set_expired()`. This causes the
878 job's timestamp to be set in the future. This is done so as
879 to test that when `days_old` is 0, the job is always treated
880 as eligible for offload, regardless of the timestamp's value.
881
882 Testing covers the following assertions:
883 A. Each time `enqueue_offload()` is called, a message that
884 includes the job's directory name will be logged using
885 `logging.debug()`, regardless of whether the job was
886 enqueued. Nothing else is allowed to be logged.
887 B. If the job is not eligible to be offloaded,
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700888 `get_failure_time()` and `get_failure_count()` are 0.
J. Richard Barnetteea785362014-03-17 16:00:53 -0700889 C. If the job is not eligible for offload, nothing is
890 enqueued in `queue`.
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700891 D. When the job is offloaded, `get_failure_count()` increments
J. Richard Barnetteea785362014-03-17 16:00:53 -0700892 each time.
893 E. When the job is offloaded, the appropriate parameters are
894 enqueued exactly once.
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700895 F. The first time a job is offloaded, `get_failure_time()` is
J. Richard Barnetteea785362014-03-17 16:00:53 -0700896 set to the current time.
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700897 G. `get_failure_time()` only changes the first time that the
J. Richard Barnetteea785362014-03-17 16:00:53 -0700898 job is offloaded.
899
900 The test cases below are designed to exercise all of the
901 meaningful state transitions at least once.
902
903 """
904
905 def setUp(self):
906 super(JobDirectoryOffloadTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -0700907 self._job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -0700908 self._queue = Queue.Queue()
J. Richard Barnetteea785362014-03-17 16:00:53 -0700909
Jakob Juelich24f22c22014-09-26 11:46:11 -0700910
J. Richard Barnetteea785362014-03-17 16:00:53 -0700911 def _offload_unexpired_job(self, days_old):
912 """Make calls to `enqueue_offload()` for an unexpired job.
913
914 This method tests assertions B and C that calling
915 `enqueue_offload()` has no effect.
916
917 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700918 self.assertEqual(self._job.get_failure_count(), 0)
919 self.assertEqual(self._job.get_failure_time(), 0)
920 self._job.enqueue_offload(self._queue, days_old)
921 self._job.enqueue_offload(self._queue, days_old)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700922 self.assertTrue(self._queue.empty())
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700923 self.assertEqual(self._job.get_failure_count(), 0)
924 self.assertEqual(self._job.get_failure_time(), 0)
925 self.assertFalse(self._job.is_reportable())
J. Richard Barnetteea785362014-03-17 16:00:53 -0700926
Jakob Juelich24f22c22014-09-26 11:46:11 -0700927
J. Richard Barnetteea785362014-03-17 16:00:53 -0700928 def _offload_expired_once(self, days_old, count):
929 """Make one call to `enqueue_offload()` for an expired job.
930
931 This method tests assertions D and E regarding side-effects
932 expected when a job is offloaded.
933
934 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700935 self._job.enqueue_offload(self._queue, days_old)
936 self.assertEqual(self._job.get_failure_count(), count)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700937 self.assertFalse(self._queue.empty())
938 v = self._queue.get_nowait()
939 self.assertTrue(self._queue.empty())
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -0700940 self.assertEqual(v, self._job.queue_args)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700941
Jakob Juelich24f22c22014-09-26 11:46:11 -0700942
J. Richard Barnetteea785362014-03-17 16:00:53 -0700943 def _offload_expired_job(self, days_old):
944 """Make calls to `enqueue_offload()` for a just-expired job.
945
946 This method directly tests assertions F and G regarding
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700947 side-effects on `get_failure_time()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -0700948
949 """
950 t0 = time.time()
951 self._offload_expired_once(days_old, 1)
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700952 self.assertFalse(self._job.is_reportable())
953 t1 = self._job.get_failure_time()
J. Richard Barnetteea785362014-03-17 16:00:53 -0700954 self.assertLessEqual(t1, time.time())
955 self.assertGreaterEqual(t1, t0)
956 self._offload_expired_once(days_old, 2)
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700957 self.assertTrue(self._job.is_reportable())
958 self.assertEqual(self._job.get_failure_time(), t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700959 self._offload_expired_once(days_old, 3)
J. Richard Barnette22dd7482014-06-23 12:25:02 -0700960 self.assertTrue(self._job.is_reportable())
961 self.assertEqual(self._job.get_failure_time(), t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700962
Jakob Juelich24f22c22014-09-26 11:46:11 -0700963
J. Richard Barnetteea785362014-03-17 16:00:53 -0700964 def test_case_1_no_expiration(self):
965 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
966
967 This tests that offload works as expected if calls are
968 made both before and after the job becomes expired.
969
970 """
971 self._offload_unexpired_job(0)
972 self._job.set_finished(0)
973 self._offload_expired_job(0)
974
Jakob Juelich24f22c22014-09-26 11:46:11 -0700975
J. Richard Barnetteea785362014-03-17 16:00:53 -0700976 def test_case_2_no_expiration(self):
977 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
978
979 This tests that offload works as expected if calls are made
980 only after the job becomes expired.
981
982 """
983 self._job.set_finished(0)
984 self._offload_expired_job(0)
985
Jakob Juelich24f22c22014-09-26 11:46:11 -0700986
J. Richard Barnetteea785362014-03-17 16:00:53 -0700987 def test_case_1_with_expiration(self):
988 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
989
990 This tests that offload works as expected if calls are made
991 before the job finishes, before the job expires, and after
992 the job expires.
993
994 """
995 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
996 self._job.set_finished(_TEST_EXPIRATION_AGE)
997 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
998 self._job.set_expired(_TEST_EXPIRATION_AGE)
999 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1000
Jakob Juelich24f22c22014-09-26 11:46:11 -07001001
J. Richard Barnetteea785362014-03-17 16:00:53 -07001002 def test_case_2_with_expiration(self):
1003 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1004
1005 This tests that offload works as expected if calls are made
1006 between finishing and expiration, and after the job expires.
1007
1008 """
1009 self._job.set_finished(_TEST_EXPIRATION_AGE)
1010 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1011 self._job.set_expired(_TEST_EXPIRATION_AGE)
1012 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1013
Jakob Juelich24f22c22014-09-26 11:46:11 -07001014
J. Richard Barnetteea785362014-03-17 16:00:53 -07001015 def test_case_3_with_expiration(self):
1016 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1017
1018 This tests that offload works as expected if calls are made
1019 only before finishing and after expiration.
1020
1021 """
1022 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1023 self._job.set_expired(_TEST_EXPIRATION_AGE)
1024 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1025
Jakob Juelich24f22c22014-09-26 11:46:11 -07001026
J. Richard Barnetteea785362014-03-17 16:00:53 -07001027 def test_case_4_with_expiration(self):
1028 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1029
1030 This tests that offload works as expected if calls are made
1031 only after expiration.
1032
1033 """
1034 self._job.set_expired(_TEST_EXPIRATION_AGE)
1035 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1036
1037
1038class GetJobDirectoriesTests(_TempResultsDirTestBase):
1039 """Tests for `_JobDirectory.get_job_directories()`."""
1040
J. Richard Barnetteea785362014-03-17 16:00:53 -07001041 def setUp(self):
1042 super(GetJobDirectoriesTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -07001043 self.make_job_hierarchy()
1044 os.mkdir('not-a-job')
1045 open('not-a-dir', 'w').close()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001046
Jakob Juelich24f22c22014-09-26 11:46:11 -07001047
J. Richard Barnetteea785362014-03-17 16:00:53 -07001048 def _run_get_directories(self, cls, expected_list):
1049 """Test `get_job_directories()` for the given class.
1050
1051 Calls the method, and asserts that the returned list of
1052 directories matches the expected return value.
1053
1054 @param expected_list Expected return value from the call.
1055 """
J. Richard Barnetteea785362014-03-17 16:00:53 -07001056 dirlist = cls.get_job_directories()
1057 self.assertEqual(set(dirlist), set(expected_list))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001058
Jakob Juelich24f22c22014-09-26 11:46:11 -07001059
J. Richard Barnetteea785362014-03-17 16:00:53 -07001060 def test_get_regular_jobs(self):
1061 """Test `RegularJobDirectory.get_job_directories()`."""
1062 self._run_get_directories(job_directories.RegularJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001063 self.REGULAR_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001064
Jakob Juelich24f22c22014-09-26 11:46:11 -07001065
J. Richard Barnetteea785362014-03-17 16:00:53 -07001066 def test_get_special_jobs(self):
1067 """Test `SpecialJobDirectory.get_job_directories()`."""
1068 self._run_get_directories(job_directories.SpecialJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001069 self.SPECIAL_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001070
1071
1072class AddJobsTests(_TempResultsDirTestBase):
1073 """Tests for `Offloader._add_new_jobs()`."""
1074
J. Richard Barnette08800322014-05-16 14:49:46 -07001075 MOREJOBS = ['115-fubar', '116-fubar', '117-fubar', '118-snafu']
J. Richard Barnetteea785362014-03-17 16:00:53 -07001076
1077 def setUp(self):
1078 super(AddJobsTests, self).setUp()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001079 self._initial_job_names = (
1080 set(self.REGULAR_JOBLIST) | set(self.SPECIAL_JOBLIST))
J. Richard Barnette08800322014-05-16 14:49:46 -07001081 self.make_job_hierarchy()
1082 self._offloader = gs_offloader.Offloader(_get_options(['-a']))
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001083 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001084
Jakob Juelich24f22c22014-09-26 11:46:11 -07001085
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001086 def _run_add_new_jobs(self, expected_key_set):
J. Richard Barnetteea785362014-03-17 16:00:53 -07001087 """Basic test assertions for `_add_new_jobs()`.
1088
1089 Asserts the following:
1090 * The keys in the offloader's `_open_jobs` dictionary
1091 matches the expected set of keys.
1092 * For every job in `_open_jobs`, the job has the expected
1093 directory name.
1094
1095 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001096 count = len(expected_key_set) - len(self._offloader._open_jobs)
1097 logging.debug(mox.IgnoreArg(), count)
1098 self.mox.ReplayAll()
1099 self._offloader._add_new_jobs()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001100 self.assertEqual(expected_key_set,
1101 set(self._offloader._open_jobs.keys()))
1102 for jobkey, job in self._offloader._open_jobs.items():
1103 self.assertEqual(jobkey, job._dirname)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001104 self.mox.VerifyAll()
1105 self.mox.ResetAll()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001106
Jakob Juelich24f22c22014-09-26 11:46:11 -07001107
J. Richard Barnetteea785362014-03-17 16:00:53 -07001108 def test_add_jobs_empty(self):
1109 """Test adding jobs to an empty dictionary.
1110
1111 Calls the offloader's `_add_new_jobs()`, then perform
1112 the assertions of `self._check_open_jobs()`.
1113
1114 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001115 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001116
Jakob Juelich24f22c22014-09-26 11:46:11 -07001117
J. Richard Barnetteea785362014-03-17 16:00:53 -07001118 def test_add_jobs_non_empty(self):
1119 """Test adding jobs to a non-empty dictionary.
1120
1121 Calls the offloader's `_add_new_jobs()` twice; once from
1122 initial conditions, and then again after adding more
1123 directories. After the second call, perform the assertions
1124 of `self._check_open_jobs()`. Additionally, assert that
1125 keys added by the first call still map to their original
1126 job object after the second call.
1127
1128 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001129 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001130 jobs_copy = self._offloader._open_jobs.copy()
J. Richard Barnette08800322014-05-16 14:49:46 -07001131 for d in self.MOREJOBS:
1132 os.mkdir(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001133 self._run_add_new_jobs(self._initial_job_names |
1134 set(self.MOREJOBS))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001135 for key in jobs_copy.keys():
1136 self.assertIs(jobs_copy[key],
1137 self._offloader._open_jobs[key])
1138
1139
1140class JobStateTests(_TempResultsDirTestBase):
1141 """Tests for job state predicates.
1142
1143 This tests for the expected results from the
1144 `is_offloaded()` and `is_reportable()` predicate
1145 methods.
1146
1147 """
1148
1149 def test_unfinished_job(self):
1150 """Test that an unfinished job reports the correct state.
1151
1152 A job is "unfinished" if it isn't marked complete in the
1153 database. A job in this state is neither "complete" nor
1154 "reportable".
1155
1156 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001157 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001158 self.assertFalse(job.is_offloaded())
1159 self.assertFalse(job.is_reportable())
1160
Jakob Juelich24f22c22014-09-26 11:46:11 -07001161
J. Richard Barnetteea785362014-03-17 16:00:53 -07001162 def test_incomplete_job(self):
1163 """Test that an incomplete job reports the correct state.
1164
1165 A job is "incomplete" if exactly one attempt has been made
1166 to offload the job, but its results directory still exists.
1167 A job in this state is neither "complete" nor "reportable".
1168
1169 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001170 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001171 job.set_incomplete()
1172 self.assertFalse(job.is_offloaded())
1173 self.assertFalse(job.is_reportable())
1174
Jakob Juelich24f22c22014-09-26 11:46:11 -07001175
J. Richard Barnetteea785362014-03-17 16:00:53 -07001176 def test_reportable_job(self):
1177 """Test that a reportable job reports the correct state.
1178
1179 A job is "reportable" if more than one attempt has been made
1180 to offload the job, and its results directory still exists.
1181 A job in this state is "reportable", but not "complete".
1182
1183 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001184 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001185 job.set_reportable()
1186 self.assertFalse(job.is_offloaded())
1187 self.assertTrue(job.is_reportable())
1188
Jakob Juelich24f22c22014-09-26 11:46:11 -07001189
J. Richard Barnetteea785362014-03-17 16:00:53 -07001190 def test_completed_job(self):
1191 """Test that a completed job reports the correct state.
1192
1193 A job is "completed" if at least one attempt has been made
1194 to offload the job, and its results directory still exists.
1195 A job in this state is "complete", and not "reportable".
1196
1197 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001198 job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001199 job.set_complete()
1200 self.assertTrue(job.is_offloaded())
1201 self.assertFalse(job.is_reportable())
1202
1203
1204class ReportingTests(_TempResultsDirTestBase):
1205 """Tests for `Offloader._update_offload_results()`."""
1206
J. Richard Barnetteea785362014-03-17 16:00:53 -07001207 def setUp(self):
1208 super(ReportingTests, self).setUp()
1209 self._offloader = gs_offloader.Offloader(_get_options([]))
1210 self.mox.StubOutWithMock(email_manager.manager,
1211 'send_email')
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001212 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001213
Jakob Juelich24f22c22014-09-26 11:46:11 -07001214
J. Richard Barnetteea785362014-03-17 16:00:53 -07001215 def _add_job(self, jobdir):
1216 """Add a job to the dictionary of unfinished jobs."""
1217 j = self.make_job(jobdir)
1218 self._offloader._open_jobs[j._dirname] = j
1219 return j
1220
Jakob Juelich24f22c22014-09-26 11:46:11 -07001221
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001222 def _expect_log_message(self, new_open_jobs, with_failures):
1223 """Mock expected logging calls.
1224
1225 `_update_offload_results()` logs one message with the number
1226 of jobs removed from the open job set and the number of jobs
1227 still remaining. Additionally, if there are reportable
1228 jobs, then it logs the number of jobs that haven't yet
1229 offloaded.
1230
1231 This sets up the logging calls using `new_open_jobs` to
1232 figure the job counts. If `with_failures` is true, then
1233 the log message is set up assuming that all jobs in
1234 `new_open_jobs` have offload failures.
1235
1236 @param new_open_jobs New job set for calculating counts
1237 in the messages.
1238 @param with_failures Whether the log message with a
1239 failure count is expected.
1240
1241 """
1242 count = len(self._offloader._open_jobs) - len(new_open_jobs)
1243 logging.debug(mox.IgnoreArg(), count, len(new_open_jobs))
1244 if with_failures:
1245 logging.debug(mox.IgnoreArg(), len(new_open_jobs))
1246
Jakob Juelich24f22c22014-09-26 11:46:11 -07001247
J. Richard Barnetteea785362014-03-17 16:00:53 -07001248 def _run_update_no_report(self, new_open_jobs):
1249 """Call `_update_offload_results()` expecting no report.
1250
1251 Initial conditions are set up by the caller. This calls
1252 `_update_offload_results()` once, and then checks these
1253 assertions:
1254 * The offloader's `_next_report_time` field is unchanged.
1255 * The offloader's new `_open_jobs` field contains only
1256 the entries in `new_open_jobs`.
1257 * The email_manager's `send_email` stub wasn't called.
1258
1259 @param new_open_jobs A dictionary representing the expected
1260 new value of the offloader's
1261 `_open_jobs` field.
1262 """
1263 self.mox.ReplayAll()
1264 next_report_time = self._offloader._next_report_time
1265 self._offloader._update_offload_results()
1266 self.assertEqual(next_report_time,
1267 self._offloader._next_report_time)
1268 self.assertEqual(self._offloader._open_jobs, new_open_jobs)
1269 self.mox.VerifyAll()
1270 self.mox.ResetAll()
1271
Jakob Juelich24f22c22014-09-26 11:46:11 -07001272
J. Richard Barnetteea785362014-03-17 16:00:53 -07001273 def _run_update_with_report(self, new_open_jobs):
1274 """Call `_update_offload_results()` expecting an e-mail report.
1275
1276 Initial conditions are set up by the caller. This calls
1277 `_update_offload_results()` once, and then checks these
1278 assertions:
1279 * The offloader's `_next_report_time` field is updated
1280 to an appropriate new time.
1281 * The offloader's new `_open_jobs` field contains only
1282 the entries in `new_open_jobs`.
1283 * The email_manager's `send_email` stub was called.
1284
1285 @param new_open_jobs A dictionary representing the expected
1286 new value of the offloader's
1287 `_open_jobs` field.
1288 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001289 logging.debug(mox.IgnoreArg())
J. Richard Barnetteea785362014-03-17 16:00:53 -07001290 email_manager.manager.send_email(
1291 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg())
1292 self.mox.ReplayAll()
1293 t0 = time.time() + gs_offloader.REPORT_INTERVAL_SECS
1294 self._offloader._update_offload_results()
1295 t1 = time.time() + gs_offloader.REPORT_INTERVAL_SECS
1296 next_report_time = self._offloader._next_report_time
1297 self.assertGreaterEqual(next_report_time, t0)
1298 self.assertLessEqual(next_report_time, t1)
1299 self.assertEqual(self._offloader._open_jobs, new_open_jobs)
1300 self.mox.VerifyAll()
1301 self.mox.ResetAll()
1302
Jakob Juelich24f22c22014-09-26 11:46:11 -07001303
J. Richard Barnetteea785362014-03-17 16:00:53 -07001304 def test_no_jobs(self):
1305 """Test `_update_offload_results()` with no open jobs.
1306
1307 Initial conditions are an empty `_open_jobs` list and
1308 `_next_report_time` in the past. Expected result is no
1309 e-mail report, and an empty `_open_jobs` list.
1310
1311 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001312 self._expect_log_message({}, False)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001313 self._run_update_no_report({})
1314
Jakob Juelich24f22c22014-09-26 11:46:11 -07001315
J. Richard Barnetteea785362014-03-17 16:00:53 -07001316 def test_all_completed(self):
1317 """Test `_update_offload_results()` with only complete jobs.
1318
1319 Initial conditions are an `_open_jobs` list consisting of
1320 only completed jobs and `_next_report_time` in the past.
1321 Expected result is no e-mail report, and an empty
1322 `_open_jobs` list.
1323
1324 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001325 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001326 self._add_job(d).set_complete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001327 self._expect_log_message({}, False)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001328 self._run_update_no_report({})
1329
Jakob Juelich24f22c22014-09-26 11:46:11 -07001330
J. Richard Barnetteea785362014-03-17 16:00:53 -07001331 def test_none_finished(self):
1332 """Test `_update_offload_results()` with only unfinished jobs.
1333
1334 Initial conditions are an `_open_jobs` list consisting of
1335 only unfinished jobs and `_next_report_time` in the past.
1336 Expected result is no e-mail report, and no change to the
1337 `_open_jobs` list.
1338
1339 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001340 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001341 self._add_job(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001342 new_jobs = self._offloader._open_jobs.copy()
1343 self._expect_log_message(new_jobs, False)
1344 self._run_update_no_report(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001345
Jakob Juelich24f22c22014-09-26 11:46:11 -07001346
J. Richard Barnetteea785362014-03-17 16:00:53 -07001347 def test_none_reportable(self):
1348 """Test `_update_offload_results()` with only incomplete jobs.
1349
1350 Initial conditions are an `_open_jobs` list consisting of
1351 only incomplete jobs and `_next_report_time` in the past.
1352 Expected result is no e-mail report, and no change to the
1353 `_open_jobs` list.
1354
1355 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001356 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001357 self._add_job(d).set_incomplete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001358 new_jobs = self._offloader._open_jobs.copy()
1359 self._expect_log_message(new_jobs, False)
1360 self._run_update_no_report(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001361
Jakob Juelich24f22c22014-09-26 11:46:11 -07001362
J. Richard Barnetteea785362014-03-17 16:00:53 -07001363 def test_report_not_ready(self):
1364 """Test `_update_offload_results()` e-mail throttling.
1365
1366 Initial conditions are an `_open_jobs` list consisting of
1367 only reportable jobs but with `_next_report_time` in
1368 the future. Expected result is no e-mail report, and no
1369 change to the `_open_jobs` list.
1370
1371 """
1372 # N.B. This test may fail if its run time exceeds more than
1373 # about _MARGIN_SECS seconds.
J. Richard Barnette08800322014-05-16 14:49:46 -07001374 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001375 self._add_job(d).set_reportable()
1376 self._offloader._next_report_time += _MARGIN_SECS
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001377 new_jobs = self._offloader._open_jobs.copy()
1378 self._expect_log_message(new_jobs, True)
1379 self._run_update_no_report(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001380
Jakob Juelich24f22c22014-09-26 11:46:11 -07001381
J. Richard Barnetteea785362014-03-17 16:00:53 -07001382 def test_reportable(self):
1383 """Test `_update_offload_results()` with reportable jobs.
1384
1385 Initial conditions are an `_open_jobs` list consisting of
1386 only reportable jobs and with `_next_report_time` in
1387 the past. Expected result is an e-mail report, and no
1388 change to the `_open_jobs` list.
1389
1390 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001391 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001392 self._add_job(d).set_reportable()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001393 new_jobs = self._offloader._open_jobs.copy()
1394 self._expect_log_message(new_jobs, True)
1395 self._run_update_with_report(new_jobs)
1396
Jakob Juelich24f22c22014-09-26 11:46:11 -07001397
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001398 def test_reportable_mixed(self):
1399 """Test `_update_offload_results()` with a mixture of jobs.
1400
1401 Initial conditions are an `_open_jobs` list consisting of
1402 one reportable jobs and the remainder of the jobs
1403 incomplete. The value of `_next_report_time` is in the
1404 past. Expected result is an e-mail report that includes
1405 both the reportable and the incomplete jobs, and no change
1406 to the `_open_jobs` list.
1407
1408 """
1409 self._add_job(self.REGULAR_JOBLIST[0]).set_reportable()
1410 for d in self.REGULAR_JOBLIST[1:]:
1411 self._add_job(d).set_incomplete()
1412 new_jobs = self._offloader._open_jobs.copy()
1413 self._expect_log_message(new_jobs, True)
1414 self._run_update_with_report(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001415
1416
1417if __name__ == '__main__':
1418 unittest.main()