~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

  • Committer: John Arbash Meinel
  • Date: 2008-08-18 22:34:21 UTC
  • mto: (3606.5.6 1.6)
  • mto: This revision was merged to the branch mainline in revision 3641.
  • Revision ID: john@arbash-meinel.com-20080818223421-todjny24vj4faj4t
Add tests for the fetching behavior.

The proper parameter passed is 'unordered' add an assert for it, and
fix callers that were passing 'unsorted' instead.
Add tests that we make the right get_record_stream call based
on the value of _fetch_uses_deltas.
Fix the fetch request for signatures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2
 
# Copyright (C) 2005, 2006 Canonical <canonical.com>
3
 
#
4
 
#    This program is free software; you can redistribute it and/or modify
5
 
#    it under the terms of the GNU General Public License as published by
6
 
#    the Free Software Foundation; either version 2 of the License, or
7
 
#    (at your option) any later version.
8
 
#
9
 
#    This program is distributed in the hope that it will be useful,
10
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
#    GNU General Public License for more details.
13
 
#
14
 
#    You should have received a copy of the GNU General Public License
15
 
#    along with this program; if not, write to the Free Software
16
 
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
 
 
18
 
 
19
 
"""Simple text-mode progress indicator.
20
 
 
21
 
To display an indicator, create a ProgressBar object.  Call it,
22
 
passing Progress objects indicating the current state.  When done,
23
 
call clear().
24
 
 
25
 
Progress is suppressed when output is not sent to a terminal, so as
26
 
not to clutter log files.
 
2
# Copyright (C) 2005, 2006 Canonical Ltd
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
 
 
18
 
 
19
"""Progress indicators.
 
20
 
 
21
The usual way to use this is via bzrlib.ui.ui_factory.nested_progress_bar which
 
22
will maintain a ProgressBarStack for you.
 
23
 
 
24
For direct use, the factory ProgressBar will return an auto-detected progress
 
25
bar that should match your terminal type. You can manually create a
 
26
ProgressBarStack too if you need multiple levels of cooperating progress bars.
 
27
Note that bzrlib's internal functions use the ui module, so if you are using
 
28
bzrlib it really is best to use bzrlib.ui.ui_factory.
27
29
"""
28
30
 
29
 
# TODO: should be a global option e.g. --silent that disables progress
30
 
# indicators, preferably without needing to adjust all code that
31
 
# potentially calls them.
32
 
 
33
 
# TODO: If not on a tty perhaps just print '......' for the benefit of IDEs, etc
34
 
 
35
31
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
36
32
# when the rate is unpredictable
37
33
 
38
 
 
39
34
import sys
40
35
import time
41
36
import os
42
 
from collections import deque
43
 
 
44
 
 
45
 
import bzrlib.errors as errors
46
 
from bzrlib.trace import mutter 
 
37
 
 
38
from bzrlib.lazy_import import lazy_import
 
39
lazy_import(globals(), """
 
40
from bzrlib import (
 
41
    errors,
 
42
    )
 
43
""")
 
44
 
 
45
from bzrlib.trace import mutter
47
46
 
48
47
 
49
48
def _supports_progress(f):
50
 
    if not hasattr(f, 'isatty'):
 
49
    """Detect if we can use pretty progress bars on the output stream f.
 
50
 
 
51
    If this returns true we expect that a human may be looking at that 
 
52
    output, and that we can repaint a line to update it.
 
53
    """
 
54
    isatty = getattr(f, 'isatty', None)
 
55
    if isatty is None:
51
56
        return False
52
 
    if not f.isatty():
 
57
    if not isatty():
53
58
        return False
54
59
    if os.environ.get('TERM') == 'dumb':
55
60
        # e.g. emacs compile window
57
62
    return True
58
63
 
59
64
 
 
65
_progress_bar_types = {}
 
66
 
60
67
 
61
68
def ProgressBar(to_file=None, **kwargs):
62
69
    """Abstract factory"""
63
70
    if to_file is None:
64
71
        to_file = sys.stderr
65
 
    if _supports_progress(to_file):
66
 
        return TTYProgressBar(to_file=to_file, **kwargs)
 
72
    requested_bar_type = os.environ.get('BZR_PROGRESS_BAR')
 
73
    # An value of '' or not set reverts to standard processing
 
74
    if requested_bar_type in (None, ''):
 
75
        if _supports_progress(to_file):
 
76
            return TTYProgressBar(to_file=to_file, **kwargs)
 
77
        else:
 
78
            return DummyProgress(to_file=to_file, **kwargs)
67
79
    else:
68
 
        return DotsProgressBar(to_file=to_file, **kwargs)
69
 
    
 
80
        # Minor sanitation to prevent spurious errors
 
81
        requested_bar_type = requested_bar_type.lower().strip()
 
82
        # TODO: jam 20060710 Arguably we shouldn't raise an exception
 
83
        #       but should instead just disable progress bars if we
 
84
        #       don't recognize the type
 
85
        if requested_bar_type not in _progress_bar_types:
 
86
            raise errors.InvalidProgressBarType(requested_bar_type,
 
87
                                                _progress_bar_types.keys())
 
88
        return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
 
89
 
70
90
 
71
91
class ProgressBarStack(object):
72
92
    """A stack of progress bars."""
93
113
        self._show_count = show_count
94
114
        self._to_messages_file = to_messages_file
95
115
        self._stack = []
96
 
        self._klass = klass or TTYProgressBar
 
116
        self._klass = klass or ProgressBar
97
117
 
98
118
    def top(self):
99
119
        if len(self._stack) != 0:
137
157
                 to_file=None,
138
158
                 show_pct=False,
139
159
                 show_spinner=False,
140
 
                 show_eta=True,
 
160
                 show_eta=False,
141
161
                 show_bar=True,
142
162
                 show_count=True,
143
163
                 to_messages_file=None,
160
180
        self._stack = _stack
161
181
        # seed throttler
162
182
        self.MIN_PAUSE = 0.1 # seconds
163
 
        now = time.clock()
 
183
        now = time.time()
164
184
        # starting now
165
185
        self.start_time = now
166
186
        # next update should not throttle
169
189
    def finished(self):
170
190
        """Return this bar to its progress stack."""
171
191
        self.clear()
172
 
        assert self._stack is not None
173
192
        self._stack.return_pb(self)
174
193
 
175
194
    def note(self, fmt_string, *args, **kwargs):
206
225
        return DummyProgress(**kwargs)
207
226
 
208
227
 
 
228
_progress_bar_types['dummy'] = DummyProgress
 
229
_progress_bar_types['none'] = DummyProgress
 
230
 
 
231
 
209
232
class DotsProgressBar(_BaseProgressBar):
210
233
 
211
234
    def __init__(self, **kwargs):
233
256
    def child_update(self, message, current, total):
234
257
        self.tick()
235
258
 
 
259
 
 
260
_progress_bar_types['dots'] = DotsProgressBar
 
261
 
236
262
    
237
263
class TTYProgressBar(_BaseProgressBar):
238
264
    """Progress bar display object.
262
288
        _BaseProgressBar.__init__(self, **kwargs)
263
289
        self.spin_pos = 0
264
290
        self.width = terminal_width()
265
 
        self.start_time = None
266
 
        self.last_updates = deque()
 
291
        self.last_updates = []
 
292
        self._max_last_updates = 10
267
293
        self.child_fraction = 0
 
294
        self._have_output = False
268
295
    
269
 
 
270
 
    def throttle(self):
 
296
    def throttle(self, old_msg):
271
297
        """Return True if the bar was updated too recently"""
272
298
        # time.time consistently takes 40/4000 ms = 0.01 ms.
273
 
        # but every single update to the pb invokes it.
274
 
        # so we use time.clock which takes 20/4000 ms = 0.005ms
275
 
        # on the downside, time.clock() appears to have approximately
276
 
        # 10ms granularity, so we treat a zero-time change as 'throttled.'
277
 
        
278
 
        now = time.clock()
 
299
        # time.clock() is faster, but gives us CPU time, not wall-clock time
 
300
        now = time.time()
 
301
        if self.start_time is not None and (now - self.start_time) < 1:
 
302
            return True
 
303
        if old_msg != self.last_msg:
 
304
            return False
279
305
        interval = now - self.last_update
280
306
        # if interval > 0
281
307
        if interval < self.MIN_PAUSE:
282
308
            return True
283
309
 
284
310
        self.last_updates.append(now - self.last_update)
 
311
        # Don't let the queue grow without bound
 
312
        self.last_updates = self.last_updates[-self._max_last_updates:]
285
313
        self.last_update = now
286
314
        return False
287
315
        
288
316
    def tick(self):
289
 
        self.update(self.last_msg, self.last_cnt, self.last_total, 
 
317
        self.update(self.last_msg, self.last_cnt, self.last_total,
290
318
                    self.child_fraction)
291
319
 
292
320
    def child_update(self, message, current, total):
296
324
                pass
297
325
            elif self.last_cnt + child_fraction <= self.last_total:
298
326
                self.child_fraction = child_fraction
299
 
            else:
300
 
                mutter('not updating child fraction')
301
327
        if self.last_msg is None:
302
328
            self.last_msg = ''
303
329
        self.tick()
304
330
 
305
 
    def update(self, msg, current_cnt=None, total_cnt=None, 
 
331
    def update(self, msg, current_cnt=None, total_cnt=None,
306
332
               child_fraction=0):
307
333
        """Update and redraw progress bar."""
308
334
        if msg is None:
341
367
        # but multiple that by 4000 calls -> starts to cost.
342
368
        # so anything to make this function call faster
343
369
        # will improve base 'diff' time by up to 0.1 seconds.
344
 
        if old_msg == self.last_msg and self.throttle():
 
370
        if self.throttle(old_msg):
345
371
            return
346
372
 
347
373
        if self.show_eta and self.start_time and self.last_total:
400
426
            bar_str = ''
401
427
 
402
428
        m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
403
 
 
404
 
        assert len(m) < self.width
405
 
        self.to_file.write('\r' + m.ljust(self.width - 1))
 
429
        self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
 
430
        self._have_output = True
406
431
        #self.to_file.flush()
407
432
            
408
 
    def clear(self):        
409
 
        self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
 
433
    def clear(self):
 
434
        if self._have_output:
 
435
            self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
 
436
        self._have_output = False
410
437
        #self.to_file.flush()        
411
438
 
412
439
 
 
440
_progress_bar_types['tty'] = TTYProgressBar
 
441
 
 
442
 
413
443
class ChildProgress(_BaseProgressBar):
414
444
    """A progress indicator that pushes its data to the parent"""
415
445
 
423
453
 
424
454
    def update(self, msg, current_cnt=None, total_cnt=None):
425
455
        self.current = current_cnt
426
 
        self.total = total_cnt
 
456
        if total_cnt is not None:
 
457
            self.total = total_cnt
427
458
        self.message = msg
428
459
        self.child_fraction = 0
429
460
        self.tick()
452
483
    def note(self, *args, **kwargs):
453
484
        self.parent.note(*args, **kwargs)
454
485
 
455
 
 
 
486
 
 
487
class InstrumentedProgress(TTYProgressBar):
 
488
    """TTYProgress variant that tracks outcomes"""
 
489
 
 
490
    def __init__(self, *args, **kwargs):
 
491
        self.always_throttled = True
 
492
        self.never_throttle = False
 
493
        TTYProgressBar.__init__(self, *args, **kwargs)
 
494
 
 
495
    def throttle(self, old_message):
 
496
        if self.never_throttle:
 
497
            result =  False
 
498
        else:
 
499
            result = TTYProgressBar.throttle(self, old_message)
 
500
        if result is False:
 
501
            self.always_throttled = False
 
502
 
 
503
 
456
504
def str_tdelta(delt):
457
505
    if delt is None:
458
506
        return "-:--:--"
475
523
    if current > total:
476
524
        return None                     # wtf?
477
525
 
478
 
    elapsed = time.clock() - start_time
 
526
    elapsed = time.time() - start_time
479
527
 
480
528
    if elapsed < 2.0:                   # not enough time to estimate
481
529
        return None
482
530
    
483
531
    total_duration = float(elapsed) * float(total) / float(current)
484
532
 
485
 
    assert total_duration >= elapsed
486
 
 
487
533
    if last_updates and len(last_updates) >= n_recent:
488
 
        while len(last_updates) > n_recent:
489
 
            last_updates.popleft()
490
534
        avg = sum(last_updates) / float(len(last_updates))
491
535
        time_left = avg * (total - current)
492
536
 
512
556
            self.cur_phase = 0
513
557
        else:
514
558
            self.cur_phase += 1
515
 
        assert self.cur_phase < self.total 
516
559
        self.pb.update(self.message, self.cur_phase, self.total)
517
 
 
518
 
 
519
 
def run_tests():
520
 
    import doctest
521
 
    result = doctest.testmod()
522
 
    if result[1] > 0:
523
 
        if result[0] == 0:
524
 
            print "All tests passed"
525
 
    else:
526
 
        print "No tests to run"
527
 
 
528
 
 
529
 
def demo():
530
 
    sleep = time.sleep
531
 
    
532
 
    print 'dumb-terminal test:'
533
 
    pb = DotsProgressBar()
534
 
    for i in range(100):
535
 
        pb.update('Leoparden', i, 99)
536
 
        sleep(0.1)
537
 
    sleep(1.5)
538
 
    pb.clear()
539
 
    sleep(1.5)
540
 
    
541
 
    print 'smart-terminal test:'
542
 
    pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
543
 
    for i in range(100):
544
 
        pb.update('Elephanten', i, 99)
545
 
        sleep(0.1)
546
 
    sleep(2)
547
 
    pb.clear()
548
 
    sleep(1)
549
 
 
550
 
    print 'done!'
551
 
 
552
 
if __name__ == "__main__":
553
 
    demo()