mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-09 08:01:31 +00:00
tstest: rename from testy.
Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:

committed by
Dave Anderson

parent
e1526b796e
commit
0038223632
50
tstest/clock.go
Normal file
50
tstest/clock.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tstest
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Clock is a testing clock that advances every time its Now method is
|
||||
// called, beginning at Start.
|
||||
//
|
||||
// The zero value starts virtual time at an arbitrary value recorded
|
||||
// in Start on the first call to Now, and time never advances.
|
||||
type Clock struct {
|
||||
// Start is the first value returned by Now.
|
||||
Start time.Time
|
||||
// Step is how much to advance with each Now call.
|
||||
Step time.Duration
|
||||
// Present is the time that the next Now call will receive.
|
||||
Present time.Time
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Now returns the virtual clock's current time, and avances it
|
||||
// according to its step configuration.
|
||||
func (c *Clock) Now() time.Time {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if c.Start.IsZero() {
|
||||
c.Start = time.Now()
|
||||
}
|
||||
if c.Present.Before(c.Start) {
|
||||
c.Present = c.Start
|
||||
}
|
||||
step := c.Step
|
||||
ret := c.Present
|
||||
c.Present = c.Present.Add(step)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Reset rewinds the virtual clock to its start time.
|
||||
func (c *Clock) Reset() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.Present = c.Start
|
||||
}
|
6
tstest/doc.go
Normal file
6
tstest/doc.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tstest provides utilities for use in unit tests.
|
||||
package tstest
|
30
tstest/log.go
Normal file
30
tstest/log.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tstest
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testLogWriter struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (w *testLogWriter) Write(b []byte) (int, error) {
|
||||
w.t.Helper()
|
||||
w.t.Logf("%s", b)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func FixLogs(t *testing.T) {
|
||||
log.SetFlags(log.Ltime | log.Lshortfile)
|
||||
log.SetOutput(&testLogWriter{t})
|
||||
}
|
||||
|
||||
func UnfixLogs(t *testing.T) {
|
||||
defer log.SetOutput(os.Stderr)
|
||||
}
|
72
tstest/resource.go
Normal file
72
tstest/resource.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tstest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
type ResourceCheck struct {
|
||||
startNumRoutines int
|
||||
startDump string
|
||||
}
|
||||
|
||||
func NewResourceCheck() *ResourceCheck {
|
||||
// NOTE(apenwarr): I'd rather not pre-generate a goroutine dump here.
|
||||
// However, it turns out to be tricky to debug when eg. the initial
|
||||
// goroutine count > the ending goroutine count, because of course
|
||||
// the missing ones are not in the final dump. Also, we have to
|
||||
// render the profile as a string right away, because the
|
||||
// pprof.Profile object doesn't stay stable over time. Every time
|
||||
// you render the string, you might get a different answer.
|
||||
r := &ResourceCheck{}
|
||||
r.startNumRoutines, r.startDump = goroutineDump()
|
||||
return r
|
||||
}
|
||||
|
||||
func goroutineDump() (int, string) {
|
||||
p := pprof.Lookup("goroutine")
|
||||
b := &bytes.Buffer{}
|
||||
p.WriteTo(b, 1)
|
||||
return p.Count(), b.String()
|
||||
}
|
||||
|
||||
func (r *ResourceCheck) Assert(t *testing.T) {
|
||||
t.Helper()
|
||||
want := r.startNumRoutines
|
||||
|
||||
// Some goroutines might be still exiting, so give them a chance
|
||||
got := runtime.NumGoroutine()
|
||||
if want != got {
|
||||
_, dump := goroutineDump()
|
||||
for i := 0; i < 100; i++ {
|
||||
got = runtime.NumGoroutine()
|
||||
if want == got {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
|
||||
// If the count is *still* wrong, that's a failure.
|
||||
if want != got {
|
||||
t.Logf("goroutine diff:\n%v\n", cmp.Diff(r.startDump, dump))
|
||||
t.Logf("goroutine count: expected %d, got %d\n", want, got)
|
||||
// Don't fail if there are *fewer* goroutines than
|
||||
// expected. That just might be some leftover ones
|
||||
// from the previous test, which are pretty hard to
|
||||
// eliminate.
|
||||
if want < got {
|
||||
t.Fatalf("goroutine count: expected %d, got %d\n", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Logf("Assert: goroutines before=%d after=%d - ok\n", got, want)
|
||||
}
|
Reference in New Issue
Block a user