Merge pull request #173 from takluyver/update-tooling
[third_party/pexpect] / tests / test_which.py
1 import subprocess
2 import tempfile
3 import shutil
4 import errno
5 import os
6
7 import pexpect
8 from . import PexpectTestCase
9
10 import pytest
11
12
13 class TestCaseWhich(PexpectTestCase.PexpectTestCase):
14     " Tests for pexpect.which(). "
15
16     def test_which_finds_ls(self):
17         " which() can find ls(1). "
18         exercise = pexpect.which("ls")
19         assert exercise is not None
20         assert exercise.startswith('/')
21
22     def test_os_defpath_which(self):
23         " which() finds an executable in $PATH and returns its abspath. "
24         fname = 'cc'
25         bin_dir = tempfile.mkdtemp()
26         bin_path = os.path.join(bin_dir, fname)
27         save_path = os.environ['PATH']
28         save_defpath = os.defpath
29
30         try:
31             # setup
32             os.environ['PATH'] = ''
33             os.defpath = bin_dir
34             with open(bin_path, 'w') as fp:
35                 pass
36
37             # given non-executable,
38             os.chmod(bin_path, 0o400)
39
40             # exercise absolute and relative,
41             assert pexpect.which(bin_path) is None
42             assert pexpect.which(fname) is None
43
44             # given executable,
45             os.chmod(bin_path, 0o700)
46
47             # exercise absolute and relative,
48             assert pexpect.which(bin_path) == bin_path
49             assert pexpect.which(fname) == bin_path
50
51         finally:
52             # restore,
53             os.environ['PATH'] = save_path
54             os.defpath = save_defpath
55
56             # destroy scratch files and folders,
57             if os.path.exists(bin_path):
58                 os.unlink(bin_path)
59             if os.path.exists(bin_dir):
60                 os.rmdir(bin_dir)
61
62     def test_path_search_which(self):
63         " which() finds an executable in $PATH and returns its abspath. "
64         fname = 'gcc'
65         bin_dir = tempfile.mkdtemp()
66         bin_path = os.path.join(bin_dir, fname)
67         save_path = os.environ['PATH']
68         try:
69             # setup
70             os.environ['PATH'] = bin_dir
71             with open(bin_path, 'w') as fp:
72                 pass
73
74             # given non-executable,
75             os.chmod(bin_path, 0o400)
76
77             # exercise absolute and relative,
78             assert pexpect.which(bin_path) is None
79             assert pexpect.which(fname) is None
80
81             # given executable,
82             os.chmod(bin_path, 0o700)
83
84             # exercise absolute and relative,
85             assert pexpect.which(bin_path) == bin_path
86             assert pexpect.which(fname) == bin_path
87
88         finally:
89             # restore,
90             os.environ['PATH'] = save_path
91
92             # destroy scratch files and folders,
93             if os.path.exists(bin_path):
94                 os.unlink(bin_path)
95             if os.path.exists(bin_dir):
96                 os.rmdir(bin_dir)
97
98     def test_which_follows_symlink(self):
99         " which() follows symlinks and returns its path. "
100         fname = 'original'
101         symname = 'extra-crispy'
102         bin_dir = tempfile.mkdtemp()
103         bin_path = os.path.join(bin_dir, fname)
104         sym_path = os.path.join(bin_dir, symname)
105         save_path = os.environ['PATH']
106         try:
107             # setup
108             os.environ['PATH'] = bin_dir
109             with open(bin_path, 'w') as fp:
110                 pass
111             os.chmod(bin_path, 0o400)
112             os.symlink(bin_path, sym_path)
113
114             # should not be found because symlink points to non-executable
115             assert pexpect.which(symname) is None
116
117             # but now it should -- because it is executable
118             os.chmod(bin_path, 0o700)
119             assert pexpect.which(symname) == sym_path
120
121         finally:
122             # restore,
123             os.environ['PATH'] = save_path
124
125             # destroy scratch files, symlinks, and folders,
126             if os.path.exists(sym_path):
127                 os.unlink(sym_path)
128             if os.path.exists(bin_path):
129                 os.unlink(bin_path)
130             if os.path.exists(bin_dir):
131                 os.rmdir(bin_dir)
132
133     def test_which_should_not_match_folders(self):
134         " Which does not match folders, even though they are executable. "
135         # make up a path and insert a folder that is 'executable', a naive
136         # implementation might match (previously pexpect versions 3.2 and
137         # sh versions 1.0.8, reported by @lcm337.)
138         fname = 'g++'
139         bin_dir = tempfile.mkdtemp()
140         bin_dir2 = os.path.join(bin_dir, fname)
141         save_path = os.environ['PATH']
142         try:
143             os.environ['PATH'] = bin_dir
144             os.mkdir(bin_dir2, 0o755)
145             # should not be found because it is not executable *file*,
146             # but rather, has the executable bit set, as a good folder
147             # should -- it should not be returned because it fails isdir()
148             exercise = pexpect.which(fname)
149             assert exercise is None
150
151         finally:
152             # restore,
153             os.environ['PATH'] = save_path
154             # destroy scratch folders,
155             for _dir in (bin_dir2, bin_dir,):
156                 if os.path.exists(_dir):
157                     os.rmdir(_dir)
158
159     def test_which_should_match_other_group_user(self):
160         " which() returns executables by other, group, and user ownership. "
161         # create an executable and test that it is found using which() for
162         # each of the 'other', 'group', and 'user' permission bits.
163         fname = 'g77'
164         bin_dir = tempfile.mkdtemp()
165         bin_path = os.path.join(bin_dir, fname)
166         save_path = os.environ['PATH']
167         try:
168             # setup
169             os.environ['PATH'] = bin_dir
170
171             # an interpreted script requires the ability to read,
172             # whereas a binary program requires only to be executable.
173             #
174             # to gain access to a binary program, we make a copy of
175             # the existing system program echo(1).
176             bin_echo = None
177             for pth in ('/bin/echo', '/usr/bin/echo'):
178                 if os.path.exists(pth):
179                     bin_echo = pth
180                     break
181             bin_which = None
182             for pth in ('/bin/which', '/usr/bin/which'):
183                 if os.path.exists(pth):
184                     bin_which = pth
185                     break
186             if not bin_echo or not bin_which:
187                 pytest.skip('needs `echo` and `which` binaries')
188             shutil.copy(bin_echo, bin_path)
189             isroot = os.getuid() == 0
190             for should_match, mode in (
191                 # note that although the file may have matching 'group' or
192                 # 'other' executable permissions, it is *not* executable
193                 # because the current uid is the owner of the file -- which
194                 # takes precedence
195                 (False,  0o000),   # ----------, no
196                 (isroot, 0o001),   # ---------x, no
197                 (isroot, 0o010),   # ------x---, no
198                 (True,   0o100),   # ---x------, yes
199                 (False,  0o002),   # --------w-, no
200                 (False,  0o020),   # -----w----, no
201                 (False,  0o200),   # --w-------, no
202                 (isroot, 0o003),   # --------wx, no
203                 (isroot, 0o030),   # -----wx---, no
204                 (True,   0o300),   # --wx------, yes
205                 (False,  0o004),   # -------r--, no
206                 (False,  0o040),   # ----r-----, no
207                 (False,  0o400),   # -r--------, no
208                 (isroot, 0o005),   # -------r-x, no
209                 (isroot, 0o050),   # ----r-x---, no
210                 (True,   0o500),   # -r-x------, yes
211                 (False,  0o006),   # -------rw-, no
212                 (False,  0o060),   # ----rw----, no
213                 (False,  0o600),   # -rw-------, no
214                 (isroot, 0o007),   # -------rwx, no
215                 (isroot, 0o070),   # ----rwx---, no
216                 (True,   0o700),   # -rwx------, yes
217                 (isroot, 0o4001),  # ---S-----x, no
218                 (isroot, 0o4010),  # ---S--x---, no
219                 (True,   0o4100),  # ---s------, yes
220                 (isroot, 0o4003),  # ---S----wx, no
221                 (isroot, 0o4030),  # ---S-wx---, no
222                 (True,   0o4300),  # --ws------, yes
223                 (isroot, 0o2001),  # ------S--x, no
224                 (isroot, 0o2010),  # ------s---, no
225                 (True,   0o2100),  # ---x--S---, yes
226
227             ):
228                 mode_str = '{0:0>4o}'.format(mode)
229
230                 # given file mode,
231                 os.chmod(bin_path, mode)
232
233                 # exercise whether we may execute
234                 can_execute = True
235                 try:
236                     subprocess.Popen(fname).wait() == 0
237                 except OSError as err:
238                     if err.errno != errno.EACCES:
239                         raise
240                     # permission denied
241                     can_execute = False
242
243                 assert should_match == can_execute, (
244                     should_match, can_execute, mode_str)
245
246                 # exercise whether which(1) would match
247                 proc = subprocess.Popen((bin_which, fname),
248                                         env={'PATH': bin_dir},
249                                         stdout=subprocess.PIPE)
250                 bin_which_match = bool(not proc.wait())
251                 assert should_match == bin_which_match, (
252                     should_match, bin_which_match, mode_str)
253
254                 # finally, exercise pexpect's which(1) matches
255                 # the same.
256                 pexpect_match = bool(pexpect.which(fname))
257
258                 assert should_match == pexpect_match == bin_which_match, (
259                     should_match, pexpect_match, bin_which_match, mode_str)
260
261         finally:
262             # restore,
263             os.environ['PATH'] = save_path
264
265             # destroy scratch files and folders,
266             if os.path.exists(bin_path):
267                 os.unlink(bin_path)
268             if os.path.exists(bin_dir):
269                 os.rmdir(bin_dir)