-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathpeer_test.go
209 lines (170 loc) · 5.5 KB
/
peer_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package enet_test
import (
"fmt"
"github.com/eikarna/gotops"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"testing"
)
func TestPeerData(t *testing.T) {
testData := []byte{0x1, 0x2, 0x3}
// peer is connected to our server.
// events will produce events as the server receives them.
peer, events := createServerClient(t)
// Wait for the server to respond with a connection.
ev := <-events
if data := ev.GetPeer().GetData(); data != nil {
t.Fatalf("did not expect new peer to have data set, but has %x", data)
}
// Set some data against our peer and immediately check it's there.
ev.GetPeer().SetData(testData)
assertPeerData(t, ev.GetPeer(), testData, "immediate after set")
// Send a message to the server.
if err := peer.SendString("testmessage", 0, enet.PacketFlagReliable); err != nil {
t.Fatal(err)
}
// Wait for the server to receive this message, then check the
// server-side peer associated with this event has the data
// we set previously.
ev = <-events
assertPeerData(t, ev.GetPeer(), testData, "on packet received")
t.Run("clear-data", func(t *testing.T) {
ev.GetPeer().SetData(nil)
assertPeerData(t, ev.GetPeer(), nil, "nil set")
})
t.Run("empty-slice", func(t *testing.T) {
ev.GetPeer().SetData([]byte{})
assertPeerData(t, ev.GetPeer(), []byte{}, "empty set")
})
// Check that our data stored in C survives garbage collection.
t.Run("survives-gc", func(t *testing.T) {
ev.GetPeer().SetData([]byte{1, 2, 3})
runtime.GC()
assertPeerData(t, ev.GetPeer(), []byte{1, 2, 3}, "after GC")
})
// Sniffs for a potential memory leak in our set data implementation.
// We expect SetData to clear whatever C memory was used previously.
// This may end up being a flaky test, but will keep it in for now to
// build confidence in our implementation.
t.Run("memory-leaks", func(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skipf("skipping mem leak test as not running on a linux host")
}
noOfIncreases := 0
last := currentMemory(t)
// Assign a large string (10MB) to data over and over again, checking
// for continuous increases in mem usage, with some threshold.
for i := 0; i < 99; i++ {
ev.GetPeer().SetData([]byte(strings.Repeat("x", 1024*1024*10)))
// Detect a memory leak by checking if we're using more than 1MB last than the
// previous for too many iterations.
now := currentMemory(t)
if now-last > 1024 {
noOfIncreases++
} else {
// If it's not an increase, reset our counter.
noOfIncreases = 0
}
// If we reach a threshold of 5 continuous increases, consider this a leak.
if noOfIncreases > 5 {
t.Fatal("potential memory leak detected")
}
last = now
}
})
}
func assertPeerData(t testing.TB, peer enet.Peer, expected []byte, msg string) {
t.Helper()
actual := peer.GetData()
if (actual == nil) != (expected == nil) {
t.Fatalf("%s: expected peer data to be present? %t vs actual: %t", msg, expected != nil, actual != nil)
}
if len(actual) != len(expected) {
t.Fatalf("%s: expected peer data to have len %d vs actual %d", msg, len(expected), len(actual))
}
if string(actual) != string(expected) {
t.Fatalf("%s: expected peer data to be %v, but it was %v", msg, expected, actual)
}
}
// createServerClient creates a dummy enet server and client. The returned
// peer can be used to send messages to the server, and the blocking events
// channel returned will be given each event as the server picks it up.
func createServerClient(t *testing.T) (clientConn enet.Peer, serverEvents <-chan enet.Event) {
port := getFreePort()
done := make(chan bool, 0)
events := make(chan enet.Event)
t.Cleanup(func() {
// Kill our background service routines for client & server.
close(done)
})
// Create a server and continuously service it, exposing any captured events.
server, err := enet.NewHost(enet.NewListenAddress(port), 10, 1, 0, 0)
if err != nil {
t.Fatal(err)
}
go func() {
for true {
select {
case <-done:
return
default:
ev := server.Service(0)
// Pass any event out to our channel. This will block
// until a test consumes it.
if ev.GetType() != enet.EventNone {
events <- ev
}
}
}
}()
// Create a client and continuously service it in the background.
client, err := enet.NewHost(nil, 1, 1, 0, 0)
if err != nil {
t.Fatal(err)
}
go func() {
for true {
select {
case <-done:
return
default:
client.Service(0)
}
}
}()
// Connect to our server.
peer, err := client.Connect(enet.NewAddress("localhost", port), 1, 0)
if err != nil {
t.Fatal(err)
}
return peer, events
}
var port uint16 = 49152
// getFreePort returns a unique private port. Note this doesn't guarantee
// it's free, but should be good enough from within docker tests.
func getFreePort() uint16 {
port++
return port
}
// currentMemory returns the memory usage of the current process according to
// the OS. This uses linux's proc FS to give a rough estimate based on VmSize.
// Note we don't want to use runtime.MemStats here as we're looking for the
// total memory (including C allocations).
func currentMemory(t testing.TB) int {
// GC to give a stable measure.
runtime.GC()
pmapCmd := fmt.Sprintf("pmap %d | grep total | grep -Eo '[0-9]+'", os.Getpid())
cmd := exec.Command("bash", "-c", pmapCmd)
output, err := cmd.Output()
if err != nil {
t.Fatalf("failed to run memory check command: %s", err)
}
kb, err := strconv.Atoi(strings.TrimSpace(string(output)))
if err != nil {
t.Fatalf("failed converting memory output provided by pmap to int: %s", err)
}
return kb
}