Skip to content

Commit 0379066

Browse files
Added out file in github actions and added tests for wsstore
1 parent d060bf2 commit 0379066

File tree

11 files changed

+863
-715
lines changed

11 files changed

+863
-715
lines changed

.github/workflows/go.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ jobs:
1919
- name: Install dependencies
2020
run: go mod vendor
2121
- name: Test with Go
22-
run: go test ./... -json > TestResults-${{ matrix.go-version }}.json
22+
run: go test -coverprofile cover.out ./... -json > TestResults-${{ matrix.go-version }}.json
2323
- name: Upload Go test results
2424
uses: actions/upload-artifact@v4
2525
with:
2626
name: Go-results-${{ matrix.go-version }}
27-
path: TestResults-${{ matrix.go-version }}.json
27+
path: TestResults-${{ matrix.go-version }}.jso
28+
29+
30+

cmd/app/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ func startServer() {
7676
Redis: rds,
7777
Logger: lgr,
7878
Sender: senderService,
79-
WsStore: wsstoreservice.NewChatBackendService(wsstoreservice.NewWsStoreServiceReq{
79+
WsStore: wsstoreservice.NewWSStoreService(wsstoreservice.NewWsStoreServiceReq{
8080
UserConnections: map[string]wsconnadapter.Adapter{},
81-
Lock: map[string]*sync.Mutex{},
81+
ConnLock: map[string]sync.Locker{},
8282
}),
8383
})
8484

@@ -87,9 +87,9 @@ func startServer() {
8787
Logger: lgr,
8888
})
8989

90-
wsstoreService := wsstoreservice.NewChatBackendService(wsstoreservice.NewWsStoreServiceReq{
90+
wsstoreService := wsstoreservice.NewWSStoreService(wsstoreservice.NewWsStoreServiceReq{
9191
UserConnections: make(map[string]wsconnadapter.Adapter),
92-
Lock: make(map[string]*sync.Mutex),
92+
ConnLock: make(map[string]sync.Locker),
9393
})
9494

9595
upgraderService := wsupgrader.NewWsUpgraderService(wsupgrader.NewWsUpgraderServiceReq{

cover.out

Lines changed: 0 additions & 679 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package lockeradapter
2+
3+
import _ "github.com/golang/mock/mockgen/model" //When generating mocks in reflect mode, this workaround should be used.
4+
5+
//go:generate mockgen -package=lockeradapter -destination=adapter_mock.go sync Locker
6+
//Instead of using our own adapter, we'll be using the built in sync.Locker interface

internal/pkg/core/adapter/lockeradapter/adapter_mock.go

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package wsstoreservice
2+
3+
const (
4+
ErrConnExistString = "connection with user id %s exists"
5+
)

internal/pkg/core/service/wsstoreservice/wsstore.go

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,71 +9,73 @@ import (
99

1010
type NewWsStoreServiceReq struct {
1111
UserConnections map[string]wsconnadapter.Adapter
12-
Lock map[string]*sync.Mutex
12+
ConnLock map[string]sync.Locker
13+
LockMutex sync.Locker
1314
}
1415

1516
type WsStore struct {
16-
UserConnections map[string]wsconnadapter.Adapter
17-
Lock map[string]*sync.Mutex
18-
LockMutex sync.Mutex
17+
userConnections map[string]wsconnadapter.Adapter //In memory store for the connections
18+
connLock map[string]sync.Locker //Mutex for the connections
19+
lockMutex sync.Locker //Mutex for the maps
1920
}
2021

21-
func NewChatBackendService(req NewWsStoreServiceReq) wsstoreadapter.Adapter {
22+
func NewWSStoreService(req NewWsStoreServiceReq) wsstoreadapter.Adapter {
2223
return &WsStore{
23-
UserConnections: req.UserConnections,
24-
Lock: req.Lock,
24+
userConnections: req.UserConnections,
25+
connLock: req.ConnLock,
26+
lockMutex: req.LockMutex,
2527
}
2628
}
2729

2830
// AddConn adds a user connection key value entry in the online users map provided that it is able to obtain all required locks
2931
func (c *WsStore) AddConn(conn wsconnadapter.Adapter, userID string) error {
30-
c.LockMutex.Lock()
31-
defer c.LockMutex.Unlock()
32-
if _, ok := c.Lock[userID]; !ok {
33-
c.Lock[userID] = &sync.Mutex{}
32+
c.lockMutex.Lock()
33+
defer c.lockMutex.Unlock()
34+
if _, ok := c.connLock[userID]; !ok {
35+
c.connLock[userID] = &sync.Mutex{}
3436
}
35-
c.Lock[userID].Lock()
36-
defer c.Lock[userID].Unlock()
37-
if _, ok := c.UserConnections[userID]; !ok {
38-
c.UserConnections[userID] = conn
37+
c.connLock[userID].Lock()
38+
defer c.connLock[userID].Unlock()
39+
if _, ok := c.userConnections[userID]; !ok {
40+
c.userConnections[userID] = conn
3941
return nil
4042
}
41-
return fmt.Errorf("connection with user id %s exists", userID)
43+
return fmt.Errorf(ErrConnExistString, userID)
4244
}
4345

4446
/*
4547
GetConn locks the mutex associated with a specific connection, before returning the connection itself.
4648
After use, call the Release function to unlock the mutex.
4749
*/
4850
func (c *WsStore) GetConn(userID string) wsconnadapter.Adapter {
49-
c.LockMutex.Lock()
50-
defer c.LockMutex.Unlock()
51-
if _, ok := c.Lock[userID]; !ok {
51+
c.lockMutex.Lock()
52+
defer c.lockMutex.Unlock()
53+
if _, ok := c.connLock[userID]; !ok {
5254
return nil
5355
}
54-
c.Lock[userID].Lock()
55-
if val, ok := c.UserConnections[userID]; ok {
56+
c.connLock[userID].Lock()
57+
if val, ok := c.userConnections[userID]; ok {
5658
return val
5759
}
5860
return nil
5961
}
6062

6163
// Release releases the lock for the specific connection
6264
func (c *WsStore) Release(userID string) {
63-
c.LockMutex.Lock()
64-
defer c.LockMutex.Unlock()
65-
c.Lock[userID].Unlock() // There will always be a lock with userID for unlocking since deletion only occurs if we obtain the lock.
65+
c.lockMutex.Lock()
66+
defer c.lockMutex.Unlock()
67+
c.connLock[userID].Unlock() // There will always be a lock with userID for unlocking since deletion only occurs if we obtain the lock.
6668
}
6769

6870
// DeleteConn deletes a user entry in the online users map
6971
func (c *WsStore) DeleteConn(userID string) {
70-
c.LockMutex.Lock()
71-
defer c.LockMutex.Unlock()
72+
c.lockMutex.Lock()
73+
defer c.lockMutex.Unlock()
7274
//We lock this for the final time before removing it, hence the absence of the unlock.
73-
if _, ok := c.Lock[userID]; ok {
74-
c.Lock[userID].Lock()
75+
if _, ok := c.connLock[userID]; ok {
76+
c.connLock[userID].Lock()
7577
}
7678
// Deletes are safe if key value pairs with supplied key do not exist
77-
go delete(c.UserConnections, userID)
78-
go delete(c.Lock, userID)
79+
go delete(c.userConnections, userID)
80+
go delete(c.connLock, userID)
7981
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package wsstoreservice_test
2+
3+
import (
4+
"sync"
5+
"testing"
6+
7+
"websocket_client/internal/pkg/core/adapter/lockeradapter"
8+
"websocket_client/internal/pkg/core/adapter/wsconnadapter"
9+
"websocket_client/internal/pkg/core/service/wsstoreservice"
10+
11+
"github.com/golang/mock/gomock"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestAddConn(t *testing.T) {
16+
ctrl := gomock.NewController(t)
17+
18+
mockLockMutex := lockeradapter.NewMockLocker(ctrl)
19+
mockFooMutex := lockeradapter.NewMockLocker(ctrl)
20+
mockConnLock := map[string]sync.Locker{
21+
"foo": mockFooMutex,
22+
}
23+
24+
mockConn := wsconnadapter.NewMockAdapter(ctrl)
25+
26+
tests := []struct {
27+
name string
28+
mock func()
29+
userConnections map[string]wsconnadapter.Adapter
30+
expectError bool
31+
}{
32+
{
33+
name: "Test AddConn creates and uses the mutexes properly before and after adding the new connection",
34+
mock: func() {
35+
mockLockMutex.EXPECT().Lock().Times(1)
36+
mockFooMutex.EXPECT().Lock().Times(1)
37+
mockFooMutex.EXPECT().Unlock().Times(1)
38+
mockLockMutex.EXPECT().Unlock().Times(1)
39+
},
40+
userConnections: map[string]wsconnadapter.Adapter{},
41+
},
42+
{
43+
name: "Test AddConn creates and uses the mutexes properly before and after adding the new connection, but connection exist",
44+
mock: func() {
45+
mockLockMutex.EXPECT().Lock().Times(1)
46+
mockFooMutex.EXPECT().Lock().Times(1)
47+
mockFooMutex.EXPECT().Unlock().Times(1)
48+
mockLockMutex.EXPECT().Unlock().Times(1)
49+
},
50+
userConnections: map[string]wsconnadapter.Adapter{
51+
"foo": mockConn,
52+
},
53+
expectError: true,
54+
},
55+
}
56+
57+
for _, tt := range tests {
58+
t.Run(tt.name, func(t *testing.T) {
59+
mockWsStore := wsstoreservice.NewWSStoreService(wsstoreservice.NewWsStoreServiceReq{
60+
ConnLock: mockConnLock,
61+
LockMutex: mockLockMutex,
62+
UserConnections: tt.userConnections,
63+
})
64+
65+
tt.mock()
66+
err := mockWsStore.AddConn(mockConn, "foo")
67+
if tt.expectError {
68+
assert.Error(t, err)
69+
} else {
70+
assert.NoError(t, err)
71+
}
72+
})
73+
}
74+
}
75+
76+
func TestGetConn(t *testing.T) {
77+
ctrl := gomock.NewController(t)
78+
79+
mockLockMutex := lockeradapter.NewMockLocker(ctrl)
80+
mockFooMutex := lockeradapter.NewMockLocker(ctrl)
81+
82+
mockConn := wsconnadapter.NewMockAdapter(ctrl)
83+
84+
tests := []struct {
85+
name string
86+
mock func()
87+
userConnections map[string]wsconnadapter.Adapter
88+
connLock map[string]sync.Locker
89+
expectedConn wsconnadapter.Adapter
90+
}{
91+
{
92+
name: "Test GetConn locks mutex and returns existing connection",
93+
mock: func() {
94+
mockLockMutex.EXPECT().Lock().Times(1)
95+
mockFooMutex.EXPECT().Lock().Times(1)
96+
mockLockMutex.EXPECT().Unlock().Times(1)
97+
},
98+
userConnections: map[string]wsconnadapter.Adapter{
99+
"foo": mockConn,
100+
},
101+
connLock: map[string]sync.Locker{
102+
"foo": mockFooMutex,
103+
},
104+
expectedConn: mockConn,
105+
},
106+
{
107+
name: "Test GetConn returns nil if no mutex for userID",
108+
mock: func() {
109+
mockLockMutex.EXPECT().Lock().Times(1)
110+
mockLockMutex.EXPECT().Unlock().Times(1)
111+
},
112+
userConnections: map[string]wsconnadapter.Adapter{},
113+
connLock: map[string]sync.Locker{},
114+
expectedConn: nil,
115+
},
116+
{
117+
name: "Test GetConn returns nil if no connection for userID",
118+
mock: func() {
119+
mockLockMutex.EXPECT().Lock()
120+
mockFooMutex.EXPECT().Lock()
121+
mockLockMutex.EXPECT().Unlock()
122+
},
123+
connLock: map[string]sync.Locker{
124+
"foo": mockFooMutex,
125+
},
126+
userConnections: map[string]wsconnadapter.Adapter{},
127+
expectedConn: nil,
128+
},
129+
}
130+
131+
for _, tt := range tests {
132+
t.Run(tt.name, func(t *testing.T) {
133+
mockWsStore := wsstoreservice.NewWSStoreService(wsstoreservice.NewWsStoreServiceReq{
134+
ConnLock: tt.connLock,
135+
LockMutex: mockLockMutex,
136+
UserConnections: tt.userConnections,
137+
})
138+
139+
tt.mock()
140+
conn := mockWsStore.GetConn("foo")
141+
assert.Equal(t, tt.expectedConn, conn)
142+
})
143+
}
144+
}
145+
146+
func TestRelease(t *testing.T) {
147+
ctrl := gomock.NewController(t)
148+
149+
mockLockMutex := lockeradapter.NewMockLocker(ctrl)
150+
mockFooMutex := lockeradapter.NewMockLocker(ctrl)
151+
mockLock := map[string]sync.Locker{
152+
"foo": mockFooMutex,
153+
}
154+
155+
tests := []struct {
156+
name string
157+
mock func()
158+
}{
159+
{
160+
name: "Test Release locks lockMutex and unlocks user mutex",
161+
mock: func() {
162+
mockLockMutex.EXPECT().Lock()
163+
mockFooMutex.EXPECT().Unlock()
164+
mockLockMutex.EXPECT().Unlock()
165+
},
166+
},
167+
}
168+
169+
for _, tt := range tests {
170+
t.Run(tt.name, func(t *testing.T) {
171+
mockWsStore := wsstoreservice.NewWSStoreService(wsstoreservice.NewWsStoreServiceReq{
172+
ConnLock: mockLock,
173+
LockMutex: mockLockMutex,
174+
})
175+
176+
tt.mock()
177+
mockWsStore.Release("foo")
178+
})
179+
}
180+
}

0 commit comments

Comments
 (0)