Skip to content

Commit 95f91d0

Browse files
authored
Better support for mocking methods like std::io::Read::read (#651)
std::io::Read::read is often called with an uninitialized buffer as its argument. So Mockall shouldn't try to read from that buffer, even though the language rules allow it to. Previously, Mockall would try to read from it, in order to format a panic message in case no matching Expectation was found. Now, Mockall will not try to format such a panic message preemptively. It will only do it if there actually isn't any matching Expectation. That also provides a small performance boost to all users. Also, do a better job of clearing poison from static methods' Mutexes following a panic. Previously we would only do that if the user had created a Context object. Now we will do it regardless. Fixes #647
1 parent 204456a commit 95f91d0

File tree

5 files changed

+198
-72
lines changed

5 files changed

+198
-72
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1212

1313
### Fixed
1414

15-
- No longer poison a Context object's internal `Mutex` when panicing, even when
16-
using a stable Rust compiler.
15+
- No longer poison a static mock method's internal `Mutex` when panicing, even when
16+
using a stable Rust compiler. Also, no longer poison it even if there is no
17+
`Context` object. For example, if the user never set an Expectation at all.
1718
([#650](https://github.com/asomers/mockall/pull/650))
1819

1920
- Suppress `single-use-lifetimes` lints in the generated code, for cases where

mockall/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,7 +1691,7 @@ impl SeqHandle {
16911691
}
16921692

16931693
/// Verify that this handle was called in the correct order
1694-
pub fn verify(&self, desc: &str) {
1694+
pub fn verify<F: Fn() -> String>(&self, desc: F) {
16951695
self.inner.verify(self.seq, desc);
16961696
}
16971697
}
@@ -1709,9 +1709,9 @@ impl SeqInner {
17091709
}
17101710

17111711
/// Verify that the call identified by `seq` was called in the correct order
1712-
fn verify(&self, seq: usize, desc: &str) {
1712+
fn verify<F: Fn() -> String>(&self, seq: usize, desc: F) {
17131713
assert_eq!(seq, self.satisfaction_level.load(Ordering::Relaxed),
1714-
"{desc}: Method sequence violation")
1714+
"{}: Method sequence violation", &desc())
17151715
}
17161716
}
17171717

mockall/tests/clear_expectations_on_panic.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! https://github.com/asomers/mockall/issues/442
55
#![deny(warnings)]
66

7-
use std::panic;
7+
use std::{panic, sync::Mutex};
88

99
use mockall::*;
1010

@@ -14,8 +14,12 @@ pub trait Foo {
1414
fn bar() -> i32;
1515
}
1616

17+
static FOO_MTX: Mutex<()> = Mutex::new(());
18+
1719
#[test]
1820
fn too_few_calls() {
21+
let _m = FOO_MTX.lock().unwrap();
22+
1923
panic::catch_unwind(|| {
2024
let ctx = MockFoo::foo_context();
2125
ctx.expect()
@@ -34,17 +38,29 @@ fn too_few_calls() {
3438

3539
// We shouldn't panic during drop in this case. Regression test for
3640
// https://github.com/asomers/mockall/issues/491
37-
#[cfg_attr(not(feature = "nightly"), ignore)]
3841
#[test]
3942
fn too_many_calls() {
43+
let _m = FOO_MTX.lock().unwrap();
44+
4045
panic::catch_unwind(|| {
4146
let ctx = MockFoo::bar_context();
4247
ctx.expect()
4348
.times(0);
4449
MockFoo::bar();
4550
}).unwrap_err();
4651

47-
// This line will panic with a PoisonError, at least until issue #515 is
48-
// complete.
52+
let _ctx = MockFoo::bar_context();
53+
}
54+
55+
// If we panic due to not setting any expectations at all, the mock method's
56+
// mutex shouldn't be poisoned.
57+
#[test]
58+
fn no_expectations_at_all() {
59+
let _m = FOO_MTX.lock().unwrap();
60+
61+
panic::catch_unwind(|| {
62+
MockFoo::bar();
63+
}).unwrap_err();
64+
4965
let _ctx = MockFoo::bar_context();
5066
}

mockall/tests/mock_read.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// vim: tw=80
2+
//! Mock a trait like std::io::Read. Mockall should not try to read the read
3+
//! method's arguments if the test succeeds.
4+
//! https://github.com/asomers/mockall/issues/647
5+
#![deny(warnings)]
6+
7+
use std::{
8+
fmt::{self, Debug, Formatter},
9+
io
10+
};
11+
12+
use mockall::*;
13+
14+
pub struct MyBuf<'a>(&'a mut [u8]);
15+
impl<'a> Debug for MyBuf<'a> {
16+
fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
17+
// An implementor of std::io::Read::read should not attempt to read from
18+
// the provided buffer, because it might be uninitialized. Here, we
19+
// implement a panic in Debug::fmt, just to ensure that Mockall won't
20+
// try to format the mock method's arguments.
21+
panic!("Shouldn't attempt to read from me!");
22+
}
23+
}
24+
25+
// Similarly, this wrapper type ensures that Mockall won't try to format such an
26+
// object.
27+
#[derive(Clone, Copy, Eq, PartialEq)]
28+
pub struct NonDebuggable<T: 'static>(T);
29+
impl<T: 'static> Debug for NonDebuggable<T> {
30+
fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
31+
panic!("Shouldn't attempt to debug me!");
32+
}
33+
}
34+
35+
trait Readlike {
36+
fn read<'a>(&mut self, buf: MyBuf<'a>) -> io::Result<usize>;
37+
}
38+
39+
mock! {
40+
pub Foo {
41+
/* Also test some methods with different Expectation types */
42+
fn foo(&mut self, x: NonDebuggable<i32>) -> &usize;
43+
fn bar(&mut self, x: NonDebuggable<i32>) -> &mut usize;
44+
fn baz<T: 'static>(&mut self, x: NonDebuggable<T>) -> usize;
45+
}
46+
/* Just like std::io::Read, but with the MyBuf wrapper */
47+
impl Readlike for Foo {
48+
fn read<'a>(&mut self, buf: MyBuf<'a>) -> io::Result<usize>;
49+
}
50+
}
51+
52+
const HELLO: &[u8] = b"Hello, World!\0";
53+
54+
#[test]
55+
fn like_read() {
56+
let mut mock = MockFoo::new();
57+
let mut seq = Sequence::new();
58+
mock.expect_read()
59+
.times(1)
60+
.in_sequence(&mut seq)
61+
.returning(|buf| {
62+
buf.0[0..HELLO.len()].copy_from_slice(HELLO);
63+
Ok(HELLO.len())
64+
});
65+
66+
let mut inner_buf = [0; 80];
67+
let my_buf = MyBuf(&mut inner_buf);
68+
mock.read(my_buf).unwrap();
69+
assert_eq!(&inner_buf[0..HELLO.len()], HELLO);
70+
}
71+
72+
#[test]
73+
fn return_ref() {
74+
let mut mock = MockFoo::new();
75+
let mut seq = Sequence::new();
76+
mock.expect_foo()
77+
.times(1)
78+
.in_sequence(&mut seq)
79+
.return_const(42usize);
80+
81+
assert_eq!(*mock.foo(NonDebuggable(5i32)), 42);
82+
}
83+
84+
#[test]
85+
fn return_refmut() {
86+
let mut mock = MockFoo::new();
87+
let mut seq = Sequence::new();
88+
mock.expect_bar()
89+
.times(1)
90+
.in_sequence(&mut seq)
91+
.return_var(42usize);
92+
93+
assert_eq!(*mock.bar(NonDebuggable(5i32)), 42);
94+
}
95+
96+
#[test]
97+
fn generic() {
98+
let mut mock = MockFoo::new();
99+
let mut seq = Sequence::new();
100+
mock.expect_baz::<u32>()
101+
.times(1)
102+
.in_sequence(&mut seq)
103+
.return_const(42usize);
104+
105+
assert_eq!(mock.baz(NonDebuggable(5u32)), 42);
106+
}

0 commit comments

Comments
 (0)