summaryrefslogtreecommitdiff
path: root/tests/test_which.py
diff options
context:
space:
mode:
authorjquast <contact@jeffquast.com>2014-08-24 23:46:10 -0700
committerjquast <contact@jeffquast.com>2014-08-24 23:46:10 -0700
commit67e6c4ac018a0dabe50962beba537612cfb4fa22 (patch)
tree1188f80867cdf7c76447e92faec3c7391f8399ec /tests/test_which.py
parent8d96042177a6986ae5b117e31916638309b2fd03 (diff)
downloadpexpect-git-67e6c4ac018a0dabe50962beba537612cfb4fa22.tar.gz
Closes issue #104 -- cannot execute sudo(8)
Previously, misinterpreted that os.access(file, X_OK) always returns True on Solaris. Yes, but only for the uid of 0. Python issue #13706 closed "not a bug" reads to "just use os.stat()", so we went to great lengths to do so quite exhaustively. But this is wrong -- *only* when root, should we check the file modes -- os.access of X_OK works perfectly fine for non-root users. And, we should only check if any of the executable bits are set. Alas, it is true, you may execute that which you may not read -- because as root, you can always read it anyway. Verified similar solution in NetBSD test.c (/bin/test), OpenBSD ksh for its built-in test, and what FreeBSD/Darwin for their implementation of which.c.
Diffstat (limited to 'tests/test_which.py')
-rw-r--r--tests/test_which.py105
1 files changed, 90 insertions, 15 deletions
diff --git a/tests/test_which.py b/tests/test_which.py
index 83575fb..ed9493d 100644
--- a/tests/test_which.py
+++ b/tests/test_which.py
@@ -1,9 +1,14 @@
+import subprocess
import tempfile
+import shutil
+import errno
import os
import pexpect
from . import PexpectTestCase
+import pytest
+
class TestCaseWhich(PexpectTestCase.PexpectTestCase):
" Tests for pexpect.which(). "
@@ -162,27 +167,97 @@ class TestCaseWhich(PexpectTestCase.PexpectTestCase):
try:
# setup
os.environ['PATH'] = bin_dir
- with open(bin_path, 'w') as fp:
- fp.write('#!/bin/sh\necho hello, world\n')
- for should_match, mode in ((False, 0o000),
- (True, 0o005),
- (True, 0o050),
- (True, 0o500),
- (False, 0o004),
- (False, 0o040),
- (False, 0o400)):
+
+ # an interpreted script requires the ability to read,
+ # whereas a binary program requires only to be executable.
+ #
+ # to gain access to a binary program, we make a copy of
+ # the existing system program echo(1).
+ bin_echo = None
+ for pth in ('/bin/echo', '/usr/bin/echo'):
+ if os.path.exists(pth):
+ bin_echo = pth
+ break
+ bin_which = None
+ for pth in ('/bin/which', '/usr/bin/which'):
+ if os.path.exists(pth):
+ bin_which = pth
+ break
+ if not bin_echo or not bin_which:
+ pytest.skip('needs `echo` and `which` binaries')
+ shutil.copy(bin_echo, bin_path)
+
+ for should_match, mode in (
+ (False, 0o000), # ----------, no
+ (False, 0o001), # ---------x, no
+ (False, 0o010), # ------x---, no
+ (True, 0o100), # ---x------, yes
+ (False, 0o002), # --------w-, no
+ (False, 0o020), # -----w----, no
+ (False, 0o200), # --w-------, no
+ (False, 0o003), # --------wx, no
+ (False, 0o030), # -----wx---, no
+ (True, 0o300), # --wx------, yes
+ (False, 0o004), # -------r--, no
+ (False, 0o040), # ----r-----, no
+ (False, 0o400), # -r--------, no
+ (False, 0o005), # -------r-x, no
+ (False, 0o050), # ----r-x---, no
+ (True, 0o500), # -r-x------, yes
+ (False, 0o006), # -------rw-, no
+ (False, 0o060), # ----rw----, no
+ (False, 0o600), # -rw-------, no
+ (False, 0o007), # -------rwx, no
+ (False, 0o070), # ----rwx---, no
+ (True, 0o700), # -rwx------, yes
+ (False, 0o4001), # ---S-----x, no
+ (False, 0o4010), # ---S--x---, no
+ (True, 0o4100), # ---s------, yes
+ (False, 0o4003), # ---S----wx, no
+ (False, 0o4030), # ---S-wx---, no
+ (True, 0o4300), # --ws------, yes
+ (False, 0o2001), # ------S--x, no
+ (False, 0o2010), # ------s---, no
+ (True, 0o2100), # ---x--S---, yes
+
+ ):
+ mode_str = '{0:0>4o}'.format(mode)
+
+ # given file mode,
os.chmod(bin_path, mode)
- if not should_match:
- # should not be found because it is not executable
- assert pexpect.which(fname) is None
- else:
- # should match full path
- assert pexpect.which(fname) == bin_path
+ # exercise whether we may execute
+ can_execute = True
+ try:
+ subprocess.Popen(fname).wait() == 0
+ except OSError as err:
+ if err.errno != errno.EACCES:
+ raise
+ # permission denied
+ can_execute = False
+
+ assert should_match == can_execute, (
+ should_match, can_execute, mode_str)
+
+ # exercise whether which(1) would match
+ proc = subprocess.Popen((bin_which, fname),
+ env={'PATH': bin_dir},
+ stdout=subprocess.PIPE)
+ bin_which_match = bool(not proc.wait())
+ assert should_match == bin_which_match, (
+ should_match, bin_which_match, mode_str)
+
+ # finally, exercise pexpect's which(1) matches
+ # the same.
+ pexpect_match = bool(pexpect.which(fname))
+
+ assert should_match == pexpect_match == bin_which_match, (
+ should_match, pexpect_match, bin_which_match, mode_str)
finally:
# restore,
os.environ['PATH'] = save_path
+
# destroy scratch files and folders,
if os.path.exists(bin_path):
os.unlink(bin_path)