From f982b13a59a59e22d1ff3fdf791c1aa86b9420df Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 3 Feb 2026 09:50:35 +0900 Subject: [PATCH] internal/pkg: improve error resolution This was taking way too long for early failures. Signed-off-by: Ophestra --- internal/pkg/pkg.go | 42 +++++++++++++++++++--------------------- internal/pkg/pkg_test.go | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go index 99772c8..76bae91 100644 --- a/internal/pkg/pkg.go +++ b/internal/pkg/pkg.go @@ -1188,39 +1188,38 @@ func (e *CureError) Error() string { return e.Err.Error() } // A DependencyCureError wraps errors returned while curing dependencies. type DependencyCureError []*CureError -// sort sorts underlying errors by their identifier. -func (e *DependencyCureError) sort() { - var identBuf [2]ID - slices.SortFunc(*e, func(a, b *CureError) int { - identBuf[0], identBuf[1] = a.Ident.Value(), b.Ident.Value() - return slices.Compare(identBuf[0][:], identBuf[1][:]) - }) +// unwrapM recursively expands underlying errors into a caller-supplied map. +func (e *DependencyCureError) unwrapM(me map[unique.Handle[ID]]*CureError) { + for _, err := range *e { + if _e, ok := err.Err.(*DependencyCureError); ok { + _e.unwrapM(me) + continue + } + me[err.Ident] = err + } } // unwrap recursively expands and deduplicates underlying errors. func (e *DependencyCureError) unwrap() DependencyCureError { - errs := make(DependencyCureError, 0, len(*e)) - for _, err := range *e { - if _e, ok := err.Err.(*DependencyCureError); ok { - errs = append(errs, _e.unwrap()...) - continue - } - errs = append(errs, err) - } - me := make(map[unique.Handle[ID]]*CureError, len(errs)) - for _, err := range errs { - me[err.Ident] = err - } - return slices.AppendSeq( + me := make(map[unique.Handle[ID]]*CureError) + e.unwrapM(me) + errs := slices.AppendSeq( make(DependencyCureError, 0, len(me)), maps.Values(me), ) + + var identBuf [2]ID + slices.SortFunc(errs, func(a, b *CureError) int { + identBuf[0], identBuf[1] = a.Ident.Value(), b.Ident.Value() + return slices.Compare(identBuf[0][:], identBuf[1][:]) + }) + + return errs } // Unwrap returns a deduplicated slice of underlying errors. func (e *DependencyCureError) Unwrap() []error { errs := e.unwrap() - errs.sort() _errs := make([]error, len(errs)) for i, err := range errs { _errs[i] = err @@ -1231,7 +1230,6 @@ func (e *DependencyCureError) Unwrap() []error { // Error returns a user-facing multiline error message. func (e *DependencyCureError) Error() string { errs := e.unwrap() - errs.sort() if len(errs) == 0 { return "invalid dependency cure outcome" } diff --git a/internal/pkg/pkg_test.go b/internal/pkg/pkg_test.go index 719bf71..d03cd83 100644 --- a/internal/pkg/pkg_test.go +++ b/internal/pkg/pkg_test.go @@ -15,6 +15,7 @@ import ( "os" "path/filepath" "reflect" + "strconv" "syscall" "testing" "unique" @@ -1137,6 +1138,40 @@ func TestDependencyCureError(t *testing.T) { } } +// earlyFailureF is a [FloodArtifact] with a large dependency graph resulting in +// a large [DependencyCureError]. +type earlyFailureF int + +func (earlyFailureF) Kind() pkg.Kind { return pkg.KindExec } +func (earlyFailureF) Params(*pkg.IContext) {} +func (earlyFailureF) IsExclusive() bool { return false } + +func (a earlyFailureF) Dependencies() []pkg.Artifact { + deps := make([]pkg.Artifact, a) + for i := range deps { + deps[i] = a - 1 + } + return deps +} + +func (a earlyFailureF) Cure(*pkg.FContext) error { + if a != 0 { + panic("unexpected cure on " + strconv.Itoa(int(a))) + } + return stub.UniqueError(0xcafe) +} + +func TestDependencyCureErrorEarly(t *testing.T) { + checkWithCache(t, []cacheTestCase{ + {"early", nil, func(t *testing.T, _ *check.Absolute, c *pkg.Cache) { + _, _, err := c.Cure(earlyFailureF(8)) + if !errors.Is(err, stub.UniqueError(0xcafe)) { + t.Fatalf("Cure: error = %v", err) + } + }, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")}, + }) +} + func TestNew(t *testing.T) { t.Parallel()