ink_e2e_macro/
config.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/// The type of the architecture that should be used to run test.
16#[derive(Clone, Eq, PartialEq, Debug, darling::FromMeta)]
17#[darling(rename_all = "snake_case")]
18pub enum Backend {
19    /// The standard approach with running dedicated single-node blockchain in a
20    /// background process.
21    Node(Node),
22
23    /// The lightweight approach skipping node layer.
24    ///
25    /// This runs a runtime emulator within `TestExternalities`
26    /// the same process as the test.
27    #[cfg(any(test, feature = "sandbox"))]
28    RuntimeOnly(RuntimeOnly),
29}
30
31impl Default for Backend {
32    fn default() -> Self {
33        Backend::Node(Node::Auto)
34    }
35}
36
37/// Configure whether to automatically spawn a node instance for the test or to use
38/// an already running node at the supplied URL.
39#[derive(Clone, Eq, PartialEq, Debug, darling::FromMeta)]
40pub enum Node {
41    /// A fresh node instance will be spawned for the lifetime of the test.
42    #[darling(word)]
43    #[darling(skip)]
44    Auto,
45    /// The test will run against an already running node at the supplied URL.
46    Url(String),
47}
48
49impl Node {
50    /// The URL to the running node, default value can be overridden with
51    /// `CONTRACTS_NODE_URL`.
52    ///
53    /// Returns `None` if [`Self::Auto`] and `CONTRACTS_NODE_URL` not specified.
54    pub fn url(&self) -> Option<String> {
55        std::env::var("CONTRACTS_NODE_URL").ok().or_else(|| {
56            match self {
57                Node::Auto => None,
58                Node::Url(url) => Some(url.clone()),
59            }
60        })
61    }
62}
63
64/// The runtime emulator that should be used within `TestExternalities`
65#[cfg(any(test, feature = "sandbox"))]
66#[derive(Clone, Eq, PartialEq, Debug, darling::FromMeta)]
67pub enum RuntimeOnly {
68    #[darling(word)]
69    #[darling(skip)]
70    Default,
71    Sandbox(syn::Path),
72}
73
74#[cfg(any(test, feature = "sandbox"))]
75impl From<RuntimeOnly> for syn::Path {
76    fn from(value: RuntimeOnly) -> Self {
77        match value {
78            RuntimeOnly::Default => syn::parse_quote! { ::ink_e2e::DefaultSandbox },
79            RuntimeOnly::Sandbox(path) => path,
80        }
81    }
82}
83
84/// The End-to-End test configuration.
85#[derive(Debug, Default, PartialEq, Eq, darling::FromMeta)]
86pub struct E2EConfig {
87    /// The [`Environment`](https://docs.rs/ink_env/4.1.0/ink_env/trait.Environment.html) to use
88    /// during test execution.
89    ///
90    /// If no `Environment` is specified, the
91    /// [`DefaultEnvironment`](https://docs.rs/ink_env/4.1.0/ink_env/enum.DefaultEnvironment.html)
92    /// will be used.
93    #[darling(default)]
94    environment: Option<syn::Path>,
95    /// The type of the architecture that should be used to run test.
96    #[darling(default)]
97    backend: Backend,
98}
99
100impl E2EConfig {
101    /// Custom environment for the contracts, if specified.
102    pub fn environment(&self) -> Option<syn::Path> {
103        self.environment.clone()
104    }
105
106    /// The type of the architecture that should be used to run test.
107    pub fn backend(&self) -> Backend {
108        self.backend.clone()
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use darling::{
116        ast::NestedMeta,
117        FromMeta,
118    };
119    use quote::quote;
120
121    #[test]
122    fn config_works_backend_runtime_only() {
123        let input = quote! {
124            environment = crate::CustomEnvironment,
125            backend(runtime_only),
126        };
127        let config =
128            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
129
130        assert_eq!(
131            config.environment(),
132            Some(syn::parse_quote! { crate::CustomEnvironment })
133        );
134
135        assert_eq!(config.backend(), Backend::RuntimeOnly(RuntimeOnly::Default));
136    }
137
138    #[test]
139    #[should_panic(expected = "ErrorUnknownField")]
140    fn config_backend_runtime_only_default_not_allowed() {
141        let input = quote! {
142            backend(runtime_only(default)),
143        };
144        let config =
145            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
146
147        assert_eq!(config.backend(), Backend::RuntimeOnly(RuntimeOnly::Default));
148    }
149
150    #[test]
151    fn config_works_runtime_only_with_custom_backend() {
152        let input = quote! {
153            backend(runtime_only(sandbox = ::ink_e2e::DefaultSandbox)),
154        };
155        let config =
156            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
157
158        assert_eq!(
159            config.backend(),
160            Backend::RuntimeOnly(RuntimeOnly::Sandbox(
161                syn::parse_quote! { ::ink_e2e::DefaultSandbox }
162            ))
163        );
164    }
165
166    #[test]
167    fn config_works_backend_node() {
168        let input = quote! {
169            backend(node),
170        };
171        let config =
172            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
173
174        assert_eq!(config.backend(), Backend::Node(Node::Auto));
175
176        match config.backend() {
177            Backend::Node(node_config) => {
178                assert_eq!(node_config, Node::Auto);
179
180                temp_env::with_vars([("CONTRACTS_NODE_URL", None::<&str>)], || {
181                    assert_eq!(node_config.url(), None);
182                });
183
184                temp_env::with_vars(
185                    [("CONTRACTS_NODE_URL", Some("ws://127.0.0.1:9000"))],
186                    || {
187                        assert_eq!(
188                            node_config.url(),
189                            Some(String::from("ws://127.0.0.1:9000"))
190                        );
191                    },
192                );
193            }
194            _ => panic!("Expected Backend::Node"),
195        }
196    }
197
198    #[test]
199    #[should_panic(expected = "ErrorUnknownField")]
200    fn config_backend_node_auto_not_allowed() {
201        let input = quote! {
202            backend(node(auto)),
203        };
204        let config =
205            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
206
207        assert_eq!(config.backend(), Backend::Node(Node::Auto));
208    }
209
210    #[test]
211    fn config_works_backend_node_url() {
212        let input = quote! {
213            backend(node(url = "ws://0.0.0.0:9999")),
214        };
215        let config =
216            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
217
218        match config.backend() {
219            Backend::Node(node_config) => {
220                assert_eq!(node_config, Node::Url("ws://0.0.0.0:9999".to_owned()));
221
222                temp_env::with_vars([("CONTRACTS_NODE_URL", None::<&str>)], || {
223                    assert_eq!(node_config.url(), Some("ws://0.0.0.0:9999".to_owned()));
224                });
225
226                temp_env::with_vars(
227                    [("CONTRACTS_NODE_URL", Some("ws://127.0.0.1:9000"))],
228                    || {
229                        assert_eq!(
230                            node_config.url(),
231                            Some(String::from("ws://127.0.0.1:9000"))
232                        );
233                    },
234                );
235            }
236            _ => panic!("Expected Backend::Node"),
237        }
238    }
239}