Skip to content
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

Support oneliner interface for kernel targets #6

Closed
wants to merge 2 commits into from
Closed
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
54 changes: 45 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use std::env;
use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process::exit;

use anyhow::{Context, Result};
use clap::Parser;
use regex::Regex;

use ::vmtest::{Ui, Vmtest};
use ::vmtest::{Config, Target, Ui, Vmtest};

#[derive(Parser, Debug)]
#[clap(version)]
struct Args {
/// Path to config file
#[clap(short, long, default_value = "vmtest.toml")]
config: PathBuf,
#[clap(short, long)]
config: Option<PathBuf>,
/// Filter by regex which targets to run
///
/// This option takes a regular expression. If a target matches this regular
Expand All @@ -22,19 +23,54 @@ struct Args {
/// Supported regex syntax: https://docs.rs/regex/latest/regex/#syntax.
#[clap(short, long, default_value = ".*")]
filter: String,
#[clap(short, long, conflicts_with = "config")]
kernel: Option<PathBuf>,
#[clap(conflicts_with = "config")]
command: Vec<String>,
}

/// Configure a `Vmtest` instance from command line arguments.
fn config(args: &Args) -> Result<Vmtest> {
if let Some(kernel) = &args.kernel {
let cwd = env::current_dir().context("Failed to get current directory")?;
let config = Config {
target: vec![Target {
name: kernel.file_name().unwrap().to_string_lossy().to_string(),
image: None,
uefi: false,
kernel: Some(kernel.clone()),
kernel_args: None,
command: args.command.join(" "),
}],
};

Vmtest::new(cwd, config)
} else {
let default = Path::new("vmtest.toml").to_owned();
let config_path = args.config.as_ref().unwrap_or(&default);
let contents = fs::read_to_string(config_path).context("Failed to read config file")?;
let config = toml::from_str(&contents).context("Failed to parse config")?;
let base = config_path.parent().unwrap();

Vmtest::new(base, config)
}
}

/// Whether or not to collapse command output in UI.
///
/// This is useful for one-liner invocations.
fn show_cmd(args: &Args) -> bool {
args.config.is_none()
}

fn main() -> Result<()> {
let args = Args::parse();

env_logger::init();
let contents = fs::read_to_string(&args.config).context("Failed to read config file")?;
let config = toml::from_str(&contents).context("Failed to parse config")?;
let base = args.config.parent().unwrap();
let vmtest = Vmtest::new(base, config)?;
let vmtest = config(&args)?;
let filter = Regex::new(&args.filter).context("Failed to compile regex")?;
let ui = Ui::new(vmtest);
let failed = ui.run(&filter);
let failed = ui.run(&filter, show_cmd(&args));
let rc = i32::from(failed != 0);

exit(rc);
Expand Down
45 changes: 27 additions & 18 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl Ui {
/// UI for a single target. Must be run on its own thread.
///
/// Returns if the target was successful or not>
fn target_ui(updates: Receiver<Output>, target: String) -> bool {
fn target_ui(updates: Receiver<Output>, target: String, show_cmd: bool) -> bool {
let term = Term::stdout();
let mut stage = Stage::new(term.clone(), &heading(&target, 1), None);
let mut stages = 0;
Expand Down Expand Up @@ -201,32 +201,38 @@ impl Ui {
stages += 1;
}
Output::Command(s) => stage.print_line(s, None),
Output::CommandEnd(r) => match r {
Ok(retval) => {
if *retval != 0 {
error_out_stage(
&mut stage,
&anyhow!("Command failed with exit code: {}", retval),
);
Output::CommandEnd(r) => {
if show_cmd {
stage.expand(true);
}

match r {
Ok(retval) => {
if *retval != 0 {
error_out_stage(
&mut stage,
&anyhow!("Command failed with exit code: {}", retval),
);
errors += 1;
}
}
Err(e) => {
error_out_stage(&mut stage, e);
errors += 1;
}
}
Err(e) => {
error_out_stage(&mut stage, e);
errors += 1;
}
},
};
}
}
}

// Force stage cleanup so we can do final fixup if we want
drop(stage);

// Only clear target stages if target was successful
if errors == 0 {
if errors == 0 && !show_cmd {
clear_last_lines(&term, stages);
term.write_line("PASS").expect("Failed to write terminal");
} else {
} else if !show_cmd {
term.write_line("FAILED").expect("Failed to write terminal");
}

Expand All @@ -235,10 +241,13 @@ impl Ui {

/// Run all the targets in the provided `vmtest`
///
/// `filter` specifies the regex to filter targets by.
/// `show_cmd` specifies if the command output should always be shown.
///
/// Note this function is "infallible" b/c on error it will display
/// the appropriate error message to screen. Rather, it returns how
/// many targets failed.
pub fn run(self, filter: &Regex) -> usize {
pub fn run(self, filter: &Regex, show_cmd: bool) -> usize {
let mut failed = 0;
for (idx, target) in self
.vmtest
Expand All @@ -251,7 +260,7 @@ impl Ui {

// Start UI on its own thread b/c `Vmtest::run_one()` will block
let name = target.name.clone();
let ui = thread::spawn(move || Self::target_ui(receiver, name));
let ui = thread::spawn(move || Self::target_ui(receiver, name, show_cmd));

// Run a target
self.vmtest.run_one(idx, sender);
Expand Down
2 changes: 1 addition & 1 deletion tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fn assert_no_error(recv: Receiver<Output>) {
fn test_run() {
let vmtest = vmtest("vmtest.toml.allgood");
let ui = Ui::new(vmtest);
let failed = ui.run(&*FILTER_ALL);
let failed = ui.run(&*FILTER_ALL, false);
assert_eq!(failed, 0);
}

Expand Down