Repository: grpc/grpc-go
File: balancer/randomsubsetting/randomsubsetting_test.go (Go)

Code before:
					time.Sleep(10 * time.Microsecond)
					i--
				}
			} else {
				t.Fatalf("peer.Addr == nil, peer: %v", peer)
			}
		}
	}

	minClientsPerBackend := math.MaxInt
	maxClientsPerBackend := 0
	for _, v := range clientsPerBackend {
		if len(v) < minClientsPerBackend {
			minClientsPerBackend = len(v)
		}
		if len(v) > maxClientsPerBackend {
			maxClientsPerBackend = len(v)
		}
	}

	if maxClientsPerBackend > minClientsPerBackend+maxDiff {
		t.Fatalf("the difference between min and max clients per backend should be <= %d, clientsPerBackend: %v", maxDiff, clientsPerBackend)
	}
}

func (s) TestSubsettingE2E(t *testing.T) {
	// Save the original hash seed and restore it at the end of the test.
	originalHashSeed := HashSeed
	defer func() {
		HashSeed = originalHashSeed
	}()

	// Set a fixed hash seed to make the test deterministic.
	HashSeed = 1
	tests := []struct {
		name       string
		subsetSize int
		clients    int
		backends   int
		maxDiff    int
	}{
		{
			name:       "backends could be evenly distributed between small number of clients",
			backends:   3,
			clients:    2,
			subsetSize: 2,
			maxDiff:    1,
		},
		{
			name:       "backends could be evenly distributed between clients",
			backends:   12,

Review comment (refactor):
The only real piece of functionality provided by this LB policy is the subsetting algorithm. If we refactor it the way I recommended in another comment, then we could simply have unit tests for that method to ensure that subsetting behavior is as expected. 

And then we could have a functional test where we:
- create an instance of the LB policy, 
- call its `UpdateClientConnState` method to pass it some configuration with a child policy that is controlled by the test. See here for how to define a balancer that is completely under the control of the test: https://github.com/grpc/grpc-go/blob/master/internal/balancer/stub/stub.go
- Verify that the child policy gets the expected `resolver.State`

There is very little value is having an E2E style for this LB policy.

Code after:
	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()

	// Register the stub balancer builder, which will be used as the child
	// policy in the random_subsetting balancer.
	updateCh := make(chan balancer.ClientConnState, 1)
	stub.Register("stub-child-balancer", stub.BalancerFuncs{
		UpdateClientConnState: func(_ *stub.BalancerData, s balancer.ClientConnState) error {
			select {
			case <-ctx.Done():
			case updateCh <- s:
			}
			return nil
		},
	})

	// Create a random_subsetting balancer.
	tcc := testutils.NewBalancerClientConn(t)
	rsb := balancer.Get(Name).Build(tcc, balancer.BuildOptions{})
	defer rsb.Close()

	// Prepare the configuration and resolver state to be passed to the
	// random_subsetting balancer.
	endpoints := makeEndpoints(10)
	state := balancer.ClientConnState{
		ResolverState: resolver.State{Endpoints: endpoints},
		BalancerConfig: &lbConfig{
			SubsetSize:  5,
			ChildPolicy: &iserviceconfig.BalancerConfig{Name: "stub-child-balancer"},
		},
	}

	// Send the resolver state to the random_subsetting balancer and verify that
	// the child policy receives the expected number of endpoints.
	if err := rsb.UpdateClientConnState(state); err != nil {
		t.Fatalf("UpdateClientConnState failed: %v", err)
	}

	var wantEndpoints []resolver.Endpoint
	select {
	case s := <-updateCh:
		if len(s.ResolverState.Endpoints) != 5 {
			t.Fatalf("Child policy received %d endpoints, want 5", len(s.ResolverState.Endpoints))
		}
		// Store the subset for the next comparison.
		wantEndpoints = s.ResolverState.Endpoints
	case <-ctx.Done():
		t.Fatal("Timed out waiting for child policy to receive an update")
	}

	// Call UpdateClientConnState again with the same configuration.