Kaynağa Gözat

WIP: A correspondence model version 'embeds' one CS and one AS version.

Joeri Exelmans 2 yıl önce
ebeveyn
işleme
a61a4464c8

+ 91 - 0
src/onion/composite_delta.test.ts

@@ -3,6 +3,8 @@ import * as _ from "lodash";
 import {
   NodeCreation,
   NodeDeletion,
+  EdgeCreation,
+  EdgeUpdate,
 } from "./primitive_delta";
 
 import {
@@ -52,6 +54,46 @@ describe("Composite delta", () => {
     assert(_.isEqual(compositeDelete2.getConflicts(), [compositeDelete1]), "expected compositeDelete1 to conflict with compositeDelete2");
   });
 
+  it("Conflict, but no composite yet", () => {
+    const getId = mockUuid();
+    const level = new CompositeLevel();
+
+    // Same as above, but now we first create all the L0 deltas:
+    const nodeCreation = new NodeCreation(getId());
+    const nodeDeletion1 = new NodeDeletion(nodeCreation, [], []);
+    const nodeDeletion2 = new NodeDeletion(nodeCreation, [], []);
+
+    // And then the L1 deltas:
+    const compositeCreate = level.createComposite([nodeCreation]);
+    const compositeDelete1 = level.createComposite([nodeDeletion1]);
+    assert(_.isEqual(compositeDelete1.getConflicts(), []), "there should not yet be a conflict")
+    const compositeDelete2 = level.createComposite([nodeDeletion2]);
+
+    assert(_.isEqual(compositeDelete1.getConflicts(), [compositeDelete2]), "expected compositeDelete1 to conflict with compositeDelete2");
+    assert(_.isEqual(compositeDelete2.getConflicts(), [compositeDelete1]), "expected compositeDelete1 to conflict with compositeDelete2");
+  });
+
+  it("Conflicting deltas cannot be part of the same composite", () => {
+    const getId = mockUuid();
+    const level = new CompositeLevel();
+
+    const nodeCreation = new NodeCreation(getId());
+    const nodeDeletion1 = new NodeDeletion(nodeCreation, [], []);
+    const nodeDeletion2 = new NodeDeletion(nodeCreation, [], []);
+
+    let threw = false;
+    try {
+      level.createComposite([nodeCreation, nodeDeletion1, nodeDeletion2]); // should throw
+    } catch (e) {
+      threw = true;
+      assert(e.toString() === "Error: Cannot create a composite delta out of conflicting deltas.", "Wrong error thrown");
+    }
+
+    if (!threw) {
+      throw new Error("Failed!");
+    }
+  })
+
   // This was added after I found a bug:
   it("Dependency within composite", () => {
     const getId = mockUuid();
@@ -65,4 +107,53 @@ describe("Composite delta", () => {
     assert(_.isEqual(composite.getDependencies(), []), "expected composite to have no dependencies");
     assert(_.isEqual(composite.getConflicts(), []), "expected composite to have no conflicts");
   });
+
+  it("Multi-level", () => {
+    const getId = mockUuid();
+
+    const lvl1 = new CompositeLevel(); // transactions on CS or CORR
+    const lvl2 = new CompositeLevel(); // transactions on CS+CORR
+
+    // CS: a node is created, then deleted
+    const csNode = new NodeCreation(getId());
+    const csDel = new NodeDeletion(csNode, [], []);
+
+    const csNodeLvl1 = lvl1.createComposite([csNode]);
+    const csDelLvl1 = lvl1.createComposite([csDel]);
+
+    assert(csNodeLvl1.getConflicts().length === 0, "did not expect any conflicts so far");
+    assert(csDelLvl1.getConflicts().length === 0, "did not expect any conflicts so far");
+
+    // CORR: a link is created from a corr-node to the cs-node, followed by the deletion of the corr-node
+    // const corrLink = corrLvl.createComposite([csNode, new NodeCreation(getId()), corrLink]);
+    const corrNode = new NodeCreation(getId());
+    const corrLink = new EdgeCreation(corrNode, "cs", csNode);
+    const corrDel = new NodeDeletion(corrNode, [corrLink], []);
+
+    assert(_.isEqual(corrLink.getConflicts(), [csDel]), "expected corrLink to conflict with csDel");
+    assert(_.isEqual(csDel.getConflicts(), [corrLink]), "expected csDel to conflict with corrLink");
+
+    const corrLinkLvl1 = lvl1.createComposite([corrNode, corrLink]);
+
+    assert(_.isEqual(corrLinkLvl1.getConflicts(), [csDelLvl1]), "expected corrLinkLvl1 to conflict with csDelLvl1");
+    assert(_.isEqual(csDelLvl1.getConflicts(), [corrLinkLvl1]), "expected csDelLvl1 to conflict with corrLinkLvl1");
+
+    const csCorrLinkLvl2 = lvl2.createComposite([csNodeLvl1, corrLinkLvl1]);
+
+    assert(csCorrLinkLvl2.getConflicts().length === 0, "expected no conflicts here");
+
+    // Patch: in order to avoid a conflict between csDel and corrLink, in corrV2, we override csDel by csDel1.
+    const csDel1 = new NodeDeletion(csNode, [], [corrDel]);
+
+    assert(_.isEqual(csDel1.getConflicts(), [csDel]), "expected csDel1 to only conflict with csDel");
+
+    const corrDelLvl1 = lvl1.createComposite([corrDel]);
+    const csDel1Lvl1 = lvl1.createComposite([csDel1]);
+
+    assert(_.isEqual(csDel1Lvl1.getConflicts(), [csDelLvl1]), "expected csDel1Lvl1 to conflict with csDelLvl1");
+
+    const csCorrDelLvl2 = lvl2.createComposite([corrDelLvl1, csDel1Lvl1]);
+
+    assert(csCorrDelLvl2.getConflicts().length === 0, "expected no conflicts here");
+  });
 });

+ 11 - 5
src/onion/composite_delta.ts

@@ -71,7 +71,7 @@ export class CompositeLevel {
           // We got ourselves an inter-composite dependency.
           const compositeDependency = this.containedBy.get(dependency);
           if (compositeDependency === undefined) {
-            throw new Error("Assertion failed: cannot find composite of " + dependency.constructor.name);
+            throw new Error("Assertion failed: cannot find composite of " + dependency.getDescription());
           }
           const existingDependency = typedDependencies.find(([dep,_]) => dep === compositeDependency);
           if (existingDependency !== undefined) {
@@ -83,12 +83,18 @@ export class CompositeLevel {
         }
       }
       for (const conflict of delta.getConflicts()) {
+        if (deltas.includes(conflict)) {
+          throw new Error("Cannot create a composite delta out of conflicting deltas.");
+        }
         const compositeConflict = this.containedBy.get(conflict);
         if (compositeConflict === undefined) {
-          throw new Error("Assertion failed: cannot find composite of " + conflict.constructor.name);
-        }
-        if (!conflicts.includes(compositeConflict)) {
-          conflicts.push(compositeConflict);
+          // We used to treat this as an error, however, it's possible that a conflicting delta simply isn't part yet of a composite delta...
+          
+          // throw new Error("Assertion failed: cannot find composite of " + conflict.getDescription());
+        } else {
+          if (!conflicts.includes(compositeConflict)) {
+            conflicts.push(compositeConflict);
+          }
         }
       }
     }

+ 3 - 3
src/onion/primitive_delta.test.ts

@@ -18,7 +18,7 @@ import {
 } from "../util/assert";
 
 
-describe("Delta", () => {
+describe("Primitive Delta", () => {
 
   it("Delete/delete node conflict", () => {
     const getId = mockUuid();
@@ -188,7 +188,7 @@ describe("Delta", () => {
     // because of the edgeUpdate, 'target' is no longer the target of the edge, and can be deleted:
     const targetDeletion = new NodeDeletion(targetCreation, [], [edgeUpdate]);
 
-    console.log(edgeCreation.getConflicts())
+    // console.log(edgeCreation.getConflicts())
     assert(edgeCreation.getConflicts().length === 0, "expected no require/delete conflict");
     assert(targetDeletion.getConflicts().length === 0, "expected no require/delete conflict");
   });
@@ -216,7 +216,7 @@ describe("Delta", () => {
     const edgeUpdate = new EdgeUpdate(edgeCreation, null);
     const nodeDeletion = new NodeDeletion(nodeCreation, [edgeUpdate], [edgeUpdate]);
 
-    console.log(nodeDeletion.getConflicts());
+    // console.log(nodeDeletion.getConflicts());
     assert(nodeDeletion.getConflicts().length === 0, "expected no conflicts");
   })
 });

+ 36 - 10
src/onion/version.test.ts

@@ -17,6 +17,10 @@ import {
   EdgeUpdate,
 } from "./primitive_delta";
 
+import {
+  CompositeLevel,
+} from "./composite_delta"
+
 import {
   mockUuid,
 } from "./test_helpers";
@@ -229,22 +233,44 @@ describe("Version", () => {
     });
   });
 
-
-  describe("Containment", () => {
-    it("", () => {
+  describe("Embedding of versions", () => {
+    it("Create embedded versions", () => {
       const getId = mockUuid();
       const registry = new VersionRegistry();
 
-      const cs_node = new NodeCreation(getId());
+      const lvl1 = new CompositeLevel(); // transactions on CS or CORR
+      const lvl2 = new CompositeLevel(); // transactions on CS+CORR
+
+      // CS: a node is created, then deleted
+      const csNode = new NodeCreation(getId());
+      const csDel = new NodeDeletion(csNode, [], []);
+
+      const csNodeLvl1 = lvl1.createComposite([csNode]);
+      const csDelLvl1 = lvl1.createComposite([csDel]);
+
+      const csV1 = registry.createVersion(initialVersion, csNodeLvl1);
+      const csV2 = registry.createVersion(csV1, csDelLvl1);
 
-      const corr_node = new NodeCreation(getId());
-      const corr_link = new EdgeCreation(corr_node, "cs", cs_node);
+      // CORR: a link is created from a corr-node to the cs-node, followed by the deletion of the corr-node
+      // const corrLink = corrLvl.createComposite([csNode, new NodeCreation(getId()), corrLink]);
+      const corrNode = new NodeCreation(getId());
+      const corrLink = new EdgeCreation(corrNode, "cs", csNode);
+      const corrDel = new NodeDeletion(corrNode, [corrLink], []);
 
-      const cs_version = registry.quickVersion([cs_node]);
-      const corr_version = registry.quickVersion([corr_link, corr_node, cs_node]);
+      const corrLinkLvl1 = lvl1.createComposite([corrNode, corrLink]);
 
-      
+      const csCorrLinkLvl2 = lvl2.createComposite([csNodeLvl1, corrLinkLvl1]);
+
+      // Patch: in order to avoid a conflict between csDel and corrLink, in corrV2, we override csDel by csDel1.
+      const csDel1 = new NodeDeletion(csNode, [], [corrDel]);
+
+      const corrDelLvl1 = lvl1.createComposite([corrDel]);
+      const csDel1Lvl1 = lvl1.createComposite([csDel1]);
+
+      const csCorrDelLvl2 = lvl2.createComposite([corrDelLvl1, csDel1Lvl1]);
+
+      const corrV1 = registry.createVersion(initialVersion, csCorrLinkLvl2);
+      const corrV2 = registry.createVersion(corrV1, csCorrDelLvl2);
     });
   });
-
 });

+ 12 - 0
src/onion/version.ts

@@ -13,6 +13,18 @@ import {
 
 import {bufferXOR} from "./buffer_xor";
 
+// Wrapper class, each time a version is embedded into another version, an instance of this class is contructed.
+// More precisely, if a version is embedded by N other versions, then N instances of EmbeddedVersion are constructed.
+export interface EmbeddedVersion {
+  // The Version that is embedded:
+  embedded: Version;
+
+  // Mapping from embedded-version-deltas to embedding-version-deltas:
+  overriddenDeltas: Map<Delta, Delta>;
+}
+
+export type EmbeddedVersionMap = Map<string, EmbeddedVersion>;
+
 
 // A typed link from one version to another.
 // There are two types: 'p' (parent) and 'c' (child)