use kaish_repl::{ProcessResult, Repl};
fn run_script(script: &str) -> Vec<String> {
let mut repl = Repl::new().expect("Failed to create REPL");
let mut outputs = Vec::new();
let mut current_block = String::new();
let mut block_depth: usize = 0;
for line in script.lines() {
let trimmed = line.trim();
if block_depth == 0 && (trimmed.is_empty() || trimmed.starts_with('#')) {
continue;
}
for word in trimmed.split_whitespace() {
match word {
"if" | "for" | "while" | "case" => block_depth += 1,
"fi" | "done" | "esac" => block_depth = block_depth.saturating_sub(1),
_ => {}
}
let word_clean = word.trim_end_matches(';');
match word_clean {
"fi" | "done" | "esac" if word != word_clean => {
}
_ => {}
}
}
if !current_block.is_empty() {
current_block.push('\n');
}
current_block.push_str(trimmed);
if block_depth == 0 && !current_block.is_empty() {
match repl.process_line(¤t_block) {
ProcessResult::Output(output) => outputs.push(output),
ProcessResult::Empty => {}
ProcessResult::Exit => break,
}
current_block.clear();
}
}
if !current_block.is_empty() {
match repl.process_line(¤t_block) {
ProcessResult::Output(output) => outputs.push(output),
ProcessResult::Empty => {}
ProcessResult::Exit => {}
}
}
outputs
}
fn outputs_contain(outputs: &[String], expected: &[&str]) -> bool {
let joined = outputs.join("\n");
expected.iter().all(|e| joined.contains(e))
}
#[test]
fn scope_basic_variable() {
let outputs = run_script(r#"
X=42
echo ${X}
"#);
assert!(outputs_contain(&outputs, &["42"]));
}
#[test]
fn scope_variable_shadowing_in_loop() {
let outputs = run_script(r#"
X="outer"
for I in "inner"; do X=${I}; echo ${X}; done
echo ${X}
"#);
let joined = outputs.join("\n");
assert!(joined.contains("inner"), "Should print inner inside loop. Output was: {}", joined);
}
#[test]
fn scope_json_as_string() {
let outputs = run_script(r#"
DATA='{"user": {"name": "Alice"}}'
echo ${DATA}
"#);
let joined = outputs.join("\n");
assert!(joined.contains("Alice"), "JSON string should contain Alice. Output was: {}", joined);
}
#[test]
fn scope_last_result_propagation() {
let outputs = run_script(r#"
echo "first"
echo "code was ${?.code}"
"#);
assert!(outputs_contain(&outputs, &["first", "code was 0"]));
}
#[test]
fn scope_last_result_fields() {
let outputs = run_script(r#"
echo "test output"
echo "ok=${?.ok}"
"#);
assert!(outputs_contain(&outputs, &["ok=true"]));
}
#[test]
fn interpolation_basic() {
let outputs = run_script(r#"
NAME="World"
echo "Hello ${NAME}"
"#);
assert!(outputs_contain(&outputs, &["Hello World"]));
}
#[test]
fn interpolation_empty_string() {
let outputs = run_script(r#"
EMPTY=""
echo "before${EMPTY}after"
"#);
assert!(outputs_contain(&outputs, &["beforeafter"]));
}
#[test]
fn interpolation_multiple_vars() {
let outputs = run_script(r#"
A="one"
B="two"
echo "${A} ${B}"
"#);
assert!(outputs_contain(&outputs, &["one two"]));
}
#[test]
fn interpolation_adjacent_no_space() {
let outputs = run_script(r#"
A="one"
B="two"
echo "${A}${B}"
"#);
assert!(outputs_contain(&outputs, &["onetwo"]));
}
#[test]
fn interpolation_json_string() {
let outputs = run_script(r#"
OBJ='{"inner": {"value": "nested"}}'
echo "got: ${OBJ}"
"#);
let joined = outputs.join("\n");
assert!(joined.contains("nested"), "JSON string should contain nested. Output was: {}", joined);
}
#[test]
fn interpolation_word_split() {
let outputs = run_script(r#"
ITEMS="zero one two"
for I in $(split ${ITEMS}); do echo ${I}; done
"#);
assert!(outputs_contain(&outputs, &["zero", "one", "two"]));
}
#[test]
fn interpolation_number() {
let outputs = run_script(r#"
NUM=42
echo "num=${NUM}"
"#);
assert!(outputs_contain(&outputs, &["num=42"]));
}
#[test]
fn interpolation_boolean() {
let outputs = run_script(r#"
FLAG=true
echo "flag=${FLAG}"
"#);
assert!(outputs_contain(&outputs, &["flag=true"]));
}
#[test]
fn interpolation_null() {
let outputs = run_script(r#"
NOTHING=null
echo "val=${NOTHING}"
"#);
assert!(outputs_contain(&outputs, &["val=null"]));
}
#[test]
fn expr_equality() {
let outputs = run_script(r#"
X=5
if [[ ${X} == 5 ]]; then echo "equal"; fi
"#);
assert!(outputs_contain(&outputs, &["equal"]));
}
#[test]
fn expr_inequality() {
let outputs = run_script(r#"
X=5
if [[ ${X} != 3 ]]; then echo "not equal"; fi
"#);
assert!(outputs_contain(&outputs, &["not equal"]));
}
#[test]
fn expr_less_than() {
let outputs = run_script(r#"
if [[ 3 -lt 5 ]]; then echo "less"; fi
"#);
assert!(outputs_contain(&outputs, &["less"]));
}
#[test]
fn expr_greater_than() {
let outputs = run_script(r#"
if [[ 5 -gt 3 ]]; then echo "greater"; fi
"#);
assert!(outputs_contain(&outputs, &["greater"]));
}
#[test]
fn expr_and_short_circuit() {
let outputs = run_script(r#"
if true && true; then echo "both true"; fi
if true && false; then echo "wrong"; else echo "short circuit"; fi
"#);
assert!(outputs_contain(&outputs, &["both true", "short circuit"]));
}
#[test]
fn expr_or_short_circuit() {
let outputs = run_script(r#"
if false || true; then echo "found true"; fi
if true || false; then echo "first true"; fi
"#);
assert!(outputs_contain(&outputs, &["found true", "first true"]));
}
#[test]
fn expr_precedence_and_or() {
let outputs = run_script(r#"
if true || false && false; then echo "precedence ok"; fi
"#);
assert!(outputs_contain(&outputs, &["precedence ok"]));
}
#[test]
fn expr_int_float_comparison() {
let outputs = run_script(r#"
I=5
F=5.0
if [[ ${I} == ${F} ]]; then echo "int equals float"; fi
"#);
assert!(outputs_contain(&outputs, &["int equals float"]));
}
#[test]
fn expr_string_comparison() {
let outputs = run_script(r#"
if [[ "apple" != "banana" ]]; then echo "apple different"; fi
"#);
let joined = outputs.join("\n");
assert!(outputs_contain(&outputs, &["apple different"]), "Output was: {}", joined);
}
#[test]
fn expr_truthiness_zero() {
let outputs = run_script(r#"
if [[ 0 == 0 ]]; then echo "zero is zero"; fi
if [[ 0 != 1 ]]; then echo "zero not one"; fi
"#);
assert!(outputs_contain(&outputs, &["zero is zero", "zero not one"]));
}
#[test]
fn expr_truthiness_empty_string() {
let outputs = run_script(r#"
if [[ -z "" ]]; then echo "empty falsy"; fi
"#);
assert!(outputs_contain(&outputs, &["empty falsy"]));
}
#[test]
fn expr_truthiness_null() {
let outputs = run_script(r#"
ZERO=0
if [[ ${ZERO} == 0 ]]; then echo "zero falsy"; fi
"#);
let joined = outputs.join("\n");
assert!(joined.contains("zero falsy"), "Output was: {}", joined);
}
#[test]
fn expr_truthiness_empty_string_var() {
let outputs = run_script(r#"
EMPTY=""
if [[ -z ${EMPTY} ]]; then echo "empty var falsy"; fi
"#);
assert!(outputs_contain(&outputs, &["empty var falsy"]));
}
#[test]
fn expr_truthiness_non_empty_string() {
let outputs = run_script(r#"
STR="hello"
if [[ -n ${STR} ]]; then echo "string truthy"; fi
"#);
assert!(outputs_contain(&outputs, &["string truthy"]));
}
#[test]
fn control_if_then() {
let outputs = run_script(r#"
if true; then echo "yes"; fi
"#);
assert!(outputs_contain(&outputs, &["yes"]));
}
#[test]
fn control_if_else() {
let outputs = run_script(r#"
if false; then echo "wrong"; else echo "else branch"; fi
"#);
assert!(outputs_contain(&outputs, &["else branch"]));
}
#[test]
fn control_nested_if() {
let outputs = run_script(r#"
X=5
if [[ ${X} -gt 0 ]]; then
if [[ ${X} -lt 10 ]]; then
echo "in range"
fi
fi
"#);
assert!(outputs_contain(&outputs, &["in range"]));
}
#[test]
fn control_for_loop() {
let outputs = run_script(r#"
for I in $(split "1 2 3"); do echo ${I}; done
"#);
let joined = outputs.join("\n");
assert!(joined.contains("1"), "Output was: {}", joined);
assert!(joined.contains("2"), "Output was: {}", joined);
assert!(joined.contains("3"), "Output was: {}", joined);
}
#[test]
fn control_nested_loops() {
let outputs = run_script(r#"
for I in $(split "1 2"); do for J in $(split "a b"); do echo "${I}-${J}"; done; done
"#);
assert!(outputs_contain(&outputs, &["1-a", "1-b", "2-a", "2-b"]));
}
#[test]
fn control_empty_loop() {
let outputs = run_script(r#"
EMPTY=""
for I in $(split ${EMPTY}); do echo "never"; done
echo "after"
"#);
assert!(outputs_contain(&outputs, &["after"]));
assert!(!outputs_contain(&outputs, &["never"]));
}
#[test]
fn control_loop_with_conditional() {
let outputs = run_script(r#"
for I in $(split "1 2 3"); do if [[ ${I} == "2" ]]; then echo "found two"; fi; done
"#);
assert!(outputs_contain(&outputs, &["found two"]));
}
#[test]
fn cmd_subst_basic() {
let outputs = run_script(r#"
R=$(echo "hello")
echo ${R}
"#);
let joined = outputs.join("\n");
assert!(joined.contains("hello"), "Should have captured output. Output was: {}", joined);
}
#[test]
fn cmd_subst_last_result() {
let outputs = run_script(r#"
echo "hello"
echo "code was ${?}"
"#);
assert!(outputs_contain(&outputs, &["code was 0"]));
}
#[test]
fn error_undefined_variable() {
let outputs = run_script(r#"
echo ${UNDEFINED}
"#);
let joined = outputs.join("\n");
assert!(joined.contains("ERROR") || joined.contains("undefined"));
}
#[test]
fn error_invalid_path() {
let outputs = run_script(r#"
X=42
echo ${X.field}
"#);
let joined = outputs.join("\n");
assert!(joined.contains("ERROR") || joined.contains("undefined"));
}
#[test]
fn error_invalid_field_access() {
let outputs = run_script(r#"
NUM=42
echo ${NUM.field}
"#);
let joined = outputs.join("\n");
assert!(joined.contains("ERROR") || joined.contains("undefined"));
}
#[test]
fn unicode_basic() {
let outputs = run_script(r#"
echo "Hello, 世界!"
"#);
assert!(outputs_contain(&outputs, &["Hello, 世界!"]));
}
#[test]
fn unicode_emoji() {
let outputs = run_script(r#"
echo "🎉🚀✨"
"#);
assert!(outputs_contain(&outputs, &["🎉🚀✨"]));
}
#[test]
fn unicode_in_variable() {
let outputs = run_script(r#"
GREETING="こんにちは"
echo ${GREETING}
"#);
assert!(outputs_contain(&outputs, &["こんにちは"]));
}
#[test]
fn stress_many_variables() {
let outputs = run_script(r#"
V1=1
V2=2
V3=3
V4=4
V5=5
echo "${V1}${V2}${V3}${V4}${V5}"
"#);
assert!(outputs_contain(&outputs, &["12345"]));
}
#[test]
fn stress_json_string() {
let outputs = run_script(r#"
D='{"a": {"b": {"c": {"d": "deep"}}}}'
echo ${D}
"#);
let joined = outputs.join("\n");
assert!(joined.contains("deep"), "JSON string should contain deep. Output was: {}", joined);
}
#[test]
fn stress_many_items() {
let outputs = run_script(r#"
ITEMS="1 2 3 4 5 6 7 8 9 10"
for I in $(split ${ITEMS}); do echo ${I}; done
"#);
assert!(outputs_contain(&outputs, &["1", "5", "10"]));
}
#[test]
fn stress_complex_condition() {
let outputs = run_script(r#"
if true && true && true && true && true; then
if false || false || false || true; then
echo "complex passed"
fi
fi
"#);
assert!(outputs_contain(&outputs, &["complex passed"]));
}
#[test]
fn introspect_vars_shows_set_variables() {
let outputs = run_script(r#"
X=42
NAME="Alice"
vars
"#);
let joined = outputs.join("\n");
assert!(joined.contains("X"), "vars should show X. Output was: {}", joined);
assert!(joined.contains("42"), "vars should show 42. Output was: {}", joined);
assert!(joined.contains("NAME"), "vars should show NAME. Output was: {}", joined);
}
#[test]
fn introspect_vars_json_format() {
let outputs = run_script(r#"
COUNT=100
vars --json
"#);
let joined = outputs.join("\n");
assert!(joined.contains("\"NAME\""), "vars --json should have NAME field. Output was: {}", joined);
assert!(joined.contains("\"VALUE\""), "vars --json should have VALUE field. Output was: {}", joined);
assert!(joined.contains("COUNT"), "vars --json should include COUNT. Output was: {}", joined);
}
#[test]
fn introspect_tools_lists_builtins() {
let outputs = run_script(r#"
tools
"#);
let joined = outputs.join("\n");
assert!(joined.contains("echo"), "tools should list echo. Output was: {}", joined);
assert!(joined.contains("ls"), "tools should list ls. Output was: {}", joined);
assert!(joined.contains("cat"), "tools should list cat. Output was: {}", joined);
assert!(joined.contains("vars"), "tools should list vars. Output was: {}", joined);
}
#[test]
fn introspect_tools_json_format() {
let outputs = run_script(r#"
tools --json
"#);
let joined = outputs.join("\n");
assert!(joined.contains("\"NAME\""), "tools --json should have NAME field. Output was: {}", joined);
assert!(joined.contains("\"DESCRIPTION\""), "tools --json should have DESCRIPTION field. Output was: {}", joined);
assert!(joined.contains('[') && joined.contains(']'), "tools --json should return array. Output was: {}", joined);
}
#[test]
fn introspect_tools_detail() {
let outputs = run_script(r#"
tools echo
"#);
let joined = outputs.join("\n");
assert!(joined.contains("echo"), "tools echo should show echo info. Output was: {}", joined);
}
#[test]
fn introspect_mounts_shows_vfs() {
let outputs = run_script(r#"
mounts
"#);
let joined = outputs.join("\n");
assert!(joined.contains("/"), "mounts should show root. Output was: {}", joined);
assert!(joined.contains("rw") || joined.contains("ro"), "mounts should show mode. Output was: {}", joined);
}
#[test]
fn introspect_mounts_json_format() {
let outputs = run_script(r#"
mounts --json
"#);
let joined = outputs.join("\n");
assert!(joined.contains("\"PATH\""), "mounts --json should have PATH. Output was: {}", joined);
assert!(joined.contains("\"MODE\""), "mounts --json should have MODE. Output was: {}", joined);
}
#[test]
fn case_simple_match() {
let outputs = run_script(r#"
EXT="rs"
case ${EXT} in
rs) echo "Rust" ;;
py) echo "Python" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["Rust"]));
}
#[test]
fn case_wildcard_match() {
let outputs = run_script(r#"
FILE="main.rs"
case ${FILE} in
*.py) echo "Python" ;;
*.rs) echo "Rust" ;;
*) echo "Unknown" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["Rust"]));
}
#[test]
fn case_default_fallthrough() {
let outputs = run_script(r#"
FILE="data.json"
case ${FILE} in
*.py) echo "Python" ;;
*.rs) echo "Rust" ;;
*) echo "Other" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["Other"]));
}
#[test]
fn case_no_match() {
let outputs = run_script(r#"
X="nomatch"
case ${X} in
foo) echo "foo" ;;
bar) echo "bar" ;;
esac
echo "done"
"#);
assert!(outputs_contain(&outputs, &["done"]));
let joined = outputs.join("\n");
assert!(!joined.contains("foo") && !joined.contains("bar"));
}
#[test]
fn case_multiple_patterns() {
let outputs = run_script(r#"
EXT="ts"
case ${EXT} in
js|ts|jsx|tsx) echo "JavaScript family" ;;
py|pyc|pyw) echo "Python family" ;;
*) echo "other" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["JavaScript family"]));
}
#[test]
fn case_char_class() {
let outputs = run_script(r#"
CH="B"
case ${CH} in
[a-z]) echo "lower" ;;
[A-Z]) echo "upper" ;;
[0-9]) echo "digit" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["upper"]));
}
#[test]
fn case_question_mark() {
let outputs = run_script(r#"
CODE="A1"
case ${CODE} in
??) echo "two chars" ;;
???) echo "three chars" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["two chars"]));
}
#[test]
fn case_in_loop() {
let outputs = run_script(r#"
for F in "a.rs" "b.py" "c.go"; do
case ${F} in
*.rs) echo "rust" ;;
*.py) echo "python" ;;
*) echo "other" ;;
esac
done
"#);
let joined = outputs.join("\n");
assert!(joined.contains("rust"), "Should find rust");
assert!(joined.contains("python"), "Should find python");
assert!(joined.contains("other"), "Should find other");
}
#[test]
fn case_nested_control_flow() {
let outputs = run_script(r#"
X="test"
case ${X} in
test)
if true; then
echo "nested-if"
fi
;;
esac
"#);
assert!(outputs_contain(&outputs, &["nested-if"]));
}
#[test]
fn case_with_brace_expansion() {
let outputs = run_script(r#"
FILE="code.ts"
case ${FILE} in
*.{js,ts}) echo "JavaScript family" ;;
*.{c,cpp,h}) echo "C family" ;;
*) echo "Unknown" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["JavaScript family"]));
}
#[test]
fn arithmetic_simple() {
let outputs = run_script(r#"
X=$((1 + 2))
echo ${X}
"#);
assert!(outputs_contain(&outputs, &["3"]));
}
#[test]
fn arithmetic_with_variables() {
let outputs = run_script(r#"
A=10
B=20
C=$((A + B))
echo ${C}
"#);
assert!(outputs_contain(&outputs, &["30"]));
}
#[test]
fn arithmetic_complex() {
let outputs = run_script(r#"
X=$((2 * (3 + 4) - 5))
echo ${X}
"#);
assert!(outputs_contain(&outputs, &["9"]));
}
#[test]
fn arithmetic_negative() {
let outputs = run_script(r#"
X=$((-5 + 3))
echo ${X}
"#);
assert!(outputs_contain(&outputs, &["-2"]));
}
#[test]
fn arithmetic_modulo() {
let outputs = run_script(r#"
X=$((17 % 5))
echo ${X}
"#);
assert!(outputs_contain(&outputs, &["2"]));
}
#[test]
fn arithmetic_in_condition() {
let outputs = run_script(r#"
A=5
B=3
if [[ $((A + B)) -gt 5 ]]; then
echo "sum is big"
fi
"#);
assert!(outputs_contain(&outputs, &["sum is big"]));
}
#[test]
fn arithmetic_in_loop() {
let outputs = run_script(r#"
SUM=0
for N in 1 2 3 4 5; do
SUM=$((SUM + N))
done
echo ${SUM}
"#);
assert!(outputs_contain(&outputs, &["15"]));
}
#[test]
fn arithmetic_division() {
let outputs = run_script(r#"
X=$((100 / 4))
echo ${X}
"#);
assert!(outputs_contain(&outputs, &["25"]));
}
#[test]
fn arithmetic_precedence() {
let outputs = run_script(r#"
X=$((2 + 3 * 4))
echo ${X}
"#);
assert!(outputs_contain(&outputs, &["14"]));
}
#[test]
fn arithmetic_dollar_var() {
let outputs = run_script(r#"
N=7
X=$(($N * 2))
echo ${X}
"#);
assert!(outputs_contain(&outputs, &["14"]));
}
#[test]
fn cross_case_with_arithmetic() {
let outputs = run_script(r#"
N=$((2 + 3))
case ${N} in
5) echo "five" ;;
*) echo "other" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["five"]));
}
#[test]
fn cross_loop_with_arithmetic_and_case() {
let outputs = run_script(r#"
for I in 1 2 3; do
DOUBLE=$((I * 2))
case ${DOUBLE} in
2) echo "one doubled" ;;
4) echo "two doubled" ;;
6) echo "three doubled" ;;
esac
done
"#);
let joined = outputs.join("\n");
assert!(joined.contains("one doubled"));
assert!(joined.contains("two doubled"));
assert!(joined.contains("three doubled"));
}
#[test]
fn cross_condition_with_arithmetic() {
let outputs = run_script(r#"
A=10
B=5
if [[ $((A - B)) -gt 3 ]]; then
echo "difference is significant"
else
echo "difference is small"
fi
"#);
assert!(outputs_contain(&outputs, &["difference is significant"]));
}
#[test]
fn cross_nested_case_statements() {
let outputs = run_script(r#"
CATEGORY="animal"
TYPE="dog"
case ${CATEGORY} in
animal)
case ${TYPE} in
dog) echo "woof" ;;
cat) echo "meow" ;;
esac
;;
plant) echo "photosynthesis" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["woof"]));
}
#[test]
fn cross_conditional_arithmetic() {
let outputs = run_script(r#"
X=10
if [[ $X -gt 5 ]]; then
Y=$((X * 2))
else
Y=$((X / 2))
fi
echo ${Y}
"#);
assert!(outputs_contain(&outputs, &["20"]));
}
#[test]
fn cross_case_in_if() {
let outputs = run_script(r#"
FLAG=true
if [[ ${FLAG} == true ]]; then
case "test.rs" in
*.rs) echo "rust in if" ;;
esac
fi
"#);
assert!(outputs_contain(&outputs, &["rust in if"]));
}
#[test]
fn var_with_default_unset() {
let outputs = run_script(r#"
echo "${UNDEFINED:-fallback}"
"#);
assert!(outputs_contain(&outputs, &["fallback"]));
}
#[test]
fn var_with_default_set() {
let outputs = run_script(r#"
DEFINED="value"
echo "${DEFINED:-fallback}"
"#);
assert!(outputs_contain(&outputs, &["value"]));
}
#[test]
fn var_with_default_empty() {
let outputs = run_script(r#"
EMPTY=""
echo "${EMPTY:-fallback}"
"#);
assert!(outputs_contain(&outputs, &["fallback"]));
}
#[test]
fn var_length() {
let outputs = run_script(r#"
MSG="hello"
echo "${#MSG}"
"#);
assert!(outputs_contain(&outputs, &["5"]));
}
#[test]
fn var_length_empty() {
let outputs = run_script(r#"
EMPTY=""
echo "${#EMPTY}"
"#);
assert!(outputs_contain(&outputs, &["0"]));
}
#[test]
fn while_loop_with_counter() {
let outputs = run_script(r#"
I=0
while [[ $I -lt 3 ]]; do
echo "iteration ${I}"
I=$((I + 1))
done
"#);
let joined = outputs.join("\n");
assert!(joined.contains("iteration 0"));
assert!(joined.contains("iteration 1"));
assert!(joined.contains("iteration 2"));
}
#[test]
fn break_in_loop() {
let outputs = run_script(r#"
for I in "1" "2" "3" "4" "5"; do
if [[ $I > "2" ]]; then
break
fi
echo ${I}
done
echo "done"
"#);
let joined = outputs.join("\n");
assert!(joined.contains("1"), "Should contain 1: {}", joined);
assert!(joined.contains("2"), "Should contain 2: {}", joined);
assert!(!joined.contains("3"), "Should not contain 3: {}", joined);
assert!(joined.contains("done"), "Should contain done: {}", joined);
}
#[test]
fn continue_in_loop() {
let outputs = run_script(r#"
for I in "1" "2" "3" "4" "5"; do
if [[ $I == "3" ]]; then
continue
fi
echo ${I}
done
"#);
let joined = outputs.join("\n");
assert!(joined.contains("1"), "Should contain 1: {}", joined);
assert!(joined.contains("2"), "Should contain 2: {}", joined);
assert!(!joined.contains("\n3\n") && !joined.contains("3\n") && !joined.contains("\n3"), "Should not contain 3 as separate output: {}", joined);
assert!(joined.contains("4"), "Should contain 4: {}", joined);
assert!(joined.contains("5"), "Should contain 5: {}", joined);
}
#[test]
fn elif_chain() {
let outputs = run_script(r#"
X=2
if [[ $X == 1 ]]; then
echo "one"
elif [[ $X == 2 ]]; then
echo "two"
elif [[ $X == 3 ]]; then
echo "three"
else
echo "other"
fi
"#);
assert!(outputs_contain(&outputs, &["two"]));
}
#[test]
fn glob_asterisk() {
let outputs = run_script(r#"
case "hello.txt" in
*.txt) echo "text file" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["text file"]));
}
#[test]
fn glob_question() {
let outputs = run_script(r#"
case "ab" in
??) echo "two chars" ;;
???) echo "three chars" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["two chars"]));
}
#[test]
fn glob_char_class_range() {
let outputs = run_script(r#"
case "5" in
[0-9]) echo "digit" ;;
[a-z]) echo "letter" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["digit"]));
}
#[test]
fn glob_char_class_negated() {
let outputs = run_script(r#"
case "A" in
[!a-z]) echo "not lowercase" ;;
[a-z]) echo "lowercase" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["not lowercase"]));
}
#[test]
fn glob_brace_expansion() {
let outputs = run_script(r#"
case "file.go" in
*.{rs,go,py}) echo "supported lang" ;;
*) echo "unsupported" ;;
esac
"#);
assert!(outputs_contain(&outputs, &["supported lang"]));
}