Skip to content

testing: fuzzing results include duplicate output #48884

@mvdan

Description

@mvdan
$ go version
go version devel go1.18-7fcf9a1e58 Fri Oct 8 19:41:16 2021 +0000 linux/amd64
$ cat fuzz_test.go 
package main

import (
	"testing"
)

func FuzzFoo(f *testing.F) {
	f.Logf("parent f.Logf")
	f.Fuzz(func(t *testing.T, n uint8) {
		f.Logf("child f.Logf %d", n) // I'm allowed to use f.Logf here? f.Fatalf panics.
		t.Logf("child t.Logf %d", n)
		if n%3 == 0 {
			t.Fatal("boom")
		}
	})
}

If I then fuzz via go test -fuzz=. on an empty corpus, I get:

$ rm -rf testdata/fuzz && rm -rf $(go env GOCACHE)/fuzz
$ go test -fuzz=.
warning: starting with empty corpus
fuzz: elapsed: 0s, execs: 0 (0/sec), new interesting: 0 (total: 0)
fuzz: minimizing 26-byte crash input...
fuzz: elapsed: 0s, minimizing
--- FAIL: FuzzFoo (0.10s)
    fuzz_test.go:8: parent f.Logf
        fuzz_test.go:8: parent f.Logf
        fuzz_test.go:10: child f.Logf 78
        --- FAIL: FuzzFoo (0.00s)
            fuzz_test.go:11: child t.Logf 78
            fuzz_test.go:13: boom
        fuzz_test.go:10: child f.Logf 78
        --- FAIL: FuzzFoo (0.00s)
            fuzz_test.go:11: child t.Logf 78
            fuzz_test.go:13: boom
        fuzz_test.go:10: child f.Logf 78
        --- FAIL: FuzzFoo (0.00s)
            fuzz_test.go:11: child t.Logf 78
            fuzz_test.go:13: boom
    
    Crash written to testdata/fuzz/FuzzFoo/eca4c171b466a632930330d73dc661bd6d35f037e8dc7f2314c1adfc855b008b
    To re-run:
    go test test -run=FuzzFoo/eca4c171b466a632930330d73dc661bd6d35f037e8dc7f2314c1adfc855b008b
FAIL
exit status 1
FAIL	test	0.106s

I see multiple unexpected issues:

  1. Note the two parent f.Logf lines. I suspect one of them is redundant. My actual fuzz func logged over a dozen times while calling f.Add, so the duplication really added a lot of noise.

  2. I'm kinda confused why I see three separate --- FAIL: FuzzFoo blocks, all sharing the same input 78. Presumably the fuzzer should only test each input case once? If keeping track of all visited inputs or otherwise avoiding duplicates is too expensive, then at least we should be deduplicating the results being printed. To the end user, printing the same failure result more than once is unnecessary noise.

  3. Note how child f.Logf is allowed and ends up being printed along with parent f.Logf. It is not allowed to call f.Fatalf from inside the fuzz function taking a *testing.T, so I think we should similarly disallow calling any testing.TB methods on the parent *testing.F, such as Logf. I actually used f.Logf instead of t.Logf by mistake, and was very confused why my log lines ended up in the wrong position in the resulting failure output.

These are three bugs into one, apologies :) I actually only intended to file number 3, but I ran into 1 and 2 while writing the reproducer. All three issues seem small and related enough to be OK within a single thread.

cc @katiehockman @jayconrod

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeWaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.fuzzIssues related to native fuzzing support

    Type

    No type

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions