summaryrefslogtreecommitdiff
path: root/tests/test_maxcanon.py
blob: bbd08f3fa47aba262dd5e3e97aa1f96e1e14a65d (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
""" Module for canonical-mode tests. """
import sys
import os


import pexpect
from . import PexpectTestCase


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.

    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.

    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
        else:
            # All others (probably) limit exactly at PC_MAX_CANON
            self.max_input = os.fpathconf(0, 'PC_MAX_CANON')

    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, timeout=5):
            child.expect_exact('\a')

        # 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

    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

    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