e80765a9939bb9d655a5a81596913b8ba5d8bede
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / controllers / manager_worker_broker_unittest.py
1 # Copyright (C) 2010 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 optparse
30 import Queue
31 import sys
32 import unittest
33
34 try:
35     import multiprocessing
36 except ImportError:
37     multiprocessing = None
38
39
40 from webkitpy.common.system import outputcapture
41 from webkitpy.common.host_mock import MockHost
42
43 from webkitpy.layout_tests import port
44 from webkitpy.layout_tests.controllers import manager_worker_broker
45 from webkitpy.layout_tests.controllers import message_broker
46 from webkitpy.layout_tests.views import printing
47
48
49 # In order to reliably control when child workers are starting and stopping,
50 # we use a pair of global variables to hold queues used for messaging. Ideally
51 # we wouldn't need globals, but we can't pass these through a lexical closure
52 # because those can't be Pickled and sent to a subprocess, and we'd prefer not
53 # to have to pass extra arguments to the worker in the start_worker() call.
54 starting_queue = None
55 stopping_queue = None
56
57
58 def make_broker(manager, worker_model, start_queue=None, stop_queue=None):
59     global starting_queue
60     global stopping_queue
61     starting_queue = start_queue
62     stopping_queue = stop_queue
63     options = get_options(worker_model)
64     host = MockHost()
65     test_port = host.port_factory.get("test")
66     return manager_worker_broker.get(test_port, options, manager, _TestWorker)
67
68
69 class _TestWorker(manager_worker_broker.AbstractWorker):
70     def __init__(self, broker_connection, worker_number, options):
71         self._broker_connection = broker_connection
72         self._options = options
73         self._worker_number = worker_number
74         self._name = 'TestWorker/%d' % worker_number
75         self._stopped = False
76         self._canceled = False
77         self._starting_queue = starting_queue
78         self._stopping_queue = stopping_queue
79
80     def handle_stop(self, src):
81         self._stopped = True
82
83     def handle_test(self, src, an_int, a_str):
84         assert an_int == 1
85         assert a_str == "hello, world"
86         self._broker_connection.post_message('test', 2, 'hi, everybody')
87
88     def is_done(self):
89         return self._stopped or self._canceled
90
91     def name(self):
92         return self._name
93
94     def cancel(self):
95         self._canceled = True
96
97     def run(self, port):
98         if self._starting_queue:
99             self._starting_queue.put('')
100
101         if self._stopping_queue:
102             self._stopping_queue.get()
103         try:
104             self._broker_connection.run_message_loop()
105             self._broker_connection.yield_to_broker()
106             self._broker_connection.post_message('done')
107         except Exception, e:
108             self._broker_connection.post_message('exception', (type(e), str(e), None))
109
110
111 def get_options(worker_model):
112     option_list = (manager_worker_broker.runtime_options() +
113                    printing.print_options() +
114                    [optparse.make_option("--experimental-fully-parallel", default=False),
115                     optparse.make_option("--child-processes", default='2')])
116     parser = optparse.OptionParser(option_list=option_list)
117     options, args = parser.parse_args(args=['--worker-model', worker_model])
118     return options
119
120
121
122 class FunctionTests(unittest.TestCase):
123     def test_get__inline(self):
124         self.assertTrue(make_broker(self, 'inline') is not None)
125
126     def test_get__processes(self):
127         # This test sometimes fails on Windows. See <http://webkit.org/b/55087>.
128         if sys.platform in ('cygwin', 'win32'):
129             return
130
131         if multiprocessing:
132             self.assertTrue(make_broker(self, 'processes') is not None)
133         else:
134             self.assertRaises(ValueError, make_broker, self, 'processes')
135
136     def test_get__unknown(self):
137         self.assertRaises(ValueError, make_broker, self, 'unknown')
138
139
140 class _TestsMixin(object):
141     """Mixin class that implements a series of tests to enforce the
142     contract all implementations must follow."""
143
144     def name(self):
145         return 'Tester'
146
147     def is_done(self):
148         return self._done
149
150     def handle_done(self, src):
151         self._done = True
152
153     def handle_test(self, src, an_int, a_str):
154         self._an_int = an_int
155         self._a_str = a_str
156
157     def handle_exception(self, src, exc_info):
158         self._exception = exc_info
159         self._done = True
160
161     def setUp(self):
162         self._an_int = None
163         self._a_str = None
164         self._broker = None
165         self._done = False
166         self._exception = None
167         self._worker_model = None
168
169     def make_broker(self, starting_queue=None, stopping_queue=None):
170         self._broker = make_broker(self, self._worker_model, starting_queue,
171                                    stopping_queue)
172
173     def test_cancel(self):
174         self.make_broker()
175         worker = self._broker.start_worker(0)
176         worker.cancel()
177         self._broker.post_message('test', 1, 'hello, world')
178         worker.join(0.5)
179         self.assertFalse(worker.is_alive())
180
181     def test_done(self):
182         self.make_broker()
183         worker = self._broker.start_worker(0)
184         self._broker.post_message('test', 1, 'hello, world')
185         self._broker.post_message('stop')
186         self._broker.run_message_loop()
187         worker.join(0.5)
188         self.assertFalse(worker.is_alive())
189         self.assertTrue(self.is_done())
190         self.assertEqual(self._an_int, 2)
191         self.assertEqual(self._a_str, 'hi, everybody')
192
193     def test_unknown_message(self):
194         self.make_broker()
195         worker = self._broker.start_worker(0)
196         self._broker.post_message('unknown')
197         self._broker.run_message_loop()
198         worker.join(0.5)
199
200         self.assertTrue(self.is_done())
201         self.assertFalse(worker.is_alive())
202         self.assertEquals(self._exception[0], ValueError)
203         self.assertEquals(self._exception[1],
204             "TestWorker/0: received message 'unknown' it couldn't handle")
205
206
207 # FIXME: https://bugs.webkit.org/show_bug.cgi?id=54520.
208 if multiprocessing and sys.platform not in ('cygwin', 'win32'):
209
210     class MultiProcessBrokerTests(_TestsMixin, unittest.TestCase):
211         def setUp(self):
212             _TestsMixin.setUp(self)
213             self._worker_model = 'processes'
214
215         def queue(self):
216             return multiprocessing.Queue()
217
218
219 class FunctionsTest(unittest.TestCase):
220     def test_runtime_options(self):
221         option_list = manager_worker_broker.runtime_options()
222         parser = optparse.OptionParser(option_list=option_list)
223         options, args = parser.parse_args([])
224         self.assertTrue(options)
225
226
227 class InterfaceTest(unittest.TestCase):
228     # These tests mostly exist to pacify coverage.
229
230     # FIXME: There must be a better way to do this and also verify
231     # that classes do implement every abstract method in an interface.
232     def test_managerconnection_is_abstract(self):
233         # Test that all the base class methods are abstract and have the
234         # signature we expect.
235         broker = make_broker(self, 'inline')
236         obj = manager_worker_broker._ManagerConnection(broker._broker, None, self, None)
237         self.assertRaises(NotImplementedError, obj.start_worker, 0)
238
239     def test_workerconnection_is_abstract(self):
240         # Test that all the base class methods are abstract and have the
241         # signature we expect.
242         broker = make_broker(self, 'inline')
243         obj = manager_worker_broker._WorkerConnection(broker._broker, _TestWorker, 0, None)
244         self.assertRaises(NotImplementedError, obj.cancel)
245         self.assertRaises(NotImplementedError, obj.is_alive)
246         self.assertRaises(NotImplementedError, obj.join, None)
247
248
249 if __name__ == '__main__':
250     unittest.main()