Path: blob/master/src/packages/backend/conat/test/sync/dkv.test.ts
1451 views
/*1Testing basic ops with dkv23DEVELOPMENT:45pnpm test ./dkv.test.ts67*/89import { dkv as createDkv } from "@cocalc/backend/conat/sync";10import { once } from "@cocalc/util/async-utils";11import { delay } from "awaiting";12import { before, after, connect } from "@cocalc/backend/conat/test/setup";13import { wait } from "@cocalc/backend/conat/test/util";1415beforeAll(before);1617describe("create a public dkv and do basic operations", () => {18let kv;19const name = `test-${Math.random()}`;2021it("creates the dkv", async () => {22kv = await createDkv({ name, noCache: true });23expect(kv.getAll()).toEqual({});24});2526it("adds a key to the dkv", () => {27kv.a = 10;28expect(kv.a).toEqual(10);29});3031it("waits for the dkv to be longterm saved, then closing and recreates the kv and verifies that the key is there.", async () => {32await kv.save();33kv.close();34kv = await createDkv({ name, noCache: true });35expect(kv.a).toEqual(10);36});3738it("closes the kv", async () => {39await kv.clear();40await kv.close();41expect(() => kv.getAll()).toThrow("closed");42});43});4445describe("opens a dkv twice and verifies the cache works and is reference counted", () => {46let kv1;47let kv2;48const name = `test-${Math.random()}`;4950it("creates the same dkv twice", async () => {51kv1 = await createDkv({ name });52kv2 = await createDkv({ name });53expect(kv1.getAll()).toEqual({});54expect(kv1 === kv2).toBe(true);55});5657it("closes kv1 (one reference)", async () => {58await kv1.close();59expect(kv2.getAll).not.toThrow();60});6162it("closes kv2 (another reference)", async () => {63await kv2.close();64await delay(1);65// really closed!66expect(() => kv2.getAll()).toThrow("closed");67});6869it("create and see it is new now", async () => {70kv1 = await createDkv({ name });71expect(kv1 === kv2).toBe(false);72});73});7475describe("opens a dkv twice at once and observe sync", () => {76let kv1;77let kv2;78const name = `test-${Math.random()}`;7980it("creates the dkv twice", async () => {81kv1 = await createDkv({ name, noCache: true, noAutosave: true });82kv2 = await createDkv({ name, noCache: true, noAutosave: true });83expect(kv1.getAll()).toEqual({});84expect(kv2.getAll()).toEqual({});85expect(kv1 === kv2).toBe(false);86});8788it("sets a value in one and sees that it is NOT instantly set in the other", () => {89kv1.a = 25;90expect(kv2.a).toBe(undefined);91});9293it("awaits save and then sees the value *eventually* appears in the other", async () => {94// not initially not there.95expect(kv2.a).toBe(undefined);96await kv1.save();97if (kv2.a === undefined) {98await once(kv2, "change");99}100expect(kv2.a).toBe(kv1.a);101});102103it("close up", async () => {104await kv1.clear();105await kv1.close();106await kv2.clear();107await kv2.close();108});109});110111describe("check server assigned times", () => {112let kv;113const name = `test-${Math.random()}`;114115it("create a kv", async () => {116kv = await createDkv({ name });117expect(kv.getAll()).toEqual({});118expect(kv.time()).toEqual({});119});120121it("set a key, then get the time and confirm it is reasonable", async () => {122kv.a = { b: 7 };123// not serve assigned yet124expect(kv.time("a")).toEqual(undefined);125kv.save();126await once(kv, "change");127// now we must have it.128// sanity check: within a second129expect(kv.time("a").getTime()).toBeCloseTo(Date.now(), -3);130// all the times131expect(Object.keys(kv.time()).length).toBe(1);132});133134it("setting again with a *different* value changes the time", async () => {135kv.a = { b: 8 };136const t0 = kv.time("a");137await once(kv, "change");138expect(kv.time("a").getTime()).toBeCloseTo(Date.now(), -3);139expect(t0).not.toEqual(kv.time("a"));140});141142it("close", async () => {143await kv.clear();144await kv.close();145});146});147148describe("test deleting and clearing a dkv", () => {149let kv1;150let kv2;151152const reset = async () => {153const name = `test-${Math.random()}`;154kv1 = await createDkv({ name, noCache: true });155kv2 = await createDkv({ name, noCache: true });156};157158it("creates the dkv twice without caching so can make sure sync works", async () => {159await reset();160expect(kv1.getAll()).toEqual({});161expect(kv2.getAll()).toEqual({});162expect(kv1 === kv2).toBe(false);163});164165it("adds an entry, deletes it and confirms", async () => {166kv1.foo = "bar";167expect(kv1.has("foo")).toBe(true);168expect(kv2.has("foo")).toBe(false);169await once(kv2, "change");170expect(kv2.foo).toBe(kv1.foo);171expect(kv2.has("foo")).toBe(true);172delete kv1.foo;173await once(kv2, "change");174expect(kv2.foo).toBe(undefined);175expect(kv2.has("foo")).toBe(false);176});177178it("adds an entry, clears it and confirms", async () => {179await reset();180181kv1.foo10 = "bar";182await once(kv2, "change");183expect(kv2.foo10).toBe(kv1.foo10);184kv2.clear();185expect(kv2.has("foo10")).toBe(false);186await once(kv1, "change");187while (kv1.has("foo10")) {188await once(kv1, "change");189}190expect(kv1.has("foo10")).toBe(false);191});192193it("adds an entry, syncs, adds another local entry (not sync'd), clears in sync and confirms NOT everything was cleared", async () => {194await reset();195kv1["foo"] = Math.random();196await kv1.save();197if (kv2["foo"] != kv1["foo"]) {198await once(kv2, "change");199}200expect(kv2["foo"]).toBe(kv1["foo"]);201kv1["bar"] = "yyy";202expect(kv2["bar"]).toBe(undefined);203// this ONLY clears 'foo', not 'bar'204kv2.clear();205await once(kv1, "change");206expect(kv1.has("bar")).toBe(true);207});208209it("adds an entry, syncs, adds another local entry (not sync'd), clears in first one, and confirms everything was cleared", async () => {210await reset();211212const key = Math.random();213kv1[key] = Math.random();214await kv1.save();215if (kv2[key] != kv1[key]) {216await once(kv2, "change");217}218const key2 = Math.random();219kv1[key2] = "yyy";220expect(kv2[key2]).toBe(undefined);221// this ONLY clears foo, not xxx222kv1.clear();223expect(kv1.has(key2)).toBe(false);224});225226it("close", async () => {227await kv1.clear();228await kv1.close();229await kv2.clear();230await kv2.close();231});232});233234describe("set several items, confirm write worked, save, and confirm they are still there after save", () => {235const name = `test-${Math.random()}`;236const count = 50;237// the time thresholds should be "trivial"238it(`adds ${count} entries`, async () => {239const kv = await createDkv({ name });240expect(kv.getAll()).toEqual({});241const obj: any = {};242const t0 = Date.now();243for (let i = 0; i < count; i++) {244obj[`${i}`] = i;245kv.set(`${i}`, i);246}247expect(Date.now() - t0).toBeLessThan(50);248expect(Object.keys(kv.getAll()).length).toEqual(count);249expect(kv.getAll()).toEqual(obj);250await kv.save();251expect(Date.now() - t0).toBeLessThan(2000);252await wait({ until: () => Object.keys(kv.getAll()).length == count });253expect(Object.keys(kv.getAll()).length).toEqual(count);254// // the local state maps should also get cleared quickly,255// // but there is no event for this, so we loop:256// @ts-ignore: saved is private257while (Object.keys(kv.local).length > 0) {258await delay(10);259}260// @ts-ignore: local is private261expect(kv.local).toEqual({});262// @ts-ignore: saved is private263expect(kv.saved).toEqual({});264265await kv.clear();266await kv.close();267});268});269270describe("do an insert and clear test", () => {271const name = `test-${Math.random()}`;272const count = 25;273it(`adds ${count} entries, saves, clears, and confirms empty`, async () => {274const kv = await createDkv({ name });275expect(kv.getAll()).toEqual({});276for (let i = 0; i < count; i++) {277kv[`${i}`] = i;278}279expect(Object.keys(kv.getAll()).length).toEqual(count);280await kv.save();281await wait({ until: () => Object.keys(kv.getAll()).length == count });282expect(Object.keys(kv.getAll()).length).toEqual(count);283kv.clear();284expect(kv.getAll()).toEqual({});285await kv.save();286await wait({ until: () => Object.keys(kv.getAll()).length == 0 });287expect(kv.getAll()).toEqual({});288});289});290291describe("create many distinct clients at once, write to all of them, and see that that results are merged", () => {292const name = `test-${Math.random()}`;293const count = 4;294const clients: any[] = [];295296it(`creates the ${count} clients`, async () => {297for (let i = 0; i < count; i++) {298clients[i] = await createDkv({ name, noCache: true });299}300});301302// what the combination should be303let combined: any = {};304it("writes a separate key/value for each client", () => {305for (let i = 0; i < count; i++) {306clients[i].set(`${i}`, i);307combined[`${i}`] = i;308expect(clients[i].get(`${i}`)).toEqual(i);309}310});311312it("saves and checks that everybody has the combined values", async () => {313for (const kv of clients) {314await kv.save();315}316let done = false;317let i = 0;318while (!done && i < 50) {319done = true;320i += 1;321for (const client of clients) {322if (client.length != count) {323done = false;324await delay(10);325break;326}327}328}329for (const client of clients) {330expect(client.length).toEqual(count);331expect(client.getAll()).toEqual(combined);332}333});334335it("close", async () => {336for (const client of clients) {337await client.clear();338await client.close();339}340});341});342343describe("tests involving null/undefined values and delete", () => {344let kv1;345let kv2;346const name = `test-${Math.round(100 * Math.random())}`;347348it("creates the dkv twice", async () => {349kv1 = await createDkv({ name, noAutosave: true, noCache: true });350kv2 = await createDkv({ name, noAutosave: true, noCache: true });351expect(kv1.getAll()).toEqual({});352expect(kv1 === kv2).toBe(false);353});354355// it("sets a value to null, which is fully supported like any other value", () => {356// kv1.a = null;357// expect(kv1.a).toBe(null);358// expect(kv1.a === null).toBe(true);359// expect(kv1.length).toBe(1);360// });361362it("make sure null value sync's as expected", async () => {363kv1.a = null;364await kv1.save();365await wait({ until: () => kv2.has("a") });366expect(kv2.a).toBe(null);367expect(kv2.a === null).toBe(true);368expect(kv2.length).toBe(1);369});370371it("sets a value to undefined, which is the same as deleting a value", () => {372kv1.a = undefined;373expect(kv1.has("a")).toBe(false);374expect(kv1.a).toBe(undefined);375expect(kv1.a === undefined).toBe(true);376expect(kv1.length).toBe(0);377expect(kv1.getAll()).toEqual({});378});379380it("make sure undefined (i.e., delete) sync's as expected", async () => {381await kv1.save();382await wait({ until: () => kv2.a === undefined });383expect(kv2.a).toBe(undefined);384expect(kv2.a === undefined).toBe(true);385expect(kv2.length).toBe(0);386expect(kv1.getAll()).toEqual({});387});388389it("makes sure using '' as a key works", async () => {390kv1.set("", 10);391expect(kv1.get("")).toEqual(10);392await kv1.save();393if (kv2.get("") != 10) {394await once(kv2, "change");395}396expect(kv2.get("")).toEqual(10);397});398399it("close", async () => {400await kv1.clear();401await kv1.close();402await kv2.clear();403await kv2.close();404});405});406407describe("ensure there isn't a really obvious subscription leak", () => {408let client;409410it("create a client, which initially has only one subscription (the inbox)", async () => {411client = connect();412await client.getInbox();413expect(client.numSubscriptions()).toBe(1);414});415416const count = 10;417it(`creates and closes ${count} streams and checks there is no leak`, async () => {418const before = client.numSubscriptions();419// create420const a: any = [];421for (let i = 0; i < count; i++) {422a[i] = await createDkv({423name: `${Math.random()}`,424noAutosave: true,425noCache: true,426});427}428// NOTE: in fact there's very unlikely to be a subscription leak, since429// dkv's don't use new subscriptions -- they all use requestMany instead430// to a common inbox prefix, and there's just one subscription for an inbox.431expect(client.numSubscriptions()).toEqual(before);432for (let i = 0; i < count; i++) {433await a[i].close();434}435const after = client.numSubscriptions();436expect(after).toBe(before);437438// also check count on server went down.439expect((await client.getSubscriptions()).size).toBe(before);440});441442it("does another leak test, but with a set operation each time", async () => {443const before = client.numSubscriptions();444// create445const a: any = [];446for (let i = 0; i < count; i++) {447a[i] = await createDkv({448name: `${Math.random()}`,449noAutosave: true,450noCache: true,451});452a[i].set(`${i}`, i);453await a[i].save();454}455// this isn't going to be a problem:456expect(client.numSubscriptions()).toEqual(before);457for (let i = 0; i < count; i++) {458await a[i].close();459}460const after = client.numSubscriptions();461expect(after).toBe(before);462});463});464465describe("test creating and closing a dkv doesn't leak subscriptions", () => {466let client;467let kv;468let name = "sub.count";469let subs;470471it("make a new client and count subscriptions", async () => {472client = connect();473await once(client, "connected");474await client.getInbox();475subs = client.numSubscriptions();476expect(subs).toBe(1); // the inbox477});478479it("creates dkv", async () => {480kv = await createDkv({ name });481kv.set("x", 5);482await wait({ until: () => kv.length == 1 });483});484485it("close the kv and confirm subs returns to 1", async () => {486kv.close();487await expect(() => {488client.numSubscriptions() == 1;489});490});491});492493afterAll(after);494495496