github.com/shaardie/u-root@v4.0.1-0.20190127173353-f24a1c26aa2e+incompatible/integration/integration.go (about) 1 // Copyright 2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package integration 6 7 import ( 8 "fmt" 9 "io" 10 "io/ioutil" 11 "log" 12 "os" 13 "path" 14 "path/filepath" 15 "runtime" 16 "strings" 17 "testing" 18 "time" 19 20 "github.com/u-root/u-root/pkg/cp" 21 "github.com/u-root/u-root/pkg/golang" 22 "github.com/u-root/u-root/pkg/qemu" 23 "github.com/u-root/u-root/pkg/uroot" 24 "github.com/u-root/u-root/pkg/uroot/builder" 25 "github.com/u-root/u-root/pkg/uroot/initramfs" 26 "github.com/u-root/u-root/pkg/uroot/logger" 27 ) 28 29 // Serial output is written to this directory and picked up by circleci, or 30 // you, if you want to read the serial logs. 31 const logDir = "serial" 32 33 const template = ` 34 package main 35 36 import ( 37 "log" 38 "os" 39 "os/exec" 40 ) 41 42 func main() { 43 for _, cmds := range %#v { 44 cmd := exec.Command(cmds[0], cmds[1:]...) 45 cmd.Stdout = os.Stdout 46 cmd.Stderr = os.Stderr 47 err := cmd.Run() 48 if err != nil { 49 log.Fatal(err) 50 } 51 } 52 } 53 ` 54 55 // Options are integration test options. 56 type Options struct { 57 // Env is the Go environment to use to build u-root. 58 Env *golang.Environ 59 60 // Name is the test's name. 61 // 62 // If name is left empty, the calling function's function name will be 63 // used as determined by runtime.Caller 64 Name string 65 66 // Go commands to include in the initramfs for the VM. 67 // 68 // If left empty, all u-root commands will be included. 69 Cmds []string 70 71 // Uinit are commands to execute after init. 72 // 73 // If populated, a uinit.go will be generated from these. 74 Uinit []string 75 76 // Files are files to include in the VMs initramfs. 77 Files []string 78 79 // TmpDir is a temporary directory for build artifacts. 80 TmpDir string 81 82 // LogFile is a file to log serial output to. 83 // 84 // The default is serial/$Name.log 85 LogFile string 86 87 // Logger logs build statements. 88 Logger logger.Logger 89 90 // Timeout is the timeout for expect statements. 91 Timeout time.Duration 92 93 // Network is the VM's network. 94 Network *qemu.Network 95 96 // Extra environment variables to set when building (used by u-bmc) 97 ExtraBuildEnv []string 98 99 // Serial Output 100 SerialOutput io.WriteCloser 101 } 102 103 func last(s string) string { 104 l := strings.Split(s, ".") 105 return l[len(l)-1] 106 } 107 108 type testLogger struct { 109 t *testing.T 110 } 111 112 func (tl testLogger) Printf(format string, v ...interface{}) { 113 tl.t.Logf(format, v...) 114 } 115 116 func (tl testLogger) Print(v ...interface{}) { 117 tl.t.Log(v...) 118 } 119 120 func callerName(depth int) string { 121 // Use the test name as the serial log's file name. 122 pc, _, _, ok := runtime.Caller(depth) 123 if !ok { 124 panic("runtime caller failed") 125 } 126 f := runtime.FuncForPC(pc) 127 return last(f.Name()) 128 } 129 130 // TestArch returns the architecture under test. Pass this as GOARCH when 131 // building Go programs to be run in the QEMU environment. 132 func TestArch() string { 133 if env := os.Getenv("UROOT_TESTARCH"); env != "" { 134 return env 135 } 136 return "amd64" 137 } 138 139 // SkipWithoutQEMU skips the test when the QEMU environment variables are not 140 // set. This is already called by QEMUTest(), so use if some expensive 141 // operations are performed before calling QEMUTest(). 142 func SkipWithoutQEMU(t *testing.T) { 143 if _, ok := os.LookupEnv("UROOT_QEMU"); !ok { 144 t.Skip("QEMU test is skipped unless UROOT_QEMU is set") 145 } 146 if _, ok := os.LookupEnv("UROOT_KERNEL"); !ok { 147 t.Skip("QEMU test is skipped unless UROOT_KERNEL is set") 148 } 149 } 150 151 func QEMUTest(t *testing.T, o *Options) (*qemu.VM, func()) { 152 SkipWithoutQEMU(t) 153 154 if len(o.Name) == 0 { 155 o.Name = callerName(2) 156 } 157 if o.Logger == nil { 158 o.Logger = &testLogger{t} 159 } 160 161 qOpts, err := QEMU(o) 162 if err != nil { 163 t.Fatalf("Failed to create QEMU VM %s: %v", o.Name, err) 164 } 165 166 vm, err := qOpts.Start() 167 if err != nil { 168 t.Fatalf("Failed to start QEMU VM %s: %v", o.Name, err) 169 } 170 t.Logf("QEMU command line for %s:\n%s", o.Name, vm.CmdlineQuoted()) 171 172 return vm, func() { 173 vm.Close() 174 dir := vm.Options.Devices[0].(qemu.ReadOnlyDirectory).Dir 175 if t.Failed() { 176 t.Log("keeping temp dir: ", dir) 177 } else if len(o.TmpDir) == 0 { 178 if err := os.RemoveAll(dir); err != nil { 179 t.Logf("failed to remove temporary directory %s: %v", dir, err) 180 } 181 } 182 } 183 } 184 185 func QEMU(o *Options) (*qemu.Options, error) { 186 if len(o.Name) == 0 { 187 o.Name = callerName(2) 188 } 189 190 if o.Env == nil { 191 env := golang.Default() 192 o.Env = &env 193 o.Env.CgoEnabled = false 194 env.GOARCH = TestArch() 195 } 196 197 if len(o.LogFile) == 0 { 198 // Create file for serial logs. 199 if err := os.MkdirAll(logDir, 0755); err != nil { 200 return nil, fmt.Errorf("could not create serial log directory: %v", err) 201 } 202 203 o.LogFile = filepath.Join(logDir, fmt.Sprintf("%s.log", o.Name)) 204 } 205 206 var cmds []string 207 if len(o.Cmds) == 0 { 208 cmds = append(cmds, "github.com/u-root/u-root/cmds/*") 209 } else { 210 cmds = append(cmds, o.Cmds...) 211 } 212 // Create a uinit from the commands given. 213 if len(o.Uinit) > 0 { 214 urootPkg, err := o.Env.Package("github.com/u-root/u-root/integration") 215 if err != nil { 216 return nil, err 217 } 218 testDir := filepath.Join(urootPkg.Dir, "testcmd") 219 220 dirpath, err := ioutil.TempDir(testDir, "uinit-") 221 if err != nil { 222 return nil, err 223 } 224 defer os.RemoveAll(dirpath) 225 226 if err := os.MkdirAll(filepath.Join(dirpath, "uinit"), 0755); err != nil { 227 return nil, err 228 } 229 230 var realUinit [][]string 231 for _, cmd := range o.Uinit { 232 realUinit = append(realUinit, fields(cmd)) 233 } 234 235 if err := ioutil.WriteFile( 236 filepath.Join(dirpath, "uinit", "uinit.go"), 237 []byte(fmt.Sprintf(template, realUinit)), 238 0755); err != nil { 239 return nil, err 240 } 241 cmds = append(cmds, path.Join("github.com/u-root/u-root/integration/testcmd", filepath.Base(dirpath), "uinit")) 242 } 243 244 // Create or reuse a temporary directory. 245 tmpDir := o.TmpDir 246 if len(tmpDir) == 0 { 247 var err error 248 tmpDir, err = ioutil.TempDir("", "uroot-integration") 249 if err != nil { 250 return nil, err 251 } 252 } 253 254 if o.Logger == nil { 255 o.Logger = log.New(os.Stderr, "", 0) 256 } 257 258 // OutputFile 259 outputFile := filepath.Join(tmpDir, "initramfs.cpio") 260 w, err := initramfs.CPIO.OpenWriter(o.Logger, outputFile, "", "") 261 if err != nil { 262 return nil, err 263 } 264 265 // Build u-root 266 opts := uroot.Opts{ 267 Env: *o.Env, 268 Commands: []uroot.Commands{ 269 { 270 Builder: builder.BusyBox, 271 Packages: cmds, 272 }, 273 }, 274 ExtraFiles: o.Files, 275 TempDir: tmpDir, 276 BaseArchive: uroot.DefaultRamfs.Reader(), 277 OutputFile: w, 278 InitCmd: "init", 279 DefaultShell: "elvish", 280 } 281 if err := uroot.CreateInitramfs(o.Logger, opts); err != nil { 282 return nil, err 283 } 284 285 // Copy kernel to tmpDir. 286 bzImage := filepath.Join(tmpDir, "bzImage") 287 if err := cp.Copy(os.Getenv("UROOT_KERNEL"), bzImage); err != nil { 288 return nil, err 289 } 290 291 logFile := o.SerialOutput 292 if logFile == nil { 293 if o.LogFile != "" { 294 logFile, err = os.Create(o.LogFile) 295 if err != nil { 296 return nil, fmt.Errorf("could not create log file: %v", err) 297 } 298 } 299 } 300 301 kernelArgs := "" 302 switch TestArch() { 303 case "amd64": 304 kernelArgs = "console=ttyS0 earlyprintk=ttyS0" 305 case "arm": 306 kernelArgs = "console=ttyAMA0" 307 } 308 309 return &qemu.Options{ 310 Initramfs: outputFile, 311 Kernel: bzImage, 312 KernelArgs: kernelArgs, 313 SerialOutput: logFile, 314 Timeout: o.Timeout, 315 Devices: []qemu.Device{ 316 qemu.ReadOnlyDirectory{Dir: tmpDir}, 317 qemu.VirtioRandom{}, 318 o.Network, 319 }, 320 }, nil 321 }