Skip to content

Commit

Permalink
[E2E alternative backend]: Backend choice (#1864)
Browse files Browse the repository at this point in the history
* Parse backend type

* Config tests

* Extract client building

* OCD

* CHANGELOG.md

* Add drink dependency, fix with wasm-instrument

* ChainApi

* Instantiate

* Calling

* Upload

* Use all arguments

* Build client in macro

* fmt, implement e2e backend

* remove actor types

* convert accounts and hashes

* get rid of session

* add dedicated example

* example working

* clean a bit

* merging cleanup

* use published drink

* Review

* Add DRink! as valid word

* Give it up
  • Loading branch information
pmikolajczyk41 authored Aug 24, 2023
1 parent a71990f commit 7d0e0c8
Show file tree
Hide file tree
Showing 14 changed files with 672 additions and 132 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make E2E testcases generic over `E2EBackend` trait - [#1867](https://github.com/paritytech/ink/pull/1867)
- Modify static buffer size via environmental variables - [#1869](https://github.com/paritytech/ink/pull/1869)
- Persist static buffer size in metadata - [#1880](https://github.com/paritytech/ink/pull/1880)
- Add backend choice to the E2E testcase configuration ‒ [#1864](https://github.com/paritytech/ink/pull/1864)

### Added
- Schema generation - [#1765](https://github.com/paritytech/ink/pull/1765)
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ cargo_metadata = { version = "0.17.0" }
cfg-if = { version = "1.0" }
contract-build = { version = "3.2.0" }
derive_more = { version = "0.99.17", default-features = false }
drink = { version = "0.1.2" }
either = { version = "1.5", default-features = false }
funty = { version = "2.0.0" }
heck = { version = "0.4.0" }
Expand Down Expand Up @@ -72,6 +73,7 @@ tokio = { version = "1.18.2" }
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.17" }
trybuild = { version = "1.0.60" }
wasm-instrument = { version = "0.4.0", features = ["sign_ext"] }
which = { version = "4.4.0" }
xxhash-rust = { version = "0.8" }
const_env = { version = "0.1"}
Expand Down
2 changes: 2 additions & 0 deletions crates/e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ink_primitives = { workspace = true, default-features = true }

cargo_metadata = { workspace = true }
contract-build = { workspace = true }
drink = { workspace = true }
funty = { workspace = true }
impl-serde = { workspace = true }
jsonrpsee = { workspace = true, features = ["ws-client"] }
Expand All @@ -33,6 +34,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] }
scale = { package = "parity-scale-codec", workspace = true }
subxt = { workspace = true }
subxt-signer = { workspace = true, features = ["subxt", "sr25519"] }
wasm-instrument = { workspace = true }

# Substrate
pallet-contracts-primitives = { workspace = true }
Expand Down
91 changes: 54 additions & 37 deletions crates/e2e/macro/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::ir;
use crate::{
config::Backend,
ir,
};
use derive_more::From;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;

const DEFAULT_CONTRACTS_NODE: &str = "substrate-contracts-node";

/// Generates code for the `[ink::e2e_test]` macro.
#[derive(From)]
pub struct InkE2ETest {
Expand Down Expand Up @@ -61,23 +66,10 @@ impl InkE2ETest {
}
};

const DEFAULT_CONTRACTS_NODE: &str = "substrate-contracts-node";

// use the user supplied `CONTRACTS_NODE` or default to `substrate-contracts-node`
let contracts_node: &'static str =
option_env!("CONTRACTS_NODE").unwrap_or(DEFAULT_CONTRACTS_NODE);

// check the specified 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.")
}
}
let client_building = match self.test.config.backend() {
Backend::Full => build_full_client(&environment, exec_build_contracts),
Backend::RuntimeOnly => build_runtime_client(exec_build_contracts),
};

quote! {
#( #attrs )*
Expand All @@ -97,24 +89,7 @@ impl InkE2ETest {
log_info("creating new client");

let run = async {
// spawn a contracts node process just for this test
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 contracts = #exec_build_contracts;

let mut client = ::ink_e2e::Client::<
::ink_e2e::PolkadotConfig,
#environment
>::new(
node_proc.client(),
contracts,
).await;
#client_building

let __ret = {
#block
Expand All @@ -126,10 +101,52 @@ impl InkE2ETest {
return ::ink_e2e::tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap_or_else(|err| panic!("Failed building the Runtime: {}", err))
.unwrap_or_else(|err| panic!("Failed building the Runtime: {err}"))
.block_on(run);
}
}
}
}
}

fn build_full_client(environment: &syn::Path, contracts: TokenStream2) -> TokenStream2 {
// Use the user supplied `CONTRACTS_NODE` or default to `DEFAULT_CONTRACTS_NODE`.
let contracts_node: &'static str =
option_env!("CONTRACTS_NODE").unwrap_or(DEFAULT_CONTRACTS_NODE);

// Check the specified 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! {
// Spawn a contracts node process just for this test.
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 contracts = #contracts;
let mut client = ::ink_e2e::Client::<
::ink_e2e::PolkadotConfig,
#environment
>::new(node_proc.client(), contracts).await;
}
}

fn build_runtime_client(contracts: TokenStream2) -> TokenStream2 {
quote! {
let contracts = #contracts;
let mut client = ::ink_e2e::DrinkClient::new(contracts);
}
}
89 changes: 89 additions & 0 deletions crates/e2e/macro/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,38 @@ use ink_ir::{
utils::duplicate_config_err,
};

/// The type of the architecture that should be used to run test.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub enum Backend {
/// The standard approach with running dedicated single-node blockchain in a
/// background process.
#[default]
Full,
/// The lightweight approach skipping node layer.
///
/// This runs a runtime emulator within `TestExternalities` (using drink! library) in
/// the same process as the test.
RuntimeOnly,
}

impl TryFrom<syn::LitStr> for Backend {
type Error = syn::Error;

fn try_from(value: syn::LitStr) -> Result<Self, Self::Error> {
match value.value().as_str() {
"full" => Ok(Self::Full),
"runtime_only" | "runtime-only" => Ok(Self::RuntimeOnly),
_ => {
Err(format_err_spanned!(
value,
"unknown backend `{}` for ink! E2E test configuration argument",
value.value()
))
}
}
}
}

/// The End-to-End test configuration.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct E2EConfig {
Expand All @@ -30,6 +62,8 @@ pub struct E2EConfig {
/// [`DefaultEnvironment`](https://docs.rs/ink_env/4.1.0/ink_env/enum.DefaultEnvironment.html)
/// will be used.
environment: Option<syn::Path>,
/// The type of the architecture that should be used to run test.
backend: Backend,
}

impl TryFrom<ast::AttributeArgs> for E2EConfig {
Expand All @@ -38,6 +72,7 @@ impl TryFrom<ast::AttributeArgs> for E2EConfig {
fn try_from(args: ast::AttributeArgs) -> Result<Self, Self::Error> {
let mut additional_contracts: Option<(syn::LitStr, ast::MetaNameValue)> = None;
let mut environment: Option<(syn::Path, ast::MetaNameValue)> = None;
let mut backend: Option<(syn::LitStr, ast::MetaNameValue)> = None;

for arg in args.into_iter() {
if arg.name.is_ident("additional_contracts") {
Expand Down Expand Up @@ -69,6 +104,18 @@ impl TryFrom<ast::AttributeArgs> for E2EConfig {
"expected a path for `environment` ink! E2E test configuration argument",
));
}
} else if arg.name.is_ident("backend") {
if let Some((_, ast)) = backend {
return Err(duplicate_config_err(ast, arg, "backend", "E2E test"))
}
if let ast::MetaValue::Lit(syn::Lit::Str(lit_str)) = &arg.value {
backend = Some((lit_str.clone(), arg))
} else {
return Err(format_err_spanned!(
arg,
"expected a string literal for `backend` ink! E2E test configuration argument",
));
}
} else {
return Err(format_err_spanned!(
arg,
Expand All @@ -80,10 +127,15 @@ impl TryFrom<ast::AttributeArgs> for E2EConfig {
.map(|(value, _)| value.value().split(' ').map(String::from).collect())
.unwrap_or_else(Vec::new);
let environment = environment.map(|(path, _)| path);
let backend = backend
.map(|(b, _)| Backend::try_from(b))
.transpose()?
.unwrap_or_default();

Ok(E2EConfig {
additional_contracts,
environment,
backend,
})
}
}
Expand All @@ -99,6 +151,11 @@ impl E2EConfig {
pub fn environment(&self) -> Option<syn::Path> {
self.environment.clone()
}

/// The type of the architecture that should be used to run test.
pub fn backend(&self) -> Backend {
self.backend
}
}

#[cfg(test)]
Expand Down Expand Up @@ -180,19 +237,51 @@ mod tests {
);
}

#[test]
fn backend_must_be_literal() {
assert_try_from(
syn::parse_quote! { backend = full },
Err("expected a string literal for `backend` ink! E2E test configuration argument"),
);
}

#[test]
fn duplicate_backend_fails() {
assert_try_from(
syn::parse_quote! {
backend = "full",
backend = "runtime-only",
},
Err("encountered duplicate ink! E2E test `backend` configuration argument"),
);
}

#[test]
fn specifying_backend_works() {
assert_try_from(
syn::parse_quote! { backend = "runtime-only" },
Ok(E2EConfig {
backend: Backend::RuntimeOnly,
..Default::default()
}),
);
}

#[test]
fn full_config_works() {
assert_try_from(
syn::parse_quote! {
additional_contracts = "adder/Cargo.toml flipper/Cargo.toml",
environment = crate::CustomEnvironment,
backend = "full",
},
Ok(E2EConfig {
additional_contracts: vec![
"adder/Cargo.toml".into(),
"flipper/Cargo.toml".into(),
],
environment: Some(syn::parse_quote! { crate::CustomEnvironment }),
backend: Backend::Full,
}),
);
}
Expand Down
Loading

0 comments on commit 7d0e0c8

Please sign in to comment.