Skip to content

Commit 3c03e45

Browse files
test-runner: Add TestsComplete message
At the end of the test-runner app, right before we exit boot services, send a TestsComplete message to the host. If the host doesn't receive this message, the tests will be marked as failed.
1 parent 12a7b44 commit 3c03e45

File tree

3 files changed

+48
-16
lines changed

3 files changed

+48
-16
lines changed

uefi-test-runner/src/main.rs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,23 @@ fn check_revision(rev: uefi::table::Revision) {
7676
);
7777
}
7878

79-
/// Send the `request` string to the host via the `serial` device, then
80-
/// wait up to 10 seconds to receive a reply. Returns an error if the
81-
/// reply is not `"OK\n"`.
82-
fn send_request_helper(serial: &mut Serial, request: &str) -> Result {
79+
#[derive(Clone, Copy, Debug)]
80+
enum HostRequest {
81+
/// Tell the host to take a screenshot and compare against the
82+
/// golden image.
83+
Screenshot(&'static str),
84+
85+
/// Tell the host that tests are complete. The host will consider
86+
/// the tests failed if this message is not received.
87+
TestsComplete,
88+
}
89+
90+
fn send_request_helper(serial: &mut Serial, request: HostRequest) -> Result {
91+
let request = match request {
92+
HostRequest::Screenshot(name) => format!("SCREENSHOT: {name}\n"),
93+
HostRequest::TestsComplete => "TESTS_COMPLETE\n".to_string(),
94+
};
95+
8396
// Set a 10 second timeout for the read and write operations.
8497
let mut io_mode = *serial.io_mode();
8598
io_mode.timeout = 10_000_000;
@@ -99,10 +112,10 @@ fn send_request_helper(serial: &mut Serial, request: &str) -> Result {
99112
}
100113
}
101114

102-
/// Ask the test runner to check the current screen output against a reference.
103-
fn check_screenshot(bt: &BootServices, name: &str) {
104-
let request = format!("SCREENSHOT: {name}\n");
105-
115+
/// Send the `request` string to the host via the `serial` device, then
116+
/// wait up to 10 seconds to receive a reply. Returns an error if the
117+
/// reply is not `"OK\n"`.
118+
fn send_request_to_host(bt: &BootServices, request: HostRequest) {
106119
let serial_handle = bt
107120
.get_handle_for_protocol::<Serial>()
108121
.expect("Failed to get serial handle");
@@ -124,7 +137,7 @@ fn check_screenshot(bt: &BootServices, name: &str) {
124137

125138
// Send the request, but don't check the result yet so that first
126139
// we can reconnect the console output for the logger.
127-
let res = send_request_helper(&mut serial, &request);
140+
let res = send_request_helper(&mut serial, request);
128141

129142
// Release the serial device and reconnect all controllers to the
130143
// serial handle. This is necessary to restore the connection
@@ -135,11 +148,7 @@ fn check_screenshot(bt: &BootServices, name: &str) {
135148
let _ = bt.connect_controller(serial_handle, None, None, true);
136149

137150
if let Err(err) = res {
138-
panic!(
139-
"request failed: \"{}\": {:?}",
140-
request.trim_end(),
141-
err.status()
142-
);
151+
panic!("request failed: \"{request:?}\": {:?}", err.status());
143152
}
144153
}
145154

@@ -149,6 +158,11 @@ fn shutdown(image: uefi::Handle, mut st: SystemTable<Boot>) -> ! {
149158

150159
info!("Testing complete, shutting down...");
151160

161+
// Tell the host that tests are done. We are about to exit boot
162+
// services, so we can't easily communicate with the host any later
163+
// than this.
164+
send_request_to_host(st.boot_services(), HostRequest::TestsComplete);
165+
152166
// Exit boot services as a proof that it works :)
153167
let sizes = st.boot_services().memory_map_size();
154168
let max_mmap_size = sizes.map_size + 2 * sizes.entry_size;

uefi-test-runner/src/proto/console/gop.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::{send_request_to_host, HostRequest};
12
use uefi::prelude::*;
23
use uefi::proto::console::gop::{BltOp, BltPixel, FrameBuffer, GraphicsOutput, PixelFormat};
34
use uefi::table::boot::{BootServices, OpenProtocolAttributes, OpenProtocolParams};
@@ -23,7 +24,7 @@ pub unsafe fn test(image: Handle, bt: &BootServices) {
2324
fill_color(gop);
2425
draw_fb(gop);
2526

26-
crate::check_screenshot(bt, "gop_test");
27+
send_request_to_host(bt, HostRequest::Screenshot("gop_test"));
2728
} else {
2829
// No tests can be run.
2930
warn!("UEFI Graphics Output Protocol is not supported");

xtask/src/qemu.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ impl Io {
282282
}
283283

284284
fn process_qemu_io(mut monitor_io: Io, mut serial_io: Io, tmp_dir: &Path) -> Result<()> {
285+
let mut tests_complete = false;
286+
285287
// This regex is used to detect and strip ANSI escape codes. These
286288
// escapes are added by the console output protocol when writing to
287289
// the serial device.
@@ -298,6 +300,9 @@ fn process_qemu_io(mut monitor_io: Io, mut serial_io: Io, tmp_dir: &Path) -> Res
298300
let line = ansi_escape.replace_all(line.as_bytes(), &b""[..]);
299301
let line = String::from_utf8(line.into()).expect("line is not utf8");
300302

303+
// Send an "OK" response to the app.
304+
let mut reply_ok = || serial_io.write_all("OK\n");
305+
301306
// If the app requests a screenshot, take it.
302307
if let Some(reference_name) = line.strip_prefix("SCREENSHOT: ") {
303308
let screenshot_path = tmp_dir.join("screenshot.ppm");
@@ -316,7 +321,7 @@ fn process_qemu_io(mut monitor_io: Io, mut serial_io: Io, tmp_dir: &Path) -> Res
316321
assert_eq!(reply, json!({"return": {}}));
317322

318323
// Tell the VM that the screenshot was taken
319-
serial_io.write_all("OK\n")?;
324+
reply_ok()?;
320325

321326
// Compare screenshot to the reference file specified by the user.
322327
// TODO: Add an operating mode where the reference is created if it doesn't exist.
@@ -330,11 +335,23 @@ fn process_qemu_io(mut monitor_io: Io, mut serial_io: Io, tmp_dir: &Path) -> Res
330335
expected == actual,
331336
"screenshot does not match reference image"
332337
)
338+
} else if line == "TESTS_COMPLETE" {
339+
// The app sends this command after running its tests to
340+
// indicate it actually got to the end. If the tests failed
341+
// earlier with a panic, this command will never be
342+
// received.
343+
tests_complete = true;
344+
345+
reply_ok()?;
333346
} else {
334347
println!("{line}");
335348
}
336349
}
337350

351+
if !tests_complete {
352+
bail!("tests did not complete successfully");
353+
}
354+
338355
Ok(())
339356
}
340357

0 commit comments

Comments
 (0)