diff --git a/internal/mockfs/mockfs.go b/internal/mockfs/mockfs.go index 1a485201f..7b8459a98 100644 --- a/internal/mockfs/mockfs.go +++ b/internal/mockfs/mockfs.go @@ -204,7 +204,7 @@ func (imd *Directory) AddDir(name string, permissions os.FileMode) *Directory { return subdir } -// AddErrorEntry adds a fake directory with a given name and permissions. +// AddErrorEntry adds a fake directory-typed error entry with a given name and permissions. func (imd *Directory) AddErrorEntry(name string, permissions os.FileMode, err error) *ErrorEntry { imd, name = imd.resolveSubdir(name) @@ -222,6 +222,24 @@ func (imd *Directory) AddErrorEntry(name string, permissions os.FileMode, err er return ee } +// AddFileErrorEntry adds a fake file-typed error entry with a given name and permissions. +func (imd *Directory) AddFileErrorEntry(name string, permissions os.FileMode, err error) *ErrorEntry { + imd, name = imd.resolveSubdir(name) + + ee := &ErrorEntry{ + entry: entry{ + name: name, + mode: permissions &^ os.ModeDir, + modTime: DefaultModTime, + }, + err: err, + } + + imd.addChild(ee) + + return ee +} + // AddDirDevice adds a fake directory with a given name and permissions. func (imd *Directory) AddDirDevice(name string, permissions os.FileMode, deviceInfo fs.DeviceInfo) *Directory { imd, name = imd.resolveSubdir(name) diff --git a/snapshot/upload/upload.go b/snapshot/upload/upload.go index a9be13533..da18c0f8b 100644 --- a/snapshot/upload/upload.go +++ b/snapshot/upload/upload.go @@ -928,8 +928,13 @@ func (u *Uploader) processSingle( prefix string ) + // Use the child policy for the specific entry path, not the parent directory policy. + // This ensures per-entry error handling rules are respected, consistent with how + // directory processing derives childTree via policyTree.Child(). + childPolicy := policyTree.Child(entry.Name()).EffectivePolicy() + if errors.Is(entry.ErrorInfo(), fs.ErrUnknown) { - isIgnoredError = policyTree.EffectivePolicy().ErrorHandlingPolicy.IgnoreUnknownTypes.OrDefault(true) + isIgnoredError = childPolicy.ErrorHandlingPolicy.IgnoreUnknownTypes.OrDefault(true) // If unknown types are configured to be ignored, skip them completely without any error reporting if isIgnoredError { @@ -939,7 +944,7 @@ func (u *Uploader) processSingle( prefix = "unknown entry" } else { prefix = "error" - ehp := policyTree.EffectivePolicy().ErrorHandlingPolicy + ehp := childPolicy.ErrorHandlingPolicy if entry.IsDir() { isIgnoredError = ehp.IgnoreDirectoryErrors.OrDefault(false) diff --git a/snapshot/upload/upload_test.go b/snapshot/upload/upload_test.go index 5e1491b3b..fdee296b1 100644 --- a/snapshot/upload/upload_test.go +++ b/snapshot/upload/upload_test.go @@ -520,6 +520,117 @@ func TestUpload_ErrorEntries(t *testing.T) { } } +func TestUpload_ErrorEntryChildPolicy(t *testing.T) { + t.Parallel() + + ctx := testlogging.Context(t) + th := newUploadTestHarness(ctx, t) + + t.Cleanup(th.cleanup) + + // Add a dir-typed error entry, a file-typed error entry, and an unknown-typed error entry under d1. + th.sourceDir.Subdir("d1").AddErrorEntry("dir-err", 0, errors.New("dir-error")) + th.sourceDir.Subdir("d1").AddFileErrorEntry("file-err", 0, errors.New("file-error")) + th.sourceDir.Subdir("d1").AddErrorEntry("unknown-err", os.ModeIrregular, fs.ErrUnknown) + + trueValue := policy.OptionalBool(true) + falseValue := policy.OptionalBool(false) + + cases := []struct { + desc string + defined map[string]*policy.Policy + defaultPolicy *policy.Policy + wantFatalErrors int + wantIgnoredErrors int + wantErrors entryPathToError + }{ + { + desc: "child policy ignores dir errors only", + defined: map[string]*policy.Policy{ + "./d1/dir-err": { + ErrorHandlingPolicy: policy.ErrorHandlingPolicy{ + IgnoreDirectoryErrors: &trueValue, + IgnoreFileErrors: &falseValue, + }, + }, + }, + defaultPolicy: &policy.Policy{ + ErrorHandlingPolicy: policy.ErrorHandlingPolicy{ + IgnoreDirectoryErrors: &falseValue, + IgnoreFileErrors: &falseValue, + }, + }, + wantFatalErrors: 1, // file-err is fatal (uses default policy) + wantIgnoredErrors: 1, // dir-err is ignored (uses child policy) + // unknown-err is silently skipped (IgnoreUnknownTypes defaults to true) + wantErrors: entryPathToError{ + "d1/dir-err": errors.New("dir-error"), + "d1/file-err": errors.New("file-error"), + }, + }, + { + desc: "child policy ignores file errors only", + defined: map[string]*policy.Policy{ + "./d1/file-err": { + ErrorHandlingPolicy: policy.ErrorHandlingPolicy{ + IgnoreDirectoryErrors: &falseValue, + IgnoreFileErrors: &trueValue, + }, + }, + }, + defaultPolicy: &policy.Policy{ + ErrorHandlingPolicy: policy.ErrorHandlingPolicy{ + IgnoreDirectoryErrors: &falseValue, + IgnoreFileErrors: &falseValue, + }, + }, + wantFatalErrors: 1, // dir-err is fatal (uses default policy) + wantIgnoredErrors: 1, // file-err is ignored (uses child policy) + // unknown-err is silently skipped (IgnoreUnknownTypes defaults to true) + wantErrors: entryPathToError{ + "d1/dir-err": errors.New("dir-error"), + "d1/file-err": errors.New("file-error"), + }, + }, + { + desc: "child policy disables unknown type ignore", + defined: map[string]*policy.Policy{ + "./d1/unknown-err": { + ErrorHandlingPolicy: policy.ErrorHandlingPolicy{ + IgnoreUnknownTypes: &falseValue, + }, + }, + }, + defaultPolicy: &policy.Policy{ + ErrorHandlingPolicy: policy.ErrorHandlingPolicy{ + IgnoreDirectoryErrors: &trueValue, + IgnoreFileErrors: &trueValue, + }, + }, + wantFatalErrors: 1, // unknown-err is fatal (child policy overrides default) + wantIgnoredErrors: 2, // dir-err and file-err are ignored (default policy) + wantErrors: entryPathToError{ + "d1/dir-err": errors.New("dir-error"), + "d1/file-err": errors.New("file-error"), + "d1/unknown-err": fs.ErrUnknown, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + u := NewUploader(th.repo) + + policyTree := policy.BuildTree(tc.defined, tc.defaultPolicy) + + man, err := u.Upload(ctx, th.sourceDir, policyTree, snapshot.SourceInfo{}) + require.NoError(t, err) + + verifyErrors(t, man, tc.wantFatalErrors, tc.wantIgnoredErrors, tc.wantErrors) + }) + } +} + func verifyErrors(t *testing.T, man *snapshot.Manifest, wantFatalErrors, wantIgnoredErrors int, wantErrors entryPathToError) { t.Helper()