Skip to content

Commit 46be712

Browse files
committed
[213_24] Add unit tests for patch (OT) module in moebius
The patch module implements core OT primitives (commute/swap/pull/invert) essential for collaborative editing, but currently has zero test coverage. Add comprehensive tests covering: - Basic patch construction (modification, compound, birth, author) - Patch application (assign, insert, remove, split, join) - Patch inversion and round-trip verification - Patch equality and copy - is_applicable validity checks
1 parent 23f4bb8 commit 46be712

2 files changed

Lines changed: 212 additions & 0 deletions

File tree

devel/213_24.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# [213_24] Add unit tests for patch (OT) module in moebius
2+
3+
The `moebius/Data/History/patch` module implements the core Operational Transformation (OT) primitives that are essential for collaborative editing. Currently, this module has **zero test coverage** while `modification`, `path`, and `tree` all have tests.
4+
5+
This PR adds comprehensive unit tests covering:
6+
7+
## Test Categories
8+
1. **Basic patch construction** — modification, compound, branch, birth, author patches
9+
2. **Patch application** — assign, insert, remove, split, join operations on trees
10+
3. **Patch inversion** — verifying that `invert(p, t)` produces a patch that restores the original tree
11+
4. **Patch equality and copy** — structural equality and deep copy
12+
5. **is_applicable** — validity checks before applying patches
13+
14+
## Why This Matters
15+
These tests are foundational for the Web-Based Collaborative Editing Core (GSoC 2026 project), where OT correctness is critical for concurrent editing sessions. Having a solid test suite ensures that porting the editing engine to WebAssembly does not introduce regressions in the OT layer.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
#include "modification.hpp"
2+
#include "moe_doctests.hpp"
3+
#include "patch.hpp"
4+
#include "tree.hpp"
5+
6+
/******************************************************************************
7+
* Basic patch construction
8+
******************************************************************************/
9+
10+
TEST_CASE ("patch modification construction") {
11+
modification m = mod_assign (path (), tree ("hello"));
12+
modification inv= mod_assign (path (), tree (""));
13+
patch p (m, inv);
14+
CHECK (is_modification (p));
15+
CHECK (get_type (p) == PATCH_MODIFICATION);
16+
CHECK (get_modification (p) == m);
17+
CHECK (get_inverse (p) == inv);
18+
}
19+
20+
TEST_CASE ("patch compound construction") {
21+
modification m1= mod_assign (path (), tree ("a"));
22+
modification i1= mod_assign (path (), tree (""));
23+
modification m2= mod_assign (path (), tree ("b"));
24+
modification i2= mod_assign (path (), tree ("a"));
25+
patch p1 (m1, i1);
26+
patch p2 (m2, i2);
27+
patch compound (p1, p2);
28+
CHECK (is_compound (compound));
29+
CHECK (N (compound) == 2);
30+
CHECK (compound[0] == p1);
31+
CHECK (compound[1] == p2);
32+
}
33+
34+
TEST_CASE ("patch birth construction") {
35+
double author= new_author ();
36+
patch p (author, true);
37+
CHECK (is_birth (p));
38+
CHECK (get_author (p) == author);
39+
CHECK (get_birth (p) == true);
40+
}
41+
42+
TEST_CASE ("patch author construction") {
43+
double author= new_author ();
44+
modification m = mod_assign (path (), tree ("x"));
45+
modification inv = mod_assign (path (), tree (""));
46+
patch inner (m, inv);
47+
patch p (author, inner);
48+
CHECK (is_author (p));
49+
CHECK (get_author (p) == author);
50+
CHECK (N (p) == 1);
51+
}
52+
53+
/******************************************************************************
54+
* Patch application
55+
******************************************************************************/
56+
57+
TEST_CASE ("apply modification patch to tree") {
58+
tree t = tree (DOCUMENT, "hello", "world");
59+
modification m = mod_assign (path (0), tree ("hi"));
60+
modification inv= mod_assign (path (0), tree ("hello"));
61+
patch p (m, inv);
62+
tree result= clean_apply (p, t);
63+
CHECK (result[0] == tree ("hi"));
64+
CHECK (result[1] == tree ("world"));
65+
}
66+
67+
TEST_CASE ("apply compound patch to tree") {
68+
tree t = tree ("original");
69+
modification m1= mod_assign (path (), tree ("step1"));
70+
modification i1= mod_assign (path (), tree ("original"));
71+
modification m2= mod_assign (path (), tree ("step2"));
72+
modification i2= mod_assign (path (), tree ("step1"));
73+
patch p1 (m1, i1);
74+
patch p2 (m2, i2);
75+
patch compound (p1, p2);
76+
tree result= clean_apply (compound, t);
77+
CHECK (result == tree ("step2"));
78+
}
79+
80+
TEST_CASE ("apply insert modification") {
81+
tree t = tree (DOCUMENT, "abc");
82+
modification m = mod_insert (path (0), 1, tree ("X"));
83+
modification inv= mod_remove (path (0), 1, 1);
84+
patch p (m, inv);
85+
tree result= clean_apply (p, t);
86+
CHECK (result[0] == tree ("aXbc"));
87+
}
88+
89+
TEST_CASE ("apply remove modification") {
90+
tree t = tree (DOCUMENT, "abcde");
91+
modification m = mod_remove (path (0), 1, 2);
92+
modification inv= mod_insert (path (0), 1, tree ("bc"));
93+
patch p (m, inv);
94+
tree result= clean_apply (p, t);
95+
CHECK (result[0] == tree ("ade"));
96+
}
97+
98+
TEST_CASE ("apply split modification") {
99+
tree t = tree (DOCUMENT, "abcde");
100+
modification m = mod_split (path (), 0, 2);
101+
modification inv= mod_join (path (), 0);
102+
patch p (m, inv);
103+
tree result= clean_apply (p, t);
104+
CHECK (N (result) == 2);
105+
CHECK (result[0] == tree ("ab"));
106+
CHECK (result[1] == tree ("cde"));
107+
}
108+
109+
TEST_CASE ("apply join modification") {
110+
tree t = tree (DOCUMENT, "ab", "cde");
111+
modification m = mod_join (path (), 0);
112+
modification inv= mod_split (path (), 0, 2);
113+
patch p (m, inv);
114+
tree result= clean_apply (p, t);
115+
CHECK (N (result) == 1);
116+
CHECK (result[0] == tree ("abcde"));
117+
}
118+
119+
/******************************************************************************
120+
* Patch inversion
121+
******************************************************************************/
122+
123+
TEST_CASE ("invert modification patch") {
124+
tree t = tree ("hello");
125+
modification m = mod_assign (path (), tree ("world"));
126+
modification inv= mod_assign (path (), tree ("hello"));
127+
patch p (m, inv);
128+
patch p_inv= invert (p, t);
129+
CHECK (is_modification (p_inv));
130+
CHECK (get_modification (p_inv) == inv);
131+
CHECK (get_inverse (p_inv) == m);
132+
}
133+
134+
TEST_CASE ("invert then apply restores original") {
135+
tree t = tree ("original");
136+
modification m = mod_assign (path (), tree ("modified"));
137+
modification inv= mod_assign (path (), tree ("original"));
138+
patch p (m, inv);
139+
tree t2 = clean_apply (p, t);
140+
patch p_inv= invert (p, t);
141+
tree t3 = clean_apply (p_inv, t2);
142+
CHECK (t3 == t);
143+
}
144+
145+
TEST_CASE ("invert compound patch") {
146+
tree t = tree (DOCUMENT, "a", "b");
147+
modification m1= mod_assign (path (0), tree ("x"));
148+
modification i1= mod_assign (path (0), tree ("a"));
149+
modification m2= mod_assign (path (1), tree ("y"));
150+
modification i2= mod_assign (path (1), tree ("b"));
151+
patch p1 (m1, i1);
152+
patch p2 (m2, i2);
153+
patch compound (p1, p2);
154+
tree t2 = clean_apply (compound, t);
155+
patch inv= invert (compound, t);
156+
tree t3 = clean_apply (inv, t2);
157+
CHECK (t3 == t);
158+
}
159+
160+
/******************************************************************************
161+
* Patch equality and copy
162+
******************************************************************************/
163+
164+
TEST_CASE ("patch equality") {
165+
modification m = mod_assign (path (), tree ("a"));
166+
modification inv= mod_assign (path (), tree (""));
167+
patch p1 (m, inv);
168+
patch p2 (m, inv);
169+
CHECK (p1 == p2);
170+
CHECK_FALSE (p1 != p2);
171+
}
172+
173+
TEST_CASE ("patch copy") {
174+
modification m = mod_assign (path (), tree ("a"));
175+
modification inv= mod_assign (path (), tree (""));
176+
patch p1 (m, inv);
177+
patch p2= copy (p1);
178+
CHECK (p1 == p2);
179+
}
180+
181+
/******************************************************************************
182+
* is_applicable
183+
******************************************************************************/
184+
185+
TEST_CASE ("is_applicable for valid modification") {
186+
tree t= tree (DOCUMENT, "hello", "world");
187+
patch p (mod_assign (path (0), tree ("hi")),
188+
mod_assign (path (0), tree ("hello")));
189+
CHECK (is_applicable (p, t));
190+
}
191+
192+
TEST_CASE ("is_applicable for birth patch") {
193+
tree t= tree ("anything");
194+
double a= new_author ();
195+
patch p (a, true);
196+
CHECK (is_applicable (p, t));
197+
}

0 commit comments

Comments
 (0)