Merge pull request #686 from harmony-ek/fix_out-of-range_index_#676
Fix out-of-range indexpull/692/head
commit
5d19a8ebb5
@ -0,0 +1,124 @@ |
||||
// Package ctxerror provides a context-aware error facility.
|
||||
//
|
||||
// Inspired by log15-style (semi-)structured logging,
|
||||
// it also provides a log15 bridge.
|
||||
package ctxerror |
||||
|
||||
//go:generate mockgen -source ctxerror.go -destination mock/ctxerror.go
|
||||
|
||||
import ( |
||||
"fmt" |
||||
) |
||||
|
||||
// CtxError is a context-aware error container.
|
||||
type CtxError interface { |
||||
// Error returns a fully formatted message, with context info.
|
||||
Error() string |
||||
|
||||
// Message returns the bare error message, without context info.
|
||||
Message() string |
||||
|
||||
// Contexts returns message contexts.
|
||||
// Caller shall not modify the returned map.
|
||||
Contexts() map[string]interface{} |
||||
|
||||
// WithCause chains an error after the receiver.
|
||||
// It returns the merged/chained instance,
|
||||
// where the message is "<receiver.Message>: <c.Message>",
|
||||
// and with contexts merged (ones in c takes precedence).
|
||||
WithCause(c error) CtxError |
||||
} |
||||
|
||||
type ctxError struct { |
||||
msg string |
||||
ctx map[string]interface{} |
||||
} |
||||
|
||||
// New creates and returns a new context-aware error.
|
||||
func New(msg string, ctx ...interface{}) CtxError { |
||||
e := &ctxError{msg: msg, ctx: make(map[string]interface{})} |
||||
e.updateCtx(ctx...) |
||||
return e |
||||
} |
||||
|
||||
func (e *ctxError) updateCtx(ctx ...interface{}) { |
||||
var name string |
||||
if len(ctx)%2 == 1 { |
||||
ctx = append(ctx, nil) |
||||
} |
||||
for idx, value := range ctx { |
||||
if idx%2 == 0 { |
||||
name = value.(string) |
||||
} else { |
||||
e.ctx[name] = value |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Error returns a fully formatted message, with context info.
|
||||
func (e *ctxError) Error() string { |
||||
s := e.msg |
||||
for k, v := range e.ctx { |
||||
s += fmt.Sprintf(", %s=%#v", k, v) |
||||
} |
||||
return s |
||||
} |
||||
|
||||
// Message returns the bare error message, without context info.
|
||||
func (e *ctxError) Message() string { |
||||
return e.msg |
||||
} |
||||
|
||||
// Contexts returns message contexts.
|
||||
// Caller shall not modify the returned map.
|
||||
func (e *ctxError) Contexts() map[string]interface{} { |
||||
return e.ctx |
||||
} |
||||
|
||||
// WithCause chains an error after the receiver.
|
||||
// It returns the merged/chained instance,
|
||||
// where the message is “<receiver.Message>: <c.Message>”,
|
||||
// and with contexts merged (ones in c takes precedence).
|
||||
func (e *ctxError) WithCause(c error) CtxError { |
||||
r := &ctxError{msg: e.msg + ": ", ctx: make(map[string]interface{})} |
||||
for k, v := range e.ctx { |
||||
r.ctx[k] = v |
||||
} |
||||
switch c := c.(type) { |
||||
case *ctxError: |
||||
r.msg += c.msg |
||||
for k, v := range c.ctx { |
||||
r.ctx[k] = v |
||||
} |
||||
default: |
||||
r.msg += c.Error() |
||||
} |
||||
return r |
||||
} |
||||
|
||||
// Log15Func is a log15-compatible logging function.
|
||||
type Log15Func func(msg string, ctx ...interface{}) |
||||
|
||||
// Log15Logger logs something with a log15-style logging function.
|
||||
type Log15Logger interface { |
||||
Log15(f Log15Func) |
||||
} |
||||
|
||||
// Log15 logs the receiver with a log15-style logging function.
|
||||
func (e *ctxError) Log15(f Log15Func) { |
||||
var ctx []interface{} |
||||
for k, v := range e.ctx { |
||||
ctx = append(ctx, k, v) |
||||
} |
||||
f(e.msg, ctx...) |
||||
} |
||||
|
||||
// Log15 logs an error with a log15-style logging function.
|
||||
// It handles both regular errors and Log15Logger-compliant errors.
|
||||
func Log15(f Log15Func, e error) { |
||||
if e15, ok := e.(Log15Logger); ok { |
||||
e15.Log15(f) |
||||
} else { |
||||
f(e.Error()) |
||||
} |
||||
} |
@ -0,0 +1,363 @@ |
||||
package ctxerror |
||||
|
||||
import ( |
||||
"errors" |
||||
"reflect" |
||||
"testing" |
||||
) |
||||
|
||||
func TestNew(t *testing.T) { |
||||
type args struct { |
||||
msg string |
||||
ctx []interface{} |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want CtxError |
||||
}{ |
||||
{ |
||||
name: "Empty", |
||||
args: args{msg: "", ctx: []interface{}{}}, |
||||
want: &ctxError{msg: "", ctx: map[string]interface{}{}}, |
||||
}, |
||||
{ |
||||
name: "Regular", |
||||
args: args{msg: "omg", ctx: []interface{}{"wtf", 1, "bbq", 2}}, |
||||
want: &ctxError{msg: "omg", ctx: map[string]interface{}{"wtf": 1, "bbq": 2}}, |
||||
}, |
||||
{ |
||||
name: "Truncated", |
||||
args: args{ |
||||
msg: "omg", |
||||
ctx: []interface{}{"wtf", 1, "bbq" /* missing value... */}, |
||||
}, |
||||
want: &ctxError{ |
||||
msg: "omg", |
||||
ctx: map[string]interface{}{"wtf": 1, "bbq": /* becomes */ nil}, |
||||
}, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := New(tt.args.msg, tt.args.ctx...); !reflect.DeepEqual(got, tt.want) { |
||||
t.Errorf("New() = %#v, want %#v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_ctxError_updateCtx(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
before, after map[string]interface{} |
||||
delta []interface{} |
||||
}{ |
||||
{ |
||||
name: "Empty", |
||||
before: map[string]interface{}{"omg": 1, "wtf": 2, "bbq": 3}, |
||||
delta: []interface{}{}, |
||||
after: map[string]interface{}{"omg": 1, "wtf": 2, "bbq": 3}, |
||||
}, |
||||
{ |
||||
name: "Regular", |
||||
before: map[string]interface{}{"omg": 1, "wtf": 2, "bbq": 3}, |
||||
delta: []interface{}{"omg", 10, "wtf", 20}, |
||||
after: map[string]interface{}{"omg": 10, "wtf": 20, "bbq": 3}, |
||||
}, |
||||
{ |
||||
name: "Truncated", |
||||
before: map[string]interface{}{"omg": 1, "wtf": 2, "bbq": 3}, |
||||
delta: []interface{}{"omg", 10, "wtf" /* missing value... */}, |
||||
after: map[string]interface{}{"omg": 10, "wtf": /* becomes */ nil, "bbq": 3}, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
e := &ctxError{msg: tt.name, ctx: tt.before} |
||||
e.updateCtx(tt.delta...) |
||||
if !reflect.DeepEqual(e.ctx, tt.after) { |
||||
t.Errorf("expected ctx %#v != %#v seen", tt.after, e.ctx) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_ctxError_Error(t *testing.T) { |
||||
type fields struct { |
||||
msg string |
||||
ctx map[string]interface{} |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
fields fields |
||||
want string |
||||
}{ |
||||
{ |
||||
name: "AllEmpty", |
||||
fields: fields{msg: "", ctx: map[string]interface{}{}}, |
||||
want: "", |
||||
}, |
||||
{ |
||||
name: "CtxEmpty", |
||||
fields: fields{msg: "omg", ctx: map[string]interface{}{}}, |
||||
want: "omg", |
||||
}, |
||||
{ |
||||
name: "MsgEmpty", |
||||
fields: fields{msg: "", ctx: map[string]interface{}{"wtf": "bbq"}}, |
||||
want: ", wtf=\"bbq\"", |
||||
}, |
||||
{ |
||||
name: "Regular", |
||||
fields: fields{msg: "omg", ctx: map[string]interface{}{"wtf": "bbq"}}, |
||||
want: "omg, wtf=\"bbq\"", |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
e := &ctxError{ |
||||
msg: tt.fields.msg, |
||||
ctx: tt.fields.ctx, |
||||
} |
||||
if got := e.Error(); got != tt.want { |
||||
t.Errorf("Error() = %#v, want %#v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_ctxError_Message(t *testing.T) { |
||||
type fields struct { |
||||
msg string |
||||
ctx map[string]interface{} |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
fields fields |
||||
want string |
||||
}{ |
||||
{ |
||||
name: "AllEmpty", |
||||
fields: fields{msg: "", ctx: map[string]interface{}{}}, |
||||
want: "", |
||||
}, |
||||
{ |
||||
name: "CtxEmpty", |
||||
fields: fields{msg: "omg", ctx: map[string]interface{}{}}, |
||||
want: "omg", |
||||
}, |
||||
{ |
||||
name: "MsgEmpty", |
||||
fields: fields{msg: "", ctx: map[string]interface{}{"wtf": "bbq"}}, |
||||
want: "", |
||||
}, |
||||
{ |
||||
name: "Regular", |
||||
fields: fields{msg: "omg", ctx: map[string]interface{}{"wtf": "bbq"}}, |
||||
want: "omg", |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
e := &ctxError{ |
||||
msg: tt.fields.msg, |
||||
ctx: tt.fields.ctx, |
||||
} |
||||
if got := e.Message(); got != tt.want { |
||||
t.Errorf("Message() = %#v, want %#v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_ctxError_Contexts(t *testing.T) { |
||||
type fields struct { |
||||
msg string |
||||
ctx map[string]interface{} |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
fields fields |
||||
want map[string]interface{} |
||||
}{ |
||||
{ |
||||
name: "Empty", |
||||
fields: fields{msg: "", ctx: map[string]interface{}{}}, |
||||
want: map[string]interface{}{}, |
||||
}, |
||||
{ |
||||
name: "Regular", |
||||
fields: fields{ |
||||
msg: "", |
||||
ctx: map[string]interface{}{"omg": 1, "wtf": 2, "bbq": 3}, |
||||
}, |
||||
want: map[string]interface{}{"omg": 1, "wtf": 2, "bbq": 3}, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
e := &ctxError{ |
||||
msg: tt.fields.msg, |
||||
ctx: tt.fields.ctx, |
||||
} |
||||
if got := e.Contexts(); !reflect.DeepEqual(got, tt.want) { |
||||
t.Errorf("Contexts() = %#v, want %#v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_ctxError_WithCause(t *testing.T) { |
||||
type fields struct { |
||||
msg string |
||||
ctx map[string]interface{} |
||||
} |
||||
type args struct { |
||||
c error |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
fields fields |
||||
args args |
||||
want CtxError |
||||
}{ |
||||
{ |
||||
name: "CtxError", |
||||
fields: fields{ |
||||
msg: "hello", |
||||
ctx: map[string]interface{}{"omg": 1, "wtf": 2}, |
||||
}, |
||||
args: args{c: &ctxError{ |
||||
msg: "world", |
||||
ctx: map[string]interface{}{"wtf": 20, "bbq": 30}, |
||||
}}, |
||||
want: &ctxError{ |
||||
msg: "hello: world", |
||||
ctx: map[string]interface{}{"omg": 1, "wtf": 20, "bbq": 30}, |
||||
}, |
||||
}, |
||||
{ |
||||
name: "RegularError", |
||||
fields: fields{ |
||||
msg: "hello", |
||||
ctx: map[string]interface{}{"omg": 1, "wtf": 2}, |
||||
}, |
||||
args: args{c: errors.New("world")}, |
||||
want: &ctxError{ |
||||
msg: "hello: world", |
||||
ctx: map[string]interface{}{"omg": 1, "wtf": 2}, |
||||
}, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
e := &ctxError{ |
||||
msg: tt.fields.msg, |
||||
ctx: tt.fields.ctx, |
||||
} |
||||
if got := e.WithCause(tt.args.c); !reflect.DeepEqual(got, tt.want) { |
||||
t.Errorf("WithCause() = %#v, want %#v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_ctxError_Log15(t *testing.T) { |
||||
type fields struct { |
||||
msg string |
||||
ctx map[string]interface{} |
||||
} |
||||
type want struct { |
||||
msg string |
||||
ctx []interface{} |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
fields fields |
||||
want want |
||||
}{ |
||||
{ |
||||
name: "Empty", |
||||
fields: fields{msg: "", ctx: map[string]interface{}{}}, |
||||
want: want{msg: "", ctx: nil}, |
||||
}, |
||||
{ |
||||
name: "Regular", |
||||
fields: fields{msg: "hello", ctx: map[string]interface{}{"omg": 1}}, |
||||
want: want{msg: "hello", ctx: []interface{}{"omg", 1}}, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
called := false |
||||
f := func(msg string, ctx ...interface{}) { |
||||
called = true |
||||
if msg != tt.want.msg { |
||||
t.Errorf("expected message %#v != %#v seen", |
||||
tt.want.msg, msg) |
||||
} |
||||
if !reflect.DeepEqual(ctx, tt.want.ctx) { |
||||
t.Errorf("expected ctx %#v != %#v seen", ctx, tt.want.ctx) |
||||
} |
||||
} |
||||
e := &ctxError{ |
||||
msg: tt.fields.msg, |
||||
ctx: tt.fields.ctx, |
||||
} |
||||
e.Log15(f) |
||||
if !called { |
||||
t.Error("logging func not called") |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestLog15(t *testing.T) { |
||||
type args struct { |
||||
e error |
||||
} |
||||
type want struct { |
||||
msg string |
||||
ctx []interface{} |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want want |
||||
}{ |
||||
{ |
||||
name: "Regular", |
||||
args: args{e: errors.New("hello")}, |
||||
want: want{msg: "hello", ctx: nil}, |
||||
}, |
||||
{ |
||||
name: "CtxError", |
||||
args: args{e: &ctxError{ |
||||
msg: "hello", |
||||
ctx: map[string]interface{}{"omg": 1}, |
||||
}}, |
||||
want: want{msg: "hello", ctx: []interface{}{"omg", 1}}, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
called := false |
||||
f := func(msg string, ctx ...interface{}) { |
||||
called = true |
||||
if msg != tt.want.msg { |
||||
t.Errorf("expected message %#v != %#v seen", |
||||
tt.want.msg, msg) |
||||
} |
||||
if !reflect.DeepEqual(ctx, tt.want.ctx) { |
||||
t.Errorf("expected ctx %#v != %#v seen", |
||||
tt.want.ctx, ctx) |
||||
} |
||||
} |
||||
Log15(f, tt.args.e) |
||||
if !called { |
||||
t.Errorf("logging func not called") |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,125 @@ |
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ctxerror.go
|
||||
|
||||
// Package mock_ctxerror is a generated GoMock package.
|
||||
package mock_ctxerror |
||||
|
||||
import ( |
||||
gomock "github.com/golang/mock/gomock" |
||||
ctxerror "github.com/harmony-one/harmony/internal/ctxerror" |
||||
reflect "reflect" |
||||
) |
||||
|
||||
// MockCtxError is a mock of CtxError interface
|
||||
type MockCtxError struct { |
||||
ctrl *gomock.Controller |
||||
recorder *MockCtxErrorMockRecorder |
||||
} |
||||
|
||||
// MockCtxErrorMockRecorder is the mock recorder for MockCtxError
|
||||
type MockCtxErrorMockRecorder struct { |
||||
mock *MockCtxError |
||||
} |
||||
|
||||
// NewMockCtxError creates a new mock instance
|
||||
func NewMockCtxError(ctrl *gomock.Controller) *MockCtxError { |
||||
mock := &MockCtxError{ctrl: ctrl} |
||||
mock.recorder = &MockCtxErrorMockRecorder{mock} |
||||
return mock |
||||
} |
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockCtxError) EXPECT() *MockCtxErrorMockRecorder { |
||||
return m.recorder |
||||
} |
||||
|
||||
// Error mocks base method
|
||||
func (m *MockCtxError) Error() string { |
||||
m.ctrl.T.Helper() |
||||
ret := m.ctrl.Call(m, "Error") |
||||
ret0, _ := ret[0].(string) |
||||
return ret0 |
||||
} |
||||
|
||||
// Error indicates an expected call of Error
|
||||
func (mr *MockCtxErrorMockRecorder) Error() *gomock.Call { |
||||
mr.mock.ctrl.T.Helper() |
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockCtxError)(nil).Error)) |
||||
} |
||||
|
||||
// Message mocks base method
|
||||
func (m *MockCtxError) Message() string { |
||||
m.ctrl.T.Helper() |
||||
ret := m.ctrl.Call(m, "Message") |
||||
ret0, _ := ret[0].(string) |
||||
return ret0 |
||||
} |
||||
|
||||
// Message indicates an expected call of Message
|
||||
func (mr *MockCtxErrorMockRecorder) Message() *gomock.Call { |
||||
mr.mock.ctrl.T.Helper() |
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Message", reflect.TypeOf((*MockCtxError)(nil).Message)) |
||||
} |
||||
|
||||
// Contexts mocks base method
|
||||
func (m *MockCtxError) Contexts() map[string]interface{} { |
||||
m.ctrl.T.Helper() |
||||
ret := m.ctrl.Call(m, "Contexts") |
||||
ret0, _ := ret[0].(map[string]interface{}) |
||||
return ret0 |
||||
} |
||||
|
||||
// Contexts indicates an expected call of Contexts
|
||||
func (mr *MockCtxErrorMockRecorder) Contexts() *gomock.Call { |
||||
mr.mock.ctrl.T.Helper() |
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Contexts", reflect.TypeOf((*MockCtxError)(nil).Contexts)) |
||||
} |
||||
|
||||
// WithCause mocks base method
|
||||
func (m *MockCtxError) WithCause(c error) ctxerror.CtxError { |
||||
m.ctrl.T.Helper() |
||||
ret := m.ctrl.Call(m, "WithCause", c) |
||||
ret0, _ := ret[0].(ctxerror.CtxError) |
||||
return ret0 |
||||
} |
||||
|
||||
// WithCause indicates an expected call of WithCause
|
||||
func (mr *MockCtxErrorMockRecorder) WithCause(c interface{}) *gomock.Call { |
||||
mr.mock.ctrl.T.Helper() |
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithCause", reflect.TypeOf((*MockCtxError)(nil).WithCause), c) |
||||
} |
||||
|
||||
// MockLog15Logger is a mock of Log15Logger interface
|
||||
type MockLog15Logger struct { |
||||
ctrl *gomock.Controller |
||||
recorder *MockLog15LoggerMockRecorder |
||||
} |
||||
|
||||
// MockLog15LoggerMockRecorder is the mock recorder for MockLog15Logger
|
||||
type MockLog15LoggerMockRecorder struct { |
||||
mock *MockLog15Logger |
||||
} |
||||
|
||||
// NewMockLog15Logger creates a new mock instance
|
||||
func NewMockLog15Logger(ctrl *gomock.Controller) *MockLog15Logger { |
||||
mock := &MockLog15Logger{ctrl: ctrl} |
||||
mock.recorder = &MockLog15LoggerMockRecorder{mock} |
||||
return mock |
||||
} |
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockLog15Logger) EXPECT() *MockLog15LoggerMockRecorder { |
||||
return m.recorder |
||||
} |
||||
|
||||
// Log15 mocks base method
|
||||
func (m *MockLog15Logger) Log15(f ctxerror.Log15Func) { |
||||
m.ctrl.T.Helper() |
||||
m.ctrl.Call(m, "Log15", f) |
||||
} |
||||
|
||||
// Log15 indicates an expected call of Log15
|
||||
func (mr *MockLog15LoggerMockRecorder) Log15(f interface{}) *gomock.Call { |
||||
mr.mock.ctrl.T.Helper() |
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log15", reflect.TypeOf((*MockLog15Logger)(nil).Log15), f) |
||||
} |
Loading…
Reference in new issue