~didrocks/ubuntuone-client/use_result_var

« back to all changes in this revision

Viewing changes to tests/status/test_aggregator.py

  • Committer: Bazaar Package Importer
  • Author(s): Rodney Dawes
  • Date: 2011-02-11 16:18:11 UTC
  • mto: This revision was merged to the branch mainline in revision 67.
  • Revision ID: james.westby@ubuntu.com-20110211161811-n18dj9lde7dxqjzr
Tags: upstream-1.5.4
ImportĀ upstreamĀ versionĀ 1.5.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
# with this program.  If not, see <http://www.gnu.org/licenses/>.
18
18
"""Tests for the status events aggregator."""
19
19
 
 
20
import logging
 
21
 
20
22
from twisted.internet.task import Clock
21
23
from twisted.trial.unittest import TestCase
22
24
 
 
25
from ubuntuone.devtools.handlers import MementoHandler
23
26
from ubuntuone.status import aggregator
24
 
from ubuntuone.syncdaemon.status_listener import StatusListener
25
 
 
26
 
 
27
 
class FakeNotification(object):
 
27
from ubuntuone.status.notification import AbstractNotification
 
28
from ubuntuone.status.messaging import AbstractMessaging
 
29
from ubuntuone.syncdaemon import status_listener
 
30
from ubuntuone.syncdaemon.volume_manager import Share, UDF
 
31
 
 
32
 
 
33
class PatchedClock(Clock):
 
34
    """Patch the clock to fix twisted bug #4823."""
 
35
 
 
36
    def advance(self, amount):
 
37
        self.calls.sort(lambda a, b: cmp(a.getTime(), b.getTime()))
 
38
        Clock.advance(self, amount)
 
39
 
 
40
 
 
41
class TimerTestCase(TestCase):
 
42
    """Test the Timer class."""
 
43
 
 
44
    TIMEOUT = 3.0
 
45
 
 
46
    def setUp(self):
 
47
        """Initialize this test instance."""
 
48
        self.clock = PatchedClock()
 
49
        self.timer = aggregator.Timer(delay=3.0, clock=self.clock)
 
50
 
 
51
    def test_not_fired_initially(self):
 
52
        """The timer is not fired initially"""
 
53
        self.assertFalse(self.timer.called)
 
54
 
 
55
    def test_fired_after_delay(self):
 
56
        """The timer is fired after the initial delay."""
 
57
        self.clock.advance(self.timer.delay)
 
58
        self.assertTrue(self.timer.called)
 
59
 
 
60
    def test_cleanup_cancels_delay_call(self):
 
61
        """Calling cleanup cancels the delay call."""
 
62
        self.timer.cleanup()
 
63
        self.assertTrue(self.timer.delay_call.cancelled)
 
64
 
 
65
    def test_not_fired_immediately(self):
 
66
        """The timer is not fired immediately."""
 
67
        self.timer.reset()
 
68
        self.assertFalse(self.timer.called)
 
69
 
 
70
    def test_fired_after_initial_wait(self):
 
71
        """The timer is fired after an initial wait."""
 
72
        self.timer.reset()
 
73
        self.clock.advance(self.timer.delay)
 
74
        self.assertTrue(self.timer.called)
 
75
 
 
76
    def test_not_fired_if_reset_within_delay(self):
 
77
        """The timer is not fired if it is reset within the delay."""
 
78
        self.timer.reset()
 
79
        self.clock.advance(self.timer.delay/0.8)
 
80
        self.timer.reset()
 
81
        self.clock.advance(self.timer.delay/0.8)
 
82
        self.assertTrue(self.timer.called)
 
83
 
 
84
 
 
85
class DeadlineTimerTestCase(TimerTestCase):
 
86
    """Test the DeadlineTimer class."""
 
87
 
 
88
    DELAY = 0.5
 
89
 
 
90
    def setUp(self):
 
91
        """Initialize this test instance."""
 
92
        self.clock = PatchedClock()
 
93
        self.timer = aggregator.DeadlineTimer(delay=0.5, timeout=3.0,
 
94
                                            clock=self.clock)
 
95
 
 
96
    def test_fired_if_initial_timeout_exceeded(self):
 
97
        """Timer is fired if the initial timeout is exceeded."""
 
98
        small_delay = self.timer.delay * 0.8
 
99
        for n in range(int(self.timer.timeout / small_delay) + 1):
 
100
            self.timer.reset()
 
101
            self.clock.advance(small_delay)
 
102
        self.assertTrue(self.timer.called)
 
103
 
 
104
    def test_not_fired_twice_if_delay_exceeded(self):
 
105
        """Timer is not fired twice if the delay is exceeded."""
 
106
        large_delay = self.timer.delay * 1.2
 
107
        for n in range(int(self.timer.timeout / large_delay) + 1):
 
108
            self.timer.reset()
 
109
            self.clock.advance(large_delay)
 
110
        self.clock.advance(self.timer.delay)
 
111
        self.assertTrue(self.timer.called)
 
112
 
 
113
    def test_not_fired_twice_if_timeout_exceeded(self):
 
114
        """Timer is not fired twice if the timeout is exceeded."""
 
115
        small_delay = self.timer.delay * 0.8
 
116
        for n in range(int(self.timer.timeout / small_delay) + 1):
 
117
            self.timer.reset()
 
118
            self.clock.advance(small_delay)
 
119
        self.clock.advance(self.timer.delay)
 
120
        self.assertTrue(self.timer.called)
 
121
 
 
122
    def test_cleanup_cancels_timeout_call(self):
 
123
        """Calling cleanup cancels the delay call."""
 
124
        self.timer.cleanup()
 
125
        self.assertTrue(self.timer.timeout_call.cancelled)
 
126
 
 
127
 
 
128
class FakeNotification(AbstractNotification):
28
129
    """A fake notification class."""
29
130
 
30
131
    def __init__(self, application_name="fake app"):
35
136
    def send_notification(self, title, message, icon=None, append=False):
36
137
        """Show a notification to the user."""
37
138
        self.notifications_shown.append((title, message, icon, append))
38
 
 
39
 
 
40
 
class DelayedBufferTestCase(TestCase):
41
 
    """Test the delayed buffer class."""
42
 
 
43
 
    THRESHOLD = 5.0
44
 
    SMALL_DELAY = 0.5
45
 
    COUNT_MESSAGES = 4
46
 
 
47
 
    def setUp(self):
48
 
        """Initialize this test instance."""
49
 
        self.result = []
50
 
        self.clock = Clock()
51
 
        append = lambda *args: self.result.append(args)
52
 
        self.ag = aggregator.DelayedBuffer(cb=append, clock=self.clock,
53
 
                                        threshold=self.THRESHOLD)
54
 
 
55
 
    def test_a_bunch_at_once_aggregated_as_one(self):
56
 
        """A bunch of messages sent at once are aggregated into one."""
57
 
        for n in range(self.COUNT_MESSAGES):
58
 
            self.ag.push("message1")
59
 
        self.clock.advance(self.THRESHOLD)
60
 
        self.assertEqual(len(self.result), 1)
61
 
 
62
 
    def test_a_bunch_under_threshold_aggregated_as_one(self):
63
 
        """A bunch of messages sent under threshold are aggregated into one."""
64
 
        for n in range(self.COUNT_MESSAGES):
65
 
            self.ag.push("message1")
66
 
            self.clock.advance(self.SMALL_DELAY)
67
 
        self.clock.advance(self.THRESHOLD)
68
 
        self.assertEqual(len(self.result), 1)
69
 
 
70
 
    def test_a_bunch_over_threshold_are_not_aggregated(self):
71
 
        """A bunch of messages sent over the threshold are not aggregated."""
72
 
        for n in range(self.COUNT_MESSAGES):
73
 
            self.ag.push("message1")
74
 
            self.clock.advance(self.THRESHOLD)
75
 
        self.clock.advance(self.THRESHOLD + self.SMALL_DELAY)
76
 
        self.assertEqual(len(self.result), self.COUNT_MESSAGES)
77
 
 
78
 
 
79
 
class FakeAggregator(object):
 
139
        return len(self.notifications_shown) - 1
 
140
 
 
141
    def update_notification(self, notification, new_title, new_message,
 
142
                            new_icon=None):
 
143
        """Update the notification with a new message body."""
 
144
        title, old_message, icon, append = self.notifications_shown[
 
145
            notification]
 
146
        if new_icon is not None:
 
147
            icon = new_icon
 
148
        # we store it as a new notification, to ease testing
 
149
        notification_params = (new_title, new_message, icon, append)
 
150
        self.notifications_shown.append(notification_params)
 
151
 
 
152
 
 
153
def FakeNotificationSingleton():
 
154
    """Builds a notification singleton, that logs all notifications shown."""
 
155
    instance = FakeNotification()
 
156
 
 
157
    def get_instance():
 
158
        """Returns the single instance."""
 
159
        return instance
 
160
 
 
161
    return get_instance
 
162
 
 
163
 
 
164
class FakeMessaging(AbstractMessaging):
 
165
    """A fake messaging class."""
 
166
 
 
167
    def __init__(self):  # pylint: disable=W0231
 
168
        self.messages_shown = {}
 
169
        self.messages_updated = {}
 
170
 
 
171
    # pylint: disable=R0913
 
172
    def show_message(self, sender, callback=None, message_time=None,
 
173
                     message_count=None, icon=None):
 
174
        """Show a message to the user."""
 
175
        if message_count and sender in self.messages_shown:
 
176
            self.update_count(sender, message_count)
 
177
        self.messages_shown[sender] = (
 
178
            callback, message_time, message_count, icon)
 
179
    # pylint: enable=R0913
 
180
 
 
181
    def update_count(self, sender, add_count):
 
182
        """Update the count for an existing indicator."""
 
183
        self.messages_updated[sender] = (sender, add_count)
 
184
 
 
185
 
 
186
class FakeStatusAggregator(object):
 
187
    """A fake status aggregator."""
 
188
 
 
189
    def __init__(self, clock):          # pylint: disable=W0613
 
190
        """Initialize this instance."""
 
191
        self.discovered = 0
 
192
        self.completed = 0
 
193
        self.restart_progress_bubble_called = False
 
194
 
 
195
    def get_discovery_message(self):
 
196
        """Return the file discovery message."""
 
197
        self.discovered += 1
 
198
        return self.build_discovery_message()
 
199
 
 
200
    def build_discovery_message(self):
 
201
        """Build the file discovery message."""
 
202
        return "a lot of files found (%d).""" % self.discovered
 
203
 
 
204
    def get_progress_message(self):
 
205
        """Return the progress message."""
 
206
        self.completed += 1
 
207
        return self.build_progress_message()
 
208
 
 
209
    def build_progress_message(self):
 
210
        """Build the progress message."""
 
211
        params = (self.discovered, self.completed)
 
212
        return "a lot of files transferring (%d/%d).""" % params
 
213
 
 
214
    def get_final_status_message(self):
 
215
        """Return the final status message."""
 
216
        return "a lot of files completed."""
 
217
 
 
218
    def restart_progress_bubble(self):
 
219
        """Reset the progress bubble."""
 
220
        self.restart_progress_bubble_called = True
 
221
 
 
222
 
 
223
class FileDiscoveryBubbleTestCase(TestCase):
 
224
    """Test the FileDiscoveryBubble class."""
 
225
 
 
226
    def setUp(self):
 
227
        """Initialize this test instance."""
 
228
        self.patch(aggregator, "Notification", FakeNotificationSingleton())
 
229
        self.clock = PatchedClock()
 
230
        self.aggregator = FakeStatusAggregator(clock=self.clock)
 
231
        self.bubble = aggregator.FileDiscoveryBubble(self.aggregator,
 
232
                                                     clock=self.clock)
 
233
        self.addCleanup(self.bubble.cleanup)
 
234
        fdis = aggregator.FileDiscoveryGatheringState
 
235
        self.initial_delay = fdis.initial_delay
 
236
        self.smaller_delay = self.initial_delay * 0.8
 
237
        self.initial_timeout = fdis.initial_timeout
 
238
        fdus = aggregator.FileDiscoveryUpdateState
 
239
        self.updates_delay = fdus.updates_delay
 
240
        self.updates_timeout = fdus.updates_timeout
 
241
        fdss = aggregator.FileDiscoverySleepState
 
242
        self.sleep_delay = fdss.sleep_delay
 
243
 
 
244
        self.handler = MementoHandler()
 
245
        self.handler.setLevel(logging.DEBUG)
 
246
        aggregator.logger.addHandler(self.handler)
 
247
        aggregator.logger.setLevel(logging.DEBUG)
 
248
        self.addCleanup(aggregator.logger.removeHandler, self.handler)
 
249
 
 
250
 
 
251
    def get_notifications_shown(self):
 
252
        """The list of notifications shown."""
 
253
        return self.bubble.notification.notifications_shown
 
254
 
 
255
    def test_popup_shows_notification(self):
 
256
        """The popup callback shows notifications."""
 
257
        self.bubble.new_file_found()
 
258
        self.bubble._popup()
 
259
        message = self.aggregator.build_discovery_message()
 
260
        notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
 
261
        self.assertIn(notification, self.get_notifications_shown())
 
262
 
 
263
    def test_popup_resets_progress_bubble(self):
 
264
        """The popup callback resets the progress bubble."""
 
265
        self.bubble.new_file_found()
 
266
        self.bubble._popup()
 
267
        self.assertTrue(self.aggregator.restart_progress_bubble_called)
 
268
 
 
269
    def test_notification_is_logged_in_debug(self):
 
270
        """The notification is printed in the debug log."""
 
271
        self.bubble.new_file_found()
 
272
        self.bubble._popup()
 
273
        msg = "notification shown: %s" % self.get_notifications_shown()[0][1]
 
274
        self.assertTrue(self.handler.check_debug(msg))
 
275
 
 
276
    def test_bubble_is_not_shown_initially(self):
 
277
        """The bubble is not shown initially."""
 
278
        self.bubble.new_file_found()
 
279
        self.assertEqual(0, len(self.get_notifications_shown()))
 
280
 
 
281
    def test_bubble_is_shown_after_delay(self):
 
282
        """The bubble is shown after a delay."""
 
283
        self.bubble.new_file_found()
 
284
        self.clock.advance(self.initial_delay)
 
285
        self.assertEqual(1, len(self.get_notifications_shown()))
 
286
 
 
287
    def test_bubble_not_shown_if_more_files_found(self):
 
288
        """The bubble is not shown if more files found within delay."""
 
289
        self.clock.advance(self.smaller_delay)
 
290
        self.bubble.new_file_found()
 
291
        self.clock.advance(self.smaller_delay)
 
292
        self.assertEqual(0, len(self.get_notifications_shown()))
 
293
 
 
294
    def test_bubble_shown_if_timeout_exceeded(self):
 
295
        """The bubble is shown if the timeout is exceeded."""
 
296
        self.bubble.new_file_found()
 
297
        count = int(self.initial_timeout / self.smaller_delay) + 1
 
298
        for n in range(count):
 
299
            self.clock.advance(self.smaller_delay)
 
300
            self.bubble.new_file_found()
 
301
        self.assertEqual(1, len(self.get_notifications_shown()))
 
302
 
 
303
    def test_idle_state(self):
 
304
        """The idle state is verified."""
 
305
        self.assertEqual(type(self.bubble.state),
 
306
                                   aggregator.FileDiscoveryIdleState)
 
307
 
 
308
    def test_gathering_state(self):
 
309
        """The gathering state is set after the first file is found."""
 
310
        self.bubble.new_file_found()
 
311
        self.assertEqual(type(self.bubble.state),
 
312
                                   aggregator.FileDiscoveryGatheringState)
 
313
 
 
314
    def test_update_state(self):
 
315
        """When the gathering state finishes, the update state is started."""
 
316
        self.bubble.new_file_found()
 
317
        self.clock.advance(self.initial_delay)
 
318
        self.assertEqual(type(self.bubble.state),
 
319
                                   aggregator.FileDiscoveryUpdateState)
 
320
 
 
321
    def test_sleeping_state(self):
 
322
        """When the update state finishes, the sleeping state is started."""
 
323
        self.bubble.new_file_found()
 
324
        self.clock.advance(self.initial_delay)
 
325
        self.clock.advance(self.updates_timeout)
 
326
        self.assertEqual(type(self.bubble.state),
 
327
                                   aggregator.FileDiscoverySleepState)
 
328
 
 
329
    def test_back_to_initial_state(self):
 
330
        """When the last state finishes, we return to the idle state."""
 
331
        self.bubble.new_file_found()
 
332
        self.clock.advance(self.initial_delay)
 
333
        self.clock.advance(self.updates_timeout)
 
334
        self.clock.advance(self.sleep_delay)
 
335
        self.assertEqual(type(self.bubble.state),
 
336
                                   aggregator.FileDiscoveryIdleState)
 
337
 
 
338
    def test_new_files_found_while_updating_not_shown_immediately(self):
 
339
        """New files found in the updating state are not shown immediately."""
 
340
        self.bubble.new_file_found()
 
341
        self.clock.advance(self.initial_delay)
 
342
        self.bubble.new_file_found()
 
343
        self.assertEqual(1, len(self.get_notifications_shown()))
 
344
 
 
345
    def test_new_files_found_while_updating_are_shown_after_a_delay(self):
 
346
        """New files found in the updating state are shown after a delay."""
 
347
        self.bubble.new_file_found()
 
348
        self.clock.advance(self.initial_delay)
 
349
        self.bubble.new_file_found()
 
350
        self.clock.advance(self.updates_delay)
 
351
        self.assertEqual(2, len(self.get_notifications_shown()))
 
352
 
 
353
    def test_update_resets_progress_bubble(self):
 
354
        """The update callback resets the progress bubble."""
 
355
        self.bubble.new_file_found()
 
356
        self.bubble._popup()
 
357
        self.bubble.new_file_found()
 
358
        self.bubble._update()
 
359
        self.assertTrue(self.aggregator.restart_progress_bubble_called)
 
360
 
 
361
    def test_update_modifies_notification(self):
 
362
        """The update callback updates notifications."""
 
363
        self.bubble.new_file_found()
 
364
        self.bubble._popup()
 
365
        self.bubble.new_file_found()
 
366
        self.bubble._update()
 
367
        message = self.aggregator.build_discovery_message()
 
368
        notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
 
369
        self.assertIn(notification, self.get_notifications_shown())
 
370
 
 
371
    def test_update_is_logged_in_debug(self):
 
372
        """The notification is logged when _update is called."""
 
373
        self.bubble.new_file_found()
 
374
        self.bubble._popup()
 
375
        self.bubble.new_file_found()
 
376
        self.bubble._update()
 
377
        msg = "notification updated: %s" % self.get_notifications_shown()[1][1]
 
378
        self.assertTrue(self.handler.check_debug(msg))
 
379
 
 
380
 
 
381
class ProgressBubbleTestCase(TestCase):
 
382
    """Tests for the progress bubble."""
 
383
 
 
384
    def setUp(self):
 
385
        """Initialize this test instance."""
 
386
        self.patch(aggregator, "Notification", FakeNotificationSingleton())
 
387
        self.clock = PatchedClock()
 
388
        self.aggregator = FakeStatusAggregator(clock=self.clock)
 
389
        self.bubble = aggregator.ProgressBubble(self.aggregator,
 
390
                                                clock=self.clock)
 
391
        self.addCleanup(self.bubble.cleanup)
 
392
        self.smaller_delay = aggregator.ProgressBubble.sleep_delay * 0.8
 
393
        self.right_delay = aggregator.ProgressBubble.sleep_delay
 
394
        self.longer_delay = aggregator.ProgressBubble.sleep_delay * 1.2
 
395
 
 
396
    def test_is_created_successfully(self):
 
397
        """The progress bubble is created and idle."""
 
398
        self.assertEqual(None, self.bubble.timer)
 
399
 
 
400
    def test_restart_creates_timer(self):
 
401
        """Restarting creates a timer."""
 
402
        self.bubble.restart()
 
403
        self.assertNotEqual(None, self.bubble.timer)
 
404
 
 
405
    def test_not_shown_initially(self):
 
406
        """The bubble is not shown initially."""
 
407
        self.bubble.restart()
 
408
        self.assertEqual(None, self.bubble.notification)
 
409
 
 
410
    def test_not_shown_immediately(self):
 
411
        """The bubble is not shown immediately."""
 
412
        self.bubble.restart()
 
413
        self.clock.advance(self.smaller_delay)
 
414
        self.assertEqual(None, self.bubble.notification)
 
415
 
 
416
    def test_shown_after_delay(self):
 
417
        """The bubble is shown after the standard delay."""
 
418
        self.bubble.restart()
 
419
        self.clock.advance(self.longer_delay)
 
420
        self.assertEqual(1, len(self.bubble.notification.notifications_shown))
 
421
 
 
422
    def test_not_shown_if_restarted(self):
 
423
        """The delay is adjusted when restarting."""
 
424
        self.bubble.restart()
 
425
        self.clock.advance(self.smaller_delay)
 
426
        self.bubble.restart()
 
427
        self.clock.advance(self.smaller_delay)
 
428
        self.assertEqual(None, self.bubble.notification)
 
429
 
 
430
    def test_delay_reached_after_restart(self):
 
431
        """The timeout is still reached after a restart."""
 
432
        self.bubble.restart()
 
433
        self.clock.advance(self.smaller_delay)
 
434
        self.bubble.restart()
 
435
        self.clock.advance(self.longer_delay)
 
436
        self.assertEqual(1, len(self.bubble.notification.notifications_shown))
 
437
 
 
438
    def test_can_be_stopped(self):
 
439
        """The timeout can be stopped."""
 
440
        self.bubble.restart()
 
441
        self.clock.advance(self.smaller_delay)
 
442
        self.bubble.stop()
 
443
        self.clock.advance(self.longer_delay)
 
444
        self.assertEqual(None, self.bubble.notification)
 
445
 
 
446
    def test_delay_reached_after_stopping_then_restarting(self):
 
447
        """The timeout is still reached after a stop followed by a restart."""
 
448
        self.bubble.restart()
 
449
        self.clock.advance(self.smaller_delay)
 
450
        self.bubble.stop()
 
451
        self.clock.advance(self.longer_delay)
 
452
        self.bubble.restart()
 
453
        self.clock.advance(self.longer_delay)
 
454
        self.assertEqual(1, len(self.bubble.notification.notifications_shown))
 
455
 
 
456
    def test_restarts_automatically(self):
 
457
        """The timer is restarted automatically."""
 
458
        self.bubble.restart()
 
459
        self.clock.advance(self.right_delay)
 
460
        self.assertEqual(1, len(self.bubble.notification.notifications_shown))
 
461
        self.clock.advance(self.right_delay)
 
462
        self.assertEqual(2, len(self.bubble.notification.notifications_shown))
 
463
 
 
464
 
 
465
class FinalBubbleTestCase(TestCase):
 
466
    """Test for the final status notification bubble."""
 
467
 
 
468
    def setUp(self):
 
469
        """Initialize this test instance."""
 
470
        self.patch(aggregator, "Notification", FakeNotificationSingleton())
 
471
        self.clock = PatchedClock()
 
472
        self.aggregator = FakeStatusAggregator(clock=self.clock)
 
473
        self.bubble = aggregator.FinalStatusBubble(self.aggregator)
 
474
        self.addCleanup(self.bubble.cleanup)
 
475
 
 
476
    def test_notification_not_shown_initially(self):
 
477
        """The notification is not shown initially."""
 
478
        self.assertEqual(None, self.bubble.notification)
 
479
 
 
480
    def test_show_pops_bubble(self):
 
481
        """The show method pops the bubble immediately."""
 
482
        self.bubble.show()
 
483
        self.assertEqual(1, len(self.bubble.notification.notifications_shown))
 
484
 
 
485
 
 
486
class ProgressBarTestCase(TestCase):
 
487
    """Tests for the progress bar."""
 
488
 
 
489
    def setUp(self):
 
490
        """Initialize this test instance."""
 
491
        self.clock = PatchedClock()
 
492
        self.bar = aggregator.ProgressBar(clock=self.clock)
 
493
        self.addCleanup(self.bar.cleanup)
 
494
        self.timeout_calls = []
 
495
        original_timeout = self.bar._timeout
 
496
 
 
497
        def fake_timeout(result):
 
498
            """A fake _timeout method."""
 
499
            self.timeout_calls.append(self.bar.percentage)
 
500
            original_timeout(result)
 
501
 
 
502
        self.patch(self.bar, "_timeout", fake_timeout)
 
503
 
 
504
    def test_shown_when_progress_made(self):
 
505
        """The progress bar is shown when progress is made."""
 
506
        self.bar.progress_made(50, 100)
 
507
        self.assertTrue(self.bar.visible)
 
508
 
 
509
    def test_progress_made_updates_counter(self):
 
510
        """Progress made updates the counter."""
 
511
        self.bar.progress_made(50, 100)
 
512
        self.assertEqual(self.bar.percentage, 50.0)
 
513
 
 
514
    def test_no_timer_set_initially(self):
 
515
        """There's no timer set initially."""
 
516
        self.assertEqual(self.bar.timer, None)
 
517
 
 
518
    def test_progress_made_sets_timer(self):
 
519
        """Progress made sets up a timer."""
 
520
        self.bar.progress_made(50, 100)
 
521
        self.assertNotEqual(self.bar.timer, None)
 
522
 
 
523
    def test_progress_made_not_updated_initially(self):
 
524
        """Progress made is not updated initially."""
 
525
        self.bar.progress_made(50, 100)
 
526
        self.assertEqual(0, len(self.timeout_calls))
 
527
 
 
528
    def test_progress_made_updated_after_a_delay(self):
 
529
        """The progressbar is updated after a delay."""
 
530
        self.bar.progress_made(50, 100)
 
531
        self.clock.advance(aggregator.ProgressBar.updates_delay)
 
532
        self.assertIn(50.0, self.timeout_calls)
 
533
 
 
534
    def test_progress_updates_are_aggregated(self):
 
535
        """The progressbar is updated after a delay."""
 
536
        self.bar.progress_made(50, 100)
 
537
        self.clock.advance(aggregator.ProgressBar.updates_delay / 2)
 
538
        self.bar.progress_made(60, 100)
 
539
        self.clock.advance(aggregator.ProgressBar.updates_delay / 2)
 
540
        self.assertEqual(1, len(self.timeout_calls))
 
541
 
 
542
    def test_progress_updates_are_continuous(self):
 
543
        """The progressbar updates are continuous."""
 
544
        self.bar.progress_made(50, 100)
 
545
        self.clock.advance(aggregator.ProgressBar.updates_delay)
 
546
        self.bar.progress_made(60, 100)
 
547
        self.clock.advance(aggregator.ProgressBar.updates_delay)
 
548
        self.assertEqual(2, len(self.timeout_calls))
 
549
 
 
550
    def test_hidden_when_completed(self):
 
551
        """The progressbar is hidden when everything completes."""
 
552
        self.bar.progress_made(50, 100)
 
553
        self.bar.completed()
 
554
        self.assertFalse(self.bar.visible)
 
555
 
 
556
    def test_disable(self):
 
557
        """The bar pulse is disabled."""
 
558
        self.bar.enable()
 
559
        self.bar.disable()
 
560
        self.assertFalse(self.bar.pulsating)
 
561
 
 
562
    def test_enable(self):
 
563
        """The bar pulse is enabled."""
 
564
        self.bar.disable()
 
565
        self.bar.enable()
 
566
        self.assertTrue(self.bar.pulsating)
 
567
 
 
568
 
 
569
class FakeDelayedBuffer(object):
80
570
    """Appends all status pushed into a list."""
 
571
    timer_reset = False
 
572
    processed = False
81
573
 
82
574
    def __init__(self, *args, **kwargs):
83
575
        """Initialize this instance."""
84
 
        self.statuses = []
85
 
 
86
 
    def push(self, event):
87
 
        self.statuses.append(event)
88
 
 
89
 
 
90
 
class StatusAggregatorFrontendTestCase(TestCase):
91
 
    """Test the status events aggregator."""
 
576
        self.events = []
 
577
 
 
578
    def push_event(self, event):
 
579
        """Push an event into this buffer."""
 
580
        self.events.append(event)
 
581
 
 
582
    def reset_threshold_timer(self):
 
583
        """The status has changed."""
 
584
        self.timer_reset = True
 
585
 
 
586
    def process_accumulated(self):
 
587
        """Process accumulated events."""
 
588
        self.processed = True
 
589
 
 
590
 
 
591
class FakeCommand(object):
 
592
    """A fake command."""
 
593
 
 
594
 
 
595
class FakeVolumeManager(object):
 
596
    """A fake vm."""
 
597
 
 
598
    def __init__(self):
 
599
        """Initialize this instance."""
 
600
        self.volumes = {}
 
601
 
 
602
    def get_volume(self, volume_id):
 
603
        """Return a volume given its id."""
 
604
        return self.volumes[volume_id]
 
605
 
 
606
 
 
607
class FakeAggregator(object):
 
608
    """A fake aggregator object."""
 
609
 
 
610
    def __init__(self, clock):
 
611
        """Initialize this fake instance."""
 
612
        self.queued_commands = set()
 
613
 
 
614
    def queue_done(self):
 
615
        """The queue completed all operations."""
 
616
        self.queued_commands.clear()
 
617
 
 
618
    def misc_command_queued(self, command):
 
619
        """A new command was queued."""
 
620
        self.queued_commands.add(command)
 
621
 
 
622
    def misc_command_unqueued(self, command):
 
623
        """A new command was unqueued."""
 
624
        self.queued_commands.discard(command)
 
625
 
 
626
    download_started = misc_command_queued
 
627
    download_finished = misc_command_unqueued
 
628
    upload_started = misc_command_queued
 
629
    upload_finished = misc_command_unqueued
 
630
 
 
631
 
 
632
class StatusFrontendTestCase(TestCase):
 
633
    """Test the status frontend."""
92
634
 
93
635
    def setUp(self):
94
636
        """Initialize this test instance."""
 
637
        self.patch(aggregator, "StatusAggregator", FakeAggregator)
 
638
        self.patch(aggregator, "Notification", FakeNotificationSingleton())
 
639
        self.patch(aggregator, "Messaging", FakeMessaging)
95
640
        self.fakefsm = None
96
 
        self.fakevm = None
97
 
        self.patch(aggregator, "DelayedBuffer", FakeAggregator)
98
 
        self.status_aggregator = aggregator.StatusAggregator()
99
 
        self.listener = StatusListener(self.fakefsm, self.fakevm,
100
 
                                       self.status_aggregator)
 
641
        self.fakevm = FakeVolumeManager()
 
642
        self.status_frontend = aggregator.StatusFrontend()
 
643
        self.listener = status_listener.StatusListener(self.fakefsm,
 
644
                                                       self.fakevm,
 
645
                                                       self.status_frontend)
101
646
 
102
647
    def test_file_published(self):
103
648
        """A file published event is processed."""
105
650
        node_id = "fake node id"
106
651
        is_public = True
107
652
        public_url = "http://fake_public/url"
108
 
 
109
653
        self.listener.handle_AQ_CHANGE_PUBLIC_ACCESS_OK(share_id, node_id,
110
654
                                                        is_public, public_url)
111
 
        status = self.status_aggregator.aggregator.statuses[0]
112
 
        self.assertIsInstance(status, aggregator.FilePublishingStatus)
 
655
        self.assertEqual(
 
656
            1, len(self.status_frontend.notification.notifications_shown))
 
657
        self.assertEqual(
 
658
            (aggregator.UBUNTUONE_TITLE,
 
659
             'A file was just made public at http://fake_public/url', None,
 
660
             False),
 
661
            self.status_frontend.notification.notifications_shown[0])
113
662
 
114
663
    def test_file_unpublished(self):
115
664
        """A file unpublished event is processed."""
116
665
        share_id = "fake share id"
117
666
        node_id = "fake node id"
118
667
        is_public = False
119
 
        public_url = None # SD sends None when unpublishing
 
668
        public_url = None  # SD sends None when unpublishing
120
669
 
121
670
        self.listener.handle_AQ_CHANGE_PUBLIC_ACCESS_OK(share_id, node_id,
122
671
                                                        is_public, public_url)
123
 
        status = self.status_aggregator.aggregator.statuses[0]
124
 
        self.assertIsInstance(status, aggregator.FileUnpublishingStatus)
125
 
 
126
 
 
127
 
class FakeOtherStatus(aggregator.StatusEvent):
128
 
    """A fake status to test weight comparisons."""
129
 
 
130
 
    def __init__(self, weight):
131
 
        """Initialize with the fake weight."""
132
 
        super(FakeOtherStatus, self).__init__()
133
 
        self.WEIGHT = weight
 
672
        self.assertEqual(
 
673
            1, len(self.status_frontend.notification.notifications_shown))
 
674
        self.assertEqual(
 
675
            (aggregator.UBUNTUONE_TITLE, 'A file is no longer published', None,
 
676
             False),
 
677
            self.status_frontend.notification.notifications_shown[0])
 
678
 
 
679
    def test_download_started(self):
 
680
        """A download was added to the queue."""
 
681
        self.patch(status_listener.action_queue, "Download", FakeCommand)
 
682
        fake_command = FakeCommand()
 
683
        self.listener.handle_SYS_QUEUE_ADDED(fake_command)
 
684
        qc = self.status_frontend.aggregator.queued_commands
 
685
        self.assertIn(fake_command, qc)
 
686
 
 
687
    def test_download_finished(self):
 
688
        """A download was removed from the queue."""
 
689
        self.patch(status_listener.action_queue, "Download", FakeCommand)
 
690
        fake_command = FakeCommand()
 
691
        self.listener.handle_SYS_QUEUE_ADDED(fake_command)
 
692
        self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
 
693
        qc = self.status_frontend.aggregator.queued_commands
 
694
        self.assertNotIn(fake_command, qc)
 
695
 
 
696
    def test_upload_started(self):
 
697
        """A upload was added to the queue."""
 
698
        self.patch(status_listener.action_queue, "Upload", FakeCommand)
 
699
        fake_command = FakeCommand()
 
700
        self.listener.handle_SYS_QUEUE_ADDED(fake_command)
 
701
        qc = self.status_frontend.aggregator.queued_commands
 
702
        self.assertIn(fake_command, qc)
 
703
 
 
704
    def test_upload_finished(self):
 
705
        """A upload was removed from the queue."""
 
706
        self.patch(status_listener.action_queue, "Upload", FakeCommand)
 
707
        fake_command = FakeCommand()
 
708
        self.listener.handle_SYS_QUEUE_ADDED(fake_command)
 
709
        self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
 
710
        qc = self.status_frontend.aggregator.queued_commands
 
711
        self.assertNotIn(fake_command, qc)
 
712
 
 
713
    def test_queue_added(self):
 
714
        """A command was added to the queue."""
 
715
        fake_command = FakeCommand()
 
716
        self.listener.handle_SYS_QUEUE_ADDED(fake_command)
 
717
        qc = self.status_frontend.aggregator.queued_commands
 
718
        self.assertIn(fake_command, qc)
 
719
 
 
720
    def test_queue_removed(self):
 
721
        """A command has finished and is removed from the queue."""
 
722
        fake_command = FakeCommand()
 
723
        self.listener.handle_SYS_QUEUE_ADDED(fake_command)
 
724
        self.listener.handle_SYS_QUEUE_REMOVED(fake_command)
 
725
        qc = self.status_frontend.aggregator.queued_commands
 
726
        self.assertNotIn(fake_command, qc)
 
727
 
 
728
    def test_queue_done(self):
 
729
        """The queue is empty."""
 
730
        fake_command = FakeCommand()
 
731
        qc = self.status_frontend.aggregator.queued_commands
 
732
        qc.add(fake_command)
 
733
        self.listener.handle_SYS_QUEUE_DONE()
 
734
        self.assertEqual(0, len(qc))
 
735
 
 
736
    def test_new_share_available(self):
 
737
        """A new share is available for subscription."""
 
738
        SHARE_ID = "fake share id"
 
739
        FAKE_SENDER = 'Mom'
 
740
        share = Share(volume_id=SHARE_ID, other_visible_name=FAKE_SENDER)
 
741
        self.fakevm.volumes[SHARE_ID] = share
 
742
        self.listener.handle_VM_SHARE_CREATED(SHARE_ID)
 
743
        self.assertEqual(
 
744
            1, len(self.status_frontend.notification.notifications_shown))
 
745
        self.assertEqual(
 
746
            (aggregator.UBUNTUONE_TITLE,
 
747
             'New cloud folder available: <%s> shared by <%s>' % (
 
748
                 'None', FAKE_SENDER), None, False),
 
749
            self.status_frontend.notification.notifications_shown[0])
 
750
        msg = self.status_frontend.messaging.messages_shown[FAKE_SENDER]
 
751
        # msg did not receive a time argument
 
752
        self.assertEqual(None, msg[1])
 
753
        # msg did not receive a count argument
 
754
        self.assertEqual(None, msg[2])
 
755
 
 
756
    def test_new_udf_available(self):
 
757
        """A new udf is available for subscription."""
 
758
        udf = UDF()
 
759
        self.listener.handle_VM_UDF_CREATED(udf)
 
760
        self.assertEqual(
 
761
            1, len(self.status_frontend.notification.notifications_shown))
 
762
        self.assertEqual(
 
763
            (aggregator.UBUNTUONE_TITLE, 'New cloud folder available: None',
 
764
             None, False),
 
765
            self.status_frontend.notification.notifications_shown[0])
 
766
        self.assertEqual(
 
767
            1, len(self.status_frontend.messaging.messages_shown))
 
768
        self.assertEqual(
 
769
            0, len(self.status_frontend.messaging.messages_updated))
 
770
        msg = self.status_frontend.messaging.messages_shown[
 
771
            aggregator.NEW_UDFS_SENDER]
 
772
        # msg did not receive a time argument
 
773
        self.assertEqual(None, msg[1])
 
774
        # msg did receive a count argument
 
775
        self.assertEqual(1, msg[2])
 
776
 
 
777
    def test_two_new_udfs_available(self):
 
778
        """A new udf is available for subscription."""
 
779
        udf1 = UDF()
 
780
        self.listener.handle_VM_UDF_CREATED(udf1)
 
781
        udf2 = UDF()
 
782
        self.listener.handle_VM_UDF_CREATED(udf2)
 
783
        self.assertEqual(
 
784
            2, len(self.status_frontend.notification.notifications_shown))
 
785
        self.assertEqual(
 
786
            (aggregator.UBUNTUONE_TITLE, 'New cloud folder available: None',
 
787
             None, False),
 
788
            self.status_frontend.notification.notifications_shown[0])
 
789
        self.assertEqual(
 
790
            (aggregator.UBUNTUONE_TITLE, 'New cloud folder available: None',
 
791
             None, False),
 
792
            self.status_frontend.notification.notifications_shown[1])
 
793
        self.assertEqual(
 
794
            1, len(self.status_frontend.messaging.messages_shown))
 
795
        self.assertEqual(
 
796
            1, len(self.status_frontend.messaging.messages_updated))
 
797
        msg = self.status_frontend.messaging.messages_shown[
 
798
            aggregator.NEW_UDFS_SENDER]
 
799
        # msg did not receive a time argument
 
800
        self.assertEqual(None, msg[1])
 
801
        # msg did receive a count argument
 
802
        self.assertEqual(1, msg[2])
 
803
        self.assertEqual(
 
804
            (aggregator.NEW_UDFS_SENDER, 1),
 
805
            self.status_frontend.messaging.messages_updated[
 
806
                aggregator.NEW_UDFS_SENDER])
 
807
 
 
808
    def test_server_connection_lost(self):
 
809
        """The client connected to the server."""
 
810
        self.listener.handle_SYS_CONNECTION_LOST()
 
811
        self.assertEqual(
 
812
            1, len(self.status_frontend.notification.notifications_shown))
 
813
        self.assertEqual(
 
814
            (aggregator.UBUNTUONE_TITLE,
 
815
             aggregator.ConnectionLostStatus.MESSAGE_ONE, None, False),
 
816
            self.status_frontend.notification.notifications_shown[0])
 
817
 
 
818
    def test_server_connection_made(self):
 
819
        """The client connected to the server."""
 
820
        self.listener.handle_SYS_CONNECTION_MADE()
 
821
        self.assertEqual(
 
822
            1, len(self.status_frontend.notification.notifications_shown))
 
823
        self.assertEqual(
 
824
            (aggregator.UBUNTUONE_TITLE,
 
825
             aggregator.ConnectionMadeStatus.MESSAGE_ONE, None, False),
 
826
            self.status_frontend.notification.notifications_shown[0])
 
827
 
134
828
 
135
829
class StatusEventTestCase(TestCase):
136
 
 
137
830
    """Test the status event class and children."""
138
831
 
139
832
    CLASS = aggregator.StatusEvent
143
836
    def setUp(self):
144
837
        """Initialize this test instance."""
145
838
        if type(self) == StatusEventTestCase:
146
 
            self.assertRaises(AssertionError, self.CLASS, self.CLASS_ARGS)
 
839
            self.assertRaises(AssertionError, self.CLASS, *self.CLASS_ARGS)
147
840
        else:
148
 
            self.status = self.CLASS(self.CLASS_ARGS)
 
841
            self.status = self.CLASS(*self.CLASS_ARGS)
 
842
 
 
843
    def test_one_message_defined(self):
 
844
        """The singular message is defined as MESSAGE_ONE."""
 
845
        if self.status:
 
846
            self.assertNotEqual(None, self.CLASS.MESSAGE_ONE)
 
847
 
 
848
    def test_many_message_defined(self):
 
849
        """The plural message is defined as MESSAGE_MANY."""
 
850
        if self.status:
 
851
            self.assertNotEqual(None, self.CLASS.MESSAGE_MANY)
149
852
 
150
853
    def test_one_message_built_correctly(self):
151
854
        """The message returned by one() is returned ok."""
155
858
    def test_many_message_built_correctly(self):
156
859
        """The message returned by many() is returned ok."""
157
860
        if self.status:
158
 
            test_count = 99
159
 
            expected = self.CLASS.MESSAGE_MANY % test_count
160
 
            self.assertEqual(self.status.many(test_count), expected)
161
 
 
162
 
    def test_compare_sorts_according_to_weight(self):
163
 
        if self.status:
164
 
            other = FakeOtherStatus(99)
165
 
            self.assertTrue(cmp(self.status, other) < 0)
166
 
            self.assertTrue(cmp(other, self.status) > 0)
 
861
            count = 99
 
862
            test_events = [FakeStatus(88)] * count
 
863
            expected = self.CLASS.MESSAGE_MANY % count
 
864
            self.assertEqual(self.status.many(test_events), expected)
167
865
 
168
866
 
169
867
class FilePublishingStatusTestCase(StatusEventTestCase):
185
883
    CLASS_ARGS = (None,)
186
884
 
187
885
 
188
 
class StatusAggregatorBackendTestCase(TestCase):
189
 
    """Test the callback of the status aggregator."""
 
886
class ShareAvailableEventTestCase(StatusEventTestCase):
 
887
    """Test the folder available status class with a Share."""
 
888
 
 
889
    FOLDER_NAME = "folder name"
 
890
    OTHER_USER_NAME = "person name"
 
891
    SAMPLE_SHARE = Share(accepted=False, name=FOLDER_NAME,
 
892
                         other_visible_name=OTHER_USER_NAME)
 
893
    CLASS = aggregator.ShareAvailableStatus
 
894
    CLASS_ARGS = (SAMPLE_SHARE,)
 
895
 
 
896
    def test_one_message_built_correctly(self):
 
897
        """one() must return the folder name and user name."""
 
898
        params = (self.FOLDER_NAME, self.OTHER_USER_NAME)
 
899
        expected = self.CLASS.MESSAGE_ONE % params
 
900
        self.assertEqual(self.status.one(), expected)
 
901
 
 
902
 
 
903
class UDFAvailableEventTestCase(StatusEventTestCase):
 
904
    """Test the folder available status class with a UDF."""
 
905
 
 
906
    FOLDER_NAME = "folder name"
 
907
    SAMPLE_UDF = UDF(subscribed=False, suggested_path=FOLDER_NAME)
 
908
    CLASS = aggregator.UDFAvailableStatus
 
909
    CLASS_ARGS = (SAMPLE_UDF,)
 
910
 
 
911
    def test_one_message_built_correctly(self):
 
912
        """one() must return the folder name."""
 
913
        params = (self.FOLDER_NAME,)
 
914
        expected = self.CLASS.MESSAGE_ONE % params
 
915
        self.assertEqual(self.status.one(), expected)
 
916
 
 
917
 
 
918
class ConnectionLostEventTestCase(StatusEventTestCase):
 
919
    """Test the event when the connection is lost."""
 
920
 
 
921
    CLASS = aggregator.ConnectionLostStatus
 
922
    CLASS_ARGS = ()
 
923
 
 
924
    def test_many_message_built_correctly(self):
 
925
        """The message returned by many() is returned ok."""
 
926
        if self.status:
 
927
            count = 99
 
928
            test_events = [FakeStatus(88)] * count + [self.CLASS()]
 
929
            expected = self.CLASS.MESSAGE_ONE
 
930
            self.assertEqual(self.status.many(test_events), expected)
 
931
 
 
932
 
 
933
class ConnectionMadeEventTestCase(ConnectionLostEventTestCase):
 
934
    """Test the event when the connection is made."""
 
935
 
 
936
    CLASS = aggregator.ConnectionMadeStatus
 
937
    CLASS_ARGS = ()
 
938
 
 
939
 
 
940
class FakeStatus(aggregator.StatusEvent):
 
941
    """A fake status to test weight comparisons."""
 
942
 
 
943
    def __init__(self, weight):
 
944
        """Initialize with the fake weight."""
 
945
        super(FakeStatus, self).__init__()
 
946
        self.WEIGHT = weight
 
947
 
 
948
 
 
949
class FakeFileDiscoveryBubble(object):
 
950
    """A fake FileDiscoveryBubble."""
 
951
 
 
952
    count = 0
 
953
 
 
954
    def __init__(self, status_aggregator, clock=None):
 
955
        """Initialize this instance."""
 
956
        self.status_aggregator = status_aggregator
 
957
 
 
958
    def new_file_found(self):
 
959
        """New files were found."""
 
960
        self.count += 1
 
961
 
 
962
    def cleanup(self):
 
963
        """Cleanup this instance."""
 
964
 
 
965
 
 
966
class FakeProgressBubble(object):
 
967
    """A fake ProgressBubble object."""
 
968
 
 
969
    started = False
 
970
    stopped = False
 
971
 
 
972
    def __init__(self, status_aggregator, clock=None):
 
973
        """Initialize this instance."""
 
974
        self.status_aggregator = status_aggregator
 
975
 
 
976
    def cleanup(self):
 
977
        """Cleanup this instance."""
 
978
 
 
979
    def restart(self):
 
980
        """Start this bubble waiting."""
 
981
        self.started = True
 
982
 
 
983
    def stop(self):
 
984
        """Make this bubble stop."""
 
985
 
 
986
 
 
987
class FakeFinalBubble(object):
 
988
    """A fake FinalStatusBubble."""
 
989
 
 
990
    shown = False
 
991
 
 
992
    def __init__(self, status_aggregator):
 
993
        """Initialize this fake instance."""
 
994
        self.status_aggregator = status_aggregator
 
995
 
 
996
    def cleanup(self):
 
997
        """Cleanup this instance."""
 
998
 
 
999
    def show(self):
 
1000
        """Show this bubble."""
 
1001
        self.shown = True
 
1002
 
 
1003
 
 
1004
class StatusAggregatorTestCase(TestCase):
 
1005
    """Test the backend of the status aggregator."""
190
1006
 
191
1007
    def setUp(self):
192
1008
        """Initialize this test instance."""
193
 
        self.patch(aggregator, "Notification", FakeNotification)
194
 
        self.patch(aggregator, "DelayedBuffer", FakeAggregator)
195
 
        self.status_aggregator = aggregator.StatusAggregator()
196
 
        self.fake_notification = self.status_aggregator.notification
197
 
 
198
 
    def test_pushed_one(self):
199
 
        """One event pushed."""
200
 
        public_url = "http://fake_public/url"
201
 
        event = aggregator.FilePublishingStatus(public_url)
202
 
        events = [event]
203
 
        self.status_aggregator.callback(events)
204
 
        message = aggregator.FilePublishingStatus.MESSAGE_ONE % public_url
205
 
        notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
206
 
        self.assertIn(notification, self.fake_notification.notifications_shown)
207
 
 
208
 
    def test_pushed_many_same_type(self):
209
 
        """Pushed many events, all of the same type."""
210
 
        public_url = "http://fake_public/url%d"
211
 
        count = 10
212
 
        events = []
213
 
 
214
 
        for n in range(count):
215
 
            event = aggregator.FilePublishingStatus(public_url % n)
216
 
            events.append(event)
217
 
 
218
 
        self.status_aggregator.callback(events)
219
 
        message = aggregator.FilePublishingStatus.MESSAGE_MANY % count
220
 
        notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
221
 
        self.assertIn(notification, self.fake_notification.notifications_shown)
222
 
 
223
 
    def test_pushed_many_mixed_types(self):
224
 
        """Pushed many events, of mixed types."""
225
 
        public_url = "http://fake_public/url%d"
226
 
        count = 5
227
 
        events = []
228
 
 
229
 
        for n in range(count):
230
 
            event = aggregator.FilePublishingStatus(public_url % n)
231
 
            events.append(event)
232
 
            event = aggregator.FileUnpublishingStatus(public_url % n)
233
 
            events.append(event)
234
 
 
235
 
        self.status_aggregator.callback(events)
236
 
        lines = [aggregator.FilePublishingStatus.MESSAGE_MANY % count,
237
 
                 aggregator.FileUnpublishingStatus.MESSAGE_MANY % count]
238
 
        message = "\n".join(lines)
239
 
        notification = (aggregator.UBUNTUONE_TITLE, message, None, False)
240
 
        self.assertIn(notification, self.fake_notification.notifications_shown)
 
1009
        self.patch(aggregator, "FileDiscoveryBubble",
 
1010
                   FakeFileDiscoveryBubble)
 
1011
        self.patch(aggregator, "ProgressBubble",
 
1012
                   FakeProgressBubble)
 
1013
        self.patch(aggregator, "FinalStatusBubble",
 
1014
                   FakeFinalBubble)
 
1015
        self.status_frontend = aggregator.StatusFrontend()
 
1016
        self.aggregator = self.status_frontend.aggregator
 
1017
        self.fake_bubble = self.aggregator.file_discovery_bubble
 
1018
 
 
1019
        self.handler = MementoHandler()
 
1020
        self.handler.setLevel(logging.DEBUG)
 
1021
        aggregator.logger.addHandler(self.handler)
 
1022
        aggregator.logger.setLevel(logging.DEBUG)
 
1023
        self.addCleanup(aggregator.logger.removeHandler, self.handler)
 
1024
        self.addCleanup(self.aggregator.progress_bar.cleanup)
 
1025
 
 
1026
    def assertStatusReset(self):
 
1027
        """Assert that the status is at zero."""
 
1028
        self.assertEqual(self.aggregator.download_total, 0)
 
1029
        self.assertEqual(self.aggregator.download_done, 0)
 
1030
        self.assertEqual(self.aggregator.upload_total, 0)
 
1031
        self.assertEqual(self.aggregator.upload_done, 0)
 
1032
        self.assertEqual(self.aggregator.total_counter, 0)
 
1033
        self.assertEqual(self.aggregator.done_counter, 0)
 
1034
        self.assertEqual(len(self.aggregator.files_uploading), 0)
 
1035
        self.assertEqual(len(self.aggregator.files_downloading), 0)
 
1036
 
 
1037
    def assertMiscCommandQueued(self, fc):
 
1038
        """Assert that some command was queued."""
 
1039
        self.assertEqual(self.aggregator.total_counter, 1)
 
1040
        message = "queueing command (0/1): %s" % fc.__class__.__name__
 
1041
        self.assertTrue(self.handler.check_debug(message))
 
1042
        self.assertTrue(self.aggregator.progress_bar.visible)
 
1043
 
 
1044
    def assertMiscCommandUnqueued(self, fc):
 
1045
        """Assert that some command was unqueued."""
 
1046
        self.assertEqual(self.aggregator.done_counter, 1)
 
1047
        message = "unqueueing command (1/1): %s" % fc.__class__.__name__
 
1048
        self.assertTrue(self.handler.check_debug(message))
 
1049
 
 
1050
    def test_counters_start_at_zero(self):
 
1051
        """Test that the counters start at zero."""
 
1052
        self.assertStatusReset()
 
1053
 
 
1054
    def test_misc_command_queue(self):
 
1055
        """Test that a misc command was queued."""
 
1056
        fc = FakeCommand()
 
1057
        self.status_frontend.queue_added(fc)
 
1058
        self.assertMiscCommandQueued(fc)
 
1059
        self.assertEqual(0, self.aggregator.progress_bar.percentage)
 
1060
 
 
1061
    def test_misc_command_unqueue(self):
 
1062
        """Test that a misc command was unqueued."""
 
1063
        fc = FakeCommand()
 
1064
        self.status_frontend.queue_added(fc)
 
1065
        self.status_frontend.queue_removed(fc)
 
1066
        self.assertMiscCommandUnqueued(fc)
 
1067
        self.assertEqual(100.0, self.aggregator.progress_bar.percentage)
 
1068
 
 
1069
    def test_file_download_started(self):
 
1070
        """Test that a file has started download."""
 
1071
        fc = FakeCommand()
 
1072
        self.status_frontend.download_started(fc)
 
1073
        self.assertEqual(self.aggregator.download_total, 1)
 
1074
        self.assertMiscCommandQueued(fc)
 
1075
        self.assertEqual(1, self.fake_bubble.count)
 
1076
 
 
1077
    def test_file_download_finished(self):
 
1078
        """Test that a file has finished downloading."""
 
1079
        fc = FakeCommand()
 
1080
        self.status_frontend.download_started(fc)
 
1081
        self.status_frontend.download_finished(fc)
 
1082
        self.assertEqual(self.aggregator.download_done, 1)
 
1083
        self.assertMiscCommandUnqueued(fc)
 
1084
 
 
1085
    def test_file_upload_started(self):
 
1086
        """Test that a file has started upload."""
 
1087
        fc = FakeCommand()
 
1088
        self.status_frontend.upload_started(fc)
 
1089
        self.assertEqual(self.aggregator.upload_total, 1)
 
1090
        self.assertMiscCommandQueued(fc)
 
1091
        self.assertEqual(1, self.fake_bubble.count)
 
1092
 
 
1093
    def test_file_upload_finished(self):
 
1094
        """Test that a file has finished uploading."""
 
1095
        fc = FakeCommand()
 
1096
        self.status_frontend.upload_started(fc)
 
1097
        self.status_frontend.upload_finished(fc)
 
1098
        self.assertEqual(self.aggregator.upload_done, 1)
 
1099
        self.assertMiscCommandUnqueued(fc)
 
1100
 
 
1101
    def test_get_discovery_message(self):
 
1102
        """Test the message that's shown on the discovery bubble."""
 
1103
        uploading = 10
 
1104
        downloading = 8
 
1105
        self.aggregator.files_uploading.extend(range(uploading))
 
1106
        self.aggregator.files_downloading.extend(range(downloading))
 
1107
        expected = (aggregator.FILES_UPLOADING % uploading + "\n" +
 
1108
                    aggregator.FILES_DOWNLOADING % downloading)
 
1109
        result = self.aggregator.get_discovery_message()
 
1110
        self.assertEqual(expected, result)
 
1111
 
 
1112
    def test_get_progress_message(self):
 
1113
        """Test the message that's shown on the progress bubble."""
 
1114
        self.aggregator.upload_done = 5
 
1115
        self.aggregator.upload_total = 10
 
1116
        self.aggregator.download_done = 3
 
1117
        self.aggregator.download_total = 8
 
1118
        self.aggregator.done_counter = 9
 
1119
        self.aggregator.total_counter = 20
 
1120
        expected = (aggregator.PROGRESS_UPLOADED % (5, 10) + " " +
 
1121
                    aggregator.PROGRESS_DOWNLOADED % (3, 8) + " " +
 
1122
                    aggregator.PROGRESS_COMPLETED % int(100.0 * 9 / 20))
 
1123
        result = self.aggregator.get_progress_message()
 
1124
        self.assertEqual(expected, result)
 
1125
 
 
1126
    def test_get_progress_message_no_uploads(self):
 
1127
        """The progress message when no uploads are going on."""
 
1128
        self.aggregator.upload_done = 0
 
1129
        self.aggregator.upload_total = 0
 
1130
        self.aggregator.download_done = 3
 
1131
        self.aggregator.download_total = 8
 
1132
        self.aggregator.done_counter = 9
 
1133
        self.aggregator.total_counter = 20
 
1134
        expected = (aggregator.PROGRESS_DOWNLOADED % (3, 8) + " " +
 
1135
                    aggregator.PROGRESS_COMPLETED % int(100.0 * 9 / 20))
 
1136
        result = self.aggregator.get_progress_message()
 
1137
        self.assertEqual(expected, result)
 
1138
 
 
1139
    def test_get_progress_message_no_downloads(self):
 
1140
        """The progress message when no downloads are going on."""
 
1141
        self.aggregator.upload_done = 5
 
1142
        self.aggregator.upload_total = 10
 
1143
        self.aggregator.download_done = 0
 
1144
        self.aggregator.download_total = 0
 
1145
        self.aggregator.done_counter = 9
 
1146
        self.aggregator.total_counter = 20
 
1147
        expected = (aggregator.PROGRESS_UPLOADED % (5, 10) + " " +
 
1148
                    aggregator.PROGRESS_COMPLETED % int(100.0 * 9 / 20))
 
1149
        result = self.aggregator.get_progress_message()
 
1150
        self.assertEqual(expected, result)
 
1151
 
 
1152
    def test_get_progress_message_no_total(self):
 
1153
        """No progress message possible if total counter is zero."""
 
1154
        self.aggregator.total_counter = 0
 
1155
        self.assertRaises(AssertionError, self.aggregator.get_progress_message)
 
1156
 
 
1157
    def test_get_final_status_message(self):
 
1158
        """The final status message."""
 
1159
        done = (5, 10)
 
1160
        self.aggregator.upload_done, self.aggregator.download_done = done
 
1161
        expected = (aggregator.FINAL_COMPLETED + "\n" +
 
1162
                    aggregator.FINAL_UPLOADED % done[0] + "\n" +
 
1163
                    aggregator.FINAL_DOWNLOADED % done[1])
 
1164
        result = self.aggregator.get_final_status_message()
 
1165
        self.assertEqual(expected, result)
 
1166
 
 
1167
    def test_get_final_status_message_no_uploads(self):
 
1168
        """The final status message when there were no uploads."""
 
1169
        done = (0, 12)
 
1170
        self.aggregator.upload_done, self.aggregator.download_done = done
 
1171
        expected = (aggregator.FINAL_COMPLETED + "\n" +
 
1172
                    aggregator.FINAL_DOWNLOADED % done[1])
 
1173
        result = self.aggregator.get_final_status_message()
 
1174
        self.assertEqual(expected, result)
 
1175
 
 
1176
    def test_get_final_status_message_no_downloads(self):
 
1177
        """The final status message when there were no downloads."""
 
1178
        done = (8, 0)
 
1179
        self.aggregator.upload_done, self.aggregator.download_done = done
 
1180
        expected = (aggregator.FINAL_COMPLETED + "\n" +
 
1181
                    aggregator.FINAL_UPLOADED % done[0])
 
1182
        result = self.aggregator.get_final_status_message()
 
1183
        self.assertEqual(expected, result)
 
1184
 
 
1185
    def test_started_progress_bubble(self):
 
1186
        """The progress bubble is started."""
 
1187
        self.aggregator.restart_progress_bubble()
 
1188
        self.assertTrue(self.aggregator.progress_bubble.started)
 
1189
 
 
1190
    def test_queue_done(self):
 
1191
        """On queue done, show final bubble and reset counters."""
 
1192
        fc = FakeCommand()
 
1193
        self.status_frontend.upload_started(fc)
 
1194
        old_final_bubble = self.aggregator.final_status_bubble
 
1195
        self.aggregator.queue_done()
 
1196
        self.assertTrue(old_final_bubble.shown)
 
1197
        self.assertStatusReset()
 
1198
        self.assertEqual(0.0, self.aggregator.progress_bar.percentage)
 
1199
        self.assertFalse(self.aggregator.progress_bar.visible)
 
1200
 
 
1201
 
 
1202
class StatusGrouperTestCase(TestCase):
 
1203
    """Tests for the group_statuses function."""
 
1204
 
 
1205
    def test_group_status(self):
 
1206
        """The status grouper sorts and groups by weight."""
 
1207
        status99 = FakeStatus(99)
 
1208
        statuses = [
 
1209
            status99,
 
1210
            status99,
 
1211
            FakeStatus(12),
 
1212
            FakeStatus(1),
 
1213
        ]
 
1214
 
 
1215
        result = [ list(k) for j, k in aggregator.group_statuses(statuses) ]
 
1216
        expected = [
 
1217
            [statuses[3]],
 
1218
            [statuses[2]],
 
1219
            [status99, status99],
 
1220
        ]
 
1221
 
 
1222
        self.assertEqual(result, expected)
 
1223
 
 
1224
 
 
1225
class HundredFeetTestCase(TestCase):
 
1226
    """Try to make all parts work together."""
 
1227
 
 
1228
    def test_all_together_now(self):
 
1229
        """Make all parts work together."""
 
1230
        self.patch(aggregator, "Notification", FakeNotificationSingleton())
 
1231
        clock = PatchedClock()
 
1232
        upload = FakeCommand()
 
1233
        sf = aggregator.StatusFrontend(clock=clock)
 
1234
        clock.advance(aggregator.ProgressBubble.sleep_delay)
 
1235
        # the progress bar is not visible yet
 
1236
        self.assertFalse(sf.aggregator.progress_bar.visible)
 
1237
        sf.upload_started(upload)
 
1238
        # the progress bar is now shown
 
1239
        self.assertTrue(sf.aggregator.progress_bar.visible)
 
1240
        notifications_shown = (sf.aggregator.file_discovery_bubble.
 
1241
                                             notification.notifications_shown)
 
1242
        # no notifications shown yet
 
1243
        self.assertEqual(0, len(notifications_shown))
 
1244
        clock.advance(aggregator.FileDiscoveryGatheringState.initial_delay)
 
1245
        # files found notification
 
1246
        self.assertEqual(1, len(notifications_shown))
 
1247
        download = FakeCommand()
 
1248
        sf.download_started(download)
 
1249
        self.assertEqual(1, len(notifications_shown))
 
1250
        # the progress still is zero
 
1251
        self.assertEqual(0.0, sf.aggregator.progress_bar.percentage)
 
1252
        clock.advance(aggregator.FileDiscoveryUpdateState.updates_delay)
 
1253
        # files count update
 
1254
        self.assertEqual(2, len(notifications_shown))
 
1255
        clock.advance(aggregator.FileDiscoveryUpdateState.updates_timeout -
 
1256
                      aggregator.FileDiscoveryUpdateState.updates_delay)
 
1257
        sf.upload_finished(upload)
 
1258
        # the progress bubble has no notifications yet
 
1259
        self.assertEqual(None, sf.aggregator.progress_bubble.notification)
 
1260
        clock.advance(aggregator.ProgressBubble.sleep_delay)
 
1261
        # the progress bubble is shown
 
1262
        self.assertEqual(3, len(notifications_shown))
 
1263
        sf.upload_finished(download)
 
1264
        # the progress still is now 100%
 
1265
        self.assertEqual(100.0, sf.aggregator.progress_bar.percentage)
 
1266
        # progress, but the progress bubble is not updated immediately
 
1267
        self.assertEqual(3, len(notifications_shown))
 
1268
        clock.advance(aggregator.ProgressBubble.sleep_delay)
 
1269
        # the progress bubble is updated after a delay
 
1270
        self.assertEqual(4, len(notifications_shown))
 
1271
        sf.queue_done()
 
1272
        # the final bubble is shown immediately
 
1273
        self.assertEqual(5, len(notifications_shown))
 
1274
        clock.advance(aggregator.ProgressBubble.sleep_delay * 2)
 
1275
        # no more notifications are shown
 
1276
        self.assertEqual(5, len(notifications_shown))