-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
tail
: fix argument parsing of sleep interval
#4239
tail
: fix argument parsing of sleep interval
#4239
Conversation
1e5b081
to
faae8b8
Compare
GNU testsuite comparison:
|
GNU testsuite comparison:
|
faae8b8
to
bf767e2
Compare
GNU testsuite comparison:
|
src/uu/tail/src/args.rs
Outdated
let trimmed = src.trim(); | ||
match src.parse::<f64>() { | ||
// We're working here with the string representation instead of num.fract() to avoid further | ||
// floating point precision problems where we can. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting to a string is lossy too. Are you sure this is worth it? We could also try to copy the unstable implementation in std.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting to a string is lossy too.
It's the multiplication with 1_000_000_000
which causes further imprecision. Some of the tests fail with the multiplication. No sure which one it was, but I think something like 1.999999999
resulted in Duration::new(1, 999_999_998)
. I'm trying to be as precise as possible to make the parsing more predictable.
Are you sure this is worth it?
Dunno. Sure it's slower than the the multiplication but parsing the sleep interval is a one time action and the string length of the f64 parsed src
is also managable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd imagine that conversion to a string would lead to inaccuracies in different places, but I don't have evidence for that. Let's keep this, but add a link to the tracking issue for try_from_secs_f64
, so we can periodically check whether it has been stabilized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, it's been stabilized in 1.66! Let's put a Replace with Duration::try_from_secs_f64 once we hit MSRV 1.66
here in a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, I've added a comment.
src/uu/tail/src/args.rs
Outdated
}, | ||
Ok(num) | ||
if num.is_infinite() | ||
&& (trimmed.eq_ignore_ascii_case("inf") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is to distinguish positive infinity from negative? Can't we do that with is_sign_negative
too if we check that first? In any case, could you document what this check is for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is to distinguish positive infinity from negative
Not only positive infinity from negative, but also if the infinity is caused by a src
number greater than f64::MAX
or if inf
or similar was given as src
. gnu's tail
fails with an error in the first case. I'm describing the problem in the comment below these lines. Maybe I should that comment move up.
Can't we do that with is_sign_negative
If we deviate from gnu's tail, sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, I assumed it would be Err
if it was more than f64::MAX
, but that makes sense. If you could add a quick comment explaining that in the code that would be great!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/uu/tail/src/args.rs
Outdated
// positive infinite (if src > f64::MAX) or NaN. In case of positive infinite we need to | ||
// match gnu's tail behavior and return an error although it may be a nice feature to | ||
// interpret this result as a valid (maximum) Duration. | ||
Ok(_) => Err(String::from("Not a number")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting Duration::MAX makes sense to me here if it is a valid positive number, but that's a bit tricky to check (especially with an exponent). So cool idea!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting Duration::MAX makes sense to me here if it is a valid positive number
Ok, I think checking for sign_positive
in the match arm
Ok(num) if num.is_infinite() ...
should do the trick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't changed this because of your comment above.
Oh I see, I assumed it would be Err if it was more than f64::MAX, but that makes sense. If you could add a quick comment explaining that in the code that would be great!
src/uu/tail/src/args.rs
Outdated
@@ -524,4 +590,136 @@ mod tests { | |||
assert!(result.is_ok()); | |||
assert_eq!(result.unwrap(), Signum::Negative(1)); | |||
} | |||
|
|||
#[test] | |||
fn test_parse_duration_when_simple_arguments_are_valid() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent tests!
bf767e2
to
ea29b96
Compare
GNU testsuite comparison:
|
ea29b96
to
1ce6f51
Compare
GNU testsuite comparison:
|
How does this coreutils/src/uucore/src/lib/parser/parse_time.rs Lines 17 to 49 in bdd6f56
|
Not sure what you mean, but they don't relate I think. This function tries to provide the same functionality like gnu's tail parsing of the |
1ce6f51
to
fde84f9
Compare
Please do not merge. I'm just trying something, to get a more performant, precise and unlossy version of the |
fde84f9
to
4853796
Compare
@tertsdiepraam I've completely rewritten the This duration parser is almost as fast as If the seconds overflow |
I'll have to look closely into the code later, but it sounds great! It does add significant complexity to the code over a single function call, but the advantages you list might be worth it. Unrelated to the actual change, but only to the tests (which I only quickly skimmed), I'm not sure I like using |
Ok, cool. Parameterized tests have the advantage that all test cases can run (and fail). A for loop stops at the first error encountered and it's maybe not clear which case actually failed. That's even worse in the CI, because we don't output the backtrace there. Parameterizing the tests helped me a lot during the writing. I actually don't think this is a huge obstacle for new contributors, since the syntax is quiet clear |
GNU testsuite comparison:
|
Have you looked at this yet? |
I had the idea to move this into |
cd5e71c
to
250b53a
Compare
No offense, but I extracted the code I've written into an own crate https://crates.io/crates/fundu. The idea and much of the code is the same. I've taken care, that it's compatible with the requirements of |
250b53a
to
83d7de6
Compare
GNU testsuite comparison:
|
3dc1348
to
d8e8689
Compare
GNU testsuite comparison:
|
GNU testsuite comparison:
|
@tertsdiepraam ping? |
I'll take a look now :) |
Agreed, it's also much better documented and tested now, which is great! It's a nice crate for other projects too. Excellent work! I think it would be interesting to show benchmarks against simple |
} | ||
} | ||
if let Some(source) = matches.get_one::<String>(options::SLEEP_INT) { | ||
settings.sleep_sec = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a quick comment here explaining what the advantage of fundu
is over try_from_f64_secs
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, no problem but it'll take until tomorrow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Thanks for your review. It's much appreciated.
good idea. Currently, the comparison with
yeah that would be good. It'll be part of
you're right it's confusing. Do you want that method |
Not really, I was just checking out the code and this stood out :) |
ok :) I think this method is not really useful outside of the crate, so I'll remove it from the public api for now. |
d8e8689
to
256ef9a
Compare
GNU testsuite comparison:
|
Are we done here? |
We made a change to how we declare dependencies so the |
ef5c527
to
fb15539
Compare
…rate. Activate tests for parsing sleep interval
ok great! The errors in the ci aren't related to this pr as far as I can see. However, I'm triggering a rerun, so maybe the |
fb15539
to
0ed6a9f
Compare
ok, looks like the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for taking so long on this! Excellent work!
Thanks :) |
Like discussed, I've extracted this from the refactoring pr of tail, which fixes the parsing of the sleep interval.
The main intend of this pr is to match the behavior of gnu's tail and properly parse a string in f64 format to a Duration.
Short summary:
f64
instead of af32
Duration::from_secs_f64
panics andDuration::try_from_secs_f64
is not stable, so we need a separate function to accomplish the conversion fromf64
to aDuration
.