-
Notifications
You must be signed in to change notification settings - Fork 33
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
Roundtrip tests with QuickCheck #233
Conversation
Note: Upon further reflection, I realized that I had inadvertently conflated Also, a note for @c-cube: I just came across qcheck, a QuickCheck |
I would suggest going with AFL fuzzer, and a very good https://github.com/stedolan/crowbar library that supports it. I've had a lot of success finding obsure bugs in my code with this combo. AFL supports dictionary-based mode which saves some CPU cycles for coming up with protobuf syntax on its own (although it definitely can do that, it can even pull JPEGs out of thin air). It maximizes branch coverage in the tested process by producing new inputs, and it can also minimimize the test cases so that you have minimal set that has the same coverage. I was thinking that we could also add some official protobuf implementation into the loop, like Golang one, and have ocaml-protoc encode a message, and Golang generated code decode it and vice versa, this way we can property-test compatibility with official Google tooling. |
Hm, probably AFL is not really required for roundtrip testing that you suggest, it's powers are not really helpful here. Given some protobuf message some quick-check style fuzzing is more than sufficient to cover the input space... We could use AFL/crowbar though to come up with a minimal collection of input .proto files that cover most of what ocaml-protoc can parse though. Filter out incorrect inputs with some official tool like buf, and feed the rest into ocaml-protoc. This will find cases where ocaml-protoc fails to parse valid .proto code. Maybe we could just use some already-created set of .proto files used as test corpus for other protobuf implementations for this purpose... Having a decent collection of .proto files, we could come up with code generation plugin that will emit quickcheck code for all the generated types. Actually you can tell ocaml-protoc to add quickcheck ppx annotations to all types by its own, this way you don't have to make duplicates of types in your quickcheck code only to add the necessary ppxes. Having automatically generated code for quickcheck for all types, you can just generate the whole testing code for whole collection of .proto's and run it! This will ensure that whatever is encoded by ocaml-protoc is properly decoded back, and the only thing that would be missing is compatibility with other tooling. |
Hi @Lupus ! I want to express my gratitude for your innovative thinking and consideration of While I may not be able to fully articulate the essence of my feelings at this While I currently have reservations about the accuracy of proto files generated Your suggestion about adding ppx deriving directly to the generated code has not I greatly appreciate your long-term vision. If we were to think about near-term, To reduce the amount of code needed to wrap the generated code, we could module T = struct
type t = Messages.person
let encode_pb = Messages.encode_pb_person
let decode_pb = Messages.decode_pb_person
let encode_json = Messages.encode_json_person
let decode_json = Messages.decode_json_person
end This binding could be automatically generated and made available as a type-class Thank you again for your valuable input and inspiring vision. I look forward to |
This currently shows a failing test (unit_or_error).
75fa867
to
1ad3aa1
Compare
Latest Developments:
I'm eager to discuss whether you believe this PR, in its current state, is For further improvements:
While both options are viable in the long term, I'm questioning whether it's |
A few comments off the top of my head: Thank you for your work, it is very much appreciated. Your thoughtfulness in comments is also noted :-). The further improvement 1. would be nice, the ppx for qcheck should work for files with .mli in general. It should be too hard as it's a signature per type. I don't think 2. is useful because then it means we can't access the printers from the outside. QCheck can be used with alcotest or ounit, but it also has its own runner library in qcheck-core.runner. It just takes a list of tests and CLI arguments, and it might be enough. |
I believe you're referring to the qcheck generators, correct? Just to make sure (** {2 QuickCheck} *)
val quickcheck_person : person Pbrt_quickcheck.Type_class.t
(** [quickcheck_person] provides helpers to test the type person with quickcheck *) This would be accompanied by a modification to the type_class type to include ------ /ocaml-protoc/src/runtime-qcheck/pbrt_quickcheck.ml
++++++ /ocaml-protoc/src/runtime-qcheck/pbrt_quickcheck.ml
@|-1,13 +1,14 ============================================================
|(** Runtime for QuickCheck based tests. *)
|
|(** A type class generated for each type *)
|module Type_class = struct
| type 'a t = {
| pp: Format.formatter -> 'a -> unit;
+| gen: 'a QCheck2.Gen.t
| equal: 'a -> 'a -> bool;
| encode_pb: 'a -> Pbrt.Encoder.t -> unit;
| decode_pb: Pbrt.Decoder.t -> 'a;
| encode_json: 'a -> Yojson.Basic.t;
| decode_json: Yojson.Basic.t -> 'a;
| }
|end
Thanks for this. I plan to try this when I next work on the PR. |
I decided to take one more step while the details are still fresh in my mind. I've modified the test to use qcheck's core runner, as you suggested. Here's the outcome: $ dune exec ./src/tests/roundtrip/main.exe -- --verbose --colors
random seed: 130848014
generated error fail pass / total time test name
[✓] 100 0 0 100 / 100 0.0s error.protobuf-roundtrip
[✓] 100 0 0 100 / 100 0.0s error.json-roundtrip
[✓] 100 0 0 100 / 100 1.5s person.protobuf-roundtrip
[✓] 100 0 0 100 / 100 1.5s person.json-roundtrip
[✓] 100 0 0 100 / 100 0.0s empty.protobuf-roundtrip
[✓] 100 0 0 100 / 100 0.0s empty.json-roundtrip
[✓] 100 0 0 100 / 100 0.0s error.protobuf-roundtrip
[✓] 100 0 0 100 / 100 0.0s error.json-roundtrip
[✗] 1 1 0 0 / 100 0.0s unit_or_error.protobuf-roundtrip
[✓] 100 0 0 100 / 100 0.0s unit_or_error.json-roundtrip
=== Error ======================================================================
Test unit_or_error.protobuf-roundtrip errored on (0 shrink steps):
Unit
exception Pbrt.Decoder.Failure(Malformed_variant("unit_or_error"))
================================================================================
failure (0 tests failed, 1 tests errored, ran 10 tests) Currently, I haven't found a way to incorporate this failing test in a manner that doesn't disrupt the build, without resorting to cram tests, which I believe aren't enabled in this repository. This is something to be determined. Regarding the issue with qcheck2 in I believe this latest round of changes brings us significantly closer to the level of automation discussed by @Lupus. |
Don't we want to fix the bug just after merging this anyway? I've never need a failing proptest before.
|
Do you not use proptests in TDD? Long-term, users submitting failing tests 1 in PRs could be a valuable workflow. In the near term, we could pragmatically just disable the only failing item just Regarding the bug, I want to manage expectations: I am not currently For the qcheck part, I've opened a PR to add the missing support for signature Footnotes |
I don't necessarily use TDD. I like the regression tests part, but I've never before used a failing qcheck property as a positive test (if you make it work, why not though!). Pragmatically we can comment/disable the failing test and re-enable it once the bug is fixed, that works for me. |
- Now it's possible to add failing tests without breaking the build. - The new stanza added required bumping dune-lang to `2.2`.
d5abc18
to
db07526
Compare
This reverts commit 5b1a17e. The ci needs to know what dependencies to install. I'm not sure where to declare them if not in an unused package definition? TBD.
I've added the following:
Thanks! I've made a new attempt: I've observed that the qcheck-runner, when executed without the This looks like this: (rule
(target test.outputs)
(action
(with-accepted-exit-codes
(or 0 1)
(with-outputs-to
%{target}
(run ./%{dep:main.exe} --no-colors --seed 382079347))))) To use the I'm pleased to report that the CI is now passing. Looking forward to your next |
That looks good! Dune 2.2 is a reasonable dependency. Do you plan to add more tests, or do you want to mark this PR as ready? |
Thank you, @c-cube, for your continued involvement with this PR. I'm OK with the I've published a preview package1 and listed it2 for experimentation. I've @Lupus, I'd love to hear your thoughts too before transitioning this PR to the Footnotes |
Hey @mbarbin , sorry for late response. I doubt I'll have time to test this with our proto interfaces. We don't have a large collection of protos as of now anyways, so I doubt that it will bring much value. There is that Google's test suite for protobuf, have you tried testing those? |
Hi! 👋 I've been away from the protoc project for a while, but I'm now revisiting my open PRs. I've been using the quickcheck generation implemented in this PR for several months experimentally, but I'm now reconsidering the approach. Firstly, the workaround I've used here to bypass the lack of support for For my current use case, I'm not directly manipulating the types generated by Looking back at the protoc code generator, I notice that the boundaries for the abstraction of a generation plugin are already very well established in the Given these considerations, I'm inclined to close this PR for now. I could revisit it in the future if the plugin route seems appealing. I'd appreciate your thoughts on this. Thank you for your time! |
cc @c-cube - I don't know how GitHub notifications works on draft or closed PRs. Just wanted to make sure you'd see the reasons I closed this one. Thanks a lot, and looking forward to potential future work together! |
That's fair, no worries! :-) (Notifications work just the same btw.) |
While working with ocaml-protoc, I encountered a message type that caused an RPC
deserialization error when the client attempted to decode the server's response.
This prompted me to explore an implementation of roundtrip property tests for
ocaml-protoc.
In this PR, I've created a new test directory that adds roundtrip tests using
QuickCheck. The property under test is the ability of a message to roundtrip
through protoc serialization (using both protobuf and json). The quickcheck
generation is focused on generating inputs of the message type under test.
This test was able to automagically find the problematic value I was dealing
with.
This new test directory is structured as a separate test package and does not
introduce new dependencies to the ocaml-protoc codebase. However, it does have a
dependency footprint that might not align with your preferences. Therefore, I'm
submitting this PR as a draft for your review, and start a discussion.
As for the original issue, I haven't investigated its root cause. I'm also
unsure if the message type I'm trying to express is a valid proto specification.
I would greatly appreciate your insights on this matter.
The message in question is the
Unit
case of theUnitOrError
message type.Thank you for your time and consideration!