diff --git a/agent/consul/state/catalog_events_test.go b/agent/consul/state/catalog_events_test.go index 95d13c0df4..50d95e0134 100644 --- a/agent/consul/state/catalog_events_test.go +++ b/agent/consul/state/catalog_events_test.go @@ -4,12 +4,15 @@ import ( "fmt" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/require" + "github.com/hashicorp/consul/agent/consul/stream" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/proto/pbsubscribe" "github.com/hashicorp/consul/types" - "github.com/stretchr/testify/require" ) func TestServiceHealthEventsFromChanges(t *testing.T) { @@ -884,17 +887,36 @@ func TestServiceHealthEventsFromChanges(t *testing.T) { } require.NoError(t, err) - // Make sure we have the right events, only taking ordering into account - // where it matters to account for non-determinism. - requireEventsInCorrectPartialOrder(t, tc.WantEvents, got, func(e stream.Event) string { - // We need events affecting unique registrations to be ordered, within a topic - csn := getPayloadCheckServiceNode(e.Payload) - return fmt.Sprintf("%s/%s/%s", e.Topic, csn.Node.Node, csn.Service.Service) - }) + assertDeepEqual(t, tc.WantEvents, got, cmpPartialOrderEvents) }) } } +func assertDeepEqual(t *testing.T, x, y interface{}, opts ...cmp.Option) { + t.Helper() + if diff := cmp.Diff(x, y, opts...); diff != "" { + t.Fatalf("assertion failed: values are not equal\n--- expected\n+++ actual\n%v", diff) + } +} + +// cmpPartialOrderEvents returns a compare option which sorts events so that +// all events for a particular node/service are grouped together. The sort is +// stable so events with the same node/service retain their relative order. +var cmpPartialOrderEvents = cmp.Options{ + cmpopts.SortSlices(func(i, j stream.Event) bool { + key := func(e stream.Event) string { + csn := getPayloadCheckServiceNode(e.Payload) + return fmt.Sprintf("%s/%s/%s", e.Topic, csn.Node.Node, csn.Service.Service) + } + return key(i) < key(j) + }), + cmpEvents, +} + +var cmpEvents = cmp.Options{ + cmp.AllowUnexported(EventPayloadCheckServiceNode{}), +} + type regOption func(req *structs.RegisterRequest) error func testNodeRegistration(t *testing.T, opts ...regOption) *structs.RegisterRequest { @@ -1337,48 +1359,6 @@ func evServiceCheckDelete(e *stream.Event) error { return nil } -// requireEventsInCorrectPartialOrder compares that the expected set of events -// was emitted. It allows for _independent_ events to be emitted in any order - -// this can be important because even though the transaction processing is all -// strictly ordered up until the processing func, grouping multiple updates that -// affect the same logical entity may be necessary and may impose random -// ordering changes on the eventual events if a map is used. We only care that -// events _affecting the same topic and key_ are ordered correctly with respect -// to the "expected" set of events so this helper asserts that. -// -// The caller provides a func that can return a partition key for the given -// event types and we assert that all events with the same partition key are -// deliveries in the same order. Note that this is not necessarily the same as -// topic/key since for example in Catalog only events about a specific service -// _instance_ need to be ordered while topic and key are more general. -func requireEventsInCorrectPartialOrder(t *testing.T, want, got []stream.Event, - partKey func(stream.Event) string) { - t.Helper() - - // Partion both arrays by topic/key - wantParts := make(map[string][]stream.Event) - gotParts := make(map[string][]stream.Event) - - for _, e := range want { - k := partKey(e) - wantParts[k] = append(wantParts[k], e) - } - for _, e := range got { - k := partKey(e) - gotParts[k] = append(gotParts[k], e) - } - - for k, want := range wantParts { - require.Equal(t, want, gotParts[k], "got incorrect events for partition: %s", k) - } - - for k, got := range gotParts { - if _, ok := wantParts[k]; !ok { - require.Equal(t, nil, got, "got unwanted events for partition: %s", k) - } - } -} - // newTestEventServiceHealthRegister returns a realistically populated service // health registration event. The nodeNum is a // logical node and is used to create the node name ("node%d") but also change