From 5bc238fb1ad84252b2add4bf0f1cf4184ac4addd Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Thu, 13 Feb 2025 06:39:27 +0530 Subject: [PATCH 1/4] feat: Handle failure of runtime.Caller --- assert.go | 8 +++++++- types.go | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/assert.go b/assert.go index b79aa4c..9c3dc47 100644 --- a/assert.go +++ b/assert.go @@ -39,8 +39,14 @@ func assert(condition bool, msg string, values ...any) { // Skip 2 frames: // 1. this assert() function // 2. the Assert() function that called us - _, file, line, _ := runtime.Caller(2) //nolint:mnd // Explained in comment + _, file, line, ok := runtime.Caller(2) //nolint:mnd // Explained in comment + // Could not get Caller info + if !ok { + panic(AssertionError{ + Message: msg, + }) + } // If values were provided for dumping numValues := len(values) if numValues%2 != 0 { diff --git a/types.go b/types.go index b50c53a..f7cb1d5 100644 --- a/types.go +++ b/types.go @@ -30,7 +30,11 @@ type AssertionError struct { func (e AssertionError) Error() string { var sb strings.Builder - sb.WriteString(fmt.Sprintf("Assertion failed at %s:%d\n", e.File, e.Line)) + if e.File != "" { + sb.WriteString(fmt.Sprintf("Assertion failed at %s:%d\n", e.File, e.Line)) + } else { + sb.WriteString("Assertion failed (Runtime caller info is not available)\n") + } sb.WriteString(fmt.Sprintf("Message: %s\n", e.Message)) if e.SourceContext != "" { From 13d733af13c7ec82e563f373f3894846249763f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niko=20K=C3=B6ser?= Date: Thu, 13 Feb 2025 06:31:09 +0530 Subject: [PATCH 2/4] test: Add test for failure of caller info, and pass skipFrames as parameter --- assert.go | 14 ++++++++------ assert_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/assert.go b/assert.go index 9c3dc47..69d97f8 100644 --- a/assert.go +++ b/assert.go @@ -27,19 +27,21 @@ func SetConfig(config Config) { // // WARN: This assertion is live! func Assert(condition bool, msg string, values ...any) { - assert(condition, msg, values...) + // skipFrames is the number of stack frames to skip when getting the source context. + // By default, we skip 2 frames: + // 1. The assert() function itself + // 2. The Assert()/Debug() function that called assert() + assert(condition, msg, 2, values...) } // Assert panics if the condition is false. Configurable via SetConfig. -func assert(condition bool, msg string, values ...any) { +func assert(condition bool, msg string, skipFrames int, values ...any) { if condition { return // Assertion met } - // Skip 2 frames: - // 1. this assert() function - // 2. the Assert() function that called us - _, file, line, ok := runtime.Caller(2) //nolint:mnd // Explained in comment + + _, file, line, ok := runtime.Caller(skipFrames) //nolint:mnd // Explained in comment // Could not get Caller info if !ok { diff --git a/assert_test.go b/assert_test.go index 3c3c394..c377c67 100644 --- a/assert_test.go +++ b/assert_test.go @@ -289,3 +289,38 @@ func TestAssert_EmptyValues(t *testing.T) { "empty_map", map[string]int{}, ) } + +func TestAssertCallerFailure(t *testing.T) { + + // Capture and verify the panic + defer func() { + r := recover() + if r == nil { + t.Fatal("Expected panic, but none occurred") + } + + ae, ok := r.(AssertionError) + if !ok { + t.Fatalf("Expected AssertionError, got %T", r) + } + + // Verify we got the simplified error without file/line info + if ae.File != "" { + t.Errorf("Expected empty file, got %q", ae.File) + } + if ae.Line != 0 { + t.Errorf("Expected line to be 0, got %d", ae.Line) + } + if ae.SourceContext != "" { + t.Errorf("Expected empty source context, got %q", ae.SourceContext) + } + // Message should still be included + if ae.Message == "" { + t.Error("Expected non-empty message") + } + }() + + // Using the assert function (only accessible internally) instead of Assert + // to pass a specific skipFrames value + assert(false, "This should fail to get caller info", 1000) +} \ No newline at end of file From a579f525e4d5f10b87a58f796ffef327f27c1a24 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Thu, 13 Feb 2025 06:45:44 +0530 Subject: [PATCH 3/4] fix: Use a more strict check in test, add skipFrame value in Debug() --- assert.go | 10 +++++----- assert_debug.go | 2 +- assert_test.go | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/assert.go b/assert.go index 69d97f8..d2af26f 100644 --- a/assert.go +++ b/assert.go @@ -27,20 +27,20 @@ func SetConfig(config Config) { // // WARN: This assertion is live! func Assert(condition bool, msg string, values ...any) { - // skipFrames is the number of stack frames to skip when getting the source context. - // By default, we skip 2 frames: - // 1. The assert() function itself - // 2. The Assert()/Debug() function that called assert() assert(condition, msg, 2, values...) } // Assert panics if the condition is false. Configurable via SetConfig. +// skipFrames is the number of stack frames to skip when getting the source context. +// By default, we skip 2 frames: +// 1. The assert() function itself +// 2. The Assert()/Debug() function that called assert() func assert(condition bool, msg string, skipFrames int, values ...any) { if condition { return // Assertion met } - + _, file, line, ok := runtime.Caller(skipFrames) //nolint:mnd // Explained in comment // Could not get Caller info diff --git a/assert_debug.go b/assert_debug.go index 4481c1b..62bc8e9 100644 --- a/assert_debug.go +++ b/assert_debug.go @@ -10,5 +10,5 @@ package assert // // WARN: Under the current build configuration, this assertion is enabled. func Debug(condition bool, msg string, values ...any) { - assert(condition, msg, values...) + assert(condition, msg, 2, values...) } diff --git a/assert_test.go b/assert_test.go index c377c67..289af9a 100644 --- a/assert_test.go +++ b/assert_test.go @@ -291,7 +291,7 @@ func TestAssert_EmptyValues(t *testing.T) { } func TestAssertCallerFailure(t *testing.T) { - + assertMessage := "This should fail to get caller info" // Capture and verify the panic defer func() { r := recover() @@ -315,12 +315,12 @@ func TestAssertCallerFailure(t *testing.T) { t.Errorf("Expected empty source context, got %q", ae.SourceContext) } // Message should still be included - if ae.Message == "" { - t.Error("Expected non-empty message") + if ae.Message != assertMessage { + t.Errorf("Expected %q as error message, got %q", assertMessage, ae.Message) } }() // Using the assert function (only accessible internally) instead of Assert // to pass a specific skipFrames value - assert(false, "This should fail to get caller info", 1000) + assert(false, assertMessage, 1000) } \ No newline at end of file From 14dafe1dea01768601c79325ec7837b8725af11e Mon Sep 17 00:00:00 2001 From: Nitin Kumar <59679977+lazysegtree@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:03:37 +0530 Subject: [PATCH 4/4] fix: PR comments - Put the comments about skipFrames at their appropriate place MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Niko Köser --- assert.go | 10 +++++----- assert_debug.go | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/assert.go b/assert.go index d2af26f..7ac55cc 100644 --- a/assert.go +++ b/assert.go @@ -27,21 +27,21 @@ func SetConfig(config Config) { // // WARN: This assertion is live! func Assert(condition bool, msg string, values ...any) { - assert(condition, msg, 2, values...) + // We tell assert() to skip 2 frames here: + // 1. The assert() function itself + // 2. This Assert() function that calls assert() + assert(condition, msg, 2, values...) //nolint:mnd // Explained in comment } // Assert panics if the condition is false. Configurable via SetConfig. // skipFrames is the number of stack frames to skip when getting the source context. -// By default, we skip 2 frames: -// 1. The assert() function itself -// 2. The Assert()/Debug() function that called assert() func assert(condition bool, msg string, skipFrames int, values ...any) { if condition { return // Assertion met } - _, file, line, ok := runtime.Caller(skipFrames) //nolint:mnd // Explained in comment + _, file, line, ok := runtime.Caller(skipFrames) // Could not get Caller info if !ok { diff --git a/assert_debug.go b/assert_debug.go index 62bc8e9..f16b295 100644 --- a/assert_debug.go +++ b/assert_debug.go @@ -10,5 +10,8 @@ package assert // // WARN: Under the current build configuration, this assertion is enabled. func Debug(condition bool, msg string, values ...any) { - assert(condition, msg, 2, values...) + // We tell assert() to skip 2 frames here: + // 1. The assert() function itself + // 2. This Debug() function that calls assert() + assert(condition, msg, 2, values...) //nolint:mnd // Explained in comment }