From 3113ed271d961be922c91db4a30e2cb1ce6d2cbd Mon Sep 17 00:00:00 2001 From: Abrar Shivani Date: Tue, 28 Jul 2026 16:08:54 -0700 Subject: [PATCH 1/2] Dedupe GetFilesWithSuffix and document hash struct precondition GetFilesWithSuffix appended a path once per matching suffix, so a file that matched more than one of the provided suffixes was returned multiple times. Break after the first matching suffix so each file is returned at most once; this is behavior-identical for non-overlapping suffixes (the only current caller passes yaml/yml/json). Add a test asserting the single result. Also document that GetObjectHashIgnoreEmptyKeys requires a struct (or pointer to struct) and panics on other kinds, matching what all callers already pass. Signed-off-by: Abrar Shivani --- internal/utils/getfiles_dedup_test.go | 36 +++++++++++++++++++++++++++ internal/utils/utils.go | 4 +++ 2 files changed, 40 insertions(+) create mode 100644 internal/utils/getfiles_dedup_test.go diff --git a/internal/utils/getfiles_dedup_test.go b/internal/utils/getfiles_dedup_test.go new file mode 100644 index 0000000000..80ad316040 --- /dev/null +++ b/internal/utils/getfiles_dedup_test.go @@ -0,0 +1,36 @@ +/** +# Copyright (c) NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package utils + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetFilesWithSuffix_MatchesMultipleSuffixes(t *testing.T) { + dir := t.TempDir() + f := filepath.Join(dir, "archive.tar.gz") + require.NoError(t, os.WriteFile(f, []byte("x"), 0o600)) + + got, err := GetFilesWithSuffix(dir, ".gz", ".tar.gz") + require.NoError(t, err) + assert.Equal(t, []string{f}, got) +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 27692f2711..5ea33617b2 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -53,6 +53,7 @@ func GetFilesWithSuffix(baseDir string, suffixes ...string) ([]string, error) { for _, s := range suffixes { if strings.HasSuffix(base, s) { files = append(files, path) + break } } return nil @@ -81,6 +82,9 @@ func GetObjectHash(obj interface{}) string { // GetObjectHashIgnoreEmptyKeys returns an FNV-32a hash of only the non-zero // fields of a struct. Adding a new zero-valued field will not change // the digest. Embedded structs are flattened. +// +// obj must be a struct or a pointer to a struct; other kinds (map, slice, +// scalar) or a nil pointer will panic, since only struct fields can be enumerated. func GetObjectHashIgnoreEmptyKeys(obj interface{}) string { hasher := fnv.New32a() hashNonZeroFields(hasher, reflect.Indirect(reflect.ValueOf(obj))) From 90b2ccd6d987d8a48bbaabcbedbfde7708147380 Mon Sep 17 00:00:00 2001 From: Abrar Shivani Date: Tue, 28 Jul 2026 16:33:23 -0700 Subject: [PATCH 2/2] Expand GetFilesWithSuffix dedupe test coverage Cover the break-on-first-match path fully: order-independence, single-of-many match, multiple overlapping-match files, non-matching exclusion, and recursion. Signed-off-by: Abrar Shivani --- internal/utils/getfiles_dedup_test.go | 78 ++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/internal/utils/getfiles_dedup_test.go b/internal/utils/getfiles_dedup_test.go index 80ad316040..c28e06a29d 100644 --- a/internal/utils/getfiles_dedup_test.go +++ b/internal/utils/getfiles_dedup_test.go @@ -25,12 +25,76 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetFilesWithSuffix_MatchesMultipleSuffixes(t *testing.T) { - dir := t.TempDir() - f := filepath.Join(dir, "archive.tar.gz") - require.NoError(t, os.WriteFile(f, []byte("x"), 0o600)) +func writeDedupFile(t *testing.T, base, rel string) { + t.Helper() + full := filepath.Join(base, rel) + require.NoError(t, os.MkdirAll(filepath.Dir(full), 0o755)) + require.NoError(t, os.WriteFile(full, []byte("x"), 0o600)) +} + +// TestGetFilesWithSuffix_Dedupe exercises the break after the first matching +// suffix: a file whose name ends with more than one requested suffix must be +// returned exactly once, regardless of suffix order or directory depth. +func TestGetFilesWithSuffix_Dedupe(t *testing.T) { + testCases := []struct { + name string + files []string + suffixes []string + want []string + }{ + { + name: "file matching two overlapping suffixes is returned once", + files: []string{"archive.tar.gz"}, + suffixes: []string{".gz", ".tar.gz"}, + want: []string{"archive.tar.gz"}, + }, + { + name: "dedupe is independent of suffix order", + files: []string{"archive.tar.gz"}, + suffixes: []string{".tar.gz", ".gz"}, + want: []string{"archive.tar.gz"}, + }, + { + name: "file matching exactly one of several suffixes is returned once", + files: []string{"config.json"}, + suffixes: []string{".yaml", ".yml", ".json"}, + want: []string{"config.json"}, + }, + { + name: "each of several overlapping-match files is returned once", + files: []string{"a.tar.gz", "b.tar.gz"}, + suffixes: []string{".gz", ".tar.gz"}, + want: []string{"a.tar.gz", "b.tar.gz"}, + }, + { + name: "non-matching files are excluded while overlapping matches dedupe", + files: []string{"keep.tar.gz", "skip.txt", "keep2.json"}, + suffixes: []string{".gz", ".tar.gz", ".json"}, + want: []string{"keep.tar.gz", "keep2.json"}, + }, + { + name: "dedupe applies to files in subdirectories", + files: []string{filepath.Join("sub", "nested.tar.gz")}, + suffixes: []string{".gz", ".tar.gz"}, + want: []string{filepath.Join("sub", "nested.tar.gz")}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + base := t.TempDir() + for _, rel := range tc.files { + writeDedupFile(t, base, rel) + } + want := make([]string, len(tc.want)) + for i, rel := range tc.want { + want[i] = filepath.Join(base, rel) + } - got, err := GetFilesWithSuffix(dir, ".gz", ".tar.gz") - require.NoError(t, err) - assert.Equal(t, []string{f}, got) + got, err := GetFilesWithSuffix(base, tc.suffixes...) + require.NoError(t, err) + // ElementsMatch also asserts multiplicity, so a duplicated path fails. + assert.ElementsMatch(t, want, got) + }) + } }