-
Notifications
You must be signed in to change notification settings - Fork 557
feat(directory): Add oracle for event testing #25477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 12 commits
67b9933
0145b44
eb435c8
f34b302
6270c98
8a5cb97
e5b9d7d
5daccaf
b92a838
85c3d42
0d88469
1e0cfac
2ba78e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| /*! | ||
| * Copyright (c) Microsoft Corporation and contributors. All rights reserved. | ||
| * Licensed under the MIT License. | ||
| */ | ||
|
|
||
| import { strict as assert } from "node:assert"; | ||
|
|
||
| import type { IEventThisPlaceHolder } from "@fluidframework/core-interfaces"; | ||
|
|
||
| import type { | ||
| IDirectory, | ||
| IDirectoryValueChanged, | ||
| ISharedDirectory, | ||
| IValueChanged, | ||
| } from "../interfaces.js"; | ||
|
|
||
| /** | ||
| * Oracle for directory | ||
| * @internal | ||
| */ | ||
| export class SharedDirectoryOracle { | ||
| private readonly model = new Map<string, unknown>(); | ||
|
|
||
| public constructor(private readonly sharedDir: ISharedDirectory) { | ||
| this.sharedDir.on("valueChanged", this.onValueChanged); | ||
| this.sharedDir.on("clear", this.onClear); | ||
| this.sharedDir.on("subDirectoryCreated", this.onSubDirCreated); | ||
| this.sharedDir.on("subDirectoryDeleted", this.onSubDirDeleted); | ||
| this.sharedDir.on("containedValueChanged", this.onContainedValueChanged); | ||
|
|
||
| this.takeSnapshot(sharedDir); | ||
| } | ||
|
|
||
| private takeSnapshot(dir: ISharedDirectory | IDirectory): void { | ||
| for (const [k, v] of this.sharedDir.entries()) { | ||
| this.model.set(k, v); | ||
| } | ||
| } | ||
|
|
||
| private readonly onValueChanged = ( | ||
| changed: IDirectoryValueChanged, | ||
| local: boolean, | ||
| target: IEventThisPlaceHolder, | ||
| ): void => { | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
| const { path, key, previousValue } = changed; | ||
| const fullPath = path === "/" ? `/${key}` : `${path}/${key}`; | ||
|
|
||
| if (this.model.has(fullPath)) { | ||
| const prevVal = this.model.get(fullPath); | ||
| assert.strictEqual( | ||
| prevVal, | ||
| previousValue, | ||
| `previous value mismatch at ${fullPath}: expected: ${prevVal}, actual: ${previousValue}`, | ||
| ); | ||
| } | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
| const newVal = this.sharedDir.get(fullPath); | ||
|
||
|
|
||
| if (newVal === undefined) { | ||
| // deletion | ||
| this.model.delete(fullPath); | ||
| } else { | ||
| this.model.set(fullPath, newVal); | ||
| } | ||
| }; | ||
|
|
||
| private readonly onClear = (local: boolean, target: IEventThisPlaceHolder): void => { | ||
| this.model.clear(); | ||
| }; | ||
|
|
||
| private readonly onSubDirCreated = ( | ||
| path: string, | ||
| local: boolean, | ||
| target: IEventThisPlaceHolder, | ||
| ): void => { | ||
| this.model.set(path, undefined); | ||
| }; | ||
|
|
||
| private readonly onSubDirDeleted = ( | ||
| path: string, | ||
| local: boolean, | ||
| target: IEventThisPlaceHolder, | ||
| ): void => { | ||
| this.model.delete(path); | ||
| }; | ||
|
|
||
| private readonly onContainedValueChanged = ( | ||
| changed: IValueChanged, | ||
| local: boolean, | ||
| target: IEventThisPlaceHolder, | ||
| ): void => { | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
| const { key, previousValue } = changed; | ||
|
|
||
| if (this.model.has(key)) { | ||
| const prevVal = this.model.get(key); | ||
| assert.strictEqual( | ||
| prevVal, | ||
| previousValue, | ||
| `contained previous value mismatch at ${key}: expected: ${prevVal}, actual: ${previousValue}`, | ||
| ); | ||
| } | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
| const newVal = this.sharedDir.get(key); | ||
| if (newVal === undefined) { | ||
| this.model.delete(key); | ||
| } else { | ||
| this.model.set(key, newVal); | ||
| } | ||
| }; | ||
|
|
||
| public validate(): void { | ||
| for (const [pathKey, value] of this.model.entries()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm not sure this is correct. i think entries just returns the keys of the directory and you have to use subdirectories to get the subdirectories There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
nvm - subdirs are not pushed to the snapshot. looking into it... |
||
| const parts = pathKey.split("/").filter((p) => p.length > 0); | ||
| assert(parts.length > 0, "Invalid path, cannot extract key"); | ||
|
||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| const leafKey = parts.pop()!; // The actual key | ||
| let dir: IDirectory | undefined = this.sharedDir; | ||
|
|
||
| for (const part of parts) { | ||
| dir = dir.getSubDirectory(part); | ||
| if (!dir) break; | ||
| } | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
| const actual = dir?.get(leafKey); | ||
| assert.deepStrictEqual( | ||
| actual, | ||
| value, | ||
| `SharedDirectoryOracle mismatch at path="${pathKey}" with actual value = ${actual} and oracle value = ${value} with model entries = ${JSON.stringify(this.model.entries())}}`, | ||
sonalideshpandemsft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ); | ||
| } | ||
| } | ||
|
|
||
| public dispose(): void { | ||
| this.sharedDir.off("valueChanged", this.onValueChanged); | ||
| this.sharedDir.off("clear", this.onClear); | ||
| this.sharedDir.off("subDirectoryCreated", this.onSubDirCreated); | ||
| this.sharedDir.off("subDirectoryDeleted", this.onSubDirDeleted); | ||
| this.sharedDir.off("containedValueChanged", this.onContainedValueChanged); | ||
| this.model.clear(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,11 +5,18 @@ | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import * as dirPath from "node:path"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import { TypedEventEmitter } from "@fluid-internal/client-utils"; | ||||||||||||||||||||||||||||||||||||||
| import { takeAsync } from "@fluid-private/stochastic-test-utils"; | ||||||||||||||||||||||||||||||||||||||
| import { type DDSFuzzModel, createDDSFuzzSuite } from "@fluid-private/test-dds-utils"; | ||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||
| type DDSFuzzHarnessEvents, | ||||||||||||||||||||||||||||||||||||||
| type DDSFuzzModel, | ||||||||||||||||||||||||||||||||||||||
| createDDSFuzzSuite, | ||||||||||||||||||||||||||||||||||||||
| registerOracle, | ||||||||||||||||||||||||||||||||||||||
| } from "@fluid-private/test-dds-utils"; | ||||||||||||||||||||||||||||||||||||||
| import { FlushMode } from "@fluidframework/runtime-definitions/internal"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import { DirectoryFactory } from "../../index.js"; | ||||||||||||||||||||||||||||||||||||||
| import { SharedDirectoryOracle } from "../directoryOracle.js"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import { assertEquivalentDirectories } from "./directoryEquivalenceUtils.js"; | ||||||||||||||||||||||||||||||||||||||
| import { _dirname } from "./dirname.cjs"; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -21,6 +28,16 @@ import { | |||||||||||||||||||||||||||||||||||||
| type DirOperation, | ||||||||||||||||||||||||||||||||||||||
| type DirOperationGenerationConfig, | ||||||||||||||||||||||||||||||||||||||
| } from "./fuzzUtils.js"; | ||||||||||||||||||||||||||||||||||||||
| import { hasSharedDirectroyOracle, type ISharedDirectoryWithOracle } from "./oracleUtils.js"; | ||||||||||||||||||||||||||||||||||||||
sonalideshpandemsft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const oracleEmitter = new TypedEventEmitter<DDSFuzzHarnessEvents>(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| oracleEmitter.on("clientCreate", (client) => { | ||||||||||||||||||||||||||||||||||||||
| const channel = client.channel as ISharedDirectoryWithOracle; | ||||||||||||||||||||||||||||||||||||||
| const directroyOracle = new SharedDirectoryOracle(channel); | ||||||||||||||||||||||||||||||||||||||
| channel.sharedDirectoryOracle = directroyOracle; | ||||||||||||||||||||||||||||||||||||||
| registerOracle(directroyOracle); | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
| const directroyOracle = new SharedDirectoryOracle(channel); | |
| channel.sharedDirectoryOracle = directroyOracle; | |
| registerOracle(directroyOracle); | |
| const directoryOracle = new SharedDirectoryOracle(channel); | |
| channel.sharedDirectoryOracle = directoryOracle; | |
| registerOracle(directoryOracle); |
Outdated
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable reference has a typo: 'directroyOracle' should be 'directoryOracle'
| const directroyOracle = new SharedDirectoryOracle(channel); | |
| channel.sharedDirectoryOracle = directroyOracle; | |
| registerOracle(directroyOracle); | |
| const directoryOracle = new SharedDirectoryOracle(channel); | |
| channel.sharedDirectoryOracle = directoryOracle; | |
| registerOracle(directoryOracle); |
Outdated
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable reference has a typo: 'directroyOracle' should be 'directoryOracle'
| const directroyOracle = new SharedDirectoryOracle(channel); | |
| channel.sharedDirectoryOracle = directroyOracle; | |
| registerOracle(directroyOracle); | |
| const directoryOracle = new SharedDirectoryOracle(channel); | |
| channel.sharedDirectoryOracle = directoryOracle; | |
| registerOracle(directoryOracle); |
Outdated
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function call has a typo: 'hasSharedDirectroyOracle' should be 'hasSharedDirectoryOracle'
sonalideshpandemsft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,7 +29,7 @@ import { | |
| } from "../../index.js"; | ||
|
|
||
| import { assertEquivalentDirectories } from "./directoryEquivalenceUtils.js"; | ||
| import { hasSharedMapOracle } from "./oracleUtils.js"; | ||
| import { hasSharedMapOracle, hasSharedDirectroyOracle } from "./oracleUtils.js"; | ||
sonalideshpandemsft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Represents a map clear operation. | ||
|
|
@@ -518,7 +518,16 @@ export const baseDirModel: DDSFuzzModel<DirectoryFactory, DirOperation> = { | |
| workloadName: "default directory 1", | ||
| generatorFactory: () => takeAsync(100, makeDirOperationGenerator(dirDefaultOptions)), | ||
| reducer: makeDirReducer({ clientIds: ["A", "B", "C"], printConsoleLogs: false }), | ||
| validateConsistency: async (a, b) => assertEquivalentDirectories(a.channel, b.channel), | ||
| validateConsistency: async (a, b) => { | ||
| if (hasSharedDirectroyOracle(a.channel)) { | ||
|
||
| a.channel.sharedDirectoryOracle.validate(); | ||
| } | ||
|
|
||
| if (hasSharedDirectroyOracle(b.channel)) { | ||
sonalideshpandemsft marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| b.channel.sharedDirectoryOracle.validate(); | ||
| } | ||
| return assertEquivalentDirectories(a.channel, b.channel); | ||
| }, | ||
| factory: new DirectoryFactory(), | ||
| minimizationTransforms: [ | ||
| (op: DirOperation): void => { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.