Skip to content

Commit 27d2111

Browse files
authored
Fix log and grpc flakey/incomplete tests (#45)
This PR has a bunch of fixes to the tests. The env, log, and grpc packages were touched but most of the changes were in log and grpc. The log package had a bunch of env vars leaking between tests and influencing tests within the test, it was a pain to keep straight so I've fixed all of that by clearing envs and running withing `t.Run` so that `defer ` works. The grpc tests had an issue with the port used for the server tests staying around long enough between `make test` and `make coverage` where the latter would fail. The env package got the same env clearing work just to do things right.
2 parents cc071a6 + 9912f7f commit 27d2111

7 files changed

Lines changed: 284 additions & 64 deletions

File tree

env/env_test.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import (
88
"os"
99
"strconv"
1010
"time"
11+
12+
"github.com/packethost/pkg/internal/testenv"
1113
)
1214

1315
func ExampleBool() {
16+
defer testenv.Clear().Restore()
1417
name := "some_bool_environment_variable_that_is_not_set"
15-
os.Unsetenv(name)
1618

1719
fmt.Println(Bool(name))
1820
fmt.Println(Bool(name, true))
@@ -62,8 +64,9 @@ func ExampleBool() {
6264
}
6365

6466
func ExampleGet() {
67+
defer testenv.Clear().Restore()
68+
6569
name := "some_environment_variable_that_is_not_set"
66-
os.Unsetenv(name)
6770
fmt.Println(Get(name))
6871
fmt.Println(Get(name, "this is the default"))
6972
fmt.Println(Get(name, "this is the default", "this one is ignored"))
@@ -81,9 +84,9 @@ func ExampleGet() {
8184
}
8285

8386
func ExampleInt() {
84-
name := "some_int_environment_variable_that_is_not_set"
85-
os.Unsetenv(name)
87+
defer testenv.Clear().Restore()
8688

89+
name := "some_int_environment_variable_that_is_not_set"
8790
fmt.Println(Int(name))
8891
fmt.Println(Int(name, 42))
8992
fmt.Println(Int(name, 42, 21))
@@ -102,8 +105,9 @@ func ExampleInt() {
102105
}
103106

104107
func ExampleURL() {
108+
defer testenv.Clear().Restore()
109+
105110
name := "some_url_environment_variable_that_is_not_set"
106-
os.Unsetenv(name)
107111
fmt.Println(URL(name))
108112
fmt.Println(URL(name, "https://packet.com"))
109113
fmt.Println(URL(name, "https://packet.com", "https://www.equinix.com"))
@@ -123,8 +127,9 @@ func ExampleURL() {
123127
}
124128

125129
func ExampleDuration() {
130+
defer testenv.Clear().Restore()
131+
126132
name := "some_duration_environment_variable_that_is_not_set"
127-
os.Unsetenv(name)
128133
fmt.Println(Duration(name))
129134
fmt.Println(Duration(name, 15*time.Minute))
130135
fmt.Println(Duration(name, 15*time.Minute, 2*time.Hour))

grpc/grpc.go

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net"
1010
"strconv"
11+
"sync"
1112

1213
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
1314
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
@@ -22,13 +23,17 @@ import (
2223
// Server is used to hold configured info and ultimately the grpc server
2324
type Server struct {
2425
err error
25-
port int
2626
server *grpc.Server
2727
cert tls.Certificate
2828
options []grpc.ServerOption
2929
streamers []grpc.StreamServerInterceptor
3030
unariers []grpc.UnaryServerInterceptor
3131
registry []func(*grpc.Server)
32+
33+
mu sync.RWMutex
34+
port int
35+
once sync.Once
36+
listener net.Listener
3237
}
3338

3439
// The ServiceRegister type is used as a callback once the underlying grpc server is setup to register the main service.
@@ -99,6 +104,8 @@ func NewServer(l log.Logger, reg ServiceRegister, options ...Option) (*Server, e
99104

100105
// Port returns the port the server is listening on
101106
func (s *Server) Port() int {
107+
s.mu.RLock()
108+
defer s.mu.RUnlock()
102109
return s.port
103110
}
104111

@@ -107,15 +114,49 @@ func (s *Server) Server() *grpc.Server {
107114
return s.server
108115
}
109116

117+
func (s *Server) listen() error {
118+
var err error
119+
s.once.Do(func() {
120+
s.mu.RLock()
121+
port := s.port
122+
s.mu.RUnlock()
123+
124+
s.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", port)) //nolint:ineffassign // we do in fact want to assign to err in the outer scope
125+
if err != nil {
126+
err = errors.Wrap(err, "listen")
127+
return
128+
}
129+
130+
var p string
131+
_, p, err = net.SplitHostPort(s.listener.Addr().String()) //nolint:ineffassign // we do in fact want to assign to err in the outer scope
132+
if err != nil {
133+
err = errors.Wrap(err, "extract server port")
134+
return
135+
}
136+
137+
port, err = strconv.Atoi(p)
138+
if err != nil {
139+
err = errors.Wrap(err, "parse server port")
140+
return
141+
}
142+
143+
s.mu.Lock()
144+
s.port = port
145+
s.mu.Unlock()
146+
})
147+
148+
return err
149+
}
150+
110151
// Serve starts the grpc server
111152
func (s *Server) Serve() error {
112-
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.port))
153+
err := s.listen()
113154
if err != nil {
114-
return errors.Wrap(err, "listen")
155+
return err
115156
}
116-
defer lis.Close()
117157

118-
return errors.Wrap(s.server.Serve(lis), "serve")
158+
defer s.listener.Close()
159+
return errors.Wrap(s.server.Serve(s.listener), "serve")
119160
}
120161

121162
// ServerOption will add the opt param to the underlying grpc.NewServer() call.

grpc/grpc_test.go

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
"testing"
2121
"time"
2222

23+
"github.com/packethost/pkg/internal/testenv"
2324
"github.com/packethost/pkg/log"
2425
"github.com/stretchr/testify/require"
26+
assert "github.com/stretchr/testify/require"
2527
"google.golang.org/grpc"
2628
"google.golang.org/grpc/credentials"
2729
pb "google.golang.org/grpc/examples/helloworld/helloworld"
@@ -64,7 +66,7 @@ func TestDefaultsAndOrdering(t *testing.T) {
6466
s, err := NewServer(l, srv, pre1, Register(reg1), pre2, Register(reg2))
6567
assert.NoError(err)
6668
assert.NotNil(s)
67-
assert.Equal(s.port, 8080)
69+
assert.Equal(s.Port(), 8080)
6870

6971
// ensure order
7072
assert.True(pre1I > 0)
@@ -75,13 +77,15 @@ func TestDefaultsAndOrdering(t *testing.T) {
7577
}
7678

7779
func TestPort(t *testing.T) {
80+
defer testenv.Clear().Restore()
81+
7882
l := log.Test(t, svc)
7983
assert := require.New(t)
8084

8185
s, err := NewServer(l, defSrv)
8286
assert.NoError(err)
8387
assert.NotNil(s)
84-
assert.Equal(s.port, 8080)
88+
assert.Equal(s.Port(), 8080)
8589
assert.Equal(s.Port(), 8080)
8690

8791
os.Setenv("GRPC_PORT", "4242")
@@ -90,13 +94,13 @@ func TestPort(t *testing.T) {
9094
s, err = NewServer(l, defSrv)
9195
assert.NoError(err)
9296
assert.NotNil(s)
93-
assert.Equal(s.port, 4242)
97+
assert.Equal(s.Port(), 4242)
9498
assert.Equal(s.Port(), 4242)
9599

96100
s, err = NewServer(l, defSrv, Port(2424))
97101
assert.NoError(err)
98102
assert.NotNil(s)
99-
assert.Equal(s.port, 2424)
103+
assert.Equal(s.Port(), 2424)
100104
assert.Equal(s.Port(), 2424)
101105

102106
os.Setenv("GRPC_PORT", "0")
@@ -191,6 +195,8 @@ func serve(t *testing.T, s *Server, test func()) {
191195
wg.Add(1)
192196

193197
go func() {
198+
s.port = 0
199+
s.listen()
194200
wg.Done()
195201
if err := s.Serve(); err != nil {
196202
panic(err)
@@ -232,6 +238,8 @@ func connectGRPC(t *testing.T, port int, cert string) error {
232238
}
233239

234240
func TestX509(t *testing.T) {
241+
defer testenv.Clear().Restore()
242+
235243
t.Run("insecure", func(t *testing.T) {
236244
l := log.Test(t, svc)
237245
assert := require.New(t)
@@ -240,7 +248,7 @@ func TestX509(t *testing.T) {
240248
assert.NoError(err)
241249
assert.NotNil(s)
242250
serve(t, s, func() {
243-
assert.NoError(connectGRPC(t, s.port, ""))
251+
assert.NoError(connectGRPC(t, s.Port(), ""))
244252
})
245253
})
246254

@@ -257,8 +265,8 @@ func TestX509(t *testing.T) {
257265
assert.NoError(err)
258266
assert.NotNil(s)
259267
serve(t, s, func() {
260-
assert.Error(connectGRPC(t, s.port, ""))
261-
assert.NoError(connectGRPC(t, s.port, certE))
268+
assert.Error(connectGRPC(t, s.Port(), ""))
269+
assert.NoError(connectGRPC(t, s.Port(), certE))
262270
})
263271
})
264272

@@ -271,9 +279,9 @@ func TestX509(t *testing.T) {
271279
assert.NoError(err)
272280
assert.NotNil(s)
273281
serve(t, s, func() {
274-
assert.Error(connectGRPC(t, s.port, ""))
275-
assert.Error(connectGRPC(t, s.port, certE))
276-
assert.NoError(connectGRPC(t, s.port, certKP))
282+
assert.Error(connectGRPC(t, s.Port(), ""))
283+
assert.Error(connectGRPC(t, s.Port(), certE))
284+
assert.NoError(connectGRPC(t, s.Port(), certKP))
277285
})
278286
})
279287

@@ -287,10 +295,10 @@ func TestX509(t *testing.T) {
287295
assert.NoError(err)
288296
assert.NotNil(s)
289297
serve(t, s, func() {
290-
assert.Error(connectGRPC(t, s.port, ""))
291-
assert.Error(connectGRPC(t, s.port, certE))
292-
assert.Error(connectGRPC(t, s.port, certKP))
293-
assert.NoError(connectGRPC(t, s.port, certLKP))
298+
assert.Error(connectGRPC(t, s.Port(), ""))
299+
assert.Error(connectGRPC(t, s.Port(), certE))
300+
assert.Error(connectGRPC(t, s.Port(), certKP))
301+
assert.NoError(connectGRPC(t, s.Port(), certLKP))
294302
})
295303
})
296304

@@ -303,3 +311,52 @@ func TestX509(t *testing.T) {
303311
assert.Nil(s)
304312
})
305313
}
314+
315+
func TestListen(t *testing.T) {
316+
t.Run("invalid port fails", func(t *testing.T) {
317+
s := Server{port: -1}
318+
assert.Error(t, s.listen())
319+
assert.Nil(t, s.listener)
320+
})
321+
t.Run("port 0 updates .port", func(t *testing.T) {
322+
s := Server{}
323+
assert.NoError(t, s.listen())
324+
assert.NotNil(t, s.listener)
325+
defer s.listener.Close()
326+
327+
assert.NotZero(t, s.Port())
328+
})
329+
t.Run("can run 2 servers by using port 0", func(t *testing.T) {
330+
s1 := Server{}
331+
assert.NoError(t, s1.listen())
332+
assert.NotNil(t, s1.listener)
333+
defer s1.listener.Close()
334+
assert.NotZero(t, s1.port)
335+
336+
s2 := Server{}
337+
assert.NoError(t, s2.listen())
338+
assert.NotNil(t, s2.listener)
339+
defer s2.listener.Close()
340+
assert.NotZero(t, s2.port)
341+
342+
assert.NotEqual(t, s1.port, s2.port)
343+
})
344+
t.Run("Serve calls s.listen if necessary", func(t *testing.T) {
345+
l := log.Test(t, t.Name())
346+
347+
s, err := NewServer(l, defSrv)
348+
assert.NoError(t, err)
349+
assert.NotNil(t, s)
350+
351+
go func() {
352+
if err := s.Serve(); err != nil {
353+
panic(err)
354+
}
355+
}()
356+
357+
for port := s.Port(); port == 0; port = s.Port() {
358+
time.Sleep(100 * time.Microsecond)
359+
}
360+
assert.NoError(t, connectGRPC(t, s.Port(), ""))
361+
})
362+
}

internal/testenv/clearer.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Package testenv provides functionality for clearing and restoring back the environment.
2+
// This is mainly intended to be used in tests.
3+
package testenv
4+
5+
import (
6+
"os"
7+
"strings"
8+
)
9+
10+
// Restorer is the interface that wraps the Restore method.
11+
//
12+
// Restore will fully restore the environment variables to what it was
13+
// before cleared with Clear.
14+
type Restorer interface {
15+
Restore()
16+
}
17+
18+
type restorer func()
19+
20+
func (r restorer) Restore() {
21+
r()
22+
23+
}
24+
25+
// Clear will clear the environment and return a Restorer, whose
26+
// Restore function should be called to return the environment
27+
// back to what it was before Clear was called.
28+
//
29+
// A nice way to call this would be `defer Clear().Restore()`
30+
func Clear() Restorer {
31+
old := os.Environ()
32+
fn := func() {
33+
os.Clearenv()
34+
for _, e := range old {
35+
kv := strings.SplitN(e, "=", 2)
36+
os.Setenv(kv[0], kv[1])
37+
}
38+
}
39+
os.Clearenv()
40+
41+
return restorer(fn)
42+
}

0 commit comments

Comments
 (0)