From 1f139118b20a5c7889ea9658b87ba3fe5684aaf1 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Fri, 21 Apr 2023 15:32:21 -0700 Subject: [PATCH] Tag asserts (#15203) ## Description `pnpm exec flub release -g client` and `pnpm run prettier:fix` Includes fixes for experimental/dds/tree so its tests for asserts keep working after the tagging. --- api-report/test-runtime-utils.api.md | 2 +- .../partial-checkout/src/demo/squaresApp.ts | 2 +- .../dds/attributable-map/src/mapKernel.ts | 23 ++-- experimental/dds/ot/ot/src/ot.ts | 5 +- .../sequence-deprecated/src/sparsematrix.ts | 2 +- .../src/test/SharedTreeQuerier.tests.ts | 10 +- .../tree-graphql/src/test/TestUtilities.ts | 2 +- experimental/dds/tree/src/ChangeTypes.ts | 4 +- experimental/dds/tree/src/Checkout.ts | 27 ++-- experimental/dds/tree/src/EditLog.ts | 12 +- experimental/dds/tree/src/Forest.ts | 44 +++--- .../dds/tree/src/HistoryEditFactory.ts | 10 +- experimental/dds/tree/src/LogViewer.ts | 8 +- .../dds/tree/src/RevisionValueCache.ts | 2 +- experimental/dds/tree/src/SharedTree.ts | 15 +- .../dds/tree/src/SharedTreeEncoder.ts | 10 +- .../dds/tree/src/TransactionInternal.ts | 8 +- experimental/dds/tree/src/TreeCompressor.ts | 2 +- experimental/dds/tree/src/TreeView.ts | 2 +- .../src/id-compressor/AppendOnlySortedMap.ts | 4 +- .../tree/src/id-compressor/IdCompressor.ts | 50 ++++--- .../dds/tree/src/id-compressor/IdRange.ts | 2 +- .../src/id-compressor/SessionIdNormalizer.ts | 17 ++- .../dds/tree/src/persisted-types/0.1.1.ts | 4 +- .../src/test/AppendOnlySortedMap.tests.ts | 21 ++- .../dds/tree/src/test/Checkout.tests.ts | 45 ++++-- .../dds/tree/src/test/EditLog.tests.ts | 29 ++-- .../dds/tree/src/test/EditUtilities.tests.ts | 6 +- .../dds/tree/src/test/Forest.tests.ts | 46 ++++--- .../dds/tree/src/test/IdCompressor.tests.ts | 98 +++++++++---- .../dds/tree/src/test/LogViewer.tests.ts | 31 +++-- .../dds/tree/src/test/NumericUuid.tests.ts | 39 ++++-- .../tree/src/test/RevisionValueCache.tests.ts | 16 ++- .../src/test/SessionIdNormalizer.tests.ts | 66 ++++++--- .../dds/tree/src/test/StringInterner.tests.ts | 7 +- .../dds/tree/src/test/TreeView.tests.ts | 7 +- .../utilities/IdCompressorTestUtilities.ts | 9 +- .../src/test/utilities/SharedTreeTests.ts | 12 +- .../utilities/SharedTreeVersioningTests.ts | 13 +- .../tree/src/test/utilities/TestUtilities.ts | 4 +- .../sequence-field/invert.ts | 2 +- .../dds/tree2/src/shared-tree-core/branch.ts | 2 +- .../src/shared-tree-core/sharedTreeCore.ts | 2 +- .../src/assertionShortCodesMap.ts | 129 +++++++++++++++++- .../src/validateAssertionError.ts | 8 +- 45 files changed, 584 insertions(+), 275 deletions(-) diff --git a/api-report/test-runtime-utils.api.md b/api-report/test-runtime-utils.api.md index 96940b57ebf7..1e96736965ce 100644 --- a/api-report/test-runtime-utils.api.md +++ b/api-report/test-runtime-utils.api.md @@ -591,7 +591,7 @@ export class MockStorage implements IChannelStorageService { } // @public -export function validateAssertionError(error: Error, expectedErrorMsg: string): boolean; +export function validateAssertionError(error: Error, expectedErrorMsg: string | RegExp): boolean; // (No @packageDocumentation comment for this package) diff --git a/experimental/PropertyDDS/examples/partial-checkout/src/demo/squaresApp.ts b/experimental/PropertyDDS/examples/partial-checkout/src/demo/squaresApp.ts index 540ca3b7047c..a2d611d6d650 100644 --- a/experimental/PropertyDDS/examples/partial-checkout/src/demo/squaresApp.ts +++ b/experimental/PropertyDDS/examples/partial-checkout/src/demo/squaresApp.ts @@ -83,7 +83,7 @@ export class SquaresApp { (property) => { assert( property instanceof ContainerProperty, - "Property should always be a ContainerProperty.", + 0x5eb /* Property should always be a ContainerProperty. */, ); const values = property.getValues(); diff --git a/experimental/dds/attributable-map/src/mapKernel.ts b/experimental/dds/attributable-map/src/mapKernel.ts index 3a35a08b842f..7fc024c367bf 100644 --- a/experimental/dds/attributable-map/src/mapKernel.ts +++ b/experimental/dds/attributable-map/src/mapKernel.ts @@ -425,7 +425,7 @@ export class AttributableMapKernel { assert( !attribution || attribution.type !== "local", - "The attribution for summarization should not be local type", + 0x5ec /* The attribution for summarization should not be local type */, ); serializableMapData[key] = localValue.makeSerialized( @@ -690,7 +690,7 @@ export class AttributableMapKernel { localOpMetadata !== undefined && isMapKeyLocalOpMetadata(localOpMetadata) && localOpMetadata.pendingMessageId < this.pendingClearMessageIds[0], - "Received out of order op when there is an unackd clear message", + 0x5ed /* Received out of order op when there is an unackd clear message */, ); } // If we have an unack'd clear, we can ignore all ops. @@ -704,13 +704,13 @@ export class AttributableMapKernel { if (local) { assert( localOpMetadata !== undefined && isMapKeyLocalOpMetadata(localOpMetadata), - "pendingMessageId is missing from the local client's operation", + 0x5ee /* pendingMessageId is missing from the local client's operation */, ); const pendingMessageIds = this.pendingKeys.get(op.key); assert( pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, - "Unexpected pending message received", + 0x5ef /* Unexpected pending message received */, ); pendingMessageIds.shift(); if (pendingMessageIds.length === 0) { @@ -741,12 +741,12 @@ export class AttributableMapKernel { if (local) { assert( isClearLocalOpMetadata(localOpMetadata), - "pendingMessageId is missing from the local client's clear operation", + 0x5f0 /* pendingMessageId is missing from the local client's clear operation */, ); const pendingClearMessageId = this.pendingClearMessageIds.shift(); assert( pendingClearMessageId === localOpMetadata.pendingMessageId, - "pendingMessageId does not match", + 0x5f1 /* pendingMessageId does not match */, ); this.clearAllAttribution(); return; @@ -761,13 +761,13 @@ export class AttributableMapKernel { submit: (op: IMapClearOperation, localOpMetadata: IMapClearLocalOpMetadata) => { assert( isClearLocalOpMetadata(localOpMetadata), - "Invalid localOpMetadata for clear", + 0x5f2 /* Invalid localOpMetadata for clear */, ); // We don't reuse the metadata pendingMessageId but send a new one on each submit. const pendingClearMessageId = this.pendingClearMessageIds.shift(); assert( pendingClearMessageId === localOpMetadata.pendingMessageId, - "pendingMessageId does not match", + 0x5f3 /* pendingMessageId does not match */, ); this.submitMapClearMessage(op, localOpMetadata.previousMap); }, @@ -874,14 +874,17 @@ export class AttributableMapKernel { * @param localOpMetadata - Metadata from the previous submit */ private resubmitMapKeyMessage(op: IMapKeyOperation, localOpMetadata: MapLocalOpMetadata): void { - assert(isMapKeyLocalOpMetadata(localOpMetadata), "Invalid localOpMetadata in submit"); + assert( + isMapKeyLocalOpMetadata(localOpMetadata), + 0x5f4 /* Invalid localOpMetadata in submit */, + ); // clear the old pending message id const pendingMessageIds = this.pendingKeys.get(op.key); assert( pendingMessageIds !== undefined && pendingMessageIds[0] === localOpMetadata.pendingMessageId, - "Unexpected pending message received", + 0x5f5 /* Unexpected pending message received */, ); pendingMessageIds.shift(); if (pendingMessageIds.length === 0) { diff --git a/experimental/dds/ot/ot/src/ot.ts b/experimental/dds/ot/ot/src/ot.ts index ac85a1a4edd7..40013fc5b838 100644 --- a/experimental/dds/ot/ot/src/ot.ts +++ b/experimental/dds/ot/ot/src/ot.ts @@ -83,7 +83,10 @@ export abstract class SharedOT extends SharedObject { protected abstract transform(input: TOp, transform: TOp): TOp; protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats { - assert(this.pendingOps.length === 0, "Summarizer must not have locally pending changes."); + assert( + this.pendingOps.length === 0, + 0x5f6 /* Summarizer must not have locally pending changes. */, + ); return createSingleBlobSummary("header", serializer.stringify(this.global, this.handle)); } diff --git a/experimental/dds/sequence-deprecated/src/sparsematrix.ts b/experimental/dds/sequence-deprecated/src/sparsematrix.ts index 38925c234206..8ccf8e6b574a 100644 --- a/experimental/dds/sequence-deprecated/src/sparsematrix.ts +++ b/experimental/dds/sequence-deprecated/src/sparsematrix.ts @@ -73,7 +73,7 @@ export class PaddingSegment extends BaseSegment { } public append(segment: ISegment) { - assert(PaddingSegment.is(segment), "can only append padding segment"); + assert(PaddingSegment.is(segment), 0x5f7 /* can only append padding segment */); super.append(segment); } diff --git a/experimental/dds/tree-graphql/src/test/SharedTreeQuerier.tests.ts b/experimental/dds/tree-graphql/src/test/SharedTreeQuerier.tests.ts index 0adaada076a5..3f4ebe29d2fc 100644 --- a/experimental/dds/tree-graphql/src/test/SharedTreeQuerier.tests.ts +++ b/experimental/dds/tree-graphql/src/test/SharedTreeQuerier.tests.ts @@ -32,7 +32,7 @@ describe('SharedTreeQuerier', () => { } function intNode(idContext: NodeIdContext, value: number): ChangeNode { - assert(value === Math.round(value), 'Not an int'); + assert(value === Math.round(value), 0x5f8 /* Not an int */); return { definition: 'Int' as Definition, identifier: idContext.generateNodeId(), @@ -178,14 +178,14 @@ describe('SharedTreeQuerier', () => { } function getPizzas(query: Maybe): Pizza[] { - assert(query !== null && query !== undefined, 'Query returned null unexpectedly'); - assert(query.pizzas !== null && query.pizzas !== undefined, 'Query returned no pizzas'); + assert(query !== null && query !== undefined, 0x5f9 /* Query returned null unexpectedly */); + assert(query.pizzas !== null && query.pizzas !== undefined, 0x5fa /* Query returned no pizzas */); return query.pizzas; } function getDrinks(query: Maybe): Drink[] { - assert(query !== null && query !== undefined, 'Query returned null unexpectedly'); - assert(query.drinks !== null && query.drinks !== undefined, 'Query returned no pizzas'); + assert(query !== null && query !== undefined, 0x5fb /* Query returned null unexpectedly */); + assert(query.drinks !== null && query.drinks !== undefined, 0x5fc /* Query returned no pizzas */); return query.drinks; } diff --git a/experimental/dds/tree-graphql/src/test/TestUtilities.ts b/experimental/dds/tree-graphql/src/test/TestUtilities.ts index b0a4ed64cd20..7e08d0955e64 100644 --- a/experimental/dds/tree-graphql/src/test/TestUtilities.ts +++ b/experimental/dds/tree-graphql/src/test/TestUtilities.ts @@ -25,7 +25,7 @@ export function createTestQueryTree(nodeFactory: (idContext: NodeIdContext) => C // Follow the graphql convention that the root type of a schema must of type 'Query' // Traits are copied off of the Query node and applied to the root node // This is simply to save space/complexity in the tree, rather than adding the query root node _under_ the `initialTree` root node - assert(treeNode.definition === 'Query', 'root node must be a Query node'); + assert(treeNode.definition === 'Query', 0x5fd /* root node must be a Query node */); for (const [label, trait] of Object.entries(treeNode.traits)) { tree.applyEdit( ...Change.insertTree( diff --git a/experimental/dds/tree/src/ChangeTypes.ts b/experimental/dds/tree/src/ChangeTypes.ts index f62d7e2ccc82..b4821c65ff68 100644 --- a/experimental/dds/tree/src/ChangeTypes.ts +++ b/experimental/dds/tree/src/ChangeTypes.ts @@ -366,11 +366,11 @@ export const StableRange = { if (start.referenceTrait && end.referenceTrait) { assert( start.referenceTrait.parent === end.referenceTrait.parent, - 'StableRange must be constructed with endpoints from the same trait' + 0x5fe /* StableRange must be constructed with endpoints from the same trait */ ); assert( start.referenceTrait.label === end.referenceTrait.label, - 'StableRange must be constructed with endpoints from the same trait' + 0x5ff /* StableRange must be constructed with endpoints from the same trait */ ); } return { start, end }; diff --git a/experimental/dds/tree/src/Checkout.ts b/experimental/dds/tree/src/Checkout.ts index 7c006edb9de4..0e6fed1bffc4 100644 --- a/experimental/dds/tree/src/Checkout.ts +++ b/experimental/dds/tree/src/Checkout.ts @@ -156,7 +156,7 @@ export abstract class Checkout extends EventEmitterWithErrorHandling): void { - assert(this.currentEdit !== undefined, 'Changes must be applied as part of an ongoing edit.'); + assert(this.currentEdit !== undefined, 0x602 /* Changes must be applied as part of an ongoing edit. */); const changeArray = unwrapRestOrArray(changes); const { status } = this.currentEdit.applyChanges(changeArray.map((c) => this.tree.internalizeChange(c))); this.validateChangesApplied({ status, failure: this.currentEdit.failure }); @@ -273,7 +274,7 @@ export abstract class Checkout extends EventEmitterWithErrorHandling): EditStatus { - assert(this.currentEdit !== undefined, 'Changes must be applied as part of an ongoing edit.'); + assert(this.currentEdit !== undefined, 0x603 /* Changes must be applied as part of an ongoing edit. */); const changeArray = unwrapRestOrArray(changes); const { status } = this.currentEdit.applyChanges(changeArray); if (status === EditStatus.Applied) { @@ -305,7 +306,7 @@ export abstract class Checkout extends EventEmitterWithErrorHandling): EditId | undefined { this.openEdit(); - assert(this.currentEdit !== undefined, 'Changes must be applied as part of an ongoing edit.'); + assert(this.currentEdit !== undefined, 0x604 /* Changes must be applied as part of an ongoing edit. */); const changeArray = unwrapRestOrArray(changes); const { status } = this.currentEdit.applyChanges(changeArray.map((c) => this.tree.internalizeChange(c))); if (status === EditStatus.Applied) { @@ -329,15 +330,15 @@ export abstract class Checkout extends EventEmitterWithErrorHandling extends TypedEventEmitter extends TypedEventEmitter extends TypedEventEmitter= this._minSequenceNumber, - 'Sequenced edits should carry a monotonically increasing min number' + 0x60c /* Sequenced edits should carry a monotonically increasing min number */ ); this._minSequenceNumber = minSequenceNumber; @@ -492,10 +492,10 @@ export class EditLog extends TypedEventEmitter extends TypedEventEmitter; const hasParent = parentedNode.parentId !== undefined; const hasTraitParent = parentedNode.traitParent !== undefined; - assert(hasParent === hasTraitParent, 'node must have either both parent and traitParent set or neither'); + assert(hasParent === hasTraitParent, 0x610 /* node must have either both parent and traitParent set or neither */); return hasParent; } @@ -147,12 +147,12 @@ export class Forest { for (const node of newNodes) { const { identifier } = node; for (const [traitLabel, trait] of node.traits) { - assert(trait.length > 0, 'any trait arrays present in a node must be non-empty'); + assert(trait.length > 0, 0x611 /* any trait arrays present in a node must be non-empty */); for (const childId of trait) { const child = mutableNodes.get(childId); if (child !== undefined) { // A child already exists in the forest, and its parent is now being added - assert(!isParentedForestNode(child), 'can not give a child multiple parents'); + assert(!isParentedForestNode(child), 0x612 /* can not give a child multiple parents */); const parentedChild = { definition: child.definition, identifier: child.identifier, @@ -174,7 +174,7 @@ export class Forest { // Now add each node to the forest and apply any parentage information that was recorded above for (const node of newNodes) { const parentData = childToParent.get(node.identifier); - assert(!mutableNodes.has(node.identifier), 'can not add node with already existing id'); + assert(!mutableNodes.has(node.identifier), 0x613 /* can not add node with already existing id */); if (parentData !== undefined) { // This is a child whom we haven't added yet, but whose parent we already added above. Supply the recorded parentage info. const child = { @@ -210,13 +210,13 @@ export class Forest { index: number, childIds: readonly NodeId[] ): Forest { - assert(index >= 0, 'invalid attach index'); + assert(index >= 0, 0x614 /* invalid attach index */); const parentNode = this.nodes.get(parentId); - assert(parentNode !== undefined, 'can not insert children under node that does not exist'); + assert(parentNode !== undefined, 0x615 /* can not insert children under node that does not exist */); const mutableNodes = this.nodes.clone(); const traits = new Map(parentNode.traits); const trait = traits.get(label) ?? []; - assert(index <= trait.length, 'invalid attach index'); + assert(index <= trait.length, 0x616 /* invalid attach index */); // If there is nothing to insert, return early. // This is good for performance, but also avoids an edge case where an empty trait could be created (which is an error). @@ -229,7 +229,7 @@ export class Forest { for (const childId of childIds) { mutableNodes.editRange(childId, childId, true, (_, n) => { - assert(!isParentedForestNode(n), 'can not attach node that already has a parent'); + assert(!isParentedForestNode(n), 0x617 /* can not attach node that already has a parent */); const breakVal: { value: ParentedForestNode } = { value: { ...n, @@ -261,9 +261,9 @@ export class Forest { startIndex: number, endIndex: number ): { forest: Forest; detached: readonly NodeId[] } { - assert(startIndex >= 0 && endIndex >= startIndex, 'invalid detach index range'); + assert(startIndex >= 0 && endIndex >= startIndex, 0x618 /* invalid detach index range */); const parentNode = this.nodes.get(parentId); - assert(parentNode !== undefined, 'can not detach children under node that does not exist'); + assert(parentNode !== undefined, 0x619 /* can not detach children under node that does not exist */); if (startIndex === endIndex) { return { forest: this, detached: [] }; } @@ -271,7 +271,7 @@ export class Forest { const mutableNodes = this.nodes.clone(); const traits = new Map(parentNode.traits); const trait = traits.get(label) ?? []; - assert(endIndex <= trait.length, 'invalid detach index range'); + assert(endIndex <= trait.length, 0x61a /* invalid detach index range */); const detached: NodeId[] = trait.slice(startIndex, endIndex); const newChildren = [...trait.slice(0, startIndex), ...trait.slice(endIndex)]; const deleteTrait = newChildren.length === 0; @@ -313,7 +313,7 @@ export class Forest { // eslint-disable-next-line @rushstack/no-new-null public setValue(nodeId: NodeId, value: Payload | null): Forest { const node = this.nodes.get(nodeId); - assert(node !== undefined, 'can not replace payload for node that does not exist'); + assert(node !== undefined, 0x61b /* can not replace payload for node that does not exist */); const mutableNodes = this.nodes.clone(); const newNode = { ...node }; if (value !== null) { @@ -368,7 +368,7 @@ export class Forest { private deleteRecursive(mutableNodes: BTree, id: NodeId, deleteChildren: boolean): void { const node = mutableNodes.get(id) ?? fail('node to delete must exist'); - assert(!isParentedForestNode(node), 'deleted nodes must be unparented'); + assert(!isParentedForestNode(node), 0x61c /* deleted nodes must be unparented */); mutableNodes.delete(id); for (const trait of node.traits.values()) { for (const childId of trait) { @@ -401,24 +401,24 @@ export class Forest { if (isParentedForestNode(node)) { const parent = this.get(node.parentId); const trait = parent.traits.get(node.traitParent); - assert(trait !== undefined, 'trait exists'); - assert(trait.includes(node.identifier), 'node is parented incorrectly'); + assert(trait !== undefined, 0x61d /* trait exists */); + assert(trait.includes(node.identifier), 0x61e /* node is parented incorrectly */); } for (const trait of node.traits.values()) { - assert(trait.length > 0, 'trait is present but empty'); + assert(trait.length > 0, 0x61f /* trait is present but empty */); for (const childId of trait) { const child = this.nodes.get(childId); - assert(child !== undefined, 'child in trait is not in forest'); - assert(isParentedForestNode(child), 'child is not parented'); - assert(child.parentId === node.identifier, 'child parent pointer is incorrect'); + assert(child !== undefined, 0x620 /* child in trait is not in forest */); + assert(isParentedForestNode(child), 0x621 /* child is not parented */); + assert(child.parentId === node.identifier, 0x622 /* child parent pointer is incorrect */); assert( !checkedChildren.has(childId), - 'the item tree tree must not contain cycles or multi-parented nodes' + 0x623 /* the item tree tree must not contain cycles or multi-parented nodes */ ); assert( (child.parentId ?? fail('each node must have associated metadata')) === nodeId, - 'cached parent is incorrect' + 0x624 /* cached parent is incorrect */ ); checkedChildren.add(childId); } @@ -435,7 +435,7 @@ export class Forest { fail('NodeId not found'); } - assert(isParentedForestNode(child), 'Node is not parented'); + assert(isParentedForestNode(child), 0x625 /* Node is not parented */); return { parentId: child.parentId, traitParent: child.traitParent }; } diff --git a/experimental/dds/tree/src/HistoryEditFactory.ts b/experimental/dds/tree/src/HistoryEditFactory.ts index 7ef8fb607e03..614fd19e5022 100644 --- a/experimental/dds/tree/src/HistoryEditFactory.ts +++ b/experimental/dds/tree/src/HistoryEditFactory.ts @@ -69,8 +69,14 @@ export function revert( case ChangeTypeInternal.Build: { // Save nodes added to the detached state for use in future changes const { destination, source } = change; - assert(!builtNodes.has(destination), `Cannot revert Build: destination is already used by a Build`); - assert(!detachedNodes.has(destination), `Cannot revert Build: destination is already used by a Detach`); + assert( + !builtNodes.has(destination), + 0x626 /* Cannot revert Build: destination is already used by a Build */ + ); + assert( + !detachedNodes.has(destination), + 0x627 /* Cannot revert Build: destination is already used by a Detach */ + ); builtNodes.set( destination, source.reduce((ids: NodeId[], curr: BuildNodeInternal) => { diff --git a/experimental/dds/tree/src/LogViewer.ts b/experimental/dds/tree/src/LogViewer.ts index 2aa196fd77a8..5a981914f4a0 100644 --- a/experimental/dds/tree/src/LogViewer.ts +++ b/experimental/dds/tree/src/LogViewer.ts @@ -301,10 +301,10 @@ export class CachingLogViewer extends TypedEventEmitter super(); this.log = log; if (initialRevision !== undefined) { - assert(Number.isInteger(initialRevision[0]), 'revision must be an integer'); + assert(Number.isInteger(initialRevision[0]), 0x628 /* revision must be an integer */); assert( this.log.isSequencedRevision(initialRevision[0]), - 'revision must correspond to the result of a SequencedEdit' + 0x629 /* revision must correspond to the result of a SequencedEdit */ ); } @@ -373,7 +373,7 @@ export class CachingLogViewer extends TypedEventEmitter * @returns the {@link EditCacheEntry} for the requested revision */ public getEditResultInMemory(revision: Revision): EditCacheEntry { - assert(revision >= this.log.earliestAvailableEditIndex, 'revision not stored in memory'); + assert(revision >= this.log.earliestAvailableEditIndex, 0x62a /* revision not stored in memory */); const startingPoint = this.getStartingPoint(revision); const { startRevision } = startingPoint; let current: EditCacheEntry = startingPoint; @@ -507,7 +507,7 @@ export class CachingLogViewer extends TypedEventEmitter // calls to this method for all local revisions prior, guaranteeing the correct push order. assert( revision === this.log.numberOfSequencedEdits + this.localRevisionCache.length + 1, - 'Local revision view cached out of order.' + 0x62b /* Local revision view cached out of order. */ ); this.localRevisionCache.push(computedCacheEntry); } diff --git a/experimental/dds/tree/src/RevisionValueCache.ts b/experimental/dds/tree/src/RevisionValueCache.ts index 356c982638eb..269a79fb9176 100644 --- a/experimental/dds/tree/src/RevisionValueCache.ts +++ b/experimental/dds/tree/src/RevisionValueCache.ts @@ -69,7 +69,7 @@ export class RevisionValueCache { */ retainedRevision?: [Revision, TValue] ) { - assert(retentionWindowStart >= 0, 'retentionWindowStart must be initialized >= 0'); + assert(retentionWindowStart >= 0, 0x62c /* retentionWindowStart must be initialized >= 0 */); this.evictableRevisions = new LRU({ max: evictableSize, noDisposeOnSet: true, diff --git a/experimental/dds/tree/src/SharedTree.ts b/experimental/dds/tree/src/SharedTree.ts index b66a0d93c7e6..0c1ad89f4f6f 100644 --- a/experimental/dds/tree/src/SharedTree.ts +++ b/experimental/dds/tree/src/SharedTree.ts @@ -594,7 +594,7 @@ export class SharedTree extends SharedObject implements NodeI return false; } - assert(this.runtime.clientId !== undefined, 'Client id should be set if connected.'); + assert(this.runtime.clientId !== undefined, 0x62d /* Client id should be set if connected. */); const quorum = this.runtime.getQuorum(); const selfSequencedClient = quorum.getMember(this.runtime.clientId); @@ -754,7 +754,7 @@ export class SharedTree extends SharedObject implements NodeI if (this.editLog.numberOfLocalEdits > 0) { assert( this.runtime.attachState !== AttachState.Attached, - 'Summarizing should not occur with local edits except on first attach.' + 0x62e /* Summarizing should not occur with local edits except on first attach. */ ); if (this.writeFormat === WriteFormat.v0_1_1) { // Since we're the first client to attach, we can safely finalize ourselves since we're the only ones who have made IDs. @@ -766,7 +766,7 @@ export class SharedTree extends SharedObject implements NodeI this.editLog.sequenceLocalEdits(); } - assert(this.editLog.numberOfLocalEdits === 0, 'generateSummary must not be called with local edits'); + assert(this.editLog.numberOfLocalEdits === 0, 0x62f /* generateSummary must not be called with local edits */); return this.generateSummary(); } @@ -823,7 +823,7 @@ export class SharedTree extends SharedObject implements NodeI assert( this.idCompressor.getAllIdsFromLocalSession().next().done === true, - 'Summary load should not be executed after local state is created.' + 0x630 /* Summary load should not be executed after local state is created. */ ); let convertedSummary: SummaryContents; @@ -1395,7 +1395,7 @@ export class SharedTree extends SharedObject implements NodeI private submitOp(content: SharedTreeOp | SharedTreeOp_0_0_2, localOpMetadata: unknown = undefined): void { assert( compareSummaryFormatVersions(content.version, this.writeFormat) === 0, - 'Attempted to submit op of wrong version' + 0x631 /* Attempted to submit op of wrong version */ ); this.submitLocalMessage(content, localOpMetadata); } @@ -1442,7 +1442,10 @@ export class SharedTree extends SharedObject implements NodeI break; } case WriteFormat.v0_1_1: { - assert(this.stashedIdCompressor !== null, 'Stashed op applied after expected window'); + assert( + this.stashedIdCompressor !== null, + 0x632 /* Stashed op applied after expected window */ + ); if (this.stashedIdCompressor === undefined) { // Use a temporary compressor that will help translate the stashed ops this.stashedIdCompressor = IdCompressor.deserialize( diff --git a/experimental/dds/tree/src/SharedTreeEncoder.ts b/experimental/dds/tree/src/SharedTreeEncoder.ts index 8f8c1b481ac6..359f6dd6b720 100644 --- a/experimental/dds/tree/src/SharedTreeEncoder.ts +++ b/experimental/dds/tree/src/SharedTreeEncoder.ts @@ -156,7 +156,7 @@ export class SharedTreeEncoder_0_1_1 { version === WriteFormat.v0_1_1, `Invalid summary version to decode: ${version}, expected: 0.1.1` ); - assert(typeof editHistory === 'object', '0.1.1 summary encountered with non-object edit history.'); + assert(typeof editHistory === 'object', 0x633 /* 0.1.1 summary encountered with non-object edit history. */); const idCompressor = hasOngoingSession(serializedIdCompressor) ? IdCompressor.deserialize(serializedIdCompressor) @@ -170,7 +170,7 @@ export class SharedTreeEncoder_0_1_1 { : undefined; const { editChunks, editIds } = editHistory; assertWithMessage(editChunks !== undefined, 'Missing editChunks on 0.1.1 summary.'); - assert(editIds !== undefined, 'Missing editIds on 0.1.1 summary.'); + assert(editIds !== undefined, 0x634 /* Missing editIds on 0.1.1 summary. */); const uncompressedChunks = editChunks.map(({ startRevision, chunk }) => ({ startRevision, @@ -231,7 +231,7 @@ export class SharedTreeEncoder_0_1_1 { assert( currentTree.identifier === initialTreeId && currentTree.definition === initialTree.definition, - 'root definition and identifier should be immutable.' + 0x635 /* root definition and identifier should be immutable. */ ); const edit = newEdit(changes); const compressedChanges = edit.changes.map((change) => @@ -370,7 +370,7 @@ export class SharedTreeEncoder_0_0_2 { { currentTree, sequencedEdits }: SharedTreeSummary_0_0_2, attributionId?: AttributionId ): SummaryContents { - assert(sequencedEdits !== undefined, '0.0.2 summary encountered with missing sequencedEdits field.'); + assert(sequencedEdits !== undefined, 0x636 /* 0.0.2 summary encountered with missing sequencedEdits field. */); const idCompressor = new IdCompressor(createSessionId(), reservedIdCount, attributionId); const idGenerator = getNodeIdContext(idCompressor); const generateId = (id) => idGenerator.generateNodeId(id); @@ -433,7 +433,7 @@ export class SharedTreeEncoder_0_0_2 { assert( currentTree.identifier === initialTree.identifier && currentTree.definition === initialTree.definition, - 'root definition and identifier should be immutable.' + 0x637 /* root definition and identifier should be immutable. */ ); const edit = newEdit(changes); diff --git a/experimental/dds/tree/src/TransactionInternal.ts b/experimental/dds/tree/src/TransactionInternal.ts index bd4b04d12dfa..b2aee449c841 100644 --- a/experimental/dds/tree/src/TransactionInternal.ts +++ b/experimental/dds/tree/src/TransactionInternal.ts @@ -246,7 +246,7 @@ export class GenericTransaction { /** @returns the final `EditStatus` and `TreeView` after all changes are applied. */ public close(): EditingResult { - assert(this.open, 'transaction has already been closed'); + assert(this.open, 0x638 /* transaction has already been closed */); this.open = false; if (this.state.status === EditStatus.Applied) { const validation = this.policy.validateOnClose(this.state); @@ -344,7 +344,7 @@ export class GenericTransaction { * @returns this */ public applyChange(change: ChangeInternal, path: ReconciliationPath = []): this { - assert(this.open, 'Editor must be open to apply changes.'); + assert(this.open, 0x639 /* Editor must be open to apply changes. */); if (this.state.status !== EditStatus.Applied) { fail('Cannot apply change to an edit unless all previous changes have applied'); } @@ -652,8 +652,8 @@ export namespace TransactionInternal { private applyConstraint(state: ValidState, change: ConstraintInternal): ChangeResult { // TODO: Implement identityHash and contentHash - assert(change.identityHash === undefined, 'identityHash constraint is not implemented'); - assert(change.contentHash === undefined, 'contentHash constraint is not implemented'); + assert(change.identityHash === undefined, 0x63a /* identityHash constraint is not implemented */); + assert(change.contentHash === undefined, 0x63b /* contentHash constraint is not implemented */); const validatedChange = validateStableRange(state.view, change.toConstrain); if (validatedChange.result !== RangeValidationResultKind.Valid) { diff --git a/experimental/dds/tree/src/TreeCompressor.ts b/experimental/dds/tree/src/TreeCompressor.ts index 379e0ac4bc55..3490a9ea06c1 100644 --- a/experimental/dds/tree/src/TreeCompressor.ts +++ b/experimental/dds/tree/src/TreeCompressor.ts @@ -108,7 +108,7 @@ export class InterningTreeCompressor { - assert(this.root === view.root, 'Delta can only be calculated between views that share a root'); + assert(this.root === view.root, 0x63d /* Delta can only be calculated between views that share a root */); return this.forest.delta(view.forest); } } diff --git a/experimental/dds/tree/src/id-compressor/AppendOnlySortedMap.ts b/experimental/dds/tree/src/id-compressor/AppendOnlySortedMap.ts index 76717e46128c..5ce379a0e198 100644 --- a/experimental/dds/tree/src/id-compressor/AppendOnlySortedMap.ts +++ b/experimental/dds/tree/src/id-compressor/AppendOnlySortedMap.ts @@ -212,7 +212,7 @@ export class AppendOnlySortedMap { let prev: readonly [K, unknown] | undefined; for (const kv of this.entries()) { if (prev !== undefined) { - assert(this.comparator(kv[0], prev[0]) > 0, 'Keys in map must be sorted.'); + assert(this.comparator(kv[0], prev[0]) > 0, 0x63e /* Keys in map must be sorted. */); } prev = kv; } @@ -406,7 +406,7 @@ export class AppendOnlyDoublySortedMap extends AppendOnlySortedMap 0, - 'Values in map must be sorted.' + 0x63f /* Values in map must be sorted. */ ); } prev = kv; diff --git a/experimental/dds/tree/src/id-compressor/IdCompressor.ts b/experimental/dds/tree/src/id-compressor/IdCompressor.ts index 0e3cf931f1cb..ebee8936f2db 100644 --- a/experimental/dds/tree/src/id-compressor/IdCompressor.ts +++ b/experimental/dds/tree/src/id-compressor/IdCompressor.ts @@ -299,8 +299,8 @@ export class IdCompressor { * `IdCompressor.maxClusterSize`. */ public set clusterCapacity(value: number) { - assert(value > 0, 'Clusters must have a positive capacity'); - assert(value <= IdCompressor.maxClusterSize, 'Clusters must not exceed max cluster size'); + assert(value > 0, 0x640 /* Clusters must have a positive capacity */); + assert(value <= IdCompressor.maxClusterSize, 0x641 /* Clusters must not exceed max cluster size */); this.newClusterCapacity = value; } @@ -399,7 +399,7 @@ export class IdCompressor { attributionId?: AttributionId, private readonly logger?: ITelemetryLogger ) { - assert(reservedIdCount >= 0, 'reservedIdCount must be non-negative'); + assert(reservedIdCount >= 0, 0x642 /* reservedIdCount must be non-negative */); if (attributionId !== undefined) { assertIsUuidString(attributionId); } @@ -537,7 +537,7 @@ export class IdCompressor { assert( this.lastTakenLocalId === -this.localIdCount && this.lastTakenLocalId !== lastTakenNormalized, - 'Non-empty range must properly consume local IDs' + 0x643 /* Non-empty range must properly consume local IDs */ ); range.ids = ids; @@ -555,7 +555,7 @@ export class IdCompressor { const session = this.sessions.get(sessionId) ?? this.createSession(sessionId, attributionId); assert( range.attributionId === undefined || range.attributionId === session.attributionId, - "A session's attribution ID may never be modified." + 0x644 /* A session's attribution ID may never be modified. */ ); const ids = getIds(range); @@ -572,11 +572,11 @@ export class IdCompressor { const normalizedLastFinalizedLocal = session.lastFinalizedLocalId ?? 0; const { first: newFirstFinalizedLocal, last: newLastFinalizedLocal } = ids; - assert(newFirstFinalizedLocal === normalizedLastFinalizedLocal - 1, 'Ranges finalized out of order.'); + assert(newFirstFinalizedLocal === normalizedLastFinalizedLocal - 1, 0x645 /* Ranges finalized out of order. */); // The total number of session-local IDs to finalize const finalizeCount = normalizedLastFinalizedLocal - newLastFinalizedLocal; - assert(finalizeCount >= 1, 'Cannot finalize an empty range.'); + assert(finalizeCount >= 1, 0x646 /* Cannot finalize an empty range. */); let eagerFinalIdCount = 0; let initialClusterCount = 0; @@ -616,7 +616,7 @@ export class IdCompressor { this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId + expansionAmount) as FinalCompressedId; assert( this.nextClusterBaseFinalId < Number.MAX_SAFE_INTEGER, - 'The number of allocated final IDs must not exceed the JS maximum safe integer.' + 0x647 /* The number of allocated final IDs must not exceed the JS maximum safe integer. */ ); this.checkClusterForCollision(currentCluster); if (isLocal) { @@ -635,7 +635,7 @@ export class IdCompressor { 1) as FinalCompressedId; assert( session.lastFinalizedLocalId !== undefined, - 'Cluster already exists for session but there is no finalized local ID' + 0x648 /* Cluster already exists for session but there is no finalized local ID */ ); const finalPivot = (newLastFinalizedFinal - overflow + 1) as FinalCompressedId; // Inform the normalizer of all IDs that we now know will end up being finalized into this cluster, including the ones @@ -727,7 +727,7 @@ export class IdCompressor { this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId + newCluster.capacity) as FinalCompressedId; assert( this.nextClusterBaseFinalId < Number.MAX_SAFE_INTEGER, - 'The number of allocated final IDs must not exceed the JS maximum safe integer.' + 0x649 /* The number of allocated final IDs must not exceed the JS maximum safe integer. */ ); this.finalIdToCluster.append(newBaseFinalId, newCluster); } @@ -738,16 +738,22 @@ export class IdCompressor { for (let i = 0; i < overrides.length; i++) { const [overriddenLocal, override] = overrides[i]; // Note: recall that local IDs are negative - assert(i === 0 || overriddenLocal < overrides[i - 1][0], 'Override IDs must be in sorted order.'); - assert(overriddenLocal < normalizedLastFinalizedLocal, 'Ranges finalized out of order.'); - assert(overriddenLocal >= newLastFinalizedLocal, 'Malformed range: override ID ahead of range start.'); + assert( + i === 0 || overriddenLocal < overrides[i - 1][0], + 0x64a /* Override IDs must be in sorted order. */ + ); + assert(overriddenLocal < normalizedLastFinalizedLocal, 0x64b /* Ranges finalized out of order. */); + assert( + overriddenLocal >= newLastFinalizedLocal, + 0x64c /* Malformed range: override ID ahead of range start. */ + ); let cluster: IdCluster; let overriddenFinal: FinalCompressedId; if (localIdPivot !== undefined && overriddenLocal <= localIdPivot) { // Override is at or past the pivot, so it is in a new cluster. assert( newCluster !== undefined && newBaseFinalId !== undefined, - 'No cluster was created when overflow occurred.' + 0x64d /* No cluster was created when overflow occurred. */ ); cluster = newCluster; overriddenFinal = (newBaseFinalId + (localIdPivot - overriddenLocal)) as FinalCompressedId; @@ -755,7 +761,7 @@ export class IdCompressor { // Override was finalized into an existing cluster assert( currentCluster !== undefined && currentBaseFinalId !== undefined, - 'No cluster exists but IDs were finalized.' + 0x64e /* No cluster exists but IDs were finalized. */ ); cluster = currentCluster; overriddenFinal = (currentBaseFinalId + @@ -785,7 +791,7 @@ export class IdCompressor { } else { assert( !isLocal || mostFinalExistingOverride === overriddenLocal, - 'Cannot have multiple local IDs with identical overrides.' + 0x64f /* Cannot have multiple local IDs with identical overrides. */ ); // This session has created an ID with this override before, but has not finalized it yet. The incoming // range "wins" and will contain the final ID associated with that override, regardless of if that range was @@ -797,7 +803,7 @@ export class IdCompressor { overrideForCluster = override; } - assert(!cluster.overrides.has(overriddenFinal), 'Cannot add a second override for final id'); + assert(!cluster.overrides.has(overriddenFinal), 0x650 /* Cannot add a second override for final id */); if (typeof overrideForCluster === 'string') { if (isLocal || associatedLocal === undefined) { cluster.overrides.set(overriddenFinal, override); @@ -1011,7 +1017,7 @@ export class IdCompressor { if (overrideInversionKey !== undefined) { const registeredLocal = sessionIdNormalizer.addLocalId(); - assert(registeredLocal === newLocalId, 'Session ID Normalizer produced unexpected local ID'); + assert(registeredLocal === newLocalId, 0x651 /* Session ID Normalizer produced unexpected local ID */); if (eagerFinalId !== undefined) { sessionIdNormalizer.addFinalIds(eagerFinalId, eagerFinalId, cluster ?? fail()); } @@ -1024,7 +1030,7 @@ export class IdCompressor { return eagerFinalId; } else { const registeredLocal = sessionIdNormalizer.addLocalId(); - assert(registeredLocal === newLocalId, 'Session ID Normalizer produced unexpected local ID'); + assert(registeredLocal === newLocalId, 0x652 /* Session ID Normalizer produced unexpected local ID */); } return newLocalId; @@ -1587,7 +1593,7 @@ export class IdCompressor { } // Reserved session not serialized, and local session is present but may not make IDs - assert(serializedSessions.length - this.sessions.size <= 2, 'session not serialized'); + assert(serializedSessions.length - this.sessions.size <= 2, 0x653 /* session not serialized */); const serializedIdCompressor: Omit = { version: currentWrittenVersion, @@ -1713,14 +1719,14 @@ export class IdCompressor { for (const serializedSession of serializedSessions) { const [sessionId, attributionIndex] = serializedSession; if (sessionId === localSessionId) { - assert(hasOngoingSession(serialized), 'Cannot resume existing session.'); + assert(hasOngoingSession(serialized), 0x654 /* Cannot resume existing session. */); sessionInfos.push({ session: compressor.localSession, sessionId }); } else { let attributionId: AttributionId | undefined; if (attributionIndex !== undefined) { assert( serializedAttributionIds !== undefined && serializedAttributionIds.length > attributionIndex, - 'AttributionId index out of bounds' + 0x655 /* AttributionId index out of bounds */ ); attributionId = serializedAttributionIds[attributionIndex]; } diff --git a/experimental/dds/tree/src/id-compressor/IdRange.ts b/experimental/dds/tree/src/id-compressor/IdRange.ts index 70a1f566038b..2242caeecc65 100644 --- a/experimental/dds/tree/src/id-compressor/IdRange.ts +++ b/experimental/dds/tree/src/id-compressor/IdRange.ts @@ -23,7 +23,7 @@ export function getIds( last ??= overrides.overrides[overrides.overrides.length - 1][0]; } - assert(first !== undefined && last !== undefined, 'malformed IdCreationRange'); + assert(first !== undefined && last !== undefined, 0x656 /* malformed IdCreationRange */); return { first, diff --git a/experimental/dds/tree/src/id-compressor/SessionIdNormalizer.ts b/experimental/dds/tree/src/id-compressor/SessionIdNormalizer.ts index 56b6de697211..c7fe63eb9ce2 100644 --- a/experimental/dds/tree/src/id-compressor/SessionIdNormalizer.ts +++ b/experimental/dds/tree/src/id-compressor/SessionIdNormalizer.ts @@ -211,14 +211,14 @@ export class SessionIdNormalizer { if (this.expensiveAsserts) { if (lastLocalRange === undefined) { - assert(localId === -1, 'Local ID space must start at -1.'); + assert(localId === -1, 0x657 /* Local ID space must start at -1. */); } else { const [firstLocal, [_, finalRanges]] = lastLocalRange; let finalDelta = 0; for (const [_, [firstFinal, lastFinal]] of entries(firstLocal, finalRanges)) { finalDelta += lastFinal - firstFinal + 1; } - assert(localId === firstLocal - finalDelta, 'Local ID space must be contiguous.'); + assert(localId === firstLocal - finalDelta, 0x658 /* Local ID space must be contiguous. */); } } @@ -250,7 +250,7 @@ export class SessionIdNormalizer { * ^final ID 9 is not contiguous and does not have a corresponding local ID */ public addFinalIds(firstFinal: FinalCompressedId, lastFinal: FinalCompressedId, rangeObject: TRangeObject): void { - assert(lastFinal >= firstFinal, 'Malformed normalization range.'); + assert(lastFinal >= firstFinal, 0x659 /* Malformed normalization range. */); const [firstLocal, finalRangesObj] = this.idRanges.last() ?? fail('Final IDs must be added to an existing local range.'); const [lastLocal, finalRanges] = finalRangesObj; @@ -279,7 +279,7 @@ export class SessionIdNormalizer { rangeMap = finalRanges; } rangeMap.append(alignedLocal, [firstFinal, lastFinal, rangeObject]); - assert(alignedLocal >= lastLocal, 'Gaps in final space must align to a local.'); + assert(alignedLocal >= lastLocal, 0x65a /* Gaps in final space must align to a local. */); } if (this.expensiveAsserts) { this.idRanges.assertValid(); @@ -314,7 +314,7 @@ export class SessionIdNormalizer { * */ public registerFinalIdBlock(firstFinalInBlock: FinalCompressedId, count: number, rangeObject: TRangeObject): void { - assert(count >= 1, 'Malformed normalization block.'); + assert(count >= 1, 0x65b /* Malformed normalization block. */); const [firstLocal, [lastLocal, finalRanges]] = this.idRanges.last() ?? fail('Final ID block should not be registered before any locals.'); let unalignedLocalCount: number; @@ -324,7 +324,10 @@ export class SessionIdNormalizer { const [_, lastAlignedLocal] = this.getAlignmentOfLastRange(firstLocal, finalRanges); unalignedLocalCount = lastAlignedLocal - lastLocal; } - assert(unalignedLocalCount > 0, 'Final ID block should not be registered without an existing local range.'); + assert( + unalignedLocalCount > 0, + 0x65c /* Final ID block should not be registered without an existing local range. */ + ); const lastFinal = (firstFinalInBlock + Math.min(unalignedLocalCount, count) - 1) as FinalCompressedId; this.addFinalIds(firstFinalInBlock, lastFinal, rangeObject); } @@ -407,7 +410,7 @@ export class SessionIdNormalizer { for (const [firstLocal, lastLocal, serializedFinalRanges] of serialized.localRanges) { let finalRanges: FinalRanges | undefined; if (serializedFinalRanges !== undefined) { - assert(serializedFinalRanges.length !== 0, 'Empty range should not be reified.'); + assert(serializedFinalRanges.length !== 0, 0x65d /* Empty range should not be reified. */); if (serializedFinalRanges.length === 1) { const [_, firstFinal, lastFinal] = serializedFinalRanges[0]; finalRanges = [firstFinal, lastFinal, getRangeObject(firstFinal)]; diff --git a/experimental/dds/tree/src/persisted-types/0.1.1.ts b/experimental/dds/tree/src/persisted-types/0.1.1.ts index c30b742025df..f3cf1ff032ab 100644 --- a/experimental/dds/tree/src/persisted-types/0.1.1.ts +++ b/experimental/dds/tree/src/persisted-types/0.1.1.ts @@ -421,11 +421,11 @@ export const StableRangeInternal = { if (start.referenceTrait && end.referenceTrait) { assert( start.referenceTrait.parent === end.referenceTrait.parent, - 'StableRange must be constructed with endpoints from the same trait' + 0x65e /* StableRange must be constructed with endpoints from the same trait */ ); assert( start.referenceTrait.label === end.referenceTrait.label, - 'StableRange must be constructed with endpoints from the same trait' + 0x65f /* StableRange must be constructed with endpoints from the same trait */ ); } return { start, end }; diff --git a/experimental/dds/tree/src/test/AppendOnlySortedMap.tests.ts b/experimental/dds/tree/src/test/AppendOnlySortedMap.tests.ts index ccc61db44345..ba91b12f413e 100644 --- a/experimental/dds/tree/src/test/AppendOnlySortedMap.tests.ts +++ b/experimental/dds/tree/src/test/AppendOnlySortedMap.tests.ts @@ -5,7 +5,9 @@ /* eslint-disable no-bitwise */ +import { strict as assert } from 'assert'; import { expect } from 'chai'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { assertNotUndefined, compareFiniteNumbers } from '../Common'; import { AppendOnlyDoublySortedMap, AppendOnlySortedMap } from '../id-compressor/AppendOnlySortedMap'; @@ -14,8 +16,11 @@ function runAppendOnlyMapTests(mapBuilder: () => AppendOnlySortedMap map.append(-1, 1)).to.throw(exception); - expect(() => map.append(1, 2)).to.not.throw(); + assert.throws( + () => map.append(-1, 1), + (e) => validateAssertionError(e, exception) + ); + map.append(1, 2); }); it('can get the min and max keys', () => { @@ -193,8 +198,11 @@ describe('AppendOnlyDoublySortedMap', () => { const map = mapBuilder(); map.append(0, 0); const exception = 'Inserted value must be > all others in the map.'; - expect(() => map.append(1, -1)).to.throw(exception); - expect(() => map.append(2, 1)).to.not.throw(); + assert.throws( + () => map.append(1, -1), + (e) => validateAssertionError(e, exception) + ); + map.append(2, 1); }); it('can get an entry or next lower by value', () => { @@ -236,6 +244,9 @@ describe('AppendOnlyDoublySortedMap', () => { map.append([0], [0]); map.append([1], [1]); assertNotUndefined(map.get([1]))[0] = -1; // mutate value - expect(() => map.assertValid()).to.throw('Values in map must be sorted.'); + assert.throws( + () => map.assertValid(), + (e) => validateAssertionError(e, 'Values in map must be sorted.') + ); }); }); diff --git a/experimental/dds/tree/src/test/Checkout.tests.ts b/experimental/dds/tree/src/test/Checkout.tests.ts index dec2edb8ec52..0a26fc88c874 100644 --- a/experimental/dds/tree/src/test/Checkout.tests.ts +++ b/experimental/dds/tree/src/test/Checkout.tests.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. */ +import { strict as assert } from 'assert'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { ITelemetryBaseEvent } from '@fluidframework/common-definitions'; import { expect } from 'chai'; import { @@ -85,22 +87,22 @@ export function checkoutTests( it('can only have one edit open at a time', async () => { const { checkout } = await setUpTestCheckout(); checkout.openEdit(); - expect(() => checkout.openEdit()).throws(); + assert.throws(() => checkout.openEdit()); }); it('can only close an edit if one is open', async () => { const { checkout } = await setUpTestCheckout(); - expect(() => checkout.closeEdit()).throws(); + assert.throws(() => checkout.closeEdit()); }); it('can only apply changes if an edit is open', async () => { const { checkout, testTree } = await setUpTestTreeCheckout(); - expect(() => checkout.applyChanges(Change.delete(StableRange.only(testTree.left)))).throws(); + assert.throws(() => checkout.applyChanges(Change.delete(StableRange.only(testTree.left)))); }); it('cannot abort an edit if no edit is open', async () => { const { checkout } = await setUpTestCheckout(); - expect(() => checkout.abortEdit()).throws(); + assert.throws(() => checkout.abortEdit()); }); it('can abort valid edits', async () => { @@ -154,8 +156,10 @@ export function checkoutTests( expect(checkout.getEditStatus()).equals(EditStatus.Malformed); // Is still malformed after a subsequent valid edit - expect(() => checkout.applyChanges(Change.delete(StableRange.only(testTree.left)))).throws( - 'Cannot apply change to an edit unless all previous changes have applied' + assert.throws( + () => checkout.applyChanges(Change.delete(StableRange.only(testTree.left))), + (e) => + validateAssertionError(e, 'Cannot apply change to an edit unless all previous changes have applied') ); expect(checkout.getEditStatus()).equals(EditStatus.Malformed); @@ -199,7 +203,10 @@ export function checkoutTests( status: 'Malformed', }); - expect(() => checkout.closeEdit()).throws('Cannot close a transaction that has already failed.'); + assert.throws( + () => checkout.closeEdit(), + (e) => validateAssertionError(e, /Cannot close a transaction that has already failed./) + ); expect(events.length).to.equal(1); }); @@ -220,7 +227,7 @@ export function checkoutTests( it('cannot get the edit status if no edit is open', async () => { const { checkout } = await setUpTestCheckout(); - expect(() => checkout.getEditStatus()).throws(); + assert.throws(() => checkout.getEditStatus()); }); it('Surfaces error events to SharedTree', async () => { @@ -266,12 +273,17 @@ export function checkoutTests( expect(checkout.getEditStatus()).equals(EditStatus.Invalid); // Is still invalid after a subsequent valid edit - expect(() => checkout.applyChanges(Change.delete(StableRange.only(testTree.left)))).throws( - 'Cannot apply change to an edit unless all previous changes have applied' + assert.throws( + () => checkout.applyChanges(Change.delete(StableRange.only(testTree.left))), + (e) => + validateAssertionError(e, 'Cannot apply change to an edit unless all previous changes have applied') ); expect(checkout.getEditStatus()).equals(EditStatus.Invalid); - expect(() => checkout.closeEdit()).throws('Cannot close a transaction that has already failed.'); + assert.throws( + () => checkout.closeEdit(), + (e) => validateAssertionError(e, /Cannot close a transaction that has already failed./) + ); // Next edit is unaffected checkout.openEdit(); @@ -299,12 +311,17 @@ export function checkoutTests( expect(checkout.getEditStatus()).equals(EditStatus.Malformed); // Is still malformed after a subsequent valid edit - expect(() => checkout.applyChanges(Change.delete(StableRange.only(testTree.left)))).throws( - 'Cannot apply change to an edit unless all previous changes have applied' + assert.throws( + () => checkout.applyChanges(Change.delete(StableRange.only(testTree.left))), + (e) => + validateAssertionError(e, 'Cannot apply change to an edit unless all previous changes have applied') ); expect(checkout.getEditStatus()).equals(EditStatus.Malformed); - expect(() => checkout.closeEdit()).throws('Cannot close a transaction that has already failed.'); + assert.throws( + () => checkout.closeEdit(), + (e) => validateAssertionError(e, /Cannot close a transaction that has already failed./) + ); // Next edit is unaffected checkout.openEdit(); diff --git a/experimental/dds/tree/src/test/EditLog.tests.ts b/experimental/dds/tree/src/test/EditLog.tests.ts index 35f00ec12bb7..ffbf59b9c2bb 100644 --- a/experimental/dds/tree/src/test/EditLog.tests.ts +++ b/experimental/dds/tree/src/test/EditLog.tests.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. */ +import { strict as assert } from 'assert'; import { expect } from 'chai'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { EditLog, separateEditAndId } from '../EditLog'; import { EditId } from '../Identifiers'; import { assertNotUndefined } from '../Common'; @@ -149,12 +151,14 @@ describe('EditLog', () => { expect(log.minSequenceNumber).equals(0); log.addSequencedEdit(edit1, { sequenceNumber: 43, referenceSequenceNumber: 42, minimumSequenceNumber: 42 }); expect(log.minSequenceNumber).equals(42); - expect(() => - log.addSequencedEdit('fake-edit' as unknown as Edit, { - sequenceNumber: 44, - referenceSequenceNumber: 43, - }) - ).throws('min number'); + assert.throws( + () => + log.addSequencedEdit('fake-edit' as unknown as Edit, { + sequenceNumber: 44, + referenceSequenceNumber: 43, + }), + (e) => validateAssertionError(e, /min number/) + ); }); it('detects causal ordering violations', () => { @@ -162,7 +166,10 @@ describe('EditLog', () => { log.addLocalEdit(edit0); log.addLocalEdit(edit1); - expect(() => log.addSequencedEdit(edit1, { sequenceNumber: 1, referenceSequenceNumber: 0 })).throws('ordering'); + assert.throws( + () => log.addSequencedEdit(edit1, { sequenceNumber: 1, referenceSequenceNumber: 0 }), + (e) => validateAssertionError(e, /ordering/) + ); }); it('can sequence a local edit', async () => { @@ -186,10 +193,10 @@ describe('EditLog', () => { it('Throws on duplicate sequenced edits', async () => { const log = new EditLog(); log.addSequencedEdit(edit0, { sequenceNumber: 1, referenceSequenceNumber: 0 }); - expect(() => log.addSequencedEdit(edit0, { sequenceNumber: 1, referenceSequenceNumber: 0 })) - .to.throw(Error) - .that.has.property('message') - .which.matches(/Duplicate/); + assert.throws( + () => log.addSequencedEdit(edit0, { sequenceNumber: 1, referenceSequenceNumber: 0 }), + (e) => validateAssertionError(e, /Duplicate/) + ); }); it('can sequence multiple local edits', async () => { diff --git a/experimental/dds/tree/src/test/EditUtilities.tests.ts b/experimental/dds/tree/src/test/EditUtilities.tests.ts index 30a8f1bfe39d..09979bd54851 100644 --- a/experimental/dds/tree/src/test/EditUtilities.tests.ts +++ b/experimental/dds/tree/src/test/EditUtilities.tests.ts @@ -195,7 +195,7 @@ describe('EditUtilities', () => { traits: { [leafTrait]: [testTree.buildLeaf(leafId)] }, }; const clone = convertTreeNodes(tree, (node) => ({ ...node })); - assert(typeof clone !== 'number', ''); + assert(typeof clone !== 'number', 0x660 /* */); expect(clone.definition).to.equal(tree.definition); expect(clone.identifier).to.equal(tree.identifier); expect(clone.payload).to.equal(tree.payload); @@ -237,7 +237,7 @@ describe('EditUtilities', () => { (n) => internalizeBuildNode(n, testTree), isNumber ); - assert(typeof converted !== 'number', 'unexpected detached ID'); + assert(typeof converted !== 'number', 0x661 /* unexpected detached ID */); expect(converted.traits).to.not.be.undefined; }); @@ -248,7 +248,7 @@ describe('EditUtilities', () => { (n) => internalizeBuildNode(n, testTree), isNumber ); - assert(typeof converted !== 'number', 'unexpected detached ID'); + assert(typeof converted !== 'number', 0x662 /* unexpected detached ID */); expect(converted.traits).to.not.be.undefined; }); diff --git a/experimental/dds/tree/src/test/Forest.tests.ts b/experimental/dds/tree/src/test/Forest.tests.ts index 4bff7da83396..2412bc390c52 100644 --- a/experimental/dds/tree/src/test/Forest.tests.ts +++ b/experimental/dds/tree/src/test/Forest.tests.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. */ +import { strict as assert } from 'assert'; import { expect } from 'chai'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { compareForestNodes, Forest, ForestNode } from '../Forest'; import { NodeId, TraitLabel } from '../Identifiers'; import { Payload } from '../persisted-types'; @@ -61,11 +63,11 @@ describe('Forest', () => { }); it('fails on multiparenting', () => { - expect(() => oneNodeForest.add([makeForestNodeWithChildren(testTree, parentId, childId, childId)])).to.throw(); + assert.throws(() => oneNodeForest.add([makeForestNodeWithChildren(testTree, parentId, childId, childId)])); }); it('cannot add a node with a duplicate ID', () => { - expect(() => oneNodeForest.add([makeForestNodeWithChildren(testTree, parentId)])).to.throw(); + assert.throws(() => oneNodeForest.add([makeForestNodeWithChildren(testTree, parentId)])); }); it('can get nodes in the forest', () => { @@ -140,11 +142,13 @@ describe('Forest', () => { it('only accepts valid indices for attaches', () => { const twoNodeForest = oneNodeForest.add([makeForestNodeWithChildren(testTree, childId)]); - expect(() => twoNodeForest.attachRangeOfChildren(parentId, mainTraitLabel, -1, [childId])).to.throw( - 'invalid attach index' + assert.throws( + () => twoNodeForest.attachRangeOfChildren(parentId, mainTraitLabel, -1, [childId]), + (e) => validateAssertionError(e, 'invalid attach index') ); - expect(() => twoNodeForest.attachRangeOfChildren(parentId, mainTraitLabel, 1, [childId])).to.throw( - 'invalid attach index' + assert.throws( + () => twoNodeForest.attachRangeOfChildren(parentId, mainTraitLabel, 1, [childId]), + (e) => validateAssertionError(e, 'invalid attach index') ); }); @@ -167,25 +171,33 @@ describe('Forest', () => { }); it('only accepts valid indices for detaches', () => { - expect(() => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, -1, -1)).to.throw( - 'invalid detach index range' + assert.throws( + () => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, -1, -1), + (e) => validateAssertionError(e, 'invalid detach index range') ); - expect(() => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, -1, 0)).to.throw( - 'invalid detach index range' + assert.throws( + () => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, -1, 0), + (e) => validateAssertionError(e, 'invalid detach index range') ); - expect(() => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, 1, 0)).to.throw( - 'invalid detach index range' + assert.throws( + () => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, 1, 0), + (e) => validateAssertionError(e, 'invalid detach index range') ); - expect(() => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, 0, 2)).to.throw( - 'invalid detach index range' + assert.throws( + () => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, 0, 2), + (e) => validateAssertionError(e, 'invalid detach index range') ); - expect(() => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, 1, 2)).to.throw( - 'invalid detach index range' + assert.throws( + () => parentForest.detachRangeOfChildren(parentId, mainTraitLabel, 1, 2), + (e) => validateAssertionError(e, 'invalid detach index range') ); }); it('cannot delete parented nodes', () => { - expect(() => parentForest.delete([childId], false)).throws('deleted nodes must be unparented'); + assert.throws( + () => parentForest.delete([childId], false), + (e) => validateAssertionError(e, 'deleted nodes must be unparented') + ); }); it('can delete a root', () => { diff --git a/experimental/dds/tree/src/test/IdCompressor.tests.ts b/experimental/dds/tree/src/test/IdCompressor.tests.ts index 1e689e9eaeaf..ece0ee56f7e7 100644 --- a/experimental/dds/tree/src/test/IdCompressor.tests.ts +++ b/experimental/dds/tree/src/test/IdCompressor.tests.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. */ -import { assert, expect } from 'chai'; +import { strict as assert } from 'assert'; +import { expect } from 'chai'; import { v4, v5 } from 'uuid'; import { MockLogger } from '@fluidframework/telemetry-utils'; import { take } from '@fluid-internal/stochastic-test-utils'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { IdCompressor, isFinalId, @@ -52,10 +54,17 @@ import { expectDefined, expectAssert } from './utilities/TestCommon'; describe('IdCompressor', () => { it('detects invalid cluster sizes', () => { const compressor = createCompressor(Client.Client1, 1); - expect(() => (compressor.clusterCapacity = -1)).to.throw('Clusters must have a positive capacity'); - expect(() => (compressor.clusterCapacity = 0)).to.throw('Clusters must have a positive capacity'); - expect(() => (compressor.clusterCapacity = IdCompressor.maxClusterSize + 1)).to.throw( - 'Clusters must not exceed max cluster size' + assert.throws( + () => (compressor.clusterCapacity = -1), + (e) => validateAssertionError(e, 'Clusters must have a positive capacity') + ); + assert.throws( + () => (compressor.clusterCapacity = 0), + (e) => validateAssertionError(e, 'Clusters must have a positive capacity') + ); + assert.throws( + () => (compressor.clusterCapacity = IdCompressor.maxClusterSize + 1), + (e) => validateAssertionError(e, 'Clusters must not exceed max cluster size') ); }); @@ -102,8 +111,14 @@ describe('IdCompressor', () => { it('will not decompress IDs it did not compress', () => { const errorMessage = 'Compressed ID was not generated by this compressor'; const compressor = createCompressor(Client.Client1); - expect(() => compressor.decompress(-1 as LocalCompressedId)).to.throw(errorMessage); - expect(() => compressor.decompress(compressor.reservedIdCount as FinalCompressedId)).to.throw(errorMessage); + assert.throws( + () => compressor.decompress(-1 as LocalCompressedId), + (e) => validateAssertionError(e, errorMessage) + ); + assert.throws( + () => compressor.decompress(compressor.reservedIdCount as FinalCompressedId), + (e) => validateAssertionError(e, errorMessage) + ); }); it('will not re-compress uuids it did not originally compress', () => { @@ -422,7 +437,10 @@ describe('IdCompressor', () => { generateCompressedIds(rangeCompressor, 3); const batchRange = rangeCompressor.takeNextCreationRange(); rangeCompressor.finalizeCreationRange(batchRange); - expect(() => rangeCompressor.finalizeCreationRange(batchRange)).to.throw('Ranges finalized out of order.'); + assert.throws( + () => rangeCompressor.finalizeCreationRange(batchRange), + (e) => validateAssertionError(e, 'Ranges finalized out of order.') + ); // Make a new compressor, as the first one will be left in a bad state const explicitCompressor = createCompressor(Client.Client1); @@ -430,8 +448,9 @@ describe('IdCompressor', () => { explicitCompressor.generateCompressedId('override'); const explicitRange = explicitCompressor.takeNextCreationRange(); explicitCompressor.finalizeCreationRange(explicitRange); - expect(() => explicitCompressor.finalizeCreationRange(explicitRange)).to.throw( - 'Ranges finalized out of order.' + assert.throws( + () => explicitCompressor.finalizeCreationRange(explicitRange), + (e) => validateAssertionError(e, 'Ranges finalized out of order.') ); }); @@ -441,7 +460,10 @@ describe('IdCompressor', () => { compressor.takeNextCreationRange(); compressor.generateCompressedId(); const secondRange = compressor.takeNextCreationRange(); - expect(() => compressor.finalizeCreationRange(secondRange)).to.throw('Ranges finalized out of order.'); + assert.throws( + () => compressor.finalizeCreationRange(secondRange), + (e) => validateAssertionError(e, 'Ranges finalized out of order.') + ); }); it('can finalize ranges into clusters of varying sizes', () => { @@ -477,8 +499,13 @@ describe('IdCompressor', () => { last: (-Number.MAX_SAFE_INTEGER - 2) as UnackedLocalId, }, }; - expect(() => compressor1.finalizeCreationRange(largeRange2)).to.throw( - 'The number of allocated final IDs must not exceed the JS maximum safe integer.' + assert.throws( + () => compressor1.finalizeCreationRange(largeRange2), + (e) => + validateAssertionError( + e, + 'The number of allocated final IDs must not exceed the JS maximum safe integer.' + ) ); }); }); @@ -571,8 +598,14 @@ describe('IdCompressor', () => { expect(finalIdForReserved).to.equal(reservedId); } const outOfBoundsError = 'Reserved Id index out of bounds'; - expect(() => compressor.getReservedId(-1)).to.throw(outOfBoundsError); - expect(() => compressor.getReservedId(compressor.reservedIdCount)).to.throw(outOfBoundsError); + assert.throws( + () => compressor.getReservedId(-1), + (e) => validateAssertionError(e, outOfBoundsError) + ); + assert.throws( + () => compressor.getReservedId(compressor.reservedIdCount), + (e) => validateAssertionError(e, outOfBoundsError) + ); }); it('can decompress a final ID', () => { @@ -672,8 +705,9 @@ describe('IdCompressor', () => { const compressor1 = createCompressor(Client.Client1); const compressor2 = createCompressor(Client.Client2); const normalized = compressor1.normalizeToOpSpace(compressor1.generateCompressedId()); - expect(() => compressor2.normalizeToSessionSpace(normalized, compressor1.localSessionId)).to.throw( - 'No IDs have ever been finalized by the supplied session.' + assert.throws( + () => compressor2.normalizeToSessionSpace(normalized, compressor1.localSessionId), + (e) => validateAssertionError(e, 'No IDs have ever been finalized by the supplied session.') ); }); @@ -1226,9 +1260,7 @@ describe('IdCompressor', () => { const uuid = assertIsStableId(compressor2.decompress(id)); const nextUuid = stableIdFromNumericUuid(numericUuidFromStableId(uuid), 1); // TODO:#283: Re-assess test when full unification is implemented - expect(() => network.allocateAndSendIds(Client.Client1, 1, { 0: nextUuid })).to.not.throw( - `Override '${nextUuid}' collides with another allocated UUID.` - ); + network.allocateAndSendIds(Client.Client1, 1, { 0: nextUuid }); } ); @@ -1246,8 +1278,9 @@ describe('IdCompressor', () => { network.allocateAndSendIds(Client.Client2, 1); network.deliverOperations(DestinationClient.All); network.allocateAndSendIds(Client.Client1, 1); // new cluster - expect(() => network.deliverOperations(Client.Client1)).to.throw( - `Override '${nextUuid}' collides with another allocated UUID.` + assert.throws( + () => network.deliverOperations(Client.Client1), + (e) => validateAssertionError(e, `Override '${nextUuid}' collides with another allocated UUID.`) ); } ); @@ -1262,8 +1295,9 @@ describe('IdCompressor', () => { const expansion = 3; const nextUuid = stableIdFromNumericUuid(numericUuidFromStableId(uuid), expansion); network.allocateAndSendIds(Client.Client1, expansion, { 0: nextUuid }); - expect(() => network.deliverOperations(DestinationClient.All)).to.throw( - `Override '${nextUuid}' collides with another allocated UUID.` + assert.throws( + () => network.deliverOperations(DestinationClient.All), + (e) => validateAssertionError(e, `Override '${nextUuid}' collides with another allocated UUID.`) ); }); }); @@ -1414,7 +1448,9 @@ describe('IdCompressor', () => { // Mimic sending a reference to an ID that hasn't been acked yet, such as in a slow network const id = opSpaceIds[0]; const getSessionNormalizedId = () => compressor2.normalizeToSessionSpace(id, compressor1.localSessionId); - expect(getSessionNormalizedId).to.throw('No IDs have ever been finalized by the supplied session.'); + assert.throws(getSessionNormalizedId, (e) => + validateAssertionError(e, 'No IDs have ever been finalized by the supplied session.') + ); network.deliverOperations(Client.Client2); expect(isFinalId(getSessionNormalizedId())).to.be.true; }); @@ -1624,8 +1660,9 @@ describe('IdCompressor', () => { expect(isFinalId(id)).to.be.true; // eslint-disable-next-line @typescript-eslint/restrict-plus-operands const emptyId = (id + 1) as FinalCompressedId; - expect(() => network.getCompressor(Client.Client2).decompress(emptyId)).to.throw( - 'Compressed ID was not generated by this compressor' + assert.throws( + () => network.getCompressor(Client.Client2).decompress(emptyId), + (e) => validateAssertionError(e, 'Compressed ID was not generated by this compressor') ); }); @@ -1677,9 +1714,10 @@ describe('IdCompressor', () => { network.allocateAndSendIds(Client.Client3, 1); network.deliverOperations(Client.Client1); const serializedWithoutLocalState = compressor.serialize(false); - expect(() => - IdCompressor.deserialize(serializedWithoutLocalState, sessionIds.get(Client.Client2)) - ).to.throw('Cannot resume existing session.'); + assert.throws( + () => IdCompressor.deserialize(serializedWithoutLocalState, sessionIds.get(Client.Client2)), + (e) => validateAssertionError(e, 'Cannot resume existing session.') + ); } ); diff --git a/experimental/dds/tree/src/test/LogViewer.tests.ts b/experimental/dds/tree/src/test/LogViewer.tests.ts index 19594de6e329..ed51d4019993 100644 --- a/experimental/dds/tree/src/test/LogViewer.tests.ts +++ b/experimental/dds/tree/src/test/LogViewer.tests.ts @@ -6,6 +6,7 @@ import { strict as assert } from 'assert'; import { expect } from 'chai'; import { v4 as uuidv4 } from 'uuid'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { EditLog } from '../EditLog'; import { CachingLogViewer, @@ -297,21 +298,27 @@ describe('CachingLogViewer', () => { runLogViewerCorrectnessTests(getCachingLogViewerAssumeAppliedEdits); it('detects non-integer revisions when setting revision views', async () => { - expect(() => { - return getCachingLogViewerAssumeAppliedEdits(simpleLog, simpleLogBaseView, undefined, undefined, [ - 2.4, - simpleLogInitialView, - ]); - }).to.throw('revision must be an integer'); + assert.throws( + () => { + return getCachingLogViewerAssumeAppliedEdits(simpleLog, simpleLogBaseView, undefined, undefined, [ + 2.4, + simpleLogInitialView, + ]); + }, + (e) => validateAssertionError(e, 'revision must be an integer') + ); }); it('detects out-of-bounds revisions when setting revision views', async () => { - expect(() => { - return getCachingLogViewerAssumeAppliedEdits(simpleLog, simpleLogBaseView, undefined, undefined, [ - 1000, - simpleLogInitialView, - ]); - }).to.throw('revision must correspond to the result of a SequencedEdit'); + assert.throws( + () => { + return getCachingLogViewerAssumeAppliedEdits(simpleLog, simpleLogBaseView, undefined, undefined, [ + 1000, + simpleLogInitialView, + ]); + }, + (e) => validateAssertionError(e, 'revision must correspond to the result of a SequencedEdit') + ); }); it('can be created with an initial revision', async () => { diff --git a/experimental/dds/tree/src/test/NumericUuid.tests.ts b/experimental/dds/tree/src/test/NumericUuid.tests.ts index e7790e2e7c1f..2275c38fc62f 100644 --- a/experimental/dds/tree/src/test/NumericUuid.tests.ts +++ b/experimental/dds/tree/src/test/NumericUuid.tests.ts @@ -5,8 +5,10 @@ /* eslint-disable no-bitwise */ +import { strict as assert } from 'assert'; import { expect } from 'chai'; import { makeRandom } from '@fluid-internal/stochastic-test-utils'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { compareStrings } from '../Common'; import { numericUuidEquals, @@ -50,13 +52,29 @@ describe('NumericUuid', () => { it('detects increment overflow', () => { const uuid = numericUuidFromStableId(maxStableId); - expect(() => stableIdFromNumericUuid(uuid, 1)).to.throw('Exceeded maximum numeric UUID'); - expect(() => stableIdFromNumericUuid(incrementUuid(uuid, 1))).to.throw('Exceeded maximum numeric UUID'); - expect(() => stableIdFromNumericUuid(uuid, 256)).to.throw('Exceeded maximum numeric UUID'); - expect(() => stableIdFromNumericUuid(incrementUuid(uuid, 256))).to.throw('Exceeded maximum numeric UUID'); - expect(() => stableIdFromNumericUuid(uuid, Number.MAX_SAFE_INTEGER)).to.throw('Exceeded maximum numeric UUID'); - expect(() => stableIdFromNumericUuid(incrementUuid(uuid, Number.MAX_SAFE_INTEGER))).to.throw( - 'Exceeded maximum numeric UUID' + assert.throws( + () => stableIdFromNumericUuid(uuid, 1), + (e) => validateAssertionError(e, 'Exceeded maximum numeric UUID') + ); + assert.throws( + () => stableIdFromNumericUuid(incrementUuid(uuid, 1)), + (e) => validateAssertionError(e, 'Exceeded maximum numeric UUID') + ); + assert.throws( + () => stableIdFromNumericUuid(uuid, 256), + (e) => validateAssertionError(e, 'Exceeded maximum numeric UUID') + ); + assert.throws( + () => stableIdFromNumericUuid(incrementUuid(uuid, 256)), + (e) => validateAssertionError(e, 'Exceeded maximum numeric UUID') + ); + assert.throws( + () => stableIdFromNumericUuid(uuid, Number.MAX_SAFE_INTEGER), + (e) => validateAssertionError(e, 'Exceeded maximum numeric UUID') + ); + assert.throws( + () => stableIdFromNumericUuid(incrementUuid(uuid, Number.MAX_SAFE_INTEGER)), + (e) => validateAssertionError(e, 'Exceeded maximum numeric UUID') ); }); @@ -70,10 +88,9 @@ describe('NumericUuid', () => { for (let i = 0; i < 100; i++) { const sessionId = createSessionId(); expect(sessionId.length).to.equal(36); - expect(() => { - const sessionNumericUuid = numericUuidFromStableId(sessionId); - expect(stableIdFromNumericUuid(sessionNumericUuid)).to.equal(sessionId); - }).to.not.throw(); + + const sessionNumericUuid = numericUuidFromStableId(sessionId); + expect(stableIdFromNumericUuid(sessionNumericUuid)).to.equal(sessionId); } }); diff --git a/experimental/dds/tree/src/test/RevisionValueCache.tests.ts b/experimental/dds/tree/src/test/RevisionValueCache.tests.ts index cd34539ebf02..52498cfd1cc8 100644 --- a/experimental/dds/tree/src/test/RevisionValueCache.tests.ts +++ b/experimental/dds/tree/src/test/RevisionValueCache.tests.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. */ +import { strict as assert } from 'assert'; import { expect } from 'chai'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { fail } from '../Common'; import { RevisionValueCache } from '../RevisionValueCache'; @@ -16,14 +18,18 @@ describe('RevisionValueCache', () => { } it('cannot be created with a negative retention window', () => { - expect(() => new RevisionValueCache(1, -1)).to.throw( - 'retentionWindowStart must be initialized >= 0' + assert.throws( + () => new RevisionValueCache(1, -1), + (e) => validateAssertionError(e, 'retentionWindowStart must be initialized >= 0') ); }); it('cannot move the retention window backwards', () => { const cache = new RevisionValueCache(1, 0); - expect(() => cache.updateRetentionWindow(-1)).to.throw('retention window boundary must not move backwards'); + assert.throws( + () => cache.updateRetentionWindow(-1), + (e) => validateAssertionError(e, 'retention window boundary must not move backwards') + ); }); it('can find closest entry to a queried revision', () => { @@ -97,11 +103,11 @@ describe('RevisionValueCache', () => { expect(closestEntry(cache, 5)).to.equal(5); cache.cacheValue(2, dummyValue); // Evict 1 cache.updateRetentionWindow(10); // Should not add 5, so 2 will still be in cache - expect(() => closestEntry(cache, 1)).to.throw; // 0 will no longer be retained so 1 should be inaccessible + assert.throws(() => closestEntry(cache, 1)); // 0 will no longer be retained so 1 should be inaccessible expect(closestEntry(cache, 2)).to.equal(2); expect(closestEntry(cache, 5)).to.equal(5); cache.cacheValue(3, dummyValue); // Evict 2 - expect(() => closestEntry(cache, 2)).to.throw; + assert.throws(() => closestEntry(cache, 2)); expect(closestEntry(cache, 5)).to.equal(5); }); diff --git a/experimental/dds/tree/src/test/SessionIdNormalizer.tests.ts b/experimental/dds/tree/src/test/SessionIdNormalizer.tests.ts index 6440081385a5..fdf8ed8ed898 100644 --- a/experimental/dds/tree/src/test/SessionIdNormalizer.tests.ts +++ b/experimental/dds/tree/src/test/SessionIdNormalizer.tests.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. */ +import { strict as assert } from 'assert'; import { benchmark, BenchmarkType } from '@fluid-tools/benchmark'; -import { expect, assert } from 'chai'; +import { expect } from 'chai'; import { BaseFuzzTestState, chain, @@ -16,6 +17,7 @@ import { take, makeRandom, } from '@fluid-internal/stochastic-test-utils'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { fail } from '../Common'; import { isFinalId, isLocalId } from '../id-compressor'; import { SessionIdNormalizer } from '../id-compressor/SessionIdNormalizer'; @@ -24,34 +26,46 @@ import { FinalCompressedId, LocalCompressedId, SessionSpaceCompressedId } from ' describe('SessionIdNormalizer', () => { it('fails when adding finals with no corresponding locals', () => { const normalizer = makeTestNormalizer(); - expect(() => normalizer.addFinalIds(final(0), final(1), dummy)).to.throw( - 'Final IDs must be added to an existing local range.' + assert.throws( + () => normalizer.addFinalIds(final(0), final(1), dummy), + (e) => validateAssertionError(e, 'Final IDs must be added to an existing local range.') ); }); it('fails when adding finals out of order', () => { const normalizer = makeTestNormalizer(); normalizer.addLocalId(); - expect(() => normalizer.addFinalIds(final(1), final(0), dummy)).to.throw('Malformed normalization range.'); + assert.throws( + () => normalizer.addFinalIds(final(1), final(0), dummy), + (e) => validateAssertionError(e, 'Malformed normalization range.') + ); }); it('fails when registering final blocks with no corresponding locals', () => { const normalizer = makeTestNormalizer(); - expect(() => normalizer.registerFinalIdBlock(final(0), 5, dummy)).to.throw( - 'Final ID block should not be registered before any locals.' + assert.throws( + () => normalizer.registerFinalIdBlock(final(0), 5, dummy), + (e) => validateAssertionError(e, 'Final ID block should not be registered before any locals.') ); normalizer.addLocalId(); addFinalIds(normalizer, final(0), final(0)); - expect(() => normalizer.registerFinalIdBlock(final(1), 5, dummy)).to.throw( - 'Final ID block should not be registered without an existing local range.' + assert.throws( + () => normalizer.registerFinalIdBlock(final(1), 5, dummy), + (e) => validateAssertionError(e, 'Final ID block should not be registered without an existing local range.') ); }); it('fails when registering final blocks with an invalid count', () => { const normalizer = makeTestNormalizer(); normalizer.addLocalId(); - expect(() => normalizer.registerFinalIdBlock(final(1), 0, dummy)).to.throw('Malformed normalization block.'); - expect(() => normalizer.registerFinalIdBlock(final(1), -1, dummy)).to.throw('Malformed normalization block.'); + assert.throws( + () => normalizer.registerFinalIdBlock(final(1), 0, dummy), + (e) => validateAssertionError(e, 'Malformed normalization block.') + ); + assert.throws( + () => normalizer.registerFinalIdBlock(final(1), -1, dummy), + (e) => validateAssertionError(e, 'Malformed normalization block.') + ); }); it('fails when gaps in finals do not align with a local', () => { @@ -70,8 +84,9 @@ describe('SessionIdNormalizer', () => { addFinalIds(normalizer, final(0), final(2)); normalizer.addLocalId(); // -4 addFinalIds(normalizer, final(5), final(5)); - expect(() => addFinalIds(normalizer, final(9), final(9))).to.throw( - 'Gaps in final space must align to a local.' + assert.throws( + () => addFinalIds(normalizer, final(9), final(9)), + (e) => validateAssertionError(e, 'Gaps in final space must align to a local.') ); }); @@ -118,21 +133,29 @@ describe('SessionIdNormalizer', () => { normalizer.addLocalId(); // -1 normalizer.addFinalIds(final(0), final(1), dummy); - expect(() => normalizer.registerFinalIdBlock(final(5), 10, dummy)).to.throw( - 'Final ID block should not be registered without an existing local range.' + assert.throws( + () => normalizer.registerFinalIdBlock(final(5), 10, dummy), + (e) => validateAssertionError(e, 'Final ID block should not be registered without an existing local range.') ); }); it('fails when attempting to normalize a local ID that was never registered', () => { const normalizer = makeTestNormalizer(); - expect(() => normalizer.getFinalId(-1 as LocalCompressedId)).to.throw( - 'Local ID was never recorded with this normalizer.' + assert.throws( + () => normalizer.getFinalId(-1 as LocalCompressedId), + (e) => validateAssertionError(e, 'Local ID was never recorded with this normalizer.') ); const local = normalizer.addLocalId(); const secondLocal = (local - 1) as LocalCompressedId; - expect(() => normalizer.getFinalId(secondLocal)).to.throw('Local ID was never recorded with this normalizer.'); + assert.throws( + () => normalizer.getFinalId(secondLocal), + (e) => validateAssertionError(e, 'Local ID was never recorded with this normalizer.') + ); addFinalIds(normalizer, final(0), final(5)); - expect(() => normalizer.getFinalId(secondLocal)).to.throw('Local ID was never recorded with this normalizer.'); + assert.throws( + () => normalizer.getFinalId(secondLocal), + (e) => validateAssertionError(e, 'Local ID was never recorded with this normalizer.') + ); }); itWithNormalizer('can normalize IDs with only local forms', (normalizer) => { @@ -266,14 +289,13 @@ function itWithNormalizer(title: string, itFn: (normalizer: SessionIdNormalizer< // both should never occur assert( (localExpected !== undefined && isLocalId(localExpected)) || - (finalExpected !== undefined && isFinalId(finalExpected)), - 'Test error.' + (finalExpected !== undefined && isFinalId(finalExpected)) ); if (prevFinal !== undefined && finalExpected !== undefined) { - assert(finalExpected > prevFinal, 'Test error.'); + assert(finalExpected > prevFinal); } if (prevLocal !== undefined && localExpected !== undefined) { - assert(localExpected < prevLocal, 'Test error.'); + assert(localExpected < prevLocal); } prevLocal = localExpected; prevFinal = finalExpected; diff --git a/experimental/dds/tree/src/test/StringInterner.tests.ts b/experimental/dds/tree/src/test/StringInterner.tests.ts index 3ee6a092a813..69e90c815d45 100644 --- a/experimental/dds/tree/src/test/StringInterner.tests.ts +++ b/experimental/dds/tree/src/test/StringInterner.tests.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. */ +import { strict as assert } from 'assert'; import { expect } from 'chai'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { MutableStringInterner } from '../StringInterner'; describe('MutableStringInterner', () => { @@ -47,7 +49,10 @@ describe('MutableStringInterner', () => { it("throws an error when trying to retrieve a string that hasn't been encountered", () => { const interner = new MutableStringInterner(); - expect(() => interner.getString(0)).to.throw('No string associated with 0.'); + assert.throws( + () => interner.getString(0), + (e) => validateAssertionError(e, 'No string associated with 0.') + ); }); it('can return a serializable representation of its state', () => { diff --git a/experimental/dds/tree/src/test/TreeView.tests.ts b/experimental/dds/tree/src/test/TreeView.tests.ts index 28d11c73fea5..8c434b0e417b 100644 --- a/experimental/dds/tree/src/test/TreeView.tests.ts +++ b/experimental/dds/tree/src/test/TreeView.tests.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. */ +import { strict as assert } from 'assert'; import { expect } from 'chai'; +import { validateAssertionError } from '@fluidframework/test-runtime-utils'; import { Definition, TraitLabel } from '../Identifiers'; import { RevisionView } from '../RevisionView'; import { ChangeNode } from '../persisted-types'; @@ -37,7 +39,10 @@ describe('TreeView', () => { it('with different root ids', () => { const viewA = RevisionView.fromTree(testTree.buildLeaf(testTree.generateNodeId())); const viewB = RevisionView.fromTree(testTree.buildLeaf(testTree.generateNodeId())); - expect(() => viewA.delta(viewB)).to.throw('Delta can only be calculated between views that share a root'); + assert.throws( + () => viewA.delta(viewB), + (e) => validateAssertionError(e, 'Delta can only be calculated between views that share a root') + ); }); it('with different subtrees', () => { diff --git a/experimental/dds/tree/src/test/utilities/IdCompressorTestUtilities.ts b/experimental/dds/tree/src/test/utilities/IdCompressorTestUtilities.ts index 34aa68840819..e6c6b3033843 100644 --- a/experimental/dds/tree/src/test/utilities/IdCompressorTestUtilities.ts +++ b/experimental/dds/tree/src/test/utilities/IdCompressorTestUtilities.ts @@ -297,7 +297,8 @@ export class IdCompressorTestNetwork { numIds: number, overrides: { [index: number]: string } = {} ): OpSpaceCompressedId[] | IdCreationRange { - assert(numIds > 0, 'Must allocate a non-zero number of IDs'); + // Must allocate a non-zero number of IDs + assert(numIds > 0); const compressor = this.compressors.get(client); let nextIdIndex = 0; const opSpaceIds: OpSpaceCompressedId[] = []; @@ -465,10 +466,8 @@ export class IdCompressorTestNetwork { const sessionSpaceIdA = idDataA.id; const idIndex = getOrCreate(idIndicesAggregator, idDataA.originatingClient, () => 0); originatingClient ??= idDataA.originatingClient; - assert( - idDataA.originatingClient === originatingClient, - 'Test infra gave wrong originating client to TestIdData' - ); + // Test infra gave wrong originating client to TestIdData + assert(idDataA.originatingClient === originatingClient); const attributionA = compressorA.attributeId(idDataA.id); if (attributionA !== attributionIds.get(idDataA.originatingClient)) { // Unification diff --git a/experimental/dds/tree/src/test/utilities/SharedTreeTests.ts b/experimental/dds/tree/src/test/utilities/SharedTreeTests.ts index b863a1093c04..67c2bbf3727a 100644 --- a/experimental/dds/tree/src/test/utilities/SharedTreeTests.ts +++ b/experimental/dds/tree/src/test/utilities/SharedTreeTests.ts @@ -3,13 +3,15 @@ * Licensed under the MIT License. */ -import { assert, expect } from 'chai'; +import { strict as assert } from 'assert'; +import { expect } from 'chai'; import { ITelemetryBaseEvent, ITelemetryBaseLogger } from '@fluidframework/common-definitions'; import { ISequencedDocumentMessage } from '@fluidframework/protocol-definitions'; import { MockContainerRuntime, MockContainerRuntimeFactory, MockFluidDataStoreRuntime, + validateAssertionError, } from '@fluidframework/test-runtime-utils'; import { assertArrayOfOne, assertNotUndefined, fail, isSharedTreeEvent } from '../../Common'; import { EditId, NodeId, TraitLabel } from '../../Identifiers'; @@ -921,10 +923,10 @@ export function runSharedTreeOperationsTests( ...summary, sequencedEdits, }; - expect(() => sharedTree2.loadSummary(corruptedSummary)) - .to.throw(Error) - .that.has.property('message') - .which.matches(/Duplicate/); + assert.throws( + () => sharedTree2.loadSummary(corruptedSummary), + (e) => validateAssertionError(e, /Duplicate/) + ); }); it('can be used without history preservation', async () => { diff --git a/experimental/dds/tree/src/test/utilities/SharedTreeVersioningTests.ts b/experimental/dds/tree/src/test/utilities/SharedTreeVersioningTests.ts index d8b32495dda8..f7c30535a9dc 100644 --- a/experimental/dds/tree/src/test/utilities/SharedTreeVersioningTests.ts +++ b/experimental/dds/tree/src/test/utilities/SharedTreeVersioningTests.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. */ +import { strict as assert } from 'assert'; import { ITelemetryBaseEvent } from '@fluidframework/common-definitions'; import { LoaderHeader } from '@fluidframework/container-definitions'; -import { MockFluidDataStoreRuntime } from '@fluidframework/test-runtime-utils'; +import { MockFluidDataStoreRuntime, validateAssertionError } from '@fluidframework/test-runtime-utils'; import { expect } from 'chai'; import { StableRange, StablePlace, BuildNode, Change } from '../../ChangeTypes'; import { Mutable } from '../../Common'; @@ -121,8 +122,9 @@ export function runSharedTreeVersioningTests( // Process an edit and expect it to throw applyNoop(newerTree); - expect(() => containerRuntimeFactory.processAllMessages()).to.throw( - 'Newer op version received by a client that has yet to be updated.' + assert.throws( + () => containerRuntimeFactory.processAllMessages(), + (e) => validateAssertionError(e, 'Newer op version received by a client that has yet to be updated.') ); }); @@ -569,7 +571,10 @@ export function runSharedTreeVersioningTests( event.error === 'Simulated issue in update'; expect(events.some(matchesFailedVersionUpdate)).to.equal(false); - expect(() => containerRuntimeFactory.processAllMessages()).to.throw(/Simulated issue in update/); + assert.throws( + () => containerRuntimeFactory.processAllMessages(), + (e) => validateAssertionError(e, /Simulated issue in update/) + ); expect(events.some(matchesFailedVersionUpdate)).to.equal(true); }); }); diff --git a/experimental/dds/tree/src/test/utilities/TestUtilities.ts b/experimental/dds/tree/src/test/utilities/TestUtilities.ts index f169ce8b29ab..bd43add56a6e 100644 --- a/experimental/dds/tree/src/test/utilities/TestUtilities.ts +++ b/experimental/dds/tree/src/test/utilities/TestUtilities.ts @@ -499,7 +499,7 @@ export const versionComparator = (versionA: string, versionB: string): number => assert( versionASplit.length === versionBSplit.length && versionASplit.length === 3, - 'Version numbers should follow semantic versioning.' + 0x668 /* Version numbers should follow semantic versioning. */ ); for (let i = 0; i < 3; ++i) { @@ -524,7 +524,7 @@ export const versionComparator = (versionA: string, versionB: string): number => export function setUpTestTree(idSource?: IdCompressor | SharedTree, expensiveValidation = false): TestTree { const source = idSource ?? new IdCompressor(createSessionId(), reservedIdCount); if (source instanceof SharedTree) { - assert(source.edits.length === 0, 'tree must be a new SharedTree'); + assert(source.edits.length === 0, 0x669 /* tree must be a new SharedTree */); const getNormalizer = () => getIdNormalizerFromSharedTree(source); const contextWrapper = { normalizeToOpSpace: (id: NodeId) => getNormalizer().normalizeToOpSpace(id), diff --git a/experimental/dds/tree2/src/feature-libraries/sequence-field/invert.ts b/experimental/dds/tree2/src/feature-libraries/sequence-field/invert.ts index 6f670c2588cf..137f89c07c89 100644 --- a/experimental/dds/tree2/src/feature-libraries/sequence-field/invert.ts +++ b/experimental/dds/tree2/src/feature-libraries/sequence-field/invert.ts @@ -237,7 +237,7 @@ function invertModifyOrSkip( inverter: NodeChangeInverter, ): Mark { if (changes !== undefined) { - assert(length === 1, "A modify mark must have length equal to one"); + assert(length === 1, 0x66c /* A modify mark must have length equal to one */); return { type: "Modify", changes: inverter(changes, index) }; } diff --git a/experimental/dds/tree2/src/shared-tree-core/branch.ts b/experimental/dds/tree2/src/shared-tree-core/branch.ts index e8b0b9d5002a..1d2a080a3f33 100644 --- a/experimental/dds/tree2/src/shared-tree-core/branch.ts +++ b/experimental/dds/tree2/src/shared-tree-core/branch.ts @@ -177,7 +177,7 @@ export class SharedTreeBranch exten public undo(): void { // TODO: allow this once it becomes possible to compose the changesets created by edits made // within transactions and edits that represent completed transactions. - assert(!this.isTransacting(), "Undo is not yet supported during transactions"); + assert(!this.isTransacting(), 0x66a /* Undo is not yet supported during transactions */); const undoChange = this.undoRedoManager.undo(); if (undoChange !== undefined) { diff --git a/experimental/dds/tree2/src/shared-tree-core/sharedTreeCore.ts b/experimental/dds/tree2/src/shared-tree-core/sharedTreeCore.ts index 071a2ee11a96..af6a72b71726 100644 --- a/experimental/dds/tree2/src/shared-tree-core/sharedTreeCore.ts +++ b/experimental/dds/tree2/src/shared-tree-core/sharedTreeCore.ts @@ -366,7 +366,7 @@ export class SharedTreeCore extends public undo(): void { // TODO: allow this once it becomes possible to compose the changesets created by edits made // within transactions and edits that represent completed transactions. - assert(!this.isTransacting(), "Undo is not yet supported during transactions"); + assert(!this.isTransacting(), 0x66b /* Undo is not yet supported during transactions */); const undoChange = this.undoRedoManager.undo(); if (undoChange !== undefined) { diff --git a/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts b/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts index c4122262ed26..b11ba88f9b51 100644 --- a/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts +++ b/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts @@ -442,6 +442,7 @@ export const shortCodeMap = { "0x246": "checking consistency of socket & _disposed flags", "0x247": "Use addTrackedListener instead", "0x248": "should not subscribe to forwarded events", + "0x24b": "internal signaler should be defined", "0x24c": "Cannot call `flush()` from `orderSequentially`'s callback", "0x24e": "assertion for type narrowing", "0x250": "serialized container with attachment blobs must be rehydrated with detached blob storage", @@ -1166,5 +1167,131 @@ export const shortCodeMap = { "0x5e7": "expected changeset to be defined", "0x5e8": "expected changeset to be defined", "0x5e9": "Must be in a node to set its value", - "0x5ea": "Tried to encode unsupported fieldKind" + "0x5ea": "Tried to encode unsupported fieldKind", + "0x5eb": "Property should always be a ContainerProperty.", + "0x5ec": "The attribution for summarization should not be local type", + "0x5ed": "Received out of order op when there is an unackd clear message", + "0x5ee": "pendingMessageId is missing from the local client's operation", + "0x5ef": "Unexpected pending message received", + "0x5f0": "pendingMessageId is missing from the local client's clear operation", + "0x5f1": "pendingMessageId does not match", + "0x5f2": "Invalid localOpMetadata for clear", + "0x5f3": "pendingMessageId does not match", + "0x5f4": "Invalid localOpMetadata in submit", + "0x5f5": "Unexpected pending message received", + "0x5f6": "Summarizer must not have locally pending changes.", + "0x5f7": "can only append padding segment", + "0x5f8": "Not an int", + "0x5f9": "Query returned null unexpectedly", + "0x5fa": "Query returned no pizzas", + "0x5fb": "Query returned null unexpectedly", + "0x5fc": "Query returned no pizzas", + "0x5fd": "root node must be a Query node", + "0x5fe": "StableRange must be constructed with endpoints from the same trait", + "0x5ff": "StableRange must be constructed with endpoints from the same trait", + "0x600": "An edit is already open.", + "0x601": "An edit is not open.", + "0x602": "Changes must be applied as part of an ongoing edit.", + "0x603": "Changes must be applied as part of an ongoing edit.", + "0x604": "Changes must be applied as part of an ongoing edit.", + "0x605": "An edit is not open.", + "0x606": "Local edits should always be valid.", + "0x607": "Malformed changes should have been caught on original application.", + "0x608": "An edit is not open.", + "0x609": "An edit is not open.", + "0x60a": "Duplicate acked edit.", + "0x60b": "local edit should be local", + "0x60c": "Sequenced edits should carry a monotonically increasing min number", + "0x60d": "Duplicate acked edit.", + "0x60e": "Causal ordering should be upheld", + "0x60f": "Edits should never be evicted if the target length is set to infinity", + "0x610": "node must have either both parent and traitParent set or neither", + "0x611": "any trait arrays present in a node must be non-empty", + "0x612": "can not give a child multiple parents", + "0x613": "can not add node with already existing id", + "0x614": "invalid attach index", + "0x615": "can not insert children under node that does not exist", + "0x616": "invalid attach index", + "0x617": "can not attach node that already has a parent", + "0x618": "invalid detach index range", + "0x619": "can not detach children under node that does not exist", + "0x61a": "invalid detach index range", + "0x61b": "can not replace payload for node that does not exist", + "0x61c": "deleted nodes must be unparented", + "0x61d": "trait exists", + "0x61e": "node is parented incorrectly", + "0x61f": "trait is present but empty", + "0x620": "child in trait is not in forest", + "0x621": "child is not parented", + "0x622": "child parent pointer is incorrect", + "0x623": "the item tree tree must not contain cycles or multi-parented nodes", + "0x624": "cached parent is incorrect", + "0x625": "Node is not parented", + "0x626": "Cannot revert Build: destination is already used by a Build", + "0x627": "Cannot revert Build: destination is already used by a Detach", + "0x628": "revision must be an integer", + "0x629": "revision must correspond to the result of a SequencedEdit", + "0x62a": "revision not stored in memory", + "0x62b": "Local revision view cached out of order.", + "0x62c": "retentionWindowStart must be initialized >= 0", + "0x62d": "Client id should be set if connected.", + "0x62e": "Summarizing should not occur with local edits except on first attach.", + "0x62f": "generateSummary must not be called with local edits", + "0x630": "Summary load should not be executed after local state is created.", + "0x631": "Attempted to submit op of wrong version", + "0x632": "Stashed op applied after expected window", + "0x633": "0.1.1 summary encountered with non-object edit history.", + "0x634": "Missing editIds on 0.1.1 summary.", + "0x635": "root definition and identifier should be immutable.", + "0x636": "0.0.2 summary encountered with missing sequencedEdits field.", + "0x637": "root definition and identifier should be immutable.", + "0x638": "transaction has already been closed", + "0x639": "Editor must be open to apply changes.", + "0x63a": "identityHash constraint is not implemented", + "0x63b": "contentHash constraint is not implemented", + "0x63c": "Root node was compressed with no ID", + "0x63d": "Delta can only be calculated between views that share a root", + "0x63e": "Keys in map must be sorted.", + "0x63f": "Values in map must be sorted.", + "0x640": "Clusters must have a positive capacity", + "0x641": "Clusters must not exceed max cluster size", + "0x642": "reservedIdCount must be non-negative", + "0x643": "Non-empty range must properly consume local IDs", + "0x644": "A session's attribution ID may never be modified.", + "0x645": "Ranges finalized out of order.", + "0x646": "Cannot finalize an empty range.", + "0x647": "The number of allocated final IDs must not exceed the JS maximum safe integer.", + "0x648": "Cluster already exists for session but there is no finalized local ID", + "0x649": "The number of allocated final IDs must not exceed the JS maximum safe integer.", + "0x64a": "Override IDs must be in sorted order.", + "0x64b": "Ranges finalized out of order.", + "0x64c": "Malformed range: override ID ahead of range start.", + "0x64d": "No cluster was created when overflow occurred.", + "0x64e": "No cluster exists but IDs were finalized.", + "0x64f": "Cannot have multiple local IDs with identical overrides.", + "0x650": "Cannot add a second override for final id", + "0x651": "Session ID Normalizer produced unexpected local ID", + "0x652": "Session ID Normalizer produced unexpected local ID", + "0x653": "session not serialized", + "0x654": "Cannot resume existing session.", + "0x655": "AttributionId index out of bounds", + "0x656": "malformed IdCreationRange", + "0x657": "Local ID space must start at -1.", + "0x658": "Local ID space must be contiguous.", + "0x659": "Malformed normalization range.", + "0x65a": "Gaps in final space must align to a local.", + "0x65b": "Malformed normalization block.", + "0x65c": "Final ID block should not be registered without an existing local range.", + "0x65d": "Empty range should not be reified.", + "0x65e": "StableRange must be constructed with endpoints from the same trait", + "0x65f": "StableRange must be constructed with endpoints from the same trait", + "0x660": "", + "0x661": "unexpected detached ID", + "0x662": "unexpected detached ID", + "0x668": "Version numbers should follow semantic versioning.", + "0x669": "tree must be a new SharedTree", + "0x66a": "Undo is not yet supported during transactions", + "0x66b": "Undo is not yet supported during transactions", + "0x66c": "A modify mark must have length equal to one", + "0x66d": "Cannot close a transaction that has already failed. Use abortEdit instead." }; diff --git a/packages/runtime/test-runtime-utils/src/validateAssertionError.ts b/packages/runtime/test-runtime-utils/src/validateAssertionError.ts index 0679ab5aada3..8138a5ec9ea7 100644 --- a/packages/runtime/test-runtime-utils/src/validateAssertionError.ts +++ b/packages/runtime/test-runtime-utils/src/validateAssertionError.ts @@ -22,9 +22,13 @@ import { shortCodeMap } from "./assertionShortCodesMap"; * @returns `true` if the message in the error object that was passed in matches the expected * message. Otherwise it throws an error. */ -export function validateAssertionError(error: Error, expectedErrorMsg: string): boolean { +export function validateAssertionError(error: Error, expectedErrorMsg: string | RegExp): boolean { const mappedMsg = (shortCodeMap[error.message] as string) ?? error.message; - if (mappedMsg !== expectedErrorMsg) { + if ( + typeof expectedErrorMsg === "string" + ? mappedMsg !== expectedErrorMsg + : !expectedErrorMsg.test(mappedMsg) + ) { // This throws an Error instead of an AssertionError because AssertionError would require a dependency on the // node assert library, which we don't want to do for this library because it's used in the browser. throw new Error(`Unexpected assertion thrown: ${error.message} ('${mappedMsg}')`);