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 "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.NoteTestWritesToUmask() 322 323 ctx.env = []string{"FORCE_DISABLE_WERROR=1"} 324 work(ctx) 325 }) 326} 327 328func readLoggedWarnings(ctx *testContext) *warningsJSONData { 329 files, err := ioutil.ReadDir(ctx.cfg.newWarningsDir) 330 if err != nil { 331 if _, ok := err.(*os.PathError); ok { 332 return nil 333 } 334 ctx.t.Fatal(err) 335 } 336 if len(files) != 1 { 337 ctx.t.Fatalf("expected 1 warning log file. Got: %s", files) 338 } 339 data, err := ioutil.ReadFile(filepath.Join(ctx.cfg.newWarningsDir, files[0].Name())) 340 if err != nil { 341 ctx.t.Fatal(err) 342 } 343 jsonData := warningsJSONData{} 344 if err := json.Unmarshal(data, &jsonData); err != nil { 345 ctx.t.Fatal(err) 346 } 347 return &jsonData 348} 349 350func TestDoubleBuildWerrorChmodsThingsAppropriately(t *testing.T) { 351 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 352 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 353 switch ctx.cmdCount { 354 case 1: 355 if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil { 356 return err 357 } 358 fmt.Fprint(stderr, "-Werror originalerror") 359 return newExitCodeError(1) 360 case 2: 361 if err := verifyArgCount(cmd, 1, "-Wno-error"); err != nil { 362 return err 363 } 364 return nil 365 default: 366 t.Fatalf("unexpected command: %#v", cmd) 367 return nil 368 } 369 } 370 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 371 if ctx.cmdCount != 2 { 372 // Later errors are likely senseless if we didn't get called twice. 373 t.Fatalf("expected 2 calls. Got: %d", ctx.cmdCount) 374 } 375 376 t.Logf("Warnings dir is at %q", ctx.cfg.newWarningsDir) 377 warningsDir, err := os.Open(ctx.cfg.newWarningsDir) 378 if err != nil { 379 t.Fatalf("failed to open the new warnings dir: %v", err) 380 } 381 defer warningsDir.Close() 382 383 fi, err := warningsDir.Stat() 384 if err != nil { 385 t.Fatalf("failed stat'ing the warnings dir: %v", err) 386 } 387 388 permBits := func(mode os.FileMode) int { return int(mode & 0777) } 389 390 if perms := permBits(fi.Mode()); perms != 0777 { 391 t.Errorf("mode for tempdir are %#o; expected 0777", perms) 392 } 393 394 entries, err := warningsDir.Readdir(0) 395 if err != nil { 396 t.Fatalf("failed reading entries of the tempdir: %v", err) 397 } 398 399 if len(entries) != 1 { 400 t.Errorf("found %d tempfiles in the tempdir; expected 1", len(entries)) 401 } 402 403 for _, e := range entries { 404 if perms := permBits(e.Mode()); perms != 0666 { 405 t.Errorf("mode for %q is %#o; expected 0666", e.Name(), perms) 406 } 407 } 408 }) 409} 410 411func TestAndroidDisableWerror(t *testing.T) { 412 withTestContext(t, func(ctx *testContext) { 413 ctx.cfg.isAndroidWrapper = true 414 415 // Disable werror ON 416 ctx.cfg.useLlvmNext = true 417 if !shouldForceDisableWerror(ctx, ctx.cfg, gccType) { 418 t.Errorf("disable Werror not enabled for Android with useLlvmNext") 419 } 420 421 if !shouldForceDisableWerror(ctx, ctx.cfg, clangType) { 422 t.Errorf("disable Werror not enabled for Android with useLlvmNext") 423 } 424 425 // Disable werror OFF 426 ctx.cfg.useLlvmNext = false 427 if shouldForceDisableWerror(ctx, ctx.cfg, gccType) { 428 t.Errorf("disable-Werror enabled for Android without useLlvmNext") 429 } 430 431 if shouldForceDisableWerror(ctx, ctx.cfg, clangType) { 432 t.Errorf("disable-Werror enabled for Android without useLlvmNext") 433 } 434 }) 435} 436 437func TestChromeOSNoForceDisableWerror(t *testing.T) { 438 withTestContext(t, func(ctx *testContext) { 439 if shouldForceDisableWerror(ctx, ctx.cfg, gccType) { 440 t.Errorf("disable Werror enabled for ChromeOS without FORCE_DISABLE_WERROR set") 441 } 442 443 if shouldForceDisableWerror(ctx, ctx.cfg, clangType) { 444 t.Errorf("disable Werror enabled for ChromeOS without FORCE_DISABLE_WERROR set") 445 } 446 }) 447} 448 449func TestChromeOSForceDisableWerrorOnlyAppliesToClang(t *testing.T) { 450 withForceDisableWErrorTestContext(t, func(ctx *testContext) { 451 if !shouldForceDisableWerror(ctx, ctx.cfg, clangType) { 452 t.Errorf("Disable -Werror should be enabled for clang.") 453 } 454 455 if shouldForceDisableWerror(ctx, ctx.cfg, gccType) { 456 t.Errorf("Disable -Werror should be disabled for gcc.") 457 } 458 }) 459} 460 461func TestClangTidyNoDoubleBuild(t *testing.T) { 462 withTestContext(t, func(ctx *testContext) { 463 ctx.cfg.isAndroidWrapper = true 464 ctx.cfg.useLlvmNext = true 465 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "--", mainCc))) 466 if ctx.cmdCount != 1 { 467 t.Errorf("expected 1 call. Got: %d", ctx.cmdCount) 468 } 469 }) 470} 471 472func withAndroidClangTidyTestContext(t *testing.T, work func(ctx *testContext)) { 473 withTestContext(t, func(ctx *testContext) { 474 ctx.cfg.isAndroidWrapper = true 475 ctx.cfg.useLlvmNext = true 476 ctx.env = []string{"OUT_DIR=/tmp"} 477 478 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 479 hasArg := func(s string) bool { 480 for _, e := range cmd.Args { 481 if strings.Contains(e, s) { 482 return true 483 } 484 } 485 return false 486 } 487 switch ctx.cmdCount { 488 case 1: 489 if hasArg("-Werror") { 490 fmt.Fprint(stdout, "clang-diagnostic-") 491 return newExitCodeError(1) 492 } 493 if hasArg("-warnings-as-errors") { 494 fmt.Fprint(stdout, "warnings-as-errors") 495 return newExitCodeError(1) 496 } 497 return nil 498 case 2: 499 if hasArg("warnings-as-errors") { 500 return fmt.Errorf("Unexpected arg warnings-as-errors found. All args: %s", cmd.Args) 501 } 502 return nil 503 default: 504 t.Fatalf("unexpected command: %#v", cmd) 505 return nil 506 } 507 } 508 work(ctx) 509 }) 510} 511 512func TestClangTidyDoubleBuildClangTidyError(t *testing.T) { 513 withAndroidClangTidyTestContext(t, func(ctx *testContext) { 514 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-warnings-as-errors=*", "--", mainCc))) 515 if ctx.cmdCount != 2 { 516 t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount) 517 } 518 }) 519} 520 521func TestClangTidyDoubleBuildClangError(t *testing.T) { 522 withAndroidClangTidyTestContext(t, func(ctx *testContext) { 523 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-Werrors=*", "--", mainCc))) 524 if ctx.cmdCount != 2 { 525 t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount) 526 } 527 }) 528} 529 530func TestProcPidStatParsingWorksAsIntended(t *testing.T) { 531 t.Parallel() 532 533 type expected struct { 534 parent int 535 ok bool 536 } 537 538 testCases := []struct { 539 input string 540 expected expected 541 }{ 542 { 543 input: "2556041 (cat) R 2519408 2556041 2519408 34818 2556041 4194304", 544 expected: expected{ 545 parent: 2519408, 546 ok: true, 547 }, 548 }, 549 { 550 input: "2556041 (c a t) R 2519408 2556041 2519408 34818 2556041 4194304", 551 expected: expected{ 552 parent: 2519408, 553 ok: true, 554 }, 555 }, 556 { 557 input: "", 558 expected: expected{ 559 ok: false, 560 }, 561 }, 562 { 563 input: "foo (bar)", 564 expected: expected{ 565 ok: false, 566 }, 567 }, 568 { 569 input: "foo (bar) baz", 570 expected: expected{ 571 ok: false, 572 }, 573 }, 574 { 575 input: "foo (bar) baz 1qux2", 576 expected: expected{ 577 ok: false, 578 }, 579 }, 580 } 581 582 for _, tc := range testCases { 583 parent, ok := parseParentPidFromPidStat(tc.input) 584 if tc.expected.ok != ok { 585 t.Errorf("Got ok=%v when parsing %q; expected %v", ok, tc.input, tc.expected.ok) 586 continue 587 } 588 589 if tc.expected.parent != parent { 590 t.Errorf("Got parent=%v when parsing %q; expected %v", parent, tc.input, tc.expected.parent) 591 } 592 } 593} 594