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.
1415/// 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.
21Node(Node),
2223/// 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"))]
28RuntimeOnly(RuntimeOnly),
29}
3031impl Default for Backend {
32fn default() -> Self {
33 Backend::Node(Node::Auto)
34 }
35}
3637/// 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)]
44Auto,
45/// The test will run against an already running node at the supplied URL.
46Url(String),
47}
4849impl 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.
54pub fn url(&self) -> Option<String> {
55 std::env::var("CONTRACTS_NODE_URL").ok().or_else(|| {
56match self {
57 Node::Auto => None,
58 Node::Url(url) => Some(url.clone()),
59 }
60 })
61 }
62}
6364/// 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)]
70Default,
71 Sandbox(syn::Path),
72}
7374#[cfg(any(test, feature = "sandbox"))]
75impl From<RuntimeOnly> for syn::Path {
76fn from(value: RuntimeOnly) -> Self {
77match value {
78 RuntimeOnly::Default => syn::parse_quote! { ::ink_e2e::DefaultSandbox },
79 RuntimeOnly::Sandbox(path) => path,
80 }
81 }
82}
8384/// 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)]
94environment: Option<syn::Path>,
95/// The type of the architecture that should be used to run test.
96#[darling(default)]
97backend: Backend,
98/// Features that are enabled in the contract during the build process.
99 /// todo add tests below in this file
100#[darling(default)]
101features: Vec<syn::LitStr>,
102}
103104impl E2EConfig {
105/// Custom environment for the contracts, if specified.
106pub fn environment(&self) -> Option<syn::Path> {
107self.environment.clone()
108 }
109110/// Features for the contract build.
111pub fn features(&self) -> Vec<String> {
112self.features.iter().map(|ls| ls.value()).collect()
113 }
114115/// The type of the architecture that should be used to run test.
116pub fn backend(&self) -> Backend {
117self.backend.clone()
118 }
119}
120121#[cfg(test)]
122mod tests {
123use super::*;
124use darling::{
125 ast::NestedMeta,
126 FromMeta,
127 };
128use quote::quote;
129130#[test]
131fn config_works_backend_runtime_only() {
132let input = quote! {
133 environment = crate::CustomEnvironment,
134 backend(runtime_only),
135 };
136let config =
137 E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
138139assert_eq!(
140 config.environment(),
141Some(syn::parse_quote! { crate::CustomEnvironment })
142 );
143144assert_eq!(config.backend(), Backend::RuntimeOnly(RuntimeOnly::Default));
145 }
146147#[test]
148 #[should_panic(expected = "ErrorUnknownField")]
149fn config_backend_runtime_only_default_not_allowed() {
150let input = quote! {
151 backend(runtime_only(default)),
152 };
153let config =
154 E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
155156assert_eq!(config.backend(), Backend::RuntimeOnly(RuntimeOnly::Default));
157 }
158159#[test]
160fn config_works_runtime_only_with_custom_backend() {
161let input = quote! {
162 backend(runtime_only(sandbox = ::ink_e2e::DefaultSandbox)),
163 };
164let config =
165 E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
166167assert_eq!(
168 config.backend(),
169 Backend::RuntimeOnly(RuntimeOnly::Sandbox(
170syn::parse_quote! { ::ink_e2e::DefaultSandbox }
171 ))
172 );
173 }
174175#[test]
176fn config_works_backend_node() {
177let input = quote! {
178 backend(node),
179 };
180let config =
181 E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
182183assert_eq!(config.backend(), Backend::Node(Node::Auto));
184185match config.backend() {
186 Backend::Node(node_config) => {
187assert_eq!(node_config, Node::Auto);
188189 temp_env::with_vars([("CONTRACTS_NODE_URL", None::<&str>)], || {
190assert_eq!(node_config.url(), None);
191 });
192193 temp_env::with_vars(
194 [("CONTRACTS_NODE_URL", Some("ws://127.0.0.1:9000"))],
195 || {
196assert_eq!(
197 node_config.url(),
198Some(String::from("ws://127.0.0.1:9000"))
199 );
200 },
201 );
202 }
203_ => panic!("Expected Backend::Node"),
204 }
205 }
206207#[test]
208 #[should_panic(expected = "ErrorUnknownField")]
209fn config_backend_node_auto_not_allowed() {
210let input = quote! {
211 backend(node(auto)),
212 };
213let config =
214 E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
215216assert_eq!(config.backend(), Backend::Node(Node::Auto));
217 }
218219#[test]
220fn config_works_backend_node_url() {
221let input = quote! {
222 backend(node(url = "ws://0.0.0.0:9999")),
223 };
224let config =
225 E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
226227match config.backend() {
228 Backend::Node(node_config) => {
229assert_eq!(node_config, Node::Url("ws://0.0.0.0:9999".to_owned()));
230231 temp_env::with_vars([("CONTRACTS_NODE_URL", None::<&str>)], || {
232assert_eq!(node_config.url(), Some("ws://0.0.0.0:9999".to_owned()));
233 });
234235 temp_env::with_vars(
236 [("CONTRACTS_NODE_URL", Some("ws://127.0.0.1:9000"))],
237 || {
238assert_eq!(
239 node_config.url(),
240Some(String::from("ws://127.0.0.1:9000"))
241 );
242 },
243 );
244 }
245_ => panic!("Expected Backend::Node"),
246 }
247 }
248}