mod common;
use common::{run_awkrs_stdin, run_awkrs_stdin_args_env};
use std::ffi::OsString;
use std::io::Write;
use std::process::{Command, Stdio};
#[test]
fn prints_second_field() {
let (code, stdout, _) = run_awkrs_stdin("{print $2}", "one two three\n");
assert_eq!(code, 0);
assert_eq!(stdout, "two\n");
}
#[test]
fn parallel_record_mode_preserves_output_order() {
let bin = env!("CARGO_BIN_EXE_awkrs");
let mut child = Command::new(bin)
.args(["-j", "8", "{ print $1 }"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn awkrs");
child
.stdin
.take()
.expect("stdin")
.write_all(b"a\nb\nc\nd\ne\n")
.expect("write stdin");
let out = child.wait_with_output().expect("wait");
assert_eq!(out.status.code(), Some(0));
assert_eq!(String::from_utf8_lossy(&out.stdout), "a\nb\nc\nd\ne\n");
}
#[test]
fn begin_end_sum() {
let (code, stdout, _) =
run_awkrs_stdin("BEGIN { s=0 } { s += $1 } END { print s }", "1\n2\n3\n");
assert_eq!(code, 0);
assert_eq!(stdout, "6\n");
}
#[test]
fn regex_pattern_matches_line() {
let (code, stdout, _) = run_awkrs_stdin(r#"/hello/ { print "yes" }"#, "hello\nworld\n");
assert_eq!(code, 0);
assert_eq!(stdout, "yes\n");
}
#[test]
fn unknown_function_errors() {
let (code, _, stderr) = run_awkrs_stdin("{nosuch()}", "a\n");
assert_ne!(code, 0);
assert!(stderr.contains("unknown function"), "stderr={stderr:?}");
}
#[test]
fn split_populates_array() {
let (code, stdout, _) = run_awkrs_stdin(
"BEGIN { n = split(\"a:b:c\", parts, \":\"); print n, parts[1], parts[2], parts[3] }",
"",
);
assert_eq!(code, 0);
assert_eq!(stdout, "3 a b c\n");
}
#[test]
fn user_function() {
let (code, stdout, _) = run_awkrs_stdin(
"function add(x,y){ return x+y } { print add($1, $2) }",
"3 4\n",
);
assert_eq!(code, 0);
assert_eq!(stdout, "7\n");
}
#[test]
fn next_skips_following_rules() {
let (code, stdout, _) = run_awkrs_stdin("{ print \"a\"; next } { print \"b\" }", "x\n");
assert_eq!(code, 0);
assert_eq!(stdout, "a\n");
}
#[test]
fn exit_runs_end_before_process_exit() {
let (code, stdout, _) = run_awkrs_stdin("BEGIN { exit 2 } END { print \"done\" }", "");
assert_eq!(code, 2);
assert_eq!(stdout, "done\n");
}
#[test]
fn getline_primary_advances_record() {
let (code, stdout, _) = run_awkrs_stdin("{ print $0; getline; print $0 }", "a\nb\n");
assert_eq!(code, 0);
assert_eq!(stdout, "a\nb\n");
}
#[test]
fn cyberpunk_help_banner() {
let out = Command::new(env!("CARGO_BIN_EXE_awkrs"))
.arg("--help")
.output()
.expect("spawn awkrs --help");
assert!(
out.status.success(),
"stderr={}",
String::from_utf8_lossy(&out.stderr)
);
let s = String::from_utf8_lossy(&out.stdout);
assert!(
s.contains("STATUS: ONLINE") && s.contains("SIGNAL:"),
"expected HUD status line, got: {s:?}"
);
assert!(
s.contains("TEXT HAX") || s.contains("████"),
"expected tagline or logo, got: {s:?}"
);
}
#[test]
fn gsub_on_record() {
let (code, stdout, _) = run_awkrs_stdin("{ gsub(\"o\", \"x\"); print }", "hello\n");
assert_eq!(code, 0);
assert_eq!(stdout, "hellx\n");
}
#[test]
fn multidimensional_array_subsep() {
let (code, stdout, _) = run_awkrs_stdin("BEGIN { a[1,2] = 42; print a[1,2] }", "");
assert_eq!(code, 0);
assert_eq!(stdout, "42\n");
}
#[test]
fn beginfile_endfile_stdin() {
let (code, stdout, _) = run_awkrs_stdin(
"BEGINFILE { s = s \"B\" } ENDFILE { s = s \"E\" } END { print s }",
"x\n",
);
assert_eq!(code, 0);
assert_eq!(stdout, "BE\n");
}
#[test]
fn sprintf_width_zero_pad() {
let (code, stdout, _) = run_awkrs_stdin("BEGIN { print sprintf(\"%05d\", 7) }", "");
assert_eq!(code, 0);
assert_eq!(stdout, "00007\n");
}
#[test]
fn patsplit_with_pattern() {
let (code, stdout, _) = run_awkrs_stdin(
"BEGIN { n = patsplit(\"a b c\", p, \"[^ ]+\"); print n, p[1], p[2], p[3] }",
"",
);
assert_eq!(code, 0);
assert_eq!(stdout, "3 a b c\n");
}
#[test]
fn patsplit_seps_between_fields() {
let (code, stdout, _) = run_awkrs_stdin(
"BEGIN { n = patsplit(\"aa bb\", a, \"[a-z]+\", seps); print n, length(seps[1]) }",
"",
);
assert_eq!(code, 0);
assert_eq!(stdout, "2 2\n");
}
#[test]
fn sprintf_star_width() {
let (code, stdout, _) = run_awkrs_stdin("BEGIN { print sprintf(\"%*d\", 4, 7) }", "");
assert_eq!(code, 0);
assert_eq!(stdout, " 7\n");
}
#[test]
fn sprintf_positional_args() {
let (code, stdout, _) =
run_awkrs_stdin("BEGIN { print sprintf(\"%2$s %1$s\", \"a\", \"b\") }", "");
assert_eq!(code, 0);
assert_eq!(stdout, "b a\n");
}
#[test]
fn printf_statement_no_parens() {
let (code, stdout, _) = run_awkrs_stdin("BEGIN { printf \"%d\\n\", 42 }", "");
assert_eq!(code, 0);
assert_eq!(stdout, "42\n");
}
#[test]
fn sprintf_star_positional_width() {
let (code, stdout, _) = run_awkrs_stdin("BEGIN { print sprintf(\"%*1$d\", 4, 9) }", "");
assert_eq!(code, 0);
assert_eq!(stdout, " 9\n");
}
#[test]
fn print_pipe_to_cat() {
let (code, stdout, stderr) = run_awkrs_stdin("BEGIN { print \"hello\" | \"cat\" }", "");
assert_eq!(code, 0, "stderr={stderr:?}");
assert_eq!(stdout, "hello\n");
}
#[test]
fn print_coproc_getline_cat() {
let (code, stdout, stderr) = run_awkrs_stdin(
"BEGIN { print \"hi\" |& \"cat\"; fflush(\"cat\"); getline x <& \"cat\"; print x }",
"",
);
assert_eq!(code, 0, "stderr={stderr:?}");
assert_eq!(stdout, "hi\n");
}
#[test]
fn printf_coproc_getline_cat() {
let (code, stdout, stderr) = run_awkrs_stdin(
"BEGIN { printf \"%s\\n\", \"q\" |& \"cat\"; fflush(\"cat\"); getline x <& \"cat\"; print x }",
"",
);
assert_eq!(code, 0, "stderr={stderr:?}");
assert_eq!(stdout, "q\n");
}
#[test]
fn coproc_two_lines_roundtrip() {
let (code, stdout, stderr) = run_awkrs_stdin(
r#"BEGIN {
print "first" |& "cat"
print "second" |& "cat"
fflush("cat")
getline a <& "cat"
getline b <& "cat"
print a
print b
}"#,
"",
);
assert_eq!(code, 0, "stderr={stderr:?}");
assert_eq!(stdout, "first\nsecond\n");
}
#[test]
fn coproc_close_after_roundtrip() {
let (code, stdout, stderr) = run_awkrs_stdin(
r#"BEGIN {
print "x" |& "cat"
fflush("cat")
getline v <& "cat"
print v
close("cat")
}"#,
"",
);
assert_eq!(code, 0, "stderr={stderr:?}");
assert_eq!(stdout, "x\n");
}
#[test]
fn pipe_then_coproc_same_cmd_errors() {
let (code, _, stderr) =
run_awkrs_stdin(r#"BEGIN { print "a" | "cat"; print "b" |& "cat" }"#, "");
assert_ne!(code, 0);
assert!(
stderr.contains("two-way pipe") && stderr.contains("conflicts"),
"stderr={stderr:?}"
);
}
#[test]
fn coproc_then_pipe_same_cmd_errors() {
let (code, _, stderr) =
run_awkrs_stdin(r#"BEGIN { print "a" |& "cat"; print "b" | "cat" }"#, "");
assert_ne!(code, 0);
assert!(
stderr.contains("one-way pipe") && stderr.contains("conflicts"),
"stderr={stderr:?}"
);
}
#[test]
fn fflush_unknown_target_errors() {
let (code, _, stderr) = run_awkrs_stdin(r#"BEGIN { fflush("not_an_open_redirection") }"#, "");
assert_ne!(code, 0);
assert!(stderr.contains("fflush"), "stderr={stderr:?}");
}
#[test]
fn sprintf_star_width_second_arg_end_to_end() {
let (code, stdout, _) = run_awkrs_stdin(r#"BEGIN { print sprintf("%*2$d", 5, 4, 9) }"#, "");
assert_eq!(code, 0);
assert_eq!(stdout, " 9\n");
}
#[test]
fn threads_with_print_coproc_warns_not_parallel_safe() {
let bin = env!("CARGO_BIN_EXE_awkrs");
let mut child = Command::new(bin)
.args(["-j", "8", r#"{ print $1 |& "cat" }"#])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn awkrs");
child
.stdin
.take()
.expect("stdin")
.write_all(b"a\n")
.expect("write stdin");
let out = child.wait_with_output().expect("wait");
assert_eq!(
out.status.code(),
Some(0),
"stderr={}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("not parallel-safe"),
"expected warning, stderr={stderr:?}"
);
}
#[test]
fn print_redirect_and_fflush() {
let dir = std::env::temp_dir();
let path = dir.join(format!("awkrs_out_{}.txt", std::process::id()));
let _ = std::fs::remove_file(&path);
let p = path.to_string_lossy().replace('\\', "/");
let prog = format!("BEGIN {{ print \"hello\" > \"{p}\" ; fflush(\"{p}\") }}");
let (code, stdout, stderr) = run_awkrs_stdin(&prog, "");
assert_eq!(code, 0, "stderr={stderr:?}");
assert!(stdout.is_empty());
let contents = std::fs::read_to_string(&path).expect("read redirected output");
assert_eq!(contents, "hello\n");
let _ = std::fs::remove_file(&path);
}
#[test]
fn use_lc_numeric_short_flag_c_locale_printf() {
let (code, stdout, stderr) = run_awkrs_stdin_args_env(
["-N"],
r#"BEGIN { printf "%f\n", 1.5 }"#,
"",
[(OsString::from("LC_NUMERIC"), OsString::from("C"))],
);
assert_eq!(code, 0, "stderr={stderr:?}");
assert_eq!(stdout, "1.500000\n");
}
#[test]
fn use_lc_numeric_long_flag_c_locale_sprintf() {
let (code, stdout, stderr) = run_awkrs_stdin_args_env(
["--use-lc-numeric"],
r#"BEGIN { print sprintf("%e", 0.25) }"#,
"",
[(OsString::from("LC_NUMERIC"), OsString::from("C"))],
);
assert_eq!(code, 0, "stderr={stderr:?}");
assert!(
stdout.starts_with("2.5") && stdout.contains('e') && stdout.ends_with('\n'),
"unexpected scientific output: {stdout:?}"
);
}
#[test]
fn stdin_parallel_safe_high_threads_no_parallel_unsafe_warning() {
let bin = env!("CARGO_BIN_EXE_awkrs");
let mut child = Command::new(bin)
.args(["-j", "8", "{ print $1 }"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn awkrs");
child
.stdin
.take()
.expect("stdin")
.write_all(b"a\nb\nc\n")
.expect("write stdin");
let out = child.wait_with_output().expect("wait");
assert_eq!(out.status.code(), Some(0));
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.contains("not parallel-safe"),
"stdin should not hit parallel-unsafe path; stderr={stderr:?}"
);
}
#[test]
fn parallel_mode_with_input_file_preserves_line_order() {
let dir = std::env::temp_dir();
let id = std::process::id();
let path = dir.join(format!("awkrs_par_order_{id}.txt"));
std::fs::write(&path, "a\nb\nc\nd\n").expect("write temp file");
let bin = env!("CARGO_BIN_EXE_awkrs");
let out = Command::new(bin)
.args(["-j", "8", "{ print $1 }"])
.arg(&path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn awkrs");
let _ = std::fs::remove_file(&path);
assert_eq!(
out.status.code(),
Some(0),
"stderr={}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(!stderr.contains("not parallel-safe"), "stderr={stderr:?}");
assert_eq!(String::from_utf8_lossy(&out.stdout), "a\nb\nc\nd\n");
}