-
Notifications
You must be signed in to change notification settings - Fork 4.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
init: add shared target #9008
init: add shared target #9008
Conversation
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
/assign @htuch |
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.
LGTM, @mergeconflict do you have any thoughts?
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.
Implementation looks good. I'd like to spend a bit more time focusing on cleaning up the tests; see comments below. Let me know when you're ready and I'll take another look to try and figure out whether we're covering all the possible interesting interaction sequences.
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 @mergeconflict for your advises.
I will revert the WrapUnique and leave the potential actions in other PR/Issue
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
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.
LGTM; will give @mergeconflict a chance to look before merging.
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
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 you're almost there, thanks for working on this! Just a couple more bits of cleanup.
class ExpectableSharedTargetImpl : public SharedTargetImpl { | ||
public: | ||
ExpectableSharedTargetImpl(absl::string_view name = "test"); | ||
ExpectableSharedTargetImpl(absl::string_view name, InitializeFn fn); |
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 ctor defeats the purpose of the mock. The idea of this class is that it's wired up to always call the mocked initialize
method, that you can set expectations on using expectInitialize
. I don't think you actually use this anyway, so it should be easy to delete.
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.
Right. Didn't use it in this PR. I will delete it for simplicity
test/common/init/target_impl_test.cc
Outdated
// SharedTarget acts as TargetImpl if single watcher is provided. | ||
TEST(InitSharedTargetTestImpl, InitializeSingleWatcher) { | ||
InSequence s; | ||
|
||
ExpectableSharedTargetImpl target; | ||
ExpectableWatcherImpl watcher; | ||
|
||
// initializing the target through its handle should invoke initialize()... | ||
target.expectInitialize(); | ||
EXPECT_TRUE(target.createHandle("test")->initialize(watcher)); | ||
|
||
// calling ready() on the target should invoke the saved watcher handle... | ||
watcher.expectReady(); | ||
EXPECT_TRUE(target.ready()); | ||
|
||
// calling ready() a second time should have no effect. | ||
watcher.expectReady().Times(0); | ||
target.ready(); | ||
} | ||
|
||
// Initializing TargetHandle return false if uninitialized SharedTarget is destroyed. | ||
TEST(InitSharedTargetTestImpl, InitializeWhenUnavailable) { | ||
InSequence s; | ||
ExpectableWatcherImpl watcher; | ||
TargetHandlePtr handle; | ||
{ | ||
ExpectableSharedTargetImpl target; | ||
|
||
// initializing the target after it's been destroyed should do nothing. | ||
handle = target.createHandle("test"); | ||
target.expectInitialize().Times(0); | ||
} | ||
EXPECT_FALSE(handle->initialize(watcher)); | ||
} | ||
|
||
// Initializing TargetHandle return false if initialized SharedTarget is destroyed. | ||
TEST(InitSharedTargetTestImpl, ReInitializeWhenUnavailable) { | ||
InSequence s; | ||
ExpectableWatcherImpl w; | ||
TargetHandlePtr handle; | ||
{ | ||
ExpectableSharedTargetImpl target; | ||
|
||
target.expectInitialize(); | ||
TargetHandlePtr handle1 = target.createHandle("m1"); | ||
ExpectableWatcherImpl w1; | ||
EXPECT_TRUE(handle1->initialize(w1)); | ||
|
||
// initializing the target after it's been destroyed should do nothing. | ||
handle = target.createHandle("m2"); | ||
target.expectInitialize().Times(0); | ||
// target destroyed | ||
} | ||
EXPECT_FALSE(handle->initialize(w)); | ||
} |
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.
Rather than copy-pasting these tests from above, can you use https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#typed-tests to refactor? I think all the invariants held by TargetImpl
must also be held by SharedTargetImpl
.
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 caught me :)
I delete the expectation from target.ready()
Will recover and use typed test
EXPECT_FALSE(handle->initialize(w)); | ||
} | ||
|
||
// SharedTarget notifies multiple watchers. |
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.
So these two new tests capture these two sequences, if I'm reading correctly:
initialize(w1)
initialize(w2)
ready
and
initialize(w1)
ready
initialize(w2)
I guess you could also write one for
ready
initialize(w1)
initialize(w2)
But that's probably not very interesting... I can't think of any more interesting cases, so I think you're good. Can you think of anything?
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.
It's not hard to support a ready() prior to initialize any watcher.
The confusing part what are the expected consequences.
MUST: notify the following watchers
Question: Should the initialization function defined in SharedTarget should be called?
I am going to leave the questionable behavior as undefined and set no expectation.
Let me know what you think
@mergeconflict Thank you! I am updating the PR |
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
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.
Looking good, just a few last nits now. Thanks!
@@ -73,7 +73,7 @@ TargetHandlePtr SharedTargetImpl::createHandle(absl::string_view handle_name) co | |||
|
|||
bool SharedTargetImpl::ready() { | |||
initialized_ = true; | |||
bool all_notified = true; | |||
bool all_notified = !watcher_handles_.empty(); |
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.
Interesting... I guess this is for consistency with TargetImpl::ready
returning false if there's no watcher handle. But now I'm wondering if I made the right choice there. What do you think?
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.
Yes it is.
It seems no production code is relying on this return value.
It's OK to leave the return value here since so far it is easy to maintain.
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
@mergeconflict So the latest tests are
|
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.
LGTM, thanks for all the work on this!
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 @lambdai! Also thanks @mergeconflict for the deep dive.
template <typename T> class TargetImplTest : public ::testing::Test {}; | ||
TYPED_TEST_SUITE_P(TargetImplTest); | ||
|
||
template <typename T> std::string getName() { return ""; } |
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.
FWIW, you could avoid templates here and do something similar to what was done in https://github.com/envoyproxy/envoy/blob/master/test/common/config/subscription_test_harness.h, but I think this isn't too bad and makes things nice and clean.
Full context is here The above PR is split into #9008 and this one. In this PR envoy will maintain only one copy of rds config for each resource name. Also fixed a the bug of early listener notified ready describe in #8781 The test case is included in this PR. Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Full context is here The above PR is split into envoyproxy#9008 and this one. In this PR envoy will maintain only one copy of rds config for each resource name. Also fixed a the bug of early listener notified ready describe in envoyproxy#8781 The test case is included in this PR. Signed-off-by: Yuchen Dai <silentdai@gmail.com> Signed-off-by: Prakhar <prakhar_au@yahoo.com>
Signed-off-by: Yuchen Dai silentdai@gmail.com
Extracted from #8523
@mergeconflict mentioned we can also maintain a collection of
TargetImpl
(and a initialize_ flag, std::once_flag) at the target owner.I agree it works but a SharedTarget provides stronger cohesion.
Follow up: