~bzr-pqm/bzr/bzr.dev

2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
1
# Copyright (C) 2006, 2007 Canonical Ltd
1551.2.27 by Aaron Bentley
Got propogation under test
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
17
import os
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
18
from StringIO import StringIO
19
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
20
from bzrlib import errors
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
21
from bzrlib.progress import (
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
22
        DummyProgress,
23
        ChildProgress,
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
24
        TTYProgressBar,
25
        DotsProgressBar,
26
        ProgressBarStack,
27
        )
1551.2.27 by Aaron Bentley
Got propogation under test
28
from bzrlib.tests import TestCase
29
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
30
1551.2.29 by Aaron Bentley
Got stack handling under test
31
class FakeStack:
32
    def __init__(self, top):
33
        self.__top = top
34
35
    def top(self):
36
        return self.__top
37
1793.1.1 by Aaron Bentley
Hide TTYProgressBars unless they last more than 1 second
38
class InstrumentedProgress(TTYProgressBar):
39
    """TTYProgress variant that tracks outcomes"""
40
41
    def __init__(self, *args, **kwargs):
42
        self.always_throttled = True
43
        TTYProgressBar.__init__(self, *args, **kwargs)
44
45
    def throttle(self, old_message):
46
        result = TTYProgressBar.throttle(self, old_message)
47
        if result is False:
48
            self.always_throttled = False
49
        
50
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
51
class _TTYStringIO(StringIO):
52
    """A helper class which makes a StringIO look like a terminal"""
53
54
    def isatty(self):
55
        return True
56
57
58
class _NonTTYStringIO(StringIO):
59
    """Helper that implements isatty() but returns False"""
60
61
    def isatty(self):
62
        return False
63
64
1551.2.27 by Aaron Bentley
Got propogation under test
65
class TestProgress(TestCase):
66
    def setUp(self):
1551.2.29 by Aaron Bentley
Got stack handling under test
67
        q = DummyProgress()
68
        self.top = ChildProgress(_stack=FakeStack(q))
1551.2.27 by Aaron Bentley
Got propogation under test
69
70
    def test_propogation(self):
71
        self.top.update('foobles', 1, 2)
72
        self.assertEqual(self.top.message, 'foobles')
73
        self.assertEqual(self.top.current, 1)
74
        self.assertEqual(self.top.total, 2)
75
        self.assertEqual(self.top.child_fraction, 0)
1551.2.29 by Aaron Bentley
Got stack handling under test
76
        child = ChildProgress(_stack=FakeStack(self.top))
1551.2.27 by Aaron Bentley
Got propogation under test
77
        child.update('baubles', 2, 4)
78
        self.assertEqual(self.top.message, 'foobles')
79
        self.assertEqual(self.top.current, 1)
80
        self.assertEqual(self.top.total, 2)
81
        self.assertEqual(self.top.child_fraction, 0.5)
1551.2.29 by Aaron Bentley
Got stack handling under test
82
        grandchild = ChildProgress(_stack=FakeStack(child))
1551.2.27 by Aaron Bentley
Got propogation under test
83
        grandchild.update('barbells', 1, 2)
84
        self.assertEqual(self.top.child_fraction, 0.625)
85
        self.assertEqual(child.child_fraction, 0.5)
86
        child.update('baubles', 3, 4)
87
        self.assertEqual(child.child_fraction, 0)
88
        self.assertEqual(self.top.child_fraction, 0.75)
89
        grandchild.update('barbells', 1, 2)
90
        self.assertEqual(self.top.child_fraction, 0.875)
91
        grandchild.update('barbells', 2, 2)
92
        self.assertEqual(self.top.child_fraction, 1)
93
        child.update('baubles', 4, 4)
94
        self.assertEqual(self.top.child_fraction, 1)
95
        #test clamping
96
        grandchild.update('barbells', 2, 2)
97
        self.assertEqual(self.top.child_fraction, 1)
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
98
99
    def test_implementations(self):
100
        for implementation in (TTYProgressBar, DotsProgressBar, 
101
                               DummyProgress):
102
            self.check_parent_handling(implementation)
103
104
    def check_parent_handling(self, parentclass):
105
        top = parentclass(to_file=StringIO())
106
        top.update('foobles', 1, 2)
1551.2.29 by Aaron Bentley
Got stack handling under test
107
        child = ChildProgress(_stack=FakeStack(top))
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
108
        child.update('baubles', 4, 4)
109
        top.update('lala', 2, 2)
110
        child.update('baubles', 4, 4)
1551.2.29 by Aaron Bentley
Got stack handling under test
111
112
    def test_stacking(self):
113
        self.check_stack(TTYProgressBar, ChildProgress)
114
        self.check_stack(DotsProgressBar, ChildProgress)
115
        self.check_stack(DummyProgress, DummyProgress)
116
117
    def check_stack(self, parent_class, child_class):
118
        stack = ProgressBarStack(klass=parent_class, to_file=StringIO())
119
        parent = stack.get_nested()
120
        try:
121
            self.assertIs(parent.__class__, parent_class)
122
            child = stack.get_nested()
123
            try:
124
                self.assertIs(child.__class__, child_class)
125
            finally:
126
                child.finished()
127
        finally:
128
            parent.finished()
1793.1.1 by Aaron Bentley
Hide TTYProgressBars unless they last more than 1 second
129
130
    def test_throttling(self):
131
        pb = InstrumentedProgress(to_file=StringIO())
132
        # instantaneous updates should be squelched
133
        pb.update('me', 1, 1)
134
        self.assertTrue(pb.always_throttled)
135
        pb = InstrumentedProgress(to_file=StringIO())
136
        # It's like an instant sleep(1)!
137
        pb.start_time -= 1
138
        # Updates after a second should not be squelched
139
        pb.update('me', 1, 1)
140
        self.assertFalse(pb.always_throttled)
1843.3.1 by John Arbash Meinel
Don't clear anything if nothing has been written.
141
142
    def test_clear(self):
143
        sio = StringIO()
144
        pb = TTYProgressBar(to_file=sio, show_eta=False)
145
        pb.width = 20 # Just make it easier to test
146
        # This should not output anything
147
        pb.clear()
148
        # These two should not be displayed because
149
        # of throttling
150
        pb.update('foo', 1, 3)
151
        pb.update('bar', 2, 3)
152
        # So pb.clear() has nothing to do
153
        pb.clear()
154
155
        # Make sure the next update isn't throttled
156
        pb.start_time -= 1
157
        pb.update('baz', 3, 3)
158
        pb.clear()
159
160
        self.assertEqual('\r[=========] baz 3/3'
161
                         '\r                   \r',
162
                         sio.getvalue())
1843.3.3 by John Arbash Meinel
Don't let the last_updates list grow without bound.
163
164
    def test_no_eta(self):
165
        # An old version of the progress bar would
166
        # store every update if show_eta was false
167
        # because the eta routine was where it was
168
        # cleaned out
169
        pb = InstrumentedProgress(to_file=StringIO(), show_eta=False)
170
        # Just make sure this first few are throttled
171
        pb.start_time += 5
172
173
        # These messages are throttled, and don't contribute
174
        for count in xrange(100):
175
            pb.update('x', count, 300)
176
        self.assertEqual(0, len(pb.last_updates))
177
178
        # Unthrottle by time
179
        pb.start_time -= 10
180
181
        # These happen too fast, so only one gets through
182
        for count in xrange(100):
183
            pb.update('x', count+100, 200)
184
        self.assertEqual(1, len(pb.last_updates))
185
186
        pb.MIN_PAUSE = 0.0
187
188
        # But all of these go through, don't let the
189
        # last_update list grow without bound
190
        for count in xrange(100):
191
            pb.update('x', count+100, 200)
192
193
        self.assertEqual(pb._max_last_updates, len(pb.last_updates))
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
194
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
195
196
class TestProgressTypes(TestCase):
197
    """Test that the right ProgressBar gets instantiated at the right time."""
198
199
    def get_nested(self, outf, term, env_progress=None):
200
        """Setup so that ProgressBar thinks we are in the supplied terminal."""
201
        orig_term = os.environ.get('TERM')
202
        orig_progress = os.environ.get('BZR_PROGRESS_BAR')
203
        os.environ['TERM'] = term
204
        if env_progress is not None:
205
            os.environ['BZR_PROGRESS_BAR'] = env_progress
206
        elif orig_progress is not None:
207
            del os.environ['BZR_PROGRESS_BAR']
208
209
        def reset():
210
            if orig_term is None:
211
                del os.environ['TERM']
212
            else:
213
                os.environ['TERM'] = orig_term
214
            # We may have never created BZR_PROGRESS_BAR
215
            # So we can't just delete like we can 'TERM' (which is always set)
216
            if orig_progress is None:
217
                if 'BZR_PROGRESS_BAR' in os.environ:
218
                    del os.environ['BZR_PROGRESS_BAR']
219
            else:
220
                os.environ['BZR_PROGRESS_BAR'] = orig_progress
221
222
        self.addCleanup(reset)
223
224
        stack = ProgressBarStack(to_file=outf)
225
        pb = stack.get_nested()
226
        pb.start_time -= 1 # Make sure it is ready to write
227
        pb.width = 20 # And it is of reasonable size
228
        return pb
229
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
230
    def test_tty_progress(self):
231
        # Make sure the ProgressBarStack thinks it is
232
        # writing out to a terminal, and thus uses a TTYProgressBar
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
233
        out = _TTYStringIO()
234
        pb = self.get_nested(out, 'xterm')
235
        self.assertIsInstance(pb, TTYProgressBar)
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
236
        try:
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
237
            pb.update('foo', 1, 2)
238
            pb.update('bar', 2, 2)
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
239
        finally:
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
240
            pb.finished()
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
241
242
        self.assertEqual('\r/ [====   ] foo 1/2'
243
                         '\r- [=======] bar 2/2'
244
                         '\r                   \r',
245
                         out.getvalue())
246
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
247
    def test_noninteractive_progress(self):
248
        out = _NonTTYStringIO()
249
        pb = self.get_nested(out, 'xterm')
250
        self.assertIsInstance(pb, DummyProgress)
251
        try:
252
            pb.update('foo', 1, 2)
253
            pb.update('bar', 2, 2)
254
        finally:
255
            pb.finished()
256
        self.assertEqual('', out.getvalue())
257
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
258
    def test_dots_progress(self):
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
259
        # make sure we get the right progress bar when not on a terminal
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
260
        out = _NonTTYStringIO()
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
261
        pb = self.get_nested(out, 'xterm', 'dots')
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
262
        self.assertIsInstance(pb, DotsProgressBar)
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
263
        try:
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
264
            pb.update('foo', 1, 2)
265
            pb.update('bar', 2, 2)
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
266
        finally:
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
267
            pb.finished()
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
268
        self.assertEqual('foo: .'
269
                         '\nbar: .'
270
                         '\n',
271
                         out.getvalue())
272
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
273
    def test_no_isatty_progress(self):
274
        # Make sure ProgressBarStack handles a plain StringIO()
275
        import cStringIO
276
        out = cStringIO.StringIO()
277
        pb = self.get_nested(out, 'xterm')
278
        pb.finished()
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
279
        self.assertIsInstance(pb, DummyProgress)
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
280
1843.3.5 by John Arbash Meinel
Add tests to assert we fall back to DotsProgressBar when appropriate.
281
    def test_dumb_progress(self):
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
282
        # using a terminal that can't do cursor movement
1843.3.6 by John Arbash Meinel
Cleanup tests by using a helper
283
        out = _TTYStringIO()
284
        pb = self.get_nested(out, 'dumb')
285
        pb.finished()
2599.1.1 by Martin Pool
Don't show dots progress indicatiors in noninteractive mode
286
        self.assertIsInstance(pb, DummyProgress)
1843.3.7 by John Arbash Meinel
new env var 'BZR_PROGRESS_BAR' to select the exact progress type
287
288
    def test_progress_env_tty(self):
289
        # The environ variable BZR_PROGRESS_BAR controls what type of
290
        # progress bar we will get, even if it wouldn't usually be that type
291
        import cStringIO
292
293
        # Usually, this would be a DotsProgressBar
294
        out = cStringIO.StringIO()
295
        pb = self.get_nested(out, 'dumb', 'tty')
296
        pb.finished()
297
        # Even though we are not a tty, the env_var will override
298
        self.assertIsInstance(pb, TTYProgressBar)
299
300
    def test_progress_env_none(self):
301
        # Even though we are in a valid tty, no progress
302
        out = _TTYStringIO()
303
        pb = self.get_nested(out, 'xterm', 'none')
304
        pb.finished()
305
        self.assertIsInstance(pb, DummyProgress)
306
307
    def test_progress_env_invalid(self):
308
        out = _TTYStringIO()
309
        self.assertRaises(errors.InvalidProgressBarType, self.get_nested,
310
            out, 'xterm', 'nonexistant')