@fluidframework/id-compressor 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/id-compressor.api.md +2 -1
- package/dist/appendOnlySortedMap.js +2 -2
- package/dist/appendOnlySortedMap.js.map +1 -1
- package/dist/finalSpace.d.ts +1 -1
- package/dist/finalSpace.d.ts.map +1 -1
- package/dist/finalSpace.js +2 -2
- package/dist/finalSpace.js.map +1 -1
- package/dist/idCompressor.d.ts +1 -1
- package/dist/idCompressor.d.ts.map +1 -1
- package/dist/idCompressor.js +25 -24
- package/dist/idCompressor.js.map +1 -1
- package/dist/legacy.d.ts +28 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/public.d.ts +18 -0
- package/dist/sessionSpaceNormalizer.d.ts +2 -0
- package/dist/sessionSpaceNormalizer.d.ts.map +1 -1
- package/dist/sessionSpaceNormalizer.js +44 -0
- package/dist/sessionSpaceNormalizer.js.map +1 -1
- package/dist/sessions.d.ts +1 -1
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +5 -5
- package/dist/sessions.js.map +1 -1
- package/dist/test/appendOnlySortedMap.perf.spec.js.map +1 -1
- package/dist/test/appendOnlySortedMap.spec.js.map +1 -1
- package/dist/test/idCompressor.perf.spec.js.map +1 -1
- package/dist/test/idCompressor.spec.js.map +1 -1
- package/dist/test/idCompressorTestUtilities.d.ts +2 -2
- package/dist/test/idCompressorTestUtilities.d.ts.map +1 -1
- package/dist/test/idCompressorTestUtilities.js +25 -1
- package/dist/test/idCompressorTestUtilities.js.map +1 -1
- package/dist/test/numericUuid.spec.js.map +1 -1
- package/dist/test/sessionSpaceNormalizer.spec.js.map +1 -1
- package/dist/test/snapshots/summary.spec.js.map +1 -1
- package/dist/test/testCommon.d.ts +1 -1
- package/dist/test/testCommon.d.ts.map +1 -1
- package/dist/test/testCommon.js +4 -4
- package/dist/test/testCommon.js.map +1 -1
- package/dist/types/idCompressor.d.ts +2 -2
- package/dist/types/idCompressor.d.ts.map +1 -1
- package/dist/types/idCompressor.js.map +1 -1
- package/dist/types/persisted-types/0.0.1.d.ts +4 -0
- package/dist/types/persisted-types/0.0.1.d.ts.map +1 -1
- package/dist/types/persisted-types/0.0.1.js.map +1 -1
- package/dist/utilities.d.ts +1 -1
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +2 -2
- package/dist/utilities.js.map +1 -1
- package/internal.d.ts +11 -0
- package/legacy.d.ts +11 -0
- package/lib/appendOnlySortedMap.js +1 -1
- package/lib/appendOnlySortedMap.js.map +1 -1
- package/lib/finalSpace.d.ts +1 -1
- package/lib/finalSpace.d.ts.map +1 -1
- package/lib/finalSpace.js +1 -1
- package/lib/finalSpace.js.map +1 -1
- package/lib/idCompressor.d.ts +1 -1
- package/lib/idCompressor.d.ts.map +1 -1
- package/lib/idCompressor.js +6 -5
- package/lib/idCompressor.js.map +1 -1
- package/lib/legacy.d.ts +28 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/public.d.ts +18 -0
- package/lib/sessionSpaceNormalizer.d.ts +2 -0
- package/lib/sessionSpaceNormalizer.d.ts.map +1 -1
- package/lib/sessionSpaceNormalizer.js +44 -0
- package/lib/sessionSpaceNormalizer.js.map +1 -1
- package/lib/sessions.d.ts +1 -1
- package/lib/sessions.d.ts.map +1 -1
- package/lib/sessions.js +2 -2
- package/lib/sessions.js.map +1 -1
- package/lib/test/appendOnlySortedMap.perf.spec.js.map +1 -1
- package/lib/test/appendOnlySortedMap.spec.js.map +1 -1
- package/lib/test/idCompressor.perf.spec.js.map +1 -1
- package/lib/test/idCompressor.spec.js.map +1 -1
- package/lib/test/idCompressorTestUtilities.d.ts +2 -2
- package/lib/test/idCompressorTestUtilities.d.ts.map +1 -1
- package/lib/test/idCompressorTestUtilities.js +27 -3
- package/lib/test/idCompressorTestUtilities.js.map +1 -1
- package/lib/test/numericUuid.spec.js.map +1 -1
- package/lib/test/sessionSpaceNormalizer.spec.js.map +1 -1
- package/lib/test/snapshots/summary.spec.js.map +1 -1
- package/lib/test/testCommon.d.ts +1 -1
- package/lib/test/testCommon.d.ts.map +1 -1
- package/lib/test/testCommon.js +1 -1
- package/lib/test/testCommon.js.map +1 -1
- package/lib/types/idCompressor.d.ts +2 -2
- package/lib/types/idCompressor.d.ts.map +1 -1
- package/lib/types/idCompressor.js.map +1 -1
- package/lib/types/persisted-types/0.0.1.d.ts +4 -0
- package/lib/types/persisted-types/0.0.1.d.ts.map +1 -1
- package/lib/types/persisted-types/0.0.1.js.map +1 -1
- package/lib/utilities.d.ts +1 -1
- package/lib/utilities.d.ts.map +1 -1
- package/lib/utilities.js +1 -1
- package/lib/utilities.js.map +1 -1
- package/package.json +35 -65
- package/src/appendOnlySortedMap.ts +1 -1
- package/src/finalSpace.ts +3 -2
- package/src/idCompressor.ts +31 -25
- package/src/packageVersion.ts +1 -1
- package/src/sessionSpaceNormalizer.ts +63 -0
- package/src/sessions.ts +5 -4
- package/src/test/idCompressorTestUtilities.ts +38 -10
- package/src/test/testCommon.ts +4 -3
- package/src/test/tsconfig.json +1 -1
- package/src/types/idCompressor.ts +2 -2
- package/src/types/persisted-types/0.0.1.ts +5 -0
- package/src/utilities.ts +3 -2
- package/api-extractor-cjs.json +0 -8
- package/dist/id-compressor-alpha.d.ts +0 -352
- package/dist/id-compressor-beta.d.ts +0 -179
- package/dist/id-compressor-public.d.ts +0 -179
- package/dist/id-compressor-untrimmed.d.ts +0 -365
- package/lib/id-compressor-alpha.d.ts +0 -352
- package/lib/id-compressor-beta.d.ts +0 -179
- package/lib/id-compressor-public.d.ts +0 -179
- package/lib/id-compressor-untrimmed.d.ts +0 -365
- package/lib/test/appendOnlySortedMap.perf.spec.js +0 -49
- package/lib/test/appendOnlySortedMap.spec.js +0 -166
- package/lib/test/idCompressor.perf.spec.js +0 -281
- package/lib/test/idCompressor.spec.js +0 -953
- package/lib/test/numericUuid.spec.js +0 -74
- package/lib/test/sessionSpaceNormalizer.spec.js +0 -43
- package/lib/test/snapshots/summary.spec.js +0 -127
- /package/{dist → lib}/tsdoc-metadata.json +0 -0
|
@@ -1,953 +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 "assert";
|
|
6
|
-
import { MockLogger } from "@fluidframework/telemetry-utils";
|
|
7
|
-
import { take } from "@fluid-private/stochastic-test-utils";
|
|
8
|
-
import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
|
|
9
|
-
import { IdCompressor, createIdCompressor, deserializeIdCompressor } from "../idCompressor.js";
|
|
10
|
-
import { createSessionId } from "../utilities.js";
|
|
11
|
-
import { performFuzzActions, sessionIds, IdCompressorTestNetwork, Client, DestinationClient, MetaClient, expectSerializes, roundtrip, makeOpGenerator, CompressorFactory, } from "./idCompressorTestUtilities.js";
|
|
12
|
-
import { incrementStableId, isFinalId, isLocalId, fail } from "./testCommon.js";
|
|
13
|
-
describe("IdCompressor", () => {
|
|
14
|
-
it("reports the proper session ID", () => {
|
|
15
|
-
const sessionId = createSessionId();
|
|
16
|
-
const compressor = CompressorFactory.createCompressorWithSession(sessionId);
|
|
17
|
-
assert(compressor.localSessionId, sessionId);
|
|
18
|
-
});
|
|
19
|
-
describe("ID Generation", () => {
|
|
20
|
-
it("can manually create a compressed ID", () => {
|
|
21
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
22
|
-
const id = compressor.generateCompressedId();
|
|
23
|
-
const uuid = compressor.decompress(id);
|
|
24
|
-
assert.equal(id, compressor.recompress(uuid));
|
|
25
|
-
});
|
|
26
|
-
it("can generate document unique IDs", () => {
|
|
27
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 2);
|
|
28
|
-
let id = compressor.generateDocumentUniqueId();
|
|
29
|
-
assert(typeof id === "string");
|
|
30
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
31
|
-
id = compressor.generateDocumentUniqueId();
|
|
32
|
-
assert(typeof id === "number" && isFinalId(id));
|
|
33
|
-
id = compressor.generateDocumentUniqueId();
|
|
34
|
-
assert(typeof id === "number" && isFinalId(id));
|
|
35
|
-
id = compressor.generateDocumentUniqueId();
|
|
36
|
-
assert(typeof id === "string");
|
|
37
|
-
});
|
|
38
|
-
describe("Eager final ID allocation", () => {
|
|
39
|
-
it("eagerly allocates final IDs when cluster creation has been finalized", () => {
|
|
40
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 3);
|
|
41
|
-
const localId1 = compressor.generateCompressedId();
|
|
42
|
-
assert(isLocalId(localId1));
|
|
43
|
-
const localId2 = compressor.generateCompressedId();
|
|
44
|
-
assert(isLocalId(localId2));
|
|
45
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
46
|
-
const finalId3 = compressor.generateCompressedId();
|
|
47
|
-
assert(isFinalId(finalId3));
|
|
48
|
-
const finalId4 = compressor.generateCompressedId();
|
|
49
|
-
assert(isFinalId(finalId4));
|
|
50
|
-
const finalId5 = compressor.generateCompressedId();
|
|
51
|
-
assert(isFinalId(finalId5));
|
|
52
|
-
const localId6 = compressor.generateCompressedId();
|
|
53
|
-
assert(isLocalId(localId6));
|
|
54
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
55
|
-
const opSpaceId1 = compressor.normalizeToOpSpace(localId1);
|
|
56
|
-
const opSpaceId2 = compressor.normalizeToOpSpace(localId2);
|
|
57
|
-
const opSpaceId3 = compressor.normalizeToOpSpace(finalId3);
|
|
58
|
-
const opSpaceId4 = compressor.normalizeToOpSpace(finalId4);
|
|
59
|
-
const opSpaceId5 = compressor.normalizeToOpSpace(finalId5);
|
|
60
|
-
const opSpaceId6 = compressor.normalizeToOpSpace(localId6);
|
|
61
|
-
assert(isFinalId(opSpaceId1));
|
|
62
|
-
assert(isFinalId(opSpaceId2));
|
|
63
|
-
assert(isFinalId(opSpaceId3) && opSpaceId3 === finalId3);
|
|
64
|
-
assert(isFinalId(opSpaceId4) && opSpaceId4 === finalId4);
|
|
65
|
-
assert(isFinalId(opSpaceId5) && opSpaceId5 === finalId5);
|
|
66
|
-
assert(isFinalId(opSpaceId6));
|
|
67
|
-
assert.equal(compressor.normalizeToSessionSpace(opSpaceId1, compressor.localSessionId), localId1);
|
|
68
|
-
assert.equal(compressor.normalizeToSessionSpace(opSpaceId2, compressor.localSessionId), localId2);
|
|
69
|
-
assert.equal(compressor.normalizeToSessionSpace(opSpaceId3, compressor.localSessionId), finalId3);
|
|
70
|
-
assert.equal(compressor.normalizeToSessionSpace(opSpaceId4, compressor.localSessionId), finalId4);
|
|
71
|
-
assert.equal(compressor.normalizeToSessionSpace(opSpaceId5, compressor.localSessionId), finalId5);
|
|
72
|
-
assert.equal(compressor.normalizeToSessionSpace(opSpaceId6, compressor.localSessionId), localId6);
|
|
73
|
-
});
|
|
74
|
-
it("correctly normalizes eagerly allocated final IDs", () => {
|
|
75
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5);
|
|
76
|
-
const localId1 = compressor.generateCompressedId();
|
|
77
|
-
const range1 = compressor.takeNextCreationRange();
|
|
78
|
-
const localId2 = compressor.generateCompressedId();
|
|
79
|
-
const range2 = compressor.takeNextCreationRange();
|
|
80
|
-
assert(isLocalId(localId1));
|
|
81
|
-
assert(isLocalId(localId2));
|
|
82
|
-
compressor.finalizeCreationRange(range1);
|
|
83
|
-
compressor.finalizeCreationRange(range2);
|
|
84
|
-
const opSpaceId1 = compressor.normalizeToOpSpace(localId1);
|
|
85
|
-
const opSpaceId2 = compressor.normalizeToOpSpace(localId2);
|
|
86
|
-
assert(isFinalId(opSpaceId1));
|
|
87
|
-
assert(isFinalId(opSpaceId2));
|
|
88
|
-
assert.equal(compressor.normalizeToSessionSpace(opSpaceId1, compressor.localSessionId), localId1);
|
|
89
|
-
assert.equal(compressor.normalizeToSessionSpace(opSpaceId2, compressor.localSessionId), localId2);
|
|
90
|
-
});
|
|
91
|
-
it("generates correct eager finals when there are outstanding locals after cluster expansion", () => {
|
|
92
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 2);
|
|
93
|
-
// Before cluster expansion
|
|
94
|
-
assert(isLocalId(compressor.generateCompressedId()));
|
|
95
|
-
const rangeA = compressor.takeNextCreationRange();
|
|
96
|
-
compressor.finalizeCreationRange(rangeA);
|
|
97
|
-
assert(isFinalId(compressor.generateCompressedId()));
|
|
98
|
-
assert(isFinalId(compressor.generateCompressedId()));
|
|
99
|
-
// After cluster expansion
|
|
100
|
-
assert(isLocalId(compressor.generateCompressedId()));
|
|
101
|
-
const rangeB = compressor.takeNextCreationRange();
|
|
102
|
-
const localId = compressor.generateCompressedId();
|
|
103
|
-
assert(isLocalId(localId));
|
|
104
|
-
// Take a range that won't be finalized in this test; the finalizing of range B should associate this range with finals
|
|
105
|
-
const rangeC = compressor.takeNextCreationRange();
|
|
106
|
-
compressor.finalizeCreationRange(rangeB);
|
|
107
|
-
const eagerId = compressor.generateCompressedId();
|
|
108
|
-
assert(isFinalId(eagerId));
|
|
109
|
-
assert.equal(compressor.recompress(compressor.decompress(localId)), localId);
|
|
110
|
-
assert.equal(compressor.recompress(compressor.decompress(eagerId)), eagerId);
|
|
111
|
-
compressor.finalizeCreationRange(rangeC);
|
|
112
|
-
assert.equal(compressor.recompress(compressor.decompress(localId)), localId);
|
|
113
|
-
assert.equal(compressor.recompress(compressor.decompress(eagerId)), eagerId);
|
|
114
|
-
});
|
|
115
|
-
it("generates unique eager finals when multiple outstanding creation ranges during finalizing", () => {
|
|
116
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 10 /* must be 10 for the test to make sense */);
|
|
117
|
-
// Make a first outstanding range
|
|
118
|
-
const id1_1 = compressor.generateCompressedId();
|
|
119
|
-
const id1_2 = compressor.generateCompressedId();
|
|
120
|
-
assert(isLocalId(id1_1));
|
|
121
|
-
assert(isLocalId(id1_2));
|
|
122
|
-
const range1 = compressor.takeNextCreationRange();
|
|
123
|
-
// Make a second outstanding range
|
|
124
|
-
const id2_1 = compressor.generateCompressedId();
|
|
125
|
-
const id2_2 = compressor.generateCompressedId();
|
|
126
|
-
assert(isLocalId(id2_1));
|
|
127
|
-
assert(isLocalId(id2_2));
|
|
128
|
-
const range2 = compressor.takeNextCreationRange();
|
|
129
|
-
// Finalize just the first one, which should create finals that align with both outstanding ranges
|
|
130
|
-
compressor.finalizeCreationRange(range1);
|
|
131
|
-
// Make a third range. This one should be composed of eager finals that align after the two ranges above.
|
|
132
|
-
const id3_1 = compressor.generateCompressedId();
|
|
133
|
-
const id3_2 = compressor.generateCompressedId();
|
|
134
|
-
assert(isFinalId(id3_1));
|
|
135
|
-
assert(isFinalId(id3_2));
|
|
136
|
-
const range3 = compressor.takeNextCreationRange();
|
|
137
|
-
// Finalize both initial ranges.
|
|
138
|
-
compressor.finalizeCreationRange(range2);
|
|
139
|
-
compressor.finalizeCreationRange(range3);
|
|
140
|
-
// Make some more eager finals that should be aligned correctly.
|
|
141
|
-
const id4_1 = compressor.generateCompressedId();
|
|
142
|
-
const id4_2 = compressor.generateCompressedId();
|
|
143
|
-
assert(isFinalId(id4_1));
|
|
144
|
-
assert(isFinalId(id4_2));
|
|
145
|
-
// Assert everything is unique and consistent.
|
|
146
|
-
const ids = new Set();
|
|
147
|
-
const uuids = new Set();
|
|
148
|
-
[id1_1, id1_2, id2_1, id2_2, id3_1, id3_2, id4_1, id4_2].forEach((id) => {
|
|
149
|
-
ids.add(id);
|
|
150
|
-
uuids.add(compressor.decompress(id));
|
|
151
|
-
});
|
|
152
|
-
assert.equal(ids.size, 8);
|
|
153
|
-
assert.equal(uuids.size, 8);
|
|
154
|
-
});
|
|
155
|
-
it("generates unique eager finals when there are still outstanding locals after a cluster is expanded", () => {
|
|
156
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 2 /* must be 2 for the test to make sense */);
|
|
157
|
-
// Make locals to fill half the future cluster
|
|
158
|
-
const id1_1 = compressor.generateCompressedId();
|
|
159
|
-
const id1_2 = compressor.generateCompressedId();
|
|
160
|
-
assert(isLocalId(id1_1));
|
|
161
|
-
assert(isLocalId(id1_2));
|
|
162
|
-
const range1 = compressor.takeNextCreationRange();
|
|
163
|
-
// Make locals to overflow the future cluster
|
|
164
|
-
const id2_1 = compressor.generateCompressedId();
|
|
165
|
-
const id2_2 = compressor.generateCompressedId();
|
|
166
|
-
const id2_3 = compressor.generateCompressedId();
|
|
167
|
-
assert(isLocalId(id2_1));
|
|
168
|
-
assert(isLocalId(id2_2));
|
|
169
|
-
assert(isLocalId(id2_3));
|
|
170
|
-
const range2 = compressor.takeNextCreationRange();
|
|
171
|
-
// Finalize the first range. This should align the first four locals (i.e. all of range1, and 2/3 of range2)
|
|
172
|
-
compressor.finalizeCreationRange(range1);
|
|
173
|
-
assert(isFinalId(compressor.normalizeToOpSpace(id2_2)));
|
|
174
|
-
assert(isLocalId(compressor.normalizeToOpSpace(id2_3)));
|
|
175
|
-
// Make a single range that should still be overflowing the initial cluster (i.e. be local)
|
|
176
|
-
const id3_1 = compressor.generateCompressedId();
|
|
177
|
-
assert(isLocalId(id3_1));
|
|
178
|
-
const range3 = compressor.takeNextCreationRange();
|
|
179
|
-
// Second finalize should expand the cluster and align all outstanding ranges.
|
|
180
|
-
compressor.finalizeCreationRange(range2);
|
|
181
|
-
// All generated IDs should have aligned finals (even though range3 has not been finalized)
|
|
182
|
-
const allIds = [
|
|
183
|
-
id1_1,
|
|
184
|
-
id1_2,
|
|
185
|
-
id2_1,
|
|
186
|
-
id2_2,
|
|
187
|
-
id2_3,
|
|
188
|
-
id3_1,
|
|
189
|
-
];
|
|
190
|
-
allIds.forEach((id) => assert(isFinalId(compressor.normalizeToOpSpace(id))));
|
|
191
|
-
compressor.finalizeCreationRange(range3);
|
|
192
|
-
// Make one eager final
|
|
193
|
-
const id4_1 = compressor.generateCompressedId();
|
|
194
|
-
allIds.push(id4_1);
|
|
195
|
-
assert(isFinalId(id4_1));
|
|
196
|
-
// Assert everything is unique and consistent.
|
|
197
|
-
const ids = new Set();
|
|
198
|
-
const uuids = new Set();
|
|
199
|
-
allIds.forEach((id) => {
|
|
200
|
-
ids.add(id);
|
|
201
|
-
uuids.add(compressor.decompress(id));
|
|
202
|
-
});
|
|
203
|
-
assert.equal(ids.size, 7);
|
|
204
|
-
assert.equal(uuids.size, 7);
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
/**
|
|
209
|
-
* Helper to generate a fixed number of IDs.
|
|
210
|
-
*/
|
|
211
|
-
function generateCompressedIds(compressor, count) {
|
|
212
|
-
const ids = [];
|
|
213
|
-
for (let i = 0; i < count; i++) {
|
|
214
|
-
ids.push(compressor.generateCompressedId());
|
|
215
|
-
}
|
|
216
|
-
return ids;
|
|
217
|
-
}
|
|
218
|
-
describe("can produce a creation range", () => {
|
|
219
|
-
const clusterSize = 5;
|
|
220
|
-
const tests = [
|
|
221
|
-
{ title: "that is empty", idCount: 0 },
|
|
222
|
-
{ title: "with one ID", idCount: 1 },
|
|
223
|
-
{ title: "with more IDs than fit in a cluster", idCount: clusterSize * 2 },
|
|
224
|
-
];
|
|
225
|
-
tests.forEach(({ title, idCount }) => {
|
|
226
|
-
it(title, () => {
|
|
227
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
228
|
-
generateCompressedIds(compressor, idCount);
|
|
229
|
-
const range = compressor.takeNextCreationRange();
|
|
230
|
-
if (range.ids !== undefined) {
|
|
231
|
-
assert.equal(range.ids.count, idCount);
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
assert.equal(idCount, 0);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
it("does not return the same non-empty range twice", () => {
|
|
240
|
-
const rangeCompressor = CompressorFactory.createCompressor(Client.Client1);
|
|
241
|
-
generateCompressedIds(rangeCompressor, 3);
|
|
242
|
-
const range1 = rangeCompressor.takeNextCreationRange();
|
|
243
|
-
assert.notEqual(range1.ids, undefined);
|
|
244
|
-
const range2 = rangeCompressor.takeNextCreationRange();
|
|
245
|
-
assert.equal(range2.ids, undefined);
|
|
246
|
-
rangeCompressor.finalizeCreationRange(range1);
|
|
247
|
-
rangeCompressor.finalizeCreationRange(range2);
|
|
248
|
-
});
|
|
249
|
-
describe("Finalizing", () => {
|
|
250
|
-
it("prevents attempts to finalize ranges twice", () => {
|
|
251
|
-
const rangeCompressor = CompressorFactory.createCompressor(Client.Client1);
|
|
252
|
-
generateCompressedIds(rangeCompressor, 3);
|
|
253
|
-
const batchRange = rangeCompressor.takeNextCreationRange();
|
|
254
|
-
rangeCompressor.finalizeCreationRange(batchRange);
|
|
255
|
-
assert.throws(() => rangeCompressor.finalizeCreationRange(batchRange), (e) => e.message === "Ranges finalized out of order.");
|
|
256
|
-
});
|
|
257
|
-
it("prevents attempts to finalize ranges out of order", () => {
|
|
258
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
259
|
-
compressor.generateCompressedId();
|
|
260
|
-
compressor.takeNextCreationRange();
|
|
261
|
-
compressor.generateCompressedId();
|
|
262
|
-
const secondRange = compressor.takeNextCreationRange();
|
|
263
|
-
assert.throws(() => compressor.finalizeCreationRange(secondRange), (e) => e.message === "Ranges finalized out of order.");
|
|
264
|
-
});
|
|
265
|
-
it("can finalize ranges into clusters of varying sizes", () => {
|
|
266
|
-
for (let i = 1; i < 5; i++) {
|
|
267
|
-
for (let j = 0; j <= i; j++) {
|
|
268
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, i);
|
|
269
|
-
const ids = new Set();
|
|
270
|
-
for (let k = 0; k <= j; k++) {
|
|
271
|
-
ids.add(compressor.generateCompressedId());
|
|
272
|
-
}
|
|
273
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
274
|
-
const opIds = new Set();
|
|
275
|
-
ids.forEach((id) => opIds.add(compressor.normalizeToOpSpace(id)));
|
|
276
|
-
assert.equal(ids.size, opIds.size);
|
|
277
|
-
opIds.forEach((id) => assert.equal(isFinalId(id), true));
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
describe("Ghost sessions", () => {
|
|
283
|
-
it("prevents non-allocation mutations during a ghost session", () => {
|
|
284
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
285
|
-
const range = compressor.takeNextCreationRange();
|
|
286
|
-
compressor.beginGhostSession(createSessionId(), () => {
|
|
287
|
-
assert.throws(() => compressor.takeNextCreationRange());
|
|
288
|
-
assert.throws(() => compressor.finalizeCreationRange(range));
|
|
289
|
-
assert.throws(() => compressor.serialize(false));
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
it("can generate IDs during a ghost session", () => {
|
|
293
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
294
|
-
const idCount = 10;
|
|
295
|
-
const ids = new Set();
|
|
296
|
-
const ghostSession = createSessionId();
|
|
297
|
-
compressor.beginGhostSession(ghostSession, () => {
|
|
298
|
-
for (let i = 0; i < idCount; i++) {
|
|
299
|
-
const id = compressor.generateCompressedId();
|
|
300
|
-
assert(isFinalId(id));
|
|
301
|
-
assert(compressor.decompress(id) === incrementStableId(ghostSession, i));
|
|
302
|
-
ids.add(id);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
assert.equal(ids.size, idCount);
|
|
306
|
-
});
|
|
307
|
-
it("does not create a cluster for a no-op ghost session", () => {
|
|
308
|
-
const mockLogger = new MockLogger();
|
|
309
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5, mockLogger);
|
|
310
|
-
compressor.serialize(false);
|
|
311
|
-
mockLogger.assertMatchAny([
|
|
312
|
-
{
|
|
313
|
-
eventName: "RuntimeIdCompressor:SerializedIdCompressorSize",
|
|
314
|
-
clusterCount: 0,
|
|
315
|
-
sessionCount: 0,
|
|
316
|
-
},
|
|
317
|
-
]);
|
|
318
|
-
compressor.beginGhostSession(createSessionId(), () => { });
|
|
319
|
-
compressor.serialize(false);
|
|
320
|
-
mockLogger.assertMatchAny([
|
|
321
|
-
{
|
|
322
|
-
eventName: "RuntimeIdCompressor:SerializedIdCompressorSize",
|
|
323
|
-
clusterCount: 0,
|
|
324
|
-
sessionCount: 0,
|
|
325
|
-
},
|
|
326
|
-
]);
|
|
327
|
-
compressor.beginGhostSession(createSessionId(), () => {
|
|
328
|
-
compressor.generateCompressedId();
|
|
329
|
-
});
|
|
330
|
-
compressor.serialize(false);
|
|
331
|
-
mockLogger.assertMatchAny([
|
|
332
|
-
{
|
|
333
|
-
eventName: "RuntimeIdCompressor:SerializedIdCompressorSize",
|
|
334
|
-
clusterCount: 1,
|
|
335
|
-
sessionCount: 1,
|
|
336
|
-
},
|
|
337
|
-
]);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
describe("Recompression", () => {
|
|
341
|
-
it("can re-compress a uuid it generated", () => {
|
|
342
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
343
|
-
const id = compressor.generateCompressedId();
|
|
344
|
-
const uuid = compressor.decompress(id);
|
|
345
|
-
assert.equal(compressor.recompress(uuid), id);
|
|
346
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
347
|
-
assert.equal(compressor.recompress(uuid), id);
|
|
348
|
-
});
|
|
349
|
-
it("can re-compress uuids from a remote client", () => {
|
|
350
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
351
|
-
const uuid = compressor.decompress(compressor.generateCompressedId());
|
|
352
|
-
const compressor2 = CompressorFactory.createCompressor(Client.Client2);
|
|
353
|
-
compressor2.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
354
|
-
const finalId = compressor2.recompress(uuid);
|
|
355
|
-
if (finalId === undefined) {
|
|
356
|
-
assert.fail();
|
|
357
|
-
}
|
|
358
|
-
assert(isFinalId(finalId));
|
|
359
|
-
});
|
|
360
|
-
it("will not re-compress uuids it did not originally generate", () => {
|
|
361
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
362
|
-
assert.equal(compressor.tryRecompress("5fff846a-efd4-42fb-8b78-b32ce2672f99"), undefined);
|
|
363
|
-
});
|
|
364
|
-
it("can re-compress an eager final ID that is not finalized", () => {
|
|
365
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5);
|
|
366
|
-
compressor.generateCompressedId();
|
|
367
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
368
|
-
const finalId = compressor.generateCompressedId();
|
|
369
|
-
assert(isFinalId(finalId));
|
|
370
|
-
const stableId = incrementStableId(sessionIds.get(Client.Client1), 1);
|
|
371
|
-
assert.equal(compressor.recompress(stableId), finalId);
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
describe("Decompression", () => {
|
|
375
|
-
it("will not decompress IDs it did not generate", () => {
|
|
376
|
-
const errorMessage = "Unknown ID";
|
|
377
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
378
|
-
assert.throws(() => compressor.decompress(-1), (e) => e.message === errorMessage);
|
|
379
|
-
assert.throws(() => compressor.decompress(0), (e) => e.message === errorMessage);
|
|
380
|
-
});
|
|
381
|
-
it("can decompress a local ID before and after finalizing", () => {
|
|
382
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
383
|
-
const id = compressor.generateCompressedId();
|
|
384
|
-
const uuid = compressor.decompress(id);
|
|
385
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
386
|
-
assert.equal(compressor.decompress(id), uuid);
|
|
387
|
-
});
|
|
388
|
-
it("can decompress a final ID", () => {
|
|
389
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
390
|
-
const id = compressor.generateCompressedId();
|
|
391
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
392
|
-
const finalId = compressor.normalizeToOpSpace(id);
|
|
393
|
-
if (isLocalId(finalId)) {
|
|
394
|
-
assert.fail("Op space ID was finalized but is local");
|
|
395
|
-
}
|
|
396
|
-
compressor.decompress(compressor.normalizeToSessionSpace(finalId, compressor.localSessionId));
|
|
397
|
-
});
|
|
398
|
-
it("can decompress an eagerly generated final ID that is not finalized", () => {
|
|
399
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5);
|
|
400
|
-
compressor.generateCompressedId();
|
|
401
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
402
|
-
const finalId = compressor.generateCompressedId();
|
|
403
|
-
assert(isFinalId(finalId));
|
|
404
|
-
assert.equal(compressor.decompress(finalId), incrementStableId(sessionIds.get(Client.Client1), 1));
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
describe("Normalization", () => {
|
|
408
|
-
it("can normalize a local ID to op space before finalizing", () => {
|
|
409
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
410
|
-
const id = compressor.generateCompressedId();
|
|
411
|
-
const normalized = compressor.normalizeToOpSpace(id);
|
|
412
|
-
assert(isLocalId(id));
|
|
413
|
-
assert.equal(id, normalized);
|
|
414
|
-
});
|
|
415
|
-
it("can normalize a local ID to op space after finalizing", () => {
|
|
416
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
417
|
-
const id = compressor.generateCompressedId();
|
|
418
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
419
|
-
const normalized = compressor.normalizeToOpSpace(id);
|
|
420
|
-
assert(isFinalId(normalized));
|
|
421
|
-
assert.notEqual(id, normalized);
|
|
422
|
-
});
|
|
423
|
-
it("can normalize an eagerly generated final ID", () => {
|
|
424
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5);
|
|
425
|
-
compressor.generateCompressedId();
|
|
426
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
427
|
-
const eagerFinalId = compressor.generateCompressedId();
|
|
428
|
-
assert(isFinalId(eagerFinalId));
|
|
429
|
-
const opNormalized = compressor.normalizeToOpSpace(eagerFinalId);
|
|
430
|
-
assert.equal(eagerFinalId, opNormalized);
|
|
431
|
-
const sessionNormalized = compressor.normalizeToSessionSpace(opNormalized, compressor.localSessionId);
|
|
432
|
-
assert.equal(sessionNormalized, opNormalized);
|
|
433
|
-
});
|
|
434
|
-
it("cannot normalize a remote ID to session space if it has not been finalized", () => {
|
|
435
|
-
const compressor1 = CompressorFactory.createCompressor(Client.Client1);
|
|
436
|
-
const compressor2 = CompressorFactory.createCompressor(Client.Client2);
|
|
437
|
-
const normalized = compressor1.normalizeToOpSpace(compressor1.generateCompressedId());
|
|
438
|
-
assert.throws(() => compressor2.normalizeToSessionSpace(normalized, compressor1.localSessionId), (e) => e.message === "No IDs have ever been finalized by the supplied session.");
|
|
439
|
-
});
|
|
440
|
-
it("can normalize local and final IDs from a remote session to session space", () => {
|
|
441
|
-
const compressor1 = CompressorFactory.createCompressor(Client.Client1);
|
|
442
|
-
const compressor2 = CompressorFactory.createCompressor(Client.Client2);
|
|
443
|
-
const id = compressor1.generateCompressedId();
|
|
444
|
-
const normalizedLocal = compressor1.normalizeToOpSpace(id);
|
|
445
|
-
const range = compressor1.takeNextCreationRange();
|
|
446
|
-
compressor1.finalizeCreationRange(range);
|
|
447
|
-
const normalizedFinal = compressor1.normalizeToOpSpace(id);
|
|
448
|
-
compressor2.finalizeCreationRange(range);
|
|
449
|
-
assert(isLocalId(normalizedLocal));
|
|
450
|
-
assert(isFinalId(normalizedFinal));
|
|
451
|
-
assert.equal(compressor2.normalizeToSessionSpace(normalizedFinal, compressor1.localSessionId), normalizedFinal);
|
|
452
|
-
assert.equal(compressor2.normalizeToSessionSpace(normalizedLocal, compressor1.localSessionId), normalizedFinal);
|
|
453
|
-
});
|
|
454
|
-
it("can normalize a final ID created by the local session but sent in another client's op space", () => {
|
|
455
|
-
// Regression test for the situation in which a client creates a final ID and another client references
|
|
456
|
-
// that final ID in a message back to the creating client. The creating client will normalize it and
|
|
457
|
-
// pass the session ID of the remote (non-creating) client. This should be handled correctly.
|
|
458
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5);
|
|
459
|
-
const compressor2 = CompressorFactory.createCompressor(Client.Client2, 5);
|
|
460
|
-
const id = compressor.generateCompressedId();
|
|
461
|
-
const creationRange = compressor.takeNextCreationRange();
|
|
462
|
-
compressor.finalizeCreationRange(creationRange);
|
|
463
|
-
compressor2.finalizeCreationRange(creationRange);
|
|
464
|
-
const idInClient2OpSpace = compressor2.normalizeToOpSpace(compressor2.normalizeToSessionSpace(compressor.normalizeToOpSpace(id), compressor.localSessionId));
|
|
465
|
-
const normalizedToClient1SessionSpace = compressor.normalizeToSessionSpace(idInClient2OpSpace, compressor2.localSessionId);
|
|
466
|
-
assert.equal(normalizedToClient1SessionSpace, id);
|
|
467
|
-
});
|
|
468
|
-
});
|
|
469
|
-
describe("Telemetry", () => {
|
|
470
|
-
it("emits first cluster and new cluster telemetry events", () => {
|
|
471
|
-
const mockLogger = new MockLogger();
|
|
472
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5, mockLogger);
|
|
473
|
-
const localId1 = compressor.generateCompressedId();
|
|
474
|
-
assert(isLocalId(localId1));
|
|
475
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
476
|
-
mockLogger.assertMatch([
|
|
477
|
-
{
|
|
478
|
-
eventName: "RuntimeIdCompressor:FirstCluster",
|
|
479
|
-
sessionId: compressor.localSessionId,
|
|
480
|
-
},
|
|
481
|
-
{
|
|
482
|
-
eventName: "RuntimeIdCompressor:IdCompressorStatus",
|
|
483
|
-
eagerFinalIdCount: 0,
|
|
484
|
-
localIdCount: 1,
|
|
485
|
-
sessionId: compressor.localSessionId,
|
|
486
|
-
},
|
|
487
|
-
]);
|
|
488
|
-
});
|
|
489
|
-
it("emits new cluster event on second cluster", () => {
|
|
490
|
-
// Fill the first cluster
|
|
491
|
-
const mockLogger = new MockLogger();
|
|
492
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 1, mockLogger);
|
|
493
|
-
compressor.generateCompressedId();
|
|
494
|
-
const range = compressor.takeNextCreationRange();
|
|
495
|
-
compressor.finalizeCreationRange(range);
|
|
496
|
-
// Create another cluster with a different client so that expansion doesn't happen
|
|
497
|
-
const mockLogger2 = new MockLogger();
|
|
498
|
-
const compressor2 = CompressorFactory.createCompressor(Client.Client2, 1, mockLogger2);
|
|
499
|
-
compressor2.finalizeCreationRange(range);
|
|
500
|
-
compressor2.generateCompressedId();
|
|
501
|
-
const range2 = compressor2.takeNextCreationRange();
|
|
502
|
-
compressor2.finalizeCreationRange(range2);
|
|
503
|
-
compressor.finalizeCreationRange(range2);
|
|
504
|
-
// Make sure we emitted the FirstCluster event
|
|
505
|
-
mockLogger.assertMatchAny([
|
|
506
|
-
{
|
|
507
|
-
eventName: "RuntimeIdCompressor:FirstCluster",
|
|
508
|
-
},
|
|
509
|
-
]);
|
|
510
|
-
mockLogger.clear();
|
|
511
|
-
// Fill the one remaining spot
|
|
512
|
-
compressor.generateCompressedId();
|
|
513
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
514
|
-
// Trigger a new cluster creation
|
|
515
|
-
compressor.generateCompressedId();
|
|
516
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
517
|
-
mockLogger.assertMatchAny([
|
|
518
|
-
{
|
|
519
|
-
eventName: "RuntimeIdCompressor:NewCluster",
|
|
520
|
-
},
|
|
521
|
-
]);
|
|
522
|
-
});
|
|
523
|
-
it("correctly logs telemetry events for eager final id allocations", () => {
|
|
524
|
-
const mockLogger = new MockLogger();
|
|
525
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5, mockLogger);
|
|
526
|
-
const localId1 = compressor.generateCompressedId();
|
|
527
|
-
assert(isLocalId(localId1));
|
|
528
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
529
|
-
mockLogger.assertMatchAny([
|
|
530
|
-
{
|
|
531
|
-
eventName: "RuntimeIdCompressor:IdCompressorStatus",
|
|
532
|
-
eagerFinalIdCount: 0,
|
|
533
|
-
localIdCount: 1,
|
|
534
|
-
sessionId: compressor.localSessionId,
|
|
535
|
-
},
|
|
536
|
-
]);
|
|
537
|
-
mockLogger.clear();
|
|
538
|
-
const finalId1 = compressor.generateCompressedId();
|
|
539
|
-
const finalId2 = compressor.generateCompressedId();
|
|
540
|
-
assert(isFinalId(finalId1));
|
|
541
|
-
assert(isFinalId(finalId2));
|
|
542
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
543
|
-
mockLogger.assertMatchAny([
|
|
544
|
-
{
|
|
545
|
-
eventName: "RuntimeIdCompressor:IdCompressorStatus",
|
|
546
|
-
eagerFinalIdCount: 2,
|
|
547
|
-
localIdCount: 0,
|
|
548
|
-
sessionId: compressor.localSessionId,
|
|
549
|
-
},
|
|
550
|
-
]);
|
|
551
|
-
});
|
|
552
|
-
it("correctly logs telemetry events for expansion case", () => {
|
|
553
|
-
const mockLogger = new MockLogger();
|
|
554
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5, mockLogger);
|
|
555
|
-
const localId1 = compressor.generateCompressedId();
|
|
556
|
-
assert(isLocalId(localId1));
|
|
557
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
558
|
-
mockLogger.assertMatchAny([
|
|
559
|
-
{
|
|
560
|
-
eventName: "RuntimeIdCompressor:IdCompressorStatus",
|
|
561
|
-
eagerFinalIdCount: 0,
|
|
562
|
-
localIdCount: 1,
|
|
563
|
-
sessionId: compressor.localSessionId,
|
|
564
|
-
},
|
|
565
|
-
]);
|
|
566
|
-
mockLogger.clear();
|
|
567
|
-
for (let i = 0; i < 5; i++) {
|
|
568
|
-
const id = compressor.generateCompressedId();
|
|
569
|
-
assert(isFinalId(id));
|
|
570
|
-
}
|
|
571
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
572
|
-
mockLogger.assertMatchAny([
|
|
573
|
-
{
|
|
574
|
-
eventName: "RuntimeIdCompressor:IdCompressorStatus",
|
|
575
|
-
eagerFinalIdCount: 5,
|
|
576
|
-
localIdCount: 0,
|
|
577
|
-
sessionId: compressor.localSessionId,
|
|
578
|
-
},
|
|
579
|
-
]);
|
|
580
|
-
mockLogger.clear();
|
|
581
|
-
const expansionId1 = compressor.generateCompressedId();
|
|
582
|
-
const expansionId2 = compressor.generateCompressedId();
|
|
583
|
-
assert(isLocalId(expansionId1));
|
|
584
|
-
assert(isLocalId(expansionId2));
|
|
585
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
586
|
-
mockLogger.assertMatchAny([
|
|
587
|
-
{
|
|
588
|
-
eventName: "RuntimeIdCompressor:ClusterExpansion",
|
|
589
|
-
sessionId: compressor.localSessionId,
|
|
590
|
-
previousCapacity: 6,
|
|
591
|
-
newCapacity: 13,
|
|
592
|
-
overflow: 2,
|
|
593
|
-
},
|
|
594
|
-
]);
|
|
595
|
-
});
|
|
596
|
-
it("emits telemetry when serialized", () => {
|
|
597
|
-
const mockLogger = new MockLogger();
|
|
598
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1, 5, mockLogger);
|
|
599
|
-
const localId1 = compressor.generateCompressedId();
|
|
600
|
-
assert(isLocalId(localId1));
|
|
601
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
602
|
-
compressor.serialize(false);
|
|
603
|
-
mockLogger.assertMatchAny([
|
|
604
|
-
{
|
|
605
|
-
eventName: "RuntimeIdCompressor:SerializedIdCompressorSize",
|
|
606
|
-
size: 72,
|
|
607
|
-
clusterCount: 1,
|
|
608
|
-
sessionCount: 1,
|
|
609
|
-
},
|
|
610
|
-
]);
|
|
611
|
-
});
|
|
612
|
-
it("correctly passes logger when no session specified", () => {
|
|
613
|
-
const mockLogger = new MockLogger();
|
|
614
|
-
const compressor = createIdCompressor(mockLogger);
|
|
615
|
-
compressor.generateCompressedId();
|
|
616
|
-
compressor.finalizeCreationRange(compressor.takeNextCreationRange());
|
|
617
|
-
mockLogger.assertMatchAny([
|
|
618
|
-
{
|
|
619
|
-
eventName: "RuntimeIdCompressor:FirstCluster",
|
|
620
|
-
},
|
|
621
|
-
]);
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
describe("Serialization", () => {
|
|
625
|
-
it("can serialize an empty compressor", () => {
|
|
626
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
627
|
-
expectSerializes(compressor);
|
|
628
|
-
compressor.generateCompressedId();
|
|
629
|
-
expectSerializes(compressor);
|
|
630
|
-
});
|
|
631
|
-
it("correctly deserializes and resumes a session", () => {
|
|
632
|
-
const compressor1 = CompressorFactory.createCompressor(Client.Client1);
|
|
633
|
-
const compressor2 = CompressorFactory.createCompressor(Client.Client2);
|
|
634
|
-
compressor1.generateCompressedId();
|
|
635
|
-
const creationRange = compressor1.takeNextCreationRange();
|
|
636
|
-
compressor1.finalizeCreationRange(creationRange);
|
|
637
|
-
compressor2.finalizeCreationRange(creationRange);
|
|
638
|
-
const [_, serializedWithSession] = expectSerializes(compressor1);
|
|
639
|
-
const compressorResumed = IdCompressor.deserialize(serializedWithSession);
|
|
640
|
-
compressorResumed.generateCompressedId();
|
|
641
|
-
const range2 = compressorResumed.takeNextCreationRange();
|
|
642
|
-
compressorResumed.finalizeCreationRange(range2);
|
|
643
|
-
compressor2.finalizeCreationRange(range2);
|
|
644
|
-
const [__, roundtrippedCompressorResumed] = roundtrip(compressorResumed, false);
|
|
645
|
-
const [___, roundtrippedCompressor2] = roundtrip(compressor2, false);
|
|
646
|
-
assert(roundtrippedCompressorResumed.equals(roundtrippedCompressor2, false));
|
|
647
|
-
});
|
|
648
|
-
it("can detect and fails to load 1.0 documents", () => {
|
|
649
|
-
const compressor = CompressorFactory.createCompressor(Client.Client1);
|
|
650
|
-
const base64Content = compressor.serialize(false);
|
|
651
|
-
const floatView = new Float64Array(stringToBuffer(base64Content, "base64"));
|
|
652
|
-
// Change the version to 1.0
|
|
653
|
-
floatView[0] = 1.0;
|
|
654
|
-
const docString1 = bufferToString(floatView.buffer, "base64");
|
|
655
|
-
assert.throws(() => deserializeIdCompressor(docString1, createSessionId()), (e) => e.message === "IdCompressor version 1.0 is no longer supported.");
|
|
656
|
-
});
|
|
657
|
-
});
|
|
658
|
-
describe("Collision detection", () => {
|
|
659
|
-
it("detects when a new cluster is allocated whose UUIDs collide with another cluster", () => {
|
|
660
|
-
const compressor1 = CompressorFactory.createCompressor(Client.Client1);
|
|
661
|
-
const compressor2 = CompressorFactory.createCompressorWithSession(incrementStableId(compressor1.localSessionId, 5));
|
|
662
|
-
compressor1.generateCompressedId();
|
|
663
|
-
const creationRange1 = compressor1.takeNextCreationRange();
|
|
664
|
-
compressor2.generateCompressedId();
|
|
665
|
-
const creationRange2 = compressor2.takeNextCreationRange();
|
|
666
|
-
const errorMessage = "0x758";
|
|
667
|
-
// Simulate world in which range1 was sequenced first
|
|
668
|
-
compressor1.finalizeCreationRange(creationRange1);
|
|
669
|
-
assert.throws(() => compressor1.finalizeCreationRange(creationRange2), (e) => e.message === errorMessage);
|
|
670
|
-
// Simulate world in which range2 was sequenced first
|
|
671
|
-
compressor2.finalizeCreationRange(creationRange2);
|
|
672
|
-
assert.throws(() => compressor2.finalizeCreationRange(creationRange1), (e) => e.message === errorMessage);
|
|
673
|
-
});
|
|
674
|
-
});
|
|
675
|
-
describeNetwork("Networked", (itNetwork) => {
|
|
676
|
-
itNetwork("upholds the invariant that IDs always decompress to the same UUID", 2, (network) => {
|
|
677
|
-
network.allocateAndSendIds(Client.Client1, 5);
|
|
678
|
-
network.allocateAndSendIds(Client.Client2, 5);
|
|
679
|
-
network.allocateAndSendIds(Client.Client3, 5);
|
|
680
|
-
const preAckLocals = new Map();
|
|
681
|
-
for (const [client, compressor] of network.getTargetCompressors(MetaClient.All)) {
|
|
682
|
-
const locals = [];
|
|
683
|
-
for (const idData of network.getIdLog(client)) {
|
|
684
|
-
locals.push([idData.id, compressor.decompress(idData.id)]);
|
|
685
|
-
}
|
|
686
|
-
preAckLocals.set(client, locals);
|
|
687
|
-
}
|
|
688
|
-
// Ack all IDs
|
|
689
|
-
network.deliverOperations(DestinationClient.All);
|
|
690
|
-
for (const [client, compressor] of network.getTargetCompressors(MetaClient.All)) {
|
|
691
|
-
const preAckLocalIds = preAckLocals.get(client) ?? fail("Expected preack locals for client");
|
|
692
|
-
let i = 0;
|
|
693
|
-
for (const idData of network.getIdLog(client)) {
|
|
694
|
-
if (idData.originatingClient === client) {
|
|
695
|
-
assert(!isFinalId(idData.id));
|
|
696
|
-
const currentUuid = compressor.decompress(idData.id);
|
|
697
|
-
assert.equal(currentUuid, preAckLocalIds[i % preAckLocalIds.length][1]);
|
|
698
|
-
i++;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
itNetwork("can normalize session space IDs to op space", 5, (network) => {
|
|
704
|
-
const clusterCapacity = 5;
|
|
705
|
-
const idCount = clusterCapacity * 2;
|
|
706
|
-
for (let i = 0; i < idCount; i++) {
|
|
707
|
-
network.allocateAndSendIds(Client.Client1, 1);
|
|
708
|
-
network.allocateAndSendIds(Client.Client2, 1);
|
|
709
|
-
network.allocateAndSendIds(Client.Client3, 1);
|
|
710
|
-
}
|
|
711
|
-
for (const [client, compressor] of network.getTargetCompressors(MetaClient.All)) {
|
|
712
|
-
for (const idData of network.getIdLog(client)) {
|
|
713
|
-
assert.equal(idData.originatingClient, client);
|
|
714
|
-
assert(isLocalId(compressor.normalizeToOpSpace(idData.id)));
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
network.deliverOperations(DestinationClient.All);
|
|
718
|
-
for (const [client, compressor] of network.getTargetCompressors(MetaClient.All)) {
|
|
719
|
-
for (const idData of network.getIdLog(client)) {
|
|
720
|
-
assert(isFinalId(compressor.normalizeToOpSpace(idData.id)));
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
});
|
|
724
|
-
itNetwork("can normalize local op space IDs from a local session to session space IDs", (network) => {
|
|
725
|
-
const compressor = network.getCompressor(Client.Client1);
|
|
726
|
-
network.allocateAndSendIds(Client.Client1, 1);
|
|
727
|
-
network.deliverOperations(Client.Client1);
|
|
728
|
-
const sessionSpaceIds = network.getIdLog(Client.Client1);
|
|
729
|
-
const opSpaceId = compressor.normalizeToOpSpace(sessionSpaceIds[0].id);
|
|
730
|
-
const sessionSpaceId = compressor.normalizeToSessionSpace(opSpaceId, compressor.localSessionId);
|
|
731
|
-
assert(isFinalId(opSpaceId));
|
|
732
|
-
assert(isLocalId(sessionSpaceId));
|
|
733
|
-
});
|
|
734
|
-
itNetwork("can normalize local op space IDs from a remote session to session space IDs", (network) => {
|
|
735
|
-
const compressor1 = network.getCompressor(Client.Client1);
|
|
736
|
-
const compressor2 = network.getCompressor(Client.Client2);
|
|
737
|
-
const opSpaceIds = network.allocateAndSendIds(Client.Client1, 1);
|
|
738
|
-
// Mimic sending a reference to an ID that hasn't been acked yet, such as in a slow network
|
|
739
|
-
const id = opSpaceIds[0];
|
|
740
|
-
const getSessionNormalizedId = () => compressor2.normalizeToSessionSpace(id, compressor1.localSessionId);
|
|
741
|
-
assert.throws(getSessionNormalizedId, (e) => e.message === "No IDs have ever been finalized by the supplied session.");
|
|
742
|
-
network.deliverOperations(Client.Client2);
|
|
743
|
-
assert(isFinalId(getSessionNormalizedId()));
|
|
744
|
-
});
|
|
745
|
-
function expectSequencedLogsAlign(network, client1, client2, numUnifications = 0) {
|
|
746
|
-
network.deliverOperations(DestinationClient.All);
|
|
747
|
-
assert(client1 !== client2, "Clients must not be the same");
|
|
748
|
-
const log1 = network.getSequencedIdLog(client1);
|
|
749
|
-
const log2 = network.getSequencedIdLog(client2);
|
|
750
|
-
assert.equal(log1.length, log2.length);
|
|
751
|
-
const compressor1 = network.getCompressor(client1);
|
|
752
|
-
const compressor2 = network.getCompressor(client2);
|
|
753
|
-
const ids = new Set();
|
|
754
|
-
const uuids = new Set();
|
|
755
|
-
for (let i = 0; i < log1.length; i++) {
|
|
756
|
-
const data1 = log1[i];
|
|
757
|
-
const id1 = compressor1.normalizeToOpSpace(data1.id);
|
|
758
|
-
const id2 = compressor2.normalizeToOpSpace(log2[i].id);
|
|
759
|
-
assert(isFinalId(id1));
|
|
760
|
-
ids.add(id1);
|
|
761
|
-
assert.equal(id1, id2);
|
|
762
|
-
const uuidOrOverride1 = compressor1.decompress(compressor1.normalizeToSessionSpace(id1, compressor1.localSessionId));
|
|
763
|
-
uuids.add(uuidOrOverride1);
|
|
764
|
-
assert.equal(uuidOrOverride1, compressor2.decompress(compressor2.normalizeToSessionSpace(id2, compressor2.localSessionId)));
|
|
765
|
-
}
|
|
766
|
-
const expectedSize = log1.length - numUnifications;
|
|
767
|
-
assert.equal(ids.size, expectedSize);
|
|
768
|
-
assert.equal(uuids.size, expectedSize);
|
|
769
|
-
}
|
|
770
|
-
itNetwork("produces ID spaces correctly", (network) => {
|
|
771
|
-
// This test asserts that IDs returned from IdCompressor APIs are correctly encoded as either local or final.
|
|
772
|
-
// This is a glass box test in that it assumes the negative/positive encoding of CompressedIds (negative = local, positive = final).
|
|
773
|
-
const compressor1 = network.getCompressor(Client.Client1);
|
|
774
|
-
// Client 1 makes three IDs
|
|
775
|
-
network.allocateAndSendIds(Client.Client1, 3);
|
|
776
|
-
network.getIdLog(Client.Client1).forEach(({ id }) => assert(isLocalId(id)));
|
|
777
|
-
// Client 1's IDs have not been acked so have no op space equivalent
|
|
778
|
-
network
|
|
779
|
-
.getIdLog(Client.Client1)
|
|
780
|
-
.forEach((idData) => assert(isLocalId(compressor1.normalizeToOpSpace(idData.id))));
|
|
781
|
-
// Client 1's IDs are acked
|
|
782
|
-
network.deliverOperations(Client.Client1);
|
|
783
|
-
network.getIdLog(Client.Client1).forEach(({ id }) => assert(isLocalId(id)));
|
|
784
|
-
// Client 2 makes three IDs
|
|
785
|
-
network.allocateAndSendIds(Client.Client2, 3);
|
|
786
|
-
network.getIdLog(Client.Client2).forEach(({ id }) => assert(isLocalId(id)));
|
|
787
|
-
// Client 1 receives Client 2's IDs
|
|
788
|
-
network.deliverOperations(Client.Client1);
|
|
789
|
-
network
|
|
790
|
-
.getIdLog(Client.Client1)
|
|
791
|
-
.slice(-3)
|
|
792
|
-
.forEach(({ id }) => assert(isFinalId(id)));
|
|
793
|
-
// All IDs have been acked or are from another client, and therefore have a final form in op space
|
|
794
|
-
network
|
|
795
|
-
.getIdLog(Client.Client1)
|
|
796
|
-
.forEach(({ id }) => assert(isFinalId(compressor1.normalizeToOpSpace(id))));
|
|
797
|
-
// Compression should preserve ID space correctness
|
|
798
|
-
network.getIdLog(Client.Client1).forEach((idData) => {
|
|
799
|
-
const roundtripped = compressor1.recompress(compressor1.decompress(idData.id));
|
|
800
|
-
assert.equal(Math.sign(roundtripped), Math.sign(idData.id));
|
|
801
|
-
});
|
|
802
|
-
network.getIdLog(Client.Client1).forEach((idData) => {
|
|
803
|
-
const opNormalized = compressor1.normalizeToOpSpace(idData.id);
|
|
804
|
-
assert.equal(Math.sign(compressor1.normalizeToSessionSpace(opNormalized, idData.sessionId)), Math.sign(idData.id));
|
|
805
|
-
});
|
|
806
|
-
});
|
|
807
|
-
itNetwork("produces consistent IDs with large fuzz input", (network) => {
|
|
808
|
-
const generator = take(5000, makeOpGenerator({}));
|
|
809
|
-
performFuzzActions(generator, network, 1984, undefined, true, (n) => n.assertNetworkState());
|
|
810
|
-
network.deliverOperations(DestinationClient.All);
|
|
811
|
-
});
|
|
812
|
-
itNetwork("does not decompress ids for empty parts of clusters", 2, (network) => {
|
|
813
|
-
// This is a glass box test in that it creates a final ID outside of the ID compressor
|
|
814
|
-
network.allocateAndSendIds(Client.Client1, 1);
|
|
815
|
-
network.deliverOperations(DestinationClient.All);
|
|
816
|
-
const id = network.getSequencedIdLog(Client.Client2)[0].id;
|
|
817
|
-
assert(isFinalId(id));
|
|
818
|
-
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
819
|
-
const emptyId = (id + 1);
|
|
820
|
-
assert.throws(() => network.getCompressor(Client.Client2).decompress(emptyId), (e) => e.message === "Unknown ID");
|
|
821
|
-
});
|
|
822
|
-
describe("Finalizing", () => {
|
|
823
|
-
itNetwork("can finalize IDs from multiple clients", (network) => {
|
|
824
|
-
network.allocateAndSendIds(Client.Client1, 3);
|
|
825
|
-
network.allocateAndSendIds(Client.Client2,
|
|
826
|
-
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
827
|
-
network.getCompressor(Client.Client2)["nextRequestedClusterSize"] * 2);
|
|
828
|
-
network.allocateAndSendIds(Client.Client3, 5);
|
|
829
|
-
expectSequencedLogsAlign(network, Client.Client1, Client.Client2);
|
|
830
|
-
});
|
|
831
|
-
itNetwork("can finalize a range when the current cluster is full", 5, (network) => {
|
|
832
|
-
const clusterCapacity = network.getCompressor(Client.Client1)["nextRequestedClusterSize"];
|
|
833
|
-
network.allocateAndSendIds(Client.Client1, clusterCapacity);
|
|
834
|
-
network.allocateAndSendIds(Client.Client2, clusterCapacity);
|
|
835
|
-
network.allocateAndSendIds(Client.Client1, clusterCapacity);
|
|
836
|
-
expectSequencedLogsAlign(network, Client.Client1, Client.Client2);
|
|
837
|
-
});
|
|
838
|
-
itNetwork("can finalize a range that spans multiple clusters", 5, (network) => {
|
|
839
|
-
const clusterCapacity = network.getCompressor(Client.Client1)["nextRequestedClusterSize"];
|
|
840
|
-
network.allocateAndSendIds(Client.Client1, 1);
|
|
841
|
-
network.allocateAndSendIds(Client.Client2, 1);
|
|
842
|
-
network.allocateAndSendIds(Client.Client1, clusterCapacity * 3);
|
|
843
|
-
expectSequencedLogsAlign(network, Client.Client1, Client.Client2);
|
|
844
|
-
});
|
|
845
|
-
});
|
|
846
|
-
describe("Serialization", () => {
|
|
847
|
-
itNetwork("prevents attempts to resume a session from a serialized compressor with no session", (network) => {
|
|
848
|
-
const compressor = network.getCompressor(Client.Client1);
|
|
849
|
-
network.allocateAndSendIds(Client.Client2, 1);
|
|
850
|
-
network.allocateAndSendIds(Client.Client3, 1);
|
|
851
|
-
network.deliverOperations(Client.Client1);
|
|
852
|
-
const serializedWithoutLocalState = compressor.serialize(false);
|
|
853
|
-
assert.throws(() => IdCompressor.deserialize(serializedWithoutLocalState, sessionIds.get(Client.Client2)), (e) => e.message === "Cannot resume existing session.");
|
|
854
|
-
});
|
|
855
|
-
itNetwork("round-trips local state", 3, (network) => {
|
|
856
|
-
network.allocateAndSendIds(Client.Client1, 2);
|
|
857
|
-
network.allocateAndSendIds(Client.Client2, 3);
|
|
858
|
-
network.allocateAndSendIds(Client.Client1, 5);
|
|
859
|
-
network.allocateAndSendIds(Client.Client1, 5);
|
|
860
|
-
network.allocateAndSendIds(Client.Client3, 3);
|
|
861
|
-
network.allocateAndSendIds(Client.Client2, 3);
|
|
862
|
-
network.deliverOperations(Client.Client1);
|
|
863
|
-
// Some un-acked locals at the end
|
|
864
|
-
network.allocateAndSendIds(Client.Client1, 4);
|
|
865
|
-
expectSerializes(network.getCompressor(Client.Client1));
|
|
866
|
-
});
|
|
867
|
-
itNetwork("can serialize a partially empty cluster", 5, (network) => {
|
|
868
|
-
network.allocateAndSendIds(Client.Client1, 2);
|
|
869
|
-
network.deliverOperations(DestinationClient.All);
|
|
870
|
-
expectSerializes(network.getCompressor(Client.Client1));
|
|
871
|
-
expectSerializes(network.getCompressor(Client.Client3));
|
|
872
|
-
});
|
|
873
|
-
itNetwork("can serialize a full cluster", 2, (network) => {
|
|
874
|
-
network.allocateAndSendIds(Client.Client1, 2);
|
|
875
|
-
network.deliverOperations(DestinationClient.All);
|
|
876
|
-
expectSerializes(network.getCompressor(Client.Client1));
|
|
877
|
-
expectSerializes(network.getCompressor(Client.Client3));
|
|
878
|
-
});
|
|
879
|
-
itNetwork("can serialize full clusters from different clients", 2, (network) => {
|
|
880
|
-
network.allocateAndSendIds(Client.Client1, 2);
|
|
881
|
-
network.allocateAndSendIds(Client.Client2, 2);
|
|
882
|
-
network.deliverOperations(DestinationClient.All);
|
|
883
|
-
expectSerializes(network.getCompressor(Client.Client1));
|
|
884
|
-
expectSerializes(network.getCompressor(Client.Client3));
|
|
885
|
-
});
|
|
886
|
-
itNetwork("can serialize clusters of different sizes and clients", 3, (network) => {
|
|
887
|
-
network.allocateAndSendIds(Client.Client1, 2);
|
|
888
|
-
network.allocateAndSendIds(Client.Client2, 3);
|
|
889
|
-
network.allocateAndSendIds(Client.Client1, 5);
|
|
890
|
-
network.allocateAndSendIds(Client.Client1, 5);
|
|
891
|
-
network.allocateAndSendIds(Client.Client2, 3);
|
|
892
|
-
network.deliverOperations(DestinationClient.All);
|
|
893
|
-
expectSerializes(network.getCompressor(Client.Client1));
|
|
894
|
-
expectSerializes(network.getCompressor(Client.Client3));
|
|
895
|
-
});
|
|
896
|
-
itNetwork("can resume a session and interact with multiple other clients", 3, (network) => {
|
|
897
|
-
const clusterSize = network.getCompressor(Client.Client1)["nextRequestedClusterSize"];
|
|
898
|
-
network.allocateAndSendIds(Client.Client1, clusterSize);
|
|
899
|
-
network.allocateAndSendIds(Client.Client2, clusterSize);
|
|
900
|
-
network.allocateAndSendIds(Client.Client3, clusterSize);
|
|
901
|
-
network.allocateAndSendIds(Client.Client1, clusterSize);
|
|
902
|
-
network.allocateAndSendIds(Client.Client2, clusterSize);
|
|
903
|
-
network.allocateAndSendIds(Client.Client3, clusterSize);
|
|
904
|
-
network.deliverOperations(DestinationClient.All);
|
|
905
|
-
network.goOfflineThenResume(Client.Client1);
|
|
906
|
-
network.allocateAndSendIds(Client.Client1, 2);
|
|
907
|
-
network.allocateAndSendIds(Client.Client2, 2);
|
|
908
|
-
network.allocateAndSendIds(Client.Client3, 2);
|
|
909
|
-
expectSequencedLogsAlign(network, Client.Client1, Client.Client2);
|
|
910
|
-
});
|
|
911
|
-
itNetwork("can serialize after a large fuzz input", 3, (network) => {
|
|
912
|
-
const generator = take(5000, makeOpGenerator({}));
|
|
913
|
-
performFuzzActions(generator, network, Math.PI, undefined, true, (n) => {
|
|
914
|
-
// Periodically check that everyone in the network has the same serialized state
|
|
915
|
-
n.deliverOperations(DestinationClient.All);
|
|
916
|
-
const compressors = n.getTargetCompressors(DestinationClient.All);
|
|
917
|
-
let deserializedPrev = roundtrip(compressors[0][1], false)[1];
|
|
918
|
-
for (let i = 1; i < compressors.length; i++) {
|
|
919
|
-
const deserializedCur = roundtrip(compressors[i][1], false)[1];
|
|
920
|
-
assert(deserializedPrev.equals(deserializedCur, false));
|
|
921
|
-
deserializedPrev = deserializedCur;
|
|
922
|
-
}
|
|
923
|
-
});
|
|
924
|
-
expectSerializes(network.getCompressor(Client.Client1));
|
|
925
|
-
expectSerializes(network.getCompressor(Client.Client2));
|
|
926
|
-
expectSerializes(network.getCompressor(Client.Client3));
|
|
927
|
-
});
|
|
928
|
-
});
|
|
929
|
-
});
|
|
930
|
-
});
|
|
931
|
-
function createNetworkTestFunction(validateAfter) {
|
|
932
|
-
return (title, testOrCapacity, test) => {
|
|
933
|
-
it(title, () => {
|
|
934
|
-
const hasCapacity = typeof testOrCapacity === "number";
|
|
935
|
-
const capacity = hasCapacity ? testOrCapacity : undefined;
|
|
936
|
-
const network = new IdCompressorTestNetwork(capacity);
|
|
937
|
-
(hasCapacity ? test ?? fail("test must be defined") : testOrCapacity)(network);
|
|
938
|
-
if (validateAfter) {
|
|
939
|
-
network.deliverOperations(DestinationClient.All);
|
|
940
|
-
network.assertNetworkState();
|
|
941
|
-
}
|
|
942
|
-
}).timeout(10000);
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
function describeNetwork(title, its) {
|
|
946
|
-
describe(title, () => {
|
|
947
|
-
its(createNetworkTestFunction(false));
|
|
948
|
-
});
|
|
949
|
-
describe(`${title} (with validation)`, () => {
|
|
950
|
-
its(createNetworkTestFunction(true));
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
//# sourceMappingURL=idCompressor.spec.js.map
|