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