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  }