From 74bcfc2f969ad55a651c4ced257fc7c60a581966 Mon Sep 17 00:00:00 2001 From: Yiqiao Pu Date: Mon, 29 Oct 2018 14:56:07 +0800 Subject: Separate common used test functions and structs to test/utils Put common used test functions and structs to a separated package. So we can use them for more testsuites. Signed-off-by: Yiqiao Pu --- test/utils/common_function_test.go | 150 +++++++++++++ test/utils/podmansession_test.go | 90 ++++++++ test/utils/podmantest_test.go | 74 +++++++ test/utils/utils.go | 431 +++++++++++++++++++++++++++++++++++++ test/utils/utils_suite_test.go | 52 +++++ 5 files changed, 797 insertions(+) create mode 100644 test/utils/common_function_test.go create mode 100644 test/utils/podmansession_test.go create mode 100644 test/utils/podmantest_test.go create mode 100644 test/utils/utils.go create mode 100644 test/utils/utils_suite_test.go (limited to 'test/utils') diff --git a/test/utils/common_function_test.go b/test/utils/common_function_test.go new file mode 100644 index 000000000..1648a4899 --- /dev/null +++ b/test/utils/common_function_test.go @@ -0,0 +1,150 @@ +package utils_test + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "reflect" + "strings" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("Common functions test", func() { + var defaultOSPath string + var defaultCgroupPath string + + BeforeEach(func() { + defaultOSPath = OSReleasePath + defaultCgroupPath = ProcessOneCgroupPath + }) + + AfterEach(func() { + OSReleasePath = defaultOSPath + ProcessOneCgroupPath = defaultCgroupPath + }) + + It("Test CreateTempDirInTempDir", func() { + tmpDir, _ := CreateTempDirInTempDir() + _, err := os.Stat(tmpDir) + Expect(os.IsNotExist(err)).ShouldNot(BeTrue(), "Directory is not created as expect") + }) + + It("Test SystemExec", func() { + session := SystemExec(GoechoPath, []string{}) + Expect(session.Command.Process).ShouldNot(BeNil(), "SystemExec can not start a process") + }) + + It("Test StringInSlice", func() { + testSlice := []string{"apple", "peach", "pear"} + Expect(StringInSlice("apple", testSlice)).To(BeTrue(), "apple should in ['apple', 'peach', 'pear']") + Expect(StringInSlice("banana", testSlice)).ShouldNot(BeTrue(), "banana should not in ['apple', 'peach', 'pear']") + Expect(StringInSlice("anything", []string{})).ShouldNot(BeTrue(), "anything should not in empty slice") + }) + + DescribeTable("Test GetHostDistributionInfo", + func(path, id, ver string, empty bool) { + txt := fmt.Sprintf("ID=%s\nVERSION_ID=%s", id, ver) + if !empty { + f, _ := os.Create(path) + f.WriteString(txt) + f.Close() + } + + OSReleasePath = path + host := GetHostDistributionInfo() + if empty { + Expect(host).To(Equal(HostOS{}), "HostOs should be empty.") + } else { + Expect(host.Distribution).To(Equal(strings.Trim(id, "\""))) + Expect(host.Version).To(Equal(strings.Trim(ver, "\""))) + } + }, + Entry("Configure file is not exist.", "/tmp/notexist", "", "", true), + Entry("Item value with and without \"", "/tmp/os-release.test", "fedora", "\"28\"", false), + Entry("Item empty with and without \"", "/tmp/os-release.test", "", "\"\"", false), + ) + + DescribeTable("Test IsKernelNewerThan", + func(kv string, expect, isNil bool) { + newer, err := IsKernelNewerThan(kv) + Expect(newer).To(Equal(expect), "Version compare results is not as expect.") + Expect(err == nil).To(Equal(isNil), "Error is not as expect.") + }, + Entry("Invlid kernel version: 0", "0", false, false), + Entry("Older kernel version:0.0", "0.0", true, true), + Entry("Newer kernel version: 100.17.14", "100.17.14", false, true), + Entry("Invlid kernel version: I am not a kernel version", "I am not a kernel version", false, false), + ) + + DescribeTable("Test TestIsCommandAvailable", + func(cmd string, expect bool) { + cmdExist := IsCommandAvailable(cmd) + Expect(cmdExist).To(Equal(expect)) + }, + Entry("Command exist", GoechoPath, true), + Entry("Command exist", "Fakecmd", false), + ) + + It("Test WriteJsonFile", func() { + type testJson struct { + Item1 int + Item2 []string + } + compareData := &testJson{} + + testData := &testJson{ + Item1: 5, + Item2: []string{"test"}, + } + + testByte, _ := json.Marshal(testData) + err := WriteJsonFile(testByte, "/tmp/testJson") + + Expect(err).To(BeNil(), "Failed to write JSON to file.") + + read, err := os.Open("/tmp/testJson") + defer read.Close() + + Expect(err).To(BeNil(), "Can not find the JSON file after we write it.") + + bytes, _ := ioutil.ReadAll(read) + json.Unmarshal(bytes, compareData) + + Expect(reflect.DeepEqual(testData, compareData)).To(BeTrue(), "Data chaned after we store it to file.") + }) + + DescribeTable("Test Containerized", + func(path string, setEnv, createFile, expect bool) { + if setEnv && (os.Getenv("container") == "") { + os.Setenv("container", "test") + defer os.Setenv("container", "") + } + if !setEnv && (os.Getenv("container") != "") { + containerized := os.Getenv("container") + os.Setenv("container", "") + defer os.Setenv("container", containerized) + } + txt := "1:test:/" + if expect { + txt = "2:docker:/" + } + if createFile { + f, _ := os.Create(path) + f.WriteString(txt) + f.Close() + } + ProcessOneCgroupPath = path + Expect(Containerized()).To(Equal(expect)) + }, + Entry("Set container in env", "", true, false, true), + Entry("Can not read from file", "/tmp/notexist", false, false, false), + Entry("Docker in cgroup file", "/tmp/cgroup.test", false, true, true), + Entry("Docker not in cgroup file", "/tmp/cgroup.test", false, true, false), + ) + +}) diff --git a/test/utils/podmansession_test.go b/test/utils/podmansession_test.go new file mode 100644 index 000000000..de8c20b24 --- /dev/null +++ b/test/utils/podmansession_test.go @@ -0,0 +1,90 @@ +package utils_test + +import ( + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("PodmanSession test", func() { + var session *PodmanSession + + BeforeEach(func() { + session = StartFakeCmdSession([]string{"PodmanSession", "test", "Podman Session"}) + session.WaitWithDefaultTimeout() + }) + + It("Test OutputToString", func() { + Expect(session.OutputToString()).To(Equal("PodmanSession test Podman Session")) + }) + + It("Test OutputToStringArray", func() { + Expect(session.OutputToStringArray()).To(Equal([]string{"PodmanSession", "test", "Podman Session"})) + }) + + It("Test ErrorToString", func() { + Expect(session.ErrorToString()).To(Equal("PodmanSession test Podman Session")) + }) + + It("Test ErrorToStringArray", func() { + Expect(session.ErrorToStringArray()).To(Equal([]string{"PodmanSession", "test", "Podman Session", ""})) + }) + + It("Test GrepString", func() { + match, backStr := session.GrepString("Session") + Expect(match).To(BeTrue()) + Expect(backStr).To(Equal([]string{"PodmanSession", "Podman Session"})) + + match, backStr = session.GrepString("I am not here") + Expect(match).To(Not(BeTrue())) + Expect(backStr).To(BeNil()) + + }) + + It("Test ErrorGrepString", func() { + match, backStr := session.ErrorGrepString("Session") + Expect(match).To(BeTrue()) + Expect(backStr).To(Equal([]string{"PodmanSession", "Podman Session"})) + + match, backStr = session.ErrorGrepString("I am not here") + Expect(match).To(Not(BeTrue())) + Expect(backStr).To(BeNil()) + + }) + + It("Test LineInOutputStartsWith", func() { + Expect(session.LineInOuputStartsWith("Podman")).To(BeTrue()) + Expect(session.LineInOuputStartsWith("Session")).To(Not(BeTrue())) + }) + + It("Test LineInOutputContains", func() { + Expect(session.LineInOutputContains("Podman")).To(BeTrue()) + Expect(session.LineInOutputContains("Session")).To(BeTrue()) + Expect(session.LineInOutputContains("I am not here")).To(Not(BeTrue())) + }) + + It("Test LineInOutputContainsTag", func() { + session = StartFakeCmdSession([]string{"HEAD LINE", "docker.io/library/busybox latest e1ddd7948a1c 5 weeks ago 1.38MB"}) + session.WaitWithDefaultTimeout() + Expect(session.LineInOutputContainsTag("docker.io/library/busybox", "latest")).To(BeTrue()) + Expect(session.LineInOutputContainsTag("busybox", "latest")).To(Not(BeTrue())) + }) + + It("Test IsJSONOutputValid", func() { + session = StartFakeCmdSession([]string{`{"page":1,"fruits":["apple","peach","pear"]}`}) + session.WaitWithDefaultTimeout() + Expect(session.IsJSONOutputValid()).To(BeTrue()) + + session = StartFakeCmdSession([]string{"I am not JSON"}) + session.WaitWithDefaultTimeout() + Expect(session.IsJSONOutputValid()).To(Not(BeTrue())) + }) + + It("Test WaitWithDefaultTimeout", func() { + session = StartFakeCmdSession([]string{"sleep", "2"}) + Expect(session.ExitCode()).Should(Equal(-1)) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).Should(Equal(0)) + }) + +}) diff --git a/test/utils/podmantest_test.go b/test/utils/podmantest_test.go new file mode 100644 index 000000000..87f756920 --- /dev/null +++ b/test/utils/podmantest_test.go @@ -0,0 +1,74 @@ +package utils_test + +import ( + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("PodmanTest test", func() { + var podmanTest *FakePodmanTest + + BeforeEach(func() { + podmanTest = FakePodmanTestCreate() + }) + + AfterEach(func() { + FakeOutputs = make(map[string][]string) + }) + + It("Test PodmanAsUser", func() { + FakeOutputs["check"] = []string{"check"} + os.Setenv("HOOK_OPTION", "hook_option") + env := os.Environ() + session := podmanTest.PodmanAsUser([]string{"check"}, 1000, 1000, env) + os.Unsetenv("HOOK_OPTION") + session.WaitWithDefaultTimeout() + Expect(session.Command.Process).ShouldNot(BeNil()) + }) + + It("Test NumberOfContainersRunning", func() { + FakeOutputs["ps -q"] = []string{"one", "two"} + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2)) + }) + + It("Test NumberOfContainers", func() { + FakeOutputs["ps -aq"] = []string{"one", "two"} + Expect(podmanTest.NumberOfContainers()).To(Equal(2)) + }) + + It("Test NumberOfPods", func() { + FakeOutputs["pod ps -q"] = []string{"one", "two"} + Expect(podmanTest.NumberOfPods()).To(Equal(2)) + }) + + It("Test WaitForContainer", func() { + FakeOutputs["ps -q"] = []string{"one", "two"} + Expect(WaitForContainer(podmanTest)).To(BeTrue()) + + FakeOutputs["ps -q"] = []string{"one"} + Expect(WaitForContainer(podmanTest)).To(BeTrue()) + + FakeOutputs["ps -q"] = []string{""} + Expect(WaitForContainer(podmanTest)).To(Not(BeTrue())) + }) + + It("Test GetContainerStatus", func() { + FakeOutputs["ps --all --format={{.Status}}"] = []string{"Need func update"} + Expect(podmanTest.GetContainerStatus()).To(Equal("Need func update")) + }) + + It("Test WaitContainerReady", func() { + FakeOutputs["logs testimage"] = []string{""} + Expect(WaitContainerReady(podmanTest, "testimage", "ready", 2, 1)).To(Not(BeTrue())) + + FakeOutputs["logs testimage"] = []string{"I am ready"} + Expect(WaitContainerReady(podmanTest, "testimage", "am ready", 2, 1)).To(BeTrue()) + + FakeOutputs["logs testimage"] = []string{"I am ready"} + Expect(WaitContainerReady(podmanTest, "testimage", "", 2, 1)).To(BeTrue()) + }) + +}) diff --git a/test/utils/utils.go b/test/utils/utils.go new file mode 100644 index 000000000..e61171269 --- /dev/null +++ b/test/utils/utils.go @@ -0,0 +1,431 @@ +package utils + +import ( + "bufio" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "strings" + "time" + + "github.com/containers/storage/pkg/parsers/kernel" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var ( + defaultWaitTimeout = 90 + OSReleasePath = "/etc/os-release" + ProcessOneCgroupPath = "/proc/1/cgroup" +) + +// PodmanTestCommon contains common functions will be updated later in +// the inheritance structs +type PodmanTestCommon interface { + MakeOptions(args []string) []string + WaitForContainer() bool + WaitContainerReady(id string, expStr string, timeout int, step int) bool +} + +// PodmanTest struct for command line options +type PodmanTest struct { + PodmanMakeOptions func(args []string) []string + PodmanBinary string + ArtifactPath string + TempDir string +} + +// PodmanSession wraps the gexec.session so we can extend it +type PodmanSession struct { + *gexec.Session +} + +// HostOS is a simple struct for the test os +type HostOS struct { + Distribution string + Version string + Arch string +} + +// MakeOptions assembles all podman options +func (p *PodmanTest) MakeOptions(args []string) []string { + return p.PodmanMakeOptions(args) +} + +// PodmanAsUser exec podman as user. uid and gid is set for credentials useage. env is used +// to record the env for debugging +func (p *PodmanTest) PodmanAsUser(args []string, uid, gid uint32, env []string) *PodmanSession { + var command *exec.Cmd + podmanOptions := p.MakeOptions(args) + + if env == nil { + fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) + } else { + fmt.Printf("Running: (env: %v) %s %s\n", env, p.PodmanBinary, strings.Join(podmanOptions, " ")) + } + if uid != 0 || gid != 0 { + nsEnterOpts := append([]string{"--userspec", fmt.Sprintf("%d:%d", uid, gid), "/", p.PodmanBinary}, podmanOptions...) + command = exec.Command("chroot", nsEnterOpts...) + } else { + command = exec.Command(p.PodmanBinary, podmanOptions...) + } + if env != nil { + command.Env = env + } + + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + if err != nil { + Fail(fmt.Sprintf("unable to run podman command: %s\n%v", strings.Join(podmanOptions, " "), err)) + } + return &PodmanSession{session} +} + +// PodmanBase exec podman with default env. +func (p *PodmanTest) PodmanBase(args []string) *PodmanSession { + return p.PodmanAsUser(args, 0, 0, nil) +} + +// WaitForContainer waits on a started container +func (p *PodmanTest) WaitForContainer() bool { + for i := 0; i < 10; i++ { + if p.NumberOfContainersRunning() > 0 { + return true + } + time.Sleep(1 * time.Second) + } + return false +} + +// NumberOfContainersRunning returns an int of how many +// containers are currently running. +func (p *PodmanTest) NumberOfContainersRunning() int { + var containers []string + ps := p.PodmanBase([]string{"ps", "-q"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + for _, i := range ps.OutputToStringArray() { + if i != "" { + containers = append(containers, i) + } + } + return len(containers) +} + +// NumberOfContainers returns an int of how many +// containers are currently defined. +func (p *PodmanTest) NumberOfContainers() int { + var containers []string + ps := p.PodmanBase([]string{"ps", "-aq"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + for _, i := range ps.OutputToStringArray() { + if i != "" { + containers = append(containers, i) + } + } + return len(containers) +} + +// NumberOfPods returns an int of how many +// pods are currently defined. +func (p *PodmanTest) NumberOfPods() int { + var pods []string + ps := p.PodmanBase([]string{"pod", "ps", "-q"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + for _, i := range ps.OutputToStringArray() { + if i != "" { + pods = append(pods, i) + } + } + return len(pods) +} + +// GetContainerStatus returns the containers state. +// This function assumes only one container is active. +func (p *PodmanTest) GetContainerStatus() string { + var podmanArgs = []string{"ps"} + podmanArgs = append(podmanArgs, "--all", "--format={{.Status}}") + session := p.PodmanBase(podmanArgs) + session.WaitWithDefaultTimeout() + return session.OutputToString() +} + +// WaitContainerReady waits process or service inside container start, and ready to be used. +func (p *PodmanTest) WaitContainerReady(id string, expStr string, timeout int, step int) bool { + startTime := time.Now() + s := p.PodmanBase([]string{"logs", id}) + s.WaitWithDefaultTimeout() + + for { + if time.Since(startTime) >= time.Duration(timeout)*time.Second { + fmt.Printf("Container %s is not ready in %ds", id, timeout) + return false + } + + if strings.Contains(s.OutputToString(), expStr) { + return true + } + time.Sleep(time.Duration(step) * time.Second) + s = p.PodmanBase([]string{"logs", id}) + s.WaitWithDefaultTimeout() + } +} + +// WaitForContainer is a wrapper function for accept inheritance PodmanTest struct. +func WaitForContainer(p PodmanTestCommon) bool { + return p.WaitForContainer() +} + +// WaitForContainerReady is a wrapper function for accept inheritance PodmanTest struct. +func WaitContainerReady(p PodmanTestCommon, id string, expStr string, timeout int, step int) bool { + return p.WaitContainerReady(id, expStr, timeout, step) +} + +// OutputToString formats session output to string +func (s *PodmanSession) OutputToString() string { + fields := strings.Fields(fmt.Sprintf("%s", s.Out.Contents())) + return strings.Join(fields, " ") +} + +// OutputToStringArray returns the output as a []string +// where each array item is a line split by newline +func (s *PodmanSession) OutputToStringArray() []string { + var results []string + output := fmt.Sprintf("%s", s.Out.Contents()) + for _, line := range strings.Split(output, "\n") { + if line != "" { + results = append(results, line) + } + } + return results +} + +// ErrorToString formats session stderr to string +func (s *PodmanSession) ErrorToString() string { + fields := strings.Fields(fmt.Sprintf("%s", s.Err.Contents())) + return strings.Join(fields, " ") +} + +// ErrorToStringArray returns the stderr output as a []string +// where each array item is a line split by newline +func (s *PodmanSession) ErrorToStringArray() []string { + output := fmt.Sprintf("%s", s.Err.Contents()) + return strings.Split(output, "\n") +} + +// GrepString takes session output and behaves like grep. it returns a bool +// if successful and an array of strings on positive matches +func (s *PodmanSession) GrepString(term string) (bool, []string) { + var ( + greps []string + matches bool + ) + + for _, line := range s.OutputToStringArray() { + if strings.Contains(line, term) { + matches = true + greps = append(greps, line) + } + } + return matches, greps +} + +// ErrorGrepString takes session stderr output and behaves like grep. it returns a bool +// if successful and an array of strings on positive matches +func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) { + var ( + greps []string + matches bool + ) + + for _, line := range s.ErrorToStringArray() { + if strings.Contains(line, term) { + matches = true + greps = append(greps, line) + } + } + return matches, greps +} + +//LineInOutputStartsWith returns true if a line in a +// session output starts with the supplied string +func (s *PodmanSession) LineInOuputStartsWith(term string) bool { + for _, i := range s.OutputToStringArray() { + if strings.HasPrefix(i, term) { + return true + } + } + return false +} + +//LineInOutputContains returns true if a line in a +// session output starts with the supplied string +func (s *PodmanSession) LineInOutputContains(term string) bool { + for _, i := range s.OutputToStringArray() { + if strings.Contains(i, term) { + return true + } + } + return false +} + +//LineInOutputContainsTag returns true if a line in the +// session's output contains the repo-tag pair as returned +// by podman-images(1). +func (s *PodmanSession) LineInOutputContainsTag(repo, tag string) bool { + tagMap := tagOutputToMap(s.OutputToStringArray()) + for r, t := range tagMap { + if repo == r && tag == t { + return true + } + } + return false +} + +// IsJSONOutputValid attempts to unmarshal the session buffer +// and if successful, returns true, else false +func (s *PodmanSession) IsJSONOutputValid() bool { + var i interface{} + if err := json.Unmarshal(s.Out.Contents(), &i); err != nil { + fmt.Println(err) + return false + } + return true +} + +// WaitWithDefaultTimeout waits for process finished with defaultWaitTimeout +func (s *PodmanSession) WaitWithDefaultTimeout() { + s.Wait(defaultWaitTimeout) + fmt.Println("output:", s.OutputToString()) +} + +// CreateTempDirinTempDir create a temp dir with prefix podman_test +func CreateTempDirInTempDir() (string, error) { + return ioutil.TempDir("", "podman_test") +} + +// SystemExec is used to exec a system command to check its exit code or output +func SystemExec(command string, args []string) *PodmanSession { + c := exec.Command(command, args...) + session, err := gexec.Start(c, GinkgoWriter, GinkgoWriter) + if err != nil { + Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " "))) + } + return &PodmanSession{session} +} + +// StringInSlice determines if a string is in a string slice, returns bool +func StringInSlice(s string, sl []string) bool { + for _, i := range sl { + if i == s { + return true + } + } + return false +} + +//tagOutPutToMap parses each string in imagesOutput and returns +// a map of repo:tag pairs. Notice, the first array item will +// be skipped as it's considered to be the header. +func tagOutputToMap(imagesOutput []string) map[string]string { + m := make(map[string]string) + // iterate over output but skip the header + for _, i := range imagesOutput[1:] { + tmp := []string{} + for _, x := range strings.Split(i, " ") { + if x != "" { + tmp = append(tmp, x) + } + } + // podman-images(1) return a list like output + // in the format of "Repository Tag [...]" + if len(tmp) < 2 { + continue + } + m[tmp[0]] = tmp[1] + } + return m +} + +//GetHostDistributionInfo returns a struct with its distribution name and version +func GetHostDistributionInfo() HostOS { + f, err := os.Open(OSReleasePath) + defer f.Close() + if err != nil { + return HostOS{} + } + + l := bufio.NewScanner(f) + host := HostOS{} + host.Arch = runtime.GOARCH + for l.Scan() { + if strings.HasPrefix(l.Text(), "ID=") { + host.Distribution = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1) + } + if strings.HasPrefix(l.Text(), "VERSION_ID=") { + host.Version = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1) + } + } + return host +} + +// IsKernelNewerThan compares the current kernel version to one provided. If +// the kernel is equal to or greater, returns true +func IsKernelNewerThan(version string) (bool, error) { + inputVersion, err := kernel.ParseRelease(version) + if err != nil { + return false, err + } + kv, err := kernel.GetKernelVersion() + if err != nil { + return false, err + } + + // CompareKernelVersion compares two kernel.VersionInfo structs. + // Returns -1 if a < b, 0 if a == b, 1 it a > b + result := kernel.CompareKernelVersion(*kv, *inputVersion) + if result >= 0 { + return true, nil + } + return false, nil + +} + +//IsCommandAvaible check if command exist +func IsCommandAvailable(command string) bool { + check := exec.Command("bash", "-c", strings.Join([]string{"command -v", command}, " ")) + err := check.Run() + if err != nil { + return false + } + return true +} + +// WriteJsonFile write json format data to a json file +func WriteJsonFile(data []byte, filePath string) error { + var jsonData map[string]interface{} + json.Unmarshal(data, &jsonData) + formatJson, _ := json.MarshalIndent(jsonData, "", " ") + return ioutil.WriteFile(filePath, formatJson, 0644) +} + +// Containerized check the podman command run inside container +func Containerized() bool { + container := os.Getenv("container") + if container != "" { + return true + } + b, err := ioutil.ReadFile(ProcessOneCgroupPath) + if err != nil { + // shrug, if we cannot read that file, return false + return false + } + if strings.Index(string(b), "docker") > -1 { + return true + } + return false +} diff --git a/test/utils/utils_suite_test.go b/test/utils/utils_suite_test.go new file mode 100644 index 000000000..b1100892b --- /dev/null +++ b/test/utils/utils_suite_test.go @@ -0,0 +1,52 @@ +package utils_test + +import ( + "fmt" + "io" + "os/exec" + "strings" + "testing" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var FakeOutputs map[string][]string +var GoechoPath = "../goecho/goecho" + +type FakePodmanTest struct { + PodmanTest +} + +func FakePodmanTestCreate() *FakePodmanTest { + FakeOutputs = make(map[string][]string) + p := &FakePodmanTest{ + PodmanTest: PodmanTest{ + PodmanBinary: GoechoPath, + }, + } + + p.PodmanMakeOptions = p.makeOptions + return p +} + +func (p *FakePodmanTest) makeOptions(args []string) []string { + return FakeOutputs[strings.Join(args, " ")] +} + +func StartFakeCmdSession(args []string) *PodmanSession { + var outWriter, errWriter io.Writer + command := exec.Command(GoechoPath, args...) + session, err := gexec.Start(command, outWriter, errWriter) + if err != nil { + fmt.Println(err) + } + return &PodmanSession{session} +} + +func TestUtils(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Unit test for test utils package") +} -- cgit v1.2.3-54-g00ecf From a7122d68de0d93ea22da4f44e7f92c75198af771 Mon Sep 17 00:00:00 2001 From: Yiqiao Pu Date: Mon, 29 Oct 2018 14:56:23 +0800 Subject: The system test write with ginkgo The tests can be filter by --focus and --skip to fit different test target. Also be able to set global options and cmd options by export it to ENV to fit different test matrix. Signed-off-by: Yiqiao Pu --- Makefile | 5 +- test/README.md | 18 ++++ test/system/libpod_suite_test.go | 217 +++++++++++++++++++++++++++++++++++++++ test/system/version_test.go | 51 +++++++++ test/utils/utils.go | 1 + 5 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 test/system/libpod_suite_test.go create mode 100644 test/system/version_test.go (limited to 'test/utils') diff --git a/Makefile b/Makefile index 18f60d1dd..144e54528 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions OCIUMOUNTINSTALLDIR=$(PREFIX)/share/oci-umount/oci-umount.d SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z) -PACKAGES ?= $(shell $(GO) list -tags "${BUILDTAGS}" ./... | grep -v github.com/containers/libpod/vendor | grep -v e2e ) +PACKAGES ?= $(shell $(GO) list -tags "${BUILDTAGS}" ./... | grep -v github.com/containers/libpod/vendor | grep -v e2e | grep -v system ) COMMIT_NO ?= $(shell git rev-parse HEAD 2> /dev/null || true) GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),"${COMMIT_NO}-dirty","${COMMIT_NO}") @@ -178,6 +178,9 @@ ginkgo: localintegration: varlink_generate test-binaries clientintegration ginkgo +localsystem: + ginkgo -v -noColor test/system/ + clientintegration: $(MAKE) -C contrib/python/podman integration $(MAKE) -C contrib/python/pypodman integration diff --git a/test/README.md b/test/README.md index 7a1145a9b..2a9a4d4b1 100644 --- a/test/README.md +++ b/test/README.md @@ -100,3 +100,21 @@ make shell ``` This will run a container and give you a shell and you can follow the instructions above. + +# System test +System tests are used for testing the *podman* CLI in the context of a complete system. It +requires that *podman*, all dependencies, and configurations are in place. The intention of +system testing is to match as closely as possible with real-world user/developer use-cases +and environments. The orchestration of the environments and tests is left to external +tooling. + +* `PodmanTestSystem`: System test *struct* as a composite of `PodmanTest`. It will not add any +options to the command by default. When you run system test, you can set GLOBALOPTIONS, +PODMAN_SUBCMD_OPTIONS or PODMAN_BINARY in ENV to run the test suite for different test matrices. + +## Run system test +You can run the test with following command: + +``` +make localsystem +``` diff --git a/test/system/libpod_suite_test.go b/test/system/libpod_suite_test.go new file mode 100644 index 000000000..5de50e4e7 --- /dev/null +++ b/test/system/libpod_suite_test.go @@ -0,0 +1,217 @@ +package system + +import ( + "fmt" + "os" + "strings" + "testing" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var ( + PODMAN_BINARY string + GLOBALOPTIONS = []string{"--cgroup-manager", + "--cni-config-dir", + "--config", "-c", + "--conmon", + "--cpu-profile", + "--log-level", + "--root", + "--tmpdir", + "--runroot", + "--runtime", + "--storage-driver", + "--storage-opt", + "--syslog", + } + PODMAN_SUBCMD = []string{"attach", + "commit", + "container", + "build", + "create", + "diff", + "exec", + "export", + "history", + "image", + "images", + "import", + "info", + "inspect", + "kill", + "load", + "login", + "logout", + "logs", + "mount", + "pause", + "ps", + "pod", + "port", + "pull", + "push", + "restart", + "rm", + "rmi", + "run", + "save", + "search", + "start", + "stats", + "stop", + "tag", + "top", + "umount", + "unpause", + "version", + "wait", + "h", + } + INTEGRATION_ROOT string + ARTIFACT_DIR = "/tmp/.artifacts" + ALPINE = "docker.io/library/alpine:latest" + BB = "docker.io/library/busybox:latest" + BB_GLIBC = "docker.io/library/busybox:glibc" + fedoraMinimal = "registry.fedoraproject.org/fedora-minimal:latest" + nginx = "quay.io/baude/alpine_nginx:latest" + redis = "docker.io/library/redis:alpine" + registry = "docker.io/library/registry:2" + infra = "k8s.gcr.io/pause:3.1" + defaultWaitTimeout = 90 +) + +// PodmanTestSystem struct for command line options +type PodmanTestSystem struct { + PodmanTest + GlobalOptions map[string]string + PodmanCmdOptions map[string][]string +} + +// TestLibpod ginkgo master function +func TestLibpod(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Libpod Suite") +} + +var _ = BeforeSuite(func() { +}) + +// PodmanTestCreate creates a PodmanTestSystem instance for the tests +func PodmanTestCreate(tempDir string) *PodmanTestSystem { + var envKey string + globalOptions := make(map[string]string) + podmanCmdOptions := make(map[string][]string) + + for _, n := range GLOBALOPTIONS { + envKey = strings.Replace(strings.ToUpper(strings.Trim(n, "-")), "-", "_", -1) + if isEnvSet(envKey) { + globalOptions[n] = os.Getenv(envKey) + } + } + + for _, n := range PODMAN_SUBCMD { + envKey = strings.Replace("PODMAN_SUBCMD_OPTIONS", "SUBCMD", strings.ToUpper(n), -1) + if isEnvSet(envKey) { + podmanCmdOptions[n] = strings.Split(os.Getenv(envKey), " ") + } + } + + podmanBinary := "podman" + if os.Getenv("PODMAN_BINARY") != "" { + podmanBinary = os.Getenv("PODMAN_BINARY") + } + + p := &PodmanTestSystem{ + PodmanTest: PodmanTest{ + PodmanBinary: podmanBinary, + ArtifactPath: ARTIFACT_DIR, + TempDir: tempDir, + }, + GlobalOptions: globalOptions, + PodmanCmdOptions: podmanCmdOptions, + } + + p.PodmanMakeOptions = p.makeOptions + + return p +} + +func (p *PodmanTestSystem) Podman(args []string) *PodmanSession { + return p.PodmanBase(args) +} + +//MakeOptions assembles all the podman options +func (p *PodmanTestSystem) makeOptions(args []string) []string { + var addOptions, subArgs []string + for _, n := range GLOBALOPTIONS { + if p.GlobalOptions[n] != "" { + addOptions = append(addOptions, n, p.GlobalOptions[n]) + } + } + + if len(args) == 0 { + return addOptions + } + + subCmd := args[0] + addOptions = append(addOptions, subCmd) + if subCmd == "unmount" { + subCmd = "umount" + } + if subCmd == "help" { + subCmd = "h" + } + + if _, ok := p.PodmanCmdOptions[subCmd]; ok { + m := make(map[string]bool) + subArgs = p.PodmanCmdOptions[subCmd] + for i := 0; i < len(subArgs); i++ { + m[subArgs[i]] = true + } + for i := 1; i < len(args); i++ { + if _, ok := m[args[i]]; !ok { + subArgs = append(subArgs, args[i]) + } + } + } else { + subArgs = args[1:] + } + + addOptions = append(addOptions, subArgs...) + + return addOptions +} + +// Cleanup cleans up the temporary store +func (p *PodmanTestSystem) Cleanup() { + // Remove all containers + stopall := p.Podman([]string{"stop", "-a", "--timeout", "0"}) + stopall.WaitWithDefaultTimeout() + + session := p.Podman([]string{"rm", "-fa"}) + session.Wait(90) + // Nuke tempdir + if err := os.RemoveAll(p.TempDir); err != nil { + fmt.Printf("%q\n", err) + } +} + +// CleanupPod cleans up the temporary store +func (p *PodmanTestSystem) CleanupPod() { + // Remove all containers + session := p.Podman([]string{"pod", "rm", "-fa"}) + session.Wait(90) + // Nuke tempdir + if err := os.RemoveAll(p.TempDir); err != nil { + fmt.Printf("%q\n", err) + } +} + +// Check if the key is set in Env +func isEnvSet(key string) bool { + _, set := os.LookupEnv(key) + return set +} diff --git a/test/system/version_test.go b/test/system/version_test.go new file mode 100644 index 000000000..ada0093b7 --- /dev/null +++ b/test/system/version_test.go @@ -0,0 +1,51 @@ +package system + +import ( + "fmt" + "os" + "regexp" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman version test", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestSystem + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("Smoking test: podman version with extra args", func() { + logc := podmanTest.Podman([]string{"version", "anything", "-", "--"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).To(Equal(0)) + ver := logc.OutputToString() + Expect(regexp.MatchString("Version:.*?Go Version:.*?OS/Arch", ver)).To(BeTrue()) + }) + + It("Negative test: podman version with extra flag", func() { + logc := podmanTest.Podman([]string{"version", "--foo"}) + logc.WaitWithDefaultTimeout() + Expect(logc.ExitCode()).NotTo(Equal(0)) + err, _ := logc.GrepString("Incorrect Usage: flag provided but not defined: -foo") + Expect(err).To(BeTrue()) + }) + +}) diff --git a/test/utils/utils.go b/test/utils/utils.go index e61171269..c9409c9d4 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "os/exec" + "runtime" "strings" "time" -- cgit v1.2.3-54-g00ecf From 266c4952a89be89b1741f9fa69443f51387dd5c6 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Thu, 22 Nov 2018 10:57:05 +0100 Subject: tests: change return type for PodmanAsUser to PodmanTestIntegration Signed-off-by: Giuseppe Scrivano --- test/e2e/libpod_suite_test.go | 6 ++++++ test/utils/podmantest_test.go | 4 ++-- test/utils/utils.go | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) (limited to 'test/utils') diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 52507c083..f4f154ef2 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -181,6 +181,12 @@ func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration return &PodmanSessionIntegration{podmanSession} } +// PodmanAsUser is the exec call to podman on the filesystem with the specified uid/gid and environment +func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, env []string) *PodmanSessionIntegration { + podmanSession := p.PodmanAsUserBase(args, uid, gid, env) + return &PodmanSessionIntegration{podmanSession} +} + // PodmanPID execs podman and returns its PID func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) { podmanOptions := p.MakeOptions(args) diff --git a/test/utils/podmantest_test.go b/test/utils/podmantest_test.go index 87f756920..60e3e2a97 100644 --- a/test/utils/podmantest_test.go +++ b/test/utils/podmantest_test.go @@ -19,11 +19,11 @@ var _ = Describe("PodmanTest test", func() { FakeOutputs = make(map[string][]string) }) - It("Test PodmanAsUser", func() { + It("Test PodmanAsUserBase", func() { FakeOutputs["check"] = []string{"check"} os.Setenv("HOOK_OPTION", "hook_option") env := os.Environ() - session := podmanTest.PodmanAsUser([]string{"check"}, 1000, 1000, env) + session := podmanTest.PodmanAsUserBase([]string{"check"}, 1000, 1000, env) os.Unsetenv("HOOK_OPTION") session.WaitWithDefaultTimeout() Expect(session.Command.Process).ShouldNot(BeNil()) diff --git a/test/utils/utils.go b/test/utils/utils.go index c9409c9d4..288c768d4 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -56,9 +56,9 @@ func (p *PodmanTest) MakeOptions(args []string) []string { return p.PodmanMakeOptions(args) } -// PodmanAsUser exec podman as user. uid and gid is set for credentials useage. env is used +// PodmanAsUserBase exec podman as user. uid and gid is set for credentials useage. env is used // to record the env for debugging -func (p *PodmanTest) PodmanAsUser(args []string, uid, gid uint32, env []string) *PodmanSession { +func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, env []string) *PodmanSession { var command *exec.Cmd podmanOptions := p.MakeOptions(args) @@ -86,7 +86,7 @@ func (p *PodmanTest) PodmanAsUser(args []string, uid, gid uint32, env []string) // PodmanBase exec podman with default env. func (p *PodmanTest) PodmanBase(args []string) *PodmanSession { - return p.PodmanAsUser(args, 0, 0, nil) + return p.PodmanAsUserBase(args, 0, 0, nil) } // WaitForContainer waits on a started container -- cgit v1.2.3-54-g00ecf