forked from microsoft/FluidFramework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Validate serializing a container after a failed attach (microsoft#20216)
Adds tests to ensure the correct behavior of serialize after a container fails an attach attempt and goes into the attaching state. The tests include validation that a data store and a DDS created while attaching can be dealt with on rehydrate. [AB#5917](https://dev.azure.com/fluidframework/235294da-091d-4c29-84fc-cdfc3d90890b/_workitems/edit/5917)
- Loading branch information
Showing
1 changed file
with
265 additions
and
0 deletions.
There are no files selected for viewing
265 changes: 265 additions & 0 deletions
265
packages/test/test-end-to-end-tests/src/test/serializeAfterFailedAttach.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
/*! | ||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { strict as assert } from "assert"; | ||
import { | ||
AttachState, | ||
IContainer, | ||
IFluidCodeDetails, | ||
IHostLoader, | ||
} from "@fluidframework/container-definitions"; | ||
import { Loader } from "@fluidframework/container-loader"; | ||
import { | ||
LocalCodeLoader, | ||
TestFluidObjectFactory, | ||
ITestFluidObject, | ||
TestFluidObject, | ||
createDocumentId, | ||
ITestObjectProvider, | ||
} from "@fluidframework/test-utils"; | ||
import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; | ||
import { IFluidHandle, IRequest } from "@fluidframework/core-interfaces"; | ||
import { describeCompat } from "@fluid-private/test-version-utils"; | ||
import { IDocumentServiceFactory } from "@fluidframework/driver-definitions"; | ||
import type { ISharedMap } from "@fluidframework/map"; | ||
import { wrapObjectAndOverride } from "../mocking.js"; | ||
|
||
describeCompat( | ||
`Serialize After Failure to Attach Container Test`, | ||
"NoCompat", | ||
(getTestObjectProvider, apis) => { | ||
const { SharedMap, SharedString } = apis.dds; | ||
|
||
const codeDetails: IFluidCodeDetails = { | ||
package: "detachedContainerTestPackage1", | ||
config: {}, | ||
}; | ||
const sharedStringId = "ss1Key"; | ||
const sharedMapId = "sm1Key"; | ||
async function createDetachedContainerAndGetEntryPoint( | ||
loader: IHostLoader, | ||
request: IRequest, | ||
) { | ||
const container: IContainer = await loader.createDetachedContainer(codeDetails); | ||
// Get the root dataStore from the detached container. | ||
const defaultDataStore = (await container.getEntryPoint()) as TestFluidObject; | ||
|
||
// Attempt to attach the container, then validate the attaching state | ||
await container.attach(request).then( | ||
() => assert.fail("should fail"), | ||
() => {}, | ||
); | ||
assert.equal( | ||
container.attachState, | ||
AttachState.Attaching, | ||
"Container should be attaching", | ||
); | ||
return { | ||
container, | ||
defaultDataStore, | ||
}; | ||
} | ||
|
||
function createTestLoader(provider: ITestObjectProvider): Loader { | ||
const factory: TestFluidObjectFactory = new TestFluidObjectFactory([ | ||
[sharedStringId, SharedString.getFactory()], | ||
[sharedMapId, SharedMap.getFactory()], | ||
]); | ||
const codeLoader = new LocalCodeLoader([[codeDetails, factory]], {}); | ||
const testLoader = new Loader({ | ||
urlResolver: provider.urlResolver, | ||
documentServiceFactory: wrapObjectAndOverride<IDocumentServiceFactory>( | ||
provider.documentServiceFactory, | ||
{ | ||
createContainer: () => assert.fail("fail on attach"), | ||
}, | ||
), | ||
codeLoader, | ||
logger: provider.logger, | ||
configProvider: { | ||
getRawConfig: (name) => | ||
name === "Fluid.Container.RetryOnAttachFailure" ? true : undefined, | ||
}, | ||
}); | ||
return testLoader; | ||
} | ||
|
||
const createPeerDataStore = async (containerRuntime: IContainerRuntimeBase) => { | ||
const dataStore = await containerRuntime.createDataStore(["default"]); | ||
const peerDataStore = (await dataStore.entryPoint.get()) as ITestFluidObject; | ||
return { | ||
peerDataStore, | ||
peerDataStoreRuntimeChannel: peerDataStore.channel, | ||
}; | ||
}; | ||
|
||
it("Can serialize detached container", async () => { | ||
const provider = getTestObjectProvider(); | ||
const documentId = createDocumentId(); | ||
const request = provider.driver.createCreateNewRequest(documentId); | ||
const loader = createTestLoader(provider); | ||
|
||
const { container } = await createDetachedContainerAndGetEntryPoint(loader, request); | ||
|
||
const snapshotTree = container.serialize(); | ||
|
||
const container2 = await loader.rehydrateDetachedContainerFromSnapshot(snapshotTree); | ||
|
||
// Check for default data store | ||
const entryPoint = await container2.getEntryPoint(); | ||
assert.notStrictEqual(entryPoint, undefined, "Component should exist!"); | ||
// Might need some other asserts, but not sure what | ||
}); | ||
it("Can serialize detached container with data stores after failed attach", async () => { | ||
const provider = getTestObjectProvider(); | ||
const documentId = createDocumentId(); | ||
const request = provider.driver.createCreateNewRequest(documentId); | ||
const loader = createTestLoader(provider); | ||
|
||
// create a detached container and attempt to attach | ||
const { container, defaultDataStore } = await createDetachedContainerAndGetEntryPoint( | ||
loader, | ||
request, | ||
); | ||
|
||
// create a new data store | ||
const peerDataStore = await createPeerDataStore( | ||
defaultDataStore.context.containerRuntime, | ||
); | ||
const dataStore2 = peerDataStore.peerDataStore as TestFluidObject; | ||
const rootOfDataStore1 = | ||
await defaultDataStore.getSharedObject<ISharedMap>(sharedMapId); | ||
const dataStore2Key = "dataStore2"; | ||
// attach a new data store | ||
rootOfDataStore1.set(dataStore2Key, dataStore2.handle); | ||
|
||
// serialize and rehydrate | ||
const snapshotTree = container.serialize(); | ||
const rehydratedContainer = | ||
await loader.rehydrateDetachedContainerFromSnapshot(snapshotTree); | ||
|
||
const rehydratedEntryPoint = | ||
(await rehydratedContainer.getEntryPoint()) as TestFluidObject; | ||
const rehydratedRootOfDataStore = | ||
await rehydratedEntryPoint.getSharedObject<ISharedMap>(sharedMapId); | ||
|
||
const dataStore2Handle: IFluidHandle<TestFluidObject> | undefined = | ||
rehydratedRootOfDataStore.get(dataStore2Key); | ||
|
||
// validate data store | ||
assert(dataStore2Handle !== undefined, `handle for [${dataStore2Key}] must exist`); | ||
const dataStore2FromRC = await dataStore2Handle.get(); | ||
assert(dataStore2FromRC, "DataStore2 should have been serialized properly"); | ||
assert.strictEqual( | ||
dataStore2FromRC.runtime.id, | ||
dataStore2.runtime.id, | ||
"DataStore2 id should match", | ||
); | ||
}); | ||
it("Can serialize detached container with DDS after failed attach", async () => { | ||
const provider = getTestObjectProvider(); | ||
const documentId = createDocumentId(); | ||
const request = provider.driver.createCreateNewRequest(documentId); | ||
const loader = createTestLoader(provider); | ||
|
||
// create a detached container and attempt to attach | ||
const { container, defaultDataStore } = await createDetachedContainerAndGetEntryPoint( | ||
loader, | ||
request, | ||
); | ||
|
||
// create a new dds | ||
const ddsId = "notbounddds"; | ||
const dds2 = defaultDataStore.runtime.createChannel( | ||
ddsId, | ||
SharedString.getFactory().type, | ||
); | ||
|
||
const rootOfDataStore1 = | ||
await defaultDataStore.getSharedObject<ISharedMap>(sharedMapId); | ||
const dds2Key = "dds2"; | ||
// attach a new dds | ||
rootOfDataStore1.set(dds2Key, dds2.handle); | ||
|
||
// serialize and rehydrate | ||
const snapshotTree = container.serialize(); | ||
const rehydratedContainer = | ||
await loader.rehydrateDetachedContainerFromSnapshot(snapshotTree); | ||
|
||
const rehydratedEntryPoint = | ||
(await rehydratedContainer.getEntryPoint()) as TestFluidObject; | ||
const rootOfDds2 = await rehydratedEntryPoint.getSharedObject<ISharedMap>(sharedMapId); | ||
const dds2Handle: IFluidHandle<ISharedMap> | undefined = rootOfDds2.get(dds2Key); | ||
|
||
// validate dds | ||
assert(dds2Handle !== undefined, `handle for [${dds2Key}] must exist`); | ||
const dds2FromRC = await dds2Handle.get(); | ||
assert(dds2FromRC, "DDS2 should have been serialized properly"); | ||
assert.strictEqual(dds2FromRC.id, ddsId, "DDS id should match"); | ||
assert.strictEqual(dds2FromRC.id, dds2.id, "Both DDS id should match"); | ||
}); | ||
|
||
it("Can serialize detached container with data store and DDS after failed attach", async () => { | ||
const provider = getTestObjectProvider(); | ||
const documentId = createDocumentId(); | ||
const request = provider.driver.createCreateNewRequest(documentId); | ||
const loader = createTestLoader(provider); | ||
|
||
// create a detached container and attempt to attach | ||
const { container, defaultDataStore } = await createDetachedContainerAndGetEntryPoint( | ||
loader, | ||
request, | ||
); | ||
|
||
// create a new data store | ||
const peerDataStore = await createPeerDataStore( | ||
defaultDataStore.context.containerRuntime, | ||
); | ||
const dataStore2 = peerDataStore.peerDataStore as TestFluidObject; | ||
|
||
// create a new dds | ||
const ddsId = "notbounddds"; | ||
const dds2 = dataStore2.runtime.createChannel(ddsId, SharedString.getFactory().type); | ||
|
||
// attach the new data store and dds | ||
const dds2Key = "dds2"; | ||
const dataStore2Key = "dataStore2"; | ||
const rootOfDataStore1 = | ||
await defaultDataStore.getSharedObject<ISharedMap>(sharedMapId); | ||
rootOfDataStore1.set(dataStore2Key, dataStore2.handle); | ||
rootOfDataStore1.set(dds2Key, dds2.handle); | ||
|
||
// serialize and rehydrate | ||
const snapshotTree = container.serialize(); | ||
const rehydratedContainer = | ||
await loader.rehydrateDetachedContainerFromSnapshot(snapshotTree); | ||
|
||
const rehydratedEntryPoint = | ||
(await rehydratedContainer.getEntryPoint()) as TestFluidObject; | ||
const rehydratedRoot = | ||
await rehydratedEntryPoint.getSharedObject<ISharedMap>(sharedMapId); | ||
const dataStore2Handle: IFluidHandle<TestFluidObject> | undefined = | ||
rehydratedRoot.get(dataStore2Key); | ||
const dds2Handle: IFluidHandle<ISharedMap> | undefined = rehydratedRoot.get(dds2Key); | ||
|
||
// validate data store | ||
assert(dataStore2Handle !== undefined, `handle for [${dataStore2Key}] must exist`); | ||
const dataStore2FromRC = await dataStore2Handle.get(); | ||
assert(dataStore2FromRC, "DataStore2 should have been serialized properly"); | ||
assert.strictEqual( | ||
dataStore2FromRC.runtime.id, | ||
dataStore2.runtime.id, | ||
"DataStore2 id should match", | ||
); | ||
|
||
// validate dds | ||
assert(dds2Handle !== undefined, `handle for [${dds2Key}] must exist`); | ||
const dds2FromRC = await dds2Handle.get(); | ||
assert(dds2FromRC, "DDS2 should have been serialized properly"); | ||
assert.strictEqual(dds2FromRC.id, ddsId, "DDS id should match"); | ||
assert.strictEqual(dds2FromRC.id, dds2.id, "Both DDS id should match"); | ||
}); | ||
}, | ||
); |