From 205a026bb192868955e732f321d09fd909b61947 Mon Sep 17 00:00:00 2001 From: Nikola Date: Wed, 3 Jul 2024 11:50:46 +0300 Subject: [PATCH] extend pod warning rules for overlapping paths --- pkg/api/pod/warnings.go | 166 +++++++++++------ pkg/api/pod/warnings_test.go | 343 +++++++++++++++++++++++++++++++++-- 2 files changed, 439 insertions(+), 70 deletions(-) diff --git a/pkg/api/pod/warnings.go b/pkg/api/pod/warnings.go index ea05b82ba31..58f80ac9813 100644 --- a/pkg/api/pod/warnings.go +++ b/pkg/api/pod/warnings.go @@ -19,6 +19,8 @@ package pod import ( "context" "fmt" + "os" + "sort" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -170,16 +172,11 @@ func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta } } - overlappingPathInContainers := warningsForOverlappingVirtualPaths(podSpec) + overlappingPathInContainers := warningsForOverlappingVirtualPaths(podSpec.Volumes) if len(overlappingPathInContainers) > 0 { warnings = append(warnings, overlappingPathInContainers...) } - overlappingPathInInitContainers := warningsForOverlappingVirtualPaths(podSpec) - if len(overlappingPathInInitContainers) > 0 { - warnings = append(warnings, overlappingPathInInitContainers...) - } - // duplicate hostAliases (#91670, #58477) if len(podSpec.HostAliases) > 1 { items := sets.New[string]() @@ -368,76 +365,84 @@ func warningsForWeightedPodAffinityTerms(terms []api.WeightedPodAffinityTerm, fi return warnings } -func warningsForOverlappingVirtualPaths(podSpec *api.PodSpec) []string { +// warningsForOverlappingVirtualPaths validates that there are no overlapping paths in single ConfigMapVolume, SecretVolume, DownwardAPIVolume and ProjectedVolume. +// A volume can try to load different keys to the same path which will result in overwriting of the value from the latest registered key +// Another possible scenario is when one of the path contains the other key path. Example: +// configMap: +// +// name: myconfig +// items: +// - key: key1 +// path: path +// - key: key2 +// path: path/path2 +// +// In such cases we either get `is directory` or 'file exists' error message. +func warningsForOverlappingVirtualPaths(volumes []api.Volume) []string { warnings := make([]string, 0) - for _, v := range podSpec.Volumes { + + for _, v := range volumes { if v.ConfigMap != nil && v.ConfigMap.Items != nil { - a := sets.NewString() - for _, item := range v.ConfigMap.Items { - if a.Has(item.Path) { - warnings = append(warnings, fmt.Sprintf("config map: %q - conflicting duplicate paths", v.ConfigMap.Name)) - } - a.Insert(item.Path) + w := checkVolumeMappingForOverlap(extractPaths(v.ConfigMap.Items), fmt.Sprintf("volume %q (ConfigMap %q): overlapping path", v.Name, v.ConfigMap.Name)) + if len(w) > 0 { + warnings = append(warnings, w...) } } if v.Secret != nil && v.Secret.Items != nil { - a := sets.NewString() - for _, item := range v.Secret.Items { - if a.Has(item.Path) { - warnings = append(warnings, fmt.Sprintf("secret: %q - conflicting duplicate paths", v.Secret.SecretName)) - } - a.Insert(item.Path) + w := checkVolumeMappingForOverlap(extractPaths(v.Secret.Items), fmt.Sprintf("volume %q (Secret %q): overlapping path", v.Name, v.Secret.SecretName)) + if len(w) > 0 { + warnings = append(warnings, w...) } } if v.DownwardAPI != nil && v.DownwardAPI.Items != nil { - a := sets.NewString() - for _, item := range v.DownwardAPI.Items { - if a.Has(item.Path) { - warnings = append(warnings, "downward api - conflicting duplicate paths") - } - a.Insert(item.Path) + w := checkVolumeMappingForOverlap(extractPathsDownwardAPI(v.DownwardAPI.Items), fmt.Sprintf("volume %q (DownwardAPI): overlapping path", v.Name)) + if len(w) > 0 { + warnings = append(warnings, w...) } } if v.Projected != nil { - a := sets.NewString() - for _, item := range v.Projected.Sources { - if item.ServiceAccountToken != nil { - a.Insert(item.ServiceAccountToken.Path) - } - } + var sourcePaths, allPaths []string + var errorMessage string - for _, item := range v.Projected.Sources { - if item.ConfigMap != nil && item.ConfigMap.Items != nil { - for _, item := range item.ConfigMap.Items { - if a.Has(item.Path) { - warnings = append(warnings, fmt.Sprintf("projected volume: %q - conflicting duplicate paths", v.Name)) - } + for _, source := range v.Projected.Sources { + switch { + case source.ConfigMap != nil && source.ConfigMap.Items != nil: + sourcePaths = extractPaths(source.ConfigMap.Items) + errorMessage = fmt.Sprintf("volume %q (Projected ConfigMap %q): overlapping path", v.Name, source.ConfigMap.Name) + case source.Secret != nil && source.Secret.Items != nil: + sourcePaths = extractPaths(source.Secret.Items) + errorMessage = fmt.Sprintf("volume %q (Projected Secret %q): overlapping path", v.Name, source.Secret.Name) + case source.DownwardAPI != nil && source.DownwardAPI.Items != nil: + sourcePaths = extractPathsDownwardAPI(source.DownwardAPI.Items) + errorMessage = fmt.Sprintf("volume %q (Projected DownwardAPI): overlapping path", v.Name) + case source.ServiceAccountToken != nil: + sourcePaths = []string{source.ServiceAccountToken.Path} + errorMessage = fmt.Sprintf("volume %q (Projected ServiceAccountToken): overlapping path", v.Name) + case source.ClusterTrustBundle != nil: + sourcePaths = []string{source.ClusterTrustBundle.Path} + if source.ClusterTrustBundle.Name != nil { + errorMessage = fmt.Sprintf("volume %q (Projected ClusterTrustBundle %q): overlapping path", v.Name, *source.ClusterTrustBundle.Name) + } else { + errorMessage = fmt.Sprintf("volume %q (Projected ClusterTrustBundle %q): overlapping path", v.Name, *source.ClusterTrustBundle.SignerName) } } - if item.Secret != nil && item.Secret.Items != nil { - for _, item := range item.Secret.Items { - if a.Has(item.Path) { - warnings = append(warnings, fmt.Sprintf("projected volume: %q - conflicting duplicate paths", v.Name)) - } - } + if len(sourcePaths) == 0 { + continue } - if item.DownwardAPI != nil && item.DownwardAPI.Items != nil { - for _, item := range item.DownwardAPI.Items { - if a.Has(item.Path) { - warnings = append(warnings, fmt.Sprintf("projected volume: %q - conflicting duplicate paths", v.Name)) - } - } + warningsInSource := checkVolumeMappingForOverlap(sourcePaths, errorMessage) + if len(warningsInSource) > 0 { + warnings = append(warnings, warningsInSource...) } - if item.ClusterTrustBundle != nil { - if a.Has(item.ClusterTrustBundle.Path) { - warnings = append(warnings, fmt.Sprintf("projected volume: %q - conflicting duplicate paths", v.Name)) - } + allPaths = append(allPaths, sourcePaths...) + warningsInAllPaths := checkVolumeMappingForOverlap(allPaths, errorMessage) + if len(warningsInAllPaths) > 0 { + warnings = append(warnings, warningsInAllPaths...) } } } @@ -445,3 +450,56 @@ func warningsForOverlappingVirtualPaths(podSpec *api.PodSpec) []string { } return warnings } + +func extractPaths(mapping []api.KeyToPath) []string { + result := make([]string, 0, len(mapping)) + + for _, v := range mapping { + result = append(result, v.Path) + } + return result +} + +func extractPathsDownwardAPI(mapping []api.DownwardAPIVolumeFile) []string { + result := make([]string, 0, len(mapping)) + + for _, v := range mapping { + result = append(result, v.Path) + } + return result +} + +func checkVolumeMappingForOverlap(paths []string, warningMessage string) []string { + pathSeparator := string(os.PathSeparator) + warnings := make([]string, 0) + uniquePaths := sets.New[string]() + + for _, path := range paths { + normalizedPath := strings.TrimRight(path, pathSeparator) + if collision := checkForOverlap(uniquePaths, normalizedPath); collision != nil { + warnings = append(warnings, fmt.Sprintf("%s %q with %q", warningMessage, normalizedPath, *collision)) + } + uniquePaths.Insert(normalizedPath) + } + + return warnings +} + +func checkForOverlap(paths sets.Set[string], path string) *string { + pathSeparator := string(os.PathSeparator) + p := paths.UnsortedList() + sort.Strings(p) + + for _, item := range p { + switch { + case item == path: + return &item + case strings.HasPrefix(item+pathSeparator, path): + return &item + case strings.HasPrefix(path+pathSeparator, item): + return &item + } + } + + return nil +} diff --git a/pkg/api/pod/warnings_test.go b/pkg/api/pod/warnings_test.go index 9fd678c4f14..9a70dc6261e 100644 --- a/pkg/api/pod/warnings_test.go +++ b/pkg/api/pod/warnings_test.go @@ -143,6 +143,7 @@ func TestWarnings(t *testing.T) { api.ResourceMemory: resource.MustParse("4m"), api.ResourceEphemeralStorage: resource.MustParse("4m"), } + testName := "Test" testcases := []struct { name string template *api.PodTemplateSpec @@ -240,6 +241,7 @@ func TestWarnings(t *testing.T) { template: &api.PodTemplateSpec{Spec: api.PodSpec{ Volumes: []api.Volume{ { + Name: "Test", VolumeSource: api.VolumeSource{ ConfigMap: &api.ConfigMapVolumeSource{ LocalObjectReference: api.LocalObjectReference{Name: "foo"}, @@ -253,7 +255,51 @@ func TestWarnings(t *testing.T) { }, }}, expected: []string{ - "config map: \"foo\" - conflicting duplicate paths", + "volume \"Test\" (ConfigMap \"foo\"): overlapping path \"test\" with \"test\"", + }, + }, + { + name: "overlapping paths in a configmap volume - try to mount dir path into a file", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "Test", + VolumeSource: api.VolumeSource{ + ConfigMap: &api.ConfigMapVolumeSource{ + LocalObjectReference: api.LocalObjectReference{Name: "foo"}, + Items: []api.KeyToPath{ + {Key: "foo", Path: "test"}, + {Key: "bar", Path: "test/app"}, + }, + }, + }, + }, + }, + }}, + expected: []string{ + "volume \"Test\" (ConfigMap \"foo\"): overlapping path \"test/app\" with \"test\"", + }, + }, + { + name: "overlapping paths in a configmap volume - try to mount file into a dir path", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "Test", + VolumeSource: api.VolumeSource{ + ConfigMap: &api.ConfigMapVolumeSource{ + LocalObjectReference: api.LocalObjectReference{Name: "foo"}, + Items: []api.KeyToPath{ + {Key: "bar", Path: "test/app"}, + {Key: "foo", Path: "test"}, + }, + }, + }, + }, + }, + }}, + expected: []string{ + "volume \"Test\" (ConfigMap \"foo\"): overlapping path \"test\" with \"test/app\"", }, }, { @@ -261,6 +307,7 @@ func TestWarnings(t *testing.T) { template: &api.PodTemplateSpec{Spec: api.PodSpec{ Volumes: []api.Volume{ { + Name: "Test", VolumeSource: api.VolumeSource{ Secret: &api.SecretVolumeSource{ SecretName: "foo", @@ -274,7 +321,7 @@ func TestWarnings(t *testing.T) { }, }}, expected: []string{ - "secret: \"foo\" - conflicting duplicate paths", + "volume \"Test\" (Secret \"foo\"): overlapping path \"test\" with \"test\"", }, }, { @@ -282,11 +329,12 @@ func TestWarnings(t *testing.T) { template: &api.PodTemplateSpec{Spec: api.PodSpec{ Volumes: []api.Volume{ { + Name: "Test", VolumeSource: api.VolumeSource{ DownwardAPI: &api.DownwardAPIVolumeSource{ Items: []api.DownwardAPIVolumeFile{ - {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name\n"}, Path: "test"}, - {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.labels\n"}, Path: "test"}, + {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"}, + {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.labels"}, Path: "test"}, }, }, }, @@ -294,11 +342,11 @@ func TestWarnings(t *testing.T) { }, }}, expected: []string{ - "downward api - conflicting duplicate paths", + "volume \"Test\" (DownwardAPI): overlapping path \"test\" with \"test\"", }, }, { - name: "overlapping paths in projected volume volume - service account and config", + name: "overlapping paths in projected volume - service account and config", template: &api.PodTemplateSpec{Spec: api.PodSpec{ Volumes: []api.Volume{ { @@ -307,6 +355,7 @@ func TestWarnings(t *testing.T) { Projected: &api.ProjectedVolumeSource{ Sources: []api.VolumeProjection{ {ConfigMap: &api.ConfigMapProjection{ + LocalObjectReference: api.LocalObjectReference{Name: "Test"}, Items: []api.KeyToPath{ {Key: "foo", Path: "test"}, }, @@ -321,11 +370,67 @@ func TestWarnings(t *testing.T) { }, }}, expected: []string{ - "projected volume: \"foo\" - conflicting duplicate paths", + "volume \"foo\" (Projected ServiceAccountToken): overlapping path \"test\" with \"test\"", }, }, { - name: "overlapping paths in projected volume volume - service account and secret", + name: "overlapping paths in projected volume volume - service account dir and config file", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{ + {ConfigMap: &api.ConfigMapProjection{ + LocalObjectReference: api.LocalObjectReference{Name: "Test"}, + Items: []api.KeyToPath{ + {Key: "foo", Path: "test"}, + }, + }}, + {ServiceAccountToken: &api.ServiceAccountTokenProjection{ + Path: "test/file", + }}, + }, + }, + }, + }, + }, + }}, + expected: []string{ + "volume \"foo\" (Projected ServiceAccountToken): overlapping path \"test/file\" with \"test\"", + }, + }, + { + name: "overlapping paths in projected volume - service account file and config dir", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{ + {ConfigMap: &api.ConfigMapProjection{ + LocalObjectReference: api.LocalObjectReference{Name: "Test"}, + Items: []api.KeyToPath{ + {Key: "foo", Path: "test/file"}, + }, + }}, + {ServiceAccountToken: &api.ServiceAccountTokenProjection{ + Path: "test", + }}, + }, + }, + }, + }, + }, + }}, + expected: []string{ + "volume \"foo\" (Projected ServiceAccountToken): overlapping path \"test\" with \"test/file\"", + }, + }, + { + name: "overlapping paths in projected volume - service account and secret", template: &api.PodTemplateSpec{Spec: api.PodSpec{ Volumes: []api.Volume{ { @@ -334,10 +439,12 @@ func TestWarnings(t *testing.T) { Projected: &api.ProjectedVolumeSource{ Sources: []api.VolumeProjection{ {Secret: &api.SecretProjection{ + LocalObjectReference: api.LocalObjectReference{Name: "Test"}, Items: []api.KeyToPath{ {Key: "foo", Path: "test"}, }, - }}, + }, + }, {ServiceAccountToken: &api.ServiceAccountTokenProjection{ Path: "test", }}, @@ -348,11 +455,11 @@ func TestWarnings(t *testing.T) { }, }}, expected: []string{ - "projected volume: \"foo\" - conflicting duplicate paths", + "volume \"foo\" (Projected ServiceAccountToken): overlapping path \"test\" with \"test\"", }, }, { - name: "overlapping paths in projected volume volume - service account and downward api", + name: "overlapping paths in projected volume - service account and downward api", template: &api.PodTemplateSpec{Spec: api.PodSpec{ Volumes: []api.Volume{ { @@ -362,7 +469,7 @@ func TestWarnings(t *testing.T) { Sources: []api.VolumeProjection{ {DownwardAPI: &api.DownwardAPIProjection{ Items: []api.DownwardAPIVolumeFile{ - {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name\n"}, Path: "test"}, + {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"}, }, }}, {ServiceAccountToken: &api.ServiceAccountTokenProjection{ @@ -375,11 +482,11 @@ func TestWarnings(t *testing.T) { }, }}, expected: []string{ - "projected volume: \"foo\" - conflicting duplicate paths", + "volume \"foo\" (Projected ServiceAccountToken): overlapping path \"test\" with \"test\"", }, }, { - name: "overlapping paths in projected volume volume - service account and cluster trust bundle", + name: "overlapping paths in projected volume - service account and cluster trust bundle", template: &api.PodTemplateSpec{Spec: api.PodSpec{ Volumes: []api.Volume{ { @@ -388,7 +495,7 @@ func TestWarnings(t *testing.T) { Projected: &api.ProjectedVolumeSource{ Sources: []api.VolumeProjection{ {ClusterTrustBundle: &api.ClusterTrustBundleProjection{ - Path: "test", + Name: &testName, Path: "test", }}, {ServiceAccountToken: &api.ServiceAccountTokenProjection{ Path: "test", @@ -400,7 +507,178 @@ func TestWarnings(t *testing.T) { }, }}, expected: []string{ - "projected volume: \"foo\" - conflicting duplicate paths", + "volume \"foo\" (Projected ServiceAccountToken): overlapping path \"test\" with \"test\"", + }, + }, + { + name: "overlapping paths in projected volume - service account and cluster trust bundle with signer name", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{ + {ClusterTrustBundle: &api.ClusterTrustBundleProjection{ + SignerName: &testName, Path: "test", + }}, + {ServiceAccountToken: &api.ServiceAccountTokenProjection{ + Path: "test", + }}, + }, + }, + }, + }, + }, + }}, + expected: []string{ + "volume \"foo\" (Projected ServiceAccountToken): overlapping path \"test\" with \"test\"", + }, + }, + { + name: "overlapping paths in projected volume - secret and config map", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{ + {Secret: &api.SecretProjection{ + LocalObjectReference: api.LocalObjectReference{Name: "TestSecret"}, + Items: []api.KeyToPath{ + { + Key: "mykey", + Path: "test", + }, + }, + }}, + { + ConfigMap: &api.ConfigMapProjection{ + LocalObjectReference: api.LocalObjectReference{Name: "TestConfigMap"}, + Items: []api.KeyToPath{ + { + Key: "mykey", + Path: "test/test1", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }}, + expected: []string{ + "volume \"foo\" (Projected ConfigMap \"TestConfigMap\"): overlapping path \"test/test1\" with \"test\"", + }, + }, + { + name: "overlapping paths in projected volume - config map and downward api", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{ + {Secret: &api.SecretProjection{ + LocalObjectReference: api.LocalObjectReference{Name: "TestSecret"}, + Items: []api.KeyToPath{ + { + Key: "mykey", + Path: "test", + }, + }, + }}, + { + DownwardAPI: &api.DownwardAPIProjection{ + Items: []api.DownwardAPIVolumeFile{ + {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test/test2"}, + }, + }, + }, + }, + }, + }, + }, + }, + }}, + expected: []string{ + "volume \"foo\" (Projected DownwardAPI): overlapping path \"test/test2\" with \"test\"", + }, + }, + { + name: "overlapping paths in projected volume - downward api and downward api", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{ + { + DownwardAPI: &api.DownwardAPIProjection{ + Items: []api.DownwardAPIVolumeFile{ + {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test/test2"}, + }, + }, + }, + { + ClusterTrustBundle: &api.ClusterTrustBundleProjection{ + Name: &testName, Path: "test", + }, + }, + }, + }, + }, + }, + }, + }}, + expected: []string{ + "volume \"foo\" (Projected ClusterTrustBundle \"Test\"): overlapping path \"test\" with \"test/test2\"", + }, + }, + { + name: "overlapping paths in projected volume - multiple sources", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{ + {ClusterTrustBundle: &api.ClusterTrustBundleProjection{ + SignerName: &testName, Path: "test/test", + }}, + {DownwardAPI: &api.DownwardAPIProjection{ + Items: []api.DownwardAPIVolumeFile{ + {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"}, + }, + }}, + {Secret: &api.SecretProjection{ + LocalObjectReference: api.LocalObjectReference{Name: "Test"}, + Items: []api.KeyToPath{ + {Key: "foo", Path: "test"}, + }, + }, + }, + {ServiceAccountToken: &api.ServiceAccountTokenProjection{ + Path: "test", + }}, + }, + }, + }, + }, + }, + }}, + expected: []string{ + "volume \"foo\" (Projected DownwardAPI): overlapping path \"test\" with \"test/test\"", + "volume \"foo\" (Projected Secret \"Test\"): overlapping path \"test\" with \"test\"", + "volume \"foo\" (Projected Secret \"Test\"): overlapping path \"test\" with \"test/test\"", + "volume \"foo\" (Projected ServiceAccountToken): overlapping path \"test\" with \"test/test\"", + "volume \"foo\" (Projected ServiceAccountToken): overlapping path \"test\" with \"test\"", }, }, { @@ -1351,3 +1629,36 @@ func TestTemplateOnlyWarnings(t *testing.T) { }) } } + +func TestCheckForOverLap(t *testing.T) { + testCase := map[string]struct { + checkPaths []string + path string + expected string + }{ + "exact match": { + checkPaths: []string{"path/path1"}, + path: "path/path1", + expected: "path/path1", + }, + "between file and dir": { + checkPaths: []string{"path/path1"}, + path: "path", + expected: "path/path1", + }, + "between dir and file": { + checkPaths: []string{"path"}, + path: "path/path1", + expected: "path", + }, + } + + for name, tc := range testCase { + t.Run(name, func(t *testing.T) { + result := checkForOverlap(sets.New(tc.checkPaths...), tc.path) + if *result != tc.expected { + t.Errorf("expected %v, got %v", tc.expected, *result) + } + }) + } +}