-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
refactor(test/clitools)!: refine semantics of CliTestContext::kill_at()
#4966
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
base: main
Are you sure you want to change the base?
Changes from all commits
31ae095
9b3ff4f
85a43af
4928643
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,7 @@ use std::{ | |
| mem, | ||
| ops::{Deref, DerefMut}, | ||
| path::{Path, PathBuf}, | ||
| process::{Command, ExitStatus}, | ||
| process::{Child, Command, ExitStatus}, | ||
| string::FromUtf8Error, | ||
| sync::{Arc, LazyLock, RwLock, RwLockWriteGuard}, | ||
| thread, | ||
|
|
@@ -975,44 +975,72 @@ impl CliTestContext { | |
|
|
||
| /// Run a rustup command until it reaches `checkpoint`, then terminate it | ||
| /// without giving destructors an opportunity to run. | ||
| pub fn kill_at_checkpoint(&self, mut command: Command, checkpoint: &str) -> ExitStatus { | ||
| pub fn kill_at<S: AsRef<OsStr> + Clone + Debug>( | ||
| &self, | ||
| checkpoint: &str, | ||
| args: impl AsRef<[S]>, | ||
| ) -> ExitStatus { | ||
| self.spawn_at(checkpoint, args).kill() | ||
| } | ||
|
|
||
| /// Run a rustup command until it reaches `checkpoint` and keep it parked | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: documentation comments should start with a single-line short sentence. |
||
| /// there, e.g. to observe or race the paused operation from another | ||
| /// process. The returned guard kills the command when dropped. | ||
| pub fn spawn_at<S: AsRef<OsStr> + Clone + Debug>( | ||
| &self, | ||
| checkpoint: &str, | ||
| args: impl AsRef<[S]>, | ||
| ) -> ParkedChild { | ||
| let marker = checkpoint_path(&self.config.test_root_dir, checkpoint); | ||
| if let Err(error) = fs::remove_file(&marker) | ||
| && error.kind() != io::ErrorKind::NotFound | ||
| { | ||
| panic!("failed to remove stale checkpoint marker: {error}"); | ||
| } | ||
|
|
||
| command.env(CHECKPOINT_ENV, checkpoint); | ||
| let mut child = command | ||
| .spawn() | ||
| .expect("failed to start command for checkpoint test"); | ||
| let (program, args) = args | ||
| .as_ref() | ||
| .split_first() | ||
| .expect("args should not be empty"); | ||
| let mut cmd = self.config.cmd( | ||
| program | ||
| .as_ref() | ||
| .to_str() | ||
| .expect("invalid UTF-8 in program name"), | ||
| args, | ||
| ); | ||
| cmd.env(CHECKPOINT_ENV, checkpoint); | ||
|
|
||
| let child = { | ||
| // The lock covers the spawn itself. It must not be held while the | ||
| // child stays parked: a writer queued behind it would then block | ||
| // every command spawned in the meantime, including the ones a test | ||
| // wants to run against the parked process. | ||
| let _lock = CMD_LOCK.read().unwrap(); | ||
| cmd.spawn() | ||
| .expect("failed to start command for checkpoint test") | ||
| }; | ||
| let mut parked = ParkedChild { child: Some(child) }; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like weird style -- why don't we wrap |
||
|
|
||
| let deadline = Instant::now() + StdDuration::from_secs(10); | ||
| loop { | ||
| if matches!(fs::read_to_string(&marker), Ok(contents) if contents == checkpoint) { | ||
| break; | ||
| break parked; | ||
| } | ||
| if let Some(status) = child | ||
| if let Some(status) = parked | ||
| .child | ||
| .as_mut() | ||
| .unwrap() | ||
| .try_wait() | ||
| .expect("failed to query checkpoint test command") | ||
| { | ||
| panic!("command exited before reaching checkpoint {checkpoint:?}: {status}"); | ||
| } | ||
| if Instant::now() >= deadline { | ||
| let _ = child.kill(); | ||
| let _ = child.wait(); | ||
| panic!("timed out waiting for checkpoint {checkpoint:?}"); | ||
| } | ||
| thread::sleep(StdDuration::from_millis(10)); | ||
| } | ||
|
|
||
| child | ||
| .kill() | ||
| .expect("failed to terminate command at checkpoint"); | ||
| child | ||
| .wait() | ||
| .expect("failed to reap command after checkpoint") | ||
| } | ||
|
|
||
| /// Move the dist server to the specified scenario and restore it | ||
|
|
@@ -1091,6 +1119,37 @@ impl Drop for WorkDirGuard<'_> { | |
| } | ||
| } | ||
|
|
||
| /// A rustup command spawned by [`CliTestContext::spawn_at`], parked at a | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: documentation comments. |
||
| /// checkpoint. Killed on drop unless [`ParkedChild::kill`] was called first. | ||
| #[must_use] | ||
| pub struct ParkedChild { | ||
| child: Option<Child>, | ||
| } | ||
|
|
||
| impl ParkedChild { | ||
| /// Terminate the parked command without giving destructors an opportunity | ||
| /// to run, then reap it. | ||
| pub fn kill(mut self) -> ExitStatus { | ||
| let mut child = self.child.take().unwrap(); | ||
| child | ||
| .kill() | ||
| .expect("failed to terminate command at checkpoint"); | ||
| child | ||
| .wait() | ||
| .expect("failed to reap command after checkpoint") | ||
| } | ||
| } | ||
|
|
||
| impl Drop for ParkedChild { | ||
| fn drop(&mut self) { | ||
| let Some(mut child) = self.child.take() else { | ||
| return; | ||
| }; | ||
| let _ = child.kill(); | ||
| let _ = child.wait(); | ||
| } | ||
| } | ||
|
|
||
| #[must_use] | ||
| pub struct DistDirGuard<'a> { | ||
| inner: &'a mut CliTestContext, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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 we should just move this into the existing
Processimpl.View changes since the review