1// Copyright 2019 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package main 6 7import ( 8 "bytes" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "regexp" 16 "strings" 17 "testing" 18) 19 20const ( 21 mainCc = "main.cc" 22 clangAndroid = "./clang" 23 clangTidyAndroid = "./clang-tidy" 24 clangX86_64 = "./x86_64-cros-linux-gnu-clang" 25 gccX86_64 = "./x86_64-cros-linux-gnu-gcc" 26 gccX86_64Eabi = "./x86_64-cros-eabi-gcc" 27 gccArmV7 = "./armv7m-cros-linux-gnu-gcc" 28 gccArmV7Eabi = "./armv7m-cros-eabi-gcc" 29 gccArmV8 = "./armv8m-cros-linux-gnu-gcc" 30 gccArmV8Eabi = "./armv8m-cros-eabi-gcc" 31) 32 33type testContext struct { 34 t *testing.T 35 wd string 36 tempDir string 37 env []string 38 cfg *config 39 inputCmd *command 40 lastCmd *command 41 cmdCount int 42 cmdMock func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error 43 stdinBuffer bytes.Buffer 44 stdoutBuffer bytes.Buffer 45 stderrBuffer bytes.Buffer 46} 47 48func withTestContext(t *testing.T, work func(ctx *testContext)) { 49 t.Parallel() 50 tempDir, err := ioutil.TempDir("", "compiler_wrapper") 51 if err != nil { 52 t.Fatalf("Unable to create the temp dir. Error: %s", err) 53 } 54 defer os.RemoveAll(tempDir) 55 56 ctx := testContext{ 57 t: t, 58 wd: tempDir, 59 tempDir: tempDir, 60 env: nil, 61 cfg: &config{}, 62 } 63 ctx.updateConfig(&config{}) 64 65 work(&ctx) 66} 67 68var _ env = (*testContext)(nil) 69 70func (ctx *testContext) getenv(key string) (string, bool) { 71 for i := len(ctx.env) - 1; i >= 0; i-- { 72 entry := ctx.env[i] 73 if strings.HasPrefix(entry, key+"=") { 74 return entry[len(key)+1:], true 75 } 76 } 77 return "", false 78} 79 80func (ctx *testContext) environ() []string { 81 return ctx.env 82} 83 84func (ctx *testContext) getwd() string { 85 return ctx.wd 86} 87 88func (ctx *testContext) stdin() io.Reader { 89 return &ctx.stdinBuffer 90} 91 92func (ctx *testContext) stdout() io.Writer { 93 return &ctx.stdoutBuffer 94} 95 96func (ctx *testContext) stdoutString() string { 97 return ctx.stdoutBuffer.String() 98} 99 100func (ctx *testContext) stderr() io.Writer { 101 return &ctx.stderrBuffer 102} 103 104func (ctx *testContext) stderrString() string { 105 return ctx.stderrBuffer.String() 106} 107 108func (ctx *testContext) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 109 ctx.cmdCount++ 110 ctx.lastCmd = cmd 111 if ctx.cmdMock != nil { 112 return ctx.cmdMock(cmd, stdin, stdout, stderr) 113 } 114 return nil 115} 116 117func (ctx *testContext) exec(cmd *command) error { 118 ctx.cmdCount++ 119 ctx.lastCmd = cmd 120 if ctx.cmdMock != nil { 121 return ctx.cmdMock(cmd, ctx.stdin(), ctx.stdout(), ctx.stderr()) 122 } 123 return nil 124} 125 126func (ctx *testContext) must(exitCode int) *command { 127 if exitCode != 0 { 128 ctx.t.Fatalf("expected no error, but got exit code %d. Stderr: %s", 129 exitCode, ctx.stderrString()) 130 } 131 return ctx.lastCmd 132} 133 134func (ctx *testContext) mustFail(exitCode int) string { 135 if exitCode == 0 { 136 ctx.t.Fatalf("expected an error, but got none") 137 } 138 return ctx.stderrString() 139} 140 141func (ctx *testContext) updateConfig(cfg *config) { 142 *ctx.cfg = *cfg 143 ctx.cfg.newWarningsDir = filepath.Join(ctx.tempDir, "fatal_clang_warnings") 144 ctx.cfg.triciumNitsDir = filepath.Join(ctx.tempDir, "tricium_nits") 145 ctx.cfg.crashArtifactsDir = filepath.Join(ctx.tempDir, "clang_crash_diagnostics") 146} 147 148func (ctx *testContext) newCommand(path string, args ...string) *command { 149 // Create an empty wrapper at the given path. 150 // Needed as we are resolving symlinks which stats the wrapper file. 151 ctx.writeFile(path, "") 152 return &command{ 153 Path: path, 154 Args: args, 155 } 156} 157 158func (ctx *testContext) writeFile(fullFileName string, fileContent string) { 159 if !filepath.IsAbs(fullFileName) { 160 fullFileName = filepath.Join(ctx.tempDir, fullFileName) 161 } 162 if err := os.MkdirAll(filepath.Dir(fullFileName), 0777); err != nil { 163 ctx.t.Fatal(err) 164 } 165 if err := ioutil.WriteFile(fullFileName, []byte(fileContent), 0777); err != nil { 166 ctx.t.Fatal(err) 167 } 168} 169 170func (ctx *testContext) symlink(oldname string, newname string) { 171 if !filepath.IsAbs(oldname) { 172 oldname = filepath.Join(ctx.tempDir, oldname) 173 } 174 if !filepath.IsAbs(newname) { 175 newname = filepath.Join(ctx.tempDir, newname) 176 } 177 if err := os.MkdirAll(filepath.Dir(newname), 0777); err != nil { 178 ctx.t.Fatal(err) 179 } 180 if err := os.Symlink(oldname, newname); err != nil { 181 ctx.t.Fatal(err) 182 } 183} 184 185func (ctx *testContext) readAllString(r io.Reader) string { 186 if r == nil { 187 return "" 188 } 189 bytes, err := ioutil.ReadAll(r) 190 if err != nil { 191 ctx.t.Fatal(err) 192 } 193 return string(bytes) 194} 195 196func verifyPath(cmd *command, expectedRegex string) error { 197 compiledRegex := regexp.MustCompile(matchFullString(expectedRegex)) 198 if !compiledRegex.MatchString(cmd.Path) { 199 return fmt.Errorf("path does not match %s. Actual %s", expectedRegex, cmd.Path) 200 } 201 return nil 202} 203 204func verifyArgCount(cmd *command, expectedCount int, expectedRegex string) error { 205 compiledRegex := regexp.MustCompile(matchFullString(expectedRegex)) 206 count := 0 207 for _, arg := range cmd.Args { 208 if compiledRegex.MatchString(arg) { 209 count++ 210 } 211 } 212 if count != expectedCount { 213 return fmt.Errorf("expected %d matches for arg %s. All args: %s", 214 expectedCount, expectedRegex, cmd.Args) 215 } 216 return nil 217} 218 219func verifyArgOrder(cmd *command, expectedRegexes ...string) error { 220 compiledRegexes := []*regexp.Regexp{} 221 for _, regex := range expectedRegexes { 222 compiledRegexes = append(compiledRegexes, regexp.MustCompile(matchFullString(regex))) 223 } 224 expectedArgIndex := 0 225 for _, arg := range cmd.Args { 226 if expectedArgIndex == len(compiledRegexes) { 227 break 228 } else if compiledRegexes[expectedArgIndex].MatchString(arg) { 229 expectedArgIndex++ 230 } 231 } 232 if expectedArgIndex != len(expectedRegexes) { 233 return fmt.Errorf("expected args %s in order. All args: %s", 234 expectedRegexes, cmd.Args) 235 } 236 return nil 237} 238 239func verifyEnvUpdate(cmd *command, expectedRegex string) error { 240 compiledRegex := regexp.MustCompile(matchFullString(expectedRegex)) 241 for _, update := range cmd.EnvUpdates { 242 if compiledRegex.MatchString(update) { 243 return nil 244 } 245 } 246 return fmt.Errorf("expected at least one match for env update %s. All env updates: %s", 247 expectedRegex, cmd.EnvUpdates) 248} 249 250func verifyNoEnvUpdate(cmd *command, expectedRegex string) error { 251 compiledRegex := regexp.MustCompile(matchFullString(expectedRegex)) 252 updates := cmd.EnvUpdates 253 for _, update := range updates { 254 if compiledRegex.MatchString(update) { 255 return fmt.Errorf("expected no match for env update %s. All env updates: %s", 256 expectedRegex, cmd.EnvUpdates) 257 } 258 } 259 return nil 260} 261 262func hasInternalError(stderr string) bool { 263 return strings.Contains(stderr, "Internal error") 264} 265 266func verifyInternalError(stderr string) error { 267 if !hasInternalError(stderr) { 268 return fmt.Errorf("expected an internal error. Got: %s", stderr) 269 } 270 if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); !ok { 271 return fmt.Errorf("expected a source line reference. Got: %s", stderr) 272 } 273 return nil 274} 275 276func verifyNonInternalError(stderr string, expectedRegex string) error { 277 if hasInternalError(stderr) { 278 return fmt.Errorf("expected a non internal error. Got: %s", stderr) 279 } 280 if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); ok { 281 return fmt.Errorf("expected no source line reference. Got: %s", stderr) 282 } 283 if ok, _ := regexp.MatchString(matchFullString(expectedRegex), strings.TrimSpace(stderr)); !ok { 284 return fmt.Errorf("expected stderr matching %s. Got: %s", expectedRegex, stderr) 285 } 286 return nil 287} 288 289func matchFullString(regex string) string { 290 return "^" + regex + "$" 291} 292 293func newExitCodeError(exitCode int) error { 294 // It's actually hard to create an error that represents a command 295 // with exit code. Using a real command instead. 296 tmpCmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("exit %d", exitCode)) 297 return tmpCmd.Run() 298} 299