From df1b34cea880a373256dbc3385e2ba9df82b1cc5 Mon Sep 17 00:00:00 2001 From: Maxim Panfilov Date: Thu, 17 Oct 2024 18:23:22 +0300 Subject: [PATCH] simplify the interface --- LICENSE | 21 + README.md | 111 +++- stacktrace.go | 300 +++++++---- stacktrace_test.go | 1237 +++++++++++++++++++++++++++++++++----------- traces.go | 20 +- traces_test.go | 176 +++---- 6 files changed, 1349 insertions(+), 516 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..49c2e16 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright © 2024 Acronis International GmbH. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 3d9dee8..6bd141d 100644 --- a/README.md +++ b/README.md @@ -1 +1,110 @@ -# Stack trace implementation for Go \ No newline at end of file +# Stack Trace Implementation for Go + +This repository provides a comprehensive stack trace implementation for Go, allowing for detailed error handling and reporting. It includes features such as error wrapping, severity levels, and position tracking. + +## Features + +- **Error Wrapping**: Wrap errors with additional context. +- **Severity Levels**: Define the severity of errors. +- **Position Tracking**: Track the position (line and column) of errors in files. +- **Trace Options**: Customize trace generation with options like ensuring duplicates are not printed. + +## Installation + +To install the package, use: + +```sh +go get github.com/acronis/go-stacktrace +``` + +## Usage + +Basic usage +```Go +package main + +import ( + "fmt" + "github.com/acronis/go-stacktrace" +) + +func main() { + err := stacktrace.New("An error occurred", stacktrace.WithLocation("/path/to/file"), stacktrace.WithPosition(stacktrace.NewPosition(10, 1))) + fmt.Println(err) + // Output: + // /path/to/file:10:1: An error occurred +} +``` + +Wrapping errors +```Go +package main + +import ( + "fmt" + "github.com/acronis/go-stacktrace" +) + +func main() { + baseErr := fmt.Errorf("base error") + wrappedErr := stacktrace.NewWrapped("an error occurred", baseErr, stacktrace.WithLocation("/path/to/file"), stacktrace.WithPosition(stacktrace.NewPosition(10, 1))) + fmt.Println(wrappedErr) + // Output: + // /path/to/file:10:1: an error occurred: base error + + unwrappedErr, ok := stacktrace.Unwrap(wrappedErr) + if ok { + fmt.Println(unwrappedErr) + } + + wrappedErr2 := stacktrace.Wrap(baseErr, stacktrace.WithLocation("/path/to/file"), stacktrace.WithPosition(stacktrace.NewPosition(10, 1))) + fmt.Println(wrappedErr2) + // Output: + // /path/to/file:10:1: base error +} +``` + +Customizing Traces +```Go +package main + +import ( + "fmt" + "github.com/acronis/go-stacktrace" +) + +func main() { + err := stacktrace.New("an error occurred", stacktrace.WithLocation("/path/to/file"), stacktrace.WithPosition(stacktrace.NewPosition(10, 1))) + traces := err.GetTraces(stacktrace.WithEnsureDuplicates()) + fmt.Println(traces) +} +``` + +## API + +### Types + +* **StackTrace**: Represents a stack trace with various attributes. +* **Severity**: Represents the severity level of an error. +* **Type**: Represents the type of an error. +* **Position**: Represents the position (line and column) of an error in a file. +* **Location**: Represents the location (file path) of an error. + +### Functions + +* `New(message string, opts ...Option) *StackTrace`: Creates a new stack trace. +* `NewWrapped(message string, err error, opts ...Option) *StackTrace`: Creates a new wrapped stack trace. +* `Wrap(err error, opts ...Option) *StackTrace`: Wraps an existing error in a stack trace. +* `Unwrap(err error) (*StackTrace, bool)`: Unwraps a stack trace from an error. + +### Options + +* `WithLocation(location string) Option`: Sets the location of the error. +* `WithSeverity(severity Severity) Option`: Sets the severity of the error. +* `WithPosition(position *Position) Option`: Sets the position of the error. +* `WithInfo(key string, value fmt.Stringer) Option`: Adds additional information to the error. +* `WithType(errType Type) Option`: Sets the type of the error. +* `WithEnsureDuplicates() TracesOpt`: Ensures that duplicates are not printed in traces. + +## Contributing +Contributions are welcome! Please open an issue or submit a pull request. \ No newline at end of file diff --git a/stacktrace.go b/stacktrace.go index 4642e6e..534038f 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -10,24 +10,24 @@ import ( // Type is the type of the error. type Type string -const ( - TypeUnknown Type = "unknown" - TypeParsing Type = "parsing" - TypeLoading Type = "loading" - TypeReading Type = "reading" - TypeResolving Type = "resolving" - TypeValidating Type = "validating" - TypeUnwrapping Type = "unwrapping" -) +// String is a fmt.Stringer implementation. +func (t *Type) String() string { + if t == nil { + return "" + } + return string(*t) +} // Severity is the severity of the error. type Severity string -const ( - SeverityError Severity = "error" - SeverityWarning Severity = "warning" - SeverityCritical Severity = "critical" -) +// String is a fmt.Stringer implementation. +func (s *Severity) String() string { + if s == nil { + return "" + } + return string(*s) +} // stringer is a fmt.Stringer implementation. type stringer struct { @@ -36,6 +36,9 @@ type stringer struct { // String implements the fmt.Stringer interface. func (s *stringer) String() string { + if s == nil { + return "" + } return s.msg } @@ -46,8 +49,18 @@ func Stringer(v interface{}) fmt.Stringer { return w case string: return &stringer{msg: w} + case *string: + return &stringer{msg: *w} case error: return &stringer{msg: w.Error()} + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return &stringer{msg: fmt.Sprintf("%d", w)} + case float32, float64: + return &stringer{msg: fmt.Sprintf("%f", w)} + case bool: + return &stringer{msg: fmt.Sprintf("%t", w)} + case nil: + return &stringer{msg: "nil"} default: return &stringer{msg: fmt.Sprintf("%v", w)} } @@ -153,14 +166,23 @@ func NewStructInfo() *StructInfo { } } +type Location string + +func (loc *Location) String() string { + if loc == nil { + return "" + } + return string(*loc) +} + // StackTrace contains information about a parser error. type StackTrace struct { // Severity is the severity of the error. - Severity Severity + Severity *Severity // Type is the type of the error. - Type Type + Type *Type // Location is the location file path of the error. - Location string + Location *Location // Position is the position of the error in the file. Position *Position @@ -170,11 +192,10 @@ type StackTrace struct { Err error // Message is the error message. Message string - // WrappingMessage is the error messages of the wrapped errors. - WrappingMessage string // Info is the additional information about the error. Info StructInfo + // List is the list of stack traces. List []*StackTrace typeIsSet bool @@ -182,24 +203,16 @@ type StackTrace struct { // Header returns the header of the StackTrace. func (st *StackTrace) Header() string { - result := fmt.Sprintf("[%s] %s: %s", - st.Severity, - st.Type, - st.GetLocWithPos(), - ) - return result -} - -// FullMessage returns the full message of the error including the wrapped messages. -func (st *StackTrace) FullMessage() string { - if st.WrappingMessage != "" { - if st.Message != "" { - return fmt.Sprintf("%s: %s", st.WrappingMessage, st.Message) - } - return st.WrappingMessage - } else { - return st.Message + segs := make([]string, 0) + result := st.Type.String() + if result != "" { + segs = append(segs, result) + } + loc := st.GetLocWithPos() + if loc != "" { + segs = append(segs, loc) } + return strings.Join(segs, ": ") } // Option is an option for the StackTrace creation. @@ -209,48 +222,65 @@ type Option interface { // OrigString returns the original error message without the wrapping messages. func (st *StackTrace) OrigString() string { - result := st.Header() - if st.Message != "" { - result = fmt.Sprintf("%s: %s", result, st.Message) + segs := make([]string, 0) + header := st.Header() + if header != "" { + segs = append(segs, header) } - if len(st.Info.info) > 0 { - result = fmt.Sprintf("%s: %s", result, st.Info.String()) + msg := st.MessageWithInfo() + if msg != "" { + segs = append(segs, msg) } - return result + return strings.Join(segs, ": ") } -// OrigStringW returns the original error message with the wrapped error messages +// OrigStringW returns the original error message with the wrapped OrigStringW func (st *StackTrace) OrigStringW() string { - result := st.OrigString() + segs := make([]string, 0) + orig := st.OrigString() + if orig != "" { + segs = append(segs, orig) + } if st.Wrapped != nil { - result = fmt.Sprintf("%s: %s", result, st.Wrapped.String()) + segs = append(segs, st.Wrapped.OrigStringW()) } - return result + return strings.Join(segs, ": ") } -func (st *StackTrace) FullMessageWithInfo() string { - result := st.FullMessage() +func (st *StackTrace) MessageWithInfo() string { + segments := make([]string, 0) + if st.Message != "" { + segments = append(segments, st.Message) + } if len(st.Info.info) > 0 { - result = fmt.Sprintf("%s: %s", result, st.Info.String()) + segments = append(segments, st.Info.String()) } - return result + return strings.Join(segments, ": ") } // String implements the fmt.Stringer interface. // It returns the string representation of the StackTrace. func (st *StackTrace) String() string { - result := st.Header() - msg := st.FullMessageWithInfo() - if msg != "" { - result = fmt.Sprintf("%s: %s", result, msg) + segs := make([]string, 0) + orig := st.OrigString() + if orig != "" { + segs = append(segs, orig) } if st.Wrapped != nil { - result = fmt.Sprintf("%s: %s", result, st.Wrapped.String()) + segs = append(segs, st.Wrapped.String()) } + + res := strings.Join(segs, ": ") + if len(st.List) > 0 { - result = fmt.Sprintf("%s: and more (%d)...", result, len(st.List)) + lists := make([]string, 0) + lists = append(lists, res) + for _, elem := range st.List { + lists = append(lists, elem.String()) + } + res = strings.Join(lists, "; ") } - return result + return res } // StackTrace implements the error interface. @@ -259,38 +289,57 @@ func (st *StackTrace) Error() string { return st.String() } -// Unwrap checks if the given error is an StackTrace and returns it. -// It returns false if the error is not an StackTrace. +// Unwrap checks if the given error is a StackTrace and returns it. +// It returns false if the error is not a StackTrace. +// if err is a StackTrace, it returns the wrapped StackTrace and true. +// if err is not a StackTrace, it is unwrapped and wrapped with a new StackTrace if it has a wrapped StackTrace. func Unwrap(err error) (*StackTrace, bool) { if err == nil { return nil, false } - st, ok := err.(*StackTrace) + wrapped, ok := err.(*StackTrace) if !ok { - wrappedErr := errors.Unwrap(err) - if wrappedErr == nil { + errWrapped := errors.Unwrap(err) + if errWrapped == nil { return nil, false } - st, ok = Unwrap(wrappedErr) - if ok { - msg := strings.ReplaceAll(err.Error(), st.OrigStringW(), "") + wrapped, ok = Unwrap(errWrapped) + if ok && wrapped != nil { + msg, _, _ := strings.Cut(err.Error(), wrapped.Message) msg = strings.TrimSuffix(msg, ": ") - st.WrappingMessage = msg - st.Err = err + wrapped = New(msg).Wrap(wrapped) + wrapped.Err = err } } + if wrapped == nil { + return nil, false + } - // Clone the error to avoid modifying the original error. - return st.Clone(), ok + return wrapped, ok +} + +// Is checks if the given error is the same as the StackTrace. +func (st *StackTrace) Is(err error) bool { + if st == nil { + return false + } + if err == nil { + return false + } + wrapped, ok := Unwrap(err) + if !ok { + return false + } + if st == wrapped { + return true + } + return st.Is(wrapped.Wrapped) } // New creates a new StackTrace. -func New(message string, location string, opts ...Option) *StackTrace { +func New(message string, opts ...Option) *StackTrace { e := &StackTrace{ - Severity: SeverityError, - Type: TypeParsing, - Message: message, - Location: location, + Message: message, } for _, opt := range opts { opt.Apply(e) @@ -315,12 +364,20 @@ func (o optErrPosition) Apply(e *StackTrace) { e.Position = o.Pos } +type optErrLocation struct { + Location Location +} + +func (o optErrLocation) Apply(e *StackTrace) { + e.Location = &o.Location +} + type optErrSeverity struct { Severity Severity } func (o optErrSeverity) Apply(e *StackTrace) { - e.Severity = o.Severity + e.Severity = &o.Severity } type optErrType struct { @@ -348,44 +405,56 @@ func WithType(errType Type) Option { return optErrType{ErrType: errType} } +// WithLocation sets the location of the error. +func WithLocation(location string) Option { + return optErrLocation{Location: Location(location)} +} + // NewWrapped creates a new StackTrace from the given go error. -func NewWrapped(message string, err error, location string, opts ...Option) *StackTrace { - resultErr := &StackTrace{} - if st, ok := Unwrap(err); ok { - resultErr = New( +func NewWrapped(message string, err error, opts ...Option) *StackTrace { + if wrapped, ok := Unwrap(err); ok { + return New( message, - location, - ).Wrap(st).SetErr(st.Err) - } else { - resultErr = New(fmt.Sprintf("%s: %s", message, err.Error()), location).SetErr(err) + opts..., + ).Wrap(wrapped).SetErr(wrapped.Err) } - for _, opt := range opts { - opt.Apply(resultErr) + return New(fmt.Sprintf("%s: %s", message, err.Error()), opts...).SetErr(err) +} + +// Wrap wraps the given error with the StackTrace if it is not a StackTrace. +// It returns the wrapped StackTrace. +func Wrap(err error, opts ...Option) *StackTrace { + if st, ok := Unwrap(err); ok { + for _, opt := range opts { + opt.Apply(st) + } + return st } - return resultErr + return New(err.Error(), opts...).SetErr(err) } // SetSeverity sets the severity of the StackTrace and returns it func (st *StackTrace) SetSeverity(severity Severity) *StackTrace { - st.Severity = severity + st.Severity = &severity return st } // SetType sets the type of the StackTrace and returns it, operation can be done only once. -func (st *StackTrace) SetType(errType Type) *StackTrace { +func (st *StackTrace) SetType(t Type) *StackTrace { if !st.typeIsSet { - st.Type = errType + st.Type = &t st.typeIsSet = true } if st.Wrapped != nil { - _ = st.Wrapped.SetType(errType) + _ = st.Wrapped.SetType(t) } return st } // SetLocation sets the location of the StackTrace and returns it func (st *StackTrace) SetLocation(location string) *StackTrace { - st.Location = location + loc := Location(location) + st.Location = &loc return st } @@ -395,12 +464,6 @@ func (st *StackTrace) SetPosition(pos *Position) *StackTrace { return st } -// SetWrappingMessage sets a message which wraps the message of StackTrace and returns *StackTrace -func (st *StackTrace) SetWrappingMessage(msg string, a ...any) *StackTrace { - st.WrappingMessage = fmt.Sprintf(msg, a...) - return st -} - // SetMessage sets the message of the StackTrace and returns it func (st *StackTrace) SetMessage(message string, a ...any) *StackTrace { st.Message = fmt.Sprintf(message, a...) @@ -428,24 +491,25 @@ func (st *StackTrace) Append(e *StackTrace) *StackTrace { return st } -// Clone returns a clone of the StackTrace. -func (st *StackTrace) Clone() *StackTrace { - if st == nil { - return nil +// GetLocWithPos returns the location with position of the StackTrace. +func (st *StackTrace) GetLocWithPos() string { + res := st.GetLocWithPosPtr() + if res == nil { + return "" } - c := *st - return &c + return *res } -// GetLocWithPos returns the location with position of the StackTrace. -func (st *StackTrace) GetLocWithPos() string { - result := st.Location - if st.Position != nil { - result = fmt.Sprintf("%s:%d:%d", result, st.Position.Line, st.Position.Column) - } else { - result = fmt.Sprintf("%s:1", result) +// GetLocWithPosPtr returns the location with position of the StackTrace. +func (st *StackTrace) GetLocWithPosPtr() *string { + if st.Location == nil { + return nil } - return result + result := st.Location.String() + if result != "" { + result = fmt.Sprintf("%s:%s", result, st.Position) + } + return &result } // Position contains the line and column where the error occurred. @@ -454,6 +518,20 @@ type Position struct { Column int } +func (p *Position) String() string { + if p == nil { + return "1" + } + if p.Line == 0 { + return "1" + } + result := fmt.Sprintf("%d", p.Line) + if p.Column > 0 { + result = fmt.Sprintf("%s:%d", result, p.Column) + } + return result +} + // NewPosition creates a new position with the given line and column. func NewPosition(line, column int) *Position { return &Position{Line: line, Column: column} diff --git a/stacktrace_test.go b/stacktrace_test.go index e9f7518..9b3e9af 100644 --- a/stacktrace_test.go +++ b/stacktrace_test.go @@ -6,140 +6,6 @@ import ( "testing" ) -func TestUnwrapError(t *testing.T) { - type args struct { - err error - } - firstValidErr := New( - "first validation error", - "/usr/local/raml.raml", - ).SetPosition(&Position{Line: 10, Column: 1}) - simpleErr := fmt.Errorf("simple error") - wrappedSimpleErr := fmt.Errorf("wrapped simpleErr: %w", simpleErr) - wrappedFirstValidErr := fmt.Errorf("wrapped firstValidErr: %w", firstValidErr) - secondValidErr := NewWrapped( - "wrapped firstValidErr", - firstValidErr, - "/usr/local/raml2.raml", - ).SetPosition(&Position{Line: 20, Column: 2}) - wrappedSecondValidErr := fmt.Errorf("wrapped secondValidErr: %w", secondValidErr) - tests := []struct { - name string - args args - want *StackTrace - want1 bool - wantMsg string - }{ - { - name: "Check the same first validation error", - args: args{ - err: firstValidErr, - }, - want: firstValidErr, - want1: true, - wantMsg: "[error] parsing: /usr/local/raml.raml:10:1: first validation error", - }, - { - name: "Check wrapped first validation error", - args: args{ - err: wrappedFirstValidErr, - }, - want: &StackTrace{ - Severity: SeverityError, - Type: TypeParsing, - Message: firstValidErr.Message, - Location: firstValidErr.Location, - Position: firstValidErr.Position, - WrappingMessage: "wrapped firstValidErr", - Err: wrappedFirstValidErr, - }, - want1: true, - wantMsg: "[error] parsing: /usr/local/raml.raml:10:1: wrapped firstValidErr: first validation error", - }, - { - name: "Check not a validation error", - args: args{ - err: simpleErr, - }, - want: nil, - want1: false, - }, - { - name: "Check not a validation wrapped error", - args: args{ - err: wrappedSimpleErr, - }, - want: nil, - want1: false, - }, - { - name: "Check err is nil", - args: args{ - err: nil, - }, - want: nil, - want1: false, - }, - { - name: "Check validation error wrapped in another error", - args: args{ - err: NewWrapped("wrapped", wrappedSimpleErr, "/usr/local/raml3.raml").SetPosition(&Position{Line: 30, Column: 3}), - }, - want: &StackTrace{ - Severity: SeverityError, - Type: TypeParsing, - Message: fmt.Sprintf("wrapped: %s", wrappedSimpleErr.Error()), - Location: "/usr/local/raml3.raml", - Position: &Position{Line: 30, Column: 3}, - Err: wrappedSimpleErr, - WrappingMessage: "", - }, - want1: true, - wantMsg: fmt.Sprintf("[error] parsing: /usr/local/raml3.raml:30:3: wrapped: %s", wrappedSimpleErr.Error()), - }, - { - name: "Check double wrapped error", - args: args{ - err: wrappedSecondValidErr, - }, - want: &StackTrace{ - Severity: SeverityError, - Type: TypeParsing, - Message: secondValidErr.Message, - Location: secondValidErr.Location, - Position: secondValidErr.Position, - Err: wrappedSecondValidErr, - WrappingMessage: "wrapped secondValidErr", - Wrapped: &StackTrace{ - Severity: SeverityError, - Type: TypeParsing, - Message: firstValidErr.Message, - Location: firstValidErr.Location, - Position: firstValidErr.Position, - }, - }, - want1: true, - wantMsg: "[error] parsing: /usr/local/raml2.raml:20:2: wrapped secondValidErr: wrapped firstValidErr: [error] parsing: /usr/local/raml.raml:10:1: first validation error", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1 := Unwrap(tt.args.err) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Unwrap()\ngot:\n%v\nwant:\n%v\n", got, tt.want) - } - if got1 != tt.want1 { - t.Errorf("Unwrap() got1 = %v, want %v", got1, tt.want1) - } - if got != nil { - if got.Error() != tt.wantMsg { - t.Errorf("Unwrap() Message\ngot:\n%s\nwant:\n%s\n", got.Error(), tt.wantMsg) - } - } - }) - } -} - func TestStringer(t *testing.T) { type args struct { v interface{} @@ -175,7 +41,7 @@ func TestStringer(t *testing.T) { args: args{ v: nil, }, - want: "", + want: "nil", }, { name: "Check stringer with error", @@ -184,6 +50,36 @@ func TestStringer(t *testing.T) { }, want: "error", }, + { + name: "Check stringer with pointer of string", + args: args{ + v: func() *string { s := "string"; return &s }(), + }, + want: "string", + }, + { + name: "Check stringer with bool", + args: args{ + v: true, + }, + want: "true", + }, + { + name: "Check stringer with float", + args: args{ + v: 10.1, + }, + want: "10.100000", + }, + { + name: "Check stringer with struct", + args: args{ + v: struct { + key string + }{key: "value"}, + }, + want: "{value}", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -541,22 +437,20 @@ func TestError_SetSeverity(t *testing.T) { want *StackTrace }{ { - name: "Check set severity", - fields: fields{ - Severity: SeverityError, - }, + name: "Check set severity", + fields: fields{}, args: args{ - severity: SeverityCritical, + severity: Severity("critical"), }, want: &StackTrace{ - Severity: SeverityCritical, + Severity: func() *Severity { s := Severity("critical"); return &s }(), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := &StackTrace{ - Severity: tt.fields.Severity, + Severity: &tt.fields.Severity, } if got := v.SetSeverity(tt.args.severity); !reflect.DeepEqual(got, tt.want) { t.Errorf("SetSeverity() = %v, want %v", got, tt.want) @@ -579,22 +473,20 @@ func TestError_SetType(t *testing.T) { want *StackTrace }{ { - name: "Check set type", - fields: fields{ - ErrType: TypeValidating, - }, + name: "Check set type", + fields: fields{}, args: args{ - errType: TypeParsing, + errType: Type("parsing"), }, want: &StackTrace{ - Type: TypeParsing, + Type: func() *Type { tt := Type("parsing"); return &tt }(), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := &StackTrace{ - Type: tt.fields.ErrType, + Type: &tt.fields.ErrType, } if got := v.SetType(tt.args.errType); !reflect.DeepEqual(got.Type, tt.want.Type) { t.Errorf("SetType() = %v, want %v", got.Type, tt.want.Type) @@ -605,7 +497,7 @@ func TestError_SetType(t *testing.T) { func TestError_SetLocationAndPosition(t *testing.T) { type fields struct { - Location string + Location Location Position Position } type args struct { @@ -635,7 +527,7 @@ func TestError_SetLocationAndPosition(t *testing.T) { }, }, want: &StackTrace{ - Location: "/usr/local/raml2.raml", + Location: func() *Location { l := Location("/usr/local/raml2.raml"); return &l }(), Position: &Position{ Line: 20, Column: 2, @@ -646,7 +538,7 @@ func TestError_SetLocationAndPosition(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := &StackTrace{ - Location: tt.fields.Location, + Location: &tt.fields.Location, Position: &tt.fields.Position, } if got := v.SetLocation(tt.args.location).SetPosition(&tt.args.pos); !reflect.DeepEqual(got, tt.want) { @@ -709,70 +601,17 @@ func TestError_SetMessage(t *testing.T) { } } -func TestError_SetWrappingMessage(t *testing.T) { - type fields struct { - Message string - } - type args struct { - message string - a []any - } - tests := []struct { - name string - fields fields - args args - want *StackTrace - }{ - { - name: "Check set message", - fields: fields{ - Message: "message", - }, - args: args{ - message: "new message", - a: []any{}, - }, - want: &StackTrace{ - WrappingMessage: "new message", - }, - }, - { - name: "Check set message with arguments", - fields: fields{ - Message: "message", - }, - args: args{ - message: "new message with %s", - a: []any{"argument"}, - }, - want: &StackTrace{ - WrappingMessage: "new message with argument", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - v := &StackTrace{ - WrappingMessage: tt.fields.Message, - } - if got := v.SetWrappingMessage(tt.args.message, tt.args.a...); !reflect.DeepEqual(got, tt.want) { - t.Errorf("SetWrappingMessage() = %v, want %v", got, tt.want) - } - }) - } -} - func TestError_Error(t *testing.T) { type fields struct { - Severity Severity - ErrType Type - Location string - Position *Position - Wrapped *StackTrace - Err error - Message string - WrappedMessages string - Info StructInfo + Severity Severity + ErrType Type + Location Location + Position *Position + Wrapped *StackTrace + Err error + Message string + Info StructInfo + List []*StackTrace } tests := []struct { name string @@ -782,108 +621,99 @@ func TestError_Error(t *testing.T) { { name: "Check error", fields: fields{ - Severity: SeverityError, - ErrType: TypeValidating, - Location: "/usr/local/raml.raml", - Position: &Position{Line: 10, Column: 1}, - Message: "message", - WrappedMessages: "wrapped message", + Location: "/usr/local/raml.raml", + Position: &Position{Line: 10, Column: 1}, + Message: "message", }, - want: "[error] validating: /usr/local/raml.raml:10:1: wrapped message: message", + want: "/usr/local/raml.raml:10:1: message", }, { name: "Check error without position", fields: fields{ - Severity: SeverityError, - ErrType: TypeValidating, Location: "/usr/local/raml.raml", Message: "message", }, - want: "[error] validating: /usr/local/raml.raml:1: message", + want: "/usr/local/raml.raml:1: message", }, { name: "Check error with info", fields: fields{ - Severity: SeverityError, - ErrType: TypeValidating, Location: "/usr/local/raml.raml", Position: &Position{Line: 10, Column: 1}, Message: "message", Info: *NewStructInfo().Add("key", Stringer("value")), }, - want: "[error] validating: /usr/local/raml.raml:10:1: message: key: value", + want: "/usr/local/raml.raml:10:1: message: key: value", }, { name: "Check error with empty message", fields: fields{ - Severity: SeverityError, - ErrType: TypeValidating, Location: "/usr/local/raml.raml", Position: &Position{Line: 10, Column: 1}, }, - want: "[error] validating: /usr/local/raml.raml:10:1", - }, - { - name: "Check error with only wrapped messages", - fields: fields{ - Severity: SeverityError, - ErrType: TypeValidating, - Location: "/usr/local/raml.raml", - Position: &Position{Line: 10, Column: 1}, - WrappedMessages: "wrapped message", - }, - want: "[error] validating: /usr/local/raml.raml:10:1: wrapped message", + want: "/usr/local/raml.raml:10:1", }, { - name: "Check error with only wrapped messages and info", + name: "Check error with only info", fields: fields{ - Severity: SeverityError, - ErrType: TypeValidating, - Location: "/usr/local/raml.raml", - Position: &Position{Line: 10, Column: 1}, - WrappedMessages: "wrapped message", - Info: *NewStructInfo().Add("key", Stringer("value")), + Location: "/usr/local/raml.raml", + Position: &Position{Line: 10, Column: 1}, + Info: *NewStructInfo().Add("key", Stringer("value")), }, - want: "[error] validating: /usr/local/raml.raml:10:1: wrapped message: key: value", + want: "/usr/local/raml.raml:10:1: key: value", }, { name: "Check error with only wrapped error", fields: fields{ - Severity: SeverityError, - ErrType: TypeParsing, Location: "/usr/local/raml.raml", Position: &Position{Line: 10, Column: 1}, Wrapped: &StackTrace{ - Severity: SeverityCritical, - Type: TypeValidating, - Location: "/usr/local/raml2.raml", - Position: &Position{Line: 20, Column: 2}, - Message: "message 1", + Message: "message 1", Wrapped: &StackTrace{ - Severity: SeverityError, - Type: TypeResolving, - Location: "/usr/local/raml3.raml", - Position: &Position{Line: 30, Column: 3}, - Message: "message 2", + Message: "message 2", }, }, Message: "message", }, - want: "[error] parsing: /usr/local/raml.raml:10:1: message: [critical] validating: /usr/local/raml2.raml:20:2: message 1: [error] resolving: /usr/local/raml3.raml:30:3: message 2", + want: "/usr/local/raml.raml:10:1: message: message 1: message 2", + }, + { + name: "Check error with list", + fields: fields{ + Message: "message", + Wrapped: &StackTrace{ + Message: "message 1", + }, + List: []*StackTrace{ + { + Message: "message 2", + Wrapped: &StackTrace{ + Message: "message 3", + }, + }, + { + Message: "message 4", + Wrapped: &StackTrace{ + Message: "message 5", + }, + }, + }, + }, + want: "message: message 1; message 2: message 3; message 4: message 5", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := &StackTrace{ - Severity: tt.fields.Severity, - Type: tt.fields.ErrType, - Location: tt.fields.Location, - Position: tt.fields.Position, - Wrapped: tt.fields.Wrapped, - Err: tt.fields.Err, - Message: tt.fields.Message, - WrappingMessage: tt.fields.WrappedMessages, - Info: tt.fields.Info, + Severity: &tt.fields.Severity, + Type: &tt.fields.ErrType, + Location: &tt.fields.Location, + Position: tt.fields.Position, + Wrapped: tt.fields.Wrapped, + Err: tt.fields.Err, + Message: tt.fields.Message, + Info: tt.fields.Info, + List: tt.fields.List, } if got := v.Error(); got != tt.want { t.Errorf("StackTrace() = %v, want %v", got, tt.want) @@ -893,10 +723,15 @@ func TestError_Error(t *testing.T) { } func TestError_OrigString(t *testing.T) { + var SeverityError Severity = "error" + var TypeValidating Type = "validating" + var loc Location = "/usr/local/raml.raml" + var loc2 Location = "/usr/local/raml2.raml" + type fields struct { - Severity Severity - ErrType Type - Location string + Severity *Severity + ErrType *Type + Location *Location Position Position Wrapped *StackTrace Err error @@ -912,36 +747,35 @@ func TestError_OrigString(t *testing.T) { { name: "Check original string", fields: fields{ - Severity: SeverityError, - ErrType: TypeValidating, - Location: "/usr/local/raml.raml", + Severity: &SeverityError, + ErrType: &TypeValidating, + Location: &loc, Position: Position{Line: 10, Column: 1}, Message: "message", Info: *NewStructInfo().Add("key", Stringer("value")), Wrapped: &StackTrace{ - Severity: SeverityError, - Type: TypeValidating, - Location: "/usr/local/raml2.raml", + Severity: &SeverityError, + Type: &TypeValidating, + Location: &loc2, Position: &Position{Line: 20, Column: 2}, Message: "wrapped", Info: *NewStructInfo().Add("key", Stringer("value")), }, }, - want: "[error] validating: /usr/local/raml.raml:10:1: message: key: value", + want: "validating: /usr/local/raml.raml:10:1: message: key: value", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := &StackTrace{ - Severity: tt.fields.Severity, - Type: tt.fields.ErrType, - Location: tt.fields.Location, - Position: &tt.fields.Position, - Wrapped: tt.fields.Wrapped, - Err: tt.fields.Err, - Message: tt.fields.Message, - WrappingMessage: tt.fields.WrappedMessages, - Info: tt.fields.Info, + Severity: tt.fields.Severity, + Type: tt.fields.ErrType, + Location: tt.fields.Location, + Position: &tt.fields.Position, + Wrapped: tt.fields.Wrapped, + Err: tt.fields.Err, + Message: tt.fields.Message, + Info: tt.fields.Info, } if got := v.OrigString(); got != tt.want { t.Errorf("OrigString() = %v, want %v", got, tt.want) @@ -951,11 +785,14 @@ func TestError_OrigString(t *testing.T) { } func TestNewWrappedError(t *testing.T) { + var SeverityCritical Severity = "critical" + var TypeParsing Type = "parsing" + var loc Location = "/usr/local/raml.raml" + type args struct { - message string - err error - location string - opts []Option + message string + err error + opts []Option } tests := []struct { name string @@ -965,10 +802,10 @@ func TestNewWrappedError(t *testing.T) { { name: "Check wrapped error", args: args{ - message: "message", - err: fmt.Errorf("error"), - location: "/usr/local/raml.raml", + message: "message", + err: fmt.Errorf("error"), opts: []Option{ + WithLocation("/usr/local/raml.raml"), WithSeverity(SeverityCritical), WithPosition(NewPosition(10, 1)), WithInfo("key", Stringer("value")), @@ -976,10 +813,10 @@ func TestNewWrappedError(t *testing.T) { }, }, want: &StackTrace{ - Severity: SeverityCritical, - Type: TypeParsing, + Severity: &SeverityCritical, + Type: &TypeParsing, Message: "message: error", - Location: "/usr/local/raml.raml", + Location: &loc, Position: &Position{Line: 10, Column: 1}, Err: fmt.Errorf("error"), Info: *NewStructInfo().Add("key", Stringer("value")), @@ -989,7 +826,7 @@ func TestNewWrappedError(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := NewWrapped(tt.args.message, tt.args.err, tt.args.location, tt.args.opts...); !reflect.DeepEqual(got, tt.want) { + if got := NewWrapped(tt.args.message, tt.args.err, tt.args.opts...); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewWrapped() = %v, want %v", got, tt.want) } }) @@ -997,6 +834,10 @@ func TestNewWrappedError(t *testing.T) { } func TestNewError(t *testing.T) { + var SeverityCritical Severity = "critical" + var TypeParsing Type = "parsing" + var loc Location = "/usr/local/raml.raml" + type args struct { message string location string @@ -1010,9 +851,9 @@ func TestNewError(t *testing.T) { { name: "Check error", args: args{ - message: "message", - location: "/usr/local/raml.raml", + message: "message", opts: []Option{ + WithLocation("/usr/local/raml.raml"), WithSeverity(SeverityCritical), WithPosition(NewPosition(10, 1)), WithInfo("key", Stringer("value")), @@ -1020,10 +861,10 @@ func TestNewError(t *testing.T) { }, }, want: &StackTrace{ - Severity: SeverityCritical, - Type: TypeParsing, + Severity: &SeverityCritical, + Type: &TypeParsing, Message: "message", - Location: "/usr/local/raml.raml", + Location: &loc, Position: &Position{Line: 10, Column: 1}, Info: *NewStructInfo().Add("key", Stringer("value")), typeIsSet: true, @@ -1032,9 +873,791 @@ func TestNewError(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := New(tt.args.message, tt.args.location, tt.args.opts...); !reflect.DeepEqual(got, tt.want) { + if got := New(tt.args.message, tt.args.opts...); !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } + +func TestType_String(t *testing.T) { + tests := []struct { + name string + t *Type + want string + }{ + { + name: "Check type no nil", + t: func() *Type { tt := Type("type"); return &tt }(), + want: "type", + }, + { + name: "Check type nil", + t: nil, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.t.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSeverity_String(t *testing.T) { + tests := []struct { + name string + s *Severity + want string + }{ + { + name: "Check severity no nil", + s: func() *Severity { s := Severity("severity"); return &s }(), + want: "severity", + }, + { + name: "Check severity nil", + s: nil, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_stringer_String(t *testing.T) { + type fields struct { + stringer *stringer + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "Check stringer not nil", + fields: fields{ + stringer: &stringer{ + msg: "message", + }, + }, + want: "message", + }, + { + name: "Check stringer nil", + fields: fields{ + stringer: nil, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := tt.fields.stringer + if got := s.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLocation_String(t *testing.T) { + tests := []struct { + name string + loc *Location + want string + }{ + { + name: "Check location", + loc: func() *Location { l := Location("location"); return &l }(), + want: "location", + }, + { + name: "Check location nil", + loc: nil, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.loc.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStackTrace_OrigStringW(t *testing.T) { + type fields struct { + Severity *Severity + Type *Type + Location *Location + Position *Position + Wrapped *StackTrace + Err error + Message string + Info StructInfo + List []*StackTrace + typeIsSet bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "Check original string", + fields: fields{ + Severity: func() *Severity { s := Severity("severity"); return &s }(), + Type: func() *Type { t := Type("type"); return &t }(), + Location: func() *Location { l := Location("location"); return &l }(), + Position: &Position{Line: 10, Column: 1}, + Message: "message", + Info: *NewStructInfo().Add("key", Stringer("value")), + Wrapped: &StackTrace{ + Message: "wrapped", + }, + }, + want: "type: location:10:1: message: key: value: wrapped", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + st := &StackTrace{ + Severity: tt.fields.Severity, + Type: tt.fields.Type, + Location: tt.fields.Location, + Position: tt.fields.Position, + Wrapped: tt.fields.Wrapped, + Err: tt.fields.Err, + Message: tt.fields.Message, + Info: tt.fields.Info, + List: tt.fields.List, + typeIsSet: tt.fields.typeIsSet, + } + if got := st.OrigStringW(); got != tt.want { + t.Errorf("OrigStringW() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnwrap(t *testing.T) { + type args struct { + err error + } + tests := []struct { + name string + args args + want func(t *testing.T, got *StackTrace, got1 bool) + }{ + { + name: "Check unwrap: nil", + args: args{ + err: nil, + }, + want: func(t *testing.T, got *StackTrace, got1 bool) { + if got != nil { + t.Errorf("Unwrap() = %v, want %v", got, nil) + } + if got1 { + t.Errorf("Unwrap() = %v, want %v", got1, false) + } + }, + }, + { + name: "Check unwrap", + args: args{ + err: fmt.Errorf("error"), + }, + want: func(t *testing.T, got *StackTrace, got1 bool) { + if got != nil { + t.Errorf("Unwrap() = %v, want %v", got, nil) + } + if got1 { + t.Errorf("Unwrap() = %v, want %v", got1, false) + } + }, + }, + { + name: "Check unwrap with stack trace", + args: args{ + err: &StackTrace{Message: "message"}, + }, + want: func(t *testing.T, got *StackTrace, got1 bool) { + if got == nil { + t.Errorf("Unwrap() = %v, want %v", got, nil) + } + if !got1 { + t.Errorf("Unwrap() = %v, want %v", got1, true) + } + if got.Message != "message" { + t.Errorf("Unwrap() = %v, want %v", got.Message, "message") + } + }, + }, + { + name: "Check unwrap with wrapped error", + args: args{ + err: fmt.Errorf("error: %w", &StackTrace{ + Message: "message", + Wrapped: &StackTrace{ + Message: "wrapped", + }, + }), + }, + want: func(t *testing.T, got *StackTrace, got1 bool) { + if got == nil { + t.Errorf("Unwrap() = %v, want %v", got, nil) + } + if !got1 { + t.Errorf("Unwrap() = %v, want %v", got1, true) + } + if got.Message != "error" { + t.Errorf("Unwrap() = %v, want %v", got.Message, "error") + } + if got.Wrapped == nil { + t.Errorf("Unwrap() = %v, want %v", got.Wrapped, nil) + } + if got.Wrapped.Message != "message" { + t.Errorf("Unwrap() = %v, want %v", got.Wrapped.Message, "message") + } + if got.Wrapped.Wrapped == nil { + t.Errorf("Unwrap() = %v, want %v", got.Wrapped.Wrapped, nil) + } + if got.Wrapped.Wrapped.Message != "wrapped" { + t.Errorf("Unwrap() = %v, want %v", got.Wrapped.Wrapped.Message, "wrapped") + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := Unwrap(tt.args.err) + if tt.want != nil { + tt.want(t, got, got1) + } + }) + } +} + +func TestStackTrace_Is(t *testing.T) { + checkErr := &StackTrace{Message: "message"} + diffErr := &StackTrace{Message: "message"} + type fields struct { + st *StackTrace + } + type args struct { + err error + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "Check is", + fields: fields{ + st: checkErr, + }, + args: args{ + err: checkErr, + }, + want: true, + }, + { + name: "Check is with wrapped error", + fields: fields{ + st: checkErr, + }, + args: args{ + err: fmt.Errorf("error: %w", checkErr), + }, + want: true, + }, + { + name: "Check is with wrapped stacktrace", + fields: fields{ + st: checkErr, + }, + args: args{ + err: fmt.Errorf("error: %w", &StackTrace{ + Message: "message", + Wrapped: checkErr, + }), + }, + want: true, + }, + { + name: "Negative: Check is with different error", + fields: fields{ + st: checkErr, + }, + args: args{ + err: diffErr, + }, + want: false, + }, + { + name: "Negative: Check is with wrapped different error", + fields: fields{ + st: checkErr, + }, + args: args{ + err: fmt.Errorf("error: %w", diffErr), + }, + want: false, + }, + { + name: "Negative: Check is with wrapped stacktrace with different error", + fields: fields{ + st: checkErr, + }, + args: args{ + err: fmt.Errorf("error: %w", &StackTrace{ + Message: "message", + Wrapped: diffErr, + }), + }, + want: false, + }, + { + name: "Negative: Check is with nil", + fields: fields{ + st: nil, + }, + args: args{ + err: checkErr, + }, + want: false, + }, + { + name: "Negative: Check is with arg nil", + fields: fields{ + st: checkErr, + }, + args: args{ + err: nil, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + st := tt.fields.st + if got := st.Is(tt.args.err); got != tt.want { + t.Errorf("Is() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewWrapped(t *testing.T) { + type args struct { + message string + err error + opts []Option + } + tests := []struct { + name string + args args + want func(t *testing.T, got *StackTrace) + }{ + { + name: "Check with simple error", + args: args{ + message: "message", + err: fmt.Errorf("error"), + }, + want: func(t *testing.T, got *StackTrace) { + if got.Message != "message: error" { + t.Errorf("NewWrapped() = %v, want %v", got.Message, "message: error") + } + if got.Err == nil { + t.Errorf("NewWrapped() = %v, want %v", got.Err, nil) + } + }, + }, + { + name: "Check with wrapped error", + args: args{ + message: "message", + err: fmt.Errorf("error: %w", &StackTrace{Message: "wrapped"}), + }, + want: func(t *testing.T, got *StackTrace) { + if got.Message != "message" { + t.Errorf("NewWrapped() = %v, want %v", got.Message, "message") + } + if got.Err == nil { + t.Errorf("NewWrapped() = %v, want %v", got.Err, nil) + } + if got.Wrapped == nil { + t.Errorf("NewWrapped() = %v, want %v", got.Wrapped, nil) + } + if got.Wrapped.Message != "error" { + t.Errorf("NewWrapped() = %v, want %v", got.Wrapped.Message, "error") + } + if got.Wrapped.Wrapped == nil { + t.Errorf("NewWrapped() = %v, want %v", got.Wrapped.Wrapped, nil) + } + if got.Wrapped.Wrapped.Message != "wrapped" { + t.Errorf("NewWrapped() = %v, want %v", got.Wrapped.Wrapped.Message, "wrapped") + } + }, + }, + { + name: "Check with wrapped stacktrace", + args: args{ + message: "message", + err: &StackTrace{ + Message: "error", + }, + }, + want: func(t *testing.T, got *StackTrace) { + if got.Message != "message" { + t.Errorf("NewWrapped() = %v, want %v", got.Message, "message") + } + if got.Err != nil { + t.Errorf("NewWrapped() = %v, want %v", got.Err, nil) + } + if got.Wrapped == nil { + t.Errorf("NewWrapped() = %v, want %v", got.Wrapped, nil) + } + if got.Wrapped.Message != "error" { + t.Errorf("NewWrapped() = %v, want %v", got.Wrapped.Message, "error") + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewWrapped(tt.args.message, tt.args.err, tt.args.opts...) + if tt.want != nil { + tt.want(t, got) + } + }) + } +} + +func TestWrap(t *testing.T) { + type args struct { + err error + opts []Option + } + tests := []struct { + name string + args args + want func(t *testing.T, got *StackTrace) + }{ + { + name: "Check wrap", + args: args{ + err: fmt.Errorf("error"), + }, + want: func(t *testing.T, got *StackTrace) { + if got.Message != "error" { + t.Errorf("Wrap() = %v, want %v", got.Message, "error") + } + if got.Err == nil { + t.Errorf("Wrap() = %v, want %v", got.Err, nil) + } + }, + }, + { + name: "Check wrap stacktrace", + args: args{ + err: &StackTrace{ + Message: "message", + }, + }, + want: func(t *testing.T, got *StackTrace) { + if got.Message != "message" { + t.Errorf("Wrap() = %v, want %v", got.Message, "message") + } + if got.Err != nil { + t.Errorf("Wrap() = %v, want %v", got.Err, nil) + } + }, + }, + { + name: "Check wrap stacktrace with options", + args: args{ + err: &StackTrace{Message: "message"}, + opts: []Option{ + WithLocation("/usr/local/raml.raml"), + }, + }, + want: func(t *testing.T, got *StackTrace) { + if got.Message != "message" { + t.Errorf("Wrap() = %v, want %v", got.Message, "message") + } + if got.Err != nil { + t.Errorf("Wrap() = %v, want %v", got.Err, nil) + } + if got.Location == nil { + t.Errorf("Wrap() = %v, want %v", got.Location, nil) + } + if *got.Location != "/usr/local/raml.raml" { + t.Errorf("Wrap() = %v, want %v", *got.Location, "/usr/local/raml.raml") + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Wrap(tt.args.err, tt.args.opts...) + if tt.want != nil { + tt.want(t, got) + } + }) + } +} + +func TestStackTrace_SetType(t *testing.T) { + type fields struct { + Severity *Severity + Type *Type + Location *Location + Position *Position + Wrapped *StackTrace + Err error + Message string + Info StructInfo + List []*StackTrace + typeIsSet bool + } + type args struct { + t Type + } + tests := []struct { + name string + fields fields + args args + want func(t *testing.T, got *StackTrace) + }{ + { + name: "Check set type", + fields: fields{ + Type: func() *Type { t := Type("type"); return &t }(), + }, + args: args{ + t: Type("new type"), + }, + want: func(t *testing.T, got *StackTrace) { + if got.Type == nil { + t.Errorf("SetType() = %v, want %v", got.Type, nil) + } + if *got.Type != "new type" { + t.Errorf("SetType() = %v, want %v", *got.Type, "new type") + } + }, + }, + { + name: "Check set type with wrapped", + fields: fields{ + Type: func() *Type { t := Type("type"); return &t }(), + Wrapped: &StackTrace{ + Type: func() *Type { t := Type("type 2"); return &t }(), + }, + }, + args: args{ + t: Type("new type"), + }, + want: func(t *testing.T, got *StackTrace) { + if got.Type == nil { + t.Errorf("SetType() = %v, want %v", got.Type, nil) + } + if *got.Type != "new type" { + t.Errorf("SetType() = %v, want %v", *got.Type, "new type") + } + if got.Wrapped.Type == nil { + t.Errorf("SetType() = %v, want %v", got.Wrapped.Type, nil) + } + if *got.Wrapped.Type != "new type" { + t.Errorf("SetType() = %v, want %v", *got.Wrapped.Type, "new type") + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + st := &StackTrace{ + Severity: tt.fields.Severity, + Type: tt.fields.Type, + Location: tt.fields.Location, + Position: tt.fields.Position, + Wrapped: tt.fields.Wrapped, + Err: tt.fields.Err, + Message: tt.fields.Message, + Info: tt.fields.Info, + List: tt.fields.List, + typeIsSet: tt.fields.typeIsSet, + } + got := st.SetType(tt.args.t) + if tt.want != nil { + tt.want(t, got) + } + }) + } +} + +func TestStackTrace_Append(t *testing.T) { + type fields struct { + Severity *Severity + Type *Type + Location *Location + Position *Position + Wrapped *StackTrace + Err error + Message string + Info StructInfo + List []*StackTrace + typeIsSet bool + } + type args struct { + e *StackTrace + } + tests := []struct { + name string + fields fields + args args + want func(t *testing.T, got *StackTrace) + }{ + { + name: "Check append", + fields: fields{}, + args: args{ + e: &StackTrace{Message: "message"}, + }, + want: func(t *testing.T, got *StackTrace) { + if len(got.List) != 1 { + t.Errorf("Append() = %v, want %v", len(got.List), 1) + } + if got.List[0].Message != "message" { + t.Errorf("Append() = %v, want %v", got.List[0].Message, "message") + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + st := &StackTrace{ + Severity: tt.fields.Severity, + Type: tt.fields.Type, + Location: tt.fields.Location, + Position: tt.fields.Position, + Wrapped: tt.fields.Wrapped, + Err: tt.fields.Err, + Message: tt.fields.Message, + Info: tt.fields.Info, + List: tt.fields.List, + typeIsSet: tt.fields.typeIsSet, + } + got := st.Append(tt.args.e) + if tt.want != nil { + tt.want(t, got) + } + }) + } +} + +func TestPosition_String(t *testing.T) { + type fields struct { + pos *Position + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "Check position: line only", + fields: fields{ + pos: &Position{Line: 10}, + }, + want: "10", + }, + { + name: "Check position: line and column", + fields: fields{ + pos: &Position{Line: 10, Column: 1}, + }, + want: "10:1", + }, + { + name: "Check position: column only: ignored, line is 1", + fields: fields{ + pos: &Position{Column: 3}, + }, + want: "1", + }, + { + name: "Check position: nil: default line is 1", + fields: fields{}, + want: "1", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := tt.fields.pos + if got := p.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_optErrNodePosition_Apply(t *testing.T) { + type fields struct { + pos *Position + } + type args struct { + e *StackTrace + } + tests := []struct { + name string + fields fields + args args + want func(t *testing.T, got *StackTrace) + }{ + { + name: "Check apply", + fields: fields{ + pos: &Position{Line: 10, Column: 1}, + }, + args: args{ + e: &StackTrace{}, + }, + want: func(t *testing.T, got *StackTrace) { + if got.Position == nil { + t.Errorf("Apply() = %v, want %v", got.Position, nil) + } + if got.Position.Line != 10 { + t.Errorf("Apply() = %v, want %v", got.Position.Line, 10) + } + if got.Position.Column != 1 { + t.Errorf("Apply() = %v, want %v", got.Position.Column, 1) + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := optErrNodePosition{ + pos: tt.fields.pos, + } + o.Apply(tt.args.e) + if tt.want != nil { + tt.want(t, tt.args.e) + } + }) + } +} diff --git a/traces.go b/traces.go index 2b11644..ca2d755 100644 --- a/traces.go +++ b/traces.go @@ -29,10 +29,10 @@ func WithEnsureDuplicates() TracesOpt { } type Stack struct { - LinePos string - Severity Severity + LinePos *string + Severity *Severity Message string - Type Type + Type *Type } func NewStack() *Stack { @@ -60,13 +60,15 @@ func (st *StackTrace) getTraces(opts *TracesOptions) []Trace { trace := NewTrace() stack := NewStack() - stack.LinePos = st.GetLocWithPos() + stack.LinePos = st.GetLocWithPosPtr() stack.Severity = st.Severity - stack.Message = st.FullMessageWithInfo() + stack.Message = st.MessageWithInfo() stack.Type = st.Type - if _, ok := opts.dupLocs[stack.LinePos]; ok { - return tracesWithList() + if stack.LinePos != nil { + if _, ok := opts.dupLocs[*stack.LinePos]; ok { + return tracesWithList() + } } trace.Stack = append(trace.Stack, *stack) @@ -78,8 +80,8 @@ func (st *StackTrace) getTraces(opts *TracesOptions) []Trace { for i := range wrappedTraces { trace.Stack = append(trace.Stack, wrappedTraces[i].Stack...) } - } else if opts.EnsureDuplicates { - opts.dupLocs[stack.LinePos] = struct{}{} + } else if opts.EnsureDuplicates && stack.LinePos != nil { + opts.dupLocs[*stack.LinePos] = struct{}{} } traces = append(traces, *trace) diff --git a/traces_test.go b/traces_test.go index abef8c5..b3640de 100644 --- a/traces_test.go +++ b/traces_test.go @@ -6,18 +6,22 @@ import ( ) func TestStackTrace_GetTraces(t *testing.T) { + var SeverityError Severity = "error" + var SeverityCritical Severity = "critical" + var TypeValidating Type = "validating" + var TypeParsing Type = "parsing" + type fields struct { - Severity Severity - Type Type - Location string - Position *Position - Wrapped *StackTrace - Err error - Message string - WrappingMessage string - Info StructInfo - List []*StackTrace - typeIsSet bool + Severity *Severity + Type *Type + Location *Location + Position *Position + Wrapped *StackTrace + Err error + Message string + Info StructInfo + List []*StackTrace + typeIsSet bool } type args struct { opts []TracesOpt @@ -31,19 +35,19 @@ func TestStackTrace_GetTraces(t *testing.T) { { name: "Test simple", fields: fields{ - Severity: SeverityError, - Type: TypeValidating, - Location: "/tmp/location.raml", + Severity: &SeverityError, + Type: &TypeValidating, + Location: func() *Location { s := Location("/tmp/location.raml"); return &s }(), Message: "error message", }, want: []Trace{ { Stack: []Stack{ { - LinePos: "/tmp/location.raml:1", - Severity: SeverityError, + LinePos: func() *string { s := "/tmp/location.raml:1"; return &s }(), + Severity: &SeverityError, Message: "error message", - Type: TypeValidating, + Type: &TypeValidating, }, }, }, @@ -52,16 +56,15 @@ func TestStackTrace_GetTraces(t *testing.T) { { name: "Test with wrapped", fields: fields{ - Severity: SeverityError, - Type: TypeValidating, - Location: "/tmp/location.raml", - Position: &Position{1, 2}, - Message: "error message", - WrappingMessage: "wrapping message", + Severity: &SeverityError, + Type: &TypeValidating, + Location: func() *Location { s := Location("/tmp/location.raml"); return &s }(), + Position: &Position{1, 2}, + Message: "error message", Wrapped: &StackTrace{ - Severity: SeverityCritical, - Type: TypeParsing, - Location: "/tmp/location2.raml", + Severity: &SeverityCritical, + Type: &TypeParsing, + Location: func() *Location { s := Location("/tmp/location2.raml"); return &s }(), Position: &Position{3, 4}, Message: "error message 2", }, @@ -70,16 +73,16 @@ func TestStackTrace_GetTraces(t *testing.T) { { Stack: []Stack{ { - LinePos: "/tmp/location.raml:1:2", - Severity: SeverityError, - Message: "wrapping message: error message", - Type: TypeValidating, + LinePos: func() *string { s := "/tmp/location.raml:1:2"; return &s }(), + Severity: &SeverityError, + Message: "error message", + Type: &TypeValidating, }, { - LinePos: "/tmp/location2.raml:3:4", - Severity: SeverityCritical, + LinePos: func() *string { s := "/tmp/location2.raml:3:4"; return &s }(), + Severity: &SeverityCritical, Message: "error message 2", - Type: TypeParsing, + Type: &TypeParsing, }, }, }, @@ -88,32 +91,30 @@ func TestStackTrace_GetTraces(t *testing.T) { { name: "Test with wrapped and EnsureDuplicates", fields: fields{ - Severity: SeverityError, - Type: TypeValidating, - Location: "/tmp/location.raml", - Position: &Position{1, 2}, - Message: "error message", - WrappingMessage: "wrapping message", + Severity: &SeverityError, + Type: &TypeValidating, + Location: func() *Location { s := Location("/tmp/location.raml"); return &s }(), + Position: &Position{1, 2}, + Message: "error message", Wrapped: &StackTrace{ - Severity: SeverityCritical, - Type: TypeParsing, - Location: "/tmp/location2.raml", + Severity: &SeverityCritical, + Type: &TypeParsing, + Location: func() *Location { s := Location("/tmp/location2.raml"); return &s }(), Position: &Position{3, 4}, Message: "error message 2", }, List: []*StackTrace{ { - Severity: SeverityCritical, - Type: TypeParsing, - Location: "/tmp/location3.raml", - Position: &Position{5, 6}, - Message: "error message 3", - WrappingMessage: "wrapping message 3", + Severity: &SeverityCritical, + Type: &TypeParsing, + Location: func() *Location { s := Location("/tmp/location2.raml"); return &s }(), + Position: &Position{5, 6}, + Message: "error message 3", Wrapped: &StackTrace{ - Severity: SeverityCritical, - Type: TypeParsing, - Location: "/tmp/location2.raml", // duplicate location - Position: &Position{3, 4}, // duplicate position + Severity: &SeverityCritical, + Type: &TypeParsing, + Location: func() *Location { s := Location("/tmp/location2.raml"); return &s }(), // duplicate location + Position: &Position{3, 4}, // duplicate position Message: "error message 4", }, }, @@ -128,16 +129,16 @@ func TestStackTrace_GetTraces(t *testing.T) { { Stack: []Stack{ { - LinePos: "/tmp/location.raml:1:2", - Severity: SeverityError, - Message: "wrapping message: error message", - Type: TypeValidating, + LinePos: func() *string { s := "/tmp/location.raml:1:2"; return &s }(), + Severity: &SeverityError, + Message: "error message", + Type: &TypeValidating, }, { - LinePos: "/tmp/location2.raml:3:4", - Severity: SeverityCritical, + LinePos: func() *string { s := "/tmp/location2.raml:3:4"; return &s }(), + Severity: &SeverityCritical, Message: "error message 2", - Type: TypeParsing, + Type: &TypeParsing, }, }, }, @@ -146,23 +147,23 @@ func TestStackTrace_GetTraces(t *testing.T) { { name: "Test with list", fields: fields{ - Severity: SeverityError, - Type: TypeValidating, - Location: "/tmp/location.raml", + Severity: &SeverityError, + Type: &TypeValidating, + Location: func() *Location { s := Location("/tmp/location.raml"); return &s }(), Position: &Position{1, 2}, Message: "error message", List: []*StackTrace{ { - Severity: SeverityCritical, - Type: TypeParsing, - Location: "/tmp/location2.raml", + Severity: &SeverityCritical, + Type: &TypeParsing, + Location: func() *Location { s := Location("/tmp/location2.raml"); return &s }(), Position: &Position{3, 4}, Message: "error message 2", }, { - Severity: SeverityCritical, - Type: TypeParsing, - Location: "/tmp/location3.raml", + Severity: &SeverityCritical, + Type: &TypeParsing, + Location: func() *Location { s := Location("/tmp/location3.raml"); return &s }(), Position: &Position{5, 6}, Message: "error message 3", }, @@ -172,30 +173,30 @@ func TestStackTrace_GetTraces(t *testing.T) { { Stack: []Stack{ { - LinePos: "/tmp/location.raml:1:2", - Severity: SeverityError, + LinePos: func() *string { s := "/tmp/location.raml:1:2"; return &s }(), + Severity: &SeverityError, Message: "error message", - Type: TypeValidating, + Type: &TypeValidating, }, }, }, { Stack: []Stack{ { - LinePos: "/tmp/location2.raml:3:4", - Severity: SeverityCritical, + LinePos: func() *string { s := "/tmp/location2.raml:3:4"; return &s }(), + Severity: &SeverityCritical, Message: "error message 2", - Type: TypeParsing, + Type: &TypeParsing, }, }, }, { Stack: []Stack{ { - LinePos: "/tmp/location3.raml:5:6", - Severity: SeverityCritical, + LinePos: func() *string { s := "/tmp/location3.raml:5:6"; return &s }(), + Severity: &SeverityCritical, Message: "error message 3", - Type: TypeParsing, + Type: &TypeParsing, }, }, }, @@ -205,17 +206,16 @@ func TestStackTrace_GetTraces(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { st := &StackTrace{ - Severity: tt.fields.Severity, - Type: tt.fields.Type, - Location: tt.fields.Location, - Position: tt.fields.Position, - Wrapped: tt.fields.Wrapped, - Err: tt.fields.Err, - Message: tt.fields.Message, - WrappingMessage: tt.fields.WrappingMessage, - Info: tt.fields.Info, - List: tt.fields.List, - typeIsSet: tt.fields.typeIsSet, + Severity: tt.fields.Severity, + Type: tt.fields.Type, + Location: tt.fields.Location, + Position: tt.fields.Position, + Wrapped: tt.fields.Wrapped, + Err: tt.fields.Err, + Message: tt.fields.Message, + Info: tt.fields.Info, + List: tt.fields.List, + typeIsSet: tt.fields.typeIsSet, } if got := st.GetTraces(tt.args.opts...); !reflect.DeepEqual(got, tt.want) { t.Errorf("getTraces() = %v, want %v", got, tt.want)