-
-
Notifications
You must be signed in to change notification settings - Fork 142
Accept user-defined strategies #2061
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
Conversation
Will use in the next commits. We could generalize this to accept a mapping of types to strategies, but I want to start with something simple, since this is complicated code.
This should make it easier to test and extend. Added tests for the current behavior when a type has an existing strategy and when it does not.
No functional change yet. I want to pass in a strategy in those settings. I'm making `Settings` public, which should be fine since we mention its fields in the docs.
This currently overrides the strategy for `MyContainer`. However, it does not yet override the strategy for ancestor interfaces. Coming up next.
Want to change it to use the strategy from the settings.
I'm calling it `container_strategy` to leave room for future extensions that would allow configuring the pure function strategy, etc.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #2061 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 80 81 +1
Lines 2485 2576 +91
Branches 437 44 -393
==========================================
+ Hits 2485 2576 +91 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
No idea how to format this better.
sobolevn
left a comment
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.
Thank you! Great work! A couple of nits.
returns/contrib/hypothesis/laws.py
Outdated
|
|
||
| """ | ||
| settings = _Settings( | ||
| settings = Settings( |
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.
Maybe we should assert that users cannot pass both use_init and container_stratregy 🤔 ?
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've added a __post_init__ check for Settings. There are already overloads for check_all_laws, so that function should be safe. Ideally, we would make the inputs mutually exclusive in the types, but I don't know how to do that in a backward-compatible way. Leaving it at this.
returns/contrib/hypothesis/laws.py
Outdated
|
|
||
| @final | ||
| class _Settings(NamedTuple): | ||
| class Settings(NamedTuple): |
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.
Let's also document Settings in hypothesis_plugins.rst since it is now public.
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 made it public just so that I could use it in tests :P. I'm making it private again, because check_all_laws accepts direct arguments, not a settings object. So, I don't think it makes much sense to document the class in public and unnecessarily make the class harder to change in the future. Lmk if you feel strongly.
Ideally, we would enforce the mutually exclusive settings in the types, but I'm leaving it at this for now. I'm making settings private again, because `check_all_laws` accepts direct arguments, not a settings object. So, I don't think it makes much sense to document the class in public and make it harder to change in the future. I'd made it public just so that I could use it in tests.
sobolevn
left a comment
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.
Thanks!
tl;dr: This PR adds support for user-defined strategies in the settings of
check_all_laws. Below is a root cause analysis for why we can get flaky tests if we directly register custom strategies withhypothesis.Why custom strategies registered with
hypothesiscan lead to spurious test failuresProblem: If a user registers a HKT that is not a
ContainerN, that will still be considered byhypothesisas a subclass ofKindN[ContainerN, ...].Example: Say we have
_Wrapperthat is aKindNbut not aContainerNand we register a custom strategy for it.If we run
tests/test_laws.py::test_io_containern_associative_law,hypothesiswill needKindN['ContainerN', _NewType1, _SecondType, _ThirdType]and will try all the types in the global type lookup that are subclasses of the type. Unfortunately,_Wrapperwill fit the bill:So,
hypothesiswill create a_Wrapperas an instance ofKindN[ContainerN, ...], which will crash the test:This means we can't risk having any descendant of
KindNin the_global_type_lookup. We should discourage users from directly registering their own strategies for their containers.Current Behavior: We drop any user-defined strategies
The good news is that we currently don't allow it right now. We used to allow users to register custom strategies with
hypothesis, but we stopped in #1893, probably because of the difficulties above. This means any strategy registered usingst.register_type_strategygets ignored.Unfortunately, there is no way to override the strategies created by the plugin.
This PR: Register strategies only when running laws
How to register a custom strategy for a container without unintentionally using it for
KindN?Proposed solution: Register the user-defined strategy only when running laws for that container.
We accept the strategy in
check_all_laws. This gives us the flexibility to try different strategies without modifying the source code.Alternative designs:
hypothesisbehavior was pretty frustrating because a change in one test broke unrelated tests. The whole point ofreturnsis to avoid global mutation :)Implementation:
Checklist
CHANGELOG.md