test_util/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This crate defines a collection of useful utilities for testing rust code.
6
7use std::sync::Mutex;
8
9/// Asserts that the first argument is strictly less than the second.
10#[macro_export]
11macro_rules! assert_lt {
12    ($x:expr, $y:expr) => {
13        assert!(
14            $x < $y,
15            "assertion `{} < {}` failed; actual: {:?} is not less than {:?}",
16            stringify!($x),
17            stringify!($y),
18            $x,
19            $y
20        );
21    };
22}
23
24/// Asserts that the first argument is less than or equal to the second.
25#[macro_export]
26macro_rules! assert_leq {
27    ($x:expr, $y:expr) => {
28        assert!(
29            $x <= $y,
30            "assertion `{} <= {}` failed; actual: {:?} is not less than or equal to {:?}",
31            stringify!($x),
32            stringify!($y),
33            $x,
34            $y
35        );
36    };
37}
38
39/// Asserts that the first argument is strictly greater than the second.
40#[macro_export]
41macro_rules! assert_gt {
42    ($x:expr, $y:expr) => {
43        assert!(
44            $x > $y,
45            "assertion `{} > {}` failed; actual: {:?} is not greater than {:?}",
46            stringify!($x),
47            stringify!($y),
48            $x,
49            $y
50        );
51    };
52}
53
54/// Asserts that the first argument is greater than or equal to the second.
55#[macro_export]
56macro_rules! assert_geq {
57    ($x:expr, $y:expr) => {
58        assert!(
59            $x >= $y,
60            "assertion `{} >= {}` failed; actual: {:?} is not greater than or equal to {:?}",
61            stringify!($x),
62            stringify!($y),
63            $x,
64            $y
65        );
66    };
67}
68
69/// Asserts that `x` and `y` are within `delta` of one another.
70///
71/// `x` and `y` must be of a common type that supports both subtraction and negation. (Note that it
72/// would be natural to define this macro using `abs()`, but when attempting to do so, the compiler
73/// fails to apply its inference rule for under-constrained types. See
74/// [https://github.com/rust-lang/reference/issues/104].)
75#[macro_export]
76macro_rules! assert_near {
77    ($x: expr, $y: expr, $delta: expr) => {
78        let difference = $x - $y;
79        assert!(
80            (-$delta..=$delta).contains(&difference),
81            "assertion `{} is near {} (within delta {})` failed; actual: |{:?} - {:?}| > {:?}",
82            stringify!($x),
83            stringify!($y),
84            stringify!($delta),
85            $x,
86            $y,
87            $delta
88        );
89    };
90}
91
92/// A mutually exclusive counter that is not shareable, but can be defined statically for the
93/// duration of a test. This simplifies the implementation of a simple test-global counter,
94/// avoiding the complexity of alternatives like std::sync::atomic objects that are typically
95/// wrapped in Arc()s, cloned, and require non-intuitive memory management options.
96///
97/// # Example
98///
99/// ```
100///    use test_util::Counter;
101///    use std::sync::LazyLock;
102///
103///    #[test]
104///    async fn my_test() {
105///        static CALL_COUNT: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
106///
107///        let handler = || {
108///            // some async callback
109///            // ...
110///            CALL_COUNT.inc();
111///        };
112///        handler();
113///        // ...
114///        CALL_COUNT.inc();
115///
116///        assert_eq!(CALL_COUNT.get(), 2);
117///    }
118/// ```
119///
120/// *Important:* Since inc() and get() obtain separate Mutex lock()s to access the underlying
121/// counter value, it is very possible that a separate thread, if also mutating the same counter,
122/// may increment the value between the first thread's calls to inc() and get(), in which case,
123/// the two threads could get() the same value (the result after both calls to inc()). If get()
124/// is used to, for example, print the values after each call to inc(), the resulting values might
125/// include duplicate intermediate counter values, with some numbers skipped, but the final value
126/// after all threads complete will be the exact number of all calls to inc() (offset by the
127/// initial value).
128///
129/// To provide slightly more consistent results, inc() returns the new value after incrementing
130/// the counter, obtaining the value while the lock is held. This way, each incremental value will
131/// be returned to the calling threads; *however* the threads could still receive the values out of
132/// order.
133///
134/// Consider, thread 1 calls inc() starting at count 0. A value of 1 is returned, but before thread
135/// 1 receives the new value, it might be interrupted by thread 2. Thread 2 increments the counter
136/// from 1 to 2, return the new value 2, and (let's say, for example) prints the value "2". Thread 1
137/// then resumes, and prints "1".
138///
139/// Specifically, the Counter guarantees that each invocation of inc() will return a value that is
140/// 1 greater than the previous value returned by inc() (or 1 greater than the `initial` value, if
141/// it is the first invocation). Call get() after completing all invocations of inc() to get the
142/// total number of times inc() was called (offset by the initial value).
143pub struct Counter {
144    count: Mutex<usize>,
145}
146
147impl Counter {
148    /// Initializes a new counter to the given value.
149    pub fn new(initial: usize) -> Self {
150        Counter { count: Mutex::new(initial) }
151    }
152
153    /// Increments the counter by one and returns the new value.
154    pub fn inc(&self) -> usize {
155        let mut count = self.count.lock().unwrap();
156        *count += 1;
157        *count
158    }
159
160    /// Returns the current value of the counter.
161    pub fn get(&self) -> usize {
162        *self.count.lock().unwrap()
163    }
164}
165
166impl std::fmt::Debug for Counter {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        f.debug_struct("Counter").field("count", &self.get()).finish()
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use std::collections::BTreeSet;
176    use std::sync::mpsc::{Receiver, Sender};
177    use std::sync::{LazyLock, mpsc};
178    use std::thread;
179
180    #[derive(Debug, PartialEq, PartialOrd)]
181    struct NotDisplay {
182        x: i32,
183    }
184
185    impl core::ops::Sub for NotDisplay {
186        type Output = Self;
187
188        fn sub(self, other: Self) -> Self {
189            NotDisplay { x: self.x - other.x }
190        }
191    }
192
193    impl core::ops::Neg for NotDisplay {
194        type Output = Self;
195
196        fn neg(self) -> Self {
197            NotDisplay { x: -self.x }
198        }
199    }
200
201    #[test]
202    fn test_assert_lt_passes() {
203        assert_lt!(1, 2);
204        assert_lt!(1u8, 2u8);
205        assert_lt!(1u16, 2u16);
206        assert_lt!(1u32, 2u32);
207        assert_lt!(1u64, 2u64);
208        assert_lt!(-1, 3);
209        assert_lt!(-1i8, 3i8);
210        assert_lt!(-1i16, 3i16);
211        assert_lt!(-1i32, 3i32);
212        assert_lt!(-1i64, 3i64);
213        assert_lt!(-2.0, 7.0);
214        assert_lt!(-2.0f32, 7.0f32);
215        assert_lt!(-2.0f64, 7.0f64);
216        assert_lt!('a', 'b');
217        assert_lt!(NotDisplay { x: 1 }, NotDisplay { x: 2 });
218    }
219
220    #[test]
221    #[should_panic(expected = "assertion `a < b` failed; actual: 2 is not less than 2")]
222    fn test_assert_lt_fails_a_equals_b() {
223        let a = 2;
224        let b = 2;
225        assert_lt!(a, b);
226    }
227
228    #[test]
229    #[should_panic(expected = "assertion `a < b` failed; actual: 5 is not less than 2")]
230    fn test_assert_lt_fails_a_greater_than_b() {
231        let a = 5;
232        let b = 2;
233        assert_lt!(a, b);
234    }
235
236    #[test]
237    fn test_assert_leq_passes() {
238        assert_leq!(1, 2);
239        assert_leq!(2, 2);
240        assert_leq!(-2.0, 3.0);
241        assert_leq!(3.0, 3.0);
242        assert_leq!('a', 'b');
243        assert_leq!('b', 'b');
244        assert_leq!(NotDisplay { x: 1 }, NotDisplay { x: 2 });
245        assert_leq!(NotDisplay { x: 2 }, NotDisplay { x: 2 });
246    }
247
248    #[test]
249    #[should_panic(
250        expected = "assertion `a <= b` failed; actual: 3 is not less than or equal to 2"
251    )]
252    fn test_assert_leq_fails() {
253        let a = 3;
254        let b = 2;
255        assert_leq!(a, b);
256    }
257
258    #[test]
259    fn test_assert_gt_passes() {
260        assert_gt!(2, 1);
261        assert_gt!(2u8, 1u8);
262        assert_gt!(2u16, 1u16);
263        assert_gt!(2u32, 1u32);
264        assert_gt!(2u64, 1u64);
265        assert_gt!(3, -1);
266        assert_gt!(3i8, -1i8);
267        assert_gt!(3i16, -1i16);
268        assert_gt!(3i32, -1i32);
269        assert_gt!(3i64, -1i64);
270        assert_gt!(7.0, -2.0);
271        assert_gt!(7.0f32, -2.0f32);
272        assert_gt!(7.0f64, -2.0f64);
273        assert_gt!('b', 'a');
274        assert_gt!(NotDisplay { x: 2 }, NotDisplay { x: 1 });
275    }
276
277    #[test]
278    #[should_panic(expected = "assertion `a > b` failed; actual: 2 is not greater than 2")]
279    fn test_assert_gt_fails_a_equals_b() {
280        let a = 2;
281        let b = 2;
282        assert_gt!(a, b);
283    }
284
285    #[test]
286    #[should_panic(expected = "assertion `a > b` failed; actual: -1 is not greater than 2")]
287    fn test_assert_gt_fails_a_less_than_b() {
288        let a = -1;
289        let b = 2;
290        assert_gt!(a, b);
291    }
292
293    #[test]
294    fn test_assert_geq_passes() {
295        assert_geq!(2, 1);
296        assert_geq!(2, 2);
297        assert_geq!(3.0, -2.0);
298        assert_geq!(3.0, 3.0);
299        assert_geq!('b', 'a');
300        assert_geq!('b', 'b');
301        assert_geq!(NotDisplay { x: 2 }, NotDisplay { x: 1 });
302        assert_geq!(NotDisplay { x: 2 }, NotDisplay { x: 2 });
303    }
304
305    #[test]
306    #[should_panic(
307        expected = "assertion `a >= b` failed; actual: 2 is not greater than or equal to 3"
308    )]
309    fn test_assert_geq_fails() {
310        let a = 2;
311        let b = 3;
312        assert_geq!(a, b);
313    }
314
315    #[test]
316    fn test_assert_near_passes() {
317        // Test both possible orderings and equality with literals.
318        assert_near!(1.0001, 1.0, 0.01);
319        assert_near!(1.0, 1.0001, 0.01);
320        assert_near!(1.0, 1.0, 0.0);
321
322        // Ensure the macro operates on all other expected input types.
323        assert_near!(1.0001f32, 1.0f32, 0.01f32);
324        assert_near!(1.0001f64, 1.0f64, 0.01f64);
325        assert_near!(7, 5, 2);
326        assert_near!(7i8, 5i8, 2i8);
327        assert_near!(7i16, 5i16, 2i16);
328        assert_near!(7i32, 5i32, 2i32);
329        assert_near!(7i64, 5i64, 2i64);
330
331        assert_near!(NotDisplay { x: 7 }, NotDisplay { x: 5 }, NotDisplay { x: 2 });
332    }
333
334    #[test]
335    #[should_panic]
336    fn test_assert_near_fails() {
337        assert_near!(1.00001, 1.0, 1e-8);
338    }
339
340    // Test error message with integers so display is predictable.
341    #[test]
342    #[should_panic(
343        expected = "assertion `a is near b (within delta d)` failed; actual: |3 - 5| > 1"
344    )]
345    fn test_assert_near_fails_with_message() {
346        let a = 3;
347        let b = 5;
348        let d = 1;
349        assert_near!(a, b, d);
350    }
351
352    #[test]
353    fn test_inc() {
354        static CALL_COUNT: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
355
356        CALL_COUNT.inc();
357
358        assert_eq!(CALL_COUNT.get(), 1);
359    }
360
361    #[test]
362    fn test_incs_from_10() {
363        static CALL_COUNT: LazyLock<Counter> = LazyLock::new(|| Counter::new(10));
364
365        CALL_COUNT.inc();
366        CALL_COUNT.inc();
367        CALL_COUNT.inc();
368
369        assert_eq!(CALL_COUNT.get(), 13);
370    }
371
372    #[test]
373    fn async_counts() {
374        static CALL_COUNT: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
375
376        let (tx, rx): (Sender<usize>, Receiver<usize>) = mpsc::channel();
377        let mut children = Vec::new();
378
379        static NTHREADS: usize = 10;
380
381        for _ in 0..NTHREADS {
382            let thread_tx = tx.clone();
383            let child = thread::spawn(move || {
384                let new_value = CALL_COUNT.inc();
385                thread_tx.send(new_value).unwrap();
386                println!("Sent: {} (OK if out of order)", new_value);
387            });
388
389            children.push(child);
390        }
391
392        let mut ordered_ids: BTreeSet<usize> = BTreeSet::new();
393        for _ in 0..NTHREADS {
394            let received_id = rx.recv().unwrap();
395            println!("Received: {} (OK if in yet a different order)", received_id);
396            ordered_ids.insert(received_id);
397        }
398
399        // Wait for the threads to complete any remaining work
400        for child in children {
401            child.join().expect("child thread panicked");
402        }
403
404        // All threads should have incremented the count by 1 each.
405        assert_eq!(CALL_COUNT.get(), NTHREADS);
406
407        // All contiguous incremental values should have been received, though possibly not in
408        // order. The BTreeSet will return them in order so the complete set can be verified.
409        let mut expected_id: usize = 1;
410        for id in ordered_ids.iter() {
411            assert_eq!(*id, expected_id);
412            expected_id += 1;
413        }
414    }
415
416    #[test]
417    fn debug_format_counter() {
418        let counter = Counter::new(0);
419        assert_eq!(format!("{:?}", counter), "Counter { count: 0 }");
420    }
421}