Format & lint
This commit is contained in:
parent
fefac224b0
commit
7f62273e72
179 changed files with 2210 additions and 1319 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -7,4 +7,4 @@
|
|||
"**/.sqlx": true,
|
||||
"**/target": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,20 +17,25 @@ All tests run in parallel up to a concurrency limit.
|
|||
Clients always start with syncing disabled.
|
||||
|
||||
**File operations** (per-client, fire-and-forget — sync is enqueued but not awaited):
|
||||
|
||||
- `create`, `update`, `rename`, `delete`
|
||||
|
||||
**Sync control:**
|
||||
|
||||
- `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)
|
||||
- `enable-sync` / `disable-sync` — simulate going online/offline
|
||||
|
||||
**WebSocket control** (per-client):
|
||||
|
||||
- `pause-websocket` / `resume-websocket` — buffer/release WebSocket messages for a specific client
|
||||
|
||||
**Server control:**
|
||||
|
||||
- `pause-server` / `resume-server` — SIGSTOP/SIGCONT the server process
|
||||
|
||||
**Assertions:**
|
||||
|
||||
- `assert-consistent` — all clients have identical files; optionally takes a custom `verify(state: AssertableState)` callback
|
||||
|
||||
## Running
|
||||
|
|
@ -57,15 +62,19 @@ npm run test -w deterministic-tests -- -j 4
|
|||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const myScenarioTest: TestDefinition = {
|
||||
description: "Client 0 creates A.md offline. After syncing, both clients should have the file.",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{ type: "create", client: 0, path: "A.md", content: "hello" },
|
||||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "barrier" },
|
||||
{ type: "assert-consistent", verify: (s) => s.assertFileCount(1).assertContent("A.md", "hello") }
|
||||
]
|
||||
description:
|
||||
"Client 0 creates A.md offline. After syncing, both clients should have the file.",
|
||||
clients: 2,
|
||||
steps: [
|
||||
{ type: "create", client: 0, path: "A.md", content: "hello" },
|
||||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "enable-sync", client: 1 },
|
||||
{ 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";
|
||||
|
||||
const TESTS = {
|
||||
// ...
|
||||
"my-scenario": myScenarioTest
|
||||
// ...
|
||||
"my-scenario": myScenarioTest
|
||||
};
|
||||
```
|
||||
|
|
|
|||
|
|
@ -38,137 +38,6 @@ interface NamedTestResult {
|
|||
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(
|
||||
name: string,
|
||||
test: TestDefinition,
|
||||
|
|
@ -229,3 +98,132 @@ async function runDedicatedServerTest(
|
|||
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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
import type { StoredDatabase, SyncSettings, RelativePath, TextWithCursors } from "sync-client";
|
||||
import { SyncClient, debugging, LogLevel } from "sync-client";
|
||||
import type {
|
||||
StoredDatabase,
|
||||
SyncSettings,
|
||||
RelativePath,
|
||||
TextWithCursors
|
||||
} from "sync-client";
|
||||
import { SyncClient, debugging, LogLevel, utils } from "sync-client";
|
||||
import { assert } from "./utils/assert";
|
||||
import { sleep } from "./utils/sleep";
|
||||
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";
|
||||
|
||||
|
||||
|
||||
export class DeterministicAgent extends debugging.InMemoryFileSystem {
|
||||
public readonly clientId: number;
|
||||
private readonly logger: (msg: string) => void;
|
||||
|
|
@ -33,7 +41,7 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
|
|||
}
|
||||
|
||||
public async init(
|
||||
fetchImplementation: typeof globalThis.fetch,
|
||||
fetchImplementation: typeof globalThis.fetch
|
||||
): Promise<void> {
|
||||
this.client = await SyncClient.create({
|
||||
fs: this,
|
||||
|
|
@ -138,7 +146,6 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
|
|||
await this.waitForWebSocket();
|
||||
}
|
||||
|
||||
|
||||
public async getFileContent(path: string): Promise<string> {
|
||||
const bytes = await this.read(path);
|
||||
return new TextDecoder().decode(bytes);
|
||||
|
|
@ -146,9 +153,11 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
|
|||
|
||||
public async cleanup(): Promise<void> {
|
||||
this.log("Cleaning up...");
|
||||
// Guard against uninitialized client (init() failed partway)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!this.client) {
|
||||
// Guard against uninitialized client (init() failed partway).
|
||||
// The class field uses `!:` so TS thinks this is always defined,
|
||||
// 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");
|
||||
return;
|
||||
}
|
||||
|
|
@ -184,11 +193,13 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
|
|||
await super.write(path, content);
|
||||
|
||||
if (isNew) {
|
||||
this.enqueueSync(async () => { this.client.syncLocallyCreatedFile(path); }
|
||||
);
|
||||
this.enqueueSync(async () => {
|
||||
this.client.syncLocallyCreatedFile(path);
|
||||
});
|
||||
} 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
|
||||
): Promise<string> {
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public override async delete(path: RelativePath): Promise<void> {
|
||||
await super.delete(path);
|
||||
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,
|
||||
relativePath: newPath
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private async waitForWebSocket(): Promise<void> {
|
||||
|
|
@ -243,7 +253,7 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
|
|||
*/
|
||||
private async drainPendingSyncOperations(): Promise<void> {
|
||||
while (this.pendingSyncOperations.size > 0) {
|
||||
await Promise.all(this.pendingSyncOperations);
|
||||
await utils.awaitAll([...this.pendingSyncOperations]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,129 @@
|
|||
* A WebSocket wrapper that can pause and resume message delivery.
|
||||
* When paused, incoming messages are buffered. When resumed, buffered
|
||||
* 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 {
|
||||
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 paused = false;
|
||||
private readonly bufferedMessages: MessageEvent[] = [];
|
||||
private paused = false;
|
||||
private externalOnMessage: ((event: MessageEvent) => unknown) | null = null;
|
||||
|
||||
public constructor(url: string | URL, protocols?: string | string[]) {
|
||||
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 => {
|
||||
if (this.paused) {
|
||||
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 {
|
||||
this.ws.send(data);
|
||||
}
|
||||
|
|
@ -118,16 +169,6 @@ export class ManagedWebSocket implements WebSocket {
|
|||
public dispatchEvent(event: Event): boolean {
|
||||
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[] = [];
|
||||
|
||||
public get constructorFn(): typeof globalThis.WebSocket {
|
||||
const factory = this;
|
||||
const ctor = function ManagedWS(
|
||||
url: string | URL,
|
||||
protocols?: string | string[]
|
||||
): ManagedWebSocket {
|
||||
const ws = new ManagedWebSocket(url, protocols);
|
||||
factory.instances.push(ws);
|
||||
return ws;
|
||||
} as unknown as typeof globalThis.WebSocket;
|
||||
|
||||
Object.defineProperty(ctor, "CONNECTING", { value: WebSocket.CONNECTING });
|
||||
Object.defineProperty(ctor, "OPEN", { value: WebSocket.OPEN });
|
||||
Object.defineProperty(ctor, "CLOSING", { value: WebSocket.CLOSING });
|
||||
Object.defineProperty(ctor, "CLOSED", { value: WebSocket.CLOSED });
|
||||
|
||||
return ctor;
|
||||
const trackInstance = (instance: ManagedWebSocket): void => {
|
||||
this.instances.push(instance);
|
||||
};
|
||||
class TrackedManagedWebSocket extends ManagedWebSocket {
|
||||
public constructor(
|
||||
url: string | URL,
|
||||
protocols?: string | string[]
|
||||
) {
|
||||
super(url, protocols);
|
||||
trackInstance(this);
|
||||
}
|
||||
}
|
||||
return TrackedManagedWebSocket;
|
||||
}
|
||||
|
||||
public pause(): void {
|
||||
|
|
|
|||
|
|
@ -42,9 +42,7 @@ export class ServerControl {
|
|||
this._port = reservation.port;
|
||||
// Prefer tmpfs (/host/tmp) over disk-backed /tmp for faster SQLite I/O
|
||||
const tmpBase = fs.existsSync("/host/tmp") ? "/host/tmp" : os.tmpdir();
|
||||
this.tempDir = fs.mkdtempSync(
|
||||
path.join(tmpBase, "vault-link-test-")
|
||||
);
|
||||
this.tempDir = fs.mkdtempSync(path.join(tmpBase, "vault-link-test-"));
|
||||
const tempConfigPath = path.join(this.tempDir, "config.yml");
|
||||
const dbDir = path.join(this.tempDir, "databases");
|
||||
|
||||
|
|
@ -225,7 +223,7 @@ export class ServerControl {
|
|||
}
|
||||
|
||||
private cleanupTempDir(): void {
|
||||
if (this.tempDir) {
|
||||
if (this.tempDir !== undefined) {
|
||||
try {
|
||||
fs.rmSync(this.tempDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
|
|
@ -234,5 +232,4 @@ export class ServerControl {
|
|||
this.tempDir = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,14 +39,18 @@ export class ServerManager {
|
|||
process.on("SIGINT", () => {
|
||||
this.logger.info("Received SIGINT, shutting down...");
|
||||
void this.stopAll()
|
||||
.catch(() => {})
|
||||
.catch(() => {
|
||||
/* no-op */
|
||||
})
|
||||
.then(() => process.exit(130));
|
||||
});
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
this.logger.info("Received SIGTERM, shutting down...");
|
||||
void this.stopAll()
|
||||
.catch(() => {})
|
||||
.catch(() => {
|
||||
/* no-op */
|
||||
})
|
||||
.then(() => process.exit(143));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,10 +102,12 @@ export const TESTS: Partial<Record<string, TestDefinition>> = {
|
|||
"delete-recreate-same-path": deleteRecreateSamePathTest,
|
||||
"offline-rename-and-edit": offlineRenameAndEditTest,
|
||||
"rename-to-existing-path": renameToExistingPathTest,
|
||||
"simultaneous-create-delete-same-path": simultaneousCreateDeleteSamePathTest,
|
||||
"simultaneous-create-delete-same-path":
|
||||
simultaneousCreateDeleteSamePathTest,
|
||||
"idempotency-after-server-pause": idempotencyAfterServerPauseTest,
|
||||
"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-cross-create-rename-same-target": mcCrossCreateRenameSameTargetTest,
|
||||
"mc-delete-then-offline-rename": mcDeleteThenOfflineRenameTest,
|
||||
|
|
@ -117,7 +119,8 @@ export const TESTS: Partial<Record<string, TestDefinition>> = {
|
|||
"rename-swap": renameSwapTest,
|
||||
"rename-circular": renameCircularTest,
|
||||
"rename-roundtrip": renameRoundtripTest,
|
||||
"offline-rename-remote-create-old-path": offlineRenameRemoteCreateOldPathTest,
|
||||
"offline-rename-remote-create-old-path":
|
||||
offlineRenameRemoteCreateOldPathTest,
|
||||
"offline-edit-remote-rename": offlineEditRemoteRenameTest,
|
||||
"rename-chain-then-delete": renameChainThenDeleteTest,
|
||||
"offline-delete-remote-rename": offlineDeleteRemoteRenameTest,
|
||||
|
|
@ -140,34 +143,45 @@ export const TESTS: Partial<Record<string, TestDefinition>> = {
|
|||
"delete-recreate-different-content": deleteRecreateDifferentContentTest,
|
||||
"update-during-create-processing": updateDuringCreateProcessingTest,
|
||||
"offline-move-then-remote-delete": offlineMoveThenRemoteDeleteTest,
|
||||
"reset-clears-recently-deleted-resurrection": resetClearsRecentlyDeletedResurrectionTest,
|
||||
"reset-clears-recently-deleted-resurrection":
|
||||
resetClearsRecentlyDeletedResurrectionTest,
|
||||
"move-then-delete-stale-path": moveThenDeleteStalePathTest,
|
||||
"offline-delete-vs-remote-update": offlineDeleteVsRemoteUpdateTest,
|
||||
"interrupted-delete-retry": interruptedDeleteRetryTest,
|
||||
"update-survives-remote-delete": updateDoesNotSurvivesRemoteDeleteTest,
|
||||
"move-preserves-remote-update": movePreservesRemoteUpdateTest,
|
||||
"recently-deleted-cleared-on-reconnect": recentlyDeletedClearedOnReconnectTest,
|
||||
"recently-deleted-cleared-on-reconnect":
|
||||
recentlyDeletedClearedOnReconnectTest,
|
||||
"migrate-key-preserves-existing": migrateKeyPreservesExistingTest,
|
||||
"failed-vfs-move-falls-back": failedVfsMoveFallsBackTest,
|
||||
"watermark-advances-on-skip": watermarkAdvancesOnSkipTest,
|
||||
"watermark-gap-remote-update-not-recorded": watermarkGapRemoteUpdateNotRecordedTest,
|
||||
"queue-reset-loses-coalesced-local-edit": queueResetLosesCoalescedLocalEditTest,
|
||||
"watermark-gap-remote-update-not-recorded":
|
||||
watermarkGapRemoteUpdateNotRecordedTest,
|
||||
"queue-reset-loses-coalesced-local-edit":
|
||||
queueResetLosesCoalescedLocalEditTest,
|
||||
"rename-to-pending-path-fallback": renameToPendingPathFallbackTest,
|
||||
"move-remote-update-reverts-rename": moveRemoteUpdateRevertsRenameTest,
|
||||
"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,
|
||||
"online-create-rename-concurrent-create-orphan": onlineCreateRenameConcurrentCreateOrphanTest,
|
||||
"online-create-rename-concurrent-create-orphan":
|
||||
onlineCreateRenameConcurrentCreateOrphanTest,
|
||||
"concurrent-rename-first-wins": concurrentRenameFirstWinsTest,
|
||||
"binary-to-text-transition": binaryToTextTransitionTest,
|
||||
"text-pending-create-not-displaced": textPendingCreateNotDisplacedTest,
|
||||
"binary-pending-create-not-displaced": binaryPendingCreateNotDisplacedTest,
|
||||
"coalesce-update-remote-update-data-loss": coalesceUpdateRemoteUpdateDataLossTest,
|
||||
"coalesced-remote-update-watermark-loss": coalescedRemoteUpdateWatermarkLossTest,
|
||||
"concurrent-delete-during-remote-update": concurrentDeleteDuringRemoteUpdateTest,
|
||||
"coalesce-update-remote-update-data-loss":
|
||||
coalesceUpdateRemoteUpdateDataLossTest,
|
||||
"coalesced-remote-update-watermark-loss":
|
||||
coalescedRemoteUpdateWatermarkLossTest,
|
||||
"concurrent-delete-during-remote-update":
|
||||
concurrentDeleteDuringRemoteUpdateTest,
|
||||
"concurrent-edit-exact-same-position": concurrentEditExactSamePositionTest,
|
||||
"concurrent-rename-and-create-at-target-rename-first": concurrentRenameAndCreateAtTargetRenameFirstTest,
|
||||
"concurrent-rename-and-create-at-target-create-first": concurrentRenameAndCreateAtTargetCreateFirstTest,
|
||||
"concurrent-rename-and-create-at-target-rename-first":
|
||||
concurrentRenameAndCreateAtTargetRenameFirstTest,
|
||||
"concurrent-rename-and-create-at-target-create-first":
|
||||
concurrentRenameAndCreateAtTargetCreateFirstTest,
|
||||
"concurrent-rename-same-target": concurrentRenameSameTargetTest,
|
||||
"concurrent-update-diff-consistency": concurrentUpdateDiffConsistencyTest,
|
||||
"user-parenthesized-file-not-deleted": userParenthesizedFileNotDeletedTest,
|
||||
|
|
@ -176,15 +190,19 @@ export const TESTS: Partial<Record<string, TestDefinition>> = {
|
|||
"move-identical-content-ambiguity": moveIdenticalContentAmbiguityTest,
|
||||
"create-update-coalesce-server-pause": createUpdateCoalesceServerPauseTest,
|
||||
"create-during-reconciliation": createDuringReconciliationTest,
|
||||
"create-merge-preserves-renamed-update": createMergePreservesRenamedUpdateTest,
|
||||
"create-merge-preserves-renamed-update":
|
||||
createMergePreservesRenamedUpdateTest,
|
||||
"create-rename-create-same-path": createRenameCreateSamePathTest,
|
||||
"move-chain-three-files": moveChainThreeFilesTest,
|
||||
"delete-by-other-client-then-recreate": deleteByOtherClientThenRecreateTest,
|
||||
"online-delete-recreate-rapid-cycle": onlineDeleteRecreateRapidCycleTest,
|
||||
"online-edit-vs-delete-convergence": onlineEditVsDeleteConvergenceTest,
|
||||
"rapid-edit-delete-online-convergence": rapidEditDeleteOnlineConvergenceTest,
|
||||
"rapid-edit-delete-online-convergence":
|
||||
rapidEditDeleteOnlineConvergenceTest,
|
||||
"server-pause-delete-recreate": serverPauseDeleteRecreateTest,
|
||||
"online-both-create-same-path-deconflict": onlineBothCreateSamePathDeconflictTest,
|
||||
"online-create-update-while-other-creates-same-path": onlineCreateUpdateWhileOtherCreatesSamePathTest,
|
||||
"displaced-file-not-marked-deleted": displacedFileNotMarkedDeletedTest,
|
||||
"online-both-create-same-path-deconflict":
|
||||
onlineBothCreateSamePathDeconflictTest,
|
||||
"online-create-update-while-other-creates-same-path":
|
||||
onlineCreateUpdateWhileOtherCreatesSamePathTest,
|
||||
"displaced-file-not-marked-deleted": displacedFileNotMarkedDeletedTest
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
import type {
|
||||
TestDefinition,
|
||||
TestResult,
|
||||
TestStep
|
||||
} from "./test-definition";
|
||||
import type { TestDefinition, TestResult, TestStep } from "./test-definition";
|
||||
import { DeterministicAgent } from "./deterministic-agent";
|
||||
import type { ServerControl } from "./server-control";
|
||||
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
|
||||
this.agents.push(agent);
|
||||
await withTimeout(
|
||||
agent.init(
|
||||
fetch,
|
||||
),
|
||||
agent.init(fetch),
|
||||
AGENT_INIT_TIMEOUT_MS,
|
||||
`Client ${i} init timed out after ${AGENT_INIT_TIMEOUT_MS}ms`
|
||||
);
|
||||
|
|
@ -276,7 +270,10 @@ export class TestRunner {
|
|||
verify?: (state: AssertableState) => void
|
||||
): Promise<void> {
|
||||
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
|
||||
// where background sync could mutate state between reads.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const textPendingCreateNotDisplacedTest: TestDefinition = {
|
||||
|
|
@ -23,6 +24,13 @@ export const textPendingCreateNotDisplacedTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const concurrentUpdateDiffConsistencyTest: TestDefinition = {
|
||||
|
|
@ -35,6 +36,16 @@ export const concurrentUpdateDiffConsistencyTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const userParenthesizedFileNotDeletedTest: TestDefinition = {
|
||||
|
|
@ -34,7 +35,7 @@ export const userParenthesizedFileNotDeletedTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(3)
|
||||
.assertFileExists("Chapter.bin")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const createDeleteNoopTest: TestDefinition = {
|
||||
|
|
@ -16,6 +17,11 @@ export const createDeleteNoopTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "barrier" },
|
||||
|
||||
{ type: "assert-consistent", verify: (s) => s.assertFileNotExists("temp.md") }
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("temp.md");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const createMergeDeleteTest: TestDefinition = {
|
||||
|
|
@ -16,12 +17,21 @@ export const createMergeDeleteTest: TestDefinition = {
|
|||
|
||||
{
|
||||
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: "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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const moveIdenticalContentAmbiguityTest: TestDefinition = {
|
||||
|
|
@ -31,7 +32,7 @@ export const moveIdenticalContentAmbiguityTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(1)
|
||||
.assertFileNotExists("A.md")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const createUpdateCoalesceServerPauseTest: TestDefinition = {
|
||||
|
|
@ -19,6 +20,13 @@ export const createUpdateCoalesceServerPauseTest: TestDefinition = {
|
|||
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const createDuringReconciliationTest: TestDefinition = {
|
||||
|
|
@ -37,7 +38,7 @@ export const createDuringReconciliationTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(3)
|
||||
.assertContent("A.md", "offline A")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const createMergePreservesRenamedUpdateTest: TestDefinition = {
|
||||
|
|
@ -39,6 +40,13 @@ export const createMergePreservesRenamedUpdateTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const createRenameCreateSamePathTest: TestDefinition = {
|
||||
|
|
@ -22,7 +23,7 @@ export const createRenameCreateSamePathTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(3)
|
||||
.assertContent("B.md", "first file")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const moveChainThreeFilesTest: TestDefinition = {
|
||||
|
|
@ -29,7 +30,7 @@ export const moveChainThreeFilesTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(3)
|
||||
.assertContent("A.md", "was C")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const binaryPendingCreateNotDisplacedTest: TestDefinition = {
|
||||
|
|
@ -23,6 +24,17 @@ export const binaryPendingCreateNotDisplacedTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const coalesceUpdateRemoteUpdateDataLossTest: TestDefinition = {
|
||||
|
|
@ -38,10 +39,14 @@ export const coalesceUpdateRemoteUpdateDataLossTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(1)
|
||||
.assertContains("doc.md", "client 0 addition", "client 1 addition");
|
||||
.assertContains(
|
||||
"doc.md",
|
||||
"client 0 addition",
|
||||
"client 1 addition"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = {
|
||||
|
|
@ -18,7 +19,12 @@ export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = {
|
|||
{ type: "sync", client: 0 },
|
||||
|
||||
{ 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: 1 },
|
||||
|
|
@ -26,13 +32,23 @@ export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ 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: 1 },
|
||||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "enable-sync", client: 1 },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const concurrentDeleteDuringRemoteUpdateTest: TestDefinition = {
|
||||
|
|
@ -21,7 +22,11 @@ export const concurrentDeleteDuringRemoteUpdateTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "barrier" },
|
||||
|
||||
{ type: "assert-consistent", verify: (state) => state.assertFileCount(0) }
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state: AssertableState): void => {
|
||||
state.assertFileCount(0);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const concurrentEditExactSamePositionTest: TestDefinition = {
|
||||
|
|
@ -38,7 +39,7 @@ export const concurrentEditExactSamePositionTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(1)
|
||||
.assertContains("doc.md", "slow", "fast", "brown fox");
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const concurrentRenameAndCreateAtTargetTest: TestDefinition = {
|
||||
|
|
@ -37,10 +38,14 @@ export const concurrentRenameAndCreateAtTargetTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileNotExists("X.md")
|
||||
.assertContains("Y.md", "original file X", "brand new Y content");
|
||||
.assertContains(
|
||||
"Y.md",
|
||||
"original file X",
|
||||
"brand new Y content"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const concurrentRenameAndCreateAtTargetTest: TestDefinition = {
|
||||
|
|
@ -37,7 +38,7 @@ export const concurrentRenameAndCreateAtTargetTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(2)
|
||||
.assertContains("Y (1).md", "original file X")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const concurrentRenameSameTargetTest: TestDefinition = {
|
||||
|
|
@ -25,7 +26,7 @@ export const concurrentRenameSameTargetTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(2)
|
||||
.assertFileNotExists("A.md")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const binaryToTextTransitionTest: TestDefinition = {
|
||||
|
|
@ -8,11 +9,21 @@ export const binaryToTextTransitionTest: TestDefinition = {
|
|||
"offline. The text merge should preserve both edits.",
|
||||
clients: 2,
|
||||
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: 1 },
|
||||
{ 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: 1 },
|
||||
|
|
@ -24,26 +35,63 @@ export const binaryToTextTransitionTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ 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: "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: "enable-sync", client: 1 },
|
||||
{ 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: 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: 1 },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const concurrentRenameFirstWinsTest: TestDefinition = {
|
||||
|
|
@ -8,29 +9,52 @@ export const concurrentRenameFirstWinsTest: TestDefinition = {
|
|||
"edits are merged.",
|
||||
clients: 2,
|
||||
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: 1 },
|
||||
{ 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: 1 },
|
||||
|
||||
{ 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: "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: 1 },
|
||||
{ type: "barrier" },
|
||||
|
||||
{ type: "assert-consistent", verify: (s) => {
|
||||
s.assertFileNotExists("A.md");
|
||||
s.assertFileCount(1);
|
||||
s.assertAnyFileContains("edit from 0", "edit from 1");
|
||||
} },
|
||||
],
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md");
|
||||
s.assertFileCount(1);
|
||||
s.assertAnyFileContains("edit from 0", "edit from 1");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const createRenameResponseSkipsFileTest: TestDefinition = {
|
||||
|
|
@ -29,6 +30,11 @@ export const createRenameResponseSkipsFileTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const deleteByOtherClientThenRecreateTest: TestDefinition = {
|
||||
|
|
@ -14,11 +15,26 @@ export const deleteByOtherClientThenRecreateTest: TestDefinition = {
|
|||
{ type: "delete", client: 1, path: "A.md" },
|
||||
{ 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: "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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const deleteDuringPendingCreateTest: TestDefinition = {
|
||||
|
|
@ -26,6 +27,11 @@ export const deleteDuringPendingCreateTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const deleteRecreateConcurrentUpdateTest: TestDefinition = {
|
||||
|
|
@ -14,7 +15,12 @@ export const deleteRecreateConcurrentUpdateTest: TestDefinition = {
|
|||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
{ 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",
|
||||
|
|
@ -28,6 +34,11 @@ export const deleteRecreateConcurrentUpdateTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const deleteRecreateDifferentContentTest: TestDefinition = {
|
||||
|
|
@ -41,6 +42,15 @@ export const deleteRecreateDifferentContentTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const deleteRecreateSamePathTest: TestDefinition = {
|
||||
|
|
@ -11,7 +12,12 @@ export const deleteRecreateSamePathTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "sync" },
|
||||
{ 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: "delete", client: 0, path: "A.md" },
|
||||
|
|
@ -20,6 +26,11 @@ export const deleteRecreateSamePathTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const deleteRenameConflictTest: TestDefinition = {
|
||||
|
|
@ -12,7 +13,12 @@ export const deleteRenameConflictTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "sync" },
|
||||
{ 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 },
|
||||
|
||||
|
|
@ -25,10 +31,15 @@ export const deleteRenameConflictTest: TestDefinition = {
|
|||
{ type: "sync", client: 1 },
|
||||
{ type: "barrier" },
|
||||
|
||||
{ type: "assert-consistent", verify: (s) => {
|
||||
s.assertContent("B.md", "content-b");
|
||||
s.assertFileNotExists("A.md");
|
||||
s.ifFileExists("C.md", (s) => s.assertContent("C.md", "content-a"));
|
||||
} },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("B.md", "content-b");
|
||||
s.assertFileNotExists("A.md");
|
||||
s.ifFileExists("C.md", (inner) =>
|
||||
inner.assertContent("C.md", "content-a")
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const displacedFileNotMarkedDeletedTest: TestDefinition = {
|
||||
|
|
@ -20,14 +21,19 @@ export const displacedFileNotMarkedDeletedTest: TestDefinition = {
|
|||
{ type: "sync", client: 0 },
|
||||
|
||||
{ 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: "barrier" },
|
||||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileNotExists("A.md")
|
||||
.assertFileExists("B.md")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const doubleOfflineCycleTest: TestDefinition = {
|
||||
|
|
@ -16,7 +17,12 @@ export const doubleOfflineCycleTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ type: "sync" },
|
||||
{ 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 },
|
||||
{
|
||||
|
|
@ -29,7 +35,12 @@ export const doubleOfflineCycleTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "sync" },
|
||||
{ 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 },
|
||||
{
|
||||
|
|
@ -42,7 +53,12 @@ export const doubleOfflineCycleTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "sync" },
|
||||
{ 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 },
|
||||
{
|
||||
|
|
@ -55,6 +71,11 @@ export const doubleOfflineCycleTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 0 },
|
||||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const failedVfsMoveFallsBackTest: TestDefinition = {
|
||||
|
|
@ -17,6 +18,11 @@ export const failedVfsMoveFallsBackTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const idempotencyAfterServerPauseTest: TestDefinition = {
|
||||
|
|
@ -11,7 +12,12 @@ export const idempotencyAfterServerPauseTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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: "resume-server" },
|
||||
|
|
@ -19,6 +25,11 @@ export const idempotencyAfterServerPauseTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const interruptedDeleteRetryTest: TestDefinition = {
|
||||
|
|
@ -20,6 +21,11 @@ export const interruptedDeleteRetryTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ type: "barrier" },
|
||||
|
||||
{ type: "assert-consistent", verify: (s) => s.assertFileCount(0) },
|
||||
],
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(0);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const keyMigrationEventDropTest: TestDefinition = {
|
||||
|
|
@ -30,6 +31,11 @@ export const keyMigrationEventDropTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const localEditLostDuringCreateMergeTest: TestDefinition = {
|
||||
|
|
@ -28,12 +29,13 @@ export const localEditLostDuringCreateMergeTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContains(
|
||||
"doc.md",
|
||||
"from-client-1",
|
||||
"local-edit-during-create"
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const mcCrossCreateRenameSameTargetTest: TestDefinition = {
|
||||
|
|
@ -17,7 +18,9 @@ export const mcCrossCreateRenameSameTargetTest: TestDefinition = {
|
|||
|
||||
{
|
||||
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 },
|
||||
|
|
@ -33,7 +36,7 @@ export const mcCrossCreateRenameSameTargetTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(2)
|
||||
.assertFileNotExists("X.md")
|
||||
.assertFileNotExists("Y.md")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const mcDeleteThenOfflineRenameTest: TestDefinition = {
|
||||
|
|
@ -27,10 +28,13 @@ export const mcDeleteThenOfflineRenameTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
s.assertContent("C.md", "unrelated")
|
||||
.assertFileNotExists("A.md");
|
||||
s.ifFileExists("B.md", (s) => s.assertContent("B.md", "original"));
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("C.md", "unrelated").assertFileNotExists(
|
||||
"A.md"
|
||||
);
|
||||
s.ifFileExists("B.md", (inner) =>
|
||||
inner.assertContent("B.md", "original")
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const mcMultiDeleteOfflineRenameTest: TestDefinition = {
|
||||
|
|
@ -22,7 +23,12 @@ export const mcMultiDeleteOfflineRenameTest: TestDefinition = {
|
|||
{ type: "delete", client: 1, path: "file-4.md" },
|
||||
{ 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: "sync" },
|
||||
|
|
@ -30,13 +36,15 @@ export const mcMultiDeleteOfflineRenameTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileExists("file-1.md")
|
||||
.assertFileExists("file-3.md")
|
||||
.assertFileExists("file-5.md")
|
||||
.assertFileNotExists("file-2.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")
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const mcThreeClientRenameOfflineUpdateTest: TestDefinition = {
|
||||
|
|
@ -19,12 +20,24 @@ export const mcThreeClientRenameOfflineUpdateTest: TestDefinition = {
|
|||
{ type: "sync", client: 1 },
|
||||
{ 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: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const migrateKeyPreservesExistingTest: TestDefinition = {
|
||||
|
|
@ -25,6 +26,14 @@ export const migrateKeyPreservesExistingTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const moveAndConcurrentRemoteUpdateTest: TestDefinition = {
|
||||
|
|
@ -32,6 +33,13 @@ export const moveAndConcurrentRemoteUpdateTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const movePreservesRemoteUpdateTest: TestDefinition = {
|
||||
|
|
@ -6,7 +7,12 @@ export const movePreservesRemoteUpdateTest: TestDefinition = {
|
|||
"After both reconnect, the renamed file should contain client 1's edit.",
|
||||
clients: 2,
|
||||
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: 1 },
|
||||
{ type: "sync" },
|
||||
|
|
@ -16,7 +22,12 @@ export const movePreservesRemoteUpdateTest: TestDefinition = {
|
|||
{ type: "disable-sync", client: 1 },
|
||||
|
||||
{ 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: 1 },
|
||||
|
|
@ -25,13 +36,15 @@ export const movePreservesRemoteUpdateTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1);
|
||||
const content = Array.from(s.files.values())[0];
|
||||
const [content] = Array.from(s.files.values());
|
||||
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}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const moveRemoteUpdateRevertsRenameTest: TestDefinition = {
|
||||
|
|
@ -13,7 +14,12 @@ export const moveRemoteUpdateRevertsRenameTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
|
||||
{ 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: "enable-sync", client: 0 },
|
||||
|
|
@ -23,11 +29,13 @@ export const moveRemoteUpdateRevertsRenameTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1);
|
||||
const content = Array.from(s.files.values())[0];
|
||||
const [content] = Array.from(s.files.values());
|
||||
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}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const moveThenDeleteStalePathTest: TestDefinition = {
|
||||
|
|
@ -23,6 +24,13 @@ export const moveThenDeleteStalePathTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ 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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const multiFileOperationsTest: TestDefinition = {
|
||||
|
|
@ -19,7 +20,12 @@ export const multiFileOperationsTest: TestDefinition = {
|
|||
{ type: "delete", client: 0, path: "A.md" },
|
||||
{ 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: "enable-sync", client: 1 },
|
||||
|
|
@ -28,11 +34,13 @@ export const multiFileOperationsTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContains("B.md", "updated")
|
||||
.assertFileExists("C.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")
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineConcurrentRenamesTest: TestDefinition = {
|
||||
|
|
@ -15,7 +16,9 @@ export const offlineConcurrentRenamesTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
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 },
|
||||
|
|
@ -42,15 +45,15 @@ export const offlineConcurrentRenamesTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md")
|
||||
.assertFileCount(1)
|
||||
.assertAnyFileContains("shared-content");
|
||||
s.ifFileExists("B.md", (s) =>
|
||||
s.assertContent("B.md", "shared-content")
|
||||
s.ifFileExists("B.md", (inner) =>
|
||||
inner.assertContent("B.md", "shared-content")
|
||||
);
|
||||
s.ifFileExists("C.md", (s) =>
|
||||
s.assertContent("C.md", "shared-content")
|
||||
s.ifFileExists("C.md", (inner) =>
|
||||
inner.assertContent("C.md", "shared-content")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineCreateSamePathMergeableTest: TestDefinition = {
|
||||
|
|
@ -27,15 +28,15 @@ export const offlineCreateSamePathMergeableTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileCount(1)
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1)
|
||||
.assertFileExists("notes.md")
|
||||
.assertContains(
|
||||
"notes.md",
|
||||
"alpha wrote this line",
|
||||
"beta wrote this different line"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineDeleteRemoteRenameTest: TestDefinition = {
|
||||
|
|
@ -27,9 +28,10 @@ export const offlineDeleteRemoteRenameTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
s.assertFileNotExists("A.md")
|
||||
.assertFileNotExists("A_renamed.md");
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md").assertFileNotExists(
|
||||
"A_renamed.md"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineDeleteVsRemoteUpdateTest: TestDefinition = {
|
||||
|
|
@ -17,7 +18,9 @@ export const offlineDeleteVsRemoteUpdateTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
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 },
|
||||
|
|
@ -37,7 +40,9 @@ export const offlineDeleteVsRemoteUpdateTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertFileCount(0)
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(0);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineEditRemoteRenameTest: TestDefinition = {
|
||||
|
|
@ -13,7 +14,9 @@ export const offlineEditRemoteRenameTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertContent("A.md", "original")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("A.md", "original");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
|
|
@ -38,11 +41,11 @@ export const offlineEditRemoteRenameTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileNotExists("A.md")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md")
|
||||
.assertFileCount(1)
|
||||
.assertContains("B.md", "edited by client 0")
|
||||
.assertContains("B.md", "edited by client 0");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineEditThenMoveSameContentTest: TestDefinition = {
|
||||
|
|
@ -41,12 +42,12 @@ export const offlineEditThenMoveSameContentTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileNotExists("A.md")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md")
|
||||
.assertFileNotExists("B.md")
|
||||
.assertContent("C.md", "content A")
|
||||
.assertFileCount(1)
|
||||
.assertFileCount(1);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineMixedOperationsTest: TestDefinition = {
|
||||
|
|
@ -17,11 +18,11 @@ export const offlineMixedOperationsTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertContent("file1.md", "content-1")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("file1.md", "content-1")
|
||||
.assertContent("file2.md", "content-2")
|
||||
.assertContent("file3.md", "content-3")
|
||||
.assertContent("file3.md", "content-3");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
|
|
@ -46,13 +47,13 @@ export const offlineMixedOperationsTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileNotExists("file1.md")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("file1.md")
|
||||
.assertFileNotExists("file2.md")
|
||||
.assertContent("moved.md", "content-2")
|
||||
.assertContent("file3.md", "updated-content-3")
|
||||
.assertFileCount(2)
|
||||
.assertFileCount(2);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineMoveThenRemoteDeleteTest: TestDefinition = {
|
||||
|
|
@ -29,11 +30,11 @@ export const offlineMoveThenRemoteDeleteTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileNotExists("A.md")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md")
|
||||
.assertFileNotExists("B.md")
|
||||
.assertFileCount(0)
|
||||
.assertFileCount(0);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineMultipleEditsTest: TestDefinition = {
|
||||
|
|
@ -14,7 +15,9 @@ export const offlineMultipleEditsTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertContent("doc.md", "original")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("doc.md", "original");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
|
|
@ -31,8 +34,9 @@ export const offlineMultipleEditsTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileCount(1).assertContent("doc.md", "edit-5-final")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContent("doc.md", "edit-5-final");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineRenameAndEditTest: TestDefinition = {
|
||||
|
|
@ -14,12 +15,19 @@ export const offlineRenameAndEditTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
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: "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: "sync" },
|
||||
|
|
@ -27,11 +35,11 @@ export const offlineRenameAndEditTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileNotExists("A.md")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md")
|
||||
.assertFileCount(1)
|
||||
.assertContent("B.md", "edited after rename")
|
||||
.assertContent("B.md", "edited after rename");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineRenameRemoteCreateOldPathTest: TestDefinition = {
|
||||
|
|
@ -14,7 +15,9 @@ export const offlineRenameRemoteCreateOldPathTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertContent("X.md", "original")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("X.md", "original");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
|
|
@ -39,10 +42,12 @@ export const offlineRenameRemoteCreateOldPathTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileCount(1)
|
||||
.assertContains("Y.md", "updated-by-client-1")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContains(
|
||||
"Y.md",
|
||||
"updated-by-client-1"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const offlineUpdateBothThenDeleteOneTest: TestDefinition = {
|
||||
|
|
@ -26,10 +27,12 @@ export const offlineUpdateBothThenDeleteOneTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertContent("A.md", "A original")
|
||||
.assertContent("B.md", "B original")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("A.md", "A original").assertContent(
|
||||
"B.md",
|
||||
"B original"
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
|
|
@ -63,10 +66,12 @@ export const offlineUpdateBothThenDeleteOneTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertContent("A.md", "A updated by client 0")
|
||||
.assertFileNotExists("B.md")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent(
|
||||
"A.md",
|
||||
"A updated by client 0"
|
||||
).assertFileNotExists("B.md");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const onlineBothCreateSamePathDeconflictTest: TestDefinition = {
|
||||
|
|
@ -23,7 +24,7 @@ export const onlineBothCreateSamePathDeconflictTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(1)
|
||||
.assertContains("A.md", "updated-by-0", "from-client-1 ");
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const onlineCreateRenameConcurrentCreateOrphanTest: TestDefinition = {
|
||||
|
|
@ -12,8 +13,18 @@ export const onlineCreateRenameConcurrentCreateOrphanTest: TestDefinition = {
|
|||
|
||||
{ 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: "delete", client: 0, path: "moved.bin" },
|
||||
|
|
@ -22,7 +33,7 @@ export const onlineCreateRenameConcurrentCreateOrphanTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state.assertFileCount(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const onlineCreateUpdateWhileOtherCreatesSamePathTest: TestDefinition = {
|
||||
|
|
@ -11,16 +12,33 @@ export const onlineCreateUpdateWhileOtherCreatesSamePathTest: TestDefinition = {
|
|||
{ type: "enable-sync", 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", client: 1, path: "data.bin", content: "BINARY:other-content" },
|
||||
{
|
||||
type: "create",
|
||||
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: "barrier" },
|
||||
|
||||
{
|
||||
type: "assert-consistent", verify: (state) => {
|
||||
state.assertFileCount(2)
|
||||
type: "assert-consistent",
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(2)
|
||||
.assertContains("data.bin", "content-v2")
|
||||
.assertContains("data (1).bin", "other-content");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const onlineDeleteRecreateRapidCycleTest: TestDefinition = {
|
||||
|
|
@ -28,7 +29,9 @@ export const onlineDeleteRecreateRapidCycleTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertContent("A.md", "round 3"),
|
||||
},
|
||||
],
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("A.md", "round 3");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const onlineEditVsDeleteConvergenceTest: TestDefinition = {
|
||||
|
|
@ -11,17 +12,22 @@ export const onlineEditVsDeleteConvergenceTest: TestDefinition = {
|
|||
{ type: "enable-sync", client: 1 },
|
||||
{ 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: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state.ifFileExists("A.md", (s) =>
|
||||
s.assertContainsAny("A.md", "edited by client 0")
|
||||
);
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const overlappingEditsSameSectionTest: TestDefinition = {
|
||||
|
|
@ -41,9 +42,15 @@ export const overlappingEditsSameSectionTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileCount(1)
|
||||
.assertContains("doc.md", "# Title", "alpha addition", "beta addition", "footer"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContains(
|
||||
"doc.md",
|
||||
"# Title",
|
||||
"alpha addition",
|
||||
"beta addition",
|
||||
"footer"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const queueResetLosesCoalescedLocalEditTest: TestDefinition = {
|
||||
|
|
@ -23,8 +24,13 @@ export const queueResetLosesCoalescedLocalEditTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileCount(1).assertContains("doc.md", "alpha", "charlie"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContains(
|
||||
"doc.md",
|
||||
"alpha",
|
||||
"charlie"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const rapidCreateUpdateDeleteCycleTest: TestDefinition = {
|
||||
|
|
@ -41,7 +42,12 @@ export const rapidCreateUpdateDeleteCycleTest: TestDefinition = {
|
|||
|
||||
{
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const rapidEditDeleteOnlineConvergenceTest: TestDefinition = {
|
||||
|
|
@ -28,17 +29,20 @@ export const rapidEditDeleteOnlineConvergenceTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
verify: (s: AssertableState): void => {
|
||||
for (const [path, content] of s.files) {
|
||||
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(
|
||||
`Content mismatch for ${path}: "${clientFiles.get(path)}" vs "${content}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const rapidUpdatesAfterMergeTest: TestDefinition = {
|
||||
|
|
@ -42,7 +43,9 @@ export const rapidUpdatesAfterMergeTest: TestDefinition = {
|
|||
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const recentlyDeletedClearedOnReconnectTest: TestDefinition = {
|
||||
|
|
@ -19,7 +20,12 @@ export const recentlyDeletedClearedOnReconnectTest: TestDefinition = {
|
|||
{ type: "disable-sync", client: 0 },
|
||||
{ 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: "sync", client: 1 },
|
||||
|
|
@ -28,8 +34,12 @@ export const recentlyDeletedClearedOnReconnectTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileCount(1).assertContent("doc.md", "new content from client 1"),
|
||||
},
|
||||
],
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContent(
|
||||
"doc.md",
|
||||
"new content from client 1"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameChainThenDeleteTest: TestDefinition = {
|
||||
|
|
@ -13,7 +14,9 @@ export const renameChainThenDeleteTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
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 },
|
||||
|
|
@ -39,6 +42,11 @@ export const renameChainThenDeleteTest: TestDefinition = {
|
|||
{ type: "sync" },
|
||||
{ type: "barrier" },
|
||||
|
||||
{ type: "assert-consistent", verify: (s) => s.assertFileCount(0) }
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(0);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameChainTest: TestDefinition = {
|
||||
|
|
@ -9,7 +10,12 @@ export const renameChainTest: TestDefinition = {
|
|||
steps: [
|
||||
{ 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: "B.md", newPath: "C.md" },
|
||||
|
||||
|
|
@ -19,10 +25,11 @@ export const renameChainTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md")
|
||||
.assertFileNotExists("B.md")
|
||||
.assertContent("C.md", "important content"),
|
||||
.assertContent("C.md", "important content");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameCircularTest: TestDefinition = {
|
||||
|
|
@ -13,10 +14,11 @@ export const renameCircularTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("A.md", "content-a")
|
||||
.assertContent("B.md", "content-b")
|
||||
.assertContent("C.md", "content-c"),
|
||||
.assertContent("C.md", "content-c");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
|
|
@ -31,12 +33,13 @@ export const renameCircularTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("temp-a.md")
|
||||
.assertFileCount(3)
|
||||
.assertContent("A.md", "content-c")
|
||||
.assertContent("B.md", "content-a")
|
||||
.assertContent("C.md", "content-b"),
|
||||
.assertContent("C.md", "content-b");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameCreateConflictTest: TestDefinition = {
|
||||
|
|
@ -12,7 +13,9 @@ export const renameCreateConflictTest: TestDefinition = {
|
|||
{ type: "sync", client: 1 },
|
||||
{
|
||||
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: "rename", client: 1, oldPath: "A.md", newPath: "B.md" },
|
||||
|
|
@ -23,8 +26,9 @@ export const renameCreateConflictTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileNotExists("A.md").assertContent("B.md", "hi"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md").assertContent("B.md", "hi");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renamePendingCreateBeforeResponseTest: TestDefinition = {
|
||||
|
|
@ -34,8 +35,12 @@ export const renamePendingCreateBeforeResponseTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileCount(1).assertContent("renamed.md", "original-content"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContent(
|
||||
"renamed.md",
|
||||
"original-content"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameRoundtripTest: TestDefinition = {
|
||||
|
|
@ -12,7 +13,9 @@ export const renameRoundtripTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
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" },
|
||||
|
|
@ -21,8 +24,9 @@ export const renameRoundtripTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileNotExists("A.md").assertContent("B.md", "original"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md").assertContent("B.md", "original");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "rename", client: 0, oldPath: "B.md", newPath: "A.md" },
|
||||
|
|
@ -31,8 +35,9 @@ export const renameRoundtripTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileNotExists("B.md").assertContent("A.md", "original"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("B.md").assertContent("A.md", "original");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameSwapTest: TestDefinition = {
|
||||
|
|
@ -15,8 +16,12 @@ export const renameSwapTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertContent("A.md", "content-a").assertContent("B.md", "content-b"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("A.md", "content-a").assertContent(
|
||||
"B.md",
|
||||
"content-b"
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 0 },
|
||||
|
|
@ -29,12 +34,12 @@ export const renameSwapTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileNotExists("temp.md")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("temp.md")
|
||||
.assertFileCount(2)
|
||||
.assertContent("A.md", "content-b")
|
||||
.assertContent("B.md", "content-a"),
|
||||
.assertContent("B.md", "content-a");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameToExistingPathTest: TestDefinition = {
|
||||
|
|
@ -19,8 +20,9 @@ export const renameToExistingPathTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileNotExists("A.md").assertContent("B.md", "alpha"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md").assertContent("B.md", "alpha");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameToPathOfUnconfirmedDeleteTest: TestDefinition = {
|
||||
|
|
@ -32,10 +33,12 @@ export const renameToPathOfUnconfirmedDeleteTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileNotExists("B.md")
|
||||
.assertContains("A.md", "content B"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("B.md").assertContains(
|
||||
"A.md",
|
||||
"content B"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
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.",
|
||||
clients: 2,
|
||||
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: 1 },
|
||||
{ type: "sync" },
|
||||
|
|
@ -13,7 +19,12 @@ export const renameToPendingPathFallbackTest: TestDefinition = {
|
|||
|
||||
{ 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" },
|
||||
|
||||
|
|
@ -23,8 +34,12 @@ export const renameToPendingPathFallbackTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileNotExists("B.md").assertContains("A.md", "tracked B content"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("B.md").assertContains(
|
||||
"A.md",
|
||||
"tracked B content"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameToRecentlyDeletedPathTest: TestDefinition = {
|
||||
|
|
@ -30,11 +31,11 @@ export const renameToRecentlyDeletedPathTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileCount(1)
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1)
|
||||
.assertFileNotExists("A.md")
|
||||
.assertContent("B.md", "content-a"),
|
||||
.assertContent("B.md", "content-a");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const renameUpdateConflictTest: TestDefinition = {
|
||||
|
|
@ -12,7 +13,9 @@ export const renameUpdateConflictTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertContent("A.md", "original"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("A.md", "original");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 1 },
|
||||
|
|
@ -20,7 +23,12 @@ export const renameUpdateConflictTest: TestDefinition = {
|
|||
{ type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" },
|
||||
{ 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: "sync", client: 1 },
|
||||
|
|
@ -28,8 +36,9 @@ export const renameUpdateConflictTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileNotExists("A.md").assertContains("B.md", "updated"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("A.md").assertContains("B.md", "updated");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const resetClearsRecentlyDeletedResurrectionTest: TestDefinition = {
|
||||
|
|
@ -26,7 +27,9 @@ export const resetClearsRecentlyDeletedResurrectionTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertFileNotExists("ghost.md"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("ghost.md");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "disable-sync", client: 1 },
|
||||
|
|
@ -36,7 +39,9 @@ export const resetClearsRecentlyDeletedResurrectionTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertFileCount(0),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(0);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
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.",
|
||||
clients: 2,
|
||||
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: 1 },
|
||||
{ type: "sync" },
|
||||
|
|
@ -13,20 +19,27 @@ export const sequentialCreateDuplicateContentTest: TestDefinition = {
|
|||
|
||||
{
|
||||
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: "barrier" },
|
||||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileCount(2)
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(2)
|
||||
.assertContent("A.md", "identical content here")
|
||||
.assertContent("B.md", "identical content here"),
|
||||
.assertContent("B.md", "identical content here");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const serverPauseBothClientsCreateTest: TestDefinition = {
|
||||
|
|
@ -32,10 +33,12 @@ export const serverPauseBothClientsCreateTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertContains("alpha.md", "from client 0")
|
||||
.assertContains("beta.md", "from client 1"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContains("alpha.md", "from client 0").assertContains(
|
||||
"beta.md",
|
||||
"from client 1"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const serverPauseBothEditSameFileTest: TestDefinition = {
|
||||
|
|
@ -39,10 +40,13 @@ export const serverPauseBothEditSameFileTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileCount(1)
|
||||
.assertContains("shared.md", "edited by client 0", "edited by client 1"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContains(
|
||||
"shared.md",
|
||||
"edited by client 0",
|
||||
"edited by client 1"
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
|
|
@ -56,8 +60,12 @@ export const serverPauseBothEditSameFileTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileCount(1).assertContains("shared.md", "post-merge edit from client 0"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContains(
|
||||
"shared.md",
|
||||
"post-merge edit from client 0"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const serverPauseDeleteRecreateTest: TestDefinition = {
|
||||
|
|
@ -15,18 +16,23 @@ export const serverPauseDeleteRecreateTest: TestDefinition = {
|
|||
|
||||
{ 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: "barrier" },
|
||||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (state) => {
|
||||
verify: (state: AssertableState): void => {
|
||||
state
|
||||
.assertFileCount(1)
|
||||
.assertContent("A.md", "recreated during contention");
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const serverPauseRenameEditResumeTest: TestDefinition = {
|
||||
|
|
@ -19,7 +20,9 @@ export const serverPauseRenameEditResumeTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertContent("A.md", "original content"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("A.md", "original content");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "pause-server" },
|
||||
|
|
@ -39,11 +42,11 @@ export const serverPauseRenameEditResumeTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileCount(1)
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1)
|
||||
.assertFileNotExists("A.md")
|
||||
.assertContent("B.md", "edited after rename during pause"),
|
||||
.assertContent("B.md", "edited after rename during pause");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const serverPauseUpdateAndCreateTest: TestDefinition = {
|
||||
|
|
@ -17,7 +18,9 @@ export const serverPauseUpdateAndCreateTest: TestDefinition = {
|
|||
{ type: "barrier" },
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => s.assertContent("shared.md", "initial content"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent("shared.md", "initial content");
|
||||
}
|
||||
},
|
||||
|
||||
{ type: "pause-server" },
|
||||
|
|
@ -42,10 +45,12 @@ export const serverPauseUpdateAndCreateTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertContent("shared.md", "updated during pause")
|
||||
.assertContent("new-file.md", "created by client 1"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertContent(
|
||||
"shared.md",
|
||||
"updated during pause"
|
||||
).assertContent("new-file.md", "created by client 1");
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const simultaneousCreateDeleteSamePathTest: TestDefinition = {
|
||||
|
|
@ -18,7 +19,12 @@ export const simultaneousCreateDeleteSamePathTest: TestDefinition = {
|
|||
{ type: "delete", client: 0, path: "A.md" },
|
||||
{ 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: "sync", client: 1 },
|
||||
|
|
@ -26,14 +32,16 @@ export const simultaneousCreateDeleteSamePathTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) => {
|
||||
s.ifFileExists("A.md", (s) =>
|
||||
s.assertFileCount(1).assertContent("A.md", "modified by 1 while offline")
|
||||
verify: (s: AssertableState): void => {
|
||||
s.ifFileExists("A.md", (inner) =>
|
||||
inner
|
||||
.assertFileCount(1)
|
||||
.assertContent("A.md", "modified by 1 while offline")
|
||||
);
|
||||
if (!s.files.has("A.md")) {
|
||||
s.assertFileCount(0);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const threeClientRenameCreateDeleteTest: TestDefinition = {
|
||||
|
|
@ -44,10 +45,11 @@ export const threeClientRenameCreateDeleteTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s
|
||||
.assertFileNotExists("X.md")
|
||||
.assertAnyFileContains("new from C"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileNotExists("X.md").assertAnyFileContains(
|
||||
"new from C"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const updateDuringCreateProcessingTest: TestDefinition = {
|
||||
|
|
@ -32,8 +33,12 @@ export const updateDuringCreateProcessingTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileCount(1).assertContent("file.md", "updated during create"),
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(1).assertContent(
|
||||
"file.md",
|
||||
"updated during create"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { AssertableState } from "../utils/assertable-state";
|
||||
import type { TestDefinition } from "../test-definition";
|
||||
|
||||
export const updateDoesNotSurvivesRemoteDeleteTest: TestDefinition = {
|
||||
|
|
@ -14,7 +15,12 @@ export const updateDoesNotSurvivesRemoteDeleteTest: TestDefinition = {
|
|||
{ type: "disable-sync", client: 1 },
|
||||
|
||||
{ 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: 1 },
|
||||
|
|
@ -22,8 +28,9 @@ export const updateDoesNotSurvivesRemoteDeleteTest: TestDefinition = {
|
|||
|
||||
{
|
||||
type: "assert-consistent",
|
||||
verify: (s) =>
|
||||
s.assertFileCount(0)
|
||||
},
|
||||
],
|
||||
verify: (s: AssertableState): void => {
|
||||
s.assertFileCount(0);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue