version.test.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import * as _ from "lodash";
  2. import {
  3. Version,
  4. VersionRegistry,
  5. embed,
  6. overrideDeltas,
  7. } from "./version";
  8. import {
  9. Delta,
  10. } from "./delta";
  11. import {
  12. NodeCreation,
  13. NodeDeletion,
  14. EdgeCreation,
  15. EdgeUpdate,
  16. } from "./primitive_delta";
  17. import {
  18. CompositeLevel,
  19. } from "./composite_delta"
  20. import {
  21. mockUuid,
  22. } from "./test_helpers";
  23. import {
  24. assert,
  25. assertThrows,
  26. } from "../util/assert";
  27. describe("Version", () => {
  28. it("Get deltas", () => {
  29. const getId = mockUuid();
  30. const registry = new VersionRegistry();
  31. const nodeCreation = new NodeCreation(getId());
  32. const nodeDeletion = new NodeDeletion(nodeCreation, [], []);
  33. const version1 = registry.createVersion(registry.initialVersion, nodeCreation);
  34. const version2 = registry.createVersion(version1, nodeDeletion);
  35. assert(_.isEqual([... registry.initialVersion], []), "expected initialVersion to be empty");
  36. assert(_.isEqual([... version1], [nodeCreation]), "expected version1 to contain creation");
  37. assert(_.isEqual([... version2], [nodeDeletion, nodeCreation]), "expected version2 to contain creation and deletion");
  38. });
  39. it("Commutating operations yield equal versions", () => {
  40. const getId = mockUuid();
  41. const registry = new VersionRegistry();
  42. const nodeCreationA = new NodeCreation(getId());
  43. const nodeCreationB = new NodeCreation(getId());
  44. const versionA = registry.createVersion(registry.initialVersion, nodeCreationA);
  45. const versionAB = registry.createVersion(versionA, nodeCreationB);
  46. const versionB = registry.createVersion(registry.initialVersion, nodeCreationB);
  47. const versionBA = registry.createVersion(versionB, nodeCreationA);
  48. assert(versionAB === versionBA, "expected versions to be equal");
  49. });
  50. it("Intersection", () => {
  51. const getId = mockUuid();
  52. const registry = new VersionRegistry();
  53. const A = new NodeCreation(getId());
  54. const B = new NodeCreation(getId());
  55. const C = new NodeCreation(getId());
  56. const D = new NodeDeletion(A, [], []);
  57. const v1 = registry.quickVersion([D,B,A]);
  58. const v2 = registry.quickVersion([C,B]);
  59. const intersection0 = registry.getIntersection([v1, v2]);
  60. assert(intersection0 === registry.quickVersion([B]), "expected intersection of v1 and v2 to be B.");
  61. const intersection1 = registry.getIntersection([v1, v1]);
  62. assert(intersection1 === v1, "expected intersection of v1 with itself to be v1");
  63. const intersection2 = registry.getIntersection([v1]);
  64. assert(intersection2 === v1, "expected intersection of v1 with itself to be v1");
  65. const intersection3 = registry.getIntersection([]);
  66. assert(intersection3 === registry.initialVersion, "expected intersection of empty set to be initial (empty) version");
  67. });
  68. describe("Merging", () => {
  69. // Helper
  70. function mergeAgain(registry, merged, nameMap) {
  71. const mergedAgain = registry.merge(merged, nameMap);
  72. assert(mergedAgain.length === merged.length
  73. && mergedAgain.every(version => merged.includes(version)),
  74. "merging a merge result should just give the same result again.");
  75. }
  76. it("Merge empty set", () => {
  77. const registry = new VersionRegistry();
  78. const merged = registry.merge([], new Map());
  79. assert(merged.length === 1 && merged[0] === registry.initialVersion, "expected intial version");
  80. mergeAgain(registry, merged, new Map());
  81. })
  82. it("Merge non-conflicting versions", () => {
  83. const getId = mockUuid();
  84. const registry = new VersionRegistry();
  85. const nodeCreationA = new NodeCreation(getId());
  86. const nodeCreationB = new NodeCreation(getId());
  87. const versionA = registry.createVersion(registry.initialVersion, nodeCreationA);
  88. const versionB = registry.createVersion(registry.initialVersion, nodeCreationB);
  89. const nameMap = new Map([[nodeCreationA, "A"], [nodeCreationB, "B"]]);
  90. const merged = registry.merge([versionA, versionB], nameMap);
  91. assert(merged.length === 1, "expected 1 merged version");
  92. const deltas = [... merged[0]];
  93. assert(deltas.length === 2
  94. && deltas.includes(nodeCreationA)
  95. && deltas.includes(nodeCreationB),
  96. "expected merged version to contain nodes A and B");
  97. mergeAgain(registry, merged, nameMap);
  98. });
  99. it("Merge complex conflicting versions", () => {
  100. const getId = mockUuid();
  101. const registry = new VersionRegistry();
  102. // the names of the deltas and the versions in this test trace back to an illustration in a Xournal++ file.
  103. const X = new NodeCreation(getId());
  104. const Y = new NodeCreation(getId());
  105. const Z = new NodeCreation(getId());
  106. const A = new NodeDeletion(X, [], []);
  107. const B = new EdgeCreation(X, "label", Y); // conflicts with A
  108. assert(_.isEqual(B.getConflicts(), [A]), "Expected B to conflict with A");
  109. const C = new EdgeCreation(Y, "label", Z);
  110. const BB = new EdgeUpdate(B, null); // unset edge B.
  111. const D = new NodeDeletion(Y, [], [BB]); // conflicts with C
  112. assert(_.isEqual(D.getConflicts(), [C]), "Expected D to conflict with C");
  113. const nameMap: Map<Delta, string> = new Map<Delta,string>([
  114. [X, "X"],
  115. [Y, "Y"],
  116. [Z, "Z"],
  117. [A, "A"],
  118. [B, "B"],
  119. [BB, "BB"],
  120. [C, "C"],
  121. [D, "D"],
  122. ]);
  123. const three = registry.quickVersion([A,X,Y,Z]);
  124. const seven = registry.quickVersion([C,X,Y,Z]);
  125. const five = registry.quickVersion([D,BB,B,X,Y,Z]);
  126. const merged = registry.merge([three, seven, five], nameMap);
  127. assert(merged.length === 3, "expected three maximal versions");
  128. assert(merged.includes(registry.quickVersion([A,C,X,Y,Z])), "expected [X,Y,Z,A,C] to be a maximal version");
  129. assert(merged.includes(registry.quickVersion([BB,B,C,X,Y,Z])), "expected [X,Y,Z,B,C] to be a maximal version");
  130. assert(merged.includes(registry.quickVersion([D,BB,B,X,Y,Z])), "expected [X,Y,Z,B,D] to be a maximal version");
  131. mergeAgain(registry, merged, nameMap);
  132. });
  133. it("Merge many non-conflicting versions (scalability test)", () => {
  134. const getId = mockUuid();
  135. const registry = new VersionRegistry();
  136. // Bunch of non-conflicting deltas:
  137. const deltas: Array<Delta> = [];
  138. const nameMap = new Map();
  139. for (let i=0; i<10; i++) {
  140. const delta = new NodeCreation(getId());
  141. deltas.push(delta);
  142. nameMap.set(delta, i.toString());
  143. }
  144. // Create a version for each delta, containing only that delta:
  145. const versions = deltas.map(d => registry.createVersion(registry.initialVersion, d));
  146. const merged = registry.merge(versions, nameMap);
  147. assert(merged.length === 1, "only one merged version should result");
  148. const mergedAgain = registry.merge(merged, nameMap);
  149. assert(mergedAgain.length === merged.length
  150. && mergedAgain.every(version => merged.includes(version)),
  151. "merging a merge result should just give the same result again.");
  152. });
  153. it("Merge many conflicting versions (scalability test)", () => {
  154. const getId = mockUuid();
  155. const registry = new VersionRegistry();
  156. const HOW_MANY = 3;
  157. const creations: Array<Delta> = [];
  158. const deletions: Array<Delta> = [];
  159. const edges: Array<Delta> = [];
  160. const versions: Array<Version> = [];
  161. const nameMap = new Map();
  162. for (let i=0; i<HOW_MANY; i++) {
  163. const creation = new NodeCreation(getId());
  164. const deletion = new NodeDeletion(creation, [], []);
  165. const edge = new EdgeCreation(creation, "l", creation); // conflicts with deletion0
  166. creations.push(creation);
  167. deletions.push(deletion);
  168. edges.push(edge);
  169. nameMap.set(creation, "C"+i.toString());
  170. nameMap.set(deletion, "D"+i.toString());
  171. nameMap.set(edge, "E"+i.toString());
  172. versions.push(registry.quickVersion([deletion, creation]));
  173. versions.push(registry.quickVersion([edge, creation]));
  174. }
  175. const merged = registry.merge(versions, nameMap);
  176. 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.");
  177. mergeAgain(registry, merged, nameMap);
  178. });
  179. });
  180. // describe("Embedding of versions", () => {
  181. // it("Create embedded versions", () => {
  182. // const getId = mockUuid();
  183. // const registry = new VersionRegistry();
  184. // const lvl1 = new CompositeLevel(); // transactions on CS or CORR
  185. // const lvl2 = new CompositeLevel(); // transactions on CS+CORR
  186. // // CS: a node is created, then deleted
  187. // const csNode = new NodeCreation(getId());
  188. // const csDel = new NodeDeletion(csNode, [], []);
  189. // const csNodeLvl1 = lvl1.createComposite([csNode]);
  190. // const csDelLvl1 = lvl1.createComposite([csDel]);
  191. // const csV1 = registry.createVersion(initialVersion, csNodeLvl1);
  192. // const csV2 = registry.createVersion(csV1, csDelLvl1);
  193. // // CORR: a link is created from a corr-node to the cs-node, followed by the deletion of the corr-node
  194. // // const corrLink = corrLvl.createComposite([csNode, new NodeCreation(getId()), corrLink]);
  195. // const corrNode = new NodeCreation(getId());
  196. // const corrLink = new EdgeCreation(corrNode, "cs", csNode);
  197. // const corrDel = new NodeDeletion(corrNode, [corrLink], []);
  198. // const corrLinkLvl1 = lvl1.createComposite([corrNode, corrLink]);
  199. // const csCorrLinkLvl2 = lvl2.createComposite([csNodeLvl1, corrLinkLvl1]);
  200. // // Patch: in order to avoid a conflict between csDel and corrLink, in corrV2, we override csDel by csDel1.
  201. // const csDel1 = new NodeDeletion(csNode, [], [corrDel]);
  202. // const corrDelLvl1 = lvl1.createComposite([corrDel]);
  203. // const csDel1Lvl1 = lvl1.createComposite([csDel1]);
  204. // const csCorrDelLvl2 = lvl2.createComposite([corrDelLvl1, csDel1Lvl1]);
  205. // const corrV1 = registry.createVersion(initialVersion, csCorrLinkLvl2, embed(["cs", csV1, overrideDeltas()]));
  206. // const corrV2 = registry.createVersion(corrV1, csCorrDelLvl2, embed(["cs", csV2, overrideDeltas( [csDel, csDel1] )]));
  207. // assertThrows(() => {
  208. // // same as above, but without the overridden deltas:
  209. // registry.createVersion(corrV1, csCorrDelLvl2, embed(["cs", csV2, overrideDeltas()])); // should throw
  210. // }, "should not be able to create a version that embeds csV2 without overriding csDel");
  211. // });
  212. // });
  213. });