diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8cfafd4..05996f6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,10 +17,16 @@ jobs: - uses: taiki-e/install-action@cargo-hack - name: Test macros run: cd unimock_macros && cargo test - - name: Test std - run: cargo hack --feature-powerset --features std --exclude-features default,spin-lock,nightly-tests,unstable-doc-cfg test - - name: Test no_std - run: cargo hack --feature-powerset --features critical-section --exclude-features std,mock-std,default,nightly-tests,unstable-doc-cfg test + - name: Test + run: > + cargo hack --feature-powerset + --exclude-no-default-features + --at-least-one-of std,critical-section + --mutually-exclusive-features std,critical-section + --mutually-exclusive-features std,spin-lock + --group-features mock-std,mock-tokio-1,mock-futures-io-0-3 + --exclude-features nightly-tests,unstable-doc-cfg + test - name: Doctest run: cargo test --doc --features mock-core,mock-std - name: Clippy diff --git a/CHANGELOG.md b/CHANGELOG.md index a3defed..b8f7653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Added +- Support for returning references to non-Send types. ## [0.6.3] - 2024-03-28 ### Added diff --git a/Cargo.toml b/Cargo.toml index 464b115..5320ac4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,9 @@ categories = ["development-tools", "development-tools::testing", "no-std"] [features] default = ["std", "pretty-print"] -pretty-print = ["dep:pretty_assertions"] std = ["once_cell/std"] +pretty-print = ["dep:pretty_assertions"] +fragile = ["std", "dep:fragile"] spin-lock = ["dep:spin"] mock-core = [] mock-std = ["std", "mock-core"] @@ -31,6 +32,7 @@ once_cell = { version = "1.17", default-features = false } polonius-the-crab = "0.4" pretty_assertions = { version = "1.3", optional = true } spin = { version = "0.9.8", optional = true } +fragile = { version = "2.0.0", optional = true } futures-io-0-3 = { package = "futures-io", version = "0.3.30", optional = true } tokio-1 = { package = "tokio", version = "1.36", default-features = false, optional = true } @@ -45,7 +47,7 @@ rustversion = "1" doctest = false [package.metadata.docs.rs] -features = ["unstable-doc-cfg", "mock-core", "mock-std", "mock-futures-io-0-3", "mock-tokio-1"] +features = ["unstable-doc-cfg", "fragile", "mock-core", "mock-std", "mock-futures-io-0-3", "mock-tokio-1"] [workspace] members = ["unimock_macros"] diff --git a/src/lib.rs b/src/lib.rs index a54f57b..3975765 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -866,16 +866,37 @@ impl Unimock { /// /// This can be useful when returning references from `answers` functions. pub fn make_ref(&self, value: T) -> &T { - self.value_chain.add(value) + self.value_chain.push(value) } /// Convert the given value into a mutable reference. /// /// This can be useful when returning mutable references from `answers` functions. pub fn make_mut(&mut self, value: T) -> &mut T { - self.value_chain.add_mut(value) + self.value_chain.push_mut(value) + } +} + +#[cfg(feature = "fragile")] +impl Unimock { + /// Convert the given value into a reference. + /// + /// The value does not need to be [Send]. + /// Unimock will panic/abort if the instance is sent to another thread after calling this. + pub fn make_fragile_ref(&self, value: T) -> &T { + self.value_chain.push_fragile(value) } + /// Convert the given value into a mutable reference. + /// + /// The value does not need to be [Send]. + /// Unimock will panic/abort if the instance is sent to another thread after calling this. + pub fn make_fragile_mut(&mut self, value: T) -> &mut T { + self.value_chain.push_fragile_mut(value) + } +} + +impl Unimock { #[track_caller] fn from_assembler( assembler_result: Result, diff --git a/src/value_chain.rs b/src/value_chain.rs index 668d7c5..f0ab1b1 100644 --- a/src/value_chain.rs +++ b/src/value_chain.rs @@ -12,23 +12,47 @@ pub struct ValueChain { } impl ValueChain { - pub fn add(&self, value: T) -> &T { + pub fn push(&self, value: T) -> &T { + self.push_value(Value::Send(Box::new(value))) + .downcast_ref::() + .unwrap() + } + + pub fn push_mut(&mut self, value: T) -> &mut T { + self.push_value_mut(Value::Send(Box::new(value))) + .downcast_mut::() + .unwrap() + } +} + +#[cfg(feature = "fragile")] +impl ValueChain { + pub fn push_fragile(&self, value: T) -> &T { + self.push_value(Value::Fragile(fragile::Fragile::new(Box::new(value)))) + .downcast_ref::() + .unwrap() + } + + pub fn push_fragile_mut(&mut self, value: T) -> &mut T { + self.push_value_mut(Value::Fragile(fragile::Fragile::new(Box::new(value)))) + .downcast_mut::() + .unwrap() + } +} + +impl ValueChain { + fn push_value(&self, value: Value) -> &Value { let node = self.push_node(Node::new(value)); - node.value.as_ref().downcast_ref::().unwrap() + &node.value } - pub fn add_mut(&mut self, value: T) -> &mut T { + fn push_value_mut(&mut self, value: Value) -> &mut Value { // note: There is no need for keeping the old chain. // All those references are out of scope when add_mut is called. self.root = Node::new(value).into(); - self.root - .get_mut() - .unwrap() - .value - .downcast_mut::() - .unwrap() + &mut self.root.get_mut().unwrap().value } fn push_node(&self, mut new_node: Node) -> &Node { @@ -62,25 +86,49 @@ impl Drop for ValueChain { } struct Node { - value: Box, + value: Value, next: Box>, } impl Node { - pub fn new(value: T) -> Self { + fn new(value: Value) -> Self { Self { - value: Box::new(value), + value, next: Default::default(), } } } +enum Value { + Send(Box), + #[cfg(feature = "fragile")] + Fragile(fragile::Fragile>), +} + +impl Value { + fn downcast_ref(&self) -> Option<&T> { + match self { + Self::Send(any) => any.downcast_ref::(), + #[cfg(feature = "fragile")] + Self::Fragile(fragile) => fragile.get().downcast_ref::(), + } + } + + fn downcast_mut(&mut self) -> Option<&mut T> { + match self { + Self::Send(any) => any.downcast_mut::(), + #[cfg(feature = "fragile")] + Self::Fragile(fragile) => fragile.get_mut().downcast_mut::(), + } + } +} + #[test] fn it_works() { let value_chain = ValueChain::default(); - let first = value_chain.add(1); - let second = value_chain.add(""); - let third = value_chain.add(42.0); + let first = value_chain.push(1); + let second = value_chain.push(""); + let third = value_chain.push(42.0); assert_eq!(&1, first); assert_eq!(&"", second); @@ -90,6 +138,6 @@ fn it_works() { #[test] fn it_works_mut() { let mut value_chain = ValueChain::default(); - let first = value_chain.add_mut(1); + let first = value_chain.push_mut(1); *first += 1; } diff --git a/test_all_stable.sh b/test_all_stable.sh index 012b827..6aa1c20 100755 --- a/test_all_stable.sh +++ b/test_all_stable.sh @@ -3,6 +3,12 @@ set -e set -x (cd unimock_macros; cargo test) -cargo hack --feature-powerset --features std --exclude-features default,spin-lock,nightly-tests,unstable-doc-cfg test -cargo hack --feature-powerset --features critical-section --exclude-features std,mock-std,default,nightly-tests,unstable-doc-cfg test +cargo hack --feature-powerset \ + --exclude-no-default-features \ + --at-least-one-of std,critical-section \ + --mutually-exclusive-features std,critical-section \ + --mutually-exclusive-features std,spin-lock \ + --group-features mock-std,mock-tokio-1,mock-futures-io-0-3 \ + --exclude-features nightly-tests,unstable-doc-cfg \ + test cargo test --doc --features mock-core,mock-std diff --git a/tests/it/main.rs b/tests/it/main.rs index a2c1c12..ed97bde 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -38,6 +38,9 @@ mod std; #[cfg(all(feature = "mock-tokio-1", feature = "std"))] mod test_mock_tokio; +#[cfg(feature = "fragile")] +mod test_fragile; + mod unmock; fn main() {} diff --git a/tests/it/test_fragile.rs b/tests/it/test_fragile.rs new file mode 100644 index 0000000..cb6cedc --- /dev/null +++ b/tests/it/test_fragile.rs @@ -0,0 +1,64 @@ +use unimock::*; + +mod fragile_send_panic_msg { + use std::thread::{self, Thread}; + + use alloc::Rc; + + use super::*; + + #[unimock(api = TraitMock)] + trait Trait { + fn get_rc_ref(&self) -> &Rc; + } + + #[test] + #[should_panic = "destructor of fragile object ran on wrong thread"] + fn test_move() { + let u = Unimock::new( + TraitMock::get_rc_ref + .next_call(matching!()) + .answers(&|u| u.make_fragile_ref(Rc::new(666))), + ); + + thread::scope(|s| { + s.spawn(|| { + u.get_rc_ref(); + }); + }); + } +} + +mod answers_fragile { + use alloc::Rc; + + use super::*; + + #[unimock(api = TraitMock)] + trait Trait { + fn get_rc_ref(&self) -> &Rc; + fn get_rc_mut(&mut self) -> &mut Rc; + } + + #[test] + fn test_ref() { + let u = Unimock::new( + TraitMock::get_rc_ref + .next_call(matching!()) + .answers(&|u| u.make_fragile_ref(Rc::new(42))), + ); + + assert_eq!(&Rc::new(42), u.get_rc_ref()); + } + + #[test] + fn test_mut() { + let mut u = Unimock::new( + TraitMock::get_rc_mut + .next_call(matching!()) + .answers(&|u| u.make_fragile_mut(Rc::new(42))), + ); + + assert_eq!(&Rc::new(42), u.get_rc_mut()); + } +}