about summary refs log tree commit diff
path: root/library/std/src/io/stdio/tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/std/src/io/stdio/tests.rs')
-rw-r--r--library/std/src/io/stdio/tests.rs165
1 files changed, 165 insertions, 0 deletions
diff --git a/library/std/src/io/stdio/tests.rs b/library/std/src/io/stdio/tests.rs
new file mode 100644
index 00000000000..bf8f3a5adfb
--- /dev/null
+++ b/library/std/src/io/stdio/tests.rs
@@ -0,0 +1,165 @@
+use super::*;
+use crate::panic::{RefUnwindSafe, UnwindSafe};
+use crate::sync::mpsc::sync_channel;
+use crate::thread;
+
+#[test]
+fn stdout_unwind_safe() {
+    assert_unwind_safe::<Stdout>();
+}
+#[test]
+fn stdoutlock_unwind_safe() {
+    assert_unwind_safe::<StdoutLock<'_>>();
+    assert_unwind_safe::<StdoutLock<'static>>();
+}
+#[test]
+fn stderr_unwind_safe() {
+    assert_unwind_safe::<Stderr>();
+}
+#[test]
+fn stderrlock_unwind_safe() {
+    assert_unwind_safe::<StderrLock<'_>>();
+    assert_unwind_safe::<StderrLock<'static>>();
+}
+
+fn assert_unwind_safe<T: UnwindSafe + RefUnwindSafe>() {}
+
+#[test]
+#[cfg_attr(any(target_os = "emscripten", target_os = "wasi"), ignore)] // no threads
+fn panic_doesnt_poison() {
+    thread::spawn(|| {
+        let _a = stdin();
+        let _a = _a.lock();
+        let _a = stdout();
+        let _a = _a.lock();
+        let _a = stderr();
+        let _a = _a.lock();
+        panic!();
+    })
+    .join()
+    .unwrap_err();
+
+    let _a = stdin();
+    let _a = _a.lock();
+    let _a = stdout();
+    let _a = _a.lock();
+    let _a = stderr();
+    let _a = _a.lock();
+}
+
+#[test]
+#[cfg_attr(any(target_os = "emscripten", target_os = "wasi"), ignore)] // no threads
+fn test_lock_stderr() {
+    test_lock(stderr, || stderr().lock());
+}
+#[test]
+#[cfg_attr(any(target_os = "emscripten", target_os = "wasi"), ignore)] // no threads
+fn test_lock_stdin() {
+    test_lock(stdin, || stdin().lock());
+}
+#[test]
+#[cfg_attr(any(target_os = "emscripten", target_os = "wasi"), ignore)] // no threads
+fn test_lock_stdout() {
+    test_lock(stdout, || stdout().lock());
+}
+
+// Helper trait to make lock testing function generic.
+trait Stdio<'a>: 'static
+where
+    Self::Lock: 'a,
+{
+    type Lock;
+    fn lock(&'a self) -> Self::Lock;
+}
+impl<'a> Stdio<'a> for Stderr {
+    type Lock = StderrLock<'a>;
+    fn lock(&'a self) -> StderrLock<'a> {
+        self.lock()
+    }
+}
+impl<'a> Stdio<'a> for Stdin {
+    type Lock = StdinLock<'a>;
+    fn lock(&'a self) -> StdinLock<'a> {
+        self.lock()
+    }
+}
+impl<'a> Stdio<'a> for Stdout {
+    type Lock = StdoutLock<'a>;
+    fn lock(&'a self) -> StdoutLock<'a> {
+        self.lock()
+    }
+}
+
+// Helper trait to make lock testing function generic.
+trait StdioOwnedLock: 'static {}
+impl StdioOwnedLock for StderrLock<'static> {}
+impl StdioOwnedLock for StdinLock<'static> {}
+impl StdioOwnedLock for StdoutLock<'static> {}
+
+// Tests locking on stdio handles by starting two threads and checking that
+// they block each other appropriately.
+fn test_lock<T, U>(get_handle: fn() -> T, get_locked: fn() -> U)
+where
+    T: for<'a> Stdio<'a>,
+    U: StdioOwnedLock,
+{
+    // State enum to track different phases of the test, primarily when
+    // each lock is acquired and released.
+    #[derive(Debug, PartialEq)]
+    enum State {
+        Start1,
+        Acquire1,
+        Start2,
+        Release1,
+        Acquire2,
+        Release2,
+    }
+    use State::*;
+    // Logging vector to be checked to make sure lock acquisitions and
+    // releases happened in the correct order.
+    let log = Arc::new(Mutex::new(Vec::new()));
+    let ((tx1, rx1), (tx2, rx2)) = (sync_channel(0), sync_channel(0));
+    let th1 = {
+        let (log, tx) = (Arc::clone(&log), tx1);
+        thread::spawn(move || {
+            log.lock().unwrap().push(Start1);
+            let handle = get_handle();
+            {
+                let locked = handle.lock();
+                log.lock().unwrap().push(Acquire1);
+                tx.send(Acquire1).unwrap(); // notify of acquisition
+                tx.send(Release1).unwrap(); // wait for release command
+                log.lock().unwrap().push(Release1);
+            }
+            tx.send(Acquire1).unwrap(); // wait for th2 acquire
+            {
+                let locked = handle.lock();
+                log.lock().unwrap().push(Acquire1);
+            }
+            log.lock().unwrap().push(Release1);
+        })
+    };
+    let th2 = {
+        let (log, tx) = (Arc::clone(&log), tx2);
+        thread::spawn(move || {
+            tx.send(Start2).unwrap(); // wait for start command
+            let locked = get_locked();
+            log.lock().unwrap().push(Acquire2);
+            tx.send(Acquire2).unwrap(); // notify of acquisition
+            tx.send(Release2).unwrap(); // wait for release command
+            log.lock().unwrap().push(Release2);
+        })
+    };
+    assert_eq!(rx1.recv().unwrap(), Acquire1); // wait for th1 acquire
+    log.lock().unwrap().push(Start2);
+    assert_eq!(rx2.recv().unwrap(), Start2); // block th2
+    assert_eq!(rx1.recv().unwrap(), Release1); // release th1
+    assert_eq!(rx2.recv().unwrap(), Acquire2); // wait for th2 acquire
+    assert_eq!(rx1.recv().unwrap(), Acquire1); // block th1
+    assert_eq!(rx2.recv().unwrap(), Release2); // release th2
+    th2.join().unwrap();
+    th1.join().unwrap();
+    assert_eq!(*log.lock().unwrap(), [
+        Start1, Acquire1, Start2, Release1, Acquire2, Release2, Acquire1, Release1
+    ]);
+}