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 "encoding/json" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 "path/filepath" 15 "strings" 16 "testing" 17) 18 19func TestOmitDoubleBuildForSuccessfulCall(t *testing.T) { 20 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 21 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 22 if ctx.cmdCount != 1 { 23 t.Errorf("expected 1 call. Got: %d", ctx.cmdCount) 24 } 25 }) 26} 27 28func TestOmitDoubleBuildForGeneralError(t *testing.T) { 29 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 30 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 31 return errors.New("someerror") 32 } 33 stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 34 if err := verifyInternalError(stderr); err != nil { 35 t.Fatal(err) 36 } 37 if !strings.Contains(stderr, "someerror") { 38 t.Errorf("unexpected error. Got: %s", stderr) 39 } 40 if ctx.cmdCount != 1 { 41 t.Errorf("expected 1 call. Got: %d", ctx.cmdCount) 42 } 43 }) 44} 45 46func TestDoubleBuildWithWNoErrorFlag(t *testing.T) { 47 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 48 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 49 switch ctx.cmdCount { 50 case 1: 51 if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil { 52 return err 53 } 54 fmt.Fprint(stderr, "-Werror originalerror") 55 return newExitCodeError(1) 56 case 2: 57 if err := verifyArgCount(cmd, 1, "-Wno-error"); err != nil { 58 return err 59 } 60 return nil 61 default: 62 t.Fatalf("unexpected command: %#v", cmd) 63 return nil 64 } 65 } 66 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 67 if ctx.cmdCount != 2 { 68 t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount) 69 } 70 }) 71} 72 73func TestKnownConfigureFileParsing(t *testing.T) { 74 withTestContext(t, func(ctx *testContext) { 75 for _, f := range []string{"conftest.c", "conftest.cpp", "/dev/null"} { 76 if !isLikelyAConfTest(ctx.cfg, ctx.newCommand(clangX86_64, f)) { 77 t.Errorf("%q isn't considered a conf test file", f) 78 } 79 } 80 }) 81} 82 83func TestDoubleBuildWithKnownConfigureFile(t *testing.T) { 84 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 85 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 86 switch ctx.cmdCount { 87 case 1: 88 if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil { 89 return err 90 } 91 fmt.Fprint(stderr, "-Werror originalerror") 92 return newExitCodeError(1) 93 default: 94 t.Fatalf("unexpected command: %#v", cmd) 95 return nil 96 } 97 } 98 99 ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, "conftest.c"))) 100 if ctx.cmdCount != 1 { 101 t.Errorf("expected 1 call. Got: %d", ctx.cmdCount) 102 } 103 }) 104} 105 106func TestDoubleBuildWithWNoErrorAndCCache(t *testing.T) { 107 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 108 ctx.cfg.useCCache = true 109 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 110 switch ctx.cmdCount { 111 case 1: 112 // TODO: This is a bug in the old wrapper that it drops the ccache path 113 // during double build. Fix this once we don't compare to the old wrapper anymore. 114 if err := verifyPath(cmd, "ccache"); err != nil { 115 return err 116 } 117 fmt.Fprint(stderr, "-Werror originalerror") 118 return newExitCodeError(1) 119 case 2: 120 if err := verifyPath(cmd, "ccache"); err != nil { 121 return err 122 } 123 return nil 124 default: 125 t.Fatalf("unexpected command: %#v", cmd) 126 return nil 127 } 128 } 129 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 130 if ctx.cmdCount != 2 { 131 t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount) 132 } 133 }) 134} 135 136func TestForwardStdoutAndStderrWhenDoubleBuildSucceeds(t *testing.T) { 137 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 138 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 139 switch ctx.cmdCount { 140 case 1: 141 fmt.Fprint(stdout, "originalmessage") 142 fmt.Fprint(stderr, "-Werror originalerror") 143 return newExitCodeError(1) 144 case 2: 145 fmt.Fprint(stdout, "retrymessage") 146 fmt.Fprint(stderr, "retryerror") 147 return nil 148 default: 149 t.Fatalf("unexpected command: %#v", cmd) 150 return nil 151 } 152 } 153 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 154 if err := verifyNonInternalError(ctx.stderrString(), "retryerror"); err != nil { 155 t.Error(err) 156 } 157 if !strings.Contains(ctx.stdoutString(), "retrymessage") { 158 t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString()) 159 } 160 }) 161} 162 163func TestForwardStdoutAndStderrWhenDoubleBuildFails(t *testing.T) { 164 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 165 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 166 switch ctx.cmdCount { 167 case 1: 168 fmt.Fprint(stdout, "originalmessage") 169 fmt.Fprint(stderr, "-Werror originalerror") 170 return newExitCodeError(3) 171 case 2: 172 fmt.Fprint(stdout, "retrymessage") 173 fmt.Fprint(stderr, "retryerror") 174 return newExitCodeError(5) 175 default: 176 t.Fatalf("unexpected command: %#v", cmd) 177 return nil 178 } 179 } 180 exitCode := callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)) 181 if exitCode != 3 { 182 t.Errorf("unexpected exitcode. Got: %d", exitCode) 183 } 184 if err := verifyNonInternalError(ctx.stderrString(), "-Werror originalerror"); err != nil { 185 t.Error(err) 186 } 187 if !strings.Contains(ctx.stdoutString(), "originalmessage") { 188 t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString()) 189 } 190 }) 191} 192 193func TestForwardStdinFromDoubleBuild(t *testing.T) { 194 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 195 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 196 // Note: This is called for the clang syntax call as well as for 197 // the gcc call, and we assert that stdin is cloned and forwarded 198 // to both. 199 stdinStr := ctx.readAllString(stdin) 200 if stdinStr != "someinput" { 201 return fmt.Errorf("unexpected stdin. Got: %s", stdinStr) 202 } 203 204 switch ctx.cmdCount { 205 case 1: 206 fmt.Fprint(stderr, "-Werror originalerror") 207 return newExitCodeError(1) 208 case 2: 209 return nil 210 default: 211 t.Fatalf("unexpected command: %#v", cmd) 212 return nil 213 } 214 } 215 io.WriteString(&ctx.stdinBuffer, "someinput") 216 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, "-", mainCc))) 217 }) 218} 219 220func TestForwardGeneralErrorWhenDoubleBuildFails(t *testing.T) { 221 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 222 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 223 switch ctx.cmdCount { 224 case 1: 225 fmt.Fprint(stderr, "-Werror originalerror") 226 return newExitCodeError(3) 227 case 2: 228 return errors.New("someerror") 229 default: 230 t.Fatalf("unexpected command: %#v", cmd) 231 return nil 232 } 233 } 234 stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 235 if err := verifyInternalError(stderr); err != nil { 236 t.Error(err) 237 } 238 if !strings.Contains(stderr, "someerror") { 239 t.Errorf("unexpected stderr. Got: %s", stderr) 240 } 241 }) 242} 243 244func TestOmitLogWarningsIfNoDoubleBuild(t *testing.T) { 245 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 246 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 247 if ctx.cmdCount != 1 { 248 t.Errorf("expected 1 call. Got: %d", ctx.cmdCount) 249 } 250 if loggedWarnings := readLoggedWarnings(ctx); loggedWarnings != nil { 251 t.Errorf("expected no logged warnings. Got: %#v", loggedWarnings) 252 } 253 }) 254} 255 256func TestLogWarningsWhenDoubleBuildSucceeds(t *testing.T) { 257 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 258 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 259 switch ctx.cmdCount { 260 case 1: 261 fmt.Fprint(stdout, "originalmessage") 262 fmt.Fprint(stderr, "-Werror originalerror") 263 return newExitCodeError(1) 264 case 2: 265 fmt.Fprint(stdout, "retrymessage") 266 fmt.Fprint(stderr, "retryerror") 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(clangX86_64, mainCc))) 274 loggedWarnings := readLoggedWarnings(ctx) 275 if loggedWarnings == nil { 276 t.Fatal("expected logged warnings") 277 } 278 if loggedWarnings.Cwd != ctx.getwd() { 279 t.Fatalf("unexpected cwd. Got: %s", loggedWarnings.Cwd) 280 } 281 loggedCmd := &command{ 282 Path: loggedWarnings.Command[0], 283 Args: loggedWarnings.Command[1:], 284 } 285 if err := verifyPath(loggedCmd, "usr/bin/clang"); err != nil { 286 t.Error(err) 287 } 288 if err := verifyArgOrder(loggedCmd, "--sysroot=.*", mainCc); err != nil { 289 t.Error(err) 290 } 291 }) 292} 293 294func TestLogWarningsWhenDoubleBuildFails(t *testing.T) { 295 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 296 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 297 switch ctx.cmdCount { 298 case 1: 299 fmt.Fprint(stdout, "originalmessage") 300 fmt.Fprint(stderr, "-Werror originalerror") 301 return newExitCodeError(1) 302 case 2: 303 fmt.Fprint(stdout, "retrymessage") 304 fmt.Fprint(stderr, "retryerror") 305 return newExitCodeError(1) 306 default: 307 t.Fatalf("unexpected command: %#v", cmd) 308 return nil 309 } 310 } 311 ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 312 loggedWarnings := readLoggedWarnings(ctx) 313 if loggedWarnings != nil { 314 t.Fatal("expected no warnings to be logged") 315 } 316 }) 317} 318 319func withForceDisableWErrorTestContext(t *testing.T, work func(ctx *testContext)) { 320 withTestContext(t, func(ctx *testContext) { 321 ctx.env = []string{"FORCE_DISABLE_WERROR=1"} 322 work(ctx) 323 }) 324} 325 326func readLoggedWarnings(ctx *testContext) *warningsJSONData { 327 files, err := ioutil.ReadDir(ctx.cfg.newWarningsDir) 328 if err != nil { 329 if _, ok := err.(*os.PathError); ok { 330 return nil 331 } 332 ctx.t.Fatal(err) 333 } 334 if len(files) != 1 { 335 ctx.t.Fatalf("expected 1 warning log file. Got: %s", files) 336 } 337 data, err := ioutil.ReadFile(filepath.Join(ctx.cfg.newWarningsDir, files[0].Name())) 338 if err != nil { 339 ctx.t.Fatal(err) 340 } 341 jsonData := warningsJSONData{} 342 if err := json.Unmarshal(data, &jsonData); err != nil { 343 ctx.t.Fatal(err) 344 } 345 return &jsonData 346} 347 348func TestDoubleBuildWerrorChmodsThingsAppropriately(t *testing.T) { 349 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 350 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 351 switch ctx.cmdCount { 352 case 1: 353 if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil { 354 return err 355 } 356 fmt.Fprint(stderr, "-Werror originalerror") 357 return newExitCodeError(1) 358 case 2: 359 if err := verifyArgCount(cmd, 1, "-Wno-error"); err != nil { 360 return err 361 } 362 return nil 363 default: 364 t.Fatalf("unexpected command: %#v", cmd) 365 return nil 366 } 367 } 368 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 369 if ctx.cmdCount != 2 { 370 // Later errors are likely senseless if we didn't get called twice. 371 t.Fatalf("expected 2 calls. Got: %d", ctx.cmdCount) 372 } 373 374 t.Logf("Warnings dir is at %q", ctx.cfg.newWarningsDir) 375 warningsDir, err := os.Open(ctx.cfg.newWarningsDir) 376 if err != nil { 377 t.Fatalf("failed to open the new warnings dir: %v", err) 378 } 379 defer warningsDir.Close() 380 381 fi, err := warningsDir.Stat() 382 if err != nil { 383 t.Fatalf("failed stat'ing the warnings dir: %v", err) 384 } 385 386 permBits := func(mode os.FileMode) int { return int(mode & 0777) } 387 388 if perms := permBits(fi.Mode()); perms != 0777 { 389 t.Errorf("mode for tempdir are %#o; expected 0777", perms) 390 } 391 392 entries, err := warningsDir.Readdir(0) 393 if err != nil { 394 t.Fatalf("failed reading entries of the tempdir: %v", err) 395 } 396 397 if len(entries) != 1 { 398 t.Errorf("found %d tempfiles in the tempdir; expected 1", len(entries)) 399 } 400 401 for _, e := range entries { 402 if perms := permBits(e.Mode()); perms != 0666 { 403 t.Errorf("mode for %q is %#o; expected 0666", e.Name(), perms) 404 } 405 } 406 }) 407} 408 409func TestAndroidDisableWerror(t *testing.T) { 410 withTestContext(t, func(ctx *testContext) { 411 ctx.cfg.isAndroidWrapper = true 412 413 // Disable werror ON 414 ctx.cfg.useLlvmNext = true 415 if !shouldForceDisableWerror(ctx, ctx.cfg) { 416 t.Errorf("disable Werror not enabled for Android with useLlvmNext") 417 } 418 419 // Disable werror OFF 420 ctx.cfg.useLlvmNext = false 421 if shouldForceDisableWerror(ctx, ctx.cfg) { 422 t.Errorf("disable-Werror enabled for Android without useLlvmNext") 423 } 424 }) 425} 426 427func TestChromeOSNoForceDisableWerror(t *testing.T) { 428 withTestContext(t, func(ctx *testContext) { 429 if shouldForceDisableWerror(ctx, ctx.cfg) { 430 t.Errorf("disable Werror enabled for ChromeOS without FORCE_DISABLE_WERROR set") 431 } 432 }) 433} 434 435func TestClangTidyNoDoubleBuild(t *testing.T) { 436 withTestContext(t, func(ctx *testContext) { 437 ctx.cfg.isAndroidWrapper = true 438 ctx.cfg.useLlvmNext = true 439 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "--", mainCc))) 440 if ctx.cmdCount != 1 { 441 t.Errorf("expected 1 call. Got: %d", ctx.cmdCount) 442 } 443 }) 444} 445 446func withAndroidClangTidyTestContext(t *testing.T, work func(ctx *testContext)) { 447 withTestContext(t, func(ctx *testContext) { 448 ctx.cfg.isAndroidWrapper = true 449 ctx.cfg.useLlvmNext = true 450 ctx.env = []string{"OUT_DIR=/tmp"} 451 452 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 453 hasArg := func(s string) bool { 454 for _, e := range cmd.Args { 455 if strings.Contains(e, s) { 456 return true 457 } 458 } 459 return false 460 } 461 switch ctx.cmdCount { 462 case 1: 463 if hasArg("-Werror") { 464 fmt.Fprint(stdout, "clang-diagnostic-") 465 return newExitCodeError(1) 466 } 467 if hasArg("-warnings-as-errors") { 468 fmt.Fprint(stdout, "warnings-as-errors") 469 return newExitCodeError(1) 470 } 471 return nil 472 case 2: 473 if hasArg("warnings-as-errors") { 474 return fmt.Errorf("Unexpected arg warnings-as-errors found. All args: %s", cmd.Args) 475 } 476 return nil 477 default: 478 t.Fatalf("unexpected command: %#v", cmd) 479 return nil 480 } 481 } 482 work(ctx) 483 }) 484} 485 486func TestClangTidyDoubleBuildClangTidyError(t *testing.T) { 487 withAndroidClangTidyTestContext(t, func(ctx *testContext) { 488 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-warnings-as-errors=*", "--", mainCc))) 489 if ctx.cmdCount != 2 { 490 t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount) 491 } 492 }) 493} 494 495func TestClangTidyDoubleBuildClangError(t *testing.T) { 496 withAndroidClangTidyTestContext(t, func(ctx *testContext) { 497 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-Werrors=*", "--", mainCc))) 498 if ctx.cmdCount != 2 { 499 t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount) 500 } 501 }) 502} 503 504func TestProcPidStatParsingWorksAsIntended(t *testing.T) { 505 t.Parallel() 506 507 type expected struct { 508 parent int 509 ok bool 510 } 511 512 testCases := []struct { 513 input string 514 expected expected 515 }{ 516 { 517 input: "2556041 (cat) R 2519408 2556041 2519408 34818 2556041 4194304", 518 expected: expected{ 519 parent: 2519408, 520 ok: true, 521 }, 522 }, 523 { 524 input: "2556041 (c a t) R 2519408 2556041 2519408 34818 2556041 4194304", 525 expected: expected{ 526 parent: 2519408, 527 ok: true, 528 }, 529 }, 530 { 531 input: "", 532 expected: expected{ 533 ok: false, 534 }, 535 }, 536 { 537 input: "foo (bar)", 538 expected: expected{ 539 ok: false, 540 }, 541 }, 542 { 543 input: "foo (bar) baz", 544 expected: expected{ 545 ok: false, 546 }, 547 }, 548 { 549 input: "foo (bar) baz 1qux2", 550 expected: expected{ 551 ok: false, 552 }, 553 }, 554 } 555 556 for _, tc := range testCases { 557 parent, ok := parseParentPidFromPidStat(tc.input) 558 if tc.expected.ok != ok { 559 t.Errorf("Got ok=%v when parsing %q; expected %v", ok, tc.input, tc.expected.ok) 560 continue 561 } 562 563 if tc.expected.parent != parent { 564 t.Errorf("Got parent=%v when parsing %q; expected %v", parent, tc.input, tc.expected.parent) 565 } 566 } 567} 568