#![allow(clippy::unwrap_used, clippy::expect_used)]
use kaish_kernel::{Kernel, KernelConfig};
fn kernel() -> Kernel {
Kernel::new(KernelConfig::isolated()).expect("kernel")
}
async fn run(source: &str) -> (i64, String) {
let k = kernel();
let r = k.execute(source).await.expect("kernel execute");
(r.code, r.text_out().trim().to_string())
}
#[tokio::test]
async fn every_door_reaches_the_same_variable() {
for (source, expected) in [
("v=ok; echo $v", "ok"),
("v=ok; echo ${v}", "ok"),
("v=ok; echo \"$v\"", "ok"),
("v=ok; echo \"${v}\"", "ok"),
("café=ok; echo $café", "ok"),
("café=ok; echo ${café}", "ok"),
("café=ok; echo \"$café\"", "ok"),
("café=ok; echo \"${café}\"", "ok"),
("名前=ok; echo $名前", "ok"),
("名前=ok; echo ${名前}", "ok"),
("名前=ok; echo \"$名前\"", "ok"),
("😁=grin; echo $😁", "grin"),
("😁=grin; echo ${😁}", "grin"),
("😁=grin; echo \"$😁\"", "grin"),
("x😁=ok; echo $x😁", "ok"),
("café_1=ok; echo ${café_1}", "ok"),
] {
let (code, out) = run(source).await;
assert_eq!(code, 0, "{source} exited {code}");
assert_eq!(out, expected, "for: {source}");
}
}
#[tokio::test]
async fn quoted_reference_does_not_truncate_the_name() {
let (code, out) = run("caf=TRAP; café=RIGHT; echo \"$café\"").await;
assert_eq!(code, 0);
assert_eq!(out, "RIGHT", "the ASCII head of the name must not win");
let (code, out) = run("caf=TRAP; echo \"$café\"").await;
assert_eq!(code, 0);
assert_eq!(out, "", "an unset name must resolve empty, not to its ASCII head");
}
#[tokio::test]
async fn nfc_and_nfd_spellings_are_one_variable() {
let nfc = "caf\u{e9}"; let nfd = "cafe\u{301}"; assert_ne!(nfc, nfd, "the fixture must actually differ in bytes");
for (bind, read) in [(nfc, nfd), (nfd, nfc), (nfc, nfc), (nfd, nfd)] {
let source = format!("{bind}=bound; echo ${{{read}}}");
let (code, out) = run(&source).await;
assert_eq!(code, 0, "{source} exited {code}");
assert_eq!(out, "bound", "bind {bind:?} read {read:?}");
}
}
#[tokio::test]
async fn normalization_reaches_every_door() {
let nfc = "caf\u{e9}";
let nfd = "cafe\u{301}";
for source in [
format!("{nfc}=bound; echo ${nfd}"),
format!("{nfc}=bound; echo \"${nfd}\""),
format!("{nfd}=bound; echo ${nfc}"),
format!("{nfd}=bound; echo \"${{{nfc}}}\""),
] {
let (code, out) = run(&source).await;
assert_eq!(code, 0, "{source} exited {code}");
assert_eq!(out, "bound", "for: {source}");
}
}
#[tokio::test]
async fn length_of_a_non_ascii_name() {
for (source, expected) in [
("café=abcd; echo ${#café}", "4"),
("😁=abc; echo ${#😁}", "3"),
] {
let (code, out) = run(source).await;
assert_eq!(code, 0, "{source} exited {code}");
assert_eq!(out, expected, "for: {source}");
}
}
#[tokio::test]
async fn flags_remain_ascii_and_say_so() {
for source in ["grep --café x", "grep -café x", "set +café"] {
let k = kernel();
let err = k.execute(source).await;
let failed = match err {
Err(_) => true,
Ok(r) => r.code != 0,
};
assert!(failed, "{source} should be an error");
}
}
#[tokio::test]
async fn single_quotes_suppress_a_non_ascii_reference() {
for (source, expected) in [
("café=RIGHT; echo '$café'", "$café"),
("😁=grin; echo '$😁'", "$😁"),
] {
let (code, out) = run(source).await;
assert_eq!(code, 0, "{source} exited {code}");
assert_eq!(out, expected, "for: {source}");
}
}
#[tokio::test]
async fn non_ascii_data_still_passes_through() {
for (source, expected) in [
("echo café", "café"),
("echo 日本語", "日本語"),
("echo 😁", "😁"),
("echo hi😁there", "hi😁there"),
("x=😁; echo $x", "😁"),
("echo \"a😁b\"", "a😁b"),
] {
let (code, out) = run(source).await;
assert_eq!(code, 0, "{source} exited {code}");
assert_eq!(out, expected, "for: {source}");
}
}
#[tokio::test]
async fn runtime_binders_normalize_too() {
let nfc = "caf\u{e9}";
let nfd = "cafe\u{301}";
let (code, out) = run(&format!("for {nfd} in bound; do echo \"${{{nfc}}}\"; done")).await;
assert_eq!(code, 0);
assert_eq!(out, "bound", "the for-loop variable must normalize");
let (code, out) = run(&format!("printf bound | read {nfd}; echo \"${{{nfc}}}\"")).await;
assert_eq!(code, 0);
assert_eq!(out, "bound", "read must normalize its target");
let (code, out) = run(&format!("{nfc}=bound; unset {nfd}; echo \"[${{{nfc}}}]\"")).await;
assert_eq!(code, 0);
assert_eq!(out, "[]", "unset must reach the variable it names");
}
#[tokio::test]
async fn export_agrees_with_assignment_about_names() {
for (source, expected) in [
("export café=ok; echo $café", "ok"),
("export 名前=ok; echo ${名前}", "ok"),
("export _v=ok; echo $_v", "ok"),
] {
let (code, out) = run(source).await;
assert_eq!(code, 0, "{source:?} exited {code}");
assert_eq!(out, expected, "for: {source:?}");
}
let k = kernel();
let refused = match k.execute("export 1x=v").await {
Err(_) => true,
Ok(r) => r.code != 0,
};
assert!(refused, "a digit-leading name is still refused");
}