| /* | |
| Copyright 2017 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 framework | |
| import ( | |
| "context" | |
| "flag" | |
| "fmt" | |
| "os/exec" | |
| "strings" | |
| "time" | |
| . "github.com/onsi/ginkgo/v2" | |
| . "github.com/onsi/gomega" | |
| . "github.com/onsi/gomega/gbytes" | |
| . "github.com/onsi/gomega/gexec" | |
| "github.com/sirupsen/logrus" | |
| ) | |
| var ( | |
| crictlBinaryPath string | |
| crictlRuntimeEndpoint string | |
| crictlTimeout string | |
| ) | |
| func init() { | |
| flag.StringVar(&crictlBinaryPath, "crictl-binary-path", "", "`crictl` binary path to be used") | |
| flag.StringVar( | |
| &crictlRuntimeEndpoint, | |
| "crictl-runtime-endpoint", | |
| "", | |
| "`crictl --runtime-endpoint` to be used", | |
| ) | |
| flag.StringVar(&crictlTimeout, "crictl-timeout", "", "`crictl --timeout` to be used (e.g. 30s)") | |
| } | |
| // TestFramework is used to support commonly used test features. | |
| type TestFramework struct { | |
| OtelCollector *OtelCollector | |
| } | |
| // NewTestFramework creates a new test framework instance. | |
| func NewTestFramework() *TestFramework { | |
| return &TestFramework{} | |
| } | |
| // Setup is the global initialization function which runs before each test | |
| // suite. | |
| func (t *TestFramework) Setup() { | |
| // Global initialization for the whole framework goes in here | |
| logrus.SetLevel(logrus.DebugLevel) | |
| logrus.SetOutput(GinkgoWriter) | |
| } | |
| // Teardown is the global deinitialization function which runs after each test | |
| // suite. | |
| func (t *TestFramework) Teardown() { | |
| if t.OtelCollector != nil { | |
| t.OtelCollector.Stop() | |
| } | |
| } | |
| // Describe is a convenience wrapper around the `ginkgo.Describe` function. | |
| func (t *TestFramework) Describe(text string, body func()) bool { | |
| return Describe("crictl: "+text, body) | |
| } | |
| // Convenience method for command creation. | |
| func cmd(workDir, format string, args ...any) *Session { | |
| c := strings.Split(fmt.Sprintf(format, args...), " ") | |
| binary, err := exec.LookPath(c[0]) | |
| Expect(err).ToNot(HaveOccurred()) | |
| command := exec.CommandContext(context.TODO(), binary, c[1:]...) | |
| if workDir != "" { | |
| command.Dir = workDir | |
| } | |
| session, err := Start(command, GinkgoWriter, GinkgoWriter) | |
| Expect(err).ToNot(HaveOccurred()) | |
| return session | |
| } | |
| func crictlBinaryPathFlag() (path string) { | |
| if crictlBinaryPath != "" { | |
| return crictlBinaryPath | |
| } | |
| return "crictl" | |
| } | |
| func crictlRuntimeEndpointFlag() string { | |
| if crictlRuntimeEndpoint != "" { | |
| return " --runtime-endpoint=" + crictlRuntimeEndpoint | |
| } | |
| return "" | |
| } | |
| func crictlTimeoutFlag() string { | |
| if crictlTimeout != "" { | |
| return " --timeout=" + crictlTimeout | |
| } | |
| return "" | |
| } | |
| // Convenience method for command creation in the current working directory. | |
| func lcmd(format string, args ...any) *Session { | |
| return cmd("", format, args...) | |
| } | |
| // Crictl runs crictl on the specified endpoint and returns the resulting session. | |
| func (t *TestFramework) Crictl(args string) *Session { | |
| return lcmd( | |
| "%s%s%s %s", | |
| crictlBinaryPathFlag(), | |
| crictlRuntimeEndpointFlag(), | |
| crictlTimeoutFlag(), | |
| args, | |
| ).Wait(time.Minute) | |
| } | |
| // CrictlNoWait runs crictl on the specified endpoint and returns the resulting session without wait. | |
| func (t *TestFramework) CrictlNoWait(args string) *Session { | |
| return lcmd( | |
| "%s%s%s %s", | |
| crictlBinaryPathFlag(), | |
| crictlRuntimeEndpointFlag(), | |
| crictlTimeoutFlag(), | |
| args, | |
| ) | |
| } | |
| // CrictlWithTracing runs crictl with tracing enabled and returns the resulting session. | |
| // The OtelCollector is started lazily on the first call. | |
| func (t *TestFramework) CrictlWithTracing(args string) *Session { | |
| if t.OtelCollector == nil { | |
| t.OtelCollector = NewOtelCollector() | |
| Expect(t.OtelCollector.Start()).To(Succeed()) | |
| } | |
| t.OtelCollector.ClearSpans() | |
| tracingArgs := fmt.Sprintf("--enable-tracing --tracing-endpoint=%s %s", | |
| t.OtelCollector.GetEndpoint(), args) | |
| return t.Crictl(tracingArgs) | |
| } | |
| // CrictlExpect runs crictl and expects exit, expectedOut, expectedErr. | |
| func (t *TestFramework) CrictlExpect( | |
| args string, exit int, expectedOut, expectedErr string, | |
| ) { | |
| // When | |
| res := t.Crictl(args) | |
| // Then | |
| Expect(res).To(Exit(exit)) | |
| if expectedOut == "" { | |
| Expect(string(res.Out.Contents())).To(BeEmpty()) | |
| } else { | |
| Expect(res.Out).To(Say(expectedOut)) | |
| } | |
| if expectedErr == "" { | |
| Expect(string(res.Err.Contents())).To(BeEmpty()) | |
| } else { | |
| Expect(res.Err).To(Say(expectedErr)) | |
| } | |
| } | |
| // CrictlExpectSuccess runs crictl and expects success containing the specified output. | |
| func (t *TestFramework) CrictlExpectSuccess(args, expectedOut string) { | |
| t.CrictlExpect(args, 0, expectedOut, "") | |
| } | |
| // CrictlExpectFailure runs crictl and expects error containing the specified outputs. | |
| func (t *TestFramework) CrictlExpectFailure( | |
| args string, expectedOut, expectedErr string, | |
| ) { | |
| t.CrictlExpect(args, 1, expectedOut, expectedErr) | |
| } | |
| // CrictlRemovePauseImages can be used to cleanup the pause images. | |
| func (t *TestFramework) CrictlRemovePauseImages() { | |
| res := t.Crictl("images --filter reference=registry.k8s.io/pause -q") | |
| Expect(res).To(Exit(0)) | |
| contents := res.Out.Contents() | |
| if len(contents) > 0 { | |
| output := strings.Split(string(contents), "\n") | |
| t.CrictlExpectSuccess("rmi "+strings.TrimSpace(strings.Join(output, " ")), "Deleted") | |
| } | |
| } | |
| func (t *TestFramework) IsContainerd() bool { | |
| return strings.Contains(crictlRuntimeEndpoint, "containerd") | |
| } |