Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions common/readutmp_nix.c.v
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
module common

// utmpxname is a glibc extension, declared in its <utmpx.h> 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 <utmp.h>
#include <utmpx.h>
#include <errno.h>
Expand Down
13 changes: 13 additions & 0 deletions common/readutmp_nix.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// The one glibc extension this module calls that <utmpx.h> 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
24 changes: 24 additions & 0 deletions common/testing/split_args.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
54 changes: 27 additions & 27 deletions src/cksum/cksum_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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}'
Expand All @@ -118,30 +118,30 @@ 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 ==
'7ea13b762c7b42138a17be9aaa8e71cbbdc8604750fb59e3d346a5715252bf09 ${test1_txt_path}${eol}'
}

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 ==
'SM3 (${test1_txt_path}) = b995b196231877a750f608e124d453461856e6f62b967f1bc2d0647e0e86e8eb${eol}'
}

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 ==
Expand All @@ -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}'
Expand All @@ -183,22 +183,22 @@ 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 ==
'MD5 (${test1_txt_path}) = m17yzP44VmmKZynzG54AcQ==${eol}'
}

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’')
Expand All @@ -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}'
Expand All @@ -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}'
Expand All @@ -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 == ''
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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}'
Expand All @@ -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')
Expand All @@ -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}'
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions src/comm/comm_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions src/cp/cp_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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('[')
Expand Down
8 changes: 4 additions & 4 deletions src/dircolors/dircolors_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
2 changes: 1 addition & 1 deletion src/fold/fold_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Loading
Loading