Skip to content

Commit 5063af7

Browse files
committed
Move runner code into new run module
1 parent 224dfdb commit 5063af7

File tree

3 files changed

+147
-64
lines changed

3 files changed

+147
-64
lines changed

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
pub mod args;
88
pub mod builder;
99
pub mod config;
10+
pub mod run;
1011

1112
/// Contains help messages for the command line application.
1213
pub mod help;

src/main.rs

+3-64
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ use anyhow::{anyhow, Context, Result};
33
use bootimage::{
44
args::{RunnerArgs, RunnerCommand},
55
builder::Builder,
6-
config, help,
6+
config, help, run,
77
};
8+
use std::process;
89
use std::{env, path::Path};
9-
use std::{process, time::Duration};
10-
use wait_timeout::ChildExt;
1110

1211
pub fn main() -> Result<()> {
1312
let mut raw_args = env::args();
@@ -93,67 +92,7 @@ pub(crate) fn runner(args: RunnerArgs) -> Result<i32> {
9392
args.quiet,
9493
)?;
9594

96-
let mut run_command: Vec<_> = config
97-
.run_command
98-
.iter()
99-
.map(|arg| arg.replace("{}", &format!("{}", output_bin_path.display())))
100-
.collect();
101-
if is_test {
102-
if let Some(args) = config.test_args {
103-
run_command.extend(args);
104-
}
105-
} else if let Some(args) = config.run_args {
106-
run_command.extend(args);
107-
}
108-
if let Some(args) = args.runner_args {
109-
run_command.extend(args);
110-
}
111-
112-
if !args.quiet {
113-
println!("Running: `{}`", run_command.join(" "));
114-
}
115-
let mut command = process::Command::new(&run_command[0]);
116-
command.args(&run_command[1..]);
117-
118-
let exit_code = if is_test {
119-
let mut child = command
120-
.spawn()
121-
.with_context(|| format!("Failed to launch QEMU: {:?}", command))?;
122-
let timeout = Duration::from_secs(config.test_timeout.into());
123-
match child
124-
.wait_timeout(timeout)
125-
.context("Failed to wait with timeout")?
126-
{
127-
None => {
128-
child.kill().context("Failed to kill QEMU")?;
129-
child.wait().context("Failed to wait for QEMU process")?;
130-
return Err(anyhow!("Timed Out"));
131-
}
132-
Some(exit_status) => {
133-
#[cfg(unix)]
134-
{
135-
if exit_status.code().is_none() {
136-
use std::os::unix::process::ExitStatusExt;
137-
if let Some(signal) = exit_status.signal() {
138-
eprintln!("QEMU process was terminated by signal {}", signal);
139-
}
140-
}
141-
}
142-
let qemu_exit_code = exit_status
143-
.code()
144-
.ok_or_else(|| anyhow!("Failed to read QEMU exit code"))?;
145-
match config.test_success_exit_code {
146-
Some(code) if qemu_exit_code == code => 0,
147-
_ => qemu_exit_code,
148-
}
149-
}
150-
}
151-
} else {
152-
let status = command
153-
.status()
154-
.with_context(|| format!("Failed to execute `{:?}`", command))?;
155-
status.code().unwrap_or(1)
156-
};
95+
let exit_code = run::run(config, args, &output_bin_path, is_test)?;
15796

15897
Ok(exit_code)
15998
}

src/run.rs

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
//! Provides a function for running a disk image in QEMU.
2+
3+
use crate::{args::RunnerArgs, config::Config};
4+
use std::{io, path::Path, process, time::Duration};
5+
use thiserror::Error;
6+
use wait_timeout::ChildExt;
7+
8+
/// Run the given disk image in QEMU.
9+
///
10+
/// Automatically takes into account the runner arguments and the run/test
11+
/// commands defined in the given `Config`. Since test executables are treated
12+
/// differently (run with a timeout and match exit status), the caller needs to
13+
/// specify whether the given disk image is a test or not.
14+
pub fn run(
15+
config: Config,
16+
args: RunnerArgs,
17+
image_path: &Path,
18+
is_test: bool,
19+
) -> Result<i32, RunError> {
20+
let mut run_command: Vec<_> = config
21+
.run_command
22+
.iter()
23+
.map(|arg| arg.replace("{}", &format!("{}", image_path.display())))
24+
.collect();
25+
if is_test {
26+
if let Some(args) = config.test_args {
27+
run_command.extend(args);
28+
}
29+
} else if let Some(args) = config.run_args {
30+
run_command.extend(args);
31+
}
32+
if let Some(args) = args.runner_args {
33+
run_command.extend(args);
34+
}
35+
36+
if !args.quiet {
37+
println!("Running: `{}`", run_command.join(" "));
38+
}
39+
let mut command = process::Command::new(&run_command[0]);
40+
command.args(&run_command[1..]);
41+
42+
let exit_code = if is_test {
43+
let mut child = command.spawn().map_err(|error| RunError::Io {
44+
context: IoErrorContext::QemuTestCommand {
45+
command: format!("{:?}", command),
46+
},
47+
error,
48+
})?;
49+
let timeout = Duration::from_secs(config.test_timeout.into());
50+
match child
51+
.wait_timeout(timeout)
52+
.map_err(context(IoErrorContext::WaitWithTimeout))?
53+
{
54+
None => {
55+
child.kill().map_err(context(IoErrorContext::KillQemu))?;
56+
child.wait().map_err(context(IoErrorContext::WaitForQemu))?;
57+
return Err(RunError::TestTimedOut);
58+
}
59+
Some(exit_status) => {
60+
#[cfg(unix)]
61+
{
62+
if exit_status.code().is_none() {
63+
use std::os::unix::process::ExitStatusExt;
64+
if let Some(signal) = exit_status.signal() {
65+
eprintln!("QEMU process was terminated by signal {}", signal);
66+
}
67+
}
68+
}
69+
let qemu_exit_code = exit_status.code().ok_or(RunError::NoQemuExitCode)?;
70+
match config.test_success_exit_code {
71+
Some(code) if qemu_exit_code == code => 0,
72+
_ => qemu_exit_code,
73+
}
74+
}
75+
}
76+
} else {
77+
let status = command.status().map_err(|error| RunError::Io {
78+
context: IoErrorContext::QemuRunCommand {
79+
command: format!("{:?}", command),
80+
},
81+
error,
82+
})?;
83+
status.code().unwrap_or(1)
84+
};
85+
86+
Ok(exit_code)
87+
}
88+
89+
/// Running the disk image failed.
90+
#[derive(Debug, Error)]
91+
pub enum RunError {
92+
/// Test timed out
93+
#[error("Test timed out")]
94+
TestTimedOut,
95+
96+
/// Failed to read QEMU exit code
97+
#[error("Failed to read QEMU exit code")]
98+
NoQemuExitCode,
99+
100+
/// An I/O error occured
101+
#[error("{context}: An I/O error occured: {error}")]
102+
Io {
103+
/// The operation that caused the I/O error.
104+
context: IoErrorContext,
105+
/// The I/O error that occured.
106+
error: io::Error,
107+
},
108+
}
109+
110+
/// An I/O error occured while trying to run the disk image.
111+
#[derive(Debug, Error)]
112+
pub enum IoErrorContext {
113+
/// QEMU command for non-test failed
114+
#[error("Failed to execute QEMU run command `{command}`")]
115+
QemuRunCommand {
116+
/// The QEMU command that was executed
117+
command: String,
118+
},
119+
120+
/// QEMU command for test failed
121+
#[error("Failed to execute QEMU test command `{command}`")]
122+
QemuTestCommand {
123+
/// The QEMU command that was executed
124+
command: String,
125+
},
126+
127+
/// Waiting for test with timeout failed
128+
#[error("Failed to wait with timeout")]
129+
WaitWithTimeout,
130+
131+
/// Failed to kill QEMU
132+
#[error("Failed to kill QEMU")]
133+
KillQemu,
134+
135+
/// Failed to wait for QEMU process
136+
#[error("Failed to wait for QEMU process")]
137+
WaitForQemu,
138+
}
139+
140+
/// Helper function for IO error construction
141+
fn context(context: IoErrorContext) -> impl FnOnce(io::Error) -> RunError {
142+
|error| RunError::Io { context, error }
143+
}

0 commit comments

Comments
 (0)