Document ProgramTest::new and fix ProgramTest::add_program (#17754)
* document ProgramTest::new * simplify ProgramTest::new doc-string * make ProgramTest::add_program noisier `add_program` (and `new`, implicitly) now prints a warning when the user supplies a bogus program name to a ProgramTest and invokes `test-bpf`. Additionally, it is now impossible to ask for a regular `test` and for the generated ProgramTest to load BPF code instead of native code. Previously, this was caused by a precedence issue: BPF code would always be preferred over native if the program name was valid, regardless of user choice.
This commit is contained in:
parent
8f5e773caf
commit
2aaf55795f
|
@ -393,6 +393,16 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_file(filename: &str) -> Option<PathBuf> {
|
pub fn find_file(filename: &str) -> Option<PathBuf> {
|
||||||
|
for dir in default_shared_object_dirs() {
|
||||||
|
let candidate = dir.join(&filename);
|
||||||
|
if candidate.exists() {
|
||||||
|
return Some(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_shared_object_dirs() -> Vec<PathBuf> {
|
||||||
let mut search_path = vec![];
|
let mut search_path = vec![];
|
||||||
if let Ok(bpf_out_dir) = std::env::var("BPF_OUT_DIR") {
|
if let Ok(bpf_out_dir) = std::env::var("BPF_OUT_DIR") {
|
||||||
search_path.push(PathBuf::from(bpf_out_dir));
|
search_path.push(PathBuf::from(bpf_out_dir));
|
||||||
|
@ -401,15 +411,8 @@ pub fn find_file(filename: &str) -> Option<PathBuf> {
|
||||||
if let Ok(dir) = std::env::current_dir() {
|
if let Ok(dir) = std::env::current_dir() {
|
||||||
search_path.push(dir);
|
search_path.push(dir);
|
||||||
}
|
}
|
||||||
trace!("search path: {:?}", search_path);
|
trace!("BPF .so search path: {:?}", search_path);
|
||||||
|
search_path
|
||||||
for path in search_path {
|
|
||||||
let candidate = path.join(&filename);
|
|
||||||
if candidate.exists() {
|
|
||||||
return Some(candidate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
|
pub fn read_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
|
||||||
|
@ -502,6 +505,13 @@ impl Default for ProgramTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProgramTest {
|
impl ProgramTest {
|
||||||
|
/// Create a `ProgramTest`.
|
||||||
|
///
|
||||||
|
/// This is a wrapper around [`default`] and [`add_program`]. See their documentation for more
|
||||||
|
/// details.
|
||||||
|
///
|
||||||
|
/// [`default`]: #method.default
|
||||||
|
/// [`add_program`]: #method.add_program
|
||||||
pub fn new(
|
pub fn new(
|
||||||
program_name: &str,
|
program_name: &str,
|
||||||
program_id: Pubkey,
|
program_id: Pubkey,
|
||||||
|
@ -579,7 +589,7 @@ impl ProgramTest {
|
||||||
|
|
||||||
/// Add a BPF program to the test environment.
|
/// Add a BPF program to the test environment.
|
||||||
///
|
///
|
||||||
/// `program_name` will also used to locate the BPF shared object in the current or fixtures
|
/// `program_name` will also be used to locate the BPF shared object in the current or fixtures
|
||||||
/// directory.
|
/// directory.
|
||||||
///
|
///
|
||||||
/// If `process_instruction` is provided, the natively built-program may be used instead of the
|
/// If `process_instruction` is provided, the natively built-program may be used instead of the
|
||||||
|
@ -590,20 +600,7 @@ impl ProgramTest {
|
||||||
program_id: Pubkey,
|
program_id: Pubkey,
|
||||||
process_instruction: Option<ProcessInstructionWithContext>,
|
process_instruction: Option<ProcessInstructionWithContext>,
|
||||||
) {
|
) {
|
||||||
let loader = solana_sdk::bpf_loader::id();
|
let add_bpf = |this: &mut ProgramTest, program_file: PathBuf| {
|
||||||
let program_file = find_file(&format!("{}.so", program_name));
|
|
||||||
|
|
||||||
if process_instruction.is_none() && program_file.is_none() {
|
|
||||||
panic!("Unable to add program {} ({})", program_name, program_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (program_file.is_some() && self.prefer_bpf) || process_instruction.is_none() {
|
|
||||||
let program_file = program_file.unwrap_or_else(|| {
|
|
||||||
panic!(
|
|
||||||
"Program file data not available for {} ({})",
|
|
||||||
program_name, program_id
|
|
||||||
);
|
|
||||||
});
|
|
||||||
let data = read_file(&program_file);
|
let data = read_file(&program_file);
|
||||||
info!(
|
info!(
|
||||||
"\"{}\" BPF program from {}{}",
|
"\"{}\" BPF program from {}{}",
|
||||||
|
@ -627,28 +624,87 @@ impl ProgramTest {
|
||||||
.unwrap_or_else(|| "".to_string())
|
.unwrap_or_else(|| "".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
self.add_account(
|
this.add_account(
|
||||||
program_id,
|
program_id,
|
||||||
Account {
|
Account {
|
||||||
lamports: Rent::default().minimum_balance(data.len()).min(1),
|
lamports: Rent::default().minimum_balance(data.len()).min(1),
|
||||||
data,
|
data,
|
||||||
owner: loader,
|
owner: solana_sdk::bpf_loader::id(),
|
||||||
executable: true,
|
executable: true,
|
||||||
rent_epoch: 0,
|
rent_epoch: 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
};
|
||||||
|
|
||||||
|
let add_native = |this: &mut ProgramTest, process_fn: ProcessInstructionWithContext| {
|
||||||
info!("\"{}\" program loaded as native code", program_name);
|
info!("\"{}\" program loaded as native code", program_name);
|
||||||
self.builtins.push(Builtin::new(
|
this.builtins
|
||||||
|
.push(Builtin::new(program_name, program_id, process_fn));
|
||||||
|
};
|
||||||
|
|
||||||
|
let warn_invalid_program_name = || {
|
||||||
|
let valid_program_names = default_shared_object_dirs()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|dir| dir.read_dir().ok())
|
||||||
|
.flat_map(|read_dir| {
|
||||||
|
read_dir.filter_map(|entry| {
|
||||||
|
let path = entry.ok()?.path();
|
||||||
|
if !path.is_file() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match path.extension()?.to_str()? {
|
||||||
|
"so" => Some(path.file_stem()?.to_os_string()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if valid_program_names.is_empty() {
|
||||||
|
// This should be unreachable as `test-bpf` should guarantee at least one shared
|
||||||
|
// object exists somewhere.
|
||||||
|
warn!("No BPF shared objects found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"Possible bogus program name. Ensure the program name ({}) \
|
||||||
|
matches one of the following recognizable program names:",
|
||||||
program_name,
|
program_name,
|
||||||
program_id,
|
);
|
||||||
process_instruction.unwrap_or_else(|| {
|
for name in valid_program_names {
|
||||||
|
warn!(" - {}", name.to_str().unwrap());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let program_file = find_file(&format!("{}.so", program_name));
|
||||||
|
match (self.prefer_bpf, program_file, process_instruction) {
|
||||||
|
// If BPF is preferred (i.e., `test-bpf` is invoked) and a BPF shared object exists,
|
||||||
|
// use that as the program data.
|
||||||
|
(true, Some(file), _) => add_bpf(self, file),
|
||||||
|
|
||||||
|
// If BPF is not required (i.e., we were invoked with `test`), use the provided
|
||||||
|
// processor function as is.
|
||||||
|
//
|
||||||
|
// TODO: figure out why tests hang if a processor panics when running native code.
|
||||||
|
(false, _, Some(process)) => add_native(self, process),
|
||||||
|
|
||||||
|
// Invalid: `test-bpf` invocation with no matching BPF shared object.
|
||||||
|
(true, None, _) => {
|
||||||
|
warn_invalid_program_name();
|
||||||
|
panic!(
|
||||||
|
"Program file data not available for {} ({})",
|
||||||
|
program_name, program_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid: regular `test` invocation without a processor.
|
||||||
|
(false, _, None) => {
|
||||||
panic!(
|
panic!(
|
||||||
"Program processor not available for {} ({})",
|
"Program processor not available for {} ({})",
|
||||||
program_name, program_id
|
program_name, program_id
|
||||||
);
|
);
|
||||||
}),
|
}
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue