@fluidframework/merge-tree 2.31.0 → 2.32.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.
Files changed (137) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/client.d.ts +7 -1
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +153 -44
  5. package/dist/client.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/mergeTree.d.ts +17 -5
  11. package/dist/mergeTree.d.ts.map +1 -1
  12. package/dist/mergeTree.js +188 -79
  13. package/dist/mergeTree.js.map +1 -1
  14. package/dist/mergeTreeNodes.d.ts +16 -18
  15. package/dist/mergeTreeNodes.d.ts.map +1 -1
  16. package/dist/mergeTreeNodes.js +6 -0
  17. package/dist/mergeTreeNodes.js.map +1 -1
  18. package/dist/perspective.d.ts +9 -0
  19. package/dist/perspective.d.ts.map +1 -1
  20. package/dist/perspective.js +14 -1
  21. package/dist/perspective.js.map +1 -1
  22. package/dist/segmentInfos.d.ts +32 -4
  23. package/dist/segmentInfos.d.ts.map +1 -1
  24. package/dist/segmentInfos.js +3 -1
  25. package/dist/segmentInfos.js.map +1 -1
  26. package/dist/sortedSegmentSet.d.ts +1 -0
  27. package/dist/sortedSegmentSet.d.ts.map +1 -1
  28. package/dist/sortedSegmentSet.js +3 -0
  29. package/dist/sortedSegmentSet.js.map +1 -1
  30. package/dist/test/beastTest.spec.js +5 -5
  31. package/dist/test/beastTest.spec.js.map +1 -1
  32. package/dist/test/client.localReference.spec.js +3 -3
  33. package/dist/test/client.localReference.spec.js.map +1 -1
  34. package/dist/test/client.rollback.spec.js +17 -0
  35. package/dist/test/client.rollback.spec.js.map +1 -1
  36. package/dist/test/clientTestHelper.d.ts +100 -0
  37. package/dist/test/clientTestHelper.d.ts.map +1 -0
  38. package/dist/test/clientTestHelper.js +196 -0
  39. package/dist/test/clientTestHelper.js.map +1 -0
  40. package/dist/test/mergeTree.annotate.spec.js +12 -12
  41. package/dist/test/mergeTree.annotate.spec.js.map +1 -1
  42. package/dist/test/mergeTree.markRangeRemoved.deltaCallback.spec.js +1 -1
  43. package/dist/test/mergeTree.markRangeRemoved.deltaCallback.spec.js.map +1 -1
  44. package/dist/test/obliterate.concurrent.spec.js +93 -90
  45. package/dist/test/obliterate.concurrent.spec.js.map +1 -1
  46. package/dist/test/obliterate.deltaCallback.spec.js +121 -116
  47. package/dist/test/obliterate.deltaCallback.spec.js.map +1 -1
  48. package/dist/test/obliterate.rangeExpansion.spec.js +29 -79
  49. package/dist/test/obliterate.rangeExpansion.spec.js.map +1 -1
  50. package/dist/test/obliterate.reconnect.spec.js +235 -58
  51. package/dist/test/obliterate.reconnect.spec.js.map +1 -1
  52. package/dist/test/testClient.js +1 -1
  53. package/dist/test/testClient.js.map +1 -1
  54. package/dist/test/testUtils.d.ts +13 -0
  55. package/dist/test/testUtils.d.ts.map +1 -1
  56. package/dist/test/testUtils.js +22 -1
  57. package/dist/test/testUtils.js.map +1 -1
  58. package/lib/client.d.ts +7 -1
  59. package/lib/client.d.ts.map +1 -1
  60. package/lib/client.js +155 -46
  61. package/lib/client.js.map +1 -1
  62. package/lib/index.d.ts +1 -1
  63. package/lib/index.d.ts.map +1 -1
  64. package/lib/index.js +1 -0
  65. package/lib/index.js.map +1 -1
  66. package/lib/mergeTree.d.ts +17 -5
  67. package/lib/mergeTree.d.ts.map +1 -1
  68. package/lib/mergeTree.js +192 -83
  69. package/lib/mergeTree.js.map +1 -1
  70. package/lib/mergeTreeNodes.d.ts +16 -18
  71. package/lib/mergeTreeNodes.d.ts.map +1 -1
  72. package/lib/mergeTreeNodes.js +7 -1
  73. package/lib/mergeTreeNodes.js.map +1 -1
  74. package/lib/perspective.d.ts +9 -0
  75. package/lib/perspective.d.ts.map +1 -1
  76. package/lib/perspective.js +12 -0
  77. package/lib/perspective.js.map +1 -1
  78. package/lib/segmentInfos.d.ts +32 -4
  79. package/lib/segmentInfos.d.ts.map +1 -1
  80. package/lib/segmentInfos.js +2 -1
  81. package/lib/segmentInfos.js.map +1 -1
  82. package/lib/sortedSegmentSet.d.ts +1 -0
  83. package/lib/sortedSegmentSet.d.ts.map +1 -1
  84. package/lib/sortedSegmentSet.js +3 -0
  85. package/lib/sortedSegmentSet.js.map +1 -1
  86. package/lib/test/beastTest.spec.js +5 -5
  87. package/lib/test/beastTest.spec.js.map +1 -1
  88. package/lib/test/client.localReference.spec.js +3 -3
  89. package/lib/test/client.localReference.spec.js.map +1 -1
  90. package/lib/test/client.rollback.spec.js +18 -1
  91. package/lib/test/client.rollback.spec.js.map +1 -1
  92. package/lib/test/clientTestHelper.d.ts +100 -0
  93. package/lib/test/clientTestHelper.d.ts.map +1 -0
  94. package/lib/test/clientTestHelper.js +192 -0
  95. package/lib/test/clientTestHelper.js.map +1 -0
  96. package/lib/test/mergeTree.annotate.spec.js +12 -12
  97. package/lib/test/mergeTree.annotate.spec.js.map +1 -1
  98. package/lib/test/mergeTree.markRangeRemoved.deltaCallback.spec.js +1 -1
  99. package/lib/test/mergeTree.markRangeRemoved.deltaCallback.spec.js.map +1 -1
  100. package/lib/test/obliterate.concurrent.spec.js +93 -90
  101. package/lib/test/obliterate.concurrent.spec.js.map +1 -1
  102. package/lib/test/obliterate.deltaCallback.spec.js +121 -116
  103. package/lib/test/obliterate.deltaCallback.spec.js.map +1 -1
  104. package/lib/test/obliterate.rangeExpansion.spec.js +1 -51
  105. package/lib/test/obliterate.rangeExpansion.spec.js.map +1 -1
  106. package/lib/test/obliterate.reconnect.spec.js +236 -59
  107. package/lib/test/obliterate.reconnect.spec.js.map +1 -1
  108. package/lib/test/testClient.js +1 -1
  109. package/lib/test/testClient.js.map +1 -1
  110. package/lib/test/testUtils.d.ts +13 -0
  111. package/lib/test/testUtils.d.ts.map +1 -1
  112. package/lib/test/testUtils.js +20 -0
  113. package/lib/test/testUtils.js.map +1 -1
  114. package/package.json +19 -18
  115. package/src/client.ts +286 -55
  116. package/src/index.ts +1 -1
  117. package/src/mergeTree.ts +265 -98
  118. package/src/mergeTreeNodes.ts +24 -18
  119. package/src/perspective.ts +21 -0
  120. package/src/segmentInfos.ts +48 -6
  121. package/src/sortedSegmentSet.ts +4 -0
  122. package/dist/test/partialSyncHelper.d.ts +0 -42
  123. package/dist/test/partialSyncHelper.d.ts.map +0 -1
  124. package/dist/test/partialSyncHelper.js +0 -96
  125. package/dist/test/partialSyncHelper.js.map +0 -1
  126. package/dist/test/reconnectHelper.d.ts +0 -50
  127. package/dist/test/reconnectHelper.d.ts.map +0 -1
  128. package/dist/test/reconnectHelper.js +0 -106
  129. package/dist/test/reconnectHelper.js.map +0 -1
  130. package/lib/test/partialSyncHelper.d.ts +0 -42
  131. package/lib/test/partialSyncHelper.d.ts.map +0 -1
  132. package/lib/test/partialSyncHelper.js +0 -92
  133. package/lib/test/partialSyncHelper.js.map +0 -1
  134. package/lib/test/reconnectHelper.d.ts +0 -50
  135. package/lib/test/reconnectHelper.d.ts.map +0 -1
  136. package/lib/test/reconnectHelper.js +0 -102
  137. package/lib/test/reconnectHelper.js.map +0 -1
@@ -0,0 +1,100 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
6
+ import type { IMergeTreeOptions, InteriorSequencePlace } from "../index.js";
7
+ import type { TestClient } from "./testClient.js";
8
+ import { TestClientLogger } from "./testClientLogger.js";
9
+ declare const ClientIds: readonly ["A", "B", "C", "D"];
10
+ type ClientName = (typeof ClientIds)[number];
11
+ /**
12
+ * Helper for authoring tests which perform operations on a number of clients.
13
+ * This class essentially serves the role of the server in that it maintains a sequencing order of ops as well as information
14
+ * about the latest op that each client has applied.
15
+ *
16
+ * When using this class, *do not* perform operations which may submit ops on clients directly: it does not currently wire up client events
17
+ * to the server. Instead, use methods on the helper and pass the client name they should apply to as the first argument.
18
+ *
19
+ * It is analogous to MockContainerRuntimeFactory in the test-runtime-utils package, though the APIs are not completely equivalent
20
+ * (see for example differences noted on disconnect and reconnect).
21
+ *
22
+ * This helper is also designed to support advancing only some clients to a given sequence number
23
+ * (not all clients must be synchronized at the same time).
24
+ *
25
+ * This allows testing sequences of operations where clients have varying refSeqs, rather than having all clients advance refSeq
26
+ * in lockstep.
27
+ *
28
+ * @remarks
29
+ * If we wired the server up to "delta" events on the client, it would be reasonable to rename this to `MockServer` and the API for tests
30
+ * would be a bit cleaner.
31
+ */
32
+ export declare class ClientTestHelper {
33
+ clients: Record<ClientName, TestClient> & {
34
+ all: TestClient[];
35
+ };
36
+ idxFromName(name: ClientName): number;
37
+ logger: TestClientLogger;
38
+ ops: ISequencedDocumentMessage[];
39
+ clientToLastAppliedSeq: Map<"A" | "B" | "C" | "D", number>;
40
+ perClientOps: ISequencedDocumentMessage[][];
41
+ private readonly disconnectedClientOps;
42
+ private seq;
43
+ constructor(options?: IMergeTreeOptions);
44
+ private addMessage;
45
+ insertText(clientName: ClientName, pos: number, text: string): void;
46
+ removeRange(clientName: ClientName, start: number, end: number): void;
47
+ obliterateRange(clientName: ClientName, start: number | InteriorSequencePlace, end: number | InteriorSequencePlace): void;
48
+ advanceClientToSeq(clientName: ClientName, seq: number): void;
49
+ /**
50
+ * Sends all known ops to the procieded client ids.
51
+ */
52
+ advanceClients(...clientNames: ClientName[]): void;
53
+ processAllOps(): void;
54
+ /**
55
+ * Causes the provided clients to "disconnect" by isolating their outbound queue from the op stream.
56
+ * These clients will still receive ops from this helper just like all other clients.
57
+ * When a client reconnects (see {@link reconnect}), the helper will rebase all of the client's pending ops (that they submitted since
58
+ * `disconnect` was called) and re-add them to the op stream.
59
+ *
60
+ * BEWARE: This is slightly different from what the mocks in test-runtime-utils do in two ways:
61
+ *
62
+ * 1. Those mocks actually wipe outstanding ops submitted by the disconnected clients from the op stream and rebases those as well.
63
+ * 2. Those mocks freeze the inbound queue of the disconnected clients as well as the outbound queue.
64
+ *
65
+ * E.g. (pseudocode; syntax is different for those mocks and these):
66
+ *
67
+ * ```typescript
68
+ * B.submitLocalOp1()
69
+ * B.submitLocalOp2()
70
+ * disconnect(B)
71
+ * A.submitLocalOp1()
72
+ * B.submitLocalOp3()
73
+ * processAllOps()
74
+ * ```
75
+ *
76
+ * the test-runtime-mocks will...
77
+ *
78
+ * - have none of B's local ops be receieved by A in the processAllOps() line
79
+ * - have B not receive A's local op 1
80
+ *
81
+ * whereas this helper will...
82
+ *
83
+ * - have B's local ops 1 and 2 be received by A, but not its third local op
84
+ * - have B receive acks for its first two local ops as well as A's op in the processAllOps() line
85
+ *
86
+ * This operation is a no-op if called multiple times on the same client with no intervening reconnects.
87
+ */
88
+ disconnect(...clientNames: ClientName[]): void;
89
+ /**
90
+ * Reconnects clients which have been disconnected. No-ops on any client which is already connected.
91
+ * For disconnected clients, any pending ops they have will be resubmitted using the resubmit flow, see {@link disconnect} for details and an example.
92
+ *
93
+ * @remarks
94
+ * If reconnecting multiple clients, the order that their ops will be resubmitted in will match the order in the array
95
+ */
96
+ reconnect(...clientNames: ClientName[]): void;
97
+ private isDisconnected;
98
+ }
99
+ export {};
100
+ //# sourceMappingURL=clientTestHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientTestHelper.d.ts","sourceRoot":"","sources":["../../src/test/clientTestHelper.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAExF,OAAO,KAAK,EAAgB,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAG1F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAA+B,MAAM,uBAAuB,CAAC;AAEtF,QAAA,MAAM,SAAS,+BAAgC,CAAC;AAChD,KAAK,UAAU,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAY7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,GAAG;QAAE,GAAG,EAAE,UAAU,EAAE,CAAA;KAAE,CAAC;IAEhE,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM;IAIrC,MAAM,EAAE,gBAAgB,CAAC;IAEzB,GAAG,EAAE,yBAAyB,EAAE,CAAM;IACtC,sBAAsB,qCAAiC;IAEvD,YAAY,EAAE,yBAAyB,EAAE,EAAE,CAAC;IAC5C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAGlC;IAEJ,OAAO,CAAC,GAAG,CAAa;gBAEL,OAAO,GAAE,iBAAsB;IAgBlD,OAAO,CAAC,UAAU;IAmBX,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQnE,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAKrE,eAAe,CACrB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,MAAM,GAAG,qBAAqB,EACrC,GAAG,EAAE,MAAM,GAAG,qBAAqB,GACjC,IAAI;IAKA,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAwBpE;;OAEG;IACI,cAAc,CAAC,GAAG,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI;IAOlD,aAAa,IAAI,IAAI;IAO5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACI,UAAU,CAAC,GAAG,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI;IAUrD;;;;;;OAMG;IACI,SAAS,CAAC,GAAG,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI;IAepD,OAAO,CAAC,cAAc;CAGtB"}
@@ -0,0 +1,192 @@
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 { TestClientLogger, createClientsAtInitialState } from "./testClientLogger.js";
7
+ const ClientIds = ["A", "B", "C", "D"];
8
+ function isClientId(s) {
9
+ return ClientIds.includes(s);
10
+ }
11
+ function clientNameOf(client) {
12
+ const { longClientId } = client;
13
+ assert(isClientId(longClientId), "Client ID is not a valid client name");
14
+ return longClientId;
15
+ }
16
+ /**
17
+ * Helper for authoring tests which perform operations on a number of clients.
18
+ * This class essentially serves the role of the server in that it maintains a sequencing order of ops as well as information
19
+ * about the latest op that each client has applied.
20
+ *
21
+ * When using this class, *do not* perform operations which may submit ops on clients directly: it does not currently wire up client events
22
+ * to the server. Instead, use methods on the helper and pass the client name they should apply to as the first argument.
23
+ *
24
+ * It is analogous to MockContainerRuntimeFactory in the test-runtime-utils package, though the APIs are not completely equivalent
25
+ * (see for example differences noted on disconnect and reconnect).
26
+ *
27
+ * This helper is also designed to support advancing only some clients to a given sequence number
28
+ * (not all clients must be synchronized at the same time).
29
+ *
30
+ * This allows testing sequences of operations where clients have varying refSeqs, rather than having all clients advance refSeq
31
+ * in lockstep.
32
+ *
33
+ * @remarks
34
+ * If we wired the server up to "delta" events on the client, it would be reasonable to rename this to `MockServer` and the API for tests
35
+ * would be a bit cleaner.
36
+ */
37
+ export class ClientTestHelper {
38
+ idxFromName(name) {
39
+ return (name.codePointAt(0) ?? 0) - ("A".codePointAt(0) ?? 0);
40
+ }
41
+ constructor(options = {}) {
42
+ this.ops = [];
43
+ this.clientToLastAppliedSeq = new Map();
44
+ this.disconnectedClientOps = new Map();
45
+ this.seq = 0;
46
+ this.clients = createClientsAtInitialState({
47
+ initialState: "",
48
+ options: {
49
+ mergeTreeEnableObliterate: true,
50
+ mergeTreeEnableObliterateReconnect: true,
51
+ ...options,
52
+ },
53
+ }, ...ClientIds);
54
+ this.logger = new TestClientLogger(this.clients.all);
55
+ this.perClientOps = this.clients.all.map(() => []);
56
+ }
57
+ addMessage(client, op) {
58
+ const disconnectedQueue = this.disconnectedClientOps.get(clientNameOf(client));
59
+ if (disconnectedQueue === undefined) {
60
+ const message = client.makeOpMessage(op, ++this.seq);
61
+ this.ops.push(message);
62
+ // This implementation (specifically, that of applying ops / synchronizing clients) assumes messages
63
+ // are pushed sequentially starting with seq 1.
64
+ assert(message.sequenceNumber === this.ops.length, "Partial sync test helper invariant violated");
65
+ }
66
+ else {
67
+ // Client is not currently connected.
68
+ const segmentGroup = client.peekPendingSegmentGroups();
69
+ assert(segmentGroup !== undefined, "Client should have a pending segment group");
70
+ disconnectedQueue.push({ op, segmentGroup });
71
+ }
72
+ }
73
+ insertText(clientName, pos, text) {
74
+ const client = this.clients[clientName];
75
+ const insertOp = client.insertTextLocal(pos, text);
76
+ if (insertOp) {
77
+ this.addMessage(client, insertOp);
78
+ }
79
+ }
80
+ removeRange(clientName, start, end) {
81
+ const client = this.clients[clientName];
82
+ this.addMessage(client, client.removeRangeLocal(start, end));
83
+ }
84
+ obliterateRange(clientName, start, end) {
85
+ const client = this.clients[clientName];
86
+ this.addMessage(client, client.obliterateRangeLocal(start, end));
87
+ }
88
+ advanceClientToSeq(clientName, seq) {
89
+ const client = this.clients[clientName];
90
+ const lastApplied = this.clientToLastAppliedSeq.get(clientName);
91
+ assert(seq > 0, "Can only advance clients to sequence numbers that exist");
92
+ assert(this.ops.length >= seq, "Cannot attempt to advance clients to sequence numbers that don't yet exist");
93
+ let startIndex;
94
+ if (lastApplied === undefined) {
95
+ startIndex = 0;
96
+ }
97
+ else {
98
+ if (lastApplied >= seq) {
99
+ return;
100
+ }
101
+ startIndex = lastApplied;
102
+ }
103
+ for (let i = startIndex; i < seq; i++) {
104
+ const nextMessage = this.ops[i];
105
+ client.applyMsg(nextMessage);
106
+ this.clientToLastAppliedSeq.set(clientName, nextMessage.sequenceNumber);
107
+ }
108
+ }
109
+ /**
110
+ * Sends all known ops to the procieded client ids.
111
+ */
112
+ advanceClients(...clientNames) {
113
+ const latestSeq = this.ops[this.ops.length - 1].sequenceNumber;
114
+ for (const name of clientNames) {
115
+ this.advanceClientToSeq(name, latestSeq);
116
+ }
117
+ }
118
+ processAllOps() {
119
+ const latestSeq = this.ops[this.ops.length - 1].sequenceNumber;
120
+ for (const name of ClientIds) {
121
+ this.advanceClientToSeq(name, latestSeq);
122
+ }
123
+ }
124
+ /**
125
+ * Causes the provided clients to "disconnect" by isolating their outbound queue from the op stream.
126
+ * These clients will still receive ops from this helper just like all other clients.
127
+ * When a client reconnects (see {@link reconnect}), the helper will rebase all of the client's pending ops (that they submitted since
128
+ * `disconnect` was called) and re-add them to the op stream.
129
+ *
130
+ * BEWARE: This is slightly different from what the mocks in test-runtime-utils do in two ways:
131
+ *
132
+ * 1. Those mocks actually wipe outstanding ops submitted by the disconnected clients from the op stream and rebases those as well.
133
+ * 2. Those mocks freeze the inbound queue of the disconnected clients as well as the outbound queue.
134
+ *
135
+ * E.g. (pseudocode; syntax is different for those mocks and these):
136
+ *
137
+ * ```typescript
138
+ * B.submitLocalOp1()
139
+ * B.submitLocalOp2()
140
+ * disconnect(B)
141
+ * A.submitLocalOp1()
142
+ * B.submitLocalOp3()
143
+ * processAllOps()
144
+ * ```
145
+ *
146
+ * the test-runtime-mocks will...
147
+ *
148
+ * - have none of B's local ops be receieved by A in the processAllOps() line
149
+ * - have B not receive A's local op 1
150
+ *
151
+ * whereas this helper will...
152
+ *
153
+ * - have B's local ops 1 and 2 be received by A, but not its third local op
154
+ * - have B receive acks for its first two local ops as well as A's op in the processAllOps() line
155
+ *
156
+ * This operation is a no-op if called multiple times on the same client with no intervening reconnects.
157
+ */
158
+ disconnect(...clientNames) {
159
+ for (const clientName of clientNames) {
160
+ const client = this.clients[clientName];
161
+ if (this.isDisconnected(client)) {
162
+ continue;
163
+ }
164
+ this.disconnectedClientOps.set(clientName, []);
165
+ }
166
+ }
167
+ /**
168
+ * Reconnects clients which have been disconnected. No-ops on any client which is already connected.
169
+ * For disconnected clients, any pending ops they have will be resubmitted using the resubmit flow, see {@link disconnect} for details and an example.
170
+ *
171
+ * @remarks
172
+ * If reconnecting multiple clients, the order that their ops will be resubmitted in will match the order in the array
173
+ */
174
+ reconnect(...clientNames) {
175
+ for (const clientName of clientNames) {
176
+ const submittedOps = this.disconnectedClientOps.get(clientName);
177
+ if (submittedOps === undefined) {
178
+ continue;
179
+ }
180
+ this.disconnectedClientOps.delete(clientName);
181
+ const client = this.clients[clientName];
182
+ for (const { op, segmentGroup } of submittedOps) {
183
+ const rebasedOp = client.regeneratePendingOp(op, segmentGroup);
184
+ this.addMessage(client, rebasedOp);
185
+ }
186
+ }
187
+ }
188
+ isDisconnected(client) {
189
+ return this.disconnectedClientOps.has(clientNameOf(client));
190
+ }
191
+ }
192
+ //# sourceMappingURL=clientTestHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientTestHelper.js","sourceRoot":"","sources":["../../src/test/clientTestHelper.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 { strict as assert } from \"node:assert\";\n\nimport { ISequencedDocumentMessage } from \"@fluidframework/driver-definitions/internal\";\n\nimport type { IMergeTreeOp, IMergeTreeOptions, InteriorSequencePlace } from \"../index.js\";\nimport type { SegmentGroup } from \"../mergeTreeNodes.js\";\n\nimport type { TestClient } from \"./testClient.js\";\nimport { TestClientLogger, createClientsAtInitialState } from \"./testClientLogger.js\";\n\nconst ClientIds = [\"A\", \"B\", \"C\", \"D\"] as const;\ntype ClientName = (typeof ClientIds)[number];\n\nfunction isClientId(s: unknown): s is ClientName {\n\treturn ClientIds.includes(s as ClientName);\n}\n\nfunction clientNameOf(client: TestClient): ClientName {\n\tconst { longClientId } = client;\n\tassert(isClientId(longClientId), \"Client ID is not a valid client name\");\n\treturn longClientId;\n}\n\n/**\n * Helper for authoring tests which perform operations on a number of clients.\n * This class essentially serves the role of the server in that it maintains a sequencing order of ops as well as information\n * about the latest op that each client has applied.\n *\n * When using this class, *do not* perform operations which may submit ops on clients directly: it does not currently wire up client events\n * to the server. Instead, use methods on the helper and pass the client name they should apply to as the first argument.\n *\n * It is analogous to MockContainerRuntimeFactory in the test-runtime-utils package, though the APIs are not completely equivalent\n * (see for example differences noted on disconnect and reconnect).\n *\n * This helper is also designed to support advancing only some clients to a given sequence number\n * (not all clients must be synchronized at the same time).\n *\n * This allows testing sequences of operations where clients have varying refSeqs, rather than having all clients advance refSeq\n * in lockstep.\n *\n * @remarks\n * If we wired the server up to \"delta\" events on the client, it would be reasonable to rename this to `MockServer` and the API for tests\n * would be a bit cleaner.\n */\nexport class ClientTestHelper {\n\tclients: Record<ClientName, TestClient> & { all: TestClient[] };\n\n\tidxFromName(name: ClientName): number {\n\t\treturn (name.codePointAt(0) ?? 0) - (\"A\".codePointAt(0) ?? 0);\n\t}\n\n\tlogger: TestClientLogger;\n\n\tops: ISequencedDocumentMessage[] = [];\n\tclientToLastAppliedSeq = new Map<ClientName, number>();\n\n\tperClientOps: ISequencedDocumentMessage[][];\n\tprivate readonly disconnectedClientOps = new Map<\n\t\tClientName,\n\t\t{ op: IMergeTreeOp; segmentGroup: SegmentGroup }[]\n\t>();\n\n\tprivate seq: number = 0;\n\n\tpublic constructor(options: IMergeTreeOptions = {}) {\n\t\tthis.clients = createClientsAtInitialState(\n\t\t\t{\n\t\t\t\tinitialState: \"\",\n\t\t\t\toptions: {\n\t\t\t\t\tmergeTreeEnableObliterate: true,\n\t\t\t\t\tmergeTreeEnableObliterateReconnect: true,\n\t\t\t\t\t...options,\n\t\t\t\t},\n\t\t\t},\n\t\t\t...ClientIds,\n\t\t);\n\t\tthis.logger = new TestClientLogger(this.clients.all);\n\t\tthis.perClientOps = this.clients.all.map(() => []);\n\t}\n\n\tprivate addMessage(client: TestClient, op: IMergeTreeOp): void {\n\t\tconst disconnectedQueue = this.disconnectedClientOps.get(clientNameOf(client));\n\t\tif (disconnectedQueue === undefined) {\n\t\t\tconst message = client.makeOpMessage(op, ++this.seq);\n\t\t\tthis.ops.push(message);\n\t\t\t// This implementation (specifically, that of applying ops / synchronizing clients) assumes messages\n\t\t\t// are pushed sequentially starting with seq 1.\n\t\t\tassert(\n\t\t\t\tmessage.sequenceNumber === this.ops.length,\n\t\t\t\t\"Partial sync test helper invariant violated\",\n\t\t\t);\n\t\t} else {\n\t\t\t// Client is not currently connected.\n\t\t\tconst segmentGroup = client.peekPendingSegmentGroups();\n\t\t\tassert(segmentGroup !== undefined, \"Client should have a pending segment group\");\n\t\t\tdisconnectedQueue.push({ op, segmentGroup });\n\t\t}\n\t}\n\n\tpublic insertText(clientName: ClientName, pos: number, text: string): void {\n\t\tconst client = this.clients[clientName];\n\t\tconst insertOp = client.insertTextLocal(pos, text);\n\t\tif (insertOp) {\n\t\t\tthis.addMessage(client, insertOp);\n\t\t}\n\t}\n\n\tpublic removeRange(clientName: ClientName, start: number, end: number): void {\n\t\tconst client = this.clients[clientName];\n\t\tthis.addMessage(client, client.removeRangeLocal(start, end));\n\t}\n\n\tpublic obliterateRange(\n\t\tclientName: ClientName,\n\t\tstart: number | InteriorSequencePlace,\n\t\tend: number | InteriorSequencePlace,\n\t): void {\n\t\tconst client = this.clients[clientName];\n\t\tthis.addMessage(client, client.obliterateRangeLocal(start, end));\n\t}\n\n\tpublic advanceClientToSeq(clientName: ClientName, seq: number): void {\n\t\tconst client = this.clients[clientName];\n\t\tconst lastApplied = this.clientToLastAppliedSeq.get(clientName);\n\t\tassert(seq > 0, \"Can only advance clients to sequence numbers that exist\");\n\t\tassert(\n\t\t\tthis.ops.length >= seq,\n\t\t\t\"Cannot attempt to advance clients to sequence numbers that don't yet exist\",\n\t\t);\n\t\tlet startIndex: number;\n\t\tif (lastApplied === undefined) {\n\t\t\tstartIndex = 0;\n\t\t} else {\n\t\t\tif (lastApplied >= seq) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstartIndex = lastApplied;\n\t\t}\n\t\tfor (let i = startIndex; i < seq; i++) {\n\t\t\tconst nextMessage = this.ops[i];\n\t\t\tclient.applyMsg(nextMessage);\n\t\t\tthis.clientToLastAppliedSeq.set(clientName, nextMessage.sequenceNumber);\n\t\t}\n\t}\n\n\t/**\n\t * Sends all known ops to the procieded client ids.\n\t */\n\tpublic advanceClients(...clientNames: ClientName[]): void {\n\t\tconst latestSeq = this.ops[this.ops.length - 1].sequenceNumber;\n\t\tfor (const name of clientNames) {\n\t\t\tthis.advanceClientToSeq(name, latestSeq);\n\t\t}\n\t}\n\n\tpublic processAllOps(): void {\n\t\tconst latestSeq = this.ops[this.ops.length - 1].sequenceNumber;\n\t\tfor (const name of ClientIds) {\n\t\t\tthis.advanceClientToSeq(name, latestSeq);\n\t\t}\n\t}\n\n\t/**\n\t * Causes the provided clients to \"disconnect\" by isolating their outbound queue from the op stream.\n\t * These clients will still receive ops from this helper just like all other clients.\n\t * When a client reconnects (see {@link reconnect}), the helper will rebase all of the client's pending ops (that they submitted since\n\t * `disconnect` was called) and re-add them to the op stream.\n\t *\n\t * BEWARE: This is slightly different from what the mocks in test-runtime-utils do in two ways:\n\t *\n\t * 1. Those mocks actually wipe outstanding ops submitted by the disconnected clients from the op stream and rebases those as well.\n\t * 2. Those mocks freeze the inbound queue of the disconnected clients as well as the outbound queue.\n\t *\n\t * E.g. (pseudocode; syntax is different for those mocks and these):\n\t *\n\t * ```typescript\n\t * B.submitLocalOp1()\n\t * B.submitLocalOp2()\n\t * disconnect(B)\n\t * A.submitLocalOp1()\n\t * B.submitLocalOp3()\n\t * processAllOps()\n\t * ```\n\t *\n\t * the test-runtime-mocks will...\n\t *\n\t * - have none of B's local ops be receieved by A in the processAllOps() line\n\t * - have B not receive A's local op 1\n\t *\n\t * whereas this helper will...\n\t *\n\t * - have B's local ops 1 and 2 be received by A, but not its third local op\n\t * - have B receive acks for its first two local ops as well as A's op in the processAllOps() line\n\t *\n\t * This operation is a no-op if called multiple times on the same client with no intervening reconnects.\n\t */\n\tpublic disconnect(...clientNames: ClientName[]): void {\n\t\tfor (const clientName of clientNames) {\n\t\t\tconst client = this.clients[clientName];\n\t\t\tif (this.isDisconnected(client)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.disconnectedClientOps.set(clientName, []);\n\t\t}\n\t}\n\n\t/**\n\t * Reconnects clients which have been disconnected. No-ops on any client which is already connected.\n\t * For disconnected clients, any pending ops they have will be resubmitted using the resubmit flow, see {@link disconnect} for details and an example.\n\t *\n\t * @remarks\n\t * If reconnecting multiple clients, the order that their ops will be resubmitted in will match the order in the array\n\t */\n\tpublic reconnect(...clientNames: ClientName[]): void {\n\t\tfor (const clientName of clientNames) {\n\t\t\tconst submittedOps = this.disconnectedClientOps.get(clientName);\n\t\t\tif (submittedOps === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.disconnectedClientOps.delete(clientName);\n\t\t\tconst client = this.clients[clientName];\n\t\t\tfor (const { op, segmentGroup } of submittedOps) {\n\t\t\t\tconst rebasedOp = client.regeneratePendingOp(op, segmentGroup);\n\t\t\t\tthis.addMessage(client, rebasedOp);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate isDisconnected(client: TestClient): boolean {\n\t\treturn this.disconnectedClientOps.has(clientNameOf(client));\n\t}\n}\n"]}
@@ -111,7 +111,7 @@ describe("MergeTree", () => {
111
111
  assert.equal(splitSegment.properties?.propertySource, "local");
112
112
  assert.equal(splitSegment.properties?.secondChange, 1);
113
113
  assert.equal(splitSegment.properties?.splitOnly, 1);
114
- mergeTree.ackPendingSegment({
114
+ mergeTree.ackOp({
115
115
  op: {
116
116
  pos1: annotateStart,
117
117
  pos2: annotateEnd,
@@ -130,7 +130,7 @@ describe("MergeTree", () => {
130
130
  assert.equal(splitSegment.properties?.propertySource, "local");
131
131
  assert.equal(splitSegment.properties?.secondChange, 1);
132
132
  assert.equal(splitSegment.properties?.splitOnly, 1);
133
- mergeTree.ackPendingSegment({
133
+ mergeTree.ackOp({
134
134
  op: {
135
135
  pos1: annotateStart,
136
136
  pos2: annotateEnd,
@@ -149,7 +149,7 @@ describe("MergeTree", () => {
149
149
  assert.equal(splitSegment.properties?.propertySource, "local");
150
150
  assert.equal(splitSegment.properties?.secondChange, 1);
151
151
  assert.equal(splitSegment.properties?.splitOnly, 1);
152
- mergeTree.ackPendingSegment({
152
+ mergeTree.ackOp({
153
153
  op: {
154
154
  pos1: splitPos,
155
155
  pos2: annotateEnd,
@@ -180,7 +180,7 @@ describe("MergeTree", () => {
180
180
  assert.equal(segment.properties?.remoteProperty, 1);
181
181
  });
182
182
  it("sequenced local", () => {
183
- mergeTree.ackPendingSegment({
183
+ mergeTree.ackOp({
184
184
  op: {
185
185
  pos1: annotateStart,
186
186
  pos2: annotateEnd,
@@ -197,7 +197,7 @@ describe("MergeTree", () => {
197
197
  assert.equal(segment.properties?.propertySource, "local");
198
198
  });
199
199
  it("sequenced local before remote", () => {
200
- mergeTree.ackPendingSegment({
200
+ mergeTree.ackOp({
201
201
  op: {
202
202
  pos1: annotateStart,
203
203
  pos2: annotateEnd,
@@ -236,7 +236,7 @@ describe("MergeTree", () => {
236
236
  assert.equal(segment.properties?.propertySource, "local2");
237
237
  assert.equal(segment.properties?.secondSource, 1);
238
238
  assert.equal(segment.properties?.thirdSource, 1);
239
- mergeTree.ackPendingSegment({
239
+ mergeTree.ackOp({
240
240
  op: {
241
241
  pos1: annotateStart,
242
242
  pos2: annotateEnd,
@@ -250,7 +250,7 @@ describe("MergeTree", () => {
250
250
  assert.equal(segment.properties?.propertySource, "local2");
251
251
  assert.equal(segment.properties?.secondSource, 1);
252
252
  assert.equal(segment.properties?.thirdSource, 1);
253
- mergeTree.ackPendingSegment({
253
+ mergeTree.ackOp({
254
254
  op: {
255
255
  pos1: annotateStart,
256
256
  pos2: annotateEnd,
@@ -264,7 +264,7 @@ describe("MergeTree", () => {
264
264
  assert.equal(segment.properties?.propertySource, "local2");
265
265
  assert.equal(segment.properties?.secondSource, 1);
266
266
  assert.equal(segment.properties?.thirdSource, 1);
267
- mergeTree.ackPendingSegment({
267
+ mergeTree.ackOp({
268
268
  op: {
269
269
  pos1: annotateStart,
270
270
  pos2: annotateEnd,
@@ -283,7 +283,7 @@ describe("MergeTree", () => {
283
283
  mergeTree.annotateRange(annotateStart, annotateEnd, {
284
284
  props: { secondSource: "local2" },
285
285
  }, mergeTree.localPerspective, mergeTree.collabWindow.mintNextLocalOperationStamp(), undefined);
286
- mergeTree.ackPendingSegment({
286
+ mergeTree.ackOp({
287
287
  op: {
288
288
  pos1: annotateStart,
289
289
  pos2: annotateEnd,
@@ -344,7 +344,7 @@ describe("MergeTree", () => {
344
344
  assert(segmentInfo.segment?.segmentGroups?.empty !== false);
345
345
  mergeTree.annotateRange(annotateStart, annotateEnd, props, mergeTree.localPerspective, mergeTree.collabWindow.mintNextLocalOperationStamp(), undefined);
346
346
  assert.equal(segmentInfo.segment?.segmentGroups?.size, 1);
347
- mergeTree.ackPendingSegment({
347
+ mergeTree.ackOp({
348
348
  op: {
349
349
  pos1: annotateStart,
350
350
  pos2: annotateEnd,
@@ -387,7 +387,7 @@ describe("MergeTree", () => {
387
387
  assert.equal(segment.properties?.remoteProperty, 1);
388
388
  });
389
389
  it("sequenced local before remote", () => {
390
- mergeTree.ackPendingSegment({
390
+ mergeTree.ackOp({
391
391
  op: {
392
392
  pos1: annotateStart,
393
393
  pos2: annotateEnd,
@@ -411,7 +411,7 @@ describe("MergeTree", () => {
411
411
  mergeTree.annotateRange(annotateStart, annotateEnd, {
412
412
  props: { secondSource: "local2" },
413
413
  }, mergeTree.localPerspective, mergeTree.collabWindow.mintNextLocalOperationStamp(), undefined);
414
- mergeTree.ackPendingSegment({
414
+ mergeTree.ackOp({
415
415
  op: {
416
416
  pos1: annotateStart,
417
417
  pos2: annotateEnd,