summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/braintree/manners/server_test.go
blob: 99428421662ccf7e15c394fe3e411aad6ae6b199 (plain)
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package manners

import (
	"net"
	"net/http"
	"testing"
	"time"

	helpers "github.com/braintree/manners/test_helpers"
)

type httpInterface interface {
	ListenAndServe() error
	ListenAndServeTLS(certFile, keyFile string) error
	Serve(listener net.Listener) error
}

// Test that the method signatures of the methods we override from net/http/Server match those of the original.
func TestInterface(t *testing.T) {
	var original, ours interface{}
	original = &http.Server{}
	ours = &GracefulServer{}
	if _, ok := original.(httpInterface); !ok {
		t.Errorf("httpInterface definition does not match the canonical server!")
	}
	if _, ok := ours.(httpInterface); !ok {
		t.Errorf("GracefulServer does not implement httpInterface")
	}
}

// Tests that the server allows in-flight requests to complete
// before shutting down.
func TestGracefulness(t *testing.T) {
	server := NewServer()
	wg := helpers.NewWaitGroup()
	server.wg = wg
	statechanged := make(chan http.ConnState)
	listener, exitchan := startServer(t, server, statechanged)

	client := newClient(listener.Addr(), false)
	client.Run()

	// wait for client to connect, but don't let it send the request yet
	if err := <-client.connected; err != nil {
		t.Fatal("Client failed to connect to server", err)
	}
	// Even though the client is connected, the server ConnState handler may
	// not know about that yet. So wait until it is called.
	waitForState(t, statechanged, http.StateNew, "Request not received")

	server.Close()

	waiting := <-wg.WaitCalled
	if waiting < 1 {
		t.Errorf("Expected the waitgroup to equal 1 at shutdown; actually %d", waiting)
	}

	// allow the client to finish sending the request and make sure the server exits after
	// (client will be in connected but idle state at that point)
	client.sendrequest <- true
	close(client.sendrequest)
	if err := <-exitchan; err != nil {
		t.Error("Unexpected error during shutdown", err)
	}
}

// Tests that starting the server and closing in 2 new, separate goroutines doesnot
// get flagged by the race detector (need to run 'go test' w/the -race flag)
func TestRacyClose(t *testing.T) {
	go func() {
		ListenAndServe(":9000", nil)
	}()

	go func() {
		Close()
	}()
}

// Tests that the server begins to shut down when told to and does not accept
// new requests once shutdown has begun
func TestShutdown(t *testing.T) {
	server := NewServer()
	wg := helpers.NewWaitGroup()
	server.wg = wg
	statechanged := make(chan http.ConnState)
	listener, exitchan := startServer(t, server, statechanged)

	client1 := newClient(listener.Addr(), false)
	client1.Run()

	// wait for client1 to connect
	if err := <-client1.connected; err != nil {
		t.Fatal("Client failed to connect to server", err)
	}
	// Even though the client is connected, the server ConnState handler may
	// not know about that yet. So wait until it is called.
	waitForState(t, statechanged, http.StateNew, "Request not received")

	// start the shutdown; once it hits waitgroup.Wait()
	// the listener should of been closed, though client1 is still connected
	if server.Close() != true {
		t.Fatal("first call to Close returned false")
	}
	if server.Close() != false {
		t.Fatal("second call to Close returned true")
	}

	waiting := <-wg.WaitCalled
	if waiting != 1 {
		t.Errorf("Waitcount should be one, got %d", waiting)
	}

	// should get connection refused at this point
	client2 := newClient(listener.Addr(), false)
	client2.Run()

	if err := <-client2.connected; err == nil {
		t.Fatal("client2 connected when it should of received connection refused")
	}

	// let client1 finish so the server can exit
	close(client1.sendrequest) // don't bother sending an actual request

	<-exitchan
}

// If a request is sent to a closed server via a kept alive connection then
// the server closes the connection upon receiving the request.
func TestRequestAfterClose(t *testing.T) {
	// Given
	server := NewServer()
	srvStateChangedCh := make(chan http.ConnState, 100)
	listener, srvClosedCh := startServer(t, server, srvStateChangedCh)

	client := newClient(listener.Addr(), false)
	client.Run()
	<-client.connected
	client.sendrequest <- true
	<-client.response

	server.Close()
	if err := <-srvClosedCh; err != nil {
		t.Error("Unexpected error during shutdown", err)
	}

	// When
	client.sendrequest <- true
	rr := <-client.response

	// Then
	if rr.body != nil || rr.err != nil {
		t.Errorf("Request should be rejected, body=%v, err=%v", rr.body, rr.err)
	}
}

func waitForState(t *testing.T, waiter chan http.ConnState, state http.ConnState, errmsg string) {
	for {
		select {
		case ns := <-waiter:
			if ns == state {
				return
			}
		case <-time.After(time.Second):
			t.Fatal(errmsg)
		}
	}
}

// Test that a request moving from active->idle->active using an actual
// network connection still results in a corect shutdown
func TestStateTransitionActiveIdleActive(t *testing.T) {
	server := NewServer()
	wg := helpers.NewWaitGroup()
	statechanged := make(chan http.ConnState)
	server.wg = wg
	listener, exitchan := startServer(t, server, statechanged)

	client := newClient(listener.Addr(), false)
	client.Run()

	// wait for client to connect, but don't let it send the request
	if err := <-client.connected; err != nil {
		t.Fatal("Client failed to connect to server", err)
	}

	for i := 0; i < 2; i++ {
		client.sendrequest <- true
		waitForState(t, statechanged, http.StateActive, "Client failed to reach active state")
		<-client.response
		waitForState(t, statechanged, http.StateIdle, "Client failed to reach idle state")
	}

	// client is now in an idle state

	server.Close()
	waiting := <-wg.WaitCalled
	if waiting != 0 {
		t.Errorf("Waitcount should be zero, got %d", waiting)
	}

	if err := <-exitchan; err != nil {
		t.Error("Unexpected error during shutdown", err)
	}
}

// Test state transitions from new->active->-idle->closed using an actual
// network connection and make sure the waitgroup count is correct at the end.
func TestStateTransitionActiveIdleClosed(t *testing.T) {
	var (
		listener net.Listener
		exitchan chan error
	)

	keyFile, err1 := helpers.NewTempFile(helpers.Key)
	certFile, err2 := helpers.NewTempFile(helpers.Cert)
	defer keyFile.Unlink()
	defer certFile.Unlink()

	if err1 != nil || err2 != nil {
		t.Fatal("Failed to create temporary files", err1, err2)
	}

	for _, withTLS := range []bool{false, true} {
		server := NewServer()
		wg := helpers.NewWaitGroup()
		statechanged := make(chan http.ConnState)
		server.wg = wg
		if withTLS {
			listener, exitchan = startTLSServer(t, server, certFile.Name(), keyFile.Name(), statechanged)
		} else {
			listener, exitchan = startServer(t, server, statechanged)
		}

		client := newClient(listener.Addr(), withTLS)
		client.Run()

		// wait for client to connect, but don't let it send the request
		if err := <-client.connected; err != nil {
			t.Fatal("Client failed to connect to server", err)
		}

		client.sendrequest <- true
		waitForState(t, statechanged, http.StateActive, "Client failed to reach active state")

		rr := <-client.response
		if rr.err != nil {
			t.Fatalf("tls=%t unexpected error from client %s", withTLS, rr.err)
		}

		waitForState(t, statechanged, http.StateIdle, "Client failed to reach idle state")

		// client is now in an idle state
		close(client.sendrequest)
		<-client.closed
		waitForState(t, statechanged, http.StateClosed, "Client failed to reach closed state")

		server.Close()
		waiting := <-wg.WaitCalled
		if waiting != 0 {
			t.Errorf("Waitcount should be zero, got %d", waiting)
		}

		if err := <-exitchan; err != nil {
			t.Error("Unexpected error during shutdown", err)
		}
	}
}

func TestRoutinesCount(t *testing.T) {
	var count int
	server := NewServer()

	count = server.RoutinesCount()
	if count != 0 {
		t.Errorf("Expected the routines count to equal 0; actually %d", count)
	}

	server.StartRoutine()
	count = server.RoutinesCount()
	if count != 1 {
		t.Errorf("Expected the routines count to equal 1; actually %d", count)
	}

	server.FinishRoutine()
	count = server.RoutinesCount()
	if count != 0 {
		t.Errorf("Expected the routines count to equal 0; actually %d", count)
	}
}