Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix De-duplication logic causes some sentinel error patterns to result in error chain losses #41 #43

Merged
merged 2 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions flatten.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package fault

import (
"errors"
"strings"
)

// Chain represents an unwound error chain. Each step is a useful error. Errors
Expand Down Expand Up @@ -70,19 +69,13 @@ func Flatten(err error) Chain {
default:
message := err.Error()

// de-duplicate nested error messages
// de-duplicate identical error messages
if next != nil {
if idx := strings.Index(message, next.Error()); idx != -1 {
// cut off the duplicate message and remove the separator.
message = strings.Trim(message[:idx], ": ")
if message == next.Error() {
continue
}
}

// the entire error message was a duplicate, skip.
if message == "" {
continue
}

f = append([]Step{{
Location: lastLocation,
Message: message,
Expand Down
24 changes: 0 additions & 24 deletions tests/fctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,30 +171,6 @@ func TestWithMetaDifferentMapAddress(t *testing.T) {
data1 := fctx.Unwrap(err1)
data2 := fctx.Unwrap(err2)

assert.Equal(t,
`&context.valueCtx{
Context: &context.emptyCtx(0),
key: fctx.contextKey{},
val: map[string]string{"key1":"value1"},
}`,
pretty.Sprint(ctx1),
"The map from the first context should be left unmodified by the second call to fctx.WithMeta",
)

assert.Equal(t,
`&context.valueCtx{
Context: &context.valueCtx{
Context: &context.emptyCtx(0),
key: fctx.contextKey{},
val: map[string]string{"key1":"value1"},
},
key: fctx.contextKey{},
val: map[string]string{"key1":"value1", "key2":"value2"},
}`,
pretty.Sprint(ctx2),
"The second context value should contain both maps and the first only contains the first key-value pair.",
)

assert.Equal(t,
`map[string]string{"key1":"value1"}`,
pretty.Sprint(data1),
Expand Down
16 changes: 8 additions & 8 deletions tests/flatten_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,15 @@ func TestFlattenStdlibErrorfWrappedError(t *testing.T) {
chain := fault.Flatten(err)
full := err.Error()

a.Equal("failed to call function: errorf wrapped: stdlib sentinel error", full)
a.Equal("failed to call function: errorf wrapped: stdlib sentinel error: stdlib sentinel error", full)
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.Equal("errorf wrapped: stdlib sentinel error", e1.Message)
a.Empty(e1.Location)

e2 := chain[2]
Expand All @@ -154,19 +154,19 @@ func TestFlattenStdlibErrorfWrappedExternalError(t *testing.T) {
chain := fault.Flatten(err)
full := err.Error()

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

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

e1 := chain[1]
a.Equal("external error wrapped with errorf", e1.Message)
a.Equal("external error wrapped with errorf: stdlib external error", e1.Message)
a.Empty(e1.Location)

e2 := chain[2]
a.Equal("errorf wrapped external", e2.Message)
a.Equal("errorf wrapped external: external error wrapped with errorf: stdlib external error", e2.Message)
a.Empty(e2.Location)

e3 := chain[3]
Expand Down Expand Up @@ -197,7 +197,7 @@ func TestFlattenStdlibErrorfWrappedExternallyWrappedError(t *testing.T) {
a.Empty(e0.Location)

e1 := chain[1]
a.Equal("external error wrapped with pkg/errors", e1.Message)
a.Equal("external error wrapped with pkg/errors: github.com/pkg/errors external error", e1.Message)
a.Empty(e1.Location)
}

Expand All @@ -209,14 +209,14 @@ func TestFlattenStdlibErrorfWrappedExternallyWrappedErrorBrokenChain(t *testing.
chain := fault.Flatten(err)
full := err.Error()

a.Equal("failed to query: external pg error: fatal: your sql was wrong bro (SQLSTATE 123)", full)
a.Equal("failed to query: external pg error: fatal: your sql was wrong bro (SQLSTATE 123): fatal: your sql was wrong bro (SQLSTATE 123)", full)
a.Len(chain, 3)

e0 := chain[0]
a.Equal("fatal: your sql was wrong bro (SQLSTATE 123)", e0.Message)

e1 := chain[1]
a.Equal("external pg error", e1.Message)
a.Equal("external pg error: fatal: your sql was wrong bro (SQLSTATE 123)", e1.Message)

e2 := chain[2]
a.Equal("failed to query", e2.Message)
Expand Down
66 changes: 0 additions & 66 deletions tests/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,72 +75,6 @@ failed to call function
`, fmt.Sprintf("%+v", err))
}

func TestFormatStdlibErrorfWrappedError(t *testing.T) {
a := assert.New(t)

err := errorCaller(5)

a.Equal("failed to call function: errorf wrapped: stdlib sentinel error", err.Error())
a.Equal("failed to call function: errorf wrapped: stdlib sentinel error", fmt.Sprintf("%s", err))
a.Equal("failed to call function: errorf wrapped: stdlib sentinel error", fmt.Sprintf("%v", err))
a.Regexp(`stdlib sentinel error
errorf wrapped
\s+.+fault/tests/test_callers.go:29
failed to call function
\s+.+fault/tests/test_callers.go:20
`, fmt.Sprintf("%+v", err))
}

func TestFormatStdlibErrorfWrappedExternalError(t *testing.T) {
a := assert.New(t)

err := errorCaller(6)

a.Equal("failed to call function: errorf wrapped external: external error wrapped with errorf: stdlib external error", err.Error())
a.Equal("failed to call function: errorf wrapped external: external error wrapped with errorf: stdlib external error", fmt.Sprintf("%s", err))
a.Equal("failed to call function: errorf wrapped external: external error wrapped with errorf: stdlib external error", fmt.Sprintf("%v", err))
a.Regexp(`stdlib external error
external error wrapped with errorf
errorf wrapped external
\s+.+fault/tests/test_callers.go:29
failed to call function
\s+.+fault/tests/test_callers.go:20
`, fmt.Sprintf("%+v", err))
}

func TestFormatStdlibErrorfWrappedExternallyWrappedError(t *testing.T) {
a := assert.New(t)

err := errorCaller(7)

a.Equal("failed to call function: errorf wrapped external: external error wrapped with pkg/errors: github.com/pkg/errors external error", err.Error())
a.Equal("failed to call function: errorf wrapped external: external error wrapped with pkg/errors: github.com/pkg/errors external error", fmt.Sprintf("%s", err))
a.Equal("failed to call function: errorf wrapped external: external error wrapped with pkg/errors: github.com/pkg/errors external error", fmt.Sprintf("%v", err))
a.Regexp(`github.com/pkg/errors external error
external error wrapped with pkg/errors
errorf wrapped external
\s+.+fault/tests/test_callers.go:29
failed to call function
\s+.+fault/tests/test_callers.go:20
`, fmt.Sprintf("%+v", err))
}

func TestFormatStdlibErrorfWrappedExternallyWrappedErrorBlank(t *testing.T) {
a := assert.New(t)

err := errorProducerFromRootCause(8)

a.Equal("external error wrapped with pkg/errors: github.com/pkg/errors external error", err.Error())
a.Equal("external error wrapped with pkg/errors: github.com/pkg/errors external error", fmt.Sprintf("%s", err))
a.Equal("external error wrapped with pkg/errors: github.com/pkg/errors external error", fmt.Sprintf("%v", err))

a.Regexp(
`github.com/pkg/errors external error
external error wrapped with pkg/errors
\s+.+fault/tests/test_callers.go:29
`, fmt.Sprintf("%+v", err))
}

func TestFormatStdlibSentinelErrorWrappedWithoutMessage(t *testing.T) {
a := assert.New(t)
ctx := context.Background()
Expand Down