Repository: grpc/grpc-go
File: balancer/pickfirst/pickfirstleaf/pickfirstleaf_ext_test.go (Go)

Code before:
	rb.UpdateState(resolver.State{Addresses: addrs})
	cc.Connect()

	testutils.AwaitState(ctx, t, cc, connectivity.Connecting)

	// Verify that only the first server is contacted.
	if got, want := holds[0].Wait(ctx), true; got != want {
		t.Fatalf("hold[%q].Wait() = %t, want %t", addrs[0], got, want)
	}

	// Ensure no other servers are contacted.
	if got, want := holds[1].IsStarted(), false; got != want {
		t.Fatalf("holds[%q].IsStarted() = %t, want %t", addrs[1], got, want)
	}

	if got, want := holds[2].IsStarted(), false; got != want {
		t.Fatalf("holds[%q].IsStarted() = %t, want %t", addrs[2], got, want)
	}

	// Make the happy eyeballs timer fire twice so that pickfirst reaches the
	// last address in the list.
	timerCh <- struct{}{}

	// Verify that the second server is contacted and 3rd isn't.
	if got, want := holds[1].Wait(ctx), true; got != want {
		t.Fatalf("hold[%q].Wait() = %t, want %t", addrs[1], got, want)
	}

	if got, want := holds[2].IsStarted(), false; got != want {
		t.Fatalf("holds[%q].IsStarted() = %t, want %t", addrs[2], got, want)
	}

	timerCh <- struct{}{}
	// Verify that the second server is contacted and 3rd isn't.
	if got, want := holds[1].Wait(ctx), true; got != want {
		t.Fatalf("hold[%q].Wait() = %t, want %t", addrs[1], got, want)
	}

	// First SubConn Fails.
	holds[0].Fail(fmt.Errorf("test error"))

	// No TF should be reported until the first pass is complete.
	shortCtx, shortCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
	defer shortCancel()

	testutils.AwaitNotState(shortCtx, t, cc, connectivity.TransientFailure)

	// Third SubConn fails.
	holds[2].Fail(fmt.Errorf("test error"))

	testutils.AwaitNotState(shortCtx, t, cc, connectivity.TransientFailure)

Review comment (refactor):
I understand the style guide recommendation to identify the function in the test error message, but this is a function on a test utility type and not the function under test. So, maybe something like:
```
	t.Fatalf("Timeout when waiting for second server with address %q to be contacted", addrs[1])
```

Similarly for other error strings where we are calling `hold.Wait` or `hold.IsStarted`.

Code after:
	triggerTimer()
	if holds[1].Wait(ctx) != true {
		t.Fatalf("Timeout waiting for server %d with address %q to be contacted", 1, addrs[1])
	}
	if holds[2].IsStarted() != false {
		t.Fatalf("Server %d with address %q contacted unexpectedly", 2, addrs[2])
	}

	// Make the happy eyeballs timer fire once more and verify that the
	// third server is contacted.
	triggerTimer()
	if holds[2].Wait(ctx) != true {
		t.Fatalf("Timeout waiting for server %d with address %q to be contacted", 2, addrs[2])
	}

	// First SubConn Fails.
	holds[0].Fail(fmt.Errorf("test error"))

	// No TF should be reported until the first pass is complete.
	shortCtx, shortCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
	defer shortCancel()
	testutils.AwaitNotState(shortCtx, t, cc, connectivity.TransientFailure)

	// Third SubConn fails.
	shortCtx, shortCancel = context.WithTimeout(ctx, defaultTestShortTimeout)
	defer shortCancel()
	holds[2].Fail(fmt.Errorf("test error"))
	testutils.AwaitNotState(shortCtx, t, cc, connectivity.TransientFailure)

	// Last SubConn fails, this should result in a TF update.
	holds[1].Fail(fmt.Errorf("test error"))
	testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure)
}

// Test verifies that pickfirst attempts to connect to the second backend once
// the happy eyeballs timer expires.
func (s) TestPickFirstLeaf_HappyEyeballs_TriggerConnectionDelay(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()

	originalTimer := pfinternal.TimeAfterFunc
	defer func() {
		pfinternal.TimeAfterFunc = originalTimer
	}()
	triggerTimer, timeAfter := mockTimer()
	pfinternal.TimeAfterFunc = timeAfter

	dialer := testutils.NewBlockingDialer()
	opts := []grpc.DialOption{
		grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, pickfirstleaf.Name)),
		grpc.WithContextDialer(dialer.DialContext),