We’re excited to announce the open source release of Go Interface Fuzzer, a tool for automating the boilerplate of writing a fuzz tester to compare implementations of an interface. Let’s jump straight into the real-world example which motivated this. We have a message store, where all the operations are expressed in an interface. Here’s a \[…\]
We’re excited to announce the open source release of Go Interface Fuzzer, a tool for automating the boilerplate of writing a fuzz tester to compare implementations of an interface.
Let’s jump straight into the real-world example which motivated this. We have a message store, where all the operations are expressed in an interface. Here’s a simplified version of that interface:
1type Store interface { 2 // Inserts an entry in the store. Returns an error if an 3 // entry with greater or equal ID was 4 // already inserted. 5 Put(msg Message) error 6 7 // Returns a slice of all messages in the specified 8 // channel, from the specified ID to the message with 9 // most recent ID. 10 EntriesSince(sinceID uint64, channel Channel) []Message 11 12 // Returns the ID of the most recently inserted message. 13 MostRecentID() uint64 14 15 // Returns the number of messages in the store. 16 NumEntries() uint64 17 18 // Returns all messages across all channels as a single 19 // slice, sorted by ID. 20 AsSlice() []Message 21 22 // Returns the maximum number of messages in the store. 23 MessageLimit() uint64 24}
It’s nothing special, it’s even lacking some features you might expect, like the ability to retrieve a specific message. Speaking of messages, here’s what they look like:
1type Message struct { 2 // Each message has a unique ID. 3 ID uint64 4 5 // A message belongs to a specific channel. 6 Channel Channel 7 8 // And has a body 9 Body string 10} 11 12type Channel string
We have two implementations of the Store
interface. One is simple and was written such that it would be obviously correct, with no attention paid to performance. The other is faster, does less allocation, and is a bit more complex. We wanted to make sure both of these implementations behaved the same. This is a fairly simple task, it just involves a lot of boilerplate. The code goes like this:
Store
and test Store
.The code is tightly coupled to the interface, but is totally mechanical and requires very little thought. It’s all boilerplate, and we as programmers don’t like writing boilerplate.
It can generate all the boilerplate for you, given the source file containing the interface and some special comments to guide the generation. A minimally marked-up Store
looks like this:
1/* 2@fuzz interface: Store 3@known correct: makeReferenceStore 4@generator: generateChannel Channel 5@generator: generateMessage Message 6*/ 7type Store interface { 8 /* ... */ 9}
There are three special things here:
@fuzz interface
starts a fuzzer definition;@known correct
gives a function to generate the reference implementation;@generator
gives a function to generate values of a given type.If we run go-interface-fuzzer store.go
, it prints three functions to stdout:
FuzzTestStore(makeTest func() Store, t *testing.T)
go test
FuzzStore(makeTest func() Store, rand *rand.Rand, max uint) error
FuzzStoreWith(reference Store, test Store, rand *rand.Rand, max uint) error
There are flags to suppress the first two functions, if all you want is the third. There are also flags to generate a complete source file, with package name and imports; and to write out to a file.
There are more facilities: invariant checking, stateful generators, and custom comparators. We’re happily using Go Interface Fuzzer internally here at Pusher and it has actually found some bugs, some even in the reference implementation! It’s easy to inadvertently write passing tests, because it’s easy to not notice edge cases. Being able to verify that a large number of random operations produce the same output greatly increases confidence.
Check out the project on GitHub, there’s also a more fully-featured version of the Store
example in the repository.
If you have any comments or questions, feel free to open an issue on GitHub, or contact me on Twitter (@barrucadu).