Skip to content
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

Fix and reorganize stashed ops that wait for a summary #23542

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

dannimad
Copy link
Contributor

@dannimad dannimad commented Jan 14, 2025

Some tests among stashedOps.spec.ts need to wait for a summary in order to fall into its test scenario. There was some missing configuration on them:

  • ensureSynchonized should be called before summarizing to ensure that it summaries all changes up to that point.
  • When created the provider via getTestObjectProvider, pass in option { syncSummarizer: true } so that ensureSynchonized will sync summarizer clients.
  • explicitly load summarizers from the snapshot generated by previous summarizers by specifying the version of that snapshot

Besides those changes, I made some reorganization to these tests since added config is necessary only for tests that wait for a summary so I separated them into a different file and move some functions to a utils file well.

Some of these tests are currently failing in ODSP and so I'll head up OCE in case they keep failing after this PR is merged. If this approach work, I'll use it for refresh snapshot tests that are currently failing for what I suspect the same reason.

@Copilot Copilot bot review requested due to automatic review settings January 14, 2025 02:01
@github-actions github-actions bot added base: main PRs targeted against main branch area: tests Tests to add, test infrastructure improvements, etc labels Jan 14, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

packages/test/test-end-to-end-tests/src/test/offline/offlineTestsUtils.ts:43

  • [nitpick] The magic number 30 should be defined as a constant or passed as a parameter for better readability and maintainability.
const lots = 30;

packages/test/test-end-to-end-tests/src/test/offline/containerDirtyFlag.spec.ts:31

  • Ensure that map2 is properly initialized before using it. Add a check to verify that map2 is not null or undefined.
const map2 = await dataStore2.getSharedObject<ISharedMap>(mapId);

export async function loadOffline(
testContainerConfig: ITestContainerConfig,
testObjectProvider: ITestObjectProvider,
request: IRequest,
Copy link
Preview

Copilot AI Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request parameter should be validated to ensure it is not null or undefined before being used.

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
(getTestObjectProvider, apis) => {
const mapId = "map";
const { SharedMap } = apis.dds;
const registry: ChannelFactoryRegistry = [[mapId, SharedMap.getFactory()]];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really need a map? It would be simpler and less code to use the default root DDS that the test data object exposes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map is not really needed but I'm trying to keep tests as close as we have them as stashedOps.spec.ts, so it's more just for consistency purposes.

let map1: ISharedMap;
let dataStore1: ITestFluidObject;
const testContainerConfig: ITestContainerConfig = {
fluidDataObjectType: DataObjectFactoryType.Test,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to pass this? The default test object created by the test object provider should be sufficient right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as map, just trying to keep it consistent with other offline tests.

loader,
provider.driver.createCreateNewRequest(provider.documentId),
);
provider.updateDocumentId(container.resolvedUrl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason for this 3-step process to create a container rather than just doing provider.createTestContainer(mainContainerConfig)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, consistency purposes. I'll consider making those suggestions to other offline tests as well if there is a strong reason for them.

await waitForContainerConnection(container2);
await provider.ensureSynchronized();

assert.strictEqual(map2.get("1"), "1");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add messages to assert so that when one of these is hit, it's possible to tell which assert was hit. As of now, if this fails in say CI, there will be no way to tell which one of these asserts is hit making debugging harder.


it("can stash between summary op and ack", async function () {
const waitForSummaryPromise = waitForSummary(provider, container, testContainerConfig);
const pendingOps = await new Promise<string | undefined>((resolve, reject) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use timeoutPromise or timeoutAwait here in case the summarize op never comes.

* @param pendingLocalState - (Optional) custom PendingLocalState to load from. Defaults to using getPendingOps helper if omitted.
* @returns A container instance with a connect function to unblock the Driver (simulating coming back from offline)
*/
export async function loadOffline(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call this loadContainerWithDeferredConnection or something along those lines. As of now, this name doesn't align with what it does.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of now, this name doesn't align with what it does.

Not sure I see your point. What does "load offline" mean to you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really mean anything to me. The fact that it loads a container is not obvious. loadOfflineContainer or loadContainerOffline would be understandable.

/**
* load container, pause, create (local) ops from callback, then optionally send ops before closing container
*/
export const getPendingOps = async (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call this loadContainerAndGetPendingState. It's confusing that getPendingOps would load a container as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe generatePendingState? It needs a live container to do so, but it closes that container before returning, so I wouldn't put loadContainer in the title.


map1.set("2", "2");
await waitForSummary(provider, container, testContainerConfig, summaryVersion);
const container2 = await loader.resolve({ url }, pendingOps);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that container2 doesn't load from the summary that happens before this step?

type ITestObjectProvider,
} from "@fluidframework/test-utils/internal";

import { wrapObjectAndOverride } from "../mocking.js";

// eslint-disable-next-line import/no-internal-modules
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you avoid this by re-exporting under /offline/index.ts? Not sure if there's precedence either way.

Could also consider moving that utils file to another package, probably @fluidframework/test-utils? (tag it as internal)

Comment on lines +226 to +232
const offlineObject = await loadOffline(
testContainerConfig,
provider,
{ url },
pendingOps,
);
container2 = offlineObject.container;
Copy link
Member

@markfields markfields Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, if you like it: This avoids having to name offlineObject variable

Suggested change
const offlineObject = await loadOffline(
testContainerConfig,
provider,
{ url },
pendingOps,
);
container2 = offlineObject.container;
const { container } = await loadOffline(
testContainerConfig,
provider,
{ url },
pendingOps,
);
container2 = container;

url = await container.getAbsoluteUrl("");
dataStore1 = (await container.getEntryPoint()) as ITestFluidObject;
map1 = await dataStore1.getSharedObject<ISharedMap>(mapId);
map1.set("1", "1");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you doing this to force a write connection? Nice to put a comment to that effect (some places even do set("force", "write connection"); 🤷‍♂️


it("works with summary while offline", async function () {
const summaryVersion = await waitForSummary(provider, container, testContainerConfig);
const pendingOps = await getPendingOps(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I right that the container used here is totally discarded never to be used again? And not just the container instance but also the backing file that's created?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: tests Tests to add, test infrastructure improvements, etc base: main PRs targeted against main branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants