Home | History | Annotate | Download | only in bot
      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.common.system.outputcapture import OutputCapture
     38 from webkitpy.tool.bot.queueengine import QueueEngine, QueueEngineDelegate, TerminateQueue
     39 
     40 
     41 class 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 
    105 class 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 
    115 class 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 
    122 class 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 
    133 class 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 
    208 if __name__ == '__main__':
    209     unittest.main()
    210