diff --git a/api-report/mocha-test-setup.api.md b/api-report/mocha-test-setup.api.md
index ed47a710abd9..8e1540c2fe41 100644
--- a/api-report/mocha-test-setup.api.md
+++ b/api-report/mocha-test-setup.api.md
@@ -4,14 +4,15 @@
```ts
+///
+
// @public (undocumented)
export const mochaHooks: {
beforeAll(): void;
- beforeEach(): void;
- afterEach(): void;
+ beforeEach(this: Mocha.Context): void;
+ afterEach(this: Mocha.Context): void;
};
-
// (No @packageDocumentation comment for this package)
```
diff --git a/api-report/test-utils.api.md b/api-report/test-utils.api.md
index 6b70c3351d40..0bececfe584d 100644
--- a/api-report/test-utils.api.md
+++ b/api-report/test-utils.api.md
@@ -337,7 +337,6 @@ export function timeoutPromise(executor: (resolve: (value: T | Promise
// @public (undocumented)
export interface TimeoutWithError {
- // (undocumented)
durationMs?: number;
// (undocumented)
errorMsg?: string;
@@ -347,7 +346,6 @@ export interface TimeoutWithError {
// @public (undocumented)
export interface TimeoutWithValue {
- // (undocumented)
durationMs?: number;
// (undocumented)
reject: false;
diff --git a/experimental/PropertyDDS/packages/property-dds/src/test/rebasing.spec.ts b/experimental/PropertyDDS/packages/property-dds/src/test/rebasing.spec.ts
index 8a9480343e07..67fdbd85b535 100644
--- a/experimental/PropertyDDS/packages/property-dds/src/test/rebasing.spec.ts
+++ b/experimental/PropertyDDS/packages/property-dds/src/test/rebasing.spec.ts
@@ -12,987 +12,987 @@ import { requestFluidObject } from "@fluidframework/runtime-utils";
import { IUrlResolver } from "@fluidframework/driver-definitions";
import { LocalDeltaConnectionServer, ILocalDeltaConnectionServer } from "@fluidframework/server-local-server";
import {
- createAndAttachContainer,
- createLoader,
- ITestFluidObject,
- TestFluidObjectFactory,
- LoaderContainerTracker,
+ createAndAttachContainer,
+ createLoader,
+ ITestFluidObject,
+ TestFluidObjectFactory,
+ LoaderContainerTracker,
} from "@fluidframework/test-utils";
import { DeterministicRandomGenerator } from "@fluid-experimental/property-common";
import * as _ from "lodash";
import {
- StringArrayProperty,
- PropertyFactory,
- ArrayProperty,
- NamedProperty,
+ StringArrayProperty,
+ PropertyFactory,
+ ArrayProperty,
+ NamedProperty,
Int32Property,
} from "@fluid-experimental/property-properties";
import { assert } from "@fluidframework/common-utils";
import { SharedPropertyTree } from "../propertyTree";
function createLocalLoader(
- packageEntries: Iterable<[IFluidCodeDetails, TestFluidObjectFactory]>,
- deltaConnectionServer: ILocalDeltaConnectionServer,
- urlResolver: IUrlResolver,
- options?: ILoaderOptions,
+ packageEntries: Iterable<[IFluidCodeDetails, TestFluidObjectFactory]>,
+ deltaConnectionServer: ILocalDeltaConnectionServer,
+ urlResolver: IUrlResolver,
+ options?: ILoaderOptions,
): IHostLoader {
- const documentServiceFactory = new LocalDocumentServiceFactory(deltaConnectionServer);
+ const documentServiceFactory = new LocalDocumentServiceFactory(deltaConnectionServer);
- return createLoader(packageEntries, documentServiceFactory, urlResolver, undefined, options);
+ return createLoader(packageEntries, documentServiceFactory, urlResolver, undefined, options);
}
function createDerivedGuid(referenceGuid: string, identifier: string) {
- const hash = crypto.createHash("sha1");
- hash.write(`${referenceGuid}:${identifier}`);
- hash.end();
-
- const hexHash = hash.digest("hex");
- return (
- `${hexHash.substr(0, 8)}-${hexHash.substr(8, 4)}-` +
- `${hexHash.substr(12, 4)}-${hexHash.substr(16, 4)}-${hexHash.substr(20, 12)}`
- );
+ const hash = crypto.createHash("sha1");
+ hash.write(`${referenceGuid}:${identifier}`);
+ hash.end();
+
+ const hexHash = hash.digest("hex");
+ return (
+ `${hexHash.substr(0, 8)}-${hexHash.substr(8, 4)}-` +
+ `${hexHash.substr(12, 4)}-${hexHash.substr(16, 4)}-${hexHash.substr(20, 12)}`
+ );
}
console.assert = (condition: boolean, ...data: any[]) => {
- assert(!!condition, "Console Assert");
+ assert(!!condition, "Console Assert");
};
function getFunctionSource(fun: any): string {
- let source = fun.toString() as string;
- source = source.replace(/^.*=>\s*{?\n?\s*/m, "");
- source = source.replace(/}\s*$/m, "");
- source = source.replace(/^\s*/gm, "");
+ let source = fun.toString() as string;
+ source = source.replace(/^.*=>\s*{?\n?\s*/m, "");
+ source = source.replace(/}\s*$/m, "");
+ source = source.replace(/^\s*/gm, "");
- return source;
+ return source;
}
describe("PropertyDDS", () => {
const documentId = "localServerTest";
- const documentLoadUrl = `fluid-test://localhost/${documentId}`;
- const propertyDdsId = "PropertyTree";
- const codeDetails: IFluidCodeDetails = {
- package: "localServerTestPackage",
- config: {},
- };
- const factory = new TestFluidObjectFactory([[propertyDdsId, SharedPropertyTree.getFactory()]]);
-
- let deltaConnectionServer: ILocalDeltaConnectionServer;
- let urlResolver: LocalResolver;
- let opProcessingController: LoaderContainerTracker;
- let container1: IContainer;
- let container2: IContainer;
- let dataObject1: ITestFluidObject;
- let dataObject2: ITestFluidObject;
- let sharedPropertyTree1: SharedPropertyTree;
- let sharedPropertyTree2: SharedPropertyTree;
-
- let errorHandler: (Error) => void;
-
- async function createContainer(): Promise {
- const loader = createLocalLoader([[codeDetails, factory]], deltaConnectionServer, urlResolver);
- opProcessingController.add(loader);
- return createAndAttachContainer(codeDetails, loader, urlResolver.createCreateNewRequest(documentId));
- }
-
- async function loadContainer(): Promise {
- const loader = createLocalLoader([[codeDetails, factory]], deltaConnectionServer, urlResolver);
- opProcessingController.add(loader);
- return loader.resolve({ url: documentLoadUrl });
- }
-
- function createRandomTests(
- operations: {
- getParameters: (random: DeterministicRandomGenerator) => Record void)>;
- op: (parameters: Record) => Promise;
- probability: number;
- }[],
- final: () => Promise,
- count = 100,
- startTest = 0,
- maxOperations = 30,
- ) {
- for (let i = startTest; i < count; i++) {
- const seed = createDerivedGuid("", String(i));
- it(`Generated Test Case #${i} (seed: ${seed})`, async function() {
- this.timeout(10000);
- let testString = "";
-
- errorHandler = (err) => {
- console.error(`Failed Test code: ${testString}`);
- };
- const random = new DeterministicRandomGenerator(seed);
- const operationCumSums = [] as number[];
- for (const operation of operations) {
- operationCumSums.push(
- (operationCumSums[operationCumSums.length - 1] ?? 0) + operation.probability,
- );
- }
-
- try {
- const numOperations = random.irandom(maxOperations);
- const maxCount = operationCumSums[operationCumSums.length - 1];
- for (const j of _.range(numOperations)) {
- const operationId = 1 + random.irandom(maxCount);
- const selectedOperation = _.sortedIndex(operationCumSums, operationId);
-
- const parameters = operations[selectedOperation].getParameters(random);
-
- // Create the source code for the test
- let operationSource = getFunctionSource(operations[selectedOperation].op.toString());
- for (const [key, value] of Object.entries(parameters)) {
- const valueString = _.isFunction(value) ? getFunctionSource(value) : value.toString();
- operationSource = operationSource.replace(
- new RegExp(`parameters.${key}\\(?\\)?`),
- valueString,
- );
- }
- testString += operationSource;
-
- await operations[selectedOperation].op(parameters);
- }
-
- testString += getFunctionSource(final);
- await final();
- } catch (e) {
- console.error(`Failed Test code: ${testString}`);
- throw e;
- }
- });
- }
- }
-
- async function setupContainers(mode = true) {
- opProcessingController = new LoaderContainerTracker();
- deltaConnectionServer = LocalDeltaConnectionServer.create();
- urlResolver = new LocalResolver();
-
- // Create a Container for the first client.
- container1 = await createContainer();
- dataObject1 = await requestFluidObject(container1, "default");
- sharedPropertyTree1 = await dataObject1.getSharedObject(propertyDdsId);
- (sharedPropertyTree1 as any).__id = 1; // Add an id to simplify debugging via conditional breakpoints
-
- // Load the Container that was created by the first client.
- container2 = await loadContainer();
- dataObject2 = await requestFluidObject(container2, "default");
- sharedPropertyTree2 = await dataObject2.getSharedObject(propertyDdsId);
- (sharedPropertyTree2 as any).__id = 2; // Add an id to simplify debugging via conditional breakpoints
-
- if (mode) {
- // Submitting empty changeset to make sure both trees are in "write" mode, so the tests could control
- // which commits are being synced at every point of time.
- sharedPropertyTree1.commit(true);
- sharedPropertyTree2.commit(true);
- }
+ const documentLoadUrl = `fluid-test://localhost/${documentId}`;
+ const propertyDdsId = "PropertyTree";
+ const codeDetails: IFluidCodeDetails = {
+ package: "localServerTestPackage",
+ config: {},
+ };
+ const factory = new TestFluidObjectFactory([[propertyDdsId, SharedPropertyTree.getFactory()]]);
+
+ let deltaConnectionServer: ILocalDeltaConnectionServer;
+ let urlResolver: LocalResolver;
+ let opProcessingController: LoaderContainerTracker;
+ let container1: IContainer;
+ let container2: IContainer;
+ let dataObject1: ITestFluidObject;
+ let dataObject2: ITestFluidObject;
+ let sharedPropertyTree1: SharedPropertyTree;
+ let sharedPropertyTree2: SharedPropertyTree;
+
+ let errorHandler: (Error) => void;
+
+ async function createContainer(): Promise {
+ const loader = createLocalLoader([[codeDetails, factory]], deltaConnectionServer, urlResolver);
+ opProcessingController.add(loader);
+ return createAndAttachContainer(codeDetails, loader, urlResolver.createCreateNewRequest(documentId));
+ }
+
+ async function loadContainer(): Promise {
+ const loader = createLocalLoader([[codeDetails, factory]], deltaConnectionServer, urlResolver);
+ opProcessingController.add(loader);
+ return loader.resolve({ url: documentLoadUrl });
+ }
+
+ function createRandomTests(
+ operations: {
+ getParameters: (random: DeterministicRandomGenerator) => Record void)>;
+ op: (parameters: Record) => Promise;
+ probability: number;
+ }[],
+ final: () => Promise,
+ count = 100,
+ startTest = 0,
+ maxOperations = 30,
+ ) {
+ for (let i = startTest; i < count; i++) {
+ const seed = createDerivedGuid("", String(i));
+ it(`Generated Test Case #${i} (seed: ${seed})`, async () => {
+ let testString = "";
+
+ errorHandler = (err) => {
+ console.error(`Failed Test code: ${testString}`);
+ };
+ const random = new DeterministicRandomGenerator(seed);
+ const operationCumSums = [] as number[];
+ for (const operation of operations) {
+ operationCumSums.push(
+ (operationCumSums[operationCumSums.length - 1] ?? 0) + operation.probability,
+ );
+ }
+
+ try {
+ const numOperations = random.irandom(maxOperations);
+ const maxCount = operationCumSums[operationCumSums.length - 1];
+ for (const j of _.range(numOperations)) {
+ const operationId = 1 + random.irandom(maxCount);
+ const selectedOperation = _.sortedIndex(operationCumSums, operationId);
+
+ const parameters = operations[selectedOperation].getParameters(random);
+
+ // Create the source code for the test
+ let operationSource = getFunctionSource(operations[selectedOperation].op.toString());
+ for (const [key, value] of Object.entries(parameters)) {
+ const valueString = _.isFunction(value) ? getFunctionSource(value) : value.toString();
+ operationSource = operationSource.replace(
+ new RegExp(`parameters.${key}\\(?\\)?`),
+ valueString,
+ );
+ }
+ testString += operationSource;
- // Attach error handlers to make debugging easier and ensure that internal failures cause the test to fail
- errorHandler = (err) => { }; // This enables the create random tests function to register its own handler
- container1.on("closed", (err: any) => {
- if (err !== undefined) {
- errorHandler(err);
- throw err;
+ await operations[selectedOperation].op(parameters);
+ }
+
+ testString += getFunctionSource(final);
+ await final();
+ } catch (e) {
+ console.error(`Failed Test code: ${testString}`);
+ throw e;
}
+ }).timeout(10000);
+ }
+ }
+
+ async function setupContainers(mode = true) {
+ opProcessingController = new LoaderContainerTracker();
+ deltaConnectionServer = LocalDeltaConnectionServer.create();
+ urlResolver = new LocalResolver();
+
+ // Create a Container for the first client.
+ container1 = await createContainer();
+ dataObject1 = await requestFluidObject(container1, "default");
+ sharedPropertyTree1 = await dataObject1.getSharedObject(propertyDdsId);
+ (sharedPropertyTree1 as any).__id = 1; // Add an id to simplify debugging via conditional breakpoints
+
+ // Load the Container that was created by the first client.
+ container2 = await loadContainer();
+ dataObject2 = await requestFluidObject(container2, "default");
+ sharedPropertyTree2 = await dataObject2.getSharedObject(propertyDdsId);
+ (sharedPropertyTree2 as any).__id = 2; // Add an id to simplify debugging via conditional breakpoints
+
+ if (mode) {
+ // Submitting empty changeset to make sure both trees are in "write" mode, so the tests could control
+ // which commits are being synced at every point of time.
+ sharedPropertyTree1.commit(true);
+ sharedPropertyTree2.commit(true);
+ }
+
+ // Attach error handlers to make debugging easier and ensure that internal failures cause the test to fail
+ errorHandler = (err) => { }; // This enables the create random tests function to register its own handler
+ container1.on("closed", (err: any) => {
+ if (err !== undefined) {
+ errorHandler(err);
+ throw err;
+ }
+ });
+ container2.on("closed", (err: any) => {
+ if (err !== undefined) {
+ errorHandler(err);
+ throw err;
+ }
+ });
+ }
+
+ function rebaseTests() {
+ describe("with non overlapping inserts", () => {
+ let ACount: number;
+ let CCount: number;
+
+ beforeEach(async function() {
+ this.timeout(10000);
+
+ // Insert and prepare an array within the container
+ sharedPropertyTree1.root.insert("array", PropertyFactory.create("String", "array"));
+
+ const array = sharedPropertyTree1.root.get("array") as StringArrayProperty;
+ array.push("B1");
+ array.push("B2");
+ array.push("B3");
+ sharedPropertyTree1.commit();
+
+ ACount = 0;
+ CCount = 0;
+
+ // Make sure both shared trees are in sync
+ await opProcessingController.ensureSynchronized();
+ await opProcessingController.pauseProcessing();
});
- container2.on("closed", (err: any) => {
- if (err !== undefined) {
- errorHandler(err);
- throw err;
+
+ afterEach(() => {
+ const result = _.range(1, ACount + 1)
+ .map((i) => `A${i}`)
+ .concat(["B1", "B2", "B3"])
+ .concat(_.range(1, CCount + 1).map((i) => `C${i}`));
+
+ const array1 = sharedPropertyTree1.root.get("array") as StringArrayProperty;
+ const array2 = sharedPropertyTree2.root.get("array") as StringArrayProperty;
+ for (const array of [array1, array2]) {
+ for (const [i, value] of result.entries()) {
+ expect(array.get(i)).to.equal(value);
+ }
}
});
- }
- function rebaseTests() {
- describe("with non overlapping inserts", () => {
- let ACount: number;
- let CCount: number;
+ function insertInArray(tree: SharedPropertyTree, letter: string) {
+ const array = tree.root.get("array") as StringArrayProperty;
- beforeEach(async function() {
- this.timeout(10000);
- // Insert and prepare an array within the container
- sharedPropertyTree1.root.insert("array", PropertyFactory.create("String", "array"));
+ // Find the insert position
+ let insertPosition: number;
+ let insertString: string;
+ if (letter === "A") {
+ // We insert all As in front of B1
+ const values: string[] = array.getValues();
+ insertPosition = values.indexOf("B1");
- const array = sharedPropertyTree1.root.get("array") as StringArrayProperty;
- array.push("B1");
- array.push("B2");
- array.push("B3");
- sharedPropertyTree1.commit();
+ // For these letters we can just use the position to get the number for the inserted string
+ insertString = `A${insertPosition + 1}`;
- ACount = 0;
- CCount = 0;
+ ACount++;
+ } else {
+ // Alway insert B at the end
+ insertPosition = array.getLength();
- // Make sure both shared trees are in sync
- await opProcessingController.ensureSynchronized();
- await opProcessingController.pauseProcessing();
- });
+ // Get the number from the previous entry
+ const previous = array.get(insertPosition - 1) as string;
+ const entryNumber = previous[0] === "B" ? 1 : Number.parseInt(previous[1], 10) + 1;
+ insertString = `C${entryNumber}`;
- afterEach(() => {
- const result = _.range(1, ACount + 1)
- .map((i) => `A${i}`)
- .concat(["B1", "B2", "B3"])
- .concat(_.range(1, CCount + 1).map((i) => `C${i}`));
-
- const array1 = sharedPropertyTree1.root.get("array") as StringArrayProperty;
- const array2 = sharedPropertyTree2.root.get("array") as StringArrayProperty;
- for (const array of [array1, array2]) {
- for (const [i, value] of result.entries()) {
- expect(array.get(i)).to.equal(value);
- }
- }
- });
+ CCount++;
+ }
- function insertInArray(tree: SharedPropertyTree, letter: string) {
- const array = tree.root.get("array") as StringArrayProperty;
+ array.insert(insertPosition, insertString);
+ tree.commit();
+ }
- // Find the insert position
- let insertPosition: number;
- let insertString: string;
- if (letter === "A") {
- // We insert all As in front of B1
- const values: string[] = array.getValues();
- insertPosition = values.indexOf("B1");
+ it("Should work when doing two batches with synchronization in between", async () => {
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree1, "A");
- // For these letters we can just use the position to get the number for the inserted string
- insertString = `A${insertPosition + 1}`;
+ await opProcessingController.ensureSynchronized();
- ACount++;
- } else {
- // Alway insert B at the end
- insertPosition = array.getLength();
+ insertInArray(sharedPropertyTree2, "C");
+ insertInArray(sharedPropertyTree2, "C");
+ insertInArray(sharedPropertyTree2, "C");
- // Get the number from the previous entry
- const previous = array.get(insertPosition - 1) as string;
- const entryNumber = previous[0] === "B" ? 1 : Number.parseInt(previous[1], 10) + 1;
- insertString = `C${entryNumber}`;
+ await opProcessingController.ensureSynchronized();
+ });
- CCount++;
- }
+ it("Should work when doing two batches without synchronization inbetween", async () => {
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree1, "A");
- array.insert(insertPosition, insertString);
- tree.commit();
- }
+ insertInArray(sharedPropertyTree2, "C");
+ insertInArray(sharedPropertyTree2, "C");
+ insertInArray(sharedPropertyTree2, "C");
- it("Should work when doing two batches with synchronization inbetween", async () => {
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.ensureSynchronized();
+ });
- await opProcessingController.ensureSynchronized();
+ it("Should work when creating local branches with different remote heads", async () => {
+ insertInArray(sharedPropertyTree2, "C");
+ insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.ensureSynchronized();
+ insertInArray(sharedPropertyTree2, "C");
+ insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.ensureSynchronized();
+ insertInArray(sharedPropertyTree2, "C");
+ insertInArray(sharedPropertyTree1, "A");
+
+ await opProcessingController.ensureSynchronized();
+ });
- insertInArray(sharedPropertyTree2, "C");
- insertInArray(sharedPropertyTree2, "C");
- insertInArray(sharedPropertyTree2, "C");
+ it("Should work when synchronizing after each operation", async () => {
+ insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.ensureSynchronized();
+ insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.ensureSynchronized();
+ insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.ensureSynchronized();
+
+ insertInArray(sharedPropertyTree2, "C");
+ await opProcessingController.ensureSynchronized();
+ insertInArray(sharedPropertyTree2, "C");
+ await opProcessingController.ensureSynchronized();
+ insertInArray(sharedPropertyTree2, "C");
+ await opProcessingController.ensureSynchronized();
+ });
- await opProcessingController.ensureSynchronized();
- });
+ it("Should work when synchronizing after pairs of operations", async () => {
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree2, "C");
+ await opProcessingController.ensureSynchronized();
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree2, "C");
+ await opProcessingController.ensureSynchronized();
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree2, "C");
+ await opProcessingController.ensureSynchronized();
+ });
+
+ it("works with overlapping sequences", async () => {
+ insertInArray(sharedPropertyTree2, "C");
+ await opProcessingController.processOutgoing(container2);
+
+ // Insert five operations to make this overlap with the insert position of C
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree1, "A");
+ insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.processIncoming(container1);
+ insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.processIncoming(container2);
+
+ await opProcessingController.ensureSynchronized();
+ });
- it("Should work when doing two batches without synchronization inbetween", async () => {
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree1, "A");
+ it("Should work when the remote head points to a change that is not the reference change", async () => {
+ insertInArray(sharedPropertyTree2, "C");
+ await opProcessingController.processOutgoing(container2);
+ insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.processOutgoing(container1);
+ insertInArray(sharedPropertyTree2, "C");
+ await opProcessingController.processIncoming(container2);
+ insertInArray(sharedPropertyTree2, "C");
+ insertInArray(sharedPropertyTree2, "C");
+
+ await opProcessingController.ensureSynchronized();
+ });
- insertInArray(sharedPropertyTree2, "C");
- insertInArray(sharedPropertyTree2, "C");
- insertInArray(sharedPropertyTree2, "C");
+ describe("Randomized Tests", () => {
+ const count = 100;
+ const startTest = 0;
+ const logTest = true;
+
+ for (let i = startTest; i < count; i++) {
+ const seed = createDerivedGuid("", String(i));
+ it(`Generated Test Case #${i} (seed: ${seed})`, async () => {
+ const random = new DeterministicRandomGenerator(seed);
+ let testString = "";
+
+ const numOperations = random.irandom(30);
+ for (const j of _.range(numOperations)) {
+ const operation = random.irandom(6);
+ switch (operation) {
+ case 0:
+ insertInArray(sharedPropertyTree1, "A");
+ if (logTest) {
+ testString += 'insertInArray(sharedPropertyTree1, "A");\n';
+ }
+ break;
+ case 1:
+ insertInArray(sharedPropertyTree2, "C");
+ if (logTest) {
+ testString += 'insertInArray(sharedPropertyTree2, "C");\n';
+ }
+ break;
+ case 2:
+ await opProcessingController.processOutgoing(container1);
+ if (logTest) {
+ testString +=
+ "await opProcessingController.processOutgoing(container1);\n";
+ }
+ break;
+ case 3:
+ await opProcessingController.processIncoming(container1);
+ if (logTest) {
+ testString +=
+ "await opProcessingController.processIncoming(container1);\n";
+ }
+ break;
+ case 4:
+ await opProcessingController.processOutgoing(container2);
+ if (logTest) {
+ testString +=
+ "await opProcessingController.processOutgoing(container2);\n";
+ }
+ break;
+ case 5:
+ await opProcessingController.processIncoming(container2);
+ if (logTest) {
+ testString +=
+ "await opProcessingController.processIncoming(container2);\n";
+ }
+ break;
+ default:
+ throw new Error("Should never happen");
+ }
+ }
- await opProcessingController.ensureSynchronized();
+ await opProcessingController.ensureSynchronized();
+ if (logTest) {
+ testString +=
+ "await opProcessingController.ensureSynchronized();\n";
+ }
+ });
+ }
+ });
+ });
+
+ describe("with inserts and deletes at arbitrary positions", () => {
+ let createdProperties: Set;
+ let deletedProperties: Set;
+ beforeEach(async () => {
+ createdProperties = new Set();
+ deletedProperties = new Set();
+ (PropertyFactory as any)._reregister({
+ typeid: "test:namedEntry-1.0.0",
+ inherits: ["NamedProperty"],
+ properties: [],
});
- it("Should work when creating local branches with different remote heads", async () => {
- insertInArray(sharedPropertyTree2, "C");
- insertInArray(sharedPropertyTree1, "A");
- await opProcessingController.ensureSynchronized();
- insertInArray(sharedPropertyTree2, "C");
- insertInArray(sharedPropertyTree1, "A");
- await opProcessingController.ensureSynchronized();
- insertInArray(sharedPropertyTree2, "C");
- insertInArray(sharedPropertyTree1, "A");
+ await opProcessingController.pauseProcessing();
+ sharedPropertyTree1.root.insert("array", PropertyFactory.create("test:namedEntry-1.0.0", "array"));
+ sharedPropertyTree1.commit();
- await opProcessingController.ensureSynchronized();
- });
+ // // Making sure that both trees are in write mode.
+ // sharedPropertyTree2.commit(true);
+ // Make sure both shared trees are in sync
+ await opProcessingController.ensureSynchronized();
+ });
+ afterEach(async () => {
+ // We expect the internal representation to be the same between both properties
+ expect((sharedPropertyTree1 as any).remoteTipView).to.deep.equal(
+ (sharedPropertyTree2 as any).remoteTipView,
+ );
+
+ // We expect the property tree to be the same between both
+ expect(sharedPropertyTree1.root.serialize()).to.deep.equal(sharedPropertyTree2.root.serialize());
+
+ // We expect the property tree to correspond to the remote tip view
+ expect((sharedPropertyTree1 as any).remoteTipView).to.deep.equal(
+ sharedPropertyTree2.root.serialize());
+
+ // We expect all properties from the set to be present
+ const array = sharedPropertyTree1.root.get("array");
+ assert(array !== undefined, "property undefined");
+
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ for (const property of array.getValues() as any[]) {
+ expect(!deletedProperties.has(property.guid)).to.be.true;
+ expect(createdProperties.has(property.guid)).to.be.true;
+ createdProperties.delete(property.guid);
+ }
+ expect(createdProperties.size).to.equal(0);
+ });
+ function insertProperties(tree: SharedPropertyTree, index: number, count = 1, commit = true) {
+ for (let i = 0; i < count; i++) {
+ const property = PropertyFactory.create("test:namedEntry-1.0.0");
+ tree.root.get("array")?.insert(index + i, property);
+ createdProperties.add(property.getGuid());
+ }
- it("Should work when synchronizing after each operation", async () => {
- insertInArray(sharedPropertyTree1, "A");
- await opProcessingController.ensureSynchronized();
- insertInArray(sharedPropertyTree1, "A");
- await opProcessingController.ensureSynchronized();
- insertInArray(sharedPropertyTree1, "A");
- await opProcessingController.ensureSynchronized();
+ if (commit) {
+ tree.commit();
+ }
+ }
+ function removeProperties(tree: SharedPropertyTree, index: number, count = 1, commit = true) {
+ const array = tree.root.get("array");
+ assert(array !== undefined, "property undefined");
- insertInArray(sharedPropertyTree2, "C");
- await opProcessingController.ensureSynchronized();
- insertInArray(sharedPropertyTree2, "C");
- await opProcessingController.ensureSynchronized();
- insertInArray(sharedPropertyTree2, "C");
+ for (let i = 0; i < count; i++) {
+ if (index >= array.getLength()) {
+ break;
+ }
+ const property = array.get(index);
+ assert(property !== undefined, "property undefined");
+ array.remove(index);
+ createdProperties.delete(property.getGuid());
+ deletedProperties.add(property.getGuid());
+ }
+ if (commit) {
+ tree.commit();
+ }
+ }
+
+ it("inserting properties into both trees", async () => {
+ insertProperties(sharedPropertyTree1, 0);
+ insertProperties(sharedPropertyTree1, 1);
+ insertProperties(sharedPropertyTree2, 0);
+ insertProperties(sharedPropertyTree2, 1);
+ await opProcessingController.ensureSynchronized();
+ });
+
+ it("inserting properties in one tree and deleting in the other", async () => {
+ insertProperties(sharedPropertyTree1, 0);
+ insertProperties(sharedPropertyTree1, 1);
+ await opProcessingController.ensureSynchronized();
+ removeProperties(sharedPropertyTree2, 0);
+ removeProperties(sharedPropertyTree2, 0);
+ await opProcessingController.ensureSynchronized();
+ });
+
+ it("inserting properties in one tree and deleting in both", async () => {
+ insertProperties(sharedPropertyTree1, 0);
+ insertProperties(sharedPropertyTree1, 1);
+ await opProcessingController.ensureSynchronized();
+ removeProperties(sharedPropertyTree1, 0);
+ removeProperties(sharedPropertyTree2, 0);
+ await opProcessingController.ensureSynchronized();
+ });
+ it("Multiple inserts in sequence in tree 1", async () => {
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 1, 1, true);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
+
+ await opProcessingController.ensureSynchronized();
+ });
+
+ describe("Random tests", () => {
+ createRandomTests(
+ [
+ {
+ getParameters: (random: DeterministicRandomGenerator) => {
+ const tree = random.irandom(2) === 0 ?
+ () => sharedPropertyTree1 :
+ () => sharedPropertyTree2;
+ const array = tree().root.get("array");
+ return {
+ position: random.irandom(array?.getLength()) || 0,
+ count: (random.irandom(3)) + 1,
+ tree,
+ };
+ },
+ op: async (parameters) => {
+ insertProperties(parameters.tree(), parameters.position, parameters.count, true);
+ },
+ probability: 1,
+ },
+ {
+ getParameters: (random: DeterministicRandomGenerator) => {
+ const tree = random.irandom(2) === 0 ?
+ () => sharedPropertyTree1 : () => sharedPropertyTree2;
+ const array = tree().root.get("array");
+ return {
+ position: random.irandom(array?.getLength()) || 0,
+ count: (random.irandom(3)) + 1,
+ tree,
+ };
+ },
+ op: async (parameters) => {
+ removeProperties(parameters.tree(), parameters.position, parameters.count, true);
+ },
+ probability: 1,
+ },
+ {
+ getParameters: (random: DeterministicRandomGenerator) => {
+ const container = random.irandom(2) === 0 ? () => container1 : () => container2;
+ return {
+ container,
+ };
+ },
+ op: async (parameters) => {
+ await opProcessingController.processOutgoing(parameters.container());
+ },
+ probability: 1,
+ },
+ {
+ getParameters: (random: DeterministicRandomGenerator) => {
+ const container = random.irandom(2) === 0 ? () => container1 : () => container2;
+ return {
+ container,
+ };
+ },
+ op: async (parameters) => {
+ await opProcessingController.processIncoming(parameters.container());
+ },
+ probability: 1,
+ },
+ ],
+ async () => {
+ await opProcessingController.ensureSynchronized();
+ },
+ 1000,
+ 0,
+ 25,
+ );
+ });
+ describe("Failed Random Tests", () => {
+ it("Test Failure 1", async () => {
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 0, 3, true);
+ insertProperties(sharedPropertyTree1, 3, 3, true);
+ insertProperties(sharedPropertyTree1, 6, 2, true);
+ insertProperties(sharedPropertyTree1, 0, 3, true);
+ insertProperties(sharedPropertyTree1, 0, 2, true);
+ insertProperties(sharedPropertyTree1, 2, 2, true);
+ insertProperties(sharedPropertyTree1, 8, 2, true);
+ insertProperties(sharedPropertyTree1, 2, 2, true);
+ insertProperties(sharedPropertyTree1, 16, 3, true);
+ insertProperties(sharedPropertyTree1, 9, 1, true);
+ insertProperties(sharedPropertyTree1, 4, 2, true);
+ insertProperties(sharedPropertyTree1, 13, 3, true);
+ insertProperties(sharedPropertyTree1, 9, 3, true);
+ insertProperties(sharedPropertyTree1, 16, 2, true);
+ insertProperties(sharedPropertyTree1, 12, 2, true);
+ insertProperties(sharedPropertyTree2, 0, 2, true);
+ insertProperties(sharedPropertyTree1, 12, 2, true);
+ insertProperties(sharedPropertyTree1, 12, 3, true);
+ insertProperties(sharedPropertyTree1, 25, 3, true);
await opProcessingController.ensureSynchronized();
});
- it("Should work when synchronizing after pairs of operations", async () => {
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree2, "C");
- await opProcessingController.ensureSynchronized();
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree2, "C");
- await opProcessingController.ensureSynchronized();
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree2, "C");
+ it("Test Failure 2", async () => {
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 1, 1, true);
+ insertProperties(sharedPropertyTree1, 1, 1, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 2, 1, true);
+ insertProperties(sharedPropertyTree1, 4, 1, true);
+ insertProperties(sharedPropertyTree1, 2, 1, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 6, 1, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 6, 1, true);
+ insertProperties(sharedPropertyTree1, 9, 1, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 2, 1, true);
+ insertProperties(sharedPropertyTree1, 9, 1, true);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 4, 1, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 3, 1, true);
await opProcessingController.ensureSynchronized();
});
- it("works with overlapping sequences", async () => {
- insertInArray(sharedPropertyTree2, "C");
- await opProcessingController.processOutgoing(container2);
-
- // Insert five operations to make this overlap with the insert position of C
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree1, "A");
- insertInArray(sharedPropertyTree1, "A");
- await opProcessingController.processIncoming(container1);
- insertInArray(sharedPropertyTree1, "A");
- await opProcessingController.processIncoming(container2);
+ it("Test Failure 3", async () => {
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
await opProcessingController.ensureSynchronized();
});
- it("Should work when the remote head points to a change that is not the reference change", async () => {
- insertInArray(sharedPropertyTree2, "C");
- await opProcessingController.processOutgoing(container2);
- insertInArray(sharedPropertyTree1, "A");
- await opProcessingController.processOutgoing(container1);
- insertInArray(sharedPropertyTree2, "C");
- await opProcessingController.processIncoming(container2);
- insertInArray(sharedPropertyTree2, "C");
- insertInArray(sharedPropertyTree2, "C");
+ it("Test Failure 4", async () => {
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
+ insertProperties(sharedPropertyTree2, 1, 1, true);
await opProcessingController.ensureSynchronized();
});
- describe("Randomized Tests", () => {
- const count = 100;
- const startTest = 0;
- const logTest = true;
-
- for (let i = startTest; i < count; i++) {
- const seed = createDerivedGuid("", String(i));
- it(`Generated Test Case #${i} (seed: ${seed})`, async () => {
- const random = new DeterministicRandomGenerator(seed);
- let testString = "";
-
- const numOperations = random.irandom(30);
- for (const j of _.range(numOperations)) {
- const operation = random.irandom(6);
- switch (operation) {
- case 0:
- insertInArray(sharedPropertyTree1, "A");
- if (logTest) {
- testString += 'insertInArray(sharedPropertyTree1, "A");\n';
- }
- break;
- case 1:
- insertInArray(sharedPropertyTree2, "C");
- if (logTest) {
- testString += 'insertInArray(sharedPropertyTree2, "C");\n';
- }
- break;
- case 2:
- await opProcessingController.processOutgoing(container1);
- if (logTest) {
- testString +=
- "await opProcessingController.processOutgoing(container1);\n";
- }
- break;
- case 3:
- await opProcessingController.processIncoming(container1);
- if (logTest) {
- testString +=
- "await opProcessingController.processIncoming(container1);\n";
- }
- break;
- case 4:
- await opProcessingController.processOutgoing(container2);
- if (logTest) {
- testString +=
- "await opProcessingController.processOutgoing(container2);\n";
- }
- break;
- case 5:
- await opProcessingController.processIncoming(container2);
- if (logTest) {
- testString +=
- "await opProcessingController.processIncoming(container2);\n";
- }
- break;
- default:
- throw new Error("Should never happen");
- }
- }
+ it("Test Failure 5", async () => {
+ insertProperties(sharedPropertyTree1, 0, 8, true);
+ removeProperties(sharedPropertyTree1, 4, 3, true);
- await opProcessingController.ensureSynchronized();
- if (logTest) {
- testString +=
- "await opProcessingController.ensureSynchronized();\n";
- }
- });
- }
+ await opProcessingController.ensureSynchronized();
});
- });
- describe("with inserts and deletes at arbitrary positions", () => {
- let createdProperties: Set;
- let deletedProperties: Set;
- beforeEach(async () => {
- createdProperties = new Set();
- deletedProperties = new Set();
- (PropertyFactory as any)._reregister({
- typeid: "test:namedEntry-1.0.0",
- inherits: ["NamedProperty"],
- properties: [],
- });
-
- await opProcessingController.pauseProcessing();
- sharedPropertyTree1.root.insert("array", PropertyFactory.create("test:namedEntry-1.0.0", "array"));
- sharedPropertyTree1.commit();
+ it("Test Failure 6", async () => {
+ insertProperties(sharedPropertyTree1, 0, 2, true);
+ removeProperties(sharedPropertyTree1, 0, 2, true);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
+ removeProperties(sharedPropertyTree2, 0, 1, true);
- // // Making sure that both trees are in write mode.
- // sharedPropertyTree2.commit(true);
- // Make sure both shared trees are in sync
await opProcessingController.ensureSynchronized();
});
- afterEach(async () => {
- // We expect the internal representation to be the same between both properties
- expect((sharedPropertyTree1 as any).remoteTipView).to.deep.equal(
- (sharedPropertyTree2 as any).remoteTipView,
- );
- // We expect the property tree to be the same between both
- expect(sharedPropertyTree1.root.serialize()).to.deep.equal(sharedPropertyTree2.root.serialize());
+ it("Test Failure 7", async () => {
+ insertProperties(sharedPropertyTree2, 0, 8, true);
+ insertProperties(sharedPropertyTree1, 0, 2, true);
+ await opProcessingController.processOutgoing(container1);
+ await opProcessingController.processOutgoing(container2);
+ insertProperties(sharedPropertyTree1, 1, 4, true);
+ await opProcessingController.processOutgoing(container1);
+ removeProperties(sharedPropertyTree1, 4, 3, true);
+ removeProperties(sharedPropertyTree1, 0, 2, true);
+ insertProperties(sharedPropertyTree1, 0, 4, true);
- // We expect the property tree to correspond to the remote tip view
- expect((sharedPropertyTree1 as any).remoteTipView).to.deep.equal(
- sharedPropertyTree2.root.serialize());
+ await opProcessingController.ensureSynchronized();
+ });
- // We expect all properties from the set to be present
- const array = sharedPropertyTree1.root.get("array");
- assert(array !== undefined, "property undefined");
+ it("Test Failure 8", async () => {
+ insertProperties(sharedPropertyTree2, 0, 3, true);
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processIncoming(container1);
+ removeProperties(sharedPropertyTree2, 0, 3, true);
+ insertProperties(sharedPropertyTree2, 0, 3, true);
+ await opProcessingController.processOutgoing(container2);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
- for (const property of array.getValues() as any[]) {
- expect(!deletedProperties.has(property.guid)).to.be.true;
- expect(createdProperties.has(property.guid)).to.be.true;
- createdProperties.delete(property.guid);
- }
- expect(createdProperties.size).to.equal(0);
+ await opProcessingController.ensureSynchronized();
});
- function insertProperties(tree: SharedPropertyTree, index: number, count = 1, commit = true) {
- for (let i = 0; i < count; i++) {
- const property = PropertyFactory.create("test:namedEntry-1.0.0");
- tree.root.get("array")?.insert(index + i, property);
- createdProperties.add(property.getGuid());
- }
- if (commit) {
- tree.commit();
- }
- }
- function removeProperties(tree: SharedPropertyTree, index: number, count = 1, commit = true) {
- const array = tree.root.get("array");
- assert(array !== undefined, "property undefined");
-
- for (let i = 0; i < count; i++) {
- if (index >= array.getLength()) {
- break;
- }
- const property = array.get(index);
- assert(property !== undefined, "property undefined");
- array.remove(index);
- createdProperties.delete(property.getGuid());
- deletedProperties.add(property.getGuid());
- }
- if (commit) {
- tree.commit();
- }
- }
+ it("Test Failure 9", async () => {
+ insertProperties(sharedPropertyTree2, 0, 9, true);
+ insertProperties(sharedPropertyTree2, 4, 1, true);
+ await opProcessingController.processOutgoing(container2);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree2, 1, 2, true);
- it("inserting properties into both trees", async () => {
- insertProperties(sharedPropertyTree1, 0);
- insertProperties(sharedPropertyTree1, 1);
- insertProperties(sharedPropertyTree2, 0);
- insertProperties(sharedPropertyTree2, 1);
await opProcessingController.ensureSynchronized();
});
- it("inserting properties in one tree and deleting in the other", async () => {
- insertProperties(sharedPropertyTree1, 0);
- insertProperties(sharedPropertyTree1, 1);
+ it("Test Failure 10", async () => {
+ insertProperties(sharedPropertyTree2, 0, 3, true);
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processIncoming(container1);
+ removeProperties(sharedPropertyTree2, 0, 3, true);
+ insertProperties(sharedPropertyTree2, 0, 3, true);
+ await opProcessingController.processOutgoing(container2);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ removeProperties(sharedPropertyTree1, 0, 1, true);
await opProcessingController.ensureSynchronized();
- removeProperties(sharedPropertyTree2, 0);
- removeProperties(sharedPropertyTree2, 0);
+ });
+ it("Test Failure 11", async () => {
+ insertProperties(sharedPropertyTree2, 0, 6, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ await opProcessingController.processOutgoing(container1);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree2, 4, 2, true);
await opProcessingController.ensureSynchronized();
});
-
- it("inserting properties in one tree and deleting in both", async () => {
- insertProperties(sharedPropertyTree1, 0);
- insertProperties(sharedPropertyTree1, 1);
+ it("Test Failure 12", async () => {
+ insertProperties(sharedPropertyTree1, 0, 2, true);
+ insertProperties(sharedPropertyTree2, 0, 3, true);
+ await opProcessingController.processOutgoing(container1);
+ await opProcessingController.processOutgoing(container2);
+ removeProperties(sharedPropertyTree2, 2, 2, true);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree2, 1, 2, true);
await opProcessingController.ensureSynchronized();
- removeProperties(sharedPropertyTree1, 0);
- removeProperties(sharedPropertyTree2, 0);
+ });
+ it("Test Failure 13", async () => {
+ insertProperties(sharedPropertyTree1, 0, 2, true);
+ await opProcessingController.processOutgoing(container1);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree2, 1, 3, true);
+ removeProperties(sharedPropertyTree2, 4, 1, true);
+ insertProperties(sharedPropertyTree2, 4, 3, true);
+ insertProperties(sharedPropertyTree1, 1, 2, true);
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processOutgoing(container1);
await opProcessingController.ensureSynchronized();
});
- it("Multiple inserts in sequence in tree 1", async () => {
+ it("Test Failure 14", async () => {
insertProperties(sharedPropertyTree1, 0, 1, true);
+ await opProcessingController.processOutgoing(container1);
+ insertProperties(sharedPropertyTree2, 0, 2, true);
+ await opProcessingController.processIncoming(container2);
+ removeProperties(sharedPropertyTree2, 0, 1, true);
+ await opProcessingController.processOutgoing(container2);
insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 1, 1, true);
+ await opProcessingController.ensureSynchronized();
+ });
+ it("Test Failure 15", async () => {
insertProperties(sharedPropertyTree2, 0, 1, true);
-
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processIncoming(container1);
+ insertProperties(sharedPropertyTree2, 0, 2, true);
+ await opProcessingController.processOutgoing(container2);
+ insertProperties(sharedPropertyTree1, 0, 2, true);
+ removeProperties(sharedPropertyTree1, 1, 3, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
await opProcessingController.ensureSynchronized();
});
-
- describe("Random tests", () => {
- createRandomTests(
- [
- {
- getParameters: (random: DeterministicRandomGenerator) => {
- const tree = random.irandom(2) === 0 ?
- () => sharedPropertyTree1 :
- () => sharedPropertyTree2;
- const array = tree().root.get("array");
- return {
- position: random.irandom(array?.getLength()) || 0,
- count: (random.irandom(3)) + 1,
- tree,
- };
- },
- op: async (parameters) => {
- insertProperties(parameters.tree(), parameters.position, parameters.count, true);
- },
- probability: 1,
- },
- {
- getParameters: (random: DeterministicRandomGenerator) => {
- const tree = random.irandom(2) === 0 ?
- () => sharedPropertyTree1 : () => sharedPropertyTree2;
- const array = tree().root.get("array");
- return {
- position: random.irandom(array?.getLength()) || 0,
- count: (random.irandom(3)) + 1,
- tree,
- };
- },
- op: async (parameters) => {
- removeProperties(parameters.tree(), parameters.position, parameters.count, true);
- },
- probability: 1,
- },
- {
- getParameters: (random: DeterministicRandomGenerator) => {
- const container = random.irandom(2) === 0 ? () => container1 : () => container2;
- return {
- container,
- };
- },
- op: async (parameters) => {
- await opProcessingController.processOutgoing(parameters.container());
- },
- probability: 1,
- },
- {
- getParameters: (random: DeterministicRandomGenerator) => {
- const container = random.irandom(2) === 0 ? () => container1 : () => container2;
- return {
- container,
- };
- },
- op: async (parameters) => {
- await opProcessingController.processIncoming(parameters.container());
- },
- probability: 1,
- },
- ],
- async () => {
- await opProcessingController.ensureSynchronized();
- },
- 1000,
- 0,
- 25,
- );
+ it("Test Failure 16", async () => {
+ insertProperties(sharedPropertyTree1, 0, 3, true);
+ await opProcessingController.processOutgoing(container1);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
+ removeProperties(sharedPropertyTree2, 0, 1, true);
+ removeProperties(sharedPropertyTree1, 0, 3, true);
+ insertProperties(sharedPropertyTree1, 0, 3, true);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree2, 2, 2, true);
+ await opProcessingController.ensureSynchronized();
});
- describe("Failed Random Tests", () => {
- it("Test Failure 1", async () => {
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 0, 3, true);
- insertProperties(sharedPropertyTree1, 3, 3, true);
- insertProperties(sharedPropertyTree1, 6, 2, true);
- insertProperties(sharedPropertyTree1, 0, 3, true);
- insertProperties(sharedPropertyTree1, 0, 2, true);
- insertProperties(sharedPropertyTree1, 2, 2, true);
- insertProperties(sharedPropertyTree1, 8, 2, true);
- insertProperties(sharedPropertyTree1, 2, 2, true);
- insertProperties(sharedPropertyTree1, 16, 3, true);
- insertProperties(sharedPropertyTree1, 9, 1, true);
- insertProperties(sharedPropertyTree1, 4, 2, true);
- insertProperties(sharedPropertyTree1, 13, 3, true);
- insertProperties(sharedPropertyTree1, 9, 3, true);
- insertProperties(sharedPropertyTree1, 16, 2, true);
- insertProperties(sharedPropertyTree1, 12, 2, true);
- insertProperties(sharedPropertyTree2, 0, 2, true);
- insertProperties(sharedPropertyTree1, 12, 2, true);
- insertProperties(sharedPropertyTree1, 12, 3, true);
- insertProperties(sharedPropertyTree1, 25, 3, true);
- await opProcessingController.ensureSynchronized();
- });
-
- it("Test Failure 2", async () => {
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 1, 1, true);
- insertProperties(sharedPropertyTree1, 1, 1, true);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 2, 1, true);
- insertProperties(sharedPropertyTree1, 4, 1, true);
- insertProperties(sharedPropertyTree1, 2, 1, true);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 6, 1, true);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 6, 1, true);
- insertProperties(sharedPropertyTree1, 9, 1, true);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 2, 1, true);
- insertProperties(sharedPropertyTree1, 9, 1, true);
- insertProperties(sharedPropertyTree2, 0, 1, true);
- insertProperties(sharedPropertyTree1, 4, 1, true);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 3, 1, true);
- await opProcessingController.ensureSynchronized();
- });
-
- it("Test Failure 3", async () => {
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree2, 0, 1, true);
- insertProperties(sharedPropertyTree2, 0, 1, true);
-
- await opProcessingController.ensureSynchronized();
- });
-
- it("Test Failure 4", async () => {
- insertProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree2, 0, 1, true);
- insertProperties(sharedPropertyTree2, 0, 1, true);
- insertProperties(sharedPropertyTree2, 1, 1, true);
-
- await opProcessingController.ensureSynchronized();
- });
-
- it("Test Failure 5", async () => {
- insertProperties(sharedPropertyTree1, 0, 8, true);
- removeProperties(sharedPropertyTree1, 4, 3, true);
-
- await opProcessingController.ensureSynchronized();
- });
-
- it("Test Failure 6", async () => {
- insertProperties(sharedPropertyTree1, 0, 2, true);
- removeProperties(sharedPropertyTree1, 0, 2, true);
- insertProperties(sharedPropertyTree2, 0, 1, true);
- removeProperties(sharedPropertyTree2, 0, 1, true);
-
- await opProcessingController.ensureSynchronized();
- });
-
- it("Test Failure 7", async () => {
- insertProperties(sharedPropertyTree2, 0, 8, true);
- insertProperties(sharedPropertyTree1, 0, 2, true);
- await opProcessingController.processOutgoing(container1);
- await opProcessingController.processOutgoing(container2);
- insertProperties(sharedPropertyTree1, 1, 4, true);
- await opProcessingController.processOutgoing(container1);
- removeProperties(sharedPropertyTree1, 4, 3, true);
- removeProperties(sharedPropertyTree1, 0, 2, true);
- insertProperties(sharedPropertyTree1, 0, 4, true);
-
- await opProcessingController.ensureSynchronized();
- });
-
- it("Test Failure 8", async () => {
- insertProperties(sharedPropertyTree2, 0, 3, true);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processIncoming(container1);
- removeProperties(sharedPropertyTree2, 0, 3, true);
- insertProperties(sharedPropertyTree2, 0, 3, true);
- await opProcessingController.processOutgoing(container2);
- insertProperties(sharedPropertyTree1, 0, 1, true);
-
- await opProcessingController.ensureSynchronized();
- });
-
- it("Test Failure 9", async () => {
- insertProperties(sharedPropertyTree2, 0, 9, true);
- insertProperties(sharedPropertyTree2, 4, 1, true);
- await opProcessingController.processOutgoing(container2);
- insertProperties(sharedPropertyTree2, 0, 1, true);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree2, 1, 2, true);
-
- await opProcessingController.ensureSynchronized();
- });
-
- it("Test Failure 10", async () => {
- insertProperties(sharedPropertyTree2, 0, 3, true);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processIncoming(container1);
- removeProperties(sharedPropertyTree2, 0, 3, true);
- insertProperties(sharedPropertyTree2, 0, 3, true);
- await opProcessingController.processOutgoing(container2);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- removeProperties(sharedPropertyTree1, 0, 1, true);
- await opProcessingController.ensureSynchronized();
- });
- it("Test Failure 11", async () => {
- insertProperties(sharedPropertyTree2, 0, 6, true);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- await opProcessingController.processOutgoing(container1);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree2, 4, 2, true);
- await opProcessingController.ensureSynchronized();
- });
- it("Test Failure 12", async () => {
- insertProperties(sharedPropertyTree1, 0, 2, true);
- insertProperties(sharedPropertyTree2, 0, 3, true);
- await opProcessingController.processOutgoing(container1);
- await opProcessingController.processOutgoing(container2);
- removeProperties(sharedPropertyTree2, 2, 2, true);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree2, 1, 2, true);
- await opProcessingController.ensureSynchronized();
- });
- it("Test Failure 13", async () => {
- insertProperties(sharedPropertyTree1, 0, 2, true);
- await opProcessingController.processOutgoing(container1);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree2, 1, 3, true);
- removeProperties(sharedPropertyTree2, 4, 1, true);
- insertProperties(sharedPropertyTree2, 4, 3, true);
- insertProperties(sharedPropertyTree1, 1, 2, true);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processOutgoing(container1);
- await opProcessingController.ensureSynchronized();
- });
- it("Test Failure 14", async () => {
- insertProperties(sharedPropertyTree1, 0, 1, true);
- await opProcessingController.processOutgoing(container1);
- insertProperties(sharedPropertyTree2, 0, 2, true);
- await opProcessingController.processIncoming(container2);
- removeProperties(sharedPropertyTree2, 0, 1, true);
- await opProcessingController.processOutgoing(container2);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- await opProcessingController.ensureSynchronized();
- });
- it("Test Failure 15", async () => {
- insertProperties(sharedPropertyTree2, 0, 1, true);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processIncoming(container1);
- insertProperties(sharedPropertyTree2, 0, 2, true);
- await opProcessingController.processOutgoing(container2);
- insertProperties(sharedPropertyTree1, 0, 2, true);
- removeProperties(sharedPropertyTree1, 1, 3, true);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- await opProcessingController.ensureSynchronized();
- });
- it("Test Failure 16", async () => {
- insertProperties(sharedPropertyTree1, 0, 3, true);
- await opProcessingController.processOutgoing(container1);
- insertProperties(sharedPropertyTree2, 0, 1, true);
- removeProperties(sharedPropertyTree2, 0, 1, true);
- removeProperties(sharedPropertyTree1, 0, 3, true);
- insertProperties(sharedPropertyTree1, 0, 3, true);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree2, 2, 2, true);
- await opProcessingController.ensureSynchronized();
- });
- it("Test Failure 17", async () => {
- insertProperties(sharedPropertyTree1, 0, 3, true);
- await opProcessingController.processOutgoing(container1);
- insertProperties(sharedPropertyTree1, 2, 4, true);
- removeProperties(sharedPropertyTree1, 0, 3, true);
- removeProperties(sharedPropertyTree1, 1, 3, true);
- insertProperties(sharedPropertyTree1, 0, 2, true);
+ it("Test Failure 17", async () => {
+ insertProperties(sharedPropertyTree1, 0, 3, true);
+ await opProcessingController.processOutgoing(container1);
+ insertProperties(sharedPropertyTree1, 2, 4, true);
+ removeProperties(sharedPropertyTree1, 0, 3, true);
+ removeProperties(sharedPropertyTree1, 1, 3, true);
+ insertProperties(sharedPropertyTree1, 0, 2, true);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree2, 1, 1, true);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree2, 1, 1, true);
- await opProcessingController.ensureSynchronized();
- });
- it("Test Failure 18", async () => {
- insertProperties(sharedPropertyTree2, 0, 3, true);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processIncoming(container1);
- removeProperties(sharedPropertyTree2, 0, 3, true);
- insertProperties(sharedPropertyTree2, 0, 3, true);
- await opProcessingController.processOutgoing(container2);
- removeProperties(sharedPropertyTree1, 1, 2, true);
- insertProperties(sharedPropertyTree1, 0, 1, true);
+ await opProcessingController.ensureSynchronized();
+ });
+ it("Test Failure 18", async () => {
+ insertProperties(sharedPropertyTree2, 0, 3, true);
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processIncoming(container1);
+ removeProperties(sharedPropertyTree2, 0, 3, true);
+ insertProperties(sharedPropertyTree2, 0, 3, true);
+ await opProcessingController.processOutgoing(container2);
+ removeProperties(sharedPropertyTree1, 1, 2, true);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
- await opProcessingController.ensureSynchronized();
- });
- it("Test Failure 19", async () => {
- insertProperties(sharedPropertyTree1, 0, 3, true);
- insertProperties(sharedPropertyTree2, 0, 1, true);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processOutgoing(container1);
- await opProcessingController.processIncoming(container2);
- removeProperties(sharedPropertyTree2, 1, 3, true);
- await opProcessingController.processOutgoing(container1);
- removeProperties(sharedPropertyTree1, 0, 1, true);
- insertProperties(sharedPropertyTree1, 0, 3, true);
- removeProperties(sharedPropertyTree2, 0, 2, true);
+ await opProcessingController.ensureSynchronized();
+ });
+ it("Test Failure 19", async () => {
+ insertProperties(sharedPropertyTree1, 0, 3, true);
+ insertProperties(sharedPropertyTree2, 0, 1, true);
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processOutgoing(container1);
+ await opProcessingController.processIncoming(container2);
+ removeProperties(sharedPropertyTree2, 1, 3, true);
+ await opProcessingController.processOutgoing(container1);
+ removeProperties(sharedPropertyTree1, 0, 1, true);
+ insertProperties(sharedPropertyTree1, 0, 3, true);
+ removeProperties(sharedPropertyTree2, 0, 2, true);
- await opProcessingController.ensureSynchronized();
- });
+ await opProcessingController.ensureSynchronized();
+ });
- it("Test Failure 20", async () => {
- insertProperties(sharedPropertyTree1, 0, 2, true);
- await opProcessingController.processOutgoing(container2);
- insertProperties(sharedPropertyTree2, 0, 2, true);
- await opProcessingController.processOutgoing(container2);
- removeProperties(sharedPropertyTree2, 1, 3, true);
- await opProcessingController.processIncoming(container1);
- removeProperties(sharedPropertyTree1, 1, 3, true);
- insertProperties(sharedPropertyTree1, 1, 2, true);
- removeProperties(sharedPropertyTree2, 0, 1, true);
+ it("Test Failure 20", async () => {
+ insertProperties(sharedPropertyTree1, 0, 2, true);
+ await opProcessingController.processOutgoing(container2);
+ insertProperties(sharedPropertyTree2, 0, 2, true);
+ await opProcessingController.processOutgoing(container2);
+ removeProperties(sharedPropertyTree2, 1, 3, true);
+ await opProcessingController.processIncoming(container1);
+ removeProperties(sharedPropertyTree1, 1, 3, true);
+ insertProperties(sharedPropertyTree1, 1, 2, true);
+ removeProperties(sharedPropertyTree2, 0, 1, true);
- await opProcessingController.ensureSynchronized();
- });
+ await opProcessingController.ensureSynchronized();
+ });
- it("Test Failure 21", async () => {
- insertProperties(sharedPropertyTree1, 0, 7, true);
- await opProcessingController.processOutgoing(container1);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree1, 4, 2, true);
- await opProcessingController.processOutgoing(container1);
- insertProperties(sharedPropertyTree2, 5, 1, true);
- removeProperties(sharedPropertyTree2, 6, 2, true);
- insertProperties(sharedPropertyTree1, 6, 1, true);
- removeProperties(sharedPropertyTree1, 8, 1, true);
- await opProcessingController.processOutgoing(container2);
- removeProperties(sharedPropertyTree1, 7, 2, true);
+ it("Test Failure 21", async () => {
+ insertProperties(sharedPropertyTree1, 0, 7, true);
+ await opProcessingController.processOutgoing(container1);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree1, 4, 2, true);
+ await opProcessingController.processOutgoing(container1);
+ insertProperties(sharedPropertyTree2, 5, 1, true);
+ removeProperties(sharedPropertyTree2, 6, 2, true);
+ insertProperties(sharedPropertyTree1, 6, 1, true);
+ removeProperties(sharedPropertyTree1, 8, 1, true);
+ await opProcessingController.processOutgoing(container2);
+ removeProperties(sharedPropertyTree1, 7, 2, true);
- await opProcessingController.ensureSynchronized();
- });
+ await opProcessingController.ensureSynchronized();
+ });
- it("Test Failure 22", async () => {
- insertProperties(sharedPropertyTree2, 0, 3, true);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree2, 1, 2, true);
- removeProperties(sharedPropertyTree1, 0, 2, true);
- await opProcessingController.processOutgoing(container1);
- insertProperties(sharedPropertyTree1, 0, 1, true);
- await opProcessingController.processOutgoing(container1);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processIncoming(container1);
- await opProcessingController.ensureSynchronized();
- insertProperties(sharedPropertyTree1, 5, 2, true);
- await opProcessingController.processIncoming(container1);
- await opProcessingController.processOutgoing(container2);
- removeProperties(sharedPropertyTree2, 1, 1, true);
- removeProperties(sharedPropertyTree1, 6, 2, true);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree1, 3, 2, true);
- await opProcessingController.processOutgoing(container2);
- removeProperties(sharedPropertyTree2, 2, 3, true);
- removeProperties(sharedPropertyTree1, 3, 2, true);
- insertProperties(sharedPropertyTree2, 1, 3, true);
- await opProcessingController.ensureSynchronized();
- });
+ it("Test Failure 22", async () => {
+ insertProperties(sharedPropertyTree2, 0, 3, true);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree2, 1, 2, true);
+ removeProperties(sharedPropertyTree1, 0, 2, true);
+ await opProcessingController.processOutgoing(container1);
+ insertProperties(sharedPropertyTree1, 0, 1, true);
+ await opProcessingController.processOutgoing(container1);
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processIncoming(container1);
+ await opProcessingController.ensureSynchronized();
+ insertProperties(sharedPropertyTree1, 5, 2, true);
+ await opProcessingController.processIncoming(container1);
+ await opProcessingController.processOutgoing(container2);
+ removeProperties(sharedPropertyTree2, 1, 1, true);
+ removeProperties(sharedPropertyTree1, 6, 2, true);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree1, 3, 2, true);
+ await opProcessingController.processOutgoing(container2);
+ removeProperties(sharedPropertyTree2, 2, 3, true);
+ removeProperties(sharedPropertyTree1, 3, 2, true);
+ insertProperties(sharedPropertyTree2, 1, 3, true);
+ await opProcessingController.ensureSynchronized();
+ });
- it("Test failure 23", async () => {
- insertProperties(sharedPropertyTree2, 0, 4, true);
- insertProperties(sharedPropertyTree1, 0, 3, true);
- await opProcessingController.processOutgoing(container2);
- insertProperties(sharedPropertyTree2, 1, 3, true);
- removeProperties(sharedPropertyTree2, 0, 2, true);
- await opProcessingController.ensureSynchronized();
- });
+ it("Test failure 23", async () => {
+ insertProperties(sharedPropertyTree2, 0, 4, true);
+ insertProperties(sharedPropertyTree1, 0, 3, true);
+ await opProcessingController.processOutgoing(container2);
+ insertProperties(sharedPropertyTree2, 1, 3, true);
+ removeProperties(sharedPropertyTree2, 0, 2, true);
+ await opProcessingController.ensureSynchronized();
+ });
- it("Test failure 24", async () => {
- insertProperties(sharedPropertyTree2, 0, 6, true);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processIncoming(container1);
- removeProperties(sharedPropertyTree2, 4, 1, true);
- removeProperties(sharedPropertyTree1, 3, 3, true);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processIncoming(container1);
- removeProperties(sharedPropertyTree1, 2, 3, true);
+ it("Test failure 24", async () => {
+ insertProperties(sharedPropertyTree2, 0, 6, true);
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processIncoming(container1);
+ removeProperties(sharedPropertyTree2, 4, 1, true);
+ removeProperties(sharedPropertyTree1, 3, 3, true);
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processIncoming(container1);
+ removeProperties(sharedPropertyTree1, 2, 3, true);
- await opProcessingController.ensureSynchronized();
- });
+ await opProcessingController.ensureSynchronized();
+ });
- it("Test failure 25", async () => {
- insertProperties(sharedPropertyTree1, 0, 3, true);
- await opProcessingController.processOutgoing(container2);
- await opProcessingController.processOutgoing(container1);
- insertProperties(sharedPropertyTree2, 0, 3, true);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree2, 2, 1, true);
- removeProperties(sharedPropertyTree2, 0, 1, true);
- await opProcessingController.processOutgoing(container1);
- await opProcessingController.processIncoming(container2);
- insertProperties(sharedPropertyTree1, 1, 2, true);
- removeProperties(sharedPropertyTree2, 1, 2, true);
- await opProcessingController.processIncoming(container1);
- await opProcessingController.ensureSynchronized();
- });
+ it("Test failure 25", async () => {
+ insertProperties(sharedPropertyTree1, 0, 3, true);
+ await opProcessingController.processOutgoing(container2);
+ await opProcessingController.processOutgoing(container1);
+ insertProperties(sharedPropertyTree2, 0, 3, true);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree2, 2, 1, true);
+ removeProperties(sharedPropertyTree2, 0, 1, true);
+ await opProcessingController.processOutgoing(container1);
+ await opProcessingController.processIncoming(container2);
+ insertProperties(sharedPropertyTree1, 1, 2, true);
+ removeProperties(sharedPropertyTree2, 1, 2, true);
+ await opProcessingController.processIncoming(container1);
+ await opProcessingController.ensureSynchronized();
});
});
+ });
- describe("Rebase with pending changes.", () => {
- it("Int32", async () => {
- const val1 = 600;
- const val2 = 500;
+ describe("Rebase with pending changes.", () => {
+ it("Int32", async () => {
+ const val1 = 600;
+ const val2 = 500;
- await opProcessingController.pauseProcessing();
+ await opProcessingController.pauseProcessing();
- const prop = PropertyFactory.create("Int32");
- sharedPropertyTree1.root.insert("int32Prop", prop);
+ const prop = PropertyFactory.create("Int32");
+ sharedPropertyTree1.root.insert("int32Prop", prop);
- sharedPropertyTree1.commit();
- // Make sure both shared trees are in sync
- await opProcessingController.ensureSynchronized();
- await opProcessingController.pauseProcessing();
+ sharedPropertyTree1.commit();
+ // Make sure both shared trees are in sync
+ await opProcessingController.ensureSynchronized();
+ await opProcessingController.pauseProcessing();
- // Make local changes for both collaborators
- prop.setValue(val1);
- sharedPropertyTree2.root.get("int32Prop")?.setValue(val2);
+ // Make local changes for both collaborators
+ prop.setValue(val1);
+ sharedPropertyTree2.root.get("int32Prop")?.setValue(val2);
- sharedPropertyTree1.commit();
- await opProcessingController.ensureSynchronized();
- await opProcessingController.pauseProcessing();
+ sharedPropertyTree1.commit();
+ await opProcessingController.ensureSynchronized();
+ await opProcessingController.pauseProcessing();
- // This collaborator should still have pending changes after rebase the incoming commits
- expect(Object.keys(
- sharedPropertyTree2.root.getPendingChanges().getSerializedChangeSet()).length).to.not.equal(0);
+ // This collaborator should still have pending changes after rebase the incoming commits
+ expect(Object.keys(
+ sharedPropertyTree2.root.getPendingChanges().getSerializedChangeSet()).length).to.not.equal(0);
- // Committing the new pending change
- sharedPropertyTree2.commit();
- await opProcessingController.ensureSynchronized();
+ // Committing the new pending change
+ sharedPropertyTree2.commit();
+ await opProcessingController.ensureSynchronized();
- // The pending change val2 should be now the new value cross collaborators
- expect(prop.getValue()).to.equal(val2);
- });
+ // The pending change val2 should be now the new value cross collaborators
+ expect(prop.getValue()).to.equal(val2);
});
- }
+ });
+ }
- describe("Rebasing", () => {
+ describe("Rebasing", () => {
beforeEach(async () => {
- await setupContainers();
- });
+ await setupContainers();
+ });
- rebaseTests();
- });
+ rebaseTests();
+ });
describe("Rebasing with reconnection", () => {
beforeEach(async () => {
- await setupContainers(false);
- });
+ await setupContainers(false);
+ });
- rebaseTests();
- });
+ rebaseTests();
+ });
});
diff --git a/packages/drivers/odsp-driver/src/test/test.ts b/packages/drivers/odsp-driver/src/test/test.ts
index da4f97dcfe30..3de42768679c 100644
--- a/packages/drivers/odsp-driver/src/test/test.ts
+++ b/packages/drivers/odsp-driver/src/test/test.ts
@@ -40,5 +40,5 @@ describe("Binary WireFormat perf", () => {
const parseTime = performance.now() - start;
console.log("Binary Format medium snapshot parse time ", parseTime);
- });
+ }).timeout(3000); // This can take more time if running in parallel
});
diff --git a/packages/test/mocha-test-setup/mocharc-common.js b/packages/test/mocha-test-setup/mocharc-common.js
index a3075cb49348..a973c8631d9f 100644
--- a/packages/test/mocha-test-setup/mocharc-common.js
+++ b/packages/test/mocha-test-setup/mocharc-common.js
@@ -13,10 +13,10 @@ function getFluidTestMochaConfig(packageDir, additionalRequiredModules, testRepo
const moduleDir = `${packageDir}/node_modules`;
const requiredModules = [
- ...(additionalRequiredModules ? additionalRequiredModules : []),
- // General mocha setup e.g. suppresses console.log
- // Moved to last in required modules, so that aria logger will be ready to access in mochaHooks.ts
+ // General mocha setup e.g. suppresses console.log,
+ // This has to be before others (except logger) so that registerMochaTestWrapperFuncs is available
`@fluidframework/mocha-test-setup`,
+ ...(additionalRequiredModules ? additionalRequiredModules : []),
];
// mocha install node_modules directory might not be the same as the module required because of hoisting
@@ -32,7 +32,7 @@ function getFluidTestMochaConfig(packageDir, additionalRequiredModules, testRepo
});
if (process.env.FLUID_TEST_LOGGER_PKG_PATH) {
- // Inject implementation of getTestLogger
+ // Inject implementation of getTestLogger, put it first before mocha-test-setup
requiredModulePaths.unshift(process.env.FLUID_TEST_LOGGER_PKG_PATH);
}
diff --git a/packages/test/mocha-test-setup/src/mochaHooks.ts b/packages/test/mocha-test-setup/src/mochaHooks.ts
index 19a1d5c341a2..3e730c525447 100644
--- a/packages/test/mocha-test-setup/src/mochaHooks.ts
+++ b/packages/test/mocha-test-setup/src/mochaHooks.ts
@@ -5,7 +5,7 @@
import { ITelemetryBufferedLogger } from "@fluidframework/test-driver-definitions";
import { ITelemetryBaseEvent } from "@fluidframework/common-definitions";
-import { Context } from "mocha";
+import * as mochaModule from "mocha";
import { pkgName } from "./packageVersion";
const testVariant = process.env.FLUID_TEST_VARIANT;
@@ -52,7 +52,7 @@ export const mochaHooks = {
return currentTestLogger ?? originalLogger;
};
},
- beforeEach() {
+ beforeEach(this: Mocha.Context) {
// Suppress console.log if not verbose mode
if (process.env.FLUID_TEST_VERBOSE === undefined) {
console.log = () => { };
@@ -60,8 +60,7 @@ export const mochaHooks = {
console.warn = () => { };
}
// save the test name can and clear the previous logger (if afterEach didn't get ran and it got left behind)
- const context = this as any as Context;
- currentTestName = context.currentTest?.fullTitle();
+ currentTestName = this.currentTest?.fullTitle();
currentTestLogger = undefined;
// send event on test start
@@ -73,16 +72,15 @@ export const mochaHooks = {
hostName: pkgName,
});
},
- afterEach() {
+ afterEach(this: Mocha.Context) {
// send event on test end
- const context = this as any as Context;
originalLogger.send({
category: "generic",
eventName: "fluid:telemetry:Test_end",
testName: currentTestName,
- state: context.currentTest?.state,
- duration: context.currentTest?.duration,
- timedOut: context.currentTest?.timedOut,
+ state: this.currentTest?.state,
+ duration: this.currentTest?.duration,
+ timedOut: this.currentTest?.timedOut,
testVariant,
hostName: pkgName,
});
@@ -96,3 +94,7 @@ export const mochaHooks = {
currentTestName = undefined;
},
};
+
+globalThis.getMochaModule = () => {
+ return mochaModule;
+};
diff --git a/packages/test/test-end-to-end-tests/src/test/blobs.spec.ts b/packages/test/test-end-to-end-tests/src/test/blobs.spec.ts
index 2944d48ea998..fef147900095 100644
--- a/packages/test/test-end-to-end-tests/src/test/blobs.spec.ts
+++ b/packages/test/test-end-to-end-tests/src/test/blobs.spec.ts
@@ -130,7 +130,7 @@ describeFullCompat("blobs", (getTestObjectProvider) => {
const container2 = await provider.loadTestContainer(testContainerConfig);
const dataStore2 = await requestFluidObject(container2, "default");
- await provider.ensureSynchronized(this.timeout() / 3);
+ await provider.ensureSynchronized();
const blobHandle = dataStore2._root.get>(testKey);
assert(blobHandle);
@@ -181,7 +181,7 @@ describeFullCompat("blobs", (getTestObjectProvider) => {
// validate on remote container, local container, and container loaded from summary
for (const container of [container1, container2, await provider.loadTestContainer(testContainerConfig)]) {
const dataStore2 = await requestFluidObject(container, "default");
- await provider.ensureSynchronized(this.timeout() / 3);
+ await provider.ensureSynchronized();
const handle = dataStore2._root.get>("sharedString");
assert(handle);
const sharedString2 = await handle.get();
diff --git a/packages/test/test-end-to-end-tests/src/test/counterEndToEndTests.spec.ts b/packages/test/test-end-to-end-tests/src/test/counterEndToEndTests.spec.ts
index 6fe6f9cf5c18..09f2d7f3c5ca 100644
--- a/packages/test/test-end-to-end-tests/src/test/counterEndToEndTests.spec.ts
+++ b/packages/test/test-end-to-end-tests/src/test/counterEndToEndTests.spec.ts
@@ -127,7 +127,7 @@ describeFullCompat("SharedCounter", (getTestObjectProvider) => {
// do the increment
incrementer.increment(incrementAmount);
- await provider.ensureSynchronized(this.timeout() / 3);
+ await provider.ensureSynchronized();
// event count is correct
assert.equal(eventCount1, expectedEventCount);
diff --git a/packages/test/test-end-to-end-tests/src/test/deRehydrateContainerTests.spec.ts b/packages/test/test-end-to-end-tests/src/test/deRehydrateContainerTests.spec.ts
index 41f69da5c750..b7765b657b21 100644
--- a/packages/test/test-end-to-end-tests/src/test/deRehydrateContainerTests.spec.ts
+++ b/packages/test/test-end-to-end-tests/src/test/deRehydrateContainerTests.spec.ts
@@ -608,7 +608,7 @@ describeFullCompat(`Dehydrate Rehydrate Container Test`, (getTestObjectProvider)
// Create and reference another dataStore
const { peerDataStore: dataStore2 } = await createPeerDataStore(defaultDataStore.context.containerRuntime);
defaultDataStore.root.set("dataStore2", dataStore2.handle);
- await provider.ensureSynchronized(this.timeout() / 3);
+ await provider.ensureSynchronized();
const sharedMap1 = await dataStore2.getSharedObject(sharedMapId);
sharedMap1.set("0", "A");
diff --git a/packages/test/test-end-to-end-tests/src/test/directoryEndToEndTests.spec.ts b/packages/test/test-end-to-end-tests/src/test/directoryEndToEndTests.spec.ts
index 90f9b4dfdd64..81d57eae3926 100644
--- a/packages/test/test-end-to-end-tests/src/test/directoryEndToEndTests.spec.ts
+++ b/packages/test/test-end-to-end-tests/src/test/directoryEndToEndTests.spec.ts
@@ -247,7 +247,7 @@ describeFullCompat("SharedDirectory", (getTestObjectProvider) => {
expectAllBeforeValues("testKey3", "/", "value3.1", "value3.2", undefined);
- await provider.ensureSynchronized(this.timeout() / 3);
+ await provider.ensureSynchronized();
expectAllAfterValues("testKey3", "/", undefined);
});
@@ -511,7 +511,7 @@ describeFullCompat("SharedDirectory", (getTestObjectProvider) => {
expectAllBeforeValues("testKey3", "/testSubDir", "value3.1", "value3.2", undefined);
- await provider.ensureSynchronized(this.timeout() / 3);
+ await provider.ensureSynchronized();
expectAllAfterValues("testKey3", "/testSubDir", undefined);
});
@@ -576,7 +576,7 @@ describeFullCompat("SharedDirectory", (getTestObjectProvider) => {
const root2SubDir = sharedDirectory2.createSubDirectory("testSubDir");
root2SubDir.set("testKey2", "testValue2");
- await provider.ensureSynchronized(this.timeout() / 2);
+ await provider.ensureSynchronized();
assert.strictEqual(sharedDirectory1.getSubDirectory("testSubDir"), root1SubDir,
"Created two separate subdirectories in root1");
diff --git a/packages/test/test-end-to-end-tests/src/test/localLoader.spec.ts b/packages/test/test-end-to-end-tests/src/test/localLoader.spec.ts
index e911773a0d48..8190fbb4f4f7 100644
--- a/packages/test/test-end-to-end-tests/src/test/localLoader.spec.ts
+++ b/packages/test/test-end-to-end-tests/src/test/localLoader.spec.ts
@@ -8,7 +8,8 @@ import {
ContainerRuntimeFactoryWithDefaultDataStore,
DataObject,
DataObjectFactory,
- IDataObjectProps } from "@fluidframework/aqueduct";
+ IDataObjectProps,
+} from "@fluidframework/aqueduct";
import { IContainer, IFluidCodeDetails } from "@fluidframework/container-definitions";
import { IFluidHandle, IRequest } from "@fluidframework/core-interfaces";
import { SharedCounter } from "@fluidframework/counter";
diff --git a/packages/test/test-utils/package.json b/packages/test/test-utils/package.json
index 1b19fb9d74e9..3a3347952ee9 100644
--- a/packages/test/test-utils/package.json
+++ b/packages/test/test-utils/package.json
@@ -29,6 +29,9 @@
"eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
"lint": "npm run eslint",
"lint:fix": "npm run eslint:fix",
+ "test": "npm run test:mocha",
+ "test:mocha": "mocha --unhandled-rejections=strict --recursive dist/test/*.spec.js --exit --project src/test/tsconfig.json -r node_modules/@fluidframework/mocha-test-setup",
+ "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
"tsc": "tsc",
"tsfmt": "tsfmt --verify",
"tsfmt:fix": "tsfmt --replace",
@@ -69,6 +72,7 @@
"@fluidframework/driver-utils": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
"@fluidframework/local-driver": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
"@fluidframework/map": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
"@fluidframework/protocol-definitions": "^1.1.0-97957",
"@fluidframework/request-handler": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
"@fluidframework/routerlicious-driver": ">=2.0.0-internal.3.0.0 <2.0.0-internal.4.0.0",
@@ -94,6 +98,7 @@
"@types/random-js": "^1.0.31",
"concurrently": "^6.2.0",
"copyfiles": "^2.4.1",
+ "cross-env": "^7.0.2",
"diff": "^3.5.0",
"eslint": "~8.6.0",
"mocha": "^10.0.0",
@@ -102,9 +107,5 @@
"rimraf": "^2.6.2",
"typescript": "~4.5.5",
"typescript-formatter": "7.1.0"
- },
- "typeValidation": {
- "version": "2.0.0",
- "broken": {}
}
}
diff --git a/packages/test/test-utils/src/TestSummaryUtils.ts b/packages/test/test-utils/src/TestSummaryUtils.ts
index 0d7a9683e81e..879bf10a284f 100644
--- a/packages/test/test-utils/src/TestSummaryUtils.ts
+++ b/packages/test/test-utils/src/TestSummaryUtils.ts
@@ -23,6 +23,7 @@ import { requestFluidObject } from "@fluidframework/runtime-utils";
import { IConfigProviderBase } from "@fluidframework/telemetry-utils";
import { ITestContainerConfig, ITestObjectProvider } from "./testObjectProvider";
import { mockConfigProvider } from "./TestConfigs";
+import { timeoutAwait } from "./timeoutUtils";
const summarizerClientType = "summarizer";
@@ -117,16 +118,16 @@ export async function createSummarizer(
export async function summarizeNow(summarizer: ISummarizer, reason: string = "end-to-end test") {
const result = summarizer.summarizeOnDemand({ reason });
- const submitResult = await result.summarySubmitted;
+ const submitResult = await timeoutAwait(result.summarySubmitted);
assert(submitResult.success, "on-demand summary should submit");
assert(submitResult.data.stage === "submit",
"on-demand summary submitted data stage should be submit");
assert(submitResult.data.summaryTree !== undefined, "summary tree should exist");
- const broadcastResult = await result.summaryOpBroadcasted;
+ const broadcastResult = await timeoutAwait(result.summaryOpBroadcasted);
assert(broadcastResult.success, "summary op should be broadcast");
- const ackNackResult = await result.receivedSummaryAckOrNack;
+ const ackNackResult = await timeoutAwait(result.receivedSummaryAckOrNack);
assert(ackNackResult.success, "summary op should be acked");
await new Promise((resolve) => process.nextTick(resolve));
diff --git a/packages/test/test-utils/src/loaderContainerTracker.ts b/packages/test/test-utils/src/loaderContainerTracker.ts
index b2cc7c8c0bfe..bfe92a7f4d54 100644
--- a/packages/test/test-utils/src/loaderContainerTracker.ts
+++ b/packages/test/test-utils/src/loaderContainerTracker.ts
@@ -2,8 +2,6 @@
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/
-/* eslint-disable @typescript-eslint/strict-boolean-expressions */
-
import { assert } from "@fluidframework/common-utils";
import { IContainer, IDeltaQueue, IHostLoader } from "@fluidframework/container-definitions";
import { Container } from "@fluidframework/container-loader";
@@ -16,9 +14,6 @@ import { timeoutAwait, timeoutPromise } from "./timeoutUtils";
const debugOp = debug.extend("ops");
const debugWait = debug.extend("wait");
-// set the maximum timeout value as 5 mins
-const defaultMaxTimeout = 5 * 6000;
-
interface ContainerRecord {
// A short number for debug output
index: number;
@@ -187,7 +182,6 @@ export class LoaderContainerTracker implements IOpProcessingController {
* - Trailing NoOp is tracked and don't count as pending ops.
*/
private async processSynchronized(timeoutDuration: number | undefined, ...containers: IContainer[]) {
- const start = Date.now();
const resumed = this.resumeProcessing(...containers);
let waitingSequenceNumberSynchronized = false;
@@ -214,14 +208,12 @@ export class LoaderContainerTracker implements IOpProcessingController {
waitingSequenceNumberSynchronized = true;
debugWait("Waiting for sequence number synchronized");
await timeoutAwait(this.waitForAnyInboundOps(containersToApply), {
- durationMs: timeoutDuration ? timeoutDuration - (Date.now() - start) : defaultMaxTimeout,
errorMsg: "Timeout on waiting for sequence number synchronized",
});
}
} else {
waitingSequenceNumberSynchronized = false;
await timeoutAwait(this.waitForPendingClients(pendingClients), {
- durationMs: timeoutDuration ? timeoutDuration - (Date.now() - start) : defaultMaxTimeout,
errorMsg: "Timeout on waiting for pending join or leave op",
});
}
@@ -230,12 +222,10 @@ export class LoaderContainerTracker implements IOpProcessingController {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
debugWait(`Waiting container to be saved ${dirtyContainers.map((c) => this.containers.get(c)!.index)}`);
waitingSequenceNumberSynchronized = false;
- const remainedDuration = timeoutDuration ? timeoutDuration - (Date.now() - start) : defaultMaxTimeout;
await Promise.all(dirtyContainers.map(async (c) => Promise.race(
[timeoutPromise(
(resolve) => c.once("saved", () => resolve()),
{
- durationMs: remainedDuration,
errorMsg: "Timeout on waiting a container to be saved",
},
),
@@ -252,7 +242,6 @@ export class LoaderContainerTracker implements IOpProcessingController {
// don't call pause if resumed is empty and pause everything, which is not what we want
if (resumed.length !== 0) {
await timeoutAwait(this.pauseProcessing(...resumed), {
- durationMs: timeoutDuration ? timeoutDuration - (Date.now() - start) : defaultMaxTimeout,
errorMsg: "Timeout on waiting for pausing all resumed containers",
});
}
diff --git a/packages/test/test-utils/src/test/timeoutUtils.spec.ts b/packages/test/test-utils/src/test/timeoutUtils.spec.ts
new file mode 100644
index 000000000000..349d73337b3d
--- /dev/null
+++ b/packages/test/test-utils/src/test/timeoutUtils.spec.ts
@@ -0,0 +1,158 @@
+/*!
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+import { strict as assert } from "assert";
+import { timeoutPromise } from "../timeoutUtils";
+
+describe("TimeoutPromise", () => {
+ beforeEach(async () => {
+ // Make sure there are no timeout set left behind, wait larger then the test timeout
+ await timeoutPromise((resolve) => setTimeout(resolve, 50));
+ });
+
+ afterEach(async () => {
+ // Make sure there are no timeout set left behind, wait larger then the test timeout
+ await timeoutPromise((resolve) => setTimeout(resolve, 50));
+ });
+
+ let runCount = 0;
+
+ it("No timeout", async () => {
+ if (runCount++ !== 1) {
+ // only timeout the first time to test, but pass the second one,
+ // to test the behavior of test timed out by mocha
+ // to ensure that we reset the timeoutPromise state.
+ const value = await timeoutPromise((resolve) => { resolve(3); });
+ assert(value === 3, "Value not returned");
+ }
+ }).timeout(25).retries(1);
+
+ it("Timeout", async () => {
+ if (runCount++ !== 1) {
+ // only timeout the first time to test, but pass the second one,
+ // to test the behavior of test timed out by mocha
+ // to ensure that we reset the timeoutPromise state.
+ return new Promise(() => { });
+ }
+ }).timeout(25).retries(1);
+
+ it("Timeout with no options", async () => {
+ try {
+ await timeoutPromise(() => { });
+ assert(false, "should have timed out");
+ } catch (e: any) {
+ assert(e.message.startsWith("Test timed out ("), `expected timeout error message: got ${e.message}`);
+ }
+ }).timeout(25);
+
+ it("Timeout with no duration", async () => {
+ try {
+ await timeoutPromise(() => { }, {});
+ assert(false, "should have timed out");
+ } catch (e: any) {
+ assert(e.message.startsWith("Test timed out ("), `expected timeout error message: got ${e.message}`);
+ }
+ }).timeout(25);
+
+ it("Timeout with duration", async () => {
+ try {
+ await timeoutPromise(() => { }, { durationMs: 1 });
+ assert(false, "should have timed out");
+ } catch (e: any) {
+ assert(e.message.startsWith("Timed out ("), `expected timeout error message: got ${e.message}`);
+ }
+ }).timeout(25);
+
+ it("No timeout with zero duration", async () => {
+ try {
+ await timeoutPromise((resolve) => {
+ setTimeout(resolve, 10);
+ }, { durationMs: 0 });
+ } catch (e: any) {
+ assert(false, `should not have timed out: ${e.message}`);
+ }
+ }).timeout(25);
+
+ it("No timeout with negative duration", async function() {
+ // Make sure resetTimeout in the test works
+ this.timeout(100);
+ try {
+ await timeoutPromise((resolve) => {
+ setTimeout(resolve, 50);
+ }, { durationMs: -1 });
+ } catch (e: any) {
+ assert(false, `should not have timed out: ${e.message}`);
+ }
+ }).timeout(25);
+
+ it("No timeout with Infinity duration", async function() {
+ // Make sure resetTimeout in the test works
+ this.timeout(100);
+ try {
+ await timeoutPromise((resolve) => {
+ setTimeout(resolve, 50);
+ }, { durationMs: Infinity });
+ } catch (e: any) {
+ assert(false, `should not have timed out: ${e.message}`);
+ }
+ }).timeout(25);
+
+ it("No timeout with valid duration", async function() {
+ // Make sure resetTimeout in the test works
+ this.timeout(100);
+ try {
+ await timeoutPromise((resolve) => { setTimeout(resolve, 50); }, { durationMs: 75 });
+ } catch (e: any) {
+ assert(false, `should not have timed out: ${e.message}`);
+ }
+ }).timeout(25);
+
+ it("No timeout with throw", async function() {
+ // Make sure resetTimeout in the test works
+ this.timeout(100);
+ try {
+ await timeoutPromise((resolve, reject) => { reject(new Error("blah")); });
+ assert(false, "should have thrown");
+ } catch (e: any) {
+ assert(e.message === "blah", `should not have timed out: ${e.message}`);
+ }
+ }).timeout(25);
+
+ it("Timeout with valid duration", async function() {
+ // Make sure resetTimeout in the test works
+ this.timeout(100);
+ try {
+ await timeoutPromise((resolve) => { setTimeout(resolve, 75); }, { durationMs: 50 });
+ assert(false, "should have timed out");
+ } catch (e: any) {
+ assert(e.message.startsWith("Timed out ("), "expected timeout error message");
+ }
+ }).timeout(25);
+
+ it("Timeout with no reject option", async () => {
+ try {
+ const value = await timeoutPromise(() => { }, {
+ durationMs: 1,
+ reject: false,
+ value: 1,
+ });
+ assert(value === 1, "expect timeout to return value given in option");
+ } catch (e: any) {
+ assert(false, `should not have timed out: ${e.message}`);
+ }
+ }).timeout(25);
+
+ it("Timeout rejection with error option", async () => {
+ try {
+ await timeoutPromise(() => { }, {
+ durationMs: 1,
+ errorMsg: "hello",
+ });
+ assert(false, "should have timed out");
+ } catch (e: any) {
+ assert(e.message.startsWith("hello"), "expected timeout reject error message given in option");
+ }
+ }).timeout(25);
+});
diff --git a/packages/test/test-utils/src/test/tsconfig.json b/packages/test/test-utils/src/test/tsconfig.json
index 913f09ed3b1b..710a3b4b93b2 100644
--- a/packages/test/test-utils/src/test/tsconfig.json
+++ b/packages/test/test-utils/src/test/tsconfig.json
@@ -3,10 +3,12 @@
"compilerOptions": {
"rootDir": "./",
"outDir": "../../dist/test",
+ "types": [
+ "mocha",
+ ],
"declaration": false,
"declarationMap": false,
"skipLibCheck": true,
- "noEmit": true,
},
"include": [
"./**/*"
diff --git a/packages/test/test-utils/src/testObjectProvider.ts b/packages/test/test-utils/src/testObjectProvider.ts
index 64b77bce7f92..21d158862313 100644
--- a/packages/test/test-utils/src/testObjectProvider.ts
+++ b/packages/test/test-utils/src/testObjectProvider.ts
@@ -140,7 +140,7 @@ export class EventAndErrorTrackingLogger extends TelemetryLogger {
private readonly expectedEvents: ({ index: number; event: ITelemetryGenericEvent | undefined; } | undefined)[] = [];
private readonly unexpectedErrors: ITelemetryBaseEvent[] = [];
- public registerExpectedEvent(... orderedExpectedEvents: ITelemetryGenericEvent[]) {
+ public registerExpectedEvent(...orderedExpectedEvents: ITelemetryGenericEvent[]) {
if (this.expectedEvents.length !== 0) {
// we don't have to error here. just no reason not to. given the events must be
// ordered it could be tricky to figure out problems around multiple registrations.
@@ -148,7 +148,7 @@ export class EventAndErrorTrackingLogger extends TelemetryLogger {
"Expected events already registered.\n"
+ "Call reportAndClearTrackedEvents to clear them before registering more");
}
- this.expectedEvents.push(... orderedExpectedEvents.map((event, index) => ({ index, event })));
+ this.expectedEvents.push(...orderedExpectedEvents.map((event, index) => ({ index, event })));
}
send(event: ITelemetryBaseEvent): void {
@@ -219,14 +219,14 @@ export class TestObjectProvider implements ITestObjectProvider {
if (this._logger === undefined) {
this._logger = new EventAndErrorTrackingLogger(
ChildLogger.create(getTestLogger?.(), undefined,
- {
- all: {
- driverType: this.driver.type,
- driverEndpointName: this.driver.endpointName,
- driverTenantName: this.driver.tenantName,
- driverUserIndex: this.driver.userIndex,
- },
- }));
+ {
+ all: {
+ driverType: this.driver.type,
+ driverEndpointName: this.driver.endpointName,
+ driverTenantName: this.driver.tenantName,
+ driverUserIndex: this.driver.userIndex,
+ },
+ }));
}
return this._logger;
}
@@ -280,7 +280,7 @@ export class TestObjectProvider implements ITestObjectProvider {
}
const loader = new this.LoaderConstructor({
- ... loaderProps,
+ ...loaderProps,
logger: multiSinkLogger,
codeLoader: loaderProps?.codeLoader ?? new LocalCodeLoader(packageEntries),
urlResolver: loaderProps?.urlResolver ?? this.urlResolver,
@@ -394,10 +394,9 @@ export class TestObjectProvider implements ITestObjectProvider {
}
public async ensureSynchronized(timeoutDuration?: number): Promise {
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- return !timeoutDuration
- ? this._loaderContainerTracker.ensureSynchronized()
- : this._loaderContainerTracker.ensureSynchronizedWithTimeout?.(timeoutDuration);
+ return this._loaderContainerTracker.ensureSynchronizedWithTimeout
+ ? this._loaderContainerTracker.ensureSynchronizedWithTimeout(timeoutDuration)
+ : this._loaderContainerTracker.ensureSynchronized();
}
public async waitContainerToCatchUp(container: IContainer) {
diff --git a/packages/test/test-utils/src/timeoutUtils.ts b/packages/test/test-utils/src/timeoutUtils.ts
index 4fcc765633a0..bc4dd86a18b2 100644
--- a/packages/test/test-utils/src/timeoutUtils.ts
+++ b/packages/test/test-utils/src/timeoutUtils.ts
@@ -4,15 +4,116 @@
*/
import { Container } from "@fluidframework/container-loader";
+import { assert, Deferred } from "@fluidframework/common-utils";
+// @deprecated this value is no longer used
export const defaultTimeoutDurationMs = 250;
+// TestTimeout class manage tracking of test timeout. It create a timer when timeout is in effect,
+// and provide a promise that will be reject before the test timeout happen with a `timeBuffer` of 15 ms.
+// Once rejected, a new TestTimeout object will be create for the timeout.
+
+const timeBuffer = 15; // leave 15 ms leeway for finish processing
+
+class TestTimeout {
+ private timeout: number = 0;
+ private timer: NodeJS.Timeout | undefined;
+ private readonly deferred: Deferred;
+ private rejected = false;
+
+ private static instance: TestTimeout = new TestTimeout();
+ public static reset(runnable: Mocha.Runnable) {
+ TestTimeout.clear();
+ TestTimeout.instance.resetTimer(runnable);
+ }
+
+ public static clear() {
+ if (TestTimeout.instance.rejected) {
+ TestTimeout.instance = new TestTimeout();
+ } else {
+ TestTimeout.instance.clearTimer();
+ }
+ }
+
+ public static getInstance() {
+ return TestTimeout.instance;
+ }
+
+ public async getPromise() {
+ return this.deferred.promise;
+ }
+
+ public getTimeout() {
+ return this.timeout;
+ }
+
+ private constructor() {
+ this.deferred = new Deferred();
+ // Ignore rejection for timeout promise if no one is waiting for it.
+ this.deferred.promise.catch(() => { });
+ }
+
+ private resetTimer(runnable: Mocha.Runnable) {
+ assert(!this.timer, "clearTimer should have been called before reset");
+ assert(!this.deferred.isCompleted, "can't reset a completed TestTimeout");
+
+ // Check the test timeout setting
+ const timeout = runnable.timeout();
+ if (!(Number.isFinite(timeout) && timeout > 0)) { return; }
+
+ // subtract a buffer
+ this.timeout = Math.max(timeout - timeBuffer, 1);
+
+ // Set up timer to reject near the test timeout.
+ this.timer = setTimeout(() => {
+ this.deferred.reject(this);
+ this.rejected = true;
+ }, this.timeout);
+ }
+ private clearTimer() {
+ if (this.timer) {
+ clearTimeout(this.timer);
+ this.timer = undefined;
+ }
+ }
+}
+
+// only register if we are running with mocha-test-setup loaded
+if (globalThis.getMochaModule !== undefined) {
+ // patching resetTimeout and clearTimeout on the runnable object
+ // so we can track when test timeout are enforced
+ const mochaModule = globalThis.getMochaModule() as typeof Mocha;
+ const runnablePrototype = mochaModule.Runnable.prototype;
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ const oldResetTimeoutFunc = runnablePrototype.resetTimeout;
+ runnablePrototype.resetTimeout = function(this: Mocha.Runnable) {
+ oldResetTimeoutFunc.call(this);
+ TestTimeout.reset(this);
+ };
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ const oldClearTimeoutFunc = runnablePrototype.clearTimeout;
+ runnablePrototype.clearTimeout = function(this: Mocha.Runnable) {
+ TestTimeout.clear();
+ oldClearTimeoutFunc.call(this);
+ };
+}
+
export interface TimeoutWithError {
+ /**
+ * Timeout duration in milliseconds, if it is great than 0 and not Infinity
+ * If it is undefined, then it will use test timeout if we are in side the test function
+ * Otherwise, there is no timeout
+ */
durationMs?: number;
reject?: true;
errorMsg?: string;
}
export interface TimeoutWithValue {
+ /**
+ * Timeout duration in milliseconds, if it is great than 0 and not Infinity
+ * If it is undefined, then it will use test timeout if we are in side the test function
+ * Otherwise, there is no timeout
+ */
durationMs?: number;
reject: false;
value: T;
@@ -31,23 +132,26 @@ export async function ensureContainerConnected(container: Container): Promise(
+// Create a promise based on the timeout options
+async function getTimeoutPromise(
executor: (resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void) => void,
- timeoutOptions: TimeoutWithError | TimeoutWithValue = {},
-): Promise {
- const timeout =
- timeoutOptions.durationMs !== undefined
- && Number.isFinite(timeoutOptions.durationMs)
- && timeoutOptions.durationMs > 0
- ? timeoutOptions.durationMs : defaultTimeoutDurationMs;
- // create the timeout error outside the async task, so its callstack includes
- // the original call site, this makes it easier to debug
- const err = timeoutOptions.reject === false
- ? undefined
- : new Error(`${timeoutOptions.errorMsg ?? "Timed out"}(${timeout}ms)`);
+ timeoutOptions: TimeoutWithError | TimeoutWithValue,
+ err: Error | undefined,
+) {
+ const timeout = timeoutOptions.durationMs ?? 0;
+ if (timeout <= 0 || !Number.isFinite(timeout)) {
+ return new Promise(executor);
+ }
+
return new Promise((resolve, reject) => {
+ const timeoutRejections = () => {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const errorObject = err!;
+ errorObject.message = `${errorObject.message} (${timeout}ms)`;
+ reject(err);
+ };
const timer = setTimeout(
- () => timeoutOptions.reject === false ? resolve(timeoutOptions.value) : reject(err),
+ () => timeoutOptions.reject === false ? resolve(timeoutOptions.value) : timeoutRejections(),
timeout);
executor(
@@ -61,3 +165,34 @@ export async function timeoutPromise(
});
});
}
+
+// Create a promise based on test timeout and the timeout options
+export async function timeoutPromise(
+ executor: (resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void) => void,
+ timeoutOptions: TimeoutWithError | TimeoutWithValue = {},
+): Promise {
+ // create the timeout error outside the async task, so its callstack includes
+ // the original call site, this makes it easier to debug
+ const err = timeoutOptions.reject === false
+ ? undefined
+ : new Error(timeoutOptions.errorMsg ?? "Timed out");
+ const executorPromise = getTimeoutPromise(executor, timeoutOptions, err);
+
+ const currentTestTimeout = TestTimeout.getInstance();
+ if (currentTestTimeout === undefined) { return executorPromise; }
+
+ return Promise.race([executorPromise, currentTestTimeout.getPromise()]).catch((e) => {
+ if (e === currentTestTimeout) {
+ if (timeoutOptions.reject !== false) {
+ // If the rejection is because of the timeout then
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const errorObject = err!;
+ errorObject.message =
+ `${timeoutOptions.errorMsg ?? "Test timed out"} (${currentTestTimeout.getTimeout()}ms)`;
+ throw errorObject;
+ }
+ return timeoutOptions.value;
+ }
+ throw e;
+ }) as Promise;
+}
diff --git a/packages/test/test-utils/tsconfig.json b/packages/test/test-utils/tsconfig.json
index f156559ebdf6..aa4bc4eb8d3c 100644
--- a/packages/test/test-utils/tsconfig.json
+++ b/packages/test/test-utils/tsconfig.json
@@ -6,7 +6,10 @@
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
- "composite": true
+ "composite": true,
+ "types": [
+ "mocha",
+ ]
},
"include": [
"src/**/*"
diff --git a/packages/test/test-version-utils/src/versionUtils.ts b/packages/test/test-version-utils/src/versionUtils.ts
index 4ff55f91c9e0..95703ee76c55 100644
--- a/packages/test/test-version-utils/src/versionUtils.ts
+++ b/packages/test/test-version-utils/src/versionUtils.ts
@@ -101,7 +101,7 @@ export function resolveVersion(requested: string, installed: boolean) {
}
if (installed) {
- // Check the install directory instad of asking NPM for it.
+ // Check the install directory instead of asking NPM for it.
const files = readdirSync(baseModulePath, { withFileTypes: true });
let found: string | undefined;
files.map((dirent) => {
@@ -122,7 +122,7 @@ export function resolveVersion(requested: string, installed: boolean) {
);
// if we are requesting an x.x.0-0 prerelease and failed the first try, try
// again using the virtualPatch schema
- if (result === "") {
+ if (result === "" && !requested.includes("internal")) {
const requestedVersion = new semver.SemVer(requested.substring(requested.indexOf("^") + 1));
if (requestedVersion.patch === 0 && requestedVersion.prerelease.length > 0) {
const retryVersion = `^${requestedVersion.major}.${requestedVersion.minor}.1000-0`;
@@ -134,14 +134,17 @@ export function resolveVersion(requested: string, installed: boolean) {
}
try {
- const versions: string | string[] = JSON.parse(result);
+ const versions: string | string[] = result !== "" ? JSON.parse(result) : "";
const version = Array.isArray(versions) ? versions.sort(semver.rcompare)[0] : versions;
- if (!version) { throw new Error(`No version found for ${requested}`); }
- resolutionCache.set(requested, version);
- return version;
+ if (version) {
+ resolutionCache.set(requested, version);
+ return version;
+ }
} catch (e) {
throw new Error(`Error parsing versions for ${requested}`);
}
+
+ throw new Error(`No version found for ${requested}`);
}
}