From 8207153d6ef5082e7e1de53d5f821e18910794f1 Mon Sep 17 00:00:00 2001 From: Fred Tzeng Date: Mon, 13 Apr 2026 15:21:46 -0700 Subject: [PATCH 1/3] add protorequire.ProtoEqualIgnoreFields helper --- common/testing/protoassert/assert.go | 22 +++++++++ common/testing/protoassert/assert_test.go | 55 +++++++++++++++++++++++ common/testing/protorequire/require.go | 21 +++++++++ tests/standalone_activity_test.go | 49 ++++++++------------ 4 files changed, 117 insertions(+), 30 deletions(-) diff --git a/common/testing/protoassert/assert.go b/common/testing/protoassert/assert.go index 5de8acc84e0..3d9a83928b4 100644 --- a/common/testing/protoassert/assert.go +++ b/common/testing/protoassert/assert.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "go.temporal.io/api/temporalproto" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/testing/protocmp" ) @@ -34,6 +35,18 @@ func ProtoEqual(t assert.TestingT, a proto.Message, b proto.Message) bool { return true } +// ProtoEqualIgnoreFields compares two proto messages for equality, ignoring the specified fields +// on the given message type. Fields are specified by their proto name (snake_case). +func ProtoEqualIgnoreFields(t assert.TestingT, a proto.Message, b proto.Message, msgType proto.Message, fields ...protoreflect.Name) bool { + if th, ok := t.(helper); ok { + th.Helper() + } + if diff := cmp.Diff(a, b, protocmp.Transform(), protocmp.IgnoreFields(msgType, fields...)); diff != "" { + return assert.Fail(t, fmt.Sprintf("Proto mismatch (-want +got):\n%v", diff)) + } + return true +} + func NotProtoEqual(t assert.TestingT, a proto.Message, b proto.Message) bool { if th, ok := t.(helper); ok { th.Helper() @@ -120,3 +133,12 @@ func (x ProtoAssertions) ProtoElementsMatch(a any, b any) bool { return ProtoElementsMatch(x.t, a, b) } + +// ProtoEqualIgnoreFields compares two proto messages for equality, ignoring the specified fields +// on the given message type. Fields are specified by their proto name (snake_case). +func (x ProtoAssertions) ProtoEqualIgnoreFields(a proto.Message, b proto.Message, msgType proto.Message, fields ...protoreflect.Name) bool { + if th, ok := x.t.(helper); ok { + th.Helper() + } + return ProtoEqualIgnoreFields(x.t, a, b, msgType, fields...) +} diff --git a/common/testing/protoassert/assert_test.go b/common/testing/protoassert/assert_test.go index 1b91bba56e7..d75d00c3a31 100644 --- a/common/testing/protoassert/assert_test.go +++ b/common/testing/protoassert/assert_test.go @@ -21,6 +21,61 @@ type canHazProto struct { B *commonpb.WorkflowExecution } +func TestProtoEqualIgnoreFields(t *testing.T) { + a := &workflowpb.WorkflowExecutionInfo{ + Execution: &commonpb.WorkflowExecution{ + WorkflowId: "wf-1", + RunId: myUUID, + }, + Status: 1, + } + b := &workflowpb.WorkflowExecutionInfo{ + Execution: &commonpb.WorkflowExecution{ + WorkflowId: "wf-1", + RunId: myUUID, + }, + Status: 2, // different — will be ignored + } + + result := protoassert.ProtoEqualIgnoreFields( + t, + a, + b, + &workflowpb.WorkflowExecutionInfo{}, + "status", + ) + if !result { + t.Error("expected equality when ignoring 'status' field") + } +} + +func TestProtoEqualIgnoreFields_FailsWhenNonIgnoredFieldDiffers(t *testing.T) { + a := &workflowpb.WorkflowExecutionInfo{ + Execution: &commonpb.WorkflowExecution{ + WorkflowId: "wf-1", + RunId: myUUID, + }, + } + b := &workflowpb.WorkflowExecutionInfo{ + Execution: &commonpb.WorkflowExecution{ + WorkflowId: "wf-DIFFERENT", + RunId: myUUID, + }, + } + + mockT := &testing.T{} + result := protoassert.ProtoEqualIgnoreFields( + mockT, + a, + b, + &workflowpb.WorkflowExecutionInfo{}, + "status", + ) + if result { + t.Error("expected failure when non-ignored field differs") + } +} + func TestProtoElementsMatch(t *testing.T) { assert := protoassert.New(t) for _, tc := range []struct { diff --git a/common/testing/protorequire/require.go b/common/testing/protorequire/require.go index 5ad0286a1f6..bb356cb2243 100644 --- a/common/testing/protorequire/require.go +++ b/common/testing/protorequire/require.go @@ -4,6 +4,7 @@ import ( "github.com/stretchr/testify/require" "go.temporal.io/server/common/testing/protoassert" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" ) type helper interface { @@ -27,6 +28,17 @@ func ProtoEqual(t require.TestingT, a proto.Message, b proto.Message) { } } +// ProtoEqualIgnoreFields compares two proto messages for equality, ignoring the specified fields +// on the given message type. Calls FailNow on mismatch. +func ProtoEqualIgnoreFields(t require.TestingT, a proto.Message, b proto.Message, msgType proto.Message, fields ...protoreflect.Name) { + if th, ok := t.(helper); ok { + th.Helper() + } + if !protoassert.ProtoEqualIgnoreFields(t, a, b, msgType, fields...) { + t.FailNow() + } +} + func NotProtoEqual(t require.TestingT, a proto.Message, b proto.Message) { if th, ok := t.(helper); ok { th.Helper() @@ -83,3 +95,12 @@ func (x ProtoAssertions) ProtoElementsMatch(a any, b any) bool { return protoassert.ProtoElementsMatch(x.t, a, b) } + +func (x ProtoAssertions) ProtoEqualIgnoreFields(a proto.Message, b proto.Message, msgType proto.Message, fields ...protoreflect.Name) { + if th, ok := x.t.(helper); ok { + th.Helper() + } + if !protoassert.ProtoEqualIgnoreFields(x.t, a, b, msgType, fields...) { + x.t.FailNow() + } +} diff --git a/tests/standalone_activity_test.go b/tests/standalone_activity_test.go index e04cf84f26c..f2ef4203d44 100644 --- a/tests/standalone_activity_test.go +++ b/tests/standalone_activity_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" activitypb "go.temporal.io/api/activity/v1" @@ -29,7 +28,6 @@ import ( "go.temporal.io/server/common/testing/testvars" "go.temporal.io/server/tests/testcore" "google.golang.org/grpc/codes" - "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -2866,16 +2864,13 @@ func (s *standaloneActivityTestSuite) TestDescribeActivityExecution_NoWait() { UserMetadata: defaultUserMetadata, } - diff := cmp.Diff(expected, respInfo, - protocmp.Transform(), - // Ignore non-deterministic fields. Validated separately. - protocmp.IgnoreFields(&activitypb.ActivityExecutionInfo{}, - "execution_duration", - "schedule_time", - "state_transition_count", - ), + // Ignore non-deterministic fields. Validated separately. + protorequire.ProtoEqualIgnoreFields(t, expected, respInfo, + &activitypb.ActivityExecutionInfo{}, + "execution_duration", + "schedule_time", + "state_transition_count", ) - require.Empty(t, diff) require.Equal(t, respInfo.GetExecutionDuration().AsDuration(), time.Duration(0)) // Never completed, so expect 0 require.Nil(t, describeResp.GetInfo().GetCloseTime()) require.Positive(t, respInfo.GetScheduleTime().AsTime().Unix()) @@ -2927,16 +2922,13 @@ func (s *standaloneActivityTestSuite) TestDescribeActivityExecution_WaitAnyState Status: enumspb.ACTIVITY_EXECUTION_STATUS_RUNNING, TaskQueue: taskQueue.Name, } - diff := cmp.Diff(expected, firstDescribeResp.GetInfo(), - protocmp.Transform(), - // Ignore non-deterministic fields. Validated separately. - protocmp.IgnoreFields(&activitypb.ActivityExecutionInfo{}, - "execution_duration", - "schedule_time", - "state_transition_count", - ), + // Ignore non-deterministic fields. Validated separately. + protorequire.ProtoEqualIgnoreFields(t, expected, firstDescribeResp.GetInfo(), + &activitypb.ActivityExecutionInfo{}, + "execution_duration", + "schedule_time", + "state_transition_count", ) - require.Empty(t, diff) taskQueuePollErr := make(chan error, 1) activityPollDone := make(chan struct{}) @@ -2985,17 +2977,14 @@ func (s *standaloneActivityTestSuite) TestDescribeActivityExecution_WaitAnyState Status: enumspb.ACTIVITY_EXECUTION_STATUS_RUNNING, TaskQueue: taskQueue.Name, } - diff := cmp.Diff(expected, describeResp.GetInfo(), - protocmp.Transform(), - // Ignore non-deterministic fields. Validated separately. - protocmp.IgnoreFields(&activitypb.ActivityExecutionInfo{}, - "execution_duration", - "last_started_time", - "schedule_time", - "state_transition_count", - ), + // Ignore non-deterministic fields. Validated separately. + protorequire.ProtoEqualIgnoreFields(t, expected, describeResp.GetInfo(), + &activitypb.ActivityExecutionInfo{}, + "execution_duration", + "last_started_time", + "schedule_time", + "state_transition_count", ) - require.Empty(t, diff) protorequire.ProtoEqual(t, defaultInput, describeResp.Input) From 89e4431ef8b0687901ef45524b4dfeab46c45dfd Mon Sep 17 00:00:00 2001 From: Fred Tzeng Date: Mon, 13 Apr 2026 15:49:49 -0700 Subject: [PATCH 2/3] remove assert version --- common/testing/protoassert/assert.go | 22 --------- common/testing/protoassert/assert_test.go | 55 --------------------- common/testing/protorequire/require.go | 16 +++--- common/testing/protorequire/require_test.go | 34 +++++++++++++ 4 files changed, 43 insertions(+), 84 deletions(-) create mode 100644 common/testing/protorequire/require_test.go diff --git a/common/testing/protoassert/assert.go b/common/testing/protoassert/assert.go index 3d9a83928b4..5de8acc84e0 100644 --- a/common/testing/protoassert/assert.go +++ b/common/testing/protoassert/assert.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "go.temporal.io/api/temporalproto" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/testing/protocmp" ) @@ -35,18 +34,6 @@ func ProtoEqual(t assert.TestingT, a proto.Message, b proto.Message) bool { return true } -// ProtoEqualIgnoreFields compares two proto messages for equality, ignoring the specified fields -// on the given message type. Fields are specified by their proto name (snake_case). -func ProtoEqualIgnoreFields(t assert.TestingT, a proto.Message, b proto.Message, msgType proto.Message, fields ...protoreflect.Name) bool { - if th, ok := t.(helper); ok { - th.Helper() - } - if diff := cmp.Diff(a, b, protocmp.Transform(), protocmp.IgnoreFields(msgType, fields...)); diff != "" { - return assert.Fail(t, fmt.Sprintf("Proto mismatch (-want +got):\n%v", diff)) - } - return true -} - func NotProtoEqual(t assert.TestingT, a proto.Message, b proto.Message) bool { if th, ok := t.(helper); ok { th.Helper() @@ -133,12 +120,3 @@ func (x ProtoAssertions) ProtoElementsMatch(a any, b any) bool { return ProtoElementsMatch(x.t, a, b) } - -// ProtoEqualIgnoreFields compares two proto messages for equality, ignoring the specified fields -// on the given message type. Fields are specified by their proto name (snake_case). -func (x ProtoAssertions) ProtoEqualIgnoreFields(a proto.Message, b proto.Message, msgType proto.Message, fields ...protoreflect.Name) bool { - if th, ok := x.t.(helper); ok { - th.Helper() - } - return ProtoEqualIgnoreFields(x.t, a, b, msgType, fields...) -} diff --git a/common/testing/protoassert/assert_test.go b/common/testing/protoassert/assert_test.go index d75d00c3a31..1b91bba56e7 100644 --- a/common/testing/protoassert/assert_test.go +++ b/common/testing/protoassert/assert_test.go @@ -21,61 +21,6 @@ type canHazProto struct { B *commonpb.WorkflowExecution } -func TestProtoEqualIgnoreFields(t *testing.T) { - a := &workflowpb.WorkflowExecutionInfo{ - Execution: &commonpb.WorkflowExecution{ - WorkflowId: "wf-1", - RunId: myUUID, - }, - Status: 1, - } - b := &workflowpb.WorkflowExecutionInfo{ - Execution: &commonpb.WorkflowExecution{ - WorkflowId: "wf-1", - RunId: myUUID, - }, - Status: 2, // different — will be ignored - } - - result := protoassert.ProtoEqualIgnoreFields( - t, - a, - b, - &workflowpb.WorkflowExecutionInfo{}, - "status", - ) - if !result { - t.Error("expected equality when ignoring 'status' field") - } -} - -func TestProtoEqualIgnoreFields_FailsWhenNonIgnoredFieldDiffers(t *testing.T) { - a := &workflowpb.WorkflowExecutionInfo{ - Execution: &commonpb.WorkflowExecution{ - WorkflowId: "wf-1", - RunId: myUUID, - }, - } - b := &workflowpb.WorkflowExecutionInfo{ - Execution: &commonpb.WorkflowExecution{ - WorkflowId: "wf-DIFFERENT", - RunId: myUUID, - }, - } - - mockT := &testing.T{} - result := protoassert.ProtoEqualIgnoreFields( - mockT, - a, - b, - &workflowpb.WorkflowExecutionInfo{}, - "status", - ) - if result { - t.Error("expected failure when non-ignored field differs") - } -} - func TestProtoElementsMatch(t *testing.T) { assert := protoassert.New(t) for _, tc := range []struct { diff --git a/common/testing/protorequire/require.go b/common/testing/protorequire/require.go index bb356cb2243..f973561ea8d 100644 --- a/common/testing/protorequire/require.go +++ b/common/testing/protorequire/require.go @@ -1,10 +1,14 @@ package protorequire import ( + "fmt" + + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" "go.temporal.io/server/common/testing/protoassert" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/testing/protocmp" ) type helper interface { @@ -28,14 +32,14 @@ func ProtoEqual(t require.TestingT, a proto.Message, b proto.Message) { } } -// ProtoEqualIgnoreFields compares two proto messages for equality, ignoring the specified fields -// on the given message type. Calls FailNow on mismatch. +// ProtoEqualIgnoreFields compares two proto messages for equality, ignoring the specified fields on the given message +// type. Fields are specified by their proto name (snake_case). func ProtoEqualIgnoreFields(t require.TestingT, a proto.Message, b proto.Message, msgType proto.Message, fields ...protoreflect.Name) { if th, ok := t.(helper); ok { th.Helper() } - if !protoassert.ProtoEqualIgnoreFields(t, a, b, msgType, fields...) { - t.FailNow() + if diff := cmp.Diff(a, b, protocmp.Transform(), protocmp.IgnoreFields(msgType, fields...)); diff != "" { + require.Fail(t, fmt.Sprintf("Proto mismatch (-want +got):\n%v", diff)) } } @@ -100,7 +104,5 @@ func (x ProtoAssertions) ProtoEqualIgnoreFields(a proto.Message, b proto.Message if th, ok := x.t.(helper); ok { th.Helper() } - if !protoassert.ProtoEqualIgnoreFields(x.t, a, b, msgType, fields...) { - x.t.FailNow() - } + ProtoEqualIgnoreFields(x.t, a, b, msgType, fields...) } diff --git a/common/testing/protorequire/require_test.go b/common/testing/protorequire/require_test.go new file mode 100644 index 00000000000..e8539d9d171 --- /dev/null +++ b/common/testing/protorequire/require_test.go @@ -0,0 +1,34 @@ +package protorequire_test + +import ( + "testing" + + commonpb "go.temporal.io/api/common/v1" + workflowpb "go.temporal.io/api/workflow/v1" + "go.temporal.io/server/common/testing/protorequire" +) + +const myUUID = "deb7b204-b384-4fde-85c6-e5a56c42336a" + +func TestProtoEqualIgnoreFields(t *testing.T) { + a := &workflowpb.WorkflowExecutionInfo{ + Execution: &commonpb.WorkflowExecution{ + WorkflowId: "wf-1", + RunId: myUUID, + }, + Status: 1, + } + b := &workflowpb.WorkflowExecutionInfo{ + Execution: &commonpb.WorkflowExecution{ + WorkflowId: "wf-1", + RunId: myUUID, + }, + Status: 2, // different — will be ignored + } + + // Should pass: "status" is ignored + protorequire.ProtoEqualIgnoreFields(t, a, b, + &workflowpb.WorkflowExecutionInfo{}, + "status", + ) +} From 2c6e553f3f65accd9f8a642c052e9060ba9f2cdb Mon Sep 17 00:00:00 2001 From: Fred Tzeng Date: Mon, 13 Apr 2026 15:57:27 -0700 Subject: [PATCH 3/3] change to test multiple ignore fields --- common/testing/protorequire/require_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/common/testing/protorequire/require_test.go b/common/testing/protorequire/require_test.go index e8539d9d171..ece41854bb9 100644 --- a/common/testing/protorequire/require_test.go +++ b/common/testing/protorequire/require_test.go @@ -16,19 +16,22 @@ func TestProtoEqualIgnoreFields(t *testing.T) { WorkflowId: "wf-1", RunId: myUUID, }, - Status: 1, + Status: 1, + TaskQueue: "queue-a", } b := &workflowpb.WorkflowExecutionInfo{ Execution: &commonpb.WorkflowExecution{ WorkflowId: "wf-1", RunId: myUUID, }, - Status: 2, // different — will be ignored + Status: 2, + TaskQueue: "queue-b", } - // Should pass: "status" is ignored + // Should pass: both differing fields are ignored protorequire.ProtoEqualIgnoreFields(t, a, b, &workflowpb.WorkflowExecutionInfo{}, "status", + "task_queue", ) }