| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2001-2008 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 """ | |
| 5 Test cases for twisted.words.xish.utility | |
| 6 """ | |
| 7 | |
| 8 from twisted.trial import unittest | |
| 9 | |
| 10 from twisted.python.util import OrderedDict | |
| 11 from twisted.words.xish import utility | |
| 12 from twisted.words.xish.domish import Element | |
| 13 from twisted.words.xish.utility import EventDispatcher | |
| 14 | |
| 15 class CallbackTracker: | |
| 16 """ | |
| 17 Test helper for tracking callbacks. | |
| 18 | |
| 19 Increases a counter on each call to L{call} and stores the object | |
| 20 passed in the call. | |
| 21 """ | |
| 22 | |
| 23 def __init__(self): | |
| 24 self.called = 0 | |
| 25 self.obj = None | |
| 26 | |
| 27 | |
| 28 def call(self, obj): | |
| 29 self.called = self.called + 1 | |
| 30 self.obj = obj | |
| 31 | |
| 32 | |
| 33 | |
| 34 class OrderedCallbackTracker: | |
| 35 """ | |
| 36 Test helper for tracking callbacks and their order. | |
| 37 """ | |
| 38 | |
| 39 def __init__(self): | |
| 40 self.callList = [] | |
| 41 | |
| 42 | |
| 43 def call1(self, object): | |
| 44 self.callList.append(self.call1) | |
| 45 | |
| 46 | |
| 47 def call2(self, object): | |
| 48 self.callList.append(self.call2) | |
| 49 | |
| 50 | |
| 51 def call3(self, object): | |
| 52 self.callList.append(self.call3) | |
| 53 | |
| 54 | |
| 55 | |
| 56 class EventDispatcherTest(unittest.TestCase): | |
| 57 """ | |
| 58 Tests for L{EventDispatcher}. | |
| 59 """ | |
| 60 | |
| 61 def testStuff(self): | |
| 62 d = EventDispatcher() | |
| 63 cb1 = CallbackTracker() | |
| 64 cb2 = CallbackTracker() | |
| 65 cb3 = CallbackTracker() | |
| 66 | |
| 67 d.addObserver("/message/body", cb1.call) | |
| 68 d.addObserver("/message", cb1.call) | |
| 69 d.addObserver("/presence", cb2.call) | |
| 70 d.addObserver("//event/testevent", cb3.call) | |
| 71 | |
| 72 msg = Element(("ns", "message")) | |
| 73 msg.addElement("body") | |
| 74 | |
| 75 pres = Element(("ns", "presence")) | |
| 76 pres.addElement("presence") | |
| 77 | |
| 78 d.dispatch(msg) | |
| 79 self.assertEquals(cb1.called, 2) | |
| 80 self.assertEquals(cb1.obj, msg) | |
| 81 self.assertEquals(cb2.called, 0) | |
| 82 | |
| 83 d.dispatch(pres) | |
| 84 self.assertEquals(cb1.called, 2) | |
| 85 self.assertEquals(cb2.called, 1) | |
| 86 self.assertEquals(cb2.obj, pres) | |
| 87 self.assertEquals(cb3.called, 0) | |
| 88 | |
| 89 d.dispatch(d, "//event/testevent") | |
| 90 self.assertEquals(cb3.called, 1) | |
| 91 self.assertEquals(cb3.obj, d) | |
| 92 | |
| 93 d.removeObserver("/presence", cb2.call) | |
| 94 d.dispatch(pres) | |
| 95 self.assertEquals(cb2.called, 1) | |
| 96 | |
| 97 | |
| 98 def test_addObserverTwice(self): | |
| 99 """ | |
| 100 Test adding two observers for the same query. | |
| 101 | |
| 102 When the event is dispath both of the observers need to be called. | |
| 103 """ | |
| 104 d = EventDispatcher() | |
| 105 cb1 = CallbackTracker() | |
| 106 cb2 = CallbackTracker() | |
| 107 | |
| 108 d.addObserver("//event/testevent", cb1.call) | |
| 109 d.addObserver("//event/testevent", cb2.call) | |
| 110 d.dispatch(d, "//event/testevent") | |
| 111 | |
| 112 self.assertEquals(cb1.called, 1) | |
| 113 self.assertEquals(cb1.obj, d) | |
| 114 self.assertEquals(cb2.called, 1) | |
| 115 self.assertEquals(cb2.obj, d) | |
| 116 | |
| 117 | |
| 118 def test_addObserverInDispatch(self): | |
| 119 """ | |
| 120 Test for registration of an observer during dispatch. | |
| 121 """ | |
| 122 d = EventDispatcher() | |
| 123 msg = Element(("ns", "message")) | |
| 124 cb = CallbackTracker() | |
| 125 | |
| 126 def onMessage(_): | |
| 127 d.addObserver("/message", cb.call) | |
| 128 | |
| 129 d.addOnetimeObserver("/message", onMessage) | |
| 130 | |
| 131 d.dispatch(msg) | |
| 132 self.assertEquals(cb.called, 0) | |
| 133 | |
| 134 d.dispatch(msg) | |
| 135 self.assertEquals(cb.called, 1) | |
| 136 | |
| 137 d.dispatch(msg) | |
| 138 self.assertEquals(cb.called, 2) | |
| 139 | |
| 140 | |
| 141 def test_addOnetimeObserverInDispatch(self): | |
| 142 """ | |
| 143 Test for registration of a onetime observer during dispatch. | |
| 144 """ | |
| 145 d = EventDispatcher() | |
| 146 msg = Element(("ns", "message")) | |
| 147 cb = CallbackTracker() | |
| 148 | |
| 149 def onMessage(msg): | |
| 150 d.addOnetimeObserver("/message", cb.call) | |
| 151 | |
| 152 d.addOnetimeObserver("/message", onMessage) | |
| 153 | |
| 154 d.dispatch(msg) | |
| 155 self.assertEquals(cb.called, 0) | |
| 156 | |
| 157 d.dispatch(msg) | |
| 158 self.assertEquals(cb.called, 1) | |
| 159 | |
| 160 d.dispatch(msg) | |
| 161 self.assertEquals(cb.called, 1) | |
| 162 | |
| 163 | |
| 164 def testOnetimeDispatch(self): | |
| 165 d = EventDispatcher() | |
| 166 msg = Element(("ns", "message")) | |
| 167 cb = CallbackTracker() | |
| 168 | |
| 169 d.addOnetimeObserver("/message", cb.call) | |
| 170 d.dispatch(msg) | |
| 171 self.assertEquals(cb.called, 1) | |
| 172 d.dispatch(msg) | |
| 173 self.assertEquals(cb.called, 1) | |
| 174 | |
| 175 | |
| 176 def testDispatcherResult(self): | |
| 177 d = EventDispatcher() | |
| 178 msg = Element(("ns", "message")) | |
| 179 pres = Element(("ns", "presence")) | |
| 180 cb = CallbackTracker() | |
| 181 | |
| 182 d.addObserver("/presence", cb.call) | |
| 183 result = d.dispatch(msg) | |
| 184 self.assertEquals(False, result) | |
| 185 | |
| 186 result = d.dispatch(pres) | |
| 187 self.assertEquals(True, result) | |
| 188 | |
| 189 | |
| 190 def testOrderedXPathDispatch(self): | |
| 191 d = EventDispatcher() | |
| 192 cb = OrderedCallbackTracker() | |
| 193 d.addObserver("/message/body", cb.call2) | |
| 194 d.addObserver("/message", cb.call3, -1) | |
| 195 d.addObserver("/message/body", cb.call1, 1) | |
| 196 | |
| 197 msg = Element(("ns", "message")) | |
| 198 msg.addElement("body") | |
| 199 d.dispatch(msg) | |
| 200 self.assertEquals(cb.callList, [cb.call1, cb.call2, cb.call3], | |
| 201 "Calls out of order: %s" % | |
| 202 repr([c.__name__ for c in cb.callList])) | |
| 203 | |
| 204 | |
| 205 # Observers are put into CallbackLists that are then put into dictionaries | |
| 206 # keyed by the event trigger. Upon removal of the last observer for a | |
| 207 # particular event trigger, the (now empty) CallbackList and corresponding | |
| 208 # event trigger should be removed from those dictionaries to prevent | |
| 209 # slowdown and memory leakage. | |
| 210 | |
| 211 def test_cleanUpRemoveEventObserver(self): | |
| 212 """ | |
| 213 Test observer clean-up after removeObserver for named events. | |
| 214 """ | |
| 215 | |
| 216 d = EventDispatcher() | |
| 217 cb = CallbackTracker() | |
| 218 | |
| 219 d.addObserver('//event/test', cb.call) | |
| 220 d.dispatch(None, '//event/test') | |
| 221 self.assertEqual(1, cb.called) | |
| 222 d.removeObserver('//event/test', cb.call) | |
| 223 self.assertEqual(0, len(d._eventObservers.pop(0))) | |
| 224 | |
| 225 | |
| 226 def test_cleanUpRemoveXPathObserver(self): | |
| 227 """ | |
| 228 Test observer clean-up after removeObserver for XPath events. | |
| 229 """ | |
| 230 | |
| 231 d = EventDispatcher() | |
| 232 cb = CallbackTracker() | |
| 233 msg = Element((None, "message")) | |
| 234 | |
| 235 d.addObserver('/message', cb.call) | |
| 236 d.dispatch(msg) | |
| 237 self.assertEqual(1, cb.called) | |
| 238 d.removeObserver('/message', cb.call) | |
| 239 self.assertEqual(0, len(d._xpathObservers.pop(0))) | |
| 240 | |
| 241 | |
| 242 def test_cleanUpOnetimeEventObserver(self): | |
| 243 """ | |
| 244 Test observer clean-up after onetime named events. | |
| 245 """ | |
| 246 | |
| 247 d = EventDispatcher() | |
| 248 cb = CallbackTracker() | |
| 249 | |
| 250 d.addOnetimeObserver('//event/test', cb.call) | |
| 251 d.dispatch(None, '//event/test') | |
| 252 self.assertEqual(1, cb.called) | |
| 253 self.assertEqual(0, len(d._eventObservers.pop(0))) | |
| 254 | |
| 255 | |
| 256 def test_cleanUpOnetimeXPathObserver(self): | |
| 257 """ | |
| 258 Test observer clean-up after onetime XPath events. | |
| 259 """ | |
| 260 | |
| 261 d = EventDispatcher() | |
| 262 cb = CallbackTracker() | |
| 263 msg = Element((None, "message")) | |
| 264 | |
| 265 d.addOnetimeObserver('/message', cb.call) | |
| 266 d.dispatch(msg) | |
| 267 self.assertEqual(1, cb.called) | |
| 268 self.assertEqual(0, len(d._xpathObservers.pop(0))) | |
| 269 | |
| 270 | |
| 271 def test_observerRaisingException(self): | |
| 272 """ | |
| 273 Test that exceptions in observers do not bubble up to dispatch. | |
| 274 | |
| 275 The exceptions raised in observers should be logged and other | |
| 276 observers should be called as if nothing happened. | |
| 277 """ | |
| 278 | |
| 279 class OrderedCallbackList(utility.CallbackList): | |
| 280 def __init__(self): | |
| 281 self.callbacks = OrderedDict() | |
| 282 | |
| 283 class TestError(Exception): | |
| 284 pass | |
| 285 | |
| 286 def raiseError(_): | |
| 287 raise TestError() | |
| 288 | |
| 289 d = EventDispatcher() | |
| 290 cb = CallbackTracker() | |
| 291 | |
| 292 originalCallbackList = utility.CallbackList | |
| 293 | |
| 294 try: | |
| 295 utility.CallbackList = OrderedCallbackList | |
| 296 | |
| 297 d.addObserver('//event/test', raiseError) | |
| 298 d.addObserver('//event/test', cb.call) | |
| 299 try: | |
| 300 d.dispatch(None, '//event/test') | |
| 301 except TestError: | |
| 302 self.fail("TestError raised. Should have been logged instead.") | |
| 303 | |
| 304 self.assertEqual(1, len(self.flushLoggedErrors(TestError))) | |
| 305 self.assertEqual(1, cb.called) | |
| 306 finally: | |
| 307 utility.CallbackList = originalCallbackList | |
| OLD | NEW |