mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 14:05:39 +00:00
ipn/ipnlocal: fix test flake when we log after a test completes
This switches from using an atomic.Bool to a mutex for reasons that are described in the commit, and should address the flakes that we're still seeing. Fixes #3020 Change-Id: I4e39471c0eb95886db03020ea1ccf688c7564a11 Signed-off-by: Andrew Dunham <andrew@tailscale.com>
This commit is contained in:
parent
0af57fce4c
commit
e975cb6b05
@ -17,6 +17,7 @@
|
|||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/store/mem"
|
"tailscale.com/ipn/store/mem"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/tstest"
|
||||||
"tailscale.com/types/empty"
|
"tailscale.com/types/empty"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
@ -92,7 +93,6 @@ type mockControl struct {
|
|||||||
opts controlclient.Options
|
opts controlclient.Options
|
||||||
logfActual logger.Logf
|
logfActual logger.Logf
|
||||||
statusFunc func(controlclient.Status)
|
statusFunc func(controlclient.Status)
|
||||||
preventLog atomic.Bool
|
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
calls []string
|
calls []string
|
||||||
@ -109,7 +109,7 @@ func newMockControl(tb testing.TB) *mockControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cc *mockControl) logf(format string, args ...any) {
|
func (cc *mockControl) logf(format string, args ...any) {
|
||||||
if cc.preventLog.Load() || cc.logfActual == nil {
|
if cc.logfActual == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cc.logfActual(format, args...)
|
cc.logfActual(format, args...)
|
||||||
@ -282,7 +282,7 @@ func (cc *mockControl) UpdateEndpoints(endpoints []tailcfg.Endpoint) {
|
|||||||
func TestStateMachine(t *testing.T) {
|
func TestStateMachine(t *testing.T) {
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
logf := t.Logf
|
logf := tstest.WhileTestRunningLogger(t)
|
||||||
store := new(testStateStorage)
|
store := new(testStateStorage)
|
||||||
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -297,7 +297,6 @@ func TestStateMachine(t *testing.T) {
|
|||||||
|
|
||||||
cc := newMockControl(t)
|
cc := newMockControl(t)
|
||||||
cc.statusFunc = b.setClientStatus
|
cc.statusFunc = b.setClientStatus
|
||||||
t.Cleanup(func() { cc.preventLog.Store(true) }) // hacky way to pacify issue 3020
|
|
||||||
|
|
||||||
b.SetControlClientGetterForTesting(func(opts controlclient.Options) (controlclient.Client, error) {
|
b.SetControlClientGetterForTesting(func(opts controlclient.Options) (controlclient.Client, error) {
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
@ -316,9 +315,6 @@ func TestStateMachine(t *testing.T) {
|
|||||||
notifies.expect(0)
|
notifies.expect(0)
|
||||||
|
|
||||||
b.SetNotifyCallback(func(n ipn.Notify) {
|
b.SetNotifyCallback(func(n ipn.Notify) {
|
||||||
if cc.preventLog.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if n.State != nil ||
|
if n.State != nil ||
|
||||||
n.Prefs.Valid() ||
|
n.Prefs.Valid() ||
|
||||||
n.BrowseToURL != nil ||
|
n.BrowseToURL != nil ||
|
||||||
@ -329,7 +325,6 @@ func TestStateMachine(t *testing.T) {
|
|||||||
logf("\n(ignored) %v\n\n", n)
|
logf("\n(ignored) %v\n\n", n)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Cleanup(func() { b.SetNotifyCallback(nil) }) // hacky way to pacify issue 3020
|
|
||||||
|
|
||||||
// Check that it hasn't called us right away.
|
// Check that it hasn't called us right away.
|
||||||
// The state machine should be idle until we call Start().
|
// The state machine should be idle until we call Start().
|
||||||
@ -924,7 +919,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEditPrefsHasNoKeys(t *testing.T) {
|
func TestEditPrefsHasNoKeys(t *testing.T) {
|
||||||
logf := t.Logf
|
logf := tstest.WhileTestRunningLogger(t)
|
||||||
store := new(testStateStorage)
|
store := new(testStateStorage)
|
||||||
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
e, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1006,7 +1001,7 @@ func (s *testStateStorage) sawWrite() bool {
|
|||||||
func TestWGEngineStatusRace(t *testing.T) {
|
func TestWGEngineStatusRace(t *testing.T) {
|
||||||
t.Skip("test fails")
|
t.Skip("test fails")
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
logf := t.Logf
|
logf := tstest.WhileTestRunningLogger(t)
|
||||||
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
t.Cleanup(eng.Close)
|
t.Cleanup(eng.Close)
|
||||||
|
@ -145,3 +145,50 @@ func (ml *MemLogger) String() string {
|
|||||||
defer ml.Unlock()
|
defer ml.Unlock()
|
||||||
return ml.Buffer.String()
|
return ml.Buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WhileTestRunningLogger returns a logger.Logf that logs to t.Logf until the
|
||||||
|
// test finishes, at which point it no longer logs anything.
|
||||||
|
func WhileTestRunningLogger(t testing.TB) logger.Logf {
|
||||||
|
var (
|
||||||
|
mu sync.RWMutex
|
||||||
|
done bool
|
||||||
|
)
|
||||||
|
|
||||||
|
logger := func(format string, args ...any) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
mu.RLock()
|
||||||
|
defer mu.RUnlock()
|
||||||
|
|
||||||
|
if done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Logf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Cleanup is run before the test is marked as done, so by acquiring
|
||||||
|
// the mutex and then disabling logs, we know that all existing log
|
||||||
|
// functions have completed, and that no future calls to the logger
|
||||||
|
// will log something.
|
||||||
|
//
|
||||||
|
// We can't do this with an atomic bool, since it's possible to
|
||||||
|
// observe the following race:
|
||||||
|
//
|
||||||
|
// test goroutine goroutine 1
|
||||||
|
// -------------- -----------
|
||||||
|
// check atomic, testFinished = no
|
||||||
|
// test finishes
|
||||||
|
// run t.Cleanups
|
||||||
|
// set testFinished = true
|
||||||
|
// call t.Logf
|
||||||
|
// panic
|
||||||
|
//
|
||||||
|
// Using a mutex ensures that all actions in goroutine 1 in the
|
||||||
|
// sequence above occur atomically, and thus should not panic.
|
||||||
|
t.Cleanup(func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
done = true
|
||||||
|
})
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user