1// Copyright 2019 The ChromiumOS Authors 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 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "strings" 15 "syscall" 16 "testing" 17) 18 19// Save this off before goroutines start running, since this necessarily involves modifying the 20// value for our umask, and that screams subtle race conditions. :) 21var umaskAtStartup = func() os.FileMode { 22 umask := syscall.Umask(0) 23 syscall.Umask(umask) 24 return os.FileMode(umask) 25}() 26 27func TestOmitFallbackCompileForSuccessfulCall(t *testing.T) { 28 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 29 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc))) 30 if ctx.cmdCount != 1 { 31 t.Errorf("expected 1 call. Got: %d", ctx.cmdCount) 32 } 33 }) 34} 35 36func TestOmitFallbackCompileForGeneralError(t *testing.T) { 37 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 38 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 39 return errors.New("someerror") 40 } 41 stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc))) 42 if err := verifyInternalError(stderr); err != nil { 43 t.Fatal(err) 44 } 45 if !strings.Contains(stderr, "someerror") { 46 t.Errorf("unexpected error. Got: %s", stderr) 47 } 48 if ctx.cmdCount != 1 { 49 t.Errorf("expected 1 call. Got: %d", ctx.cmdCount) 50 } 51 }) 52} 53 54func TestCompileWithFallbackForNonZeroExitCode(t *testing.T) { 55 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 56 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 57 switch ctx.cmdCount { 58 case 1: 59 return newExitCodeError(1) 60 case 2: 61 if err := verifyPath(cmd, "fallback_compiler/clang"); err != nil { 62 return err 63 } 64 if err := verifyEnvUpdate(cmd, "ANDROID_LLVM_PREBUILT_COMPILER_PATH="); err != nil { 65 return err 66 } 67 return nil 68 default: 69 t.Fatalf("unexpected command: %#v", cmd) 70 return nil 71 } 72 } 73 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc))) 74 if ctx.cmdCount != 2 { 75 t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount) 76 } 77 }) 78} 79 80func TestCompileWithFallbackForwardStdoutAndStderr(t *testing.T) { 81 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 82 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 83 switch ctx.cmdCount { 84 case 1: 85 fmt.Fprint(stdout, "originalmessage") 86 fmt.Fprint(stderr, "originalerror") 87 return newExitCodeError(1) 88 case 2: 89 fmt.Fprint(stdout, "fallbackmessage") 90 fmt.Fprint(stderr, "fallbackerror") 91 return nil 92 default: 93 t.Fatalf("unexpected command: %#v", cmd) 94 return nil 95 } 96 } 97 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc))) 98 if err := verifyNonInternalError(ctx.stderrString(), "originalerrorfallbackerror"); err != nil { 99 t.Error(err) 100 } 101 if !strings.Contains(ctx.stdoutString(), "originalmessagefallbackmessage") { 102 t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString()) 103 } 104 }) 105} 106 107func TestForwardGeneralErrorWhenFallbackCompileFails(t *testing.T) { 108 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 109 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 110 switch ctx.cmdCount { 111 case 1: 112 return newExitCodeError(1) 113 case 2: 114 return errors.New("someerror") 115 default: 116 t.Fatalf("unexpected command: %#v", cmd) 117 return nil 118 } 119 } 120 stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc))) 121 if err := verifyInternalError(stderr); err != nil { 122 t.Error(err) 123 } 124 if !strings.Contains(stderr, "someerror") { 125 t.Errorf("unexpected stderr. Got: %s", stderr) 126 } 127 }) 128} 129 130func TestForwardExitCodeWhenFallbackCompileFails(t *testing.T) { 131 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 132 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 133 switch ctx.cmdCount { 134 case 1: 135 return newExitCodeError(1) 136 case 2: 137 return newExitCodeError(2) 138 default: 139 t.Fatalf("unexpected command: %#v", cmd) 140 return nil 141 } 142 } 143 exitCode := callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc)) 144 if exitCode != 2 { 145 t.Errorf("unexpected exit code. Got: %d", exitCode) 146 } 147 }) 148} 149 150func TestForwardStdinToFallbackCompile(t *testing.T) { 151 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 152 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 153 stdinStr := ctx.readAllString(stdin) 154 if stdinStr != "someinput" { 155 return fmt.Errorf("unexpected stdin. Got: %s", stdinStr) 156 } 157 158 switch ctx.cmdCount { 159 case 1: 160 return newExitCodeError(1) 161 case 2: 162 return nil 163 default: 164 t.Fatalf("unexpected command: %#v", cmd) 165 return nil 166 } 167 } 168 io.WriteString(&ctx.stdinBuffer, "someinput") 169 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, "-", mainCc))) 170 }) 171} 172 173func TestCompileWithFallbackExtraArgs(t *testing.T) { 174 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 175 testData := []struct { 176 compiler string 177 expectExtraArgs bool 178 }{ 179 {"./clang", true}, 180 {"./clang++", true}, 181 {"./clang-tidy", false}, 182 } 183 ctx.env = append(ctx.env, "ANDROID_LLVM_FALLBACK_DISABLED_WARNINGS=-a -b") 184 extraArgs := []string{"-fno-color-diagnostics", "-a", "-b"} 185 for _, tt := range testData { 186 ctx.cmdCount = 0 187 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 188 switch ctx.cmdCount { 189 case 1: 190 if tt.expectExtraArgs { 191 if err := verifyArgOrder(cmd, extraArgs...); err != nil { 192 return err 193 } 194 } else { 195 for _, arg := range extraArgs { 196 if err := verifyArgCount(cmd, 0, arg); err != nil { 197 return err 198 } 199 } 200 } 201 return newExitCodeError(1) 202 case 2: 203 for _, arg := range extraArgs { 204 if err := verifyArgCount(cmd, 0, arg); err != nil { 205 return err 206 } 207 } 208 return nil 209 default: 210 t.Fatalf("unexpected command: %#v", cmd) 211 return nil 212 } 213 } 214 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(tt.compiler, mainCc))) 215 if ctx.cmdCount != 2 { 216 t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount) 217 } 218 } 219 }) 220} 221 222func TestCompileWithFallbackLogCommandAndErrors(t *testing.T) { 223 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 224 ctx.NoteTestReadsFromUmask() 225 226 ctx.env = append(ctx.env, "ANDROID_LLVM_FALLBACK_DISABLED_WARNINGS=-a -b") 227 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 228 switch ctx.cmdCount { 229 case 1: 230 fmt.Fprint(stderr, "someerror\n") 231 return newExitCodeError(1) 232 case 2: 233 return nil 234 default: 235 t.Fatalf("unexpected command: %#v", cmd) 236 return nil 237 } 238 } 239 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc))) 240 241 log := readCompileWithFallbackErrorLog(ctx) 242 if log != `==================COMMAND:==================== 243./clang.real main.cc -fno-color-diagnostics -a -b 244 245someerror 246============================================== 247 248` { 249 t.Errorf("unexpected log. Got: %s", log) 250 } 251 252 entry, _ := os.Lstat(filepath.Join(ctx.tempDir, "fallback_stderr")) 253 if entry.Mode()&0777 != 0644 & ^umaskAtStartup { 254 t.Errorf("unexpected mode for logfile. Got: %#o", entry.Mode()) 255 } 256 }) 257} 258 259func TestCompileWithFallbackAppendToLog(t *testing.T) { 260 withCompileWithFallbackTestContext(t, func(ctx *testContext) { 261 ctx.writeFile(filepath.Join(ctx.tempDir, "fallback_stderr"), "oldContent\n") 262 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 263 switch ctx.cmdCount { 264 case 1: 265 return newExitCodeError(1) 266 case 2: 267 return nil 268 default: 269 t.Fatalf("unexpected command: %#v", cmd) 270 return nil 271 } 272 } 273 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc))) 274 275 log := readCompileWithFallbackErrorLog(ctx) 276 if !strings.Contains(log, "oldContent") { 277 t.Errorf("old content not present: %s", log) 278 } 279 if !strings.Contains(log, "clang.real") { 280 t.Errorf("new content not present: %s", log) 281 } 282 }) 283} 284 285func withCompileWithFallbackTestContext(t *testing.T, work func(ctx *testContext)) { 286 withTestContext(t, func(ctx *testContext) { 287 ctx.cfg.isAndroidWrapper = true 288 ctx.env = []string{ 289 "ANDROID_LLVM_PREBUILT_COMPILER_PATH=fallback_compiler", 290 "ANDROID_LLVM_STDERR_REDIRECT=" + filepath.Join(ctx.tempDir, "fallback_stderr"), 291 } 292 work(ctx) 293 }) 294} 295 296func readCompileWithFallbackErrorLog(ctx *testContext) string { 297 logFile := filepath.Join(ctx.tempDir, "fallback_stderr") 298 data, err := ioutil.ReadFile(logFile) 299 if err != nil { 300 ctx.t.Fatalf("error reading log file %s: %s", logFile, err) 301 } 302 return string(data) 303} 304