github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/action/cppcompile/preprocessor_test.go (about) 1 // Copyright 2023 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cppcompile 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "path/filepath" 22 "sync" 23 "testing" 24 25 spb "github.com/bazelbuild/reclient/api/scandeps" 26 "github.com/bazelbuild/reclient/internal/pkg/execroot" 27 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor" 28 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/depscache" 29 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/flags" 30 31 "github.com/bazelbuild/remote-apis-sdks/go/pkg/command" 32 "github.com/bazelbuild/remote-apis-sdks/go/pkg/filemetadata" 33 "github.com/google/go-cmp/cmp" 34 "github.com/google/go-cmp/cmp/cmpopts" 35 ) 36 37 var ( 38 strSliceCmp = cmpopts.SortSlices(func(a, b string) bool { return a < b }) 39 ) 40 41 func TestComputeSpec(t *testing.T) { 42 tests := []struct { 43 name string 44 scandepsCapabilities *spb.CapabilitiesResponse 45 printResDirOut string 46 inputExtraCmd []string 47 wantResDir string 48 }{ 49 { 50 name: "ResourceDirProvided/ExpectsResourceDir", 51 scandepsCapabilities: &spb.CapabilitiesResponse{ExpectsResourceDir: true}, 52 printResDirOut: " /some/other/dir ", 53 inputExtraCmd: []string{"-resource-dir", "/some/dir"}, 54 wantResDir: "/some/dir", 55 }, 56 { 57 name: "ResourceDirProvided/DoesntExpectResourceDir", 58 scandepsCapabilities: &spb.CapabilitiesResponse{ExpectsResourceDir: false}, 59 printResDirOut: " /some/other/dir ", 60 inputExtraCmd: []string{"-resource-dir", "/some/dir"}, 61 wantResDir: "/some/dir", 62 }, 63 { 64 name: "ResourceDirMissing/ExpectsResourceDir", 65 scandepsCapabilities: &spb.CapabilitiesResponse{ExpectsResourceDir: true}, 66 printResDirOut: " /some/other/dir ", 67 wantResDir: "/some/other/dir", 68 }, 69 { 70 name: "ResourceDirMissing/DoesntExpectResourceDir", 71 scandepsCapabilities: &spb.CapabilitiesResponse{ExpectsResourceDir: false}, 72 printResDirOut: " /some/other/dir ", 73 }, 74 } 75 76 for _, tc := range tests { 77 tc := tc 78 t.Run(tc.name, func(t *testing.T) { 79 ctx := context.Background() 80 fmc := filemetadata.NewSingleFlightCache() 81 s := &stubCPPDepScanner{ 82 res: []string{"include/foo.h"}, 83 err: nil, 84 capabilities: tc.scandepsCapabilities, 85 } 86 c := &Preprocessor{ 87 CPPDepScanner: s, 88 BasePreprocessor: &inputprocessor.BasePreprocessor{ 89 Ctx: ctx, 90 FileMetadataCache: fmc, 91 Executor: &stubExecutor{outStr: tc.printResDirOut}, 92 }, 93 } 94 95 // Tests that virtual inputs only include existing directories and excludes files. 96 existingFiles := []string{ 97 filepath.Clean("bin/clang++"), 98 filepath.Clean("src/test.cpp"), 99 filepath.Clean("include/foo/a"), 100 filepath.Clean("include/a/b.hmap"), 101 filepath.Clean("out/dummy"), 102 } 103 er, cleanup := execroot.Setup(t, existingFiles) 104 defer cleanup() 105 inputs := []string{filepath.Clean("include/a/b.hmap")} 106 opts := inputprocessor.Options{ 107 Cmd: append([]string{"../bin/clang++", "-o", "test.o", "-MF", "test.d", "-I../include/foo", "-I../include/bar", "-I../include/a/b.hmap", 108 "-std=c++14", "-Xclang", "-verify", "-c", "../src/test.cpp"}, tc.inputExtraCmd...), 109 WorkingDir: "out", 110 ExecRoot: er, 111 Labels: map[string]string{"type": "compile", "compiler": "clang", "lang": "cpp"}, 112 Inputs: &command.InputSpec{ 113 Inputs: inputs, 114 }, 115 } 116 got, err := inputprocessor.Compute(c, opts) 117 if err != nil { 118 t.Errorf("Spec() failed: %v", err) 119 } 120 want := &command.InputSpec{ 121 Inputs: []string{ 122 filepath.Clean("src/test.cpp"), 123 filepath.Clean("bin/clang++"), 124 filepath.Clean("include/a/b.hmap"), 125 }, 126 VirtualInputs: []*command.VirtualInput{ 127 {Path: filepath.Clean("include/foo"), IsEmptyDirectory: true}, 128 }, 129 } 130 if diff := cmp.Diff(want, got.InputSpec, strSliceCmp); diff != "" { 131 t.Errorf("Compute() returned diff (-want +got): %v", diff) 132 } 133 wantCmd := []string{ 134 filepath.Join(er, "bin/clang++"), 135 "-I../include/foo", 136 "-I../include/bar", 137 "-I../include/a/b.hmap", 138 "-std=c++14", "-c", // expect -std=xx to not be normalized 139 "-Qunused-arguments", // expect Qunused-arguments to be added, and -Xclang -verify to be removed 140 "-o", "test.o", 141 filepath.Join(er, "src/test.cpp"), 142 } 143 gotCmdNoResDir := make([]string, 0, len(s.gotCmd)) 144 i := 0 145 gotResDir := "" 146 for i < len(s.gotCmd) { 147 if s.gotCmd[i] == "-resource-dir" { 148 i++ 149 gotResDir = s.gotCmd[i] 150 } else { 151 gotCmdNoResDir = append(gotCmdNoResDir, s.gotCmd[i]) 152 } 153 i++ 154 } 155 if diff := cmp.Diff(wantCmd, gotCmdNoResDir); diff != "" { 156 t.Errorf("Unexpected command passed to the dependency scanner (-want +got): %v", diff) 157 } 158 if tc.wantResDir != gotResDir { 159 t.Errorf("CPP command had incorrect resource dir, wanted %v, got %v", tc.wantResDir, gotResDir) 160 } 161 }) 162 } 163 } 164 165 func TestComputeSpecWithDepsCache(t *testing.T) { 166 ctx := context.Background() 167 fmc := filemetadata.NewSingleFlightCache() 168 s := &stubCPPDepScanner{ 169 res: []string{"include/foo.h"}, 170 err: nil, 171 } 172 c := &Preprocessor{ 173 CPPDepScanner: s, 174 BasePreprocessor: &inputprocessor.BasePreprocessor{Ctx: ctx, FileMetadataCache: fmc}, 175 DepsCache: depscache.New(filemetadata.NewSingleFlightCache()), 176 } 177 178 existingFiles := []string{ 179 filepath.Clean("bin/clang++"), 180 filepath.Clean("src/test.cpp"), 181 filepath.Clean("include/foo/a"), 182 filepath.Clean("out/dummy"), 183 } 184 er, cleanup := execroot.Setup(t, existingFiles) 185 defer cleanup() 186 c.DepsCache.LoadFromDir(er) 187 opts := inputprocessor.Options{ 188 Cmd: []string{"../bin/clang++", "-o", "test.o", "-MF", "test.d", "-I../include/foo", "-I", "../include/bar", "-c", "../src/test.cpp"}, 189 WorkingDir: "out", 190 ExecRoot: er, 191 Labels: map[string]string{"type": "compile", "compiler": "clang", "lang": "cpp"}, 192 } 193 var wg sync.WaitGroup 194 wg.Add(1) 195 c.testOnlySetDone = func() { wg.Done() } 196 got, err := inputprocessor.Compute(c, opts) 197 if err != nil { 198 t.Errorf("Compute() failed: %v", err) 199 } 200 want := &command.InputSpec{ 201 Inputs: []string{ 202 filepath.Clean("src/test.cpp"), 203 filepath.Clean("bin/clang++"), 204 }, 205 VirtualInputs: []*command.VirtualInput{ 206 {Path: filepath.Clean("include/foo"), IsEmptyDirectory: true}, 207 }, 208 } 209 if diff := cmp.Diff(want, got.InputSpec, strSliceCmp); diff != "" { 210 t.Errorf("Compute() returned diff (-want +got): %v", diff) 211 } 212 wg.Wait() 213 c.DepsCache.WriteToDisk(er) 214 c = &Preprocessor{ 215 CPPDepScanner: s, 216 BasePreprocessor: &inputprocessor.BasePreprocessor{Ctx: ctx, FileMetadataCache: fmc}, 217 DepsCache: depscache.New(filemetadata.NewSingleFlightCache()), 218 } 219 c.DepsCache.LoadFromDir(er) 220 got, err = inputprocessor.Compute(c, opts) 221 if err != nil { 222 t.Errorf("Compute() failed: %v", err) 223 } 224 if diff := cmp.Diff(want, got.InputSpec, strSliceCmp); diff != "" { 225 t.Errorf("Compute() returned diff (-want +got): %v", diff) 226 } 227 if s.processCalls != 1 { 228 t.Errorf("Wrong number of ProcessInputs calls: got %v, want 1", s.processCalls) 229 } 230 } 231 232 func TestComputeSpecWithDepsCache_ResourceDirChanged(t *testing.T) { 233 ctx := context.Background() 234 fmc := filemetadata.NewSingleFlightCache() 235 s := &stubCPPDepScanner{ 236 res: []string{"include/foo.h"}, 237 err: nil, 238 capabilities: &spb.CapabilitiesResponse{ExpectsResourceDir: true}, 239 } 240 c := &Preprocessor{ 241 CPPDepScanner: s, 242 BasePreprocessor: &inputprocessor.BasePreprocessor{ 243 Ctx: ctx, 244 FileMetadataCache: fmc, 245 Executor: &stubExecutor{outStr: "/first/resource/dir"}, 246 }, 247 DepsCache: depscache.New(filemetadata.NewSingleFlightCache()), 248 } 249 250 existingFiles := []string{ 251 filepath.Clean("bin/clang++"), 252 filepath.Clean("src/test.cpp"), 253 filepath.Clean("include/foo/a"), 254 filepath.Clean("out/dummy"), 255 } 256 er, cleanup := execroot.Setup(t, existingFiles) 257 defer cleanup() 258 c.DepsCache.LoadFromDir(er) 259 opts := inputprocessor.Options{ 260 Cmd: []string{"../bin/clang++", "-o", "test.o", "-MF", "test.d", "-I../include/foo", "-I", "../include/bar", "-c", "../src/test.cpp"}, 261 WorkingDir: "out", 262 ExecRoot: er, 263 Labels: map[string]string{"type": "compile", "compiler": "clang", "lang": "cpp"}, 264 } 265 var wg sync.WaitGroup 266 wg.Add(1) 267 c.testOnlySetDone = func() { wg.Done() } 268 got, err := inputprocessor.Compute(c, opts) 269 if err != nil { 270 t.Errorf("Compute() failed: %v", err) 271 } 272 want := &command.InputSpec{ 273 Inputs: []string{ 274 filepath.Clean("src/test.cpp"), 275 filepath.Clean("bin/clang++"), 276 }, 277 VirtualInputs: []*command.VirtualInput{ 278 {Path: filepath.Clean("include/foo"), IsEmptyDirectory: true}, 279 }, 280 } 281 if diff := cmp.Diff(want, got.InputSpec, strSliceCmp); diff != "" { 282 t.Errorf("Compute() returned diff (-want +got): %v", diff) 283 } 284 wg.Wait() 285 c.DepsCache.WriteToDisk(er) 286 // Simulate new run of reproxy 287 clearResourceDirCache(t) 288 c = &Preprocessor{ 289 CPPDepScanner: s, 290 BasePreprocessor: &inputprocessor.BasePreprocessor{ 291 Ctx: ctx, 292 FileMetadataCache: fmc, 293 Executor: &stubExecutor{outStr: "/second/resource/dir"}, 294 }, 295 DepsCache: depscache.New(filemetadata.NewSingleFlightCache()), 296 } 297 c.DepsCache.LoadFromDir(er) 298 got, err = inputprocessor.Compute(c, opts) 299 if err != nil { 300 t.Errorf("Compute() failed: %v", err) 301 } 302 if diff := cmp.Diff(want, got.InputSpec, strSliceCmp); diff != "" { 303 t.Errorf("Compute() returned diff (-want +got): %v", diff) 304 } 305 if s.processCalls != 2 { 306 t.Errorf("Wrong number of ProcessInputs calls: got %v, want 1", s.processCalls) 307 } 308 } 309 310 func clearResourceDirCache(t *testing.T) { 311 t.Helper() 312 resourceDirsMu.Lock() 313 defer resourceDirsMu.Unlock() 314 resourceDirs = map[string]resourceDirInfo{} 315 } 316 317 func TestComputeSpec_SysrootAndProfileSampleUseArgsConvertedToAbsolutePath(t *testing.T) { 318 ctx := context.Background() 319 fmc := filemetadata.NewSingleFlightCache() 320 s := &stubCPPDepScanner{ 321 res: []string{"include/foo.h"}, 322 err: nil, 323 } 324 c := &Preprocessor{CPPDepScanner: s, BasePreprocessor: &inputprocessor.BasePreprocessor{Ctx: ctx, FileMetadataCache: fmc}} 325 326 pwd, err := os.Getwd() 327 if err != nil { 328 t.Fatalf("Unable to get current working directory: %v", err) 329 } 330 c.Flags = &flags.CommandFlags{ 331 ExecutablePath: "../bin/clang++", 332 TargetFilePaths: []string{"../src/test.cpp"}, 333 OutputFilePaths: []string{"test.o"}, 334 ExecRoot: pwd, 335 WorkingDirectory: "out", 336 Flags: []*flags.Flag{ 337 {Key: "-c"}, 338 {Key: "--sysroot", Value: "a/b", Joined: false}, 339 {Key: "-isysroot", Value: "../c/d", Joined: true}, 340 {Key: "--sysroot=", Value: fmt.Sprintf("%s/e/f", pwd), Joined: true}, 341 {Key: "-fprofile-sample-use=", Value: "../c/d/abc.prof", Joined: true}, 342 {Key: "-fsanitize-blacklist=", Value: fmt.Sprintf("%s/e/f/ignores.txt", pwd), Joined: true}, 343 {Key: "-fsanitize-ignorelist=", Value: fmt.Sprintf("%s/e/f/ignores2.txt", pwd), Joined: true}, 344 {Key: "-fprofile-list=", Value: fmt.Sprintf("%s/e/f/fun.list", pwd), Joined: true}, 345 }, 346 } 347 if err := c.ComputeSpec(); err != nil { 348 t.Fatalf("ComputeSpec() failed: %v", err) 349 } 350 351 wantCmd := []string{ 352 filepath.Join(pwd, "bin/clang++"), 353 "-c", 354 "--sysroot", filepath.Join(pwd, "out/a/b"), 355 "-isysroot" + filepath.Join(pwd, "c/d"), 356 "--sysroot=" + filepath.Join(pwd, "e/f"), 357 "-fprofile-sample-use=" + filepath.Join(pwd, "c/d/abc.prof"), 358 "-fsanitize-blacklist=" + filepath.Join(pwd, "e/f/ignores.txt"), 359 "-fsanitize-ignorelist=" + filepath.Join(pwd, "e/f/ignores2.txt"), 360 "-fprofile-list=" + filepath.Join(pwd, "e/f/fun.list"), 361 "-Qunused-arguments", 362 "-o", 363 "test.o", 364 filepath.Join(pwd, "src/test.cpp"), 365 } 366 if diff := cmp.Diff(wantCmd, s.gotCmd); diff != "" { 367 t.Errorf("ComputeSpec() called clang-scan-deps incorrectly, diff (-want +got): %v", diff) 368 } 369 } 370 371 func TestComputeSpecAbsolutePaths(t *testing.T) { 372 ctx := context.Background() 373 fmc := filemetadata.NewSingleFlightCache() 374 s := &stubCPPDepScanner{ 375 res: []string{"include/foo.h"}, 376 err: nil, 377 } 378 c := &Preprocessor{CPPDepScanner: s, BasePreprocessor: &inputprocessor.BasePreprocessor{Ctx: ctx, FileMetadataCache: fmc}} 379 380 pwd, err := os.Getwd() 381 if err != nil { 382 t.Fatalf("Unable to get current working directory: %v", err) 383 } 384 wd := "out" 385 c.Flags = &flags.CommandFlags{ 386 ExecutablePath: filepath.Join(pwd, "bin/clang++"), 387 TargetFilePaths: []string{filepath.Join(pwd, "src/test.cpp")}, 388 OutputFilePaths: []string{filepath.Join(pwd, wd, "test.o")}, 389 ExecRoot: pwd, 390 WorkingDirectory: wd, 391 Flags: []*flags.Flag{ 392 {Key: "-c"}, 393 {Key: "--sysroot", Value: filepath.Join(pwd, wd, "a/b"), Joined: false}, 394 {Key: "-isysroot", Value: filepath.Join(pwd, "c/d"), Joined: true}, 395 {Key: "--sysroot=", Value: filepath.Join(pwd, "e/f"), Joined: true}, 396 {Key: "-fprofile-sample-use=", Value: filepath.Join(pwd, "c/d/abc.prof"), Joined: true}, 397 {Key: "-fsanitize-blacklist=", Value: filepath.Join(pwd, "e/f/ignores.txt"), Joined: true}, 398 {Key: "-fsanitize-ignorelist=", Value: filepath.Join(pwd, "e/f/ignores2.txt"), Joined: true}, 399 {Key: "-fprofile-list=", Value: filepath.Join(pwd, "e/f/fun.list"), Joined: true}, 400 }, 401 } 402 if err := c.ComputeSpec(); err != nil { 403 t.Fatalf("ComputeSpec() failed: %v", err) 404 } 405 406 wantCmd := []string{ 407 filepath.Join(pwd, "bin/clang++"), 408 "-c", 409 "--sysroot", filepath.Join(pwd, "out/a/b"), 410 "-isysroot" + filepath.Join(pwd, "c/d"), 411 "--sysroot=" + filepath.Join(pwd, "e/f"), 412 "-fprofile-sample-use=" + filepath.Join(pwd, "c/d/abc.prof"), 413 "-fsanitize-blacklist=" + filepath.Join(pwd, "e/f/ignores.txt"), 414 "-fsanitize-ignorelist=" + filepath.Join(pwd, "e/f/ignores2.txt"), 415 "-fprofile-list=" + filepath.Join(pwd, "e/f/fun.list"), 416 "-Qunused-arguments", 417 "-o", 418 filepath.Join(pwd, wd, "test.o"), 419 filepath.Join(pwd, "src/test.cpp"), 420 } 421 if diff := cmp.Diff(wantCmd, s.gotCmd); diff != "" { 422 t.Errorf("ComputeSpec() called clang-scan-deps incorrectly, diff (-want +got): %v", diff) 423 } 424 if s.gotFileName != filepath.Join(pwd, "src/test.cpp") { 425 t.Errorf("ComputeSpec() called the input processor incorrectly, want filename=%q, got %q", filepath.Join(pwd, "src/test.cpp"), s.gotFileName) 426 } 427 } 428 429 func TestComputeSpec_RemovesUnsupportedFlags(t *testing.T) { 430 ctx := context.Background() 431 fmc := filemetadata.NewSingleFlightCache() 432 s := &stubCPPDepScanner{ 433 res: []string{"include/foo.h"}, 434 err: nil, 435 } 436 c := &Preprocessor{CPPDepScanner: s, BasePreprocessor: &inputprocessor.BasePreprocessor{Ctx: ctx, FileMetadataCache: fmc}} 437 438 // Tests that virtual inputs only include existing directories and excludes files. 439 existingFiles := []string{ 440 filepath.Clean("bin/clang++"), 441 filepath.Clean("src/test.cpp"), 442 filepath.Clean("include/foo/a"), 443 filepath.Clean("include/a/b.hmap"), 444 filepath.Clean("out/dummy"), 445 } 446 er, cleanup := execroot.Setup(t, existingFiles) 447 defer cleanup() 448 inputs := []string{filepath.Clean("include/a/b.hmap")} 449 opts := inputprocessor.Options{ 450 Cmd: []string{"../bin/clang++", "-o", "test.o", "-MF", "test.d", "-I../include/foo", "-I../include/bar", "-I../include/a/b.hmap", 451 "-fno-experimental-new-pass-manager", "-fexperimental-new-pass-manager", "-std=c++14", "-Xclang", "-verify", "-c", "../src/test.cpp"}, 452 WorkingDir: "out", 453 ExecRoot: er, 454 Labels: map[string]string{"type": "compile", "compiler": "clang", "lang": "cpp"}, 455 Inputs: &command.InputSpec{ 456 Inputs: inputs, 457 }, 458 } 459 if _, err := inputprocessor.Compute(c, opts); err != nil { 460 t.Errorf("Spec() failed: %v", err) 461 } 462 wantCmd := []string{ 463 filepath.Join(er, "bin/clang++"), 464 "-I../include/foo", 465 "-I../include/bar", 466 "-I../include/a/b.hmap", 467 "-std=c++14", "-c", // expect -std=xx to not be normalized 468 "-Qunused-arguments", // expect Qunused-arguments to be added, and -Xclang -verify to be removed 469 // -fno-experimental-new-pass-manager and -fexperimental-new-pass-manager are removed since 470 // they're not supported in newer clang versions. 471 "-o", "test.o", 472 filepath.Join(er, "src/test.cpp"), 473 } 474 if diff := cmp.Diff(wantCmd, s.gotCmd); diff != "" { 475 t.Errorf("Unexpected command passed to the dependency scanner (-want +got): %v", diff) 476 } 477 } 478 479 func TestBuildCommandLine(t *testing.T) { 480 f := &flags.CommandFlags{ 481 ExecutablePath: "clang++", 482 Flags: []*flags.Flag{{Key: "-c"}, {Key: "-Xclang", Value: "-verify"}, {Key: "-Xclang", Value: "-fallow-half-arguments-and-returns"}, {Key: "--sysroot=", Value: "a/b", Joined: true}}, 483 TargetFilePaths: []string{"test.cpp"}, 484 OutputFilePaths: []string{"test.o", "test.d"}, 485 EmittedDependencyFile: "test.d", 486 WorkingDirectory: "foo", 487 ExecRoot: "/exec/root", 488 } 489 p := &Preprocessor{ 490 BasePreprocessor: &inputprocessor.BasePreprocessor{Flags: f}, 491 CPPDepScanner: &stubCPPDepScanner{}, 492 } 493 got := p.BuildCommandLine("-o", false, map[string]bool{"--sysroot=": true}) 494 want := []string{filepath.Clean("/exec/root/foo/clang++"), "-c", "--sysroot=" + filepath.Clean("/exec/root/foo/a/b"), "-Qunused-arguments", "-o", "test.o", filepath.Clean("/exec/root/foo/test.cpp")} 495 if diff := cmp.Diff(want, got); diff != "" { 496 t.Errorf("BuildCommandLine returned diff, (-want +got): %s", diff) 497 } 498 } 499 500 // TestVirtualInputFlags checks that all the expected flags that can possibly add virtual 501 // inputs actually do. 502 func TestVirtualInputFlags(t *testing.T) { 503 pwd, err := os.Getwd() 504 if err != nil { 505 t.Fatalf("Unable to get current working directory: %v", err) 506 } 507 f := &flags.CommandFlags{ 508 ExecRoot: pwd, 509 WorkingDirectory: "out", 510 Flags: []*flags.Flag{ 511 {Value: "-c"}, 512 // These flags should result in virtual inputs. 513 {Key: "--sysroot", Value: "a/b"}, 514 {Key: "-isysroot", Value: "../c/d", Joined: true}, 515 {Key: "--sysroot=", Value: fmt.Sprintf("%s/e/f", pwd), Joined: true}, 516 {Key: "-I", Value: "g/h", Joined: true}, 517 {Key: "-I", Value: "i/j", Joined: true}, 518 {Key: "-isysroot", Value: "../foo", Joined: true}, 519 {Key: "-isystem", Value: "../goo", Joined: true}, 520 {Key: "-internal-isystem", Value: "../doo", Joined: true}, 521 {Key: "-internal-externc-isystem", Value: "../loo", Joined: true}, 522 // These flags should not result in virtual inputs. 523 {Key: "-sysroot", Value: "../bar"}, 524 {Key: "-fprofile-sample-use=", Value: "../c/d/abc.prof", Joined: true}, 525 {Key: "-fsanitize-blacklist=", Value: fmt.Sprintf("%s/e/f/ignores.txt", pwd), Joined: true}, 526 {Key: "-fsanitize-ignorelist=", Value: fmt.Sprintf("%s/e/f/ignores.txt", pwd), Joined: true}, 527 }, 528 } 529 vi := VirtualInputs(f, &Preprocessor{}) 530 531 want := []*command.VirtualInput{ 532 {Path: filepath.Clean("out/a/b"), IsEmptyDirectory: true}, 533 {Path: filepath.Clean("c/d"), IsEmptyDirectory: true}, 534 {Path: filepath.Clean("e/f"), IsEmptyDirectory: true}, 535 {Path: filepath.Clean("out/g/h"), IsEmptyDirectory: true}, 536 {Path: filepath.Clean("out/i/j"), IsEmptyDirectory: true}, 537 {Path: filepath.Clean("foo"), IsEmptyDirectory: true}, 538 {Path: filepath.Clean("goo"), IsEmptyDirectory: true}, 539 {Path: filepath.Clean("doo"), IsEmptyDirectory: true}, 540 {Path: filepath.Clean("loo"), IsEmptyDirectory: true}, 541 } 542 if diff := cmp.Diff(want, vi); diff != "" { 543 t.Errorf("virtualInputs(%+v) returned incorrect result diff (-want +got): %v", f, diff) 544 } 545 } 546 547 // TestExtractVirtualSubdirectories checks that paths are translated into virtual inputs correctly. 548 func TestExtractVirtualSubdirectories(t *testing.T) { 549 tests := []struct { 550 path string 551 want []string 552 }{ 553 { 554 path: filepath.FromSlash("a/b/c"), 555 want: []string{filepath.FromSlash("a/b/c")}, 556 }, 557 { 558 path: filepath.FromSlash("../a/b/c"), 559 want: []string{filepath.FromSlash("../a/b/c")}, 560 }, 561 { 562 path: filepath.FromSlash("a/b/../c"), 563 want: []string{filepath.FromSlash("a/b"), filepath.FromSlash("a/c")}, 564 }, 565 { 566 path: filepath.FromSlash("a/b/../c/../d"), 567 want: []string{filepath.FromSlash("a/b"), filepath.FromSlash("a/c"), filepath.FromSlash("a/d")}, 568 }, { 569 path: filepath.FromSlash("a/b/../c/d/../../../e/f"), 570 want: []string{filepath.FromSlash("a/b"), filepath.FromSlash("a/c/d"), filepath.FromSlash("e/f")}, 571 }, { 572 path: filepath.FromSlash("a/b/c/../.."), 573 want: []string{filepath.FromSlash("a/b/c")}, 574 }, { 575 path: filepath.FromSlash("../../.."), 576 want: []string{filepath.FromSlash("../../..")}, 577 }, 578 } 579 580 for _, test := range tests { 581 t.Run(test.path, func(t *testing.T) { 582 vi := extractVirtualSubdirectories(test.path) 583 584 if diff := cmp.Diff(test.want, vi); diff != "" { 585 t.Errorf("extractVirtualSubdirectories(%+v) returned incorrect result diff (-want +got): %v", test.path, diff) 586 } 587 }) 588 } 589 } 590 591 // TestComputeSpecEventTimes tests that the Event Times "InputProcessorCacheLookup", 592 // "CPPInputProcessor", and "InputProcessorWait" are properly setup when ComputeSpec() is called. 593 // This was based on TestComputeSpecWithDepsCache and modified to check for Event Times. 594 func TestComputeSpecEventTimes(t *testing.T) { 595 ctx := context.Background() 596 fmc := filemetadata.NewSingleFlightCache() 597 s := &stubCPPDepScanner{ 598 res: []string{"include/foo.h"}, 599 err: nil, 600 cacheInput: false, 601 } 602 c := &Preprocessor{ 603 CPPDepScanner: s, 604 BasePreprocessor: &inputprocessor.BasePreprocessor{Ctx: ctx, FileMetadataCache: fmc}, 605 DepsCache: depscache.New(filemetadata.NewSingleFlightCache()), 606 } 607 608 existingFiles := []string{ 609 filepath.Clean("bin/clang++"), 610 filepath.Clean("src/test.cpp"), 611 filepath.Clean("include/foo/a"), 612 filepath.Clean("out/dummy"), 613 } 614 er, cleanup := execroot.Setup(t, existingFiles) 615 defer cleanup() 616 c.DepsCache.LoadFromDir(er) 617 opts := inputprocessor.Options{ 618 Cmd: []string{"../bin/clang++", "-o", "test.o", "-MF", "test.d", "-I../include/foo", "-I", "../include/bar", "-c", "../src/test.cpp"}, 619 WorkingDir: "out", 620 ExecRoot: er, 621 Labels: map[string]string{"type": "compile", "compiler": "clang", "lang": "cpp"}, 622 } 623 624 // No deps cache hit 625 var wg sync.WaitGroup 626 wg.Add(1) 627 c.testOnlySetDone = func() { wg.Done() } 628 inputprocessor.Compute(c, opts) 629 wg.Wait() 630 if _, okIPCL := c.Rec.GetLocalMetadata().GetEventTimes()["InputProcessorCacheLookup"]; okIPCL { 631 t.Errorf("Event Time %s was set but DepsCache was not used", "InputProcessorCacheLookup") 632 } 633 if _, okIPW := c.Rec.GetLocalMetadata().GetEventTimes()["InputProcessorWait"]; !okIPW { 634 t.Errorf("Event Time %s was not set correctly", "InputProcessorWait") 635 } 636 if _, okCIP := c.Rec.GetLocalMetadata().GetEventTimes()["CPPInputProcessor"]; !okCIP { 637 t.Errorf("Event Time %s was not set but DepsCache was not used", "CPPInputProcessor") 638 } 639 // Cache used but no early deps cache hit 640 s.cacheInput = true 641 c = &Preprocessor{ 642 CPPDepScanner: s, 643 BasePreprocessor: &inputprocessor.BasePreprocessor{Ctx: ctx, FileMetadataCache: fmc}, 644 DepsCache: depscache.New(filemetadata.NewSingleFlightCache()), 645 } 646 c.DepsCache.LoadFromDir(er) 647 wg.Add(1) 648 c.testOnlySetDone = func() { wg.Done() } 649 inputprocessor.Compute(c, opts) 650 wg.Wait() 651 if _, okIPCL := c.Rec.GetLocalMetadata().GetEventTimes()["InputProcessorCacheLookup"]; !okIPCL { 652 t.Errorf("Event Time %s was not set but DepsCache was used", "InputProcessorCacheLookup") 653 } 654 if _, okIPW := c.Rec.GetLocalMetadata().GetEventTimes()["InputProcessorWait"]; !okIPW { 655 t.Errorf("Event Time %s was not set correctly", "InputProcessorWait") 656 } 657 if _, okCIP := c.Rec.GetLocalMetadata().GetEventTimes()["CPPInputProcessor"]; okCIP { 658 t.Errorf("Event Time %s was set but DepsCache was used", "CPPInputProcessor") 659 } 660 // Early deps cache hit 661 s.cacheInput = false 662 c = &Preprocessor{ 663 CPPDepScanner: s, 664 BasePreprocessor: &inputprocessor.BasePreprocessor{Ctx: ctx, FileMetadataCache: fmc}, 665 DepsCache: c.DepsCache, 666 } 667 inputprocessor.Compute(c, opts) 668 if _, okIPCL := c.Rec.GetLocalMetadata().GetEventTimes()["InputProcessorCacheLookup"]; !okIPCL { 669 t.Errorf("Event Time for %s was not set but DepsCache was used", "InputProcessorCacheLookup") 670 } 671 if _, okIPW := c.Rec.GetLocalMetadata().GetEventTimes()["InputProcessorWait"]; okIPW { 672 t.Errorf("Event Time %s was set but an early Deps Cache hit was found", "InputProcessorWait") 673 } 674 if _, okCIP := c.Rec.GetLocalMetadata().GetEventTimes()["CPPInputProcessor"]; okCIP { 675 t.Errorf("Event Time %s was set but an early Deps Cache hit was found", "CPPInputProcessor") 676 } 677 } 678 679 type stubCPPDepScanner struct { 680 gotCmd []string 681 gotFileName string 682 gotDirectory string 683 684 res []string 685 err error 686 687 processCalls int 688 minimizeCalls int 689 690 capabilities *spb.CapabilitiesResponse 691 692 cacheInput bool 693 } 694 695 func (s *stubCPPDepScanner) ProcessInputs(_ context.Context, _ string, command []string, filename, directory string, _ []string) ([]string, bool, error) { 696 s.gotCmd = command 697 s.gotFileName = filename 698 s.gotDirectory = directory 699 s.processCalls++ 700 701 return s.res, s.cacheInput, s.err 702 } 703 704 func (s *stubCPPDepScanner) Capabilities() *spb.CapabilitiesResponse { 705 return s.capabilities 706 } 707 708 type stubExecutor struct { 709 gotCmd *command.Command 710 711 outStr string 712 errStr string 713 err error 714 } 715 716 func (s *stubExecutor) Execute(ctx context.Context, cmd *command.Command) (string, string, error) { 717 s.gotCmd = cmd 718 return s.outStr, s.errStr, s.err 719 }