From fd5b6786d29d056ff0721a59435b50005f13f05c Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 9 Oct 2022 20:41:09 +0200 Subject: test: add more unit tests --- macros/src/fn_trait.rs | 91 ++++++++++++++++- macros/src/macro_flag.rs | 45 ++++++++- macros/src/util/iterator_ext.rs | 57 ++++++++++- macros/src/util/string.rs | 14 +++ src/castable_factory/blocking.rs | 23 +++++ src/castable_factory/threadsafe.rs | 24 +++++ src/di_container/binding_map.rs | 201 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + 8 files changed, 455 insertions(+), 3 deletions(-) diff --git a/macros/src/fn_trait.rs b/macros/src/fn_trait.rs index 9820f02..a52a00d 100644 --- a/macros/src/fn_trait.rs +++ b/macros/src/fn_trait.rs @@ -5,7 +5,7 @@ use syn::token::Paren; use syn::{parenthesized, parse_str, Ident, Token, TraitBound, Type}; /// A function trait. `dyn Fn(u32) -> String` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct FnTrait { pub dyn_token: Token![dyn], @@ -84,3 +84,92 @@ impl ToTokens for FnTrait } } } + +#[cfg(test)] +mod tests +{ + use std::error::Error; + + use quote::{format_ident, quote}; + use syn::token::{Dyn, RArrow}; + use syn::{parse2, Path, PathSegment, TypePath}; + + use super::*; + + fn create_path(segments: &[PathSegment]) -> Path + { + Path { + leading_colon: None, + segments: segments.iter().cloned().collect(), + } + } + + fn create_type(path: Path) -> Type + { + Type::Path(TypePath { qself: None, path }) + } + + #[test] + fn can_parse_fn_trait() -> Result<(), Box> + { + assert_eq!( + parse2::(quote! { + dyn Fn(String, u32) -> Handle + })?, + FnTrait { + dyn_token: Dyn::default(), + trait_ident: format_ident!("Fn"), + paren_token: Paren::default(), + inputs: Punctuated::from_iter(vec![ + create_type(create_path(&[PathSegment::from(format_ident!( + "String" + ))])), + create_type(create_path(&[PathSegment::from(format_ident!("u32"))])) + ]), + r_arrow_token: RArrow::default(), + output: create_type(create_path(&[PathSegment::from(format_ident!( + "Handle" + ))])), + trait_bounds: Punctuated::new() + } + ); + + assert!(parse2::(quote! { + Fn(u32) -> Handle + }) + .is_err()); + + Ok(()) + } + + #[test] + fn can_parse_fn_trait_to_tokens() + { + assert_eq!( + FnTrait { + dyn_token: Dyn::default(), + trait_ident: format_ident!("Fn"), + paren_token: Paren::default(), + inputs: Punctuated::from_iter(vec![ + create_type(create_path(&[PathSegment::from(format_ident!( + "Bread" + ))])), + create_type(create_path(&[PathSegment::from(format_ident!( + "Cheese" + ))])), + create_type(create_path(&[PathSegment::from(format_ident!( + "Tomatoes" + ))])) + ]), + r_arrow_token: RArrow::default(), + output: create_type(create_path(&[PathSegment::from(format_ident!( + "Taco" + ))])), + trait_bounds: Punctuated::new() + } + .into_token_stream() + .to_string(), + "dyn Fn (Bread , Cheese , Tomatoes) -> Taco".to_string() + ); + } +} diff --git a/macros/src/macro_flag.rs b/macros/src/macro_flag.rs index 257a059..97a8ff2 100644 --- a/macros/src/macro_flag.rs +++ b/macros/src/macro_flag.rs @@ -1,7 +1,7 @@ use syn::parse::{Parse, ParseStream}; use syn::{Ident, LitBool, Token}; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct MacroFlag { pub flag: Ident, @@ -25,3 +25,46 @@ impl Parse for MacroFlag Ok(Self { flag, is_on }) } } + +#[cfg(test)] +mod tests +{ + use std::error::Error; + + use proc_macro2::Span; + use quote::{format_ident, quote}; + use syn::parse2; + + use super::*; + + #[test] + fn can_parse_macro_flag() -> Result<(), Box> + { + assert_eq!( + parse2::(quote! { + more = true + })?, + MacroFlag { + flag: format_ident!("more"), + is_on: LitBool::new(true, Span::call_site()) + } + ); + + assert_eq!( + parse2::(quote! { + do_something = false + })?, + MacroFlag { + flag: format_ident!("do_something"), + is_on: LitBool::new(false, Span::call_site()) + } + ); + + assert!(parse2::(quote! { + 10 = false + }) + .is_err()); + + Ok(()) + } +} diff --git a/macros/src/util/iterator_ext.rs b/macros/src/util/iterator_ext.rs index 86db6cb..5001068 100644 --- a/macros/src/util/iterator_ext.rs +++ b/macros/src/util/iterator_ext.rs @@ -9,7 +9,7 @@ pub trait IteratorExt impl IteratorExt for Iter where Iter: Iterator, - Iter::Item: Eq + Hash + Copy, + Iter::Item: Eq + Hash + Clone, { fn find_duplicate(&mut self) -> Option { @@ -26,3 +26,58 @@ where None } } + +#[cfg(test)] +mod tests +{ + use super::*; + + #[test] + fn can_find_duplicate() + { + #[derive(Debug, PartialEq, Eq, Clone, Hash)] + struct Fruit + { + name: String, + } + + assert_eq!( + vec![ + Fruit { + name: "Apple".to_string(), + }, + Fruit { + name: "Banana".to_string(), + }, + Fruit { + name: "Apple".to_string(), + }, + Fruit { + name: "Orange".to_string(), + }, + ] + .iter() + .find_duplicate(), + Some(&Fruit { + name: "Apple".to_string() + }) + ); + + assert_eq!( + vec![ + Fruit { + name: "Banana".to_string(), + }, + Fruit { + name: "Apple".to_string(), + }, + Fruit { + name: "Orange".to_string(), + }, + ] + .iter() + .find_duplicate(), + None + ); + } +} diff --git a/macros/src/util/string.rs b/macros/src/util/string.rs index 90cccee..a04a021 100644 --- a/macros/src/util/string.rs +++ b/macros/src/util/string.rs @@ -10,3 +10,17 @@ pub fn camelcase_to_snakecase(camelcased: &str) -> String .to_string() .to_lowercase() } + +#[cfg(test)] +mod tests +{ + use super::*; + + #[test] + fn camelcase_to_snakecase_works() + { + assert_eq!(camelcase_to_snakecase("LoginHandler"), "login_handler"); + + assert_eq!(camelcase_to_snakecase("Regex"), "regex"); + } +} diff --git a/src/castable_factory/blocking.rs b/src/castable_factory/blocking.rs index 5ff4db0..5dc12e5 100644 --- a/src/castable_factory/blocking.rs +++ b/src/castable_factory/blocking.rs @@ -72,3 +72,26 @@ where ReturnInterface: 'static + ?Sized, { } + +#[cfg(test)] +mod tests +{ + use super::*; + + #[test] + fn can_call() + { + #[derive(Debug, PartialEq, Eq)] + struct Bacon + { + heal_amount: u32, + } + + let castable_factory = + CastableFactory::new(&|heal_amount| TransientPtr::new(Bacon { heal_amount })); + + let output = castable_factory(27); + + assert_eq!(output, Box::new(Bacon { heal_amount: 27 })); + } +} diff --git a/src/castable_factory/threadsafe.rs b/src/castable_factory/threadsafe.rs index 08c5ecf..84e15b9 100644 --- a/src/castable_factory/threadsafe.rs +++ b/src/castable_factory/threadsafe.rs @@ -85,3 +85,27 @@ where ReturnInterface: 'static + ?Sized, { } + +#[cfg(test)] +mod tests +{ + use super::*; + + #[test] + fn can_call() + { + #[derive(Debug, PartialEq, Eq)] + struct Bacon + { + heal_amount: u32, + } + + let castable_factory = ThreadsafeCastableFactory::new(&|heal_amount| { + TransientPtr::new(Bacon { heal_amount }) + }); + + let output = castable_factory(27); + + assert_eq!(output, Box::new(Bacon { heal_amount: 27 })); + } +} diff --git a/src/di_container/binding_map.rs b/src/di_container/binding_map.rs index eb71ff7..6ecd34c 100644 --- a/src/di_container/binding_map.rs +++ b/src/di_container/binding_map.rs @@ -89,3 +89,204 @@ where self.bindings.len() } } + +#[cfg(test)] +mod tests +{ + use super::*; + + mod subjects + { + pub trait SomeProvider + { + fn get_id(&self) -> u8; + } + + pub struct SomeProviderImpl + { + pub id: u8, + } + + impl SomeProvider for SomeProviderImpl + { + fn get_id(&self) -> u8 + { + self.id + } + } + } + + #[test] + fn can_get() + { + type Interface = (); + + let mut binding_map = DIContainerBindingMap::::new(); + + binding_map.bindings.insert( + DIContainerBindingKey { + type_id: TypeId::of::(), + name: None, + }, + Box::new(subjects::SomeProviderImpl { id: 20 }), + ); + + assert!(binding_map + .get::(None) + .map_or_else(|| false, |provider| provider.get_id() == 20)); + } + + #[test] + fn can_get_with_name() + { + type Interface = (); + + let mut binding_map = DIContainerBindingMap::::new(); + + binding_map.bindings.insert( + DIContainerBindingKey { + type_id: TypeId::of::(), + name: Some("hello"), + }, + Box::new(subjects::SomeProviderImpl { id: 11 }), + ); + + assert!(binding_map + .get::(Some("hello")) + .map_or_else(|| false, |provider| provider.get_id() == 11)); + + assert!(binding_map.get::(None).is_none()); + } + + #[test] + fn can_set() + { + type Interface = (); + + let mut binding_map = DIContainerBindingMap::::new(); + + binding_map + .set::(None, Box::new(subjects::SomeProviderImpl { id: 65 })); + + let expected_key = &DIContainerBindingKey { + type_id: TypeId::of::(), + name: None, + }; + + assert!(binding_map.bindings.contains_key(expected_key)); + + assert_eq!(binding_map.bindings[expected_key].get_id(), 65); + } + + #[test] + fn can_set_with_name() + { + type Interface = (); + + let mut binding_map = DIContainerBindingMap::::new(); + + binding_map.set::( + Some("special"), + Box::new(subjects::SomeProviderImpl { id: 3 }), + ); + + let expected_key = &DIContainerBindingKey { + type_id: TypeId::of::(), + name: Some("special"), + }; + + assert!(binding_map.bindings.contains_key(expected_key)); + + assert_eq!(binding_map.bindings[expected_key].get_id(), 3); + } + + #[test] + fn can_remove() + { + type Interface = (); + + let mut binding_map = DIContainerBindingMap::::new(); + + binding_map.bindings.insert( + DIContainerBindingKey { + type_id: TypeId::of::(), + name: None, + }, + Box::new(subjects::SomeProviderImpl { id: 103 }), + ); + + binding_map.remove::(None); + + let expected_key = &DIContainerBindingKey { + type_id: TypeId::of::(), + name: None, + }; + + assert!(!binding_map.bindings.contains_key(expected_key)); + } + + #[test] + fn can_remove_with_name() + { + type Interface = (); + + let mut binding_map = DIContainerBindingMap::::new(); + + binding_map.bindings.insert( + DIContainerBindingKey { + type_id: TypeId::of::(), + name: Some("cool"), + }, + Box::new(subjects::SomeProviderImpl { id: 42 }), + ); + + binding_map.remove::(Some("cool")); + + let expected_key = &DIContainerBindingKey { + type_id: TypeId::of::(), + name: Some("cool"), + }; + + assert!(!binding_map.bindings.contains_key(expected_key)); + } + + #[test] + fn can_get_has() + { + type Interface = (); + + let mut binding_map = DIContainerBindingMap::::new(); + + assert!(!binding_map.has::(None)); + + binding_map.bindings.insert( + DIContainerBindingKey { + type_id: TypeId::of::(), + name: None, + }, + Box::new(subjects::SomeProviderImpl { id: 103 }), + ); + + assert!(binding_map.has::(None)); + } + + #[test] + fn can_get_has_with_name() + { + type Interface = (); + + let mut binding_map = DIContainerBindingMap::::new(); + + assert!(!binding_map.has::(Some("awesome"))); + + binding_map.bindings.insert( + DIContainerBindingKey { + type_id: TypeId::of::(), + name: Some("awesome"), + }, + Box::new(subjects::SomeProviderImpl { id: 101 }), + ); + + assert!(binding_map.has::(Some("awesome"))); + } +} diff --git a/src/lib.rs b/src/lib.rs index d01ecc2..2cc3cc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #![cfg_attr(feature = "factory", feature(unboxed_closures, fn_traits))] #![cfg_attr(doc_cfg, feature(doc_cfg))] +#![cfg_attr(test, feature(register_tool))] +#![cfg_attr(test, register_tool(tarpaulin))] #![deny(clippy::all)] #![deny(clippy::pedantic)] #![allow(clippy::module_name_repetitions)] @@ -41,6 +43,7 @@ pub mod libs; mod provider; #[cfg(test)] +#[tarpaulin::skip] mod test_utils; /// Shortcut for creating a DI container binding for a injectable without a declared -- cgit v1.2.3-18-g5258