1*760c253cSXin Li// Copyright 2019 The ChromiumOS Authors 2*760c253cSXin Li// Use of this source code is governed by a BSD-style license that can be 3*760c253cSXin Li// found in the LICENSE file. 4*760c253cSXin Li 5*760c253cSXin Lipackage main 6*760c253cSXin Li 7*760c253cSXin Liimport ( 8*760c253cSXin Li "bytes" 9*760c253cSXin Li "fmt" 10*760c253cSXin Li "io" 11*760c253cSXin Li "io/ioutil" 12*760c253cSXin Li "os" 13*760c253cSXin Li "os/exec" 14*760c253cSXin Li "path/filepath" 15*760c253cSXin Li "regexp" 16*760c253cSXin Li "strings" 17*760c253cSXin Li "sync" 18*760c253cSXin Li "syscall" 19*760c253cSXin Li "testing" 20*760c253cSXin Li "time" 21*760c253cSXin Li) 22*760c253cSXin Li 23*760c253cSXin Liconst ( 24*760c253cSXin Li mainCc = "main.cc" 25*760c253cSXin Li clangAndroid = "./clang" 26*760c253cSXin Li clangTidyAndroid = "./clang-tidy" 27*760c253cSXin Li clangX86_64 = "./x86_64-cros-linux-gnu-clang" 28*760c253cSXin Li gccX86_64 = "./x86_64-cros-linux-gnu-gcc" 29*760c253cSXin Li gccX86_64Eabi = "./x86_64-cros-eabi-gcc" 30*760c253cSXin Li gccArmV7 = "./armv7m-cros-linux-gnu-gcc" 31*760c253cSXin Li gccArmV7Eabi = "./armv7m-cros-eabi-gcc" 32*760c253cSXin Li gccArmV8 = "./armv8m-cros-linux-gnu-gcc" 33*760c253cSXin Li gccArmV8Eabi = "./armv8m-cros-eabi-gcc" 34*760c253cSXin Li) 35*760c253cSXin Li 36*760c253cSXin Litype testContext struct { 37*760c253cSXin Li t *testing.T 38*760c253cSXin Li wd string 39*760c253cSXin Li tempDir string 40*760c253cSXin Li env []string 41*760c253cSXin Li cfg *config 42*760c253cSXin Li lastCmd *command 43*760c253cSXin Li cmdCount int 44*760c253cSXin Li cmdMock func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error 45*760c253cSXin Li stdinBuffer bytes.Buffer 46*760c253cSXin Li stdoutBuffer bytes.Buffer 47*760c253cSXin Li stderrBuffer bytes.Buffer 48*760c253cSXin Li 49*760c253cSXin Li umaskRestoreAction func() 50*760c253cSXin Li} 51*760c253cSXin Li 52*760c253cSXin Li// We have some tests which modify our umask, and other tests which depend upon the value of our 53*760c253cSXin Li// umask remaining consistent. This lock serializes those. Please use `NoteTestWritesToUmask()` and 54*760c253cSXin Li// `NoteTestDependsOnUmask()` on `testContext` rather than using this directly. 55*760c253cSXin Livar umaskModificationLock sync.RWMutex 56*760c253cSXin Li 57*760c253cSXin Lifunc withTestContext(t *testing.T, work func(ctx *testContext)) { 58*760c253cSXin Li t.Parallel() 59*760c253cSXin Li tempDir, err := ioutil.TempDir("", "compiler_wrapper") 60*760c253cSXin Li if err != nil { 61*760c253cSXin Li t.Fatalf("Unable to create the temp dir. Error: %s", err) 62*760c253cSXin Li } 63*760c253cSXin Li defer os.RemoveAll(tempDir) 64*760c253cSXin Li 65*760c253cSXin Li ctx := testContext{ 66*760c253cSXin Li t: t, 67*760c253cSXin Li wd: tempDir, 68*760c253cSXin Li tempDir: tempDir, 69*760c253cSXin Li env: nil, 70*760c253cSXin Li cfg: &config{}, 71*760c253cSXin Li } 72*760c253cSXin Li ctx.updateConfig(&config{}) 73*760c253cSXin Li 74*760c253cSXin Li defer ctx.maybeReleaseUmaskDependency() 75*760c253cSXin Li work(&ctx) 76*760c253cSXin Li} 77*760c253cSXin Li 78*760c253cSXin Livar _ env = (*testContext)(nil) 79*760c253cSXin Li 80*760c253cSXin Lifunc (ctx *testContext) umask(mask int) (oldmask int) { 81*760c253cSXin Li if ctx.umaskRestoreAction == nil { 82*760c253cSXin Li panic("Umask operations requested in test without declaring a umask dependency") 83*760c253cSXin Li } 84*760c253cSXin Li return syscall.Umask(mask) 85*760c253cSXin Li} 86*760c253cSXin Li 87*760c253cSXin Lifunc (ctx *testContext) setArbitraryClangArtifactsDir() string { 88*760c253cSXin Li d := filepath.Join(ctx.tempDir, "cros-artifacts") 89*760c253cSXin Li ctx.env = append(ctx.env, crosArtifactsEnvVar+"="+d) 90*760c253cSXin Li return d 91*760c253cSXin Li} 92*760c253cSXin Li 93*760c253cSXin Lifunc (ctx *testContext) initUmaskDependency(lockFn func(), unlockFn func()) { 94*760c253cSXin Li if ctx.umaskRestoreAction != nil { 95*760c253cSXin Li // Use a panic so we get a backtrace. 96*760c253cSXin Li panic("Multiple notes of a test depending on the value of `umask` given -- tests " + 97*760c253cSXin Li "are only allowed up to one.") 98*760c253cSXin Li } 99*760c253cSXin Li 100*760c253cSXin Li lockFn() 101*760c253cSXin Li ctx.umaskRestoreAction = unlockFn 102*760c253cSXin Li} 103*760c253cSXin Li 104*760c253cSXin Lifunc (ctx *testContext) maybeReleaseUmaskDependency() { 105*760c253cSXin Li if ctx.umaskRestoreAction != nil { 106*760c253cSXin Li ctx.umaskRestoreAction() 107*760c253cSXin Li } 108*760c253cSXin Li} 109*760c253cSXin Li 110*760c253cSXin Li// Note that the test depends on a stable value for the process' umask. 111*760c253cSXin Lifunc (ctx *testContext) NoteTestReadsFromUmask() { 112*760c253cSXin Li ctx.initUmaskDependency(umaskModificationLock.RLock, umaskModificationLock.RUnlock) 113*760c253cSXin Li} 114*760c253cSXin Li 115*760c253cSXin Li// Note that the test modifies the process' umask. This implies a dependency on the process' umask, 116*760c253cSXin Li// so it's an error to call both NoteTestWritesToUmask and NoteTestReadsFromUmask from the same 117*760c253cSXin Li// test. 118*760c253cSXin Lifunc (ctx *testContext) NoteTestWritesToUmask() { 119*760c253cSXin Li ctx.initUmaskDependency(umaskModificationLock.Lock, umaskModificationLock.Unlock) 120*760c253cSXin Li} 121*760c253cSXin Li 122*760c253cSXin Lifunc (ctx *testContext) getenv(key string) (string, bool) { 123*760c253cSXin Li for i := len(ctx.env) - 1; i >= 0; i-- { 124*760c253cSXin Li entry := ctx.env[i] 125*760c253cSXin Li if strings.HasPrefix(entry, key+"=") { 126*760c253cSXin Li return entry[len(key)+1:], true 127*760c253cSXin Li } 128*760c253cSXin Li } 129*760c253cSXin Li return "", false 130*760c253cSXin Li} 131*760c253cSXin Li 132*760c253cSXin Lifunc (ctx *testContext) environ() []string { 133*760c253cSXin Li return ctx.env 134*760c253cSXin Li} 135*760c253cSXin Li 136*760c253cSXin Lifunc (ctx *testContext) getwd() string { 137*760c253cSXin Li return ctx.wd 138*760c253cSXin Li} 139*760c253cSXin Li 140*760c253cSXin Lifunc (ctx *testContext) stdin() io.Reader { 141*760c253cSXin Li return &ctx.stdinBuffer 142*760c253cSXin Li} 143*760c253cSXin Li 144*760c253cSXin Lifunc (ctx *testContext) stdout() io.Writer { 145*760c253cSXin Li return &ctx.stdoutBuffer 146*760c253cSXin Li} 147*760c253cSXin Li 148*760c253cSXin Lifunc (ctx *testContext) stdoutString() string { 149*760c253cSXin Li return ctx.stdoutBuffer.String() 150*760c253cSXin Li} 151*760c253cSXin Li 152*760c253cSXin Lifunc (ctx *testContext) stderr() io.Writer { 153*760c253cSXin Li return &ctx.stderrBuffer 154*760c253cSXin Li} 155*760c253cSXin Li 156*760c253cSXin Lifunc (ctx *testContext) stderrString() string { 157*760c253cSXin Li return ctx.stderrBuffer.String() 158*760c253cSXin Li} 159*760c253cSXin Li 160*760c253cSXin Lifunc (ctx *testContext) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 161*760c253cSXin Li ctx.cmdCount++ 162*760c253cSXin Li ctx.lastCmd = cmd 163*760c253cSXin Li if ctx.cmdMock != nil { 164*760c253cSXin Li return ctx.cmdMock(cmd, stdin, stdout, stderr) 165*760c253cSXin Li } 166*760c253cSXin Li return nil 167*760c253cSXin Li} 168*760c253cSXin Li 169*760c253cSXin Lifunc (ctx *testContext) runWithTimeout(cmd *command, duration time.Duration) error { 170*760c253cSXin Li return ctx.exec(cmd) 171*760c253cSXin Li} 172*760c253cSXin Li 173*760c253cSXin Lifunc (ctx *testContext) exec(cmd *command) error { 174*760c253cSXin Li ctx.cmdCount++ 175*760c253cSXin Li ctx.lastCmd = cmd 176*760c253cSXin Li if ctx.cmdMock != nil { 177*760c253cSXin Li return ctx.cmdMock(cmd, ctx.stdin(), ctx.stdout(), ctx.stderr()) 178*760c253cSXin Li } 179*760c253cSXin Li return nil 180*760c253cSXin Li} 181*760c253cSXin Li 182*760c253cSXin Lifunc (ctx *testContext) must(exitCode int) *command { 183*760c253cSXin Li if exitCode != 0 { 184*760c253cSXin Li ctx.t.Fatalf("expected no error, but got exit code %d. Stderr: %s", 185*760c253cSXin Li exitCode, ctx.stderrString()) 186*760c253cSXin Li } 187*760c253cSXin Li return ctx.lastCmd 188*760c253cSXin Li} 189*760c253cSXin Li 190*760c253cSXin Lifunc (ctx *testContext) mustFail(exitCode int) string { 191*760c253cSXin Li if exitCode == 0 { 192*760c253cSXin Li ctx.t.Fatalf("expected an error, but got none") 193*760c253cSXin Li } 194*760c253cSXin Li return ctx.stderrString() 195*760c253cSXin Li} 196*760c253cSXin Li 197*760c253cSXin Lifunc (ctx *testContext) updateConfig(cfg *config) { 198*760c253cSXin Li *ctx.cfg = *cfg 199*760c253cSXin Li} 200*760c253cSXin Li 201*760c253cSXin Lifunc (ctx *testContext) newCommand(path string, args ...string) *command { 202*760c253cSXin Li // Create an empty wrapper at the given path. 203*760c253cSXin Li // Needed as we are resolving symlinks which stats the wrapper file. 204*760c253cSXin Li ctx.writeFile(path, "") 205*760c253cSXin Li return &command{ 206*760c253cSXin Li Path: path, 207*760c253cSXin Li Args: args, 208*760c253cSXin Li } 209*760c253cSXin Li} 210*760c253cSXin Li 211*760c253cSXin Lifunc (ctx *testContext) writeFile(fullFileName string, fileContent string) { 212*760c253cSXin Li if !filepath.IsAbs(fullFileName) { 213*760c253cSXin Li fullFileName = filepath.Join(ctx.tempDir, fullFileName) 214*760c253cSXin Li } 215*760c253cSXin Li if err := os.MkdirAll(filepath.Dir(fullFileName), 0777); err != nil { 216*760c253cSXin Li ctx.t.Fatal(err) 217*760c253cSXin Li } 218*760c253cSXin Li if err := ioutil.WriteFile(fullFileName, []byte(fileContent), 0777); err != nil { 219*760c253cSXin Li ctx.t.Fatal(err) 220*760c253cSXin Li } 221*760c253cSXin Li} 222*760c253cSXin Li 223*760c253cSXin Lifunc (ctx *testContext) symlink(oldname string, newname string) { 224*760c253cSXin Li if !filepath.IsAbs(oldname) { 225*760c253cSXin Li oldname = filepath.Join(ctx.tempDir, oldname) 226*760c253cSXin Li } 227*760c253cSXin Li if !filepath.IsAbs(newname) { 228*760c253cSXin Li newname = filepath.Join(ctx.tempDir, newname) 229*760c253cSXin Li } 230*760c253cSXin Li if err := os.MkdirAll(filepath.Dir(newname), 0777); err != nil { 231*760c253cSXin Li ctx.t.Fatal(err) 232*760c253cSXin Li } 233*760c253cSXin Li if err := os.Symlink(oldname, newname); err != nil { 234*760c253cSXin Li ctx.t.Fatal(err) 235*760c253cSXin Li } 236*760c253cSXin Li} 237*760c253cSXin Li 238*760c253cSXin Lifunc (ctx *testContext) readAllString(r io.Reader) string { 239*760c253cSXin Li if r == nil { 240*760c253cSXin Li return "" 241*760c253cSXin Li } 242*760c253cSXin Li bytes, err := ioutil.ReadAll(r) 243*760c253cSXin Li if err != nil { 244*760c253cSXin Li ctx.t.Fatal(err) 245*760c253cSXin Li } 246*760c253cSXin Li return string(bytes) 247*760c253cSXin Li} 248*760c253cSXin Li 249*760c253cSXin Lifunc verifyPath(cmd *command, expectedRegex string) error { 250*760c253cSXin Li compiledRegex := regexp.MustCompile(matchFullString(expectedRegex)) 251*760c253cSXin Li if !compiledRegex.MatchString(cmd.Path) { 252*760c253cSXin Li return fmt.Errorf("path does not match %s. Actual %s", expectedRegex, cmd.Path) 253*760c253cSXin Li } 254*760c253cSXin Li return nil 255*760c253cSXin Li} 256*760c253cSXin Li 257*760c253cSXin Lifunc verifyArgCount(cmd *command, expectedCount int, expectedRegex string) error { 258*760c253cSXin Li compiledRegex := regexp.MustCompile(matchFullString(expectedRegex)) 259*760c253cSXin Li count := 0 260*760c253cSXin Li for _, arg := range cmd.Args { 261*760c253cSXin Li if compiledRegex.MatchString(arg) { 262*760c253cSXin Li count++ 263*760c253cSXin Li } 264*760c253cSXin Li } 265*760c253cSXin Li if count != expectedCount { 266*760c253cSXin Li return fmt.Errorf("expected %d matches for arg %s. All args: %s", 267*760c253cSXin Li expectedCount, expectedRegex, cmd.Args) 268*760c253cSXin Li } 269*760c253cSXin Li return nil 270*760c253cSXin Li} 271*760c253cSXin Li 272*760c253cSXin Lifunc verifyArgOrder(cmd *command, expectedRegexes ...string) error { 273*760c253cSXin Li compiledRegexes := []*regexp.Regexp{} 274*760c253cSXin Li for _, regex := range expectedRegexes { 275*760c253cSXin Li compiledRegexes = append(compiledRegexes, regexp.MustCompile(matchFullString(regex))) 276*760c253cSXin Li } 277*760c253cSXin Li expectedArgIndex := 0 278*760c253cSXin Li for _, arg := range cmd.Args { 279*760c253cSXin Li if expectedArgIndex == len(compiledRegexes) { 280*760c253cSXin Li break 281*760c253cSXin Li } else if compiledRegexes[expectedArgIndex].MatchString(arg) { 282*760c253cSXin Li expectedArgIndex++ 283*760c253cSXin Li } 284*760c253cSXin Li } 285*760c253cSXin Li if expectedArgIndex != len(expectedRegexes) { 286*760c253cSXin Li return fmt.Errorf("expected args %s in order. All args: %s", 287*760c253cSXin Li expectedRegexes, cmd.Args) 288*760c253cSXin Li } 289*760c253cSXin Li return nil 290*760c253cSXin Li} 291*760c253cSXin Li 292*760c253cSXin Lifunc verifyEnvUpdate(cmd *command, expectedRegex string) error { 293*760c253cSXin Li compiledRegex := regexp.MustCompile(matchFullString(expectedRegex)) 294*760c253cSXin Li for _, update := range cmd.EnvUpdates { 295*760c253cSXin Li if compiledRegex.MatchString(update) { 296*760c253cSXin Li return nil 297*760c253cSXin Li } 298*760c253cSXin Li } 299*760c253cSXin Li return fmt.Errorf("expected at least one match for env update %s. All env updates: %s", 300*760c253cSXin Li expectedRegex, cmd.EnvUpdates) 301*760c253cSXin Li} 302*760c253cSXin Li 303*760c253cSXin Lifunc verifyNoEnvUpdate(cmd *command, expectedRegex string) error { 304*760c253cSXin Li compiledRegex := regexp.MustCompile(matchFullString(expectedRegex)) 305*760c253cSXin Li updates := cmd.EnvUpdates 306*760c253cSXin Li for _, update := range updates { 307*760c253cSXin Li if compiledRegex.MatchString(update) { 308*760c253cSXin Li return fmt.Errorf("expected no match for env update %s. All env updates: %s", 309*760c253cSXin Li expectedRegex, cmd.EnvUpdates) 310*760c253cSXin Li } 311*760c253cSXin Li } 312*760c253cSXin Li return nil 313*760c253cSXin Li} 314*760c253cSXin Li 315*760c253cSXin Lifunc hasInternalError(stderr string) bool { 316*760c253cSXin Li return strings.Contains(stderr, "Internal error") 317*760c253cSXin Li} 318*760c253cSXin Li 319*760c253cSXin Lifunc verifyInternalError(stderr string) error { 320*760c253cSXin Li if !hasInternalError(stderr) { 321*760c253cSXin Li return fmt.Errorf("expected an internal error. Got: %s", stderr) 322*760c253cSXin Li } 323*760c253cSXin Li if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); !ok { 324*760c253cSXin Li return fmt.Errorf("expected a source line reference. Got: %s", stderr) 325*760c253cSXin Li } 326*760c253cSXin Li return nil 327*760c253cSXin Li} 328*760c253cSXin Li 329*760c253cSXin Lifunc verifyNonInternalError(stderr string, expectedRegex string) error { 330*760c253cSXin Li if hasInternalError(stderr) { 331*760c253cSXin Li return fmt.Errorf("expected a non internal error. Got: %s", stderr) 332*760c253cSXin Li } 333*760c253cSXin Li if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); ok { 334*760c253cSXin Li return fmt.Errorf("expected no source line reference. Got: %s", stderr) 335*760c253cSXin Li } 336*760c253cSXin Li if ok, _ := regexp.MatchString(matchFullString(expectedRegex), strings.TrimSpace(stderr)); !ok { 337*760c253cSXin Li return fmt.Errorf("expected stderr matching %s. Got: %s", expectedRegex, stderr) 338*760c253cSXin Li } 339*760c253cSXin Li return nil 340*760c253cSXin Li} 341*760c253cSXin Li 342*760c253cSXin Lifunc matchFullString(regex string) string { 343*760c253cSXin Li return "^" + regex + "$" 344*760c253cSXin Li} 345*760c253cSXin Li 346*760c253cSXin Lifunc newExitCodeError(exitCode int) error { 347*760c253cSXin Li // It's actually hard to create an error that represents a command 348*760c253cSXin Li // with exit code. Using a real command instead. 349*760c253cSXin Li tmpCmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("exit %d", exitCode)) 350*760c253cSXin Li return tmpCmd.Run() 351*760c253cSXin Li} 352