imported from "final" folder

This commit is contained in:
2025-11-28 12:12:50 +01:00
parent f9288986cf
commit ff8e725b35
1061 changed files with 225150 additions and 96 deletions

View File

@@ -0,0 +1,342 @@
// g++ --std=c++11 test.cpp
#include "test.h"
#include "fl/ui.h"
#include "fl/variant.h"
#include "fl/optional.h"
#include "fl/str.h"
#include "fl/shared_ptr.h"
#include "fl/function.h"
using namespace fl;
// Test object that tracks construction/destruction for move semantics testing
struct TrackedObject {
static int construction_count;
static int destruction_count;
static int move_construction_count;
static int copy_construction_count;
int value;
bool moved_from;
TrackedObject(int v = 0) : value(v), moved_from(false) {
construction_count++;
}
TrackedObject(const TrackedObject& other) : value(other.value), moved_from(false) {
copy_construction_count++;
}
TrackedObject(TrackedObject&& other) noexcept : value(other.value), moved_from(false) {
other.moved_from = true;
move_construction_count++;
}
TrackedObject& operator=(const TrackedObject& other) {
if (this != &other) {
value = other.value;
moved_from = false;
}
return *this;
}
TrackedObject& operator=(TrackedObject&& other) noexcept {
if (this != &other) {
value = other.value;
moved_from = false;
other.moved_from = true;
}
return *this;
}
~TrackedObject() {
destruction_count++;
}
static void reset_counters() {
construction_count = 0;
destruction_count = 0;
move_construction_count = 0;
copy_construction_count = 0;
}
};
// Static member definitions
int TrackedObject::construction_count = 0;
int TrackedObject::destruction_count = 0;
int TrackedObject::move_construction_count = 0;
int TrackedObject::copy_construction_count = 0;
TEST_CASE("Variant move semantics and RAII") {
// Test the core issue: moved-from variants should be empty and not destroy moved-from objects
TrackedObject::reset_counters();
// Test 1: Verify moved-from variant is empty
{
Variant<int, TrackedObject> source(TrackedObject(42));
CHECK(source.is<TrackedObject>());
// Move construct - this is where the bug was
Variant<int, TrackedObject> destination(fl::move(source));
// Critical test: source should be empty after move
CHECK(source.empty());
CHECK(!source.is<TrackedObject>());
CHECK(!source.is<int>());
// destination should have the object
CHECK(destination.is<TrackedObject>());
CHECK_EQ(destination.ptr<TrackedObject>()->value, 42);
}
TrackedObject::reset_counters();
// Test 2: Verify moved-from variant via assignment is empty
{
Variant<int, TrackedObject> source(TrackedObject(100));
Variant<int, TrackedObject> destination;
CHECK(source.is<TrackedObject>());
CHECK(destination.empty());
// Move assign - this is where the bug was
destination = fl::move(source);
// Critical test: source should be empty after move
CHECK(source.empty());
CHECK(!source.is<TrackedObject>());
CHECK(!source.is<int>());
// destination should have the object
CHECK(destination.is<TrackedObject>());
CHECK_EQ(destination.ptr<TrackedObject>()->value, 100);
}
TrackedObject::reset_counters();
// Test 3: Simulate the original fetch callback scenario
// The key issue was that function objects containing shared_ptr were being destroyed
// after being moved, causing use-after-free in the shared_ptr reference counting
{
using MockCallback = fl::function<void()>;
auto shared_resource = fl::make_shared<TrackedObject>(999);
// Create callback that captures shared_ptr (like fetch callbacks did)
MockCallback callback = [shared_resource]() {
// Use the resource
FL_WARN("Using resource with value: " << shared_resource->value);
};
// Store in variant (like WasmFetchCallbackManager did)
Variant<int, MockCallback> callback_variant(fl::move(callback));
CHECK(callback_variant.is<MockCallback>());
// Extract via move (like takeCallback did) - this was causing heap-use-after-free
Variant<int, MockCallback> extracted_callback(fl::move(callback_variant));
// Original variant should be empty - this is the key fix
CHECK(callback_variant.empty());
CHECK(!callback_variant.is<MockCallback>());
// Extracted callback should work and shared_ptr should be valid
CHECK(extracted_callback.is<MockCallback>());
CHECK_EQ(shared_resource.use_count(), 2); // One in extracted callback, one local
// Call the extracted callback - should not crash
if (auto* cb = extracted_callback.ptr<MockCallback>()) {
(*cb)();
}
// Shared resource should still be valid
CHECK_EQ(shared_resource.use_count(), 2);
}
}
TEST_CASE("HashMap iterator-based erase") {
fl::hash_map<int, fl::string> map;
// Fill the map with some data
map[1] = "one";
map[2] = "two";
map[3] = "three";
map[4] = "four";
map[5] = "five";
CHECK_EQ(map.size(), 5);
// Test iterator-based erase
auto it = map.find(3);
CHECK(it != map.end());
CHECK_EQ(it->second, "three");
// Erase using iterator - should return iterator to next element
auto next_it = map.erase(it);
CHECK_EQ(map.size(), 4);
CHECK(map.find(3) == map.end()); // Element should be gone
// Verify all other elements are still there
CHECK(map.find(1) != map.end());
CHECK(map.find(2) != map.end());
CHECK(map.find(4) != map.end());
CHECK(map.find(5) != map.end());
// Test erasing at end
auto end_it = map.find(999); // Non-existent key
CHECK(end_it == map.end());
auto result_it = map.erase(end_it); // Should handle gracefully
CHECK(result_it == map.end());
CHECK_EQ(map.size(), 4); // Size should be unchanged
// Test erasing all remaining elements using iterators
while (!map.empty()) {
auto first = map.begin();
map.erase(first);
}
CHECK_EQ(map.size(), 0);
CHECK(map.empty());
}
// Test the original test cases
TEST_CASE("Variant tests") {
// 1) Default is empty
Variant<int, fl::string> v;
REQUIRE(v.empty());
REQUIRE(!v.is<int>());
REQUIRE(!v.is<fl::string>());
// 2) Emplace a T
v = 123;
REQUIRE(v.is<int>());
REQUIRE(!v.is<fl::string>());
REQUIRE_EQ(*v.ptr<int>(), 123);
// 3) Reset back to empty
v.reset();
REQUIRE(v.empty());
// 4) Emplace a U
v = fl::string("hello");
REQUIRE(v.is<fl::string>());
REQUIRE(!v.is<int>());
REQUIRE(v.equals(fl::string("hello")));
// 5) Copyconstruct preserves the U
Variant<int, fl::string> v2(v);
REQUIRE(v2.is<fl::string>());
fl::string* str_ptr = v2.ptr<fl::string>();
REQUIRE_NE(str_ptr, nullptr);
REQUIRE_EQ(*str_ptr, fl::string("hello"));
const bool is_str = v2.is<fl::string>();
const bool is_int = v2.is<int>();
CHECK(is_str);
CHECK(!is_int);
#if 0
// 6) Moveconstruct leaves source empty
Variant<int, fl::string> v3(std::move(v2));
REQUIRE(v3.is<fl::string>());
REQUIRE_EQ(v3.getU(), fl::string("hello"));
REQUIRE(v2.isEmpty());
// 7) Copyassign
Variant<int, fl::string> v4;
v4 = v3;
REQUIRE(v4.is<fl::string>());
REQUIRE_EQ(v4.getU(), fl::string("hello"));
// 8) Swap two variants
v4.emplaceT(7);
v3.swap(v4);
REQUIRE(v3.is<int>());
REQUIRE_EQ(v3.getT(), 7);
REQUIRE(v4.is<fl::string>());
REQUIRE_EQ(v4.getU(), fl::string("hello"));
#endif
}
TEST_CASE("Variant") {
// 1) Default is empty
Variant<int, fl::string, double> v;
REQUIRE(v.empty());
REQUIRE(!v.is<int>());
REQUIRE(!v.is<fl::string>());
REQUIRE(!v.is<double>());
// 2) Construct with a value
Variant<int, fl::string, double> v1(123);
REQUIRE(v1.is<int>());
REQUIRE(!v1.is<fl::string>());
REQUIRE(!v1.is<double>());
REQUIRE_EQ(*v1.ptr<int>(), 123);
// 3) Construct with a different type
Variant<int, fl::string, double> v2(fl::string("hello"));
REQUIRE(!v2.is<int>());
REQUIRE(v2.is<fl::string>());
REQUIRE(!v2.is<double>());
REQUIRE_EQ(*v2.ptr<fl::string>(), fl::string("hello"));
// 4) Copy construction
Variant<int, fl::string, double> v3(v2);
REQUIRE(v3.is<fl::string>());
REQUIRE(v3.equals(fl::string("hello")));
// 5) Assignment
v = v1;
REQUIRE(v.is<int>());
REQUIRE_EQ(*v.ptr<int>(), 123);
// 6) Reset
v.reset();
REQUIRE(v.empty());
// 7) Assignment of a value
v = 3.14;
REQUIRE(v.is<double>());
// REQUIRE_EQ(v.get<double>(), 3.14);
REQUIRE_EQ(*v.ptr<double>(), 3.14);
// 8) Visitor pattern
struct TestVisitor {
int result = 0;
void accept(int value) { result = value; }
void accept(const fl::string& value) { result = value.length(); }
void accept(double value) { result = static_cast<int>(value); }
};
TestVisitor visitor;
v.visit(visitor);
REQUIRE_EQ(visitor.result, 3); // 3.14 truncated to 3
v = fl::string("hello world");
v.visit(visitor);
REQUIRE_EQ(visitor.result, 11); // length of "hello world"
v = 42;
v.visit(visitor);
REQUIRE_EQ(visitor.result, 42);
}
// TEST_CASE("Optional") {
// Optional<int> opt;
// REQUIRE(opt.empty());
// opt = 42;
// REQUIRE(!opt.empty());
// REQUIRE_EQ(*opt.ptr(), 42);
// Optional<int> opt2 = opt;
// REQUIRE(!opt2.empty());
// REQUIRE_EQ(*opt2.ptr(), 42);
// opt2 = 100;
// REQUIRE_EQ(*opt2.ptr(), 100);
// }