ink_e2e_macro/lib.rs
1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![doc(
16 html_logo_url = "https://use.ink/img/crate-docs/logo.png",
17 html_favicon_url = "https://use.ink/crate-docs/favicon.png"
18)]
19
20mod codegen;
21mod config;
22mod ir;
23
24use proc_macro::TokenStream;
25use proc_macro2::TokenStream as TokenStream2;
26use syn::Result;
27
28/// Defines an End-to-End test.
29///
30/// The system requirements are:
31///
32/// - A Substrate node with `pallet-revive` installed on the local system. You can e.g.
33/// use [`ink-node`](https://github.com/use-ink/ink-node)
34/// and install it on your PATH, or provide a path to an executable using the
35/// `CONTRACTS_NODE` environment variable.
36///
37/// Before the test function is invoked the contract will be built. Any errors that occur
38/// during the contract build will prevent the test function from being invoked.
39///
40/// ## Header Arguments
41///
42/// The `#[ink_e2e::test]` macro can be provided with additional arguments.
43///
44/// ### Custom Environment
45///
46/// You can specify the usage of a custom environment:
47///
48/// ```ignore
49/// #[ink_e2e::test(environment = crate::EnvironmentWithManyTopics)]
50/// ```
51///
52/// Our documentation contains [an explainer of what custom environments are](https://use.ink/basics/chain-environment-types).
53/// For a full example [see here](https://github.com/use-ink/ink-examples/tree/v5.x.x/custom-environment).
54///
55/// ### Custom Backend
56///
57/// You can switch the E2E test to use the [DRink!](https://use.ink/basics/contract-testing/drink)
58/// testing framework with this syntax:
59///
60/// ```
61/// type E2EResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
62///
63/// #[ink_e2e::test(runtime)]
64/// async fn runtime_call_works(mut client: Client) -> E2EResult<()> {
65/// // ...
66/// }
67/// ```
68///
69/// This defaults to `ink_runtime::DefaultRuntime` together with the built-in
70/// `ink_runtime::RuntimeClient`.
71///
72/// In this configuration the test will not run against a node that is running in the
73/// background, but against an in-process slimmed down `pallet-revive` execution
74/// environment.
75///
76/// Please see [the page on testing with DRink!](https://use.ink/basics/contract-testing/drink)
77/// in our documentation for more details.
78/// For a full example [see here](https://github.com/use-ink/ink-examples/tree/v5.x.x/e2e-runtime-only-backend).
79///
80/// You can also provide a custom runtime:
81///
82/// ```
83/// #[ink_e2e::test(runtime(crate::CustomRuntime))]
84/// async fn runtime_call_works(mut client: Client) -> E2EResult<()> {
85/// // ...
86/// }
87/// ```
88///
89/// # Example
90///
91/// ```
92/// # use ink::env::{
93/// # Environment,
94/// # DefaultEnvironment,
95/// # };
96/// # type AccountId = <DefaultEnvironment as Environment>::AccountId;
97/// #
98/// #[ink::contract]
99/// mod my_module {
100/// #[ink(storage)]
101/// pub struct MyContract {}
102///
103/// impl MyContract {
104/// #[ink(constructor)]
105/// pub fn new() -> Self {
106/// Self {}
107/// }
108///
109/// #[ink(message)]
110/// pub fn my_message(&self) {}
111/// }
112/// }
113///
114/// type E2EResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
115///
116/// #[ink_e2e::test]
117/// async fn e2e_test(mut client: ::ink_e2e::Client<C, E>) -> E2EResult<()> {
118/// // given
119/// use my_module::MyContract;
120/// let mut constructor = MyContract::new();
121/// let contract = client
122/// .instantiate("contract_transfer", &ink_e2e::bob(), &mut constructor)
123/// .submit()
124/// .await
125/// .expect("instantiate failed");
126/// let mut call_builder = contract.call_builder::<MyContract>();
127///
128/// // when
129/// let my_message = call_builder.my_message();
130/// let call_res = client
131/// .call(&ink_e2e::eve(), &my_message)
132/// .submit()
133/// .await
134/// .expect("call failed");
135///
136/// // then
137/// assert!(call_res.is_ok());
138///
139/// Ok(())
140/// }
141/// ```
142///
143/// You can also build the `Keypair` type yourself, without going through
144/// the pre-defined functions (`ink_e2e::alice()`, …):
145///
146/// ```
147/// use std::str::FromStr;
148/// let suri = ::ink_e2e::subxt_signer::SecretUri::from_str("//Alice").unwrap();
149/// let alice = ::ink_e2e::Keypair::from_uri(&suri).unwrap();
150/// ```
151#[proc_macro_attribute]
152pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
153 generate(attr.into(), item.into()).into()
154}
155
156fn generate(attr: TokenStream2, input: TokenStream2) -> TokenStream2 {
157 match generate_or_err(attr, input) {
158 Ok(tokens) => tokens,
159 Err(err) => err.to_compile_error(),
160 }
161}
162
163fn generate_or_err(attr: TokenStream2, input: TokenStream2) -> Result<TokenStream2> {
164 let test_definition = ir::InkE2ETest::new(attr, input)?;
165 let codegen = codegen::InkE2ETest::from(test_definition);
166 Ok(codegen.generate_code())
167}