blob: 052cbf9f849cf38073f051ee8f231e143c6f2af3 [file] [log] [blame]
Mike Frysingerd03e6b52019-08-03 12:49:01 -04001#!/usr/bin/python2
Michael Tang97d188c2016-06-25 11:18:42 -07002# Copyright 2016 The Chromium OS Authors. All rights reserved.
J. Richard Barnetteea785362014-03-17 16:00:53 -07003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Keith Haddow5ba5fb82016-11-09 11:39:36 -08006import __builtin__
J. Richard Barnetteea785362014-03-17 16:00:53 -07007import Queue
Xinan Lin9eb782f2019-10-23 23:14:49 -07008import json
J. Richard Barnetteea785362014-03-17 16:00:53 -07009import logging
10import os
11import shutil
J. Richard Barnette2e443ef2014-05-20 12:31:35 -070012import signal
Laurence Goodbyca7726d2017-02-14 17:09:07 -080013import stat
Xinan Lin9eb782f2019-10-23 23:14:49 -070014import subprocess
J. Richard Barnetteea785362014-03-17 16:00:53 -070015import sys
Rohit Makasanac1c7e782019-04-02 18:06:00 -070016import tarfile
J. Richard Barnetteea785362014-03-17 16:00:53 -070017import tempfile
18import time
19import unittest
20
Allen Li9579b382017-05-05 17:07:43 -070021import mock
J. Richard Barnetteea785362014-03-17 16:00:53 -070022import mox
23
24import common
Allen Li5ed7e632017-02-03 16:31:33 -080025from autotest_lib.client.common_lib import global_config
Dan Shi1b4c7c32015-10-05 10:38:57 -070026from autotest_lib.client.common_lib import utils
Michael Tange8bc9592017-07-06 10:59:32 -070027#For unittest without cloud_client.proto compiled.
28try:
29 from autotest_lib.site_utils import cloud_console_client
30except ImportError:
31 cloud_console_client = None
Prathmesh Prabhubeb9e012017-01-30 16:18:39 -080032from autotest_lib.site_utils import gs_offloader
33from autotest_lib.site_utils import job_directories
Prathmesh Prabhud47e9d32019-03-13 13:04:05 -070034from autotest_lib.site_utils import job_directories_unittest as jd_test
Ningning Xia2d981ee2016-07-06 17:59:54 -070035from autotest_lib.tko import models
Allen Lib41527d2017-06-22 17:28:00 -070036from autotest_lib.utils import gslib
Michael Tang0f553bd2017-06-16 17:38:45 -070037from autotest_lib.site_utils import pubsub_utils
Allen Lib41527d2017-06-22 17:28:00 -070038from chromite.lib import timeout_util
Jakob Juelich24f22c22014-09-26 11:46:11 -070039
J. Richard Barnetteea785362014-03-17 16:00:53 -070040# Test value to use for `days_old`, if nothing else is required.
41_TEST_EXPIRATION_AGE = 7
42
J. Richard Barnetteea785362014-03-17 16:00:53 -070043
44def _get_options(argv):
45 """Helper function to exercise command line parsing.
46
47 @param argv Value of sys.argv to be parsed.
48
49 """
50 sys.argv = ['bogus.py'] + argv
51 return gs_offloader.parse_options()
52
53
Laurence Goodbyca7726d2017-02-14 17:09:07 -080054def is_fifo(path):
Allen Li93585382017-05-05 14:24:53 -070055 """Determines whether a path is a fifo.
56
57 @param path: fifo path string.
58 """
Laurence Goodbyca7726d2017-02-14 17:09:07 -080059 return stat.S_ISFIFO(os.lstat(path).st_mode)
60
61
Xinan Lin9eb782f2019-10-23 23:14:49 -070062def _get_fake_process():
63 return FakeProcess()
64
65
66class FakeProcess(object):
67 """Fake process object."""
68
69 def __init__(self):
70 self.returncode = 0
71
72
73 def wait(self):
74 return True
75
76
Simran Basidd129972014-09-11 14:34:49 -070077class OffloaderOptionsTests(mox.MoxTestBase):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070078 """Tests for the `Offloader` constructor.
79
80 Tests that offloader instance fields are set as expected
81 for given command line options.
82
83 """
84
Allen Li7402f092018-06-26 15:42:21 -070085 _REGULAR_ONLY = {job_directories.SwarmingJobDirectory,
86 job_directories.RegularJobDirectory}
87 _SPECIAL_ONLY = {job_directories.SwarmingJobDirectory,
88 job_directories.SpecialJobDirectory}
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -070089 _BOTH = _REGULAR_ONLY | _SPECIAL_ONLY
J. Richard Barnetteea785362014-03-17 16:00:53 -070090
Jakob Juelich24f22c22014-09-26 11:46:11 -070091
Simran Basidd129972014-09-11 14:34:49 -070092 def setUp(self):
93 super(OffloaderOptionsTests, self).setUp()
94 self.mox.StubOutWithMock(utils, 'get_offload_gsuri')
Simran Basif3e305f2014-10-03 14:43:53 -070095 gs_offloader.GS_OFFLOADING_ENABLED = True
Michael Tang0df2eb42016-05-13 19:06:54 -070096 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = False
Simran Basidd129972014-09-11 14:34:49 -070097
Jakob Juelich24f22c22014-09-26 11:46:11 -070098
Allen Lib41527d2017-06-22 17:28:00 -070099 def _mock_get_sub_offloader(self, is_moblab, multiprocessing=False,
Michael Tang0f553bd2017-06-16 17:38:45 -0700100 console_client=None, delete_age=0):
Simran Basidd129972014-09-11 14:34:49 -0700101 """Mock the process of getting the offload_dir function."""
102 if is_moblab:
103 expected_gsuri = '%sresults/%s/%s/' % (
104 global_config.global_config.get_config_value(
105 'CROS', 'image_storage_server'),
106 'Fa:ke:ma:c0:12:34', 'rand0m-uu1d')
107 else:
108 expected_gsuri = utils.DEFAULT_OFFLOAD_GSURI
109 utils.get_offload_gsuri().AndReturn(expected_gsuri)
Allen Lib41527d2017-06-22 17:28:00 -0700110 sub_offloader = gs_offloader.GSOffloader(expected_gsuri,
Michael Tang0f553bd2017-06-16 17:38:45 -0700111 multiprocessing, delete_age, console_client)
Allen Lib41527d2017-06-22 17:28:00 -0700112 self.mox.StubOutWithMock(gs_offloader, 'GSOffloader')
Michael Tange8bc9592017-07-06 10:59:32 -0700113 if cloud_console_client:
114 self.mox.StubOutWithMock(cloud_console_client,
115 'is_cloud_notification_enabled')
Michael Tang0f553bd2017-06-16 17:38:45 -0700116 if console_client:
117 cloud_console_client.is_cloud_notification_enabled().AndReturn(True)
118 gs_offloader.GSOffloader(
119 expected_gsuri, multiprocessing, delete_age,
120 mox.IsA(cloud_console_client.PubSubBasedClient)).AndReturn(
121 sub_offloader)
122 else:
Michael Tange8bc9592017-07-06 10:59:32 -0700123 if cloud_console_client:
124 cloud_console_client.is_cloud_notification_enabled().AndReturn(
125 False)
Michael Tang0f553bd2017-06-16 17:38:45 -0700126 gs_offloader.GSOffloader(
127 expected_gsuri, multiprocessing, delete_age, None).AndReturn(
128 sub_offloader)
Simran Basidd129972014-09-11 14:34:49 -0700129 self.mox.ReplayAll()
Allen Lib41527d2017-06-22 17:28:00 -0700130 return sub_offloader
Simran Basidd129972014-09-11 14:34:49 -0700131
Jakob Juelich24f22c22014-09-26 11:46:11 -0700132
J. Richard Barnetteea785362014-03-17 16:00:53 -0700133 def test_process_no_options(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700134 """Test default offloader options."""
Allen Lib41527d2017-06-22 17:28:00 -0700135 sub_offloader = self._mock_get_sub_offloader(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700136 offloader = gs_offloader.Offloader(_get_options([]))
137 self.assertEqual(set(offloader._jobdir_classes),
138 self._REGULAR_ONLY)
139 self.assertEqual(offloader._processes, 1)
Allen Lib41527d2017-06-22 17:28:00 -0700140 self.assertEqual(offloader._gs_offloader,
141 sub_offloader)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800142 self.assertEqual(offloader._upload_age_limit, 0)
143 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700144
Jakob Juelich24f22c22014-09-26 11:46:11 -0700145
J. Richard Barnetteea785362014-03-17 16:00:53 -0700146 def test_process_all_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700147 """Test offloader handling for the --all option."""
Allen Lib41527d2017-06-22 17:28:00 -0700148 sub_offloader = self._mock_get_sub_offloader(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700149 offloader = gs_offloader.Offloader(_get_options(['--all']))
150 self.assertEqual(set(offloader._jobdir_classes), self._BOTH)
151 self.assertEqual(offloader._processes, 1)
Allen Lib41527d2017-06-22 17:28:00 -0700152 self.assertEqual(offloader._gs_offloader,
153 sub_offloader)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800154 self.assertEqual(offloader._upload_age_limit, 0)
155 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700156
Jakob Juelich24f22c22014-09-26 11:46:11 -0700157
J. Richard Barnetteea785362014-03-17 16:00:53 -0700158 def test_process_hosts_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700159 """Test offloader handling for the --hosts option."""
Allen Lib41527d2017-06-22 17:28:00 -0700160 sub_offloader = self._mock_get_sub_offloader(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700161 offloader = gs_offloader.Offloader(
162 _get_options(['--hosts']))
163 self.assertEqual(set(offloader._jobdir_classes),
164 self._SPECIAL_ONLY)
165 self.assertEqual(offloader._processes, 1)
Allen Lib41527d2017-06-22 17:28:00 -0700166 self.assertEqual(offloader._gs_offloader,
167 sub_offloader)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800168 self.assertEqual(offloader._upload_age_limit, 0)
169 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700170
Jakob Juelich24f22c22014-09-26 11:46:11 -0700171
J. Richard Barnetteea785362014-03-17 16:00:53 -0700172 def test_parallelism_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700173 """Test offloader handling for the --parallelism option."""
Allen Lib41527d2017-06-22 17:28:00 -0700174 sub_offloader = self._mock_get_sub_offloader(False)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700175 offloader = gs_offloader.Offloader(
176 _get_options(['--parallelism', '2']))
177 self.assertEqual(set(offloader._jobdir_classes),
178 self._REGULAR_ONLY)
179 self.assertEqual(offloader._processes, 2)
Allen Lib41527d2017-06-22 17:28:00 -0700180 self.assertEqual(offloader._gs_offloader,
181 sub_offloader)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800182 self.assertEqual(offloader._upload_age_limit, 0)
183 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700184
Jakob Juelich24f22c22014-09-26 11:46:11 -0700185
J. Richard Barnetteea785362014-03-17 16:00:53 -0700186 def test_delete_only_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700187 """Test offloader handling for the --delete_only option."""
188 offloader = gs_offloader.Offloader(
189 _get_options(['--delete_only']))
190 self.assertEqual(set(offloader._jobdir_classes),
191 self._REGULAR_ONLY)
192 self.assertEqual(offloader._processes, 1)
Allen Lib41527d2017-06-22 17:28:00 -0700193 self.assertIsInstance(offloader._gs_offloader,
194 gs_offloader.FakeGSOffloader)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800195 self.assertEqual(offloader._upload_age_limit, 0)
196 self.assertEqual(offloader._delete_age_limit, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700197
Jakob Juelich24f22c22014-09-26 11:46:11 -0700198
Simran Basidf4751e2014-10-10 14:19:22 -0700199 def test_days_old_option(self):
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700200 """Test offloader handling for the --days_old option."""
Allen Lib41527d2017-06-22 17:28:00 -0700201 sub_offloader = self._mock_get_sub_offloader(False, delete_age=7)
J. Richard Barnetteef4b47d2014-05-19 07:52:08 -0700202 offloader = gs_offloader.Offloader(
203 _get_options(['--days_old', '7']))
204 self.assertEqual(set(offloader._jobdir_classes),
205 self._REGULAR_ONLY)
206 self.assertEqual(offloader._processes, 1)
Allen Lib41527d2017-06-22 17:28:00 -0700207 self.assertEqual(offloader._gs_offloader,
208 sub_offloader)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800209 self.assertEqual(offloader._upload_age_limit, 7)
210 self.assertEqual(offloader._delete_age_limit, 7)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700211
Jakob Juelich24f22c22014-09-26 11:46:11 -0700212
Simran Basidd129972014-09-11 14:34:49 -0700213 def test_moblab_gsuri_generation(self):
214 """Test offloader construction for Moblab."""
Allen Lib41527d2017-06-22 17:28:00 -0700215 sub_offloader = self._mock_get_sub_offloader(True)
Simran Basidd129972014-09-11 14:34:49 -0700216 offloader = gs_offloader.Offloader(_get_options([]))
217 self.assertEqual(set(offloader._jobdir_classes),
218 self._REGULAR_ONLY)
219 self.assertEqual(offloader._processes, 1)
Allen Lib41527d2017-06-22 17:28:00 -0700220 self.assertEqual(offloader._gs_offloader,
221 sub_offloader)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800222 self.assertEqual(offloader._upload_age_limit, 0)
223 self.assertEqual(offloader._delete_age_limit, 0)
Simran Basidd129972014-09-11 14:34:49 -0700224
J. Richard Barnetteea785362014-03-17 16:00:53 -0700225
Simran Basif3e305f2014-10-03 14:43:53 -0700226 def test_globalconfig_offloading_flag(self):
227 """Test enabling of --delete_only via global_config."""
228 gs_offloader.GS_OFFLOADING_ENABLED = False
229 offloader = gs_offloader.Offloader(
230 _get_options([]))
Allen Lib41527d2017-06-22 17:28:00 -0700231 self.assertIsInstance(offloader._gs_offloader,
232 gs_offloader.FakeGSOffloader)
Simran Basif3e305f2014-10-03 14:43:53 -0700233
Michael Tang0df2eb42016-05-13 19:06:54 -0700234 def test_offloader_multiprocessing_flag_set(self):
235 """Test multiprocessing is set."""
Allen Lib41527d2017-06-22 17:28:00 -0700236 sub_offloader = self._mock_get_sub_offloader(True, True)
Michael Tang0df2eb42016-05-13 19:06:54 -0700237 offloader = gs_offloader.Offloader(_get_options(['-m']))
Allen Lib41527d2017-06-22 17:28:00 -0700238 self.assertEqual(offloader._gs_offloader,
239 sub_offloader)
Michael Tang0df2eb42016-05-13 19:06:54 -0700240 self.mox.VerifyAll()
241
242 def test_offloader_multiprocessing_flag_not_set_default_false(self):
243 """Test multiprocessing is set."""
244 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = False
Allen Lib41527d2017-06-22 17:28:00 -0700245 sub_offloader = self._mock_get_sub_offloader(True, False)
Michael Tang0df2eb42016-05-13 19:06:54 -0700246 offloader = gs_offloader.Offloader(_get_options([]))
Allen Lib41527d2017-06-22 17:28:00 -0700247 self.assertEqual(offloader._gs_offloader,
248 sub_offloader)
Michael Tang0df2eb42016-05-13 19:06:54 -0700249 self.mox.VerifyAll()
250
251 def test_offloader_multiprocessing_flag_not_set_default_true(self):
252 """Test multiprocessing is set."""
253 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = True
Allen Lib41527d2017-06-22 17:28:00 -0700254 sub_offloader = self._mock_get_sub_offloader(True, True)
Michael Tang0df2eb42016-05-13 19:06:54 -0700255 offloader = gs_offloader.Offloader(_get_options([]))
Allen Lib41527d2017-06-22 17:28:00 -0700256 self.assertEqual(offloader._gs_offloader,
257 sub_offloader)
Michael Tang0df2eb42016-05-13 19:06:54 -0700258 self.mox.VerifyAll()
259
Michael Tang97d188c2016-06-25 11:18:42 -0700260
Michael Tang0f553bd2017-06-16 17:38:45 -0700261 def test_offloader_pubsub_enabled(self):
Michael Tang97d188c2016-06-25 11:18:42 -0700262 """Test multiprocessing is set."""
Michael Tange8bc9592017-07-06 10:59:32 -0700263 if not cloud_console_client:
264 return
Michael Tang0f553bd2017-06-16 17:38:45 -0700265 self.mox.StubOutWithMock(pubsub_utils, "PubSubClient")
266 sub_offloader = self._mock_get_sub_offloader(True, False,
267 cloud_console_client.PubSubBasedClient())
268 offloader = gs_offloader.Offloader(_get_options([]))
Allen Lib41527d2017-06-22 17:28:00 -0700269 self.assertEqual(offloader._gs_offloader,
270 sub_offloader)
Michael Tang97d188c2016-06-25 11:18:42 -0700271 self.mox.VerifyAll()
272
Simran Basif3e305f2014-10-03 14:43:53 -0700273
J. Richard Barnetteea785362014-03-17 16:00:53 -0700274class _MockJobDirectory(job_directories._JobDirectory):
275 """Subclass of `_JobDirectory` used as a helper for tests."""
276
277 GLOB_PATTERN = '[0-9]*-*'
278
Jakob Juelich24f22c22014-09-26 11:46:11 -0700279
J. Richard Barnetteea785362014-03-17 16:00:53 -0700280 def __init__(self, resultsdir):
281 """Create new job in initial state."""
282 super(_MockJobDirectory, self).__init__(resultsdir)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700283 self._timestamp = None
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800284 self.queue_args = [resultsdir, os.path.dirname(resultsdir), self._timestamp]
J. Richard Barnetteea785362014-03-17 16:00:53 -0700285
Jakob Juelich24f22c22014-09-26 11:46:11 -0700286
J. Richard Barnetteea785362014-03-17 16:00:53 -0700287 def get_timestamp_if_finished(self):
288 return self._timestamp
289
Jakob Juelich24f22c22014-09-26 11:46:11 -0700290
J. Richard Barnetteea785362014-03-17 16:00:53 -0700291 def set_finished(self, days_old):
292 """Make this job appear to be finished.
293
294 After calling this function, calls to `enqueue_offload()`
295 will find this job as finished, but not expired and ready
296 for offload. Note that when `days_old` is 0,
297 `enqueue_offload()` will treat a finished job as eligible
298 for offload.
299
300 @param days_old The value of the `days_old` parameter that
301 will be passed to `enqueue_offload()` for
302 testing.
303
304 """
Prathmesh Prabhud47e9d32019-03-13 13:04:05 -0700305 self._timestamp = jd_test.make_timestamp(days_old, False)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800306 self.queue_args[2] = self._timestamp
J. Richard Barnetteea785362014-03-17 16:00:53 -0700307
Jakob Juelich24f22c22014-09-26 11:46:11 -0700308
J. Richard Barnetteea785362014-03-17 16:00:53 -0700309 def set_expired(self, days_old):
310 """Make this job eligible to be offloaded.
311
312 After calling this function, calls to `offload` will attempt
313 to offload this job.
314
315 @param days_old The value of the `days_old` parameter that
316 will be passed to `enqueue_offload()` for
317 testing.
318
319 """
Prathmesh Prabhud47e9d32019-03-13 13:04:05 -0700320 self._timestamp = jd_test.make_timestamp(days_old, True)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800321 self.queue_args[2] = self._timestamp
J. Richard Barnetteea785362014-03-17 16:00:53 -0700322
Jakob Juelich24f22c22014-09-26 11:46:11 -0700323
J. Richard Barnetteea785362014-03-17 16:00:53 -0700324 def set_incomplete(self):
325 """Make this job appear to have failed offload just once."""
Allen Lib41527d2017-06-22 17:28:00 -0700326 self.offload_count += 1
327 self.first_offload_start = time.time()
328 if not os.path.isdir(self.dirname):
329 os.mkdir(self.dirname)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700330
Jakob Juelich24f22c22014-09-26 11:46:11 -0700331
J. Richard Barnetteea785362014-03-17 16:00:53 -0700332 def set_reportable(self):
333 """Make this job be reportable."""
J. Richard Barnetteea785362014-03-17 16:00:53 -0700334 self.set_incomplete()
Allen Lib41527d2017-06-22 17:28:00 -0700335 self.offload_count += 1
J. Richard Barnetteea785362014-03-17 16:00:53 -0700336
Jakob Juelich24f22c22014-09-26 11:46:11 -0700337
J. Richard Barnetteea785362014-03-17 16:00:53 -0700338 def set_complete(self):
339 """Make this job be completed."""
Allen Lib41527d2017-06-22 17:28:00 -0700340 self.offload_count += 1
341 if os.path.isdir(self.dirname):
342 os.rmdir(self.dirname)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700343
344
Simran Basi1e10e922015-04-16 15:09:56 -0700345 def process_gs_instructions(self):
346 """Always still offload the job directory."""
347 return True
348
349
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700350class CommandListTests(unittest.TestCase):
Allen Lib41527d2017-06-22 17:28:00 -0700351 """Tests for `_get_cmd_list()`."""
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700352
MK Ryue93c8572015-08-11 11:53:00 -0700353 def _command_list_assertions(self, job, use_rsync=True, multi=False):
Allen Lib41527d2017-06-22 17:28:00 -0700354 """Call `_get_cmd_list()` and check the return value.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700355
356 Check the following assertions:
357 * The command name (argv[0]) is 'gsutil'.
MK Ryue93c8572015-08-11 11:53:00 -0700358 * '-m' option (argv[1]) is on when the argument, multi, is True.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700359 * The arguments contain the 'cp' subcommand.
360 * The next-to-last argument (the source directory) is the
361 job's `queue_args[0]`.
Simran Basidd129972014-09-11 14:34:49 -0700362 * The last argument (the destination URL) is the job's
363 'queue_args[1]'.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700364
365 @param job A job with properly calculated arguments to
Allen Lib41527d2017-06-22 17:28:00 -0700366 `_get_cmd_list()`
MK Ryue93c8572015-08-11 11:53:00 -0700367 @param use_rsync True when using 'rsync'. False when using 'cp'.
368 @param multi True when using '-m' option for gsutil.
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700369
370 """
Jakob Juelich24f22c22014-09-26 11:46:11 -0700371 test_bucket_uri = 'gs://a-test-bucket'
372
373 gs_offloader.USE_RSYNC_ENABLED = use_rsync
374
Aviv Keshetacef00e2019-11-13 16:35:29 -0800375 gs_path = os.path.join(test_bucket_uri, job.queue_args[1])
376
Allen Lib41527d2017-06-22 17:28:00 -0700377 command = gs_offloader._get_cmd_list(
Aviv Keshetacef00e2019-11-13 16:35:29 -0800378 multi, job.queue_args[0], gs_path)
Jakob Juelich24f22c22014-09-26 11:46:11 -0700379
Dan Shi365049f2017-05-28 08:00:02 +0000380 self.assertEqual(command[0], 'gsutil')
MK Ryue93c8572015-08-11 11:53:00 -0700381 if multi:
382 self.assertEqual(command[1], '-m')
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700383 self.assertEqual(command[-2], job.queue_args[0])
Jakob Juelich24f22c22014-09-26 11:46:11 -0700384
385 if use_rsync:
386 self.assertTrue('rsync' in command)
387 self.assertEqual(command[-1],
388 os.path.join(test_bucket_uri, job.queue_args[0]))
389 else:
390 self.assertTrue('cp' in command)
391 self.assertEqual(command[-1],
392 os.path.join(test_bucket_uri, job.queue_args[1]))
393
Aviv Keshetacef00e2019-11-13 16:35:29 -0800394 finish_command = gs_offloader._get_finish_cmd_list(gs_path)
395 self.assertEqual(finish_command[0], 'gsutil')
396 self.assertEqual(finish_command[1], 'cp')
397 self.assertEqual(finish_command[2], '/dev/null')
398 self.assertEqual(finish_command[3],
399 os.path.join(gs_path, '.finished_offload'))
400
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700401
Allen Lib41527d2017-06-22 17:28:00 -0700402 def test__get_cmd_list_regular(self):
403 """Test `_get_cmd_list()` as for a regular job."""
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700404 job = _MockJobDirectory('118-debug')
405 self._command_list_assertions(job)
406
Jakob Juelich24f22c22014-09-26 11:46:11 -0700407
Allen Lib41527d2017-06-22 17:28:00 -0700408 def test__get_cmd_list_special(self):
409 """Test `_get_cmd_list()` as for a special job."""
J. Richard Barnette9f4be0d2014-05-20 12:28:31 -0700410 job = _MockJobDirectory('hosts/host1/118-reset')
411 self._command_list_assertions(job)
412
413
Jakob Juelich24f22c22014-09-26 11:46:11 -0700414 def test_get_cmd_list_regular_no_rsync(self):
Allen Lib41527d2017-06-22 17:28:00 -0700415 """Test `_get_cmd_list()` as for a regular job."""
Jakob Juelich24f22c22014-09-26 11:46:11 -0700416 job = _MockJobDirectory('118-debug')
417 self._command_list_assertions(job, use_rsync=False)
418
419
420 def test_get_cmd_list_special_no_rsync(self):
Allen Lib41527d2017-06-22 17:28:00 -0700421 """Test `_get_cmd_list()` as for a special job."""
Jakob Juelich24f22c22014-09-26 11:46:11 -0700422 job = _MockJobDirectory('hosts/host1/118-reset')
423 self._command_list_assertions(job, use_rsync=False)
424
425
MK Ryue93c8572015-08-11 11:53:00 -0700426 def test_get_cmd_list_regular_multi(self):
Allen Lib41527d2017-06-22 17:28:00 -0700427 """Test `_get_cmd_list()` as for a regular job with True multi."""
MK Ryue93c8572015-08-11 11:53:00 -0700428 job = _MockJobDirectory('118-debug')
429 self._command_list_assertions(job, multi=True)
430
431
Allen Lib41527d2017-06-22 17:28:00 -0700432 def test__get_cmd_list_special_multi(self):
433 """Test `_get_cmd_list()` as for a special job with True multi."""
MK Ryue93c8572015-08-11 11:53:00 -0700434 job = _MockJobDirectory('hosts/host1/118-reset')
435 self._command_list_assertions(job, multi=True)
436
437
Allen Lib41527d2017-06-22 17:28:00 -0700438class _TempResultsDirTestCase(unittest.TestCase):
439 """Mixin class for tests using a temporary results directory."""
J. Richard Barnetteea785362014-03-17 16:00:53 -0700440
J. Richard Barnette08800322014-05-16 14:49:46 -0700441 REGULAR_JOBLIST = [
442 '111-fubar', '112-fubar', '113-fubar', '114-snafu']
443 HOST_LIST = ['host1', 'host2', 'host3']
444 SPECIAL_JOBLIST = [
445 'hosts/host1/333-reset', 'hosts/host1/334-reset',
446 'hosts/host2/444-reset', 'hosts/host3/555-reset']
447
Jakob Juelich24f22c22014-09-26 11:46:11 -0700448
J. Richard Barnetteea785362014-03-17 16:00:53 -0700449 def setUp(self):
Allen Lib41527d2017-06-22 17:28:00 -0700450 super(_TempResultsDirTestCase, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -0700451 self._resultsroot = tempfile.mkdtemp()
452 self._cwd = os.getcwd()
453 os.chdir(self._resultsroot)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700454
Jakob Juelich24f22c22014-09-26 11:46:11 -0700455
J. Richard Barnetteea785362014-03-17 16:00:53 -0700456 def tearDown(self):
J. Richard Barnette08800322014-05-16 14:49:46 -0700457 os.chdir(self._cwd)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700458 shutil.rmtree(self._resultsroot)
Allen Lib41527d2017-06-22 17:28:00 -0700459 super(_TempResultsDirTestCase, self).tearDown()
J. Richard Barnetteea785362014-03-17 16:00:53 -0700460
Jakob Juelich24f22c22014-09-26 11:46:11 -0700461
J. Richard Barnetteea785362014-03-17 16:00:53 -0700462 def make_job(self, jobdir):
463 """Create a job with results in `self._resultsroot`.
464
465 @param jobdir Name of the subdirectory to be created in
466 `self._resultsroot`.
467
468 """
Xinan Line1dc6962019-09-20 15:43:52 -0700469 os.makedirs(jobdir)
J. Richard Barnette08800322014-05-16 14:49:46 -0700470 return _MockJobDirectory(jobdir)
471
Jakob Juelich24f22c22014-09-26 11:46:11 -0700472
J. Richard Barnette08800322014-05-16 14:49:46 -0700473 def make_job_hierarchy(self):
474 """Create a sample hierarchy of job directories.
475
476 `self.REGULAR_JOBLIST` is a list of directories for regular
477 jobs to be created; `self.SPECIAL_JOBLIST` is a list of
478 directories for special jobs to be created.
479
480 """
481 for d in self.REGULAR_JOBLIST:
482 os.mkdir(d)
483 hostsdir = 'hosts'
484 os.mkdir(hostsdir)
485 for host in self.HOST_LIST:
486 os.mkdir(os.path.join(hostsdir, host))
487 for d in self.SPECIAL_JOBLIST:
488 os.mkdir(d)
J. Richard Barnetteea785362014-03-17 16:00:53 -0700489
490
Allen Lib41527d2017-06-22 17:28:00 -0700491class _TempResultsDirTestBase(_TempResultsDirTestCase, mox.MoxTestBase):
492 """Base Mox test class for tests using a temporary results directory."""
493
494
Prathmesh Prabhu80dfb1e2017-01-30 18:01:29 -0800495class FailedOffloadsLogTest(_TempResultsDirTestBase):
496 """Test the formatting of failed offloads log file."""
497 # Below is partial sample of a failed offload log file. This text is
498 # deliberately hard-coded and then parsed to create the test data; the idea
499 # is to make sure the actual text format will be reviewed by a human being.
500 #
501 # first offload count directory
502 # --+----1----+---- ----+ ----+----1----+----2----+----3
503 _SAMPLE_DIRECTORIES_REPORT = '''\
504 =================== ====== ==============================
505 2014-03-14 15:09:26 1 118-fubar
506 2014-03-14 15:19:23 2 117-fubar
507 2014-03-14 15:29:20 6 116-fubar
508 2014-03-14 15:39:17 24 115-fubar
509 2014-03-14 15:49:14 120 114-fubar
510 2014-03-14 15:59:11 720 113-fubar
511 2014-03-14 16:09:08 5040 112-fubar
512 2014-03-14 16:19:05 40320 111-fubar
513 '''
514
515 def setUp(self):
516 super(FailedOffloadsLogTest, self).setUp()
517 self._offloader = gs_offloader.Offloader(_get_options([]))
518 self._joblist = []
519 for line in self._SAMPLE_DIRECTORIES_REPORT.split('\n')[1 : -1]:
520 date_, time_, count, dir_ = line.split()
521 job = _MockJobDirectory(dir_)
Allen Lib41527d2017-06-22 17:28:00 -0700522 job.offload_count = int(count)
Prathmesh Prabhu80dfb1e2017-01-30 18:01:29 -0800523 timestruct = time.strptime("%s %s" % (date_, time_),
524 gs_offloader.FAILED_OFFLOADS_TIME_FORMAT)
Allen Lib41527d2017-06-22 17:28:00 -0700525 job.first_offload_start = time.mktime(timestruct)
Prathmesh Prabhu80dfb1e2017-01-30 18:01:29 -0800526 # enter the jobs in reverse order, to make sure we
527 # test that the output will be sorted.
528 self._joblist.insert(0, job)
529
530
531 def assert_report_well_formatted(self, report_file):
Allen Li93585382017-05-05 14:24:53 -0700532 """Assert that report file is well formatted.
533
534 @param report_file: Path to report file
535 """
Prathmesh Prabhu80dfb1e2017-01-30 18:01:29 -0800536 with open(report_file, 'r') as f:
537 report_lines = f.read().split()
538
539 for end_of_header_index in range(len(report_lines)):
540 if report_lines[end_of_header_index].startswith('=='):
541 break
542 self.assertLess(end_of_header_index, len(report_lines),
543 'Failed to find end-of-header marker in the report')
544
545 relevant_lines = report_lines[end_of_header_index:]
546 expected_lines = self._SAMPLE_DIRECTORIES_REPORT.split()
547 self.assertListEqual(relevant_lines, expected_lines)
548
549
550 def test_failed_offload_log_format(self):
551 """Trigger an e-mail report and check its contents."""
552 log_file = os.path.join(self._resultsroot, 'failed_log')
553 report = self._offloader._log_failed_jobs_locally(self._joblist,
554 log_file=log_file)
555 self.assert_report_well_formatted(log_file)
556
557
558 def test_failed_offload_file_overwrite(self):
559 """Verify that we can saefly overwrite the log file."""
560 log_file = os.path.join(self._resultsroot, 'failed_log')
561 with open(log_file, 'w') as f:
562 f.write('boohoohoo')
563 report = self._offloader._log_failed_jobs_locally(self._joblist,
564 log_file=log_file)
565 self.assert_report_well_formatted(log_file)
566
567
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700568class OffloadDirectoryTests(_TempResultsDirTestBase):
569 """Tests for `offload_dir()`."""
570
571 def setUp(self):
572 super(OffloadDirectoryTests, self).setUp()
573 # offload_dir() logs messages; silence them.
574 self._saved_loglevel = logging.getLogger().getEffectiveLevel()
575 logging.getLogger().setLevel(logging.CRITICAL+1)
576 self._job = self.make_job(self.REGULAR_JOBLIST[0])
Allen Lib41527d2017-06-22 17:28:00 -0700577 self.mox.StubOutWithMock(gs_offloader, '_get_cmd_list')
578 alarm = mock.patch('signal.alarm', return_value=0)
579 alarm.start()
580 self.addCleanup(alarm.stop)
Ningning Xia2d981ee2016-07-06 17:59:54 -0700581 self.mox.StubOutWithMock(models.test, 'parse_job_keyval')
Xinan Line1dc6962019-09-20 15:43:52 -0700582 self.should_remove_sarming_req_dir = False
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700583
Jakob Juelich24f22c22014-09-26 11:46:11 -0700584
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700585 def tearDown(self):
586 logging.getLogger().setLevel(self._saved_loglevel)
587 super(OffloadDirectoryTests, self).tearDown()
588
Allen Lib41527d2017-06-22 17:28:00 -0700589 def _mock__upload_cts_testresult(self):
590 self.mox.StubOutWithMock(gs_offloader, '_upload_cts_testresult')
591 gs_offloader._upload_cts_testresult(
Ningning Xia42111242016-06-15 14:35:58 -0700592 mox.IgnoreArg(),mox.IgnoreArg()).AndReturn(None)
Jakob Juelich24f22c22014-09-26 11:46:11 -0700593
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800594 def _mock_create_marker_file(self):
595 self.mox.StubOutWithMock(__builtin__, 'open')
Allen Li9579b382017-05-05 17:07:43 -0700596 open(mox.IgnoreArg(), 'a').AndReturn(mock.MagicMock())
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800597
598
599 def _mock_offload_dir_calls(self, command, queue_args,
Allen Lib41527d2017-06-22 17:28:00 -0700600 marker_initially_exists=False):
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700601 """Mock out the calls needed by `offload_dir()`.
602
603 This covers only the calls made when there is no timeout.
604
605 @param command Command list to be returned by the mocked
Allen Lib41527d2017-06-22 17:28:00 -0700606 call to `_get_cmd_list()`.
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700607
608 """
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800609 self.mox.StubOutWithMock(os.path, 'isfile')
610 os.path.isfile(mox.IgnoreArg()).AndReturn(marker_initially_exists)
Simran Basidd129972014-09-11 14:34:49 -0700611 command.append(queue_args[0])
Allen Lib41527d2017-06-22 17:28:00 -0700612 gs_offloader._get_cmd_list(
MK Ryue93c8572015-08-11 11:53:00 -0700613 False, queue_args[0],
614 '%s%s' % (utils.DEFAULT_OFFLOAD_GSURI,
615 queue_args[1])).AndReturn(command)
Allen Lib41527d2017-06-22 17:28:00 -0700616 self._mock__upload_cts_testresult()
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700617
Jakob Juelich24f22c22014-09-26 11:46:11 -0700618
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800619 def _run_offload_dir(self, should_succeed, delete_age):
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700620 """Make one call to `offload_dir()`.
621
622 The caller ensures all mocks are set up already.
623
624 @param should_succeed True iff the call to `offload_dir()`
625 is expected to succeed and remove the
626 offloaded job directory.
627
628 """
629 self.mox.ReplayAll()
Allen Lib41527d2017-06-22 17:28:00 -0700630 gs_offloader.GSOffloader(
631 utils.DEFAULT_OFFLOAD_GSURI, False, delete_age).offload(
MK Ryue93c8572015-08-11 11:53:00 -0700632 self._job.queue_args[0],
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800633 self._job.queue_args[1],
634 self._job.queue_args[2])
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700635 self.mox.VerifyAll()
636 self.assertEqual(not should_succeed,
637 os.path.isdir(self._job.queue_args[0]))
Xinan Line1dc6962019-09-20 15:43:52 -0700638 swarming_req_dir = gs_offloader._get_swarming_req_dir(
639 self._job.queue_args[0])
640 if swarming_req_dir:
641 self.assertEqual(not self.should_remove_sarming_req_dir,
642 os.path.exists(swarming_req_dir))
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700643
Jakob Juelich24f22c22014-09-26 11:46:11 -0700644
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700645 def test_offload_success(self):
646 """Test that `offload_dir()` can succeed correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700647 self._mock_offload_dir_calls(['test', '-d'],
648 self._job.queue_args)
Allen Lib41527d2017-06-22 17:28:00 -0700649 os.path.isfile(mox.IgnoreArg()).AndReturn(True)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800650 self._mock_create_marker_file()
651 self._run_offload_dir(True, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700652
Jakob Juelich24f22c22014-09-26 11:46:11 -0700653
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700654 def test_offload_failure(self):
655 """Test that `offload_dir()` can fail correctly."""
Simran Basidd129972014-09-11 14:34:49 -0700656 self._mock_offload_dir_calls(['test', '!', '-d'],
Allen Lib41527d2017-06-22 17:28:00 -0700657 self._job.queue_args)
Keith Haddow5ba5fb82016-11-09 11:39:36 -0800658 self._run_offload_dir(False, 0)
J. Richard Barnette2e443ef2014-05-20 12:31:35 -0700659
660
Xinan Line1dc6962019-09-20 15:43:52 -0700661 def test_offload_swarming_req_dir_remove(self):
662 """Test that `offload_dir()` can prune the empty swarming task dir."""
663 should_remove = os.path.join('results', 'swarming-123abc0')
664 self._job = self.make_job(os.path.join(should_remove, '1'))
665 self._mock_offload_dir_calls(['test', '-d'],
666 self._job.queue_args)
667
668 os.path.isfile(mox.IgnoreArg()).AndReturn(True)
669 self.should_remove_sarming_req_dir = True
670 self._mock_create_marker_file()
671 self._run_offload_dir(True, 0)
672
673
674 def test_offload_swarming_req_dir_exist(self):
675 """Test that `offload_dir()` keeps the non-empty swarming task dir."""
676 should_not_remove = os.path.join('results', 'swarming-456edf0')
677 self._job = self.make_job(os.path.join(should_not_remove, '1'))
678 self.make_job(os.path.join(should_not_remove, '2'))
679 self._mock_offload_dir_calls(['test', '-d'],
680 self._job.queue_args)
681
682 os.path.isfile(mox.IgnoreArg()).AndReturn(True)
683 self.should_remove_sarming_req_dir = False
684 self._mock_create_marker_file()
685 self._run_offload_dir(True, 0)
686
687
Dan Shiaffb9222015-04-15 17:05:47 -0700688 def test_sanitize_dir(self):
689 """Test that folder/file name with invalid character can be corrected.
690 """
691 results_folder = tempfile.mkdtemp()
Allen Lib41527d2017-06-22 17:28:00 -0700692 invalid_chars = '_'.join(['[', ']', '*', '?', '#'])
Dan Shiaffb9222015-04-15 17:05:47 -0700693 invalid_files = []
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800694 invalid_folder_name = 'invalid_name_folder_%s' % invalid_chars
Dan Shiaffb9222015-04-15 17:05:47 -0700695 invalid_folder = os.path.join(
696 results_folder,
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800697 invalid_folder_name)
Dan Shiaffb9222015-04-15 17:05:47 -0700698 invalid_files.append(os.path.join(
699 invalid_folder,
700 'invalid_name_file_%s' % invalid_chars))
Dan Shiaffb9222015-04-15 17:05:47 -0700701 good_folder = os.path.join(results_folder, 'valid_name_folder')
702 good_file = os.path.join(good_folder, 'valid_name_file')
703 for folder in [invalid_folder, good_folder]:
704 os.makedirs(folder)
705 for f in invalid_files + [good_file]:
706 with open(f, 'w'):
707 pass
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800708 # check that broken symlinks don't break sanitization
709 symlink = os.path.join(invalid_folder, 'broken-link')
710 os.symlink(os.path.join(results_folder, 'no-such-file'),
711 symlink)
712 fifo1 = os.path.join(results_folder, 'test_fifo1')
713 fifo2 = os.path.join(good_folder, 'test_fifo2')
714 fifo3 = os.path.join(invalid_folder, 'test_fifo3')
715 invalid_fifo4_name = 'test_fifo4_%s' % invalid_chars
716 fifo4 = os.path.join(invalid_folder, invalid_fifo4_name)
717 os.mkfifo(fifo1)
718 os.mkfifo(fifo2)
719 os.mkfifo(fifo3)
720 os.mkfifo(fifo4)
Dan Shiaffb9222015-04-15 17:05:47 -0700721 gs_offloader.sanitize_dir(results_folder)
722 for _, dirs, files in os.walk(results_folder):
723 for name in dirs + files:
Allen Lib41527d2017-06-22 17:28:00 -0700724 self.assertEqual(name, gslib.escape(name))
Dan Shiaffb9222015-04-15 17:05:47 -0700725 for c in name:
Allen Lib41527d2017-06-22 17:28:00 -0700726 self.assertFalse(c in ['[', ']', '*', '?', '#'])
Dan Shiaffb9222015-04-15 17:05:47 -0700727 self.assertTrue(os.path.exists(good_file))
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800728
729 self.assertTrue(os.path.exists(fifo1))
730 self.assertFalse(is_fifo(fifo1))
731 self.assertTrue(os.path.exists(fifo2))
732 self.assertFalse(is_fifo(fifo2))
733 corrected_folder = os.path.join(
Allen Lib41527d2017-06-22 17:28:00 -0700734 results_folder, gslib.escape(invalid_folder_name))
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800735 corrected_fifo3 = os.path.join(
736 corrected_folder,
737 'test_fifo3')
738 self.assertFalse(os.path.exists(fifo3))
739 self.assertTrue(os.path.exists(corrected_fifo3))
740 self.assertFalse(is_fifo(corrected_fifo3))
741 corrected_fifo4 = os.path.join(
Allen Lib41527d2017-06-22 17:28:00 -0700742 corrected_folder, gslib.escape(invalid_fifo4_name))
Laurence Goodbyca7726d2017-02-14 17:09:07 -0800743 self.assertFalse(os.path.exists(fifo4))
744 self.assertTrue(os.path.exists(corrected_fifo4))
745 self.assertFalse(is_fifo(corrected_fifo4))
746
747 corrected_symlink = os.path.join(
748 corrected_folder,
749 'broken-link')
750 self.assertFalse(os.path.lexists(symlink))
751 self.assertTrue(os.path.exists(corrected_symlink))
752 self.assertFalse(os.path.islink(corrected_symlink))
Dan Shiaffb9222015-04-15 17:05:47 -0700753 shutil.rmtree(results_folder)
754
755
Dan Shi1b4c7c32015-10-05 10:38:57 -0700756 def check_limit_file_count(self, is_test_job=True):
757 """Test that folder with too many files can be compressed.
758
759 @param is_test_job: True to check the method with test job result
760 folder. Set to False for special task folder.
761 """
762 results_folder = tempfile.mkdtemp()
763 host_folder = os.path.join(
764 results_folder,
765 'lab1-host1' if is_test_job else 'hosts/lab1-host1/1-repair')
766 debug_folder = os.path.join(host_folder, 'debug')
767 sysinfo_folder = os.path.join(host_folder, 'sysinfo')
768 for folder in [debug_folder, sysinfo_folder]:
769 os.makedirs(folder)
770 for i in range(10):
771 with open(os.path.join(folder, str(i)), 'w') as f:
772 f.write('test')
773
Allen Lib41527d2017-06-22 17:28:00 -0700774 gs_offloader._MAX_FILE_COUNT = 100
Dan Shi1b4c7c32015-10-05 10:38:57 -0700775 gs_offloader.limit_file_count(
776 results_folder if is_test_job else host_folder)
777 self.assertTrue(os.path.exists(sysinfo_folder))
778
Allen Lib41527d2017-06-22 17:28:00 -0700779 gs_offloader._MAX_FILE_COUNT = 10
Dan Shi1b4c7c32015-10-05 10:38:57 -0700780 gs_offloader.limit_file_count(
781 results_folder if is_test_job else host_folder)
782 self.assertFalse(os.path.exists(sysinfo_folder))
783 self.assertTrue(os.path.exists(sysinfo_folder + '.tgz'))
784 self.assertTrue(os.path.exists(debug_folder))
785
786 shutil.rmtree(results_folder)
787
788
789 def test_limit_file_count(self):
790 """Test that folder with too many files can be compressed.
791 """
792 self.check_limit_file_count(is_test_job=True)
793 self.check_limit_file_count(is_test_job=False)
794
Ningning Xia2d88eec2016-07-25 23:18:46 -0700795
Ningning Xia8db632f2016-08-19 11:01:35 -0700796 def test_is_valid_result(self):
797 """Test _is_valid_result."""
Ningning Xia21922c82016-07-29 11:03:15 -0700798 release_build = 'veyron_minnie-cheets-release/R52-8248.0.0'
799 pfq_build = 'cyan-cheets-android-pfq/R54-8623.0.0-rc1'
800 trybot_build = 'trybot-samus-release/R54-8640.0.0-b5092'
801 trybot_2_build = 'trybot-samus-pfq/R54-8640.0.0-b5092'
802 release_2_build = 'test-trybot-release/R54-8640.0.0-b5092'
Ningning Xia8db632f2016-08-19 11:01:35 -0700803 self.assertTrue(gs_offloader._is_valid_result(
804 release_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
Rohit Makasana6384c102016-10-21 17:09:47 -0700805 self.assertTrue(gs_offloader._is_valid_result(
806 release_build, gs_offloader.CTS_RESULT_PATTERN, 'test_that_wrapper'))
Rohit Makasana8d868c92018-06-08 11:29:50 -0700807 self.assertTrue(gs_offloader._is_valid_result(
Rohit Makasana54c78f72019-09-17 17:20:03 -0700808 release_build, gs_offloader.CTS_RESULT_PATTERN, 'cros_test_platform'))
809 self.assertTrue(gs_offloader._is_valid_result(
Richard Barnette33e12892017-05-26 09:25:34 -0700810 release_build, gs_offloader.CTS_RESULT_PATTERN, 'bvt-arc'))
Rohit Makasana8d868c92018-06-08 11:29:50 -0700811 self.assertFalse(gs_offloader._is_valid_result(
812 release_build, gs_offloader.CTS_RESULT_PATTERN, 'bvt-cq'))
Ningning Xia8db632f2016-08-19 11:01:35 -0700813 self.assertTrue(gs_offloader._is_valid_result(
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800814 release_build, gs_offloader.CTS_V2_RESULT_PATTERN, 'arc-gts'))
Ningning Xia8db632f2016-08-19 11:01:35 -0700815 self.assertFalse(gs_offloader._is_valid_result(
816 None, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
817 self.assertFalse(gs_offloader._is_valid_result(
818 release_build, gs_offloader.CTS_RESULT_PATTERN, None))
819 self.assertFalse(gs_offloader._is_valid_result(
820 pfq_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
821 self.assertFalse(gs_offloader._is_valid_result(
822 trybot_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
823 self.assertFalse(gs_offloader._is_valid_result(
824 trybot_2_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
825 self.assertTrue(gs_offloader._is_valid_result(
826 release_2_build, gs_offloader.CTS_RESULT_PATTERN, 'arc-cts'))
Ningning Xia21922c82016-07-29 11:03:15 -0700827
828
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700829 def create_results_folder(self):
830 """Create CTS/GTS results folders."""
Ningning Xia42111242016-06-15 14:35:58 -0700831 results_folder = tempfile.mkdtemp()
832 host_folder = os.path.join(results_folder, 'chromeos4-row9-rack11-host22')
833 debug_folder = os.path.join(host_folder, 'debug')
834 sysinfo_folder = os.path.join(host_folder, 'sysinfo')
835 cts_result_folder = os.path.join(
836 host_folder, 'cheets_CTS.android.dpi', 'results', 'cts-results')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800837 cts_v2_result_folder = os.path.join(host_folder,
838 'cheets_CTS_N.CtsGraphicsTestCases', 'results', 'android-cts')
Ningning Xia2d88eec2016-07-25 23:18:46 -0700839 gts_result_folder = os.path.join(
Ilja H. Friedelbfa63142017-01-26 00:56:29 -0800840 host_folder, 'cheets_GTS.google.admin', 'results', 'android-gts')
Ningning Xia42111242016-06-15 14:35:58 -0700841 timestamp_str = '2016.04.28_01.41.44'
Ningning Xia2d88eec2016-07-25 23:18:46 -0700842 timestamp_cts_folder = os.path.join(cts_result_folder, timestamp_str)
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800843 timestamp_cts_v2_folder = os.path.join(cts_v2_result_folder, timestamp_str)
Ningning Xia2d88eec2016-07-25 23:18:46 -0700844 timestamp_gts_folder = os.path.join(gts_result_folder, timestamp_str)
845
Rohit Makasana2030dde2019-04-05 04:18:52 -0700846 # Build host keyvals set to parse model info.
847 host_info_path = os.path.join(host_folder, 'host_keyvals')
848 dir_to_create = '/'
849 for tdir in host_info_path.split('/'):
850 dir_to_create = os.path.join(dir_to_create, tdir)
851 if not os.path.exists(dir_to_create):
852 os.mkdir(dir_to_create)
853 with open(os.path.join(host_info_path, 'chromeos4-row9-rack11-host22'), 'w') as store_file:
854 store_file.write('labels=board%3Acoral,hw_video_acc_vp9,cros,'+
855 'hw_jpeg_acc_dec,bluetooth,model%3Arobo360,'+
856 'accel%3Acros-ec,'+
857 'sku%3Arobo360_IntelR_CeleronR_CPU_N3450_1_10GHz_4Gb')
858
859 # .autoserv_execute file is needed for the test results package to look
860 # legit.
861 autoserve_path = os.path.join(host_folder, '.autoserv_execute')
862 with open(autoserve_path, 'w') as temp_file:
863 temp_file.write(' ')
864
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700865 # Test results in cts_result_folder with a different time-stamp.
866 timestamp_str_2 = '2016.04.28_10.41.44'
867 timestamp_cts_folder_2 = os.path.join(cts_result_folder, timestamp_str_2)
868
Ningning Xia2d88eec2016-07-25 23:18:46 -0700869 for folder in [debug_folder, sysinfo_folder, cts_result_folder,
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800870 timestamp_cts_folder, timestamp_cts_folder_2,
871 timestamp_cts_v2_folder, timestamp_gts_folder]:
Ningning Xia42111242016-06-15 14:35:58 -0700872 os.makedirs(folder)
Ningning Xia2d88eec2016-07-25 23:18:46 -0700873
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700874 path_pattern_pair = [(timestamp_cts_folder, gs_offloader.CTS_RESULT_PATTERN),
875 (timestamp_cts_folder_2, gs_offloader.CTS_RESULT_PATTERN),
Rohit Makasanac1c7e782019-04-02 18:06:00 -0700876 (timestamp_cts_folder_2, gs_offloader.CTS_COMPRESSED_RESULT_PATTERN),
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800877 (timestamp_cts_v2_folder, gs_offloader.CTS_V2_RESULT_PATTERN),
Rohit Makasanac1c7e782019-04-02 18:06:00 -0700878 (timestamp_cts_v2_folder, gs_offloader.CTS_V2_COMPRESSED_RESULT_PATTERN),
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800879 (timestamp_gts_folder, gs_offloader.CTS_V2_RESULT_PATTERN)]
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700880
881 # Create timestamp.zip file_path.
Ningning Xia2d88eec2016-07-25 23:18:46 -0700882 cts_zip_file = os.path.join(cts_result_folder, timestamp_str + '.zip')
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700883 cts_zip_file_2 = os.path.join(cts_result_folder, timestamp_str_2 + '.zip')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800884 cts_v2_zip_file = os.path.join(cts_v2_result_folder, timestamp_str + '.zip')
Ningning Xia2d88eec2016-07-25 23:18:46 -0700885 gts_zip_file = os.path.join(gts_result_folder, timestamp_str + '.zip')
Ningning Xia2d88eec2016-07-25 23:18:46 -0700886
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700887 # Create xml file_path.
888 cts_result_file = os.path.join(timestamp_cts_folder, 'testResult.xml')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800889 cts_result_file_2 = os.path.join(timestamp_cts_folder_2,
890 'testResult.xml')
Rohit Makasanac1c7e782019-04-02 18:06:00 -0700891 cts_result_compressed_file_2 = os.path.join(timestamp_cts_folder_2,
892 'testResult.xml.tgz')
Ilja H. Friedelbfa63142017-01-26 00:56:29 -0800893 gts_result_file = os.path.join(timestamp_gts_folder, 'test_result.xml')
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800894 cts_v2_result_file = os.path.join(timestamp_cts_v2_folder,
895 'test_result.xml')
Rohit Makasanac1c7e782019-04-02 18:06:00 -0700896 cts_v2_result_compressed_file = os.path.join(timestamp_cts_v2_folder,
897 'test_result.xml.tgz')
Ningning Xia2d88eec2016-07-25 23:18:46 -0700898
Ilja H. Friedel73cf6cd2017-03-01 12:23:00 -0800899 for file_path in [cts_zip_file, cts_zip_file_2, cts_v2_zip_file,
900 gts_zip_file, cts_result_file, cts_result_file_2,
Rohit Makasanac1c7e782019-04-02 18:06:00 -0700901 cts_result_compressed_file_2, gts_result_file,
902 cts_v2_result_file, cts_v2_result_compressed_file]:
903 if file_path.endswith('tgz'):
904 test_result_file = gs_offloader.CTS_COMPRESSED_RESULT_TYPES[
905 os.path.basename(file_path)]
906 with open(test_result_file, 'w') as f:
907 f.write('test')
908 with tarfile.open(file_path, 'w:gz') as tar_file:
909 tar_file.add(test_result_file)
910 os.remove(test_result_file)
911 else:
912 with open(file_path, 'w') as f:
913 f.write('test')
Ningning Xia42111242016-06-15 14:35:58 -0700914
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700915 return (results_folder, host_folder, path_pattern_pair)
Ningning Xia2d88eec2016-07-25 23:18:46 -0700916
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700917
Allen Lib41527d2017-06-22 17:28:00 -0700918 def test__upload_cts_testresult(self):
919 """Test _upload_cts_testresult."""
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700920 results_folder, host_folder, path_pattern_pair = self.create_results_folder()
921
922 self.mox.StubOutWithMock(gs_offloader, '_upload_files')
923 gs_offloader._upload_files(
Rohit Makasanaea337c52018-04-11 18:03:41 -0700924 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False,
925 mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700926 ['test', '-d', host_folder])
927 gs_offloader._upload_files(
Rohit Makasanaea337c52018-04-11 18:03:41 -0700928 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False,
929 mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700930 ['test', '-d', host_folder])
931 gs_offloader._upload_files(
Rohit Makasanaea337c52018-04-11 18:03:41 -0700932 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(), False,
933 mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700934 ['test', '-d', host_folder])
Ningning Xia2d88eec2016-07-25 23:18:46 -0700935
Ningning Xia42111242016-06-15 14:35:58 -0700936 self.mox.ReplayAll()
Allen Lib41527d2017-06-22 17:28:00 -0700937 gs_offloader._upload_cts_testresult(results_folder, False)
Ningning Xia42111242016-06-15 14:35:58 -0700938 self.mox.VerifyAll()
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700939 shutil.rmtree(results_folder)
940
941
Keith Haddow295a54e2019-07-10 15:27:17 -0700942 def test_parse_cts_job_results_file_path(self):
943 # A autotest path
944 path = ('/317739475-chromeos-test/chromeos4-row9-rack11-host22/'
945 'cheets_CTS.android.dpi/results/cts-results/'
946 '2016.04.28_01.41.44')
947 job_id, package, timestamp = \
948 gs_offloader._parse_cts_job_results_file_path(path)
949 self.assertEqual('317739475-chromeos-test', job_id)
950 self.assertEqual('cheets_CTS.android.dpi', package)
951 self.assertEqual('2016.04.28_01.41.44', timestamp)
952
953
954 # A skylab path
Prathmesh Prabhu174640b2019-10-17 21:30:07 +0000955 path = ('/swarming-458e3a3a7fc6f210/1/autoserv_test/'
Keith Haddow295a54e2019-07-10 15:27:17 -0700956 'cheets_CTS.android.dpi/results/cts-results/'
957 '2016.04.28_01.41.44')
958 job_id, package, timestamp = \
959 gs_offloader._parse_cts_job_results_file_path(path)
Prathmesh Prabhu174640b2019-10-17 21:30:07 +0000960 self.assertEqual('swarming-458e3a3a7fc6f210-1', job_id)
Keith Haddow295a54e2019-07-10 15:27:17 -0700961 self.assertEqual('cheets_CTS.android.dpi', package)
962 self.assertEqual('2016.04.28_01.41.44', timestamp)
963
964
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700965 def test_upload_files(self):
966 """Test upload_files"""
967 results_folder, host_folder, path_pattern_pair = self.create_results_folder()
968
969 for path, pattern in path_pattern_pair:
970 models.test.parse_job_keyval(mox.IgnoreArg()).AndReturn({
971 'build': 'veyron_minnie-cheets-release/R52-8248.0.0',
Rohit Makasana2030dde2019-04-05 04:18:52 -0700972 'hostname': 'chromeos4-row9-rack11-host22',
Ningning Xia8db632f2016-08-19 11:01:35 -0700973 'parent_job_id': 'p_id',
974 'suite': 'arc-cts'
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700975 })
976
Allen Lib41527d2017-06-22 17:28:00 -0700977 gs_offloader._get_cmd_list(
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700978 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
979 ['test', '-d', path])
Allen Lib41527d2017-06-22 17:28:00 -0700980 gs_offloader._get_cmd_list(
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700981 False, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
982 ['test', '-d', path])
983
984 self.mox.ReplayAll()
Rohit Makasanaea337c52018-04-11 18:03:41 -0700985 gs_offloader._upload_files(host_folder, path, pattern, False,
986 'gs://a-test-bucket/',
987 'gs://a-test-apfe-bucket/')
Ningning Xia0c27d9b2016-08-04 14:02:39 -0700988 self.mox.VerifyAll()
989 self.mox.ResetAll()
Ningning Xia42111242016-06-15 14:35:58 -0700990
991 shutil.rmtree(results_folder)
Dan Shi1b4c7c32015-10-05 10:38:57 -0700992
Michael Tang97d188c2016-06-25 11:18:42 -0700993
Dan Shi02dd0662017-05-23 11:24:32 -0700994 def test_get_metrics_fields(self):
995 """Test method _get_metrics_fields."""
996 results_folder, host_folder, _ = self.create_results_folder()
997 models.test.parse_job_keyval(mox.IgnoreArg()).AndReturn({
998 'build': 'veyron_minnie-cheets-release/R52-8248.0.0',
999 'parent_job_id': 'p_id',
1000 'suite': 'arc-cts'
1001 })
1002 try:
1003 self.mox.ReplayAll()
1004 self.assertEqual({'board': 'veyron_minnie-cheets',
1005 'milestone': 'R52'},
1006 gs_offloader._get_metrics_fields(host_folder))
1007 self.mox.VerifyAll()
1008 finally:
1009 shutil.rmtree(results_folder)
1010
1011
Xinan Lin9eb782f2019-10-23 23:14:49 -07001012class OffladerConfigTests(_TempResultsDirTestBase):
1013 """Tests for the `Offloader` to follow side_effect config."""
1014
1015 def setUp(self):
1016 super(OffladerConfigTests, self).setUp()
1017 gs_offloader.GS_OFFLOADING_ENABLED = True
1018 gs_offloader.GS_OFFLOADER_MULTIPROCESSING = True
1019 self.dest_path = '/results'
1020 self.mox.StubOutWithMock(gs_offloader, '_get_metrics_fields')
1021 self.mox.StubOutWithMock(gs_offloader, '_OffloadError')
1022 self.mox.StubOutWithMock(gs_offloader, '_upload_cts_testresult')
1023 self.mox.StubOutWithMock(gs_offloader, '_emit_offload_metrics')
1024 self.mox.StubOutWithMock(gs_offloader, '_get_cmd_list')
1025 self.mox.StubOutWithMock(subprocess, 'Popen')
1026 self.mox.StubOutWithMock(gs_offloader, '_emit_gs_returncode_metric')
1027
1028
1029 def _run(self, results_dir, gs_bucket, expect_dest, cts_enabled):
1030 stdout = os.path.join(results_dir, 'std.log')
1031 stderr = os.path.join(results_dir, 'std.err')
1032 config = {
1033 'tko': {
1034 'proxy_socket': '/file-system/foo-socket',
1035 'mysql_user': 'foo-user',
1036 'mysql_password_file': '/file-system/foo-password-file'
1037 },
1038 'google_storage': {
1039 'bucket': gs_bucket,
1040 'credentials_file': '/foo-creds'
1041 },
1042 'cts': {
1043 'enabled': cts_enabled,
1044 },
1045 'this_field_is_ignored': True
1046 }
1047 path = os.path.join(results_dir, 'side_effects_config.json')
1048 with open(path, 'w') as f:
1049 f.write(json.dumps(config))
1050 gs_offloader._get_metrics_fields(results_dir)
1051 if cts_enabled:
1052 gs_offloader._upload_cts_testresult(results_dir, True)
1053 gs_offloader._get_cmd_list(
1054 True,
1055 mox.IgnoreArg(),
1056 expect_dest).AndReturn(['test', '-d', expect_dest])
1057 subprocess.Popen(mox.IgnoreArg(),
1058 stdout=stdout,
1059 stderr=stderr).AndReturn(_get_fake_process())
1060 gs_offloader._OffloadError(mox.IgnoreArg())
1061 gs_offloader._emit_gs_returncode_metric(mox.IgnoreArg()).AndReturn(True)
1062 gs_offloader._emit_offload_metrics(mox.IgnoreArg()).AndReturn(True)
1063 sub_offloader = gs_offloader.GSOffloader(results_dir, True, 0, None)
Aviv Keshetacef00e2019-11-13 16:35:29 -08001064 subprocess.Popen(mox.IgnoreArg(),
1065 stdout=stdout,
1066 stderr=stderr).AndReturn(_get_fake_process())
Xinan Lin9eb782f2019-10-23 23:14:49 -07001067 self.mox.ReplayAll()
1068 sub_offloader._try_offload(results_dir, self.dest_path, stdout, stderr)
1069 self.mox.VerifyAll()
1070 self.mox.ResetAll()
1071 shutil.rmtree(results_dir)
1072
1073
1074 def test_upload_files_to_dev(self):
1075 """Test upload results to dev gs bucket and skip cts uploading."""
1076 res = tempfile.mkdtemp()
1077 gs_bucket = 'dev-bucket'
1078 expect_dest = 'gs://' + gs_bucket + self.dest_path
1079 self._run(res, gs_bucket, expect_dest, False)
1080
1081
1082 def test_upload_files_prod(self):
1083 """Test upload results to the prod gs bucket and also upload to cts."""
1084 res = tempfile.mkdtemp()
1085 gs_bucket = 'prod-bucket'
1086 expect_dest = 'gs://' + gs_bucket + self.dest_path
1087 self._run(res, gs_bucket, expect_dest, True)
1088
1089
1090 def test_skip_gs_prefix(self):
1091 """Test skip the 'gs://' prefix if already presented."""
1092 res = tempfile.mkdtemp()
1093 gs_bucket = 'gs://prod-bucket'
1094 expect_dest = gs_bucket + self.dest_path
1095 self._run(res, gs_bucket, expect_dest, True)
1096
1097
J. Richard Barnetteea785362014-03-17 16:00:53 -07001098class JobDirectoryOffloadTests(_TempResultsDirTestBase):
1099 """Tests for `_JobDirectory.enqueue_offload()`.
1100
1101 When testing with a `days_old` parameter of 0, we use
1102 `set_finished()` instead of `set_expired()`. This causes the
1103 job's timestamp to be set in the future. This is done so as
1104 to test that when `days_old` is 0, the job is always treated
1105 as eligible for offload, regardless of the timestamp's value.
1106
1107 Testing covers the following assertions:
1108 A. Each time `enqueue_offload()` is called, a message that
1109 includes the job's directory name will be logged using
1110 `logging.debug()`, regardless of whether the job was
1111 enqueued. Nothing else is allowed to be logged.
1112 B. If the job is not eligible to be offloaded,
Allen Lib41527d2017-06-22 17:28:00 -07001113 `first_offload_start` and `offload_count` are 0.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001114 C. If the job is not eligible for offload, nothing is
1115 enqueued in `queue`.
Allen Lib41527d2017-06-22 17:28:00 -07001116 D. When the job is offloaded, `offload_count` increments
J. Richard Barnetteea785362014-03-17 16:00:53 -07001117 each time.
1118 E. When the job is offloaded, the appropriate parameters are
1119 enqueued exactly once.
Allen Lib41527d2017-06-22 17:28:00 -07001120 F. The first time a job is offloaded, `first_offload_start` is
J. Richard Barnetteea785362014-03-17 16:00:53 -07001121 set to the current time.
Allen Lib41527d2017-06-22 17:28:00 -07001122 G. `first_offload_start` only changes the first time that the
J. Richard Barnetteea785362014-03-17 16:00:53 -07001123 job is offloaded.
1124
1125 The test cases below are designed to exercise all of the
1126 meaningful state transitions at least once.
1127
1128 """
1129
1130 def setUp(self):
1131 super(JobDirectoryOffloadTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -07001132 self._job = self.make_job(self.REGULAR_JOBLIST[0])
J. Richard Barnetteea785362014-03-17 16:00:53 -07001133 self._queue = Queue.Queue()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001134
Jakob Juelich24f22c22014-09-26 11:46:11 -07001135
J. Richard Barnetteea785362014-03-17 16:00:53 -07001136 def _offload_unexpired_job(self, days_old):
1137 """Make calls to `enqueue_offload()` for an unexpired job.
1138
1139 This method tests assertions B and C that calling
1140 `enqueue_offload()` has no effect.
1141
1142 """
Allen Lib41527d2017-06-22 17:28:00 -07001143 self.assertEqual(self._job.offload_count, 0)
1144 self.assertEqual(self._job.first_offload_start, 0)
1145 gs_offloader._enqueue_offload(self._job, self._queue, days_old)
1146 gs_offloader._enqueue_offload(self._job, self._queue, days_old)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001147 self.assertTrue(self._queue.empty())
Allen Lib41527d2017-06-22 17:28:00 -07001148 self.assertEqual(self._job.offload_count, 0)
1149 self.assertEqual(self._job.first_offload_start, 0)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001150
Jakob Juelich24f22c22014-09-26 11:46:11 -07001151
J. Richard Barnetteea785362014-03-17 16:00:53 -07001152 def _offload_expired_once(self, days_old, count):
1153 """Make one call to `enqueue_offload()` for an expired job.
1154
1155 This method tests assertions D and E regarding side-effects
1156 expected when a job is offloaded.
1157
1158 """
Allen Lib41527d2017-06-22 17:28:00 -07001159 gs_offloader._enqueue_offload(self._job, self._queue, days_old)
1160 self.assertEqual(self._job.offload_count, count)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001161 self.assertFalse(self._queue.empty())
1162 v = self._queue.get_nowait()
1163 self.assertTrue(self._queue.empty())
J. Richard Barnette2c72ddd2014-05-20 12:17:37 -07001164 self.assertEqual(v, self._job.queue_args)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001165
Jakob Juelich24f22c22014-09-26 11:46:11 -07001166
J. Richard Barnetteea785362014-03-17 16:00:53 -07001167 def _offload_expired_job(self, days_old):
1168 """Make calls to `enqueue_offload()` for a just-expired job.
1169
1170 This method directly tests assertions F and G regarding
Allen Lib41527d2017-06-22 17:28:00 -07001171 side-effects on `first_offload_start`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001172
1173 """
1174 t0 = time.time()
1175 self._offload_expired_once(days_old, 1)
Allen Lib41527d2017-06-22 17:28:00 -07001176 t1 = self._job.first_offload_start
J. Richard Barnetteea785362014-03-17 16:00:53 -07001177 self.assertLessEqual(t1, time.time())
1178 self.assertGreaterEqual(t1, t0)
1179 self._offload_expired_once(days_old, 2)
Allen Lib41527d2017-06-22 17:28:00 -07001180 self.assertEqual(self._job.first_offload_start, t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001181 self._offload_expired_once(days_old, 3)
Allen Lib41527d2017-06-22 17:28:00 -07001182 self.assertEqual(self._job.first_offload_start, t1)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001183
Jakob Juelich24f22c22014-09-26 11:46:11 -07001184
J. Richard Barnetteea785362014-03-17 16:00:53 -07001185 def test_case_1_no_expiration(self):
1186 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
1187
1188 This tests that offload works as expected if calls are
1189 made both before and after the job becomes expired.
1190
1191 """
1192 self._offload_unexpired_job(0)
1193 self._job.set_finished(0)
1194 self._offload_expired_job(0)
1195
Jakob Juelich24f22c22014-09-26 11:46:11 -07001196
J. Richard Barnetteea785362014-03-17 16:00:53 -07001197 def test_case_2_no_expiration(self):
1198 """Test a series of `enqueue_offload()` calls with `days_old` of 0.
1199
1200 This tests that offload works as expected if calls are made
1201 only after the job becomes expired.
1202
1203 """
1204 self._job.set_finished(0)
1205 self._offload_expired_job(0)
1206
Jakob Juelich24f22c22014-09-26 11:46:11 -07001207
J. Richard Barnetteea785362014-03-17 16:00:53 -07001208 def test_case_1_with_expiration(self):
1209 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1210
1211 This tests that offload works as expected if calls are made
1212 before the job finishes, before the job expires, and after
1213 the job expires.
1214
1215 """
1216 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1217 self._job.set_finished(_TEST_EXPIRATION_AGE)
1218 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1219 self._job.set_expired(_TEST_EXPIRATION_AGE)
1220 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1221
Jakob Juelich24f22c22014-09-26 11:46:11 -07001222
J. Richard Barnetteea785362014-03-17 16:00:53 -07001223 def test_case_2_with_expiration(self):
1224 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1225
1226 This tests that offload works as expected if calls are made
1227 between finishing and expiration, and after the job expires.
1228
1229 """
1230 self._job.set_finished(_TEST_EXPIRATION_AGE)
1231 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1232 self._job.set_expired(_TEST_EXPIRATION_AGE)
1233 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1234
Jakob Juelich24f22c22014-09-26 11:46:11 -07001235
J. Richard Barnetteea785362014-03-17 16:00:53 -07001236 def test_case_3_with_expiration(self):
1237 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1238
1239 This tests that offload works as expected if calls are made
1240 only before finishing and after expiration.
1241
1242 """
1243 self._offload_unexpired_job(_TEST_EXPIRATION_AGE)
1244 self._job.set_expired(_TEST_EXPIRATION_AGE)
1245 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1246
Jakob Juelich24f22c22014-09-26 11:46:11 -07001247
J. Richard Barnetteea785362014-03-17 16:00:53 -07001248 def test_case_4_with_expiration(self):
1249 """Test a series of `enqueue_offload()` calls with `days_old` non-zero.
1250
1251 This tests that offload works as expected if calls are made
1252 only after expiration.
1253
1254 """
1255 self._job.set_expired(_TEST_EXPIRATION_AGE)
1256 self._offload_expired_job(_TEST_EXPIRATION_AGE)
1257
1258
1259class GetJobDirectoriesTests(_TempResultsDirTestBase):
1260 """Tests for `_JobDirectory.get_job_directories()`."""
1261
J. Richard Barnetteea785362014-03-17 16:00:53 -07001262 def setUp(self):
1263 super(GetJobDirectoriesTests, self).setUp()
J. Richard Barnette08800322014-05-16 14:49:46 -07001264 self.make_job_hierarchy()
1265 os.mkdir('not-a-job')
1266 open('not-a-dir', 'w').close()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001267
Jakob Juelich24f22c22014-09-26 11:46:11 -07001268
J. Richard Barnetteea785362014-03-17 16:00:53 -07001269 def _run_get_directories(self, cls, expected_list):
1270 """Test `get_job_directories()` for the given class.
1271
1272 Calls the method, and asserts that the returned list of
1273 directories matches the expected return value.
1274
1275 @param expected_list Expected return value from the call.
1276 """
J. Richard Barnetteea785362014-03-17 16:00:53 -07001277 dirlist = cls.get_job_directories()
1278 self.assertEqual(set(dirlist), set(expected_list))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001279
Jakob Juelich24f22c22014-09-26 11:46:11 -07001280
J. Richard Barnetteea785362014-03-17 16:00:53 -07001281 def test_get_regular_jobs(self):
1282 """Test `RegularJobDirectory.get_job_directories()`."""
1283 self._run_get_directories(job_directories.RegularJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001284 self.REGULAR_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001285
Jakob Juelich24f22c22014-09-26 11:46:11 -07001286
J. Richard Barnetteea785362014-03-17 16:00:53 -07001287 def test_get_special_jobs(self):
1288 """Test `SpecialJobDirectory.get_job_directories()`."""
1289 self._run_get_directories(job_directories.SpecialJobDirectory,
J. Richard Barnette08800322014-05-16 14:49:46 -07001290 self.SPECIAL_JOBLIST)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001291
1292
1293class AddJobsTests(_TempResultsDirTestBase):
1294 """Tests for `Offloader._add_new_jobs()`."""
1295
J. Richard Barnette08800322014-05-16 14:49:46 -07001296 MOREJOBS = ['115-fubar', '116-fubar', '117-fubar', '118-snafu']
J. Richard Barnetteea785362014-03-17 16:00:53 -07001297
1298 def setUp(self):
1299 super(AddJobsTests, self).setUp()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001300 self._initial_job_names = (
1301 set(self.REGULAR_JOBLIST) | set(self.SPECIAL_JOBLIST))
J. Richard Barnette08800322014-05-16 14:49:46 -07001302 self.make_job_hierarchy()
1303 self._offloader = gs_offloader.Offloader(_get_options(['-a']))
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001304 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001305
Jakob Juelich24f22c22014-09-26 11:46:11 -07001306
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001307 def _run_add_new_jobs(self, expected_key_set):
J. Richard Barnetteea785362014-03-17 16:00:53 -07001308 """Basic test assertions for `_add_new_jobs()`.
1309
1310 Asserts the following:
1311 * The keys in the offloader's `_open_jobs` dictionary
1312 matches the expected set of keys.
1313 * For every job in `_open_jobs`, the job has the expected
1314 directory name.
1315
1316 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001317 count = len(expected_key_set) - len(self._offloader._open_jobs)
1318 logging.debug(mox.IgnoreArg(), count)
1319 self.mox.ReplayAll()
1320 self._offloader._add_new_jobs()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001321 self.assertEqual(expected_key_set,
1322 set(self._offloader._open_jobs.keys()))
1323 for jobkey, job in self._offloader._open_jobs.items():
Allen Lib41527d2017-06-22 17:28:00 -07001324 self.assertEqual(jobkey, job.dirname)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001325 self.mox.VerifyAll()
1326 self.mox.ResetAll()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001327
Jakob Juelich24f22c22014-09-26 11:46:11 -07001328
J. Richard Barnetteea785362014-03-17 16:00:53 -07001329 def test_add_jobs_empty(self):
1330 """Test adding jobs to an empty dictionary.
1331
1332 Calls the offloader's `_add_new_jobs()`, then perform
1333 the assertions of `self._check_open_jobs()`.
1334
1335 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001336 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001337
Jakob Juelich24f22c22014-09-26 11:46:11 -07001338
J. Richard Barnetteea785362014-03-17 16:00:53 -07001339 def test_add_jobs_non_empty(self):
1340 """Test adding jobs to a non-empty dictionary.
1341
1342 Calls the offloader's `_add_new_jobs()` twice; once from
1343 initial conditions, and then again after adding more
1344 directories. After the second call, perform the assertions
1345 of `self._check_open_jobs()`. Additionally, assert that
1346 keys added by the first call still map to their original
1347 job object after the second call.
1348
1349 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001350 self._run_add_new_jobs(self._initial_job_names)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001351 jobs_copy = self._offloader._open_jobs.copy()
J. Richard Barnette08800322014-05-16 14:49:46 -07001352 for d in self.MOREJOBS:
1353 os.mkdir(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001354 self._run_add_new_jobs(self._initial_job_names |
1355 set(self.MOREJOBS))
J. Richard Barnetteea785362014-03-17 16:00:53 -07001356 for key in jobs_copy.keys():
1357 self.assertIs(jobs_copy[key],
1358 self._offloader._open_jobs[key])
1359
1360
J. Richard Barnetteea785362014-03-17 16:00:53 -07001361class ReportingTests(_TempResultsDirTestBase):
Allen Lib41527d2017-06-22 17:28:00 -07001362 """Tests for `Offloader._report_failed_jobs()`."""
J. Richard Barnetteea785362014-03-17 16:00:53 -07001363
J. Richard Barnetteea785362014-03-17 16:00:53 -07001364 def setUp(self):
1365 super(ReportingTests, self).setUp()
1366 self._offloader = gs_offloader.Offloader(_get_options([]))
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001367 self.mox.StubOutWithMock(self._offloader, '_log_failed_jobs_locally')
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001368 self.mox.StubOutWithMock(logging, 'debug')
J. Richard Barnetteea785362014-03-17 16:00:53 -07001369
Jakob Juelich24f22c22014-09-26 11:46:11 -07001370
J. Richard Barnetteea785362014-03-17 16:00:53 -07001371 def _add_job(self, jobdir):
1372 """Add a job to the dictionary of unfinished jobs."""
1373 j = self.make_job(jobdir)
Allen Lib41527d2017-06-22 17:28:00 -07001374 self._offloader._open_jobs[j.dirname] = j
J. Richard Barnetteea785362014-03-17 16:00:53 -07001375 return j
1376
Jakob Juelich24f22c22014-09-26 11:46:11 -07001377
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001378 def _expect_log_message(self, new_open_jobs, with_failures):
1379 """Mock expected logging calls.
1380
Allen Lib41527d2017-06-22 17:28:00 -07001381 `_report_failed_jobs()` logs one message with the number
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001382 of jobs removed from the open job set and the number of jobs
1383 still remaining. Additionally, if there are reportable
1384 jobs, then it logs the number of jobs that haven't yet
1385 offloaded.
1386
1387 This sets up the logging calls using `new_open_jobs` to
1388 figure the job counts. If `with_failures` is true, then
1389 the log message is set up assuming that all jobs in
1390 `new_open_jobs` have offload failures.
1391
1392 @param new_open_jobs New job set for calculating counts
1393 in the messages.
1394 @param with_failures Whether the log message with a
1395 failure count is expected.
1396
1397 """
1398 count = len(self._offloader._open_jobs) - len(new_open_jobs)
1399 logging.debug(mox.IgnoreArg(), count, len(new_open_jobs))
1400 if with_failures:
1401 logging.debug(mox.IgnoreArg(), len(new_open_jobs))
1402
Jakob Juelich24f22c22014-09-26 11:46:11 -07001403
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001404 def _run_update(self, new_open_jobs):
Allen Lib41527d2017-06-22 17:28:00 -07001405 """Call `_report_failed_jobs()`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001406
1407 Initial conditions are set up by the caller. This calls
Allen Lib41527d2017-06-22 17:28:00 -07001408 `_report_failed_jobs()` once, and then checks these
J. Richard Barnetteea785362014-03-17 16:00:53 -07001409 assertions:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001410 * The offloader's new `_open_jobs` field contains only
1411 the entries in `new_open_jobs`.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001412
1413 @param new_open_jobs A dictionary representing the expected
1414 new value of the offloader's
1415 `_open_jobs` field.
1416 """
1417 self.mox.ReplayAll()
Allen Lib41527d2017-06-22 17:28:00 -07001418 self._offloader._report_failed_jobs()
1419 self._offloader._remove_offloaded_jobs()
J. Richard Barnetteea785362014-03-17 16:00:53 -07001420 self.assertEqual(self._offloader._open_jobs, new_open_jobs)
1421 self.mox.VerifyAll()
1422 self.mox.ResetAll()
1423
Jakob Juelich24f22c22014-09-26 11:46:11 -07001424
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001425 def _expect_failed_jobs(self, failed_jobs):
1426 """Mock expected call to log the failed jobs on local disk.
1427
1428 TODO(crbug.com/686904): The fact that we have to mock an internal
1429 function for this test is evidence that we need to pull out the local
1430 file formatter in its own object in a future CL.
1431
1432 @param failed_jobs: The list of jobs being logged as failed.
1433 """
1434 self._offloader._log_failed_jobs_locally(failed_jobs)
1435
1436
J. Richard Barnetteea785362014-03-17 16:00:53 -07001437 def test_no_jobs(self):
Allen Lib41527d2017-06-22 17:28:00 -07001438 """Test `_report_failed_jobs()` with no open jobs.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001439
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001440 Initial conditions are an empty `_open_jobs` list.
1441 Expected result is an empty `_open_jobs` list.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001442
1443 """
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001444 self._expect_log_message({}, False)
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001445 self._expect_failed_jobs([])
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001446 self._run_update({})
J. Richard Barnetteea785362014-03-17 16:00:53 -07001447
Jakob Juelich24f22c22014-09-26 11:46:11 -07001448
J. Richard Barnetteea785362014-03-17 16:00:53 -07001449 def test_all_completed(self):
Allen Lib41527d2017-06-22 17:28:00 -07001450 """Test `_report_failed_jobs()` with only complete jobs.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001451
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001452 Initial conditions are an `_open_jobs` list consisting of only completed
1453 jobs.
1454 Expected result is an empty `_open_jobs` list.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001455
1456 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001457 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001458 self._add_job(d).set_complete()
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001459 self._expect_log_message({}, False)
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001460 self._expect_failed_jobs([])
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001461 self._run_update({})
J. Richard Barnetteea785362014-03-17 16:00:53 -07001462
Jakob Juelich24f22c22014-09-26 11:46:11 -07001463
J. Richard Barnetteea785362014-03-17 16:00:53 -07001464 def test_none_finished(self):
Allen Lib41527d2017-06-22 17:28:00 -07001465 """Test `_report_failed_jobs()` with only unfinished jobs.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001466
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001467 Initial conditions are an `_open_jobs` list consisting of only
1468 unfinished jobs.
1469 Expected result is no change to the `_open_jobs` list.
J. Richard Barnetteea785362014-03-17 16:00:53 -07001470
1471 """
J. Richard Barnette08800322014-05-16 14:49:46 -07001472 for d in self.REGULAR_JOBLIST:
J. Richard Barnetteea785362014-03-17 16:00:53 -07001473 self._add_job(d)
J. Richard Barnette22dd7482014-06-23 12:25:02 -07001474 new_jobs = self._offloader._open_jobs.copy()
1475 self._expect_log_message(new_jobs, False)
Prathmesh Prabhu16f9e5c2017-01-30 17:54:40 -08001476 self._expect_failed_jobs([])
Prathmesh Prabhu33e3e1a2017-01-30 18:03:34 -08001477 self._run_update(new_jobs)
J. Richard Barnetteea785362014-03-17 16:00:53 -07001478
1479
Allen Lib41527d2017-06-22 17:28:00 -07001480class GsOffloaderMockTests(_TempResultsDirTestCase):
1481 """Tests using mock instead of mox."""
1482
1483 def setUp(self):
1484 super(GsOffloaderMockTests, self).setUp()
1485 alarm = mock.patch('signal.alarm', return_value=0)
1486 alarm.start()
1487 self.addCleanup(alarm.stop)
1488
1489 self._saved_loglevel = logging.getLogger().getEffectiveLevel()
1490 logging.getLogger().setLevel(logging.CRITICAL + 1)
1491
1492 self._job = self.make_job(self.REGULAR_JOBLIST[0])
1493
1494
1495 def test_offload_timeout_early(self):
1496 """Test that `offload_dir()` times out correctly.
1497
1498 This test triggers timeout at the earliest possible moment,
1499 at the first call to set the timeout alarm.
1500
1501 """
1502 signal.alarm.side_effect = [0, timeout_util.TimeoutError('fubar')]
1503 with mock.patch.object(gs_offloader, '_upload_cts_testresult',
1504 autospec=True) as upload:
1505 upload.return_value = None
1506 gs_offloader.GSOffloader(
1507 utils.DEFAULT_OFFLOAD_GSURI, False, 0).offload(
1508 self._job.queue_args[0],
1509 self._job.queue_args[1],
1510 self._job.queue_args[2])
1511 self.assertTrue(os.path.isdir(self._job.queue_args[0]))
1512
1513
1514 # TODO(ayatane): This tests passes when run locally, but it fails
1515 # when run on trybot. I have no idea why, but the assert isdir
1516 # fails.
1517 #
1518 # This test is also kind of redundant since we are using the timeout
1519 # from chromite which has its own tests.
1520 @unittest.skip('This fails on trybot')
1521 def test_offload_timeout_late(self):
1522 """Test that `offload_dir()` times out correctly.
1523
1524 This test triggers timeout at the latest possible moment, at
1525 the call to clear the timeout alarm.
1526
1527 """
1528 signal.alarm.side_effect = [0, 0, timeout_util.TimeoutError('fubar')]
1529 with mock.patch.object(gs_offloader, '_upload_cts_testresult',
1530 autospec=True) as upload, \
1531 mock.patch.object(gs_offloader, '_get_cmd_list',
1532 autospec=True) as get_cmd_list:
1533 upload.return_value = None
1534 get_cmd_list.return_value = ['test', '-d', self._job.queue_args[0]]
1535 gs_offloader.GSOffloader(
1536 utils.DEFAULT_OFFLOAD_GSURI, False, 0).offload(
1537 self._job.queue_args[0],
1538 self._job.queue_args[1],
1539 self._job.queue_args[2])
1540 self.assertTrue(os.path.isdir(self._job.queue_args[0]))
1541
1542
1543
J. Richard Barnetteea785362014-03-17 16:00:53 -07001544if __name__ == '__main__':
1545 unittest.main()