NOT-OMEGA commited on
Commit
1d54862
·
verified ·
1 Parent(s): 7e09d8b

Create Ot_test · cpp

Browse files
Files changed (1) hide show
  1. Ot_test · cpp +343 -0
Ot_test · cpp ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * CollabDocs C++ — OT Engine Test Suite
3
+ *
4
+ * Tests all 4 transform cases + convergence + log-transform.
5
+ * No external test framework needed — pure C++17.
6
+ */
7
+
8
+ #include "ot_engine.hpp"
9
+ #include <iostream>
10
+ #include <cassert>
11
+ #include <cstring>
12
+ #include <random>
13
+ #include <string>
14
+
15
+ using namespace collab;
16
+
17
+ // ─── Mini test framework ─────────────────────────────────────────────────────
18
+
19
+ static int g_pass = 0, g_fail = 0;
20
+
21
+ #define EXPECT_EQ(a, b) do { \
22
+ if ((a) == (b)) { g_pass++; } \
23
+ else { g_fail++; \
24
+ std::cerr << "FAIL " << __FILE__ << ":" << __LINE__ \
25
+ << " expected=" << (b) << " got=" << (a) << "\n"; } \
26
+ } while(0)
27
+
28
+ #define EXPECT_STR_EQ(a, b) do { \
29
+ if (std::string(a) == std::string(b)) { g_pass++; } \
30
+ else { g_fail++; \
31
+ std::cerr << "FAIL " << __FILE__ << ":" << __LINE__ \
32
+ << "\n expected: " << (b) \
33
+ << "\n got: " << (a) << "\n"; } \
34
+ } while(0)
35
+
36
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
37
+
38
+ Operation ins(int pos, const std::string& val,
39
+ const std::string& uid = "", int base = 0) {
40
+ Operation op;
41
+ op.type = OpType::INSERT;
42
+ op.position = pos;
43
+ op.value = val;
44
+ op.length = static_cast<int>(val.size());
45
+ op.user_id = uid;
46
+ op.base_version = base;
47
+ return op;
48
+ }
49
+
50
+ Operation del(int pos, int len,
51
+ const std::string& uid = "", int base = 0) {
52
+ Operation op;
53
+ op.type = OpType::DELETE;
54
+ op.position = pos;
55
+ op.length = len;
56
+ op.user_id = uid;
57
+ op.base_version = base;
58
+ return op;
59
+ }
60
+
61
+ // ─── Insert-Insert ────────────────────────────────────────────────────────────
62
+
63
+ void test_ii_before() {
64
+ auto r = transform_operation(ins(5,"X"), ins(2,"AB"));
65
+ EXPECT_EQ(r.position, 7);
66
+ }
67
+
68
+ void test_ii_after() {
69
+ auto r = transform_operation(ins(2,"X"), ins(5,"AB"));
70
+ EXPECT_EQ(r.position, 2);
71
+ }
72
+
73
+ void test_ii_same_tiebreak_shift() {
74
+ // alice < bob → alice wins → bob shifts
75
+ auto r = transform_operation(ins(3,"X","bob"), ins(3,"Y","alice"));
76
+ EXPECT_EQ(r.position, 4);
77
+ }
78
+
79
+ void test_ii_same_tiebreak_no_shift() {
80
+ auto r = transform_operation(ins(3,"X","alice"), ins(3,"Y","bob"));
81
+ EXPECT_EQ(r.position, 3);
82
+ }
83
+
84
+ // ─── Delete-Insert ────────────────────────────────────────────────────────────
85
+
86
+ void test_di_insert_before_delete() {
87
+ auto r = transform_operation(del(5,3), ins(2,"XX"));
88
+ EXPECT_EQ(r.position, 7);
89
+ EXPECT_EQ(r.length, 3);
90
+ }
91
+
92
+ void test_di_insert_inside_delete() {
93
+ auto r = transform_operation(del(3,5), ins(5,"AB"));
94
+ EXPECT_EQ(r.position, 3);
95
+ EXPECT_EQ(r.length, 7); // expanded
96
+ }
97
+
98
+ void test_di_insert_after_delete() {
99
+ auto r = transform_operation(del(3,4), ins(10,"AB"));
100
+ EXPECT_EQ(r.position, 3);
101
+ EXPECT_EQ(r.length, 4);
102
+ }
103
+
104
+ // ─── Insert-Delete ────────────────────────────────────────────────────────────
105
+
106
+ void test_id_delete_before_insert() {
107
+ auto r = transform_operation(ins(8,"X"), del(2,3));
108
+ EXPECT_EQ(r.position, 5);
109
+ }
110
+
111
+ void test_id_delete_contains_insert() {
112
+ auto r = transform_operation(ins(5,"X"), del(3,5));
113
+ EXPECT_EQ(r.position, 3);
114
+ }
115
+
116
+ void test_id_delete_after_insert() {
117
+ auto r = transform_operation(ins(2,"X"), del(5,3));
118
+ EXPECT_EQ(r.position, 2);
119
+ }
120
+
121
+ // ─── Delete-Delete ────────────────────────────────────────────────────────────
122
+
123
+ void test_dd_applied_before() {
124
+ auto r = transform_operation(del(8,4), del(2,3));
125
+ EXPECT_EQ(r.position, 5);
126
+ EXPECT_EQ(r.length, 4);
127
+ }
128
+
129
+ void test_dd_applied_after() {
130
+ auto r = transform_operation(del(2,3), del(8,4));
131
+ EXPECT_EQ(r.position, 2);
132
+ EXPECT_EQ(r.length, 3);
133
+ }
134
+
135
+ void test_dd_overlap_right() {
136
+ // incoming [3,7), applied [5,9): overlap [5,7)=2
137
+ auto r = transform_operation(del(3,4), del(5,4));
138
+ EXPECT_EQ(r.position, 3);
139
+ EXPECT_EQ(r.length, 2);
140
+ }
141
+
142
+ void test_dd_overlap_left() {
143
+ // incoming [5,9), applied [2,7): overlap [5,7)=2
144
+ auto r = transform_operation(del(5,4), del(2,5));
145
+ EXPECT_EQ(r.position, 2);
146
+ EXPECT_EQ(r.length, 2);
147
+ }
148
+
149
+ void test_dd_applied_contains_incoming() {
150
+ auto r = transform_operation(del(4,2), del(2,8));
151
+ EXPECT_EQ(r.length, 0);
152
+ }
153
+
154
+ void test_dd_identical() {
155
+ auto r = transform_operation(del(3,4,"b"), del(3,4,"a"));
156
+ EXPECT_EQ(r.length, 0);
157
+ }
158
+
159
+ // ─── Convergence ─────────────────────────────────────────────────────────────
160
+
161
+ void test_convergence_ii() {
162
+ std::string doc = "hello world";
163
+ auto op1 = ins(5,"_A_","u1");
164
+ auto op2 = ins(5,"_B_","u2");
165
+
166
+ auto d1 = apply_operation(doc, op1);
167
+ d1 = apply_operation(d1, transform_operation(op2, op1));
168
+
169
+ auto d2 = apply_operation(doc, op2);
170
+ d2 = apply_operation(d2, transform_operation(op1, op2));
171
+
172
+ EXPECT_STR_EQ(d1, d2);
173
+ }
174
+
175
+ void test_convergence_dd() {
176
+ std::string doc = "abcdefghij";
177
+ auto op1 = del(2,5,"u1");
178
+ auto op2 = del(4,4,"u2");
179
+
180
+ auto d1 = apply_operation(doc, op1);
181
+ d1 = apply_operation(d1, transform_operation(op2, op1));
182
+
183
+ auto d2 = apply_operation(doc, op2);
184
+ d2 = apply_operation(d2, transform_operation(op1, op2));
185
+
186
+ EXPECT_STR_EQ(d1, d2);
187
+ }
188
+
189
+ void test_transform_against_log() {
190
+ std::string doc = "hello world";
191
+ Operation srv1 = ins(0,">> ","s",0);
192
+ Operation srv2 = del(9,3,"s",1);
193
+
194
+ std::vector<std::pair<int,Operation>> log = {{1,srv1},{2,srv2}};
195
+
196
+ Operation client_op = ins(5,"X","c",0);
197
+ auto result = transform_against_log(client_op, log, 0, 2);
198
+
199
+ // Manual:
200
+ // After srv1 (ins 3 chars at 0): pos 5 → 8
201
+ auto after1 = transform_operation(client_op, srv1);
202
+ // After srv2 (del 3 at 9): pos 8 < 9 → no change
203
+ auto after2 = transform_operation(after1, srv2);
204
+
205
+ EXPECT_EQ(result.position, after2.position);
206
+ }
207
+
208
+ // ─── Apply tests ──────────────────────────────────────────────────────────────
209
+
210
+ void test_apply_insert() {
211
+ EXPECT_STR_EQ(apply_operation("abcde", ins(2,"XY")), "abXYcde");
212
+ }
213
+
214
+ void test_apply_delete() {
215
+ EXPECT_STR_EQ(apply_operation("abcde", del(1,3)), "ae");
216
+ }
217
+
218
+ void test_apply_insert_end() {
219
+ EXPECT_STR_EQ(apply_operation("abc", ins(3,"Z")), "abcZ");
220
+ }
221
+
222
+ void test_apply_delete_out_of_range() {
223
+ EXPECT_STR_EQ(apply_operation("abc", del(2,100)), "ab");
224
+ }
225
+
226
+ // ─── Cursor sync ──────────────────────────────────────────────────────────────
227
+
228
+ void test_cursor_insert_before() {
229
+ auto c = transform_cursor(8, ins(3,"AB"));
230
+ EXPECT_EQ(c, 10);
231
+ }
232
+
233
+ void test_cursor_insert_after() {
234
+ auto c = transform_cursor(2, ins(5,"AB"));
235
+ EXPECT_EQ(c, 2);
236
+ }
237
+
238
+ void test_cursor_delete_before() {
239
+ auto c = transform_cursor(8, del(2,3));
240
+ EXPECT_EQ(c, 5);
241
+ }
242
+
243
+ void test_cursor_delete_over() {
244
+ auto c = transform_cursor(4, del(2,5));
245
+ EXPECT_EQ(c, 2);
246
+ }
247
+
248
+ // ─── Stress convergence ───────────────────────────────────────────────────────
249
+
250
+ void stress_convergence(int trials = 500) {
251
+ std::mt19937 rng(42);
252
+ const std::string alphabet = "abcdefghijklmnopqrstuvwxyz ";
253
+ int failures = 0;
254
+
255
+ auto rand_op = [&](const std::string& content, const std::string& uid) {
256
+ if (content.empty() || std::uniform_real_distribution<>()(rng) < 0.55) {
257
+ int pos = std::uniform_int_distribution<>(0, static_cast<int>(content.size()))(rng);
258
+ char c = alphabet[std::uniform_int_distribution<>(0,static_cast<int>(alphabet.size())-1)(rng)];
259
+ return ins(pos, std::string(1,c), uid);
260
+ } else {
261
+ int pos = std::uniform_int_distribution<>(0, static_cast<int>(content.size())-1)(rng);
262
+ int len = std::uniform_int_distribution<>(1, std::min(3, static_cast<int>(content.size())-pos))(rng);
263
+ return del(pos, len, uid);
264
+ }
265
+ };
266
+
267
+ std::string initial = "The quick brown fox jumps.";
268
+ for (int t = 0; t < trials; ++t) {
269
+ std::string doc = initial;
270
+ auto op1 = rand_op(doc, "u1");
271
+ auto op2 = rand_op(doc, "u2");
272
+
273
+ auto d1 = apply_operation(doc, op1);
274
+ d1 = apply_operation(d1, transform_operation(op2, op1));
275
+
276
+ auto d2 = apply_operation(doc, op2);
277
+ d2 = apply_operation(d2, transform_operation(op1, op2));
278
+
279
+ if (d1 != d2) failures++;
280
+ }
281
+
282
+ if (failures > 0) {
283
+ g_fail++;
284
+ std::cerr << "FAIL stress_convergence: " << failures << "/" << trials << " diverged\n";
285
+ } else {
286
+ g_pass++;
287
+ std::cout << " stress_convergence: " << trials << " trials OK\n";
288
+ }
289
+ }
290
+
291
+ // ─── Main ─────────────────────────────────────────────────────────────────────
292
+
293
+ int main() {
294
+ std::cout << "=== CollabDocs C++ OT Test Suite ===\n\n";
295
+
296
+ // Insert-Insert
297
+ test_ii_before();
298
+ test_ii_after();
299
+ test_ii_same_tiebreak_shift();
300
+ test_ii_same_tiebreak_no_shift();
301
+
302
+ // Delete-Insert
303
+ test_di_insert_before_delete();
304
+ test_di_insert_inside_delete();
305
+ test_di_insert_after_delete();
306
+
307
+ // Insert-Delete
308
+ test_id_delete_before_insert();
309
+ test_id_delete_contains_insert();
310
+ test_id_delete_after_insert();
311
+
312
+ // Delete-Delete
313
+ test_dd_applied_before();
314
+ test_dd_applied_after();
315
+ test_dd_overlap_right();
316
+ test_dd_overlap_left();
317
+ test_dd_applied_contains_incoming();
318
+ test_dd_identical();
319
+
320
+ // Convergence
321
+ test_convergence_ii();
322
+ test_convergence_dd();
323
+ test_transform_against_log();
324
+
325
+ // Apply
326
+ test_apply_insert();
327
+ test_apply_delete();
328
+ test_apply_insert_end();
329
+ test_apply_delete_out_of_range();
330
+
331
+ // Cursor
332
+ test_cursor_insert_before();
333
+ test_cursor_insert_after();
334
+ test_cursor_delete_before();
335
+ test_cursor_delete_over();
336
+
337
+ // Stress
338
+ stress_convergence(500);
339
+
340
+ std::cout << "\n=== Results: "
341
+ << g_pass << " passed, " << g_fail << " failed ===\n";
342
+ return g_fail > 0 ? 1 : 0;
343
+ }