#include <atomic>
#include <chrono>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
#include <agent_smith/agent_smith.hpp>
#include <agent_smith/detail/async_queue.hpp>
#include <agent_smith/detail/gather_all.hpp>
#include <gtest/gtest.h>
namespace agent_smith
{
namespace
{
mw::Task<mw::E<int>> markAndSleep(Runtime& runtime, std::atomic<bool>& started,
int value, std::chrono::milliseconds duration)
{
started.store(true);
auto slept = co_await runtime.sleepFor(duration);
if(!slept)
{
co_return std::unexpected(slept.error());
}
co_return value;
}
mw::Task<mw::E<int>> awaitStarted(internal::StartedTask<int> task)
{
co_return co_await std::move(task);
}
TEST(RuntimeTest, ConstructsSharedHttpSession)
{
Runtime runtime;
EXPECT_NE(&runtime.http(), nullptr);
EXPECT_EQ(runtime.http().connectionTimeout(), std::chrono::seconds(60));
EXPECT_EQ(runtime.http().transferTimeout(), std::chrono::seconds(60));
}
TEST(RuntimeTest, RunsPlainBlockingResult)
{
Runtime runtime;
auto result = syncWait(runtime.runOnWorker([]() { return 42; }));
ASSERT_TRUE(result.has_value());
EXPECT_EQ(*result, 42);
}
TEST(RuntimeTest, FlattensExpectedBlockingResult)
{
Runtime runtime;
auto result = syncWait(runtime.runOnWorker(
[]() -> mw::E<int>
{ return std::unexpected(mw::runtimeError("tool failed")); }));
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().msg(), "tool failed");
}
TEST(RuntimeTest, ConvertsBlockingExceptionsToErrors)
{
Runtime runtime;
auto result = syncWait(runtime.runOnWorker(
[]() -> int { throw std::runtime_error("unexpected failure"); }));
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().msg(), "unexpected failure");
}
TEST(RuntimeTest, SleepsWithoutBlockingTheCaller)
{
Runtime runtime;
const auto start = std::chrono::steady_clock::now();
auto result = syncWait(runtime.sleepFor(std::chrono::milliseconds(25)));
ASSERT_TRUE(result.has_value());
EXPECT_GE(std::chrono::steady_clock::now() - start,
std::chrono::milliseconds(20));
}
TEST(StartedTaskTest, StartsImmediatelyAndCanBeAwaited)
{
Runtime runtime;
std::atomic<bool> started = false;
auto task = internal::StartedTask<int>::start(
markAndSleep(runtime, started, 7, std::chrono::milliseconds(1)));
EXPECT_TRUE(started.load());
auto result = syncWait(awaitStarted(std::move(task)));
ASSERT_TRUE(result.has_value());
EXPECT_EQ(*result, 7);
}
TEST(AsyncQueueTest, DeliversItemsAcrossThreadsAndCloses)
{
internal::AsyncQueue<int> queue;
std::thread producer(
[&queue]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
queue.push(9);
queue.close();
});
auto first = syncWait(queue.pop());
auto second = syncWait(queue.pop());
producer.join();
ASSERT_TRUE(first.has_value());
EXPECT_EQ(*first, 9);
EXPECT_FALSE(second.has_value());
}
TEST(AsyncQueueTest, DrainsBufferedItemsBeforeClosure)
{
internal::AsyncQueue<int> queue;
queue.push(3);
queue.push(5);
queue.close();
auto first = syncWait(queue.pop());
auto second = syncWait(queue.pop());
auto closed = syncWait(queue.pop());
ASSERT_TRUE(first.has_value());
ASSERT_TRUE(second.has_value());
EXPECT_EQ(*first, 3);
EXPECT_EQ(*second, 5);
EXPECT_FALSE(closed.has_value());
}
TEST(GatherAllTest, StartsAllTasksAndPreservesInputOrder)
{
Runtime runtime;
std::atomic<bool> first_started = false;
std::atomic<bool> second_started = false;
std::vector<mw::Task<mw::E<int>>> tasks;
tasks.push_back(
markAndSleep(runtime, first_started, 1, std::chrono::milliseconds(20)));
tasks.push_back(
markAndSleep(runtime, second_started, 2, std::chrono::milliseconds(1)));
auto results = syncWait(internal::gatherAll(std::move(tasks)));
ASSERT_EQ(results.size(), 2U);
ASSERT_TRUE(results[0].has_value());
ASSERT_TRUE(results[1].has_value());
EXPECT_TRUE(first_started.load());
EXPECT_TRUE(second_started.load());
EXPECT_EQ(*results[0], 1);
EXPECT_EQ(*results[1], 2);
}
} // namespace
} // namespace agent_smith