From ec67f53e00e3870053e97fd2171d4867a31877b2 Mon Sep 17 00:00:00 2001 From: amvtek Date: Wed, 24 Jun 2026 06:04:03 +0300 Subject: [PATCH 1/4] Allow controlling RaisedPropChain cause error. --- pkg/raised/design_test.go | 65 +++++---------------------------------- pkg/raised/error_test.go | 4 +-- pkg/raised/only_test.go | 59 +++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 59 deletions(-) create mode 100644 pkg/raised/only_test.go diff --git a/pkg/raised/design_test.go b/pkg/raised/design_test.go index 784fb3d..6da5ce5 100644 --- a/pkg/raised/design_test.go +++ b/pkg/raised/design_test.go @@ -1,8 +1,6 @@ package raised import ( - "crypto/rand" - "encoding/base64" "errors" "fmt" "strings" @@ -89,7 +87,7 @@ func BenchmarkDesign_propRces64r4(b *testing.B) { } func BenchmarkDesign_propRaiseds64r4(b *testing.B) { - ef := makeRaisedPropChain(64, 4) + ef := makeRaisedPropChain(64, 4, errPropSentinel) for b.Loop() { _ = ef() } @@ -117,7 +115,7 @@ func BenchmarkDesign_propRceTraces64r4(b *testing.B) { } func BenchmarkDesign_propRaisedTraces64r4(b *testing.B) { - ef := makeRaisedPropChain(64, 4) + ef := makeRaisedPropChain(64, 4, errPropSentinel) for b.Loop() { _ = fmt.Sprintf("%+v", ef()) // using fmt results in 1 extra alloc... } @@ -145,7 +143,7 @@ func BenchmarkDesign_propRces64r8(b *testing.B) { } func BenchmarkDesign_propRaiseds64r8(b *testing.B) { - ef := makeRaisedPropChain(64, 8) + ef := makeRaisedPropChain(64, 8, errPropSentinel) for b.Loop() { _ = ef() } @@ -173,7 +171,7 @@ func BenchmarkDesign_propRceTraces64r8(b *testing.B) { } func BenchmarkDesign_propRaisedTraces64r8(b *testing.B) { - ef := makeRaisedPropChain(64, 8) + ef := makeRaisedPropChain(64, 8, errPropSentinel) for b.Loop() { _ = fmt.Sprintf("%+v", ef()) // using fmt results in 1 extra alloc... } @@ -201,7 +199,7 @@ func BenchmarkDesign_propRces256r16(b *testing.B) { } func BenchmarkDesign_propRaiseds256r16(b *testing.B) { - ef := makeRaisedPropChain(256, 16) + ef := makeRaisedPropChain(256, 16, errPropSentinel) for b.Loop() { _ = ef() } @@ -229,7 +227,7 @@ func BenchmarkDesign_propRceTraces256r16(b *testing.B) { } func BenchmarkDesign_propRaisedTraces256r16(b *testing.B) { - ef := makeRaisedPropChain(256, 16) + ef := makeRaisedPropChain(256, 16, errPropSentinel) for b.Loop() { _ = fmt.Sprintf("%+v", ef()) // using fmt results in 1 extra alloc... } @@ -321,43 +319,6 @@ func makeRcePropChain(strsz int, chnsz int) errfunc { return erf } -func makeRaisedPropChain(strsz int, chnsz int) errfunc { - makeNextFunc := func(fls int, prev errfunc) errfunc { - msg := fmt.Sprintf("[%d]: %s", fls, rndString(strsz)) - switch fls % 4 { - case 0: - return func() error { - return Trace(prev(), msg) - } - case 1: - return func() error { - return Trace(prev(), msg) - } - case 2: - return func() error { - return Trace(prev(), msg) - } - case 3: - return func() error { - return Trace(prev(), msg) - } - default: - return func() error { - return Trace(prev(), msg) - } - } - } - - erf := func() error { - return errPropSentinel - } - for i := range chnsz { - erf = makeNextFunc(i, erf) - } - - return erf -} - func makeRaisedPCCachePropChain(strsz int, chnsz int) errfunc { makeNextFunc := func(fls int, prev errfunc) errfunc { msg := fmt.Sprintf("[%d]: %s", fls, rndString(strsz)) @@ -396,7 +357,7 @@ func makeRaisedPCCachePropChain(strsz int, chnsz int) errfunc { } func TestDesign_propRaised(t *testing.T) { - errFunc := makeRaisedPropChain(32, 12) + errFunc := makeRaisedPropChain(32, 12, errPropSentinel) err := errFunc() t.Logf("err -> %v", err) t.Logf("err %%s \n%s", err) @@ -405,7 +366,7 @@ func TestDesign_propRaised(t *testing.T) { } func TestDesign_propRaised02(t *testing.T) { - errFunc := makeRaisedPropChain(32, 16) + errFunc := makeRaisedPropChain(32, 16, errPropSentinel) err := errFunc() t.Logf("err1 -> \n%v", err) err = errFunc() @@ -930,16 +891,6 @@ type errStr struct { // ==================================================================================== // Utilities -var rawB64 = base64.StdEncoding.WithPadding(base64.NoPadding) - -// rndString returns a base64 string encoding sz random bytes. -func rndString(sz int) string { - buf := make([]byte, sz) - rand.Read(buf) - - return rawB64.EncodeToString(buf) -} - func TestDesign_RndString(t *testing.T) { t.Logf("rndString(32) -> %s", rndString(32)) t.Logf("rndString(64) -> %s", rndString(64)) diff --git a/pkg/raised/error_test.go b/pkg/raised/error_test.go index c8a40c5..e4d4317 100644 --- a/pkg/raised/error_test.go +++ b/pkg/raised/error_test.go @@ -93,7 +93,7 @@ func TestError_Classify(t *testing.T) { func TestError_Compression(t *testing.T) { // produce a chain longer than traceSize to trigger compression - errFunc := makeRaisedPropChain(16, traceSize+4) + errFunc := makeRaisedPropChain(16, traceSize+4, errPropSentinel) err := errFunc() et, ok := err.(*errTrace) @@ -118,7 +118,7 @@ func TestError_Compression(t *testing.T) { } func TestError_Show(t *testing.T) { - errFunc := makeRaisedPropChain(16, traceSize+8) + errFunc := makeRaisedPropChain(16, traceSize+8, errPropSentinel) err := errFunc() t.Logf("err.Error() ->\n%v", err) diff --git a/pkg/raised/only_test.go b/pkg/raised/only_test.go new file mode 100644 index 0000000..63c2e8b --- /dev/null +++ b/pkg/raised/only_test.go @@ -0,0 +1,59 @@ +package raised + +import ( + "crypto/rand" + "encoding/base64" + "fmt" +) + +type errfunc = func() error + +var rawB64 = base64.StdEncoding.WithPadding(base64.NoPadding) + +// makeRaisedPropChain returns an errfunc which when called propagates the +// cause error accross chnsz locations. Each Trace message holds strsz random +// bytes encoded to base64. +func makeRaisedPropChain(strsz int, chnsz int, cause error) errfunc { + makeNextFunc := func(fls int, prev errfunc) errfunc { + msg := fmt.Sprintf("[%d]: %s", fls, rndString(strsz)) + switch fls % 4 { + case 0: + return func() error { + return Trace(prev(), msg) + } + case 1: + return func() error { + return Trace(prev(), msg) + } + case 2: + return func() error { + return Trace(prev(), msg) + } + case 3: + return func() error { + return Trace(prev(), msg) + } + default: + return func() error { + return Trace(prev(), msg) + } + } + } + + erf := func() error { + return cause + } + for i := range chnsz { + erf = makeNextFunc(i, erf) + } + + return erf +} + +// rndString returns a base64 string encoding sz random bytes. +func rndString(sz int) string { + buf := make([]byte, sz) + rand.Read(buf) + + return rawB64.EncodeToString(buf) +} From 69b2de27f7dbaa5ddfac364482e59b09c158146b Mon Sep 17 00:00:00 2001 From: amvtek Date: Wed, 24 Jun 2026 06:05:23 +0300 Subject: [PATCH 2/4] Use RaisedPropChain for ErrorKeyer testing. --- pkg/raised/keying_test.go | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/pkg/raised/keying_test.go b/pkg/raised/keying_test.go index a8b0640..823ec77 100644 --- a/pkg/raised/keying_test.go +++ b/pkg/raised/keying_test.go @@ -93,6 +93,57 @@ func TestKeying_Key_NoResolvableTerminal(t *testing.T) { t.Skip("nil-terminal path requires internal construction; covered by TestKey_NonTracedError and TestKey_NilError") } +// ---- Key: correctness tests using RaisedPropChain ---- + +func TestKeying_KeyPropChain_SamePropChainSameCauseSentinel_Equal(t *testing.T) { + ek := mustKeyer(t, nil) + s1 := NewSentinelError[familyA]("ERROR(110): sentinel can be recognized") + + // ef1 propagates sentinel + ef1 := makeRaisedPropChain(64, 25, s1) + k1, ok1 := ek.Key(ef1()) + + // ef2 propagates an error that wraps s1 + err2 := fmt.Errorf("I should be ignored %w", s1) + ef2 := makeRaisedPropChain(64, 25, err2) + k2, ok2 := ek.Key(ef2()) + + if !ok1 || !ok2 { + t.Fatalf("Key returned false: ok1=%v ok2=%v", ok1, ok2) + } + + // RMQ: note that the error propagated by ef2 is different than the one propagated by ef1 + // but they have same sentinel, hence keys are the same. + if k1 != k2 { + t.Errorf("expected same keys, got %X != %X", k1, k2) + } +} + +func TestKeying_KeyPropChain_SamePropChainDistinctCauseSentinel_NotEqual(t *testing.T) { + ek := mustKeyer(t, nil) + s1 := NewSentinelError[familyA]("ERROR(110): distinct phantom types, distinct sentinels") + s2 := NewSentinelError[familyB]("ERROR(110): distinct phantom types, distinct sentinels") + + // ef1 propagates sentinel s1 + ef1 := makeRaisedPropChain(64, 25, s1) + k1, ok1 := ek.Key(ef1()) + + // ef2 propagates sentinel s2 + ef2 := makeRaisedPropChain(64, 25, s2) + k2, ok2 := ek.Key(ef2()) + + if !ok1 || !ok2 { + t.Fatalf("Key returned false: ok1=%v ok2=%v", ok1, ok2) + } + + if k1 == k2 { + t.Error("expected distinct keys") + } + + t.Logf("k1 -> %X", k1[:]) + t.Logf("k2 -> %X", k2[:]) +} + // ---- Key: correctness tests ---- func TestKeying_Key_SameCallSiteSameCause_Equal(t *testing.T) { From 86246cbc30d337ee3dce414c0b8c4cd4e9da03a1 Mon Sep 17 00:00:00 2001 From: amvtek Date: Wed, 24 Jun 2026 06:06:16 +0300 Subject: [PATCH 3/4] Add ErrorKeyer benchmarks. --- pkg/raised/design_test.go | 116 +++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/pkg/raised/design_test.go b/pkg/raised/design_test.go index 6da5ce5..98c63db 100644 --- a/pkg/raised/design_test.go +++ b/pkg/raised/design_test.go @@ -240,9 +240,123 @@ func BenchmarkDesign_propRaisedPCCacheTraces256r16(b *testing.B) { } } +// ==================================================================================== +// Keying errors returned by raised.Trace +// +// To run those benchmarks use: +// go test ./pkg/raised -bench Design_key -benchmem +// + +func BenchmarkDesign_keyRaised_Stables64r4(b *testing.B) { + + ek, fail := NewErrorKeyer[pkg](nil) + if nil != fail { + b.Fatalf("failed instantiating ErrorKeyer, got error %v", fail) + } + + ef := makeRaisedPropChain(64, 4, errPropSentinel) + err := ef() + for b.Loop() { + ek.Key(err) + } +} + +func BenchmarkDesign_keyRaised_Stables64r8(b *testing.B) { + + ek, fail := NewErrorKeyer[pkg](nil) + if nil != fail { + b.Fatalf("failed instantiating ErrorKeyer, got error %v", fail) + } + + ef := makeRaisedPropChain(64, 8, errPropSentinel) + err := ef() + for b.Loop() { + ek.Key(err) + } +} + +func BenchmarkDesign_keyRaised_Stables64r16(b *testing.B) { + + ek, fail := NewErrorKeyer[pkg](nil) + if nil != fail { + b.Fatalf("failed instantiating ErrorKeyer, got error %v", fail) + } + + ef := makeRaisedPropChain(64, 16, errPropSentinel) + err := ef() + for b.Loop() { + ek.Key(err) + } +} + +func BenchmarkDesign_keyRaised_Unstables64r4(b *testing.B) { + + ek, fail := NewErrorKeyer[pkg](nil) + if nil != fail { + b.Fatalf("failed instantiating ErrorKeyer, got error %v", fail) + } + + erts := make([]error, 4096) + for i := range 4096 { + cause := fmt.Errorf("root cause %d", i) + ef := makeRaisedPropChain(64, 4, cause) + erts[i] = ef() + } + + c := 0 + for b.Loop() { + ek.Key(erts[c % 4096]) + c++ + } +} + +func BenchmarkDesign_keyRaised_Unstables64r8(b *testing.B) { + + ek, fail := NewErrorKeyer[pkg](nil) + if nil != fail { + b.Fatalf("failed instantiating ErrorKeyer, got error %v", fail) + } + + erts := make([]error, 4096) + for i := range 4096 { + cause := fmt.Errorf("root cause %d", i) + ef := makeRaisedPropChain(64, 8, cause) + erts[i] = ef() + } + + c := 0 + for b.Loop() { + ek.Key(erts[c % 4096]) + c++ + } +} + +func BenchmarkDesign_keyRaised_Unstables64r16(b *testing.B) { + + ek, fail := NewErrorKeyer[pkg](nil) + if nil != fail { + b.Fatalf("failed instantiating ErrorKeyer, got error %v", fail) + } + + erts := make([]error, 4096) + for i := range 4096 { + cause := fmt.Errorf("root cause %d", i) + ef := makeRaisedPropChain(64, 16, cause) + erts[i] = ef() + } + + c := 0 + for b.Loop() { + ek.Key(erts[c % 4096]) + c++ + } +} + +// ==================================================================================== var errPropSentinel = NewSentinel("ERROR(245): propagation sentinel") -type errfunc = func() error +// makeRaisedPropChain is defined in only_test.go +// as it is also used to support other tests func makeErrorfPropChain(strsz int, chnsz int) errfunc { makeNextFunc := func(fls int, prev errfunc) errfunc { From c2d91d10578f5946f6542805484735b0202e0ff2 Mon Sep 17 00:00:00 2001 From: amvtek Date: Wed, 24 Jun 2026 06:16:54 +0300 Subject: [PATCH 4/4] Fix formatting issue. --- pkg/raised/design_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/raised/design_test.go b/pkg/raised/design_test.go index 98c63db..54339c8 100644 --- a/pkg/raised/design_test.go +++ b/pkg/raised/design_test.go @@ -305,7 +305,7 @@ func BenchmarkDesign_keyRaised_Unstables64r4(b *testing.B) { c := 0 for b.Loop() { - ek.Key(erts[c % 4096]) + ek.Key(erts[c%4096]) c++ } } @@ -326,7 +326,7 @@ func BenchmarkDesign_keyRaised_Unstables64r8(b *testing.B) { c := 0 for b.Loop() { - ek.Key(erts[c % 4096]) + ek.Key(erts[c%4096]) c++ } } @@ -347,7 +347,7 @@ func BenchmarkDesign_keyRaised_Unstables64r16(b *testing.B) { c := 0 for b.Loop() { - ek.Key(erts[c % 4096]) + ek.Key(erts[c%4096]) c++ } }