...

Source file src/github.com/sourcegraph/conc/panics/panics_test.go

Documentation: github.com/sourcegraph/conc/panics

     1  package panics
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"runtime"
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  func ExampleCatcher() {
    15  	var pc Catcher
    16  	i := 0
    17  	pc.Try(func() { i += 1 })
    18  	pc.Try(func() { panic("abort!") })
    19  	pc.Try(func() { i += 1 })
    20  
    21  	rc := pc.Recovered()
    22  
    23  	fmt.Println(i)
    24  	fmt.Println(rc.Value.(string))
    25  	// Output:
    26  	// 2
    27  	// abort!
    28  }
    29  
    30  func ExampleCatcher_callers() {
    31  	var pc Catcher
    32  	pc.Try(func() { panic("mayday!") })
    33  
    34  	recovered := pc.Recovered()
    35  
    36  	// For debugging, the pre-formatted recovered.Stack is easier to use than
    37  	// rc.Callers. This is not used in the example because its output is
    38  	// machine-specific.
    39  
    40  	frames := runtime.CallersFrames(recovered.Callers)
    41  	for {
    42  		frame, more := frames.Next()
    43  
    44  		fmt.Println(frame.Function)
    45  
    46  		if !more {
    47  			break
    48  		}
    49  	}
    50  	// Output:
    51  	// github.com/sourcegraph/conc/panics.(*Catcher).tryRecover
    52  	// runtime.gopanic
    53  	// github.com/sourcegraph/conc/panics.ExampleCatcher_callers.func1
    54  	// github.com/sourcegraph/conc/panics.(*Catcher).Try
    55  	// github.com/sourcegraph/conc/panics.ExampleCatcher_callers
    56  	// testing.runExample
    57  	// testing.runExamples
    58  	// testing.(*M).Run
    59  	// main.main
    60  	// runtime.main
    61  	// runtime.goexit
    62  }
    63  
    64  func ExampleCatcher_error() {
    65  	helper := func() error {
    66  		var pc Catcher
    67  		pc.Try(func() { panic(errors.New("error")) })
    68  		return pc.Recovered().AsError()
    69  	}
    70  
    71  	if err := helper(); err != nil {
    72  		// In normal use cases, you can use err.Error() output directly to
    73  		// dump the panic's stack. This is not used in the example because
    74  		// its output is machine-specific - instead, we demonstrate getting
    75  		// the underlying error that was used for the panic.
    76  		if cause := errors.Unwrap(err); cause != nil {
    77  			fmt.Printf("helper panicked with an error: %s", cause)
    78  		}
    79  	}
    80  	// Output:
    81  	// helper panicked with an error: error
    82  }
    83  
    84  func TestCatcher(t *testing.T) {
    85  	t.Parallel()
    86  
    87  	err1 := errors.New("SOS")
    88  
    89  	t.Run("error", func(t *testing.T) {
    90  		t.Parallel()
    91  		var pc Catcher
    92  		pc.Try(func() { panic(err1) })
    93  		recovered := pc.Recovered()
    94  		require.ErrorIs(t, recovered.AsError(), err1)
    95  		require.ErrorAs(t, recovered.AsError(), &err1)
    96  		// The exact contents aren't tested because the stacktrace contains local file paths
    97  		// and even the structure of the stacktrace is bound to be unstable over time. Just
    98  		// test a couple of basics.
    99  		require.Contains(t, recovered.String(), "SOS", "formatted panic should contain the panic message")
   100  		require.Contains(t, recovered.String(), "panics.(*Catcher).Try", recovered.String(), "formatted panic should contain the stack trace")
   101  	})
   102  
   103  	t.Run("not error", func(t *testing.T) {
   104  		var pc Catcher
   105  		pc.Try(func() { panic("definitely not an error") })
   106  		recovered := pc.Recovered()
   107  		require.NotErrorIs(t, recovered.AsError(), err1)
   108  		require.Nil(t, errors.Unwrap(recovered.AsError()))
   109  	})
   110  
   111  	t.Run("repanic panics", func(t *testing.T) {
   112  		var pc Catcher
   113  		pc.Try(func() { panic(err1) })
   114  		require.Panics(t, pc.Repanic)
   115  	})
   116  
   117  	t.Run("repanic does not panic without child panic", func(t *testing.T) {
   118  		t.Parallel()
   119  		var pc Catcher
   120  		pc.Try(func() { _ = 1 })
   121  		require.NotPanics(t, pc.Repanic)
   122  	})
   123  
   124  	t.Run("is goroutine safe", func(t *testing.T) {
   125  		t.Parallel()
   126  		var wg sync.WaitGroup
   127  		var pc Catcher
   128  		for i := 0; i < 100; i++ {
   129  			i := i
   130  			wg.Add(1)
   131  			func() {
   132  				defer wg.Done()
   133  				pc.Try(func() {
   134  					if i == 50 {
   135  						panic("50")
   136  					}
   137  
   138  				})
   139  			}()
   140  		}
   141  		wg.Wait()
   142  		require.Equal(t, "50", pc.Recovered().Value)
   143  	})
   144  }
   145  
   146  func TestRecoveredAsError(t *testing.T) {
   147  	t.Parallel()
   148  	t.Run("as error is nil", func(t *testing.T) {
   149  		t.Parallel()
   150  		fn := func() error {
   151  			var c Catcher
   152  			c.Try(func() {})
   153  			return c.Recovered().AsError()
   154  		}
   155  		err := fn()
   156  		assert.Nil(t, err)
   157  	})
   158  
   159  	t.Run("as error is not nil nil", func(t *testing.T) {
   160  		t.Parallel()
   161  		fn := func() error {
   162  			var c Catcher
   163  			c.Try(func() { panic("oh dear!") })
   164  			return c.Recovered().AsError()
   165  		}
   166  		err := fn()
   167  		assert.NotNil(t, err)
   168  	})
   169  }
   170  

View as plain text