744 lines
22 KiB
C++
744 lines
22 KiB
C++
#include "test.h"
|
|
#include "fl/promise.h"
|
|
#include "fl/promise_result.h"
|
|
#include "fl/unused.h"
|
|
|
|
using namespace fl;
|
|
|
|
TEST_CASE("fl::promise - Basic Operations") {
|
|
SUBCASE("Default constructor creates invalid promise") {
|
|
fl::promise<int> p;
|
|
CHECK(!p.valid());
|
|
CHECK(!p.is_completed());
|
|
CHECK(!p.is_resolved());
|
|
CHECK(!p.is_rejected());
|
|
}
|
|
|
|
SUBCASE("Static create() creates valid, pending promise") {
|
|
auto p = fl::promise<int>::create();
|
|
CHECK(p.valid());
|
|
CHECK(!p.is_completed());
|
|
CHECK(!p.is_resolved());
|
|
CHECK(!p.is_rejected());
|
|
}
|
|
|
|
SUBCASE("Clear makes promise invalid") {
|
|
auto p = fl::promise<int>::create();
|
|
CHECK(p.valid());
|
|
|
|
p.clear();
|
|
CHECK(!p.valid());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Static Factory Methods") {
|
|
SUBCASE("resolve() creates resolved promise") {
|
|
auto p = fl::promise<int>::resolve(42);
|
|
CHECK(p.valid());
|
|
CHECK(p.is_completed());
|
|
CHECK(p.is_resolved());
|
|
CHECK(!p.is_rejected());
|
|
CHECK_EQ(p.value(), 42);
|
|
}
|
|
|
|
SUBCASE("resolve() with move semantics") {
|
|
fl::string test_str = "test string";
|
|
auto p = fl::promise<fl::string>::resolve(fl::move(test_str));
|
|
CHECK(p.valid());
|
|
CHECK(p.is_completed());
|
|
CHECK(p.is_resolved());
|
|
CHECK_EQ(p.value(), "test string");
|
|
}
|
|
|
|
SUBCASE("reject() creates rejected promise") {
|
|
auto p = fl::promise<int>::reject(Error("Test error"));
|
|
CHECK(p.valid());
|
|
CHECK(p.is_completed());
|
|
CHECK(!p.is_resolved());
|
|
CHECK(p.is_rejected());
|
|
CHECK_EQ(p.error().message, "Test error");
|
|
}
|
|
|
|
SUBCASE("reject() with Error object") {
|
|
Error err("Custom error");
|
|
auto p = fl::promise<int>::reject(err);
|
|
CHECK(p.valid());
|
|
CHECK(p.is_completed());
|
|
CHECK(!p.is_resolved());
|
|
CHECK(p.is_rejected());
|
|
CHECK_EQ(p.error().message, "Custom error");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Producer Interface") {
|
|
SUBCASE("complete_with_value() resolves promise") {
|
|
auto p = fl::promise<int>::create();
|
|
CHECK(!p.is_completed());
|
|
|
|
bool success = p.complete_with_value(123);
|
|
CHECK(success);
|
|
CHECK(p.is_completed());
|
|
CHECK(p.is_resolved());
|
|
CHECK(!p.is_rejected());
|
|
CHECK_EQ(p.value(), 123);
|
|
}
|
|
|
|
SUBCASE("complete_with_value() with move semantics") {
|
|
auto p = fl::promise<fl::string>::create();
|
|
fl::string test_str = "moved string";
|
|
|
|
bool success = p.complete_with_value(fl::move(test_str));
|
|
CHECK(success);
|
|
CHECK(p.is_completed());
|
|
CHECK(p.is_resolved());
|
|
CHECK_EQ(p.value(), "moved string");
|
|
}
|
|
|
|
SUBCASE("complete_with_error() rejects promise") {
|
|
auto p = fl::promise<int>::create();
|
|
CHECK(!p.is_completed());
|
|
|
|
bool success = p.complete_with_error(Error("Test error"));
|
|
CHECK(success);
|
|
CHECK(p.is_completed());
|
|
CHECK(!p.is_resolved());
|
|
CHECK(p.is_rejected());
|
|
CHECK_EQ(p.error().message, "Test error");
|
|
}
|
|
|
|
SUBCASE("complete_with_error() with Error object") {
|
|
auto p = fl::promise<int>::create();
|
|
Error err("Custom error");
|
|
|
|
bool success = p.complete_with_error(err);
|
|
CHECK(success);
|
|
CHECK(p.is_completed());
|
|
CHECK(p.is_rejected());
|
|
CHECK_EQ(p.error().message, "Custom error");
|
|
}
|
|
|
|
SUBCASE("Cannot complete promise twice") {
|
|
auto p = fl::promise<int>::create();
|
|
|
|
// First completion should succeed
|
|
bool first = p.complete_with_value(42);
|
|
CHECK(first);
|
|
CHECK(p.is_resolved());
|
|
CHECK_EQ(p.value(), 42);
|
|
|
|
// Second completion should fail
|
|
bool second = p.complete_with_value(99);
|
|
CHECK(!second);
|
|
CHECK_EQ(p.value(), 42); // Value unchanged
|
|
|
|
// Trying to complete with error should also fail
|
|
bool third = p.complete_with_error(Error("Should not work"));
|
|
CHECK(!third);
|
|
CHECK(p.is_resolved()); // Still resolved, not rejected
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Callback Interface") {
|
|
SUBCASE("then() callback called immediately on resolved promise") {
|
|
bool callback_called = false;
|
|
int received_value = 0;
|
|
|
|
auto p = fl::promise<int>::resolve(42);
|
|
p.then([&](const int& value) {
|
|
callback_called = true;
|
|
received_value = value;
|
|
});
|
|
|
|
CHECK(callback_called);
|
|
CHECK_EQ(received_value, 42);
|
|
}
|
|
|
|
SUBCASE("then() callback called after resolution") {
|
|
bool callback_called = false;
|
|
int received_value = 0;
|
|
|
|
auto p = fl::promise<int>::create();
|
|
p.then([&](const int& value) {
|
|
callback_called = true;
|
|
received_value = value;
|
|
});
|
|
|
|
CHECK(!callback_called); // Should not be called yet
|
|
|
|
p.complete_with_value(123);
|
|
CHECK(callback_called);
|
|
CHECK_EQ(received_value, 123);
|
|
}
|
|
|
|
SUBCASE("catch_() callback called immediately on rejected promise") {
|
|
bool callback_called = false;
|
|
fl::string received_error;
|
|
|
|
auto p = fl::promise<int>::reject(Error("Test error"));
|
|
p.catch_([&](const Error& err) {
|
|
callback_called = true;
|
|
received_error = err.message;
|
|
});
|
|
|
|
CHECK(callback_called);
|
|
CHECK_EQ(received_error, "Test error");
|
|
}
|
|
|
|
SUBCASE("catch_() callback called after rejection") {
|
|
bool callback_called = false;
|
|
fl::string received_error;
|
|
|
|
auto p = fl::promise<int>::create();
|
|
p.catch_([&](const Error& err) {
|
|
callback_called = true;
|
|
received_error = err.message;
|
|
});
|
|
|
|
CHECK(!callback_called); // Should not be called yet
|
|
|
|
p.complete_with_error(Error("Async error"));
|
|
CHECK(callback_called);
|
|
CHECK_EQ(received_error, "Async error");
|
|
}
|
|
|
|
SUBCASE("then() returns reference for chaining") {
|
|
auto p = fl::promise<int>::create();
|
|
|
|
// Should be able to chain
|
|
auto& ref = p.then([](const int& value) {
|
|
FL_UNUSED(value);
|
|
// Success callback
|
|
}).catch_([](const Error& err) {
|
|
FL_UNUSED(err);
|
|
// Error callback
|
|
});
|
|
|
|
// Reference should be the same promise
|
|
CHECK(&ref == &p);
|
|
}
|
|
|
|
SUBCASE("catch_() returns reference for chaining") {
|
|
auto p = fl::promise<int>::create();
|
|
|
|
// Should be able to chain
|
|
auto& ref = p.catch_([](const Error& err) {
|
|
FL_UNUSED(err);
|
|
// Error callback
|
|
}).then([](const int& value) {
|
|
FL_UNUSED(value);
|
|
// Success callback
|
|
});
|
|
|
|
// Reference should be the same promise
|
|
CHECK(&ref == &p);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Update and Callback Processing") {
|
|
SUBCASE("update() processes callbacks after manual completion") {
|
|
bool then_called = false;
|
|
bool catch_called = false;
|
|
|
|
auto p = fl::promise<int>::create();
|
|
p.then([&](const int& value) { FL_UNUSED(value); then_called = true; });
|
|
p.catch_([&](const Error& err) { FL_UNUSED(err); catch_called = true; });
|
|
|
|
// Complete and then update
|
|
p.complete_with_value(42);
|
|
p.update();
|
|
|
|
CHECK(then_called);
|
|
CHECK(!catch_called);
|
|
}
|
|
|
|
SUBCASE("update() on invalid promise does nothing") {
|
|
fl::promise<int> invalid_promise;
|
|
// Should not crash
|
|
invalid_promise.update();
|
|
CHECK(!invalid_promise.valid());
|
|
}
|
|
|
|
SUBCASE("Callbacks only called once") {
|
|
int call_count = 0;
|
|
|
|
auto p = fl::promise<int>::create();
|
|
p.then([&](const int& value) { FL_UNUSED(value); call_count++; });
|
|
|
|
p.complete_with_value(42);
|
|
CHECK_EQ(call_count, 1);
|
|
|
|
// Multiple updates should not call callback again
|
|
p.update();
|
|
p.update();
|
|
CHECK_EQ(call_count, 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Copy Semantics") {
|
|
SUBCASE("Promises are copyable") {
|
|
auto p1 = fl::promise<int>::create();
|
|
auto p2 = p1; // Copy constructor
|
|
|
|
CHECK(p1.valid());
|
|
CHECK(p2.valid());
|
|
|
|
// Both should refer to the same promise
|
|
p1.complete_with_value(42);
|
|
CHECK(p1.is_resolved());
|
|
CHECK(p2.is_resolved());
|
|
CHECK_EQ(p1.value(), 42);
|
|
CHECK_EQ(p2.value(), 42);
|
|
}
|
|
|
|
SUBCASE("Copy assignment works") {
|
|
auto p1 = fl::promise<int>::create();
|
|
auto p2 = fl::promise<int>::create();
|
|
|
|
p2 = p1; // Copy assignment
|
|
|
|
CHECK(p1.valid());
|
|
CHECK(p2.valid());
|
|
|
|
// Both should refer to the same promise
|
|
p1.complete_with_value(123);
|
|
CHECK(p1.is_resolved());
|
|
CHECK(p2.is_resolved());
|
|
CHECK_EQ(p1.value(), 123);
|
|
CHECK_EQ(p2.value(), 123);
|
|
}
|
|
|
|
SUBCASE("Callbacks work on copied promises") {
|
|
bool callback1_called = false;
|
|
bool callback2_called = false;
|
|
|
|
auto p1 = fl::promise<int>::create();
|
|
auto p2 = p1; // Copy
|
|
|
|
p1.then([&](const int& value) {
|
|
FL_UNUSED(value);
|
|
callback1_called = true;
|
|
});
|
|
p2.then([&](const int& value) {
|
|
FL_UNUSED(value);
|
|
callback2_called = true;
|
|
});
|
|
|
|
p1.complete_with_value(42);
|
|
|
|
// NOTE: Current implementation only stores one callback per promise
|
|
// The second then() call overwrites the first callback
|
|
// Only the last callback set will be called
|
|
CHECK(!callback1_called); // First callback was overwritten
|
|
CHECK(callback2_called); // Second callback is called
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Move Semantics") {
|
|
SUBCASE("Promises are moveable") {
|
|
auto p1 = fl::promise<int>::create();
|
|
auto p2 = fl::move(p1); // Move constructor
|
|
|
|
CHECK(!p1.valid()); // p1 should be invalid after move
|
|
CHECK(p2.valid()); // p2 should be valid
|
|
|
|
p2.complete_with_value(42);
|
|
CHECK(p2.is_resolved());
|
|
CHECK_EQ(p2.value(), 42);
|
|
}
|
|
|
|
SUBCASE("Move assignment works") {
|
|
auto p1 = fl::promise<int>::create();
|
|
auto p2 = fl::promise<int>::create();
|
|
|
|
p2 = fl::move(p1); // Move assignment
|
|
|
|
CHECK(!p1.valid()); // p1 should be invalid after move
|
|
CHECK(p2.valid()); // p2 should be valid
|
|
|
|
p2.complete_with_value(123);
|
|
CHECK(p2.is_resolved());
|
|
CHECK_EQ(p2.value(), 123);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Convenience Functions") {
|
|
SUBCASE("make_resolved_promise() works") {
|
|
auto p = make_resolved_promise(42);
|
|
CHECK(p.valid());
|
|
CHECK(p.is_resolved());
|
|
CHECK_EQ(p.value(), 42);
|
|
}
|
|
|
|
SUBCASE("make_rejected_promise() with string works") {
|
|
auto p = make_rejected_promise<int>("Test error");
|
|
CHECK(p.valid());
|
|
CHECK(p.is_rejected());
|
|
CHECK_EQ(p.error().message, "Test error");
|
|
}
|
|
|
|
SUBCASE("make_rejected_promise() with const char* works") {
|
|
auto p = make_rejected_promise<int>("C string error");
|
|
CHECK(p.valid());
|
|
CHECK(p.is_rejected());
|
|
CHECK_EQ(p.error().message, "C string error");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Error Type") {
|
|
SUBCASE("Error default constructor") {
|
|
Error err;
|
|
CHECK(err.message.empty());
|
|
}
|
|
|
|
SUBCASE("Error with string") {
|
|
fl::string msg = "Test message";
|
|
Error err(msg);
|
|
CHECK_EQ(err.message, "Test message");
|
|
}
|
|
|
|
SUBCASE("Error with const char*") {
|
|
Error err("C string message");
|
|
CHECK_EQ(err.message, "C string message");
|
|
}
|
|
|
|
SUBCASE("Error with move string") {
|
|
fl::string msg = "Move message";
|
|
Error err(fl::move(msg));
|
|
CHECK_EQ(err.message, "Move message");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Edge Cases") {
|
|
SUBCASE("Invalid promise methods return safe defaults") {
|
|
fl::promise<int> invalid;
|
|
|
|
CHECK(!invalid.valid());
|
|
CHECK(!invalid.is_completed());
|
|
CHECK(!invalid.is_resolved());
|
|
CHECK(!invalid.is_rejected());
|
|
|
|
// Should return default-constructed values for invalid promise
|
|
CHECK_EQ(invalid.value(), int{});
|
|
CHECK_EQ(invalid.error().message, fl::string{});
|
|
|
|
// Methods should safely handle invalid state
|
|
CHECK(!invalid.complete_with_value(42));
|
|
CHECK(!invalid.complete_with_error(Error("error")));
|
|
|
|
// Chaining should return reference even for invalid promise
|
|
auto& ref = invalid.then([](const int&) {}).catch_([](const Error&) {});
|
|
CHECK(&ref == &invalid);
|
|
}
|
|
|
|
SUBCASE("Multiple callbacks on same promise") {
|
|
bool callback1_called = false;
|
|
bool callback2_called = false;
|
|
int value1 = 0, value2 = 0;
|
|
|
|
auto p = fl::promise<int>::create();
|
|
|
|
// Add multiple then callbacks
|
|
p.then([&](const int& value) {
|
|
callback1_called = true;
|
|
value1 = value;
|
|
});
|
|
|
|
p.then([&](const int& value) {
|
|
callback2_called = true;
|
|
value2 = value;
|
|
});
|
|
|
|
p.complete_with_value(42);
|
|
|
|
// Only the last callback is stored and called
|
|
// (This is a design limitation of the lightweight implementation)
|
|
CHECK(!callback1_called); // First callback was overwritten
|
|
CHECK(callback2_called); // Only the last callback is called
|
|
CHECK_EQ(value2, 42);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::promise - Complex Types") {
|
|
SUBCASE("Promise with string type") {
|
|
auto p = fl::promise<fl::string>::create();
|
|
bool callback_called = false;
|
|
fl::string received;
|
|
|
|
p.then([&](const fl::string& value) {
|
|
callback_called = true;
|
|
received = value;
|
|
});
|
|
|
|
p.complete_with_value(fl::string("test string"));
|
|
|
|
CHECK(callback_called);
|
|
CHECK_EQ(received, "test string");
|
|
}
|
|
|
|
SUBCASE("Promise with custom struct") {
|
|
struct TestData {
|
|
int x;
|
|
fl::string name;
|
|
|
|
bool operator==(const TestData& other) const {
|
|
return x == other.x && name == other.name;
|
|
}
|
|
};
|
|
|
|
auto p = fl::promise<TestData>::create();
|
|
bool callback_called = false;
|
|
TestData received{};
|
|
|
|
p.then([&](const TestData& value) {
|
|
callback_called = true;
|
|
received = value;
|
|
});
|
|
|
|
TestData test_data{42, "test"};
|
|
p.complete_with_value(test_data);
|
|
|
|
CHECK(callback_called);
|
|
CHECK(received == test_data);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::PromiseResult - Basic Construction") {
|
|
SUBCASE("construct with success value") {
|
|
PromiseResult<int> result(42);
|
|
|
|
CHECK(result.ok());
|
|
CHECK(static_cast<bool>(result));
|
|
CHECK_EQ(result.value(), 42);
|
|
CHECK_EQ(result.error_message(), "");
|
|
}
|
|
|
|
SUBCASE("construct with error") {
|
|
Error err("Test error");
|
|
PromiseResult<int> result(err);
|
|
|
|
CHECK(!result.ok());
|
|
CHECK(!static_cast<bool>(result));
|
|
CHECK_EQ(result.error().message, "Test error");
|
|
CHECK_EQ(result.error_message(), "Test error");
|
|
}
|
|
|
|
SUBCASE("construct with move semantics") {
|
|
fl::string text = "Hello World";
|
|
PromiseResult<fl::string> result(fl::move(text));
|
|
|
|
CHECK(result.ok());
|
|
CHECK_EQ(result.value(), "Hello World");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::PromiseResult - Value Access") {
|
|
SUBCASE("safe value access on success") {
|
|
PromiseResult<int> result(100);
|
|
|
|
CHECK(result.ok());
|
|
|
|
// Test const access
|
|
const PromiseResult<int>& const_result = result;
|
|
const int& const_value = const_result.value();
|
|
CHECK_EQ(const_value, 100);
|
|
|
|
// Test mutable access
|
|
int& mutable_value = result.value();
|
|
CHECK_EQ(mutable_value, 100);
|
|
|
|
// Test modification
|
|
mutable_value = 200;
|
|
CHECK_EQ(result.value(), 200);
|
|
}
|
|
|
|
SUBCASE("value access on error in release builds") {
|
|
PromiseResult<int> result(Error("Test error"));
|
|
|
|
CHECK(!result.ok());
|
|
|
|
// In release builds, this should return a static empty object
|
|
// In debug builds, this would crash (which we can't test automatically)
|
|
#ifndef DEBUG
|
|
// Only test this in release builds to avoid crashes
|
|
const int& value = result.value();
|
|
// Should return a default-constructed int (0)
|
|
CHECK_EQ(value, 0);
|
|
#endif
|
|
}
|
|
|
|
SUBCASE("string value access") {
|
|
PromiseResult<fl::string> result(fl::string("Test"));
|
|
|
|
CHECK(result.ok());
|
|
CHECK_EQ(result.value(), "Test");
|
|
|
|
// Modify string
|
|
result.value() = "Modified";
|
|
CHECK_EQ(result.value(), "Modified");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::PromiseResult - Error Access") {
|
|
SUBCASE("safe error access on error") {
|
|
Error original_error("Network timeout");
|
|
PromiseResult<int> result(original_error);
|
|
|
|
CHECK(!result.ok());
|
|
|
|
const Error& error = result.error();
|
|
CHECK_EQ(error.message, "Network timeout");
|
|
}
|
|
|
|
SUBCASE("error access on success in release builds") {
|
|
PromiseResult<int> result(42);
|
|
|
|
CHECK(result.ok());
|
|
|
|
// In release builds, this should return a static descriptive error
|
|
// In debug builds, this would crash (which we can't test automatically)
|
|
#ifndef DEBUG
|
|
const Error& error = result.error();
|
|
// Should return a descriptive error message
|
|
CHECK(error.message.find("success value") != fl::string::npos);
|
|
#endif
|
|
}
|
|
|
|
SUBCASE("error_message convenience method") {
|
|
// Test with error
|
|
PromiseResult<int> error_result(Error("Connection failed"));
|
|
CHECK_EQ(error_result.error_message(), "Connection failed");
|
|
|
|
// Test with success
|
|
PromiseResult<int> success_result(42);
|
|
CHECK_EQ(success_result.error_message(), "");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::PromiseResult - Type Conversions") {
|
|
SUBCASE("boolean conversion") {
|
|
PromiseResult<int> success(42);
|
|
PromiseResult<int> failure(Error("Error"));
|
|
|
|
// Test explicit bool conversion
|
|
CHECK(static_cast<bool>(success));
|
|
CHECK(!static_cast<bool>(failure));
|
|
|
|
// Test in if statements
|
|
if (success) {
|
|
CHECK(true); // Should reach here
|
|
} else {
|
|
CHECK(false); // Should not reach here
|
|
}
|
|
|
|
if (failure) {
|
|
CHECK(false); // Should not reach here
|
|
} else {
|
|
CHECK(true); // Should reach here
|
|
}
|
|
}
|
|
|
|
SUBCASE("variant access") {
|
|
PromiseResult<int> result(42);
|
|
|
|
const auto& variant = result.variant();
|
|
CHECK(variant.is<int>());
|
|
CHECK_EQ(variant.get<int>(), 42);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::PromiseResult - Helper Functions") {
|
|
SUBCASE("make_success") {
|
|
auto result1 = make_success(42);
|
|
CHECK(result1.ok());
|
|
CHECK_EQ(result1.value(), 42);
|
|
|
|
fl::string text = "Hello";
|
|
auto result2 = make_success(fl::move(text));
|
|
CHECK(result2.ok());
|
|
CHECK_EQ(result2.value(), "Hello");
|
|
}
|
|
|
|
SUBCASE("make_error with Error object") {
|
|
Error err("Custom error");
|
|
auto result = make_error<int>(err);
|
|
|
|
CHECK(!result.ok());
|
|
CHECK_EQ(result.error().message, "Custom error");
|
|
}
|
|
|
|
SUBCASE("make_error with string") {
|
|
auto result1 = make_error<int>(fl::string("String error"));
|
|
CHECK(!result1.ok());
|
|
CHECK_EQ(result1.error().message, "String error");
|
|
|
|
auto result2 = make_error<int>("C-string error");
|
|
CHECK(!result2.ok());
|
|
CHECK_EQ(result2.error().message, "C-string error");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::PromiseResult - Complex Types") {
|
|
SUBCASE("custom struct") {
|
|
struct TestStruct {
|
|
int x;
|
|
fl::string name;
|
|
|
|
TestStruct() : x(0), name("") {}
|
|
TestStruct(int x_, const fl::string& name_) : x(x_), name(name_) {}
|
|
|
|
bool operator==(const TestStruct& other) const {
|
|
return x == other.x && name == other.name;
|
|
}
|
|
};
|
|
|
|
TestStruct original{42, "test"};
|
|
PromiseResult<TestStruct> result(original);
|
|
|
|
CHECK(result.ok());
|
|
|
|
const TestStruct& retrieved = result.value();
|
|
CHECK(retrieved == original);
|
|
CHECK_EQ(retrieved.x, 42);
|
|
CHECK_EQ(retrieved.name, "test");
|
|
|
|
// Test modification
|
|
TestStruct& mutable_struct = result.value();
|
|
mutable_struct.x = 99;
|
|
CHECK_EQ(result.value().x, 99);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fl::PromiseResult - Copy and Move Semantics") {
|
|
SUBCASE("copy construction") {
|
|
PromiseResult<int> original(42);
|
|
PromiseResult<int> copy(original);
|
|
|
|
CHECK(copy.ok());
|
|
CHECK_EQ(copy.value(), 42);
|
|
|
|
// Modify copy, original should be unchanged
|
|
copy.value() = 100;
|
|
CHECK_EQ(original.value(), 42);
|
|
CHECK_EQ(copy.value(), 100);
|
|
}
|
|
|
|
SUBCASE("copy assignment") {
|
|
PromiseResult<int> original(42);
|
|
PromiseResult<int> copy = make_error<int>("temp");
|
|
|
|
copy = original;
|
|
|
|
CHECK(copy.ok());
|
|
CHECK_EQ(copy.value(), 42);
|
|
}
|
|
|
|
SUBCASE("move construction") {
|
|
fl::string text = "Move me";
|
|
PromiseResult<fl::string> original(fl::move(text));
|
|
PromiseResult<fl::string> moved(fl::move(original));
|
|
|
|
CHECK(moved.ok());
|
|
CHECK_EQ(moved.value(), "Move me");
|
|
}
|
|
}
|