-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[dev.fuzz] testing: use generics with testing.F #46890
Comments
+1, I like this idea. We talked about this while drafting the proposal, but at that time, we weren't comfortable blocking development on generics (it seemed like we might land fuzzing in 1.17). The performance difference is a bit surprising, but I don't think it's decisive yet. It depends a lot of how generics are implemented. It sounds like there will be separate, specialized code generated for each type (with some deduplication), so we might be able to avoid dynamic dispatch altogether. Hard to say though. Mainly I like how the API is more obvious. Methods that accept Agree that the key tradeoff is that we lose multiple arguments though. |
#46218 is somewhat related: we can use vet to type-check fuzz targets even without generics. We may want to do that anyway to detect misusage of |
FWIW I ran the benchmark on the Go tip (using
Not in all cases - I could imagine people using helper packages for fuzzing that end up invoking However, although the vet check would work in almost all cases, it's still a poor cousin of the
Yup, some vet checking would definitely still be useful. |
Just a note that performance measurements of the current implementation on the dev.typeparams branch are completely meaningless. Please don't try to draw any conclusions from how the branch behaves today. |
I spent some time playing around with this, and doing some prototyping. I ran into a few roadblocks. I'm still pretty new in my understanding/use of generics, so these may very well be solvable problems, and if so, LMK.
I haven't investigated this fully, so there may be other roadblocks as well. Assuming we can get around these issues and can support generics, like Ian said, we can't assume anything about the performance yet. That said, it seems to me that the decision then comes down to which we value more: compile-time checking or easier support for fuzzing multiple types. That's a hard call to make right now. Although compile-time checking would be nice, Jay is right in that vet checks would probably cover the majority of the use cases for this. We can always add more documentation and examples to make this clearer, too.
with generics, it would have to be something like this:
(Edit: updated code below, see following comments)
which really hurts readability and usability in my opinion. |
There is some discussion of aliasing of generic types over on #46477. A description of the use case here would be helpful there. Thanks. It does seem clear that this proposal requires that func FuzzCmp(f *testing.F) {
f.Fuzz(func(t *testing.T, x struct { a, b string }) {
strings.Compare(x.a, x.b)
}
} which is still far from ideal but perhaps not quite as awful. One could also consider |
Oh, actually, I forgot that with generics the
vs.
Which is much worse. LMK if I'm missing something here though. |
Good point, I think you are right. I think you'll have to pick either generic fuzzing or single fuzzing argument. |
It's becoming apparent to me that the cost of supporting generics in this design would be pretty high, and I'm not yet convinced that fuzz testing is a good application of generics. There are a lot of situations where developers will benefit from fuzzing more than one argument at a time, and the overhead/complexity of needing to use a struct as shown in #46890 (comment) outweigh the benefits. Here are some real-world examples of fuzz targets which use multiple types today (/cc @dsnet):
|
I don't have a strong opinion whether the generic approach is better or not. The readability of the generic version is worse, but not so much worse that I find #46890 (comment) unbearable. If we had type inferencing for composite literals (see #12854), we could avoid the named type when calling On the other hand, the potential for performance gain is a non-trivial strength of the generic approach. The non-generic approach must use Also, the type safety of the generic approach avoids issues like #45593. |
I am going to close this issue as it seems like the discussions has fizzled to an end, and the general consensus is not to support generics. |
Sorry for the late comment here - I was on holiday when the above discussion happened. I agree with @dsnet that the performance issue could be significant. Also, thinking forward to generics in general, I suspect it is going to be quite common for a generic API to support calling an arbitrary function with a single argument, but it's not possible with the current proposal to support generalising such an API across functions with multiple arguments. That is, a usability like the one found here seems likely to be a common issue across many generic APIs. I hope that such a usability issue doesn't push all those APIs towards using If we agree that this is a more general problem than just in this one API, then perhaps a more general solution might work. Without changing the language, we could implement a
Then you could write your fuzzer like this, without the need to declare another type:
Looking forward a bit further, I don't believe there's any reason we couldn't support tuples directly in the language. It's still a little awkward compared to using the reflect.Call approach, but that's often true of DSL-type APIs - I believe it's worth paying a small usability price for the additional clarity and performance brought from the use of static types. |
@rogpeppe Thanks for adding this. I don't think we'd really thought about adding @katiehockman, @rolandshoemaker and I talked about this over a few calls. I think we reached a consensus internally, so I'll try to sum that up here. Pros:
Cons:
Concerns:
|
Another pro: you need less type conversions, as the compiler knows what the type is. For example, in the
It seems clear to me that the performance would definitely be better than
I don't see variadic type arguments on the horizon any time soon (I don't think anyone has any idea how they might work), but tuples themselves might fit in quite easily, leading to something like this, which is comparable in readability to the current proposal, IMHO.
FWIW I took a brief look at this, and at first glance I don't think it would require a lot of rewriting. The main things that need to change AFAICS are the code in A sketch of the changes to the testing package (diff against 988d024): https://gist.github.com/rogpeppe/ff213c3553143a1600005b0f71ad3628 The main problem with this for landing in 1.18 is that the Go compiler on tip doesn't currently support exported generic functions, and might not until later in the cycle, so that may well be a show stopper even if this proposal is deemed to be a good idea. |
Using generics doesn't seem like a good user experience. If there is a performance problem being solved by generics, |
This API idea and explanation comes from @rogpeppe. He suggests a change to the
testing.F
type which uses generics, rather thaninterface{}
types, for theFuzz
andAdd
functions. I haven't yet formed an opinion on whether or not we should do this, but I think this issue can help start that conversation./cc @golang/fuzzing
In Roger's own words:
Essentially when you define a fuzz function, you also declare what type of arguments you expect to receive too. This is all compatible with the current generics proposal.
In my view, this does give some nice advantages:
interface{}
function arguments)reflect.Value.Call
Here's a little micro-benchmark that demonstrates some performance difference (the overhead of the call goes from 150ns to 6ns): https://go2goplay.golang.org/p/Nz0CRLC2DA0
Here's a little piece of code to demonstrate that the technique could actually work for real: https://go2goplay.golang.org/p/ygvwbHw_S11
[Update: using testing/quick for a tiny bit more realism: https://go2goplay.golang.org/p/FOlQQIH5YXI ]
The main thing I'm arguing for here is that I think it's a good idea to restrict fuzz functions to have a single argument. That means they're amenable to the type-safe approach outlined above, with the arguably minor restriction that you'd have to define a struct in order to pass multiple arguments.
The text was updated successfully, but these errors were encountered: