Format & lint

This commit is contained in:
Andras Schmelczer 2026-04-25 17:55:46 +01:00
parent fefac224b0
commit 7f62273e72
179 changed files with 2210 additions and 1319 deletions

View file

@ -7,4 +7,4 @@
"**/.sqlx": true, "**/.sqlx": true,
"**/target": true "**/target": true
} }
} }

View file

@ -17,20 +17,25 @@ All tests run in parallel up to a concurrency limit.
Clients always start with syncing disabled. Clients always start with syncing disabled.
**File operations** (per-client, fire-and-forget — sync is enqueued but not awaited): **File operations** (per-client, fire-and-forget — sync is enqueued but not awaited):
- `create`, `update`, `rename`, `delete` - `create`, `update`, `rename`, `delete`
**Sync control:** **Sync control:**
- `sync` — wait for a specific client or all clients to finish pending operations - `sync` — wait for a specific client or all clients to finish pending operations
- `barrier` — retry until all clients converge to identical file state (60s timeout) - `barrier` — retry until all clients converge to identical file state (60s timeout)
- `enable-sync` / `disable-sync` — simulate going online/offline - `enable-sync` / `disable-sync` — simulate going online/offline
**WebSocket control** (per-client): **WebSocket control** (per-client):
- `pause-websocket` / `resume-websocket` — buffer/release WebSocket messages for a specific client - `pause-websocket` / `resume-websocket` — buffer/release WebSocket messages for a specific client
**Server control:** **Server control:**
- `pause-server` / `resume-server` — SIGSTOP/SIGCONT the server process - `pause-server` / `resume-server` — SIGSTOP/SIGCONT the server process
**Assertions:** **Assertions:**
- `assert-consistent` — all clients have identical files; optionally takes a custom `verify(state: AssertableState)` callback - `assert-consistent` — all clients have identical files; optionally takes a custom `verify(state: AssertableState)` callback
## Running ## Running
@ -57,15 +62,19 @@ npm run test -w deterministic-tests -- -j 4
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const myScenarioTest: TestDefinition = { export const myScenarioTest: TestDefinition = {
description: "Client 0 creates A.md offline. After syncing, both clients should have the file.", description:
clients: 2, "Client 0 creates A.md offline. After syncing, both clients should have the file.",
steps: [ clients: 2,
{ type: "create", client: 0, path: "A.md", content: "hello" }, steps: [
{ type: "enable-sync", client: 0 }, { type: "create", client: 0, path: "A.md", content: "hello" },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 0 },
{ type: "barrier" }, { type: "enable-sync", client: 1 },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContent("A.md", "hello") } { type: "barrier" },
] {
type: "assert-consistent",
verify: (s) => s.assertFileCount(1).assertContent("A.md", "hello")
}
]
}; };
``` ```
@ -88,7 +97,7 @@ s.ifFileExists("path", (s) => ...) // conditional assertion
import { myScenarioTest } from "./tests/my-scenario.test"; import { myScenarioTest } from "./tests/my-scenario.test";
const TESTS = { const TESTS = {
// ... // ...
"my-scenario": myScenarioTest "my-scenario": myScenarioTest
}; };
``` ```

View file

@ -38,137 +38,6 @@ interface NamedTestResult {
result: TestResult; result: TestResult;
} }
async function main(): Promise<void> {
const cwd = process.cwd();
let projectRoot = cwd;
if (cwd.endsWith("frontend/deterministic-tests")) {
projectRoot = path.resolve(cwd, "../..");
} else if (cwd.endsWith("frontend")) {
projectRoot = path.resolve(cwd, "..");
}
const serverPath = path.join(projectRoot, SERVER_BINARY_PATH);
if (!fs.existsSync(serverPath)) {
logger.error(`Server binary not found at: ${serverPath}`);
process.exit(1);
}
const configPath = path.join(projectRoot, CONFIG_PATH);
if (!fs.existsSync(configPath)) {
logger.error(`Config file not found at: ${configPath}`);
process.exit(1);
}
const filterArg = process.argv.find((a) => a.startsWith("--filter="));
const filter = filterArg?.slice("--filter=".length);
const testsToRun: [string, TestDefinition][] = [];
for (const [key, test] of Object.entries(TESTS)) {
if (test) {
if (filter && !key.includes(filter)) {
continue;
}
testsToRun.push([key, test]);
}
}
if (testsToRun.length === 0) {
logger.error(
filter
? `No tests matched filter "${filter}"`
: "No tests found"
);
process.exit(1);
}
const concurrency = parseConcurrency();
const regularTests = testsToRun.filter(
([, t]) => !testUsesPauseServer(t)
);
const pauseTests = testsToRun.filter(([, t]) => testUsesPauseServer(t));
logger.info(`Server: ${serverPath}`);
logger.info(`Config: ${configPath}`);
logger.info(
`Tests: ${testsToRun.length} total (${regularTests.length} regular, ${pauseTests.length} server-pause)`
);
logger.info(`Concurrency: ${concurrency}`);
const allResults: NamedTestResult[] = [];
if (regularTests.length > 0) {
logger.info(
`\n--- Running ${regularTests.length} regular tests (shared server, concurrency ${concurrency}) ---`
);
const sharedServer = new ServerControl(
serverPath,
configPath,
logger
);
serverManager.track(sharedServer);
try {
await sharedServer.start();
const results = await runWithConcurrency(
regularTests,
concurrency,
async ([name, test]) =>
runSharedServerTest(name, test, sharedServer)
);
allResults.push(...results);
} finally {
try {
await sharedServer.stop();
} catch (error) {
logger.warn(
`Error stopping shared server: ${error instanceof Error ? error.message : String(error)}`
);
}
serverManager.untrack(sharedServer);
}
}
if (pauseTests.length > 0) {
logger.info(
`\n--- Running ${pauseTests.length} server-pause tests (dedicated servers, concurrency ${concurrency}) ---`
);
const results = await runWithConcurrency(
pauseTests,
concurrency,
async ([name, test]) =>
runDedicatedServerTest(name, test, serverPath, configPath)
);
allResults.push(...results);
}
const passed = allResults.filter((r) => r.result.success);
const failed = allResults.filter((r) => !r.result.success);
logger.info(`\n--- Results: ${passed.length}/${allResults.length} passed ---`);
if (failed.length > 0) {
for (const { name, result } of failed) {
logger.error(` FAILED: ${name}: ${result.error}`);
}
process.exit(1);
} else {
logger.info("All tests passed!");
process.exit(0);
}
}
main().catch((err: unknown) => {
logger.error(`Unexpected error: ${err}`);
process.exit(1);
});
async function runSharedServerTest( async function runSharedServerTest(
name: string, name: string,
test: TestDefinition, test: TestDefinition,
@ -229,3 +98,132 @@ async function runDedicatedServerTest(
serverManager.untrack(server); serverManager.untrack(server);
} }
} }
async function main(): Promise<void> {
const cwd = process.cwd();
let projectRoot = cwd;
if (cwd.endsWith("frontend/deterministic-tests")) {
projectRoot = path.resolve(cwd, "../..");
} else if (cwd.endsWith("frontend")) {
projectRoot = path.resolve(cwd, "..");
}
const serverPath = path.join(projectRoot, SERVER_BINARY_PATH);
if (!fs.existsSync(serverPath)) {
logger.error(`Server binary not found at: ${serverPath}`);
process.exit(1);
}
const configPath = path.join(projectRoot, CONFIG_PATH);
if (!fs.existsSync(configPath)) {
logger.error(`Config file not found at: ${configPath}`);
process.exit(1);
}
const filterArg = process.argv.find((a) => a.startsWith("--filter="));
const filter = filterArg?.slice("--filter=".length);
const testsToRun: [string, TestDefinition][] = [];
for (const [key, test] of Object.entries(TESTS)) {
if (test) {
if (
filter !== undefined &&
filter.length > 0 &&
!key.includes(filter)
) {
continue;
}
testsToRun.push([key, test]);
}
}
if (testsToRun.length === 0) {
logger.error(
filter !== undefined && filter.length > 0
? `No tests matched filter "${filter}"`
: "No tests found"
);
process.exit(1);
}
const concurrency = parseConcurrency();
const regularTests = testsToRun.filter(([, t]) => !testUsesPauseServer(t));
const pauseTests = testsToRun.filter(([, t]) => testUsesPauseServer(t));
logger.info(`Server: ${serverPath}`);
logger.info(`Config: ${configPath}`);
logger.info(
`Tests: ${testsToRun.length} total (${regularTests.length} regular, ${pauseTests.length} server-pause)`
);
logger.info(`Concurrency: ${concurrency}`);
const allResults: NamedTestResult[] = [];
if (regularTests.length > 0) {
logger.info(
`\n--- Running ${regularTests.length} regular tests (shared server, concurrency ${concurrency}) ---`
);
const sharedServer = new ServerControl(serverPath, configPath, logger);
serverManager.track(sharedServer);
try {
await sharedServer.start();
const results = await runWithConcurrency(
regularTests,
concurrency,
async ([name, test]) =>
runSharedServerTest(name, test, sharedServer)
);
allResults.push(...results);
} finally {
try {
await sharedServer.stop();
} catch (error) {
logger.warn(
`Error stopping shared server: ${error instanceof Error ? error.message : String(error)}`
);
}
serverManager.untrack(sharedServer);
}
}
if (pauseTests.length > 0) {
logger.info(
`\n--- Running ${pauseTests.length} server-pause tests (dedicated servers, concurrency ${concurrency}) ---`
);
const results = await runWithConcurrency(
pauseTests,
concurrency,
async ([name, test]) =>
runDedicatedServerTest(name, test, serverPath, configPath)
);
allResults.push(...results);
}
const passed = allResults.filter((r) => r.result.success);
const failed = allResults.filter((r) => !r.result.success);
logger.info(
`\n--- Results: ${passed.length}/${allResults.length} passed ---`
);
if (failed.length > 0) {
for (const { name, result } of failed) {
logger.error(` FAILED: ${name}: ${result.error}`);
}
process.exit(1);
} else {
logger.info("All tests passed!");
process.exit(0);
}
}
main().catch((err: unknown) => {
logger.error(`Unexpected error: ${err}`);
process.exit(1);
});

View file

@ -1,13 +1,21 @@
import type { StoredDatabase, SyncSettings, RelativePath, TextWithCursors } from "sync-client"; import type {
import { SyncClient, debugging, LogLevel } from "sync-client"; StoredDatabase,
SyncSettings,
RelativePath,
TextWithCursors
} from "sync-client";
import { SyncClient, debugging, LogLevel, utils } from "sync-client";
import { assert } from "./utils/assert"; import { assert } from "./utils/assert";
import { sleep } from "./utils/sleep"; import { sleep } from "./utils/sleep";
import { withTimeout } from "./utils/with-timeout"; import { withTimeout } from "./utils/with-timeout";
import { IS_SYNC_ENABLED_BY_DEFAULT, WAIT_TIMEOUT_MS, WEBSOCKET_CONNECT_TIMEOUT_MS, WEBSOCKET_POLL_INTERVAL_MS } from "./consts"; import {
IS_SYNC_ENABLED_BY_DEFAULT,
WAIT_TIMEOUT_MS,
WEBSOCKET_CONNECT_TIMEOUT_MS,
WEBSOCKET_POLL_INTERVAL_MS
} from "./consts";
import { ManagedWebSocketFactory } from "./managed-websocket"; import { ManagedWebSocketFactory } from "./managed-websocket";
export class DeterministicAgent extends debugging.InMemoryFileSystem { export class DeterministicAgent extends debugging.InMemoryFileSystem {
public readonly clientId: number; public readonly clientId: number;
private readonly logger: (msg: string) => void; private readonly logger: (msg: string) => void;
@ -33,7 +41,7 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
} }
public async init( public async init(
fetchImplementation: typeof globalThis.fetch, fetchImplementation: typeof globalThis.fetch
): Promise<void> { ): Promise<void> {
this.client = await SyncClient.create({ this.client = await SyncClient.create({
fs: this, fs: this,
@ -138,7 +146,6 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
await this.waitForWebSocket(); await this.waitForWebSocket();
} }
public async getFileContent(path: string): Promise<string> { public async getFileContent(path: string): Promise<string> {
const bytes = await this.read(path); const bytes = await this.read(path);
return new TextDecoder().decode(bytes); return new TextDecoder().decode(bytes);
@ -146,9 +153,11 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
public async cleanup(): Promise<void> { public async cleanup(): Promise<void> {
this.log("Cleaning up..."); this.log("Cleaning up...");
// Guard against uninitialized client (init() failed partway) // Guard against uninitialized client (init() failed partway).
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // The class field uses `!:` so TS thinks this is always defined,
if (!this.client) { // but at runtime it can be undefined when init() throws partway.
const maybeClient = this.client as SyncClient | undefined;
if (maybeClient === undefined) {
this.log("Client not initialized, nothing to clean up"); this.log("Client not initialized, nothing to clean up");
return; return;
} }
@ -184,11 +193,13 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
await super.write(path, content); await super.write(path, content);
if (isNew) { if (isNew) {
this.enqueueSync(async () => { this.client.syncLocallyCreatedFile(path); } this.enqueueSync(async () => {
); this.client.syncLocallyCreatedFile(path);
});
} else { } else {
this.enqueueSync(async () => { this.client.syncLocallyUpdatedFile({ relativePath: path }); } this.enqueueSync(async () => {
); this.client.syncLocallyUpdatedFile({ relativePath: path });
});
} }
} }
@ -197,18 +208,18 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
updater: (current: TextWithCursors) => TextWithCursors updater: (current: TextWithCursors) => TextWithCursors
): Promise<string> { ): Promise<string> {
const result = await super.atomicUpdateText(path, updater); const result = await super.atomicUpdateText(path, updater);
this.enqueueSync(async () => { this.client.syncLocallyUpdatedFile({ relativePath: path }); } this.enqueueSync(async () => {
); this.client.syncLocallyUpdatedFile({ relativePath: path });
});
return result; return result;
} }
public override async delete(path: RelativePath): Promise<void> { public override async delete(path: RelativePath): Promise<void> {
await super.delete(path); await super.delete(path);
if (this.isSyncEnabled) { if (this.isSyncEnabled) {
this.enqueueSync(async () => { this.client.syncLocallyDeletedFile(path); } this.enqueueSync(async () => {
); this.client.syncLocallyDeletedFile(path);
});
} }
} }
@ -222,8 +233,7 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
oldPath, oldPath,
relativePath: newPath relativePath: newPath
}); });
} });
);
} }
private async waitForWebSocket(): Promise<void> { private async waitForWebSocket(): Promise<void> {
@ -243,7 +253,7 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
*/ */
private async drainPendingSyncOperations(): Promise<void> { private async drainPendingSyncOperations(): Promise<void> {
while (this.pendingSyncOperations.size > 0) { while (this.pendingSyncOperations.size > 0) {
await Promise.all(this.pendingSyncOperations); await utils.awaitAll([...this.pendingSyncOperations]);
} }
} }

View file

@ -2,16 +2,129 @@
* A WebSocket wrapper that can pause and resume message delivery. * A WebSocket wrapper that can pause and resume message delivery.
* When paused, incoming messages are buffered. When resumed, buffered * When paused, incoming messages are buffered. When resumed, buffered
* messages are delivered in order via the onmessage handler. * messages are delivered in order via the onmessage handler.
*
* Member layout follows typescript-eslint default member-ordering: all
* accessor properties are declared with `declare` and wired through the
* constructor using Object.defineProperty so we don't need conflicting
* get/set accessor pairs.
*/ */
export class ManagedWebSocket implements WebSocket { export class ManagedWebSocket implements WebSocket {
public static readonly CONNECTING = WebSocket.CONNECTING;
public static readonly OPEN = WebSocket.OPEN;
public static readonly CLOSING = WebSocket.CLOSING;
public static readonly CLOSED = WebSocket.CLOSED;
public readonly CONNECTING = WebSocket.CONNECTING;
public readonly OPEN = WebSocket.OPEN;
public readonly CLOSING = WebSocket.CLOSING;
public readonly CLOSED = WebSocket.CLOSED;
declare public readonly readyState: number;
declare public readonly url: string;
declare public readonly protocol: string;
declare public readonly extensions: string;
declare public readonly bufferedAmount: number;
declare public binaryType: BinaryType;
declare public onopen: ((this: WebSocket, ev: Event) => unknown) | null;
declare public onclose:
| ((this: WebSocket, ev: CloseEvent) => unknown)
| null;
declare public onerror: ((this: WebSocket, ev: Event) => unknown) | null;
declare public onmessage:
| ((this: WebSocket, ev: MessageEvent) => unknown)
| null;
private readonly ws: WebSocket; private readonly ws: WebSocket;
private paused = false;
private readonly bufferedMessages: MessageEvent[] = []; private readonly bufferedMessages: MessageEvent[] = [];
private paused = false;
private externalOnMessage: ((event: MessageEvent) => unknown) | null = null; private externalOnMessage: ((event: MessageEvent) => unknown) | null = null;
public constructor(url: string | URL, protocols?: string | string[]) { public constructor(url: string | URL, protocols?: string | string[]) {
this.ws = new WebSocket(url, protocols); this.ws = new WebSocket(url, protocols);
const { ws } = this;
Object.defineProperties(this, {
readyState: {
get: (): number => ws.readyState,
enumerable: true,
configurable: true
},
url: {
get: (): string => ws.url,
enumerable: true,
configurable: true
},
protocol: {
get: (): string => ws.protocol,
enumerable: true,
configurable: true
},
extensions: {
get: (): string => ws.extensions,
enumerable: true,
configurable: true
},
bufferedAmount: {
get: (): number => ws.bufferedAmount,
enumerable: true,
configurable: true
},
binaryType: {
get: (): BinaryType => ws.binaryType,
set: (v: BinaryType): void => {
ws.binaryType = v;
},
enumerable: true,
configurable: true
},
onopen: {
get: (): ((this: WebSocket, ev: Event) => unknown) | null =>
ws.onopen,
set: (
h: ((this: WebSocket, ev: Event) => unknown) | null
): void => {
ws.onopen = h;
},
enumerable: true,
configurable: true
},
onclose: {
get: ():
| ((this: WebSocket, ev: CloseEvent) => unknown)
| null => ws.onclose,
set: (
h: ((this: WebSocket, ev: CloseEvent) => unknown) | null
): void => {
ws.onclose = h;
},
enumerable: true,
configurable: true
},
onerror: {
get: (): ((this: WebSocket, ev: Event) => unknown) | null =>
ws.onerror,
set: (
h: ((this: WebSocket, ev: Event) => unknown) | null
): void => {
ws.onerror = h;
},
enumerable: true,
configurable: true
},
onmessage: {
get: ():
| ((this: WebSocket, ev: MessageEvent) => unknown)
| null => this.externalOnMessage,
set: (
h: ((this: WebSocket, ev: MessageEvent) => unknown) | null
): void => {
this.externalOnMessage = h;
},
enumerable: true,
configurable: true
}
});
this.ws.onmessage = (event: MessageEvent): void => { this.ws.onmessage = (event: MessageEvent): void => {
if (this.paused) { if (this.paused) {
this.bufferedMessages.push(event); this.bufferedMessages.push(event);
@ -33,68 +146,6 @@ export class ManagedWebSocket implements WebSocket {
} }
} }
get readyState(): number {
return this.ws.readyState;
}
get url(): string {
return this.ws.url;
}
get protocol(): string {
return this.ws.protocol;
}
get extensions(): string {
return this.ws.extensions;
}
get bufferedAmount(): number {
return this.ws.bufferedAmount;
}
get binaryType(): BinaryType {
return this.ws.binaryType;
}
set binaryType(value: BinaryType) {
this.ws.binaryType = value;
}
get onopen(): ((this: WebSocket, ev: Event) => unknown) | null {
return this.ws.onopen;
}
set onopen(handler: ((this: WebSocket, ev: Event) => unknown) | null) {
this.ws.onopen = handler;
}
get onclose(): ((this: WebSocket, ev: CloseEvent) => unknown) | null {
return this.ws.onclose;
}
set onclose(handler: ((this: WebSocket, ev: CloseEvent) => unknown) | null) {
this.ws.onclose = handler;
}
get onerror(): ((this: WebSocket, ev: Event) => unknown) | null {
return this.ws.onerror;
}
set onerror(handler: ((this: WebSocket, ev: Event) => unknown) | null) {
this.ws.onerror = handler;
}
get onmessage(): ((this: WebSocket, ev: MessageEvent) => unknown) | null {
return this.externalOnMessage;
}
set onmessage(
handler: ((this: WebSocket, ev: MessageEvent) => unknown) | null
) {
this.externalOnMessage = handler;
}
public send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void { public send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {
this.ws.send(data); this.ws.send(data);
} }
@ -118,16 +169,6 @@ export class ManagedWebSocket implements WebSocket {
public dispatchEvent(event: Event): boolean { public dispatchEvent(event: Event): boolean {
return this.ws.dispatchEvent(event); return this.ws.dispatchEvent(event);
} }
static readonly CONNECTING = WebSocket.CONNECTING;
static readonly OPEN = WebSocket.OPEN;
static readonly CLOSING = WebSocket.CLOSING;
static readonly CLOSED = WebSocket.CLOSED;
readonly CONNECTING = WebSocket.CONNECTING;
readonly OPEN = WebSocket.OPEN;
readonly CLOSING = WebSocket.CLOSING;
readonly CLOSED = WebSocket.CLOSED;
} }
/** /**
@ -138,22 +179,19 @@ export class ManagedWebSocketFactory {
private readonly instances: ManagedWebSocket[] = []; private readonly instances: ManagedWebSocket[] = [];
public get constructorFn(): typeof globalThis.WebSocket { public get constructorFn(): typeof globalThis.WebSocket {
const factory = this; const trackInstance = (instance: ManagedWebSocket): void => {
const ctor = function ManagedWS( this.instances.push(instance);
url: string | URL, };
protocols?: string | string[] class TrackedManagedWebSocket extends ManagedWebSocket {
): ManagedWebSocket { public constructor(
const ws = new ManagedWebSocket(url, protocols); url: string | URL,
factory.instances.push(ws); protocols?: string | string[]
return ws; ) {
} as unknown as typeof globalThis.WebSocket; super(url, protocols);
trackInstance(this);
Object.defineProperty(ctor, "CONNECTING", { value: WebSocket.CONNECTING }); }
Object.defineProperty(ctor, "OPEN", { value: WebSocket.OPEN }); }
Object.defineProperty(ctor, "CLOSING", { value: WebSocket.CLOSING }); return TrackedManagedWebSocket;
Object.defineProperty(ctor, "CLOSED", { value: WebSocket.CLOSED });
return ctor;
} }
public pause(): void { public pause(): void {

View file

@ -42,9 +42,7 @@ export class ServerControl {
this._port = reservation.port; this._port = reservation.port;
// Prefer tmpfs (/host/tmp) over disk-backed /tmp for faster SQLite I/O // Prefer tmpfs (/host/tmp) over disk-backed /tmp for faster SQLite I/O
const tmpBase = fs.existsSync("/host/tmp") ? "/host/tmp" : os.tmpdir(); const tmpBase = fs.existsSync("/host/tmp") ? "/host/tmp" : os.tmpdir();
this.tempDir = fs.mkdtempSync( this.tempDir = fs.mkdtempSync(path.join(tmpBase, "vault-link-test-"));
path.join(tmpBase, "vault-link-test-")
);
const tempConfigPath = path.join(this.tempDir, "config.yml"); const tempConfigPath = path.join(this.tempDir, "config.yml");
const dbDir = path.join(this.tempDir, "databases"); const dbDir = path.join(this.tempDir, "databases");
@ -225,7 +223,7 @@ export class ServerControl {
} }
private cleanupTempDir(): void { private cleanupTempDir(): void {
if (this.tempDir) { if (this.tempDir !== undefined) {
try { try {
fs.rmSync(this.tempDir, { recursive: true, force: true }); fs.rmSync(this.tempDir, { recursive: true, force: true });
} catch { } catch {
@ -234,5 +232,4 @@ export class ServerControl {
this.tempDir = undefined; this.tempDir = undefined;
} }
} }
} }

View file

@ -39,14 +39,18 @@ export class ServerManager {
process.on("SIGINT", () => { process.on("SIGINT", () => {
this.logger.info("Received SIGINT, shutting down..."); this.logger.info("Received SIGINT, shutting down...");
void this.stopAll() void this.stopAll()
.catch(() => {}) .catch(() => {
/* no-op */
})
.then(() => process.exit(130)); .then(() => process.exit(130));
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
this.logger.info("Received SIGTERM, shutting down..."); this.logger.info("Received SIGTERM, shutting down...");
void this.stopAll() void this.stopAll()
.catch(() => {}) .catch(() => {
/* no-op */
})
.then(() => process.exit(143)); .then(() => process.exit(143));
}); });
} }

View file

@ -102,10 +102,12 @@ export const TESTS: Partial<Record<string, TestDefinition>> = {
"delete-recreate-same-path": deleteRecreateSamePathTest, "delete-recreate-same-path": deleteRecreateSamePathTest,
"offline-rename-and-edit": offlineRenameAndEditTest, "offline-rename-and-edit": offlineRenameAndEditTest,
"rename-to-existing-path": renameToExistingPathTest, "rename-to-existing-path": renameToExistingPathTest,
"simultaneous-create-delete-same-path": simultaneousCreateDeleteSamePathTest, "simultaneous-create-delete-same-path":
simultaneousCreateDeleteSamePathTest,
"idempotency-after-server-pause": idempotencyAfterServerPauseTest, "idempotency-after-server-pause": idempotencyAfterServerPauseTest,
"sequential-create-duplicate-content": sequentialCreateDuplicateContentTest, "sequential-create-duplicate-content": sequentialCreateDuplicateContentTest,
"mc-three-client-rename-offline-update": mcThreeClientRenameOfflineUpdateTest, "mc-three-client-rename-offline-update":
mcThreeClientRenameOfflineUpdateTest,
"mc-multi-delete-offline-rename": mcMultiDeleteOfflineRenameTest, "mc-multi-delete-offline-rename": mcMultiDeleteOfflineRenameTest,
"mc-cross-create-rename-same-target": mcCrossCreateRenameSameTargetTest, "mc-cross-create-rename-same-target": mcCrossCreateRenameSameTargetTest,
"mc-delete-then-offline-rename": mcDeleteThenOfflineRenameTest, "mc-delete-then-offline-rename": mcDeleteThenOfflineRenameTest,
@ -117,7 +119,8 @@ export const TESTS: Partial<Record<string, TestDefinition>> = {
"rename-swap": renameSwapTest, "rename-swap": renameSwapTest,
"rename-circular": renameCircularTest, "rename-circular": renameCircularTest,
"rename-roundtrip": renameRoundtripTest, "rename-roundtrip": renameRoundtripTest,
"offline-rename-remote-create-old-path": offlineRenameRemoteCreateOldPathTest, "offline-rename-remote-create-old-path":
offlineRenameRemoteCreateOldPathTest,
"offline-edit-remote-rename": offlineEditRemoteRenameTest, "offline-edit-remote-rename": offlineEditRemoteRenameTest,
"rename-chain-then-delete": renameChainThenDeleteTest, "rename-chain-then-delete": renameChainThenDeleteTest,
"offline-delete-remote-rename": offlineDeleteRemoteRenameTest, "offline-delete-remote-rename": offlineDeleteRemoteRenameTest,
@ -140,34 +143,45 @@ export const TESTS: Partial<Record<string, TestDefinition>> = {
"delete-recreate-different-content": deleteRecreateDifferentContentTest, "delete-recreate-different-content": deleteRecreateDifferentContentTest,
"update-during-create-processing": updateDuringCreateProcessingTest, "update-during-create-processing": updateDuringCreateProcessingTest,
"offline-move-then-remote-delete": offlineMoveThenRemoteDeleteTest, "offline-move-then-remote-delete": offlineMoveThenRemoteDeleteTest,
"reset-clears-recently-deleted-resurrection": resetClearsRecentlyDeletedResurrectionTest, "reset-clears-recently-deleted-resurrection":
resetClearsRecentlyDeletedResurrectionTest,
"move-then-delete-stale-path": moveThenDeleteStalePathTest, "move-then-delete-stale-path": moveThenDeleteStalePathTest,
"offline-delete-vs-remote-update": offlineDeleteVsRemoteUpdateTest, "offline-delete-vs-remote-update": offlineDeleteVsRemoteUpdateTest,
"interrupted-delete-retry": interruptedDeleteRetryTest, "interrupted-delete-retry": interruptedDeleteRetryTest,
"update-survives-remote-delete": updateDoesNotSurvivesRemoteDeleteTest, "update-survives-remote-delete": updateDoesNotSurvivesRemoteDeleteTest,
"move-preserves-remote-update": movePreservesRemoteUpdateTest, "move-preserves-remote-update": movePreservesRemoteUpdateTest,
"recently-deleted-cleared-on-reconnect": recentlyDeletedClearedOnReconnectTest, "recently-deleted-cleared-on-reconnect":
recentlyDeletedClearedOnReconnectTest,
"migrate-key-preserves-existing": migrateKeyPreservesExistingTest, "migrate-key-preserves-existing": migrateKeyPreservesExistingTest,
"failed-vfs-move-falls-back": failedVfsMoveFallsBackTest, "failed-vfs-move-falls-back": failedVfsMoveFallsBackTest,
"watermark-advances-on-skip": watermarkAdvancesOnSkipTest, "watermark-advances-on-skip": watermarkAdvancesOnSkipTest,
"watermark-gap-remote-update-not-recorded": watermarkGapRemoteUpdateNotRecordedTest, "watermark-gap-remote-update-not-recorded":
"queue-reset-loses-coalesced-local-edit": queueResetLosesCoalescedLocalEditTest, watermarkGapRemoteUpdateNotRecordedTest,
"queue-reset-loses-coalesced-local-edit":
queueResetLosesCoalescedLocalEditTest,
"rename-to-pending-path-fallback": renameToPendingPathFallbackTest, "rename-to-pending-path-fallback": renameToPendingPathFallbackTest,
"move-remote-update-reverts-rename": moveRemoteUpdateRevertsRenameTest, "move-remote-update-reverts-rename": moveRemoteUpdateRevertsRenameTest,
"local-edit-lost-during-create-merge": localEditLostDuringCreateMergeTest, "local-edit-lost-during-create-merge": localEditLostDuringCreateMergeTest,
"rename-pending-create-before-response": renamePendingCreateBeforeResponseTest, "rename-pending-create-before-response":
renamePendingCreateBeforeResponseTest,
"create-rename-response-skips-file": createRenameResponseSkipsFileTest, "create-rename-response-skips-file": createRenameResponseSkipsFileTest,
"online-create-rename-concurrent-create-orphan": onlineCreateRenameConcurrentCreateOrphanTest, "online-create-rename-concurrent-create-orphan":
onlineCreateRenameConcurrentCreateOrphanTest,
"concurrent-rename-first-wins": concurrentRenameFirstWinsTest, "concurrent-rename-first-wins": concurrentRenameFirstWinsTest,
"binary-to-text-transition": binaryToTextTransitionTest, "binary-to-text-transition": binaryToTextTransitionTest,
"text-pending-create-not-displaced": textPendingCreateNotDisplacedTest, "text-pending-create-not-displaced": textPendingCreateNotDisplacedTest,
"binary-pending-create-not-displaced": binaryPendingCreateNotDisplacedTest, "binary-pending-create-not-displaced": binaryPendingCreateNotDisplacedTest,
"coalesce-update-remote-update-data-loss": coalesceUpdateRemoteUpdateDataLossTest, "coalesce-update-remote-update-data-loss":
"coalesced-remote-update-watermark-loss": coalescedRemoteUpdateWatermarkLossTest, coalesceUpdateRemoteUpdateDataLossTest,
"concurrent-delete-during-remote-update": concurrentDeleteDuringRemoteUpdateTest, "coalesced-remote-update-watermark-loss":
coalescedRemoteUpdateWatermarkLossTest,
"concurrent-delete-during-remote-update":
concurrentDeleteDuringRemoteUpdateTest,
"concurrent-edit-exact-same-position": concurrentEditExactSamePositionTest, "concurrent-edit-exact-same-position": concurrentEditExactSamePositionTest,
"concurrent-rename-and-create-at-target-rename-first": concurrentRenameAndCreateAtTargetRenameFirstTest, "concurrent-rename-and-create-at-target-rename-first":
"concurrent-rename-and-create-at-target-create-first": concurrentRenameAndCreateAtTargetCreateFirstTest, concurrentRenameAndCreateAtTargetRenameFirstTest,
"concurrent-rename-and-create-at-target-create-first":
concurrentRenameAndCreateAtTargetCreateFirstTest,
"concurrent-rename-same-target": concurrentRenameSameTargetTest, "concurrent-rename-same-target": concurrentRenameSameTargetTest,
"concurrent-update-diff-consistency": concurrentUpdateDiffConsistencyTest, "concurrent-update-diff-consistency": concurrentUpdateDiffConsistencyTest,
"user-parenthesized-file-not-deleted": userParenthesizedFileNotDeletedTest, "user-parenthesized-file-not-deleted": userParenthesizedFileNotDeletedTest,
@ -176,15 +190,19 @@ export const TESTS: Partial<Record<string, TestDefinition>> = {
"move-identical-content-ambiguity": moveIdenticalContentAmbiguityTest, "move-identical-content-ambiguity": moveIdenticalContentAmbiguityTest,
"create-update-coalesce-server-pause": createUpdateCoalesceServerPauseTest, "create-update-coalesce-server-pause": createUpdateCoalesceServerPauseTest,
"create-during-reconciliation": createDuringReconciliationTest, "create-during-reconciliation": createDuringReconciliationTest,
"create-merge-preserves-renamed-update": createMergePreservesRenamedUpdateTest, "create-merge-preserves-renamed-update":
createMergePreservesRenamedUpdateTest,
"create-rename-create-same-path": createRenameCreateSamePathTest, "create-rename-create-same-path": createRenameCreateSamePathTest,
"move-chain-three-files": moveChainThreeFilesTest, "move-chain-three-files": moveChainThreeFilesTest,
"delete-by-other-client-then-recreate": deleteByOtherClientThenRecreateTest, "delete-by-other-client-then-recreate": deleteByOtherClientThenRecreateTest,
"online-delete-recreate-rapid-cycle": onlineDeleteRecreateRapidCycleTest, "online-delete-recreate-rapid-cycle": onlineDeleteRecreateRapidCycleTest,
"online-edit-vs-delete-convergence": onlineEditVsDeleteConvergenceTest, "online-edit-vs-delete-convergence": onlineEditVsDeleteConvergenceTest,
"rapid-edit-delete-online-convergence": rapidEditDeleteOnlineConvergenceTest, "rapid-edit-delete-online-convergence":
rapidEditDeleteOnlineConvergenceTest,
"server-pause-delete-recreate": serverPauseDeleteRecreateTest, "server-pause-delete-recreate": serverPauseDeleteRecreateTest,
"online-both-create-same-path-deconflict": onlineBothCreateSamePathDeconflictTest, "online-both-create-same-path-deconflict":
"online-create-update-while-other-creates-same-path": onlineCreateUpdateWhileOtherCreatesSamePathTest, onlineBothCreateSamePathDeconflictTest,
"displaced-file-not-marked-deleted": displacedFileNotMarkedDeletedTest, "online-create-update-while-other-creates-same-path":
onlineCreateUpdateWhileOtherCreatesSamePathTest,
"displaced-file-not-marked-deleted": displacedFileNotMarkedDeletedTest
}; };

View file

@ -1,8 +1,4 @@
import type { import type { TestDefinition, TestResult, TestStep } from "./test-definition";
TestDefinition,
TestResult,
TestStep
} from "./test-definition";
import { DeterministicAgent } from "./deterministic-agent"; import { DeterministicAgent } from "./deterministic-agent";
import type { ServerControl } from "./server-control"; import type { ServerControl } from "./server-control";
import type { SyncSettings, Logger } from "sync-client"; import type { SyncSettings, Logger } from "sync-client";
@ -113,9 +109,7 @@ export class TestRunner {
// Push before init so cleanup() handles this agent if init fails // Push before init so cleanup() handles this agent if init fails
this.agents.push(agent); this.agents.push(agent);
await withTimeout( await withTimeout(
agent.init( agent.init(fetch),
fetch,
),
AGENT_INIT_TIMEOUT_MS, AGENT_INIT_TIMEOUT_MS,
`Client ${i} init timed out after ${AGENT_INIT_TIMEOUT_MS}ms` `Client ${i} init timed out after ${AGENT_INIT_TIMEOUT_MS}ms`
); );
@ -276,7 +270,10 @@ export class TestRunner {
verify?: (state: AssertableState) => void verify?: (state: AssertableState) => void
): Promise<void> { ): Promise<void> {
this.logger.info("Asserting all clients are consistent..."); this.logger.info("Asserting all clients are consistent...");
assert(this.agents.length >= 2, "Need at least 2 agents for consistency check"); assert(
this.agents.length >= 2,
"Need at least 2 agents for consistency check"
);
// Snapshot all agents' file states upfront to minimize the window // Snapshot all agents' file states upfront to minimize the window
// where background sync could mutate state between reads. // where background sync could mutate state between reads.

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const textPendingCreateNotDisplacedTest: TestDefinition = { export const textPendingCreateNotDisplacedTest: TestDefinition = {
@ -23,6 +24,13 @@ export const textPendingCreateNotDisplacedTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertFileExists("data.txt").assertAnyFileContains("client-0", "client-1") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1)
.assertFileExists("data.txt")
.assertAnyFileContains("client-0", "client-1");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const concurrentUpdateDiffConsistencyTest: TestDefinition = { export const concurrentUpdateDiffConsistencyTest: TestDefinition = {
@ -35,6 +36,16 @@ export const concurrentUpdateDiffConsistencyTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (state) => state.assertFileCount(1).assertContent("doc.md", "header by 0\nmiddle\nfooter by 1") } {
type: "assert-consistent",
verify: (state: AssertableState): void => {
state
.assertFileCount(1)
.assertContent(
"doc.md",
"header by 0\nmiddle\nfooter by 1"
);
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const userParenthesizedFileNotDeletedTest: TestDefinition = { export const userParenthesizedFileNotDeletedTest: TestDefinition = {
@ -34,7 +35,7 @@ export const userParenthesizedFileNotDeletedTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(3) .assertFileCount(3)
.assertFileExists("Chapter.bin") .assertFileExists("Chapter.bin")

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const createDeleteNoopTest: TestDefinition = { export const createDeleteNoopTest: TestDefinition = {
@ -16,6 +17,11 @@ export const createDeleteNoopTest: TestDefinition = {
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileNotExists("temp.md") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileNotExists("temp.md");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const createMergeDeleteTest: TestDefinition = { export const createMergeDeleteTest: TestDefinition = {
@ -16,12 +17,21 @@ export const createMergeDeleteTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => state.assertFileCount(1).assertContains("A.md", "from-zero", "from-one") verify: (state: AssertableState): void => {
state
.assertFileCount(1)
.assertContains("A.md", "from-zero", "from-one");
}
}, },
{ type: "delete", client: 0, path: "A.md" }, { type: "delete", client: 0, path: "A.md" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(0).assertFileNotExists("A.md") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(0).assertFileNotExists("A.md");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const moveIdenticalContentAmbiguityTest: TestDefinition = { export const moveIdenticalContentAmbiguityTest: TestDefinition = {
@ -31,7 +32,7 @@ export const moveIdenticalContentAmbiguityTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(1) .assertFileCount(1)
.assertFileNotExists("A.md") .assertFileNotExists("A.md")

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const createUpdateCoalesceServerPauseTest: TestDefinition = { export const createUpdateCoalesceServerPauseTest: TestDefinition = {
@ -19,6 +20,13 @@ export const createUpdateCoalesceServerPauseTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (state) => state.assertFileCount(1).assertContent("doc.md", "final version") } {
type: "assert-consistent",
verify: (state: AssertableState): void => {
state
.assertFileCount(1)
.assertContent("doc.md", "final version");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const createDuringReconciliationTest: TestDefinition = { export const createDuringReconciliationTest: TestDefinition = {
@ -37,7 +38,7 @@ export const createDuringReconciliationTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(3) .assertFileCount(3)
.assertContent("A.md", "offline A") .assertContent("A.md", "offline A")

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const createMergePreservesRenamedUpdateTest: TestDefinition = { export const createMergePreservesRenamedUpdateTest: TestDefinition = {
@ -39,6 +40,13 @@ export const createMergePreservesRenamedUpdateTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (state) => state.assertContent("moved.md", "alpha beta extra-update").assertContent("doc.md", "new-content") } {
type: "assert-consistent",
verify: (state: AssertableState): void => {
state
.assertContent("moved.md", "alpha beta extra-update")
.assertContent("doc.md", "new-content");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const createRenameCreateSamePathTest: TestDefinition = { export const createRenameCreateSamePathTest: TestDefinition = {
@ -22,7 +23,7 @@ export const createRenameCreateSamePathTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(3) .assertFileCount(3)
.assertContent("B.md", "first file") .assertContent("B.md", "first file")

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const moveChainThreeFilesTest: TestDefinition = { export const moveChainThreeFilesTest: TestDefinition = {
@ -29,7 +30,7 @@ export const moveChainThreeFilesTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(3) .assertFileCount(3)
.assertContent("A.md", "was C") .assertContent("A.md", "was C")

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const binaryPendingCreateNotDisplacedTest: TestDefinition = { export const binaryPendingCreateNotDisplacedTest: TestDefinition = {
@ -23,6 +24,17 @@ export const binaryPendingCreateNotDisplacedTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(2).assertFileExists("data.bin").assertFileExists("data (1).bin").assertAnyFileContains("binary data from client 0", "binary data from client 1") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(2)
.assertFileExists("data.bin")
.assertFileExists("data (1).bin")
.assertAnyFileContains(
"binary data from client 0",
"binary data from client 1"
);
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const coalesceUpdateRemoteUpdateDataLossTest: TestDefinition = { export const coalesceUpdateRemoteUpdateDataLossTest: TestDefinition = {
@ -38,10 +39,14 @@ export const coalesceUpdateRemoteUpdateDataLossTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(1) .assertFileCount(1)
.assertContains("doc.md", "client 0 addition", "client 1 addition"); .assertContains(
"doc.md",
"client 0 addition",
"client 1 addition"
);
} }
} }
] ]

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = { export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = {
@ -18,7 +19,12 @@ export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = {
{ type: "sync", client: 0 }, { type: "sync", client: 0 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContent("doc.md", "final update") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("doc.md", "final update");
}
},
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
@ -26,13 +32,23 @@ export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContent("doc.md", "final update") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("doc.md", "final update");
}
},
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContent("doc.md", "final update") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("doc.md", "final update");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const concurrentDeleteDuringRemoteUpdateTest: TestDefinition = { export const concurrentDeleteDuringRemoteUpdateTest: TestDefinition = {
@ -21,7 +22,11 @@ export const concurrentDeleteDuringRemoteUpdateTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (state) => state.assertFileCount(0) } {
type: "assert-consistent",
verify: (state: AssertableState): void => {
state.assertFileCount(0);
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const concurrentEditExactSamePositionTest: TestDefinition = { export const concurrentEditExactSamePositionTest: TestDefinition = {
@ -38,7 +39,7 @@ export const concurrentEditExactSamePositionTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(1) .assertFileCount(1)
.assertContains("doc.md", "slow", "fast", "brown fox"); .assertContains("doc.md", "slow", "fast", "brown fox");

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const concurrentRenameAndCreateAtTargetTest: TestDefinition = { export const concurrentRenameAndCreateAtTargetTest: TestDefinition = {
@ -37,10 +38,14 @@ export const concurrentRenameAndCreateAtTargetTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileNotExists("X.md") .assertFileNotExists("X.md")
.assertContains("Y.md", "original file X", "brand new Y content"); .assertContains(
"Y.md",
"original file X",
"brand new Y content"
);
} }
} }
] ]

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const concurrentRenameAndCreateAtTargetTest: TestDefinition = { export const concurrentRenameAndCreateAtTargetTest: TestDefinition = {
@ -37,7 +38,7 @@ export const concurrentRenameAndCreateAtTargetTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(2) .assertFileCount(2)
.assertContains("Y (1).md", "original file X") .assertContains("Y (1).md", "original file X")

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const concurrentRenameSameTargetTest: TestDefinition = { export const concurrentRenameSameTargetTest: TestDefinition = {
@ -25,7 +26,7 @@ export const concurrentRenameSameTargetTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(2) .assertFileCount(2)
.assertFileNotExists("A.md") .assertFileNotExists("A.md")

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const binaryToTextTransitionTest: TestDefinition = { export const binaryToTextTransitionTest: TestDefinition = {
@ -8,11 +9,21 @@ export const binaryToTextTransitionTest: TestDefinition = {
"offline. The text merge should preserve both edits.", "offline. The text merge should preserve both edits.",
clients: 2, clients: 2,
steps: [ steps: [
{ type: "create", client: 0, path: "data.bin", content: "original content" }, {
type: "create",
client: 0,
path: "data.bin",
content: "original content"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertContent("data.bin", "original content") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertContent("data.bin", "original content");
}
},
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
@ -24,26 +35,63 @@ export const binaryToTextTransitionTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContainsAny("data.bin", "version A", "version B") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContainsAny(
"data.bin",
"version A",
"version B"
);
}
},
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
{ type: "rename", client: 0, oldPath: "data.bin", newPath: "data.md" }, { type: "rename", client: 0, oldPath: "data.bin", newPath: "data.md" },
{ type: "update", client: 0, path: "data.md", content: "top line\nmiddle line\nbottom line" }, {
type: "update",
client: 0,
path: "data.md",
content: "top line\nmiddle line\nbottom line"
},
{ type: "sync", client: 0 }, { type: "sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertContent("data.md", "top line\nmiddle line\nbottom line") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertContent(
"data.md",
"top line\nmiddle line\nbottom line"
);
}
},
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
{ type: "update", client: 0, path: "data.md", content: "alpha\nmiddle line\nbottom line" }, {
{ type: "update", client: 1, path: "data.md", content: "top line\nmiddle line\nbeta" }, type: "update",
client: 0,
path: "data.md",
content: "alpha\nmiddle line\nbottom line"
},
{
type: "update",
client: 1,
path: "data.md",
content: "top line\nmiddle line\nbeta"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContains("data.md", "alpha", "beta") }, {
], type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContains("data.md", "alpha", "beta");
}
}
]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const concurrentRenameFirstWinsTest: TestDefinition = { export const concurrentRenameFirstWinsTest: TestDefinition = {
@ -8,29 +9,52 @@ export const concurrentRenameFirstWinsTest: TestDefinition = {
"edits are merged.", "edits are merged.",
clients: 2, clients: 2,
steps: [ steps: [
{ type: "create", client: 0, path: "A.md", content: "line 1\nline 2\nline 3" }, {
type: "create",
client: 0,
path: "A.md",
content: "line 1\nline 2\nline 3"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertContent("A.md", "line 1\nline 2\nline 3") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertContent("A.md", "line 1\nline 2\nline 3");
}
},
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
{ type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" }, { type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" },
{ type: "update", client: 0, path: "B.md", content: "edit from 0\nline 2\nline 3" }, {
type: "update",
client: 0,
path: "B.md",
content: "edit from 0\nline 2\nline 3"
},
{ type: "rename", client: 1, oldPath: "A.md", newPath: "C.md" }, { type: "rename", client: 1, oldPath: "A.md", newPath: "C.md" },
{ type: "update", client: 1, path: "C.md", content: "line 1\nline 2\nedit from 1" }, {
type: "update",
client: 1,
path: "C.md",
content: "line 1\nline 2\nedit from 1"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => { {
s.assertFileNotExists("A.md"); type: "assert-consistent",
s.assertFileCount(1); verify: (s: AssertableState): void => {
s.assertAnyFileContains("edit from 0", "edit from 1"); s.assertFileNotExists("A.md");
} }, s.assertFileCount(1);
], s.assertAnyFileContains("edit from 0", "edit from 1");
}
}
]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const createRenameResponseSkipsFileTest: TestDefinition = { export const createRenameResponseSkipsFileTest: TestDefinition = {
@ -29,6 +30,11 @@ export const createRenameResponseSkipsFileTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertAnyFileContains("the-content") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertAnyFileContains("the-content");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const deleteByOtherClientThenRecreateTest: TestDefinition = { export const deleteByOtherClientThenRecreateTest: TestDefinition = {
@ -14,11 +15,26 @@ export const deleteByOtherClientThenRecreateTest: TestDefinition = {
{ type: "delete", client: 1, path: "A.md" }, { type: "delete", client: 1, path: "A.md" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileNotExists("A.md") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileNotExists("A.md");
}
},
{ type: "create", client: 0, path: "A.md", content: "recreated by client 0" }, {
type: "create",
client: 0,
path: "A.md",
content: "recreated by client 0"
},
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertContent("A.md", "recreated by client 0") }, {
], type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertContent("A.md", "recreated by client 0");
}
}
]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const deleteDuringPendingCreateTest: TestDefinition = { export const deleteDuringPendingCreateTest: TestDefinition = {
@ -26,6 +27,11 @@ export const deleteDuringPendingCreateTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(0).assertFileNotExists("ephemeral.md") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(0).assertFileNotExists("ephemeral.md");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const deleteRecreateConcurrentUpdateTest: TestDefinition = { export const deleteRecreateConcurrentUpdateTest: TestDefinition = {
@ -14,7 +15,12 @@ export const deleteRecreateConcurrentUpdateTest: TestDefinition = {
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "delete", client: 0, path: "A.md" }, { type: "delete", client: 0, path: "A.md" },
{ type: "create", client: 0, path: "A.md", content: "recreated by client 0" }, {
type: "create",
client: 0,
path: "A.md",
content: "recreated by client 0"
},
{ {
type: "update", type: "update",
@ -28,6 +34,11 @@ export const deleteRecreateConcurrentUpdateTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileExists("A.md").assertContains("A.md", "recreated") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileExists("A.md").assertContains("A.md", "recreated");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const deleteRecreateDifferentContentTest: TestDefinition = { export const deleteRecreateDifferentContentTest: TestDefinition = {
@ -41,6 +42,15 @@ export const deleteRecreateDifferentContentTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContains("A.md", "brand new", "client 1") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContains(
"A.md",
"brand new",
"client 1"
);
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const deleteRecreateSamePathTest: TestDefinition = { export const deleteRecreateSamePathTest: TestDefinition = {
@ -11,7 +12,12 @@ export const deleteRecreateSamePathTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertContent("A.md", "version 1") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertContent("A.md", "version 1");
}
},
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "delete", client: 0, path: "A.md" }, { type: "delete", client: 0, path: "A.md" },
@ -20,6 +26,11 @@ export const deleteRecreateSamePathTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertContent("A.md", "version 2") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertContent("A.md", "version 2");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const deleteRenameConflictTest: TestDefinition = { export const deleteRenameConflictTest: TestDefinition = {
@ -12,7 +13,12 @@ export const deleteRenameConflictTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileExists("A.md").assertFileExists("B.md") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileExists("A.md").assertFileExists("B.md");
}
},
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
@ -25,10 +31,15 @@ export const deleteRenameConflictTest: TestDefinition = {
{ type: "sync", client: 1 }, { type: "sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => { {
s.assertContent("B.md", "content-b"); type: "assert-consistent",
s.assertFileNotExists("A.md"); verify: (s: AssertableState): void => {
s.ifFileExists("C.md", (s) => s.assertContent("C.md", "content-a")); s.assertContent("B.md", "content-b");
} }, s.assertFileNotExists("A.md");
s.ifFileExists("C.md", (inner) =>
inner.assertContent("C.md", "content-a")
);
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const displacedFileNotMarkedDeletedTest: TestDefinition = { export const displacedFileNotMarkedDeletedTest: TestDefinition = {
@ -20,14 +21,19 @@ export const displacedFileNotMarkedDeletedTest: TestDefinition = {
{ type: "sync", client: 0 }, { type: "sync", client: 0 },
{ type: "rename", client: 1, oldPath: "A.md", newPath: "B.md" }, { type: "rename", client: 1, oldPath: "A.md", newPath: "B.md" },
{ type: "update", client: 1, path: "B.md", content: "edited A content" }, {
type: "update",
client: 1,
path: "B.md",
content: "edited A content"
},
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileNotExists("A.md") .assertFileNotExists("A.md")
.assertFileExists("B.md") .assertFileExists("B.md")

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const doubleOfflineCycleTest: TestDefinition = { export const doubleOfflineCycleTest: TestDefinition = {
@ -16,7 +17,12 @@ export const doubleOfflineCycleTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertContent("doc.md", "initial") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertContent("doc.md", "initial");
}
},
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ {
@ -29,7 +35,12 @@ export const doubleOfflineCycleTest: TestDefinition = {
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertContent("doc.md", "first edit") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertContent("doc.md", "first edit");
}
},
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ {
@ -42,7 +53,12 @@ export const doubleOfflineCycleTest: TestDefinition = {
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertContent("doc.md", "second edit") }, {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertContent("doc.md", "second edit");
}
},
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ {
@ -55,6 +71,11 @@ export const doubleOfflineCycleTest: TestDefinition = {
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContent("doc.md", "third edit") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("doc.md", "third edit");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const failedVfsMoveFallsBackTest: TestDefinition = { export const failedVfsMoveFallsBackTest: TestDefinition = {
@ -17,6 +18,11 @@ export const failedVfsMoveFallsBackTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContent("B.md", "content A") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("B.md", "content A");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const idempotencyAfterServerPauseTest: TestDefinition = { export const idempotencyAfterServerPauseTest: TestDefinition = {
@ -11,7 +12,12 @@ export const idempotencyAfterServerPauseTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "create", client: 0, path: "doc.md", content: "important data" }, {
type: "create",
client: 0,
path: "doc.md",
content: "important data"
},
{ type: "pause-server" }, { type: "pause-server" },
{ type: "resume-server" }, { type: "resume-server" },
@ -19,6 +25,11 @@ export const idempotencyAfterServerPauseTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContent("doc.md", "important data") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("doc.md", "important data");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const interruptedDeleteRetryTest: TestDefinition = { export const interruptedDeleteRetryTest: TestDefinition = {
@ -20,6 +21,11 @@ export const interruptedDeleteRetryTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(0) }, {
], type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(0);
}
}
]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const keyMigrationEventDropTest: TestDefinition = { export const keyMigrationEventDropTest: TestDefinition = {
@ -30,6 +31,11 @@ export const keyMigrationEventDropTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContent("A.md", "updated content") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("A.md", "updated content");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const localEditLostDuringCreateMergeTest: TestDefinition = { export const localEditLostDuringCreateMergeTest: TestDefinition = {
@ -28,12 +29,13 @@ export const localEditLostDuringCreateMergeTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContains( s.assertFileCount(1).assertContains(
"doc.md", "doc.md",
"from-client-1", "from-client-1",
"local-edit-during-create" "local-edit-during-create"
), );
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const mcCrossCreateRenameSameTargetTest: TestDefinition = { export const mcCrossCreateRenameSameTargetTest: TestDefinition = {
@ -17,7 +18,9 @@ export const mcCrossCreateRenameSameTargetTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertFileExists("X.md").assertFileExists("Y.md") verify: (s: AssertableState): void => {
s.assertFileExists("X.md").assertFileExists("Y.md");
}
}, },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
@ -33,7 +36,7 @@ export const mcCrossCreateRenameSameTargetTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
s.assertFileCount(2) s.assertFileCount(2)
.assertFileNotExists("X.md") .assertFileNotExists("X.md")
.assertFileNotExists("Y.md") .assertFileNotExists("Y.md")

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const mcDeleteThenOfflineRenameTest: TestDefinition = { export const mcDeleteThenOfflineRenameTest: TestDefinition = {
@ -27,10 +28,13 @@ export const mcDeleteThenOfflineRenameTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
s.assertContent("C.md", "unrelated") s.assertContent("C.md", "unrelated").assertFileNotExists(
.assertFileNotExists("A.md"); "A.md"
s.ifFileExists("B.md", (s) => s.assertContent("B.md", "original")); );
s.ifFileExists("B.md", (inner) =>
inner.assertContent("B.md", "original")
);
} }
} }
] ]

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const mcMultiDeleteOfflineRenameTest: TestDefinition = { export const mcMultiDeleteOfflineRenameTest: TestDefinition = {
@ -22,7 +23,12 @@ export const mcMultiDeleteOfflineRenameTest: TestDefinition = {
{ type: "delete", client: 1, path: "file-4.md" }, { type: "delete", client: 1, path: "file-4.md" },
{ type: "sync", client: 1 }, { type: "sync", client: 1 },
{ type: "rename", client: 0, oldPath: "file-2.md", newPath: "renamed.md" }, {
type: "rename",
client: 0,
oldPath: "file-2.md",
newPath: "renamed.md"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "sync" }, { type: "sync" },
@ -30,13 +36,15 @@ export const mcMultiDeleteOfflineRenameTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
s.assertFileExists("file-1.md") s.assertFileExists("file-1.md")
.assertFileExists("file-3.md") .assertFileExists("file-3.md")
.assertFileExists("file-5.md") .assertFileExists("file-5.md")
.assertFileNotExists("file-2.md") .assertFileNotExists("file-2.md")
.assertFileNotExists("file-4.md"); .assertFileNotExists("file-4.md");
s.ifFileExists("renamed.md", (s) => s.assertContent("renamed.md", "content-2")); s.ifFileExists("renamed.md", (inner) =>
inner.assertContent("renamed.md", "content-2")
);
} }
} }
] ]

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const mcThreeClientRenameOfflineUpdateTest: TestDefinition = { export const mcThreeClientRenameOfflineUpdateTest: TestDefinition = {
@ -19,12 +20,24 @@ export const mcThreeClientRenameOfflineUpdateTest: TestDefinition = {
{ type: "sync", client: 1 }, { type: "sync", client: 1 },
{ type: "sync", client: 0 }, { type: "sync", client: 0 },
{ type: "update", client: 2, path: "A.md", content: "updated-by-client-2" }, {
type: "update",
client: 2,
path: "A.md",
content: "updated-by-client-2"
},
{ type: "enable-sync", client: 2 }, { type: "enable-sync", client: 2 },
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertFileNotExists("A.md").assertContains("B.md", "updated-by-client-2") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1)
.assertFileNotExists("A.md")
.assertContains("B.md", "updated-by-client-2");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const migrateKeyPreservesExistingTest: TestDefinition = { export const migrateKeyPreservesExistingTest: TestDefinition = {
@ -25,6 +26,14 @@ export const migrateKeyPreservesExistingTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContains("A.md", "updated by client 0") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContains(
"A.md",
"updated by client 0"
);
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const moveAndConcurrentRemoteUpdateTest: TestDefinition = { export const moveAndConcurrentRemoteUpdateTest: TestDefinition = {
@ -32,6 +33,13 @@ export const moveAndConcurrentRemoteUpdateTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertFileNotExists("A.md").assertContains("B.md", "updated by client 1") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(1)
.assertFileNotExists("A.md")
.assertContains("B.md", "updated by client 1");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const movePreservesRemoteUpdateTest: TestDefinition = { export const movePreservesRemoteUpdateTest: TestDefinition = {
@ -6,7 +7,12 @@ export const movePreservesRemoteUpdateTest: TestDefinition = {
"After both reconnect, the renamed file should contain client 1's edit.", "After both reconnect, the renamed file should contain client 1's edit.",
clients: 2, clients: 2,
steps: [ steps: [
{ type: "create", client: 0, path: "doc.md", content: "line 1\nline 2" }, {
type: "create",
client: 0,
path: "doc.md",
content: "line 1\nline 2"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "sync" }, { type: "sync" },
@ -16,7 +22,12 @@ export const movePreservesRemoteUpdateTest: TestDefinition = {
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
{ type: "rename", client: 0, oldPath: "doc.md", newPath: "renamed.md" }, { type: "rename", client: 0, oldPath: "doc.md", newPath: "renamed.md" },
{ type: "update", client: 1, path: "doc.md", content: "line 1\nclient 1 edit\nline 2" }, {
type: "update",
client: 1,
path: "doc.md",
content: "line 1\nclient 1 edit\nline 2"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
@ -25,13 +36,15 @@ export const movePreservesRemoteUpdateTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
s.assertFileCount(1); s.assertFileCount(1);
const content = Array.from(s.files.values())[0]; const [content] = Array.from(s.files.values());
if (!content.includes("client 1 edit")) { if (!content.includes("client 1 edit")) {
throw new Error(`Expected merged content to include "client 1 edit", got: "${content}"`); throw new Error(
`Expected merged content to include "client 1 edit", got: "${content}"`
);
} }
} }
}, }
], ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const moveRemoteUpdateRevertsRenameTest: TestDefinition = { export const moveRemoteUpdateRevertsRenameTest: TestDefinition = {
@ -13,7 +14,12 @@ export const moveRemoteUpdateRevertsRenameTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "update", client: 1, path: "doc.md", content: "updated by client 1" }, {
type: "update",
client: 1,
path: "doc.md",
content: "updated by client 1"
},
{ type: "sync", client: 1 }, { type: "sync", client: 1 },
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
@ -23,11 +29,13 @@ export const moveRemoteUpdateRevertsRenameTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
s.assertFileCount(1); s.assertFileCount(1);
const content = Array.from(s.files.values())[0]; const [content] = Array.from(s.files.values());
if (content !== "updated by client 1") { if (content !== "updated by client 1") {
throw new Error(`Expected "updated by client 1", got: "${content}"`); throw new Error(
`Expected "updated by client 1", got: "${content}"`
);
} }
} }
} }

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const moveThenDeleteStalePathTest: TestDefinition = { export const moveThenDeleteStalePathTest: TestDefinition = {
@ -23,6 +24,13 @@ export const moveThenDeleteStalePathTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(0).assertFileNotExists("A.md").assertFileNotExists("B.md") } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(0)
.assertFileNotExists("A.md")
.assertFileNotExists("B.md");
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const multiFileOperationsTest: TestDefinition = { export const multiFileOperationsTest: TestDefinition = {
@ -19,7 +20,12 @@ export const multiFileOperationsTest: TestDefinition = {
{ type: "delete", client: 0, path: "A.md" }, { type: "delete", client: 0, path: "A.md" },
{ type: "sync", client: 0 }, { type: "sync", client: 0 },
{ type: "update", client: 1, path: "B.md", content: "updated by client 1" }, {
type: "update",
client: 1,
path: "B.md",
content: "updated by client 1"
},
{ type: "rename", client: 1, oldPath: "A.md", newPath: "D.md" }, { type: "rename", client: 1, oldPath: "A.md", newPath: "D.md" },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
@ -28,11 +34,13 @@ export const multiFileOperationsTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
s.assertContains("B.md", "updated") s.assertContains("B.md", "updated")
.assertFileExists("C.md") .assertFileExists("C.md")
.assertFileNotExists("A.md"); .assertFileNotExists("A.md");
s.ifFileExists("D.md", (s) => s.assertContent("D.md", "content-a")); s.ifFileExists("D.md", (inner) =>
inner.assertContent("D.md", "content-a")
);
} }
} }
] ]

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineConcurrentRenamesTest: TestDefinition = { export const offlineConcurrentRenamesTest: TestDefinition = {
@ -15,7 +16,9 @@ export const offlineConcurrentRenamesTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "shared-content") verify: (s: AssertableState): void => {
s.assertContent("A.md", "shared-content");
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
@ -42,15 +45,15 @@ export const offlineConcurrentRenamesTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
s.assertFileNotExists("A.md") s.assertFileNotExists("A.md")
.assertFileCount(1) .assertFileCount(1)
.assertAnyFileContains("shared-content"); .assertAnyFileContains("shared-content");
s.ifFileExists("B.md", (s) => s.ifFileExists("B.md", (inner) =>
s.assertContent("B.md", "shared-content") inner.assertContent("B.md", "shared-content")
); );
s.ifFileExists("C.md", (s) => s.ifFileExists("C.md", (inner) =>
s.assertContent("C.md", "shared-content") inner.assertContent("C.md", "shared-content")
); );
} }
} }

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineCreateSamePathMergeableTest: TestDefinition = { export const offlineCreateSamePathMergeableTest: TestDefinition = {
@ -27,15 +28,15 @@ export const offlineCreateSamePathMergeableTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileCount(1)
.assertFileCount(1)
.assertFileExists("notes.md") .assertFileExists("notes.md")
.assertContains( .assertContains(
"notes.md", "notes.md",
"alpha wrote this line", "alpha wrote this line",
"beta wrote this different line" "beta wrote this different line"
) );
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineDeleteRemoteRenameTest: TestDefinition = { export const offlineDeleteRemoteRenameTest: TestDefinition = {
@ -27,9 +28,10 @@ export const offlineDeleteRemoteRenameTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
s.assertFileNotExists("A.md") s.assertFileNotExists("A.md").assertFileNotExists(
.assertFileNotExists("A_renamed.md"); "A_renamed.md"
);
} }
} }
] ]

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineDeleteVsRemoteUpdateTest: TestDefinition = { export const offlineDeleteVsRemoteUpdateTest: TestDefinition = {
@ -17,7 +18,9 @@ export const offlineDeleteVsRemoteUpdateTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "original content") verify: (s: AssertableState): void => {
s.assertContent("A.md", "original content");
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
@ -37,7 +40,9 @@ export const offlineDeleteVsRemoteUpdateTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertFileCount(0) verify: (s: AssertableState): void => {
s.assertFileCount(0);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineEditRemoteRenameTest: TestDefinition = { export const offlineEditRemoteRenameTest: TestDefinition = {
@ -13,7 +14,9 @@ export const offlineEditRemoteRenameTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "original") verify: (s: AssertableState): void => {
s.assertContent("A.md", "original");
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
@ -38,11 +41,11 @@ export const offlineEditRemoteRenameTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileNotExists("A.md")
.assertFileNotExists("A.md")
.assertFileCount(1) .assertFileCount(1)
.assertContains("B.md", "edited by client 0") .assertContains("B.md", "edited by client 0");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineEditThenMoveSameContentTest: TestDefinition = { export const offlineEditThenMoveSameContentTest: TestDefinition = {
@ -41,12 +42,12 @@ export const offlineEditThenMoveSameContentTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileNotExists("A.md")
.assertFileNotExists("A.md")
.assertFileNotExists("B.md") .assertFileNotExists("B.md")
.assertContent("C.md", "content A") .assertContent("C.md", "content A")
.assertFileCount(1) .assertFileCount(1);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineMixedOperationsTest: TestDefinition = { export const offlineMixedOperationsTest: TestDefinition = {
@ -17,11 +18,11 @@ export const offlineMixedOperationsTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertContent("file1.md", "content-1")
.assertContent("file1.md", "content-1")
.assertContent("file2.md", "content-2") .assertContent("file2.md", "content-2")
.assertContent("file3.md", "content-3") .assertContent("file3.md", "content-3");
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
@ -46,13 +47,13 @@ export const offlineMixedOperationsTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileNotExists("file1.md")
.assertFileNotExists("file1.md")
.assertFileNotExists("file2.md") .assertFileNotExists("file2.md")
.assertContent("moved.md", "content-2") .assertContent("moved.md", "content-2")
.assertContent("file3.md", "updated-content-3") .assertContent("file3.md", "updated-content-3")
.assertFileCount(2) .assertFileCount(2);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineMoveThenRemoteDeleteTest: TestDefinition = { export const offlineMoveThenRemoteDeleteTest: TestDefinition = {
@ -29,11 +30,11 @@ export const offlineMoveThenRemoteDeleteTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileNotExists("A.md")
.assertFileNotExists("A.md")
.assertFileNotExists("B.md") .assertFileNotExists("B.md")
.assertFileCount(0) .assertFileCount(0);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineMultipleEditsTest: TestDefinition = { export const offlineMultipleEditsTest: TestDefinition = {
@ -14,7 +15,9 @@ export const offlineMultipleEditsTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("doc.md", "original") verify: (s: AssertableState): void => {
s.assertContent("doc.md", "original");
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
@ -31,8 +34,9 @@ export const offlineMultipleEditsTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("doc.md", "edit-5-final") s.assertFileCount(1).assertContent("doc.md", "edit-5-final");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineRenameAndEditTest: TestDefinition = { export const offlineRenameAndEditTest: TestDefinition = {
@ -14,12 +15,19 @@ export const offlineRenameAndEditTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "original") verify: (s: AssertableState): void => {
s.assertContent("A.md", "original");
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" }, { type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" },
{ type: "update", client: 0, path: "B.md", content: "edited after rename" }, {
type: "update",
client: 0,
path: "B.md",
content: "edited after rename"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "sync" }, { type: "sync" },
@ -27,11 +35,11 @@ export const offlineRenameAndEditTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileNotExists("A.md")
.assertFileNotExists("A.md")
.assertFileCount(1) .assertFileCount(1)
.assertContent("B.md", "edited after rename") .assertContent("B.md", "edited after rename");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineRenameRemoteCreateOldPathTest: TestDefinition = { export const offlineRenameRemoteCreateOldPathTest: TestDefinition = {
@ -14,7 +15,9 @@ export const offlineRenameRemoteCreateOldPathTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("X.md", "original") verify: (s: AssertableState): void => {
s.assertContent("X.md", "original");
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
@ -39,10 +42,12 @@ export const offlineRenameRemoteCreateOldPathTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileCount(1).assertContains(
.assertFileCount(1) "Y.md",
.assertContains("Y.md", "updated-by-client-1") "updated-by-client-1"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const offlineUpdateBothThenDeleteOneTest: TestDefinition = { export const offlineUpdateBothThenDeleteOneTest: TestDefinition = {
@ -26,10 +27,12 @@ export const offlineUpdateBothThenDeleteOneTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertContent("A.md", "A original").assertContent(
.assertContent("A.md", "A original") "B.md",
.assertContent("B.md", "B original") "B original"
);
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
@ -63,10 +66,12 @@ export const offlineUpdateBothThenDeleteOneTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertContent(
.assertContent("A.md", "A updated by client 0") "A.md",
.assertFileNotExists("B.md") "A updated by client 0"
).assertFileNotExists("B.md");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const onlineBothCreateSamePathDeconflictTest: TestDefinition = { export const onlineBothCreateSamePathDeconflictTest: TestDefinition = {
@ -23,7 +24,7 @@ export const onlineBothCreateSamePathDeconflictTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(1) .assertFileCount(1)
.assertContains("A.md", "updated-by-0", "from-client-1 "); .assertContains("A.md", "updated-by-0", "from-client-1 ");

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const onlineCreateRenameConcurrentCreateOrphanTest: TestDefinition = { export const onlineCreateRenameConcurrentCreateOrphanTest: TestDefinition = {
@ -12,8 +13,18 @@ export const onlineCreateRenameConcurrentCreateOrphanTest: TestDefinition = {
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "create", client: 0, path: "data.bin", content: "BINARY:offline-content" }, {
{ type: "rename", client: 0, oldPath: "data.bin", newPath: "moved.bin" }, type: "create",
client: 0,
path: "data.bin",
content: "BINARY:offline-content"
},
{
type: "rename",
client: 0,
oldPath: "data.bin",
newPath: "moved.bin"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "delete", client: 0, path: "moved.bin" }, { type: "delete", client: 0, path: "moved.bin" },
@ -22,7 +33,7 @@ export const onlineCreateRenameConcurrentCreateOrphanTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state.assertFileCount(0); state.assertFileCount(0);
} }
} }

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const onlineCreateUpdateWhileOtherCreatesSamePathTest: TestDefinition = { export const onlineCreateUpdateWhileOtherCreatesSamePathTest: TestDefinition = {
@ -11,16 +12,33 @@ export const onlineCreateUpdateWhileOtherCreatesSamePathTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "pause-websocket", client: 1 }, { type: "pause-websocket", client: 1 },
{ type: "create", client: 0, path: "data.bin", content: "BINARY:content-v1" }, {
{ type: "update", client: 0, path: "data.bin", content: "BINARY:content-v2" }, type: "create",
{ type: "create", client: 1, path: "data.bin", content: "BINARY:other-content" }, client: 0,
path: "data.bin",
content: "BINARY:content-v1"
},
{
type: "update",
client: 0,
path: "data.bin",
content: "BINARY:content-v2"
},
{
type: "create",
client: 1,
path: "data.bin",
content: "BINARY:other-content"
},
{ type: "resume-websocket", client: 1 }, { type: "resume-websocket", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", verify: (state) => { type: "assert-consistent",
state.assertFileCount(2) verify: (state: AssertableState): void => {
state
.assertFileCount(2)
.assertContains("data.bin", "content-v2") .assertContains("data.bin", "content-v2")
.assertContains("data (1).bin", "other-content"); .assertContains("data (1).bin", "other-content");
} }

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const onlineDeleteRecreateRapidCycleTest: TestDefinition = { export const onlineDeleteRecreateRapidCycleTest: TestDefinition = {
@ -28,7 +29,9 @@ export const onlineDeleteRecreateRapidCycleTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "round 3"), verify: (s: AssertableState): void => {
}, s.assertContent("A.md", "round 3");
], }
}
]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const onlineEditVsDeleteConvergenceTest: TestDefinition = { export const onlineEditVsDeleteConvergenceTest: TestDefinition = {
@ -11,17 +12,22 @@ export const onlineEditVsDeleteConvergenceTest: TestDefinition = {
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "barrier" }, { type: "barrier" },
{ type: "update", client: 0, path: "A.md", content: "edited by client 0" }, {
type: "update",
client: 0,
path: "A.md",
content: "edited by client 0"
},
{ type: "delete", client: 1, path: "A.md" }, { type: "delete", client: 1, path: "A.md" },
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state.ifFileExists("A.md", (s) => state.ifFileExists("A.md", (s) =>
s.assertContainsAny("A.md", "edited by client 0") s.assertContainsAny("A.md", "edited by client 0")
); );
} }
}, }
], ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const overlappingEditsSameSectionTest: TestDefinition = { export const overlappingEditsSameSectionTest: TestDefinition = {
@ -41,9 +42,15 @@ export const overlappingEditsSameSectionTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileCount(1) s.assertFileCount(1).assertContains(
.assertContains("doc.md", "# Title", "alpha addition", "beta addition", "footer"), "doc.md",
"# Title",
"alpha addition",
"beta addition",
"footer"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const queueResetLosesCoalescedLocalEditTest: TestDefinition = { export const queueResetLosesCoalescedLocalEditTest: TestDefinition = {
@ -23,8 +24,13 @@ export const queueResetLosesCoalescedLocalEditTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContains("doc.md", "alpha", "charlie"), s.assertFileCount(1).assertContains(
"doc.md",
"alpha",
"charlie"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const rapidCreateUpdateDeleteCycleTest: TestDefinition = { export const rapidCreateUpdateDeleteCycleTest: TestDefinition = {
@ -41,7 +42,12 @@ export const rapidCreateUpdateDeleteCycleTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertFileCount(1).assertContent("cycle.md", "final creation"), verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent(
"cycle.md",
"final creation"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const rapidEditDeleteOnlineConvergenceTest: TestDefinition = { export const rapidEditDeleteOnlineConvergenceTest: TestDefinition = {
@ -28,17 +29,20 @@ export const rapidEditDeleteOnlineConvergenceTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
for (const [path, content] of s.files) { for (const [path, content] of s.files) {
for (const clientFiles of s.clientFiles) { for (const clientFiles of s.clientFiles) {
if (clientFiles.has(path) && clientFiles.get(path) !== content) { if (
clientFiles.has(path) &&
clientFiles.get(path) !== content
) {
throw new Error( throw new Error(
`Content mismatch for ${path}: "${clientFiles.get(path)}" vs "${content}"` `Content mismatch for ${path}: "${clientFiles.get(path)}" vs "${content}"`
); );
} }
} }
} }
}, }
}, }
], ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const rapidUpdatesAfterMergeTest: TestDefinition = { export const rapidUpdatesAfterMergeTest: TestDefinition = {
@ -42,7 +43,9 @@ export const rapidUpdatesAfterMergeTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertFileCount(1).assertContains("doc.md", "update 3"), verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContains("doc.md", "update 3");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const recentlyDeletedClearedOnReconnectTest: TestDefinition = { export const recentlyDeletedClearedOnReconnectTest: TestDefinition = {
@ -19,7 +20,12 @@ export const recentlyDeletedClearedOnReconnectTest: TestDefinition = {
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
{ type: "create", client: 1, path: "doc.md", content: "new content from client 1" }, {
type: "create",
client: 1,
path: "doc.md",
content: "new content from client 1"
},
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "sync", client: 1 }, { type: "sync", client: 1 },
@ -28,8 +34,12 @@ export const recentlyDeletedClearedOnReconnectTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("doc.md", "new content from client 1"), s.assertFileCount(1).assertContent(
}, "doc.md",
], "new content from client 1"
);
}
}
]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameChainThenDeleteTest: TestDefinition = { export const renameChainThenDeleteTest: TestDefinition = {
@ -13,7 +14,9 @@ export const renameChainThenDeleteTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("X.md", "chain-content"), verify: (s: AssertableState): void => {
s.assertContent("X.md", "chain-content");
}
}, },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
@ -39,6 +42,11 @@ export const renameChainThenDeleteTest: TestDefinition = {
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ type: "assert-consistent", verify: (s) => s.assertFileCount(0) } {
type: "assert-consistent",
verify: (s: AssertableState): void => {
s.assertFileCount(0);
}
}
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameChainTest: TestDefinition = { export const renameChainTest: TestDefinition = {
@ -9,7 +10,12 @@ export const renameChainTest: TestDefinition = {
steps: [ steps: [
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "create", client: 0, path: "A.md", content: "important content" }, {
type: "create",
client: 0,
path: "A.md",
content: "important content"
},
{ type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" }, { type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" },
{ type: "rename", client: 0, oldPath: "B.md", newPath: "C.md" }, { type: "rename", client: 0, oldPath: "B.md", newPath: "C.md" },
@ -19,10 +25,11 @@ export const renameChainTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileNotExists("A.md") s.assertFileNotExists("A.md")
.assertFileNotExists("B.md") .assertFileNotExists("B.md")
.assertContent("C.md", "important content"), .assertContent("C.md", "important content");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameCircularTest: TestDefinition = { export const renameCircularTest: TestDefinition = {
@ -13,10 +14,11 @@ export const renameCircularTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertContent("A.md", "content-a") s.assertContent("A.md", "content-a")
.assertContent("B.md", "content-b") .assertContent("B.md", "content-b")
.assertContent("C.md", "content-c"), .assertContent("C.md", "content-c");
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
@ -31,12 +33,13 @@ export const renameCircularTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileNotExists("temp-a.md") s.assertFileNotExists("temp-a.md")
.assertFileCount(3) .assertFileCount(3)
.assertContent("A.md", "content-c") .assertContent("A.md", "content-c")
.assertContent("B.md", "content-a") .assertContent("B.md", "content-a")
.assertContent("C.md", "content-b"), .assertContent("C.md", "content-b");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameCreateConflictTest: TestDefinition = { export const renameCreateConflictTest: TestDefinition = {
@ -12,7 +13,9 @@ export const renameCreateConflictTest: TestDefinition = {
{ type: "sync", client: 1 }, { type: "sync", client: 1 },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "hi"), verify: (s: AssertableState): void => {
s.assertContent("A.md", "hi");
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "rename", client: 1, oldPath: "A.md", newPath: "B.md" }, { type: "rename", client: 1, oldPath: "A.md", newPath: "B.md" },
@ -23,8 +26,9 @@ export const renameCreateConflictTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileNotExists("A.md").assertContent("B.md", "hi"), s.assertFileNotExists("A.md").assertContent("B.md", "hi");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renamePendingCreateBeforeResponseTest: TestDefinition = { export const renamePendingCreateBeforeResponseTest: TestDefinition = {
@ -34,8 +35,12 @@ export const renamePendingCreateBeforeResponseTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("renamed.md", "original-content"), s.assertFileCount(1).assertContent(
"renamed.md",
"original-content"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameRoundtripTest: TestDefinition = { export const renameRoundtripTest: TestDefinition = {
@ -12,7 +13,9 @@ export const renameRoundtripTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "original"), verify: (s: AssertableState): void => {
s.assertContent("A.md", "original");
}
}, },
{ type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" }, { type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" },
@ -21,8 +24,9 @@ export const renameRoundtripTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileNotExists("A.md").assertContent("B.md", "original"), s.assertFileNotExists("A.md").assertContent("B.md", "original");
}
}, },
{ type: "rename", client: 0, oldPath: "B.md", newPath: "A.md" }, { type: "rename", client: 0, oldPath: "B.md", newPath: "A.md" },
@ -31,8 +35,9 @@ export const renameRoundtripTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileNotExists("B.md").assertContent("A.md", "original"), s.assertFileNotExists("B.md").assertContent("A.md", "original");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameSwapTest: TestDefinition = { export const renameSwapTest: TestDefinition = {
@ -15,8 +16,12 @@ export const renameSwapTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertContent("A.md", "content-a").assertContent("B.md", "content-b"), s.assertContent("A.md", "content-a").assertContent(
"B.md",
"content-b"
);
}
}, },
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
@ -29,12 +34,12 @@ export const renameSwapTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileNotExists("temp.md")
.assertFileNotExists("temp.md")
.assertFileCount(2) .assertFileCount(2)
.assertContent("A.md", "content-b") .assertContent("A.md", "content-b")
.assertContent("B.md", "content-a"), .assertContent("B.md", "content-a");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameToExistingPathTest: TestDefinition = { export const renameToExistingPathTest: TestDefinition = {
@ -19,8 +20,9 @@ export const renameToExistingPathTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileNotExists("A.md").assertContent("B.md", "alpha"), s.assertFileNotExists("A.md").assertContent("B.md", "alpha");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameToPathOfUnconfirmedDeleteTest: TestDefinition = { export const renameToPathOfUnconfirmedDeleteTest: TestDefinition = {
@ -32,10 +33,12 @@ export const renameToPathOfUnconfirmedDeleteTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileNotExists("B.md").assertContains(
.assertFileNotExists("B.md") "A.md",
.assertContains("A.md", "content B"), "content B"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameToPendingPathFallbackTest: TestDefinition = { export const renameToPendingPathFallbackTest: TestDefinition = {
@ -5,7 +6,12 @@ export const renameToPendingPathFallbackTest: TestDefinition = {
"Client 0 creates B.md and syncs. Goes offline, creates A.md, then renames B.md to A.md (overwriting the unsynced A). After reconnecting, B.md should be gone and A.md should have B's content.", "Client 0 creates B.md and syncs. Goes offline, creates A.md, then renames B.md to A.md (overwriting the unsynced A). After reconnecting, B.md should be gone and A.md should have B's content.",
clients: 2, clients: 2,
steps: [ steps: [
{ type: "create", client: 0, path: "B.md", content: "tracked B content" }, {
type: "create",
client: 0,
path: "B.md",
content: "tracked B content"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "sync" }, { type: "sync" },
@ -13,7 +19,12 @@ export const renameToPendingPathFallbackTest: TestDefinition = {
{ type: "disable-sync", client: 0 }, { type: "disable-sync", client: 0 },
{ type: "create", client: 0, path: "A.md", content: "pending A content" }, {
type: "create",
client: 0,
path: "A.md",
content: "pending A content"
},
{ type: "rename", client: 0, oldPath: "B.md", newPath: "A.md" }, { type: "rename", client: 0, oldPath: "B.md", newPath: "A.md" },
@ -23,8 +34,12 @@ export const renameToPendingPathFallbackTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileNotExists("B.md").assertContains("A.md", "tracked B content"), s.assertFileNotExists("B.md").assertContains(
"A.md",
"tracked B content"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameToRecentlyDeletedPathTest: TestDefinition = { export const renameToRecentlyDeletedPathTest: TestDefinition = {
@ -30,11 +31,11 @@ export const renameToRecentlyDeletedPathTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileCount(1)
.assertFileCount(1)
.assertFileNotExists("A.md") .assertFileNotExists("A.md")
.assertContent("B.md", "content-a"), .assertContent("B.md", "content-a");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const renameUpdateConflictTest: TestDefinition = { export const renameUpdateConflictTest: TestDefinition = {
@ -12,7 +13,9 @@ export const renameUpdateConflictTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "original"), verify: (s: AssertableState): void => {
s.assertContent("A.md", "original");
}
}, },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
@ -20,7 +23,12 @@ export const renameUpdateConflictTest: TestDefinition = {
{ type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" }, { type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" },
{ type: "sync", client: 0 }, { type: "sync", client: 0 },
{ type: "update", client: 1, path: "A.md", content: "updated by client 1" }, {
type: "update",
client: 1,
path: "A.md",
content: "updated by client 1"
},
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "sync", client: 1 }, { type: "sync", client: 1 },
@ -28,8 +36,9 @@ export const renameUpdateConflictTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileNotExists("A.md").assertContains("B.md", "updated"), s.assertFileNotExists("A.md").assertContains("B.md", "updated");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const resetClearsRecentlyDeletedResurrectionTest: TestDefinition = { export const resetClearsRecentlyDeletedResurrectionTest: TestDefinition = {
@ -26,7 +27,9 @@ export const resetClearsRecentlyDeletedResurrectionTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertFileNotExists("ghost.md"), verify: (s: AssertableState): void => {
s.assertFileNotExists("ghost.md");
}
}, },
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
@ -36,7 +39,9 @@ export const resetClearsRecentlyDeletedResurrectionTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertFileCount(0), verify: (s: AssertableState): void => {
s.assertFileCount(0);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const sequentialCreateDuplicateContentTest: TestDefinition = { export const sequentialCreateDuplicateContentTest: TestDefinition = {
@ -5,7 +6,12 @@ export const sequentialCreateDuplicateContentTest: TestDefinition = {
"Client 0 creates A.md, syncs, then creates B.md with identical content. Both files must remain as separate documents on both clients.", "Client 0 creates A.md, syncs, then creates B.md with identical content. Both files must remain as separate documents on both clients.",
clients: 2, clients: 2,
steps: [ steps: [
{ type: "create", client: 0, path: "A.md", content: "identical content here" }, {
type: "create",
client: 0,
path: "A.md",
content: "identical content here"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "sync" }, { type: "sync" },
@ -13,20 +19,27 @@ export const sequentialCreateDuplicateContentTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "identical content here"), verify: (s: AssertableState): void => {
s.assertContent("A.md", "identical content here");
}
}, },
{ type: "create", client: 0, path: "B.md", content: "identical content here" }, {
type: "create",
client: 0,
path: "B.md",
content: "identical content here"
},
{ type: "sync" }, { type: "sync" },
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileCount(2)
.assertFileCount(2)
.assertContent("A.md", "identical content here") .assertContent("A.md", "identical content here")
.assertContent("B.md", "identical content here"), .assertContent("B.md", "identical content here");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const serverPauseBothClientsCreateTest: TestDefinition = { export const serverPauseBothClientsCreateTest: TestDefinition = {
@ -32,10 +33,12 @@ export const serverPauseBothClientsCreateTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertContains("alpha.md", "from client 0").assertContains(
.assertContains("alpha.md", "from client 0") "beta.md",
.assertContains("beta.md", "from client 1"), "from client 1"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const serverPauseBothEditSameFileTest: TestDefinition = { export const serverPauseBothEditSameFileTest: TestDefinition = {
@ -39,10 +40,13 @@ export const serverPauseBothEditSameFileTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileCount(1).assertContains(
.assertFileCount(1) "shared.md",
.assertContains("shared.md", "edited by client 0", "edited by client 1"), "edited by client 0",
"edited by client 1"
);
}
}, },
{ {
@ -56,8 +60,12 @@ export const serverPauseBothEditSameFileTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContains("shared.md", "post-merge edit from client 0"), s.assertFileCount(1).assertContains(
"shared.md",
"post-merge edit from client 0"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const serverPauseDeleteRecreateTest: TestDefinition = { export const serverPauseDeleteRecreateTest: TestDefinition = {
@ -15,18 +16,23 @@ export const serverPauseDeleteRecreateTest: TestDefinition = {
{ type: "pause-server" }, { type: "pause-server" },
{ type: "create", client: 0, path: "A.md", content: "recreated during contention" }, {
type: "create",
client: 0,
path: "A.md",
content: "recreated during contention"
},
{ type: "resume-server" }, { type: "resume-server" },
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (state) => { verify: (state: AssertableState): void => {
state state
.assertFileCount(1) .assertFileCount(1)
.assertContent("A.md", "recreated during contention"); .assertContent("A.md", "recreated during contention");
} }
}, }
], ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const serverPauseRenameEditResumeTest: TestDefinition = { export const serverPauseRenameEditResumeTest: TestDefinition = {
@ -19,7 +20,9 @@ export const serverPauseRenameEditResumeTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("A.md", "original content"), verify: (s: AssertableState): void => {
s.assertContent("A.md", "original content");
}
}, },
{ type: "pause-server" }, { type: "pause-server" },
@ -39,11 +42,11 @@ export const serverPauseRenameEditResumeTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileCount(1)
.assertFileCount(1)
.assertFileNotExists("A.md") .assertFileNotExists("A.md")
.assertContent("B.md", "edited after rename during pause"), .assertContent("B.md", "edited after rename during pause");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const serverPauseUpdateAndCreateTest: TestDefinition = { export const serverPauseUpdateAndCreateTest: TestDefinition = {
@ -17,7 +18,9 @@ export const serverPauseUpdateAndCreateTest: TestDefinition = {
{ type: "barrier" }, { type: "barrier" },
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => s.assertContent("shared.md", "initial content"), verify: (s: AssertableState): void => {
s.assertContent("shared.md", "initial content");
}
}, },
{ type: "pause-server" }, { type: "pause-server" },
@ -42,10 +45,12 @@ export const serverPauseUpdateAndCreateTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertContent(
.assertContent("shared.md", "updated during pause") "shared.md",
.assertContent("new-file.md", "created by client 1"), "updated during pause"
).assertContent("new-file.md", "created by client 1");
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const simultaneousCreateDeleteSamePathTest: TestDefinition = { export const simultaneousCreateDeleteSamePathTest: TestDefinition = {
@ -18,7 +19,12 @@ export const simultaneousCreateDeleteSamePathTest: TestDefinition = {
{ type: "delete", client: 0, path: "A.md" }, { type: "delete", client: 0, path: "A.md" },
{ type: "sync", client: 0 }, { type: "sync", client: 0 },
{ type: "update", client: 1, path: "A.md", content: "modified by 1 while offline" }, {
type: "update",
client: 1,
path: "A.md",
content: "modified by 1 while offline"
},
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
{ type: "sync", client: 1 }, { type: "sync", client: 1 },
@ -26,14 +32,16 @@ export const simultaneousCreateDeleteSamePathTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => { verify: (s: AssertableState): void => {
s.ifFileExists("A.md", (s) => s.ifFileExists("A.md", (inner) =>
s.assertFileCount(1).assertContent("A.md", "modified by 1 while offline") inner
.assertFileCount(1)
.assertContent("A.md", "modified by 1 while offline")
); );
if (!s.files.has("A.md")) { if (!s.files.has("A.md")) {
s.assertFileCount(0); s.assertFileCount(0);
} }
}, }
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const threeClientRenameCreateDeleteTest: TestDefinition = { export const threeClientRenameCreateDeleteTest: TestDefinition = {
@ -44,10 +45,11 @@ export const threeClientRenameCreateDeleteTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s s.assertFileNotExists("X.md").assertAnyFileContains(
.assertFileNotExists("X.md") "new from C"
.assertAnyFileContains("new from C"), );
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const updateDuringCreateProcessingTest: TestDefinition = { export const updateDuringCreateProcessingTest: TestDefinition = {
@ -32,8 +33,12 @@ export const updateDuringCreateProcessingTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileCount(1).assertContent("file.md", "updated during create"), s.assertFileCount(1).assertContent(
"file.md",
"updated during create"
);
}
} }
] ]
}; };

View file

@ -1,3 +1,4 @@
import type { AssertableState } from "../utils/assertable-state";
import type { TestDefinition } from "../test-definition"; import type { TestDefinition } from "../test-definition";
export const updateDoesNotSurvivesRemoteDeleteTest: TestDefinition = { export const updateDoesNotSurvivesRemoteDeleteTest: TestDefinition = {
@ -14,7 +15,12 @@ export const updateDoesNotSurvivesRemoteDeleteTest: TestDefinition = {
{ type: "disable-sync", client: 1 }, { type: "disable-sync", client: 1 },
{ type: "delete", client: 0, path: "doc.md" }, { type: "delete", client: 0, path: "doc.md" },
{ type: "update", client: 1, path: "doc.md", content: "edited by client 1" }, {
type: "update",
client: 1,
path: "doc.md",
content: "edited by client 1"
},
{ type: "enable-sync", client: 0 }, { type: "enable-sync", client: 0 },
{ type: "enable-sync", client: 1 }, { type: "enable-sync", client: 1 },
@ -22,8 +28,9 @@ export const updateDoesNotSurvivesRemoteDeleteTest: TestDefinition = {
{ {
type: "assert-consistent", type: "assert-consistent",
verify: (s) => verify: (s: AssertableState): void => {
s.assertFileCount(0) s.assertFileCount(0);
}, }
], }
]
}; };

Some files were not shown because too many files have changed in this diff Show more