Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

w: Format time and get cmdline #41

Merged
merged 7 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ xattr = "1.3.1"
tempfile = "3.9.0"
rand = { version = "0.8", features = ["small_rng"] }
bytesize = "1.3.0"
time = { version = "0.3", features = ["formatting"] }
sylvestre marked this conversation as resolved.
Show resolved Hide resolved

[workspace.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
utmpx = "0.1"
Expand Down Expand Up @@ -75,6 +76,7 @@ tempfile = { workspace = true }
libc = { workspace = true }
rand = { workspace = true }
uucore = { workspace = true, features = ["entries", "process", "signals"] }
time = { workspace = true, features = ["formatting"] }

[target.'cfg(unix)'.dev-dependencies]
xattr = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions src/uu/w/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ categories = ["command-line-utilities"]
[dependencies]
uucore = { workspace = true, features = ["utmpx"] }
clap = { workspace = true }
time = { workspace = true, features = ["formatting"] }

['cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
utmpx = { workspace = true }
Expand Down
44 changes: 40 additions & 4 deletions src/uu/w/src/w.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

use clap::crate_version;
use clap::{Arg, ArgAction, Command};
use std::process;
use std::{fs, path::Path, process};
use time;
use uucore::utmpx::Utmpx;
use uucore::{error::UResult, format_usage, help_about, help_usage};

Expand All @@ -22,18 +23,28 @@ struct UserInfo {
command: String,
}

fn format_time(time: time::OffsetDateTime) -> Result<String, time::error::Format> {
let time_format = time::format_description::parse("[hour]:[minute]").unwrap();
time.format(&time_format)
}

fn fetch_cmdline(pid: i32) -> Result<String, std::io::Error> {
let cmdline_path = Path::new("/proc").join(pid.to_string()).join("cmdline");
fs::read_to_string(cmdline_path)
}

fn fetch_user_info() -> Result<Vec<UserInfo>, std::io::Error> {
let mut user_info_list = Vec::new();
for entry in Utmpx::iter_all_records() {
if entry.is_user_process() {
let user_info = UserInfo {
user: entry.user(),
terminal: entry.tty_device(),
login_time: format!("{}", entry.login_time()), // Needs formatting
login_time: format_time(entry.login_time()).unwrap_or_default(),
idle_time: String::new(), // Placeholder, needs actual implementation
jcpu: String::new(), // Placeholder, needs actual implementation
pcpu: String::new(), // Placeholder, needs actual implementation
command: String::new(), // Placeholder, needs actual implementation
command: fetch_cmdline(entry.pid()).unwrap_or_default(),
};
user_info_list.push(user_info);
}
Expand All @@ -50,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
match fetch_user_info() {
Ok(user_info) => {
if !no_header {
println!("USER\tTTY\t\tLOGIN@\t\tIDLE\tJCPU\tPCPU\tWHAT");
println!("USER\tTTY\tLOGIN@\tIDLE\tJCPU\tPCPU\tWHAT");
}
for user in user_info {
println!(
Expand Down Expand Up @@ -131,3 +142,28 @@ pub fn uu_app() -> Command {
.action(ArgAction::SetTrue),
)
}

#[cfg(test)]
mod tests {
use crate::{fetch_cmdline, format_time};
use std::{fs, path::Path, process};
use time::OffsetDateTime;

#[test]
fn test_format_time() {
let unix_epoc = OffsetDateTime::UNIX_EPOCH;
assert_eq!(format_time(unix_epoc).unwrap(), "00:00");
}

#[test]
// Get PID of current process and use that for cmdline testing
fn test_fetch_cmdline() {
// uucore's utmpx returns an i32, so we cast to that to mimic it.
let pid = process::id() as i32;
let path = Path::new("/proc").join(pid.to_string()).join("cmdline");
assert_eq!(
fs::read_to_string(path).unwrap(),
fetch_cmdline(pid).unwrap()
)
}
}
25 changes: 24 additions & 1 deletion tests/by-util/test_w.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// spell-checker:ignore (words) symdir somefakedir

use crate::common::util::TestScenario;
use std::path::Path;

#[test]
fn test_invalid_arg() {
Expand All @@ -17,5 +18,27 @@ fn test_no_header() {

let result = cmd.stdout_str();

assert!(!result.contains("USER\tTTY\t\tLOGIN@\t\tIDLE\tJCPU\tPCPU\tWHAT"));
assert!(!result.contains("USER\tTTY\tLOGIN@\tIDLE\tJCPU\tPCPU\tWHAT"));
}

#[test]
fn test_output_format() {
// Use no header to simplify testing
let cmd = new_ucmd!().arg("--no-header").succeeds();
let output_lines = cmd.stdout_str().lines();
// There is no guarantee that there will be a cmdline entry, but for testing purposes, we will assume so,
// since there will be one present in most cases.
for line in output_lines {
// We need to get rid of extra 0 terminators on strings, such as the cmdline, so we don't have characters at the end.
let line_vec: Vec<String> = line
.split_whitespace()
.map(|s| String::from(s.trim_end_matches('\0')))
.collect();
// Check the time formatting, this should be the third entry in list
// For now, we are just going to check that that length of time is 5 and it has a colon
assert!(line_vec[2].contains(":") && line_vec[2].chars().count() == 5);
// Check to make sure that cmdline is a path that exists.
// For now, cmdline will be in index 3, until the output is complete.
assert!(Path::new(line_vec.last().unwrap().as_str()).exists())
}
}
Loading