1// This file is part of Substrate.
23// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
56// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
1718//! Integration tests for runtime interface primitives
19#![cfg(test)]
2021use sp_runtime_interface::*;
2223use sp_runtime_interface_test_wasm::{test_api::HostFunctions, wasm_binary_unwrap};
24use sp_runtime_interface_test_wasm_deprecated::wasm_binary_unwrap as wasm_binary_deprecated_unwrap;
2526use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::AllocationStats};
27use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions as HostFunctionsT};
2829use std::{
30 collections::HashSet,
31 sync::{Arc, Mutex},
32};
3334type TestExternalities = sp_state_machine::TestExternalities<sp_runtime::traits::BlakeTwo256>;
3536fn call_wasm_method_with_result<HF: HostFunctionsT>(
37 binary: &[u8],
38 method: &str,
39) -> (Result<TestExternalities, String>, Option<AllocationStats>) {
40let mut ext = TestExternalities::default();
41let mut ext_ext = ext.ext();
4243let executor = sc_executor::WasmExecutor::<
44 ExtendedHostFunctions<sp_io::SubstrateHostFunctions, HF>,
45 >::builder()
46 .build();
4748let (result, allocation_stats) = executor.uncached_call_with_allocation_stats(
49 RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"),
50&mut ext_ext,
51false,
52 method,
53&[],
54 );
55let result = result
56 .map_err(|e| format!("Failed to execute `{}`: {}", method, e))
57 .map(|_| ext);
58 (result, allocation_stats)
59}
6061fn call_wasm_method<HF: HostFunctionsT>(binary: &[u8], method: &str) -> TestExternalities {
62 call_wasm_method_with_result::<HF>(binary, method).0.unwrap()
63}
6465#[test]
66fn test_return_data() {
67 call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_data");
68}
6970#[test]
71fn test_return_option_data() {
72 call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_option_data");
73}
7475#[test]
76fn test_set_storage() {
77let mut ext = call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_set_storage");
7879let expected = "world";
80assert_eq!(expected.as_bytes(), &ext.ext().storage("hello".as_bytes()).unwrap()[..]);
81}
8283#[test]
84fn test_return_value_into_mutable_reference() {
85 call_wasm_method::<HostFunctions>(
86 wasm_binary_unwrap(),
87"test_return_value_into_mutable_reference",
88 );
89}
9091#[test]
92fn test_get_and_return_array() {
93 call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_get_and_return_array");
94}
9596#[test]
97fn test_array_as_mutable_reference() {
98 call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_array_as_mutable_reference");
99}
100101#[test]
102fn test_return_input_public_key() {
103 call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_input_public_key");
104}
105106#[test]
107fn host_function_not_found() {
108let err = call_wasm_method_with_result::<()>(wasm_binary_unwrap(), "test_return_data")
109 .0
110.unwrap_err();
111112assert!(err.contains("test_return_data"));
113assert!(err.contains(" Failed to create module"));
114}
115116#[test]
117fn test_invalid_utf8_data_should_return_an_error() {
118 call_wasm_method_with_result::<HostFunctions>(
119 wasm_binary_unwrap(),
120"test_invalid_utf8_data_should_return_an_error",
121 )
122 .0
123.unwrap_err();
124}
125126#[test]
127fn test_overwrite_native_function_implementation() {
128 call_wasm_method::<HostFunctions>(
129 wasm_binary_unwrap(),
130"test_overwrite_native_function_implementation",
131 );
132}
133134#[test]
135fn test_vec_return_value_memory_is_freed() {
136 call_wasm_method::<HostFunctions>(
137 wasm_binary_unwrap(),
138"test_vec_return_value_memory_is_freed",
139 );
140}
141142#[test]
143fn test_encoded_return_value_memory_is_freed() {
144 call_wasm_method::<HostFunctions>(
145 wasm_binary_unwrap(),
146"test_encoded_return_value_memory_is_freed",
147 );
148}
149150#[test]
151fn test_array_return_value_memory_is_freed() {
152 call_wasm_method::<HostFunctions>(
153 wasm_binary_unwrap(),
154"test_array_return_value_memory_is_freed",
155 );
156}
157158#[test]
159fn test_versioning_with_new_host_works() {
160// We call to the new wasm binary with new host function.
161call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_versioning_works");
162163// we call to the old wasm binary with a new host functions
164 // old versions of host functions should be called and test should be ok!
165call_wasm_method::<HostFunctions>(wasm_binary_deprecated_unwrap(), "test_versioning_works");
166}
167168#[test]
169fn test_versioning_register_only() {
170 call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_versioning_register_only_works");
171}
172173fn run_test_in_another_process(
174 test_name: &str,
175 test_body: impl FnOnce(),
176) -> Option<std::process::Output> {
177if std::env::var("RUN_FORKED_TEST").is_ok() {
178 test_body();
179None
180} else {
181let output = std::process::Command::new(std::env::current_exe().unwrap())
182 .arg(test_name)
183 .env("RUN_FORKED_TEST", "1")
184 .output()
185 .unwrap();
186187assert!(output.status.success());
188Some(output)
189 }
190}
191192#[test]
193fn test_tracing() {
194// Run in a different process to ensure that the `Span` is registered with our local
195 // `TracingSubscriber`.
196run_test_in_another_process("test_tracing", || {
197use std::fmt;
198use tracing::span::Id as SpanId;
199use tracing_core::field::{Field, Visit};
200201#[derive(Clone)]
202struct TracingSubscriber(Arc<Mutex<Inner>>);
203204struct FieldConsumer(&'static str, Option<String>);
205impl Visit for FieldConsumer {
206fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
207if field.name() == self.0 {
208self.1 = Some(format!("{:?}", value))
209 }
210 }
211 }
212213#[derive(Default)]
214struct Inner {
215 spans: HashSet<String>,
216 }
217218impl tracing::subscriber::Subscriber for TracingSubscriber {
219fn enabled(&self, _: &tracing::Metadata) -> bool {
220true
221}
222223fn new_span(&self, span: &tracing::span::Attributes) -> tracing::Id {
224let mut inner = self.0.lock().unwrap();
225let id = SpanId::from_u64((inner.spans.len() + 1) as _);
226let mut f = FieldConsumer("name", None);
227 span.record(&mut f);
228 inner.spans.insert(f.1.unwrap_or_else(|| span.metadata().name().to_owned()));
229 id
230 }
231232fn record(&self, _: &SpanId, _: &tracing::span::Record) {}
233234fn record_follows_from(&self, _: &SpanId, _: &SpanId) {}
235236fn event(&self, _: &tracing::Event) {}
237238fn enter(&self, _: &SpanId) {}
239240fn exit(&self, _: &SpanId) {}
241 }
242243let subscriber = TracingSubscriber(Default::default());
244let _guard = tracing::subscriber::set_default(subscriber.clone());
245246// Call some method to generate a trace
247call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_data");
248249let inner = subscriber.0.lock().unwrap();
250assert!(inner.spans.contains("return_input_version_1"));
251 });
252}
253254#[test]
255fn test_return_input_as_tuple() {
256 call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_input_as_tuple");
257}
258259#[test]
260fn test_returning_option_bytes_from_a_host_function_is_efficient() {
261let (result, stats_vec) = call_wasm_method_with_result::<HostFunctions>(
262 wasm_binary_unwrap(),
263"test_return_option_vec",
264 );
265 result.unwrap();
266let (result, stats_bytes) = call_wasm_method_with_result::<HostFunctions>(
267 wasm_binary_unwrap(),
268"test_return_option_bytes",
269 );
270 result.unwrap();
271272let stats_vec = stats_vec.unwrap();
273let stats_bytes = stats_bytes.unwrap();
274275// The way we currently implement marshaling of `Option<Vec<u8>>` through
276 // the WASM FFI boundary from the host to the runtime requires that it is
277 // marshaled through SCALE. This is quite inefficient as it requires two
278 // memory allocations inside of the runtime:
279 //
280 // 1) the first allocation to copy the SCALE-encoded blob into the runtime;
281 // 2) and another allocation for the resulting `Vec<u8>` when decoding that blob.
282 //
283 // Both of these allocations are are as big as the `Vec<u8>` which is being
284 // passed to the runtime. This is especially bad when fetching big values
285 // from storage, as it can lead to an out-of-memory situation.
286 //
287 // Our `Option<Bytes>` marshaling is better; it still must go through SCALE,
288 // and it still requires two allocations, however since `Bytes` is zero-copy
289 // only the first allocation is `Vec<u8>`-sized, and the second allocation
290 // which creates the deserialized `Bytes` is tiny, and is only necessary because
291 // the underlying `Bytes` buffer from which we're deserializing gets automatically
292 // turned into an `Arc`.
293 //
294 // So this assertion tests that deserializing `Option<Bytes>` allocates less than
295 // deserializing `Option<Vec<u8>>`.
296assert_eq!(stats_bytes.bytes_allocated_sum + 16 * 1024 + 8, stats_vec.bytes_allocated_sum);
297}
298299#[test]
300fn test_marshalling_strategies() {
301 call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_marshalling_strategies");
302}