diff --git a/crates/bashkit/tests/spec_cases/bash/alias.test.sh b/crates/bashkit/tests/spec_cases/bash/alias.test.sh new file mode 100644 index 00000000..38c9c5f7 --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/alias.test.sh @@ -0,0 +1,169 @@ +# Alias tests +# Inspired by Oils spec/alias.test.sh +# https://github.com/oilshell/oil/blob/master/spec/alias.test.sh + +### alias_basic +# Basic alias definition and use +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias hi='echo hello world' +hi +### expect +hello world +### end + +### alias_override_builtin +# alias can override builtin +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias echo='echo foo' +echo bar +### expect +foo bar +### end + +### alias_define_multiple +# defining multiple aliases +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias echo_x='echo X' echo_y='echo Y' +echo_x +echo_y +### expect +X +Y +### end + +### alias_unalias +# unalias removes alias +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias hi='echo hello' +hi +unalias hi +hi 2>/dev/null +echo status=$? +### expect +hello +status=127 +### end + +### alias_unalias_all +# unalias -a removes all +### skip: TODO alias expansion not implemented +alias foo=bar +alias spam=eggs +unalias -a +alias 2>/dev/null | wc -l +### expect +0 +### end + +### alias_not_defined_error +# alias for non-existent returns error +### skip: TODO alias expansion not implemented +alias nonexistentZZZ 2>/dev/null +echo status=$? +### expect +status=1 +### end + +### alias_unalias_not_defined_error +# unalias for non-existent returns error +### skip: TODO alias expansion not implemented +unalias nonexistentZZZ 2>/dev/null +echo status=$? +### expect +status=1 +### end + +### alias_with_variable +# Alias with variable expansion at use-time +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +x=early +alias echo_x='echo $x' +x=late +echo_x +### expect +late +### end + +### alias_trailing_space +# alias with trailing space triggers expansion of next word +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias hi='echo hello world ' +alias punct='!!!' +hi punct +### expect +hello world !!! +### end + +### alias_recursive_first_word +# Recursive alias expansion of first word +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias hi='e_ hello world' +alias e_='echo __' +hi +### expect +__ hello world +### end + +### alias_must_be_unquoted +# Alias must be an unquoted word +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias echo_alias_='echo' +cmd=echo_alias_ +echo_alias_ X +$cmd X 2>/dev/null +echo status=$? +### expect +X +status=127 +### end + +### alias_in_pipeline +# Two aliases in pipeline +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias myseq='seq ' +alias mywc='wc ' +myseq 3 | mywc -l +### expect +3 +### end + +### alias_used_in_subshell +# alias used in subshell +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias echo_='echo [ ' +( echo_ subshell; ) +echo $(echo_ commandsub) +### expect +[ subshell +[ commandsub +### end + +### alias_with_semicolon_pipeline +# Alias that is && || ; +### skip: TODO alias expansion not implemented +shopt -s expand_aliases +alias t1='echo one && echo two' +t1 +### expect +one +two +### end + +### alias_list_all +# alias without args lists all +### skip: TODO alias expansion not implemented +alias ex=exit ll='ls -l' +alias | grep -c 'ex\|ll' +### expect +2 +### end diff --git a/crates/bashkit/tests/spec_cases/bash/arith-dynamic.test.sh b/crates/bashkit/tests/spec_cases/bash/arith-dynamic.test.sh new file mode 100644 index 00000000..1b4352af --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/arith-dynamic.test.sh @@ -0,0 +1,138 @@ +# Dynamic arithmetic tests +# Inspired by Oils spec/arith-dynamic.test.sh +# https://github.com/oilshell/oil/blob/master/spec/arith-dynamic.test.sh + +### arith_dyn_var_reference +# Variable references in arithmetic +x='1' +echo $(( x + 2 * 3 )) +### expect +7 +### end + +### arith_dyn_var_expression +# Variable containing expression is re-evaluated +### skip: TODO arithmetic variable re-evaluation of expressions not implemented +x='1 + 2' +echo $(( x * 3 )) +### expect +9 +### end + +### arith_dyn_substitution +# $x in arithmetic +x='1 + 2' +echo $(( $x * 3 )) +### expect +7 +### end + +### arith_dyn_quoted_substitution +# "$x" in arithmetic +### skip: TODO double-quoted substitution in arithmetic not implemented +x='1 + 2' +echo $(( "$x" * 3 )) +### expect +7 +### end + +### arith_dyn_nested_var +# Nested variable reference in arithmetic +### skip: TODO recursive variable dereferencing in arithmetic not implemented +a=3 +b=a +echo $(( b + 1 )) +### expect +4 +### end + +### arith_dyn_array_index +# Dynamic array index +### skip: TODO array access in arithmetic expressions not implemented +arr=(10 20 30 40) +i=2 +echo $(( arr[i] )) +### expect +30 +### end + +### arith_dyn_array_index_expr +# Expression as array index +### skip: TODO array access in arithmetic expressions not implemented +arr=(10 20 30 40) +echo $(( arr[1+1] )) +### expect +30 +### end + +### arith_dyn_conditional +# Ternary operator +x=5 +echo $(( x > 3 ? 1 : 0 )) +echo $(( x < 3 ? 1 : 0 )) +### expect +1 +0 +### end + +### arith_dyn_comma +# Comma operator +echo $(( 1, 2, 3 )) +### expect +3 +### end + +### arith_dyn_assign_in_expr +# Assignment within arithmetic expression +echo $(( x = 5 + 3 )) +echo $x +### expect +8 +8 +### end + +### arith_dyn_pre_post_increment +# Pre/post increment in dynamic context +x=5 +echo $(( x++ )) +echo $x +echo $(( ++x )) +echo $x +### expect +5 +6 +7 +7 +### end + +### arith_dyn_compound_assign +# Compound assignment operators +x=10 +echo $(( x += 5 )) +echo $(( x -= 3 )) +echo $(( x *= 2 )) +echo $(( x /= 4 )) +echo $(( x %= 2 )) +### expect +15 +12 +24 +6 +0 +### end + +### arith_dyn_unset_var_is_zero +# Unset variable in arithmetic treated as 0 +unset arith_undef_xyz +echo $(( arith_undef_xyz + 5 )) +### expect +5 +### end + +### arith_dyn_string_var_is_zero +# Non-numeric string in arithmetic treated as 0 +x=hello +echo $(( x + 5 )) +### expect +5 +### end diff --git a/crates/bashkit/tests/spec_cases/bash/empty-bodies.test.sh b/crates/bashkit/tests/spec_cases/bash/empty-bodies.test.sh new file mode 100644 index 00000000..7dc7b5d1 --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/empty-bodies.test.sh @@ -0,0 +1,90 @@ +# Empty body tests +# Inspired by Oils spec/empty-bodies.test.sh +# https://github.com/oilshell/oil/blob/master/spec/empty-bodies.test.sh + +### empty_case_esac +# Empty case/esac is valid +case foo in +esac +echo empty +### expect +empty +### end + +### empty_while_do_done +# Empty while body - bash treats as parse error, bashkit allows it +### skip: TODO empty while body not rejected as parse error +bash -c 'while false; do +done +echo empty' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### empty_if_then_fi +# Empty then body - bash treats as parse error, bashkit allows it +### skip: TODO empty if body not rejected as parse error +bash -c 'if true; then +fi +echo empty' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### empty_else_clause +# Empty else clause - bash treats as parse error, bashkit allows it +### skip: TODO empty else body not rejected as parse error +bash -c 'if false; then echo yes; else +fi' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### empty_for_body +# Empty for body - bash treats as parse error, bashkit allows it +### skip: TODO empty for body not rejected as parse error +bash -c 'for i in 1 2 3; do +done' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### empty_function_body +# Empty function body - bash treats as parse error, bashkit allows it +### skip: TODO empty function body not rejected as parse error +bash -c 'f() { }' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### case_with_empty_clause +# case with empty clauses is valid +case foo in + bar) + ;; + foo) + echo matched + ;; +esac +### expect +matched +### end + +### case_empty_fallthrough +# case with empty clause and fallthrough +case x in + x) + ;; + *) + echo no + ;; +esac +echo done +### expect +done +### end diff --git a/crates/bashkit/tests/spec_cases/bash/exit-status.test.sh b/crates/bashkit/tests/spec_cases/bash/exit-status.test.sh new file mode 100644 index 00000000..7e8b6bbc --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/exit-status.test.sh @@ -0,0 +1,179 @@ +# Exit status tests +# Inspired by Oils spec/exit-status.test.sh +# https://github.com/oilshell/oil/blob/master/spec/exit-status.test.sh + +### exit_truncation_255 +# exit 255 is preserved +bash -c 'exit 255' +echo status=$? +### expect +status=255 +### end + +### exit_truncation_256 +# exit 256 truncates to 0 +### skip: TODO exit status not truncated to 8-bit range +bash -c 'exit 256' +echo status=$? +### expect +status=0 +### end + +### exit_truncation_257 +# exit 257 truncates to 1 +### skip: TODO exit status not truncated to 8-bit range +bash -c 'exit 257' +echo status=$? +### expect +status=1 +### end + +### exit_negative_minus1 +# exit -1 wraps to 255 +### skip: TODO negative exit codes not wrapped to unsigned 8-bit +bash -c 'exit -1' 2>/dev/null +echo status=$? +### expect +status=255 +### end + +### exit_negative_minus2 +# exit -2 wraps to 254 +### skip: TODO negative exit codes not wrapped to unsigned 8-bit +bash -c 'exit -2' 2>/dev/null +echo status=$? +### expect +status=254 +### end + +### return_truncation_255 +# return 255 is preserved +f() { return 255; }; f +echo status=$? +### expect +status=255 +### end + +### return_truncation_256 +# return 256 truncates to 0 +### skip: TODO return status not truncated to 8-bit range +f() { return 256; }; f +echo status=$? +### expect +status=0 +### end + +### return_truncation_257 +# return 257 truncates to 1 +### skip: TODO return status not truncated to 8-bit range +f() { return 257; }; f +echo status=$? +### expect +status=1 +### end + +### return_negative_minus1 +# return -1 wraps to 255 +### skip: TODO negative return codes not wrapped to unsigned 8-bit +f() { return -1; }; f 2>/dev/null +echo status=$? +### expect +status=255 +### end + +### return_negative_minus2 +# return -2 wraps to 254 +### skip: TODO negative return codes not wrapped to unsigned 8-bit +f() { return -2; }; f 2>/dev/null +echo status=$? +### expect +status=254 +### end + +### if_empty_command +# If empty command string - '' as command should fail +### skip: TODO empty string as command not treated as failed command +if ''; then echo TRUE; else echo FALSE; fi +### exit_code: 0 +### expect +FALSE +### end + +### empty_command_sub_exit_code +# Exit code propagation through empty command sub +`true`; echo $? +`false`; echo $? +$(true); echo $? +$(false); echo $? +### expect +0 +1 +0 +1 +### end + +### empty_argv_with_command_sub +# More test cases with empty argv from command sub +true $(false) +echo status=$? +$(exit 42) +echo status=$? +### expect +status=0 +status=42 +### end + +### pipeline_exit_status +# Pipeline exit status is last command +true | false +echo $? +false | true +echo $? +### expect +1 +0 +### end + +### and_list_exit_status +# AND list exit status +true && true; echo $? +true && false; echo $? +false && true; echo $? +### expect +0 +1 +1 +### end + +### or_list_exit_status +# OR list exit status +true || true; echo $? +true || false; echo $? +false || true; echo $? +false || false; echo $? +### expect +0 +0 +0 +1 +### end + +### subshell_exit_code +# Subshell preserves exit code +(exit 0); echo $? +(exit 1); echo $? +(exit 42); echo $? +### expect +0 +1 +42 +### end + +### negation_exit_status +# ! negates exit status +! true; echo $? +! false; echo $? +### expect +1 +0 +### end diff --git a/crates/bashkit/tests/spec_cases/bash/heredoc-edge.test.sh b/crates/bashkit/tests/spec_cases/bash/heredoc-edge.test.sh new file mode 100644 index 00000000..19868e16 --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/heredoc-edge.test.sh @@ -0,0 +1,199 @@ +# Heredoc edge cases +# Inspired by Oils spec/here-doc.test.sh +# https://github.com/oilshell/oil/blob/master/spec/here-doc.test.sh + +### heredoc_with_var_sub +# Here doc with var sub, command sub, arith sub +var=v +cat </dev/null +echo status=$? +### exit_code: 1 +### expect +status=1 +### end + +### parse_incomplete_while +# Incomplete while is a parse error (tested indirectly) +echo done +### expect +done +### end + +### parse_unexpected_do +# do unexpected outside loop - bashkit should reject this +### skip: TODO parser does not reject unexpected 'do' keyword +bash -c 'do echo hi' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_unexpected_rbrace +# } is a parse error at top level +### skip: TODO parser does not reject unexpected '}' at top level +bash -c '}' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_lbrace_needs_space +# { needs a space after it +### skip: TODO parser does not require space after '{' +bash -c '{ls; }' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_invalid_for_var +# Invalid for loop variable name +### skip: TODO parser does not reject invalid for-loop variable names +bash -c 'for i.j in a b c; do echo hi; done' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_bad_var_name_not_assignment +# bad var name globally isn't parsed like an assignment +bash -c 'FOO-BAR=foo' 2>/dev/null +echo status=$? +### expect +status=127 +### end + +### parse_bad_var_name_export +# bad var name in export +### skip: TODO export does not reject invalid variable names +bash -c 'export FOO-BAR=foo' 2>/dev/null +test $? -ne 0 && echo error +### expect +error +### end + +### parse_bad_var_name_local +# bad var name in local +### skip: TODO local does not reject invalid variable names +bash -c 'f() { local FOO-BAR=foo; }; f' 2>/dev/null +test $? -ne 0 && echo error +### expect +error +### end + +### parse_misplaced_parens +# misplaced parentheses are a syntax error +### skip: TODO parser does not reject misplaced parentheses +bash -c 'echo a(b)' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_incomplete_command_sub +# incomplete command sub +### skip: TODO parser does not reject incomplete command substitution +bash -c '$(x' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_incomplete_backticks +# incomplete backticks +### skip: TODO parser does not reject incomplete backticks +bash -c '`x' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_misplaced_double_semi +# misplaced ;; outside case +bash -c 'echo 1 ;; echo 2' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_empty_double_bracket +# empty clause in [[ +### skip: TODO [[ || true ]] not rejected as parse error +bash -c '[[ || true ]]' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_unterminated_single_quote +# Unterminated single quote is a parse error +### skip: TODO unterminated quotes not detected as parse error +bash -c "echo 'unterminated" 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_unterminated_double_quote +# Unterminated double quote is a parse error +### skip: TODO unterminated quotes not detected as parse error +bash -c 'echo "unterminated' 2>/dev/null +echo status=$? +### expect +status=2 +### end + +### parse_case_bad_semicolon +# Using ; instead of ;; in case +bash -c 'case x in x) ; y) echo ;; esac' 2>/dev/null +echo status=$? +### expect +status=2 +### end diff --git a/crates/bashkit/tests/spec_cases/bash/quote.test.sh b/crates/bashkit/tests/spec_cases/bash/quote.test.sh new file mode 100644 index 00000000..0ed5119c --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/quote.test.sh @@ -0,0 +1,287 @@ +# Quoting tests +# Inspired by Oils spec/quote.test.sh +# https://github.com/oilshell/oil/blob/master/spec/quote.test.sh + +### quote_unquoted_words +# Unquoted words collapse whitespace +echo unquoted words +### expect +unquoted words +### end + +### quote_single_quoted +# Single-quoted preserves whitespace +echo 'single quoted' +### expect +single quoted +### end + +### quote_two_single_parts +# Two single-quoted parts join +### skip: TODO adjacent single-quoted strings not joined correctly +echo 'two single-quoted pa''rts in one token' +### expect +two single-quoted parts in one token +### end + +### quote_unquoted_and_single +# Unquoted and single-quoted join +echo unquoted' and single-quoted' +### expect +unquoted and single-quoted +### end + +### quote_newline_in_single +# newline inside single-quoted string +echo 'newline +inside single-quoted string' +### expect +newline +inside single-quoted string +### end + +### quote_double_quoted +# Double-quoted preserves whitespace +echo "double quoted" +### expect +double quoted +### end + +### quote_mix_in_one_word +# Mix of quotes in one word +echo unquoted' single-quoted'" double-quoted "unquoted +### expect +unquoted single-quoted double-quoted unquoted +### end + +### quote_var_sub +# Var substitution in double quotes +FOO=bar +echo "==$FOO==" +### expect +==bar== +### end + +### quote_var_sub_braces +# Var substitution with braces +FOO=bar +echo foo${FOO} +### expect +foobar +### end + +### quote_var_sub_braces_quoted +# Var substitution with braces, quoted +FOO=bar +echo "foo${FOO}" +### expect +foobar +### end + +### quote_var_length +# Var length in double quotes +FOO=bar +echo "foo${#FOO}" +### expect +foo3 +### end + +### quote_backslash_store_echo +# Storing backslashes and then echoing them +one='\' +two='\\' +echo "$one" "$two" +### expect +\ \\ +### end + +### quote_backslash_escapes +# Backslash escapes outside quotes +echo \$ \| \a \b \c \d \\ +### expect +$ | a b c d \ +### end + +### quote_backslash_in_double +# Backslash escapes inside double quoted string +echo "\$ \\ \\ \p \q" +### expect +$ \ \ \p \q +### end + +### quote_no_c_escape_in_double +# C-style backslash escapes NOT special in double quotes +echo "\a \b" +### expect +\a \b +### end + +### quote_literal_dollar +# Literal $ +echo $ +### expect +$ +### end + +### quote_quoted_literal_dollar +# Quoted literal $ +echo $ "$" $ +### expect +$ $ $ +### end + +### quote_line_continuation +# Line continuation +echo foo\ +$ +### expect +foo$ +### end + +### quote_line_continuation_double +# Line continuation inside double quotes +echo "foo\ +$" +### expect +foo$ +### end + +### quote_semicolon +# Semicolon separates commands +echo separated; echo by semi-colon +### expect +separated +by semi-colon +### end + +### quote_no_tab_in_single +# No tab escapes within single quotes +echo 'a\tb' +### expect +a\tb +### end + +### quote_dollar_single_basic +# $'' basic +### skip: TODO $'' (dollar single quote) not implemented +echo $'foo' +### expect +foo +### end + +### quote_dollar_single_quotes +# $'' with quotes +### skip: TODO $'' (dollar single quote) not implemented +echo $'single \' double \"' +### expect +single ' double " +### end + +### quote_dollar_single_newlines +# $'' with newlines +### skip: TODO $'' (dollar single quote) not implemented +echo $'col1\ncol2\ncol3' +### expect +col1 +col2 +col3 +### end + +### quote_dollar_double_synonym +# $"" is a synonym for "" +### skip: TODO $"" (dollar double quote) not implemented +echo $"foo" +x=x +echo $"foo $x" +### expect +foo +foo x +### end + +### quote_dollar_single_hex +# $'' with hex escapes +### skip: TODO $'' (dollar single quote) not implemented +echo $'\x41\x42\x43' +### expect +ABC +### end + +### quote_dollar_single_octal +# $'' with octal escapes +### skip: TODO $'' (dollar single quote) not implemented +echo $'\101\102\103' +### expect +ABC +### end + +### quote_dollar_single_unicode_u +# $'' with \u unicode escape +### skip: TODO $'' (dollar single quote) not implemented +echo $'\u0041\u0042' +### expect +AB +### end + +### quote_dollar_single_unicode_U +# $'' with \U unicode escape +### skip: TODO $'' (dollar single quote) not implemented +echo $'\U00000041\U00000042' +### expect +AB +### end + +### quote_dollar_single_special +# $'' with special escapes +### skip: TODO $'' (dollar single quote) not implemented +printf '%s' $'\a' | od -A n -t x1 | tr -d ' \n' +echo +printf '%s' $'\b' | od -A n -t x1 | tr -d ' \n' +echo +printf '%s' $'\t' | od -A n -t x1 | tr -d ' \n' +echo +printf '%s' $'\n' | od -A n -t x1 | tr -d ' \n' +echo +### expect +07 +08 +09 +0a +### end + +### quote_empty_string_preserved +# Empty string as argument is preserved when quoted +### skip: TODO set -- with quoted empty args not preserving count +set -- "" "a" "" +echo $# +### expect +3 +### end + +### quote_nested_quotes_in_command_sub +# Nested quotes in command substitution +echo "$(echo "hello world")" +### expect +hello world +### end + +### quote_backslash_newline_removed +# Backslash-newline is line continuation (removed) +echo he\ +llo +### expect +hello +### end + +### quote_single_quote_in_double +# Single quote inside double quotes +echo "it's" +### expect +it's +### end + +### quote_double_in_single +# Double quote inside single quotes +echo 'say "hi"' +### expect +say "hi" +### end diff --git a/crates/bashkit/tests/spec_cases/bash/shell-grammar.test.sh b/crates/bashkit/tests/spec_cases/bash/shell-grammar.test.sh new file mode 100644 index 00000000..ace75c76 --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/shell-grammar.test.sh @@ -0,0 +1,243 @@ +# Shell grammar edge cases +# Inspired by Oils spec/shell-grammar.test.sh +# https://github.com/oilshell/oil/blob/master/spec/shell-grammar.test.sh + +### grammar_brace_group_oneline +# Brace group on one line +{ echo one; echo two; } +### expect +one +two +### end + +### grammar_subshell_oneline +# Subshell on one line +(echo one; echo two) +### expect +one +two +### end + +### grammar_subshell_multiline +# Subshell on multiple lines +(echo one +echo two +echo three +) +### expect +one +two +three +### end + +### grammar_for_do_done +# For loop standard form +for name in a b c +do + echo $name +done +### expect +a +b +c +### end + +### grammar_while_empty_lines +# While loop with empty lines in body +i=0 +while [ $i -lt 3 ]; do + + echo $i + + i=$((i+1)) + +done +### expect +0 +1 +2 +### end + +### grammar_until_loop +# Until loop +i=0 +until [ $i -ge 3 ]; do + echo $i + i=$((i+1)) +done +### expect +0 +1 +2 +### end + +### grammar_if_then_else +# If with then on separate line +if true +then + echo yes +else + echo no +fi +### expect +yes +### end + +### grammar_if_then_sameline +# If with then on same line +if true; then + echo yes +else + echo no +fi +### expect +yes +### end + +### grammar_if_oneline +# If on one line +if true; then echo yes; else echo no; fi +### expect +yes +### end + +### grammar_if_pipe +# If condition is a pipeline +if echo hello | grep -q hello; then + echo matched +fi +### expect +matched +### end + +### grammar_case_empty +# Empty case +case foo in +esac +echo done +### expect +done +### end + +### grammar_case_without_last_dsemi +# Case without trailing ;; +case foo in + foo) echo matched +esac +### expect +matched +### end + +### grammar_case_with_dsemi +# Case with trailing ;; +case foo in + foo) echo matched + ;; +esac +### expect +matched +### end + +### grammar_case_empty_clauses +# Case with empty clauses +case foo in + bar) + ;; + foo) + echo matched + ;; +esac +### expect +matched +### end + +### grammar_case_dsemi_sameline +# Case with ;; on same line +case foo in + foo) echo matched ;; +esac +### expect +matched +### end + +### grammar_case_two_patterns +# Case with two patterns +case b in + a|b) + echo matched + ;; + c) + echo no + ;; +esac +### expect +matched +### end + +### grammar_case_oneline +# Case all on one line +case foo in foo) echo matched ;; bar) echo no ;; esac +### expect +matched +### end + +### grammar_function_def +# Function definition +f() { + echo hello +} +f +### expect +hello +### end + +### grammar_function_keyword +# Function with keyword +function g { + echo world +} +g +### expect +world +### end + +### grammar_nested_if +# Nested if statements +if true; then + if false; then + echo no + else + echo yes + fi +fi +### expect +yes +### end + +### grammar_semicolons_and_newlines +# Mixed semicolons and newlines +echo a; echo b +echo c +### expect +a +b +c +### end + +### grammar_command_with_ampersand +# Background command (& as separator) +echo foreground +### expect +foreground +### end + +### grammar_and_or_lists +# AND and OR lists +true && echo and_true +false && echo and_false +false || echo or_false +true || echo or_true +### expect +and_true +or_false +### end diff --git a/crates/bashkit/tests/spec_cases/bash/subshell.test.sh b/crates/bashkit/tests/spec_cases/bash/subshell.test.sh new file mode 100644 index 00000000..12b85ff0 --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/subshell.test.sh @@ -0,0 +1,135 @@ +# Subshell tests +# Inspired by Oils spec/subshell.test.sh +# https://github.com/oilshell/oil/blob/master/spec/subshell.test.sh + +### subshell_exit_code +# Subshell exit code +( false; ) +echo $? +### expect +1 +### end + +### subshell_with_redirects +# Subshell with redirects +( echo 1 ) > /tmp/bashkit_sub_a.txt +( echo 2 ) > /tmp/bashkit_sub_b.txt +( echo 3; ) > /tmp/bashkit_sub_c.txt +( echo 4; echo 5 ) > /tmp/bashkit_sub_d.txt +echo status=$? +cat /tmp/bashkit_sub_a.txt /tmp/bashkit_sub_b.txt /tmp/bashkit_sub_c.txt /tmp/bashkit_sub_d.txt +rm -f /tmp/bashkit_sub_a.txt /tmp/bashkit_sub_b.txt /tmp/bashkit_sub_c.txt /tmp/bashkit_sub_d.txt +### expect +status=0 +1 +2 +3 +4 +5 +### end + +### subshell_var_isolation +# Variables set in subshell don't leak +X=original +( X=modified ) +echo $X +### expect +original +### end + +### subshell_function_isolation +# Functions defined in subshell don't leak +### skip: TODO function definitions in subshell leak to parent scope +( f() { echo inside; }; f ) +f 2>/dev/null +echo status=$? +### expect +inside +status=127 +### end + +### subshell_nested +# Nested subshells +echo $(echo $(echo deep)) +### expect +deep +### end + +### subshell_exit_propagation +# Exit in subshell doesn't exit parent +( exit 42 ) +echo "still running, status=$?" +### expect +still running, status=42 +### end + +### subshell_pipeline +# Subshell in pipeline +( echo hello; echo world ) | sort +### expect +hello +world +### end + +### subshell_cd_isolation +# cd in subshell doesn't affect parent +### skip: TODO cd in subshell leaks to parent (VFS model) +original=$(pwd) +( cd / ) +test "$(pwd)" = "$original" && echo isolated +### expect +isolated +### end + +### subshell_traps_isolated +# Traps in subshell don't leak to parent +### skip: TODO trap in subshell not isolated +trap 'echo parent' EXIT +( trap 'echo child' EXIT ) +trap - EXIT +echo done +### expect +child +done +### end + +### subshell_brace_group +# Brace group is NOT a subshell +X=original +{ X=modified; } +echo $X +### expect +modified +### end + +### subshell_command_sub_exit +# Command substitution exit code +result=$(exit 3) +echo "status=$?" +### expect +status=3 +### end + +### subshell_multiple_statements +# Multiple statements in subshell +( + echo first + echo second + echo third +) +### expect +first +second +third +### end + +### subshell_preserves_positional +# Positional params in subshell don't leak +### skip: TODO positional params in subshell leak to parent +set -- a b c +( set -- x y; echo "$@" ) +echo "$@" +### expect +x y +a b c +### end diff --git a/crates/bashkit/tests/spec_cases/bash/temp-binding.test.sh b/crates/bashkit/tests/spec_cases/bash/temp-binding.test.sh new file mode 100644 index 00000000..2a580b7e --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/temp-binding.test.sh @@ -0,0 +1,97 @@ +# Temporary variable binding tests +# Inspired by Oils spec/temp-binding.test.sh +# https://github.com/oilshell/oil/blob/master/spec/temp-binding.test.sh + +### temp_basic +# Temporary binding for command +X=original +X=temp echo done +echo X=$X +### expect +done +X=original +### end + +### temp_in_env +# Temp binding visible in command's environment +X=original +X=override printenv X +echo X=$X +### expect +override +X=original +### end + +### temp_multiple +# Multiple temp bindings +A=1 B=2 printenv A +A=1 B=2 printenv B +echo A=$A B=$B +### expect +1 +2 +A= B= +### end + +### temp_with_builtin +# Temp binding with builtin command +IFS=: read a b c <<< "x:y:z" +echo "$a $b $c" +### expect +x y z +### end + +### temp_empty_command +# Temp binding with no command persists +X=before +X=after +echo X=$X +### expect +X=after +### end + +### temp_in_function +# Temp binding with function call +f() { echo "inside X=$X"; } +X=original +X=temp f +echo "after X=$X" +### expect +inside X=temp +after X=original +### end + +### temp_ifs_for_read +# IFS temp binding for read +echo "a:b:c" | { IFS=: read x y z; echo "$x $y $z"; } +### expect +a b c +### end + +### temp_overwrites_during_command +# Temp binding overwrites var during command only +X=original +show() { echo "X=$X"; } +X=temp show +echo "X=$X" +### expect +X=temp +X=original +### end + +### temp_unset_var +# Temp binding on previously unset variable +unset TEMP_VAR_XYZ +TEMP_VAR_XYZ=hello printenv TEMP_VAR_XYZ +echo "after=${TEMP_VAR_XYZ:-unset}" +### expect +hello +after=unset +### end + +### temp_export_behavior +# Temp binding makes var available in child env +X=exported bash -c 'echo X=$X' +### expect +X=exported +### end diff --git a/crates/bashkit/tests/spec_cases/bash/unicode.test.sh b/crates/bashkit/tests/spec_cases/bash/unicode.test.sh new file mode 100644 index 00000000..78f7e6a4 --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/unicode.test.sh @@ -0,0 +1,143 @@ +# Unicode tests +# Inspired by Oils spec/unicode.test.sh +# https://github.com/oilshell/oil/blob/master/spec/unicode.test.sh + +### unicode_echo_literal +# Unicode literal in echo +echo μ +### expect +μ +### end + +### unicode_single_quoted +# Unicode in single quotes +echo 'μ' +### expect +μ +### end + +### unicode_double_quoted +# Unicode in double quotes +echo "μ" +### expect +μ +### end + +### unicode_dollar_single +# Unicode in $'' via \u escape +### skip: TODO $'' (dollar single quote) not implemented +echo $'\u03bc' +### expect +μ +### end + +### unicode_dollar_single_U +# Unicode in $'' via \U escape +### skip: TODO $'' (dollar single quote) not implemented +echo $'\U000003bc' +### expect +μ +### end + +### unicode_printf_u +# printf \u escape +### skip: TODO printf \u unicode escape not implemented +printf '\u03bc\n' +### expect +μ +### end + +### unicode_printf_U +# printf \U escape +### skip: TODO printf \U unicode escape not implemented +printf '\U000003bc\n' +### expect +μ +### end + +### unicode_var_with_unicode +# Variable with unicode value +x=café +echo $x +### expect +café +### end + +### unicode_string_length +# String length of unicode string +### skip: TODO ${#x} counts bytes instead of characters for unicode +x=café +echo ${#x} +### expect +4 +### end + +### unicode_in_array +# Unicode strings in array +arr=(α β γ) +echo ${arr[0]} ${arr[1]} ${arr[2]} +echo ${#arr[@]} +### expect +α β γ +3 +### end + +### unicode_in_case +# Unicode in case pattern +x=α +case $x in + α) echo matched ;; + *) echo no ;; +esac +### expect +matched +### end + +### unicode_in_test +# Unicode in test/comparison +x=café +if [[ $x == café ]]; then echo equal; fi +### expect +equal +### end + +### unicode_concatenation +# Unicode string concatenation +a="hello " +b="世界" +echo "$a$b" +### expect +hello 世界 +### end + +### unicode_in_for +# Unicode in for loop +for c in α β γ; do + printf "[%s]" "$c" +done +echo +### expect +[α][β][γ] +### end + +### unicode_multibyte_echo +# Multi-byte unicode characters +echo "日本語" +### expect +日本語 +### end + +### unicode_emoji +# Emoji in strings +echo "hello 🌍" +### expect +hello 🌍 +### end + +### unicode_dollar_single_ascii +# $'' with unicode for ASCII range +### skip: TODO $'' (dollar single quote) not implemented +echo $'\u0041\u0042\u0043' +### expect +ABC +### end diff --git a/crates/bashkit/tests/spec_cases/bash/var-op-test.test.sh b/crates/bashkit/tests/spec_cases/bash/var-op-test.test.sh new file mode 100644 index 00000000..fa4a666c --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/var-op-test.test.sh @@ -0,0 +1,264 @@ +# Variable operator test (:- :+ := :?) edge cases +# Inspired by Oils spec/var-op-test.test.sh +# https://github.com/oilshell/oil/blob/master/spec/var-op-test.test.sh + +### vop_lazy_eval_alternative +# Lazy Evaluation of Alternative +### skip: TODO lazy evaluation of ${x:-expr} not implemented (expr always evaluated) +i=0 +x=x +echo ${x:-$((i++))} +echo $i +echo ${undefined:-$((i++))} +echo $i +### expect +x +0 +0 +1 +### end + +### vop_default_when_empty +# Default value when empty +empty='' +echo ${empty:-is empty} +### expect +is empty +### end + +### vop_default_when_unset +# Default value when unset +### skip: TODO ${var-value} (without colon) not implemented +echo ${unset_var_xyz-is unset} +### expect +is unset +### end + +### vop_assign_default_empty +# Assign default value when empty +empty='' +: ${empty:=is empty} +echo $empty +### expect +is empty +### end + +### vop_assign_default_unset +# Assign default value when unset +### skip: TODO ${var=value} (without colon) not implemented +: ${vop_unset_var=is unset} +echo $vop_unset_var +### expect +is unset +### end + +### vop_alternative_when_set +# ${v:+foo} Alternative value when set +### skip: TODO ${v:+foo} outputs trailing space +v=foo +empty='' +echo "${v:+v is not empty}" "${empty:+is not empty}" +### expect +v is not empty +### end + +### vop_alternative_when_unset +# ${v+foo} Alternative value when unset +### skip: TODO ${v+foo} (without colon) not implemented correctly +v=foo +echo "${v+v is not unset}" "${vop_unset2:+is not unset}" +### expect +v is not unset +### end + +### vop_quoted_alternative_regression +# "${x+foo}" quoted regression +echo "${vop_with_icc+set}" = set +### expect + = set +### end + +### vop_plus_with_set_u +# ${s+foo} and ${s:+foo} when set -u +### skip: TODO ${v+foo} and set -u interaction not implemented +set -u +v=v +echo v=${v:+foo} +echo v=${v+foo} +unset v +echo v=${v:+foo} +echo v=${v+foo} +set +u +### expect +v=foo +v=foo +v= +v= +### end + +### vop_minus_with_set_u +# ${v-foo} and ${v:-foo} when set -u +### skip: TODO ${v-foo} and set -u interaction not implemented +set -u +v=v +echo v=${v:-foo} +echo v=${v-foo} +unset v +echo v=${v:-foo} +echo v=${v-foo} +set +u +### expect +v=v +v=v +v=foo +v=foo +### end + +### vop_error_when_empty +# Error when empty +### bash_diff: uses bash -c which may differ in sandbox +bash -c 'empty=""; echo ${empty:?"is empty"}' 2>/dev/null +echo status=$? +### expect +status=1 +### end + +### vop_error_when_unset +# Error when unset +### skip: TODO ${var?msg} (without colon) not implemented +bash -c 'echo ${vop_unset3?"is unset"}' 2>/dev/null +echo status=$? +### expect +status=1 +### end + +### vop_assign_dynamic_scope +# ${var=x} dynamic scope in function +f() { : "${hello:=x}"; echo $hello; } +f +echo hello=$hello +### expect +x +hello=x +### end + +### vop_array_assign_default +# array ${arr[0]=x} +### skip: TODO ${arr[0]=x} array element default assignment not implemented +arr=() +echo ${#arr[@]} +: ${arr[0]=x} +echo ${#arr[@]} +### expect +0 +1 +### end + +### vop_backslash_in_default +# "\z" as default value arg +### skip: TODO backslash escapes in ${undef-value} not implemented +echo "${undef_bs1-\$}" +echo "${undef_bs2-\(}" +echo "${undef_bs3-\z}" +echo "${undef_bs4-\"}" +echo "${undef_bs5-\`}" +echo "${undef_bs6-\\}" +### expect +$ +\( +\z +" +` +\ +### end + +### vop_at_empty_minus_plus +# $@ (empty) and - and + +### skip: TODO ${@-value} and ${@+value} operators on $@ not implemented +set -- +echo "argv=${@-minus}" +echo "argv=${@+plus}" +echo "argv=${@:-minus}" +echo "argv=${@:+plus}" +### expect +argv=minus +argv= +argv=minus +argv= +### end + +### vop_at_one_empty_minus_plus +# $@ ("") and - and + +### skip: TODO ${@-value} and ${@+value} operators on $@ not implemented +set -- "" +echo "argv=${@-minus}" +echo "argv=${@+plus}" +echo "argv=${@:-minus}" +echo "argv=${@:+plus}" +### expect +argv= +argv=plus +argv=minus +argv= +### end + +### vop_at_two_empty_minus_plus +# $@ ("" "") and - and + +### skip: TODO ${@-value} and ${@+value} operators on $@ not implemented +set -- "" "" +echo "argv=${@-minus}" +echo "argv=${@+plus}" +echo "argv=${@:-minus}" +echo "argv=${@:+plus}" +### expect +argv= +argv=plus +argv= +argv=plus +### end + +### vop_array_empty_minus +# array and - operator +### skip: TODO ${arr[@]-value} operator on arrays not implemented +arr=() +echo ${arr[@]-minus} +arr=('') +echo ${arr[@]-minus} +arr=(3 4) +echo ${arr[@]-minus} +### expect +minus + +3 4 +### end + +### vop_array_empty_plus +# array and + operator +### skip: TODO ${arr[@]+value} operator on arrays not implemented +arr=() +echo ${arr[@]+plus} +arr=('') +echo ${arr[@]+plus} +arr=(3 4) +echo ${arr[@]+plus} +### expect + +plus +plus +### end + +### vop_assoc_array_minus_plus +# assoc array and - and + +### skip: TODO ${arr[@]-value} and ${arr[@]+value} on assoc arrays not implemented +declare -A empty_assoc=() +declare -A assoc=(['k']=v) +echo empty=${empty_assoc[@]-minus} +echo empty=${empty_assoc[@]+plus} +echo assoc=${assoc[@]-minus} +echo assoc=${assoc[@]+plus} +### expect +empty=minus +empty= +assoc=v +assoc=plus +### end diff --git a/crates/bashkit/tests/spec_cases/bash/word-split.test.sh b/crates/bashkit/tests/spec_cases/bash/word-split.test.sh new file mode 100644 index 00000000..f6cf5df2 --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/word-split.test.sh @@ -0,0 +1,505 @@ +# Word splitting tests +# Inspired by Oils spec/word-split.test.sh +# https://github.com/oilshell/oil/blob/master/spec/word-split.test.sh + +### ws_ifs_scoped +# IFS is scoped with local +### skip: TODO IFS-based word splitting not implemented +IFS=b +word=abcd +f() { local IFS=c; echo "$word" | tr "$IFS" '\n' | while read part; do printf '[%s]' "$part"; done; echo; } +# Actually test splitting directly +f2() { local IFS=c; set -- $word; echo "$#:$1:$2"; } +f2 +IFS=b +set -- $word +echo "$#:$1:$2" +### expect +2:a:d +2:a:cd +### end + +### ws_tilde_not_split +# Tilde sub is not split, but var sub is +### skip: TODO set -- with word splitting not implemented +HOME="foo bar" +set -- ~ +echo $# +set -- $HOME +echo $# +### expect +1 +2 +### end + +### ws_word_joining +# Word splitting with quoted and unquoted parts +### skip: TODO set -- with word splitting not implemented +a="1 2" +b="3 4" +set -- $a"$b" +echo $# +echo "$1" +echo "$2" +### expect +2 +1 +23 4 +### end + +### ws_word_joining_complex +# Complex word splitting with multiple parts +### skip: TODO set -- with word splitting not implemented +a="1 2" +b="3 4" +c="5 6" +d="7 8" +set -- $a"$b"$c"$d" +echo "$#" +echo "$1" +echo "$2" +echo "$3" +### expect +3 +1 +23 45 +67 8 +### end + +### ws_dollar_star +# $* splits arguments +### skip: TODO $@/$* splitting/joining not implemented +fun() { set -- $*; echo "$#:$1:$2:$3"; } +fun "a 1" "b 2" +### expect +4:a:1:b +### end + +### ws_quoted_dollar_star +# "$*" joins with first char of IFS +fun() { echo "$*"; } +fun "a 1" "b 2" "c 3" +### expect +a 1 b 2 c 3 +### end + +### ws_dollar_at +# $@ splits arguments +### skip: TODO $@/$* splitting/joining not implemented +fun() { set -- $@; echo "$#:$1:$2:$3"; } +fun "a 1" "b 2" +### expect +4:a:1:b +### end + +### ws_quoted_dollar_at +# "$@" preserves arguments +### skip: TODO $@/$* splitting/joining not implemented +fun() { echo $#; for a in "$@"; do echo "[$a]"; done; } +fun "a 1" "b 2" "c 3" +### expect +3 +[a 1] +[b 2] +[c 3] +### end + +### ws_empty_argv +# empty $@ and $* are elided +### skip: TODO $@/$* splitting/joining not implemented +set -- +set -- 1 "$@" 2 $@ 3 "$*" 4 $* 5 +echo "$#" +echo "$1 $2 $3 $4 $5" +### expect +6 +1 2 3 4 +### end + +### ws_star_empty_ifs +# $* with empty IFS +### skip: TODO $@/$* splitting/joining not implemented +set -- "1 2" "3 4" +IFS= +set -- $* +echo $# +echo "$1" +echo "$2" +### expect +2 +1 2 +3 4 +### end + +### ws_star_empty_ifs_quoted +# "$*" with empty IFS joins without separator +### skip: TODO $@/$* splitting/joining not implemented +set -- "1 2" "3 4" +IFS= +echo "$*" +### expect +1 23 4 +### end + +### ws_elision_space +# Unquoted whitespace-only var is elided +s1=' ' +set -- $s1 +echo $# +### expect +0 +### end + +### ws_elision_nonwhitespace_ifs +# Non-whitespace IFS char produces empty field +### skip: TODO IFS-based word splitting not implemented +IFS='_' +char='_' +space=' ' +empty='' +set -- $char; echo $# +set -- $space; echo "$1" +set -- $empty; echo $# +### expect +1 + +0 +### end + +### ws_leading_trailing_nonwhitespace_ifs +# Leading/trailing with non-whitespace IFS +### skip: TODO IFS-based word splitting not implemented +IFS=_ +s1='_a_b_' +set -- $s1 +echo "$#:$1:$2:$3" +### expect +3::a:b +### end + +### ws_mixed_ifs_whitespace_nonwhitespace +# Mixed whitespace and non-whitespace IFS +### skip: TODO IFS-based word splitting not implemented +IFS='_ ' +s1='_ a b _ ' +s2=' a b _ ' +set -- $s1; echo "$#:$1:$2:$3" +set -- $s2; echo "$#:$1:$2" +### expect +3::a:b +2:a:b +### end + +### ws_multiple_nonwhitespace_ifs +# Multiple non-whitespace IFS chars produce empty fields +### skip: TODO IFS-based word splitting not implemented +IFS=_- +s1='a__b---c_d' +set -- $s1 +echo "$#" +for arg in "$@"; do echo "[$arg]"; done +### expect +7 +[a] +[] +[b] +[] +[] +[c] +[d] +### end + +### ws_ifs_whitespace_and_nonwhitespace +# IFS with whitespace and non-whitespace +### skip: TODO IFS-based word splitting not implemented +IFS='_ ' +s1='a_b _ _ _ c _d e' +set -- $s1 +echo "$#" +for arg in "$@"; do echo "[$arg]"; done +### expect +7 +[a] +[b] +[] +[] +[c] +[d] +[e] +### end + +### ws_empty_at_star_elided +# empty $@ and $* are elided in argument list +### skip: TODO $@/$* splitting/joining not implemented +fun() { set -- 1 $@ $* 2; echo $#; } +fun +### expect +2 +### end + +### ws_unquoted_empty_elided +# unquoted empty var is elided +### skip: TODO word elision not implemented +empty="" +set -- 1 $empty 2 +echo $# +### expect +2 +### end + +### ws_unquoted_whitespace_elided +# unquoted whitespace var is elided +### skip: TODO word elision not implemented +space=" " +set -- 1 $space 2 +echo $# +### expect +2 +### end + +### ws_empty_literal_not_elided +# empty literal prevents elision +### skip: TODO word elision not implemented +space=" " +set -- 1 $space"" 2 +echo $# +### expect +3 +### end + +### ws_no_split_empty_ifs +# no splitting when IFS is empty +### skip: TODO IFS-based word splitting not implemented +IFS="" +foo="a b" +set -- $foo +echo "$#:$1" +### expect +1:a b +### end + +### ws_default_value_multiple_words +# default value can yield multiple words +### skip: TODO word splitting in default values not implemented +set -- ${undefined:-"2 3" "4 5"} +echo "$#" +echo "$1" +echo "$2" +### expect +2 +2 3 +4 5 +### end + +### ws_default_value_part_joining +# default value with part joining +### skip: TODO word splitting in default values not implemented +set -- 1${undefined:-"2 3" "4 5"}6 +echo "$#" +echo "$1" +echo "$2" +### expect +2 +12 3 +4 56 +### end + +### ws_ifs_empty_no_split +# IFS empty prevents all splitting +### skip: TODO IFS-based word splitting not implemented +IFS='' +x="a b c" +set -- $x +echo "$#:$1" +### expect +1:a b c +### end + +### ws_ifs_unset_default +# IFS unset behaves like space/tab/newline +### skip: TODO IFS-based word splitting not implemented +unset IFS +x="a b c" +set -- $x +echo "$#:$1:$2:$3" +### expect +3:a:b:c +### end + +### ws_ifs_backslash +# IFS=backslash splits on backslash +### skip: TODO IFS-based word splitting not implemented +IFS='\' +s='a\b' +set -- $s +echo "$#:$1:$2" +### expect +2:a:b +### end + +### ws_ifs_glob_metachar_star +# IFS characters that are glob metacharacters +### skip: TODO IFS-based word splitting not implemented +IFS='* ' +s='a*b c' +set -f +set -- $s +echo "$#:$1:$2:$3" +set +f +### expect +3:a:b:c +### end + +### ws_ifs_glob_metachar_question +# IFS with ? glob metacharacter +### skip: TODO IFS-based word splitting not implemented +IFS='?' +s='?x?y?z?' +set -f +set -- $s +echo "$#" +for arg in "$@"; do echo "[$arg]"; done +set +f +### expect +4 +[] +[x] +[y] +[z] +### end + +### ws_empty_ifs_star_join +# Empty IFS and $* join +### skip: TODO $@/$* splitting/joining not implemented +IFS= +echo ["$*"] +set a b c +echo ["$*"] +### expect +[] +[abc] +### end + +### ws_unset_ifs_star_join +# Unset IFS and $* join with space +### skip: TODO $@/$* splitting/joining not implemented +set a b c +unset IFS +echo ["$*"] +### expect +[a b c] +### end + +### ws_ifs_custom_char +# IFS=o doesn't break echo +IFS=o +echo hi +### expect +hi +### end + +### ws_ifs_custom_at_join +# IFS and joining $@ vs $* +### skip: TODO $@/$* splitting/joining not implemented +IFS=: +set -- x 'y z' +for a in "$@"; do echo "[@$a]"; done +for a in "$*"; do echo "[*$a]"; done +### expect +[@x] +[@y z] +[*x:y z] +### end + +### ws_ifs_custom_at_assignment +# IFS and $@ / $* in assignments +### skip: TODO $@/$* splitting/joining not implemented +IFS=: +set -- x 'y z' +s="$@" +echo "at=$s" +s="$*" +echo "star=$s" +### expect +at=x y z +star=x:y z +### end + +### ws_ifs_empty_at_preserved +# IFS='' with $@ preserves args +### skip: TODO $@/$* splitting/joining not implemented +set -- a 'b c' +IFS='' +set -- $@ +echo "$#" +echo "$1" +echo "$2" +### expect +2 +a +b c +### end + +### ws_ifs_empty_array_preserved +# IFS='' with ${a[@]} preserves elements +### skip: TODO $@/$* splitting/joining not implemented +myarray=(a 'b c') +IFS='' +set -- ${myarray[@]} +echo "$#" +echo "$1" +echo "$2" +### expect +2 +a +b c +### end + +### ws_unicode_in_ifs +# Unicode in IFS +### skip: TODO IFS-based word splitting not implemented +x=çx IFS=ç +set -- $x +echo "$#" +printf "<%s>\n" "$@" +### expect +2 +<> + +### end + +### ws_default_value_ifs_unquoted +# Default value with unquoted IFS char +### skip: TODO word splitting in default values not implemented +IFS=_ +set -- ${v:-AxBxC} +echo "$#:$1" +IFS=_ +set -- ${v:-A_B_C} +echo "$#" +for a in "$@"; do echo "[$a]"; done +### expect +1:AxBxC +3 +[A] +[B] +[C] +### end + +### ws_empty_string_both_sides +# ""$A"" - empty string on both sides +### skip: TODO set -- with word splitting not implemented +A=" abc def " +set -- ""$A"" +echo "$#" +echo "[$1]" +echo "[$2]" +echo "[$3]" +echo "[$4]" +### expect +4 +[] +[abc] +[def] +[] +### end