From 3ffe392e146bc268b9bc0bb27805c65d3aa57785 Mon Sep 17 00:00:00 2001 From: vms Date: Wed, 31 Mar 2021 11:36:54 +0300 Subject: [PATCH] progress --- crates/fce-test-macro/Cargo.toml | 6 +- crates/fce-test-macro/src/errors.rs | 38 +++++ crates/fce-test-macro/src/fce_test.rs | 220 ++++++++++++++++++++++++++ crates/fce-test-macro/src/lib.rs | 5 +- fluence-test/Cargo.toml | 2 +- 5 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 crates/fce-test-macro/src/errors.rs diff --git a/crates/fce-test-macro/Cargo.toml b/crates/fce-test-macro/Cargo.toml index e0b9cd5..80a16b1 100644 --- a/crates/fce-test-macro/Cargo.toml +++ b/crates/fce-test-macro/Cargo.toml @@ -16,8 +16,12 @@ all-features = true proc-macro = true [dependencies] +fluence-app-service = { version = "0.5.2", features = ["raw-module-api"] } +fce-wit-parser = "0.4.0" + +darling = "0.12.2" quote = "1.0.9" proc-macro2 = "1.0.24" syn = { version = '1.0.64', features = ['full'] } +thiserror = "1.0.24" uuid = { version = "0.8.2", features = ["v4"] } -darling = "0.12.2" diff --git a/crates/fce-test-macro/src/errors.rs b/crates/fce-test-macro/src/errors.rs new file mode 100644 index 0000000..188dde9 --- /dev/null +++ b/crates/fce-test-macro/src/errors.rs @@ -0,0 +1,38 @@ +/* + * 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 fce_wit_parser::WITParserError; + +use syn::Error as SynError; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub(crate) enum TestGeneratorError { + #[error("{0}")] + WITParserError(#[from] WITParserError), + + #[error("{0}")] + CorruptedITSection(#[from] CorruptedITSection), + + #[error("{0}")] + SynError(#[from] SynError), +} + +#[derive(Debug, ThisError)] +pub(crate) enum CorruptedITSection { + #[error("record with {0} is absent in embedded IT section")] + AbsentRecord(u64), +} diff --git a/crates/fce-test-macro/src/fce_test.rs b/crates/fce-test-macro/src/fce_test.rs index 2d63100..3ea6f93 100644 --- a/crates/fce-test-macro/src/fce_test.rs +++ b/crates/fce-test-macro/src/fce_test.rs @@ -15,7 +15,9 @@ */ use crate::attributes::FCETestAttributes; +use crate::TResult; +use fluence_app_service::TomlAppServiceConfig; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; @@ -69,3 +71,221 @@ fn generate_fce_ctor(config_path: &str) -> TokenStream2 { .unwrap_or_else(|e| panic!("app service can't be created: {}", e)); } } + +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; + +fn generate_module_definition( + module_name: &str, + module_interface: &FCEModuleInterface, +) -> TResult { + let module_name = new_ident(module_name)?; + let module_records = generate_records(&module_interface.record_types)?; + let module_functions = generate_module_methods( + module_interface.function_signatures.iter(), + &module_interface.record_types, + )?; + + let module_definition = quote! { + pub mod #module_name { + #module_records + + struct #module_name { + pub fce: fluence_test::internal::AppService, + } + + impl #module_name { + #(#module_functions)* + } + } + }; + + Ok(module_definition) +} + +fn generate_module_methods<'m, 'r>( + 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 module_method = quote! { + pub fn #func_name(&self, #(#arguments),*) #output_type { + let result + } + }; + + result.push(module_method); + } + + Ok(result) +} + +fn generate_fce_call(method_signature: &FCEFunctionSignature) -> TokenStream2 { + let output_type = get_output_type(&method_signature.outputs); + + quote! { + #convert_arguments + + #set_result #function_call + + #convert_result_to_output_type + + #ret + } +} + +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 name = new_ident(&record.name)?; + let fields = prepare_field(record.fields.deref(), records)?; + + let record = quote! { + struct #name { + #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 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 delete from IType"), + }; + + Ok(token_stream) +} + +fn collect_module_interfaces( + config: &TomlAppServiceConfig, +) -> TResult> { + let module_paths = collect_module_paths(config); + + 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) -> Vec<(&str, PathBuf)> { + let base_dir = config + .toml_faas_config + .modules_dir + .as_ref() + .map(|p| PathBuf::from(p)) + .unwrap_or_default(); + + 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); + let module_path = base_dir.join(module_file_name); + + (m.name.as_str(), module_path) + }) + .collect::>() +} diff --git a/crates/fce-test-macro/src/lib.rs b/crates/fce-test-macro/src/lib.rs index 5e0a373..58e7749 100644 --- a/crates/fce-test-macro/src/lib.rs +++ b/crates/fce-test-macro/src/lib.rs @@ -16,7 +16,7 @@ #![doc(html_root_url = "https://docs.rs/fluence-sdk-macro/0.4.2")] #![deny( - dead_code, + // dead_code, nonstandard_style, unused_imports, unused_mut, @@ -28,11 +28,14 @@ #![recursion_limit = "1024"] mod attributes; +mod errors; mod fce_test; use fce_test::fce_test_impl; use proc_macro::TokenStream; +pub(crate) type TResult = std::result::Result; + /// This macro allows user to write tests for services in the following form: ///```ignore /// #[fce_test(config = "/path/to/Config.toml")] diff --git a/fluence-test/Cargo.toml b/fluence-test/Cargo.toml index bf7fcef..5562f61 100644 --- a/fluence-test/Cargo.toml +++ b/fluence-test/Cargo.toml @@ -19,4 +19,4 @@ path = "src/lib.rs" [dependencies] fluence-sdk-test-macro = { path = "../crates/fce-test-macro", version = "=0.5.0" } -fluence-app-service= { version = "0.5.2", features = ["raw-module-api"] } +fluence-app-service = { version = "0.5.2", features = ["raw-module-api"] }