OILS / spec / vars-special.test.sh View on Github | oils.pub

797 lines, 391 significant
1## oils_failures_allowed: 3
2## compare_shells: dash bash-4.4 mksh zsh
3
4
5# NOTE:
6# - $! is tested in background.test.sh
7# - $- is tested in sh-options
8#
9# TODO: It would be nice to make a table, like:
10#
11# $$ $BASHPID $PPID $SHLVL $BASH_SUBSHELL
12# X
13# (Subshell, Command Sub, Pipeline, Spawn $0)
14#
15# And see whether the variable changed.
16
17#### $PWD is set
18# Just test that it has a slash for now.
19echo $PWD | grep -q /
20echo status=$?
21## STDOUT:
22status=0
23## END
24
25#### $PWD is not only set, but exported
26env | grep -q PWD
27echo status=$?
28## stdout: status=0
29## BUG mksh stdout: status=1
30
31#### $PATH is set if unset at startup
32
33# WORKAROUND for Python version of bin/osh -- we can't run bin/oils_for_unix.py
34# because it a shebang #!/usr/bin/env python2
35# This test is still useful for the C++ oils-for-unix.
36
37case $SH in
38 */bin/osh)
39 echo yes
40 echo yes
41 exit
42 ;;
43esac
44
45# Get absolute path before changing PATH
46sh=$(which $SH)
47
48old_path=$PATH
49unset PATH
50
51$sh -c 'echo $PATH' > path.txt
52
53PATH=$old_path
54
55# looks like PATH=/usr/bin:/bin for mksh, but more complicated for others
56# cat path.txt
57
58# should contain /usr/bin
59if egrep -q '(^|:)/usr/bin($|:)' path.txt; then
60 echo yes
61fi
62
63# should contain /bin
64if egrep -q '(^|:)/bin($|:)' path.txt ; then
65 echo yes
66fi
67
68## STDOUT:
69yes
70yes
71## END
72
73#### $HOME is NOT set
74case $SH in *zsh) echo 'zsh sets HOME'; exit ;; esac
75
76home=$(echo $HOME)
77test "$home" = ""
78echo status=$?
79
80env | grep HOME
81echo status=$?
82
83# not in interactive shell either
84$SH -i -c 'echo $HOME' | grep /
85echo status=$?
86
87## STDOUT:
88status=0
89status=1
90status=1
91## END
92## BUG zsh STDOUT:
93zsh sets HOME
94## END
95
96#### Vars set interactively only: $HISTFILE
97case $SH in dash|mksh|zsh) exit ;; esac
98
99$SH --norc --rcfile /dev/null -c 'echo histfile=${HISTFILE:+yes}'
100$SH --norc --rcfile /dev/null -i -c 'echo histfile=${HISTFILE:+yes}'
101
102## STDOUT:
103histfile=
104histfile=yes
105## END
106
107## N-I dash/mksh/zsh STDOUT:
108## END
109
110#### Some vars are set, even without startup file, or env: PATH, PWD
111
112flags=''
113case $SH in
114 dash) exit ;;
115 bash*)
116 flags='--noprofile --norc --rcfile /devnull'
117 ;;
118 osh)
119 flags='--rcfile /devnull'
120 ;;
121esac
122
123sh_path=$(which $SH)
124
125case $sh_path in
126 */bin/osh)
127 # Hack for running with Python2
128 export PYTHONPATH="$REPO_ROOT:$REPO_ROOT/vendor"
129 sh_prefix="$(which python2) $REPO_ROOT/bin/oils_for_unix.py osh"
130 ;;
131 *)
132 sh_prefix=$sh_path
133 ;;
134esac
135
136#echo PATH=$PATH
137
138
139# mksh has typeset, not declare
140# bash exports PWD, but not PATH PS4
141
142/usr/bin/env -i PYTHONPATH=$PYTHONPATH $sh_prefix $flags -c 'typeset -p PATH PWD PS4' >&2
143echo path pwd ps4 $?
144
145/usr/bin/env -i PYTHONPATH=$PYTHONPATH $sh_prefix $flags -c 'typeset -p SHELLOPTS' >&2
146echo shellopts $?
147
148# bash doesn't set HOME, mksh and zsh do
149/usr/bin/env -i PYTHONPATH=$PYTHONPATH $sh_prefix $flags -c 'typeset -p HOME PS1' >&2
150echo home ps1 $?
151
152# IFS is set, but not exported
153/usr/bin/env -i PYTHONPATH=$PYTHONPATH $sh_prefix $flags -c 'typeset -p IFS' >&2
154echo ifs $?
155
156## STDOUT:
157path pwd ps4 0
158shellopts 0
159home ps1 1
160ifs 0
161## END
162
163## OK mksh STDOUT:
164path pwd ps4 0
165shellopts 0
166home ps1 0
167ifs 0
168## END
169
170## OK zsh STDOUT:
171path pwd ps4 0
172shellopts 1
173home ps1 0
174ifs 0
175## END
176
177## N-I dash STDOUT:
178## END
179
180#### UID EUID PPID can't be changed
181
182# bash makes these 3 read-only
183{
184 UID=xx $SH -c 'echo uid=$UID'
185
186 EUID=xx $SH -c 'echo euid=$EUID'
187
188 PPID=xx $SH -c 'echo ppid=$PPID'
189
190} > out.txt
191
192# bash shows that vars are readonly
193# zsh shows other errors
194# cat out.txt
195#echo
196
197grep '=xx' out.txt
198echo status=$?
199
200## STDOUT:
201status=1
202## END
203## BUG dash/mksh STDOUT:
204uid=xx
205euid=xx
206status=0
207## END
208
209#### HOSTNAME OSTYPE can be changed
210case $SH in zsh) exit ;; esac
211
212#$SH -c 'echo hostname=$HOSTNAME'
213
214HOSTNAME=x $SH -c 'echo hostname=$HOSTNAME'
215OSTYPE=x $SH -c 'echo ostype=$OSTYPE'
216echo
217
218#PS4=x $SH -c 'echo ps4=$PS4'
219
220# OPTIND is special
221#OPTIND=xx $SH -c 'echo optind=$OPTIND'
222
223
224## STDOUT:
225hostname=x
226ostype=x
227
228## END
229
230## BUG zsh STDOUT:
231## END
232
233
234#### $1 .. $9 are scoped, while $0 is not
235fun() {
236 case $0 in
237 *sh)
238 echo 'sh'
239 ;;
240 *sh-*) # bash-4.4 is OK
241 echo 'sh'
242 ;;
243 esac
244
245 echo $1 $2
246}
247fun a b
248
249## STDOUT:
250sh
251a b
252## END
253## BUG zsh STDOUT:
254a b
255## END
256
257#### $?
258echo $? # starts out as 0
259sh -c 'exit 33'
260echo $?
261## STDOUT:
2620
26333
264## END
265## status: 0
266
267#### $#
268set -- 1 2 3 4
269echo $#
270## stdout: 4
271## status: 0
272
273#### $$ looks like a PID
274echo $$ | egrep -q '[0-9]+' # Test that it has decimal digits
275echo status=$?
276## STDOUT:
277status=0
278## END
279
280#### $$ doesn't change with subshell or command sub
281# Just test that it has decimal digits
282set -o errexit
283die() {
284 echo 1>&2 "$@"; exit 1
285}
286parent=$$
287test -n "$parent" || die "empty PID in parent"
288( child=$$
289 test -n "$child" || die "empty PID in subshell"
290 test "$parent" = "$child" || die "should be equal: $parent != $child"
291 echo 'subshell OK'
292)
293echo $( child=$$
294 test -n "$child" || die "empty PID in command sub"
295 test "$parent" = "$child" || die "should be equal: $parent != $child"
296 echo 'command sub OK'
297 )
298exit 3 # make sure we got here
299## status: 3
300## STDOUT:
301subshell OK
302command sub OK
303## END
304
305#### $BASHPID DOES change with subshell and command sub
306set -o errexit
307die() {
308 echo 1>&2 "$@"; exit 1
309}
310parent=$BASHPID
311test -n "$parent" || die "empty BASHPID in parent"
312( child=$BASHPID
313 test -n "$child" || die "empty BASHPID in subshell"
314 test "$parent" != "$child" || die "should not be equal: $parent = $child"
315 echo 'subshell OK'
316)
317echo $( child=$BASHPID
318 test -n "$child" || die "empty BASHPID in command sub"
319 test "$parent" != "$child" ||
320 die "should not be equal: $parent = $child"
321 echo 'command sub OK'
322 )
323exit 3 # make sure we got here
324
325# mksh also implements BASHPID!
326
327## status: 3
328## STDOUT:
329subshell OK
330command sub OK
331## END
332## N-I dash/zsh status: 1
333## N-I dash/zsh stdout-json: ""
334
335#### Background PID $! looks like a PID
336sleep 0.01 &
337pid=$!
338wait
339echo $pid | egrep '[0-9]+' >/dev/null
340echo status=$?
341## stdout: status=0
342
343#### $PPID
344echo $PPID | egrep '[0-9]+'
345## status: 0
346
347# NOTE: There is also $BASHPID
348
349#### $PIPESTATUS
350echo hi | sh -c 'cat; exit 33' | wc -l >/dev/null
351argv.py "${PIPESTATUS[@]}"
352## status: 0
353## STDOUT:
354['0', '33', '0']
355## END
356## N-I dash stdout-json: ""
357## N-I dash status: 2
358## N-I zsh STDOUT:
359['']
360## END
361
362#### $RANDOM
363expr $0 : '.*/osh$' && exit 99 # Disabled because of spec-runner.sh issue
364echo $RANDOM | egrep '[0-9]+'
365## status: 0
366## N-I dash status: 1
367
368#### $UID and $EUID
369# These are both bash-specific.
370set -o errexit
371echo $UID | egrep -o '[0-9]+' >/dev/null
372echo $EUID | egrep -o '[0-9]+' >/dev/null
373echo status=$?
374## stdout: status=0
375## N-I dash/mksh stdout-json: ""
376## N-I dash/mksh status: 1
377
378#### $OSTYPE is non-empty
379test -n "$OSTYPE"
380echo status=$?
381## STDOUT:
382status=0
383## END
384## N-I dash/mksh STDOUT:
385status=1
386## END
387
388#### $HOSTNAME
389test "$HOSTNAME" = "$(hostname)"
390echo status=$?
391## STDOUT:
392status=0
393## END
394## N-I dash/mksh/zsh STDOUT:
395status=1
396## END
397
398#### $LINENO is the current line, not line of function call
399echo $LINENO # first line
400g() {
401 argv.py $LINENO # line 3
402}
403f() {
404 argv.py $LINENO # line 6
405 g
406 argv.py $LINENO # line 8
407}
408f
409## STDOUT:
4101
411['6']
412['3']
413['8']
414## END
415## BUG zsh STDOUT:
4161
417['1']
418['1']
419['3']
420## END
421## BUG dash STDOUT:
4221
423['2']
424['2']
425['4']
426## END
427
428#### $LINENO in "bare" redirect arg (bug regression)
429filename=$TMP/bare3
430rm -f $filename
431> $TMP/bare$LINENO
432test -f $filename && echo written
433echo $LINENO
434## STDOUT:
435written
4365
437## END
438## BUG zsh STDOUT:
439## END
440
441#### $LINENO in redirect arg (bug regression)
442filename=$TMP/lineno_regression3
443rm -f $filename
444echo x > $TMP/lineno_regression$LINENO
445test -f $filename && echo written
446echo $LINENO
447## STDOUT:
448written
4495
450## END
451
452#### $LINENO in [[
453echo one
454[[ $LINENO -eq 2 ]] && echo OK
455## STDOUT:
456one
457OK
458## END
459## N-I dash status: 127
460## N-I dash stdout: one
461## N-I mksh status: 1
462## N-I mksh stdout: one
463
464#### $LINENO in ((
465echo one
466(( x = LINENO ))
467echo $x
468## STDOUT:
469one
4702
471## END
472## N-I dash STDOUT:
473one
474
475## END
476
477#### $LINENO in for loop
478# hm bash doesn't take into account the word break. That's OK; we won't either.
479echo one
480for x in \
481 $LINENO zzz; do
482 echo $x
483done
484## STDOUT:
485one
4862
487zzz
488## END
489## OK mksh STDOUT:
490one
4911
492zzz
493## END
494
495#### $LINENO in other for loops
496set -- a b c
497for x; do
498 echo $LINENO $x
499done
500## STDOUT:
5013 a
5023 b
5033 c
504## END
505
506#### $LINENO in for (( loop
507# This is a real edge case that I'm not sure we care about. We would have to
508# change the span ID inside the loop to make it really correct.
509echo one
510for (( i = 0; i < $LINENO; i++ )); do
511 echo $i
512done
513## STDOUT:
514one
5150
5161
517## END
518## N-I dash stdout: one
519## N-I dash status: 2
520## BUG mksh stdout: one
521## BUG mksh status: 1
522
523#### $LINENO for assignment
524a1=$LINENO a2=$LINENO
525b1=$LINENO b2=$LINENO
526echo $a1 $a2
527echo $b1 $b2
528## STDOUT:
5291 1
5302 2
531## END
532
533#### $LINENO in case
534case $LINENO in
535 1) echo 'got line 1' ;;
536 *) echo line=$LINENO
537esac
538## STDOUT:
539got line 1
540## END
541## BUG mksh STDOUT:
542line=3
543## END
544
545#### $_ with simple command and evaluation
546
547name=world
548echo "hi $name"
549echo "$_"
550## STDOUT:
551hi world
552hi world
553## END
554## N-I dash/mksh STDOUT:
555hi world
556
557## END
558
559#### $_ and ${_}
560case $SH in dash|mksh) exit ;; esac
561
562_var=value
563
564: 42
565echo $_ $_var ${_}var
566
567: 'foo'"bar"
568echo $_
569
570## STDOUT:
57142 value 42var
572foobar
573## END
574## N-I dash/mksh stdout-json: ""
575
576#### $_ with word splitting
577case $SH in dash|mksh) exit ;; esac
578
579setopt shwordsplit # for ZSH
580
581x='with spaces'
582: $x
583echo $_
584
585## STDOUT:
586spaces
587## END
588## N-I dash/mksh stdout-json: ""
589
590#### $_ with pipeline and subshell
591case $SH in dash|mksh) exit ;; esac
592
593shopt -s lastpipe
594
595seq 3 | echo last=$_
596
597echo pipeline=$_
598
599( echo subshell=$_ )
600echo done=$_
601
602## STDOUT:
603last=
604pipeline=last=
605subshell=pipeline=last=
606done=pipeline=last=
607## END
608
609# very weird semantics for zsh!
610## OK zsh STDOUT:
611last=3
612pipeline=last=3
613subshell=
614done=
615## END
616
617## N-I dash/mksh stdout-json: ""
618
619
620#### $_ with && and ||
621case $SH in dash|mksh) exit ;; esac
622
623echo hi && echo last=$_
624echo and=$_
625
626echo hi || echo last=$_
627echo or=$_
628
629## STDOUT:
630hi
631last=hi
632and=last=hi
633hi
634or=hi
635## END
636
637## N-I dash/mksh stdout-json: ""
638
639#### $_ is not reset with (( and [[
640
641# bash is inconsistent because it does it for pipelines and assignments, but
642# not (( and [[
643
644case $SH in dash|mksh) exit ;; esac
645
646echo simple
647(( a = 2 + 3 ))
648echo "(( $_"
649
650[[ a == *.py ]]
651echo "[[ $_"
652
653## STDOUT:
654simple
655(( simple
656[[ (( simple
657## END
658
659## N-I dash/mksh stdout-json: ""
660
661
662#### $_ with assignments, arrays, etc.
663case $SH in dash|mksh) exit ;; esac
664
665: foo
666echo "colon [$_]"
667
668s=bar
669echo "bare assign [$_]"
670
671# zsh uses declare; bash uses s=bar
672declare s=bar
673echo "declare [$_]"
674
675# zsh remains s:declare, bash resets it
676a=(1 2)
677echo "array [$_]"
678
679# zsh sets it to declare, bash uses the LHS a
680declare a=(1 2)
681echo "declare array [$_]"
682
683declare -g d=(1 2)
684echo "declare flag [$_]"
685
686## STDOUT:
687colon [foo]
688bare assign []
689declare [s=bar]
690array []
691declare array [a]
692declare flag [d]
693## END
694
695## OK zsh STDOUT:
696colon [foo]
697bare assign []
698declare [declare]
699array [declare [declare]]
700declare array [declare]
701declare flag [-g]
702## END
703
704## OK osh STDOUT:
705colon [foo]
706bare assign [colon [foo]]
707declare [bare assign [colon [foo]]]
708array [declare [bare assign [colon [foo]]]]
709declare array [array [declare [bare assign [colon [foo]]]]]
710declare flag [declare array [array [declare [bare assign [colon [foo]]]]]]
711## END
712
713## N-I dash/mksh stdout-json: ""
714
715#### $_ with loop
716
717case $SH in dash|mksh) exit ;; esac
718
719# zsh resets it when in a loop
720
721echo init
722echo begin=$_
723for x in 1 2 3; do
724 echo prev=$_
725done
726
727## STDOUT:
728init
729begin=init
730prev=begin=init
731prev=prev=begin=init
732prev=prev=prev=begin=init
733## END
734
735## OK zsh STDOUT:
736init
737begin=init
738prev=
739prev=prev=
740prev=prev=prev=
741## END
742## N-I dash/mksh stdout-json: ""
743
744
745#### $_ is not undefined on first use
746set -e
747
748x=$($SH -u -c 'echo prev=$_')
749echo status=$?
750
751# bash and mksh set $_ to $0 at first; zsh is empty
752#echo "$x"
753
754## STDOUT:
755status=0
756## END
757
758## N-I dash status: 2
759## N-I dash stdout-json: ""
760
761#### BASH_VERSION / OILS_VERSION
762case $SH in
763 bash*)
764 # BASH_VERSION=zz
765
766 echo $BASH_VERSION | egrep -o '4\.4\.0' > /dev/null
767 echo matched=$?
768 ;;
769 *osh)
770 # note: version string is mutable like in bash. I guess that's useful for
771 # testing? We might want a strict mode to eliminate that?
772
773 echo $OILS_VERSION | egrep -o '[0-9]+\.[0-9]+\.' > /dev/null
774 echo matched=$?
775 ;;
776 *)
777 echo 'no version'
778 ;;
779esac
780## STDOUT:
781matched=0
782## END
783## N-I dash/mksh/zsh STDOUT:
784no version
785## END
786
787#### $SECONDS
788
789# most likely 0 seconds, but in CI I've seen 1 second
790echo $SECONDS | awk '/[0-9]+/ { print "ok" }'
791
792## status: 0
793## STDOUT:
794ok
795## END
796## N-I dash STDOUT:
797## END