Generate a script to rerun the failed sub-command in cargo-test-bpf (#18582)

* Generate a script to rerun the failed sub-command in cargo-test-bpf

* Generate a script to rerun the failed sub-command in cargo-build-bpf

* Add cargo-build-bpf test for generate-child-script-on-failure option
This commit is contained in:
Dmitri Makarov 2021-07-14 18:02:42 -07:00 committed by GitHub
parent ad3f18f031
commit 6f72f8b1fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 162 additions and 16 deletions

2
.gitignore vendored
View File

@ -27,3 +27,5 @@ log-*/
/spl_*.so /spl_*.so
.DS_Store .DS_Store
# scripts that may be generated by cargo *-bpf commands
**/cargo-*-bpf-child-script-*.sh

View File

@ -26,6 +26,7 @@ struct Config<'a> {
bpf_sdk: PathBuf, bpf_sdk: PathBuf,
dump: bool, dump: bool,
features: Vec<String>, features: Vec<String>,
generate_child_script_on_failure: bool,
no_default_features: bool, no_default_features: bool,
offline: bool, offline: bool,
verbose: bool, verbose: bool,
@ -46,6 +47,7 @@ impl Default for Config<'_> {
bpf_out_dir: None, bpf_out_dir: None,
dump: false, dump: false,
features: vec![], features: vec![],
generate_child_script_on_failure: false,
no_default_features: false, no_default_features: false,
offline: false, offline: false,
verbose: false, verbose: false,
@ -54,13 +56,13 @@ impl Default for Config<'_> {
} }
} }
fn spawn<I, S>(program: &Path, args: I) -> String fn spawn<I, S>(program: &Path, args: I, generate_child_script_on_failure: bool) -> String
where where
I: IntoIterator<Item = S>, I: IntoIterator<Item = S>,
S: AsRef<OsStr>, S: AsRef<OsStr>,
{ {
let args = args.into_iter().collect::<Vec<_>>(); let args = args.into_iter().collect::<Vec<_>>();
print!("Running: {}", program.display()); print!("cargo-build-bpf child: {}", program.display());
for arg in args.iter() { for arg in args.iter() {
print!(" {}", arg.as_ref().to_str().unwrap_or("?")); print!(" {}", arg.as_ref().to_str().unwrap_or("?"));
} }
@ -77,6 +79,29 @@ where
let output = child.wait_with_output().expect("failed to wait on child"); let output = child.wait_with_output().expect("failed to wait on child");
if !output.status.success() { if !output.status.success() {
if !generate_child_script_on_failure {
exit(1);
}
eprintln!("cargo-build-bpf exited on command execution failure");
let script_name = format!(
"cargo-build-bpf-child-script-{}.sh",
program.file_name().unwrap().to_str().unwrap(),
);
let file = File::create(&script_name).unwrap();
let mut out = BufWriter::new(file);
for (key, value) in env::vars() {
writeln!(out, "{}=\"{}\" \\", key, value).unwrap();
}
write!(out, "{}", program.display()).unwrap();
for arg in args.iter() {
write!(out, " {}", arg.as_ref().to_str().unwrap_or("?")).unwrap();
}
writeln!(out).unwrap();
out.flush().unwrap();
eprintln!(
"To rerun the failed command for debugging use {}",
script_name,
);
exit(1); exit(1);
} }
output output
@ -276,7 +301,11 @@ fn check_undefined_symbols(config: &Config, program: &Path) {
.join("llvm-readelf"); .join("llvm-readelf");
let mut readelf_args = vec!["--dyn-symbols"]; let mut readelf_args = vec!["--dyn-symbols"];
readelf_args.push(program.to_str().unwrap()); readelf_args.push(program.to_str().unwrap());
let output = spawn(&readelf, &readelf_args); let output = spawn(
&readelf,
&readelf_args,
config.generate_child_script_on_failure,
);
if config.verbose { if config.verbose {
println!("{}", output); println!("{}", output);
} }
@ -309,7 +338,11 @@ fn link_bpf_toolchain(config: &Config) {
.join("rust"); .join("rust");
let rustup = PathBuf::from("rustup"); let rustup = PathBuf::from("rustup");
let rustup_args = vec!["toolchain", "list", "-v"]; let rustup_args = vec!["toolchain", "list", "-v"];
let rustup_output = spawn(&rustup, &rustup_args); let rustup_output = spawn(
&rustup,
&rustup_args,
config.generate_child_script_on_failure,
);
if config.verbose { if config.verbose {
println!("{}", rustup_output); println!("{}", rustup_output);
} }
@ -321,7 +354,11 @@ fn link_bpf_toolchain(config: &Config) {
let path = it.next(); let path = it.next();
if path.unwrap() != toolchain_path.to_str().unwrap() { if path.unwrap() != toolchain_path.to_str().unwrap() {
let rustup_args = vec!["toolchain", "uninstall", "bpf"]; let rustup_args = vec!["toolchain", "uninstall", "bpf"];
let output = spawn(&rustup, &rustup_args); let output = spawn(
&rustup,
&rustup_args,
config.generate_child_script_on_failure,
);
if config.verbose { if config.verbose {
println!("{}", output); println!("{}", output);
} }
@ -333,7 +370,11 @@ fn link_bpf_toolchain(config: &Config) {
} }
if do_link { if do_link {
let rustup_args = vec!["toolchain", "link", "bpf", toolchain_path.to_str().unwrap()]; let rustup_args = vec!["toolchain", "link", "bpf", toolchain_path.to_str().unwrap()];
let output = spawn(&rustup, &rustup_args); let output = spawn(
&rustup,
&rustup_args,
config.generate_child_script_on_failure,
);
if config.verbose { if config.verbose {
println!("{}", output); println!("{}", output);
} }
@ -475,7 +516,11 @@ fn build_bpf_package(config: &Config, target_directory: &Path, package: &cargo_m
cargo_build_args.push(arg); cargo_build_args.push(arg);
} }
} }
let output = spawn(&cargo_build, &cargo_build_args); let output = spawn(
&cargo_build,
&cargo_build_args,
config.generate_child_script_on_failure,
);
if config.verbose { if config.verbose {
println!("{}", output); println!("{}", output);
} }
@ -520,6 +565,7 @@ fn build_bpf_package(config: &Config, target_directory: &Path, package: &cargo_m
let output = spawn( let output = spawn(
&config.bpf_sdk.join("scripts").join("strip.sh"), &config.bpf_sdk.join("scripts").join("strip.sh"),
&[&program_unstripped_so, &program_so], &[&program_unstripped_so, &program_so],
config.generate_child_script_on_failure,
); );
if config.verbose { if config.verbose {
println!("{}", output); println!("{}", output);
@ -530,6 +576,7 @@ fn build_bpf_package(config: &Config, target_directory: &Path, package: &cargo_m
let output = spawn( let output = spawn(
&config.bpf_sdk.join("scripts").join("dump.sh"), &config.bpf_sdk.join("scripts").join("dump.sh"),
&[&program_unstripped_so, &program_dump], &[&program_unstripped_so, &program_dump],
config.generate_child_script_on_failure,
); );
if config.verbose { if config.verbose {
println!("{}", output); println!("{}", output);
@ -643,6 +690,12 @@ fn main() {
.multiple(true) .multiple(true)
.help("Space-separated list of features to activate"), .help("Space-separated list of features to activate"),
) )
.arg(
Arg::with_name("generate_child_script_on_failure")
.long("generate-child-script-on-failure")
.takes_value(false)
.help("Generate rerun-script.sh to rerun test command on failure"),
)
.arg( .arg(
Arg::with_name("manifest_path") Arg::with_name("manifest_path")
.long("manifest-path") .long("manifest-path")
@ -706,6 +759,7 @@ fn main() {
features: values_t!(matches, "features", String) features: values_t!(matches, "features", String)
.ok() .ok()
.unwrap_or_else(Vec::new), .unwrap_or_else(Vec::new),
generate_child_script_on_failure: matches.is_present("generate_child_script_on_failure"),
no_default_features: matches.is_present("no_default_features"), no_default_features: matches.is_present("no_default_features"),
offline: matches.is_present("offline"), offline: matches.is_present("offline"),
verbose: matches.is_present("verbose"), verbose: matches.is_present("verbose"),

View File

@ -7,7 +7,7 @@ use std::{
#[macro_use] #[macro_use]
extern crate serial_test; extern crate serial_test;
fn run_cargo_build(extra_args: &[&str]) -> Output { fn run_cargo_build(crate_name: &str, extra_args: &[&str]) -> Output {
let cwd = env::current_dir().expect("Unable to get current working directory"); let cwd = env::current_dir().expect("Unable to get current working directory");
let root = cwd let root = cwd
.parent() .parent()
@ -17,7 +17,7 @@ fn run_cargo_build(extra_args: &[&str]) -> Output {
let toml = cwd let toml = cwd
.join("tests") .join("tests")
.join("crates") .join("crates")
.join("noop") .join(crate_name)
.join("Cargo.toml"); .join("Cargo.toml");
let toml = format!("{}", toml.display()); let toml = format!("{}", toml.display());
let mut args = vec!["--bpf-sdk", "../bpf", "--manifest-path", &toml]; let mut args = vec!["--bpf-sdk", "../bpf", "--manifest-path", &toml];
@ -42,7 +42,7 @@ fn run_cargo_build(extra_args: &[&str]) -> Output {
#[test] #[test]
#[serial] #[serial]
fn test_build() { fn test_build() {
let output = run_cargo_build(&[]); let output = run_cargo_build("noop", &[]);
assert!(output.status.success()); assert!(output.status.success());
} }
@ -55,7 +55,7 @@ fn test_dump() {
.status() .status()
.expect("Unable to install rustfilt required for --dump option") .expect("Unable to install rustfilt required for --dump option")
.success()); .success());
let output = run_cargo_build(&["--dump"]); let output = run_cargo_build("noop", &["--dump"]);
assert!(output.status.success()); assert!(output.status.success());
let cwd = env::current_dir().expect("Unable to get current working directory"); let cwd = env::current_dir().expect("Unable to get current working directory");
let dump = cwd let dump = cwd
@ -71,10 +71,25 @@ fn test_dump() {
#[test] #[test]
#[serial] #[serial]
fn test_out_dir() { fn test_out_dir() {
let output = run_cargo_build(&["--bpf-out-dir", "tmp_out"]); let output = run_cargo_build("noop", &["--bpf-out-dir", "tmp_out"]);
assert!(output.status.success()); assert!(output.status.success());
let cwd = env::current_dir().expect("Unable to get current working directory"); let cwd = env::current_dir().expect("Unable to get current working directory");
let dir = cwd.join("tmp_out"); let dir = cwd.join("tmp_out");
assert!(dir.exists()); assert!(dir.exists());
fs::remove_dir_all("tmp_out").expect("Failed to remove tmp_out dir"); fs::remove_dir_all("tmp_out").expect("Failed to remove tmp_out dir");
} }
#[test]
#[serial]
fn test_generate_child_script_on_failre() {
let output = run_cargo_build("fail", &["--generate-child-script-on-failure"]);
assert!(!output.status.success());
let cwd = env::current_dir().expect("Unable to get current working directory");
let scr = cwd
.join("tests")
.join("crates")
.join("fail")
.join("cargo-build-bpf-child-script-cargo.sh");
assert!(scr.exists());
fs::remove_file(scr).expect("Failed to remove script");
}

View File

@ -0,0 +1,18 @@
[package]
name = "fail"
version = "1.8.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
publish = false
[dependencies]
solana-program = { path = "../../../../program", version = "=1.8.0" }
[lib]
crate-type = ["cdylib"]
[workspace]

View File

@ -0,0 +1,15 @@
//! @brief Example Rust-based BPF noop program
extern crate solana_program;
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
};
entrypoint!(process_instruction);
fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
// error to make build fail: no return value
}

View File

@ -4,6 +4,8 @@ use clap::{
use std::{ use std::{
env, env,
ffi::OsStr, ffi::OsStr,
fs::File,
io::{prelude::*, BufWriter},
path::{Path, PathBuf}, path::{Path, PathBuf},
process::exit, process::exit,
process::Command, process::Command,
@ -16,6 +18,7 @@ struct Config {
cargo_build_bpf: PathBuf, cargo_build_bpf: PathBuf,
extra_cargo_test_args: Vec<String>, extra_cargo_test_args: Vec<String>,
features: Vec<String>, features: Vec<String>,
generate_child_script_on_failure: bool,
test_name: Option<String>, test_name: Option<String>,
no_default_features: bool, no_default_features: bool,
no_run: bool, no_run: bool,
@ -33,6 +36,7 @@ impl Default for Config {
cargo_build_bpf: PathBuf::from("cargo-build-bpf"), cargo_build_bpf: PathBuf::from("cargo-build-bpf"),
extra_cargo_test_args: vec![], extra_cargo_test_args: vec![],
features: vec![], features: vec![],
generate_child_script_on_failure: false,
test_name: None, test_name: None,
no_default_features: false, no_default_features: false,
no_run: false, no_run: false,
@ -43,13 +47,13 @@ impl Default for Config {
} }
} }
fn spawn<I, S>(program: &Path, args: I) fn spawn<I, S>(program: &Path, args: I, generate_child_script_on_failure: bool)
where where
I: IntoIterator<Item = S>, I: IntoIterator<Item = S>,
S: AsRef<OsStr>, S: AsRef<OsStr>,
{ {
let args = args.into_iter().collect::<Vec<_>>(); let args = args.into_iter().collect::<Vec<_>>();
print!("Running: {}", program.display()); print!("cargo-test-bpf child: {}", program.display());
for arg in args.iter() { for arg in args.iter() {
print!(" {}", arg.as_ref().to_str().unwrap_or("?")); print!(" {}", arg.as_ref().to_str().unwrap_or("?"));
} }
@ -65,6 +69,29 @@ where
let exit_status = child.wait().expect("failed to wait on child"); let exit_status = child.wait().expect("failed to wait on child");
if !exit_status.success() { if !exit_status.success() {
if !generate_child_script_on_failure {
exit(1);
}
eprintln!("cargo-test-bpf exited on command execution failure");
let script_name = format!(
"cargo-test-bpf-child-script-{}.sh",
program.file_name().unwrap().to_str().unwrap(),
);
let file = File::create(&script_name).unwrap();
let mut out = BufWriter::new(file);
for (key, value) in env::vars() {
writeln!(out, "{}=\"{}\" \\", key, value).unwrap();
}
write!(out, "{}", program.display()).unwrap();
for arg in args.iter() {
write!(out, " {}", arg.as_ref().to_str().unwrap_or("?")).unwrap();
}
writeln!(out).unwrap();
out.flush().unwrap();
eprintln!(
"To rerun the failed command for debugging use {}",
script_name,
);
exit(1); exit(1);
} }
} }
@ -99,7 +126,11 @@ fn test_bpf_package(config: &Config, target_directory: &Path, package: &cargo_me
build_bpf_args.push("--bpf-out-dir"); build_bpf_args.push("--bpf-out-dir");
build_bpf_args.push(&bpf_out_dir); build_bpf_args.push(&bpf_out_dir);
spawn(&config.cargo_build_bpf, &build_bpf_args); spawn(
&config.cargo_build_bpf,
&build_bpf_args,
config.generate_child_script_on_failure,
);
// Pass --bpf-out-dir along to the solana-program-test crate // Pass --bpf-out-dir along to the solana-program-test crate
env::set_var("BPF_OUT_DIR", bpf_out_dir); env::set_var("BPF_OUT_DIR", bpf_out_dir);
@ -124,7 +155,11 @@ fn test_bpf_package(config: &Config, target_directory: &Path, package: &cargo_me
for extra_cargo_test_arg in &config.extra_cargo_test_args { for extra_cargo_test_arg in &config.extra_cargo_test_args {
cargo_args.push(extra_cargo_test_arg); cargo_args.push(extra_cargo_test_arg);
} }
spawn(&config.cargo, &cargo_args); spawn(
&config.cargo,
&cargo_args,
config.generate_child_script_on_failure,
);
} }
fn test_bpf(config: Config, manifest_path: Option<PathBuf>) { fn test_bpf(config: Config, manifest_path: Option<PathBuf>) {
@ -239,6 +274,12 @@ fn main() {
.takes_value(false) .takes_value(false)
.help("Run without accessing the network"), .help("Run without accessing the network"),
) )
.arg(
Arg::with_name("generate_child_script_on_failure")
.long("generate-child-script-on-failure")
.takes_value(false)
.help("Generate rerun-script.sh to rerun test command on failure"),
)
.arg( .arg(
Arg::with_name("verbose") Arg::with_name("verbose")
.short("v") .short("v")
@ -271,6 +312,7 @@ fn main() {
features: values_t!(matches, "features", String) features: values_t!(matches, "features", String)
.ok() .ok()
.unwrap_or_else(Vec::new), .unwrap_or_else(Vec::new),
generate_child_script_on_failure: matches.is_present("generate_child_script_on_failure"),
test_name: value_t!(matches, "test", String).ok(), test_name: value_t!(matches, "test", String).ok(),
no_default_features: matches.is_present("no_default_features"), no_default_features: matches.is_present("no_default_features"),
no_run: matches.is_present("no_run"), no_run: matches.is_present("no_run"),