From a6012a91345b7a413d7c3f8468faf3d8cd101e7f Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 18 Aug 2023 17:05:54 +0300 Subject: [PATCH 01/14] t.Parallel for all that's possible --- assert/goid_test.go | 1 + internal/debug/debug_test.go | 15 ++++++++++++--- internal/handler/handler_test.go | 11 +++++++++-- internal/str/str_test.go | 9 ++++----- try/out_test.go | 2 ++ 5 files changed, 28 insertions(+), 10 deletions(-) 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/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_test.go b/internal/handler/handler_test.go index d748c5f..62f1983 100644 --- a/internal/handler/handler_test.go +++ b/internal/handler/handler_test.go @@ -12,6 +12,7 @@ import ( ) func TestProcess(t *testing.T) { + t.Parallel() 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) { + t.Parallel() if handler.WorkToDo(tt.args.Any, tt.args.Err) { handler.Process(&tt.args.Info) @@ -142,6 +145,7 @@ func Handle() { } func TestPreProcess_debug(t *testing.T) { + t.Parallel() // 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 +163,7 @@ func TestPreProcess_debug(t *testing.T) { } func TestPreProcess(t *testing.T) { + t.Parallel() type args struct { handler.Info a []any @@ -225,8 +230,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) { + t.Parallel() 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/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) From a4478e4583b69d76e84e348f1e7e7a4924e429fd Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 18 Aug 2023 17:07:06 +0300 Subject: [PATCH 02/14] tests for issue-18 & t.Parallel for rest --- err2_test.go | 205 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 186 insertions(+), 19 deletions(-) diff --git a/err2_test.go b/err2_test.go index 18caecb..b893172 100644 --- a/err2_test.go +++ b/err2_test.go @@ -24,7 +24,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 +33,7 @@ func TestTry_noError(_ *testing.T) { } func TestDefault_Error(t *testing.T) { + t.Parallel() var err error defer err2.Handle(&err) @@ -41,6 +43,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 +52,176 @@ 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()) }) - // This is the handler we are thesting! - defer err2.Handle(&err, func(noerr bool) { - handlerCalled = noerr + t.Run("noerr is error order is first and 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, false, "if error occurs/reset, this cannot happen") + handlerCalled = noerr + }) + + 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 err + }) + try.To1(throw()) }) - try.To(noErr()) + 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()) + }) + + 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, 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 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, 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 +276,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 +289,7 @@ func TestPanickingCatchAll(t *testing.T) { } func TestPanickingCarryOn_Handle(t *testing.T) { + t.Parallel() type args struct { f func() } @@ -165,8 +320,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 +333,7 @@ func TestPanickingCarryOn_Handle(t *testing.T) { } func TestPanicking_Handle(t *testing.T) { + t.Parallel() type args struct { f func() (err error) } @@ -286,8 +444,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 +463,7 @@ func TestPanicking_Handle(t *testing.T) { } func TestPanicking_Catch(t *testing.T) { + t.Parallel() type args struct { f func() } @@ -331,8 +492,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 +505,7 @@ func TestPanicking_Catch(t *testing.T) { } func TestCatch_Error(t *testing.T) { + t.Parallel() defer err2.Catch() try.To1(throw()) @@ -350,6 +514,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 +533,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 +556,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 From a44fce96cc28d8b4b93e34766506974a5ca11685 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 18 Aug 2023 17:07:52 +0300 Subject: [PATCH 03/14] issue-18 bug fix: err wasn't checked, but now is --- internal/handler/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index ce7583b..e6d29e4 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 From 8589b10df1f99d526a499e0733531342cea1af2c Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 18 Aug 2023 17:39:38 +0300 Subject: [PATCH 04/14] handler_test uses pkg vars, cannot t.Parallel --- internal/handler/handler_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/internal/handler/handler_test.go b/internal/handler/handler_test.go index 62f1983..027e9d7 100644 --- a/internal/handler/handler_test.go +++ b/internal/handler/handler_test.go @@ -12,7 +12,7 @@ import ( ) func TestProcess(t *testing.T) { - t.Parallel() + // NOTE. No Parallel, uses pkg lvl variables type args struct { handler.Info } @@ -117,7 +117,7 @@ func TestProcess(t *testing.T) { for _, ttv := range tests { tt := ttv t.Run(tt.name, func(t *testing.T) { - t.Parallel() + // NOTE. No Parallel, uses pkg lvl variables if handler.WorkToDo(tt.args.Any, tt.args.Err) { handler.Process(&tt.args.Info) @@ -145,7 +145,8 @@ func Handle() { } func TestPreProcess_debug(t *testing.T) { - t.Parallel() + // 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 @@ -163,7 +164,7 @@ func TestPreProcess_debug(t *testing.T) { } func TestPreProcess(t *testing.T) { - t.Parallel() + // NOTE. No Parallel, uses pkg lvl variables type args struct { handler.Info a []any @@ -233,7 +234,7 @@ func TestPreProcess(t *testing.T) { for _, ttv := range tests { tt := ttv t.Run(tt.name, func(t *testing.T) { - t.Parallel() + // NOTE. No Parallel, uses pkg lvl variables if handler.WorkToDo(tt.args.Any, tt.args.Err) && len(tt.args.a) > 0 { From 3398b4962abfe1f18c1983af42a6d97ee5ce08ef Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 18 Aug 2023 18:12:27 +0300 Subject: [PATCH 05/14] version info 0.9.41 to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) 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 From a1e64a4912a89501a86499b171d58dbc060a21ba Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 18 Aug 2023 18:33:12 +0300 Subject: [PATCH 06/14] godoc typos & better example with multi-error --- err2.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 From 049027fbc3b63cc888e399427149e9a2f073a835 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 19 Aug 2023 10:15:06 +0300 Subject: [PATCH 07/14] asserter methods deprecated --- assert/asserter.go | 9 +++++++++ 1 file changed, 9 insertions(+) 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 { From c432749e053f2144e7079ca029ba2002804ada1d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 19 Aug 2023 10:15:53 +0300 Subject: [PATCH 08/14] assert pkg documentation for asserters --- assert/assert.go | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) 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") From 56256a77217d4d8a4244c415bbac70887291d9b0 Mon Sep 17 00:00:00 2001 From: lainio Date: Sun, 20 Aug 2023 14:09:26 +0300 Subject: [PATCH 09/14] more norrr test cases --- err2_test.go | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/err2_test.go b/err2_test.go index b893172..0463d38 100644 --- a/err2_test.go +++ b/err2_test.go @@ -113,7 +113,7 @@ func TestHandle_noerrHandler(t *testing.T) { try.To1(throw()) }) - t.Run("noerr is error order is first and error happens", func(t *testing.T) { + t.Run("noerr is first and error happens", func(t *testing.T) { t.Parallel() var err error var handlerCalled bool @@ -173,6 +173,8 @@ func TestHandle_noerrHandler(t *testing.T) { 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... @@ -189,6 +191,44 @@ func TestHandle_noerrHandler(t *testing.T) { 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) + 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, true) // 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, true) // 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()) + }) + t.Run("noerr handler is middle of MANY and NO error happens", func(t *testing.T) { t.Parallel() var err error @@ -197,6 +237,8 @@ func TestHandle_noerrHandler(t *testing.T) { test.Require(t, handlerCalled) }() + 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... From b756914e667de88b02b198364b25137ba9f440f9 Mon Sep 17 00:00:00 2001 From: lainio Date: Sun, 20 Aug 2023 14:10:02 +0300 Subject: [PATCH 10/14] more go doc to try.ToX functions --- try/try.go | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/try/try.go b/try/try.go index f7c10c8..f476ae9 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 detects the 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 detects the 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 detects the 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 detects the 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 From 9cf5db4f6e8d7b8b282ce3ada3427ddd4d84828c Mon Sep 17 00:00:00 2001 From: lainio Date: Sun, 20 Aug 2023 16:27:10 +0300 Subject: [PATCH 11/14] english in try.To docs --- try/try.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/try/try.go b/try/try.go index f476ae9..07e5137 100644 --- a/try/try.go +++ b/try/try.go @@ -70,7 +70,7 @@ import ( // 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 detects the error, the error is not handled, and the +// 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. // @@ -86,7 +86,7 @@ func To(err error) { // 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 detects the error, the error is not handled, +// 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. // @@ -101,7 +101,7 @@ func To1[T any](v T, err error) T { // 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 detects the error, the error is not handled, +// 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. // @@ -116,7 +116,7 @@ func To2[T, U any](v1 T, v2 U, err error) (T, U) { // 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 detects the error, the error is +// 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) { From c1538a08a55160c2dd1608b9ca1672c7cbf2e74c Mon Sep 17 00:00:00 2001 From: lainio Date: Sun, 20 Aug 2023 17:55:45 +0300 Subject: [PATCH 12/14] better tests for noerr case and chaingin handlers --- err2_test.go | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/err2_test.go b/err2_test.go index 0463d38..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 } @@ -113,12 +115,16 @@ func TestHandle_noerrHandler(t *testing.T) { try.To1(throw()) }) - t.Run("noerr is first and error happens", func(t *testing.T) { + t.Run("noerr is first and error happens with many handlers", func(t *testing.T) { t.Parallel() - var err error - var handlerCalled bool + 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! @@ -127,11 +133,18 @@ func TestHandle_noerrHandler(t *testing.T) { 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 err + return finalAnnotatedErr }) try.To1(throw()) }) @@ -191,7 +204,7 @@ func TestHandle_noerrHandler(t *testing.T) { try.To(noErr()) }) - t.Run("noerr handler is first of MANY and error happens UNTIL reset", func(t *testing.T) { + 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 @@ -202,14 +215,14 @@ func TestHandle_noerrHandler(t *testing.T) { // This is the handler we are thesting! defer err2.Handle(&err, func(noerr bool) { - test.Require(t, true) + 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 + // this is the err handler that -- RESETS -- the error to nil defer err2.Handle(&err, func(err error) error { - test.Require(t, true) // helps fast debugging + 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 @@ -219,7 +232,7 @@ func TestHandle_noerrHandler(t *testing.T) { }) defer err2.Handle(&err, func(err error) error { - test.Require(t, true) // helps fast debugging + 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 @@ -237,6 +250,7 @@ func TestHandle_noerrHandler(t *testing.T) { test.Require(t, handlerCalled) }() + defer err2.Handle(&err) defer err2.Handle(&err) defer err2.Handle(&err, func(err error) error { From 9924243dc38fda0fb60cc3ae8c8804f59762e02c Mon Sep 17 00:00:00 2001 From: lainio Date: Sun, 20 Aug 2023 17:56:32 +0300 Subject: [PATCH 13/14] keep werr up-to-date in Info --- internal/handler/handler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index e6d29e4..a97df33 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -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 From 7b5af1d01f025812a72a247c55dd9548807bafb3 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 20 Aug 2023 21:28:32 +0300 Subject: [PATCH 14/14] new migration helpers for marginal cases --- scripts/README.md | 8 ++++++-- scripts/functions.sh | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) 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"