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

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