@ruvector/edge-net 0.4.2 → 0.4.4
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/deploy/.env.example +97 -0
- package/deploy/DEPLOY.md +481 -0
- package/deploy/Dockerfile +100 -0
- package/deploy/docker-compose.yml +162 -0
- package/deploy/genesis-prod.js +1550 -0
- package/deploy/health-check.js +187 -0
- package/deploy/prometheus.yml +38 -0
- package/firebase-signaling.js +57 -2
- package/package.json +8 -1
- package/real-workers.js +9 -4
- package/scheduler.js +8 -4
- package/tests/distributed-workers-test.js +1609 -0
- package/tests/multitenancy-test.js +130 -0
- package/tests/p2p-migration-test.js +1102 -0
- package/tests/task-execution-test.js +534 -0
- package/tests/webrtc-peer-test.js +686 -0
- package/webrtc.js +693 -40
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Task Execution Handler Integration Test
|
|
4
|
+
*
|
|
5
|
+
* Tests the distributed task execution system:
|
|
6
|
+
* - TaskExecutionHandler receives task-assign signals
|
|
7
|
+
* - Validates tasks and executes via RealWorkerPool
|
|
8
|
+
* - Sends task-result or task-error back to originator
|
|
9
|
+
*
|
|
10
|
+
* Run: node tests/task-execution-test.js
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { EventEmitter } from 'events';
|
|
14
|
+
import { randomBytes } from 'crypto';
|
|
15
|
+
|
|
16
|
+
// Import components
|
|
17
|
+
import { TaskExecutionHandler, TaskValidator, DistributedTaskNetwork } from '../task-execution-handler.js';
|
|
18
|
+
import { RealWorkerPool } from '../real-workers.js';
|
|
19
|
+
|
|
20
|
+
// ============================================
|
|
21
|
+
// MOCK SIGNALING (for testing without Firebase)
|
|
22
|
+
// ============================================
|
|
23
|
+
|
|
24
|
+
class MockSignaling extends EventEmitter {
|
|
25
|
+
constructor(peerId) {
|
|
26
|
+
super();
|
|
27
|
+
this.peerId = peerId || `mock-${randomBytes(8).toString('hex')}`;
|
|
28
|
+
this.isConnected = true;
|
|
29
|
+
this.peers = new Map();
|
|
30
|
+
this.sentSignals = [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async sendSignal(toPeerId, type, data) {
|
|
34
|
+
const signal = {
|
|
35
|
+
from: this.peerId,
|
|
36
|
+
to: toPeerId,
|
|
37
|
+
type,
|
|
38
|
+
data,
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
};
|
|
41
|
+
this.sentSignals.push(signal);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getOnlinePeers() {
|
|
46
|
+
return Array.from(this.peers.values());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Simulate receiving a signal
|
|
50
|
+
simulateSignal(signal) {
|
|
51
|
+
this.emit('signal', signal);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async connect() {
|
|
55
|
+
this.isConnected = true;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async disconnect() {
|
|
60
|
+
this.isConnected = false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================
|
|
65
|
+
// TEST UTILITIES
|
|
66
|
+
// ============================================
|
|
67
|
+
|
|
68
|
+
function log(msg, level = 'info') {
|
|
69
|
+
const timestamp = new Date().toISOString().slice(11, 23);
|
|
70
|
+
const prefix = {
|
|
71
|
+
info: '\x1b[36m[INFO]\x1b[0m',
|
|
72
|
+
pass: '\x1b[32m[PASS]\x1b[0m',
|
|
73
|
+
fail: '\x1b[31m[FAIL]\x1b[0m',
|
|
74
|
+
warn: '\x1b[33m[WARN]\x1b[0m',
|
|
75
|
+
}[level] || '[INFO]';
|
|
76
|
+
console.log(`${timestamp} ${prefix} ${msg}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function runTest(name, testFn) {
|
|
80
|
+
log(`Running: ${name}`);
|
|
81
|
+
try {
|
|
82
|
+
await testFn();
|
|
83
|
+
log(`${name}`, 'pass');
|
|
84
|
+
return true;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
log(`${name}: ${error.message}`, 'fail');
|
|
87
|
+
console.error(error);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================
|
|
93
|
+
// TESTS
|
|
94
|
+
// ============================================
|
|
95
|
+
|
|
96
|
+
async function testTaskValidator() {
|
|
97
|
+
const validator = new TaskValidator();
|
|
98
|
+
|
|
99
|
+
// Valid task
|
|
100
|
+
const validTask = {
|
|
101
|
+
id: 'task-123',
|
|
102
|
+
type: 'compute',
|
|
103
|
+
data: [1, 2, 3, 4, 5],
|
|
104
|
+
priority: 'medium',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const result1 = validator.validate(validTask);
|
|
108
|
+
if (!result1.valid) throw new Error(`Valid task rejected: ${result1.errors.join(', ')}`);
|
|
109
|
+
|
|
110
|
+
// Missing ID
|
|
111
|
+
const result2 = validator.validate({ type: 'compute', data: [] });
|
|
112
|
+
if (result2.valid) throw new Error('Task without ID should be rejected');
|
|
113
|
+
|
|
114
|
+
// Missing type
|
|
115
|
+
const result3 = validator.validate({ id: 'test', data: [] });
|
|
116
|
+
if (result3.valid) throw new Error('Task without type should be rejected');
|
|
117
|
+
|
|
118
|
+
// Invalid type
|
|
119
|
+
const result4 = validator.validate({ id: 'test', type: 'invalid-type', data: [] });
|
|
120
|
+
if (result4.valid) throw new Error('Task with invalid type should be rejected');
|
|
121
|
+
|
|
122
|
+
// Missing data
|
|
123
|
+
const result5 = validator.validate({ id: 'test', type: 'compute' });
|
|
124
|
+
if (result5.valid) throw new Error('Task without data should be rejected');
|
|
125
|
+
|
|
126
|
+
log('TaskValidator tests passed');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function testWorkerPoolExecution() {
|
|
130
|
+
const pool = new RealWorkerPool({ size: 2 });
|
|
131
|
+
await pool.initialize();
|
|
132
|
+
|
|
133
|
+
// Test compute task
|
|
134
|
+
const computeResult = await pool.execute('compute', [1, 2, 3, 4, 5], { operation: 'sum' });
|
|
135
|
+
if (computeResult.result !== 15) {
|
|
136
|
+
throw new Error(`Expected sum to be 15, got ${computeResult.result}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Test embed task
|
|
140
|
+
const embedResult = await pool.execute('embed', 'hello world');
|
|
141
|
+
if (!embedResult.embedding || embedResult.embedding.length !== 384) {
|
|
142
|
+
throw new Error('Embed result should have 384-dimension embedding');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Test transform task
|
|
146
|
+
const transformResult = await pool.execute('transform', 'hello', { transform: 'uppercase' });
|
|
147
|
+
if (transformResult.transformed !== 'HELLO') {
|
|
148
|
+
throw new Error(`Expected HELLO, got ${transformResult.transformed}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await pool.shutdown();
|
|
152
|
+
log('WorkerPool execution tests passed');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function testTaskExecutionHandler() {
|
|
156
|
+
// Create mock signaling and real worker pool
|
|
157
|
+
const nodeASignaling = new MockSignaling('node-A');
|
|
158
|
+
const nodeBSignaling = new MockSignaling('node-B');
|
|
159
|
+
|
|
160
|
+
const workerPool = new RealWorkerPool({ size: 2 });
|
|
161
|
+
await workerPool.initialize();
|
|
162
|
+
|
|
163
|
+
// Create handler for Node B (the executor)
|
|
164
|
+
const handler = new TaskExecutionHandler({
|
|
165
|
+
signaling: nodeBSignaling,
|
|
166
|
+
workerPool,
|
|
167
|
+
nodeId: 'node-B',
|
|
168
|
+
capabilities: ['compute', 'embed', 'process'],
|
|
169
|
+
});
|
|
170
|
+
handler.attach();
|
|
171
|
+
|
|
172
|
+
// Track events with promise-based waiting
|
|
173
|
+
const taskId = `task-${Date.now()}`;
|
|
174
|
+
let startEvent = null;
|
|
175
|
+
let completeEvent = null;
|
|
176
|
+
|
|
177
|
+
const completePromise = new Promise((resolve) => {
|
|
178
|
+
handler.on('task-start', (e) => {
|
|
179
|
+
if (e.taskId === taskId) startEvent = e;
|
|
180
|
+
});
|
|
181
|
+
handler.on('task-complete', (e) => {
|
|
182
|
+
if (e.taskId === taskId) {
|
|
183
|
+
completeEvent = e;
|
|
184
|
+
resolve(e);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
handler.on('task-error', (e) => {
|
|
188
|
+
if (e.taskId === taskId) resolve(e);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Simulate Node A sending a task-assign signal to Node B
|
|
193
|
+
const taskAssignSignal = {
|
|
194
|
+
type: 'task-assign',
|
|
195
|
+
from: 'node-A',
|
|
196
|
+
data: {
|
|
197
|
+
task: {
|
|
198
|
+
id: taskId,
|
|
199
|
+
type: 'compute',
|
|
200
|
+
data: [10, 20, 30],
|
|
201
|
+
options: { operation: 'sum' },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
verified: true,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Send signal to Node B's handler
|
|
208
|
+
nodeBSignaling.simulateSignal(taskAssignSignal);
|
|
209
|
+
|
|
210
|
+
// Wait for completion (with timeout)
|
|
211
|
+
await Promise.race([
|
|
212
|
+
completePromise,
|
|
213
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for task completion')), 5000))
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
// Check events
|
|
217
|
+
if (!startEvent) throw new Error('task-start event not emitted');
|
|
218
|
+
if (!completeEvent) throw new Error('task-complete event not emitted');
|
|
219
|
+
|
|
220
|
+
// Check that result was sent back via signaling
|
|
221
|
+
const resultSignal = nodeBSignaling.sentSignals.find(
|
|
222
|
+
s => s.type === 'task-result' && s.data.taskId === taskId
|
|
223
|
+
);
|
|
224
|
+
if (!resultSignal) throw new Error('task-result signal not sent');
|
|
225
|
+
|
|
226
|
+
if (resultSignal.data.result.result !== 60) {
|
|
227
|
+
throw new Error(`Expected sum result 60, got ${resultSignal.data.result.result}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Cleanup
|
|
231
|
+
handler.detach();
|
|
232
|
+
await workerPool.shutdown();
|
|
233
|
+
|
|
234
|
+
log('TaskExecutionHandler tests passed');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function testTaskRejection() {
|
|
238
|
+
const signaling = new MockSignaling('node-B');
|
|
239
|
+
const workerPool = new RealWorkerPool({ size: 1 });
|
|
240
|
+
await workerPool.initialize();
|
|
241
|
+
|
|
242
|
+
const handler = new TaskExecutionHandler({
|
|
243
|
+
signaling,
|
|
244
|
+
workerPool,
|
|
245
|
+
nodeId: 'node-B',
|
|
246
|
+
capabilities: ['compute'],
|
|
247
|
+
});
|
|
248
|
+
handler.attach();
|
|
249
|
+
|
|
250
|
+
const events = [];
|
|
251
|
+
handler.on('task-rejected', (e) => events.push(e));
|
|
252
|
+
|
|
253
|
+
// Test 1: Invalid task (missing type)
|
|
254
|
+
signaling.simulateSignal({
|
|
255
|
+
type: 'task-assign',
|
|
256
|
+
from: 'node-A',
|
|
257
|
+
data: {
|
|
258
|
+
task: {
|
|
259
|
+
id: 'bad-task-1',
|
|
260
|
+
data: [1, 2, 3],
|
|
261
|
+
// missing type
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
verified: true,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
268
|
+
|
|
269
|
+
const rejection1 = events.find(e => e.taskId === 'bad-task-1');
|
|
270
|
+
if (!rejection1) throw new Error('Task without type should be rejected');
|
|
271
|
+
|
|
272
|
+
// Check error signal was sent
|
|
273
|
+
const errorSignal = signaling.sentSignals.find(
|
|
274
|
+
s => s.type === 'task-error' && s.data.taskId === 'bad-task-1'
|
|
275
|
+
);
|
|
276
|
+
if (!errorSignal) throw new Error('task-error signal should be sent for invalid task');
|
|
277
|
+
|
|
278
|
+
// Test 2: Missing capabilities
|
|
279
|
+
signaling.simulateSignal({
|
|
280
|
+
type: 'task-assign',
|
|
281
|
+
from: 'node-A',
|
|
282
|
+
data: {
|
|
283
|
+
task: {
|
|
284
|
+
id: 'bad-task-2',
|
|
285
|
+
type: 'compute',
|
|
286
|
+
data: [1, 2, 3],
|
|
287
|
+
requiredCapabilities: ['special-gpu'],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
verified: true,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
294
|
+
|
|
295
|
+
const rejection2 = events.find(e => e.taskId === 'bad-task-2');
|
|
296
|
+
if (!rejection2 || rejection2.reason !== 'capabilities') {
|
|
297
|
+
throw new Error('Task with missing capabilities should be rejected');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
handler.detach();
|
|
301
|
+
await workerPool.shutdown();
|
|
302
|
+
|
|
303
|
+
log('Task rejection tests passed');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function testSubmitTaskToRemote() {
|
|
307
|
+
// Create two nodes that can communicate
|
|
308
|
+
const nodeASignaling = new MockSignaling('node-A');
|
|
309
|
+
const nodeBSignaling = new MockSignaling('node-B');
|
|
310
|
+
|
|
311
|
+
// Register each other as peers
|
|
312
|
+
nodeASignaling.peers.set('node-B', { id: 'node-B', capabilities: ['compute'] });
|
|
313
|
+
nodeBSignaling.peers.set('node-A', { id: 'node-A', capabilities: ['compute'] });
|
|
314
|
+
|
|
315
|
+
// Create worker pool and handler for Node B
|
|
316
|
+
const workerPool = new RealWorkerPool({ size: 2 });
|
|
317
|
+
await workerPool.initialize();
|
|
318
|
+
|
|
319
|
+
const nodeBHandler = new TaskExecutionHandler({
|
|
320
|
+
signaling: nodeBSignaling,
|
|
321
|
+
workerPool,
|
|
322
|
+
nodeId: 'node-B',
|
|
323
|
+
capabilities: ['compute'],
|
|
324
|
+
});
|
|
325
|
+
nodeBHandler.attach();
|
|
326
|
+
|
|
327
|
+
// Create handler for Node A (the submitter)
|
|
328
|
+
const nodeAHandler = new TaskExecutionHandler({
|
|
329
|
+
signaling: nodeASignaling,
|
|
330
|
+
workerPool: null, // Node A won't execute locally
|
|
331
|
+
nodeId: 'node-A',
|
|
332
|
+
capabilities: [],
|
|
333
|
+
});
|
|
334
|
+
nodeAHandler.attach();
|
|
335
|
+
|
|
336
|
+
// Override sendSignal for Node A to deliver to Node B
|
|
337
|
+
const origSendA = nodeASignaling.sendSignal.bind(nodeASignaling);
|
|
338
|
+
nodeASignaling.sendSignal = async (to, type, data) => {
|
|
339
|
+
await origSendA(to, type, data);
|
|
340
|
+
if (to === 'node-B') {
|
|
341
|
+
// Small delay to simulate network
|
|
342
|
+
await new Promise(r => setTimeout(r, 10));
|
|
343
|
+
// Deliver to Node B with proper signal structure
|
|
344
|
+
nodeBSignaling.simulateSignal({
|
|
345
|
+
type,
|
|
346
|
+
from: 'node-A',
|
|
347
|
+
data,
|
|
348
|
+
verified: true,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Override sendSignal for Node B to deliver results back to Node A
|
|
354
|
+
const origSendB = nodeBSignaling.sendSignal.bind(nodeBSignaling);
|
|
355
|
+
nodeBSignaling.sendSignal = async (to, type, data) => {
|
|
356
|
+
await origSendB(to, type, data);
|
|
357
|
+
if (to === 'node-A' && (type === 'task-result' || type === 'task-error')) {
|
|
358
|
+
// Small delay to simulate network
|
|
359
|
+
await new Promise(r => setTimeout(r, 10));
|
|
360
|
+
// Deliver result back to Node A
|
|
361
|
+
nodeASignaling.simulateSignal({
|
|
362
|
+
type,
|
|
363
|
+
from: 'node-B',
|
|
364
|
+
data,
|
|
365
|
+
verified: true,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Submit task from Node A to Node B
|
|
371
|
+
const resultPromise = nodeAHandler.submitTask('node-B', {
|
|
372
|
+
id: 'remote-task-1',
|
|
373
|
+
type: 'compute',
|
|
374
|
+
data: [5, 10, 15],
|
|
375
|
+
options: { operation: 'sum' },
|
|
376
|
+
}, { timeout: 5000 });
|
|
377
|
+
|
|
378
|
+
// Wait for result
|
|
379
|
+
const result = await resultPromise;
|
|
380
|
+
|
|
381
|
+
if (result.result.result !== 30) {
|
|
382
|
+
throw new Error(`Expected 30, got ${result.result.result}`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (result.processedBy !== 'node-B') {
|
|
386
|
+
throw new Error(`Expected processedBy to be node-B, got ${result.processedBy}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Cleanup
|
|
390
|
+
nodeAHandler.detach();
|
|
391
|
+
nodeBHandler.detach();
|
|
392
|
+
await workerPool.shutdown();
|
|
393
|
+
|
|
394
|
+
log('Remote task submission tests passed');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function testCapacityLimit() {
|
|
398
|
+
const signaling = new MockSignaling('node-B');
|
|
399
|
+
const workerPool = new RealWorkerPool({ size: 1 });
|
|
400
|
+
await workerPool.initialize();
|
|
401
|
+
|
|
402
|
+
const handler = new TaskExecutionHandler({
|
|
403
|
+
signaling,
|
|
404
|
+
workerPool,
|
|
405
|
+
nodeId: 'node-B',
|
|
406
|
+
maxConcurrentTasks: 2, // Low limit for testing
|
|
407
|
+
capabilities: ['compute'],
|
|
408
|
+
});
|
|
409
|
+
handler.attach();
|
|
410
|
+
|
|
411
|
+
const events = [];
|
|
412
|
+
handler.on('task-rejected', (e) => events.push(e));
|
|
413
|
+
handler.on('task-start', (e) => events.push({ type: 'start', ...e }));
|
|
414
|
+
|
|
415
|
+
// Submit 3 tasks rapidly
|
|
416
|
+
for (let i = 0; i < 3; i++) {
|
|
417
|
+
signaling.simulateSignal({
|
|
418
|
+
type: 'task-assign',
|
|
419
|
+
from: 'node-A',
|
|
420
|
+
data: {
|
|
421
|
+
task: {
|
|
422
|
+
id: `capacity-task-${i}`,
|
|
423
|
+
type: 'compute',
|
|
424
|
+
data: Array(1000).fill(1), // Larger task to take time
|
|
425
|
+
options: { operation: 'sum' },
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
verified: true,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Wait a bit
|
|
433
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
434
|
+
|
|
435
|
+
// Check that we got at least one rejection for capacity
|
|
436
|
+
const capacityRejection = events.find(e => e.reason === 'capacity');
|
|
437
|
+
if (!capacityRejection) {
|
|
438
|
+
log('Note: Capacity test depends on timing, may not always trigger', 'warn');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
handler.detach();
|
|
442
|
+
await workerPool.shutdown();
|
|
443
|
+
|
|
444
|
+
log('Capacity limit tests completed');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function testProgressReporting() {
|
|
448
|
+
const signaling = new MockSignaling('node-B');
|
|
449
|
+
const workerPool = new RealWorkerPool({ size: 1 });
|
|
450
|
+
await workerPool.initialize();
|
|
451
|
+
|
|
452
|
+
const handler = new TaskExecutionHandler({
|
|
453
|
+
signaling,
|
|
454
|
+
workerPool,
|
|
455
|
+
nodeId: 'node-B',
|
|
456
|
+
capabilities: ['compute'],
|
|
457
|
+
reportProgress: true,
|
|
458
|
+
progressInterval: 100, // Fast progress for testing
|
|
459
|
+
});
|
|
460
|
+
handler.attach();
|
|
461
|
+
|
|
462
|
+
// Submit a task that takes some time
|
|
463
|
+
signaling.simulateSignal({
|
|
464
|
+
type: 'task-assign',
|
|
465
|
+
from: 'node-A',
|
|
466
|
+
data: {
|
|
467
|
+
task: {
|
|
468
|
+
id: 'progress-task',
|
|
469
|
+
type: 'compute',
|
|
470
|
+
data: Array(10000).fill(1),
|
|
471
|
+
options: { operation: 'sum' },
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
verified: true,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// Wait for completion
|
|
478
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
479
|
+
|
|
480
|
+
// Check for progress signals
|
|
481
|
+
const progressSignals = signaling.sentSignals.filter(s => s.type === 'task-progress');
|
|
482
|
+
// Progress reporting may or may not fire depending on execution speed
|
|
483
|
+
log(`Progress signals sent: ${progressSignals.length}`, 'info');
|
|
484
|
+
|
|
485
|
+
// Check for result signal
|
|
486
|
+
const resultSignal = signaling.sentSignals.find(s => s.type === 'task-result');
|
|
487
|
+
if (!resultSignal) throw new Error('Result signal not sent');
|
|
488
|
+
|
|
489
|
+
handler.detach();
|
|
490
|
+
await workerPool.shutdown();
|
|
491
|
+
|
|
492
|
+
log('Progress reporting tests passed');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ============================================
|
|
496
|
+
// MAIN
|
|
497
|
+
// ============================================
|
|
498
|
+
|
|
499
|
+
async function main() {
|
|
500
|
+
console.log('\n========================================');
|
|
501
|
+
console.log(' Task Execution Handler Integration Tests');
|
|
502
|
+
console.log('========================================\n');
|
|
503
|
+
|
|
504
|
+
const tests = [
|
|
505
|
+
['TaskValidator', testTaskValidator],
|
|
506
|
+
['WorkerPool Execution', testWorkerPoolExecution],
|
|
507
|
+
['TaskExecutionHandler Basic', testTaskExecutionHandler],
|
|
508
|
+
['Task Rejection', testTaskRejection],
|
|
509
|
+
['Remote Task Submission', testSubmitTaskToRemote],
|
|
510
|
+
['Capacity Limits', testCapacityLimit],
|
|
511
|
+
['Progress Reporting', testProgressReporting],
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
let passed = 0;
|
|
515
|
+
let failed = 0;
|
|
516
|
+
|
|
517
|
+
for (const [name, testFn] of tests) {
|
|
518
|
+
console.log('');
|
|
519
|
+
const success = await runTest(name, testFn);
|
|
520
|
+
if (success) passed++;
|
|
521
|
+
else failed++;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
console.log('\n========================================');
|
|
525
|
+
console.log(` Results: ${passed} passed, ${failed} failed`);
|
|
526
|
+
console.log('========================================\n');
|
|
527
|
+
|
|
528
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
main().catch(err => {
|
|
532
|
+
console.error('Test runner error:', err);
|
|
533
|
+
process.exit(1);
|
|
534
|
+
});
|