Skip to content

Commit

Permalink
fix: issue 28 wrong location of some errors
Browse files Browse the repository at this point in the history
  • Loading branch information
matdurand authored and Southclaws committed Jun 5, 2023
1 parent 17181ef commit 1be749f
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .gitignore
@@ -1 +1 @@
.idea
.idea/
11 changes: 11 additions & 0 deletions fault.go
Expand Up @@ -21,6 +21,17 @@ func Wrap(err error, w ...Wrapper) error {
return nil
}

// the passed err might already have a location if it's a fault.New error, or it might not if it's another type of
// error like one from the standard library. Wrapping it in a container with an empty location ensures that the
// location will be reset when we flatten the error chain. If the error is a fault.New error, it will itself be
// wrapped in a container which will have a location
if _, ok := err.(*container); !ok {
err = &container{
cause: err,
location: "",
}
}

for _, fn := range w {
err = fn(err)
}
Expand Down
10 changes: 8 additions & 2 deletions flatten.go
Expand Up @@ -49,6 +49,14 @@ func Flatten(err error) Chain {
// exist to contain other errors that actually contain information,
// store the container's recorded location for usage with the next item.
case *container:
if _, ok := next.(*container); ok && unwrapped.location != "" {
// Having 2 containers back to back can happen if we're using .Wrap without using any wrappers. In that
// case, we add a Step to avoid losing the location whe the wrapping occurred
f = append([]Step{{
Location: unwrapped.location,
Message: "",
}}, f...)
}
lastLocation = unwrapped.location

case *fundamental:
Expand Down Expand Up @@ -79,8 +87,6 @@ func Flatten(err error) Chain {
Location: lastLocation,
Message: message,
}}, f...)

lastLocation = ""
}
}

Expand Down
92 changes: 70 additions & 22 deletions tests/flatten_test.go
Expand Up @@ -16,15 +16,23 @@ func TestFlattenStdlibSentinelError(t *testing.T) {
full := err.Error()

a.Equal("failed to call function: stdlib sentinel error", full)
a.Len(chain, 2)
a.Len(chain, 4)

e0 := chain[0]
a.Equal("stdlib sentinel error", e0.Message)
a.Contains(e0.Location, "test_callers.go:29")
a.Empty(e0.Location)

e1 := chain[1]
a.Equal("failed to call function", e1.Message)
a.Contains(e1.Location, "test_callers.go:20")
a.Empty(e1.Message)
a.Contains(e1.Location, "test_callers.go:29")

e2 := chain[2]
a.Equal("failed to call function", e2.Message)
a.Contains(e2.Location, "test_callers.go:20")

e3 := chain[3]
a.Empty(e3.Message)
a.Contains(e3.Location, "test_callers.go:11")
}

func TestFlattenFaultSentinelError(t *testing.T) {
Expand All @@ -35,15 +43,23 @@ func TestFlattenFaultSentinelError(t *testing.T) {
full := err.Error()

a.Equal("failed to call function: fault sentinel error", full)
a.Len(chain, 2)
a.Len(chain, 4)

e0 := chain[0]
a.Equal("fault sentinel error", e0.Message)
a.Contains(e0.Location, "root.go:15")

e1 := chain[1]
a.Equal("failed to call function", e1.Message)
a.Contains(e1.Location, "test_callers.go:20")
a.Empty(e1.Message)
a.Contains(e1.Location, "test_callers.go:29")

e2 := chain[2]
a.Equal("failed to call function", e2.Message)
a.Contains(e2.Location, "test_callers.go:20")

e3 := chain[3]
a.Empty(e3.Message)
a.Contains(e3.Location, "test_callers.go:11")
}

func TestFlattenStdlibInlineError(t *testing.T) {
Expand All @@ -54,15 +70,23 @@ func TestFlattenStdlibInlineError(t *testing.T) {
full := err.Error()

a.Equal("failed to call function: stdlib root cause error", full)
a.Len(chain, 2)
a.Len(chain, 4)

e0 := chain[0]
a.Equal("stdlib root cause error", e0.Message)
a.Contains(e0.Location, "test_callers.go:29")
a.Empty(e0.Location)

e1 := chain[1]
a.Equal("failed to call function", e1.Message)
a.Contains(e1.Location, "test_callers.go:20")
a.Empty(e1.Message)
a.Contains(e1.Location, "test_callers.go:29")

e2 := chain[2]
a.Equal("failed to call function", e2.Message)
a.Contains(e2.Location, "test_callers.go:20")

e3 := chain[3]
a.Empty(e3.Message)
a.Contains(e3.Location, "test_callers.go:11")
}

func TestFlattenFaultInlineError(t *testing.T) {
Expand All @@ -73,15 +97,23 @@ func TestFlattenFaultInlineError(t *testing.T) {
full := err.Error()

a.Equal("failed to call function: fault root cause error", full)
a.Len(chain, 2)
a.Len(chain, 4)

e0 := chain[0]
a.Equal("fault root cause error", e0.Message)
a.Contains(e0.Location, "root.go:28")

e1 := chain[1]
a.Equal("failed to call function", e1.Message)
a.Contains(e1.Location, "test_callers.go:20")
a.Empty(e1.Message)
a.Contains(e1.Location, "test_callers.go:29")

e2 := chain[2]
a.Equal("failed to call function", e2.Message)
a.Contains(e2.Location, "test_callers.go:20")

e3 := chain[3]
a.Empty(e3.Message)
a.Contains(e3.Location, "test_callers.go:11")
}

func TestFlattenStdlibErrorfWrappedError(t *testing.T) {
Expand All @@ -92,19 +124,27 @@ func TestFlattenStdlibErrorfWrappedError(t *testing.T) {
full := err.Error()

a.Equal("failed to call function: errorf wrapped: stdlib sentinel error", full)
a.Len(chain, 3)
a.Len(chain, 5)

e0 := chain[0]
a.Equal("stdlib sentinel error", e0.Message)
a.Empty(e0.Location)

e1 := chain[1]
a.Equal("errorf wrapped", e1.Message)
a.Contains(e1.Location, "test_callers.go:29")
a.Empty(e1.Location)

e2 := chain[2]
a.Equal("failed to call function", e2.Message)
a.Contains(e2.Location, "test_callers.go:20")
a.Empty(e2.Message)
a.Contains(e2.Location, "test_callers.go:29")

e3 := chain[3]
a.Equal("failed to call function", e3.Message)
a.Contains(e3.Location, "test_callers.go:20")

e4 := chain[4]
a.Empty(e4.Message)
a.Contains(e4.Location, "test_callers.go:11")
}

func TestFlattenStdlibErrorfWrappedExternalError(t *testing.T) {
Expand All @@ -115,7 +155,7 @@ func TestFlattenStdlibErrorfWrappedExternalError(t *testing.T) {
full := err.Error()

a.Equal("failed to call function: errorf wrapped external: external error wrapped with errorf: stdlib external error", full)
a.Len(chain, 4)
a.Len(chain, 6)

e0 := chain[0]
a.Equal("stdlib external error", e0.Message)
Expand All @@ -127,11 +167,19 @@ func TestFlattenStdlibErrorfWrappedExternalError(t *testing.T) {

e2 := chain[2]
a.Equal("errorf wrapped external", e2.Message)
a.Contains(e2.Location, "test_callers.go:29")
a.Empty(e2.Location)

e3 := chain[3]
a.Equal("failed to call function", e3.Message)
a.Contains(e3.Location, "test_callers.go:20")
a.Empty(e3.Message)
a.Contains(e3.Location, "test_callers.go:29")

e4 := chain[4]
a.Equal("failed to call function", e4.Message)
a.Contains(e4.Location, "test_callers.go:20")

e5 := chain[5]
a.Empty(e5.Message)
a.Contains(e5.Location, "test_callers.go:11")
}

func TestFlattenStdlibErrorfWrappedExternallyWrappedError(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions tests/format_test.go
Expand Up @@ -36,8 +36,10 @@ func TestFormatFaultSentinelError(t *testing.T) {
a.Equal("failed to call function: fault sentinel error", fmt.Sprintf("%v", err))
a.Regexp(`fault sentinel error
\s+.+fault/tests/root.go:15
\s+.+fault/tests/test_callers.go:29
failed to call function
\s+.+fault/tests/test_callers.go:20
\s+.+fault/tests/test_callers.go:11
`, fmt.Sprintf("%+v", err))
}

Expand Down Expand Up @@ -66,8 +68,10 @@ func TestFormatFaultInlineError(t *testing.T) {
a.Equal("failed to call function: fault root cause error", fmt.Sprintf("%v", err))
a.Regexp(`fault root cause error
\s+.+fault/tests/root.go:28
\s+.+fault/tests/test_callers.go:29
failed to call function
\s+.+fault/tests/test_callers.go:20
\s+.+fault/tests/test_callers.go:11
`, fmt.Sprintf("%+v", err))
}

Expand Down

0 comments on commit 1be749f

Please sign in to comment.