timed_event_unittest.py revision c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00e
1#!/usr/bin/python
2#
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Unit tests for site_utils/timed_event.py."""
8
9import datetime, logging, mox, unittest
10
11# driver must be imported first due to circular imports in base_event and task
12import driver  # pylint: disable-msg=W0611
13import base_event, deduping_scheduler, forgiving_config_parser
14import manifest_versions, task, timed_event
15
16
17class TimedEventTestBase(mox.MoxTestBase):
18    """Base class for TimedEvent unit test classes."""
19
20
21    def setUp(self):
22        super(TimedEventTestBase, self).setUp()
23        self.mox.StubOutWithMock(timed_event.TimedEvent, '_now')
24        self.mv = self.mox.CreateMock(manifest_versions.ManifestVersions)
25
26
27    def BaseTime(self):
28        """Return the TimedEvent trigger-time as a datetime instance."""
29        raise NotImplementedError()
30
31
32    def CreateEvent(self):
33        """Return an instance of the TimedEvent subclass being tested."""
34        raise NotImplementedError()
35
36
37    def TimeBefore(self, now):
38        """Return a datetime that's before |now|."""
39        raise NotImplementedError()
40
41
42    def TimeLaterThan(self, now):
43        """Return a datetime that's later than |now|."""
44        raise NotImplementedError()
45
46
47    def doTestDeadlineInFuture(self):
48        fake_now = self.TimeBefore(self.BaseTime())
49        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
50        self.mox.ReplayAll()
51
52        t = self.CreateEvent()  # Deadline gets set for a future time.
53        self.assertFalse(t.ShouldHandle())
54        self.mox.VerifyAll()
55
56        self.mox.ResetAll()
57        fake_now = self.TimeLaterThan(fake_now)  # Jump past that future time.
58        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
59        self.mox.ReplayAll()
60        self.assertTrue(t.ShouldHandle())
61
62
63    def doTestDeadlineIsNow(self):
64        """We happened to create the trigger at the exact right time."""
65        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
66        self.mox.ReplayAll()
67        to_test = self.CreateEvent()
68        self.assertTrue(to_test.ShouldHandle())
69
70
71    def doTestTOCTOU(self):
72        """Even if deadline passes during initialization, trigger must fire."""
73        init_now = self.BaseTime() - datetime.timedelta(seconds=1)
74        fire_now = self.BaseTime() + datetime.timedelta(seconds=1)
75        timed_event.TimedEvent._now().AndReturn(init_now)
76        timed_event.TimedEvent._now().AndReturn(fire_now)
77        self.mox.ReplayAll()
78
79        t = self.CreateEvent()  # Deadline gets set for later tonight...
80        # ...but has passed by the time we get around to firing.
81        self.assertTrue(t.ShouldHandle())
82
83
84    def doTestDeadlineUpdate(self, days_to_jump):
85        fake_now = self.TimeBefore(self.BaseTime())
86        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
87        self.mox.ReplayAll()
88
89        nightly = self.CreateEvent()  # Deadline gets set for tonight.
90        self.assertFalse(nightly.ShouldHandle())
91        self.mox.VerifyAll()
92
93        self.mox.ResetAll()
94        fake_now = self.TimeLaterThan(self.BaseTime())  # Jump past deadline.
95        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
96        self.mox.ReplayAll()
97
98        self.assertTrue(nightly.ShouldHandle())
99        nightly.UpdateCriteria()  # Deadline moves to tomorrow night
100        self.assertFalse(nightly.ShouldHandle())
101        self.mox.VerifyAll()
102
103        self.mox.ResetAll()
104        fake_now += datetime.timedelta(days=days_to_jump)  # Jump past deadline.
105        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
106        self.mox.ReplayAll()
107        self.assertTrue(nightly.ShouldHandle())
108
109
110    def doTestGetBranchBuilds(self, days):
111        board = 'faux_board'
112        branch_manifests = {('factory','16'): ['last16'],
113                            ('release','17'): ['first17', 'last17']}
114        self.mv.ManifestsSinceDays(days, board).AndReturn(branch_manifests)
115        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
116        self.mox.ReplayAll()
117
118        branch_builds = self.CreateEvent().GetBranchBuildsForBoard(board)
119        for (type, milestone), manifests in branch_manifests.iteritems():
120            build = None
121            if type in task.BARE_BRANCHES:
122                self.assertEquals(len(branch_builds[type]), 1)
123                build = branch_builds[type][0]
124                self.assertTrue(build.startswith('%s-%s' % (board, type)))
125            else:
126                self.assertEquals(len(branch_builds[milestone]), 1)
127                build = branch_builds[milestone][0]
128                self.assertTrue(build.startswith('%s-release' % board))
129            self.assertTrue('R%s-%s' % (milestone, manifests[-1]) in build)
130
131
132class NightlyTest(TimedEventTestBase):
133    """Unit tests for Weekly.
134
135    @var _HOUR: The time of night to use in these unit tests.
136    """
137
138    _HOUR = 20
139
140
141    def setUp(self):
142        super(NightlyTest, self).setUp()
143
144
145    def BaseTime(self):
146        return datetime.datetime(2012, 1, 1, self._HOUR)
147
148
149    def CreateEvent(self):
150        """Return an instance of timed_event.Nightly."""
151        return timed_event.Nightly(self.mv, False, self._HOUR)
152
153
154    def testCreateFromConfig(self):
155        """Test that creating from config is equivalent to using constructor."""
156        config = forgiving_config_parser.ForgivingConfigParser()
157        section = base_event.SectionName(timed_event.Nightly.KEYWORD)
158        config.add_section(section)
159        config.set(section, 'hour', '%d' % self._HOUR)
160
161        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
162        self.mox.ReplayAll()
163
164        self.assertEquals(self.CreateEvent(),
165                          timed_event.Nightly.CreateFromConfig(config, self.mv))
166
167
168    def testCreateFromEmptyConfig(self):
169        """Test that creating from empty config uses defaults."""
170        config = forgiving_config_parser.ForgivingConfigParser()
171
172        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
173        self.mox.ReplayAll()
174
175        self.assertEquals(
176            timed_event.Nightly(self.mv, False,
177                                timed_event.Nightly._DEFAULT_HOUR),
178            timed_event.Nightly.CreateFromConfig(config, self.mv))
179
180
181    def testCreateFromAlwaysHandleConfig(self):
182        """Test that creating with always_handle works as intended."""
183        config = forgiving_config_parser.ForgivingConfigParser()
184        section = base_event.SectionName(timed_event.Nightly.KEYWORD)
185        config.add_section(section)
186        config.set(section, 'hour', '%d' % (self._HOUR + 1))
187        config.set(section, 'always_handle', 'True')
188
189        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
190        self.mox.ReplayAll()
191
192        event = timed_event.Nightly.CreateFromConfig(config, self.mv)
193        self.assertTrue(event.ShouldHandle())
194
195
196    def testMerge(self):
197        """Test that Merge() works when the deadline time of day changes."""
198        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
199        self.mox.ReplayAll()
200
201        old = timed_event.Nightly(self.mv, False, self._HOUR)
202        new = timed_event.Nightly(self.mv, False, (self._HOUR + 23) % 24)
203        self.assertNotEquals(old._deadline, new._deadline)
204        old.Merge(new)
205        self.assertEquals(old._deadline, new._deadline)
206
207
208    def testSkipMerge(self):
209        """Test that deadline is unchanged when time of day is unchanged."""
210        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
211        self.mox.ReplayAll()
212
213        old = timed_event.Nightly(self.mv, False, self._HOUR)
214        new = timed_event.Nightly(self.mv, False, self._HOUR)
215        new._deadline += datetime.timedelta(days=1)
216        self.assertNotEquals(old._deadline, new._deadline)
217        saved_deadline = old._deadline
218        old.Merge(new)
219        self.assertEquals(saved_deadline, old._deadline)
220
221
222    def testDeadlineInPast(self):
223        """Ensure we work if the deadline aready passed today."""
224        fake_now = self.BaseTime() + datetime.timedelta(hours=1)
225        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
226        self.mox.ReplayAll()
227
228        nightly = self.CreateEvent()  # Deadline gets set for tomorrow night.
229        self.assertFalse(nightly.ShouldHandle())
230        self.mox.VerifyAll()
231
232        self.mox.ResetAll()
233        fake_now += datetime.timedelta(days=1)  # Jump to tomorrow night.
234        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
235        self.mox.ReplayAll()
236        self.assertTrue(nightly.ShouldHandle())
237
238
239    def TimeBefore(self, now):
240        return now - datetime.timedelta(hours=1)
241
242
243    def TimeLaterThan(self, now):
244        return now + datetime.timedelta(hours=2)
245
246
247    def testDeadlineInFuture(self):
248        """Ensure we work if the deadline is later today."""
249        self.doTestDeadlineInFuture()
250
251
252    def testDeadlineIsNow(self):
253        """We happened to create the trigger at the exact right time."""
254        self.doTestDeadlineIsNow()
255
256
257    def testTOCTOU(self):
258        """Even if deadline passes during initialization, trigger must fire."""
259        self.doTestTOCTOU()
260
261
262    def testDeadlineUpdate(self):
263        """Ensure we update the deadline correctly."""
264        self.doTestDeadlineUpdate(days_to_jump=1)
265
266
267    def testGetBranchBuilds(self):
268        """Ensure Nightly gets most recent builds in last day."""
269        self.doTestGetBranchBuilds(days=1)
270
271
272class WeeklyTest(TimedEventTestBase):
273    """Unit tests for Weekly.
274
275    @var _DAY: The day of the week to use in these unit tests.
276    @var _HOUR: The time of night to use in these unit tests.
277    """
278
279    _DAY = 5
280    _HOUR = 22
281
282
283    def setUp(self):
284        super(WeeklyTest, self).setUp()
285
286
287    def BaseTime(self):
288        basetime = datetime.datetime(2012, 1, 1, self._HOUR)
289        basetime += datetime.timedelta(self._DAY-basetime.weekday())
290        return basetime
291
292
293    def CreateEvent(self):
294        """Return an instance of timed_event.Weekly."""
295        return timed_event.Weekly(self.mv, False, self._DAY, self._HOUR)
296
297
298    def testCreateFromConfig(self):
299        """Test that creating from config is equivalent to using constructor."""
300        config = forgiving_config_parser.ForgivingConfigParser()
301        section = base_event.SectionName(timed_event.Weekly.KEYWORD)
302        config.add_section(section)
303        config.set(section, 'day', '%d' % self._DAY)
304        config.set(section, 'hour', '%d' % self._HOUR)
305
306        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
307        self.mox.ReplayAll()
308
309        self.assertEquals(self.CreateEvent(),
310                          timed_event.Weekly.CreateFromConfig(config, self.mv))
311
312
313    def testMergeDueToTimeChange(self):
314        """Test that Merge() works when the deadline time of day changes."""
315        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
316        self.mox.ReplayAll()
317
318        old = timed_event.Weekly(self.mv, False, self._DAY, self._HOUR)
319        new = timed_event.Weekly(self.mv, False, self._DAY, self._HOUR + 1)
320        self.assertNotEquals(old._deadline, new._deadline)
321        old.Merge(new)
322        self.assertEquals(old._deadline, new._deadline)
323
324
325    def testMergeDueToDayChange(self):
326        """Test that Merge() works when the deadline day of week changes."""
327        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
328        self.mox.ReplayAll()
329
330        old = timed_event.Weekly(self.mv, False, self._DAY, self._HOUR)
331        new = timed_event.Weekly(self.mv, False, self._DAY, self._HOUR)
332        new._deadline += datetime.timedelta(days=1)
333        self.assertNotEquals(old._deadline, new._deadline)
334        old.Merge(new)
335        self.assertEquals(old._deadline, new._deadline)
336
337
338    def testSkipMerge(self):
339        """Test that deadline is unchanged when only the week is changed."""
340        timed_event.TimedEvent._now().MultipleTimes().AndReturn(self.BaseTime())
341        self.mox.ReplayAll()
342
343        old = timed_event.Weekly(self.mv, False, self._DAY, self._HOUR)
344        new = timed_event.Weekly(self.mv, False, self._DAY, self._HOUR)
345        new._deadline += datetime.timedelta(days=7)
346        self.assertNotEquals(old._deadline, new._deadline)
347        saved_deadline = old._deadline
348        old.Merge(new)
349        self.assertEquals(saved_deadline, old._deadline)
350
351
352    def testDeadlineInPast(self):
353        """Ensure we work if the deadline already passed this week."""
354        fake_now = self.BaseTime() + datetime.timedelta(days=1)
355        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
356        self.mox.ReplayAll()
357
358        weekly = self.CreateEvent()  # Deadline gets set for next week.
359        self.assertFalse(weekly.ShouldHandle())
360        self.mox.VerifyAll()
361
362        self.mox.ResetAll()
363        fake_now += datetime.timedelta(days=1)  # Jump to tomorrow.
364        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
365        self.mox.ReplayAll()
366        self.assertFalse(weekly.ShouldHandle())
367        self.mox.VerifyAll()
368
369        self.mox.ResetAll()
370        fake_now += datetime.timedelta(days=7)  # Jump to next week.
371        timed_event.TimedEvent._now().MultipleTimes().AndReturn(fake_now)
372        self.mox.ReplayAll()
373        self.assertTrue(weekly.ShouldHandle())
374
375
376    def TimeBefore(self, now):
377        return now - datetime.timedelta(days=1)
378
379
380    def TimeLaterThan(self, now):
381        return now + datetime.timedelta(days=2)
382
383
384    def testDeadlineInFuture(self):
385        """Ensure we work if the deadline is later this week."""
386        self.doTestDeadlineInFuture()
387
388
389    def testDeadlineIsNow(self):
390        """We happened to create the trigger at the exact right time."""
391        self.doTestDeadlineIsNow()
392
393
394    def testTOCTOU(self):
395        """Even if deadline passes during initialization, trigger must fire."""
396        self.doTestTOCTOU()
397
398
399    def testDeadlineUpdate(self):
400        """Ensure we update the deadline correctly."""
401        self.doTestDeadlineUpdate(days_to_jump=7)
402
403
404    def testGetBranchBuilds(self):
405        """Ensure Weekly gets most recent builds in last 7 days."""
406        self.doTestGetBranchBuilds(days=7)
407
408
409if __name__ == '__main__':
410  unittest.main()
411