Skip to content

Commit 5b9e9a0

Browse files
committed
Change cleanup to dispose. Apply suggestions.
1 parent 4d16e9c commit 5b9e9a0

File tree

8 files changed

+225
-182
lines changed

8 files changed

+225
-182
lines changed

.changeset/nice-carrots-sink.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"miniflare": patch
3+
---
4+
5+
Include workflow bining name in workflow plugin.

fixtures/vitest-pool-workers-examples/workflows/test/integration.test.ts

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ const STATUS_COMPLETE = "complete";
55
const STEP_NAME = "AI content scan";
66
const mockResult = { violationScore: 0 };
77

8+
// This example implicitly disposes the Workflow instance
89
it("workflow should be able to reach the end and be successful", async () => {
9-
// This example shows how to implicitly cleanUp Workflow instances
10-
11-
// CONFIG with `using` to ensure Workflow instances cleanup:
10+
// CONFIG with `await using` to ensure Workflow instances cleanup:
1211
await using introspector = await introspectWorkflow(env.MODERATOR);
13-
introspector.modifyAll(async (m) => {
12+
await introspector.modifyAll(async (m) => {
1413
await m.disableSleeps();
1514
await m.mockStepResult({ name: STEP_NAME }, mockResult);
1615
});
@@ -25,36 +24,39 @@ it("workflow should be able to reach the end and be successful", async () => {
2524
expect(await instance.waitForStepResult({ name: STEP_NAME })).toEqual(
2625
mockResult
2726
);
28-
await instance.waitForStatus(STATUS_COMPLETE);
27+
await expect(instance.waitForStatus(STATUS_COMPLETE)).resolves.not.toThrow();
2928

30-
// CLEANUP: ensured by `using`
29+
// DISPOSE: ensured by `await using`
3130
});
3231

32+
// This example explicitly disposes the Workflow instances
3333
it("workflow batch should be able to reach the end and be successful", async () => {
34-
// This example shows how to explicitly cleanUp Workflow instances
35-
3634
// CONFIG:
3735
let introspector = await introspectWorkflow(env.MODERATOR);
38-
introspector.modifyAll(async (m) => {
39-
await m.disableSleeps();
40-
await m.mockStepResult({ name: STEP_NAME }, mockResult);
41-
});
42-
43-
await SELF.fetch(`https://mock-worker.local/moderate-batch`);
44-
45-
const instances = introspector.get();
46-
expect(instances.length).toBe(3);
47-
48-
// ASSERTIONS:
49-
for (const instance of instances) {
50-
expect(await instance.waitForStepResult({ name: STEP_NAME })).toEqual(
51-
mockResult
52-
);
53-
await instance.waitForStatus(STATUS_COMPLETE);
36+
try {
37+
await introspector.modifyAll(async (m) => {
38+
await m.disableSleeps();
39+
await m.mockStepResult({ name: STEP_NAME }, mockResult);
40+
});
41+
42+
await SELF.fetch(`https://mock-worker.local/moderate-batch`);
43+
44+
const instances = introspector.get();
45+
expect(instances.length).toBe(3);
46+
47+
// ASSERTIONS:
48+
for (const instance of instances) {
49+
expect(await instance.waitForStepResult({ name: STEP_NAME })).toEqual(
50+
mockResult
51+
);
52+
await expect(
53+
instance.waitForStatus(STATUS_COMPLETE)
54+
).resolves.not.toThrow();
55+
}
56+
} finally {
57+
// DISPOSE:
58+
// Workflow introspector should be disposed the end of each test, if no `await using` dyntax is used
59+
// Also disposes all intercepted instances
60+
await introspector.dispose();
5461
}
55-
56-
// CLEANUP:
57-
// Workflow introspector should be cleaned at the end of/after each test, if no `using` keyword is used for the introspector
58-
// Cleans up all intercepted instances
59-
await introspector.cleanUp();
6062
});

fixtures/vitest-pool-workers-examples/workflows/test/unit.test.ts

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ const STATUS_COMPLETE = "complete";
66
const STATUS_ERROR = "errored";
77
const STEP_NAME = "AI content scan";
88

9+
// This example implicitly disposes the Workflow instance
910
it("should mock a non-violation score and complete", async () => {
1011
const mockResult = { violationScore: 0 };
1112

13+
// CONFIG with `await using` to ensure Workflow instances cleanup:
1214
await using instance = await introspectWorkflowInstance(
1315
env.MODERATOR,
1416
INSTANCE_ID
@@ -22,45 +24,58 @@ it("should mock a non-violation score and complete", async () => {
2224
id: INSTANCE_ID,
2325
});
2426

27+
// ASSERTIONS:
2528
expect(await instance.waitForStepResult({ name: STEP_NAME })).toEqual(
2629
mockResult
2730
);
2831
expect(
2932
await instance.waitForStepResult({ name: "auto approve content" })
3033
).toEqual({ status: "auto_approved" });
3134

32-
await instance.waitForStatus(STATUS_COMPLETE);
35+
await expect(instance.waitForStatus(STATUS_COMPLETE)).resolves.not.toThrow();
36+
37+
// DISPOSE: ensured by `await using`
3338
});
3439

40+
// This example explicitly disposes the Workflow instance
3541
it("should mock the violation score calculation to fail 2 times and then complete", async () => {
3642
const mockResult = { violationScore: 0 };
3743

38-
await using instance = await introspectWorkflowInstance(
39-
env.MODERATOR,
40-
INSTANCE_ID
41-
);
42-
await instance.modify(async (m) => {
43-
await m.disableSleeps();
44-
await m.mockStepError(
45-
{ name: STEP_NAME },
46-
new Error("Something went wrong!"),
47-
2
48-
);
49-
await m.mockStepResult({ name: STEP_NAME }, mockResult);
50-
});
51-
52-
await env.MODERATOR.create({
53-
id: INSTANCE_ID,
54-
});
44+
// CONFIG:
45+
const instance = await introspectWorkflowInstance(env.MODERATOR, INSTANCE_ID);
46+
47+
try {
48+
await instance.modify(async (m) => {
49+
await m.disableSleeps();
50+
await m.mockStepError(
51+
{ name: STEP_NAME },
52+
new Error("Something went wrong!"),
53+
2
54+
);
55+
await m.mockStepResult({ name: STEP_NAME }, mockResult);
56+
});
5557

56-
expect(await instance.waitForStepResult({ name: STEP_NAME })).toEqual(
57-
mockResult
58-
);
59-
expect(
60-
await instance.waitForStepResult({ name: "auto approve content" })
61-
).toEqual({ status: "auto_approved" });
58+
await env.MODERATOR.create({
59+
id: INSTANCE_ID,
60+
});
6261

63-
await instance.waitForStatus(STATUS_COMPLETE);
62+
// ASSERTIONS:
63+
expect(await instance.waitForStepResult({ name: STEP_NAME })).toEqual(
64+
mockResult
65+
);
66+
expect(
67+
await instance.waitForStepResult({ name: "auto approve content" })
68+
).toEqual({ status: "auto_approved" });
69+
70+
await expect(
71+
instance.waitForStatus(STATUS_COMPLETE)
72+
).resolves.not.toThrow();
73+
} finally {
74+
// DISPOSE:
75+
// Workflow introspector should be disposed the end of each test, if no `await using` dyntax is used
76+
// Also disposes all intercepted instances
77+
await instance.dispose();
78+
}
6479
});
6580

6681
it("should mock a violation score and complete", async () => {
@@ -86,7 +101,7 @@ it("should mock a violation score and complete", async () => {
86101
await instance.waitForStepResult({ name: "auto reject content" })
87102
).toEqual({ status: "auto_rejected" });
88103

89-
await instance.waitForStatus(STATUS_COMPLETE);
104+
await expect(instance.waitForStatus(STATUS_COMPLETE)).resolves.not.toThrow();
90105
});
91106

92107
it("should be reviewed, accepted and complete", async () => {
@@ -116,7 +131,7 @@ it("should be reviewed, accepted and complete", async () => {
116131
await instance.waitForStepResult({ name: "apply moderator decision" })
117132
).toEqual({ status: "moderated", decision: "approve" });
118133

119-
await instance.waitForStatus(STATUS_COMPLETE);
134+
await expect(instance.waitForStatus(STATUS_COMPLETE)).resolves.not.toThrow();
120135
});
121136

122137
it("should force human review to timeout and error", async () => {
@@ -140,5 +155,5 @@ it("should force human review to timeout and error", async () => {
140155
mockResult
141156
);
142157

143-
await instance.waitForStatus(STATUS_ERROR);
158+
await expect(instance.waitForStatus(STATUS_ERROR)).resolves.not.toThrow();
144159
});

packages/vitest-pool-workers/src/pool/loopback.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,7 @@ function checkAllStorageOperationsResolved(
360360
"",
361361
separator,
362362
`Workflows are being created in ${source}.`,
363-
"Isolated storage is enabled, requiring all created Workflow instances to be cleaned/disposed at the end of each test.",
364-
"Alternatively, if isolated storage is not required and Workflow instance state needs to be preserved across tests, `isolatedStorage` must be set to false in vitest.config.ts.",
363+
"Even with isolated storage, Workflows are required to be manually disposed at the end of each test.",
365364
"See https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/ for more details.",
366365
"",
367366
].join("\n")

packages/vitest-pool-workers/src/worker/workflows.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ export interface WorkflowInstanceIntrospector {
2020

2121
waitForStatus(status: string): Promise<void>;
2222

23-
cleanUp(): Promise<void>;
23+
dispose(): Promise<void>;
2424
}
2525

26+
// Note(osilva): `introspectWorkflowInstance()` doesn’t need to be async, but we keep it that way
27+
// to avoid potential breaking changes later and to stay consistent with `introspectWorkflow`.
28+
29+
// In the "cloudflare:test" module, the exposed type is `Workflow`. Here we use `WorkflowBinding`
30+
// (which implements `Workflow`) to access unsafe functions.
2631
export async function introspectWorkflowInstance(
2732
workflow: WorkflowBinding,
2833
instanceId: string
@@ -85,24 +90,29 @@ class WorkflowInstanceIntrospectorHandle
8590
await this.#workflow.unsafeWaitForStatus(this.#instanceId, status);
8691
}
8792

88-
async cleanUp(): Promise<void> {
89-
await this.#workflow.unsafeAbort(this.#instanceId, "Instance clean up");
93+
async dispose(): Promise<void> {
94+
await this.#workflow.unsafeAbort(this.#instanceId, "Instance dispose");
9095
}
9196

9297
async [Symbol.asyncDispose](): Promise<void> {
93-
await this.cleanUp();
98+
await this.dispose();
9499
}
95100
}
96101

97102
// See public facing `cloudflare:test` types for docs
98103
export interface WorkflowIntrospector {
99-
modifyAll(fn: ModifierCallback): void;
104+
modifyAll(fn: ModifierCallback): Promise<void>;
100105

101106
get(): WorkflowInstanceIntrospector[];
102107

103-
cleanUp(): Promise<void>;
108+
dispose(): Promise<void>;
104109
}
105110

111+
// Note(osilva): `introspectWorkflow` could be sync with some changes, but we keep it async
112+
// to avoid potential breaking changes later.
113+
114+
// In the "cloudflare:test" module, the exposed type is `Workflow`. Here we use `WorkflowBinding`
115+
// (which implements `Workflow`) to access unsafe functions.
106116
export async function introspectWorkflow(
107117
workflow: WorkflowBinding
108118
): Promise<WorkflowIntrospectorHandle> {
@@ -189,7 +199,7 @@ export async function introspectWorkflow(
189199
};
190200
};
191201

192-
const cleanup = () => {
202+
const dispose = () => {
193203
internalEnv[bindingName] = internalOriginalWorkflow;
194204
env[bindingName] = externalOriginalWorkflow;
195205
};
@@ -209,47 +219,47 @@ export async function introspectWorkflow(
209219
workflow,
210220
modifierCallbacks,
211221
instanceIntrospectors,
212-
cleanup
222+
dispose
213223
);
214224
}
215225

216226
class WorkflowIntrospectorHandle implements WorkflowIntrospector {
217227
workflow: WorkflowBinding;
218228
#modifierCallbacks: ModifierCallback[];
219229
#instanceIntrospectors: WorkflowInstanceIntrospector[];
220-
#cleanupCallback: () => void;
230+
#disposeCallback: () => void;
221231

222232
constructor(
223233
workflow: WorkflowBinding,
224234
modifierCallbacks: ModifierCallback[],
225235
instanceIntrospectors: WorkflowInstanceIntrospector[],
226-
cleanupCallback: () => void
236+
disposeCallback: () => void
227237
) {
228238
this.workflow = workflow;
229239
this.#modifierCallbacks = modifierCallbacks;
230240
this.#instanceIntrospectors = instanceIntrospectors;
231-
this.#cleanupCallback = cleanupCallback;
241+
this.#disposeCallback = disposeCallback;
232242
}
233243

234-
modifyAll(fn: ModifierCallback): void {
244+
async modifyAll(fn: ModifierCallback): Promise<void> {
235245
this.#modifierCallbacks.push(fn);
236246
}
237247

238248
get(): WorkflowInstanceIntrospector[] {
239249
return this.#instanceIntrospectors;
240250
}
241251

242-
async cleanUp(): Promise<void> {
243-
// also cleans all instance introspectors
252+
async dispose(): Promise<void> {
253+
// also disposes all instance introspectors
244254
await Promise.all(
245-
this.#instanceIntrospectors.map((introspector) => introspector.cleanUp())
255+
this.#instanceIntrospectors.map((introspector) => introspector.dispose())
246256
);
247257
this.#modifierCallbacks = [];
248258
this.#instanceIntrospectors = [];
249-
this.#cleanupCallback();
259+
this.#disposeCallback();
250260
}
251261

252262
async [Symbol.asyncDispose](): Promise<void> {
253-
await this.cleanUp();
263+
await this.dispose();
254264
}
255265
}

0 commit comments

Comments
 (0)