Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
JanLewDev committed Jul 30, 2024
2 parents eb2a3bb + 7ebcbd1 commit f9d301f
Show file tree
Hide file tree
Showing 11 changed files with 501 additions and 129 deletions.
14 changes: 6 additions & 8 deletions packages/crdt/src/builtins/GCounter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,7 @@ export class GCounter {
}

compare(peerCounter: GCounter): boolean {
for (let key in Object.keys(this.counts)) {
if (this.counts[key] > peerCounter.counts[key]) {
return false;
}
}
return true;
return (this.counts.length === peerCounter.counts.length && Object.keys(this.counts).every(key => this.counts[key] <= peerCounter.counts[key]));
}

merge(peerCounter: GCounter): void {
Expand All @@ -34,8 +29,11 @@ export class GCounter {
this.counts,
peerCounter.counts,
);

Object.keys(temp).forEach((key) => {
this.counts[key] = Math.max(this.counts[key], peerCounter.counts[key]);
this.counts[key] = Math.max(this.counts[key] || 0, peerCounter.counts[key] || 0);
});

this.globalCounter = Object.values(this.counts).reduce((a, b) => a + b, 0);
}
}
}
4 changes: 2 additions & 2 deletions packages/crdt/src/builtins/GSet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export class GSet<T> {
}

compare(peerSet: GSet<T>): boolean {
return this._set === peerSet.set();
return (this._set.size == peerSet.set().size && [...this._set].every(value => peerSet.set().has(value)));
}

merge(peerSet: GSet<T>): void {
this._set = new Set<T>([...this._set, ...peerSet.set()]);
}
}
}
75 changes: 75 additions & 0 deletions packages/crdt/src/builtins/LWWElementSet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
export enum Bias {
ADD,
REMOVE
}

export class LWWElementSet<T> {
private _adds: Map<T, number>;
private _removes: Map<T, number>;
public _bias: Bias;

constructor(adds: Map<T, number>, removes: Map<T, number>, bias: Bias) {
this._adds = adds;
this._removes = removes;
this._bias = bias;
}

lookup(element: T): boolean {
const addTimestamp = this._adds.get(element);
if(addTimestamp === undefined) {
return false;
}

const removeTimestamp = this._removes.get(element);
if (removeTimestamp === undefined) {
return true;
}
if (addTimestamp > removeTimestamp) {
return true;
}
if (addTimestamp - removeTimestamp === 0 && this._bias === Bias.ADD) {
return true;
}

return false;
}

add(element: T): void {
this._adds.set(element, Date.now());
}

remove(element: T): void {
this._removes.set(element, Date.now());
}

getAdds(): Map<T, number> {
return this._adds;
}

getRemoves(): Map<T, number> {
return this._removes;
}

compare(peerSet: LWWElementSet<T>): boolean {
return (compareSets(this._adds, peerSet._adds) && compareSets(this._removes, peerSet._removes));
}

merge(peerSet: LWWElementSet<T>): void {
for (let [element, timestamp] of peerSet._adds.entries()) {
const thisTimestamp = this._adds.get(element);
if (!thisTimestamp || thisTimestamp < timestamp) {
this._adds.set(element, timestamp);
}
}
for (let [element, timestamp] of peerSet._removes.entries()) {
const thisTimestamp = this._removes.get(element);
if (!thisTimestamp || thisTimestamp < timestamp) {
this._removes.set(element, timestamp);
}
}
}
}

function compareSets<T>(set1: Map<T, number>, set2: Map<T, number>): boolean {
return (set1.size === set2.size && [...set1.keys()].every(key => set2.has(key)));
}
42 changes: 42 additions & 0 deletions packages/crdt/tests/GCounter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, test, expect, beforeEach } from "vitest";
import { GCounter } from "../src/builtins/GCounter";

describe("G-Counter Tests", () => {
let set1: GCounter;

beforeEach(() => {
set1 = new GCounter({ "node1": 5, "node2": 10});
});

test("Test Initial Values", () => {
expect(set1.value()).toBe(15);
});

test("Test Increment", () => {
set1.increment("node1", 10);
set1.increment("node2", 5);

expect(set1.value()).toBe(30);
});

test("Test Compare", () => {
let set2 = new GCounter({ "node1": 5, "node2": 10});
let set3 = new GCounter({ "node1": 5, "node2": 10, "node3": 15 });

expect(set1.compare(set2)).toBe(true);
set1.increment("node1", 5);
expect(set1.compare(set2)).toBe(false);
expect(set1.compare(set3)).toBe(false);
});

test("Test Merge", () => {
let set2 = new GCounter({ "node1": 3, "node2": 10});
let set3 = new GCounter({ "node1": 5, "node3": 15});

expect(set1.counts).toEqual({"node1": 5, "node2": 10});
set2.merge(set1);
expect(set2.counts).toEqual({"node1": 5, "node2": 10});
set1.merge(set3);
expect(set1.counts).toEqual({"node1": 5, "node2": 10, "node3": 15});
});
});
46 changes: 46 additions & 0 deletions packages/crdt/tests/GSet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, test, expect, beforeEach, afterEach } from "vitest";
import { GSet } from "../src/builtins/GSet";

describe("G-Set Tests", () => {

let set1: GSet<string>;
let set2: GSet<string>;

beforeEach(() => {
set1 = new GSet<string>(new Set<string>(["walter", "jesse", "mike"]));
set2 = new GSet<string>(new Set<string>(["walter", "jesse", "mike"]));
});

test("Test Add", () => {
set1.add("gustavo");
set2.add("gustavo");

expect(set1.lookup("gustavo")).toBe(true);
expect(set2.lookup("gustavo")).toBe(true);
});

test("Test Compare", () => {
expect(set1.compare(set2)).toBe(true);

set1.add("gustavo");

expect(set1.compare(set2)).toBe(false);

set2.add("gustavo");

expect(set1.compare(set2)).toBe(true);
});

test("Test Merge", () => {
set1.add("gustavo");
set2.add("lalo");

expect(set1.compare(set2)).toBe(false);

set1.merge(set2);
set2.merge(set1);

expect(set1.compare(set2)).toBe(true);

});
});
90 changes: 90 additions & 0 deletions packages/crdt/tests/LWWElementSet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, test, expect, beforeEach } from "vitest";
import { LWWElementSet, Bias } from "../src/builtins/LWWElementSet";

describe("LWW-Element-Set Tests", () => {
const testValues = ["walter", "jesse", "mike"];

let set1: LWWElementSet<string>;
let set2: LWWElementSet<string>;
let set3: LWWElementSet<string>;

beforeEach(() => {
set1 = new LWWElementSet<string>(new Map(), new Map(), Bias.ADD);
set2 = new LWWElementSet<string>(new Map(), new Map(), Bias.ADD);
set3 = new LWWElementSet<string>(new Map(), new Map(), Bias.REMOVE);

testValues.forEach((value) => {
set1.add(value);
set2.add(value);
set3.add(value);
});
});

test("Test Add Elements", () => {
expect(set1.lookup("gustavo")).toBe(false);

set1.add("gustavo");
expect(set1.lookup("gustavo")).toBe(true);
});

test("Test Remove Elements", () => {
expect(set1.lookup("mike")).toBe(true);

set1.getRemoves().set("mike", Date.now() + 1);

expect(set1.lookup("mike")).toBe(false);
});

test("Test Compare Sets", () => {
expect(set1.compare(set2)).toBe(true);
expect(set1.compare(set3)).toBe(true);
expect(set3.compare(set2)).toBe(true);

set1.remove("jesse");

expect(set1.compare(set2)).toBe(false);
expect(set1.compare(set3)).toBe(false);
expect(set3.compare(set2)).toBe(true);
});

describe("Test Merge Elements" , () => {
test("Merge Sets", () => {
// Adding different names to each set
set1.add("gustavo");
set2.add("saul");

expect(set1.compare(set2)).toBe(false);

set1.merge(set2);
set2.merge(set1);

expect(set1.compare(set2)).toBe(true);
});

test("Same Element, different Timestamps", () => {
const timestamp = Date.now();
set1.getAdds().set("gustavo", timestamp);
set2.getAdds().set("gustavo", timestamp + 5);

expect(set1.getAdds().get("gustavo")).toBe(timestamp);

set1.merge(set2);
set2.merge(set1);

expect(set1.getAdds().get("gustavo")).toBe(timestamp + 5);
expect(set2.getAdds().get("gustavo")).toBe(timestamp + 5);
});

test("Merge Removal Timestamps", () => {
const timestamp = Date.now();

set1.getAdds().set("gustavo", timestamp);
set2.getRemoves().set("gustavo", timestamp + 5);

set1.merge(set2);

expect(set1.lookup("gustavo")).toBe(false);
expect(set1.getRemoves().get("gustavo")).toBe(timestamp + 5);
});
});
});
51 changes: 51 additions & 0 deletions packages/crdt/tests/PNCounter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, test, expect, beforeEach, afterEach } from "vitest";
import { PNCounter } from "../src/builtins/PNCounter";
import { GCounter } from "../src/builtins/GCounter";

describe("PN-Counter Tests", () => {
let set1: PNCounter;
let set2: PNCounter;

beforeEach(() => {
set1 = new PNCounter(new GCounter({ "node1": 5, "node2": 10, "node3": 15 }), new GCounter({ "node1": 3, "node2": 4, "node3": 3 }));
set2 = new PNCounter(new GCounter({ "node1": 5, "node2": 10, "node3": 15 }), new GCounter({ "node1": 3, "node2": 4, "node3": 3 }));
});

test("Test Initial Value", () => {
expect(set1.value()).toBe(20);
expect(set2.value()).toBe(20);
});

test("Test Increment", () => {
set1.increment("node1",10);
set2.increment("node1",20);
expect(set1.value()).toBe(30);
expect(set2.value()).toBe(40);
});

test("Test Decrement", () => {
set1.decrement("node1",10);
set2.decrement("node1",20);
expect(set1.value()).toBe(10);
expect(set2.value()).toBe(0);
});

test("Test Compare", () => {
expect(set1.compare(set2)).toBe(true);
set1.decrement("node1",10);
expect(set1.compare(set2)).toBe(false);
set2.decrement("node1",10);
expect(set1.compare(set2)).toBe(true);
});

test("Test Merge", () => {
set1.increment("node1",10);
set2.decrement("node2",5);
expect(set1.compare(set2)).toBe(false);
expect(set2.compare(set1)).toBe(false);
set1.merge(set2);
set2.merge(set1);
expect(set1.compare(set2)).toBe(true);
expect(set2.compare(set1)).toBe(true);
});
});
Loading

0 comments on commit f9d301f

Please sign in to comment.