diff --git a/README.md b/README.md index 0678ae9..14457c3 100644 --- a/README.md +++ b/README.md @@ -430,6 +430,10 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). ### Latest Release +##### 0.9.41 +- Issue #18: **bug fixed**: noerr-handler had to be the last one of the err2 + handlers + ##### 0.9.40 - Significant performance boost for: `defer err2.Handle/Catch()` - **3x faster happy path than the previous version, which is now equal to diff --git a/assert/assert.go b/assert/assert.go index 7f376aa..58772d8 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -16,34 +16,45 @@ import ( type defInd = uint32 const ( - // Production is the best asserter for most uses. The assertion violations - // are treated as Go error values. And only a pragmatic caller info is - // automatically included into the error message like file name, line - // number, and caller function. + // Production (pkg default) is the best asserter for most uses. The + // assertion violations are treated as Go error values. And only a + // pragmatic caller info is automatically included into the error message + // like file name, line number, and caller function, all in one line: + // + // copy file: main.go:37: CopyFile(): assertion violation: string shouldn't be empty Production defInd = 0 + iota // Development is the best asserter for most development uses. The // assertion violations are treated as Go error values. And a formatted // caller info is automatically included to the error message like file - // name, line number, and caller function. + // name, line number, and caller function. Everything in a beautiful + // multi-line message: + // + // -------------------------------- + // Assertion Fault at: + // main.go:37 CopyFile(): + // assertion violation: string shouldn't be empty + // -------------------------------- Development // Test minimalistic asserter for unit test use. More pragmatic is the - // TestFull asserter (default). + // TestFull asserter (test default). // - // Use this asserter if your IDE/editor doesn't support long file names, or - // for temporary problem solving for your programming environment. + // Use this asserter if your IDE/editor doesn't support full file names and + // it relies a relative path (Go standard). You can use this also if you + // need temporary problem solving for your programming environment. Test - // TestFull asserter (default). The TestFull asserter includes caller info - // and call stack for unit testing, similarly like err2's error traces. + // TestFull asserter (test default). The TestFull asserter includes the + // caller info and the call stack for unit testing, similarly like err2's + // error traces. // // The call stack produced by the test asserts can be used over Go module // boundaries. For example, if your app and it's sub packages both use // err2/assert for unit testing and runtime checks, the runtime assertions - // will be automatically converted to test asserts on the fly. If any of - // the runtime asserts in sub packages fails during the app tests, the - // current app test fails too. + // will be automatically converted to test asserts. If any of the runtime + // asserts of the sub packages fails during the app tests, the app test + // fails as well. // // Note, that the cross-module assertions produce long file names (path // included), and some of the Go test result parsers cannot handle that. @@ -53,7 +64,7 @@ const ( TestFull // Debug asserter transforms assertion violations to panics which is the - // patter that e.g. Go's standard library uses: + // pattern that e.g. Go's standard library uses: // // if p == nil { // panic("pkg: ptr cannot be nil") diff --git a/assert/asserter.go b/assert/asserter.go index ddc10b8..5974e0f 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -48,6 +48,7 @@ const ( const officialTestOutputPrefix = " " // NoImplementation always fails with no implementation. +// Deprecated: use e.g. assert.NotImplemented(), only default asserter is used. func (asserter Asserter) NoImplementation(a ...any) { asserter.reportAssertionFault("not implemented", a...) } @@ -55,6 +56,7 @@ func (asserter Asserter) NoImplementation(a ...any) { // True asserts that term is true. If not it panics with the given formatting // string. Note! This and Truef are the most performant of all the assertion // functions. +// Deprecated: use e.g. assert.That(), only default asserter is used. func (asserter Asserter) True(term bool, a ...any) { if !term { asserter.reportAssertionFault("assertion fault", a...) @@ -63,6 +65,7 @@ func (asserter Asserter) True(term bool, a ...any) { // Truef asserts that term is true. If not it panics with the given formatting // string. +// Deprecated: use e.g. assert.That(), only default asserter is used. func (asserter Asserter) Truef(term bool, format string, a ...any) { if !term { if asserter.hasStackTrace() { @@ -76,6 +79,7 @@ func (asserter Asserter) Truef(term bool, format string, a ...any) { // panics/errors (current Asserter) with the given msg. Note! This is very slow // (before we have generics). If you need performance use EqualInt. It's not so // convenient, though. +// Deprecated: use e.g. assert.Len(), only default asserter is used. func (asserter Asserter) Len(obj any, length int, a ...any) { ok, l := getLen(obj) if !ok { @@ -90,6 +94,7 @@ func (asserter Asserter) Len(obj any, length int, a ...any) { // EqualInt asserts that integers are equal. If not it panics/errors (current // Asserter) with the given msg. +// Deprecated: use e.g. assert.Equal(), only default asserter is used. func (asserter Asserter) EqualInt(val, want int, a ...any) { if want != val { defMsg := fmt.Sprintf("got %d, want %d", val, want) @@ -101,6 +106,7 @@ func (asserter Asserter) EqualInt(val, want int, a ...any) { // panics/errors (current Asserter) with the given msg. Note! This is very slow // (before we have generics). If you need performance use EqualInt. It's not so // convenient, though. +// Deprecated: use e.g. assert.Len(), only default asserter is used. func (asserter Asserter) Lenf(obj any, length int, format string, a ...any) { args := combineArgs(format, a) asserter.Len(obj, length, args...) @@ -108,6 +114,7 @@ func (asserter Asserter) Lenf(obj any, length int, format string, a ...any) { // Empty asserts that length of the object is zero. If not it panics with the // given formatting string. Note! This is slow. +// Deprecated: use e.g. assert.Empty(), only default asserter is used. func (asserter Asserter) Empty(obj any, msg ...any) { ok, l := getLen(obj) if !ok { @@ -122,6 +129,7 @@ func (asserter Asserter) Empty(obj any, msg ...any) { // NotEmptyf asserts that length of the object greater than zero. If not it // panics with the given formatting string. Note! This is slow. +// Deprecated: use e.g. assert.NotEmpty(), only default asserter is used. func (asserter Asserter) NotEmptyf(obj any, format string, msg ...any) { args := combineArgs(format, msg) asserter.Empty(obj, args...) @@ -129,6 +137,7 @@ func (asserter Asserter) NotEmptyf(obj any, format string, msg ...any) { // NotEmpty asserts that length of the object greater than zero. If not it // panics with the given formatting string. Note! This is slow. +// Deprecated: use e.g. assert.NotEmpty(), only default asserter is used. func (asserter Asserter) NotEmpty(obj any, msg ...any) { ok, l := getLen(obj) if !ok { diff --git a/assert/goid_test.go b/assert/goid_test.go index 451e199..2e53415 100644 --- a/assert/goid_test.go +++ b/assert/goid_test.go @@ -5,6 +5,7 @@ import ( ) func TestGoid(t *testing.T) { + t.Parallel() stackBytes := []byte(`goroutine 518 [running]: `) diff --git a/err2.go b/err2.go index d5ed484..c9cf1db 100644 --- a/err2.go +++ b/err2.go @@ -63,7 +63,9 @@ var ( // an second argument: // // defer err2.Handle(&err, func(err error) error { -// os.Remove(dst) +// if rmErr := os.Remove(dst); rmErr != nil { +// return fmt.Errorf("%w: cleanup error: %w", err, rmErr) +// } // return err // }) // @@ -71,8 +73,8 @@ var ( // panic handler function: // // defer err2.Handle(&err, -// err2.Err( func(error) { os.Remove(dst) }), // err2.Err keeps it short -// func(p any) {} // panic handler, it's stops panics, you can re-throw +// err2.Err( func(error) { os.Remove(dst) }), // err2.Err() keeps it short +// func(p any) {} // <- handler stops panics, re-throw or not // ) func Handle(err *error, a ...any) { // This and others are similar but we need to call `recover` here because diff --git a/err2_test.go b/err2_test.go index 18caecb..b1540da 100644 --- a/err2_test.go +++ b/err2_test.go @@ -11,8 +11,10 @@ import ( "github.com/lainio/err2/try" ) +const errStringInThrow = "this is an ERROR" + func throw() (string, error) { - return "", fmt.Errorf("this is an ERROR") + return "", fmt.Errorf(errStringInThrow) } func twoStrNoThrow() (string, string, error) { return "test", "test", nil } @@ -24,7 +26,8 @@ func noErr() error { return nil } -func TestTry_noError(_ *testing.T) { +func TestTry_noError(t *testing.T) { + t.Parallel() try.To1(noThrow()) try.To2(twoStrNoThrow()) try.To2(intStrNoThrow()) @@ -32,6 +35,7 @@ func TestTry_noError(_ *testing.T) { } func TestDefault_Error(t *testing.T) { + t.Parallel() var err error defer err2.Handle(&err) @@ -41,6 +45,7 @@ func TestDefault_Error(t *testing.T) { } func TestTry_Error(t *testing.T) { + t.Parallel() var err error defer err2.Handle(&err, func(err error) error { return err }) @@ -49,27 +54,230 @@ func TestTry_Error(t *testing.T) { t.Fail() // If everything works we are never here } -func TestHandle_NoError(t *testing.T) { - var err error - var handlerCalled bool - defer func() { - test.Require(t, handlerCalled) - }() - defer err2.Handle(&err, func(err error) error { - // this should not be called, so lets try to fuckup things... - handlerCalled = false - return err +func TestHandle_noerrHandler(t *testing.T) { + t.Parallel() + t.Run("noerr handler in ONLY one and NO error happens", func(t *testing.T) { + t.Parallel() + var err error + var handlerCalled bool + defer func() { + test.Require(t, handlerCalled) + }() + // This is the handler we are thesting! + defer err2.Handle(&err, func(noerr bool) { + handlerCalled = noerr + }) + + try.To(noErr()) + }) + + t.Run("noerr handler is the last and NO error happens", func(t *testing.T) { + t.Parallel() + var err error + var handlerCalled bool + defer func() { + test.Require(t, handlerCalled) + }() + defer err2.Handle(&err, func(err error) error { + // this should not be called, so lets try to fuckup things... + handlerCalled = false + test.Require(t, false) + return err + }) + + // This is the handler we are thesting! + defer err2.Handle(&err, func(noerr bool) { + handlerCalled = noerr + }) + + try.To(noErr()) + }) + + t.Run("noerr handler is the last and error happens", func(t *testing.T) { + t.Parallel() + var err error + var handlerCalled bool + defer func() { + test.Require(t, !handlerCalled) + }() + defer err2.Handle(&err, func(err error) error { + handlerCalled = false + test.Require(t, true, "error should be handled") + return err + }) + + // This is the handler we are thesting! + defer err2.Handle(&err, func(noerr bool) { + test.Require(t, noerr) + handlerCalled = noerr + }) + + try.To1(throw()) + }) + + t.Run("noerr is first and error happens with many handlers", func(t *testing.T) { + t.Parallel() + var ( + err error + finalAnnotatedErr = fmt.Errorf("err: %v", errStringInThrow) + handlerCalled bool + ) + defer func() { + test.Require(t, !handlerCalled) + test.RequireEqual(t, err.Error(), finalAnnotatedErr.Error()) + }() + + // This is the handler we are thesting! + defer err2.Handle(&err, func(noerr bool) { + test.Require(t, false, "if error occurs/reset, this cannot happen") + handlerCalled = noerr + }) + + // important! test that our handler doesn't change the current error + // and it's not nil + defer err2.Handle(&err, func(er error) error { + test.Require(t, er != nil, "er val: ", er, err) + return er + }) + + defer err2.Handle(&err, func(err error) error { + // this should not be called, so lets try to fuckup things... + handlerCalled = false + test.Require(t, err != nil) + return finalAnnotatedErr + }) + try.To1(throw()) + }) + + t.Run("noerr handler is first and NO error happens", func(t *testing.T) { + t.Parallel() + var err error + var handlerCalled bool + defer func() { + test.Require(t, handlerCalled) + }() + + // This is the handler we are thesting! + defer err2.Handle(&err, func(noerr bool) { + test.Require(t, noerr) + handlerCalled = noerr + }) + + defer err2.Handle(&err, func(err error) error { + test.Require(t, false, "no error to handle!") + // this should not be called, so lets try to fuckup things... + handlerCalled = false // see first deferred function + return err + }) + try.To(noErr()) }) - // This is the handler we are thesting! - defer err2.Handle(&err, func(noerr bool) { - handlerCalled = noerr + t.Run("noerr handler is first of MANY and NO error happens", func(t *testing.T) { + t.Parallel() + var err error + var handlerCalled bool + defer func() { + test.Require(t, handlerCalled) + }() + + // This is the handler we are thesting! + defer err2.Handle(&err, func(noerr bool) { + test.Require(t, noerr) + handlerCalled = noerr + }) + + defer err2.Handle(&err) + + defer err2.Handle(&err, func(err error) error { + test.Require(t, false, "no error to handle!") + // this should not be called, so lets try to fuckup things... + handlerCalled = false // see first deferred function + return err + }) + + defer err2.Handle(&err, func(err error) error { + test.Require(t, false, "no error to handle!") + // this should not be called, so lets try to fuckup things... + handlerCalled = false // see first deferred function + return err + }) + try.To(noErr()) + }) + + t.Run("noerr handler is first of MANY and error happens UNTIL RESET", func(t *testing.T) { + t.Parallel() + var err error + var noerrHandlerCalled, errHandlerCalled bool + defer func() { + test.Require(t, noerrHandlerCalled) + test.Require(t, errHandlerCalled) + }() + + // This is the handler we are thesting! + defer err2.Handle(&err, func(noerr bool) { + test.Require(t, true) // we are here, for debugging + test.Require(t, noerr) + noerrHandlerCalled = noerr + }) + + // this is the err handler that -- RESETS -- the error to nil + defer err2.Handle(&err, func(err error) error { + test.Require(t, err != nil) // helps fast debugging + + // this should not be called, so lets try to fuckup things... + noerrHandlerCalled = false // see first deferred function + // keep the track that we have been here + errHandlerCalled = true // see first deferred function + return nil + }) + + defer err2.Handle(&err, func(err error) error { + test.Require(t, err != nil) // helps fast debugging + // this should not be called, so lets try to fuckup things... + noerrHandlerCalled = false // see first deferred function + + errHandlerCalled = true // see first deferred function + return err + }) + try.To1(throw()) }) - try.To(noErr()) + t.Run("noerr handler is middle of MANY and NO error happens", func(t *testing.T) { + t.Parallel() + var err error + var handlerCalled bool + defer func() { + test.Require(t, handlerCalled) + }() + + defer err2.Handle(&err) + defer err2.Handle(&err) + + defer err2.Handle(&err, func(err error) error { + test.Require(t, false, "no error to handle!") + // this should not be called, so lets try to fuckup things... + handlerCalled = false // see first deferred function + return err + }) + + // This is the handler we are thesting! + defer err2.Handle(&err, func(noerr bool) { + test.Require(t, noerr) + handlerCalled = noerr + }) + + defer err2.Handle(&err, func(err error) error { + test.Require(t, false, "no error to handle!") + // this should not be called, so lets try to fuckup things... + handlerCalled = false // see first deferred function + return err + }) + try.To(noErr()) + }) } func TestPanickingCatchAll(t *testing.T) { + t.Parallel() type args struct { f func() } @@ -124,8 +332,10 @@ func TestPanickingCatchAll(t *testing.T) { nil, }, } - for _, tt := range tests { + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { + t.Parallel() defer func() { test.Require(t, recover() == nil, "panics should NOT carry on") }() @@ -135,6 +345,7 @@ func TestPanickingCatchAll(t *testing.T) { } func TestPanickingCarryOn_Handle(t *testing.T) { + t.Parallel() type args struct { f func() } @@ -165,8 +376,10 @@ func TestPanickingCarryOn_Handle(t *testing.T) { nil, }, } - for _, tt := range tests { + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { + t.Parallel() defer func() { test.Require(t, recover() != nil, "panics should went thru when not our errors") }() @@ -176,6 +389,7 @@ func TestPanickingCarryOn_Handle(t *testing.T) { } func TestPanicking_Handle(t *testing.T) { + t.Parallel() type args struct { f func() (err error) } @@ -286,8 +500,10 @@ func TestPanicking_Handle(t *testing.T) { myErr, }, } - for _, tt := range tests { + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { + t.Parallel() defer func() { r := recover() if tt.wants == nil { @@ -303,6 +519,7 @@ func TestPanicking_Handle(t *testing.T) { } func TestPanicking_Catch(t *testing.T) { + t.Parallel() type args struct { f func() } @@ -331,8 +548,10 @@ func TestPanicking_Catch(t *testing.T) { nil, }, } - for _, tt := range tests { + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { + t.Parallel() defer func() { test.Require(t, recover() == nil, "panics should NOT carry on") }() @@ -342,6 +561,7 @@ func TestPanicking_Catch(t *testing.T) { } func TestCatch_Error(t *testing.T) { + t.Parallel() defer err2.Catch() try.To1(throw()) @@ -350,6 +570,7 @@ func TestCatch_Error(t *testing.T) { } func Test_TryOutError(t *testing.T) { + t.Parallel() defer err2.Catch(func(err error) error { test.RequireEqual(t, err.Error(), "fails: test: this is an ERROR", "=> we should catch right error str here") @@ -368,6 +589,7 @@ func Test_TryOutError(t *testing.T) { } func TestCatch_Panic(t *testing.T) { + t.Parallel() panicHandled := false defer func() { // when err2.Catch's panic handler works fine, panic is handled @@ -390,6 +612,7 @@ func TestCatch_Panic(t *testing.T) { } func TestSetErrorTracer(t *testing.T) { + t.Parallel() w := err2.ErrorTracer() test.Require(t, w == nil, "error tracer should be nil") var w1 io.Writer diff --git a/internal/debug/debug_test.go b/internal/debug/debug_test.go index 8d34d0a..638486a 100644 --- a/internal/debug/debug_test.go +++ b/internal/debug/debug_test.go @@ -37,6 +37,7 @@ func TestFullName(t *testing.T) { } func TestIsAnchor(t *testing.T) { + t.Parallel() type args struct { input string StackInfo @@ -78,8 +79,10 @@ func TestIsAnchor(t *testing.T) { "github.com/lainio/err2/try.To1[...](...)", StackInfo{"lainio/err2", "", 0, nil, nil}}, true}, } - for _, tt := range tests { + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { + t.Parallel() test.Require(t, tt.retval == tt.isAnchor(tt.input), "equal") }) } @@ -132,6 +135,7 @@ func TestIsFuncAnchor(t *testing.T) { } func TestFnLNro(t *testing.T) { + t.Parallel() tests := []struct { name string input string @@ -141,8 +145,10 @@ func TestFnLNro(t *testing.T) { " /Users/harrilainio/go/pkg/mod/github.com/lainio/err2@v0.8.5/internal/handler/handler.go:69 +0xbc", 69}, } - for _, tt := range tests { + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { + t.Parallel() output := fnLNro(tt.input) test.Require(t, output == tt.output, output) }) @@ -210,6 +216,7 @@ func TestStackPrint_noLimits(t *testing.T) { } func TestStackPrintForTest(t *testing.T) { + t.Parallel() tests := []struct { name string input string @@ -220,8 +227,10 @@ func TestStackPrintForTest(t *testing.T) { {"short", input, outputForTestLvl2, 2}, //{"real test trace", inputFromTest, outputFromTest, 4}, } - for _, tt := range tests { + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { + t.Parallel() r := strings.NewReader(tt.input) w := new(bytes.Buffer) printStackForTest(r, w, tt.lvl) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index ce7583b..a97df33 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -67,7 +67,7 @@ func NilNoop(err error) error { return err } // func ErrorNoop(err error) {} func (i *Info) callNilHandler() { - if i.CheckHandler != nil { + if i.CheckHandler != nil && i.safeErr() == nil { i.CheckHandler(true) // there is no err and user wants to handle OK with our pkg: // nothing more to do here after callNilHandler call @@ -79,6 +79,7 @@ func (i *Info) callNilHandler() { } if i.NilHandler != nil { *i.Err = i.NilHandler(i.werr) + i.werr = *i.Err // remember change both our errors! } else { i.defaultNilHandler() } @@ -101,6 +102,7 @@ func (i *Info) callErrorHandler() { i.checkErrorTracer() if i.ErrorHandler != nil { *i.Err = i.ErrorHandler(i.Any.(error)) + i.werr = *i.Err // remember change both our errors! } else { i.defaultErrorHandler() } @@ -254,6 +256,7 @@ func PreProcess(errPtr *error, info *Info, a ...any) error { // named return val. Reason is unknown. err := x.Whom(errPtr != nil, *errPtr, nil) info.Err = &err + info.werr = *info.Err // remember change both our errors! // We want the function who sets the handler, i.e. calls the // err2.Handle function via defer. Because call stack is in reverse diff --git a/internal/handler/handler_test.go b/internal/handler/handler_test.go index d748c5f..027e9d7 100644 --- a/internal/handler/handler_test.go +++ b/internal/handler/handler_test.go @@ -12,6 +12,7 @@ import ( ) func TestProcess(t *testing.T) { + // NOTE. No Parallel, uses pkg lvl variables type args struct { handler.Info } @@ -113,8 +114,10 @@ func TestProcess(t *testing.T) { errStr: "error", }}, } - for _, tt := range tests { + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { + // NOTE. No Parallel, uses pkg lvl variables if handler.WorkToDo(tt.args.Any, tt.args.Err) { handler.Process(&tt.args.Info) @@ -142,6 +145,8 @@ func Handle() { } func TestPreProcess_debug(t *testing.T) { + // NOTE. No Parallel, uses pkg lvl variables + // in real case PreProcess is called from Handle function. So, we make our // own Handle here. Now our test function name will be the Handle caller // and that's what error stack tracing is all about @@ -159,6 +164,7 @@ func TestPreProcess_debug(t *testing.T) { } func TestPreProcess(t *testing.T) { + // NOTE. No Parallel, uses pkg lvl variables type args struct { handler.Info a []any @@ -225,8 +231,10 @@ func TestPreProcess(t *testing.T) { errStr: "", }}, } - for _, tt := range tests { + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { + // NOTE. No Parallel, uses pkg lvl variables if handler.WorkToDo(tt.args.Any, tt.args.Err) && len(tt.args.a) > 0 { diff --git a/internal/str/str_test.go b/internal/str/str_test.go index f9e0b77..dbb13c6 100644 --- a/internal/str/str_test.go +++ b/internal/str/str_test.go @@ -46,13 +46,12 @@ func TestDecamel(t *testing.T) { {"unnatural method and anonym", args{"(**DIDAgent)...AssertWallet...Func1"}, "didagent assert wallet: func1"}, {"from spf13 cobra", args{"bot.glob..func5"}, "bot: glob: func5"}, } - for _, tt := range tests { - s := tt.args.s - want := tt.want + for _, ttv := range tests { + tt := ttv t.Run(tt.name, func(t *testing.T) { t.Parallel() - got := str.Decamel(s) - test.RequireEqual(t, got, want) + got := str.Decamel(tt.args.s) + test.RequireEqual(t, got, tt.want) }) } } diff --git a/scripts/README.md b/scripts/README.md index d8780b0..642e98d 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -9,7 +9,7 @@ successfully from type variables (see below) to Go generics API. This readme will guide you to use auto-migration scripts when ever we deprecate functions or make something obsolete. -### Auto-Migration of v0.9.40 +### Auto-Migration of v0.9.40 & v0.9.41 The version 0.9.40 is a major update because of the performance and API change. We have managed to eliminate `defer` slowdown. Our benchmarks are 3x faster than @@ -52,7 +52,11 @@ Follow these steps: 1. Use `git diff` or similar to skimming that all changes seem to be OK. Here you have an opportunity to start use new features of `err2` like logging. 1. You are ready to commit changes. - +1. *Problem solving tip.* Use following command to search helper functions: + ```shell + migr-name.sh -h todo_catch + migr-name.sh -h repl_catch + ``` #### Manual Migration With a Location List Follow these steps check do you have migration needs for v0.9.40: diff --git a/scripts/functions.sh b/scripts/functions.sh index 88bf7c0..29266f8 100644 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -475,6 +475,33 @@ repl_handle_func() { } search_catch_multi='(^\s*)(defer err2\.Catch\(func\(err error\) \{)([\s\S]*?)(^\s*\}\)$)' +search_catch_multi_with_panic_handler='(^\s*)(defer err2\.Catch\(func\(err error\) \{)([\s\S]*?)(^\s*\}, func\()(\w* any\) \{)' +# '^\s*\}, func\(\w* any\) \{' 1 2 3 4 5 + +# --- make Catch panic easier +# '^\s*\}, func\(\w* interface\{\}\) \{' +search_catch_old_signature='(^\s*\}, func\(\w* )(interface\{\})(\) \{)' +# 1 2 3 + +todo_catchold_sig() { + vlog "searching old Catch panic handler signatures" + ag "$search_catch_old_signature" +} + +repl_catchold_sig() { + vlog "replacing old Catch panic handler signatures" + check_commit "$search_catch_old_signature" '\1any\3' +} + +todo_catchp_func() { + vlog "searching old error Catchers" + ag "$search_catch_multi_with_panic_handler" +} + +repl_catchp_func() { + vlog "replacing old error Catchers with panic" + check_commit "$search_catch_multi_with_panic_handler" '\1defer err2.Catch(err2.Err(func(err error) {\3\1}), func(\5' +} todo_catch_func() { vlog "searching old error Catchers" diff --git a/try/out_test.go b/try/out_test.go index 000daff..d032771 100644 --- a/try/out_test.go +++ b/try/out_test.go @@ -90,6 +90,7 @@ func ExampleResult1_Logf() { } func TestResult2_Logf(t *testing.T) { + t.Parallel() // Set log tracing to stdout that we can see it in Example output. In // normal cases that would be a Logging stream or stderr. err2.SetLogTracer(os.Stdout) @@ -110,6 +111,7 @@ func TestResult2_Logf(t *testing.T) { } func TestResult_Handle(t *testing.T) { + t.Parallel() // try out f() |err| handle to show how to stop propagate error callFn := func(mode int) (err error) { defer err2.Handle(&err) diff --git a/try/try.go b/try/try.go index f7c10c8..07e5137 100644 --- a/try/try.go +++ b/try/try.go @@ -67,34 +67,58 @@ import ( "github.com/lainio/err2" ) -// To is a helper function to call functions which returns (error) -// and check the error value. If an error occurs, it panics the error where err2 -// handlers can catch it if needed. +// To is a helper function to call functions which returns an error value and +// check the value. If an error occurs, it panics the error so that err2 +// handlers can catch it if needed. Note! If no err2.Handle or err2.Catch exist +// in the call stack and To panics an error, the error is not handled, and the +// app will crash. When using try.To functions you should always have proper +// err2.Handle or err2.Catch statements in the call stack. +// +// defer err2.Handle(&err) +// ... +// try.To(w.Close()) func To(err error) { if err != nil { panic(err) } } -// To1 is a helper function to call functions which returns (T, error) -// and check the error value. If an error occurs, it panics the error where err2 -// handlers can catch it if needed. +// To1 is a helper function to call functions which returns values (T, error) +// and check the error value. If an error occurs, it panics the error so that +// err2 handlers can catch it if needed. Note! If no err2.Handle or err2.Catch +// exist in the call stack and To1 panics an error, the error is not handled, +// and the app will crash. When using try.To1 functions you should always have +// proper err2.Handle or err2.Catch statements in the call stack. +// +// defer err2.Handle(&err) +// ... +// r := try.To1(os.Open(src)) func To1[T any](v T, err error) T { To(err) return v } -// To2 is a helper function to call functions which returns (T, U, error) -// and check the error value. If an error occurs, it panics the error where err2 -// handlers can catch it if needed. +// To2 is a helper function to call functions which returns values (T, U, error) +// and check the error value. If an error occurs, it panics the error so that +// err2 handlers can catch it if needed. Note! If no err2.Handle or err2.Catch +// exist in the call stack and To2 panics an error, the error is not handled, +// and the app will crash. When using try.To2 functions you should always have +// proper err2.Handle or err2.Catch statements in the call stack. +// +// defer err2.Handle(&err) +// ... +// kid, pk := try.To2(keys.CreateAndExportPubKeyBytes(kms.ED25519)) func To2[T, U any](v1 T, v2 U, err error) (T, U) { To(err) return v1, v2 } -// To3 is a helper function to call functions which returns (T, U, V, error) -// and check the error value. If an error occurs, it panics the error where err2 -// handlers can catch it if needed. +// To3 is a helper function to call functions which returns values (T, U, V, +// error) and check the error value. If an error occurs, it panics the error so +// that err2 handlers can catch it if needed. Note! If no err2.Handle or +// err2.Catch exist in the call stack and To3 panics an error, the error is +// not handled, and the app will crash. When using try.To3 functions you should +// always have proper err2.Handle or err2.Catch statements in the call stack. func To3[T, U, V any](v1 T, v2 U, v3 V, err error) (T, U, V) { To(err) return v1, v2, v3