fix(snapshots): ErrorEntry policy resolution to use child policy (#5234)

Fix ErrorEntry child policy resolution, includes tests.

- Fixes kopia/kopia#5232
This commit is contained in:
Baixiaochun
2026-03-21 08:05:51 +08:00
committed by GitHub
parent 66aa378203
commit df247ecf92
3 changed files with 137 additions and 3 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()