// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js package wgengine import ( "runtime" "sync" "testing" "time" "tailscale.com/health" "tailscale.com/util/eventbus/eventbustest" "tailscale.com/util/usermetric" ) func TestWatchdog(t *testing.T) { t.Parallel() var maxWaitMultiple time.Duration = 1 if runtime.GOOS == "darwin" { // Work around slow close syscalls on Big Sur with content filter Network Extensions installed. // See https://github.com/tailscale/tailscale/issues/1598. maxWaitMultiple = 15 } t.Run("default watchdog does not fire", func(t *testing.T) { t.Parallel() bus := eventbustest.NewBus(t) ht := health.NewTracker(bus) reg := new(usermetric.Registry) e, err := NewFakeUserspaceEngine(t.Logf, 0, ht, reg, bus) if err != nil { t.Fatal(err) } e = NewWatchdog(e) e.(*watchdogEngine).maxWait = maxWaitMultiple * 150 * time.Millisecond e.(*watchdogEngine).logf = t.Logf e.(*watchdogEngine).fatalf = t.Fatalf e.RequestStatus() e.RequestStatus() e.RequestStatus() e.Close() }) } func TestWatchdogMetrics(t *testing.T) { tests := []struct { name string events []watchdogEvent wantCounts map[watchdogEvent]int64 }{ { name: "single event types", events: []watchdogEvent{RequestStatus, PeerForIPEvent, Ping}, wantCounts: map[watchdogEvent]int64{ RequestStatus: 1, PeerForIPEvent: 1, Ping: 1, }, }, { name: "repeated events", events: []watchdogEvent{RequestStatus, RequestStatus, Ping, RequestStatus}, wantCounts: map[watchdogEvent]int64{ RequestStatus: 3, Ping: 1, }, }, } // For swallowing fatalf calls and stack traces logf := func(format string, args ...any) {} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { clearMetrics(t) bus := eventbustest.NewBus(t) ht := health.NewTracker(bus) reg := new(usermetric.Registry) e, err := NewFakeUserspaceEngine(logf, 0, ht, reg, bus) if err != nil { t.Fatal(err) } e = NewWatchdog(e) w := e.(*watchdogEngine) w.maxWait = 1 * time.Microsecond w.logf = logf w.fatalf = logf var wg sync.WaitGroup wg.Add(len(tt.events)) for _, ev := range tt.events { blocked := make(chan struct{}) w.watchdog(ev, func() { defer wg.Done() <-blocked }) close(blocked) } wg.Wait() // Check individual event counts for ev, want := range tt.wantCounts { m, ok := watchdogMetrics[ev] if !ok { t.Fatalf("no metric found for event %q", ev) } got := m.Value() if got != want { t.Errorf("got %d metric events for %q, want %d", got, ev, want) } } // Check total count for Any m, ok := watchdogMetrics[Any] if !ok { t.Fatalf("no Any metric found") } got := m.Value() if got != int64(len(tt.events)) { t.Errorf("got %d metric events for Any, want %d", got, len(tt.events)) } }) } } func clearMetrics(t *testing.T) { t.Helper() if watchdogMetrics == nil { return } for _, m := range watchdogMetrics { m.Set(0) } }