• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
29import datetime
30import os
31import shutil
32import tempfile
33import threading
34import unittest
35
36from webkitpy.common.system.executive import ScriptError
37from webkitpy.common.system.outputcapture import OutputCapture
38from webkitpy.tool.bot.queueengine import QueueEngine, QueueEngineDelegate, TerminateQueue
39
40
41class LoggingDelegate(QueueEngineDelegate):
42    def __init__(self, test):
43        self._test = test
44        self._callbacks = []
45        self._run_before = False
46        self.stop_message = None
47
48    expected_callbacks = [
49        'queue_log_path',
50        'begin_work_queue',
51        'should_continue_work_queue',
52        'next_work_item',
53        'should_proceed_with_work_item',
54        'work_item_log_path',
55        'process_work_item',
56        'should_continue_work_queue',
57        'stop_work_queue',
58    ]
59
60    def record(self, method_name):
61        self._callbacks.append(method_name)
62
63    def queue_log_path(self):
64        self.record("queue_log_path")
65        return os.path.join(self._test.temp_dir, "queue_log_path")
66
67    def work_item_log_path(self, work_item):
68        self.record("work_item_log_path")
69        return os.path.join(self._test.temp_dir, "work_log_path", "%s.log" % work_item)
70
71    def begin_work_queue(self):
72        self.record("begin_work_queue")
73
74    def should_continue_work_queue(self):
75        self.record("should_continue_work_queue")
76        if not self._run_before:
77            self._run_before = True
78            return True
79        return False
80
81    def next_work_item(self):
82        self.record("next_work_item")
83        return "work_item"
84
85    def should_proceed_with_work_item(self, work_item):
86        self.record("should_proceed_with_work_item")
87        self._test.assertEquals(work_item, "work_item")
88        fake_patch = { 'bug_id' : 42 }
89        return (True, "waiting_message", fake_patch)
90
91    def process_work_item(self, work_item):
92        self.record("process_work_item")
93        self._test.assertEquals(work_item, "work_item")
94        return True
95
96    def handle_unexpected_error(self, work_item, message):
97        self.record("handle_unexpected_error")
98        self._test.assertEquals(work_item, "work_item")
99
100    def stop_work_queue(self, message):
101        self.record("stop_work_queue")
102        self.stop_message = message
103
104
105class RaisingDelegate(LoggingDelegate):
106    def __init__(self, test, exception):
107        LoggingDelegate.__init__(self, test)
108        self._exception = exception
109
110    def process_work_item(self, work_item):
111        self.record("process_work_item")
112        raise self._exception
113
114
115class NotSafeToProceedDelegate(LoggingDelegate):
116    def should_proceed_with_work_item(self, work_item):
117        self.record("should_proceed_with_work_item")
118        self._test.assertEquals(work_item, "work_item")
119        return False
120
121
122class FastQueueEngine(QueueEngine):
123    def __init__(self, delegate):
124        QueueEngine.__init__(self, "fast-queue", delegate, threading.Event())
125
126    # No sleep for the wicked.
127    seconds_to_sleep = 0
128
129    def _sleep(self, message):
130        pass
131
132
133class QueueEngineTest(unittest.TestCase):
134    def test_trivial(self):
135        delegate = LoggingDelegate(self)
136        self._run_engine(delegate)
137        self.assertEquals(delegate.stop_message, "Delegate terminated queue.")
138        self.assertEquals(delegate._callbacks, LoggingDelegate.expected_callbacks)
139        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, "queue_log_path")))
140        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, "work_log_path", "work_item.log")))
141
142    def test_unexpected_error(self):
143        delegate = RaisingDelegate(self, ScriptError(exit_code=3))
144        self._run_engine(delegate)
145        expected_callbacks = LoggingDelegate.expected_callbacks[:]
146        work_item_index = expected_callbacks.index('process_work_item')
147        # The unexpected error should be handled right after process_work_item starts
148        # but before any other callback.  Otherwise callbacks should be normal.
149        expected_callbacks.insert(work_item_index + 1, 'handle_unexpected_error')
150        self.assertEquals(delegate._callbacks, expected_callbacks)
151
152    def test_handled_error(self):
153        delegate = RaisingDelegate(self, ScriptError(exit_code=QueueEngine.handled_error_code))
154        self._run_engine(delegate)
155        self.assertEquals(delegate._callbacks, LoggingDelegate.expected_callbacks)
156
157    def _run_engine(self, delegate, engine=None, termination_message=None):
158        if not engine:
159            engine = QueueEngine("test-queue", delegate, threading.Event())
160        if not termination_message:
161            termination_message = "Delegate terminated queue."
162        expected_stderr = "\n%s\n" % termination_message
163        OutputCapture().assert_outputs(self, engine.run, [], expected_stderr=expected_stderr)
164
165    def _test_terminating_queue(self, exception, termination_message):
166        work_item_index = LoggingDelegate.expected_callbacks.index('process_work_item')
167        # The terminating error should be handled right after process_work_item.
168        # There should be no other callbacks after stop_work_queue.
169        expected_callbacks = LoggingDelegate.expected_callbacks[:work_item_index + 1]
170        expected_callbacks.append("stop_work_queue")
171
172        delegate = RaisingDelegate(self, exception)
173        self._run_engine(delegate, termination_message=termination_message)
174
175        self.assertEquals(delegate._callbacks, expected_callbacks)
176        self.assertEquals(delegate.stop_message, termination_message)
177
178    def test_terminating_error(self):
179        self._test_terminating_queue(KeyboardInterrupt(), "User terminated queue.")
180        self._test_terminating_queue(TerminateQueue(), "TerminateQueue exception received.")
181
182    def test_not_safe_to_proceed(self):
183        delegate = NotSafeToProceedDelegate(self)
184        self._run_engine(delegate, engine=FastQueueEngine(delegate))
185        expected_callbacks = LoggingDelegate.expected_callbacks[:]
186        expected_callbacks.remove('work_item_log_path')
187        expected_callbacks.remove('process_work_item')
188        self.assertEquals(delegate._callbacks, expected_callbacks)
189
190    def test_now(self):
191        """Make sure there are no typos in the QueueEngine.now() method."""
192        engine = QueueEngine("test", None, None)
193        self.assertTrue(isinstance(engine._now(), datetime.datetime))
194
195    def test_sleep_message(self):
196        engine = QueueEngine("test", None, None)
197        engine._now = lambda: datetime.datetime(2010, 1, 1)
198        expected_sleep_message = "MESSAGE Sleeping until 2010-01-01 00:02:00 (2 mins)."
199        self.assertEqual(engine._sleep_message("MESSAGE"), expected_sleep_message)
200
201    def setUp(self):
202        self.temp_dir = tempfile.mkdtemp(suffix="work_queue_test_logs")
203
204    def tearDown(self):
205        shutil.rmtree(self.temp_dir)
206
207
208if __name__ == '__main__':
209    unittest.main()
210