-
Notifications
You must be signed in to change notification settings - Fork 66
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
test: refactor concurrency test using orchestrate #709
Conversation
google/cloud/ndb/_cache.py
Outdated
@@ -34,6 +34,14 @@ | |||
log = logging.getLogger(__name__) | |||
|
|||
|
|||
def _syncpoint_692(): |
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 name is super puzzling -- can you provide an explanation, or a link to an issue, in the comment? Even, better, give it a name that indicates where it is used, e.g., _syncpoint_update_key
, or something similar.
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 see the link in the testcase below to #692: seems like it would be good to repeat here. Also, I wonder about wrapping it in something like if __debug__
, to avoid the overhead of even an empty python function call. Of course, that would require geting __debug__
set during the test runs (IIRC it is set by default, and only switched off with -O
on the command line, which would also disable assert
statments).
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.
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.
Poking around, it looks like you're correct about when __debug__
is True
. I'm not sure how common it is in practice to pass -O
in production. I know I've never made a habit of it.
Another solution I thought about was getting rid of the no-op function and using something like # pragma: SYNCPOINT syncname
to declare syncpoints, then do some coverage
style postprocessing to insert the calls during testing. If you think the current implementation is mind-bending, though...
It's almost certainly not even something worth considering, unless orchestrate
gets some kind of life outside of just NDB
.
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.
Why not build this on sys.settrace
? Then you wouldn't have to modify the code under tests or cause extra calls and associated overhead.
You could pass in breakpoints for each functions as file-line locations to yield.
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.
Well, I didn't know about it, for starters. I can take a peek. On first blush, it seems like using line numbers is pretty fragile. I'd want a more robust way to define the syncpoint/breakpoint.
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.
Looks like I could probably use this to implement the # pragma: SYNCPOINT
idea.
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 actually have something like this working now. Will try and make it presentable tomorrow.
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.
An interesting side-effect of using sys.settrace
is it breaks coverage
, since it also uses sys.settrace
. ;-)
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.
tests can help with this. | ||
|
||
As soon as any error or failure is detected, no more scenarios are run | ||
and that error is propagated to the main thread. |
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 really appreciate the extensive writeup here of the rationale. Even with its help, following the implementation below is more than a bit brain-bending.
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.
Sorry. I was trying to be sensitive to the fact that it would be difficult to follow without a lot of explanation.
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 wonder if using the same test function twice contributes to the bendy-ness: maybe using a simple test (no syncpoints) along with the one you have already would clarify any? Maybe as an additional example?
Is the |
28dee77
to
e9e33fe
Compare
I missed the Kokoro error, so I can't comment specifically on whatever you saw earlier. There is a little flakiness to lots of the system tests due to eventual consistency. |
def advance(self): | ||
"""Allow a call to the wrapper to proceed. | ||
if os.environ.get("REDIS_CACHE_URL"): | ||
yield redis_cache |
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.
Coverage issue here.
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.
Added "pragma: NO COVER". Don't want coverage to depend on which caches are available.
""" | ||
self._futures.popleft().set_result(None) | ||
def memcache_cache(): | ||
return global_cache_module.MemcacheCache.from_environment() |
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.
Uncovered.
if syncpoints: | ||
syncpoints.pop() | ||
else: | ||
do_nothing() # pragma: SYNCPOINT |
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 shows a limitation of the current settrace
approach, in that the comment must be on a reachable line, so the call to do_nothing
is just here to have something to hang the pragma off of. It would be more satisfying to be able to write something like:
if syncpoints:
syncpoints.pop()
else:
# pragma: SYNCPOINT
test_calls.append(name)
I spent some time trying to figure out how to make this possible and concluded it was complicated enough to leave out for the first pass. Unfortunately, neither tokenize
nor ast
, on their own, provide enough information to figure out which side of the else
the pragma is on and assign to a line of code. It should be possible to use both to figure it out, but it's not trivial.
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 think it's fine for the pragma to mark the syncpoint.
""" | ||
|
||
|
||
def _get_syncpoints(filename): |
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 very simplistic implementation that requires every pragma be located on a reachable line of code. This is probably sufficient. A more sophisticated algorithm that allowed pragmas to appear on their own lines is possible, but not trivial. See this 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.
ISTM that orchestrate
might find its home in python-testutils
-- other code with tricksy concurrency (e.g., pubsub flow control, firestore watch, etc.) might find it useful.
That's indeed a possibility. For example, see one of the unit tests for publisher flow controller that tests a specific sequence of There are quite a few |
Towards #691