~bzr-pqm/bzr/bzr.dev

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