blob: 9a251e0b98ee7076ed73f94634980afd5ad3440e [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
49def _get_ground_truth_task_list_from_config():
50 """Get the ground truth of to-be-scheduled task list from config file."""
51 task_config = config_reader.TaskConfig(_SUITE_CONFIG_READER)
52 tasks = {}
53 for keyword, klass in config_reader.EVENT_CLASSES.iteritems():
Xixuan Wu4ac56dd2017-10-12 11:59:30 -070054 tasks[keyword] = task_config.get_tasks_by_keyword(klass.KEYWORD)['tasks']
Xixuan Wu40998892017-08-29 14:32:26 -070055
56 return tasks
57
58
59def _should_schedule_nightly_task(last_now, now):
60 """Check whether nightly task should be scheduled.
61
62 A nightly task should be schduled when next hour is coming.
63
64 Args:
65 last_now: the last time to check if nightly task should be scheduled
66 or not.
67 now: the current time to check if nightly task should be scheduled.
68
69 Returns:
70 a boolean indicating whether there will be nightly tasks scheduled.
71 """
72 if last_now is not None and last_now.hour != now.hour:
73 return True
74
75 return False
76
77
78def _should_schedule_weekly_task(last_now, now, weekly_time_info):
79 """Check whether weekly task should be scheduled.
80
81 A weekly task should be schduled when it comes to the default weekly tasks
82 scheduling hour in next day.
83
84 Args:
85 last_now: the last time to check if weekly task should be scheduled
86 or not.
87 now: the current time to check if weekly task should be scheduled.
88 weekly_time_info: the default weekly tasks scheduling time info.
89
90 Returns:
91 a boolean indicating whether there will be weekly tasks scheduled.
92 """
93 if (last_now is not None and last_now.hour != now.hour and
94 now.hour == weekly_time_info.hour):
95 return True
96
97 return False
98
99
100def _should_schedule_new_build_task(last_now, now):
101 """Check whether weekly task should be scheduled.
102
103 A new_build task should be schduled when there're new builds between last
104 check and this check.
105
106 Args:
107 last_now: the last time to check if new_build task should be scheduled.
108 now: the current time to check if new_build task should be scheduled.
109
110 Returns:
111 a boolean indicating whether there will be new_build tasks scheduled.
112 """
113 if last_now is not None and last_now != now:
114 return True
115
116 return False
117
118
119class FakeCIDBClient(object):
120 """Mock cloud_sql_client.CIDBClient."""
121
122 def get_passed_builds_since_date(self, since_date):
123 """Mock cloud_sql_client.CIDBClient.get_passed_builds_since_date."""
124 del since_date # unused
125 return [cloud_sql_client.BuildInfo('link', '62', '9868.0.0',
126 'link-release')]
127
128 def get_latest_passed_builds(self, build_config):
129 """Mock cloud_sql_client.CIDBClient.get_latest_passed_builds."""
130 del build_config # unused
131 return cloud_sql_client.BuildInfo('link', '62', '9868.0.0', build_config)
132
133
134class FakeAndroidBuildRestClient(object):
135 """Mock rest_client.AndroidBuildRestClient."""
136
137 def get_latest_build_id(self, branch, target):
138 """Mock rest_client.AndroidBuildRestClient.get_latest_build_id."""
139 del branch, target # unused
140 return '100'
141
142
143class FakeLabConfig(object):
144 """Mock rest_client.AndroidBuildRestClient."""
145
146 def get_android_board_list(self):
147 """Mock config_reader.LabConfig.get_android_board_list."""
148 return ('android-angler', 'android-bullhead')
149
150 def get_firmware_ro_build_list(self, release_board):
151 """Mock config_reader.LabConfig.get_firmware_ro_build_list."""
152 del release_board # unused
153 return 'firmware1,firmware2'
154
155
156class TriggerReceiverTestCase(unittest.TestCase):
157
158 def setUp(self):
159 self.testbed = testbed.Testbed()
160 self.testbed.activate()
161 self.addCleanup(self.testbed.deactivate)
162
163 self.testbed.init_datastore_v3_stub()
164 self.testbed.init_memcache_stub()
165 ndb.get_context().clear_cache()
166 self.testbed.init_taskqueue_stub(
167 root_path=os.path.join(os.path.dirname(__file__)))
168 self.taskqueue_stub = self.testbed.get_stub(
169 testbed.TASKQUEUE_SERVICE_NAME)
170
171 mock_cidb_client = mock.patch('cloud_sql_client.CIDBClient')
172 self._mock_cidb_client = mock_cidb_client.start()
173 self.addCleanup(mock_cidb_client.stop)
174
175 mock_android_client = mock.patch('rest_client.AndroidBuildRestClient')
176 self._mock_android_client = mock_android_client.start()
177 self.addCleanup(mock_android_client.stop)
178
179 mock_config_reader = mock.patch('config_reader.ConfigReader')
180 self._mock_config_reader = mock_config_reader.start()
181 self.addCleanup(mock_config_reader.stop)
182
183 mock_lab_config = mock.patch('config_reader.LabConfig')
184 self._mock_lab_config = mock_lab_config.start()
185 self.addCleanup(mock_lab_config.stop)
186
187 mock_utc_now = mock.patch('time_converter.utc_now')
188 self._mock_utc_now = mock_utc_now.start()
189 self.addCleanup(mock_utc_now.stop)
190
191 self._mock_cidb_client.return_value = FakeCIDBClient()
192 self._mock_android_client.return_value = FakeAndroidBuildRestClient()
193 self._mock_config_reader.return_value = _SUITE_CONFIG_READER
194 self._mock_lab_config.return_value = FakeLabConfig()
195
196 def testCronWithoutLastExec(self):
197 """Test the first round of cron can be successfully executed."""
198 self._mock_utc_now.return_value = datetime.datetime.now(
199 time_converter.UTC_TZ)
200 suite_trigger = trigger_receiver.TriggerReceiver()
201 suite_trigger.cron()
202 self.assertFalse(suite_trigger.events['nightly'].should_handle)
203 self.assertFalse(suite_trigger.events['weekly'].should_handle)
204 self.assertFalse(suite_trigger.events['new_build'].should_handle)
205
206 self.assertEqual(suite_trigger.event_results, {})
207
208 def testCronTriggerNightly(self):
209 """Test nightly event is read with available nightly last_exec_time."""
210 utc_now = datetime.datetime.now(time_converter.UTC_TZ)
211 last_exec_client = datastore_client.LastExecutionRecordStore()
212 last_exec_client.set_last_execute_time(
213 'nightly', utc_now - datetime.timedelta(hours=1))
214 self._mock_utc_now.return_value = utc_now
215 suite_trigger = trigger_receiver.TriggerReceiver()
216 self.assertTrue(suite_trigger.events['nightly'].should_handle)
217 self.assertFalse(suite_trigger.events['weekly'].should_handle)
218 self.assertFalse(suite_trigger.events['new_build'].should_handle)
219
Xixuan Wu33179672017-09-12 11:44:04 -0700220 def testCronTriggerNightlyOutdated(self):
221 """Test nightly event is read with available nightly last_exec_time."""
222 utc_now = datetime.datetime.now(time_converter.UTC_TZ)
223 last_exec_client = datastore_client.LastExecutionRecordStore()
224 last_exec_client.set_last_execute_time(
225 'nightly', utc_now - datetime.timedelta(days=3))
226 self._mock_utc_now.return_value = utc_now
227 suite_trigger = trigger_receiver.TriggerReceiver()
228 self.assertFalse(suite_trigger.events['nightly'].should_handle)
229
230 def testCronTriggerWeeklyOutdated(self):
231 """Test weekly event is read with available weekly last_exec_time."""
232 utc_now = datetime.datetime.now(time_converter.UTC_TZ)
233 last_exec_client = datastore_client.LastExecutionRecordStore()
234 last_exec_client.set_last_execute_time(
235 'weekly', utc_now - datetime.timedelta(days=8))
236 self._mock_utc_now.return_value = utc_now
237 suite_trigger = trigger_receiver.TriggerReceiver()
238 self.assertFalse(suite_trigger.events['weekly'].should_handle)
239
Xixuan Wu40998892017-08-29 14:32:26 -0700240 def testCronForWeeks(self):
241 """Ensure cron job can be successfully scheduled for several weeks."""
242 all_tasks = _get_ground_truth_task_list_from_config()
243 nightly_time_info = time_converter.convert_time_info_to_utc(
244 time_converter.TimeInfo(
245 config_reader.EVENT_CLASSES['nightly'].DEFAULT_PST_DAY,
246 config_reader.EVENT_CLASSES['nightly'].DEFAULT_PST_HOUR))
247 weekly_time_info = time_converter.convert_time_info_to_utc(
248 time_converter.TimeInfo(
249 config_reader.EVENT_CLASSES['weekly'].DEFAULT_PST_DAY,
250 config_reader.EVENT_CLASSES['weekly'].DEFAULT_PST_HOUR))
251 last_now = None
252
253 for now in now_generator(datetime.datetime.now(time_converter.UTC_TZ)):
254 self._mock_utc_now.return_value = now
255 suite_trigger = trigger_receiver.TriggerReceiver()
256 suite_trigger.cron()
257
258 # Verify nightly tasks
259 should_scheduled_nightly_tasks = [
260 t.name for t in all_tasks['nightly']
261 if (t.hour is not None and t.hour == now.hour) or
262 (t.hour is None and now.hour == nightly_time_info.hour)]
263 if (_should_schedule_nightly_task(last_now, now) and
264 should_scheduled_nightly_tasks):
265 self.assertEqual(suite_trigger.event_results['nightly'],
266 should_scheduled_nightly_tasks)
267 else:
268 self.assertNotIn('nightly', suite_trigger.event_results.keys())
269
270 # Verify weekly tasks
271 should_scheduled_weekly_tasks = [
272 t.name for t in all_tasks['weekly']
273 if (t.day is not None and now.weekday() == t.day) or
274 (t.day is None and now.weekday() == weekly_time_info.weekday)]
275 if (_should_schedule_weekly_task(last_now, now, weekly_time_info) and
276 should_scheduled_weekly_tasks):
277 self.assertEqual(suite_trigger.event_results['weekly'],
278 should_scheduled_weekly_tasks)
279 else:
280 self.assertNotIn('weekly', suite_trigger.event_results.keys())
281
282 # Verify new_build tasks
283 should_scheduled_new_build_tasks = [
284 t.name for t in all_tasks['new_build']]
285 if (_should_schedule_new_build_task(last_now, now) and
286 should_scheduled_new_build_tasks):
287 self.assertEqual(suite_trigger.event_results['new_build'],
288 should_scheduled_new_build_tasks)
289 else:
290 self.assertNotIn('new_build', suite_trigger.event_results.keys())
291
292 last_now = now
293
294
295if __name__ == '__main__':
296 unittest.main()