blob: 7f6b362380d6effe3142a20ad416df9a96f18cd9 [file] [log] [blame]
// 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 <unistd.h>
#include <string>
#include <thread>
#include <lib/fit/bridge.h>
#include <lib/fit/sequencer.h>
#include <lib/fit/single_threaded_executor.h>
#include <unittest/unittest.h>
namespace {
bool sequencing_tasks() {
BEGIN_TEST;
fit::sequencer seq;
std::string str;
// This promise writes ":a" sequentially then writes ":a2" later.
auto a = fit::make_promise([&] { str += ":a"; })
.wrap_with(seq)
.then([&](const fit::result<>&) { str += ":a2"; });
// This promise writes ":b" sequentially then writes ":b2" and ":b3" later.
// Also schedules another sequential task that writes ":e".
auto b = fit::make_promise([&](fit::context& context) {
str += ":b";
context.executor()->schedule_task(
fit::make_promise([&] { str += ":e"; })
.wrap_with(seq));
})
.wrap_with(seq)
.then([&, count = 0](fit::context& context, const fit::result<>&) mutable
-> fit::result<> {
if (++count == 5) {
str += ":b3";
return fit::error();
}
str += ":b2";
context.suspend_task().resume_task(); // immediately resume
return fit::pending();
});
// This promise writes ":c" sequentially then abandons itself.
auto c = fit::make_promise([&](fit::context& context) {
str += ":c";
context.suspend_task(); // abandon result
return fit::pending();
})
.wrap_with(seq)
.then([&](const fit::result<>&) { str += ":c2"; });
// This promise writes ":d" sequentially.
auto d = fit::make_promise([&] { str += ":d"; })
.wrap_with(seq);
// These promises just write ":z1" and ":z2" whenever they happen to run.
auto z1 = fit::make_promise([&] { str += ":z1"; });
auto z2 = fit::make_promise([&] { str += ":z2"; });
// Schedule the promises in an order which intentionally does not
// match the sequencing.
fit::single_threaded_executor executor;
executor.schedule_task(std::move(z1));
executor.schedule_task(std::move(b));
executor.schedule_task(std::move(c));
executor.schedule_task(std::move(a));
executor.schedule_task(std::move(d));
executor.schedule_task(std::move(z2));
executor.run();
// Evaluate the promises and check the execution order.
EXPECT_STR_EQ(":z1:a:a2:z2:b:b2:c:b2:d:b2:e:b2:b3", str.c_str());
END_TEST;
}
bool thread_safety() {
BEGIN_TEST;
fit::sequencer seq;
fit::single_threaded_executor executor;
uint64_t run_count = 0;
// Schedule work from a few threads, just to show that we can.
constexpr int num_threads = 4;
constexpr int num_tasks_per_thread = 100;
std::thread threads[num_threads];
for (int i = 0; i < num_threads; i++) {
fit::bridge bridge;
threads[i] =
std::thread([&, completer = std::move(bridge.completer)]() mutable {
for (int j = 0; j < num_tasks_per_thread; j++) {
executor.schedule_task(
fit::make_promise([&] { run_count++; }).wrap_with(seq));
usleep(1);
}
completer.complete_ok();
});
executor.schedule_task(bridge.consumer.promise());
}
// Run the tasks.
executor.run();
for (int i = 0; i < num_threads; i++)
threads[i].join();
// We expect all tasks to have run.
EXPECT_EQ(num_threads * num_tasks_per_thread, run_count);
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(sequencer_tests)
RUN_TEST(sequencing_tasks)
RUN_TEST(thread_safety)
END_TEST_CASE(sequencer_tests)