Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions lib/propolis/src/block/attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ impl BlockData {
}
Err(err) => match err {
ReqError::NonePending | ReqError::Paused => {
let guard = self.lock.lock().unwrap();
// Double-check for attachment-related "error"
// conditions under protection of the lock before
// finally blocking.
if check_state(att_state).is_err() {
let guard = self.lock.lock().unwrap();
if !att_state.is_attached() || att_state.is_stopped() {
return None;
}

let _guard = self.cv.wait(guard).unwrap();
continue;
}
Expand Down
66 changes: 65 additions & 1 deletion phd-tests/framework/src/serial/raw_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ impl RawBuffer {
"checking wait on raw serial buffer"
);
if let Some(idx) = self.wait_buffer.rfind(&waiter.wanted) {
let out = self.wait_buffer[..idx].to_owned();
let out = self.wait_buffer.drain(..idx).collect();
self.wait_buffer = self.wait_buffer.split_off(waiter.wanted.len());

// Because incoming bytes from Propolis may be processed on a
// separate task than the task that registered the wait, this
Expand Down Expand Up @@ -152,3 +153,66 @@ impl Drop for RawBuffer {
}
}
}

#[cfg(test)]
mod test {
use tokio::sync::oneshot;

use super::*;

fn make_buffer() -> RawBuffer {
let file =
std::fs::OpenOptions::new().write(true).open("/dev/null").unwrap();

RawBuffer {
log: std::io::BufWriter::new(file),
line_buffer: String::new(),
wait_buffer: String::new(),
waiter: None,
parser: Parser::new(),
}
}

#[tokio::test]
async fn successful_wait_consumes_buffer_contents() {
let mut buf = make_buffer();
let (tx, mut rx) = oneshot::channel();
buf.push_str("the quick brown fox jumped over the lazy propolis");
buf.satisfy_or_set_wait(OutputWaiter {
wanted: "jumped over".to_string(),
preceding_tx: tx,
});
assert_eq!(rx.try_recv().unwrap(), "the quick brown fox ");
assert_eq!(buf.wait_buffer, " the lazy propolis");

// Repeat the test, but register the wait before the characters are
// pushed.
buf.clear();
let (tx, mut rx) = oneshot::channel();
buf.satisfy_or_set_wait(OutputWaiter {
wanted: "jumped over".to_string(),
preceding_tx: tx,
});
buf.push_str("the quick brown fox jumped over the lazy propolis");
assert_eq!(rx.try_recv().unwrap(), "the quick brown fox ");
assert_eq!(buf.wait_buffer, " the lazy propolis");
}

#[tokio::test]
async fn successful_wait_consumes_last_match() {
let mut buf = make_buffer();
let (tx, mut rx) = oneshot::channel();
buf.push_str(
"I put some Oxide in your Oxide so you can Oxide while you Oxide",
);
buf.satisfy_or_set_wait(OutputWaiter {
wanted: "you".to_string(),
preceding_tx: tx,
});
assert_eq!(
rx.try_recv().unwrap(),
"I put some Oxide in your Oxide so you can Oxide while "
);
assert_eq!(buf.wait_buffer, " Oxide");
}
}
2 changes: 1 addition & 1 deletion phd-tests/framework/src/test_vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ impl TestVm {

/// Sends `string` to the guest's serial console worker, then waits for the
/// entire string to be sent to the guest before returning.
pub(crate) async fn send_serial_str(&self, string: &str) -> Result<()> {
pub async fn send_serial_str(&self, string: &str) -> Result<()> {
if !string.is_empty() {
self.send_serial_bytes(Vec::from(string.as_bytes()))?.await?;
}
Expand Down
27 changes: 27 additions & 0 deletions phd-tests/tests/src/crucible/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,33 @@ async fn boot_test(ctx: &Framework) {
vm.wait_to_boot().await?;
}

#[phd_testcase]
async fn api_reboot_test(ctx: &Framework) {
let mut config = ctx.vm_config_builder("crucible_guest_reboot_test");
super::add_default_boot_disk(ctx, &mut config)?;

let mut vm = ctx.spawn_vm(&config, None).await?;
vm.launch().await?;
vm.wait_to_boot().await?;
vm.reset().await?;
vm.wait_to_boot().await?;
}

#[phd_testcase]
async fn guest_reboot_test(ctx: &Framework) {
let mut config = ctx.vm_config_builder("crucible_guest_reboot_test");
super::add_default_boot_disk(ctx, &mut config)?;

let mut vm = ctx.spawn_vm(&config, None).await?;
vm.launch().await?;
vm.wait_to_boot().await?;

// Don't use `run_shell_command` because the guest won't echo another prompt
// after this.
vm.send_serial_str("reboot\n").await?;
vm.wait_to_boot().await?;
}

#[phd_testcase]
async fn shutdown_persistence_test(ctx: &Framework) {
let mut config =
Expand Down
21 changes: 21 additions & 0 deletions phd-tests/tests/src/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ async fn nproc_test(ctx: &Framework) {
assert_eq!(nproc.parse::<u8>().unwrap(), 6);
}

#[phd_testcase]
async fn api_reboot_test(ctx: &Framework) {
let mut vm = ctx.spawn_default_vm("api_reboot_test").await?;
vm.launch().await?;
vm.wait_to_boot().await?;
vm.reset().await?;
vm.wait_to_boot().await?;
}

#[phd_testcase]
async fn guest_reboot_test(ctx: &Framework) {
let mut vm = ctx.spawn_default_vm("guest_reboot_test").await?;
vm.launch().await?;
vm.wait_to_boot().await?;

// Don't use `run_shell_command` because the guest won't echo another prompt
// after this.
vm.send_serial_str("reboot\n").await?;
vm.wait_to_boot().await?;
}

#[phd_testcase]
async fn instance_spec_get_test(ctx: &Framework) {
let mut vm = ctx
Expand Down