github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/pkg/inputprocessor/inputprocessor_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 inputprocessor 16 17 import ( 18 "bufio" 19 "context" 20 "errors" 21 "fmt" 22 "os" 23 "path" 24 "path/filepath" 25 "testing" 26 "time" 27 28 lpb "github.com/bazelbuild/reclient/api/log" 29 ppb "github.com/bazelbuild/reclient/api/proxy" 30 spb "github.com/bazelbuild/reclient/api/scandeps" 31 "github.com/bazelbuild/reclient/internal/pkg/execroot" 32 "github.com/bazelbuild/reclient/internal/pkg/labels" 33 "github.com/bazelbuild/reclient/internal/pkg/localresources" 34 "github.com/bazelbuild/reclient/internal/pkg/logger" 35 36 "github.com/bazelbuild/remote-apis-sdks/go/pkg/command" 37 "github.com/bazelbuild/remote-apis-sdks/go/pkg/filemetadata" 38 "github.com/bazelbuild/remote-apis-sdks/go/pkg/outerr" 39 "github.com/bazelbuild/rules_go/go/tools/bazel" 40 "github.com/google/go-cmp/cmp" 41 "github.com/google/go-cmp/cmp/cmpopts" 42 "google.golang.org/grpc/codes" 43 "google.golang.org/grpc/status" 44 ) 45 46 const ( 47 fakeExecutionID = "fake-execution-1" 48 wd = "wd" 49 50 androidCommandFilePath = "testdata/sample_android_command.txt" 51 ) 52 53 var ( 54 strSliceCmp = cmpopts.SortSlices(func(a, b string) bool { return a < b }) 55 dsTimeout = 10 * time.Second 56 ) 57 58 func TestCpp(t *testing.T) { 59 ctx := context.Background() 60 ds := &stubCPPDependencyScanner{ 61 processInputsReturnValue: []string{ 62 "wd/ISoundTriggerClient.cpp", 63 "wd/frameworks/av/soundtrigger/ISoundTriggerClient.cpp", 64 }, 65 } 66 resMgr := localresources.NewDefaultManager() 67 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 68 existingFiles := []string{ 69 filepath.Clean("wd/libc++.so.1"), 70 filepath.Clean("wd/ISoundTriggerClient.cpp"), 71 filepath.Clean("wd/frameworks/av/soundtrigger/ISoundTriggerClient.cpp"), 72 } 73 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 74 defer cleanup() 75 76 cmd := []string{"clang++", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 77 lbls := map[string]string{ 78 "type": "compile", 79 "compiler": "clang", 80 "lang": "cpp", 81 "shallow": "false", 82 } 83 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/libc++.so.1")}} 84 opts := &ProcessInputsOptions{ 85 ExecutionID: fakeExecutionID, 86 Cmd: cmd, 87 WorkingDir: wd, 88 ExecRoot: er, 89 Inputs: i, 90 Labels: lbls, 91 } 92 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 93 if err != nil { 94 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 95 } 96 wantIO := &CommandIO{ 97 InputSpec: &command.InputSpec{Inputs: existingFiles}, 98 OutputFiles: []string{filepath.Clean("wd/test.d"), filepath.Clean("wd/test.o")}, 99 EmittedDependencyFile: filepath.Clean("wd/test.d"), 100 } 101 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 102 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 103 } 104 } 105 106 // TestCppEventTimes tests that the EventTime "ProcessInputs" is properly set when creating an input processor. 107 // This was based on TestCpp and modified to test for EventTimes. 108 func TestCppEventTimes(t *testing.T) { 109 ctx := context.Background() 110 ds := &stubCPPDependencyScanner{ 111 processInputsReturnValue: []string{ 112 "wd/ISoundTriggerClient.cpp", 113 "wd/frameworks/av/soundtrigger/ISoundTriggerClient.cpp", 114 }, 115 } 116 resMgr := localresources.NewDefaultManager() 117 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 118 existingFiles := []string{ 119 filepath.Clean("wd/libc++.so.1"), 120 filepath.Clean("wd/ISoundTriggerClient.cpp"), 121 filepath.Clean("wd/frameworks/av/soundtrigger/ISoundTriggerClient.cpp"), 122 } 123 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 124 defer cleanup() 125 126 cmd := []string{"clang++", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 127 lbls := map[string]string{ 128 "type": "compile", 129 "compiler": "clang", 130 "lang": "cpp", 131 "shallow": "false", 132 } 133 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/libc++.so.1")}} 134 opts := &ProcessInputsOptions{ 135 ExecutionID: fakeExecutionID, 136 Cmd: cmd, 137 WorkingDir: wd, 138 ExecRoot: er, 139 Inputs: i, 140 Labels: lbls, 141 } 142 rec := &logger.LogRecord{LogRecord: &lpb.LogRecord{}} 143 ip.ProcessInputs(ctx, opts, rec) 144 if _, ok := rec.GetLocalMetadata().GetEventTimes()["ProcessInputs"]; !ok { 145 t.Errorf("Event Time for %s was not set correctly", "ProcessInputs") 146 } 147 } 148 149 // TestCppWithDepsCache tests creating an input processor with a deps cache. It does not test the 150 // correctness of the deps cache as this is already covered by unit tests of the depscache package. 151 func TestCppWithDepsCache(t *testing.T) { 152 ctx := context.Background() 153 ds := &stubCPPDependencyScanner{ 154 processInputsReturnValue: []string{ 155 "wd/ISoundTriggerClient.cpp", 156 "wd/frameworks/av/soundtrigger/ISoundTriggerClient.cpp", 157 }, 158 } 159 existingFiles := []string{ 160 filepath.Clean("wd/libc++.so.1"), 161 filepath.Clean("wd/ISoundTriggerClient.cpp"), 162 filepath.Clean("wd/frameworks/av/soundtrigger/ISoundTriggerClient.cpp"), 163 } 164 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 165 defer cleanup() 166 resMgr := localresources.NewDefaultManager() 167 fmc := filemetadata.NewSingleFlightCache() 168 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, fmc, nil) 169 ip.depsCache, cleanup = newDepsCache(fmc, er, nil) 170 171 cmd := []string{"clang++", "-c", "-o", "test.o", 172 "-Xclang", "-add-plugin", "-Xclang", "bar", 173 "-MF", "test.d", "test.cpp"} 174 lbls := map[string]string{ 175 "type": "compile", 176 "compiler": "clang", 177 "lang": "cpp", 178 "shallow": "false", 179 } 180 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/libc++.so.1")}} 181 opts := &ProcessInputsOptions{ 182 ExecutionID: fakeExecutionID, 183 Cmd: cmd, 184 WorkingDir: wd, 185 ExecRoot: er, 186 Inputs: i, 187 Labels: lbls, 188 } 189 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 190 if err != nil { 191 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 192 } 193 wantIO := &CommandIO{ 194 InputSpec: &command.InputSpec{Inputs: existingFiles}, 195 OutputFiles: []string{filepath.Clean("wd/test.d"), filepath.Clean("wd/test.o")}, 196 EmittedDependencyFile: filepath.Clean("wd/test.d"), 197 } 198 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 199 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 200 } 201 cleanup() 202 dcPath := filepath.Join(er, "reproxy.cache") 203 if _, err := os.Stat(dcPath); os.IsNotExist(err) { 204 t.Errorf("Deps cache file (%v) does not exist", dcPath) 205 } 206 wantDepScanArgs := []string{"-c", "-Xclang", "-add-plugin", "-Xclang", "bar", "-Qunused-arguments", "-o", "test.o", filepath.Join(er, wd, "test.cpp")} 207 if diff := cmp.Diff(wantDepScanArgs, ds.processInputsArgs[1:]); diff != "" { 208 t.Errorf("CPPDepScanner called with unexpected arguments (-want +got): %s", diff) 209 } 210 } 211 212 func TestCppShallowFallback(t *testing.T) { 213 ctx := context.Background() 214 ds := &stubCPPDependencyScanner{ 215 processInputsError: errors.New("failed to call clang-scan-deps"), 216 } 217 resMgr := localresources.NewDefaultManager() 218 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 219 existingFiles := []string{ 220 filepath.Clean("wd/libc++.so.1"), 221 filepath.Clean("wd/test.cpp"), 222 } 223 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 224 defer cleanup() 225 226 cmd := []string{"clang++", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 227 lbls := map[string]string{ 228 "type": "compile", 229 "compiler": "clang", 230 "lang": "cpp", 231 "shallow": "false", 232 } 233 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/libc++.so.1")}} 234 opts := &ProcessInputsOptions{ 235 ExecutionID: fakeExecutionID, 236 Cmd: cmd, 237 WorkingDir: wd, 238 ExecRoot: er, 239 Inputs: i, 240 Labels: lbls, 241 } 242 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 243 if err != nil { 244 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 245 } 246 wantIO := &CommandIO{ 247 InputSpec: &command.InputSpec{Inputs: existingFiles}, 248 OutputFiles: []string{filepath.Clean("wd/test.d"), filepath.Clean("wd/test.o")}, 249 EmittedDependencyFile: filepath.Clean("wd/test.d"), 250 UsedShallowMode: true, 251 } 252 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 253 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 254 } 255 } 256 257 func TestNoCppShallowFallbackWithRemote(t *testing.T) { 258 ctx := context.Background() 259 ds := &stubCPPDependencyScanner{ 260 processInputsError: errors.New("failed to call clang-scan-deps"), 261 } 262 resMgr := localresources.NewDefaultManager() 263 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 264 existingFiles := []string{ 265 filepath.Clean("wd/libc++.so.1"), 266 filepath.Clean("wd/test.cpp"), 267 } 268 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 269 defer cleanup() 270 271 cmd := []string{"clang++", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 272 lbls := map[string]string{ 273 "type": "compile", 274 "compiler": "clang", 275 "lang": "cpp", 276 } 277 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/libc++.so.1")}} 278 opts := &ProcessInputsOptions{ 279 ExecutionID: fakeExecutionID, 280 Cmd: cmd, 281 WorkingDir: wd, 282 ExecRoot: er, 283 Inputs: i, 284 Labels: lbls, 285 ExecStrategy: ppb.ExecutionStrategy_REMOTE, 286 } 287 if _, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}); err == nil { 288 t.Errorf("ProcessInputs(%+v) did shallow fall back when remote CPP compile specified", opts) 289 } 290 } 291 292 func TestNoCppShallowFallbackWithRemoteLocalFallback(t *testing.T) { 293 ctx := context.Background() 294 ds := &stubCPPDependencyScanner{ 295 processInputsError: errors.New("failed to call clang-scan-deps"), 296 } 297 resMgr := localresources.NewDefaultManager() 298 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 299 existingFiles := []string{ 300 filepath.Clean("wd/libc++.so.1"), 301 filepath.Clean("wd/test.cpp"), 302 } 303 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 304 defer cleanup() 305 306 cmd := []string{"clang++", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 307 lbls := map[string]string{ 308 "type": "compile", 309 "compiler": "clang", 310 "lang": "cpp", 311 } 312 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/libc++.so.1")}} 313 opts := &ProcessInputsOptions{ 314 ExecutionID: fakeExecutionID, 315 Cmd: cmd, 316 WorkingDir: wd, 317 ExecRoot: er, 318 Inputs: i, 319 Labels: lbls, 320 ExecStrategy: ppb.ExecutionStrategy_REMOTE_LOCAL_FALLBACK, 321 } 322 if _, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}); err == nil { 323 t.Errorf("ProcessInputs(%+v) did shallow fall back when remote CPP compile specified", opts) 324 } 325 } 326 327 func TestCppShallowFallbackWithLocal(t *testing.T) { 328 ctx := context.Background() 329 ds := &stubCPPDependencyScanner{ 330 processInputsError: errors.New("failed to call clang-scan-deps"), 331 } 332 resMgr := localresources.NewDefaultManager() 333 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 334 existingFiles := []string{ 335 filepath.Clean("wd/libc++.so.1"), 336 filepath.Clean("wd/test.cpp"), 337 } 338 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 339 defer cleanup() 340 341 cmd := []string{"clang++", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 342 lbls := map[string]string{ 343 "type": "compile", 344 "compiler": "clang", 345 "lang": "cpp", 346 } 347 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/libc++.so.1")}} 348 opts := &ProcessInputsOptions{ 349 ExecutionID: fakeExecutionID, 350 Cmd: cmd, 351 WorkingDir: wd, 352 ExecRoot: er, 353 Inputs: i, 354 Labels: lbls, 355 ExecStrategy: ppb.ExecutionStrategy_LOCAL, 356 } 357 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 358 if err != nil { 359 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 360 } 361 wantIO := &CommandIO{ 362 InputSpec: &command.InputSpec{Inputs: existingFiles}, 363 OutputFiles: []string{filepath.Clean("wd/test.d"), filepath.Clean("wd/test.o")}, 364 EmittedDependencyFile: filepath.Clean("wd/test.d"), 365 UsedShallowMode: true, 366 } 367 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 368 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 369 } 370 } 371 372 func TestHeaderABIDumper(t *testing.T) { 373 ctx := context.Background() 374 ds := &stubCPPDependencyScanner{ 375 processInputsReturnValue: []string{ 376 "wd/ISoundTriggerClient.cpp", 377 "wd/frameworks/av/soundtrigger/ISoundTriggerClient.cpp", 378 }, 379 } 380 resMgr := localresources.NewDefaultManager() 381 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 382 existingFiles := []string{ 383 filepath.Clean("wd/abi-header-dumper"), 384 filepath.Clean("wd/libc++.so.1"), 385 filepath.Clean("wd/ISoundTriggerClient.cpp"), 386 filepath.Clean("wd/frameworks/av/soundtrigger/ISoundTriggerClient.cpp"), 387 } 388 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 389 defer cleanup() 390 391 cmd := []string{"abi-header-dumper", "-o", "test.sdump", "test.cpp", "--", "-Werror"} 392 lbls := map[string]string{ 393 "type": "abi-dump", 394 "tool": "header-abi-dumper", 395 } 396 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/libc++.so.1")}} 397 opts := &ProcessInputsOptions{ 398 ExecutionID: fakeExecutionID, 399 Cmd: cmd, 400 WorkingDir: wd, 401 ExecRoot: er, 402 Inputs: i, 403 Labels: lbls, 404 } 405 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 406 if err != nil { 407 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 408 } 409 wantIO := &CommandIO{ 410 InputSpec: &command.InputSpec{Inputs: existingFiles}, 411 OutputFiles: []string{filepath.Clean("wd/test.sdump")}, 412 } 413 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 414 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 415 } 416 } 417 418 func TestJavac(t *testing.T) { 419 ctx := context.Background() 420 resMgr := localresources.NewDefaultManager() 421 ip := newInputProcessor(nil, dsTimeout, false, nil, resMgr, nil, nil) 422 existingFiles := []string{ 423 filepath.Clean("wd/foo.java"), 424 filepath.Clean("wd/bar/bar.java"), 425 filepath.Clean("wd/bar/baz.java"), 426 } 427 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 428 execroot.AddFileWithContent(t, filepath.Join(er, wd, "foo.rsp"), []byte("foo.java")) 429 defer cleanup() 430 431 cmd := []string{"javac", "-classpath", "bar", "-s", "out", "@foo.rsp"} 432 lbls := map[string]string{ 433 "type": "compile", 434 "compiler": "javac", 435 "lang": "java", 436 "shallow": "false", 437 } 438 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/bar/baz.java")}} 439 opts := &ProcessInputsOptions{ 440 ExecutionID: fakeExecutionID, 441 Cmd: cmd, 442 WorkingDir: wd, 443 ExecRoot: er, 444 Inputs: i, 445 Labels: lbls, 446 } 447 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 448 if err != nil { 449 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 450 } 451 wantIO := &CommandIO{ 452 InputSpec: &command.InputSpec{Inputs: []string{filepath.Clean("wd/foo.rsp"), filepath.Clean("wd/bar"), filepath.Clean("wd/bar/baz.java"), filepath.Clean("wd/foo.java")}}, 453 OutputDirectories: []string{filepath.Clean("wd/out")}, 454 } 455 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 456 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 457 } 458 } 459 460 func TestMetalava(t *testing.T) { 461 ctx := context.Background() 462 resMgr := localresources.NewDefaultManager() 463 ip := newInputProcessor(nil, dsTimeout, false, &execStub{stdout: "Metalava: 1.3.0"}, resMgr, nil, nil) 464 existingFiles := []string{ 465 filepath.Clean("wd/foo.java"), 466 filepath.Clean("wd/bar/bar.java"), 467 filepath.Clean("metalava.jar"), 468 } 469 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 470 execroot.AddFileWithContent(t, filepath.Join(er, wd, "foo.rsp"), []byte("foo.java")) 471 execroot.AddDirs(t, er, []string{"wd/src"}) 472 defer cleanup() 473 474 cmd := []string{"metalava", "-classpath", "bar", "--api", "api.txt", "@foo.rsp", "-sourcepath", "src"} 475 lbls := map[string]string{ 476 "type": "compile", 477 "compiler": "metalava", 478 "lang": "java", 479 "shallow": "false", 480 } 481 i := &command.InputSpec{ 482 Inputs: []string{filepath.Clean("metalava.jar")}, 483 EnvironmentVariables: map[string]string{"FOO": filepath.Join(er, "foo")}, 484 } 485 opts := &ProcessInputsOptions{ 486 ExecutionID: fakeExecutionID, 487 Cmd: cmd, 488 WorkingDir: wd, 489 ExecRoot: er, 490 Inputs: i, 491 Labels: lbls, 492 } 493 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 494 if err != nil { 495 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 496 } 497 wantIO := &CommandIO{ 498 InputSpec: &command.InputSpec{ 499 Inputs: []string{filepath.Clean("wd/foo.rsp"), filepath.Clean("wd/bar"), filepath.Clean("wd/foo.java"), filepath.Clean("metalava.jar")}, 500 VirtualInputs: []*command.VirtualInput{ 501 &command.VirtualInput{Path: filepath.Clean("wd/src"), IsEmptyDirectory: true}, 502 }, 503 EnvironmentVariables: map[string]string{ 504 "FOO": filepath.Join("..", "foo"), 505 }, 506 }, 507 OutputFiles: []string{filepath.Clean("wd/api.txt")}, 508 } 509 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 510 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 511 } 512 } 513 514 func TestMetalavaInputsUnderSourcePath(t *testing.T) { 515 ctx := context.Background() 516 resMgr := localresources.NewDefaultManager() 517 ip := newInputProcessor(nil, dsTimeout, false, &execStub{stdout: "Metalava: 1.3.0"}, resMgr, nil, nil) 518 existingFiles := []string{ 519 filepath.Clean("wd/foo.java"), 520 filepath.Clean("wd/src/bar/bar.java"), 521 filepath.Clean("metalava.jar"), 522 } 523 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 524 execroot.AddFileWithContent(t, filepath.Join(er, wd, "foo.rsp"), []byte("foo.java")) 525 defer cleanup() 526 527 cmd := []string{"metalava", "-classpath", "src/bar", "--api", "api.txt", "@foo.rsp", "-sourcepath", "src"} 528 lbls := map[string]string{ 529 "type": "compile", 530 "compiler": "metalava", 531 "lang": "java", 532 "shallow": "false", 533 } 534 535 i := &command.InputSpec{Inputs: []string{filepath.Clean("metalava.jar")}} 536 opts := &ProcessInputsOptions{ 537 ExecutionID: fakeExecutionID, 538 Cmd: cmd, 539 WorkingDir: wd, 540 ExecRoot: er, 541 Inputs: i, 542 Labels: lbls, 543 } 544 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 545 if err != nil { 546 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 547 } 548 wantIO := &CommandIO{ 549 InputSpec: &command.InputSpec{ 550 Inputs: []string{filepath.Clean("wd/foo.rsp"), filepath.Clean("wd/src/bar"), filepath.Clean("wd/foo.java"), filepath.Clean("metalava.jar")}, 551 VirtualInputs: []*command.VirtualInput{{Path: filepath.Clean("wd/src"), IsEmptyDirectory: true}}, 552 }, 553 OutputFiles: []string{filepath.Clean("wd/api.txt")}, 554 } 555 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 556 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 557 } 558 } 559 560 func TestIncludeDirectoriesWithNoInputs_VirtualInputsAdded(t *testing.T) { 561 tests := []struct { 562 cmd []string 563 name string 564 files []string 565 dirs []string 566 want *command.InputSpec 567 }{ 568 { 569 name: "Single non-existent directory", 570 cmd: []string{"clang++", "-c", "-o", "test.o", "-Ia", "-MF", "test.d", "test.cpp"}, 571 dirs: []string{"wd/a"}, 572 want: &command.InputSpec{ 573 Inputs: []string{filepath.Clean("wd/test.cpp")}, 574 VirtualInputs: []*command.VirtualInput{ 575 &command.VirtualInput{Path: filepath.Clean("wd/a"), IsEmptyDirectory: true}, 576 }, 577 }, 578 }, 579 { 580 name: "-I<path> follows -I <path>", 581 cmd: []string{"clang++", "-c", "-o", "test.o", "-Ia", "-I", "b/c", "-MF", "test.d", "test.cpp"}, 582 dirs: []string{"wd/a", "wd/b/c"}, 583 want: &command.InputSpec{ 584 Inputs: []string{filepath.Clean("wd/test.cpp")}, 585 VirtualInputs: []*command.VirtualInput{ 586 &command.VirtualInput{Path: filepath.Clean("wd/a"), IsEmptyDirectory: true}, 587 &command.VirtualInput{Path: filepath.Clean("wd/b/c"), IsEmptyDirectory: true}, 588 }, 589 }, 590 }, 591 { 592 name: "-I <path> follows -I<path>", 593 cmd: []string{"clang++", "-c", "-o", "test.o", "-I", "a/b", "-Ic/d", "-MF", "test.d", "test.cpp"}, 594 dirs: []string{"wd/a/b", "wd/c/d"}, 595 want: &command.InputSpec{ 596 Inputs: []string{filepath.Clean("wd/test.cpp")}, 597 VirtualInputs: []*command.VirtualInput{ 598 &command.VirtualInput{Path: filepath.Clean("wd/a/b"), IsEmptyDirectory: true}, 599 &command.VirtualInput{Path: filepath.Clean("wd/c/d"), IsEmptyDirectory: true}, 600 }, 601 }, 602 }, 603 { 604 name: "-isystem<path> follows -I<path>", 605 cmd: []string{"clang++", "-c", "-o", "test.o", "-isystema/b", "-Ic/d", "-MF", "test.d", "test.cpp"}, 606 dirs: []string{"wd/a/b", "wd/c/d"}, 607 want: &command.InputSpec{ 608 Inputs: []string{filepath.Clean("wd/test.cpp")}, 609 VirtualInputs: []*command.VirtualInput{ 610 &command.VirtualInput{Path: filepath.Clean("wd/a/b"), IsEmptyDirectory: true}, 611 &command.VirtualInput{Path: filepath.Clean("wd/c/d"), IsEmptyDirectory: true}, 612 }, 613 }, 614 }, 615 { 616 name: "-isystem <path> follows -I<path>", 617 cmd: []string{"clang++", "-c", "-o", "test.o", "-isystem", "a/b", "-Ic/d", "-MF", "test.d", "test.cpp"}, 618 dirs: []string{"wd/a/b", "wd/c/d"}, 619 want: &command.InputSpec{ 620 Inputs: []string{filepath.Clean("wd/test.cpp")}, 621 VirtualInputs: []*command.VirtualInput{ 622 &command.VirtualInput{Path: filepath.Clean("wd/a/b"), IsEmptyDirectory: true}, 623 &command.VirtualInput{Path: filepath.Clean("wd/c/d"), IsEmptyDirectory: true}, 624 }, 625 }, 626 }, 627 { 628 name: "-isystem <path> follows -isystem <path>/<subpath>", 629 cmd: []string{"clang++", "-c", "-o", "test.o", "-isystem", "a/b", "-isystem", "a/b/c", "-MF", "test.d", "test.cpp"}, 630 dirs: []string{"wd/a/b/c"}, 631 want: &command.InputSpec{ 632 Inputs: []string{filepath.Clean("wd/test.cpp")}, 633 VirtualInputs: []*command.VirtualInput{ 634 &command.VirtualInput{Path: filepath.Clean("wd/a/b"), IsEmptyDirectory: true}, 635 &command.VirtualInput{Path: filepath.Clean("wd/a/b/c"), IsEmptyDirectory: true}, 636 }, 637 }, 638 }, 639 } 640 641 for _, test := range tests { 642 ctx := context.Background() 643 ds := &stubCPPDependencyScanner{ 644 processInputsReturnValue: []string{"wd/test.cpp"}, 645 } 646 resMgr := localresources.NewDefaultManager() 647 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 648 existingFiles := []string{filepath.Clean("wd/test.cpp")} 649 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 650 execroot.AddFiles(t, er, test.files) 651 execroot.AddDirs(t, er, test.dirs) 652 defer cleanup() 653 654 lbls := map[string]string{ 655 "type": "compile", 656 "compiler": "clang", 657 "lang": "cpp", 658 "shallow": "false", 659 } 660 shallowFallbackConfig[labels.FromMap(lbls)] = map[ppb.ExecutionStrategy_Value]bool{ppb.ExecutionStrategy_UNSPECIFIED: false} 661 defer func() { 662 shallowFallbackConfig[labels.FromMap(lbls)] = map[ppb.ExecutionStrategy_Value]bool{ppb.ExecutionStrategy_UNSPECIFIED: true} 663 }() 664 i := &command.InputSpec{Inputs: []string{filepath.Clean("wd/libc++.so.1")}} 665 opts := &ProcessInputsOptions{ 666 ExecutionID: fakeExecutionID, 667 Cmd: test.cmd, 668 WorkingDir: wd, 669 ExecRoot: er, 670 Inputs: i, 671 Labels: lbls, 672 } 673 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 674 if err != nil { 675 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 676 } 677 678 if diff := cmp.Diff(test.want, gotIO.InputSpec); diff != "" { 679 t.Errorf("ProcessInputs(%+v) returned diff in inputs, (-want +got): %s", opts, diff) 680 } 681 } 682 } 683 684 func TestFromAndroidCompileCommand(t *testing.T) { 685 ctx := context.Background() 686 ds := &stubCPPDependencyScanner{ 687 processInputsReturnValue: []string{ 688 "ISoundTriggerClient.cpp", 689 "frameworks/av/soundtrigger/ISoundTriggerClient.cpp", 690 "external/libcxx/include/stdint.h", 691 }, 692 } 693 resMgr := localresources.NewDefaultManager() 694 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 695 existingFiles := []string{ 696 filepath.Clean("ISoundTriggerClient.cpp"), 697 filepath.Clean("frameworks/av/soundtrigger/ISoundTriggerClient.cpp"), 698 filepath.Clean("external/libcxx/include/stdint.h"), 699 } 700 er, cleanup := execroot.Setup(t, append(existingFiles, "remote_toolchain_inputs")) 701 defer cleanup() 702 703 execroot.AddDirs(t, er, []string{ 704 "system/core/libcutils/include", 705 "system/core/libutils/include", 706 "system/core/libbacktrace/include", 707 "system/core/liblog/include", 708 "system/core/libsystem/include", 709 "out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm_armv7-a-neon_core_static/gen/aidl", 710 "frameworks/native/libs/binder/include", 711 "system/core/base/include", 712 "external/libcxxabi/include", 713 "bionic/libc/system_properties/include", 714 "system/core/property_service/libpropertyinfoparser/include", 715 "system/core/include", 716 "system/media/audio/include", 717 "hardware/libhardware/include", 718 "hardware/libhardware_legacy/include", 719 "hardware/ril/include", 720 "libnativehelper/include", 721 "frameworks/native/include", 722 "frameworks/native/opengl/include", 723 "frameworks/av/include", 724 "bionic/libc/include", 725 "bionic/libc/kernel/uapi/asm-arm", 726 "bionic/libc/kernel/android/scsi", 727 "bionic/libc/kernel/android/uapi", 728 "libnativehelper/include_jni", 729 }) 730 731 cmd := fromFile(t, androidCommandFilePath) 732 lbls := map[string]string{ 733 "type": "compile", 734 "compiler": "clang", 735 "lang": "cpp", 736 "shallow": "false", 737 } 738 opts := &ProcessInputsOptions{ 739 ExecutionID: fakeExecutionID, 740 Cmd: cmd, 741 ExecRoot: er, 742 Labels: lbls, 743 } 744 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 745 if err != nil { 746 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 747 } 748 749 wantInp := &command.InputSpec{ 750 Inputs: existingFiles, 751 VirtualInputs: []*command.VirtualInput{ 752 &command.VirtualInput{Path: filepath.Clean("frameworks/av/soundtrigger"), IsEmptyDirectory: true}, 753 &command.VirtualInput{Path: filepath.Clean("system/core/libcutils/include"), IsEmptyDirectory: true}, 754 &command.VirtualInput{Path: filepath.Clean("system/core/libutils/include"), IsEmptyDirectory: true}, 755 &command.VirtualInput{Path: filepath.Clean("system/core/libbacktrace/include"), IsEmptyDirectory: true}, 756 &command.VirtualInput{Path: filepath.Clean("system/core/liblog/include"), IsEmptyDirectory: true}, 757 &command.VirtualInput{Path: filepath.Clean("system/core/libsystem/include"), IsEmptyDirectory: true}, 758 &command.VirtualInput{Path: filepath.Clean("out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm_armv7-a-neon_core_static/gen/aidl"), IsEmptyDirectory: true}, 759 &command.VirtualInput{Path: filepath.Clean("frameworks/native/libs/binder/include"), IsEmptyDirectory: true}, 760 &command.VirtualInput{Path: filepath.Clean("system/core/base/include"), IsEmptyDirectory: true}, 761 &command.VirtualInput{Path: filepath.Clean("external/libcxx/include"), IsEmptyDirectory: true}, 762 &command.VirtualInput{Path: filepath.Clean("external/libcxxabi/include"), IsEmptyDirectory: true}, 763 &command.VirtualInput{Path: filepath.Clean("bionic/libc/system_properties/include"), IsEmptyDirectory: true}, 764 &command.VirtualInput{Path: filepath.Clean("system/core/property_service/libpropertyinfoparser/include"), IsEmptyDirectory: true}, 765 &command.VirtualInput{Path: filepath.Clean("system/core/include"), IsEmptyDirectory: true}, 766 &command.VirtualInput{Path: filepath.Clean("system/media/audio/include"), IsEmptyDirectory: true}, 767 &command.VirtualInput{Path: filepath.Clean("hardware/libhardware/include"), IsEmptyDirectory: true}, 768 &command.VirtualInput{Path: filepath.Clean("hardware/libhardware_legacy/include"), IsEmptyDirectory: true}, 769 &command.VirtualInput{Path: filepath.Clean("hardware/ril/include"), IsEmptyDirectory: true}, 770 &command.VirtualInput{Path: filepath.Clean("libnativehelper/include"), IsEmptyDirectory: true}, 771 &command.VirtualInput{Path: filepath.Clean("frameworks/native/include"), IsEmptyDirectory: true}, 772 &command.VirtualInput{Path: filepath.Clean("frameworks/native/opengl/include"), IsEmptyDirectory: true}, 773 &command.VirtualInput{Path: filepath.Clean("frameworks/av/include"), IsEmptyDirectory: true}, 774 &command.VirtualInput{Path: filepath.Clean("bionic/libc/include"), IsEmptyDirectory: true}, 775 &command.VirtualInput{Path: filepath.Clean("bionic/libc/kernel/uapi"), IsEmptyDirectory: true}, 776 &command.VirtualInput{Path: filepath.Clean("bionic/libc/kernel/uapi/asm-arm"), IsEmptyDirectory: true}, 777 &command.VirtualInput{Path: filepath.Clean("bionic/libc/kernel/android/scsi"), IsEmptyDirectory: true}, 778 &command.VirtualInput{Path: filepath.Clean("bionic/libc/kernel/android/uapi"), IsEmptyDirectory: true}, 779 &command.VirtualInput{Path: filepath.Clean("libnativehelper/include_jni"), IsEmptyDirectory: true}, 780 }, 781 } 782 wantOut := []string{ 783 filepath.Clean("out/soong/.intermediates/frameworks/av/soundtrigger/libsoundtrigger/android_arm_armv7-a-neon_core_shared/obj/frameworks/av/soundtrigger/ISoundTriggerClient.o.d"), 784 filepath.Clean("out/soong/.intermediates/frameworks/av/soundtrigger/libsoundtrigger/android_arm_armv7-a-neon_core_shared/obj/frameworks/av/soundtrigger/ISoundTriggerClient.o"), 785 } 786 wantIO := &CommandIO{ 787 InputSpec: wantInp, 788 OutputFiles: wantOut, 789 EmittedDependencyFile: filepath.Clean("out/soong/.intermediates/frameworks/av/soundtrigger/libsoundtrigger/android_arm_armv7-a-neon_core_shared/obj/frameworks/av/soundtrigger/ISoundTriggerClient.o.d"), 790 } 791 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 792 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 793 } 794 } 795 796 func TestError(t *testing.T) { 797 cwd, err := os.Getwd() 798 if err != nil { 799 t.Fatalf("Unable to get working directory: %v", err) 800 } 801 802 tests := []struct { 803 name string 804 executionID string 805 workingDir string 806 execRoot string 807 inputSpec *command.InputSpec 808 lbls map[string]string 809 command []string 810 shallowFallback bool 811 depsScanErr error 812 wantErr error 813 }{ 814 { 815 name: "ProcessInputs fails when subprocess returns error", 816 executionID: "test1", 817 workingDir: ".", 818 execRoot: cwd, 819 inputSpec: &command.InputSpec{}, 820 lbls: map[string]string{ 821 "type": "compile", 822 "compiler": "clang", 823 "lang": "cpp", 824 "shallow": "false", 825 }, 826 command: []string{"clang++", "-c", "test.cpp"}, 827 // expect deps scanner DeadlineExceeded to be wrapped as ErrIPTimeout 828 depsScanErr: context.DeadlineExceeded, 829 wantErr: ErrIPTimeout, 830 }, 831 { 832 name: "ProcessInputs succeeds when subprocess returns error but fallback is on", 833 executionID: "test1", 834 workingDir: ".", 835 execRoot: cwd, 836 inputSpec: &command.InputSpec{}, 837 lbls: map[string]string{ 838 "type": "compile", 839 "compiler": "clang", 840 "lang": "cpp", 841 }, 842 command: []string{"clang++", "-c", "test.cpp"}, 843 shallowFallback: true, 844 }, 845 } 846 847 for _, test := range tests { 848 t.Run(test.name, func(t *testing.T) { 849 ctx := context.Background() 850 _, cleanup := execroot.Setup(t, []string{filepath.Join(test.workingDir, "remote_toolchain_inputs")}) 851 defer cleanup() 852 ds := &stubCPPDependencyScanner{processInputsError: fmt.Errorf("fail: %w", test.depsScanErr)} 853 resMgr := localresources.NewDefaultManager() 854 ip := newInputProcessor(ds, dsTimeout, false, nil, resMgr, nil, nil) 855 shallowFallbackConfig[labels.FromMap(test.lbls)] = map[ppb.ExecutionStrategy_Value]bool{ppb.ExecutionStrategy_UNSPECIFIED: test.shallowFallback} 856 defer func() { 857 shallowFallbackConfig[labels.FromMap(test.lbls)] = map[ppb.ExecutionStrategy_Value]bool{ppb.ExecutionStrategy_UNSPECIFIED: !test.shallowFallback} 858 }() 859 860 opts := &ProcessInputsOptions{ 861 ExecutionID: test.executionID, 862 Cmd: test.command, 863 WorkingDir: test.workingDir, 864 ExecRoot: test.execRoot, 865 Inputs: test.inputSpec, 866 Labels: test.lbls, 867 } 868 // verify that ProcessInputs wraps correctly dependency scanner's error 869 if _, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}); !errors.Is(err, test.wantErr) { 870 t.Errorf("ProcessInputs(%+v)=%q, want: %q", opts, err, test.depsScanErr) 871 } 872 }) 873 } 874 } 875 876 func TestTool(t *testing.T) { 877 ctx := context.Background() 878 ip := &InputProcessor{} 879 existingFiles := []string{ 880 filepath.Join(wd, "my/bin"), 881 filepath.Join(wd, "input"), 882 } 883 er, cleanup := execroot.Setup(t, existingFiles) 884 defer cleanup() 885 886 cmd := []string{"my/bin", "input"} 887 lbls := map[string]string{ 888 "type": "tool", 889 } 890 opts := &ProcessInputsOptions{ 891 ExecutionID: fakeExecutionID, 892 Cmd: cmd, 893 WorkingDir: wd, 894 ExecRoot: er, 895 Labels: lbls, 896 Inputs: &command.InputSpec{ 897 Inputs: []string{filepath.Join(wd, "input")}, 898 }, 899 } 900 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 901 if err != nil { 902 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 903 } 904 wantIO := &CommandIO{ 905 InputSpec: &command.InputSpec{Inputs: existingFiles}, 906 } 907 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 908 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 909 } 910 } 911 912 func TestSystemTool(t *testing.T) { 913 ctx := context.Background() 914 ip := &InputProcessor{} 915 existingFiles := []string{ 916 filepath.Join(wd, "input"), 917 } 918 er, cleanup := execroot.Setup(t, existingFiles) 919 defer cleanup() 920 921 cmd := []string{"cat", "input"} 922 lbls := map[string]string{ 923 "type": "tool", 924 } 925 opts := &ProcessInputsOptions{ 926 ExecutionID: fakeExecutionID, 927 Cmd: cmd, 928 WorkingDir: wd, 929 ExecRoot: er, 930 Labels: lbls, 931 Inputs: &command.InputSpec{ 932 Inputs: []string{filepath.Join(wd, "input")}, 933 }, 934 } 935 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 936 if err != nil { 937 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 938 } 939 wantIO := &CommandIO{ 940 InputSpec: &command.InputSpec{Inputs: existingFiles}, 941 } 942 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 943 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 944 } 945 } 946 947 func TestShallow(t *testing.T) { 948 ctx := context.Background() 949 resMgr := localresources.NewDefaultManager() 950 ip := newInputProcessor(nil, dsTimeout, false, nil, resMgr, nil, nil) 951 existingFiles := []string{ 952 filepath.Clean("wd/test.cpp"), 953 filepath.Clean("clang++"), 954 } 955 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 956 defer cleanup() 957 958 cmd := []string{"../clang++", "-Ifoo", "-I", "bar", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 959 lbls := map[string]string{ 960 "type": "compile", 961 "compiler": "clang", 962 "lang": "cpp", 963 "shallow": "true", 964 } 965 opts := &ProcessInputsOptions{ 966 ExecutionID: fakeExecutionID, 967 Cmd: cmd, 968 WorkingDir: wd, 969 ExecRoot: er, 970 Labels: lbls, 971 } 972 gotIO, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 973 if err != nil { 974 t.Errorf("ProcessInputs(%+v).err = %v, want no error.", opts, err) 975 } 976 wantIO := &CommandIO{ 977 InputSpec: &command.InputSpec{Inputs: existingFiles}, 978 OutputFiles: []string{filepath.Clean("wd/test.o"), filepath.Clean("wd/test.d")}, 979 EmittedDependencyFile: filepath.Clean("wd/test.d"), 980 UsedShallowMode: true, 981 } 982 if diff := cmp.Diff(wantIO, gotIO, strSliceCmp); diff != "" { 983 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 984 } 985 } 986 987 func TestError_InvalidLabels(t *testing.T) { 988 ctx := context.Background() 989 ip := &InputProcessor{} 990 er, cleanup := execroot.Setup(t, []string{filepath.Join(wd, "remote_toolchain_inputs")}) 991 defer cleanup() 992 993 cmd := []string{"clang++", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 994 lbls := map[string]string{ 995 "compiler": "clang", 996 "lang": "cpp", 997 } 998 opts := &ProcessInputsOptions{ 999 ExecutionID: fakeExecutionID, 1000 Cmd: cmd, 1001 WorkingDir: wd, 1002 ExecRoot: er, 1003 Labels: lbls, 1004 } 1005 if _, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}); status.Code(err) != codes.Unimplemented { 1006 t.Errorf("ProcessInputs(%+v).err.Code() = %v, want %v.", opts, status.Code(err), codes.Unimplemented) 1007 } 1008 } 1009 1010 // Tests that the cache returns the expected result by deleting the file between first and second call 1011 func TestFileCache(t *testing.T) { 1012 ctx := context.Background() 1013 resMgr := localresources.NewDefaultManager() 1014 ip := newInputProcessor(nil, dsTimeout, false, nil, resMgr, nil, nil) 1015 existingFiles := []string{ 1016 filepath.Clean("wd/test.cpp"), 1017 filepath.Clean("clang++"), 1018 } 1019 er, cleanup := execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 1020 defer cleanup() 1021 1022 cmd := []string{"../clang++", "-Ifoo", "-I", "bar", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 1023 lbls := map[string]string{ 1024 "type": "compile", 1025 "compiler": "clang", 1026 "lang": "cpp", 1027 "shallow": "true", 1028 } 1029 opts := &ProcessInputsOptions{ 1030 ExecutionID: fakeExecutionID, 1031 Cmd: cmd, 1032 WorkingDir: wd, 1033 ExecRoot: er, 1034 Labels: lbls, 1035 } 1036 // Process the inputs 1037 gotIOA, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 1038 if err != nil { 1039 t.Errorf("First ProcessInputs(%+v).err = %v, want no error.", opts, err) 1040 } 1041 // Cleanup the generated files 1042 cleanup() 1043 // Re-process the inputs 1044 gotIOB, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 1045 if err != nil { 1046 t.Errorf("Second ProcessInputs(%+v).err = %v, want no error.", opts, err) 1047 } 1048 1049 if diff := cmp.Diff(gotIOA, gotIOB, strSliceCmp); diff != "" { 1050 // If both cached, expect the results to be the same 1051 t.Errorf("ProcessInputs(%v) returned different results on both executions, expected identical (-first +cached): %s", opts, diff) 1052 } 1053 } 1054 1055 // Tests that missing files are not cached on the first call, and are returned by the second call 1056 // after they are created. 1057 func TestNoFileCache(t *testing.T) { 1058 ctx := context.Background() 1059 resMgr := localresources.NewDefaultManager() 1060 ip := newInputProcessor(nil, dsTimeout, false, nil, resMgr, nil, nil) 1061 existingFiles := []string{ 1062 filepath.Clean("wd/test.cpp"), 1063 filepath.Clean("clang++"), 1064 } 1065 er, cleanup := execroot.Setup(t, []string{filepath.Join(wd, "remote_toolchain_inputs")}) 1066 defer cleanup() 1067 1068 cmd := []string{"../clang++", "-Ifoo", "-I", "bar", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"} 1069 lbls := map[string]string{ 1070 "type": "compile", 1071 "compiler": "clang", 1072 "lang": "cpp", 1073 "shallow": "true", 1074 } 1075 opts := &ProcessInputsOptions{ 1076 ExecutionID: fakeExecutionID, 1077 Cmd: cmd, 1078 WorkingDir: wd, 1079 ExecRoot: er, 1080 Labels: lbls, 1081 } 1082 // Process the inputs; expect failure because "existingFiles" do not exist 1083 gotIOA, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 1084 if err != nil { 1085 t.Errorf("First ProcessInputs(%+v).err = %v, want no error.", opts, err) 1086 } 1087 // Cleanup then recreate files, with the "existingFiles" this time 1088 cleanup() 1089 er, cleanup = execroot.Setup(t, append(existingFiles, filepath.Join(wd, "remote_toolchain_inputs"))) 1090 defer cleanup() // cleanup() is a no-op if the execroot has already been cleaned up 1091 opts.ExecRoot = er 1092 // Re-process the inputs 1093 gotIOB, err := ip.ProcessInputs(ctx, opts, &logger.LogRecord{LogRecord: &lpb.LogRecord{}}) 1094 cleanup() 1095 if err != nil { 1096 t.Errorf("Second ProcessInputs(%+v).err = %v, want no error.", opts, err) 1097 } 1098 1099 wantIO := &CommandIO{ 1100 InputSpec: &command.InputSpec{Inputs: existingFiles}, 1101 OutputFiles: []string{filepath.Clean("wd/test.o"), filepath.Clean("wd/test.d")}, 1102 EmittedDependencyFile: filepath.Clean("wd/test.d"), 1103 UsedShallowMode: true, 1104 } 1105 // A and B should be different 1106 if diff := cmp.Diff(gotIOA, gotIOB, strSliceCmp); diff == "" { 1107 // If both cached, expect the results to be the same 1108 t.Errorf("ProcessInputs(%v) returned identical results on both executions, expected different: %s", opts, gotIOA) 1109 } 1110 // But B should still have everything we want 1111 if diff := cmp.Diff(wantIO, gotIOB, strSliceCmp); diff != "" { 1112 t.Errorf("ProcessInputs(%v) returned diff in CommandIO, (-want +got): %s", opts, diff) 1113 } 1114 } 1115 1116 type stubCPPDependencyScanner struct { 1117 processInputsReturnValue []string 1118 processInputsError error 1119 calls int 1120 processInputsArgs []string 1121 } 1122 1123 func (s *stubCPPDependencyScanner) ProcessInputs(_ context.Context, _ string, args []string, _ string, _ string, _ []string) ([]string, bool, error) { 1124 s.processInputsArgs = args 1125 s.calls++ 1126 return s.processInputsReturnValue, false, s.processInputsError 1127 } 1128 1129 func (s *stubCPPDependencyScanner) Capabilities() *spb.CapabilitiesResponse { 1130 return nil 1131 } 1132 1133 type execStub struct { 1134 stdout string 1135 stderr string 1136 err error 1137 } 1138 1139 func (e *execStub) Execute(ctx context.Context, cmd *command.Command) (string, string, error) { 1140 return e.stdout, e.stderr, e.err 1141 } 1142 1143 // This is unused in inputprocessor tests but required due to depsscannerservice 1144 func (e *execStub) ExecuteInBackground(_ context.Context, _ *command.Command, _ outerr.OutErr, _ chan *command.Result) error { 1145 return nil 1146 } 1147 1148 func fromFile(t *testing.T, name string) []string { 1149 t.Helper() 1150 1151 runfile, err := bazel.Runfile(path.Join("pkg/inputprocessor", name)) 1152 if err == nil { 1153 name = runfile 1154 } 1155 f, err := os.Open(name) 1156 if err != nil { 1157 t.Fatalf("Unable to read file %v: %v", name, err) 1158 } 1159 defer f.Close() 1160 1161 var res []string 1162 scanner := bufio.NewScanner(f) 1163 for scanner.Scan() { 1164 res = append(res, scanner.Text()) 1165 } 1166 err = scanner.Err() 1167 if err != nil { 1168 t.Fatalf("Failed to scan %s: %v", name, err) 1169 } 1170 return res 1171 }