github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/cppdependencyscanner/depsscannerclient/depsscannerclient_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 depsscannerclient 16 17 import ( 18 "context" 19 "errors" 20 "os" 21 "path/filepath" 22 "runtime" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "testing" 27 "time" 28 29 pb "github.com/bazelbuild/reclient/api/scandeps" 30 31 "github.com/bazelbuild/remote-apis-sdks/go/pkg/command" 32 "github.com/bazelbuild/remote-apis-sdks/go/pkg/outerr" 33 "github.com/google/go-cmp/cmp" 34 "github.com/google/uuid" 35 36 "google.golang.org/grpc" 37 "google.golang.org/protobuf/types/known/emptypb" 38 ) 39 40 // A stub Executor class that records actions but does not actually execute a command. 41 type stubExecutor struct { 42 // The error (if non-nil) to return from ExecuteInBackground(). 43 err error 44 // Output: the Context passed to ExecuteInBackground(). 45 ctx context.Context 46 // Output: the Command passed to ExecuteInBackground(). 47 cmd *command.Command 48 // output: the OutErr passed to ExecuteInBackground(). 49 oe outerr.OutErr 50 // output: the channel passed to ExecuteInBackground(). 51 ch chan *command.Result 52 } 53 54 // An object for returning preset values for certain calls during New(). 55 type testService struct { 56 // A fake gRPC client to return from connect. 57 stubClient *stubClient 58 // The number of times connect should fail before returning stubClient. 59 connectDelay time.Time 60 // Output: the number of times connect() was called. 61 connectCount atomic.Int64 62 } 63 64 // connect returns a preset stubClient. 65 func (s *testService) connect(ctx context.Context, address string) (pb.CPPDepsScannerClient, error) { 66 s.connectCount.Add(1) 67 select { 68 case <-time.After(time.Until(s.connectDelay)): 69 // Sleep, simulate a slow connection that may or may not timeout 70 case <-ctx.Done(): 71 return nil, errors.New("Connection timed out") 72 } 73 if s.stubClient != nil { 74 return s.stubClient, nil 75 } 76 return nil, errors.New("Connection not ready yet") 77 } 78 79 // A stub CPPDepsScannerClient. 80 type stubClient struct { 81 // The response to return from ProcessInputs(). 82 processInputsResponse *pb.CPPProcessInputsResponse 83 // The error to return from ProcessInputs(). 84 processInputsError error 85 // A delay to simulate work being done by ProcessInputs(). 86 processInputsSleep time.Duration 87 // The "Status" returned from a Status call or Shutdown call. 88 status *pb.StatusResponse 89 // The error to return from a Shutdown call. 90 shutdownError error 91 // The number of times Shutdown has been called. 92 shutdownCalled int 93 // The response to return from Capabilities(). 94 capabilitiesResponse *pb.CapabilitiesResponse 95 // The error to return from Capabilities(). 96 capabilitiesError error 97 } 98 99 // Fake ProcessInputs that returns a preset value with optional delay to emulate processing time. 100 func (c *stubClient) ProcessInputs(ctx context.Context, in *pb.CPPProcessInputsRequest, opts ...grpc.CallOption) (*pb.CPPProcessInputsResponse, error) { 101 select { 102 case <-time.After(c.processInputsSleep): 103 // Sleep 104 case <-ctx.Done(): 105 return nil, errors.New("timeout") 106 } 107 return c.processInputsResponse, c.processInputsError 108 } 109 110 func (c *stubClient) Status(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.StatusResponse, error) { 111 return nil, nil 112 } 113 114 // Fake Shutdown that records a Shutdown attempt but does not actually shutdown anything. 115 func (c *stubClient) Shutdown(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.StatusResponse, error) { 116 c.shutdownCalled++ 117 return c.status, c.shutdownError 118 } 119 120 func (c *stubClient) Capabilities(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.CapabilitiesResponse, error) { 121 return c.capabilitiesResponse, c.capabilitiesError 122 } 123 124 // Fake Execution that performs a setup but does not actually run the command. 125 func (e *stubExecutor) ExecuteInBackground(ctx context.Context, cmd *command.Command, oe outerr.OutErr, ch chan *command.Result) error { 126 e.ctx = ctx 127 e.cmd = cmd 128 e.oe = oe 129 e.ch = ch 130 return e.err 131 } 132 133 var ( 134 directory, _ = os.Getwd() 135 filename = "tests/integ/testdata/test.cpp" 136 // This command will not actually be used by any input processor, it is merely a prop. 137 compileCommand = []string{ 138 "tests/integ/testdata/clang/bin/clang++", 139 "--sysroot", "tests/integ/testdata/sysroot", 140 "-c", 141 "-I", 142 "tests/integ/testdata/clang/include/c++/v1", 143 "-o", "test.obj", 144 filename, 145 } 146 processInputsTimeout = 100 * time.Millisecond 147 ) 148 149 // TestNew_ConnectSuccess tests that a call to New() can connect to an already running dependency 150 // scanner service. 151 func TestNew_ConnectSuccess(t *testing.T) { 152 testService := &testService{ 153 stubClient: &stubClient{}, 154 } 155 oldConnect := connect 156 connect = testService.connect 157 t.Cleanup(func() { 158 connect = oldConnect 159 }) 160 depsScannerClient, err := New(context.Background(), nil, "", 0, false, "", "127.0.0.1:8001", "127.0.0.1:1000") 161 if err != nil { 162 t.Errorf("New() retured unexpected error: %v", err) 163 } 164 if testService.connectCount.Load() != 1 { 165 t.Errorf("New(); expected 1 connection attempt, got %v", testService.connectCount.Load()) 166 } 167 if depsScannerClient == nil { 168 t.Error("New(): Expected DepsScannerClient; got nil") 169 } 170 } 171 172 // TestNew_ConnectFailure tests that a call to New() will fail if no dependency scanner service is 173 // running when expected. 174 func TestNew_ConnectFailure(t *testing.T) { 175 setConnTimeout(t, 500*time.Millisecond) 176 testService := &testService{ 177 stubClient: nil, 178 connectDelay: time.Now().Add(5 * time.Second), // Wait for 5 seconds before "accepting" the connection 179 } 180 oldConnect := connect 181 connect = testService.connect 182 t.Cleanup(func() { 183 connect = oldConnect 184 }) 185 depsScannerClient, err := New(context.Background(), nil, "", 0, false, "", "127.0.0.1:8001", "127.0.0.1:1000") 186 if err == nil { 187 t.Errorf("New() did not return expected error") 188 } 189 // Windows and mac runs are inconsistent with exactly how many attempts fit in 500ms 190 if testService.connectCount.Load() != 1 { 191 t.Errorf("New(): expected 1 connection attempt, got %v", testService.connectCount.Load()) 192 } 193 if depsScannerClient != nil { 194 t.Error("New(): Returned DepsScannerClient; expected nil") 195 } 196 } 197 198 // TestNew_StartSuccess tests that a call to New() will start and connect to a dependency scanner 199 // service executable. 200 func TestNew_StartSuccess(t *testing.T) { 201 testService := &testService{ 202 stubClient: &stubClient{}, 203 } 204 oldConnect := connect 205 connect = testService.connect 206 t.Cleanup(func() { 207 connect = oldConnect 208 }) 209 stubExecutor := &stubExecutor{} 210 depsScannerClient, err := New(context.Background(), stubExecutor, "", 0, false, "", "exec://test_exec", "127.0.0.1:1000") 211 if err != nil { 212 t.Errorf("New() retured unexpected error: %v", err) 213 } 214 if testService.connectCount.Load() != 1 { 215 t.Errorf("New(); expected 1 connection attempt, got %v", testService.connectCount.Load()) 216 } 217 if depsScannerClient == nil { 218 t.Error("New(): Expected DepsScannerClient; got nil") 219 } 220 if stubExecutor.oe == nil { 221 t.Error("New(): Executor did not get an output") 222 } 223 if stubExecutor.ch == nil { 224 t.Error("New(): Executor did not get a channel") 225 } 226 if stubExecutor.cmd == nil { 227 t.Error("New(): Executor did not get a cmd") 228 } 229 if !strings.HasPrefix(depsScannerClient.address, "127.0.0.1:") { 230 t.Errorf("New(): Connected to %v; expected prefix %v", depsScannerClient.address, "127.0.0.1:") 231 } 232 if depsScannerClient.executable != "test_exec" { 233 t.Errorf("New(): Executed %v; expected %v", depsScannerClient.executable, "test_exec") 234 } 235 select { 236 case <-stubExecutor.ctx.Done(): 237 t.Error("New(): Unexpected Cancel() call") 238 default: 239 // No Cancel() call. Expected. 240 } 241 } 242 243 // TestNew_StartFailure tests that a call to New() will fail if an executable is provided but cannot 244 // be started. 245 func TestNew_StartFailure(t *testing.T) { 246 testService := &testService{ 247 stubClient: &stubClient{}, 248 } 249 oldConnect := connect 250 connect = testService.connect 251 t.Cleanup(func() { 252 connect = oldConnect 253 }) 254 stubExecutor := &stubExecutor{ 255 err: errors.New("File not found"), 256 } 257 depsScannerClient, err := New(context.Background(), stubExecutor, "", 0, false, "", "exec://test_exec", "127.0.0.1:1000") 258 if err == nil { 259 t.Errorf("New() did not return expected error") 260 } 261 if testService.connectCount.Load() != 0 { 262 t.Errorf("New(); expected 0 connection attempts, got %v", testService.connectCount.Load()) 263 } 264 if depsScannerClient != nil { 265 t.Error("New(): Received DepsScannerClient; expected nil") 266 } 267 if stubExecutor.oe == nil { 268 t.Error("New(): Executor did not get an output") 269 } 270 if stubExecutor.ch == nil { 271 t.Error("New(): Executor did not get a channel") 272 } 273 if stubExecutor.cmd == nil { 274 t.Error("New(): Executor did not get a cmd") 275 } 276 select { 277 case <-stubExecutor.ctx.Done(): 278 t.Error("New(): Unexpected Cancel() call") 279 default: 280 // No Cancel() call. Expected. 281 } 282 } 283 284 // TestNew_StartNoConnect tests that New() will error if it is able to successfully start a 285 // dependency scanner service, but is unable to connect to it for any reason. 286 func TestNew_StartNoConnect(t *testing.T) { 287 setConnTimeout(t, 500*time.Millisecond) 288 testService := &testService{ 289 connectDelay: time.Now().Add(5 * time.Second), // Wait for 5 seconds before "accepting" the connection 290 } 291 oldConnect := connect 292 connect = testService.connect 293 t.Cleanup(func() { 294 connect = oldConnect 295 }) 296 stubExecutor := &stubExecutor{} 297 depsScannerClient, err := New(context.Background(), stubExecutor, "", 0, false, "", "exec://test_exec", "127.0.0.1:1000") 298 if err == nil { 299 t.Errorf("New() did not return expected error") 300 } 301 // Windows and mac runs are inconsistent with exactly how many attempts fit in 500ms 302 if testService.connectCount.Load() != 1 { 303 t.Errorf("New(): expected 1 connection attempt, got %v", testService.connectCount.Load()) 304 } 305 if depsScannerClient != nil { 306 t.Error("New(): DepsScannerClient returned; expected nil") 307 } 308 if stubExecutor.oe == nil { 309 t.Error("New(): Executor did not get an output") 310 } 311 if stubExecutor.ch == nil { 312 t.Error("New(): Executor did not get a channel") 313 } 314 if stubExecutor.cmd == nil { 315 t.Error("New(): Executor did not get a cmd") 316 } 317 } 318 319 // TestNew_StartDelayedConnect tests that a call to New() will be successful if the started 320 // dependency scanner service takes a little while (more than 5 seconds, less than 15) to become 321 // available. 322 func TestNew_StartDelayedConnect(t *testing.T) { 323 testService := &testService{ 324 stubClient: &stubClient{}, 325 connectDelay: time.Now().Add(5 * time.Second), // Wait for 5 seconds before "accepting" the connection 326 } 327 oldConnect := connect 328 connect = testService.connect 329 t.Cleanup(func() { 330 connect = oldConnect 331 }) 332 stubExecutor := &stubExecutor{} 333 depsScannerClient, err := New(context.Background(), stubExecutor, "", 0, false, "", "exec://test_exec", "127.0.0.1:1000") 334 if err != nil { 335 t.Errorf("New() retured unexpected error: %v", err) 336 } 337 if testService.connectCount.Load() != 1 { 338 t.Errorf("New(); expected 1 connection attempt, got %v", testService.connectCount.Load()) 339 } 340 if depsScannerClient == nil { 341 t.Error("New(): Expected DepsScannerClient; got nil") 342 } 343 if stubExecutor.oe == nil { 344 t.Error("New(): Executor did not get an output") 345 } 346 if stubExecutor.ch == nil { 347 t.Error("New(): Executor did not get a channel") 348 } 349 if stubExecutor.cmd == nil { 350 t.Error("New(): Executor did not get a cmd") 351 } 352 select { 353 case <-stubExecutor.ctx.Done(): 354 t.Error("New(): Unexpected Cancel() call") 355 default: 356 // No Cancel() call. Expected. 357 } 358 } 359 360 // TestStopService_Success tests that a call to stopService (trivially called by the exported 361 // Close() function) will properly stop the service. 362 func TestStopService_Success(t *testing.T) { 363 terminateCalled := 0 364 stubClient := &stubClient{} 365 depsScannerClient := &DepsScannerClient{ 366 address: "127.0.0.1:8001", 367 ctx: context.Background(), 368 terminate: func() { 369 terminateCalled++ 370 }, 371 executable: "test_exec", 372 oe: outerr.NewRecordingOutErr(), 373 ch: make(chan *command.Result), 374 client: stubClient, 375 } 376 var wg sync.WaitGroup 377 var err error 378 wg.Add(1) 379 go func() { 380 defer wg.Done() 381 err = depsScannerClient.stopService(1 * time.Second) 382 }() 383 384 select { 385 case depsScannerClient.ch <- command.NewResultFromExitCode(0): 386 // Succesfully "stopped" the service. 387 case <-time.After(1 * time.Second): 388 t.Error("stopService was not waiting for shutdown") 389 } 390 391 wg.Wait() 392 if err != nil { 393 t.Error("Unexpected error shutting down service: %v", err) 394 } 395 if stubClient.shutdownCalled != 1 { 396 t.Error("Expected Shutdown to be called exactly once; called %v times", stubClient.shutdownCalled) 397 } 398 if terminateCalled != 0 { 399 t.Error("Terminate called %v times; expected 0 (clean shutdown)", terminateCalled) 400 } 401 } 402 403 // TestStopService_Failure tests that stopService (trivially called by the exported Close() 404 // function) will error if it was unable to verify the service had been shutdown after a fixed 405 // timeout. 406 func TestStopService_Failure(t *testing.T) { 407 terminateCalled := 0 408 stubClient := &stubClient{} 409 depsScannerClient := &DepsScannerClient{ 410 address: "127.0.0.1:8001", 411 ctx: context.Background(), 412 terminate: func() { 413 terminateCalled++ 414 }, 415 executable: "test_exec", 416 oe: outerr.NewRecordingOutErr(), 417 ch: make(chan *command.Result), 418 client: stubClient, 419 } 420 var wg sync.WaitGroup 421 var err error 422 wg.Add(1) 423 go func() { 424 defer wg.Done() 425 err = depsScannerClient.stopService(1 * time.Second) 426 }() 427 428 wg.Wait() 429 if err == nil { 430 t.Error("Error expected while shutting down; received none") 431 } 432 if stubClient.shutdownCalled != 1 { 433 t.Error("Expected Shutdown to be called exactly once; called %v times", stubClient.shutdownCalled) 434 } 435 if terminateCalled != 1 { 436 t.Error("Terminate called %v times; expected 1 (forced shutdown)", terminateCalled) 437 } 438 } 439 440 func TestProcessInputs_nocache(t *testing.T) { 441 execID := uuid.New().String() 442 fileList := []string{ 443 filename, 444 "foo.h", 445 "bar.h", 446 } 447 wantDeps := []string{} 448 for _, d := range fileList { 449 wantDeps = append(wantDeps, filepath.Join(directory, d)) 450 } 451 stubClient := &stubClient{ 452 processInputsResponse: &pb.CPPProcessInputsResponse{ 453 // Set values to verify they are not returned on error 454 Dependencies: fileList, 455 UsedCache: false, 456 }, 457 } 458 459 client := &DepsScannerClient{ 460 ctx: context.Background(), 461 client: stubClient, 462 } 463 gotDeps, cached, err := client.ProcessInputs(context.Background(), execID, compileCommand, filename, directory, []string{}) 464 if err != nil { 465 t.Errorf("ProcessInputs failed: %v", err) 466 } 467 if cached != false { 468 t.Errorf("ProcessInputs UsedCache == true; expected false") 469 } 470 if !cmp.Equal(wantDeps, gotDeps) { 471 t.Errorf("ProcessInputs(%q)=%q; want %q\ndiff -want +got\n%s", filename, gotDeps, wantDeps, cmp.Diff(wantDeps, gotDeps)) 472 } 473 } 474 475 func TestProcessInputs_abspath(t *testing.T) { 476 execID := uuid.New().String() 477 fileList := []string{ 478 filename, 479 "foo.h", 480 "bar.h", 481 } 482 wantDeps := []string{} 483 for _, d := range fileList { 484 wantDeps = append(wantDeps, filepath.Join(directory, d)) 485 } 486 absFile := "/path/to/some/file.h" 487 if runtime.GOOS == "windows" { 488 absFile = filepath.Join("C:/", absFile) 489 } 490 fileList = append(fileList, absFile) 491 wantDeps = append(wantDeps, absFile) 492 493 stubClient := &stubClient{ 494 processInputsResponse: &pb.CPPProcessInputsResponse{ 495 // Set values to verify they are not returned on error 496 Dependencies: fileList, 497 }, 498 } 499 500 client := &DepsScannerClient{ 501 ctx: context.Background(), 502 client: stubClient, 503 } 504 505 gotDeps, _, err := client.ProcessInputs(context.Background(), execID, compileCommand, filename, directory, []string{}) 506 if err != nil { 507 t.Errorf("ProcessInputs failed: %v", err) 508 } 509 if !cmp.Equal(wantDeps, gotDeps) { 510 t.Errorf("ProcessInputs(%q)=%q; want %q\ndiff -want +got\n%s", filename, gotDeps, wantDeps, cmp.Diff(wantDeps, gotDeps)) 511 } 512 } 513 514 func TestProcessInputs_cache(t *testing.T) { 515 execID := uuid.New().String() 516 fileList := []string{ 517 filename, 518 "foo.h", 519 "bar.h", 520 } 521 wantDeps := []string{} 522 for _, d := range fileList { 523 wantDeps = append(wantDeps, filepath.Join(directory, d)) 524 } 525 stubClient := &stubClient{ 526 processInputsResponse: &pb.CPPProcessInputsResponse{ 527 // Set values to verify they are not returned on error 528 Dependencies: fileList, 529 UsedCache: true, 530 }, 531 } 532 533 client := &DepsScannerClient{ 534 ctx: context.Background(), 535 client: stubClient, 536 } 537 538 gotDeps, cached, err := client.ProcessInputs(context.Background(), execID, compileCommand, filename, directory, []string{}) 539 if err != nil { 540 t.Errorf("ProcessInputs failed: %v", err) 541 } 542 if cached != true { 543 t.Errorf("ProcessInputs UsedCache == false; expected true") 544 } 545 if !cmp.Equal(wantDeps, gotDeps) { 546 t.Errorf("ProcessInputs(%q)=%q; want %q\ndiff -want +got\n%s", filename, gotDeps, wantDeps, cmp.Diff(wantDeps, gotDeps)) 547 } 548 } 549 550 func TestProcessInputs_remoteError(t *testing.T) { 551 execID := uuid.New().String() 552 stubClient := &stubClient{ 553 processInputsResponse: &pb.CPPProcessInputsResponse{ 554 // Set values to verify they are not returned on error 555 Dependencies: []string{filename}, 556 UsedCache: true, 557 }, 558 processInputsError: errors.New("Error"), 559 } 560 561 client := &DepsScannerClient{ 562 ctx: context.Background(), 563 client: stubClient, 564 } 565 566 gotDeps, cached, err := client.ProcessInputs(context.Background(), execID, compileCommand, filename, directory, []string{}) 567 if err == nil { 568 t.Errorf("ProcessInputs succeeded; expected error") 569 } 570 if cached != false { 571 t.Errorf("ProcessInputs produced an error, but UsedCache is true (expected false)") 572 } 573 if gotDeps != nil { 574 t.Errorf("ProcessInputs produced an error, but dependencies was not nil: %v", gotDeps) 575 } 576 } 577 578 func TestProcessInputs_timeoutError(t *testing.T) { 579 execID := uuid.New().String() 580 stubClient := &stubClient{ 581 processInputsResponse: &pb.CPPProcessInputsResponse{ 582 // Set values to verify they are not returned on error 583 Dependencies: []string{filename}, 584 UsedCache: true, 585 }, 586 processInputsSleep: 2 * processInputsTimeout, 587 } 588 589 client := &DepsScannerClient{ 590 ctx: context.Background(), 591 client: stubClient, 592 } 593 ctx, cancel := context.WithTimeout(context.Background(), processInputsTimeout) 594 defer cancel() 595 gotDeps, cached, err := client.ProcessInputs(ctx, execID, compileCommand, filename, directory, []string{}) 596 if err == nil { 597 t.Errorf("ProcessInputs succeeded; expected error") 598 } 599 if cached != false { 600 t.Errorf("ProcessInputs produced an error, but UsedCache is true (expected false)") 601 } 602 if gotDeps != nil { 603 t.Errorf("ProcessInputs produced an error, but dependencies was not nil: %v", gotDeps) 604 } 605 } 606 607 func TestFindKeyVal(t *testing.T) { 608 tests := []struct { 609 name string 610 envStr string 611 knownVars map[string]string 612 wantKey string 613 wantVal string 614 wantErr bool 615 }{{ 616 name: "Normal", 617 envStr: "Key=Val", 618 knownVars: map[string]string{ 619 "Key": "Val", 620 }, 621 wantKey: "Key", 622 wantVal: "Val", 623 }, { 624 name: "EmptyVal", 625 envStr: "Key=", 626 knownVars: map[string]string{ 627 "Key": "", 628 }, 629 wantKey: "Key", 630 wantVal: "", 631 }, { 632 name: "SpaceInVal", 633 envStr: "Key=Val Abc", 634 knownVars: map[string]string{ 635 "Key": "Val Abc", 636 }, 637 wantKey: "Key", 638 wantVal: "Val Abc", 639 }, { 640 name: "EqualsInKey", 641 envStr: "Key=StillTheKey=Val", 642 knownVars: map[string]string{ 643 "Key=StillTheKey": "Val", 644 }, 645 wantKey: "Key=StillTheKey", 646 wantVal: "Val", 647 }, { 648 name: "EqualsInKeyEmptyVal", 649 envStr: "Key=StillTheKey=", 650 knownVars: map[string]string{ 651 "Key=StillTheKey": "", 652 }, 653 wantKey: "Key=StillTheKey", 654 wantVal: "", 655 }, { 656 name: "EqualsInVal", 657 envStr: "Key=Val=StillTheVal", 658 knownVars: map[string]string{ 659 "Key": "Val=StillTheVal", 660 }, 661 wantKey: "Key", 662 wantVal: "Val=StillTheVal", 663 }, { 664 name: "EqualsInKeyOverlap", 665 envStr: "Key=StillTheKey=Val", 666 knownVars: map[string]string{ 667 "Key": "Val=StillTheVal", 668 "Key=StillTheKey": "Val", 669 }, 670 wantKey: "Key=StillTheKey", 671 wantVal: "Val", 672 }, { 673 name: "MissingEnvVar", 674 envStr: "Key=StillTheKey=Val", 675 knownVars: map[string]string{}, 676 wantKey: "", 677 wantVal: "", 678 wantErr: true, 679 }, 680 } 681 for _, test := range tests { 682 test := test 683 t.Run(test.name, func(t *testing.T) { 684 t.Parallel() 685 lookupEnvFunc := func(k string) (string, bool) { 686 v, ok := test.knownVars[k] 687 return v, ok 688 } 689 gotKey, gotVal, err := findKeyVal(test.envStr, lookupEnvFunc) 690 if test.wantErr && err == nil { 691 t.Errorf("findKeyVal(%v) did not return expected error", test.envStr) 692 } 693 if !test.wantErr && err != nil { 694 t.Errorf("findKeyVal(%v) returned unexpected error: %v", test.envStr, err) 695 } 696 if gotKey != test.wantKey || gotVal != test.wantVal { 697 t.Errorf("findKeyVal(%v) returned wrong key value pair, wanted key=%v value=%v, got key=%v value=%v", test.envStr, test.wantKey, test.wantVal, gotKey, gotVal) 698 } 699 }) 700 } 701 } 702 703 func TestBuildAddress(t *testing.T) { 704 tests := []struct { 705 name string 706 platforms []string 707 serverAddr string 708 openPortFunc func() (int, error) 709 wantAddr string 710 }{ 711 { 712 name: "UnixSocketReplacereproxy", 713 platforms: []string{"linux", "darwin"}, 714 serverAddr: "unix:///some/dir/reproxy_123.sock", 715 openPortFunc: func() (int, error) { return 111, nil }, 716 wantAddr: "unix:///some/dir/depscan_123.sock", 717 }, 718 { 719 name: "UnixSocketAbsOnUnix", 720 platforms: []string{"linux", "darwin"}, 721 serverAddr: "unix:///some/dir/somesocket.sock", 722 openPortFunc: func() (int, error) { return 111, nil }, 723 wantAddr: "unix:///some/dir/ds_somesocket.sock", 724 }, 725 { 726 name: "UnixSocketAbsNoDirsOnUnix", 727 platforms: []string{"linux", "darwin"}, 728 serverAddr: "unix:///somesocket.sock", 729 openPortFunc: func() (int, error) { return 111, nil }, 730 wantAddr: "unix:///ds_somesocket.sock", 731 }, 732 { 733 name: "UnixSocketRelNoDirsOnUnix", 734 platforms: []string{"linux", "darwin"}, 735 serverAddr: "unix://somesocket.sock", 736 openPortFunc: func() (int, error) { return 111, nil }, 737 wantAddr: "unix://ds_somesocket.sock", 738 }, 739 { 740 name: "UnixSocketRelOnUnix", 741 platforms: []string{"linux", "darwin"}, 742 serverAddr: "unix://a/b/somesocket.sock", 743 openPortFunc: func() (int, error) { return 111, nil }, 744 wantAddr: "unix://a/b/ds_somesocket.sock", 745 }, 746 { 747 name: "UnixSocketOnWindows", 748 platforms: []string{"windows"}, 749 serverAddr: "unix://some/dir/somesocket.sock", 750 openPortFunc: func() (int, error) { return 222, nil }, 751 wantAddr: "127.0.0.1:222", 752 }, 753 { 754 name: "WindowsPipe", 755 platforms: []string{"windows"}, 756 serverAddr: "pipe://pipename.pipe", 757 openPortFunc: func() (int, error) { return 333, nil }, 758 wantAddr: "127.0.0.1:333", 759 }, 760 { 761 name: "TCP", 762 platforms: []string{"linux", "darwin", "windows"}, 763 serverAddr: "127.0.0.1:8000", 764 openPortFunc: func() (int, error) { return 444, nil }, 765 wantAddr: "127.0.0.1:444", 766 }, 767 { 768 name: "AnotherTCPAddress", 769 platforms: []string{"linux", "darwin", "windows"}, 770 serverAddr: "192.168.1.1:8000", 771 openPortFunc: func() (int, error) { return 555, nil }, 772 wantAddr: "192.168.1.1:555", 773 }, 774 } 775 for _, test := range tests { 776 test := test 777 runForPlatforms(t, test.name, test.platforms, func(t *testing.T) { 778 t.Parallel() 779 gotAddr, err := buildAddress(test.serverAddr, test.openPortFunc) 780 if err != nil { 781 t.Errorf("buildAddress(%v) returned unexpected error: %v", test.serverAddr, err) 782 } 783 if gotAddr != test.wantAddr { 784 t.Errorf("buildAddress(%v) returned wrong address, wanted '%v', got '%v'", test.serverAddr, test.wantAddr, gotAddr) 785 } 786 }) 787 } 788 } 789 790 func TestCapabilities(t *testing.T) { 791 want := &pb.CapabilitiesResponse{ 792 Caching: false, 793 ExpectsResourceDir: true, 794 } 795 stubClient := &stubClient{ 796 capabilitiesResponse: want, 797 } 798 client := &DepsScannerClient{ 799 ctx: context.Background(), 800 client: stubClient, 801 } 802 client.updateCapabilities(context.Background()) 803 804 if got := client.Capabilities(); got != want { 805 t.Errorf("Capabilities() returned unexpected value, wanted %v, got %v", want, got) 806 } 807 } 808 809 func runForPlatforms(t *testing.T, name string, platforms []string, test func(t *testing.T)) { 810 t.Helper() 811 for _, platform := range platforms { 812 if runtime.GOOS == platform { 813 t.Run(name, test) 814 return 815 } 816 } 817 } 818 819 func setConnTimeout(t *testing.T, newTimeout time.Duration) { 820 t.Helper() 821 oldConnTimeout := connTimeout 822 connTimeout = newTimeout 823 t.Cleanup(func() { 824 connTimeout = oldConnTimeout 825 }) 826 }