From de2e61e60b3fcee0d44903a68af81da7165cd43e Mon Sep 17 00:00:00 2001 From: vms Date: Thu, 1 Apr 2021 04:26:36 +0300 Subject: [PATCH] houskeeping --- crates/fce-test-macro-impl/src/errors.rs | 4 +- crates/fce-test-macro-impl/src/fce_test.rs | 451 ------------------ .../src/fce_test/config_worker.rs | 82 ++++ .../src/fce_test/fce_test_impl.rs | 35 ++ .../src/fce_test/glue_code_generator.rs | 109 +++++ .../fce-test-macro-impl/src/fce_test/mod.rs | 23 + .../src/fce_test/module_generator.rs | 72 +++ .../module_generator/methods_generator.rs | 168 +++++++ .../module_generator/record_type_generator.rs | 62 +++ .../fce-test-macro-impl/src/fce_test/utils.rs | 77 +++ crates/fce-test-macro-impl/src/fce_test_.rs | 41 ++ crates/fce-test-macro-impl/src/lib.rs | 2 +- fluence-test/Cargo.toml | 1 - fluence-test/src/lib.rs | 1 - 14 files changed, 672 insertions(+), 456 deletions(-) delete mode 100644 crates/fce-test-macro-impl/src/fce_test.rs create mode 100644 crates/fce-test-macro-impl/src/fce_test/config_worker.rs create mode 100644 crates/fce-test-macro-impl/src/fce_test/fce_test_impl.rs create mode 100644 crates/fce-test-macro-impl/src/fce_test/glue_code_generator.rs create mode 100644 crates/fce-test-macro-impl/src/fce_test/mod.rs create mode 100644 crates/fce-test-macro-impl/src/fce_test/module_generator.rs create mode 100644 crates/fce-test-macro-impl/src/fce_test/module_generator/methods_generator.rs create mode 100644 crates/fce-test-macro-impl/src/fce_test/module_generator/record_type_generator.rs create mode 100644 crates/fce-test-macro-impl/src/fce_test/utils.rs create mode 100644 crates/fce-test-macro-impl/src/fce_test_.rs diff --git a/crates/fce-test-macro-impl/src/errors.rs b/crates/fce-test-macro-impl/src/errors.rs index d1f4b9f..12655a6 100644 --- a/crates/fce-test-macro-impl/src/errors.rs +++ b/crates/fce-test-macro-impl/src/errors.rs @@ -23,7 +23,7 @@ use thiserror::Error as ThisError; #[derive(Debug, ThisError)] pub enum TestGeneratorError { - #[error("{0}")] + #[error("Can't load Wasm modules into FCE: {0}")] WITParserError(#[from] WITParserError), #[error("{0}")] @@ -32,7 +32,7 @@ pub enum TestGeneratorError { #[error("{0}")] SynError(#[from] SynError), - #[error("{0}")] + #[error("Can't load Wasm modules from the provided config: {0}")] ConfigLoadError(#[from] AppServiceError), #[error("{0}")] diff --git a/crates/fce-test-macro-impl/src/fce_test.rs b/crates/fce-test-macro-impl/src/fce_test.rs deleted file mode 100644 index 8319fb6..0000000 --- a/crates/fce-test-macro-impl/src/fce_test.rs +++ /dev/null @@ -1,451 +0,0 @@ -/* - * Copyright 2020 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::attributes::FCETestAttributes; -use crate::{TResult, TestGeneratorError}; - -use fluence_app_service::TomlAppServiceConfig; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use quote::ToTokens; - -pub fn fce_test_impl(attrs: TokenStream2, input: TokenStream2) -> TResult { - use darling::FromMeta; - - // from https://github.com/dtolnay/syn/issues/788 - let parser = syn::punctuated::Punctuated::::parse_terminated; - let attrs = parser.parse2(attrs)?; - let attrs: Vec = attrs.into_iter().collect(); - let attrs = FCETestAttributes::from_list(&attrs)?; - - let func_item = syn::parse2::(input)?; - - generate_test_glue_code(func_item, attrs) -} - -fn generate_test_glue_code( - func_item: syn::ItemFn, - attrs: FCETestAttributes, -) -> TResult { - let fce_config = TomlAppServiceConfig::load(&attrs.config_path)?; - let modules_dir = match determine_modules_dir(&fce_config, attrs.modules_dir) { - Some(modules_dir) => modules_dir, - None => return Err(TestGeneratorError::ModulesDirUnspecified), - }; - - let fce_ctor = generate_fce_ctor(&attrs.config_path, &modules_dir); - let module_interfaces = collect_module_interfaces(&fce_config, modules_dir)?; - - let module_definitions = generate_module_definitions(module_interfaces.iter())?; - let module_iter = module_interfaces - .iter() - .map(|(module_name, _)| *module_name); - let module_ctors = generate_module_ctors(module_iter)?; - let original_block = func_item.block; - let signature = func_item.sig; - - let glue_code = quote! { - #[test] - #signature { - #module_definitions - - #fce_ctor - - #module_ctors - - #original_block - } - }; - - Ok(glue_code) -} - -fn generate_fce_ctor(config_path: &str, modules_dir: &PathBuf) -> TokenStream2 { - let config_path = config_path.to_token_stream(); - let modules_dir = modules_dir.to_string_lossy().to_string(); - - quote! { - let tmp_dir = std::env::temp_dir(); - let service_id = fluence_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 __fce_generated_fce_config = fluence_test::internal::TomlAppServiceConfig::load(#config_path.to_string()) - .unwrap_or_else(|e| panic!("app service located at `{}` config can't be loaded: {}", #config_path, e)); - __fce_generated_fce_config.service_base_dir = Some(tmp_dir); - __fce_generated_fce_config.toml_faas_config.modules_dir = Some(#modules_dir.to_string()); - - let fce = fluence_test::internal::AppService::new_with_empty_facade(__fce_generated_fce_config, service_id, std::collections::HashMap::new()) - .unwrap_or_else(|e| panic!("app service can't be created: {}", e)); - - let fce = std::rc::Rc::new(std::cell::RefCell::new(fce)); - } -} - -fn generate_module_ctors<'n>( - module_names: impl ExactSizeIterator, -) -> TResult { - let mut module_ctors = Vec::with_capacity(module_names.len()); - for name in module_names { - // TODO: optimize these two call because they are called twice for each module name - // and internally allocate memory in format - let module_name = generate_module_name(&name)?; - let struct_name = generate_struct_name(&name)?; - let name_for_user = new_ident(&name)?; - - let module_ctor = - quote! { let mut #name_for_user = #module_name::#struct_name { fce: fce.clone() }; }; - module_ctors.push(module_ctor); - } - - let module_ctors = quote! { #(#module_ctors),* }; - - Ok(module_ctors) -} - -use fce_wit_parser::module_raw_interface; -use fce_wit_parser::interface::FCEModuleInterface; -use fce_wit_parser::interface::FCERecordTypes; -use fce_wit_parser::interface::FCEFunctionSignature; -use fce_wit_parser::interface::it::IFunctionArg; -use fce_wit_parser::interface::it::IRecordFieldType; -use fce_wit_parser::interface::it::IType; - -use std::path::PathBuf; -use syn::parse::Parser; - -fn generate_module_definitions<'i>( - module_interfaces: impl ExactSizeIterator, -) -> TResult { - let mut module_definitions = Vec::with_capacity(module_interfaces.len()); - - for interface in module_interfaces { - let module_definition = generate_module_definition(&interface.0, &interface.1)?; - module_definitions.push(module_definition); - } - - let module_definitions = quote! { #(#module_definitions),*}; - - Ok(module_definitions) -} - -fn generate_module_definition( - module_name: &str, - module_interface: &FCEModuleInterface, -) -> TResult { - let module_name_ident = generate_module_name(module_name)?; - let struct_name_ident = generate_struct_name(module_name)?; - let module_records = generate_records(&module_interface.record_types)?; - let module_functions = generate_module_methods( - module_name, - module_interface.function_signatures.iter(), - &module_interface.record_types, - )?; - - let module_definition = quote! { - pub mod #module_name_ident { - #module_records - - pub struct #struct_name_ident { - pub fce: std::rc::Rc>, - } - - impl #struct_name_ident { - #(#module_functions)* - } - } - }; - - Ok(module_definition) -} - -fn generate_module_methods<'m, 'r>( - module_name: &str, - method_signatures: impl ExactSizeIterator, - records: &'r FCERecordTypes, -) -> TResult> { - let mut result = Vec::with_capacity(method_signatures.len()); - - for signature in method_signatures { - let func_name = new_ident(&signature.name)?; - let arguments = generate_arguments(signature.arguments.iter(), records)?; - let output_type = generate_output_type(&signature.outputs, records)?; - let fce_call = generate_fce_call(module_name, &signature, records)?; - - let module_method = quote! { - pub fn #func_name(&mut self, #(#arguments),*) #output_type { - #fce_call - } - }; - - result.push(module_method); - } - - Ok(result) -} - -fn generate_fce_call( - module_name: &str, - method_signature: &FCEFunctionSignature, - records: &FCERecordTypes, -) -> TResult { - let args = method_signature.arguments.iter().map(|a| a.name.as_str()); - let convert_arguments = generate_arguments_converter(args)?; - - let output_type = get_output_type(&method_signature.outputs); - let set_result = generate_set_result(&output_type); - let function_call = generate_function_call(module_name, &method_signature.name); - let convert_result_to_output_type = generate_convert_to_output(&output_type, records)?; - let ret = generate_ret(&output_type); - - let function_call = quote! { - use std::ops::DerefMut; - - #convert_arguments - - #set_result #function_call - - #convert_result_to_output_type - - #ret - }; - - Ok(function_call) -} - -fn generate_arguments_converter<'a>( - args: impl ExactSizeIterator, -) -> TResult { - let mut arguments = Vec::with_capacity(args.len()); - - for arg in args { - let arg_ident = new_ident(arg)?; - arguments.push(arg_ident); - } - - let arguments_serializer = - quote! { let arguments = fluence_test::internal::json!([#(#arguments),*]); }; - Ok(arguments_serializer) -} - -fn generate_function_call(module_name: &str, method_name: &str) -> TokenStream2 { - quote! { self.fce.as_ref().borrow_mut().call_with_module_name(#module_name, #method_name, arguments, <_>::default()).expect("call to FCE failed"); } -} - -fn generate_set_result(output_type: &Option<&IType>) -> TokenStream2 { - match output_type { - Some(_) => quote! { let result = }, - None => TokenStream2::new(), - } -} - -fn generate_convert_to_output( - output_type: &Option<&IType>, - records: &FCERecordTypes, -) -> TResult { - let result_stream = match output_type { - Some(ty) => { - let ty = itype_to_tokens(ty, records)?; - quote! { - let result: #ty = serde_json::from_value(result).expect("the default deserializer shouldn't fail"); - } - } - None => TokenStream2::new(), - }; - - Ok(result_stream) -} - -fn generate_ret(output_type: &Option<&IType>) -> TokenStream2 { - match output_type { - Some(_) => quote! { result }, - None => TokenStream2::new(), - } -} - -fn generate_arguments<'a, 'r>( - arguments: impl ExactSizeIterator, - records: &'r FCERecordTypes, -) -> TResult> { - let mut result = Vec::with_capacity(arguments.len()); - for argument in arguments { - let arg_name = new_ident(&argument.name)?; - let arg_type = itype_to_tokens(&argument.ty, records)?; - - let arg = quote! { #arg_name: #arg_type }; - result.push(arg); - } - - Ok(result) -} - -fn generate_output_type(output_types: &[IType], records: &FCERecordTypes) -> TResult { - let output_type = get_output_type(output_types); - match output_type { - None => Ok(TokenStream2::new()), - Some(ty) => { - let output_type = itype_to_tokens(&ty, records)?; - let output_type = quote! { -> #output_type }; - - Ok(output_type) - } - } -} - -fn get_output_type(output_types: &[IType]) -> Option<&IType> { - match output_types.len() { - 0 => None, - 1 => Some(&output_types[0]), - _ => unimplemented!("function with more than 1 arguments aren't supported now"), - } -} - -fn generate_records(records: &FCERecordTypes) -> TResult { - use std::ops::Deref; - - let mut result = TokenStream2::new(); - - for (_, record) in records.iter() { - let record_name_ident = generate_record_name(&record.name)?; - let fields = prepare_field(record.fields.deref(), records)?; - - let record = quote! { - #[derive(Clone, fluence_test::internal::Serialize, fluence_test::internal::Deserialize)] - pub struct #record_name_ident { - #fields - } - }; - result.extend(record); - } - - Ok(result) -} - -fn prepare_field(fields: &[IRecordFieldType], records: &FCERecordTypes) -> TResult { - let mut result = TokenStream2::new(); - - for field in fields { - let field_name = new_ident(&field.name)?; - let field_type = itype_to_tokens(&field.ty, records)?; - - let field = quote! { #field_name: #field_type, }; - result.extend(field); - } - - Ok(result) -} - -fn generate_module_name(module_name: &str) -> TResult { - let extended_module_name = format!("__fce_generated_{}", module_name); - new_ident(&extended_module_name) -} - -fn generate_record_name(record_name: &str) -> TResult { - let extended_record_name = format!("{}", record_name); - new_ident(&extended_record_name) -} - -fn generate_struct_name(struct_name: &str) -> TResult { - let extended_struct_name = format!("FCEGeneratedStruct{}", struct_name); - new_ident(&extended_struct_name) -} - -fn new_ident(ident_str: &str) -> TResult { - syn::parse_str::(ident_str).map_err(Into::into) -} - -fn itype_to_tokens(itype: &IType, records: &FCERecordTypes) -> TResult { - let token_stream = match itype { - IType::Record(record_id) => { - let record = records - .get(record_id) - .ok_or_else(|| crate::errors::CorruptedITSection::AbsentRecord(*record_id))?; - let record_name = new_ident(&record.name)?; - let token_stream = quote! { #record_name }; - token_stream - } - IType::Array(ty) => { - let inner_ty_token_stream = itype_to_tokens(ty, records)?; - let token_stream = quote! { Vec<#inner_ty_token_stream> }; - token_stream - } - IType::String => quote! { String }, - IType::S8 => quote! { i8 }, - IType::S16 => quote! { i16 }, - IType::S32 => quote! { i32 }, - IType::S64 => quote! { i64 }, - IType::U8 => quote! { u8 }, - IType::U16 => quote! { u16 }, - IType::U32 => quote! { u32 }, - IType::U64 => quote! { u64 }, - IType::I32 => quote! { i32 }, - IType::I64 => quote! { i64 }, - IType::F32 => quote! { f32 }, - IType::F64 => quote! { f64 }, - IType::Anyref => unimplemented!("anyref isn't supported and will be deleted from IType"), - }; - - Ok(token_stream) -} - -fn collect_module_interfaces( - config: &TomlAppServiceConfig, - modules_dir: PathBuf, -) -> TResult> { - let module_paths = collect_module_paths(config, modules_dir); - println!("module paths: {:?}", module_paths); - - module_paths - .into_iter() - .map(|(name, path)| module_raw_interface(path).map(|interface| (name, interface))) - .collect::, _>>() - .map_err(Into::into) -} - -fn collect_module_paths( - config: &TomlAppServiceConfig, - modules_dir: PathBuf, -) -> Vec<(&str, PathBuf)> { - config - .toml_faas_config - .module - .iter() - .map(|m| { - let module_file_name = m.file_name.as_ref().unwrap_or_else(|| &m.name); - let module_file_name = PathBuf::from(module_file_name); - // TODO: is it right to always have .wasm extension? - let module_path = modules_dir.join(module_file_name).with_extension("wasm"); - - (m.name.as_str(), module_path) - }) - .collect::>() -} - -fn determine_modules_dir( - config: &TomlAppServiceConfig, - modules_dir: Option, -) -> Option { - match modules_dir { - Some(modules_dir) => Some(PathBuf::from(modules_dir)), - None => config - .toml_faas_config - .modules_dir - .as_ref() - .map(|p| PathBuf::from(p)), - } -} diff --git a/crates/fce-test-macro-impl/src/fce_test/config_worker.rs b/crates/fce-test-macro-impl/src/fce_test/config_worker.rs new file mode 100644 index 0000000..6d3ffbc --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/config_worker.rs @@ -0,0 +1,82 @@ +/* + * 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::TResult; + +use fluence_app_service::TomlAppServiceConfig; +use fce_wit_parser::module_raw_interface; +use fce_wit_parser::interface::FCEModuleInterface; + +use std::path::PathBuf; + +pub(super) struct Module<'m> { + pub name: &'m str, + pub interface: FCEModuleInterface, +} + +impl<'m> Module<'m> { + fn new(name: &'m str, interface: FCEModuleInterface) -> Self { + Self { name, interface } + } +} + +pub(super) fn collect_modules( + config: &TomlAppServiceConfig, + modules_dir: PathBuf, +) -> TResult>> { + let module_paths = collect_module_paths(config, modules_dir); + + module_paths + .into_iter() + .map(|(name, path)| { + module_raw_interface(path).map(|interface| Module::new(name, interface)) + }) + .collect::, _>>() + .map_err(Into::into) +} + +fn collect_module_paths( + config: &TomlAppServiceConfig, + modules_dir: PathBuf, +) -> Vec<(&str, PathBuf)> { + config + .toml_faas_config + .module + .iter() + .map(|m| { + let module_file_name = m.file_name.as_ref().unwrap_or_else(|| &m.name); + let module_file_name = PathBuf::from(module_file_name); + // TODO: is it correct to always have .wasm extension? + let module_path = modules_dir.join(module_file_name).with_extension("wasm"); + + (m.name.as_str(), module_path) + }) + .collect::>() +} + +pub(super) fn determine_modules_dir( + config: &TomlAppServiceConfig, + modules_dir: Option, +) -> Option { + match modules_dir { + Some(modules_dir) => Some(PathBuf::from(modules_dir)), + None => config + .toml_faas_config + .modules_dir + .as_ref() + .map(|p| PathBuf::from(p)), + } +} diff --git a/crates/fce-test-macro-impl/src/fce_test/fce_test_impl.rs b/crates/fce-test-macro-impl/src/fce_test/fce_test_impl.rs new file mode 100644 index 0000000..b739529 --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/fce_test_impl.rs @@ -0,0 +1,35 @@ +/* + * 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::attributes::FCETestAttributes; +use crate::TResult; +use crate::fce_test::glue_code_generator::generate_test_glue_code; + +use proc_macro2::TokenStream; +use darling::FromMeta; +use syn::parse::Parser; + +pub fn fce_test_impl(attrs: TokenStream, input: TokenStream) -> TResult { + // from https://github.com/dtolnay/syn/issues/788 + let parser = syn::punctuated::Punctuated::::parse_terminated; + let attrs = parser.parse2(attrs)?; + let attrs: Vec = attrs.into_iter().collect(); + let attrs = FCETestAttributes::from_list(&attrs)?; + + let func_item = syn::parse2::(input)?; + + generate_test_glue_code(func_item, attrs) +} diff --git a/crates/fce-test-macro-impl/src/fce_test/glue_code_generator.rs b/crates/fce-test-macro-impl/src/fce_test/glue_code_generator.rs new file mode 100644 index 0000000..b84cea4 --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/glue_code_generator.rs @@ -0,0 +1,109 @@ +/* + * 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::attributes::FCETestAttributes; +use crate::TResult; +use crate::TestGeneratorError; +use crate::fce_test; + +use fluence_app_service::TomlAppServiceConfig; +use proc_macro2::TokenStream; +use quote::quote; +use quote::ToTokens; + +use std::path::PathBuf; + +pub(super) fn generate_test_glue_code( + func_item: syn::ItemFn, + attrs: FCETestAttributes, +) -> TResult { + let fce_config = TomlAppServiceConfig::load(&attrs.config_path)?; + let modules_dir = + match fce_test::config_worker::determine_modules_dir(&fce_config, attrs.modules_dir) { + Some(modules_dir) => modules_dir, + None => return Err(TestGeneratorError::ModulesDirUnspecified), + }; + + let fce_ctor = generate_fce_ctor(&attrs.config_path, &modules_dir); + let module_interfaces = fce_test::config_worker::collect_modules(&fce_config, modules_dir)?; + + let module_definitions = + fce_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 original_block = func_item.block; + let signature = func_item.sig; + + let glue_code = quote! { + #[test] + #signature { + #module_definitions + + #fce_ctor + + #module_ctors + + #original_block + } + }; + + Ok(glue_code) +} + +fn generate_fce_ctor(config_path: &str, modules_dir: &PathBuf) -> TokenStream { + let config_path = config_path.to_token_stream(); + let modules_dir = modules_dir.to_string_lossy().to_string(); + + quote! { + let tmp_dir = std::env::temp_dir(); + let service_id = fluence_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 __fce_generated_fce_config = fluence_test::internal::TomlAppServiceConfig::load(#config_path.to_string()) + .unwrap_or_else(|e| panic!("app service located at `{}` config can't be loaded: {}", #config_path, e)); + __fce_generated_fce_config.service_base_dir = Some(tmp_dir); + __fce_generated_fce_config.toml_faas_config.modules_dir = Some(#modules_dir.to_string()); + + let fce = fluence_test::internal::AppService::new_with_empty_facade(__fce_generated_fce_config, service_id, std::collections::HashMap::new()) + .unwrap_or_else(|e| panic!("app service can't be created: {}", e)); + + let fce = std::rc::Rc::new(std::cell::RefCell::new(fce)); + } +} + +fn generate_module_ctors<'n>( + module_names: impl ExactSizeIterator, +) -> TResult { + let mut module_ctors = Vec::with_capacity(module_names.len()); + for name in module_names { + // TODO: optimize these two call because they are called twice for each module name + // and internally allocate memory in format call. + let module_name = fce_test::utils::generate_module_name(&name)?; + let struct_name = fce_test::utils::generate_struct_name(&name)?; + let name_for_user = fce_test::utils::new_ident(&name)?; + + let module_ctor = + quote! { let mut #name_for_user = #module_name::#struct_name { fce: fce.clone() }; }; + module_ctors.push(module_ctor); + } + + let module_ctors = quote! { #(#module_ctors),* }; + + Ok(module_ctors) +} diff --git a/crates/fce-test-macro-impl/src/fce_test/mod.rs b/crates/fce-test-macro-impl/src/fce_test/mod.rs new file mode 100644 index 0000000..ed7a3a5 --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/mod.rs @@ -0,0 +1,23 @@ +/* + * 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 config_worker; +mod fce_test_impl; +mod glue_code_generator; +mod module_generator; +mod utils; + +pub use fce_test_impl::fce_test_impl; diff --git a/crates/fce-test-macro-impl/src/fce_test/module_generator.rs b/crates/fce-test-macro-impl/src/fce_test/module_generator.rs new file mode 100644 index 0000000..492115a --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/module_generator.rs @@ -0,0 +1,72 @@ +/* + * 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 methods_generator; +mod record_type_generator; + +use crate::fce_test::utils; +use crate::fce_test::config_worker::Module; +use crate::TResult; + +use fce_wit_parser::interface::FCEModuleInterface; + +use proc_macro2::TokenStream; +use quote::quote; + +pub(super) fn generate_module_definitions<'i>( + modules: impl ExactSizeIterator>, +) -> TResult { + let mut module_definitions = Vec::with_capacity(modules.len()); + + for module in modules { + let module_definition = generate_module_definition(&module.name, &module.interface)?; + module_definitions.push(module_definition); + } + + let module_definitions = quote! { #(#module_definitions),*}; + + Ok(module_definitions) +} + +fn generate_module_definition( + module_name: &str, + module_interface: &FCEModuleInterface, +) -> TResult { + let module_name_ident = utils::generate_module_name(module_name)?; + let struct_name_ident = utils::generate_struct_name(module_name)?; + let module_records = record_type_generator::generate_records(&module_interface.record_types)?; + let module_functions = methods_generator::generate_module_methods( + module_name, + module_interface.function_signatures.iter(), + &module_interface.record_types, + )?; + + let module_definition = quote! { + pub mod #module_name_ident { + #module_records + + pub struct #struct_name_ident { + pub fce: std::rc::Rc>, + } + + impl #struct_name_ident { + #(#module_functions)* + } + } + }; + + Ok(module_definition) +} diff --git a/crates/fce-test-macro-impl/src/fce_test/module_generator/methods_generator.rs b/crates/fce-test-macro-impl/src/fce_test/module_generator/methods_generator.rs new file mode 100644 index 0000000..9facb9d --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/module_generator/methods_generator.rs @@ -0,0 +1,168 @@ +/* + * 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::fce_test::utils; +use crate::TResult; + +use fce_wit_parser::interface::it::IType; +use fce_wit_parser::interface::it::IFunctionArg; +use fce_wit_parser::interface::FCERecordTypes; +use fce_wit_parser::interface::FCEFunctionSignature; + +use proc_macro2::TokenStream; +use quote::quote; + +pub(super) fn generate_module_methods<'m, 'r>( + module_name: &str, + method_signatures: impl ExactSizeIterator, + records: &'r FCERecordTypes, +) -> TResult> { + let mut result = Vec::with_capacity(method_signatures.len()); + + for signature in method_signatures { + let func_name = utils::new_ident(&signature.name)?; + let arguments = generate_arguments(signature.arguments.iter(), records)?; + let output_type = generate_output_type(&signature.outputs, records)?; + let fce_call = generate_fce_call(module_name, &signature, records)?; + + let module_method = quote! { + pub fn #func_name(&mut self, #(#arguments),*) #output_type { + #fce_call + } + }; + + result.push(module_method); + } + + Ok(result) +} + +fn generate_fce_call( + module_name: &str, + method_signature: &FCEFunctionSignature, + records: &FCERecordTypes, +) -> TResult { + let args = method_signature.arguments.iter().map(|a| a.name.as_str()); + let convert_arguments = generate_arguments_converter(args)?; + + let output_type = get_output_type(&method_signature.outputs); + let set_result = generate_set_result(&output_type); + let function_call = generate_function_call(module_name, &method_signature.name); + let convert_result_to_output_type = generate_convert_to_output(&output_type, records)?; + let ret = generate_ret(&output_type); + + let function_call = quote! { + use std::ops::DerefMut; + + #convert_arguments + + #set_result #function_call + + #convert_result_to_output_type + + #ret + }; + + Ok(function_call) +} + +fn generate_arguments_converter<'a>( + args: impl ExactSizeIterator, +) -> TResult { + let mut arguments = Vec::with_capacity(args.len()); + + for arg in args { + let arg_ident = utils::new_ident(arg)?; + arguments.push(arg_ident); + } + + let arguments_serializer = + quote! { let arguments = fluence_test::internal::json!([#(#arguments),*]); }; + + Ok(arguments_serializer) +} + +fn generate_function_call(module_name: &str, method_name: &str) -> TokenStream { + quote! { self.fce.as_ref().borrow_mut().call_with_module_name(#module_name, #method_name, arguments, <_>::default()).expect("call to FCE failed"); } +} + +fn generate_set_result(output_type: &Option<&IType>) -> TokenStream { + match output_type { + Some(_) => quote! { let result = }, + None => TokenStream::new(), + } +} + +fn generate_convert_to_output( + output_type: &Option<&IType>, + records: &FCERecordTypes, +) -> TResult { + let result_stream = match output_type { + Some(ty) => { + let ty = utils::itype_to_tokens(ty, records)?; + quote! { + let result: #ty = serde_json::from_value(result).expect("the default deserializer shouldn't fail"); + } + } + None => TokenStream::new(), + }; + + Ok(result_stream) +} + +fn generate_ret(output_type: &Option<&IType>) -> TokenStream { + match output_type { + Some(_) => quote! { result }, + None => TokenStream::new(), + } +} + +fn generate_arguments<'a, 'r>( + arguments: impl ExactSizeIterator, + records: &'r FCERecordTypes, +) -> TResult> { + let mut result = Vec::with_capacity(arguments.len()); + for argument in arguments { + let arg_name = utils::new_ident(&argument.name)?; + let arg_type = utils::itype_to_tokens(&argument.ty, records)?; + + let arg = quote! { #arg_name: #arg_type }; + result.push(arg); + } + + Ok(result) +} + +fn generate_output_type(output_types: &[IType], records: &FCERecordTypes) -> TResult { + let output_type = get_output_type(output_types); + match output_type { + None => Ok(TokenStream::new()), + Some(ty) => { + let output_type = utils::itype_to_tokens(&ty, records)?; + let output_type = quote! { -> #output_type }; + + Ok(output_type) + } + } +} + +fn get_output_type(output_types: &[IType]) -> Option<&IType> { + match output_types.len() { + 0 => None, + 1 => Some(&output_types[0]), + _ => unimplemented!("function with more than 1 arguments aren't supported now"), + } +} diff --git a/crates/fce-test-macro-impl/src/fce_test/module_generator/record_type_generator.rs b/crates/fce-test-macro-impl/src/fce_test/module_generator/record_type_generator.rs new file mode 100644 index 0000000..9fb81dc --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/module_generator/record_type_generator.rs @@ -0,0 +1,62 @@ +/* + * 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::fce_test::utils; +use crate::TResult; + +use fce_wit_parser::interface::it::IRecordFieldType; +use fce_wit_parser::interface::FCERecordTypes; + +use proc_macro2::TokenStream; +use quote::quote; + +pub(super) fn generate_records(records: &FCERecordTypes) -> TResult { + use std::ops::Deref; + + let mut result = TokenStream::new(); + + for (_, record) in records.iter() { + let record_name_ident = utils::generate_record_name(&record.name)?; + let fields = prepare_field(record.fields.deref().iter(), records)?; + + let record = quote! { + #[derive(Clone, fluence_test::internal::Serialize, fluence_test::internal::Deserialize)] + pub struct #record_name_ident { + #(#fields),* + } + }; + result.extend(record); + } + + Ok(result) +} + +fn prepare_field<'f>( + fields: impl ExactSizeIterator, + records: &FCERecordTypes, +) -> TResult> { + let mut result = Vec::with_capacity(fields.len()); + + for field in fields { + let field_name = utils::new_ident(&field.name)?; + let field_type = utils::itype_to_tokens(&field.ty, records)?; + + let field = quote! { #field_name: #field_type }; + result.push(field); + } + + Ok(result) +} diff --git a/crates/fce-test-macro-impl/src/fce_test/utils.rs b/crates/fce-test-macro-impl/src/fce_test/utils.rs new file mode 100644 index 0000000..a39f823 --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test/utils.rs @@ -0,0 +1,77 @@ +/* + * 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::TResult; +use fce_wit_parser::interface::FCERecordTypes; +use fce_wit_parser::interface::it::IType; + +use proc_macro2::TokenStream; +use quote::quote; + +pub(super) fn generate_module_name(module_name: &str) -> TResult { + let extended_module_name = format!("__fce_generated_{}", module_name); + new_ident(&extended_module_name) +} + +pub(super) fn generate_record_name(record_name: &str) -> TResult { + let extended_record_name = format!("{}", record_name); + new_ident(&extended_record_name) +} + +pub(super) fn generate_struct_name(struct_name: &str) -> TResult { + let extended_struct_name = format!("FCEGeneratedStruct{}", struct_name); + new_ident(&extended_struct_name) +} + +pub(super) fn new_ident(ident_str: &str) -> TResult { + syn::parse_str::(ident_str).map_err(Into::into) +} + +pub(super) fn itype_to_tokens(itype: &IType, records: &FCERecordTypes) -> TResult { + let token_stream = match itype { + IType::Record(record_id) => { + let record = records + .get(record_id) + .ok_or_else(|| crate::errors::CorruptedITSection::AbsentRecord(*record_id))?; + let record_name = new_ident(&record.name)?; + let token_stream = quote! { #record_name }; + token_stream + } + IType::Array(ty) => { + let inner_ty_token_stream = itype_to_tokens(ty, records)?; + let token_stream = quote! { Vec<#inner_ty_token_stream> }; + token_stream + } + IType::String => quote! { String }, + IType::S8 => quote! { i8 }, + IType::S16 => quote! { i16 }, + IType::S32 => quote! { i32 }, + IType::S64 => quote! { i64 }, + IType::U8 => quote! { u8 }, + IType::U16 => quote! { u16 }, + IType::U32 => quote! { u32 }, + IType::U64 => quote! { u64 }, + IType::I32 => quote! { i32 }, + IType::I64 => quote! { i64 }, + IType::F32 => quote! { f32 }, + IType::F64 => quote! { f64 }, + IType::Anyref => { + unimplemented!("anyrefs aren't supported and will be deleted from IType soon") + } + }; + + Ok(token_stream) +} diff --git a/crates/fce-test-macro-impl/src/fce_test_.rs b/crates/fce-test-macro-impl/src/fce_test_.rs new file mode 100644 index 0000000..fccd95c --- /dev/null +++ b/crates/fce-test-macro-impl/src/fce_test_.rs @@ -0,0 +1,41 @@ +/* + * Copyright 2020 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::attributes::FCETestAttributes; +use crate::TResult; +use crate::TestGeneratorError; + +use fluence_app_service::TomlAppServiceConfig; +use proc_macro2::TokenStream; +use quote::quote; +use quote::ToTokens; + + + +use fce_wit_parser::module_raw_interface; +use fce_wit_parser::interface::FCEModuleInterface; +use fce_wit_parser::interface::FCERecordTypes; +use fce_wit_parser::interface::FCEFunctionSignature; +use fce_wit_parser::interface::it::IFunctionArg; +use fce_wit_parser::interface::it::IRecordFieldType; +use fce_wit_parser::interface::it::IType; + +use std::path::PathBuf; +use syn::parse::Parser; + + + + diff --git a/crates/fce-test-macro-impl/src/lib.rs b/crates/fce-test-macro-impl/src/lib.rs index 9a16b6d..51f196e 100644 --- a/crates/fce-test-macro-impl/src/lib.rs +++ b/crates/fce-test-macro-impl/src/lib.rs @@ -16,7 +16,7 @@ #![doc(html_root_url = "https://docs.rs/fluence-sdk-macro/0.5.0")] #![deny( - // dead_code, + dead_code, nonstandard_style, unused_imports, unused_mut, diff --git a/fluence-test/Cargo.toml b/fluence-test/Cargo.toml index d92d49c..d75e52e 100644 --- a/fluence-test/Cargo.toml +++ b/fluence-test/Cargo.toml @@ -19,7 +19,6 @@ path = "src/lib.rs" [dependencies] fluence-sdk-test-macro = { path = "../crates/fce-test-macro", version = "=0.5.0" } -fluence-sdk-test-macro-impl = { path = "../crates/fce-test-macro-impl", version = "=0.5.0" } fluence-app-service = { version = "0.5.2", features = ["raw-module-api"] } serde = { version = "1.0.118", features = ["derive"] } diff --git a/fluence-test/src/lib.rs b/fluence-test/src/lib.rs index dd21149..278355f 100644 --- a/fluence-test/src/lib.rs +++ b/fluence-test/src/lib.rs @@ -27,7 +27,6 @@ #![warn(rust_2018_idioms)] pub use fluence_sdk_test_macro::fce_test; -pub use fluence_sdk_test_macro_impl::fce_test_impl; /// These API functions are intended for internal usage in generated code. /// Normally, you shouldn't use them.