diff --git a/README.md b/README.md index e5f3689..aa08a21 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # chattypantz [![License MIT](https://img.shields.io/npm/l/express.svg)](http://opensource.org/licenses/MIT) [![Build Status](https://travis-ci.org/composer22/chattypantz.svg?branch=master)](http://travis-ci.org/composer22/chattypantz) -[![Current Release](https://img.shields.io/badge/release-v0.1.1-brightgreen.svg)](https://github.com/composer22/chattypantz/releases/tag/v0.1.1) +[![Current Release](https://img.shields.io/badge/release-v0.1.2-brightgreen.svg)](https://github.com/composer22/chattypantz/releases/tag/v0.1.2) [![Coverage Status](https://coveralls.io/repos/composer22/chattypantz/badge.svg?branch=master)](https://coveralls.io/r/composer22/chattypantz?branch=master) ![chattypantz-logo](assets/img/chattypantz.png) diff --git a/server/chat_manager.go b/server/chat_manager.go new file mode 100644 index 0000000..40fff47 --- /dev/null +++ b/server/chat_manager.go @@ -0,0 +1,163 @@ +package server + +import ( + "errors" + "fmt" + "sync" + + "golang.org/x/net/websocket" +) + +// ChatManager represents a control hub of chat rooms and chatters for the server. +type ChatManager struct { + rooms map[string]*ChatRoom // A list of rooms on the server. + chatters map[*Chatter]bool // A list of chatters on the server. + done chan bool // Shut down chatters and rooms + maxRooms int // Maximum number of rooms allowed to be created. + maxIdle int // Maximum idle time allowed for a ws connection. + log *ChatLogger // Application log for events. + wg sync.WaitGroup // Synchronizer for manager reqq. + mu sync.Mutex // Lock for update. +} + +// ChatManagerNew is a factory function that returns a new instance of a chat manager. +func ChatManagerNew(n int, mi int, cl *ChatLogger) *ChatManager { + return &ChatManager{ + rooms: make(map[string]*ChatRoom), + chatters: make(map[*Chatter]bool), + done: make(chan bool), + maxRooms: n, + maxIdle: mi, + log: cl, + } +} + +// list returns a list of chat room names. +func (m *ChatManager) list() []string { + m.mu.Lock() + defer m.mu.Unlock() + var names []string + for n := range m.rooms { + names = append(names, n) + } + return names +} + +// find will find a chat room for a given name. +func (m *ChatManager) find(n string) (*ChatRoom, error) { + m.mu.Lock() + r, ok := m.rooms[n] + m.mu.Unlock() + if !ok { + return nil, errors.New(fmt.Sprintf(`Chatroom "%s" not found.`, n)) + } + return r, nil +} + +// findCreate returns a chat room for a given name or create a new one. +func (m *ChatManager) findCreate(n string) (*ChatRoom, error) { + r, err := m.find(n) + if err != nil { + mr := m.MaxRooms() + m.mu.Lock() // cover rooms + if mr > 0 && mr == len(m.rooms) { + m.mu.Unlock() + return nil, errors.New("Maximum number of rooms reached. Cannot create new room.") + } + r = ChatRoomNew(n, m.done, m.log, &m.wg) + m.rooms[n] = r + m.wg.Add(1) + go r.Run() + m.mu.Unlock() + } + return r, nil +} + +// removeChatterAllRooms sends a broadcast to all rooms to release the chatter. +func (m *ChatManager) removeChatterAllRooms(c *Chatter) { + m.mu.Lock() + defer m.mu.Unlock() + for _, r := range m.rooms { + if q, err := ChatRequestNew(c, r.name, ChatReqTypeLeave, ""); err == nil { + r.reqq <- q + } + } +} + +// getRoomStats returns statistics from each room. +func (m *ChatManager) getRoomStats() []*ChatRoomStats { + m.mu.Lock() + defer m.mu.Unlock() + var s = []*ChatRoomStats{} + for _, r := range m.rooms { + s = append(s, r.ChatRoomStatsNew()) + } + return s +} + +// registerChatter registers a new chatter with the chat manager. +func (m *ChatManager) registerNewChatter(ws *websocket.Conn) *Chatter { + m.mu.Lock() + defer m.mu.Unlock() + c := ChatterNew(m, ws, m.log) + m.chatters[c] = true + return c +} + +// getChatterStats returns statistics from all chatters +func (m *ChatManager) getChatterStats() []*ChatterStats { + m.mu.Lock() + defer m.mu.Unlock() + var s = []*ChatterStats{} + for c := range m.chatters { + s = append(s, c.ChatterStatsNew()) + } + return s +} + +// unregisterChatter removes a new chatter from the chat manager. +func (m *ChatManager) unregisterChatter(c *Chatter) { + m.mu.Lock() + defer m.mu.Unlock() + if _, ok := m.chatters[c]; ok { + delete(m.chatters, c) + } +} + +// Shuts down the chatters and the rooms. Used by server on quit. +func (m *ChatManager) shutdownAll() { + close(m.done) + m.wg.Wait() + m.mu.Lock() + m.rooms = make(map[string]*ChatRoom) + m.chatters = make(map[*Chatter]bool) + m.mu.Unlock() +} + +// MaxRooms returns the current maximum number of rooms allowed on the server. +func (m *ChatManager) MaxRooms() int { + m.mu.Lock() + defer m.mu.Unlock() + return m.maxRooms +} + +// SetMaxRooms sets the maximum number of rooms allowed on the server. +func (m *ChatManager) SetMaxRooms(mr int) { + m.mu.Lock() + defer m.mu.Unlock() + m.maxRooms = mr +} + +// MaxIdle returns the current maximum idle time for a connection. +func (m *ChatManager) MaxIdle() int { + m.mu.Lock() + defer m.mu.Unlock() + return m.maxIdle +} + +// SetMaxIdle sets the maximum idle time for a connection. +func (m *ChatManager) SetMaxIdle(mi int) { + m.mu.Lock() + defer m.mu.Unlock() + m.maxIdle = mi +} diff --git a/server/chat_room.go b/server/chat_room.go index aababe9..aa6de88 100644 --- a/server/chat_room.go +++ b/server/chat_room.go @@ -21,17 +21,19 @@ type ChatRoom struct { reqCount uint64 // Total requests received. rspCount uint64 // Total responses sent. reqq chan *ChatRequest // Channel to receive requests. + done chan bool // Channel to receive signal to shutdown now. log *ChatLogger // Application log for events. mu sync.Mutex // Lock against stats. wg *sync.WaitGroup // Wait group for the run from the chat room manager. } // ChatRoomNew is a factory function that returns a new instance of a chat room. -func ChatRoomNew(n string, cl *ChatLogger, g *sync.WaitGroup) *ChatRoom { +func ChatRoomNew(n string, d chan bool, cl *ChatLogger, g *sync.WaitGroup) *ChatRoom { return &ChatRoom{ name: n, chatters: make(map[*Chatter]bool), reqq: make(chan *ChatRequest, maxChatRoomReq), + done: d, log: cl, wg: g, } @@ -43,6 +45,8 @@ func (r *ChatRoom) Run() { r.start = time.Now() for { select { + case <-r.done: // Server signal quit + return case req, ok := <-r.reqq: if !ok { // Assume ch closed and shutdown notification return @@ -219,18 +223,14 @@ func (r *ChatRoom) isMemberName(n string) bool { // sendResponse sends a message to a single chatter in the room. func (r *ChatRoom) sendResponse(c *Chatter, rt int, ct string, l []string) { - if c.isConnected() { - if l == nil { - l = []string{} - } - if rsp, err := ChatResponseNew(r.name, rt, ct, l); err == nil { - r.mu.Lock() - r.lastRsp = time.Now() - r.rspCount++ - r.mu.Unlock() - c.rspq <- rsp - } + if l == nil { + l = []string{} } + c.sendResponse(r.name, rt, ct, l) + r.mu.Lock() + r.lastRsp = time.Now() + r.rspCount++ + r.mu.Unlock() } // sendResponseAll sends a message to all chatters in the room. @@ -238,15 +238,11 @@ func (r *ChatRoom) sendResponseAll(rt int, ct string, l []string) { if l == nil { l = []string{} } - if rsp, err := ChatResponseNew(r.name, rt, ct, l); err == nil { - r.mu.Lock() - for c := range r.chatters { - if c.isConnected() { - r.lastRsp = time.Now() - r.rspCount++ - c.rspq <- rsp - } - } - r.mu.Unlock() + r.mu.Lock() + for c := range r.chatters { + c.sendResponse(r.name, rt, ct, l) + r.lastRsp = time.Now() + r.rspCount++ } + r.mu.Unlock() } diff --git a/server/chat_room_manager.go b/server/chat_room_manager.go deleted file mode 100644 index cabc05f..0000000 --- a/server/chat_room_manager.go +++ /dev/null @@ -1,100 +0,0 @@ -package server - -import ( - "errors" - "fmt" - "sync" -) - -// ChatRoomManager represents a hub of chat rooms for the server. -type ChatRoomManager struct { - rooms map[string]*ChatRoom // A list of rooms on the server. - maxRooms int // Maximum number of rooms allowed to be created. - log *ChatLogger // Application log for events. - wg sync.WaitGroup // Synchronizer for manager reqq. - mu sync.Mutex // Lock for update. -} - -// ChatRoomManagerNew is a factory function that returns a new instance of a chat room manager. -func ChatRoomManagerNew(n int, cl *ChatLogger) *ChatRoomManager { - return &ChatRoomManager{ - rooms: make(map[string]*ChatRoom), - maxRooms: n, - log: cl, - } -} - -// list returns a list of chat room names. -func (m *ChatRoomManager) list() []string { - m.mu.Lock() - defer m.mu.Unlock() - var names []string - for n := range m.rooms { - names = append(names, n) - } - return names -} - -// find will find a chat room for a given name. -func (m *ChatRoomManager) find(n string) (*ChatRoom, error) { - m.mu.Lock() - r, ok := m.rooms[n] - m.mu.Unlock() - if !ok { - return nil, errors.New(fmt.Sprintf(`Chatroom "%s" not found.`, n)) - } - return r, nil -} - -// findCreate returns a chat room for a given name or create a new one. -func (m *ChatRoomManager) findCreate(n string) (*ChatRoom, error) { - r, err := m.find(n) - if err != nil { - m.mu.Lock() // cover rooms - if m.maxRooms > 0 && m.maxRooms == len(m.rooms) { - m.mu.Unlock() - return nil, errors.New("Maximum number of rooms reached. Cannot create new room.") - } - r = ChatRoomNew(n, m.log, &m.wg) - m.rooms[n] = r - m.wg.Add(1) - go r.Run() - m.mu.Unlock() - } - return r, nil -} - -// removeChatterAllRooms releases the chatter from any rooms. -func (m *ChatRoomManager) removeChatterAllRooms(c *Chatter) { - m.mu.Lock() - defer m.mu.Unlock() - for _, r := range m.rooms { - if q, err := ChatRequestNew(c, r.name, ChatReqTypeLeave, ""); err == nil { - r.reqq <- q - } - } -} - -// getRoomStats returns statistics from each room. -func (m *ChatRoomManager) getRoomStats() []*ChatRoomStats { - m.mu.Lock() - defer m.mu.Unlock() - var s = []*ChatRoomStats{} - for _, r := range m.rooms { - s = append(s, r.ChatRoomStatsNew()) - } - return s -} - -// shutDownRooms releases all rooms from processing and memory. -func (m *ChatRoomManager) shutDownRooms() { - m.mu.Lock() - defer m.mu.Unlock() - // Close the channel which signals a stop run - for _, r := range m.rooms { - close(r.reqq) - } - m.wg.Wait() - m.rooms = nil - m.rooms = make(map[string]*ChatRoom) -} diff --git a/server/chat_room_manager_test.go b/server/chat_room_manager_test.go deleted file mode 100644 index 6a5cc8f..0000000 --- a/server/chat_room_manager_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package server - -import "testing" - -func TestChatRoomMMngrNew(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomMngrList(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomMngrFind(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomMngrFindCreate(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomMngrRemoveChatterAll(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomMngrGetStats(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomMngrShutdownRooms(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} diff --git a/server/chat_room_test.go b/server/chat_room_test.go deleted file mode 100644 index 392fc6c..0000000 --- a/server/chat_room_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package server - -import "testing" - -func TestChatRoomNew(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomRun(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomJoin(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomListNames(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomHide(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomUnhide(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomMessage(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomLeave(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomStatsNew(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomIsMember(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomIsMemberName(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomSendResponse(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatRoomSendResponseAll(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} diff --git a/server/chatter.go b/server/chatter.go index 1fccc22..3b16bd7 100644 --- a/server/chatter.go +++ b/server/chatter.go @@ -16,65 +16,67 @@ var ( ) // Chatter is a wrapper around a connection that represents one chat client on the server. -// It is a parasitic class in that it lives and dies within the context of the server and is trusted -// to use and modify server atttributes directly. type Chatter struct { - srvr *Server // The server this chatter is connected to. - ws *websocket.Conn // The socket to the remote client. - connected bool // Indicates if the socket is active, - nickname string // The friendly nickname to display in a conversation. - start time.Time // The start time of the connection. - lastReq time.Time // The last request time of the connection. - lastRsp time.Time // The last response time to the connection. - reqCount uint64 // Total requests received. - rspCount uint64 // Total responses sent. - rspq chan *ChatResponse // A channel to receive information to send to the remote client. - mu sync.Mutex // For locking access to chatter attributes. - wg sync.WaitGroup // Synchronization of channel close. + cMngr *ChatManager // The chat manager this chatter is attached to. + ws *websocket.Conn // The socket to the remote client. + nickname string // The friendly nickname to display in a conversation. + start time.Time // The start time of the connection. + lastReq time.Time // The last request time of the connection. + lastRsp time.Time // The last response time to the connection. + reqCount uint64 // Total requests received. + rspCount uint64 // Total responses sent. + done chan bool // Signal that chatter is closed. + rspq chan *ChatResponse // A channel to receive information to send to the remote client. + log *ChatLogger // Server logger + mu sync.Mutex // For locking access to chatter attributes. + wg sync.WaitGroup // Synchronization of channel close. + } // ChatterNew is a factory function that returns a new Chatter instance -func ChatterNew(s *Server, c *websocket.Conn) *Chatter { +func ChatterNew(cm *ChatManager, c *websocket.Conn, l *ChatLogger) *Chatter { return &Chatter{ - srvr: s, - ws: c, - rspq: make(chan *ChatResponse, maxChatterRsp), + cMngr: cm, + ws: c, + done: make(chan bool, 1), + rspq: make(chan *ChatResponse, maxChatterRsp), + log: l, } } // Run starts the event loop that manages the sending and receiving of information to the client. func (c *Chatter) Run() { c.start = time.Now() - c.connected = true - c.srvr.wg.Add(1) // We let the big boss also perform waits for chatters, so it can close down, - c.wg.Add(1) // but we also have our own in send(). - go c.send() // Spawn response handling to the client in the background. - c.receive() // Then wait on incoming requests. + c.cMngr.wg.Add(1) // We let the big boss also perform waits for chatters, so it can close down, + c.wg.Add(1) // but we also have our own in send(). + go c.send() // Spawn response handling to the client in the background. + c.receive() // Then wait on incoming requests. } // receive polls and handles any commands or information sent from the remote client. func (c *Chatter) receive() { - defer c.srvr.wg.Done() + defer c.cMngr.wg.Done() remoteAddr := c.ws.Request().RemoteAddr for { // Set optional idle timeout on the receive. - if c.srvr.info.MaxIdle > 0 { - c.ws.SetReadDeadline(time.Now().Add(time.Duration(c.srvr.info.MaxIdle) * time.Second)) + mi := c.cMngr.MaxIdle() + if mi > 0 { + c.ws.SetReadDeadline(time.Now().Add(time.Duration(mi) * time.Second)) } var req ChatRequest if err := websocket.JSON.Receive(c.ws, &req); err != nil { e, ok := err.(net.Error) switch { case ok && e.Timeout(): - c.srvr.log.LogSession("disconnected", remoteAddr, "Client forced to disconnect due to inactivity.") + c.log.LogSession("disconnected", remoteAddr, "Client forced to disconnect due to inactivity.") c.shutDown() case err.Error() == "EOF": - c.srvr.log.LogSession("disconnected", remoteAddr, "Client disconnected.") + c.log.LogSession("disconnected", remoteAddr, "Client disconnected.") c.shutDown() case strings.Contains(err.Error(), "use of closed network connection"): // cntl-c safety. c.shutDown() default: - c.srvr.log.LogError(remoteAddr, fmt.Sprintf("Couldn't receive. Error: %s", err.Error())) + c.log.LogError(remoteAddr, fmt.Sprintf("Couldn't receive. Error: %s", err.Error())) } return } @@ -82,7 +84,7 @@ func (c *Chatter) receive() { c.lastReq = time.Now() c.reqCount++ c.mu.Unlock() - c.srvr.log.LogSession("received", remoteAddr, fmt.Sprintf("%s", &req)) + c.log.LogSession("received", remoteAddr, fmt.Sprintf("%s", &req)) switch req.ReqType { case ChatReqTypeSetNickname: c.setNickname(&req) @@ -90,7 +92,7 @@ func (c *Chatter) receive() { c.getNickname() case ChatReqTypeListRooms: c.listRooms() - default: // let room handle other requests or send error if no room name provided. + default: // Let room handle other requests or send error if no room name provided. req.Who = c c.sendRequestToRoom(&req) } @@ -103,11 +105,14 @@ func (c *Chatter) send() { remoteAddr := fmt.Sprint(c.ws.Request().RemoteAddr) for { select { - case <-c.srvr.done: // Server shutdown signal. + case <-c.cMngr.done: // Server shutdown signal. + c.ws.Close() // Break the receive() loop and force a chatter shutdown. + return + case <-c.done: // Chatter shutdown signal. c.ws.Close() // Break the receive() loop and force a chatter shutdown. return case rsp, ok := <-c.rspq: - if !ok { // Assume ch closed is a shutdown notification from anybody. + if !ok { // Assume ch closed might also be shutdown notification from somebody. c.ws.Close() // Break the receive() looper and force a chatter shutdown. return } @@ -115,14 +120,14 @@ func (c *Chatter) send() { c.lastRsp = time.Now() c.rspCount++ c.mu.Unlock() - c.srvr.log.LogSession("sent", remoteAddr, fmt.Sprintf("%s", rsp)) + c.log.LogSession("sent", remoteAddr, fmt.Sprintf("%s", rsp)) if err := websocket.JSON.Send(c.ws, rsp); err != nil { switch { case err.Error() == "EOF": - c.srvr.log.LogSession("disconnected", remoteAddr, "Client disconnected.") + c.log.LogSession("disconnected", remoteAddr, "Client disconnected.") return default: - c.srvr.log.LogError(remoteAddr, fmt.Sprintf("Couldn't send. Error: %s", err.Error())) + c.log.LogError(remoteAddr, fmt.Sprintf("Couldn't send. Error: %s", err.Error())) } } } @@ -130,31 +135,28 @@ func (c *Chatter) send() { } } -// shutDown removes the chatter from any rooms, and shuts down sending/receiving. +// shutDown shuts down sending/receiving. func (c *Chatter) shutDown() { - c.srvr.roomMngr.removeChatterAllRooms(c) - c.mu.Lock() - c.connected = false - c.mu.Unlock() - close(c.rspq) // signal stop to send() - c.wg.Wait() + close(c.done) // Signal to send() and rooms we are quitting. + c.wg.Wait() // Wait for send() + c.cMngr.removeChatterAllRooms(c) } // setNickname sets the nickname for the chatter. func (c *Chatter) setNickname(r *ChatRequest) { if r.Content == "" { - c.sendResponse(ChatRspTypeErrNicknameMandatory, "Nickname cannot be blank.", nil) + c.sendResponse("", ChatRspTypeErrNicknameMandatory, "Nickname cannot be blank.", nil) return } c.mu.Lock() c.nickname = r.Content c.mu.Unlock() - c.sendResponse(ChatRspTypeSetNickname, fmt.Sprintf(`Nickname set to "%s".`, c.Nickname()), nil) + c.sendResponse("", ChatRspTypeSetNickname, fmt.Sprintf(`Nickname set to "%s".`, c.Nickname()), nil) } -// nickname returns the nickname for the chatter via the response queue. +// getNickname returns the nickname for the chatter via the response queue. func (c *Chatter) getNickname() { - c.sendResponse(ChatRspTypeGetNickname, c.Nickname(), nil) + c.sendResponse("", ChatRspTypeGetNickname, c.Nickname(), nil) } // Nickname returns the raw nickname for the chatter. @@ -166,14 +168,7 @@ func (c *Chatter) Nickname() string { // listRooms returns a list of chat rooms to the chatter. func (c *Chatter) listRooms() { - c.sendResponse(ChatRspTypeListRooms, "", c.srvr.roomMngr.list()) -} - -// isConnected returns the state of the websocket connection. -func (c *Chatter) isConnected() bool { - c.mu.Lock() - defer c.mu.Unlock() - return c.connected + c.sendResponse("", ChatRspTypeListRooms, "", c.cMngr.list()) } // ChatterStats is a simple structure for returning statistic information on the chatter. @@ -205,24 +200,26 @@ func (c *Chatter) ChatterStatsNew() *ChatterStats { // sendRequestToRoom sends the request to a room or creates a mew room to receive the request. func (c *Chatter) sendRequestToRoom(r *ChatRequest) { if r.RoomName == "" { - c.sendResponse(ChatRspTypeErrRoomMandatory, "Room name is mandatory to access a room.", nil) + c.sendResponse("", ChatRspTypeErrRoomMandatory, "Room name is mandatory to access a room.", nil) return } - m, err := c.srvr.roomMngr.findCreate(r.RoomName) + m, err := c.cMngr.findCreate(r.RoomName) if err != nil { - c.sendResponse(ChatRspTypeErrMaxRoomsReached, err.Error(), nil) + c.sendResponse("", ChatRspTypeErrMaxRoomsReached, err.Error(), nil) return } m.reqq <- r } -// sendResponse sends a message to a chatter. -func (c *Chatter) sendResponse(rt int, msg string, l []string) { - if c.isConnected() { - if l == nil { - l = []string{} - } - if rsp, err := ChatResponseNew("", rt, msg, l); err == nil { +// sendResponse sends a message to the send() go routine to send message back to chatter. +func (c *Chatter) sendResponse(n string, rt int, msg string, l []string) { + if l == nil { + l = []string{} + } + if rsp, err := ChatResponseNew(n, rt, msg, l); err == nil { + select { + case <-c.done: + default: c.rspq <- rsp } } diff --git a/server/chatter_test.go b/server/chatter_test.go deleted file mode 100644 index 74d93ff..0000000 --- a/server/chatter_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package server - -import "testing" - -func TestChatterNew(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterRun(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterReceive(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterSend(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterShutdown(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterSetNickname(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterGetNickname(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterNickname(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterListRooms(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterIsConnected(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterStatsNew(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterSendRequestToRoom(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} - -func TestChatterSendResponse(t *testing.T) { - t.Parallel() - t.Skip("Covered by server test.") -} diff --git a/server/server.go b/server/server.go index 61f398d..6922c67 100644 --- a/server/server.go +++ b/server/server.go @@ -23,17 +23,15 @@ import ( // Server is the main structure that represents a server instance. type Server struct { - info *Info // Basic server information used to run the server. - opts *Options // Original options used to create the server. - stats *Stats // Server statistics since it started. - mu sync.Mutex // For locking access to server attributes. - running bool // Is the server running? - log *ChatLogger // Log instance for recording error and other messages. - roomMngr *ChatRoomManager // Manager of chat rooms. - chatters map[*Chatter]bool // A list of chatters connected to the server. - done chan bool // A channel to signal to web socked to close. - srvr *http.Server // HTTP server. - wg sync.WaitGroup // Synchronization of channel close. + info *Info // Basic server information used to run the server. + opts *Options // Original options used to create the server. + stats *Stats // Server statistics since it started. + mu sync.Mutex // For locking access to server attributes. + running bool // Is the server running? + log *ChatLogger // Log instance for recording error and other messages. + cMngr *ChatManager // Manager of chatters and chat rooms. + done chan bool // A channel to signal to web socked to close. + srvr *http.Server // HTTP server. } // New is a factory function that returns a new server instance. @@ -49,11 +47,10 @@ func New(ops *Options) *Server { i.MaxIdle = ops.MaxIdle i.Debug = ops.Debug }), - opts: ops, - stats: StatsNew(), - log: ChatLoggerNew(), - chatters: map[*Chatter]bool{}, - running: false, + opts: ops, + stats: StatsNew(), + log: ChatLoggerNew(), + running: false, } if s.info.Debug { @@ -68,8 +65,8 @@ func New(ops *Options) *Server { Addr: fmt.Sprintf("%s:%d", s.info.Hostname, s.info.Port), } - s.roomMngr = ChatRoomManagerNew(s.info.MaxRooms, s.log) // Set the manager of the chat rooms - s.handleSignals() // Evoke trap signals handler + s.cMngr = ChatManagerNew(s.info.MaxRooms, s.info.MaxIdle, s.log) + s.handleSignals() return s } @@ -106,7 +103,6 @@ func (s *Server) Start() error { } s.stats.Start = time.Now() - s.done = make(chan bool) s.running = true s.mu.Unlock() err = s.srvr.Serve(ln) @@ -139,15 +135,11 @@ func (s *Server) Shutdown() { return } s.log.Infof("BEGIN server service stop.") - + s.log.Infof("Shutting down chatters and rooms...") + s.cMngr.shutdownAll() s.mu.Lock() - s.log.Infof("\tShutting down chatters...") - close(s.done) s.running = false s.mu.Unlock() - s.wg.Wait() - s.log.Infof("\tShutting down rooms...") - s.roomMngr.shutDownRooms() s.log.Infof("END server service stop.") } @@ -169,14 +161,9 @@ func (s *Server) handleSignals() { func (s *Server) chatHandler(ws *websocket.Conn) { s.log.LogConnect(ws.Request()) s.incrementStats(ws.Request()) - c := ChatterNew(s, ws) - s.mu.Lock() - s.chatters[c] = true // register chatter - s.mu.Unlock() + c := s.cMngr.registerNewChatter(ws) c.Run() - s.mu.Lock() - delete(s.chatters, c) // unregister chatter - s.mu.Unlock() + s.cMngr.unregisterChatter(c) } // aliveHandler handles a client http:// "is the server alive?" request. @@ -193,11 +180,8 @@ func (s *Server) statsHandler(w http.ResponseWriter, r *http.Request) { s.initResponseHeader(w) s.mu.Lock() defer s.mu.Unlock() - s.stats.ChatterStats = []*ChatterStats{} - for c := range s.chatters { - s.stats.ChatterStats = append(s.stats.ChatterStats, c.ChatterStatsNew()) - } - s.stats.RoomStats = s.roomMngr.getRoomStats() + s.stats.ChatterStats = s.cMngr.getChatterStats() + s.stats.RoomStats = s.cMngr.getRoomStats() mStats := &runtime.MemStats{} runtime.ReadMemStats(mStats) b, _ := json.Marshal( diff --git a/server/server_test.go b/server/server_test.go index 601ef10..251e9e6 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -12,14 +12,15 @@ import ( ) const ( - testServerHostname = "localhost" - testServerPort = 6660 - testServerMaxConns = 4 - testServerMaxRooms = 2 - testChatRoomName1 = "Room1" - testChatRoomName2 = "Room2" - testChatRoomName3 = "Room3" - testChatterNickname = "ChatMonkey" + testServerHostname = "localhost" + testServerPort = 6660 + testServerMaxConns = 4 + testServerMaxRooms = 2 + testChatRoomName1 = "Room1" + testChatRoomName2 = "Room2" + testChatRoomName3 = "Room3" + testChatterNickname1 = "ChatMonkey" + testChatterNickname2 = "MonkeyTester" ) var ( @@ -40,17 +41,19 @@ var ( testSrvrOrg = fmt.Sprintf("ws://%s/", testServerHostname) TestServerSetNickname = fmt.Sprintf(`{"reqType":%d,"content":"%s"}`, - ChatReqTypeSetNickname, testChatterNickname) + ChatReqTypeSetNickname, testChatterNickname1) + TestServerSetNickname2 = fmt.Sprintf(`{"reqType":%d,"content":"%s"}`, + ChatReqTypeSetNickname, testChatterNickname2) TestServerSetNicknameErr = fmt.Sprintf(`{"reqType":%d,"content":""}`, ChatReqTypeSetNickname) TestServerSetNicknameExp = fmt.Sprintf(`{"roomName":"","rspType":%d,"content":"Nickname set to \"%s\".","list":[]}`, - ChatRspTypeSetNickname, testChatterNickname) + ChatRspTypeSetNickname, testChatterNickname1) TestServerSetNicknameExpErr = fmt.Sprintf(`{"roomName":"","rspType":%d,"content":"Nickname cannot be blank.","list":[]}`, ChatRspTypeErrNicknameMandatory) TestServerGetNickname = fmt.Sprintf(`{"reqType":%d}`, ChatReqTypeGetNickname) TestServerGetNicknameExp = fmt.Sprintf(`{"roomName":"","rspType":%d,"content":"%s","list":[]}`, - ChatRspTypeGetNickname, testChatterNickname) + ChatRspTypeGetNickname, testChatterNickname1) TestServerListRooms = fmt.Sprintf(`{"reqType":%d}`, ChatReqTypeListRooms) TestServerListRoomsExp0 = fmt.Sprintf(`{"roomName":"","rspType":%d,"content":"","list":[]}`, @@ -66,13 +69,13 @@ var ( TestServerJoin3 = fmt.Sprintf(`{"roomName":"%s","reqType":%d}`, testChatRoomName3, ChatReqTypeJoin) TestServerJoinExp = fmt.Sprintf(`{"roomName":"%s","rspType":%d,`+ `"content":"%s has joined the room.","list":["%s"]}`, testChatRoomName1, ChatRspTypeJoin, - testChatterNickname, testChatterNickname) + testChatterNickname1, testChatterNickname1) TestServerJoinExp2 = fmt.Sprintf(`{"roomName":"%s","rspType":%d,`+ `"content":"%s has joined the room.","list":["%s"]}`, testChatRoomName2, ChatRspTypeJoin, - testChatterNickname, testChatterNickname) + testChatterNickname1, testChatterNickname1) TestServerJoinExpHidden = fmt.Sprintf(`{"roomName":"%s","rspType":%d,`+ `"content":"%s has joined the room.","list":[]}`, testChatRoomName1, ChatRspTypeJoin, - testChatterNickname) + testChatterNickname1) TestServerJoinExpErr = fmt.Sprintf(`{"roomName":"","rspType":%d,`+ `"content":"Room name is mandatory to access a room.","list":[]}`, ChatRspTypeErrRoomMandatory) @@ -81,7 +84,7 @@ var ( ChatRspTypeErrAlreadyJoined, testChatRoomName1) TestServerJoinExpErrSame = fmt.Sprintf(`{"roomName":"%s","rspType":%d,`+ `"content":"Nickname \"%s\" is already in use in room \"%s\".","list":[]}`, testChatRoomName1, - ChatRspTypeErrNicknameUsed, testChatterNickname, testChatRoomName1) + ChatRspTypeErrNicknameUsed, testChatterNickname1, testChatRoomName1) TestServerJoinExpErrRoom = fmt.Sprintf(`{"roomName":"","rspType":%d,`+ `"content":"Maximum number of rooms reached. Cannot create new room.","list":[]}`, ChatRspTypeErrMaxRoomsReached) @@ -90,7 +93,7 @@ var ( TestServerListNamesExp0 = fmt.Sprintf(`{"roomName":"%s","rspType":%d,"content":"","list":[]}`, testChatRoomName1, ChatRspTypeListNames) TestServerListNamesExp1 = fmt.Sprintf(`{"roomName":"%s","rspType":%d,"content":"","list":["%s"]}`, - testChatRoomName1, ChatRspTypeListNames, testChatterNickname) + testChatRoomName1, ChatRspTypeListNames, testChatterNickname1) TestServerHideNickname = fmt.Sprintf(`{"roomName":"%s","reqType":%d}`, testChatRoomName1, ChatReqTypeHide) TestServerHideNicknameExp = fmt.Sprintf(`{"roomName":"%s","rspType":%d,"content":"You are now hidden in room \"%s\".","list":[]}`, @@ -103,10 +106,10 @@ var ( TestServerMsg = fmt.Sprintf(`{"roomName":"%s","reqType":%d,"content":"Hello you monkeys."}`, testChatRoomName1, ChatReqTypeMsg) TestServerMsgExp = fmt.Sprintf(`{"roomName":"%s","rspType":%d,"content":"%s: Hello you monkeys.","list":[]}`, - testChatRoomName1, ChatRspTypeMsg, testChatterNickname) + testChatRoomName1, ChatRspTypeMsg, testChatterNickname1) TestServerMsgExpErrHide = fmt.Sprintf(`{"roomName":"%s","rspType":%d,"content":"Nickname \"%s\" `+ `is hidden. Cannot post in room \"%s\".","list":[]}`, - testChatRoomName1, ChatRspTypeErrHiddenNickname, testChatterNickname, testChatRoomName1) + testChatRoomName1, ChatRspTypeErrHiddenNickname, testChatterNickname1, testChatRoomName1) TestServerLeave = fmt.Sprintf(`{"roomName":"%s","reqType":%d}`, testChatRoomName1, ChatReqTypeLeave) TestServerLeaveExp = fmt.Sprintf(`{"roomName":"%s","rspType":%d,"content":"You have left room \"%s\".","list":[]}`, @@ -158,22 +161,29 @@ func TestServerValidWSSession(t *testing.T) { var rsp = make([]byte, 1024) var n int testChatterStartTime := time.Now() - ws, err := websocket.Dial(testSrvrURL, "", testSrvrOrg) + ws1, err := websocket.Dial(testSrvrURL, "", testSrvrOrg) + if err != nil { + t.Errorf("Server dialing error: %s", err) + return + } + defer ws1.Close() + + ws2, err := websocket.Dial(testSrvrURL, "", testSrvrOrg) if err != nil { t.Errorf("Server dialing error: %s", err) return } - defer ws.Close() + defer ws2.Close() // Set Nickname tTestIncrChatterStats() - if _, err := ws.Write([]byte(TestServerSetNickname)); err != nil { + if _, err := ws1.Write([]byte(TestServerSetNickname)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -186,13 +196,13 @@ func TestServerValidWSSession(t *testing.T) { // Get Nickname tTestIncrChatterStats() - if _, err := ws.Write([]byte(TestServerGetNickname)); err != nil { + if _, err := ws1.Write([]byte(TestServerGetNickname)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -202,15 +212,16 @@ func TestServerValidWSSession(t *testing.T) { if result != TestServerGetNicknameExp { t.Errorf("Get Nickname error.\nExpected: %s\n\nActual: %s\n", TestServerGetNicknameExp, result) } + // Get List of Rooms (0 rooms) tTestIncrChatterStats() - if _, err := ws.Write([]byte(TestServerListRooms)); err != nil { + if _, err := ws1.Write([]byte(TestServerListRooms)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -225,13 +236,13 @@ func TestServerValidWSSession(t *testing.T) { tTestIncrChatterStats() testRoomStartTime := time.Now() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerJoin)); err != nil { + if _, err := ws1.Write([]byte(TestServerJoin)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -244,13 +255,13 @@ func TestServerValidWSSession(t *testing.T) { // Get List of Rooms (1 room) tTestIncrChatterStats() - if _, err := ws.Write([]byte(TestServerListRooms)); err != nil { + if _, err := ws1.Write([]byte(TestServerListRooms)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -264,13 +275,13 @@ func TestServerValidWSSession(t *testing.T) { // Get list of nicknames in a room (expect 1) tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerListNames)); err != nil { + if _, err := ws1.Write([]byte(TestServerListNames)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -284,13 +295,13 @@ func TestServerValidWSSession(t *testing.T) { // Hide nickname tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerHideNickname)); err != nil { + if _, err := ws1.Write([]byte(TestServerHideNickname)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -304,13 +315,13 @@ func TestServerValidWSSession(t *testing.T) { // Validate nickname is invisible in list (expect 0) tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerListNames)); err != nil { + if _, err := ws1.Write([]byte(TestServerListNames)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -325,13 +336,13 @@ func TestServerValidWSSession(t *testing.T) { // Unhide nickname tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerUnhideNickname)); err != nil { + if _, err := ws1.Write([]byte(TestServerUnhideNickname)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -345,13 +356,13 @@ func TestServerValidWSSession(t *testing.T) { // Validate nickname is now visible in list (expect 1) tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerListNames)); err != nil { + if _, err := ws1.Write([]byte(TestServerListNames)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -366,13 +377,13 @@ func TestServerValidWSSession(t *testing.T) { // Send a message to the room. tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerMsg)); err != nil { + if _, err := ws1.Write([]byte(TestServerMsg)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -386,13 +397,13 @@ func TestServerValidWSSession(t *testing.T) { // Leave the room tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerLeave)); err != nil { + if _, err := ws1.Write([]byte(TestServerLeave)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -407,13 +418,13 @@ func TestServerValidWSSession(t *testing.T) { tTestIncrChatterStats() testRoomLastReqTime := time.Now() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerListNames)); err != nil { + if _, err := ws1.Write([]byte(TestServerListNames)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -428,13 +439,13 @@ func TestServerValidWSSession(t *testing.T) { // Join the room as hidden tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerJoinHidden)); err != nil { + if _, err := ws1.Write([]byte(TestServerJoinHidden)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -448,13 +459,13 @@ func TestServerValidWSSession(t *testing.T) { // Validate again nickname is invisible in list (expect 0) tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerListNames)); err != nil { + if _, err := ws1.Write([]byte(TestServerListNames)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -467,20 +478,15 @@ func TestServerValidWSSession(t *testing.T) { } // Leave the room - rm, _ := testSrvr.roomMngr.find(testChatRoomName1) // hold onto the chatter for later stat tests - var ch *Chatter - for k := range rm.chatters { - ch = k - } tTestIncrChatterStats() tTestIncrRoomStats() - if _, err := ws.Write([]byte(TestServerLeave)); err != nil { + if _, err := ws1.Write([]byte(TestServerLeave)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) return } } - if n, err = ws.Read(rsp); err != nil { + if n, err = ws1.Read(rsp); err != nil { if err != nil { t.Errorf("Websocket receive error: %s", err) return @@ -491,6 +497,62 @@ func TestServerValidWSSession(t *testing.T) { t.Errorf("Leave room error.\nExpected: %s\n\nActual: %s\n", TestServerLeaveExp, result) } + // Join room again for stats test. + tTestIncrChatterStats() + tTestIncrRoomStats() + if _, err := ws1.Write([]byte(TestServerJoin)); err != nil { + if err != nil { + t.Errorf("Websocket send error: %s", err) + return + } + } + if n, err = ws1.Read(rsp); err != nil { + if err != nil { + t.Errorf("Websocket receive error: %s", err) + return + } + } + result = string(rsp[:n]) + if result != TestServerJoinExp { + t.Errorf("Join room error.\nExpected: %s\n\nActual: %s\n", TestServerJoinExp, result) + } + rm, _ := testSrvr.cMngr.find(testChatRoomName1) + + var ch *Chatter + for k := range rm.chatters { + ch = k + } + + // Have chatter 2 also participate in the room for coverage. + if _, err := ws2.Write([]byte(TestServerSetNickname2)); err != nil { + if err != nil { + t.Errorf("Websocket send error: %s", err) + return + } + } + if n, err = ws2.Read(rsp); err != nil { + if err != nil { + t.Errorf("Websocket receive error: %s", err) + return + } + } + + tTestIncrRoomStats() // one for ws2 + testChatterRsps++ // but ws1 also gets a join update increment + testRoomRsps++ + if _, err := ws2.Write([]byte(TestServerJoin)); err != nil { + if err != nil { + t.Errorf("Websocket send error: %s", err) + return + } + } + if n, err = ws2.Read(rsp); err != nil { + if err != nil { + t.Errorf("Websocket receive error: %s", err) + return + } + } + // Validate Chat Room statistics from this session. s := rm.ChatRoomStatsNew() if s.Name != testChatRoomName1 { @@ -517,7 +579,7 @@ func TestServerValidWSSession(t *testing.T) { // Validate Chatter statistics for this session. cs := ch.ChatterStatsNew() - if cs.Nickname != testChatterNickname { + if cs.Nickname != testChatterNickname1 { t.Errorf("Chatter stats error. Nickname not correct.") } if cs.RemoteAddr == "" { @@ -540,10 +602,25 @@ func TestServerValidWSSession(t *testing.T) { t.Errorf("Chatter stats error. RsqCount is incorrect.\nExpected: %d\n\nActual: %d\n", testChatterRsps, cs.RspCount) } + + // Chatter 1 leaves again for coverage. + tTestIncrChatterStats() + tTestIncrRoomStats() + if _, err := ws1.Write([]byte(TestServerLeave)); err != nil { + if err != nil { + t.Errorf("Websocket send error: %s", err) + return + } + } + if n, err = ws1.Read(rsp); err != nil { + if err != nil { + t.Errorf("Websocket receive error: %s", err) + return + } + } } func TestServerWSErrorSession(t *testing.T) { - var rsp = make([]byte, 1024) var n int ws1, err := websocket.Dial(testSrvrURL, "", testSrvrOrg) @@ -710,7 +787,8 @@ func TestServerWSErrorSession(t *testing.T) { TestServerMsgExpErrHide, result) } - // Nickname already used in room should prevent joining. User 2 joins room 1 w/ same name User 1 + // Nickname already used in room should prevent joining. User 2 joins room 1 w/ same + // name User 1 if _, err := ws2.Write([]byte(TestServerJoin)); err != nil { if err != nil { t.Errorf("Websocket send error: %s", err) @@ -828,6 +906,13 @@ func TestHTTPRoutes(t *testing.T) { func TestServerTakeDown(t *testing.T) { time.Sleep(1 * time.Second) // allow all connections to leave cleanly from previous test. + ws1, err := websocket.Dial(testSrvrURL, "", testSrvrOrg) + if err != nil { + t.Errorf("Server dialing error: %s", err) + return + } + defer ws1.Close() + time.Sleep(1 * time.Second) // Keep one up. testSrvr.Shutdown() testSrvr.Shutdown() // Coverage only if testSrvr.isRunning() { diff --git a/server/usage_test.go b/server/usage_test.go deleted file mode 100644 index 69c0bcc..0000000 --- a/server/usage_test.go +++ /dev/null @@ -1,8 +0,0 @@ -package server - -import "testing" - -func TestPrintUsageAndExit(t *testing.T) { - t.Parallel() - t.Skip("Cannot test exit point.") -}