Merge lp:~mbp/bzr/387717-progress-bar-tty into lp:~bzr/bzr/trunk-old
- 387717-progress-bar-tty
- Merge into trunk-old
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Martin Pool | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp:~mbp/bzr/387717-progress-bar-tty | ||||
Merge into: | lp:~bzr/bzr/trunk-old | ||||
Diff against target: | 1426 lines | ||||
To merge this branch: | bzr merge lp:~mbp/bzr/387717-progress-bar-tty | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pool | Approve | ||
Review via email: mp+9012@code.launchpad.net |
This proposal supersedes a proposal from 2009-06-22.
Commit message
Description of the change
Martin Pool (mbp) wrote : Posted in a previous version of this proposal | # |
Andrew Bennetts (spiv) wrote : Posted in a previous version of this proposal | # |
> + # 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 : Posted in a previous version of this proposal | # |
> * 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 : Posted in a previous version of this proposal | # |
-----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 : Posted in a previous version of this proposal | # |
>>>>> "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 : Posted in a previous version of this proposal | # |
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 : Posted in a previous version of this proposal | # |
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 : Posted in a previous version of this proposal | # |
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 : Posted in a previous version of this proposal | # |
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 : Posted in a previous version of this proposal | # |
>>>>> "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 : Posted in a previous version of this proposal | # |
>>>>> "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 : Posted in a previous version of this proposal | # |
>>>>> "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 : Posted in a previous version of this proposal | # |
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 : Posted in a previous version of this proposal | # |
> > + # 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.
Martin Pool (mbp) wrote : Posted in a previous version of this proposal | # |
Here's an updated version of this patch. The main functional change is to implement Vincent's suggestion that user settings of environment variables should have effect regardless of what would be autodetected.
Enhance assertIsInstance so you can pass extra explanation.
SilentUI now clearly fails when asked for input rather than looking like it would read from stdin. (I don't think that was ever used or very useful.)
The deprecated bar_type parameter to TextUI was removed.
CLIUIFactory is deprecated and most of the code is fused with TextUIFactory; there's some commentary there on why:
+# Previously, there was CLIUIFactory for dumb terminals, and TextUIFactory for
+# those on smarter terminals. However, there are actually a few independent
+# variables so simple subclassing doesn't make sense. The user may or may not
+# want progress bars, but even if they want them off they may want a rich
+# interface in other ways. And on the other hand input may be redirected from
+# a file so stdin is not a terminal, but we should still try to read input
+# from it and display progress bars etc. Therefore if we're doing a text UI
+# at all, we just use TextUIFactory and it turns some features on or off
+# depending on the detected settings and capabilities.
+#
+# We also use this factory for most blackbox tests, but typically with non-tty
+# streams and TERM=dumb. For api tests, SilentUIFactory is probably
+# appropriate.
+#
+# GUIs may actually choose to subclass TextUIFactory, so unimplemented methods
+# fall back to working through the terminal.
+#
+# SilentUIFactory used to read input from stdin, but not print anything, which
+# now seems like an unhelpful combination. So it will now just fail if asked
+# for input.
+
Martin Pool (mbp) wrote : Posted in a previous version of this proposal | # |
I discovered at least one failure with this change, which is
^^^^[log from bzrlib.
-------
Traceback (most recent call last):
File "/home/
master.
File "/home/
thing_
File "/home/
self.
File "/home/
self.
File "/home/
if bzrlib.
File "/home/
raise NotImplementedE
NotImplementedE
Martin Pool (mbp) wrote : Posted in a previous version of this proposal | # |
> I discovered at least one failure with this change, which is
Therefore needs-fixing.
Martin Pool (mbp) wrote : | # |
Another attempt at this branch, originally reviewed here: https:/
More could be done here including more model/view separation, but this is a large enough patch for now.
Martin Pool (mbp) wrote : | # |
John asked in the original review
====
@@ -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?
=====
Because there are still a few bits like doing pb.note that need to be detangled first; they can't so clearly be seen as operations only on the model of the progress task.
Martin Pool (mbp) wrote : | # |
Here's the incremental patch (in two parts, to cut out a merge from trunk):
=== modified file 'NEWS'
--- NEWS 2009-06-19 08:49:14 +0000
+++ NEWS 2009-06-22 10:11:31 +0000
@@ -20,9 +20,11 @@
* Better message in ``bzr split`` error suggesting a rich root format.
(Neil Martinsen-Burrell, #220067)
-* Progress bars are now suppressed again when the environment variable
- ``BZR_PROGRESS_
- a terminal.
+* The environment variable ``BZR_PROGRESS_
+ always forces progress bars either on or off respectively. Otherwise,
+ they're turned on if ``TERM`` is not ``dumb`` and stderr is a terminal.
+ bzr always uses the 'text' user interface when run as a command, so
+ ``BZR_USE_TEXT_UI`` is no longer needed.
(Martin Pool, #339385, #387717)
Internals
@@ -58,6 +60,12 @@
API Changes
***********
+* ``CLIUIFactory`` is deprecated; use ``TextUIFactory`` instead if you
+ need to subclass or create a specific class, or better yet the existing
+ ``make_
+ defined to raise errors when asked to read input, rather than taking it
+ from stdin. (Martin Pool)
+
* ProgressTasks now prefer to talk direct to their ProgressView not to the
UIFactory.
(Martin Pool)
=== modified file 'bzrlib/
--- bzrlib/progress.py 2009-06-19 08:37:24 +0000
+++ bzrlib/progress.py 2009-06-22 10:27:55 +0000
@@ -50,12 +50,15 @@
This doesn't check the policy for whether we *should* use them.
"""
- # XXX: This is a bit redundant with make_ui_
isatty = getattr(f, 'isatty', None)
if isatty is None:
return False
if not isatty():
return False
+ # The following case also handles Win32 - on that platform $TERM is
+ # typically never set, so the case None is treated as a smart terminal,
+ # not dumb. <https:/
+ # isatty methods that return true.
if os.environ.
# e.g. emacs compile window
return False
=== modified file 'bzrlib/
--- bzrlib/
+++ bzrlib/
@@ -102,6 +102,7 @@
from bzrlib.
+from bzrlib.ui.text import TextUIFactory
import bzrlib.
from bzrlib.workingtree import WorkingTree, WorkingTreeFormat2
@@ -702,7 +703,7 @@
return setattr(
-class TestUIFactory(
+class TestUIFactory(
"""A UI Factory for testing.
Hide the progress bar but emit note()s.
@@ -1093,11 +1094,17 @@
- def assertIsInstanc
- """Fail if obj is not an instance of kls"""
+ def assertIsInstanc
+ """Fail if obj is not an instance of kls
+ ...
Martin Pool (mbp) wrote : | # |
Robert did a review on irc, and I've done the points, therefore now submitting this.
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-07-22 01:36:42 +0000 |
3 | +++ NEWS 2009-07-22 08:35:19 +0000 |
4 | @@ -30,6 +30,13 @@ |
5 | * BranchBuilder now accepts timezone to avoid test failures in countries far |
6 | from GMT. (Vincent Ladeuil, #397716) |
7 | |
8 | +* The environment variable ``BZR_PROGRESS_BAR`` set to either ``text`` or ``none`` |
9 | + always forces progress bars either on or off respectively. Otherwise, |
10 | + they're turned on if ``TERM`` is not ``dumb`` and stderr is a terminal. |
11 | + bzr always uses the 'text' user interface when run as a command, so |
12 | + ``BZR_USE_TEXT_UI`` is no longer needed. |
13 | + (Martin Pool, #339385, #387717) |
14 | + |
15 | * ``bzr commit`` no longer saves the unversioning of missing files until |
16 | the commit has completed on the branch. This means that aborting a |
17 | commit that found a missing file will leave the tree unedited. |
18 | @@ -74,9 +81,20 @@ |
19 | API Changes |
20 | *********** |
21 | |
22 | +* ``CLIUIFactory`` is deprecated; use ``TextUIFactory`` instead if you |
23 | + need to subclass or create a specific class, or better yet the existing |
24 | + ``make_ui_for_terminal``. ``SilentUIFactory`` is clarified to do no |
25 | + user interaction at all, rather than trying to read from stdin but not |
26 | + writing any output, which would be strange if reading prompts or |
27 | + passwords. (Martin Pool) |
28 | + |
29 | * New TransformPreview.commit() allows committing without a working tree. |
30 | (Aaron Bentley) |
31 | |
32 | +* ProgressTasks now prefer to talk direct to their ProgressView not to the |
33 | + UIFactory. |
34 | + (Martin Pool) |
35 | + |
36 | Internals |
37 | ********* |
38 | |
39 | |
40 | === modified file 'bzrlib/progress.py' |
41 | --- bzrlib/progress.py 2009-07-01 10:54:51 +0000 |
42 | +++ bzrlib/progress.py 2009-07-22 08:35:19 +0000 |
43 | @@ -37,18 +37,23 @@ |
44 | ) |
45 | |
46 | |
47 | -# XXX: deprecated; can be removed when the ProgressBar factory is removed |
48 | def _supports_progress(f): |
49 | - """Detect if we can use pretty progress bars on the output stream f. |
50 | + """Detect if we can use pretty progress bars on file F. |
51 | |
52 | If this returns true we expect that a human may be looking at that |
53 | output, and that we can repaint a line to update it. |
54 | + |
55 | + This doesn't check the policy for whether we *should* use them. |
56 | """ |
57 | isatty = getattr(f, 'isatty', None) |
58 | if isatty is None: |
59 | return False |
60 | if not isatty(): |
61 | return False |
62 | + # The following case also handles Win32 - on that platform $TERM is |
63 | + # typically never set, so the case None is treated as a smart terminal, |
64 | + # not dumb. <https://bugs.launchpad.net/bugs/334808> win32 files do have |
65 | + # isatty methods that return true. |
66 | if os.environ.get('TERM') == 'dumb': |
67 | # e.g. emacs compile window |
68 | return False |
69 | @@ -66,9 +71,16 @@ |
70 | will not necessarily respect all these fields. |
71 | """ |
72 | |
73 | - def __init__(self, parent_task=None, ui_factory=None): |
74 | + def __init__(self, parent_task=None, ui_factory=None, progress_view=None): |
75 | """Construct a new progress task. |
76 | |
77 | + :param parent_task: Enclosing ProgressTask or None. |
78 | + |
79 | + :param progress_view: ProgressView to display this ProgressTask. |
80 | + |
81 | + :param ui_factory: The UI factory that will display updates; |
82 | + deprecated in favor of passing progress_view directly. |
83 | + |
84 | Normally you should not call this directly but rather through |
85 | `ui_factory.nested_progress_bar`. |
86 | """ |
87 | @@ -77,7 +89,9 @@ |
88 | self.total_cnt = None |
89 | self.current_cnt = None |
90 | self.msg = '' |
91 | + # TODO: deprecate passing ui_factory |
92 | self.ui_factory = ui_factory |
93 | + self.progress_view = progress_view |
94 | self.show_pct = False |
95 | self.show_spinner = True |
96 | self.show_eta = False, |
97 | @@ -96,16 +110,23 @@ |
98 | self.current_cnt = current_cnt |
99 | if total_cnt: |
100 | self.total_cnt = total_cnt |
101 | - self.ui_factory._progress_updated(self) |
102 | + if self.progress_view: |
103 | + self.progress_view.show_progress(self) |
104 | + else: |
105 | + self.ui_factory._progress_updated(self) |
106 | |
107 | def tick(self): |
108 | self.update(self.msg) |
109 | |
110 | def finished(self): |
111 | - self.ui_factory._progress_finished(self) |
112 | + if self.progress_view: |
113 | + self.progress_view.task_finished(self) |
114 | + else: |
115 | + self.ui_factory._progress_finished(self) |
116 | |
117 | def make_sub_task(self): |
118 | - return ProgressTask(self, self.ui_factory) |
119 | + return ProgressTask(self, ui_factory=self.ui_factory, |
120 | + progress_view=self.progress_view) |
121 | |
122 | def _overall_completion_fraction(self, child_fraction=0.0): |
123 | """Return fractional completion of this task and its parents |
124 | @@ -134,7 +155,10 @@ |
125 | |
126 | def clear(self): |
127 | # XXX: shouldn't be here; put it in mutter or the ui instead |
128 | - self.ui_factory.clear_term() |
129 | + if self.progress_view: |
130 | + self.progress_view.clear() |
131 | + else: |
132 | + self.ui_factory.clear_term() |
133 | |
134 | |
135 | @deprecated_function(deprecated_in((1, 16, 0))) |
136 | @@ -161,6 +185,7 @@ |
137 | return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs) |
138 | |
139 | |
140 | +# NOTE: This is also deprecated; you should provide a ProgressView instead. |
141 | class _BaseProgressBar(object): |
142 | |
143 | def __init__(self, |
144 | @@ -448,6 +473,8 @@ |
145 | #self.to_file.flush() |
146 | |
147 | |
148 | + |
149 | +# DEPRECATED |
150 | class ChildProgress(_BaseProgressBar): |
151 | """A progress indicator that pushes its data to the parent""" |
152 | |
153 | |
154 | === modified file 'bzrlib/tests/__init__.py' |
155 | --- bzrlib/tests/__init__.py 2009-07-20 04:22:47 +0000 |
156 | +++ bzrlib/tests/__init__.py 2009-07-22 08:35:19 +0000 |
157 | @@ -102,6 +102,7 @@ |
158 | TestLoader, |
159 | ) |
160 | from bzrlib.tests.treeshape import build_tree_contents |
161 | +from bzrlib.ui.text import TextUIFactory |
162 | import bzrlib.version_info_formats.format_custom |
163 | from bzrlib.workingtree import WorkingTree, WorkingTreeFormat2 |
164 | |
165 | @@ -702,7 +703,7 @@ |
166 | return setattr(self._cstring, name, val) |
167 | |
168 | |
169 | -class TestUIFactory(ui.CLIUIFactory): |
170 | +class TestUIFactory(TextUIFactory): |
171 | """A UI Factory for testing. |
172 | |
173 | Hide the progress bar but emit note()s. |
174 | @@ -1093,11 +1094,17 @@ |
175 | osutils.realpath(path2), |
176 | "apparent paths:\na = %s\nb = %s\n," % (path1, path2)) |
177 | |
178 | - def assertIsInstance(self, obj, kls): |
179 | - """Fail if obj is not an instance of kls""" |
180 | + def assertIsInstance(self, obj, kls, msg=None): |
181 | + """Fail if obj is not an instance of kls |
182 | + |
183 | + :param msg: Supplementary message to show if the assertion fails. |
184 | + """ |
185 | if not isinstance(obj, kls): |
186 | - self.fail("%r is an instance of %s rather than %s" % ( |
187 | - obj, obj.__class__, kls)) |
188 | + m = "%r is an instance of %s rather than %s" % ( |
189 | + obj, obj.__class__, kls) |
190 | + if msg: |
191 | + m += ": " + msg |
192 | + self.fail(m) |
193 | |
194 | def expectFailure(self, reason, assertion, *args, **kwargs): |
195 | """Invoke a test, expecting it to fail for the given reason. |
196 | @@ -1325,6 +1332,13 @@ |
197 | 'BZR_PROGRESS_BAR': None, |
198 | 'BZR_LOG': None, |
199 | 'BZR_PLUGIN_PATH': None, |
200 | + # Make sure that any text ui tests are consistent regardless of |
201 | + # the environment the test case is run in; you may want tests that |
202 | + # test other combinations. 'dumb' is a reasonable guess for tests |
203 | + # going to a pipe or a StringIO. |
204 | + 'TERM': 'dumb', |
205 | + 'LINES': '25', |
206 | + 'COLUMNS': '80', |
207 | # SSH Agent |
208 | 'SSH_AUTH_SOCK': None, |
209 | # Proxies |
210 | |
211 | === modified file 'bzrlib/tests/per_branch/test_break_lock.py' |
212 | --- bzrlib/tests/per_branch/test_break_lock.py 2009-07-10 05:49:34 +0000 |
213 | +++ bzrlib/tests/per_branch/test_break_lock.py 2009-07-22 08:35:20 +0000 |
214 | @@ -1,4 +1,4 @@ |
215 | -# Copyright (C) 2006 Canonical Ltd |
216 | +# Copyright (C) 2006, 2009 Canonical Ltd |
217 | # |
218 | # This program is free software; you can redistribute it and/or modify |
219 | # it under the terms of the GNU General Public License as published by |
220 | @@ -22,6 +22,9 @@ |
221 | import bzrlib.errors as errors |
222 | from bzrlib.tests import TestCase, TestCaseWithTransport, TestNotApplicable |
223 | from bzrlib.tests.per_branch.test_branch import TestCaseWithBranch |
224 | +from bzrlib.ui import ( |
225 | + CannedInputUIFactory, |
226 | + ) |
227 | |
228 | |
229 | class TestBreakLock(TestCaseWithBranch): |
230 | @@ -35,7 +38,6 @@ |
231 | # ours |
232 | self.old_factory = bzrlib.ui.ui_factory |
233 | self.addCleanup(self.restoreFactory) |
234 | - bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory() |
235 | |
236 | def restoreFactory(self): |
237 | bzrlib.ui.ui_factory = self.old_factory |
238 | @@ -59,7 +61,7 @@ |
239 | other_instance = self.branch.repository.bzrdir.open_repository() |
240 | if not other_instance.get_physical_lock_status(): |
241 | raise TestNotApplicable("Repository does not lock persistently.") |
242 | - bzrlib.ui.ui_factory.stdin = StringIO("y\n") |
243 | + bzrlib.ui.ui_factory = CannedInputUIFactory([True]) |
244 | try: |
245 | self.unused_branch.break_lock() |
246 | except NotImplementedError: |
247 | @@ -71,7 +73,7 @@ |
248 | def test_locked(self): |
249 | # break_lock when locked should unlock the branch and repo |
250 | self.branch.lock_write() |
251 | - bzrlib.ui.ui_factory.stdin = StringIO("y\ny\n") |
252 | + bzrlib.ui.ui_factory = CannedInputUIFactory([True, True]) |
253 | try: |
254 | self.unused_branch.break_lock() |
255 | except NotImplementedError: |
256 | @@ -90,7 +92,7 @@ |
257 | # this branch does not support binding. |
258 | return |
259 | master.lock_write() |
260 | - bzrlib.ui.ui_factory.stdin = StringIO("y\ny\n") |
261 | + bzrlib.ui.ui_factory = CannedInputUIFactory([True, True]) |
262 | try: |
263 | self.unused_branch.break_lock() |
264 | except NotImplementedError: |
265 | |
266 | === modified file 'bzrlib/tests/per_bzrdir/test_bzrdir.py' |
267 | --- bzrlib/tests/per_bzrdir/test_bzrdir.py 2009-07-10 06:45:04 +0000 |
268 | +++ bzrlib/tests/per_bzrdir/test_bzrdir.py 2009-07-22 08:35:20 +0000 |
269 | @@ -57,6 +57,9 @@ |
270 | from bzrlib.trace import mutter |
271 | from bzrlib.transport import get_transport |
272 | from bzrlib.transport.local import LocalTransport |
273 | +from bzrlib.ui import ( |
274 | + CannedInputUIFactory, |
275 | + ) |
276 | from bzrlib.upgrade import upgrade |
277 | from bzrlib.remote import RemoteBzrDir, RemoteRepository |
278 | from bzrlib.repofmt import weaverepo |
279 | @@ -1774,7 +1777,6 @@ |
280 | # ours |
281 | self.old_factory = bzrlib.ui.ui_factory |
282 | self.addCleanup(self.restoreFactory) |
283 | - bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory() |
284 | |
285 | def restoreFactory(self): |
286 | bzrlib.ui.ui_factory = self.old_factory |
287 | @@ -1800,7 +1802,7 @@ |
288 | return |
289 | # only one yes needed here: it should only be unlocking |
290 | # the repo |
291 | - bzrlib.ui.ui_factory.stdin = StringIO("y\n") |
292 | + bzrlib.ui.ui_factory = CannedInputUIFactory([True]) |
293 | try: |
294 | repo.bzrdir.break_lock() |
295 | except NotImplementedError: |
296 | @@ -1831,17 +1833,19 @@ |
297 | # two yes's : branch and repository. If the repo in this |
298 | # dir is inappropriately accessed, 3 will be needed, and |
299 | # we'll see that because the stream will be fully consumed |
300 | - bzrlib.ui.ui_factory.stdin = StringIO("y\ny\ny\n") |
301 | + bzrlib.ui.ui_factory = CannedInputUIFactory([True, True, True]) |
302 | # determine if the repository will have been locked; |
303 | this_repo_locked = \ |
304 | thisdir.open_repository().get_physical_lock_status() |
305 | master.bzrdir.break_lock() |
306 | if this_repo_locked: |
307 | # only two ys should have been read |
308 | - self.assertEqual("y\n", bzrlib.ui.ui_factory.stdin.read()) |
309 | + self.assertEqual([True], |
310 | + bzrlib.ui.ui_factory.responses) |
311 | else: |
312 | # only one y should have been read |
313 | - self.assertEqual("y\ny\n", bzrlib.ui.ui_factory.stdin.read()) |
314 | + self.assertEqual([True, True], |
315 | + bzrlib.ui.ui_factory.responses) |
316 | # we should be able to lock a newly opened branch now |
317 | branch = master.bzrdir.open_branch() |
318 | branch.lock_write() |
319 | @@ -1865,7 +1869,7 @@ |
320 | tree = self.make_branch_and_tree('.') |
321 | tree.lock_write() |
322 | # three yes's : tree, branch and repository. |
323 | - bzrlib.ui.ui_factory.stdin = StringIO("y\ny\ny\ny\n") |
324 | + bzrlib.ui.ui_factory = CannedInputUIFactory([True, True, True]) |
325 | try: |
326 | tree.bzrdir.break_lock() |
327 | except (NotImplementedError, errors.LockActive): |
328 | @@ -1875,7 +1879,8 @@ |
329 | # object. |
330 | tree.unlock() |
331 | return |
332 | - self.assertEqual("y\n", bzrlib.ui.ui_factory.stdin.read()) |
333 | + self.assertEqual([True], |
334 | + bzrlib.ui.ui_factory.responses) |
335 | lock_tree = tree.bzrdir.open_workingtree() |
336 | lock_tree.lock_write() |
337 | lock_tree.unlock() |
338 | |
339 | === modified file 'bzrlib/tests/per_repository/test_break_lock.py' |
340 | --- bzrlib/tests/per_repository/test_break_lock.py 2009-03-23 14:59:43 +0000 |
341 | +++ bzrlib/tests/per_repository/test_break_lock.py 2009-07-22 08:35:20 +0000 |
342 | @@ -23,6 +23,9 @@ |
343 | from bzrlib.tests.per_repository.test_repository import TestCaseWithRepository |
344 | from bzrlib.transport import get_transport |
345 | from bzrlib.workingtree import WorkingTree |
346 | +from bzrlib.ui import ( |
347 | + CannedInputUIFactory, |
348 | + ) |
349 | |
350 | |
351 | class TestBreakLock(TestCaseWithRepository): |
352 | @@ -36,8 +39,7 @@ |
353 | # ours |
354 | self.old_factory = bzrlib.ui.ui_factory |
355 | self.addCleanup(self.restoreFactory) |
356 | - bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory() |
357 | - bzrlib.ui.ui_factory.stdin = StringIO("y\n") |
358 | + bzrlib.ui.ui_factory = CannedInputUIFactory([True]) |
359 | |
360 | def restoreFactory(self): |
361 | bzrlib.ui.ui_factory = self.old_factory |
362 | |
363 | === modified file 'bzrlib/tests/per_repository_reference/test_break_lock.py' |
364 | --- bzrlib/tests/per_repository_reference/test_break_lock.py 2009-03-23 14:59:43 +0000 |
365 | +++ bzrlib/tests/per_repository_reference/test_break_lock.py 2009-07-22 08:35:20 +0000 |
366 | @@ -21,6 +21,9 @@ |
367 | from bzrlib.tests.per_repository_reference import ( |
368 | TestCaseWithExternalReferenceRepository, |
369 | ) |
370 | +from bzrlib.ui import ( |
371 | + CannedInputUIFactory, |
372 | + ) |
373 | |
374 | |
375 | class TestBreakLock(TestCaseWithExternalReferenceRepository): |
376 | @@ -39,12 +42,8 @@ |
377 | # 'lock_write' has not taken a physical mutex out. |
378 | repo.unlock() |
379 | return |
380 | - # we want a UI factory that accepts canned input for the tests: |
381 | - # while SilentUIFactory still accepts stdin, we need to customise |
382 | - # ours |
383 | self.old_factory = bzrlib.ui.ui_factory |
384 | self.addCleanup(self.restoreFactory) |
385 | - bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory() |
386 | - bzrlib.ui.ui_factory.stdin = StringIO("y\n") |
387 | + bzrlib.ui.ui_factory = CannedInputUIFactory([True]) |
388 | unused_repo.break_lock() |
389 | self.assertRaises(errors.LockBroken, repo.unlock) |
390 | |
391 | === modified file 'bzrlib/tests/per_workingtree/test_break_lock.py' |
392 | --- bzrlib/tests/per_workingtree/test_break_lock.py 2009-07-10 07:14:02 +0000 |
393 | +++ bzrlib/tests/per_workingtree/test_break_lock.py 2009-07-22 08:35:20 +0000 |
394 | @@ -30,12 +30,8 @@ |
395 | super(TestBreakLock, self).setUp() |
396 | self.unused_workingtree = self.make_branch_and_tree('.') |
397 | self.workingtree = self.unused_workingtree.bzrdir.open_workingtree() |
398 | - # we want a UI factory that accepts canned input for the tests: |
399 | - # while SilentUIFactory still accepts stdin, we need to customise |
400 | - # ours |
401 | self.old_factory = bzrlib.ui.ui_factory |
402 | self.addCleanup(self.restoreFactory) |
403 | - bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory() |
404 | |
405 | def restoreFactory(self): |
406 | bzrlib.ui.ui_factory = self.old_factory |
407 | @@ -52,16 +48,14 @@ |
408 | # if the workingtree isn't locked - and the easiest way |
409 | # to see if that happened is to lock the repo. |
410 | self.workingtree.branch.repository.lock_write() |
411 | - stdin = StringIO("y\n") |
412 | - bzrlib.ui.ui_factory.stdin = stdin |
413 | + bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True]) |
414 | try: |
415 | self.unused_workingtree.break_lock() |
416 | except NotImplementedError: |
417 | # workingtree does not support break_lock |
418 | self.workingtree.branch.repository.unlock() |
419 | return |
420 | - remaining = stdin.read() |
421 | - if remaining == 'y\n': |
422 | + if bzrlib.ui.ui_factory.responses == [True]: |
423 | raise TestNotApplicable("repository does not physically lock.") |
424 | self.assertRaises(errors.LockBroken, |
425 | self.workingtree.branch.repository.unlock) |
426 | @@ -69,7 +63,7 @@ |
427 | def test_locked(self): |
428 | # break_lock when locked should |
429 | self.workingtree.lock_write() |
430 | - bzrlib.ui.ui_factory.stdin = StringIO("y\ny\ny\n") |
431 | + bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True, True, True]) |
432 | try: |
433 | self.unused_workingtree.break_lock() |
434 | except (NotImplementedError, errors.LockActive): |
435 | |
436 | === modified file 'bzrlib/tests/test_config.py' |
437 | --- bzrlib/tests/test_config.py 2009-07-02 08:59:16 +0000 |
438 | +++ bzrlib/tests/test_config.py 2009-07-22 08:35:19 +0000 |
439 | @@ -1,5 +1,4 @@ |
440 | -# Copyright (C) 2005, 2006, 2008 Canonical Ltd |
441 | -# Authors: Robert Collins <robert.collins@canonical.com> |
442 | +# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd |
443 | # |
444 | # This program is free software; you can redistribute it and/or modify |
445 | # it under the terms of the GNU General Public License as published by |
446 | @@ -1561,8 +1560,9 @@ |
447 | """)) |
448 | entered_password = 'typed-by-hand' |
449 | stdout = tests.StringIOWrapper() |
450 | + stderr = tests.StringIOWrapper() |
451 | ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n', |
452 | - stdout=stdout) |
453 | + stdout=stdout, stderr=stderr) |
454 | |
455 | # Since the password defined in the authentication config is ignored, |
456 | # the user is prompted |
457 | @@ -1582,8 +1582,10 @@ |
458 | """)) |
459 | entered_password = 'typed-by-hand' |
460 | stdout = tests.StringIOWrapper() |
461 | + stderr = tests.StringIOWrapper() |
462 | ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n', |
463 | - stdout=stdout) |
464 | + stdout=stdout, |
465 | + stderr=stderr) |
466 | |
467 | # Since the password defined in the authentication config is ignored, |
468 | # the user is prompted |
469 | |
470 | === modified file 'bzrlib/tests/test_ftp_transport.py' |
471 | --- bzrlib/tests/test_ftp_transport.py 2009-04-10 15:58:09 +0000 |
472 | +++ bzrlib/tests/test_ftp_transport.py 2009-07-22 08:35:19 +0000 |
473 | @@ -90,27 +90,24 @@ |
474 | configuration.""", |
475 | self.get_server().add_user(getpass.getuser(), self.password) |
476 | t = self.get_transport() |
477 | - ui.ui_factory = tests.TestUIFactory(stdin=self.password + '\n', |
478 | - stdout=tests.StringIOWrapper()) |
479 | + ui.ui_factory = ui.CannedInputUIFactory([self.password]) |
480 | # Issue a request to the server to connect |
481 | t.put_bytes('foo', 'test bytes\n') |
482 | self.assertEqual('test bytes\n', t.get_bytes('foo')) |
483 | # Only the password should've been read |
484 | - self.assertEqual('', ui.ui_factory.stdin.readline()) |
485 | + ui.ui_factory.assert_all_input_consumed() |
486 | |
487 | def test_prompt_for_password(self): |
488 | t = self.get_transport() |
489 | - ui.ui_factory = tests.TestUIFactory(stdin=self.password+'\n', |
490 | - stdout=tests.StringIOWrapper()) |
491 | + ui.ui_factory = ui.CannedInputUIFactory([self.password]) |
492 | # Issue a request to the server to connect |
493 | t.has('whatever/not/existing') |
494 | # stdin should be empty (the provided password have been consumed) |
495 | - self.assertEqual('', ui.ui_factory.stdin.readline()) |
496 | + ui.ui_factory.assert_all_input_consumed() |
497 | |
498 | def test_no_prompt_for_password_when_using_auth_config(self): |
499 | t = self.get_transport() |
500 | - ui.ui_factory = tests.TestUIFactory(stdin='precious\n', |
501 | - stdout=tests.StringIOWrapper()) |
502 | + ui.ui_factory = ui.CannedInputUIFactory([]) |
503 | # Create a config file with the right password |
504 | conf = config.AuthenticationConfig() |
505 | conf._get_config().update({'ftptest': {'scheme': 'ftp', |
506 | @@ -120,8 +117,6 @@ |
507 | # Issue a request to the server to connect |
508 | t.put_bytes('foo', 'test bytes\n') |
509 | self.assertEqual('test bytes\n', t.get_bytes('foo')) |
510 | - # stdin should have been left untouched |
511 | - self.assertEqual('precious\n', ui.ui_factory.stdin.readline()) |
512 | |
513 | def test_empty_password(self): |
514 | # Override the default user/password from setUp |
515 | @@ -129,11 +124,9 @@ |
516 | self.password = '' |
517 | self.get_server().add_user(self.user, self.password) |
518 | t = self.get_transport() |
519 | - ui.ui_factory = tests.TestUIFactory(stdin=self.password+'\n', |
520 | - stdout=tests.StringIOWrapper()) |
521 | + ui.ui_factory = ui.CannedInputUIFactory([self.password]) |
522 | # Issue a request to the server to connect |
523 | t.has('whatever/not/existing') |
524 | # stdin should be empty (the provided password have been consumed), |
525 | # even if the password is empty, it's followed by a newline. |
526 | - self.assertEqual('', ui.ui_factory.stdin.readline()) |
527 | - |
528 | + ui.ui_factory.assert_all_input_consumed() |
529 | |
530 | === modified file 'bzrlib/tests/test_lockable_files.py' |
531 | --- bzrlib/tests/test_lockable_files.py 2009-05-11 07:14:58 +0000 |
532 | +++ bzrlib/tests/test_lockable_files.py 2009-07-22 08:35:19 +0000 |
533 | @@ -157,8 +157,7 @@ |
534 | l2 = self.get_lockable() |
535 | orig_factory = bzrlib.ui.ui_factory |
536 | # silent ui - no need for stdout |
537 | - bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory() |
538 | - bzrlib.ui.ui_factory.stdin = StringIO("y\n") |
539 | + bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True]) |
540 | try: |
541 | l2.break_lock() |
542 | finally: |
543 | |
544 | === modified file 'bzrlib/tests/test_lockdir.py' |
545 | --- bzrlib/tests/test_lockdir.py 2009-05-08 15:58:00 +0000 |
546 | +++ bzrlib/tests/test_lockdir.py 2009-07-22 08:35:19 +0000 |
547 | @@ -561,9 +561,7 @@ |
548 | # do this without IO redirection to ensure it doesn't prompt. |
549 | self.assertRaises(AssertionError, ld1.break_lock) |
550 | orig_factory = bzrlib.ui.ui_factory |
551 | - # silent ui - no need for stdout |
552 | - bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory() |
553 | - bzrlib.ui.ui_factory.stdin = StringIO("y\n") |
554 | + bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True]) |
555 | try: |
556 | ld2.break_lock() |
557 | self.assertRaises(LockBroken, ld1.unlock) |
558 | |
559 | === modified file 'bzrlib/tests/test_pack_repository.py' |
560 | --- bzrlib/tests/test_pack_repository.py 2009-06-26 09:24:34 +0000 |
561 | +++ bzrlib/tests/test_pack_repository.py 2009-07-22 08:35:20 +0000 |
562 | @@ -517,8 +517,7 @@ |
563 | def restoreFactory(): |
564 | ui.ui_factory = old_factory |
565 | self.addCleanup(restoreFactory) |
566 | - ui.ui_factory = ui.SilentUIFactory() |
567 | - ui.ui_factory.stdin = StringIO("y\n") |
568 | + ui.ui_factory = ui.CannedInputUIFactory([True]) |
569 | |
570 | def test_break_lock_breaks_physical_lock(self): |
571 | repo = self.make_repository('.', format=self.get_format()) |
572 | |
573 | === modified file 'bzrlib/tests/test_progress.py' |
574 | --- bzrlib/tests/test_progress.py 2009-06-10 03:56:49 +0000 |
575 | +++ bzrlib/tests/test_progress.py 2009-07-22 08:35:20 +0000 |
576 | @@ -14,14 +14,21 @@ |
577 | # along with this program; if not, write to the Free Software |
578 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
579 | |
580 | + |
581 | import os |
582 | from StringIO import StringIO |
583 | |
584 | from bzrlib import errors |
585 | -from bzrlib.tests import TestCase |
586 | +from bzrlib.progress import ( |
587 | + ProgressTask, |
588 | + ) |
589 | from bzrlib.symbol_versioning import ( |
590 | deprecated_in, |
591 | ) |
592 | +from bzrlib.tests import TestCase |
593 | +from bzrlib.ui.text import ( |
594 | + TextProgressView, |
595 | + ) |
596 | |
597 | |
598 | class _TTYStringIO(StringIO): |
599 | @@ -36,3 +43,72 @@ |
600 | |
601 | def isatty(self): |
602 | return False |
603 | + |
604 | + |
605 | +class TestTextProgressView(TestCase): |
606 | + """Tests for text display of progress bars. |
607 | + |
608 | + These try to exercise the progressview independently of its construction, |
609 | + which is arranged by the TextUIFactory. |
610 | + """ |
611 | + # The ProgressTask now connects directly to the ProgressView, so we can |
612 | + # check them independently of the factory or of the determination of what |
613 | + # view to use. |
614 | + |
615 | + def make_view(self): |
616 | + out = StringIO() |
617 | + view = TextProgressView(out) |
618 | + view._width = 80 |
619 | + return out, view |
620 | + |
621 | + def make_task(self, parent_task, view, msg, curr, total): |
622 | + # would normally be done by UIFactory; is done here so that we don't |
623 | + # have to have one. |
624 | + task = ProgressTask(parent_task, progress_view=view) |
625 | + task.msg = msg |
626 | + task.current_cnt = curr |
627 | + task.total_cnt = total |
628 | + return task |
629 | + |
630 | + def test_render_progress_easy(self): |
631 | + """Just one task and one quarter done""" |
632 | + out, view = self.make_view() |
633 | + task = self.make_task(None, view, 'reticulating splines', 5, 20) |
634 | + view.show_progress(task) |
635 | + self.assertEqual( |
636 | +'\r[####/ ] reticulating splines 5/20 \r' |
637 | + , out.getvalue()) |
638 | + |
639 | + def test_render_progress_nested(self): |
640 | + """Tasks proportionally contribute to overall progress""" |
641 | + out, view = self.make_view() |
642 | + task = self.make_task(None, view, 'reticulating splines', 0, 2) |
643 | + task2 = self.make_task(task, view, 'stage2', 1, 2) |
644 | + view.show_progress(task2) |
645 | + # so we're in the first half of the main task, and half way through |
646 | + # that |
647 | + self.assertEqual( |
648 | +r'[####- ] reticulating splines:stage2 1/2' |
649 | + , view._render_line()) |
650 | + # if the nested task is complete, then we're all the way through the |
651 | + # first half of the overall work |
652 | + task2.update('stage2', 2, 2) |
653 | + self.assertEqual( |
654 | +r'[#########\ ] reticulating splines:stage2 2/2' |
655 | + , view._render_line()) |
656 | + |
657 | + def test_render_progress_sub_nested(self): |
658 | + """Intermediate tasks don't mess up calculation.""" |
659 | + out, view = self.make_view() |
660 | + task_a = ProgressTask(None, progress_view=view) |
661 | + task_a.update('a', 0, 2) |
662 | + task_b = ProgressTask(task_a, progress_view=view) |
663 | + task_b.update('b') |
664 | + task_c = ProgressTask(task_b, progress_view=view) |
665 | + task_c.update('c', 1, 2) |
666 | + # the top-level task is in its first half; the middle one has no |
667 | + # progress indication, just a label; and the bottom one is half done, |
668 | + # so the overall fraction is 1/4 |
669 | + self.assertEqual( |
670 | + r'[####| ] a:b:c 1/2' |
671 | + , view._render_line()) |
672 | |
673 | === modified file 'bzrlib/tests/test_selftest.py' |
674 | --- bzrlib/tests/test_selftest.py 2009-07-20 04:22:47 +0000 |
675 | +++ bzrlib/tests/test_selftest.py 2009-07-22 08:35:20 +0000 |
676 | @@ -1687,8 +1687,15 @@ |
677 | def test_assert_isinstance(self): |
678 | self.assertIsInstance(2, int) |
679 | self.assertIsInstance(u'', basestring) |
680 | - self.assertRaises(AssertionError, self.assertIsInstance, None, int) |
681 | + e = self.assertRaises(AssertionError, self.assertIsInstance, None, int) |
682 | + self.assertEquals(str(e), |
683 | + "None is an instance of <type 'NoneType'> rather than <type 'int'>") |
684 | self.assertRaises(AssertionError, self.assertIsInstance, 23.3, int) |
685 | + e = self.assertRaises(AssertionError, |
686 | + self.assertIsInstance, None, int, "it's just not") |
687 | + self.assertEquals(str(e), |
688 | + "None is an instance of <type 'NoneType'> rather than <type 'int'>" |
689 | + ": it's just not") |
690 | |
691 | def test_assertEndsWith(self): |
692 | self.assertEndsWith('foo', 'oo') |
693 | |
694 | === modified file 'bzrlib/tests/test_smtp_connection.py' |
695 | --- bzrlib/tests/test_smtp_connection.py 2009-03-25 18:43:31 +0000 |
696 | +++ bzrlib/tests/test_smtp_connection.py 2009-07-22 08:35:20 +0000 |
697 | @@ -1,4 +1,4 @@ |
698 | -# Copyright (C) 2005, 2007 Canonical Ltd |
699 | +# Copyright (C) 2005, 2007, 2009 Canonical Ltd |
700 | # |
701 | # This program is free software; you can redistribute it and/or modify |
702 | # it under the terms of the GNU General Public License as published by |
703 | @@ -136,12 +136,9 @@ |
704 | smtp_factory=factory) |
705 | self.assertIs(None, conn._smtp_password) |
706 | |
707 | - ui.ui_factory = tests.TestUIFactory(stdin=password + '\n', |
708 | - stdout=tests.StringIOWrapper()) |
709 | + ui.ui_factory = ui.CannedInputUIFactory([password]) |
710 | conn._connect() |
711 | self.assertEqual(password, conn._smtp_password) |
712 | - # stdin should be empty (the provided password have been consumed) |
713 | - self.assertEqual('', ui.ui_factory.stdin.readline()) |
714 | |
715 | def test_smtp_password_from_auth_config(self): |
716 | user = 'joe' |
717 | |
718 | === modified file 'bzrlib/tests/test_ui.py' |
719 | --- bzrlib/tests/test_ui.py 2009-07-02 09:09:35 +0000 |
720 | +++ bzrlib/tests/test_ui.py 2009-07-22 08:35:20 +0000 |
721 | @@ -31,7 +31,22 @@ |
722 | from bzrlib.symbol_versioning import ( |
723 | deprecated_in, |
724 | ) |
725 | -from bzrlib.tests.test_progress import _TTYStringIO |
726 | +from bzrlib.tests import ( |
727 | + TestCase, |
728 | + TestUIFactory, |
729 | + StringIOWrapper, |
730 | + ) |
731 | +from bzrlib.tests.test_progress import ( |
732 | + _NonTTYStringIO, |
733 | + _TTYStringIO, |
734 | + ) |
735 | +from bzrlib.ui import ( |
736 | + CannedInputUIFactory, |
737 | + CLIUIFactory, |
738 | + SilentUIFactory, |
739 | + UIFactory, |
740 | + make_ui_for_terminal, |
741 | + ) |
742 | from bzrlib.ui.text import ( |
743 | NullProgressView, |
744 | TextProgressView, |
745 | @@ -41,20 +56,6 @@ |
746 | |
747 | class UITests(tests.TestCase): |
748 | |
749 | - def test_silent_factory(self): |
750 | - ui = _mod_ui.SilentUIFactory() |
751 | - stdout = StringIO() |
752 | - self.assertEqual(None, |
753 | - self.apply_redirected(None, stdout, stdout, |
754 | - ui.get_password)) |
755 | - self.assertEqual('', stdout.getvalue()) |
756 | - self.assertEqual(None, |
757 | - self.apply_redirected(None, stdout, stdout, |
758 | - ui.get_password, |
759 | - u'Hello\u1234 %(user)s', |
760 | - user=u'some\u1234')) |
761 | - self.assertEqual('', stdout.getvalue()) |
762 | - |
763 | def test_text_factory_ascii_password(self): |
764 | ui = tests.TestUIFactory(stdin='secret\n', |
765 | stdout=tests.StringIOWrapper(), |
766 | @@ -102,21 +103,52 @@ |
767 | def test_progress_construction(self): |
768 | """TextUIFactory constructs the right progress view. |
769 | """ |
770 | - os.environ['BZR_PROGRESS_BAR'] = 'none' |
771 | - self.assertIsInstance(TextUIFactory()._progress_view, |
772 | - NullProgressView) |
773 | - |
774 | - os.environ['BZR_PROGRESS_BAR'] = 'text' |
775 | - self.assertIsInstance(TextUIFactory()._progress_view, |
776 | - TextProgressView) |
777 | - |
778 | - os.environ['BZR_PROGRESS_BAR'] = 'text' |
779 | - self.assertIsInstance(TextUIFactory()._progress_view, |
780 | - TextProgressView) |
781 | - |
782 | - del os.environ['BZR_PROGRESS_BAR'] |
783 | - self.assertIsInstance(TextUIFactory()._progress_view, |
784 | - TextProgressView) |
785 | + for (file_class, term, pb, expected_pb_class) in ( |
786 | + # on an xterm, either use them or not as the user requests, |
787 | + # otherwise default on |
788 | + (_TTYStringIO, 'xterm', 'none', NullProgressView), |
789 | + (_TTYStringIO, 'xterm', 'text', TextProgressView), |
790 | + (_TTYStringIO, 'xterm', None, TextProgressView), |
791 | + # on a dumb terminal, again if there's explicit configuration do |
792 | + # it, otherwise default off |
793 | + (_TTYStringIO, 'dumb', 'none', NullProgressView), |
794 | + (_TTYStringIO, 'dumb', 'text', TextProgressView), |
795 | + (_TTYStringIO, 'dumb', None, NullProgressView), |
796 | + # on a non-tty terminal, it's null regardless of $TERM |
797 | + (StringIO, 'xterm', None, NullProgressView), |
798 | + (StringIO, 'dumb', None, NullProgressView), |
799 | + # however, it can still be forced on |
800 | + (StringIO, 'dumb', 'text', TextProgressView), |
801 | + ): |
802 | + os.environ['TERM'] = term |
803 | + if pb is None: |
804 | + if 'BZR_PROGRESS_BAR' in os.environ: |
805 | + del os.environ['BZR_PROGRESS_BAR'] |
806 | + else: |
807 | + os.environ['BZR_PROGRESS_BAR'] = pb |
808 | + stdin = file_class('') |
809 | + stderr = file_class() |
810 | + stdout = file_class() |
811 | + uif = make_ui_for_terminal(stdin, stdout, stderr) |
812 | + self.assertIsInstance(uif, TextUIFactory, |
813 | + "TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,)) |
814 | + self.assertIsInstance(uif.make_progress_view(), |
815 | + expected_pb_class, |
816 | + "TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,)) |
817 | + |
818 | + def test_text_ui_non_terminal(self): |
819 | + """Even on non-ttys, make_ui_for_terminal gives a text ui.""" |
820 | + stdin = _NonTTYStringIO('') |
821 | + stderr = _NonTTYStringIO() |
822 | + stdout = _NonTTYStringIO() |
823 | + for term_type in ['dumb', None, 'xterm']: |
824 | + if term_type is None: |
825 | + del os.environ['TERM'] |
826 | + else: |
827 | + os.environ['TERM'] = term_type |
828 | + uif = make_ui_for_terminal(stdin, stdout, stderr) |
829 | + self.assertIsInstance(uif, TextUIFactory, |
830 | + 'TERM=%r' % (term_type,)) |
831 | |
832 | def test_progress_note(self): |
833 | stderr = StringIO() |
834 | @@ -137,14 +169,15 @@ |
835 | pb.finished() |
836 | |
837 | def test_progress_note_clears(self): |
838 | - stderr = StringIO() |
839 | - stdout = StringIO() |
840 | - # The PQM redirects the output to a file, so it |
841 | - # defaults to creating a Dots progress bar. we |
842 | - # need to force it to believe we are a TTY |
843 | + stderr = _TTYStringIO() |
844 | + stdout = _TTYStringIO() |
845 | + # so that we get a TextProgressBar |
846 | + os.environ['TERM'] = 'xterm' |
847 | ui_factory = TextUIFactory( |
848 | stdin=StringIO(''), |
849 | stdout=stdout, stderr=stderr) |
850 | + self.assertIsInstance(ui_factory._progress_view, |
851 | + TextProgressView) |
852 | pb = ui_factory.nested_progress_bar() |
853 | try: |
854 | # Create a progress update that isn't throttled |
855 | @@ -173,17 +206,17 @@ |
856 | pb2.finished() |
857 | pb1.finished() |
858 | |
859 | - def assert_get_bool_acceptance_of_user_input(self, factory): |
860 | - factory.stdin = StringIO("y\n" # True |
861 | - "n\n" # False |
862 | - "yes with garbage\nY\n" # True |
863 | - "not an answer\nno\n" # False |
864 | - "I'm sure!\nyes\n" # True |
865 | - "NO\n" # False |
866 | - "foo\n") |
867 | - factory.stdout = StringIO() |
868 | - factory.stderr = StringIO() |
869 | - # there is no output from the base factory |
870 | + def test_text_ui_get_boolean(self): |
871 | + stdin = StringIO("y\n" # True |
872 | + "n\n" # False |
873 | + "yes with garbage\nY\n" # True |
874 | + "not an answer\nno\n" # False |
875 | + "I'm sure!\nyes\n" # True |
876 | + "NO\n" # False |
877 | + "foo\n") |
878 | + stdout = StringIO() |
879 | + stderr = StringIO() |
880 | + factory = TextUIFactory(stdin, stdout, stderr) |
881 | self.assertEqual(True, factory.get_boolean("")) |
882 | self.assertEqual(False, factory.get_boolean("")) |
883 | self.assertEqual(True, factory.get_boolean("")) |
884 | @@ -194,28 +227,9 @@ |
885 | # stdin should be empty |
886 | self.assertEqual('', factory.stdin.readline()) |
887 | |
888 | - def test_silent_ui_getbool(self): |
889 | - factory = _mod_ui.SilentUIFactory() |
890 | - self.assert_get_bool_acceptance_of_user_input(factory) |
891 | - |
892 | - def test_silent_factory_prompts_silently(self): |
893 | - factory = _mod_ui.SilentUIFactory() |
894 | - stdout = StringIO() |
895 | - factory.stdin = StringIO("y\n") |
896 | - self.assertEqual(True, |
897 | - self.apply_redirected(None, stdout, stdout, |
898 | - factory.get_boolean, "foo")) |
899 | - self.assertEqual("", stdout.getvalue()) |
900 | - # stdin should be empty |
901 | - self.assertEqual('', factory.stdin.readline()) |
902 | - |
903 | - def test_text_ui_getbool(self): |
904 | - factory = TextUIFactory(None, None, None) |
905 | - self.assert_get_bool_acceptance_of_user_input(factory) |
906 | - |
907 | def test_text_factory_prompt(self): |
908 | # see <https://launchpad.net/bugs/365891> |
909 | - factory = TextUIFactory(None, StringIO(), StringIO(), StringIO()) |
910 | + factory = TextUIFactory(StringIO(), StringIO(), StringIO()) |
911 | factory.prompt('foo %2e') |
912 | self.assertEqual('', factory.stdout.getvalue()) |
913 | self.assertEqual('foo %2e', factory.stderr.getvalue()) |
914 | @@ -223,8 +237,8 @@ |
915 | def test_text_factory_prompts_and_clears(self): |
916 | # a get_boolean call should clear the pb before prompting |
917 | out = _TTYStringIO() |
918 | - factory = TextUIFactory(stdin=StringIO("yada\ny\n"), |
919 | - stdout=out, stderr=out) |
920 | + os.environ['TERM'] = 'xterm' |
921 | + factory = TextUIFactory(stdin=StringIO("yada\ny\n"), stdout=out, stderr=out) |
922 | pb = factory.nested_progress_bar() |
923 | pb.show_bar = False |
924 | pb.show_spinner = False |
925 | @@ -254,17 +268,6 @@ |
926 | finally: |
927 | pb.finished() |
928 | |
929 | - def test_silent_ui_getusername(self): |
930 | - factory = _mod_ui.SilentUIFactory() |
931 | - factory.stdin = StringIO("someuser\n\n") |
932 | - factory.stdout = StringIO() |
933 | - factory.stderr = StringIO() |
934 | - self.assertEquals(None, |
935 | - factory.get_username(u'Hello\u1234 %(host)s', host=u'some\u1234')) |
936 | - self.assertEquals("", factory.stdout.getvalue()) |
937 | - self.assertEquals("", factory.stderr.getvalue()) |
938 | - self.assertEquals("someuser\n\n", factory.stdin.getvalue()) |
939 | - |
940 | def test_text_ui_getusername(self): |
941 | factory = TextUIFactory(None, None, None) |
942 | factory.stdin = StringIO("someuser\n\n") |
943 | @@ -298,63 +301,47 @@ |
944 | pb.finished() |
945 | |
946 | |
947 | -class TestTextProgressView(tests.TestCase): |
948 | - """Tests for text display of progress bars. |
949 | - """ |
950 | - # XXX: These might be a bit easier to write if the rendering and |
951 | - # state-maintaining parts of TextProgressView were more separate, and if |
952 | - # the progress task called back directly to its own view not to the ui |
953 | - # factory. -- mbp 20090312 |
954 | +class CLIUITests(TestCase): |
955 | + |
956 | + def test_cli_factory_deprecated(self): |
957 | + uif = self.applyDeprecated(deprecated_in((1, 18, 0)), |
958 | + CLIUIFactory, |
959 | + StringIO(), StringIO(), StringIO()) |
960 | + self.assertIsInstance(uif, UIFactory) |
961 | + |
962 | + |
963 | +class SilentUITests(TestCase): |
964 | + |
965 | + def test_silent_factory_get_password(self): |
966 | + # A silent factory that can't do user interaction can't get a |
967 | + # password. Possibly it should raise a more specific error but it |
968 | + # can't succeed. |
969 | + ui = SilentUIFactory() |
970 | + stdout = StringIO() |
971 | + self.assertRaises( |
972 | + NotImplementedError, |
973 | + self.apply_redirected, |
974 | + None, stdout, stdout, ui.get_password) |
975 | + # and it didn't write anything out either |
976 | + self.assertEqual('', stdout.getvalue()) |
977 | + |
978 | + def test_silent_ui_getbool(self): |
979 | + factory = SilentUIFactory() |
980 | + stdout = StringIO() |
981 | + self.assertRaises( |
982 | + NotImplementedError, |
983 | + self.apply_redirected, |
984 | + None, stdout, stdout, factory.get_boolean, "foo") |
985 | + |
986 | + |
987 | +class CannedInputUIFactoryTests(TestCase): |
988 | |
989 | - def _make_factory(self): |
990 | - out = StringIO() |
991 | - uif = TextUIFactory(stderr=out) |
992 | - uif._progress_view._width = 80 |
993 | - return out, uif |
994 | - |
995 | - def test_render_progress_easy(self): |
996 | - """Just one task and one quarter done""" |
997 | - out, uif = self._make_factory() |
998 | - task = uif.nested_progress_bar() |
999 | - task.update('reticulating splines', 5, 20) |
1000 | - self.assertEqual( |
1001 | -'\r[####/ ] reticulating splines 5/20 \r' |
1002 | - , out.getvalue()) |
1003 | - |
1004 | - def test_render_progress_nested(self): |
1005 | - """Tasks proportionally contribute to overall progress""" |
1006 | - out, uif = self._make_factory() |
1007 | - task = uif.nested_progress_bar() |
1008 | - task.update('reticulating splines', 0, 2) |
1009 | - task2 = uif.nested_progress_bar() |
1010 | - task2.update('stage2', 1, 2) |
1011 | - # so we're in the first half of the main task, and half way through |
1012 | - # that |
1013 | - self.assertEqual( |
1014 | -r'[####\ ] reticulating splines:stage2 1/2' |
1015 | - , uif._progress_view._render_line()) |
1016 | - # if the nested task is complete, then we're all the way through the |
1017 | - # first half of the overall work |
1018 | - task2.update('stage2', 2, 2) |
1019 | - self.assertEqual( |
1020 | -r'[#########| ] reticulating splines:stage2 2/2' |
1021 | - , uif._progress_view._render_line()) |
1022 | - |
1023 | - def test_render_progress_sub_nested(self): |
1024 | - """Intermediate tasks don't mess up calculation.""" |
1025 | - out, uif = self._make_factory() |
1026 | - task_a = uif.nested_progress_bar() |
1027 | - task_a.update('a', 0, 2) |
1028 | - task_b = uif.nested_progress_bar() |
1029 | - task_b.update('b') |
1030 | - task_c = uif.nested_progress_bar() |
1031 | - task_c.update('c', 1, 2) |
1032 | - # the top-level task is in its first half; the middle one has no |
1033 | - # progress indication, just a label; and the bottom one is half done, |
1034 | - # so the overall fraction is 1/4 |
1035 | - self.assertEqual( |
1036 | - r'[####| ] a:b:c 1/2' |
1037 | - , uif._progress_view._render_line()) |
1038 | + def test_canned_input_get_input(self): |
1039 | + uif = CannedInputUIFactory([True, 'mbp', 'password']) |
1040 | + self.assertEqual(uif.get_boolean('Extra cheese?'), True) |
1041 | + self.assertEqual(uif.get_username('Enter your user name'), 'mbp') |
1042 | + self.assertEqual(uif.get_password('Password for %(host)s', host='example.com'), |
1043 | + 'password') |
1044 | |
1045 | |
1046 | class TestBoolFromString(tests.TestCase): |
1047 | |
1048 | === modified file 'bzrlib/ui/__init__.py' |
1049 | --- bzrlib/ui/__init__.py 2009-07-02 09:09:35 +0000 |
1050 | +++ bzrlib/ui/__init__.py 2009-07-22 08:35:20 +0000 |
1051 | @@ -14,18 +14,34 @@ |
1052 | # along with this program; if not, write to the Free Software |
1053 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
1054 | |
1055 | -"""UI abstraction. |
1056 | - |
1057 | -This tells the library how to display things to the user. Through this |
1058 | -layer different applications can choose the style of UI. |
1059 | - |
1060 | -At the moment this layer is almost trivial: the application can just |
1061 | -choose the style of progress bar. |
1062 | - |
1063 | -Set the ui_factory member to define the behaviour. The default |
1064 | -displays no output. |
1065 | +"""Abstraction for interacting with the user. |
1066 | + |
1067 | +Applications can choose different types of UI, and they deal with displaying |
1068 | +messages or progress to the user, and with gathering different types of input. |
1069 | + |
1070 | +Several levels are supported, and you can also register new factories such as |
1071 | +for a GUI. |
1072 | + |
1073 | +UIFactory |
1074 | + Semi-abstract base class |
1075 | + |
1076 | +SilentUIFactory |
1077 | + Produces no output and cannot take any input; useful for programs using |
1078 | + bzrlib in batch mode or for programs such as loggerhead. |
1079 | + |
1080 | +CannedInputUIFactory |
1081 | + For use in testing; the input values to be returned are provided |
1082 | + at construction. |
1083 | + |
1084 | +TextUIFactory |
1085 | + Standard text command-line interface, with stdin, stdout, stderr. |
1086 | + May make more or less advanced use of them, eg in drawing progress bars, |
1087 | + depending on the detected capabilities of the terminal. |
1088 | + GUIs may choose to subclass this so that unimplemented methods fall |
1089 | + back to working through the terminal. |
1090 | """ |
1091 | |
1092 | + |
1093 | import os |
1094 | import sys |
1095 | import warnings |
1096 | @@ -41,6 +57,11 @@ |
1097 | trace, |
1098 | ) |
1099 | """) |
1100 | +from bzrlib.symbol_versioning import ( |
1101 | + deprecated_function, |
1102 | + deprecated_in, |
1103 | + deprecated_method, |
1104 | + ) |
1105 | |
1106 | |
1107 | _valid_boolean_strings = dict(yes=True, no=False, |
1108 | @@ -158,6 +179,14 @@ |
1109 | """ |
1110 | raise NotImplementedError(self.get_boolean) |
1111 | |
1112 | + def make_progress_view(self): |
1113 | + """Construct a new ProgressView object for this UI. |
1114 | + |
1115 | + Application code should normally not call this but instead |
1116 | + nested_progress_bar(). |
1117 | + """ |
1118 | + return NullProgressView() |
1119 | + |
1120 | def recommend_upgrade(self, |
1121 | current_format_name, |
1122 | basedir): |
1123 | @@ -182,10 +211,9 @@ |
1124 | |
1125 | |
1126 | class CLIUIFactory(UIFactory): |
1127 | - """Common behaviour for command line UI factories. |
1128 | - |
1129 | - This is suitable for dumb terminals that can't repaint existing text.""" |
1130 | - |
1131 | + """Deprecated in favor of TextUIFactory.""" |
1132 | + |
1133 | + @deprecated_method(deprecated_in((1, 18, 0))) |
1134 | def __init__(self, stdin=None, stdout=None, stderr=None): |
1135 | UIFactory.__init__(self) |
1136 | self.stdin = stdin or sys.stdin |
1137 | @@ -271,28 +299,51 @@ |
1138 | self.stdout.write(msg + '\n') |
1139 | |
1140 | |
1141 | -class SilentUIFactory(CLIUIFactory): |
1142 | +class SilentUIFactory(UIFactory): |
1143 | """A UI Factory which never prints anything. |
1144 | |
1145 | - This is the default UI, if another one is never registered. |
1146 | + This is the default UI, if another one is never registered by a program |
1147 | + using bzrlib, and it's also active for example inside 'bzr serve'. |
1148 | + |
1149 | + Methods that try to read from the user raise an error; methods that do |
1150 | + output do nothing. |
1151 | """ |
1152 | |
1153 | def __init__(self): |
1154 | - CLIUIFactory.__init__(self) |
1155 | + UIFactory.__init__(self) |
1156 | + |
1157 | + def note(self, msg): |
1158 | + pass |
1159 | + |
1160 | + def get_username(self, prompt, **kwargs): |
1161 | + return None |
1162 | + |
1163 | + |
1164 | +class CannedInputUIFactory(SilentUIFactory): |
1165 | + """A silent UI that return canned input.""" |
1166 | + |
1167 | + def __init__(self, responses): |
1168 | + self.responses = responses |
1169 | + |
1170 | + def __repr__(self): |
1171 | + return "%s(%r)" % (self.__class__.__name__, self.responses) |
1172 | + |
1173 | + def get_boolean(self, prompt): |
1174 | + return self.responses.pop(0) |
1175 | |
1176 | def get_password(self, prompt='', **kwargs): |
1177 | - return None |
1178 | - |
1179 | - def get_username(self, prompt='', **kwargs): |
1180 | - return None |
1181 | - |
1182 | - def prompt(self, prompt, **kwargs): |
1183 | - pass |
1184 | - |
1185 | - def note(self, msg): |
1186 | - pass |
1187 | - |
1188 | - |
1189 | + return self.responses.pop(0) |
1190 | + |
1191 | + def get_username(self, prompt, **kwargs): |
1192 | + return self.responses.pop(0) |
1193 | + |
1194 | + def assert_all_input_consumed(self): |
1195 | + if self.responses: |
1196 | + raise AssertionError("expected all input in %r to be consumed" |
1197 | + % (self,)) |
1198 | + |
1199 | + |
1200 | +@deprecated_function(deprecated_in((1, 18, 0))) |
1201 | def clear_decorator(func, *args, **kwargs): |
1202 | """Decorator that clears the term""" |
1203 | ui_factory.clear_term() |
1204 | @@ -300,28 +351,27 @@ |
1205 | |
1206 | |
1207 | ui_factory = SilentUIFactory() |
1208 | -"""IMPORTANT: never import this symbol directly. ONLY ever access it as |
1209 | -ui.ui_factory.""" |
1210 | +# IMPORTANT: never import this symbol directly. ONLY ever access it as |
1211 | +# ui.ui_factory, so that you refer to the current value. |
1212 | |
1213 | |
1214 | def make_ui_for_terminal(stdin, stdout, stderr): |
1215 | """Construct and return a suitable UIFactory for a text mode program. |
1216 | - |
1217 | - If stdout is a smart terminal, this gets a smart UIFactory with |
1218 | - progress indicators, etc. If it's a dumb terminal, just plain text output. |
1219 | """ |
1220 | - cls = None |
1221 | - isatty = getattr(stdin, 'isatty', None) |
1222 | - if isatty is None: |
1223 | - cls = CLIUIFactory |
1224 | - elif not isatty(): |
1225 | - cls = CLIUIFactory |
1226 | - elif os.environ.get('TERM') in ('dumb', ''): |
1227 | - # e.g. emacs compile window |
1228 | - cls = CLIUIFactory |
1229 | - # User may know better, otherwise default to TextUIFactory |
1230 | - if ( os.environ.get('BZR_USE_TEXT_UI', None) is not None |
1231 | - or cls is None): |
1232 | - from bzrlib.ui.text import TextUIFactory |
1233 | - cls = TextUIFactory |
1234 | - return cls(stdin=stdin, stdout=stdout, stderr=stderr) |
1235 | + # this is now always TextUIFactory, which in turn decides whether it |
1236 | + # should display progress bars etc |
1237 | + from bzrlib.ui.text import TextUIFactory |
1238 | + return TextUIFactory(stdin, stdout, stderr) |
1239 | + |
1240 | + |
1241 | +class NullProgressView(object): |
1242 | + """Soak up and ignore progress information.""" |
1243 | + |
1244 | + def clear(self): |
1245 | + pass |
1246 | + |
1247 | + def show_progress(self, task): |
1248 | + pass |
1249 | + |
1250 | + def show_transport_activity(self, transport, direction, byte_count): |
1251 | + pass |
1252 | |
1253 | === modified file 'bzrlib/ui/text.py' |
1254 | --- bzrlib/ui/text.py 2009-06-26 03:29:57 +0000 |
1255 | +++ bzrlib/ui/text.py 2009-07-22 08:35:20 +0000 |
1256 | @@ -15,7 +15,6 @@ |
1257 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
1258 | |
1259 | |
1260 | - |
1261 | """Text UI, write output to the console. |
1262 | """ |
1263 | |
1264 | @@ -34,14 +33,16 @@ |
1265 | |
1266 | """) |
1267 | |
1268 | -from bzrlib.ui import CLIUIFactory |
1269 | - |
1270 | - |
1271 | -class TextUIFactory(CLIUIFactory): |
1272 | +from bzrlib.ui import ( |
1273 | + UIFactory, |
1274 | + NullProgressView, |
1275 | + ) |
1276 | + |
1277 | + |
1278 | +class TextUIFactory(UIFactory): |
1279 | """A UI factory for Text user interefaces.""" |
1280 | |
1281 | def __init__(self, |
1282 | - bar_type=None, |
1283 | stdin=None, |
1284 | stdout=None, |
1285 | stderr=None): |
1286 | @@ -51,13 +52,14 @@ |
1287 | letting the bzrlib.progress.ProgressBar factory auto |
1288 | select. Deprecated. |
1289 | """ |
1290 | - super(TextUIFactory, self).__init__(stdin=stdin, |
1291 | - stdout=stdout, stderr=stderr) |
1292 | - if bar_type: |
1293 | - symbol_versioning.warn(symbol_versioning.deprecated_in((1, 11, 0)) |
1294 | - % "bar_type parameter") |
1295 | + super(TextUIFactory, self).__init__() |
1296 | + # TODO: there's no good reason not to pass all three streams, maybe we |
1297 | + # should deprecate the default values... |
1298 | + self.stdin = stdin |
1299 | + self.stdout = stdout |
1300 | + self.stderr = stderr |
1301 | # paints progress, network activity, etc |
1302 | - self._progress_view = self._make_progress_view() |
1303 | + self._progress_view = self.make_progress_view() |
1304 | |
1305 | def clear_term(self): |
1306 | """Prepare the terminal for output. |
1307 | @@ -70,8 +72,78 @@ |
1308 | # to clear it. We might need to separately check for the case of |
1309 | self._progress_view.clear() |
1310 | |
1311 | - def _make_progress_view(self): |
1312 | - if os.environ.get('BZR_PROGRESS_BAR') in ('text', None, ''): |
1313 | + def get_boolean(self, prompt): |
1314 | + while True: |
1315 | + self.prompt(prompt + "? [y/n]: ") |
1316 | + line = self.stdin.readline().lower() |
1317 | + if line in ('y\n', 'yes\n'): |
1318 | + return True |
1319 | + elif line in ('n\n', 'no\n'): |
1320 | + return False |
1321 | + elif line in ('', None): |
1322 | + # end-of-file; possibly should raise an error here instead |
1323 | + return None |
1324 | + |
1325 | + def get_non_echoed_password(self): |
1326 | + isatty = getattr(self.stdin, 'isatty', None) |
1327 | + if isatty is not None and isatty(): |
1328 | + # getpass() ensure the password is not echoed and other |
1329 | + # cross-platform niceties |
1330 | + password = getpass.getpass('') |
1331 | + else: |
1332 | + # echo doesn't make sense without a terminal |
1333 | + password = self.stdin.readline() |
1334 | + if not password: |
1335 | + password = None |
1336 | + elif password[-1] == '\n': |
1337 | + password = password[:-1] |
1338 | + return password |
1339 | + |
1340 | + def get_password(self, prompt='', **kwargs): |
1341 | + """Prompt the user for a password. |
1342 | + |
1343 | + :param prompt: The prompt to present the user |
1344 | + :param kwargs: Arguments which will be expanded into the prompt. |
1345 | + This lets front ends display different things if |
1346 | + they so choose. |
1347 | + :return: The password string, return None if the user |
1348 | + canceled the request. |
1349 | + """ |
1350 | + prompt += ': ' |
1351 | + self.prompt(prompt, **kwargs) |
1352 | + # There's currently no way to say 'i decline to enter a password' |
1353 | + # as opposed to 'my password is empty' -- does it matter? |
1354 | + return self.get_non_echoed_password() |
1355 | + |
1356 | + def get_username(self, prompt, **kwargs): |
1357 | + """Prompt the user for a username. |
1358 | + |
1359 | + :param prompt: The prompt to present the user |
1360 | + :param kwargs: Arguments which will be expanded into the prompt. |
1361 | + This lets front ends display different things if |
1362 | + they so choose. |
1363 | + :return: The username string, return None if the user |
1364 | + canceled the request. |
1365 | + """ |
1366 | + prompt += ': ' |
1367 | + self.prompt(prompt, **kwargs) |
1368 | + username = self.stdin.readline() |
1369 | + if not username: |
1370 | + username = None |
1371 | + elif username[-1] == '\n': |
1372 | + username = username[:-1] |
1373 | + return username |
1374 | + |
1375 | + def make_progress_view(self): |
1376 | + """Construct and return a new ProgressView subclass for this UI. |
1377 | + """ |
1378 | + # if the user specifically requests either text or no progress bars, |
1379 | + # always do that. otherwise, guess based on $TERM and tty presence. |
1380 | + if os.environ.get('BZR_PROGRESS_BAR') == 'text': |
1381 | + return TextProgressView(self.stderr) |
1382 | + elif os.environ.get('BZR_PROGRESS_BAR') == 'none': |
1383 | + return NullProgressView() |
1384 | + elif progress._supports_progress(self.stderr): |
1385 | return TextProgressView(self.stderr) |
1386 | else: |
1387 | return NullProgressView() |
1388 | @@ -81,6 +153,19 @@ |
1389 | self.clear_term() |
1390 | self.stdout.write(msg + '\n') |
1391 | |
1392 | + def prompt(self, prompt, **kwargs): |
1393 | + """Emit prompt on the CLI. |
1394 | + |
1395 | + :param kwargs: Dictionary of arguments to insert into the prompt, |
1396 | + to allow UIs to reformat the prompt. |
1397 | + """ |
1398 | + if kwargs: |
1399 | + # See <https://launchpad.net/bugs/365891> |
1400 | + prompt = prompt % kwargs |
1401 | + prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace') |
1402 | + self.clear_term() |
1403 | + self.stderr.write(prompt) |
1404 | + |
1405 | def report_transport_activity(self, transport, byte_count, direction): |
1406 | """Called by transports as they do IO. |
1407 | |
1408 | @@ -105,19 +190,6 @@ |
1409 | self._progress_view.clear() |
1410 | |
1411 | |
1412 | -class NullProgressView(object): |
1413 | - """Soak up and ignore progress information.""" |
1414 | - |
1415 | - def clear(self): |
1416 | - pass |
1417 | - |
1418 | - def show_progress(self, task): |
1419 | - pass |
1420 | - |
1421 | - def show_transport_activity(self, transport, direction, byte_count): |
1422 | - pass |
1423 | - |
1424 | - |
1425 | class TextProgressView(object): |
1426 | """Display of progress bar and other information on a tty. |
1427 |
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.