~bzr-pqm/bzr/bzr.dev

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