Skip to content

testing: support subtests in fuzzer #47413

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsDecisionFeedback 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

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions