Skip to content

testing: support subtests in fuzzer #47413

Closed as not planned
Closed as not planned
@AlekSi

Description

@AlekSi

Consider the following test using testing.F:

func FuzzAtoi(f *testing.F) {
    for _, tc := range []struct {
        s        string
        expected int
    }{
        {s: "1", expected: 1},
        {s: "0", expected: 1},
    } {
        actual, err := strconv.Atoi(tc.s)
        if err != nil {
            f.Fatal(err)
        }
        if tc.expected != actual {
            f.Error(actual)
        }
    }
}

It fails with an unhelpful error message:

=== RUN  FuzzAtoi
    atoi_test.go:77: 0
--- FAIL: FuzzAtoi (0.00s)
FAIL

It may be made better by adding additional context into f.Error line. But there is another way for that available to testing.T, but not testing.F - subtests:

func TestAtoi(t *testing.T) {
    for _, tc := range []struct {
        s        string
        expected int
    }{
        {s: "1", expected: 1},
        {s: "0", expected: 1},
    } {
        tc := tc
        t.Run(tc.s, func(t *testing.T) {
            actual, err := strconv.Atoi(tc.s)
            if err != nil {
                t.Fatal(err)
            }
            if tc.expected != actual {
                t.Error(actual)
            }
        })
    }
}

This includes subtest name in the error message automatically, which is very helpful:

=== RUN   TestAtoi
=== RUN   TestAtoi/1
=== RUN   TestAtoi/0
    atoi_test.go:77: 0
--- FAIL: TestAtoi (0.00s)
    --- PASS: TestAtoi/1 (0.00s)
    --- FAIL: TestAtoi/0 (0.00s)
FAIL

In the end, I want to be able to do something like that:

func FuzzAtoi(f *testing.F) {
    for _, tc := range []struct {
        s        string
        expected int
    }{
        {s: "1", expected: 1},
        {s: "0", expected: 1},
    } {
        tc := tc
        f.Run(tc.s, func(t *testing.T) { // note 1
            t.Parallel()

            actual, err := strconv.Atoi(tc.s)
            if err != nil {
                f.Fatal(err)
            }
            if tc.expected != actual {
                f.Error(actual)
            }

            f.Add(tc.s) // note 2
        })

        f.Add(tc.s) // note 3
    }

    f.Fuzz(func(t *testing.T, s string) {
        t.Parallel()

        // ...
    })
}

Note that f.Run accepts a function accepting testing.T instead of testing.F. That allows one to run subtests in parallel and also removes ambiguity about the fuzz corpus - this way, there is only one for the whole FuzzAtoi function.
f.Add could be called in both places - inside and outside of a subtest. So it should be made thread-safe.

This issue is different from #46780 as it requests subtests support for testing.F instead of more complicated subfuzz targets.

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.fuzzIssues related to native fuzzing support

    Type

    No type

    Projects

    Status

    Done

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions