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

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