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 "path" 12 "path/filepath" 13 "strings" 14 "testing" 15) 16 17func TestClangTidyBasename(t *testing.T) { 18 withClangTidyTestContext(t, func(ctx *testContext) { 19 testData := []struct { 20 in string 21 out string 22 }{ 23 {"./x86_64-cros-linux-gnu-clang", ".*/clang-tidy"}, 24 {"./x86_64-cros-linux-gnu-clang++", ".*/clang-tidy"}, 25 } 26 27 var clangTidyCmd *command 28 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 29 if ctx.cmdCount == 2 { 30 clangTidyCmd = cmd 31 } 32 return nil 33 } 34 35 for _, tt := range testData { 36 ctx.cmdCount = 0 37 clangTidyCmd = nil 38 ctx.must(callCompiler(ctx, ctx.cfg, 39 ctx.newCommand(tt.in, mainCc))) 40 if ctx.cmdCount != 3 { 41 t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount) 42 } 43 if err := verifyPath(clangTidyCmd, tt.out); err != nil { 44 t.Error(err) 45 } 46 } 47 }) 48} 49 50func TestClangTidyClangResourceDir(t *testing.T) { 51 withClangTidyTestContext(t, func(ctx *testContext) { 52 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 53 switch ctx.cmdCount { 54 case 1: 55 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 56 t.Error(err) 57 } 58 if err := verifyArgOrder(cmd, "--print-resource-dir"); err != nil { 59 t.Error(err) 60 } 61 fmt.Fprint(stdout, "someResourcePath") 62 return nil 63 case 2: 64 if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil { 65 return err 66 } 67 if err := verifyArgOrder(cmd, "-resource-dir=someResourcePath", mainCc); err != nil { 68 return err 69 } 70 return nil 71 case 3: 72 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 73 t.Error(err) 74 } 75 return nil 76 default: 77 t.Fatalf("unexpected command %#v", cmd) 78 return nil 79 } 80 } 81 ctx.must(callCompiler(ctx, ctx.cfg, 82 ctx.newCommand(clangX86_64, mainCc))) 83 if ctx.cmdCount != 3 { 84 t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount) 85 } 86 }) 87} 88 89func TestClangTidyArgOrder(t *testing.T) { 90 withClangTidyTestContext(t, func(ctx *testContext) { 91 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 92 if ctx.cmdCount == 2 { 93 if err := verifyArgOrder(cmd, "-checks=.*", mainCc, "--", "-resource-dir=.*", mainCc, "--some_arg"); err != nil { 94 return err 95 } 96 } 97 return nil 98 } 99 ctx.must(callCompiler(ctx, ctx.cfg, 100 ctx.newCommand(clangX86_64, mainCc, "--some_arg"))) 101 if ctx.cmdCount != 3 { 102 t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount) 103 } 104 }) 105} 106 107func TestForwardStdOutAndStderrFromClangTidyCall(t *testing.T) { 108 withClangTidyTestContext(t, func(ctx *testContext) { 109 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 110 if ctx.cmdCount == 2 { 111 fmt.Fprint(stdout, "somemessage") 112 fmt.Fprint(stderr, "someerror") 113 } 114 return nil 115 } 116 ctx.must(callCompiler(ctx, ctx.cfg, 117 ctx.newCommand(clangX86_64, mainCc))) 118 if ctx.stdoutString() != "somemessage" { 119 t.Errorf("stdout was not forwarded. Got: %s", ctx.stdoutString()) 120 } 121 if ctx.stderrString() != "someerror" { 122 t.Errorf("stderr was not forwarded. Got: %s", ctx.stderrString()) 123 } 124 }) 125} 126 127func TestIgnoreNonZeroExitCodeFromClangTidy(t *testing.T) { 128 withClangTidyTestContext(t, func(ctx *testContext) { 129 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 130 if ctx.cmdCount == 2 { 131 return newExitCodeError(23) 132 } 133 return nil 134 } 135 ctx.must(callCompiler(ctx, ctx.cfg, 136 ctx.newCommand(clangX86_64, mainCc))) 137 stderr := ctx.stderrString() 138 if err := verifyNonInternalError(stderr, "clang-tidy failed"); err != nil { 139 t.Error(err) 140 } 141 }) 142} 143 144func TestReportGeneralErrorsFromClangTidy(t *testing.T) { 145 withClangTidyTestContext(t, func(ctx *testContext) { 146 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 147 if ctx.cmdCount == 2 { 148 return errors.New("someerror") 149 } 150 return nil 151 } 152 stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, 153 ctx.newCommand(clangX86_64, mainCc))) 154 if err := verifyInternalError(stderr); err != nil { 155 t.Fatal(err) 156 } 157 if !strings.Contains(stderr, "someerror") { 158 t.Errorf("unexpected error. Got: %s", stderr) 159 } 160 }) 161} 162 163func TestOmitClangTidyForGcc(t *testing.T) { 164 withClangTidyTestContext(t, func(ctx *testContext) { 165 ctx.must(callCompiler(ctx, ctx.cfg, 166 ctx.newCommand(gccX86_64, mainCc))) 167 if ctx.cmdCount > 1 { 168 t.Errorf("expected 1 command. Got: %d", ctx.cmdCount) 169 } 170 }) 171} 172 173func TestOmitClangTidyForGccWithClangSyntax(t *testing.T) { 174 withClangTidyTestContext(t, func(ctx *testContext) { 175 ctx.must(callCompiler(ctx, ctx.cfg, 176 ctx.newCommand(gccX86_64, "-clang-syntax", mainCc))) 177 if ctx.cmdCount > 2 { 178 t.Errorf("expected 2 commands. Got: %d", ctx.cmdCount) 179 } 180 }) 181} 182 183func TestUseClangTidyBasedOnFileExtension(t *testing.T) { 184 withClangTidyTestContext(t, func(ctx *testContext) { 185 testData := []struct { 186 args []string 187 clangTidy bool 188 }{ 189 {[]string{"main.cc"}, true}, 190 {[]string{"main.cc"}, true}, 191 {[]string{"main.C"}, true}, 192 {[]string{"main.cxx"}, true}, 193 {[]string{"main.c++"}, true}, 194 {[]string{"main.xy"}, false}, 195 {[]string{"-o", "main.cc"}, false}, 196 {[]string{}, false}, 197 } 198 for _, tt := range testData { 199 ctx.cmdCount = 0 200 ctx.must(callCompiler(ctx, ctx.cfg, 201 ctx.newCommand(clangX86_64, tt.args...))) 202 if ctx.cmdCount > 1 && !tt.clangTidy { 203 t.Errorf("expected a call to clang tidy but got none for args %s", tt.args) 204 } 205 if ctx.cmdCount == 1 && tt.clangTidy { 206 t.Errorf("expected no call to clang tidy but got one for args %s", tt.args) 207 } 208 } 209 }) 210} 211 212func TestOmitCCacheWithClangTidy(t *testing.T) { 213 withClangTidyTestContext(t, func(ctx *testContext) { 214 ctx.cfg.useCCache = true 215 216 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 217 switch ctx.cmdCount { 218 case 1: 219 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 220 t.Error(err) 221 } 222 return nil 223 case 2: 224 if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil { 225 return err 226 } 227 return nil 228 default: 229 return nil 230 } 231 } 232 cmd := ctx.must(callCompiler(ctx, ctx.cfg, 233 ctx.newCommand(clangX86_64, mainCc))) 234 if ctx.cmdCount != 3 { 235 t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount) 236 } 237 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 238 t.Error(err) 239 } 240 }) 241} 242 243func TestPartiallyOmitGomaWithClangTidy(t *testing.T) { 244 withClangTidyTestContext(t, func(ctx *testContext) { 245 gomaPath := path.Join(ctx.tempDir, "gomacc") 246 // Create a file so the gomacc path is valid. 247 ctx.writeFile(gomaPath, "") 248 ctx.env = append(ctx.env, "GOMACC_PATH="+gomaPath) 249 250 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 251 switch ctx.cmdCount { 252 case 1: 253 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 254 t.Error(err) 255 } 256 return nil 257 case 2: 258 if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil { 259 return err 260 } 261 return nil 262 default: 263 return nil 264 } 265 } 266 cmd := ctx.must(callCompiler(ctx, ctx.cfg, 267 ctx.newCommand(clangX86_64, mainCc))) 268 if ctx.cmdCount != 3 { 269 t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount) 270 } 271 if err := verifyPath(cmd, gomaPath); err != nil { 272 t.Error(err) 273 } 274 }) 275} 276 277func TestTriciumClangTidyIsProperlyDetectedFromEnv(t *testing.T) { 278 withClangTidyTriciumTestContext(t, func(ctx *testContext) { 279 artifactsBase := strings.Split(ctx.env[0], "=")[1] 280 outputDir := artifactsBase + "/linting-output/clang-tidy" 281 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 282 switch ctx.cmdCount { 283 case 1: 284 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 285 t.Error(err) 286 } 287 return nil 288 case 2: 289 if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil { 290 return err 291 } 292 293 hasFixesFile := false 294 for _, arg := range cmd.Args { 295 if path := strings.TrimPrefix(arg, "--export-fixes="); path != arg { 296 hasFixesFile = true 297 298 if !strings.HasPrefix(path, outputDir) { 299 t.Errorf("fixes file was %q; expected it to be in %q", path, outputDir) 300 } 301 break 302 } 303 } 304 305 if !hasFixesFile { 306 t.Error("no fixes file was provided to a tricium invocation") 307 } 308 309 return nil 310 default: 311 return nil 312 } 313 } 314 cmd := ctx.must(callCompiler(ctx, ctx.cfg, 315 ctx.newCommand(clangX86_64, mainCc))) 316 if ctx.cmdCount != 3 { 317 t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount) 318 } 319 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 320 t.Error(err) 321 } 322 }) 323} 324 325func TestTriciumClangTidySkipsProtobufFiles(t *testing.T) { 326 withClangTidyTriciumTestContext(t, func(ctx *testContext) { 327 cmd := ctx.must(callCompiler(ctx, ctx.cfg, 328 ctx.newCommand(clangX86_64, mainCc+".pb.cc"))) 329 if ctx.cmdCount != 1 { 330 t.Errorf("expected tricium clang-tidy to not execute on a protobuf file") 331 } 332 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 333 t.Error(err) 334 } 335 }) 336} 337 338func testClangTidyFiltersClangTidySpecificFlagsWithPresetEnv(t *testing.T, ctx *testContext) { 339 addedFlag := "--some_clang_tidy=flag" 340 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 341 switch ctx.cmdCount { 342 case 1: 343 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 344 t.Error(err) 345 } else if err := verifyArgCount(cmd, 0, addedFlag); err != nil { 346 t.Error(err) 347 } 348 return nil 349 case 2: 350 if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil { 351 t.Error(err) 352 } else if verifyArgCount(cmd, 1, addedFlag); err != nil { 353 t.Error(err) 354 } 355 return nil 356 default: 357 return nil 358 } 359 } 360 cmd := ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc, "-clang-tidy-flag="+addedFlag))) 361 if ctx.cmdCount != 3 { 362 t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount) 363 } 364 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 365 t.Error(err) 366 } 367} 368 369func TestClangTidyFiltersClangTidySpecificFlagsForTricium(t *testing.T) { 370 withClangTidyTriciumTestContext(t, func(ctx *testContext) { 371 testClangTidyFiltersClangTidySpecificFlagsWithPresetEnv(t, ctx) 372 }) 373} 374 375func TestClangTidyFiltersClangTidySpecificFlags(t *testing.T) { 376 withClangTidyTestContext(t, func(ctx *testContext) { 377 testClangTidyFiltersClangTidySpecificFlagsWithPresetEnv(t, ctx) 378 }) 379} 380 381func TestClangTidyFlagsAreFilteredFromGccInvocations(t *testing.T) { 382 withClangTidyTestContext(t, func(ctx *testContext) { 383 cmd := ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc, "-clang-tidy-flag=--foo"))) 384 if err := verifyArgCount(cmd, 0, ".*--foo.*"); err != nil { 385 t.Error(err) 386 } 387 }) 388} 389 390func TestTriciumReportsClangTidyCrashesGracefully(t *testing.T) { 391 withClangTidyTriciumTestContext(t, func(ctx *testContext) { 392 crashArtifactsDir := filepath.Join(ctx.setArbitraryClangArtifactsDir(), clangCrashArtifactsSubdir) 393 ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 394 switch ctx.cmdCount { 395 case 1: 396 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 397 t.Error(err) 398 } 399 return nil 400 case 2: 401 if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil { 402 return err 403 } 404 405 if _, err := io.WriteString(stdout, clangTidyCrashSubstring); err != nil { 406 return err 407 } 408 return nil 409 case 3: 410 if err := verifyPath(cmd, "usr/bin/clang"); err != nil { 411 t.Error(err) 412 } 413 414 args := cmd.Args 415 if len(args) < 3 { 416 t.Errorf("insufficient number of args provided; got %d; want at least 3", len(args)) 417 return nil 418 } 419 420 lastArgs := args[len(args)-3:] 421 eArg, oArg, outFileArg := lastArgs[0], lastArgs[1], lastArgs[2] 422 if eArg != "-E" { 423 t.Errorf("got eArg=%q; wanted -E", eArg) 424 } 425 426 if oArg != "-o" { 427 t.Errorf("got oArg=%q; wanted -o", oArg) 428 } 429 430 wantPrefix := path.Join(crashArtifactsDir, "clang-tidy") 431 if !strings.HasPrefix(outFileArg, wantPrefix) { 432 t.Errorf("got out file %q; wanted one starting with %q", outFileArg, wantPrefix) 433 } 434 435 return nil 436 default: 437 return nil 438 } 439 } 440 ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))) 441 if ctx.cmdCount != 4 { 442 t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount) 443 } 444 }) 445} 446 447func withClangTidyTestContextBaseDir(t *testing.T, work func(ctx *testContext)) { 448 withTestContext(t, func(ctx *testContext) { 449 artifactDir := t.TempDir() 450 ctx.env = []string{"CROS_ARTIFACTS_TMP_DIR=" + artifactDir} 451 work(ctx) 452 }) 453} 454 455func withClangTidyTestContext(t *testing.T, work func(ctx *testContext)) { 456 withClangTidyTestContextBaseDir(t, func(ctx *testContext) { 457 ctx.env = append(ctx.env, "WITH_TIDY=1") 458 work(ctx) 459 }) 460} 461 462func withClangTidyTriciumTestContext(t *testing.T, work func(ctx *testContext)) { 463 withClangTidyTestContextBaseDir(t, func(ctx *testContext) { 464 ctx.env = append(ctx.env, "WITH_TIDY=tricium") 465 work(ctx) 466 }) 467} 468