mod common;
use common::{run_awkrs_file, run_awkrs_stdin};
use std::process::{Command, Stdio};
#[test]
fn empty_print_prints_dollar0() {
let (c, o, _) = run_awkrs_stdin("{ print }", "hello world\n");
assert_eq!(c, 0);
assert_eq!(o, "hello world\n");
}
#[test]
fn dollar_nf_last_field() {
let (c, o, _) = run_awkrs_stdin("{ print $NF }", "a b c d\n");
assert_eq!(c, 0);
assert_eq!(o, "d\n");
}
#[test]
fn nr_increments_record_rules() {
let (c, o, _) = run_awkrs_stdin("{ print NR }", "a\nb\nc\n");
assert_eq!(c, 0);
assert_eq!(o, "1\n2\n3\n");
}
#[test]
fn fnr_resets_each_input_file() {
let dir = std::env::temp_dir();
let id = std::process::id();
let f1 = dir.join(format!("awkrs_fnr1_{id}.txt"));
let f2 = dir.join(format!("awkrs_fnr2_{id}.txt"));
std::fs::write(&f1, "a\n").unwrap();
std::fs::write(&f2, "b\n").unwrap();
let bin = env!("CARGO_BIN_EXE_awkrs");
let out = Command::new(bin)
.arg(r"{ print FNR }")
.arg(&f1)
.arg(&f2)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn awkrs");
let _ = std::fs::remove_file(&f1);
let _ = std::fs::remove_file(&f2);
assert_eq!(
out.status.code(),
Some(0),
"stderr={}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(String::from_utf8_lossy(&out.stdout), "1\n1\n");
}
#[test]
fn arithmetic_plus_mul() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print 2 + 3 * 4 }", "");
assert_eq!(c, 0);
assert_eq!(o, "14\n");
}
#[test]
fn string_concat() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print \"a\" \"b\" }", "");
assert_eq!(c, 0);
assert_eq!(o, "ab\n");
}
#[test]
fn compare_strings_lt() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print (\"a\" < \"b\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "1\n");
}
#[test]
fn compare_numeric_eq() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print (1 == 1) }", "");
assert_eq!(c, 0);
assert_eq!(o, "1\n");
}
#[test]
fn ternary() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print 1 ? \"yes\" : \"no\" }", "");
assert_eq!(c, 0);
assert_eq!(o, "yes\n");
}
#[test]
fn if_else() {
let (c, o, _) = run_awkrs_stdin("BEGIN { if (0) print \"a\"; else print \"b\" }", "");
assert_eq!(c, 0);
assert_eq!(o, "b\n");
}
#[test]
fn while_loop_sum() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { i = 1; s = 0; while (i <= 5) { s += i; i = i + 1 } print s }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "15\n");
}
#[test]
fn for_c_style_loop() {
let (c, o, _) = run_awkrs_stdin("BEGIN { for (i = 1; i <= 3; i = i + 1) print i }", "");
assert_eq!(c, 0);
assert_eq!(o, "1\n2\n3\n");
}
#[test]
fn for_in_array() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { a[\"x\"] = 1; a[\"y\"] = 2; for (k in a) s += a[k]; print s }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "3\n");
}
#[test]
fn break_in_while() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { i = 0; while (1) { i = i + 1; if (i == 2) break } print i }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "2\n");
}
#[test]
fn continue_in_for() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { for (i = 1; i <= 3; i = i + 1) { if (i == 2) continue; print i } }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "1\n3\n");
}
#[test]
fn in_operator_membership() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { a[\"k\"] = 1; print (\"k\" in a), (\"z\" in a) }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "1 0\n");
}
#[test]
fn in_operator_numeric_index() {
let (c, o, _) = run_awkrs_stdin("BEGIN { a[7] = 1; print (7 in a), (8 in a) }", "");
assert_eq!(c, 0);
assert_eq!(o, "1 0\n");
}
#[test]
fn in_operator_multidimensional_key() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { a[1,2] = 42; k = 1 SUBSEP 2; print (k in a), (k in b) }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "1 0\n");
}
#[test]
fn in_operator_chained_comparison() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { a[\"x\"] = 1; print ((\"x\" in a) == 1), ((\"y\" in a) == 0) }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "1 1\n");
}
#[test]
fn in_operator_false_when_name_not_array() {
let (c, o, _) = run_awkrs_stdin("BEGIN { s = \"scalar\"; print (\"k\" in s) }", "");
assert_eq!(c, 0);
assert_eq!(o, "0\n");
}
#[test]
fn delete_array_element() {
let (c, o, _) = run_awkrs_stdin("BEGIN { a[1] = 1; delete a[1]; print length(a) }", "");
assert_eq!(c, 0);
assert_eq!(o, "0\n");
}
#[test]
fn delete_entire_array() {
let (c, o, _) = run_awkrs_stdin("BEGIN { a[1] = 1; delete a; print length(a) }", "");
assert_eq!(c, 0);
assert_eq!(o, "0\n");
}
#[test]
fn substr_two_args() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print substr(\"abcdef\", 3) }", "");
assert_eq!(c, 0);
assert_eq!(o, "cdef\n");
}
#[test]
fn substr_three_args() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print substr(\"abcdef\", 2, 3) }", "");
assert_eq!(c, 0);
assert_eq!(o, "bcd\n");
}
#[test]
fn index_found() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print index(\"foobar\", \"bar\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "4\n");
}
#[test]
fn index_empty_needle() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print index(\"abc\", \"\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "0\n");
}
#[test]
fn length_no_args_is_record() {
let (c, o, _) = run_awkrs_stdin("{ print length() }", "abcd\n");
assert_eq!(c, 0);
assert_eq!(o, "4\n");
}
#[test]
fn tolower_toupper() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print tolower(\"AbC\"), toupper(\"xYz\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "abc XYZ\n");
}
#[test]
fn int_truncates() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print int(-3.7), int(3.9) }", "");
assert_eq!(c, 0);
assert_eq!(o, "-3 3\n");
}
#[test]
fn sqrt_builtin() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print sqrt(16) }", "");
assert_eq!(c, 0);
assert_eq!(o, "4\n");
}
#[test]
fn sub_on_record() {
let (c, o, _) = run_awkrs_stdin("{ sub(\"l\", \"L\"); print }", "hello\n");
assert_eq!(c, 0);
assert_eq!(o, "heLlo\n");
}
#[test]
fn gsub_global() {
let (c, o, _) = run_awkrs_stdin("{ gsub(\"l\", \"L\"); print }", "hello\n");
assert_eq!(c, 0);
assert_eq!(o, "heLLo\n");
}
#[test]
fn match_returns_position() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print match(\"foo123\", \"[0-9]+\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "4\n");
}
#[test]
fn sprintf_hex_upper() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print sprintf(\"%X\", 255) }", "");
assert_eq!(c, 0);
assert_eq!(o, "FF\n");
}
#[test]
fn sprintf_octal_alt() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print sprintf(\"%#o\", 8) }", "");
assert_eq!(c, 0);
assert_eq!(o, "010\n");
}
#[test]
fn sprintf_unsigned() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print sprintf(\"%u\", 42) }", "");
assert_eq!(c, 0);
assert_eq!(o, "42\n");
}
#[test]
fn sprintf_scientific() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print sprintf(\"%.2e\", 3.14159) }", "");
assert_eq!(c, 0);
assert!(o.starts_with("3.14e"), "got {o:?}");
}
#[test]
fn sprintf_char_c() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print sprintf(\"%c\", 65) }", "");
assert_eq!(c, 0);
assert_eq!(o, "A\n");
}
#[test]
fn ofs_join_fields() {
let (c, o, _) = run_awkrs_stdin("BEGIN { OFS = \"-\" } { print $1, $2 }", "a b\n");
assert_eq!(c, 0);
assert_eq!(o, "a-b\n");
}
#[test]
fn fs_single_char() {
let (c, o, _) = run_awkrs_stdin("BEGIN { FS = \",\" } { print $2 }", "a,b,c\n");
assert_eq!(c, 0);
assert_eq!(o, "b\n");
}
#[test]
fn default_fs_whitespace() {
let (c, o, _) = run_awkrs_stdin("{ print $2 }", " x y z \n");
assert_eq!(c, 0);
assert_eq!(o, "y\n");
}
#[test]
fn getline_from_file() {
let dir = std::env::temp_dir();
let path = dir.join(format!("awkrs_gl_{}.txt", std::process::id()));
std::fs::write(&path, "fileline\n").unwrap();
let p = path.to_string_lossy().replace('\\', "/");
let prog = format!("BEGIN {{ getline x < \"{p}\"; print x }}");
let (c, o, _) = run_awkrs_stdin(&prog, "");
let _ = std::fs::remove_file(&path);
assert_eq!(c, 0);
assert_eq!(o, "fileline\n");
}
#[test]
fn append_redirect() {
let dir = std::env::temp_dir();
let path = dir.join(format!("awkrs_app_{}.txt", std::process::id()));
let _ = std::fs::remove_file(&path);
let p = path.to_string_lossy().replace('\\', "/");
let prog = format!("BEGIN {{ print \"a\" > \"{p}\"; print \"b\" >> \"{p}\" }}");
let (c, _, _) = run_awkrs_stdin(&prog, "");
assert_eq!(c, 0);
let contents = std::fs::read_to_string(&path).unwrap();
let _ = std::fs::remove_file(&path);
assert_eq!(contents, "a\nb\n");
}
#[test]
fn fflush_empty_string_stdout() {
let (c, _, _) = run_awkrs_stdin(r#"BEGIN { fflush("") }"#, "");
assert_eq!(c, 0);
}
#[test]
fn pipe_fflush() {
let (c, _, e) = run_awkrs_stdin(r#"BEGIN { print "z" | "cat"; fflush("cat") }"#, "");
assert_eq!(c, 0, "stderr={e:?}");
}
#[test]
fn srand_returns_previous_seed() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print srand(42); print rand() }", "");
assert_eq!(c, 0);
let lines: Vec<&str> = o.lines().collect();
assert_eq!(lines.len(), 2);
}
#[test]
fn system_true_exit() {
let (c, o, _) = run_awkrs_stdin(r#"BEGIN { print system("exit 0") }"#, "");
assert_eq!(c, 0);
assert_eq!(o.trim(), "0");
}
#[test]
fn pattern_expression_gt() {
let (c, o, _) = run_awkrs_stdin("$1 > 1 { print $2 }", "1 a\n3 b\n");
assert_eq!(c, 0);
assert_eq!(o, "b\n");
}
#[test]
fn range_pattern_two_lines() {
let (c, o, _) = run_awkrs_stdin("/start/,/end/ { print $1 }", "x\nstart\nm\nend\ny\n");
assert_eq!(c, 0);
assert_eq!(o, "start\nm\nend\n");
}
#[test]
fn awk_style_mod() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print 7 % 3 }", "");
assert_eq!(c, 0);
assert_eq!(o, "1\n");
}
#[test]
fn unary_minus() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print - -5 }", "");
assert_eq!(c, 0);
assert_eq!(o, "5\n");
}
#[test]
fn not_operator() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print !0, !1 }", "");
assert_eq!(c, 0);
assert_eq!(o, "1 0\n");
}
#[test]
fn regexp_match_operator() {
let (c, o, _) = run_awkrs_stdin("{ print ($0 ~ /[0-9]+/) }", "abc42\n");
assert_eq!(c, 0);
assert_eq!(o, "1\n");
}
#[test]
fn block_statement() {
let (c, o, _) = run_awkrs_stdin("{ { print $1 } }", "ok\n");
assert_eq!(c, 0);
assert_eq!(o, "ok\n");
}
#[test]
fn multidimensional_delete_one_key() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { SUBSEP = \",\"; a[1,2] = 9; delete a[1,2]; print a[1,2]+0 }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "0\n");
}
#[test]
fn printf_function_to_stdout() {
let (c, o, _) = run_awkrs_stdin(r#"BEGIN { printf "%d", 7 }"#, "");
assert_eq!(c, 0);
assert_eq!(o, "7");
}
#[test]
fn split_default_fs_from_fs_var() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { FS = \":\"; n = split(\"a:b:c\", p); print n, p[2] }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "3 b\n");
}
#[test]
fn compound_assign_add() {
let (c, o, _) = run_awkrs_stdin("BEGIN { x = 1; x += 5; print x }", "");
assert_eq!(c, 0);
assert_eq!(o, "6\n");
}
#[test]
fn or_short_circuit() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print (1 || 0) }", "");
assert_eq!(c, 0);
assert_eq!(o, "1\n");
}
#[test]
fn and_short_circuit() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print (1 && 0) }", "");
assert_eq!(c, 0);
assert_eq!(o, "0\n");
}
#[test]
fn default_thread_count_silences_parallel_unsafe_warning() {
let (c, _, e) = run_awkrs_stdin(r#"{ print $1 |& "cat" }"#, "a\n");
assert_eq!(c, 0);
assert!(
!e.contains("not parallel-safe"),
"unexpected warning: {e:?}"
);
}
#[test]
fn expr_pattern_nr_equals_one() {
let (c, o, _) = run_awkrs_stdin("NR == 1 { print \"first\" }", "a\nb\n");
assert_eq!(c, 0);
assert_eq!(o, "first\n");
}
#[test]
fn two_rules_same_record() {
let (c, o, _) = run_awkrs_stdin("{ print \"A\" } { print \"B\" }", "x\n");
assert_eq!(c, 0);
assert_eq!(o, "A\nB\n");
}
#[test]
fn field_assignment_updates_dollar_zero() {
let (c, o, _) = run_awkrs_stdin("{$1 = \"new\"; print}", "old rest\n");
assert_eq!(c, 0);
assert_eq!(o, "new rest\n");
}
#[test]
fn compound_assign_mul() {
let (c, o, _) = run_awkrs_stdin("BEGIN { x = 3; x *= 4; print x }", "");
assert_eq!(c, 0);
assert_eq!(o, "12\n");
}
#[test]
fn compound_assign_div() {
let (c, o, _) = run_awkrs_stdin("BEGIN { x = 10; x /= 4; print x }", "");
assert_eq!(c, 0);
assert_eq!(o, "2.5\n");
}
#[test]
fn length_with_string_arg() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print length(\"abc\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "3\n");
}
#[test]
fn return_from_function() {
let (c, o, _) = run_awkrs_stdin("function id(z){ return z } BEGIN { print id(\"ok\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "ok\n");
}
#[test]
fn nested_function_calls() {
let (c, o, _) = run_awkrs_stdin(
"function a(x){return x+1} function b(y){return y*2} BEGIN { print b(a(3)) }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "8\n");
}
#[test]
fn regexp_negated_match_operator() {
let (c, o, _) = run_awkrs_stdin("{ print ($0 !~ /[0-9]+/) }", "abc\n42\n");
assert_eq!(c, 0);
assert_eq!(o, "1\n0\n");
}
#[test]
fn string_compare_gt() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print (\"z\" > \"a\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "1\n");
}
#[test]
fn concat_with_numeric_in_middle() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print \"a\" 2 \"b\" }", "");
assert_eq!(c, 0);
assert_eq!(o, "a2b\n");
}
#[test]
fn empty_input_still_runs_begin_end() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print \"B\" } END { print \"E\" }", "");
assert_eq!(c, 0);
assert_eq!(o, "B\nE\n");
}
#[test]
fn next_in_middle_rule_chain() {
let (c, o, _) = run_awkrs_stdin("{ if (NR == 1) next; print $1 }", "skip\nkeep\n");
assert_eq!(c, 0);
assert_eq!(o, "keep\n");
}
#[test]
fn sprintf_percent_s_mixed_types() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print sprintf(\"%s-%d\", \"x\", 7) }", "");
assert_eq!(c, 0);
assert_eq!(o, "x-7\n");
}
#[test]
fn rand_bounded_after_srand() {
let (c, o, _) = run_awkrs_stdin("BEGIN { srand(1); print (rand() < 1 && rand() >= 0) }", "");
assert_eq!(c, 0);
assert_eq!(o, "1\n");
}
#[test]
fn match_with_array_captures() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { n = match(\"foo123bar\", \"([0-9]+)\", a); print n, a[1] }",
"",
);
assert_eq!(c, 0);
assert_eq!(o, "4 123\n");
}
#[test]
fn sub_amp_backreference_style() {
let (c, o, _) = run_awkrs_stdin("{ sub(\"a\", \"[&]\"); print }", "abc\n");
assert_eq!(c, 0);
assert_eq!(o, "[a]bc\n");
}
#[test]
fn gsub_ampersand_replacement() {
let (c, o, _) = run_awkrs_stdin("{ gsub(\"o\", \"(&)\"); print }", "foo\n");
assert_eq!(c, 0);
assert_eq!(o, "f(o)(o)\n");
}
#[test]
fn begin_end_nr_fnr() {
let (c, o, _) = run_awkrs_stdin(
"BEGIN { print NR, FNR } { print NR, FNR } END { print NR, FNR }",
"a\n",
);
assert_eq!(c, 0);
assert_eq!(o, "0 0\n1 1\n1 1\n");
}
#[test]
fn empty_field_between_commas() {
let (c, o, _) = run_awkrs_stdin("BEGIN { FS = \",\" } { print NF, $2 }", "a,,c\n");
assert_eq!(c, 0);
assert_eq!(o, "3 \n");
}
#[test]
fn default_ofmt_prints_number() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print 1.5 }", "");
assert_eq!(c, 0);
assert!(o.contains('1') && o.contains('5'), "got {o:?}");
}
#[test]
fn relop_string_vs_number_mixed() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print (\"10\" < 9), (\"10\" < \"9\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "0 0\n");
}
#[test]
fn print_multiple_args_ofs() {
let (c, o, _) = run_awkrs_stdin("BEGIN { OFS=\":\"; print 1,2,3 }", "");
assert_eq!(c, 0);
assert_eq!(o, "1:2:3\n");
}
#[test]
fn record_ors_concat_print() {
let (c, o, _) = run_awkrs_stdin("BEGIN { ORS=\"|\"; print \"a\"; print \"b\" }", "");
assert_eq!(c, 0);
assert_eq!(o, "a|b|");
}
#[test]
fn nested_arrays_subscript_expr() {
let (c, o, _) = run_awkrs_stdin("BEGIN { i=1; a[i]=10; print a[1] }", "");
assert_eq!(c, 0);
assert_eq!(o, "10\n");
}
#[test]
fn comparison_chain() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print (1 < 2 && 2 < 3) }", "");
assert_eq!(c, 0);
assert_eq!(o, "1\n");
}
#[test]
fn exit_in_action_end_still_runs() {
let (c, o, _) = run_awkrs_stdin("{ exit 0 } END { print \"e\" }", "x\n");
assert_eq!(c, 0);
assert_eq!(o, "e\n");
}
#[test]
fn getline_into_var_from_string_file() {
let dir = std::env::temp_dir();
let path = dir.join(format!("awkrs_gl2_{}.txt", std::process::id()));
std::fs::write(&path, "onlyline\n").unwrap();
let p = path.to_string_lossy().replace('\\', "/");
let prog = format!("BEGIN {{ getline x < \"{p}\"; print x }}");
let (c, o, _) = run_awkrs_stdin(&prog, "");
let _ = std::fs::remove_file(&path);
assert_eq!(c, 0);
assert_eq!(o, "onlyline\n");
}
#[test]
fn split_returns_zero_on_empty_string() {
let (c, o, _) = run_awkrs_stdin("BEGIN { n = split(\"\", a); print n }", "");
assert_eq!(c, 0);
assert_eq!(o, "0\n");
}
#[test]
fn match_returns_zero_for_no_match_end_to_end() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print match(\"abc\", \"z\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "0\n");
}
#[test]
fn sprintf_escaped_percent() {
let (c, o, _) = run_awkrs_stdin("BEGIN { print sprintf(\"100%%\") }", "");
assert_eq!(c, 0);
assert_eq!(o, "100%\n");
}
#[test]
fn delete_scalar_then_use() {
let (c, o, _) = run_awkrs_stdin("BEGIN { x=1; delete x; print x+0 }", "");
assert_eq!(c, 0);
assert_eq!(o, "0\n");
}
#[test]
fn file_input_slurped_fast_path_two_lines() {
let dir = std::env::temp_dir();
let path = dir.join(format!("awkrs_slurp_{}.txt", std::process::id()));
std::fs::write(&path, "aa bb\ncc dd\n").expect("temp data");
let (c, o, e) = run_awkrs_file("{ print $1 }", &path);
let _ = std::fs::remove_file(&path);
assert_eq!(c, 0, "stderr={e:?}");
assert_eq!(o, "aa\ncc\n");
}