Skip to content

proposal: testing: report failing test after panic #32121

@neild

Description

@neild

When a test panics in a table-driven test, it is difficult to tell which case caused the failure. For example:

func Test(t *testing.T) {
        for _, test := range []int{1, 2, 0, 3} {
                t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
                        _ = 1 / test
                })
        }
}
panic: runtime error: integer divide by zero [recovered]
        panic: runtime error: integer divide by zero

goroutine 8 [running]:
testing.tRunner.func1(0xc0000ac400)
        /usr/lib/google-golang/src/testing/testing.go:830 +0x388
panic(0x511aa0, 0x623f00)
        /usr/lib/google-golang/src/runtime/panic.go:522 +0x1b5
_/usr/local/google/home/dneil/src/testpanic_test.Test.func1(0xc0000ac400)
        /usr/local/google/home/dneil/src/testpanic/panic_test.go:11 +0x24
testing.tRunner(0xc0000ac400, 0xc00005c490)
        /usr/lib/google-golang/src/testing/testing.go:865 +0xc0
created by testing.(*T).Run
        /usr/lib/google-golang/src/testing/testing.go:916 +0x357
FAIL    _/usr/local/google/home/dneil/src/testpanic     0.018s

We know that testpanic_test.Test panicked, but we don't know which test case is to blame. You can get a much more useful error by manually recovering the panic and converting it to a t.Fatal:

func Test(t *testing.T) {
        for _, test := range []int{1, 2, 0, 3} {
                t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
                        defer func() {
                                if err := recover(); err != nil {
                                        t.Error(string(debug.Stack()))
                                        t.Fatal(err)
                                }
                        }()
                        _ = 1 / test
                })
        }
}
--- FAIL: Test (0.00s)
    --- FAIL: Test/0 (0.00s)
        panic_test.go:14: runtime error: integer divide by zero
            
            goroutine 8 [running]:
            runtime/debug.Stack(0xc000046670, 0x512aa0, 0x625f00)
                /usr/lib/google-golang/src/runtime/debug/stack.go:24 +0x9d
            _/usr/local/google/home/dneil/src/testpanic_test.Test.func1.1(0xc0000ac400)
                /usr/local/google/home/dneil/src/testpanic/panic_test.go:14 +0x57
            panic(0x512aa0, 0x625f00)
                /usr/lib/google-golang/src/runtime/panic.go:522 +0x1b5
            _/usr/local/google/home/dneil/src/testpanic_test.Test.func1(0xc0000ac400)
                /usr/local/google/home/dneil/src/testpanic/panic_test.go:17 +0x7c
            testing.tRunner(0xc0000ac400, 0xc00005c490)
                /usr/lib/google-golang/src/testing/testing.go:865 +0xc0
            created by testing.(*T).Run
                /usr/lib/google-golang/src/testing/testing.go:916 +0x35a
FAIL
FAIL    _/usr/local/google/home/dneil/src/testpanic     0.016s

I propose that the testing package do something like that automatically. Perhaps rather than converting the panic to a t.Fatal it should print the failing test name and continue panicking; the important point is to provide information about which test case triggered the panic.

This doesn't help with panics in goroutines started by a test, of course.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions