github.com/argoproj/argo-cd/v3@v3.2.1/util/guard/guard_test.go (about) 1 package guard 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 "testing" 8 ) 9 10 type nop struct{} 11 12 func (nop) Errorf(string, ...any) {} 13 14 // recorder is a thread-safe logger that captures formatted messages. 15 type recorder struct { 16 mu sync.Mutex 17 calls int 18 msgs []string 19 } 20 21 func (r *recorder) Errorf(format string, args ...any) { 22 r.mu.Lock() 23 defer r.mu.Unlock() 24 r.calls++ 25 r.msgs = append(r.msgs, fmt.Sprintf(format, args...)) 26 } 27 28 func TestRun_Recovers(_ *testing.T) { 29 RecoverAndLog(func() { panic("boom") }, nop{}, "msg") // fails if panic escapes 30 } 31 32 func TestRun_AllowsNextCall(t *testing.T) { 33 ran := false 34 RecoverAndLog(func() { panic("boom") }, nop{}, "msg") 35 RecoverAndLog(func() { ran = true }, nop{}, "msg") 36 if !ran { 37 t.Fatal("expected second callback to run") 38 } 39 } 40 41 func TestRun_LogsMessageAndStack(t *testing.T) { 42 r := &recorder{} 43 RecoverAndLog(func() { panic("boom") }, r, "msg") 44 if r.calls != 1 { 45 t.Fatalf("expected 1 log call, got %d", r.calls) 46 } 47 got := strings.Join(r.msgs, "\n") 48 if !strings.Contains(got, "msg") { 49 t.Errorf("expected log to contain message %q; got %q", "msg", got) 50 } 51 if !strings.Contains(got, "boom") { 52 t.Errorf("expected log to contain panic value %q; got %q", "boom", got) 53 } 54 // Heuristic check that a stack trace was included. 55 if !strings.Contains(got, "guard.go") && !strings.Contains(got, "runtime/panic.go") && !strings.Contains(got, "goroutine") { 56 t.Errorf("expected log to contain a stack trace; got %q", got) 57 } 58 } 59 60 func TestRun_NilLoggerDoesNotPanic(_ *testing.T) { 61 var l Logger // nil 62 RecoverAndLog(func() { panic("boom") }, l, "ignored") 63 } 64 65 func TestRun_NoPanicDoesNotLog(t *testing.T) { 66 r := &recorder{} 67 ran := false 68 RecoverAndLog(func() { ran = true }, r, "msg") 69 if !ran { 70 t.Fatal("expected fn to run") 71 } 72 if r.calls != 0 { 73 t.Fatalf("expected 0 log calls, got %d", r.calls) 74 } 75 } 76 77 func TestRun_ConcurrentPanicsLogged(t *testing.T) { 78 r := &recorder{} 79 const n = 10 80 var wg sync.WaitGroup 81 wg.Add(n) 82 for i := 0; i < n; i++ { 83 go func(i int) { 84 defer wg.Done() 85 RecoverAndLog(func() { panic(fmt.Sprintf("boom-%d", i)) }, r, "msg") 86 }(i) 87 } 88 wg.Wait() 89 if r.calls != n { 90 t.Fatalf("expected %d log calls, got %d", n, r.calls) 91 } 92 }