#include "text_crdt.hpp"
#include <iostream>
#include <cassert>
#include <string>
#define TEST_ASSERT(condition, message) \
do { \
if (!(condition)) { \
std::cerr << "FAILED: " << message << "\n"; \
std::cerr << " at " << __FILE__ << ":" << __LINE__ << "\n"; \
return false; \
} \
} while (0)
#define RUN_TEST(test_func) \
do { \
std::cout << "Running " << #test_func << "... "; \
if (test_func()) { \
std::cout << "PASSED\n"; \
passed++; \
} else { \
std::cout << "FAILED\n"; \
failed++; \
} \
total++; \
} while (0)
bool test_fractional_position_basic() {
auto pos1 = FractionalPosition::first();
TEST_ASSERT(pos1.is_valid(), "First position should be valid");
TEST_ASSERT(pos1.path.size() == 1, "First position should have depth 1");
auto pos2 = FractionalPosition::after(pos1);
TEST_ASSERT(pos2 > pos1, "After position should be greater");
auto pos0 = FractionalPosition::before(pos1);
TEST_ASSERT(pos0 < pos1, "Before position should be smaller");
return true;
}
bool test_fractional_position_between_with_room() {
FractionalPosition a({1000});
FractionalPosition b({3000});
auto mid = FractionalPosition::between(a, b);
TEST_ASSERT(mid > a, "Mid should be greater than a");
TEST_ASSERT(mid < b, "Mid should be less than b");
TEST_ASSERT(mid.path.size() == 1, "Should not increase depth when room available");
TEST_ASSERT(mid.path[0] == 2000, "Should be exactly in middle");
return true;
}
bool test_fractional_position_between_adjacent() {
FractionalPosition a({1000});
FractionalPosition b({1001});
auto mid = FractionalPosition::between(a, b);
TEST_ASSERT(mid > a, "Mid should be greater than a");
TEST_ASSERT(mid < b, "Mid should be less than b");
TEST_ASSERT(mid.path.size() > 1, "Should increase depth when no room");
return true;
}
bool test_fractional_position_prefix() {
FractionalPosition a({1000});
FractionalPosition b({1000, 5000});
auto mid = FractionalPosition::between(a, b);
TEST_ASSERT(mid > a, "Mid should be greater than a");
TEST_ASSERT(mid < b, "Mid should be less than b");
return true;
}
bool test_fractional_position_repeated_bisection() {
FractionalPosition a({1000});
FractionalPosition b({2000});
std::vector<FractionalPosition> positions;
FractionalPosition current = a;
for (int i = 0; i < 10; i++) {
auto mid = FractionalPosition::between(current, b);
TEST_ASSERT(mid > current, "Should maintain ordering");
TEST_ASSERT(mid < b, "Should maintain ordering");
positions.push_back(mid);
current = mid; }
for (size_t i = 1; i < positions.size(); i++) {
TEST_ASSERT(positions[i] > positions[i-1], "Positions should be monotonically increasing");
}
return true;
}
bool test_text_crdt_insert_at_start() {
TextCRDT<std::string> doc(1);
auto id1 = doc.insert_line_at_start("First line");
auto id2 = doc.insert_line_at_start("New first line");
auto lines = doc.get_all_lines();
TEST_ASSERT(lines.size() == 2, "Should have 2 lines");
TEST_ASSERT(lines[0].content == "New first line", "First line should be newest insert");
TEST_ASSERT(lines[1].content == "First line", "Second line should be original");
return true;
}
bool test_text_crdt_insert_at_end() {
TextCRDT<std::string> doc(1);
auto id1 = doc.insert_line_at_end("First line");
auto id2 = doc.insert_line_at_end("Second line");
auto id3 = doc.insert_line_at_end("Third line");
auto lines = doc.get_all_lines();
TEST_ASSERT(lines.size() == 3, "Should have 3 lines");
TEST_ASSERT(lines[0].content == "First line", "Order should be maintained");
TEST_ASSERT(lines[1].content == "Second line", "Order should be maintained");
TEST_ASSERT(lines[2].content == "Third line", "Order should be maintained");
return true;
}
bool test_text_crdt_insert_after() {
TextCRDT<std::string> doc(1);
auto id1 = doc.insert_line_at_end("Line 1");
auto id2 = doc.insert_line_at_end("Line 3");
auto id_mid = doc.insert_line_after(id1, "Line 2");
auto lines = doc.get_all_lines();
TEST_ASSERT(lines.size() == 3, "Should have 3 lines");
TEST_ASSERT(lines[0].content == "Line 1", "Order should be correct");
TEST_ASSERT(lines[1].content == "Line 2", "Inserted line should be in middle");
TEST_ASSERT(lines[2].content == "Line 3", "Order should be correct");
return true;
}
bool test_text_crdt_insert_before() {
TextCRDT<std::string> doc(1);
auto id1 = doc.insert_line_at_end("Line 2");
auto id2 = doc.insert_line_at_end("Line 3");
auto id_first = doc.insert_line_before(id1, "Line 1");
auto lines = doc.get_all_lines();
TEST_ASSERT(lines.size() == 3, "Should have 3 lines");
TEST_ASSERT(lines[0].content == "Line 1", "Inserted line should be first");
TEST_ASSERT(lines[1].content == "Line 2", "Order should be correct");
TEST_ASSERT(lines[2].content == "Line 3", "Order should be correct");
return true;
}
bool test_text_crdt_edit() {
TextCRDT<std::string> doc(1);
auto id = doc.insert_line_at_end("Original content");
bool success = doc.edit_line(id, "Modified content");
TEST_ASSERT(success, "Edit should succeed");
auto line = doc.get_line(id);
TEST_ASSERT(line.has_value(), "Line should exist");
TEST_ASSERT(line->content == "Modified content", "Content should be updated");
return true;
}
bool test_text_crdt_delete() {
TextCRDT<std::string> doc(1);
auto id1 = doc.insert_line_at_end("Line 1");
auto id2 = doc.insert_line_at_end("Line 2");
auto id3 = doc.insert_line_at_end("Line 3");
bool success = doc.delete_line(id2);
TEST_ASSERT(success, "Delete should succeed");
TEST_ASSERT(doc.is_tombstoned(id2), "Line should be tombstoned");
auto lines = doc.get_all_lines();
TEST_ASSERT(lines.size() == 2, "Should have 2 lines after deletion");
TEST_ASSERT(lines[0].content == "Line 1", "Remaining lines should be correct");
TEST_ASSERT(lines[1].content == "Line 3", "Remaining lines should be correct");
return true;
}
bool test_text_crdt_many_insertions() {
TextCRDT<std::string> doc(1);
std::vector<std::string> ids;
for (int i = 0; i < 100; i++) {
auto id = doc.insert_line_at_end("Line " + std::to_string(i));
ids.push_back(id);
}
auto lines = doc.get_all_lines();
TEST_ASSERT(lines.size() == 100, "Should have 100 lines");
for (size_t i = 0; i < lines.size(); i++) {
std::string expected = "Line " + std::to_string(i);
TEST_ASSERT(lines[i].content == expected, "Lines should be in insertion order");
}
return true;
}
bool test_text_crdt_interleaved_insertions() {
TextCRDT<std::string> doc(1);
auto id1 = doc.insert_line_at_end("A");
auto id2 = doc.insert_line_at_end("C");
auto idB = doc.insert_line_after(id1, "B");
auto idA5 = doc.insert_line_after(id1, "A.5");
auto idB5 = doc.insert_line_after(idB, "B.5");
auto lines = doc.get_all_lines();
TEST_ASSERT(lines.size() == 5, "Should have 5 lines");
TEST_ASSERT(lines[0].content == "A", "Order should be correct");
TEST_ASSERT(lines[1].content == "A.5", "Order should be correct");
TEST_ASSERT(lines[2].content == "B", "Order should be correct");
TEST_ASSERT(lines[3].content == "B.5", "Order should be correct");
TEST_ASSERT(lines[4].content == "C", "Order should be correct");
return true;
}
bool test_text_crdt_index_consistency() {
TextCRDT<std::string> doc(1);
std::vector<std::string> ids;
for (int i = 0; i < 50; i++) {
ids.push_back(doc.insert_line_at_end("Line " + std::to_string(i)));
}
for (size_t i = 0; i < ids.size(); i += 2) {
doc.delete_line(ids[i]);
}
auto lines = doc.get_all_lines();
TEST_ASSERT(lines.size() == 25, "Should have 25 lines remaining");
for (size_t i = 0; i < lines.size(); i++) {
std::string expected = "Line " + std::to_string(i * 2 + 1);
TEST_ASSERT(lines[i].content == expected, "Remaining lines should be correct");
}
return true;
}
bool test_text_crdt_basic_sync() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
auto id1 = node1.insert_line_at_end("Line 1");
auto id2 = node1.insert_line_at_end("Line 2");
uint64_t last_sync = 0;
auto changes = node1.get_changes_since(last_sync);
node2.merge_changes(changes);
auto lines2 = node2.get_all_lines();
TEST_ASSERT(lines2.size() == 2, "Node2 should have 2 lines after sync");
TEST_ASSERT(lines2[0].content == "Line 1", "Content should match");
TEST_ASSERT(lines2[1].content == "Line 2", "Content should match");
return true;
}
bool test_text_crdt_concurrent_insertions() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
auto id1 = node1.insert_line_at_start("From Node 1");
auto id2 = node2.insert_line_at_start("From Node 2");
uint64_t sync1 = 0, sync2 = 0;
auto changes1 = node1.get_changes_since(sync1);
auto changes2 = node2.get_changes_since(sync2);
node2.merge_changes(changes1);
node1.merge_changes(changes2);
auto lines1 = node1.get_all_lines();
auto lines2 = node2.get_all_lines();
TEST_ASSERT(lines1.size() == lines2.size(), "Node line counts should match");
TEST_ASSERT(lines1.size() == 2, "Should have 2 lines total");
bool has_node1_content = false, has_node2_content = false;
for (const auto &line : lines1) {
if (line.content == "From Node 1") has_node1_content = true;
if (line.content == "From Node 2") has_node2_content = true;
}
TEST_ASSERT(has_node1_content && has_node2_content, "Both contents should exist");
return true;
}
bool test_text_crdt_concurrent_edits_lww() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
auto id = node1.insert_line_at_end("Original");
uint64_t sync_version = 0;
auto changes = node1.get_changes_since(sync_version);
node2.merge_changes(changes);
sync_version = node1.current_version();
node1.edit_line(id, "Edit from Node 1");
node2.edit_line(id, "Edit from Node 2");
auto changes1 = node1.get_changes_since(sync_version);
auto changes2 = node2.get_changes_since(sync_version);
node2.merge_changes(changes1);
node1.merge_changes(changes2);
auto line1 = node1.get_line(id);
auto line2 = node2.get_line(id);
TEST_ASSERT(line1.has_value() && line2.has_value(), "Line should exist on both nodes");
TEST_ASSERT(line1->content == line2->content, "Nodes should converge to same content");
TEST_ASSERT(line1->content == "Edit from Node 2", "Node 2's edit should win with LWW");
return true;
}
bool test_text_crdt_concurrent_edits_bww() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
auto id = node1.insert_line_at_end("Original");
uint64_t sync_version = 0;
auto changes = node1.get_changes_since(sync_version);
node2.merge_changes(changes);
sync_version = node1.current_version();
node1.edit_line(id, "Edit from Node 1");
node2.edit_line(id, "Edit from Node 2");
auto changes1 = node1.get_changes_since(sync_version);
auto changes2 = node2.get_changes_since(sync_version);
BothWritesWinMergeRule<std::string, std::string> bww_rule;
node2.merge_changes_with_rule(changes1, bww_rule);
node1.merge_changes_with_rule(changes2, bww_rule);
auto line1 = node1.get_line(id);
auto line2 = node2.get_line(id);
TEST_ASSERT(line1.has_value() && line2.has_value(), "Line should exist");
TEST_ASSERT(line1->has_conflicts(), "Node 1 should have conflicts");
TEST_ASSERT(line2->has_conflicts(), "Node 2 should have conflicts");
auto conflicts1 = line1->get_all_versions();
auto conflicts2 = line2->get_all_versions();
TEST_ASSERT(conflicts1.size() == 2, "Should have 2 conflicting versions");
TEST_ASSERT(conflicts2.size() == 2, "Should have 2 conflicting versions");
return true;
}
bool test_text_crdt_delete_propagation() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
auto id1 = node1.insert_line_at_end("Line 1");
auto id2 = node1.insert_line_at_end("Line 2");
auto id3 = node1.insert_line_at_end("Line 3");
uint64_t sync_version = 0;
auto changes = node1.get_changes_since(sync_version);
node2.merge_changes(changes);
sync_version = node1.current_version();
node1.delete_line(id2);
changes = node1.get_changes_since(sync_version);
node2.merge_changes(changes);
auto lines2 = node2.get_all_lines();
TEST_ASSERT(lines2.size() == 2, "Node2 should have 2 lines after deletion");
TEST_ASSERT(lines2[0].content == "Line 1", "First line should remain");
TEST_ASSERT(lines2[1].content == "Line 3", "Third line should remain");
TEST_ASSERT(node2.is_tombstoned(id2), "Deleted line should be tombstoned");
return true;
}
bool test_text_crdt_three_way_sync() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
TextCRDT<std::string> node3(3);
uint64_t sync12 = 0, sync13 = 0, sync23 = 0;
uint64_t sync21 = 0, sync31 = 0, sync32 = 0;
auto id1 = node1.insert_line_at_end("From Node 1");
auto id2 = node2.insert_line_at_end("From Node 2");
auto id3 = node3.insert_line_at_end("From Node 3");
sync_text_crdts(node1, node2, sync12);
sync_text_crdts(node1, node3, sync13);
sync_text_crdts(node2, node1, sync21);
sync_text_crdts(node2, node3, sync23);
sync_text_crdts(node3, node1, sync31);
sync_text_crdts(node3, node2, sync32);
sync_text_crdts(node1, node2, sync12);
sync_text_crdts(node2, node1, sync21);
sync_text_crdts(node1, node3, sync13);
sync_text_crdts(node3, node1, sync31);
sync_text_crdts(node2, node3, sync23);
sync_text_crdts(node3, node2, sync32);
auto lines1 = node1.get_all_lines();
auto lines2 = node2.get_all_lines();
auto lines3 = node3.get_all_lines();
if (lines1.size() != 3 || lines2.size() != 3 || lines3.size() != 3) {
std::cout << "\nDEBUG: Node1 has " << lines1.size() << " lines:\n";
for (const auto& line : lines1) {
std::cout << " - " << line.content << "\n";
}
std::cout << "DEBUG: Node2 has " << lines2.size() << " lines:\n";
for (const auto& line : lines2) {
std::cout << " - " << line.content << "\n";
}
std::cout << "DEBUG: Node3 has " << lines3.size() << " lines:\n";
for (const auto& line : lines3) {
std::cout << " - " << line.content << "\n";
}
std::cout << "\nDEBUG: Changes from node3 since sync31=" << sync31 << ": " << node3.get_changes_since(sync31).size() << "\n";
std::cout << "DEBUG: Changes from node3 since sync32=" << sync32 << ": " << node3.get_changes_since(sync32).size() << "\n";
}
TEST_ASSERT(lines1.size() == 3, "Node1 should have 3 lines");
TEST_ASSERT(lines2.size() == 3, "Node2 should have 3 lines");
TEST_ASSERT(lines3.size() == 3, "Node3 should have 3 lines");
return true;
}
bool test_text_crdt_delete_vs_edit_conflict() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
auto id = node1.insert_line_at_end("Original content");
uint64_t sync_version = 0;
auto changes = node1.get_changes_since(sync_version);
node2.merge_changes(changes);
sync_version = node1.current_version();
node1.delete_line(id);
node2.edit_line(id, "Edited content");
auto changes1 = node1.get_changes_since(sync_version);
auto changes2 = node2.get_changes_since(sync_version);
node2.merge_changes(changes1);
node1.merge_changes(changes2);
TEST_ASSERT(node1.is_tombstoned(id), "Node1 should have line tombstoned");
TEST_ASSERT(node2.is_tombstoned(id), "Node2 should have line tombstoned");
auto lines1 = node1.get_all_lines();
auto lines2 = node2.get_all_lines();
TEST_ASSERT(lines1.size() == 0, "Both nodes should have no lines after delete wins");
TEST_ASSERT(lines2.size() == 0, "Both nodes should have no lines after delete wins");
return true;
}
bool test_text_crdt_multi_node_concurrent_edits() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
TextCRDT<std::string> node3(3);
TextCRDT<std::string> node4(4);
auto id = node1.insert_line_at_end("Base content");
uint64_t sync12 = 0, sync13 = 0, sync14 = 0;
node2.merge_changes(node1.get_changes_since(sync12));
node3.merge_changes(node1.get_changes_since(sync13));
node4.merge_changes(node1.get_changes_since(sync14));
sync12 = sync13 = sync14 = node1.current_version();
node1.edit_line(id, "Edit from Node 1");
node2.edit_line(id, "Edit from Node 2");
node3.edit_line(id, "Edit from Node 3");
node4.edit_line(id, "Edit from Node 4");
BothWritesWinMergeRule<std::string, std::string> bww_rule;
node1.merge_changes_with_rule(node2.get_changes_since(sync12), bww_rule);
node1.merge_changes_with_rule(node3.get_changes_since(sync13), bww_rule);
node1.merge_changes_with_rule(node4.get_changes_since(sync14), bww_rule);
auto line1 = node1.get_line(id);
TEST_ASSERT(line1.has_value(), "Line should exist");
TEST_ASSERT(line1->has_conflicts(), "Should have conflicts");
auto all_versions = line1->get_all_versions();
TEST_ASSERT(all_versions.size() == 4, "Should have all 4 concurrent versions");
return true;
}
bool test_text_crdt_auto_merge_non_overlapping() {
return true;
}
bool test_text_crdt_auto_merge_overlapping() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
auto id = node1.insert_line_at_end("The quick brown fox");
uint64_t sync_version = 0;
auto changes = node1.get_changes_since(sync_version);
node2.merge_changes(changes);
sync_version = node1.current_version();
node1.edit_line(id, "The fast brown fox");
node2.edit_line(id, "The slow brown fox");
auto changes1 = node1.get_changes_since(sync_version);
auto changes2 = node2.get_changes_since(sync_version);
AutoMergingTextRule<std::string> auto_merge_rule;
node2.merge_changes_with_rule(changes1, auto_merge_rule);
node1.merge_changes_with_rule(changes2, auto_merge_rule);
auto line1 = node1.get_line(id);
auto line2 = node2.get_line(id);
TEST_ASSERT(line1.has_value() && line2.has_value(), "Lines should exist");
TEST_ASSERT(line1->has_conflicts() || line2->has_conflicts(),
"Should have conflicts on overlapping edits");
return true;
}
bool test_text_crdt_change_streaming_insert() {
TextCRDT<std::string> node(1);
std::vector<TextChange<std::string, std::string>> changes;
auto id1 = node.insert_line_at_end("Line 1", &changes);
TEST_ASSERT(changes.size() == 1, "Should have 1 change after first insert");
TEST_ASSERT(changes[0].line_id == id1, "Change should have correct line ID");
TEST_ASSERT(changes[0].content == "Line 1", "Change should have correct content");
TEST_ASSERT(changes[0].type == TextChangeType::Insert, "Change should be Insert type");
auto id2 = node.insert_line_at_start("Line 0", &changes);
TEST_ASSERT(changes.size() == 2, "Should have 2 changes after second insert");
TEST_ASSERT(changes[1].line_id == id2, "Second change should have correct line ID");
auto id3 = node.insert_line_after(id1, "Line 1.5", &changes);
TEST_ASSERT(changes.size() == 3, "Should have 3 changes after third insert");
auto id4 = node.insert_line_before(id2, "Line -1", &changes);
TEST_ASSERT(changes.size() == 4, "Should have 4 changes after fourth insert");
return true;
}
bool test_text_crdt_change_streaming_edit() {
TextCRDT<std::string> node(1);
std::vector<TextChange<std::string, std::string>> changes;
auto id = node.insert_line_at_end("Original", &changes);
changes.clear();
node.edit_line(id, "Modified", &changes);
TEST_ASSERT(changes.size() == 1, "Should have 1 change after edit");
TEST_ASSERT(changes[0].line_id == id, "Change should have correct line ID");
TEST_ASSERT(changes[0].content == "Modified", "Change should have new content");
TEST_ASSERT(changes[0].type == TextChangeType::Edit, "Change should be Edit type");
return true;
}
bool test_text_crdt_change_streaming_delete() {
TextCRDT<std::string> node(1);
std::vector<TextChange<std::string, std::string>> changes;
auto id = node.insert_line_at_end("To delete", &changes);
changes.clear();
node.delete_line(id, &changes);
TEST_ASSERT(changes.size() == 1, "Should have 1 change after delete");
TEST_ASSERT(changes[0].line_id == id, "Change should have correct line ID");
TEST_ASSERT(!changes[0].content.has_value(), "Delete change should have no content");
TEST_ASSERT(changes[0].type == TextChangeType::Delete, "Change should be Delete type");
return true;
}
bool test_text_crdt_change_streaming_live_sync() {
TextCRDT<std::string> node1(1);
TextCRDT<std::string> node2(2);
std::vector<TextChange<std::string, std::string>> changes;
auto id1 = node1.insert_line_at_end("Hello", &changes);
auto id2 = node1.insert_line_at_end("World", &changes);
TEST_ASSERT(changes.size() == 2, "Should have 2 changes");
node2.merge_changes(changes);
TEST_ASSERT(node2.line_count() == 2, "Node2 should have 2 lines");
TEST_ASSERT(node2.to_text() == "Hello\nWorld", "Node2 should have correct content");
changes.clear();
node1.edit_line(id1, "Hi", &changes);
node1.delete_line(id2, &changes);
TEST_ASSERT(changes.size() == 2, "Should have 2 more changes");
node2.merge_changes(changes);
TEST_ASSERT(node2.line_count() == 1, "Node2 should have 1 line after delete");
TEST_ASSERT(node2.to_text() == "Hi", "Node2 should have updated content");
return true;
}
bool test_text_crdt_to_text() {
TextCRDT<std::string> node(1);
TEST_ASSERT(node.to_text() == "", "Empty document should return empty string");
node.insert_line_at_end("Line 1");
TEST_ASSERT(node.to_text() == "Line 1", "Single line should have no separator");
node.insert_line_at_end("Line 2");
node.insert_line_at_end("Line 3");
TEST_ASSERT(node.to_text() == "Line 1\nLine 2\nLine 3", "Multiple lines should be separated by newline");
TEST_ASSERT(node.to_text(" | ") == "Line 1 | Line 2 | Line 3", "Custom separator should work");
return true;
}
bool test_text_crdt_get_all_lines() {
TextCRDT<std::string> node(1);
auto lines = node.get_all_lines();
TEST_ASSERT(lines.size() == 0, "Empty document should return empty vector");
auto id1 = node.insert_line_at_end("First");
auto id2 = node.insert_line_at_end("Second");
auto id3 = node.insert_line_at_start("Zeroth");
lines = node.get_all_lines();
TEST_ASSERT(lines.size() == 3, "Should have 3 lines");
TEST_ASSERT(lines[0].content == "Zeroth", "First line should be 'Zeroth'");
TEST_ASSERT(lines[1].content == "First", "Second line should be 'First'");
TEST_ASSERT(lines[2].content == "Second", "Third line should be 'Second'");
TEST_ASSERT(lines[0].id == id3, "First line ID should match");
TEST_ASSERT(lines[1].id == id1, "Second line ID should match");
TEST_ASSERT(lines[2].id == id2, "Third line ID should match");
return true;
}
int main() {
int total = 0, passed = 0, failed = 0;
std::cout << "=== Text CRDT Test Suite ===\n\n";
std::cout << "--- FractionalPosition Tests ---\n";
RUN_TEST(test_fractional_position_basic);
RUN_TEST(test_fractional_position_between_with_room);
RUN_TEST(test_fractional_position_between_adjacent);
RUN_TEST(test_fractional_position_prefix);
RUN_TEST(test_fractional_position_repeated_bisection);
std::cout << "\n--- TextCRDT Basic Operations ---\n";
RUN_TEST(test_text_crdt_insert_at_start);
RUN_TEST(test_text_crdt_insert_at_end);
RUN_TEST(test_text_crdt_insert_after);
RUN_TEST(test_text_crdt_insert_before);
RUN_TEST(test_text_crdt_edit);
RUN_TEST(test_text_crdt_delete);
std::cout << "\n--- TextCRDT Advanced Tests ---\n";
RUN_TEST(test_text_crdt_many_insertions);
RUN_TEST(test_text_crdt_interleaved_insertions);
RUN_TEST(test_text_crdt_index_consistency);
std::cout << "\n--- Distributed CRDT Tests ---\n";
RUN_TEST(test_text_crdt_basic_sync);
RUN_TEST(test_text_crdt_concurrent_insertions);
RUN_TEST(test_text_crdt_concurrent_edits_lww);
RUN_TEST(test_text_crdt_concurrent_edits_bww);
RUN_TEST(test_text_crdt_delete_propagation);
RUN_TEST(test_text_crdt_three_way_sync);
RUN_TEST(test_text_crdt_delete_vs_edit_conflict);
RUN_TEST(test_text_crdt_multi_node_concurrent_edits);
std::cout << "\n--- Auto-Merge Tests ---\n";
RUN_TEST(test_text_crdt_auto_merge_non_overlapping);
RUN_TEST(test_text_crdt_auto_merge_overlapping);
std::cout << "\n--- Change Streaming Tests ---\n";
RUN_TEST(test_text_crdt_change_streaming_insert);
RUN_TEST(test_text_crdt_change_streaming_edit);
RUN_TEST(test_text_crdt_change_streaming_delete);
RUN_TEST(test_text_crdt_change_streaming_live_sync);
std::cout << "\n--- Text Output Tests ---\n";
RUN_TEST(test_text_crdt_to_text);
RUN_TEST(test_text_crdt_get_all_lines);
std::cout << "\n=== Summary ===\n";
std::cout << "Total: " << total << "\n";
std::cout << "Passed: " << passed << "\n";
std::cout << "Failed: " << failed << "\n";
return failed == 0 ? 0 : 1;
}