-
Notifications
You must be signed in to change notification settings - Fork 536
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add test for legacy chunking (#12999)
Related to ADO:2465 There is a plan to refactor some of the op processing code inside the container runtime, along with the upcoming plan to use chunking to bypass the 1MB payload size limit for compressed batches. This implies few changes in the way we process incoming ops at the runtime layer and a resurrection of the code which produces chunked ops. But before that happens, we need to ensure that the new work is not breaking backwards compatibility wrt to processing legacy chunked ops, as these might already be serialized in older document snapshots (Excluding instances of really old clients collaborating on the same doc with new clients). This test will make sure that we don't break the basic scenario of processing chunked ops. Also adding a bit of test infra to allow for installing specific older versions of fluid.
- Loading branch information
Showing
5 changed files
with
286 additions
and
17 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
packages/test/test-end-to-end-tests/src/test/legacyChunking.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,107 @@ | ||
/*! | ||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { strict as assert } from "assert"; | ||
import { SharedMap } from "@fluidframework/map"; | ||
import { requestFluidObject } from "@fluidframework/runtime-utils"; | ||
import { | ||
ITestFluidObject, | ||
ChannelFactoryRegistry, | ||
ITestObjectProvider, | ||
ITestContainerConfig, | ||
DataObjectFactoryType, | ||
TestFluidObjectFactory, | ||
} from "@fluidframework/test-utils"; | ||
import { describeInstallVersions, getContainerRuntimeApi } from "@fluidframework/test-version-utils"; | ||
import { IContainer } from "@fluidframework/container-definitions"; | ||
import { FlushMode, IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; | ||
import { IRequest } from "@fluidframework/core-interfaces"; | ||
|
||
const versionWithChunking = "0.56.0"; | ||
|
||
describeInstallVersions( | ||
{ | ||
requestAbsoluteVersions: [versionWithChunking], | ||
} | ||
)( | ||
"Legacy chunking", | ||
(getTestObjectProvider) => { | ||
let provider: ITestObjectProvider; | ||
let oldMap: SharedMap; | ||
let newMap: SharedMap; | ||
beforeEach(() => { | ||
provider = getTestObjectProvider(); | ||
}); | ||
afterEach(async () => provider.reset()); | ||
|
||
const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
runtime.IFluidHandleContext.resolveHandle(request); | ||
const mapId = "map"; | ||
const registry: ChannelFactoryRegistry = [ | ||
[mapId, SharedMap.getFactory()], | ||
]; | ||
const factory: TestFluidObjectFactory = new TestFluidObjectFactory( | ||
registry, | ||
"default", | ||
); | ||
const testContainerConfig: ITestContainerConfig = { | ||
fluidDataObjectType: DataObjectFactoryType.Test, | ||
registry, | ||
}; | ||
|
||
const createOldContainer = async (): Promise<IContainer> => { | ||
const oldContainerRuntimeFactoryWithDefaultDataStore = | ||
getContainerRuntimeApi(versionWithChunking).ContainerRuntimeFactoryWithDefaultDataStore; | ||
const oldRuntimeFactory = | ||
new oldContainerRuntimeFactoryWithDefaultDataStore( | ||
factory, | ||
[ | ||
[factory.type, Promise.resolve(factory)], | ||
], | ||
undefined, | ||
[innerRequestHandler], | ||
{ | ||
// Chunking did not work with FlushMode.TurnBased, | ||
// as it was breaking batching semantics. So we need | ||
// to force the container to flush the ops as soon as | ||
// they are produced. | ||
flushMode: FlushMode.Immediate, | ||
gcOptions: { | ||
gcAllowed: true, | ||
}, | ||
}, | ||
); | ||
|
||
return provider.createContainer(oldRuntimeFactory); | ||
}; | ||
|
||
const setupContainers = async () => { | ||
const oldContainer = await createOldContainer(); | ||
const oldDataObject = await requestFluidObject<ITestFluidObject>(oldContainer, "default"); | ||
oldMap = await oldDataObject.getSharedObject<SharedMap>(mapId); | ||
|
||
const containerOnLatest = await provider.loadTestContainer(testContainerConfig); | ||
const newDataObject = await requestFluidObject<ITestFluidObject>(containerOnLatest, "default"); | ||
newMap = await newDataObject.getSharedObject<SharedMap>(mapId); | ||
|
||
await provider.ensureSynchronized(); | ||
}; | ||
|
||
const generateStringOfSize = (sizeInBytes: number): string => new Array(sizeInBytes + 1).join("0"); | ||
|
||
it("If an old container sends a large chunked op, a new container is able to process it successfully", async () => { | ||
await setupContainers(); | ||
// Ops larger than 16k will end up chunked in older versions of fluid | ||
const messageSizeInBytes = 300 * 1024; | ||
const value = generateStringOfSize(messageSizeInBytes); | ||
oldMap.set("key1", value); | ||
oldMap.set("key2", value); | ||
|
||
await provider.ensureSynchronized(); | ||
assert.strictEqual(newMap.get("key1"), value, "Wrong value found in the new map"); | ||
assert.strictEqual(newMap.get("key2"), value, "Wrong value found in the new map"); | ||
}); | ||
}); |
151 changes: 151 additions & 0 deletions
151
packages/test/test-version-utils/src/describeWithVersions.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,151 @@ | ||
/*! | ||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { getUnexpectedLogErrorException, ITestObjectProvider, TestObjectProvider } from "@fluidframework/test-utils"; | ||
import { driver, r11sEndpointName, tenantIndex } from "./compatOptions"; | ||
import { getVersionedTestObjectProvider } from "./compatUtils"; | ||
import { ITestObjectProviderOptions } from "./describeCompat"; | ||
import { pkgVersion } from "./packageVersion"; | ||
import { ensurePackageInstalled, InstalledPackage } from "./testApi"; | ||
|
||
/** | ||
* Interface to hold the requested versions which should be installed | ||
* prior to running the test suite. The properties are cumulative, as all | ||
* versions deduced from all properties will be installed. | ||
*/ | ||
export interface IRequestedFluidVersions { | ||
/** | ||
* Delta of versions to be installed with the current | ||
* package version as the baseline. | ||
*/ | ||
requestRelativeVersions?: number; | ||
/** | ||
* Array of specific versions to be installed | ||
*/ | ||
requestAbsoluteVersions?: string[]; | ||
} | ||
|
||
const installRequiredVersions = async (config: IRequestedFluidVersions) => { | ||
const installPromises: Promise<InstalledPackage | undefined>[] = []; | ||
if (config.requestAbsoluteVersions !== undefined) { | ||
installPromises.push( | ||
...config.requestAbsoluteVersions | ||
.map(async (version) => ensurePackageInstalled(version, 0, /* force */ false))); | ||
} | ||
|
||
if (config.requestRelativeVersions !== undefined) { | ||
installPromises.push(ensurePackageInstalled(pkgVersion, config.requestRelativeVersions, /* force */ false)); | ||
} | ||
|
||
let hadErrors = false; | ||
for (const promise of installPromises) { | ||
try { | ||
await promise; | ||
} catch (e) { | ||
console.error(e); | ||
hadErrors = true; | ||
} | ||
} | ||
|
||
if (hadErrors) { | ||
throw new Error("Exceptions while installing package versions. Check STDERR"); | ||
} | ||
}; | ||
|
||
const defaultTimeoutMs = 20000; | ||
const defaultRequestedVersions: IRequestedFluidVersions = { requestRelativeVersions: -2 }; | ||
|
||
function createTestSuiteWithInstalledVersion( | ||
tests: (this: Mocha.Suite, provider: () => ITestObjectProvider) => void, | ||
requiredVersions: IRequestedFluidVersions = defaultRequestedVersions, | ||
timeoutMs: number = defaultTimeoutMs, | ||
) { | ||
return function(this: Mocha.Suite) { | ||
let defaultProvider: TestObjectProvider; | ||
let resetAfterEach: boolean; | ||
before(async function() { | ||
this.timeout(Math.max(defaultTimeoutMs, timeoutMs)); | ||
|
||
await installRequiredVersions(requiredVersions); | ||
defaultProvider = await getVersionedTestObjectProvider( | ||
pkgVersion, // baseVersion | ||
pkgVersion, // loaderVersion | ||
{ | ||
type: driver, | ||
version: pkgVersion, | ||
config: { | ||
r11s: { r11sEndpointName }, | ||
odsp: { tenantIndex }, | ||
}, | ||
}, // driverConfig | ||
pkgVersion, // runtimeVersion | ||
pkgVersion, // dataRuntimeVersion | ||
); | ||
|
||
Object.defineProperty(this, "__fluidTestProvider", { get: () => defaultProvider }); | ||
}); | ||
|
||
tests.bind(this)((options?: ITestObjectProviderOptions) => { | ||
resetAfterEach = options?.resetAfterEach ?? true; | ||
if (options?.syncSummarizer === true) { | ||
defaultProvider.resetLoaderContainerTracker(true /* syncSummarizerClients */); | ||
} | ||
|
||
return defaultProvider; | ||
}); | ||
|
||
afterEach(function(done: Mocha.Done) { | ||
const logErrors = getUnexpectedLogErrorException(defaultProvider.logger); | ||
// if the test failed for another reason | ||
// then we don't need to check errors | ||
// and fail the after each as well | ||
if (this.currentTest?.state === "passed") { | ||
done(logErrors); | ||
} else { | ||
done(); | ||
} | ||
|
||
if (resetAfterEach) { | ||
defaultProvider.reset(); | ||
} | ||
}); | ||
}; | ||
} | ||
|
||
type DescribeSuiteWithVersions = | ||
(name: string, | ||
tests: ( | ||
this: Mocha.Suite, | ||
provider: (options?: ITestObjectProviderOptions) => ITestObjectProvider) => void | ||
) => Mocha.Suite | void; | ||
|
||
type DescribeWithVersions = DescribeSuiteWithVersions & Record<"skip" | "only", DescribeSuiteWithVersions>; | ||
|
||
/** | ||
* Creates a test suite which will priorly install a set of requested Fluid versions for the tests to use. | ||
* The packages are installed before any test code runs, so it is guaranteed that the package is present | ||
* when the test code is invoked, including the top level scope inside the `describeInstallVersions` block. | ||
* | ||
* If package installation fails for any of the requested versions, the test suite will not be created and | ||
* the test run will fail. | ||
* | ||
* @param requestedVersions - See {@link IRequestedFluidVersions}. | ||
* If unspecified, the test will install the last 2 versions. | ||
* @param timeoutMs - the timeout for the tests in milliseconds, as package installation is time consuming. | ||
* If unspecified, the timeout is 20000 ms. | ||
* @returns A mocha test suite | ||
*/ | ||
export function describeInstallVersions( | ||
requestedVersions?: IRequestedFluidVersions, | ||
timeoutMs?: number, | ||
): DescribeWithVersions { | ||
const d: DescribeWithVersions = | ||
(name, tests) => describe(name, createTestSuiteWithInstalledVersion(tests, requestedVersions, timeoutMs)); | ||
d.skip = | ||
(name, tests) => describe.skip(name, createTestSuiteWithInstalledVersion(tests, requestedVersions, timeoutMs)); | ||
d.only = | ||
(name, tests) => describe.only(name, createTestSuiteWithInstalledVersion(tests, requestedVersions, timeoutMs)); | ||
return d; | ||
} |
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
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
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