Merge lp:~mbp/bzr/387717-progress-bar-tty into lp:~bzr/bzr/trunk-old
- 387717-progress-bar-tty
- Merge into trunk-old
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~mbp/bzr/387717-progress-bar-tty | ||||
Merge into: | lp:~bzr/bzr/trunk-old | ||||
Diff against target: | 477 lines | ||||
To merge this branch: | bzr merge lp:~mbp/bzr/387717-progress-bar-tty | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Vincent Ladeuil | Needs Fixing | ||
John A Meinel | Needs Fixing | ||
Andrew Bennetts | Approve | ||
Review via email: mp+7669@code.launchpad.net |
This proposal has been superseded by a proposal from 2009-06-22.
Commit message
Description of the change
Martin Pool (mbp) wrote : | # |
Andrew Bennetts (spiv) wrote : | # |
> + # TODO: deprecate passing ui_factory
It would be good to actually do this TODO :)
It shouldn't be hard to do.
> 145 + 'TERM': 'dumb',
> 146 + 'LINES': '25',
> 147 + 'COLUMNS': '80',
(Idle thought... I wonder if the cost of constructing that dictionary every test is a measureable cost on the total test suite run, especially as we keep adding things to it?)
Part of me wonders if it would be better to just set these variables on TestTextProgres
> + task = self.make_
:)
> 248 + self.assertEqual(
> 249 + r'[####| ] a:b:c 1/2'
> 250 + , view._render_
I think adding a assertRendering method might reduce boilerplate a little, but it's not a big issue given there's only a few test methods. (And it's only moved code anyway, rather than new code.)
Matthew Fuller (fullermd) wrote : | # |
> * Progress bars are now suppressed again when the environment variable
> + ``BZR_PROGRESS_
> + a terminal.
> + (Martin Pool, #339385, #387717)
They're suppressed when stderr *IS* directed to a terminal? 8-}
--
Matthew Fuller (MF4839) | <email address hidden>
Systems/Network Administrator | http://
On the Internet, nobody can hear you scream.
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Martin Pool wrote:
> Martin Pool has proposed merging lp:~mbp/bzr/387717-progress-bar-tty into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This stops progress bars being sent to stderr when it's not a file.
>
> (That's not quite the point of bug 387717, but it's still useful.)
>
> There are some more changes in here: ProgressTasks now talk directly to their ProgressView rather than to the UIFactory which makes them more easily testable in isolation, and may let us remove some forwarding methods in the UIFactory. (The factory still constructs the task so it can arrange to have notifications sent where ever it wants.)
>
> Some classes whose constructor is deprecated now have comments pointing this out.
>
> LINES, TERM and COLUMNS are set to specific values when running tests to reduce variation.
>
...
@@ -82,7 +91,9 @@
self.msg = ''
+ # TODO: deprecate passing ui_factory
+ self.progress_view = progress_view
^- Why not just deprecate it?
if ui_factory is not None:
symbol_
'Passing ui_factory' % symbol_
' Pass progress_view instead')
or are there still bits that use the ui_factory without going to
progress_view?
...
+# NOTE: This is also deprecated; you should provide a ProgressView instead.
class _BaseProgressBa
Same here, just wrap __init__:
@deprecate
def __init__(self,
Alternatively if you still need to do it from time to time, we've used a
flag like "_internal=False" and then:
if not _internal:
warn...
So other than
1) Deprecating things with comments rather than deprecated_in()
2)
* Progress bars are now suppressed again when the environment variable
- - ``BZR_PROGRESS_
- - (Martin Pool, #339385)
+ ``BZR_PROGRESS_
+ a terminal.
+ (Martin Pool, #339385, #387717)
^- And this having the wrong meaning.
I approve, but it needs fixing. I would vote "tweak" but I don't know
how to do that.
Review Approve
review: needs_fixing
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAko
BMgAoICd00+
=Kpqi
-----END PGP SIGNATURE-----
Vincent Ladeuil (vila) wrote : | # |
>>>>> "martin" == Martin Pool <email address hidden> writes:
martin> Martin Pool has proposed merging
martin> lp:~mbp/bzr/387717-progress-bar-tty into lp:bzr.
martin> Requested reviews:
martin> bzr-core (bzr-core)
martin> This stops progress bars being sent to stderr when
martin> it's not a file.
martin> (That's not quite the point of bug 387717, but it's still
martin> useful.)
martin> There are some more changes in here: ProgressTasks now talk
martin> directly to their ProgressView rather than to the UIFactory which
martin> makes them more easily testable in isolation,
martin> and may let us remove some forwarding methods in the
martin> UIFactory.
Eerk, that's where bzr-gtk is plugged in: it uses
_progress_
private methods, bad vila, no sugar :).
martin> (The factory still constructs the task so it can
martin> arrange to have notifications sent where ever it
martin> wants.)
At least make UIFactory use a progress_view class attribute or
define _make_progress_view in UIFactory, not in TextUIFactory and
make it public.
Additional bonus points if you can extract the reusable parts
From TextProgressView ;-D
martin> Some classes whose constructor is deprecated now have
martin> comments pointing this out.
Hardly discoverable I'm afraid...
I know the deprecation dance is painful, but bzr-gtk is currently
claiming compatibility with bzr 1.6 -> 1.13 and 1.15, if you
remove the forwarding methods above, that would become quite a
challenge. So at least these methods will have to be deprecated
properly (I don't really care about the classes you refer to
here).
<snip/>
martin> === modified file 'NEWS'
martin> --- NEWS 2009-06-17 05:05:28 +0000
martin> +++ NEWS 2009-06-19 08:49:14 +0000
martin> @@ -21,8 +21,9 @@
martin> (Neil Martinsen-Burrell, #220067)
martin> * Progress bars are now suppressed again when the environment
martin> variable
martin> - ``BZR_PROGRESS_
martin> - (Martin Pool, #339385)
martin> + ``BZR_PROGRESS_
Who ate that 'not' ? :)
<snip/>
martin> @@ -71,7 +70,8 @@
martin> self._progress_
martin> def _make_progress_
martin> - if os.environ.
martin> + if (os.environ.
martin> + and progress.
martin> return TextProgressVie
martin> else:
martin> return NullProgressView()
Could we please stop outsmarting the user ? ;-D
Please do:
def _make_progress_
- if (os.environ.
+ if os.environ.
+ return TextProgressVie
+ elif (os.environ.
I assure you I dig hard, but there is no way to reliably detect
where the output is going when bzr is run under emacs, i...
Martin Pool (mbp) wrote : | # |
2009/6/19 fullermd <email address hidden>:
>> * Progress bars are now suppressed again when the environment variable
>> + ``BZR_PROGRESS_
>> + a terminal.
>> + (Martin Pool, #339385, #387717)
>
> They're suppressed when stderr *IS* directed to a terminal? 8-}
Good catch, it was backwards.
--
Martin <http://
Martin Pool (mbp) wrote : | # |
2009/6/20 John A Meinel <email address hidden>:
> @@ -82,7 +91,9 @@
> self.total_cnt = None
> self.current_cnt = None
> self.msg = ''
> + # TODO: deprecate passing ui_factory
> self.ui_factory = ui_factory
> + self.progress_view = progress_view
> self.show_pct = False
> self.show_spinner = True
> self.show_eta = False,
>
>
> ^- Why not just deprecate it?
>
> if ui_factory is not None:
> symbol_
> 'Passing ui_factory' % symbol_
> ' Pass progress_view instead')
>
> or are there still bits that use the ui_factory without going to
> progress_view?
I though there might be some nontrivial work to deprecate it and to
look into how that would impact bzr-gtk, so I wanted to land this
first. (And from Vincent's comments it looks like that's true.)
That is the kind of change I'll put in when it's ready.
>
> ...
>
> +# NOTE: This is also deprecated; you should provide a ProgressView instead.
> class _BaseProgressBa
>
> Same here, just wrap __init__:
>
> @deprecated_
> def __init__(self,
>
> Alternatively if you still need to do it from time to time, we've used a
> flag like "_internal=False" and then:
>
> if not _internal:
> warn...
All the public concrete constructors are deprecated. You're right I
could and maybe should add an _internal flag so that any callers other
than those subclasses will warn.
>
>
> So other than
>
> 1) Deprecating things with comments rather than deprecated_in()
My cover letter wasn't very clear: these things are already actually
deprecated, it's just that the deprecation marks the constructor not
the class and I wanted it to be more obvious.
> I approve, but it needs fixing. I would vote "tweak" but I don't know
> how to do that.
There's a bug asking for a 'tweak' status. -
https:/
--
Martin <http://
Martin Pool (mbp) wrote : | # |
2009/6/20 Vincent Ladeuil <email address hidden>:
> Review: Needs Fixing
>
>>>>>> "martin" == Martin Pool <email address hidden> writes:
>
> martin> Martin Pool has proposed merging
> martin> lp:~mbp/bzr/387717-progress-bar-tty into lp:bzr.
>
> martin> Requested reviews:
> martin> bzr-core (bzr-core)
>
> martin> This stops progress bars being sent to stderr when
> martin> it's not a file.
>
>
> martin> (That's not quite the point of bug 387717, but it's still
> martin> useful.)
>
> martin> There are some more changes in here: ProgressTasks now talk
> martin> directly to their ProgressView rather than to the UIFactory which
> martin> makes them more easily testable in isolation,
>
> martin> and may let us remove some forwarding methods in the
> martin> UIFactory.
>
> Eerk, that's where bzr-gtk is plugged in: it uses
> _progress_
> private methods, bad vila, no sugar :).
This is why I have some doubt about our past approach of saying
private interfaces can be changed without warning - if people need to
access something, they'll do it regardless of the name and then we
break them. Therefore I think the naming should be just as a guide to
use that particular class, and we need to make the assessment more
broadly.
At any rate I have not actually removed those methods. I'll have a
look at bzr-gtk.
> martin> (The factory still constructs the task so it can
> martin> arrange to have notifications sent where ever it
> martin> wants.)
>
> At least make UIFactory use a progress_view class attribute or
> define _make_progress_view in UIFactory, not in TextUIFactory and
> make it public.
>
> Additional bonus points if you can extract the reusable parts
> >From TextProgressView ;-D
Good ideas.
Though I'll need to think about whether the public should be able to
ask the UIFactory for a ProgressView as opposed to asking it for a
progress bar already bound to a view. How the views get hooked up is
possibly part of the UI's policy...
> martin> Some classes whose constructor is deprecated now have
> martin> comments pointing this out.
>
> Hardly discoverable I'm afraid...
>
> I know the deprecation dance is painful, but bzr-gtk is currently
> claiming compatibility with bzr 1.6 -> 1.13 and 1.15, if you
> remove the forwarding methods above, that would become quite a
> challenge. So at least these methods will have to be deprecated
> properly (I don't really care about the classes you refer to
> here).
I didn't deprecate anything, I just added comments on classes whose
constructor is deprecated.
> martin> def _make_progress_
> martin> - if os.environ.
> martin> + if (os.environ.
> martin> + and progress.
> martin> return TextProgressVie
> martin> else:
> martin> return NullProgressView()
>
>
> Could we please stop outsmarting the user ? ;-D
>
> Please do:
>
> def _make_progress_
> - if (os.environ.
Martin Pool (mbp) wrote : | # |
In bzr-gtk I see:
if getattr(
# We'are using our own ui, let's tell it to use our widget.
so it can redirect the progress display into the relevant window, and
then it uses _progress_updated and _progress_
them.
So I don't think my patch will break this at all.
If we did deprecate the things I mentioned deprecating, then bzr-gtk
would need to (at least with later bzrlibs) return its own
ProgressView that points to the right progress bar. I don't think it
will be traumatic.
I was in passing thinking about letting it use the activity
notifications to update a throbber and/or to run a nested gtk main
loop.
Vincent Ladeuil (vila) wrote : | # |
>>>>> "martin" == Martin Pool <email address hidden> writes:
<snip/>
>> martin> and may let us remove some forwarding methods in the
>> martin> UIFactory.
>>
>> Eerk, that's where bzr-gtk is plugged in: it uses
>> _progress_
>> private methods, bad vila, no sugar :).
martin> This is why I have some doubt about our past approach
martin> of saying private interfaces can be changed without
martin> warning - if people need to access something, they'll
martin> do it regardless of the name and then we break them.
Right. I think our approach about deprecation is sound.
Regarding plugins that use private methods because they couldn't
find a way to achieve something with the public ones, well, we
can't predict *all* needs. But we may encourage people to
*report* what private methods they use so that we can either make
them public or explain how to avoid using them.
<snip/>
martin> At any rate I have not actually removed those
martin> methods.
Sure, but you said you will :)
<snip/>
>> At least make UIFactory use a progress_view class attribute or
>> define _make_progress_view in UIFactory, not in TextUIFactory and
>> make it public.
>>
>> Additional bonus points if you can extract the reusable parts
>> >From TextProgressView ;-D
martin> Good ideas.
Great.
martin> Though I'll need to think about whether the public
martin> should be able to ask the UIFactory for a
martin> ProgressView as opposed to asking it for a progress
martin> bar already bound to a view.
As I understand your design, there are no more progress bars,
there are progress tasks, a stack of them is maintained
transparently, and the whole stack uses a single report view (I
call it widget).
martin> How the views get hooked up is possibly part of the
martin> UI's policy...
Definitely.
There is still a problem specific to bzr-gtk (at least I couldn't
a figure a way bzrlib itself could address it).
Right now, bzr-gtk use a single UIFactory instance (and that's
the design encouraged by bzrlib). From there, the progress widget
is unique too (since UIFactory forwards to it).
Now, imagine that 'bzr viz' starts 'bzr gblame' and that each of
them use a report widget... at one point the progress will
reported by the wrong widget.
Again, I anticipate, it hasn't been encountered yet.
Just a limitation I wanted you to be aware of.
The way that could be addressed in bzr-gtk is to set the progress
widget before each call to bzrlib that requires it (as long as
the bzrlib calls are issued by objects that know, or have a way
to know, which progress widget they should use, that shouldn't be
a too heavy constraint).
And if no more progress widgets are around, bzr-gtk falls back to
creating a top-level window for that purpose.
The following threads give more details:
https:/
https:/
>> martin> Some classes whose constructor is deprecated now have
>> martin> comments pointing this out.
>>
>> Ha...
Vincent Ladeuil (vila) wrote : | # |
>>>>> "martin" == Martin Pool <email address hidden> writes:
<snip/>
>> martin> and may let us remove some forwarding methods in the
>> martin> UIFactory.
>>
>> Eerk, that's where bzr-gtk is plugged in: it uses
>> _progress_
>> private methods, bad vila, no sugar :).
martin> This is why I have some doubt about our past approach
martin> of saying private interfaces can be changed without
martin> warning - if people need to access something, they'll
martin> do it regardless of the name and then we break them.
Right. I think our approach about deprecation is sound.
Regarding plugins that use private methods because they couldn't
find a way to achieve something with the public ones, well, we
can't predict *all* needs. But we may encourage people to
*report* what private methods they use so that we can either make
them public or explain how to avoid using them.
<snip/>
martin> At any rate I have not actually removed those
martin> methods.
Sure, but you said you will :)
<snip/>
>> At least make UIFactory use a progress_view class attribute or
>> define _make_progress_view in UIFactory, not in TextUIFactory and
>> make it public.
>>
>> Additional bonus points if you can extract the reusable parts
>> >From TextProgressView ;-D
martin> Good ideas.
Great.
martin> Though I'll need to think about whether the public
martin> should be able to ask the UIFactory for a
martin> ProgressView as opposed to asking it for a progress
martin> bar already bound to a view.
As I understand your design, there are no more progress bars,
there are progress tasks, a stack of them is maintained
transparently, and the whole stack uses a single report view (I
call it widget).
martin> How the views get hooked up is possibly part of the
martin> UI's policy...
Definitely.
There is still a problem specific to bzr-gtk (at least I couldn't
a figure a way bzrlib itself could address it).
Right now, bzr-gtk use a single UIFactory instance (and that's
the design encouraged by bzrlib). From there, the progress widget
is unique too (since UIFactory forwards to it).
Now, imagine that 'bzr viz' starts 'bzr gblame' and that each of
them use a report widget... at one point the progress will
reported by the wrong widget.
Again, I anticipate, it hasn't been encountered yet.
Just a limitation I wanted you to be aware of.
The way that could be addressed in bzr-gtk is to set the progress
widget before each call to bzrlib that requires it (as long as
the bzrlib calls are issued by objects that know, or have a way
to know, which progress widget they should use, that shouldn't be
a too heavy constraint).
And if no more progress widgets are around, bzr-gtk falls back to
creating a top-level window for that purpose.
The following threads give more details:
https:/
https:/
>> martin> Some classes whose constructor is deprecated now have
>> martin> comments pointing this out.
>>
>> Ha...
Vincent Ladeuil (vila) wrote : | # |
>>>>> "martin" == Martin Pool <email address hidden> writes:
martin> In bzr-gtk I see:
martin> if getattr(
martin> # We'are using our own ui, let's tell it to use our widget.
martin> ui.ui_factory.
martin> so it can redirect the progress display into the relevant window, and
martin> then it uses _progress_updated and _progress_
martin> them.
That's it.
martin> So I don't think my patch will break this at all.
Not yet :-)
martin> If we did deprecate the things I mentioned
martin> deprecating, then bzr-gtk would need to (at least
martin> with later bzrlibs) return its own ProgressView that
martin> points to the right progress bar. I don't think it
martin> will be traumatic.
Agreed.
My point was that it will not be able (as it is now) to support
multiple bzr versions, not a big deal, but one aim of our
deprecation policy is to warn people about what part of their
toolset they should upgrade...
martin> I was in passing thinking about letting it use the
martin> activity notifications to update a throbber and/or to
martin> run a nested gtk main loop.
The GtkProgressBar.
while gtk.events_
Vincent
Martin Pool (mbp) wrote : | # |
2009/6/22 Vincent Ladeuil <email address hidden>:
> martin> If we did deprecate the things I mentioned
> martin> deprecating, then bzr-gtk would need to (at least
> martin> with later bzrlibs) return its own ProgressView that
> martin> points to the right progress bar. I don't think it
> martin> will be traumatic.
>
> Agreed.
>
> My point was that it will not be able (as it is now) to support
> multiple bzr versions, not a big deal, but one aim of our
> deprecation policy is to warn people about what part of their
> toolset they should upgrade...
I'll try to deprecate them soon so that we can have a long warning period.
> martin> I was in passing thinking about letting it use the
> martin> activity notifications to update a throbber and/or to
> martin> run a nested gtk main loop.
>
> The GtkProgressBar.
>
> while gtk.events_
> gtk.main_
>
> Vincent
If it does that from the transport activity notification callback too
it will probably get much more responsive when network IO is running.
--
Martin <http://
Martin Pool (mbp) wrote : | # |
> > + # TODO: deprecate passing ui_factory
>
> It would be good to actually do this TODO :)
>
> It shouldn't be hard to do.
I'll probably do it next or soon, but wanted to keep this review small.
> > 145 + 'TERM': 'dumb',
> > 146 + 'LINES': '25',
> > 147 + 'COLUMNS': '80',
>
> (Idle thought... I wonder if the cost of constructing that dictionary every
> test is a measureable cost on the total test suite run, especially as we keep
> adding things to it?)
>
> Part of me wonders if it would be better to just set these variables on
> TestTextProgres
We have occasionally had errors in other cases, particularly in black-box tests but also in others, due to them inadvertently depending on one of these. In a sense every one of those failures is a chance to think about whether that particular case should be tested across various screen setups, but on the whole I think this is an inefficient way to discover them. Let's instead have a standard environment across all tests and then add variations where we need to.
>
> > + task = self.make_
>
> :)
>
> > 248 + self.assertEqual(
> > 249 + r'[####| ] a:b:c 1/2'
> > 250 + , view._render_
>
> I think adding a assertRendering method might reduce boilerplate a little, but
> it's not a big issue given there's only a few test methods. (And it's only
> moved code anyway, rather than new code.)
Right.
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-06-20 01:53:18 +0000 |
3 | +++ NEWS 2009-06-20 08:35:18 +0000 |
4 | @@ -39,8 +39,9 @@ |
5 | (Ian Clatworthy) |
6 | |
7 | * Progress bars are now suppressed again when the environment variable |
8 | - ``BZR_PROGRESS_BAR`` is set to ``none``. |
9 | - (Martin Pool, #339385) |
10 | + ``BZR_PROGRESS_BAR`` is set to ``none``, or when stderr is directed to |
11 | + a terminal. |
12 | + (Martin Pool, #339385, #387717) |
13 | |
14 | * Unshelve works correctly when multiple zero-length files are present on |
15 | the shelf. (Aaron Bentley, #363444) |
16 | @@ -94,6 +95,10 @@ |
17 | API Changes |
18 | *********** |
19 | |
20 | +* ProgressTasks now prefer to talk direct to their ProgressView not to the |
21 | + UIFactory. |
22 | + (Martin Pool) |
23 | + |
24 | * Removed overspecific error class ``InvalidProgressBarType``. |
25 | (Martin Pool) |
26 | |
27 | |
28 | === modified file 'bzrlib/progress.py' |
29 | --- bzrlib/progress.py 2009-06-10 03:56:49 +0000 |
30 | +++ bzrlib/progress.py 2009-06-20 08:35:18 +0000 |
31 | @@ -42,13 +42,15 @@ |
32 | ) |
33 | |
34 | |
35 | -# XXX: deprecated; can be removed when the ProgressBar factory is removed |
36 | def _supports_progress(f): |
37 | - """Detect if we can use pretty progress bars on the output stream f. |
38 | + """Detect if we can use pretty progress bars on file F. |
39 | |
40 | If this returns true we expect that a human may be looking at that |
41 | output, and that we can repaint a line to update it. |
42 | + |
43 | + This doesn't check the policy for whether we *should* use them. |
44 | """ |
45 | + # XXX: This is a bit redundant with make_ui_for_terminal. |
46 | isatty = getattr(f, 'isatty', None) |
47 | if isatty is None: |
48 | return False |
49 | @@ -71,9 +73,16 @@ |
50 | will not necessarily respect all these fields. |
51 | """ |
52 | |
53 | - def __init__(self, parent_task=None, ui_factory=None): |
54 | + def __init__(self, parent_task=None, ui_factory=None, progress_view=None): |
55 | """Construct a new progress task. |
56 | |
57 | + :param parent_task: Enclosing ProgressTask or None. |
58 | + |
59 | + :param progress_view: ProgressView to display this ProgressTask. |
60 | + |
61 | + :param ui_factory: The UI factory that will display updates; |
62 | + deprecated in favor of passing progress_view directly. |
63 | + |
64 | Normally you should not call this directly but rather through |
65 | `ui_factory.nested_progress_bar`. |
66 | """ |
67 | @@ -82,7 +91,9 @@ |
68 | self.total_cnt = None |
69 | self.current_cnt = None |
70 | self.msg = '' |
71 | + # TODO: deprecate passing ui_factory |
72 | self.ui_factory = ui_factory |
73 | + self.progress_view = progress_view |
74 | self.show_pct = False |
75 | self.show_spinner = True |
76 | self.show_eta = False, |
77 | @@ -101,16 +112,23 @@ |
78 | self.current_cnt = current_cnt |
79 | if total_cnt: |
80 | self.total_cnt = total_cnt |
81 | - self.ui_factory._progress_updated(self) |
82 | + if self.progress_view: |
83 | + self.progress_view.show_progress(self) |
84 | + else: |
85 | + self.ui_factory._progress_updated(self) |
86 | |
87 | def tick(self): |
88 | self.update(self.msg) |
89 | |
90 | def finished(self): |
91 | - self.ui_factory._progress_finished(self) |
92 | + if self.progress_view: |
93 | + self.progress_view.task_finished(self) |
94 | + else: |
95 | + self.ui_factory._progress_finished(self) |
96 | |
97 | def make_sub_task(self): |
98 | - return ProgressTask(self, self.ui_factory) |
99 | + return ProgressTask(self, ui_factory=self.ui_factory, |
100 | + progress_view=self.progress_view) |
101 | |
102 | def _overall_completion_fraction(self, child_fraction=0.0): |
103 | """Return fractional completion of this task and its parents |
104 | @@ -139,7 +157,10 @@ |
105 | |
106 | def clear(self): |
107 | # XXX: shouldn't be here; put it in mutter or the ui instead |
108 | - self.ui_factory.clear_term() |
109 | + if self.progress_view: |
110 | + self.progress_view.clear() |
111 | + else: |
112 | + self.ui_factory.clear_term() |
113 | |
114 | |
115 | @deprecated_function(deprecated_in((1, 16, 0))) |
116 | @@ -166,6 +187,7 @@ |
117 | return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs) |
118 | |
119 | |
120 | +# NOTE: This is also deprecated; you should provide a ProgressView instead. |
121 | class _BaseProgressBar(object): |
122 | |
123 | def __init__(self, |
124 | @@ -453,6 +475,8 @@ |
125 | #self.to_file.flush() |
126 | |
127 | |
128 | + |
129 | +# DEPRECATED |
130 | class ChildProgress(_BaseProgressBar): |
131 | """A progress indicator that pushes its data to the parent""" |
132 | |
133 | |
134 | === modified file 'bzrlib/tests/__init__.py' |
135 | --- bzrlib/tests/__init__.py 2009-06-10 18:58:02 +0000 |
136 | +++ bzrlib/tests/__init__.py 2009-06-20 08:35:18 +0000 |
137 | @@ -1325,6 +1325,13 @@ |
138 | 'BZR_PROGRESS_BAR': None, |
139 | 'BZR_LOG': None, |
140 | 'BZR_PLUGIN_PATH': None, |
141 | + # Make sure that any text ui tests are consistent regardless of |
142 | + # the environment the test case is run in; you may want tests that |
143 | + # test other combinations. 'dumb' is a reasonable guess for tests |
144 | + # going to a pipe or a StringIO. |
145 | + 'TERM': 'dumb', |
146 | + 'LINES': '25', |
147 | + 'COLUMNS': '80', |
148 | # SSH Agent |
149 | 'SSH_AUTH_SOCK': None, |
150 | # Proxies |
151 | |
152 | === modified file 'bzrlib/tests/test_progress.py' |
153 | --- bzrlib/tests/test_progress.py 2009-06-10 03:56:49 +0000 |
154 | +++ bzrlib/tests/test_progress.py 2009-06-20 08:35:19 +0000 |
155 | @@ -14,14 +14,21 @@ |
156 | # along with this program; if not, write to the Free Software |
157 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
158 | |
159 | + |
160 | import os |
161 | from StringIO import StringIO |
162 | |
163 | from bzrlib import errors |
164 | -from bzrlib.tests import TestCase |
165 | +from bzrlib.progress import ( |
166 | + ProgressTask, |
167 | + ) |
168 | from bzrlib.symbol_versioning import ( |
169 | deprecated_in, |
170 | ) |
171 | +from bzrlib.tests import TestCase |
172 | +from bzrlib.ui.text import ( |
173 | + TextProgressView, |
174 | + ) |
175 | |
176 | |
177 | class _TTYStringIO(StringIO): |
178 | @@ -36,3 +43,72 @@ |
179 | |
180 | def isatty(self): |
181 | return False |
182 | + |
183 | + |
184 | +class TestTextProgressView(TestCase): |
185 | + """Tests for text display of progress bars. |
186 | + |
187 | + These try to exercise the progressview independently of its construction, |
188 | + which is arranged by the TextUIFactory. |
189 | + """ |
190 | + # The ProgressTask now connects directly to the ProgressView, so we can |
191 | + # check them independently of the factory or of the determination of what |
192 | + # view to use. |
193 | + |
194 | + def make_view(self): |
195 | + out = StringIO() |
196 | + view = TextProgressView(out) |
197 | + view._width = 80 |
198 | + return out, view |
199 | + |
200 | + def make_task(self, parent_task, view, msg, curr, total): |
201 | + # would normally be done by UIFactory; is done here so that we don't |
202 | + # have to have one. |
203 | + task = ProgressTask(parent_task, progress_view=view) |
204 | + task.msg = msg |
205 | + task.current_cnt = curr |
206 | + task.total_cnt = total |
207 | + return task |
208 | + |
209 | + def test_render_progress_easy(self): |
210 | + """Just one task and one quarter done""" |
211 | + out, view = self.make_view() |
212 | + task = self.make_task(None, view, 'reticulating splines', 5, 20) |
213 | + view.show_progress(task) |
214 | + self.assertEqual( |
215 | +'\r[####/ ] reticulating splines 5/20 \r' |
216 | + , out.getvalue()) |
217 | + |
218 | + def test_render_progress_nested(self): |
219 | + """Tasks proportionally contribute to overall progress""" |
220 | + out, view = self.make_view() |
221 | + task = self.make_task(None, view, 'reticulating splines', 0, 2) |
222 | + task2 = self.make_task(task, view, 'stage2', 1, 2) |
223 | + view.show_progress(task2) |
224 | + # so we're in the first half of the main task, and half way through |
225 | + # that |
226 | + self.assertEqual( |
227 | +r'[####- ] reticulating splines:stage2 1/2' |
228 | + , view._render_line()) |
229 | + # if the nested task is complete, then we're all the way through the |
230 | + # first half of the overall work |
231 | + task2.update('stage2', 2, 2) |
232 | + self.assertEqual( |
233 | +r'[#########\ ] reticulating splines:stage2 2/2' |
234 | + , view._render_line()) |
235 | + |
236 | + def test_render_progress_sub_nested(self): |
237 | + """Intermediate tasks don't mess up calculation.""" |
238 | + out, view = self.make_view() |
239 | + task_a = ProgressTask(None, progress_view=view) |
240 | + task_a.update('a', 0, 2) |
241 | + task_b = ProgressTask(task_a, progress_view=view) |
242 | + task_b.update('b') |
243 | + task_c = ProgressTask(task_b, progress_view=view) |
244 | + task_c.update('c', 1, 2) |
245 | + # the top-level task is in its first half; the middle one has no |
246 | + # progress indication, just a label; and the bottom one is half done, |
247 | + # so the overall fraction is 1/4 |
248 | + self.assertEqual( |
249 | + r'[####| ] a:b:c 1/2' |
250 | + , view._render_line()) |
251 | |
252 | === modified file 'bzrlib/tests/test_ui.py' |
253 | --- bzrlib/tests/test_ui.py 2009-06-17 05:16:48 +0000 |
254 | +++ bzrlib/tests/test_ui.py 2009-06-20 08:35:19 +0000 |
255 | @@ -25,6 +25,9 @@ |
256 | |
257 | import bzrlib |
258 | import bzrlib.errors as errors |
259 | +from bzrlib.progress import ( |
260 | + ProgressTask, |
261 | + ) |
262 | from bzrlib.symbol_versioning import ( |
263 | deprecated_in, |
264 | ) |
265 | @@ -33,7 +36,10 @@ |
266 | TestUIFactory, |
267 | StringIOWrapper, |
268 | ) |
269 | -from bzrlib.tests.test_progress import _TTYStringIO |
270 | +from bzrlib.tests.test_progress import ( |
271 | + _NonTTYStringIO, |
272 | + _TTYStringIO, |
273 | + ) |
274 | from bzrlib.ui import ( |
275 | CLIUIFactory, |
276 | SilentUIFactory, |
277 | @@ -107,22 +113,35 @@ |
278 | def test_progress_construction(self): |
279 | """TextUIFactory constructs the right progress view. |
280 | """ |
281 | + err = _TTYStringIO() |
282 | + os.environ['TERM'] = 'xterm' |
283 | os.environ['BZR_PROGRESS_BAR'] = 'none' |
284 | - self.assertIsInstance(TextUIFactory()._progress_view, |
285 | + self.assertIsInstance(TextUIFactory(stderr=err)._progress_view, |
286 | NullProgressView) |
287 | |
288 | os.environ['BZR_PROGRESS_BAR'] = 'text' |
289 | - self.assertIsInstance(TextUIFactory()._progress_view, |
290 | + self.assertIsInstance(TextUIFactory(stderr=err)._progress_view, |
291 | TextProgressView) |
292 | |
293 | os.environ['BZR_PROGRESS_BAR'] = 'text' |
294 | - self.assertIsInstance(TextUIFactory()._progress_view, |
295 | + self.assertIsInstance(TextUIFactory(stderr=err)._progress_view, |
296 | TextProgressView) |
297 | |
298 | del os.environ['BZR_PROGRESS_BAR'] |
299 | - self.assertIsInstance(TextUIFactory()._progress_view, |
300 | + self.assertIsInstance(TextUIFactory(stderr=err)._progress_view, |
301 | TextProgressView) |
302 | |
303 | + # if not a tty, no progress bars |
304 | + self.assertIsInstance( |
305 | + TextUIFactory(stderr=_NonTTYStringIO())._progress_view, |
306 | + NullProgressView) |
307 | + |
308 | + # if a tty but dumb, no progress bars |
309 | + os.environ['TERM'] = 'dumb' |
310 | + self.assertIsInstance( |
311 | + TextUIFactory(stderr=_TTYStringIO())._progress_view, |
312 | + NullProgressView) |
313 | + |
314 | def test_progress_note(self): |
315 | stderr = StringIO() |
316 | stdout = StringIO() |
317 | @@ -142,14 +161,15 @@ |
318 | pb.finished() |
319 | |
320 | def test_progress_note_clears(self): |
321 | - stderr = StringIO() |
322 | - stdout = StringIO() |
323 | - # The PQM redirects the output to a file, so it |
324 | - # defaults to creating a Dots progress bar. we |
325 | - # need to force it to believe we are a TTY |
326 | + stderr = _TTYStringIO() |
327 | + stdout = _TTYStringIO() |
328 | + # so that we get a TextProgressBar |
329 | + os.environ['TERM'] = 'xterm' |
330 | ui_factory = TextUIFactory( |
331 | stdin=StringIO(''), |
332 | stdout=stdout, stderr=stderr) |
333 | + self.assertIsInstance(ui_factory._progress_view, |
334 | + TextProgressView) |
335 | pb = ui_factory.nested_progress_bar() |
336 | try: |
337 | # Create a progress update that isn't throttled |
338 | @@ -222,6 +242,7 @@ |
339 | def test_text_factory_prompts_and_clears(self): |
340 | # a get_boolean call should clear the pb before prompting |
341 | out = _TTYStringIO() |
342 | + os.environ['TERM'] = 'xterm' |
343 | factory = TextUIFactory(stdin=StringIO("yada\ny\n"), stdout=out, stderr=out) |
344 | pb = factory.nested_progress_bar() |
345 | pb.show_bar = False |
346 | @@ -295,61 +316,3 @@ |
347 | pb.finished() |
348 | |
349 | |
350 | -class TestTextProgressView(TestCase): |
351 | - """Tests for text display of progress bars. |
352 | - """ |
353 | - # XXX: These might be a bit easier to write if the rendering and |
354 | - # state-maintaining parts of TextProgressView were more separate, and if |
355 | - # the progress task called back directly to its own view not to the ui |
356 | - # factory. -- mbp 20090312 |
357 | - |
358 | - def _make_factory(self): |
359 | - out = StringIO() |
360 | - uif = TextUIFactory(stderr=out) |
361 | - uif._progress_view._width = 80 |
362 | - return out, uif |
363 | - |
364 | - def test_render_progress_easy(self): |
365 | - """Just one task and one quarter done""" |
366 | - out, uif = self._make_factory() |
367 | - task = uif.nested_progress_bar() |
368 | - task.update('reticulating splines', 5, 20) |
369 | - self.assertEqual( |
370 | -'\r[####/ ] reticulating splines 5/20 \r' |
371 | - , out.getvalue()) |
372 | - |
373 | - def test_render_progress_nested(self): |
374 | - """Tasks proportionally contribute to overall progress""" |
375 | - out, uif = self._make_factory() |
376 | - task = uif.nested_progress_bar() |
377 | - task.update('reticulating splines', 0, 2) |
378 | - task2 = uif.nested_progress_bar() |
379 | - task2.update('stage2', 1, 2) |
380 | - # so we're in the first half of the main task, and half way through |
381 | - # that |
382 | - self.assertEqual( |
383 | -r'[####\ ] reticulating splines:stage2 1/2' |
384 | - , uif._progress_view._render_line()) |
385 | - # if the nested task is complete, then we're all the way through the |
386 | - # first half of the overall work |
387 | - task2.update('stage2', 2, 2) |
388 | - self.assertEqual( |
389 | -r'[#########| ] reticulating splines:stage2 2/2' |
390 | - , uif._progress_view._render_line()) |
391 | - |
392 | - def test_render_progress_sub_nested(self): |
393 | - """Intermediate tasks don't mess up calculation.""" |
394 | - out, uif = self._make_factory() |
395 | - task_a = uif.nested_progress_bar() |
396 | - task_a.update('a', 0, 2) |
397 | - task_b = uif.nested_progress_bar() |
398 | - task_b.update('b') |
399 | - task_c = uif.nested_progress_bar() |
400 | - task_c.update('c', 1, 2) |
401 | - # the top-level task is in its first half; the middle one has no |
402 | - # progress indication, just a label; and the bottom one is half done, |
403 | - # so the overall fraction is 1/4 |
404 | - self.assertEqual( |
405 | - r'[####| ] a:b:c 1/2' |
406 | - , uif._progress_view._render_line()) |
407 | - |
408 | |
409 | === modified file 'bzrlib/ui/__init__.py' |
410 | --- bzrlib/ui/__init__.py 2009-06-10 03:56:49 +0000 |
411 | +++ bzrlib/ui/__init__.py 2009-06-20 08:35:19 +0000 |
412 | @@ -19,11 +19,21 @@ |
413 | This tells the library how to display things to the user. Through this |
414 | layer different applications can choose the style of UI. |
415 | |
416 | -At the moment this layer is almost trivial: the application can just |
417 | -choose the style of progress bar. |
418 | - |
419 | -Set the ui_factory member to define the behaviour. The default |
420 | -displays no output. |
421 | +Several levels are supported, and you can also register new factories such as |
422 | +for a GUI. |
423 | + |
424 | +UIFactory |
425 | + Abstract base class |
426 | + |
427 | +CLIUIFactory |
428 | + Basic text output, and can read input from the user. |
429 | + |
430 | +TextUIFactory |
431 | + Can also repaint the screen to draw progress bars. |
432 | + |
433 | +SilentUIFactory |
434 | + Produces no output and cannot take any input; useful for programs using |
435 | + bzrlib in batch mode or for programs such as loggerhead. |
436 | """ |
437 | |
438 | import os |
439 | @@ -279,11 +289,15 @@ |
440 | cls = CLIUIFactory |
441 | elif not isatty(): |
442 | cls = CLIUIFactory |
443 | + # The following case also handles Win32 - on that platform $TERM is |
444 | + # typically never set, so the case None is treated as a smart terminal, |
445 | + # not dumb. <https://bugs.launchpad.net/bugs/334808> win32 files do have |
446 | + # isatty methods that return true. |
447 | elif os.environ.get('TERM') in ('dumb', ''): |
448 | # e.g. emacs compile window |
449 | cls = CLIUIFactory |
450 | # User may know better, otherwise default to TextUIFactory |
451 | - if ( os.environ.get('BZR_USE_TEXT_UI', None) is not None |
452 | + if (os.environ.get('BZR_USE_TEXT_UI', None) is not None |
453 | or cls is None): |
454 | from bzrlib.ui.text import TextUIFactory |
455 | cls = TextUIFactory |
456 | |
457 | === modified file 'bzrlib/ui/text.py' |
458 | --- bzrlib/ui/text.py 2009-06-17 05:03:18 +0000 |
459 | +++ bzrlib/ui/text.py 2009-06-20 08:35:19 +0000 |
460 | @@ -15,7 +15,6 @@ |
461 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
462 | |
463 | |
464 | - |
465 | """Text UI, write output to the console. |
466 | """ |
467 | |
468 | @@ -71,7 +70,8 @@ |
469 | self._progress_view.clear() |
470 | |
471 | def _make_progress_view(self): |
472 | - if os.environ.get('BZR_PROGRESS_BAR') in ('text', None, ''): |
473 | + if (os.environ.get('BZR_PROGRESS_BAR') in ('text', None, '') |
474 | + and progress._supports_progress(self.stderr)): |
475 | return TextProgressView(self.stderr) |
476 | else: |
477 | return NullProgressView() |
This stops progress bars being sent to stderr when it's not a file.
(That's not quite the point of bug 387717, but it's still useful.)
There are some more changes in here: ProgressTasks now talk directly to their ProgressView rather than to the UIFactory which makes them more easily testable in isolation, and may let us remove some forwarding methods in the UIFactory. (The factory still constructs the task so it can arrange to have notifications sent where ever it wants.)
Some classes whose constructor is deprecated now have comments pointing this out.
LINES, TERM and COLUMNS are set to specific values when running tests to reduce variation.