-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
tests/util: Small Refactor/Fixes of UCommand and add method to run a UCommand in a shell platform independently
#4293
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
tests/util: Small Refactor/Fixes of UCommand and add method to run a UCommand in a shell platform independently
#4293
Conversation
|
GNU testsuite comparison: |
1 similar comment
|
GNU testsuite comparison: |
db5e0bf to
39546ea
Compare
tests/util: Add possibility to run UCommand in a shell platform independentlytests/util: Small Refactor/Fixes of UCommand and add method to run a UCommand in a shell platform independently
39546ea to
8454c55
Compare
|
This pr is ready for review but not for merge. The prs, this branch is based on need to be merged first and I'll do a final squash. |
d135169 to
3dcc1c2
Compare
|
I have to add that I'm not very satisfied with the This approach makes the |
|
FWIW, the lack of Android support for (Edit to add: IMO, this project should have a comment any time there is one of |
|
Filed a PR upstream for the Android bug: Nugine/rlimit/pull/46 |
That's great! Thanks. I'll integrate the changes of the new release of |
9c93eb3 to
8249ff3
Compare
8249ff3 to
83704ab
Compare
tertsdiepraam
left 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.
I think this is close to done. Just a couple of points:
- Could you reconsider what the public API should be (I'd like it to be as small as possible)?
- I'd like the git history to be a bit more compressed.
- There are some TODOs left in the code that should be addressed.
| /// Set the execution binary. | ||
| /// | ||
| /// Make sure the binary found at this path is executable. It's safest to provide the | ||
| /// canonicalized path instead of just the name of the executable, since path resolution is not | ||
| /// guaranteed to work on all platforms. | ||
| pub fn bin_path<T>(&mut self, bin_path: T) -> &mut Self | ||
| where | ||
| T: Into<PathBuf>, | ||
| { | ||
| self.bin_path = Some(bin_path.into()); | ||
| self | ||
| } |
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.
Are these setters necessary (also util, temp_dir)? Or can we maybe not make them pub?
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.
util could be private, with_util and from_test_scenario should be sufficient. As far as I can see, temp_dir could be private too if you think it's not useful to be pub. Same for bin_path (TestScenario::cmd provides the same functionality but then the only way to set a custom binary would be via TestScenario). current_dir is actually useful, since it removes the need for access to the raw std::process::Command. Compared to the public api of Command this is the last missing method and it's in use in test_cp and test_pwd.
We could reduce the public api of TestScenario too. ucmd_keepenv and cmd_keepenv are much less useful, since there's keep_env in UCommand, now.
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.
That sounds great!
No problem, I'll squash the commits down to a minimum in the end. I'm also cleaning up the TODOs. What do you think about renaming |
|
I like removing the |
That's another good point!
Right, it should be just |
83704ab to
7ce9e50
Compare
|
GNU testsuite comparison: |
|
Just ping me when it's ready for another review! |
…ce constant TESTS_BINARY.
Summary of changes in tests/util:
* Introduce global constant TESTS_BINARY holding the path to `coreutils`
* Implement running an arbitrary command in a sh or cmd shell per default.
* Implement std::fmt::Display for UCommand.
* Change usages of UCommand::util_name from &Option<OsStr> to Option<OsStr>
* `UCommand::new_from_tmp`: Use OsStr directly instead of TempDir -> String -> OsStr
* Collect arguments in `UCommand::args` field
* Build environment variables in `UCommand` itself instead of `std::process::Command`
* Move building of std::process:Command from fields in UCommand to own method `build`
* Remove assertions of UCommand::has_run in arg, args and env.
Summary of changes in tests/by-util:
* Remove usages of UCommand::raw. Fix tests to use UCommand::to_string.
* test_test: Adjust test_invalid_utf8_integer_compare to use `UCommand::args`
* test_chmod: run_single_test
* test_pwd: symlinked_env
* test_cp:
Fix the usage of &Option<OsStr> in `test_src_base_dot`
Refactor test_src_base_dot to not use UCommand::new but ts.ucmd() instead
7ce9e50 to
22b18ed
Compare
|
@tertsdiepraam ok, deal :) ping. |
|
GNU testsuite comparison: |
tests/common/util.rs
Outdated
| // neither `bin_path` nor `util_name` was set so we apply the default to run the arguments | ||
| // in a shell platform specific |
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.
This comment seems to be unfinished? Also maybe we want to panic if neither was set? I'm also wondering why the bin_path would not be set? Can't we just make it a PathBuf and force it to be set in any constructor?
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.
This comment is actually finished. It's just a helping comment to guide the quick reader a little bit through the build method.
Also maybe we want to panic if neither was set? I'm also wondering why the bin_path would not be set? Can't we just make it a PathBuf and force it to be set in any constructor?
The build method is built on the concept to collect all the information given by the user up to this call of build and then apply the defaults appropriately. So, bin_path is always set (a few lines later, you'll find another helping comment above the unwrap which points this out). The application of defaults only in build simplifies the building process and keeps UCommand as flexible as possible (also for future changes). I think it's much clearer to apply the defaults in a single, dedicated method instead of spreading the setting of defaults over the init methods and the build method.
I'll place a doc comment above the build method which will describe this strategy in detail.
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 done.
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.
Does this answer your questsions?
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.
Ah I understand the comment now. It was the word order that tripped me up. "in a platform specific shell" might be clearer. I understand the builder, but I just think the command name should always be given in the constructor of the builder. If we do that the bin_name does not need to be an option anymore.
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.
Would splitting the build method into logically related smaller methods help, too?
No the build method is fine!
In short, we have a good default for a non-existent bin_path, Command doesn't.
Well, that's the thing, I don't want there to be a default, because I don't see the point of doing this. It would be like calling a function but not specifying the function name but only the arguments, wouldn't it? It seems to me that this is a clear user error and we can prevent that error.
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.
That being said, if you still disagree it's not a big deal and we can move on.
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.
Let me try to give an example. If I understand this PR correctly, this would be valid:
UCommand::new()
.arg("foo")
.arg("bar")
.run();Under the hood, this would become sh -c foo bar, so that's the same as calling foo bar which the shell option. That's very weird to me and I'd much rather have this be an error (and preferably at compile time). Also remember that flexibility is not really the point here. We're building these tools for a very specific purpose, so we get to tailor it to our (current) preferences. If the requirements change, this will just change with it.
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.
yeah the code above would be valid. I actually would prefer providing a default if possible over errors. Would renaming new to with_shell (and giving up the new method) resolve your concerns?
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.
Not really, but I just realized there's some additional complexity because there's both bin_name and util_name and I need to think for a bit about how we want those to interact with the shell. There are some fundamental problems with doing this platform-independent shell API and how it interacts with the tests binary.
22b18ed to
efb1ad5
Compare
|
GNU testsuite comparison: |
Summary of changes in UCommand: * Extend UCommand by builder methods and simplify methods in TestScenario * Simplify code structures where possible. Add documentation. * Store bin_path as PathBuf and util_name as String in all structs * Remove UCommand::util and make bin_path, temp_dir private * Rename UCommand::with_limit -> UCommand::limit Summary of changes in TestScenario: * Rename some parameters in TestScenario methods to be more descriptive * Remove ucmd_keepenv, cmd_keepenv from TestScenario. Use UCommand::keep_env instead.
efb1ad5 to
1c230fd
Compare
|
GNU testsuite comparison: |
| /// Spawns the command, feeds the stdin if any, and returns the | ||
| /// child process immediately. | ||
| pub fn run_no_wait(&mut self) -> UChild { | ||
| assert!(!self.has_run, "{}", ALREADY_RUN); |
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.
Since this is a wrapper now that builds a Command on every run, we don't need to check this anymore do we? That's kind of a cool side effect of doing it this way.
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.
Actually, we do need to check this because currently build mutates self, but it doesn't have to! We can write it in a way that it doesn't do that and then we can make reusable commands, which could be nice in some cases where we test many small variations on one command.
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 have thought about it, too. There's a problem however: Stdio is not cloneable, so every Stdio field we would set explicitely with set_stdin, set_stdout and set_stderr would be gone after the first usage. I'm not sure if this behavior is obvious enough. Another possibility to achieve almost the same would be to add a method like from_other(other: &UCommand, stdin: Option<Stdio>, stdout: ...).
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 that's a good point! Nevermind then :)
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.
Maybe the opposite approach then, could we make build consume self?
|
To clarify some things that I think needs some thought:
For this next step, the most important thing to figure out is the use case of this feature. Now I do want something like this PR and I think we can use most of the changes you made. I just want to discuss the API further before we get into the implementation further if that makes sense. Also, sorry, I should have realized these things earlier so I could have brought them up earlier. |
Sure, no problem.
What about
I'm not sure if two different methods would help much around the problem, that we always have to differentiate platform dependent between those methods in the test itself. So, if I'm not wrong the code would alwasy look something like that #[cfg(unix)]
let command = UCommand::with_shell("echo hello; echo world");
#[cfg(windows)]
let command = UCommand::with_shell("echo hello&echo world");
command.run();Maybe a single `with_shell(unix_cmd: ..., win_cmd: ... ) method would work? let command = UCommand::with_shell("echo hello; echo world", "echo hello&echo world").run();
Replacing the |
That's possible! Although maybe it should not be a
Yes it would, but I want to enforce that at the API level. In the API that I proposed it would be: #[cfg(unix)]
let command = UCommand::unix_shell("echo hello; echo world");
#[cfg(windows)]
let command = UCommand::windows_shell("echo hello&echo world");And it would be a compile time error if you forget the
Nice idea, but we often want to call this in a test for unix only and it doesn't scale well if we want to add other shells (e.g. zsh, fish, nushell, PowerShell, who knows)
Yes, with an important caveat: that's only true in your current implementation. That's why I want to look at the API first and the implementation after. More importantly than any of the above, what use cases do you have in mind here? |
The use cases are very rare. Maybe we should avoid overcomplicating things? I found 2 cases in |
That's what I'm trying to do. I don't have particular cases in mind, that's why I'm asking. If it's really that rare that it would only apply to 2 cases in tail then why should we put it in Maybe some thing like this: fn shell(cmd: String) -> UCommand {
if cfg!(unix) {
UCommand::cmd("sh").arg("-c").arg(cmd)
} else {
UCommand::cmd("cmd").arg("/C").arg(cmd)
}Note that even if it turns out that the shell thing is not useful, the surrounding changes are great on their own in my opinion. Things like the separate |
|
It started off that way, but you told me you wanted it in UCommand::with_shell()
.arg(
#[cfg(unix)]
"echo hello; echo world",
#[cfg(windows)]
"echo hello&echo world",
)
.run();I really like this :) |
Oh yeah I see. Whoops 😄 Now I feel bad for pushing back. I suppose I didn't think about it properly back then. To be fair, though, you haven't made an issue to discuss this feature first. If you start out without the sunk-cost of an implementation, discussions like this one are much easier to have. I do like it much better with the
Looks nice, but doesn't solve the issue I had with the API. I'll think about this for a bit and get back to you. |
tertsdiepraam
left 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.
Alright, I've decided that this is indeed good enough and your time is probably much better spent on tail instead :) Sorry for the wild ride and being inconsistent. I really appreciate all the work you put into this. Thank you!
|
Great! Thanks :) |
Like discussed in #4261, this pr restructures
UCommandto run a command in a shell context (sh -con unix systems andcmd /Con windows systems) per default.A little bit of a refactoring is needed, to not break the other functions in
UCommandlikearg,argsetc. and store the arguments and environment variables inUCommanditself instead of delegating them to the underlyingstd::process::Command.Other fixes/changes in
UCommand:rawfield and build thestd::process::Commandinrun_no_waitor more specificallyUCommand::buildinstead.OsStrings andPathBufsfor paths inUCommandinstead of converting tostrorStringback and forth.comm_stringfield and implementDisplayforUCommand.UCommand::current_dirto set the working directory of theUCommandkeep_envto stop the default behavior of clearing the environment.UCommand::argsmethod generated the wrong assertion messageMULTIPLE_STDIN_MEANINGLESSinstead ofALREADY_RUN. The assertions forself.has_runinarg,args,envwere removed and only the one inrun_no_waitis kept.with_limitto justlimitto integrate itself better in the general naming scheme inUCommandprlimitversion to be able to apply the limits on android platforms too.Changes in
TestScenario:ucmd_keepenvandcmd_keepenv. Make use ofUCommand::keep_envinstead.Changes in the
utilmodule in general:TESTS_BINARYwhich holds the platform and profile specific path to thecoreutilsbinary, so it can be used in all tests.Short summary of tests that needed to be adjusted:
UCommand::keep_envinstead of the removedTestScenario::ucmd_keepenvandTestScenario::cmd_keepenvtests/test:test_invalid_utf8_integer_comparenow usesUCommand::argsinstead ofUCommand::raw::argtests/chmod: UseDisplayofUCommandinstead ofDebugofUCommand.tests/cp: Do not use theUCommand::newmethod to set the working directy. UseTestScenario::ucmdinstead, together withUCommand::current_dirto set the working.directory.tests/pwd: UseUCommand::current_dirinstead ofUCommand::raw::current_dirtests/test: Do not use theUCommand::rawfield butUCommand::argswithOsStrs