ink_e2e_macro/
codegen.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
15use crate::{
16    config::{
17        Backend,
18        Node,
19    },
20    ir,
21};
22use derive_more::From;
23use proc_macro2::TokenStream as TokenStream2;
24use quote::quote;
25
26/// Generates code for the `[ink_e2e::test]` macro.
27#[derive(From)]
28pub struct InkE2ETest {
29    /// The test function to generate code for.
30    test: ir::InkE2ETest,
31}
32
33impl InkE2ETest {
34    /// Generates the code for `#[ink_e2e:test]`.
35    pub fn generate_code(&self) -> TokenStream2 {
36        #[cfg(clippy)]
37        if true {
38            return quote! {};
39        }
40
41        let item_fn = &self.test.item_fn.item_fn;
42        let fn_name = &item_fn.sig.ident;
43        let block = &item_fn.block;
44        let fn_return_type = &item_fn.sig.output;
45        let vis = &item_fn.vis;
46        let attrs = &item_fn.attrs;
47        let ret = match fn_return_type {
48            syn::ReturnType::Default => quote! {},
49            syn::ReturnType::Type(rarrow, ret_type) => quote! { #rarrow #ret_type },
50        };
51
52        let environment = self
53            .test
54            .config
55            .environment()
56            .unwrap_or_else(|| syn::parse_quote! { ::ink::env::DefaultEnvironment });
57
58        let chosen_test_attr = self
59            .test
60            .config
61            .replace_test_attr()
62            .unwrap_or_else(|| "#[test]".to_string());
63        let possibly_fn_input = if chosen_test_attr == "#[test]" {
64            quote! {}
65        } else {
66            let inputs = &item_fn.sig.inputs;
67            quote! { #inputs }
68        };
69
70        let features = self.test.config.features();
71        let exec_build_contracts = quote! {
72            ::ink_e2e::build_root_and_contract_dependencies(
73                vec![#( #features.to_string() ),*]
74            )
75        };
76
77        let client_building = match self.test.config.backend() {
78            Backend::Node(node_config) => {
79                build_full_client(&environment, exec_build_contracts, node_config)
80            }
81            #[cfg(any(test, feature = "sandbox"))]
82            Backend::RuntimeOnly(runtime) => {
83                build_runtime_client(exec_build_contracts, runtime.into())
84            }
85        };
86
87        let parser = syn::Attribute::parse_outer;
88        use syn::parse::Parser;
89        let chosen_test_attr = parser
90            .parse_str(&chosen_test_attr)
91            .expect("Failed to parse attribute");
92
93        quote! {
94            #( #attrs )*
95            #( #chosen_test_attr )*
96            #vis fn #fn_name (#possibly_fn_input) #ret {
97                use ::ink_e2e::log_info;
98                ::ink_e2e::LOG_PREFIX.with(|log_prefix| {
99                    let str = format!("test: {}", stringify!(#fn_name));
100                    *log_prefix.borrow_mut() = String::from(str);
101                });
102                log_info("setting up e2e test");
103
104                ::ink_e2e::INIT.call_once(|| {
105                    // A global subscriber might already have been set up.
106                    let _ = ::ink_e2e::tracing_subscriber::fmt::try_init();
107                });
108
109                log_info("creating new client");
110
111                let run = async {
112                    #client_building
113
114                    let __ret = {
115                        #block
116                    };
117                    __ret
118                };
119
120                {
121                    return ::ink_e2e::tokio::runtime::Builder::new_current_thread()
122                        .enable_all()
123                        .build()
124                        .unwrap_or_else(|err| panic!("Failed building the Runtime: {err}"))
125                        .block_on(run);
126                }
127            }
128        }
129    }
130}
131
132fn build_full_client(
133    environment: &syn::Path,
134    contracts: TokenStream2,
135    node_config: Node,
136) -> TokenStream2 {
137    match node_config.url() {
138        Some(url) => {
139            quote! {
140                let contracts = #contracts;
141                let rpc = ::ink_e2e::RpcClient::from_url(#url)
142                    .await
143                    .unwrap_or_else(|err|
144                        ::core::panic!("Error connecting to node at {}: {err:?}", #url)
145                    );
146                let mut client = ::ink_e2e::Client::<
147                    ::ink_e2e::PolkadotConfig,
148                    #environment
149                >::new(rpc, contracts, #url.to_string()).await
150                    .expect("Failed creating Client");
151            }
152        }
153        None => {
154            quote! {
155                let contracts = #contracts;
156                let node_rpc = ::ink_e2e::TestNodeProcess::<::ink_e2e::PolkadotConfig>
157                    ::build_with_env_or_default()
158                    .spawn()
159                    .await
160                    .unwrap_or_else(|err|
161                        ::core::panic!("Error spawning ink-node: {err:?}")
162                    );
163                let mut client = ::ink_e2e::Client::<
164                    ::ink_e2e::PolkadotConfig,
165                    #environment
166                >::new(node_rpc.rpc(), contracts, node_rpc.url().to_string()).await
167                    .expect("Failed creating Client");
168            }
169        }
170    }
171}
172
173#[cfg(any(test, feature = "sandbox"))]
174fn build_runtime_client(contracts: TokenStream2, runtime: syn::Path) -> TokenStream2 {
175    quote! {
176        let contracts = #contracts;
177        let mut client = ::ink_e2e::SandboxClient::<_, #runtime>::new(contracts);
178    }
179}