-
Notifications
You must be signed in to change notification settings - Fork 573
Expand file tree
/
Copy pathdescribeCompat.ts
More file actions
337 lines (312 loc) · 11.6 KB
/
describeCompat.ts
File metadata and controls
337 lines (312 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/
import type { OdspTestDriver } from "@fluid-private/test-drivers";
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
import type { IPersistedCache } from "@fluidframework/driver-definitions/internal";
import { createChildLogger } from "@fluidframework/telemetry-utils/internal";
import {
getUnexpectedLogErrorException,
ITestObjectProvider,
} from "@fluidframework/test-utils/internal";
import { testBaseVersion } from "./baseVersion.js";
import {
CompatConfig,
configList,
isCompatVersionBelowMinVersion,
mochaGlobalSetup,
isOdspCompatCompliant,
} from "./compatConfig.js";
import {
CompatKind,
driver,
odspEndpointName,
r11sEndpointName,
tenantIndex,
} from "./compatOptions.js";
import {
getVersionedTestObjectProviderFromApis,
getCompatVersionedTestObjectProviderFromApis,
getDriverInformationWhenNoProviderIsAvailable,
} from "./compatUtils.js";
import {
getContainerRuntimeApi,
getDataRuntimeApi,
getLoaderApi,
CompatApis,
getDriverApi,
getCompatModeFromKind,
} from "./testApi.js";
import { getRequestedVersion } from "./versionUtils.js";
// See doc comment on mochaGlobalSetup.
await mochaGlobalSetup();
/*
* Mocha Utils for test to generate the compat variants.
*/
function createCompatSuite(
tests: (
this: Mocha.Suite,
provider: (options?: ITestObjectProviderOptions) => ITestObjectProvider,
apis: CompatApis,
) => void,
compatFilter?: CompatKind[],
minVersion?: string,
): (this: Mocha.Suite) => void {
return function (this: Mocha.Suite) {
let configs = configList.value;
if (compatFilter !== undefined) {
configs = configs.filter((value) => compatFilter.includes(value.kind));
}
for (const config of configs) {
if (minVersion && isCompatVersionBelowMinVersion(minVersion, config)) {
// skip current config if compat version is below min version supported for test suite
continue;
}
if (driver === "odsp" && !isOdspCompatCompliant(config)) {
continue;
}
describe(config.name, function () {
let provider: ITestObjectProvider | undefined;
let resetAfterEach: boolean;
const apis: CompatApis = getVersionedApis(config);
before("Create TestObjectProvider", async function () {
try {
provider =
config.kind === CompatKind.CrossClient
? await getCompatVersionedTestObjectProviderFromApis(apis, {
type: driver,
config: {
r11s: { r11sEndpointName },
odsp: { tenantIndex, odspEndpointName },
},
})
: await getVersionedTestObjectProviderFromApis(apis, {
type: driver,
config: {
r11s: { r11sEndpointName },
odsp: { tenantIndex, odspEndpointName },
},
});
} catch (error) {
const logger = createChildLogger({
logger: getTestLogger?.(),
namespace: "DescribeCompatSetup",
});
logger.sendErrorEvent(
{
// Note: TestObjectProvider already adds driverType and driverEndpointName to logs that go through it.
// In this code path we could not create the provider so we have to do things by hand.
...getDriverInformationWhenNoProviderIsAvailable(
driver,
odspEndpointName,
r11sEndpointName,
),
eventName: "TestObjectProviderLoadFailed",
},
error,
);
throw error;
}
Object.defineProperty(this, "__fluidTestProvider", {
get: () => provider,
configurable: true,
});
});
tests.bind(this)((options?: ITestObjectProviderOptions) => {
resetAfterEach = options?.resetAfterEach ?? true;
if (provider === undefined) {
throw new Error("Expected provider to be set up by before hook");
}
if (options?.syncSummarizer === true) {
provider.resetLoaderContainerTracker(true /* syncSummarizerClients */);
}
if (options?.persistedCache !== undefined && provider.driver.type === "odsp") {
(provider.driver as OdspTestDriver).setPersistedCache(options.persistedCache);
}
return provider;
}, apis);
afterEach("Verify container telemetry", function (done: Mocha.Done) {
if (provider === undefined) {
throw new Error("Expected provider to be set up by before hook");
}
const logErrors = getUnexpectedLogErrorException(provider.tracker);
// if the test failed for another reason
// then we don't need to check errors
// and fail the after each as well.
// This also avoids failing tests that are skipped from inside the test body, which is
// a pattern we use to only run tests on certain drivers.
if (this.currentTest?.state === "passed") {
done(logErrors);
} else {
done();
}
});
afterEach("Reset TestObjectProvider", () => {
if (provider === undefined) {
throw new Error("Expected provider to be set up by before hook");
}
if (resetAfterEach) {
provider.reset();
}
});
// Mocha contexts are long-lived, and leaking the testObjectProvider on them severely eats into
// memory over the course of our e2e tests. This is especially bad for local server, where the
// server ends up retaining direct references to containers. This hook resolves that issue by explicitly
// removing retainers for the test object provider from the context.
// A good way to test memory impact of changes here is by doing one of:
// - Put an existing e2e test's `it` block in a loop to create many copies of it and run only this test
// - Put a single test in a `describeCompat` block and put the `describeCompat` block in a loop
// then taking heap snapshots over the course of various runs.
// Because of things like the summarizer process, containers may not be GC'd as soon as tests are done executing,
// but you should see the total number of retained containers as well as server objects stabilize over time rather than grow.
// Heap snapshots for a large number of tests within a single suite help detect bugs with leaking objects while a suite executes,
// which is problematic for suites that run a large number of test cases (usually combintorially generated).
// Heap snapshots for a large number of suites help detect bugs with leaking objects across suites,
// which is problematic for issues that tend to get hit "later in the overall test run".
after("Cleanup TestObjectProvider", function () {
if (provider === undefined) {
throw new Error("Expected provider to be set up by before hook");
}
provider.driver.dispose?.();
provider = undefined;
Object.defineProperty(this, "__fluidTestProvider", {
get: () => {
throw new Error(
"Attempted to use __fluidTestProvider after test suite disposed.",
);
},
});
});
});
}
};
}
/**
* Get versioned APIs for the given config.
*/
function getVersionedApis(config: CompatConfig): CompatApis {
const mode = getCompatModeFromKind(config.kind);
// If this is cross-clients compat scenario, make sure we use the correct versions
if (config.kind === CompatKind.CrossClient) {
assert(
config.createVersion !== undefined,
"createVersion must be defined for cross-client tests",
);
assert(
config.loadVersion !== undefined,
"loadVersion must be defined for cross-client tests",
);
const dataRuntime = getDataRuntimeApi(config.createVersion);
const dataRuntimeForLoading = getDataRuntimeApi(config.loadVersion);
return {
mode,
containerRuntime: getContainerRuntimeApi(config.createVersion),
containerRuntimeForLoading: getContainerRuntimeApi(config.loadVersion),
dataRuntime,
dataRuntimeForLoading,
dds: dataRuntime.dds,
ddsForLoading: dataRuntimeForLoading.dds,
driver: getDriverApi(config.createVersion),
driverForLoading: getDriverApi(config.loadVersion),
loader: getLoaderApi(config.createVersion),
loaderForLoading: getLoaderApi(config.loadVersion),
};
}
const dataRuntimeApi = getDataRuntimeApi(
getRequestedVersion(testBaseVersion(config.dataRuntime), config.dataRuntime),
);
return {
mode,
containerRuntime: getContainerRuntimeApi(
getRequestedVersion(testBaseVersion(config.containerRuntime), config.containerRuntime),
),
dataRuntime: dataRuntimeApi,
dds: dataRuntimeApi.dds,
driver: getDriverApi(getRequestedVersion(testBaseVersion(config.driver), config.driver)),
loader: getLoaderApi(getRequestedVersion(testBaseVersion(config.loader), config.loader)),
};
}
/**
* @internal
*/
export interface ITestObjectProviderOptions {
/** If true, resets all state after each test completes. */
resetAfterEach?: boolean;
/** If true, synchronizes summarizer client as well when ensureSynchronized() is called. */
syncSummarizer?: boolean;
/** Persisted Cache provided by ODSP */
persistedCache?: IPersistedCache;
}
/**
* @internal
*/
export type DescribeCompatSuite = (
name: string,
compatVersion: CompatType,
tests: (
this: Mocha.Suite,
provider: (options?: ITestObjectProviderOptions) => ITestObjectProvider,
apis: CompatApis,
) => void,
) => Mocha.Suite | void;
/**
* @internal
*/
export type DescribeCompat = DescribeCompatSuite & {
/**
* Like Mocha's `describe.skip`, but for compat tests.
*/
skip: DescribeCompatSuite;
/**
* Like Mocha's `describe.only`, but for compat tests.
*/
only: DescribeCompatSuite;
/**
* Run the test suite ignoring the compatibility matrix. In other words, all Fluid layers will
* reference the current code version.
*
* This is meant as a debug utility for e2e tests: do not check in tests that use it as they won't have any
* compat coverage (attempting to do so will fail the PR gate anyway).
*/
noCompat: DescribeCompatSuite;
};
/** @internal */
export type CompatType = "FullCompat" | "LoaderCompat" | "NoCompat";
function createCompatDescribe(): DescribeCompat {
const createCompatSuiteWithDefault = (
tests: (this: Mocha.Suite, provider: () => ITestObjectProvider, apis: CompatApis) => void,
compatVersion: CompatType,
): ((this: Mocha.Suite) => void) => {
switch (compatVersion) {
case "FullCompat":
return createCompatSuite(tests, undefined);
case "LoaderCompat":
return createCompatSuite(tests, [CompatKind.None, CompatKind.Loader]);
case "NoCompat":
return createCompatSuite(tests, [CompatKind.None]);
default:
unreachableCase(compatVersion, "unknown compat version");
}
};
const d: DescribeCompat = (name: string, compatVersion: CompatType, tests) =>
describe(name, createCompatSuiteWithDefault(tests, compatVersion));
d.skip = (name, compatVersion: CompatType, tests) =>
describe.skip(name, createCompatSuiteWithDefault(tests, compatVersion));
d.only = (name, compatVersion: CompatType, tests) =>
describe.only(name, createCompatSuiteWithDefault(tests, compatVersion));
d.noCompat = (name, _, tests) =>
describe(name, createCompatSuiteWithDefault(tests, "NoCompat"));
return d;
}
/**
* `describeCompat` expects 3 arguments (name: string, compatVersion: string, tests).
* There are three compatVersion options to generate different combinations, depending of the need of the tests:
* `FullCompat`: generate test variants with compat combinations that varies the version for all layers.
* `LoaderCompat`: generate test variants with compat combinations that only varies the loader version.
* Specific version (String) : specify a minimum compat version (e.g. "2.0.0-rc.1.0.0") which will be the minimum version a
* test suite will test against. This should be equal to the value of pkgVersion at the time you're writing the new test suite.
*
* @internal
*/
export const describeCompat: DescribeCompat = createCompatDescribe();