diff options
| author | Jeff Quast <jquast@io.com> | 2014-12-18 17:36:05 -0800 |
|---|---|---|
| committer | Jeff Quast <jquast@io.com> | 2014-12-18 17:36:22 -0800 |
| commit | ad7c35dce59db55f9d3dd5fea8b925abc81211ca (patch) | |
| tree | 4b27b6eccfe88e656f0d69b0edf27a461fa3fe47 /tests | |
| parent | 01ea0ff440facd47894c076747488c28291a47cc (diff) | |
| parent | 77357d107b1ebb38287695149d7cf1e66df4addd (diff) | |
| download | pexpect-ad7c35dce59db55f9d3dd5fea8b925abc81211ca.tar.gz | |
Merge remote-tracking branch 'origin/master' into issue-104-cannot-exec-setuids
Conflicts:
pexpect/__init__.py
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/PexpectTestCase.py | 48 | ||||
| -rwxr-xr-x | tests/test_ctrl_chars.py | 7 | ||||
| -rw-r--r-- | tests/test_maxcanon.py | 152 | ||||
| -rwxr-xr-x | tests/test_misc.py | 53 | ||||
| -rw-r--r-- | tests/test_replwrap.py | 2 |
5 files changed, 221 insertions, 41 deletions
diff --git a/tests/PexpectTestCase.py b/tests/PexpectTestCase.py index 7a9574e..307437e 100644 --- a/tests/PexpectTestCase.py +++ b/tests/PexpectTestCase.py @@ -22,26 +22,68 @@ from __future__ import print_function import contextlib import unittest +import signal import sys import os + class PexpectTestCase(unittest.TestCase): def setUp(self): self.PYTHONBIN = sys.executable self.original_path = os.getcwd() tests_dir = os.path.dirname(__file__) self.project_dir = project_dir = os.path.dirname(tests_dir) + + # all tests are executed in this folder; there are many auxiliary + # programs in this folder executed by spawn(). os.chdir(tests_dir) - os.environ['COVERAGE_PROCESS_START'] = os.path.join(project_dir, '.coveragerc') + + # If the pexpect raises an exception after fork(), but before + # exec(), our test runner *also* forks. We prevent this by + # storing our pid and asserting equality on tearDown. + self.pid = os.getpid() + + coverage_rc = os.path.join(project_dir, '.coveragerc') + os.environ['COVERAGE_PROCESS_START'] = coverage_rc os.environ['COVERAGE_FILE'] = os.path.join(project_dir, '.coverage') print('\n', self.id(), end=' ') sys.stdout.flush() + + # some build agents will ignore SIGHUP and SIGINT, which python + # inherits. This causes some of the tests related to terminate() + # to fail. We set them to the default handlers that they should + # be, and restore them back to their SIG_IGN value on tearDown. + # + # I'm not entirely convinced they need to be restored, only our + # test runner is affected. + self.restore_ignored_signals = [ + value for value in (signal.SIGHUP, signal.SIGINT,) + if signal.getsignal(value) == signal.SIG_IGN] + if signal.SIGHUP in self.restore_ignored_signals: + # sighup should be set to default handler + signal.signal(signal.SIGHUP, signal.SIG_DFL) + if signal.SIGINT in self.restore_ignored_signals: + # SIGINT should be set to signal.default_int_handler + signal.signal(signal.SIGINT, signal.default_int_handler) unittest.TestCase.setUp(self) def tearDown(self): - os.chdir (self.original_path) + # restore original working folder + os.chdir(self.original_path) + + if self.pid != os.getpid(): + # The build server pattern-matches phrase 'Test runner has forked!' + print("Test runner has forked! This means a child process raised " + "an exception before exec() in a test case, the error is " + "more than likely found above this line in stderr.", + file=sys.stderr) + exit(1) + + # restore signal handlers + for signal_value in self.restore_ignored_signals: + signal.signal(signal_value, signal.SIG_IGN) - if sys.version_info < (2,7): + if sys.version_info < (2, 7): # We want to use these methods, which are new/improved in 2.7, but # we are still supporting 2.6 for the moment. This section can be # removed when we drop Python 2.6 support. diff --git a/tests/test_ctrl_chars.py b/tests/test_ctrl_chars.py index 9c7b869..10d03db 100755 --- a/tests/test_ctrl_chars.py +++ b/tests/test_ctrl_chars.py @@ -26,6 +26,9 @@ from . import PexpectTestCase import time import sys +from ptyprocess import ptyprocess +ptyprocess._make_eof_intr() + if sys.version_info[0] >= 3: def byte(i): return bytes([i]) @@ -54,7 +57,7 @@ class TestCtrlChars(PexpectTestCase.PexpectTestCase): child = pexpect.spawn('python getch.py', echo=False, timeout=5) child.expect('READY') child.sendintr() - child.expect(str(child._INTR) + '<STOP>') + child.expect(str(ord(ptyprocess._INTR)) + '<STOP>') child.send(byte(0)) child.expect('0<STOP>') @@ -66,7 +69,7 @@ class TestCtrlChars(PexpectTestCase.PexpectTestCase): child = pexpect.spawn('python getch.py', echo=False, timeout=5) child.expect('READY') child.sendeof() - child.expect(str(child._EOF) + '<STOP>') + child.expect(str(ord(ptyprocess._EOF)) + '<STOP>') child.send(byte(0)) child.expect('0<STOP>') diff --git a/tests/test_maxcanon.py b/tests/test_maxcanon.py new file mode 100644 index 0000000..bbd08f3 --- /dev/null +++ b/tests/test_maxcanon.py @@ -0,0 +1,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 diff --git a/tests/test_misc.py b/tests/test_misc.py index a2245aa..28df570 100755 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -149,41 +149,24 @@ class TestCaseMisc(PexpectTestCase.PexpectTestCase): def test_sighup(self): " validate argument `ignore_sighup=True` and `ignore_sighup=False`. " - # If a parent process sets an Ignore handler for SIGHUP (as on Fedora's - # build machines), this test breaks. We temporarily restore the default - # handler, so the child process will quit. However, we can't simply - # replace any installed handler, because getsignal returns None for - # handlers not set in Python code, so we wouldn't be able to restore - # them. - if signal.getsignal(signal.SIGHUP) == signal.SIG_IGN: - signal.signal(signal.SIGHUP, signal.SIG_DFL) - restore_sig_ign = True - else: - restore_sig_ign = False - getch = sys.executable + ' getch.py' - try: - child = pexpect.spawn(getch, ignore_sighup=True) - child.expect('READY') - child.kill(signal.SIGHUP) - for _ in range(10): - if not child.isalive(): - self.fail('Child process should not have exited.') - time.sleep(0.1) - - child = pexpect.spawn(getch, ignore_sighup=False) - child.expect('READY') - child.kill(signal.SIGHUP) - for _ in range(10): - if not child.isalive(): - break - time.sleep(0.1) - else: - self.fail('Child process should have exited.') - - finally: - if restore_sig_ign: - signal.signal(signal.SIGHUP, signal.SIG_IGN) + child = pexpect.spawn(getch, ignore_sighup=True) + child.expect('READY') + child.kill(signal.SIGHUP) + for _ in range(10): + if not child.isalive(): + self.fail('Child process should not have exited.') + time.sleep(0.1) + + child = pexpect.spawn(getch, ignore_sighup=False) + child.expect('READY') + child.kill(signal.SIGHUP) + for _ in range(10): + if not child.isalive(): + break + time.sleep(0.1) + else: + self.fail('Child process should have exited.') def test_bad_child_pid(self): " assert bad condition error in isalive(). " @@ -191,7 +174,7 @@ class TestCaseMisc(PexpectTestCase.PexpectTestCase): child = pexpect.spawn('cat') child.terminate(force=1) # Force an invalid state to test isalive - child.terminated = 0 + child.ptyproc.terminated = 0 try: with self.assertRaisesRegexp(pexpect.ExceptionPexpect, ".*" + expect_errmsg): diff --git a/tests/test_replwrap.py b/tests/test_replwrap.py index 14f7c39..28c7599 100644 --- a/tests/test_replwrap.py +++ b/tests/test_replwrap.py @@ -26,7 +26,7 @@ class REPLWrapTestCase(unittest.TestCase): assert 'real' in res, res # PAGER should be set to cat, otherwise man hangs - res = bash.run_command('man sleep', timeout=2) + res = bash.run_command('man sleep', timeout=5) assert 'SLEEP' in res, res def test_multiline(self): |
