1#[cfg(not(shuttle))]
11pub mod sync {
12 pub use std::sync::atomic;
13 pub use std::sync::mpsc;
14 #[allow(unused_imports)]
15 pub use std::sync::{Arc, Barrier, Mutex, Weak};
16}
17
18#[cfg(not(shuttle))]
19pub mod thread {
20 #[allow(unused_imports)]
21 pub use std::thread::{JoinHandle, sleep, spawn};
22
23 pub fn spawn_named<F, T>(name: &str, f: F) -> JoinHandle<T>
26 where
27 F: FnOnce() -> T + Send + 'static,
28 T: Send + 'static,
29 {
30 std::thread::Builder::new()
31 .name(name.into())
32 .spawn(f)
33 .expect("failed to spawn thread")
34 }
35}
36
37#[cfg(not(shuttle))]
38#[macro_export]
39macro_rules! define_thread_local {
40 ($($tt:tt)*) => { std::thread_local! { $($tt)* } };
41}
42#[cfg(not(shuttle))]
43pub use crate::define_thread_local as thread_local;
44
45#[cfg(shuttle)]
48pub mod sync {
49 pub use shuttle::sync::atomic;
50 #[allow(unused_imports)]
51 pub use shuttle::sync::{Arc, Barrier, Mutex, Weak};
52
53 pub mod mpsc {
58 pub use shuttle::sync::mpsc::{RecvTimeoutError, SyncSender};
59
60 pub struct Receiver<T> {
61 inner: shuttle::sync::mpsc::Receiver<T>,
62 }
63
64 unsafe impl<T: Send> Send for Receiver<T> {}
66
67 impl<T> Receiver<T> {
68 pub fn recv_timeout(
69 &self,
70 _timeout: std::time::Duration,
71 ) -> Result<T, RecvTimeoutError> {
72 if shuttle::rand::thread_rng().gen_bool(0.8) {
75 match self.inner.try_recv() {
76 Ok(val) => Ok(val),
77 Err(shuttle::sync::mpsc::TryRecvError::Empty) => {
78 Err(RecvTimeoutError::Timeout)
79 }
80 Err(shuttle::sync::mpsc::TryRecvError::Disconnected) => {
81 Err(RecvTimeoutError::Disconnected)
82 }
83 }
84 } else {
85 self.inner
88 .recv()
89 .map_err(|_| RecvTimeoutError::Disconnected)
90 }
91 }
92
93 pub fn recv(&self) -> Result<T, shuttle::sync::mpsc::RecvError> {
94 self.inner.recv()
95 }
96 }
97
98 use shuttle::rand::Rng;
99
100 pub fn sync_channel<T>(bound: usize) -> (SyncSender<T>, Receiver<T>) {
102 let (tx, rx) = shuttle::sync::mpsc::sync_channel(bound);
103 (tx, Receiver { inner: rx })
104 }
105 }
106}
107
108#[cfg(shuttle)]
109pub mod thread {
110 #[allow(unused_imports)]
111 pub use shuttle::thread::{JoinHandle, sleep, spawn};
112
113 pub fn spawn_named<F, T>(_name: &str, f: F) -> JoinHandle<T>
114 where
115 F: FnOnce() -> T + Send + 'static,
116 T: Send + 'static,
117 {
118 spawn(f)
119 }
120}
121
122#[cfg(shuttle)]
123#[macro_export]
124macro_rules! define_thread_local {
125 ($($tt:tt)*) => { shuttle::thread_local! { $($tt)* } };
126}
127#[cfg(shuttle)]
128pub use crate::define_thread_local as thread_local;
129
130#[cfg(shuttle)]
170#[macro_export]
171macro_rules! shuttle_test {
172 (default; $(#[$attr:meta])* fn $name:ident() $body:block) => {
175 $crate::shuttle_test! {
176 num_iters = 5_000, depth = 3;
177 $(#[$attr])* fn $name() $body
178 }
179 };
180 (default, determinism_only; $(#[$attr:meta])* fn $name:ident() $body:block) => {
181 $crate::shuttle_test! {
182 num_iters = 100, determinism_only;
183 $(#[$attr])* fn $name() $body
184 }
185 };
186 (default, verify_faults_triggered; $(#[$attr:meta])* fn $name:ident() $body:block) => {
187 $crate::shuttle_test! {
188 num_iters = 10_000, depth = 3, verify_faults_triggered;
189 $(#[$attr])* fn $name() $body
190 }
191 };
192 (num_iters = $num_iters:expr, depth = $depth:expr; $(#[$attr:meta])* fn $name:ident() $body:block) => {
193 mod $name {
194 use super::*;
195
196 $(#[$attr])*
197 fn $name() $body
198
199 #[test]
200 fn pct() {
201 shuttle::check_pct($name, $num_iters, $depth);
202 }
203
204 #[test]
205 fn determinism() {
206 shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
207 }
208 }
209 };
210 (num_iters = $num_iters:expr, determinism_only; $(#[$attr:meta])* fn $name:ident() $body:block) => {
216 mod $name {
217 use super::*;
218
219 $(#[$attr])*
220 fn $name() $body
221
222 #[test]
223 fn determinism() {
224 shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
225 }
226 }
227 };
228 (num_iters = $num_iters:expr, depth = $depth:expr, should_panic $(, expect_panic = $msg:expr)? $(, replay = $schedule:expr)?; $(#[$attr:meta])* fn $name:ident() $body:block) => {
229 mod $name {
230 use super::*;
231
232 $(#[$attr])*
233 fn $name() $body
234
235 #[test]
236 #[should_panic $((expected = $msg))?]
237 fn pct() {
238 shuttle::check_pct($name, $num_iters, $depth);
239 }
240
241 #[test]
242 #[should_panic $((expected = $msg))?]
243 fn determinism() {
244 shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
245 }
246
247 $(
248 #[test]
254 #[should_panic]
255 fn replay_known_failure() {
256 shuttle::replay($name, $schedule);
257 }
258 )?
259 }
260 };
261 (num_iters = $num_iters:expr, depth = $depth:expr, should_panic, flaky_sigabrt_determinism_only $(, expect_panic = $msg:expr)? $(, replay = $schedule:expr)?; $(#[$attr:meta])* fn $name:ident() $body:block) => {
277 mod $name {
278 use super::*;
279
280 $(#[$attr])*
281 fn $name() $body
282
283 #[test]
284 #[should_panic $((expected = $msg))?]
285 fn pct() {
286 shuttle::check_pct($name, $num_iters, $depth);
287 }
288
289 #[test]
290 #[should_panic $((expected = $msg))?]
291 #[ignore = "can SIGABRT the whole process under shuttle -- see shuttle_test!'s flaky_sigabrt_determinism_only arm; run manually with --ignored"]
292 fn determinism() {
293 shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
294 }
295
296 $(
297 #[test]
303 #[should_panic]
304 fn replay_known_failure() {
305 shuttle::replay($name, $schedule);
306 }
307 )?
308 }
309 };
310 (num_iters = $num_iters:expr, depth = $depth:expr, verify_faults_triggered; $(#[$attr:meta])* fn $name:ident() $body:block) => {
316 mod $name {
317 use super::*;
318
319 $(#[$attr])*
320 fn $name() $body
321
322 fn assert_faults_were_triggered() {
323 assert!(
324 $crate::primitives::fs::take_faults_triggered() > 0,
325 "no run across {} iterations triggered a single fault; fault injection is \
326 not reaching the flush thread (e.g. a broken fault-visibility thread-local), \
327 so this test is not exercising any error path.",
328 $num_iters,
329 );
330 }
331
332 #[test]
333 fn pct() {
334 $crate::primitives::fs::take_faults_triggered(); shuttle::check_pct($name, $num_iters, $depth);
336 assert_faults_were_triggered();
337 }
338
339 #[test]
340 fn determinism() {
341 $crate::primitives::fs::take_faults_triggered(); shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
343 assert_faults_were_triggered();
344 }
345 }
346 };
347}
348
349#[cfg(not(shuttle))]
350pub mod fs {
351 use std::io::{self, Write};
352 use std::path::Path;
353
354 pub fn create_dir_all(path: &Path) -> io::Result<()> {
355 std::fs::create_dir_all(path)
356 }
357 pub fn rename(from: &Path, to: &Path) -> io::Result<()> {
358 std::fs::rename(from, to)
359 }
360 pub fn remove_file(path: &Path) -> io::Result<()> {
361 std::fs::remove_file(path)
362 }
363 pub fn remove_dir(path: &Path) -> io::Result<()> {
364 std::fs::remove_dir(path)
365 }
366 pub fn read_dir(path: &Path) -> io::Result<std::fs::ReadDir> {
367 std::fs::read_dir(path)
368 }
369 pub fn metadata(path: &Path) -> io::Result<std::fs::Metadata> {
370 std::fs::metadata(path)
371 }
372 pub fn read(path: &Path) -> io::Result<Vec<u8>> {
373 std::fs::read(path)
374 }
375
376 #[derive(Debug)]
378 pub struct File(std::fs::File);
379
380 impl File {
381 pub fn create(path: &Path) -> io::Result<File> {
382 std::fs::File::create(path).map(File)
383 }
384 }
385
386 impl Write for File {
387 #[inline]
388 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
389 self.0.write(buf)
390 }
391 #[inline]
392 fn flush(&mut self) -> io::Result<()> {
393 self.0.flush()
394 }
395 }
396}
397
398#[cfg(shuttle)]
399pub mod fs {
400 use std::cell::Cell;
401 use std::io::{self, ErrorKind, Write};
402 use std::path::Path;
403
404 use shuttle::rand::Rng;
405
406 #[derive(Clone, Copy, Debug)]
408 pub enum FaultPolicy {
409 None,
411 FailAll,
413 FailProb(f64),
416 }
417
418 std::thread_local! {
419 static FAULT: Cell<FaultPolicy> = const { Cell::new(FaultPolicy::None) };
420 }
421
422 #[must_use]
425 pub fn set_fault(policy: FaultPolicy) -> FaultGuard {
426 let prev = FAULT.with(|f| f.replace(policy));
427 FaultGuard { prev }
428 }
429
430 pub struct FaultGuard {
431 prev: FaultPolicy,
432 }
433
434 impl Drop for FaultGuard {
435 fn drop(&mut self) {
436 FAULT.with(|f| f.set(self.prev));
437 }
438 }
439
440 fn check() -> io::Result<()> {
441 let fail = match FAULT.with(|f| f.get()) {
442 FaultPolicy::None => false,
443 FaultPolicy::FailAll => true,
444 FaultPolicy::FailProb(p) => shuttle::rand::thread_rng().gen_bool(p),
445 };
446 if fail {
447 Err(io::Error::from(ErrorKind::PermissionDenied))
448 } else {
449 Ok(())
450 }
451 }
452
453 pub fn create_dir_all(path: &Path) -> io::Result<()> {
454 check()?;
455 std::fs::create_dir_all(path)
456 }
457 pub fn rename(from: &Path, to: &Path) -> io::Result<()> {
458 check()?;
459 std::fs::rename(from, to)
460 }
461 pub fn remove_file(path: &Path) -> io::Result<()> {
462 check()?;
463 std::fs::remove_file(path)
464 }
465 pub fn remove_dir(path: &Path) -> io::Result<()> {
466 check()?;
467 std::fs::remove_dir(path)
468 }
469 pub fn read_dir(path: &Path) -> io::Result<std::fs::ReadDir> {
470 check()?;
471 std::fs::read_dir(path)
472 }
473 pub fn metadata(path: &Path) -> io::Result<std::fs::Metadata> {
474 check()?;
475 std::fs::metadata(path)
476 }
477 pub fn read(path: &Path) -> io::Result<Vec<u8>> {
478 check()?;
479 std::fs::read(path)
480 }
481
482 #[derive(Debug)]
485 pub struct File(std::fs::File);
486
487 impl File {
488 pub fn create(path: &Path) -> io::Result<File> {
489 std::fs::File::create(path).map(File)
490 }
491 }
492
493 impl Write for File {
494 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
495 check()?;
496 self.0.write(buf)
497 }
498 fn flush(&mut self) -> io::Result<()> {
499 check()?;
500 self.0.flush()
501 }
502 }
503}
504
505#[cfg(not(shuttle))]
511pub struct BoundedQueue<T> {
512 inner: crossbeam_queue::ArrayQueue<T>,
513}
514
515#[cfg(not(shuttle))]
516impl<T> std::fmt::Debug for BoundedQueue<T> {
517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518 f.debug_struct("BoundedQueue")
519 .field("len", &self.inner.len())
520 .field("capacity", &self.inner.capacity())
521 .finish()
522 }
523}
524
525#[cfg(not(shuttle))]
526impl<T> BoundedQueue<T> {
527 pub fn new(capacity: usize) -> Self {
528 Self {
529 inner: crossbeam_queue::ArrayQueue::new(capacity),
530 }
531 }
532
533 pub fn force_push(&self, value: T) -> Option<T> {
535 self.inner.force_push(value)
536 }
537
538 pub fn pop(&self) -> Option<T> {
539 self.inner.pop()
540 }
541}
542
543#[cfg(shuttle)]
544pub struct BoundedQueue<T> {
545 inner: shuttle::sync::Mutex<std::collections::VecDeque<T>>,
546 capacity: usize,
547}
548
549#[cfg(shuttle)]
550impl<T> std::fmt::Debug for BoundedQueue<T> {
551 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
552 f.debug_struct("BoundedQueue")
553 .field("capacity", &self.capacity)
554 .finish_non_exhaustive()
555 }
556}
557
558#[cfg(shuttle)]
559impl<T> BoundedQueue<T> {
560 pub fn new(capacity: usize) -> Self {
561 Self {
562 inner: shuttle::sync::Mutex::new(std::collections::VecDeque::with_capacity(capacity)),
563 capacity,
564 }
565 }
566
567 pub fn force_push(&self, value: T) -> Option<T> {
568 let mut q = self.inner.lock().unwrap();
569 let evicted = if q.len() >= self.capacity {
570 q.pop_front()
571 } else {
572 None
573 };
574 q.push_back(value);
575 evicted
576 }
577
578 pub fn pop(&self) -> Option<T> {
579 self.inner.lock().unwrap().pop_front()
580 }
581}