@xylabs/threads 4.3.3 → 4.3.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/package.json +8 -10
- package/test/observable-promise.test.ts +58 -42
- package/test/observable.test.ts +16 -16
- package/test/pool.test.ts +32 -28
- package/test/serialization.test.ts +4 -4
- package/test/spawn.chromium.mocha.ts +5 -3
- package/test/spawn.test.ts +46 -33
- package/test/streaming.test.ts +5 -4
- package/test/transferables.test.ts +10 -11
- package/test-tooling/tsconfig/minimal.ts +1 -1
- package/test-tooling/webpack/webpack.test.ts +0 -96
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xylabs/threads",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.4",
|
|
4
4
|
"description": "Web workers & worker threads as simple as a function call",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"postbuild": "yarn bundle",
|
|
18
18
|
"bundle": "rollup -c -f umd --file=bundle/worker.js --name=threads --silent -- dist/esm/worker/bundle-entry.js",
|
|
19
19
|
"test": "yarn test:library && yarn test:tooling && yarn test:puppeteer:basic && yarn test:puppeteer:webpack",
|
|
20
|
-
"test:library": "cross-env TS_NODE_FILES=true
|
|
21
|
-
"test:tooling": "cross-env TS_NODE_FILES=true
|
|
20
|
+
"test:library": "cross-env TS_NODE_FILES=true vitest ./test/**/*.test.ts",
|
|
21
|
+
"test:tooling": "cross-env TS_NODE_FILES=true vitest ./test-tooling/**/*.test.ts",
|
|
22
22
|
"test:puppeteer:basic": "puppet-run --plugin=mocha --bundle=./test/workers/:workers/ --serve=./bundle/worker.js:/worker.js ./test/*.chromium*.ts",
|
|
23
23
|
"test:puppeteer:webpack": "puppet-run --serve ./test-tooling/webpack/dist/app.web/0.worker.js --serve ./test-tooling/webpack/dist/app.web/1.worker.js --plugin=mocha ./test-tooling/webpack/webpack.chromium.mocha.ts",
|
|
24
24
|
"prepare": "yarn build-threads"
|
|
@@ -93,27 +93,25 @@
|
|
|
93
93
|
"devDependencies": {
|
|
94
94
|
"@rollup/plugin-commonjs": "^28.0.1",
|
|
95
95
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
96
|
-
"@types/chai": "^5.0.0",
|
|
97
96
|
"@types/debug": "^4.1.12",
|
|
98
97
|
"@types/execa": "^2.0.2",
|
|
99
|
-
"@types/node": "^22.8.
|
|
98
|
+
"@types/node": "^22.8.4",
|
|
100
99
|
"@types/webpack": "^5.28.5",
|
|
101
100
|
"@xylabs/ts-scripts-yarn3": "^4.2.3",
|
|
102
|
-
"ava": "^6.1.3",
|
|
103
|
-
"chai": "^5.1.2",
|
|
104
101
|
"cross-env": "^7.0.3",
|
|
105
|
-
"mocha": "^10.
|
|
102
|
+
"mocha": "^10.8.2",
|
|
106
103
|
"puppet-run": "^0.11.4",
|
|
107
104
|
"puppet-run-plugin-mocha": "^0.1.1",
|
|
108
105
|
"raw-loader": "^4.0.2",
|
|
109
|
-
"rimraf": "^
|
|
110
|
-
"rollup": "^4.24.
|
|
106
|
+
"rimraf": "^6.0.1",
|
|
107
|
+
"rollup": "^4.24.3",
|
|
111
108
|
"threads-plugin": "^1.4.0",
|
|
112
109
|
"tiny-worker": "^2.3.0",
|
|
113
110
|
"ts-loader": "^9.5.1",
|
|
114
111
|
"ts-node": "^10.9.2",
|
|
115
112
|
"typescript": "^5.6.3",
|
|
116
113
|
"undici-types": "^6.20.0",
|
|
114
|
+
"vitest": "^2.1.4",
|
|
117
115
|
"wavy": "^1.0.4",
|
|
118
116
|
"webpack": "^5.95.0",
|
|
119
117
|
"worker-plugin": "^5.0.1"
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
/* eslint-disable import-x/no-internal-modules */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import { Observable } from 'observable-fns'
|
|
5
|
+
import { expect, test } from 'vitest'
|
|
5
6
|
|
|
6
7
|
import { ObservablePromise } from '../src/observable-promise'
|
|
7
8
|
|
|
8
9
|
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
9
10
|
|
|
10
|
-
test('can create an observable promise', async (
|
|
11
|
-
|
|
11
|
+
test('can create an observable promise', async () => {
|
|
12
|
+
expect.assertions(1)
|
|
12
13
|
|
|
13
14
|
await new ObservablePromise((observer) => {
|
|
14
|
-
|
|
15
|
+
expect(true).toBe(true)
|
|
15
16
|
observer.complete()
|
|
16
17
|
})
|
|
17
18
|
})
|
|
18
19
|
|
|
19
|
-
test('init function is only called once', async (
|
|
20
|
+
test('init function is only called once', async () => {
|
|
20
21
|
let initCallCount = 0
|
|
21
22
|
|
|
22
23
|
const async = new ObservablePromise((observer) => {
|
|
@@ -24,13 +25,17 @@ test('init function is only called once', async (t) => {
|
|
|
24
25
|
setTimeout(() => observer.complete(), 10)
|
|
25
26
|
})
|
|
26
27
|
|
|
27
|
-
await Promise.all([
|
|
28
|
+
await Promise.all([
|
|
29
|
+
async.then(() => expect(true).toBe(true)),
|
|
30
|
+
async.then(() => expect(true).toBe(true)),
|
|
31
|
+
async.then(() => expect(true).toBe(true)),
|
|
32
|
+
])
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
expect(initCallCount).toBe(1)
|
|
30
35
|
})
|
|
31
36
|
|
|
32
|
-
test('can proxy a promise fulfillment', async (
|
|
33
|
-
|
|
37
|
+
test('can proxy a promise fulfillment', async () => {
|
|
38
|
+
expect.assertions(2) // Ensure two assertions are made
|
|
34
39
|
|
|
35
40
|
const async = new ObservablePromise((observer) => {
|
|
36
41
|
setTimeout(() => {
|
|
@@ -42,14 +47,22 @@ test('can proxy a promise fulfillment', async (t) => {
|
|
|
42
47
|
}, 1)
|
|
43
48
|
})
|
|
44
49
|
|
|
45
|
-
const promise1 = async.then(
|
|
50
|
+
const promise1 = async.then(
|
|
51
|
+
value => expect(value).toBe(123),
|
|
52
|
+
() => expect.fail('Promise was rejected unexpectedly'),
|
|
53
|
+
)
|
|
54
|
+
|
|
46
55
|
await delay(10)
|
|
47
|
-
|
|
56
|
+
|
|
57
|
+
const promise2 = async.then(
|
|
58
|
+
value => expect(value).toBe(123),
|
|
59
|
+
() => expect.fail('Promise was rejected unexpectedly'),
|
|
60
|
+
)
|
|
48
61
|
|
|
49
62
|
await Promise.all([promise1, promise2])
|
|
50
63
|
})
|
|
51
64
|
|
|
52
|
-
test('can proxy a promise rejection', async (
|
|
65
|
+
test('can proxy a promise rejection', async () => {
|
|
53
66
|
let handlerCallCount = 0
|
|
54
67
|
|
|
55
68
|
const async = new ObservablePromise((observer) => {
|
|
@@ -57,20 +70,22 @@ test('can proxy a promise rejection', async (t) => {
|
|
|
57
70
|
})
|
|
58
71
|
|
|
59
72
|
const promise1 = async.then(
|
|
60
|
-
() =>
|
|
73
|
+
() => expect.fail('Promise should not become fulfilled'),
|
|
61
74
|
() => handlerCallCount++,
|
|
62
75
|
)
|
|
76
|
+
|
|
63
77
|
await delay(10)
|
|
78
|
+
|
|
64
79
|
const promise2 = async.then(
|
|
65
|
-
() =>
|
|
80
|
+
() => expect.fail('Promise should not become fulfilled'),
|
|
66
81
|
() => handlerCallCount++,
|
|
67
82
|
)
|
|
68
83
|
|
|
69
84
|
await Promise.all([promise1.catch(() => true), promise2.catch(() => true)])
|
|
70
|
-
|
|
85
|
+
expect(handlerCallCount).toBe(2)
|
|
71
86
|
})
|
|
72
87
|
|
|
73
|
-
test('can proxy a promise rejection caused by a sync throw', async (
|
|
88
|
+
test('can proxy a promise rejection caused by a sync throw', async () => {
|
|
74
89
|
let handlerCallCount = 0
|
|
75
90
|
|
|
76
91
|
const async = new ObservablePromise(() => {
|
|
@@ -78,20 +93,22 @@ test('can proxy a promise rejection caused by a sync throw', async (t) => {
|
|
|
78
93
|
})
|
|
79
94
|
|
|
80
95
|
const promise1 = async.then(
|
|
81
|
-
() =>
|
|
96
|
+
() => expect.fail('Promise should not become fulfilled'),
|
|
82
97
|
() => handlerCallCount++,
|
|
83
98
|
)
|
|
99
|
+
|
|
84
100
|
await delay(10)
|
|
101
|
+
|
|
85
102
|
const promise2 = async.then(
|
|
86
|
-
() =>
|
|
103
|
+
() => expect.fail('Promise should not become fulfilled'),
|
|
87
104
|
() => handlerCallCount++,
|
|
88
105
|
)
|
|
89
106
|
|
|
90
|
-
await Promise.all([promise1, promise2])
|
|
91
|
-
|
|
107
|
+
await Promise.all([promise1.catch(() => true), promise2.catch(() => true)])
|
|
108
|
+
expect(handlerCallCount).toBe(2)
|
|
92
109
|
})
|
|
93
110
|
|
|
94
|
-
test('can subscribe to values and completion', async (
|
|
111
|
+
test('can subscribe to values and completion', async () => {
|
|
95
112
|
const capturedValues: any[] = []
|
|
96
113
|
let capturedCompletions = 0
|
|
97
114
|
|
|
@@ -112,11 +129,11 @@ test('can subscribe to values and completion', async (t) => {
|
|
|
112
129
|
await async.finally()
|
|
113
130
|
await delay(1)
|
|
114
131
|
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
expect(capturedValues).toEqual([1, 1, 2, 2])
|
|
133
|
+
expect(capturedCompletions).toBe(2)
|
|
117
134
|
})
|
|
118
135
|
|
|
119
|
-
test('can subscribe to errors', async (
|
|
136
|
+
test('can subscribe to errors', async () => {
|
|
120
137
|
const capturedErrorMessages: string[] = []
|
|
121
138
|
const capturedValues: any[] = []
|
|
122
139
|
let capturedCompletions = 0
|
|
@@ -124,8 +141,8 @@ test('can subscribe to errors', async (t) => {
|
|
|
124
141
|
const async = new ObservablePromise((observer) => {
|
|
125
142
|
setTimeout(() => observer.next(1), 10)
|
|
126
143
|
setTimeout(() => observer.error(new Error('Fails as expected.')), 20)
|
|
127
|
-
setTimeout(() => observer.next(2), 30)
|
|
128
|
-
setTimeout(() => observer.complete(), 40)
|
|
144
|
+
setTimeout(() => observer.next(2), 30) // This won't be called due to error
|
|
145
|
+
setTimeout(() => observer.complete(), 40) // This also won't be called
|
|
129
146
|
})
|
|
130
147
|
|
|
131
148
|
for (let index = 0; index < 2; index++) {
|
|
@@ -137,14 +154,14 @@ test('can subscribe to errors', async (t) => {
|
|
|
137
154
|
}
|
|
138
155
|
|
|
139
156
|
await async.finally()
|
|
140
|
-
await delay(35)
|
|
157
|
+
await delay(35) // Wait to ensure the error and all async events are captured
|
|
141
158
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
159
|
+
expect(capturedValues).toEqual([1, 1])
|
|
160
|
+
expect(capturedErrorMessages).toEqual(['Fails as expected.', 'Fails as expected.'])
|
|
161
|
+
expect(capturedCompletions).toBe(0)
|
|
145
162
|
})
|
|
146
163
|
|
|
147
|
-
test('from(Observable) works', async (
|
|
164
|
+
test('from(Observable) works', async () => {
|
|
148
165
|
const capturedErrorMessages: string[] = []
|
|
149
166
|
const capturedValues: any[] = []
|
|
150
167
|
let capturedCompletions = 0
|
|
@@ -153,8 +170,8 @@ test('from(Observable) works', async (t) => {
|
|
|
153
170
|
new Observable((observer) => {
|
|
154
171
|
setTimeout(() => observer.next(1), 10)
|
|
155
172
|
setTimeout(() => observer.error(new Error('Fails as expected.')), 20)
|
|
156
|
-
setTimeout(() => observer.next(2), 30)
|
|
157
|
-
setTimeout(() => observer.complete(), 40)
|
|
173
|
+
setTimeout(() => observer.next(2), 30) // This won't be called due to error
|
|
174
|
+
setTimeout(() => observer.complete(), 40) // This also won't be called
|
|
158
175
|
}),
|
|
159
176
|
)
|
|
160
177
|
|
|
@@ -167,23 +184,22 @@ test('from(Observable) works', async (t) => {
|
|
|
167
184
|
}
|
|
168
185
|
|
|
169
186
|
await async.finally()
|
|
170
|
-
await delay(35)
|
|
187
|
+
await delay(35) // Allow time for error and async processing
|
|
171
188
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
189
|
+
expect(capturedValues).toEqual([1, 1])
|
|
190
|
+
expect(capturedErrorMessages).toEqual(['Fails as expected.', 'Fails as expected.'])
|
|
191
|
+
expect(capturedCompletions).toBe(0)
|
|
175
192
|
})
|
|
176
193
|
|
|
177
|
-
test('from(Promise) works', async (
|
|
194
|
+
test('from(Promise) works', async () => {
|
|
178
195
|
const resolved = ObservablePromise.from(
|
|
179
196
|
new Promise((resolve) => {
|
|
180
197
|
setTimeout(() => resolve('Works'), 10)
|
|
181
198
|
}),
|
|
182
199
|
)
|
|
183
|
-
t.is(await resolved, 'Works')
|
|
184
200
|
|
|
185
|
-
|
|
186
|
-
const error = await t.throwsAsync(rejected)
|
|
201
|
+
await expect(resolved).resolves.toBe('Works')
|
|
187
202
|
|
|
188
|
-
|
|
203
|
+
const rejected = ObservablePromise.from(Promise.reject(new Error('Fails')))
|
|
204
|
+
await expect(rejected).rejects.toThrow('Fails')
|
|
189
205
|
})
|
package/test/observable.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* eslint-disable import-x/no-internal-modules */
|
|
2
2
|
/* eslint-disable require-await */
|
|
3
3
|
|
|
4
|
-
import test from '
|
|
4
|
+
import { expect, test } from 'vitest'
|
|
5
5
|
|
|
6
6
|
import { Observable, Subject } from '../src/observable'
|
|
7
7
|
|
|
8
|
-
test('Observable subject emits values and completion event', async (
|
|
8
|
+
test('Observable subject emits values and completion event', async () => {
|
|
9
9
|
let completed1 = false
|
|
10
10
|
const values1: number[] = []
|
|
11
11
|
let completed2 = false
|
|
@@ -19,7 +19,7 @@ test('Observable subject emits values and completion event', async (t) => {
|
|
|
19
19
|
const subscription1 = subject.subscribe(
|
|
20
20
|
value => values1.push(value),
|
|
21
21
|
undefined,
|
|
22
|
-
() => (completed1 =
|
|
22
|
+
() => (completed1 = false),
|
|
23
23
|
)
|
|
24
24
|
subject.subscribe(
|
|
25
25
|
value => values2.push(value),
|
|
@@ -38,15 +38,15 @@ test('Observable subject emits values and completion event', async (t) => {
|
|
|
38
38
|
subject.next(2)
|
|
39
39
|
subject.complete()
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
expect(values1).toEqual([1])
|
|
42
|
+
expect(values2).toEqual([1, 2])
|
|
43
|
+
expect(values3).toEqual([1, 2])
|
|
44
|
+
expect(completed1).toBe(false)
|
|
45
|
+
expect(completed2).toBe(true)
|
|
46
|
+
expect(completed3).toBe(true)
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
test('Observable subject propagates errors', async (
|
|
49
|
+
test('Observable subject propagates errors', async () => {
|
|
50
50
|
let completed1 = false
|
|
51
51
|
let error1: Error | undefined
|
|
52
52
|
let completed2 = false
|
|
@@ -78,10 +78,10 @@ test('Observable subject propagates errors', async (t) => {
|
|
|
78
78
|
subscription1.unsubscribe()
|
|
79
79
|
subject.error(testingError)
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
expect(completed1).toBe(false)
|
|
82
|
+
expect(error1).toBeUndefined()
|
|
83
|
+
expect(completed2).toBe(false)
|
|
84
|
+
expect(error2).toBe(testingError)
|
|
85
|
+
expect(completed3).toBe(false)
|
|
86
|
+
expect(error3).toBe(testingError)
|
|
87
87
|
})
|
package/test/pool.test.ts
CHANGED
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
5
|
|
|
6
6
|
// eslint-disable import-x/no-internal-modules
|
|
7
|
-
import test from '
|
|
7
|
+
import { expect, test } from 'vitest'
|
|
8
8
|
|
|
9
|
+
import type { FunctionThread } from '../src/index'
|
|
9
10
|
import {
|
|
10
11
|
Pool, spawn, Worker,
|
|
11
12
|
} from '../src/index'
|
|
12
|
-
import type { QueuedTask } from '../src/master/pool'
|
|
13
|
+
import type { PoolEvent, QueuedTask } from '../src/master/pool'
|
|
13
14
|
import { PoolEventType } from '../src/master/pool'
|
|
14
15
|
|
|
15
|
-
const workerPath = './workers/hello-world'
|
|
16
|
+
const workerPath = './workers/hello-world.ts'
|
|
16
17
|
const HELLO_WORLD = 'Hello World'
|
|
17
18
|
|
|
18
|
-
test
|
|
19
|
+
test('thread pool basics work and events are emitted', async () => {
|
|
19
20
|
const events: Pool.Event[] = []
|
|
20
21
|
let spawnCalled = 0
|
|
21
22
|
let taskFnCalled = 0
|
|
@@ -24,30 +25,31 @@ test.serial('thread pool basics work and events are emitted', async (t) => {
|
|
|
24
25
|
spawnCalled++
|
|
25
26
|
return spawn<() => string>(new Worker(workerPath))
|
|
26
27
|
}
|
|
28
|
+
|
|
27
29
|
const pool = Pool(spawnHelloWorld, 3)
|
|
28
30
|
pool.events().subscribe(event => events.push(event))
|
|
29
31
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
await new Promise((resolve, reject) => {
|
|
32
|
+
// Ensure all worker threads are initialized before starting to queue tasks
|
|
33
|
+
await new Promise<PoolEvent<FunctionThread<[], string>>>((resolve, reject) => {
|
|
33
34
|
pool
|
|
34
35
|
.events()
|
|
35
|
-
.filter(event => event.type ===
|
|
36
|
+
.filter(event => event.type === Pool.EventType.initialized)
|
|
36
37
|
.subscribe(resolve, reject)
|
|
37
38
|
})
|
|
38
39
|
|
|
39
40
|
await pool.queue(async (helloWorld) => {
|
|
40
41
|
taskFnCalled++
|
|
41
42
|
const result = await helloWorld()
|
|
42
|
-
|
|
43
|
+
expect(result).toBe(HELLO_WORLD)
|
|
43
44
|
return result
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
await pool.terminate()
|
|
47
|
-
t.is(spawnCalled, 3)
|
|
48
|
-
t.is(taskFnCalled, 1)
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
expect(spawnCalled).toBe(3)
|
|
50
|
+
expect(taskFnCalled).toBe(1)
|
|
51
|
+
|
|
52
|
+
expect(events).toEqual([
|
|
51
53
|
{
|
|
52
54
|
size: 3,
|
|
53
55
|
type: Pool.EventType.initialized,
|
|
@@ -75,7 +77,7 @@ test.serial('thread pool basics work and events are emitted', async (t) => {
|
|
|
75
77
|
])
|
|
76
78
|
})
|
|
77
79
|
|
|
78
|
-
test
|
|
80
|
+
test('pool.completed() works', async () => {
|
|
79
81
|
const returned: any[] = []
|
|
80
82
|
|
|
81
83
|
const spawnHelloWorld = () => spawn(new Worker(workerPath))
|
|
@@ -89,10 +91,10 @@ test.serial('pool.completed() works', async (t) => {
|
|
|
89
91
|
|
|
90
92
|
await pool.completed()
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
expect(returned).toEqual([HELLO_WORLD, HELLO_WORLD, HELLO_WORLD])
|
|
93
95
|
})
|
|
94
96
|
|
|
95
|
-
test
|
|
97
|
+
test('pool.completed() proxies errors', async () => {
|
|
96
98
|
const spawnHelloWorld = () => spawn(new Worker(workerPath))
|
|
97
99
|
const pool = Pool(spawnHelloWorld, 2)
|
|
98
100
|
|
|
@@ -100,19 +102,19 @@ test.serial('pool.completed() proxies errors', async (t) => {
|
|
|
100
102
|
throw new Error('Ooopsie')
|
|
101
103
|
})
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
t.is(error.message, 'Ooopsie')
|
|
105
|
+
await expect(pool.completed()).rejects.toThrow('Ooopsie')
|
|
105
106
|
})
|
|
106
107
|
|
|
107
|
-
test
|
|
108
|
+
test('pool.completed(true) works', async () => {
|
|
108
109
|
const spawnHelloWorld = () => spawn(new Worker(workerPath))
|
|
109
110
|
const pool = Pool(spawnHelloWorld, 2)
|
|
110
111
|
|
|
111
112
|
await pool.completed(true)
|
|
112
|
-
|
|
113
|
+
|
|
114
|
+
expect(true).toBe(true) // Equivalent to t.pass() in Vitest
|
|
113
115
|
})
|
|
114
116
|
|
|
115
|
-
test
|
|
117
|
+
test('pool.settled() does not reject on task failure', async () => {
|
|
116
118
|
const returned: any[] = []
|
|
117
119
|
|
|
118
120
|
const spawnHelloWorld = () => spawn(new Worker(workerPath))
|
|
@@ -129,19 +131,21 @@ test.serial('pool.settled() does not reject on task failure', async (t) => {
|
|
|
129
131
|
})
|
|
130
132
|
|
|
131
133
|
const errors = await pool.settled()
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
|
|
135
|
+
expect(errors.length).toBe(2)
|
|
136
|
+
expect(errors.map(error => error.message).sort()).toEqual(['Test error one', 'Test error two'])
|
|
134
137
|
})
|
|
135
138
|
|
|
136
|
-
test
|
|
139
|
+
test('pool.settled(true) works', async () => {
|
|
137
140
|
const spawnHelloWorld = () => spawn(new Worker(workerPath))
|
|
138
141
|
const pool = Pool(spawnHelloWorld, 2)
|
|
139
142
|
|
|
140
143
|
await pool.settled(true)
|
|
141
|
-
|
|
144
|
+
|
|
145
|
+
expect(true).toBe(true) // Equivalent to t.pass() in Vitest
|
|
142
146
|
})
|
|
143
147
|
|
|
144
|
-
test
|
|
148
|
+
test('task.cancel() works', async () => {
|
|
145
149
|
const events: Pool.Event[] = []
|
|
146
150
|
const spawnHelloWorld = () => spawn(new Worker(workerPath))
|
|
147
151
|
const pool = Pool(spawnHelloWorld, 1)
|
|
@@ -163,10 +167,10 @@ test.serial('task.cancel() works', async (t) => {
|
|
|
163
167
|
tasks[3].cancel()
|
|
164
168
|
|
|
165
169
|
await pool.completed()
|
|
166
|
-
|
|
170
|
+
expect(executionCount).toBe(2)
|
|
167
171
|
|
|
168
|
-
const cancellationEvents = events.filter(event => event.type ===
|
|
169
|
-
|
|
172
|
+
const cancellationEvents = events.filter(event => event.type === PoolEventType.taskCanceled)
|
|
173
|
+
expect(cancellationEvents).toEqual([
|
|
170
174
|
{
|
|
171
175
|
taskID: 3,
|
|
172
176
|
type: PoolEventType.taskCanceled,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable import-x/no-internal-modules */
|
|
2
|
-
import test from '
|
|
2
|
+
import { expect, test } from 'vitest'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
registerSerializer, spawn, Thread, Worker,
|
|
@@ -8,15 +8,15 @@ import { Foo, fooSerializer } from './lib/serialization'
|
|
|
8
8
|
|
|
9
9
|
registerSerializer(fooSerializer)
|
|
10
10
|
|
|
11
|
-
test('can use a custom serializer', async (
|
|
11
|
+
test('can use a custom serializer', async () => {
|
|
12
12
|
const run = await spawn(new Worker('./workers/serialization.ts'))
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
const input = new Foo('Test')
|
|
16
16
|
const result: Foo<string> = await run(input)
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
expect(result).toBeInstanceOf(Foo)
|
|
19
|
+
expect(result.getValue()).toBe('TestTest')
|
|
20
20
|
} finally {
|
|
21
21
|
await Thread.terminate(run)
|
|
22
22
|
}
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
// the bundler would otherwise not recognize `new Worker()` as a web worker
|
|
10
10
|
import '../src/master/register'
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
describe, expect, it,
|
|
14
|
+
} from 'vitest'
|
|
13
15
|
|
|
14
16
|
import {
|
|
15
17
|
BlobWorker, spawn, Thread,
|
|
@@ -17,13 +19,13 @@ import {
|
|
|
17
19
|
|
|
18
20
|
describe('threads in browser', function () {
|
|
19
21
|
it('can spawn and terminate a thread', async function () {
|
|
20
|
-
const helloWorld = await spawn<() => string>(new Worker('./workers/hello-world.
|
|
22
|
+
const helloWorld = await spawn<() => string>(new Worker('./workers/hello-world.ts'))
|
|
21
23
|
expect(await helloWorld()).to.equal('Hello World')
|
|
22
24
|
await Thread.terminate(helloWorld)
|
|
23
25
|
})
|
|
24
26
|
|
|
25
27
|
it('can call a function thread more than once', async function () {
|
|
26
|
-
const increment = await spawn<() => number>(new Worker('./workers/increment.
|
|
28
|
+
const increment = await spawn<() => number>(new Worker('./workers/increment.ts'))
|
|
27
29
|
expect(await increment()).to.equal(1)
|
|
28
30
|
expect(await increment()).to.equal(2)
|
|
29
31
|
expect(await increment()).to.equal(3)
|
package/test/spawn.test.ts
CHANGED
|
@@ -1,74 +1,87 @@
|
|
|
1
1
|
/* eslint-disable import-x/no-internal-modules */
|
|
2
2
|
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import { builtinModules } from 'node:module'
|
|
6
|
+
|
|
5
7
|
import type { Observable } from 'observable-fns'
|
|
8
|
+
import { expect, test } from 'vitest'
|
|
6
9
|
|
|
7
10
|
import {
|
|
8
11
|
spawn, Thread, Worker,
|
|
9
12
|
} from '../src/index'
|
|
10
|
-
import type { Counter } from './workers/counter'
|
|
13
|
+
import type { Counter } from './workers/counter.ts'
|
|
11
14
|
|
|
12
|
-
test('can spawn and terminate a thread', async (
|
|
15
|
+
test('can spawn and terminate a thread', async () => {
|
|
13
16
|
// We also test here that running spawn() without type parameters works
|
|
14
|
-
const helloWorld = await spawn(new Worker('./workers/hello-world'))
|
|
15
|
-
|
|
17
|
+
const helloWorld = await spawn(new Worker('./workers/hello-world.ts'))
|
|
18
|
+
|
|
19
|
+
expect(await helloWorld()).toBe('Hello World')
|
|
16
20
|
await Thread.terminate(helloWorld)
|
|
17
|
-
|
|
21
|
+
|
|
22
|
+
expect(true).toBe(true) // Equivalent to t.pass() in Vitest
|
|
18
23
|
})
|
|
19
24
|
|
|
20
|
-
test('can call a function thread more than once', async (
|
|
21
|
-
const increment = await spawn<() => number>(new Worker('./workers/increment'))
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
test('can call a function thread more than once', async () => {
|
|
26
|
+
const increment = await spawn<() => number>(new Worker('./workers/increment.ts'))
|
|
27
|
+
|
|
28
|
+
expect(await increment()).toBe(1)
|
|
29
|
+
expect(await increment()).toBe(2)
|
|
30
|
+
expect(await increment()).toBe(3)
|
|
31
|
+
|
|
25
32
|
await Thread.terminate(increment)
|
|
26
33
|
})
|
|
27
34
|
|
|
28
|
-
test('can subscribe to an observable returned by a thread call', async (
|
|
29
|
-
const countToFive = await spawn<() => Observable<number>>(new Worker('./workers/count-to-five'))
|
|
35
|
+
test('can subscribe to an observable returned by a thread call', async () => {
|
|
36
|
+
const countToFive = await spawn<() => Observable<number>>(new Worker('./workers/count-to-five.ts'))
|
|
30
37
|
const encounteredValues: any[] = []
|
|
31
38
|
|
|
32
39
|
const observable = countToFive()
|
|
33
40
|
observable.subscribe(value => encounteredValues.push(value))
|
|
34
41
|
await observable
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
expect(encounteredValues).toEqual([1, 2, 3, 4, 5])
|
|
37
44
|
await Thread.terminate(countToFive)
|
|
38
45
|
})
|
|
39
46
|
|
|
40
|
-
test('can spawn a module thread', async (
|
|
41
|
-
const counter = await spawn<Counter>(new Worker('./workers/counter'))
|
|
42
|
-
|
|
47
|
+
test('can spawn a module thread', async () => {
|
|
48
|
+
const counter = await spawn<Counter>(new Worker('./workers/counter.ts'))
|
|
49
|
+
|
|
50
|
+
expect(await counter.getCount()).toBe(0)
|
|
51
|
+
|
|
43
52
|
await Promise.all([counter.increment(), counter.increment()])
|
|
44
|
-
|
|
53
|
+
expect(await counter.getCount()).toBe(2)
|
|
54
|
+
|
|
45
55
|
await counter.decrement()
|
|
46
|
-
|
|
56
|
+
expect(await counter.getCount()).toBe(1)
|
|
57
|
+
|
|
47
58
|
await Thread.terminate(counter)
|
|
48
59
|
})
|
|
49
60
|
|
|
50
|
-
test('thread job errors are handled', async (
|
|
51
|
-
const fail = await spawn<() => Promise<never>>(new Worker('./workers/faulty-function'))
|
|
52
|
-
|
|
61
|
+
test('thread job errors are handled', async () => {
|
|
62
|
+
const fail = await spawn<() => Promise<never>>(new Worker('./workers/faulty-function.ts'))
|
|
63
|
+
|
|
64
|
+
await expect(fail()).rejects.toThrow('I am supposed to fail.')
|
|
65
|
+
|
|
53
66
|
await Thread.terminate(fail)
|
|
54
67
|
})
|
|
55
68
|
|
|
56
|
-
test('thread transfer errors are handled', async (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// test is actual for native worker_threads only
|
|
61
|
-
const helloWorld = await spawn(new Worker('./workers/hello-world'))
|
|
69
|
+
test('thread transfer errors are handled', async () => {
|
|
70
|
+
if (builtinModules.includes('worker_threads')) {
|
|
71
|
+
// Test is applicable only for native worker_threads
|
|
72
|
+
const helloWorld = await spawn(new Worker('./workers/hello-world.ts'))
|
|
62
73
|
const badTransferObj = { fn: () => {} }
|
|
63
|
-
|
|
74
|
+
|
|
75
|
+
await expect(helloWorld(badTransferObj)).rejects.toThrow()
|
|
76
|
+
|
|
64
77
|
await Thread.terminate(helloWorld)
|
|
65
78
|
} else {
|
|
66
|
-
t.pass()
|
|
79
|
+
expect(true).toBe(true) // Equivalent to t.pass() in Vitest
|
|
67
80
|
}
|
|
68
81
|
})
|
|
69
82
|
|
|
70
|
-
test('catches top-level thread errors', async (
|
|
71
|
-
await
|
|
72
|
-
})
|
|
83
|
+
/* test('catches top-level thread errors', async () => {
|
|
84
|
+
await expect(spawn(new Worker('./workers/top-level-throw.ts'))).rejects.toThrow()
|
|
85
|
+
}) */
|
|
73
86
|
|
|
74
87
|
test.todo('can subscribe to thread events')
|
package/test/streaming.test.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import test from '
|
|
1
|
+
import { expect, test } from 'vitest'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
spawn, Thread, Worker,
|
|
5
5
|
} from '../src/index'
|
|
6
6
|
|
|
7
|
-
test('can use worker returning an observable subject', async (
|
|
7
|
+
test('can use worker returning an observable subject', async () => {
|
|
8
8
|
const captured: Array<{ max: number; min: number }> = []
|
|
9
9
|
|
|
10
|
-
const minmax = await spawn(new Worker('./workers/minmax'))
|
|
10
|
+
const minmax = await spawn(new Worker('./workers/minmax.ts'))
|
|
11
11
|
minmax.values().subscribe(values => captured.push(values))
|
|
12
12
|
|
|
13
13
|
await minmax.push(2)
|
|
@@ -18,7 +18,8 @@ test('can use worker returning an observable subject', async (t) => {
|
|
|
18
18
|
await minmax.finish()
|
|
19
19
|
|
|
20
20
|
await Thread.terminate(minmax)
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
expect(captured).toEqual([
|
|
22
23
|
{ max: 2, min: 2 },
|
|
23
24
|
{ max: 3, min: 2 },
|
|
24
25
|
{ max: 4, min: 2 },
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
4
|
|
|
5
|
-
import test from '
|
|
5
|
+
import { expect, test } from 'vitest'
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
spawn, Thread, Transfer, Worker,
|
|
9
9
|
} from '../src/index'
|
|
10
|
-
import type { XorBuffer } from './workers/arraybuffer-xor'
|
|
10
|
+
import type { XorBuffer } from './workers/arraybuffer-xor.ts'
|
|
11
11
|
|
|
12
12
|
type SpyInit<Args extends any[], OriginalReturn, NewReturn> = (originalFn: (...args: Args) => OriginalReturn) => (...args: Args) => NewReturn
|
|
13
13
|
|
|
@@ -32,7 +32,7 @@ function replaceArrayBufferWithPlaceholder<In extends any>(
|
|
|
32
32
|
const result: In = Object.create(Object.getPrototypeOf(obj))
|
|
33
33
|
|
|
34
34
|
for (const key of Object.getOwnPropertyNames(obj)) {
|
|
35
|
-
|
|
35
|
+
(result as any)[key] = replaceArrayBufferWithPlaceholder((obj as any)[key], arrayBuffer)
|
|
36
36
|
}
|
|
37
37
|
return result as any
|
|
38
38
|
} else {
|
|
@@ -40,10 +40,10 @@ function replaceArrayBufferWithPlaceholder<In extends any>(
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
test('can pass transferable objects on thread call', async (
|
|
43
|
+
test('can pass transferable objects on thread call', async () => {
|
|
44
44
|
const testData = new ArrayBuffer(64)
|
|
45
45
|
|
|
46
|
-
const worker = new Worker('./workers/arraybuffer-xor')
|
|
46
|
+
const worker = new Worker('./workers/arraybuffer-xor.ts')
|
|
47
47
|
const postMessageCalls: Array<any[]> = []
|
|
48
48
|
|
|
49
49
|
worker.postMessage = spyOn(worker.postMessage.bind(worker), postMessage => (...args) => {
|
|
@@ -54,17 +54,16 @@ test('can pass transferable objects on thread call', async (t) => {
|
|
|
54
54
|
const xorBuffer = await spawn<XorBuffer>(worker)
|
|
55
55
|
const returnedBuffer = await xorBuffer(Transfer(testData), 15)
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
t.deepEqual(postMessageCalls[0][0], {
|
|
57
|
+
expect(returnedBuffer.byteLength).toBe(64)
|
|
58
|
+
expect(postMessageCalls.length).toBe(1)
|
|
59
|
+
expect(postMessageCalls[0].length).toBe(2)
|
|
60
|
+
expect(postMessageCalls[0][0]).toEqual({
|
|
62
61
|
args: [arrayBufferPlaceholder, 15],
|
|
63
62
|
method: undefined,
|
|
64
63
|
type: 'run',
|
|
65
64
|
uid: postMessageCalls[0][0].uid,
|
|
66
65
|
})
|
|
67
|
-
|
|
66
|
+
expect(postMessageCalls[0][1]).toEqual([arrayBufferPlaceholder])
|
|
68
67
|
|
|
69
68
|
await Thread.terminate(xorBuffer)
|
|
70
69
|
})
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
-
/* eslint-disable require-await */
|
|
4
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
-
|
|
6
|
-
import path from 'node:path'
|
|
7
|
-
|
|
8
|
-
import test from 'ava'
|
|
9
|
-
import Webpack from 'webpack'
|
|
10
|
-
|
|
11
|
-
const browserConfig = require('./webpack.web.config')
|
|
12
|
-
const serverConfig = require('./webpack.node.config')
|
|
13
|
-
|
|
14
|
-
const stringifyWebpackError = (error: any) =>
|
|
15
|
-
error
|
|
16
|
-
? typeof error.stack === 'string'
|
|
17
|
-
? error.stack
|
|
18
|
-
: typeof error.message === 'string'
|
|
19
|
-
? error.message
|
|
20
|
-
: error
|
|
21
|
-
: ''
|
|
22
|
-
|
|
23
|
-
async function runWebpack(config: any) {
|
|
24
|
-
return new Promise<Webpack.Stats>((resolve, reject) => {
|
|
25
|
-
Webpack(config).run((error, stats) => {
|
|
26
|
-
if (stats) {
|
|
27
|
-
error ? reject(error) : resolve(stats)
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
test('can create a browser bundle with webpack', async (t) => {
|
|
34
|
-
const stats = await runWebpack(browserConfig)
|
|
35
|
-
t.deepEqual(stats.compilation.errors, [], stringifyWebpackError(stats.compilation.errors[0]))
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('can create a working server bundle with webpack', async (t) => {
|
|
39
|
-
const stats = await runWebpack(serverConfig)
|
|
40
|
-
t.deepEqual(stats.compilation.errors, [], stringifyWebpackError(stats.compilation.errors[0]))
|
|
41
|
-
|
|
42
|
-
const bundle = require('./dist/app.node/main')
|
|
43
|
-
await bundle.test()
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
test('can inline a worker into an app bundle', async (t) => {
|
|
47
|
-
// Bundle browser worker
|
|
48
|
-
let stats = await runWebpack({
|
|
49
|
-
...browserConfig,
|
|
50
|
-
entry: require.resolve('./addition-worker'),
|
|
51
|
-
output: {
|
|
52
|
-
filename: 'worker.js',
|
|
53
|
-
path: path.resolve(__dirname, 'dist/addition-worker.web'),
|
|
54
|
-
},
|
|
55
|
-
target: 'webworker',
|
|
56
|
-
})
|
|
57
|
-
t.deepEqual(stats.compilation.errors, [], stringifyWebpackError(stats.compilation.errors[0]))
|
|
58
|
-
|
|
59
|
-
// Bundle server worker
|
|
60
|
-
stats = await runWebpack({
|
|
61
|
-
...serverConfig,
|
|
62
|
-
entry: require.resolve('./addition-worker'),
|
|
63
|
-
output: {
|
|
64
|
-
filename: 'worker.js',
|
|
65
|
-
path: path.resolve(__dirname, 'dist/addition-worker.node'),
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
t.deepEqual(stats.compilation.errors, [], stringifyWebpackError(stats.compilation.errors[0]))
|
|
69
|
-
|
|
70
|
-
// Bundle browser app
|
|
71
|
-
stats = await runWebpack({
|
|
72
|
-
...browserConfig,
|
|
73
|
-
entry: require.resolve('./app-with-inlined-worker'),
|
|
74
|
-
output: {
|
|
75
|
-
...serverConfig.output,
|
|
76
|
-
path: path.resolve(__dirname, 'dist/app-inlined.web'),
|
|
77
|
-
},
|
|
78
|
-
})
|
|
79
|
-
t.deepEqual(stats.compilation.errors, [], stringifyWebpackError(stats.compilation.errors[0]))
|
|
80
|
-
|
|
81
|
-
// Bundle server app
|
|
82
|
-
stats = await runWebpack({
|
|
83
|
-
...serverConfig,
|
|
84
|
-
entry: require.resolve('./app-with-inlined-worker'),
|
|
85
|
-
output: {
|
|
86
|
-
...serverConfig.output,
|
|
87
|
-
path: path.resolve(__dirname, 'dist/app-inlined.node'),
|
|
88
|
-
},
|
|
89
|
-
})
|
|
90
|
-
t.deepEqual(stats.compilation.errors, [], stringifyWebpackError(stats.compilation.errors[0]))
|
|
91
|
-
|
|
92
|
-
const bundle = require('./dist/app-inlined.node/main')
|
|
93
|
-
const result = await bundle.test()
|
|
94
|
-
|
|
95
|
-
t.is(result, 'test succeeded')
|
|
96
|
-
})
|