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            Backend::RuntimeOnly(args) => {
82                let runtime: syn::Path = args.runtime_path();
83                let client: syn::Path = args.client_path();
84                build_runtime_client(exec_build_contracts, runtime, client)
85            }
86        };
87
88        let parser = syn::Attribute::parse_outer;
89        use syn::parse::Parser;
90        let chosen_test_attr = parser
91            .parse_str(&chosen_test_attr)
92            .expect("Failed to parse attribute");
93
94        quote! {
95            #( #attrs )*
96            #( #chosen_test_attr )*
97            #vis fn #fn_name (#possibly_fn_input) #ret {
98                use ::ink_e2e::log_info;
99                ::ink_e2e::LOG_PREFIX.with(|log_prefix| {
100                    let str = format!("test: {}", stringify!(#fn_name));
101                    *log_prefix.borrow_mut() = String::from(str);
102                });
103                log_info("setting up e2e test");
104
105                ::ink_e2e::INIT.call_once(|| {
106                    // A global subscriber might already have been set up.
107                    let _ = ::ink_e2e::tracing_subscriber::fmt::try_init();
108                });
109
110                log_info("creating new client");
111
112                let run = async {
113                    #client_building
114
115                    let __ret = {
116                        #block
117                    };
118                    __ret
119                };
120
121                {
122                    return ::ink_e2e::tokio::runtime::Builder::new_current_thread()
123                        .enable_all()
124                        .build()
125                        .unwrap_or_else(|err| panic!("Failed building the Runtime: {err}"))
126                        .block_on(run);
127                }
128            }
129        }
130    }
131}
132
133fn build_full_client(
134    environment: &syn::Path,
135    contracts: TokenStream2,
136    node_config: Node,
137) -> TokenStream2 {
138    match node_config.url() {
139        Some(url) => {
140            quote! {
141                let contracts = #contracts;
142                let rpc = ::ink_e2e::RpcClient::from_url(#url)
143                    .await
144                    .unwrap_or_else(|err|
145                        ::core::panic!("Error connecting to node at {}: {err:?}", #url)
146                    );
147                let mut client = ::ink_e2e::Client::<
148                    ::ink_e2e::PolkadotConfig,
149                    #environment
150                >::new(rpc, contracts, #url.to_string()).await
151                    .expect("Failed creating Client");
152            }
153        }
154        None => {
155            quote! {
156                let contracts = #contracts;
157                let node_rpc = ::ink_e2e::TestNodeProcess::<::ink_e2e::PolkadotConfig>
158                    ::build_with_env_or_default()
159                    .spawn()
160                    .await
161                    .unwrap_or_else(|err|
162                        ::core::panic!("Error spawning ink-node: {err:?}")
163                    );
164                let mut client = ::ink_e2e::Client::<
165                    ::ink_e2e::PolkadotConfig,
166                    #environment
167                >::new(node_rpc.rpc(), contracts, node_rpc.url().to_string()).await
168                    .expect("Failed creating Client");
169            }
170        }
171    }
172}
173
174fn build_runtime_client(
175    contracts: TokenStream2,
176    runtime: syn::Path,
177    client: syn::Path,
178) -> TokenStream2 {
179    quote! {
180        let contracts = #contracts;
181        let mut client = #client::<_, #runtime>::new(contracts);
182    }
183}