OILS / test / runtime-errors.sh View on Github | oils.pub

1350 lines, 698 significant
1#!/usr/bin/env bash
2#
3# A file that tickles many runtime errors, to see the error message.
4# Sometimes spec tests are better - 'test/spec.sh smoke -v -d' can also show
5# errors clearly.
6#
7# Usage:
8# test/runtime-errors.sh <function name>
9#
10# TODO: Migrate the unquoted-* functions below to test-* functions, which means
11# adding assertions.
12
13source test/common.sh
14source test/sh-assert.sh # banner, _assert-sh-status
15
16# Note: should run in bash/dash mode, where we don't check errors
17OSH=${OSH:-bin/osh}
18YSH=${YSH:-bin/ysh}
19
20_run-test-func() {
21 ### Run a function, and optionally assert status
22
23 local test_func=$1
24 local expected_status=${2:-}
25
26 echo
27 echo "===== TEST function: $test_func ====="
28 echo
29
30 $OSH $0 $test_func
31
32 status=$?
33 if test -n "$expected_status"; then
34 if test $status != $expected_status; then
35 die "*** Test $test_func -> status $status, expected $expected_status"
36 fi
37 fi
38
39 echo "----- STATUS: $?"
40 echo
41}
42
43test-FAIL() {
44 ### Make sure the assertions work
45
46 # Error string
47 _osh-error-1 'echo hi > /zzz'
48
49 return
50
51 # doesn't fail
52 _osh-error-2 'echo hi'
53
54 echo nope
55}
56
57#
58# PARSE ERRORS
59#
60
61test-source_bad_syntax() {
62 cat >_tmp/bad-syntax.sh <<EOF
63if foo; echo ls; fi
64EOF
65 _osh-error-2 '. _tmp/bad-syntax.sh'
66}
67
68# NOTE:
69# - bash correctly reports line 25 (24 would be better)
70# - mksh: no line number
71# - zsh: line 2 of eval, which doesn't really help.
72# - dash: ditto, line 2 of eval
73test-eval_bad_syntax() {
74 _osh-error-2 '
75code="if foo; echo ls; fi"
76eval "echo --
77 $code"
78'
79}
80
81#
82# COMMAND ERRORS
83#
84
85test-no_such_command() {
86 _osh-error-X 127 'set -o errexit; ZZZZZ; echo UNREACHABLE'
87}
88
89test-no_such_command_commandsub() {
90 _osh-should-run 'set -o errexit; echo $(ZZZZZ); echo UNREACHABLE'
91 _osh-error-X 127 'set -o errexit; shopt -s command_sub_errexit; echo $(ZZZZZ); echo UNREACHABLE'
92}
93
94unquoted-no_such_command_heredoc() {
95 set -o errexit
96
97 # Note: bash gives the line of the beginning of the here doc! Not the actual
98 # line.
99 cat <<EOF
100one
101$(ZZZZZ)
102three
103EOF
104 echo 'SHOULD NOT GET HERE'
105}
106
107test-failed_command() {
108 _osh-error-1 'set -o errexit; false; echo UNREACHABLE'
109}
110
111# This quotes the same line of code twice, but maybe that's OK. At least there
112# is different column information.
113test-errexit_usage_error() {
114 _osh-error-2 'set -o errexit; type -z'
115}
116
117test-errexit_subshell() {
118 # Note: for loops, while loops don't trigger errexit; their components do
119 _osh-error-X 42 'set -o errexit; ( echo subshell; exit 42; )'
120}
121
122TODO-BUG-test-errexit_pipeline() {
123 # We don't blame the right location here
124
125 # BUG: what happnened here? Is there a race?
126 local code='set -o errexit; set -o pipefail; echo subshell | cat | exit 42 | wc -l'
127
128 #local code='set -o errexit; set -o pipefail; echo subshell | cat | exit 42'
129
130 bash -c "$code"
131 echo status=$?
132
133 _osh-error-X 42 "$code"
134}
135
136test-errexit_dbracket() {
137 _osh-error-1 'set -o errexit; [[ -n "" ]]; echo UNREACHABLE'
138}
139
140shopt -s expand_aliases
141# Why can't this be in the function?
142alias foo='echo hi; ls '
143
144test-errexit_alias() {
145 _osh-error-1 'set -o errexit; type foo; foo /nonexistent'
146}
147
148_sep() {
149 echo
150 echo '---------------------------'
151}
152
153test-command-not-found() {
154 _osh-error-X 127 'findz'
155}
156
157test-errexit-one-process() {
158 # two quotations of same location: not found then errexit
159 _ysh-error-X 127 'zz'
160
161 _sep
162
163 # two quotations, different location
164 _ysh-error-1 'echo hi > ""'
165
166 _sep
167
168 _ysh-error-1 'shopt -s failglob; echo *.ZZZZ'
169
170 _sep
171
172 _ysh-error-1 'cd /x'
173
174 _sep
175
176 # TODO: remove duplicate snippet
177 _ysh-error-X 126 './README.md; echo hi'
178
179 # one location
180 _ysh-error-2 'ls /x; echo $?'
181
182 _sep
183
184 _ysh-error-2 'var cmd = "ls"; $cmd /x; echo $?'
185
186 _sep
187
188 # one location
189 _ysh-error-1 'echo $undef'
190
191 _sep
192
193 # Show multiple "nested" errors, and then errexit
194 _osh-error-1 '
195eval "("
196echo status=$?
197
198eval ")"
199echo status=$?
200
201set -e; shopt -s verbose_errexit
202false
203echo DONE
204'
205
206 _sep
207 _osh-error-1 'shopt --set ysh:upgrade; [[ 0 -eq 1 ]]'
208
209 # not parsed
210 _ysh-error-2 '[[ 0 -eq 1 ]]'
211
212 _sep
213
214 _osh-error-1 'shopt --set ysh:upgrade; (( 0 ))'
215
216 # not parsed
217 _ysh-error-2 '(( 0 ))'
218}
219
220test-errexit-multiple-processes() {
221 ### A representative set of errors. For consolidating code quotations
222
223 # command_sub_errexit. Hm this gives 2 errors as well, because of inherit_errexit
224 _ysh-error-1 'echo t=$(true) f=$(false; true)'
225 #return
226
227 _sep
228
229 # BUG introduced by shopt -s no_last_fork: Even though set -o pipefail is on
230 # in YSH, the entire shell does NOT exit!
231 #
232 # This is because 'wc -l' does exec. And then there is nothing to "modify"
233 # the exit status based on pipefail.
234 #
235 # So it's actually unsound to do this optmization when set -o pipefail is on.
236 # Combined with shopt -s lastpipe
237
238 #_ysh-should-run 'ls | false | wc -l'
239 _ysh-error-1 'ls | false | wc -l'
240
241 _sep
242
243 # note: need trailing echo to prevent pipeline optimization
244 _ysh-error-X 42 'ls | { echo hi; forkwait { exit 42 }; } | wc -l; echo'
245
246 _sep
247
248 # Showing errors for THREE PIDs here! That is technically correct, but
249 # noisy.
250 _ysh-error-1 '{ echo one; forkwait { exit 42 }; } |\
251{ false; wc -l; }'
252
253 _sep
254
255 # realistic example
256 _ysh-error-1 '{ ls; false; } \
257| wc -l
258'
259
260 _sep
261
262 # Three errors!
263 _ysh-error-1 '{ ls; forkwait { false; true }; } | wc -l; echo hi'
264
265 _sep
266
267 _ysh-error-X 127 'ls <(sort YY) <(zz); echo hi'
268
269 # 2 kinds of errors
270 _sep
271 _ysh-error-X 127 'zz <(sort YY) <(sort ZZ); echo hi'
272
273 # This one has badly interleaved errors!
274 _sep
275 _ysh-error-X 127 'yy | zz'
276
277 _sep
278 _osh-error-1 'shopt -s ysh:upgrade; echo $([[ 0 -eq 1 ]])'
279
280 _sep
281 _osh-error-1 'shopt -s ysh:upgrade; var y = $([[ 0 -eq 1 ]])'
282}
283
284
285_strict-errexit-case() {
286 local code=$1
287
288 #case-banner "[strict_errexit] $code"
289
290 _osh-error-1 \
291 "set -o errexit; shopt -s strict_errexit; $code"
292 echo
293}
294
295test-strict-errexit-1() {
296 # Test out all the location info
297
298 _strict-errexit-case '! { echo 1; echo 2; }'
299
300 _strict-errexit-case '{ echo 1; echo 2; } && true'
301 _strict-errexit-case '{ echo 1; echo 2; } || true'
302 _strict-errexit-case '{ echo 1; echo 2; } >/dev/null || true'
303
304 # More chains
305 _strict-errexit-case '{ echo 1; echo 2; } && true && true'
306 _strict-errexit-case 'true && { echo 1; echo 2; } || true || true'
307 _strict-errexit-case 'true && true && { echo 1; echo 2; } || true || true'
308
309 _strict-errexit-case 'if { echo 1; echo 2; }; then echo IF; fi'
310 _strict-errexit-case 'if { echo 1; echo 2; } >/dev/null; then echo IF; fi'
311
312 _strict-errexit-case 'while { echo 1; echo 2; }; do echo WHILE; done'
313 _strict-errexit-case 'until { echo 1; echo 2; }; do echo UNTIL; done'
314
315 # Must be separate lines for parsing options to take effect
316 _strict-errexit-case 'shopt -s ysh:upgrade
317 proc p { echo p }
318 if p { echo hi }'
319}
320
321test-strict-errexit-conditionals() {
322 # this works, even though this is a subshell
323 _strict-errexit-case '
324myfunc() { return 1; }
325
326while ( myfunc )
327do
328 echo yes
329done
330'
331
332 # Conditional - Proc - Child Interpreter Problem (command sub)
333 # Same problem here. A proc run in a command sub LOSES the exit code.
334 _strict-errexit-case '
335myfunc() { return 1; }
336
337while test "$(myfunc)" != ""
338do
339 echo yes
340done
341'
342
343 # Process Sub is be disallowed; it could invoke a proc!
344 _strict-errexit-case '
345myfunc() { return 1; }
346
347if cat <(ls)
348then
349 echo yes
350fi
351'
352
353 # Conditional - Proc - Child Interpreter Problem (pipeline)
354 _strict-errexit-case '
355myfunc() {
356 return 1
357}
358
359set -o pipefail
360while myfunc | cat
361do
362 echo yes
363done
364'
365
366 # regression for issue #1107 bad error message
367 # Also revealed #1113: the strict_errexit check was handled inside the
368 # command sub process!
369 _strict-errexit-case '
370myfunc() {
371 return 1
372}
373
374foo=$(true)
375
376# test assignment without proc
377while bar=$(false)
378do
379 echo yes
380done
381
382# issue 1007 was caused using command.ShAssignment, rather than the more common
383# command.Sentence with ;
384while spam=$(myfunc)
385do
386 echo yes
387done
388'
389}
390
391# OLD WAY OF BLAMING
392# Note: most of these don't fail
393test-strict-errexit-old() {
394 # Test out all the location info
395
396 # command.Pipeline.
397 _strict-errexit-case 'if ls | wc -l; then echo Pipeline; fi'
398 _strict-errexit-case 'if ! ls | wc -l; then echo failed; fi'
399
400 # This one is ALLOWED
401 #_strict-errexit-case 'if ! ls; then echo Pipeline; fi'
402
403 # command.AndOr
404 #_strict-errexit-case 'if echo a && echo b; then echo AndOr; fi'
405
406 # command.DoGroup
407 _strict-errexit-case '! for x in a; do echo $x; done'
408
409 # command.BraceGroup
410 _strict-errexit-case '_func() { echo; }; ! _func'
411 _strict-errexit-case '! { echo brace; }; echo "should not get here"'
412
413 # command.Subshell
414 _strict-errexit-case '! ( echo subshell ); echo "should not get here"'
415
416 # command.WhileUntil
417 _strict-errexit-case '! while false; do echo while; done; echo "should not get here"'
418
419 # command.If
420 _strict-errexit-case '! if true; then false; fi; echo "should not get here"'
421
422 # command.Case
423 _strict-errexit-case '! case x in x) echo x ;; esac; echo "should not get here"'
424
425 # command.TimeBlock
426 _strict-errexit-case '! time echo hi; echo "should not get here"'
427
428 # Command Sub
429 _strict-errexit-case '! echo $(echo hi); echo "should not get here"'
430}
431
432unquoted-pipefail() {
433 false | wc -l
434
435 set -o errexit
436 set -o pipefail
437 false | wc -l
438
439 echo 'SHOULD NOT GET HERE'
440}
441
442unquoted-pipefail_no_words() {
443 set -o errexit
444 set -o pipefail
445
446 # Make sure we can blame this
447 seq 3 | wc -l | > /nonexistent
448
449 echo done
450}
451
452unquoted-pipefail_func() {
453 set -o errexit -o pipefail
454 f42() {
455 cat
456 # NOTE: If you call 'exit 42', there is no error message displayed!
457 #exit 42
458 return 42
459 }
460
461 # TODO: blame the right location
462 echo hi | cat | f42 | wc
463
464 echo 'SHOULD NOT GET HERE'
465}
466
467# TODO: point to {. It's the same sas a subshell so you don't know exactly
468# which command failed.
469unquoted-pipefail_group() {
470 set -o errexit -o pipefail
471 echo hi | { cat; sh -c 'exit 42'; } | wc
472
473 echo 'SHOULD NOT GET HERE'
474}
475
476# TODO: point to (
477unquoted-pipefail_subshell() {
478 set -o errexit -o pipefail
479 echo hi | (cat; sh -c 'exit 42') | wc
480
481 echo 'SHOULD NOT GET HERE'
482}
483
484# TODO: point to 'while'
485unquoted-pipefail_while() {
486 set -o errexit -o pipefail
487 seq 3 | while true; do
488 read line
489 echo X $line X
490 if test "$line" = 2; then
491 sh -c 'exit 42'
492 fi
493 done | wc
494
495 echo 'SHOULD NOT GET HERE'
496}
497
498# Multiple errors from multiple processes
499# TODO: These errors get interleaved and messed up. Maybe we should always
500# print a single line from pipeline processes? We should set their
501# ErrorFormatter?
502unquoted-pipefail_multiple() {
503 set -o errexit -o pipefail
504 { echo 'four'; sh -c 'exit 4'; } |
505 { echo 'five'; sh -c 'exit 5'; } |
506 { echo 'six'; sh -c 'exit 6'; }
507}
508
509# Errors from core/process.py
510test-core_process() {
511 _osh-error-1 '
512 echo foo > not/a/file
513 echo foo > /etc/no-perms-for-this
514 '
515
516 # DISABLED! This messes up the toil log file!
517 # echo hi 1>&3
518}
519
520# Errors from core/state.py
521test-core-state() {
522
523 _osh-should-run 'HOME=(a b)'
524
525 # $HOME is an exported string, so it shouldn't be changed to an array
526 _osh-error-1 'shopt --set strict_array; HOME=(a b)'
527}
528
529unquoted-ambiguous_redirect() {
530 echo foo > "$@"
531 echo 'ambiguous redirect not fatal unless errexit'
532
533 set -o errexit
534 echo foo > "$@"
535 echo 'should not get here'
536}
537
538# bash semantics.
539unquoted-ambiguous_redirect_context() {
540 # Problem: A WORD cannot fail. Only a COMMAND can fail.
541
542 # http://stackoverflow.com/questions/29532904/bash-subshell-errexit-semantics
543 # https://groups.google.com/forum/?fromgroups=#!topic/gnu.bash.bug/NCK_0GmIv2M
544
545 # http://unix.stackexchange.com/questions/23026/how-can-i-get-bash-to-exit-on-backtick-failure-in-a-similar-way-to-pipefail
546
547 echo $(echo hi > "$@")
548 echo 'ambiguous is NOT FATAL in command sub'
549 echo
550
551 foo=$(echo hi > "$@")
552 echo $foo
553 echo 'ambiguous is NOT FATAL in assignment in command sub'
554 echo
555
556 set -o errexit
557
558 # This is the issue addressed by command_sub_errexit!
559 echo $(echo hi > "$@")
560 echo 'ambiguous is NOT FATAL in command sub, even if errexit'
561 echo
562
563 # OK this one works. Because the exit code of the assignment is the exit
564 # code of the RHS?
565 echo 'But when the command sub is in an assignment, it is fatal'
566 foo=$(echo hi > "$@")
567 echo $foo
568 echo 'SHOULD NOT REACH HERE'
569}
570
571unquoted-bad_file_descriptor() {
572 : 1>&7
573}
574
575unquoted-command_sub_errexit() {
576 #set -o errexit
577 shopt -s command_sub_errexit || true
578 shopt -s inherit_errexit || true
579
580 echo t=$(true) f=$(false) 3=$(exit 3)
581 echo 'SHOULD NOT GET HERE'
582}
583
584unquoted-process_sub_fail() {
585 shopt -s process_sub_fail || true
586 shopt -s inherit_errexit || true
587 set -o errexit
588
589 cat <(echo a; exit 2) <(echo b; exit 3)
590 echo 'SHOULD NOT GET HERE'
591}
592
593myproc() {
594 echo ---
595 grep pat BAD # exits with code 2
596 #grep pat file.txt
597 echo ---
598}
599
600unquoted-bool_status() {
601 set -o errexit
602
603 if try --allow-status-01 -- myproc; then
604 echo 'match'
605 else
606 echo 'no match'
607 fi
608}
609
610unquoted-bool_status_simple() {
611 set -o errexit
612
613 if try --allow-status-01 -- grep pat BAD; then
614 echo 'match'
615 else
616 echo 'no match'
617 fi
618}
619
620#
621# WORD ERRORS
622#
623
624unquoted-nounset() {
625 set -o nounset
626 echo $x
627
628 echo 'SHOULD NOT GET HERE'
629}
630
631unquoted-bad_var_ref() {
632 name='bad var name'
633 echo ${!name}
634}
635
636#
637# ARITHMETIC ERRORS
638#
639
640unquoted-nounset_arith() {
641 set -o nounset
642 echo $(( x ))
643
644 echo 'SHOULD NOT GET HERE'
645}
646
647test-divzero() {
648 _osh-error-1 'echo $(( 1 / 0 ))'
649 # Better location
650 _osh-error-1 'echo $(( 1 / (3 -3 ) ))'
651 _osh-error-1 'echo $(( 1 % 0 ))'
652
653 _osh-error-1 'zero=0; echo $(( 1 / zero ))'
654 _osh-error-1 'zero=0; echo $(( 1 % zero ))'
655
656 _osh-error-1 '(( a = 1 / 0 )); echo non-fatal; exit 1'
657 _osh-error-1 '(( a = 1 % 0 )); echo non-fatal; exit 1'
658
659 # fatal!
660 _osh-error-1 'set -e; (( a = 1 / 0 ));'
661 _osh-error-1 'set -e; (( a = 1 % 0 ));'
662}
663
664test-unsafe_arith_eval() {
665 _osh-error-1 '
666 local e1=1+
667 local e2="e1 + 5"
668 echo $(( e2 )) # recursively references e1
669 '
670}
671
672test-unset_expr() {
673 _osh-error-1 'unset -v 1[1]'
674 _osh-error-2 'unset -v 1+2'
675}
676
677test-strict-arith() {
678 _osh-error-1 'shopt -s strict_arith; echo $(( undef[0] ))'
679 _osh-error-1 'shopt -s strict_arith; s=abc; echo $(( s[0] ))'
680 _osh-error-1 'shopt -s strict_arith; var i = 42; echo $(( i[0] ))'
681}
682
683# Only dash flags this as an error.
684unquoted-string_to_int_arith() {
685 local x='ZZZ'
686 echo $(( x + 5 ))
687
688 shopt -s strict_arith
689
690 echo $(( x + 5 ))
691
692 echo 'SHOULD NOT GET HERE'
693}
694
695# Hm bash treats this as a fatal error
696unquoted-string_to_hex() {
697 echo $(( 0xGG + 1 ))
698
699 echo 'SHOULD NOT GET HERE'
700}
701
702# Hm bash treats this as a fatal error
703unquoted-string_to_octal() {
704 echo $(( 018 + 1 ))
705
706 echo 'SHOULD NOT GET HERE'
707}
708
709# Hm bash treats this as a fatal error
710unquoted-string_to_intbase() {
711 echo $(( 16#GG ))
712
713 echo 'SHOULD NOT GET HERE'
714}
715
716unquoted-undef_arith() {
717 (( undef++ )) # doesn't make sense
718
719 # Can't assign to characters of string? Is that strong?
720 (( undef[42]++ ))
721}
722
723unquoted-undef_arith2() {
724 a=()
725
726 # undefined cell: This is kind of what happens in awk / "wok"
727 (( a[42]++ ))
728 (( a[42]++ ))
729 spec/bin/argv.py "${a[@]}"
730}
731
732unquoted-array_arith() {
733 a=(1 2)
734 (( a++ )) # doesn't make sense
735 echo "${a[@]}"
736}
737
738unquoted-undef-assoc-array() {
739 declare -A A
740 A['foo']=bar
741 echo "${A['foo']}"
742
743 A['spam']+=1
744 A['spam']+=1
745
746 spec/bin/argv.py "${A[@]}"
747
748 (( A['spam']+=5 ))
749
750 spec/bin/argv.py "${A[@]}"
751}
752
753test-assoc-array() {
754 _osh-error-1 'declare -A assoc; assoc[x]=1'
755 _osh-should-run 'declare -A assoc; assoc[$key]=1'
756
757 # double quotes
758 _osh-should-run 'declare -A assoc; assoc["x"]=1'
759
760 # single quotes
761 _osh-should-run-here <<'EOF'
762declare -A assoc; assoc['x']=1
763EOF
764
765 _osh-error-1 'declare -A assoc; echo ${assoc[x]}'
766 _osh-should-run 'declare -A assoc; echo ${assoc["x"]}'
767 _osh-should-run 'declare -A assoc; echo ${assoc[$key]}'
768
769 _osh-error-1 'declare -A assoc; key=k; unset assoc[$key]'
770 # quotes removed
771 _osh-error-1 'declare -A assoc; key=k; unset "assoc[$key]"'
772
773 # Like Samuel's Nix error
774 # unset -v "hardeningEnableMap[$flag]"
775 _osh-error-here-X 1 <<'EOF'
776declare -A assoc; key=k; unset "assoc[$key]"
777EOF
778
779 # SINGLE quotes fixes it
780 _osh-should-run-here <<'EOF'
781declare -A assoc; key=k; unset 'assoc[$key]'
782EOF
783
784 # Wrap in eval to see how it composes
785 _osh-error-here-X 1 <<'EOF'
786eval 'declare -A assoc; assoc[x]=1'
787EOF
788
789 _osh-error-here-X 1 <<'EOF'
790eval 'declare -A assoc; unset "assoc[x]"'
791EOF
792
793}
794
795unquoted-patsub_bad_glob() {
796 local x='abc'
797 # inspired by git-completion.bash
798 echo ${x//[^]}
799}
800
801
802#
803# BOOLEAN ERRORS
804#
805
806# Only osh cares about this.
807unquoted-string_to_int_bool() {
808 [[ a -eq 0 ]]
809
810 shopt -s strict_arith
811
812 [[ a -eq 0 ]]
813 echo 'SHOULD NOT GET HERE'
814}
815
816unquoted-strict_array() {
817 set -- 1 2
818 echo foo > _tmp/"$@"
819 shopt -s strict_array
820 echo foo > _tmp/"$@"
821}
822
823unquoted-strict_array_2() {
824 local foo="$@"
825 shopt -s strict_array
826 local foo="$@"
827}
828
829unquoted-strict_array_3() {
830 local foo=${1:- "[$@]" }
831 shopt -s strict_array
832 local foo=${1:- "[$@]" }
833}
834
835unquoted-strict_array_4() {
836 local -a x
837 x[42]=99
838 echo "x[42] = ${x[42]}"
839
840 # Not implemented yet
841 shopt -s strict_array
842 local -a y
843 y[42]=99
844}
845
846unquoted-array_assign_1() {
847 s=1
848 s[0]=x # can't assign value
849}
850
851unquoted-array_assign_2() {
852 _osh-error-1 'readonly -a array=(1 2 3); array[0]=x'
853
854 _osh-error-1 'readonly -a array=(1 2 3); export array'
855}
856
857unquoted-readonly_assign() {
858 _osh-error-1 'readonly x=1; x=2'
859
860 _osh-error-1 'readonly x=2; y=3 x=99'
861
862 _osh-error-1 'readonly x=2; declare x=99'
863 _osh-error-1 'readonly x=2; export x=99'
864}
865
866unquoted-multiple_assign() {
867 readonly x=1
868 # It blames x, not a!
869 a=1 b=2 x=42
870}
871
872unquoted-multiple_assign_2() {
873 readonly y
874 local x=1 y=$(( x ))
875 echo $y
876}
877
878unquoted-string_as_array() {
879 local str='foo'
880 echo $str
881 echo "${str[@]}"
882}
883
884#
885# BUILTINS
886#
887
888unquoted-builtin_bracket() {
889 set +o errexit
890
891 # xxx is not a valid file descriptor
892 [ -t xxx ]
893 [ -t '' ]
894
895 [ zz -eq 0 ]
896
897 # This is from a different evaluator
898 #[ $((a/0)) -eq 0 ]
899}
900
901unquoted-builtin_builtin() {
902 set +o errexit
903 builtin ls
904}
905
906unquoted-builtin_source() {
907 source
908
909 bad=/nonexistent/path
910 source $bad
911}
912
913unquoted-builtin_cd() {
914 ( unset HOME
915 cd
916 )
917
918 # TODO: Hm this gives a different useful error without location info
919 ( unset HOME
920 HOME=(a b)
921 cd
922 )
923
924 # TODO: Hm this gives a different useful error without location info
925 ( unset OLDPWD
926 cd -
927 )
928
929 ( cd /nonexistent
930 )
931}
932
933unquoted-builtin_pushd() {
934 pushd /nonexistent
935}
936
937unquoted-builtin_popd() {
938 popd # empty dir stack
939
940 (
941 local dir=$PWD/_tmp/runtime-error-popd
942 mkdir -p $dir
943 pushd $dir
944 pushd /
945 rmdir $dir
946 popd
947 )
948}
949
950unquoted-builtin_unset() {
951 local x=x
952 readonly a
953
954 unset x a
955 unset -v x a
956}
957
958unquoted-builtin_alias_unalias() {
959 alias zzz
960 unalias zzz
961}
962
963unquoted-builtin_help() {
964 help zzz
965}
966
967unquoted-builtin_trap() {
968 trap
969 trap EXIT
970
971 trap zzz yyy
972}
973
974unquoted-builtin_getopts() {
975 getopts
976 getopts 'a:'
977
978 # TODO: It would be nice to put this in a loop and use it properly
979 set -- -a
980 getopts 'a:' varname
981}
982
983builtin-printf() {
984 _osh-error-here-X 1 <<'EOF'
985 printf '%s %d\n' foo not_a_number
986EOF
987
988 # bad arg recycling. This is really a runtime error.
989 _osh-error-here-X 1 <<'EOF'
990 printf '%s %d\n' foo 3 bar
991EOF
992
993 # invalid width
994 _osh-error-here-X 1 <<'EOF'
995 printf '%*d\n' foo foo
996EOF
997
998 # invalid precision on %d
999 _osh-error-here-X 1 <<'EOF'
1000 printf '%.*d\n' foo foo
1001EOF
1002
1003 # invalid precision on %s
1004 _osh-error-here-X 1 <<'EOF'
1005 printf '%.*s\n' foo foo
1006EOF
1007
1008 # invalid time
1009 _osh-error-here-X 1 <<'EOF'
1010 printf '%(%Y)T\n' z
1011EOF
1012
1013 # no time specified
1014 _osh-should-run-here <<'EOF'
1015 printf '%(%Y)T\n'
1016EOF
1017
1018 # invalid integer with no SPID
1019 _osh-error-here-X 1 <<'EOF'
1020 printf '%d %d %d\n' 1 2
1021EOF
1022
1023 # trailing data on integer
1024 _osh-error-here-X 1 <<'EOF'
1025 printf '%d\n' 12x
1026EOF
1027
1028 # integer overflow
1029 _osh-error-here-X 1 <<'EOF'
1030 printf '%d\n' 12345678901234567890
1031EOF
1032}
1033
1034unquoted-builtin_wait() {
1035 wait 1234578
1036}
1037
1038unquoted-builtin_exec() {
1039 exec nonexistent-command 1 2 3
1040 echo $?
1041}
1042
1043#
1044# Strict options (see spec/strict-options.test.sh)
1045#
1046
1047unquoted-strict_word_eval_warnings() {
1048 # Warnings when 'set +o strict_word_eval' is OFF
1049
1050 echo slice start negative
1051 s='abc'
1052 echo -${s: -2}-
1053
1054 echo slice length negative
1055 s='abc'
1056 echo -${s: 1: -2}-
1057
1058 # TODO: These need span IDs.
1059 # - invalid utf-8 and also invalid backslash escape
1060
1061 echo slice bad utf-8
1062 s=$(echo -e "\xFF")bcdef
1063 echo -${s:1:3}-
1064
1065 echo length bad utf-8
1066 echo ${#s}
1067}
1068
1069unquoted-strict_arith_warnings() {
1070 local x='xx'
1071 echo $(( x + 1 ))
1072
1073 # TODO: OSH is more lenient here actually
1074 local y='-yy-'
1075 echo $(( y + 1 ))
1076
1077 [[ $y -eq 0 ]]
1078
1079 echo 'done'
1080}
1081
1082test-strict-control-flow() {
1083 # This prints a WARNING in bash. Not fatal in any shell except zsh.
1084 _osh-error-X 0 '
1085break
1086continue
1087echo UNREACHABLE
1088'
1089
1090 _osh-error-X 1 '
1091shopt -s strict_control_flow
1092break
1093continue
1094echo UNREACHABLE
1095'
1096}
1097
1098test-control-flow-subshell() {
1099 _osh-error-1 '
1100 set -o errexit
1101 for i in $(seq 2); do
1102 echo $i
1103 ( break; echo oops)
1104 done
1105 '
1106}
1107
1108#
1109# Locations
1110#
1111
1112test-fallback-locations() {
1113 # Redirect
1114 _osh-error-1 'echo hi > /'
1115
1116 _osh-error-1 's=x; (( s[0] ))'
1117
1118 _osh-error-1 's=x; (( s[0] = 42 ))'
1119
1120 _osh-error-1 'set -u; (( undef ))'
1121
1122 _osh-error-1 '(( 3 ** -2 ))'
1123 echo
1124
1125 # DBracket
1126 _osh-error-1 'set -u; [[ $undef =~ . ]]'
1127
1128 # No good fallback info here, we need it
1129 _osh-error-1 '[[ $x =~ $(( 3 ** -2 )) ]]'
1130
1131 _osh-error-2 'type -x' # correctly points to -x
1132 _osh-error-2 'use'
1133
1134 # Assign builtin
1135 _osh-error-2 'export -f'
1136
1137 _osh-error-1 's=$(true) y=$(( 3 ** -2 ))'
1138
1139 _osh-error-1 'if s=$(true) y=$(( 3 ** -2 )); then echo hi; fi'
1140
1141 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( x ))'
1142 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( $x ))'
1143 _osh-error-1 'shopt -s strict_arith; x=a; [[ $x -gt 3 ]]'
1144 _osh-error-1 'shopt -s strict_arith; shopt -u eval_unsafe_arith; x=a; [[ $x -gt 3 ]]'
1145
1146 _osh-error-1 'shopt -s strict_arith; x=0xgg; echo $(( x ))'
1147
1148 echo done
1149}
1150
1151test-var-op-qmark() {
1152 _osh-error-1 'echo ${zz?}'
1153 _osh-error-1 'echo ${zz:?}'
1154
1155 _osh-should-run 'zz=""; echo ${zz?}'
1156 _osh-error-1 'zz=""; echo ${zz:?}'
1157
1158 _osh-error-1 'echo ${zz?Required}'
1159 _osh-error-1 'echo ${zz:?Required}'
1160}
1161
1162test-external-cmd-typed-args() {
1163 # Note: this was exit code 1 before shopt --set rewrite_extern
1164 _ysh-error-X 2 'cat ("myfile")'
1165}
1166
1167test-arith-ops-str() {
1168 _ysh-error-X 3 '= "100" + "10a"'
1169 _ysh-error-X 3 '= "100" - "10a"'
1170 _ysh-error-X 3 '= "100" * "10a"'
1171 _ysh-error-X 3 '= "100" / "10a"'
1172 _ysh-error-X 3 'var a = "100"; setvar a += "10a"'
1173 _ysh-error-X 3 'var a = "100"; setvar a -= "10a"'
1174 _ysh-error-X 3 'var a = "100"; setvar a *= "10a"'
1175 _ysh-error-X 3 'var a = "100"; setvar a /= "10a"'
1176 _ysh-error-X 3 '= "age: " + "100"'
1177 _ysh-error-X 3 'var myvar = "a string"
1178= 100 + myvar'
1179}
1180
1181assert-test-v-error() {
1182 local code=$1
1183
1184 # note: the test builtin fails with status 2, but the shell doesn't fail
1185 _osh-error-2 "shopt -s strict_word_eval; a=(1 2 3); $code"
1186}
1187
1188test-test-v-expr() {
1189 assert-test-v-error 'test -v ""'
1190 assert-test-v-error 'test -v "a[foo"'
1191 assert-test-v-error 'test -v "a[not-int]"'
1192 assert-test-v-error 'test -v "a[-42]"'
1193
1194 _osh-error-2 'shopt -s strict_word_eval; s=""; test -v s[0]'
1195}
1196
1197test-long-shell-line() {
1198 # Example from https://github.com/oilshell/oil/issues/1973
1199
1200 _ysh-error-1 'myvar=$(printf "what a very long string that we have here, which forces the command line to wrap around the terminal width. long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long") && echo $myvar'
1201 echo
1202}
1203
1204test-int-overflow() {
1205 local pos='18446744073709551616'
1206 local neg='-18446744073709551616'
1207
1208 # frontend/args.py
1209 _osh-error-2 "echo hi | read -n $pos"
1210 _osh-error-2 "echo hi | read -n $neg"
1211
1212 # osh/sh_expr_eval.py
1213 _osh-error-1 "s=$pos;"' echo $(( $s ))'
1214 _osh-error-1 "s=$neg;"' echo $(( $s ))'
1215
1216 # octal
1217 local oct_pos='01234567012345670123456701234567'
1218 local oct_neg="-$oct_pos"
1219 _osh-error-1 "s=$oct_pos;"' echo $(( $s ))'
1220 _osh-error-1 "s=$oct_neg;"' echo $(( $s ))' # treated as negation
1221
1222 # hex
1223 local hex_pos='0x123456789abcdef0123456789'
1224 local hex_neg="-$hex_pos"
1225 _osh-error-1 "s=$hex_pos;"' echo $(( $s ))'
1226 _osh-error-1 "s=$hex_neg;"' echo $(( $s ))' # treated as negation
1227
1228 # builtins
1229 _osh-error-1 'printf %d'" $pos"
1230 _osh-error-1 'printf %d'" $neg"
1231
1232 _osh-error-2 "trap $pos ERR"
1233 # ACCEPTED - integer must be "unsigned" according to posix
1234 _osh-should-run "trap -- $neg ERR"
1235
1236 _osh-error-2 "ulimit $pos"
1237 _osh-error-2 "ulimit -- $neg"
1238}
1239
1240test-bind() {
1241 _osh-error-2 'bind -x'
1242 _osh-error-1 'bind -x foo'
1243
1244 # TODO: turn these on when they pass in C++
1245 return
1246 _osh-error-1 'bind "a" "b"'
1247 _osh-error-1 'bind "\eQ: yank-pop" "b"'
1248 _osh-error-1 'bind "\eQ: yank-pop" "\eQ: yank-pop" "c"'
1249}
1250
1251test-brace-range() {
1252 _osh-error-X 127 '{35..37}'
1253 _osh-error-X 127 'x{35..37}'
1254 _osh-error-X 127 '{35..37}"x"'
1255}
1256
1257test-kill-builtin-usage() {
1258 _osh-error-2 'kill'
1259 _osh-error-2 'kill -15'
1260
1261 _osh-error-2 'kill --15 99'
1262 _osh-error-2 'kill -T 99' # invalid flag
1263 _osh-error-2 'kill -TE 99' # invalid sigspec
1264
1265 _osh-error-2 'kill -s TE 123'
1266 _osh-error-2 'kill -s TERM %zzz'
1267
1268 _osh-error-2 'kill -L TERM ZZ USR1'
1269 _osh-error-2 'kill -L 15 9999 16'
1270}
1271
1272test-umask-errors() {
1273 # BUG here
1274 #_osh-error-1 'umask ""'
1275
1276 _osh-error-1 'umask "a"'
1277
1278 _osh-error-2 'umask a b'
1279}
1280
1281#
1282# TEST DRIVER
1283#
1284
1285list-unquoted-funcs() {
1286 ### These tests need assertions, with quoting
1287
1288 compgen -A function | egrep '^unquoted-'
1289}
1290
1291run-unquoted-funcs() {
1292 local i=0
1293 list-unquoted-funcs | ( while read -r test_func; do
1294 _run-test-func $test_func '' # don't assert status
1295 i=$((i + 1))
1296 done
1297
1298 # Hacky subshell for $i
1299 echo
1300 echo "$0: $i unquoted functions run. TODO: migrate to test-* to assert status"
1301 )
1302}
1303
1304all-tests() {
1305
1306 section-banner 'Runtime errors - Unquoted test functions'
1307 # Legacy functions that don't check status
1308 run-unquoted-funcs
1309
1310 section-banner 'Runtime errors - test functions'
1311
1312 # Run with strict mode
1313 set -o nounset
1314 set -o pipefail
1315 set -o errexit
1316
1317 run-test-funcs
1318}
1319
1320# TODO: could show these as separate text files in the CI
1321
1322with-bash() {
1323 SH_ASSERT_DISABLE=1 OSH=bash YSH=bash run-test-funcs
1324}
1325
1326with-dash() {
1327 SH_ASSERT_DISABLE=1 OSH=dash YSH=dash run-test-funcs
1328}
1329
1330soil-run-py() {
1331 all-tests
1332}
1333
1334soil-run-cpp() {
1335 # TODO: There are some UBSAN errors, like downcasting mylib::LineReader.
1336 # Is that a real problem? Could be due to mylib::File.
1337
1338 #local osh=_bin/cxx-ubsan/osh
1339
1340 local osh=_bin/cxx-asan/osh
1341
1342 ninja $osh
1343 OSH=$osh all-tests
1344}
1345
1346run-for-release() {
1347 run-other-suite-for-release runtime-errors all-tests
1348}
1349
1350"$@"