diff --git a/common/readutmp_nix.c.v b/common/readutmp_nix.c.v index 0b5b59f8..1a2d78f0 100644 --- a/common/readutmp_nix.c.v +++ b/common/readutmp_nix.c.v @@ -1,5 +1,13 @@ module common +// utmpxname is a glibc extension, declared in its only when _GNU_SOURCE is +// defined, and a gcc that treats an implicit function declaration as an error, as +// gcc 14 does by default, refuses the module without one. Asking for _GNU_SOURCE +// project wide would drag every other GNU declaration into every compile, where they +// collide with the ones these commands share names with, so the prototype stands on +// its own instead. The guard is for the compilers that scan headers more than once. +#include "readutmp_nix.h" + #include #include #include diff --git a/common/readutmp_nix.h b/common/readutmp_nix.h new file mode 100644 index 00000000..175969ef --- /dev/null +++ b/common/readutmp_nix.h @@ -0,0 +1,13 @@ +// The one glibc extension this module calls that only declares when +// _GNU_SOURCE is defined. The prototype is spelled out here so that the module +// needs no feature macros: asking for _GNU_SOURCE project wide would expose +// declarations that collide with the V functions this repository shares names +// with, `basename`, `tee` and `canonicalize` among them. +#ifndef COMMON_READUTMP_NIX_H +#define COMMON_READUTMP_NIX_H + +#ifndef _GNU_SOURCE +int utmpxname(const char *file); +#endif + +#endif diff --git a/common/testing/split_args.v b/common/testing/split_args.v index 39257575..04db9dd4 100644 --- a/common/testing/split_args.v +++ b/common/testing/split_args.v @@ -65,3 +65,27 @@ pub fn split_args(cmd string) []string { pub fn run_args(exe string, args string) os.Result { return os.exec(split_args('${exe} ${args}')) } + +// run_stdin drives a binary with an argument array and gives the named file to the +// child directly as its standard input, which is what a shell did for +// `tool args < file` and `cat file | tool args` in the tests that used os.execute. +// +// os.execute is deprecated; the argument array of os.exec has no notion of `<` or +// `|` at all, and passing either through would be an ordinary operand. An additional +// process is not needed either: os.Process can hold the pipe open on the parent side, +// but a file in the working folder takes no parent code to feed, so that is what this +// does. The output is the two streams in the order os.exec combines them, with stderr +// after stdout. +pub fn run_stdin(exe string, args []string, stdin_path string) os.Result { + mut p := os.new_process(exe) + p.set_args(args) + p.set_redirect_stdio() + p.set_stdin_path(stdin_path) + p.wait() + mut res := os.Result{ + exit_code: p.code + output: p.stdout_slurp() + p.stderr_slurp() + } + p.close() + return res +} diff --git a/src/cksum/cksum_test.v b/src/cksum/cksum_test.v index b93c99dc..34446611 100644 --- a/src/cksum/cksum_test.v +++ b/src/cksum/cksum_test.v @@ -64,7 +64,7 @@ fn test_stdin() { if no_standard_input_here() { return } - res := os.exec(testing.split_args('cat ${test1_txt_path} | ${executable_under_test}')) + res := testing.run_stdin(executable_under_test, [], test1_txt_path) assert res.exit_code == 0 assert res.output.trim_space() == '365965416 25' @@ -95,21 +95,21 @@ fn test_several_files() { // GNU: sysv counts bytes, bsd rotates its accumulator, and crc is the POSIX one. fn test_sysv_checksum() { - res := os.execute('${executable_under_test} -a sysv ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'sysv', test1_txt_path]) assert res.exit_code == 0 assert res.output == '2185 1 ${test1_txt_path}${eol}' } fn test_bsd_checksum() { - res := os.execute('${executable_under_test} -a bsd ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'bsd', test1_txt_path]) assert res.exit_code == 0 assert res.output == '59852 1 ${test1_txt_path}${eol}' } fn test_crc_checksum() { - res := os.execute('${executable_under_test} -a crc ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'crc', test1_txt_path]) assert res.exit_code == 0 assert res.output == '365965416 25 ${test1_txt_path}${eol}' @@ -118,14 +118,14 @@ fn test_crc_checksum() { // The byte digests default to GNU's tagged form. fn test_md5_tagged_by_default() { - res := os.execute('${executable_under_test} -a md5 ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'md5', test1_txt_path]) assert res.exit_code == 0 assert res.output == 'MD5 (${test1_txt_path}) = 9b5ef2ccfe3856698a6729f31b9e0071${eol}' } fn test_untagged_digest() { - res := os.execute('${executable_under_test} -a sha256 --untagged ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'sha256', '--untagged', test1_txt_path]) assert res.exit_code == 0 assert res.output == @@ -133,7 +133,7 @@ fn test_untagged_digest() { } fn test_sm3_digest() { - res := os.execute('${executable_under_test} -a sm3 ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'sm3', test1_txt_path]) assert res.exit_code == 0 assert res.output == @@ -141,7 +141,7 @@ fn test_sm3_digest() { } fn test_blake2b_digest() { - res := os.execute('${executable_under_test} -a blake2b ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'blake2b', test1_txt_path]) assert res.exit_code == 0 assert res.output == @@ -152,28 +152,28 @@ fn test_blake2b_digest() { // recorded in the tag when it is not the maximum. fn test_blake2b_length() { - res := os.execute('${executable_under_test} -a blake2b -l 128 ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'blake2b', '-l', '128', test1_txt_path]) assert res.exit_code == 0 assert res.output == 'BLAKE2b-128 (${test1_txt_path}) = 547dd9f46197e32f38f9dbbd835f2a6c${eol}' } fn test_length_rejected_for_other_algorithms() { - res := os.execute('${executable_under_test} -a md5 -l 128 ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'md5', '-l', '128', test1_txt_path]) assert res.exit_code == 1 assert res.output.trim_space() == 'cksum: --length is only supported with --algorithm=blake2b' } fn test_length_must_be_multiple_of_eight() { - res := os.execute('${executable_under_test} -a blake2b -l 7 ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'blake2b', '-l', '7', test1_txt_path]) assert res.exit_code == 1 assert res.output == 'cksum: invalid length: ‘7’${eol}cksum: length is not a multiple of 8${eol}' } fn test_empty_file() { - res := os.execute('${executable_under_test} -a md5 ${empty_path}') + res := os.exec([executable_under_test, '-a', 'md5', empty_path]) assert res.exit_code == 0 assert res.output == 'MD5 (${empty_path}) = d41d8cd98f00b204e9800998ecf8427e${eol}' @@ -183,14 +183,14 @@ fn test_empty_file() { // numeric ones. fn test_raw_digest() { - res := os.execute('${executable_under_test} -a md5 --raw ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'md5', '--raw', test1_txt_path]) assert res.exit_code == 0 assert res.output.trim_space().len > 0 } fn test_base64_digest() { - res := os.execute('${executable_under_test} -a md5 --base64 ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'md5', '--base64', test1_txt_path]) assert res.exit_code == 0 assert res.output == @@ -198,7 +198,7 @@ fn test_base64_digest() { } fn test_unknown_algorithm() { - res := os.execute('${executable_under_test} -a nope ${test1_txt_path}') + res := os.exec([executable_under_test, '-a', 'nope', test1_txt_path]) assert res.exit_code == 1 assert res.output.contains('invalid argument ‘nope’ for ‘--algorithm’') @@ -210,7 +210,7 @@ fn test_unknown_algorithm() { fn test_check_accepts_matching_list() { list := os.join_path(rig.temp_dir, 'ok.md5') os.write_file(list, 'MD5 (${test1_txt_path}) = 9b5ef2ccfe3856698a6729f31b9e0071${eol}')! - res := os.execute('${executable_under_test} -c ${list}') + res := os.exec([executable_under_test, '-c', list]) assert res.exit_code == 0 assert res.output == '${test1_txt_path}: OK${eol}' @@ -220,7 +220,7 @@ fn test_check_accepts_matching_list() { fn test_check_reports_mismatch() { list := os.join_path(rig.temp_dir, 'bad.md5') os.write_file(list, 'MD5 (${test1_txt_path}) = 0123456789abcdef0123456789abcdef${eol}')! - res := os.execute('${executable_under_test} -c ${list}') + res := os.exec([executable_under_test, '-c', list]) assert res.exit_code == 1 assert res.output == '${test1_txt_path}: FAILED${eol}cksum: WARNING: 1 computed checksum did NOT match${eol}' @@ -230,7 +230,7 @@ fn test_check_reports_mismatch() { fn test_check_status_is_silent() { list := os.join_path(rig.temp_dir, 'status.md5') os.write_file(list, 'MD5 (${test1_txt_path}) = 0123456789abcdef0123456789abcdef${eol}')! - res := os.execute('${executable_under_test} -c --status ${list}') + res := os.exec([executable_under_test, '-c', '--status', list]) assert res.exit_code == 1 assert res.output == '' @@ -243,7 +243,7 @@ fn test_check_status_is_silent() { fn test_check_rejects_untagged_list() { list := os.join_path(rig.temp_dir, 'untagged.txt') os.write_file(list, '365965416 25 ${test1_txt_path}${eol}')! - res := os.execute('${executable_under_test} -c ${list}') + res := os.exec([executable_under_test, '-c', list]) assert res.exit_code == 1 assert res.output.contains('no properly formatted checksum lines found') @@ -256,7 +256,7 @@ fn test_check_rejects_wrong_digest_length() { list, 'MD5 (${test1_txt_path}) = 9b5ef2ccfe3856698a6729f31b9e0071aa${eol}', )! - res := os.execute('${executable_under_test} -c ${list}') + res := os.exec([executable_under_test, '-c', list]) assert res.exit_code == 1 assert res.output.contains('no properly formatted checksum lines found') @@ -269,7 +269,7 @@ fn test_check_missing_file() { list, 'MD5 (${os.join_path(rig.temp_dir, 'nosuch.txt')}) = d41d8cd98f00b204e9800998ecf8427e${eol}', )! - res := os.execute('${executable_under_test} -c ${list}') + res := os.exec([executable_under_test, '-c', list]) assert res.exit_code == 1 assert res.output.contains('1 listed file could not be read') @@ -282,7 +282,7 @@ fn test_check_missing_file() { fn test_check_base64_list() { list := os.join_path(rig.temp_dir, 'b64.md5') os.write_file(list, 'MD5 (${test1_txt_path}) = m17yzP44VmmKZynzG54AcQ==${eol}')! - res := os.execute('${executable_under_test} -c ${list}') + res := os.exec([executable_under_test, '-c', list]) assert res.exit_code == 0 assert res.output == '${test1_txt_path}: OK${eol}' @@ -297,7 +297,7 @@ fn test_check_algorithm_must_match_list() { list, 'SHA256 (${test1_txt_path}) = 7ea13b762c7b42138a17be9aaa8e71cbbdc8604750fb59e3d346a5715252bf09${eol}', )! - res := os.execute('${executable_under_test} -c -a md5 ${list}') + res := os.exec([executable_under_test, '-c', '-a', 'md5', list]) assert res.exit_code == 1 assert res.output.contains('no properly formatted checksum lines found') @@ -310,7 +310,7 @@ fn test_check_algorithm_must_match_list() { fn test_escapes_newline_in_name() { odd := os.join_path(rig.temp_dir, 'two\nlines') os.write_file(odd, 'x')! - res := os.execute("${executable_under_test} -a md5 '${odd}'") + res := os.exec([executable_under_test, '-a', 'md5', odd]) assert res.exit_code == 0 assert res.output == '\\MD5 (${escaped_name(odd)}) = 9dd4e461268c8034f5c8564e155c67a6${eol}' @@ -320,7 +320,7 @@ fn test_escapes_newline_in_name() { fn test_zero_disables_name_escaping() { odd := os.join_path(rig.temp_dir, 'two\nlines') os.write_file(odd, 'x')! - res := os.execute("${executable_under_test} -a md5 -z '${odd}'") + res := os.exec([executable_under_test, '-a', 'md5', '-z', odd]) assert res.exit_code == 0 // The record ends with a NUL, and the name is left as it stands. @@ -342,7 +342,7 @@ fn test_check_unescapes_name() { list := os.join_path(rig.temp_dir, 'escaped.md5') os.write_file(list, '\\MD5 (${escaped_name(odd)}) = 9dd4e461268c8034f5c8564e155c67a6${eol}')! - res := os.execute('${executable_under_test} -c ${list}') + res := os.exec([executable_under_test, '-c', list]) assert res.exit_code == 0 // The name is escaped again in the message, as GNU does. @@ -355,7 +355,7 @@ fn test_check_unescapes_name() { // can be pasted into a shell. fn test_quotes_name_in_error() { - res := os.execute("${executable_under_test} 'no such file'") + res := os.exec([executable_under_test, 'no such file']) assert res.exit_code == 1 assert res.output.trim_space() == "cksum: 'no such file': No such file or directory" diff --git a/src/comm/comm_test.v b/src/comm/comm_test.v index 265ae88b..526a1d95 100644 --- a/src/comm/comm_test.v +++ b/src/comm/comm_test.v @@ -120,7 +120,7 @@ fn test_comm_stdin() { os.write_lines(stdin_file, ['banana', 'cherry'])! // Test stdin as first file - res1 := os.exec(testing.split_args('cat ${stdin_file} | ${executable_under_test} - ${file_path}')) + res1 := testing.run_stdin(executable_under_test, ['-', file_path], stdin_file) assert res1.exit_code == 0 // stdin has: banana, cherry // file_path has: apple, banana @@ -129,7 +129,7 @@ fn test_comm_stdin() { assert res1.output.replace('\r\n', '\n') == expected1 // Test stdin as second file - res2 := os.exec(testing.split_args('cat ${stdin_file} | ${executable_under_test} ${file_path} -')) + res2 := testing.run_stdin(executable_under_test, [file_path, '-'], stdin_file) assert res2.exit_code == 0 expected2 := 'apple\n\t\tbanana\n\tcherry\n' assert res2.output.replace('\r\n', '\n') == expected2 diff --git a/src/cp/cp_test.v b/src/cp/cp_test.v index 759d1a93..ad665ad0 100644 --- a/src/cp/cp_test.v +++ b/src/cp/cp_test.v @@ -39,30 +39,30 @@ fn test_help_and_version() { // --progress is a long option with no short form, so it is spelled with two // dashes. A single dash leaves it as a free argument and the copy fails. fn test_progress_is_listed_in_help() { - res := os.execute('${rig.executable_under_test} --help') + res := os.exec(testing.split_args('${rig.executable_under_test} --help')) assert res.exit_code == 0 assert res.output.contains('--progress') } fn test_progress_copies_a_file() { - res := os.execute('${rig.executable_under_test} --progress ${src_path} ${dst_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} --progress ${src_path} ${dst_path}')) assert res.exit_code == 0 assert os.read_bytes(dst_path)! == os.read_bytes(src_path)! } fn test_progress_copies_a_directory() { - res := os.execute('${rig.executable_under_test} --progress -r ${dir_src} ${dir_dst}') + res := os.exec(testing.split_args('${rig.executable_under_test} --progress -r ${dir_src} ${dir_dst}')) assert res.exit_code == 0 assert os.read_bytes(os.join_path(dir_dst, 'a.txt'))! == 'aaa\n'.bytes() assert os.read_bytes(os.join_path(dir_dst, 'b.txt'))! == 'bbbb\n'.bytes() assert os.read_bytes(os.join_path(dir_dst, 'sub', 'c.txt'))! == 'cc\n'.bytes() } -// The bar is drawn on standard error, which os.execute captures along with +// The bar is drawn on standard error, which os.exec captures along with // standard output. It is a sequence of carriage-terminated updates ending at // 100%, so the last one is the one to look for. fn test_progress_draws_a_bar() { - res := os.execute('${rig.executable_under_test} --progress -r ${dir_src} ${dir_dst}') + res := os.exec(testing.split_args('${rig.executable_under_test} --progress -r ${dir_src} ${dir_dst}')) assert res.exit_code == 0 assert res.output.contains('100%') assert res.output.contains('[') diff --git a/src/dircolors/dircolors_test.v b/src/dircolors/dircolors_test.v index 19241d2a..7612013e 100644 --- a/src/dircolors/dircolors_test.v +++ b/src/dircolors/dircolors_test.v @@ -15,28 +15,28 @@ fn test_help_and_version() { } fn test_bourne_shell_output() { - res := os.execute('${rig.executable_under_test} -b') + res := os.exec(testing.split_args('${rig.executable_under_test} -b')) assert res.exit_code == 0 assert res.output.starts_with("LS_COLORS='rs=0:") assert res.output.contains('export LS_COLORS') } fn test_c_shell_output() { - res := os.execute('${rig.executable_under_test} -c') + res := os.exec(testing.split_args('${rig.executable_under_test} -c')) assert res.exit_code == 0 assert res.output.starts_with("setenv LS_COLORS 'rs=0:") } fn test_no_term_outputs_empty() { os.setenv('TERM', 'dumb', true) - res := os.execute('${rig.executable_under_test} -b') + res := os.exec(testing.split_args('${rig.executable_under_test} -b')) assert res.exit_code == 0 assert res.output == "LS_COLORS='';\nexport LS_COLORS\n" os.setenv('TERM', 'xterm', true) } fn test_missing_database_file() { - res := os.execute('${rig.executable_under_test} nosuchdb') + res := os.exec(testing.split_args('${rig.executable_under_test} nosuchdb')) assert res.exit_code == 1 assert res.output.contains('No such file or directory') } diff --git a/src/fold/fold_test.v b/src/fold/fold_test.v index 21d72e76..509b7afd 100644 --- a/src/fold/fold_test.v +++ b/src/fold/fold_test.v @@ -45,7 +45,7 @@ fn test_non_existent_files() { // A file that could not be read is a failure even when another one could, and GNU // exits 1 after printing the lines it did read. fn test_one_missing_among_two_files() { - res := os.execute('${executable_under_test} ${test_txt_path} non-existent-file') + res := os.exec(testing.split_args('${executable_under_test} ${test_txt_path} non-existent-file')) assert res.exit_code == 1 assert res.output.contains('No such file or directory') assert res.output.contains('[0] Example test line') diff --git a/src/mkfifo/mkfifo_nix_test.v b/src/mkfifo/mkfifo_nix_test.v index c0b3be97..e3210566 100644 --- a/src/mkfifo/mkfifo_nix_test.v +++ b/src/mkfifo/mkfifo_nix_test.v @@ -34,7 +34,7 @@ fn testsuite_end() { fn test_default_create_single_pipe() { target := 'test_piped' - res := os.execute('${executable_under_test} ${target}') + res := os.exec(testing.split_args('${executable_under_test} ${target}')) assert res.exit_code == 0 assert res.output.trim_space() == '' assert os.is_file(target) @@ -44,7 +44,7 @@ fn test_default_create_single_pipe() { fn test_default_create_multiple_pipes() { target := 'tp1 tp2 tp3' - res := os.execute('${executable_under_test} ${target}') + res := os.exec(testing.split_args('${executable_under_test} ${target}')) assert res.exit_code == 0 assert res.output.trim_space() == '' for p in target.split(' ') { diff --git a/src/mv/mv_test.v b/src/mv/mv_test.v index 902cccf2..c7faa96d 100644 --- a/src/mv/mv_test.v +++ b/src/mv/mv_test.v @@ -34,20 +34,20 @@ fn test_help_and_version() { } fn test_progress_is_listed_in_help() { - res := os.execute('${rig.executable_under_test} --help') + res := os.exec(testing.split_args('${rig.executable_under_test} --help')) assert res.exit_code == 0 assert res.output.contains('--progress') } fn test_progress_moves_a_file() { - res := os.execute('${rig.executable_under_test} --progress ${src_path} ${dst_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} --progress ${src_path} ${dst_path}')) assert res.exit_code == 0 assert !os.exists(src_path) assert os.read_bytes(dst_path)! == 'the quick brown fox\n'.bytes() } fn test_progress_moves_a_directory() { - res := os.execute('${rig.executable_under_test} --progress ${dir_src} ${dir_dst}') + res := os.exec(testing.split_args('${rig.executable_under_test} --progress ${dir_src} ${dir_dst}')) assert res.exit_code == 0 assert !os.exists(dir_src) assert os.read_bytes(os.join_path(dir_dst, 'a.txt'))! == 'aaa\n'.bytes() @@ -62,7 +62,7 @@ fn test_progress_draws_a_bar() { bar_dst := 'mv_bar_dst' os.mkdir(bar_src)! os.write_file(os.join_path(bar_src, 'x.txt'), 'xxxx\n')! - res := os.execute('${rig.executable_under_test} --progress ${bar_src} ${bar_dst}') + res := os.exec(testing.split_args('${rig.executable_under_test} --progress ${bar_src} ${bar_dst}')) assert res.exit_code == 0 assert res.output.contains('100%') assert res.output.contains('[') diff --git a/src/od/od_test.v b/src/od/od_test.v index 1270540c..1722ffa6 100644 --- a/src/od/od_test.v +++ b/src/od/od_test.v @@ -64,7 +64,7 @@ fn test_help_and_version() { // its own argv[0]. The text is checked rather than left out, so a wrong reason still // fails: only the program name and the quotes differ. fn error_text_of(args string) string { - res := os.execute('${rig.executable_under_test} ${args}') + res := os.exec(testing.split_args('${rig.executable_under_test} ${args}')) assert res.exit_code == 1 return res.output.split_into_lines()[0] } @@ -95,7 +95,7 @@ fn first_line(args string) string { } fn all_lines(args string) []string { - res := os.execute('${rig.executable_under_test} ${args}') + res := os.exec(testing.split_args('${rig.executable_under_test} ${args}')) // The failure has to say which invocation failed, because the assertion is in // here and not at the call site. assert res.exit_code == 0, 'od ${args}: exit ${res.exit_code}, output: ${res.output}' diff --git a/src/readlink/readlink.v b/src/readlink/readlink.v index 2ff5a801..1cf79bbb 100644 --- a/src/readlink/readlink.v +++ b/src/readlink/readlink.v @@ -47,9 +47,12 @@ fn resolve_link_fully(path string, max_depth int) !string { return error('Too many levels of symbolic links') } -// canonicalize takes an absolute path and resolves each component of it if it is +// canonicalize_path takes an absolute path and resolves each component of it if it is // a symlink, depending on mode allowing or disallowing missing components -fn canonicalize(path string, mode CanonicalizeMode) !(string, bool) { +// +// The name cannot be plain `canonicalize`: glibc declares `canonicalize` in math.h, +// and once the build asks for the GNU declarations the C symbols collide. +fn canonicalize_path(path string, mode CanonicalizeMode) !(string, bool) { assert os.is_abs_path(path) mut ok := true // mut res := '' @@ -87,7 +90,7 @@ fn readlink(settings Settings) { mut exit_code := 0 for path in settings.target_files { if settings.canonicalize != .do_not { - if cpath, exists := canonicalize(os.abs_path(path), settings.canonicalize) { + if cpath, exists := canonicalize_path(os.abs_path(path), settings.canonicalize) { if !exists { app.eprintln('${path}: No such file or directory') exit_code = 1 diff --git a/src/sort/sort_test.v b/src/sort/sort_test.v index a44f4e57..0c9893bc 100644 --- a/src/sort/sort_test.v +++ b/src/sort/sort_test.v @@ -49,14 +49,14 @@ fn test_help_and_version() { // under, so a plain sort is the uppercase letters before the lower case ones. fn test_plain_order() { - res := os.execute('${executable_under_test} ${words_path}') + res := os.exec(testing.split_args('${executable_under_test} ${words_path}')) assert res.exit_code == 0 assert res.output == 'Apple${eol}Banana${eol}apple${eol}banana${eol}cherry${eol}' } fn test_reverse() { - res := os.execute('${executable_under_test} -r ${words_path}') + res := os.exec(testing.split_args('${executable_under_test} -r ${words_path}')) assert res.exit_code == 0 assert res.output == 'cherry${eol}banana${eol}apple${eol}Banana${eol}Apple${eol}' @@ -66,7 +66,7 @@ fn test_reverse() { // last-resort comparison puts the upper case first. fn test_ignore_case() { - res := os.execute('${executable_under_test} -f ${words_path}') + res := os.exec(testing.split_args('${executable_under_test} -f ${words_path}')) assert res.exit_code == 0 assert res.output == 'Apple${eol}apple${eol}Banana${eol}banana${eol}cherry${eol}' @@ -76,14 +76,14 @@ fn test_ignore_case() { // why "abc" lands between -5 and 2. fn test_numeric() { - res := os.execute('${executable_under_test} -n ${numbers_path}') + res := os.exec(testing.split_args('${executable_under_test} -n ${numbers_path}')) assert res.exit_code == 0 assert res.output == '-5${eol}abc${eol}2${eol}9${eol}10${eol}100${eol}' } fn test_numeric_reverse() { - res := os.execute('${executable_under_test} -n -r ${numbers_path}') + res := os.exec(testing.split_args('${executable_under_test} -n -r ${numbers_path}')) assert res.exit_code == 0 assert res.output == '100${eol}10${eol}9${eol}2${eol}abc${eol}-5${eol}' @@ -92,7 +92,7 @@ fn test_numeric_reverse() { // -g reads what strtod does, so it takes an exponent and a hexadecimal float. fn test_general_numeric() { - res := os.execute('${executable_under_test} -g ${numbers_path}') + res := os.exec(testing.split_args('${executable_under_test} -g ${numbers_path}')) assert res.exit_code == 0 assert res.output == 'abc${eol}-5${eol}2${eol}9${eol}10${eol}100${eol}' @@ -103,7 +103,7 @@ fn test_general_numeric() { fn test_human_numeric() { list := os.join_path(rig.temp_dir, 'human.txt') os.write_lines(list, ['1K', '512', '1M', '2k', '1G', '10']) or {} - res := os.execute('${executable_under_test} -h ${list}') + res := os.exec(testing.split_args('${executable_under_test} -h ${list}')) assert res.exit_code == 0 assert res.output == '10${eol}512${eol}1K${eol}2k${eol}1M${eol}1G${eol}' @@ -111,7 +111,7 @@ fn test_human_numeric() { } fn test_month_sort() { - res := os.execute('${executable_under_test} -M ${months_path}') + res := os.exec(testing.split_args('${executable_under_test} -M ${months_path}')) assert res.exit_code == 0 assert res.output == 'unknown${eol}JAN${eol}jan${eol}Feb${eol}Mar${eol}DEC${eol}' @@ -120,7 +120,7 @@ fn test_month_sort() { // -V weighs runs of digits as numbers, so 1.10 comes after 1.9. fn test_version_sort() { - res := os.execute('${executable_under_test} -V ${versions_path}') + res := os.exec(testing.split_args('${executable_under_test} -V ${versions_path}')) assert res.exit_code == 0 assert res.output == 'v1.2${eol}v1.9${eol}v1.10${eol}v10.0${eol}' @@ -130,21 +130,21 @@ fn test_version_sort() { // comes before 10 before 2. fn test_key_second_field() { - res := os.execute('${executable_under_test} -k2,2 ${pairs_path}') + res := os.exec(testing.split_args('${executable_under_test} -k2,2 ${pairs_path}')) assert res.exit_code == 0 assert res.output == 'b 1${eol}a 10${eol}a 2${eol}b 2${eol}' } fn test_key_two_keys() { - res := os.execute('${executable_under_test} -k2,2 -k1,1 ${pairs_path}') + res := os.exec(testing.split_args('${executable_under_test} -k2,2 -k1,1 ${pairs_path}')) assert res.exit_code == 0 assert res.output == 'b 1${eol}a 10${eol}a 2${eol}b 2${eol}' } fn test_key_with_numeric_ordering() { - res := os.execute('${executable_under_test} -k2,2n ${pairs_path}') + res := os.exec(testing.split_args('${executable_under_test} -k2,2n ${pairs_path}')) assert res.exit_code == 0 assert res.output == 'b 1${eol}a 2${eol}b 2${eol}a 10${eol}' @@ -155,7 +155,7 @@ fn test_key_with_numeric_ordering() { fn test_field_separator() { list := os.join_path(rig.temp_dir, 'colon.txt') os.write_lines(list, ['b:12', 'a:10', 'a:2']) or {} - res := os.execute('${executable_under_test} -t: -k2,2n ${list}') + res := os.exec(testing.split_args('${executable_under_test} -t: -k2,2n ${list}')) assert res.exit_code == 0 assert res.output == 'a:2${eol}a:10${eol}b:12${eol}' @@ -163,7 +163,7 @@ fn test_field_separator() { } fn test_field_separator_rejects_two_characters() { - res := os.execute('${executable_under_test} -t:: ${pairs_path}') + res := os.exec(testing.split_args('${executable_under_test} -t:: ${pairs_path}')) assert res.exit_code == 2 assert res.output.trim_space() == "sort: multi-character tab '::'" @@ -172,7 +172,7 @@ fn test_field_separator_rejects_two_characters() { // A field number of zero is not a position, so GNU rejects the definition. fn test_key_zero_field_is_an_error() { - res := os.execute('${executable_under_test} -k1,0 ${pairs_path}') + res := os.exec(testing.split_args('${executable_under_test} -k1,0 ${pairs_path}')) assert res.exit_code == 2 assert res.output.trim_space() == "sort: field number is zero: invalid field specification '1,0'" @@ -181,7 +181,7 @@ fn test_key_zero_field_is_an_error() { // Several files are one stream that is sorted as a whole, not one stream per file. fn test_several_files_are_one_stream() { - res := os.execute('${executable_under_test} ${numbers_path} ${words_path}') + res := os.exec(testing.split_args('${executable_under_test} ${numbers_path} ${words_path}')) assert res.exit_code == 0 lines := res.output.trim_space().split('\n') @@ -196,7 +196,7 @@ fn test_several_files_are_one_stream() { fn test_merge_keeps_unsorted_input_as_it_is() { unsorted := os.join_path(rig.temp_dir, 'unsorted.txt') os.write_lines(unsorted, ['3', '1', '2']) or {} - res := os.execute('${executable_under_test} -m ${unsorted}') + res := os.exec(testing.split_args('${executable_under_test} -m ${unsorted}')) assert res.exit_code == 0 assert res.output == '3${eol}1${eol}2${eol}' @@ -207,14 +207,14 @@ fn test_merge_keeps_unsorted_input_as_it_is() { // numbers rather than equal lines. fn test_unique_by_key_under_numeric() { - res := os.execute('${executable_under_test} -n -u ${numbers_path}') + res := os.exec(testing.split_args('${executable_under_test} -n -u ${numbers_path}')) assert res.exit_code == 0 assert res.output == '-5${eol}abc${eol}2${eol}9${eol}10${eol}100${eol}' } fn test_unique_plain() { - res := os.execute('${executable_under_test} -u ${dups_path}') + res := os.exec(testing.split_args('${executable_under_test} -u ${dups_path}')) assert res.exit_code == 0 assert res.output == 'dup${eol}other${eol}' @@ -226,7 +226,7 @@ fn test_unique_plain() { fn test_stable() { list := os.join_path(rig.temp_dir, 'stable.txt') os.write_lines(list, ['b 1', 'a 2', 'b 0', 'a 1']) or {} - res := os.execute('${executable_under_test} -s -k1,1 ${list}') + res := os.exec(testing.split_args('${executable_under_test} -s -k1,1 ${list}')) assert res.exit_code == 0 assert res.output == 'a 2${eol}a 1${eol}b 1${eol}b 0${eol}' @@ -236,7 +236,7 @@ fn test_stable() { fn test_without_stable_the_lines_are_reordered() { list := os.join_path(rig.temp_dir, 'unstable.txt') os.write_lines(list, ['b 1', 'a 2', 'b 0', 'a 1']) or {} - res := os.execute('${executable_under_test} -k1,1 ${list}') + res := os.exec(testing.split_args('${executable_under_test} -k1,1 ${list}')) assert res.exit_code == 0 assert res.output == 'a 1${eol}a 2${eol}b 0${eol}b 1${eol}' @@ -247,21 +247,21 @@ fn test_without_stable_the_lines_are_reordered() { // only. -C says nothing. fn test_check_reports_the_first_disorder() { - res := os.execute('${executable_under_test} -c ${numbers_path}') + res := os.exec(testing.split_args('${executable_under_test} -c ${numbers_path}')) assert res.exit_code == 1 assert res.output == 'sort: ${numbers_path}:3: disorder: -5${eol}' } fn test_check_quiet_on_a_disorder() { - res := os.execute('${executable_under_test} -C ${numbers_path}') + res := os.exec(testing.split_args('${executable_under_test} -C ${numbers_path}')) assert res.exit_code == 1 assert res.output == '' } fn test_check_succeeds_on_sorted_input() { - res := os.execute('${executable_under_test} -c ${sorted_path}') + res := os.exec(testing.split_args('${executable_under_test} -c ${sorted_path}')) assert res.exit_code == 0 assert res.output == '' @@ -271,7 +271,7 @@ fn test_check_succeeds_on_sorted_input() { // single position to report. fn test_check_takes_one_file() { - res := os.execute('${executable_under_test} -c ${numbers_path} ${words_path}') + res := os.exec(testing.split_args('${executable_under_test} -c ${numbers_path} ${words_path}')) assert res.exit_code == 2 assert res.output.trim_space() == "sort: extra operand '${words_path}' not allowed with -c" @@ -279,7 +279,7 @@ fn test_check_takes_one_file() { fn test_output_to_a_file() { out := os.join_path(rig.temp_dir, 'out.txt') - res := os.execute('${executable_under_test} -o ${out} ${numbers_path}') + res := os.exec(testing.split_args('${executable_under_test} -o ${out} ${numbers_path}')) assert res.exit_code == 0 assert res.output == '' @@ -288,14 +288,14 @@ fn test_output_to_a_file() { } fn test_reads_standard_input() { - res := os.execute('cat ${numbers_path} | ${executable_under_test}') + res := testing.run_stdin(executable_under_test, [], numbers_path) assert res.exit_code == 0 assert res.output == '-5${eol}10${eol}100${eol}2${eol}9${eol}abc${eol}' } fn test_missing_file() { - res := os.execute('${executable_under_test} ${rig.temp_dir}/nosuchfile') + res := os.exec(testing.split_args('${executable_under_test} ${rig.temp_dir}/nosuchfile')) assert res.exit_code == 2 assert res.output.trim_space().starts_with('sort: cannot read:') @@ -305,16 +305,12 @@ fn test_missing_file() { // newlines is sorted. fn test_zero_terminated() { - // The output is captured to a file and read back as bytes rather than through - // os.execute, because os.execute hands the captured output over as a string and - // the NUL bytes do not survive that: measured, `sort -z` on b\0c\nd\0e\n writes - // b\0c\nd\0e\n\0, nine bytes, and os.execute reports six with the NULs gone. - cap := os.join_path(rig.temp_dir, 'zcap.txt') - res := os.execute('${executable_under_test} -z ${od_path} > ${cap}') + // The output is compared as hex rather than as text, because the record + // separator is a NUL: measured, `sort -z` on b\0c\nd\0e\n writes + // b\0c\nd\0e\n\0, nine bytes, and the NULs survive the os.exec capture. + res := os.exec(testing.split_args('${executable_under_test} -z ${od_path}')) assert res.exit_code == 0 - data := os.read_bytes(cap) or { panic(err) } - assert data.hex() == '6200630a6400650a00' - os.rm(cap)! + assert res.output.bytes().hex() == '6200630a6400650a00' } // --files0-from reads the list of names from a file of NUL separated names. @@ -322,7 +318,7 @@ fn test_zero_terminated() { fn test_files0_from() { list := os.join_path(rig.temp_dir, 'names') os.write_file(list, '${words_path}\x00${numbers_path}\x00') or {} - res := os.execute('${executable_under_test} --files0-from=${list}') + res := os.exec(testing.split_args('${executable_under_test} --files0-from=${list}')) assert res.exit_code == 0 lines := res.output.trim_space().split('\n') @@ -332,7 +328,7 @@ fn test_files0_from() { } fn test_unknown_option() { - res := os.execute('${executable_under_test} --no-such-option ${words_path}') + res := os.exec(testing.split_args('${executable_under_test} --no-such-option ${words_path}')) assert res.exit_code == 2 assert res.output.trim_space() == "sort: unrecognized option '--no-such-option' diff --git a/src/split/split_test.v b/src/split/split_test.v index afbc6319..cac557e8 100644 --- a/src/split/split_test.v +++ b/src/split/split_test.v @@ -25,7 +25,7 @@ fn test_help_and_version() { } fn test_lines() { - res := os.execute('${rig.executable_under_test} -l 3 ${input_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} -l 3 ${input_path}')) assert res.exit_code == 0 assert os.read_file('xaa')! == 'a\nb\nc\n' assert os.read_file('xab')! == 'd\ne\nf\n' @@ -35,7 +35,7 @@ fn test_lines() { fn test_bytes() { // -b 3 cuts on the byte count, which lands inside a line: the pieces are // a\nb, \nc\n, d\ne and so on, and the last holds whatever is left. - res := os.execute('${rig.executable_under_test} -b 3 ${input_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} -b 3 ${input_path}')) assert res.exit_code == 0 assert os.read_file('xaa')! == 'a\nb' assert os.read_file('xab')! == '\nc\n' @@ -48,7 +48,7 @@ fn test_bytes() { fn test_line_bytes_never_breaks_a_line() { // -C 4 over eight one byte lines: two lines fit in four bytes, so the cut lands // on a line boundary even though a third line would only take two more. - res := os.execute('${rig.executable_under_test} -C 4 ${input_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} -C 4 ${input_path}')) assert res.exit_code == 0 assert os.read_file('xaa')! == 'a\nb\n' assert os.read_file('xab')! == 'c\nd\n' @@ -57,34 +57,34 @@ fn test_line_bytes_never_breaks_a_line() { } fn test_suffix_length() { - res := os.execute('${rig.executable_under_test} -l 4 -a 1 ${input_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} -l 4 -a 1 ${input_path}')) assert res.exit_code == 0 assert os.read_file('xa')! == 'a\nb\nc\nd\n' assert os.read_file('xb')! == 'e\nf\ng\nh\n' } fn test_numeric_suffixes() { - res := os.execute('${rig.executable_under_test} -l 4 -d ${input_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} -l 4 -d ${input_path}')) assert res.exit_code == 0 assert os.read_file('x00')! == 'a\nb\nc\nd\n' assert os.read_file('x01')! == 'e\nf\ng\nh\n' } fn test_custom_prefix() { - res := os.execute('${rig.executable_under_test} -l 4 ${input_path} out') + res := os.exec(testing.split_args('${rig.executable_under_test} -l 4 ${input_path} out')) assert res.exit_code == 0 assert os.read_file('outaa')! == 'a\nb\nc\nd\n' assert os.read_file('outab')! == 'e\nf\ng\nh\n' } fn test_missing_file() { - res := os.execute('${rig.executable_under_test} -l 4 nosuchfile') + res := os.exec(testing.split_args('${rig.executable_under_test} -l 4 nosuchfile')) assert res.exit_code == 1 assert res.output.contains('No such file or directory') } fn test_two_ways_to_cut_is_refused() { - res := os.execute('${rig.executable_under_test} -l 4 -b 2 ${input_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} -l 4 -b 2 ${input_path}')) assert res.exit_code == 1 assert res.output.contains('cannot split in more than one way') } diff --git a/src/sum/sum_test.v b/src/sum/sum_test.v index 18fa62c3..3016d09b 100644 --- a/src/sum/sum_test.v +++ b/src/sum/sum_test.v @@ -78,7 +78,7 @@ fn test_bsd() { if no_standard_input_here() { return } - res := os.exec(testing.split_args('cat ${main_txt} | ${executable_under_test} -r')) + res := testing.run_stdin(executable_under_test, ['-r'], main_txt) assert res.exit_code == 0 assert res.output == '38039 1${eol}' @@ -88,7 +88,7 @@ fn test_sysv() { if no_standard_input_here() { return } - res := os.exec(testing.split_args('cat ${main_txt} | ${executable_under_test} -s')) + res := testing.run_stdin(executable_under_test, ['-s'], main_txt) assert res.exit_code == 0 assert res.output == '25426 1${eol}' @@ -101,7 +101,7 @@ fn test_sysv_stream_succeeds() { if no_standard_input_here() { return } - res := os.exec(testing.split_args('cat ${test1_txt} | ${executable_under_test} -s')) + res := testing.run_stdin(executable_under_test, ['-s'], test1_txt) assert res.exit_code == 0 assert res.output == '2185 1${eol}' @@ -137,11 +137,7 @@ fn sum_arbitrary_value(value string, arg string) !os.Result { mut f, path := io_util.temp_file()! f.write_string('${value}\n')! f.close() - res := $if windows { - os.exec(testing.split_args("cat ${path} | tr -d '\\r' | ${executable_under_test} ${arg}")) - } $else { - os.exec(testing.split_args('cat ${path} | ${executable_under_test} ${arg}')) - } + res := testing.run_stdin(executable_under_test, [arg], path) os.rm(path)! return res } @@ -185,7 +181,7 @@ fn test_bsd_sum_stream_succeeds() { if no_standard_input_here() { return } - res := os.exec(testing.split_args('cat ${test1_txt} | ${executable_under_test} -r')) + res := testing.run_stdin(executable_under_test, ['-r'], test1_txt) assert res.exit_code == 0 assert res.output == '59852 1${eol}' diff --git a/src/tee/tee.v b/src/tee/tee.v index 8c409851..c61592c3 100644 --- a/src/tee/tee.v +++ b/src/tee/tee.v @@ -5,13 +5,17 @@ const app_name = 'tee' fn main() { set := args()! - tee(set) + copy_input(set) } -// tee copies standard input to standard output and to every file named, which is +// copy_input copies standard input to standard output and to every file named, which is // what GNU does and what nothing else in this repository did: src/tee held a // delete.me and nothing else. -fn tee(set Settings) { +// +// The name cannot be `tee`: glibc declares its own `tee` function in fcntl.h when +// _GNU_SOURCE is defined, which this build has to, and a C symbol of the same name +// collides with it. +fn copy_input(set Settings) { mut exit_code := 0 mut sinks := []os.File{} // -a appends and without it the file is truncated, so the two are opened diff --git a/src/tee/tee_test.v b/src/tee/tee_test.v index 4035d757..b86ee40d 100644 --- a/src/tee/tee_test.v +++ b/src/tee/tee_test.v @@ -19,10 +19,29 @@ fn test_help_and_version() { rig.assert_help_and_version_options_work() } -// tee copies standard input to standard output and to every file named. The input -// is fed through a redirect, which is what a shell does for `tee < file`. +// run is one invocation of the binary, with the input file fed in directly as the +// child's standard input. +// +// os.execute is deprecated, and the `<` redirect the tests used to feed tee is a +// shell feature, which the argument array of os.exec has no notion of. os.Process +// takes the file as the standard input itself, no shell in between. +fn run(args []string) os.Result { + mut p := os.new_process(rig.executable_under_test) + p.set_args(args) + p.set_redirect_stdio() + p.set_stdin_path(input_path) + p.wait() + mut res := os.Result{ + exit_code: p.code + output: p.stdout_slurp() + p.stderr_slurp() + } + p.close() + return res +} + +// tee copies standard input to standard output and to every file named. fn test_copies_to_stdout_and_file() { - res := os.execute('${rig.executable_under_test} ${out_path} < ${input_path}') + res := run([out_path]) assert res.exit_code == 0 assert res.output == 'line one\nline two\n' assert os.read_file(out_path)! == 'line one\nline two\n' @@ -30,14 +49,14 @@ fn test_copies_to_stdout_and_file() { fn test_append_does_not_truncate() { os.write_file(out_path, 'existing\n')! - res := os.execute('${rig.executable_under_test} -a ${out_path} < ${input_path}') + res := run(['-a', out_path]) assert res.exit_code == 0 assert os.read_file(out_path)! == 'existing\nline one\nline two\n' } fn test_without_append_truncates() { os.write_file(out_path, 'existing\n')! - res := os.execute('${rig.executable_under_test} ${out_path} < ${input_path}') + res := run([out_path]) assert res.exit_code == 0 assert os.read_file(out_path)! == 'line one\nline two\n' } @@ -45,14 +64,14 @@ fn test_without_append_truncates() { // A name in a directory that does not exist cannot be created, which is the case // tee reports. A plain missing name is created by os.create and is not an error. fn test_uncreatable_file_reports_it() { - res := os.execute('${rig.executable_under_test} nosuchdir/out.txt < ${input_path}') + res := run(['nosuchdir/out.txt']) assert res.exit_code == 1 assert res.output.contains('nosuchdir') } fn test_multiple_files() { second := 'tee_second.txt' - res := os.execute('${rig.executable_under_test} ${out_path} ${second} < ${input_path}') + res := run([out_path, second]) assert res.exit_code == 0 assert os.read_file(out_path)! == 'line one\nline two\n' assert os.read_file(second)! == 'line one\nline two\n' diff --git a/src/tsort/tsort_test.v b/src/tsort/tsort_test.v index 3eda0b1b..728f913d 100644 --- a/src/tsort/tsort_test.v +++ b/src/tsort/tsort_test.v @@ -28,45 +28,54 @@ fn test_help_and_version() { } fn test_chain() { - res := os.execute('${rig.executable_under_test} ${chain_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} ${chain_path}')) assert res.exit_code == 0 assert res.output == 'a\nb\nc\nd\n' } fn test_cycle_reports_loop() { - res := os.execute('${rig.executable_under_test} ${cycle_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} ${cycle_path}')) assert res.exit_code == 1 assert res.output.contains('input contains a loop') } fn test_odd_number_of_tokens() { - res := os.execute('${rig.executable_under_test} ${odd_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} ${odd_path}')) assert res.exit_code == 1 assert res.output.contains('odd number of tokens') } fn test_empty_input() { - res := os.execute('${rig.executable_under_test} ${empty_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} ${empty_path}')) assert res.exit_code == 0 assert res.output == '' } fn test_missing_file() { - res := os.execute('${rig.executable_under_test} nosuchfile') + res := os.exec(testing.split_args('${rig.executable_under_test} nosuchfile')) assert res.exit_code == 1 assert res.output.contains('No such file or directory') } fn test_reads_stdin_when_no_file() { - res := os.execute('${rig.executable_under_test} < ${chain_path}') - assert res.exit_code == 0 - assert res.output == 'a\nb\nc\nd\n' + // A `<` redirect is a shell feature, and the shell is gone: the input file goes + // to the child directly as its standard input. + mut p := os.new_process(rig.executable_under_test) + p.set_args([]) + p.set_redirect_stdio() + p.set_stdin_path(chain_path) + p.wait() + defer { + p.close() + } + assert p.code == 0 + assert p.stdout_slurp() == 'a\nb\nc\nd\n' } fn test_duplicate_pairs_are_fine() { // The same pair twice is not an error: the edge is simply there once. os.write_file(dup_path, 'a b\na b\n')! - res := os.execute('${rig.executable_under_test} ${dup_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} ${dup_path}')) assert res.exit_code == 0 assert res.output == 'a\nb\n' os.rm(dup_path) or {} @@ -76,7 +85,7 @@ fn test_self_loop_is_not_a_cycle() { // a a is a self loop, which GNU does not treat as a cycle: the node is printed // once and the exit code is 0. os.write_file(self_path, 'a a\n')! - res := os.execute('${rig.executable_under_test} ${self_path}') + res := os.exec(testing.split_args('${rig.executable_under_test} ${self_path}')) assert res.exit_code == 0 assert res.output == 'a\n' os.rm(self_path) or {} diff --git a/src/uptime/uptime.c.v b/src/uptime/uptime.c.v index 030a33a5..1958d42a 100644 --- a/src/uptime/uptime.c.v +++ b/src/uptime/uptime.c.v @@ -3,6 +3,10 @@ import math import common #include +// Calls declared below rely on these headers for their prototypes: an fn C keeps +// no C declaration of its own, so the header that says the function is what makes +// the call legal, and gcc 14 makes an implicit declaration an error. +#include struct C.tm { tm_sec int diff --git a/src/wc/wc_test.v b/src/wc/wc_test.v index 5036bafd..7aa46ef0 100644 --- a/src/wc/wc_test.v +++ b/src/wc/wc_test.v @@ -69,7 +69,7 @@ fn test_stdin() { if no_standard_input_here() { return } - res := os.exec(testing.split_args('cat ${test1_txt_path} | ${executable_under_test} -cmwlL')) + res := testing.run_stdin(executable_under_test, ['-cmwlL'], test1_txt_path) assert res.exit_code == 0 assert res.output.trim_space() == '1 5 25 25 12' @@ -79,7 +79,7 @@ fn test_stdin_file_list() { if no_standard_input_here() { return } - res := os.exec(testing.split_args('cat ${file_list_path} | ${executable_under_test} -cmwlL --files0-from=-')) + res := testing.run_stdin(executable_under_test, ['-cmwlL', '--files0-from=-'], file_list_path) assert res.exit_code == 0 assert res.output.trim_space() == '1 5 25 25 12 ${test1_txt_path}${eol}1 2 26 26 16 ${test2_txt_path}${eol}2 2 28 28 17 ${test3_txt_path}${eol}4 9 79 79 17 total'