This commit is contained in:
2026-08-15 23:24:23 +08:00
parent 023250c0fc
commit 7a94264c13
32 changed files with 1450 additions and 290 deletions
@@ -193,3 +193,132 @@ TEST(render_graph_runtime_test,
EXPECT_TRUE(source.complete());
execution.join();
}
TEST(external_operation_test, cancellation_is_terminal_and_delivered_once) {
External_Operation_Source source;
const auto operation = source.operation();
EXPECT_TRUE(operation.cancel());
EXPECT_FALSE(source.complete());
EXPECT_EQ(operation.status(), External_Operation_Status::cancelled);
int completion_count{};
std::exception_ptr completion_error;
operation.on_complete([&](std::exception_ptr error) {
completion_error = std::move(error);
++completion_count;
});
EXPECT_EQ(completion_count, 1);
ASSERT_TRUE(completion_error);
EXPECT_THROW(std::rethrow_exception(completion_error),
External_Operation_Cancelled);
}
TEST(external_operation_test, deadline_cancels_pending_operation) {
External_Operation_Source source;
const auto operation = source.operation();
std::mutex mutex;
std::condition_variable condition;
bool completed{};
std::exception_ptr completion_error;
operation.on_complete([&](std::exception_ptr error) {
{
std::lock_guard lock(mutex);
completion_error = std::move(error);
completed = true;
}
condition.notify_one();
});
source.set_deadline(External_Operation_Source::Clock::now());
{
std::unique_lock lock(mutex);
ASSERT_TRUE(condition.wait_for(lock, std::chrono::seconds(1),
[&] { return completed; }));
}
ASSERT_TRUE(completion_error);
EXPECT_THROW(std::rethrow_exception(completion_error),
External_Operation_Deadline_Exceeded);
EXPECT_EQ(operation.status(), External_Operation_Status::cancelled);
}
TEST(render_graph_runtime_test,
cancellation_releases_external_wait_and_blocks_successor) {
const auto plan = two_node_plan();
External_Operation_Source source;
std::vector<Node_Execution> execution_storage;
auto slots = execution_slots(*plan, execution_storage);
std::atomic<bool> submit_started{};
std::atomic<bool> publish_executed{};
std::exception_ptr graph_error;
renderive::render_graph::detail::Render_Graph_Runtime runtime(*plan);
const auto execute_node = [&](std::size_t index, Node_Execution_Metrics*) {
if (index == 0) {
submit_started.store(true, std::memory_order_release);
submit_started.notify_all();
return Node_Execution_Result::external(source.operation());
}
publish_executed.store(true, std::memory_order_release);
return Node_Execution_Result::completed();
};
std::thread execution([&] {
try {
runtime.execute(slots, execute_node);
} catch (...) {
graph_error = std::current_exception();
}
});
submit_started.wait(false, std::memory_order_acquire);
runtime.cancel_pending();
execution.join();
ASSERT_TRUE(graph_error);
EXPECT_THROW(std::rethrow_exception(graph_error),
External_Operation_Cancelled);
EXPECT_FALSE(publish_executed.load(std::memory_order_acquire));
EXPECT_EQ(execution_storage[0].status, Node_Execution_Status::cancelled);
EXPECT_EQ(source.operation().status(), External_Operation_Status::cancelled);
}
TEST(external_operation_test, completion_commit_is_exclusive_with_cancellation) {
External_Operation_Source source;
const auto operation = source.operation();
std::atomic<int> published{};
EXPECT_TRUE(operation.cancel());
EXPECT_FALSE(source.complete([&] {
published.fetch_add(1, std::memory_order_relaxed);
}));
EXPECT_EQ(published.load(std::memory_order_relaxed), 0);
}
TEST(external_operation_test, completion_commit_precedes_completion_notification) {
External_Operation_Source source;
const auto operation = source.operation();
std::atomic<int> published{};
int observed{};
operation.on_complete([&](std::exception_ptr error) {
EXPECT_FALSE(error);
observed = published.load(std::memory_order_acquire);
});
EXPECT_TRUE(source.complete([&] {
published.store(1, std::memory_order_release);
EXPECT_EQ(operation.status(), External_Operation_Status::pending);
}));
EXPECT_EQ(observed, 1);
EXPECT_EQ(operation.status(), External_Operation_Status::completed);
}
TEST(external_operation_test, completion_commit_failure_fails_operation) {
External_Operation_Source source;
const auto operation = source.operation();
std::exception_ptr completion_error;
operation.on_complete([&](std::exception_ptr error) {
completion_error = std::move(error);
});
EXPECT_TRUE(source.complete([] { throw std::runtime_error("publish failed"); }));
ASSERT_TRUE(completion_error);
EXPECT_THROW(std::rethrow_exception(completion_error), std::runtime_error);
EXPECT_EQ(operation.status(), External_Operation_Status::failed);
}