diff --git a/go.mod b/go.mod index 8367b78c432..a29fde8574a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.1 // indirect github.com/cloudevents/sdk-go v1.2.0 - github.com/cloudevents/sdk-go/v2 v2.0.0 + github.com/cloudevents/sdk-go/v2 v2.0.1-0.20200608152019-2ab697c8fc0b github.com/ghodss/yaml v1.0.0 github.com/golang/protobuf v1.3.5 github.com/google/go-cmp v0.4.0 diff --git a/go.sum b/go.sum index 7c071ea6c2a..b229a2cf562 100644 --- a/go.sum +++ b/go.sum @@ -200,8 +200,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudevents/sdk-go v0.0.0-20190509003705-56931988abe3/go.mod h1:j1nZWMLGg3om8SswStBoY6/SHvcLM19MuZqwDtMtmzs= github.com/cloudevents/sdk-go v1.2.0 h1:2AxI14EJUw1PclJ5gZJtzbxnHIfNMdi76Qq3P3G1BRU= github.com/cloudevents/sdk-go v1.2.0/go.mod h1:ss+jWJ88wypiewnPEzChSBzTYXGpdcILoN9YHk8uhTQ= -github.com/cloudevents/sdk-go/v2 v2.0.0 h1:AUdGJwaSUnA+VvepKqgjy6XDkPcf0hf/3L7icEs1ibs= -github.com/cloudevents/sdk-go/v2 v2.0.0/go.mod h1:3CTrpB4+u7Iaj6fd7E2Xvm5IxMdRoaAhqaRVnOr2rCU= +github.com/cloudevents/sdk-go/v2 v2.0.1-0.20200608152019-2ab697c8fc0b h1:JuajVVvOdNSXhVlI5Aks9m4+y5i4rM8EOaLhIytotYc= +github.com/cloudevents/sdk-go/v2 v2.0.1-0.20200608152019-2ab697c8fc0b/go.mod h1:3CTrpB4+u7Iaj6fd7E2Xvm5IxMdRoaAhqaRVnOr2rCU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= diff --git a/test/conformance/helpers/broker_tracing_test_helper.go b/test/conformance/helpers/broker_tracing_test_helper.go index ac090125df8..f034c3ce576 100644 --- a/test/conformance/helpers/broker_tracing_test_helper.go +++ b/test/conformance/helpers/broker_tracing_test_helper.go @@ -23,6 +23,7 @@ import ( ce "github.com/cloudevents/sdk-go" ce2 "github.com/cloudevents/sdk-go/v2" + cetest "github.com/cloudevents/sdk-go/v2/test" "github.com/openzipkin/zipkin-go/model" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/uuid" @@ -82,7 +83,7 @@ func setupBrokerTracing(brokerClass string) SetupInfrastructureFunc { client *lib.Client, loggerPodName string, tc TracingTestCase, - ) (tracinghelper.TestSpanTree, lib.EventMatchFunc) { + ) (tracinghelper.TestSpanTree, cetest.EventMatcher) { // Create a configmap used by the broker. client.CreateBrokerConfigMapOrFail("br", channel) diff --git a/test/conformance/helpers/channel_tracing_test_helper.go b/test/conformance/helpers/channel_tracing_test_helper.go index aef6cb9bb43..b27e55f1704 100644 --- a/test/conformance/helpers/channel_tracing_test_helper.go +++ b/test/conformance/helpers/channel_tracing_test_helper.go @@ -26,6 +26,7 @@ import ( ce "github.com/cloudevents/sdk-go" ce2 "github.com/cloudevents/sdk-go/v2" + cetest "github.com/cloudevents/sdk-go/v2/test" "github.com/openzipkin/zipkin-go/model" "go.opentelemetry.io/otel/api/trace" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -46,7 +47,7 @@ type SetupInfrastructureFunc func( client *lib.Client, loggerPodName string, tc TracingTestCase, -) (tracinghelper.TestSpanTree, lib.EventMatchFunc) +) (tracinghelper.TestSpanTree, cetest.EventMatcher) // TracingTestCase is the test case information for tracing tests. type TracingTestCase struct { @@ -131,13 +132,13 @@ func tracingTest( // matches mustMatch. It is used to show that the expected event was sent to // the logger Pod. It returns a list of the matching events. func assertEventMatch(t *testing.T, client *lib.Client, recorderPodName string, - mustMatch lib.EventMatchFunc) []lib.EventInfo { + mustMatch cetest.EventMatcher) []lib.EventInfo { targetTracker, err := client.NewEventInfoStore(recorderPodName, t.Logf) if err != nil { t.Fatalf("Pod tracker failed: %v", err) } defer targetTracker.Cleanup() - matches, err := targetTracker.WaitAtLeastNMatch(lib.ValidEvFunc(mustMatch), 1) + matches, err := targetTracker.WaitAtLeastNMatch(lib.MatchEvent(mustMatch), 1) if err != nil { t.Fatalf("Expected messages not found: %v", err) } @@ -172,7 +173,7 @@ func setupChannelTracingWithReply( client *lib.Client, loggerPodName string, tc TracingTestCase, -) (tracinghelper.TestSpanTree, lib.EventMatchFunc) { +) (tracinghelper.TestSpanTree, cetest.EventMatcher) { // Create the Channels. channelName := "ch" client.CreateChannelOrFail(channelName, channel) diff --git a/test/e2e/helpers/broker_test_helper.go b/test/e2e/helpers/broker_test_helper.go index 2e1f728047a..b036f29226f 100644 --- a/test/e2e/helpers/broker_test_helper.go +++ b/test/e2e/helpers/broker_test_helper.go @@ -18,14 +18,20 @@ package helpers import ( "fmt" + "net/url" "sort" "strings" "testing" + cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/cloudevents/sdk-go/v2/binding/spec" + cetest "github.com/cloudevents/sdk-go/v2/test" + "github.com/google/uuid" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/eventing/v1beta1" "knative.dev/eventing/test/lib" - "knative.dev/eventing/test/lib/cloudevents" "knative.dev/eventing/test/lib/resources" ) @@ -33,8 +39,8 @@ const ( any = v1beta1.TriggerAnyFilter eventType1 = "type1" eventType2 = "type2" - eventSource1 = "source1" - eventSource2 = "source2" + eventSource1 = "http://source1.com" + eventSource2 = "http://source2.com" // Be careful with the length of extension name and values, // we use extension name and value as a part of the name of resources like subscriber and trigger, // the maximum characters allowed of resource name is 63 @@ -46,16 +52,58 @@ const ( nonMatchingExtensionValue = "nonmatchingextval" ) -type eventContext struct { +type eventTestCase struct { Type string Source string Extensions map[string]interface{} } -// Helper struct to tie the type and sources of the events we expect to receive -// in subscribers with the selectors we use when creating their pods. -type eventReceiver struct { - context eventContext +// ToString converts the test case to a string to create names for different objects (e.g., triggers, services, etc.). +func (tc eventTestCase) String() string { + eventType := tc.Type + eventSource := tc.Source + extensions := tc.Extensions + // Pod names need to be lowercase. We might have an eventType as Any, that is why we lowercase them. + if eventType == any { + eventType = "testany" + } + if eventSource == any { + eventSource = "testany" + } else { + u, _ := url.Parse(eventSource) + eventSource = strings.Split(u.Host, ".")[0] + } + name := strings.ToLower(fmt.Sprintf("%s-%s", eventType, eventSource)) + if len(extensions) > 0 { + name = strings.ToLower(fmt.Sprintf("%s-%s", name, extensionsToString(extensions))) + } + return name +} + +// ToEventMatcher converts the test case to the event matcher +func (tc eventTestCase) ToEventMatcher() cetest.EventMatcher { + var matchers []cetest.EventMatcher + if tc.Type == any { + matchers = append(matchers, cetest.ContainsAttributes(spec.Type)) + } else { + matchers = append(matchers, cetest.HasType(tc.Type)) + } + + if tc.Source == any { + matchers = append(matchers, cetest.ContainsAttributes(spec.Source)) + } else { + matchers = append(matchers, cetest.HasSource(tc.Source)) + } + + for k, v := range tc.Extensions { + if v == any { + matchers = append(matchers, cetest.ContainsExtensions(k)) + } else { + matchers = append(matchers, cetest.HasExtension(k, v)) + } + } + + return cetest.AllOf(matchers...) } // BrokerCreator creates a broker and returns its broker name. @@ -87,11 +135,12 @@ func ChannelBasedBrokerCreator(channel metav1.TypeMeta, brokerClass string) Brok func TestBrokerWithManyTriggers(t *testing.T, brokerCreator BrokerCreator, shouldLabelNamespace bool) { tests := []struct { name string + // These are the event context attributes and extension attributes that will be send. + eventsToSend []eventTestCase // These are the event context attributes and extension attributes that triggers will listen to, // to set in the subscriber and services pod - eventsToReceive []eventReceiver - // These are the event context attributes and extension attributes that will be send. - eventsToSend []eventContext + // The attributes in these test cases will be used as assertions on the receivers + eventFilters []eventTestCase //TriggerFilter with DeprecatedSourceAndType or not deprecatedTriggerFilter bool // Use v1beta1 trigger @@ -99,62 +148,53 @@ func TestBrokerWithManyTriggers(t *testing.T, brokerCreator BrokerCreator, shoul }{ { name: "test default broker with many deprecated triggers", - eventsToReceive: []eventReceiver{ - {eventContext{Type: any, Source: any}}, - {eventContext{Type: eventType1, Source: any}}, - {eventContext{Type: any, Source: eventSource1}}, - {eventContext{Type: eventType1, Source: eventSource1}}, - }, - eventsToSend: []eventContext{ + eventsToSend: []eventTestCase{ {Type: eventType1, Source: eventSource1}, {Type: eventType1, Source: eventSource2}, {Type: eventType2, Source: eventSource1}, {Type: eventType2, Source: eventSource2}, }, + eventFilters: []eventTestCase{ + {Type: any, Source: any}, + {Type: eventType1, Source: any}, + {Type: any, Source: eventSource1}, + {Type: eventType1, Source: eventSource1}, + }, deprecatedTriggerFilter: true, }, { name: "test default broker with many attribute triggers", - eventsToReceive: []eventReceiver{ - {eventContext{Type: any, Source: any}}, - {eventContext{Type: eventType1, Source: any}}, - {eventContext{Type: any, Source: eventSource1}}, - {eventContext{Type: eventType1, Source: eventSource1}}, - }, - eventsToSend: []eventContext{ + eventsToSend: []eventTestCase{ {Type: eventType1, Source: eventSource1}, {Type: eventType1, Source: eventSource2}, {Type: eventType2, Source: eventSource1}, {Type: eventType2, Source: eventSource2}, }, + eventFilters: []eventTestCase{ + {Type: any, Source: any}, + {Type: eventType1, Source: any}, + {Type: any, Source: eventSource1}, + {Type: eventType1, Source: eventSource1}, + }, deprecatedTriggerFilter: false, }, { name: "test default broker with many attribute triggers using v1beta1 trigger", - eventsToReceive: []eventReceiver{ - {eventContext{Type: any, Source: any}}, - {eventContext{Type: eventType1, Source: any}}, - {eventContext{Type: any, Source: eventSource1}}, - {eventContext{Type: eventType1, Source: eventSource1}}, - }, - eventsToSend: []eventContext{ + eventsToSend: []eventTestCase{ {Type: eventType1, Source: eventSource1}, {Type: eventType1, Source: eventSource2}, {Type: eventType2, Source: eventSource1}, {Type: eventType2, Source: eventSource2}, }, + eventFilters: []eventTestCase{ + {Type: any, Source: any}, + {Type: eventType1, Source: any}, + {Type: any, Source: eventSource1}, + {Type: eventType1, Source: eventSource1}, + }, deprecatedTriggerFilter: false, v1beta1: true, }, { name: "test default broker with many attribute and extension triggers", - eventsToReceive: []eventReceiver{ - {eventContext{Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1}}}, - {eventContext{Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}}}, - {eventContext{Type: any, Source: any, Extensions: map[string]interface{}{extensionName2: extensionValue2}}}, - {eventContext{Type: eventType1, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1}}}, - {eventContext{Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: any}}}, - {eventContext{Type: any, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1}}}, - {eventContext{Type: any, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}}}, - }, - eventsToSend: []eventContext{ + eventsToSend: []eventTestCase{ {Type: eventType1, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1}}, {Type: eventType1, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}}, {Type: eventType1, Source: eventSource1, Extensions: map[string]interface{}{extensionName2: extensionValue2}}, @@ -164,6 +204,15 @@ func TestBrokerWithManyTriggers(t *testing.T, brokerCreator BrokerCreator, shoul {Type: eventType2, Source: eventSource2, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}}, {Type: eventType2, Source: eventSource2, Extensions: map[string]interface{}{extensionName1: extensionValue1, nonMatchingExtensionName: extensionValue2}}, }, + eventFilters: []eventTestCase{ + {Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1}}, + {Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}}, + {Type: any, Source: any, Extensions: map[string]interface{}{extensionName2: extensionValue2}}, + {Type: eventType1, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1}}, + {Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: any}}, + {Type: any, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1}}, + {Type: any, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}}, + }, deprecatedTriggerFilter: false, }, } @@ -192,137 +241,134 @@ func TestBrokerWithManyTriggers(t *testing.T, brokerCreator BrokerCreator, shoul client.WaitForResourceReadyOrFail(brokerName, lib.BrokerTypeMeta) } - // Create subscribers. - for _, event := range test.eventsToReceive { - subscriberName := name("dumper", event.context.Type, event.context.Source, event.context.Extensions) - pod := resources.EventLoggerPod(subscriberName) - client.CreatePodOrFail(pod, lib.WithService(subscriberName)) - } + // Let's start event recorders and triggers + eventTrackers := make(map[string]*lib.EventInfoStore, len(test.eventFilters)) + for _, event := range test.eventFilters { + // Create event recorder pod and service + subscriberName := "dumper-" + event.String() + eventRecordPod := resources.EventRecordPod(subscriberName) + client.CreatePodOrFail(eventRecordPod, lib.WithService(subscriberName)) + eventTracker, err := client.NewEventInfoStore(subscriberName, t.Logf) + if err != nil { + t.Fatalf("Pod tracker failed: %v", err) + } + eventTrackers[subscriberName] = eventTracker + defer eventTracker.Cleanup() - // Create triggers. - for _, event := range test.eventsToReceive { - triggerName := name("trigger", event.context.Type, event.context.Source, event.context.Extensions) - subscriberName := name("dumper", event.context.Type, event.context.Source, event.context.Extensions) + // Create trigger. + triggerName := "trigger-" + event.String() client.CreateTriggerOrFailV1Beta1(triggerName, resources.WithSubscriberServiceRefForTriggerV1Beta1(subscriberName), - resources.WithAttributesTriggerFilterV1Beta1(event.context.Source, event.context.Type, event.context.Extensions), - resources.WithBrokerV1Beta1(brokerName)) - + resources.WithAttributesTriggerFilterV1Beta1(event.Source, event.Type, event.Extensions), + resources.WithBrokerV1Beta1(brokerName), + ) } - // Wait for all test resources to become ready before sending the events. client.WaitForAllTestResourcesReadyOrFail() - // Map to save the expected events per dumper so that we can verify the delivery. - expectedEvents := make(map[string][]string) - // Map to save the unexpected events per dumper so that we can verify that they weren't delivered. - unexpectedEvents := make(map[string][]string) - for _, eventToSend := range test.eventsToSend { + // Map to save the expected matchers per dumper so that we can verify the delivery. + expectedMatchers := make(map[string][]lib.EventInfoMatchFunc) + // Map to save the unexpected matchers per dumper so that we can verify that they weren't delivered. + unexpectedMatchers := make(map[string][]lib.EventInfoMatchFunc) + + // Now we need to send events and populate the expectedMatcher/unexpectedMatchers map, + // in order to assert if I correctly receive only the expected events + for _, eventTestCase := range test.eventsToSend { // Create cloud event. // Using event type, source and extensions as part of the body for easier debugging. - extensionsStr := joinSortedExtensions(eventToSend.Extensions) - body := fmt.Sprintf(("Body-%s-%s-%s"), eventToSend.Type, eventToSend.Source, extensionsStr) - cloudEvent := cloudevents.New( - fmt.Sprintf(`{"msg":%q}`, body), - cloudevents.WithSource(eventToSend.Source), - cloudevents.WithType(eventToSend.Type), - cloudevents.WithExtensions(eventToSend.Extensions), + eventToSend := cloudevents.NewEvent() + eventToSend.SetID(uuid.New().String()) + eventToSend.SetType(eventTestCase.Type) + eventToSend.SetSource(eventTestCase.Source) + for k, v := range eventTestCase.Extensions { + eventToSend.SetExtension(k, v) + } + + data := fmt.Sprintf(`{"msg":"%s"}`, eventTestCase.String()) + if err := eventToSend.SetData(cloudevents.ApplicationJSON, []byte(data)); err != nil { + t.Fatalf("Cannot set the payload of the event: %s", err.Error()) + } + + // Send event + senderPodName := "sender-" + eventTestCase.String() + client.SendEventToAddressable(senderPodName, brokerName, lib.BrokerTypeMeta, eventToSend) + + // Sent event matcher + sentEventMatcher := cetest.AllOf( + cetest.HasId(eventToSend.ID()), + eventTestCase.ToEventMatcher(), ) - // Create sender pod. - senderPodName := name("sender", eventToSend.Type, eventToSend.Source, eventToSend.Extensions) - client.SendFakeEventToAddressableOrFail(senderPodName, brokerName, lib.BrokerTypeMeta, cloudEvent) - - // Check on every dumper whether we should expect this event or not, and add its body - // to the expectedEvents/unexpectedEvents maps. - for _, eventToReceive := range test.eventsToReceive { - subscriberName := name("dumper", eventToReceive.context.Type, eventToReceive.context.Source, eventToReceive.context.Extensions) - if shouldExpectEvent(&eventToSend, &eventToReceive) { - expectedEvents[subscriberName] = append(expectedEvents[subscriberName], body) + + // Check on every dumper whether we should expect this event or not + for _, eventFilter := range test.eventFilters { + subscriberName := "dumper-" + eventFilter.String() + + if eventFilter.ToEventMatcher()(eventToSend) == nil { + // This filter should match this event + expectedMatchers[subscriberName] = append( + expectedMatchers[subscriberName], + lib.MatchEvent(sentEventMatcher), + ) } else { - unexpectedEvents[subscriberName] = append(unexpectedEvents[subscriberName], body) + // This filter should not match this event + unexpectedMatchers[subscriberName] = append( + unexpectedMatchers[subscriberName], + lib.MatchEvent(sentEventMatcher), + ) } } } - for _, event := range test.eventsToReceive { - subscriberName := name("dumper", event.context.Type, event.context.Source, event.context.Extensions) - if err := client.CheckLog(subscriberName, lib.CheckerContainsAll(expectedEvents[subscriberName])); err != nil { - t.Fatalf("Event(s) not found in logs of subscriber pod %q: %v", subscriberName, err) - } - // At this point all the events should have been received in the pod. - // We check whether we find unexpected events. If so, then we fail. - found, err := client.FindAnyLogContents(subscriberName, unexpectedEvents[subscriberName]) - if err != nil { - t.Fatalf("Failed querying to find log contents in pod %q: %v", subscriberName, err) + // Let's check that all expected matchers are fulfilled + for subscriberName, matchers := range expectedMatchers { + eventTracker := eventTrackers[subscriberName] + + for _, matcher := range matchers { + // One match per event is enough + eventTracker.MustWaitAtLeastNMatch(t, matcher, 1) } - if found { - t.Fatalf("Unexpected event(s) found in logs of subscriber pod %q", subscriberName) + } + + // Let's check the unexpected matchers + // NOTE: this check is not really robust because we could receive + // an unexpected event after the check is done + for subscriberName, matchers := range unexpectedMatchers { + eventTracker := eventTrackers[subscriberName] + + for _, matcher := range matchers { + res, _, err := eventTracker.Find(matcher) + if err != nil { + t.Fatalf("unexpected error during find: %v", err) + } + + if len(res) != 0 { + t.Fatalf("Unexpected matches on subscriber '%s', found: %v", subscriberName, res) + } } } }) } } -// Helper function to create names for different objects (e.g., triggers, services, etc.). -func name(obj, eventType, eventSource string, extensions map[string]interface{}) string { - // Pod names need to be lowercase. We might have an eventType as Any, that is why we lowercase them. - if eventType == "" { - eventType = "testany" +func extensionsToString(extensions map[string]interface{}) string { + // Sort extension keys + sortedExtensionNames := make([]string, 0) + for k := range extensions { + sortedExtensionNames = append(sortedExtensionNames, k) } - if eventSource == "" { - eventSource = "testany" - } - name := strings.ToLower(fmt.Sprintf("%s-%s-%s", obj, eventType, eventSource)) - if len(extensions) > 0 { - name = strings.ToLower(fmt.Sprintf("%s-%s", name, joinSortedExtensions(extensions))) - } - return name -} + sort.Strings(sortedExtensionNames) -func joinSortedExtensions(extensions map[string]interface{}) string { + // Write map as string var sb strings.Builder - sortedExtensionNames := sortedKeys(extensions) for _, sortedExtensionName := range sortedExtensionNames { sb.WriteString("-") sb.WriteString(sortedExtensionName) sb.WriteString("-") vStr := fmt.Sprintf("%v", extensions[sortedExtensionName]) - if vStr == "" { + if vStr == any { vStr = "testany" } sb.WriteString(vStr) } return sb.String() } - -func sortedKeys(m map[string]interface{}) []string { - keys := make([]string, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - return keys -} - -// Checks whether we should expect to receive 'eventToSend' in 'eventReceiver' based on its type and source pattern. -func shouldExpectEvent(eventToSend *eventContext, receiver *eventReceiver) bool { - if receiver.context.Type != any && receiver.context.Type != eventToSend.Type { - return false - } - if receiver.context.Source != any && receiver.context.Source != eventToSend.Source { - return false - } - for k, v := range receiver.context.Extensions { - var value interface{} - value, ok := eventToSend.Extensions[k] - // If the attribute does not exist in the event, return false. - if !ok { - return false - } - // If the attribute is not set to any and is different than the one from the event, return false. - if v != any && v != value { - return false - } - } - return true -} diff --git a/test/e2e/helpers/channel_chain_test_helper.go b/test/e2e/helpers/channel_chain_test_helper.go index 7f0629cbbe5..4cde9d9154b 100644 --- a/test/e2e/helpers/channel_chain_test_helper.go +++ b/test/e2e/helpers/channel_chain_test_helper.go @@ -95,6 +95,6 @@ func ChannelChainTestHelper(t *testing.T, expectedContentCount := len(subscriptionNames1) * len(subscriptionNames2) // verify the logger service receives the event - eventTracker.AssertWaitMatchSourceData(t, recordEventsPodName, eventSource, body, expectedContentCount, expectedContentCount) + eventTracker.AssertWaitMatchSourceData(t, eventSource, body, expectedContentCount, expectedContentCount) }) } diff --git a/test/e2e/helpers/channel_defaulter_test_helper.go b/test/e2e/helpers/channel_defaulter_test_helper.go index b95b2671d54..c85b0389f9d 100644 --- a/test/e2e/helpers/channel_defaulter_test_helper.go +++ b/test/e2e/helpers/channel_defaulter_test_helper.go @@ -153,7 +153,7 @@ func defaultChannelTestHelper(t *testing.T, client *lib.Client, expectedChannel client.SendEventToAddressable(senderName, channelName, lib.ChannelTypeMeta, event) // verify the logger service receives the event - eventTracker.AssertWaitMatchSourceData(t, recordEventsPodName, eventSource, body, 1, 1) + eventTracker.AssertWaitMatchSourceData(t, eventSource, body, 1, 1) } // updateDefaultChannelCM will update the default channel configmap diff --git a/test/e2e/helpers/channel_dls_test_helper.go b/test/e2e/helpers/channel_dls_test_helper.go index 28aa98bd35a..3179eb3e61e 100644 --- a/test/e2e/helpers/channel_dls_test_helper.go +++ b/test/e2e/helpers/channel_dls_test_helper.go @@ -83,6 +83,6 @@ func ChannelDeadLetterSinkTestHelper(t *testing.T, // check if the logging service receives the correct number of event messages expectedContentCount := len(subscriptionNames) - eventTracker.AssertWaitMatchSourceData(t, recordEventsPodName, eventSource, body, expectedContentCount, expectedContentCount) + eventTracker.AssertWaitMatchSourceData(t, eventSource, body, expectedContentCount, expectedContentCount) }) } diff --git a/test/e2e/helpers/channel_event_tranformation_test_helper.go b/test/e2e/helpers/channel_event_tranformation_test_helper.go index e8212066fd5..ff70deb775f 100644 --- a/test/e2e/helpers/channel_event_tranformation_test_helper.go +++ b/test/e2e/helpers/channel_event_tranformation_test_helper.go @@ -105,7 +105,6 @@ func EventTransformationForSubscriptionTestHelper(t *testing.T, expectedContentCount := len(subscriptionNames1) * len(subscriptionNames2) eventTracker.AssertWaitMatchSourceData( t, - recordEventsPodName, eventSource, transformedEventBody, expectedContentCount, diff --git a/test/e2e/helpers/channel_single_event_helper.go b/test/e2e/helpers/channel_single_event_helper.go index 8f929066101..90dbf763689 100644 --- a/test/e2e/helpers/channel_single_event_helper.go +++ b/test/e2e/helpers/channel_single_event_helper.go @@ -112,6 +112,6 @@ func SingleEventForChannelTestHelper(t *testing.T, encoding cloudevents.Encoding ) // verify the logger service receives the event - eventTracker.AssertWaitMatchSourceData(t, eventRecorder, eventSource, body, 1, 1) + eventTracker.AssertWaitMatchSourceData(t, eventSource, body, 1, 1) }) } diff --git a/test/e2e/helpers/parallel_test_helper.go b/test/e2e/helpers/parallel_test_helper.go index ed25ad176d8..c706c514b3e 100644 --- a/test/e2e/helpers/parallel_test_helper.go +++ b/test/e2e/helpers/parallel_test_helper.go @@ -137,7 +137,7 @@ func ParallelTestHelper(t *testing.T, event) // verify the logger service receives the correct transformed event - eventTracker.AssertWaitMatchSourceData(t, eventRecorder, eventSource, tc.expected, 1, 1) + eventTracker.AssertWaitMatchSourceData(t, eventSource, tc.expected, 1, 1) } }) } diff --git a/test/e2e/helpers/sequence_test_helper.go b/test/e2e/helpers/sequence_test_helper.go index ac6cb8431d0..8c4f6b5ef72 100644 --- a/test/e2e/helpers/sequence_test_helper.go +++ b/test/e2e/helpers/sequence_test_helper.go @@ -143,6 +143,6 @@ func SequenceTestHelper(t *testing.T, for _, config := range stepSubscriberConfigs { expectedMsg += config.msgAppender } - eventTracker.AssertWaitMatchSourceData(t, recordEventsPodName, eventSource, expectedMsg, 1, 1) + eventTracker.AssertWaitMatchSourceData(t, eventSource, expectedMsg, 1, 1) }) } diff --git a/test/e2e/helpers/trigger_no_broker_test_helper.go b/test/e2e/helpers/trigger_no_broker_test_helper.go index 27e4064fc2d..60a23faaaeb 100644 --- a/test/e2e/helpers/trigger_no_broker_test_helper.go +++ b/test/e2e/helpers/trigger_no_broker_test_helper.go @@ -35,7 +35,7 @@ func TestTriggerNoBroker(t *testing.T, channel string, brokerCreator BrokerCreat client := lib.Setup(t, true) defer lib.TearDown(client) brokerName := strings.ToLower(channel) - subscriberName := name("dumper", "", "", map[string]interface{}{}) + subscriberName := "dumper-empty" pod := resources.EventLoggerPod(subscriberName) client.CreatePodOrFail(pod, lib.WithService(subscriberName)) client.CreateTriggerOrFailV1Beta1("testtrigger", diff --git a/test/e2e/source_api_server_test.go b/test/e2e/source_api_server_test.go index ca562d8863f..1717f805380 100644 --- a/test/e2e/source_api_server_test.go +++ b/test/e2e/source_api_server_test.go @@ -22,6 +22,7 @@ import ( "testing" "time" + "github.com/cloudevents/sdk-go/v2/event" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -169,7 +170,10 @@ func TestApiServerSource(t *testing.T) { if tc.expected == "" { time.Sleep(10 * time.Second) - ev, _, err := targetTracker.Find(lib.ValidEvFunc(lib.MatchAllEvent)) + ev, _, err := targetTracker.Find(lib.MatchEvent(func(have event.Event) error { + //TODO This really needs to be no-op? + return nil + })) if err != nil { t.Fatalf("Saw error looking for events: %v", err) } diff --git a/test/e2e/source_sinkbinding_v1alpha1_test.go b/test/e2e/source_sinkbinding_v1alpha1_test.go index dadb9d56a96..d3196a5cd6c 100644 --- a/test/e2e/source_sinkbinding_v1alpha1_test.go +++ b/test/e2e/source_sinkbinding_v1alpha1_test.go @@ -143,7 +143,7 @@ func TestSinkBindingDeployment(t *testing.T) { return nil } - _, err = targetTracker.WaitAtLeastNMatch(lib.ValidEvFunc(matchFunc), expectedCount) + _, err = targetTracker.WaitAtLeastNMatch(lib.MatchEvent(matchFunc), expectedCount) if err != nil { t.Fatalf("Data %s, extension %q does not appear at least %d times in events of logger pod %q: %v", data, extensionSecret, expectedCount, loggerPodName, err) diff --git a/test/e2e/source_sinkbinding_v1alpha2_test.go b/test/e2e/source_sinkbinding_v1alpha2_test.go index f790edae9e8..d63fae5efda 100644 --- a/test/e2e/source_sinkbinding_v1alpha2_test.go +++ b/test/e2e/source_sinkbinding_v1alpha2_test.go @@ -143,7 +143,7 @@ func TestSinkBindingV1Alpha2Deployment(t *testing.T) { return nil } - _, err = targetTracker.WaitAtLeastNMatch(lib.ValidEvFunc(matchFunc), expectedCount) + _, err = targetTracker.WaitAtLeastNMatch(lib.MatchEvent(matchFunc), expectedCount) if err != nil { t.Fatalf("Data %s, extension %q does not appear at least %d times in events of logger pod %q: %v", data, extensionSecret, expectedCount, loggerPodName, err) diff --git a/test/lib/checkevents.go b/test/lib/checkevents.go index 675955a56cc..d7257094511 100644 --- a/test/lib/checkevents.go +++ b/test/lib/checkevents.go @@ -24,6 +24,7 @@ import ( "time" cloudevents "github.com/cloudevents/sdk-go/v2" + cetest "github.com/cloudevents/sdk-go/v2/test" "k8s.io/apimachinery/pkg/util/wait" "knative.dev/pkg/test/logging" @@ -39,7 +40,8 @@ const ( // This pulls events from the pod during any Find or Wait call, storing them // locally and triming them from the remote pod store. type EventInfoStore struct { - getter eventGetterInterface + podName string + getter eventGetterInterface lock sync.Mutex allEvents []EventInfo @@ -84,6 +86,7 @@ func (c *Client) NewEventInfoStore(podName string, logf logging.FormatLogger) (* return nil, err } ei := newTestableEventInfoStore(egi, -1, -1) + ei.podName = podName return ei, nil } @@ -198,15 +201,15 @@ func (ei *EventInfoStore) Find(f EventInfoMatchFunc) ([]EventInfo, SearchedInfo, return allMatch, sInfo, nil } -// Convert a boolean check function that checks valid messages to a function -// that checks EventInfo structures, returning false for any that don't +// Convert a matcher that checks valid messages to a function +// that checks EventInfo structures, returning an error for any that don't // contain valid events. -func ValidEvFunc(evf EventMatchFunc) EventInfoMatchFunc { +func MatchEvent(evf ...cetest.EventMatcher) EventInfoMatchFunc { return func(ei EventInfo) error { if ei.Event == nil { return fmt.Errorf("Saw nil event") } else { - return evf(*ei.Event) + return cetest.AllOf(evf...)(*ei.Event) } } } @@ -214,7 +217,7 @@ func ValidEvFunc(evf EventMatchFunc) EventInfoMatchFunc { // Wait a long time (currently 4 minutes) until the provided function matches at least // five events. The matching events are returned if we find at least n. If the // function times out, an error is returned. -func (ei *EventInfoStore) WaitAtLeastNMatch(f EventInfoMatchFunc, n int) ([]EventInfo, error) { +func (ei *EventInfoStore) WaitAtLeastNMatch(f EventInfoMatchFunc, min int) ([]EventInfo, error) { var matchRet []EventInfo var internalErr error @@ -225,9 +228,9 @@ func (ei *EventInfoStore) WaitAtLeastNMatch(f EventInfoMatchFunc, n int) ([]Even return false, nil } count := len(allMatch) - if count < n { + if count < min { internalErr = fmt.Errorf("FAIL MATCHING: saw %d/%d matching events. recent events: (%s)", - count, n, &sInfo) + count, min, &sInfo) return false, nil } matchRet = allMatch @@ -263,7 +266,7 @@ func (ei *EventInfoStore) WaitMatchSourceData(source string, data string, minCou } } // verify the logger service receives the event and only once - match, err := ei.WaitAtLeastNMatch(ValidEvFunc(matchFunc), minCount) + match, err := ei.WaitAtLeastNMatch(MatchEvent(matchFunc), minCount) if err != nil { return fmt.Errorf("error waiting for event: %v", err) } @@ -273,18 +276,11 @@ func (ei *EventInfoStore) WaitMatchSourceData(source string, data string, minCou return nil } -func (ei *EventInfoStore) AssertWaitMatchSourceData(tb testing.TB, eventRecord string, source string, data string, minCount int, maxCount int) { +func (ei *EventInfoStore) AssertWaitMatchSourceData(tb testing.TB, source string, data string, minCount int, maxCount int) { if err := ei.WaitMatchSourceData(source, data, minCount, maxCount); err != nil { - tb.Fatalf("Timeout waiting for source %q and data %q. It does not appear at least %d times in the event record pod %q: %v", source, data, minCount, eventRecord, err) + tb.Fatalf("Timeout waiting for source %q and data %q. It does not appear at least %d times in the event record pod %q: %v", source, data, minCount, ei.podName, err) } } // Does the provided EventInfo match some criteria type EventInfoMatchFunc func(EventInfo) error - -// Does the provided event match some criteria -type EventMatchFunc func(cloudevents.Event) error - -func MatchAllEvent(cloudevents.Event) error { - return nil -} diff --git a/test/lib/checkevents_test.go b/test/lib/checkevents_test.go index bee7c8759da..5ba4b10322a 100644 --- a/test/lib/checkevents_test.go +++ b/test/lib/checkevents_test.go @@ -250,7 +250,7 @@ func TestWaitForN(t *testing.T) { } } - allMatch, waitErr = ei.WaitAtLeastNMatch(ValidEvFunc(matchFunc), 2) + allMatch, waitErr = ei.WaitAtLeastNMatch(MatchEvent(matchFunc), 2) wg.Done() }() var tCalls int diff --git a/test/lib/resources/eventing.go b/test/lib/resources/eventing.go index 85ced726fc7..b4d374abb4e 100644 --- a/test/lib/resources/eventing.go +++ b/test/lib/resources/eventing.go @@ -207,8 +207,16 @@ func BrokerV1Beta1(name string, options ...BrokerV1Beta1Option) *eventingv1beta1 // WithAttributesTriggerFilter returns an option that adds a TriggerFilter with Attributes for the given Trigger. func WithAttributesTriggerFilterV1Beta1(eventSource, eventType string, extensions map[string]interface{}) TriggerOptionV1Beta1 { attrs := make(map[string]string) - attrs["type"] = eventType - attrs["source"] = eventSource + if eventType != "" { + attrs["type"] = eventType + } else { + attrs["type"] = eventingv1beta1.TriggerAnyFilter + } + if eventSource != "" { + attrs["source"] = eventSource + } else { + attrs["source"] = eventingv1beta1.TriggerAnyFilter + } for k, v := range extensions { attrs[k] = fmt.Sprintf("%v", v) } diff --git a/third_party/VENDOR-LICENSE/github.com/pmezard/go-difflib/difflib/LICENSE b/third_party/VENDOR-LICENSE/github.com/pmezard/go-difflib/difflib/LICENSE new file mode 100644 index 00000000000..c67dad612a3 --- /dev/null +++ b/third_party/VENDOR-LICENSE/github.com/pmezard/go-difflib/difflib/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013, Patrick Mezard +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + The names of its contributors may not be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/VENDOR-LICENSE/github.com/stretchr/testify/LICENSE b/third_party/VENDOR-LICENSE/github.com/stretchr/testify/LICENSE new file mode 100644 index 00000000000..f38ec5956b6 --- /dev/null +++ b/third_party/VENDOR-LICENSE/github.com/stretchr/testify/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/cloudevents/sdk-go/v2/client/client.go b/vendor/github.com/cloudevents/sdk-go/v2/client/client.go index 0db25476b24..a1b4e6a524c 100644 --- a/vendor/github.com/cloudevents/sdk-go/v2/client/client.go +++ b/vendor/github.com/cloudevents/sdk-go/v2/client/client.go @@ -221,6 +221,7 @@ func (c *ceClient) StartReceiver(ctx context.Context, fn interface{}) error { msg, respFn, err = c.responder.Respond(ctx) } else if c.receiver != nil { msg, err = c.receiver.Receive(ctx) + respFn = noRespFn } if err == io.EOF { // Normal close @@ -241,3 +242,8 @@ func (c *ceClient) StartReceiver(ctx context.Context, fn interface{}) error { wg.Wait() return nil } + +// noRespFn is used to simply forward the protocol.Result for receivers that aren't responders +func noRespFn(_ context.Context, _ binding.Message, r protocol.Result, _ ...binding.Transformer) error { + return r +} diff --git a/vendor/github.com/cloudevents/sdk-go/v2/event/event_validation.go b/vendor/github.com/cloudevents/sdk-go/v2/event/event_validation.go index feae2ce03ab..01592c76fd7 100644 --- a/vendor/github.com/cloudevents/sdk-go/v2/event/event_validation.go +++ b/vendor/github.com/cloudevents/sdk-go/v2/event/event_validation.go @@ -20,7 +20,7 @@ func (e ValidationError) Error() string { // Validate performs a spec based validation on this event. // Validation is dependent on the spec version specified in the event context. -func (e Event) Validate() ValidationError { +func (e Event) Validate() error { if e.Context == nil { return ValidationError{"specversion": fmt.Errorf("missing Event.Context")} } @@ -39,7 +39,7 @@ func (e Event) Validate() ValidationError { } if len(errs) > 0 { - return errs + return ValidationError(errs) } return nil } diff --git a/vendor/github.com/cloudevents/sdk-go/v2/test/event_asserts.go b/vendor/github.com/cloudevents/sdk-go/v2/test/event_asserts.go index 37029a4d92c..7469771362c 100644 --- a/vendor/github.com/cloudevents/sdk-go/v2/test/event_asserts.go +++ b/vendor/github.com/cloudevents/sdk-go/v2/test/event_asserts.go @@ -3,32 +3,27 @@ package test import ( "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/cloudevents/sdk-go/v2/binding/spec" "github.com/cloudevents/sdk-go/v2/event" ) +// AssertEvent is a "matcher like" assertion method to test the properties of an event +func AssertEvent(t testing.TB, have event.Event, matchers ...EventMatcher) { + err := AllOf(matchers...)(have) + if err != nil { + t.Fatalf("Error while matching event: %s", err.Error()) + } +} + // AssertEventContextEquals asserts that two event.Event contexts are equals func AssertEventContextEquals(t testing.TB, want event.EventContext, have event.EventContext) { - wantVersion := spec.VS.Version(want.GetSpecVersion()) - require.NotNil(t, wantVersion) - haveVersion := spec.VS.Version(have.GetSpecVersion()) - require.NotNil(t, haveVersion) - require.Equal(t, wantVersion, haveVersion) - - for _, a := range wantVersion.Attributes() { - require.Equal(t, a.Get(want), a.Get(have), "Attribute %s does not match: %v != %v", a.PrefixedName(), a.Get(want), a.Get(have)) + if err := IsContextEqualTo(want)(event.Event{Context: have}); err != nil { + t.Fatalf("Error while matching event context: %s", err.Error()) } - - require.Equal(t, want.GetExtensions(), have.GetExtensions(), "Extensions") } // AssertEventEquals asserts that two event.Event are equals func AssertEventEquals(t testing.TB, want event.Event, have event.Event) { - AssertEventContextEquals(t, want.Context, have.Context) - wantPayload := want.Data() - havePayload := have.Data() - assert.Equal(t, wantPayload, havePayload) + if err := IsEqualTo(want)(have); err != nil { + t.Fatalf("Error while matching event: %s", err.Error()) + } } diff --git a/vendor/github.com/cloudevents/sdk-go/v2/test/event_matchers.go b/vendor/github.com/cloudevents/sdk-go/v2/test/event_matchers.go new file mode 100644 index 00000000000..13f1d032c0a --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/v2/test/event_matchers.go @@ -0,0 +1,233 @@ +package test + +import ( + "fmt" + "reflect" + "time" + + "github.com/google/go-cmp/cmp" + + "github.com/cloudevents/sdk-go/v2/binding/spec" + "github.com/cloudevents/sdk-go/v2/event" +) + +type EventMatcher func(have event.Event) error + +// AllOf combines matchers together +func AllOf(matchers ...EventMatcher) EventMatcher { + return func(have event.Event) error { + for _, m := range matchers { + if err := m(have); err != nil { + return err + } + } + return nil + } +} + +func HasId(id string) EventMatcher { + return HasAttributeKind(spec.ID, id) +} + +func HasType(ty string) EventMatcher { + return HasAttributeKind(spec.Type, ty) +} + +func HasSpecVersion(specVersion string) EventMatcher { + return HasAttributeKind(spec.SpecVersion, specVersion) +} + +func HasSource(source string) EventMatcher { + return HasAttributeKind(spec.Source, source) +} + +func HasDataContentType(dataContentType string) EventMatcher { + return HasAttributeKind(spec.DataContentType, dataContentType) +} + +func HasDataSchema(schema string) EventMatcher { + return HasAttributeKind(spec.DataSchema, schema) +} + +func HasSubject(subject string) EventMatcher { + return HasAttributeKind(spec.Subject, subject) +} + +func HasTime(t time.Time) EventMatcher { + return HasAttributeKind(spec.Time, t) +} + +// ContainsAttributes checks if the event contains at least the provided context attributes +func ContainsAttributes(attrs ...spec.Kind) EventMatcher { + return func(have event.Event) error { + haveVersion := spec.VS.Version(have.SpecVersion()) + for _, k := range attrs { + attr := haveVersion.AttributeFromKind(k) + if isEmpty(attr) { + return fmt.Errorf("attribute name '%s' unrecognized", k.String()) + } + if isEmpty(attr.Get(have.Context)) { + return fmt.Errorf("missing or nil/empty attribute '%s'", k.String()) + } + } + return nil + } +} + +// ContainsExtensions checks if the event contains at least the provided extension names +func ContainsExtensions(exts ...string) EventMatcher { + return func(have event.Event) error { + for _, ext := range exts { + if _, ok := have.Extensions()[ext]; !ok { + return fmt.Errorf("expecting extension '%s'", ext) + } + } + return nil + } +} + +// HasExactlyExtensions checks if the event contains exactly the provided extensions +func HasExactlyExtensions(ext map[string]interface{}) EventMatcher { + return func(have event.Event) error { + if diff := cmp.Diff(ext, have.Extensions()); diff != "" { + return fmt.Errorf("unexpected extensions (-want, +got) = %v", diff) + } + return nil + } +} + +// HasExtensions checks if the event contains at least the provided extensions +func HasExtensions(ext map[string]interface{}) EventMatcher { + return func(have event.Event) error { + for k, v := range ext { + if _, ok := have.Extensions()[k]; !ok { + return fmt.Errorf("expecting extension '%s'", ext) + } + if !reflect.DeepEqual(v, have.Extensions()[k]) { + return fmt.Errorf("expecting extension '%s' equal to '%s', got '%s'", k, v, have.Extensions()[k]) + } + } + return nil + } +} + +// HasExtension checks if the event contains the provided extension +func HasExtension(key string, value interface{}) EventMatcher { + return HasExtensions(map[string]interface{}{key: value}) +} + +// HasData checks if the event contains the provided data +func HasData(want []byte) EventMatcher { + return func(have event.Event) error { + if diff := cmp.Diff(string(want), string(have.Data())); diff != "" { + return fmt.Errorf("data not matching (-want, +got) = %v", diff) + } + return nil + } +} + +// HasNoData checks if the event doesn't contain data +func HasNoData() EventMatcher { + return func(have event.Event) error { + if have.Data() != nil { + return fmt.Errorf("expecting nil data, got = '%v'", string(have.Data())) + } + return nil + } +} + +// IsEqualTo performs a semantic equality check of the event (like AssertEventEquals) +func IsEqualTo(want event.Event) EventMatcher { + return AllOf(IsContextEqualTo(want.Context), IsDataEqualTo(want)) +} + +// IsContextEqualTo performs a semantic equality check of the event context (like AssertEventContextEquals) +func IsContextEqualTo(want event.EventContext) EventMatcher { + return AllOf(func(have event.Event) error { + if want.GetSpecVersion() != have.SpecVersion() { + return fmt.Errorf("not matching specversion: want = '%s', got = '%s'", want.GetSpecVersion(), have.SpecVersion()) + } + vs := spec.VS.Version(want.GetSpecVersion()) + + for _, a := range vs.Attributes() { + if !reflect.DeepEqual(a.Get(want), a.Get(have.Context)) { + return fmt.Errorf("expecting attribute '%s' equal to '%s', got '%s'", a.PrefixedName(), a.Get(want), a.Get(have.Context)) + } + } + + return nil + }, HasExactlyExtensions(want.GetExtensions())) +} + +// IsDataEqualTo checks if the data field matches with want +func IsDataEqualTo(want event.Event) EventMatcher { + if want.Data() == nil { + return HasNoData() + } else { + return HasData(want.Data()) + } +} + +// IsValid checks if the event is valid +func IsValid() EventMatcher { + return func(have event.Event) error { + if err := have.Validate(); err != nil { + return fmt.Errorf("expecting valid event: %s", err.Error()) + } + return nil + } +} + +// IsInvalid checks if the event is invalid +func IsInvalid() EventMatcher { + return func(have event.Event) error { + if err := have.Validate(); err == nil { + return fmt.Errorf("expecting invalid event") + } + return nil + } +} + +func HasAttributeKind(kind spec.Kind, value interface{}) EventMatcher { + return func(have event.Event) error { + haveVersion := spec.VS.Version(have.SpecVersion()) + attr := haveVersion.AttributeFromKind(kind) + if isEmpty(attr) { + return fmt.Errorf("attribute '%s' not existing in the spec version '%s' of this event", kind.String(), haveVersion.String()) + } + if !reflect.DeepEqual(value, attr.Get(have.Context)) { + return fmt.Errorf("expecting attribute '%s' equal to '%s', got '%s'", kind.String(), value, attr.Get(have.Context)) + } + return nil + } +} + +// Code took from https://github.com/stretchr/testify +// LICENSE: MIT License + +func isEmpty(object interface{}) bool { + + // get nil case out of the way + if object == nil { + return true + } + + objValue := reflect.ValueOf(object) + + switch objValue.Kind() { + // collection types are empty when they have no element + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + return objValue.Len() == 0 + // pointers are empty if nil or if the value they point to is empty + case reflect.Ptr: + if objValue.IsNil() { + return true + } + deref := objValue.Elem().Interface() + return isEmpty(deref) + // for all other types, compare against the zero value + default: + zero := reflect.Zero(objValue.Type()) + return reflect.DeepEqual(object, zero.Interface()) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3d810208911..0db9f4b655b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -91,7 +91,7 @@ github.com/cloudevents/sdk-go/pkg/cloudevents/observability github.com/cloudevents/sdk-go/pkg/cloudevents/transport github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http github.com/cloudevents/sdk-go/pkg/cloudevents/types -# github.com/cloudevents/sdk-go/v2 v2.0.0 +# github.com/cloudevents/sdk-go/v2 v2.0.1-0.20200608152019-2ab697c8fc0b ## explicit github.com/cloudevents/sdk-go/v2 github.com/cloudevents/sdk-go/v2/binding