1
0
silverbullet/common/spaces/sync.test.ts

217 lines
6.0 KiB
TypeScript

import { removeDirectiveBody, SpaceSync, SyncStatusItem } from "./sync.ts";
import { DiskSpacePrimitives } from "./disk_space_primitives.ts";
import { assertEquals } from "../../test_deps.ts";
Deno.test("Test store", async () => {
const primaryPath = await Deno.makeTempDir();
const secondaryPath = await Deno.makeTempDir();
console.log("Primary", primaryPath);
console.log("Secondary", secondaryPath);
const primary = new DiskSpacePrimitives(primaryPath);
const secondary = new DiskSpacePrimitives(secondaryPath);
const snapshot = new Map<string, SyncStatusItem>();
const sync = new SpaceSync(primary, secondary, {
conflictResolver: SpaceSync.primaryConflictResolver,
});
// Write one page to primary
await primary.writeFile("index", stringToBytes("Hello"));
assertEquals((await secondary.fetchFileList()).length, 0);
console.log("Initial sync ops", await doSync());
assertEquals((await secondary.fetchFileList()).length, 1);
assertEquals(
(await secondary.readFile("index")).data,
stringToBytes("Hello"),
);
// Should be a no-op
assertEquals(await doSync(), 0);
// Now let's make a change on the secondary
await secondary.writeFile("index", stringToBytes("Hello!!"));
await secondary.writeFile("test", stringToBytes("Test page"));
// And sync it
await doSync();
assertEquals((await primary.fetchFileList()).length, 2);
assertEquals((await secondary.fetchFileList()).length, 2);
assertEquals(
(await primary.readFile("index")).data,
stringToBytes("Hello!!"),
);
// Let's make some random edits on both ends
await primary.writeFile("index", stringToBytes("1"));
await primary.writeFile("index2", stringToBytes("2"));
await secondary.writeFile("index3", stringToBytes("3"));
await secondary.writeFile("index4", stringToBytes("4"));
await doSync();
assertEquals((await primary.fetchFileList()).length, 5);
assertEquals((await secondary.fetchFileList()).length, 5);
assertEquals(await doSync(), 0);
console.log("Deleting pages");
// Delete some pages
await primary.deleteFile("index");
await primary.deleteFile("index3");
await doSync();
assertEquals((await primary.fetchFileList()).length, 3);
assertEquals((await secondary.fetchFileList()).length, 3);
// No-op
assertEquals(await doSync(), 0);
await secondary.deleteFile("index4");
await primary.deleteFile("index2");
await doSync();
// Just "test" left
assertEquals((await primary.fetchFileList()).length, 1);
assertEquals((await secondary.fetchFileList()).length, 1);
// No-op
assertEquals(await doSync(), 0);
await secondary.writeFile("index", stringToBytes("I'm back"));
await doSync();
assertEquals(
(await primary.readFile("index")).data,
stringToBytes("I'm back"),
);
// Cause a conflict
console.log("Introducing a conflict now");
await primary.writeFile("index", stringToBytes("Hello 1"));
await secondary.writeFile("index", stringToBytes("Hello 2"));
await doSync();
// Sync conflicting copy back
await doSync();
// Verify that primary won
assertEquals(
(await primary.readFile("index")).data,
stringToBytes("Hello 1"),
);
assertEquals(
(await secondary.readFile("index")).data,
stringToBytes("Hello 1"),
);
// test + index + index.conflicting copy
assertEquals((await primary.fetchFileList()).length, 3);
assertEquals((await secondary.fetchFileList()).length, 3);
// Introducing a fake conflict (same content, so not really conflicting)
await primary.writeFile("index", stringToBytes("Hello 1"));
await secondary.writeFile("index", stringToBytes("Hello 1"));
// And two more files with different bodies, but only within a query directive — shouldn't conflict
await primary.writeFile(
"index.md",
stringToBytes(
"Hello\n<!-- #query page -->\nHello 1\n<!-- /query -->",
),
);
await secondary.writeFile(
"index.md",
stringToBytes("Hello\n<!-- #query page -->\nHello 2\n<!-- /query -->"),
);
await doSync();
await doSync();
// test + index + index.md + previous index.conflicting copy but nothing more
assertEquals((await primary.fetchFileList()).length, 4);
console.log("Bringing a third device in the mix");
const ternaryPath = await Deno.makeTempDir();
console.log("Ternary", ternaryPath);
const ternary = new DiskSpacePrimitives(ternaryPath);
const sync2 = new SpaceSync(
secondary,
ternary,
{
conflictResolver: SpaceSync.primaryConflictResolver,
},
);
const snapshot2 = new Map<string, SyncStatusItem>();
console.log(
"N ops",
await sync2.syncFiles(snapshot2),
);
await sleep(2);
assertEquals(await sync2.syncFiles(snapshot2), 0);
// I had to look up what follows ternary (https://english.stackexchange.com/questions/25116/what-follows-next-in-the-sequence-unary-binary-ternary)
const quaternaryPath = await Deno.makeTempDir();
const quaternary = new DiskSpacePrimitives(quaternaryPath);
const sync3 = new SpaceSync(
secondary,
quaternary,
{
isSyncCandidate: (path) => !path.startsWith("index"),
conflictResolver: SpaceSync.primaryConflictResolver,
},
);
const selectingOps = await sync3.syncFiles(new Map());
assertEquals(selectingOps, 1);
await Deno.remove(primaryPath, { recursive: true });
await Deno.remove(secondaryPath, { recursive: true });
await Deno.remove(ternaryPath, { recursive: true });
await Deno.remove(quaternaryPath, { recursive: true });
async function doSync() {
await sleep();
const r = await sync.syncFiles(snapshot);
await sleep();
return r;
}
});
function sleep(ms = 10): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
Deno.test("Remove directive bodies", () => {
assertEquals(
removeDirectiveBody(`<!-- #query page -->
This is a body
bla bla bla
<!-- /query -->
Hello
<!-- #include [[test]] -->
This is a body
<!-- /include -->
`),
`<!-- #query page -->
<!-- /query -->
Hello
<!-- #include [[test]] -->
<!-- /include -->
`,
);
});
function stringToBytes(s: string): Uint8Array {
return new TextEncoder().encode(s);
}