mod common;
use common::{run_awkrs_file, run_awkrs_stdin, run_awkrs_stdin_args, run_awkrs_stdin_args_env};
use std::ffi::OsString;
use std::process::{Command, Stdio};
#[test]
fn procinfo_argv_subarray_matches_process_args() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
a = PROCINFO["argv"]
print (a["0"] ~ /awkrs/)
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "1");
}
#[test]
fn procinfo_api_and_program_identify_runtime() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
print PROCINFO["api"]
print (PROCINFO["program"] != "")
print (PROCINFO["api_major"] + 0 >= 4)
}"#,
"",
);
assert_eq!(c, 0);
let lines: Vec<&str> = o.lines().collect();
assert_eq!(lines[0].trim(), "awkrs");
assert_eq!(lines[1].trim(), "1");
assert_eq!(lines[2].trim(), "1");
}
#[test]
fn functab_lists_user_function_metadata() {
let (c, o, _) = run_awkrs_stdin(
r#"function f(x) { return x }
BEGIN { m = FUNCTAB["f"]; print m["type"], m["arity"] }"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "user 1");
}
#[test]
fn functab_lists_two_distinct_user_functions() {
let (c, o, _) = run_awkrs_stdin(
r#"function foo() { return 1 }
function bar(x, y) { return x + y }
BEGIN {
fa = FUNCTAB["foo"]
fb = FUNCTAB["bar"]
print fa["arity"], fb["arity"]
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "0 2");
}
#[test]
fn procinfo_read_timeout_composite_key_exists_for_stdin_placeholder() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
sep = SUBSEP
k = "-" sep "READ_TIMEOUT"
print (k in PROCINFO)
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "1");
}
#[test]
fn procinfo_retry_composite_key_exists_for_stdin_placeholder() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
sep = SUBSEP
k = "-" sep "RETRY"
print (k in PROCINFO)
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "1");
}
#[test]
fn procinfo_sorted_in_val_num_asc_orders_by_numeric_value() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
a["x"] = 3
a["y"] = 1
a["z"] = 2
PROCINFO["sorted_in"] = "@val_num_asc"
for (k in a) print k
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o, "y\nz\nx\n");
}
#[test]
fn procinfo_sorted_in_val_num_desc_orders_by_numeric_value() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
a["x"] = 3
a["y"] = 1
a["z"] = 2
PROCINFO["sorted_in"] = "@val_num_desc"
for (k in a) print k
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o, "x\nz\ny\n");
}
#[test]
fn procinfo_sorted_in_val_str_asc_orders_by_string_value() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
a["x"] = "b"
a["y"] = "a"
PROCINFO["sorted_in"] = "@val_str_asc"
for (k in a) print k
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o, "y\nx\n");
}
#[test]
fn procinfo_sorted_in_ind_str_desc_reverse_lexicographic() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
a["a"] = 1
a["b"] = 1
a["c"] = 1
PROCINFO["sorted_in"] = "@ind_str_desc"
for (k in a) print k
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o, "c\nb\na\n");
}
#[test]
fn procinfo_sorted_in_ind_num_asc_orders_indices_numerically() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
a["10"] = 1
a["2"] = 1
a["1"] = 1
PROCINFO["sorted_in"] = "@ind_num_asc"
for (k in a) print k
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o, "1\n2\n10\n");
}
#[test]
fn procinfo_sorted_in_unknown_at_token_warns_once_on_stderr() {
let bin = env!("CARGO_BIN_EXE_awkrs");
let out = Command::new(bin)
.args([
"-e",
r#"BEGIN {
PROCINFO["sorted_in"] = "@not_a_real_mode"
a["b"] = 1
a["a"] = 1
for (k in a) print k
}"#,
])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn");
assert_eq!(out.status.code(), Some(0));
let e = String::from_utf8_lossy(&out.stderr);
assert!(
e.contains("unknown @") || e.contains("sorted_in"),
"stderr should mention unknown sorted_in token: {e:?}"
);
}
#[test]
fn regexp_constant_split_behaves_like_regex_string() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
r = @/[;,]/
n = split("a,b;c", arr, r)
print n, arr[1], arr[2], arr[3]
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "3 a b c");
}
#[test]
fn regexp_constant_gensub_replacement() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
r = @/[0-9]+/
print gensub(r, "N", "g", "a1b2c")
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "aNbNc");
}
#[test]
fn lint_flag_warns_uninitialized_read_in_begin() {
let (c, o, e) = run_awkrs_stdin_args(["-L", "invalid"], "BEGIN { print u }", "");
assert_eq!(c, 0);
assert_eq!(o, "\n");
assert!(
e.contains("uninitialized") && e.contains('u'),
"expected uninit lint on stderr, got: {e:?}"
);
}
#[test]
fn sandbox_rejects_system_call_with_runtime_error() {
let (c, _, e) = run_awkrs_stdin_args(["-S"], "BEGIN { system(\"true\") }", "");
assert_ne!(c, 0);
assert!(
e.contains("sandbox") && e.contains("system"),
"stderr={e:?}"
);
}
#[test]
fn sandbox_rejects_chdir_builtin() {
let (c, _, e) = run_awkrs_stdin_args(["-S"], r#"BEGIN { print chdir("/") }"#, "");
assert_ne!(c, 0);
assert!(e.contains("sandbox"), "stderr={e:?}");
}
#[test]
fn sandbox_rejects_file_redirect() {
let (c, _, e) = run_awkrs_stdin_args(["-S"], r#"BEGIN { print "x" > "/dev/null" }"#, "");
assert_ne!(c, 0);
assert!(e.contains("sandbox"), "stderr={e:?}");
}
#[test]
fn stat_builtin_reports_regular_file() {
let dir = std::env::temp_dir();
let path = dir.join(format!("awkrs_stat_{}.txt", std::process::id()));
std::fs::write(&path, "x").unwrap();
let ps = path.to_string_lossy().replace('\\', "\\\\");
let prog = format!(
r#"BEGIN {{ if (stat("{ps}", st) == 0) print st["type"], (st["size"] + 0 == 1) }}"#
);
let (c, o, err) = run_awkrs_stdin(&prog, "");
let _ = std::fs::remove_file(&path);
assert_eq!(c, 0, "stderr={err}");
assert_eq!(o.trim(), "file 1");
}
#[test]
fn readfile_reads_entire_file() {
let dir = std::env::temp_dir();
let path = dir.join(format!("awkrs_rf_{}.txt", std::process::id()));
std::fs::write(&path, "hello\nworld").unwrap();
let ps = path.to_string_lossy().replace('\\', "\\\\");
let prog = format!(r#"BEGIN {{ print readfile("{ps}") }}"#);
let (c, o, err) = run_awkrs_stdin(&prog, "");
let _ = std::fs::remove_file(&path);
assert_eq!(c, 0, "stderr={err}");
assert_eq!(o, "hello\nworld\n");
}
#[test]
fn ord_chr_roundtrip_ascii() {
let (c, o, _) = run_awkrs_stdin(r#"BEGIN { print chr(ord("A")) }"#, "");
assert_eq!(c, 0);
assert_eq!(o.trim(), "A");
}
#[test]
fn sleep_zero_returns_zero() {
let (c, o, _) = run_awkrs_stdin(r#"BEGIN { print sleep(0) }"#, "");
assert_eq!(c, 0);
assert_eq!(o.trim(), "0");
}
#[test]
fn revoutput_reverses_string() {
let (c, o, _) = run_awkrs_stdin(r#"BEGIN { print revoutput("ab") }"#, "");
assert_eq!(c, 0);
assert_eq!(o.trim(), "ba");
}
#[test]
fn revtwoway_matches_revoutput() {
let (c, o, _) = run_awkrs_stdin(r#"BEGIN { print (revtwoway("x") == revoutput("x")) }"#, "");
assert_eq!(c, 0);
assert_eq!(o.trim(), "1");
}
#[test]
fn chdir_then_readfile_relative_path() {
let dir = std::env::temp_dir().join(format!("awkrs_chdir_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let inner = dir.join("inner.txt");
std::fs::write(&inner, "inside").unwrap();
let d = dir.to_string_lossy().replace('\\', "\\\\");
let prog = format!(
r#"BEGIN {{
if (chdir("{d}") != 0) {{ print "cdfail"; exit 1 }}
print readfile("inner.txt")
}}"#
);
let (c, o, err) = run_awkrs_stdin(&prog, "");
let _ = std::fs::remove_dir_all(&dir);
assert_eq!(c, 0, "stderr={err}");
assert_eq!(o.trim(), "inside");
}
#[test]
fn gettimeofday_sets_sec_and_usec() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
gettimeofday(t)
print (t["sec"] + 0 > 1000000000), (t["usec"] + 0 >= 0)
}"#,
"",
);
assert_eq!(c, 0);
let lines: Vec<&str> = o.lines().collect();
assert_eq!(lines.len(), 1);
let parts: Vec<&str> = lines[0].split_whitespace().collect();
assert_eq!(parts, vec!["1", "1"]);
}
#[test]
fn fts_lists_sorted_paths_under_temp_directory() {
let dir = std::env::temp_dir().join(format!("awkrs_fts_{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let a = dir.join("a.txt");
let b = dir.join("b.txt");
std::fs::write(&a, "1").unwrap();
std::fs::write(&b, "2").unwrap();
let ds = dir.to_string_lossy().replace('\\', "\\\\");
let prog = format!(
r#"BEGIN {{
n = fts("{ds}", paths)
print n
print paths["2"]
print paths["3"]
}}"#
);
let (c, o, err) = run_awkrs_stdin(&prog, "");
let _ = std::fs::remove_file(&a);
let _ = std::fs::remove_file(&b);
let _ = std::fs::remove_dir(&dir);
assert_eq!(c, 0, "stderr={err}");
let lines: Vec<&str> = o.lines().collect();
assert!(lines.len() >= 3);
assert_eq!(lines[0].trim(), "3"); assert!(lines[1].contains("a.txt"), "paths[2]={:?}", lines[1]);
assert!(lines[2].contains("b.txt"), "paths[3]={:?}", lines[2]);
assert!(lines[1] <= lines[2], "fts sorts paths");
}
#[test]
fn rename_builtin_moves_temp_file() {
let dir = std::env::temp_dir();
let old = dir.join(format!("awkrs_rn_old_{}.txt", std::process::id()));
let new = dir.join(format!("awkrs_rn_new_{}.txt", std::process::id()));
let _ = std::fs::remove_file(&old);
let _ = std::fs::remove_file(&new);
std::fs::write(&old, "z").unwrap();
let o = old.to_string_lossy().replace('\\', "\\\\");
let n = new.to_string_lossy().replace('\\', "\\\\");
let prog = format!(
r#"BEGIN {{
if (rename("{o}", "{n}") != 0) {{ print "fail"; exit 1 }}
print readfile("{n}")
}}"#
);
let (c, out, err) = run_awkrs_stdin(&prog, "");
let _ = std::fs::remove_file(&new);
assert_eq!(c, 0, "stderr={err}");
assert_eq!(out.trim(), "z");
}
#[test]
fn writea_reada_roundtrip_preserves_array() {
let dir = std::env::temp_dir();
let path = dir.join(format!("awkrs_wa_{}.dat", std::process::id()));
let ps = path.to_string_lossy().replace('\\', "\\\\");
let prog = format!(
r#"BEGIN {{
src["x"] = 1
src["y"] = 2
if (writea("{ps}", src) != 0) {{ print "writea_fail"; exit 1 }}
delete dst
if (reada("{ps}", dst) != 0) {{ print "reada_fail"; exit 1 }}
print dst["x"], dst["y"]
}}"#
);
let (c, o, err) = run_awkrs_stdin(&prog, "");
let _ = std::fs::remove_file(&path);
assert_eq!(c, 0, "stderr={err}");
assert_eq!(o.trim(), "1 2");
}
#[test]
fn bignum_sorted_in_four_arg_prefers_lower_value() {
let prog = r#"function vcmp(i1,v1,i2,v2) {
if (v1 < v2) return -1
if (v1 > v2) return 1
return 0
}
BEGIN {
a["low"] = 1
a["high"] = 9
PROCINFO["sorted_in"] = "vcmp"
for (k in a) print k
}"#;
let (c, o, _) = run_awkrs_stdin_args(["-M"], prog, "");
assert_eq!(c, 0);
assert_eq!(o, "low\nhigh\n");
}
#[test]
#[cfg(unix)]
fn statvfs_root_populates_f_bsize() {
let (c, o, err) = run_awkrs_stdin(
r#"BEGIN {
if (statvfs("/", v) == 0) print (v["f_bsize"] + 0 > 0)
else print 0
}"#,
"",
);
assert_eq!(c, 0, "stderr={err}");
assert_eq!(o.trim(), "1");
}
#[test]
fn fts_missing_path_returns_minus_one() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
n = fts("/no/such/path/awkrs_fts_x", a)
print n
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "-1");
}
#[test]
fn at_load_filefuncs_noop_runs_begin() {
let (c, o, _) = run_awkrs_stdin(
r#"@load "filefuncs"
BEGIN { print "ok" }"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "ok");
}
#[test]
fn debug_flag_emits_static_listing_on_stderr() {
let bin = env!("CARGO_BIN_EXE_awkrs");
let out = Command::new(bin)
.args(["-D", "-", "-e", "BEGIN { print 1 }"])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn");
assert_eq!(out.status.code(), Some(0));
let e = String::from_utf8_lossy(&out.stderr);
assert!(
e.contains("awkrs --debug") && e.contains("rules:"),
"stderr={e:?}"
);
}
#[test]
fn profile_flag_emits_wall_time_on_stderr() {
let bin = env!("CARGO_BIN_EXE_awkrs");
let out = Command::new(bin)
.args(["-p", "-", "-e", "BEGIN { x = 1 }"])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn");
assert_eq!(out.status.code(), Some(0));
let e = String::from_utf8_lossy(&out.stderr);
assert!(
e.contains("wall_seconds:") || e.contains("wall time"),
"stderr={e:?}"
);
}
#[test]
fn pretty_print_flag_emits_disclaimer_on_stdout() {
let bin = env!("CARGO_BIN_EXE_awkrs");
let out = Command::new(bin)
.args(["-o", "-", "-e", "BEGIN { z = 3 }"])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn");
assert_eq!(out.status.code(), Some(0));
let s = String::from_utf8_lossy(&out.stdout);
assert!(
s.contains("pretty-print") && s.contains("NOT gawk"),
"stdout={s:?}"
);
}
#[test]
fn dump_variables_flag_includes_user_global() {
let bin = env!("CARGO_BIN_EXE_awkrs");
let out = Command::new(bin)
.args(["-d", "-", "-e", "BEGIN { mydumpvar = 99 }"])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn");
assert_eq!(out.status.code(), Some(0));
let s = String::from_utf8_lossy(&out.stdout);
assert!(s.contains("mydumpvar") && s.contains("99"), "stdout={s:?}");
}
#[test]
fn typeof_regexp_literal_is_regexp() {
let (c, o, _) = run_awkrs_stdin(r#"BEGIN { r = @/foo/; print typeof(r) }"#, "");
assert_eq!(c, 0);
assert_eq!(o.trim(), "regexp");
}
#[test]
fn ignorecase_makes_regex_match_case_insensitive() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
IGNORECASE = 1
print ("abc" ~ /A/)
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "1");
}
#[test]
fn csv_mode_splits_quoted_comma_into_single_field() {
let (c, o, _) = run_awkrs_stdin_args(["-k"], r#"{ print NF, $2 }"#, "a,\"b,c\"\n");
assert_eq!(c, 0);
assert_eq!(o.trim(), "2 b,c");
}
#[test]
fn multiple_dash_e_sources_concatenate() {
let bin = env!("CARGO_BIN_EXE_awkrs");
let out = Command::new(bin)
.args(["-e", "BEGIN { q = 41 }", "-e", "BEGIN { print q + 1 }"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn");
assert_eq!(out.status.code(), Some(0));
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "42");
}
#[test]
fn at_load_time_extension_name_noop() {
let (c, o, _) = run_awkrs_stdin(
r#"@load "time"
BEGIN { print "t" }"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "t");
}
#[test]
fn at_load_readdir_extension_name_noop() {
let (c, o, _) = run_awkrs_stdin(
r#"@load "readdir"
BEGIN { print "r" }"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "r");
}
#[test]
fn symtab_two_globals_roundtrip() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
SYMTAB["aa"] = 10
SYMTAB["bb"] = 20
print aa, bb
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "10 20");
}
#[test]
fn exponentiation_unary_minus_binds_looser_than_pow() {
let (c, o, _) = run_awkrs_stdin(r#"BEGIN { print -2^2, (-2)^2 }"#, "");
assert_eq!(c, 0);
let parts: Vec<&str> = o.split_whitespace().collect();
assert_eq!(parts, vec!["-4", "4"]);
}
#[test]
fn match_accepts_regexp_constant_as_second_argument() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
r = @/[a-z]+/
print match("123abc", r), RSTART, RLENGTH
}"#,
"",
);
assert_eq!(c, 0);
let parts: Vec<&str> = o.split_whitespace().collect();
assert_eq!(parts.len(), 3);
assert_eq!(parts[0], "4");
assert_eq!(parts[1], "4");
assert_eq!(parts[2], "3");
}
#[test]
fn fieldwidths_splits_fields_by_widths() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN { FIELDWIDTHS = "2 3 1" }
{ print $1, $2, NF }"#,
"abcdef\n",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "ab cde 3");
}
#[test]
fn rs_empty_string_is_paragraph_mode() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN { RS = "" }
{ print NF, $1 }"#,
"a b\n\nc d\n",
);
assert_eq!(c, 0);
let lines: Vec<&str> = o.lines().collect();
assert_eq!(lines.len(), 2);
assert_eq!(lines[0].trim(), "2 a");
assert_eq!(lines[1].trim(), "2 c");
}
#[test]
fn assign_flag_v_visible_in_begin() {
let (c, o, _) = run_awkrs_stdin_args(["-v", "qq=42"], "BEGIN { print qq }", "");
assert_eq!(c, 0);
assert_eq!(o.trim(), "42");
}
#[test]
fn bignum_sprintf_integer_beyond_i64_without_double_rounding() {
let (c, o, _) = run_awkrs_stdin_args(
["-M"],
r#"BEGIN { print sprintf("%d", 9223372036854775807 + 1) }"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "9223372036854775808");
}
#[test]
fn non_decimal_flag_n_coerces_hex_in_numeric_context() {
let (c, o, _) = run_awkrs_stdin_args(["-n"], r#"BEGIN { x = "0xFF"; print x + 0 }"#, "");
assert_eq!(c, 0);
assert_eq!(o.trim(), "255");
}
#[test]
fn characters_as_bytes_flag_length_counts_utf8_bytes() {
let (c, o, _) = run_awkrs_stdin_args(["-b"], r#"BEGIN { print length("α") }"#, "");
assert_eq!(c, 0);
assert_eq!(o.trim(), "2");
}
#[test]
fn namespace_prefixes_unqualified_identifier() {
let (c, o, _) = run_awkrs_stdin(
"@namespace \"ns\"\nBEGIN { unqual = 5; print unqual }\n",
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "5");
}
#[test]
fn gsub_regexp_constant_replaces_matches() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
s = "abc"
r = @/[a-z]/
n = gsub(r, "X", s)
print n, s
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "3 XXX");
}
#[test]
fn beginfile_endfile_run_around_slurped_file() {
let dir = std::env::temp_dir();
let path = dir.join(format!("awkrs_bef_{}.txt", std::process::id()));
std::fs::write(&path, "row\n").unwrap();
let (c, o, e) = run_awkrs_file(
r#"BEGINFILE { print "BF" } { print $1 } ENDFILE { print "EF" }"#,
&path,
);
let _ = std::fs::remove_file(&path);
assert_eq!(c, 0, "stderr={e}");
assert_eq!(o, "BF\nrow\nEF\n");
}
#[test]
fn stat_missing_file_returns_minus_one() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN { print stat("/no/such/awkrs_stat_missing", a) }"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "-1");
}
#[test]
fn readfile_missing_file_sets_errno_nonempty() {
let (c, o, _) = run_awkrs_stdin(
r#"BEGIN {
readfile("/no/such/awkrs_rf_missing")
print (ERRNO != "")
}"#,
"",
);
assert_eq!(c, 0);
assert_eq!(o.trim(), "1");
}
#[test]
fn use_lc_numeric_printf_apostrophe_groups_integer() {
let (c, o, e) = run_awkrs_stdin_args_env(
["-N"],
r#"BEGIN { printf "%'d\n", 1234567 }"#,
"",
[(OsString::from("LC_ALL"), OsString::from("C"))],
);
assert_eq!(c, 0, "stderr={e}");
assert!(
o.contains("1") && o.contains("234") && o.contains("567"),
"stdout={o:?}"
);
}