1 ## compare_shells: dash bash mksh zsh
2 ## oils_failures_allowed: 3
3 ## oils_cpp_failures_allowed: 3
4
5 #### cd and $PWD
6 cd /
7 echo $PWD
8 ## stdout: /
9
10 #### cd BAD/..
11
12 # Odd divergence in shells: dash and mksh normalize the path and don't check
13 # this error.
14 # TODO: I would like OSH to behave like bash and zsh, but separating chdir_arg
15 # and pwd_arg breaks case 17.
16
17 cd nonexistent_ZZ/..
18 echo status=$?
19 ## STDOUT:
20 status=1
21 ## END
22 ## BUG dash/mksh STDOUT:
23 status=0
24 ## END
25
26 #### cd with 2 or more args
27
28 mkdir -p foo
29 cd foo
30 echo status=$?
31 cd ..
32 echo status=$?
33
34
35 cd foo bar
36 st=$?
37 if test $st -ne 0; then
38 echo 'failed with multiple args'
39 fi
40
41 ## STDOUT:
42 status=0
43 status=0
44 failed with multiple args
45 ## END
46
47 ## BUG dash STDOUT:
48 status=0
49 status=0
50 ## END
51
52 #### cd - without OLDPWD
53
54 cd - > /dev/null # silence dash output
55 echo status=$?
56 #pwd
57
58 ## STDOUT:
59 status=1
60 ## END
61
62 ## OK mksh STDOUT:
63 status=2
64 ## END
65
66 ## BUG dash/zsh STDOUT:
67 status=0
68 ## END
69
70 #### $OLDPWD
71 cd /
72 cd $TMP
73 echo "old: $OLDPWD"
74 env | grep OLDPWD # It's EXPORTED too!
75 cd -
76 ## STDOUT:
77 old: /
78 OLDPWD=/
79 /
80 ## END
81 ## BUG mksh STDOUT:
82 old: /
83 /
84 ## END
85 ## BUG zsh STDOUT:
86 old: /
87 OLDPWD=/
88 ## END
89
90 #### pwd
91 cd /
92 pwd
93 ## STDOUT:
94 /
95 ## END
96
97 #### pwd after cd ..
98 dir=$TMP/dir-one/dir-two
99 mkdir -p $dir
100 cd $dir
101 echo $(basename $(pwd))
102 cd ..
103 echo $(basename $(pwd))
104 ## STDOUT:
105 dir-two
106 dir-one
107 ## END
108
109 #### pwd with symlink and -P
110 tmp=$TMP/builtins-pwd-1
111 mkdir -p $tmp/target
112 ln -s -f $tmp/target $tmp/symlink
113
114 cd $tmp/symlink
115
116 echo pwd:
117 basename $(pwd)
118
119 echo pwd -P:
120 basename $(pwd -P)
121
122 ## STDOUT:
123 pwd:
124 symlink
125 pwd -P:
126 target
127 ## END
128
129 #### setting $PWD doesn't affect the value of 'pwd' builtin
130 dir=/tmp/oil-spec-test/pwd
131 mkdir -p $dir
132 cd $dir
133
134 PWD=foo
135 echo before $PWD
136 pwd
137 echo after $PWD
138 ## STDOUT:
139 before foo
140 /tmp/oil-spec-test/pwd
141 after foo
142 ## END
143
144 #### unset PWD; then pwd
145 dir=/tmp/oil-spec-test/pwd
146 mkdir -p $dir
147 cd $dir
148
149 unset PWD
150 echo PWD=$PWD
151 pwd
152 echo PWD=$PWD
153 ## STDOUT:
154 PWD=
155 /tmp/oil-spec-test/pwd
156 PWD=
157 ## END
158
159 #### 'unset PWD; pwd' before any cd (tickles a rare corner case)
160 dir=/tmp/oil-spec-test/pwd-2
161 mkdir -p $dir
162 cd $dir
163
164 # ensure clean shell process state
165 $SH -c 'unset PWD; pwd'
166
167 ## STDOUT:
168 /tmp/oil-spec-test/pwd-2
169 ## END
170
171 #### lie about PWD; pwd before any cd
172 dir=/tmp/oil-spec-test/pwd-3
173 mkdir -p $dir
174 cd $dir
175
176 # ensure clean shell process state
177 $SH -c 'PWD=foo; pwd'
178
179 ## STDOUT:
180 /tmp/oil-spec-test/pwd-3
181 ## END
182
183 #### remove pwd dir
184 dir=/tmp/oil-spec-test/pwd
185 mkdir -p $dir
186 cd $dir
187 pwd
188 rmdir $dir
189 echo status=$?
190 pwd
191 echo status=$?
192 ## STDOUT:
193 /tmp/oil-spec-test/pwd
194 status=0
195 /tmp/oil-spec-test/pwd
196 status=0
197 ## END
198 ## OK mksh STDOUT:
199 /tmp/oil-spec-test/pwd
200 status=0
201 status=1
202 ## END
203
204 #### pwd in symlinked dir on shell initialization
205 tmp=$TMP/builtins-pwd-2
206 mkdir -p $tmp
207 mkdir -p $tmp/target
208 ln -s -f $tmp/target $tmp/symlink
209
210 cd $tmp/symlink
211 $SH -c 'basename $(pwd)'
212 unset PWD
213 $SH -c 'basename $(pwd)'
214
215 ## STDOUT:
216 symlink
217 target
218 ## END
219 ## OK mksh STDOUT:
220 target
221 target
222 ## END
223 ## stderr-json: ""
224
225 #### Test the current directory after 'cd ..' involving symlinks
226 dir=$TMP/symlinktest
227 mkdir -p $dir
228 cd $dir
229 mkdir -p a/b/c
230 mkdir -p a/b/d
231 ln -s -f a/b/c c > /dev/null
232 cd c
233 cd ..
234 # Expecting a c/ (since we are in symlinktest) but osh gives c d (thinks we are
235 # in b/)
236 ls
237 ## STDOUT:
238 a
239 c
240 ## END
241
242 #### cd with no arguments
243 HOME=$TMP/home
244 mkdir -p $HOME
245 cd
246 test $(pwd) = "$HOME" && echo OK
247 ## stdout: OK
248
249 #### cd to nonexistent dir
250 cd /nonexistent/dir
251 echo status=$?
252 ## stdout: status=1
253 ## OK dash/mksh stdout: status=2
254
255 #### cd away from dir that was deleted
256 dir=$TMP/cd-nonexistent
257 mkdir -p $dir
258 cd $dir
259 rmdir $dir
260 cd $TMP
261 echo $(basename $OLDPWD)
262 echo status=$?
263 ## STDOUT:
264 cd-nonexistent
265 status=0
266 ## END
267
268 #### cd permits double bare dash
269 cd -- /
270 echo $PWD
271 ## stdout: /
272
273 #### cd to symlink with -L and -P
274 targ=$TMP/cd-symtarget
275 lnk=$TMP/cd-symlink
276 mkdir -p $targ
277 ln -s $targ $lnk
278
279 # -L behavior is the default
280 cd $lnk
281 test $PWD = "$TMP/cd-symlink" && echo OK
282
283 cd -L $lnk
284 test $PWD = "$TMP/cd-symlink" && echo OK
285
286 cd -P $lnk
287 test $PWD = "$TMP/cd-symtarget" && echo OK || echo $PWD
288 ## STDOUT:
289 OK
290 OK
291 OK
292 ## END
293
294 #### cd to relative path with -L and -P
295 die() { echo "$@"; exit 1; }
296
297 targ=$TMP/cd-symtarget/subdir
298 lnk=$TMP/cd-symlink
299 mkdir -p $targ
300 ln -s $TMP/cd-symtarget $lnk
301
302 # -L behavior is the default
303 cd $lnk/subdir
304 test $PWD = "$TMP/cd-symlink/subdir" || die "failed"
305 cd ..
306 test $PWD = "$TMP/cd-symlink" && echo OK
307
308 cd $lnk/subdir
309 test $PWD = "$TMP/cd-symlink/subdir" || die "failed"
310 cd -L ..
311 test $PWD = "$TMP/cd-symlink" && echo OK
312
313 cd $lnk/subdir
314 test $PWD = "$TMP/cd-symlink/subdir" || die "failed"
315 cd -P ..
316 test $PWD = "$TMP/cd-symtarget" && echo OK || echo $PWD
317 ## STDOUT:
318 OK
319 OK
320 OK
321 ## END
322
323 #### unset PWD; cd /tmp is allowed (regression)
324
325 unset PWD; cd /tmp
326 pwd
327
328 ## STDOUT:
329 /tmp
330 ## END
331
332 #### CDPATH is respected
333
334 mkdir -p /tmp/spam/foo /tmp/eggs/foo
335
336 CDPATH='/tmp/spam:/tmp/eggs'
337
338 cd foo
339 echo status=$?
340 pwd
341
342 ## STDOUT:
343 /tmp/spam/foo
344 status=0
345 /tmp/spam/foo
346 ## END
347
348 # doesn't print the dir
349 ## BUG zsh STDOUT:
350 status=0
351 /tmp/spam/foo
352 ## END
353
354
355 #### Change directory in non-shell parent process (make or Python)
356
357 # inspired by Perl package bug
358
359 old_dir=$(pwd)
360
361 mkdir -p cpan/Encode/Byte
362
363 # Simulate make changing the dir
364 wrapped_chdir() {
365 #set -- $SH -c 'echo BEFORE; pwd; echo CD; cd Byte; echo AFTER; pwd'
366
367 set -- $SH -c 'cd Byte; pwd'
368 # strace comes out the same - one getcwd() and one chdir()
369 #set -- strace -e 'getcwd,chdir' "$@"
370
371 python2 -c '
372 from __future__ import print_function
373 import os, sys, subprocess
374
375 argv = sys.argv[1:]
376 print("Python PWD = %r" % os.getenv("PWD"), file=sys.stderr)
377 print("Python argv = %r" % argv, file=sys.stderr)
378
379 os.chdir("cpan/Encode")
380 subprocess.check_call(argv)
381 ' "$@"
382 }
383
384 #wrapped_chdir
385 new_dir=$(wrapped_chdir)
386
387 #echo $old_dir
388
389 # Make the test insensitive to absolute paths
390 echo "${new_dir##$old_dir}"
391
392 ## STDOUT:
393 /cpan/Encode/Byte
394 ## END
395
396 #### What happens when inherited $PWD and current dir disagree?
397
398 DIR=/tmp/osh-spec-cd
399 mkdir -p $DIR
400 cd $DIR
401
402 old_dir=$(pwd)
403
404 mkdir -p cpan/Encode/Byte
405
406 # Simulate make changing the dir
407 wrapped_chdir() {
408 #set -- $SH -c 'echo BEFORE; pwd; echo CD; cd Byte; echo AFTER; pwd'
409
410 # disagreement before we gert here
411 set -- $SH -c '
412 echo "PWD = $PWD"; pwd
413 cd Byte; echo cd=$?
414 echo "PWD = $PWD"; pwd
415 '
416
417 # strace comes out the same - one getcwd() and one chdir()
418 #set -- strace -e 'getcwd,chdir' "$@"
419
420 python2 -c '
421 from __future__ import print_function
422 import os, sys, subprocess
423
424 argv = sys.argv[1:]
425 print("Python argv = %r" % argv, file=sys.stderr)
426
427 os.chdir("cpan/Encode")
428 print("Python PWD = %r" % os.getenv("PWD"), file=sys.stdout)
429 sys.stdout.flush()
430
431 subprocess.check_call(argv)
432 ' "$@"
433 }
434
435 #unset PWD
436 wrapped_chdir
437
438 ## STDOUT:
439 Python PWD = '/tmp/osh-spec-cd'
440 PWD = /tmp/osh-spec-cd/cpan/Encode
441 /tmp/osh-spec-cd/cpan/Encode
442 cd=0
443 PWD = /tmp/osh-spec-cd/cpan/Encode/Byte
444 /tmp/osh-spec-cd/cpan/Encode/Byte
445 ## END
446
447 ## BUG mksh STDOUT:
448 Python PWD = None
449 PWD = /tmp/osh-spec-cd/cpan/Encode
450 /tmp/osh-spec-cd/cpan/Encode
451 cd=0
452 PWD = /tmp/osh-spec-cd/cpan/Encode/Byte
453 /tmp/osh-spec-cd/cpan/Encode/Byte
454 ## END
455
456 #### Survey of getcwd() syscall
457
458 # This is not that important -- see core/sh_init.py
459 # Instead of verifying that stat('.') == stat(PWD), which is two sycalls,
460 # OSH just calls getcwd() unconditionally.
461
462 # so C++ leak sanitizer doesn't print to stderr
463 export ASAN_OPTIONS='detect_leaks=0'
464
465 strace -e getcwd -- $SH -c 'echo hi; pwd; echo $PWD' 1> /dev/null 2> err.txt
466
467 wc -l err.txt
468 #cat err.txt
469
470 ## STDOUT:
471 1 err.txt
472 ## END
473 ## BUG mksh STDOUT:
474 2 err.txt
475 ## END