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