diff --git a/Cargo.toml b/Cargo.toml index 9be04547b5..905ae05850 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ members = [ "runtime/store-tool", "sdk", "sdk/cargo-build-bpf", + "sdk/cargo-test-bpf", "scripts", "stake-accounts", "stake-monitor", diff --git a/cargo-build-bpf b/cargo-build-bpf index f232dee8de..5202bba451 100755 --- a/cargo-build-bpf +++ b/cargo-build-bpf @@ -1,5 +1,13 @@ #!/usr/bin/env bash here=$(dirname "$0") + +maybe_bpf_sdk="--bpf-sdk $here/sdk/bpf" +for a in "$@"; do + if [[ $a = --bpf-sdk ]]; then + maybe_bpf_sdk= + fi +done + set -x -exec $here/cargo run --manifest-path $here/sdk/cargo-build-bpf/Cargo.toml -- --bpf-sdk $here/sdk/bpf "$@" +exec "$here"/cargo run --manifest-path "$here"/sdk/cargo-build-bpf/Cargo.toml -- $maybe_bpf_sdk "$@" diff --git a/cargo-test-bpf b/cargo-test-bpf new file mode 100755 index 0000000000..55177a3399 --- /dev/null +++ b/cargo-test-bpf @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +here=$(dirname "$0") + +maybe_bpf_sdk="--bpf-sdk $here/sdk/bpf" +for a in "$@"; do + if [[ $a = --bpf-sdk ]]; then + maybe_bpf_sdk= + fi +done + +export CARGO_BUILD_BPF="$here"/cargo-build-bpf +set -x +exec "$here"/cargo run --manifest-path "$here"/sdk/cargo-test-bpf/Cargo.toml -- $maybe_bpf_sdk "$@" diff --git a/scripts/cargo-install-all.sh b/scripts/cargo-install-all.sh index 929640cedb..4b13886479 100755 --- a/scripts/cargo-install-all.sh +++ b/scripts/cargo-install-all.sh @@ -78,6 +78,7 @@ else BINS=( cargo-build-bpf + cargo-test-bpf solana solana-bench-exchange solana-bench-tps diff --git a/sdk/cargo-build-bpf/src/main.rs b/sdk/cargo-build-bpf/src/main.rs index 93a2a3622b..08b9f6a43e 100644 --- a/sdk/cargo-build-bpf/src/main.rs +++ b/sdk/cargo-build-bpf/src/main.rs @@ -11,12 +11,13 @@ use std::{ }; struct Config { - bpf_sdk: PathBuf, bpf_out_dir: Option, + bpf_sdk: PathBuf, dump: bool, features: Vec, manifest_path: Option, no_default_features: bool, + verbose: bool, } impl Default for Config { @@ -29,10 +30,11 @@ impl Default for Config { .to_path_buf() .join("sdk/bpf"), bpf_out_dir: None, + dump: false, features: vec![], manifest_path: None, no_default_features: false, - dump: true, + verbose: false, } } } @@ -152,22 +154,25 @@ fn build_bpf(config: Config) { } let xargo_build = config.bpf_sdk.join("rust/xargo-build.sh"); - let mut spawn_args = vec![]; + let mut xargo_build_args = vec![]; if config.no_default_features { - spawn_args.push("--no-default-features"); + xargo_build_args.push("--no-default-features"); } for feature in &config.features { - spawn_args.push("--features"); - spawn_args.push(feature); + xargo_build_args.push("--features"); + xargo_build_args.push(feature); } if legacy_program_feature_present { if !config.no_default_features { - spawn_args.push("--no-default-features"); + xargo_build_args.push("--no-default-features"); } - spawn_args.push("--features=program"); + xargo_build_args.push("--features=program"); } - spawn(&config.bpf_sdk.join(xargo_build), &spawn_args); + if config.verbose { + xargo_build_args.push("--verbose"); + } + spawn(&config.bpf_sdk.join(xargo_build), &xargo_build_args); if let Some(program_name) = program_name { let program_unstripped_so = target_build_directory.join(&format!("{}.so", program_name)); @@ -224,6 +229,13 @@ fn main() { .takes_value(false) .help("Dump ELF information to a text file on success"), ) + .arg( + Arg::with_name("verbose") + .short("v") + .long("verbose") + .takes_value(false) + .help("Use verbose output"), + ) .arg( Arg::with_name("features") .long("features") @@ -281,6 +293,7 @@ fn main() { .unwrap_or_else(Vec::new), manifest_path: value_t!(matches, "manifest_path", PathBuf).ok(), no_default_features: matches.is_present("no_default_features"), + verbose: matches.is_present("verbose"), }; build_bpf(config); } diff --git a/sdk/cargo-test-bpf/Cargo.toml b/sdk/cargo-test-bpf/Cargo.toml new file mode 100644 index 0000000000..24e65e417a --- /dev/null +++ b/sdk/cargo-test-bpf/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "solana-cargo-test-bpf" +version = "1.5.0" +description = "Execute all unit and integration tests after building with the Solana BPF SDK" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +homepage = "https://solana.com/" +license = "Apache-2.0" +edition = "2018" + +[dependencies] +clap = "2.33.3" + +[[bin]] +name = "cargo-test-bpf" +path = "src/main.rs" diff --git a/sdk/cargo-test-bpf/src/main.rs b/sdk/cargo-test-bpf/src/main.rs new file mode 100644 index 0000000000..cdc7802f79 --- /dev/null +++ b/sdk/cargo-test-bpf/src/main.rs @@ -0,0 +1,192 @@ +use clap::{ + crate_description, crate_name, crate_version, value_t, values_t, App, AppSettings, Arg, +}; +use std::{ + env, + ffi::OsStr, + path::{Path, PathBuf}, + process::exit, + process::Command, +}; + +struct Config { + bpf_sdk: Option, + bpf_out_dir: Option, + cargo: PathBuf, + cargo_build_bpf: PathBuf, + extra_cargo_test_args: Vec, + features: Vec, + manifest_path: Option, + no_default_features: bool, + verbose: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + bpf_sdk: None, + bpf_out_dir: None, + cargo: PathBuf::from("cargo"), + cargo_build_bpf: PathBuf::from("cargo-build-bpf"), + extra_cargo_test_args: vec![], + features: vec![], + manifest_path: None, + no_default_features: false, + verbose: false, + } + } +} + +fn spawn(program: &Path, args: I) +where + I: IntoIterator, + S: AsRef, +{ + let args = args.into_iter().collect::>(); + print!("Running: {}", program.display()); + for arg in args.iter() { + print!(" {}", arg.as_ref().to_str().unwrap_or("?")); + } + println!(); + + let mut child = Command::new(program) + .args(&args) + .spawn() + .unwrap_or_else(|err| { + eprintln!("Failed to execute {}: {}", program.display(), err); + exit(1); + }); + + let exit_status = child.wait().expect("failed to wait on child"); + if !exit_status.success() { + exit(1); + } +} + +fn test_bpf(config: Config) { + let mut cargo_args = vec![]; + if config.no_default_features { + cargo_args.push("--no-default-features"); + } + for feature in &config.features { + cargo_args.push("--features"); + cargo_args.push(feature); + } + if let Some(manifest_path) = config.manifest_path.as_ref() { + cargo_args.push("--manifest-path"); + cargo_args.push(&manifest_path); + } + if config.verbose { + cargo_args.push("--verbose"); + } + + let mut build_bpf_args = cargo_args.clone(); + if let Some(bpf_sdk) = config.bpf_sdk.as_ref() { + build_bpf_args.push("--bpf-sdk"); + build_bpf_args.push(bpf_sdk); + } + if let Some(bpf_out_dir) = config.bpf_out_dir.as_ref() { + build_bpf_args.push("--bpf-out-dir"); + build_bpf_args.push(bpf_out_dir); + } + spawn(&config.cargo_build_bpf, &build_bpf_args); + + env::set_var("bpf", "1"); // Hint to solana-program-test that it should load BPF programs + cargo_args.insert(0, "test"); + for extra_cargo_test_arg in &config.extra_cargo_test_args { + cargo_args.push(&extra_cargo_test_arg); + } + spawn(&config.cargo, &cargo_args); +} + +fn main() { + let mut args = env::args().collect::>(); + // When run as a cargo subcommand, the first program argument is the subcommand name. + // Remove it + if let Some(arg1) = args.get(1) { + if arg1 == "test-bpf" { + args.remove(1); + } + } + + let matches = App::new(crate_name!()) + .about(crate_description!()) + .version(crate_version!()) + .setting(AppSettings::TrailingVarArg) + .arg( + Arg::with_name("bpf_sdk") + .long("bpf-sdk") + .value_name("PATH") + .takes_value(true) + .help("Path to the Solana BPF SDK"), + ) + .arg( + Arg::with_name("features") + .long("features") + .value_name("FEATURES") + .takes_value(true) + .multiple(true) + .help("Space-separated list of features to activate"), + ) + .arg( + Arg::with_name("no_default_features") + .long("no-default-features") + .takes_value(false) + .help("Do not activate the `default` feature"), + ) + .arg( + Arg::with_name("manifest_path") + .long("manifest-path") + .value_name("PATH") + .takes_value(true) + .help("Path to Cargo.toml"), + ) + .arg( + Arg::with_name("bpf_out_dir") + .long("bpf-out-dir") + .value_name("DIRECTORY") + .takes_value(true) + .help("Place final BPF build artifacts in this directory"), + ) + .arg( + Arg::with_name("verbose") + .short("v") + .long("verbose") + .takes_value(false) + .help("Use verbose output"), + ) + .arg( + Arg::with_name("extra_cargo_test_args") + .value_name("extra args for cargo test") + .index(1) + .multiple(true) + .help("All extra arguments are passed through to cargo test"), + ) + .get_matches_from(args); + + let mut config = Config { + bpf_sdk: value_t!(matches, "bpf_sdk", String).ok(), + bpf_out_dir: value_t!(matches, "bpf_out_dir", String).ok(), + extra_cargo_test_args: values_t!(matches, "extra_cargo_test_args", String) + .ok() + .unwrap_or_else(Vec::new), + features: values_t!(matches, "features", String) + .ok() + .unwrap_or_else(Vec::new), + manifest_path: value_t!(matches, "manifest_path", String).ok(), + no_default_features: matches.is_present("no_default_features"), + verbose: matches.is_present("verbose"), + ..Config::default() + }; + + if let Ok(cargo_build_bpf) = env::var("CARGO_BUILD_BPF") { + config.cargo_build_bpf = PathBuf::from(cargo_build_bpf); + } + if let Ok(cargo_build_bpf) = env::var("CARGO") { + config.cargo = PathBuf::from(cargo_build_bpf); + } + + test_bpf(config); + + // TODO: args after -- go to ct +}