|
1 # Copyright (c) 2009 Google Inc. All rights reserved. |
|
2 # |
|
3 # Redistribution and use in source and binary forms, with or without |
|
4 # modification, are permitted provided that the following conditions are |
|
5 # met: |
|
6 # |
|
7 # * Redistributions of source code must retain the above copyright |
|
8 # notice, this list of conditions and the following disclaimer. |
|
9 # * Redistributions in binary form must reproduce the above |
|
10 # copyright notice, this list of conditions and the following disclaimer |
|
11 # in the documentation and/or other materials provided with the |
|
12 # distribution. |
|
13 # * Neither the name of Google Inc. nor the names of its |
|
14 # contributors may be used to endorse or promote products derived from |
|
15 # this software without specific prior written permission. |
|
16 # |
|
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
28 |
|
29 import datetime |
|
30 import os |
|
31 import shutil |
|
32 import tempfile |
|
33 import threading |
|
34 import unittest |
|
35 |
|
36 from webkitpy.common.system.executive import ScriptError |
|
37 from webkitpy.tool.bot.queueengine import QueueEngine, QueueEngineDelegate |
|
38 |
|
39 class LoggingDelegate(QueueEngineDelegate): |
|
40 def __init__(self, test): |
|
41 self._test = test |
|
42 self._callbacks = [] |
|
43 self._run_before = False |
|
44 |
|
45 expected_callbacks = [ |
|
46 'queue_log_path', |
|
47 'begin_work_queue', |
|
48 'should_continue_work_queue', |
|
49 'next_work_item', |
|
50 'should_proceed_with_work_item', |
|
51 'work_item_log_path', |
|
52 'process_work_item', |
|
53 'should_continue_work_queue' |
|
54 ] |
|
55 |
|
56 def record(self, method_name): |
|
57 self._callbacks.append(method_name) |
|
58 |
|
59 def queue_log_path(self): |
|
60 self.record("queue_log_path") |
|
61 return os.path.join(self._test.temp_dir, "queue_log_path") |
|
62 |
|
63 def work_item_log_path(self, work_item): |
|
64 self.record("work_item_log_path") |
|
65 return os.path.join(self._test.temp_dir, "work_log_path", "%s.log" % work_item) |
|
66 |
|
67 def begin_work_queue(self): |
|
68 self.record("begin_work_queue") |
|
69 |
|
70 def should_continue_work_queue(self): |
|
71 self.record("should_continue_work_queue") |
|
72 if not self._run_before: |
|
73 self._run_before = True |
|
74 return True |
|
75 return False |
|
76 |
|
77 def next_work_item(self): |
|
78 self.record("next_work_item") |
|
79 return "work_item" |
|
80 |
|
81 def should_proceed_with_work_item(self, work_item): |
|
82 self.record("should_proceed_with_work_item") |
|
83 self._test.assertEquals(work_item, "work_item") |
|
84 fake_patch = { 'bug_id' : 42 } |
|
85 return (True, "waiting_message", fake_patch) |
|
86 |
|
87 def process_work_item(self, work_item): |
|
88 self.record("process_work_item") |
|
89 self._test.assertEquals(work_item, "work_item") |
|
90 return True |
|
91 |
|
92 def handle_unexpected_error(self, work_item, message): |
|
93 self.record("handle_unexpected_error") |
|
94 self._test.assertEquals(work_item, "work_item") |
|
95 |
|
96 |
|
97 class ThrowErrorDelegate(LoggingDelegate): |
|
98 def __init__(self, test, error_code): |
|
99 LoggingDelegate.__init__(self, test) |
|
100 self.error_code = error_code |
|
101 |
|
102 def process_work_item(self, work_item): |
|
103 self.record("process_work_item") |
|
104 raise ScriptError(exit_code=self.error_code) |
|
105 |
|
106 |
|
107 class NotSafeToProceedDelegate(LoggingDelegate): |
|
108 def should_proceed_with_work_item(self, work_item): |
|
109 self.record("should_proceed_with_work_item") |
|
110 self._test.assertEquals(work_item, "work_item") |
|
111 return False |
|
112 |
|
113 |
|
114 class FastQueueEngine(QueueEngine): |
|
115 def __init__(self, delegate): |
|
116 QueueEngine.__init__(self, "fast-queue", delegate, threading.Event()) |
|
117 |
|
118 # No sleep for the wicked. |
|
119 seconds_to_sleep = 0 |
|
120 |
|
121 def _sleep(self, message): |
|
122 pass |
|
123 |
|
124 |
|
125 class QueueEngineTest(unittest.TestCase): |
|
126 def test_trivial(self): |
|
127 delegate = LoggingDelegate(self) |
|
128 work_queue = QueueEngine("trivial-queue", delegate, threading.Event()) |
|
129 work_queue.run() |
|
130 self.assertEquals(delegate._callbacks, LoggingDelegate.expected_callbacks) |
|
131 self.assertTrue(os.path.exists(os.path.join(self.temp_dir, "queue_log_path"))) |
|
132 self.assertTrue(os.path.exists(os.path.join(self.temp_dir, "work_log_path", "work_item.log"))) |
|
133 |
|
134 def test_unexpected_error(self): |
|
135 delegate = ThrowErrorDelegate(self, 3) |
|
136 work_queue = QueueEngine("error-queue", delegate, threading.Event()) |
|
137 work_queue.run() |
|
138 expected_callbacks = LoggingDelegate.expected_callbacks[:] |
|
139 work_item_index = expected_callbacks.index('process_work_item') |
|
140 # The unexpected error should be handled right after process_work_item starts |
|
141 # but before any other callback. Otherwise callbacks should be normal. |
|
142 expected_callbacks.insert(work_item_index + 1, 'handle_unexpected_error') |
|
143 self.assertEquals(delegate._callbacks, expected_callbacks) |
|
144 |
|
145 def test_handled_error(self): |
|
146 delegate = ThrowErrorDelegate(self, QueueEngine.handled_error_code) |
|
147 work_queue = QueueEngine("handled-error-queue", delegate, threading.Event()) |
|
148 work_queue.run() |
|
149 self.assertEquals(delegate._callbacks, LoggingDelegate.expected_callbacks) |
|
150 |
|
151 def test_not_safe_to_proceed(self): |
|
152 delegate = NotSafeToProceedDelegate(self) |
|
153 work_queue = FastQueueEngine(delegate) |
|
154 work_queue.run() |
|
155 expected_callbacks = LoggingDelegate.expected_callbacks[:] |
|
156 next_work_item_index = expected_callbacks.index('next_work_item') |
|
157 # We slice out the common part of the expected callbacks. |
|
158 # We add 2 here to include should_proceed_with_work_item, which is |
|
159 # a pain to search for directly because it occurs twice. |
|
160 expected_callbacks = expected_callbacks[:next_work_item_index + 2] |
|
161 expected_callbacks.append('should_continue_work_queue') |
|
162 self.assertEquals(delegate._callbacks, expected_callbacks) |
|
163 |
|
164 def test_now(self): |
|
165 """Make sure there are no typos in the QueueEngine.now() method.""" |
|
166 engine = QueueEngine("test", None, None) |
|
167 self.assertTrue(isinstance(engine._now(), datetime.datetime)) |
|
168 |
|
169 def test_sleep_message(self): |
|
170 engine = QueueEngine("test", None, None) |
|
171 engine._now = lambda: datetime.datetime(2010, 1, 1) |
|
172 expected_sleep_message = "MESSAGE Sleeping until 2010-01-01 00:02:00 (2 mins)." |
|
173 self.assertEqual(engine._sleep_message("MESSAGE"), expected_sleep_message) |
|
174 |
|
175 def setUp(self): |
|
176 self.temp_dir = tempfile.mkdtemp(suffix="work_queue_test_logs") |
|
177 |
|
178 def tearDown(self): |
|
179 shutil.rmtree(self.temp_dir) |
|
180 |
|
181 |
|
182 if __name__ == '__main__': |
|
183 unittest.main() |