summaryrefslogtreecommitdiff
path: root/tests/test_maxcanon.py
blob: 772a3b7e3fde7e80757e0e0cf2c663edd7e9890d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
""" 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.

    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 test_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 test_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 test_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