diff --git a/test/conformance/channel_message_modes_specversion_test.go b/test/conformance/channel_message_modes_specversion_test.go new file mode 100644 index 00000000000..7cc5309b6ec --- /dev/null +++ b/test/conformance/channel_message_modes_specversion_test.go @@ -0,0 +1,30 @@ +// +build e2e + +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package conformance + +import ( + "testing" + + "knative.dev/eventing/test/conformance/helpers" + testlib "knative.dev/eventing/test/lib" +) + +func TestChannelEventModesAndSpecVersions(t *testing.T) { + helpers.ChannelMessageModesAndSpecVersionsTestRunner(t, channelTestRunner, testlib.SetupClientOptionNoop) +} diff --git a/test/conformance/helpers/channel_message_modes_specversion_helper.go b/test/conformance/helpers/channel_message_modes_specversion_helper.go new file mode 100644 index 00000000000..6c9b7205ef1 --- /dev/null +++ b/test/conformance/helpers/channel_message_modes_specversion_helper.go @@ -0,0 +1,113 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helpers + +import ( + "strings" + "testing" + + cloudevents "github.com/cloudevents/sdk-go/v2" + . "github.com/cloudevents/sdk-go/v2/test" + "github.com/google/uuid" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + testlib "knative.dev/eventing/test/lib" + "knative.dev/eventing/test/lib/recordevents" + "knative.dev/eventing/test/lib/resources" + "knative.dev/eventing/test/lib/resources/sender" +) + +// ChannelMessageModesAndSpecVersionsTestRunner tests the support of the channel ingress for different spec versions and message modes +func ChannelMessageModesAndSpecVersionsTestRunner( + t *testing.T, + channelTestRunner testlib.ComponentsTestRunner, + options ...testlib.SetupClientOption, +) { + testCases := []struct { + encoding cloudevents.Encoding + version string + }{ + { + encoding: cloudevents.EncodingBinary, + version: cloudevents.VersionV03, + }, { + encoding: cloudevents.EncodingStructured, + version: cloudevents.VersionV03, + }, { + encoding: cloudevents.EncodingBinary, + version: cloudevents.VersionV1, + }, { + encoding: cloudevents.EncodingStructured, + version: cloudevents.VersionV1, + }, + } + + channelTestRunner.RunTests(t, testlib.FeatureBasic, func(t *testing.T, channel metav1.TypeMeta) { + for _, tc := range testCases { + t.Run("encoding_"+tc.encoding.String()+"_version_"+tc.version, func(t *testing.T) { + messageModeSpecVersionTest(t, channel, tc.encoding, tc.version, options...) + }) + } + }) +} + +// Sender -> Channel -> Subscriber -> Record Events +func messageModeSpecVersionTest(t *testing.T, channel metav1.TypeMeta, encoding cloudevents.Encoding, version string, options ...testlib.SetupClientOption) { + client := testlib.Setup(t, true, options...) + defer testlib.TearDown(client) + + resourcesNamePrefix := strings.ReplaceAll(strings.ToLower(encoding.String()+"-"+version), ".", "") + + channelName := resourcesNamePrefix + "-ch" + client.CreateChannelOrFail(channelName, &channel) + + subscriberName := resourcesNamePrefix + "-recordevents" + eventTracker, _ := recordevents.StartEventRecordOrFail(client, subscriberName) + + client.CreateSubscriptionOrFail( + resourcesNamePrefix+"-sub", + channelName, + &channel, + resources.WithSubscriberForSubscription(subscriberName), + ) + + client.WaitForAllTestResourcesReadyOrFail() + + event := FullEvent() + event.SetID(uuid.New().String()) + + switch version { + case cloudevents.VersionV1: + event.Context = event.Context.AsV1() + case cloudevents.VersionV03: + event.Context = event.Context.AsV03() + } + + client.SendEventToAddressable( + resourcesNamePrefix+"-sender", + channelName, + &channel, + event, + sender.WithEncoding(encoding), + ) + + eventTracker.AssertExact(1, recordevents.NoError(), recordevents.MatchEvent( + HasSpecVersion(version), + HasId(event.ID()), + IsValid(), + )) +} diff --git a/test/lib/recordevents/event_info_matchers.go b/test/lib/recordevents/event_info_matchers.go index 4444f865656..65d1bef4f07 100644 --- a/test/lib/recordevents/event_info_matchers.go +++ b/test/lib/recordevents/event_info_matchers.go @@ -46,6 +46,16 @@ func AllOf(matchers ...EventInfoMatcher) EventInfoMatcher { } } +// Matcher that fails if there is an error in the EventInfo +func NoError() EventInfoMatcher { + return func(ei EventInfo) error { + if ei.Error != "" { + return fmt.Errorf("not expecting an error in event info: %s", ei.Error) + } + return nil + } +} + // 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. diff --git a/test/lib/recordevents/event_info_store.go b/test/lib/recordevents/event_info_store.go index 3a5de9a481a..6576936247a 100644 --- a/test/lib/recordevents/event_info_store.go +++ b/test/lib/recordevents/event_info_store.go @@ -185,14 +185,15 @@ func (ei *EventInfoStore) refreshData() ([]EventInfo, error) { return allEvents, nil } -// Find all events received by the recordevents pod that match the provided function, +// Find all events received by the recordevents pod that match the provided matchers, // returning all matching events as well as a SearchedInfo structure including the // last 5 events seen and the total events matched. This SearchedInfo structure // is primarily to ease debugging in failure printouts. The provided function is // guaranteed to be called exactly once on each EventInfo from the pod. // The error array contains the eventual match errors, while the last return error contains // an eventual communication error while trying to get the events from the recordevents pod -func (ei *EventInfoStore) Find(f EventInfoMatcher) ([]EventInfo, SearchedInfo, []error, error) { +func (ei *EventInfoStore) Find(matchers ...EventInfoMatcher) ([]EventInfo, SearchedInfo, []error, error) { + f := AllOf(matchers...) const maxLastEvents = 5 allMatch := []EventInfo{} sInfo := SearchedInfo{} @@ -221,20 +222,20 @@ func (ei *EventInfoStore) Find(f EventInfoMatcher) ([]EventInfo, SearchedInfo, [ return allMatch, sInfo, nonMatchingErrors, nil } -// Assert that there are at least min number of matches of f. +// Assert that there are at least min number of match for the provided matchers. // This method fails the test if the assert is not fulfilled. -func (ei *EventInfoStore) AssertAtLeast(min int, f EventInfoMatcher) []EventInfo { - events, err := ei.waitAtLeastNMatch(f, min) +func (ei *EventInfoStore) AssertAtLeast(min int, matchers ...EventInfoMatcher) []EventInfo { + events, err := ei.waitAtLeastNMatch(AllOf(matchers...), min) if err != nil { ei.tb.Fatalf("Timeout waiting for at least %d matches.\nError: %v", min, errors.WithStack(err)) } return events } -// Assert that there are at least min number of matches and at most max number of matches of f. +// Assert that there are at least min number of matches and at most max number of matches for the provided matchers. // This method fails the test if the assert is not fulfilled. -func (ei *EventInfoStore) AssertInRange(min int, max int, f EventInfoMatcher) []EventInfo { - events := ei.AssertAtLeast(min, f) +func (ei *EventInfoStore) AssertInRange(min int, max int, matchers ...EventInfoMatcher) []EventInfo { + events := ei.AssertAtLeast(min, matchers...) if max > 0 && len(events) > max { ei.tb.Fatalf("Assert in range failed: %v", errors.WithStack(fmt.Errorf("expected <= %d events, saw %d", max, len(events)))) } @@ -242,10 +243,10 @@ func (ei *EventInfoStore) AssertInRange(min int, max int, f EventInfoMatcher) [] return events } -// Assert that there aren't any matches of f. +// Assert that there aren't any matches for the provided matchers. // This method fails the test if the assert is not fulfilled. -func (ei *EventInfoStore) AssertNot(f EventInfoMatcher) []EventInfo { - res, recentEvents, _, err := ei.Find(f) +func (ei *EventInfoStore) AssertNot(matchers ...EventInfoMatcher) []EventInfo { + res, recentEvents, _, err := ei.Find(matchers...) if err != nil { ei.tb.Fatalf("Unexpected error during find on recordevents '%s': %v", ei.podName, errors.WithStack(err)) } @@ -259,10 +260,10 @@ func (ei *EventInfoStore) AssertNot(f EventInfoMatcher) []EventInfo { return res } -// Assert that there are exactly n matches of f. +// Assert that there are exactly n matches for the provided matchers. // This method fails the test if the assert is not fulfilled. -func (ei *EventInfoStore) AssertExact(n int, f EventInfoMatcher) []EventInfo { - return ei.AssertInRange(n, n, f) +func (ei *EventInfoStore) AssertExact(n int, matchers ...EventInfoMatcher) []EventInfo { + return ei.AssertInRange(n, n, matchers...) } // Wait a long time (currently 4 minutes) until the provided function matches at least