/* Copyright 2022 The Kubernetes 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 suite import ( "fmt" "strings" "testing" "github.com/alibaba/higress/test/e2e/conformance/utils/config" "github.com/alibaba/higress/test/e2e/conformance/utils/kubernetes" "github.com/alibaba/higress/test/e2e/conformance/utils/roundtripper" "istio.io/istio/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" ) const ( TestAreaAll = "all" TestAreaSetup = "setup" TestAreaRun = "run" TessAreaClean = "clean" ) // ConformanceTestSuite defines the test suite used to run Gateway API // conformance tests. type ConformanceTestSuite struct { Client client.Client RoundTripper roundtripper.RoundTripper GatewayAddress string IngressClassName string Debug bool Cleanup bool BaseManifests []string Applier kubernetes.Applier SkipTests sets.Set[string] ExecuteTests sets.Set[string] TimeoutConfig config.TimeoutConfig SupportedFeatures sets.Set[string] } // Options can be used to initialize a ConformanceTestSuite. type Options struct { SupportedFeatures sets.Set[string] ExemptFeatures sets.Set[string] ExecuteTests string EnableAllSupportedFeatures bool Client client.Client GatewayAddress string IngressClassName string Debug bool RoundTripper roundtripper.RoundTripper BaseManifests []string NamespaceLabels map[string]string // Options for wasm extended features WASMOptions // CleanupBaseResources indicates whether or not the base test // resources such as Gateways should be cleaned up after the run. CleanupBaseResources bool TimeoutConfig config.TimeoutConfig // IsEnvoyConfigTest indicates whether or not the test is for envoy config IsEnvoyConfigTest bool } type WASMOptions struct { IsWasmPluginTest bool WasmPluginType string WasmPluginName string } // New returns a new ConformanceTestSuite. func New(s Options) *ConformanceTestSuite { config.SetupTimeoutConfig(&s.TimeoutConfig) roundTripper := s.RoundTripper if roundTripper == nil { roundTripper = &roundtripper.DefaultRoundTripper{Debug: s.Debug, TimeoutConfig: s.TimeoutConfig} } if s.SupportedFeatures == nil { s.SupportedFeatures = sets.Set[string]{} } if s.IsWasmPluginTest { feature, ok := WasmPluginTypeMap[s.WasmPluginType] if ok { s.SupportedFeatures.Insert(string(feature)) } else { panic("WasmPluginType [" + s.WasmPluginType + "] not support") } } else if s.IsEnvoyConfigTest { s.SupportedFeatures.Insert(string(EnvoyConfigConformanceFeature)) } else if s.EnableAllSupportedFeatures { s.SupportedFeatures = AllFeatures } for feature := range s.ExemptFeatures { s.SupportedFeatures.Delete(feature) } suite := &ConformanceTestSuite{ Client: s.Client, RoundTripper: roundTripper, IngressClassName: s.IngressClassName, Debug: s.Debug, Cleanup: s.CleanupBaseResources, BaseManifests: s.BaseManifests, SupportedFeatures: s.SupportedFeatures, GatewayAddress: s.GatewayAddress, ExecuteTests: sets.New[string](), Applier: kubernetes.Applier{ NamespaceLabels: s.NamespaceLabels, }, TimeoutConfig: s.TimeoutConfig, } // apply defaults if suite.BaseManifests == nil { suite.BaseManifests = []string{ "base/manifests.yaml", "base/consul.yaml", "base/eureka.yaml", "base/nacos.yaml", "base/dubbo.yaml", "base/opa.yaml", } } testNames := strings.Split(s.ExecuteTests, ",") for i := range testNames { if testNames[i] != "" { suite.ExecuteTests = suite.ExecuteTests.Insert(testNames[i]) } } return suite } // Setup ensures the base resources required for conformance tests are installed // in the cluster. It also ensures that all relevant resources are ready. func (suite *ConformanceTestSuite) Setup(t *testing.T) { t.Logf("๐Ÿ“ฆ Test Setup: Ensuring IngressClass has been accepted") suite.Applier.IngressClass = suite.IngressClassName t.Logf("๐Ÿ“ฆ Test Setup: Applying base manifests") for _, baseManifest := range suite.BaseManifests { suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, baseManifest, suite.Cleanup) } t.Logf("๐Ÿ“ฆ Test Setup: Applying programmatic resources") secret := kubernetes.MustCreateSelfSignedCertSecret(t, "higress-conformance-web-backend", "certificate", []string{"*"}) suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) secret = kubernetes.MustCreateSelfSignedCertSecret(t, "higress-conformance-infra", "tls-validity-checks-certificate", []string{"*"}) suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) t.Logf("๐Ÿ“ฆ Test Setup: Ensuring Pods from base manifests are ready") namespaces := []string{ "higress-conformance-infra", "higress-conformance-app-backend", "higress-conformance-web-backend", } kubernetes.NamespacesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, namespaces) t.Logf("๐ŸŒฑ Supported Features: %+v", suite.SupportedFeatures.UnsortedList()) } // Run runs the provided set of conformance tests. func (suite *ConformanceTestSuite) Run(t *testing.T, tests []ConformanceTest) { t.Logf("๐Ÿš€ Start Running %d Test Cases: \n\n%s", len(tests), globalConformanceTestsListInfo(tests)) for _, test := range tests { t.Run(test.ShortName, func(t *testing.T) { test.Run(t, suite) }) } } // Clean cleans up the base resources installed by Setup. func (suite *ConformanceTestSuite) Clean(t *testing.T) { if suite.Cleanup { t.Logf("๐Ÿงน Test Cleanup: Ensuring base resources have been cleaned up") for _, baseManifest := range suite.BaseManifests { suite.Applier.MustDelete(t, suite.Client, suite.TimeoutConfig, baseManifest) } } } func globalConformanceTestsListInfo(tests []ConformanceTest) string { var cases string for index, test := range tests { cases += fmt.Sprintf("๐ŸŽฏ CaseNum: %d\nCaseName: %s\nScenario: %s\nFeatures: %+v\n\n", index+1, test.ShortName, test.Description, test.Features) } return cases } type ConformanceTests []ConformanceTest // ConformanceTest is used to define each individual conformance test. type ConformanceTest struct { ShortName string Description string PreDeleteRs []string Manifests []string Features []SupportedFeature Slow bool Parallel bool Test func(*testing.T, *ConformanceTestSuite) NotCleanup bool } // Run runs an individual tests, applying and cleaning up the required manifests // before calling the Test function. func (test *ConformanceTest) Run(t *testing.T, suite *ConformanceTestSuite) { if test.Parallel { t.Parallel() } // Check that all features exercised by the test have been opted into by // the suite. for _, feature := range test.Features { if !suite.SupportedFeatures.Contains(string(feature)) { t.Skipf("๐ŸŠ๐Ÿผ Skipping %s: suite does not support %s", test.ShortName, feature) } } // check that the test should not be skipped if suite.SkipTests.Contains(test.ShortName) { t.Skipf("๐ŸŠ๐Ÿผ Skipping %s: test explicitly skipped", test.ShortName) } if len(suite.ExecuteTests) > 0 && !suite.ExecuteTests.Contains(test.ShortName) { t.Skipf("๐ŸŠ๐Ÿผ Skipping %s: test explicitly skipped", test.ShortName) } t.Logf("๐Ÿ”ฅ Running Conformance Test: %s", test.ShortName) for _, manifestLocation := range test.PreDeleteRs { t.Logf("๐Ÿงณ Applying PreDeleteRs Manifests: %s", manifestLocation) suite.Applier.MustDelete(t, suite.Client, suite.TimeoutConfig, manifestLocation) } for _, manifestLocation := range test.Manifests { t.Logf("๐Ÿงณ Applying Manifests: %s", manifestLocation) suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, manifestLocation, !test.NotCleanup) } test.Test(t, suite) }