Skip to content

Commit

Permalink
Add users util (#139)
Browse files Browse the repository at this point in the history
* Put eol() in common

* Add assert_same_exit_code() to TestRig

* Initial users util

* Return in sorted order

* Move pointer indexing into unsafe block

* Fix test for Windows

* Add more detail for some errors to TestRig

* v fmt

* Nicer small diff reporting in same_results

* Correct call to eprintln_small_diff

* No eol if empty result returned

* Fix quotes in extra operand error
  • Loading branch information
syrmel authored Feb 12, 2024
1 parent bfb49ed commit 045b6e4
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 11 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ count below and mark it as done in this README.md. Thanks!
GNU coreutils. They are not 100% compatiable. If you encounter different behaviors,
compare against the true GNU coreutils version on the Linux-based tests first.

## Completed (52/109)
## Completed (53/109)

| Done | Cmd | Descripton |
| :-----: | --------- | ------------------------------------------------ |
Expand Down Expand Up @@ -153,7 +153,7 @@ compare against the true GNU coreutils version on the Linux-based tests first.
| ✓ | uniq | Uniquify files |
| ✓ | unlink | Remove files via the unlink syscall |
| ✓ | uptime | Print system uptime and load |
| | users | Print login names of users currently logged in |
| ✓ | users | Print login names of users currently logged in |
| | vdir | Verbosely list directory contents |
| ✓ | wc | Print newline, word, and byte counts |
| | who | Print who is currently logged in |
Expand Down
10 changes: 10 additions & 0 deletions common/common.v
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,13 @@ pub fn (app CoreutilInfo) make_flag_parser(args []string) &flag.FlagParser {
fp.description(app.description)
return fp
}

@[inline]
pub fn eol() string {
$if windows {
// WinOS => CRLF
return '\r\n'
}
// POSIX => LF
return '\n'
}
4 changes: 3 additions & 1 deletion common/readutmp_nix.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ pub enum ReadUtmpOptions {
// readutmp.h : IS_USER_PROCESS(U)
pub fn is_user_process(u &C.utmpx) bool {
// C.USER_PROCESS = 7
return !isnil(u.ut_user[0]) && u.ut_type == C.USER_PROCESS
unsafe {
return !isnil(u.ut_user[0]) && u.ut_type == C.USER_PROCESS
}
}

fn desirable_utmp_entry(u &C.utmpx, options ReadUtmpOptions) bool {
Expand Down
8 changes: 2 additions & 6 deletions common/testing/testing.v
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module testing

import common
import os
import regex

Expand Down Expand Up @@ -225,10 +226,5 @@ pub fn check_dir_exists(d string) bool {
}

pub fn output_eol() string {
$if windows {
// WinOS => CRLF
return '\r\n'
}
// POSIX => LF
return '\n'
return common.eol()
}
25 changes: 23 additions & 2 deletions common/testing/testrig.v
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import common
import os
import regex

const small_diff_size = 64

// TestRig contains the relevant scaffolding for tests to avoid boilerplate in
// the individual <util>_test.v files
pub struct TestRig {
Expand Down Expand Up @@ -119,6 +121,17 @@ pub fn (rig TestRig) call_new(args string) os.Result {
return os.execute('${rig.executable_under_test} ${args}')
}

// print_small_diff eprints only differing small results that differ
// usually just vary by newlines or NULs
fn eprintln_small_diff(a string, b string) {
if a != b && (a.len < testing.small_diff_size && b.len < testing.small_diff_size) {
eprintln('Output 1: [${a}] (${a.len} bytes)')
eprintln(' - bytes: ${a.bytes()}')
eprintln('Output 2: [${b}] (${b.len} bytes)')
eprintln(' - bytes: ${b.bytes()}')
}
}

pub fn (rig TestRig) assert_same_results(args string) {
cmd1_res := rig.call_orig(args)
cmd2_res := rig.call_new(args)
Expand Down Expand Up @@ -148,7 +161,8 @@ pub fn (rig TestRig) assert_same_results(args string) {
if gnu_coreutils_installed {
// aim for 1:1 output compatibility:
assert cmd1_res.exit_code == cmd2_res.exit_code
assert cmd1_output == cmd2_output
eprintln_small_diff(cmd1_output, cmd2_output)
assert cmd1_output == cmd2_output, '${cmd1_output.len} bytes vs. ${cmd2_output.len} bytes'
}

match rig.util {
Expand Down Expand Up @@ -205,7 +219,14 @@ pub fn (rig TestRig) assert_same_results(args string) {
}
}
assert cmd1_res.exit_code == cmd2_res.exit_code
assert noutput1 == noutput2
eprintln_small_diff(noutput1, noutput2)
assert noutput1 == noutput2, '${noutput1.len} bytes vs. ${noutput2.len} bytes'
}

pub fn (rig TestRig) assert_same_exit_code(args string) {
cmd1_res := rig.call_orig(args)
cmd2_res := rig.call_new(args)
assert cmd1_res.exit_code == cmd2_res.exit_code
}

pub fn (rig TestRig) assert_help_and_version_options_work() {
Expand Down
Empty file removed src/users/delete.me
Empty file.
23 changes: 23 additions & 0 deletions src/users/users.c.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import common

pub struct C.utmpx {
ut_type i16 // Type of login.
ut_pid int // Process ID of login process.
ut_line &char // Devicename.
ut_id &char // Inittab ID.
ut_user &char // Username.
}

fn utmp_users(filename &char) []string {
mut utmp_buf := []C.utmpx{}
mut users := []string{}
common.read_utmp(filename, mut utmp_buf, .user_process)
unsafe {
for u in utmp_buf {
users << cstring_to_vstring(u.ut_user)
}
}
// Obtain sorted order as GNU coreutils
users.sort()
return users
}
39 changes: 39 additions & 0 deletions src/users/users.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import common
import os

const app = common.CoreutilInfo{
name: 'users'
description: 'print login names of users currently logged in'
}

// Settings for Utility: users
struct Settings {
mut:
input_file &char = ''.str
}

fn users(settings Settings) {
users := utmp_users(settings.input_file).join(' ')
print(users)
if users != '' {
print(common.eol())
}
}

fn args() Settings {
mut fp := app.make_flag_parser(os.args)
mut st := Settings{}
mut rem_pars := fp.remaining_parameters()
if rem_pars.len == 0 {
st.input_file = common.utmp_file_charptr
} else if rem_pars.len == 1 {
st.input_file = rem_pars[0].str
} else if rem_pars.len > 1 {
app.quit(message: "extra operand '${rem_pars[1]}'", show_help_advice: true)
}
return st
}

fn main() {
users(args())
}
41 changes: 41 additions & 0 deletions src/users/users_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import common
import common.testing

const supported_platform = $if windows {
false
} $else {
true
} // No utmp in Windows
const rig = testing.prepare_rig(util: 'users', is_supported_platform: supported_platform)

fn testsuite_begin() {
rig.assert_platform_util()
}

fn test_help_and_version() {
if !supported_platform {
return
}
rig.assert_help_and_version_options_work()
}

fn test_compare() {
if !supported_platform {
return
}
rig.assert_same_results('')
rig.assert_same_results('does_not_exist')
// // Don't even try to compile this for Windows
$if !windows {
unsafe { rig.assert_same_results(cstring_to_vstring(common.utmp_file_charptr)) }
unsafe { rig.assert_same_results(cstring_to_vstring(common.wtmp_file_charptr)) }
}
}

fn test_call_errors() {
if !supported_platform {
return
}
rig.assert_same_exit_code('-x')
rig.assert_same_results('a b c')
}

0 comments on commit 045b6e4

Please sign in to comment.