mirror of
				https://github.com/fluffle/goirc
				synced 2025-11-03 19:48:04 +00:00 
			
		
		
		
	Use call checker instead of writing to shared var in tests (10->1).
This commit is contained in:
		
							parent
							
								
									637cdb573f
								
							
						
					
					
						commit
						4eaad0e95e
					
				
					 2 changed files with 63 additions and 85 deletions
				
			
		| 
						 | 
					@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue