k8s.io/kubernetes@v1.29.3/test/e2e/framework/internal/unittests/cleanup/cleanup_test.go (about)

     1  //go:build linux && amd64
     2  
     3  /*
     4  Copyright 2022 The Kubernetes Authors.
     5  
     6  Licensed under the Apache License, Version 2.0 (the "License");
     7  you may not use this file except in compliance with the License.
     8  You may obtain a copy of the License at
     9  
    10      http://www.apache.org/licenses/LICENSE-2.0
    11  
    12  Unless required by applicable law or agreed to in writing, software
    13  distributed under the License is distributed on an "AS IS" BASIS,
    14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  See the License for the specific language governing permissions and
    16  limitations under the License.
    17  */
    18  
    19  // This test uses etcd that is only fully supported for AMD64 and Linux
    20  // https://etcd.io/docs/v3.5/op-guide/supported-platform/#support-tiers
    21  
    22  package cleanup
    23  
    24  import (
    25  	"context"
    26  	"flag"
    27  	"fmt"
    28  	"regexp"
    29  	"testing"
    30  
    31  	"github.com/onsi/ginkgo/v2"
    32  	"github.com/onsi/ginkgo/v2/reporters"
    33  
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/klog/v2"
    36  	"k8s.io/klog/v2/ktesting"
    37  	"k8s.io/kubernetes/test/e2e/framework"
    38  	"k8s.io/kubernetes/test/e2e/framework/internal/output"
    39  	testapiserver "k8s.io/kubernetes/test/utils/apiserver"
    40  )
    41  
    42  // The line number of the following code is checked in TestFailureOutput below.
    43  // Be careful when moving it around or changing the import statements above.
    44  // Here are some intentionally blank lines that can be removed to compensate
    45  // for future additional import statements.
    46  //
    47  //
    48  //
    49  //
    50  // This must be line #50.
    51  
    52  func init() {
    53  	framework.NewFrameworkExtensions = append(framework.NewFrameworkExtensions,
    54  		// This callback runs directly after NewDefaultFramework is done.
    55  		func(f *framework.Framework) {
    56  			ginkgo.BeforeEach(func() { framework.Logf("extension before") })
    57  			ginkgo.AfterEach(func() { framework.Logf("extension after") })
    58  		},
    59  	)
    60  }
    61  
    62  var _ = ginkgo.Describe("e2e", func() {
    63  	ginkgo.BeforeEach(func() {
    64  		framework.Logf("before")
    65  	})
    66  
    67  	f := framework.NewDefaultFramework("test-namespace")
    68  
    69  	// BeforeEach/AfterEach run in first-in-first-out order.
    70  
    71  	ginkgo.BeforeEach(func() {
    72  		framework.Logf("before #1")
    73  	})
    74  
    75  	ginkgo.BeforeEach(func() {
    76  		framework.Logf("before #2")
    77  	})
    78  
    79  	ginkgo.AfterEach(func() {
    80  		framework.Logf("after #1")
    81  		if f.ClientSet == nil {
    82  			framework.Fail("Wrong order of cleanup operations: framework.AfterEach already ran and cleared f.ClientSet.")
    83  		}
    84  	})
    85  
    86  	ginkgo.AfterEach(func() {
    87  		framework.Logf("after #2")
    88  	})
    89  
    90  	ginkgo.It("works", func(ctx context.Context) {
    91  		// DeferCleanup invokes in first-in-last-out order
    92  		ginkgo.DeferCleanup(func() {
    93  			framework.Logf("cleanup last")
    94  		})
    95  		ginkgo.DeferCleanup(func() {
    96  			framework.Logf("cleanup first")
    97  		})
    98  
    99  		ginkgo.DeferCleanup(framework.IgnoreNotFound(f.ClientSet.CoreV1().PersistentVolumes().Delete), "simple", metav1.DeleteOptions{})
   100  		fail := func(ctx context.Context, name string) error {
   101  			return fmt.Errorf("fake error for %q", name)
   102  		}
   103  		ginkgo.DeferCleanup(framework.IgnoreNotFound(fail), "failure") // Without a failure the output would not be shown in JUnit.
   104  
   105  		// More test cases can be added here without affeccting line numbering
   106  		// of existing tests.
   107  	})
   108  })
   109  
   110  const (
   111  	ginkgoOutput = `> Enter [BeforeEach] e2e - cleanup_test.go:63 <time>
   112  INFO: before
   113  < Exit [BeforeEach] e2e - cleanup_test.go:63 <time>
   114  > Enter [BeforeEach] e2e - set up framework | framework.go:xxx <time>
   115  STEP: Creating a kubernetes client - framework.go:xxx <time>
   116  INFO: >>> kubeConfig: yyy/kube.config
   117  STEP: Building a namespace api object, basename test-namespace - framework.go:xxx <time>
   118  INFO: Skipping waiting for service account
   119  < Exit [BeforeEach] e2e - set up framework | framework.go:xxx <time>
   120  > Enter [BeforeEach] e2e - cleanup_test.go:56 <time>
   121  INFO: extension before
   122  < Exit [BeforeEach] e2e - cleanup_test.go:56 <time>
   123  > Enter [BeforeEach] e2e - cleanup_test.go:71 <time>
   124  INFO: before #1
   125  < Exit [BeforeEach] e2e - cleanup_test.go:71 <time>
   126  > Enter [BeforeEach] e2e - cleanup_test.go:75 <time>
   127  INFO: before #2
   128  < Exit [BeforeEach] e2e - cleanup_test.go:75 <time>
   129  > Enter [It] works - cleanup_test.go:90 <time>
   130  < Exit [It] works - cleanup_test.go:90 <time>
   131  > Enter [AfterEach] e2e - cleanup_test.go:57 <time>
   132  INFO: extension after
   133  < Exit [AfterEach] e2e - cleanup_test.go:57 <time>
   134  > Enter [AfterEach] e2e - cleanup_test.go:79 <time>
   135  INFO: after #1
   136  < Exit [AfterEach] e2e - cleanup_test.go:79 <time>
   137  > Enter [AfterEach] e2e - cleanup_test.go:86 <time>
   138  INFO: after #2
   139  < Exit [AfterEach] e2e - cleanup_test.go:86 <time>
   140  > Enter [DeferCleanup (Each)] e2e - cleanup_test.go:103 <time>
   141  [FAILED] DeferCleanup callback returned error: fake error for "failure"
   142  In [DeferCleanup (Each)] at: cleanup_test.go:103 <time>
   143  < Exit [DeferCleanup (Each)] e2e - cleanup_test.go:103 <time>
   144  > Enter [DeferCleanup (Each)] e2e - cleanup_test.go:99 <time>
   145  < Exit [DeferCleanup (Each)] e2e - cleanup_test.go:99 <time>
   146  > Enter [DeferCleanup (Each)] e2e - cleanup_test.go:95 <time>
   147  INFO: cleanup first
   148  < Exit [DeferCleanup (Each)] e2e - cleanup_test.go:95 <time>
   149  > Enter [DeferCleanup (Each)] e2e - cleanup_test.go:92 <time>
   150  INFO: cleanup last
   151  < Exit [DeferCleanup (Each)] e2e - cleanup_test.go:92 <time>
   152  > Enter [DeferCleanup (Each)] e2e - dump namespaces | framework.go:xxx <time>
   153  < Exit [DeferCleanup (Each)] e2e - dump namespaces | framework.go:xxx <time>
   154  > Enter [DeferCleanup (Each)] e2e - tear down framework | framework.go:xxx <time>
   155  STEP: Destroying namespace "test-namespace-zzz" for this suite. - framework.go:xxx <time>
   156  < Exit [DeferCleanup (Each)] e2e - tear down framework | framework.go:xxx <time>
   157  `
   158  )
   159  
   160  func TestCleanup(t *testing.T) {
   161  	// The control plane is noisy and randomly logs through klog, for example:
   162  	// E0912 07:08:46.100164   75466 controller.go:254] unable to sync kubernetes service: Endpoints "kubernetes" is invalid: subsets[0].addresses[0].ip: Invalid value: "127.0.0.1": may not be in the loopback range (127.0.0.0/8, ::1/128)
   163  	//
   164  	// By creating a ktesting logger and registering that as global
   165  	// default logger we get the control plane output into the
   166  	// "go test" output in case of a failure (useful for debugging!)
   167  	// while keeping it out of the captured Ginkgo output that
   168  	// the test is comparing below.
   169  	//
   170  	// There are some small drawbacks:
   171  	// - The source code location for control plane log messages
   172  	//   is shown as klog.go because klog does not properly
   173  	//   skip its own helper functions. That's okay, normally
   174  	//   ktesting should not be installed as logging backend like this.
   175  	// - klog.Infof messages are printed with an extra newline.
   176  	logger, _ := ktesting.NewTestContext(t)
   177  	klog.SetLogger(logger)
   178  
   179  	apiServer := testapiserver.StartAPITestServer(t)
   180  
   181  	// This simulates how test/e2e uses the framework and how users
   182  	// invoke test/e2e.
   183  	framework.RegisterCommonFlags(flag.CommandLine)
   184  	framework.RegisterClusterFlags(flag.CommandLine)
   185  	for flagname, value := range map[string]string{
   186  		"kubeconfig": apiServer.KubeConfigFile,
   187  		// Some features are not supported by the fake cluster.
   188  		"e2e-verify-service-account": "false",
   189  		"allowed-not-ready-nodes":    "-1",
   190  		// This simplifies the text comparison.
   191  		"ginkgo.no-color": "true",
   192  	} {
   193  		if err := flag.Set(flagname, value); err != nil {
   194  			t.Fatalf("set %s: %v", flagname, err)
   195  		}
   196  	}
   197  	framework.AfterReadingAllFlags(&framework.TestContext)
   198  	suiteConfig, reporterConfig := framework.CreateGinkgoConfig()
   199  
   200  	expected := output.TestResult{
   201  		NormalizeOutput: normalizeOutput,
   202  		Suite: reporters.JUnitTestSuite{
   203  			Tests:    1,
   204  			Failures: 1,
   205  			Errors:   0,
   206  			Disabled: 0,
   207  			Skipped:  0,
   208  
   209  			TestCases: []reporters.JUnitTestCase{
   210  				{
   211  					Name:   "[It] e2e works",
   212  					Status: "failed",
   213  					Failure: &reporters.JUnitFailure{
   214  						Type: "failed",
   215  						Description: `[FAILED] DeferCleanup callback returned error: fake error for "failure"
   216  In [DeferCleanup (Each)] at: cleanup_test.go:103 <time>
   217  `,
   218  					},
   219  					SystemErr: ginkgoOutput,
   220  				},
   221  			},
   222  		},
   223  	}
   224  
   225  	output.TestGinkgoOutput(t, expected, suiteConfig, reporterConfig)
   226  }
   227  
   228  func normalizeOutput(output string) string {
   229  	for exp, replacement := range map[string]string{
   230  		// Ignore line numbers inside framework source code (likely to change).
   231  		`framework\.go:\d+`: `framework.go:xxx`,
   232  		// Config file name varies for each run.
   233  		`kubeConfig: .*/kube.config`: `kubeConfig: yyy/kube.config`,
   234  		// Random suffix for namespace.
   235  		`test-namespace-\d+`: `test-namespace-zzz`,
   236  	} {
   237  		output = regexp.MustCompile(exp).ReplaceAllString(output, replacement)
   238  	}
   239  	return output
   240  }