Skip to content

Commit fa329fd

Browse files
authored
metrics wrapper: Improve tests coverage (#1392)
* metrics wrapper: Improve tests coverage
1 parent c4a8e17 commit fa329fd

File tree

2 files changed

+106
-116
lines changed

2 files changed

+106
-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: 97 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,17 @@ import (
2828
"testing"
2929
"time"
3030

31+
"github.com/stretchr/testify/assert"
32+
33+
"go.uber.org/cadence/.gen/go/cadence/workflowservicetest"
34+
3135
"github.com/golang/mock/gomock"
3236
"github.com/stretchr/testify/require"
3337
"github.com/uber-go/tally"
3438
"github.com/uber/tchannel-go/thrift"
3539
"go.uber.org/yarpc"
3640

3741
"go.uber.org/cadence/.gen/go/cadence/workflowserviceclient"
38-
"go.uber.org/cadence/.gen/go/cadence/workflowservicetest"
3942
s "go.uber.org/cadence/.gen/go/shared"
4043
)
4144

@@ -60,144 +63,124 @@ var (
6063
)
6164

6265
type testCase struct {
63-
serviceMethod string
64-
callArgs []interface{}
65-
mockReturns []interface{}
66+
serviceMethod string
67+
callArgs []interface{}
68+
}
69+
70+
type errCase struct {
71+
err error
6672
expectedCounters []string
6773
}
6874

6975
func Test_Wrapper(t *testing.T) {
7076
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}},
77+
78+
typeWrapper := reflect.TypeOf(&workflowServiceMetricsWrapper{})
79+
tests := make([]testCase, 0, typeWrapper.NumMethod())
80+
for numMethod := 0; numMethod < typeWrapper.NumMethod(); numMethod++ {
81+
method := typeWrapper.Method(numMethod)
82+
inputs := make([]interface{}, 0, method.Type.NumIn()-1)
83+
inputs = append(inputs, ctx)
84+
85+
for i := 1; i < method.Type.NumIn(); i++ {
86+
if method.Type.In(i).Kind() == reflect.Ptr {
87+
inputs = append(inputs, reflect.New(method.Type.In(i).Elem()).Interface())
88+
}
89+
}
90+
91+
tests = append(tests, testCase{
92+
serviceMethod: method.Name,
93+
callArgs: inputs,
94+
})
10495
}
10596

10697
// run each test twice - once with the regular scope, once with a sanitized metrics scope
10798
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))
99+
for _, errCase := range []struct {
100+
err error
101+
expectedCounters []string
102+
}{
103+
{
104+
err: nil,
105+
expectedCounters: []string{CadenceRequest},
106+
},
107+
{
108+
err: &s.EntityNotExistsError{},
109+
expectedCounters: []string{CadenceRequest, CadenceInvalidRequest},
110+
},
111+
{
112+
err: &s.InternalServiceError{},
113+
expectedCounters: []string{CadenceRequest, CadenceError},
114+
},
115+
} {
116+
runTest(t, test, errCase, newService, assertMetrics, fmt.Sprintf("%v_errcase_%v_normal", test.serviceMethod, errCase.err))
117+
runTest(t, test, errCase, newPromService, assertPromMetrics, fmt.Sprintf("%v_errcase_%v_prom_sanitized", test.serviceMethod, errCase.err))
118+
}
119+
110120
}
111121
}
112122

113123
func runTest(
114124
t *testing.T,
115125
test testCase,
126+
errCase errCase,
116127
serviceFunc func(*testing.T) (*workflowservicetest.MockClient, workflowserviceclient.Interface, io.Closer, *CapturingStatsReporter),
117128
validationFunc func(*testing.T, *CapturingStatsReporter, string, []string),
118129
name string,
119130
) {
120131
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-
126132
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...)
133+
mockServiceVal := reflect.ValueOf(mockService)
134+
method, exists := mockServiceVal.Type().MethodByName(test.serviceMethod)
135+
require.True(t, exists, "method %s does not exists", test.serviceMethod)
136+
137+
expecterVals := mockServiceVal.MethodByName("EXPECT").Call(nil)
138+
expectedMethod := expecterVals[0].MethodByName(test.serviceMethod)
139+
mockInputs := make([]reflect.Value, 0, expectedMethod.Type().NumIn())
140+
for inCounter := 0; inCounter < expectedMethod.Type().NumIn(); inCounter++ {
141+
mockInputs = append(mockInputs, reflect.ValueOf(gomock.Any()))
142+
}
143+
callVals := expectedMethod.Call(mockInputs)
144+
145+
returnVals := make([]reflect.Value, 0, method.Type.NumOut())
146+
for i := 0; i < method.Type.NumOut()-1; i++ {
147+
output := method.Type.Out(i)
148+
if errCase.err == nil {
149+
returnVals = append(returnVals, reflect.New(output).Elem())
150+
} else {
151+
returnVals = append(returnVals, reflect.Zero(output))
152+
}
184153
}
185154

155+
if errCase.err != nil {
156+
returnVals = append(returnVals, reflect.ValueOf(errCase.err))
157+
} else {
158+
returnVals = append(returnVals, nilError)
159+
}
160+
161+
callVals[0].MethodByName("Return").Call(returnVals)
162+
186163
callOption := yarpc.CallOption{}
187164
inputs := make([]reflect.Value, len(test.callArgs))
188165
for i, arg := range test.callArgs {
189166
inputs[i] = reflect.ValueOf(arg)
190167
}
191168
inputs = append(inputs, reflect.ValueOf(callOption))
192-
method := reflect.ValueOf(wrapperService).MethodByName(test.serviceMethod)
193-
method.Call(inputs)
169+
actualMethod := reflect.ValueOf(wrapperService).MethodByName(test.serviceMethod)
170+
methodReturnVals := actualMethod.Call(inputs)
171+
err := methodReturnVals[len(methodReturnVals)-1].Interface()
172+
if errCase.err != nil {
173+
assert.ErrorIs(t, err.(error), errCase.err)
174+
} else {
175+
assert.Nil(t, err, "error must be nil")
176+
}
194177
require.NoError(t, closer.Close())
195-
validationFunc(t, reporter, test.serviceMethod, test.expectedCounters)
178+
validationFunc(t, reporter, test.serviceMethod, errCase.expectedCounters)
196179
})
197180
}
198181

199182
func assertMetrics(t *testing.T, reporter *CapturingStatsReporter, methodName string, counterNames []string) {
200-
require.Equal(t, len(counterNames), len(reporter.counts))
183+
assert.Equal(t, len(counterNames), len(reporter.counts), "expected %v counters, got %v", counterNames, reporter.counts)
201184
for _, name := range counterNames {
202185
counterName := CadenceMetricsPrefix + methodName + "." + name
203186
find := false
@@ -208,14 +191,14 @@ func assertMetrics(t *testing.T, reporter *CapturingStatsReporter, methodName st
208191
break
209192
}
210193
}
211-
require.True(t, find)
194+
assert.True(t, find, "counter %v not found in counters %v", counterName, reporter.counts)
212195
}
213-
require.Equal(t, 1, len(reporter.timers))
214-
require.Equal(t, CadenceMetricsPrefix+methodName+"."+CadenceLatency, reporter.timers[0].name)
196+
assert.Equal(t, 1, len(reporter.timers), "expected 1 timer, got %v", len(reporter.timers))
197+
assert.Equal(t, CadenceMetricsPrefix+methodName+"."+CadenceLatency, reporter.timers[0].name, "expected timer %v, got %v", CadenceMetricsPrefix+methodName+"."+CadenceLatency, reporter.timers[0].name)
215198
}
216199

217200
func assertPromMetrics(t *testing.T, reporter *CapturingStatsReporter, methodName string, counterNames []string) {
218-
require.Equal(t, len(counterNames), len(reporter.counts))
201+
assert.Equal(t, len(counterNames), len(reporter.counts), "expected %v counters, got %v", counterNames, reporter.counts)
219202
for _, name := range counterNames {
220203
counterName := makePromCompatible(CadenceMetricsPrefix + methodName + "." + name)
221204
find := false
@@ -226,11 +209,11 @@ func assertPromMetrics(t *testing.T, reporter *CapturingStatsReporter, methodNam
226209
break
227210
}
228211
}
229-
require.True(t, find)
212+
assert.True(t, find, "counter %v not found in counters %v", counterName, reporter.counts)
230213
}
231-
require.Equal(t, 1, len(reporter.timers))
214+
assert.Equal(t, 1, len(reporter.timers), "expected 1 timer, got %v", len(reporter.timers))
232215
expected := makePromCompatible(CadenceMetricsPrefix + methodName + "." + CadenceLatency)
233-
require.Equal(t, expected, reporter.timers[0].name)
216+
assert.Equal(t, expected, reporter.timers[0].name, "expected timer %v, got %v", expected, reporter.timers[0].name)
234217
}
235218

236219
func makePromCompatible(name string) string {
@@ -276,3 +259,5 @@ func newPromScope(isReplay *bool) (tally.Scope, io.Closer, *CapturingStatsReport
276259
scope, closer := tally.NewRootScope(opts, time.Second)
277260
return WrapScope(isReplay, scope, &realClock{}), closer, reporter
278261
}
262+
263+
var nilError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem())

0 commit comments

Comments
 (0)