Skip to content

Commit

Permalink
[PH2][Test][Promise]
Browse files Browse the repository at this point in the history
  • Loading branch information
tanvi-jagtap committed Jan 8, 2025
2 parents 4707d17 + db61551 commit c8d64f7
Show file tree
Hide file tree
Showing 6 changed files with 440 additions and 39 deletions.
2 changes: 2 additions & 0 deletions build_autogenerated.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 41 additions & 9 deletions src/core/lib/promise/if.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,47 @@ namespace grpc_core {

namespace promise_detail {

// If promise combinator.
//
// If(C condition, T if_true, F if_false)
//
// Takes exactly 3 inputs
//
// The first input C can be one of the following
// 1. A bool variable or constant.
// 2. A promise that returns Poll<bool>
// 3. A promise factory that returns a promise that returns Poll<bool>
// 4. A promise that returns Poll<absl::StatusOr<bool>>
// 5. A promise factory that returns a promise that returns
// Poll<absl::StatusOr<bool>>
//
// The second and third inputs can be one of the following
// 1. Promise
// 2. Promise Factory
// The second and third promises must have the same return type.
//
// The If combinator works in the following way
// 1. It processes the first input first. If the first input is a promise or a
// promise factory, the promise is executed. The return value of this execution
// could either be Poll<bool> or Poll<absl::StatusOr<bool>> . If the first input
// is a bool, it is taken as it is.
// 2. If the first promise returns Pending{} , the second and third promises are
// not executed.
// 3. If the promise returns any failure status , the second and third promises
// are not executed.
// 4. If the return value of the first promise is equivalent to true, the
// combinator executes the second promise (if_true).
// 5. If the return value of the first promise is equivalent to false, the
// combinator executes the third promise (if_false).
//
// Both the condition and the if_true/if_false promises will be executed
// serially on the same thread.
//
// If first input is a constant, it's guaranteed that one of the promise
// factories if_true or if_false will be evaluated before returning from this
// function. This makes it safe to capture lambda arguments in the promise
// factory by reference.

template <typename CallPoll, typename T, typename F>
GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION inline typename CallPoll::PollResult
ChooseIf(CallPoll call_poll, bool result, T* if_true, F* if_false) {
Expand Down Expand Up @@ -190,15 +231,6 @@ class If<bool, T, F> {

} // namespace promise_detail

// If promise combinator.
// Takes 3 promise factories, and evaluates the first.
// If it returns failure, returns failure for the entire combinator.
// If it returns true, evaluates the second promise.
// If it returns false, evaluates the third promise.
// If C is a constant, it's guaranteed that one of the promise factories
// if_true or if_false will be evaluated before returning from this function.
// This makes it safe to capture lambda arguments in the promise factory by
// reference.
template <typename C, typename T, typename F>
GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION inline promise_detail::If<C, T, F> If(
C condition, T if_true, F if_false) {
Expand Down
1 change: 1 addition & 0 deletions test/core/promise/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ grpc_cc_test(
"poll_matcher",
"//src/core:join",
"//src/core:poll",
"//src/core:status_flag",
],
)

Expand Down
77 changes: 74 additions & 3 deletions test/core/promise/join_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,93 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/core/lib/promise/poll.h"
#include "src/core/lib/promise/status_flag.h"
#include "test/core/promise/poll_matcher.h"

namespace grpc_core {

TEST(JoinTest, Join1) {
EXPECT_THAT(Join([] { return 3; })(), IsReady(std::make_tuple(3)));
std::string execution_order;
EXPECT_THAT(Join([&execution_order]() mutable -> Poll<int> {
absl::StrAppend(&execution_order, "1");
return 3;
})(),
IsReady(std::make_tuple(3)));
EXPECT_STREQ(execution_order.c_str(), "1");
}

TEST(JoinTest, Join2) {
EXPECT_THAT(Join([] { return 3; }, [] { return 4; })(),
std::string execution_order;
EXPECT_THAT(Join(
[&execution_order]() mutable -> Poll<int> {
absl::StrAppend(&execution_order, "3");
return 3;
},
[&execution_order]() mutable -> Poll<int> {
absl::StrAppend(&execution_order, "4");
return 4;
})(),
IsReady(std::make_tuple(3, 4)));
EXPECT_STREQ(execution_order.c_str(), "34");
}

TEST(JoinTest, Join3) {
EXPECT_THAT(Join([] { return 3; }, [] { return 4; }, [] { return 5; })(),
std::string execution_order;
EXPECT_THAT(Join(
[&execution_order]() mutable -> Poll<int> {
absl::StrAppend(&execution_order, "3");
return 3;
},
[&execution_order]() mutable -> Poll<int> {
absl::StrAppend(&execution_order, "4");
return 4;
},
[&execution_order]() mutable -> Poll<int> {
absl::StrAppend(&execution_order, "5");
return 5;
})(),
IsReady(std::make_tuple(3, 4, 5)));
EXPECT_STREQ(execution_order.c_str(), "345");
}

TEST(JoinTest, JoinPendingFailure) {
// 1. Assert that one failing promise in the Join should not cancel the
// execution of the following promises.
// 2. Also assert that only the Pending{} promise is re-run when the Join is
// run a second time.
std::string execution_order;
auto first_promise = [&execution_order]() mutable -> Poll<int> {
absl::StrAppend(&execution_order, "1");
return 1;
};
auto second_promise = [&execution_order]() mutable -> Poll<StatusFlag> {
absl::StrAppend(&execution_order, "2");
return Failure{};
};
auto third_promise = [&execution_order,
once = false]() mutable -> Poll<std::string> {
absl::StrAppend(&execution_order, "3");
if (once) return "Hello World";
once = true;
return Pending{};
};

auto join_1_2_3 = Join(first_promise, second_promise, third_promise);

using JoinTuple = std::tuple<int, StatusFlag, std::string>;
Poll<JoinTuple> first_execution = join_1_2_3();
EXPECT_FALSE(first_execution.ready());
absl::StrAppend(&execution_order, "0");

Poll<JoinTuple> second_execution = join_1_2_3();
EXPECT_TRUE(second_execution.ready());

JoinTuple& tuple = *(second_execution.value_if_ready());
EXPECT_EQ(std::get<0>(tuple), 1);
EXPECT_EQ(std::get<1>(tuple), Failure{});
EXPECT_STREQ(std::get<2>(tuple).c_str(), "Hello World");

EXPECT_STREQ(execution_order.c_str(), "12303");
}

} // namespace grpc_core
Expand Down
157 changes: 157 additions & 0 deletions test/core/promise/try_join_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,163 @@

namespace grpc_core {

TEST(TryJoinTestBasic, TryJoinPendingFour) {
std::string execution_order;
bool pending_3 = true;
bool pending_4 = true;
bool pending_5 = true;
bool pending_6 = true;
auto try_join_combinator = TryJoin<absl::StatusOr>(
[&execution_order, &pending_3]() mutable -> Poll<absl::StatusOr<int>> {
absl::StrAppend(&execution_order, "3");
if (pending_3) {
absl::StrAppend(&execution_order, "P");
return Pending{};
}
return 3;
},
[&execution_order, &pending_4]() mutable -> Poll<absl::StatusOr<double>> {
absl::StrAppend(&execution_order, "4");
if (pending_4) {
absl::StrAppend(&execution_order, "P");
return Pending{};
}
return 4.0;
},
[&execution_order,
&pending_5]() mutable -> Poll<absl::StatusOr<std::string>> {
absl::StrAppend(&execution_order, "5");
if (pending_5) {
absl::StrAppend(&execution_order, "P");
return Pending{};
}
return "5";
},
[&execution_order, &pending_6]() mutable -> Poll<absl::StatusOr<int>> {
absl::StrAppend(&execution_order, "6");
if (pending_6) {
absl::StrAppend(&execution_order, "P");
return Pending{};
}
return 6;
});

// Asserts
// 1. All pending promises are re-run when the TryJoin is executed.
// 2. Resolved promises (promises which have returned a value) should not be
// re-run when TryJoin is executed.
// 3. The TryJoin should return pending until all promises are resolved.
// 4. The TryJoin should return the tuple of values when all promises are
// resolved.
// 5. The order in which the promises are resolved should not matter. If the
// Nth promise is resolved, but the previous N promises are not resolved,
// they should still be re-run.
// 6. TryJoin returns success status only if all promises return success
// status.

// Execution 1 : All promises are pending. All should be run once.
Poll<absl::StatusOr<std::tuple<int, double, std::string, int>>> retval =
try_join_combinator();
EXPECT_TRUE(retval.pending());
// All promises are Pending
EXPECT_STREQ(execution_order.c_str(), "3P4P5P6P");

// Execution 2 : All promises should be run once. 3 gets resolved.
execution_order.clear();
pending_3 = false;
retval = try_join_combinator();
EXPECT_TRUE(retval.pending());
EXPECT_STREQ(execution_order.c_str(), "34P5P6P");

// Execution 3 : All promises other than 3 should be run. 4 gets resolved.
execution_order.clear();
pending_4 = false;
retval = try_join_combinator();
EXPECT_TRUE(retval.pending());
EXPECT_STREQ(execution_order.c_str(), "45P6P"); // 3 should not be re-run.

// Execution 4 : Order changed. 5 will still be pending. 6 will be resolved.
execution_order.clear();
pending_6 = false;
retval = try_join_combinator();
EXPECT_TRUE(retval.pending());
EXPECT_STREQ(execution_order.c_str(), "5P6");

// Execution 5 : Only 5 should be run. And 5 gets resolved.
execution_order.clear();
pending_5 = false;
retval = try_join_combinator();
EXPECT_TRUE(retval.ready()); // All promises are resolved.
EXPECT_STREQ(execution_order.c_str(), "5");

EXPECT_TRUE(retval.value().ok()); // All promises are a success.
EXPECT_EQ(retval.value().value(), std::make_tuple(3, 4.0, "5", 6));
}

TEST(TryJoinTestBasic, TryJoinPendingFailure) {
std::string execution_order;
bool pending_3 = true;
bool pending_4 = true;
bool pending_5 = true;
auto try_join_combinator = TryJoin<absl::StatusOr>(
[&execution_order, &pending_3]() mutable -> Poll<absl::StatusOr<int>> {
absl::StrAppend(&execution_order, "3");
if (pending_3) {
absl::StrAppend(&execution_order, "P");
return Pending{};
}
return 3;
},
[&execution_order, &pending_4]() mutable -> Poll<absl::StatusOr<double>> {
absl::StrAppend(&execution_order, "4");
if (pending_4) {
absl::StrAppend(&execution_order, "P");
return Pending{};
}
return absl::InternalError("Promise Errror");
},
[&execution_order,
&pending_5]() mutable -> Poll<absl::StatusOr<std::string>> {
absl::StrAppend(&execution_order, "5");
if (pending_5) {
absl::StrAppend(&execution_order, "P");
return Pending{};
}
return "5";
});

// Asserts
// 1. One failing promise in the TryJoin should cancel the execution of the
// remaining promises even if they are pending.
// 2. The TryJoin should not return a tuple if any of the promises fail. It
// should return the correct failure status.

// Execution 1 : All promises are pending. All should be run once.
Poll<absl::StatusOr<std::tuple<int, double, std::string>>> retval =
try_join_combinator();
EXPECT_TRUE(retval.pending());
// All promises are Pending
EXPECT_STREQ(execution_order.c_str(), "3P4P5P");

// Execution 2 : All promises should be run once. 3 gets resolved.
execution_order.clear();
pending_3 = false;
retval = try_join_combinator();
EXPECT_TRUE(retval.pending());
EXPECT_STREQ(execution_order.c_str(), "34P5P");

// Execution 3 : All promises other than 3 should be run. 4 fails.
// 5 should not be run (even though it is pending) because 4 failed.
execution_order.clear();
pending_4 = false;
retval = try_join_combinator();
EXPECT_TRUE(retval.ready());
EXPECT_STREQ(execution_order.c_str(), "4");

EXPECT_EQ(retval.value().status().code(), absl::StatusCode::kInternal);
EXPECT_EQ(retval.value().status().message(), "Promise Errror");
}

struct AbslStatusTraits {
template <typename... Promises>
static auto TryJoinImpl(Promises... promises) {
Expand Down
Loading

0 comments on commit c8d64f7

Please sign in to comment.