1
# tests.status.test_aggregator
3
# Author: Alejandro J. Cura <alecu@canonical.com>
5
# Copyright 2011-2012 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
# In addition, as a special exception, the copyright holders give
20
# permission to link the code of portions of this program with the
21
# OpenSSL library under certain conditions as described in each
22
# individual source file, and distribute linked combinations
24
# You must obey the GNU General Public License in all respects
25
# for all of the code used other than OpenSSL. If you modify
26
# file(s) with this exception, you may extend this exception to your
27
# version of the file(s), but you are not obligated to do so. If you
28
# do not wish to do so, delete this exception statement from your
29
# version. If you delete this exception statement from all source
30
# files in the program, then also delete it here.
31
"""Tests for the status events aggregator."""
35
from twisted.internet import defer
36
from twisted.internet.task import Clock
37
from twisted.trial.unittest import TestCase
38
from mocker import Mocker
40
from contrib.testing.testcase import BaseTwistedTestCase
41
from ubuntuone.devtools.handlers import MementoHandler
42
from ubuntuone.devtools.testcases import skipTest
43
from ubuntuone.status import aggregator
44
from ubuntuone.status.notification import AbstractNotification
45
from ubuntuone.syncdaemon import (
51
from ubuntuone.syncdaemon.volume_manager import Share, UDF, Root
53
FILENAME = 'example.txt'
54
FILENAME2 = 'another_example.mp3'
57
class PatchedClock(Clock):
58
"""Patch the clock to fix twisted bug #4823."""
60
def advance(self, amount):
61
"""Sort the calls before advancing the clock."""
62
self.calls.sort(lambda a, b: cmp(a.getTime(), b.getTime()))
63
Clock.advance(self, amount)
66
class TimerTestCase(TestCase):
67
"""Test the Timer class."""
71
@defer.inlineCallbacks
73
"""Initialize this test instance."""
74
yield super(TimerTestCase, self).setUp()
75
self.clock = PatchedClock()
76
self.timer = aggregator.Timer(delay=3.0, clock=self.clock)
78
def test_not_fired_initially(self):
79
"""The timer is not fired initially"""
80
self.assertFalse(self.timer.called)
82
def test_fired_after_delay(self):
83
"""The timer is fired after the initial delay."""
84
self.clock.advance(self.timer.delay)
85
self.assertTrue(self.timer.called)
87
def test_cleanup_cancels_delay_call(self):
88
"""Calling cleanup cancels the delay call."""
90
self.assertTrue(self.timer.delay_call.cancelled)
92
def test_not_fired_immediately(self):
93
"""The timer is not fired immediately."""
95
self.assertFalse(self.timer.called)
97
def test_fired_after_initial_wait(self):
98
"""The timer is fired after an initial wait."""
100
self.clock.advance(self.timer.delay)
101
self.assertTrue(self.timer.called)
103
def test_not_fired_if_reset_within_delay(self):
104
"""The timer is not fired if it is reset within the delay."""
106
self.clock.advance(self.timer.delay / 0.8)
108
self.clock.advance(self.timer.delay / 0.8)
109
self.assertTrue(self.timer.called)
111
def test_active(self):
112
"""The timer is active until the delay is reached."""
114
self.assertTrue(self.timer.active)
115
self.clock.advance(self.timer.delay + 1)
116
self.assertFalse(self.timer.active)
119
class DeadlineTimerTestCase(TimerTestCase):
120
"""Test the DeadlineTimer class."""
124
@defer.inlineCallbacks
126
"""Initialize this test instance."""
127
yield super(DeadlineTimerTestCase, self).setUp()
128
self.clock = PatchedClock()
129
self.timer = aggregator.DeadlineTimer(
130
delay=0.5, timeout=3.0, clock=self.clock)
132
def test_fired_if_initial_timeout_exceeded(self):
133
"""Timer is fired if the initial timeout is exceeded."""
134
small_delay = self.timer.delay * 0.8
135
for n in range(int(self.timer.timeout / small_delay) + 1):
137
self.clock.advance(small_delay)
138
self.assertTrue(self.timer.called)
140
def test_not_fired_twice_if_delay_exceeded(self):
141
"""Timer is not fired twice if the delay is exceeded."""
142
large_delay = self.timer.delay * 1.2
143
for n in range(int(self.timer.timeout / large_delay) + 1):
145
self.clock.advance(large_delay)
146
self.clock.advance(self.timer.delay)
147
self.assertTrue(self.timer.called)
149
def test_not_fired_twice_if_timeout_exceeded(self):
150
"""Timer is not fired twice if the timeout is exceeded."""
151
small_delay = self.timer.delay * 0.8
152
for n in range(int(self.timer.timeout / small_delay) + 1):
154
self.clock.advance(small_delay)
155
self.clock.advance(self.timer.delay)
156
self.assertTrue(self.timer.called)
158
def test_cleanup_cancels_timeout_call(self):
159
"""Calling cleanup cancels the delay call."""
161
self.assertTrue(self.timer.timeout_call.cancelled)
164
class FakeNotification(AbstractNotification):
165
"""A fake notification class."""
167
def __init__(self, application_name="fake app"):
168
"""Initialize this instance."""
169
self.notifications_shown = []
170
self.notification_switch = None
171
self.application_name = application_name
172
self.notification = None
174
def send_notification(self, title, message, icon=None, append=False):
175
"""Show a notification to the user."""
176
if (self.notification_switch is not None and
177
not self.notification_switch.enabled):
179
self.notification = (title, message, icon, append)
180
self.notifications_shown.append((title, message, icon, append))
181
return len(self.notifications_shown) - 1
184
def FakeNotificationSingleton():
185
"""Builds a notification singleton, that logs all notifications shown."""
186
instance = FakeNotification()
188
def get_instance(notification_switch):
189
"""Returns the single instance."""
190
instance.notification_switch = notification_switch
196
class FakeStatusAggregator(object):
197
"""A fake status aggregator."""
199
def __init__(self, clock):
200
"""Initialize this instance."""
203
self.notification_switch = aggregator.NotificationSwitch()
205
def get_discovery_message(self):
206
"""Return the file discovery message."""
208
return self.build_discovery_message()
210
def build_discovery_message(self):
211
"""Build the file discovery message."""
212
return "a lot of files found (%d)." % self.discovered
214
def get_progress_message(self):
215
"""Return the progress message."""
217
return self.build_progress_message()
219
def build_progress_message(self):
220
"""Build the progress message."""
221
params = (self.discovered, self.completed)
222
return "a lot of files transferring (%d/%d)." % params
224
def get_final_status_message(self):
225
"""Return the final status message."""
226
return "a lot of files completed."
228
def get_notification(self):
229
"""Create a new toggleable notification object."""
230
return self.notification_switch.get_notification()
233
class ToggleableNotificationTestCase(TestCase):
234
"""Test the ToggleableNotification class."""
236
@defer.inlineCallbacks
238
"""Initialize this test instance."""
239
yield super(ToggleableNotificationTestCase, self).setUp()
240
self.patch(aggregator.notification, "Notification", FakeNotification)
241
self.notification_switch = aggregator.NotificationSwitch()
242
self.toggleable = self.notification_switch.get_notification()
244
def assertShown(self, notification):
245
"""Assert that the notification was shown."""
246
self.assertIn(notification,
247
self.toggleable.notification.notifications_shown)
249
def assertNotShown(self, notification):
250
"""Assert that the notification was shown."""
251
self.assertNotIn(notification,
252
self.toggleable.notification.notifications_shown)
254
def test_send_notification_passes_thru(self):
255
"""The send_notification method passes thru."""
257
self.toggleable.send_notification(*args)
258
self.assertShown(args)
260
def test_send_notification_honored_when_enabled(self):
261
"""The send_notification method is honored when enabled."""
262
self.notification_switch.enable_notifications()
263
args = (aggregator.NAME, "hello", None, False)
264
self.toggleable.send_notification(*args)
265
self.assertShown(args)
267
def test_send_notification_ignored_when_disabled(self):
268
"""The send_notification method is ignored when disabled."""
269
self.notification_switch.disable_notifications()
270
args = (aggregator.NAME, "hello", None, False)
271
self.toggleable.send_notification(*args)
272
self.assertNotShown(args)
275
class NotificationSwitchTestCase(TestCase):
276
"""Test the NotificationSwitch class."""
278
@defer.inlineCallbacks
280
"""Initialize this test instance."""
281
yield super(NotificationSwitchTestCase, self).setUp()
282
self.notification_switch = aggregator.NotificationSwitch()
284
def test_get_notification(self):
285
"""A new notification instance is returned."""
286
notification = self.notification_switch.get_notification()
287
self.assertEqual(notification.notification_switch,
288
self.notification_switch)
290
def test_enable_notifications(self):
291
"""The switch is turned on."""
292
self.notification_switch.enable_notifications()
293
self.assertTrue(self.notification_switch.enabled)
295
def test_disable_notifications(self):
296
"""The switch is turned off."""
297
self.notification_switch.disable_notifications()
298
self.assertFalse(self.notification_switch.enabled)
301
class FileDiscoveryBubbleTestCase(TestCase):
302
"""Test the FileDiscoveryBubble class."""
304
@defer.inlineCallbacks
306
"""Initialize this test instance."""
307
yield super(FileDiscoveryBubbleTestCase, self).setUp()
308
self.patch(aggregator, "ToggleableNotification",
309
FakeNotificationSingleton())
310
self.clock = PatchedClock()
311
self.aggregator = FakeStatusAggregator(clock=self.clock)
312
self.bubble = aggregator.FileDiscoveryBubble(self.aggregator,
314
self.addCleanup(self.bubble.cleanup)
315
fdis = aggregator.FileDiscoveryGatheringState
316
self.initial_delay = fdis.initial_delay
317
self.smaller_delay = self.initial_delay * 0.8
318
self.initial_timeout = fdis.initial_timeout
319
fdus = aggregator.FileDiscoveryUpdateState
320
self.updates_delay = fdus.updates_delay
321
self.updates_timeout = fdus.updates_timeout
322
fdss = aggregator.FileDiscoverySleepState
323
self.sleep_delay = fdss.sleep_delay
325
self.handler = MementoHandler()
326
self.handler.setLevel(logging.DEBUG)
327
aggregator.logger.addHandler(self.handler)
328
aggregator.logger.setLevel(logging.DEBUG)
329
self.addCleanup(aggregator.logger.removeHandler, self.handler)
331
def get_notifications_shown(self):
332
"""The list of notifications shown."""
333
return self.bubble.notification.notifications_shown
335
def test_popup_shows_notification_when_connected(self):
336
"""The popup callback shows notifications."""
337
self.bubble.connection_made()
338
self.bubble.new_file_found()
340
message = self.aggregator.build_discovery_message()
341
notification = (aggregator.NAME, message, None, False)
342
self.assertIn(notification, self.get_notifications_shown())
344
def test_popup_shows_notification_after_connected(self):
345
"""The popup callback shows notifications."""
346
self.bubble.new_file_found()
347
self.bubble.connection_made()
348
message = self.aggregator.build_discovery_message()
349
notification = (aggregator.NAME, message, None, False)
350
self.assertIn(notification, self.get_notifications_shown())
352
def test_popup_shows_no_notification_before_connection_made(self):
353
"""The popup callback shows notifications."""
354
self.bubble.new_file_found()
356
message = self.aggregator.build_discovery_message()
357
notification = (aggregator.NAME, message, None, False)
358
self.assertNotIn(notification, self.get_notifications_shown())
360
def test_popup_shows_no_notification_after_connection_lost(self):
361
"""The popup callback shows notifications."""
362
self.bubble.connection_made()
363
self.bubble.connection_lost()
364
self.bubble.new_file_found()
366
message = self.aggregator.build_discovery_message()
367
notification = (aggregator.NAME, message, None, False)
368
self.assertNotIn(notification, self.get_notifications_shown())
370
def test_notification_is_logged_in_debug(self):
371
"""The notification is printed in the debug log."""
372
self.bubble.connection_made()
373
self.bubble.new_file_found()
375
msg = "notification shown: %s" % self.get_notifications_shown()[0][1]
376
self.assertTrue(self.handler.check_debug(msg))
378
def test_bubble_is_not_shown_initially(self):
379
"""The bubble is not shown initially."""
380
self.bubble.new_file_found()
381
self.assertEqual(0, len(self.get_notifications_shown()))
383
def test_bubble_is_shown_after_delay(self):
384
"""The bubble is shown after a delay."""
385
self.bubble.connection_made()
386
self.bubble.new_file_found()
387
self.clock.advance(self.initial_delay)
388
self.assertEqual(1, len(self.get_notifications_shown()))
390
def test_bubble_not_shown_if_more_files_found(self):
391
"""The bubble is not shown if more files found within delay."""
392
self.clock.advance(self.smaller_delay)
393
self.bubble.new_file_found()
394
self.clock.advance(self.smaller_delay)
395
self.assertEqual(0, len(self.get_notifications_shown()))
397
def test_bubble_shown_if_timeout_exceeded(self):
398
"""The bubble is shown if the timeout is exceeded."""
399
self.bubble.connection_made()
400
self.bubble.new_file_found()
401
count = int(self.initial_timeout / self.smaller_delay) + 1
402
for n in range(count):
403
self.clock.advance(self.smaller_delay)
404
self.bubble.new_file_found()
405
self.assertEqual(1, len(self.get_notifications_shown()))
407
def test_idle_state(self):
408
"""The idle state is verified."""
410
type(self.bubble.state), aggregator.FileDiscoveryIdleState)
412
def test_gathering_state(self):
413
"""The gathering state is set after the first file is found."""
414
self.bubble.new_file_found()
416
type(self.bubble.state), aggregator.FileDiscoveryGatheringState)
418
def test_update_state(self):
419
"""When the gathering state finishes, the update state is started."""
420
self.bubble.connection_made()
421
self.bubble.new_file_found()
422
self.clock.advance(self.initial_delay)
424
type(self.bubble.state), aggregator.FileDiscoveryUpdateState)
426
def test_sleeping_state(self):
427
"""When the update state finishes, the sleeping state is started."""
428
self.bubble.connection_made()
429
self.bubble.new_file_found()
430
self.clock.advance(self.initial_delay)
431
self.clock.advance(self.updates_timeout)
433
type(self.bubble.state), aggregator.FileDiscoverySleepState)
435
def test_back_to_initial_state(self):
436
"""When the last state finishes, we return to the idle state."""
437
self.bubble.connection_made()
438
self.bubble.new_file_found()
439
self.clock.advance(self.initial_delay)
440
self.clock.advance(self.updates_timeout)
441
self.clock.advance(self.sleep_delay)
443
type(self.bubble.state), aggregator.FileDiscoveryIdleState)
445
def test_new_files_found_while_updating_not_shown_immediately(self):
446
"""New files found in the updating state are not shown immediately."""
447
self.bubble.connection_made()
448
self.bubble.new_file_found()
449
self.clock.advance(self.initial_delay)
450
self.bubble.new_file_found()
451
self.assertEqual(1, len(self.get_notifications_shown()))
453
def test_new_files_found_while_updating_are_shown_after_a_delay(self):
454
"""New files found in the updating state are shown after a delay."""
455
self.bubble.connection_made()
456
self.bubble.new_file_found()
457
self.clock.advance(self.initial_delay)
458
self.bubble.new_file_found()
459
self.clock.advance(self.updates_delay)
460
self.assertEqual(2, len(self.get_notifications_shown()))
462
def test_update_modifies_notification(self):
463
"""The update callback updates notifications."""
464
self.bubble.connection_made()
465
self.bubble.new_file_found()
467
self.bubble.new_file_found()
468
self.bubble._update()
469
message = self.aggregator.build_discovery_message()
470
notification = (aggregator.NAME, message, None, False)
471
self.assertIn(notification, self.get_notifications_shown())
473
def test_update_is_logged_in_debug(self):
474
"""The notification is logged when _update is called."""
475
self.bubble.connection_made()
476
self.bubble.new_file_found()
478
self.bubble.new_file_found()
479
self.bubble._update()
480
msg = "notification updated: %s" % self.get_notifications_shown()[1][1]
481
self.assertTrue(self.handler.check_debug(msg))
484
class FinalBubbleTestCase(TestCase):
485
"""Test for the final status notification bubble."""
487
@defer.inlineCallbacks
489
"""Initialize this test instance."""
490
yield super(FinalBubbleTestCase, self).setUp()
491
self.patch(aggregator, "ToggleableNotification",
492
FakeNotificationSingleton())
493
self.clock = PatchedClock()
494
self.aggregator = FakeStatusAggregator(clock=self.clock)
495
self.bubble = aggregator.FinalStatusBubble(self.aggregator)
496
self.addCleanup(self.bubble.cleanup)
498
def test_notification_not_shown_initially(self):
499
"""The notification is not shown initially."""
500
self.assertEqual(None, self.bubble.notification)
502
def test_show_pops_bubble(self):
503
"""The show method pops the bubble immediately."""
505
self.assertEqual(1, len(self.bubble.notification.notifications_shown))
508
class FakeLauncher(object):
509
"""A fake Launcher."""
511
progress_visible = False
514
def show_progressbar(self):
515
"""The progressbar is shown."""
516
self.progress_visible = True
518
def hide_progressbar(self):
519
"""The progressbar is hidden."""
520
self.progress_visible = False
522
def set_progress(self, value):
523
"""The progressbar value is changed."""
524
self.progress = value
527
class FakeInhibitor(object):
528
"""A fake session inhibitor."""
530
def inhibit(self, flags, reason):
531
"""Inhibit some events with a given reason."""
533
return defer.succeed(self)
536
"""Cancel the inhibition for the current cookie."""
538
return defer.succeed(self)
541
class ProgressBarTestCase(TestCase):
542
"""Tests for the progress bar."""
544
@defer.inlineCallbacks
546
"""Initialize this test instance."""
547
yield super(ProgressBarTestCase, self).setUp()
548
self.patch(aggregator, "Launcher", FakeLauncher)
549
self.clock = PatchedClock()
550
self.bar = aggregator.ProgressBar(clock=self.clock)
551
self.addCleanup(self.bar.cleanup)
552
self.timeout_calls = []
553
original_timeout = self.bar._timeout
555
def fake_timeout(result):
556
"""A fake _timeout method."""
557
self.timeout_calls.append(self.bar.progress)
558
original_timeout(result)
560
self.patch(self.bar, "_timeout", fake_timeout)
562
def test_launcher_typeerror_nonfatal(self):
563
"""Test that Launcher raising TypeError is not fatal."""
564
def raise_typeerror(*args, **kwargs):
567
self.patch(aggregator, "Launcher", raise_typeerror)
568
aggregator.ProgressBar(clock=self.clock)
570
def test_shown_when_progress_made(self):
571
"""The progress bar is shown when progress is made."""
572
self.bar.set_progress(0.5)
573
self.assertTrue(self.bar.visible)
574
self.assertTrue(self.bar.launcher.progress_visible)
576
def test_progress_made_updates_counter(self):
577
"""Progress made updates the counter."""
578
self.bar.set_progress(0.5)
579
self.assertEqual(self.bar.progress, 0.5)
581
def test_no_timer_set_initially(self):
582
"""There's no timer set initially."""
583
self.assertEqual(self.bar.timer, None)
585
def test_progress_made_sets_timer(self):
586
"""Progress made sets up a timer."""
587
self.bar.set_progress(0.5)
588
self.assertNotEqual(self.bar.timer, None)
590
def test_cleanup_resets_timer(self):
591
"""The cleanup method resets the timer."""
592
self.bar.set_progress(0.5)
594
self.assertEqual(self.bar.timer, None)
596
def test_progress_made_not_updated_initially(self):
597
"""Progress made is not updated initially."""
598
self.bar.set_progress(0.5)
599
self.assertEqual(0, len(self.timeout_calls))
600
self.assertEqual(0.0, self.bar.launcher.progress)
602
def test_progress_made_updated_after_a_delay(self):
603
"""The progressbar is updated after a delay."""
604
self.bar.set_progress(0.5)
605
self.clock.advance(aggregator.ProgressBar.updates_delay)
606
self.assertIn(0.5, self.timeout_calls)
607
self.assertEqual(0.5, self.bar.launcher.progress)
609
def test_progress_updates_are_aggregated(self):
610
"""The progressbar is updated after a delay."""
611
self.bar.set_progress(0.5)
612
self.clock.advance(aggregator.ProgressBar.updates_delay / 2)
613
self.bar.set_progress(0.6)
614
self.clock.advance(aggregator.ProgressBar.updates_delay / 2)
615
self.assertEqual(1, len(self.timeout_calls))
617
def test_progress_updates_are_continuous(self):
618
"""The progressbar updates are continuous."""
619
self.bar.set_progress(0.5)
620
self.clock.advance(aggregator.ProgressBar.updates_delay)
621
self.assertEqual(0.5, self.bar.launcher.progress)
622
self.bar.set_progress(0.6)
623
self.clock.advance(aggregator.ProgressBar.updates_delay)
624
self.assertEqual(0.6, self.bar.launcher.progress)
625
self.assertEqual(2, len(self.timeout_calls))
627
def test_hidden_when_completed(self):
628
"""The progressbar is hidden when everything completes."""
629
self.bar.set_progress(0.5)
631
self.assertFalse(self.bar.visible)
632
self.assertFalse(self.bar.launcher.progress_visible)
634
@skipTest('Inhibitor is disabled to prevent bug #737620')
635
@defer.inlineCallbacks
636
def test_progress_made_inhibits_logout_suspend(self):
637
"""Suspend and logout are inhibited when the progressbar is shown."""
638
self.bar.set_progress(0.5)
639
expected = aggregator.session.INHIBIT_LOGOUT_SUSPEND
640
inhibitor = yield self.bar.inhibitor_defer
641
self.assertEqual(inhibitor.flags, expected)
643
@skipTest('Inhibitor is disabled to prevent bug #737620')
644
@defer.inlineCallbacks
645
def test_completed_uninhibits_logout_suspend(self):
646
"""Suspend and logout are uninhibited when all has completed."""
647
self.bar.set_progress(0.5)
648
d = self.bar.inhibitor_defer
651
self.assertEqual(inhibitor.flags, 0)
654
class FakeDelayedBuffer(object):
655
"""Appends all status pushed into a list."""
659
def __init__(self, *args, **kwargs):
660
"""Initialize this instance."""
663
def push_event(self, event):
664
"""Push an event into this buffer."""
665
self.events.append(event)
667
def reset_threshold_timer(self):
668
"""The status has changed."""
669
self.timer_reset = True
671
def process_accumulated(self):
672
"""Process accumulated events."""
673
self.processed = True
676
class FakeCommand(object):
677
"""A fake command."""
679
def __init__(self, path=''):
683
self.deflated_size = 10000
687
class FakeUploadCommand(FakeCommand):
689
def __init__(self, path=''):
690
super(FakeUploadCommand, self).__init__(path)
691
self.n_bytes_written = 0
694
class FakeDownloadCommand(FakeCommand):
696
def __init__(self, path=''):
697
super(FakeDownloadCommand, self).__init__(path)
698
self.n_bytes_read = 0
701
class FakeVolumeManager(object):
705
"""Initialize this instance."""
709
def get_volume(self, volume_id):
710
"""Return a volume given its id."""
711
return self.volumes[volume_id]
714
class FakeAggregator(object):
715
"""A fake aggregator object."""
717
def __init__(self, clock):
718
"""Initialize this fake instance."""
719
self.queued_commands = set()
720
self.notification_switch = aggregator.NotificationSwitch()
721
self.connected = False
722
self.clock = PatchedClock()
723
self.files_uploading = []
724
self.files_downloading = []
725
self.progress_events = []
726
self.recent_transfers = aggregator.deque(maxlen=10)
728
def queue_done(self):
729
"""The queue completed all operations."""
730
self.queued_commands.clear()
732
def get_notification(self):
733
"""Create a new toggleable notification object."""
734
return self.notification_switch.get_notification()
736
def download_started(self, command):
737
"""A download just started."""
738
self.files_downloading.append(command)
739
self.queued_commands.add(command)
741
def download_finished(self, command):
742
"""A download just finished."""
743
if command in self.files_downloading:
744
self.files_downloading.remove(command)
745
self.recent_transfers.append(command.path)
746
self.queued_commands.discard(command)
748
def upload_started(self, command):
749
"""An upload just started."""
750
self.files_uploading.append(command)
751
self.queued_commands.add(command)
753
def upload_finished(self, command):
754
"""An upload just finished."""
755
if command in self.files_uploading:
756
self.files_uploading.remove(command)
757
self.recent_transfers.append(command.path)
758
self.queued_commands.discard(command)
760
def progress_made(self, share_id, node_id, n_bytes, deflated_size):
761
"""Progress made on up- or download."""
762
self.progress_events.append(
763
(share_id, node_id, n_bytes, deflated_size))
765
def connection_made(self):
766
"""The client made the connection to the server."""
767
self.connected = True
769
def connection_lost(self):
770
"""The client lost the connection to the server."""
771
self.connected = False
774
class StatusFrontendTestCase(BaseTwistedTestCase):
775
"""Test the status frontend."""
777
@defer.inlineCallbacks
779
"""Initialize this test instance."""
780
yield super(StatusFrontendTestCase, self).setUp()
781
self.patch(aggregator, "StatusAggregator", FakeAggregator)
782
self.patch(aggregator, "ToggleableNotification",
783
FakeNotificationSingleton())
785
self.fakevm = FakeVolumeManager()
786
self.status_frontend = aggregator.StatusFrontend()
787
self.listener = status_listener.StatusListener(self.fakefsm,
789
self.status_frontend)
791
def test_recent_transfers(self):
792
"""Check that it generates a tuple with the recent transfers."""
793
self.patch(status_listener.action_queue, "Upload", FakeUploadCommand)
794
self.patch(status_listener.action_queue, "Download",
797
fake_command = FakeUploadCommand('path1')
798
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
799
self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
800
fake_command = FakeUploadCommand('path2')
801
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
802
self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
803
fake_command = FakeDownloadCommand('path3')
804
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
805
self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
806
transfers = self.status_frontend.recent_transfers()
807
expected = ['path1', 'path2', 'path3']
808
self.assertEqual(transfers, expected)
810
menu_data = self.listener.menu_data()
815
RECENT_TRANSFERS: expected})
817
def test_files_uploading(self):
818
"""Check that it returns a list with the path, size, and progress."""
819
fc = FakeUploadCommand(path='testfile.txt')
820
fc.deflated_size = 200
821
self.status_frontend.upload_started(fc)
822
uploading = self.status_frontend.files_uploading()
823
expected = [('testfile.txt', 200, 0)]
824
self.assertEqual(uploading, expected)
825
menu_data = self.listener.menu_data()
828
{UPLOADING: expected,
830
RECENT_TRANSFERS: []})
832
fc.deflated_size = 1000
833
fc.n_bytes_written = 200
834
fc2 = FakeUploadCommand(path='testfile2.txt')
835
fc2.deflated_size = 2000
836
fc2.n_bytes_written = 450
837
self.status_frontend.upload_started(fc2)
838
uploading = self.status_frontend.files_uploading()
839
expected = [('testfile.txt', 1000, 200), ('testfile2.txt', 2000, 450)]
840
self.assertEqual(uploading, expected)
842
menu_data = self.listener.menu_data()
845
{UPLOADING: expected,
847
RECENT_TRANSFERS: []})
849
def test_files_downloading(self):
850
"""Check that it returns a list with the path, size, and progress."""
851
fc = FakeDownloadCommand(path='testfile.txt')
852
fc.deflated_size = 200
853
self.status_frontend.download_started(fc)
854
downloading = self.status_frontend.files_downloading()
855
expected = [('testfile.txt', 200, 0)]
856
self.assertEqual(downloading, expected)
857
menu_data = self.listener.menu_data()
860
{DOWNLOADING: expected,
862
RECENT_TRANSFERS: []})
864
fc.deflated_size = 1000
865
fc.n_bytes_read = 200
866
fc2 = FakeDownloadCommand(path='testfile2.txt')
867
fc2.deflated_size = 2000
868
fc2.n_bytes_read = 450
869
self.status_frontend.download_started(fc2)
870
downloading = self.status_frontend.files_downloading()
871
expected = [('testfile.txt', 1000, 200), ('testfile2.txt', 2000, 450)]
872
self.assertEqual(downloading, expected)
874
menu_data = self.listener.menu_data()
877
{DOWNLOADING: expected,
879
RECENT_TRANSFERS: []})
881
def test_files_uploading_empty(self):
882
"""Check that empty files are ignored."""
883
fc = FakeUploadCommand(path='testfile.txt')
884
fc.deflated_size = None
885
self.status_frontend.upload_started(fc)
887
fc2 = FakeUploadCommand(path='testfile2.txt')
888
fc2.deflated_size = 0
889
fc2.n_bytes_written = 450
890
self.status_frontend.upload_started(fc2)
891
uploading = self.status_frontend.files_uploading()
892
self.assertEqual(uploading, [])
894
def test_files_downloading_empty(self):
895
"""Check that empty files are ignored."""
896
fc = FakeDownloadCommand(path='testfile.txt')
897
fc.deflated_size = None
898
self.status_frontend.download_started(fc)
900
fc2 = FakeDownloadCommand(path='testfile2.txt')
901
fc2.deflated_size = 0
902
fc2.n_bytes_written = 450
903
self.status_frontend.download_started(fc2)
904
downloading = self.status_frontend.files_downloading()
905
self.assertEqual(downloading, [])
907
def test_menu_data_full_response(self):
908
"""listener.menu_data returns uploading, downloading, and recent."""
909
self.patch(status_listener.action_queue, "Upload", FakeUploadCommand)
910
fake_command = FakeUploadCommand('path1')
911
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
912
self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
914
self.patch(status_listener.action_queue, "Download",
916
fake_command = FakeDownloadCommand('path2')
917
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
918
self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
920
fc = FakeUploadCommand(path='testfile.txt')
921
fc.deflated_size = 1000
922
fc.n_bytes_written = 200
923
self.status_frontend.upload_started(fc)
925
fc = FakeDownloadCommand(path='download.pdf')
926
fc.deflated_size = 10
928
self.status_frontend.download_started(fc)
930
uploading = self.status_frontend.files_uploading()
931
downloading = self.status_frontend.files_downloading()
932
transfers = self.status_frontend.recent_transfers()
933
expected = {UPLOADING: [('testfile.txt', 1000, 200)],
934
DOWNLOADING: [('download.pdf', 10, 1)],
935
RECENT_TRANSFERS: ['path1', 'path2']}
937
self.assertEqual({UPLOADING: uploading,
938
DOWNLOADING: downloading,
939
RECENT_TRANSFERS: transfers},
942
def test_file_published(self):
943
"""A file published event is processed."""
944
share_id = "fake share id"
945
node_id = "fake node id"
947
public_url = "http://fake_public/url"
948
self.listener.handle_AQ_CHANGE_PUBLIC_ACCESS_OK(share_id, node_id,
949
is_public, public_url)
951
2, len(self.status_frontend.notification.notifications_shown))
953
def test_file_unpublished(self):
954
"""A file unpublished event is processed."""
955
share_id = "fake share id"
956
node_id = "fake node id"
958
public_url = None # SD sends None when unpublishing
960
self.listener.handle_AQ_CHANGE_PUBLIC_ACCESS_OK(share_id, node_id,
961
is_public, public_url)
963
2, len(self.status_frontend.notification.notifications_shown))
965
def test_download_started(self):
966
"""A download was added to the queue."""
967
self.patch(status_listener.action_queue, "Download", FakeCommand)
968
fake_command = FakeCommand()
969
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
970
qc = self.status_frontend.aggregator.queued_commands
971
self.assertIn(fake_command, qc)
973
def test_download_started_with_no_deflated_size(self):
974
"""A download of unknown size was added to the queue."""
975
self.patch(status_listener.action_queue, "Download", FakeCommand)
976
fake_command = FakeCommand()
977
fake_command.deflated_size = None
978
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
979
qc = self.status_frontend.aggregator.queued_commands
980
self.assertIn(fake_command, qc)
982
def test_download_finished(self):
983
"""A download was removed from the queue."""
984
self.patch(status_listener.action_queue, "Download", FakeCommand)
985
fake_command = FakeCommand()
986
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
987
self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
988
qc = self.status_frontend.aggregator.queued_commands
989
self.assertNotIn(fake_command, qc)
991
def test_upload_started(self):
992
"""An upload was added to the queue."""
993
self.patch(status_listener.action_queue, "Upload", FakeCommand)
994
fake_command = FakeCommand()
995
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
996
qc = self.status_frontend.aggregator.queued_commands
997
self.assertIn(fake_command, qc)
999
def test_upload_started_with_no_deflated_size(self):
1000
"""An upload of unknown size was added to the queue."""
1001
self.patch(status_listener.action_queue, "Upload", FakeCommand)
1002
fake_command = FakeCommand()
1003
fake_command.deflated_size = None
1004
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
1005
qc = self.status_frontend.aggregator.queued_commands
1006
self.assertIn(fake_command, qc)
1008
def test_upload_finished(self):
1009
"""An upload was removed from the queue."""
1010
self.patch(status_listener.action_queue, "Upload", FakeCommand)
1011
fake_command = FakeCommand()
1012
self.listener.handle_SYS_QUEUE_ADDED(fake_command)
1013
self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
1014
qc = self.status_frontend.aggregator.queued_commands
1015
self.assertNotIn(fake_command, qc)
1017
def test_progress_made_on_upload(self):
1018
"""Progress was made on an uploading file."""
1019
share_id = 'fake_share'
1020
node_id = 'fake_node'
1021
n_bytes_written = 100
1022
deflated_size = 10000
1023
self.listener.handle_AQ_UPLOAD_FILE_PROGRESS(
1024
share_id=share_id, node_id=node_id,
1025
n_bytes_written=n_bytes_written, deflated_size=deflated_size)
1026
pe = self.status_frontend.aggregator.progress_events
1028
[(share_id, node_id, n_bytes_written, deflated_size)], pe,
1029
"progress_made was not called (exactly once) on aggregator.")
1031
def test_progress_made_on_download(self):
1032
"""Progress was made on an downloading file."""
1033
share_id = 'fake_share'
1034
node_id = 'fake_node'
1036
deflated_size = 20000
1037
self.listener.handle_AQ_DOWNLOAD_FILE_PROGRESS(
1038
share_id=share_id, node_id=node_id,
1039
n_bytes_read=n_bytes_read, deflated_size=deflated_size)
1040
pe = self.status_frontend.aggregator.progress_events
1042
[(share_id, node_id, n_bytes_read, deflated_size)], pe,
1043
"progress_made was not called (exactly once) on aggregator.")
1045
def test_queue_done(self):
1046
"""The queue is empty."""
1047
fake_command = FakeCommand()
1048
qc = self.status_frontend.aggregator.queued_commands
1049
qc.add(fake_command)
1050
self.listener.handle_SYS_QUEUE_DONE()
1051
self.assertEqual(0, len(qc))
1053
def test_new_share_available(self):
1054
"""A new share is available for subscription."""
1055
SHARE_ID = "fake share id"
1057
share = Share(volume_id=SHARE_ID, other_visible_name=FAKE_SENDER)
1058
self.fakevm.volumes[SHARE_ID] = share
1059
self.listener.handle_VM_SHARE_CREATED(SHARE_ID)
1061
2, len(self.status_frontend.notification.notifications_shown))
1063
def test_already_subscribed_new_udf_available(self):
1064
"""A new udf that was already subscribed."""
1066
udf.subscribed = True
1067
self.listener.handle_VM_UDF_CREATED(udf)
1069
1, len(self.status_frontend.notification.notifications_shown))
1071
def test_new_udf_available(self):
1072
"""A new udf is available for subscription."""
1074
self.listener.handle_VM_UDF_CREATED(udf)
1076
2, len(self.status_frontend.notification.notifications_shown))
1078
def test_two_new_udfs_available(self):
1079
"""A new udf is available for subscription."""
1081
self.listener.handle_VM_UDF_CREATED(udf1)
1083
self.listener.handle_VM_UDF_CREATED(udf2)
1085
3, len(self.status_frontend.notification.notifications_shown))
1087
def test_server_connection_lost(self):
1088
"""The client connected to the server."""
1089
self.status_frontend.aggregator.connected = True
1090
self.listener.handle_SYS_CONNECTION_LOST()
1092
1, len(self.status_frontend.notification.notifications_shown))
1093
self.assertFalse(self.status_frontend.aggregator.connected)
1095
def test_server_connection_made(self):
1096
"""The client connected to the server."""
1097
self.status_frontend.aggregator.connected = False
1098
self.listener.handle_SYS_CONNECTION_MADE()
1100
1, len(self.status_frontend.notification.notifications_shown))
1101
self.assertTrue(self.status_frontend.aggregator.connected)
1103
def test_set_show_all_notifications(self):
1104
"""Test the set_show_all_notifications method."""
1105
self.status_frontend.set_show_all_notifications(False)
1106
self.assertFalse(self.status_frontend.aggregator.
1107
notification_switch.enabled)
1109
def test_udf_quota_exceeded(self):
1110
"""Quota exceeded in udf."""
1112
launcher = mocker.replace(
1113
"ubuntuone.platform.launcher.Launcher")
1115
mock_launcher = mocker.mock()
1116
mocker.result(mock_launcher)
1117
mock_launcher.set_urgent()
1119
UDF_ID = 'fake udf id'
1120
udf = UDF(volume_id=UDF_ID)
1121
self.fakevm.volumes[UDF_ID] = udf
1122
self.listener.handle_SYS_QUOTA_EXCEEDED(
1123
volume_id=UDF_ID, free_bytes=0)
1125
1, len(self.status_frontend.notification.notifications_shown))
1129
def test_root_quota_exceeded(self):
1130
"""Quota exceeded in root."""
1132
launcher = mocker.replace(
1133
"ubuntuone.platform.launcher.Launcher")
1135
mock_launcher = mocker.mock()
1136
mocker.result(mock_launcher)
1137
mock_launcher.set_urgent()
1139
ROOT_ID = 'fake root id'
1140
root = Root(volume_id=ROOT_ID)
1141
self.fakevm.volumes[ROOT_ID] = root
1142
self.fakevm.root = root
1143
self.listener.handle_SYS_QUOTA_EXCEEDED(
1144
volume_id=ROOT_ID, free_bytes=0)
1146
1, len(self.status_frontend.notification.notifications_shown))
1150
def test_share_quota_exceeded(self):
1151
"""Quota exceeded in share."""
1153
launcher = mocker.replace(
1154
"ubuntuone.platform.launcher.Launcher")
1156
mock_launcher = mocker.mock()
1157
mocker.result(mock_launcher)
1158
mock_launcher.set_urgent()
1160
mock_launcher = mocker.mock()
1161
mocker.result(mock_launcher)
1162
mock_launcher.set_urgent()
1164
SHARE_ID = 'fake share id'
1166
share = Share(volume_id=SHARE_ID)
1167
self.fakevm.volumes[SHARE_ID] = share
1168
self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1170
2, len(self.status_frontend.notification.notifications_shown))
1171
self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1172
self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1174
2, len(self.status_frontend.notification.notifications_shown))
1175
self.status_frontend.aggregator.clock.advance(aggregator.ONE_DAY + 1)
1176
self.listener.handle_SYS_QUOTA_EXCEEDED(SHARE_ID, BYTES)
1178
3, len(self.status_frontend.notification.notifications_shown))
1183
class StatusEventTestCase(TestCase):
1184
"""Test the status event class and children."""
1186
CLASS = aggregator.StatusEvent
1190
@defer.inlineCallbacks
1192
"""Initialize this test instance."""
1193
yield super(StatusEventTestCase, self).setUp()
1194
if type(self) == StatusEventTestCase:
1195
self.assertRaises(AssertionError, self.CLASS, **self.CLASS_KWARGS)
1197
self.status = self.CLASS(**self.CLASS_KWARGS)
1199
def test_one_message_defined(self):
1200
"""The singular message is defined as MESSAGE_ONE."""
1202
self.assertNotEqual(None, self.CLASS.MESSAGE_ONE)
1204
def test_one_message_built_correctly(self):
1205
"""The message returned by one() is returned ok."""
1207
self.assertEqual(self.status.one(), self.CLASS.MESSAGE_ONE)
1210
class FilePublishingStatusTestCase(StatusEventTestCase):
1211
"""Test the file publishing status class."""
1213
CLASS = aggregator.FilePublishingStatus
1214
CLASS_KWARGS = {"new_public_url": "http://fake_public/url"}
1216
def test_one_message_built_correctly(self):
1217
"""The message returned by one() should include the url."""
1218
expected = self.CLASS.MESSAGE_ONE % self.status.kwargs
1219
self.assertEqual(self.status.one(), expected)
1222
class FileUnpublishingStatusTestCase(StatusEventTestCase):
1223
"""Test the file unpublishing status class."""
1225
CLASS = aggregator.FileUnpublishingStatus
1226
CLASS_KWARGS = {"old_public_url": None}
1229
class ShareAvailableEventTestCase(StatusEventTestCase):
1230
"""Test the folder available status class with a Share."""
1232
FOLDER_NAME = "folder name"
1233
OTHER_USER_NAME = "person name"
1234
SAMPLE_SHARE = Share(accepted=False, name=FOLDER_NAME,
1235
other_visible_name=OTHER_USER_NAME)
1236
CLASS = aggregator.ShareAvailableStatus
1237
CLASS_KWARGS = {"share": SAMPLE_SHARE}
1239
def test_one_message_built_correctly(self):
1240
"""one() must return the folder name and user name."""
1242
"folder_name": self.FOLDER_NAME,
1243
"other_user_name": self.OTHER_USER_NAME,
1245
expected = self.CLASS.MESSAGE_ONE % format_args
1246
self.assertEqual(self.status.one(), expected)
1249
class UDFAvailableEventTestCase(StatusEventTestCase):
1250
"""Test the folder available status class with a UDF."""
1252
FOLDER_NAME = "folder name"
1253
SAMPLE_UDF = UDF(subscribed=False, suggested_path=FOLDER_NAME)
1254
CLASS = aggregator.UDFAvailableStatus
1255
CLASS_KWARGS = {'udf': SAMPLE_UDF}
1257
def test_one_message_built_correctly(self):
1258
"""one() must return the folder name."""
1259
format_args = {"folder_name": self.FOLDER_NAME}
1260
expected = self.CLASS.MESSAGE_ONE % format_args
1261
self.assertEqual(self.status.one(), expected)
1264
class ConnectionLostEventTestCase(StatusEventTestCase):
1265
"""Test the event when the connection is lost."""
1267
CLASS = aggregator.ConnectionLostStatus
1269
def test_many_message_built_correctly(self):
1270
"""The message returned by many() is returned ok."""
1273
test_events = [FakeStatus(88)] * count + [self.CLASS()]
1274
expected = self.CLASS.MESSAGE_ONE
1275
self.assertEqual(self.status.many(test_events), expected)
1278
class ConnectionMadeEventTestCase(ConnectionLostEventTestCase):
1279
"""Test the event when the connection is made."""
1281
CLASS = aggregator.ConnectionMadeStatus
1284
class FakeStatus(aggregator.StatusEvent):
1285
"""A fake status to test weight comparisons."""
1287
def __init__(self, weight):
1288
"""Initialize with the fake weight."""
1289
super(FakeStatus, self).__init__()
1290
self.WEIGHT = weight
1293
class FakeFileDiscoveryBubble(object):
1294
"""A fake FileDiscoveryBubble."""
1298
def __init__(self, status_aggregator, clock=None):
1299
"""Initialize this instance."""
1300
self.status_aggregator = status_aggregator
1302
def new_file_found(self):
1303
"""New files were found."""
1307
"""Cleanup this instance."""
1309
def connection_made(self):
1310
"""Connection made."""
1312
def connection_lost(self):
1313
"""Connection lost."""
1316
class FakeFinalBubble(object):
1317
"""A fake FinalStatusBubble."""
1321
def __init__(self, status_aggregator):
1322
"""Initialize this fake instance."""
1323
self.status_aggregator = status_aggregator
1326
"""Cleanup this instance."""
1329
"""Show this bubble."""
1333
class StatusAggregatorTestCase(TestCase):
1334
"""Test the backend of the status aggregator."""
1336
@defer.inlineCallbacks
1338
"""Initialize this test instance."""
1339
yield super(StatusAggregatorTestCase, self).setUp()
1340
self.patch(aggregator, "FileDiscoveryBubble",
1341
FakeFileDiscoveryBubble)
1342
self.patch(aggregator, "FinalStatusBubble",
1344
self.patch(aggregator, "ToggleableNotification",
1345
FakeNotificationSingleton())
1346
self.patch(aggregator, "Launcher", FakeLauncher)
1347
clock = PatchedClock()
1348
self.status_frontend = aggregator.StatusFrontend(clock=clock)
1349
self.aggregator = self.status_frontend.aggregator
1350
self.fake_bubble = self.aggregator.file_discovery_bubble
1352
self.handler = MementoHandler()
1353
self.handler.setLevel(logging.DEBUG)
1354
aggregator.logger.addHandler(self.handler)
1355
aggregator.logger.setLevel(logging.DEBUG)
1356
self.addCleanup(aggregator.logger.removeHandler, self.handler)
1357
self.addCleanup(self.aggregator.progress_bar.cleanup)
1359
def assertStatusReset(self):
1360
"""Assert that the status is at zero."""
1361
self.assertEqual(0, self.aggregator.download_done)
1362
self.assertEqual(0, self.aggregator.upload_done)
1363
self.assertEqual(0, len(self.aggregator.files_uploading))
1364
self.assertEqual(0, len(self.aggregator.files_downloading))
1365
self.assertEqual({}, self.aggregator.progress)
1366
self.assertEqual({}, self.aggregator.to_do)
1367
self.assertIdentical(None, self.aggregator.queue_done_timer)
1369
def test_register_progress_listener(self):
1370
"""Check that register listener handles properly additions."""
1372
def fake_callback():
1375
self.aggregator.register_progress_listener(fake_callback)
1376
self.assertEqual(len(self.aggregator.progress_listeners), 1)
1378
def test_register_progress_listener_fail(self):
1379
"""Check that register listener fails with not Callable objects."""
1381
TypeError, self.aggregator.register_progress_listener, [])
1382
self.assertEqual(len(self.aggregator.progress_listeners), 0)
1384
def test_register_connection_listener(self):
1385
"""Check that register listener handles properly additions."""
1387
def fake_callback():
1390
self.aggregator.register_connection_listener(fake_callback)
1391
self.assertEqual(len(self.aggregator.connection_listeners), 1)
1393
def test_register_connection_listener_fail(self):
1394
"""Check that register listener fails with not Callable objects."""
1396
TypeError, self.aggregator.register_connection_listener, [])
1397
self.assertEqual(len(self.aggregator.connection_listeners), 0)
1399
def test_connection_notifications(self):
1400
"""Check that the connection lister is notified."""
1403
def fake_callback(status):
1404
"""Register status."""
1405
data['status'] = status
1407
self.aggregator.register_connection_listener(fake_callback)
1408
self.assertEqual(data, {})
1409
self.aggregator.connection_lost()
1410
self.assertFalse(data['status'])
1411
self.aggregator.connection_made()
1412
self.assertTrue(data['status'])
1414
def assertMiscCommandQueued(self, fc):
1415
"""Assert that some command was queued."""
1416
self.assertEqual(len(self.aggregator.to_do), 1)
1417
message = "queueing command (total: 1): %s" % fc.__class__.__name__
1418
self.assertEqual(fc.deflated_size, sum(self.aggregator.to_do.values()))
1419
self.assertTrue(self.handler.check_debug(message))
1420
self.assertTrue(self.aggregator.progress_bar.visible)
1422
def assertMiscCommandUnqueued(self, fc):
1423
"""Assert that some command was unqueued."""
1425
1, self.aggregator.download_done + self.aggregator.upload_done)
1426
message = "unqueueing command: %s" % fc.__class__.__name__
1427
self.assertTrue(self.handler.check_debug(message))
1429
def test_counters_start_at_zero(self):
1430
"""Test that the counters start at zero."""
1431
self.assertStatusReset()
1433
def test_file_download_started(self):
1434
"""Test that a file has started download."""
1435
fc = FakeCommand(path='testfile.txt')
1436
self.assertEqual('', self.aggregator.downloading_filename)
1437
self.status_frontend.download_started(fc)
1438
self.assertEqual(1, len(self.aggregator.files_downloading))
1439
self.assertEqual('testfile.txt', self.aggregator.downloading_filename)
1440
self.assertMiscCommandQueued(fc)
1441
self.assertEqual(1, self.fake_bubble.count)
1443
{(fc.share_id, fc.node_id): (fc.deflated_size)},
1444
self.aggregator.to_do)
1446
def test_file_download_finished(self):
1447
"""Test that a file has finished downloading."""
1449
self.status_frontend.download_started(fc)
1450
self.status_frontend.download_finished(fc)
1451
self.assertEqual(self.aggregator.download_done, 1)
1452
self.assertMiscCommandUnqueued(fc)
1454
{(fc.share_id, fc.node_id): (fc.deflated_size)},
1455
self.aggregator.progress)
1456
self.assertEqual(len(self.aggregator.recent_transfers), 1)
1458
def test_file_upload_started(self):
1459
"""Test that a file has started upload."""
1460
fc = FakeCommand(path='testfile.txt')
1461
self.assertEqual('', self.aggregator.uploading_filename)
1462
self.status_frontend.upload_started(fc)
1463
self.assertEqual(1, len(self.aggregator.files_uploading))
1464
self.assertEqual('testfile.txt', self.aggregator.uploading_filename)
1465
self.assertMiscCommandQueued(fc)
1466
self.assertEqual(1, self.fake_bubble.count)
1468
{(fc.share_id, fc.node_id): (fc.deflated_size)},
1469
self.aggregator.to_do)
1471
def test_file_upload_finished(self):
1472
"""Test that a file has finished uploading."""
1474
self.status_frontend.upload_started(fc)
1475
self.status_frontend.upload_finished(fc)
1476
self.assertEqual(self.aggregator.upload_done, 1)
1477
self.assertMiscCommandUnqueued(fc)
1479
{(fc.share_id, fc.node_id): (fc.deflated_size)},
1480
self.aggregator.progress)
1481
self.assertEqual(len(self.aggregator.recent_transfers), 1)
1483
def test_max_recent_files(self):
1484
"""Check that the queue doesn't exceed the 5 items."""
1486
fc = FakeUploadCommand(str(i))
1487
self.status_frontend.upload_started(fc)
1488
self.status_frontend.upload_finished(fc)
1490
fc = FakeDownloadCommand(str(i))
1491
self.status_frontend.download_started(fc)
1492
self.status_frontend.download_finished(fc)
1493
self.assertEqual(len(self.aggregator.recent_transfers), 5)
1495
def test_recent_transfers_is_unique(self):
1496
"""Check that a given path is not repeated in recent transfers."""
1497
fc = FakeDownloadCommand('hi')
1498
self.status_frontend.download_started(fc)
1499
self.status_frontend.download_finished(fc)
1500
fc = FakeDownloadCommand('hi')
1501
self.status_frontend.download_started(fc)
1502
self.status_frontend.download_finished(fc)
1503
self.assertEqual(len(self.aggregator.recent_transfers), 1)
1505
def test_recent_transfers_reorders(self):
1506
"""Check that if a transfer is repeated we put it back at the end."""
1507
fc = FakeDownloadCommand('hi')
1508
self.status_frontend.download_started(fc)
1509
self.status_frontend.download_finished(fc)
1510
fc = FakeDownloadCommand('howdy')
1511
self.status_frontend.download_started(fc)
1512
self.status_frontend.download_finished(fc)
1513
fc = FakeUploadCommand('hi')
1514
self.status_frontend.upload_started(fc)
1515
self.status_frontend.upload_finished(fc)
1517
self.assertEqual(len(self.aggregator.recent_transfers), 2)
1518
self.assertEqual(['howdy', 'hi'],
1519
list(self.aggregator.recent_transfers))
1521
def test_progress_made(self):
1522
"""Progress on up and downloads is tracked."""
1523
share_id = 'fake_share'
1524
node_id = 'fake_node'
1526
deflated_size = 100000
1527
self.aggregator.progress_made(
1528
share_id, node_id, n_bytes, deflated_size)
1530
{(share_id, node_id): (n_bytes)},
1531
self.aggregator.progress)
1533
def test_get_discovery_message(self):
1534
"""Test the message that's shown on the discovery bubble."""
1537
filename = 'upfile0.ext'
1538
filename2 = 'downfile0.ext'
1539
self.aggregator.files_uploading.extend([
1540
FakeCommand(path='upfile%d.ext' % n) for n in range(uploading)])
1541
self.aggregator.uploading_filename = filename
1542
self.aggregator.files_downloading.extend([
1543
FakeCommand(path='downfile%d.ext' % n) for n in
1544
range(downloading)])
1545
self.aggregator.downloading_filename = filename2
1547
aggregator.files_being_uploaded(filename, uploading) + "\n" +
1548
aggregator.files_being_downloaded(filename2, downloading))
1549
result = self.aggregator.get_discovery_message()
1550
self.assertEqual(expected, result)
1552
def test_get_discovery_message_clears_filenames(self):
1553
"""Test the message that's shown on the discovery bubble."""
1556
filename = 'upfile0.ext'
1557
filename2 = 'downfile0.ext'
1558
self.aggregator.files_uploading.extend([
1559
FakeCommand(path='upfile%d.ext' % n) for n in range(uploading)])
1560
self.aggregator.uploading_filename = filename
1561
self.aggregator.files_downloading.extend([
1562
FakeCommand(path='downfile%d.ext' % n) for n in
1563
range(downloading)])
1564
self.aggregator.downloading_filename = 'STALE FILENAME'
1565
self.aggregator.uploading_filename = 'STALE FILENAME'
1567
aggregator.files_being_uploaded(filename, uploading) + "\n" +
1568
aggregator.files_being_downloaded(filename2, downloading))
1569
result = self.aggregator.get_discovery_message()
1570
self.assertEqual(expected, result)
1572
def test_get_final_status_message(self):
1573
"""The final status message."""
1575
self.aggregator.uploading_filename = FILENAME
1576
self.aggregator.downloading_filename = FILENAME2
1577
self.aggregator.upload_done, self.aggregator.download_done = done
1580
aggregator.FINAL_COMPLETED + "\n" +
1581
aggregator.files_were_uploaded(
1582
FILENAME, self.aggregator.upload_done) + "\n" +
1583
aggregator.files_were_downloaded(
1584
FILENAME2, self.aggregator.download_done))
1586
result = self.aggregator.get_final_status_message()
1587
self.assertEqual(expected, result)
1589
def test_get_final_status_message_no_uploads(self):
1590
"""The final status message when there were no uploads."""
1592
self.aggregator.upload_done, self.aggregator.download_done = done
1593
self.aggregator.downloading_filename = FILENAME2
1596
aggregator.FINAL_COMPLETED + "\n" +
1597
aggregator.files_were_downloaded(
1598
FILENAME2, self.aggregator.download_done))
1600
result = self.aggregator.get_final_status_message()
1601
self.assertEqual(expected, result)
1603
def test_get_final_status_message_no_downloads(self):
1604
"""The final status message when there were no downloads."""
1606
self.aggregator.upload_done, self.aggregator.download_done = done
1607
self.aggregator.uploading_filename = FILENAME
1610
aggregator.FINAL_COMPLETED + "\n" +
1611
aggregator.files_were_uploaded(
1612
FILENAME, self.aggregator.upload_done))
1614
result = self.aggregator.get_final_status_message()
1615
self.assertEqual(expected, result)
1617
def test_queue_done_shows_bubble_when_downloads_happened(self):
1618
"""On queue done, show final bubble if downloads happened."""
1620
self.status_frontend.download_started(fc)
1621
self.status_frontend.download_finished(fc)
1622
old_final_bubble = self.aggregator.final_status_bubble
1623
self.aggregator.queue_done()
1624
self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1625
self.assertTrue(old_final_bubble.shown)
1627
def test_queue_done_shows_bubble_when_uploads_happened(self):
1628
"""On queue done, show final bubble if uploads happened."""
1630
self.status_frontend.upload_started(fc)
1631
self.status_frontend.upload_finished(fc)
1632
old_final_bubble = self.aggregator.final_status_bubble
1633
self.aggregator.queue_done()
1634
self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1635
self.assertTrue(old_final_bubble.shown)
1637
def test_queue_done_shows_bubble_only_after_delay(self):
1638
"""On queue_done, show final bubble only after a delay."""
1640
self.status_frontend.upload_started(fc)
1641
self.status_frontend.upload_finished(fc)
1642
old_final_bubble = self.aggregator.final_status_bubble
1643
self.aggregator.queue_done()
1644
self.assertFalse(old_final_bubble.shown)
1645
self.aggregator.clock.advance(self.aggregator.finished_delay - 1)
1646
self.assertFalse(old_final_bubble.shown)
1647
self.aggregator.queue_done()
1648
self.assertFalse(old_final_bubble.shown)
1649
self.aggregator.clock.advance(2)
1650
self.assertFalse(old_final_bubble.shown)
1651
self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1652
self.assertTrue(old_final_bubble.shown)
1654
def test_queue_done_does_not_show_bubble_when_no_transfers_happened(self):
1655
"""On queue done, don't show final bubble if no transfers happened."""
1657
self.status_frontend.upload_started(fc)
1658
old_final_bubble = self.aggregator.final_status_bubble
1659
self.aggregator.queue_done()
1660
self.assertFalse(old_final_bubble.shown)
1662
def test_queue_done_resets_status_and_hides_progressbar(self):
1663
"""On queue done, reset counters and hide progressbar."""
1665
self.status_frontend.upload_started(fc)
1666
self.aggregator.queue_done()
1667
self.aggregator.clock.advance(self.aggregator.finished_delay + 1)
1668
self.assertStatusReset()
1669
self.assertEqual(0.0, self.aggregator.progress_bar.progress)
1670
self.assertFalse(self.aggregator.progress_bar.visible)
1672
def test_download_started_cancels_timer(self):
1673
"""Starting a download cancels the queue_done timer."""
1675
self.status_frontend.download_started(fc)
1676
self.aggregator.clock.advance(self.aggregator.finished_delay)
1677
self.status_frontend.download_finished(fc)
1678
self.aggregator.queue_done()
1679
self.aggregator.clock.advance(self.aggregator.finished_delay / 2)
1681
self.status_frontend.download_started(fc2)
1682
self.assertIdentical(self.aggregator.queue_done_timer, None)
1683
self.aggregator.clock.advance(self.aggregator.finished_delay)
1684
self.status_frontend.download_finished(fc2)
1686
def test_upload_started_cancels_timer(self):
1687
"""Starting an upload cancels the queue_done timer."""
1689
self.status_frontend.upload_started(fc)
1690
self.aggregator.clock.advance(self.aggregator.finished_delay)
1691
self.status_frontend.upload_finished(fc)
1692
self.aggregator.queue_done()
1693
self.aggregator.clock.advance(self.aggregator.finished_delay / 2)
1695
self.status_frontend.upload_started(fc2)
1696
self.assertIdentical(self.aggregator.queue_done_timer, None)
1697
self.aggregator.clock.advance(self.aggregator.finished_delay)
1698
self.status_frontend.upload_finished(fc2)
1701
class StatusGrouperTestCase(TestCase):
1702
"""Tests for the group_statuses function."""
1704
def test_group_status(self):
1705
"""The status grouper sorts and groups by weight."""
1706
status99 = FakeStatus(99)
1713
result = [list(k) for _, k in aggregator.group_statuses(statuses)]
1717
[status99, status99]]
1719
self.assertEqual(result, expected)
1722
class HundredFeetTestCase(TestCase):
1723
"""Try to make all parts work together."""
1725
def test_all_together_now(self):
1726
"""Make all parts work together."""
1727
self.patch(aggregator, "ToggleableNotification",
1728
FakeNotificationSingleton())
1729
self.patch(aggregator, "Launcher", FakeLauncher)
1730
self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
1731
clock = PatchedClock()
1732
upload = FakeCommand(path='upload.foo')
1733
sf = aggregator.StatusFrontend(clock=clock)
1734
sf.server_connection_made()
1735
sf.set_show_all_notifications(True)
1737
# the progress bar is not visible yet
1738
self.assertFalse(sf.aggregator.progress_bar.visible)
1739
sf.upload_started(upload)
1740
# the progress bar is now shown
1741
self.assertTrue(sf.aggregator.progress_bar.visible)
1742
notifications_shown = (sf.aggregator.file_discovery_bubble.
1743
notification.notifications_shown)
1744
# no notifications shown yet
1745
self.assertEqual(0, len(notifications_shown))
1746
clock.advance(aggregator.FileDiscoveryGatheringState.initial_delay)
1747
# files found notification
1748
self.assertEqual(1, len(notifications_shown))
1749
download = FakeCommand('download.bar')
1750
sf.download_started(download)
1751
self.assertEqual(1, len(notifications_shown))
1752
# the progress still is zero
1753
self.assertEqual(0.0, sf.aggregator.progress_bar.progress)
1754
clock.advance(aggregator.FileDiscoveryUpdateState.updates_delay)
1755
# files count update
1756
self.assertEqual(2, len(notifications_shown))
1757
clock.advance(aggregator.FileDiscoveryUpdateState.updates_timeout -
1758
aggregator.FileDiscoveryUpdateState.updates_delay)
1759
sf.upload_finished(upload)
1760
sf.download_finished(download)
1761
# the progress still is now 100%
1762
self.assertEqual(1.0, sf.aggregator.progress_bar.progress)
1764
clock.advance(sf.aggregator.finished_delay + 1)
1765
self.assertEqual(3, len(notifications_shown))
1767
def test_all_together_now_off(self):
1768
"""Make all parts work together, but with notifications off."""
1769
self.patch(aggregator, "ToggleableNotification",
1770
FakeNotificationSingleton())
1771
self.patch(aggregator, "Launcher", FakeLauncher)
1772
self.patch(aggregator.session, "Inhibitor", FakeInhibitor)
1773
clock = PatchedClock()
1774
upload = FakeCommand('upload.foo')
1775
sf = aggregator.StatusFrontend(clock=clock)
1776
sf.set_show_all_notifications(False)
1778
# the progress bar is not visible yet
1779
self.assertFalse(sf.aggregator.progress_bar.visible)
1780
sf.upload_started(upload)
1781
# the progress bar is now shown
1782
self.assertTrue(sf.aggregator.progress_bar.visible)
1783
notifications_shown = (sf.aggregator.file_discovery_bubble.
1784
notification.notifications_shown)
1785
# no notifications shown, never
1786
self.assertEqual(0, len(notifications_shown))
1787
clock.advance(aggregator.FileDiscoveryGatheringState.initial_delay)
1788
self.assertEqual(0, len(notifications_shown))
1789
download = FakeCommand('download.bar')
1790
sf.download_started(download)
1791
self.assertEqual(0, len(notifications_shown))
1792
# the progress still is zero
1793
self.assertEqual(0.0, sf.aggregator.progress_bar.progress)
1794
clock.advance(aggregator.FileDiscoveryUpdateState.updates_delay)
1795
self.assertEqual(0, len(notifications_shown))
1796
clock.advance(aggregator.FileDiscoveryUpdateState.updates_timeout -
1797
aggregator.FileDiscoveryUpdateState.updates_delay)
1798
sf.upload_finished(upload)
1799
sf.download_finished(download)
1800
# the progress still is now 100%
1801
self.assertEqual(1.0, sf.aggregator.progress_bar.progress)
1802
self.assertEqual(0, len(notifications_shown))
1804
self.assertEqual(0, len(notifications_shown))
1806
HundredFeetTestCase.skip = "libindicate-ERROR causes core dump: #922179"