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    /// Features that are enabled in the contract during the build process.
99    /// todo add tests below in this file
100    #[darling(default)]
101    features: Vec<syn::LitStr>,
102}
103
104impl E2EConfig {
105    /// Custom environment for the contracts, if specified.
106    pub fn environment(&self) -> Option<syn::Path> {
107        self.environment.clone()
108    }
109
110    /// Features for the contract build.
111    pub fn features(&self) -> Vec<String> {
112        self.features.iter().map(|ls| ls.value()).collect()
113    }
114
115    /// The type of the architecture that should be used to run test.
116    pub fn backend(&self) -> Backend {
117        self.backend.clone()
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use darling::{
125        ast::NestedMeta,
126        FromMeta,
127    };
128    use quote::quote;
129
130    #[test]
131    fn config_works_backend_runtime_only() {
132        let input = quote! {
133            environment = crate::CustomEnvironment,
134            backend(runtime_only),
135        };
136        let config =
137            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
138
139        assert_eq!(
140            config.environment(),
141            Some(syn::parse_quote! { crate::CustomEnvironment })
142        );
143
144        assert_eq!(config.backend(), Backend::RuntimeOnly(RuntimeOnly::Default));
145    }
146
147    #[test]
148    #[should_panic(expected = "ErrorUnknownField")]
149    fn config_backend_runtime_only_default_not_allowed() {
150        let input = quote! {
151            backend(runtime_only(default)),
152        };
153        let config =
154            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
155
156        assert_eq!(config.backend(), Backend::RuntimeOnly(RuntimeOnly::Default));
157    }
158
159    #[test]
160    fn config_works_runtime_only_with_custom_backend() {
161        let input = quote! {
162            backend(runtime_only(sandbox = ::ink_e2e::DefaultSandbox)),
163        };
164        let config =
165            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
166
167        assert_eq!(
168            config.backend(),
169            Backend::RuntimeOnly(RuntimeOnly::Sandbox(
170                syn::parse_quote! { ::ink_e2e::DefaultSandbox }
171            ))
172        );
173    }
174
175    #[test]
176    fn config_works_backend_node() {
177        let input = quote! {
178            backend(node),
179        };
180        let config =
181            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
182
183        assert_eq!(config.backend(), Backend::Node(Node::Auto));
184
185        match config.backend() {
186            Backend::Node(node_config) => {
187                assert_eq!(node_config, Node::Auto);
188
189                temp_env::with_vars([("CONTRACTS_NODE_URL", None::<&str>)], || {
190                    assert_eq!(node_config.url(), None);
191                });
192
193                temp_env::with_vars(
194                    [("CONTRACTS_NODE_URL", Some("ws://127.0.0.1:9000"))],
195                    || {
196                        assert_eq!(
197                            node_config.url(),
198                            Some(String::from("ws://127.0.0.1:9000"))
199                        );
200                    },
201                );
202            }
203            _ => panic!("Expected Backend::Node"),
204        }
205    }
206
207    #[test]
208    #[should_panic(expected = "ErrorUnknownField")]
209    fn config_backend_node_auto_not_allowed() {
210        let input = quote! {
211            backend(node(auto)),
212        };
213        let config =
214            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
215
216        assert_eq!(config.backend(), Backend::Node(Node::Auto));
217    }
218
219    #[test]
220    fn config_works_backend_node_url() {
221        let input = quote! {
222            backend(node(url = "ws://0.0.0.0:9999")),
223        };
224        let config =
225            E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
226
227        match config.backend() {
228            Backend::Node(node_config) => {
229                assert_eq!(node_config, Node::Url("ws://0.0.0.0:9999".to_owned()));
230
231                temp_env::with_vars([("CONTRACTS_NODE_URL", None::<&str>)], || {
232                    assert_eq!(node_config.url(), Some("ws://0.0.0.0:9999".to_owned()));
233                });
234
235                temp_env::with_vars(
236                    [("CONTRACTS_NODE_URL", Some("ws://127.0.0.1:9000"))],
237                    || {
238                        assert_eq!(
239                            node_config.url(),
240                            Some(String::from("ws://127.0.0.1:9000"))
241                        );
242                    },
243                );
244            }
245            _ => panic!("Expected Backend::Node"),
246        }
247    }
248}