Repository: grpc/grpc-go
File: internal/xds/clients/xdsclient/channel_test.go (Go)

Code before:
	}

	select {
	case name = <-ta.nameCh:
	case <-ctx.Done():
		return ResourceType{}, "", ctx.Err()
	}
	return typ, name, nil
}

type panicDecoder struct{}

func (panicDecoder) Decode(*AnyProto, DecodeOptions) (*DecodeResult, error) {
	panic("simulate panic")
}

// TestDecodeResponse_PanicRecovery tests the panic recovery mechanism in
// decodeResponse. It verifies that when XDSRecoverPanicInResourceParsing env
// variable is enabled, panics during unmarshaling are caught and returned as
// errors. When the env variable is disabled, it ensures the panic propagates.
func (s) TestDecodeResponse_PanicRecovery(t *testing.T) {
	tests := []struct {
		name          string
		enableRecover bool
		wantPanic     bool
		wantErr       string
	}{
		{
			name:          "Enable_XDSRecoverPanic",
			enableRecover: true,
			wantErr:       "panic during resourceType resource decoding: simulate panic",
		},
		{
			name:      "Disable_XDSRecoverPanic",
			wantPanic: true,
			wantErr:   "simulate panic",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			utils.SetEnvConfig(t, &envconfig.XDSRecoverPanicInResourceParsing, tt.enableRecover)

			opts := &DecodeOptions{}
			rType := &ResourceType{
				TypeName: "resourceType",
				Decoder:  panicDecoder{},
			}
			resp := response{
				resources: []*anypb.Any{{Value: []byte("test")}},
			}


Review comment (suggestion):
I don't think it is very valuable to check the exact error string being returned. Please correct me if you think otherwise.

Code after:
	}

	select {
	case name = <-ta.nameCh:
	case <-ctx.Done():
		return ResourceType{}, "", ctx.Err()
	}
	return typ, name, nil
}

type panicDecoder struct{}

func (panicDecoder) Decode(*AnyProto, DecodeOptions) (*DecodeResult, error) {
	panic("simulate panic")
}

// TestDecodeResponse_PanicRecoveryEnabled tests the panic recovery mechanism
// in decodeResponse. It verifies that if XDSRecoverPanicInResourceParsing
// env variable is enabled, panics during unmarshaling are caught and returned
// as errors.
func (s) TestDecodeResponse_PanicRecoveryEnabled(t *testing.T) {
	rType := &ResourceType{
		TypeName: "resourceType",
		Decoder:  panicDecoder{},
	}
	resp := response{resources: []*anypb.Any{{Value: []byte("test")}}}
	wantErr := "recovered from panic during resource parsing"

	if _, _, err := decodeResponse(&DecodeOptions{}, rType, resp); err == nil || !strings.Contains(err.Error(), wantErr) {
		t.Fatalf("decodeResponse() failed with err: %v, want %q", err, wantErr)
	}
}

// TestDecodeResponse_PanicRecoveryDisabled tests the panic recovery mechanism
// in decodeResponse. It verifies that when XDSRecoverPanicInResourceParsing
// env variable is disabled, panics during unmarshaling propagate.
func (s) TestDecodeResponse_PanicRecoveryDisabled(t *testing.T) {
	testutils.SetEnvConfig(t, &envconfig.XDSRecoverPanicInResourceParsing, false)
	rType := &ResourceType{
		TypeName: "resourceType",
		Decoder:  panicDecoder{},
	}
	resp := response{resources: []*anypb.Any{{Value: []byte("test")}}}
	wantErr := "simulate panic"

	defer func() {
		if r := recover(); r == nil || !strings.Contains(fmt.Sprint(r), wantErr) {
			t.Fatalf("Expected panic in decodeResponse, got: %v, want: %q", r, wantErr)
		}
	}()
	decodeResponse(&DecodeOptions{}, rType, resp)