use clap::Parser;
use crate::{
buck2::Buck2Command,
buckal_error, buckal_note,
filter::{FilterCaller, TargetFilter, get_available_targets},
utils::{
UnwrapOrExit, ensure_prerequisites, get_buck2_root, get_target, is_inside_buck2_project,
platform_exists, validate_target_triple,
},
};
#[derive(Parser, Debug)]
pub struct TestArgs {
#[arg(long)]
pub all_targets: bool,
#[arg(long)]
pub lib: bool,
#[arg(long, value_name = "NAME")]
pub bin: Vec<String>,
#[arg(long)]
pub bins: bool,
#[arg(long, value_name = "NAME")]
pub example: Vec<String>,
#[arg(long)]
pub examples: bool,
#[arg(long, value_name = "NAME")]
pub test: Vec<String>,
#[arg(long)]
pub tests: bool,
#[arg(long, value_name = "NAME")]
pub bench: Vec<String>,
#[arg(long)]
pub benches: bool,
#[arg(long)]
pub no_run: bool,
#[arg(long)]
pub no_fail_fast: bool,
#[arg(short = 'j', long, value_name = "THREADS")]
pub num_threads: Option<usize>,
#[arg(long, value_name = "TRIPLE", conflicts_with = "target_platforms")]
pub target: Option<String>,
#[arg(long, value_name = "PLATFORM", conflicts_with = "target")]
pub target_platforms: Option<String>,
#[arg(short, long)]
pub release: bool,
#[arg(short, action = clap::ArgAction::Count)]
pub verbose: u8,
#[arg(value_name = "TESTNAME")]
pub test_name: Option<String>,
#[arg(last = true)]
pub args: Vec<String>,
}
pub fn execute(args: &TestArgs) {
ensure_prerequisites().unwrap_or_exit();
is_inside_buck2_project().unwrap_or_exit();
if args.verbose > 2 {
buckal_error!("maximum verbosity");
std::process::exit(1);
}
let buck2_root = get_buck2_root().unwrap_or_exit();
let cwd = std::env::current_dir().unwrap_or_exit_ctx("failed to get current directory");
let mut relative = cwd
.strip_prefix(&buck2_root)
.unwrap_or_exit_ctx("test command should invoke inside a Buck2 project")
.to_string_lossy()
.into_owned();
relative = relative.replace('\\', "/");
let mut target_filter = TargetFilter::from_raw_arguments(
args.lib,
args.bin.clone(),
args.bins,
args.test.clone(),
args.tests,
args.example.clone(),
args.examples,
args.bench.clone(),
args.benches,
args.all_targets,
FilterCaller::Test,
)
.unwrap_or_exit();
if args.test_name.is_some() && !target_filter.is_specific() {
target_filter = TargetFilter::all_test_targets();
}
let available_targets = get_available_targets(&relative).unwrap_or_exit();
let mut buck2_cmd = if args.no_run {
Buck2Command::build().verbosity(args.verbose)
} else {
Buck2Command::test().verbosity(args.verbose)
};
if let Some(num_threads) = args.num_threads {
buck2_cmd = buck2_cmd.arg("-j").arg(num_threads.to_string());
}
let target_platforms = if let Some(triple) = &args.target {
match validate_target_triple(triple) {
Ok(platform) => Some(platform),
Err(e) => {
buckal_error!(e);
std::process::exit(1);
}
}
} else if let Some(platform) = &args.target_platforms {
Some(platform.clone())
} else {
let platform = format!("//platforms:{}", get_target());
if platform_exists(&platform) {
Some(platform)
} else {
None
}
};
if let Some(platform) = &target_platforms {
buck2_cmd = buck2_cmd.arg("--target-platforms").arg(platform);
}
if args.release {
buck2_cmd = buck2_cmd.arg("-m").arg("release");
}
if args.no_fail_fast {
buck2_cmd = buck2_cmd.arg("--keep-going");
}
let mut target_specified = false;
for target in &available_targets {
if target_filter.target_run(target) {
if let Some(test_name) = &args.test_name
&& !target.name().contains(test_name)
{
continue;
}
buck2_cmd = buck2_cmd.arg(target.label());
target_specified = true;
}
}
if !target_specified {
buckal_error!("all targets filtered out, nothing to test");
buckal_note!(
"please check the filter arguments and ensure if there are any testable targets in current directory"
);
std::process::exit(1);
}
if !args.no_run && !args.args.is_empty() {
buck2_cmd = buck2_cmd.arg("--");
for arg in &args.args {
buck2_cmd = buck2_cmd.arg(arg);
}
}
match buck2_cmd.status() {
Ok(status) if status.success() => {}
_ => std::process::exit(1),
}
}