Blob Blame History Raw
From ae932d179adc4c602f9a4298076cdc5a82f9351a Mon Sep 17 00:00:00 2001
From: Jeff Quast <contact@jeffquast.com>
Date: Tue, 6 Oct 2015 10:17:50 -0700
Subject: [PATCH 2/2] 2 new tools: display-{fpathconf.maxcanon}.py

tests/test_maxcanon.py has been deleted and turned into
an "autodetection" tool of sorts, no longer attempting
to assert exacting values, but determine it programmatically.
---
 pexpect/pty_spawn.py       |  14 ++--
 tests/test_maxcanon.py     | 179 ---------------------------------------------
 tools/display-fpathconf.py |  41 +++++++++++
 tools/display-maxcanon.py  |  80 ++++++++++++++++++++
 4 files changed, 128 insertions(+), 186 deletions(-)
 delete mode 100644 tests/test_maxcanon.py
 create mode 100644 tools/display-fpathconf.py
 create mode 100644 tools/display-maxcanon.py

diff --git a/pexpect/pty_spawn.py b/pexpect/pty_spawn.py
index 7fc27fe..299016c 100644
--- a/pexpect/pty_spawn.py
+++ b/pexpect/pty_spawn.py
@@ -492,9 +492,9 @@ class spawn(SpawnBase):
 
         This value may be discovered using fpathconf(3)::
 
-        >>> from os import fpathconf
-        >>> print(fpathconf(0, 'PC_MAX_CANON'))
-        256
+            >>> from os import fpathconf
+            >>> print(fpathconf(0, 'PC_MAX_CANON'))
+            256
 
         On such a system, only 256 bytes may be received per line. Any
         subsequent bytes received will be discarded. BEL (``'\a'``) is then
@@ -505,10 +505,10 @@ class spawn(SpawnBase):
         Canonical input processing may be disabled altogether by executing
         a shell, then stty(1), before executing the final program::
 
-        >>> bash = pexpect.spawn('/bin/bash', echo=False)
-        >>> bash.sendline('stty -icanon')
-        >>> bash.sendline('base64')
-        >>> bash.sendline('x' * 5000)
+            >>> bash = pexpect.spawn('/bin/bash', echo=False)
+            >>> bash.sendline('stty -icanon')
+            >>> bash.sendline('base64')
+            >>> bash.sendline('x' * 5000)
         '''
 
         time.sleep(self.delaybeforesend)
diff --git a/tests/test_maxcanon.py b/tests/test_maxcanon.py
deleted file mode 100644
index cd48cbc..0000000
--- a/tests/test_maxcanon.py
+++ /dev/null
@@ -1,179 +0,0 @@
-""" Module for canonical-mode tests. """
-# std imports
-import sys
-import os
-
-# local
-import pexpect
-from . import PexpectTestCase
-
-# 3rd-party
-import pytest
-
-
-class TestCaseCanon(PexpectTestCase.PexpectTestCase):
-    """
-    Test expected Canonical mode behavior (limited input line length).
-
-    All systems use the value of MAX_CANON which can be found using
-    fpathconf(3) value PC_MAX_CANON -- with the exception of Linux
-    and FreeBSD.
-
-    Linux, though defining a value of 255, actually honors the value
-    of 4096 from linux kernel include file tty.h definition
-    N_TTY_BUF_SIZE.
-
-    Linux also does not honor IMAXBEL. termios(3) states, "Linux does not
-    implement this bit, and acts as if it is always set." Although these
-    tests ensure it is enabled, this is a non-op for Linux.
-
-    More unsettling in regards to Linux, Fedora and Debian have different
-    behaviours.  For this reason, **these test has been disabled entirely**.
-
-    FreeBSD supports neither, and instead uses a fraction (1/5) of the tty
-    speed which is always 9600.  Therefor, the maximum limited input line
-    length is 9600 / 5 = 1920.
-
-    These tests only ensure the correctness of the behavior described by
-    the sendline() docstring. pexpect is not particularly involved in
-    these scenarios, though if we wish to expose some kind of interface
-    to tty.setraw, for example, these tests may be re-purposed as such.
-
-    Lastly, portions of these tests are skipped on Travis-CI. It produces
-    unexpected behavior not reproduced on Debian/GNU Linux.
-    """
-
-    def setUp(self):
-        super(TestCaseCanon, self).setUp()
-
-        self.echo = False
-        if sys.platform.lower().startswith('linux'):
-            # linux is 4096, N_TTY_BUF_SIZE.
-            self.max_input = 4096
-            self.echo = True
-        elif sys.platform.lower().startswith('sunos'):
-            # SunOS allows PC_MAX_CANON + 1; see
-            # https://bitbucket.org/illumos/illumos-gate/src/d07a59219ab7fd2a7f39eb47c46cf083c88e932f/usr/src/uts/common/io/ldterm.c?at=default#cl-1888
-            self.max_input = os.fpathconf(0, 'PC_MAX_CANON') + 1
-        elif sys.platform.lower().startswith('freebsd'):
-            # http://lists.freebsd.org/pipermail/freebsd-stable/2009-October/052318.html
-            self.max_input = 9600 / 5
-        else:
-            # All others (probably) limit exactly at PC_MAX_CANON
-            self.max_input = os.fpathconf(0, 'PC_MAX_CANON')
-
-    @pytest.mark.skipif(
-        sys.platform.lower().startswith('freebsd'),
-        reason='os.write to BLOCK indefinitely on FreeBSD in this case'
-    )
-    def disabled_under_max_canon(self):
-        " BEL is not sent by terminal driver at maximum bytes - 1. "
-        # given,
-        child = pexpect.spawn('bash', echo=self.echo, timeout=5)
-        child.sendline('echo READY')
-        child.sendline('stty icanon imaxbel')
-        child.sendline('echo BEGIN; cat')
-
-        # some systems BEL on (maximum - 1), not able to receive CR,
-        # even though all characters up until then were received, they
-        # simply cannot be transmitted, as CR is part of the transmission.
-        send_bytes = self.max_input - 1
-
-        # exercise,
-        child.sendline('_' * send_bytes)
-
-        # fast forward beyond 'cat' command, as ^G can be found as part of
-        # set-xterm-title sequence of $PROMPT_COMMAND or $PS1.
-        child.expect_exact('BEGIN')
-
-        # verify, all input is found in echo output,
-        child.expect_exact('_' * send_bytes)
-
-        # BEL is not found,
-        with self.assertRaises(pexpect.TIMEOUT):
-            child.expect_exact('\a', timeout=1)
-
-        # cleanup,
-        child.sendeof()           # exit cat(1)
-        child.sendline('exit 0')  # exit bash(1)
-        child.expect(pexpect.EOF)
-        assert not child.isalive()
-        assert child.exitstatus == 0
-
-    @pytest.mark.skipif(
-        sys.platform.lower().startswith('freebsd'),
-        reason='os.write to BLOCK indefinitely on FreeBSD in this case'
-    )
-    def disabled_beyond_max_icanon(self):
-        " a single BEL is sent when maximum bytes is reached. "
-        # given,
-        child = pexpect.spawn('bash', echo=self.echo, timeout=5)
-        child.sendline('stty icanon imaxbel erase ^H')
-        child.sendline('cat')
-        send_bytes = self.max_input
-
-        # exercise,
-        child.sendline('_' * send_bytes)
-        child.expect_exact('\a')
-
-        # exercise, we must now backspace to send CR.
-        child.sendcontrol('h')
-        child.sendline()
-
-        if os.environ.get('TRAVIS', None) == 'true':
-            # Travis-CI has intermittent behavior here, possibly
-            # because the master process is itself, a PTY?
-            return
-
-        # verify the length of (maximum - 1) received by cat(1),
-        # which has written it back out,
-        child.expect_exact('_' * (send_bytes - 1))
-        # and not a byte more.
-        with self.assertRaises(pexpect.TIMEOUT):
-            child.expect_exact('_', timeout=1)
-
-        # cleanup,
-        child.sendeof()           # exit cat(1)
-        child.sendline('exit 0')  # exit bash(1)
-        child.expect_exact(pexpect.EOF)
-        assert not child.isalive()
-        assert child.exitstatus == 0
-
-    @pytest.mark.skipif(
-        sys.platform.lower().startswith('freebsd'),
-        reason='os.write to BLOCK indefinitely on FreeBSD in this case'
-    )
-    def disabled_max_no_icanon(self):
-        " may exceed maximum input bytes if canonical mode is disabled. "
-        # given,
-        child = pexpect.spawn('bash', echo=self.echo, timeout=5)
-        child.sendline('stty -icanon imaxbel')
-        child.sendline('echo BEGIN; cat')
-        send_bytes = self.max_input + 11
-
-        # exercise,
-        child.sendline('_' * send_bytes)
-
-        # fast forward beyond 'cat' command, as ^G can be found as part of
-        # set-xterm-title sequence of $PROMPT_COMMAND or $PS1.
-        child.expect_exact('BEGIN')
-
-        if os.environ.get('TRAVIS', None) == 'true':
-            # Travis-CI has intermittent behavior here, possibly
-            # because the master process is itself, a PTY?
-            return
-
-        # BEL is *not* found,
-        with self.assertRaises(pexpect.TIMEOUT):
-            child.expect_exact('\a', timeout=1)
-
-        # verify, all input is found in output,
-        child.expect_exact('_' * send_bytes)
-
-        # cleanup,
-        child.sendcontrol('c')    # exit cat(1) (eof wont work in -icanon)
-        child.sendcontrol('c')
-        child.sendline('exit 0')  # exit bash(1)
-        child.expect(pexpect.EOF)
-        assert not child.isalive()
-        assert child.exitstatus == 0
diff --git a/tools/display-fpathconf.py b/tools/display-fpathconf.py
new file mode 100644
index 0000000..d40cbae
--- /dev/null
+++ b/tools/display-fpathconf.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+"""Displays os.fpathconf values related to terminals. """
+from __future__ import print_function
+import sys
+import os
+
+
+def display_fpathconf():
+    DISP_VALUES = (
+        ('PC_MAX_CANON', ('Max no. of bytes in a '
+                          'terminal canonical input line.')),
+        ('PC_MAX_INPUT', ('Max no. of bytes for which '
+                          'space is available in a terminal input queue.')),
+        ('PC_PIPE_BUF', ('Max no. of bytes which will '
+                         'be written atomically to a pipe.')),
+        ('PC_VDISABLE', 'Terminal character disabling value.')
+    )
+    FMT = '{name:<13} {value:<5} {description}'
+
+    # column header
+    print(FMT.format(name='name', value='value', description='description'))
+    print(FMT.format(name=('-' * 13), value=('-' * 5), description=('-' * 11)))
+
+    fd = sys.stdin.fileno()
+    for name, description in DISP_VALUES:
+        key = os.pathconf_names.get(name, None)
+        if key is None:
+            value = 'UNDEF'
+        else:
+            try:
+                value = os.fpathconf(fd, name)
+            except OSError as err:
+                value = 'OSErrno {0.errno}'.format(err)
+        if name == 'PC_VDISABLE':
+            value = hex(value)
+        print(FMT.format(name=name, value=value, description=description))
+    print()
+
+
+if __name__ == '__main__':
+    display_fpathconf()
diff --git a/tools/display-maxcanon.py b/tools/display-maxcanon.py
new file mode 100644
index 0000000..cbd664f
--- /dev/null
+++ b/tools/display-maxcanon.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+"""
+This tool uses pexpect to test expected Canonical mode length.
+
+All systems use the value of MAX_CANON which can be found using
+fpathconf(3) value PC_MAX_CANON -- with the exception of Linux
+and FreeBSD.
+
+Linux, though defining a value of 255, actually honors the value
+of 4096 from linux kernel include file tty.h definition
+N_TTY_BUF_SIZE.
+
+Linux also does not honor IMAXBEL. termios(3) states, "Linux does not
+implement this bit, and acts as if it is always set." Although these
+tests ensure it is enabled, this is a non-op for Linux.
+
+FreeBSD supports neither, and instead uses a fraction (1/5) of the tty
+speed which is always 9600.  Therefor, the maximum limited input line
+length is 9600 / 5 = 1920.
+
+These tests only ensure the correctness of the behavior described by
+the sendline() docstring -- the values listed there, and above should
+be equal to the output of the given OS described, but no promises!
+"""
+# std import
+from __future__ import print_function
+import sys
+import os
+
+
+def detect_maxcanon():
+    import pexpect
+    bashrc = os.path.join(
+        # re-use pexpect/replwrap.py's bashrc file,
+        os.path.dirname(__file__), os.path.pardir, 'pexpect', 'bashrc.sh')
+
+    child = pexpect.spawn('bash', ['--rcfile', bashrc],
+                          echo=True, encoding='utf8', timeout=3)
+
+    child.sendline(u'echo -n READY_; echo GO')
+    child.expect_exact(u'READY_GO')
+
+    child.sendline(u'stty icanon imaxbel erase ^H; echo -n retval: $?')
+    child.expect_exact(u'retval: 0')
+
+    child.sendline(u'echo -n GO_; echo AGAIN')
+    child.expect_exact(u'GO_AGAIN')
+    child.sendline(u'cat')
+
+    child.delaybeforesend = 0
+
+    column, blocksize = 0, 64
+    ch_marker = u'_'
+
+    print('auto-detecting MAX_CANON: ', end='')
+    sys.stdout.flush()
+
+    while True:
+        child.send(ch_marker * blocksize)
+        result = child.expect([ch_marker * blocksize, u'\a'])
+        if result == 0:
+            # entire block fit without emitting bel
+            column += blocksize
+        elif result == 1:
+            # an '\a' was emitted, count the number of ch_markers
+            # found since last blocksize, determining our MAX_CANON
+            column += child.before.count(ch_marker)
+            break
+    print(column)
+
+if __name__ == '__main__':
+    try:
+        detect_maxcanon()
+    except ImportError:
+        # we'd like to use this with CI -- but until we integrate
+        # with tox, we can't determine a period in testing when
+        # the pexpect module has been installed 
+        print('warning: pexpect not in module path, MAX_CANON '
+              'could not be determined by systems test.',
+              file=sys.stderr)
-- 
2.6.1