1
# tests.syncdaemon.test_states.py - States tests
3
# Author: Facundo Batista <facundo@canonical.com>
5
# Copyright 2010 Canonical Ltd.
7
# This program is free software: you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License version 3, as published
9
# by the Free Software Foundation.
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranties of
13
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14
# PURPOSE. See the GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License along
17
# with this program. If not, see <http://www.gnu.org/licenses/>.
19
"""Tests for States."""
21
from twisted.internet import defer, reactor
22
from twisted.trial.unittest import TestCase as TwistedTestCase
24
from contrib.testing.testcase import FakeLogger
25
from ubuntuone.syncdaemon.states import (
26
StateManager, ConnectionManager, QueueManager, Node
30
class FakeEventQueue(object):
35
def push(self, event, *a, **k):
36
"""Appends event to the list."""
37
self.events.append(event)
39
def subscribe(self, _):
43
class FakeRequestQueue(object):
44
"""Fake object that marks the runs.
46
It has the behaviour of the real queues regarding waiting, done,
49
- WAITING when command is queued and the queue was empty (first cmd, bah).
50
- DONE when command is done and queue is empty
52
It does NOT send a WAITING everytime something is queued, and it does
53
NOT send a DONE everytime a command finishes.
66
self.qm.on_event('SYS_QUEUE_WAITING')
77
class FakeActionQueue(object):
78
"""Fake class to log actions on AQ."""
80
self.queue = FakeRequestQueue()
83
def __getattribute__(self, name):
84
"""Generic method logger."""
85
if name in ("connect", "disconnect", "cleanup"):
86
return lambda *a, **k: self.actions.append(name)
88
return object.__getattribute__(self, name)
91
class FakeMain(object):
92
"""Fake class to log actions on Main."""
93
def __init__(self, aq, eq):
98
def __getattribute__(self, name):
99
"""Generic method logger."""
100
if name in ("local_rescan", "check_version", "authenticate",
101
"set_capabilities", "server_rescan", "restart"):
102
return lambda *a, **k: self.actions.append(name)
104
return object.__getattribute__(self, name)
107
class Base(TwistedTestCase):
108
"""Base class for state tests."""
111
# create fake classes
112
self.eq = FakeEventQueue()
113
self.aq = FakeActionQueue()
114
self.main = FakeMain(self.aq, self.eq)
115
self.sm = StateManager(self.main, handshake_timeout=30)
116
self.qm = self.sm.queues
117
self.cm = self.sm.connection
119
# add QM to queues, to push events for testing
120
self.aq.queue.qm = self.qm
123
self.fake_logger = FakeLogger()
124
self.sm.log = self.fake_logger
125
self.qm.log = self.fake_logger
128
self.sm_allnodes = [getattr(StateManager, x) for x in dir(StateManager)
129
if isinstance(getattr(StateManager, x), Node)]
130
self.sm_connected = set(x for x in self.sm_allnodes if x.is_connected)
131
self.sm_disconnected = set(x for x in self.sm_allnodes
132
if not x.is_connected and not x.is_error)
133
self.sm_nodes_ok = set(x for x in self.sm_allnodes if not x.is_error)
134
self.sm_nodes_error = set(x for x in self.sm_allnodes if x.is_error)
136
def check_log(self, where, txt):
137
for line in self.fake_logger.logged[where]:
142
def wait_event(self, event):
143
"""Waits for the event in our EQ."""
147
"""Check the event."""
150
d.errback("Not %s found!" % event)
152
if event in self.eq.events:
155
reactor.callLater(.1, check, count)
157
reactor.callLater(.1, check, 0)
161
self.sm.connection.shutdown()
164
class QueueBase(Base):
165
"""Basic setup for QueueManager."""
169
self.sm.state = StateManager.QUEUE_MANAGER
172
class TestQueueManagerTransitions(QueueBase):
173
"""Transitions of QueueManager."""
175
def check(self, n_from, event, n_to, in_log=False):
176
self.qm.state = getattr(QueueManager, n_from)
177
self.qm.on_event(event)
178
self.assertEqual(self.qm.state, getattr(QueueManager, n_to),
179
"%s != %s" % (self.qm.state.name, n_to))
181
m = "Bad Event received: Got %r while in %s" % (event, n_from)
182
self.assertEqual(in_log, self.check_log('warning', m))
184
def test_idle_waiting(self):
185
"""Waiting when in IDLE."""
186
self.check('IDLE', 'SYS_QUEUE_WAITING', 'WORKING')
188
def test_idle_done(self):
189
"""Done when in IDLE."""
190
self.check('IDLE', 'SYS_QUEUE_DONE', 'IDLE', in_log=True)
192
def test_working_waiting(self):
193
"""Waiting when in WORKING."""
194
self.check('WORKING', 'SYS_QUEUE_WAITING', 'WORKING', in_log=True)
196
def test_working_done(self):
197
"""Done when in WORKING."""
198
self.check('WORKING', 'SYS_QUEUE_DONE', 'IDLE')
201
class TestQueueManagerFromOutside(QueueBase):
202
"""Getting in/out QueueManager."""
204
def test_into_idle(self):
205
"""Entering when IDLE."""
206
assert not self.aq.queue.active
207
self.qm.state = QueueManager.IDLE
208
self.sm.state = StateManager.SERVER_RESCAN
209
self.sm.handle_default('SYS_SERVER_RESCAN_DONE')
210
self.assertEqual(self.qm.state, QueueManager.IDLE)
211
self.assertTrue(self.aq.queue.active)
213
def test_into_working(self):
214
"""Entering when WORKING."""
215
assert not self.aq.queue.active
216
self.qm.state = QueueManager.WORKING
217
self.sm.state = StateManager.SERVER_RESCAN
218
self.sm.handle_default('SYS_SERVER_RESCAN_DONE')
219
self.assertEqual(self.qm.state, QueueManager.WORKING)
220
self.assertTrue(self.aq.queue.active)
223
class TestConnectionManager(Base):
224
"""Test the "internal network" transitions."""
229
# put SM on some state that does not generate further
230
# transition-related efforts for this CM
231
self.sm.state = StateManager.AUTH_FAILED
233
def check(self, n_from, event, n_to):
234
self.cm.state = getattr(ConnectionManager, n_from)
235
self.cm.on_event(event)
236
self.assertEqual(self.cm.state, getattr(ConnectionManager, n_to))
238
def test_initial_state(self):
239
"""The initial state is INIT."""
240
self.assertEqual(self.cm.state, ConnectionManager.NU_NN)
242
def test_nunn_netconn(self):
243
"""Receive SYS_NET_CONNECTED while in NotUserNotNetwork."""
244
self.check('NU_NN', 'SYS_NET_CONNECTED', 'NU_WN')
246
def test_nunn_userconn(self):
247
"""Receive SYS_USER_CONNECT while in NotUserNotNetwork."""
248
self.check('NU_NN', 'SYS_USER_CONNECT', 'WU_NN')
250
def test_nunn_netdisconn(self):
251
"""Receive SYS_NET_DISCONNECTED while in NotUserNotNetwork."""
252
self.check('NU_NN', 'SYS_NET_DISCONNECTED', 'NU_NN')
254
def test_nunn_userdisconn(self):
255
"""Receive SYS_USER_DISCONNECT while in NotUserNotNetwork."""
256
self.check('NU_NN', 'SYS_USER_DISCONNECT', 'NU_NN')
258
def test_nunn_connlost(self):
259
"""Receive SYS_CONNECTION_LOST while in NotUserNotNetwork."""
260
self.check('NU_NN', 'SYS_CONNECTION_LOST', 'NU_NN')
262
def test_nuwn_netconn(self):
263
"""Receive SYS_NET_CONNECTED while in NotUserWithNetwork."""
264
self.check('NU_WN', 'SYS_NET_CONNECTED', 'NU_WN')
266
def test_nuwn_userconn(self):
267
"""Receive SYS_USER_CONNECT while in NotUserWithNetwork."""
268
self.check('NU_WN', 'SYS_USER_CONNECT', 'WU_WN')
270
def test_nuwn_netdisconn(self):
271
"""Receive SYS_NET_DISCONNECTED while in NotUserWithNetwork."""
272
self.check('NU_WN', 'SYS_NET_DISCONNECTED', 'NU_NN')
274
def test_nuwn_userdisconn(self):
275
"""Receive SYS_USER_DISCONNECT while in NotUserWithNetwork."""
276
self.check('NU_WN', 'SYS_USER_DISCONNECT', 'NU_WN')
278
def test_nuwn_connlost(self):
279
"""Receive SYS_CONNECTION_LOST while in NotUserWithNetwork."""
280
self.check('NU_WN', 'SYS_CONNECTION_LOST', 'NU_WN')
282
def test_wunn_netconn(self):
283
"""Receive SYS_NET_CONNECTED while in WithUserNotNetwork."""
284
self.check('WU_NN', 'SYS_NET_CONNECTED', 'WU_WN')
286
def test_wunn_userconn(self):
287
"""Receive SYS_USER_CONNECT while in WithUserNotNetwork."""
288
self.check('WU_NN', 'SYS_USER_CONNECT', 'WU_NN')
290
def test_wunn_netdisconn(self):
291
"""Receive SYS_NET_DISCONNECTED while in WithUserNotNetwork."""
292
self.check('WU_NN', 'SYS_NET_DISCONNECTED', 'WU_NN')
294
def test_wunn_userdisconn(self):
295
"""Receive SYS_USER_DISCONNECT while in WithUserNotNetwork."""
296
self.check('WU_NN', 'SYS_USER_DISCONNECT', 'NU_NN')
298
def test_wunn_connlost(self):
299
"""Receive SYS_CONNECTION_LOST while in WithUserNotNetwork."""
300
self.check('WU_NN', 'SYS_CONNECTION_LOST', 'WU_NN')
302
def test_wuwn_netconn(self):
303
"""Receive SYS_NET_CONNECTED while in WithUserWithNetwork."""
304
self.check('WU_WN', 'SYS_NET_CONNECTED', 'WU_WN')
306
def test_wuwn_userconn(self):
307
"""Receive SYS_USER_CONNECT while in WithUserWithNetwork."""
308
self.check('WU_WN', 'SYS_USER_CONNECT', 'WU_WN')
310
def test_wuwn_netdisconn(self):
311
"""Receive SYS_NET_DISCONNECTED while in WithUserWithNetwork."""
312
self.check('WU_WN', 'SYS_NET_DISCONNECTED', 'WU_NN')
314
def test_wuwn_userdisconn(self):
315
"""Receive SYS_USER_DISCONNECT while in WithUserWithNetwork."""
316
self.check('WU_WN', 'SYS_USER_DISCONNECT', 'NU_WN')
318
def test_wuwn_connlost(self):
319
"""Receive SYS_CONNECTION_LOST while in WithUserWithNetwork."""
320
self.check('WU_WN', 'SYS_CONNECTION_LOST', 'WU_WN')
322
def test_shutdown_flag(self):
323
"""Shutdown puts itself in not working."""
324
self.assertTrue(self.cm.working)
326
self.assertFalse(self.cm.working)
328
def test_not_working_internal(self):
329
"""Not working, really! (internal check)."""
330
self.cm.working = False
331
self.check('WU_WN', 'SYS_NET_DISCONNECTED', 'WU_WN') # if working, WU_NN
333
def test_not_working_external(self):
334
"""Not working, really! (external check)."""
335
self.cm.working = False
336
self.sm.state = StateManager.STANDOFF
337
new_node = self.cm.on_event('SYS_CONNECTION_LOST')
338
self.assertTrue(new_node is None) # if working, it should return a node
341
class TestConnectionManagerTimings(Base):
342
"""Times handled by ConnectionManager."""
347
# set timeout values to really low, to make tests run quicker
348
self.sm.connection.handshake_timeout = 0
350
def check(self, n_from, event, result):
352
self.sm.state = getattr(StateManager, n_from)
353
self.sm.handle_default(event)
354
return self.wait_event(result)
356
def test_handshaketimeout_checkversion(self):
357
"""Handshake timing is controlled in CheckVersion."""
358
self.sm.connection.state = ConnectionManager.WU_WN
359
return self.check('READY', 'SYS_CONNECTION_MADE',
360
'SYS_HANDSHAKE_TIMEOUT')
362
def test_handshaketimeout_setcapabilities(self):
363
"""Handshake timing is controlled in SetCapabilities."""
364
return self.check('CHECK_VERSION', 'SYS_PROTOCOL_VERSION_OK',
365
'SYS_HANDSHAKE_TIMEOUT')
367
def test_handshaketimeout_authenticate(self):
368
"""Handshake timing is controlled in Authenticate."""
369
return self.check('SET_CAPABILITIES', 'SYS_SET_CAPABILITIES_OK',
370
'SYS_HANDSHAKE_TIMEOUT')
372
def test_waiting_triggers(self):
373
"""Check WAITING triggers the event."""
374
self.sm.connection.waiting_timeout = 0
375
return self.check('READY', 'SYS_CONNECTION_FAILED',
376
'SYS_CONNECTION_RETRY')
378
def test_waiting_behaviour(self):
379
"""Check WAITING increases values ok."""
380
timeouts = [2, 4, 8, 16, 32, 64, 120, 120, 120]
381
self.sm.connection.waiting_timeout = 1
383
self.sm.state = StateManager.READY
384
self.sm.handle_default('SYS_CONNECTION_FAILED')
385
self.assertEqual(self.sm.connection.waiting_timeout, t)
388
class StateManagerTransitions(Base):
389
"""Base class for all transition tests."""
393
def check(self, n_from, event, n_to, in_log=False):
394
if isinstance(n_from, str):
395
n_from = getattr(StateManager, n_from)
396
if isinstance(n_to, str):
397
n_to = getattr(StateManager, n_to)
398
self.sm.state = n_from
399
self.sm.handle_default(event)
400
self.assertEqual(self.sm.state, n_to, "%s / %s should %s but got %s" %
401
(n_from, event, n_to, self.sm.state))
403
m = "Bad Event received: Got %r while in %r" % (event, n_from.name)
404
self.assertEqual(in_log, self.check_log('warning', m),
405
"Bad log for %s / %s" % (n_from, event))
407
# wait state changed only if the before and after nodes are different
409
return defer.succeed(True)
411
return self.wait_event('SYS_STATE_CHANGED')
414
class TestStateManagerHighLevelTransitions(StateManagerTransitions):
415
"""Test StateManager high level transitions."""
417
def test_initial_state(self):
418
"""The initial state is INIT."""
419
self.assertEqual(self.sm.state, StateManager.INIT)
421
def test_init_localrescan(self):
422
"""Transition Init -> LocalRescan."""
423
return self.check('INIT', 'SYS_INIT_DONE', 'LOCAL_RESCAN')
425
def test_localrescan_ready(self):
426
"""Transition LocalRescan -> Ready."""
427
return self.check('LOCAL_RESCAN', 'SYS_LOCAL_RESCAN_DONE', 'READY')
429
def test_ready_standoff_nunn(self):
430
"""Transition (not) from Ready when connection is NU_NN."""
431
self.sm.connection.state = ConnectionManager.NU_NN
432
return self.check('READY', 'SYS_CONNECTION_MADE', 'READY', in_log=True)
434
def test_ready_standoff_nuwn(self):
435
"""Transition (not) from Ready when connection is NU_WN."""
436
self.sm.connection.state = ConnectionManager.NU_WN
437
return self.check('READY', 'SYS_CONNECTION_MADE', 'READY', in_log=True)
439
def test_ready_standoff_wunn(self):
440
"""Transition (not) from Ready when connection is WU_NN."""
441
self.sm.connection.state = ConnectionManager.WU_NN
442
return self.check('READY', 'SYS_CONNECTION_MADE', 'READY', in_log=True)
444
def test_ready_checkversion(self):
445
"""Transition Ready -> CheckVersion when connection is WU_WN."""
446
self.sm.connection.state = ConnectionManager.WU_WN
447
return self.check('READY', 'SYS_CONNECTION_MADE', 'CHECK_VERSION')
449
def test_ready_waiting(self):
450
"""Transition Ready -> Waiting."""
451
return self.check('READY', 'SYS_CONNECTION_FAILED', 'WAITING')
453
def test_waiting_ready(self):
454
"""Transition Waiting -> Ready."""
455
return self.check('WAITING', 'SYS_CONNECTION_RETRY', 'READY')
457
def test_checkversion_setcapabilities(self):
458
"""Transition CheckVersion -> SetCapabilities."""
459
return self.check('CHECK_VERSION', 'SYS_PROTOCOL_VERSION_OK',
462
def test_checkversion_error(self):
463
"""Transition CheckVersion -> Error."""
464
return self.check('CHECK_VERSION', 'SYS_PROTOCOL_VERSION_ERROR',
467
def test_checkversion_server_error(self):
468
"""Transition CheckVersion -> Standoff."""
469
return self.check('CHECK_VERSION', 'SYS_SERVER_ERROR', 'STANDOFF')
471
def test_setcapabilities_authenticate(self):
472
"""Transition SetCapabilities -> Authenticate."""
473
return self.check('SET_CAPABILITIES', 'SYS_SET_CAPABILITIES_OK',
476
def test_setcapabilities_error(self):
477
"""Transition SetCapabilities -> Error."""
478
return self.check('SET_CAPABILITIES', 'SYS_SET_CAPABILITIES_ERROR',
479
'CAPABILITIES_MISMATCH')
481
def test_setcapabilities_server_error(self):
482
"""Transition SetCapabilities -> Standoff."""
483
return self.check('SET_CAPABILITIES', 'SYS_SERVER_ERROR', 'STANDOFF')
485
def test_authenticate_serverrescan(self):
486
"""Transition Authenticate -> ServerRescan."""
487
return self.check('AUTHENTICATE', 'SYS_AUTH_OK', 'SERVER_RESCAN')
489
def test_authenticate_error(self):
490
"""Transition Authenticate -> Error."""
491
return self.check('AUTHENTICATE', 'SYS_AUTH_ERROR', 'AUTH_FAILED')
493
def test_authenticate_server_error(self):
494
"""Transition Authenticate -> Standoff."""
495
return self.check('AUTHENTICATE', 'SYS_SERVER_ERROR', 'STANDOFF')
497
def test_serverrescan_queuemanager(self):
498
"""Transition ServerRescan -> QueueManager."""
499
return self.check('SERVER_RESCAN', 'SYS_SERVER_RESCAN_DONE',
502
def test_serverrescan_standoff(self):
503
"""Transition ServerRescan -> Standoff."""
504
return self.check('SERVER_RESCAN', 'SYS_SERVER_ERROR', 'STANDOFF')
506
def test_network_events(self):
507
"""Don't make transition, and don't log warning."""
508
nodes = ['INIT', 'READY', 'STANDOFF', 'QUEUE_MANAGER'] # examples
510
for event in ('SYS_QUEUE_WAITING', 'SYS_QUEUE_DONE'):
512
d.append(self.check(node, event, node))
513
return defer.DeferredList(d)
516
class TestStateManagerQueueTransitions(StateManagerTransitions):
517
"""Test Queue transitions from StateManager POV."""
519
def check(self, node_name, event):
520
"""Checks the transition."""
521
self.sm.queues.state = getattr(QueueManager, node_name)
522
self.sm.handle_default(event)
523
return self.wait_event('SYS_STATE_CHANGED')
525
def test_IDLE_SYS_QUEUE_WAITING(self):
526
"""Transition from IDLE when SYS_QUEUE_WAITING."""
527
return self.check('IDLE', 'SYS_QUEUE_WAITING')
529
def test_WORKING_SYS_QUEUE_DONE(self):
530
"""Transition from WORKING when SYS_QUEUE_DONE."""
531
return self.check('WORKING', 'SYS_QUEUE_DONE')
534
class TestStateManagerNetworkTransitions(StateManagerTransitions):
535
"""Test StateManager network transitions."""
537
def test_net_connected(self):
538
"""The SYS_NET_CONNECTED event never changes from node."""
540
for node_name in self.sm_nodes_ok:
541
d.append(self.check(node_name, 'SYS_NET_CONNECTED', node_name))
542
return defer.DeferredList(d)
544
def test_user_connect(self):
545
"""The SYS_USER_CONNECT event never changes from node."""
547
for node_name in self.sm_nodes_ok:
548
d.append(self.check(node_name, 'SYS_USER_CONNECT', node_name))
549
return defer.DeferredList(d)
551
def test_disconn_net_disconnected(self):
552
"""Test SYS_NET_DISCONNECTED when disconnected."""
554
for node_name in self.sm_disconnected:
555
d.append(self.check(node_name, 'SYS_NET_DISCONNECTED', node_name))
556
return defer.DeferredList(d)
558
def test_disconn_user_disconnect(self):
559
"""Test SYS_USER_DISCONNECT when disconnected."""
561
for node_name in self.sm_disconnected:
562
d.append(self.check(node_name, 'SYS_USER_DISCONNECT', node_name))
563
return defer.DeferredList(d)
565
def test_disconn_connection_lost(self):
566
"""Test SYS_CONNECTION_LOST when disconnected."""
568
for node_name in self.sm_disconnected:
569
d.append(self.check(node_name, 'SYS_CONNECTION_LOST', node_name,
571
return defer.DeferredList(d)
573
def test_conn_connection_lost(self):
574
"""Test SYS_CONNECTION_LOST when connected."""
576
for node_name in self.sm_connected:
577
d.append(self.check(node_name, 'SYS_CONNECTION_LOST', 'WAITING'))
578
return defer.DeferredList(d)
580
def test_someconnected_netdisconn_userdisconn(self):
581
"""Test both DISCONNECT when connected (except standoff)."""
583
for node_name in self.sm_connected - set([StateManager.STANDOFF]):
584
for event in ('SYS_USER_DISCONNECT', 'SYS_NET_DISCONNECTED'):
585
d.append(self.check(node_name, event, 'STANDOFF'))
586
return defer.DeferredList(d)
588
def test_standoff_netdisconn_userdisconn(self):
589
"""Test both DISCONNECT on StandOff."""
591
for event in ('SYS_USER_DISCONNECT', 'SYS_NET_DISCONNECTED'):
592
d.append(self.check('STANDOFF', event, 'STANDOFF'))
593
return defer.DeferredList(d)
596
class TestStateManagerTimeoutTransitions(StateManagerTransitions):
597
"""Test StateManager when HandshakeTimeouts."""
599
def test_disconn(self):
600
"""Test when disconnected."""
602
for node in self.sm_disconnected:
603
d.append(self.check(node, 'SYS_HANDSHAKE_TIMEOUT', node, True))
604
return defer.DeferredList(d)
606
def test_some_connected_events(self):
607
"""Test on some connected events."""
609
for node in ('CHECK_VERSION', 'SET_CAPABILITIES', 'AUTHENTICATE'):
610
d.append(self.check(node, 'SYS_HANDSHAKE_TIMEOUT', 'STANDOFF'))
611
return defer.DeferredList(d)
613
def test_serverrescan(self):
614
"""Test on ServerRescan."""
615
return self.check('SERVER_RESCAN', 'SYS_HANDSHAKE_TIMEOUT',
618
def test_queuemanager(self):
619
"""Test on QueueManager."""
620
return self.check('QUEUE_MANAGER', 'SYS_HANDSHAKE_TIMEOUT',
621
'QUEUE_MANAGER', True)
623
def test_standoff(self):
624
"""Test on StandOff."""
625
return self.check('STANDOFF', 'SYS_HANDSHAKE_TIMEOUT',
629
class TestStateManagerShutdown(StateManagerTransitions):
630
"""Test StateManager when shutting down."""
632
def test_shutdown(self):
633
"""All nodes go to SHUTDOWN."""
635
for node in self.sm_allnodes:
636
d.append(self.check(node, 'SYS_QUIT', 'SHUTDOWN'))
637
return defer.DeferredList(d)
640
class TestStateManagerErrors(StateManagerTransitions):
641
"""Test StateManager on error conditions."""
643
def test_unknown_error(self):
644
"""All nodes go to unknown_error."""
646
for node in self.sm_nodes_ok:
647
d.append(self.check(node, 'SYS_UNKNOWN_ERROR', 'UNKNOWN_ERROR'))
648
return defer.DeferredList(d)
650
def test_root_mismatch_error(self):
651
"""All nodes go to root_mismatch."""
653
for node in self.sm_nodes_ok:
654
d.append(self.check(node, 'SYS_ROOT_MISMATCH', 'ROOT_MISMATCH'))
655
return defer.DeferredList(d)
657
def test_not_exiting_from_errors(self):
658
"""No return from errors."""
660
for node in self.sm_nodes_error:
661
d.append(self.check(node, 'SYS_CONNECTION_LOST', node))
662
d.append(self.check(node, 'SYS_HANDSHAKE_TIMEOUT', node))
663
d.append(self.check(node, 'SYS_CONNECTION_MADE', node))
664
d.append(self.check(node, 'SYS_SERVER_ERROR', node))
665
return defer.DeferredList(d)
668
class TestStateManagerEnterExit(Base):
669
"""Test StateManager on enter and exit transitions."""
671
def test_to_error(self):
672
"""Transition to error."""
673
self.sm.handle_default('SYS_UNKNOWN_ERROR')
674
self.assertEqual(self.main.actions, ['restart'])
676
def test_init_localrescan(self):
677
"""Transition Init -> LocalRescan."""
678
self.sm.state = StateManager.INIT
679
self.sm.handle_default('SYS_INIT_DONE')
680
self.assertEqual(self.main.actions, ['local_rescan'])
682
def test_localrescan_ready_netok(self):
683
"""Transition LocalRescan -> Ready with network ok."""
684
self.sm.state = StateManager.LOCAL_RESCAN
685
self.sm.connection.state = ConnectionManager.WU_WN
686
self.sm.handle_default('SYS_LOCAL_RESCAN_DONE')
687
self.assertEqual(self.aq.actions, ['connect'])
689
def test_localrescan_ready_netbad(self):
690
"""Transition LocalRescan -> Ready with network bad."""
691
for net in (ConnectionManager.WU_NN, ConnectionManager.NU_WN,
692
ConnectionManager.NU_NN):
693
self.sm.connection.state = net
694
self.sm.state = StateManager.LOCAL_RESCAN
695
self.sm.handle_default('SYS_LOCAL_RESCAN_DONE')
696
self.assertEqual(self.aq.actions, [])
698
def test_waiting_ready_netok(self):
699
"""Transition LocalRescan -> Ready with network ok."""
700
self.sm.state = StateManager.WAITING
701
self.sm.connection.state = ConnectionManager.WU_WN
702
self.sm.handle_default('SYS_CONNECTION_RETRY')
703
self.assertEqual(self.aq.actions, ['connect'])
705
def test_waiting_ready_netbad(self):
706
"""Transition LocalRescan -> Ready with network bad."""
707
for net in (ConnectionManager.WU_NN, ConnectionManager.NU_WN,
708
ConnectionManager.NU_NN):
709
self.sm.connection.state = net
710
self.sm.state = StateManager.WAITING
711
self.sm.handle_default('SYS_CONNECTION_RETRY')
712
self.assertEqual(self.aq.actions, [])
714
def test_ready_internal_nunn(self):
715
"""Internal READY transition from NU_NN."""
716
for evt in ('SYS_NET_CONNECTED', 'SYS_USER_CONNECT',
717
'SYS_NET_DISCONNECTED', 'SYS_USER_DISCONNECT',
718
'SYS_CONNECTION_LOST'):
719
self.sm.state = StateManager.READY
720
self.sm.connection.state = ConnectionManager.NU_NN
721
self.sm.handle_default(evt)
722
self.assertEqual(self.aq.actions, [])
724
def test_ready_internal_nuwn(self):
725
"""Internal READY transition from NU_WN."""
726
for evt in ('SYS_NET_CONNECTED' 'SYS_NET_DISCONNECTED',
727
'SYS_USER_DISCONNECT', 'SYS_CONNECTION_LOST'):
728
self.sm.state = StateManager.READY
729
self.sm.connection.state = ConnectionManager.NU_WN
730
self.sm.handle_default(evt)
731
self.assertEqual(self.aq.actions, [])
733
self.sm.state = StateManager.READY
734
self.sm.connection.state = ConnectionManager.NU_WN
735
self.sm.handle_default('SYS_USER_CONNECT')
736
self.assertEqual(self.aq.actions, ['connect'])
738
def test_ready_internal_wunn(self):
739
"""Internal READY transition from WU_NN."""
740
for evt in ('SYS_USER_CONNECT' 'SYS_NET_DISCONNECTED',
741
'SYS_USER_DISCONNECT', 'SYS_CONNECTION_LOST'):
742
self.sm.state = StateManager.READY
743
self.sm.connection.state = ConnectionManager.WU_NN
744
self.sm.handle_default(evt)
745
self.assertEqual(self.aq.actions, [])
747
self.sm.state = StateManager.READY
748
self.sm.connection.state = ConnectionManager.WU_NN
749
self.sm.handle_default('SYS_NET_CONNECTED')
750
self.assertEqual(self.aq.actions, ['connect'])
752
def test_internal_valid_othernode(self):
753
"""Don't call connect if in other node."""
754
self.sm.state = StateManager.LOCAL_RESCAN # whatever
755
self.sm.connection.state = ConnectionManager.NU_WN
756
self.sm.handle_default('SYS_USER_CONNECT')
757
self.assertEqual(self.aq.actions, [])
759
self.sm.state = StateManager.STANDOFF # whatever
760
self.sm.connection.state = ConnectionManager.WU_NN
761
self.sm.handle_default('SYS_NET_CONNECTED')
762
self.assertEqual(self.aq.actions, [])
764
def test_ready_internal_wuwn(self):
765
"""Internal READY transition from WU_WN."""
766
for evt in ('SYS_NET_CONNECTED', 'SYS_USER_CONNECT',
767
'SYS_NET_DISCONNECTED', 'SYS_USER_DISCONNECT',
768
'SYS_CONNECTION_LOST'):
769
self.sm.state = StateManager.READY
770
self.sm.connection.state = ConnectionManager.WU_WN
771
self.sm.handle_default(evt)
772
self.assertEqual(self.aq.actions, [])
774
def test_ready_checkversion(self):
775
"""Transition Ready -> CheckVersion."""
776
self.sm.state = StateManager.READY
777
self.sm.connection.state = ConnectionManager.WU_WN
778
self.sm.handle_default('SYS_CONNECTION_MADE')
779
self.assertEqual(self.main.actions, ['check_version'])
781
def test_checkversion_setcapabilities(self):
782
"""Transition CheckVersion -> SetCapabilities."""
783
self.sm.state = StateManager.CHECK_VERSION
784
self.sm.handle_default('SYS_PROTOCOL_VERSION_OK')
785
self.assertEqual(self.main.actions, ['set_capabilities'])
787
def test_setcapabilities_authenticate(self):
788
"""Transition SetCapabilities -> Authenticate."""
789
self.sm.state = StateManager.SET_CAPABILITIES
790
self.sm.handle_default('SYS_SET_CAPABILITIES_OK')
791
self.assertEqual(self.main.actions, ['authenticate'])
793
def test_authenticate_serverrescan(self):
794
"""Transition Authenticate -> ServerRescan."""
795
self.sm.state = StateManager.AUTHENTICATE
796
self.sm.handle_default('SYS_AUTH_OK')
797
self.assertEqual(self.main.actions, ['server_rescan'])
799
def test_alot_standoff(self):
800
"""Lots of transitions to Standoff."""
801
nodes = (StateManager.CHECK_VERSION, StateManager.SET_CAPABILITIES,
802
StateManager.AUTHENTICATE)
803
events = ('SYS_NET_DISCONNECTED', 'SYS_USER_DISCONNECT',
804
'SYS_HANDSHAKE_TIMEOUT', 'SYS_SERVER_ERROR')
810
self.sm.handle_default(event)
811
self.assertEqual(self.aq.actions, ['disconnect']*cnt)
813
self.aq.actions[:] = []
815
for node in (StateManager.SERVER_RESCAN, StateManager.QUEUE_MANAGER):
816
for event in ('SYS_NET_DISCONNECTED', 'SYS_USER_DISCONNECT'):
819
self.sm.handle_default(event)
820
# we just count the disconnects because there're some on_exit mixed
822
self.assertEqual(self.aq.actions.count('disconnect'), cnt)
824
def test_server_rescan_to_standoff(self):
825
"""ServerRescan transitions to Standoff generates an AQ.disconnect."""
826
# case of ServerRescan and SYS_SERVER_ERROR
827
self.aq.actions[:] = []
828
self.sm.state = StateManager.SERVER_RESCAN
829
self.sm.handle_default('SYS_SERVER_ERROR')
830
self.assertEqual(self.aq.actions, ['disconnect'])
832
def test_exit_queuemanager(self):
833
"""Exit transitions from QueueManager."""
834
events = ('SYS_NET_DISCONNECTED', 'SYS_USER_DISCONNECT',
835
'SYS_CONNECTION_LOST')
837
self.aq.queue.active = True
838
self.sm.state = StateManager.QUEUE_MANAGER
839
self.sm.handle_default(event)
840
self.assertFalse(self.aq.queue.active)
843
class TestStateManagerPassToNetworkManager(Base):
844
"""All network events should go to NetworkManager."""
849
# put a function in the middle to log calls
850
self.called_events = []
851
orig_on_event = self.sm.connection.on_event
852
def fake_on_event(event):
853
"""Log the call and call original."""
854
self.called_events.append(event)
856
self.sm.connection.on_event = fake_on_event
858
def _test(self, event):
859
"""Generic test method."""
861
for node in self.sm_nodes_ok:
864
self.sm.handle_default(event)
865
self.assertEqual(self.called_events, [event]*cnt)
867
def test_net_connected(self):
868
"""SYS_NET_CONNECTED should go to Connection no matter where."""
869
self._test('SYS_NET_CONNECTED')
871
def test_user_connect(self):
872
"""SYS_USER_CONNECT should go to Connection no matter where."""
873
self._test('SYS_USER_CONNECT')
875
def test_net_disconnected(self):
876
"""SYS_NET_DISCONNECTED should go to Connection no matter where."""
877
self._test('SYS_NET_DISCONNECTED')
879
def test_user_disconnect(self):
880
"""SYS_USER_DISCONNECT should go to Connection no matter where."""
881
self._test('SYS_USER_DISCONNECT')
883
def test_connection_lost(self):
884
"""SYS_CONNECTION_LOST should go to Connection no matter where."""
885
self._test('SYS_CONNECTION_LOST')
887
def test_handshake_timeout(self):
888
"""SYS_HANDSHAKE_TIMEOUT should go to Connection no matter where."""
889
self._test('SYS_HANDSHAKE_TIMEOUT')
893
class TestStateManagerPassToQueueManager(Base):
894
"""All queue events should go to QueueManager."""
899
# put a function in the middle to log calls
900
self.called_events = []
901
orig_on_event = self.sm.queues.on_event
902
def fake_on_event(event):
903
"""Log the call and call original."""
904
self.called_events.append(event)
906
self.sm.queues.on_event = fake_on_event
908
def _test(self, event):
909
"""Generic test method."""
911
for node in self.sm_nodes_ok:
914
self.sm.handle_default(event)
915
self.assertEqual(self.called_events, [event]*cnt)
917
def test_meta_waiting(self):
918
"""SYS_QUEUE_WAITING should go to QueueMgr no matter where."""
919
self._test('SYS_QUEUE_WAITING')
921
def test_meta_done(self):
922
"""SYS_QUEUE_DONE should go to QueueMgr no matter where."""
923
self._test('SYS_QUEUE_DONE')
926
class TestStateManagerAPI(Base):
927
"""Test StateManager API."""
929
def test_states_have_data(self):
930
"""The StateManager states have some info."""
932
for node in self.sm_allnodes:
934
self.assertTrue(isinstance(sm.state.name, basestring))
935
self.assertTrue(isinstance(sm.state.description, basestring))
936
self.assertTrue(isinstance(sm.state.is_error, bool))
937
self.assertTrue(isinstance(sm.state.is_connected, bool))
938
self.assertTrue(isinstance(sm.state.is_online, bool))
940
def check_node(self, name, error, conn, online):
941
self.assertEqual(self.sm.state.name, name)
942
self.assertEqual(self.sm.state.is_error, error)
943
self.assertEqual(self.sm.state.is_connected, conn)
944
self.assertEqual(self.sm.state.is_online, online)
947
"""INIT internals."""
948
self.sm.state = StateManager.INIT
949
self.check_node("INIT", error=False, conn=False, online=False)
951
def test_LOCAL_RESCAN(self):
952
"""LOCAL_RESCAN internals."""
953
self.sm.state = StateManager.LOCAL_RESCAN
954
self.check_node("LOCAL_RESCAN", error=False, conn=False, online=False)
956
def test_READY(self):
957
"""READY internals."""
958
self.sm.state = StateManager.READY
959
self.check_node("READY", error=False, conn=False, online=False)
961
def test_WAITING(self):
962
"""WAITING internals."""
963
self.sm.state = StateManager.WAITING
964
self.check_node("WAITING", error=False, conn=False, online=False)
966
def test_CHECK_VERSION(self):
967
"""CHECK_VERSION internals."""
968
self.sm.state = StateManager.CHECK_VERSION
969
self.check_node("CHECK_VERSION", error=False, conn=True, online=False)
971
def test_BAD_VERSION(self):
972
"""BAD_VERSION internals."""
973
self.sm.state = StateManager.BAD_VERSION
974
self.check_node("BAD_VERSION", error=True, conn=False, online=False)
976
def test_SET_CAPABILITIES(self):
977
"""SET_CAPABILITIES internals."""
978
self.sm.state = StateManager.SET_CAPABILITIES
979
self.check_node("SET_CAPABILITIES",
980
error=False, conn=True, online=False)
982
def test_CAPABILITIES_MISMATCH(self):
983
"""CAPABILITIES_MISMATCH internals."""
984
self.sm.state = StateManager.CAPABILITIES_MISMATCH
985
self.check_node("CAPABILITIES_MISMATCH",
986
error=True, conn=False, online=False)
988
def test_AUTHENTICATE(self):
989
"""AUTHENTICATE internals."""
990
self.sm.state = StateManager.AUTHENTICATE
991
self.check_node("AUTHENTICATE", error=False, conn=True, online=False)
993
def test_AUTH_FAILED(self):
994
"""AUTH_FAILED internals."""
995
self.sm.state = StateManager.AUTH_FAILED
996
self.check_node("AUTH_FAILED", error=True, conn=False, online=False)
998
def test_SERVER_RESCAN(self):
999
"""SERVER_RESCAN internals."""
1000
self.sm.state = StateManager.SERVER_RESCAN
1001
self.check_node("SERVER_RESCAN", error=False, conn=True, online=False)
1003
def test_QUEUE_MANAGER(self):
1004
"""QUEUE_MANAGER internals."""
1005
self.sm.state = StateManager.QUEUE_MANAGER
1006
self.check_node("QUEUE_MANAGER", error=False, conn=True, online=True)
1008
def test_STANDOFF(self):
1009
"""STANDOFF internals."""
1010
self.sm.state = StateManager.STANDOFF
1011
self.check_node("STANDOFF", error=False, conn=True, online=False)
1013
def test_SHUTDOWN(self):
1014
"""SHUTDOWN internals."""
1015
self.sm.state = StateManager.SHUTDOWN
1016
self.check_node("SHUTDOWN", error=False, conn=False, online=False)