@fluid-experimental/attributor 2.0.0-rc.2.0.2 → 2.0.0-rc.3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/api-report/attributor.api.md +3 -3
- package/dist/attributor.d.ts +6 -2
- package/dist/attributor.d.ts.map +1 -1
- package/dist/attributor.js +6 -6
- package/dist/attributor.js.map +1 -1
- package/dist/encoders.d.ts +5 -1
- package/dist/encoders.d.ts.map +1 -1
- package/dist/encoders.js +5 -5
- package/dist/encoders.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lz4Encoder.d.ts +5 -1
- package/dist/lz4Encoder.d.ts.map +1 -1
- package/dist/lz4Encoder.js +3 -3
- package/dist/lz4Encoder.js.map +1 -1
- package/dist/mixinAttributor.d.ts +6 -2
- package/dist/mixinAttributor.d.ts.map +1 -1
- package/dist/mixinAttributor.js +21 -17
- package/dist/mixinAttributor.js.map +1 -1
- package/dist/stringInterner.js +2 -2
- package/dist/stringInterner.js.map +1 -1
- package/lib/attributor.d.ts +6 -2
- package/lib/attributor.d.ts.map +1 -1
- package/lib/attributor.js +2 -2
- package/lib/attributor.js.map +1 -1
- package/lib/encoders.d.ts +5 -1
- package/lib/encoders.d.ts.map +1 -1
- package/lib/encoders.js +1 -1
- package/lib/encoders.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/lz4Encoder.d.ts +5 -1
- package/lib/lz4Encoder.d.ts.map +1 -1
- package/lib/lz4Encoder.js +1 -1
- package/lib/lz4Encoder.js.map +1 -1
- package/lib/mixinAttributor.d.ts +6 -2
- package/lib/mixinAttributor.d.ts.map +1 -1
- package/lib/mixinAttributor.js +8 -4
- package/lib/mixinAttributor.js.map +1 -1
- package/lib/stringInterner.js +1 -1
- package/lib/stringInterner.js.map +1 -1
- package/package.json +31 -65
- package/src/attributor.ts +5 -4
- package/src/encoders.ts +4 -2
- package/src/index.ts +1 -0
- package/src/lz4Encoder.ts +4 -2
- package/src/mixinAttributor.ts +22 -18
- package/src/stringInterner.ts +1 -1
- package/api-extractor-cjs.json +0 -8
- package/dist/attributor-alpha.d.ts +0 -27
- package/dist/attributor-beta.d.ts +0 -31
- package/dist/attributor-public.d.ts +0 -31
- package/dist/attributor-untrimmed.d.ts +0 -127
- package/lib/attributor-alpha.d.ts +0 -27
- package/lib/attributor-beta.d.ts +0 -31
- package/lib/attributor-public.d.ts +0 -31
- package/lib/attributor-untrimmed.d.ts +0 -127
- package/lib/test/attribution/dirname.cjs +0 -16
- package/lib/test/attribution/dirname.cjs.map +0 -1
- package/lib/test/attribution/sharedString.attribution.spec.js +0 -398
- package/lib/test/attribution/sharedString.attribution.spec.js.map +0 -1
- package/lib/test/attributor.spec.js +0 -32
- package/lib/test/attributor.spec.js.map +0 -1
- package/lib/test/attributorSerializer.spec.js +0 -123
- package/lib/test/attributorSerializer.spec.js.map +0 -1
- package/lib/test/deltaEncoder.spec.js +0 -36
- package/lib/test/deltaEncoder.spec.js.map +0 -1
- package/lib/test/lz4Encoder.spec.js +0 -27
- package/lib/test/lz4Encoder.spec.js.map +0 -1
- package/lib/test/mixinAttributor.spec.js +0 -219
- package/lib/test/mixinAttributor.spec.js.map +0 -1
- package/lib/test/opStreamAttributor.spec.js +0 -43
- package/lib/test/opStreamAttributor.spec.js.map +0 -1
- package/lib/test/stringInterner.spec.js +0 -71
- package/lib/test/stringInterner.spec.js.map +0 -1
- package/lib/test/utils.js +0 -32
- package/lib/test/utils.js.map +0 -1
- /package/{dist → lib}/tsdoc-metadata.json +0 -0
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { strict as assert } from "node:assert";
|
|
8
|
-
import { chain, createWeightedGenerator, done, generatorFromArray, interleave, makeRandom, performFuzzActions, take, } from "@fluid-private/stochastic-test-utils";
|
|
9
|
-
import { MockFluidDataStoreRuntime, MockStorage, MockContainerRuntimeFactoryForReconnection, } from "@fluidframework/test-runtime-utils";
|
|
10
|
-
import { SummaryType } from "@fluidframework/protocol-definitions";
|
|
11
|
-
import { SharedString, SharedStringFactory } from "@fluidframework/sequence";
|
|
12
|
-
import { createInsertOnlyAttributionPolicy } from "@fluidframework/merge-tree";
|
|
13
|
-
import { OpStreamAttributor } from "../../attributor.js";
|
|
14
|
-
import { AttributorSerializer, chain as chainEncoders, deltaEncoder, } from "../../encoders.js";
|
|
15
|
-
import { makeLZ4Encoder } from "../../lz4Encoder.js";
|
|
16
|
-
import { _dirname } from "./dirname.cjs";
|
|
17
|
-
function makeMockAudience(clientIds) {
|
|
18
|
-
const clients = new Map();
|
|
19
|
-
for (const [index, clientId] of clientIds.entries()) {
|
|
20
|
-
// eslint-disable-next-line unicorn/prefer-code-point
|
|
21
|
-
const stringId = String.fromCharCode(index + 65);
|
|
22
|
-
const name = stringId.repeat(10);
|
|
23
|
-
const userId = `${name}@microsoft.com`;
|
|
24
|
-
const email = userId;
|
|
25
|
-
const user = {
|
|
26
|
-
id: userId,
|
|
27
|
-
name,
|
|
28
|
-
email,
|
|
29
|
-
};
|
|
30
|
-
clients.set(clientId, {
|
|
31
|
-
mode: "write",
|
|
32
|
-
details: { capabilities: { interactive: true } },
|
|
33
|
-
permission: [],
|
|
34
|
-
user,
|
|
35
|
-
scopes: [],
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
39
|
-
return {
|
|
40
|
-
getMember: (clientId) => {
|
|
41
|
-
return clients.get(clientId);
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const defaultOptions = {
|
|
46
|
-
maxStringLength: 1000,
|
|
47
|
-
maxIntervals: 100,
|
|
48
|
-
maxInsertLength: 10,
|
|
49
|
-
propertyNamePool: ["prop1", "prop2", "prop3"],
|
|
50
|
-
validateInterval: 100,
|
|
51
|
-
};
|
|
52
|
-
function makeOperationGenerator(optionsParam) {
|
|
53
|
-
const options = { ...defaultOptions, ...optionsParam };
|
|
54
|
-
// All subsequent helper functions are generators; note that they don't actually apply any operations.
|
|
55
|
-
function startPosition({ random, sharedString }) {
|
|
56
|
-
return random.integer(0, Math.max(0, sharedString.getLength() - 1));
|
|
57
|
-
}
|
|
58
|
-
function exclusiveRange(state) {
|
|
59
|
-
const start = startPosition(state);
|
|
60
|
-
const end = state.random.integer(start + 1, state.sharedString.getLength());
|
|
61
|
-
return { start, end };
|
|
62
|
-
}
|
|
63
|
-
function propertySet(state) {
|
|
64
|
-
const propNamesShuffled = [...options.propertyNamePool];
|
|
65
|
-
state.random.shuffle(propNamesShuffled);
|
|
66
|
-
const propsToChange = propNamesShuffled.slice(0, state.random.integer(1, propNamesShuffled.length));
|
|
67
|
-
const propSet = {};
|
|
68
|
-
for (const name of propsToChange) {
|
|
69
|
-
propSet[name] = state.random.string(5);
|
|
70
|
-
}
|
|
71
|
-
return propSet;
|
|
72
|
-
}
|
|
73
|
-
function addText(state) {
|
|
74
|
-
const { random, sharedString } = state;
|
|
75
|
-
return {
|
|
76
|
-
type: "addText",
|
|
77
|
-
index: random.integer(0, sharedString.getLength()),
|
|
78
|
-
content: random.string(random.integer(0, options.maxInsertLength)),
|
|
79
|
-
stringId: sharedString.id,
|
|
80
|
-
props: random.bool(0.1) ? propertySet(state) : undefined,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function removeRange(state) {
|
|
84
|
-
return { type: "removeRange", ...exclusiveRange(state), stringId: state.sharedString.id };
|
|
85
|
-
}
|
|
86
|
-
function annotateRange(state) {
|
|
87
|
-
return {
|
|
88
|
-
type: "annotateRange",
|
|
89
|
-
...exclusiveRange(state),
|
|
90
|
-
properties: propertySet(state),
|
|
91
|
-
stringId: state.sharedString.id,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
const lengthSatisfies = (criteria) => ({ sharedString }) => criteria(sharedString.getLength());
|
|
95
|
-
const hasNonzeroLength = lengthSatisfies((length) => length > 0);
|
|
96
|
-
const isShorterThanMaxLength = lengthSatisfies((length) => length < options.maxStringLength);
|
|
97
|
-
const clientBaseOperationGenerator = createWeightedGenerator([
|
|
98
|
-
[addText, 6, isShorterThanMaxLength],
|
|
99
|
-
[removeRange, 2, hasNonzeroLength],
|
|
100
|
-
[annotateRange, 1, hasNonzeroLength],
|
|
101
|
-
]);
|
|
102
|
-
const clientOperationGenerator = (state) => clientBaseOperationGenerator({
|
|
103
|
-
...state,
|
|
104
|
-
sharedString: state.random.pick(state.clients).sharedString,
|
|
105
|
-
});
|
|
106
|
-
return interleave(clientOperationGenerator, () => ({ type: "synchronize" }), options.validateInterval);
|
|
107
|
-
}
|
|
108
|
-
function createSharedString(random, generator, makeSerializer) {
|
|
109
|
-
const numClients = 3;
|
|
110
|
-
const clientIds = Array.from({ length: numClients }, () => random.uuid4());
|
|
111
|
-
const audience = makeMockAudience(clientIds);
|
|
112
|
-
const containerRuntimeFactory = new MockContainerRuntimeFactoryForReconnection();
|
|
113
|
-
let attributor;
|
|
114
|
-
let serializer;
|
|
115
|
-
const initialState = {
|
|
116
|
-
clients: clientIds.map((clientId, index) => {
|
|
117
|
-
const dataStoreRuntime = new MockFluidDataStoreRuntime({ clientId });
|
|
118
|
-
dataStoreRuntime.options = {
|
|
119
|
-
attribution: {
|
|
120
|
-
track: makeSerializer !== undefined,
|
|
121
|
-
policyFactory: createInsertOnlyAttributionPolicy,
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
const { deltaManager } = dataStoreRuntime;
|
|
125
|
-
const sharedString = new SharedString(dataStoreRuntime,
|
|
126
|
-
// eslint-disable-next-line unicorn/prefer-code-point
|
|
127
|
-
String.fromCharCode(index + 65), SharedStringFactory.Attributes);
|
|
128
|
-
if (index === 0 && makeSerializer !== undefined) {
|
|
129
|
-
attributor = new OpStreamAttributor(deltaManager, audience);
|
|
130
|
-
serializer = makeSerializer(dataStoreRuntime);
|
|
131
|
-
// DeltaManager mock doesn't have high fidelity but attribution requires DataStoreRuntime implements
|
|
132
|
-
// audience / op emission.
|
|
133
|
-
let opIndex = 0;
|
|
134
|
-
sharedString.on("op", (message) => {
|
|
135
|
-
opIndex++;
|
|
136
|
-
message.timestamp = getTimestamp(opIndex);
|
|
137
|
-
deltaManager.emit("op", message);
|
|
138
|
-
});
|
|
139
|
-
dataStoreRuntime.getAudience = () => audience;
|
|
140
|
-
}
|
|
141
|
-
const containerRuntime = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime);
|
|
142
|
-
const services = {
|
|
143
|
-
deltaConnection: dataStoreRuntime.createDeltaConnection(),
|
|
144
|
-
objectStorage: new MockStorage(),
|
|
145
|
-
};
|
|
146
|
-
sharedString.initializeLocal();
|
|
147
|
-
sharedString.connect(services);
|
|
148
|
-
return { containerRuntime, sharedString };
|
|
149
|
-
}),
|
|
150
|
-
containerRuntimeFactory,
|
|
151
|
-
random,
|
|
152
|
-
};
|
|
153
|
-
initialState.attributor = attributor;
|
|
154
|
-
initialState.serializer = serializer;
|
|
155
|
-
// Small wrapper to avoid having to return the same state repeatedly; all operations in this suite mutate.
|
|
156
|
-
// Also a reasonable point to inject logging of incremental state.
|
|
157
|
-
const statefully = (statefulReducer) => (state, operation) => {
|
|
158
|
-
statefulReducer(state, operation);
|
|
159
|
-
return state;
|
|
160
|
-
};
|
|
161
|
-
return performFuzzActions(generator, {
|
|
162
|
-
addText: statefully(({ clients }, { stringId, index, content, props }) => {
|
|
163
|
-
const { sharedString } = clients.find((c) => c.sharedString.id === stringId) ?? {};
|
|
164
|
-
assert(sharedString);
|
|
165
|
-
sharedString.insertText(index, content, props);
|
|
166
|
-
}),
|
|
167
|
-
removeRange: statefully(({ clients }, { stringId, start, end }) => {
|
|
168
|
-
const { sharedString } = clients.find((c) => c.sharedString.id === stringId) ?? {};
|
|
169
|
-
assert(sharedString);
|
|
170
|
-
sharedString.removeRange(start, end);
|
|
171
|
-
}),
|
|
172
|
-
annotateRange: statefully(({ clients }, { stringId, properties, start, end }) => {
|
|
173
|
-
const { sharedString } = clients.find((c) => c.sharedString.id === stringId) ?? {};
|
|
174
|
-
assert(sharedString);
|
|
175
|
-
sharedString.annotateRange(start, end, properties);
|
|
176
|
-
}),
|
|
177
|
-
synchronize: statefully((state) => {
|
|
178
|
-
state.containerRuntimeFactory.processAllMessages();
|
|
179
|
-
}),
|
|
180
|
-
}, initialState);
|
|
181
|
-
}
|
|
182
|
-
const directory = path.join(_dirname, "../../../src/test/attribution/documents");
|
|
183
|
-
function getDocumentPaths(docName) {
|
|
184
|
-
mkdirSync(path.join(directory, docName), { recursive: true });
|
|
185
|
-
return {
|
|
186
|
-
directory: path.join(directory, docName),
|
|
187
|
-
operations: path.join(directory, docName, "operations.json"),
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
function getDocuments() {
|
|
191
|
-
return readdirSync(directory).filter((name) => name !== "README.md");
|
|
192
|
-
}
|
|
193
|
-
// Format a number separating 3 digits by comma
|
|
194
|
-
const formatNumber = (num) => num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
195
|
-
function spyOnOperations(baseGenerator) {
|
|
196
|
-
const operations = [];
|
|
197
|
-
const generator = (state) => {
|
|
198
|
-
const operation = baseGenerator(state);
|
|
199
|
-
if (operation !== done) {
|
|
200
|
-
operations.push(operation);
|
|
201
|
-
}
|
|
202
|
-
return operation;
|
|
203
|
-
};
|
|
204
|
-
return { operations, generator };
|
|
205
|
-
}
|
|
206
|
-
function readJson(filepath) {
|
|
207
|
-
return JSON.parse(readFileSync(filepath, { encoding: "utf8" }));
|
|
208
|
-
}
|
|
209
|
-
function writeJson(filepath, content) {
|
|
210
|
-
writeFileSync(filepath, JSON.stringify(content, undefined, 4), { encoding: "utf8" });
|
|
211
|
-
}
|
|
212
|
-
const validateInterval = 10;
|
|
213
|
-
function getTimestamp(opIndex) {
|
|
214
|
-
// This is an arbitrary start point of the same magnitude as current data.
|
|
215
|
-
// Wed Oct 12 2022 11:40:00 GMT-0700
|
|
216
|
-
const baseDate = 1665600000000;
|
|
217
|
-
return baseDate + Math.floor(opIndex / 10) * 5000;
|
|
218
|
-
}
|
|
219
|
-
function embedAttributionInProps(operations) {
|
|
220
|
-
return operations.map((operation, index) => {
|
|
221
|
-
if (operation.type === "addText") {
|
|
222
|
-
const name = operation.stringId.repeat(10);
|
|
223
|
-
const id = `${name}@contoso.com`;
|
|
224
|
-
const email = id;
|
|
225
|
-
const props = {
|
|
226
|
-
attribution: {
|
|
227
|
-
id,
|
|
228
|
-
name,
|
|
229
|
-
email,
|
|
230
|
-
timestamp: getTimestamp(index),
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
return {
|
|
234
|
-
...operation,
|
|
235
|
-
props,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
return operation;
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Validates that summary tree does not have a blob with a Uint8Array as content.
|
|
245
|
-
*
|
|
246
|
-
* @param summary - Summary tree to validate
|
|
247
|
-
*/
|
|
248
|
-
function assertSerializableSummary(summary) {
|
|
249
|
-
for (const value of Object.values(summary.tree)) {
|
|
250
|
-
switch (value.type) {
|
|
251
|
-
case SummaryType.Tree: {
|
|
252
|
-
assertSerializableSummary(value);
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
case SummaryType.Blob: {
|
|
256
|
-
assert(typeof value.content === "string");
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
default: {
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
const summaryFromState = async (state) => {
|
|
266
|
-
state.containerRuntimeFactory.processAllMessages();
|
|
267
|
-
const { sharedString } = state.clients[0];
|
|
268
|
-
const { summary } = await sharedString.summarize();
|
|
269
|
-
// KLUDGE: For now, since attribution info isn't embedded at a proper location in the summary tree, just
|
|
270
|
-
// add a property to the root so that its size is reported
|
|
271
|
-
if (state.attributor && state.serializer) {
|
|
272
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
|
|
273
|
-
summary.attribution = state.serializer.encode(state.attributor);
|
|
274
|
-
}
|
|
275
|
-
assertSerializableSummary(summary);
|
|
276
|
-
return summary;
|
|
277
|
-
};
|
|
278
|
-
const noopEncoder = {
|
|
279
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
280
|
-
encode: (x) => x,
|
|
281
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
282
|
-
decode: (x) => x,
|
|
283
|
-
};
|
|
284
|
-
class DataTable {
|
|
285
|
-
constructor(columnNames) {
|
|
286
|
-
this.columnNames = columnNames;
|
|
287
|
-
this.rows = new Map();
|
|
288
|
-
}
|
|
289
|
-
addRow(name, data) {
|
|
290
|
-
this.rows.set(name, data);
|
|
291
|
-
}
|
|
292
|
-
log(dataToString = (t) => `${t}`) {
|
|
293
|
-
const namePaddingLength = 1 + Math.max(...Array.from(this.rows.keys(), (docName) => docName.length));
|
|
294
|
-
const rowStrings = new Map();
|
|
295
|
-
const paddingByColumn = this.columnNames.map((name) => name.length);
|
|
296
|
-
for (const [name, data] of this.rows.entries()) {
|
|
297
|
-
const dataStrings = data.map((entry) => dataToString(entry));
|
|
298
|
-
rowStrings.set(name, dataStrings);
|
|
299
|
-
for (const [i, s] of dataStrings.entries()) {
|
|
300
|
-
paddingByColumn[i] = Math.max(paddingByColumn[i], s.length);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
for (const [i, _] of paddingByColumn.entries()) {
|
|
304
|
-
paddingByColumn[i]++;
|
|
305
|
-
}
|
|
306
|
-
console.log([
|
|
307
|
-
`${"Name".padEnd(namePaddingLength)}`,
|
|
308
|
-
...this.columnNames.map((name, i) => `${name.padStart(paddingByColumn[i])} `),
|
|
309
|
-
].join("|"));
|
|
310
|
-
for (const [name, result] of rowStrings.entries()) {
|
|
311
|
-
console.log(`${name.padEnd(namePaddingLength)}|${result
|
|
312
|
-
.map((s, i) => `${s.padStart(paddingByColumn[i])} `)
|
|
313
|
-
.join("|")}`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
const getSummaryLength = (summary) => formatNumber(JSON.stringify(summary).length);
|
|
318
|
-
describe("SharedString Attribution", () => {
|
|
319
|
-
/**
|
|
320
|
-
* This test suite is aimed at assessing the overhead of storing attribution information in a document.
|
|
321
|
-
* See 'documents/README.md' for more details.
|
|
322
|
-
*/
|
|
323
|
-
describe("using randomly generated documents", () => {
|
|
324
|
-
// Entries of this list produce documents which should contain the same attribution information but
|
|
325
|
-
// stored in different formats. The "None" factory is an exception in that it contains no attribution
|
|
326
|
-
// information, and is useful as a baseline for comparison.
|
|
327
|
-
const dataGenerators = [
|
|
328
|
-
{
|
|
329
|
-
name: "None",
|
|
330
|
-
factory: (operations) => createSharedString(makeRandom(0), generatorFromArray(operations)),
|
|
331
|
-
filename: "no-attribution-snap.json",
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
name: "Prop",
|
|
335
|
-
factory: (operations) => createSharedString(makeRandom(0), generatorFromArray(embedAttributionInProps(operations))),
|
|
336
|
-
filename: "prop-attribution-snap.json",
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
name: "OpStreamAttributor without any compression",
|
|
340
|
-
factory: (operations) => createSharedString(makeRandom(0), generatorFromArray(operations), (runtime) => chainEncoders(new AttributorSerializer((entries) => new OpStreamAttributor(runtime.deltaManager, runtime.getAudience(), entries), noopEncoder), noopEncoder)),
|
|
341
|
-
filename: "attributor-no-compression-snap.json",
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
name: "OpStreamAttributor without delta encoding",
|
|
345
|
-
factory: (operations) => createSharedString(makeRandom(0), generatorFromArray(operations), (runtime) => chainEncoders(new AttributorSerializer((entries) => new OpStreamAttributor(runtime.deltaManager, runtime.getAudience(), entries), noopEncoder), makeLZ4Encoder())),
|
|
346
|
-
filename: "attributor-lz4-compression-snap.json",
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
name: "OpStreamAttributor",
|
|
350
|
-
factory: (operations) => createSharedString(makeRandom(0), generatorFromArray(operations), (runtime) => chainEncoders(new AttributorSerializer((entries) => new OpStreamAttributor(runtime.deltaManager, runtime.getAudience(), entries), deltaEncoder), makeLZ4Encoder())),
|
|
351
|
-
filename: "attributor-lz4-and-delta-snap.json",
|
|
352
|
-
},
|
|
353
|
-
];
|
|
354
|
-
it.skip("Generate a new document", async () => {
|
|
355
|
-
const paths = getDocumentPaths("default");
|
|
356
|
-
const attributionlessGenerator = chain(take(100, makeOperationGenerator({ validateInterval })), generatorFromArray([{ type: "synchronize" }]));
|
|
357
|
-
const { generator, operations } = spyOnOperations(attributionlessGenerator);
|
|
358
|
-
createSharedString(makeRandom(0), generator);
|
|
359
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
|
360
|
-
writeJson(paths.operations, operations);
|
|
361
|
-
await Promise.all(dataGenerators.map(async ({ filename, factory }) => {
|
|
362
|
-
const summary = await summaryFromState(factory(operations));
|
|
363
|
-
writeJson(path.join(paths.directory, filename), summary);
|
|
364
|
-
}));
|
|
365
|
-
});
|
|
366
|
-
const documents = getDocuments();
|
|
367
|
-
for (const document of documents) {
|
|
368
|
-
describe(`document name: ${document} has an up-to-date`, () => {
|
|
369
|
-
let paths;
|
|
370
|
-
let operations;
|
|
371
|
-
before(() => {
|
|
372
|
-
paths = getDocumentPaths(document);
|
|
373
|
-
operations = readJson(paths.operations);
|
|
374
|
-
});
|
|
375
|
-
for (const { filename, factory } of dataGenerators) {
|
|
376
|
-
it(`snapshot at ${filename}`, async () => {
|
|
377
|
-
const expected = readJson(path.join(paths.directory, filename));
|
|
378
|
-
const actual = await summaryFromState(factory(operations));
|
|
379
|
-
assert.deepEqual(actual, expected);
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
// Note: to see output, FLUID_TEST_VERBOSE needs to be enabled. Using the `test:mocha:verbose` script is
|
|
385
|
-
// sufficient to do so.
|
|
386
|
-
it("generate snapshot size impact report", async () => {
|
|
387
|
-
const table = new DataTable(dataGenerators.map(({ name }) => name));
|
|
388
|
-
for (const docName of documents) {
|
|
389
|
-
const paths = getDocumentPaths(docName);
|
|
390
|
-
const operations = readJson(paths.operations);
|
|
391
|
-
const data = await Promise.all(dataGenerators.map(async ({ factory }) => summaryFromState(factory(operations))));
|
|
392
|
-
table.addRow(docName, data);
|
|
393
|
-
}
|
|
394
|
-
table.log(getSummaryLength);
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
});
|
|
398
|
-
//# sourceMappingURL=sharedString.attribution.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sharedString.attribution.spec.js","sourceRoot":"","sources":["../../../src/test/attribution/sharedString.attribution.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport * as path from \"node:path\";\nimport { mkdirSync, readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { strict as assert } from \"node:assert\";\nimport {\n\ttype AcceptanceCondition,\n\ttype BaseFuzzTestState,\n\tchain,\n\tcreateWeightedGenerator,\n\tdone,\n\ttype Generator,\n\tgeneratorFromArray,\n\tinterleave,\n\ttype IRandom,\n\tmakeRandom,\n\tperformFuzzActions,\n\ttype Reducer,\n\ttake,\n} from \"@fluid-private/stochastic-test-utils\";\nimport {\n\tMockFluidDataStoreRuntime,\n\tMockStorage,\n\tMockContainerRuntimeFactoryForReconnection,\n\ttype MockContainerRuntimeForReconnection,\n} from \"@fluidframework/test-runtime-utils\";\nimport {\n\ttype IChannelServices,\n\ttype IFluidDataStoreRuntime,\n\ttype Jsonable,\n} from \"@fluidframework/datastore-definitions\";\nimport { type IClient, type ISummaryTree, SummaryType } from \"@fluidframework/protocol-definitions\";\nimport { type IAudience } from \"@fluidframework/container-definitions\";\nimport { SharedString, SharedStringFactory } from \"@fluidframework/sequence\";\nimport { createInsertOnlyAttributionPolicy } from \"@fluidframework/merge-tree\";\nimport { type IAttributor, OpStreamAttributor } from \"../../attributor.js\";\nimport {\n\tAttributorSerializer,\n\tchain as chainEncoders,\n\tdeltaEncoder,\n\ttype Encoder,\n} from \"../../encoders.js\";\nimport { makeLZ4Encoder } from \"../../lz4Encoder.js\";\nimport { _dirname } from \"./dirname.cjs\";\n\nfunction makeMockAudience(clientIds: string[]): IAudience {\n\tconst clients = new Map<string, IClient>();\n\tfor (const [index, clientId] of clientIds.entries()) {\n\t\t// eslint-disable-next-line unicorn/prefer-code-point\n\t\tconst stringId = String.fromCharCode(index + 65);\n\t\tconst name = stringId.repeat(10);\n\t\tconst userId = `${name}@microsoft.com`;\n\t\tconst email = userId;\n\t\tconst user = {\n\t\t\tid: userId,\n\t\t\tname,\n\t\t\temail,\n\t\t};\n\t\tclients.set(clientId, {\n\t\t\tmode: \"write\",\n\t\t\tdetails: { capabilities: { interactive: true } },\n\t\t\tpermission: [],\n\t\t\tuser,\n\t\t\tscopes: [],\n\t\t});\n\t}\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\treturn {\n\t\tgetMember: (clientId: string): IClient | undefined => {\n\t\t\treturn clients.get(clientId);\n\t\t},\n\t} as IAudience;\n}\n\ntype PropertySet = Record<string, unknown>;\n\ninterface Client {\n\tsharedString: SharedString;\n\tcontainerRuntime: MockContainerRuntimeForReconnection;\n}\n\ninterface FuzzTestState extends BaseFuzzTestState {\n\tcontainerRuntimeFactory: MockContainerRuntimeFactoryForReconnection;\n\tclients: Client[];\n\t// Note: eventually each client will have an Attributor. While design/implementation is in flux, this test suite\n\t// just stores a singleton attributor for the purposes of assessing its size.\n\tattributor?: IAttributor;\n\tserializer?: Encoder<IAttributor, string>;\n}\n\ninterface ClientSpec {\n\tstringId: string;\n}\n\ninterface RangeSpec {\n\tstart: number;\n\tend: number;\n}\n\ninterface AddText extends ClientSpec {\n\ttype: \"addText\";\n\tindex: number;\n\tcontent: string;\n\tprops?: PropertySet;\n}\n\ninterface RemoveRange extends ClientSpec, RangeSpec {\n\ttype: \"removeRange\";\n}\n\ninterface AnnotateRange extends ClientSpec, RangeSpec {\n\ttype: \"annotateRange\";\n\tproperties: PropertySet;\n}\n\ninterface Synchronize {\n\ttype: \"synchronize\";\n}\n\ntype TextOperation = AddText | RemoveRange | AnnotateRange;\n\ntype Operation = TextOperation | Synchronize;\n\ninterface OperationGenerationConfig {\n\t/**\n\t * Maximum length of the SharedString (locally) before no further AddText operations are generated.\n\t * Note due to concurrency, during test execution the actual length of the string may exceed this.\n\t */\n\tmaxStringLength?: number;\n\t/**\n\t * Maximum number of intervals (locally) before no further AddInterval operations are generated.\n\t * Note due to concurrency, during test execution the actual number of intervals may exceed this.\n\t */\n\tmaxIntervals?: number;\n\tmaxInsertLength?: number;\n\tpropertyNamePool?: string[];\n\tvalidateInterval?: number;\n}\n\nconst defaultOptions: Required<OperationGenerationConfig> = {\n\tmaxStringLength: 1000,\n\tmaxIntervals: 100,\n\tmaxInsertLength: 10,\n\tpropertyNamePool: [\"prop1\", \"prop2\", \"prop3\"],\n\tvalidateInterval: 100,\n};\n\nfunction makeOperationGenerator(\n\toptionsParam?: OperationGenerationConfig,\n): Generator<Operation, FuzzTestState> {\n\tconst options = { ...defaultOptions, ...optionsParam };\n\ttype ClientOpState = FuzzTestState & { sharedString: SharedString };\n\n\t// All subsequent helper functions are generators; note that they don't actually apply any operations.\n\tfunction startPosition({ random, sharedString }: ClientOpState): number {\n\t\treturn random.integer(0, Math.max(0, sharedString.getLength() - 1));\n\t}\n\n\tfunction exclusiveRange(state: ClientOpState): RangeSpec {\n\t\tconst start = startPosition(state);\n\t\tconst end = state.random.integer(start + 1, state.sharedString.getLength());\n\t\treturn { start, end };\n\t}\n\n\tfunction propertySet(state: ClientOpState): PropertySet {\n\t\tconst propNamesShuffled = [...options.propertyNamePool];\n\t\tstate.random.shuffle(propNamesShuffled);\n\t\tconst propsToChange = propNamesShuffled.slice(\n\t\t\t0,\n\t\t\tstate.random.integer(1, propNamesShuffled.length),\n\t\t);\n\t\tconst propSet: PropertySet = {};\n\t\tfor (const name of propsToChange) {\n\t\t\tpropSet[name] = state.random.string(5);\n\t\t}\n\t\treturn propSet;\n\t}\n\n\tfunction addText(state: ClientOpState): AddText {\n\t\tconst { random, sharedString } = state;\n\t\treturn {\n\t\t\ttype: \"addText\",\n\t\t\tindex: random.integer(0, sharedString.getLength()),\n\t\t\tcontent: random.string(random.integer(0, options.maxInsertLength)),\n\t\t\tstringId: sharedString.id,\n\t\t\tprops: random.bool(0.1) ? propertySet(state) : undefined,\n\t\t};\n\t}\n\n\tfunction removeRange(state: ClientOpState): RemoveRange {\n\t\treturn { type: \"removeRange\", ...exclusiveRange(state), stringId: state.sharedString.id };\n\t}\n\n\tfunction annotateRange(state: ClientOpState): AnnotateRange {\n\t\treturn {\n\t\t\ttype: \"annotateRange\",\n\t\t\t...exclusiveRange(state),\n\t\t\tproperties: propertySet(state),\n\t\t\tstringId: state.sharedString.id,\n\t\t};\n\t}\n\n\tconst lengthSatisfies =\n\t\t(criteria: (length: number) => boolean): AcceptanceCondition<ClientOpState> =>\n\t\t({ sharedString }) =>\n\t\t\tcriteria(sharedString.getLength());\n\tconst hasNonzeroLength = lengthSatisfies((length) => length > 0);\n\tconst isShorterThanMaxLength = lengthSatisfies((length) => length < options.maxStringLength);\n\n\tconst clientBaseOperationGenerator = createWeightedGenerator<Operation, ClientOpState>([\n\t\t[addText, 6, isShorterThanMaxLength],\n\t\t[removeRange, 2, hasNonzeroLength],\n\t\t[annotateRange, 1, hasNonzeroLength],\n\t]);\n\n\tconst clientOperationGenerator = (state: FuzzTestState): Operation | typeof done =>\n\t\tclientBaseOperationGenerator({\n\t\t\t...state,\n\t\t\tsharedString: state.random.pick(state.clients).sharedString,\n\t\t});\n\n\treturn interleave(\n\t\tclientOperationGenerator,\n\t\t() => ({ type: \"synchronize\" }),\n\t\toptions.validateInterval,\n\t);\n}\n\nfunction createSharedString(\n\trandom: IRandom,\n\tgenerator: Generator<Operation, FuzzTestState>,\n\tmakeSerializer?: (runtime: IFluidDataStoreRuntime) => Encoder<IAttributor, string>,\n): FuzzTestState {\n\tconst numClients = 3;\n\tconst clientIds = Array.from({ length: numClients }, () => random.uuid4());\n\tconst audience = makeMockAudience(clientIds);\n\tconst containerRuntimeFactory = new MockContainerRuntimeFactoryForReconnection();\n\tlet attributor: IAttributor | undefined;\n\tlet serializer: Encoder<IAttributor, string> | undefined;\n\tconst initialState: FuzzTestState = {\n\t\tclients: clientIds.map((clientId, index) => {\n\t\t\tconst dataStoreRuntime = new MockFluidDataStoreRuntime({ clientId });\n\t\t\tdataStoreRuntime.options = {\n\t\t\t\tattribution: {\n\t\t\t\t\ttrack: makeSerializer !== undefined,\n\t\t\t\t\tpolicyFactory: createInsertOnlyAttributionPolicy,\n\t\t\t\t},\n\t\t\t};\n\t\t\tconst { deltaManager } = dataStoreRuntime;\n\t\t\tconst sharedString = new SharedString(\n\t\t\t\tdataStoreRuntime,\n\t\t\t\t// eslint-disable-next-line unicorn/prefer-code-point\n\t\t\t\tString.fromCharCode(index + 65),\n\t\t\t\tSharedStringFactory.Attributes,\n\t\t\t);\n\n\t\t\tif (index === 0 && makeSerializer !== undefined) {\n\t\t\t\tattributor = new OpStreamAttributor(deltaManager, audience);\n\t\t\t\tserializer = makeSerializer(dataStoreRuntime);\n\t\t\t\t// DeltaManager mock doesn't have high fidelity but attribution requires DataStoreRuntime implements\n\t\t\t\t// audience / op emission.\n\t\t\t\tlet opIndex = 0;\n\t\t\t\tsharedString.on(\"op\", (message) => {\n\t\t\t\t\topIndex++;\n\t\t\t\t\tmessage.timestamp = getTimestamp(opIndex);\n\t\t\t\t\tdeltaManager.emit(\"op\", message);\n\t\t\t\t});\n\t\t\t\tdataStoreRuntime.getAudience = (): IAudience => audience;\n\t\t\t}\n\n\t\t\tconst containerRuntime =\n\t\t\t\tcontainerRuntimeFactory.createContainerRuntime(dataStoreRuntime);\n\t\t\tconst services: IChannelServices = {\n\t\t\t\tdeltaConnection: dataStoreRuntime.createDeltaConnection(),\n\t\t\t\tobjectStorage: new MockStorage(),\n\t\t\t};\n\n\t\t\tsharedString.initializeLocal();\n\t\t\tsharedString.connect(services);\n\t\t\treturn { containerRuntime, sharedString };\n\t\t}),\n\t\tcontainerRuntimeFactory,\n\t\trandom,\n\t};\n\tinitialState.attributor = attributor;\n\tinitialState.serializer = serializer;\n\n\t// Small wrapper to avoid having to return the same state repeatedly; all operations in this suite mutate.\n\t// Also a reasonable point to inject logging of incremental state.\n\tconst statefully =\n\t\t<T>(\n\t\t\tstatefulReducer: (state: FuzzTestState, operation: T) => void,\n\t\t): Reducer<T, FuzzTestState> =>\n\t\t(state, operation) => {\n\t\t\tstatefulReducer(state, operation);\n\t\t\treturn state;\n\t\t};\n\n\treturn performFuzzActions(\n\t\tgenerator,\n\t\t{\n\t\t\taddText: statefully(({ clients }, { stringId, index, content, props }) => {\n\t\t\t\tconst { sharedString } = clients.find((c) => c.sharedString.id === stringId) ?? {};\n\t\t\t\tassert(sharedString);\n\t\t\t\tsharedString.insertText(index, content, props);\n\t\t\t}),\n\t\t\tremoveRange: statefully(({ clients }, { stringId, start, end }) => {\n\t\t\t\tconst { sharedString } = clients.find((c) => c.sharedString.id === stringId) ?? {};\n\t\t\t\tassert(sharedString);\n\t\t\t\tsharedString.removeRange(start, end);\n\t\t\t}),\n\t\t\tannotateRange: statefully(({ clients }, { stringId, properties, start, end }) => {\n\t\t\t\tconst { sharedString } = clients.find((c) => c.sharedString.id === stringId) ?? {};\n\t\t\t\tassert(sharedString);\n\t\t\t\tsharedString.annotateRange(start, end, properties);\n\t\t\t}),\n\t\t\tsynchronize: statefully((state) => {\n\t\t\t\tstate.containerRuntimeFactory.processAllMessages();\n\t\t\t}),\n\t\t},\n\t\tinitialState,\n\t);\n}\n\nconst directory = path.join(_dirname, \"../../../src/test/attribution/documents\");\n\ninterface TestPaths {\n\tdirectory: string;\n\toperations: string;\n}\n\nfunction getDocumentPaths(docName: string): TestPaths {\n\tmkdirSync(path.join(directory, docName), { recursive: true });\n\treturn {\n\t\tdirectory: path.join(directory, docName),\n\t\toperations: path.join(directory, docName, \"operations.json\"),\n\t};\n}\n\nfunction getDocuments(): string[] {\n\treturn readdirSync(directory).filter((name) => name !== \"README.md\");\n}\n\n// Format a number separating 3 digits by comma\nconst formatNumber = (num: number): string => num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\n\nfunction spyOnOperations(baseGenerator: Generator<Operation, FuzzTestState>): {\n\tgenerator: Generator<Operation, FuzzTestState>;\n\toperations: Operation[];\n} {\n\tconst operations: Operation[] = [];\n\tconst generator = (state: FuzzTestState): Operation | typeof done => {\n\t\tconst operation = baseGenerator(state);\n\t\tif (operation !== done) {\n\t\t\toperations.push(operation);\n\t\t}\n\t\treturn operation;\n\t};\n\treturn { operations, generator };\n}\n\n/**\n * Type constraint for types that are likely deserializable from JSON or have a custom\n * alternate type.\n */\ntype JsonDeserializedTypeWith<T> =\n\t// eslint-disable-next-line @rushstack/no-new-null\n\t| null\n\t| boolean\n\t| number\n\t| string\n\t| T\n\t| { [P in string]: JsonDeserializedTypeWith<T> }\n\t| JsonDeserializedTypeWith<T>[];\n\ntype NonSymbolWithDefinedNonFunctionPropertyOf<T extends object> = Exclude<\n\t{\n\t\t// eslint-disable-next-line @typescript-eslint/ban-types\n\t\t[K in keyof T]: undefined extends T[K] ? never : T[K] extends Function ? never : K;\n\t}[keyof T],\n\tundefined | symbol\n>;\ntype NonSymbolWithUndefinedNonFunctionPropertyOf<T extends object> = Exclude<\n\t{\n\t\t// eslint-disable-next-line @typescript-eslint/ban-types\n\t\t[K in keyof T]: undefined extends T[K] ? (T[K] extends Function ? never : K) : never;\n\t}[keyof T],\n\tundefined | symbol\n>;\n\n/**\n * Used to constrain a type `T` to types that are deserializable from JSON.\n *\n * When used as a filter to inferred generic `T`, a compile-time error can be\n * produced trying to assign `JsonDeserialized<T>` to `T`.\n *\n * Deserialized JSON never contains `undefined` values, so properties with\n * `undefined` values become optional. If the original property was not already\n * optional, then compilation of assignment will fail.\n *\n * Similarly, function valued properties are removed.\n */\ntype JsonDeserialized<T, TReplaced = never> = /* test for 'any' */ boolean extends (\n\tT extends never ? true : false\n)\n\t? /* 'any' => */ JsonDeserializedTypeWith<TReplaced>\n\t: /* test for 'unknown' */ unknown extends T\n\t? /* 'unknown' => */ JsonDeserializedTypeWith<TReplaced>\n\t: // eslint-disable-next-line @rushstack/no-new-null\n\t/* test for Jsonable primitive types */ T extends null | boolean | number | string | TReplaced\n\t? /* primitive types => */ T\n\t: // eslint-disable-next-line @typescript-eslint/ban-types\n\t/* test for not a function */ Extract<T, Function> extends never\n\t? /* not a function => test for object */ T extends object\n\t\t? /* object => test for array */ T extends (infer E)[]\n\t\t\t? /* array => */ JsonDeserialized<E, TReplaced>[]\n\t\t\t: /* property bag => */\n\t\t\t /* properties with symbol keys or function values are removed */\n\t\t\t {\n\t\t\t\t\t/* properties with defined values are recursed */\n\t\t\t\t\t[K in NonSymbolWithDefinedNonFunctionPropertyOf<T>]: JsonDeserialized<\n\t\t\t\t\t\tT[K],\n\t\t\t\t\t\tTReplaced\n\t\t\t\t\t>;\n\t\t\t } & {\n\t\t\t\t\t/* properties that may have undefined values are optional */\n\t\t\t\t\t[K in NonSymbolWithUndefinedNonFunctionPropertyOf<T>]?: JsonDeserialized<\n\t\t\t\t\t\tT[K],\n\t\t\t\t\t\tTReplaced\n\t\t\t\t\t>;\n\t\t\t }\n\t\t: /* not an object => */ never\n\t: /* function => */ never;\n\nfunction readJson<T>(filepath: string): JsonDeserialized<T> {\n\treturn JSON.parse(readFileSync(filepath, { encoding: \"utf8\" })) as JsonDeserialized<T>;\n}\n\nfunction writeJson<T>(filepath: string, content: Jsonable<T>): void {\n\twriteFileSync(filepath, JSON.stringify(content, undefined, 4), { encoding: \"utf8\" });\n}\n\nconst validateInterval = 10;\n\nfunction getTimestamp(opIndex: number): number {\n\t// This is an arbitrary start point of the same magnitude as current data.\n\t// Wed Oct 12 2022 11:40:00 GMT-0700\n\tconst baseDate = 1665600000000;\n\treturn baseDate + Math.floor(opIndex / 10) * 5000;\n}\n\nfunction embedAttributionInProps(operations: Operation[]): Operation[] {\n\treturn operations.map((operation, index) => {\n\t\tif (operation.type === \"addText\") {\n\t\t\tconst name = operation.stringId.repeat(10);\n\t\t\tconst id = `${name}@contoso.com`;\n\t\t\tconst email = id;\n\t\t\tconst props = {\n\t\t\t\tattribution: {\n\t\t\t\t\tid,\n\t\t\t\t\tname,\n\t\t\t\t\temail,\n\t\t\t\t\ttimestamp: getTimestamp(index),\n\t\t\t\t},\n\t\t\t};\n\t\t\treturn {\n\t\t\t\t...operation,\n\t\t\t\tprops,\n\t\t\t};\n\t\t} else {\n\t\t\treturn operation;\n\t\t}\n\t});\n}\n\n// ISummaryTree is not serializable due to Uint8Array content in ISummaryBlob.\n// SerializableISummaryTree is a version of ISummaryTree with Uint8Array content removed.\ntype SerializableISummaryTree = ExcludeDeeply<ISummaryTree, Uint8Array>;\n\ntype ExcludeDeeply<T, Exclusion, TBase = Exclude<T, Exclusion>> = TBase extends object\n\t? { [K in keyof TBase]: ExcludeDeeply<TBase[K], Exclusion> }\n\t: TBase;\n\n/**\n * Validates that summary tree does not have a blob with a Uint8Array as content.\n *\n * @param summary - Summary tree to validate\n */\nfunction assertSerializableSummary(\n\tsummary: ISummaryTree,\n): asserts summary is SerializableISummaryTree {\n\tfor (const value of Object.values(summary.tree)) {\n\t\tswitch (value.type) {\n\t\t\tcase SummaryType.Tree: {\n\t\t\t\tassertSerializableSummary(value);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SummaryType.Blob: {\n\t\t\t\tassert(typeof value.content === \"string\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nconst summaryFromState = async (state: FuzzTestState): Promise<SerializableISummaryTree> => {\n\tstate.containerRuntimeFactory.processAllMessages();\n\tconst { sharedString } = state.clients[0];\n\tconst { summary } = await sharedString.summarize();\n\t// KLUDGE: For now, since attribution info isn't embedded at a proper location in the summary tree, just\n\t// add a property to the root so that its size is reported\n\tif (state.attributor && state.serializer) {\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any\n\t\t(summary as any).attribution = state.serializer.encode(state.attributor);\n\t}\n\tassertSerializableSummary(summary);\n\treturn summary;\n};\n\nconst noopEncoder = {\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tencode: (x: any): any => x,\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tdecode: (x: any): any => x,\n};\n\nclass DataTable<T> {\n\tprivate readonly rows = new Map<string, T[]>();\n\tpublic constructor(private readonly columnNames: string[]) {}\n\n\tpublic addRow(name: string, data: T[]): void {\n\t\tthis.rows.set(name, data);\n\t}\n\n\tpublic log(dataToString: (t: T) => string = (t): string => `${t}`): void {\n\t\tconst namePaddingLength =\n\t\t\t1 + Math.max(...Array.from(this.rows.keys(), (docName) => docName.length));\n\t\tconst rowStrings = new Map<string, string[]>();\n\t\tconst paddingByColumn = this.columnNames.map((name) => name.length);\n\t\tfor (const [name, data] of this.rows.entries()) {\n\t\t\tconst dataStrings = data.map((entry: T) => dataToString(entry));\n\t\t\trowStrings.set(name, dataStrings);\n\t\t\tfor (const [i, s] of dataStrings.entries()) {\n\t\t\t\tpaddingByColumn[i] = Math.max(paddingByColumn[i], s.length);\n\t\t\t}\n\t\t}\n\t\tfor (const [i, _] of paddingByColumn.entries()) {\n\t\t\tpaddingByColumn[i]++;\n\t\t}\n\n\t\tconsole.log(\n\t\t\t[\n\t\t\t\t`${\"Name\".padEnd(namePaddingLength)}`,\n\t\t\t\t...this.columnNames.map((name, i) => `${name.padStart(paddingByColumn[i])} `),\n\t\t\t].join(\"|\"),\n\t\t);\n\n\t\tfor (const [name, result] of rowStrings.entries()) {\n\t\t\tconsole.log(\n\t\t\t\t`${name.padEnd(namePaddingLength)}|${result\n\t\t\t\t\t.map((s, i) => `${s.padStart(paddingByColumn[i])} `)\n\t\t\t\t\t.join(\"|\")}`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nconst getSummaryLength = (summary: ISummaryTree): string =>\n\tformatNumber(JSON.stringify(summary).length);\n\ndescribe(\"SharedString Attribution\", () => {\n\t/**\n\t * This test suite is aimed at assessing the overhead of storing attribution information in a document.\n\t * See 'documents/README.md' for more details.\n\t */\n\n\tdescribe(\"using randomly generated documents\", () => {\n\t\t// Entries of this list produce documents which should contain the same attribution information but\n\t\t// stored in different formats. The \"None\" factory is an exception in that it contains no attribution\n\t\t// information, and is useful as a baseline for comparison.\n\t\tconst dataGenerators: {\n\t\t\tname: string;\n\t\t\tfactory: (operations: Operation[]) => FuzzTestState;\n\t\t\tfilename: string;\n\t\t}[] = [\n\t\t\t{\n\t\t\t\tname: \"None\",\n\t\t\t\tfactory: (operations: Operation[]) =>\n\t\t\t\t\tcreateSharedString(makeRandom(0), generatorFromArray(operations)),\n\t\t\t\tfilename: \"no-attribution-snap.json\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Prop\",\n\t\t\t\tfactory: (operations: Operation[]) =>\n\t\t\t\t\tcreateSharedString(\n\t\t\t\t\t\tmakeRandom(0),\n\t\t\t\t\t\tgeneratorFromArray(embedAttributionInProps(operations)),\n\t\t\t\t\t),\n\t\t\t\tfilename: \"prop-attribution-snap.json\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"OpStreamAttributor without any compression\",\n\t\t\t\tfactory: (operations: Operation[]) =>\n\t\t\t\t\tcreateSharedString(makeRandom(0), generatorFromArray(operations), (runtime) =>\n\t\t\t\t\t\tchainEncoders(\n\t\t\t\t\t\t\tnew AttributorSerializer(\n\t\t\t\t\t\t\t\t(entries) =>\n\t\t\t\t\t\t\t\t\tnew OpStreamAttributor(\n\t\t\t\t\t\t\t\t\t\truntime.deltaManager,\n\t\t\t\t\t\t\t\t\t\truntime.getAudience(),\n\t\t\t\t\t\t\t\t\t\tentries,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tnoopEncoder,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tnoopEncoder,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\tfilename: \"attributor-no-compression-snap.json\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"OpStreamAttributor without delta encoding\",\n\t\t\t\tfactory: (operations: Operation[]) =>\n\t\t\t\t\tcreateSharedString(makeRandom(0), generatorFromArray(operations), (runtime) =>\n\t\t\t\t\t\tchainEncoders(\n\t\t\t\t\t\t\tnew AttributorSerializer(\n\t\t\t\t\t\t\t\t(entries) =>\n\t\t\t\t\t\t\t\t\tnew OpStreamAttributor(\n\t\t\t\t\t\t\t\t\t\truntime.deltaManager,\n\t\t\t\t\t\t\t\t\t\truntime.getAudience(),\n\t\t\t\t\t\t\t\t\t\tentries,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tnoopEncoder,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tmakeLZ4Encoder(),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\tfilename: \"attributor-lz4-compression-snap.json\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"OpStreamAttributor\",\n\t\t\t\tfactory: (operations: Operation[]) =>\n\t\t\t\t\tcreateSharedString(makeRandom(0), generatorFromArray(operations), (runtime) =>\n\t\t\t\t\t\tchainEncoders(\n\t\t\t\t\t\t\tnew AttributorSerializer(\n\t\t\t\t\t\t\t\t(entries) =>\n\t\t\t\t\t\t\t\t\tnew OpStreamAttributor(\n\t\t\t\t\t\t\t\t\t\truntime.deltaManager,\n\t\t\t\t\t\t\t\t\t\truntime.getAudience(),\n\t\t\t\t\t\t\t\t\t\tentries,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tdeltaEncoder,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tmakeLZ4Encoder(),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\tfilename: \"attributor-lz4-and-delta-snap.json\",\n\t\t\t},\n\t\t];\n\n\t\tit.skip(\"Generate a new document\", async () => {\n\t\t\tconst paths = getDocumentPaths(\"default\");\n\t\t\tconst attributionlessGenerator = chain(\n\t\t\t\ttake(100, makeOperationGenerator({ validateInterval })),\n\t\t\t\tgeneratorFromArray<Operation, FuzzTestState>([{ type: \"synchronize\" }]),\n\t\t\t);\n\n\t\t\tconst { generator, operations } = spyOnOperations(attributionlessGenerator);\n\t\t\tcreateSharedString(makeRandom(0), generator);\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any\n\t\t\twriteJson(paths.operations, operations as any);\n\n\t\t\tawait Promise.all(\n\t\t\t\tdataGenerators.map(async ({ filename, factory }) => {\n\t\t\t\t\tconst summary = await summaryFromState(factory(operations));\n\t\t\t\t\twriteJson(path.join(paths.directory, filename), summary);\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\n\t\tconst documents = getDocuments();\n\t\tfor (const document of documents) {\n\t\t\tdescribe(`document name: ${document} has an up-to-date`, () => {\n\t\t\t\tlet paths: TestPaths;\n\t\t\t\tlet operations: Operation[];\n\t\t\t\tbefore(() => {\n\t\t\t\t\tpaths = getDocumentPaths(document);\n\t\t\t\t\toperations = readJson<Operation[]>(paths.operations);\n\t\t\t\t});\n\n\t\t\t\tfor (const { filename, factory } of dataGenerators) {\n\t\t\t\t\tit(`snapshot at ${filename}`, async () => {\n\t\t\t\t\t\tconst expected = readJson(path.join(paths.directory, filename));\n\t\t\t\t\t\tconst actual = await summaryFromState(factory(operations));\n\t\t\t\t\t\tassert.deepEqual(actual, expected);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t// Note: to see output, FLUID_TEST_VERBOSE needs to be enabled. Using the `test:mocha:verbose` script is\n\t\t// sufficient to do so.\n\t\tit(\"generate snapshot size impact report\", async () => {\n\t\t\tconst table = new DataTable<ISummaryTree>(dataGenerators.map(({ name }) => name));\n\t\t\tfor (const docName of documents) {\n\t\t\t\tconst paths = getDocumentPaths(docName);\n\t\t\t\tconst operations: Operation[] = readJson(paths.operations);\n\t\t\t\tconst data = await Promise.all(\n\t\t\t\t\tdataGenerators.map(async ({ factory }) =>\n\t\t\t\t\t\tsummaryFromState(factory(operations)),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\ttable.addRow(docName, data);\n\t\t\t}\n\n\t\t\ttable.log(getSummaryLength);\n\t\t});\n\t});\n});\n"]}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
import { strict as assert } from "node:assert";
|
|
6
|
-
import { Attributor } from "../attributor.js";
|
|
7
|
-
describe("Attributor", () => {
|
|
8
|
-
it("can retrieve user information from its initial entries", () => {
|
|
9
|
-
const key = 42;
|
|
10
|
-
const timestamp = 50;
|
|
11
|
-
const user = { id: "user foo" };
|
|
12
|
-
const attributor = new Attributor([[key, { user, timestamp }]]);
|
|
13
|
-
assert.deepEqual(attributor.getAttributionInfo(key), { user, timestamp });
|
|
14
|
-
});
|
|
15
|
-
it(".entries() retrieves all user information", () => {
|
|
16
|
-
const entries = [
|
|
17
|
-
[50, { user: { id: "a" }, timestamp: 30 }],
|
|
18
|
-
[51, { user: { id: "b" }, timestamp: 60 }],
|
|
19
|
-
];
|
|
20
|
-
const attributor = new Attributor(entries);
|
|
21
|
-
assert.deepEqual([...attributor.entries()], entries);
|
|
22
|
-
});
|
|
23
|
-
it("getAttributionInfo throws on attempt to retrieve user information for an invalid key", () => {
|
|
24
|
-
const attributor = new Attributor();
|
|
25
|
-
assert.throws(() => attributor.getAttributionInfo(42), /Requested attribution information for unstored key/, "invalid key should throw");
|
|
26
|
-
});
|
|
27
|
-
it("tryGetAttributionInfo returns undefined to retrieve user information for an invalid key", () => {
|
|
28
|
-
const attributor = new Attributor();
|
|
29
|
-
assert.equal(attributor.tryGetAttributionInfo(42), undefined);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
//# sourceMappingURL=attributor.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attributor.spec.js","sourceRoot":"","sources":["../../src/test/attributor.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAG/C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QACjE,MAAM,GAAG,GAAG,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,EAAE,CAAC;QACrB,MAAM,IAAI,GAAU,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC;QACvC,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;QAChE,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACpD,MAAM,OAAO,GAAwC;YACpD,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;YAC1C,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;SAC1C,CAAC;QACF,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sFAAsF,EAAE,GAAG,EAAE;QAC/F,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;QACpC,MAAM,CAAC,MAAM,CACZ,GAAG,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC,EACvC,oDAAoD,EACpD,0BAA0B,CAC1B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yFAAyF,EAAE,GAAG,EAAE;QAClG,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { strict as assert } from \"node:assert\";\nimport { type IUser } from \"@fluidframework/protocol-definitions\";\nimport { type AttributionInfo } from \"@fluidframework/runtime-definitions\";\nimport { Attributor } from \"../attributor.js\";\n\ndescribe(\"Attributor\", () => {\n\tit(\"can retrieve user information from its initial entries\", () => {\n\t\tconst key = 42;\n\t\tconst timestamp = 50;\n\t\tconst user: IUser = { id: \"user foo\" };\n\t\tconst attributor = new Attributor([[key, { user, timestamp }]]);\n\t\tassert.deepEqual(attributor.getAttributionInfo(key), { user, timestamp });\n\t});\n\n\tit(\".entries() retrieves all user information\", () => {\n\t\tconst entries: Iterable<[number, AttributionInfo]> = [\n\t\t\t[50, { user: { id: \"a\" }, timestamp: 30 }],\n\t\t\t[51, { user: { id: \"b\" }, timestamp: 60 }],\n\t\t];\n\t\tconst attributor = new Attributor(entries);\n\t\tassert.deepEqual([...attributor.entries()], entries);\n\t});\n\n\tit(\"getAttributionInfo throws on attempt to retrieve user information for an invalid key\", () => {\n\t\tconst attributor = new Attributor();\n\t\tassert.throws(\n\t\t\t() => attributor.getAttributionInfo(42),\n\t\t\t/Requested attribution information for unstored key/,\n\t\t\t\"invalid key should throw\",\n\t\t);\n\t});\n\n\tit(\"tryGetAttributionInfo returns undefined to retrieve user information for an invalid key\", () => {\n\t\tconst attributor = new Attributor();\n\t\tassert.equal(attributor.tryGetAttributionInfo(42), undefined);\n\t});\n});\n"]}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
import { strict as assert } from "node:assert";
|
|
6
|
-
import { Attributor } from "../attributor.js";
|
|
7
|
-
import { AttributorSerializer, chain, } from "../encoders.js";
|
|
8
|
-
function makeNoopEncoder() {
|
|
9
|
-
return {
|
|
10
|
-
encode: (x) => x,
|
|
11
|
-
decode: (x) => x,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
describe("AttributorSerializer", () => {
|
|
15
|
-
it("uses its timestamp encoder", () => {
|
|
16
|
-
it("on encode", () => {
|
|
17
|
-
const attributor = new Attributor([
|
|
18
|
-
[1, { user: { id: "a" }, timestamp: 500 }],
|
|
19
|
-
[2, { user: { id: "a" }, timestamp: 6001 }],
|
|
20
|
-
]);
|
|
21
|
-
const calls = [];
|
|
22
|
-
const serializer = new AttributorSerializer((entries) => new Attributor(entries), {
|
|
23
|
-
encode: (x) => {
|
|
24
|
-
calls.push(x);
|
|
25
|
-
return x;
|
|
26
|
-
},
|
|
27
|
-
decode: (x) => x,
|
|
28
|
-
});
|
|
29
|
-
assert.equal(calls.length, 0);
|
|
30
|
-
serializer.encode(attributor);
|
|
31
|
-
assert.equal(calls.length, 1);
|
|
32
|
-
assert.deepEqual(calls[0], [500, 6001]);
|
|
33
|
-
});
|
|
34
|
-
it("on decode", () => {
|
|
35
|
-
const calls = [];
|
|
36
|
-
const serializer = new AttributorSerializer((entries) => new Attributor(entries), {
|
|
37
|
-
encode: (x) => x,
|
|
38
|
-
decode: (x) => {
|
|
39
|
-
calls.push(x);
|
|
40
|
-
return x;
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
const encoded = {
|
|
44
|
-
interner: ["a"],
|
|
45
|
-
seqs: [1, 2],
|
|
46
|
-
timestamps: [501, 604],
|
|
47
|
-
attributionRefs: [0, 0],
|
|
48
|
-
};
|
|
49
|
-
assert.equal(calls.length, 0);
|
|
50
|
-
serializer.decode(encoded);
|
|
51
|
-
assert.equal(calls.length, 1);
|
|
52
|
-
assert.deepEqual(calls[0], [501, 604]);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
describe("correctly round-trips", () => {
|
|
56
|
-
const testCases = [
|
|
57
|
-
{
|
|
58
|
-
name: "empty attribution information",
|
|
59
|
-
entries: [],
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: "one attribution",
|
|
63
|
-
entries: [[5, { user: { id: "only user" }, timestamp: 6000 }]],
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: "multiple keys associated with the same user",
|
|
67
|
-
entries: [
|
|
68
|
-
[1, { user: { id: "user foo" }, timestamp: 500 }],
|
|
69
|
-
[2, { user: { id: "user foo" }, timestamp: 7000 }],
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: "multiple keys associated with different users",
|
|
74
|
-
entries: [
|
|
75
|
-
[1, { user: { id: "user foo" }, timestamp: 500 }],
|
|
76
|
-
[2, { user: { id: "user bar" }, timestamp: 7000 }],
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
];
|
|
80
|
-
for (const { name, entries } of testCases) {
|
|
81
|
-
it(name, () => {
|
|
82
|
-
const calls = [];
|
|
83
|
-
let retVal;
|
|
84
|
-
const serializer = new AttributorSerializer((providedEntries) => {
|
|
85
|
-
calls.push(providedEntries);
|
|
86
|
-
retVal = new Attributor(providedEntries);
|
|
87
|
-
return retVal;
|
|
88
|
-
}, makeNoopEncoder());
|
|
89
|
-
const attributor = new Attributor(entries);
|
|
90
|
-
const roundTrippedAttributor = serializer.decode(serializer.encode(attributor));
|
|
91
|
-
assert.equal(calls.length, 1);
|
|
92
|
-
assert.deepEqual(calls[0], entries);
|
|
93
|
-
assert.equal(retVal, roundTrippedAttributor);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
describe("chain", () => {
|
|
99
|
-
it("correctly chains encoders", () => {
|
|
100
|
-
const encodeCalls = [];
|
|
101
|
-
const decodeCalls = [];
|
|
102
|
-
const makeLoggingChain = (tag) => ({
|
|
103
|
-
encode: (s) => {
|
|
104
|
-
encodeCalls.push(tag);
|
|
105
|
-
return s;
|
|
106
|
-
},
|
|
107
|
-
decode: (s) => {
|
|
108
|
-
decodeCalls.push(tag);
|
|
109
|
-
return s;
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
const encoder = chain(makeLoggingChain("A"), makeLoggingChain("B"));
|
|
113
|
-
assert.deepEqual(encodeCalls, []);
|
|
114
|
-
assert.deepEqual(decodeCalls, []);
|
|
115
|
-
assert.equal(encoder.encode("foo"), "foo");
|
|
116
|
-
assert.deepEqual(encodeCalls, ["A", "B"]);
|
|
117
|
-
assert.deepEqual(decodeCalls, []);
|
|
118
|
-
assert.equal(encoder.decode("bar"), "bar");
|
|
119
|
-
assert.deepEqual(encodeCalls, ["A", "B"]);
|
|
120
|
-
assert.deepEqual(decodeCalls, ["B", "A"]);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
//# sourceMappingURL=attributorSerializer.spec.js.map
|