From 8cf18d1b5c50a4605f004264de83f7e085fd2116 Mon Sep 17 00:00:00 2001 From: bells17 Date: Wed, 8 May 2024 16:18:26 +0900 Subject: [PATCH] [pkg/volume] Changed to use sets.Set[string] instead of sets.String --- .../desired_state_of_world_populator.go | 2 +- .../csi/nodeinfomanager/nodeinfomanager.go | 16 +++++----- pkg/volume/plugins.go | 4 +-- pkg/volume/testing/testing.go | 6 ++-- pkg/volume/testing/volume_host.go | 4 +-- pkg/volume/util/atomic_writer.go | 18 +++++------ pkg/volume/util/atomic_writer_test.go | 26 ++++++++-------- pkg/volume/util/nested_volumes_test.go | 16 +++++----- pkg/volume/util/util.go | 10 +++---- pkg/volume/util/util_test.go | 30 +++++++++---------- 10 files changed, 66 insertions(+), 66 deletions(-) diff --git a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go index 1987c032722..2d9b07a6021 100644 --- a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go +++ b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go @@ -455,7 +455,7 @@ func (dswp *desiredStateOfWorldPopulator) deleteProcessedPod( // specified volume. It dereference any PVC to get PV objects, if needed. // Returns an error if unable to obtain the volume at this time. func (dswp *desiredStateOfWorldPopulator) createVolumeSpec( - podVolume v1.Volume, pod *v1.Pod, mounts, devices sets.String) (*v1.PersistentVolumeClaim, *volume.Spec, string, error) { + podVolume v1.Volume, pod *v1.Pod, mounts, devices sets.Set[string]) (*v1.PersistentVolumeClaim, *volume.Spec, string, error) { pvcSource := podVolume.VolumeSource.PersistentVolumeClaim isEphemeral := pvcSource == nil && podVolume.VolumeSource.Ephemeral != nil if isEphemeral { diff --git a/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go b/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go index 89b02258702..da62be547fe 100644 --- a/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go +++ b/pkg/volume/csi/nodeinfomanager/nodeinfomanager.go @@ -474,16 +474,16 @@ func setMigrationAnnotation(migratedPlugins map[string](func() bool), nodeInfo * nodeInfoAnnotations = map[string]string{} } - var oldAnnotationSet sets.String + var oldAnnotationSet sets.Set[string] mpa := nodeInfoAnnotations[v1.MigratedPluginsAnnotationKey] tok := strings.Split(mpa, ",") if len(mpa) == 0 { - oldAnnotationSet = sets.NewString() + oldAnnotationSet = sets.New[string]() } else { - oldAnnotationSet = sets.NewString(tok...) + oldAnnotationSet = sets.New[string](tok...) } - newAnnotationSet := sets.NewString() + newAnnotationSet := sets.New[string]() for pluginName, migratedFunc := range migratedPlugins { if migratedFunc() { newAnnotationSet.Insert(pluginName) @@ -494,7 +494,7 @@ func setMigrationAnnotation(migratedPlugins map[string](func() bool), nodeInfo * return false } - nas := strings.Join(newAnnotationSet.List(), ",") + nas := strings.Join(sets.List[string](newAnnotationSet), ",") if len(nas) != 0 { nodeInfoAnnotations[v1.MigratedPluginsAnnotationKey] = nas } else { @@ -526,7 +526,7 @@ func (nim *nodeInfoManager) installDriverToCSINode( return fmt.Errorf("error getting CSI client") } - topologyKeys := sets.StringKeySet(topology) + topologyKeys := sets.KeySet[string, string](topology) specModified := true // Clone driver list, omitting the driver that matches the given driverName @@ -534,7 +534,7 @@ func (nim *nodeInfoManager) installDriverToCSINode( for _, driverInfoSpec := range nodeInfo.Spec.Drivers { if driverInfoSpec.Name == driverName { if driverInfoSpec.NodeID == driverNodeID && - sets.NewString(driverInfoSpec.TopologyKeys...).Equal(topologyKeys) && + sets.New[string](driverInfoSpec.TopologyKeys...).Equal(topologyKeys) && keepAllocatableCount(driverInfoSpec, maxAttachLimit) { specModified = false } @@ -554,7 +554,7 @@ func (nim *nodeInfoManager) installDriverToCSINode( driverSpec := storagev1.CSINodeDriver{ Name: driverName, NodeID: driverNodeID, - TopologyKeys: topologyKeys.List(), + TopologyKeys: sets.List[string](topologyKeys), } if maxAttachLimit > 0 { diff --git a/pkg/volume/plugins.go b/pkg/volume/plugins.go index 399ca814c26..d28dce7ded9 100644 --- a/pkg/volume/plugins.go +++ b/pkg/volume/plugins.go @@ -425,7 +425,7 @@ type VolumePluginMgr struct { plugins map[string]VolumePlugin prober DynamicPluginProber probedPlugins map[string]VolumePlugin - loggedDeprecationWarnings sets.String + loggedDeprecationWarnings sets.Set[string] Host VolumeHost } @@ -567,7 +567,7 @@ func (pm *VolumePluginMgr) InitPlugins(plugins []VolumePlugin, prober DynamicPlu defer pm.mutex.Unlock() pm.Host = host - pm.loggedDeprecationWarnings = sets.NewString() + pm.loggedDeprecationWarnings = sets.New[string]() if prober == nil { // Use a dummy prober to prevent nil deference. diff --git a/pkg/volume/testing/testing.go b/pkg/volume/testing/testing.go index 51bc943b258..961ba6ea66c 100644 --- a/pkg/volume/testing/testing.go +++ b/pkg/volume/testing/testing.go @@ -231,7 +231,7 @@ func (plugin *FakeVolumePlugin) getFakeVolume(list *[]*FakeVolume) *FakeVolume { WaitForAttachHook: plugin.WaitForAttachHook, UnmountDeviceHook: plugin.UnmountDeviceHook, } - volume.VolumesAttached = make(map[string]sets.String) + volume.VolumesAttached = make(map[string]sets.Set[string]) volume.DeviceMountState = make(map[string]string) volume.VolumeMountState = make(map[string]string) if list != nil { @@ -679,7 +679,7 @@ type FakeVolume struct { VolName string Plugin *FakeVolumePlugin volume.MetricsNil - VolumesAttached map[string]sets.String + VolumesAttached map[string]sets.Set[string] DeviceMountState map[string]string VolumeMountState map[string]string @@ -1021,7 +1021,7 @@ func (fv *FakeVolume) Attach(spec *volume.Spec, nodeName types.NodeName) (string return "", fmt.Errorf("volume %q trying to attach to node %q is already attached to node %q", volumeName, nodeName, volumeNodes) } - fv.VolumesAttached[volumeName] = sets.NewString(string(nodeName)) + fv.VolumesAttached[volumeName] = sets.New[string](string(nodeName)) if nodeName == UncertainAttachNode || nodeName == TimeoutAttachNode { return "", fmt.Errorf("timed out to attach volume %q to node %q", volumeName, nodeName) } diff --git a/pkg/volume/testing/volume_host.go b/pkg/volume/testing/volume_host.go index 43fde87bab8..5ed3a786c0a 100644 --- a/pkg/volume/testing/volume_host.go +++ b/pkg/volume/testing/volume_host.go @@ -296,9 +296,9 @@ func enableMigrationOnNode(csiNode *storagev1.CSINode, pluginName string) { nodeInfoAnnotations = map[string]string{} } - newAnnotationSet := sets.NewString() + newAnnotationSet := sets.New[string]() newAnnotationSet.Insert(pluginName) - nas := strings.Join(newAnnotationSet.List(), ",") + nas := strings.Join(sets.List(newAnnotationSet), ",") nodeInfoAnnotations[v1.MigratedPluginsAnnotationKey] = nas csiNode.Annotations = nodeInfoAnnotations diff --git a/pkg/volume/util/atomic_writer.go b/pkg/volume/util/atomic_writer.go index 3ac4dcffe9a..bdac9914ca5 100644 --- a/pkg/volume/util/atomic_writer.go +++ b/pkg/volume/util/atomic_writer.go @@ -158,7 +158,7 @@ func (w *AtomicWriter) Write(payload map[string]FileProjection, setPerms func(su } oldTsPath := filepath.Join(w.targetDir, oldTsDir) - var pathsToRemove sets.String + var pathsToRemove sets.Set[string] shouldWrite := true // if there was no old version, there's nothing to remove if len(oldTsDir) != 0 { @@ -355,10 +355,10 @@ func shouldWriteFile(path string, content []byte) (bool, error) { // pathsToRemove walks the current version of the data directory and // determines which paths should be removed (if any) after the payload is // written to the target directory. -func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection, oldTsDir string) (sets.String, error) { - paths := sets.NewString() +func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection, oldTSDir string) (sets.Set[string], error) { + paths := sets.New[string]() visitor := func(path string, info os.FileInfo, err error) error { - relativePath := strings.TrimPrefix(path, oldTsDir) + relativePath := strings.TrimPrefix(path, oldTSDir) relativePath = strings.TrimPrefix(relativePath, string(os.PathSeparator)) if relativePath == "" { return nil @@ -368,15 +368,15 @@ func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection, oldTsDir return nil } - err := filepath.Walk(oldTsDir, visitor) + err := filepath.Walk(oldTSDir, visitor) if os.IsNotExist(err) { return nil, nil } else if err != nil { return nil, err } - klog.V(5).Infof("%s: current paths: %+v", w.targetDir, paths.List()) + klog.V(5).Infof("%s: current paths: %+v", w.targetDir, sets.List(paths)) - newPaths := sets.NewString() + newPaths := sets.New[string]() for file := range payload { // add all subpaths for the payload to the set of new paths // to avoid attempting to remove non-empty dirs @@ -386,7 +386,7 @@ func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection, oldTsDir subPath = strings.TrimSuffix(subPath, string(os.PathSeparator)) } } - klog.V(5).Infof("%s: new paths: %+v", w.targetDir, newPaths.List()) + klog.V(5).Infof("%s: new paths: %+v", w.targetDir, sets.List(newPaths)) result := paths.Difference(newPaths) klog.V(5).Infof("%s: paths to remove: %+v", w.targetDir, result) @@ -487,7 +487,7 @@ func (w *AtomicWriter) createUserVisibleFiles(payload map[string]FileProjection) // removeUserVisiblePaths removes the set of paths from the user-visible // portion of the writer's target directory. -func (w *AtomicWriter) removeUserVisiblePaths(paths sets.String) error { +func (w *AtomicWriter) removeUserVisiblePaths(paths sets.Set[string]) error { ps := string(os.PathSeparator) var lasterr error for p := range paths { diff --git a/pkg/volume/util/atomic_writer_test.go b/pkg/volume/util/atomic_writer_test.go index 9261962e47b..dfe01d92f5b 100644 --- a/pkg/volume/util/atomic_writer_test.go +++ b/pkg/volume/util/atomic_writer_test.go @@ -133,7 +133,7 @@ func TestPathsToRemove(t *testing.T) { name string payload1 map[string]FileProjection payload2 map[string]FileProjection - expected sets.String + expected sets.Set[string] }{ { name: "simple", @@ -144,7 +144,7 @@ func TestPathsToRemove(t *testing.T) { payload2: map[string]FileProjection{ "foo.txt": {Mode: 0644, Data: []byte("foo")}, }, - expected: sets.NewString("bar.txt"), + expected: sets.New[string]("bar.txt"), }, { name: "simple 2", @@ -155,7 +155,7 @@ func TestPathsToRemove(t *testing.T) { payload2: map[string]FileProjection{ "foo.txt": {Mode: 0644, Data: []byte("foo")}, }, - expected: sets.NewString("zip/bar.txt", "zip"), + expected: sets.New[string]("zip/bar.txt", "zip"), }, { name: "subdirs 1", @@ -166,7 +166,7 @@ func TestPathsToRemove(t *testing.T) { payload2: map[string]FileProjection{ "foo.txt": {Mode: 0644, Data: []byte("foo")}, }, - expected: sets.NewString("zip/zap/bar.txt", "zip", "zip/zap"), + expected: sets.New[string]("zip/zap/bar.txt", "zip", "zip/zap"), }, { name: "subdirs 2", @@ -177,7 +177,7 @@ func TestPathsToRemove(t *testing.T) { payload2: map[string]FileProjection{ "foo.txt": {Mode: 0644, Data: []byte("foo")}, }, - expected: sets.NewString("zip/1/2/3/4/bar.txt", "zip", "zip/1", "zip/1/2", "zip/1/2/3", "zip/1/2/3/4"), + expected: sets.New[string]("zip/1/2/3/4/bar.txt", "zip", "zip/1", "zip/1/2", "zip/1/2/3", "zip/1/2/3/4"), }, { name: "subdirs 3", @@ -189,7 +189,7 @@ func TestPathsToRemove(t *testing.T) { payload2: map[string]FileProjection{ "foo.txt": {Mode: 0644, Data: []byte("foo")}, }, - expected: sets.NewString("zip/1/2/3/4/bar.txt", "zip", "zip/1", "zip/1/2", "zip/1/2/3", "zip/1/2/3/4", "zap", "zap/a", "zap/a/b", "zap/a/b/c", "zap/a/b/c/bar.txt"), + expected: sets.New[string]("zip/1/2/3/4/bar.txt", "zip", "zip/1", "zip/1/2", "zip/1/2/3", "zip/1/2/3/4", "zap", "zap/a", "zap/a/b", "zap/a/b/c", "zap/a/b/c/bar.txt"), }, { name: "subdirs 4", @@ -203,7 +203,7 @@ func TestPathsToRemove(t *testing.T) { "foo.txt": {Mode: 0644, Data: []byte("foo")}, "zap/1/2/magic.txt": {Mode: 0644, Data: []byte("indigo")}, }, - expected: sets.NewString("zap/1/2/3/4/bar.txt", "zap/1/2/3", "zap/1/2/3/4", "zap/1/2/3/4/bar.txt", "zap/1/2/c", "zap/1/2/c/bar.txt"), + expected: sets.New[string]("zap/1/2/3/4/bar.txt", "zap/1/2/3", "zap/1/2/3/4", "zap/1/2/3/4/bar.txt", "zap/1/2/c", "zap/1/2/c/bar.txt"), }, { name: "subdirs 5", @@ -216,7 +216,7 @@ func TestPathsToRemove(t *testing.T) { "foo.txt": {Mode: 0644, Data: []byte("foo")}, "zap/1/2/magic.txt": {Mode: 0644, Data: []byte("indigo")}, }, - expected: sets.NewString("zap/1/2/3/4/bar.txt", "zap/1/2/3", "zap/1/2/3/4", "zap/1/2/3/4/bar.txt", "zap/1/2/c", "zap/1/2/c/bar.txt"), + expected: sets.New[string]("zap/1/2/3/4/bar.txt", "zap/1/2/3", "zap/1/2/3/4", "zap/1/2/3/4/bar.txt", "zap/1/2/c", "zap/1/2/c/bar.txt"), }, } @@ -818,7 +818,7 @@ func TestValidatePayload(t *testing.T) { cases := []struct { name string payload map[string]FileProjection - expected sets.String + expected sets.Set[string] valid bool }{ { @@ -828,7 +828,7 @@ func TestValidatePayload(t *testing.T) { "bar": {}, }, valid: true, - expected: sets.NewString("foo", "bar"), + expected: sets.New[string]("foo", "bar"), }, { name: "payload with path length > 4096 is invalid", @@ -871,11 +871,11 @@ func TestValidatePayload(t *testing.T) { "foo////bar": {}, }, valid: true, - expected: sets.NewString("foo/bar"), + expected: sets.New[string]("foo/bar"), }, } - getPayloadPaths := func(payload map[string]FileProjection) sets.String { - paths := sets.NewString() + getPayloadPaths := func(payload map[string]FileProjection) sets.Set[string] { + paths := sets.New[string]() for path := range payload { paths.Insert(path) } diff --git a/pkg/volume/util/nested_volumes_test.go b/pkg/volume/util/nested_volumes_test.go index 9e23106e9dc..936af115ce7 100644 --- a/pkg/volume/util/nested_volumes_test.go +++ b/pkg/volume/util/nested_volumes_test.go @@ -21,7 +21,7 @@ import ( "path/filepath" "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" @@ -30,7 +30,7 @@ import ( type testCases struct { name string err bool - expected sets.String + expected sets.Set[string] volname string pod v1.Pod } @@ -45,7 +45,7 @@ func TestGetNestedMountpoints(t *testing.T) { { name: "Simple Pod", err: false, - expected: sets.NewString(), + expected: sets.New[string](), volname: "vol1", pod: v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -66,7 +66,7 @@ func TestGetNestedMountpoints(t *testing.T) { { name: "Simple Nested Pod", err: false, - expected: sets.NewString("nested"), + expected: sets.New[string]("nested"), volname: "vol1", pod: v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -88,7 +88,7 @@ func TestGetNestedMountpoints(t *testing.T) { { name: "Unsorted Nested Pod", err: false, - expected: sets.NewString("nested", "nested2", "nested-vol", "nested.vol"), + expected: sets.New[string]("nested", "nested2", "nested-vol", "nested.vol"), volname: "vol1", pod: v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -117,7 +117,7 @@ func TestGetNestedMountpoints(t *testing.T) { { name: "Multiple vol1 mounts Pod", err: false, - expected: sets.NewString("nested", "nested2"), + expected: sets.New[string]("nested", "nested2"), volname: "vol1", pod: v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -143,7 +143,7 @@ func TestGetNestedMountpoints(t *testing.T) { name: "Big Pod", err: false, volname: "vol1", - expected: sets.NewString(filepath.Join("sub1", "sub2", "sub3"), filepath.Join("sub1", "sub2", "sub4"), filepath.Join("sub1", "sub2", "sub6"), "sub"), + expected: sets.New[string](filepath.Join("sub1", "sub2", "sub3"), filepath.Join("sub1", "sub2", "sub4"), filepath.Join("sub1", "sub2", "sub6"), "sub"), pod: v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: testNamespace, @@ -227,7 +227,7 @@ func TestGetNestedMountpoints(t *testing.T) { continue } } - actual := sets.NewString(dirs...) + actual := sets.New[string](dirs...) if !test.expected.Equal(actual) { t.Errorf("%v: unexpected nested directories created:\nexpected: %v\n got: %v", test.name, test.expected, actual) } diff --git a/pkg/volume/util/util.go b/pkg/volume/util/util.go index 54869386a83..0f14eae4113 100644 --- a/pkg/volume/util/util.go +++ b/pkg/volume/util/util.go @@ -259,7 +259,7 @@ func MountOptionFromSpec(spec *volume.Spec, options ...string) []string { // JoinMountOptions joins mount options eliminating duplicates func JoinMountOptions(userOptions []string, systemOptions []string) []string { - allMountOptions := sets.NewString() + allMountOptions := sets.New[string]() for _, mountOption := range userOptions { if len(mountOption) > 0 { @@ -270,7 +270,7 @@ func JoinMountOptions(userOptions []string, systemOptions []string) []string { for _, mountOption := range systemOptions { allMountOptions.Insert(mountOption) } - return allMountOptions.List() + return sets.List(allMountOptions) } // ContainsAccessMode returns whether the requested mode is contained by modes @@ -612,9 +612,9 @@ func GetLocalPersistentVolumeNodeNames(pv *v1.PersistentVolume) []string { // GetPodVolumeNames returns names of volumes that are used in a pod, // either as filesystem mount or raw block device, together with list // of all SELinux contexts of all containers that use the volumes. -func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String, seLinuxContainerContexts map[string][]*v1.SELinuxOptions) { - mounts = sets.NewString() - devices = sets.NewString() +func GetPodVolumeNames(pod *v1.Pod) (mounts sets.Set[string], devices sets.Set[string], seLinuxContainerContexts map[string][]*v1.SELinuxOptions) { + mounts = sets.New[string]() + devices = sets.New[string]() seLinuxContainerContexts = make(map[string][]*v1.SELinuxOptions) podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(container *v1.Container, containerType podutil.ContainerType) bool { diff --git a/pkg/volume/util/util_test.go b/pkg/volume/util/util_test.go index 8036c514dc9..79be63f54f0 100644 --- a/pkg/volume/util/util_test.go +++ b/pkg/volume/util/util_test.go @@ -666,8 +666,8 @@ func TestGetPodVolumeNames(t *testing.T) { tests := []struct { name string pod *v1.Pod - expectedMounts sets.String - expectedDevices sets.String + expectedMounts sets.Set[string] + expectedDevices sets.Set[string] expectedSELinuxContexts map[string][]*v1.SELinuxOptions }{ { @@ -675,8 +675,8 @@ func TestGetPodVolumeNames(t *testing.T) { pod: &v1.Pod{ Spec: v1.PodSpec{}, }, - expectedMounts: sets.NewString(), - expectedDevices: sets.NewString(), + expectedMounts: sets.New[string](), + expectedDevices: sets.New[string](), }, { name: "pod with volumes", @@ -719,8 +719,8 @@ func TestGetPodVolumeNames(t *testing.T) { }, }, }, - expectedMounts: sets.NewString("vol1", "vol2"), - expectedDevices: sets.NewString("vol3", "vol4"), + expectedMounts: sets.New[string]("vol1", "vol2"), + expectedDevices: sets.New[string]("vol3", "vol4"), }, { name: "pod with init containers", @@ -763,8 +763,8 @@ func TestGetPodVolumeNames(t *testing.T) { }, }, }, - expectedMounts: sets.NewString("vol1", "vol2"), - expectedDevices: sets.NewString("vol3", "vol4"), + expectedMounts: sets.New[string]("vol1", "vol2"), + expectedDevices: sets.New[string]("vol3", "vol4"), }, { name: "pod with multiple containers", @@ -822,8 +822,8 @@ func TestGetPodVolumeNames(t *testing.T) { }, }, }, - expectedMounts: sets.NewString("vol1", "vol3"), - expectedDevices: sets.NewString("vol2", "vol4"), + expectedMounts: sets.New[string]("vol1", "vol3"), + expectedDevices: sets.New[string]("vol2", "vol4"), }, { name: "pod with ephemeral containers", @@ -864,8 +864,8 @@ func TestGetPodVolumeNames(t *testing.T) { }, }, }, - expectedMounts: sets.NewString("vol1", "vol2"), - expectedDevices: sets.NewString(), + expectedMounts: sets.New[string]("vol1", "vol2"), + expectedDevices: sets.New[string](), }, { name: "pod with SELinuxOptions", @@ -937,7 +937,7 @@ func TestGetPodVolumeNames(t *testing.T) { }, }, }, - expectedMounts: sets.NewString("vol1", "vol2", "vol3"), + expectedMounts: sets.New[string]("vol1", "vol2", "vol3"), expectedSELinuxContexts: map[string][]*v1.SELinuxOptions{ "vol1": { { @@ -973,10 +973,10 @@ func TestGetPodVolumeNames(t *testing.T) { t.Run(test.name, func(t *testing.T) { mounts, devices, contexts := GetPodVolumeNames(test.pod) if !mounts.Equal(test.expectedMounts) { - t.Errorf("Expected mounts: %q, got %q", mounts.List(), test.expectedMounts.List()) + t.Errorf("Expected mounts: %q, got %q", sets.List[string](mounts), sets.List[string](test.expectedMounts)) } if !devices.Equal(test.expectedDevices) { - t.Errorf("Expected devices: %q, got %q", devices.List(), test.expectedDevices.List()) + t.Errorf("Expected devices: %q, got %q", sets.List[string](devices), sets.List[string](test.expectedDevices)) } if len(contexts) == 0 { contexts = nil