diff --git a/Cargo.lock b/Cargo.lock index ce57bbc..f6ced0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" +checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" [[package]] name = "arrayref" @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake3" @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" [[package]] name = "cfg-if" @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" dependencies = [ "quote", "syn", @@ -425,13 +425,14 @@ dependencies = [ [[package]] name = "fluence-it-types" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5006d09553345421af5dd2334cc945fc34dc2a73d7c1ed842a39a3803699619d" +checksum = "047f670b4807cab8872550a607b1515daff08b3e3bb7576ce8f45971fd811a4e" dependencies = [ "it-to-bytes", "nom", "serde", + "variant_count", "wast", ] @@ -638,9 +639,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "lazy_static" @@ -669,9 +670,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.98" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] name = "lock_api" @@ -705,7 +706,7 @@ checksum = "e62f29b16bbdb0763a04f8561c954624ee9cd9f558af4e67b95eb00880da11ec" dependencies = [ "cargo_toml", "it-lilo", - "marine-it-parser 0.6.5", + "marine-it-parser", "marine-macro-impl 0.6.10", "once_cell", "serde", @@ -715,16 +716,6 @@ dependencies = [ "wasmer-interface-types-fl", ] -[[package]] -name = "marine-it-interfaces" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c533789e72808630cc35b5d14d286382236282525f82ddce8fb47eb9d659e8" -dependencies = [ - "multimap", - "wasmer-interface-types-fl", -] - [[package]] name = "marine-it-interfaces" version = "0.4.0" @@ -735,23 +726,6 @@ dependencies = [ "wasmer-interface-types-fl", ] -[[package]] -name = "marine-it-parser" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e59c7067a18b9e4aebe67bee033638fae97d6fe4fb00f70f9a509eb5d03d1c5d" -dependencies = [ - "anyhow", - "marine-it-interfaces 0.3.0", - "nom", - "semver 0.11.0", - "serde", - "thiserror", - "walrus", - "wasmer-interface-types-fl", - "wasmer-runtime-core-fl", -] - [[package]] name = "marine-it-parser" version = "0.6.5" @@ -760,7 +734,7 @@ checksum = "19a6606e472587b2e7b759b16d037a4ea951facc2a6650f668f22403978c2442" dependencies = [ "anyhow", "itertools 0.10.1", - "marine-it-interfaces 0.4.0", + "marine-it-interfaces", "marine-module-interface", "nom", "semver 0.11.0", @@ -805,6 +779,7 @@ dependencies = [ name = "marine-macro-impl" version = "0.6.11" dependencies = [ + "marine-macro-testing-utils", "pretty_assertions", "proc-macro2", "quote", @@ -814,6 +789,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "marine-macro-testing-utils" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "marine-module-info-parser" version = "0.2.0" @@ -838,7 +822,7 @@ checksum = "d8a5936273bebb523ed169863282dbc19fc66bb983c7031c5b8b0556584f2401" dependencies = [ "anyhow", "itertools 0.10.1", - "marine-it-interfaces 0.4.0", + "marine-it-interfaces", "nom", "semver 0.11.0", "serde", @@ -895,12 +879,13 @@ dependencies = [ [[package]] name = "marine-rs-sdk-test" -version = "0.1.11" +version = "0.2.0" dependencies = [ "fluence-app-service", "marine-test-macro", "serde", "serde_json", + "trybuild", "uuid", ] @@ -915,8 +900,8 @@ dependencies = [ "it-lilo", "log", "marine-it-generator", - "marine-it-interfaces 0.4.0", - "marine-it-parser 0.6.5", + "marine-it-interfaces", + "marine-it-parser", "marine-module-info-parser", "marine-module-interface", "marine-utils", @@ -936,7 +921,7 @@ dependencies = [ [[package]] name = "marine-test-macro" -version = "0.1.11" +version = "0.2.0" dependencies = [ "marine-test-macro-impl", "proc-macro-error", @@ -947,14 +932,17 @@ dependencies = [ [[package]] name = "marine-test-macro-impl" -version = "0.1.11" +version = "0.2.0" dependencies = [ "darling", "fluence-app-service", - "marine-it-parser 0.5.0", + "itertools 0.10.1", + "marine-it-parser", + "marine-macro-testing-utils", "proc-macro-error", "proc-macro2", "quote", + "static_assertions", "syn", "thiserror", ] @@ -985,9 +973,9 @@ checksum = "8dc5838acba84ce4d802d672afd0814fae0ae7098021ae5b06d975e70d09f812" [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap" @@ -1177,9 +1165,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" dependencies = [ "unicode-xid", ] @@ -1347,9 +1335,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" +checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" dependencies = [ "itoa", "ryu", @@ -1358,9 +1346,9 @@ dependencies = [ [[package]] name = "simple_logger" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28475b72d7e5da6ad80c6d284aa557821bb4f7f788b9f607632635e3783a8608" +checksum = "b7de33c687404ec3045d4a0d437580455257c0436f858d702f244e7d652f9f07" dependencies = [ "atty", "chrono", @@ -1395,9 +1383,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" dependencies = [ "proc-macro2", "quote", @@ -1421,18 +1409,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +checksum = "283d5230e63df9608ac7d9691adc1dfb6e701225436eb64d0b9a7f0a5a04f6ec" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +checksum = "fa3884228611f5cd3608e2d409bf7dce832e4eb3135e3f11addbd7e41bd68e71" dependencies = [ "proc-macro2", "quote", @@ -1461,9 +1449,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.43" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02c413315329fc96167f922b46fd0caa3a43f4697b7a7896b183c7142635832" +checksum = "5bdaf2a1d317f3d58b44b31c7f6436b9b9acafe7bddfeace50897c2b804d7792" dependencies = [ "glob", "lazy_static", @@ -1530,6 +1518,16 @@ dependencies = [ "getrandom 0.2.3", ] +[[package]] +name = "variant_count" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae2faf80ac463422992abf4de234731279c058aaf33171ca70277c98406b124" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "version_check" version = "0.9.3" diff --git a/crates/macro-testing-utils/Cargo.toml b/crates/macro-testing-utils/Cargo.toml new file mode 100644 index 0000000..54b876b --- /dev/null +++ b/crates/macro-testing-utils/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "marine-macro-testing-utils" +version = "0.1.0" +edition = "2018" +description = "Some functions for testing procedural macros" +documentation = "https://docs.rs/fluence/marine-macro-testing-utils" +repository = "https://github.com/fluencelabs/marine-rs-sdk/tree/master/crates/marine-macro-testing-utils" +authors = ["Fluence Labs"] +keywords = ["fluence", "marine", "sdk", "webassembly"] +categories = ["development-tools::testing"] +license = "Apache-2.0" + +[lib] +path = "src/lib.rs" +crate-type = ["rlib"] +doctest = false + +[dependencies] +quote = "1.0.9" +proc-macro2 = "1.0.26" +syn = { version = '1.0.64', features = ['full'] } diff --git a/crates/macro-testing-utils/src/lib.rs b/crates/macro-testing-utils/src/lib.rs new file mode 100644 index 0000000..d3af384 --- /dev/null +++ b/crates/macro-testing-utils/src/lib.rs @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::io::Read; +use std::path::Path; + +pub fn stream_from_file

(path: P) -> proc_macro2::TokenStream +where + P: AsRef, +{ + let items = items_from_file(path); + quote::quote! { #(#items)* } +} + +pub fn items_from_file

(path: P) -> Vec +where + P: AsRef, +{ + let mut file = std::fs::File::open(path).expect("Unable to open file"); + + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + + let token_file = syn::parse_file(&src).expect("Unable to parse file"); + token_file.items +} + +pub fn to_syn_item(token_stream: proc_macro2::TokenStream) -> Vec { + let file: syn::File = syn::parse2(token_stream).expect("token stream should be parsed"); + file.items +} diff --git a/crates/marine-macro-impl/Cargo.toml b/crates/marine-macro-impl/Cargo.toml index db0e227..7ea2abd 100644 --- a/crates/marine-macro-impl/Cargo.toml +++ b/crates/marine-macro-impl/Cargo.toml @@ -23,3 +23,4 @@ uuid = { version = "0.8.2", features = ["v4"] } [dev-dependencies] pretty_assertions = "0.7.1" +marine-macro-testing-utils = {path = "../macro-testing-utils"} diff --git a/crates/marine-macro-impl/tests/utils.rs b/crates/marine-macro-impl/tests/utils.rs index 788ed0a..df252ba 100644 --- a/crates/marine-macro-impl/tests/utils.rs +++ b/crates/marine-macro-impl/tests/utils.rs @@ -16,7 +16,8 @@ use marine_macro_impl::marine; -use std::io::Read; +use marine_macro_testing_utils::{items_from_file, stream_from_file, to_syn_item}; + use std::path::Path; pub fn test_marine_token_streams(marine_path: FP, expanded_path: EP) -> bool @@ -34,29 +35,3 @@ where marine_item == expanded_item } - -fn stream_from_file

(path: P) -> proc_macro2::TokenStream -where - P: AsRef, -{ - let items = items_from_file(path); - quote::quote! { #(#items)* } -} - -fn items_from_file

(path: P) -> Vec -where - P: AsRef, -{ - let mut file = std::fs::File::open(path).expect("Unable to open file"); - - let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); - - let token_file = syn::parse_file(&src).expect("Unable to parse file"); - token_file.items -} - -fn to_syn_item(token_stream: proc_macro2::TokenStream) -> Vec { - let file: syn::File = syn::parse2(token_stream).expect("token stream should be parsed"); - file.items -} diff --git a/crates/marine-test-macro-impl/Cargo.toml b/crates/marine-test-macro-impl/Cargo.toml index 1fee2ce..9d3a074 100644 --- a/crates/marine-test-macro-impl/Cargo.toml +++ b/crates/marine-test-macro-impl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "marine-test-macro-impl" -version = "0.1.11" # remember to update html_root_url +version = "0.2.0" # remember to update html_root_url edition = "2018" description = "Implementation of the `#[marine_test]` macro" documentation = "https://docs.rs/fluence/marine-test-macro-impl" @@ -15,11 +15,15 @@ all-features = true [dependencies] fluence-app-service = { version = "0.9.0", features = ["raw-module-api"] } -marine-it-parser = "0.5.0" - +marine-it-parser = "0.6.5" +itertools = "0.10.1" darling = "0.12.2" quote = "1.0.9" proc-macro2 = "1.0.26" proc-macro-error = { version = "1.0.4", default-features = false } syn = { version = '1.0.64', features = ['full'] } thiserror = "1.0.24" +static_assertions = "1.1.0" + +[dev-dependencies] +marine-macro-testing-utils = {path = "../macro-testing-utils"} diff --git a/crates/marine-test-macro-impl/src/errors.rs b/crates/marine-test-macro-impl/src/errors.rs index 72a0421..a84b7d2 100644 --- a/crates/marine-test-macro-impl/src/errors.rs +++ b/crates/marine-test-macro-impl/src/errors.rs @@ -50,6 +50,12 @@ pub enum TestGeneratorError { #[error("{0} is invalid UTF8 path")] InvalidUTF8Path(PathBuf), + + #[error(r#"a "self" argument found and it is not supported in test function"#)] + UnexpectedSelf, + + #[error("Duplicate module: {0}")] + DuplicateModuleName(String), } #[derive(Debug, ThisError)] diff --git a/crates/marine-test-macro-impl/src/marine_test/config_utils.rs b/crates/marine-test-macro-impl/src/marine_test/config_utils.rs index 87dd52a..9750952 100644 --- a/crates/marine-test-macro-impl/src/marine_test/config_utils.rs +++ b/crates/marine-test-macro-impl/src/marine_test/config_utils.rs @@ -17,19 +17,19 @@ use crate::TResult; use fluence_app_service::TomlAppServiceConfig; -use marine_it_parser::module_raw_interface; -use marine_it_parser::interface::MModuleInterface; +use marine_it_parser::module_it_interface; +use marine_it_parser::it_interface::IModuleInterface; use std::path::PathBuf; #[derive(Debug, Clone, PartialEq, Eq)] pub(super) struct Module<'m> { pub name: &'m str, - pub interface: MModuleInterface, + pub interface: IModuleInterface, } impl<'m> Module<'m> { - fn new(name: &'m str, interface: MModuleInterface) -> Self { + fn new(name: &'m str, interface: IModuleInterface) -> Self { Self { name, interface } } } @@ -43,9 +43,7 @@ pub(super) fn collect_modules( module_paths .into_iter() - .map(|(name, path)| { - module_raw_interface(path).map(|interface| Module::new(name, interface)) - }) + .map(|(name, path)| module_it_interface(path).map(|interface| Module::new(name, interface))) .collect::, _>>() .map_err(Into::into) } diff --git a/crates/marine-test-macro-impl/src/marine_test/glue_code_generator.rs b/crates/marine-test-macro-impl/src/marine_test/glue_code_generator.rs index 5d90b31..c775e67 100644 --- a/crates/marine-test-macro-impl/src/marine_test/glue_code_generator.rs +++ b/crates/marine-test-macro-impl/src/marine_test/glue_code_generator.rs @@ -23,9 +23,11 @@ use crate::marine_test::config_utils; use fluence_app_service::TomlAppServiceConfig; use proc_macro2::TokenStream; use quote::quote; +use quote::ToTokens; use std::path::Path; use std::path::PathBuf; +use syn::FnArg; /// Generates glue code for tests. /// F.e. for this test for the greeting service @@ -128,30 +130,40 @@ pub(super) fn generate_test_glue_code( let modules_dir = file_path.join(modules_dir); let module_interfaces = marine_test::config_utils::collect_modules(&marine_config, modules_dir)?; + let linked_modules = marine_test::modules_linker::link_modules(&module_interfaces)?; - let module_definitions = - marine_test::module_generator::generate_module_definitions(module_interfaces.iter())?; - - let module_iter = module_interfaces.iter().map(|module| module.name); - let module_ctors = generate_module_ctors(module_iter)?; + let module_definitions = marine_test::module_generator::generate_module_definitions( + module_interfaces.iter(), + &linked_modules, + )?; let original_block = func_item.block; let signature = func_item.sig; + let name = &signature.ident; + let inputs = &signature.inputs; + let arg_names = generate_arg_names(inputs.iter())?; + let module_ctors = generate_module_ctors(inputs.iter())?; let glue_code = quote! { #[test] - #signature { + fn #name() { // definitions for wasm modules specified in config - #(#module_definitions)* - + pub mod marine_test_env { + #(#module_definitions)* + } // AppService constructor and instantiation to implicit `marine` variable #app_service_ctor // constructors of all modules of the tested service #(#module_ctors)* - // original test function as is - #original_block + fn test_func(#inputs) { + #(let mut #arg_names = #arg_names;)* + // original test function as is + #original_block + } + + test_func(#(#arg_names,)*) } }; @@ -181,9 +193,7 @@ fn generate_app_service_ctor(config_path: &str, modules_dir: &Path) -> TResult { - Some((file_path, p)) - } + std::path::Component::Normal(_) | std::path::Component::CurDir | std::path::Component::ParentDir => Some((file_path, p)), _ => None, }) { Some(t) => t, @@ -218,21 +228,32 @@ fn generate_app_service_ctor(config_path: &str, modules_dir: &Path) -> TResult( - module_names: impl ExactSizeIterator, +fn generate_module_ctors<'inputs>( + inputs: impl Iterator, ) -> TResult> { - module_names - .map(|name| -> TResult<_> { - // TODO: optimize these two call because they are called twice for each module name - // and internally allocate memory in format call. - let module_name = marine_test::utils::generate_structs_module_ident(&name)?; - let struct_name = marine_test::utils::generate_struct_name(&name)?; - let name_for_user = marine_test::utils::new_ident(&name)?; - - let module_ctor = - quote! { let mut #name_for_user = #module_name::#struct_name::new(marine.clone()); }; - - Ok(module_ctor) + inputs + .map(|x| -> TResult<_> { + match x { + FnArg::Receiver(_) => Err(TestGeneratorError::UnexpectedSelf), + FnArg::Typed(x) => { + let pat = &x.pat; + let ty = &x.ty; + Ok(quote! {let mut #pat = #ty::new(marine.clone());}) + } + } + }) + .collect::>() +} + +fn generate_arg_names<'inputs>( + inputs: impl Iterator, +) -> TResult> { + inputs + .map(|x| -> TResult<_> { + match x { + FnArg::Receiver(_) => Err(TestGeneratorError::UnexpectedSelf), + FnArg::Typed(x) => Ok(x.pat.to_token_stream()), + } }) .collect::>() } diff --git a/crates/marine-test-macro-impl/src/marine_test/mod.rs b/crates/marine-test-macro-impl/src/marine_test/mod.rs index ffb4434..8b98009 100644 --- a/crates/marine-test-macro-impl/src/marine_test/mod.rs +++ b/crates/marine-test-macro-impl/src/marine_test/mod.rs @@ -19,5 +19,6 @@ mod marine_test_impl; mod glue_code_generator; mod module_generator; mod utils; +mod modules_linker; pub use marine_test_impl::marine_test_impl; diff --git a/crates/marine-test-macro-impl/src/marine_test/module_generator.rs b/crates/marine-test-macro-impl/src/marine_test/module_generator.rs index 80940bb..a41022a 100644 --- a/crates/marine-test-macro-impl/src/marine_test/module_generator.rs +++ b/crates/marine-test-macro-impl/src/marine_test/module_generator.rs @@ -21,6 +21,7 @@ mod record_type_generator; use crate::marine_test::utils; use crate::marine_test::config_utils::Module; use crate::TResult; +use crate::marine_test::modules_linker::{LinkedModules, LinkedModule}; use proc_macro2::TokenStream; use quote::quote; @@ -56,21 +57,24 @@ use quote::quote; ///``` pub(super) fn generate_module_definitions<'i>( modules: impl ExactSizeIterator>, + linked_modules: &'i LinkedModules<'_>, ) -> TResult> { modules .into_iter() - .map(generate_module_definition) + .map(|value| generate_module_definition(value, linked_modules.get(&value.name).unwrap())) // linked_modules are built from modules .collect::>>() } -fn generate_module_definition(module: &Module<'_>) -> TResult { +fn generate_module_definition( + module: &Module<'_>, + linked_module: &'_ LinkedModule<'_>, +) -> TResult { let module_name = module.name; - let module_ident = utils::generate_module_ident(module_name)?; - let structs_module_ident = utils::generate_structs_module_ident(module_name)?; - let struct_ident = utils::generate_struct_name(module_name)?; + let module_ident = utils::new_ident(module_name)?; + let struct_ident = utils::new_ident("ModuleInterface")?; let module_interface = &module.interface; - let module_records = record_type_generator::generate_records(&module_interface.record_types)?; + let module_records = record_type_generator::generate_records(linked_module)?; let module_functions = methods_generator::generate_module_methods( module_name, module_interface.function_signatures.iter(), @@ -79,26 +83,22 @@ fn generate_module_definition(module: &Module<'_>) -> TResult { let module_definition = quote! { // it's a sort of hack: this module structure allows user to import structs by - // use module_name_structs::StructName; - pub mod #structs_module_ident { - pub use #module_ident::*; + // using marine_env_test::module_name::StructName; + pub mod #module_ident { + #(#module_records)* - pub mod #module_ident { - #(#module_records)* + pub struct #struct_ident { + marine: std::rc::Rc, >, + } - pub struct #struct_ident { - marine: std::rc::Rc>, + impl #struct_ident { + pub fn new(marine: std::rc::Rc, >) -> Self { + Self { marine } } + } - impl #struct_ident { - pub fn new(marine: std::rc::Rc>) -> Self { - Self { marine } - } - } - - impl #struct_ident { - #(#module_functions)* - } + impl #struct_ident { + #(#module_functions)* } } }; diff --git a/crates/marine-test-macro-impl/src/marine_test/module_generator/methods_generator.rs b/crates/marine-test-macro-impl/src/marine_test/module_generator/methods_generator.rs index 9799025..893cffa 100644 --- a/crates/marine-test-macro-impl/src/marine_test/module_generator/methods_generator.rs +++ b/crates/marine-test-macro-impl/src/marine_test/module_generator/methods_generator.rs @@ -17,27 +17,32 @@ use super::methods_generator_utils::*; use crate::TResult; -use marine_it_parser::interface::MRecordTypes; -use marine_it_parser::interface::MFunctionSignature; +use marine_it_parser::it_interface::IFunctionSignature; +use marine_it_parser::it_interface::IRecordTypes; + +use itertools::Itertools; pub(super) fn generate_module_methods<'m, 'r>( module_name: &str, - mut method_signatures: impl ExactSizeIterator, - records: &'r MRecordTypes, + method_signatures: impl ExactSizeIterator, + records: &'r IRecordTypes, ) -> TResult> { use CallParametersSettings::*; let methods_count = 2 * method_signatures.len(); - method_signatures.try_fold::<_, _, TResult<_>>( - Vec::with_capacity(methods_count), - |mut methods, signature| { - let default_cp = generate_module_method(module_name, &signature, Default, records)?; - let user_cp = generate_module_method(module_name, &signature, UserDefined, records)?; + method_signatures + .sorted_by(|lhs, rhs| lhs.name.cmp(&rhs.name)) + .try_fold::<_, _, TResult<_>>( + Vec::with_capacity(methods_count), + |mut methods, signature| { + let default_cp = generate_module_method(module_name, &signature, Default, records)?; + let user_cp = + generate_module_method(module_name, &signature, UserDefined, records)?; - methods.push(default_cp); - methods.push(user_cp); + methods.push(default_cp); + methods.push(user_cp); - Ok(methods) - }, - ) + Ok(methods) + }, + ) } diff --git a/crates/marine-test-macro-impl/src/marine_test/module_generator/methods_generator_utils.rs b/crates/marine-test-macro-impl/src/marine_test/module_generator/methods_generator_utils.rs index ff1be4d..9156381 100644 --- a/crates/marine-test-macro-impl/src/marine_test/module_generator/methods_generator_utils.rs +++ b/crates/marine-test-macro-impl/src/marine_test/module_generator/methods_generator_utils.rs @@ -18,10 +18,10 @@ use crate::marine_test::utils::new_ident; use crate::marine_test::utils::itype_to_tokens; use crate::TResult; -use marine_it_parser::interface::it::IType; -use marine_it_parser::interface::it::IFunctionArg; -use marine_it_parser::interface::MRecordTypes; -use marine_it_parser::interface::MFunctionSignature; +use marine_it_parser::it_interface::it::IType; +use marine_it_parser::it_interface::it::IFunctionArg; +use marine_it_parser::it_interface::IRecordTypes; +use marine_it_parser::it_interface::IFunctionSignature; use proc_macro2::TokenStream; use quote::quote; @@ -34,9 +34,9 @@ pub(super) enum CallParametersSettings { pub(super) fn generate_module_method( module_name: &str, - signature: &MFunctionSignature, + signature: &IFunctionSignature, cp_setting: CallParametersSettings, - records: &MRecordTypes, + records: &IRecordTypes, ) -> TResult { let arguments = generate_arguments(signature.arguments.iter(), records)?; let output_type = generate_output_type(&signature.outputs, records)?; @@ -73,8 +73,8 @@ pub(super) fn generate_module_method( fn generate_marine_call( module_name: &str, cp_settings: CallParametersSettings, - method_signature: &MFunctionSignature, - records: &MRecordTypes, + method_signature: &IFunctionSignature, + records: &IRecordTypes, ) -> TResult { let args = method_signature.arguments.iter().map(|a| a.name.as_str()); let convert_arguments = generate_arguments_converter(args)?; @@ -133,7 +133,7 @@ fn generate_set_result(output_type: &Option<&IType>) -> TokenStream { fn generate_convert_to_output( output_type: &Option<&IType>, - records: &MRecordTypes, + records: &IRecordTypes, ) -> TResult { let result_stream = match output_type { Some(ty) => { @@ -157,7 +157,7 @@ fn generate_ret(output_type: &Option<&IType>) -> TokenStream { fn generate_arguments<'a, 'r>( arguments: impl ExactSizeIterator, - records: &'r MRecordTypes, + records: &'r IRecordTypes, ) -> TResult> { arguments .map(|argument| -> TResult<_> { @@ -170,7 +170,7 @@ fn generate_arguments<'a, 'r>( .collect::>>() } -fn generate_output_type(output_types: &[IType], records: &MRecordTypes) -> TResult { +fn generate_output_type(output_types: &[IType], records: &IRecordTypes) -> TResult { let output_type = get_output_type(output_types)?; match output_type { None => Ok(TokenStream::new()), diff --git a/crates/marine-test-macro-impl/src/marine_test/module_generator/record_type_generator.rs b/crates/marine-test-macro-impl/src/marine_test/module_generator/record_type_generator.rs index df529f6..1e7bd16 100644 --- a/crates/marine-test-macro-impl/src/marine_test/module_generator/record_type_generator.rs +++ b/crates/marine-test-macro-impl/src/marine_test/module_generator/record_type_generator.rs @@ -17,35 +17,47 @@ use crate::marine_test::utils; use crate::TResult; -use marine_it_parser::interface::it::IRecordFieldType; -use marine_it_parser::interface::MRecordTypes; +use marine_it_parser::it_interface::it::IRecordFieldType; +use marine_it_parser::it_interface::IRecordTypes; use proc_macro2::TokenStream; use quote::quote; -pub(super) fn generate_records(records: &MRecordTypes) -> TResult> { - use std::ops::Deref; +use crate::marine_test::modules_linker::{LinkedModule, RecordEntry}; +use itertools::Itertools; - records.iter().map(|(_, record)| -> TResult<_> { - let record_name_ident = utils::generate_record_name(&record.name)?; - let fields = prepare_field(record.fields.deref().iter(), records)?; +pub(super) fn generate_records(linked_module: &LinkedModule<'_>) -> TResult> { + linked_module.records + .iter() + .sorted() + .map(|record| -> TResult<_> { + use RecordEntry::*; + match record { + Use(use_info) => { + let from_module_ident = utils::new_ident(use_info.from)?; + let record_name_ident = utils::new_ident(use_info.name)?; + Ok(quote! {pub use super::#from_module_ident::#record_name_ident;}) + }, + Declare(record) => { + let record_name_ident = utils::new_ident(&record.record_type.name)?; + let fields = prepare_field(record.record_type.fields.iter(), record.records)?; - let generated_record = quote! { - #[derive(Clone, Debug, marine_rs_sdk_test::internal::serde::Serialize, marine_rs_sdk_test::internal::serde::Deserialize)] - #[serde(crate = "marine_rs_sdk_test::internal::serde")] - pub struct #record_name_ident { - #(pub #fields),* + Ok(quote! { + #[derive(Clone, Debug, marine_rs_sdk_test::internal::serde::Serialize, marine_rs_sdk_test::internal::serde::Deserialize)] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct #record_name_ident { + #(pub #fields),* + } + }) + } } - }; - - Ok(generated_record) - } - ).collect::>>() + }) + .collect::>>() } fn prepare_field<'f>( fields: impl ExactSizeIterator, - records: &MRecordTypes, + records: &IRecordTypes, ) -> TResult> { fields .map(|field| -> TResult<_> { diff --git a/crates/marine-test-macro-impl/src/marine_test/modules_linker.rs b/crates/marine-test-macro-impl/src/marine_test/modules_linker.rs new file mode 100644 index 0000000..23894e6 --- /dev/null +++ b/crates/marine-test-macro-impl/src/marine_test/modules_linker.rs @@ -0,0 +1,198 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::marine_test::config_utils::Module; +use crate::{TResult, TestGeneratorError}; + +use marine_it_parser::it_interface::IRecordTypes; +use marine_it_parser::it_interface::it::{IType, IRecordType}; + +use itertools::zip; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::hash::Hasher; +use std::rc::Rc; +use static_assertions::const_assert; + +pub(super) fn link_modules<'modules>( + modules: &'modules [Module<'_>], +) -> TResult> { + let mut all_record_types = HashMap::, &str>::new(); + let mut linked_modules = HashMap::<&str, LinkedModule<'_>>::new(); + + for module in modules { + let mut linking_module = LinkedModule::default(); + for (_, record_type) in &module.interface.record_types { + let record_type_ex = + IRecordTypeClosed::new(record_type.clone(), &module.interface.record_types); + + let entry = match all_record_types.get(&record_type_ex) { + Some(owner_module) => RecordEntry::Use(UseDescription { + from: owner_module, + name: &record_type.name, + }), + None => { + all_record_types.insert(record_type_ex.clone(), module.name); + RecordEntry::Declare(record_type_ex) + } + }; + + linking_module.records.push(entry); + } + + if linked_modules.insert(module.name, linking_module).is_some() { + return Err(TestGeneratorError::DuplicateModuleName( + module.name.to_string(), + )); + } + } + + Ok(linked_modules) +} + +struct ITypeClosed<'r> { + ty: &'r IType, + records: &'r IRecordTypes, +} + +impl<'r> ITypeClosed<'r> { + fn new(ty: &'r IType, records: &'r IRecordTypes) -> Self { + Self { ty, records } + } +} + +impl PartialEq for ITypeClosed<'_> { + fn eq(&self, other: &Self) -> bool { + use IType::*; + // check if new variants require special handling in the match below + #[allow(unused)] + const LAST_VERIFIED_ITYPE_SIZE: usize = 17; + const_assert!(IType::VARIANT_COUNT == LAST_VERIFIED_ITYPE_SIZE); + + match (&self.ty, &other.ty) { + (Array(self_ty), Array(other_ty)) => { + ITypeClosed::new(self_ty, self.records) == ITypeClosed::new(other_ty, other.records) + } + (Record(self_record), Record(other_record)) => { + let self_record = self.records.get(self_record); + let other_record = other.records.get(other_record); + + // ID from Record(ID) potentially may not be in .records, if it happens comparision is always FALSE + match (self_record, other_record) { + (None, _) => false, + (_, None) => false, + (Some(self_record), Some(other_record)) => { + IRecordTypeClosed::new(self_record.clone(), self.records) + == IRecordTypeClosed::new(other_record.clone(), other.records) + } + } + } + (lhs, rhs) if lhs == rhs => true, + _ => false, + } + } +} + +#[derive(Clone)] +pub struct IRecordTypeClosed<'r> { + pub record_type: Rc, + pub records: &'r IRecordTypes, +} + +impl<'r> IRecordTypeClosed<'r> { + fn new(record_type: Rc, records: &'r IRecordTypes) -> Self { + Self { + record_type, + records, + } + } +} + +impl PartialEq for IRecordTypeClosed<'_> { + fn eq(&self, other: &Self) -> bool { + let names_are_equal = self.record_type.name == other.record_type.name; + names_are_equal && fields_are_equal(self, other) + } +} + +fn fields_are_equal(lhs: &IRecordTypeClosed<'_>, rhs: &IRecordTypeClosed<'_>) -> bool { + let same_fields_count = lhs.record_type.fields.len() == rhs.record_type.fields.len(); + same_fields_count + && zip(lhs.record_type.fields.iter(), rhs.record_type.fields.iter()).all( + |(lhs_field, rhs_field)| -> bool { + lhs_field.name == rhs_field.name + && ITypeClosed::new(&lhs_field.ty, lhs.records) + == ITypeClosed::new(&rhs_field.ty, rhs.records) + }, + ) +} + +impl PartialOrd for IRecordTypeClosed<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for IRecordTypeClosed<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.record_type.name.cmp(&other.record_type.name) + } +} + +impl Eq for IRecordTypeClosed<'_> {} + +impl std::hash::Hash for IRecordTypeClosed<'_> { + fn hash(&self, state: &mut H) { + self.record_type.name.hash(state); + } +} + +pub type LinkedModules<'r> = HashMap<&'r str, LinkedModule<'r>>; + +#[derive(Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct UseDescription<'r> { + pub from: &'r str, + pub name: &'r str, +} + +#[derive(PartialEq, Eq)] +pub enum RecordEntry<'r> { + Use(UseDescription<'r>), + Declare(IRecordTypeClosed<'r>), +} + +impl PartialOrd for RecordEntry<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for RecordEntry<'_> { + fn cmp(&self, other: &Self) -> Ordering { + use RecordEntry::*; + match (self, other) { + (Use(_), Declare(_)) => Ordering::Less, + (Declare(_), Use(_)) => Ordering::Greater, + (Use(lhs), Use(rhs)) => lhs.cmp(rhs), + (Declare(lhs), Declare(rhs)) => lhs.record_type.name.cmp(&rhs.record_type.name), + } + } +} + +#[derive(Default)] +pub struct LinkedModule<'all> { + pub records: Vec>, +} diff --git a/crates/marine-test-macro-impl/src/marine_test/utils.rs b/crates/marine-test-macro-impl/src/marine_test/utils.rs index f609cae..30eadfb 100644 --- a/crates/marine-test-macro-impl/src/marine_test/utils.rs +++ b/crates/marine-test-macro-impl/src/marine_test/utils.rs @@ -15,38 +15,18 @@ */ use crate::TResult; -use marine_it_parser::interface::MRecordTypes; -use marine_it_parser::interface::it::IType; +use marine_it_parser::it_interface::IRecordTypes; +use marine_it_parser::it_interface::it::IType; use proc_macro2::TokenStream; use quote::quote; -pub(super) fn generate_module_ident(module_name: &str) -> TResult { - let generated_module_name = format!("__m_generated_{}", module_name); - new_ident(&generated_module_name) -} - -pub(super) fn generate_structs_module_ident(module_name: &str) -> TResult { - let generated_module_name = format!("{}_structs", module_name); - new_ident(&generated_module_name) -} - -pub(super) fn generate_record_name(record_name: &str) -> TResult { - let extended_record_name = record_name.to_string(); - new_ident(&extended_record_name) -} - -pub(super) fn generate_struct_name(struct_name: &str) -> TResult { - let extended_struct_name = format!("MGeneratedStruct{}", struct_name); - new_ident(&extended_struct_name) -} - pub(super) fn new_ident(ident_str: &str) -> TResult { let ident_str = ident_str.replace('-', "_"); syn::parse_str::(&ident_str).map_err(Into::into) } -pub(super) fn itype_to_tokens(itype: &IType, records: &MRecordTypes) -> TResult { +pub(super) fn itype_to_tokens(itype: &IType, records: &IRecordTypes) -> TResult { let token_stream = match itype { IType::Record(record_id) => { let record = records diff --git a/crates/marine-test-macro-impl/tests/generation_tests/empty_func/Config.toml b/crates/marine-test-macro-impl/tests/generation_tests/empty_func/Config.toml new file mode 100644 index 0000000..2e740e3 --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests/empty_func/Config.toml @@ -0,0 +1,6 @@ +modules_dir = "artifacts/" + +[[module]] + name = "greeting" + mem_pages_count = 1 + logger_enabled = false diff --git a/crates/marine-test-macro-impl/tests/generation_tests/empty_func/artifacts/greeting.wasm b/crates/marine-test-macro-impl/tests/generation_tests/empty_func/artifacts/greeting.wasm new file mode 100755 index 0000000..20cd5a2 Binary files /dev/null and b/crates/marine-test-macro-impl/tests/generation_tests/empty_func/artifacts/greeting.wasm differ diff --git a/crates/marine-test-macro-impl/tests/generation_tests/empty_func/expanded.rs b/crates/marine-test-macro-impl/tests/generation_tests/empty_func/expanded.rs new file mode 100644 index 0000000..e48e13b --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests/empty_func/expanded.rs @@ -0,0 +1,128 @@ +#[test] +fn empty_string() { + pub mod marine_test_env { + pub mod greeting { + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct CallParameters { + pub init_peer_id: String, + pub service_id: String, + pub service_creator_peer_id: String, + pub host_id: String, + pub particle_id: String, + pub tetraplets: Vec> + } + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct MountedBinaryResult { + pub ret_code: i32, + pub error: String, + pub stdout: Vec, + pub stderr: Vec + } + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct MountedBinaryStringResult { + pub ret_code: i32, + pub error: String, + pub stdout: String, + pub stderr: String + } + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct SecurityTetraplet { + pub peer_pk: String, + pub service_id: String, + pub function_name: String, + pub json_path: String + } + pub struct ModuleInterface { + marine: + std::rc::Rc, >, + } + impl ModuleInterface { + pub fn new( + marine: std::rc::Rc< + std::cell::RefCell,> + ) -> Self { + Self { marine } + } + } + impl ModuleInterface {} + } + } + let tmp_dir = std::env::temp_dir(); + let service_id = marine_rs_sdk_test::internal::Uuid::new_v4().to_string(); + let tmp_dir = tmp_dir.join(&service_id); + let tmp_dir = tmp_dir.to_string_lossy().to_string(); + std::fs::create_dir(&tmp_dir).expect("can't create a directory for service in tmp"); + let mut module_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut file_path = std::path::Path::new(file!()).components(); + let mut truncated_file_path = Vec::new(); + loop { + if module_path.ends_with(file_path.as_path()) { + break; + } + let (file_path_, remainder) = match file_path.next_back().and_then(|p| match p { + std::path::Component::Normal(_) + | std::path::Component::CurDir + | std::path::Component::ParentDir => Some((file_path, p)), + _ => None, + }) { + Some(t) => t, + None => break, + }; + file_path = file_path_; + truncated_file_path.push(remainder); + } + for path in truncated_file_path.iter().rev() { + module_path.push(path); + } + let _ = module_path.pop(); + let config_path = module_path.join("Config.toml"); + let modules_dir = module_path.join("artifacts"); + let modules_dir = modules_dir + .to_str() + .expect("modules_dir contains invalid UTF8 string"); + let mut __m_generated_marine_config = marine_rs_sdk_test::internal::TomlAppServiceConfig::load( + &config_path + ) + .unwrap_or_else(|e| + panic!( + "app service config located at `{:?}` can't be loaded: {}", + config_path, e + )); + __m_generated_marine_config.service_base_dir = Some(tmp_dir); + __m_generated_marine_config.toml_faas_config.modules_dir = Some(modules_dir.to_string()); + let marine = marine_rs_sdk_test::internal::AppService::new_with_empty_facade( + __m_generated_marine_config, + service_id, + std::collections::HashMap::new() + ) + .unwrap_or_else(|e| panic!("app service can't be created: {}", e)); + let marine = std::rc::Rc::new(std::cell::RefCell::new(marine)); + fn test_func() { + {} + } + test_func() +} diff --git a/crates/marine-test-macro-impl/tests/generation_tests/empty_func/marine_test.rs b/crates/marine-test-macro-impl/tests/generation_tests/empty_func/marine_test.rs new file mode 100644 index 0000000..a8c58fd --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests/empty_func/marine_test.rs @@ -0,0 +1 @@ +fn empty_string() {} diff --git a/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/Config.toml b/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/Config.toml new file mode 100644 index 0000000..a2db194 --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/Config.toml @@ -0,0 +1,8 @@ +modules_dir = "artifacts/" + +[[module]] + name = "greeting" + mem_pages_count = 1 + logger_enabled = false + [module.mounted_binaries] + echo = "/usr/bin/curl" diff --git a/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/artifacts/greeting.wasm b/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/artifacts/greeting.wasm new file mode 100755 index 0000000..7f605b6 Binary files /dev/null and b/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/artifacts/greeting.wasm differ diff --git a/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/expanded.rs b/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/expanded.rs new file mode 100644 index 0000000..4b23eb7 --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/expanded.rs @@ -0,0 +1,167 @@ +#[test] +fn test() { + pub mod marine_test_env { + pub mod greeting { + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct CallParameters { + pub init_peer_id: String, + pub service_id: String, + pub service_creator_peer_id: String, + pub host_id: String, + pub particle_id: String, + pub tetraplets: Vec> + } + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct MountedBinaryResult { + pub ret_code: i32, + pub error: String, + pub stdout: Vec, + pub stderr: Vec + } + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct MountedBinaryStringResult { + pub ret_code: i32, + pub error: String, + pub stdout: String, + pub stderr: String + } + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct SecurityTetraplet { + pub peer_pk: String, + pub service_id: String, + pub function_name: String, + pub json_path: String + } + pub struct ModuleInterface { + marine: + std::rc::Rc, >, + } + impl ModuleInterface { + pub fn new( + marine: std::rc::Rc< + std::cell::RefCell, + > + ) -> Self { + Self { marine } + } + } + impl ModuleInterface { + pub fn download(&mut self, url: String) -> String { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([url]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module("greeting", "download", arguments, <_>::default()) + .expect("call to Marine failed"); + let result: String = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + pub fn download_cp( + &mut self, + url: String, + cp: marine_rs_sdk_test::CallParameters + ) -> String { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([url]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module("greeting", "download", arguments, cp) + .expect("call to Marine failed"); + let result: String = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + } + } + } + let tmp_dir = std::env::temp_dir(); + let service_id = marine_rs_sdk_test::internal::Uuid::new_v4().to_string(); + let tmp_dir = tmp_dir.join(&service_id); + let tmp_dir = tmp_dir.to_string_lossy().to_string(); + std::fs::create_dir(&tmp_dir).expect("can't create a directory for service in tmp"); + let mut module_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut file_path = std::path::Path::new(file!()).components(); + let mut truncated_file_path = Vec::new(); + loop { + if module_path.ends_with(file_path.as_path()) { + break; + } + let (file_path_, remainder) = match file_path.next_back().and_then(|p| match p { + std::path::Component::Normal(_) + | std::path::Component::CurDir + | std::path::Component::ParentDir => Some((file_path, p)), + _ => None, + }) { + Some(t) => t, + None => break, + }; + file_path = file_path_; + truncated_file_path.push(remainder); + } + for path in truncated_file_path.iter().rev() { + module_path.push(path); + } + let _ = module_path.pop(); + let config_path = module_path.join("Config.toml"); + let modules_dir = module_path.join("artifacts"); + let modules_dir = modules_dir + .to_str() + .expect("modules_dir contains invalid UTF8 string"); + let mut __m_generated_marine_config = marine_rs_sdk_test::internal::TomlAppServiceConfig::load( + &config_path + ) + .unwrap_or_else(|e| + panic!( + "app service config located at `{:?}` can't be loaded: {}", + config_path, e + ) + ); + __m_generated_marine_config.service_base_dir = Some(tmp_dir); + __m_generated_marine_config.toml_faas_config.modules_dir = Some(modules_dir.to_string()); + let marine = marine_rs_sdk_test::internal::AppService::new_with_empty_facade( + __m_generated_marine_config, + service_id, + std::collections::HashMap::new() + ) + .unwrap_or_else(|e| panic!("app service can't be created: {}", e)); + let marine = std::rc::Rc::new(std::cell::RefCell::new(marine)); + let mut greeting = marine_test_env::greeting::ModuleInterface::new(marine.clone()); + fn test_func(greeting: marine_test_env::greeting::ModuleInterface) { + let mut greeting = greeting; + { + let _ = greeting.download("duckduckgo.com"); + } + } + test_func(greeting,) +} diff --git a/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/marine_test.rs b/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/marine_test.rs new file mode 100644 index 0000000..47bb39c --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests/mounted_binary/marine_test.rs @@ -0,0 +1,3 @@ +fn test(greeting: marine_test_env::greeting::ModuleInterface) { + let _ = greeting.download("duckduckgo.com"); +} diff --git a/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/Config.toml b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/Config.toml new file mode 100644 index 0000000..3927911 --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/Config.toml @@ -0,0 +1,10 @@ +modules_dir = "artifacts/" +[[module]] + name = "greeting" + mem_pages_count = 1 + logger_enabled = false + +[[module]] + name = "call_parameters" + mem_pages_count = 1 + logger_enabled = false diff --git a/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/artifacts/call_parameters.wasm b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/artifacts/call_parameters.wasm new file mode 100755 index 0000000..27ec9a7 Binary files /dev/null and b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/artifacts/call_parameters.wasm differ diff --git a/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/artifacts/greeting.wasm b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/artifacts/greeting.wasm new file mode 100755 index 0000000..cb54970 Binary files /dev/null and b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/artifacts/greeting.wasm differ diff --git a/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/expanded.rs b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/expanded.rs new file mode 100644 index 0000000..7c0a886 --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/expanded.rs @@ -0,0 +1,321 @@ +#[test] +fn empty_string() { + pub mod marine_test_env { + pub mod greeting { + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct CallParameters { + pub init_peer_id: String, + pub service_id: String, + pub service_creator_peer_id: String, + pub host_id: String, + pub particle_id: String, + pub tetraplets: Vec> + } + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct MountedBinaryResult { + pub ret_code: i32, + pub error: String, + pub stdout: Vec, + pub stderr: Vec + } + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct MountedBinaryStringResult { + pub ret_code: i32, + pub error: String, + pub stdout: String, + pub stderr: String + } + #[derive( + Clone, + Debug, + marine_rs_sdk_test :: internal :: serde :: Serialize, + marine_rs_sdk_test :: internal :: serde :: Deserialize + )] + #[serde(crate = "marine_rs_sdk_test::internal::serde")] + pub struct SecurityTetraplet { + pub peer_pk: String, + pub service_id: String, + pub function_name: String, + pub json_path: String + } + pub struct ModuleInterface { + marine: std::rc::Rc, >, + } + impl ModuleInterface { + pub fn new( + marine: std::rc::Rc< + std::cell::RefCell, + > + ) -> Self { + Self { marine } + } + } + impl ModuleInterface { + pub fn greeting(&mut self, name: String) -> String { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([name]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module("greeting", "greeting", arguments, <_>::default()) + .expect("call to Marine failed"); + let result: String = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + pub fn greeting_cp( + &mut self, + name: String, + cp: marine_rs_sdk_test::CallParameters + ) -> String { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([name]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module("greeting", "greeting", arguments, cp) + .expect("call to Marine failed"); + let result: String = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + } + } + pub mod call_parameters { + pub use super::greeting::CallParameters; + pub use super::greeting::SecurityTetraplet; + pub struct ModuleInterface { + marine: std::rc::Rc, >, + } + impl ModuleInterface { + pub fn new( + marine: std::rc::Rc< + std::cell::RefCell, + > + ) -> Self { + Self { marine } + } + } + impl ModuleInterface { + pub fn call_parameters(&mut self, ) -> String { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module( + "call_parameters", + "call_parameters", + arguments, + <_>::default() + ) + .expect("call to Marine failed"); + let result: String = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + pub fn call_parameters_cp( + &mut self, + cp: marine_rs_sdk_test::CallParameters + ) -> String { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module("call_parameters", "call_parameters", arguments, cp) + .expect("call to Marine failed"); + let result: String = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + pub fn return_string(&mut self,) -> String { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module( + "call_parameters", + "return_string", + arguments, + <_>::default() + ) + .expect("call to Marine failed"); + let result: String = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + pub fn return_string_cp( + &mut self, + cp: marine_rs_sdk_test::CallParameters + ) -> String { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module("call_parameters", "return_string", arguments, cp) + .expect("call to Marine failed"); + let result: String = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + pub fn test_array_refs(&mut self, ) -> Vec { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module( + "call_parameters", + "test_array_refs", + arguments, + <_>::default() + ) + .expect("call to Marine failed"); + let result: Vec = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + pub fn test_array_refs_cp( + &mut self, + cp: marine_rs_sdk_test::CallParameters + ) -> Vec { + use std::ops::DerefMut; + let arguments = marine_rs_sdk_test::internal::serde_json::json!([]); + let result = self + .marine + .as_ref() + .borrow_mut() + .call_module("call_parameters", "test_array_refs", arguments, cp) + .expect("call to Marine failed"); + let result: Vec = + marine_rs_sdk_test::internal::serde_json::from_value(result) + .expect("the default deserializer shouldn't fail"); + result + } + } + } + } + let tmp_dir = std::env::temp_dir(); + let service_id = marine_rs_sdk_test::internal::Uuid::new_v4().to_string(); + let tmp_dir = tmp_dir.join(&service_id); + let tmp_dir = tmp_dir.to_string_lossy().to_string(); + std::fs::create_dir(&tmp_dir).expect("can't create a directory for service in tmp"); + let mut module_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut file_path = std::path::Path::new(file!()).components(); + let mut truncated_file_path = Vec::new(); + loop { + if module_path.ends_with(file_path.as_path()) { + break; + } + let (file_path_, remainder) = match file_path.next_back().and_then(|p| match p { + std::path::Component::Normal(_) + | std::path::Component::CurDir + | std::path::Component::ParentDir => Some((file_path, p)), + _ => None, + }) { + Some(t) => t, + None => break, + }; + file_path = file_path_; + truncated_file_path.push(remainder); + } + for path in truncated_file_path.iter().rev() { + module_path.push(path); + } + let _ = module_path.pop(); + let config_path = module_path.join("Config.toml"); + let modules_dir = module_path.join("artifacts"); + let modules_dir = modules_dir + .to_str() + .expect("modules_dir contains invalid UTF8 string"); + let mut __m_generated_marine_config = marine_rs_sdk_test::internal::TomlAppServiceConfig::load( + &config_path + ) + .unwrap_or_else(|e| + panic!( + "app service config located at `{:?}` can't be loaded: {}", + config_path, e + ) + ); + __m_generated_marine_config.service_base_dir = Some(tmp_dir); + __m_generated_marine_config.toml_faas_config.modules_dir = Some(modules_dir.to_string()); + let marine = marine_rs_sdk_test::internal::AppService::new_with_empty_facade( + __m_generated_marine_config, + service_id, + std::collections::HashMap::new() + ) + .unwrap_or_else(|e| panic!("app service can't be created: {}", e)); + let marine = std::rc::Rc::new(std::cell::RefCell::new(marine)); + let mut greeting_m = marine_test_env::greeting::ModuleInterface::new(marine.clone()); + let mut call_parameters_m = + marine_test_env::call_parameters::ModuleInterface::new(marine.clone()); + fn test_func( + greeting_m: marine_test_env::greeting::ModuleInterface, + call_parameters_m: marine_test_env::call_parameters::ModuleInterface + ) { + let mut greeting_m = greeting_m; + let mut call_parameters_m = call_parameters_m; + { + let init_peer_id = "init_peer_id"; + let service_id = "service_id"; + let service_creator_peer_id = "service_creator_peer_id"; + let host_id = "host_id"; + let particle_id = "particle_id"; + let greeting = greeting_m.greeting("asd"); + let mut tetraplet = SecurityTetraplet::default(); + tetraplet.function_name = "some_func_name".to_string(); + tetraplet.json_path = "some_json_path".to_string(); + let tetraplets = vec![vec![tetraplet]]; + let cp = CallParameters { + init_peer_id: init_peer_id.to_string(), + service_id: service_id.to_string(), + service_creator_peer_id: service_creator_peer_id.to_string(), + host_id: host_id.to_string(), + particle_id: particle_id.to_string(), + tetraplets: tetraplets.clone(), + }; + let actual = call_parameters_m.call_parameters_cp(cp); + let expected = format!( + "{}\n{}\n{}\n{}\n{}\n{:?}", + init_peer_id, service_id, service_creator_peer_id, host_id, particle_id, tetraplets + ); + assert_eq!(actual, expected); + } + } + test_func(greeting_m, call_parameters_m, ) +} diff --git a/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/marine_test.rs b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/marine_test.rs new file mode 100644 index 0000000..07af15e --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests/multiple_modules/marine_test.rs @@ -0,0 +1,29 @@ +fn empty_string(greeting_m: marine_test_env::greeting::ModuleInterface, call_parameters_m: marine_test_env::call_parameters::ModuleInterface) { + let init_peer_id = "init_peer_id"; + let service_id = "service_id"; + let service_creator_peer_id = "service_creator_peer_id"; + let host_id = "host_id"; + let particle_id = "particle_id"; + + let greeting = greeting_m.greeting("asd"); + let mut tetraplet = SecurityTetraplet::default(); + tetraplet.function_name = "some_func_name".to_string(); + tetraplet.json_path = "some_json_path".to_string(); + let tetraplets = vec![vec![tetraplet]]; + + let cp = CallParameters { + init_peer_id: init_peer_id.to_string(), + service_id: service_id.to_string(), + service_creator_peer_id: service_creator_peer_id.to_string(), + host_id: host_id.to_string(), + particle_id: particle_id.to_string(), + tetraplets: tetraplets.clone(), + }; + + let actual = call_parameters_m.call_parameters_cp(cp); + let expected = format!( + "{}\n{}\n{}\n{}\n{}\n{:?}", + init_peer_id, service_id, service_creator_peer_id, host_id, particle_id, tetraplets + ); + assert_eq!(actual, expected); +} diff --git a/crates/marine-test-macro-impl/tests/generation_tests_runner.rs b/crates/marine-test-macro-impl/tests/generation_tests_runner.rs new file mode 100644 index 0000000..e10d6c1 --- /dev/null +++ b/crates/marine-test-macro-impl/tests/generation_tests_runner.rs @@ -0,0 +1,49 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod utils; + +use utils::test_marine_test_token_streams; + +#[test] +fn test_empty_func() { + assert!(test_marine_test_token_streams( + "tests/generation_tests/empty_func/marine_test.rs", + "tests/generation_tests/empty_func/expanded.rs", + "Config.toml", + "artifacts" + )); +} + +#[test] +fn test_mounted_binary() { + assert!(test_marine_test_token_streams( + "tests/generation_tests/mounted_binary/marine_test.rs", + "tests/generation_tests/mounted_binary/expanded.rs", + "Config.toml", + "artifacts" + )); +} + +#[test] +fn test_multiple_modules() { + assert!(test_marine_test_token_streams( + "tests/generation_tests/multiple_modules/marine_test.rs", + "tests/generation_tests/multiple_modules/expanded.rs", + "Config.toml", + "artifacts" + )); +} diff --git a/crates/marine-test-macro-impl/tests/utils.rs b/crates/marine-test-macro-impl/tests/utils.rs new file mode 100644 index 0000000..68a3384 --- /dev/null +++ b/crates/marine-test-macro-impl/tests/utils.rs @@ -0,0 +1,48 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use marine_test_macro_impl::marine_test_impl; + +use marine_macro_testing_utils::{items_from_file, stream_from_file, to_syn_item}; + +use std::path::Path; + +pub fn test_marine_test_token_streams( + marine_path: FP, + expanded_path: EP, + config_path: &str, + modules_dir: &str, +) -> bool +where + FP: AsRef, + EP: AsRef, +{ + let marine_item = stream_from_file(&marine_path); + let test_token_stream = quote::quote! { #marine_item }; + let buf = marine_path.as_ref().to_path_buf(); + let attrs = quote::quote! {config_path = #config_path, modules_dir = #modules_dir}; + let marine_token_streams = marine_test_impl( + attrs, + test_token_stream, + buf.parent().unwrap().to_path_buf(), + ) + .unwrap_or_else(|e| panic!("failed to apply the marine macro due {}", e)); + + let expanded_item = items_from_file(&expanded_path); + let marine_item = to_syn_item(marine_token_streams.clone()); + + marine_item == expanded_item +} diff --git a/crates/marine-test-macro/Cargo.toml b/crates/marine-test-macro/Cargo.toml index 2ac2829..0d51a4f 100644 --- a/crates/marine-test-macro/Cargo.toml +++ b/crates/marine-test-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "marine-test-macro" -version = "0.1.11" # remember to update html_root_url +version = "0.2.0" # remember to update html_root_url edition = "2018" description = "Definition of the `#[marine_test]` macro" documentation = "https://docs.rs/fluence/marine-test-macro" @@ -18,7 +18,7 @@ proc-macro = true doctest = false [dependencies] -marine-test-macro-impl = { path = "../marine-test-macro-impl", version = "=0.1.11" } +marine-test-macro-impl = { path = "../marine-test-macro-impl", version = "=0.2.0" } quote = "1.0.9" proc-macro2 = "1.0.24" diff --git a/crates/marine-test-macro/src/lib.rs b/crates/marine-test-macro/src/lib.rs index 39de54f..754d3ac 100644 --- a/crates/marine-test-macro/src/lib.rs +++ b/crates/marine-test-macro/src/lib.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -#![doc(html_root_url = "https://docs.rs/sdk-test-macro/0.1.11")] +#![doc(html_root_url = "https://docs.rs/sdk-test-macro/0.2.0")] #![deny( dead_code, nonstandard_style, @@ -36,7 +36,7 @@ use syn::spanned::Spanned; /// This macro allows user to write tests for services in the following form: ///```rust /// #[marine_test(config = "/path/to/Config.toml", modules_dir = "path/to/service/modules")] -/// fn test() { +/// fn test(greeting: marine_test_env::greeting::ModuleInterface) { /// let service_result = greeting.greeting("John".to_string()); /// assert_eq!(&service_result, "Hi, name!"); /// } diff --git a/sdk-test/Cargo.toml b/sdk-test/Cargo.toml index b77da26..b0a92d2 100644 --- a/sdk-test/Cargo.toml +++ b/sdk-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "marine-rs-sdk-test" -version = "0.1.11" # remember to update html_root_url +version = "0.2.0" # remember to update html_root_url description = "Backend SDK that allows testing modules for the Marine runtime" documentation = "https://docs.rs/marine-rs-sdk-test" repository = "https://github.com/fluencelabs/marine-rs-sdk/tree/master/fluence-test" @@ -17,8 +17,11 @@ all-features = true path = "src/lib.rs" doctest = false +[dev-dependencies] +trybuild = "1.0" + [dependencies] -marine-test-macro = { path = "../crates/marine-test-macro", version = "=0.1.11" } +marine-test-macro = { path = "../crates/marine-test-macro", version = "=0.2.0" } fluence-app-service = { version = "0.9.0", features = ["raw-module-api"] } serde = { version = "1.0.118", features = ["derive"] } diff --git a/sdk-test/src/lib.rs b/sdk-test/src/lib.rs index 2bbd124..4a0b6dc 100644 --- a/sdk-test/src/lib.rs +++ b/sdk-test/src/lib.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -#![doc(html_root_url = "https://docs.rs/sdk-test/0.1.11")] +#![doc(html_root_url = "https://docs.rs/sdk-test/0.2.0")] #![deny( dead_code, nonstandard_style,