Refactor tests
This commit is contained in:
parent
16afe31e89
commit
f53ac121e8
19 changed files with 352 additions and 570 deletions
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-console */
|
||||
import { choose } from "../utils/choose";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { assert } from "../utils/assert";
|
||||
|
|
@ -94,22 +95,12 @@ export class MockAgent extends MockClient {
|
|||
}
|
||||
|
||||
public async createInitialDocuments(count: number): Promise<void> {
|
||||
this.client.logger.info(`Creating ${count} initial documents`);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const file = `initial-${i}.md`;
|
||||
this.doNotTouchWhileOffline.push(file);
|
||||
const content = this.getContent();
|
||||
this.client.logger.info(
|
||||
`Creating initial file ${file} with content ${content}`
|
||||
);
|
||||
await this.create(file, new TextEncoder().encode(` ${content} `), {
|
||||
ignoreSlowFileEvents: true
|
||||
});
|
||||
this.files.set(file, new TextEncoder().encode(` ${content} `));
|
||||
}
|
||||
|
||||
// Wait for all initial documents to sync
|
||||
await this.client.waitUntilFinished();
|
||||
this.client.logger.info(`Initial documents created and synced`);
|
||||
}
|
||||
|
||||
public async waitUntilSynced(): Promise<void> {
|
||||
|
|
@ -159,7 +150,7 @@ export class MockAgent extends MockClient {
|
|||
JSON.stringify(this.data, null, 2)
|
||||
);
|
||||
this.client.logger.info(
|
||||
JSON.stringify(this.localFiles, null, 2)
|
||||
JSON.stringify(this.files, null, 2)
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
|
@ -192,14 +183,14 @@ export class MockAgent extends MockClient {
|
|||
}
|
||||
|
||||
public assertFileSystemsAreConsistent(otherAgent: MockAgent): void {
|
||||
const globalFiles = Array.from(otherAgent.localFiles.keys());
|
||||
const localFiles = Array.from(this.localFiles.keys());
|
||||
const globalFiles = Array.from(otherAgent.files.keys());
|
||||
const localFiles = Array.from(this.files.keys());
|
||||
|
||||
const missingInOther = localFiles.filter(
|
||||
(file) => !otherAgent.localFiles.has(file)
|
||||
(file) => !otherAgent.files.has(file)
|
||||
);
|
||||
const missingInLocal = globalFiles.filter(
|
||||
(file) => !this.localFiles.has(file)
|
||||
(file) => !this.files.has(file)
|
||||
);
|
||||
|
||||
try {
|
||||
|
|
@ -214,10 +205,10 @@ export class MockAgent extends MockClient {
|
|||
|
||||
for (const file of globalFiles) {
|
||||
const localContent = new TextDecoder().decode(
|
||||
this.localFiles.get(file)
|
||||
this.files.get(file)
|
||||
);
|
||||
const otherContent = new TextDecoder().decode(
|
||||
otherAgent.localFiles.get(file)
|
||||
otherAgent.files.get(file)
|
||||
);
|
||||
assert(
|
||||
localContent === otherContent,
|
||||
|
|
@ -229,15 +220,13 @@ export class MockAgent extends MockClient {
|
|||
"Local data: " + JSON.stringify(this.data, null, 2)
|
||||
);
|
||||
this.client.logger.info(
|
||||
"Local files: " +
|
||||
Array.from(otherAgent.localFiles.keys()).join(", ")
|
||||
"Local files: " + Array.from(otherAgent.files.keys()).join(", ")
|
||||
);
|
||||
otherAgent.client.logger.info(
|
||||
"Local data: " + JSON.stringify(otherAgent.data, null, 2)
|
||||
);
|
||||
otherAgent.client.logger.info(
|
||||
"Local files: " +
|
||||
Array.from(otherAgent.localFiles.keys()).join(", ")
|
||||
"Local files: " + Array.from(otherAgent.files.keys()).join(", ")
|
||||
);
|
||||
|
||||
throw e;
|
||||
|
|
@ -254,9 +243,9 @@ export class MockAgent extends MockClient {
|
|||
}
|
||||
|
||||
for (const content of this.writtenContents) {
|
||||
const found = Array.from(this.localFiles.keys()).filter((key) => {
|
||||
const found = Array.from(this.files.keys()).filter((key) => {
|
||||
return new TextDecoder()
|
||||
.decode(this.localFiles.get(key))
|
||||
.decode(this.files.get(key))
|
||||
.includes(content);
|
||||
});
|
||||
|
||||
|
|
@ -278,7 +267,7 @@ export class MockAgent extends MockClient {
|
|||
|
||||
const [file] = found;
|
||||
const fileContent = new TextDecoder().decode(
|
||||
this.localFiles.get(file)
|
||||
this.files.get(file)
|
||||
);
|
||||
assert(
|
||||
fileContent.split(content).length == 2,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@ import type { StoredDatabase, TextWithCursors } from "sync-client";
|
|||
import { assert } from "../utils/assert";
|
||||
import {
|
||||
type RelativePath,
|
||||
type FileSystemOperations,
|
||||
type SyncSettings,
|
||||
SyncClient
|
||||
SyncClient,
|
||||
debugging
|
||||
} from "sync-client";
|
||||
|
||||
export class MockClient implements FileSystemOperations {
|
||||
protected readonly localFiles = new Map<string, Uint8Array>();
|
||||
export class MockClient extends debugging.InMemoryFileSystem {
|
||||
protected client!: SyncClient;
|
||||
|
||||
protected data: Partial<{
|
||||
|
|
@ -20,6 +19,7 @@ export class MockClient implements FileSystemOperations {
|
|||
initialSettings: Partial<SyncSettings>,
|
||||
protected readonly useSlowFileEvents: boolean
|
||||
) {
|
||||
super();
|
||||
this.data.settings = initialSettings;
|
||||
}
|
||||
|
||||
|
|
@ -40,28 +40,6 @@ export class MockClient implements FileSystemOperations {
|
|||
await this.client.start();
|
||||
}
|
||||
|
||||
public async listFilesRecursively(
|
||||
_root: RelativePath | undefined = undefined // we don't use multi-level paths during tests
|
||||
): Promise<RelativePath[]> {
|
||||
return Array.from(this.localFiles.keys());
|
||||
}
|
||||
|
||||
public async read(path: RelativePath): Promise<Uint8Array> {
|
||||
const file = this.localFiles.get(path);
|
||||
if (!file) {
|
||||
throw new Error(`File ${path} does not exist`);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
public async getFileSize(path: RelativePath): Promise<number> {
|
||||
return (await this.read(path)).length;
|
||||
}
|
||||
|
||||
public async exists(path: RelativePath): Promise<boolean> {
|
||||
return this.localFiles.has(path);
|
||||
}
|
||||
|
||||
public async create(
|
||||
path: RelativePath,
|
||||
newContent: Uint8Array,
|
||||
|
|
@ -69,13 +47,13 @@ export class MockClient implements FileSystemOperations {
|
|||
ignoreSlowFileEvents: false
|
||||
}
|
||||
): Promise<void> {
|
||||
if (this.localFiles.has(path)) {
|
||||
if (this.files.has(path)) {
|
||||
throw new Error(`File ${path} already exists`);
|
||||
}
|
||||
this.client.logger.info(
|
||||
`Creating file ${path} with content ${new TextDecoder().decode(newContent)}`
|
||||
);
|
||||
this.localFiles.set(path, newContent);
|
||||
this.files.set(path, newContent);
|
||||
|
||||
this.executeFileOperation(
|
||||
async () => this.client.syncLocallyCreatedFile(path),
|
||||
|
|
@ -83,25 +61,21 @@ export class MockClient implements FileSystemOperations {
|
|||
);
|
||||
}
|
||||
|
||||
public async createDirectory(_path: RelativePath): Promise<void> {
|
||||
// This doesn't mean anything in our virtual FS representation
|
||||
}
|
||||
|
||||
public async atomicUpdateText(
|
||||
public override async atomicUpdateText(
|
||||
path: RelativePath,
|
||||
updater: (currentContent: TextWithCursors) => TextWithCursors,
|
||||
{ ignoreSlowFileEvents }: { ignoreSlowFileEvents: boolean } = {
|
||||
ignoreSlowFileEvents: false
|
||||
}
|
||||
): Promise<string> {
|
||||
const file = this.localFiles.get(path);
|
||||
const file = this.files.get(path);
|
||||
if (!file) {
|
||||
throw new Error(`File ${path} does not exist`);
|
||||
}
|
||||
const currentContent = new TextDecoder().decode(file);
|
||||
const newContent = updater({ text: currentContent, cursors: [] }).text;
|
||||
const newContentUint8Array = new TextEncoder().encode(newContent);
|
||||
this.localFiles.set(path, newContentUint8Array);
|
||||
this.files.set(path, newContentUint8Array);
|
||||
|
||||
if (!this.useSlowFileEvents) {
|
||||
const existingParts = currentContent
|
||||
|
|
@ -109,13 +83,13 @@ export class MockClient implements FileSystemOperations {
|
|||
.map((part) => part.trim());
|
||||
const newParts = newContent.split(" ").map((part) => part.trim());
|
||||
existingParts.forEach((part) =>
|
||||
// all changes should be additive
|
||||
{
|
||||
assert(
|
||||
newParts.includes(part),
|
||||
`Part ${part} not found in new content: ${newContent}`
|
||||
);
|
||||
}
|
||||
// all changes should be additive
|
||||
{
|
||||
assert(
|
||||
newParts.includes(part),
|
||||
`Part ${part} not found in new content: ${newContent}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -134,9 +108,12 @@ export class MockClient implements FileSystemOperations {
|
|||
return newContent;
|
||||
}
|
||||
|
||||
public async write(path: RelativePath, content: Uint8Array): Promise<void> {
|
||||
const hasExisted = this.localFiles.has(path);
|
||||
this.localFiles.set(path, content);
|
||||
public override async write(
|
||||
path: RelativePath,
|
||||
content: Uint8Array
|
||||
): Promise<void> {
|
||||
const hasExisted = this.files.has(path);
|
||||
this.files.set(path, content);
|
||||
|
||||
this.client.logger.info(
|
||||
`Updated file ${path} with:\n new content: ${new TextDecoder().decode(content)}`
|
||||
|
|
@ -153,16 +130,16 @@ export class MockClient implements FileSystemOperations {
|
|||
});
|
||||
}
|
||||
|
||||
public async delete(
|
||||
public override async delete(
|
||||
path: RelativePath,
|
||||
{ ignoreSlowFileEvents }: { ignoreSlowFileEvents: boolean } = {
|
||||
ignoreSlowFileEvents: false
|
||||
}
|
||||
): Promise<void> {
|
||||
this.client.logger.info(
|
||||
`Deleting file: ${path} with:\n content ${new TextDecoder().decode(this.localFiles.get(path))}`
|
||||
`Deleting file: ${path} with:\n content ${new TextDecoder().decode(this.files.get(path))}`
|
||||
);
|
||||
this.localFiles.delete(path);
|
||||
this.files.delete(path);
|
||||
|
||||
this.executeFileOperation(
|
||||
async () => this.client.syncLocallyDeletedFile(path),
|
||||
|
|
@ -170,20 +147,20 @@ export class MockClient implements FileSystemOperations {
|
|||
);
|
||||
}
|
||||
|
||||
public async rename(
|
||||
public override async rename(
|
||||
oldPath: RelativePath,
|
||||
newPath: RelativePath,
|
||||
{ ignoreSlowFileEvents }: { ignoreSlowFileEvents: boolean } = {
|
||||
ignoreSlowFileEvents: false
|
||||
}
|
||||
): Promise<void> {
|
||||
const file = this.localFiles.get(oldPath);
|
||||
const file = this.files.get(oldPath);
|
||||
if (!file) {
|
||||
throw new Error(`File ${oldPath} does not exist`);
|
||||
}
|
||||
this.localFiles.set(newPath, file);
|
||||
this.files.set(newPath, file);
|
||||
if (oldPath !== newPath) {
|
||||
this.localFiles.delete(oldPath);
|
||||
this.files.delete(oldPath);
|
||||
}
|
||||
|
||||
this.client.logger.info(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { v4 as uuidv4 } from "uuid";
|
|||
import { randomCasing } from "./utils/random-casing";
|
||||
|
||||
const TEST_ITERATIONS = 5;
|
||||
const MAX_INITIAL_DOCS = 5;
|
||||
const MAX_INITIAL_DOCS = 0;
|
||||
|
||||
// Simulate async file access by injecting waiting time before returning from file operations.
|
||||
let slowFileEvents = false;
|
||||
|
|
@ -65,8 +65,6 @@ async function runTest({
|
|||
}
|
||||
|
||||
try {
|
||||
await utils.awaitAll(clients.map(async (client) => client.init()));
|
||||
|
||||
for (const client of clients) {
|
||||
const initialDocCount = Math.floor(
|
||||
Math.random() * MAX_INITIAL_DOCS
|
||||
|
|
@ -79,6 +77,10 @@ async function runTest({
|
|||
}
|
||||
}
|
||||
|
||||
await utils.awaitAll(clients.map(async (client) => client.init()));
|
||||
|
||||
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
logger.info(`Iteration ${i + 1}/${iterations}`);
|
||||
await utils.awaitAll(clients.map(async (client) => client.act()));
|
||||
|
|
@ -217,5 +219,8 @@ runTests()
|
|||
})
|
||||
.catch((error: unknown) => {
|
||||
logger.error(`Error - tests failed with ${error}`);
|
||||
if (error instanceof Error && error.stack) {
|
||||
logger.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue