diff --git a/assert.go b/assert.go index b79aa4c..7ac55cc 100644 --- a/assert.go +++ b/assert.go @@ -27,20 +27,28 @@ func SetConfig(config Config) { // // WARN: This assertion is live! func Assert(condition bool, msg string, values ...any) { - assert(condition, msg, 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. -func assert(condition bool, msg string, values ...any) { +// skipFrames is the number of stack frames to skip when getting the source context. +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, _ := runtime.Caller(2) //nolint:mnd // Explained in comment + _, file, line, ok := runtime.Caller(skipFrames) + + // 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/assert_debug.go b/assert_debug.go index 4481c1b..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, 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 } diff --git a/assert_test.go b/assert_test.go index 3c3c394..289af9a 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) { + assertMessage := "This should fail to get caller info" + // 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 != 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, assertMessage, 1000) +} \ No newline at end of file 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 != "" {