From 13e75081e6b3f4d6b78b74f1d69b37779844a18b Mon Sep 17 00:00:00 2001 From: Max van Ling Date: Mon, 1 Apr 2024 14:21:30 +0200 Subject: [PATCH 1/5] watch: interval implementation --- src/uu/watch/src/watch.rs | 53 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/uu/watch/src/watch.rs b/src/uu/watch/src/watch.rs index 7d07594d..99b4389e 100644 --- a/src/uu/watch/src/watch.rs +++ b/src/uu/watch/src/watch.rs @@ -3,16 +3,54 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use std::io::{Error, ErrorKind}; use clap::crate_version; use clap::{Arg, Command}; use std::process::{Command as SystemCommand, Stdio}; use std::thread::sleep; use std::time::Duration; use uucore::{error::UResult, format_usage, help_about, help_usage}; +use std::num::{ParseIntError}; const ABOUT: &str = help_about!("watch.md"); const USAGE: &str = help_usage!("watch.md"); +fn parse_interval(input: &str) -> Result { + // Find index where to split string into seconds and nanos + let index = match input.find(|c: char| c == ',' || c == '.') { + Some(index) => index, + None => { + let seconds: u64 = input.parse()?; + return Ok(Duration::new(seconds, 0)); + } + }; + + // If the seconds string is empty, set seconds to 0 + let seconds: u64 = if index > 0 { input[..index].parse()? } else { 0 }; + + let nanos_string = &input[index + 1..]; + let nanos: u32 = match nanos_string.len() { + // If nanos string is empty, set nanos to 0 + 0 => 0, + 1..=9 => { + let nanos: u32 = nanos_string.parse()?; + nanos * 10u32.pow((9 - nanos_string.len()) as u32) + } + _ => { + // This parse is used to validate if the rest of the string is indeed numeric + if nanos_string.find(|c: char| !c.is_numeric()).is_some() { + "a".parse::()?; + } + // We can have only 9 digits of accuracy, trim the rest + nanos_string[..9].parse()? + } + }; + + let duration = Duration::new(seconds, nanos); + // Minimum duration of sleep to 0.1 s + Ok(std::cmp::max(duration, Duration::from_millis(100))) +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; @@ -20,7 +58,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let command_to_watch = matches .get_one::("command") .expect("required argument"); - let interval = 2; // TODO matches.get_one::("interval").map_or(2, |&v| v); + let interval = match matches.get_one::("interval") { + None => Duration::from_secs(2), + Some(input) => match parse_interval(input) { + Ok(interval) => interval, + Err(_) => { + return Err(Box::from(Error::new( + ErrorKind::InvalidInput, + format!("watch: failed to parse argument: '{input}': Invalid argument"), + ))); + } + } + }; loop { let output = SystemCommand::new("sh") @@ -35,7 +84,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { break; } - sleep(Duration::from_secs(interval)); + sleep(interval); } Ok(()) From 155c5a18d84f9c610659cacabf3c059ddb002ac8 Mon Sep 17 00:00:00 2001 From: Max van Ling Date: Mon, 1 Apr 2024 17:29:40 +0200 Subject: [PATCH 2/5] watch: Add test caes for interval implementation --- src/uu/watch/src/watch.rs | 57 +++++++++++++++++++++++++++++++++++++ tests/by-util/test_watch.rs | 47 ++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/uu/watch/src/watch.rs b/src/uu/watch/src/watch.rs index 99b4389e..0c65dd14 100644 --- a/src/uu/watch/src/watch.rs +++ b/src/uu/watch/src/watch.rs @@ -90,6 +90,61 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) } +#[cfg(test)] +mod parse_interval_tests { + use super::*; + + #[test] + fn test_comma_parse() { + let interval = parse_interval("1,5"); + assert_eq!(Ok(Duration::from_millis(1500)), interval); + } + + #[test] + fn test_different_nanos_length() { + let interval = parse_interval("1.12345"); + assert_eq!(Ok(Duration::new(1, 123450000)), interval); + let interval = parse_interval("1.1234"); + assert_eq!(Ok(Duration::new(1, 123400000)), interval); + } + + #[test] + fn test_period_parse() { + let interval = parse_interval("1.5"); + assert_eq!(Ok(Duration::from_millis(1500)), interval); + } + + #[test] + fn test_empty_seconds_interval() { + let interval = parse_interval(".5"); + assert_eq!(Ok(Duration::from_millis(500)), interval); + } + + #[test] + fn test_seconds_only() { + let interval = parse_interval("7"); + assert_eq!(Ok(Duration::from_secs(7)), interval); + } + + #[test] + fn test_empty_nanoseconds_interval() { + let interval = parse_interval("1."); + assert_eq!(Ok(Duration::from_millis(1000)), interval); + } + + #[test] + fn test_too_many_nanos() { + let interval = parse_interval("1.00000000009"); + assert_eq!(Ok(Duration::from_secs(1)), interval); + } + + #[test] + fn test_invalid_nano() { + let interval = parse_interval("1.00000000000a"); + assert!(interval.is_err()) + } +} + pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) @@ -184,3 +239,5 @@ pub fn uu_app() -> Command { .help("Pass command to exec instead of 'sh -c'"), ) } + + diff --git a/tests/by-util/test_watch.rs b/tests/by-util/test_watch.rs index c3afeaf6..27557283 100644 --- a/tests/by-util/test_watch.rs +++ b/tests/by-util/test_watch.rs @@ -10,3 +10,50 @@ use crate::common::util::TestScenario; fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } + +#[test] +fn test_invalid_interval() { + let args = vec!["-n", "definitely-not-valid", "true"]; + new_ucmd!().args(&args).fails(); +} + +#[test] +fn test_no_interval() { + let mut p = new_ucmd!() + .arg("true") + .run_no_wait(); + p.make_assertion_with_delay(500).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .no_stderr() + .no_stdout(); +} + +#[test] +fn test_valid_interval() { + let args = vec!["-n", "1.5", "true"]; + let mut p = new_ucmd!() + .args(&args) + .run_no_wait(); + p.make_assertion_with_delay(500).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .no_stderr() + .no_stdout(); +} + +#[test] +fn test_valid_interval_comma() { + let args = vec!["-n", "1,5", "true"]; + let mut p = new_ucmd!() + .args(&args) + .run_no_wait(); + p.make_assertion_with_delay(1000).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .no_stderr() + .no_stdout(); +} From 9a0248cda4ef111aedb34f285c6f4742db5d9dff Mon Sep 17 00:00:00 2001 From: Max van Ling Date: Mon, 1 Apr 2024 17:30:33 +0200 Subject: [PATCH 3/5] remove extra newlines --- src/uu/watch/src/watch.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uu/watch/src/watch.rs b/src/uu/watch/src/watch.rs index 0c65dd14..d5cfc0bd 100644 --- a/src/uu/watch/src/watch.rs +++ b/src/uu/watch/src/watch.rs @@ -239,5 +239,3 @@ pub fn uu_app() -> Command { .help("Pass command to exec instead of 'sh -c'"), ) } - - From 9903c1ea154d9c15e30dd1713c5afeea3c2b43d3 Mon Sep 17 00:00:00 2001 From: Max van Ling Date: Mon, 1 Apr 2024 17:54:51 +0200 Subject: [PATCH 4/5] watch: Fix formatting issues --- src/uu/watch/src/watch.rs | 12 ++++++++---- tests/by-util/test_watch.rs | 12 +++--------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/uu/watch/src/watch.rs b/src/uu/watch/src/watch.rs index d5cfc0bd..bde66911 100644 --- a/src/uu/watch/src/watch.rs +++ b/src/uu/watch/src/watch.rs @@ -3,14 +3,14 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::io::{Error, ErrorKind}; use clap::crate_version; use clap::{Arg, Command}; +use std::io::{Error, ErrorKind}; +use std::num::ParseIntError; use std::process::{Command as SystemCommand, Stdio}; use std::thread::sleep; use std::time::Duration; use uucore::{error::UResult, format_usage, help_about, help_usage}; -use std::num::{ParseIntError}; const ABOUT: &str = help_about!("watch.md"); const USAGE: &str = help_usage!("watch.md"); @@ -26,7 +26,11 @@ fn parse_interval(input: &str) -> Result { }; // If the seconds string is empty, set seconds to 0 - let seconds: u64 = if index > 0 { input[..index].parse()? } else { 0 }; + let seconds: u64 = if index > 0 { + input[..index].parse()? + } else { + 0 + }; let nanos_string = &input[index + 1..]; let nanos: u32 = match nanos_string.len() { @@ -68,7 +72,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { format!("watch: failed to parse argument: '{input}': Invalid argument"), ))); } - } + }, }; loop { diff --git a/tests/by-util/test_watch.rs b/tests/by-util/test_watch.rs index 27557283..9a2efcd1 100644 --- a/tests/by-util/test_watch.rs +++ b/tests/by-util/test_watch.rs @@ -19,9 +19,7 @@ fn test_invalid_interval() { #[test] fn test_no_interval() { - let mut p = new_ucmd!() - .arg("true") - .run_no_wait(); + let mut p = new_ucmd!().arg("true").run_no_wait(); p.make_assertion_with_delay(500).is_alive(); p.kill() .make_assertion() @@ -33,9 +31,7 @@ fn test_no_interval() { #[test] fn test_valid_interval() { let args = vec!["-n", "1.5", "true"]; - let mut p = new_ucmd!() - .args(&args) - .run_no_wait(); + let mut p = new_ucmd!().args(&args).run_no_wait(); p.make_assertion_with_delay(500).is_alive(); p.kill() .make_assertion() @@ -47,9 +43,7 @@ fn test_valid_interval() { #[test] fn test_valid_interval_comma() { let args = vec!["-n", "1,5", "true"]; - let mut p = new_ucmd!() - .args(&args) - .run_no_wait(); + let mut p = new_ucmd!().args(&args).run_no_wait(); p.make_assertion_with_delay(1000).is_alive(); p.kill() .make_assertion() From 7e93abd4a327d52273e800a3a7bda3d855c8dbeb Mon Sep 17 00:00:00 2001 From: Max van Ling Date: Mon, 1 Apr 2024 19:08:29 +0200 Subject: [PATCH 5/5] watch: Check stderr for 'Invalid argument' in `test_invalid_interval` --- tests/by-util/test_watch.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_watch.rs b/tests/by-util/test_watch.rs index 9a2efcd1..3517a421 100644 --- a/tests/by-util/test_watch.rs +++ b/tests/by-util/test_watch.rs @@ -14,7 +14,10 @@ fn test_invalid_arg() { #[test] fn test_invalid_interval() { let args = vec!["-n", "definitely-not-valid", "true"]; - new_ucmd!().args(&args).fails(); + new_ucmd!() + .args(&args) + .fails() + .stderr_contains("Invalid argument"); } #[test]