@neuralnomads/codenomad-dev 0.18.0-dev-20260715-07e8ce68 → 0.18.0-dev-20260716-40cee071
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/dist/events/bus.js +13 -0
- package/dist/events/bus.test.js +37 -0
- package/dist/index.js +33 -56
- package/dist/index.test.js +32 -0
- package/dist/server/__tests__/remote-proxy.test.js +102 -6
- package/dist/server/http-server.js +2 -0
- package/dist/server/remote-proxy.js +96 -9
- package/dist/server/routes/usage.js +18 -0
- package/dist/server/routes/usage.test.js +26 -0
- package/dist/server/routes/workspaces.js +32 -1
- package/dist/server/routes/workspaces.test.js +75 -0
- package/dist/settings/binaries.js +4 -1
- package/dist/settings/binaries.test.js +24 -0
- package/dist/shutdown.js +75 -0
- package/dist/shutdown.test.js +111 -0
- package/dist/usage/providers/api-key.js +171 -0
- package/dist/usage/providers/google.js +171 -0
- package/dist/usage/providers/minimax.js +109 -0
- package/dist/usage/providers/oauth.js +111 -0
- package/dist/usage/providers/special.js +148 -0
- package/dist/usage/service.js +83 -0
- package/dist/usage/service.test.js +59 -0
- package/dist/usage/shared.js +130 -0
- package/dist/usage/shared.test.js +33 -0
- package/dist/usage/types.js +1 -0
- package/dist/workspaces/__tests__/spawn.test.js +64 -7
- package/dist/workspaces/__tests__/workspace-identity.test.js +177 -25
- package/dist/workspaces/launch-cleanup.js +132 -0
- package/dist/workspaces/launch-cleanup.test.js +33 -0
- package/dist/workspaces/manager.js +393 -135
- package/dist/workspaces/manager.test.js +318 -0
- package/dist/workspaces/process-identity.js +408 -0
- package/dist/workspaces/process-identity.test.js +165 -0
- package/dist/workspaces/runtime.js +567 -122
- package/dist/workspaces/runtime.test.js +603 -0
- package/dist/workspaces/spawn.js +86 -15
- package/package.json +1 -1
- package/public/assets/{FilesTab-EPvldvn1.js → FilesTab-BjztpSdj.js} +2 -2
- package/public/assets/{GitChangesTab-ofjovmv2.js → GitChangesTab-B9XraSoR.js} +2 -2
- package/public/assets/{SplitFilePanel-CyoPkcLd.js → SplitFilePanel-Cu_5-Z-E.js} +1 -1
- package/public/assets/StatusTab-D_IE79r-.js +1 -0
- package/public/assets/{align-justify-Bcf1-ZOl.js → align-justify-BKS9gyRk.js} +1 -1
- package/public/assets/{bundle-full-D5dzvToA.js → bundle-full-CXGXXlzR.js} +1 -1
- package/public/assets/{diff-viewer-DViXCI-4.js → diff-viewer-CSNOYoEB.js} +1 -1
- package/public/assets/{git-diff-vendor-CSgooKT_.js → git-diff-vendor-CYS3ApzA.js} +1 -1
- package/public/assets/index-B3YjNCh4.js +1 -0
- package/public/assets/index-B3ZD2_SU.js +1 -0
- package/public/assets/index-Bh2ofaQo.js +1 -0
- package/public/assets/index-CM0c9xRZ.js +1 -0
- package/public/assets/index-CdDxouJl.js +2 -0
- package/public/assets/index-D06Y2cFq.js +1 -0
- package/public/assets/{index-lf4nUOU1.js → index-DVc4ls8c.js} +1 -1
- package/public/assets/index-PmE51vPh.js +1 -0
- package/public/assets/{index-BMvFMsuy.css → index-Qj63IiSj.css} +1 -1
- package/public/assets/index-VY_zwmsG.js +1 -0
- package/public/assets/index-qiiH7egV.js +1 -0
- package/public/assets/{index-UbL8tz_V.js → index-sLe9hEfQ.js} +1 -1
- package/public/assets/{loading-CZ_tt3h0.js → loading-CDmt9OJU.js} +1 -1
- package/public/assets/main-CTN6Wwz1.js +68 -0
- package/public/assets/{markdown-G11aN6hz.js → markdown-1ZLSxCFT.js} +55 -44
- package/public/assets/monaco-viewer-D6JgH76W.js +26 -0
- package/public/assets/{tool-call-BsSXWErm.js → tool-call-CyXb63LH.js} +32 -32
- package/public/assets/{unified-picker-BRNI3UvE.js → unified-picker-DCCdSVwI.js} +1 -1
- package/public/assets/{wrap-text-B7up-uv7.js → wrap-text-pNMdTIq3.js} +1 -1
- package/public/index.html +5 -5
- package/public/loading.html +5 -5
- package/public/sw.js +1 -1
- package/public/assets/StatusTab-Bgu1Fh24.js +0 -1
- package/public/assets/index-3KsjbWGP.js +0 -1
- package/public/assets/index-B0WK8-cF.js +0 -1
- package/public/assets/index-BDv_iVtj.js +0 -1
- package/public/assets/index-BjxZvyb9.js +0 -2
- package/public/assets/index-BleKf9Nj.js +0 -1
- package/public/assets/index-CiIQ43NW.js +0 -1
- package/public/assets/index-DBquqL6Q.js +0 -1
- package/public/assets/index-yf9zolv4.js +0 -1
- package/public/assets/index-ypaL7i_s.js +0 -1
- package/public/assets/main-CAkg_fp_.js +0 -68
- package/public/assets/monaco-viewer-CZefBfct.js +0 -26
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import pino from "pino";
|
|
4
|
+
import { EventBus } from "../events/bus";
|
|
5
|
+
import { WorkspaceRuntimeIdentityCaptureError, WorkspaceRuntimeLaunchCancelledError, WorkspaceWindowsTreeCleanupIncompleteError, } from "./runtime";
|
|
6
|
+
import { WorkspaceLaunchCancelledError, WorkspaceLaunchSettlementTimeoutError, WorkspaceManager, WorkspaceShutdownError, WorkspaceShutdownTimeoutError, } from "./manager";
|
|
7
|
+
function deferred() {
|
|
8
|
+
let resolve;
|
|
9
|
+
let reject;
|
|
10
|
+
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
11
|
+
resolve = resolvePromise;
|
|
12
|
+
reject = rejectPromise;
|
|
13
|
+
});
|
|
14
|
+
return { promise, resolve, reject };
|
|
15
|
+
}
|
|
16
|
+
class ControlledRuntime {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.launchResult = deferred();
|
|
19
|
+
this.launchCalled = deferred();
|
|
20
|
+
this.cancellation = deferred();
|
|
21
|
+
this.active = new Set();
|
|
22
|
+
this.stopCalls = [];
|
|
23
|
+
this.launchCalls = 0;
|
|
24
|
+
this.failStops = 0;
|
|
25
|
+
this.launch = (options) => {
|
|
26
|
+
this.launchCalls += 1;
|
|
27
|
+
this.active.add(options.workspaceId);
|
|
28
|
+
this.launchCalled.resolve(options.workspaceId);
|
|
29
|
+
return this.launchResult.promise;
|
|
30
|
+
};
|
|
31
|
+
this.stop = async (workspaceId) => {
|
|
32
|
+
this.stopCalls.push(workspaceId);
|
|
33
|
+
this.cancellation.resolve(new WorkspaceRuntimeLaunchCancelledError(workspaceId));
|
|
34
|
+
if (this.failStops > 0) {
|
|
35
|
+
this.failStops -= 1;
|
|
36
|
+
throw new Error("controlled stop failure");
|
|
37
|
+
}
|
|
38
|
+
this.active.delete(workspaceId);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
resolveLaunch(workspaceId) {
|
|
42
|
+
this.launchResult.resolve({
|
|
43
|
+
pid: 1234,
|
|
44
|
+
port: 4321,
|
|
45
|
+
exitPromise: new Promise(() => undefined),
|
|
46
|
+
cancellationPromise: this.cancellation.promise,
|
|
47
|
+
getLastOutput: () => "",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function createHarness(managerOptions = {}) {
|
|
52
|
+
const eventBus = new EventBus();
|
|
53
|
+
const runtime = new ControlledRuntime();
|
|
54
|
+
const readiness = deferred();
|
|
55
|
+
const started = [];
|
|
56
|
+
let createdId = "";
|
|
57
|
+
eventBus.on("workspace.created", (event) => {
|
|
58
|
+
createdId = event.workspace.id;
|
|
59
|
+
});
|
|
60
|
+
eventBus.on("workspace.started", (event) => started.push(event.workspace.id));
|
|
61
|
+
const manager = new WorkspaceManager({
|
|
62
|
+
rootDir: process.cwd(),
|
|
63
|
+
settings: { getOwner: () => ({}) },
|
|
64
|
+
binaryResolver: {
|
|
65
|
+
resolve: () => ({ path: "test-opencode", label: "test-opencode" }),
|
|
66
|
+
},
|
|
67
|
+
eventBus,
|
|
68
|
+
logger: pino({ level: "silent" }),
|
|
69
|
+
getServerBaseUrl: () => "http://127.0.0.1:4000",
|
|
70
|
+
runtime,
|
|
71
|
+
...managerOptions,
|
|
72
|
+
});
|
|
73
|
+
manager.waitForWorkspaceReadiness = () => readiness.promise;
|
|
74
|
+
return { manager, runtime, readiness, started, eventBus, getCreatedId: () => createdId };
|
|
75
|
+
}
|
|
76
|
+
describe("workspace manager launch cancellation", () => {
|
|
77
|
+
it("keeps a launched workspace unpublished until runtime identity launch succeeds", async () => {
|
|
78
|
+
const harness = createHarness();
|
|
79
|
+
let deletion;
|
|
80
|
+
harness.eventBus.on("workspace.created", (event) => {
|
|
81
|
+
deletion = harness.manager.delete(event.workspace.id);
|
|
82
|
+
});
|
|
83
|
+
const creation = harness.manager.create(process.cwd());
|
|
84
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
85
|
+
assert.equal(harness.getCreatedId(), "");
|
|
86
|
+
assert.deepEqual(harness.manager.list(), []);
|
|
87
|
+
assert.equal(harness.manager.getInstancePort(workspaceId), undefined);
|
|
88
|
+
assert.equal(harness.manager.getInstanceAuthorizationHeader(workspaceId), undefined);
|
|
89
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
90
|
+
await assert.rejects(creation, WorkspaceLaunchCancelledError);
|
|
91
|
+
await deletion;
|
|
92
|
+
assert.equal(harness.runtime.launchCalls, 1);
|
|
93
|
+
assert.equal(harness.runtime.active.size, 0);
|
|
94
|
+
assert.deepEqual(harness.started, []);
|
|
95
|
+
assert.deepEqual(harness.manager.list(), []);
|
|
96
|
+
});
|
|
97
|
+
it("stops a late launch and shares cleanup across concurrent deletes", async () => {
|
|
98
|
+
const harness = createHarness();
|
|
99
|
+
const creation = harness.manager.create(process.cwd());
|
|
100
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
101
|
+
const firstDelete = harness.manager.delete(workspaceId);
|
|
102
|
+
const secondDelete = harness.manager.delete(workspaceId);
|
|
103
|
+
assert.strictEqual(firstDelete, secondDelete);
|
|
104
|
+
assert.equal(harness.manager.get(workspaceId), undefined);
|
|
105
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
106
|
+
await assert.rejects(creation, WorkspaceLaunchCancelledError);
|
|
107
|
+
await Promise.all([firstDelete, secondDelete]);
|
|
108
|
+
assert.equal(harness.runtime.active.has(workspaceId), false);
|
|
109
|
+
assert.deepEqual(harness.started, []);
|
|
110
|
+
assert.equal(harness.manager.get(workspaceId), undefined);
|
|
111
|
+
assert.deepEqual(harness.manager.list(), []);
|
|
112
|
+
});
|
|
113
|
+
it("does not publish or retain a workspace when mandatory identity launch fails", async () => {
|
|
114
|
+
const events = [];
|
|
115
|
+
const eventBus = new EventBus();
|
|
116
|
+
eventBus.on("workspace.created", () => events.push("created"));
|
|
117
|
+
eventBus.on("workspace.started", () => events.push("started"));
|
|
118
|
+
eventBus.on("workspace.error", () => events.push("error"));
|
|
119
|
+
const runtime = {
|
|
120
|
+
launch: ((options) => Promise.reject(new WorkspaceRuntimeIdentityCaptureError(options.workspaceId, "probe unavailable"))),
|
|
121
|
+
stop: (() => Promise.resolve()),
|
|
122
|
+
};
|
|
123
|
+
const manager = new WorkspaceManager({
|
|
124
|
+
rootDir: process.cwd(),
|
|
125
|
+
settings: { getOwner: () => ({}) },
|
|
126
|
+
binaryResolver: { resolve: () => ({ path: "test-opencode", label: "test-opencode" }) },
|
|
127
|
+
eventBus,
|
|
128
|
+
logger: pino({ level: "silent" }),
|
|
129
|
+
getServerBaseUrl: () => "http://127.0.0.1:4000",
|
|
130
|
+
runtime,
|
|
131
|
+
});
|
|
132
|
+
await assert.rejects(manager.create(process.cwd()), WorkspaceRuntimeIdentityCaptureError);
|
|
133
|
+
assert.deepEqual(events, []);
|
|
134
|
+
assert.deepEqual(manager.list(), []);
|
|
135
|
+
});
|
|
136
|
+
it("retains an unpublished cleanup record when identity-failure cleanup cannot be proven", async () => {
|
|
137
|
+
let workspaceId = "";
|
|
138
|
+
let stopAttempts = 0;
|
|
139
|
+
const runtime = {
|
|
140
|
+
launch: ((options) => {
|
|
141
|
+
workspaceId = options.workspaceId;
|
|
142
|
+
return Promise.reject(new WorkspaceRuntimeIdentityCaptureError(options.workspaceId, "probe unavailable"));
|
|
143
|
+
}),
|
|
144
|
+
stop: (async () => {
|
|
145
|
+
stopAttempts += 1;
|
|
146
|
+
if (stopAttempts === 1)
|
|
147
|
+
throw new Error("cleanup proof unavailable");
|
|
148
|
+
}),
|
|
149
|
+
};
|
|
150
|
+
const manager = new WorkspaceManager({
|
|
151
|
+
rootDir: process.cwd(),
|
|
152
|
+
settings: { getOwner: () => ({}) },
|
|
153
|
+
binaryResolver: { resolve: () => ({ path: "test-opencode", label: "test-opencode" }) },
|
|
154
|
+
eventBus: new EventBus(),
|
|
155
|
+
logger: pino({ level: "silent" }),
|
|
156
|
+
getServerBaseUrl: () => "http://127.0.0.1:4000",
|
|
157
|
+
runtime,
|
|
158
|
+
});
|
|
159
|
+
await assert.rejects(manager.create(process.cwd()), /cleanup proof unavailable/);
|
|
160
|
+
assert.deepEqual(manager.list(), []);
|
|
161
|
+
assert.equal(manager.get(workspaceId), undefined);
|
|
162
|
+
await manager.delete(workspaceId);
|
|
163
|
+
assert.equal(stopAttempts, 3);
|
|
164
|
+
});
|
|
165
|
+
it("actively cancels pending readiness without publishing workspace.started", async () => {
|
|
166
|
+
const harness = createHarness();
|
|
167
|
+
const creation = harness.manager.create(process.cwd());
|
|
168
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
169
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
170
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
171
|
+
const deletion = harness.manager.delete(workspaceId);
|
|
172
|
+
await assert.rejects(creation, WorkspaceLaunchCancelledError);
|
|
173
|
+
await deletion;
|
|
174
|
+
assert.equal(harness.runtime.active.has(workspaceId), false);
|
|
175
|
+
assert.deepEqual(harness.started, []);
|
|
176
|
+
assert.equal(harness.manager.get(workspaceId), undefined);
|
|
177
|
+
});
|
|
178
|
+
it("cancels and cleans a starting child during shutdown", async () => {
|
|
179
|
+
const harness = createHarness();
|
|
180
|
+
const creation = harness.manager.create(process.cwd());
|
|
181
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
182
|
+
const shutdown = harness.manager.shutdown();
|
|
183
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
184
|
+
await assert.rejects(creation, WorkspaceLaunchCancelledError);
|
|
185
|
+
await shutdown;
|
|
186
|
+
assert.equal(harness.runtime.active.size, 0);
|
|
187
|
+
assert.deepEqual(harness.started, []);
|
|
188
|
+
assert.deepEqual(harness.manager.list(), []);
|
|
189
|
+
});
|
|
190
|
+
it("still starts and deletes an ordinary ready workspace", async () => {
|
|
191
|
+
const harness = createHarness();
|
|
192
|
+
const creation = harness.manager.create(process.cwd(), "ordinary");
|
|
193
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
194
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
195
|
+
harness.readiness.resolve("2.0.0");
|
|
196
|
+
const { workspace } = await creation;
|
|
197
|
+
assert.equal(workspace.id, harness.getCreatedId());
|
|
198
|
+
assert.equal(workspace.status, "ready");
|
|
199
|
+
assert.equal(workspace.binaryVersion, "2.0.0");
|
|
200
|
+
assert.deepEqual(harness.started, [workspaceId]);
|
|
201
|
+
assert.equal(harness.runtime.active.has(workspaceId), true);
|
|
202
|
+
await harness.manager.delete(workspaceId);
|
|
203
|
+
assert.equal(harness.runtime.active.has(workspaceId), false);
|
|
204
|
+
assert.equal(harness.manager.get(workspaceId), undefined);
|
|
205
|
+
});
|
|
206
|
+
it("publishes stopped and removes a workspace after readiness fails", async () => {
|
|
207
|
+
const harness = createHarness();
|
|
208
|
+
const stopped = [];
|
|
209
|
+
harness.eventBus.on("workspace.stopped", (event) => stopped.push(event.workspaceId));
|
|
210
|
+
const creation = harness.manager.create(process.cwd());
|
|
211
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
212
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
213
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
214
|
+
harness.readiness.reject(new Error("readiness failed"));
|
|
215
|
+
await assert.rejects(creation, /readiness failed/);
|
|
216
|
+
assert.deepEqual(stopped, [workspaceId]);
|
|
217
|
+
assert.equal(harness.manager.get(workspaceId), undefined);
|
|
218
|
+
assert.deepEqual(harness.manager.list(), []);
|
|
219
|
+
});
|
|
220
|
+
it("retains a cancelled record after failed cleanup and allows delete retry", async () => {
|
|
221
|
+
const harness = createHarness();
|
|
222
|
+
const creation = harness.manager.create(process.cwd());
|
|
223
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
224
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
225
|
+
harness.readiness.resolve(undefined);
|
|
226
|
+
await creation;
|
|
227
|
+
harness.runtime.failStops = 1;
|
|
228
|
+
const firstDelete = harness.manager.delete(workspaceId);
|
|
229
|
+
const concurrentDelete = harness.manager.delete(workspaceId);
|
|
230
|
+
assert.strictEqual(firstDelete, concurrentDelete);
|
|
231
|
+
const failedDeletes = await Promise.allSettled([firstDelete, concurrentDelete]);
|
|
232
|
+
assert.equal(failedDeletes[0].status, "rejected");
|
|
233
|
+
assert.equal(failedDeletes[1].status, "rejected");
|
|
234
|
+
if (failedDeletes[0].status === "rejected" && failedDeletes[1].status === "rejected") {
|
|
235
|
+
assert.strictEqual(failedDeletes[0].reason, failedDeletes[1].reason);
|
|
236
|
+
assert.match(String(failedDeletes[0].reason), /controlled stop failure/);
|
|
237
|
+
}
|
|
238
|
+
assert.equal(harness.manager.get(workspaceId)?.status, "ready");
|
|
239
|
+
assert.equal(harness.runtime.active.has(workspaceId), true);
|
|
240
|
+
const retryDelete = harness.manager.delete(workspaceId);
|
|
241
|
+
assert.notStrictEqual(retryDelete, firstDelete);
|
|
242
|
+
await retryDelete;
|
|
243
|
+
assert.equal(harness.runtime.active.has(workspaceId), false);
|
|
244
|
+
assert.equal(harness.manager.get(workspaceId), undefined);
|
|
245
|
+
});
|
|
246
|
+
it("rejects shutdown at its deadline instead of waiting forever", async () => {
|
|
247
|
+
let fireDeadline;
|
|
248
|
+
const timer = {};
|
|
249
|
+
const harness = createHarness({
|
|
250
|
+
shutdownTimeoutMs: 25,
|
|
251
|
+
setTimeout: (callback) => {
|
|
252
|
+
fireDeadline = callback;
|
|
253
|
+
return timer;
|
|
254
|
+
},
|
|
255
|
+
clearTimeout: () => undefined,
|
|
256
|
+
});
|
|
257
|
+
const creation = harness.manager.create(process.cwd());
|
|
258
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
259
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
260
|
+
harness.readiness.resolve(undefined);
|
|
261
|
+
await creation;
|
|
262
|
+
harness.runtime.stop = () => new Promise(() => undefined);
|
|
263
|
+
const shutdown = harness.manager.shutdown();
|
|
264
|
+
assert.ok(fireDeadline);
|
|
265
|
+
fireDeadline();
|
|
266
|
+
await assert.rejects(shutdown, WorkspaceShutdownTimeoutError);
|
|
267
|
+
assert.equal(harness.manager.get(workspaceId)?.status, "ready");
|
|
268
|
+
});
|
|
269
|
+
it("rejects failed shutdown cleanup and allows a later delete retry", async () => {
|
|
270
|
+
const harness = createHarness();
|
|
271
|
+
const creation = harness.manager.create(process.cwd());
|
|
272
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
273
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
274
|
+
harness.readiness.resolve(undefined);
|
|
275
|
+
await creation;
|
|
276
|
+
harness.runtime.failStops = 1;
|
|
277
|
+
await assert.rejects(harness.manager.shutdown(), WorkspaceShutdownError);
|
|
278
|
+
assert.equal(harness.manager.get(workspaceId)?.status, "ready");
|
|
279
|
+
assert.equal(harness.runtime.active.has(workspaceId), true);
|
|
280
|
+
await harness.manager.delete(workspaceId);
|
|
281
|
+
assert.equal(harness.manager.get(workspaceId), undefined);
|
|
282
|
+
assert.equal(harness.runtime.active.has(workspaceId), false);
|
|
283
|
+
});
|
|
284
|
+
it("reports incomplete Windows tree cleanup during shutdown", async () => {
|
|
285
|
+
const harness = createHarness();
|
|
286
|
+
const creation = harness.manager.create(process.cwd());
|
|
287
|
+
const workspaceId = await harness.runtime.launchCalled.promise;
|
|
288
|
+
harness.runtime.resolveLaunch(workspaceId);
|
|
289
|
+
harness.readiness.resolve(undefined);
|
|
290
|
+
await creation;
|
|
291
|
+
harness.runtime.stop = async () => {
|
|
292
|
+
throw new WorkspaceWindowsTreeCleanupIncompleteError(workspaceId, 4242, ["taskkill /T failed: unavailable"]);
|
|
293
|
+
};
|
|
294
|
+
await assert.rejects(harness.manager.shutdown(), (error) => {
|
|
295
|
+
assert.ok(error instanceof WorkspaceShutdownError);
|
|
296
|
+
assert.ok(error.errors[0] instanceof WorkspaceWindowsTreeCleanupIncompleteError);
|
|
297
|
+
return true;
|
|
298
|
+
});
|
|
299
|
+
assert.equal(harness.manager.get(workspaceId)?.status, "ready");
|
|
300
|
+
assert.equal(harness.runtime.active.has(workspaceId), true);
|
|
301
|
+
});
|
|
302
|
+
it("bounds cleanup when a runtime does not settle its cancelled launch", async () => {
|
|
303
|
+
let fireDeadline;
|
|
304
|
+
const timer = {};
|
|
305
|
+
const harness = createHarness({
|
|
306
|
+
launchSettlementTimeoutMs: 25,
|
|
307
|
+
setTimeout: (callback) => {
|
|
308
|
+
fireDeadline = callback;
|
|
309
|
+
return timer;
|
|
310
|
+
},
|
|
311
|
+
clearTimeout: () => undefined,
|
|
312
|
+
});
|
|
313
|
+
const deletion = harness.manager.withLaunchSettlementTimeout("workspace-1", new Promise(() => undefined));
|
|
314
|
+
assert.ok(fireDeadline);
|
|
315
|
+
fireDeadline();
|
|
316
|
+
await assert.rejects(deletion, WorkspaceLaunchSettlementTimeoutError);
|
|
317
|
+
});
|
|
318
|
+
});
|