From 39fc20fe5cd421527d9d162395bf741073cce3d0 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 26 Aug 2023 20:13:12 +0300 Subject: [PATCH 01/32] goreleaser needs --clean flag --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 446b766..27b3620 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -32,7 +32,7 @@ if [[ -z "$(git status --porcelain)" ]]; then git tag -a "$version" -m "v. $version" git push origin "$cur_branch" --tags GOPROXY=proxy.golang.org go list -m github.com/lainio/err2@"$version" - goreleaser release + goreleaser release --clean else echo 'ERROR: working dir is not clean' fi From f664e14f7898f7ab14f1a58d8d47b1838d31b543 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 31 Aug 2023 20:31:26 +0300 Subject: [PATCH 02/32] trying to find reference point for performance problem HeavyPtrPtr --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index d8e2af0..db1ed8d 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,9 @@ bench_that: bench_copy: $(GO) test $(TEST_ARGS) -bench='Benchmark_CopyBuffer' $(PKG_TRY) +bench_rech: + $(GO) test $(TEST_ARGS) -bench='BenchmarkRecursionWithTryAnd_HeavyPtrPtr_Defer' $(PKG_ERR2) + bench_rece: $(GO) test $(TEST_ARGS) -bench='BenchmarkRecursionWithTryAnd_Empty_Defer' $(PKG_ERR2) From 71ea75632a6fb6494f23d3ad687aaf72b065066e Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 31 Aug 2023 21:16:53 +0300 Subject: [PATCH 03/32] empty Catch default is logging now as try.Out().Logf() --- err2.go | 8 +++---- err2_test.go | 42 +++++++++++++++++++++++++++++++++++++ internal/handler/handler.go | 8 +++---- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/err2.go b/err2.go index c9cf1db..fe81e82 100644 --- a/err2.go +++ b/err2.go @@ -103,10 +103,10 @@ func Handle(err *error, a ...any) { // // The deferred Catch is very convenient, because it makes your current // goroutine panic and error-safe, one line only! You can fine tune its -// behavior with functions like err2.SetErrorTrace, assert.SetDefault, etc. -// Start with the defaults. +// behavior with functions like err2.SetErrorTrace, assert.SetDefault, and +// logging settings. Start with the defaults and simplest version of Catch: // -// defer err2.Catch() +// defer err2.Catch() // writes errors to logs and panic traces to stderr // // Catch support logging as well: // @@ -147,7 +147,7 @@ func Catch(a ...any) { err = handler.PreProcess(&err, &handler.Info{ CallerName: "Catch", Any: r, - NilHandler: handler.NilNoop, + // NilHandler: handler.NilNoop, // TODO: why this is here? }, a...) doTrace(err) } diff --git a/err2_test.go b/err2_test.go index b1540da..a13636d 100644 --- a/err2_test.go +++ b/err2_test.go @@ -1,9 +1,11 @@ package err2_test import ( + "errors" "fmt" "io" "os" + "runtime" "testing" "github.com/lainio/err2" @@ -898,6 +900,46 @@ func BenchmarkRecursionWithTryAnd_Empty_Defer(b *testing.B) { } } +func doWork(ePtr *error, r any) { + switch v := r.(type) { + case nil: + return + case runtime.Error: + *ePtr = errors.Join(v, *ePtr) + case error: + *ePtr = errors.Join(v, *ePtr) + default: + // panicing + } +} + +func BenchmarkRecursionWithTryAnd_HeavyPtrPtr_Defer(b *testing.B) { + var recursion func(a int) (r int, err error) + recursion = func(a int) (r int, err error) { + defer func(ePtr *error) { + r := recover() + nothingToDo := r == nil && (ePtr == nil || *ePtr == nil) + if nothingToDo { + return + } + doWork(ePtr, r) + }(&err) + + if a == 0 { + return 0, nil + } + s := try.To1(noThrow()) + _ = s + r = try.To1(recursion(a - 1)) + r += a + return r, nil + } + + for n := 0; n < b.N; n++ { + _, _ = recursion(100) + } +} + func BenchmarkRecursionWithTryAndDefer(b *testing.B) { var recursion func(a int) (r int, err error) recursion = func(a int) (r int, err error) { diff --git a/internal/handler/handler.go b/internal/handler/handler.go index a97df33..60b26cb 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -267,7 +267,7 @@ func PreProcess(errPtr *error, info *Info, a ...any) error { if len(a) > 0 { subProcess(info, a...) } else { - fnName := "Handle" + fnName := "Handle" // default if info.CallerName != "" { fnName = info.CallerName } @@ -292,8 +292,7 @@ func PreProcess(errPtr *error, info *Info, a ...any) error { Process(info) - logCatchCallMode := defCatchCallMode && firstArgIsString(a...) - if curErr := info.safeErr(); logCatchCallMode && curErr != nil { + if curErr := info.safeErr(); defCatchCallMode && curErr != nil { _, _, frame, ok := debug.FuncName(debug.StackInfo{ PackageName: "", FuncName: "Catch", @@ -308,7 +307,8 @@ func PreProcess(errPtr *error, info *Info, a ...any) error { return err } -func firstArgIsString(a ...any) bool { +// firstArgIsString not used any more. +func _(a ...any) bool { if len(a) > 0 { _, isStr := a[0].(string) return isStr From 7676ae2a3e4f701a189653ffbc0c24fd892a780c Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 24 Sep 2023 12:24:50 +0300 Subject: [PATCH 04/32] tracer flags auto flag with std lib's pkg --- err2_init.go | 13 +++++++++++++ internal/tracer/tracer.go | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 err2_init.go diff --git a/err2_init.go b/err2_init.go new file mode 100644 index 0000000..fa93c34 --- /dev/null +++ b/err2_init.go @@ -0,0 +1,13 @@ +package err2 + +import ( + "flag" + + "github.com/lainio/err2/internal/tracer" +) + +func init() { + flag.Var(&tracer.Log, "err2-log", "stream for logging") + flag.Var(&tracer.Error, "err2-trace", "stream for error tracing") + flag.Var(&tracer.Panic, "err2-panic-trace", "stream for panic tracing") +} diff --git a/internal/tracer/tracer.go b/internal/tracer/tracer.go index 0bfe1e5..c7342bc 100644 --- a/internal/tracer/tracer.go +++ b/internal/tracer/tracer.go @@ -5,6 +5,8 @@ import ( "io" "os" "sync/atomic" + + "github.com/lainio/err2/internal/x" ) type value struct { @@ -37,3 +39,22 @@ func (v *value) Tracer() io.Writer { func (v *value) SetTracer(w io.Writer) { v.Store(writer{w: w}) } + +// String is part of the flag interfaces +func (v *value) String() string { + return x.Whom(v.Tracer() != nil, "true", "false") +} + +// Get is part of the flag interfaces, getter. +func (v *value) Get() any { return nil } + +// Set is part of the flag.Value interface. +func (v *value) Set(value string) error { + switch value { + case "stderr": + v.SetTracer(os.Stderr) + case "stdout": + v.SetTracer(os.Stdout) + } + return nil +} From 15849e9585b944a962f2c7c8d8a7cde1f3dbf1a8 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 24 Sep 2023 15:02:28 +0300 Subject: [PATCH 05/32] asserter flag added --- assert/assert.go | 22 ++++++++++++++++++++++ err2_init.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/assert/assert.go b/assert/assert.go index 58772d8..c0279d0 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -97,6 +97,11 @@ var ( // These two are our indexing system for default asserter. Note also the // mutex blew. All of this is done to keep client package race detector // cool. + // Production + // Development + // Test + // TestFull + // Debug defAsserter = []Asserter{P, B, T, TF, D} def defInd @@ -536,6 +541,23 @@ func SetDefault(i defInd) Asserter { return defAsserter[i] } +// mapDefInd runtime asserters, that's why test asserts are removed for now. +var mapDefInd = map[string]defInd{ + "Production": Production, + "Development": Development, + //"Test": Test, + //"TestFull": TestFull, + "Debug": Debug, +} + +func NewDefInd(v string) defInd { + ind, found := mapDefInd[v] + if !found { + return Production + } + return ind +} + func combineArgs(format string, a []any) []any { args := make([]any, 1, len(a)+1) args[0] = format diff --git a/err2_init.go b/err2_init.go index fa93c34..78438f5 100644 --- a/err2_init.go +++ b/err2_init.go @@ -3,11 +3,42 @@ package err2 import ( "flag" + "github.com/lainio/err2/assert" "github.com/lainio/err2/internal/tracer" ) +var ( + // asserter def index flag has following values: + // + // Production + // Development + // Test + // TestFull + // Debug + asserterFlag flagAsserter +) + func init() { flag.Var(&tracer.Log, "err2-log", "stream for logging") flag.Var(&tracer.Error, "err2-trace", "stream for error tracing") flag.Var(&tracer.Panic, "err2-panic-trace", "stream for panic tracing") + flag.Var(&asserterFlag, "err2-asserter", "asserter: Production, Development, Debug") +} + +type flagAsserter struct { + v string +} + +// String is part of the flag interfaces +func (f *flagAsserter) String() string { + return f.v +} + +// Get is part of the flag interfaces, getter. +func (*flagAsserter) Get() any { return nil } + +// Set is part of the flag.Value interface. +func (f *flagAsserter) Set(flagAsserter string) error { + assert.SetDefault(assert.NewDefInd(flagAsserter)) + return nil } From e3e35e85f0d473e270dce2fa923053babc358199 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 25 Sep 2023 10:56:55 +0300 Subject: [PATCH 06/32] auto-flags: usages & functions --- assert/assert.go | 12 ++++++++++++ err2_init.go | 16 +++++++--------- internal/tracer/tracer.go | 4 +++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index c0279d0..d33b277 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -550,6 +550,18 @@ var mapDefInd = map[string]defInd{ "Debug": Debug, } +var MapDefIndToString = map[defInd]string{ + Production: "Production", + Development: "Development", + Test: "Test", + TestFull: "TestFull", + Debug: "Debug", +} + +func AsserterString() string { + return MapDefIndToString[def] +} + func NewDefInd(v string) defInd { ind, found := mapDefInd[v] if !found { diff --git a/err2_init.go b/err2_init.go index 78438f5..8e04625 100644 --- a/err2_init.go +++ b/err2_init.go @@ -19,26 +19,24 @@ var ( ) func init() { - flag.Var(&tracer.Log, "err2-log", "stream for logging") - flag.Var(&tracer.Error, "err2-trace", "stream for error tracing") + flag.Var(&tracer.Log, "err2-log", "stream for logging: nil -> log pkg") + flag.Var(&tracer.Error, "err2-trace", "stream for error tracing: stderr, stdout") flag.Var(&tracer.Panic, "err2-panic-trace", "stream for panic tracing") flag.Var(&asserterFlag, "err2-asserter", "asserter: Production, Development, Debug") } -type flagAsserter struct { - v string -} +type flagAsserter struct{} // String is part of the flag interfaces -func (f *flagAsserter) String() string { - return f.v +func (*flagAsserter) String() string { + return assert.AsserterString() } // Get is part of the flag interfaces, getter. func (*flagAsserter) Get() any { return nil } // Set is part of the flag.Value interface. -func (f *flagAsserter) Set(flagAsserter string) error { - assert.SetDefault(assert.NewDefInd(flagAsserter)) +func (*flagAsserter) Set(value string) error { + assert.SetDefault(assert.NewDefInd(value)) return nil } diff --git a/internal/tracer/tracer.go b/internal/tracer/tracer.go index c7342bc..2e788fa 100644 --- a/internal/tracer/tracer.go +++ b/internal/tracer/tracer.go @@ -42,7 +42,7 @@ func (v *value) SetTracer(w io.Writer) { // String is part of the flag interfaces func (v *value) String() string { - return x.Whom(v.Tracer() != nil, "true", "false") + return x.Whom(v.Tracer() != nil, "stderr", "nil") } // Get is part of the flag interfaces, getter. @@ -55,6 +55,8 @@ func (v *value) Set(value string) error { v.SetTracer(os.Stderr) case "stdout": v.SetTracer(os.Stdout) + default: + v.SetTracer(nil) } return nil } From 7e4bae2ce81bcf9457305e8af58091bf8cb1535b Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 25 Sep 2023 13:20:45 +0300 Subject: [PATCH 07/32] tracer flag pkg support to tracer implementation file --- err2_init.go | 4 ---- internal/tracer/tracer.go | 12 ++++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/err2_init.go b/err2_init.go index 8e04625..9e4f7ae 100644 --- a/err2_init.go +++ b/err2_init.go @@ -4,7 +4,6 @@ import ( "flag" "github.com/lainio/err2/assert" - "github.com/lainio/err2/internal/tracer" ) var ( @@ -19,9 +18,6 @@ var ( ) func init() { - flag.Var(&tracer.Log, "err2-log", "stream for logging: nil -> log pkg") - flag.Var(&tracer.Error, "err2-trace", "stream for error tracing: stderr, stdout") - flag.Var(&tracer.Panic, "err2-panic-trace", "stream for panic tracing") flag.Var(&asserterFlag, "err2-asserter", "asserter: Production, Development, Debug") } diff --git a/internal/tracer/tracer.go b/internal/tracer/tracer.go index 2e788fa..4d26cbb 100644 --- a/internal/tracer/tracer.go +++ b/internal/tracer/tracer.go @@ -2,6 +2,7 @@ package tracer import ( + "flag" "io" "os" "sync/atomic" @@ -30,10 +31,17 @@ func init() { // nil is a good default for try.Out().Logf() because then we use std log. 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(&Panic, "err2-panic-trace", "stream for panic tracing") } func (v *value) Tracer() io.Writer { - return v.Load().(writer).w + if w, ok := v.Load().(writer); ok { + return w.w + } + return nil } func (v *value) SetTracer(w io.Writer) { @@ -55,7 +63,7 @@ func (v *value) Set(value string) error { v.SetTracer(os.Stderr) case "stdout": v.SetTracer(os.Stdout) - default: + case "nil": v.SetTracer(nil) } return nil From c46ab6e8b2bc6199ff00ec67d4c77d0f5e3a8bed Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 25 Sep 2023 17:01:44 +0300 Subject: [PATCH 08/32] assert has own cmd flag --- assert/assert.go | 51 ++++++++++++++++++++++++++++++++++++++---------- err2_init.go | 38 ------------------------------------ 2 files changed, 41 insertions(+), 48 deletions(-) delete mode 100644 err2_init.go diff --git a/assert/assert.go b/assert/assert.go index d33b277..7b4f137 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -1,6 +1,7 @@ package assert import ( + "flag" "fmt" "os" "reflect" @@ -16,13 +17,17 @@ import ( type defInd = uint32 const ( + // Plain converts asserts just plain error messages without extra + // information. + Plain defInd = 0 + iota + // Production (pkg default) is the best asserter for most uses. The // assertion violations are treated as Go error values. And only a // pragmatic caller info is automatically included into the error message // like file name, line number, and caller function, all in one line: // // copy file: main.go:37: CopyFile(): assertion violation: string shouldn't be empty - Production defInd = 0 + iota + Production // Development is the best asserter for most development uses. The // assertion violations are treated as Go error values. And a formatted @@ -76,8 +81,12 @@ const ( Debug ) +type flagAsserter struct{} + // Deprecated: use e.g. assert.That(), only default asserter is used. var ( + PL = AsserterToError + // P is a production Asserter that sets panic objects to errors which // allows err2 handlers to catch them. P = AsserterToError | AsserterCallerInfo @@ -97,12 +106,13 @@ var ( // These two are our indexing system for default asserter. Note also the // mutex blew. All of this is done to keep client package race detector // cool. + // Plain // Production // Development // Test // TestFull // Debug - defAsserter = []Asserter{P, B, T, TF, D} + defAsserter = []Asserter{PL, P, B, T, TF, D} def defInd // mu is package lvl Mutex that is used to cool down race detector of @@ -112,10 +122,13 @@ var ( // indexing system we are using for default asserter (above) we are pretty // much theard safe. mu sync.Mutex + + asserterFlag flagAsserter ) func init() { SetDefault(Production) + flag.Var(&asserterFlag, "asserter", "`asserter`: Plain, Prod, Dev, Debug") } type ( @@ -543,29 +556,31 @@ func SetDefault(i defInd) Asserter { // mapDefInd runtime asserters, that's why test asserts are removed for now. var mapDefInd = map[string]defInd{ - "Production": Production, - "Development": Development, + "Plain": Plain, + "Prod": Production, + "Dev": Development, //"Test": Test, //"TestFull": TestFull, "Debug": Debug, } -var MapDefIndToString = map[defInd]string{ - Production: "Production", - Development: "Development", +var mapDefIndToString = map[defInd]string{ + Plain: "Plain", + Production: "Prod", + Development: "Dev", Test: "Test", TestFull: "TestFull", Debug: "Debug", } func AsserterString() string { - return MapDefIndToString[def] + return mapDefIndToString[def] } -func NewDefInd(v string) defInd { +func newDefInd(v string) defInd { ind, found := mapDefInd[v] if !found { - return Production + return Plain } return ind } @@ -601,3 +616,19 @@ func myByteToInt(b []byte) int { type Number interface { constraints.Float | constraints.Integer } + +// String is part of the flag interfaces +func (f *flagAsserter) String() string { + return AsserterString() +} + +// Get is part of the flag interfaces, getter. +func (f *flagAsserter) Get() any { + return mapDefIndToString[def] +} + +// Set is part of the flag.Value interface. +func (*flagAsserter) Set(value string) error { + SetDefault(newDefInd(value)) + return nil +} diff --git a/err2_init.go b/err2_init.go deleted file mode 100644 index 9e4f7ae..0000000 --- a/err2_init.go +++ /dev/null @@ -1,38 +0,0 @@ -package err2 - -import ( - "flag" - - "github.com/lainio/err2/assert" -) - -var ( - // asserter def index flag has following values: - // - // Production - // Development - // Test - // TestFull - // Debug - asserterFlag flagAsserter -) - -func init() { - flag.Var(&asserterFlag, "err2-asserter", "asserter: Production, Development, Debug") -} - -type flagAsserter struct{} - -// String is part of the flag interfaces -func (*flagAsserter) String() string { - return assert.AsserterString() -} - -// Get is part of the flag interfaces, getter. -func (*flagAsserter) Get() any { return nil } - -// Set is part of the flag.Value interface. -func (*flagAsserter) Set(value string) error { - assert.SetDefault(assert.NewDefInd(value)) - return nil -} From 29ce2509405a3a18a42c44e0b933bf90662e9761 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 25 Sep 2023 17:02:15 +0300 Subject: [PATCH 09/32] tracer flag type is 'stream' & Get implemented --- internal/tracer/tracer.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/tracer/tracer.go b/internal/tracer/tracer.go index 4d26cbb..1663c24 100644 --- a/internal/tracer/tracer.go +++ b/internal/tracer/tracer.go @@ -32,9 +32,9 @@ func init() { // nil is a good default for try.Out().Logf() because then we use std log. 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(&Panic, "err2-panic-trace", "stream for panic tracing") + flag.Var(&Log, "err2-log", "`stream` for logging: nil -> log pkg") + flag.Var(&Error, "err2-trace", "`stream` for error tracing: stderr, stdout") + flag.Var(&Panic, "err2-panic-trace", "`stream` for panic tracing") } func (v *value) Tracer() io.Writer { @@ -50,11 +50,16 @@ func (v *value) SetTracer(w io.Writer) { // String is part of the flag interfaces func (v *value) String() string { + if v == nil { + return "null" + } return x.Whom(v.Tracer() != nil, "stderr", "nil") } // Get is part of the flag interfaces, getter. -func (v *value) Get() any { return nil } +func (v *value) Get() any { + return v.Tracer() +} // Set is part of the flag.Value interface. func (v *value) Set(value string) error { From bafa2774dad730afaef564ab871a5d1e3b21684e Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 25 Sep 2023 18:24:30 +0300 Subject: [PATCH 10/32] rm errors.Join --- err2_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/err2_test.go b/err2_test.go index a13636d..38a5c33 100644 --- a/err2_test.go +++ b/err2_test.go @@ -1,7 +1,6 @@ package err2_test import ( - "errors" "fmt" "io" "os" @@ -905,9 +904,9 @@ func doWork(ePtr *error, r any) { case nil: return case runtime.Error: - *ePtr = errors.Join(v, *ePtr) + *ePtr = fmt.Errorf("%v: %w", *ePtr, v) case error: - *ePtr = errors.Join(v, *ePtr) + *ePtr = fmt.Errorf("%v: %w", *ePtr, v) default: // panicing } From 3fdc6978e5dd315ed4e8a7177dd63c39e596c80c Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Tue, 3 Oct 2023 17:09:59 +0300 Subject: [PATCH 11/32] -benchmem is default test args --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index db1ed8d..db3c80b 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ PKGS := $(PKG_ERR2) $(PKG_ASSERT) $(PKG_TRY) $(PKG_DEBUG) $(PKG_HANDLER) $(PKG_S SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) GO ?= go -TEST_ARGS ?= +TEST_ARGS ?= -benchmem # GO ?= go1.20rc2 From 6df73f326ce6a8146913badc5e3ee92a10eb3358 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 4 Oct 2023 18:54:41 +0300 Subject: [PATCH 12/32] escape analysis for err2 --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index db3c80b..61580b0 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,9 @@ testv: test: $(GO) test $(TEST_ARGS) $(PKGS) +escape_err2: + $(GO) test -c -gcflags=-m=2 $(PKG_ERR2) 2>&1 | ag 'escape' + inline_err2: $(GO) test -c -gcflags=-m=2 $(PKG_ERR2) 2>&1 | ag 'inlin' From fd677451521cae6e211a9d10ecbcffa240a44b8c Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 4 Oct 2023 18:55:59 +0300 Subject: [PATCH 13/32] reference benchmark of goroutine ID parsing --- assert/goid_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/assert/goid_test.go b/assert/goid_test.go index 2e53415..2691f73 100644 --- a/assert/goid_test.go +++ b/assert/goid_test.go @@ -1,6 +1,8 @@ package assert import ( + "bytes" + "fmt" "testing" ) @@ -15,6 +17,17 @@ func TestGoid(t *testing.T) { } } +func Test_oldGoid(t *testing.T) { + t.Parallel() + stackBytes := []byte(`goroutine 518 [running]: +`) + + id := oldGoid(stackBytes) + if id != 518 { + t.Fail() + } +} + func BenchmarkGoid(b *testing.B) { _ = []byte(`goroutine 518 [running]: `) @@ -32,3 +45,20 @@ func BenchmarkGoid_MyByteToInt(b *testing.B) { _ = myByteToInt(stackBytes[10:]) } } + +func oldGoid(buf []byte) (id int) { + _, err := fmt.Fscanf(bytes.NewReader(buf), "goroutine %d", &id) + if err != nil { + panic("cannot get goroutine id: " + err.Error()) + } + return id +} + +func BenchmarkGoid_Old(b *testing.B) { + stackBytes := []byte(`goroutine 518 [running]: +`) + + for n := 0; n < b.N; n++ { + _ = oldGoid(stackBytes) + } +} From 092183abdc2b8b3ec7d33a2f2471714af8ddf41d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 4 Oct 2023 18:57:50 +0300 Subject: [PATCH 14/32] reference benchmark for regexp decamel --- internal/str/str.go | 11 +++++++---- internal/str/str_test.go | 27 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/internal/str/str.go b/internal/str/str.go index 6950aaa..183b904 100644 --- a/internal/str/str.go +++ b/internal/str/str.go @@ -11,14 +11,17 @@ import ( ) var ( - re = regexp.MustCompile(`([A-Z]+)`) + uncamel = regexp.MustCompile(`([A-Z]+)`) + clean = regexp.MustCompile(`[^\w]`) ) -// CamelRegexp return the given string as space delimeted. Note! it's slow. Use +// DecamelRegexp return the given string as space delimeted. Note! it's slow. Use // Decamel instead. -func CamelRegexp(str string) string { - str = re.ReplaceAllString(str, ` $1`) +func DecamelRegexp(str string) string { + str = clean.ReplaceAllString(str, " ") + str = uncamel.ReplaceAllString(str, ` $1`) str = strings.Trim(str, " ") + str = strings.ToLower(str) return str } diff --git a/internal/str/str_test.go b/internal/str/str_test.go index dbb13c6..14dfe3e 100644 --- a/internal/str/str_test.go +++ b/internal/str/str_test.go @@ -13,7 +13,7 @@ const ( func BenchmarkCamelRegexp(b *testing.B) { for n := 0; n < b.N; n++ { - _ = str.CamelRegexp(camelStr) + _ = str.DecamelRegexp(camelStr) } } @@ -23,6 +23,31 @@ func BenchmarkDecamel(b *testing.B) { } } +func TestCamel(t *testing.T) { + t.Parallel() + type args struct { + s string + } + tests := []struct { + name string + args args + want string + }{ + {"simple", args{"CamelString"}, "camel string"}, + {"number", args{"CamelString2Testing"}, "camel string2 testing"}, + {"acronym", args{"ARMCamelString"}, "armcamel string"}, + {"acronym at end", args{"archIsARM"}, "arch is arm"}, + } + for _, ttv := range tests { + tt := ttv + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := str.DecamelRegexp(tt.args.s) + test.RequireEqual(t, got, tt.want) + }) + } +} + func TestDecamel(t *testing.T) { t.Parallel() type args struct { From bc6a48be9998dd1321d5a396cd15be4717f5a2e3 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 4 Oct 2023 19:26:16 +0300 Subject: [PATCH 15/32] renamed CamelRegexp -> DecamelRegexp --- internal/str/str_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/str/str_test.go b/internal/str/str_test.go index 14dfe3e..7e16b35 100644 --- a/internal/str/str_test.go +++ b/internal/str/str_test.go @@ -11,7 +11,7 @@ const ( camelStr = "BenchmarkRecursionWithOldErrorIfCheckAnd_Defer" ) -func BenchmarkCamelRegexp(b *testing.B) { +func BenchmarkDecamelRegexp(b *testing.B) { for n := 0; n < b.N; n++ { _ = str.DecamelRegexp(camelStr) } From f31a6af62265604f9b148cf1d6af63b9aef22a91 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Wed, 4 Oct 2023 19:31:15 +0300 Subject: [PATCH 16/32] refactor asciiWordToInt --- assert/assert.go | 4 ++-- assert/goid_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 7b4f137..0e5e83c 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -595,10 +595,10 @@ func combineArgs(format string, a []any) []any { func goid() int { var buf [64]byte runtime.Stack(buf[:], false) - return myByteToInt(buf[10:]) + return asciiWordToInt(buf[10:]) } -func myByteToInt(b []byte) int { +func asciiWordToInt(b []byte) int { n := 0 for _, ch := range b { if ch == ' ' { diff --git a/assert/goid_test.go b/assert/goid_test.go index 2691f73..9aa154f 100644 --- a/assert/goid_test.go +++ b/assert/goid_test.go @@ -11,7 +11,7 @@ func TestGoid(t *testing.T) { stackBytes := []byte(`goroutine 518 [running]: `) - id := myByteToInt(stackBytes[10:]) + id := asciiWordToInt(stackBytes[10:]) if id != 518 { t.Fail() } @@ -42,7 +42,7 @@ func BenchmarkGoid_MyByteToInt(b *testing.B) { `) for n := 0; n < b.N; n++ { - _ = myByteToInt(stackBytes[10:]) + _ = asciiWordToInt(stackBytes[10:]) } } From 8dc46d6f775c4f3c661a11b46fbdd2b47acc474d Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 8 Oct 2023 20:21:02 +0300 Subject: [PATCH 17/32] optimization disable comment --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 61580b0..73da499 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) GO ?= go TEST_ARGS ?= -benchmem +# -"gcflags '-N -l'" both optimization & inlining disabled # GO ?= go1.20rc2 From 79bc524bdfefc57fc2db8b13f51037048fa4dc48 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 13 Oct 2023 20:46:47 +0300 Subject: [PATCH 18/32] todo removed because it_s handled by handler.Info --- err2.go | 1 - 1 file changed, 1 deletion(-) diff --git a/err2.go b/err2.go index fe81e82..b76df42 100644 --- a/err2.go +++ b/err2.go @@ -147,7 +147,6 @@ func Catch(a ...any) { err = handler.PreProcess(&err, &handler.Info{ CallerName: "Catch", Any: r, - // NilHandler: handler.NilNoop, // TODO: why this is here? }, a...) doTrace(err) } From 3839dbce6cba9f733d8039800ed3f498d5b57629 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 13 Oct 2023 20:49:04 +0300 Subject: [PATCH 19/32] better skimmable --- internal/formatter/formatter.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/formatter/formatter.go b/internal/formatter/formatter.go index 203754e..35f3897 100644 --- a/internal/formatter/formatter.go +++ b/internal/formatter/formatter.go @@ -16,8 +16,8 @@ func SetFormatter(fmter format.Interface) { } func Formatter() format.Interface { - fmter, ok := formatter.Load().(format.Interface) - if ok { + fmter, isInterface := formatter.Load().(format.Interface) + if isInterface { return fmter } return nil From 54821d12dd929aea30739e915a4d1967057fc426 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 15 Oct 2023 13:15:44 +0300 Subject: [PATCH 20/32] rm TODOs --- internal/handler/handler.go | 6 +++--- samples/main-db-sample.go | 4 ++-- scripts/migrate.sh | 2 +- scripts/release-readme.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 60b26cb..8a3c295 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -277,9 +277,9 @@ func PreProcess(errPtr *error, info *Info, a ...any) error { Level: lvl, }) if ok { - fmter := fmtstore.Formatter() - if fmter != nil { // TODO: check the init order! - info.Format = fmter.Format(funcName) + setFmter := fmtstore.Formatter() + if setFmter != nil { + info.Format = setFmter.Format(funcName) } else { info.Format = str.Decamel(funcName) } diff --git a/samples/main-db-sample.go b/samples/main-db-sample.go index 07b5abc..ed713e6 100644 --- a/samples/main-db-sample.go +++ b/samples/main-db-sample.go @@ -43,14 +43,14 @@ func doDBMain() { db, from, to := new(Database), new(Account), new(Account) - // --- TODO: play with these lines to simulate different errors: + // --- play with these lines to simulate different errors: db.errRoll = errRollback //db.err = errBegin // tx fails from.balance = 1100 // no enough funds from.errWithdraw = errWithdraw // withdraw error to.errDeposit = errDeposit // deposit error amount := 100 // no enough funds - // --- TODO: simulation variables end + // --- simulation variables end try.To(db.MoneyTransfer(from, to, amount)) diff --git a/scripts/migrate.sh b/scripts/migrate.sh index c71f8cd..15ae2a5 100755 --- a/scripts/migrate.sh +++ b/scripts/migrate.sh @@ -128,7 +128,7 @@ echo "=================================" echo if [[ $bads != "" ]]; then - echo "====== TODO Summary ====" >&2 + echo "====== Summary ====" >&2 echo "Please check the following files before commit:" >&2 echo "" >&2 echo "$bads" | tr " " "\n" >&2 diff --git a/scripts/release-readme.md b/scripts/release-readme.md index 7d9e1a0..837ae04 100644 --- a/scripts/release-readme.md +++ b/scripts/release-readme.md @@ -7,5 +7,5 @@ Run the `release.sh` script: ``` Note! The version string format is 'v0.8.0'. Don't forget the v at the -beginning. TODO: update the script. +beginning. From 88baeb71885552bcc3564a08d497ef7bd37d37b7 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 15 Oct 2023 13:16:23 +0300 Subject: [PATCH 21/32] refactoring for readability for benchmark --- assert/assert_test.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/assert/assert_test.go b/assert/assert_test.go index 3bc16d4..a1b0624 100644 --- a/assert/assert_test.go +++ b/assert/assert_test.go @@ -152,15 +152,6 @@ func ExampleZero() { // Output: sample: assert_test.go:146: ExampleZero.func1(): assertion violation: got '1', want (== '0') } -// ifPanicZero in needed that we have argument here! It's like a macro for -// benchmarking. The others aren't needed below. TODO: refactor unneeded -// helpers. -func ifPanicZero(i int) { - if i == 0 { - panic("i == 0") - } -} - func assertZero(i int) { assert.Zero(i) } @@ -218,6 +209,12 @@ func BenchmarkEqual(b *testing.B) { } func BenchmarkAsserter_TrueIfVersion(b *testing.B) { + ifPanicZero := func(i int) { + if i == 0 { + panic("i == 0") + } + } + for n := 0; n < b.N; n++ { ifPanicZero(4) } From 145498506d99578549ccb94afa51ddc466232268 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 15 Oct 2023 13:16:56 +0300 Subject: [PATCH 22/32] Catch() documentation for logging --- err2.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/err2.go b/err2.go index b76df42..2e9c53e 100644 --- a/err2.go +++ b/err2.go @@ -106,9 +106,10 @@ func Handle(err *error, a ...any) { // behavior with functions like err2.SetErrorTrace, assert.SetDefault, and // logging settings. Start with the defaults and simplest version of Catch: // -// defer err2.Catch() // writes errors to logs and panic traces to stderr +// defer err2.Catch() // -// Catch support logging as well: +// In default the above writes errors to logs and panic traces to stderr. +// Naturally, you can annotate logging: // // defer err2.Catch("WARNING: caught errors: %s", name) // From a351035ca273b054efee0d7ac04b65e7aa963974 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 15 Oct 2023 13:17:32 +0300 Subject: [PATCH 23/32] check tag format, todo: check tags greater than last --- scripts/release.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/release.sh b/scripts/release.sh index 27b3620..28778e2 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -15,6 +15,15 @@ check_prerequisites() { echo "ERROR: give version number, e.g. v0.8.2" exit 1 fi + + if [[ $1 =~ ^v[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,3}$ ]]; then + echo "version string format is CORRECT" + else + echo "version string format ins't correct" + exit 1 + fi +exit 0 + } check_prerequisites $1 From 268fcbef71b0fd8a14f35350edde14b0b420c315 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Thu, 19 Oct 2023 15:27:49 +0300 Subject: [PATCH 24/32] more checks in release script --- scripts/release.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 28778e2..dd9eea0 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -17,7 +17,16 @@ check_prerequisites() { fi if [[ $1 =~ ^v[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,3}$ ]]; then - echo "version string format is CORRECT" + # echo "version string format is CORRECT" + local last_tag=`git tag -l | sort | tail -1` + if [[ "$last_tag" == "$1" ]]; then + echo "tag $last_tag already exists" + exit 1 + fi + if [[ "$last_tag" > "$1" ]]; then + echo "greater tag $last_tag already exists" + exit 1 + fi else echo "version string format ins't correct" exit 1 From 162879b7b33e06b69398168bbcaf5a0a4984d461 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 20 Oct 2023 17:25:24 +0300 Subject: [PATCH 25/32] readme & changelog update for next release --- CHANGELOG.md | 36 +++++++++++++++ README.md | 128 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 125 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b471d..522198f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,42 @@ ### Version history +##### 0.9.41 +- Issue #18: **bug fixed**: noerr-handler had to be the last one of the err2 + handlers + +##### 0.9.40 +- Significant performance boost for: `defer err2.Handle/Catch()` + - **3x faster happy path than the previous version, which is now equal to + simplest `defer` function in the `err`-returning function** . (Please see + the `defer` benchmarks in the `err2_test.go` and run `make bench_reca`) + - the solution caused a change to API, where the core reason is Go's + optimization "bug". (We don't have confirmation yet.) +- Changed API for deferred error handling: `defer err2.Handle/Catch()` + - *Obsolete*: + ```go + defer err2.Handle(&err, func() {}) // <- relaying closure to access err val + ``` + - Current version: + ```go + defer err2.Handle(&err, func(err error) error { return err }) // not a closure + ``` + Because handler function is not relaying closures any more, it opens a new + opportunity to use and build general helper functions: `err2.Noop`, etc. + - Use auto-migration scripts especially for large code-bases. More information + can be found in the `scripts/` directory's [readme file](./scripts/README.md). + - Added a new (*experimental*) API: + ```go + defer err2.Handle(&err, func(noerr bool) { + assert.That(noerr) // noerr is always true!! + doSomething() + }) + ``` + This is experimental because we aren't sure if this is something we want to + have in the `err2` package. +- Bug fixes: `ResultX.Logf()` now works as it should +- More documentation + ##### 0.9.29 - New API for immediate error handling: `try out handle/catch err` `val := try.Out1strconv.Atois.Catch(10)` diff --git a/README.md b/README.md index 14457c3..6af2877 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ func CopyFile(src, dst string) (err error) { - [Assertion](#assertion) - [Assertion Package for Runtime Use](#assertion-package-for-runtime-use) - [Assertion Package for Unit Testing](#assertion-package-for-unit-testing) +- [Automatic Flags](#automatic-flags) + - [Support for Cobra Flags](support-for-cobra-flags) - [Code Snippets](#code-snippets) - [Background](#background) - [Learnings by so far](#learnings-by-so-far) @@ -346,6 +348,87 @@ can be the same or different modules. execution, we will find it and can even move thru every step in the call stack.** +## Automatic Flags + +When you are using `err2` or `assert` packages, i.e., just importing them, you +have an option to automatically add support for flags. + +Let's say you have build CLI tool and it returns an error. You can run it again +with: + +``` +your-app -err2-trace stderr +``` + +Now you get full error trace addition to the error message. Naturally, this +also works asserts, which you can configure also with the flags: + +``` +your-app -assert Debug +``` + +That adds more information to the assertion statement, which in default is in +production (`Prod`) mode, i.e., K&D error 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. + +1. Add std flag package to imports in `cmd/root.go`: + + ```go + import ( + goflag "flag" + ... + ) + ``` + +1. Add the following to (usually) `cmd/root.go`'s `init` function's end: + + ```go + func init() { + ... + // NOTE! Very important. Adds support for std flag pkg users: glog, err2 + pflag.CommandLine.AddGoFlagSet(goflag.CommandLine) + } + ``` + +1. And finally modify your `PersistentPreRunE` in `cmd/root.go` to something + like: + + ```go + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + defer err2.Handle(&err) + + // NOTE! Very important. Adds support for std flag pkg users: glog, err2 + goflag.Parse() + + try.To(goflag.Set("logtostderr", "true")) + handleViperFlags(cmd) // local helper with envs + glog.CopyStandardLogTo("ERROR") // for err2 + return nil + }, + ``` + +As a result you can have bunch of usable flags added to your CLI: + +``` +Flags: + --alsologtostderr log to standard error as well as files + --asserter asserter asserter: Plain, Prod, Dev, Debug (default Prod) + --config string configuration file, FCLI_CONFIG + -n, --dry-run perform a trial run with no changes made, FCLI_DRY_RUN + --err2-log stream stream for logging: nil -> log pkg (default nil) + --err2-panic-trace stream stream for panic tracing (default stderr) + --err2-trace stream stream for error tracing: stderr, stdout (default nil) + ... +``` + +And many others form `glog` in this specific example case. + ## Code Snippets Most of the repetitive code blocks are offered as code snippets. They are in @@ -430,45 +513,12 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). ### Latest Release -##### 0.9.41 -- Issue #18: **bug fixed**: noerr-handler had to be the last one of the err2 - handlers - -##### 0.9.40 -- Significant performance boost for: `defer err2.Handle/Catch()` - - **3x faster happy path than the previous version, which is now equal to - simplest `defer` function in the `err`-returning function** . (Please see - the `defer` benchmarks in the `err2_test.go` and run `make bench_reca`) - - the solution caused a change to API, where the core reason is Go's - optimization "bug". (We don't have confirmation yet.) -- Changed API for deferred error handling: `defer err2.Handle/Catch()` - - *Obsolete*: - ```go - defer err2.Handle(&err, func() {}) // <- relaying closure to access err val - ``` - - Current version: - ```go - defer err2.Handle(&err, func(err error) error { return err }) // not a closure - ``` - Because handler function is not relaying closures any more, it opens a new - opportunity to use and build general helper functions: `err2.Noop`, etc. - - Use auto-migration scripts especially for large code-bases. More information - can be found in the `scripts/` directory's [readme file](./scripts/README.md). - - Added a new (*experimental*) API: - ```go - defer err2.Handle(&err, func(noerr bool) { - assert.That(noerr) // noerr is always true!! - doSomething() - }) - ``` - This is experimental because we aren't sure if this is something we want to - have in the `err2` package. -- Bug fixes: `ResultX.Logf()` now works as it should -- More documentation +##### 0.9.5 +- `flag` package integration: ### Upcoming releases -##### 0.9.5 -- Idea: Go's standard lib's flag pkg integration (similar to `glog`) -- Continue removing unused parts from `assert` pkg -- More documentation, repairing for some sort of marketing +##### 0.9.6 +- Idea: TODO +- Continue removing unused parts +- More documentation From b9943044b017fcd9402b8d8a633ddeea8d38c5c2 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Fri, 20 Oct 2023 18:17:32 +0300 Subject: [PATCH 26/32] linter, use same assert fmt string --- assert/assert.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/assert/assert.go b/assert/assert.go index 0e5e83c..0e90dc8 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -143,6 +143,7 @@ var ( const ( assertionMsg = "assertion violation" + gotWantFmt = ": got '%v', want '%v'" ) // PushTester sets the current testing context for default asserter. This must @@ -356,7 +357,7 @@ func NotEqual[T comparable](val, want T, a ...any) { // Asserter) with the given message. func Equal[T comparable](val, want T, a ...any) { if want != val { - defMsg := fmt.Sprintf(assertionMsg+": got '%v', want '%v'", val, want) + defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) Default().reportAssertionFault(defMsg, a...) } } @@ -365,7 +366,7 @@ func Equal[T comparable](val, want T, a ...any) { // panics/errors (current Asserter) with the given message. func DeepEqual(val, want any, a ...any) { if !reflect.DeepEqual(val, want) { - defMsg := fmt.Sprintf(assertionMsg+": got '%v', want '%v'", val, want) + defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, val, want) Default().reportAssertionFault(defMsg, a...) } } @@ -390,7 +391,7 @@ func Len(obj string, length int, a ...any) { l := len(obj) if l != length { - defMsg := fmt.Sprintf(assertionMsg+": got '%d', want '%d'", l, length) + defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) Default().reportAssertionFault(defMsg, a...) } } @@ -403,7 +404,7 @@ func SLen[S ~[]T, T any](obj S, length int, a ...any) { l := len(obj) if l != length { - defMsg := fmt.Sprintf(assertionMsg+": got '%d', want '%d'", l, length) + defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) Default().reportAssertionFault(defMsg, a...) } } @@ -416,7 +417,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+": got '%d', want '%d'", l, length) + defMsg := fmt.Sprintf(assertionMsg+gotWantFmt, l, length) Default().reportAssertionFault(defMsg, a...) } } From 78d74520b2afc67a92796b2ec9321ef64c584ab8 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 21 Oct 2023 14:56:53 +0300 Subject: [PATCH 27/32] flags section formations and language --- README.md | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 6af2877..638022e 100644 --- a/README.md +++ b/README.md @@ -351,24 +351,26 @@ stack.** ## Automatic Flags When you are using `err2` or `assert` packages, i.e., just importing them, you -have an option to automatically add support for flags. +have an option to automatically add support for Go's standard `flag` package. -Let's say you have build CLI tool and it returns an error. You can run it again -with: +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 +run it again with: ``` your-app -err2-trace stderr ``` Now you get full error trace addition to the error message. Naturally, this -also works asserts, which you can configure also with the flags: +also works with assertions. You can configure their output with the flag +`asserter`: ``` -your-app -assert Debug +your-app -asserter Debug ``` That adds more information to the assertion statement, which in default is in -production (`Prod`) mode, i.e., K&D error message. +production (`Prod`) mode, i.e., outputs K&D error message. All you need to do is to add `flag.Parse` to your `main` function. @@ -381,7 +383,7 @@ support packages like `err2` and `glog` and their flags. ```go import ( - goflag "flag" + goflag "flag" ... ) ``` @@ -391,8 +393,8 @@ support packages like `err2` and `glog` and their flags. ```go func init() { ... - // NOTE! Very important. Adds support for std flag pkg users: glog, err2 - pflag.CommandLine.AddGoFlagSet(goflag.CommandLine) + // NOTE! Very important. Adds support for std flag pkg users: glog, err2 + pflag.CommandLine.AddGoFlagSet(goflag.CommandLine) } ``` @@ -400,35 +402,30 @@ support packages like `err2` and `glog` and their flags. like: ```go - PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { - defer err2.Handle(&err) + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + defer err2.Handle(&err) - // NOTE! Very important. Adds support for std flag pkg users: glog, err2 - goflag.Parse() + // NOTE! Very important. Adds support for std flag pkg users: glog, err2 + goflag.Parse() - try.To(goflag.Set("logtostderr", "true")) - handleViperFlags(cmd) // local helper with envs - glog.CopyStandardLogTo("ERROR") // for err2 - return nil - }, + try.To(goflag.Set("logtostderr", "true")) + handleViperFlags(cmd) // local helper with envs + glog.CopyStandardLogTo("ERROR") // for err2 + return nil + }, ``` As a result you can have bunch of usable flags added to your CLI: ``` Flags: - --alsologtostderr log to standard error as well as files --asserter asserter asserter: Plain, Prod, Dev, Debug (default Prod) - --config string configuration file, FCLI_CONFIG - -n, --dry-run perform a trial run with no changes made, FCLI_DRY_RUN --err2-log stream stream for logging: nil -> log pkg (default nil) --err2-panic-trace stream stream for panic tracing (default stderr) --err2-trace stream stream for error tracing: stderr, stdout (default nil) ... ``` -And many others form `glog` in this specific example case. - ## Code Snippets Most of the repetitive code blocks are offered as code snippets. They are in From 4ae12a44923f5904965d50c943da00638d126d5a Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sat, 21 Oct 2023 15:23:24 +0300 Subject: [PATCH 28/32] more cross link information about flag support --- README.md | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 638022e..d888e3f 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,11 @@ func CopyFile(src, dst string) (err error) { - [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) - [Assertion Package for Unit Testing](#assertion-package-for-unit-testing) - [Automatic Flags](#automatic-flags) - - [Support for Cobra Flags](support-for-cobra-flags) + - [Support for Cobra Flags](#support-for-cobra-flags) - [Code Snippets](#code-snippets) - [Background](#background) - [Learnings by so far](#learnings-by-so-far) @@ -162,6 +163,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). + [Read the package documentation for more information](https://pkg.go.dev/github.com/lainio/err2). @@ -268,6 +273,8 @@ 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. +#### 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. @@ -289,6 +296,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). + #### Assertion Package for Runtime Use Following is example of use of the assert package: @@ -351,7 +362,13 @@ stack.** ## Automatic Flags When you are using `err2` or `assert` packages, i.e., just importing them, you -have an option to automatically add support for Go's standard `flag` package. +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**. 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 @@ -403,15 +420,15 @@ support packages like `err2` and `glog` and their flags. ```go PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { - defer err2.Handle(&err) - - // NOTE! Very important. Adds support for std flag pkg users: glog, err2 - goflag.Parse() - - try.To(goflag.Set("logtostderr", "true")) - handleViperFlags(cmd) // local helper with envs - glog.CopyStandardLogTo("ERROR") // for err2 - return nil + defer err2.Handle(&err) + + // NOTE! Very important. Adds support for std flag pkg users: glog, err2 + goflag.Parse() + + try.To(goflag.Set("logtostderr", "true")) + handleViperFlags(cmd) // local helper with envs + glog.CopyStandardLogTo("ERROR") // for err2 + return nil }, ``` From 99aaf5d25e15d6a1815cfc044d78fecd40b44f2a Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 22 Oct 2023 19:38:33 +0300 Subject: [PATCH 29/32] flag pkg support documentation --- assert/doc.go | 9 +++++++++ doc.go | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/assert/doc.go b/assert/doc.go index 881870e..60f9e3a 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -52,6 +52,15 @@ error messages and diagnostic they need. Please see the code examples for more information. +# Flag Package Support + +The assert package supports Go's flags. All you need to do is to call flag.Parse. +And the following flags are supported (="default-value"): + -asserter="Prod" + A name of the asserter Plain, Prod, Dev, Debug (see constants) + +And assert package's configuration flags are inserted. + Note. assert.That's performance is equal to the if-statement. Most of the generics-based versions are almost as fast. If your algorithm is performance-critical please run `make bench` in the err2 repo and decide case by diff --git a/doc.go b/doc.go index 8f6851a..20b8752 100644 --- a/doc.go +++ b/doc.go @@ -89,6 +89,17 @@ tracer API: err2.SetLogTracer(nil) // the default is nil where std log pkg is used. +# Flag Package Support + +The err2 package supports Go's flags. All you need to do is to call flag.Parse. +And the following flags are supported (="default-value"): + -err2-log="nil" + A name of the stream currently supported stderr, stdout or nil + -err2-panic-trace="stderr" + A name of the stream currently supported stderr, stdout or nil + -err2-trace="nil" + A name of the stream currently supported stderr, stdout or nil + # Error handling Package err2 relies on declarative control structures to achieve error and panic From cd2bf3793369e33b5e1ac5723d7380628ee8ba01 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 22 Oct 2023 19:39:02 +0300 Subject: [PATCH 30/32] release & version information --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d888e3f..6be1103 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,7 @@ your-app -asserter Debug ``` That adds more information to the assertion statement, which in default is in -production (`Prod`) mode, i.e., outputs K&D error message. +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. @@ -528,11 +528,12 @@ Please see the full version history from [CHANGELOG](./CHANGELOG.md). ### Latest Release ##### 0.9.5 -- `flag` package integration: +- `flag` package support to set `err2` and `assert` package configuration +- `err2.Catch` default mode is to log error +- cleanup and refactoring, new tests and benchmarks ### Upcoming releases ##### 0.9.6 -- Idea: TODO -- Continue removing unused parts -- More documentation +- Continue removing unused parts and repairing for 1.0.0 release. +- Always more and better documentation From 3f00ec0efe9531c1fb9a92244be41076a9cdfd95 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Sun, 22 Oct 2023 19:40:26 +0300 Subject: [PATCH 31/32] fmt --- assert/doc.go | 5 +++-- doc.go | 13 +++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/assert/doc.go b/assert/doc.go index 60f9e3a..d25520a 100644 --- a/assert/doc.go +++ b/assert/doc.go @@ -56,8 +56,9 @@ Please see the code examples for more information. The assert package supports Go's flags. All you need to do is to call flag.Parse. And the following flags are supported (="default-value"): - -asserter="Prod" - A name of the asserter Plain, Prod, Dev, Debug (see constants) + + -asserter="Prod" + A name of the asserter Plain, Prod, Dev, Debug (see constants) And assert package's configuration flags are inserted. diff --git a/doc.go b/doc.go index 20b8752..98bf61d 100644 --- a/doc.go +++ b/doc.go @@ -93,12 +93,13 @@ tracer API: The err2 package supports Go's flags. All you need to do is to call flag.Parse. And the following flags are supported (="default-value"): - -err2-log="nil" - A name of the stream currently supported stderr, stdout or nil - -err2-panic-trace="stderr" - A name of the stream currently supported stderr, stdout or nil - -err2-trace="nil" - A name of the stream currently supported stderr, stdout or nil + + -err2-log="nil" + A name of the stream currently supported stderr, stdout or nil + -err2-panic-trace="stderr" + A name of the stream currently supported stderr, stdout or nil + -err2-trace="nil" + A name of the stream currently supported stderr, stdout or nil # Error handling From d28d3113fc13394430c07657401515918fbb4796 Mon Sep 17 00:00:00 2001 From: Harri Lainio Date: Mon, 23 Oct 2023 19:17:56 +0300 Subject: [PATCH 32/32] remove debug line --- scripts/release.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index dd9eea0..7836f64 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -31,8 +31,6 @@ check_prerequisites() { echo "version string format ins't correct" exit 1 fi -exit 0 - } check_prerequisites $1