use crate::ir;
use core::cell::RefCell;
use derive_more::From;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::{
collections::HashMap,
sync::Once,
};
static BUILD_ONCE: Once = Once::new();
thread_local! {
pub static ALREADY_BUILT_CONTRACTS: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
}
pub fn already_built_contracts() -> HashMap<String, String> {
ALREADY_BUILT_CONTRACTS.with(|already_built| already_built.borrow().clone())
}
pub fn set_already_built_contracts(hash_map: HashMap<String, String>) {
ALREADY_BUILT_CONTRACTS.with(|metadata_paths| {
*metadata_paths.borrow_mut() = hash_map;
});
}
#[derive(From)]
pub struct InkE2ETest {
test: ir::InkE2ETest,
}
impl InkE2ETest {
pub fn generate_code(&self) -> TokenStream2 {
#[cfg(clippy)]
if true {
return quote! {}
}
let item_fn = &self.test.item_fn.item_fn;
let fn_name = &item_fn.sig.ident;
let block = &item_fn.block;
let fn_return_type = &item_fn.sig.output;
let vis = &item_fn.vis;
let attrs = &item_fn.attrs;
let ret = match fn_return_type {
syn::ReturnType::Default => quote! {},
syn::ReturnType::Type(rarrow, ret_type) => quote! { #rarrow #ret_type },
};
let environment = self
.test
.config
.environment()
.unwrap_or_else(|| syn::parse_quote! { ::ink::env::DefaultEnvironment });
let mut additional_contracts: Vec<String> =
self.test.config.additional_contracts();
let default_main_contract_manifest_path = String::from("Cargo.toml");
let mut contracts_to_build_and_import = vec![default_main_contract_manifest_path];
contracts_to_build_and_import.append(&mut additional_contracts);
let mut already_built_contracts = already_built_contracts();
if already_built_contracts.is_empty() {
BUILD_ONCE.call_once(|| {
env_logger::init();
for manifest_path in contracts_to_build_and_import {
let dest_metadata = build_contract(&manifest_path);
let _ = already_built_contracts.insert(manifest_path, dest_metadata);
}
set_already_built_contracts(already_built_contracts.clone());
});
} else if !already_built_contracts.is_empty() {
for manifest_path in contracts_to_build_and_import {
if already_built_contracts.get("Cargo.toml").is_none() {
let dest_metadata = build_contract(&manifest_path);
let _ = already_built_contracts.insert(manifest_path, dest_metadata);
}
}
set_already_built_contracts(already_built_contracts.clone());
}
assert!(
!already_built_contracts.is_empty(),
"built contract artifacts must exist here"
);
let contracts = already_built_contracts.values().map(|bundle_path| {
quote! { #bundle_path }
});
const DEFAULT_CONTRACTS_NODE: &str = "substrate-contracts-node";
let contracts_node: &'static str =
option_env!("CONTRACTS_NODE").unwrap_or(DEFAULT_CONTRACTS_NODE);
if which::which(contracts_node).is_err() {
if contracts_node == DEFAULT_CONTRACTS_NODE {
panic!(
"The '{DEFAULT_CONTRACTS_NODE}' executable was not found. Install '{DEFAULT_CONTRACTS_NODE}' on the PATH, \
or specify the `CONTRACTS_NODE` environment variable.",
)
} else {
panic!("The contracts node executable '{contracts_node}' was not found.")
}
}
quote! {
#( #attrs )*
#[test]
#vis fn #fn_name () #ret {
use ::ink_e2e::log_info;
::ink_e2e::LOG_PREFIX.with(|log_prefix| {
let str = format!("test: {}", stringify!(#fn_name));
*log_prefix.borrow_mut() = String::from(str);
});
log_info("setting up e2e test");
::ink_e2e::INIT.call_once(|| {
::ink_e2e::env_logger::init();
});
log_info("creating new client");
let run = async {
let node_proc = ::ink_e2e::TestNodeProcess::<::ink_e2e::PolkadotConfig>
::build(#contracts_node)
.spawn()
.await
.unwrap_or_else(|err|
::core::panic!("Error spawning substrate-contracts-node: {:?}", err)
);
let mut client = ::ink_e2e::Client::<
::ink_e2e::PolkadotConfig,
#environment
>::new(
node_proc.client(),
[ #( #contracts ),* ]
).await;
let __ret = {
#block
};
__ret
};
{
return ::ink_e2e::tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap_or_else(|err| panic!("Failed building the Runtime: {}", err))
.block_on(run);
}
}
}
}
}
fn build_contract(path_to_cargo_toml: &str) -> String {
use contract_build::{
BuildArtifacts,
BuildMode,
ExecuteArgs,
Features,
ManifestPath,
Network,
OptimizationPasses,
OutputType,
UnstableFlags,
Verbosity,
};
let manifest_path = ManifestPath::new(path_to_cargo_toml).unwrap_or_else(|err| {
panic!("Invalid manifest path {path_to_cargo_toml}: {err}")
});
let args = ExecuteArgs {
manifest_path,
verbosity: Verbosity::Default,
build_mode: BuildMode::Debug,
features: Features::default(),
network: Network::Online,
build_artifact: BuildArtifacts::All,
unstable_flags: UnstableFlags::default(),
optimization_passes: Some(OptimizationPasses::default()),
keep_debug_symbols: false,
lint: false,
output_type: OutputType::HumanReadable,
skip_wasm_validation: false,
};
match contract_build::execute(args) {
Ok(build_result) => {
let metadata_result = build_result
.metadata_result
.expect("Metadata artifacts not generated");
metadata_result
.dest_bundle
.canonicalize()
.expect("Invalid dest bundle path")
.to_string_lossy()
.into()
}
Err(err) => {
panic!("contract build for {path_to_cargo_toml} failed: {err}")
}
}
}