github.com/searKing/golang/go@v1.2.117/runtime/panic_test.go (about)

     1  package runtime_test
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"log"
     7  	"net/http"
     8  	"os"
     9  	"regexp"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/searKing/golang/go/runtime"
    14  )
    15  
    16  func TestPanic_Recover(t *testing.T) {
    17  	defer func() {
    18  		if x := recover(); x == nil {
    19  			t.Errorf("Expected a panic to recover from")
    20  		}
    21  	}()
    22  	defer runtime.DefaultPanic.Recover()
    23  	panic("Test Panic")
    24  }
    25  
    26  func TestPanicWith(t *testing.T) {
    27  	var result any
    28  	func() {
    29  		defer func() {
    30  			if x := recover(); x == nil {
    31  				t.Errorf("Expected a panic to recover from")
    32  			}
    33  		}()
    34  		defer runtime.HandlePanicWith(func(r any) {
    35  			result = r
    36  		}).Recover()
    37  		panic("test")
    38  	}()
    39  	if result != "test" {
    40  		t.Errorf("did not receive custom handler")
    41  	}
    42  }
    43  
    44  func TestPanic_Recover_LogPanic(t *testing.T) {
    45  	log, err := captureStderr(func() {
    46  		defer func() {
    47  			if r := recover(); r == nil {
    48  				t.Fatalf("expected a panic to recover from")
    49  			}
    50  		}()
    51  		defer runtime.LogPanic.Recover()
    52  		panic("test panic")
    53  	})
    54  	if err != nil {
    55  		t.Fatalf("%v", err)
    56  	}
    57  	// Example log:
    58  	//
    59  	// ...] Observed a panic: test panic
    60  	// goroutine 6 [running]:
    61  	// github.com/searKing/golang/go/runtime.logPanic(0x12a8b80, 0x130e590)
    62  	//	.../src/github.com/searKing/golang/go/runtime/panic.go:86 +0xda
    63  	lines := strings.Split(log, "\n")
    64  	if len(lines) < 4 {
    65  		t.Fatalf("panic log should have 1 line of message, 1 line per goroutine and 2 lines per function call")
    66  	}
    67  	if match, _ := regexp.MatchString("Observed a panic: test panic", lines[0]); !match {
    68  		t.Errorf("mismatch panic message: %s", lines[0])
    69  	}
    70  	// The following regexp's verify that Kubernetes panic log matches Golang stdlib
    71  	// stacktrace pattern. We need to update these regexp's if stdlib changes its pattern.
    72  	if match, _ := regexp.MatchString(`goroutine [0-9]+ \[.+\]:`, lines[1]); !match {
    73  		t.Errorf("mismatch goroutine: %s", lines[1])
    74  	}
    75  	if match, _ := regexp.MatchString(`logPanic(.*)`, lines[2]); !match {
    76  		t.Errorf("mismatch symbolized function name: %s", lines[2])
    77  	}
    78  	if match, _ := regexp.MatchString(`panic\.go:[0-9]+ \+0x`, lines[3]); !match {
    79  		t.Errorf("mismatch file/line/offset information: %s", lines[3])
    80  	}
    81  }
    82  
    83  func TestPanic_Recover_LogPanicSilenceHTTPErrAbortHandler(t *testing.T) {
    84  	log, err := captureStderr(func() {
    85  		defer func() {
    86  			if r := recover(); r != http.ErrAbortHandler {
    87  				t.Fatalf("expected to recover from http.ErrAbortHandler")
    88  			}
    89  		}()
    90  		defer runtime.LogPanic.Recover()
    91  		panic(http.ErrAbortHandler)
    92  	})
    93  	if err != nil {
    94  		t.Fatalf("%v", err)
    95  	}
    96  	if len(log) > 0 {
    97  		t.Fatalf("expected no stderr log, got: %s", log)
    98  	}
    99  }
   100  
   101  // captureStderr redirects stderr to result string, and then restore stderr from backup
   102  func captureStderr(f func()) (string, error) {
   103  	r, w, err := os.Pipe()
   104  	if err != nil {
   105  		return "", err
   106  	}
   107  	bak := os.Stderr
   108  	os.Stderr = w
   109  	log.SetOutput(os.Stderr)
   110  	defer func() {
   111  		os.Stderr = bak
   112  		log.SetOutput(os.Stderr)
   113  	}()
   114  
   115  	resultCh := make(chan string)
   116  	// copy the output in a separate goroutine so printing can't block indefinitely
   117  	go func() {
   118  		var buf bytes.Buffer
   119  		_, _ = io.Copy(&buf, r)
   120  		resultCh <- buf.String()
   121  	}()
   122  
   123  	f()
   124  	_ = w.Close()
   125  
   126  	return <-resultCh, nil
   127  }