Skip to content

Commit

Permalink
feat: fragile output references
Browse files Browse the repository at this point in the history
fixes #51
  • Loading branch information
audunhalland committed Apr 1, 2024
1 parent 4d8daed commit 10ebee5
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 26 deletions.
14 changes: 10 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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 }

Expand All @@ -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"]
25 changes: 23 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -866,16 +866,37 @@ impl Unimock {
///
/// This can be useful when returning references from `answers` functions.
pub fn make_ref<T: Send + Sync + 'static>(&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<T: Send + Sync + 'static>(&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<T: 'static>(&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<T: 'static>(&mut self, value: T) -> &mut T {
self.value_chain.push_fragile_mut(value)
}
}

impl Unimock {
#[track_caller]
fn from_assembler(
assembler_result: Result<MockAssembler, alloc::String>,
Expand Down
80 changes: 64 additions & 16 deletions src/value_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,47 @@ pub struct ValueChain {
}

impl ValueChain {
pub fn add<T: Any + Send + Sync>(&self, value: T) -> &T {
pub fn push<T: Any + Send + Sync>(&self, value: T) -> &T {
self.push_value(Value::Send(Box::new(value)))
.downcast_ref::<T>()
.unwrap()
}

pub fn push_mut<T: Any + Send + Sync>(&mut self, value: T) -> &mut T {
self.push_value_mut(Value::Send(Box::new(value)))
.downcast_mut::<T>()
.unwrap()
}
}

#[cfg(feature = "fragile")]
impl ValueChain {
pub fn push_fragile<T: Any>(&self, value: T) -> &T {
self.push_value(Value::Fragile(fragile::Fragile::new(Box::new(value))))
.downcast_ref::<T>()
.unwrap()
}

pub fn push_fragile_mut<T: Any>(&mut self, value: T) -> &mut T {
self.push_value_mut(Value::Fragile(fragile::Fragile::new(Box::new(value))))
.downcast_mut::<T>()
.unwrap()
}
}

impl ValueChain {
fn push_value(&self, value: Value) -> &Value {
let node = self.push_node(Node::new(value));

node.value.as_ref().downcast_ref::<T>().unwrap()
&node.value
}

pub fn add_mut<T: Any + Send + Sync>(&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::<T>()
.unwrap()
&mut self.root.get_mut().unwrap().value
}

fn push_node(&self, mut new_node: Node) -> &Node {
Expand Down Expand Up @@ -62,25 +86,49 @@ impl Drop for ValueChain {
}

struct Node {
value: Box<dyn Any + Send + Sync>,
value: Value,
next: Box<OnceCell<Node>>,
}

impl Node {
pub fn new<T: Any + Send + Sync>(value: T) -> Self {
fn new(value: Value) -> Self {
Self {
value: Box::new(value),
value,
next: Default::default(),
}
}
}

enum Value {
Send(Box<dyn Any + Send + Sync>),
#[cfg(feature = "fragile")]
Fragile(fragile::Fragile<Box<dyn Any>>),
}

impl Value {
fn downcast_ref<T: 'static>(&self) -> Option<&T> {
match self {
Self::Send(any) => any.downcast_ref::<T>(),
#[cfg(feature = "fragile")]
Self::Fragile(fragile) => fragile.get().downcast_ref::<T>(),
}
}

fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
match self {
Self::Send(any) => any.downcast_mut::<T>(),
#[cfg(feature = "fragile")]
Self::Fragile(fragile) => fragile.get_mut().downcast_mut::<T>(),
}
}
}

#[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);
Expand All @@ -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;
}
10 changes: 8 additions & 2 deletions test_all_stable.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand Down
64 changes: 64 additions & 0 deletions tests/it/test_fragile.rs
Original file line number Diff line number Diff line change
@@ -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<i32>;
}

#[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<i32>;
fn get_rc_mut(&mut self) -> &mut Rc<i32>;
}

#[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());
}
}

0 comments on commit 10ebee5

Please sign in to comment.