This commit is contained in:
vms 2021-04-01 03:49:44 +03:00
parent ca75ef5ead
commit 0574adb39a
10 changed files with 162 additions and 61 deletions

View File

@ -2,6 +2,7 @@
members = [
"crates/fce-macro",
"crates/fce-test-macro",
"crates/fce-test-macro-impl",
"crates/main",
"crates/wit",
"fluence",

View File

@ -0,0 +1,24 @@
[package]
name = "fluence-sdk-test-macro-impl"
version = "0.5.0" # remember to update html_root_url
edition = "2018"
description = "Implementation of the `#[fce_test]` macro"
repository = "https://github.com/fluencelabs/rust-sdk/crates/macro-test"
authors = ["Fluence Labs"]
keywords = ["fluence", "sdk", "webassembly", "procedural_macros"]
categories = ["api-bindings", "wasm"]
license = "Apache-2.0"
[package.metadata.docs.rs] # https://docs.rs/about
all-features = 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"
proc-macro-error = { version = "1.0.4", default-features = false }
syn = { version = '1.0.64', features = ['full'] }
thiserror = "1.0.24"

View File

@ -19,4 +19,6 @@ use darling::FromMeta;
#[derive(Debug, Default, Clone, FromMeta)]
pub(crate) struct FCETestAttributes {
pub(crate) config_path: String,
#[darling(default)]
pub(crate) modules_dir: Option<String>,
}

View File

@ -22,7 +22,7 @@ use syn::Error as SynError;
use thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub(crate) enum TestGeneratorError {
pub enum TestGeneratorError {
#[error("{0}")]
WITParserError(#[from] WITParserError),
@ -37,10 +37,15 @@ pub(crate) enum TestGeneratorError {
#[error("{0}")]
AttributesError(#[from] DarlingError),
#[error(
"neither attribute specified nor service config contains modules_dir, please specify it"
)]
ModulesDirUnspecified,
}
#[derive(Debug, ThisError)]
pub(crate) enum CorruptedITSection {
pub enum CorruptedITSection {
#[error("record with {0} is absent in embedded IT section")]
AbsentRecord(u64),
}

View File

@ -15,13 +15,14 @@
*/
use crate::attributes::FCETestAttributes;
use crate::TResult;
use crate::{TResult, TestGeneratorError};
use fluence_app_service::TomlAppServiceConfig;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use quote::ToTokens;
pub(super) fn fce_test_impl(attrs: TokenStream2, func_input: syn::ItemFn) -> TResult<TokenStream2> {
pub fn fce_test_impl(attrs: TokenStream2, input: TokenStream2) -> TResult<TokenStream2> {
use darling::FromMeta;
// from https://github.com/dtolnay/syn/issues/788
@ -30,21 +31,31 @@ pub(super) fn fce_test_impl(attrs: TokenStream2, func_input: syn::ItemFn) -> TRe
let attrs: Vec<syn::NestedMeta> = attrs.into_iter().collect();
let attrs = FCETestAttributes::from_list(&attrs)?;
generate_test_glue_code(func_input, &attrs.config_path)
let func_item = syn::parse2::<syn::ItemFn>(input)?;
generate_test_glue_code(func_item, attrs)
}
fn generate_test_glue_code(func: syn::ItemFn, config_path: &str) -> TResult<TokenStream2> {
let fce_config = TomlAppServiceConfig::load(config_path)?;
let module_interfaces = collect_module_interfaces(&fce_config)?;
fn generate_test_glue_code(
func_item: syn::ItemFn,
attrs: FCETestAttributes,
) -> TResult<TokenStream2> {
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 fce_ctor = generate_fce_ctor(config_path);
let module_iter = module_interfaces
.iter()
.map(|(module_name, _)| *module_name);
let module_ctors = generate_module_ctors(module_iter)?;
let original_block = func.block;
let signature = func.sig;
let original_block = func_item.block;
let signature = func_item.sig;
let glue_code = quote! {
#[test]
@ -62,23 +73,24 @@ fn generate_test_glue_code(func: syn::ItemFn, config_path: &str) -> TResult<Toke
Ok(glue_code)
}
fn generate_fce_ctor(config_path: &str) -> TokenStream2 {
let config_path = quote! { #config_path };
let tmp_file_path = std::env::temp_dir();
let random_uuid = uuid::Uuid::new_v4().to_string();
let service_id = quote! { #random_uuid };
let tmp_file_path = tmp_file_path.join(random_uuid);
let tmp_file_path = tmp_file_path.to_string_lossy().to_string();
let tmp_file_path = quote! { #tmp_file_path };
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 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_file_path.to_string());
let tmp_dir = std::env::temp_dir();
let service_id = fluence_test::internal::Uuid::new_v4().to_string();
let fce = fluence_test::internal::AppService::new_with_empty_facade(__fce__generated_fce_config, #service_id, std::collections::HashMap::new())
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));
@ -94,12 +106,14 @@ fn generate_module_ctors<'n>(
// 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! { #module_name::#struct_name { fce: fce.clone() }};
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)*, };
let module_ctors = quote! { #(#module_ctors),* };
Ok(module_ctors)
}
@ -125,7 +139,7 @@ fn generate_module_definitions<'i>(
module_definitions.push(module_definition);
}
let module_definitions = quote! { #(#module_definitions)*,};
let module_definitions = quote! { #(#module_definitions),*};
Ok(module_definitions)
}
@ -147,7 +161,7 @@ fn generate_module_definition(
pub mod #module_name_ident {
#module_records
struct #struct_name_ident {
pub struct #struct_name_ident {
pub fce: std::rc::Rc<std::cell::RefCell<fluence_test::internal::AppService>>,
}
@ -225,12 +239,12 @@ fn generate_arguments_converter<'a>(
}
let arguments_serializer =
quote! { let arguments = fluence_test::internal::json!([#(#arguments)*,]) };
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"); }
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 {
@ -312,7 +326,7 @@ fn generate_records(records: &FCERecordTypes) -> TResult<TokenStream2> {
let record = quote! {
#[derive(Clone, fluence_test::internal::Serialize, fluence_test::internal::Deserialize)]
struct #record_name_ident {
pub struct #record_name_ident {
#fields
}
};
@ -329,7 +343,7 @@ fn prepare_field(fields: &[IRecordFieldType], records: &FCERecordTypes) -> TResu
let field_name = new_ident(&field.name)?;
let field_type = itype_to_tokens(&field.ty, records)?;
let field = quote! { #field_name: #field_type };
let field = quote! { #field_name: #field_type, };
result.extend(field);
}
@ -342,7 +356,7 @@ fn generate_module_name(module_name: &str) -> TResult<syn::Ident> {
}
fn generate_record_name(record_name: &str) -> TResult<syn::Ident> {
let extended_record_name = format!("FCEGeneratedRecord{}", record_name);
let extended_record_name = format!("{}", record_name);
new_ident(&extended_record_name)
}
@ -391,8 +405,10 @@ fn itype_to_tokens(itype: &IType, records: &FCERecordTypes) -> TResult<TokenStre
fn collect_module_interfaces(
config: &TomlAppServiceConfig,
modules_dir: PathBuf,
) -> TResult<Vec<(&str, FCEModuleInterface)>> {
let module_paths = collect_module_paths(config);
let module_paths = collect_module_paths(config, modules_dir);
println!("module paths: {:?}", module_paths);
module_paths
.into_iter()
@ -401,14 +417,10 @@ fn collect_module_interfaces(
.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();
fn collect_module_paths(
config: &TomlAppServiceConfig,
modules_dir: PathBuf,
) -> Vec<(&str, PathBuf)> {
config
.toml_faas_config
.module
@ -416,9 +428,24 @@ fn collect_module_paths(config: &TomlAppServiceConfig) -> Vec<(&str, PathBuf)> {
.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);
// 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::<Vec<_>>()
}
fn determine_modules_dir(
config: &TomlAppServiceConfig,
modules_dir: Option<String>,
) -> Option<PathBuf> {
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)),
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.
*/
#![doc(html_root_url = "https://docs.rs/fluence-sdk-macro/0.5.0")]
#![deny(
// dead_code,
nonstandard_style,
unused_imports,
unused_mut,
unused_variables,
unused_unsafe,
unreachable_patterns
)]
#![warn(rust_2018_idioms)]
#![recursion_limit = "1024"]
mod attributes;
mod errors;
mod fce_test;
pub use fce_test::fce_test_impl;
pub use errors::TestGeneratorError;
pub(crate) type TResult<T> = std::result::Result<T, TestGeneratorError>;

View File

@ -16,13 +16,9 @@ all-features = true
proc-macro = true
[dependencies]
fluence-app-service = { version = "0.5.2", features = ["raw-module-api"] }
fce-wit-parser = "0.4.0"
fluence-sdk-test-macro-impl = { path = "../fce-test-macro-impl", version = "=0.5.0" }
darling = "0.12.2"
quote = "1.0.9"
proc-macro2 = "1.0.24"
proc-macro-error = { version = "1.0.4", default-features = false }
syn = { version = '1.0.64', features = ['full'] }
thiserror = "1.0.24"
uuid = { version = "0.8.2", features = ["v4"] }

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
#![doc(html_root_url = "https://docs.rs/fluence-sdk-macro/0.4.2")]
#![doc(html_root_url = "https://docs.rs/fluence-sdk-macro/0.5.0")]
#![deny(
// dead_code,
dead_code,
nonstandard_style,
unused_imports,
unused_mut,
@ -27,15 +27,10 @@
#![warn(rust_2018_idioms)]
#![recursion_limit = "1024"]
mod attributes;
mod errors;
mod fce_test;
use fce_test::fce_test_impl;
use fluence_sdk_test_macro_impl::fce_test_impl;
use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;
pub(crate) type TResult<T> = std::result::Result<T, errors::TestGeneratorError>;
use syn::spanned::Spanned;
/// This macro allows user to write tests for services in the following form:
///```ignore
@ -59,9 +54,11 @@ pub(crate) type TResult<T> = std::result::Result<T, errors::TestGeneratorError>;
#[proc_macro_error]
#[proc_macro_attribute]
pub fn fce_test(attrs: TokenStream, input: TokenStream) -> TokenStream {
let func_input = syn::parse_macro_input!(input as syn::ItemFn);
match fce_test_impl(attrs.into(), func_input) {
let attrs: proc_macro2::TokenStream = attrs.into();
let attrs_span = attrs.span();
match fce_test_impl(attrs, input.into()) {
Ok(stream) => stream.into(),
Err(e) => proc_macro_error::abort_call_site!(format!("{}", e)),
Err(e) => proc_macro_error::abort!(attrs_span, format!("{}", e)),
}
}

View File

@ -19,4 +19,9 @@ 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"] }
serde_json = "1.0.64"
uuid = { version = "0.8.2", features = ["v4"] }

View File

@ -27,10 +27,17 @@
#![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.
pub mod internal {
pub use fluence_app_service::AppService;
pub use fluence_app_service::TomlAppServiceConfig;
pub use serde::Serialize;
pub use serde::Deserialize;
pub use serde_json::json;
pub use uuid::Uuid;
}