@@ -22,6 +22,8 @@ package metrics
2222
2323import (
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
6263type 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
6973func 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
113121func 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
199180func 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
217198func 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
236217func 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