Retain discovered publication records when enumeration fails
This commit is contained in:
@@ -22,6 +22,10 @@ type AttemptsResult struct {
|
||||
}
|
||||
|
||||
func ListAttempts(ctx context.Context, repositoryDir, override string) (AttemptsResult, error) {
|
||||
return listAttempts(ctx, repositoryDir, override, recordPaths)
|
||||
}
|
||||
|
||||
func listAttempts(ctx context.Context, repositoryDir, override string, enumerate func(context.Context, string) ([]string, error)) (AttemptsResult, error) {
|
||||
if repositoryDir == "" {
|
||||
repositoryDir = "."
|
||||
}
|
||||
@@ -41,10 +45,7 @@ func ListAttempts(ctx context.Context, repositoryDir, override string) (Attempts
|
||||
if err := ctx.Err(); err != nil {
|
||||
return result, &RecoveryError{Kind: "canceled", cause: err}
|
||||
}
|
||||
paths, err := recordPaths(ctx, directory)
|
||||
if err != nil {
|
||||
return result, recoveryFailure(ctx, "storage_enumeration", fmt.Errorf("enumerate records in %q: %w", directory, err))
|
||||
}
|
||||
paths, enumerationErr := enumerate(ctx, directory)
|
||||
sort.Strings(paths)
|
||||
for _, path := range paths {
|
||||
if err := ctx.Err(); err != nil {
|
||||
@@ -65,6 +66,9 @@ func ListAttempts(ctx context.Context, repositoryDir, override string) (Attempts
|
||||
}
|
||||
result.Attempts = append(result.Attempts, Attempt{RecordPath: absolute, Evidence: evidence})
|
||||
}
|
||||
if enumerationErr != nil {
|
||||
return result, recoveryFailure(ctx, "storage_enumeration", fmt.Errorf("enumerate records in %q: %w", directory, enumerationErr))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -75,12 +79,16 @@ func recordPaths(ctx context.Context, directory string) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return readRecordPaths(ctx, directory, file.ReadDir)
|
||||
}
|
||||
|
||||
func readRecordPaths(ctx context.Context, directory string, read func(int) ([]os.DirEntry, error)) ([]string, error) {
|
||||
var paths []string
|
||||
for {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
entries, err := file.ReadDir(128)
|
||||
entries, err := read(128)
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".json") {
|
||||
paths = append(paths, filepath.Join(directory, entry.Name()))
|
||||
|
||||
@@ -431,6 +431,47 @@ func TestListAttemptsFiltersMixedDirectoryAcrossBatches(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAttemptsRetainsEntriesWithEnumerationFailure(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
for name, contents := range map[string]string{
|
||||
"z.json": `{"format_version":1,"evidence":{"tag":"v2.0.0"}}`,
|
||||
"a.json": `{"format_version":1,"evidence":{"tag":"v1.0.0"}}`,
|
||||
"broken.json": "{", "unrelated.txt": "ignored",
|
||||
} {
|
||||
if err := os.WriteFile(filepath.Join(directory, name), []byte(contents), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
entries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
failure := errors.New("controlled directory read failure")
|
||||
for _, empty := range []bool{false, true} {
|
||||
calls := 0
|
||||
listed, err := listAttempts(t.Context(), "", directory, func(ctx context.Context, dir string) ([]string, error) {
|
||||
return readRecordPaths(ctx, dir, func(int) ([]os.DirEntry, error) {
|
||||
calls++
|
||||
if empty {
|
||||
return nil, failure
|
||||
}
|
||||
return entries, failure
|
||||
})
|
||||
})
|
||||
var recovery *RecoveryError
|
||||
if !errors.As(err, &recovery) || recovery.Kind != "storage_enumeration" || !errors.Is(err, failure) || calls != 1 {
|
||||
t.Fatalf("enumeration failure lost: %#v, %v; reads=%d", listed, err, calls)
|
||||
}
|
||||
if empty {
|
||||
if len(listed.Attempts) != 0 || len(listed.Diagnostics) != 0 {
|
||||
t.Fatalf("empty read: %#v", listed)
|
||||
}
|
||||
} else if len(listed.Attempts) != 2 || len(listed.Diagnostics) != 1 || filepath.Base(listed.Attempts[0].RecordPath) != "a.json" || filepath.Base(listed.Attempts[1].RecordPath) != "z.json" || listed.Diagnostics[0].Kind != "record_format" {
|
||||
t.Fatalf("partial read dropped/reordered records: %#v", listed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAttemptsDistinguishesEmptyMissingAndCanceledStorage(t *testing.T) {
|
||||
empty := t.TempDir()
|
||||
listed, err := ListAttempts(t.Context(), "", empty)
|
||||
|
||||
Reference in New Issue
Block a user