Merge lp:~vila/bzr/353370-notty-no-term-width into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: John A Meinel
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~vila/bzr/353370-notty-no-term-width
Merge into: lp:bzr
Diff against target: 208 lines (+106/-38)
3 files modified
NEWS (+4/-2)
bzrlib/osutils.py (+56/-15)
bzrlib/tests/test_osutils.py (+46/-21)
To merge this branch: bzr merge lp:~vila/bzr/353370-notty-no-term-width
Reviewer Review Type Date Requested Status
Martin Pool Needs Fixing
John A Meinel Approve
Review via email: mp+15858@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

This fix (which includes jam's one) should address bug #492561
and provides more robust tests.

There have been several points raised in several discussions so
I'll try to summarize below.

1) I confirm that COLUMNS should be taken into account *before*
   querying for termios.TIOCGWINSZ.

2) TIOCGSIZE seems to be a thing of the distant past and is
   either equivalent to TIOCGWINSZ or SUN specific (I couldn't
   find it in /usr/include on Karmic nor FreeBSD nor gentoo :)

   Only on OSX 10.6 did I came back with:

     ./sys/ioctl.h:85:#define TIOCGSIZE TIOCGWINSZ

   So, no support for TIOCGSIZE sounds right.

3) I documented the rules to get the terminal size and hopefully
   write the right tests for that.

Keep in mind though that we are limited by the real controlling
terminal here unless we try to override the OS specific ways to
query for terminal size. I did that to test the default values
but trying to do more sounds a bit overkill (emulating the
controlling terminal sounds like too much work for no real
benefit).

That means we need "manual" confirmation that the ioctl call is
right for unices (it is for all the tests I could think of).

For the automated tests, so far, only PQM runs without a
controlling terminal, each of us always have one and even babune
seems to always have one defined somehow (I was a bit surprised
by that).

We rely on win32utils.get_console_size() for windows and I didn't
test the error cases here (help and feedback welcome). Instead I
trusted the function and just seed it with None as a default
value if anything goes wrong.

On windows, I'm not sure get_console_size() will do the right
thing if we use some xterm-like application and we may want to
use fcntl.ioctl(termios.TIOCGWINSZ) ? Some cygwin users can
answer that ?

Also, as Alexander mentioned at least twice, we rely on stderr
not being redirected to get the terminal size. But since we now
have BZR_COLUMNS to force a width I wonder if it's still needed
since it shouldn't be called if stdout is redirected anyway.

Revision history for this message
John A Meinel (jameinel) wrote :

So arguably, we need to change the command a bit, and add a flag that says whether we want to check stderr or stdout. So that progress bars stay the right width even when redirecting, but 'bzr log --line' gives the wide info.

Note that some of the motivation was also because of pagers. And "bzr log --line | less" goes to a terminal, even though "bzr log --line > file" does not. AFAIK there is no way to determine in process whether stdout is a pipe rather than a file. And even if there was, what do you do for "bzr log --line | tee file" ? As that goes both to the terminal and to a file. (Note this is even worse for getting the file encoding right on windows, as default file content encoding != default terminal encoding != filename encoding. So doing "bzr log > file && gvim file you would want content encoding, but bzr log | less you would want terminal encoding." Probably one of the few reasons to spawn our own pager, as then we know it is still going to a term...)

Anyway, I feel that the current patch is 'good enough'. It allows BZR_COLUMNS if people really need to tweak the width. It respects COLUMNS if available, and still uses TIOCGWINSZ.

review: Approve
Revision history for this message
Martin Pool (mbp) wrote :

capital 'Windows' when talking about that other software.

It seems reasonable. Let's try it.

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-12-09 05:47:32 +0000
+++ NEWS 2009-12-09 15:11:12 +0000
@@ -64,11 +64,13 @@
64* Lots of bugfixes for the test suite on Windows. We should once again64* Lots of bugfixes for the test suite on Windows. We should once again
65 have a test suite with no failures on Windows. (John Arbash Meinel)65 have a test suite with no failures on Windows. (John Arbash Meinel)
6666
67* ``osutils.terminal_width()`` obeys the BZR_COLUMNS envrionment67* ``osutils.terminal_width()`` obeys the BZR_COLUMNS environment
68 variable but returns None if the terminal is not a tty (when output is68 variable but returns None if the terminal is not a tty (when output is
69 redirected for example). Also fixes its usage under OSes that doesn't69 redirected for example). Also fixes its usage under OSes that doesn't
70 provide termios.TIOCGWINSZ. 70 provide termios.TIOCGWINSZ. Make sure the corresponding tests runs on
71 windows too.
71 (Joke de Buhr, Vincent Ladeuil, #353370, #62539)72 (Joke de Buhr, Vincent Ladeuil, #353370, #62539)
73 (John Arbash Meinel, Vincent Ladeuil, #492561)
7274
73* Terminate ssh subprocesses when no references to them remain, fixing75* Terminate ssh subprocesses when no references to them remain, fixing
74 subprocess and file descriptor leaks. (Andrew Bennetts, #426662)76 subprocess and file descriptor leaks. (Andrew Bennetts, #426662)
7577
=== modified file 'bzrlib/osutils.py'
--- bzrlib/osutils.py 2009-12-04 10:09:11 +0000
+++ bzrlib/osutils.py 2009-12-09 15:11:12 +0000
@@ -1342,6 +1342,23 @@
1342 """Return terminal width.1342 """Return terminal width.
13431343
1344 None is returned if the width can't established precisely.1344 None is returned if the width can't established precisely.
1345
1346 The rules are:
1347 - if BZR_COLUMNS is set, returns its value
1348 - if there is no controlling terminal, returns None
1349 - if COLUMNS is set, returns its value,
1350
1351 From there, we need to query the OS to get the size of the controlling
1352 terminal.
1353
1354 Unices:
1355 - get termios.TIOCGWINSZ
1356 - if an error occurs or a negative value is obtained, returns None
1357
1358 Windows:
1359
1360 - win32utils.get_console_size() decides,
1361 - returns None on error (provided default value)
1345 """1362 """
13461363
1347 # If BZR_COLUMNS is set, take it, user is always right1364 # If BZR_COLUMNS is set, take it, user is always right
@@ -1355,26 +1372,50 @@
1355 # Don't guess, setting BZR_COLUMNS is the recommended way to override.1372 # Don't guess, setting BZR_COLUMNS is the recommended way to override.
1356 return None1373 return None
13571374
1358 if sys.platform == 'win32':1375 # If COLUMNS is set, take it, the terminal knows better (even inside a
1359 return win32utils.get_console_size(defaultx=None)[0]1376 # given terminal, the application can decide to set COLUMNS to a lower
13601377 # value (splitted screen) or a bigger value (scroll bars))
1378 try:
1379 return int(os.environ['COLUMNS'])
1380 except (KeyError, ValueError):
1381 pass
1382
1383 width, height = _terminal_size(None, None)
1384 if width <= 0:
1385 # Consider invalid values as meaning no width
1386 return None
1387
1388 return width
1389
1390
1391def _win32_terminal_size(width, height):
1392 width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
1393 return width, height
1394
1395
1396def _ioctl_terminal_size(width, height):
1361 try:1397 try:
1362 import struct, fcntl, termios1398 import struct, fcntl, termios
1363 s = struct.pack('HHHH', 0, 0, 0, 0)1399 s = struct.pack('HHHH', 0, 0, 0, 0)
1364 x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)1400 x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1365 width = struct.unpack('HHHH', x)[1]1401 height, width = struct.unpack('HHHH', x)[0:2]
1366 except (IOError, AttributeError):1402 except (IOError, AttributeError):
1367 # If COLUMNS is set, take it1403 pass
1368 try:1404 return width, height
1369 return int(os.environ['COLUMNS'])1405
1370 except (KeyError, ValueError):1406_terminal_size = None
1371 return None1407"""Returns the terminal size as (width, height).
13721408
1373 if width <= 0:1409:param width: Default value for width.
1374 # Consider invalid values as meaning no width1410:param height: Default value for height.
1375 return None1411
13761412This is defined specifically for each OS and query the size of the controlling
1377 return width1413terminal. If any error occurs, the provided default values should be returned.
1414"""
1415if sys.platform == 'win32':
1416 _terminal_size = _win32_terminal_size
1417else:
1418 _terminal_size = _ioctl_terminal_size
13781419
13791420
1380def supports_executable():1421def supports_executable():
13811422
=== modified file 'bzrlib/tests/test_osutils.py'
--- bzrlib/tests/test_osutils.py 2009-12-08 21:46:07 +0000
+++ bzrlib/tests/test_osutils.py 2009-12-09 15:11:12 +0000
@@ -1928,39 +1928,63 @@
19281928
1929class TestTerminalWidth(tests.TestCase):1929class TestTerminalWidth(tests.TestCase):
19301930
1931 def replace_stdout(self, new):
1932 orig_stdout = sys.stdout
1933 def restore():
1934 sys.stdout = orig_stdout
1935 self.addCleanup(restore)
1936 sys.stdout = new
1937
1938 def replace__terminal_size(self, new):
1939 orig__terminal_size = osutils._terminal_size
1940 def restore():
1941 osutils._terminal_size = orig__terminal_size
1942 self.addCleanup(restore)
1943 osutils._terminal_size = new
1944
1945 def set_fake_tty(self):
1946
1947 class I_am_a_tty(object):
1948 def isatty(self):
1949 return True
1950
1951 self.replace_stdout(I_am_a_tty())
1952
1931 def test_default_values(self):1953 def test_default_values(self):
1932 self.assertEquals(80, osutils.default_terminal_width)1954 self.assertEqual(80, osutils.default_terminal_width)
19331955
1934 def test_defaults_to_BZR_COLUMNS(self):1956 def test_defaults_to_BZR_COLUMNS(self):
1935 # BZR_COLUMNS is set by the test framework1957 # BZR_COLUMNS is set by the test framework
1936 self.assertEquals('80', os.environ['BZR_COLUMNS'])1958 self.assertNotEqual('12', os.environ['BZR_COLUMNS'])
1937 os.environ['BZR_COLUMNS'] = '12'1959 os.environ['BZR_COLUMNS'] = '12'
1938 self.assertEquals(12, osutils.terminal_width())1960 self.assertEqual(12, osutils.terminal_width())
1961
1962 def test_falls_back_to_COLUMNS(self):
1963 del os.environ['BZR_COLUMNS']
1964 self.assertNotEqual('42', os.environ['COLUMNS'])
1965 self.set_fake_tty()
1966 os.environ['COLUMNS'] = '42'
1967 self.assertEqual(42, osutils.terminal_width())
19391968
1940 def test_tty_default_without_columns(self):1969 def test_tty_default_without_columns(self):
1941 del os.environ['BZR_COLUMNS']1970 del os.environ['BZR_COLUMNS']
1942 del os.environ['COLUMNS']1971 del os.environ['COLUMNS']
1943 orig_stdout = sys.stdout1972
1944 def restore():1973 def terminal_size(w, h):
1945 sys.stdout = orig_stdout1974 return 42, 42
1946 self.addCleanup(restore)1975
19471976 self.set_fake_tty()
1948 class I_am_a_tty(object):1977 # We need to override the osutils definition as it depends on the
1949 def isatty(self):1978 # running environment that we can't control (PQM running without a
1950 return True1979 # controlling terminal is one example).
19511980 self.replace__terminal_size(terminal_size)
1952 sys.stdout = I_am_a_tty()1981 self.assertEqual(42, osutils.terminal_width())
1953 self.assertEquals(None, osutils.terminal_width())
19541982
1955 def test_non_tty_default_without_columns(self):1983 def test_non_tty_default_without_columns(self):
1956 del os.environ['BZR_COLUMNS']1984 del os.environ['BZR_COLUMNS']
1957 del os.environ['COLUMNS']1985 del os.environ['COLUMNS']
1958 orig_stdout = sys.stdout1986 self.replace_stdout(None)
1959 def restore():1987 self.assertEqual(None, osutils.terminal_width())
1960 sys.stdout = orig_stdout
1961 self.addCleanup(restore)
1962 sys.stdout = None
1963 self.assertEquals(None, osutils.terminal_width())
19641988
1965 def test_no_TIOCGWINSZ(self):1989 def test_no_TIOCGWINSZ(self):
1966 self.requireFeature(TermIOSFeature)1990 self.requireFeature(TermIOSFeature)
@@ -1978,4 +2002,5 @@
1978 del termios.TIOCGWINSZ2002 del termios.TIOCGWINSZ
1979 del os.environ['BZR_COLUMNS']2003 del os.environ['BZR_COLUMNS']
1980 del os.environ['COLUMNS']2004 del os.environ['COLUMNS']
1981 self.assertIs(None, osutils.terminal_width())2005 # Whatever the result is, if we don't raise an exception, it's ok.
2006 osutils.terminal_width()