version.test.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. import * as _ from "lodash";
  2. import {
  3. Version,
  4. VersionRegistry,
  5. // embed,
  6. // overrideDeltas,
  7. } from "../lib/version";
  8. import {
  9. Delta,
  10. } from "../lib/delta";
  11. import {DeltaRegistry} from "../lib/delta_registry";
  12. import {
  13. mockUuid,
  14. } from "../lib/util/test_helpers";
  15. import {
  16. assert,
  17. assertThrows,
  18. } from "../lib/util/assert";
  19. describe("Version", () => {
  20. it("Get deltas", () => {
  21. const deltaRegistry = new DeltaRegistry();
  22. const getId = mockUuid();
  23. const registry = new VersionRegistry();
  24. const nodeCreation = deltaRegistry.newNodeCreation(getId());
  25. const nodeDeletion = deltaRegistry.newNodeDeletion(nodeCreation, [], []);
  26. const version1 = registry.createVersion(registry.initialVersion, nodeCreation);
  27. const version2 = registry.createVersion(version1, nodeDeletion);
  28. assert(_.isEqual([... registry.initialVersion], []), "expected initialVersion to be empty");
  29. assert(_.isEqual([... version1], [nodeCreation]), "expected version1 to contain creation");
  30. assert(_.isEqual([... version2], [nodeDeletion, nodeCreation]), "expected version2 to contain creation and deletion");
  31. });
  32. it("Commutating operations yield equal versions", () => {
  33. const deltaRegistry = new DeltaRegistry();
  34. const getId = mockUuid();
  35. const registry = new VersionRegistry();
  36. const nodeCreationA = deltaRegistry.newNodeCreation(getId());
  37. const nodeCreationB = deltaRegistry.newNodeCreation(getId());
  38. const versionA = registry.createVersion(registry.initialVersion, nodeCreationA);
  39. const versionAB = registry.createVersion(versionA, nodeCreationB);
  40. const versionB = registry.createVersion(registry.initialVersion, nodeCreationB);
  41. const versionBA = registry.createVersion(versionB, nodeCreationA);
  42. assert(versionAB === versionBA, "expected versions to be equal");
  43. });
  44. it("Intersection", () => {
  45. const deltaRegistry = new DeltaRegistry();
  46. const getId = mockUuid();
  47. const registry = new VersionRegistry();
  48. const A = deltaRegistry.newNodeCreation(getId());
  49. const B = deltaRegistry.newNodeCreation(getId());
  50. const C = deltaRegistry.newNodeCreation(getId());
  51. const D = deltaRegistry.newNodeDeletion(A, [], []);
  52. const v1 = registry.quickVersion([D,B,A]);
  53. const v2 = registry.quickVersion([C,B]);
  54. const intersection0 = registry.getIntersection([v1, v2]);
  55. assert(intersection0 === registry.quickVersion([B]), "expected intersection of v1 and v2 to be B.");
  56. const intersection1 = registry.getIntersection([v1, v1]);
  57. assert(intersection1 === v1, "expected intersection of v1 with itself to be v1");
  58. const intersection2 = registry.getIntersection([v1]);
  59. assert(intersection2 === v1, "expected intersection of v1 with itself to be v1");
  60. const intersection3 = registry.getIntersection([]);
  61. assert(intersection3 === registry.initialVersion, "expected intersection of empty set to be initial (empty) version");
  62. });
  63. describe("Merging", () => {
  64. // Helper
  65. function mergeAgain(registry, merged, nameMap?) {
  66. const mergedAgain = registry.merge(merged, nameMap);
  67. assert(mergedAgain.length === merged.length
  68. && mergedAgain.every(version => merged.includes(version)),
  69. "merging a merge result should just give the same result again.");
  70. }
  71. it("Merge empty set", () => {
  72. const registry = new VersionRegistry();
  73. const merged = registry.merge([]);
  74. assert(merged.length === 1 && merged[0] === registry.initialVersion, "expected intial version");
  75. mergeAgain(registry, merged);
  76. })
  77. it("Merge non-conflicting versions", () => {
  78. const deltaRegistry = new DeltaRegistry();
  79. const getId = mockUuid();
  80. const registry = new VersionRegistry();
  81. const nodeCreationA = deltaRegistry.newNodeCreation(getId());
  82. const nodeCreationB = deltaRegistry.newNodeCreation(getId());
  83. const versionA = registry.createVersion(registry.initialVersion, nodeCreationA);
  84. const versionB = registry.createVersion(registry.initialVersion, nodeCreationB);
  85. const nameMap = new Map([[nodeCreationA, "A"], [nodeCreationB, "B"]]);
  86. const merged = registry.merge([versionA, versionB], delta => nameMap.get(delta)!);
  87. assert(merged.length === 1, "expected 1 merged version");
  88. const deltas = [... merged[0]];
  89. assert(deltas.length === 2
  90. && deltas.includes(nodeCreationA)
  91. && deltas.includes(nodeCreationB),
  92. "expected merged version to contain nodes A and B");
  93. mergeAgain(registry, merged, delta => nameMap.get(delta)!);
  94. });
  95. it("Merge complex conflicting versions", () => {
  96. const deltaRegistry = new DeltaRegistry();
  97. const getId = mockUuid();
  98. const registry = new VersionRegistry();
  99. // the names of the deltas and the versions in this test trace back to an illustration in a Xournal++ file.
  100. const X = deltaRegistry.newNodeCreation(getId());
  101. const Y = deltaRegistry.newNodeCreation(getId());
  102. const Z = deltaRegistry.newNodeCreation(getId());
  103. const A = deltaRegistry.newNodeDeletion(X, [], []);
  104. const B0 = deltaRegistry.newEdgeCreation(X, "label");
  105. const B = deltaRegistry.newEdgeUpdate(B0, Y); // conflicts with A
  106. assert(_.isEqual(B.conflictsWith, [[A, 'U/D']]), "Expected B to conflict with A");
  107. const C0 = deltaRegistry.newEdgeCreation(Y, "label");
  108. const C = deltaRegistry.newEdgeUpdate(C0, Z);
  109. const BB = deltaRegistry.newEdgeUpdate(B.overwrite(), null); // unset edge B.
  110. const D = deltaRegistry.newNodeDeletion(Y, [], [BB]); // conflicts with C
  111. assert(_.isEqual(D.conflictsWith, [[C, 'U/D']]), "Expected D to conflict with C");
  112. const nameMap: Map<Delta, string> = new Map<Delta,string>([
  113. [X, "X"],
  114. [Y, "Y"],
  115. [Z, "Z"],
  116. [A, "A"],
  117. [B0, "B0"],
  118. [B, "B"],
  119. [BB, "BB"],
  120. [C0, "C0"],
  121. [C, "C"],
  122. [D, "D"],
  123. ]);
  124. const three = registry.quickVersion([A,X,Y,Z]);
  125. const seven = registry.quickVersion([C,C0,X,Y,Z]);
  126. const five = registry.quickVersion([D,BB,B,B0,X,Y,Z]);
  127. const merged = registry.merge([three, seven, five], d => nameMap.get(d)!);
  128. assert(merged.length === 3, "expected three maximal versions");
  129. // assert(merged.includes(registry.quickVersion([A,C,C0,X,Y,Z])), "expected [X,Y,Z,A,C] to be a maximal version");
  130. // assert(merged.includes(registry.quickVersion([BB,B,B0,C,C0,X,Y,Z])), "expected [X,Y,Z,B,C] to be a maximal version");
  131. // assert(merged.includes(registry.quickVersion([D,BB,B,B0,X,Y,Z])), "expected [X,Y,Z,B,D] to be a maximal version");
  132. mergeAgain(registry, merged, d => nameMap.get(d)!);
  133. });
  134. it("Merge many non-conflicting versions (scalability test)", () => {
  135. const deltaRegistry = new DeltaRegistry();
  136. const getId = mockUuid();
  137. const registry = new VersionRegistry();
  138. // Bunch of non-conflicting deltas:
  139. const deltas: Array<Delta> = [];
  140. const nameMap = new Map();
  141. for (let i=0; i<10; i++) {
  142. const delta = deltaRegistry.newNodeCreation(getId());
  143. deltas.push(delta);
  144. nameMap.set(delta, i.toString());
  145. }
  146. // Create a version for each delta, containing only that delta:
  147. const versions = deltas.map(d => registry.createVersion(registry.initialVersion, d));
  148. const merged = registry.merge(versions, d => nameMap.get(d)!);
  149. assert(merged.length === 1, "only one merged version should result");
  150. const mergedAgain = registry.merge(merged, d => nameMap.get(d)!);
  151. assert(mergedAgain.length === merged.length
  152. && mergedAgain.every(version => merged.includes(version)),
  153. "merging a merge result should just give the same result again.");
  154. });
  155. it("Merge many conflicting versions (scalability test 2)", () => {
  156. const deltaRegistry = new DeltaRegistry();
  157. const getId = mockUuid();
  158. const registry = new VersionRegistry();
  159. const HOW_MANY = 3;
  160. const versions: Array<Version> = [];
  161. const nameMap = new Map();
  162. for (let i=0; i<HOW_MANY; i++) {
  163. const creation = deltaRegistry.newNodeCreation(getId());
  164. const deletion = deltaRegistry.newNodeDeletion(creation, [], []);
  165. const edgeCreation = deltaRegistry.newEdgeCreation(creation, "l");
  166. const edgeUpdate = deltaRegistry.newEdgeUpdate(edgeCreation, creation); // conflicts with deletion0
  167. nameMap.set(creation, "C"+i.toString());
  168. nameMap.set(deletion, "D"+i.toString());
  169. nameMap.set(edgeCreation, "EC"+i.toString());
  170. nameMap.set(edgeUpdate, "EU"+i.toString());
  171. versions.push(registry.quickVersion([deletion, creation]));
  172. versions.push(registry.quickVersion([edgeUpdate, edgeCreation, creation]));
  173. }
  174. const merged = registry.merge(versions, d => nameMap.get(d)!);
  175. assert(merged.length === Math.pow(2, HOW_MANY), HOW_MANY.toString() + " binary choices should result in " + Math.pow(2,HOW_MANY).toString() + " possible conflict resolutions and therefore merge results.");
  176. console.log("merging again...");
  177. mergeAgain(registry, merged, d => nameMap.get(d)!);
  178. });
  179. });
  180. describe("Embedding of versions", () => {
  181. it("Creating embedded versions", () => {
  182. const deltaRegistry = new DeltaRegistry();
  183. const getId = mockUuid();
  184. const registry = new VersionRegistry();
  185. const guestCreate = deltaRegistry.newNodeCreation(getId());
  186. const guestV1 = registry.createVersion(registry.initialVersion, guestCreate);
  187. const guestDel = deltaRegistry.newNodeDeletion(guestCreate, [], []);
  188. const guestV2 = registry.createVersion(guestV1, guestDel);
  189. const hostCreate = deltaRegistry.newNodeCreation(getId());
  190. const hostEdgeCreate = deltaRegistry.newEdgeCreation(hostCreate, "guest")
  191. const hostLink = deltaRegistry.newEdgeUpdate(hostEdgeCreate, guestCreate);
  192. const allCreate = deltaRegistry.newTransaction([guestCreate, hostCreate, hostEdgeCreate, hostLink], "");
  193. const hostV1 = registry.createVersion(registry.initialVersion, allCreate, () => new Map([
  194. ["guest", {version: guestV1, overridings: new Map()}], // no overridings
  195. ]));
  196. const hostUnlink = deltaRegistry.newEdgeUpdate(hostLink.overwrite(), null);
  197. const hostDel = deltaRegistry.newNodeDeletion(hostCreate, [hostUnlink], []);
  198. const guestDelOverride = deltaRegistry.newNodeDeletion(guestCreate, [], [hostUnlink]);
  199. const allDel = deltaRegistry.newTransaction([hostUnlink, hostDel, guestDelOverride], "");
  200. assertThrows(() => {
  201. registry.createVersion(hostV1, allDel, () => new Map([
  202. ["guest", {version: guestV2, overridings: new Map()}]
  203. ]));
  204. }, "should not be able to create host version without explicitly stating that guestDel was overridden by guestDelOver");
  205. const hostV2 = registry.createVersion(hostV1, allDel, () => new Map([
  206. ["guest", {version: guestV2, overridings: new Map([[guestDel, guestDelOverride]])}],
  207. ]));
  208. });
  209. it("Merging host versions", () => {
  210. const deltaRegistry = new DeltaRegistry();
  211. const getId = mockUuid();
  212. const registry = new VersionRegistry();
  213. const createA = deltaRegistry.newNodeCreation(getId());
  214. const createB = deltaRegistry.newNodeCreation(getId());
  215. const guestA = registry.createVersion(registry.initialVersion, createA);
  216. const guestB = registry.createVersion(registry.initialVersion, createB);
  217. const createC = deltaRegistry.newNodeCreation(getId());
  218. const createD = deltaRegistry.newNodeCreation(getId());
  219. const createAC = deltaRegistry.newTransaction([createA, createC], "");
  220. const createBD = deltaRegistry.newTransaction([createB, createD], "");
  221. const debugNames = new Map<Delta,string>([
  222. [createA, "createA"],
  223. [createB, "createB"],
  224. [createC, "createC"],
  225. [createD, "createD"],
  226. [createAC, "createAC"],
  227. [createBD, "createBD"],
  228. ]);
  229. const hostAC = registry.createVersion(registry.initialVersion, createAC, () => new Map([
  230. ["guest", {version: guestA, overridings: new Map()}],
  231. ]));
  232. const hostBD = registry.createVersion(registry.initialVersion, createBD, () => new Map([
  233. ["guest", {version: guestB, overridings: new Map()}],
  234. ]));
  235. console.log("Merging hosts...");
  236. const mergedHosts = registry.merge([hostAC, hostBD], d => debugNames.get(d)!);
  237. assert(mergedHosts.length === 1, "expected no host merge conflict");
  238. console.log("Merging guests...");
  239. const mergedGuests = registry.merge([guestA, guestB], d => debugNames.get(d)!);
  240. assert(mergedGuests.length === 1, "expected no guest merge conflict");
  241. const [guestAB] = mergedGuests;
  242. const [hostABCD] = mergedHosts;
  243. const {version: mergedGuest, overridings: mergedOverridings} = hostABCD.getEmbedding("guest");
  244. assert(mergedGuest === guestAB, "merged host should embed merged guest");
  245. assert(mergedOverridings.size === 0, "expected no overridings in merged embedding");
  246. });
  247. it("Multi-level embedding", () => {
  248. const deltaRegistry = new DeltaRegistry();
  249. const getId = mockUuid();
  250. const registry = new VersionRegistry();
  251. const createA = deltaRegistry.newNodeCreation(getId());
  252. const createB = deltaRegistry.newNodeCreation(getId());
  253. const createC = deltaRegistry.newNodeCreation(getId());
  254. const createAB = deltaRegistry.newTransaction([createA, createB], "");
  255. const createABC = deltaRegistry.newTransaction([createAB, createC], "");
  256. const vA = registry.createVersion(registry.initialVersion, createA);
  257. const vAB = registry.createVersion(registry.initialVersion, createAB, () => new Map([
  258. ["L0", {version: vA, overridings: new Map()}],
  259. ]));
  260. const vABC = registry.createVersion(registry.initialVersion, createABC, () => new Map([
  261. ["L1", {version: vAB, overridings: new Map()}],
  262. ]));
  263. const createD = deltaRegistry.newNodeCreation(getId());
  264. const createE = deltaRegistry.newNodeCreation(getId());
  265. const createF = deltaRegistry.newNodeCreation(getId());
  266. const createDE = deltaRegistry.newTransaction([createD, createE], "");
  267. const createDEF = deltaRegistry.newTransaction([createDE, createF], "");
  268. const vD = registry.createVersion(registry.initialVersion, createD);
  269. const vDE = registry.createVersion(registry.initialVersion, createDE, () => new Map([
  270. ["L0", {version: vD, overridings: new Map()}],
  271. ]));
  272. const vDEF = registry.createVersion(registry.initialVersion, createDEF, () => new Map([
  273. ["L1", {version: vDE, overridings: new Map()}],
  274. ]));
  275. const [vABCDEF] = registry.merge([vABC, vDEF]);
  276. const vABDE = vABCDEF.embeddings.get("L1")?.version;
  277. assert(vABDE !== undefined, "No L1 merged version");
  278. assert([...vABDE!].length === 2, "L1 merge result unexpected number of deltas: " + [...vABDE!].length);
  279. const vAD = vABDE!.embeddings.get("L0")?.version;
  280. assert(vAD !== undefined, "No L0 merged version");
  281. assert([...vAD!].length === 2, "L1 merge result unexpected number of deltas: " + [...vAD!].length);
  282. });
  283. it("Self-embedding", () => {
  284. const deltaRegistry = new DeltaRegistry();
  285. const getId = mockUuid();
  286. const registry = new VersionRegistry();
  287. const createA = deltaRegistry.newNodeCreation(getId());
  288. const createB = deltaRegistry.newNodeCreation(getId());
  289. const vA = registry.createVersion(registry.initialVersion, createA, version => new Map([
  290. ["self", {version, overridings: new Map()}]]));
  291. const vB = registry.createVersion(registry.initialVersion, createB, version => new Map([
  292. ["self", {version, overridings: new Map()}]]));
  293. const [vAB] = registry.merge([vA, vB]);
  294. assert(vAB.embeddings.get("self") !== undefined, "Expected merge result to embed itself");
  295. })
  296. });
  297. });