Skip to content

Commit 15dc0d6

Browse files
committed
metrics wrapper: Improve tests coverage
1 parent c4a8e17 commit 15dc0d6

File tree

2 files changed

+104
-116
lines changed

2 files changed

+104
-116
lines changed

internal/common/metrics/service_wrapper.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const (
5656
scopeNameListClosedWorkflowExecutions = CadenceMetricsPrefix + "ListClosedWorkflowExecutions"
5757
scopeNameListOpenWorkflowExecutions = CadenceMetricsPrefix + "ListOpenWorkflowExecutions"
5858
scopeNameListWorkflowExecutions = CadenceMetricsPrefix + "ListWorkflowExecutions"
59-
scopeNameListArchivedWorkflowExecutions = CadenceMetricsPrefix + "ListArchviedExecutions"
59+
scopeNameListArchivedWorkflowExecutions = CadenceMetricsPrefix + "ListArchivedWorkflowExecutions"
6060
scopeNameScanWorkflowExecutions = CadenceMetricsPrefix + "ScanWorkflowExecutions"
6161
scopeNameCountWorkflowExecutions = CadenceMetricsPrefix + "CountWorkflowExecutions"
6262
scopeNamePollForActivityTask = CadenceMetricsPrefix + "PollForActivityTask"
@@ -90,6 +90,8 @@ const (
9090
scopeNameListTaskListPartitions = CadenceMetricsPrefix + "ListTaskListPartitions"
9191
scopeNameGetClusterInfo = CadenceMetricsPrefix + "GetClusterInfo"
9292
scopeRefreshWorkflowTasks = CadenceMetricsPrefix + "RefreshWorkflowTasks"
93+
scopeNameGetTaskListsByDomain = CadenceMetricsPrefix + "GetTaskListsByDomain"
94+
scopeRestartWorkflowExecution = CadenceMetricsPrefix + "RestartWorkflowExecution"
9395
)
9496

9597
// NewWorkflowServiceWrapper creates a new wrapper to WorkflowService that will emit metrics for each service call.
@@ -414,8 +416,11 @@ func (w *workflowServiceMetricsWrapper) GetClusterInfo(ctx context.Context, opts
414416
return result, err
415417
}
416418

417-
func (w *workflowServiceMetricsWrapper) GetTaskListsByDomain(ctx context.Context, Request *shared.GetTaskListsByDomainRequest, opts ...yarpc.CallOption) (*shared.GetTaskListsByDomainResponse, error) {
418-
panic("implement me")
419+
func (w *workflowServiceMetricsWrapper) GetTaskListsByDomain(ctx context.Context, request *shared.GetTaskListsByDomainRequest, opts ...yarpc.CallOption) (*shared.GetTaskListsByDomainResponse, error) {
420+
scope := w.getOperationScope(scopeNameGetTaskListsByDomain)
421+
result, err := w.service.GetTaskListsByDomain(ctx, request, opts...)
422+
scope.handleError(err)
423+
return result, err
419424
}
420425

421426
func (w *workflowServiceMetricsWrapper) RefreshWorkflowTasks(ctx context.Context, request *shared.RefreshWorkflowTasksRequest, opts ...yarpc.CallOption) error {
@@ -426,7 +431,7 @@ func (w *workflowServiceMetricsWrapper) RefreshWorkflowTasks(ctx context.Context
426431
}
427432

428433
func (w *workflowServiceMetricsWrapper) RestartWorkflowExecution(ctx context.Context, request *shared.RestartWorkflowExecutionRequest, opts ...yarpc.CallOption) (*shared.RestartWorkflowExecutionResponse, error) {
429-
scope := w.getOperationScope(scopeRefreshWorkflowTasks)
434+
scope := w.getOperationScope(scopeRestartWorkflowExecution)
430435
resp, err := w.service.RestartWorkflowExecution(ctx, request, opts...)
431436
scope.handleError(err)
432437
return resp, err

internal/common/metrics/service_wrapper_test.go

Lines changed: 95 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ package metrics
2222

2323
import (
2424
"fmt"
25+
"github.com/stretchr/testify/assert"
26+
"go.uber.org/cadence/.gen/go/cadence/workflowservicetest"
2527
"io"
2628
"reflect"
2729
"strings"
@@ -35,7 +37,6 @@ import (
3537
"go.uber.org/yarpc"
3638

3739
"go.uber.org/cadence/.gen/go/cadence/workflowserviceclient"
38-
"go.uber.org/cadence/.gen/go/cadence/workflowservicetest"
3940
s "go.uber.org/cadence/.gen/go/shared"
4041
)
4142

@@ -60,144 +61,124 @@ var (
6061
)
6162

6263
type testCase struct {
63-
serviceMethod string
64-
callArgs []interface{}
65-
mockReturns []interface{}
64+
serviceMethod string
65+
callArgs []interface{}
66+
}
67+
68+
type errCase struct {
69+
err error
6670
expectedCounters []string
6771
}
6872

6973
func Test_Wrapper(t *testing.T) {
7074
ctx, _ := thrift.NewContext(time.Minute)
71-
tests := []testCase{
72-
// one case for each service call
73-
{"DeprecateDomain", []interface{}{ctx, &s.DeprecateDomainRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
74-
{"DescribeDomain", []interface{}{ctx, &s.DescribeDomainRequest{}}, []interface{}{&s.DescribeDomainResponse{}, nil}, []string{CadenceRequest}},
75-
{"GetWorkflowExecutionHistory", []interface{}{ctx, &s.GetWorkflowExecutionHistoryRequest{}}, []interface{}{&s.GetWorkflowExecutionHistoryResponse{}, nil}, []string{CadenceRequest}},
76-
{"ListClosedWorkflowExecutions", []interface{}{ctx, &s.ListClosedWorkflowExecutionsRequest{}}, []interface{}{&s.ListClosedWorkflowExecutionsResponse{}, nil}, []string{CadenceRequest}},
77-
{"ListOpenWorkflowExecutions", []interface{}{ctx, &s.ListOpenWorkflowExecutionsRequest{}}, []interface{}{&s.ListOpenWorkflowExecutionsResponse{}, nil}, []string{CadenceRequest}},
78-
{"PollForActivityTask", []interface{}{ctx, &s.PollForActivityTaskRequest{}}, []interface{}{&s.PollForActivityTaskResponse{}, nil}, []string{CadenceRequest}},
79-
{"PollForDecisionTask", []interface{}{ctx, &s.PollForDecisionTaskRequest{}}, []interface{}{&s.PollForDecisionTaskResponse{}, nil}, []string{CadenceRequest}},
80-
{"RecordActivityTaskHeartbeat", []interface{}{ctx, &s.RecordActivityTaskHeartbeatRequest{}}, []interface{}{&s.RecordActivityTaskHeartbeatResponse{}, nil}, []string{CadenceRequest}},
81-
{"RegisterDomain", []interface{}{ctx, &s.RegisterDomainRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
82-
{"RequestCancelWorkflowExecution", []interface{}{ctx, &s.RequestCancelWorkflowExecutionRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
83-
{"RespondActivityTaskCanceled", []interface{}{ctx, &s.RespondActivityTaskCanceledRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
84-
{"RespondActivityTaskCompleted", []interface{}{ctx, &s.RespondActivityTaskCompletedRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
85-
{"RespondActivityTaskFailed", []interface{}{ctx, &s.RespondActivityTaskFailedRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
86-
{"RespondActivityTaskCanceledByID", []interface{}{ctx, &s.RespondActivityTaskCanceledByIDRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
87-
{"RespondActivityTaskCompletedByID", []interface{}{ctx, &s.RespondActivityTaskCompletedByIDRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
88-
{"RespondActivityTaskFailedByID", []interface{}{ctx, &s.RespondActivityTaskFailedByIDRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
89-
{"RespondDecisionTaskCompleted", []interface{}{ctx, &s.RespondDecisionTaskCompletedRequest{}}, []interface{}{nil, nil}, []string{CadenceRequest}},
90-
{"SignalWorkflowExecution", []interface{}{ctx, &s.SignalWorkflowExecutionRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
91-
{"SignalWithStartWorkflowExecution", []interface{}{ctx, &s.SignalWithStartWorkflowExecutionRequest{}}, []interface{}{&s.StartWorkflowExecutionResponse{}, nil}, []string{CadenceRequest}},
92-
{"SignalWithStartWorkflowExecutionAsync", []interface{}{ctx, &s.SignalWithStartWorkflowExecutionAsyncRequest{}}, []interface{}{&s.SignalWithStartWorkflowExecutionAsyncResponse{}, nil}, []string{CadenceRequest}},
93-
{"StartWorkflowExecution", []interface{}{ctx, &s.StartWorkflowExecutionRequest{}}, []interface{}{&s.StartWorkflowExecutionResponse{}, nil}, []string{CadenceRequest}},
94-
{"StartWorkflowExecutionAsync", []interface{}{ctx, &s.StartWorkflowExecutionAsyncRequest{}}, []interface{}{&s.StartWorkflowExecutionAsyncResponse{}, nil}, []string{CadenceRequest}},
95-
{"TerminateWorkflowExecution", []interface{}{ctx, &s.TerminateWorkflowExecutionRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
96-
{"ResetWorkflowExecution", []interface{}{ctx, &s.ResetWorkflowExecutionRequest{}}, []interface{}{&s.ResetWorkflowExecutionResponse{}, nil}, []string{CadenceRequest}},
97-
{"UpdateDomain", []interface{}{ctx, &s.UpdateDomainRequest{}}, []interface{}{&s.UpdateDomainResponse{}, nil}, []string{CadenceRequest}},
98-
// one case of invalid request
99-
{"PollForActivityTask", []interface{}{ctx, &s.PollForActivityTaskRequest{}}, []interface{}{nil, &s.EntityNotExistsError{}}, []string{CadenceRequest, CadenceInvalidRequest}},
100-
// one case of server error
101-
{"PollForActivityTask", []interface{}{ctx, &s.PollForActivityTaskRequest{}}, []interface{}{nil, &s.InternalServiceError{}}, []string{CadenceRequest, CadenceError}},
102-
{"QueryWorkflow", []interface{}{ctx, &s.QueryWorkflowRequest{}}, []interface{}{nil, &s.InternalServiceError{}}, []string{CadenceRequest, CadenceError}},
103-
{"RespondQueryTaskCompleted", []interface{}{ctx, &s.RespondQueryTaskCompletedRequest{}}, []interface{}{&s.InternalServiceError{}}, []string{CadenceRequest, CadenceError}},
75+
76+
typeWrapper := reflect.TypeOf(&workflowServiceMetricsWrapper{})
77+
tests := make([]testCase, 0, typeWrapper.NumMethod())
78+
for numMethod := 0; numMethod < typeWrapper.NumMethod(); numMethod++ {
79+
method := typeWrapper.Method(numMethod)
80+
inputs := make([]interface{}, 0, method.Type.NumIn()-1)
81+
inputs = append(inputs, ctx)
82+
83+
for i := 1; i < method.Type.NumIn(); i++ {
84+
if method.Type.In(i).Kind() == reflect.Ptr {
85+
inputs = append(inputs, reflect.New(method.Type.In(i).Elem()).Interface())
86+
}
87+
}
88+
89+
tests = append(tests, testCase{
90+
serviceMethod: method.Name,
91+
callArgs: inputs,
92+
})
10493
}
10594

10695
// run each test twice - once with the regular scope, once with a sanitized metrics scope
10796
for _, test := range tests {
108-
runTest(t, test, newService, assertMetrics, fmt.Sprintf("%v_normal", test.serviceMethod))
109-
runTest(t, test, newPromService, assertPromMetrics, fmt.Sprintf("%v_prom_sanitized", test.serviceMethod))
97+
for _, errCase := range []struct {
98+
err error
99+
expectedCounters []string
100+
}{
101+
{
102+
err: nil,
103+
expectedCounters: []string{CadenceRequest},
104+
},
105+
{
106+
err: &s.EntityNotExistsError{},
107+
expectedCounters: []string{CadenceRequest, CadenceInvalidRequest},
108+
},
109+
{
110+
err: &s.InternalServiceError{},
111+
expectedCounters: []string{CadenceRequest, CadenceError},
112+
},
113+
} {
114+
runTest(t, test, errCase, newService, assertMetrics, fmt.Sprintf("%v_errcase_%v_normal", test.serviceMethod, errCase.err))
115+
runTest(t, test, errCase, newPromService, assertPromMetrics, fmt.Sprintf("%v_errcase_%v_prom_sanitized", test.serviceMethod, errCase.err))
116+
}
117+
110118
}
111119
}
112120

113121
func runTest(
114122
t *testing.T,
115123
test testCase,
124+
errCase errCase,
116125
serviceFunc func(*testing.T) (*workflowservicetest.MockClient, workflowserviceclient.Interface, io.Closer, *CapturingStatsReporter),
117126
validationFunc func(*testing.T, *CapturingStatsReporter, string, []string),
118127
name string,
119128
) {
120129
t.Run(name, func(t *testing.T) {
121-
t.Parallel()
122-
// gomock mutates the returns slice, which leads to different test values between the two runs.
123-
// copy the slice until gomock fixes it: https://github.com/golang/mock/issues/353
124-
returns := append(make([]interface{}, 0, len(test.mockReturns)), test.mockReturns...)
125-
126130
mockService, wrapperService, closer, reporter := serviceFunc(t)
127-
switch test.serviceMethod {
128-
case "DeprecateDomain":
129-
mockService.EXPECT().DeprecateDomain(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
130-
case "DescribeDomain":
131-
mockService.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
132-
case "GetWorkflowExecutionHistory":
133-
mockService.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
134-
case "ListClosedWorkflowExecutions":
135-
mockService.EXPECT().ListClosedWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
136-
case "ListOpenWorkflowExecutions":
137-
mockService.EXPECT().ListOpenWorkflowExecutions(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
138-
case "PollForActivityTask":
139-
mockService.EXPECT().PollForActivityTask(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
140-
case "PollForDecisionTask":
141-
mockService.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
142-
case "RecordActivityTaskHeartbeat":
143-
mockService.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
144-
case "RecordActivityTaskHeartbeatByID":
145-
mockService.EXPECT().RecordActivityTaskHeartbeatByID(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
146-
case "RegisterDomain":
147-
mockService.EXPECT().RegisterDomain(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
148-
case "RequestCancelWorkflowExecution":
149-
mockService.EXPECT().RequestCancelWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
150-
case "RespondActivityTaskCanceled":
151-
mockService.EXPECT().RespondActivityTaskCanceled(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
152-
case "RespondActivityTaskCompleted":
153-
mockService.EXPECT().RespondActivityTaskCompleted(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
154-
case "RespondActivityTaskFailed":
155-
mockService.EXPECT().RespondActivityTaskFailed(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
156-
case "RespondActivityTaskCanceledByID":
157-
mockService.EXPECT().RespondActivityTaskCanceledByID(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
158-
case "RespondActivityTaskCompletedByID":
159-
mockService.EXPECT().RespondActivityTaskCompletedByID(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
160-
case "RespondActivityTaskFailedByID":
161-
mockService.EXPECT().RespondActivityTaskFailedByID(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
162-
case "RespondDecisionTaskCompleted":
163-
mockService.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
164-
case "SignalWorkflowExecution":
165-
mockService.EXPECT().SignalWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
166-
case "SignalWithStartWorkflowExecution":
167-
mockService.EXPECT().SignalWithStartWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
168-
case "SignalWithStartWorkflowExecutionAsync":
169-
mockService.EXPECT().SignalWithStartWorkflowExecutionAsync(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
170-
case "StartWorkflowExecution":
171-
mockService.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
172-
case "StartWorkflowExecutionAsync":
173-
mockService.EXPECT().StartWorkflowExecutionAsync(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
174-
case "TerminateWorkflowExecution":
175-
mockService.EXPECT().TerminateWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
176-
case "ResetWorkflowExecution":
177-
mockService.EXPECT().ResetWorkflowExecution(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
178-
case "UpdateDomain":
179-
mockService.EXPECT().UpdateDomain(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
180-
case "QueryWorkflow":
181-
mockService.EXPECT().QueryWorkflow(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
182-
case "RespondQueryTaskCompleted":
183-
mockService.EXPECT().RespondQueryTaskCompleted(gomock.Any(), gomock.Any(), gomock.Any()).Return(returns...)
131+
mockServiceVal := reflect.ValueOf(mockService)
132+
method, exists := mockServiceVal.Type().MethodByName(test.serviceMethod)
133+
require.True(t, exists, "method %s does not exists", test.serviceMethod)
134+
135+
expecterVals := mockServiceVal.MethodByName("EXPECT").Call(nil)
136+
expectedMethod := expecterVals[0].MethodByName(test.serviceMethod)
137+
mockInputs := make([]reflect.Value, 0, expectedMethod.Type().NumIn())
138+
for inCounter := 0; inCounter < expectedMethod.Type().NumIn(); inCounter++ {
139+
mockInputs = append(mockInputs, reflect.ValueOf(gomock.Any()))
140+
}
141+
callVals := expectedMethod.Call(mockInputs)
142+
143+
returnVals := make([]reflect.Value, 0, method.Type.NumOut())
144+
for i := 0; i < method.Type.NumOut()-1; i++ {
145+
output := method.Type.Out(i)
146+
if errCase.err == nil {
147+
returnVals = append(returnVals, reflect.New(output).Elem())
148+
} else {
149+
returnVals = append(returnVals, reflect.Zero(output))
150+
}
184151
}
185152

153+
if errCase.err != nil {
154+
returnVals = append(returnVals, reflect.ValueOf(errCase.err))
155+
} else {
156+
returnVals = append(returnVals, nilError)
157+
}
158+
159+
callVals[0].MethodByName("Return").Call(returnVals)
160+
186161
callOption := yarpc.CallOption{}
187162
inputs := make([]reflect.Value, len(test.callArgs))
188163
for i, arg := range test.callArgs {
189164
inputs[i] = reflect.ValueOf(arg)
190165
}
191166
inputs = append(inputs, reflect.ValueOf(callOption))
192-
method := reflect.ValueOf(wrapperService).MethodByName(test.serviceMethod)
193-
method.Call(inputs)
167+
actualMethod := reflect.ValueOf(wrapperService).MethodByName(test.serviceMethod)
168+
methodReturnVals := actualMethod.Call(inputs)
169+
err := methodReturnVals[len(methodReturnVals)-1].Interface()
170+
if errCase.err != nil {
171+
assert.ErrorIs(t, err.(error), errCase.err)
172+
} else {
173+
assert.Nil(t, err, "error must be nil")
174+
}
194175
require.NoError(t, closer.Close())
195-
validationFunc(t, reporter, test.serviceMethod, test.expectedCounters)
176+
validationFunc(t, reporter, test.serviceMethod, errCase.expectedCounters)
196177
})
197178
}
198179

199180
func assertMetrics(t *testing.T, reporter *CapturingStatsReporter, methodName string, counterNames []string) {
200-
require.Equal(t, len(counterNames), len(reporter.counts))
181+
assert.Equal(t, len(counterNames), len(reporter.counts), "expected %v counters, got %v", counterNames, reporter.counts)
201182
for _, name := range counterNames {
202183
counterName := CadenceMetricsPrefix + methodName + "." + name
203184
find := false
@@ -208,14 +189,14 @@ func assertMetrics(t *testing.T, reporter *CapturingStatsReporter, methodName st
208189
break
209190
}
210191
}
211-
require.True(t, find)
192+
assert.True(t, find, "counter %v not found in counters %v", counterName, reporter.counts)
212193
}
213-
require.Equal(t, 1, len(reporter.timers))
214-
require.Equal(t, CadenceMetricsPrefix+methodName+"."+CadenceLatency, reporter.timers[0].name)
194+
assert.Equal(t, 1, len(reporter.timers), "expected 1 timer, got %v", len(reporter.timers))
195+
assert.Equal(t, CadenceMetricsPrefix+methodName+"."+CadenceLatency, reporter.timers[0].name, "expected timer %v, got %v", CadenceMetricsPrefix+methodName+"."+CadenceLatency, reporter.timers[0].name)
215196
}
216197

217198
func assertPromMetrics(t *testing.T, reporter *CapturingStatsReporter, methodName string, counterNames []string) {
218-
require.Equal(t, len(counterNames), len(reporter.counts))
199+
assert.Equal(t, len(counterNames), len(reporter.counts), "expected %v counters, got %v", counterNames, reporter.counts)
219200
for _, name := range counterNames {
220201
counterName := makePromCompatible(CadenceMetricsPrefix + methodName + "." + name)
221202
find := false
@@ -226,11 +207,11 @@ func assertPromMetrics(t *testing.T, reporter *CapturingStatsReporter, methodNam
226207
break
227208
}
228209
}
229-
require.True(t, find)
210+
assert.True(t, find, "counter %v not found in counters %v", counterName, reporter.counts)
230211
}
231-
require.Equal(t, 1, len(reporter.timers))
212+
assert.Equal(t, 1, len(reporter.timers), "expected 1 timer, got %v", len(reporter.timers))
232213
expected := makePromCompatible(CadenceMetricsPrefix + methodName + "." + CadenceLatency)
233-
require.Equal(t, expected, reporter.timers[0].name)
214+
assert.Equal(t, expected, reporter.timers[0].name, "expected timer %v, got %v", expected, reporter.timers[0].name)
234215
}
235216

236217
func makePromCompatible(name string) string {
@@ -276,3 +257,5 @@ func newPromScope(isReplay *bool) (tally.Scope, io.Closer, *CapturingStatsReport
276257
scope, closer := tally.NewRootScope(opts, time.Second)
277258
return WrapScope(isReplay, scope, &realClock{}), closer, reporter
278259
}
260+
261+
var nilError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem())

0 commit comments

Comments
 (0)