blob: 5a65f8e85967519d961b0e6afdb7f9360c98e3da [file] [log] [blame]
Xixuan Wu40998892017-08-29 14:32:26 -07001# Copyright 2017 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
5"""Module for trigger_receiver unittests."""
6
7import datetime
8import os
9import unittest
10
11import cloud_sql_client
12import config_reader
13import datastore_client
14import file_getter
15import mock
16import time_converter
17import trigger_receiver
18
19from google.appengine.ext import ndb
20from google.appengine.ext import testbed
21
22# Ensure that SUITE_SCHEDULER_CONFIG_FILE is read only once.
23_SUITE_CONFIG_READER = config_reader.ConfigReader(
Xixuan Wu26d06e02017-09-20 14:50:28 -070024 file_getter.TEST_SUITE_SCHEDULER_CONFIG_FILE)
Xixuan Wu40998892017-08-29 14:32:26 -070025
26
27def now_generator(start_time, interval_min=30, last_days=7):
28 """A datetime.datetime.now generator.
29
30 The generator will generate 'now' from start_time till start_time+last_days
31 for every interval_min.
32
33 Args:
34 start_time: A datetime.datetime object representing the initial value of
35 the 'now'.
36 interval_min: The interval minutes between current 'now' and next 'now.
37 last_days: Representing how many days this generator will last.
38
39 Yields:
40 a datetime.datetime object to mock the current time.
41 """
42 cur_time = start_time
43 end_time = start_time + datetime.timedelta(days=last_days)
44 while cur_time < end_time:
45 yield cur_time
46 cur_time += datetime.timedelta(minutes=interval_min)
47
48
Xixuan Wu40998892017-08-29 14:32:26 -070049def _should_schedule_nightly_task(last_now, now):
50 """Check whether nightly task should be scheduled.
51
52 A nightly task should be schduled when next hour is coming.
53
54 Args:
55 last_now: the last time to check if nightly task should be scheduled
56 or not.
57 now: the current time to check if nightly task should be scheduled.
58
59 Returns:
60 a boolean indicating whether there will be nightly tasks scheduled.
61 """
62 if last_now is not None and last_now.hour != now.hour:
63 return True
64
65 return False
66
67
68def _should_schedule_weekly_task(last_now, now, weekly_time_info):
69 """Check whether weekly task should be scheduled.
70
71 A weekly task should be schduled when it comes to the default weekly tasks
72 scheduling hour in next day.
73
74 Args:
75 last_now: the last time to check if weekly task should be scheduled
76 or not.
77 now: the current time to check if weekly task should be scheduled.
78 weekly_time_info: the default weekly tasks scheduling time info.
79
80 Returns:
81 a boolean indicating whether there will be weekly tasks scheduled.
82 """
83 if (last_now is not None and last_now.hour != now.hour and
84 now.hour == weekly_time_info.hour):
85 return True
86
87 return False
88
89
90def _should_schedule_new_build_task(last_now, now):
91 """Check whether weekly task should be scheduled.
92
93 A new_build task should be schduled when there're new builds between last
94 check and this check.
95
96 Args:
97 last_now: the last time to check if new_build task should be scheduled.
98 now: the current time to check if new_build task should be scheduled.
99
100 Returns:
101 a boolean indicating whether there will be new_build tasks scheduled.
102 """
103 if last_now is not None and last_now != now:
104 return True
105
106 return False
107
108
109class FakeCIDBClient(object):
110 """Mock cloud_sql_client.CIDBClient."""
111
112 def get_passed_builds_since_date(self, since_date):
113 """Mock cloud_sql_client.CIDBClient.get_passed_builds_since_date."""
114 del since_date # unused
115 return [cloud_sql_client.BuildInfo('link', '62', '9868.0.0',
116 'link-release')]
117
118 def get_latest_passed_builds(self, build_config):
119 """Mock cloud_sql_client.CIDBClient.get_latest_passed_builds."""
120 del build_config # unused
121 return cloud_sql_client.BuildInfo('link', '62', '9868.0.0', build_config)
122
123
124class FakeAndroidBuildRestClient(object):
125 """Mock rest_client.AndroidBuildRestClient."""
126
127 def get_latest_build_id(self, branch, target):
128 """Mock rest_client.AndroidBuildRestClient.get_latest_build_id."""
129 del branch, target # unused
130 return '100'
131
132
133class FakeLabConfig(object):
134 """Mock rest_client.AndroidBuildRestClient."""
135
136 def get_android_board_list(self):
137 """Mock config_reader.LabConfig.get_android_board_list."""
138 return ('android-angler', 'android-bullhead')
139
Xixuan Wu6fb16272017-10-19 13:16:00 -0700140 def get_cros_board_list(self):
141 """Mock config_reader.LabConfig.get_android_board_list."""
142 return ('link', 'peppy', 'daisy')
143
Xixuan Wu40998892017-08-29 14:32:26 -0700144 def get_firmware_ro_build_list(self, release_board):
145 """Mock config_reader.LabConfig.get_firmware_ro_build_list."""
146 del release_board # unused
147 return 'firmware1,firmware2'
148
C Shapiro7f24a002017-12-05 14:25:09 -0700149 def get_cros_model_map(self):
150 """Mock config_reader.LabConfig.get_cros_model_map."""
151 return {}
152
Xixuan Wu40998892017-08-29 14:32:26 -0700153
Xixuan Wu008ee832017-10-12 16:59:34 -0700154class TriggerReceiverBaseTestCase(unittest.TestCase):
Xixuan Wu40998892017-08-29 14:32:26 -0700155
156 def setUp(self):
157 self.testbed = testbed.Testbed()
158 self.testbed.activate()
159 self.addCleanup(self.testbed.deactivate)
160
161 self.testbed.init_datastore_v3_stub()
162 self.testbed.init_memcache_stub()
163 ndb.get_context().clear_cache()
164 self.testbed.init_taskqueue_stub(
165 root_path=os.path.join(os.path.dirname(__file__)))
166 self.taskqueue_stub = self.testbed.get_stub(
167 testbed.TASKQUEUE_SERVICE_NAME)
168
169 mock_cidb_client = mock.patch('cloud_sql_client.CIDBClient')
170 self._mock_cidb_client = mock_cidb_client.start()
171 self.addCleanup(mock_cidb_client.stop)
172
173 mock_android_client = mock.patch('rest_client.AndroidBuildRestClient')
174 self._mock_android_client = mock_android_client.start()
175 self.addCleanup(mock_android_client.stop)
176
Xixuan Wu40998892017-08-29 14:32:26 -0700177 mock_lab_config = mock.patch('config_reader.LabConfig')
178 self._mock_lab_config = mock_lab_config.start()
179 self.addCleanup(mock_lab_config.stop)
180
181 mock_utc_now = mock.patch('time_converter.utc_now')
182 self._mock_utc_now = mock_utc_now.start()
183 self.addCleanup(mock_utc_now.stop)
184
185 self._mock_cidb_client.return_value = FakeCIDBClient()
186 self._mock_android_client.return_value = FakeAndroidBuildRestClient()
Xixuan Wu40998892017-08-29 14:32:26 -0700187 self._mock_lab_config.return_value = FakeLabConfig()
188
Xixuan Wu008ee832017-10-12 16:59:34 -0700189
190class TriggerReceiverFakeConfigTestCase(TriggerReceiverBaseTestCase):
191
192 _TEST_PST_HOUR = 20
193 _TEST_PST_DAY = 4 # Friday
194 _TEST_EVENT_PST_HOUR = 13
195
196 _FAKE_NIGHTLY_TASK_NAME = 'fake_nightly_task'
197 _FAKE_WEEKLY_TASK_NAME = 'fake_weekly_task'
198
199 def setUp(self):
200 super(TriggerReceiverFakeConfigTestCase, self).setUp()
201
202 self.fake_config = config_reader.ConfigReader(None)
203 self._add_nightly_tasks(self.fake_config)
204 self._add_weekly_tasks(self.fake_config)
205
206 self.fake_config_with_settings = config_reader.ConfigReader(None)
207 self._add_weekly_tasks(self.fake_config_with_settings)
208 self._add_weekly_params(self.fake_config_with_settings)
209
210 mock_config_reader = mock.patch('config_reader.ConfigReader')
211 self._mock_config_reader = mock_config_reader.start()
212 self.addCleanup(mock_config_reader.stop)
213
214 def _add_nightly_tasks(self, fake_config):
215 fake_config.add_section(self._FAKE_NIGHTLY_TASK_NAME)
216 fake_config.set(self._FAKE_NIGHTLY_TASK_NAME, 'suite', 'fake_suite')
217 fake_config.set(self._FAKE_NIGHTLY_TASK_NAME, 'run_on', 'nightly')
218 fake_config.set(self._FAKE_NIGHTLY_TASK_NAME, 'hour',
219 str(self._TEST_PST_HOUR))
220
221 def _add_weekly_tasks(self, fake_config):
222 fake_config.add_section(self._FAKE_WEEKLY_TASK_NAME)
223 fake_config.set(self._FAKE_WEEKLY_TASK_NAME, 'suite', 'fake_suite')
224 fake_config.set(self._FAKE_WEEKLY_TASK_NAME, 'run_on', 'weekly')
225 fake_config.set(self._FAKE_WEEKLY_TASK_NAME, 'day', str(self._TEST_PST_DAY))
226
227 def _add_weekly_params(self, fake_config):
228 weekly_section_name = config_reader.EVENT_CLASSES['weekly'].section_name()
229 fake_config.add_section(weekly_section_name)
230 fake_config.set(weekly_section_name, 'hour', str(self._TEST_EVENT_PST_HOUR))
231
232 def testInitializeTriggerReceiverWithNightlyEvent(self):
233 """Test nightly event can be handled on right hour."""
234 # A task with hour=20 should be scheduled at 20:00 in PST everyday, which
235 # is 3:00/4:00 in UTC everyday.
236 self._mock_config_reader.return_value = self.fake_config
237 given_utc_hour = time_converter.convert_time_info(
238 time_converter.TimeInfo(None, self._TEST_PST_HOUR)).hour
239 utc_now = datetime.datetime(2017, 8, 6, given_utc_hour,
240 tzinfo=time_converter.UTC_TZ)
241 last_exec_client = datastore_client.LastExecutionRecordStore()
242 last_exec_client.set_last_execute_time(
243 'nightly', utc_now - datetime.timedelta(hours=1))
244 self._mock_utc_now.return_value = utc_now
245
246 suite_trigger = trigger_receiver.TriggerReceiver()
247 suite_trigger.cron()
248 self.assertTrue(suite_trigger.events['nightly'].should_handle)
249 self.assertEqual(len(suite_trigger.event_results['nightly']), 1)
250 self.assertEqual(suite_trigger.event_results['nightly'][0],
251 self._FAKE_NIGHTLY_TASK_NAME)
252
253 def testInitializeTriggerReceiverWithWeeklyEventWithoutEventHour(self):
254 """Test weekly event without event settings can be handled on right day."""
255 # A task with day=4 (Friday) and default event_hour (23) should be
256 # scheduled at Friday 23:00 in PST, which is Saturday 6:00 or 7:00 in UTC.
257 self._mock_config_reader.return_value = self.fake_config
258 given_utc_hour = time_converter.convert_time_info(
259 time_converter.TimeInfo(
260 self._TEST_PST_DAY,
261 config_reader.EVENT_CLASSES['weekly'].DEFAULT_PST_HOUR)).hour
262 utc_now = datetime.datetime(2017, 10, 14, given_utc_hour,
263 tzinfo=time_converter.UTC_TZ)
264 last_exec_client = datastore_client.LastExecutionRecordStore()
265 last_exec_client.set_last_execute_time(
266 'weekly', utc_now - datetime.timedelta(days=1))
267 self._mock_utc_now.return_value = utc_now
268
269 suite_trigger = trigger_receiver.TriggerReceiver()
270 suite_trigger.cron()
271 self.assertTrue(suite_trigger.events['weekly'].should_handle)
272 self.assertEqual(len(suite_trigger.event_results['weekly']), 1)
273 self.assertEqual(suite_trigger.event_results['weekly'][0],
274 self._FAKE_WEEKLY_TASK_NAME)
275
276 def testInitializeTriggerReceiverWithWeeklyEventWithEventHour(self):
277 """Test weekly event with event settings can be handled on right day."""
278 # A task with day=4 (Friday) and event_hour=13 should be scheduled at
279 # Friday 13:00 in PST, which is Friday 20:00 or 21:00 in UTC.
280 self._mock_config_reader.return_value = self.fake_config_with_settings
281 given_utc_time_info = time_converter.convert_time_info(
282 time_converter.TimeInfo(self._TEST_PST_DAY, self._TEST_EVENT_PST_HOUR))
283
284 # Set the current time as a Friday 20:00 or 21:00 in UTC.
285 utc_now = datetime.datetime(2017, 10, 13, given_utc_time_info.hour,
286 tzinfo=time_converter.UTC_TZ)
287 last_exec_client = datastore_client.LastExecutionRecordStore()
288 last_exec_client.set_last_execute_time(
289 'weekly', utc_now - datetime.timedelta(days=1))
290 self._mock_utc_now.return_value = utc_now
291
292 suite_trigger = trigger_receiver.TriggerReceiver()
293 suite_trigger.cron()
294 self.assertTrue(suite_trigger.events['weekly'].should_handle)
295 self.assertEqual(len(suite_trigger.event_results['weekly']), 1)
296 self.assertEqual(suite_trigger.event_results['weekly'][0],
297 self._FAKE_WEEKLY_TASK_NAME)
298
299
300class TriggerReceiverRealConfigTestCase(TriggerReceiverBaseTestCase):
301
302 def setUp(self):
303 super(TriggerReceiverRealConfigTestCase, self).setUp()
304 mock_config_reader = mock.patch('config_reader.ConfigReader')
305 self._mock_config_reader = mock_config_reader.start()
306 self.addCleanup(mock_config_reader.stop)
307 self._mock_config_reader.return_value = _SUITE_CONFIG_READER
308
309 def _get_ground_truth_task_list_from_config(self):
310 """Get the ground truth of to-be-scheduled task list from config file."""
311 self._mock_utc_now.return_value = datetime.datetime.now(
312 time_converter.UTC_TZ)
313 task_config = config_reader.TaskConfig(_SUITE_CONFIG_READER)
314 tasks = {}
315 for keyword, klass in config_reader.EVENT_CLASSES.iteritems():
316 new_event = klass(
317 task_config.get_event_setting(klass.section_name()), None)
318 new_event.set_task_list(
319 task_config.get_tasks_by_keyword(klass.KEYWORD)['tasks'])
320 tasks[keyword] = new_event.task_list
321
322 return tasks
323
Xixuan Wu40998892017-08-29 14:32:26 -0700324 def testCronWithoutLastExec(self):
325 """Test the first round of cron can be successfully executed."""
326 self._mock_utc_now.return_value = datetime.datetime.now(
327 time_converter.UTC_TZ)
328 suite_trigger = trigger_receiver.TriggerReceiver()
329 suite_trigger.cron()
330 self.assertFalse(suite_trigger.events['nightly'].should_handle)
331 self.assertFalse(suite_trigger.events['weekly'].should_handle)
332 self.assertFalse(suite_trigger.events['new_build'].should_handle)
333
334 self.assertEqual(suite_trigger.event_results, {})
335
336 def testCronTriggerNightly(self):
337 """Test nightly event is read with available nightly last_exec_time."""
338 utc_now = datetime.datetime.now(time_converter.UTC_TZ)
339 last_exec_client = datastore_client.LastExecutionRecordStore()
340 last_exec_client.set_last_execute_time(
341 'nightly', utc_now - datetime.timedelta(hours=1))
342 self._mock_utc_now.return_value = utc_now
343 suite_trigger = trigger_receiver.TriggerReceiver()
344 self.assertTrue(suite_trigger.events['nightly'].should_handle)
345 self.assertFalse(suite_trigger.events['weekly'].should_handle)
346 self.assertFalse(suite_trigger.events['new_build'].should_handle)
347
Xixuan Wu33179672017-09-12 11:44:04 -0700348 def testCronTriggerNightlyOutdated(self):
349 """Test nightly event is read with available nightly last_exec_time."""
350 utc_now = datetime.datetime.now(time_converter.UTC_TZ)
351 last_exec_client = datastore_client.LastExecutionRecordStore()
352 last_exec_client.set_last_execute_time(
353 'nightly', utc_now - datetime.timedelta(days=3))
354 self._mock_utc_now.return_value = utc_now
355 suite_trigger = trigger_receiver.TriggerReceiver()
356 self.assertFalse(suite_trigger.events['nightly'].should_handle)
357
358 def testCronTriggerWeeklyOutdated(self):
359 """Test weekly event is read with available weekly last_exec_time."""
360 utc_now = datetime.datetime.now(time_converter.UTC_TZ)
361 last_exec_client = datastore_client.LastExecutionRecordStore()
362 last_exec_client.set_last_execute_time(
363 'weekly', utc_now - datetime.timedelta(days=8))
364 self._mock_utc_now.return_value = utc_now
365 suite_trigger = trigger_receiver.TriggerReceiver()
366 self.assertFalse(suite_trigger.events['weekly'].should_handle)
367
Xixuan Wu40998892017-08-29 14:32:26 -0700368 def testCronForWeeks(self):
369 """Ensure cron job can be successfully scheduled for several weeks."""
Xixuan Wu008ee832017-10-12 16:59:34 -0700370 all_tasks = self._get_ground_truth_task_list_from_config()
371 nightly_time_info = time_converter.convert_time_info(
Xixuan Wu40998892017-08-29 14:32:26 -0700372 time_converter.TimeInfo(
373 config_reader.EVENT_CLASSES['nightly'].DEFAULT_PST_DAY,
374 config_reader.EVENT_CLASSES['nightly'].DEFAULT_PST_HOUR))
Xixuan Wu008ee832017-10-12 16:59:34 -0700375 weekly_time_info = time_converter.convert_time_info(
Xixuan Wu40998892017-08-29 14:32:26 -0700376 time_converter.TimeInfo(
377 config_reader.EVENT_CLASSES['weekly'].DEFAULT_PST_DAY,
378 config_reader.EVENT_CLASSES['weekly'].DEFAULT_PST_HOUR))
379 last_now = None
380
381 for now in now_generator(datetime.datetime.now(time_converter.UTC_TZ)):
382 self._mock_utc_now.return_value = now
383 suite_trigger = trigger_receiver.TriggerReceiver()
Xixuan Wu5451a662017-10-17 10:57:40 -0700384 with mock.patch('task.Task.schedule', return_value=True):
385 suite_trigger.cron()
Xixuan Wu40998892017-08-29 14:32:26 -0700386
Xixuan Wu40998892017-08-29 14:32:26 -0700387 should_scheduled_nightly_tasks = [
388 t.name for t in all_tasks['nightly']
389 if (t.hour is not None and t.hour == now.hour) or
390 (t.hour is None and now.hour == nightly_time_info.hour)]
Xixuan Wu008ee832017-10-12 16:59:34 -0700391
Xixuan Wu40998892017-08-29 14:32:26 -0700392 if (_should_schedule_nightly_task(last_now, now) and
393 should_scheduled_nightly_tasks):
394 self.assertEqual(suite_trigger.event_results['nightly'],
395 should_scheduled_nightly_tasks)
396 else:
397 self.assertNotIn('nightly', suite_trigger.event_results.keys())
398
399 # Verify weekly tasks
400 should_scheduled_weekly_tasks = [
401 t.name for t in all_tasks['weekly']
402 if (t.day is not None and now.weekday() == t.day) or
403 (t.day is None and now.weekday() == weekly_time_info.weekday)]
404 if (_should_schedule_weekly_task(last_now, now, weekly_time_info) and
405 should_scheduled_weekly_tasks):
406 self.assertEqual(suite_trigger.event_results['weekly'],
407 should_scheduled_weekly_tasks)
408 else:
409 self.assertNotIn('weekly', suite_trigger.event_results.keys())
410
411 # Verify new_build tasks
412 should_scheduled_new_build_tasks = [
413 t.name for t in all_tasks['new_build']]
414 if (_should_schedule_new_build_task(last_now, now) and
415 should_scheduled_new_build_tasks):
416 self.assertEqual(suite_trigger.event_results['new_build'],
417 should_scheduled_new_build_tasks)
418 else:
419 self.assertNotIn('new_build', suite_trigger.event_results.keys())
420
421 last_now = now
422
423
424if __name__ == '__main__':
425 unittest.main()