Commit d58fe683 authored by Kirk Shoop's avatar Kirk Shoop Committed by Facebook Github Bot

migrate to gtest

Summary:
migrated tests
fixed some bugs introduced while tests were disabled

Reviewed By: yfeldblum

Differential Revision: D10515231

fbshipit-source-id: 34cddb6588c4da9cd05256111bf9626b013dd4fa
parent 20d6c1df
...@@ -84,8 +84,8 @@ class any_constrained_single_sender { ...@@ -84,8 +84,8 @@ class any_constrained_single_sender {
return ::folly::pushmi::top(*static_cast<Wrapped*>((void*)src.buffer_)); return ::folly::pushmi::top(*static_cast<Wrapped*>((void*)src.buffer_));
} }
static any_constrained_executor<E, CV> executor(data& src) { static any_constrained_executor<E, CV> executor(data& src) {
return any_constrained_executor<E, CV>{ return any_constrained_executor<E, CV>{::folly::pushmi::executor(
executor(*static_cast<Wrapped*>((void*)src.buffer_))}; *static_cast<Wrapped*>((void*)src.buffer_))};
} }
static void submit(data& src, CV cv, any_receiver<E, VN...> out) { static void submit(data& src, CV cv, any_receiver<E, VN...> out) {
::folly::pushmi::submit( ::folly::pushmi::submit(
......
...@@ -166,7 +166,7 @@ struct entangled { ...@@ -166,7 +166,7 @@ struct entangled {
entangled& operator=(entangled&&) = delete; entangled& operator=(entangled&&) = delete;
explicit entangled(T t) explicit entangled(T t)
: t(std::move(t)), dual(nullptr), stateMachine(kUnlocked) {} : stateMachine(kUnlocked), t(std::move(t)), dual(nullptr) {}
entangled(entangled&& other) entangled(entangled&& other)
: stateMachine((other.lockBoth(), kLocked)), : stateMachine((other.lockBoth(), kLocked)),
t(std::move(other.t)), t(std::move(other.t)),
......
...@@ -34,16 +34,27 @@ struct for_each_fn { ...@@ -34,16 +34,27 @@ struct for_each_fn {
using properties = using properties =
property_set_insert_t<properties_t<Out>, property_set<is_flow<>>>; property_set_insert_t<properties_t<Out>, property_set<is_flow<>>>;
std::function<void(std::ptrdiff_t)> pull; std::function<void(std::ptrdiff_t)> pull;
template <class V> template <class... VN>
void value(V&& v) { void value(VN&&... vn) {
set_value(static_cast<Out&>(*this), (V &&) v); ::folly::pushmi::set_value(static_cast<Out&>(*this), (VN &&) vn...);
pull(1); pull(1);
} }
template <class E>
void error(E&& e) {
// break circular reference
pull = nullptr;
::folly::pushmi::set_error(static_cast<Out&>(*this), (E &&) e);
}
void done() {
// break circular reference
pull = nullptr;
::folly::pushmi::set_done(static_cast<Out&>(*this));
}
PUSHMI_TEMPLATE(class Up) PUSHMI_TEMPLATE(class Up)
(requires Receiver<Up>) (requires Receiver<Up> && ReceiveValue<Up, std::ptrdiff_t>)
void starting(Up up) { void starting(Up up) {
pull = [up = std::move(up)](std::ptrdiff_t requested) mutable { pull = [up = std::move(up)](std::ptrdiff_t requested) mutable {
set_value(up, requested); ::folly::pushmi::set_value(up, requested);
}; };
pull(1); pull(1);
} }
...@@ -62,23 +73,8 @@ struct for_each_fn { ...@@ -62,23 +73,8 @@ struct for_each_fn {
property_set_index_t<properties_t<In>, is_single<>>>>()( property_set_index_t<properties_t<In>, is_single<>>>>()(
std::move(args_))}; std::move(args_))};
using Out = decltype(out); using Out = decltype(out);
submit( ::folly::pushmi::submit(
in,
::folly::pushmi::detail::receiver_from_fn<In>()(
Pull<In, Out>{std::move(out)}));
return in;
}
PUSHMI_TEMPLATE(class In)
(requires Sender<In>&& Constrained<In>&& Flow<In>&& Many<In>)
In operator()(In in) {
auto out{::folly::pushmi::detail::receiver_from_fn<subset<
is_sender<>,
property_set_index_t<properties_t<In>, is_single<>>>>()(
std::move(args_))};
using Out = decltype(out);
submit(
in, in,
::folly::pushmi::top(in),
::folly::pushmi::detail::receiver_from_fn<In>()( ::folly::pushmi::detail::receiver_from_fn<In>()(
Pull<In, Out>{std::move(out)})); Pull<In, Out>{std::move(out)}));
return in; return in;
...@@ -88,7 +84,7 @@ struct for_each_fn { ...@@ -88,7 +84,7 @@ struct for_each_fn {
public: public:
template <class... AN> template <class... AN>
auto operator()(AN&&... an) const { auto operator()(AN&&... an) const {
return for_each_fn::fn<AN...>{{(AN &&) an...}}; return for_each_fn::fn<AN...>{std::tuple<AN...>{(AN &&) an...}};
} }
}; };
......
...@@ -102,7 +102,8 @@ struct flow_from_up { ...@@ -102,7 +102,8 @@ struct flow_from_up {
return; return;
} }
// submit work to exec // submit work to exec
submit(p->exec, make_receiver([p = p, requested](auto) { ::folly::pushmi::submit(
p->exec, make_receiver([p = p, requested](auto) {
auto remaining = requested; auto remaining = requested;
// this loop is structured to work when there is // this loop is structured to work when there is
// re-entrancy out.value in the loop may call up.value. // re-entrancy out.value in the loop may call up.value.
...@@ -122,12 +123,14 @@ struct flow_from_up { ...@@ -122,12 +123,14 @@ struct flow_from_up {
template <class E> template <class E>
void error(E) noexcept { void error(E) noexcept {
p->stop.store(true); p->stop.store(true);
submit(p->exec, make_receiver([p = p](auto) { set_done(p->out); })); ::folly::pushmi::submit(
p->exec, make_receiver([p = p](auto) { set_done(p->out); }));
} }
void done() { void done() {
p->stop.store(true); p->stop.store(true);
submit(p->exec, make_receiver([p = p](auto) { set_done(p->out); })); ::folly::pushmi::submit(
p->exec, make_receiver([p = p](auto) { set_done(p->out); }));
} }
}; };
...@@ -147,7 +150,8 @@ PUSHMI_INLINE_VAR constexpr struct flow_from_fn { ...@@ -147,7 +150,8 @@ PUSHMI_INLINE_VAR constexpr struct flow_from_fn {
auto p = std::make_shared<Producer>( auto p = std::make_shared<Producer>(
begin_, end_, std::move(out), exec_, false); begin_, end_, std::move(out), exec_, false);
submit(exec_, make_receiver([p](auto) { ::folly::pushmi::submit(
exec_, make_receiver([p](auto) {
// pass reference for cancellation. // pass reference for cancellation.
set_starting(p->out, make_receiver(flow_from_up<Producer>{p})); set_starting(p->out, make_receiver(flow_from_up<Producer>{p}));
})); }));
......
...@@ -58,7 +58,7 @@ struct submit_fn { ...@@ -58,7 +58,7 @@ struct submit_fn {
In operator()(In in) { In operator()(In in) {
auto out{ auto out{
::folly::pushmi::detail::receiver_from_fn<In>{}(std::move(args_))}; ::folly::pushmi::detail::receiver_from_fn<In>{}(std::move(args_))};
submit(in, std::move(out)); ::folly::pushmi::submit(in, std::move(out));
return in; return in;
} }
}; };
...@@ -81,7 +81,7 @@ struct submit_at_fn { ...@@ -81,7 +81,7 @@ struct submit_at_fn {
In operator()(In in) { In operator()(In in) {
auto out{ auto out{
::folly::pushmi::detail::receiver_from_fn<In>()(std::move(args_))}; ::folly::pushmi::detail::receiver_from_fn<In>()(std::move(args_))};
submit(in, std::move(at_), std::move(out)); ::folly::pushmi::submit(in, std::move(at_), std::move(out));
return in; return in;
} }
}; };
...@@ -90,7 +90,8 @@ struct submit_at_fn { ...@@ -90,7 +90,8 @@ struct submit_at_fn {
PUSHMI_TEMPLATE(class TP, class... AN) PUSHMI_TEMPLATE(class TP, class... AN)
(requires Regular<TP>) (requires Regular<TP>)
auto operator()(TP at, AN... an) const { auto operator()(TP at, AN... an) const {
return submit_at_fn::fn<TP, AN...>{std::move(at), {(AN &&) an...}}; return submit_at_fn::fn<TP, AN...>{std::move(at),
std::tuple<AN...>{(AN &&) an...}};
} }
}; };
...@@ -109,7 +110,7 @@ struct submit_after_fn { ...@@ -109,7 +110,7 @@ struct submit_after_fn {
auto out{ auto out{
::folly::pushmi::detail::receiver_from_fn<In>()(std::move(args_))}; ::folly::pushmi::detail::receiver_from_fn<In>()(std::move(args_))};
auto at = ::folly::pushmi::now(in) + std::move(after_); auto at = ::folly::pushmi::now(in) + std::move(after_);
submit(in, std::move(at), std::move(out)); ::folly::pushmi::submit(in, std::move(at), std::move(out));
return in; return in;
} }
}; };
...@@ -118,7 +119,8 @@ struct submit_after_fn { ...@@ -118,7 +119,8 @@ struct submit_after_fn {
PUSHMI_TEMPLATE(class D, class... AN) PUSHMI_TEMPLATE(class D, class... AN)
(requires Regular<D>) (requires Regular<D>)
auto operator()(D after, AN... an) const { auto operator()(D after, AN... an) const {
return submit_after_fn::fn<D, AN...>{std::move(after), {(AN &&) an...}}; return submit_after_fn::fn<D, AN...>{std::move(after),
std::tuple<AN...>{(AN &&) an...}};
} }
}; };
...@@ -159,7 +161,7 @@ struct blocking_submit_fn { ...@@ -159,7 +161,7 @@ struct blocking_submit_fn {
using properties = properties_t<Exec>; using properties = properties_t<Exec>;
auto executor() { auto executor() {
return make(state_, executor(ex_)); return make(state_, ::folly::pushmi::executor(ex_));
} }
PUSHMI_TEMPLATE(class... ZN) PUSHMI_TEMPLATE(class... ZN)
...@@ -172,14 +174,16 @@ struct blocking_submit_fn { ...@@ -172,14 +174,16 @@ struct blocking_submit_fn {
(requires Receiver<Out>&& Constrained<Exec>) (requires Receiver<Out>&& Constrained<Exec>)
void submit(CV cv, Out out) { void submit(CV cv, Out out) {
++state_->nested; ++state_->nested;
submit(ex_, cv, nested_receiver_impl<Out>{state_, std::move(out)}); ::folly::pushmi::submit(
ex_, cv, nested_receiver_impl<Out>{state_, std::move(out)});
} }
PUSHMI_TEMPLATE(class Out) PUSHMI_TEMPLATE(class Out)
(requires Receiver<Out> && not Constrained<Exec>) (requires Receiver<Out> && not Constrained<Exec>)
void submit(Out out) { void submit(Out out) {
++state_->nested; ++state_->nested;
submit(ex_, nested_receiver_impl<Out>{state_, std::move(out)}); ::folly::pushmi::submit(
ex_, nested_receiver_impl<Out>{state_, std::move(out)});
} }
}; };
template <class Out> template <class Out>
...@@ -293,7 +297,7 @@ struct blocking_submit_fn { ...@@ -293,7 +297,7 @@ struct blocking_submit_fn {
PUSHMI_TEMPLATE(class Out) PUSHMI_TEMPLATE(class Out)
(requires Receiver<Out>&& SenderTo<In, Out>) (requires Receiver<Out>&& SenderTo<In, Out>)
void operator()(In& in, Out out) const { void operator()(In& in, Out out) const {
submit(in, std::move(out)); ::folly::pushmi::submit(in, std::move(out));
} }
}; };
// TODO - only move, move-only types.. // TODO - only move, move-only types..
......
...@@ -203,7 +203,8 @@ class strand_executor { ...@@ -203,7 +203,8 @@ class strand_executor {
queue_->items_.push(any_receiver<E, any_executor_ref<E>>{std::move(out)}); queue_->items_.push(any_receiver<E, any_executor_ref<E>>{std::move(out)});
if (queue_->remaining_ == 0) { if (queue_->remaining_ == 0) {
// noone is minding the shop, send a worker // noone is minding the shop, send a worker
submit(queue_->ex_, strand_queue_receiver<E, Executor>{queue_}); ::folly::pushmi::submit(
queue_->ex_, strand_queue_receiver<E, Executor>{queue_});
} }
} }
}; };
......
...@@ -25,6 +25,11 @@ ...@@ -25,6 +25,11 @@
using namespace folly::pushmi::aliases; using namespace folly::pushmi::aliases;
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
using namespace testing;
using namespace std::literals; using namespace std::literals;
#if __cpp_deduction_guides >= 201703 #if __cpp_deduction_guides >= 201703
...@@ -562,3 +567,5 @@ void flow_many_sender_test() { ...@@ -562,3 +567,5 @@ void flow_many_sender_test() {
mi::Executor<mi::executor_t<decltype(in0)>>, mi::Executor<mi::executor_t<decltype(in0)>>,
"sender has invalid executor"); "sender has invalid executor");
} }
TEST(CompileTest, Test) {}
...@@ -22,9 +22,9 @@ ...@@ -22,9 +22,9 @@
using namespace std::literals; using namespace std::literals;
#include <folly/experimental/pushmi/flow_many_sender.h> #include <folly/experimental/pushmi/flow_many_sender.h>
#include <folly/experimental/pushmi/o/submit.h>
#include <folly/experimental/pushmi/o/from.h>
#include <folly/experimental/pushmi/o/for_each.h> #include <folly/experimental/pushmi/o/for_each.h>
#include <folly/experimental/pushmi/o/from.h>
#include <folly/experimental/pushmi/o/submit.h>
#include <folly/experimental/pushmi/entangle.h> #include <folly/experimental/pushmi/entangle.h>
#include <folly/experimental/pushmi/new_thread.h> #include <folly/experimental/pushmi/new_thread.h>
...@@ -33,7 +33,10 @@ using namespace std::literals; ...@@ -33,7 +33,10 @@ using namespace std::literals;
using namespace folly::pushmi::aliases; using namespace folly::pushmi::aliases;
#if 0 #include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
using namespace testing;
#if __cpp_deduction_guides >= 201703 #if __cpp_deduction_guides >= 201703
#define MAKE(x) x MAKE_ #define MAKE(x) x MAKE_
...@@ -43,12 +46,10 @@ using namespace folly::pushmi::aliases; ...@@ -43,12 +46,10 @@ using namespace folly::pushmi::aliases;
#define MAKE(x) make_##x #define MAKE(x) make_##x
#endif #endif
SCENARIO("flow many immediate cancellation", "[flowmany][flow][sender]") { class ImmediateFlowManySender : public Test {
int signals = 0; protected:
auto make_producer() {
GIVEN("A flow many sender") { return mi::MAKE(flow_many_sender)([&](auto out) {
auto f = mi::MAKE(flow_many_sender)([&](auto out) {
using Out = decltype(out); using Out = decltype(out);
struct Data : mi::receiver<> { struct Data : mi::receiver<> {
explicit Data(Out out) : out(std::move(out)), stop(false) {} explicit Data(Out out) : out(std::move(out)), stop(false) {}
...@@ -59,21 +60,23 @@ SCENARIO("flow many immediate cancellation", "[flowmany][flow][sender]") { ...@@ -59,21 +60,23 @@ SCENARIO("flow many immediate cancellation", "[flowmany][flow][sender]") {
auto up = mi::MAKE(receiver)( auto up = mi::MAKE(receiver)(
Data{std::move(out)}, Data{std::move(out)},
[&](auto& data, auto requested) { [&](auto& data, auto requested) {
signals += 1000000; signals_ += 1000000;
if (requested < 1) {return;} if (requested < 1) {
return;
}
// check boolean to select signal // check boolean to select signal
if (!data.stop) { if (!data.stop) {
::mi::set_value(data.out, 42); ::mi::set_value(data.out, 42);
} }
::mi::set_done(data.out); ::mi::set_done(data.out);
}, },
[&](auto& data, auto e) noexcept { [&](auto& data, auto) noexcept {
signals += 100000; signals_ += 100000;
data.stop = true; data.stop = true;
::mi::set_done(data.out); ::mi::set_done(data.out);
}, },
[&](auto& data) { [&](auto& data) {
signals += 10000; signals_ += 10000;
data.stop = true; data.stop = true;
::mi::set_done(data.out); ::mi::set_done(data.out);
}); });
...@@ -81,68 +84,78 @@ SCENARIO("flow many immediate cancellation", "[flowmany][flow][sender]") { ...@@ -81,68 +84,78 @@ SCENARIO("flow many immediate cancellation", "[flowmany][flow][sender]") {
// pass reference for cancellation. // pass reference for cancellation.
::mi::set_starting(up.data().out, std::move(up)); ::mi::set_starting(up.data().out, std::move(up));
}); });
}
WHEN("submit is applied and cancels the producer") { template <class F>
f | auto make_consumer(F f) {
op::submit(mi::MAKE(flow_receiver)( return mi::MAKE(flow_receiver)(
mi::on_value([&](int) { signals += 100; }), mi::on_value([&](int) { signals_ += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }), mi::on_error([&](auto) noexcept { signals_ += 1000; }),
mi::on_done([&]() { signals += 1; }), mi::on_done([&]() { signals_ += 1; }),
mi::on_starting([&, f](auto up) {
signals_ += 10;
f(std::move(up));
}));
}
int signals_{0};
};
TEST_F(ImmediateFlowManySender, EarlyCancellation) {
make_producer() | op::submit(make_consumer([](auto up) {
// immediately stop producer // immediately stop producer
mi::on_starting([&](auto up) {
signals += 10;
::mi::set_done(up); ::mi::set_done(up);
}))); }));
THEN( EXPECT_THAT(signals_, Eq(10011))
"the starting, up.done and out.done signals are each recorded once") { << "expected that the starting, up.done and out.done signals are each recorded once";
REQUIRE(signals == 10011); }
}
}
WHEN("submit is applied and cancels the producer late") { TEST_F(ImmediateFlowManySender, LateCancellation) {
f | make_producer() | op::submit(make_consumer([](auto up) {
op::submit(mi::MAKE(flow_receiver)(
mi::on_value([&](int) { signals += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }),
mi::on_done([&]() { signals += 1; }),
// do not stop producer before it is scheduled to run // do not stop producer before it is scheduled to run
mi::on_starting([&](auto up) {
signals += 10;
::mi::set_value(up, 1); ::mi::set_value(up, 1);
}))); }));
THEN( EXPECT_THAT(signals_, Eq(1000111))
"the starting, up.value, value and done signals are each recorded once") { << "expected that the starting, up.value, value and done signals are each recorded once";
REQUIRE(signals == 1000111); }
}
using NT = decltype(mi::new_thread());
inline auto make_time(mi::time_source<>& t, NT& ex) {
return t.make(mi::systemNowF{}, [ex]() { return ex; })();
}
class ConcurrentFlowManySender : public Test {
protected:
using TNT = mi::invoke_result_t<decltype(make_time), mi::time_source<>&, NT&>;
void reset() {
at_ = mi::now(tnt_) + 100ms;
signals_ = 0;
terminal_ = 0;
cancel_ = 0;
} }
void join() {
timeproduce_.join();
timecancel_.join();
} }
}
SCENARIO("flow many cancellation new thread", "[flowmany][flow][sender]") { void cancellation_test(std::chrono::system_clock::time_point at) {
auto nt = mi::new_thread();
using NT = decltype(nt);
auto time = mi::time_source<>{};
auto tnt = time.make(mi::systemNowF{}, [nt](){ return nt; })();
using TNT = decltype(tnt);
auto tcncl = time.make(mi::systemNowF{}, [nt](){ return nt; })();
std::atomic<int> signals{0};
auto at = mi::now(tnt) + 200ms;
GIVEN("A flow many sender") {
auto f = mi::MAKE(flow_many_sender)([&](auto out) { auto f = mi::MAKE(flow_many_sender)([&](auto out) {
using Out = decltype(out); using Out = decltype(out);
// boolean cancellation // boolean cancellation
struct producer { struct producer {
producer(Out out, TNT tnt, bool s) : out(std::move(out)), tnt(std::move(tnt)), stop(s) {} producer(Out out, TNT tnt, bool s)
: out(std::move(out)), tnt(std::move(tnt)), stop(s) {}
Out out; Out out;
TNT tnt; TNT tnt;
std::atomic<bool> stop; std::atomic<bool> stop;
}; };
auto p = std::make_shared<producer>(std::move(out), tnt, false); auto p = std::make_shared<producer>(std::move(out), tnt_, false);
struct Data : mi::receiver<> { struct Data : mi::receiver<> {
explicit Data(std::shared_ptr<producer> p) : p(std::move(p)) {} explicit Data(std::shared_ptr<producer> p) : p(std::move(p)) {}
...@@ -151,143 +164,121 @@ SCENARIO("flow many cancellation new thread", "[flowmany][flow][sender]") { ...@@ -151,143 +164,121 @@ SCENARIO("flow many cancellation new thread", "[flowmany][flow][sender]") {
auto up = mi::MAKE(receiver)( auto up = mi::MAKE(receiver)(
Data{p}, Data{p},
[&at, &signals](auto& data, auto requested) { [&](auto& data, auto requested) {
signals += 1000000; signals_ += 1000000;
if (requested < 1) {return;} if (requested < 1) {
return;
}
// submit work to happen later // submit work to happen later
data.p->tnt | data.p->tnt | op::submit_at(at_, [p = data.p](auto) {
op::submit_at(
at,
[p = data.p](auto) {
// check boolean to select signal // check boolean to select signal
if (!p->stop) { if (!p->stop) {
::mi::set_value(p->out, 42); ::mi::set_value(p->out, 42);
}
::mi::set_done(p->out); ::mi::set_done(p->out);
}
}); });
}, },
[&signals](auto& data, auto e) noexcept { [&](auto& data, auto) noexcept {
signals += 100000; signals_ += 100000;
data.p->stop.store(true); data.p->stop.store(true);
data.p->tnt | op::submit([p = data.p](auto) { data.p->tnt |
::mi::set_done(p->out); op::submit([p = data.p](auto) { ::mi::set_done(p->out); });
}); ++cancel_;
}, },
[&signals](auto& data) { [&](auto& data) {
signals += 10000; signals_ += 10000;
data.p->stop.store(true); data.p->stop.store(true);
data.p->tnt | op::submit([p = data.p](auto) { data.p->tnt |
::mi::set_done(p->out); op::submit([p = data.p](auto) { ::mi::set_done(p->out); });
}); ++cancel_;
}); });
tnt | tnt_ | op::submit([p, sup = std::move(up)](auto) mutable {
op::submit([p, up = std::move(up)](auto tnt) mutable {
// pass reference for cancellation. // pass reference for cancellation.
::mi::set_starting(p->out, std::move(up)); ::mi::set_starting(p->out, std::move(sup));
}); });
}); });
WHEN("submit is applied and cancels the producer early") {
{
f | f |
op::blocking_submit( op::submit(mi::MAKE(flow_receiver)(
mi::on_value([&](int) { signals += 100; }), mi::on_value([&](int) { signals_ += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }), mi::on_error([&](auto) noexcept {
mi::on_done([&]() { signals += 1; }), signals_ += 1000;
++terminal_;
}),
mi::on_done([&]() {
signals_ += 1;
++terminal_;
}),
// stop producer before it is scheduled to run // stop producer before it is scheduled to run
mi::on_starting([&](auto up) { mi::on_starting([&, at](auto up) {
signals += 10; signals_ += 10;
mi::set_value(up, 1); mi::set_value(up, 1);
tcncl | tcncl_ | op::submit_at(at, [up = std::move(up)](auto) mutable {
op::submit_at(
at - 100ms, [up = std::move(up)](auto) mutable {
::mi::set_done(up); ::mi::set_done(up);
}); });
})); })));
}
// make sure that the completion signal arrives
std::this_thread::sleep_for(200ms);
THEN( while (terminal_ == 0 || cancel_ == 0) {
"the starting, up.done and out.done signals are each recorded once") { std::this_thread::yield();
REQUIRE(signals == 1010011);
} }
} }
WHEN("submit is applied and cancels the producer late") { NT ntproduce_{mi::new_thread()};
{ mi::time_source<> timeproduce_{};
f | TNT tnt_{make_time(timeproduce_, ntproduce_)};
op::blocking_submit(
mi::on_value([&](int) { signals += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }),
mi::on_done([&]() { signals += 1; }),
// do not stop producer before it is scheduled to run
mi::on_starting([&](auto up) {
signals += 10;
mi::set_value(up, 1);
tcncl |
op::submit_at(
at + 100ms, [up = std::move(up)](auto) mutable {
::mi::set_done(up);
});
}));
}
std::this_thread::sleep_for(200ms); NT ntcancel_{mi::new_thread()};
mi::time_source<> timecancel_{};
TNT tcncl_{make_time(timecancel_, ntcancel_)};
THEN( std::atomic<int> signals_{0};
"the starting, up.done and out.value signals are each recorded once") { std::atomic<int> terminal_{0};
REQUIRE(signals == 1010111); std::atomic<int> cancel_{0};
} std::chrono::system_clock::time_point at_{mi::now(tnt_) + 100ms};
} };
TEST_F(ConcurrentFlowManySender, EarlyCancellation) {
// this nightmare brought to you by ASAN stack-use-after-return.
cancellation_test(at_ - 50ms);
join();
EXPECT_THAT(signals_, Eq(1010011))
<< "expected that the starting, up.done and out.done signals are each recorded once";
}
TEST_F(ConcurrentFlowManySender, LateCancellation) {
// this nightmare brought to you by ASAN stack-use-after-return.
cancellation_test(at_ + 50ms);
WHEN("submit is applied and cancels the producer at the same time") { join();
// count known results
EXPECT_THAT(signals_, Eq(1010111))
<< "expected that the starting, up.done and out.value signals are each recorded once";
}
TEST_F(ConcurrentFlowManySender, RacingCancellation) {
int total = 0; int total = 0;
int cancellostrace = 0; // 1010111 int cancellostrace = 0; // 1010111
int cancelled = 0; // 1010011 int cancelled = 0; // 1010011
for (;;) { for (;;) {
signals = 0; reset();
// set completion time to be in 100ms cancellation_test(at_);
at = mi::now(tnt) + 100ms;
{
f |
op::blocking_submit(
mi::on_value([&](int) { signals += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }),
mi::on_done([&]() { signals += 1; }),
// stop producer at the same time that it is scheduled to run
mi::on_starting([&](auto up) {
signals += 10;
mi::set_value(up, 1);
tcncl | op::submit_at(at, [up = std::move(up)](auto) mutable {
::mi::set_done(up);
});
}));
}
// make sure any cancellation signal has completed
std::this_thread::sleep_for(200ms);
// accumulate known signals // accumulate known signals
++total; ++total;
cancellostrace += signals == 1010111; cancellostrace += signals_ == 1010111;
cancelled += signals == 1010011; cancelled += signals_ == 1010011;
if (total != cancellostrace + cancelled) { EXPECT_THAT(total, Eq(cancellostrace + cancelled))
// display the unrecognized signals recorded << signals_ << " <- this set of signals is unrecognized";
REQUIRE(signals == -1);
} ASSERT_THAT(total, Lt(100))
if (total >= 100) {
// too long, abort and show the signals distribution // too long, abort and show the signals distribution
WARN( << "total " << total << ", cancel-lost-race " << cancellostrace
"total " << total << ", cancel-lost-race " << cancellostrace << ", cancelled " << cancelled;
<< ", cancelled " << cancelled);
break;
}
if (cancellostrace > 4 && cancelled > 4) { if (cancellostrace > 4 && cancelled > 4) {
// yay all known outcomes were observed! // yay all known outcomes were observed!
break; break;
...@@ -295,25 +286,16 @@ SCENARIO("flow many cancellation new thread", "[flowmany][flow][sender]") { ...@@ -295,25 +286,16 @@ SCENARIO("flow many cancellation new thread", "[flowmany][flow][sender]") {
// try again // try again
continue; continue;
} }
}
time.join(); join();
}
} }
SCENARIO("flow many from", "[flow][sender][for_each]") { TEST(FlowManySender, From) {
GIVEN("A flow many sender of 5 values") {
auto v = std::array<int, 5>{0, 1, 2, 3, 4}; auto v = std::array<int, 5>{0, 1, 2, 3, 4};
auto f = op::flow_from(v); auto f = op::flow_from(v);
WHEN("for_each is applied") {
int actual = 0; int actual = 0;
f | op::for_each(mi::MAKE(receiver)([&](int){++actual;})); f | op::for_each(mi::MAKE(receiver)([&](int) { ++actual; }));
THEN("all the values are sent once") { EXPECT_THAT(actual, Eq(5)) << "expexcted that all the values are sent once";
REQUIRE(actual == 5);
}
}
}
} }
#endif
...@@ -29,7 +29,10 @@ using namespace std::literals; ...@@ -29,7 +29,10 @@ using namespace std::literals;
using namespace folly::pushmi::aliases; using namespace folly::pushmi::aliases;
#if 0 #include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
using namespace testing;
#if __cpp_deduction_guides >= 201703 #if __cpp_deduction_guides >= 201703
#define MAKE(x) x MAKE_ #define MAKE(x) x MAKE_
...@@ -39,11 +42,10 @@ using namespace folly::pushmi::aliases; ...@@ -39,11 +42,10 @@ using namespace folly::pushmi::aliases;
#define MAKE(x) make_##x #define MAKE(x) make_##x
#endif #endif
SCENARIO("flow single immediate cancellation", "[flow][sender]") { class ImmediateFlowSingleSender : public Test {
int signals = 0; protected:
auto make_producer() {
GIVEN("A flow single sender") { return mi::MAKE(flow_single_sender)([&](auto out) {
auto f = mi::MAKE(flow_single_sender)([&](auto out) {
// boolean cancellation // boolean cancellation
bool stop = false; bool stop = false;
auto set_stop = [](auto& stop) { auto set_stop = [](auto& stop) {
...@@ -60,13 +62,13 @@ SCENARIO("flow single immediate cancellation", "[flow][sender]") { ...@@ -60,13 +62,13 @@ SCENARIO("flow single immediate cancellation", "[flow][sender]") {
}; };
auto up = mi::MAKE(receiver)( auto up = mi::MAKE(receiver)(
Data{std::move(tokens.second)}, Data{std::move(tokens.second)},
[&](auto& data, auto e) noexcept { [&](auto& data, auto) noexcept {
signals += 100000; signals_ += 100000;
auto both = lock_both(data.stopper); auto both = lock_both(data.stopper);
(*(both.first))(both.second); (*(both.first))(both.second);
}, },
mi::on_done([&](auto& data) { mi::on_done([&](auto& data) {
signals += 10000; signals_ += 10000;
auto both = lock_both(data.stopper); auto both = lock_both(data.stopper);
(*(both.first))(both.second); (*(both.first))(both.second);
})); }));
...@@ -83,53 +85,69 @@ SCENARIO("flow single immediate cancellation", "[flow][sender]") { ...@@ -83,53 +85,69 @@ SCENARIO("flow single immediate cancellation", "[flow][sender]") {
::mi::set_done(out); ::mi::set_done(out);
} }
}); });
}
WHEN("submit is applied and cancels the producer") { template <class F>
f | auto make_consumer(F f) {
op::submit( return mi::MAKE(flow_receiver)(
mi::on_value([&](int) { signals += 100; }), mi::on_value([&](int) { signals_ += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }), mi::on_error([&](auto) noexcept { signals_ += 1000; }),
mi::on_done([&]() { signals += 1; }), mi::on_done([&]() { signals_ += 1; }),
mi::on_starting([&, f](auto up) {
signals_ += 10;
f(std::move(up));
}));
}
int signals_{0};
};
TEST_F(ImmediateFlowSingleSender, EarlyCancellation) {
make_producer() | op::submit(make_consumer([](auto up) {
// immediately stop producer // immediately stop producer
mi::on_starting([&](auto up) {
signals += 10;
::mi::set_done(up); ::mi::set_done(up);
})); }));
THEN( EXPECT_THAT(signals_, Eq(10011))
"the starting, up.done and out.done signals are each recorded once") { << "expected that the starting, up.done and out.done signals are each recorded once";
REQUIRE(signals == 10011); }
}
}
WHEN("submit is applied and cancels the producer late") { TEST_F(ImmediateFlowSingleSender, LateCancellation) {
f | make_producer() | op::submit(make_consumer([](auto) {
op::submit(
mi::on_value([&](int) { signals += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }),
mi::on_done([&]() { signals += 1; }),
// do not stop producer before it is scheduled to run // do not stop producer before it is scheduled to run
mi::on_starting([&](auto up) { signals += 10; })); }));
THEN( EXPECT_THAT(signals_, Eq(110))
"the starting and out.value signals are each recorded once") { << "expected that the starting and out.value signals are each recorded once";
REQUIRE(signals == 110); }
}
using NT = decltype(mi::new_thread());
inline auto make_time(mi::time_source<>& t, NT& ex) {
return t.make(mi::systemNowF{}, [ex]() { return ex; })();
}
class ConcurrentFlowSingleSender : public Test {
protected:
using TNT = mi::invoke_result_t<decltype(make_time), mi::time_source<>&, NT&>;
void reset() {
at_ = mi::now(tnt_) + 100ms;
signals_ = 0;
terminal_ = 0;
cancel_ = 0;
} }
void join() {
timeproduce_.join();
timecancel_.join();
} }
}
SCENARIO("flow single shared cancellation new thread", "[flow][sender]") { template <class MakeTokens>
auto nt = mi::new_thread(); void cancellation_test(
using NT = decltype(nt); std::chrono::system_clock::time_point at,
auto time = mi::time_source<>{}; MakeTokens make_tokens) {
auto tnt = time.make(mi::systemNowF{}, [nt](){ return nt; })(); auto f = mi::MAKE(flow_single_sender)([&, make_tokens](auto out) {
auto tcncl = time.make(mi::systemNowF{}, [nt](){ return nt; })();
std::atomic<int> signals{0};
auto at = mi::now(tnt) + 200ms;
GIVEN("A flow single sender") {
auto f = mi::MAKE(flow_single_sender)([&](auto out) {
// boolean cancellation // boolean cancellation
bool stop = false; bool stop = false;
auto set_stop = [](auto& stop) { auto set_stop = [](auto& stop) {
...@@ -137,7 +155,7 @@ SCENARIO("flow single shared cancellation new thread", "[flow][sender]") { ...@@ -137,7 +155,7 @@ SCENARIO("flow single shared cancellation new thread", "[flow][sender]") {
*stop = true; *stop = true;
} }
}; };
auto tokens = mi::shared_entangle(stop, set_stop); auto tokens = make_tokens(stop, set_stop);
using Stopper = decltype(tokens.second); using Stopper = decltype(tokens.second);
struct Data : mi::receiver<> { struct Data : mi::receiver<> {
...@@ -146,30 +164,32 @@ SCENARIO("flow single shared cancellation new thread", "[flow][sender]") { ...@@ -146,30 +164,32 @@ SCENARIO("flow single shared cancellation new thread", "[flow][sender]") {
}; };
auto up = mi::MAKE(receiver)( auto up = mi::MAKE(receiver)(
Data{std::move(tokens.second)}, Data{std::move(tokens.second)},
[&](auto& data, auto e) noexcept { [&](auto& data, auto) noexcept {
signals += 100000; signals_ += 100000;
++cancel_;
auto both = lock_both(data.stopper); auto both = lock_both(data.stopper);
(*(both.first))(both.second); (*(both.first))(both.second);
}, },
mi::on_done([&](auto& data) { mi::on_done([&](auto& data) {
signals += 10000; signals_ += 10000;
++cancel_;
auto both = lock_both(data.stopper); auto both = lock_both(data.stopper);
(*(both.first))(both.second); (*(both.first))(both.second);
})); }));
// make all the signals come from the same thread // make all the signals come from the same thread
tnt | tnt_ |
op::submit([stoppee = std::move(tokens.first), op::submit([&,
up = std::move(up), stoppee = std::move(tokens.first),
out = std::move(out), up_not_a_shadow_howtoeven = std::move(up),
at](auto tnt) mutable { out = std::move(out)](auto tnt) mutable {
// pass reference for cancellation. // pass reference for cancellation.
::mi::set_starting(out, std::move(up)); ::mi::set_starting(out, std::move(up_not_a_shadow_howtoeven));
// submit work to happen later // submit work to happen later
tnt | tnt |
op::submit_at( op::submit_at(
at, at_,
[stoppee = std::move(stoppee), [stoppee = std::move(stoppee),
out = std::move(out)](auto) mutable { out = std::move(out)](auto) mutable {
// check boolean to select signal // check boolean to select signal
...@@ -185,103 +205,89 @@ SCENARIO("flow single shared cancellation new thread", "[flow][sender]") { ...@@ -185,103 +205,89 @@ SCENARIO("flow single shared cancellation new thread", "[flow][sender]") {
}); });
}); });
WHEN("submit is applied and cancels the producer early") {
{
f | f |
op::blocking_submit( op::submit(
mi::on_value([&](int) { signals += 100; }), mi::on_value([&](int) { signals_ += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }), mi::on_error([&](auto) noexcept {
mi::on_done([&]() { signals += 1; }), signals_ += 1000;
++terminal_;
}),
mi::on_done([&]() {
signals_ += 1;
++terminal_;
}),
// stop producer before it is scheduled to run // stop producer before it is scheduled to run
mi::on_starting([&](auto up) { mi::on_starting([&, at](auto up) {
signals += 10; signals_ += 10;
tcncl | tcncl_ | op::submit_at(at, [up = std::move(up)](auto) mutable {
op::submit_at(
at - 50ms, [up = std::move(up)](auto) mutable {
::mi::set_done(up); ::mi::set_done(up);
}); });
})); }));
}
// make sure that the completion signal arrives
std::this_thread::sleep_for(100ms);
THEN( while (terminal_ == 0 || cancel_ == 0) {
"the starting, up.done and out.done signals are each recorded once") { std::this_thread::yield();
REQUIRE(signals == 10011);
} }
} }
WHEN("submit is applied and cancels the producer late") { NT ntproduce_{mi::new_thread()};
{ mi::time_source<> timeproduce_{};
f | TNT tnt_{make_time(timeproduce_, ntproduce_)};
op::blocking_submit(
mi::on_value([&](int) { signals += 100; }), NT ntcancel_{mi::new_thread()};
mi::on_error([&](auto) noexcept { signals += 1000; }), mi::time_source<> timecancel_{};
mi::on_done([&]() { signals += 1; }), TNT tcncl_{make_time(timecancel_, ntcancel_)};
// do not stop producer before it is scheduled to run
mi::on_starting([&](auto up) { std::atomic<int> signals_{0};
signals += 10; std::atomic<int> terminal_{0};
tcncl | std::atomic<int> cancel_{0};
op::submit_at( std::chrono::system_clock::time_point at_{mi::now(tnt_) + 100ms};
at + 50ms, [up = std::move(up)](auto) mutable { };
::mi::set_done(up);
TEST_F(ConcurrentFlowSingleSender, EarlySharedCancellation) {
cancellation_test(at_ - 50ms, [](auto& stop, auto& set_stop) {
return mi::shared_entangle(stop, set_stop);
}); });
}));
}
std::this_thread::sleep_for(100ms); join();
THEN( EXPECT_THAT(signals_, Eq(10011));
"the starting, up.done, out.value and out.done signals are each recorded once") { }
REQUIRE(signals == 10111);
}
}
WHEN("submit is applied and cancels the producer at the same time") { TEST_F(ConcurrentFlowSingleSender, LateSharedCancellation) {
// count known results cancellation_test(at_ + 50ms, [](auto& stop, auto& set_stop) {
return mi::shared_entangle(stop, set_stop);
});
join();
EXPECT_THAT(signals_, Eq(10111))
<< "expected that the starting, up.done, out.value and out.done signals are each recorded once";
}
TEST_F(ConcurrentFlowSingleSender, RacingSharedCancellation) {
int total = 0; int total = 0;
int cancellostrace = 0; // 10111 int cancellostrace = 0; // 10111
int cancelled = 0; // 10011 int cancelled = 0; // 10011
for (;;) { for (;;) {
signals = 0; reset();
// set completion time to be in 100ms cancellation_test(at_, [](auto& stop, auto& set_stop) {
at = mi::now(tnt) + 100ms; return mi::shared_entangle(stop, set_stop);
{
f |
op::blocking_submit(
mi::on_value([&](int) { signals += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }),
mi::on_done([&]() { signals += 1; }),
// stop producer at the same time that it is scheduled to run
mi::on_starting([&](auto up) {
signals += 10;
tcncl | op::submit_at(at, [up = std::move(up)](auto) mutable {
::mi::set_done(up);
}); });
}));
}
// make sure any cancellation signal has completed
std::this_thread::sleep_for(100ms);
// accumulate known signals // accumulate known signals
++total; ++total;
cancellostrace += signals == 10111; cancellostrace += signals_ == 10111;
cancelled += signals == 10011; cancelled += signals_ == 10011;
EXPECT_THAT(total, Eq(cancellostrace + cancelled))
<< signals_ << " <- this set of signals is unrecognized";
ASSERT_THAT(total, Lt(100))
// too long, show the signals distribution
<< "total " << total << ", cancel-lost-race " << cancellostrace
<< ", cancelled " << cancelled;
if (total != cancellostrace + cancelled) {
// display the unrecognized signals recorded
REQUIRE(signals == -1);
}
if (total >= 100) {
// too long, abort and show the signals distribution
WARN(
"total " << total << ", cancel-lost-race " << cancellostrace
<< ", cancelled " << cancelled);
break;
}
if (cancellostrace > 4 && cancelled > 4) { if (cancellostrace > 4 && cancelled > 4) {
// yay all known outcomes were observed! // yay all known outcomes were observed!
break; break;
...@@ -289,175 +295,55 @@ SCENARIO("flow single shared cancellation new thread", "[flow][sender]") { ...@@ -289,175 +295,55 @@ SCENARIO("flow single shared cancellation new thread", "[flow][sender]") {
// try again // try again
continue; continue;
} }
}
time.join(); join();
}
} }
SCENARIO("flow single entangled cancellation new thread", "[flow][sender]") { TEST_F(ConcurrentFlowSingleSender, EarlyEntangledCancellation) {
auto nt = mi::new_thread(); cancellation_test(at_ - 50ms, [](auto& stop, auto& set_stop) {
using NT = decltype(nt); return mi::entangle(stop, set_stop);
auto time = mi::time_source<>{};
auto tnt = time.make(mi::systemNowF{}, [nt](){ return nt; })();
auto tcncl = time.make(mi::systemNowF{}, [nt](){ return nt; })();
std::atomic<int> signals{0};
auto at = mi::now(tnt) + 200ms;
GIVEN("A flow single sender") {
auto f = mi::MAKE(flow_single_sender)([&](auto out) {
// boolean cancellation
bool stop = false;
auto set_stop = [](auto& stop) {
if (!!stop) {
*stop = true;
}
};
auto tokens = mi::entangle(stop, set_stop);
using Stopper = decltype(tokens.second);
struct Data : mi::receiver<> {
explicit Data(Stopper stopper) : stopper(std::move(stopper)) {}
Stopper stopper;
};
auto up = mi::MAKE(receiver)(
Data{std::move(tokens.second)},
[&](auto& data, auto e) noexcept {
signals += 100000;
auto both = lock_both(data.stopper);
(*(both.first))(both.second);
},
mi::on_done([&](auto& data) {
signals += 10000;
auto both = lock_both(data.stopper);
(*(both.first))(both.second);
}));
// make all the signals come from the same thread
tnt |
op::submit([stoppee = std::move(tokens.first),
up = std::move(up),
out = std::move(out),
at](auto tnt) mutable {
// pass reference for cancellation.
::mi::set_starting(out, std::move(up));
// submit work to happen later
tnt |
op::submit_at(
at,
[stoppee = std::move(stoppee),
out = std::move(out)](auto) mutable {
// check boolean to select signal
auto both = lock_both(stoppee);
if (!!both.first && !*(both.first)) {
::mi::set_value(out, 42);
::mi::set_done(out);
} else {
// cancellation is not an error
::mi::set_done(out);
}
});
}); });
});
WHEN("submit is applied and cancels the producer early") {
{
f |
op::blocking_submit(
mi::on_value([&](int) { signals += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }),
mi::on_done([&]() { signals += 1; }),
// stop producer before it is scheduled to run
mi::on_starting([&](auto up) {
signals += 10;
tcncl |
op::submit_at(
at - 50ms, [up = std::move(up)](auto) mutable {
::mi::set_done(up);
});
}));
}
// make sure that the completion signal arrives join();
std::this_thread::sleep_for(100ms);
THEN( EXPECT_THAT(signals_, Eq(10011));
"the starting, up.done and out.done signals are each recorded once") { }
REQUIRE(signals == 10011);
}
}
WHEN("submit is applied and cancels the producer late") { TEST_F(ConcurrentFlowSingleSender, LateEntangledCancellation) {
{ cancellation_test(at_ + 50ms, [](auto& stop, auto& set_stop) {
f | return mi::entangle(stop, set_stop);
op::blocking_submit(
mi::on_value([&](int) { signals += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }),
mi::on_done([&]() { signals += 1; }),
// do not stop producer before it is scheduled to run
mi::on_starting([&](auto up) {
signals += 10;
tcncl |
op::submit_at(
at + 50ms, [up = std::move(up)](auto) mutable {
::mi::set_done(up);
}); });
}));
}
std::this_thread::sleep_for(100ms); join();
THEN( EXPECT_THAT(signals_, Eq(10111))
"the starting, up.done, out.value and out.done signals are each recorded once") { << "expected that the starting, up.done, out.value and out.done signals are each recorded once";
REQUIRE(signals == 10111); }
}
}
WHEN("submit is applied and cancels the producer at the same time") { TEST_F(ConcurrentFlowSingleSender, RacingEntangledCancellation) {
// count known results
int total = 0; int total = 0;
int cancellostrace = 0; // 10111 int cancellostrace = 0; // 10111
int cancelled = 0; // 10011 int cancelled = 0; // 10011
for (;;) { for (;;) {
signals = 0; reset();
// set completion time to be in 100ms cancellation_test(at_, [](auto& stop, auto& set_stop) {
at = mi::now(tnt) + 100ms; return mi::entangle(stop, set_stop);
{
f |
op::blocking_submit(
mi::on_value([&](int) { signals += 100; }),
mi::on_error([&](auto) noexcept { signals += 1000; }),
mi::on_done([&]() { signals += 1; }),
// stop producer at the same time that it is scheduled to run
mi::on_starting([&](auto up) {
signals += 10;
tcncl | op::submit_at(at, [up = std::move(up)](auto) mutable {
::mi::set_done(up);
}); });
}));
}
// make sure any cancellation signal has completed
std::this_thread::sleep_for(200ms);
// accumulate known signals // accumulate known signals
++total; ++total;
cancellostrace += signals == 10111; cancellostrace += signals_ == 10111;
cancelled += signals == 10011; cancelled += signals_ == 10011;
EXPECT_THAT(total, Eq(cancellostrace + cancelled))
<< signals_ << " <- this set of signals is unrecognized";
ASSERT_THAT(total, Lt(100))
// too long, show the signals distribution
<< "total " << total << ", cancel-lost-race " << cancellostrace
<< ", cancelled " << cancelled;
if (total != cancellostrace + cancelled) {
// display the unrecognized signals recorded
REQUIRE(signals == -1);
}
if (total >= 100) {
// too long, abort and show the signals distribution
WARN(
"total " << total << ", cancel-lost-race " << cancellostrace
<< ", cancelled " << cancelled);
break;
}
if (cancellostrace > 4 && cancelled > 4) { if (cancellostrace > 4 && cancelled > 4) {
// yay all known outcomes were observed! // yay all known outcomes were observed!
break; break;
...@@ -465,9 +351,6 @@ SCENARIO("flow single entangled cancellation new thread", "[flow][sender]") { ...@@ -465,9 +351,6 @@ SCENARIO("flow single entangled cancellation new thread", "[flow][sender]") {
// try again // try again
continue; continue;
} }
}
time.join(); join();
}
} }
#endif
...@@ -21,24 +21,29 @@ using namespace std::literals; ...@@ -21,24 +21,29 @@ using namespace std::literals;
#include <folly/experimental/pushmi/flow_single_sender.h> #include <folly/experimental/pushmi/flow_single_sender.h>
#include <folly/experimental/pushmi/o/empty.h> #include <folly/experimental/pushmi/o/empty.h>
#include <folly/experimental/pushmi/o/extension_operators.h>
#include <folly/experimental/pushmi/o/just.h> #include <folly/experimental/pushmi/o/just.h>
#include <folly/experimental/pushmi/o/on.h> #include <folly/experimental/pushmi/o/on.h>
#include <folly/experimental/pushmi/o/transform.h> #include <folly/experimental/pushmi/o/submit.h>
#include <folly/experimental/pushmi/o/tap.h> #include <folly/experimental/pushmi/o/tap.h>
#include <folly/experimental/pushmi/o/transform.h>
#include <folly/experimental/pushmi/o/via.h> #include <folly/experimental/pushmi/o/via.h>
#include <folly/experimental/pushmi/o/submit.h>
#include <folly/experimental/pushmi/o/extension_operators.h>
#include <folly/experimental/pushmi/new_thread.h> #include <folly/experimental/pushmi/new_thread.h>
#include <folly/experimental/pushmi/time_source.h>
#include <folly/experimental/pushmi/strand.h> #include <folly/experimental/pushmi/strand.h>
#include <folly/experimental/pushmi/time_source.h>
using namespace folly::pushmi::aliases; using namespace folly::pushmi::aliases;
#if 0 #include <folly/Conv.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
using namespace testing;
struct countdownsingle { struct countdownsingle {
countdownsingle(int& c) explicit countdownsingle(int& c) : counter(&c) {}
: counter(&c) {}
int* counter; int* counter;
...@@ -50,115 +55,113 @@ struct countdownsingle { ...@@ -50,115 +55,113 @@ struct countdownsingle {
} }
}; };
SCENARIO( "new_thread executor", "[new_thread][sender]" ) { using NT = decltype(mi::new_thread());
inline auto make_time(mi::time_source<>& t, NT& ex) {
return t.make(mi::systemNowF{}, [ex]() { return ex; })();
}
GIVEN( "A new_thread time_single_sender" ) { class NewthreadExecutor : public Test {
auto nt = v::new_thread(); public:
using NT = decltype(nt); ~NewthreadExecutor() override {
time_.join();
}
auto time = mi::time_source<>{}; protected:
using TNT = mi::invoke_result_t<decltype(make_time), mi::time_source<>&, NT&>;
auto tnt = time.make(mi::systemNowF{}, [nt](){ return nt; })(); NT nt_{mi::new_thread()};
mi::time_source<> time_{};
TNT tnt_{make_time(time_, nt_)};
};
WHEN( "blocking submit now" ) { TEST_F(NewthreadExecutor, BlockingSubmitNow) {
auto signals = 0; auto signals = 0;
auto start = v::now(tnt); auto start = v::now(tnt_);
auto signaled = start; auto signaled = start;
tnt | tnt_ | op::transform([](auto tnt) { return tnt | ep::now(); }) |
op::transform([](auto tnt){ return tnt | ep::now(); }) |
op::blocking_submit( op::blocking_submit(
[&](auto at){ [&](auto at) {
signaled = at; signaled = at;
signals += 100; }, signals += 100;
[&](auto e) noexcept { signals += 1000; }, },
[&](){ signals += 10; }); [&](auto) noexcept { signals += 1000; },
[&]() { signals += 10; });
THEN( "the value and done signals are recorded once and the value signal did not drift much" ) {
REQUIRE( signals == 110 ); EXPECT_THAT(signals, Eq(110))
auto delay = std::chrono::duration_cast<std::chrono::milliseconds>((signaled - start)).count(); << "expected that the value and done signals are recorded once and the value signal did not drift much";
INFO("The delay is " << ::Catch::Detail::stringify(delay)); auto delay =
REQUIRE( delay < 1000 ); std::chrono::duration_cast<std::chrono::milliseconds>((signaled - start))
} .count();
} EXPECT_THAT(delay, Lt(1000)) << "The delay is " << delay;
}
WHEN( "blocking get now" ) { TEST_F(NewthreadExecutor, BlockingGetNow) {
auto start = v::now(tnt); auto start = v::now(tnt_);
auto signaled = tnt | auto signaled = tnt_ | op::transform([](auto tnt) { return v::now(tnt); }) |
op::transform([](auto tnt){
return v::now(tnt);
}) |
op::get<std::chrono::system_clock::time_point>; op::get<std::chrono::system_clock::time_point>;
THEN( "the signal did not drift much" ) { auto delay =
auto delay = std::chrono::duration_cast<std::chrono::milliseconds>((signaled - start)).count(); std::chrono::duration_cast<std::chrono::milliseconds>((signaled - start))
INFO("The delay is " << ::Catch::Detail::stringify(delay)); .count();
REQUIRE( delay < 1000 );
}
}
WHEN( "submissions are ordered in time" ) { EXPECT_THAT(delay, Lt(1000)) << "The delay is " << delay;
}
TEST_F(NewthreadExecutor, SubmissionsAreOrderedInTime) {
std::vector<std::string> times; std::vector<std::string> times;
std::atomic<int> pushed(0); std::atomic<int> pushed{0};
auto push = [&](int time) { auto push = [&](int time) {
return v::on_value([&, time](auto) { times.push_back(std::to_string(time)); ++pushed; }); return v::on_value([&, time](auto) {
times.push_back(folly::to<std::string>(time));
++pushed;
});
}; };
tnt | op::submit(v::on_value([push](auto tnt) { tnt_ | op::submit(v::on_value([push](auto tnt) {
auto now = tnt | ep::now(); auto now = tnt | ep::now();
tnt | tnt | op::submit_after(40ms, push(40)) |
op::submit_after(40ms, push(40)) | op::submit_at(now + 10ms, push(10)) | op::submit_after(20ms, push(20)) |
op::submit_at(now + 10ms, push(10)) |
op::submit_after(20ms, push(20)) |
op::submit_at(now + 10ms, push(11)); op::submit_at(now + 10ms, push(11));
})); }));
while(pushed.load() < 4) { std::this_thread::sleep_for(10ms); } while (pushed.load() < 4) {
std::this_thread::yield();
THEN( "the items were pushed in time order not insertion order" ) {
REQUIRE( times == std::vector<std::string>{"10", "11", "20", "40"});
}
} }
WHEN( "now is called" ) { EXPECT_THAT(times, ElementsAre("10", "11", "20", "40"))
<< "expected that the items were pushed in time order not insertion order";
}
TEST_F(NewthreadExecutor, NowIsCalled) {
bool done = false; bool done = false;
tnt | ep::now(); tnt_ | ep::now();
tnt | op::blocking_submit([&](auto tnt) { tnt_ | op::blocking_submit([&](auto tnt) {
tnt | ep::now(); tnt | ep::now();
done = true; done = true;
}); });
THEN( "both calls to now() complete" ) { EXPECT_THAT(done, Eq(true)) << "exptected that both calls to now() complete";
REQUIRE( done == true ); }
}
}
WHEN( "blocking submit" ) { TEST_F(NewthreadExecutor, BlockingSubmit) {
auto signals = 0; auto signals = 0;
nt | nt_ | op::transform([](auto) { return 42; }) |
op::transform([](auto){ return 42; }) |
op::blocking_submit( op::blocking_submit(
[&](auto){ [&](auto) { signals += 100; },
signals += 100; }, [&](auto) noexcept { signals += 1000; },
[&](auto e) noexcept { signals += 1000; }, [&]() { signals += 10; });
[&](){ signals += 10; });
THEN( "the value and done signals are recorded once" ) { EXPECT_THAT(signals, Eq(110))
REQUIRE( signals == 110 ); << "the value and done signals are recorded once";
} }
}
WHEN( "blocking get" ) { TEST_F(NewthreadExecutor, BlockingGet) {
auto v = nt | auto v = nt_ | op::transform([](auto) { return 42; }) | op::get<int>;
op::transform([](auto){
return 42;
}) |
op::get<int>;
THEN( "the result is" ) { EXPECT_THAT(v, Eq(42)) << "expected that the result would be different";
REQUIRE( v == 42 ); }
}
}
WHEN( "virtual derecursion is triggered" ) { TEST_F(NewthreadExecutor, VirtualDerecursion) {
int counter = 100'000; int counter = 100'000;
std::function<void(::folly::pushmi::any_executor_ref<> exec)> recurse; std::function<void(::folly::pushmi::any_executor_ref<> exec)> recurse;
recurse = [&](::folly::pushmi::any_executor_ref<> nt) { recurse = [&](::folly::pushmi::any_executor_ref<> nt) {
...@@ -166,23 +169,22 @@ SCENARIO( "new_thread executor", "[new_thread][sender]" ) { ...@@ -166,23 +169,22 @@ SCENARIO( "new_thread executor", "[new_thread][sender]" ) {
return; return;
nt | op::submit(recurse); nt | op::submit(recurse);
}; };
nt | op::blocking_submit([&](auto nt) { recurse(nt); }); nt_ | op::blocking_submit([&](auto nt) { recurse(nt); });
THEN( "all nested submissions complete" ) { EXPECT_THAT(counter, Eq(0))
REQUIRE( counter == 0 ); << "expected that all nested submissions complete";
} }
}
WHEN( "static derecursion is triggered" ) { TEST_F(NewthreadExecutor, StaticDerecursion) {
int counter = 100'000; int counter = 100'000;
countdownsingle single{counter}; countdownsingle single{counter};
nt | op::blocking_submit(single); nt_ | op::blocking_submit(single);
THEN( "all nested submissions complete" ) {
REQUIRE( counter == 0 );
}
}
WHEN( "used with on" ) { EXPECT_THAT(counter, Eq(0))
<< "expected that all nested submissions complete";
}
TEST_F(NewthreadExecutor, UsedWithOn) {
std::vector<std::string> values; std::vector<std::string> values;
auto sender = ::folly::pushmi::make_single_sender([](auto out) { auto sender = ::folly::pushmi::make_single_sender([](auto out) {
::folly::pushmi::set_value(out, 2.0); ::folly::pushmi::set_value(out, 2.0);
...@@ -192,14 +194,15 @@ SCENARIO( "new_thread executor", "[new_thread][sender]" ) { ...@@ -192,14 +194,15 @@ SCENARIO( "new_thread executor", "[new_thread][sender]" ) {
::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::min()); ::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::min());
::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::max()); ::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
}); });
sender | op::on([&](){return nt;}) | sender | op::on([&]() { return nt_; }) |
op::blocking_submit(v::on_value([&](auto v) { values.push_back(std::to_string(v)); })); op::blocking_submit(v::on_value(
THEN( "only the first item was pushed" ) { [&](auto v) { values.push_back(folly::to<std::string>(v)); }));
REQUIRE(values == std::vector<std::string>{"2.000000"});
} EXPECT_THAT(values, ElementsAre(folly::to<std::string>(2.0)))
} << "expected that only the first item was pushed";
}
WHEN( "used with via" ) { TEST_F(NewthreadExecutor, UsedWithVia) {
std::vector<std::string> values; std::vector<std::string> values;
auto sender = ::folly::pushmi::make_single_sender([](auto out) { auto sender = ::folly::pushmi::make_single_sender([](auto out) {
::folly::pushmi::set_value(out, 2.0); ::folly::pushmi::set_value(out, 2.0);
...@@ -209,14 +212,10 @@ SCENARIO( "new_thread executor", "[new_thread][sender]" ) { ...@@ -209,14 +212,10 @@ SCENARIO( "new_thread executor", "[new_thread][sender]" ) {
::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::min()); ::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::min());
::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::max()); ::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
}); });
sender | op::via(mi::strands(nt)) | sender | op::via(mi::strands(nt_)) |
op::blocking_submit(v::on_value([&](auto v) { values.push_back(std::to_string(v)); })); op::blocking_submit(v::on_value(
THEN( "only the first item was pushed" ) { [&](auto v) { values.push_back(folly::to<std::string>(v)); }));
REQUIRE(values == std::vector<std::string>{"2.000000"});
}
}
time.join(); EXPECT_THAT(values, ElementsAre(folly::to<std::string>(2.0)))
} << "expected that only the first item was pushed";
} }
#endif
...@@ -29,140 +29,169 @@ using namespace std::literals; ...@@ -29,140 +29,169 @@ using namespace std::literals;
#include <folly/experimental/pushmi/o/tap.h> #include <folly/experimental/pushmi/o/tap.h>
#include <folly/experimental/pushmi/o/transform.h> #include <folly/experimental/pushmi/o/transform.h>
#include <folly/experimental/pushmi/trampoline.h>
#include <folly/experimental/pushmi/new_thread.h> #include <folly/experimental/pushmi/new_thread.h>
#include <folly/experimental/pushmi/trampoline.h>
using namespace folly::pushmi::aliases; using namespace folly::pushmi::aliases;
#if 0 #include <folly/portability/GMock.h>
SCENARIO( "empty can be used with tap and submit", "[empty][sender]" ) { #include <folly/portability/GTest.h>
GIVEN( "An empty sender" ) { using namespace testing;
TEST(EmptyNoArgSingleSender, TapAndSubmit) {
auto e = op::empty(); auto e = op::empty();
using E = decltype(e); using E = decltype(e);
REQUIRE( v::SenderTo<E, v::any_receiver<>, v::is_single<>> ); EXPECT_THAT((v::SenderTo<E, v::any_receiver<>, v::is_single<>>), Eq(true))
<< "expected empty to return a single sender that can take an any_receiver";
WHEN( "tap and submit are applied" ) {
int signals = 0; int signals = 0;
e | e |
op::tap( op::tap(
[&](){ signals += 100; }, [&]() { signals += 100; },
[&](auto e) noexcept { signals += 1000; }, [&](auto) noexcept { signals += 1000; },
[&](){ signals += 10; }) | [&]() { signals += 10; }) |
op::submit( op::submit(
[&](){ signals += 100; }, [&]() { signals += 100; },
[&](auto e) noexcept { signals += 1000; }, [&](auto) noexcept { signals += 1000; },
[&](){ signals += 10; }); [&]() { signals += 10; });
THEN( "the done signal is recorded twice" ) { EXPECT_THAT(signals, Eq(20))
REQUIRE( signals == 20 ); << "expected the done signal to be recorded twice";
}
WHEN( "future_from is applied" ) { EXPECT_THROW(v::future_from(e).get(), std::future_error)
REQUIRE_THROWS_AS(v::future_from(e).get(), std::future_error); << "expected future_error when future_from is applied";
THEN( "future_from(e) returns std::future<void>" ) { EXPECT_THAT(
REQUIRE( std::is_same<std::future<void>, decltype(v::future_from(e))>::value ); (std::is_same<std::future<void>, decltype(v::future_from(e))>::value),
} Eq(true))
} << "expected future_from(e) to return std::future<void>";
} }
}
GIVEN( "An empty int single_sender" ) { TEST(EmptyIntSingleSender, TapAndSubmit) {
auto e = op::empty<int>(); auto e = op::empty<int>();
using E = decltype(e); using E = decltype(e);
REQUIRE( v::SenderTo<E, v::any_receiver<std::exception_ptr, int>, v::is_single<>> ); EXPECT_THAT(
(v::SenderTo<
WHEN( "tap and submit are applied" ) { E,
v::any_receiver<std::exception_ptr, int>,
v::is_single<>>),
Eq(true))
<< "expected empty to return a single sender that can take an any_receiver<int>";
int signals = 0; int signals = 0;
e | e |
op::tap( op::tap(
[&](auto v){ signals += 100; }, [&](auto) { signals += 100; },
[&](auto e) noexcept { signals += 1000; }, [&](auto) noexcept { signals += 1000; },
[&](){ signals += 10; }) | [&]() { signals += 10; }) |
op::submit( op::submit(
[&](auto v){ signals += 100; }, [&](auto) { signals += 100; },
[&](auto e) noexcept { signals += 1000; }, [&](auto) noexcept { signals += 1000; },
[&](){ signals += 10; }); [&]() { signals += 10; });
THEN( "the done signal is recorded twice" ) { EXPECT_THAT(signals, Eq(20))
REQUIRE( signals == 20 ); << "expected the done signal to be recorded twice";
}
}
}
}
SCENARIO( "just() can be used with transform and submit", "[just][sender]" ) { EXPECT_THROW(v::future_from<int>(e).get(), std::future_error)
<< "expected future_error when future_from is applied";
GIVEN( "A just int single_sender" ) { EXPECT_THAT(
(std::is_same<std::future<int>, decltype(v::future_from<int>(e))>::value),
Eq(true))
<< "expected future_from(e) to return std::future<void>";
}
TEST(JustIntSingleSender, TransformAndSubmit) {
auto j = op::just(20); auto j = op::just(20);
using J = decltype(j); using J = decltype(j);
REQUIRE( v::SenderTo<J, v::any_receiver<std::exception_ptr, int>, v::is_single<>> ); EXPECT_THAT(
(v::SenderTo<
J,
v::any_receiver<std::exception_ptr, int>,
v::is_single<>>),
Eq(true))
<< "expected empty to return a single sender that can take an any_receiver<int>";
WHEN( "transform and submit are applied" ) {
int signals = 0; int signals = 0;
int value = 0; int value = 0;
j | j |
op::transform( op::transform(
[&](int v){ signals += 10000; return v + 1; }, [&](int v) {
[&](auto v){ std:abort(); return v; }) | signals += 10000;
op::transform( return v + 1;
[&](int v){ signals += 10000; return v * 2; }) | },
[&](auto v) {
std::abort();
return v;
}) |
op::transform([&](int v) {
signals += 10000;
return v * 2;
}) |
op::submit( op::submit(
[&](auto v){ value = v; signals += 100; }, [&](auto v) {
[&](auto e) noexcept { signals += 1000; }, value = v;
[&](){ signals += 10; }); signals += 100;
},
[&](auto) noexcept { signals += 1000; },
[&]() { signals += 10; });
EXPECT_THAT(signals, Eq(20110))
<< "expected that the transform signal is recorded twice and that the value and done signals once each";
THEN( "the transform signal is recorded twice, the value and done signals once and the result is correct" ) { EXPECT_THAT(value, Eq(42)) << "expected a different result";
REQUIRE( signals == 20110 );
REQUIRE( value == 42 );
}
}
WHEN( "future_from<int> is applied" ) {
auto twenty = v::future_from<int>(j).get(); auto twenty = v::future_from<int>(j).get();
THEN( "the value signal is recorded once and the result is correct" ) { EXPECT_THAT(twenty, Eq(20))
REQUIRE( twenty == 20 ); << "expected a different result from future_from(e).get()";
REQUIRE( std::is_same<std::future<int>, decltype(v::future_from<int>(j))>::value );
}
}
}
}
SCENARIO( "from() can be used with transform and submit", "[from][sender]" ) { EXPECT_THAT(
(std::is_same<std::future<int>, decltype(v::future_from<int>(j))>::value),
Eq(true))
<< "expected future_from(e) to return std::future<int>";
}
GIVEN( "A from int many_sender" ) { TEST(FromIntManySender, TransformAndSubmit) {
int arr[] = {0, 9, 99}; std::array<int, 3> arr{0, 9, 99};
auto m = op::from(arr); auto m = op::from(arr);
using M = decltype(m); using M = decltype(m);
REQUIRE( v::SenderTo<M, v::any_receiver<std::exception_ptr, int>, v::is_many<>> ); EXPECT_THAT(
(v::SenderTo<M, v::any_receiver<std::exception_ptr, int>, v::is_many<>>),
Eq(true))
<< "expected empty to return a many sender that can take an any_receiver<int>";
WHEN( "transform and submit are applied" ) {
int signals = 0; int signals = 0;
int value = 0; int value = 0;
m | m |
op::transform( op::transform(
[&](int v){ signals += 10000; return v + 1; }, [&](int v) {
[&](auto v){ std:abort(); return v; }) | signals += 10000;
op::transform( return v + 1;
[&](int v){ signals += 10000; return v * 2; }) | },
[&](auto v) {
std::abort();
return v;
}) |
op::transform([&](int v) {
signals += 10000;
return v * 2;
}) |
op::submit( op::submit(
[&](auto v){ value += v; signals += 100; }, [&](auto v) {
[&](auto e) noexcept { signals += 1000; }, value += v;
[&](){ signals += 10; }); signals += 100;
},
[&](auto) noexcept { signals += 1000; },
[&]() { signals += 10; });
THEN( "the transform signal is recorded twice, the value signal once and the result is correct" ) { EXPECT_THAT(signals, Eq(60310))
REQUIRE( signals == 60310 ); << "expected that the transform signal is recorded six times and that the value signal three times and done signal once";
REQUIRE( value == 222 );
}
}
} EXPECT_THAT(value, Eq(222)) << "expected a different result";
} }
#endif
...@@ -21,23 +21,28 @@ using namespace std::literals; ...@@ -21,23 +21,28 @@ using namespace std::literals;
#include <folly/experimental/pushmi/flow_single_sender.h> #include <folly/experimental/pushmi/flow_single_sender.h>
#include <folly/experimental/pushmi/o/empty.h> #include <folly/experimental/pushmi/o/empty.h>
#include <folly/experimental/pushmi/o/extension_operators.h>
#include <folly/experimental/pushmi/o/just.h> #include <folly/experimental/pushmi/o/just.h>
#include <folly/experimental/pushmi/o/on.h> #include <folly/experimental/pushmi/o/on.h>
#include <folly/experimental/pushmi/o/transform.h> #include <folly/experimental/pushmi/o/submit.h>
#include <folly/experimental/pushmi/o/tap.h> #include <folly/experimental/pushmi/o/tap.h>
#include <folly/experimental/pushmi/o/transform.h>
#include <folly/experimental/pushmi/o/via.h> #include <folly/experimental/pushmi/o/via.h>
#include <folly/experimental/pushmi/o/submit.h>
#include <folly/experimental/pushmi/o/extension_operators.h>
#include <folly/experimental/pushmi/inline.h> #include <folly/experimental/pushmi/inline.h>
#include <folly/experimental/pushmi/trampoline.h> #include <folly/experimental/pushmi/trampoline.h>
using namespace folly::pushmi::aliases; using namespace folly::pushmi::aliases;
#if 0 #include <folly/Conv.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
using namespace testing;
struct countdownsingle { struct countdownsingle {
countdownsingle(int& c) explicit countdownsingle(int& c) : counter(&c) {}
: counter(&c) {}
int* counter; int* counter;
...@@ -49,38 +54,32 @@ struct countdownsingle { ...@@ -49,38 +54,32 @@ struct countdownsingle {
} }
}; };
SCENARIO( "trampoline executor", "[trampoline][sender]" ) { using TR = decltype(mi::trampoline());
GIVEN( "A trampoline single_sender" ) { class TrampolineExecutor : public Test {
auto tr = v::trampoline(); protected:
using TR = decltype(tr); TR tr_{mi::trampoline()};
};
WHEN( "submit" ) { TEST_F(TrampolineExecutor, TransformAndSubmit) {
auto signals = 0; auto signals = 0;
tr | tr_ | op::transform([](auto) { return 42; }) |
op::transform([](auto){ return 42; }) |
op::submit( op::submit(
[&](auto){ [&](auto) { signals += 100; },
signals += 100; }, [&](auto) noexcept { signals += 1000; },
[&](auto e) noexcept { signals += 1000; }, [&]() { signals += 10; });
[&](){ signals += 10; });
THEN( "the value and done signals are each recorded once" ) { EXPECT_THAT(signals, Eq(110))
REQUIRE( signals == 110 ); << "expected that the value and done signals are each recorded once";
} }
}
WHEN( "blocking get" ) { TEST_F(TrampolineExecutor, BlockingGet) {
auto v = tr | auto v = tr_ | op::transform([](auto) { return 42; }) | op::get<int>;
op::transform([](auto){ return 42; }) |
op::get<int>;
THEN( "the result is" ) { EXPECT_THAT(v, Eq(42)) << "expected that the result would be different";
REQUIRE( v == 42 ); }
}
}
WHEN( "virtual derecursion is triggered" ) { TEST_F(TrampolineExecutor, VirtualDerecursion) {
int counter = 100'000; int counter = 100'000;
std::function<void(::folly::pushmi::any_executor_ref<> exec)> recurse; std::function<void(::folly::pushmi::any_executor_ref<> exec)> recurse;
recurse = [&](::folly::pushmi::any_executor_ref<> tr) { recurse = [&](::folly::pushmi::any_executor_ref<> tr) {
...@@ -88,23 +87,22 @@ SCENARIO( "trampoline executor", "[trampoline][sender]" ) { ...@@ -88,23 +87,22 @@ SCENARIO( "trampoline executor", "[trampoline][sender]" ) {
return; return;
tr | op::submit(recurse); tr | op::submit(recurse);
}; };
tr | op::submit([&](auto exec) { recurse(exec); }); tr_ | op::submit([&](auto exec) { recurse(exec); });
THEN( "all nested submissions complete" ) { EXPECT_THAT(counter, Eq(0))
REQUIRE( counter == 0 ); << "expected that all nested submissions complete";
} }
}
WHEN( "static derecursion is triggered" ) { TEST_F(TrampolineExecutor, StaticDerecursion) {
int counter = 100'000; int counter = 100'000;
countdownsingle single{counter}; countdownsingle single{counter};
tr | op::submit(single); tr_ | op::submit(single);
THEN( "all nested submissions complete" ) {
REQUIRE( counter == 0 ); EXPECT_THAT(counter, Eq(0))
} << "expected that all nested submissions complete";
} }
WHEN( "used with on" ) { TEST_F(TrampolineExecutor, UsedWithOn) {
std::vector<std::string> values; std::vector<std::string> values;
auto sender = ::folly::pushmi::make_single_sender([](auto out) { auto sender = ::folly::pushmi::make_single_sender([](auto out) {
::folly::pushmi::set_value(out, 2.0); ::folly::pushmi::set_value(out, 2.0);
...@@ -114,18 +112,23 @@ SCENARIO( "trampoline executor", "[trampoline][sender]" ) { ...@@ -114,18 +112,23 @@ SCENARIO( "trampoline executor", "[trampoline][sender]" ) {
::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::min()); ::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::min());
::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::max()); ::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
}); });
auto inlineon = sender | op::on([&](){return mi::inline_executor();}); auto inlineon = sender | op::on([&]() { return mi::inline_executor(); });
inlineon | inlineon | op::submit(v::on_value([&](auto v) {
op::submit(v::on_value([&](auto v) { values.push_back(std::to_string(v)); })); values.push_back(folly::to<std::string>(v));
THEN( "only the first item was pushed" ) { }));
REQUIRE(values == std::vector<std::string>{"2.000000"});
} EXPECT_THAT(values, ElementsAre(folly::to<std::string>(2.0)))
THEN( "executor was not changed by on" ) { << "expected that only the first item was pushed";
REQUIRE(std::is_same<mi::executor_t<decltype(sender)>, mi::executor_t<decltype(inlineon)>>::value);
} EXPECT_THAT(
} (std::is_same<
mi::executor_t<decltype(sender)>,
mi::executor_t<decltype(inlineon)>>::value),
Eq(true))
<< "expected that executor was not changed by on";
}
WHEN( "used with via" ) { TEST_F(TrampolineExecutor, UsedWithVia) {
std::vector<std::string> values; std::vector<std::string> values;
auto sender = ::folly::pushmi::make_single_sender([](auto out) { auto sender = ::folly::pushmi::make_single_sender([](auto out) {
::folly::pushmi::set_value(out, 2.0); ::folly::pushmi::set_value(out, 2.0);
...@@ -135,16 +138,18 @@ SCENARIO( "trampoline executor", "[trampoline][sender]" ) { ...@@ -135,16 +138,18 @@ SCENARIO( "trampoline executor", "[trampoline][sender]" ) {
::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::min()); ::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::min());
::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::max()); ::folly::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
}); });
auto inlinevia = sender | op::via([&](){return mi::inline_executor();}); auto inlinevia = sender | op::via([&]() { return mi::inline_executor(); });
inlinevia | inlinevia | op::submit(v::on_value([&](auto v) {
op::submit(v::on_value([&](auto v) { values.push_back(std::to_string(v)); })); values.push_back(folly::to<std::string>(v));
THEN( "only the first item was pushed" ) { }));
REQUIRE(values == std::vector<std::string>{"2.000000"});
} EXPECT_THAT(values, ElementsAre(folly::to<std::string>(2.0)))
THEN( "executor was changed by via" ) { << "expected that only the first item was pushed";
REQUIRE(!std::is_same<mi::executor_t<decltype(sender)>, mi::executor_t<decltype(inlinevia)>>::value);
} EXPECT_THAT(
} (!std::is_same<
} mi::executor_t<decltype(sender)>,
mi::executor_t<decltype(inlinevia)>>::value),
Eq(true))
<< "expected that executor was changed by via";
} }
#endif
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