Commit 1d98189f authored by Lewis Baker's avatar Lewis Baker Committed by Facebook GitHub Bot

Add CoInvoke() GMock helper for writing safe coroutine lambda versions

Summary:
Adds a new CoInvoke() helper function that should be used instead of ::testing::Invoke() when defining mock methods using coroutine lambdas.

Use of Invoke() in a GMock definition that calls a coroutine-lambda that
contains captures will result in the returned Task holding a dangling reference
to a copy of the lambda.

The CoInvoke() helper instead, ensures that the coroutine created by the
lambda holds on to a reference to a copy of the lambda that is kept alive
until the coroutine completes by invoking the lambda using the folly::coro::co_invoke()
utility function instead of directly calling operator().

Reviewed By: andriigrynenko

Differential Revision: D21578404

fbshipit-source-id: 3322740efd00596c2311530a166ef2d5301c6b89
parent 8cc979ba
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/experimental/coro/Task.h>
#include <folly/portability/GMock.h>
namespace folly {
namespace coro {
namespace gmock_helpers {
// This helper function is intended for use in GMock implementations where the
// implementation of the method is a coroutine lambda.
//
// The GMock framework internally always takes a copy of an action/lambda
// before invoking it to prevent cases where invoking the method might end
// up destroying itself.
//
// However, this is problematic for coroutine-lambdas-with-captures as the
// return-value from invoking a coroutine lambda will typically capture a
// reference to the copy of the lambda which will immediately become a dangling
// reference as soon as the mocking framework returns that value to the caller.
//
// Use this action-factory instead of Invoke() when passing coroutine-lambdas
// to mock definitions to ensure that a copy of the lambda is kept alive until
// the coroutine completes. It does this by invoking the lambda using the
// folly::coro::co_invoke() helper instead of directly invoking the lambda.
//
//
// Example:
// using namespace ::testing
// using namespace folly::coro::gmock_helpers;
//
// MockFoo mock;
// int fooCallCount = 0;
//
// EXPECT_CALL(mock, foo(_))
// .WillRepeatedly(CoInvoke([&](int x) -> folly::coro::Task<int> {
// ++fooCallCount;
// co_return x + 1;
// }));
//
template <typename Lambda>
auto CoInvoke(Lambda&& lambda) {
return ::testing::Invoke(
[capturedLambda = static_cast<Lambda&&>(lambda)](auto&&... args) {
return folly::coro::co_invoke(
capturedLambda, static_cast<decltype(args)>(args)...);
});
}
} // namespace gmock_helpers
} // namespace coro
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/Portability.h>
#if FOLLY_HAS_COROUTINES
#include <folly/experimental/coro/GmockHelpers.h>
#include <folly/experimental/coro/BlockingWait.h>
#include <folly/experimental/coro/Task.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
using namespace ::testing;
using namespace folly::coro::gmock_helpers;
namespace {
class Foo {
public:
virtual ~Foo() = default;
virtual folly::coro::Task<std::vector<std::string>> getValues() = 0;
};
class MockFoo : Foo {
public:
MOCK_METHOD0(getValues, folly::coro::Task<std::vector<std::string>>());
};
} // namespace
TEST(CoroLambdaGtest, CoInvokeAvoidsDanglingReferences) {
MockFoo mock;
const std::vector<std::string> values{"1", "2", "3"};
EXPECT_CALL(mock, getValues())
.WillRepeatedly(
CoInvoke([&values]() -> folly::coro::Task<std::vector<std::string>> {
co_return values;
}));
auto ret = folly::coro::blockingWait(mock.getValues());
EXPECT_EQ(ret, values);
}
#endif // FOLLY_HAS_COROUTINES
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment