diff --git a/internal/xds/balancer/clusterresolver/configbuilder_childname.go b/internal/xds/balancer/clusterresolver/configbuilder_childname.go index 296ed740e401..209d7a244b0b 100644 --- a/internal/xds/balancer/clusterresolver/configbuilder_childname.go +++ b/internal/xds/balancer/clusterresolver/configbuilder_childname.go @@ -37,15 +37,17 @@ type nameGenerator struct { } func newNameGenerator(prefix uint64) *nameGenerator { - return &nameGenerator{prefix: prefix} + return &nameGenerator{ + prefix: prefix, + existingNames: make(map[clients.Locality]string), + } } // generate returns a list of names for the given list of priorities. // // Each priority is a list of localities. The name for the priority is picked as -// - for each locality in this priority, if it exists in the existing names, -// this priority will reuse the name -// - if no reusable name is found for this priority, a new name is generated +// - A new name is generated for each locality upon they are seen for the first time, +// for each priority all corresponding names are collected in order as candidates and first unused name is picked for reuse // // For example: // - update 1: [[L1], [L2], [L3]] --> ["0", "1", "2"] @@ -55,34 +57,37 @@ func newNameGenerator(prefix uint64) *nameGenerator { func (ng *nameGenerator) generate(priorities [][]xdsresource.Locality) []string { var ret []string usedNames := make(map[string]bool) - newNames := make(map[clients.Locality]string) for _, priority := range priorities { - var nameFound string + var candidates []string for _, locality := range priority { - if name, ok := ng.existingNames[locality.ID]; ok { - if !usedNames[name] { - nameFound = name - // Found a name to use. No need to process the remaining - // localities. - break - } + name, exists := ng.existingNames[locality.ID] + if !exists { + name = ng.generateNewName() + ng.existingNames[locality.ID] = name } + candidates = append(candidates, name) } - if nameFound == "" { - // No appropriate used name is found. Make a new name. - nameFound = fmt.Sprintf("priority-%d-%d", ng.prefix, ng.nextID) - ng.nextID++ + var chosenName string + for _, candidate := range candidates { + if !usedNames[candidate] { + chosenName = candidate + break + } } - - ret = append(ret, nameFound) - // All localities in this priority share the same name. Add them all to - // the new map. - for _, l := range priority { - newNames[l.ID] = nameFound + if chosenName == "" { + // All candidate names are used, generate a new name. + chosenName = ng.generateNewName() } - usedNames[nameFound] = true + + ret = append(ret, chosenName) + usedNames[chosenName] = true } - ng.existingNames = newNames return ret } + +func (ng *nameGenerator) generateNewName() string { + name := fmt.Sprintf("priority-%d-%d", ng.prefix, ng.nextID) + ng.nextID++ + return name +} diff --git a/internal/xds/balancer/clusterresolver/configbuilder_childname_test.go b/internal/xds/balancer/clusterresolver/configbuilder_childname_test.go index c5abcd8fe929..1067184ffe17 100644 --- a/internal/xds/balancer/clusterresolver/configbuilder_childname_test.go +++ b/internal/xds/balancer/clusterresolver/configbuilder_childname_test.go @@ -29,80 +29,420 @@ func Test_nameGenerator_generate(t *testing.T) { tests := []struct { name string prefix uint64 - input1 [][]xdsresource.Locality - input2 [][]xdsresource.Locality + inputs [][][]xdsresource.Locality // Array of input steps want []string }{ { name: "init, two new priorities", prefix: 3, - input1: nil, - input2: [][]xdsresource.Locality{ - {{ID: clients.Locality{Zone: "L0"}}}, - {{ID: clients.Locality{Zone: "L1"}}}, + inputs: [][][]xdsresource.Locality{ + nil, // first input is nil + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, }, want: []string{"priority-3-0", "priority-3-1"}, }, { name: "one new priority", prefix: 1, - input1: [][]xdsresource.Locality{ - {{ID: clients.Locality{Zone: "L0"}}}, - }, - input2: [][]xdsresource.Locality{ - {{ID: clients.Locality{Zone: "L0"}}}, - {{ID: clients.Locality{Zone: "L1"}}}, + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, }, want: []string{"priority-1-0", "priority-1-1"}, }, { name: "merge two priorities", prefix: 4, - input1: [][]xdsresource.Locality{ - {{ID: clients.Locality{Zone: "L0"}}}, - {{ID: clients.Locality{Zone: "L1"}}}, - {{ID: clients.Locality{Zone: "L2"}}}, - }, - input2: [][]xdsresource.Locality{ - {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, - {{ID: clients.Locality{Zone: "L2"}}}, + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, }, want: []string{"priority-4-0", "priority-4-2"}, }, { name: "swap two priorities", - input1: [][]xdsresource.Locality{ - {{ID: clients.Locality{Zone: "L0"}}}, - {{ID: clients.Locality{Zone: "L1"}}}, - {{ID: clients.Locality{Zone: "L2"}}}, - }, - input2: [][]xdsresource.Locality{ - {{ID: clients.Locality{Zone: "L1"}}}, - {{ID: clients.Locality{Zone: "L0"}}}, - {{ID: clients.Locality{Zone: "L2"}}}, + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, }, want: []string{"priority-0-1", "priority-0-0", "priority-0-2"}, }, { name: "split priority", - input1: [][]xdsresource.Locality{ - {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, - {{ID: clients.Locality{Zone: "L2"}}}, + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, // This gets a newly generated name, since "0-0" was already picked. + {{ID: clients.Locality{Zone: "L2"}}}, + }, + }, + want: []string{"priority-0-0", "priority-0-1", "priority-0-2"}, + }, + { + name: "split and reverse priority", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L0"}}}, + }, + }, + want: []string{"priority-0-1", "priority-0-0"}, + }, + { + name: "delete and bring back", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + }, + want: []string{"priority-0-0"}, + }, + { + name: "delete and bring back reverse", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L2"}}, {ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L1"}}}, + }, + }, + want: []string{"priority-0-2"}, + }, + { + name: "complex merge split sequence", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + }, + want: []string{"priority-0-0", "priority-0-1", "priority-0-2"}, + }, + { + name: "complex full merges splits sequence", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}, + {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}, + {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}, + {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + }, + want: []string{"priority-0-0", "priority-0-1", "priority-0-2"}, + }, + { + name: "merge-split reverse", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L0"}}}, + }, + }, + want: []string{"priority-0-1", "priority-0-0"}, + }, + { + name: "merge-split reverse times 2", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L0"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L0"}}}, + }, + }, + want: []string{"priority-0-1", "priority-0-0"}, + }, + { + name: "complex merge-split reverse", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}, {ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L3"}}, {ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L3"}}}, + {{ID: clients.Locality{Zone: "L0"}}}, + }, + }, + want: []string{"priority-0-3", "priority-0-0"}, + }, + { + name: "2 by 2 shuffle sequence", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}, {ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}, + {ID: clients.Locality{Zone: "L2"}}, {ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L3"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}, {ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L3"}}}, + }, + }, + want: []string{"priority-0-0", "priority-0-1", "priority-0-2", "priority-0-3"}, + }, + { + name: "traffic director proxyless example", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L3"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L3"}}, {ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L3"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L3"}}, {ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, }, - input2: [][]xdsresource.Locality{ - {{ID: clients.Locality{Zone: "L0"}}}, - {{ID: clients.Locality{Zone: "L1"}}}, // This gets a newly generated name, since "0-0" was already picked. - {{ID: clients.Locality{Zone: "L2"}}}, + want: []string{"priority-0-3", "priority-0-1"}, + }, + { + name: "defensive test - same zone in multiple priorities", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L0"}}}, // Same zone in different priority + }, + }, + want: []string{"priority-0-0", "priority-0-1"}, + }, + { + name: "defensive test - zone conflict cascade", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L2"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L1"}}}, // Both L0 and L1 conflict + }, }, - want: []string{"priority-0-0", "priority-0-2", "priority-0-1"}, + want: []string{"priority-0-1", "priority-0-0", "priority-0-3"}, + }, + { + name: "defensive test - complex zone duplication across multiple priorities", + inputs: [][][]xdsresource.Locality{ + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}}, + }, + { + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L0"}}, {ID: clients.Locality{Zone: "L3"}}}, + }, + { + {{ID: clients.Locality{Zone: "L1"}}}, + {{ID: clients.Locality{Zone: "L0"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L2"}}}, + {{ID: clients.Locality{Zone: "L1"}}, {ID: clients.Locality{Zone: "L3"}}}, + }, + }, + want: []string{"priority-0-1", "priority-0-0", "priority-0-2", "priority-0-3"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ng := newNameGenerator(tt.prefix) - got1 := ng.generate(tt.input1) - t.Logf("%v", got1) - got := ng.generate(tt.input2) + var got []string + + // Execute all input steps + for i, input := range tt.inputs { + got = ng.generate(input) + t.Logf("Step %d: %v", i+1, got) + } + + // The final result should match expected if diff := cmp.Diff(got, tt.want); diff != "" { t.Errorf("generate() = got: %v, want: %v, diff (-got +want): %s", got, tt.want, diff) } diff --git a/internal/xds/balancer/clusterresolver/configbuilder_test.go b/internal/xds/balancer/clusterresolver/configbuilder_test.go index 571b756484e2..f90c2a80f657 100644 --- a/internal/xds/balancer/clusterresolver/configbuilder_test.go +++ b/internal/xds/balancer/clusterresolver/configbuilder_test.go @@ -259,7 +259,7 @@ func TestBuildPriorityConfig(t *testing.T) { }, IgnoreReresolutionRequests: true, }, - "priority-0-1": { + "priority-0-2": { Config: &iserviceconfig.BalancerConfig{ Name: outlierdetection.Name, Config: &outlierdetection.LBConfig{ @@ -299,7 +299,7 @@ func TestBuildPriorityConfig(t *testing.T) { IgnoreReresolutionRequests: false, }, }, - Priorities: []string{"priority-0-0", "priority-0-1", "priority-1"}, + Priorities: []string{"priority-0-0", "priority-0-2", "priority-1"}, } if diff := cmp.Diff(gotConfig, wantConfig); diff != "" { t.Errorf("buildPriorityConfig() diff (-got +want) %v", diff) @@ -388,7 +388,7 @@ func TestBuildClusterImplConfigForEDS(t *testing.T) { wantNames := []string{ fmt.Sprintf("priority-%v-%v", 2, 0), - fmt.Sprintf("priority-%v-%v", 2, 1), + fmt.Sprintf("priority-%v-%v", 2, 2), } wantConfigs := map[string]*clusterimpl.LBConfig{ "priority-2-0": { @@ -403,7 +403,7 @@ func TestBuildClusterImplConfigForEDS(t *testing.T) { }, }, }, - "priority-2-1": { + "priority-2-2": { Cluster: testClusterName, EDSServiceName: testEDSServiceName, LoadReportingServer: testLRSServerConfig, @@ -421,10 +421,10 @@ func TestBuildClusterImplConfigForEDS(t *testing.T) { testEndpointWithAttrs(testEndpoints[0][1].Addresses, 20, 1, "priority-2-0", &testLocalityIDs[0]), testEndpointWithAttrs(testEndpoints[1][0].Addresses, 80, 1, "priority-2-0", &testLocalityIDs[1]), testEndpointWithAttrs(testEndpoints[1][1].Addresses, 80, 1, "priority-2-0", &testLocalityIDs[1]), - testEndpointWithAttrs(testEndpoints[2][0].Addresses, 20, 1, "priority-2-1", &testLocalityIDs[2]), - testEndpointWithAttrs(testEndpoints[2][1].Addresses, 20, 1, "priority-2-1", &testLocalityIDs[2]), - testEndpointWithAttrs(testEndpoints[3][0].Addresses, 80, 1, "priority-2-1", &testLocalityIDs[3]), - testEndpointWithAttrs(testEndpoints[3][1].Addresses, 80, 1, "priority-2-1", &testLocalityIDs[3]), + testEndpointWithAttrs(testEndpoints[2][0].Addresses, 20, 1, "priority-2-2", &testLocalityIDs[2]), + testEndpointWithAttrs(testEndpoints[2][1].Addresses, 20, 1, "priority-2-2", &testLocalityIDs[2]), + testEndpointWithAttrs(testEndpoints[3][0].Addresses, 80, 1, "priority-2-2", &testLocalityIDs[3]), + testEndpointWithAttrs(testEndpoints[3][1].Addresses, 80, 1, "priority-2-2", &testLocalityIDs[3]), } if diff := cmp.Diff(gotNames, wantNames); diff != "" {