summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJeff Quast <jquast@io.com>2014-12-18 17:36:05 -0800
committerJeff Quast <jquast@io.com>2014-12-18 17:36:22 -0800
commitad7c35dce59db55f9d3dd5fea8b925abc81211ca (patch)
tree4b27b6eccfe88e656f0d69b0edf27a461fa3fe47 /tests
parent01ea0ff440facd47894c076747488c28291a47cc (diff)
parent77357d107b1ebb38287695149d7cf1e66df4addd (diff)
downloadpexpect-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.py48
-rwxr-xr-xtests/test_ctrl_chars.py7
-rw-r--r--tests/test_maxcanon.py152
-rwxr-xr-xtests/test_misc.py53
-rw-r--r--tests/test_replwrap.py2
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):