#![allow(clippy::unwrap_used, clippy::expect_used)]
use kaish_kernel::{Kernel, KernelConfig};
use kaish_types::ExecResult;
use rstest::rstest;
async fn run(script: &str) -> ExecResult {
let k = Kernel::new(KernelConfig::isolated()).expect("kernel");
k.execute(script).await.expect("kernel execute")
}
#[rstest]
#[case("false | true; echo \"${PIPESTATUS[0]},${PIPESTATUS[1]}\"", "1,0")]
#[case("true | false; echo \"${PIPESTATUS[0]},${PIPESTATUS[1]}\"", "0,1")]
#[case("test -f | false; echo \"${PIPESTATUS[0]},${PIPESTATUS[1]}\"", "2,1")]
#[case("false | test -f; echo \"${PIPESTATUS[0]},${PIPESTATUS[1]}\"", "1,2")]
#[case("true | true | true; echo \"${PIPESTATUS[0]},${PIPESTATUS[1]},${PIPESTATUS[2]}\"", "0,0,0")]
#[tokio::test]
async fn pipestatus_reports_every_stage(#[case] script: &str, #[case] expected: &str) {
assert_eq!(run(script).await.text_out().trim_end(), expected, "`{script}`");
}
#[rstest]
#[case("false; echo \"${PIPESTATUS[0]},${#PIPESTATUS}\"", "1,1")]
#[case("true; echo \"${PIPESTATUS[0]},${#PIPESTATUS}\"", "0,1")]
#[case("false | true; echo ${#PIPESTATUS}", "2")]
#[tokio::test]
async fn pipestatus_length_matches_the_stage_count(
#[case] script: &str,
#[case] expected: &str,
) {
assert_eq!(run(script).await.text_out().trim_end(), expected, "`{script}`");
}
#[rstest]
#[case("false | true", 0)]
#[case("true | false", 1)]
#[case("cat /nonexistent | wc -l", 0)]
#[tokio::test]
async fn the_default_is_still_the_last_stages_status(
#[case] script: &str,
#[case] expected: i64,
) {
assert_eq!(run(script).await.code, expected, "`{script}`");
}
#[rstest]
#[case("set -o pipefail; false | true", 1)]
#[case("set -o pipefail; true | true", 0)]
#[case("set -o pipefail; false | false", 1)]
#[case("set -o pipefail; test -f | false", 1)]
#[case("set -o pipefail; false | test -f", 2)]
#[case("set -o pipefail; test -f | false | true", 1)]
#[case("set -o pipefail; test -f | true | true", 2)]
#[case("set -o pipefail; cat /nonexistent | wc -l", 1)]
#[tokio::test]
async fn pipefail_takes_the_rightmost_nonzero(#[case] script: &str, #[case] expected: i64) {
assert_eq!(run(script).await.code, expected, "`{script}`");
}
#[tokio::test]
async fn pipefail_turns_off_again() {
assert_eq!(run("set -o pipefail; set +o pipefail; false | true").await.code, 0);
assert_eq!(run("false | true").await.code, 0, "a fresh kernel is not in pipefail");
}
#[tokio::test]
async fn set_dash_o_reports_pipefail() {
let off_result = run("set -o").await;
let off = off_result.text_out();
assert!(off.contains("pipefail"), "`set -o` must list pipefail, got: {off}");
let on_result = run("set -o pipefail; set -o").await;
let on = on_result.text_out();
let row = on
.lines()
.find(|l| l.contains("pipefail"))
.unwrap_or_else(|| panic!("no pipefail row in: {on}"));
assert!(row.contains("on"), "pipefail should read on, got: {row}");
}
#[tokio::test]
async fn pipefail_is_no_longer_an_unknown_option() {
let r = run("set -o pipefail; echo ALIVE").await;
assert_eq!(r.code, 0, "err={:?}", r.err);
assert!(!r.err.contains("not implemented"), "err={:?}", r.err);
assert_eq!(r.text_out().trim_end(), "ALIVE");
let r = run("set -o nosuchoption").await;
assert_ne!(r.code, 0, "an unknown option must still be refused");
assert!(r.err.contains("nosuchoption"), "the error must name it: {:?}", r.err);
}
#[tokio::test]
async fn pipestatus_is_a_real_list() {
let r = run("false | true; for c in $(values $PIPESTATUS); do echo \"c=$c\"; done").await;
assert_eq!(r.text_out().trim_end(), "c=1\nc=0", "err={:?}", r.err);
let r = run("false | true; echo ${#PIPESTATUS}").await;
assert_eq!(r.text_out().trim_end(), "2", "err={:?}", r.err);
}
#[tokio::test]
async fn reading_pipestatus_replaces_it() {
let r = run("false | true; echo ${#PIPESTATUS}; echo \"again=$PIPESTATUS\"").await;
assert_eq!(r.text_out().trim_end(), "2\nagain=[0]", "err={:?}", r.err);
}