-
Notifications
You must be signed in to change notification settings - Fork 25
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
Hypothesis #71
Hypothesis #71
Conversation
@Zac-HD review, please :) |
I had it on my list for a long time and your email reminded me about it. I don't want to drop |
I know that
Unfortunately this is inherent in the "generate a list of inputs" design; Hypothesis works by executing each input and seeing what happens. For example if Hypothesis generates an input of type e.g. I'll open a PR soon with an API I'd recommend: @deal.raises(ZeroDivisionError)
def div(a: int, b: int) -> float:
assert a == 1
return a / b
# BEFORE
for case in deal.cases(div, kwargs=dict(a=1)):
case()
# AFTER
div.get_test(a=1)() # passes
div.get_test(a=st.just(1))() # passes
div.get_test()() # fails with a=0 That's implemented along the lines of div = ...
def get_test(**kwargs):
kwargs = {k: v if isinstance(st.SearchStrategy) else st.just(v) for k, v in kwargs.items()}
@given(st.fixed_dictionaries(kwargs))
def test(kw):
deal.hypothesis(div)(**kw) # as already in this PR
# add some magic to make the Hypothesis database work nicely here
return test
div.get_test = get_test HypothesisWorks/hypothesis#2679 has some more details about a recent conversation with the |
Hey, thank you for helping me there! I know about the limitations of the current implementation of
I'm not sure how limiting the API of What about reproducing failures, it definitely doesn't depend on how you generate the test cases. I have on my list to provide the To summarize, I understand the limitations and see the current API as the most simple one to understand which I value more than a bit more beautiful failure reports. On the other hand, I want to provide the extendability and room to use the full power of Hypothesis at need. This is why I'm happy to provide the fulfill function as in this PR. Is something wrong with this implementation of the wrapper? |
Your implementation of I really like your API-first approach, and working with students to design it is great! I also think you're right about the conceptual challenges of decorators... we do need them in general, but it's great to have simpler interfaces where we can. That's why I thought that a This is also important because import deal
from typing import Callable
@deal.pre(lambda callback: callable(callback))
def do_call(callback: Callable[[], int]) -> int:
return callback()
# This fails, because `case()` is called outside the Hypothesis data-generation
# scope, and the generated `callback` function is therefore unable to generate
# a return value!
for case in deal.cases(do_call):
case()
# But returning a Hypothesis-wrapped test function, which users can call like
# they would each case, does work:
test_do_call = deal.get_hypothesis_test(do_call)
test_do_call() # or let pytest handle it ;-) I've pushed an implementation you can run to hypothesis...Zac-HD:hypothesis That patch also makes Unfortunately the iterator-based approach is not feasible for us... I tried something very similar for HypoFuzz as a way to interleave examples from multiple tests, but changing that much of the engine would make it unmaintainable. It would also conflict with our current requirement that wrapped test functions may only return None (not e.g. a generator), which continues to catch user errors dealing with async tests and certain test runners 😞 |
That's interesting points, thank you. I need a bit of time to think about it. I'm always hard to change something when it is about breaking API :) Could you tell me a bit more about your example, please? Sorry, I didn't get it. Why Hypothesis can't generate |
Please, have a look at the new implementation. I've made it possible to use @deal.cases(div2)
def test_decorator_div2_smoke(case):
case() TODO:
|
This looks good! Some quick improvements:
I think a simpler API is possible though: why not have
# current
@deal.cases(div2)
def test_decorator_div2_smoke(case):
case()
# proposed
test_decorator_div2_smoke = deal.make_hypothesis_test(div2) This reduces the API surface to learn, because all of the usual Hypothesis tools work in the obvious ways - you can |
Thank you for the review! I really appreciate your help :)
I'm not sure. I expect it to generate new values each time as QuickTest does. Probably, we can change it later, in a breaking release.
I'm thinking even about putting it into the same And the same with
That's a good idea. Yes, I think we can do it now. Let me see if we can leave it for iteration but still keep it redefinable via the
Do we need such an API if we have a decorator? I'm not sure. You already can define custom strategies, custom settings, wrap it into more decorators from Hypothesis, add additional operations, wrap the call of
I'll update the documentation and move the iterator into the "advanced" documentation but I won't break it in any way, at least for now. Maybe, I have not many users but I see no reason to break the API or public behavior. Sorry, iteration protocol stays and returns the actual cases. Also, for a while it stays in
I believe, it all will work after I make it a single dispatch: test = deal.cases(div)
# This one is tricky because Hypothesis doesn't merge settings.
# This is why I provide `settings` argument.
test = settings(...)(test)
test = seed(0)(test)
atheris.Setup(sys.argv, test) I think I see the picture. Let me take care of single dispatch, support for passing fixtures in side the wrapped function, proper phases for the decorator, and the decorator-centric documentation, and we're ready to go :) |
Oooh, yeah, the single-dispatch approach is lovely. Definitely do that! Keeping iterable Overall, thanks for all your work on this - I feel like I've just dumped piles of not-always-positive feedback, and you've come to a fantastic design and API at the end. Very very nice work 😄 |
No, we don't need it; even |
That's a great idea! Let me think about how to make it better, not only for the
It's ok, I'm the most not-welcoming-changes person here. I said right at the beginning that I am stubborn when it comes to breaking API. I'm happy that we managed to make it friendly and compatible.
Right, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor comments below, but overall this looks fantastic! I look forward to recommending Deal to anyone who wants a property-testable contracts library 😃
func: typing.Callable, *, | ||
count: int = 50, | ||
kwargs: typing.Dict[str, typing.Any] = None, | ||
check_types: bool = True, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realise there are backwards compatiblity issues, but I'd rename check_types
to check_return_type
and make the former a deprecated alias. As-is, the name implies that argument types are also checked (or that =False
disables such a check).
""" | ||
Iterate over `iterator` disabling tracer on every iteration step. | ||
This is a trick to avoid using our coverage tracer when calling hypothesis machinery. | ||
Without it, testing is about 3 times slower. | ||
""" | ||
iterator = iter(items) | ||
default_trace = sys.gettrace() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd move this inside the loop body, in case the user has e.g. started a debugger between your first invocation of this function and the end of iteration. It's a little slower, but avoids a weird-if-rare edge case.
docs/details/tests.md
Outdated
|
||
## Fuzzing | ||
|
||
[Fuzzer](https://en.wikipedia.org/wiki/Fuzzing) is when an external tool or library (fuzzer) generates a bunch of random data in hope to break your program. That means, fuzzing requires a lot of resources and is performance-critical. This is why most of the fuzzers are written on C. However, there are few Python wrappers for existing fuzzers, with awful interface but working: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with awful interface but working
Maybe we can make this part a bit more friendly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, let's be more friendly :) The reason why I say "awful" is that I still can't figure out why to pass sys.argv
into atheris. I tried to search issues, readme, just to experiment with it, try to pass --help
inside. No effect. The only thing I managed to do through atheris API is just to run the test function with defaults.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point, and something I've thought of before. They are actually separate for a reason though:
atheris.Setup()
might modify the input argument list, by removing arguments handled by libFuzzer. This allows other argument parsing libraries to subsequently be called and not think there are a lot of excess arguments.
We use this feature inside of Google. We call
atheris.Setup()
to remove libFuzzer arguments, then passsys.argv
to our argument-parsing code to initialize Google infrastructure, then finally callatheris.Fuzz()
.
Originally posted by @IanPudney in google/atheris#8 (comment)
I do agree that the Python-level user experience of current fuzzers could be improved, and hence https://hypofuzz.com/ exists, but that's more a question of "who are current fuzzers designed for" than "are they any good" 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Zac-HD is hypofuzz
that project you told me about in the summer? This looks really interesting!
I would love to test this out whenever you need my help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the one! I'll be in touch in the new year 😁
With Atheris, try passing |
Thank you! |
This looks fantastic - congratulations @orsinium! I'll add a link to Hypothesis' docs shortly 😃 |
Thank you! |
Better compatibility with Hypothesis. Based on the official Hypothesis' integration with dpcontracts.