#![allow(clippy::unwrap_used, clippy::expect_used)]
#![cfg(feature = "localfs")]
mod common;
use tempfile::tempdir;
use common::{kernel_at, run};
#[tokio::test]
async fn sed_bre_alternation_default() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) = run(&kernel, r#"printf 'cat\ndog\nfish\n' | sed 's/cat\|dog/X/'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out, "X\nX\nfish", "alternation replaces cat and dog: {out:?}");
}
#[tokio::test]
async fn sed_bre_capture_groups_default() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) = run(&kernel, r#"echo ab | sed 's/\(a\)\(b\)/\2\1/'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.trim(), "ba", "BRE groups swap via \\2\\1: {out:?}");
}
#[tokio::test]
async fn sed_extended_flag_makes_pipe_literal() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) =
run(&kernel, r#"printf 'cat|dog\ncat\n' | sed -E 's/cat\|dog/X/'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out, "X\ncat", "-E treats \\| as literal pipe: {out:?}");
}
#[tokio::test]
async fn awk_bre_alternation_in_pattern() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) =
run(&kernel, r#"printf 'cat\ndog\nfish\n' | awk '/cat\|dog/ {print}'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out, "cat\ndog", "awk /re/ alternates on \\|: {out:?}");
}
#[tokio::test]
async fn awk_bre_alternation_in_match_operator() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) =
run(&kernel, r#"echo hello | awk '{ if ($0 ~ /he\|xy/) print "match" }'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.trim(), "match", "awk ~ alternates on \\|: {out:?}");
}
#[tokio::test]
async fn awk_bre_alternation_in_gsub() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) =
run(&kernel, r#"echo 'cat dog fish' | awk '{gsub(/cat\|dog/, "X"); print}'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.trim(), "X X fish", "gsub alternates on \\|: {out:?}");
}
#[tokio::test]
async fn sed_dash_r_alias_makes_pipe_literal() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) =
run(&kernel, r#"printf 'cat|dog\ncat\n' | sed -r 's/cat\|dog/X/'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out, "X\ncat", "-r treats \\| as literal pipe: {out:?}");
}
#[tokio::test]
async fn rewritten_meta_compile_errors_carry_dialect_hint() {
let kernel = kernel_at(tempdir().unwrap().path());
let err = kernel
.execute(r#"echo x | sed 's/a\)/X/'"#)
.await
.expect_err("unmatched group should fail sed validation");
let msg = err.to_string();
assert!(msg.contains("GNU BRE"), "sed should explain the dialect: {msg}");
assert!(msg.contains("-E/-r"), "sed should offer its strict-ERE flags: {msg}");
let result = kernel
.execute(r#"echo x | awk '/a\)/ {print}'"#)
.await
.expect("awk regex failure is a runtime error, not a validation error");
assert_ne!(result.code, 0, "unmatched group should fail: {result:?}");
assert!(result.err.contains("GNU BRE"), "awk should explain the dialect: {}", result.err);
assert!(
!result.err.contains("-E"),
"awk has no -E flag and must not suggest one: {}",
result.err
);
}
#[tokio::test]
async fn awk_capital_f_backslash_pipe_splits_on_literal_pipe() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) = run(&kernel, r#"echo 'a|b|c' | awk -F '\|' '{print NF, $2}'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.trim(), "3 b", "-F '\\|' is a literal pipe, like gawk: {out:?}");
}
#[tokio::test]
async fn awk_fs_assignment_backslash_pipe_splits_on_literal_pipe() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) =
run(&kernel, r#"echo 'a|b|c' | awk 'BEGIN{FS="\\|"} {print NF, $2}'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.trim(), "3 b", "FS=\"\\\\|\" is a literal pipe, like gawk: {out:?}");
}
#[tokio::test]
async fn awk_split_backslash_pipe_splits_on_literal_pipe() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) = run(
&kernel,
r#"echo x | awk '{n = split("a|b|c", parts, "\\|"); print n, parts[2]}'"#,
)
.await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.trim(), "3 b", "split() on \"\\\\|\" is a literal pipe: {out:?}");
}
#[tokio::test]
async fn awk_multichar_fs_still_gets_bre_rewrite() {
let kernel = kernel_at(tempdir().unwrap().path());
let (out, code) =
run(&kernel, r#"echo 'a--b;;c' | awk -F '--\|;;' '{print NF, $3}'"#).await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.trim(), "3 c", "multi-char FS alternates on \\|: {out:?}");
}
#[tokio::test]
async fn awk_invalid_separator_error_names_raw_form_with_hint() {
let kernel = kernel_at(tempdir().unwrap().path());
let result = kernel
.execute(r#"echo x | awk -F 'xx\(' '{print NF}'"#)
.await
.expect("invalid FS is a runtime error, not a validation error");
assert_ne!(result.code, 0, "invalid FS should fail: {result:?}");
assert!(result.err.contains(r"xx\\("), "names raw separator: {}", result.err);
assert!(result.err.contains("GNU BRE"), "carries the dialect hint: {}", result.err);
assert!(!result.err.contains("-E"), "awk must not suggest -E: {}", result.err);
let result = kernel
.execute(r#"echo x | awk '{n = split("abc", a, "yy\\("); print n}'"#)
.await
.expect("invalid split() separator is a runtime error");
assert_ne!(result.code, 0, "invalid separator should fail: {result:?}");
assert!(result.err.contains(r"yy\\("), "names raw separator: {}", result.err);
assert!(result.err.contains("GNU BRE"), "carries the dialect hint: {}", result.err);
}