diff --git a/crossbeam-channel/tests/golang.rs b/crossbeam-channel/tests/golang.rs index 57d9a6a97..8777de4d0 100644 --- a/crossbeam-channel/tests/golang.rs +++ b/crossbeam-channel/tests/golang.rs @@ -1540,7 +1540,290 @@ mod chan_test { // https://github.com/golang/go/blob/master/test/closedchan.go mod closedchan { - // TODO + use std::panic::{self, AssertUnwindSafe}; + + use super::*; + + trait ChanOps: Clone { + fn send(&self, x: i32); + #[allow(dead_code)] + fn nbsend(&self, x: i32) -> bool; + fn recv(&self) -> i32; + fn nbrecv(&self) -> (i32, bool); + fn recv2(&self) -> (i32, bool); + fn nbrecv2(&self) -> (i32, bool, bool); + #[allow(dead_code)] + fn close(&self); + } + + #[derive(Clone)] + struct XChan(Chan); + + #[derive(Clone)] + struct SChan(Chan); + + #[derive(Clone)] + struct SSChan { + c: Chan, + dummy: Chan, + } + + impl ChanOps for XChan { + fn send(&self, x: i32) { + self.0.send(x); + } + + fn nbsend(&self, x: i32) -> bool { + select! { + send(self.0.tx(), x) -> _ => true, + default => false, + } + } + + fn recv(&self) -> i32 { + self.0.recv().unwrap_or(0) + } + + fn nbrecv(&self) -> (i32, bool) { + select! { + recv(self.0.rx()) -> res => match res { + Ok(x) => (x, true), + Err(_) => (0, true), + }, + default => (0, false), + } + } + + fn recv2(&self) -> (i32, bool) { + match self.0.recv() { + Some(x) => (x, true), + None => (0, false), + } + } + + fn nbrecv2(&self) -> (i32, bool, bool) { + select! { + recv(self.0.rx()) -> res => match res { + Ok(x) => (x, true, true), + Err(_) => (0, false, true), + }, + default => (0, false, false), + } + } + + fn close(&self) { + self.0.close_s(); + } + } + + impl ChanOps for SChan { + fn send(&self, x: i32) { + self.0.send(x); + } + + fn nbsend(&self, x: i32) -> bool { + select! { + default => false, + send(self.0.tx(), x) -> _ => true, + } + } + + fn recv(&self) -> i32 { + select! { + recv(self.0.rx()) -> res => res.ok().unwrap_or(0), + } + } + + fn nbrecv(&self) -> (i32, bool) { + select! { + default => (0, false), + recv(self.0.rx()) -> res => match res { + Ok(x) => (x, true), + Err(_) => (0, true), + }, + } + } + + fn recv2(&self) -> (i32, bool) { + select! { + recv(self.0.rx()) -> res => match res { + Ok(x) => (x, true), + Err(_) => (0, false), + }, + } + } + + fn nbrecv2(&self) -> (i32, bool, bool) { + select! { + default => (0, false, false), + recv(self.0.rx()) -> res => match res { + Ok(x) => (x, true, true), + Err(_) => (0, false, true), + }, + } + } + + fn close(&self) { + self.0.close_s(); + } + } + + impl ChanOps for SSChan { + fn send(&self, x: i32) { + self.c.send(x); + } + + fn nbsend(&self, x: i32) -> bool { + select! { + default => false, + send(self.c.tx(), x) -> _ => true, + } + } + + fn recv(&self) -> i32 { + select! { + recv(self.c.rx()) -> res => res.ok().unwrap_or(0), + recv(self.dummy.rx()) -> _ => 0, + } + } + + fn nbrecv(&self) -> (i32, bool) { + select! { + default => (0, false), + recv(self.c.rx()) -> res => match res { + Ok(x) => (x, true), + Err(_) => (0, true), + }, + } + } + + fn recv2(&self) -> (i32, bool) { + select! { + recv(self.c.rx()) -> res => match res { + Ok(x) => (x, true), + Err(_) => (0, false), + }, + recv(self.dummy.rx()) -> _ => (0, false), + } + } + + fn nbrecv2(&self) -> (i32, bool, bool) { + select! { + default => (0, false, false), + recv(self.c.rx()) -> res => match res { + Ok(x) => (x, true, true), + Err(_) => (0, false, true), + }, + } + } + + fn close(&self) { + self.c.close_s(); + } + } + + fn wrap_x(c: Chan) -> XChan { + XChan(c) + } + + fn wrap_s(c: Chan) -> SChan { + SChan(c) + } + + fn wrap_ss(c: Chan) -> SSChan { + SSChan { + c, + dummy: make(0), + } + } + + fn should_panic(f: impl FnOnce()) { + assert!(panic::catch_unwind(AssertUnwindSafe(f)).is_err()); + } + + fn test1(c: &C, mk: impl Fn() -> C) { + for _ in 0..3 { + assert_eq!(c.recv(), 0, "recv on closed"); + let (x, ok) = c.recv2(); + assert_eq!((x, ok), (0, false), "recv2 on closed"); + + let (x, selected) = c.nbrecv(); + assert_eq!((x, selected), (0, true), "nbrecv on closed"); + + let (x, ok, selected) = c.nbrecv2(); + assert_eq!((x, ok, selected), (0, false, true), "nbrecv2 on closed"); + } + + should_panic(|| { + mk().send(2); + }); + + assert_eq!(mk().recv(), 0, "recv after send on closed"); + + should_panic(|| { + mk().send(2); + }); + + assert_eq!(mk().recv(), 0, "recv after nbsend on closed"); + } + + fn testasync1(c: &C, mk: impl Fn() -> C) { + assert_eq!(c.recv(), 1, "async recv"); + test1(c, mk); + } + + fn testasync2(c: &C, mk: impl Fn() -> C) { + let (x, ok) = c.recv2(); + assert_eq!((x, ok), (1, true), "async recv2"); + test1(c, mk); + } + + fn testasync3(c: &C, mk: impl Fn() -> C) { + let (x, selected) = c.nbrecv(); + assert_eq!((x, selected), (1, true), "async nbrecv"); + test1(c, mk); + } + + fn testasync4(c: &C, mk: impl Fn() -> C) { + let (x, ok, selected) = c.nbrecv2(); + assert_eq!((x, ok, selected), (1, true, true), "async nbrecv2"); + test1(c, mk); + } + + fn closed_sync() -> Chan { + let c = make(0); + c.close_s(); + c + } + + fn closed_async() -> Chan { + let c = make(2); + c.send(1); + c.close_s(); + c + } + + fn run_all(mk: fn(Chan) -> C) { + let mk_closed = || mk(closed_sync()); + test1(&mk_closed(), || mk(closed_sync())); + testasync1(&mk(closed_async()), || mk(closed_sync())); + testasync2(&mk(closed_async()), || mk(closed_sync())); + testasync3(&mk(closed_async()), || mk(closed_sync())); + testasync4(&mk(closed_async()), || mk(closed_sync())); + } + + #[test] + fn main() { + run_all(wrap_x); + run_all(wrap_s); + run_all(wrap_ss); + + let ch = make::(0); + ch.close_s(); + should_panic(|| { + ch.close_s(); + }); + } } // https://github.com/golang/go/blob/master/src/runtime/chanbarrier_test.go