#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use crate::emitter::posix::PosixEmitter;
use crate::ir::shell_ir::{ArithmeticOp, CaseArm, CasePattern, ComparisonOp};
use crate::ir::{Command, EffectSet, ShellIR, ShellValue};
use crate::models::Config;
fn e() -> PosixEmitter {
PosixEmitter::new()
}
fn s(v: &str) -> ShellValue {
ShellValue::String(v.to_string())
}
fn var(v: &str) -> ShellValue {
ShellValue::Variable(v.to_string())
}
fn cmp(op: ComparisonOp, l: ShellValue, r: ShellValue) -> ShellValue {
ShellValue::Comparison {
op,
left: Box::new(l),
right: Box::new(r),
}
}
fn echo(v: ShellValue) -> ShellIR {
ShellIR::Echo { value: v }
}
fn if_ir(test: ShellValue, then: ShellIR, el: Option<ShellIR>) -> ShellIR {
ShellIR::If {
test,
then_branch: Box::new(then),
else_branch: el.map(Box::new),
}
}
#[test]
fn test_ECOV_001_logical_operand_nested_or() {
let ir = echo(ShellValue::LogicalAnd {
left: Box::new(ShellValue::LogicalOr {
left: Box::new(var("a")),
right: Box::new(var("b")),
}),
right: Box::new(var("c")),
});
let r = e().emit(&ir).unwrap();
assert!(
r.contains("||") && r.contains("&&"),
"Nested OR in AND: {r}"
);
}
#[test]
fn test_ECOV_002_logical_operand_nested_not() {
let ir = echo(ShellValue::LogicalAnd {
left: Box::new(ShellValue::LogicalNot {
operand: Box::new(var("flag")),
}),
right: Box::new(var("ok")),
});
let r = e().emit(&ir).unwrap();
assert!(r.contains("!flag"), "Negated in logical operand: {r}");
}
#[test]
fn test_ECOV_003_logical_operand_with_arithmetic() {
let ir = echo(ShellValue::LogicalAnd {
left: Box::new(ShellValue::Arithmetic {
op: ArithmeticOp::Add,
left: Box::new(var("x")),
right: Box::new(s("1")),
}),
right: Box::new(ShellValue::Bool(true)),
});
let r = e().emit(&ir).unwrap();
assert!(r.contains("x + 1"), "Arithmetic in logical: {r}");
}
#[test]
fn test_ECOV_004_logical_operand_with_comparison() {
let ir = echo(ShellValue::LogicalOr {
left: Box::new(cmp(ComparisonOp::Gt, var("x"), s("0"))),
right: Box::new(ShellValue::Bool(false)),
});
let r = e().emit(&ir).unwrap();
assert!(r.contains("-gt"), "Comparison in logical: {r}");
}
#[test]
fn test_ECOV_005_logical_operand_bool_values() {
let ir = echo(ShellValue::LogicalAnd {
left: Box::new(ShellValue::Bool(false)),
right: Box::new(var("v")),
});
let r = e().emit(&ir).unwrap();
assert!(r.contains("0"), "Bool(false) emits 0: {r}");
}
#[test]
fn test_ECOV_033_logical_operand_string_fallback() {
let ir = echo(ShellValue::LogicalAnd {
left: Box::new(s("1")),
right: Box::new(s("0")),
});
let r = e().emit(&ir).unwrap();
assert!(r.contains("&&"), "Logical AND with strings: {r}");
}
#[test]
fn test_ECOV_006_concat_with_arithmetic() {
let ir = ShellIR::Let {
name: "msg".into(),
value: ShellValue::Concat(vec![
s("count="),
ShellValue::Arithmetic {
op: ArithmeticOp::Add,
left: Box::new(var("n")),
right: Box::new(s("1")),
},
]),
effects: EffectSet::pure(),
};
let r = e().emit(&ir).unwrap();
assert!(
r.contains("count=") && r.contains("$(("),
"Arith in concat: {r}"
);
}
#[test]
fn test_ECOV_007_concat_with_command_subst() {
let ir = ShellIR::Let {
name: "info".into(),
value: ShellValue::Concat(vec![
s("user="),
ShellValue::CommandSubst(Command {
program: "whoami".into(),
args: vec![],
}),
]),
effects: EffectSet::pure(),
};
let r = e().emit(&ir).unwrap();
assert!(r.contains("$(whoami)"), "Command subst in concat: {r}");
}
#[test]
fn test_ECOV_029_concat_logical_or_returns_error() {
let ir = ShellIR::Let {
name: "v".into(),
value: ShellValue::Concat(vec![ShellValue::LogicalOr {
left: Box::new(ShellValue::Bool(true)),
right: Box::new(ShellValue::Bool(false)),
}]),
effects: EffectSet::pure(),
};
assert!(e().emit(&ir).is_err(), "LogicalOr in concat is error");
}
#[test]
fn test_ECOV_030_concat_logical_not_returns_error() {
let ir = ShellIR::Let {
name: "v".into(),
value: ShellValue::Concat(vec![ShellValue::LogicalNot {
operand: Box::new(ShellValue::Bool(true)),
}]),
effects: EffectSet::pure(),
};
assert!(e().emit(&ir).is_err(), "LogicalNot in concat is error");
}
#[test]
fn test_ECOV_008_while_condition_nested_and_in_or() {
let ir = ShellIR::While {
condition: ShellValue::LogicalOr {
left: Box::new(ShellValue::LogicalAnd {
left: Box::new(cmp(ComparisonOp::Lt, var("i"), s("10"))),
right: Box::new(cmp(ComparisonOp::Gt, var("j"), s("0"))),
}),
right: Box::new(ShellValue::Bool(true)),
},
body: Box::new(ShellIR::Break),
};
let r = e().emit(&ir).unwrap();
assert!(
r.contains("&&") && r.contains("||"),
"Nested AND in OR: {r}"
);
}
#[test]
fn test_ECOV_009_while_condition_not_wrapping_and() {
let ir = ShellIR::While {
condition: ShellValue::LogicalNot {
operand: Box::new(ShellValue::LogicalAnd {
left: Box::new(cmp(ComparisonOp::NumEq, var("x"), s("1"))),
right: Box::new(cmp(ComparisonOp::NumEq, var("y"), s("1"))),
}),
},
body: Box::new(ShellIR::Break),
};
let r = e().emit(&ir).unwrap();
assert!(
r.contains("! ") && r.contains("&&"),
"NOT wrapping AND: {r}"
);
}
#[test]
fn test_ECOV_028_while_condition_string_value() {
let ir = ShellIR::While {
condition: s("1"),
body: Box::new(ShellIR::Break),
};
let r = e().emit(&ir).unwrap();
assert!(r.contains("while [ 1 ]"), "String as condition: {r}");
}
#[test]
fn test_ECOV_032_while_condition_bool_false_nested() {
let ir = ShellIR::While {
condition: ShellValue::LogicalOr {
left: Box::new(ShellValue::Bool(false)),
right: Box::new(cmp(ComparisonOp::Lt, var("i"), s("5"))),
},
body: Box::new(ShellIR::Break),
};
let r = e().emit(&ir).unwrap();
assert!(
r.contains("false") && r.contains("||"),
"Bool false in nested: {r}"
);
}
#[test]
fn test_ECOV_038_while_comparison_condition() {
let ir = ShellIR::While {
condition: cmp(ComparisonOp::Lt, var("i"), s("10")),
body: Box::new(ShellIR::Noop),
};
let r = e().emit(&ir).unwrap();
assert!(
r.contains("while [ ") && r.contains("-lt"),
"Comparison in while: {r}"
);
}
#[test]
fn test_ECOV_010_triple_elif_chain() {
let ir = if_ir(
cmp(ComparisonOp::NumEq, var("x"), s("1")),
echo(s("one")),
Some(if_ir(
cmp(ComparisonOp::NumEq, var("x"), s("2")),
echo(s("two")),
Some(if_ir(
cmp(ComparisonOp::NumEq, var("x"), s("3")),
echo(s("three")),
Some(echo(s("other"))),
)),
)),
);
let r = e().emit(&ir).unwrap();
assert_eq!(r.matches("elif").count(), 2, "Two elif: {r}");
assert!(r.contains("else"), "Final else: {r}");
}
#[test]
fn test_ECOV_011_elif_via_sequence_wrapped_if() {
let ir = if_ir(
ShellValue::Bool(true),
echo(s("first")),
Some(ShellIR::Sequence(vec![if_ir(
ShellValue::Bool(false),
echo(s("second")),
None,
)])),
);
let r = e().emit(&ir).unwrap();
assert!(r.contains("elif"), "Sequence-wrapped If becomes elif: {r}");
}
#[test]
fn test_ECOV_012_function_user_defined_with_body() {
let ir = ShellIR::Sequence(vec![ShellIR::Function {
name: "greet".into(),
params: vec!["name".into()],
body: Box::new(echo(var("name"))),
}]);
let r = e().emit(&ir).unwrap();
assert!(
r.contains("greet()") && r.contains("name=\"$1\"") && r.contains("echo"),
"{r}"
);
}
#[test]
fn test_ECOV_013_function_unknown_command_empty_body() {
let ir = ShellIR::Function {
name: "my_custom_cmd".into(),
params: vec![],
body: Box::new(ShellIR::Noop),
};
let r = e().emit(&ir).unwrap();
assert!(r.contains("my_custom_cmd()"), "Custom func emitted: {r}");
}
#[test]
fn test_ECOV_014_test_logical_or_runtime() {
let r = e()
.emit(&if_ir(
ShellValue::LogicalOr {
left: Box::new(var("a")),
right: Box::new(var("b")),
},
ShellIR::Noop,
None,
))
.unwrap();
assert!(r.contains("||"), "Runtime LogicalOr in test: {r}");
}
#[test]
fn test_ECOV_015_test_variable() {
let r = e().emit(&if_ir(var("flag"), ShellIR::Noop, None)).unwrap();
assert!(
r.contains("test -n") && r.contains("$flag"),
"Variable test: {r}"
);
}
#[test]
include!("emitter_coverage_tests_tests_ECOV.rs");