OILS / test / runtime-errors.sh View on Github | oilshell.org

1239 lines, 654 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 'declare 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 # no pipefail
230 _ysh-should-run 'ls | false | wc -l'
231
232 _sep
233
234 # note: need trailing echo to prevent pipeline optimization
235 _ysh-error-X 42 'ls | { echo hi; ( exit 42 ); } | wc -l; echo'
236
237 _sep
238
239 # Showing errors for THREE PIDs here! That is technically correct, but
240 # noisy.
241 _ysh-error-1 '{ echo one; ( exit 42 ); } |\
242{ false; wc -l; }'
243
244 _sep
245
246 # realistic example
247 _ysh-error-1 '{ ls; false; } \
248| wc -l
249'
250
251 _sep
252
253 # Three errors!
254 _ysh-error-1 '{ ls; ( false; true ); } | wc -l; echo hi'
255
256 _sep
257
258 _ysh-error-X 127 'ls <(sort YY) <(zz); echo hi'
259
260 # 2 kinds of errors
261 _sep
262 _ysh-error-X 127 'zz <(sort YY) <(sort ZZ); echo hi'
263
264 # This one has badly interleaved errors!
265 _sep
266 _ysh-error-X 127 'yy | zz'
267
268 _sep
269 _osh-error-1 'shopt -s ysh:upgrade; echo $([[ 0 -eq 1 ]])'
270
271 _sep
272 _osh-error-1 'shopt -s ysh:upgrade; var y = $([[ 0 -eq 1 ]])'
273}
274
275
276_strict-errexit-case() {
277 local code=$1
278
279 case-banner "[strict_errexit] $code"
280
281 _osh-error-1 \
282 "set -o errexit; shopt -s strict_errexit; $code"
283 echo
284}
285
286test-strict_errexit_1() {
287 # Test out all the location info
288
289 _strict-errexit-case '! { echo 1; echo 2; }'
290
291 _strict-errexit-case '{ echo 1; echo 2; } && true'
292 _strict-errexit-case '{ echo 1; echo 2; } || true'
293
294 # More chains
295 _strict-errexit-case '{ echo 1; echo 2; } && true && true'
296 _strict-errexit-case 'true && { echo 1; echo 2; } || true || true'
297 _strict-errexit-case 'true && true && { echo 1; echo 2; } || true || true'
298
299 _strict-errexit-case 'if { echo 1; echo 2; }; then echo IF; fi'
300 _strict-errexit-case 'while { echo 1; echo 2; }; do echo WHILE; done'
301 _strict-errexit-case 'until { echo 1; echo 2; }; do echo UNTIL; done'
302
303 # Must be separate lines for parsing options to take effect
304 _strict-errexit-case 'shopt -s ysh:upgrade
305 proc p { echo p }
306 if p { echo hi }'
307}
308
309test-strict_errexit_conditionals() {
310 # this works, even though this is a subshell
311 _strict-errexit-case '
312myfunc() { return 1; }
313
314while ( myfunc )
315do
316 echo yes
317done
318'
319
320 # Conditional - Proc - Child Interpreter Problem (command sub)
321 # Same problem here. A proc run in a command sub LOSES the exit code.
322 _strict-errexit-case '
323myfunc() { return 1; }
324
325while test "$(myfunc)" != ""
326do
327 echo yes
328done
329'
330
331 # Process Sub is be disallowed; it could invoke a proc!
332 _strict-errexit-case '
333myfunc() { return 1; }
334
335if cat <(ls)
336then
337 echo yes
338fi
339'
340
341 # Conditional - Proc - Child Interpreter Problem (pipeline)
342 _strict-errexit-case '
343myfunc() {
344 return 1
345}
346
347set -o pipefail
348while myfunc | cat
349do
350 echo yes
351done
352'
353
354 # regression for issue #1107 bad error message
355 # Also revealed #1113: the strict_errexit check was handled inside the
356 # command sub process!
357 _strict-errexit-case '
358myfunc() {
359 return 1
360}
361
362foo=$(true)
363
364# test assignment without proc
365while bar=$(false)
366do
367 echo yes
368done
369
370# issue 1007 was caused using command.ShAssignment, rather than the more common
371# command.Sentence with ;
372while spam=$(myfunc)
373do
374 echo yes
375done
376'
377}
378
379# OLD WAY OF BLAMING
380# Note: most of these don't fail
381test-strict_errexit_old() {
382 # Test out all the location info
383
384 # command.Pipeline.
385 _strict-errexit-case 'if ls | wc -l; then echo Pipeline; fi'
386 _strict-errexit-case 'if ! ls | wc -l; then echo Pipeline; fi'
387
388 # This one is ALLOWED
389 #_strict-errexit-case 'if ! ls; then echo Pipeline; fi'
390
391 # command.AndOr
392 _strict-errexit-case 'if echo a && echo b; then echo AndOr; fi'
393
394 # command.DoGroup
395 _strict-errexit-case '! for x in a; do echo $x; done'
396
397 # command.BraceGroup
398 _strict-errexit-case '_func() { echo; }; ! _func'
399 _strict-errexit-case '! { echo brace; }; echo "should not get here"'
400
401 # command.Subshell
402 _strict-errexit-case '! ( echo subshell ); echo "should not get here"'
403
404 # command.WhileUntil
405 _strict-errexit-case '! while false; do echo while; done; echo "should not get here"'
406
407 # command.If
408 _strict-errexit-case '! if true; then false; fi; echo "should not get here"'
409
410 # command.Case
411 _strict-errexit-case '! case x in x) echo x;; esac; echo "should not get here"'
412
413 # command.TimeBlock
414 _strict-errexit-case '! time echo hi; echo "should not get here"'
415
416 # Command Sub
417 _strict-errexit-case '! echo $(echo hi); echo "should not get here"'
418}
419
420unquoted-pipefail() {
421 false | wc -l
422
423 set -o errexit
424 set -o pipefail
425 false | wc -l
426
427 echo 'SHOULD NOT GET HERE'
428}
429
430unquoted-pipefail_no_words() {
431 set -o errexit
432 set -o pipefail
433
434 # Make sure we can blame this
435 seq 3 | wc -l | > /nonexistent
436
437 echo done
438}
439
440unquoted-pipefail_func() {
441 set -o errexit -o pipefail
442 f42() {
443 cat
444 # NOTE: If you call 'exit 42', there is no error message displayed!
445 #exit 42
446 return 42
447 }
448
449 # TODO: blame the right location
450 echo hi | cat | f42 | wc
451
452 echo 'SHOULD NOT GET HERE'
453}
454
455# TODO: point to {. It's the same sas a subshell so you don't know exactly
456# which command failed.
457unquoted-pipefail_group() {
458 set -o errexit -o pipefail
459 echo hi | { cat; sh -c 'exit 42'; } | wc
460
461 echo 'SHOULD NOT GET HERE'
462}
463
464# TODO: point to (
465unquoted-pipefail_subshell() {
466 set -o errexit -o pipefail
467 echo hi | (cat; sh -c 'exit 42') | wc
468
469 echo 'SHOULD NOT GET HERE'
470}
471
472# TODO: point to 'while'
473unquoted-pipefail_while() {
474 set -o errexit -o pipefail
475 seq 3 | while true; do
476 read line
477 echo X $line X
478 if test "$line" = 2; then
479 sh -c 'exit 42'
480 fi
481 done | wc
482
483 echo 'SHOULD NOT GET HERE'
484}
485
486# Multiple errors from multiple processes
487# TODO: These errors get interleaved and messed up. Maybe we should always
488# print a single line from pipeline processes? We should set their
489# ErrorFormatter?
490unquoted-pipefail_multiple() {
491 set -o errexit -o pipefail
492 { echo 'four'; sh -c 'exit 4'; } |
493 { echo 'five'; sh -c 'exit 5'; } |
494 { echo 'six'; sh -c 'exit 6'; }
495}
496
497test-control_flow() {
498 # This prints a WARNING in bash. Not fatal in any shell except zsh.
499 _osh-error-X 0 '
500break
501continue
502echo UNREACHABLE
503'
504
505 _osh-error-X 1 '
506shopt -s strict_control_flow
507break
508continue
509echo UNREACHABLE
510'
511}
512
513# Errors from core/process.py
514test-core_process() {
515 _osh-error-1 '
516 echo foo > not/a/file
517 echo foo > /etc/no-perms-for-this
518 '
519
520 # DISABLED! This messes up the toil log file!
521 # echo hi 1>&3
522}
523
524# Errors from core/state.py
525test-core-state() {
526
527 _osh-should-run 'HOME=(a b)'
528
529 # $HOME is an exported string, so it shouldn't be changed to an array
530 _osh-error-1 'shopt --set strict_array; HOME=(a b)'
531}
532
533unquoted-ambiguous_redirect() {
534 echo foo > "$@"
535 echo 'ambiguous redirect not fatal unless errexit'
536
537 set -o errexit
538 echo foo > "$@"
539 echo 'should not get here'
540}
541
542# bash semantics.
543unquoted-ambiguous_redirect_context() {
544 # Problem: A WORD cannot fail. Only a COMMAND can fail.
545
546 # http://stackoverflow.com/questions/29532904/bash-subshell-errexit-semantics
547 # https://groups.google.com/forum/?fromgroups=#!topic/gnu.bash.bug/NCK_0GmIv2M
548
549 # http://unix.stackexchange.com/questions/23026/how-can-i-get-bash-to-exit-on-backtick-failure-in-a-similar-way-to-pipefail
550
551 echo $(echo hi > "$@")
552 echo 'ambiguous is NOT FATAL in command sub'
553 echo
554
555 foo=$(echo hi > "$@")
556 echo $foo
557 echo 'ambiguous is NOT FATAL in assignment in command sub'
558 echo
559
560 set -o errexit
561
562 # This is the issue addressed by command_sub_errexit!
563 echo $(echo hi > "$@")
564 echo 'ambiguous is NOT FATAL in command sub, even if errexit'
565 echo
566
567 # OK this one works. Because the exit code of the assignment is the exit
568 # code of the RHS?
569 echo 'But when the command sub is in an assignment, it is fatal'
570 foo=$(echo hi > "$@")
571 echo $foo
572 echo 'SHOULD NOT REACH HERE'
573}
574
575unquoted-bad_file_descriptor() {
576 : 1>&7
577}
578
579unquoted-command_sub_errexit() {
580 #set -o errexit
581 shopt -s command_sub_errexit || true
582 shopt -s inherit_errexit || true
583
584 echo t=$(true) f=$(false) 3=$(exit 3)
585 echo 'SHOULD NOT GET HERE'
586}
587
588unquoted-process_sub_fail() {
589 shopt -s process_sub_fail || true
590 shopt -s inherit_errexit || true
591 set -o errexit
592
593 cat <(echo a; exit 2) <(echo b; exit 3)
594 echo 'SHOULD NOT GET HERE'
595}
596
597myproc() {
598 echo ---
599 grep pat BAD # exits with code 2
600 #grep pat file.txt
601 echo ---
602}
603
604unquoted-bool_status() {
605 set -o errexit
606
607 if try --allow-status-01 -- myproc; then
608 echo 'match'
609 else
610 echo 'no match'
611 fi
612}
613
614unquoted-bool_status_simple() {
615 set -o errexit
616
617 if try --allow-status-01 -- grep pat BAD; then
618 echo 'match'
619 else
620 echo 'no match'
621 fi
622}
623
624#
625# WORD ERRORS
626#
627
628unquoted-nounset() {
629 set -o nounset
630 echo $x
631
632 echo 'SHOULD NOT GET HERE'
633}
634
635unquoted-bad_var_ref() {
636 name='bad var name'
637 echo ${!name}
638}
639
640#
641# ARITHMETIC ERRORS
642#
643
644unquoted-nounset_arith() {
645 set -o nounset
646 echo $(( x ))
647
648 echo 'SHOULD NOT GET HERE'
649}
650
651test-divzero() {
652 _osh-error-1 'echo $(( 1 / 0 ))'
653 # Better location
654 _osh-error-1 'echo $(( 1 / (3 -3 ) ))'
655 _osh-error-1 'echo $(( 1 % 0 ))'
656
657 _osh-error-1 'zero=0; echo $(( 1 / zero ))'
658 _osh-error-1 'zero=0; echo $(( 1 % zero ))'
659
660 _osh-error-1 '(( a = 1 / 0 )); echo non-fatal; exit 1'
661 _osh-error-1 '(( a = 1 % 0 )); echo non-fatal; exit 1'
662
663 # fatal!
664 _osh-error-1 'set -e; (( a = 1 / 0 ));'
665 _osh-error-1 'set -e; (( a = 1 % 0 ));'
666}
667
668test-unsafe_arith_eval() {
669 _osh-error-1 '
670 local e1=1+
671 local e2="e1 + 5"
672 echo $(( e2 )) # recursively references e1
673 '
674}
675
676test-unset_expr() {
677 _osh-error-1 'unset -v 1[1]'
678 _osh-error-2 'unset -v 1+2'
679}
680
681test-strict-arith() {
682 _osh-error-1 'shopt -s strict_arith; echo $(( undef[0] ))'
683 _osh-error-1 'shopt -s strict_arith; s=abc; echo $(( s[0] ))'
684 _osh-error-1 'shopt -s strict_arith; var i = 42; echo $(( i[0] ))'
685}
686
687# Only dash flags this as an error.
688unquoted-string_to_int_arith() {
689 local x='ZZZ'
690 echo $(( x + 5 ))
691
692 shopt -s strict_arith
693
694 echo $(( x + 5 ))
695
696 echo 'SHOULD NOT GET HERE'
697}
698
699# Hm bash treats this as a fatal error
700unquoted-string_to_hex() {
701 echo $(( 0xGG + 1 ))
702
703 echo 'SHOULD NOT GET HERE'
704}
705
706# Hm bash treats this as a fatal error
707unquoted-string_to_octal() {
708 echo $(( 018 + 1 ))
709
710 echo 'SHOULD NOT GET HERE'
711}
712
713# Hm bash treats this as a fatal error
714unquoted-string_to_intbase() {
715 echo $(( 16#GG ))
716
717 echo 'SHOULD NOT GET HERE'
718}
719
720unquoted-undef_arith() {
721 (( undef++ )) # doesn't make sense
722
723 # Can't assign to characters of string? Is that strong?
724 (( undef[42]++ ))
725}
726
727unquoted-undef_arith2() {
728 a=()
729
730 # undefined cell: This is kind of what happens in awk / "wok"
731 (( a[42]++ ))
732 (( a[42]++ ))
733 spec/bin/argv.py "${a[@]}"
734}
735
736unquoted-array_arith() {
737 a=(1 2)
738 (( a++ )) # doesn't make sense
739 echo "${a[@]}"
740}
741
742unquoted-undef-assoc-array() {
743 declare -A A
744 A['foo']=bar
745 echo "${A['foo']}"
746
747 A['spam']+=1
748 A['spam']+=1
749
750 spec/bin/argv.py "${A[@]}"
751
752 (( A['spam']+=5 ))
753
754 spec/bin/argv.py "${A[@]}"
755}
756
757test-assoc-array() {
758 _osh-error-1 'declare -A assoc; assoc[x]=1'
759 _osh-should-run 'declare -A assoc; assoc[$key]=1'
760
761 # double quotes
762 _osh-should-run 'declare -A assoc; assoc["x"]=1'
763
764 # single quotes
765 _osh-should-run-here <<'EOF'
766declare -A assoc; assoc['x']=1
767EOF
768
769 _osh-error-1 'declare -A assoc; echo ${assoc[x]}'
770 _osh-should-run 'declare -A assoc; echo ${assoc["x"]}'
771 _osh-should-run 'declare -A assoc; echo ${assoc[$key]}'
772
773 _osh-error-1 'declare -A assoc; key=k; unset assoc[$key]'
774 # quotes removed
775 _osh-error-1 'declare -A assoc; key=k; unset "assoc[$key]"'
776
777 # Like Samuel's Nix error
778 # unset -v "hardeningEnableMap[$flag]"
779 _osh-error-here-X 1 <<'EOF'
780declare -A assoc; key=k; unset "assoc[$key]"
781EOF
782
783 # SINGLE quotes fixes it
784 _osh-should-run-here <<'EOF'
785declare -A assoc; key=k; unset 'assoc[$key]'
786EOF
787
788 # Wrap in eval to see how it composes
789 _osh-error-here-X 1 <<'EOF'
790eval 'declare -A assoc; assoc[x]=1'
791EOF
792
793 _osh-error-here-X 1 <<'EOF'
794eval 'declare -A assoc; unset "assoc[x]"'
795EOF
796
797}
798
799unquoted-patsub_bad_glob() {
800 local x='abc'
801 # inspired by git-completion.bash
802 echo ${x//[^]}
803}
804
805
806#
807# BOOLEAN ERRORS
808#
809
810# Only osh cares about this.
811unquoted-string_to_int_bool() {
812 [[ a -eq 0 ]]
813
814 shopt -s strict_arith
815
816 [[ a -eq 0 ]]
817 echo 'SHOULD NOT GET HERE'
818}
819
820unquoted-strict_array() {
821 set -- 1 2
822 echo foo > _tmp/"$@"
823 shopt -s strict_array
824 echo foo > _tmp/"$@"
825}
826
827unquoted-strict_array_2() {
828 local foo="$@"
829 shopt -s strict_array
830 local foo="$@"
831}
832
833unquoted-strict_array_3() {
834 local foo=${1:- "[$@]" }
835 shopt -s strict_array
836 local foo=${1:- "[$@]" }
837}
838
839unquoted-strict_array_4() {
840 local -a x
841 x[42]=99
842 echo "x[42] = ${x[42]}"
843
844 # Not implemented yet
845 shopt -s strict_array
846 local -a y
847 y[42]=99
848}
849
850unquoted-array_assign_1() {
851 s=1
852 s[0]=x # can't assign value
853}
854
855unquoted-array_assign_2() {
856 _osh-error-1 'readonly -a array=(1 2 3); array[0]=x'
857
858 _osh-error-1 'readonly -a array=(1 2 3); export array'
859}
860
861unquoted-readonly_assign() {
862 _osh-error-1 'readonly x=1; x=2'
863
864 _osh-error-1 'readonly x=2; y=3 x=99'
865
866 _osh-error-1 'readonly x=2; declare x=99'
867 _osh-error-1 'readonly x=2; export x=99'
868}
869
870unquoted-multiple_assign() {
871 readonly x=1
872 # It blames x, not a!
873 a=1 b=2 x=42
874}
875
876unquoted-multiple_assign_2() {
877 readonly y
878 local x=1 y=$(( x ))
879 echo $y
880}
881
882unquoted-string_as_array() {
883 local str='foo'
884 echo $str
885 echo "${str[@]}"
886}
887
888#
889# BUILTINS
890#
891
892unquoted-builtin_bracket() {
893 set +o errexit
894
895 # xxx is not a valid file descriptor
896 [ -t xxx ]
897 [ -t '' ]
898
899 [ zz -eq 0 ]
900
901 # This is from a different evaluator
902 #[ $((a/0)) -eq 0 ]
903}
904
905unquoted-builtin_builtin() {
906 set +o errexit
907 builtin ls
908}
909
910unquoted-builtin_source() {
911 source
912
913 bad=/nonexistent/path
914 source $bad
915}
916
917unquoted-builtin_cd() {
918 ( unset HOME
919 cd
920 )
921
922 # TODO: Hm this gives a different useful error without location info
923 ( unset HOME
924 HOME=(a b)
925 cd
926 )
927
928 # TODO: Hm this gives a different useful error without location info
929 ( unset OLDPWD
930 cd -
931 )
932
933 ( cd /nonexistent
934 )
935}
936
937unquoted-builtin_pushd() {
938 pushd /nonexistent
939}
940
941unquoted-builtin_popd() {
942 popd # empty dir stack
943
944 (
945 local dir=$PWD/_tmp/runtime-error-popd
946 mkdir -p $dir
947 pushd $dir
948 pushd /
949 rmdir $dir
950 popd
951 )
952}
953
954unquoted-builtin_unset() {
955 local x=x
956 readonly a
957
958 unset x a
959 unset -v x a
960}
961
962unquoted-builtin_alias_unalias() {
963 alias zzz
964 unalias zzz
965}
966
967unquoted-builtin_help() {
968 help zzz
969}
970
971unquoted-builtin_trap() {
972 trap
973 trap EXIT
974
975 trap zzz yyy
976}
977
978unquoted-builtin_getopts() {
979 getopts
980 getopts 'a:'
981
982 # TODO: It would be nice to put this in a loop and use it properly
983 set -- -a
984 getopts 'a:' varname
985}
986
987builtin_printf() {
988 printf '%s %d\n' foo not_a_number
989 echo status=$?
990
991 # bad arg recycling. This is really a runtime error.
992 printf '%s %d\n' foo 3 bar
993 echo status=$?
994
995 # invalid width
996 printf '%*d\n' foo foo
997 echo status=$?
998
999 # precision can't be specified
1000 printf '%.*d\n' foo foo
1001 echo status=$?
1002
1003 # precision can't be specified
1004 printf '%.*s\n' foo foo
1005 echo status=$?
1006
1007 # invalid time
1008 printf '%(%Y)T\n' z
1009 echo status=$?
1010
1011 # invalid time with no SPID
1012 printf '%(%Y)T\n'
1013 echo status=$?
1014
1015 # invalid integer with no SPID
1016 printf '%d %d %d\n' 1 2
1017 echo status=$?
1018}
1019
1020
1021unquoted-builtin_wait() {
1022 wait 1234578
1023}
1024
1025unquoted-builtin_exec() {
1026 exec nonexistent-command 1 2 3
1027 echo $?
1028}
1029
1030#
1031# Strict options (see spec/strict_options.sh)
1032#
1033
1034unquoted-strict_word_eval_warnings() {
1035 # Warnings when 'set +o strict_word_eval' is OFF
1036
1037 echo slice start negative
1038 s='abc'
1039 echo -${s: -2}-
1040
1041 echo slice length negative
1042 s='abc'
1043 echo -${s: 1: -2}-
1044
1045 # TODO: These need span IDs.
1046 # - invalid utf-8 and also invalid backslash escape
1047
1048 echo slice bad utf-8
1049 s=$(echo -e "\xFF")bcdef
1050 echo -${s:1:3}-
1051
1052 echo length bad utf-8
1053 echo ${#s}
1054}
1055
1056unquoted-strict_arith_warnings() {
1057 local x='xx'
1058 echo $(( x + 1 ))
1059
1060 # TODO: OSH is more lenient here actually
1061 local y='-yy-'
1062 echo $(( y + 1 ))
1063
1064 [[ $y -eq 0 ]]
1065
1066 echo 'done'
1067}
1068
1069test-control_flow_subshell() {
1070 _osh-error-1 '
1071 set -o errexit
1072 for i in $(seq 2); do
1073 echo $i
1074 ( break; echo oops)
1075 done
1076 '
1077}
1078
1079test-fallback_locations() {
1080 # Redirect
1081 _osh-error-1 'echo hi > /'
1082
1083 _osh-error-1 's=x; (( s[0] ))'
1084
1085 _osh-error-1 's=x; (( s[0] = 42 ))'
1086
1087 _osh-error-1 'set -u; (( undef ))'
1088
1089 _osh-error-1 '(( 3 ** -2 ))'
1090 echo
1091
1092 # DBracket
1093 _osh-error-1 'set -u; [[ $undef =~ . ]]'
1094
1095 # No good fallback info here, we need it
1096 _osh-error-1 '[[ $x =~ $(( 3 ** -2 )) ]]'
1097
1098 _osh-error-2 'type -x' # correctly points to -x
1099 _osh-error-2 'use x'
1100
1101 # Assign builtin
1102 _osh-error-2 'export -f'
1103
1104 _osh-error-1 's=$(true) y=$(( 3 ** -2 ))'
1105
1106 _osh-error-1 'if s=$(true) y=$(( 3 ** -2 )); then echo hi; fi'
1107
1108 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( x ))'
1109 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( $x ))'
1110 _osh-error-1 'shopt -s strict_arith; x=a; [[ $x -gt 3 ]]'
1111 _osh-error-1 'shopt -s strict_arith; shopt -u eval_unsafe_arith; x=a; [[ $x -gt 3 ]]'
1112
1113 _osh-error-1 'shopt -s strict_arith; x=0xgg; echo $(( x ))'
1114
1115 echo done
1116}
1117
1118test-var-op-qmark() {
1119 _osh-error-1 'echo ${zz?}'
1120 _osh-error-1 'echo ${zz:?}'
1121
1122 _osh-should-run 'zz=""; echo ${zz?}'
1123 _osh-error-1 'zz=""; echo ${zz:?}'
1124
1125 _osh-error-1 'echo ${zz?Required}'
1126 _osh-error-1 'echo ${zz:?Required}'
1127}
1128
1129test-external_cmd_typed_args() {
1130 _ysh-error-X 1 'cat ("myfile")'
1131}
1132
1133test-arith_ops_str() {
1134 _ysh-error-X 3 '= "100" + "10a"'
1135 _ysh-error-X 3 '= "100" - "10a"'
1136 _ysh-error-X 3 '= "100" * "10a"'
1137 _ysh-error-X 3 '= "100" / "10a"'
1138 _ysh-error-X 3 'var a = "100"; setvar a += "10a"'
1139 _ysh-error-X 3 'var a = "100"; setvar a -= "10a"'
1140 _ysh-error-X 3 'var a = "100"; setvar a *= "10a"'
1141 _ysh-error-X 3 'var a = "100"; setvar a /= "10a"'
1142 _ysh-error-X 3 '= "age: " + "100"'
1143 _ysh-error-X 3 'var myvar = "a string"
1144= 100 + myvar'
1145}
1146
1147assert-test-v-error() {
1148 local code=$1
1149
1150 # note: the test builtin fails with status 2, but the shell doesn't fail
1151 _osh-error-2 "shopt -s strict_word_eval; a=(1 2 3); $code"
1152}
1153
1154test-test-v-expr() {
1155 assert-test-v-error 'test -v ""'
1156 assert-test-v-error 'test -v "a[foo"'
1157 assert-test-v-error 'test -v "a[not-int]"'
1158 assert-test-v-error 'test -v "a[-42]"'
1159
1160 _osh-error-2 'shopt -s strict_word_eval; s=""; test -v s[0]'
1161}
1162
1163test-long-shell-line() {
1164 # Example from https://github.com/oilshell/oil/issues/1973
1165
1166 _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'
1167 echo
1168}
1169
1170#
1171# TEST DRIVER
1172#
1173
1174list-unquoted-funcs() {
1175 ### These tests need assertions, with quoting
1176
1177 compgen -A function | egrep '^unquoted-'
1178}
1179
1180run-unquoted-funcs() {
1181 local i=0
1182 list-unquoted-funcs | ( while read -r test_func; do
1183 _run-test-func $test_func '' # don't assert status
1184 i=$((i + 1))
1185 done
1186
1187 # Hacky subshell for $i
1188 echo
1189 echo "$0: $i unquoted functions run. TODO: migrate to test-* to assert status"
1190 )
1191}
1192
1193all-tests() {
1194
1195 section-banner 'Runtime errors - Unquoted test functions'
1196 # Legacy functions that don't check status
1197 run-unquoted-funcs
1198
1199 section-banner 'Runtime errors - test functions'
1200
1201 # Run with strict mode
1202 set -o nounset
1203 set -o pipefail
1204 set -o errexit
1205
1206 run-test-funcs
1207}
1208
1209# TODO: could show these as separate text files in the CI
1210
1211with-bash() {
1212 SH_ASSERT_DISABLE=1 OSH=bash YSH=bash run-test-funcs
1213}
1214
1215with-dash() {
1216 SH_ASSERT_DISABLE=1 OSH=dash YSH=dash run-test-funcs
1217}
1218
1219soil-run-py() {
1220 all-tests
1221}
1222
1223soil-run-cpp() {
1224 # TODO: There are some UBSAN errors, like downcasting mylib::LineReader.
1225 # Is that a real problem? Could be due to mylib::File.
1226
1227 #local osh=_bin/cxx-ubsan/osh
1228
1229 local osh=_bin/cxx-asan/osh
1230
1231 ninja $osh
1232 OSH=$osh all-tests
1233}
1234
1235run-for-release() {
1236 run-other-suite-for-release runtime-errors all-tests
1237}
1238
1239"$@"