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

1206 lines, 630 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
1130test-long-shell-line() {
1131 # Example from https://github.com/oilshell/oil/issues/1973
1132
1133 _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'
1134 echo
1135}
1136
1137#
1138# TEST DRIVER
1139#
1140
1141list-unquoted-funcs() {
1142 ### These tests need assertions, with quoting
1143
1144 compgen -A function | egrep '^unquoted-'
1145}
1146
1147run-unquoted-funcs() {
1148 local i=0
1149 list-unquoted-funcs | ( while read -r test_func; do
1150 _run-test-func $test_func '' # don't assert status
1151 i=$((i + 1))
1152 done
1153
1154 # Hacky subshell for $i
1155 echo
1156 echo "$0: $i unquoted functions run. TODO: migrate to test-* to assert status"
1157 )
1158}
1159
1160all-tests() {
1161
1162 section-banner 'Runtime errors - Unquoted test functions'
1163 # Legacy functions that don't check status
1164 run-unquoted-funcs
1165
1166 section-banner 'Runtime errors - test functions'
1167
1168 # Run with strict mode
1169 set -o nounset
1170 set -o pipefail
1171 set -o errexit
1172
1173 run-test-funcs
1174}
1175
1176# TODO: could show these as separate text files in the CI
1177
1178with-bash() {
1179 SH_ASSERT_DISABLE=1 OSH=bash YSH=bash run-test-funcs
1180}
1181
1182with-dash() {
1183 SH_ASSERT_DISABLE=1 OSH=dash YSH=dash run-test-funcs
1184}
1185
1186soil-run-py() {
1187 all-tests
1188}
1189
1190soil-run-cpp() {
1191 # TODO: There are some UBSAN errors, like downcasting mylib::LineReader.
1192 # Is that a real problem? Could be due to mylib::File.
1193
1194 #local osh=_bin/cxx-ubsan/osh
1195
1196 local osh=_bin/cxx-asan/osh
1197
1198 ninja $osh
1199 OSH=$osh all-tests
1200}
1201
1202run-for-release() {
1203 run-other-suite-for-release runtime-errors all-tests
1204}
1205
1206"$@"