From 5d5f0cb3a2d59f9fe7897cc3ac5a6ba578532337 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 24 Mar 2024 17:12:00 +0200 Subject: [PATCH 01/78] debugging help --- internal/debug/debug.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/debug/debug.go b/internal/debug/debug.go index eb94254..c820403 100644 --- a/internal/debug/debug.go +++ b/internal/debug/debug.go @@ -105,7 +105,9 @@ func (si StackInfo) canPrint(s string, anchorLine, i int) (ok bool) { // runtime.Stack and processed to proper format to be shown in test output by // starting from stackLevel. func PrintStackForTest(w io.Writer, stackLevel int) { - stackBuf := bytes.NewBuffer(debug.Stack()) + stack := debug.Stack() + //println(string(stack)) + stackBuf := bytes.NewBuffer(stack) printStackForTest(stackBuf, w, stackLevel) } From b8bc6812560881115b10668065bd715176c2c994 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 24 Mar 2024 17:12:24 +0200 Subject: [PATCH 02/78] fix: if test panics, adaptive callstack print for runtime.Error --- assert/assert.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 28a1600..fce9d00 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -228,29 +228,32 @@ func PopTester() { return } + var stackLvl = 5 // amount of functions before we're here + var framesToSkip = 3 // how many fn calls there is before FuncName call + var msg string switch t := r.(type) { case string: msg = t case runtime.Error: + stackLvl-- // move stack trace cursor + framesToSkip++ // see fatal(), skip 1 more when runtime panics msg = t.Error() case error: msg = t.Error() default: - msg = "test panic catch" + msg = fmt.Sprintf("panic: %v", t) } // First, print the call stack. Note that we aren't support full error // tracing with unit test logging. However, using it has proved the top // level error stack as more enough. Even so that we could consider using // it for normal error stack traces if it would be possible. - const stackLvl = 6 // amount of functions before we're here debug.PrintStackForTest(os.Stderr, stackLvl) // Now that call stack errors are printed, if any. Let's print the actual // line that caused the error, i.e., was throwing the error. Note that we // are here in the 'catch-function'. - const framesToSkip = 4 // how many fn calls there is before FuncName call fatal("assertion catching: "+msg, framesToSkip) } From c4d5fe72187c88d88f6446d8eb6adfccddc927eb Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 20 May 2024 18:40:14 +0300 Subject: [PATCH 03/78] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9473df2..9fade7c 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,7 @@ notExist := try.Is(r2.err, plugin.ErrNotExist) 3. Finally, it calls `try.To` for the non nil error, and we already know what then happens: nearest `err2.Handle` gets it first. -These `try.Is` functions help cleanup mesh idiomatic Go, i.e. mixing happy and +These `try.Is` functions help cleanup mess idiomatic Go, i.e. mixing happy and error path, leads to. For more information see the examples in the documentation of both functions. From 76d2364ea224927ec74167636790bb2e750c330d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 29 Jul 2024 22:49:22 +0300 Subject: [PATCH 04/78] new asserts for slice, chan & map --- assert/assert.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/assert/assert.go b/assert/assert.go index fce9d00..9fae9dd 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -367,6 +367,30 @@ func SNil[S ~[]T, T any](s S, a ...any) { } } +// CNil asserts that the channel is nil. If it is not it panics/errors +// (default Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +func CNil[C ~chan T, T any](c C, a ...any) { + if c != nil { + defMsg := assertionMsg + ": channel shouldn't be nil" + current().reportAssertionFault(defMsg, a) + } +} + +// MNil asserts that the map is nil. If it is not it panics/errors (default +// Asserter) the auto-generated (args appended) message. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +func MNil[M ~map[T]U, T comparable, U any](m M, a ...any) { + if m != nil { + defMsg := assertionMsg + ": map should be nil" + current().reportAssertionFault(defMsg, a) + } +} + // SNotNil asserts that the slice is not nil. If it is it panics/errors (default // Asserter) the auto-generated (args appended) message. // @@ -735,6 +759,24 @@ func Empty(obj string, a ...any) { } } +// SEmpty asserts that the slice is empty. If it is NOT, it panics/errors +// (according the current Asserter) with the auto-generated message. You can +// append the generated got-want message by using optional message arguments. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// +// Note! This is reasonably fast but not as fast as [That] because of lacking +// inlining for the current implementation of Go's type parametric functions. +func SEmpty[S ~[]T, T any](obj S, a ...any) { + l := len(obj) + + if l != 0 { + defMsg := assertionMsg + ": slice should be empty" + current().reportAssertionFault(defMsg, a) + } +} + // SNotEmpty asserts that the slice is not empty. If it is, it panics/errors // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. @@ -753,6 +795,26 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { } } +// MEmpty asserts that the map is empty. If it is NOT, it panics/errors +// (according the current Asserter) with the auto-generated message. You can +// append the generated got-want message by using optional message arguments. +// You can append the generated got-want message by using optional message +// arguments. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +// +// Note! This is reasonably fast but not as fast as [That] because of lacking +// inlining for the current implementation of Go's type parametric functions. +func MEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { + l := len(obj) + + if l != 0 { + defMsg := assertionMsg + ": map should be empty" + current().reportAssertionFault(defMsg, a) + } +} + // MNotEmpty asserts that the map is not empty. If it is, it panics/errors // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. From af3ea4b938702215732416e7ada1817afd353799 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 31 Jul 2024 15:32:20 +0300 Subject: [PATCH 05/78] wrapping comment, perf point of view --- internal/handler/handler.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 19dd9f1..9ba52e2 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -60,6 +60,12 @@ type Info struct { } const ( + // Wrapping is best default because we can be in the situation where we + // have received meaningful sentinel error which we need to wrap, even + // automatically. If we'd have used %v (no wrapping) we would lose the + // sentinel error info and we couldn't use annotation for this error. + // However, we should think about this more later from performance point of + // view. wrapError = ": %w" ) From 4a8efcbd08b5e36816ff48294c7542b8d9e724bd Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 31 Jul 2024 15:33:11 +0300 Subject: [PATCH 06/78] Less and Greater for numbers + example tests --- assert/assert.go | 26 ++++++++++++++++++++++++++ assert/assert_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/assert/assert.go b/assert/assert.go index 9fae9dd..db1f787 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -865,6 +865,32 @@ func Error(err error, a ...any) { } } +// Greater asserts that the value is greater than want. If it is not it panics +// and builds a violation message. Thanks to inlining, the performance penalty +// is equal to a single 'if-statement' that is almost nothing. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +func Greater[T Number](val, want T, a ...any) { + if val <= want { + defMsg := fmt.Sprintf(assertionMsg+": got '%v', want <= '%v'", val, want) + current().reportAssertionFault(defMsg, a) + } +} + +// Less asserts that the value is less than want. If it is not it panics and +// builds a violation message. Thanks to inlining, the performance penalty is +// equal to a single 'if-statement' that is almost nothing. +// +// Note that when [Plain] asserter is used ([SetDefault]), optional arguments +// are used to override the auto-generated assert violation message. +func Less[T Number](val, want T, a ...any) { + if val >= want { + defMsg := fmt.Sprintf(assertionMsg+": got '%v', want >= '%v'", val, want) + current().reportAssertionFault(defMsg, a) + } +} + // Zero asserts that the value is 0. If it is not it panics and builds a // violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. diff --git a/assert/assert_test.go b/assert/assert_test.go index 2db8b34..6106b49 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -191,6 +191,36 @@ func ExampleSShorter() { // Output: sample: assert_test.go:186: ExampleSShorter.func1(): assertion violation: got '1', should be shorter than '0': optional message (test_str) } +func ExampleLess() { + sample := func(b int8) (err error) { + defer err2.Handle(&err, "sample") + + assert.Equal(b, 1) // ok + assert.Less(b, 2) // ok + assert.Less(b, 1) // not ok + return err + } + var b int8 = 1 + err := sample(b) + fmt.Printf("%v", err) + // Output: sample: assert_test.go:200: ExampleLess.func1(): assertion violation: got '1', want >= '1' +} + +func ExampleGreater() { + sample := func(b int8) (err error) { + defer err2.Handle(&err, "sample") + + assert.Equal(b, 2) // ok + assert.Greater(b, 1) // ok + assert.Greater(b, 2) // not ok + return err + } + var b int8 = 2 + err := sample(b) + fmt.Printf("%v", err) + // Output: sample: assert_test.go:215: ExampleGreater.func1(): assertion violation: got '2', want <= '2' +} + func assertZero(i int) { assert.Zero(i) } From 94cc27edd52a27a9735a1366ab4e7fee32f4cc02 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 31 Jul 2024 17:04:18 +0300 Subject: [PATCH 07/78] about panic stack traces --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9fade7c..2e3b3f3 100644 --- a/README.md +++ b/README.md @@ -497,10 +497,10 @@ We have used the `err2` and `assert` packages in several projects. The results have been so far very encouraging: - If you forget to use handler, but you use checks from the package, you will -get panics (and optionally stack traces) if an error occurs. That is much better -than getting unrelated panic somewhere else in the code later. There have also -been cases when code reports error correctly because the 'upper' handler catches -it. +get panics on errors (and optimized stack traces that can be suppressed). That +is much better than getting unrelated panic somewhere else in the code later. +There have also been cases when code reports error correctly because the 'upper' +handler catches it. - Because the use of `err2.Handle` is so easy, error messages are much better and informative. When using `err2.Handle`'s automatic annotation your error From 01fe351b1c1b64fb8bf57e6c58b6ce18fd647f31 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 15 Aug 2024 14:51:26 +0300 Subject: [PATCH 08/78] remove deprecated configs --- .golangci.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index bc511f1..5bebe3a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -33,12 +33,7 @@ linters-settings: # Default: 5 min-complexity: 8 gomnd: - settings: - mnd: - # don't include the "operation" and "assign" - checks: argument,case,condition,return govet: - check-shadowing: true lll: line-length: 140 maligned: From ee26b3d233dc9a12f11d667746a52859559f3a3f Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 15 Aug 2024 15:12:55 +0300 Subject: [PATCH 09/78] readability and typos --- err2_test.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/err2_test.go b/err2_test.go index ce2cda9..ca32d71 100644 --- a/err2_test.go +++ b/err2_test.go @@ -64,7 +64,7 @@ func TestHandle_noerrHandler(t *testing.T) { defer func() { test.Require(t, handlerCalled) }() - // This is the handler we are thesting! + // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { handlerCalled = noerr }) @@ -86,7 +86,7 @@ func TestHandle_noerrHandler(t *testing.T) { return err }) - // This is the handler we are thesting! + // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { handlerCalled = noerr }) @@ -101,16 +101,18 @@ func TestHandle_noerrHandler(t *testing.T) { defer func() { test.Require(t, !handlerCalled) }() + + // This is the handler we are testing! defer err2.Handle(&err, func(err error) error { + test.Require(t, !handlerCalled) 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 + // This is the handler we are testing! AND it's not called in error. + defer err2.Handle(&err, func(bool) { + test.Require(t, false, "when error this is not called") }) try.To1(throw()) @@ -122,13 +124,15 @@ func TestHandle_noerrHandler(t *testing.T) { err error finalAnnotatedErr = fmt.Errorf("err: %v", errStringInThrow) handlerCalled bool + callCount int ) defer func() { test.Require(t, !handlerCalled) + test.Require(t, callCount == 2) test.RequireEqual(t, err.Error(), finalAnnotatedErr.Error()) }() - // This is the handler we are thesting! + // This is the handler we are testing! AND it's not called in error. defer err2.Handle(&err, func(noerr bool) { test.Require(t, false, "if error occurs/reset, this cannot happen") handlerCalled = noerr @@ -138,11 +142,15 @@ func TestHandle_noerrHandler(t *testing.T) { // and it's not nil defer err2.Handle(&err, func(er error) error { test.Require(t, er != nil, "er val: ", er, err) + test.Require(t, callCount == 1, "this is called in sencond") + callCount++ return er }) defer err2.Handle(&err, func(err error) error { // this should not be called, so lets try to fuckup things... + test.Require(t, callCount == 0, "this is called in first") + callCount++ handlerCalled = false test.Require(t, err != nil) return finalAnnotatedErr @@ -158,7 +166,7 @@ func TestHandle_noerrHandler(t *testing.T) { test.Require(t, handlerCalled) }() - // This is the handler we are thesting! + // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { test.Require(t, noerr) handlerCalled = noerr @@ -181,8 +189,9 @@ func TestHandle_noerrHandler(t *testing.T) { test.Require(t, handlerCalled) }() - // This is the handler we are thesting! + // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { + test.Require(t, true) test.Require(t, noerr) handlerCalled = noerr }) @@ -214,7 +223,7 @@ func TestHandle_noerrHandler(t *testing.T) { test.Require(t, errHandlerCalled) }() - // This is the handler we are thesting! + // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { test.Require(t, true) // we are here, for debugging test.Require(t, noerr) @@ -261,8 +270,9 @@ func TestHandle_noerrHandler(t *testing.T) { return err }) - // This is the handler we are thesting! + // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { + test.Require(t, true, "this must be called") test.Require(t, noerr) handlerCalled = noerr }) From 6ae165baf582fc3bcba7a21e7d87275558a7dcf6 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 15 Aug 2024 19:11:30 +0300 Subject: [PATCH 10/78] fmt with golines --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index 73da499..bb38148 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ PKGS := $(PKG_ERR2) $(PKG_ASSERT) $(PKG_TRY) $(PKG_DEBUG) $(PKG_HANDLER) $(PKG_S SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) +MAX_LINE ?= 80 GO ?= go TEST_ARGS ?= -benchmem # -"gcflags '-N -l'" both optimization & inlining disabled @@ -106,6 +107,12 @@ bench_x: vet: | test $(GO) vet $(PKGS) +fmt: + @golines -t 5 -w -m $(MAX_LINE) --ignore-generated . + +dry-fmt: + @golines -t 5 --dry-run -m $(MAX_LINE) --ignore-generated . + gofmt: @echo Checking code is gofmted @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" From 3fc2088857403326e35746362d5cf6f69da2cfbc Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 15 Aug 2024 19:11:59 +0300 Subject: [PATCH 11/78] refactoring comments --- internal/test/test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/test/test.go b/internal/test/test.go index b6a7894..951dddf 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -1,3 +1,5 @@ +// package test should be renamed to require. Maybe we could try 1st gopls and +// then idea? TODO: package test import ( @@ -5,6 +7,11 @@ import ( "testing" ) +// TODO: why we have 2 Require functions? If we'll use type assertion for the +// first argument and decide that if it's a string and maybe search % marks we +// could have only one function? Or we count v count: if 1 no format, if >1 +// format. + // Require fails the test if the condition is false. func Require(tb testing.TB, condition bool, v ...interface{}) { tb.Helper() From fa9e3e8834d1aca90d216a7a0e1a279273eb0893 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 16 Aug 2024 17:18:30 +0300 Subject: [PATCH 12/78] refactor: test pkg -> require pkg + new helpers --- err2_test.go | 88 +++++++++++++++---------------- internal/debug/debug_test.go | 38 +++++++------ internal/handler/handler_test.go | 26 ++++----- internal/handler/handlers_test.go | 12 ++--- internal/require/test.go | 60 +++++++++++++++++++++ internal/str/str_test.go | 6 +-- internal/test/test.go | 44 ---------------- internal/x/x_test.go | 22 ++++---- try/copy_test.go | 14 ++--- try/out_test.go | 10 ++-- 10 files changed, 167 insertions(+), 153 deletions(-) create mode 100644 internal/require/test.go delete mode 100644 internal/test/test.go diff --git a/err2_test.go b/err2_test.go index ca32d71..19a87db 100644 --- a/err2_test.go +++ b/err2_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/lainio/err2" - "github.com/lainio/err2/internal/test" + "github.com/lainio/err2/internal/require" "github.com/lainio/err2/try" ) @@ -62,7 +62,7 @@ func TestHandle_noerrHandler(t *testing.T) { var err error var handlerCalled bool defer func() { - test.Require(t, handlerCalled) + require.That(t, handlerCalled) }() // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { @@ -77,12 +77,12 @@ func TestHandle_noerrHandler(t *testing.T) { var err error var handlerCalled bool defer func() { - test.Require(t, handlerCalled) + require.That(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) + require.That(t, false) return err }) @@ -99,20 +99,20 @@ func TestHandle_noerrHandler(t *testing.T) { var err error var handlerCalled bool defer func() { - test.Require(t, !handlerCalled) + require.ThatNot(t, handlerCalled) }() // This is the handler we are testing! defer err2.Handle(&err, func(err error) error { - test.Require(t, !handlerCalled) + require.ThatNot(t, handlerCalled) handlerCalled = false - test.Require(t, true, "error should be handled") + require.That(t, true, "error should be handled") return err }) // This is the handler we are testing! AND it's not called in error. defer err2.Handle(&err, func(bool) { - test.Require(t, false, "when error this is not called") + require.That(t, false, "when error this is not called") }) try.To1(throw()) @@ -127,32 +127,32 @@ func TestHandle_noerrHandler(t *testing.T) { callCount int ) defer func() { - test.Require(t, !handlerCalled) - test.Require(t, callCount == 2) - test.RequireEqual(t, err.Error(), finalAnnotatedErr.Error()) + require.ThatNot(t, handlerCalled) + require.Equal(t, callCount, 2) + require.Equal(t, err.Error(), finalAnnotatedErr.Error()) }() // This is the handler we are testing! AND it's not called in error. defer err2.Handle(&err, func(noerr bool) { - test.Require(t, false, "if error occurs/reset, this cannot happen") + require.That(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) - test.Require(t, callCount == 1, "this is called in sencond") + require.That(t, er != nil, "er val: ", er, err) + require.Equal(t, callCount, 1, "this is called in sencond") callCount++ return er }) defer err2.Handle(&err, func(err error) error { // this should not be called, so lets try to fuckup things... - test.Require(t, callCount == 0, "this is called in first") + require.Equal(t, callCount, 0, "this is called in first") callCount++ handlerCalled = false - test.Require(t, err != nil) + require.That(t, err != nil) return finalAnnotatedErr }) try.To1(throw()) @@ -163,17 +163,17 @@ func TestHandle_noerrHandler(t *testing.T) { var err error var handlerCalled bool defer func() { - test.Require(t, handlerCalled) + require.That(t, handlerCalled) }() // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { - test.Require(t, noerr) + require.That(t, noerr) handlerCalled = noerr }) defer err2.Handle(&err, func(err error) error { - test.Require(t, false, "no error to handle!") + require.That(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 @@ -186,27 +186,27 @@ func TestHandle_noerrHandler(t *testing.T) { var err error var handlerCalled bool defer func() { - test.Require(t, handlerCalled) + require.That(t, handlerCalled) }() // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { - test.Require(t, true) - test.Require(t, noerr) + require.That(t, true) + require.That(t, noerr) handlerCalled = noerr }) defer err2.Handle(&err) defer err2.Handle(&err, func(err error) error { - test.Require(t, false, "no error to handle!") + require.That(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!") + require.That(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 @@ -219,20 +219,20 @@ func TestHandle_noerrHandler(t *testing.T) { var err error var noerrHandlerCalled, errHandlerCalled bool defer func() { - test.Require(t, noerrHandlerCalled) - test.Require(t, errHandlerCalled) + require.That(t, noerrHandlerCalled) + require.That(t, errHandlerCalled) }() // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { - test.Require(t, true) // we are here, for debugging - test.Require(t, noerr) + require.That(t, true) // we are here, for debugging + require.That(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 + require.That(t, err != nil) // helps fast debugging // this should not be called, so lets try to fuckup things... noerrHandlerCalled = false // see first deferred function @@ -242,7 +242,7 @@ func TestHandle_noerrHandler(t *testing.T) { }) defer err2.Handle(&err, func(err error) error { - test.Require(t, err != nil) // helps fast debugging + require.That(t, err != nil) // helps fast debugging // this should not be called, so lets try to fuckup things... noerrHandlerCalled = false // see first deferred function @@ -257,14 +257,14 @@ func TestHandle_noerrHandler(t *testing.T) { var err error var handlerCalled bool defer func() { - test.Require(t, handlerCalled) + require.That(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!") + require.That(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 @@ -272,13 +272,13 @@ func TestHandle_noerrHandler(t *testing.T) { // This is the handler we are testing! defer err2.Handle(&err, func(noerr bool) { - test.Require(t, true, "this must be called") - test.Require(t, noerr) + require.That(t, true, "this must be called") + require.That(t, noerr) handlerCalled = noerr }) defer err2.Handle(&err, func(err error) error { - test.Require(t, false, "no error to handle!") + require.That(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 @@ -348,7 +348,7 @@ func TestPanickingCatchAll(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() defer func() { - test.Require(t, recover() == nil, "panics should NOT carry on") + require.That(t, recover() == nil, "panics should NOT carry on") }() tt.args.f() }) @@ -392,7 +392,7 @@ func TestPanickingCarryOn_Handle(t *testing.T) { 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") + require.That(t, recover() != nil, "panics should went thru when not our errors") }() tt.args.f() }) @@ -518,12 +518,12 @@ func TestPanicking_Handle(t *testing.T) { defer func() { r := recover() if tt.wants == nil { - test.Require(t, r != nil, "wants err, then panic") + require.That(t, r != nil, "wants err, then panic") } }() err := tt.args.f() if err != nil { - test.RequireEqual(t, err.Error(), tt.wants.Error()) + require.Equal(t, err.Error(), tt.wants.Error()) } }) } @@ -564,7 +564,7 @@ func TestPanicking_Catch(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() defer func() { - test.Require(t, recover() == nil, "panics should NOT carry on") + require.That(t, recover() == nil, "panics should NOT carry on") }() tt.args.f() }) @@ -583,7 +583,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", + require.Equal(t, err.Error(), "fails: test: this is an ERROR", "=> we should catch right error str here") return err }) @@ -593,7 +593,7 @@ func Test_TryOutError(t *testing.T) { // let's test try.Out1() and it's throw capabilities here, even try.To1() // is the preferred way. retVal = try.Out1(noThrow()).Handle().Val1 - test.Require(t, retVal == "test", "if no error happens, we get value") + require.Equal(t, retVal, "test", "if no error happens, we get value") _ = try.Out1(throw()).Handle("fails: %v", retVal).Val1 t.Fail() // If everything works in Handle we are never here. @@ -625,11 +625,11 @@ 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") + require.That(t, w == nil, "error tracer should be nil") var w1 io.Writer err2.SetErrorTracer(w1) w = err2.ErrorTracer() - test.Require(t, w == nil, "error tracer should be nil") + require.That(t, w == nil, "error tracer should be nil") } func ExampleCatch_withFmt() { diff --git a/internal/debug/debug_test.go b/internal/debug/debug_test.go index cef1010..e1385cb 100644 --- a/internal/debug/debug_test.go +++ b/internal/debug/debug_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/lainio/err2/internal/test" + "github.com/lainio/err2/internal/require" ) func TestFullName(t *testing.T) { @@ -30,8 +30,7 @@ func TestFullName(t *testing.T) { tt := ttv t.Run(tt.name, func(t *testing.T) { t.Parallel() - test.Requiref(t, tt.retval == tt.fullName(), "must be equal: %s", - tt.retval) + require.Equal(t, tt.retval, tt.fullName()) }) } } @@ -83,7 +82,7 @@ func TestIsAnchor(t *testing.T) { tt := ttv t.Run(tt.name, func(t *testing.T) { t.Parallel() - test.Require(t, tt.retval == tt.isAnchor(tt.input), "equal") + require.Equal(t, tt.retval, tt.isAnchor(tt.input)) }) } } @@ -129,7 +128,7 @@ func TestIsFuncAnchor(t *testing.T) { tt := ttv t.Run(tt.name, func(t *testing.T) { t.Parallel() - test.Require(t, tt.retval == tt.isFuncAnchor(tt.input), "equal") + require.Equal(t, tt.retval, tt.isFuncAnchor(tt.input)) }) } } @@ -150,7 +149,7 @@ func TestFnLNro(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() output := fnLNro(tt.input) - test.Require(t, output == tt.output, output) + require.Equal(t, output, tt.output) }) } } @@ -183,7 +182,7 @@ func TestFnName(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() output := fnName(tt.input) - test.Require(t, output == tt.output, output) + require.Equal(t, output, tt.output) }) } } @@ -210,7 +209,7 @@ func TestStackPrint_noLimits(t *testing.T) { FuncName: "", Level: 0, }) - test.Require(t, tt.input == w.String(), "") + require.Equal(t, tt.input, w.String()) }) } } @@ -238,8 +237,8 @@ func TestStackPrintForTest(t *testing.T) { // print(tt.output) // println("------") // print(w.String()) - test.Requiref(t, a == b, "%d %d", a, b) - test.Require(t, tt.output == w.String(), w.String()) + require.Equal(t, a, b) + require.Equal(t, tt.output, w.String()) }) } } @@ -272,8 +271,7 @@ func TestCalcAnchor(t *testing.T) { t.Parallel() r := strings.NewReader(tt.input) anchor := calcAnchor(r, tt.StackInfo) - test.Requiref(t, tt.anchor == anchor, "not equal: %d != %d, got", - tt.anchor, anchor) + require.Equal(t, tt.anchor, anchor) }) } } @@ -309,10 +307,10 @@ func TestStackPrint_limit(t *testing.T) { stackPrint(r, w, tt.StackInfo) ins := strings.Split(tt.input, "\n") outs := strings.Split(w.String(), "\n") - test.Requiref(t, len(ins) > len(outs), + require.Thatf(t, len(ins) > len(outs), "input length:%d should be greater:%d", len(ins), len(outs)) - a, b := tt.output, w.String() - test.Requiref(t, a == b, "a: %v != b: %v", a, b) + b, a := tt.output, w.String() + require.Equal(t, a, b) }) } } @@ -342,15 +340,15 @@ func TestFuncName(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() r := strings.NewReader(tt.input) - name, ln, fr, ok := funcName(r, StackInfo{ + name, ln, fr, found := funcName(r, StackInfo{ PackageName: tt.PackageName, FuncName: tt.FuncName, Level: tt.Level, }) - test.Require(t, ok, "not found") - test.Requiref(t, tt.output == name, "not equal %v", name) - test.Requiref(t, ln == tt.outln, "ln must be equal %d == %d", ln, tt.outln) - test.RequireEqual(t, fr, tt.outFrame) + require.That(t, found) + require.Equal(t, tt.output, name) + require.Equal(t, ln, tt.outln) + require.Equal(t, fr, tt.outFrame) }) } } diff --git a/internal/handler/handler_test.go b/internal/handler/handler_test.go index 1214f2f..dd72499 100644 --- a/internal/handler/handler_test.go +++ b/internal/handler/handler_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/lainio/err2/internal/handler" - "github.com/lainio/err2/internal/test" + "github.com/lainio/err2/internal/require" "github.com/lainio/err2/internal/x" ) @@ -121,11 +121,11 @@ func TestProcess(t *testing.T) { if handler.WorkToDo(tt.args.Any, tt.args.Err) { handler.Process(&tt.args.Info) - test.RequireEqual(t, panicHandlerCalled, tt.want.panicCalled) - test.RequireEqual(t, errorHandlerCalled, tt.want.errorCalled) - test.RequireEqual(t, nilHandlerCalled, tt.want.nilCalled) + require.Equal(t, panicHandlerCalled, tt.want.panicCalled) + require.Equal(t, errorHandlerCalled, tt.want.errorCalled) + require.Equal(t, nilHandlerCalled, tt.want.nilCalled) - test.RequireEqual(t, myErrVal.Error(), tt.want.errStr) + require.Equal(t, myErrVal.Error(), tt.want.errStr) } resetCalled() }) @@ -152,13 +152,13 @@ func TestPreProcess_debug(t *testing.T) { // and that's what error stack tracing is all about Handle() - test.Require(t, !panicHandlerCalled) - test.Require(t, !errorHandlerCalled) - test.Require(t, !nilHandlerCalled) + require.ThatNot(t, panicHandlerCalled) + require.ThatNot(t, errorHandlerCalled) + require.ThatNot(t, nilHandlerCalled) // See the name of this test function. Decamel it + error const want = "testing: t runner: error" - test.RequireEqual(t, myErrVal.Error(), want) + require.Equal(t, myErrVal.Error(), want) resetCalled() } @@ -243,11 +243,11 @@ func TestPreProcess(t *testing.T) { err = handler.PreProcess(&err, &tt.args.Info, tt.args.a) - test.RequireEqual(t, panicHandlerCalled, tt.want.panicCalled) - test.RequireEqual(t, errorHandlerCalled, tt.want.errorCalled) - test.RequireEqual(t, nilHandlerCalled, tt.want.nilCalled) + require.Equal(t, panicHandlerCalled, tt.want.panicCalled) + require.Equal(t, errorHandlerCalled, tt.want.errorCalled) + require.Equal(t, nilHandlerCalled, tt.want.nilCalled) - test.RequireEqual(t, err.Error(), tt.want.errStr) + require.Equal(t, err.Error(), tt.want.errStr) } resetCalled() }) diff --git a/internal/handler/handlers_test.go b/internal/handler/handlers_test.go index 2e5b700..c80cabc 100644 --- a/internal/handler/handlers_test.go +++ b/internal/handler/handlers_test.go @@ -5,7 +5,7 @@ import ( "github.com/lainio/err2" "github.com/lainio/err2/internal/handler" - "github.com/lainio/err2/internal/test" + "github.com/lainio/err2/internal/require" ) func TestHandlers(t *testing.T) { @@ -43,17 +43,17 @@ func TestHandlers(t *testing.T) { t.Parallel() anys := tt.args.f - test.Require(t, anys != nil, "cannot be nil") + require.That(t, anys != nil, "cannot be nil") fns, dis := handler.ToErrorFns(anys) - test.Require(t, fns != nil, "cannot be nil") - test.Require(t, dis == tt.dis, "disabled wanted") + require.That(t, fns != nil, "cannot be nil") + require.Equal(t, dis, tt.dis, "disabled wanted") errHandler := handler.Pipeline(fns) err := errHandler(err2.ErrNotFound) if err == nil { - test.Require(t, tt.want == nil) + require.That(t, tt.want == nil) } else { - test.RequireEqual(t, err.Error(), tt.want.Error()) + require.Equal(t, err.Error(), tt.want.Error()) } }) } diff --git a/internal/require/test.go b/internal/require/test.go new file mode 100644 index 0000000..c1a87fa --- /dev/null +++ b/internal/require/test.go @@ -0,0 +1,60 @@ +package require + +import ( + "fmt" + "testing" +) + +// ThatNot fails the test if the condition is true. +func ThatNot(tb testing.TB, condition bool, v ...interface{}) { + tb.Helper() + if condition { + tb.Fatal(v...) + } +} + +// That fails the test if the condition is false. +func That(tb testing.TB, condition bool, v ...interface{}) { + tb.Helper() + if !condition { + tb.Fatal(v...) + } +} + +// Thatf fails the test if the condition is false. +func Thatf(tb testing.TB, condition bool, format string, v ...interface{}) { + tb.Helper() + if !condition { + tb.Fatalf(format, v...) + } +} + +// Equal fails the test if the values aren't equal. +func Equal[T comparable](tb testing.TB, val, want T, a ...any) { + tb.Helper() + if want != val { + defMsg := fmt.Sprintf("got '%v', want '%v' ", val, want) + if len(a) == 0 { + tb.Fatal(defMsg) + } + format, ok := a[0].(string) + if ok { + tb.Fatalf(defMsg+format, a[1:]...) + } + } +} + +// NotEqual fails the test if the values aren't equal. +func NotEqual[T comparable](tb testing.TB, val, want T, a ...any) { + tb.Helper() + if want == val { + defMsg := fmt.Sprintf("got '%v', want != '%v' ", val, want) + if len(a) == 0 { + tb.Fatal(defMsg) + } + format, ok := a[0].(string) + if ok { + tb.Fatalf(defMsg+format, a[1:]...) + } + } +} diff --git a/internal/str/str_test.go b/internal/str/str_test.go index 45d02b1..d274986 100644 --- a/internal/str/str_test.go +++ b/internal/str/str_test.go @@ -3,8 +3,8 @@ package str_test import ( "testing" + "github.com/lainio/err2/internal/require" "github.com/lainio/err2/internal/str" - "github.com/lainio/err2/internal/test" ) const ( @@ -43,7 +43,7 @@ func TestCamel(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := str.DecamelRegexp(tt.args.s) - test.RequireEqual(t, got, tt.want) + require.Equal(t, got, tt.want) }) } } @@ -76,7 +76,7 @@ func TestDecamel(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := str.Decamel(tt.args.s) - test.RequireEqual(t, got, tt.want) + require.Equal(t, got, tt.want) }) } } diff --git a/internal/test/test.go b/internal/test/test.go deleted file mode 100644 index 951dddf..0000000 --- a/internal/test/test.go +++ /dev/null @@ -1,44 +0,0 @@ -// package test should be renamed to require. Maybe we could try 1st gopls and -// then idea? TODO: -package test - -import ( - "fmt" - "testing" -) - -// TODO: why we have 2 Require functions? If we'll use type assertion for the -// first argument and decide that if it's a string and maybe search % marks we -// could have only one function? Or we count v count: if 1 no format, if >1 -// format. - -// Require fails the test if the condition is false. -func Require(tb testing.TB, condition bool, v ...interface{}) { - tb.Helper() - if !condition { - tb.Fatal(v...) - } -} - -// Requiref fails the test if the condition is false. -func Requiref(tb testing.TB, condition bool, format string, v ...interface{}) { - tb.Helper() - if !condition { - tb.Fatalf(format, v...) - } -} - -// RequireEqual fails the test if the values aren't equal -func RequireEqual[T comparable](tb testing.TB, val, want T, a ...any) { - tb.Helper() - if want != val { - defMsg := fmt.Sprintf("got '%v', want '%v' ", val, want) - if len(a) == 0 { - tb.Fatal(defMsg) - } - format, ok := a[0].(string) - if ok { - tb.Fatalf(defMsg+format, a[1:]...) - } - } -} diff --git a/internal/x/x_test.go b/internal/x/x_test.go index d79feaf..ff146ca 100644 --- a/internal/x/x_test.go +++ b/internal/x/x_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/lainio/err2/internal/test" + "github.com/lainio/err2/internal/require" ) var ( @@ -19,30 +19,30 @@ func TestSwap(t *testing.T) { var ( lhs, rhs = 1, 2 // these are ints as default ) - test.Require(t, lhs == 1) - test.Require(t, rhs == 2) + require.Equal(t, lhs, 1) + require.Equal(t, rhs, 2) Swap(&lhs, &rhs) - test.Require(t, lhs == 2) - test.Require(t, rhs == 1) + require.Equal(t, lhs, 2) + require.Equal(t, rhs, 1) } { var ( lhs, rhs float64 = 1, 2 ) - test.Require(t, lhs == 1) - test.Require(t, rhs == 2) + require.Equal(t, lhs, 1) + require.Equal(t, rhs, 2) Swap(&lhs, &rhs) - test.Require(t, lhs == 2) - test.Require(t, rhs == 1) + require.Equal(t, lhs, 2) + require.Equal(t, rhs, 1) } } func TestSReverse(t *testing.T) { t.Parallel() SReverse(lengths) - test.Require(t, reflect.DeepEqual(lengths, reverseLengths)) + require.That(t, reflect.DeepEqual(lengths, reverseLengths)) SReverse(lengths) // it's reverse now turn it to original - test.Require(t, reflect.DeepEqual(lengths, original)) + require.That(t, reflect.DeepEqual(lengths, original)) } func BenchmarkSSReverse(b *testing.B) { diff --git a/try/copy_test.go b/try/copy_test.go index c11389c..6166aaf 100644 --- a/try/copy_test.go +++ b/try/copy_test.go @@ -8,7 +8,7 @@ import ( "os" "testing" - "github.com/lainio/err2/internal/test" + "github.com/lainio/err2/internal/require" "github.com/lainio/err2/try" ) @@ -16,8 +16,8 @@ const dataFile = "./try.go" func Benchmark_CopyBufferMy(b *testing.B) { all, err := os.ReadFile(dataFile) - test.Requiref(b, err == nil, "error: %v", err) - test.Require(b, all != nil) + require.Thatf(b, err == nil, "error: %v", err) + require.That(b, all != nil) buf := make([]byte, 4) dst := bufio.NewWriter(bytes.NewBuffer(make([]byte, 0, len(all)))) @@ -29,8 +29,8 @@ func Benchmark_CopyBufferMy(b *testing.B) { func Benchmark_CopyBufferStd(b *testing.B) { all, err := os.ReadFile(dataFile) - test.Requiref(b, err == nil, "error: %v", err) - test.Require(b, all != nil) + require.Thatf(b, err == nil, "error: %v", err) + require.That(b, all != nil) buf := make([]byte, 4) dst := bufio.NewWriter(bytes.NewBuffer(make([]byte, 0, len(all)))) @@ -42,8 +42,8 @@ func Benchmark_CopyBufferStd(b *testing.B) { func Benchmark_CopyBufferOur(b *testing.B) { all, err := os.ReadFile(dataFile) - test.Requiref(b, err == nil, "error: %v", err) - test.Require(b, all != nil) + require.Thatf(b, err == nil, "error: %v", err) + require.That(b, all != nil) tmp := make([]byte, 4) dst := bufio.NewWriter(bytes.NewBuffer(make([]byte, 0, len(all)))) diff --git a/try/out_test.go b/try/out_test.go index 454bfdf..60ea173 100644 --- a/try/out_test.go +++ b/try/out_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/lainio/err2" - "github.com/lainio/err2/internal/test" + "github.com/lainio/err2/internal/require" "github.com/lainio/err2/try" ) @@ -102,8 +102,8 @@ func TestResult2_Logf(t *testing.T) { return v1 + v2, v2 } num1, num2 := countSomething("1", "bad") - test.RequireEqual(t, num2, 2) - test.RequireEqual(t, num1, 3) + require.Equal(t, num2, 2) + require.Equal(t, num1, 3) } func TestResult_Handle(t *testing.T) { @@ -121,10 +121,10 @@ func TestResult_Handle(t *testing.T) { return nil } err := callFn(1) - test.Requiref(t, err == nil, "no error when Out.Handle sets it nil") + require.That(t, err == nil, "no error when Out.Handle sets it nil") err = callFn(0) - test.Requiref(t, err != nil, "want error when Out.Handle sets it the same") + require.That(t, err != nil, "want error when Out.Handle sets it the same") } func ExampleResult1_Handle() { From 05a068463751a28d7db12eebd8fb558e2ad0470d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 17 Aug 2024 16:50:34 +0300 Subject: [PATCH 13/78] refactor result functions for testing --- err2_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/err2_test.go b/err2_test.go index 19a87db..fde4c48 100644 --- a/err2_test.go +++ b/err2_test.go @@ -1,6 +1,7 @@ package err2_test import ( + "errors" "fmt" "io" "os" @@ -14,18 +15,17 @@ import ( const errStringInThrow = "this is an ERROR" -func throw() (string, error) { - return "", fmt.Errorf(errStringInThrow) -} +var ( + errToTest = errors.New(errStringInThrow) +) + +func throw() (string, error) { return "", errToTest } +func noThrow() (string, error) { return "test", nil } +func noErr() error { return nil } func twoStrNoThrow() (string, string, error) { return "test", "test", nil } func intStrNoThrow() (int, string, error) { return 1, "test", nil } func boolIntStrNoThrow() (bool, int, string, error) { return true, 1, "test", nil } -func noThrow() (string, error) { return "test", nil } - -func noErr() error { - return nil -} func TestTry_noError(t *testing.T) { t.Parallel() From 4ad8245d5290d8caea3bcdf9b1cb6f10ed4a4911 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 17 Aug 2024 17:10:48 +0300 Subject: [PATCH 14/78] more dynamic sample --- samples/main-play.go | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/samples/main-play.go b/samples/main-play.go index 035ee3e..dd12042 100644 --- a/samples/main-play.go +++ b/samples/main-play.go @@ -9,9 +9,11 @@ package main import ( + "flag" "fmt" "io" "os" + "strconv" "github.com/lainio/err2" "github.com/lainio/err2/assert" @@ -90,22 +92,23 @@ func OrgCopyFile(src, dst string) (err error) { return nil } -func CallRecur(d int) (err error) { +func CallRecur(d int) (ret int, err error) { defer err2.Handle(&err) return doRecur(d) } -func doRecur(d int) (err error) { +func doRecur(d int) (ret int, err error) { d-- if d >= 0 { // Keep below to show how asserts work - assert.NotZero(d) + //assert.NotZero(d) // Comment out the above assert statement to simulate runtime-error - fmt.Println(10 / d) - return doRecur(d) + ret = 10 / d + fmt.Println(ret) + //return doRecur(d) } - return fmt.Errorf("root error") + return ret, fmt.Errorf("root error") } func doPlayMain() { @@ -136,7 +139,7 @@ func doPlayMain() { doDoMain() //try.To(doMain()) - println("___ happy ending ===") + fmt.Println("___ happy ending ===") } func doDoMain() { @@ -163,13 +166,20 @@ func doMain() (err error) { // Both source and destination don't exist //try.To(OrgCopyFile("/notfound/path/file.go", "/notfound/path/file.bak")) - // 2nd argument is empty - try.To(OrgCopyFile("main.go", "")) - - // Next fn demonstrates how error and panic traces work, comment out all - // above CopyFile calls to play with: - try.To(CallRecur(1)) + // to play with real args: + try.To(CopyFile(flag.Arg(0), flag.Arg(1))) + + if len(flag.Args()) > 0 { + // Next fn demonstrates how error and panic traces work, comment out all + // above CopyFile calls to play with: + argument := try.To1(strconv.Atoi(flag.Arg(0))) + ret := try.To1(CallRecur(argument)) + fmt.Println("ret val:", ret) + } else { + // 2nd argument is empty to assert + try.To(OrgCopyFile("main.go", "")) + } - println("=== you cannot see this ===") + fmt.Println("=== you cannot see this ===") return nil } From a858523740e29e3ff035e4657d838f0af1af819d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Aug 2024 14:42:27 +0300 Subject: [PATCH 15/78] export and use handler.WrapError --- internal/handler/handler.go | 4 ++-- try/out.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 9ba52e2..235dc23 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -66,7 +66,7 @@ const ( // sentinel error info and we couldn't use annotation for this error. // However, we should think about this more later from performance point of // view. - wrapError = ": %w" + WrapError = ": %w" ) func PanicNoop(any) {} @@ -226,7 +226,7 @@ func (i *Info) safeErr() error { // wrapStr returns always wrap string that means we are using "%w" to chain // errors to be able to use errors.Is and errors.As functions form Go stl. func (i *Info) wrapStr() string { - return wrapError + return WrapError } // WorkToDo returns if there is something to process. This is offered for diff --git a/try/out.go b/try/out.go index d73472c..b7c0a88 100644 --- a/try/out.go +++ b/try/out.go @@ -268,7 +268,7 @@ func Out2[T any, U any](v1 T, v2 U, err error) *Result2[T, U] { } func wrapStr() string { - return ": %w" + return handler.WrapError } func (o *Result) logf(lvl int, a []any) *Result { From 88e62f86cea2eb7904e42307a8a1daf9bfdfb2b7 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Aug 2024 14:43:52 +0300 Subject: [PATCH 16/78] new functions + tests: T, T1, T2, T3 --- err2_test.go | 50 +++++++++++++++++++++++++++++++++++++------------- try/try.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 13 deletions(-) diff --git a/err2_test.go b/err2_test.go index 19a87db..9fb2150 100644 --- a/err2_test.go +++ b/err2_test.go @@ -33,6 +33,11 @@ func TestTry_noError(t *testing.T) { try.To2(twoStrNoThrow()) try.To2(intStrNoThrow()) try.To3(boolIntStrNoThrow()) + + _ = try.T1(noThrow())("test") + _, _ = try.T2(twoStrNoThrow())("test %d", 1) + _, _ = try.T2(intStrNoThrow())("test %d", 2) + _, _, _ = try.T3(boolIntStrNoThrow())("test %d",3) } func TestDefault_Error(t *testing.T) { @@ -814,25 +819,12 @@ func BenchmarkTry_ErrVar(b *testing.B) { } } -func BenchmarkTryOut_ErrVar(b *testing.B) { - for n := 0; n < b.N; n++ { - _, err := noThrow() - try.Out(err).Handle() - } -} - func BenchmarkTry_StringGenerics(b *testing.B) { for n := 0; n < b.N; n++ { _ = try.To1(noThrow()) } } -func BenchmarkTryOut_StringGenerics(b *testing.B) { - for n := 0; n < b.N; n++ { - _ = try.Out1(noThrow()).Handle() - } -} - func BenchmarkTry_StrStrGenerics(b *testing.B) { for n := 0; n < b.N; n++ { _, _ = try.To2(twoStrNoThrow()) @@ -852,6 +844,38 @@ func BenchmarkTryVarCall(b *testing.B) { } } +func BenchmarkTryOut_ErrVar(b *testing.B) { + for n := 0; n < b.N; n++ { + _, err := noThrow() + try.Out(err).Handle() + } +} + +func BenchmarkTryOut_LogStringGenerics(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = try.Out1(noThrow()).Logf("test").Val1 + } +} + +func BenchmarkTryOut_StringGenerics(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = try.Out1(noThrow()).Handle().Val1 + } +} + +func BenchmarkT_ErrVar(b *testing.B) { + for n := 0; n < b.N; n++ { + _, err := noThrow() + try.T(err)("test") + } +} + +func BenchmarkT_StringGenerics(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = try.T1(noThrow())("test") + } +} + func BenchmarkRecursionWithOldErrorCheck(b *testing.B) { var recursionWithErrorCheck func(a int) (int, error) recursionWithErrorCheck = func(a int) (int, error) { diff --git a/try/try.go b/try/try.go index 641853f..069d014 100644 --- a/try/try.go +++ b/try/try.go @@ -65,9 +65,11 @@ package try import ( "errors" + "fmt" "io" "github.com/lainio/err2" + "github.com/lainio/err2/internal/handler" ) // To is a helper function to call functions which returns an error value and @@ -239,3 +241,50 @@ func IsNotRecoverable(err error) bool { func IsNotEnabled(err error) bool { return Is(err, err2.ErrNotEnabled) } + +// T is similar as [To] but it let's you to annotate a possible error at place. +// +// try.T(f.Close)("annotations") +func T(err error) func(fs string, a ...any) { + return func(fs string, a ...any) { + if err != nil { + er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) + panic(er) + } + } +} + +// T1 is similar as [To1] but it let's you to annotate a possible error at place. +// +// f := try.T1(os.Open("filename")("cannot open cfg file") +func T1[T any](v T, err error) func(fs string, a ...any) T { + return func(fs string, a ...any) T { + if err != nil { + er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) + panic(er) + } + return v + } +} + +// T2 is similar as [To2] but it let's you to annotate a possible error at place. +func T2[T, U any](v T, u U, err error) func(fs string, a ...any) (T, U) { + return func(fs string, a ...any) (T, U) { + if err != nil { + er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) + panic(er) + } + return v, u + } +} + +// T3 is similar as [To3] but it let's you to annotate a possible error at place. +func T3[T, U, V any](v1 T, v2 U, v3 V, err error) func(fs string, a ...any) (T, U, V) { + return func(fs string, a ...any) (T, U, V) { + if err != nil { + er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) + panic(er) + } + return v1, v2, v3 + } +} From baab7e9f3ec897d9dd0a694e8a2042a63a30c1a1 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Aug 2024 14:52:58 +0300 Subject: [PATCH 17/78] bench rule for T functions --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index bb38148..5163a9d 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,9 @@ tinline_handler: bench: $(GO) test $(TEST_ARGS) -bench=. $(PKGS) +bench_T: + $(GO) test $(TEST_ARGS) -bench='BenchmarkT_.*' $(PKG_ERR2) + bench_goid: $(GO) test $(TEST_ARGS) -bench='BenchmarkGoid' $(PKG_ASSERT) From 9e3f44cc115115cf2a677202a8161e01adb22962 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Aug 2024 14:56:23 +0300 Subject: [PATCH 18/78] make linter happy --- err2_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/err2_test.go b/err2_test.go index 9fb2150..8c5cf9c 100644 --- a/err2_test.go +++ b/err2_test.go @@ -37,7 +37,7 @@ func TestTry_noError(t *testing.T) { _ = try.T1(noThrow())("test") _, _ = try.T2(twoStrNoThrow())("test %d", 1) _, _ = try.T2(intStrNoThrow())("test %d", 2) - _, _, _ = try.T3(boolIntStrNoThrow())("test %d",3) + try.T3(boolIntStrNoThrow())("test %d", 3) // linter says: _, _, _, } func TestDefault_Error(t *testing.T) { From ac8f630122e378b16687faada6bcd89e5e275a3e Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Aug 2024 15:09:10 +0300 Subject: [PATCH 19/78] comment: tell why DRY has to be there, no inlining --- try/try.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/try/try.go b/try/try.go index 069d014..bcea2d3 100644 --- a/try/try.go +++ b/try/try.go @@ -247,6 +247,8 @@ func IsNotEnabled(err error) bool { // try.T(f.Close)("annotations") func T(err error) func(fs string, a ...any) { return func(fs string, a ...any) { + // NOTE if block cannot be refactored 'because it wouldn't inline + // then this whole function! if err != nil { er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) panic(er) @@ -259,6 +261,8 @@ func T(err error) func(fs string, a ...any) { // f := try.T1(os.Open("filename")("cannot open cfg file") func T1[T any](v T, err error) func(fs string, a ...any) T { return func(fs string, a ...any) T { + // NOTE if block cannot be refactored 'because it wouldn't inline + // then this whole function! if err != nil { er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) panic(er) @@ -270,6 +274,8 @@ func T1[T any](v T, err error) func(fs string, a ...any) T { // T2 is similar as [To2] but it let's you to annotate a possible error at place. func T2[T, U any](v T, u U, err error) func(fs string, a ...any) (T, U) { return func(fs string, a ...any) (T, U) { + // NOTE if block cannot be refactored 'because it wouldn't inline + // then this whole function! if err != nil { er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) panic(er) @@ -281,6 +287,8 @@ func T2[T, U any](v T, u U, err error) func(fs string, a ...any) (T, U) { // T3 is similar as [To3] but it let's you to annotate a possible error at place. func T3[T, U, V any](v1 T, v2 U, v3 V, err error) func(fs string, a ...any) (T, U, V) { return func(fs string, a ...any) (T, U, V) { + // NOTE if block cannot be refactored 'because it wouldn't inline + // then this whole function! if err != nil { er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) panic(er) From 193db9a3c1d58455a1b3e2814480fef376defc77 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Aug 2024 18:48:14 +0300 Subject: [PATCH 20/78] try.Out.Handle() inlined + benchmark --- err2_test.go | 14 ++++++++++++++ try/out.go | 9 +++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/err2_test.go b/err2_test.go index 6e23b58..c7c8b40 100644 --- a/err2_test.go +++ b/err2_test.go @@ -844,6 +844,12 @@ func BenchmarkTryVarCall(b *testing.B) { } } +func BenchmarkTryOut_ErrVarNoRetval(b *testing.B) { + for n := 0; n < b.N; n++ { + try.Out(noErr()).Handle() + } +} + func BenchmarkTryOut_ErrVar(b *testing.B) { for n := 0; n < b.N; n++ { _, err := noThrow() @@ -863,6 +869,14 @@ func BenchmarkTryOut_StringGenerics(b *testing.B) { } } +func BenchmarkTryOut_StringGenericsNoVal(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = try.Out1(noThrow()).Logf("test").Val1 + r := try.Out1(noThrow()).Handle() + _ = r.Val1 + } +} + func BenchmarkT_ErrVar(b *testing.B) { for n := 0; n < b.N; n++ { _, err := noThrow() diff --git a/try/out.go b/try/out.go index b7c0a88..38b6166 100644 --- a/try/out.go +++ b/try/out.go @@ -97,6 +97,11 @@ func (o *Result) Handle(a ...any) *Result { if o.Err == nil { return o } + o.transportErr(a) + return o +} + +func (o *Result) transportErr(a []any) { noArguments := len(a) == 0 if noArguments { panic(o.Err) @@ -117,11 +122,11 @@ func (o *Result) Handle(a ...any) *Result { } } } - // someone of the handler functions might reset the error value. + + // some of the handler functions might reset the error value. if o.Err != nil { panic(o.Err) } - return o } // Handle allows you to add an error handler to [try.Out] handler chain. Handle From b803124f66f93d14f34a29e30c83cb899244703e Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 18 Aug 2024 18:50:51 +0300 Subject: [PATCH 21/78] inline optimization for Logf --- try/out.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/try/out.go b/try/out.go index 38b6166..4b49921 100644 --- a/try/out.go +++ b/try/out.go @@ -43,6 +43,9 @@ type ( // // error sending response: UDP not listening func (o *Result) Logf(a ...any) *Result { + if o.Err == nil { + return o + } return o.logf(logfFrameLvl, a) } @@ -57,6 +60,9 @@ func (o *Result) Logf(a ...any) *Result { // // error sending response: UDP not listening func (o *Result1[T]) Logf(a ...any) *Result1[T] { + if o.Err == nil { + return o + } o.Result.logf(logfFrameLvl, a) return o } @@ -72,6 +78,9 @@ func (o *Result1[T]) Logf(a ...any) *Result1[T] { // // error sending response: UDP not listening func (o *Result2[T, U]) Logf(a ...any) *Result2[T, U] { + if o.Err == nil { + return o + } o.Result.logf(logfFrameLvl, a) return o } @@ -277,9 +286,6 @@ func wrapStr() string { } func (o *Result) logf(lvl int, a []any) *Result { - if o.Err == nil { - return o - } s := o.Err.Error() if len(a) != 0 { f, isFormat := a[0].(string) From 767a53d44e17557365a496eee189ffeb99305c05 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 19 Aug 2024 13:42:02 +0300 Subject: [PATCH 22/78] new benches, and inline bench for assert --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 5163a9d..bc701da 100644 --- a/Makefile +++ b/Makefile @@ -62,12 +62,18 @@ inline_handler: tinline_handler: $(GO) test -c -gcflags=-m=2 $(PKG_HANDLER) 2>&1 | ag 'inlin' +inline_assert: + $(GO) test -c -gcflags=-m=2 $(PKG_ASSERT) 2>&1 | ag 'inlin' + bench: $(GO) test $(TEST_ARGS) -bench=. $(PKGS) bench_T: $(GO) test $(TEST_ARGS) -bench='BenchmarkT_.*' $(PKG_ERR2) +bench_zero: + $(GO) test $(TEST_ARGS) -bench='BenchmarkZero.*' $(PKG_ASSERT) + bench_goid: $(GO) test $(TEST_ARGS) -bench='BenchmarkGoid' $(PKG_ASSERT) From 04fa9cca5e710d11d0f8ac758be60abeda0e5dca Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 19 Aug 2024 13:42:58 +0300 Subject: [PATCH 23/78] Q&D test to optimize assert.Zero even it's generics, good strategy --- assert/assert.go | 8 ++++++-- assert/asserter.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index db1f787..be6d66f 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -899,11 +899,15 @@ func Less[T Number](val, want T, a ...any) { // are used to override the auto-generated assert violation message. func Zero[T Number](val T, a ...any) { if val != 0 { - defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (== '0')", val) - current().reportAssertionFault(defMsg, a) + doZero(val, a) } } +func doZero[T Number](val T, a []any) { + defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (== '0')", val) + current().newReportAssertionFault(defMsg, a) +} + // NotZero asserts that the value != 0. If it is not it panics and builds a // violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. diff --git a/assert/asserter.go b/assert/asserter.go index 803213d..0b5295b 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -86,6 +86,36 @@ func (asserter asserter) reportAssertionFault(defaultMsg string, a []any) { } } +func (asserter asserter) newReportAssertionFault(defaultMsg string, a []any) { + if asserter.hasStackTrace() { + if asserter.isUnitTesting() { + // Note. that the assert in the test function is printed in + // reportPanic below + const stackLvl = 4 // amount of functions before we're here + debug.PrintStackForTest(os.Stderr, stackLvl) + } else { + // amount of functions before we're here, which is different + // between runtime (this) and test-run (above) + const stackLvl = 1 + debug.PrintStack(stackLvl) + } + } + if asserter.hasCallerInfo() { + defaultMsg = asserter.newCallerInfo(defaultMsg) + } + if len(a) > 0 { + if format, ok := a[0].(string); ok { + allowDefMsg := !asserter.isErrorOnly() && defaultMsg != "" + f := x.Whom(allowDefMsg, defaultMsg+conCatErrStr+format, format) + asserter.reportPanic(fmt.Sprintf(f, a[1:]...)) + } else { + asserter.reportPanic(fmt.Sprintln(append([]any{defaultMsg}, a...))) + } + } else { + asserter.reportPanic(defaultMsg) + } +} + func (asserter asserter) reportPanic(s string) { if asserter.isUnitTesting() && asserter.hasCallerInfo() { fmt.Fprintln(os.Stderr, officialTestOutputPrefix+s) @@ -144,6 +174,24 @@ func (asserter asserter) callerInfo(msg string) (info string) { return } +func (asserter asserter) newCallerInfo(msg string) (info string) { + ourFmtStr := shortFmtStr + if asserter.hasFormattedCallerInfo() { + ourFmtStr = longFmtStr + } + + const framesToSkip = 4 // how many fn calls there is before FuncName call + includePath := asserter.isUnitTesting() + funcName, filename, line, ok := str.FuncName(framesToSkip, includePath) + if ok { + info = fmt.Sprintf(ourFmtStr, + filename, line, + funcName, msg) + } + + return +} + func (asserter asserter) isErrorOnly() bool { return asserter == asserterToError } From 0191b869f253e670b15099184bfa6ff25651efa0 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 21 Aug 2024 18:30:42 +0300 Subject: [PATCH 24/78] T functions no perf penalty + pkg documentation of T funcs --- try/try.go | 71 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/try/try.go b/try/try.go index bcea2d3..7ed1310 100644 --- a/try/try.go +++ b/try/try.go @@ -60,6 +60,21 @@ Or you might just want to change it later to error return: Please see the documentation and examples of [Result], [Result1], and [Result2] types and their methods. + +# try.T — Checking and Annotation + +The try package offers functions [T], [T1], [T2], and [T3] to allow fast +incremental code refactoring. For example, if you want to add an error check +specific annotation to the error check already done with [To1]: + + try.To1(io.Copy(w, r)) + +you can easily change it to [T1] and give extra message added to the error: + + try.T1(io.Copy(w, r))("error during stream copy") + +The T functions are offered mainly to allow faste feedback loop to play with the +error messages and see what works the best. */ package try @@ -245,54 +260,48 @@ func IsNotEnabled(err error) bool { // T is similar as [To] but it let's you to annotate a possible error at place. // // try.T(f.Close)("annotations") -func T(err error) func(fs string, a ...any) { - return func(fs string, a ...any) { - // NOTE if block cannot be refactored 'because it wouldn't inline - // then this whole function! - if err != nil { - er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) - panic(er) +func T(err error) func(fs string) { + return func(fs string) { + if err == nil { + return } + panic(annotateErr(err, fs)) } } // T1 is similar as [To1] but it let's you to annotate a possible error at place. // // f := try.T1(os.Open("filename")("cannot open cfg file") -func T1[T any](v T, err error) func(fs string, a ...any) T { - return func(fs string, a ...any) T { - // NOTE if block cannot be refactored 'because it wouldn't inline - // then this whole function! - if err != nil { - er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) - panic(er) +func T1[T any](v T, err error) func(fs string) T { + return func(fs string) T { + if err == nil { + return v } - return v + panic(annotateErr(err, fs)) } } // T2 is similar as [To2] but it let's you to annotate a possible error at place. -func T2[T, U any](v T, u U, err error) func(fs string, a ...any) (T, U) { - return func(fs string, a ...any) (T, U) { - // NOTE if block cannot be refactored 'because it wouldn't inline - // then this whole function! - if err != nil { - er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) - panic(er) +func T2[T, U any](v T, u U, err error) func(fs string) (T, U) { + return func(fs string) (T, U) { + if err == nil { + return v, u } - return v, u + panic(annotateErr(err, fs)) } + +} + +func annotateErr(err error, fs string) error { + return fmt.Errorf(fs+handler.WrapError, err) } // T3 is similar as [To3] but it let's you to annotate a possible error at place. -func T3[T, U, V any](v1 T, v2 U, v3 V, err error) func(fs string, a ...any) (T, U, V) { - return func(fs string, a ...any) (T, U, V) { - // NOTE if block cannot be refactored 'because it wouldn't inline - // then this whole function! - if err != nil { - er := fmt.Errorf(fs+handler.WrapError, append(a[1:], err)...) - panic(er) +func T3[T, U, V any](v1 T, v2 U, v3 V, err error) func(fs string) (T, U, V) { + return func(fs string) (T, U, V) { + if err == nil { + return v1, v2, v3 } - return v1, v2, v3 + panic(annotateErr(err, fs)) } } From 9072412d3defb654fb611d892d19f61068f8b857 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 21 Aug 2024 18:31:29 +0300 Subject: [PATCH 25/78] tests and benches for try.T function --- err2_test.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/err2_test.go b/err2_test.go index c7c8b40..424cf74 100644 --- a/err2_test.go +++ b/err2_test.go @@ -35,9 +35,9 @@ func TestTry_noError(t *testing.T) { try.To3(boolIntStrNoThrow()) _ = try.T1(noThrow())("test") - _, _ = try.T2(twoStrNoThrow())("test %d", 1) - _, _ = try.T2(intStrNoThrow())("test %d", 2) - try.T3(boolIntStrNoThrow())("test %d", 3) // linter says: _, _, _, + _, _ = try.T2(twoStrNoThrow())("test") + _, _ = try.T2(intStrNoThrow())("test") + try.T3(boolIntStrNoThrow())("test") // linter says: _, _, _, } func TestDefault_Error(t *testing.T) { @@ -890,6 +890,12 @@ func BenchmarkT_StringGenerics(b *testing.B) { } } +func BenchmarkT_IntStringGenerics(b *testing.B) { + for n := 0; n < b.N; n++ { + _, _ = try.T2(intStrNoThrow())("test") + } +} + func BenchmarkRecursionWithOldErrorCheck(b *testing.B) { var recursionWithErrorCheck func(a int) (int, error) recursionWithErrorCheck = func(a int) (int, error) { From d3ea7820ae4e4a04b6b18136706e995c45981805 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 21 Aug 2024 18:33:17 +0300 Subject: [PATCH 26/78] super performance! Split assert functions go get inlining --- assert/assert.go | 33 ++++++++++++++++++++------------ assert/assert_test.go | 44 ++++++++++++++++++++++++++----------------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index be6d66f..57e5643 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -438,8 +438,7 @@ func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { // assert violation message. func NotEqual[T comparable](val, want T, a ...any) { if want == val { - defMsg := fmt.Sprintf(assertionMsg+": got '%v' want (!= '%v')", val, want) - current().reportAssertionFault(defMsg, a) + doShouldNotBeEqual(val, want, a) } } @@ -451,11 +450,20 @@ func NotEqual[T comparable](val, want T, a ...any) { // are used to override the auto-generated assert violation message. func Equal[T comparable](val, want T, a ...any) { if want != val { - defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) - current().reportAssertionFault(defMsg, a) + doShouldBeEqual(val, want, a) } } +func doShouldBeEqual[T comparable](val, want T, a []any) { + defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) + current().newReportAssertionFault(defMsg, a) +} + +func doShouldNotBeEqual[T comparable](val, want T, a []any) { + defMsg := fmt.Sprintf(assertionMsg+": got '%v' want (!= '%v')", val, want) + current().reportAssertionFault(defMsg, a) +} + // DeepEqual asserts that the (whatever) values are equal. If not it // panics/errors (according the current Asserter) with the auto-generated // message. You can append the generated got-want message by using optional @@ -503,8 +511,7 @@ func Len(obj string, length int, a ...any) { l := len(obj) if l != length { - defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - current().reportAssertionFault(defMsg, a) + doShouldBeEqual(l, length, a) } } @@ -560,8 +567,7 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) if l != length { - defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - current().reportAssertionFault(defMsg, a) + doShouldBeEqual(l, length, a) } } @@ -617,8 +623,7 @@ func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) if l != length { - defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - current().reportAssertionFault(defMsg, a) + doShouldBeEqual(l, length, a) } } @@ -916,11 +921,15 @@ func doZero[T Number](val T, a []any) { // are used to override the auto-generated assert violation message. func NotZero[T Number](val T, a ...any) { if val == 0 { - defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= 0)", val) - current().reportAssertionFault(defMsg, a) + doNotZero(val, a) } } +func doNotZero[T Number](val T, a []any) { + defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= 0)", val) + current().newReportAssertionFault(defMsg, a) +} + // current returns a current default asserter used for package-level // functions like assert.That(). // diff --git a/assert/assert_test.go b/assert/assert_test.go index 6106b49..2d160b3 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -221,20 +221,17 @@ func ExampleGreater() { // Output: sample: assert_test.go:215: ExampleGreater.func1(): assertion violation: got '2', want <= '2' } -func assertZero(i int) { - assert.Zero(i) -} - -func assertZeroGen(i int) { - assert.Equal(i, 0) -} - -func assertMLen(b map[byte]byte, l int) { - assert.MLen(b, l) -} +func ExampleNotZero() { + sample := func(b int8) (err error) { + defer err2.Handle(&err, "sample") -func assertEqualInt2(b int) { - assert.Equal(b, 2) + assert.NotZero(b) + return err + } + var b int8 + err := sample(b) + fmt.Printf("%v", err) + // Output: sample: assert_test.go:228: ExampleNotZero.func1(): assertion violation: got '0', want (!= 0) } func BenchmarkSNotNil(b *testing.B) { @@ -267,13 +264,19 @@ func BenchmarkNotEmpty(b *testing.B) { func BenchmarkZero(b *testing.B) { for n := 0; n < b.N; n++ { - assertZero(0) + assert.Zero(0) + } +} + +func BenchmarkNotZero(b *testing.B) { + for n := 0; n < b.N; n++ { + assert.NotZero(n + 1) } } func BenchmarkEqual(b *testing.B) { for n := 0; n < b.N; n++ { - assertZeroGen(0) + assert.Equal(n, n) } } @@ -292,7 +295,7 @@ func BenchmarkAsserter_TrueIfVersion(b *testing.B) { func BenchmarkMLen(b *testing.B) { d := map[byte]byte{1: 1, 2: 2} for n := 0; n < b.N; n++ { - assertMLen(d, 2) + assert.MLen(d, 2) } } @@ -317,10 +320,17 @@ func BenchmarkSLen_thatVersion(b *testing.B) { } } +func BenchmarkNotEqualInt(b *testing.B) { + const d = 2 + for n := 0; n < b.N; n++ { + assert.NotEqual(d, 3) + } +} + func BenchmarkEqualInt(b *testing.B) { const d = 2 for n := 0; n < b.N; n++ { - assertEqualInt2(d) + assert.Equal(d, 2) } } From 420ceda5ea74f577240dfb4849fc9c2e5b250332 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 21 Aug 2024 20:26:46 +0300 Subject: [PATCH 27/78] in progress: assert pkg manual inlining generic functions --- assert/assert.go | 106 ++++++++++++++++++++++++------------------ assert/assert_test.go | 77 ++++++++++++++++++++++++++++-- assert/asserter.go | 67 ++++++-------------------- 3 files changed, 146 insertions(+), 104 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 57e5643..f119077 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -266,7 +266,7 @@ func tester() (t testing.TB) { // Note that when [Plain] asserter is used ([SetDefault]), optional arguments // are used to override the auto-generated assert violation message. func NotImplemented(a ...any) { - current().reportAssertionFault("not implemented", a) + current().reportAssertionFault(0, "not implemented", a) } // ThatNot asserts that the term is NOT true. If is it panics with the given @@ -278,7 +278,7 @@ func NotImplemented(a ...any) { func ThatNot(term bool, a ...any) { if term { defMsg := assertionMsg - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -291,7 +291,7 @@ func ThatNot(term bool, a ...any) { func That(term bool, a ...any) { if !term { defMsg := assertionMsg - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -303,7 +303,7 @@ func That(term bool, a ...any) { func NotNil[P ~*T, T any](p P, a ...any) { if p == nil { defMsg := assertionMsg + ": pointer shouldn't be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -315,7 +315,7 @@ func NotNil[P ~*T, T any](p P, a ...any) { func Nil[T any](p *T, a ...any) { if p != nil { defMsg := assertionMsg + ": pointer should be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -333,7 +333,7 @@ func Nil[T any](p *T, a ...any) { func INil(i any, a ...any) { if i != nil { defMsg := assertionMsg + ": interface should be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -351,7 +351,7 @@ func INil(i any, a ...any) { func INotNil(i any, a ...any) { if i == nil { defMsg := assertionMsg + ": interface shouldn't be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -363,7 +363,7 @@ func INotNil(i any, a ...any) { func SNil[S ~[]T, T any](s S, a ...any) { if s != nil { defMsg := assertionMsg + ": slice should be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -375,7 +375,7 @@ func SNil[S ~[]T, T any](s S, a ...any) { func CNil[C ~chan T, T any](c C, a ...any) { if c != nil { defMsg := assertionMsg + ": channel shouldn't be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -387,7 +387,7 @@ func CNil[C ~chan T, T any](c C, a ...any) { func MNil[M ~map[T]U, T comparable, U any](m M, a ...any) { if m != nil { defMsg := assertionMsg + ": map should be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -399,7 +399,7 @@ func MNil[M ~map[T]U, T comparable, U any](m M, a ...any) { func SNotNil[S ~[]T, T any](s S, a ...any) { if s == nil { defMsg := assertionMsg + ": slice shouldn't be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -411,7 +411,7 @@ func SNotNil[S ~[]T, T any](s S, a ...any) { func CNotNil[C ~chan T, T any](c C, a ...any) { if c == nil { defMsg := assertionMsg + ": channel shouldn't be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -423,7 +423,7 @@ func CNotNil[C ~chan T, T any](c C, a ...any) { func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { if m == nil { defMsg := assertionMsg + ": map shouldn't be nil" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -456,12 +456,12 @@ func Equal[T comparable](val, want T, a ...any) { func doShouldBeEqual[T comparable](val, want T, a []any) { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) - current().newReportAssertionFault(defMsg, a) + current().reportAssertionFault(1, defMsg, a) } func doShouldNotBeEqual[T comparable](val, want T, a []any) { defMsg := fmt.Sprintf(assertionMsg+": got '%v' want (!= '%v')", val, want) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(1, defMsg, a) } // DeepEqual asserts that the (whatever) values are equal. If not it @@ -474,7 +474,7 @@ func doShouldNotBeEqual[T comparable](val, want T, a []any) { func DeepEqual(val, want any, a ...any) { if !reflect.DeepEqual(val, want) { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -493,7 +493,7 @@ func DeepEqual(val, want any, a ...any) { func NotDeepEqual(val, want any, a ...any) { if reflect.DeepEqual(val, want) { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= '%v')", val, want) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -530,7 +530,7 @@ func Longer(s string, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -549,7 +549,7 @@ func Shorter(str string, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -586,7 +586,7 @@ func SLonger[S ~[]T, T any](obj S, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -605,7 +605,7 @@ func SShorter[S ~[]T, T any](obj S, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -642,7 +642,7 @@ func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -661,7 +661,7 @@ func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -680,7 +680,7 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { if l != length { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -699,7 +699,7 @@ func CLonger[C ~chan T, T any](obj C, length int, a ...any) { if l <= length { defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -718,7 +718,7 @@ func CShorter[C ~chan T, T any](obj C, length int, a ...any) { if l >= length { defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -732,12 +732,16 @@ func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) val, ok = obj[key] if !ok { - defMsg := fmt.Sprintf(assertionMsg+": key '%v' doesn't exist", key) - current().reportAssertionFault(defMsg, a) + doMKeyExists(key, a) } return val } +func doMKeyExists[T comparable](key T, a []any) { + defMsg := fmt.Sprintf(assertionMsg+": key '%v' doesn't exist", key) + current().reportAssertionFault(1, defMsg, a) +} + // NotEmpty asserts that the string is not empty. If it is, it panics/errors // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. @@ -747,7 +751,7 @@ func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) func NotEmpty(obj string, a ...any) { if obj == "" { defMsg := assertionMsg + ": string shouldn't be empty" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -760,7 +764,7 @@ func NotEmpty(obj string, a ...any) { func Empty(obj string, a ...any) { if obj != "" { defMsg := assertionMsg + ": string should be empty" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -777,8 +781,7 @@ func SEmpty[S ~[]T, T any](obj S, a ...any) { l := len(obj) if l != 0 { - defMsg := assertionMsg + ": slice should be empty" - current().reportAssertionFault(defMsg, a) + doEmptyNamed("", "slice", a) } } @@ -795,8 +798,7 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { l := len(obj) if l == 0 { - defMsg := assertionMsg + ": slice shouldn't be empty" - current().reportAssertionFault(defMsg, a) + doEmptyNamed("not", "slice", a) } } @@ -815,8 +817,7 @@ func MEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { l := len(obj) if l != 0 { - defMsg := assertionMsg + ": map should be empty" - current().reportAssertionFault(defMsg, a) + doEmptyNamed("", "map", a) } } @@ -835,11 +836,16 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { l := len(obj) if l == 0 { - defMsg := assertionMsg + ": map shouldn't be empty" - current().reportAssertionFault(defMsg, a) + doEmptyNamed("not", "map", a) } } +func doEmptyNamed(not, name string, a []any) { + not = x.Whom(not == "not", " not ", "") + defMsg := assertionMsg + ": " + name + " should" + not + "be empty" + current().reportAssertionFault(1, defMsg, a) +} + // NoError asserts that the error is nil. If is not it panics with the given // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. @@ -853,7 +859,7 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { func NoError(err error, a ...any) { if err != nil { defMsg := assertionMsg + conCatErrStr + err.Error() - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -866,7 +872,7 @@ func NoError(err error, a ...any) { func Error(err error, a ...any) { if err == nil { defMsg := "Error:" + assertionMsg + ": missing error" - current().reportAssertionFault(defMsg, a) + current().reportAssertionFault(0, defMsg, a) } } @@ -878,11 +884,15 @@ func Error(err error, a ...any) { // are used to override the auto-generated assert violation message. func Greater[T Number](val, want T, a ...any) { if val <= want { - defMsg := fmt.Sprintf(assertionMsg+": got '%v', want <= '%v'", val, want) - current().reportAssertionFault(defMsg, a) + doGreater(val, want, a) } } +func doGreater[T Number](val, want T, a []any) { + defMsg := fmt.Sprintf(assertionMsg+": got '%v', want <= '%v'", val, want) + current().reportAssertionFault(1, defMsg, a) +} + // Less asserts that the value is less than want. If it is not it panics and // builds a violation message. Thanks to inlining, the performance penalty is // equal to a single 'if-statement' that is almost nothing. @@ -891,11 +901,15 @@ func Greater[T Number](val, want T, a ...any) { // are used to override the auto-generated assert violation message. func Less[T Number](val, want T, a ...any) { if val >= want { - defMsg := fmt.Sprintf(assertionMsg+": got '%v', want >= '%v'", val, want) - current().reportAssertionFault(defMsg, a) + doLess(val, want, a) } } +func doLess[T Number](val, want T, a []any) { + defMsg := fmt.Sprintf(assertionMsg+": got '%v', want >= '%v'", val, want) + current().reportAssertionFault(1, defMsg, a) +} + // Zero asserts that the value is 0. If it is not it panics and builds a // violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. @@ -910,7 +924,7 @@ func Zero[T Number](val T, a ...any) { func doZero[T Number](val T, a []any) { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (== '0')", val) - current().newReportAssertionFault(defMsg, a) + current().reportAssertionFault(1, defMsg, a) } // NotZero asserts that the value != 0. If it is not it panics and builds a @@ -927,7 +941,7 @@ func NotZero[T Number](val T, a ...any) { func doNotZero[T Number](val T, a []any) { defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= 0)", val) - current().newReportAssertionFault(defMsg, a) + current().reportAssertionFault(1, defMsg, a) } // current returns a current default asserter used for package-level diff --git a/assert/assert_test.go b/assert/assert_test.go index 2d160b3..b623178 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -106,7 +106,7 @@ func ExampleSNotEmpty() { } err := sample([]byte{}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:104: ExampleSNotEmpty.func1(): assertion violation: slice shouldn't be empty + // Output: sample: assert_test.go:104: ExampleSNotEmpty.func1(): assertion violation: slice should not be empty } func ExampleNotEmpty() { @@ -234,6 +234,55 @@ func ExampleNotZero() { // Output: sample: assert_test.go:228: ExampleNotZero.func1(): assertion violation: got '0', want (!= 0) } +func BenchmarkMKeyExists(b *testing.B) { + bs := map[int]int{0:0, 1:1} + for n := 0; n < b.N; n++ { + assert.MKeyExists(bs, 1) + } +} + +func BenchmarkMNotEmpty(b *testing.B) { + bs := map[int]int{0:0, 1:1} + for n := 0; n < b.N; n++ { + assert.MNotEmpty(bs) + } +} + +func BenchmarkMEmpty(b *testing.B) { + bs := map[int]int{} + for n := 0; n < b.N; n++ { + assert.MEmpty(bs) + } +} + +func BenchmarkNotEmpty(b *testing.B) { + bs := "not empty" + for n := 0; n < b.N; n++ { + assert.NotEmpty(bs) + } +} + +func BenchmarkEmpty(b *testing.B) { + bs := "" + for n := 0; n < b.N; n++ { + assert.Empty(bs) + } +} + +func BenchmarkSEmpty(b *testing.B) { + bs := []int{} + for n := 0; n < b.N; n++ { + assert.SEmpty(bs) + } +} + +func BenchmarkSNotEmpty(b *testing.B) { + bs := []byte{0} + for n := 0; n < b.N; n++ { + assert.SNotEmpty(bs) + } +} + func BenchmarkSNotNil(b *testing.B) { bs := []byte{0} for n := 0; n < b.N; n++ { @@ -255,10 +304,15 @@ func BenchmarkThat(b *testing.B) { } } -func BenchmarkNotEmpty(b *testing.B) { - str := "test" +func BenchmarkGreater(b *testing.B) { for n := 0; n < b.N; n++ { - assert.NotEmpty(str) + assert.Greater(1, 0) + } +} + +func BenchmarkLess(b *testing.B) { + for n := 0; n < b.N; n++ { + assert.Less(0, 1) } } @@ -274,6 +328,12 @@ func BenchmarkNotZero(b *testing.B) { } } +func BenchmarkError(b *testing.B) { + for n := 0; n < b.N; n++ { + assert.Error(err2.ErrNotAccess) + } +} + func BenchmarkEqual(b *testing.B) { for n := 0; n < b.N; n++ { assert.Equal(n, n) @@ -306,6 +366,15 @@ func BenchmarkSLen(b *testing.B) { } } +func BenchmarkCLen(b *testing.B) { + d := make(chan byte, 2) + d <- byte(1) + d <- byte(1) + for n := 0; n < b.N; n++ { + assert.CLen(d, 2) + } +} + func BenchmarkLen(b *testing.B) { s := "len" for n := 0; n < b.N; n++ { diff --git a/assert/asserter.go b/assert/asserter.go index 0b5295b..9f24f11 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -56,52 +56,28 @@ const officialTestOutputPrefix = " " // and correct assert messages, and we can add information to it if we want to. // If asserter is Plain (isErrorOnly()) user wants to override automatic assert // messgages with our given, usually simple message. -func (asserter asserter) reportAssertionFault(defaultMsg string, a []any) { +func (asserter asserter) reportAssertionFault( + extraInd int, + defaultMsg string, + a []any, +) { if asserter.hasStackTrace() { if asserter.isUnitTesting() { // Note. that the assert in the test function is printed in // reportPanic below - const stackLvl = 5 // amount of functions before we're here + const StackLvl = 5 // amount of functions before we're here + stackLvl := StackLvl + extraInd debug.PrintStackForTest(os.Stderr, stackLvl) } else { // amount of functions before we're here, which is different // between runtime (this) and test-run (above) - const stackLvl = 2 + const StackLvl = 2 + stackLvl := StackLvl + extraInd debug.PrintStack(stackLvl) } } if asserter.hasCallerInfo() { - defaultMsg = asserter.callerInfo(defaultMsg) - } - if len(a) > 0 { - if format, ok := a[0].(string); ok { - allowDefMsg := !asserter.isErrorOnly() && defaultMsg != "" - f := x.Whom(allowDefMsg, defaultMsg+conCatErrStr+format, format) - asserter.reportPanic(fmt.Sprintf(f, a[1:]...)) - } else { - asserter.reportPanic(fmt.Sprintln(append([]any{defaultMsg}, a...))) - } - } else { - asserter.reportPanic(defaultMsg) - } -} - -func (asserter asserter) newReportAssertionFault(defaultMsg string, a []any) { - if asserter.hasStackTrace() { - if asserter.isUnitTesting() { - // Note. that the assert in the test function is printed in - // reportPanic below - const stackLvl = 4 // amount of functions before we're here - debug.PrintStackForTest(os.Stderr, stackLvl) - } else { - // amount of functions before we're here, which is different - // between runtime (this) and test-run (above) - const stackLvl = 1 - debug.PrintStack(stackLvl) - } - } - if asserter.hasCallerInfo() { - defaultMsg = asserter.newCallerInfo(defaultMsg) + defaultMsg = asserter.callerInfo(defaultMsg, extraInd) } if len(a) > 0 { if format, ok := a[0].(string); ok { @@ -156,31 +132,14 @@ Assertion Fault at: var shortFmtStr = `%s:%d: %s(): %s` -func (asserter asserter) callerInfo(msg string) (info string) { - ourFmtStr := shortFmtStr - if asserter.hasFormattedCallerInfo() { - ourFmtStr = longFmtStr - } - - const framesToSkip = 3 // how many fn calls there is before FuncName call - includePath := asserter.isUnitTesting() - funcName, filename, line, ok := str.FuncName(framesToSkip, includePath) - if ok { - info = fmt.Sprintf(ourFmtStr, - filename, line, - funcName, msg) - } - - return -} - -func (asserter asserter) newCallerInfo(msg string) (info string) { +func (asserter asserter) callerInfo(msg string, extraInd int) (info string) { ourFmtStr := shortFmtStr if asserter.hasFormattedCallerInfo() { ourFmtStr = longFmtStr } - const framesToSkip = 4 // how many fn calls there is before FuncName call + const ToSkip = 3 + framesToSkip := ToSkip + extraInd // how many fn calls there is before FuncName call includePath := asserter.isUnitTesting() funcName, filename, line, ok := str.FuncName(framesToSkip, includePath) if ok { From 47d4495b9074528d1fce89be471813452e1a9a57 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 22 Aug 2024 16:09:03 +0300 Subject: [PATCH 28/78] gofmt --- assert/assert_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assert/assert_test.go b/assert/assert_test.go index b623178..5863b94 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -235,14 +235,14 @@ func ExampleNotZero() { } func BenchmarkMKeyExists(b *testing.B) { - bs := map[int]int{0:0, 1:1} + bs := map[int]int{0: 0, 1: 1} for n := 0; n < b.N; n++ { assert.MKeyExists(bs, 1) } } func BenchmarkMNotEmpty(b *testing.B) { - bs := map[int]int{0:0, 1:1} + bs := map[int]int{0: 0, 1: 1} for n := 0; n < b.N; n++ { assert.MNotEmpty(bs) } From ca6dc12277f70e5b8988fdc9b936957dd62a4ae3 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 22 Aug 2024 18:36:27 +0300 Subject: [PATCH 29/78] template asserts benches for S, M and C --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index bc701da..72125f1 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,15 @@ bench: bench_T: $(GO) test $(TEST_ARGS) -bench='BenchmarkT_.*' $(PKG_ERR2) +bench_S: + $(GO) test $(TEST_ARGS) -bench='BenchmarkS.*' $(PKG_ASSERT) + +bench_M: + $(GO) test $(TEST_ARGS) -bench='BenchmarkM.*' $(PKG_ASSERT) + +bench_C: + $(GO) test $(TEST_ARGS) -bench='BenchmarkC.*' $(PKG_ASSERT) + bench_zero: $(GO) test $(TEST_ARGS) -bench='BenchmarkZero.*' $(PKG_ASSERT) From 4fc7ffd291d7833f1a251979e064591eea6d27be Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 22 Aug 2024 18:37:32 +0300 Subject: [PATCH 30/78] rest of the assert inline optimized + benches --- assert/assert.go | 39 +++++++------- assert/assert_test.go | 122 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 139 insertions(+), 22 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index f119077..a38884c 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -529,11 +529,15 @@ func Longer(s string, length int, a ...any) { l := len(s) if l <= length { - defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - current().reportAssertionFault(0, defMsg, a) + doLonger(l, length, a) } } +func doLonger(l int, length int, a []any) { + defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) + current().reportAssertionFault(1, defMsg, a) +} + // Shorter asserts that the length of the string is shorter to the given. If not // it panics/errors (according the current Asserter) with the auto-generated // message. You can append the generated got-want message by using optional @@ -548,11 +552,15 @@ func Shorter(str string, length int, a ...any) { l := len(str) if l >= length { - defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - current().reportAssertionFault(0, defMsg, a) + doShorter(l, length, a) } } +func doShorter(l int, length int, a []any) { + defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) + current().reportAssertionFault(1, defMsg, a) +} + // SLen asserts that the length of the slice is equal to the given. If not it // panics/errors (according the current Asserter) with the auto-generated // message. You can append the generated got-want message by using optional @@ -585,8 +593,7 @@ func SLonger[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) if l <= length { - defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - current().reportAssertionFault(0, defMsg, a) + doLonger(l, length, a) } } @@ -604,8 +611,7 @@ func SShorter[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) if l >= length { - defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - current().reportAssertionFault(0, defMsg, a) + doShorter(l, length, a) } } @@ -641,8 +647,7 @@ func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) if l <= length { - defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - current().reportAssertionFault(0, defMsg, a) + doLonger(l, length, a) } } @@ -660,8 +665,7 @@ func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) if l >= length { - defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - current().reportAssertionFault(0, defMsg, a) + doShorter(l, length, a) } } @@ -679,8 +683,7 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) if l != length { - defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) - current().reportAssertionFault(0, defMsg, a) + doShouldBeEqual(l, length, a) } } @@ -698,8 +701,7 @@ func CLonger[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) if l <= length { - defMsg := fmt.Sprintf(assertionMsg+gotWantLongerFmt, l, length) - current().reportAssertionFault(0, defMsg, a) + doLonger(l, length, a) } } @@ -717,8 +719,7 @@ func CShorter[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) if l >= length { - defMsg := fmt.Sprintf(assertionMsg+gotWantShorterFmt, l, length) - current().reportAssertionFault(0, defMsg, a) + doShorter(l, length, a) } } @@ -737,7 +738,7 @@ func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) return val } -func doMKeyExists[T comparable](key T, a []any) { +func doMKeyExists(key any, a []any) { defMsg := fmt.Sprintf(assertionMsg+": key '%v' doesn't exist", key) current().reportAssertionFault(1, defMsg, a) } diff --git a/assert/assert_test.go b/assert/assert_test.go index 5863b94..836bf33 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -269,6 +269,20 @@ func BenchmarkEmpty(b *testing.B) { } } +func BenchmarkLonger(b *testing.B) { + bs := "tst" + for n := 0; n < b.N; n++ { + assert.Longer(bs, 2) + } +} + +func BenchmarkShorter(b *testing.B) { + bs := "1" + for n := 0; n < b.N; n++ { + assert.Shorter(bs, 2) + } +} + func BenchmarkSEmpty(b *testing.B) { bs := []int{} for n := 0; n < b.N; n++ { @@ -290,6 +304,41 @@ func BenchmarkSNotNil(b *testing.B) { } } +func BenchmarkMNotNil(b *testing.B) { + var bs = map[int]int{0: 0} + for n := 0; n < b.N; n++ { + assert.MNotNil(bs) + } +} + +func BenchmarkCNotNil(b *testing.B) { + var bs = make(chan int) + for n := 0; n < b.N; n++ { + assert.CNotNil(bs) + } +} + +func BenchmarkINotNil(b *testing.B) { + var bs any = err2.ErrNotAccess + for n := 0; n < b.N; n++ { + assert.INotNil(bs) + } +} + +func BenchmarkINil(b *testing.B) { + var bs any + for n := 0; n < b.N; n++ { + assert.INil(bs) + } +} + +func BenchmarkNil(b *testing.B) { + var bs *int + for n := 0; n < b.N; n++ { + assert.Nil(bs) + } +} + func BenchmarkNotNil(b *testing.B) { bs := new(int) for n := 0; n < b.N; n++ { @@ -297,6 +346,27 @@ func BenchmarkNotNil(b *testing.B) { } } +func BenchmarkSNil(b *testing.B) { + var bs []int + for n := 0; n < b.N; n++ { + assert.SNil(bs) + } +} + +func BenchmarkMNil(b *testing.B) { + var bs map[int]int + for n := 0; n < b.N; n++ { + assert.MNil(bs) + } +} + +func BenchmarkCNil(b *testing.B) { + var bs chan int + for n := 0; n < b.N; n++ { + assert.CNil(bs) + } +} + func BenchmarkThat(b *testing.B) { const four = 4 for n := 0; n < b.N; n++ { @@ -359,6 +429,20 @@ func BenchmarkMLen(b *testing.B) { } } +func BenchmarkMShorter(b *testing.B) { + d := map[byte]byte{1: 1, 2: 2} + for n := 0; n < b.N; n++ { + assert.MShorter(d, 4) + } +} + +func BenchmarkMLonger(b *testing.B) { + d := map[byte]byte{1: 1, 2: 2} + for n := 0; n < b.N; n++ { + assert.MLonger(d, 1) + } +} + func BenchmarkSLen(b *testing.B) { d := []byte{1, 2} for n := 0; n < b.N; n++ { @@ -366,15 +450,47 @@ func BenchmarkSLen(b *testing.B) { } } +func BenchmarkSShorter(b *testing.B) { + d := []byte{1, 2} + for n := 0; n < b.N; n++ { + assert.SShorter(d, 3) + } +} + +func BenchmarkSLonger(b *testing.B) { + d := []byte{1, 2} + for n := 0; n < b.N; n++ { + assert.SLonger(d, 1) + } +} + func BenchmarkCLen(b *testing.B) { - d := make(chan byte, 2) - d <- byte(1) - d <- byte(1) + d := make(chan int, 2) + d <- int(1) + d <- int(1) for n := 0; n < b.N; n++ { assert.CLen(d, 2) } } +func BenchmarkCShorter(b *testing.B) { + d := make(chan int, 2) + d <- int(1) + d <- int(1) + for n := 0; n < b.N; n++ { + assert.CShorter(d, 3) + } +} + +func BenchmarkCLonger(b *testing.B) { + d := make(chan int, 2) + d <- int(1) + d <- int(1) + for n := 0; n < b.N; n++ { + assert.CLonger(d, 1) + } +} + func BenchmarkLen(b *testing.B) { s := "len" for n := 0; n < b.N; n++ { From 4a427d47db51af44d7cd5332ab215f1902e7de76 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 1 Sep 2024 17:06:38 +0300 Subject: [PATCH 31/78] Equal & Len assert better messages --- assert/assert.go | 38 +++++++++++++++++++++++++------------- assert/assert_test.go | 4 ++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index a38884c..f3c0075 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -147,7 +147,11 @@ var ( ) const ( - assertionMsg = "assertion violation" + assertionMsg = "assertion violation" + assertionEqualMsg = "assert equal" + assertionNotEqualMsg = "assert not equal" + assertionLenMsg = "assert len" + gotWantFmt = ": got '%v', want '%v'" gotWantLongerFmt = ": got '%v', should be longer than '%v'" gotWantShorterFmt = ": got '%v', should be shorter than '%v'" @@ -438,7 +442,7 @@ func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { // assert violation message. func NotEqual[T comparable](val, want T, a ...any) { if want == val { - doShouldNotBeEqual(val, want, a) + doShouldNotBeEqual(assertionNotEqualMsg, val, want, a) } } @@ -450,17 +454,17 @@ func NotEqual[T comparable](val, want T, a ...any) { // are used to override the auto-generated assert violation message. func Equal[T comparable](val, want T, a ...any) { if want != val { - doShouldBeEqual(val, want, a) + doShouldBeEqual(assertionEqualMsg, val, want, a) } } -func doShouldBeEqual[T comparable](val, want T, a []any) { - defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) +func doShouldBeEqual[T comparable](aname string, val, want T, a []any) { + defMsg := fmt.Sprintf(aname+gotWantFmt, val, want) current().reportAssertionFault(1, defMsg, a) } -func doShouldNotBeEqual[T comparable](val, want T, a []any) { - defMsg := fmt.Sprintf(assertionMsg+": got '%v' want (!= '%v')", val, want) +func doShouldNotBeEqual[T comparable](aname string, val, want T, a []any) { + defMsg := fmt.Sprintf(aname+": got '%v' want (!= '%v')", val, want) current().reportAssertionFault(1, defMsg, a) } @@ -492,7 +496,11 @@ func DeepEqual(val, want any, a ...any) { // assert.DeepEqual(pubKey, ed25519.PublicKey(pubKeyBytes)) func NotDeepEqual(val, want any, a ...any) { if reflect.DeepEqual(val, want) { - defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (!= '%v')", val, want) + defMsg := fmt.Sprintf( + assertionMsg+": got '%v', want (!= '%v')", + val, + want, + ) current().reportAssertionFault(0, defMsg, a) } } @@ -511,7 +519,7 @@ func Len(obj string, length int, a ...any) { l := len(obj) if l != length { - doShouldBeEqual(l, length, a) + doShouldBeEqual(assertionLenMsg, l, length, a) } } @@ -575,7 +583,7 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) if l != length { - doShouldBeEqual(l, length, a) + doShouldBeEqual(assertionLenMsg, l, length, a) } } @@ -629,7 +637,7 @@ func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { l := len(obj) if l != length { - doShouldBeEqual(l, length, a) + doShouldBeEqual(assertionLenMsg, l, length, a) } } @@ -683,7 +691,7 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { l := len(obj) if l != length { - doShouldBeEqual(l, length, a) + doShouldBeEqual(assertionLenMsg, l, length, a) } } @@ -728,7 +736,11 @@ func CShorter[C ~chan T, T any](obj C, length int, a ...any) { // // Note that when [Plain] asserter is used ([SetDefault]), optional arguments // are used to override the auto-generated assert violation message. -func MKeyExists[M ~map[T]U, T comparable, U any](obj M, key T, a ...any) (val U) { +func MKeyExists[M ~map[T]U, T comparable, U any]( + obj M, + key T, + a ...any, +) (val U) { var ok bool val, ok = obj[key] diff --git a/assert/assert_test.go b/assert/assert_test.go index 836bf33..adeea70 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -82,7 +82,7 @@ func ExampleEqual() { } err := sample([]byte{1, 2}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:80: ExampleEqual.func1(): assertion violation: got '2', want '3' + // Output: sample: assert_test.go:80: ExampleEqual.func1(): assert equal: got '2', want '3' } func ExampleSLen() { @@ -94,7 +94,7 @@ func ExampleSLen() { } err := sample([]byte{1, 2}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:92: ExampleSLen.func1(): assertion violation: got '2', want '3' + // Output: sample: assert_test.go:92: ExampleSLen.func1(): assert len: got '2', want '3' } func ExampleSNotEmpty() { From c05d0026b38e98569344ab1460dbc4a96fc1afc8 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 1 Sep 2024 17:09:23 +0300 Subject: [PATCH 32/78] better formatting for skimmability --- assert/asserter.go | 3 +- err2_test.go | 359 ++++++++++++++++-------------- internal/debug/debug.go | 7 +- internal/debug/debug_test.go | 205 ++++++++++++++--- internal/handler/handlers_test.go | 70 +++++- internal/str/str_test.go | 36 ++- internal/tracer/tracer.go | 6 +- internal/x/x_test.go | 39 +++- samples/main-db-sample.go | 23 +- samples/main-nil.go | 16 +- samples/main.go | 6 +- try/copy_test.go | 6 +- try/out.go | 5 +- 13 files changed, 547 insertions(+), 234 deletions(-) diff --git a/assert/asserter.go b/assert/asserter.go index 9f24f11..7edccda 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -164,7 +164,8 @@ func (asserter asserter) hasStackTrace() bool { } func (asserter asserter) hasCallerInfo() bool { - return asserter&asserterCallerInfo != 0 || asserter.hasFormattedCallerInfo() + return asserter&asserterCallerInfo != 0 || + asserter.hasFormattedCallerInfo() } func (asserter asserter) hasFormattedCallerInfo() bool { diff --git a/err2_test.go b/err2_test.go index 424cf74..61c91fa 100644 --- a/err2_test.go +++ b/err2_test.go @@ -23,8 +23,9 @@ func throw() (string, error) { return "", errToTest } func noThrow() (string, error) { return "test", nil } func noErr() error { return nil } -func twoStrNoThrow() (string, string, error) { return "test", "test", nil } -func intStrNoThrow() (int, string, error) { return 1, "test", nil } +func twoStrNoThrow() (string, string, error) { return "test", "test", nil } +func intStrNoThrow() (int, string, error) { return 1, "test", nil } + func boolIntStrNoThrow() (bool, int, string, error) { return true, 1, "test", nil } func TestTry_noError(t *testing.T) { @@ -62,42 +63,48 @@ func TestTry_Error(t *testing.T) { 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() { - require.That(t, handlerCalled) - }() - // This is the handler we are testing! - defer err2.Handle(&err, func(noerr bool) { - handlerCalled = noerr - }) + 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() { + require.That(t, handlerCalled) + }() + // This is the handler we are testing! + defer err2.Handle(&err, func(noerr bool) { + handlerCalled = noerr + }) - try.To(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() { - require.That(t, handlerCalled) - }() - defer err2.Handle(&err, func(err error) error { - // this should not be called, so lets try to fuckup things... - handlerCalled = false - require.That(t, false) - return err - }) + 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() { + require.That(t, handlerCalled) + }() + defer err2.Handle(&err, func(err error) error { + // this should not be called, so lets try to fuckup things... + handlerCalled = false + require.That(t, false) + return err + }) - // This is the handler we are testing! - defer err2.Handle(&err, func(noerr bool) { - handlerCalled = noerr - }) + // This is the handler we are testing! + defer err2.Handle(&err, func(noerr bool) { + handlerCalled = noerr + }) - try.To(noErr()) - }) + try.To(noErr()) + }, + ) t.Run("noerr handler is the last and error happens", func(t *testing.T) { t.Parallel() @@ -123,45 +130,52 @@ func TestHandle_noerrHandler(t *testing.T) { 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 - callCount int - ) - defer func() { - require.ThatNot(t, handlerCalled) - require.Equal(t, callCount, 2) - require.Equal(t, err.Error(), finalAnnotatedErr.Error()) - }() + 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 + callCount int + ) + defer func() { + require.ThatNot(t, handlerCalled) + require.Equal(t, callCount, 2) + require.Equal(t, err.Error(), finalAnnotatedErr.Error()) + }() - // This is the handler we are testing! AND it's not called in error. - defer err2.Handle(&err, func(noerr bool) { - require.That(t, false, "if error occurs/reset, this cannot happen") - handlerCalled = noerr - }) + // This is the handler we are testing! AND it's not called in error. + defer err2.Handle(&err, func(noerr bool) { + require.That( + 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 { - require.That(t, er != nil, "er val: ", er, err) - require.Equal(t, callCount, 1, "this is called in sencond") - callCount++ - return er - }) + // important! test that our handler doesn't change the current error + // and it's not nil + defer err2.Handle(&err, func(er error) error { + require.That(t, er != nil, "er val: ", er, err) + require.Equal(t, callCount, 1, "this is called in sencond") + callCount++ + return er + }) - defer err2.Handle(&err, func(err error) error { - // this should not be called, so lets try to fuckup things... - require.Equal(t, callCount, 0, "this is called in first") - callCount++ - handlerCalled = false - require.That(t, err != nil) - return finalAnnotatedErr - }) - try.To1(throw()) - }) + defer err2.Handle(&err, func(err error) error { + // this should not be called, so lets try to fuckup things... + require.Equal(t, callCount, 0, "this is called in first") + callCount++ + handlerCalled = false + require.That(t, err != nil) + return finalAnnotatedErr + }) + try.To1(throw()) + }, + ) t.Run("noerr handler is first and NO error happens", func(t *testing.T) { t.Parallel() @@ -186,110 +200,119 @@ func TestHandle_noerrHandler(t *testing.T) { 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() { - require.That(t, handlerCalled) - }() + 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() { + require.That(t, handlerCalled) + }() - // This is the handler we are testing! - defer err2.Handle(&err, func(noerr bool) { - require.That(t, true) - require.That(t, noerr) - handlerCalled = noerr - }) + // This is the handler we are testing! + defer err2.Handle(&err, func(noerr bool) { + require.That(t, true) + require.That(t, noerr) + handlerCalled = noerr + }) - defer err2.Handle(&err) + defer err2.Handle(&err) - defer err2.Handle(&err, func(err error) error { - require.That(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 { + require.That(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 { - require.That(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()) - }) + defer err2.Handle(&err, func(err error) error { + require.That(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() { - require.That(t, noerrHandlerCalled) - require.That(t, errHandlerCalled) - }() + 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() { + require.That(t, noerrHandlerCalled) + require.That(t, errHandlerCalled) + }() - // This is the handler we are testing! - defer err2.Handle(&err, func(noerr bool) { - require.That(t, true) // we are here, for debugging - require.That(t, noerr) - noerrHandlerCalled = noerr - }) + // This is the handler we are testing! + defer err2.Handle(&err, func(noerr bool) { + require.That(t, true) // we are here, for debugging + require.That(t, noerr) + noerrHandlerCalled = noerr + }) - // this is the err handler that -- RESETS -- the error to nil - defer err2.Handle(&err, func(err error) error { - require.That(t, err != nil) // helps fast debugging + // this is the err handler that -- RESETS -- the error to nil + defer err2.Handle(&err, func(err error) error { + require.That(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 - }) + // 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 { - require.That(t, err != nil) // helps fast debugging - // this should not be called, so lets try to fuckup things... - noerrHandlerCalled = false // see first deferred function + defer err2.Handle(&err, func(err error) error { + require.That(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()) - }) + 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 - var handlerCalled bool - defer func() { - require.That(t, handlerCalled) - }() + 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() { + require.That(t, handlerCalled) + }() - defer err2.Handle(&err) - defer err2.Handle(&err) + defer err2.Handle(&err) + defer err2.Handle(&err) - defer err2.Handle(&err, func(err error) error { - require.That(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 { + require.That(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 testing! - defer err2.Handle(&err, func(noerr bool) { - require.That(t, true, "this must be called") - require.That(t, noerr) - handlerCalled = noerr - }) + // This is the handler we are testing! + defer err2.Handle(&err, func(noerr bool) { + require.That(t, true, "this must be called") + require.That(t, noerr) + handlerCalled = noerr + }) - defer err2.Handle(&err, func(err error) error { - require.That(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()) - }) + defer err2.Handle(&err, func(err error) error { + require.That(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) { @@ -353,7 +376,11 @@ func TestPanickingCatchAll(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() defer func() { - require.That(t, recover() == nil, "panics should NOT carry on") + require.That( + t, + recover() == nil, + "panics should NOT carry on", + ) }() tt.args.f() }) @@ -397,7 +424,11 @@ func TestPanickingCarryOn_Handle(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() defer func() { - require.That(t, recover() != nil, "panics should went thru when not our errors") + require.That( + t, + recover() != nil, + "panics should went thru when not our errors", + ) }() tt.args.f() }) @@ -569,7 +600,11 @@ func TestPanicking_Catch(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() defer func() { - require.That(t, recover() == nil, "panics should NOT carry on") + require.That( + t, + recover() == nil, + "panics should NOT carry on", + ) }() tt.args.f() }) diff --git a/internal/debug/debug.go b/internal/debug/debug.go index c820403..8185dbe 100644 --- a/internal/debug/debug.go +++ b/internal/debug/debug.go @@ -38,7 +38,9 @@ var ( PackageRegexp = regexp.MustCompile(`lainio/err2[a-zA-Z0-9_/.\[\]]*\(`) // we want to check that this is not our package - packageRegexp = regexp.MustCompile(`^github\.com/lainio/err2[a-zA-Z0-9_/.\[\]]*\(`) + packageRegexp = regexp.MustCompile( + `^github\.com/lainio/err2[a-zA-Z0-9_/.\[\]]*\(`, + ) // testing package exluding regexps: testingPkgRegexp = regexp.MustCompile(`^testing\.`) @@ -77,7 +79,8 @@ func (si StackInfo) needToCalcFnNameAnchor() bool { // isLvlOnly return true if all fields are nil and Level != 0 that should be // used then. func (si StackInfo) isLvlOnly() bool { - return si.Level != 0 && si.Regexp == nil && si.PackageName == "" && si.FuncName == "" + return si.Level != 0 && si.Regexp == nil && si.PackageName == "" && + si.FuncName == "" } func (si StackInfo) canPrint(s string, anchorLine, i int) (ok bool) { diff --git a/internal/debug/debug_test.go b/internal/debug/debug_test.go index e1385cb..a6b1748 100644 --- a/internal/debug/debug_test.go +++ b/internal/debug/debug_test.go @@ -21,9 +21,21 @@ func TestFullName(t *testing.T) { } tests := []ttest{ {"all empty", args{StackInfo{"", "", 0, nil, nil}}, ""}, - {"namespaces", args{StackInfo{"lainio/err2", "", 0, nil, nil}}, "lainio/err2"}, - {"both", args{StackInfo{"lainio/err2", "try", 0, nil, nil}}, "lainio/err2.try"}, - {"short both", args{StackInfo{"err2", "Handle", 0, nil, nil}}, "err2.Handle"}, + { + "namespaces", + args{StackInfo{"lainio/err2", "", 0, nil, nil}}, + "lainio/err2", + }, + { + "both", + args{StackInfo{"lainio/err2", "try", 0, nil, nil}}, + "lainio/err2.try", + }, + { + "short both", + args{StackInfo{"err2", "Handle", 0, nil, nil}}, + "err2.Handle", + }, {"func", args{StackInfo{"", "try", 0, nil, nil}}, "try"}, } for _, ttv := range tests { @@ -164,18 +176,36 @@ func TestFnName(t *testing.T) { tests := []ttest{ {"panic", "panic({0x102ed30c0, 0x1035910f0})", "panic"}, - {"our namespace", "github.com/lainio/err2/internal/debug.FprintStack({0x102ff7e88, 0x14000010020}, {{0x0, 0x0}, {0x102c012b8, 0x6}, 0x1, 0x140000bcb40})", - "debug.FprintStack"}, - {"our namespace and func1", "github.com/lainio/err2/internal/debug.FprintStack.func1({0x102ff7e88, 0x14000010020}, {{0x0, 0x0}, {0x102c012b8, 0x6}, 0x1, 0x140000bcb40})", - "debug.FprintStack"}, - {"our double namespace", "github.com/lainio/err2/internal/handler.Info.callPanicHandler({{0x102ed30c0, 0x1035910f0}, {0x102ff7e88, 0x14000010020}, 0x0, 0x140018643e0, 0x0})", - "handler.Info.callPanicHandler"}, - {"our handler process", "github.com/lainio/err2/internal/handler.Process.func1({{0x102ed30c0, 0x1035910f0}, {0x102ff7e88, 0x14000010020}, 0x0, 0x140018643e0, 0x0})", - "handler.Process"}, - {"our handler process and more anonymous funcs", "github.com/lainio/err2/internal/handler.Process.func1.2({{0x102ed30c0, 0x1035910f0}, {0x102ff7e88, 0x14000010020}, 0x0, 0x140018643e0, 0x0})", - "handler.Process"}, - {"method and package name", "github.com/findy-network/findy-agent/agent/ssi.(*DIDAgent).AssertWallet(...)", - "ssi.(*DIDAgent).AssertWallet"}, + { + "our namespace", + "github.com/lainio/err2/internal/debug.FprintStack({0x102ff7e88, 0x14000010020}, {{0x0, 0x0}, {0x102c012b8, 0x6}, 0x1, 0x140000bcb40})", + "debug.FprintStack", + }, + { + "our namespace and func1", + "github.com/lainio/err2/internal/debug.FprintStack.func1({0x102ff7e88, 0x14000010020}, {{0x0, 0x0}, {0x102c012b8, 0x6}, 0x1, 0x140000bcb40})", + "debug.FprintStack", + }, + { + "our double namespace", + "github.com/lainio/err2/internal/handler.Info.callPanicHandler({{0x102ed30c0, 0x1035910f0}, {0x102ff7e88, 0x14000010020}, 0x0, 0x140018643e0, 0x0})", + "handler.Info.callPanicHandler", + }, + { + "our handler process", + "github.com/lainio/err2/internal/handler.Process.func1({{0x102ed30c0, 0x1035910f0}, {0x102ff7e88, 0x14000010020}, 0x0, 0x140018643e0, 0x0})", + "handler.Process", + }, + { + "our handler process and more anonymous funcs", + "github.com/lainio/err2/internal/handler.Process.func1.2({{0x102ed30c0, 0x1035910f0}, {0x102ff7e88, 0x14000010020}, 0x0, 0x140018643e0, 0x0})", + "handler.Process", + }, + { + "method and package name", + "github.com/findy-network/findy-agent/agent/ssi.(*DIDAgent).AssertWallet(...)", + "ssi.(*DIDAgent).AssertWallet", + }, } for _, ttv := range tests { tt := ttv @@ -255,15 +285,52 @@ func TestCalcAnchor(t *testing.T) { anchor int } tests := []ttest{ - {"macOS from test using regexp", args{inputFromMac, StackInfo{"", "panic(", 1, PackageRegexp, nil}}, 12}, + { + "macOS from test using regexp", + args{ + inputFromMac, + StackInfo{"", "panic(", 1, PackageRegexp, nil}, + }, + 12, + }, {"short", args{input, StackInfo{"", "panic(", 0, nil, nil}}, 6}, - {"short error stack", args{inputByError, StackInfo{"", "panic(", 0, PackageRegexp, nil}}, 4}, - {"short and nolimit", args{input, StackInfo{"", "", 0, nil, nil}}, nilAnchor}, - {"short and only LVL is 2", args{input, StackInfo{"", "", 2, nil, nil}}, 2}, + { + "short error stack", + args{ + inputByError, + StackInfo{"", "panic(", 0, PackageRegexp, nil}, + }, + 4, + }, + { + "short and nolimit", + args{input, StackInfo{"", "", 0, nil, nil}}, + nilAnchor, + }, + { + "short and only LVL is 2", + args{input, StackInfo{"", "", 2, nil, nil}}, + 2, + }, {"medium", args{input1, StackInfo{"", "panic(", 0, nil, nil}}, 10}, - {"from test using panic", args{inputFromTest, StackInfo{"", "panic(", 0, nil, nil}}, 8}, - {"from test", args{inputFromTest, StackInfo{"", "panic(", 0, PackageRegexp, nil}}, 14}, - {"macOS from test using panic", args{inputFromMac, StackInfo{"", "panic(", 0, nil, nil}}, 12}, + { + "from test using panic", + args{inputFromTest, StackInfo{"", "panic(", 0, nil, nil}}, + 8, + }, + { + "from test", + args{ + inputFromTest, + StackInfo{"", "panic(", 0, PackageRegexp, nil}, + }, + 14, + }, + { + "macOS from test using panic", + args{inputFromMac, StackInfo{"", "panic(", 0, nil, nil}}, + 12, + }, } for _, ttv := range tests { tt := ttv @@ -288,15 +355,51 @@ func TestStackPrint_limit(t *testing.T) { output string } tests := []ttest{ - {"real test trace", args{inputFromTest, StackInfo{"", "", 8, nil, exludeRegexps}}, outputFromTest}, - {"only level 4", args{input1, StackInfo{"", "", 4, nil, nil}}, output1}, - {"short", args{input, StackInfo{"err2", "Returnw(", 0, nil, nil}}, output}, - {"medium", args{input1, StackInfo{"err2", "Returnw(", 0, nil, nil}}, output1}, - {"medium level 2", args{input1, StackInfo{"err2", "Returnw(", 2, nil, nil}}, output12}, - {"medium level 0", args{input1, StackInfo{"err2", "Returnw(", 0, nil, nil}}, output1}, - {"medium panic", args{input1, StackInfo{"", "panic(", 0, nil, nil}}, output1panic}, - {"long", args{input2, StackInfo{"err2", "Handle(", 0, nil, nil}}, output2}, - {"long lvl 2", args{input2, StackInfo{"err2", "Handle(", 3, nil, nil}}, output23}, + { + "real test trace", + args{inputFromTest, StackInfo{"", "", 8, nil, exludeRegexps}}, + outputFromTest, + }, + { + "only level 4", + args{input1, StackInfo{"", "", 4, nil, nil}}, + output1, + }, + { + "short", + args{input, StackInfo{"err2", "Returnw(", 0, nil, nil}}, + output, + }, + { + "medium", + args{input1, StackInfo{"err2", "Returnw(", 0, nil, nil}}, + output1, + }, + { + "medium level 2", + args{input1, StackInfo{"err2", "Returnw(", 2, nil, nil}}, + output12, + }, + { + "medium level 0", + args{input1, StackInfo{"err2", "Returnw(", 0, nil, nil}}, + output1, + }, + { + "medium panic", + args{input1, StackInfo{"", "panic(", 0, nil, nil}}, + output1panic, + }, + { + "long", + args{input2, StackInfo{"err2", "Handle(", 0, nil, nil}}, + output2, + }, + { + "long lvl 2", + args{input2, StackInfo{"err2", "Handle(", 3, nil, nil}}, + output23, + }, } for _, ttv := range tests { tt := ttv @@ -329,11 +432,41 @@ func TestFuncName(t *testing.T) { outFrame int } tests := []ttest{ - {"basic", args{input2, StackInfo{"", "Handle", 1, nil, nil}}, "err2.ReturnW", 214, 6}, - {"basic lvl 3", args{input2, StackInfo{"", "Handle", 3, nil, nil}}, "err2.ReturnW", 214, 6}, - {"basic lvl 2", args{input2, StackInfo{"lainio/err2", "Handle", 1, nil, nil}}, "err2.ReturnW", 214, 6}, - {"method", args{inputFromTest, StackInfo{"", "Handle", 1, nil, nil}}, "ssi.(*DIDAgent).AssertWallet", 146, 8}, - {"pipeline", args{inputPipelineStack, StackInfo{"", "Handle", -1, nil, nil}}, "CopyFile", 29, 9}, + { + "basic", + args{input2, StackInfo{"", "Handle", 1, nil, nil}}, + "err2.ReturnW", + 214, + 6, + }, + { + "basic lvl 3", + args{input2, StackInfo{"", "Handle", 3, nil, nil}}, + "err2.ReturnW", + 214, + 6, + }, + { + "basic lvl 2", + args{input2, StackInfo{"lainio/err2", "Handle", 1, nil, nil}}, + "err2.ReturnW", + 214, + 6, + }, + { + "method", + args{inputFromTest, StackInfo{"", "Handle", 1, nil, nil}}, + "ssi.(*DIDAgent).AssertWallet", + 146, + 8, + }, + { + "pipeline", + args{inputPipelineStack, StackInfo{"", "Handle", -1, nil, nil}}, + "CopyFile", + 29, + 9, + }, } for _, ttv := range tests { tt := ttv diff --git a/internal/handler/handlers_test.go b/internal/handler/handlers_test.go index c80cabc..cd76762 100644 --- a/internal/handler/handlers_test.go +++ b/internal/handler/handlers_test.go @@ -20,16 +20,66 @@ func TestHandlers(t *testing.T) { dis bool }{ {"one", args{f: []any{err2.Noop}}, err2.ErrNotFound, false}, - {"one disabled NOT real case", args{f: []any{nil}}, err2.ErrNotFound, true}, - {"two", args{f: []any{err2.Noop, err2.Noop}}, err2.ErrNotFound, false}, - {"three", args{f: []any{err2.Noop, err2.Noop, err2.Noop}}, err2.ErrNotFound, false}, - {"three last disabled", args{f: []any{err2.Noop, err2.Noop, nil}}, err2.ErrNotFound, true}, - {"three 2nd disabled", args{f: []any{err2.Noop, nil, err2.Noop}}, err2.ErrNotFound, true}, - {"three all disabled", args{f: []any{nil, nil, nil}}, err2.ErrNotFound, true}, - {"reset", args{f: []any{err2.Noop, err2.Noop, err2.Reset}}, nil, false}, - {"reset and disabled", args{f: []any{nil, err2.Noop, err2.Reset}}, nil, true}, - {"reset first", args{f: []any{err2.Reset, err2.Noop, err2.Noop}}, nil, false}, - {"reset second", args{f: []any{err2.Noop, err2.Reset, err2.Noop}}, nil, false}, + { + "one disabled NOT real case", + args{f: []any{nil}}, + err2.ErrNotFound, + true, + }, + { + "two", + args{f: []any{err2.Noop, err2.Noop}}, + err2.ErrNotFound, + false, + }, + { + "three", + args{f: []any{err2.Noop, err2.Noop, err2.Noop}}, + err2.ErrNotFound, + false, + }, + { + "three last disabled", + args{f: []any{err2.Noop, err2.Noop, nil}}, + err2.ErrNotFound, + true, + }, + { + "three 2nd disabled", + args{f: []any{err2.Noop, nil, err2.Noop}}, + err2.ErrNotFound, + true, + }, + { + "three all disabled", + args{f: []any{nil, nil, nil}}, + err2.ErrNotFound, + true, + }, + { + "reset", + args{f: []any{err2.Noop, err2.Noop, err2.Reset}}, + nil, + false, + }, + { + "reset and disabled", + args{f: []any{nil, err2.Noop, err2.Reset}}, + nil, + true, + }, + { + "reset first", + args{f: []any{err2.Reset, err2.Noop, err2.Noop}}, + nil, + false, + }, + { + "reset second", + args{f: []any{err2.Noop, err2.Reset, err2.Noop}}, + nil, + false, + }, {"set new first", args{f: []any{ func(error) error { return err2.ErrAlreadyExist }, err2.Noop}}, err2.ErrAlreadyExist, false}, {"set new second", args{f: []any{err2.Noop, diff --git a/internal/str/str_test.go b/internal/str/str_test.go index d274986..9bf8160 100644 --- a/internal/str/str_test.go +++ b/internal/str/str_test.go @@ -60,15 +60,39 @@ func TestDecamel(t *testing.T) { }{ {"simple", args{"CamelString"}, "camel string"}, {"underscore", args{"CamelString_error"}, "camel string error"}, - {"our contant", args{camelStr}, "benchmark recursion with old error if check and defer"}, + { + "our contant", + args{camelStr}, + "benchmark recursion with old error if check and defer", + }, {"number", args{"CamelString2Testing"}, "camel string2 testing"}, {"acronym", args{"ARMCamelString"}, "armcamel string"}, {"acronym at end", args{"archIsARM"}, "arch is arm"}, - {"simple method", args{"(*DIDAgent).AssertWallet"}, "didagent assert wallet"}, - {"package name and simple method", args{"ssi.(*DIDAgent).CreateWallet"}, "ssi: didagent create wallet"}, - {"simple method and anonym", args{"(*DIDAgent).AssertWallet.Func1"}, "didagent assert wallet: func1"}, - {"complex method and anonym", args{"(**DIDAgent).AssertWallet.Func1"}, "didagent assert wallet: func1"}, - {"unnatural method and anonym", args{"(**DIDAgent)...AssertWallet...Func1"}, "didagent assert wallet: func1"}, + { + "simple method", + args{"(*DIDAgent).AssertWallet"}, + "didagent assert wallet", + }, + { + "package name and simple method", + args{"ssi.(*DIDAgent).CreateWallet"}, + "ssi: didagent create wallet", + }, + { + "simple method and anonym", + args{"(*DIDAgent).AssertWallet.Func1"}, + "didagent assert wallet: func1", + }, + { + "complex method and anonym", + args{"(**DIDAgent).AssertWallet.Func1"}, + "didagent assert wallet: func1", + }, + { + "unnatural method and anonym", + args{"(**DIDAgent)...AssertWallet...Func1"}, + "didagent assert wallet: func1", + }, {"from spf13 cobra", args{"bot.glob..func5"}, "bot: glob: func5"}, } for _, ttv := range tests { diff --git a/internal/tracer/tracer.go b/internal/tracer/tracer.go index 1663c24..aa10296 100644 --- a/internal/tracer/tracer.go +++ b/internal/tracer/tracer.go @@ -33,7 +33,11 @@ func init() { Log.SetTracer(nil) flag.Var(&Log, "err2-log", "`stream` for logging: nil -> log pkg") - flag.Var(&Error, "err2-trace", "`stream` for error tracing: stderr, stdout") + flag.Var( + &Error, + "err2-trace", + "`stream` for error tracing: stderr, stdout", + ) flag.Var(&Panic, "err2-panic-trace", "`stream` for panic tracing") } diff --git a/internal/x/x_test.go b/internal/x/x_test.go index ff146ca..1140013 100644 --- a/internal/x/x_test.go +++ b/internal/x/x_test.go @@ -8,9 +8,42 @@ import ( ) var ( - original = []int{2, 16, 128, 1024, 8192, 65536, 524288, 4194304, 16777216, 134217728} - lengths = []int{2, 16, 128, 1024, 8192, 65536, 524288, 4194304, 16777216, 134217728} - reverseLengths = []int{134217728, 16777216, 4194304, 524288, 65536, 8192, 1024, 128, 16, 2} + original = []int{ + 2, + 16, + 128, + 1024, + 8192, + 65536, + 524288, + 4194304, + 16777216, + 134217728, + } + lengths = []int{ + 2, + 16, + 128, + 1024, + 8192, + 65536, + 524288, + 4194304, + 16777216, + 134217728, + } + reverseLengths = []int{ + 134217728, + 16777216, + 4194304, + 524288, + 65536, + 8192, + 1024, + 128, + 16, + 2, + } ) func TestSwap(t *testing.T) { diff --git a/samples/main-db-sample.go b/samples/main-db-sample.go index 9d663a7..1f9ea55 100644 --- a/samples/main-db-sample.go +++ b/samples/main-db-sample.go @@ -14,18 +14,29 @@ func (db *Database) MoneyTransfer(from, to *Account, amount int) (err error) { tx := try.To1(db.BeginTransaction()) defer err2.Handle(&err, func(err error) error { if errRoll := tx.Rollback(); errRoll != nil { - // with go 1.20: err = fmt.Errorf("%w: ROLLBACK ERROR: %w", err, errRoll) - err = fmt.Errorf("%v: ROLLBACK ERROR: %w", err, errRoll) + // with go 1.20< we can wrap two errors as below: + // err = fmt.Errorf("%w: ROLLBACK ERROR: %w", err, errRoll) + + // with go 1.18 (err2 minimum need) we cannot wrap two errors + // same time. + err = fmt.Errorf("%v\nROLLBACK ERROR: %w", err, errRoll) + + // NOTE: that this is a good sample how difficult error handling + // can be. Now we select to wrap rollback error and use original + // as a main error message, no wrapping for it. } return err }) try.To(from.ReserveBalance(tx, amount)) - defer err2.Handle(&err, func(err error) error { // optional, following sample's wording - err = fmt.Errorf("cannot %w", err) - return err - }) + defer err2.Handle( + &err, + func(err error) error { // optional, following sample's wording + err = fmt.Errorf("cannot %w", err) + return err + }, + ) try.To(from.Withdraw(tx, amount)) try.To(to.Deposit(tx, amount)) diff --git a/samples/main-nil.go b/samples/main-nil.go index c89a069..cc41b27 100644 --- a/samples/main-nil.go +++ b/samples/main-nil.go @@ -35,18 +35,26 @@ func doMainAll() { logger.Info("=== 1. preferred successful status output ===") doMain1() - logger.Info("=== 2. err2.Handle(NilThenerr, func(noerr)) and try.To successful status ===") + logger.Info( + "=== 2. err2.Handle(NilThenerr, func(noerr)) and try.To successful status ===", + ) doMain2() - logger.Info("=== 3. err2.Handle(NilThenerr, func(noerr)) and try.Out successful status ===") + logger.Info( + "=== 3. err2.Handle(NilThenerr, func(noerr)) and try.Out successful status ===", + ) doMain3() logger.Info("=== ERROR status versions ===") myErr = errAddNode logger.Info("=== 1. preferred successful status output ===") doMain1() - logger.Info("=== 2. err2.Handle(NilThenerr, func(noerr)) and try.To successful status ===") + logger.Info( + "=== 2. err2.Handle(NilThenerr, func(noerr)) and try.To successful status ===", + ) doMain2() - logger.Info("=== 3. err2.Handle(NilThenerr, func(noerr)) and try.Out successful status ===") + logger.Info( + "=== 3. err2.Handle(NilThenerr, func(noerr)) and try.Out successful status ===", + ) doMain3() } diff --git a/samples/main.go b/samples/main.go index afd933d..3b07a11 100644 --- a/samples/main.go +++ b/samples/main.go @@ -9,7 +9,11 @@ import ( ) var ( - mode = flag.String("mode", "play", "runs the wanted playground: db, play, nil") + mode = flag.String( + "mode", + "play", + "runs the wanted playground: db, play, nil", + ) isErr = flag.Bool("err", false, "tells if we want to have an error") ) diff --git a/try/copy_test.go b/try/copy_test.go index 6166aaf..7e50099 100644 --- a/try/copy_test.go +++ b/try/copy_test.go @@ -57,7 +57,11 @@ func Benchmark_CopyBufferOur(b *testing.B) { // myCopyBuffer is copy/paste from Go std lib to remove noice and measure only a // loop -func myCopyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) { +func myCopyBuffer( + dst io.Writer, + src io.Reader, + buf []byte, +) (written int64, err error) { for { nr, er := src.Read(buf) if nr > 0 { diff --git a/try/out.go b/try/out.go index 4b49921..d7677d3 100644 --- a/try/out.go +++ b/try/out.go @@ -278,7 +278,10 @@ func Out1[T any](v T, err error) *Result1[T] { // x, y := try.Out2(convTwoStr(s1, s2)).Logf("bad number").Catch(1, 2) // y := try.Out2(convTwoStr(s1, s2)).Handle().Val2 func Out2[T any, U any](v1 T, v2 U, err error) *Result2[T, U] { - return &Result2[T, U]{Val2: v2, Result1: Result1[T]{Val1: v1, Result: Result{Err: err}}} + return &Result2[T, U]{ + Val2: v2, + Result1: Result1[T]{Val1: v1, Result: Result{Err: err}}, + } } func wrapStr() string { From ed9b8970ce10a02ca4455f2a5bbd8581442012f5 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 1 Sep 2024 19:03:29 +0300 Subject: [PATCH 33/78] more output for golines fmt rules --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 72125f1..2afd9c7 100644 --- a/Makefile +++ b/Makefile @@ -126,9 +126,11 @@ vet: | test $(GO) vet $(PKGS) fmt: + @echo "Pretty formatting with golines" @golines -t 5 -w -m $(MAX_LINE) --ignore-generated . dry-fmt: + @echo "--dry-run: Pretty formatting with golines" @golines -t 5 --dry-run -m $(MAX_LINE) --ignore-generated . gofmt: From 5a1958b8390f816900b21192e762d0d738f76938 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 2 Sep 2024 16:51:07 +0300 Subject: [PATCH 34/78] 'assertion violation' -> assertion failure, msgs + new examples --- assert/assert.go | 12 ++++---- assert/assert_test.go | 71 +++++++++++++++++++++++++++++-------------- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index f3c0075..6c10db1 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -147,10 +147,10 @@ var ( ) const ( - assertionMsg = "assertion violation" - assertionEqualMsg = "assert equal" - assertionNotEqualMsg = "assert not equal" - assertionLenMsg = "assert len" + assertionMsg = "assertion failure" + assertionEqualMsg = "assertion failure: equal" + assertionNotEqualMsg = "assertion failure: not equal" + assertionLenMsg = "assertion failure: length" gotWantFmt = ": got '%v', want '%v'" gotWantLongerFmt = ": got '%v', should be longer than '%v'" @@ -270,7 +270,7 @@ func tester() (t testing.TB) { // Note that when [Plain] asserter is used ([SetDefault]), optional arguments // are used to override the auto-generated assert violation message. func NotImplemented(a ...any) { - current().reportAssertionFault(0, "not implemented", a) + current().reportAssertionFault(0, assertionMsg+": not implemented", a) } // ThatNot asserts that the term is NOT true. If is it panics with the given @@ -378,7 +378,7 @@ func SNil[S ~[]T, T any](s S, a ...any) { // are used to override the auto-generated assert violation message. func CNil[C ~chan T, T any](c C, a ...any) { if c != nil { - defMsg := assertionMsg + ": channel shouldn't be nil" + defMsg := assertionMsg + ": channel should be nil" current().reportAssertionFault(0, defMsg, a) } } diff --git a/assert/assert_test.go b/assert/assert_test.go index adeea70..8cf8307 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -18,7 +18,7 @@ func ExampleThat() { } err := sample() fmt.Printf("%v", err) - // Output: testing: run example: assert_test.go:16: ExampleThat.func1(): assertion violation: optional message + // Output: testing: run example: assert_test.go:16: ExampleThat.func1(): assertion failure: optional message } func ExampleNotNil() { @@ -31,7 +31,7 @@ func ExampleNotNil() { var b *byte err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:28: ExampleNotNil.func1(): assertion violation: pointer shouldn't be nil + // Output: sample: assert_test.go:28: ExampleNotNil.func1(): assertion failure: pointer shouldn't be nil } func ExampleMNotNil() { @@ -44,7 +44,7 @@ func ExampleMNotNil() { var b map[string]byte err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:41: ExampleMNotNil.func1(): assertion violation: map shouldn't be nil + // Output: sample: assert_test.go:41: ExampleMNotNil.func1(): assertion failure: map shouldn't be nil } func ExampleCNotNil() { @@ -57,7 +57,7 @@ func ExampleCNotNil() { var c chan byte err := sample(c) fmt.Printf("%v", err) - // Output: sample: assert_test.go:54: ExampleCNotNil.func1(): assertion violation: channel shouldn't be nil + // Output: sample: assert_test.go:54: ExampleCNotNil.func1(): assertion failure: channel shouldn't be nil } func ExampleSNotNil() { @@ -70,7 +70,7 @@ func ExampleSNotNil() { var b []byte err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:67: ExampleSNotNil.func1(): assertion violation: slice shouldn't be nil + // Output: sample: assert_test.go:67: ExampleSNotNil.func1(): assertion failure: slice shouldn't be nil } func ExampleEqual() { @@ -82,7 +82,7 @@ func ExampleEqual() { } err := sample([]byte{1, 2}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:80: ExampleEqual.func1(): assert equal: got '2', want '3' + // Output: sample: assert_test.go:80: ExampleEqual.func1(): assertion failure: equal: got '2', want '3' } func ExampleSLen() { @@ -94,7 +94,7 @@ func ExampleSLen() { } err := sample([]byte{1, 2}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:92: ExampleSLen.func1(): assert len: got '2', want '3' + // Output: sample: assert_test.go:92: ExampleSLen.func1(): assertion failure: length: got '2', want '3' } func ExampleSNotEmpty() { @@ -106,20 +106,20 @@ func ExampleSNotEmpty() { } err := sample([]byte{}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:104: ExampleSNotEmpty.func1(): assertion violation: slice should not be empty + // Output: sample: assert_test.go:104: ExampleSNotEmpty.func1(): assertion failure: slice should not be empty } func ExampleNotEmpty() { sample := func(b string) (err error) { defer err2.Handle(&err, "sample") - assert.Empty(b) - assert.NotEmpty(b) + assert.Empty(b) // OK + assert.NotEmpty(b) // not OK return err } err := sample("") fmt.Printf("%v", err) - // Output: sample: assert_test.go:117: ExampleNotEmpty.func1(): assertion violation: string shouldn't be empty + // Output: sample: assert_test.go:117: ExampleNotEmpty.func1(): assertion failure: string shouldn't be empty } func ExampleMKeyExists() { @@ -129,14 +129,14 @@ func ExampleMKeyExists() { m := map[string]string{ "1": "one", } - v := assert.MKeyExists(m, "1") - assert.Equal(v, "one") - _ = assert.MKeyExists(m, b) + v := assert.MKeyExists(m, "1") // OK, 1 --> one + assert.Equal(v, "one") // OK + _ = assert.MKeyExists(m, b) // fails with b = 2 return err } err := sample("2") fmt.Printf("%v", err) - // Output: sample: assert_test.go:134: ExampleMKeyExists.func1(): assertion violation: key '2' doesn't exist + // Output: sample: assert_test.go:134: ExampleMKeyExists.func1(): assertion failure: key '2' doesn't exist } func ExampleZero() { @@ -149,7 +149,7 @@ func ExampleZero() { var b int8 = 1 // we want sample to assert the violation. err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:146: ExampleZero.func1(): assertion violation: got '1', want (== '0') + // Output: sample: assert_test.go:146: ExampleZero.func1(): assertion failure: got '1', want (== '0') } func ExampleSLonger() { @@ -162,7 +162,7 @@ func ExampleSLonger() { } err := sample([]byte{01}) // len = 1 fmt.Printf("%v", err) - // Output: sample: assert_test.go:160: ExampleSLonger.func1(): assertion violation: got '1', should be longer than '1' + // Output: sample: assert_test.go:160: ExampleSLonger.func1(): assertion failure: got '1', should be longer than '1' } func ExampleMShorter() { @@ -175,7 +175,7 @@ func ExampleMShorter() { } err := sample(map[byte]byte{01: 01}) // len = 1 fmt.Printf("%v", err) - // Output: sample: assert_test.go:172: ExampleMShorter.func1(): assertion violation: got '1', should be shorter than '1' + // Output: sample: assert_test.go:172: ExampleMShorter.func1(): assertion failure: got '1', should be shorter than '1' } func ExampleSShorter() { @@ -188,7 +188,7 @@ func ExampleSShorter() { } err := sample([]byte{01}) // len = 1 fmt.Printf("%v", err) - // Output: sample: assert_test.go:186: ExampleSShorter.func1(): assertion violation: got '1', should be shorter than '0': optional message (test_str) + // Output: sample: assert_test.go:186: ExampleSShorter.func1(): assertion failure: got '1', should be shorter than '0': optional message (test_str) } func ExampleLess() { @@ -203,7 +203,7 @@ func ExampleLess() { var b int8 = 1 err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:200: ExampleLess.func1(): assertion violation: got '1', want >= '1' + // Output: sample: assert_test.go:200: ExampleLess.func1(): assertion failure: got '1', want >= '1' } func ExampleGreater() { @@ -218,7 +218,7 @@ func ExampleGreater() { var b int8 = 2 err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:215: ExampleGreater.func1(): assertion violation: got '2', want <= '2' + // Output: sample: assert_test.go:215: ExampleGreater.func1(): assertion failure: got '2', want <= '2' } func ExampleNotZero() { @@ -231,7 +231,34 @@ func ExampleNotZero() { var b int8 err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:228: ExampleNotZero.func1(): assertion violation: got '0', want (!= 0) + // Output: sample: assert_test.go:228: ExampleNotZero.func1(): assertion failure: got '0', want (!= 0) +} + +func ExampleMLen() { + sample := func(b map[int]byte) (err error) { + defer err2.Handle(&err, "sample") + + assert.MLen(b, 3) + return err + } + err := sample(map[int]byte{1: 1, 2: 2}) + fmt.Printf("%v", err) + // Output: sample: assert_test.go:241: ExampleMLen.func1(): assertion failure: length: got '2', want '3' +} + +func ExampleCLen() { + sample := func(b chan int) (err error) { + defer err2.Handle(&err, "sample") + + assert.CLen(b, 3) + return err + } + d := make(chan int, 2) + d <- int(1) + d <- int(1) + err := sample(d) + fmt.Printf("%v", err) + // Output: sample: assert_test.go:253: ExampleCLen.func1(): assertion failure: length: got '2', want '3' } func BenchmarkMKeyExists(b *testing.B) { From fc9a0cea5616152ff37c2bc41b22fd492eae71a2 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 7 Sep 2024 12:27:57 +0300 Subject: [PATCH 35/78] assert test --- samples/main.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/samples/main.go b/samples/main.go index 3b07a11..4e1a182 100644 --- a/samples/main.go +++ b/samples/main.go @@ -6,13 +6,14 @@ import ( "os" "github.com/lainio/err2" + "github.com/lainio/err2/assert" ) var ( mode = flag.String( "mode", "play", - "runs the wanted playground: db, play, nil", + "runs the wanted playground: db, play, nil, assert", ) isErr = flag.Bool("err", false, "tells if we want to have an error") ) @@ -40,7 +41,13 @@ func main() { doMain2() case "play": doPlayMain() + case "assert": + doAssertMain() default: err2.Throwf("unknown (%v) playground given", *mode) } } + +func doAssertMain() { + assert.That(false) +} From 8d5764087e96a23b401c5a73ea4d9f5ab4931452 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 7 Sep 2024 16:17:00 +0300 Subject: [PATCH 36/78] mark tester() calls --- assert/asserter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assert/asserter.go b/assert/asserter.go index 7edccda..cade5ae 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -95,7 +95,7 @@ func (asserter asserter) reportAssertionFault( func (asserter asserter) reportPanic(s string) { if asserter.isUnitTesting() && asserter.hasCallerInfo() { fmt.Fprintln(os.Stderr, officialTestOutputPrefix+s) - tester().FailNow() + tester().FailNow() // TODO: tester } else if asserter.isUnitTesting() { const framesToSkip = 4 // how many fn calls there is before FuncName call fatal(s, framesToSkip) @@ -119,7 +119,7 @@ func fatal(s string, framesToSkip int) { } // test output goes thru stderr, no need for t.Log(), test Fail needs it. fmt.Fprintln(os.Stderr, officialTestOutputPrefix+info) - tester().FailNow() + tester().FailNow() // TODO: tester } var longFmtStr = ` @@ -175,5 +175,5 @@ func (asserter asserter) hasFormattedCallerInfo() bool { // isUnitTesting is expensive because it calls tester(). think carefully where // to use it func (asserter asserter) isUnitTesting() bool { - return asserter&asserterUnitTesting != 0 && tester() != nil + return asserter&asserterUnitTesting != 0 && tester() != nil // TODO: tester } From 1a81899e440a56603d0cbadeefe7ddbe876cf327 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 7 Sep 2024 16:29:45 +0300 Subject: [PATCH 37/78] first bench tests for currentX() that uses TLS which is slow --- assert/assert.go | 55 +++++++++++++++++++++++++++++++++++++++++-- assert/assert_test.go | 26 +++++++++++++------- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 6c10db1..3ff32de 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -137,6 +137,8 @@ func init() { } type ( + mapAsserter = map[int]asserter + testersMap = map[int]testing.TB function = func() ) @@ -144,6 +146,8 @@ type ( var ( // testers must be set if assertion package is used for the unit testing. testers = x.NewRWMap[testersMap]() + + asserterMap = x.NewRWMap[mapAsserter]() ) const ( @@ -286,6 +290,38 @@ func ThatNot(term bool, a ...any) { } } +func ThatX(term bool, a ...any) { + if !term { + thatXDo(a) + } +} + +func ZeroX[T Number](val T, a ...any) { + if val != 0 { + doZeroX(val, a) + } +} + +func doZeroX[T Number](val T, a []any) { + defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (== '0')", val) + currentX().reportAssertionFault(1, defMsg, a) +} + +func thatXDo(a []any) { + defMsg := assertionMsg + currentX().reportAssertionFault(1, defMsg, a) +} + +func currentX() asserter { + // we need thread local storage, maybe we'll implement that to x.package? + // study `tester` and copy ideas from it. + return asserterMap.Get(goid()) +} + +func SetDefaultX(i defInd) { + asserterMap.Set(goid(), defAsserter[i]) +} + // That asserts that the term is true. If not it panics with the given // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. @@ -884,11 +920,26 @@ func NoError(err error, a ...any) { // are used to override the auto-generated assert violation message. func Error(err error, a ...any) { if err == nil { - defMsg := "Error:" + assertionMsg + ": missing error" - current().reportAssertionFault(0, defMsg, a) + doErrorX(a) + } +} + +func ErrorX(err error, a ...any) { + if err == nil { + doError(a) } } +func doErrorX(a []any) { + defMsg := "Error:" + assertionMsg + ": missing error" + currentX().reportAssertionFault(0, defMsg, a) +} + +func doError(a []any) { + defMsg := "Error:" + assertionMsg + ": missing error" + current().reportAssertionFault(0, defMsg, a) +} + // Greater asserts that the value is greater than want. If it is not it panics // and builds a violation message. Thanks to inlining, the performance penalty // is equal to a single 'if-statement' that is almost nothing. diff --git a/assert/assert_test.go b/assert/assert_test.go index 8cf8307..d61bea5 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -401,27 +401,37 @@ func BenchmarkThat(b *testing.B) { } } -func BenchmarkGreater(b *testing.B) { +func BenchmarkZeroX(b *testing.B) { + assert.SetDefaultX(assert.Production) + const zero = 0 for n := 0; n < b.N; n++ { - assert.Greater(1, 0) + assert.ZeroX(zero) } } -func BenchmarkLess(b *testing.B) { +func BenchmarkZero(b *testing.B) { + const zero = 0 for n := 0; n < b.N; n++ { - assert.Less(0, 1) + assert.Zero(zero) } } -func BenchmarkZero(b *testing.B) { +func BenchmarkGreater(b *testing.B) { for n := 0; n < b.N; n++ { - assert.Zero(0) + assert.Greater(1, 0) + } +} + +func BenchmarkLess(b *testing.B) { + for n := 0; n < b.N; n++ { + assert.Less(0, 1) } } -func BenchmarkNotZero(b *testing.B) { +func BenchmarkErrorX(b *testing.B) { + assert.SetDefaultX(assert.Production) for n := 0; n < b.N; n++ { - assert.NotZero(n + 1) + assert.ErrorX(err2.ErrNotAccess) } } From 0def096947a706f06c52321bf416f76b1e6c98a0 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 7 Sep 2024 17:47:55 +0300 Subject: [PATCH 38/78] bench rules for assert optimization --- Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2afd9c7..27928ab 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,12 @@ bench_M: bench_C: $(GO) test $(TEST_ARGS) -bench='BenchmarkC.*' $(PKG_ASSERT) +bench_nil: + $(GO) test $(TEST_ARGS) -bench='Benchmark.*Nil' $(PKG_ASSERT) + +bench_empty: + $(GO) test $(TEST_ARGS) -bench='Benchmark.*Empty' $(PKG_ASSERT) + bench_zero: $(GO) test $(TEST_ARGS) -bench='BenchmarkZero.*' $(PKG_ASSERT) @@ -96,7 +102,7 @@ bench_go: $(GO) test $(TEST_ARGS) -bench='BenchmarkTry_StringGenerics' $(PKG_ERR2) bench_that: - $(GO) test $(TEST_ARGS) -bench='BenchmarkThat.*' $(PKG_ASSERT) + $(GO) test $(TEST_ARGS) -bench='Benchmark.*That.*' $(PKG_ASSERT) bench_copy: $(GO) test $(TEST_ARGS) -bench='Benchmark_CopyBuffer' $(PKG_TRY) From e670166da2de9c1c70cffc8b1a5e4fad1e227fc8 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 7 Sep 2024 17:49:16 +0300 Subject: [PATCH 39/78] TLS based optimizations work, next test the switching --- assert/assert.go | 121 +++++++++++++++++++++++++----------------- assert/assert_test.go | 20 ++++--- 2 files changed, 87 insertions(+), 54 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 3ff32de..ace7318 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -151,6 +151,8 @@ var ( ) const ( + assertionNot = "not" + assertionMsg = "assertion failure" assertionEqualMsg = "assertion failure: equal" assertionNotEqualMsg = "assertion failure: not equal" @@ -285,14 +287,7 @@ func NotImplemented(a ...any) { // are used to override the auto-generated assert violation message. func ThatNot(term bool, a ...any) { if term { - defMsg := assertionMsg - current().reportAssertionFault(0, defMsg, a) - } -} - -func ThatX(term bool, a ...any) { - if !term { - thatXDo(a) + doThat(a) } } @@ -307,15 +302,20 @@ func doZeroX[T Number](val T, a []any) { currentX().reportAssertionFault(1, defMsg, a) } -func thatXDo(a []any) { - defMsg := assertionMsg - currentX().reportAssertionFault(1, defMsg, a) -} - -func currentX() asserter { +func currentX() (curAsserter asserter) { // we need thread local storage, maybe we'll implement that to x.package? // study `tester` and copy ideas from it. - return asserterMap.Get(goid()) + tlsID := goid() + asserterMap.Rx(func(m map[int]asserter) { + aster, found := m[tlsID] + if found { + curAsserter = aster + } else { + // use pkg lvl asserter if asserter is not set + curAsserter = defAsserter[def] + } + }) + return curAsserter } func SetDefaultX(i defInd) { @@ -330,11 +330,15 @@ func SetDefaultX(i defInd) { // are used to override the auto-generated assert violation message. func That(term bool, a ...any) { if !term { - defMsg := assertionMsg - current().reportAssertionFault(0, defMsg, a) + doThat(a) } } +func doThat(a []any) { + defMsg := assertionMsg + current().reportAssertionFault(1, defMsg, a) +} + // NotNil asserts that the pointer IS NOT nil. If it is it panics/errors (default // Asserter) the auto-generated (args appended) message. // @@ -342,8 +346,7 @@ func That(term bool, a ...any) { // are used to override the auto-generated assert violation message. func NotNil[P ~*T, T any](p P, a ...any) { if p == nil { - defMsg := assertionMsg + ": pointer shouldn't be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("not", "pointer", "nil", a) } } @@ -354,8 +357,7 @@ func NotNil[P ~*T, T any](p P, a ...any) { // are used to override the auto-generated assert violation message. func Nil[T any](p *T, a ...any) { if p != nil { - defMsg := assertionMsg + ": pointer should be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("", "pointer", "nil", a) } } @@ -372,8 +374,7 @@ func Nil[T any](p *T, a ...any) { // [the interface type]: https://go.dev/doc/faq#nil_error func INil(i any, a ...any) { if i != nil { - defMsg := assertionMsg + ": interface should be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("", "interface", "nil", a) } } @@ -390,8 +391,7 @@ func INil(i any, a ...any) { // [the interface type]: https://go.dev/doc/faq#nil_error func INotNil(i any, a ...any) { if i == nil { - defMsg := assertionMsg + ": interface shouldn't be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("not", "interface", "nil", a) } } @@ -402,8 +402,7 @@ func INotNil(i any, a ...any) { // are used to override the auto-generated assert violation message. func SNil[S ~[]T, T any](s S, a ...any) { if s != nil { - defMsg := assertionMsg + ": slice should be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("", "slice", "nil", a) } } @@ -414,8 +413,7 @@ func SNil[S ~[]T, T any](s S, a ...any) { // are used to override the auto-generated assert violation message. func CNil[C ~chan T, T any](c C, a ...any) { if c != nil { - defMsg := assertionMsg + ": channel should be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("", "channel", "nil", a) } } @@ -426,8 +424,7 @@ func CNil[C ~chan T, T any](c C, a ...any) { // are used to override the auto-generated assert violation message. func MNil[M ~map[T]U, T comparable, U any](m M, a ...any) { if m != nil { - defMsg := assertionMsg + ": map should be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("", "map", "nil", a) } } @@ -438,8 +435,7 @@ func MNil[M ~map[T]U, T comparable, U any](m M, a ...any) { // are used to override the auto-generated assert violation message. func SNotNil[S ~[]T, T any](s S, a ...any) { if s == nil { - defMsg := assertionMsg + ": slice shouldn't be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("not", "slice", "nil", a) } } @@ -450,8 +446,7 @@ func SNotNil[S ~[]T, T any](s S, a ...any) { // are used to override the auto-generated assert violation message. func CNotNil[C ~chan T, T any](c C, a ...any) { if c == nil { - defMsg := assertionMsg + ": channel shouldn't be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("not", "channel", "nil", a) } } @@ -462,8 +457,7 @@ func CNotNil[C ~chan T, T any](c C, a ...any) { // are used to override the auto-generated assert violation message. func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { if m == nil { - defMsg := assertionMsg + ": map shouldn't be nil" - current().reportAssertionFault(0, defMsg, a) + doNamed("not", "map", "nil", a) } } @@ -799,8 +793,7 @@ func doMKeyExists(key any, a []any) { // are used to override the auto-generated assert violation message. func NotEmpty(obj string, a ...any) { if obj == "" { - defMsg := assertionMsg + ": string shouldn't be empty" - current().reportAssertionFault(0, defMsg, a) + doEmpty("string", "not ", a) } } @@ -812,11 +805,15 @@ func NotEmpty(obj string, a ...any) { // are used to override the auto-generated assert violation message. func Empty(obj string, a ...any) { if obj != "" { - defMsg := assertionMsg + ": string should be empty" - current().reportAssertionFault(0, defMsg, a) + doEmpty("string", "", a) } } +func doEmpty(tname, notStr string, a []any) { + defMsg := fmt.Sprintf(assertionMsg+": %s should %sbe empty", tname, notStr) + current().reportAssertionFault(1, defMsg, a) +} + // SEmpty asserts that the slice is empty. If it is NOT, it panics/errors // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. @@ -830,7 +827,7 @@ func SEmpty[S ~[]T, T any](obj S, a ...any) { l := len(obj) if l != 0 { - doEmptyNamed("", "slice", a) + doNamed("", "slice", "empty", a) } } @@ -847,7 +844,7 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { l := len(obj) if l == 0 { - doEmptyNamed("not", "slice", a) + doNamed("not", "slice", "empty", a) } } @@ -866,7 +863,7 @@ func MEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { l := len(obj) if l != 0 { - doEmptyNamed("", "map", a) + doNamed("", "map", "empty", a) } } @@ -885,16 +882,22 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { l := len(obj) if l == 0 { - doEmptyNamed("not", "map", a) + doNamed("not", "map", "empty", a) } } func doEmptyNamed(not, name string, a []any) { - not = x.Whom(not == "not", " not ", "") + not = x.Whom(not == assertionNot, " not ", "") defMsg := assertionMsg + ": " + name + " should" + not + "be empty" current().reportAssertionFault(1, defMsg, a) } +func doNamed(not, tname, got string, a []any) { + not = x.Whom(not == assertionNot, " not ", "") + defMsg := assertionMsg + ": " + tname + " should" + not + "be " + got + current().reportAssertionFault(1, defMsg, a) +} + // NoError asserts that the error is nil. If is not it panics with the given // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. @@ -1014,8 +1017,20 @@ func doNotZero[T Number](val T, a []any) { // Note, this indexing stuff is done because of race detection to work on client // packages. And, yes, we have tested it. This is fastest way to make it without // locks HERE. Only the setting the index is secured with the mutex. -func current() asserter { - return defAsserter[def] +func current() (curAsserter asserter) { + // we need thread local storage, maybe we'll implement that to x.package? + // study `tester` and copy ideas from it. + tlsID := goid() + asserterMap.Rx(func(m map[int]asserter) { + aster, found := m[tlsID] + if found { + curAsserter = aster + } else { + // use pkg lvl asserter if asserter is not set + curAsserter = defAsserter[def] + } + }) + return curAsserter } // SetDefault sets the current default asserter for assert pkg. It also returns @@ -1053,6 +1068,16 @@ func SetDefault(i defInd) (old defInd) { return } +// SetTLS set asserter index for the current thread (Tread Local Storage). That +// allows us to have multiple different asserter in use in the same app running. +// Let's say that in some function and its sub-functions want to return plain +// error messages instead of the panic asserts, they can use following: +// +// assert.SetTLS(assert.Plain) +func SetTLS(i defInd) { + asserterMap.Set(goid(), defAsserter[i]) +} + // mapDefInd runtime asserters, that's why test asserts are removed for now. var mapDefInd = map[string]defInd{ "Plain": Plain, diff --git a/assert/assert_test.go b/assert/assert_test.go index d61bea5..e4e5d37 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -31,7 +31,7 @@ func ExampleNotNil() { var b *byte err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:28: ExampleNotNil.func1(): assertion failure: pointer shouldn't be nil + // Output: sample: assert_test.go:28: ExampleNotNil.func1(): assertion failure: pointer should not be nil } func ExampleMNotNil() { @@ -44,7 +44,7 @@ func ExampleMNotNil() { var b map[string]byte err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:41: ExampleMNotNil.func1(): assertion failure: map shouldn't be nil + // Output: sample: assert_test.go:41: ExampleMNotNil.func1(): assertion failure: map should not be nil } func ExampleCNotNil() { @@ -57,7 +57,7 @@ func ExampleCNotNil() { var c chan byte err := sample(c) fmt.Printf("%v", err) - // Output: sample: assert_test.go:54: ExampleCNotNil.func1(): assertion failure: channel shouldn't be nil + // Output: sample: assert_test.go:54: ExampleCNotNil.func1(): assertion failure: channel should not be nil } func ExampleSNotNil() { @@ -70,7 +70,7 @@ func ExampleSNotNil() { var b []byte err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:67: ExampleSNotNil.func1(): assertion failure: slice shouldn't be nil + // Output: sample: assert_test.go:67: ExampleSNotNil.func1(): assertion failure: slice should not be nil } func ExampleEqual() { @@ -119,7 +119,7 @@ func ExampleNotEmpty() { } err := sample("") fmt.Printf("%v", err) - // Output: sample: assert_test.go:117: ExampleNotEmpty.func1(): assertion failure: string shouldn't be empty + // Output: sample: assert_test.go:117: ExampleNotEmpty.func1(): assertion failure: string should not be empty } func ExampleMKeyExists() { @@ -401,8 +401,16 @@ func BenchmarkThat(b *testing.B) { } } +func BenchmarkThatX(b *testing.B) { + assert.SetTLS(assert.Production) + const four = 4 + for n := 0; n < b.N; n++ { + assert.That(four == 2+2) + } +} + func BenchmarkZeroX(b *testing.B) { - assert.SetDefaultX(assert.Production) + assert.SetTLS(assert.Production) const zero = 0 for n := 0; n < b.N; n++ { assert.ZeroX(zero) From 48750a0dc3a162434742d4376055cadc5f125672 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 8 Sep 2024 13:07:34 +0300 Subject: [PATCH 40/78] rm X() functions & cleanup + MKey OK idiom bench for ref --- assert/assert.go | 76 ++++++++++--------------------------------- assert/assert_test.go | 35 +++++++------------- 2 files changed, 30 insertions(+), 81 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index ace7318..23aed02 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -291,37 +291,6 @@ func ThatNot(term bool, a ...any) { } } -func ZeroX[T Number](val T, a ...any) { - if val != 0 { - doZeroX(val, a) - } -} - -func doZeroX[T Number](val T, a []any) { - defMsg := fmt.Sprintf(assertionMsg+": got '%v', want (== '0')", val) - currentX().reportAssertionFault(1, defMsg, a) -} - -func currentX() (curAsserter asserter) { - // we need thread local storage, maybe we'll implement that to x.package? - // study `tester` and copy ideas from it. - tlsID := goid() - asserterMap.Rx(func(m map[int]asserter) { - aster, found := m[tlsID] - if found { - curAsserter = aster - } else { - // use pkg lvl asserter if asserter is not set - curAsserter = defAsserter[def] - } - }) - return curAsserter -} - -func SetDefaultX(i defInd) { - asserterMap.Set(goid(), defAsserter[i]) -} - // That asserts that the term is true. If not it panics with the given // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. @@ -886,12 +855,6 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { } } -func doEmptyNamed(not, name string, a []any) { - not = x.Whom(not == assertionNot, " not ", "") - defMsg := assertionMsg + ": " + name + " should" + not + "be empty" - current().reportAssertionFault(1, defMsg, a) -} - func doNamed(not, tname, got string, a []any) { not = x.Whom(not == assertionNot, " not ", "") defMsg := assertionMsg + ": " + tname + " should" + not + "be " + got @@ -922,25 +885,14 @@ func NoError(err error, a ...any) { // Note that when [Plain] asserter is used ([SetDefault]), optional arguments // are used to override the auto-generated assert violation message. func Error(err error, a ...any) { - if err == nil { - doErrorX(a) - } -} - -func ErrorX(err error, a ...any) { if err == nil { doError(a) } } -func doErrorX(a []any) { - defMsg := "Error:" + assertionMsg + ": missing error" - currentX().reportAssertionFault(0, defMsg, a) -} - func doError(a []any) { defMsg := "Error:" + assertionMsg + ": missing error" - current().reportAssertionFault(0, defMsg, a) + current().reportAssertionFault(1, defMsg, a) } // Greater asserts that the value is greater than want. If it is not it panics @@ -1011,12 +963,14 @@ func doNotZero[T Number](val T, a []any) { current().reportAssertionFault(1, defMsg, a) } -// current returns a current default asserter used for package-level -// functions like assert.That(). +// current returns a current default asserter used for assert functions like +// assert.That() in this gorounine. // -// Note, this indexing stuff is done because of race detection to work on client +// NOTE this indexing stuff is done because of race detection to work on client // packages. And, yes, we have tested it. This is fastest way to make it without // locks HERE. Only the setting the index is secured with the mutex. +// +// NOTE that since our TLS [asserterMap] we still continue to use indexing. func current() (curAsserter asserter) { // we need thread local storage, maybe we'll implement that to x.package? // study `tester` and copy ideas from it. @@ -1068,14 +1022,20 @@ func SetDefault(i defInd) (old defInd) { return } -// SetTLS set asserter index for the current thread (Tread Local Storage). That -// allows us to have multiple different asserter in use in the same app running. -// Let's say that in some function and its sub-functions want to return plain -// error messages instead of the panic asserts, they can use following: +// SetAsserter set asserter index for the current thread (Tread Local +// Storage). That allows us to have multiple different asserter in use in the +// same app running. Let's say that in some function and its sub-functions want +// to return plain error messages instead of the panic asserts, they can use +// following: // -// assert.SetTLS(assert.Plain) -func SetTLS(i defInd) { +// assert.SetAsserter(assert.Plain) +func SetAsserter(i defInd) func() { asserterMap.Set(goid(), defAsserter[i]) + return popCurrentAsserter +} + +func popCurrentAsserter() { + asserterMap.Del(goid()) } // mapDefInd runtime asserters, that's why test asserts are removed for now. diff --git a/assert/assert_test.go b/assert/assert_test.go index e4e5d37..ccb089c 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -268,6 +268,18 @@ func BenchmarkMKeyExists(b *testing.B) { } } +func BenchmarkMKeyExistsOKIdiom(b *testing.B) { + bs := map[int]int{0: 0, 1: 1} + found := false + for n := 0; n < b.N; n++ { + _, ok := bs[1] + if ok { + found = ok + } + } + _ = found +} + func BenchmarkMNotEmpty(b *testing.B) { bs := map[int]int{0: 0, 1: 1} for n := 0; n < b.N; n++ { @@ -401,22 +413,6 @@ func BenchmarkThat(b *testing.B) { } } -func BenchmarkThatX(b *testing.B) { - assert.SetTLS(assert.Production) - const four = 4 - for n := 0; n < b.N; n++ { - assert.That(four == 2+2) - } -} - -func BenchmarkZeroX(b *testing.B) { - assert.SetTLS(assert.Production) - const zero = 0 - for n := 0; n < b.N; n++ { - assert.ZeroX(zero) - } -} - func BenchmarkZero(b *testing.B) { const zero = 0 for n := 0; n < b.N; n++ { @@ -436,13 +432,6 @@ func BenchmarkLess(b *testing.B) { } } -func BenchmarkErrorX(b *testing.B) { - assert.SetDefaultX(assert.Production) - for n := 0; n < b.N; n++ { - assert.ErrorX(err2.ErrNotAccess) - } -} - func BenchmarkError(b *testing.B) { for n := 0; n < b.N; n++ { assert.Error(err2.ErrNotAccess) From b8cabcededf181e7c5e22328c4d7afbd5a3235a5 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 8 Sep 2024 13:19:41 +0300 Subject: [PATCH 41/78] rm TODOs --- assert/asserter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assert/asserter.go b/assert/asserter.go index cade5ae..7edccda 100644 --- a/assert/asserter.go +++ b/assert/asserter.go @@ -95,7 +95,7 @@ func (asserter asserter) reportAssertionFault( func (asserter asserter) reportPanic(s string) { if asserter.isUnitTesting() && asserter.hasCallerInfo() { fmt.Fprintln(os.Stderr, officialTestOutputPrefix+s) - tester().FailNow() // TODO: tester + tester().FailNow() } else if asserter.isUnitTesting() { const framesToSkip = 4 // how many fn calls there is before FuncName call fatal(s, framesToSkip) @@ -119,7 +119,7 @@ func fatal(s string, framesToSkip int) { } // test output goes thru stderr, no need for t.Log(), test Fail needs it. fmt.Fprintln(os.Stderr, officialTestOutputPrefix+info) - tester().FailNow() // TODO: tester + tester().FailNow() } var longFmtStr = ` @@ -175,5 +175,5 @@ func (asserter asserter) hasFormattedCallerInfo() bool { // isUnitTesting is expensive because it calls tester(). think carefully where // to use it func (asserter asserter) isUnitTesting() bool { - return asserter&asserterUnitTesting != 0 && tester() != nil // TODO: tester + return asserter&asserterUnitTesting != 0 && tester() != nil } From bffe12b30d6e4275f7231a971b844f2324690d4a Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 8 Sep 2024 13:20:16 +0300 Subject: [PATCH 42/78] gofmt --- assert/assert_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assert/assert_test.go b/assert/assert_test.go index ccb089c..faf37fe 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -274,7 +274,7 @@ func BenchmarkMKeyExistsOKIdiom(b *testing.B) { for n := 0; n < b.N; n++ { _, ok := bs[1] if ok { - found = ok + found = ok } } _ = found From a9dc3946dad001454715ac61523566c6614dd086 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 8 Sep 2024 13:21:12 +0300 Subject: [PATCH 43/78] refactor doEmpty -> doNamed --- assert/assert.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 23aed02..3904f06 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -762,7 +762,7 @@ func doMKeyExists(key any, a []any) { // are used to override the auto-generated assert violation message. func NotEmpty(obj string, a ...any) { if obj == "" { - doEmpty("string", "not ", a) + doNamed("not", "string", "empty", a) } } @@ -774,15 +774,10 @@ func NotEmpty(obj string, a ...any) { // are used to override the auto-generated assert violation message. func Empty(obj string, a ...any) { if obj != "" { - doEmpty("string", "", a) + doNamed("", "string", "empty", a) } } -func doEmpty(tname, notStr string, a []any) { - defMsg := fmt.Sprintf(assertionMsg+": %s should %sbe empty", tname, notStr) - current().reportAssertionFault(1, defMsg, a) -} - // SEmpty asserts that the slice is empty. If it is NOT, it panics/errors // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. From 73eb244adce26d2bb85db0640b791a55cc67a1ad Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 8 Sep 2024 16:06:30 +0300 Subject: [PATCH 44/78] SetAsserter works only outside unit tests, they MUST use pkg asserter --- assert/assert.go | 13 +++++++++---- assert/assert_test.go | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 3904f06..e798a34 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -967,8 +967,6 @@ func doNotZero[T Number](val T, a []any) { // // NOTE that since our TLS [asserterMap] we still continue to use indexing. func current() (curAsserter asserter) { - // we need thread local storage, maybe we'll implement that to x.package? - // study `tester` and copy ideas from it. tlsID := goid() asserterMap.Rx(func(m map[int]asserter) { aster, found := m[tlsID] @@ -1023,9 +1021,16 @@ func SetDefault(i defInd) (old defInd) { // to return plain error messages instead of the panic asserts, they can use // following: // -// assert.SetAsserter(assert.Plain) +// defer assert.SetAsserter(assert.Plain)() func SetAsserter(i defInd) func() { - asserterMap.Set(goid(), defAsserter[i]) + // get pkg lvl asserter + curAsserter := defAsserter[def] + // .. to check if we are doing unit tests + if !curAsserter.isUnitTesting() { + // .. allow TLS specific asserter. NOTE see current() + curGoRID := goid() + asserterMap.Set(curGoRID, defAsserter[i]) + } return popCurrentAsserter } diff --git a/assert/assert_test.go b/assert/assert_test.go index faf37fe..48d11d8 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -261,6 +261,22 @@ func ExampleCLen() { // Output: sample: assert_test.go:253: ExampleCLen.func1(): assertion failure: length: got '2', want '3' } +func ExampleThatNot() { + sample := func() (err error) { + defer err2.Handle(&err) + + assert.ThatNot(true, "overrides if Plain asserter") + return err + } + + // set asserter for this thread/goroutine only, we want plain errors + defer assert.SetAsserter(assert.Plain)() + + err := sample() + fmt.Printf("%v", err) + // Output: testing: run example: overrides if Plain asserter +} + func BenchmarkMKeyExists(b *testing.B) { bs := map[int]int{0: 0, 1: 1} for n := 0; n < b.N; n++ { From d9d2b45637ccee523c780985b582f01f2889eca4 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 30 Sep 2024 17:52:25 +0300 Subject: [PATCH 45/78] makefile rule for command-line coverage studies --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 27928ab..b96496c 100644 --- a/Makefile +++ b/Makefile @@ -157,6 +157,12 @@ test_cov: test_cov_out go tool cover -html=coverage.txt -o=coverage.html firefox ./coverage.html 1>&- 2>&- & +test_cov_pc_assert: + go tool cover -func=coverage.txt | ag assert + +test_cov_pc: + go tool cover -func=coverage.txt + lint: @golangci-lint run From 2edd1424063a26f6fb2a80fa2ca8e702a4040e35 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 1 Oct 2024 15:21:35 +0300 Subject: [PATCH 46/78] update samples/README.md --- samples/README.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/samples/README.md b/samples/README.md index b239a58..9241d5f 100644 --- a/samples/README.md +++ b/samples/README.md @@ -1,23 +1,34 @@ # Samples Please play with the samples by editing them and running the main files: -- `main.go` just a starter for different playgrounds -- `main-play.go` general playground based on CopyFile and recursion -- `main-db-sample.go` simulates DB transaction and money transfer +- `main.go` just a starter for different playgrounds (includes `asserter` tester) +- `main-play.go` general playground based on `CopyFile` and recursion +- `main-db-sample.go` simulates DB transaction with money transfer - `main-nil.go` samples and tests for logger and using `err2.Handle` for success Run a default playground `play` mode: ```go -go run ./... +go run . ``` -Or run the DB based version to maybe better understand how powerful the -automatic error string building is: +> [!TIP] +> Set a proper alias to play with samples: +> ```sh +> alias sa='go run .' +> ``` + +Run the DB based version to maybe better understand how powerful the automatic +error string building is: + ```go -go run ./... -mode db +sa -mode db +# or +go run . -mode db ``` You can print usage: ```go -go run ./... -h +sa -h +# or +go run . -h ``` From d47a5c81e135748a727eae6a7287ed36aca0117f Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 5 Oct 2024 12:03:51 +0300 Subject: [PATCH 47/78] tools for cmd line coverage work --- Makefile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b96496c..4b49e5d 100644 --- a/Makefile +++ b/Makefile @@ -160,11 +160,14 @@ test_cov: test_cov_out test_cov_pc_assert: go tool cover -func=coverage.txt | ag assert +test_cov_zero: + go tool cover -func=coverage.txt | ag '\:\s*[A-Z]+.*\s+0\.0%' + +test_cov_assert_zero: + go tool cover -func=coverage.txt | ag 'assert\/.*\:\s*[A-Z]+.*\s+0\.0%' + test_cov_pc: go tool cover -func=coverage.txt lint: @golangci-lint run - -.PHONY: check - From 08168e458c6622885280581a3387b66789621668 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 6 Oct 2024 12:02:00 +0300 Subject: [PATCH 48/78] zero cov rules refresh automatically --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4b49e5d..496e8b7 100644 --- a/Makefile +++ b/Makefile @@ -160,10 +160,10 @@ test_cov: test_cov_out test_cov_pc_assert: go tool cover -func=coverage.txt | ag assert -test_cov_zero: +test_cov_zero: test_cov_out go tool cover -func=coverage.txt | ag '\:\s*[A-Z]+.*\s+0\.0%' -test_cov_assert_zero: +test_cov_assert_zero: test_cov_out go tool cover -func=coverage.txt | ag 'assert\/.*\:\s*[A-Z]+.*\s+0\.0%' test_cov_pc: From 5cc1710a632ad6d1f5086cca45cb98829c856867 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 6 Oct 2024 12:13:03 +0300 Subject: [PATCH 49/78] docs & refactoring --- assert/assert.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index e798a34..0c656b6 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -851,7 +851,7 @@ func MNotEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { } func doNamed(not, tname, got string, a []any) { - not = x.Whom(not == assertionNot, " not ", "") + not = x.Whom(not == assertionNot, " not ", " ") defMsg := assertionMsg + ": " + tname + " should" + not + "be " + got current().reportAssertionFault(1, defMsg, a) } @@ -965,15 +965,15 @@ func doNotZero[T Number](val T, a []any) { // packages. And, yes, we have tested it. This is fastest way to make it without // locks HERE. Only the setting the index is secured with the mutex. // -// NOTE that since our TLS [asserterMap] we still continue to use indexing. +// NOTE that since our GLS [asserterMap] we still continue to use indexing. func current() (curAsserter asserter) { - tlsID := goid() + glsID := goid() asserterMap.Rx(func(m map[int]asserter) { - aster, found := m[tlsID] + aster, found := m[glsID] if found { curAsserter = aster } else { - // use pkg lvl asserter if asserter is not set + // use pkg lvl asserter if asserter is not set for gorounine. curAsserter = defAsserter[def] } }) @@ -1015,11 +1015,12 @@ func SetDefault(i defInd) (old defInd) { return } -// SetAsserter set asserter index for the current thread (Tread Local -// Storage). That allows us to have multiple different asserter in use in the -// same app running. Let's say that in some function and its sub-functions want -// to return plain error messages instead of the panic asserts, they can use -// following: +// SetAsserter set asserter for the current GLS (Gorounine Local Storage). That +// allows us to have multiple different [asserter] in use in the same process. +// +// Let's say that in some function you want to return plain error messages +// instead of the panic asserts, you can use following in the top-level +// function: // // defer assert.SetAsserter(assert.Plain)() func SetAsserter(i defInd) func() { From ef35568c4b990047d5b78d0b2b9a459f1a1c1278 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 6 Oct 2024 12:37:54 +0300 Subject: [PATCH 50/78] add calls to assert pkg to cover almost all --- assert/assert_test.go | 141 +++++++++++++++++++++++++++++++++--------- 1 file changed, 111 insertions(+), 30 deletions(-) diff --git a/assert/assert_test.go b/assert/assert_test.go index 48d11d8..9a8d1b1 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -25,64 +25,71 @@ func ExampleNotNil() { sample := func(b *byte) (err error) { defer err2.Handle(&err, "sample") - assert.NotNil(b) + assert.Nil(b) // OK + assert.NotNil(b) // Not OK return err } var b *byte err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:28: ExampleNotNil.func1(): assertion failure: pointer should not be nil + // Output: sample: assert_test.go:29: ExampleNotNil.func1(): assertion failure: pointer should not be nil } func ExampleMNotNil() { sample := func(b map[string]byte) (err error) { defer err2.Handle(&err, "sample") - assert.MNotNil(b) + assert.MEmpty(b) // OK + assert.MNil(b) // OK + assert.MNotNil(b) // Not OK return err } var b map[string]byte err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:41: ExampleMNotNil.func1(): assertion failure: map should not be nil + // Output: sample: assert_test.go:44: ExampleMNotNil.func1(): assertion failure: map should not be nil } func ExampleCNotNil() { sample := func(c chan byte) (err error) { defer err2.Handle(&err, "sample") - assert.CNotNil(c) + assert.CNil(c) // OK + assert.CNotNil(c) // Not OK return err } var c chan byte err := sample(c) fmt.Printf("%v", err) - // Output: sample: assert_test.go:54: ExampleCNotNil.func1(): assertion failure: channel should not be nil + // Output: sample: assert_test.go:58: ExampleCNotNil.func1(): assertion failure: channel should not be nil } func ExampleSNotNil() { sample := func(b []byte) (err error) { defer err2.Handle(&err, "sample") - assert.SNotNil(b) + assert.SEmpty(b) // OK + assert.SNil(b) // OK + assert.SNotNil(b) // Not OK return err } var b []byte err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:67: ExampleSNotNil.func1(): assertion failure: slice should not be nil + // Output: sample: assert_test.go:73: ExampleSNotNil.func1(): assertion failure: slice should not be nil } func ExampleEqual() { sample := func(b []byte) (err error) { defer err2.Handle(&err, "sample") - assert.Equal(len(b), 3) + assert.NotEqual(b[0], 3) // OK, b[0] != 3; (b[0] == 1) + assert.Equal(b[1], 1) // Not OK, b[1] == 2 return err } err := sample([]byte{1, 2}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:80: ExampleEqual.func1(): assertion failure: equal: got '2', want '3' + // Output: sample: assert_test.go:87: ExampleEqual.func1(): assertion failure: equal: got '2', want '1' } func ExampleSLen() { @@ -94,7 +101,7 @@ func ExampleSLen() { } err := sample([]byte{1, 2}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:92: ExampleSLen.func1(): assertion failure: length: got '2', want '3' + // Output: sample: assert_test.go:99: ExampleSLen.func1(): assertion failure: length: got '2', want '3' } func ExampleSNotEmpty() { @@ -106,7 +113,7 @@ func ExampleSNotEmpty() { } err := sample([]byte{}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:104: ExampleSNotEmpty.func1(): assertion failure: slice should not be empty + // Output: sample: assert_test.go:111: ExampleSNotEmpty.func1(): assertion failure: slice should not be empty } func ExampleNotEmpty() { @@ -119,7 +126,7 @@ func ExampleNotEmpty() { } err := sample("") fmt.Printf("%v", err) - // Output: sample: assert_test.go:117: ExampleNotEmpty.func1(): assertion failure: string should not be empty + // Output: sample: assert_test.go:124: ExampleNotEmpty.func1(): assertion failure: string should not be empty } func ExampleMKeyExists() { @@ -136,7 +143,7 @@ func ExampleMKeyExists() { } err := sample("2") fmt.Printf("%v", err) - // Output: sample: assert_test.go:134: ExampleMKeyExists.func1(): assertion failure: key '2' doesn't exist + // Output: sample: assert_test.go:141: ExampleMKeyExists.func1(): assertion failure: key '2' doesn't exist } func ExampleZero() { @@ -149,33 +156,34 @@ func ExampleZero() { var b int8 = 1 // we want sample to assert the violation. err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:146: ExampleZero.func1(): assertion failure: got '1', want (== '0') + // Output: sample: assert_test.go:153: ExampleZero.func1(): assertion failure: got '1', want (== '0') } func ExampleSLonger() { sample := func(b []byte) (err error) { defer err2.Handle(&err, "sample") - assert.SLonger(b, 0) // ok - assert.SLonger(b, 1) // not ok + assert.SLonger(b, 0) // OK + assert.SLonger(b, 1) // Not OK return err } err := sample([]byte{01}) // len = 1 fmt.Printf("%v", err) - // Output: sample: assert_test.go:160: ExampleSLonger.func1(): assertion failure: got '1', should be longer than '1' + // Output: sample: assert_test.go:167: ExampleSLonger.func1(): assertion failure: got '1', should be longer than '1' } func ExampleMShorter() { sample := func(b map[byte]byte) (err error) { defer err2.Handle(&err, "sample") - assert.MShorter(b, 1) // ok - assert.MShorter(b, 0) // not ok + assert.MNotEmpty(b) // OK + assert.MShorter(b, 1) // OK + assert.MShorter(b, 0) // Not OK return err } err := sample(map[byte]byte{01: 01}) // len = 1 fmt.Printf("%v", err) - // Output: sample: assert_test.go:172: ExampleMShorter.func1(): assertion failure: got '1', should be shorter than '1' + // Output: sample: assert_test.go:180: ExampleMShorter.func1(): assertion failure: got '1', should be shorter than '1' } func ExampleSShorter() { @@ -188,7 +196,7 @@ func ExampleSShorter() { } err := sample([]byte{01}) // len = 1 fmt.Printf("%v", err) - // Output: sample: assert_test.go:186: ExampleSShorter.func1(): assertion failure: got '1', should be shorter than '0': optional message (test_str) + // Output: sample: assert_test.go:194: ExampleSShorter.func1(): assertion failure: got '1', should be shorter than '0': optional message (test_str) } func ExampleLess() { @@ -203,7 +211,7 @@ func ExampleLess() { var b int8 = 1 err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:200: ExampleLess.func1(): assertion failure: got '1', want >= '1' + // Output: sample: assert_test.go:208: ExampleLess.func1(): assertion failure: got '1', want >= '1' } func ExampleGreater() { @@ -218,7 +226,7 @@ func ExampleGreater() { var b int8 = 2 err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:215: ExampleGreater.func1(): assertion failure: got '2', want <= '2' + // Output: sample: assert_test.go:223: ExampleGreater.func1(): assertion failure: got '2', want <= '2' } func ExampleNotZero() { @@ -231,26 +239,30 @@ func ExampleNotZero() { var b int8 err := sample(b) fmt.Printf("%v", err) - // Output: sample: assert_test.go:228: ExampleNotZero.func1(): assertion failure: got '0', want (!= 0) + // Output: sample: assert_test.go:236: ExampleNotZero.func1(): assertion failure: got '0', want (!= 0) } func ExampleMLen() { sample := func(b map[int]byte) (err error) { defer err2.Handle(&err, "sample") - assert.MLen(b, 3) + assert.MLonger(b, 1) // OK + assert.MShorter(b, 3) // OK + assert.MLen(b, 3) // Not OK return err } err := sample(map[int]byte{1: 1, 2: 2}) fmt.Printf("%v", err) - // Output: sample: assert_test.go:241: ExampleMLen.func1(): assertion failure: length: got '2', want '3' + // Output: sample: assert_test.go:251: ExampleMLen.func1(): assertion failure: length: got '2', want '3' } func ExampleCLen() { sample := func(b chan int) (err error) { defer err2.Handle(&err, "sample") - assert.CLen(b, 3) + assert.CLonger(b, 1) // OK + assert.CShorter(b, 3) // OK + assert.CLen(b, 3) // Not OK return err } d := make(chan int, 2) @@ -258,7 +270,7 @@ func ExampleCLen() { d <- int(1) err := sample(d) fmt.Printf("%v", err) - // Output: sample: assert_test.go:253: ExampleCLen.func1(): assertion failure: length: got '2', want '3' + // Output: sample: assert_test.go:265: ExampleCLen.func1(): assertion failure: length: got '2', want '3' } func ExampleThatNot() { @@ -269,7 +281,7 @@ func ExampleThatNot() { return err } - // set asserter for this thread/goroutine only, we want plain errors + // Set a specific asserter for this goroutine only, we want plain errors defer assert.SetAsserter(assert.Plain)() err := sample() @@ -277,6 +289,75 @@ func ExampleThatNot() { // Output: testing: run example: overrides if Plain asserter } +func ExampleINotNil() { + sample := func(b error) (err error) { + defer err2.Handle(&err, "sample") + + assert.INotNil(b) // OK + assert.INil(b) // Not OK + return err + } + var b = fmt.Errorf("test") + err := sample(b) + fmt.Printf("%v", err) + // Output: sample: assert_test.go:297: ExampleINotNil.func1(): assertion failure: interface should be nil +} + +func ExampleLen() { + sample := func(b string) (err error) { + defer err2.Handle(&err, "sample") + + assert.Shorter(b, 3) // OK + assert.Longer(b, 1) // OK + assert.Len(b, 3) // Not OK + return err + } + err := sample("12") + fmt.Printf("%v", err) + // Output: sample: assert_test.go:312: ExampleLen.func1(): assertion failure: length: got '2', want '3' +} + +func ExampleDeepEqual() { + sample := func(b []byte) (err error) { + defer err2.Handle(&err, "sample") + + assert.NoError(err) + assert.NotDeepEqual(len(b), 3) // OK, correct size is 2 + assert.DeepEqual(len(b), 3) // Not OK, size is still 2 + return err + } + err := sample([]byte{1, 2}) + fmt.Printf("%v", err) + // Output: sample: assert_test.go:326: ExampleDeepEqual.func1(): assertion failure: got '2', want '3' +} + +func ExampleError() { + sample := func(b error) (err error) { + defer err2.Handle(&err, "sample") + + assert.Error(b) // OK + assert.NoError(b) // Not OK + return err + } + var b = fmt.Errorf("test") + err := sample(b) + fmt.Printf("%v", err) + // Output: sample: assert_test.go:339: ExampleError.func1(): assertion failure: test +} + +func ExampleNotImplemented() { + sample := func(_ error) (err error) { + defer err2.Handle(&err, "sample") + + assert.NotImplemented() // Not OK + return err + } + var b = fmt.Errorf("test") + err := sample(b) + fmt.Printf("%v", err) + // Output: sample: assert_test.go:352: ExampleNotImplemented.func1(): assertion failure: not implemented +} + func BenchmarkMKeyExists(b *testing.B) { bs := map[int]int{0: 0, 1: 1} for n := 0; n < b.N; n++ { From 4a8c5b8e08ce3b66a249b9eabcec3ed30272de7a Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 6 Oct 2024 15:56:20 +0300 Subject: [PATCH 51/78] update assert pkg doc --- assert/doc.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index d6a246e..116c76e 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -52,9 +52,12 @@ raise up quality of our software. The assert package offers a convenient way to set preconditions to code which allow us detect programming errors and API violations faster. Still allowing production-time error handling if needed. And everything is automatic. You can -set asserter with [SetDefault] function or --asserter flag if Go's flag package is -in use. This allows developer, operator and every-day user share the exact same -binary but get the error messages and diagnostic they need. +set gorountinen specific asserter with [PushAsserter] function. + +You can set the assert package's default asserter with [SetDefault] or +-asserter flag if Go's flag package is in use. This allows developer, operator +and every-day user share the exact same binary but get the error messages and +diagnostic they need. // Production asserter adds formatted caller info to normal errors. // Information is transported thru error values when err2.Handle is in use. @@ -75,15 +78,12 @@ And assert package's configuration flags are inserted. # Performance -[assert.That]'s performance is equal to the if-statement thanks for inlining. And -the most of the generics-based versions are about the equally fast. Practice has -thought that we should prefer other than [assert.That] because by using detailed -version like [assert.Shorter] we get precise error messages automatically. Some -also prefer readability of specific asserters. +The performance of the assert functions are equal to the if-statement thanks for +inlining. All of the generics-based versions are the equally fast! -If your algorithm is performance-critical please run `make bench` in the err2 -repo and decide case by case. Also you can make an issue or even PR if you would -like to have something similar like [glog.V] function. +We should prefer specialized versions like [assert.Equal] that we get precise +and readable error messages automatically. Error messagas follow Go idiom of +'got xx, want yy'. And we still can annotate error message if we want. # Naming From 6fab26b2503fea4b942dbe07aee81bca00a05218 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 6 Oct 2024 16:00:53 +0300 Subject: [PATCH 52/78] SetAsserter -> PushAsserter & export PopAsserter --- assert/assert.go | 15 ++++++++++----- assert/assert_test.go | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 0c656b6..1f179bc 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -1015,15 +1015,15 @@ func SetDefault(i defInd) (old defInd) { return } -// SetAsserter set asserter for the current GLS (Gorounine Local Storage). That +// PushAsserter set asserter for the current GLS (Gorounine Local Storage). That // allows us to have multiple different [asserter] in use in the same process. // // Let's say that in some function you want to return plain error messages // instead of the panic asserts, you can use following in the top-level // function: // -// defer assert.SetAsserter(assert.Plain)() -func SetAsserter(i defInd) func() { +// defer assert.PushAsserter(assert.Plain)() +func PushAsserter(i defInd) func() { // get pkg lvl asserter curAsserter := defAsserter[def] // .. to check if we are doing unit tests @@ -1032,10 +1032,15 @@ func SetAsserter(i defInd) func() { curGoRID := goid() asserterMap.Set(curGoRID, defAsserter[i]) } - return popCurrentAsserter + return PopAsserter } -func popCurrentAsserter() { +// PopAsserter pops current gorounine specific asserter from packages memory. +// Gorounine specific asserter can be set with [PushAsserter]. +// +// When gorounine asserter isn't set package's default asserter is used. See +// [SetDefault] for more information. +func PopAsserter() { asserterMap.Del(goid()) } diff --git a/assert/assert_test.go b/assert/assert_test.go index 9a8d1b1..f3cd074 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -282,7 +282,7 @@ func ExampleThatNot() { } // Set a specific asserter for this goroutine only, we want plain errors - defer assert.SetAsserter(assert.Plain)() + defer assert.PushAsserter(assert.Plain)() err := sample() fmt.Printf("%v", err) From 6cbef5bef4f709cf0adc97f18753d2d39e13afd6 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 6 Oct 2024 16:30:49 +0300 Subject: [PATCH 53/78] fix code sample --- assert/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assert/doc.go b/assert/doc.go index 116c76e..1406b92 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -13,7 +13,7 @@ sub-gouroutines: assert.Equal(alice.Len(), 1) // assert anything normally ... go func() { - assert.PushTester(t) // <-- Needs to do again for a new goroutine + assert.PushTester(t)() // <-- Needs to do again for a new goroutine # Merge Runtime And Unit Test Assertions From 3d6dce2cc075fc93c8bdbb04860f5f1d15270624 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 6 Oct 2024 16:31:09 +0300 Subject: [PATCH 54/78] guide to use PushAsserter 1st now that it works --- assert/assert.go | 209 ++++++++++++++++++++++++++++------------------- 1 file changed, 125 insertions(+), 84 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 1f179bc..7b04e4f 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -194,7 +194,7 @@ const ( // defer assert.PushTester(t)() // does the cleanup // ... // go func() { -// assert.PushTester(t) // left cleanup out! Leave it for the test, see ^ +// assert.PushTester(t)() // ... // // Note that the second argument, if given, changes the default asserter for @@ -223,7 +223,7 @@ func PushTester(t testing.TB, a ...defInd) function { // assert.PushTester(t) // <- important! // defer assert.PopTester() // <- for good girls and not so bad boys // ... -// assert.That(something, "test won't work") +// assert.That(something, "won't work") // }) // } // @@ -273,8 +273,9 @@ func tester() (t testing.TB) { // NotImplemented always panics with 'not implemented' assertion message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func NotImplemented(a ...any) { current().reportAssertionFault(0, assertionMsg+": not implemented", a) } @@ -283,8 +284,9 @@ func NotImplemented(a ...any) { // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func ThatNot(term bool, a ...any) { if term { doThat(a) @@ -295,8 +297,9 @@ func ThatNot(term bool, a ...any) { // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func That(term bool, a ...any) { if !term { doThat(a) @@ -311,8 +314,9 @@ func doThat(a []any) { // NotNil asserts that the pointer IS NOT nil. If it is it panics/errors (default // Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func NotNil[P ~*T, T any](p P, a ...any) { if p == nil { doNamed("not", "pointer", "nil", a) @@ -322,8 +326,9 @@ func NotNil[P ~*T, T any](p P, a ...any) { // Nil asserts that the pointer IS nil. If it is not it panics/errors (default // Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func Nil[T any](p *T, a ...any) { if p != nil { doNamed("", "pointer", "nil", a) @@ -333,8 +338,9 @@ func Nil[T any](p *T, a ...any) { // INil asserts that the interface value IS nil. If it is it panics/errors // (default Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note, use this only for real interface types. Go's interface's has two values // so this won't work e.g. slices! @@ -350,8 +356,9 @@ func INil(i any, a ...any) { // INotNil asserts that the interface value is NOT nil. If it is it // panics/errors (default Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note, use this only for real interface types. Go's interface's has two values // so this won't work e.g. slices! @@ -367,8 +374,9 @@ func INotNil(i any, a ...any) { // SNil asserts that the slice IS nil. If it is it panics/errors (default // Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func SNil[S ~[]T, T any](s S, a ...any) { if s != nil { doNamed("", "slice", "nil", a) @@ -378,8 +386,9 @@ func SNil[S ~[]T, T any](s S, a ...any) { // CNil asserts that the channel is nil. If it is not it panics/errors // (default Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func CNil[C ~chan T, T any](c C, a ...any) { if c != nil { doNamed("", "channel", "nil", a) @@ -389,8 +398,9 @@ func CNil[C ~chan T, T any](c C, a ...any) { // MNil asserts that the map is nil. If it is not it panics/errors (default // Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func MNil[M ~map[T]U, T comparable, U any](m M, a ...any) { if m != nil { doNamed("", "map", "nil", a) @@ -400,8 +410,9 @@ func MNil[M ~map[T]U, T comparable, U any](m M, a ...any) { // SNotNil asserts that the slice is not nil. If it is it panics/errors (default // Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func SNotNil[S ~[]T, T any](s S, a ...any) { if s == nil { doNamed("not", "slice", "nil", a) @@ -411,8 +422,9 @@ func SNotNil[S ~[]T, T any](s S, a ...any) { // CNotNil asserts that the channel is not nil. If it is it panics/errors // (default Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func CNotNil[C ~chan T, T any](c C, a ...any) { if c == nil { doNamed("not", "channel", "nil", a) @@ -422,8 +434,9 @@ func CNotNil[C ~chan T, T any](c C, a ...any) { // MNotNil asserts that the map is not nil. If it is it panics/errors (default // Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { if m == nil { doNamed("not", "map", "nil", a) @@ -434,8 +447,9 @@ func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note, when asserter is [Plain], optional arguments are used to build a new // assert violation message. @@ -449,8 +463,9 @@ func NotEqual[T comparable](val, want T, a ...any) { // the current Asserter) with the auto-generated message. You can append the // generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func Equal[T comparable](val, want T, a ...any) { if want != val { doShouldBeEqual(assertionEqualMsg, val, want, a) @@ -472,8 +487,9 @@ func doShouldNotBeEqual[T comparable](aname string, val, want T, a []any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func DeepEqual(val, want any, a ...any) { if !reflect.DeepEqual(val, want) { defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) @@ -486,8 +502,9 @@ func DeepEqual(val, want any, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note, it uses reflect.DeepEqual which means that also the types must be the // same: @@ -509,8 +526,9 @@ func NotDeepEqual(val, want any, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -527,8 +545,9 @@ func Len(obj string, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -550,8 +569,9 @@ func doLonger(l int, length int, a []any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -573,8 +593,9 @@ func doShorter(l int, length int, a []any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -591,8 +612,9 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -609,8 +631,9 @@ func SLonger[S ~[]T, T any](obj S, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -627,8 +650,9 @@ func SShorter[S ~[]T, T any](obj S, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -645,8 +669,9 @@ func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -663,8 +688,9 @@ func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -681,8 +707,9 @@ func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -699,8 +726,9 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -717,8 +745,9 @@ func CLonger[C ~chan T, T any](obj C, length int, a ...any) { // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -733,8 +762,9 @@ func CShorter[C ~chan T, T any](obj C, length int, a ...any) { // MKeyExists asserts that the map key exists. If not it panics/errors (current // Asserter) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func MKeyExists[M ~map[T]U, T comparable, U any]( obj M, key T, @@ -758,8 +788,9 @@ func doMKeyExists(key any, a []any) { // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func NotEmpty(obj string, a ...any) { if obj == "" { doNamed("not", "string", "empty", a) @@ -770,8 +801,9 @@ func NotEmpty(obj string, a ...any) { // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func Empty(obj string, a ...any) { if obj != "" { doNamed("", "string", "empty", a) @@ -782,8 +814,9 @@ func Empty(obj string, a ...any) { // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -799,8 +832,9 @@ func SEmpty[S ~[]T, T any](obj S, a ...any) { // (according the current Asserter) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -818,8 +852,9 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { // You can append the generated got-want message by using optional message // arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -837,8 +872,9 @@ func MEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { // You can append the generated got-want message by using optional message // arguments. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. // // Note! This is reasonably fast but not as fast as [That] because of lacking // inlining for the current implementation of Go's type parametric functions. @@ -877,8 +913,9 @@ func NoError(err error, a ...any) { // violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func Error(err error, a ...any) { if err == nil { doError(a) @@ -894,8 +931,9 @@ func doError(a []any) { // and builds a violation message. Thanks to inlining, the performance penalty // is equal to a single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func Greater[T Number](val, want T, a ...any) { if val <= want { doGreater(val, want, a) @@ -911,8 +949,9 @@ func doGreater[T Number](val, want T, a []any) { // builds a violation message. Thanks to inlining, the performance penalty is // equal to a single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func Less[T Number](val, want T, a ...any) { if val >= want { doLess(val, want, a) @@ -928,8 +967,9 @@ func doLess[T Number](val, want T, a []any) { // violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func Zero[T Number](val T, a ...any) { if val != 0 { doZero(val, a) @@ -945,8 +985,9 @@ func doZero[T Number](val T, a []any) { // violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([SetDefault]), optional arguments -// are used to override the auto-generated assert violation message. +// Note that when [Plain] asserter is used ([PushAsserter] or even +// [SetDefault]), optional arguments are used to override the auto-generated +// assert violation message. func NotZero[T Number](val T, a ...any) { if val == 0 { doNotZero(val, a) From 0e14aabd96e64560335b085346ddcb29adacc815 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 8 Oct 2024 16:45:28 +0300 Subject: [PATCH 55/78] PushAsserter snippet --- snippets/go.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/snippets/go.json b/snippets/go.json index 36b268b..19ce8e4 100644 --- a/snippets/go.json +++ b/snippets/go.json @@ -35,6 +35,11 @@ "body": "defer err2.Handle(&err, func(err error) error {\n\t$0\n})\n", "description": "Snippet for err2.Handle(&err, func(err error) error {})" }, + "defer assert.PushAsserter(asserter.Plain)()": { + "prefix": "apu", + "body": "defer assert.PushAsserter(${1:asserter.Plain})()\n", + "description": "Snippet for 1-liner pushing AND poping asserter.Plain" + }, "defer assert.PushTester(t)()": { "prefix": "aspa", "body": "defer assert.PushTester(${1:t})()\n", From ef13eedff62869b0a4a13112a7f3df51f55e8fe4 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 8 Oct 2024 16:46:01 +0300 Subject: [PATCH 56/78] better docs for assert --- assert/doc.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index 1406b92..90a1899 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -25,7 +25,7 @@ all assert violations in the unit tests as well: assert.That(c.isLeaf(invitersKey), "only leaf can invite") If some assertion violation occurs in the deep call stack, they are still -reported as a test failure. See the above code blocks. If assertion failure +reported as test failures. See the above code blocks. If assertion failure happens somewhere inside the Invite function's call stack, it's still reported correctly as a test failure of the TestInvite unit test. It doesn't matter how deep the recursion is or if parallel test runs are performed. The failure report @@ -35,12 +35,14 @@ chapter. # Call Stack Traversal During Tests The Assert package allows us to track assertion violations over the package and -even module boundaries. When an assertion fails during the unit testing, the -whole call stack is brought to unit test logs. And some help with your IDE, such -as transferring output to a location list, for example, in Neovim/Vim. For -example, you can find a proper test result parser like [nvim-go] (fork) +module boundaries. When an assertion fails during the unit testing, the whole +call stack is brought to unit test logs. And some help with your IDE, such as +transferring output to a location list in Neovim/Vim. For example, you can find +a compatible test result parser for Neovim from this plugin [nvim-go] (fork). -With a sizeable multi-repo environment, this has proven to be valuable. +The call stack traversal has proven to be very valuable for package and module +development in Go, especially when following TDD and fast development feedback +cycles. # Why Runtime Asserts Are So Important? @@ -52,12 +54,12 @@ raise up quality of our software. The assert package offers a convenient way to set preconditions to code which allow us detect programming errors and API violations faster. Still allowing production-time error handling if needed. And everything is automatic. You can -set gorountinen specific asserter with [PushAsserter] function. +set goroutine specific asserter with [PushAsserter] function. -You can set the assert package's default asserter with [SetDefault] or --asserter flag if Go's flag package is in use. This allows developer, operator -and every-day user share the exact same binary but get the error messages and -diagnostic they need. +The assert package's default asserter you can set with [SetDefault] or -asserter +flag if Go's flag package (or similar) is in use. This allows developer, +operator and every-day user share the exact same binary but get the error +messages and diagnostic they need. // Production asserter adds formatted caller info to normal errors. // Information is transported thru error values when err2.Handle is in use. From 5d98a51407f1c45907fb898067f4455afb1c04d2 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 8 Oct 2024 16:50:31 +0300 Subject: [PATCH 57/78] readable db sample --- samples/main-db-sample.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/samples/main-db-sample.go b/samples/main-db-sample.go index 1f9ea55..4b0e2a3 100644 --- a/samples/main-db-sample.go +++ b/samples/main-db-sample.go @@ -29,15 +29,6 @@ func (db *Database) MoneyTransfer(from, to *Account, amount int) (err error) { }) try.To(from.ReserveBalance(tx, amount)) - - defer err2.Handle( - &err, - func(err error) error { // optional, following sample's wording - err = fmt.Errorf("cannot %w", err) - return err - }, - ) - try.To(from.Withdraw(tx, amount)) try.To(to.Deposit(tx, amount)) try.To(tx.Commit()) From 95c1b9513a6e2857a4baf03f63d3e01db4ccf5f4 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 8 Oct 2024 18:57:05 +0300 Subject: [PATCH 58/78] new type Asserter and doc refs to it --- assert/assert.go | 211 ++++++++++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 105 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 7b04e4f..ac2b133 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -14,7 +14,7 @@ import ( "golang.org/x/exp/constraints" ) -type defInd = uint32 +type Asserter = uint32 // Asserters are the way to set what kind of messages assert package outputs if // assertion is violated. @@ -25,18 +25,18 @@ type defInd = uint32 // // assert.NotEmpty(c.PoolName, "pool name cannot be empty") // -// Note that Plain is only asserter that override auto-generated assertion +// Note that Plain is only [Asserter] that override auto-generated assertion // messages with given arguments like 'pool name cannot be empty'. Others add // given arguments at the end of the auto-generated assert message. // -// [Production] (pkg's default) is the best asserter for most cases. The +// [Production] (pkg's default) is the best [Asserter] for most cases. The // assertion violations are treated as Go error values. And only a pragmatic // caller info is included into the error values like source filename, line // number, and caller function, all in one line: // // copy file: main.go:37: CopyFile(): assertion violation: string shouldn't be empty // -// [Development] is the best asserter for development use. The assertion +// [Development] is the best [Asserter] for development use. The assertion // violations are treated as Go error values. And a formatted caller info is // included to the error message like source filename , line number, and caller // function. Everything in a noticeable multi-line message: @@ -47,14 +47,14 @@ type defInd = uint32 // assertion violation: string shouldn't be empty // -------------------------------- // -// [Test] minimalistic asserter for unit test use. More pragmatic is the -// [TestFull] asserter (test default). +// [Test] minimalistic [Asserter] for unit test use. More pragmatic is the +// [TestFull] [Asserter] (test default). // -// Use this asserter if your IDE/editor doesn't support full file names and it +// 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. // -// [TestFull] asserter (test default). The TestFull asserter includes the caller +// [TestFull] [Asserter] (test default). The TestFull [Asserter] includes the caller // info and the call stack for unit testing, similarly like err2's error traces. // Note that [PushTester] set's TestFull if it's not yet set. // @@ -70,7 +70,7 @@ type defInd = uint32 // the make result parsers can process the output properly and allow traverse of // locations of the error trace. // -// [Debug] asserter transforms assertion violations to panic calls where panic +// [Debug] [Asserter] transforms assertion violations to panic calls where panic // object's type is string, i.e., err2 package treats it as a normal panic, not // an error. // @@ -86,7 +86,7 @@ type defInd = uint32 // // [nvim-go]: https://github.com/lainio/nvim-go const ( - Plain defInd = 0 + iota + Plain Asserter = 0 + iota Production Development Test @@ -118,7 +118,7 @@ var ( // TestFull // Debug defAsserter = []asserter{plain, prod, dev, test, testFull, dbg} - def defInd + def Asserter // mu is package lvl Mutex that is used to cool down race detector of // client pkgs, i.e. packages that use us can use -race flag in their test @@ -165,7 +165,7 @@ const ( conCatErrStr = ": " ) -// PushTester sets the current testing context for default asserter. This must +// PushTester sets the current testing context for default [Asserter]. This must // be called at the beginning of every test. There is two way of doing it: // // for _, tt := range tests { @@ -186,7 +186,7 @@ const ( // to one line. See the first t.Run call above. See more information in // [PopTester]. // -// PushTester allows you to change the current default asserter by accepting it +// PushTester allows you to change the current default [Asserter] by accepting it // as a second argument. // // Note that you MUST call PushTester for sub-goroutines: @@ -197,10 +197,10 @@ const ( // assert.PushTester(t)() // ... // -// Note that the second argument, if given, changes the default asserter for +// Note that the second argument, if given, changes the default [Asserter] for // whole package. The argument is mainly for temporary development use and isn't // not preferred API usage. -func PushTester(t testing.TB, a ...defInd) function { +func PushTester(t testing.TB, a ...Asserter) function { if len(a) > 0 { SetDefault(a[0]) } else if current()&asserterUnitTesting == 0 { @@ -273,7 +273,7 @@ func tester() (t testing.TB) { // NotImplemented always panics with 'not implemented' assertion message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func NotImplemented(a ...any) { @@ -284,7 +284,7 @@ func NotImplemented(a ...any) { // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func ThatNot(term bool, a ...any) { @@ -297,7 +297,7 @@ func ThatNot(term bool, a ...any) { // formatting string. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func That(term bool, a ...any) { @@ -312,9 +312,9 @@ func doThat(a []any) { } // NotNil asserts that the pointer IS NOT nil. If it is it panics/errors (default -// Asserter) the auto-generated (args appended) message. +// [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func NotNil[P ~*T, T any](p P, a ...any) { @@ -324,9 +324,9 @@ func NotNil[P ~*T, T any](p P, a ...any) { } // Nil asserts that the pointer IS nil. If it is not it panics/errors (default -// Asserter) the auto-generated (args appended) message. +// [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func Nil[T any](p *T, a ...any) { @@ -336,9 +336,9 @@ func Nil[T any](p *T, a ...any) { } // INil asserts that the interface value IS nil. If it is it panics/errors -// (default Asserter) the auto-generated (args appended) message. +// (default [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -354,9 +354,9 @@ func INil(i any, a ...any) { } // INotNil asserts that the interface value is NOT nil. If it is it -// panics/errors (default Asserter) the auto-generated (args appended) message. +// panics/errors (default [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -372,9 +372,9 @@ func INotNil(i any, a ...any) { } // SNil asserts that the slice IS nil. If it is it panics/errors (default -// Asserter) the auto-generated (args appended) message. +// [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func SNil[S ~[]T, T any](s S, a ...any) { @@ -384,9 +384,9 @@ func SNil[S ~[]T, T any](s S, a ...any) { } // CNil asserts that the channel is nil. If it is not it panics/errors -// (default Asserter) the auto-generated (args appended) message. +// (default [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func CNil[C ~chan T, T any](c C, a ...any) { @@ -396,9 +396,9 @@ func CNil[C ~chan T, T any](c C, a ...any) { } // MNil asserts that the map is nil. If it is not it panics/errors (default -// Asserter) the auto-generated (args appended) message. +// [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func MNil[M ~map[T]U, T comparable, U any](m M, a ...any) { @@ -408,9 +408,9 @@ func MNil[M ~map[T]U, T comparable, U any](m M, a ...any) { } // SNotNil asserts that the slice is not nil. If it is it panics/errors (default -// Asserter) the auto-generated (args appended) message. +// [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func SNotNil[S ~[]T, T any](s S, a ...any) { @@ -420,9 +420,9 @@ func SNotNil[S ~[]T, T any](s S, a ...any) { } // CNotNil asserts that the channel is not nil. If it is it panics/errors -// (default Asserter) the auto-generated (args appended) message. +// (default [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func CNotNil[C ~chan T, T any](c C, a ...any) { @@ -432,9 +432,9 @@ func CNotNil[C ~chan T, T any](c C, a ...any) { } // MNotNil asserts that the map is not nil. If it is it panics/errors (default -// Asserter) the auto-generated (args appended) message. +// [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { @@ -444,14 +444,14 @@ func MNotNil[M ~map[T]U, T comparable, U any](m M, a ...any) { } // NotEqual asserts that the values aren't equal. If they are it panics/errors -// (according the current Asserter) with the auto-generated message. You can +// (according the current [Asserter]) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // -// Note, when asserter is [Plain], optional arguments are used to build a new +// Note, when [Asserter] is [Plain], optional arguments are used to build a new // assert violation message. func NotEqual[T comparable](val, want T, a ...any) { if want == val { @@ -460,10 +460,10 @@ func NotEqual[T comparable](val, want T, a ...any) { } // Equal asserts that the values are equal. If not it panics/errors (according -// the current Asserter) with the auto-generated message. You can append the +// the current [Asserter]) with the auto-generated message. You can append the // generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func Equal[T comparable](val, want T, a ...any) { @@ -483,11 +483,11 @@ func doShouldNotBeEqual[T comparable](aname string, val, want T, a []any) { } // DeepEqual asserts that the (whatever) values are equal. If not it -// panics/errors (according the current Asserter) with the auto-generated +// panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func DeepEqual(val, want any, a ...any) { @@ -498,11 +498,11 @@ func DeepEqual(val, want any, a ...any) { } // NotDeepEqual asserts that the (whatever) values are equal. If not it -// panics/errors (according the current Asserter) with the auto-generated +// panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -522,11 +522,11 @@ func NotDeepEqual(val, want any, a ...any) { } // Len asserts that the length of the string is equal to the given. If not it -// panics/errors (according the current Asserter) with the auto-generated +// panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -541,11 +541,11 @@ func Len(obj string, length int, a ...any) { } // Longer asserts that the length of the string is longer to the given. If not -// it panics/errors (according the current Asserter) with the auto-generated +// it panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -565,11 +565,11 @@ func doLonger(l int, length int, a []any) { } // Shorter asserts that the length of the string is shorter to the given. If not -// it panics/errors (according the current Asserter) with the auto-generated +// it panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -589,11 +589,11 @@ func doShorter(l int, length int, a []any) { } // SLen asserts that the length of the slice is equal to the given. If not it -// panics/errors (according the current Asserter) with the auto-generated +// panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -608,11 +608,11 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { } // SLonger asserts that the length of the slice is equal to the given. If not it -// panics/errors (according the current Asserter) with the auto-generated +// panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -627,11 +627,11 @@ func SLonger[S ~[]T, T any](obj S, length int, a ...any) { } // SShorter asserts that the length of the slice is equal to the given. If not -// it panics/errors (according the current Asserter) with the auto-generated +// it panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -646,11 +646,11 @@ func SShorter[S ~[]T, T any](obj S, length int, a ...any) { } // MLen asserts that the length of the map is equal to the given. If not it -// panics/errors (according the current Asserter) with the auto-generated +// panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -665,11 +665,11 @@ func MLen[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { } // MLonger asserts that the length of the map is longer to the given. If not it -// panics/errors (according the current Asserter) with the auto-generated +// panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -684,11 +684,11 @@ func MLonger[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { } // MShorter asserts that the length of the map is shorter to the given. If not -// it panics/errors (according the current Asserter) with the auto-generated +// it panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -703,11 +703,11 @@ func MShorter[M ~map[T]U, T comparable, U any](obj M, length int, a ...any) { } // CLen asserts that the length of the chan is equal to the given. If not it -// panics/errors (according the current Asserter) with the auto-generated +// panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -722,11 +722,11 @@ func CLen[C ~chan T, T any](obj C, length int, a ...any) { } // CLonger asserts that the length of the chan is longer to the given. If not it -// panics/errors (according the current Asserter) with the auto-generated +// panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -741,11 +741,11 @@ func CLonger[C ~chan T, T any](obj C, length int, a ...any) { } // CShorter asserts that the length of the chan is shorter to the given. If not -// it panics/errors (according the current Asserter) with the auto-generated +// it panics/errors (according the current [Asserter]) with the auto-generated // message. You can append the generated got-want message by using optional // message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -760,9 +760,9 @@ func CShorter[C ~chan T, T any](obj C, length int, a ...any) { } // MKeyExists asserts that the map key exists. If not it panics/errors (current -// Asserter) the auto-generated (args appended) message. +// [Asserter]) the auto-generated (args appended) message. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func MKeyExists[M ~map[T]U, T comparable, U any]( @@ -785,10 +785,10 @@ func doMKeyExists(key any, a []any) { } // NotEmpty asserts that the string is not empty. If it is, it panics/errors -// (according the current Asserter) with the auto-generated message. You can +// (according the current [Asserter]) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func NotEmpty(obj string, a ...any) { @@ -798,10 +798,10 @@ func NotEmpty(obj string, a ...any) { } // Empty asserts that the string is empty. If it is NOT, it panics/errors -// (according the current Asserter) with the auto-generated message. You can +// (according the current [Asserter]) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func Empty(obj string, a ...any) { @@ -811,10 +811,10 @@ func Empty(obj string, a ...any) { } // SEmpty asserts that the slice is empty. If it is NOT, it panics/errors -// (according the current Asserter) with the auto-generated message. You can +// (according the current [Asserter]) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -829,10 +829,10 @@ func SEmpty[S ~[]T, T any](obj S, a ...any) { } // SNotEmpty asserts that the slice is not empty. If it is, it panics/errors -// (according the current Asserter) with the auto-generated message. You can +// (according the current [Asserter]) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -847,12 +847,12 @@ func SNotEmpty[S ~[]T, T any](obj S, a ...any) { } // MEmpty asserts that the map is empty. If it is NOT, it panics/errors -// (according the current Asserter) with the auto-generated message. You can +// (according the current [Asserter]) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // You can append the generated got-want message by using optional message // arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -867,12 +867,12 @@ func MEmpty[M ~map[T]U, T comparable, U any](obj M, a ...any) { } // MNotEmpty asserts that the map is not empty. If it is, it panics/errors -// (according the current Asserter) with the auto-generated message. You can +// (according the current [Asserter]) with the auto-generated message. You can // append the generated got-want message by using optional message arguments. // You can append the generated got-want message by using optional message // arguments. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. // @@ -913,7 +913,7 @@ func NoError(err error, a ...any) { // violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func Error(err error, a ...any) { @@ -931,7 +931,7 @@ func doError(a []any) { // and builds a violation message. Thanks to inlining, the performance penalty // is equal to a single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func Greater[T Number](val, want T, a ...any) { @@ -949,7 +949,7 @@ func doGreater[T Number](val, want T, a []any) { // builds a violation message. Thanks to inlining, the performance penalty is // equal to a single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func Less[T Number](val, want T, a ...any) { @@ -967,7 +967,7 @@ func doLess[T Number](val, want T, a []any) { // violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func Zero[T Number](val T, a ...any) { @@ -985,7 +985,7 @@ func doZero[T Number](val T, a []any) { // violation message. Thanks to inlining, the performance penalty is equal to a // single 'if-statement' that is almost nothing. // -// Note that when [Plain] asserter is used ([PushAsserter] or even +// Note that when [Plain] [Asserter] is used ([PushAsserter] or even // [SetDefault]), optional arguments are used to override the auto-generated // assert violation message. func NotZero[T Number](val T, a ...any) { @@ -999,7 +999,7 @@ func doNotZero[T Number](val T, a []any) { current().reportAssertionFault(1, defMsg, a) } -// current returns a current default asserter used for assert functions like +// current returns a current default [Asserter] used for assert functions like // assert.That() in this gorounine. // // NOTE this indexing stuff is done because of race detection to work on client @@ -1021,8 +1021,8 @@ func current() (curAsserter asserter) { return curAsserter } -// SetDefault sets the current default asserter for assert pkg. It also returns -// the previous asserter. +// SetDefault sets the current default [Asserter] for assert pkg. It also +// returns the previous [Asserter]. // // Note that you should use this in TestMain function, and use [flag] package to // set it for the app. For the tests you can set it to panic about every @@ -1034,12 +1034,12 @@ func current() (curAsserter asserter) { // what's best for your case. // // Tip. If our own packages (client packages for assert) have lots of parallel -// testing and race detection, please try to use same asserter for all of them -// and set asserter only one in TestMain, or in init. +// testing and race detection, please try to use same [Asserter] for all of them +// and set [Asserter] only one in TestMain, or in init. // // func TestMain(m *testing.M) { // SetDefault(assert.TestFull) -func SetDefault(i defInd) (old defInd) { +func SetDefault(i Asserter) (old Asserter) { // pkg lvl lock to allow only one pkg client call this at one of the time // together with the indexing, i.e we don't need to switch asserter // variable or pointer to it but just index to array they are stored. @@ -1056,15 +1056,16 @@ func SetDefault(i defInd) (old defInd) { return } -// PushAsserter set asserter for the current GLS (Gorounine Local Storage). That -// allows us to have multiple different [asserter] in use in the same process. +// PushAsserter set [Asserter] for the current GLS (Gorounine Local Storage). +// That allows us to have multiple different [Asserter] in use in the same +// process. // // Let's say that in some function you want to return plain error messages // instead of the panic asserts, you can use following in the top-level // function: // // defer assert.PushAsserter(assert.Plain)() -func PushAsserter(i defInd) func() { +func PushAsserter(i Asserter) func() { // get pkg lvl asserter curAsserter := defAsserter[def] // .. to check if we are doing unit tests @@ -1076,17 +1077,17 @@ func PushAsserter(i defInd) func() { return PopAsserter } -// PopAsserter pops current gorounine specific asserter from packages memory. -// Gorounine specific asserter can be set with [PushAsserter]. +// PopAsserter pops current gorounine specific [Asserter] from packages memory. +// Gorounine specific [Asserter] can be set with [PushAsserter]. // -// When gorounine asserter isn't set package's default asserter is used. See +// When gorounine [Asserter] isn't set package's default [Asserter] is used. See // [SetDefault] for more information. func PopAsserter() { asserterMap.Del(goid()) } // mapDefInd runtime asserters, that's why test asserts are removed for now. -var mapDefInd = map[string]defInd{ +var mapDefInd = map[string]Asserter{ "Plain": Plain, "Prod": Production, "Dev": Development, @@ -1095,7 +1096,7 @@ var mapDefInd = map[string]defInd{ "Debug": Debug, } -var mapDefIndToString = map[defInd]string{ +var mapDefIndToString = map[Asserter]string{ Plain: "Plain", Production: "Prod", Development: "Dev", @@ -1108,7 +1109,7 @@ func defaultAsserterString() string { return mapDefIndToString[def] } -func newDefInd(v string) defInd { +func newDefInd(v string) Asserter { ind, found := mapDefInd[v] if !found { return Plain From fe63d9c684955072adbae57b50c377f667573606 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 8 Oct 2024 19:08:51 +0300 Subject: [PATCH 59/78] typo --- doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc.go b/doc.go index af37f26..7d7b007 100644 --- a/doc.go +++ b/doc.go @@ -75,7 +75,7 @@ programmatically (before [flag.Parse] if you are using that): or err2.SetPanicTracer(log.Writer()) // panic stack trace to std logger -Note. Since [Catch]'s default mode is to recover from panics, it's a good +Note that since [Catch]'s default mode is to recover from panics, it's a good practice still print their stack trace. The panic tracer's default values is [os.Stderr]. The default error tracer is nil. From d322e5f327b393b6082f6130d9ff74d24fceabee Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 8 Oct 2024 19:12:13 +0300 Subject: [PATCH 60/78] use Asserter type in pkg lvl docs --- assert/doc.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index 90a1899..b026f2f 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -54,12 +54,12 @@ raise up quality of our software. The assert package offers a convenient way to set preconditions to code which allow us detect programming errors and API violations faster. Still allowing production-time error handling if needed. And everything is automatic. You can -set goroutine specific asserter with [PushAsserter] function. +set a goroutine specific [Asserter] with [PushAsserter] function. -The assert package's default asserter you can set with [SetDefault] or -asserter -flag if Go's flag package (or similar) is in use. This allows developer, -operator and every-day user share the exact same binary but get the error -messages and diagnostic they need. +The assert package's default [Asserter] you can set with [SetDefault] or +-asserter flag if Go's flag package (or similar) is in use. This allows +developer, operator and every-day user share the exact same binary but get the +error messages and diagnostic they need. // Production asserter adds formatted caller info to normal errors. // Information is transported thru error values when err2.Handle is in use. @@ -67,6 +67,11 @@ messages and diagnostic they need. Please see the code examples for more information. +Note that if an [Asserter] is set for a goroutine level, it cannot be changed +with the -asserter flag or [SetDefault]. The GLS [Asserter] is used for a +reason, so it's good that even a unit test asserter won't override it in those +cases. + # Flag Package Support The assert package supports Go's flags. All you need to do is to call From 66af422cefba53303665cf1d07199e65acc43232 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 9 Oct 2024 16:01:27 +0300 Subject: [PATCH 61/78] release v1.1.0 comments --- CHANGELOG.md | 12 ++++++++++++ README.md | 22 +++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 662568b..9d8507a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ### Version history +##### 1.0.0 +- **Finally! We are very happy, and thanks to all who have helped!** +- Lots of documentation updates and cleanups for version 1.0.0 +- `Catch/Handle` take unlimited amount error handler functions + - allows building e.g. error handling middlewares + - this is major feature because it allows building helpers/add-ons +- automatic outputs aren't overwritten by given args, only with `assert.Plain` +- Minor API fixes to still simplify it: + - remove exported vars, obsolete types and funcs from `assert` pkg + - `Result2.Def2()` sets only `Val2` +- technical refactorings: variadic function calls only in API level + ##### 0.9.52 - `err2.Stderr` helpers for `Catch/Handle` to direct auto-logging + snippets - `assert` package `Shorter` `Longer` helpers for automatic messages diff --git a/README.md b/README.md index 2e3b3f3..3b87e2e 100644 --- a/README.md +++ b/README.md @@ -524,14 +524,14 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). ### Latest Release -##### 1.0.0 -- **Finally! We are very happy, and thanks to all who have helped!** -- Lots of documentation updates and cleanups for version 1.0.0 -- `Catch/Handle` take unlimited amount error handler functions - - allows building e.g. error handling middlewares - - this is major feature because it allows building helpers/add-ons -- automatic outputs aren't overwritten by given args, only with `assert.Plain` -- Minor API fixes to still simplify it: - - remove exported vars, obsolete types and funcs from `assert` pkg - - `Result2.Def2()` sets only `Val2` -- technical refactorings: variadic function calls only in API level +##### 1.1.0 +- `assert` package: + - bug fix: call stack traversal during unit testing in some situations + - **all generics-based functions are inline expansed** + - *performance* is now *same as if-statements for all functions* + - new assert functions: `MNil`, `CNil`, `Less`, `Greater` + - all assert messages flow Go idiom: `got want` + - `Asserter` can be set per goroutine: `PushAsserter` +- `try` package: + - new check functions: `T`, `T1`, `T2`, `T3`, for quick refactoring to annotate an error locally + - **all functions are inline expansed**: if-statement equal performance From cfb04a109f6e4b9d20e9197ee5f4455fe3a1c448 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 9 Oct 2024 19:34:37 +0300 Subject: [PATCH 62/78] github action version update --- .github/workflows/go.yml | 4 ++-- .github/workflows/test.yml | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 18adcd3..b3a373b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,10 +11,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: 1.19 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index edb0dd3..c606485 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,39 +5,39 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v5 with: version: latest args: --timeout=5m test: strategy: matrix: - go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x] + go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x] os: [windows-latest, ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - name: setup go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: test run: make test test-cov: runs-on: ubuntu-latest steps: - name: setup - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: 1.19.x - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: test run: make test_cov_out - name: upload - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v4 with: files: ./coverage.txt From 09966e0da49476d709458cbf8afbca39cff7d9eb Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 9 Oct 2024 19:56:54 +0300 Subject: [PATCH 63/78] new markup features --- README.md | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 3b87e2e..05e863b 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,14 @@ func CopyFile(src, dst string) (err error) { All of the listed above **without any performance penalty**! You are welcome to run `benchmarks` in the project repo and see yourself. -Please note that many benchmarks run 'too fast' according to the common Go -benchmarking rules, i.e., compiler optimizations -([inlining](https://en.wikipedia.org/wiki/Inline_expansion)) are working so well -that there are no meaningful results. But for this type of package, where **we -compete with if-statements, that's precisely what we hope to achieve.** The -whole package is written toward that goal. Especially with parametric -polymorphism, it's been quite the effort. +> [!IMPORTANT] +> Most of the benchmarks run 'too fast' according to the common Go +> benchmarking rules, i.e., compiler optimizations +> ([inlining](https://en.wikipedia.org/wiki/Inline_expansion)) are working so +> well that there are no meaningful results. But for this type of package, where +> **we compete with if-statements, that's precisely what we hope to achieve.** +> The whole package is written toward that goal. Especially with parametric +> polymorphism, it's been quite the effort. ## Automatic Error Propagation @@ -79,11 +80,8 @@ little error handling. But most importantly, it doesn't help developers with **automatic** error propagation, which would have the same benefits as, e.g., **automated** garbage collection or automatic testing: -> Automation is not just about efficiency but primarily about repeatability and -> resilience. -- Gregor Hohpe - -Automatic error propagation is crucial because it makes your code change -tolerant. And, of course, it helps to make your code error-safe: +Automatic error propagation is crucial because it makes your *code change +tolerant*. And, of course, it helps to make your code error-safe: ![Never send a human to do a machine's job](https://www.magicalquote.com/wp-content/uploads/2013/10/Never-send-a-human-to-do-a-machines-job.jpg) @@ -160,9 +158,10 @@ If no `Tracer` is set no stack tracing is done. This is the default because in the most cases proper error messages are enough and panics are handled immediately by a programmer. -> Note. Since v0.9.5 you can set these tracers through Go's standard flag -> package just by adding `flag.Parse()` to your program. See more information -> from [Automatic Flags](#automatic-flags). +> [!NOTE] +> Since v0.9.5 you can set these asserters through Go's standard flag package +> just by adding `flag.Parse()` to your program. See more information from +> [Automatic Flags](#automatic-flags). [Read the package documentation for more information](https://pkg.go.dev/github.com/lainio/err2). @@ -236,13 +235,15 @@ notExist := try.Is(r2.err, plugin.ErrNotExist) // real errors are cought and the returned boolean tells if value // dosen't exist returned as `plugin.ErrNotExist` ``` -**Note.** Any other error than `plugin.ErrNotExist` is treated as an real error: -1. `try.Is` function first checks `if err == nil`, and if yes, it returns - `false`. -2. Then it checks if `errors.Is(err, plugin.ErrNotExist)` and if yes, it returns - `true`. -3. Finally, it calls `try.To` for the non nil error, and we already know what then - happens: nearest `err2.Handle` gets it first. + +> [!NOTE] +> Any other error than `plugin.ErrNotExist` is treated as an real error: +> 1. `try.Is` function first checks `if err == nil`, and if yes, it returns +> `false`. +> 2. Then it checks if `errors.Is(err, plugin.ErrNotExist)` and if yes, it returns +> `true`. +> 3. Finally, it calls `try.To` for the non nil error, and we already know what then +> happens: nearest `err2.Handle` gets it first. These `try.Is` functions help cleanup mess idiomatic Go, i.e. mixing happy and error path, leads to. @@ -293,9 +294,10 @@ error messages as simple as possible. And by offering option to turn additional information on, which allows super users and developers get more technical information when needed. -> Note. Since v0.9.5 you can set these asserters through Go's standard flag -> package just by adding `flag.Parse()` to your program. See more information -> from [Automatic Flags](#automatic-flags). +> [!NOTE] +> Since v0.9.5 you can set these asserters through Go's standard flag package +> just by adding `flag.Parse()` to your program. See more information from +> [Automatic Flags](#automatic-flags). #### Assertion Package for Runtime Use From 28cc11d575dae0a9644d1ee0aead36bd2158568e Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 9 Oct 2024 20:06:35 +0300 Subject: [PATCH 64/78] learning details --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 05e863b..ea7639b 100644 --- a/README.md +++ b/README.md @@ -493,6 +493,9 @@ help: - and most importantly, **it keeps your code more refactorable** because you don't have to repeat yourself. +
+Learnings...> + ## Learnings by so far We have used the `err2` and `assert` packages in several projects. The results @@ -514,6 +517,8 @@ been much easier.** There is an excellent [blog post](https://jesseduffield.com/ about the issues you are facing with Go's error handling without the help of the err2 package. +

+ ## Support And Contributions The package has been in experimental mode quite long time. Since the Go generics @@ -531,8 +536,8 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). - bug fix: call stack traversal during unit testing in some situations - **all generics-based functions are inline expansed** - *performance* is now *same as if-statements for all functions* - - new assert functions: `MNil`, `CNil`, `Less`, `Greater` - - all assert messages flow Go idiom: `got want` + - new assert functions: `MNil`, `CNil`, `Less`, `Greater`, etc. + - all assert messages follow Go idiom: `got, want` - `Asserter` can be set per goroutine: `PushAsserter` - `try` package: - new check functions: `T`, `T1`, `T2`, `T3`, for quick refactoring to annotate an error locally From 031e87bcb1b2374f8370ccc24bf1f13f5d263d0d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 9 Oct 2024 20:08:26 +0300 Subject: [PATCH 65/78] fix details --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ea7639b..cbe2871 100644 --- a/README.md +++ b/README.md @@ -494,7 +494,7 @@ help: don't have to repeat yourself.
-Learnings...> +Learnings... ## Learnings by so far From 036cee02d82cb735c29ed3903fde7909d35ee1bf Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 10 Oct 2024 12:37:00 +0300 Subject: [PATCH 66/78] typos in PushAsserter --- snippets/go.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/snippets/go.json b/snippets/go.json index 19ce8e4..b85c415 100644 --- a/snippets/go.json +++ b/snippets/go.json @@ -35,10 +35,10 @@ "body": "defer err2.Handle(&err, func(err error) error {\n\t$0\n})\n", "description": "Snippet for err2.Handle(&err, func(err error) error {})" }, - "defer assert.PushAsserter(asserter.Plain)()": { + "defer assert.PushAsserter(assert.Plain)()": { "prefix": "apu", - "body": "defer assert.PushAsserter(${1:asserter.Plain})()\n", - "description": "Snippet for 1-liner pushing AND poping asserter.Plain" + "body": "defer assert.PushAsserter(${1:assert.Plain})()\n", + "description": "Snippet for 1-liner pushing AND poping assert.Plain" }, "defer assert.PushTester(t)()": { "prefix": "aspa", From 848d840001577c3d68c4b3598a13c69def7fcceb Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 10 Oct 2024 17:34:51 +0300 Subject: [PATCH 67/78] add --- logo/logo.png | Bin 0 -> 91122 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 logo/logo.png diff --git a/logo/logo.png b/logo/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bca3efaf766e2bb8b4208d4936961443d456236d GIT binary patch literal 91122 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 DGvU_i literal 0 HcmV?d00001 From fd7b890d80de3e23b13e4fed1b8fff0c404932ee Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 10 Oct 2024 18:01:48 +0300 Subject: [PATCH 68/78] cleanup --- README.md | 152 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 93 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index cbe2871..d3f05e7 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ +# err2 + [![test](https://github.com/lainio/err2/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/lainio/err2/actions/workflows/test.yml) ![Go Version](https://img.shields.io/badge/go%20version-%3E=1.18-61CFDD.svg?style=flat-square) [![PkgGoDev](https://pkg.go.dev/badge/mod/github.com/lainio/err2)](https://pkg.go.dev/mod/github.com/lainio/err2) [![Go Report Card](https://goreportcard.com/badge/github.com/lainio/err2?style=flat-square)](https://goreportcard.com/report/github.com/lainio/err2) -# err2 + + +---- The package extends Go's error handling with **fully automatic error checking and propagation** like other modern programming languages: **Zig**, Rust, Swift, @@ -28,6 +32,8 @@ func CopyFile(src, dst string) (err error) { } ``` +---- + `go get github.com/lainio/err2` - [Structure](#structure) @@ -37,7 +43,6 @@ func CopyFile(src, dst string) (err error) { - [Error Stack Tracing](#error-stack-tracing) - [Error Checks](#error-checks) - [Filters for non-errors like io.EOF](#filters-for-non-errors-like-ioeof) -- [Backwards Compatibility Promise for the API](#backwards-compatibility-promise-for-the-api) - [Assertion](#assertion) - [Asserters](#asserters) - [Assertion Package for Runtime Use](#assertion-package-for-runtime-use) @@ -75,17 +80,15 @@ run `benchmarks` in the project repo and see yourself. ## Automatic Error Propagation -The current version of Go tends to produce too much error checking and too -little error handling. But most importantly, it doesn't help developers with -**automatic** error propagation, which would have the same benefits as, e.g., -**automated** garbage collection or automatic testing: - Automatic error propagation is crucial because it makes your *code change -tolerant*. And, of course, it helps to make your code error-safe: +tolerant*. And, of course, it helps to make your code error-safe. ![Never send a human to do a machine's job](https://www.magicalquote.com/wp-content/uploads/2013/10/Never-send-a-human-to-do-a-machines-job.jpg) -The err2 package is your automation buddy: + +
+The err2 package is your automation buddy: +
1. It helps to declare error handlers with `defer`. If you're familiar with [Zig language](https://ziglang.org/), you can think `defer err2.Handle(&err,...)` @@ -101,31 +104,47 @@ You can use all of them or just the other. However, if you use `try` for error checks, you must remember to use Go's `recover()` by yourself, or your error isn't transformed to an `error` return value at any point. -## Error handling +
-The `err2` relies on Go's declarative programming structure `defer`. The -`err2` helps to set deferred functions (error handlers) which are only called if -`err != nil`. +## Error Handling -Every function which uses err2 for error-checking should have at least one error -handler. The current function panics if there are no error handlers and an error -occurs. However, if *any* function above in the call stack has an err2 error -handler, it will catch the error. +The err2 relies on Go's declarative programming structure `defer`. The +err2 helps to set deferred error handlers which are only called if an error +occurs. -This is the simplest form of `err2` automatic error handler: +This is the simplest form of an automatic error handler: ```go func doSomething() (err error) { - // below: if err != nil { return ftm.Errorf("%s: %w", CUR_FUNC_NAME, err) } defer err2.Handle(&err) ``` +
+The explanation of the above code and its error handler: +
+ +Simplest rule for err2 error handlers are: +1. Use named error return value: `(..., err error)` +1. Add at least one error handler at the beginning of your function (see the + above code block). *Handlers are called only if error ≠ nil.* +1. Use `err2.handle` functions different calling schemes to achieve needed + behaviour. For example, without no extra arguments `err2.Handle` + automatically annotates your errors by building annotations string from the + function's current name: `doSomething → "do something"`. Default is decamel + and add spaces. See `err2.SetFormatter` for more information. +1. Every function which uses err2 for error-checking should have at least one + error handler. The current function panics if there are no error handlers and + an error occurs. However, if *any* function above in the call stack has an + err2 error handler, it will catch the error. + See more information from `err2.Handle`'s documentation. It supports several error-handling scenarios. And remember that you can have as many error handlers per function as you need, as well as you can chain error handling functions per `err2.Handle` that allows you to build new error handling middleware for your own purposes. +
+ #### Error Stack Tracing The err2 offers optional stack tracing. It's automatic and optimized. Optimized @@ -134,7 +153,10 @@ trace starts from where the actual error/panic is occurred, not where the error is caught. You don't need to search for the line where the pointer was nil or received an error. That line is in the first one you are seeing: -```console +
+The example of the optimized call stack: + +```sh --- runtime error: index out of range [0] with length 0 --- @@ -145,6 +167,8 @@ main.main() /home/.../go/src/github.com/lainio/ic/main.go:77 +0x248 ``` +

+ Just set the `err2.SetErrorTracer` or `err2.SetPanicTracer` to the stream you want traces to be written: @@ -197,7 +221,7 @@ handling. Nevertheless, there might be cases where you might want to: 1. Suppress the error and use some default value. -1. Just write a logline and continue without a break. +1. Just write logging output and continue without breaking the execution. 1. Annotate the specific error value even when you have a general error handler. 1. You want to handle the specific error value, let's say, at the same line or statement. @@ -229,6 +253,7 @@ actual errors we have functions like `try.Is` and even `try.IsEOF` for convenience. With these you can write code where error is translated to boolean value: + ```go notExist := try.Is(r2.err, plugin.ErrNotExist) @@ -245,23 +270,8 @@ notExist := try.Is(r2.err, plugin.ErrNotExist) > 3. Finally, it calls `try.To` for the non nil error, and we already know what then > happens: nearest `err2.Handle` gets it first. -These `try.Is` functions help cleanup mess idiomatic Go, i.e. mixing happy and -error path, leads to. - For more information see the examples in the documentation of both functions. -## Backwards Compatibility Promise for the API - -The `err2` package's API will be **backward compatible**. Before version -1.0.0 is released, the API changes occasionally, but **we promise to offer -automatic conversion scripts for your repos to update them for the latest API.** -We also mark functions deprecated before they become obsolete. Usually, one -released version before. We have tested this with a large code base in our -systems, and it works wonderfully. - -More information can be found in the `scripts/` directory [readme -file](./scripts/README.md). - ## Assertion The `assert` package is meant to be used for *design-by-contract-* type of @@ -282,17 +292,15 @@ assert.SetDefault(assert.Production) ``` If you want to suppress the caller info (source file name, line number, etc.) -and get just the plain panics from the asserts, you should set the -default asserter with the following line: +from certain asserts, you can do that per a goroutine or a function. You should +set the asserter with the following line for the current function: ```go -assert.SetDefault(assert.Debug) +defer assert.PushAsserter(assert.Plain)() ``` -For certain type of programs this is the best way. It allows us to keep all the -error messages as simple as possible. And by offering option to turn additional -information on, which allows super users and developers get more technical -information when needed. +This is especially good if you want to use assert functions for CLI's flag +validation or you want your app behave like legacy Go programs. > [!NOTE] > Since v0.9.5 you can set these asserters through Go's standard flag package @@ -305,10 +313,10 @@ Following is example of use of the assert package: ```go func marshalAttestedCredentialData(json []byte, data *protocol.AuthenticatorData) []byte { - assert.SLen(data.AttData.AAGUID, 16, "wrong AAGUID length") - assert.NotEmpty(data.AttData.CredentialID, "empty credential id") - assert.SNotEmpty(data.AttData.CredentialPublicKey, "empty credential public key") - ... + assert.SLen(data.AttData.AAGUID, 16, "wrong AAGUID length") + assert.NotEmpty(data.AttData.CredentialID, "empty credential id") + assert.SNotEmpty(data.AttData.CredentialPublicKey, "empty credential public key") + ... ``` We have now described design-by-contract for development and runtime use. What @@ -317,7 +325,11 @@ automatic testing as well. #### Assertion Package for Unit Testing -The same asserts can be used **and shared** during the unit tests: +The same asserts can be used **and shared** during the unit tests. + +
+The unit test code example: + ```go func TestWebOfTrustInfo(t *testing.T) { @@ -345,6 +357,8 @@ of the actual Test function, **it's reported as a standard test failure.** That means we don't need to open our internal pre- and post-conditions just for testing. +

+ **We can share the same assertions between runtime and test execution.** The err2 `assert` package integration to the Go `testing` package is completed at @@ -365,9 +379,12 @@ have an option to automatically support for err2 configuration flags through Go's standard `flag` package. See more information about err2 settings from [Error Stack Tracing](#error-stack-tracing) and [Asserters](#asserters). -Now you can always deploy your applications and services with the simple -end-user friendly error messages and no stack traces, **but you can switch them -on when ever you need**. +You can deploy your applications and services with the simple *end-user friendly +error messages and no stack traces.* + +
+You can switch them on when ever you need them again. +
Let's say you have build CLI (`your-app`) tool with the support for Go's flag package, and the app returns an error. Let's assume you're a developer. You can @@ -389,12 +406,17 @@ That adds more information to the assertion statement, which in default is in production (`Prod`) mode, i.e., outputs a single-line assertion message. All you need to do is to add `flag.Parse` to your `main` function. +
#### Support for Cobra Flags If you are using [cobra](https://github.com/spf13/cobra) you can still easily support packages like `err2` and `glog` and their flags. +
+Add cobra support: +
+ 1. Add std flag package to imports in `cmd/root.go`: ```go @@ -442,17 +464,28 @@ Flags: ... ``` +
+ ## Code Snippets -Most of the repetitive code blocks are offered as code snippets. They are in -`./snippets` in VC code format, which is well supported e.g. neovim, etc. +
+Most of the repetitive code blocks are offered as code snippets. +
+ +They are in `./snippets` in VC code format, which is well supported e.g. neovim, +etc. The snippets must be installed manually to your preferred IDE/editor. During the installation you can modify the according your style or add new ones. We would prefer if you could contribute some of the back to the err2 package. +
## Background +
+Why this repo exists? +
+ `err2` implements similar error handling mechanism as drafted in the original [check/handle proposal](https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md). @@ -492,14 +525,15 @@ help: background) to projects, - and most importantly, **it keeps your code more refactorable** because you don't have to repeat yourself. - -
-Learnings... +
## Learnings by so far -We have used the `err2` and `assert` packages in several projects. The results -have been so far very encouraging: +
+We have used the err2 and assert packages in several projects. +
+ +The results have been so far very encouraging: - If you forget to use handler, but you use checks from the package, you will get panics on errors (and optimized stack traces that can be suppressed). That @@ -517,7 +551,7 @@ been much easier.** There is an excellent [blog post](https://jesseduffield.com/ about the issues you are facing with Go's error handling without the help of the err2 package. -

+
## Support And Contributions From b8b33e442ecc65d0b6dc63c69820e5327a434e44 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 10 Oct 2024 19:15:12 +0300 Subject: [PATCH 69/78] continue combine readme docs --- README.md | 104 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index d3f05e7..54f8bdc 100644 --- a/README.md +++ b/README.md @@ -147,15 +147,17 @@ own purposes. #### Error Stack Tracing -The err2 offers optional stack tracing. It's automatic and optimized. Optimized -means that the call stack is processed before output. That means that stack -trace starts from where the actual error/panic is occurred, not where the error -is caught. You don't need to search for the line where the pointer was nil or -received an error. That line is in the first one you are seeing: +The err2 offers optional stack tracing. It's *automatic* and *optimized*.
The example of the optimized call stack: +Optimized means that the call stack is processed before output. That means that +stack trace *starts from where the actual error/panic is occurred*, not where +the error or panic is caught. You don't need to search for the line where the +pointer was nil or received an error. That line is in the first one you are +seeing: + ```sh --- runtime error: index out of range [0] with length 0 @@ -214,38 +216,52 @@ but not without an error handler (`err2.Handle`). However, you can put your error handlers where ever you want in your call stack. That can be handy in the internal packages and certain types of algorithms. +
+Immediate Error Handling Options +
+ In cases where you want to handle the error immediately after the function call -return you can use Go's default `if` statement. However, we encourage you to use -the `errdefer` concept, `defer err2.Handle(&err)` for all of your error -handling. +return you can use Go's default `if` statement. However, `defer err2.Handle(&err)` +for all of your error handling. Nevertheless, there might be cases where you might want to: 1. Suppress the error and use some default value. + ```go + b := try.Out1(strconv.Atoi(s)).Catch(100) + ``` 1. Just write logging output and continue without breaking the execution. + ```go + b := try.Out1(strconv.Atoi(s)).Logf("%s => 100", s) + ``` 1. Annotate the specific error value even when you have a general error handler. -1. You want to handle the specific error value, let's say, at the same line - or statement. - -The `err2/try` package offers other helpers based on the DSL concept -where the DSL's domain is error-handling. It's based on functions `try.Out`, -`try.Out1`, and `try.Out2`, which return instances of types `Result`, `Result1`, -and `Result2`. The `try.Result` is similar to other programming languages, -i.e., discriminated union. Please see more from its documentation. - -Now we could have the following: - -```go -b := try.Out1(strconv.Atoi(s)).Logf("%s => 100", s).Catch(100) -``` + You are already familiar with `try.To` functions. There's *fast* annotation + versions `try.T` which can be used as shown below: + ```go + b := try.T1(io.ReadAll(r))("cfg file read") + // where original were, for example: + b := try.To1(io.ReadAll(r)) + ``` +1. You want to handle the specific error value, let's say, at the same line or + statement. In below, the function `doSomething` returns an error value. If it + returns `ErrNotSoBad`, we just suppress it. All the other errors are send to + the current error handler and be handled there, but are annotated with + 'fatal' prefix before that. + ```go + try.Out(doSomething()).Handle(ErrNotSoBad, err2.Reset).Handle("fatal") + ``` -The previous statement tries to convert incoming string value `s`, but if it -doesn't succeed, it writes a warning to logs and uses the default value (100). -The logging result includes the original error message as well. +The `err2/try` package offers other helpers based on the error-handling +language/API. It's based on functions `try.Out`, `try.Out1`, and `try.Out2`, +which return instances of types `Result`, `Result1`, and `Result2`. The +`try.Result` is similar to other programming languages, i.e., discriminated +union. Please see more from its documentation. It's easy to see that panicking about the errors at the start of the development is far better than not checking errors at all. But most importantly, `err2/try` **keeps the code readable.** +
+ #### Filters for non-errors like io.EOF When error values are used to transport some other information instead of @@ -275,17 +291,31 @@ For more information see the examples in the documentation of both functions. ## Assertion The `assert` package is meant to be used for *design-by-contract-* type of -development where you set pre- and post-conditions for your functions. It's not -meant to replace the normal error checking but speed up the incremental hacking -cycle. The default mode is to return an `error` value that includes a formatted -and detailed assertion violation message. A developer gets immediate and proper -feedback, allowing cleanup of the code and APIs before the release. +development where you set pre- and post-conditions for *all* of your functions, +*including test functions*. These asserts are as fast as if-statements. + +
+Fast Clean Code +
+ +> [!IMPORTANT] +> It works *both runtime and for tests.* And even better, same asserts work in +> both running modes. + +Asserts are not meant to replace the normal error checking but speed up the +incremental hacking cycle like TDD. The default mode is to return an `error` +value that includes a formatted and detailed assertion violation message. A +developer gets immediate and proper feedback independently of the running mode, +allowing very fast feedback cycles. + +
#### Asserters The assert package offers a few pre-build *asserters*, which are used to -configure how the assert package deals with assert violations. The line below -exemplifies how the default asserter is set in the package. +configure *how the assert package deals with assert violations*. The line below +exemplifies how the default asserter is set in the package. (See the +documentation for more information about asserters.) ```go assert.SetDefault(assert.Production) @@ -325,7 +355,8 @@ automatic testing as well. #### Assertion Package for Unit Testing -The same asserts can be used **and shared** during the unit tests. +The same asserts can be used **and shared** during the unit tests over module +boundaries.
The unit test code example: @@ -555,9 +586,10 @@ the err2 package. ## Support And Contributions -The package has been in experimental mode quite long time. Since the Go generics -we are transiting towards more official mode. Currently we offer support by -GitHub Discussions. Naturally, any issues and contributions are welcome as well! +The package was in experimental mode quite long time. Since the Go generics we +did transit to official mode. Currently we offer support by GitHub Issues and +Discussions. Naturally, we appreciate all feedback and contributions are +very welcome! ## Roadmap From 555ef4c6e66b13d244439dc7bb731031a2164ce8 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 10 Oct 2024 19:49:04 +0300 Subject: [PATCH 70/78] fix important annotation placement --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 54f8bdc..c8b1420 100644 --- a/README.md +++ b/README.md @@ -294,14 +294,14 @@ The `assert` package is meant to be used for *design-by-contract-* type of development where you set pre- and post-conditions for *all* of your functions, *including test functions*. These asserts are as fast as if-statements. -
-Fast Clean Code -
- > [!IMPORTANT] > It works *both runtime and for tests.* And even better, same asserts work in > both running modes. +
+Fast Clean Code +
+ Asserts are not meant to replace the normal error checking but speed up the incremental hacking cycle like TDD. The default mode is to return an `error` value that includes a formatted and detailed assertion violation message. A From e5113f16b186a86b7d212b1acab70c3c8c8701d7 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 11 Oct 2024 12:45:25 +0300 Subject: [PATCH 71/78] better layouts & language proofing --- README.md | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c8b1420..d9b340b 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Simplest rule for err2 error handlers are: 1. Use `err2.handle` functions different calling schemes to achieve needed behaviour. For example, without no extra arguments `err2.Handle` automatically annotates your errors by building annotations string from the - function's current name: `doSomething → "do something"`. Default is decamel + function's current name: `doSomething → "do something:"`. Default is decamel and add spaces. See `err2.SetFormatter` for more information. 1. Every function which uses err2 for error-checking should have at least one error handler. The current function panics if there are no error handlers and @@ -139,7 +139,7 @@ Simplest rule for err2 error handlers are: See more information from `err2.Handle`'s documentation. It supports several error-handling scenarios. And remember that you can have as many error handlers -per function as you need, as well as you can chain error handling functions per +per function as you need. You can also chain error handling functions per `err2.Handle` that allows you to build new error handling middleware for your own purposes. @@ -151,6 +151,7 @@ The err2 offers optional stack tracing. It's *automatic* and *optimized*.
The example of the optimized call stack: +
Optimized means that the call stack is processed before output. That means that stack trace *starts from where the actual error/panic is occurred*, not where @@ -158,7 +159,7 @@ the error or panic is caught. You don't need to search for the line where the pointer was nil or received an error. That line is in the first one you are seeing: -```sh +``` --- runtime error: index out of range [0] with length 0 --- @@ -169,7 +170,7 @@ main.main() /home/.../go/src/github.com/lainio/ic/main.go:77 +0x248 ``` -

+
Just set the `err2.SetErrorTracer` or `err2.SetPanicTracer` to the stream you want traces to be written: @@ -185,8 +186,8 @@ the most cases proper error messages are enough and panics are handled immediately by a programmer. > [!NOTE] -> Since v0.9.5 you can set these asserters through Go's standard flag package -> just by adding `flag.Parse()` to your program. See more information from +> Since v0.9.5 you can set *tracers* through Go's standard flag package just by +> adding `flag.Parse()` call to your source code. See more information from > [Automatic Flags](#automatic-flags). [Read the package documentation for more @@ -221,17 +222,20 @@ internal packages and certain types of algorithms.
In cases where you want to handle the error immediately after the function call -return you can use Go's default `if` statement. However, `defer err2.Handle(&err)` -for all of your error handling. +you can use Go's default `if` statement. However, we recommend you to use +`defer err2.Handle(&err)` for all of your error handling, because it keeps your +code modifiable, refactorable, and skimmable. Nevertheless, there might be cases where you might want to: -1. Suppress the error and use some default value. +1. Suppress the error and use some default value. In next, use 100 if `Atoi` + fails: ```go b := try.Out1(strconv.Atoi(s)).Catch(100) ``` -1. Just write logging output and continue without breaking the execution. +1. Just write logging output and continue without breaking the execution. In + next, add log if `Atoi` fails. ```go - b := try.Out1(strconv.Atoi(s)).Logf("%s => 100", s) + b := try.Out1(strconv.Atoi(s)).Logf("%s => 100", s).Catch(100) ``` 1. Annotate the specific error value even when you have a general error handler. You are already familiar with `try.To` functions. There's *fast* annotation @@ -241,11 +245,11 @@ Nevertheless, there might be cases where you might want to: // where original were, for example: b := try.To1(io.ReadAll(r)) ``` -1. You want to handle the specific error value, let's say, at the same line or - statement. In below, the function `doSomething` returns an error value. If it - returns `ErrNotSoBad`, we just suppress it. All the other errors are send to - the current error handler and be handled there, but are annotated with - 'fatal' prefix before that. +1. You want to handle the specific error value at the same line or statement. In + below, the function `doSomething` returns an error value. If it returns + `ErrNotSoBad`, we just suppress it. All the other errors are send to the + current error handler and will be handled there, but are also annotated with + 'fatal' prefix before that here. ```go try.Out(doSomething()).Handle(ErrNotSoBad, err2.Reset).Handle("fatal") ``` @@ -388,7 +392,7 @@ of the actual Test function, **it's reported as a standard test failure.** That means we don't need to open our internal pre- and post-conditions just for testing. -

+
**We can share the same assertions between runtime and test execution.** From 11f6c9f2225778a5d4f9183fe058d6dfdd8c31ba Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 11 Oct 2024 12:53:40 +0300 Subject: [PATCH 72/78] PushAsserter impl stack with Pop function that's stroge, clever! --- assert/assert.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index ac2b133..b33c653 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -1065,14 +1065,32 @@ func SetDefault(i Asserter) (old Asserter) { // function: // // defer assert.PushAsserter(assert.Plain)() -func PushAsserter(i Asserter) func() { +func PushAsserter(i Asserter) (retFn function) { + var ( + prevFound bool + prevAsserter asserter + ) + // get pkg lvl asserter curAsserter := defAsserter[def] // .. to check if we are doing unit tests if !curAsserter.isUnitTesting() { - // .. allow TLS specific asserter. NOTE see current() + // .. allow GLS specific asserter. NOTE see current() curGoRID := goid() - asserterMap.Set(curGoRID, defAsserter[i]) + //asserterMap.Set(curGoRID, defAsserter[i]) + asserterMap.Tx(func(m map[int]asserter) { + cur, found := m[curGoRID] + if found { + prevAsserter = cur + prevFound = found + } + m[curGoRID] = defAsserter[i] + }) + } + if prevFound { + return func() { + asserterMap.Set(goid(), prevAsserter) + } } return PopAsserter } From 74cb6772375ba8784b1357f125a6545e57787512 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 11 Oct 2024 12:54:26 +0300 Subject: [PATCH 73/78] test PushAsserter as a real stack: Push/Pop --- samples/main.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/samples/main.go b/samples/main.go index 4e1a182..45926cb 100644 --- a/samples/main.go +++ b/samples/main.go @@ -42,12 +42,27 @@ func main() { case "play": doPlayMain() case "assert": - doAssertMain() + doAssertMain(false) + case "assert-keep": + doAssertMain(true) default: err2.Throwf("unknown (%v) playground given", *mode) } } -func doAssertMain() { +func doAssertMain(keep bool) { + asserterPusher(keep) + asserterTester() +} + +func asserterTester() { + //defer assert.PushAsserter(assert.Development)() assert.That(false) } + +func asserterPusher(keep bool) { + pop := assert.PushAsserter(assert.Debug) + if keep { + pop() + } +} From 6c624d8b6207e86e33c1be9f6f3de26b42ccdbb2 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 11 Oct 2024 13:05:40 +0300 Subject: [PATCH 74/78] more
--- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d9b340b..926b982 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ func CopyFile(src, dst string) (err error) { - The `err2` (main) package includes declarative error handling functions. - The `try` package offers error checking functions. - The `assert` package implements assertion helpers for **both** unit-testing - and *design-by-contract*. + and *design-by-contract* with the *same API and cross-usage*. ## Performance @@ -302,8 +302,10 @@ development where you set pre- and post-conditions for *all* of your functions, > It works *both runtime and for tests.* And even better, same asserts work in > both running modes. +#### Asserters +
-Fast Clean Code +Fast Clean Code with Asserters
Asserts are not meant to replace the normal error checking but speed up the @@ -312,10 +314,6 @@ value that includes a formatted and detailed assertion violation message. A developer gets immediate and proper feedback independently of the running mode, allowing very fast feedback cycles. -
- -#### Asserters - The assert package offers a few pre-build *asserters*, which are used to configure *how the assert package deals with assert violations*. The line below exemplifies how the default asserter is set in the package. (See the @@ -336,6 +334,8 @@ defer assert.PushAsserter(assert.Plain)() This is especially good if you want to use assert functions for CLI's flag validation or you want your app behave like legacy Go programs. +
+ > [!NOTE] > Since v0.9.5 you can set these asserters through Go's standard flag package > just by adding `flag.Parse()` to your program. See more information from From 7a823fb8c666189d11b283cbe64c200eb61920e3 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 11 Oct 2024 16:13:01 +0300 Subject: [PATCH 75/78] layout & typos --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 926b982..9469d13 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,10 @@ func CopyFile(src, dst string) (err error) { All of the listed above **without any performance penalty**! You are welcome to run `benchmarks` in the project repo and see yourself. -> [!IMPORTANT] +
+It's too fast! +
+ > Most of the benchmarks run 'too fast' according to the common Go > benchmarking rules, i.e., compiler optimizations > ([inlining](https://en.wikipedia.org/wiki/Inline_expansion)) are working so @@ -78,6 +81,8 @@ run `benchmarks` in the project repo and see yourself. > The whole package is written toward that goal. Especially with parametric > polymorphism, it's been quite the effort. +
+ ## Automatic Error Propagation Automatic error propagation is crucial because it makes your *code change @@ -218,7 +223,7 @@ error handlers where ever you want in your call stack. That can be handy in the internal packages and certain types of algorithms.
-Immediate Error Handling Options +Immediate Error Handling Options
In cases where you want to handle the error immediately after the function call @@ -305,7 +310,7 @@ development where you set pre- and post-conditions for *all* of your functions, #### Asserters
-Fast Clean Code with Asserters +Fast Clean Code with Asserters
Asserts are not meant to replace the normal error checking but speed up the @@ -365,7 +370,6 @@ boundaries.
The unit test code example: - ```go func TestWebOfTrustInfo(t *testing.T) { defer assert.PushTester(t)() @@ -418,7 +422,7 @@ You can deploy your applications and services with the simple *end-user friendly error messages and no stack traces.*
-You can switch them on when ever you need them again. +You can switch them on whenever you need them again.
Let's say you have build CLI (`your-app`) tool with the support for Go's flag @@ -504,11 +508,12 @@ Flags: ## Code Snippets
-Most of the repetitive code blocks are offered as code snippets. +Code snippets as learning helpers.
-They are in `./snippets` in VC code format, which is well supported e.g. neovim, -etc. +The snippets are in `./snippets` and in VC code format, which is well supported +e.g. neovim, etc. They are proven to be useful tool especially when you are +starting to use the err2 and its sub-packages. The snippets must be installed manually to your preferred IDE/editor. During the installation you can modify the according your style or add new ones. We would From 4640f4641ae0af361021a0843cfa6a467de83d27 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 11 Oct 2024 16:52:10 +0300 Subject: [PATCH 76/78] finalizing --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9469d13..1b03e33 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ func CopyFile(src, dst string) (err error) { - [Background](#background) - [Learnings by so far](#learnings-by-so-far) - [Support And Contributions](#support-and-contributions) -- [Roadmap](#roadmap) +- [History](#history) ## Structure @@ -301,7 +301,8 @@ For more information see the examples in the documentation of both functions. The `assert` package is meant to be used for *design-by-contract-* type of development where you set pre- and post-conditions for *all* of your functions, -*including test functions*. These asserts are as fast as if-statements. +*including test functions*. These asserts are as fast as if-statements when not +triggered. > [!IMPORTANT] > It works *both runtime and for tests.* And even better, same asserts work in @@ -600,7 +601,7 @@ did transit to official mode. Currently we offer support by GitHub Issues and Discussions. Naturally, we appreciate all feedback and contributions are very welcome! -## Roadmap +## History Please see the full version history from [CHANGELOG](./CHANGELOG.md). @@ -615,5 +616,5 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). - all assert messages follow Go idiom: `got, want` - `Asserter` can be set per goroutine: `PushAsserter` - `try` package: - - new check functions: `T`, `T1`, `T2`, `T3`, for quick refactoring to annotate an error locally + - new check functions: `T`, `T1`, `T2`, `T3`, for quick refactoring from `To` functions to annotate an error locally - **all functions are inline expansed**: if-statement equal performance From 041525cfd8865d8b0e0d4f69c8fd87d6e0cce2c4 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 11 Oct 2024 18:58:16 +0300 Subject: [PATCH 77/78] fix assert-keep mode --- samples/main.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/samples/main.go b/samples/main.go index 45926cb..bb0ab8b 100644 --- a/samples/main.go +++ b/samples/main.go @@ -13,7 +13,8 @@ var ( mode = flag.String( "mode", "play", - "runs the wanted playground: db, play, nil, assert", + "runs the wanted playground: db, play, nil, assert,"+ + "\nassert-keep (= uses assert.Debug in GLS)", ) isErr = flag.Bool("err", false, "tells if we want to have an error") ) @@ -42,15 +43,15 @@ func main() { case "play": doPlayMain() case "assert": - doAssertMain(false) + doAssertMainKeepGLSAsserter(false) case "assert-keep": - doAssertMain(true) + doAssertMainKeepGLSAsserter(true) default: err2.Throwf("unknown (%v) playground given", *mode) } } -func doAssertMain(keep bool) { +func doAssertMainKeepGLSAsserter(keep bool) { asserterPusher(keep) asserterTester() } @@ -62,7 +63,7 @@ func asserterTester() { func asserterPusher(keep bool) { pop := assert.PushAsserter(assert.Debug) - if keep { + if !keep { // if not keep we free pop() } } From 208c1579f1d7e9bac0cedd8afc52f9d94d2ba355 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 11 Oct 2024 19:04:57 +0300 Subject: [PATCH 78/78] logo branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b03e33..0565455 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![PkgGoDev](https://pkg.go.dev/badge/mod/github.com/lainio/err2)](https://pkg.go.dev/mod/github.com/lainio/err2) [![Go Report Card](https://goreportcard.com/badge/github.com/lainio/err2?style=flat-square)](https://goreportcard.com/report/github.com/lainio/err2) - + ----