mirror of https://github.com/fluffle/goirc
Use call checker instead of writing to shared var in tests (10->1).
This commit is contained in:
parent
637cdb573f
commit
4eaad0e95e
|
@ -8,6 +8,36 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type checker struct {
|
||||||
|
t *testing.T
|
||||||
|
c chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func callCheck(t *testing.T) checker {
|
||||||
|
return checker{t: t, c: make(chan struct{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c checker) call() {
|
||||||
|
c.c <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c checker) assertNotCalled(fmt string, args ...interface{}) {
|
||||||
|
select {
|
||||||
|
case <-c.c:
|
||||||
|
c.t.Errorf(fmt, args...)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c checker) assertWasCalled(fmt string, args ...interface{}) {
|
||||||
|
select {
|
||||||
|
case <-c.c:
|
||||||
|
case <-time.After(time.Millisecond):
|
||||||
|
// Usually need to wait for goroutines to settle :-/
|
||||||
|
c.t.Errorf(fmt, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testState struct {
|
type testState struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
st *state.MockTracker
|
st *state.MockTracker
|
||||||
|
@ -30,7 +60,7 @@ func setUp(t *testing.T, start ...bool) (*Conn, *testState) {
|
||||||
// start the various goroutines that shuttle data around.
|
// start the various goroutines that shuttle data around.
|
||||||
c.postConnect(len(start) == 0)
|
c.postConnect(len(start) == 0)
|
||||||
// Sleep 1ms to allow background routines to start.
|
// Sleep 1ms to allow background routines to start.
|
||||||
<-time.After(1e6)
|
<-time.After(time.Millisecond)
|
||||||
|
|
||||||
return c, &testState{ctrl, st, nc, c}
|
return c, &testState{ctrl, st, nc, c}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +69,6 @@ func (s *testState) tearDown() {
|
||||||
s.st.EXPECT().Wipe()
|
s.st.EXPECT().Wipe()
|
||||||
s.nc.ExpectNothing()
|
s.nc.ExpectNothing()
|
||||||
s.c.shutdown()
|
s.c.shutdown()
|
||||||
<-time.After(time.Millisecond)
|
|
||||||
s.ctrl.Finish()
|
s.ctrl.Finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,28 +80,22 @@ func TestEOF(t *testing.T) {
|
||||||
defer s.ctrl.Finish()
|
defer s.ctrl.Finish()
|
||||||
|
|
||||||
// Set up a handler to detect whether disconnected handlers are called
|
// Set up a handler to detect whether disconnected handlers are called
|
||||||
dcon := false
|
dcon := callCheck(t)
|
||||||
c.HandleFunc(DISCONNECTED, func(conn *Conn, line *Line) {
|
c.HandleFunc(DISCONNECTED, func(conn *Conn, line *Line) {
|
||||||
dcon = true
|
dcon.call()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Simulate EOF from server
|
// Simulate EOF from server
|
||||||
s.st.EXPECT().Wipe()
|
s.st.EXPECT().Wipe()
|
||||||
s.nc.Close()
|
s.nc.Close()
|
||||||
|
|
||||||
// Since things happen in different internal goroutines, we need to wait
|
// Verify that disconnected handler was called
|
||||||
// 1 ms should be enough :-)
|
dcon.assertWasCalled("Conn did not call disconnected handlers.")
|
||||||
<-time.After(time.Millisecond)
|
|
||||||
|
|
||||||
// Verify that the connection no longer thinks it's connected
|
// Verify that the connection no longer thinks it's connected
|
||||||
if c.Connected() {
|
if c.Connected() {
|
||||||
t.Errorf("Conn still thinks it's connected to the server.")
|
t.Errorf("Conn still thinks it's connected to the server.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that disconnected handler was called
|
|
||||||
if !dcon {
|
|
||||||
t.Errorf("Conn did not call disconnected handlers.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientAndStateTracking(t *testing.T) {
|
func TestClientAndStateTracking(t *testing.T) {
|
||||||
|
@ -146,10 +169,10 @@ func TestSend(t *testing.T) {
|
||||||
s.nc.ExpectNothing()
|
s.nc.ExpectNothing()
|
||||||
|
|
||||||
// We want to test that the a goroutine calling send will exit correctly.
|
// We want to test that the a goroutine calling send will exit correctly.
|
||||||
exited := false
|
exited := callCheck(t)
|
||||||
go func() {
|
go func() {
|
||||||
c.send()
|
c.send()
|
||||||
exited = true
|
exited.call()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// send is now running in the background as if started by postConnect.
|
// send is now running in the background as if started by postConnect.
|
||||||
|
@ -162,19 +185,13 @@ func TestSend(t *testing.T) {
|
||||||
s.nc.Expect("SENT AFTER START")
|
s.nc.Expect("SENT AFTER START")
|
||||||
|
|
||||||
// Now, use the control channel to exit send and kill the goroutine.
|
// Now, use the control channel to exit send and kill the goroutine.
|
||||||
if exited {
|
|
||||||
t.Errorf("Exited before signal sent.")
|
|
||||||
}
|
|
||||||
// This sneakily uses the fact that the other two goroutines that would
|
// This sneakily uses the fact that the other two goroutines that would
|
||||||
// normally be waiting for die to close are not running, so we only send
|
// normally be waiting for die to close are not running, so we only send
|
||||||
// to the goroutine started above. Normally shutdown() closes c.die and
|
// to the goroutine started above. Normally shutdown() closes c.die and
|
||||||
// signals to all three goroutines (send, ping, runLoop) to exit.
|
// signals to all three goroutines (send, ping, runLoop) to exit.
|
||||||
|
exited.assertNotCalled("Exited before signal sent.")
|
||||||
c.die <- struct{}{}
|
c.die <- struct{}{}
|
||||||
// Allow propagation time...
|
exited.assertWasCalled("Didn't exit after signal.")
|
||||||
<-time.After(1e6)
|
|
||||||
if !exited {
|
|
||||||
t.Errorf("Didn't exit after signal.")
|
|
||||||
}
|
|
||||||
s.nc.ExpectNothing()
|
s.nc.ExpectNothing()
|
||||||
|
|
||||||
// Sending more on c.out shouldn't reach the network.
|
// Sending more on c.out shouldn't reach the network.
|
||||||
|
@ -206,16 +223,12 @@ func TestRecv(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want to test that the a goroutine calling recv will exit correctly.
|
// We want to test that the a goroutine calling recv will exit correctly.
|
||||||
exited := false
|
exited := callCheck(t)
|
||||||
go func() {
|
go func() {
|
||||||
c.recv()
|
c.recv()
|
||||||
exited = true
|
exited.call()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Strangely, recv() needs some time to start up, but *only* when this test
|
|
||||||
// is run standalone with: client/_test/_testmain --test.run TestRecv
|
|
||||||
<-time.After(time.Millisecond)
|
|
||||||
|
|
||||||
// Now, this should mean that we'll receive our parsed line on c.in
|
// Now, this should mean that we'll receive our parsed line on c.in
|
||||||
if l := reader(); l == nil || l.Cmd != "001" {
|
if l := reader(); l == nil || l.Cmd != "001" {
|
||||||
t.Errorf("Bad first line received on input channel")
|
t.Errorf("Bad first line received on input channel")
|
||||||
|
@ -235,17 +248,10 @@ func TestRecv(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The only way recv() exits is when the socket closes.
|
// The only way recv() exits is when the socket closes.
|
||||||
if exited {
|
exited.assertNotCalled("Exited before socket close.")
|
||||||
t.Errorf("Exited before socket close.")
|
|
||||||
}
|
|
||||||
s.st.EXPECT().Wipe()
|
s.st.EXPECT().Wipe()
|
||||||
s.nc.Close()
|
s.nc.Close()
|
||||||
|
exited.assertWasCalled("Didn't exit on socket close.")
|
||||||
// Give things time to shake themselves out...
|
|
||||||
<-time.After(time.Millisecond)
|
|
||||||
if !exited {
|
|
||||||
t.Errorf("Didn't exit on socket close.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since s.nc is closed we can't attempt another send on it...
|
// Since s.nc is closed we can't attempt another send on it...
|
||||||
if l := reader(); l != nil {
|
if l := reader(); l != nil {
|
||||||
|
@ -275,10 +281,10 @@ func TestPing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start ping loop.
|
// Start ping loop.
|
||||||
exited := false
|
exited := callCheck(t)
|
||||||
go func() {
|
go func() {
|
||||||
c.ping()
|
c.ping()
|
||||||
exited = true
|
exited.call()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// The first ping should be after 50ms,
|
// The first ping should be after 50ms,
|
||||||
|
@ -306,23 +312,18 @@ func TestPing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now kill the ping loop.
|
// Now kill the ping loop.
|
||||||
if exited {
|
|
||||||
t.Errorf("Exited before signal sent.")
|
|
||||||
}
|
|
||||||
// This sneakily uses the fact that the other two goroutines that would
|
// This sneakily uses the fact that the other two goroutines that would
|
||||||
// normally be waiting for die to close are not running, so we only send
|
// normally be waiting for die to close are not running, so we only send
|
||||||
// to the goroutine started above. Normally shutdown() closes c.die and
|
// to the goroutine started above. Normally shutdown() closes c.die and
|
||||||
// signals to all three goroutines (send, ping, runLoop) to exit.
|
// signals to all three goroutines (send, ping, runLoop) to exit.
|
||||||
|
exited.assertNotCalled("Exited before signal sent.")
|
||||||
c.die <- struct{}{}
|
c.die <- struct{}{}
|
||||||
|
exited.assertWasCalled("Didn't exit after signal.")
|
||||||
// Make sure we're no longer pinging by waiting ~2x PingFreq
|
// Make sure we're no longer pinging by waiting ~2x PingFreq
|
||||||
<-time.After(105 * time.Millisecond)
|
<-time.After(105 * time.Millisecond)
|
||||||
if s := reader(); s != "" {
|
if s := reader(); s != "" {
|
||||||
t.Errorf("Line output after ping stopped.")
|
t.Errorf("Line output after ping stopped.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exited {
|
|
||||||
t.Errorf("Didn't exit after signal.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunLoop(t *testing.T) {
|
func TestRunLoop(t *testing.T) {
|
||||||
|
@ -331,67 +332,47 @@ func TestRunLoop(t *testing.T) {
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// Set up a handler to detect whether 001 handler is called
|
// Set up a handler to detect whether 001 handler is called
|
||||||
h001 := false
|
h001 := callCheck(t)
|
||||||
c.HandleFunc("001", func(conn *Conn, line *Line) {
|
c.HandleFunc("001", func(conn *Conn, line *Line) {
|
||||||
h001 = true
|
h001.call()
|
||||||
})
|
})
|
||||||
|
h002 := callCheck(t)
|
||||||
// Set up a handler to detect whether 002 handler is called
|
// Set up a handler to detect whether 002 handler is called
|
||||||
h002 := false
|
|
||||||
c.HandleFunc("002", func(conn *Conn, line *Line) {
|
c.HandleFunc("002", func(conn *Conn, line *Line) {
|
||||||
h002 = true
|
h002.call()
|
||||||
})
|
})
|
||||||
|
|
||||||
l1 := parseLine(":irc.server.org 001 test :First test line.")
|
l1 := parseLine(":irc.server.org 001 test :First test line.")
|
||||||
c.in <- l1
|
c.in <- l1
|
||||||
if h001 {
|
h001.assertNotCalled("001 handler called before runLoop started.")
|
||||||
t.Errorf("001 handler called before runLoop started.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to test that the a goroutine calling runLoop will exit correctly.
|
// We want to test that the a goroutine calling runLoop will exit correctly.
|
||||||
// Now, we can expect the call to Dispatch to take place as runLoop starts.
|
// Now, we can expect the call to Dispatch to take place as runLoop starts.
|
||||||
exited := false
|
exited := callCheck(t)
|
||||||
go func() {
|
go func() {
|
||||||
c.runLoop()
|
c.runLoop()
|
||||||
exited = true
|
exited.call()
|
||||||
}()
|
}()
|
||||||
// Here, the opposite seemed to take place, with TestRunLoop failing when
|
h001.assertWasCalled("001 handler not called after runLoop started.")
|
||||||
// run as part of the suite but passing when run on it's own.
|
|
||||||
<-time.After(time.Millisecond)
|
|
||||||
if !h001 {
|
|
||||||
t.Errorf("001 handler not called after runLoop started.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send another line, just to be sure :-)
|
// Send another line, just to be sure :-)
|
||||||
|
h002.assertNotCalled("002 handler called before expected.")
|
||||||
l2 := parseLine(":irc.server.org 002 test :Second test line.")
|
l2 := parseLine(":irc.server.org 002 test :Second test line.")
|
||||||
c.in <- l2
|
c.in <- l2
|
||||||
// It appears some sleeping is needed after all of these to ensure channel
|
h002.assertWasCalled("002 handler not called while runLoop started.")
|
||||||
// sends occur before the close signal is sent below...
|
|
||||||
<-time.After(time.Millisecond)
|
|
||||||
if !h002 {
|
|
||||||
t.Errorf("002 handler not called while runLoop started.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, use the control channel to exit send and kill the goroutine.
|
// Now, use the control channel to exit send and kill the goroutine.
|
||||||
if exited {
|
|
||||||
t.Errorf("Exited before signal sent.")
|
|
||||||
}
|
|
||||||
// This sneakily uses the fact that the other two goroutines that would
|
// This sneakily uses the fact that the other two goroutines that would
|
||||||
// normally be waiting for die to close are not running, so we only send
|
// normally be waiting for die to close are not running, so we only send
|
||||||
// to the goroutine started above. Normally shutdown() closes c.die and
|
// to the goroutine started above. Normally shutdown() closes c.die and
|
||||||
// signals to all three goroutines (send, ping, runLoop) to exit.
|
// signals to all three goroutines (send, ping, runLoop) to exit.
|
||||||
|
exited.assertNotCalled("Exited before signal sent.")
|
||||||
c.die <- struct{}{}
|
c.die <- struct{}{}
|
||||||
// Allow propagation time...
|
exited.assertWasCalled("Didn't exit after signal.")
|
||||||
<-time.After(time.Millisecond)
|
|
||||||
if !exited {
|
|
||||||
t.Errorf("Didn't exit after signal.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sending more on c.in shouldn't dispatch any further events
|
// Sending more on c.in shouldn't dispatch any further events
|
||||||
h001 = false
|
|
||||||
c.in <- l1
|
c.in <- l1
|
||||||
if h001 {
|
h001.assertNotCalled("001 handler called after runLoop ended.")
|
||||||
t.Errorf("001 handler called after runLoop ended.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWrite(t *testing.T) {
|
func TestWrite(t *testing.T) {
|
||||||
|
|
|
@ -186,18 +186,15 @@ func TestPanicRecovery(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
recovered := false
|
recovered := callCheck(t)
|
||||||
c.cfg.Recover = func(conn *Conn, line *Line) {
|
c.cfg.Recover = func(conn *Conn, line *Line) {
|
||||||
if err, ok := recover().(string); ok && err == "panic!" {
|
if err, ok := recover().(string); ok && err == "panic!" {
|
||||||
recovered = true
|
recovered.call()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.HandleFunc(PRIVMSG, func(conn *Conn, line *Line) {
|
c.HandleFunc(PRIVMSG, func(conn *Conn, line *Line) {
|
||||||
panic("panic!")
|
panic("panic!")
|
||||||
})
|
})
|
||||||
c.in <- parseLine(":nick!user@host.com PRIVMSG #channel :OH NO PIGEONS")
|
c.in <- parseLine(":nick!user@host.com PRIVMSG #channel :OH NO PIGEONS")
|
||||||
<-time.After(time.Millisecond)
|
recovered.assertWasCalled("Failed to recover panic!")
|
||||||
if recovered != true {
|
|
||||||
t.Errorf("Failed to recover panic!")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue