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

1199 lines, 626 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
681# Only dash flags this as an error.
682unquoted-string_to_int_arith() {
683 local x='ZZZ'
684 echo $(( x + 5 ))
685
686 shopt -s strict_arith
687
688 echo $(( x + 5 ))
689
690 echo 'SHOULD NOT GET HERE'
691}
692
693# Hm bash treats this as a fatal error
694unquoted-string_to_hex() {
695 echo $(( 0xGG + 1 ))
696
697 echo 'SHOULD NOT GET HERE'
698}
699
700# Hm bash treats this as a fatal error
701unquoted-string_to_octal() {
702 echo $(( 018 + 1 ))
703
704 echo 'SHOULD NOT GET HERE'
705}
706
707# Hm bash treats this as a fatal error
708unquoted-string_to_intbase() {
709 echo $(( 16#GG ))
710
711 echo 'SHOULD NOT GET HERE'
712}
713
714unquoted-undef_arith() {
715 (( undef++ )) # doesn't make sense
716
717 # Can't assign to characters of string? Is that strong?
718 (( undef[42]++ ))
719}
720
721unquoted-undef_arith2() {
722 a=()
723
724 # undefined cell: This is kind of what happens in awk / "wok"
725 (( a[42]++ ))
726 (( a[42]++ ))
727 spec/bin/argv.py "${a[@]}"
728}
729
730unquoted-array_arith() {
731 a=(1 2)
732 (( a++ )) # doesn't make sense
733 echo "${a[@]}"
734}
735
736unquoted-undef-assoc-array() {
737 declare -A A
738 A['foo']=bar
739 echo "${A['foo']}"
740
741 A['spam']+=1
742 A['spam']+=1
743
744 spec/bin/argv.py "${A[@]}"
745
746 (( A['spam']+=5 ))
747
748 spec/bin/argv.py "${A[@]}"
749}
750
751test-assoc-array() {
752 _osh-error-1 'declare -A assoc; assoc[x]=1'
753 _osh-should-run 'declare -A assoc; assoc[$key]=1'
754
755 # double quotes
756 _osh-should-run 'declare -A assoc; assoc["x"]=1'
757
758 # single quotes
759 _osh-should-run-here <<'EOF'
760declare -A assoc; assoc['x']=1
761EOF
762
763 _osh-error-1 'declare -A assoc; echo ${assoc[x]}'
764 _osh-should-run 'declare -A assoc; echo ${assoc["x"]}'
765 _osh-should-run 'declare -A assoc; echo ${assoc[$key]}'
766
767 _osh-error-1 'declare -A assoc; key=k; unset assoc[$key]'
768 # quotes removed
769 _osh-error-1 'declare -A assoc; key=k; unset "assoc[$key]"'
770
771 # Like Samuel's Nix error
772 # unset -v "hardeningEnableMap[$flag]"
773 _osh-error-here-X 1 <<'EOF'
774declare -A assoc; key=k; unset "assoc[$key]"
775EOF
776
777 # SINGLE quotes fixes it
778 _osh-should-run-here <<'EOF'
779declare -A assoc; key=k; unset 'assoc[$key]'
780EOF
781
782 # Wrap in eval to see how it composes
783 _osh-error-here-X 1 <<'EOF'
784eval 'declare -A assoc; assoc[x]=1'
785EOF
786
787 _osh-error-here-X 1 <<'EOF'
788eval 'declare -A assoc; unset "assoc[x]"'
789EOF
790
791}
792
793unquoted-patsub_bad_glob() {
794 local x='abc'
795 # inspired by git-completion.bash
796 echo ${x//[^]}
797}
798
799
800#
801# BOOLEAN ERRORS
802#
803
804# Only osh cares about this.
805unquoted-string_to_int_bool() {
806 [[ a -eq 0 ]]
807
808 shopt -s strict_arith
809
810 [[ a -eq 0 ]]
811 echo 'SHOULD NOT GET HERE'
812}
813
814unquoted-strict_array() {
815 set -- 1 2
816 echo foo > _tmp/"$@"
817 shopt -s strict_array
818 echo foo > _tmp/"$@"
819}
820
821unquoted-strict_array_2() {
822 local foo="$@"
823 shopt -s strict_array
824 local foo="$@"
825}
826
827unquoted-strict_array_3() {
828 local foo=${1:- "[$@]" }
829 shopt -s strict_array
830 local foo=${1:- "[$@]" }
831}
832
833unquoted-strict_array_4() {
834 local -a x
835 x[42]=99
836 echo "x[42] = ${x[42]}"
837
838 # Not implemented yet
839 shopt -s strict_array
840 local -a y
841 y[42]=99
842}
843
844unquoted-array_assign_1() {
845 s=1
846 s[0]=x # can't assign value
847}
848
849unquoted-array_assign_2() {
850 _osh-error-1 'readonly -a array=(1 2 3); array[0]=x'
851
852 _osh-error-1 'readonly -a array=(1 2 3); export array'
853}
854
855unquoted-readonly_assign() {
856 _osh-error-1 'readonly x=1; x=2'
857
858 _osh-error-1 'readonly x=2; y=3 x=99'
859
860 _osh-error-1 'readonly x=2; declare x=99'
861 _osh-error-1 'readonly x=2; export x=99'
862}
863
864unquoted-multiple_assign() {
865 readonly x=1
866 # It blames x, not a!
867 a=1 b=2 x=42
868}
869
870unquoted-multiple_assign_2() {
871 readonly y
872 local x=1 y=$(( x ))
873 echo $y
874}
875
876unquoted-string_as_array() {
877 local str='foo'
878 echo $str
879 echo "${str[@]}"
880}
881
882#
883# BUILTINS
884#
885
886unquoted-builtin_bracket() {
887 set +o errexit
888
889 # xxx is not a valid file descriptor
890 [ -t xxx ]
891 [ -t '' ]
892
893 [ zz -eq 0 ]
894
895 # This is from a different evaluator
896 #[ $((a/0)) -eq 0 ]
897}
898
899unquoted-builtin_builtin() {
900 set +o errexit
901 builtin ls
902}
903
904unquoted-builtin_source() {
905 source
906
907 bad=/nonexistent/path
908 source $bad
909}
910
911unquoted-builtin_cd() {
912 ( unset HOME
913 cd
914 )
915
916 # TODO: Hm this gives a different useful error without location info
917 ( unset HOME
918 HOME=(a b)
919 cd
920 )
921
922 # TODO: Hm this gives a different useful error without location info
923 ( unset OLDPWD
924 cd -
925 )
926
927 ( cd /nonexistent
928 )
929}
930
931unquoted-builtin_pushd() {
932 pushd /nonexistent
933}
934
935unquoted-builtin_popd() {
936 popd # empty dir stack
937
938 (
939 local dir=$PWD/_tmp/runtime-error-popd
940 mkdir -p $dir
941 pushd $dir
942 pushd /
943 rmdir $dir
944 popd
945 )
946}
947
948unquoted-builtin_unset() {
949 local x=x
950 readonly a
951
952 unset x a
953 unset -v x a
954}
955
956unquoted-builtin_alias_unalias() {
957 alias zzz
958 unalias zzz
959}
960
961unquoted-builtin_help() {
962 help zzz
963}
964
965unquoted-builtin_trap() {
966 trap
967 trap EXIT
968
969 trap zzz yyy
970}
971
972unquoted-builtin_getopts() {
973 getopts
974 getopts 'a:'
975
976 # TODO: It would be nice to put this in a loop and use it properly
977 set -- -a
978 getopts 'a:' varname
979}
980
981builtin_printf() {
982 printf '%s %d\n' foo not_a_number
983 echo status=$?
984
985 # bad arg recycling. This is really a runtime error.
986 printf '%s %d\n' foo 3 bar
987 echo status=$?
988
989 # invalid width
990 printf '%*d\n' foo foo
991 echo status=$?
992
993 # precision can't be specified
994 printf '%.*d\n' foo foo
995 echo status=$?
996
997 # precision can't be specified
998 printf '%.*s\n' foo foo
999 echo status=$?
1000
1001 # invalid time
1002 printf '%(%Y)T\n' z
1003 echo status=$?
1004
1005 # invalid time with no SPID
1006 printf '%(%Y)T\n'
1007 echo status=$?
1008
1009 # invalid integer with no SPID
1010 printf '%d %d %d\n' 1 2
1011 echo status=$?
1012}
1013
1014
1015unquoted-builtin_wait() {
1016 wait 1234578
1017}
1018
1019unquoted-builtin_exec() {
1020 exec nonexistent-command 1 2 3
1021 echo $?
1022}
1023
1024#
1025# Strict options (see spec/strict_options.sh)
1026#
1027
1028unquoted-strict_word_eval_warnings() {
1029 # Warnings when 'set +o strict_word_eval' is OFF
1030
1031 echo slice start negative
1032 s='abc'
1033 echo -${s: -2}-
1034
1035 echo slice length negative
1036 s='abc'
1037 echo -${s: 1: -2}-
1038
1039 # TODO: These need span IDs.
1040 # - invalid utf-8 and also invalid backslash escape
1041
1042 echo slice bad utf-8
1043 s=$(echo -e "\xFF")bcdef
1044 echo -${s:1:3}-
1045
1046 echo length bad utf-8
1047 echo ${#s}
1048}
1049
1050unquoted-strict_arith_warnings() {
1051 local x='xx'
1052 echo $(( x + 1 ))
1053
1054 # TODO: OSH is more lenient here actually
1055 local y='-yy-'
1056 echo $(( y + 1 ))
1057
1058 [[ $y -eq 0 ]]
1059
1060 echo 'done'
1061}
1062
1063test-control_flow_subshell() {
1064 _osh-error-1 '
1065 set -o errexit
1066 for i in $(seq 2); do
1067 echo $i
1068 ( break; echo oops)
1069 done
1070 '
1071}
1072
1073test-fallback_locations() {
1074 # Redirect
1075 _osh-error-1 'echo hi > /'
1076
1077 _osh-error-1 's=x; (( s[0] ))'
1078
1079 _osh-error-1 's=x; (( s[0] = 42 ))'
1080
1081 _osh-error-1 'set -u; (( undef ))'
1082
1083 _osh-error-1 '(( 3 ** -2 ))'
1084 echo
1085
1086 # DBracket
1087 _osh-error-1 'set -u; [[ $undef =~ . ]]'
1088
1089 # No good fallback info here, we need it
1090 _osh-error-1 '[[ $x =~ $(( 3 ** -2 )) ]]'
1091
1092 _osh-error-2 'type -x' # correctly points to -x
1093 _osh-error-2 'use x'
1094
1095 # Assign builtin
1096 _osh-error-2 'export -f'
1097
1098 _osh-error-1 's=$(true) y=$(( 3 ** -2 ))'
1099
1100 _osh-error-1 'if s=$(true) y=$(( 3 ** -2 )); then echo hi; fi'
1101
1102 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( x ))'
1103 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( $x ))'
1104 _osh-error-1 'shopt -s strict_arith; x=a; [[ $x -gt 3 ]]'
1105 _osh-error-1 'shopt -s strict_arith; shopt -u eval_unsafe_arith; x=a; [[ $x -gt 3 ]]'
1106
1107 _osh-error-1 'shopt -s strict_arith; x=0xgg; echo $(( x ))'
1108
1109 echo done
1110}
1111
1112test-external_cmd_typed_args() {
1113 _ysh-error-X 1 'cat ("myfile")'
1114}
1115
1116test-arith_ops_str() {
1117 _ysh-error-X 3 '= "100" + "10a"'
1118 _ysh-error-X 3 '= "100" - "10a"'
1119 _ysh-error-X 3 '= "100" * "10a"'
1120 _ysh-error-X 3 '= "100" / "10a"'
1121 _ysh-error-X 3 'var a = "100"; setvar a += "10a"'
1122 _ysh-error-X 3 'var a = "100"; setvar a -= "10a"'
1123 _ysh-error-X 3 'var a = "100"; setvar a *= "10a"'
1124 _ysh-error-X 3 'var a = "100"; setvar a /= "10a"'
1125 _ysh-error-X 3 '= "age: " + "100"'
1126 _ysh-error-X 3 'var myvar = "a string"
1127= 100 + myvar'
1128}
1129
1130#
1131# TEST DRIVER
1132#
1133
1134list-unquoted-funcs() {
1135 ### These tests need assertions, with quoting
1136
1137 compgen -A function | egrep '^unquoted-'
1138}
1139
1140run-unquoted-funcs() {
1141 local i=0
1142 list-unquoted-funcs | ( while read -r test_func; do
1143 _run-test-func $test_func '' # don't assert status
1144 i=$((i + 1))
1145 done
1146
1147 # Hacky subshell for $i
1148 echo
1149 echo "$0: $i unquoted functions run. TODO: migrate to test-* to assert status"
1150 )
1151}
1152
1153all-tests() {
1154
1155 section-banner 'Runtime errors - Unquoted test functions'
1156 # Legacy functions that don't check status
1157 run-unquoted-funcs
1158
1159 section-banner 'Runtime errors - test functions'
1160
1161 # Run with strict mode
1162 set -o nounset
1163 set -o pipefail
1164 set -o errexit
1165
1166 run-test-funcs
1167}
1168
1169# TODO: could show these as separate text files in the CI
1170
1171with-bash() {
1172 SH_ASSERT_DISABLE=1 OSH=bash YSH=bash run-test-funcs
1173}
1174
1175with-dash() {
1176 SH_ASSERT_DISABLE=1 OSH=dash YSH=dash run-test-funcs
1177}
1178
1179soil-run-py() {
1180 all-tests
1181}
1182
1183soil-run-cpp() {
1184 # TODO: There are some UBSAN errors, like downcasting mylib::LineReader.
1185 # Is that a real problem? Could be due to mylib::File.
1186
1187 #local osh=_bin/cxx-ubsan/osh
1188
1189 local osh=_bin/cxx-asan/osh
1190
1191 ninja $osh
1192 OSH=$osh all-tests
1193}
1194
1195run-for-release() {
1196 run-other-suite-for-release runtime-errors all-tests
1197}
1198
1199"$@"