github.com/containers/podman/v4@v4.9.4/pkg/machine/e2e/config_test.go (about) 1 package e2e_test 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/containers/podman/v4/pkg/machine" 14 "github.com/containers/podman/v4/pkg/util" 15 "github.com/containers/storage/pkg/stringid" 16 . "github.com/onsi/ginkgo/v2" 17 . "github.com/onsi/gomega" 18 "github.com/onsi/gomega/format" 19 . "github.com/onsi/gomega/gexec" 20 "github.com/onsi/gomega/types" 21 ) 22 23 var originalHomeDir = os.Getenv("HOME") 24 25 const ( 26 defaultTimeout = 90 * time.Second 27 ) 28 29 type machineCommand interface { 30 buildCmd(m *machineTestBuilder) []string 31 } 32 33 type MachineTestBuilder interface { 34 setName(name string) *MachineTestBuilder 35 setCmd(mc machineCommand) *MachineTestBuilder 36 setTimeout(duration time.Duration) *MachineTestBuilder 37 run() (*machineSession, error) 38 } 39 type machineSession struct { 40 *Session 41 } 42 43 type machineTestBuilder struct { 44 cmd []string 45 imagePath string 46 name string 47 names []string 48 podmanBinary string 49 timeout time.Duration 50 } 51 52 // waitWithTimeout waits for a command to complete for a given 53 // number of seconds 54 func (ms *machineSession) waitWithTimeout(timeout time.Duration) { 55 Eventually(ms, timeout).Should(Exit()) 56 } 57 58 func (ms *machineSession) Bytes() []byte { 59 return []byte(ms.outputToString()) 60 } 61 62 func (ms *machineSession) outputToStringSlice() []string { 63 var results []string 64 output := string(ms.Out.Contents()) 65 for _, line := range strings.Split(output, "\n") { 66 if line != "" { 67 results = append(results, line) 68 } 69 } 70 return results 71 } 72 73 // outputToString returns the output from a session in string form 74 func (ms *machineSession) outputToString() string { 75 if ms == nil || ms.Out == nil || ms.Out.Contents() == nil { 76 return "" 77 } 78 79 fields := strings.Fields(string(ms.Out.Contents())) 80 return strings.Join(fields, " ") 81 } 82 83 // errorToString returns the error output from a session in string form 84 func (ms *machineSession) errorToString() string { 85 if ms == nil || ms.Err == nil || ms.Err.Contents() == nil { 86 return "" 87 } 88 return string(ms.Err.Contents()) 89 } 90 91 // newMB constructor for machine test builders 92 func newMB() (*machineTestBuilder, error) { 93 mb := machineTestBuilder{ 94 timeout: defaultTimeout, 95 } 96 cwd, err := os.Getwd() 97 if err != nil { 98 return nil, err 99 } 100 mb.podmanBinary = filepath.Join(cwd, podmanBinary) 101 if os.Getenv("PODMAN_BINARY") != "" { 102 mb.podmanBinary = os.Getenv("PODMAN_BINARY") 103 } 104 if os.Getenv("MACHINE_TEST_TIMEOUT") != "" { 105 seconds, err := strconv.Atoi(os.Getenv("MACHINE_TEST_TIMEOUT")) 106 if err != nil { 107 return nil, err 108 } 109 mb.timeout = time.Duration(seconds) * time.Second 110 } 111 return &mb, nil 112 } 113 114 // setName sets the name of the virtuaql machine for the command 115 func (m *machineTestBuilder) setName(name string) *machineTestBuilder { 116 m.name = name 117 return m 118 } 119 120 // setCmd takes a machineCommand struct and assembles a cmd line 121 // representation of the podman machine command 122 func (m *machineTestBuilder) setCmd(mc machineCommand) *machineTestBuilder { 123 // If no name for the machine exists, we set a random name. 124 if !util.StringInSlice(m.name, m.names) { 125 if len(m.name) < 1 { 126 m.name = randomString() 127 } 128 m.names = append(m.names, m.name) 129 } 130 m.cmd = mc.buildCmd(m) 131 return m 132 } 133 134 func (m *machineTestBuilder) setTimeout(timeout time.Duration) *machineTestBuilder { //nolint: unparam 135 m.timeout = timeout 136 return m 137 } 138 139 // toQemuInspectInfo is only for inspecting qemu machines. Other providers will need 140 // to make their own. 141 func (m *machineTestBuilder) toQemuInspectInfo() ([]machine.InspectInfo, int, error) { 142 args := []string{"machine", "inspect"} 143 args = append(args, m.names...) 144 session, err := runWrapper(m.podmanBinary, args, defaultTimeout, true) 145 if err != nil { 146 return nil, -1, err 147 } 148 mii := []machine.InspectInfo{} 149 err = json.Unmarshal(session.Bytes(), &mii) 150 return mii, session.ExitCode(), err 151 } 152 153 func (m *machineTestBuilder) runWithoutWait() (*machineSession, error) { 154 return runWrapper(m.podmanBinary, m.cmd, m.timeout, false) 155 } 156 157 func (m *machineTestBuilder) run() (*machineSession, error) { 158 return runWrapper(m.podmanBinary, m.cmd, m.timeout, true) 159 } 160 161 func runWrapper(podmanBinary string, cmdArgs []string, timeout time.Duration, wait bool) (*machineSession, error) { 162 if len(os.Getenv("DEBUG")) > 0 { 163 cmdArgs = append([]string{"--log-level=debug"}, cmdArgs...) 164 } 165 GinkgoWriter.Println(podmanBinary + " " + strings.Join(cmdArgs, " ")) 166 c := exec.Command(podmanBinary, cmdArgs...) 167 session, err := Start(c, GinkgoWriter, GinkgoWriter) 168 if err != nil { 169 Fail(fmt.Sprintf("Unable to start session: %q", err)) 170 return nil, err 171 } 172 ms := machineSession{session} 173 if wait { 174 ms.waitWithTimeout(timeout) 175 } 176 return &ms, nil 177 } 178 179 // randomString returns a string of given length composed of random characters 180 func randomString() string { 181 return stringid.GenerateRandomID()[0:12] 182 } 183 184 type ValidJSONMatcher struct { 185 types.GomegaMatcher 186 } 187 188 func BeValidJSON() *ValidJSONMatcher { 189 return &ValidJSONMatcher{} 190 } 191 192 func (matcher *ValidJSONMatcher) Match(actual interface{}) (success bool, err error) { 193 s, ok := actual.(string) 194 if !ok { 195 return false, fmt.Errorf("ValidJSONMatcher expects a string, not %q", actual) 196 } 197 198 var i interface{} 199 if err := json.Unmarshal([]byte(s), &i); err != nil { 200 return false, err 201 } 202 return true, nil 203 } 204 205 func (matcher *ValidJSONMatcher) FailureMessage(actual interface{}) (message string) { 206 return format.Message(actual, "to be valid JSON") 207 } 208 209 func (matcher *ValidJSONMatcher) NegatedFailureMessage(actual interface{}) (message string) { 210 return format.Message(actual, "to _not_ be valid JSON") 211 } 212 213 func skipIfVmtype(vmType machine.VMType, message string) { 214 if isVmtype(vmType) { 215 Skip(message) 216 } 217 } 218 219 func skipIfNotVmtype(vmType machine.VMType, message string) { 220 if !isVmtype(vmType) { 221 Skip(message) 222 } 223 } 224 225 func skipIfWSL(message string) { 226 skipIfVmtype(machine.WSLVirt, message) 227 } 228 229 func isVmtype(vmType machine.VMType) bool { 230 return testProvider.VMType() == vmType 231 } 232 233 // isWSL is a simple wrapper to determine if the testprovider is WSL 234 func isWSL() bool { 235 return isVmtype(machine.WSLVirt) 236 }