Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame^] | 1 | # ex:ts=4:sw=4:sts=4:et |
| 2 | # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- |
| 3 | # |
| 4 | # BitBake Tests for the Event implementation (event.py) |
| 5 | # |
| 6 | # Copyright (C) 2017 Intel Corporation |
| 7 | # |
| 8 | # This program is free software; you can redistribute it and/or modify |
| 9 | # it under the terms of the GNU General Public License version 2 as |
| 10 | # published by the Free Software Foundation. |
| 11 | # |
| 12 | # This program is distributed in the hope that it will be useful, |
| 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | # GNU General Public License for more details. |
| 16 | # |
| 17 | # You should have received a copy of the GNU General Public License along |
| 18 | # with this program; if not, write to the Free Software Foundation, Inc., |
| 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | # |
| 21 | |
| 22 | import unittest |
| 23 | import bb |
| 24 | import logging |
| 25 | import bb.compat |
| 26 | import bb.event |
| 27 | import importlib |
| 28 | import threading |
| 29 | import time |
| 30 | import pickle |
| 31 | from unittest.mock import Mock |
| 32 | from unittest.mock import call |
| 33 | |
| 34 | |
| 35 | class EventQueueStub(): |
| 36 | """ Class used as specification for UI event handler queue stub objects """ |
| 37 | def __init__(self): |
| 38 | return |
| 39 | |
| 40 | def send(self, event): |
| 41 | return |
| 42 | |
| 43 | |
| 44 | class PickleEventQueueStub(): |
| 45 | """ Class used as specification for UI event handler queue stub objects |
| 46 | with sendpickle method """ |
| 47 | def __init__(self): |
| 48 | return |
| 49 | |
| 50 | def sendpickle(self, pickled_event): |
| 51 | return |
| 52 | |
| 53 | |
| 54 | class UIClientStub(): |
| 55 | """ Class used as specification for UI event handler stub objects """ |
| 56 | def __init__(self): |
| 57 | self.event = None |
| 58 | |
| 59 | |
| 60 | class EventHandlingTest(unittest.TestCase): |
| 61 | """ Event handling test class """ |
| 62 | _threadlock_test_calls = [] |
| 63 | |
| 64 | def setUp(self): |
| 65 | self._test_process = Mock() |
| 66 | ui_client1 = UIClientStub() |
| 67 | ui_client2 = UIClientStub() |
| 68 | self._test_ui1 = Mock(wraps=ui_client1) |
| 69 | self._test_ui2 = Mock(wraps=ui_client2) |
| 70 | importlib.reload(bb.event) |
| 71 | |
| 72 | def _create_test_handlers(self): |
| 73 | """ Method used to create a test handler ordered dictionary """ |
| 74 | test_handlers = bb.compat.OrderedDict() |
| 75 | test_handlers["handler1"] = self._test_process.handler1 |
| 76 | test_handlers["handler2"] = self._test_process.handler2 |
| 77 | return test_handlers |
| 78 | |
| 79 | def test_class_handlers(self): |
| 80 | """ Test set_class_handlers and get_class_handlers methods """ |
| 81 | test_handlers = self._create_test_handlers() |
| 82 | bb.event.set_class_handlers(test_handlers) |
| 83 | self.assertEqual(test_handlers, |
| 84 | bb.event.get_class_handlers()) |
| 85 | |
| 86 | def test_handlers(self): |
| 87 | """ Test set_handlers and get_handlers """ |
| 88 | test_handlers = self._create_test_handlers() |
| 89 | bb.event.set_handlers(test_handlers) |
| 90 | self.assertEqual(test_handlers, |
| 91 | bb.event.get_handlers()) |
| 92 | |
| 93 | def test_clean_class_handlers(self): |
| 94 | """ Test clean_class_handlers method """ |
| 95 | cleanDict = bb.compat.OrderedDict() |
| 96 | self.assertEqual(cleanDict, |
| 97 | bb.event.clean_class_handlers()) |
| 98 | |
| 99 | def test_register(self): |
| 100 | """ Test register method for class handlers """ |
| 101 | result = bb.event.register("handler", self._test_process.handler) |
| 102 | self.assertEqual(result, bb.event.Registered) |
| 103 | handlers_dict = bb.event.get_class_handlers() |
| 104 | self.assertIn("handler", handlers_dict) |
| 105 | |
| 106 | def test_already_registered(self): |
| 107 | """ Test detection of an already registed class handler """ |
| 108 | bb.event.register("handler", self._test_process.handler) |
| 109 | handlers_dict = bb.event.get_class_handlers() |
| 110 | self.assertIn("handler", handlers_dict) |
| 111 | result = bb.event.register("handler", self._test_process.handler) |
| 112 | self.assertEqual(result, bb.event.AlreadyRegistered) |
| 113 | |
| 114 | def test_register_from_string(self): |
| 115 | """ Test register method receiving code in string """ |
| 116 | result = bb.event.register("string_handler", " return True") |
| 117 | self.assertEqual(result, bb.event.Registered) |
| 118 | handlers_dict = bb.event.get_class_handlers() |
| 119 | self.assertIn("string_handler", handlers_dict) |
| 120 | |
| 121 | def test_register_with_mask(self): |
| 122 | """ Test register method with event masking """ |
| 123 | mask = ["bb.event.OperationStarted", |
| 124 | "bb.event.OperationCompleted"] |
| 125 | result = bb.event.register("event_handler", |
| 126 | self._test_process.event_handler, |
| 127 | mask) |
| 128 | self.assertEqual(result, bb.event.Registered) |
| 129 | handlers_dict = bb.event.get_class_handlers() |
| 130 | self.assertIn("event_handler", handlers_dict) |
| 131 | |
| 132 | def test_remove(self): |
| 133 | """ Test remove method for class handlers """ |
| 134 | test_handlers = self._create_test_handlers() |
| 135 | bb.event.set_class_handlers(test_handlers) |
| 136 | count = len(test_handlers) |
| 137 | bb.event.remove("handler1", None) |
| 138 | test_handlers = bb.event.get_class_handlers() |
| 139 | self.assertEqual(len(test_handlers), count - 1) |
| 140 | with self.assertRaises(KeyError): |
| 141 | bb.event.remove("handler1", None) |
| 142 | |
| 143 | def test_execute_handler(self): |
| 144 | """ Test execute_handler method for class handlers """ |
| 145 | mask = ["bb.event.OperationProgress"] |
| 146 | result = bb.event.register("event_handler", |
| 147 | self._test_process.event_handler, |
| 148 | mask) |
| 149 | self.assertEqual(result, bb.event.Registered) |
| 150 | event = bb.event.OperationProgress(current=10, total=100) |
| 151 | bb.event.execute_handler("event_handler", |
| 152 | self._test_process.event_handler, |
| 153 | event, |
| 154 | None) |
| 155 | self._test_process.event_handler.assert_called_once_with(event) |
| 156 | |
| 157 | def test_fire_class_handlers(self): |
| 158 | """ Test fire_class_handlers method """ |
| 159 | mask = ["bb.event.OperationStarted"] |
| 160 | result = bb.event.register("event_handler1", |
| 161 | self._test_process.event_handler1, |
| 162 | mask) |
| 163 | self.assertEqual(result, bb.event.Registered) |
| 164 | result = bb.event.register("event_handler2", |
| 165 | self._test_process.event_handler2, |
| 166 | "*") |
| 167 | self.assertEqual(result, bb.event.Registered) |
| 168 | event1 = bb.event.OperationStarted() |
| 169 | event2 = bb.event.OperationCompleted(total=123) |
| 170 | bb.event.fire_class_handlers(event1, None) |
| 171 | bb.event.fire_class_handlers(event2, None) |
| 172 | bb.event.fire_class_handlers(event2, None) |
| 173 | expected_event_handler1 = [call(event1)] |
| 174 | expected_event_handler2 = [call(event1), |
| 175 | call(event2), |
| 176 | call(event2)] |
| 177 | self.assertEqual(self._test_process.event_handler1.call_args_list, |
| 178 | expected_event_handler1) |
| 179 | self.assertEqual(self._test_process.event_handler2.call_args_list, |
| 180 | expected_event_handler2) |
| 181 | |
| 182 | def test_change_handler_event_mapping(self): |
| 183 | """ Test changing the event mapping for class handlers """ |
| 184 | event1 = bb.event.OperationStarted() |
| 185 | event2 = bb.event.OperationCompleted(total=123) |
| 186 | |
| 187 | # register handler for all events |
| 188 | result = bb.event.register("event_handler1", |
| 189 | self._test_process.event_handler1, |
| 190 | "*") |
| 191 | self.assertEqual(result, bb.event.Registered) |
| 192 | bb.event.fire_class_handlers(event1, None) |
| 193 | bb.event.fire_class_handlers(event2, None) |
| 194 | expected = [call(event1), call(event2)] |
| 195 | self.assertEqual(self._test_process.event_handler1.call_args_list, |
| 196 | expected) |
| 197 | |
| 198 | # unregister handler and register it only for OperationStarted |
| 199 | result = bb.event.remove("event_handler1", |
| 200 | self._test_process.event_handler1) |
| 201 | mask = ["bb.event.OperationStarted"] |
| 202 | result = bb.event.register("event_handler1", |
| 203 | self._test_process.event_handler1, |
| 204 | mask) |
| 205 | self.assertEqual(result, bb.event.Registered) |
| 206 | bb.event.fire_class_handlers(event1, None) |
| 207 | bb.event.fire_class_handlers(event2, None) |
| 208 | expected = [call(event1), call(event2), call(event1)] |
| 209 | self.assertEqual(self._test_process.event_handler1.call_args_list, |
| 210 | expected) |
| 211 | |
| 212 | # unregister handler and register it only for OperationCompleted |
| 213 | result = bb.event.remove("event_handler1", |
| 214 | self._test_process.event_handler1) |
| 215 | mask = ["bb.event.OperationCompleted"] |
| 216 | result = bb.event.register("event_handler1", |
| 217 | self._test_process.event_handler1, |
| 218 | mask) |
| 219 | self.assertEqual(result, bb.event.Registered) |
| 220 | bb.event.fire_class_handlers(event1, None) |
| 221 | bb.event.fire_class_handlers(event2, None) |
| 222 | expected = [call(event1), call(event2), call(event1), call(event2)] |
| 223 | self.assertEqual(self._test_process.event_handler1.call_args_list, |
| 224 | expected) |
| 225 | |
| 226 | def test_register_UIHhandler(self): |
| 227 | """ Test register_UIHhandler method """ |
| 228 | result = bb.event.register_UIHhandler(self._test_ui1, mainui=True) |
| 229 | self.assertEqual(result, 1) |
| 230 | |
| 231 | def test_UIHhandler_already_registered(self): |
| 232 | """ Test registering an UIHhandler already existing """ |
| 233 | result = bb.event.register_UIHhandler(self._test_ui1, mainui=True) |
| 234 | self.assertEqual(result, 1) |
| 235 | result = bb.event.register_UIHhandler(self._test_ui1, mainui=True) |
| 236 | self.assertEqual(result, 2) |
| 237 | |
| 238 | def test_unregister_UIHhandler(self): |
| 239 | """ Test unregister_UIHhandler method """ |
| 240 | result = bb.event.register_UIHhandler(self._test_ui1, mainui=True) |
| 241 | self.assertEqual(result, 1) |
| 242 | result = bb.event.unregister_UIHhandler(1) |
| 243 | self.assertIs(result, None) |
| 244 | |
| 245 | def test_fire_ui_handlers(self): |
| 246 | """ Test fire_ui_handlers method """ |
| 247 | self._test_ui1.event = Mock(spec_set=EventQueueStub) |
| 248 | result = bb.event.register_UIHhandler(self._test_ui1, mainui=True) |
| 249 | self.assertEqual(result, 1) |
| 250 | self._test_ui2.event = Mock(spec_set=PickleEventQueueStub) |
| 251 | result = bb.event.register_UIHhandler(self._test_ui2, mainui=True) |
| 252 | self.assertEqual(result, 2) |
| 253 | event1 = bb.event.OperationStarted() |
| 254 | bb.event.fire_ui_handlers(event1, None) |
| 255 | expected = [call(event1)] |
| 256 | self.assertEqual(self._test_ui1.event.send.call_args_list, |
| 257 | expected) |
| 258 | expected = [call(pickle.dumps(event1))] |
| 259 | self.assertEqual(self._test_ui2.event.sendpickle.call_args_list, |
| 260 | expected) |
| 261 | |
| 262 | def test_fire(self): |
| 263 | """ Test fire method used to trigger class and ui event handlers """ |
| 264 | mask = ["bb.event.ConfigParsed"] |
| 265 | result = bb.event.register("event_handler1", |
| 266 | self._test_process.event_handler1, |
| 267 | mask) |
| 268 | |
| 269 | self._test_ui1.event = Mock(spec_set=EventQueueStub) |
| 270 | result = bb.event.register_UIHhandler(self._test_ui1, mainui=True) |
| 271 | self.assertEqual(result, 1) |
| 272 | |
| 273 | event1 = bb.event.ConfigParsed() |
| 274 | bb.event.fire(event1, None) |
| 275 | expected = [call(event1)] |
| 276 | self.assertEqual(self._test_process.event_handler1.call_args_list, |
| 277 | expected) |
| 278 | self.assertEqual(self._test_ui1.event.send.call_args_list, |
| 279 | expected) |
| 280 | |
| 281 | def test_fire_from_worker(self): |
| 282 | """ Test fire_from_worker method """ |
| 283 | self._test_ui1.event = Mock(spec_set=EventQueueStub) |
| 284 | result = bb.event.register_UIHhandler(self._test_ui1, mainui=True) |
| 285 | self.assertEqual(result, 1) |
| 286 | event1 = bb.event.ConfigParsed() |
| 287 | bb.event.fire_from_worker(event1, None) |
| 288 | expected = [call(event1)] |
| 289 | self.assertEqual(self._test_ui1.event.send.call_args_list, |
| 290 | expected) |
| 291 | |
| 292 | def test_print_ui_queue(self): |
| 293 | """ Test print_ui_queue method """ |
| 294 | event1 = bb.event.OperationStarted() |
| 295 | event2 = bb.event.OperationCompleted(total=123) |
| 296 | bb.event.fire(event1, None) |
| 297 | bb.event.fire(event2, None) |
| 298 | logger = logging.getLogger("BitBake") |
| 299 | logger.addHandler(bb.event.LogHandler()) |
| 300 | logger.info("Test info LogRecord") |
| 301 | logger.warning("Test warning LogRecord") |
| 302 | with self.assertLogs("BitBake", level="INFO") as cm: |
| 303 | bb.event.print_ui_queue() |
| 304 | self.assertEqual(cm.output, |
| 305 | ["INFO:BitBake:Test info LogRecord", |
| 306 | "WARNING:BitBake:Test warning LogRecord"]) |
| 307 | |
| 308 | def _set_threadlock_test_mockups(self): |
| 309 | """ Create UI event handler mockups used in enable and disable |
| 310 | threadlock tests """ |
| 311 | def ui1_event_send(event): |
| 312 | if type(event) is bb.event.ConfigParsed: |
| 313 | self._threadlock_test_calls.append("w1_ui1") |
| 314 | if type(event) is bb.event.OperationStarted: |
| 315 | self._threadlock_test_calls.append("w2_ui1") |
| 316 | time.sleep(2) |
| 317 | |
| 318 | def ui2_event_send(event): |
| 319 | if type(event) is bb.event.ConfigParsed: |
| 320 | self._threadlock_test_calls.append("w1_ui2") |
| 321 | if type(event) is bb.event.OperationStarted: |
| 322 | self._threadlock_test_calls.append("w2_ui2") |
| 323 | time.sleep(2) |
| 324 | |
| 325 | self._threadlock_test_calls = [] |
| 326 | self._test_ui1.event = EventQueueStub() |
| 327 | self._test_ui1.event.send = ui1_event_send |
| 328 | result = bb.event.register_UIHhandler(self._test_ui1, mainui=True) |
| 329 | self.assertEqual(result, 1) |
| 330 | self._test_ui2.event = EventQueueStub() |
| 331 | self._test_ui2.event.send = ui2_event_send |
| 332 | result = bb.event.register_UIHhandler(self._test_ui2, mainui=True) |
| 333 | self.assertEqual(result, 2) |
| 334 | |
| 335 | def _set_and_run_threadlock_test_workers(self): |
| 336 | """ Create and run the workers used to trigger events in enable and |
| 337 | disable threadlock tests """ |
| 338 | worker1 = threading.Thread(target=self._thread_lock_test_worker1) |
| 339 | worker2 = threading.Thread(target=self._thread_lock_test_worker2) |
| 340 | worker1.start() |
| 341 | time.sleep(1) |
| 342 | worker2.start() |
| 343 | worker1.join() |
| 344 | worker2.join() |
| 345 | |
| 346 | def _thread_lock_test_worker1(self): |
| 347 | """ First worker used to fire the ConfigParsed event for enable and |
| 348 | disable threadlocks tests """ |
| 349 | bb.event.fire(bb.event.ConfigParsed(), None) |
| 350 | |
| 351 | def _thread_lock_test_worker2(self): |
| 352 | """ Second worker used to fire the OperationStarted event for enable |
| 353 | and disable threadlocks tests """ |
| 354 | bb.event.fire(bb.event.OperationStarted(), None) |
| 355 | |
| 356 | def test_enable_threadlock(self): |
| 357 | """ Test enable_threadlock method """ |
| 358 | self._set_threadlock_test_mockups() |
| 359 | bb.event.enable_threadlock() |
| 360 | self._set_and_run_threadlock_test_workers() |
| 361 | # Calls to UI handlers should be in order as all the registered |
| 362 | # handlers for the event coming from the first worker should be |
| 363 | # called before processing the event from the second worker. |
| 364 | self.assertEqual(self._threadlock_test_calls, |
| 365 | ["w1_ui1", "w1_ui2", "w2_ui1", "w2_ui2"]) |
| 366 | |
| 367 | def test_disable_threadlock(self): |
| 368 | """ Test disable_threadlock method """ |
| 369 | self._set_threadlock_test_mockups() |
| 370 | bb.event.disable_threadlock() |
| 371 | self._set_and_run_threadlock_test_workers() |
| 372 | # Calls to UI handlers should be intertwined together. Thanks to the |
| 373 | # delay in the registered handlers for the event coming from the first |
| 374 | # worker, the event coming from the second worker starts being |
| 375 | # processed before finishing handling the first worker event. |
| 376 | self.assertEqual(self._threadlock_test_calls, |
| 377 | ["w1_ui1", "w2_ui1", "w1_ui2", "w2_ui2"]) |