// Copyright 2018 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include #include "unittest_utils.h" namespace { void async_invoke_callback_no_args( uint64_t* run_count, fit::function callback) { std::thread([run_count, callback = std::move(callback)]() mutable { (*run_count)++; callback(); }).detach(); } void async_invoke_callback_one_arg( uint64_t* run_count, fit::function callback) { std::thread([run_count, callback = std::move(callback)]() mutable { (*run_count)++; callback("Hippopotamus"); }).detach(); } void async_invoke_callback_two_args( uint64_t* run_count, fit::function callback) { std::thread([run_count, callback = std::move(callback)]() mutable { (*run_count)++; callback("What do you get when you multiply six by nine?", 42); }).detach(); } bool bridge_construction_and_assignment() { BEGIN_TEST; // Create a new bridge. fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_TRUE(bridge.consumer); // Can move-construct. fit::bridge bridge2(std::move(bridge)); EXPECT_TRUE(bridge2.completer); EXPECT_TRUE(bridge2.consumer); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer); // Can move-assign. bridge = std::move(bridge2); EXPECT_TRUE(bridge.completer); EXPECT_TRUE(bridge.consumer); EXPECT_FALSE(bridge2.completer); EXPECT_FALSE(bridge2.consumer); // It still works. bridge.completer.complete_error("Test"); EXPECT_FALSE(bridge.completer); fit::result result = fit::run_single_threaded(bridge.consumer.promise()); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::error, result.state()); EXPECT_STR_EQ("Test", result.error()); END_TEST; } bool completer_construction_and_assignment() { BEGIN_TEST; // Default constructed completer is empty. fit::completer completer; EXPECT_FALSE(completer); // Can move-construct from non-empty. fit::bridge bridge; fit::completer completer2(std::move(bridge.completer)); EXPECT_TRUE(completer2); // Can move-assign from non-empty. completer = std::move(completer2); EXPECT_TRUE(completer); EXPECT_FALSE(completer2); // It still works. completer.complete_error("Test"); EXPECT_FALSE(completer); fit::result result = fit::run_single_threaded(bridge.consumer.promise()); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::error, result.state()); EXPECT_STR_EQ("Test", result.error()); // Can move-construct from empty. fit::completer completer3(std::move(completer2)); EXPECT_FALSE(completer3); EXPECT_FALSE(completer2); // Can move-assign from empty. completer2 = std::move(completer3); EXPECT_FALSE(completer2); EXPECT_FALSE(completer3); END_TEST; } bool completer_abandon() { BEGIN_TEST; // abandon() { fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); bridge.completer.abandon(); EXPECT_FALSE(bridge.completer); EXPECT_TRUE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded(bridge.consumer.promise_or( fit::error("Abandoned"))); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::error, result.state()); EXPECT_STR_EQ("Abandoned", result.error()); } // completer is discarded { fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); bridge.completer = fit::completer(); EXPECT_FALSE(bridge.completer); EXPECT_TRUE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded(bridge.consumer.promise_or( fit::error("Abandoned"))); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::error, result.state()); EXPECT_STR_EQ("Abandoned", result.error()); } END_TEST; } bool completer_complete() { BEGIN_TEST; // complete_ok() { fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); bridge.completer.complete_ok(); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded(bridge.consumer.promise()); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::ok, result.state()); } // complete_ok(value) { fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); bridge.completer.complete_ok(42); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded(bridge.consumer.promise()); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::ok, result.state()); EXPECT_EQ(42, result.value()); } // complete_error() { fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); bridge.completer.complete_error(); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded(bridge.consumer.promise()); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::error, result.state()); } // complete_error(error) { fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); bridge.completer.complete_error("Test"); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded(bridge.consumer.promise()); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::error, result.state()); EXPECT_STR_EQ("Test", result.error()); } // complete_or_abandon(fit::ok(...)) { fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); bridge.completer.complete_or_abandon(fit::ok(42)); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded(bridge.consumer.promise()); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::ok, result.state()); EXPECT_EQ(42, result.value()); } // complete_or_abandon(fit::error(...)) { fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); bridge.completer.complete_or_abandon(fit::error("Test")); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded(bridge.consumer.promise()); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::error, result.state()); EXPECT_STR_EQ("Test", result.error()); } // complete_or_abandon(fit::pending()) { fit::bridge bridge; EXPECT_TRUE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); bridge.completer.complete_or_abandon(fit::pending()); EXPECT_FALSE(bridge.completer); EXPECT_TRUE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded(bridge.consumer.promise_or( fit::error("Abandoned"))); EXPECT_FALSE(bridge.consumer); EXPECT_EQ(fit::result_state::error, result.state()); EXPECT_STR_EQ("Abandoned", result.error()); } END_TEST; } bool completer_bind_no_arg_callback() { BEGIN_TEST; // Use bind() { uint64_t run_count = 0; fit::bridge<> bridge; async_invoke_callback_no_args(&run_count, bridge.completer.bind()); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result<> result = fit::run_single_threaded( bridge.consumer.promise()); EXPECT_EQ(fit::result_state::ok, result.state()); EXPECT_EQ(1, run_count); } // Use bind_tuple() { uint64_t run_count = 0; fit::bridge> bridge; async_invoke_callback_no_args(&run_count, bridge.completer.bind_tuple()); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result> result = fit::run_single_threaded( bridge.consumer.promise()); EXPECT_EQ(fit::result_state::ok, result.state()); EXPECT_EQ(1, run_count); } END_TEST; } bool completer_bind_one_arg_callback() { BEGIN_TEST; // Use bind() { uint64_t run_count = 0; fit::bridge bridge; async_invoke_callback_one_arg(&run_count, bridge.completer.bind()); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result result = fit::run_single_threaded( bridge.consumer.promise()); EXPECT_EQ(fit::result_state::ok, result.state()); EXPECT_TRUE(result.value() == "Hippopotamus"); EXPECT_EQ(1, run_count); } // Use bind_tuple() { uint64_t run_count = 0; fit::bridge> bridge; async_invoke_callback_one_arg(&run_count, bridge.completer.bind_tuple()); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result> result = fit::run_single_threaded( bridge.consumer.promise()); EXPECT_EQ(fit::result_state::ok, result.state()); EXPECT_TRUE(std::get<0>(result.value()) == "Hippopotamus"); EXPECT_EQ(1, run_count); } END_TEST; } bool completer_bind_two_arg_callback() { BEGIN_TEST; // Use bind_tuple() { uint64_t run_count = 0; fit::bridge> bridge; async_invoke_callback_two_args(&run_count, bridge.completer.bind_tuple()); EXPECT_FALSE(bridge.completer); EXPECT_FALSE(bridge.consumer.was_abandoned()); fit::result> result = fit::run_single_threaded( bridge.consumer.promise()); EXPECT_EQ(fit::result_state::ok, result.state()); EXPECT_TRUE(std::get<0>(result.value()) == "What do you get when you multiply six by nine?"); EXPECT_EQ(42, std::get<1>(result.value())); EXPECT_EQ(1, run_count); } END_TEST; } bool consumer_construction_and_assignment() { BEGIN_TEST; // Default constructed consumer is empty. fit::consumer consumer; EXPECT_FALSE(consumer); // Can move-construct from non-empty. fit::bridge bridge; fit::consumer consumer2(std::move(bridge.consumer)); EXPECT_TRUE(consumer2); // Can move-assign from non-empty. consumer = std::move(consumer2); EXPECT_TRUE(consumer); EXPECT_FALSE(consumer2); // It still works. bridge.completer.complete_error("Test"); EXPECT_FALSE(bridge.completer); fit::result result = fit::run_single_threaded(consumer.promise()); EXPECT_FALSE(consumer); EXPECT_EQ(fit::result_state::error, result.state()); EXPECT_STR_EQ("Test", result.error()); // Can move-construct from empty. fit::consumer consumer3(std::move(consumer2)); EXPECT_FALSE(consumer3); EXPECT_FALSE(consumer2); // Can move-assign from empty. consumer2 = std::move(consumer3); EXPECT_FALSE(consumer2); EXPECT_FALSE(consumer3); END_TEST; } bool consumer_cancel() { BEGIN_TEST; // cancel() { fit::bridge bridge; EXPECT_TRUE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); bridge.consumer.cancel(); EXPECT_FALSE(bridge.consumer); EXPECT_TRUE(bridge.completer.was_canceled()); bridge.completer.complete_ok(42); EXPECT_FALSE(bridge.completer); } // consumer is discarded() { fit::bridge bridge; EXPECT_TRUE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); bridge.consumer = fit::consumer(); EXPECT_FALSE(bridge.consumer); EXPECT_TRUE(bridge.completer.was_canceled()); bridge.completer.complete_ok(42); EXPECT_FALSE(bridge.completer); } END_TEST; } bool consumer_promise() { BEGIN_TEST; // promise() when completed { fit::bridge bridge; EXPECT_TRUE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); fit::promise promise = bridge.consumer.promise(); EXPECT_FALSE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); bridge.completer.complete_ok(42); EXPECT_FALSE(bridge.completer); fit::result result = fit::run_single_threaded(std::move(promise)); EXPECT_EQ(fit::result_state::ok, result.state()); EXPECT_EQ(42, result.value()); } // promise() when abandoned { fit::bridge bridge; EXPECT_TRUE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); fit::promise promise = bridge.consumer.promise(); EXPECT_FALSE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); bridge.completer.abandon(); EXPECT_FALSE(bridge.completer); fit::result result = fit::run_single_threaded(std::move(promise)); EXPECT_EQ(fit::result_state::pending, result.state()); } // promise_or() when completed { fit::bridge bridge; EXPECT_TRUE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); fit::promise promise = bridge.consumer.promise_or( fit::error("Abandoned")); EXPECT_FALSE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); bridge.completer.complete_ok(42); EXPECT_FALSE(bridge.completer); fit::result result = fit::run_single_threaded(std::move(promise)); EXPECT_EQ(fit::result_state::ok, result.state()); EXPECT_EQ(42, result.value()); } // promise_or() when abandoned { fit::bridge bridge; EXPECT_TRUE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); fit::promise promise = bridge.consumer.promise_or( fit::error("Abandoned")); EXPECT_FALSE(bridge.consumer); EXPECT_FALSE(bridge.completer.was_canceled()); bridge.completer.abandon(); EXPECT_FALSE(bridge.completer); fit::result result = fit::run_single_threaded(std::move(promise)); EXPECT_EQ(fit::result_state::error, result.state()); EXPECT_STR_EQ("Abandoned", result.error()); } END_TEST; } bool schedule_for_consumer() { BEGIN_TEST; // Promise completes normally. { uint64_t run_count[2] = {}; fit::single_threaded_executor executor; fit::consumer consumer = fit::schedule_for_consumer( &executor, fit::make_promise([&](fit::context& context) { ASSERT_CRITICAL(context.executor() == &executor); run_count[0]++; return fit::ok(42); })); EXPECT_EQ(0, run_count[0]); auto t = std::thread([&] { executor.run(); }); fit::run_single_threaded( consumer.promise() .then([&](fit::context& context, fit::result result) { ASSERT_CRITICAL(context.executor() != &executor); ASSERT_CRITICAL(result.value() == 42); run_count[1]++; })); EXPECT_EQ(1, run_count[0]); EXPECT_EQ(1, run_count[1]); t.join(); } // Promise abandons its task so the consumer is abandoned too. { uint64_t run_count[2] = {}; fit::single_threaded_executor executor; fit::consumer consumer = fit::schedule_for_consumer( &executor, fit::make_promise([&](fit::context& context) -> fit::result { ASSERT_CRITICAL(context.executor() == &executor); run_count[0]++; // The task will be abandoned after we return since // we do not acquire a susended task token for it. return fit::pending(); })); EXPECT_EQ(0, run_count[0]); auto t = std::thread([&] { executor.run(); }); fit::run_single_threaded( consumer.promise() .then([&](fit::context& context, fit::result result) { // This should not run because the promise was abandoned. run_count[1]++; })); EXPECT_EQ(1, run_count[0]); EXPECT_EQ(0, run_count[1]); t.join(); } // Promise abandons its task so the consumer is abandoned too // but this time we use promise_or() so we can handle the abandonment. { uint64_t run_count[2] = {}; fit::single_threaded_executor executor; fit::consumer consumer = fit::schedule_for_consumer( &executor, fit::make_promise([&](fit::context& context) -> fit::result { ASSERT_CRITICAL(context.executor() == &executor); run_count[0]++; // The task will be abandoned after we return since // we do not acquire a susended task token for it. return fit::pending(); })); EXPECT_EQ(0, run_count[0]); auto t = std::thread([&] { executor.run(); }); fit::run_single_threaded( consumer.promise_or(fit::error()) .then([&](fit::context& context, fit::result result) { ASSERT_CRITICAL(context.executor() != &executor); ASSERT_CRITICAL(result.is_error()); run_count[1]++; })); EXPECT_EQ(1, run_count[0]); EXPECT_EQ(1, run_count[1]); t.join(); } END_TEST; } } // namespace BEGIN_TEST_CASE(bridge_tests) RUN_TEST(bridge_construction_and_assignment) RUN_TEST(completer_construction_and_assignment) RUN_TEST(completer_abandon) RUN_TEST(completer_complete) RUN_TEST(completer_bind_no_arg_callback) RUN_TEST(completer_bind_one_arg_callback) RUN_TEST(completer_bind_two_arg_callback) RUN_TEST(consumer_construction_and_assignment) RUN_TEST(consumer_cancel) RUN_TEST(consumer_promise) RUN_TEST(schedule_for_consumer) END_TEST_CASE(bridge_tests)