@neurodevs/node-lsl 12.0.6 → 13.0.0
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 +2 -5
- package/build/__tests__/AbstractPackageTest.d.ts +9 -3
- package/build/__tests__/AbstractPackageTest.js +37 -3
- package/build/__tests__/AbstractPackageTest.js.map +1 -1
- package/build/__tests__/impl/LiblslAdapter.test.d.ts +6 -0
- package/build/__tests__/impl/LiblslAdapter.test.js +133 -12
- package/build/__tests__/impl/LiblslAdapter.test.js.map +1 -1
- package/build/__tests__/impl/LslEventMarkerOutlet.test.js +5 -5
- package/build/__tests__/impl/LslEventMarkerOutlet.test.js.map +1 -1
- package/build/__tests__/impl/LslStreamInfo.test.js +3 -3
- package/build/__tests__/impl/LslStreamInfo.test.js.map +1 -1
- package/build/__tests__/impl/LslStreamInlet.test.d.ts +30 -2
- package/build/__tests__/impl/LslStreamInlet.test.js +181 -18
- package/build/__tests__/impl/LslStreamInlet.test.js.map +1 -1
- package/build/__tests__/impl/LslStreamOutlet.test.d.ts +16 -7
- package/build/__tests__/impl/LslStreamOutlet.test.js +84 -23
- package/build/__tests__/impl/LslStreamOutlet.test.js.map +1 -1
- package/build/__tests__/impl/LslWebSocketBridge.test.d.ts +15 -0
- package/build/__tests__/impl/LslWebSocketBridge.test.js +112 -2
- package/build/__tests__/impl/LslWebSocketBridge.test.js.map +1 -1
- package/build/assertions.d.ts +3 -3
- package/build/assertions.js +6 -6
- package/build/assertions.js.map +1 -1
- package/build/handleError.d.ts +8 -0
- package/build/handleError.js +25 -0
- package/build/handleError.js.map +1 -0
- package/build/impl/LiblslAdapter.d.ts +128 -13
- package/build/impl/LiblslAdapter.js +149 -71
- package/build/impl/LiblslAdapter.js.map +1 -1
- package/build/impl/LslEventMarkerOutlet.d.ts +4 -1
- package/build/impl/LslEventMarkerOutlet.js +3 -3
- package/build/impl/LslEventMarkerOutlet.js.map +1 -1
- package/build/impl/LslStreamInfo.d.ts +3 -3
- package/build/impl/LslStreamInfo.js +5 -5
- package/build/impl/LslStreamInfo.js.map +1 -1
- package/build/impl/LslStreamInlet.d.ts +52 -14
- package/build/impl/LslStreamInlet.js +157 -20
- package/build/impl/LslStreamInlet.js.map +1 -1
- package/build/impl/LslStreamOutlet.d.ts +13 -13
- package/build/impl/LslStreamOutlet.js +22 -21
- package/build/impl/LslStreamOutlet.js.map +1 -1
- package/build/impl/LslWebSocketBridge.d.ts +35 -3
- package/build/impl/LslWebSocketBridge.js +66 -3
- package/build/impl/LslWebSocketBridge.js.map +1 -1
- package/build/index.d.ts +0 -1
- package/build/index.js +0 -1
- package/build/index.js.map +1 -1
- package/build/scripts/runLslStreamOutlet.js +2 -3
- package/build/scripts/runLslStreamOutlet.js.map +1 -1
- package/build/scripts/runOutletToInlet.d.ts +1 -0
- package/build/scripts/runOutletToInlet.js +45 -0
- package/build/scripts/runOutletToInlet.js.map +1 -0
- package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.d.ts +1 -2
- package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js.map +1 -1
- package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.d.ts +2 -2
- package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js.map +1 -1
- package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.d.ts +1 -2
- package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js.map +1 -1
- package/build/testDoubles/Liblsl/FakeLiblsl.d.ts +15 -3
- package/build/testDoubles/Liblsl/FakeLiblsl.js +42 -0
- package/build/testDoubles/Liblsl/FakeLiblsl.js.map +1 -1
- package/build/testDoubles/StreamInfo/FakeStreamInfo.d.ts +1 -1
- package/build/testDoubles/StreamInfo/SpyStreamInfo.d.ts +1 -1
- package/build/testDoubles/StreamInlet/FakeStreamInlet.d.ts +12 -3
- package/build/testDoubles/StreamInlet/FakeStreamInlet.js +25 -5
- package/build/testDoubles/StreamInlet/FakeStreamInlet.js.map +1 -1
- package/build/testDoubles/StreamInlet/SpyStreamInlet.d.ts +3 -3
- package/build/testDoubles/StreamInlet/SpyStreamInlet.js +2 -2
- package/build/testDoubles/StreamInlet/SpyStreamInlet.js.map +1 -1
- package/build/testDoubles/StreamOutlet/FakeStreamOutlet.d.ts +4 -4
- package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js +6 -6
- package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js.map +1 -1
- package/build/testDoubles/StreamTransportBridge/FakeStreamTransportBridge.d.ts +6 -0
- package/build/testDoubles/StreamTransportBridge/FakeStreamTransportBridge.js +15 -0
- package/build/testDoubles/StreamTransportBridge/FakeStreamTransportBridge.js.map +1 -1
- package/build/testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.d.ts +4 -0
- package/build/testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.js +7 -0
- package/build/testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.js.map +1 -0
- package/build/testDoubles/WebSockets/FakeWebSocket.d.ts +10 -0
- package/build/testDoubles/WebSockets/FakeWebSocket.js +14 -0
- package/build/testDoubles/WebSockets/FakeWebSocket.js.map +1 -0
- package/build/testDoubles/WebSockets/FakeWebSocketServer.d.ts +13 -0
- package/build/testDoubles/WebSockets/FakeWebSocketServer.js +22 -0
- package/build/testDoubles/WebSockets/FakeWebSocketServer.js.map +1 -0
- package/build/testDoubles/generateRandomOutletOptions.js +3 -3
- package/build/testDoubles/generateRandomOutletOptions.js.map +1 -1
- package/package.json +5 -3
- package/src/__tests__/AbstractPackageTest.ts +52 -4
- package/src/__tests__/impl/LiblslAdapter.test.ts +181 -16
- package/src/__tests__/impl/LslEventMarkerOutlet.test.ts +5 -5
- package/src/__tests__/impl/LslStreamInfo.test.ts +3 -3
- package/src/__tests__/impl/LslStreamInlet.test.ts +253 -20
- package/src/__tests__/impl/LslStreamOutlet.test.ts +111 -24
- package/src/__tests__/impl/LslWebSocketBridge.test.ts +163 -5
- package/src/assertions.ts +7 -7
- package/src/handleError.ts +24 -0
- package/src/impl/LiblslAdapter.ts +322 -97
- package/src/impl/LslEventMarkerOutlet.ts +8 -4
- package/src/impl/LslStreamInfo.ts +9 -9
- package/src/impl/LslStreamInlet.ts +233 -25
- package/src/impl/LslStreamOutlet.ts +43 -33
- package/src/impl/LslWebSocketBridge.ts +115 -5
- package/src/index.ts +0 -2
- package/src/scripts/runLslStreamOutlet.ts +2 -3
- package/src/scripts/runOutletToInlet.ts +53 -0
- package/src/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.ts +4 -2
- package/src/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.ts +5 -2
- package/src/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.ts +4 -2
- package/src/testDoubles/Liblsl/FakeLiblsl.ts +61 -1
- package/src/testDoubles/StreamInfo/FakeStreamInfo.ts +1 -1
- package/src/testDoubles/StreamInlet/FakeStreamInlet.ts +36 -5
- package/src/testDoubles/StreamInlet/SpyStreamInlet.ts +7 -2
- package/src/testDoubles/StreamOutlet/FakeStreamOutlet.ts +7 -7
- package/src/testDoubles/StreamTransportBridge/FakeStreamTransportBridge.ts +18 -0
- package/src/testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.ts +9 -0
- package/src/testDoubles/WebSockets/FakeWebSocket.ts +17 -0
- package/src/testDoubles/WebSockets/FakeWebSocketServer.ts +32 -0
- package/src/testDoubles/generateRandomOutletOptions.ts +3 -3
- package/build/types.d.ts +0 -95
- package/build/types.js +0 -2
- package/build/types.js.map +0 -1
- package/src/types.ts +0 -111
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { test, assert } from '@neurodevs/node-tdd'
|
|
2
|
-
import LslStreamInlet
|
|
3
|
-
StreamInletOptions,
|
|
4
|
-
} from '../../impl/LslStreamInlet.js'
|
|
2
|
+
import { StreamInletOptions } from '../../impl/LslStreamInlet.js'
|
|
5
3
|
import FakeStreamInfo from '../../testDoubles/StreamInfo/FakeStreamInfo.js'
|
|
6
4
|
import { SpyStreamInlet } from '../../testDoubles/StreamInlet/SpyStreamInlet.js'
|
|
7
5
|
import AbstractPackageTest from '../AbstractPackageTest.js'
|
|
@@ -9,12 +7,19 @@ import AbstractPackageTest from '../AbstractPackageTest.js'
|
|
|
9
7
|
export default class LslStreamInletTest extends AbstractPackageTest {
|
|
10
8
|
private static instance: SpyStreamInlet
|
|
11
9
|
|
|
10
|
+
private static callsToOnData: {
|
|
11
|
+
samples: Float32Array
|
|
12
|
+
timestamps: Float64Array
|
|
13
|
+
}[]
|
|
14
|
+
|
|
12
15
|
protected static async beforeEach() {
|
|
13
16
|
await super.beforeEach()
|
|
14
17
|
|
|
15
18
|
this.setSpyStreamInlet()
|
|
16
19
|
this.setFakeStreamInfo()
|
|
17
20
|
|
|
21
|
+
this.callsToOnData = []
|
|
22
|
+
|
|
18
23
|
this.instance = this.LslStreamInlet()
|
|
19
24
|
}
|
|
20
25
|
|
|
@@ -30,7 +35,7 @@ export default class LslStreamInletTest extends AbstractPackageTest {
|
|
|
30
35
|
{
|
|
31
36
|
channelNames: this.channelNames,
|
|
32
37
|
channelFormat: 'float32',
|
|
33
|
-
|
|
38
|
+
sampleRateHz: 0,
|
|
34
39
|
name: this.name_,
|
|
35
40
|
type: this.type,
|
|
36
41
|
sourceId: this.sourceId,
|
|
@@ -61,35 +66,263 @@ export default class LslStreamInletTest extends AbstractPackageTest {
|
|
|
61
66
|
{
|
|
62
67
|
info: fakeInfo.boundStreamInfo,
|
|
63
68
|
chunkSize: this.chunkSize,
|
|
64
|
-
|
|
69
|
+
maxBufferedMs: this.maxBufferedMs / 1000,
|
|
65
70
|
},
|
|
66
71
|
'Should have called createInlet!'
|
|
67
72
|
)
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
@test()
|
|
71
|
-
protected static async
|
|
72
|
-
this.instance.
|
|
76
|
+
protected static async flushQueueCallsLslBinding() {
|
|
77
|
+
this.instance.flushQueue()
|
|
73
78
|
|
|
74
79
|
assert.isEqualDeep(
|
|
75
80
|
this.fakeLiblsl.lastFlushInletOptions,
|
|
76
|
-
{ inlet: this.
|
|
81
|
+
{ inlet: this.boundInlet },
|
|
77
82
|
'Should have called flushInlet!'
|
|
78
83
|
)
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
@test()
|
|
87
|
+
protected static async destroyCallsLslBinding() {
|
|
88
|
+
this.instance.destroy()
|
|
89
|
+
|
|
90
|
+
assert.isEqualDeep(
|
|
91
|
+
this.fakeLiblsl.lastDestroyInletOptions,
|
|
92
|
+
{ inlet: this.boundInlet },
|
|
93
|
+
'Should have called destroyInlet!'
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@test()
|
|
98
|
+
protected static async exposesIsRunningFieldThatIsFalseAtFirst() {
|
|
99
|
+
assert.isFalse(this.isRunning, 'isRunning should be false at first!')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@test()
|
|
103
|
+
protected static async startPullingSetsIsRunningToTrue() {
|
|
104
|
+
this.startPulling()
|
|
105
|
+
await this.wait(10)
|
|
106
|
+
|
|
107
|
+
assert.isTrue(this.isRunning, 'isRunning should be true!')
|
|
108
|
+
|
|
109
|
+
this.stopPulling()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@test()
|
|
113
|
+
protected static async stopPullingSetsIsRunningToFalse() {
|
|
114
|
+
await this.startThenStop()
|
|
115
|
+
|
|
116
|
+
assert.isFalse(this.isRunning, 'isRunning should be false!')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@test()
|
|
120
|
+
protected static async callsOnDataForChunkSizeNotOne() {
|
|
121
|
+
await this.startThenStop()
|
|
122
|
+
|
|
123
|
+
assert.isEqual(
|
|
124
|
+
this.callsToOnData.length,
|
|
125
|
+
2,
|
|
126
|
+
'Did not call onData as expected!'
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@test()
|
|
131
|
+
protected static async callsOnDataForChunkSizeOne() {
|
|
132
|
+
await this.runChunkSizeOne()
|
|
133
|
+
|
|
134
|
+
assert.isEqual(
|
|
135
|
+
this.callsToOnData.length,
|
|
136
|
+
2,
|
|
137
|
+
'Did not call onData as expected!'
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@test()
|
|
142
|
+
protected static async callsPullSampleIfChunkSizeIsOne() {
|
|
143
|
+
const inlet = await this.runChunkSizeOne()
|
|
144
|
+
|
|
145
|
+
assert.isEqualDeep(
|
|
146
|
+
this.fakeLiblsl.lastPullSampleOptions,
|
|
147
|
+
{
|
|
148
|
+
inlet: this.boundInlet,
|
|
149
|
+
dataBufferPtr: inlet['dataBufferPtr'],
|
|
150
|
+
dataBufferElements: this.channelCount,
|
|
151
|
+
timeout: 0,
|
|
152
|
+
errcodePtr: inlet['errorCodeBufferPtr'],
|
|
153
|
+
},
|
|
154
|
+
'Should have called pullSample!'
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@test()
|
|
159
|
+
protected static async callsPullChunkIfChunkSizeIsNotOne() {
|
|
160
|
+
await this.startThenStop()
|
|
161
|
+
|
|
162
|
+
assert.isEqualDeep(
|
|
163
|
+
this.fakeLiblsl.lastPullChunkOptions,
|
|
164
|
+
{
|
|
165
|
+
inlet: this.boundInlet,
|
|
166
|
+
dataBufferPtr: this.instance['dataBufferPtr'],
|
|
167
|
+
timestampBufferPtr: this.instance['timestampBufferPtr'],
|
|
168
|
+
dataBufferElements: this.chunkSize * this.channelCount,
|
|
169
|
+
timestampBufferElements: this.chunkSize,
|
|
170
|
+
timeout: 0,
|
|
171
|
+
errcodePtr: this.instance['errorCodeBufferPtr'],
|
|
172
|
+
},
|
|
173
|
+
'Should have called pullChunk!'
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@test()
|
|
178
|
+
protected static async passesTimeoutMsOptionToPullSample() {
|
|
179
|
+
const timeoutMs = 1000 * Math.random()
|
|
180
|
+
|
|
181
|
+
await this.runInletWithOptions({
|
|
182
|
+
chunkSize: 1,
|
|
183
|
+
timeoutMs,
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
assert.isEqual(
|
|
187
|
+
this.fakeLiblsl.lastPullSampleOptions?.timeout,
|
|
188
|
+
timeoutMs / 1000,
|
|
189
|
+
'Did not pass timeoutMs to pullSample!'
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@test()
|
|
194
|
+
protected static async passesTimeoutMsOptionToPullChunk() {
|
|
195
|
+
const timeoutMs = 1000 * Math.random()
|
|
196
|
+
|
|
197
|
+
await this.runInletWithOptions({
|
|
89
198
|
chunkSize: this.chunkSize,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
199
|
+
timeoutMs,
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
assert.isEqual(
|
|
203
|
+
this.fakeLiblsl.lastPullChunkOptions?.timeout,
|
|
204
|
+
timeoutMs / 1000,
|
|
205
|
+
'Did not pass timeoutMs to pullChunk!'
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@test()
|
|
210
|
+
protected static async defaultsMaxBufferedMsToSixMinutesInSeconds() {
|
|
211
|
+
await this.runInletWithOptions({
|
|
212
|
+
maxBufferedMs: undefined,
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const sixMinutesInSeconds = 360
|
|
216
|
+
|
|
217
|
+
assert.isEqualDeep(
|
|
218
|
+
this.fakeLiblsl.lastCreateInletOptions?.maxBufferedMs,
|
|
219
|
+
sixMinutesInSeconds
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@test()
|
|
224
|
+
protected static async throwsWithUnknownErrorCode() {
|
|
225
|
+
await this.assertThrowsWithErrorCode(
|
|
226
|
+
-999,
|
|
227
|
+
`An unknown liblsl error has occurred!`
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@test()
|
|
232
|
+
protected static async throwsWithErrorCodeNegativeOne() {
|
|
233
|
+
await this.assertThrowsWithErrorCode(
|
|
234
|
+
-1,
|
|
235
|
+
`The liblsl operation failed due to a timeout!`
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@test()
|
|
240
|
+
protected static async throwsWithErrorCodeNegativeTwo() {
|
|
241
|
+
await this.assertThrowsWithErrorCode(
|
|
242
|
+
-2,
|
|
243
|
+
`The liblsl stream has been lost!`
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@test()
|
|
248
|
+
protected static async throwsWithErrorCodeNegativeThree() {
|
|
249
|
+
await this.assertThrowsWithErrorCode(
|
|
250
|
+
-3,
|
|
251
|
+
'A liblsl argument was incorrectly specified!'
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@test()
|
|
256
|
+
protected static async throwsWithErrorCodeNegativeFour() {
|
|
257
|
+
await this.assertThrowsWithErrorCode(
|
|
258
|
+
-4,
|
|
259
|
+
'An internal liblsl error has occurred!'
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private static async assertThrowsWithErrorCode(
|
|
264
|
+
errorCode: number,
|
|
265
|
+
message: string
|
|
266
|
+
) {
|
|
267
|
+
await this.startThenStop()
|
|
268
|
+
|
|
269
|
+
this.instance['errorCodeBuffer'].writeInt32LE(errorCode)
|
|
270
|
+
|
|
271
|
+
assert.doesThrow(() => {
|
|
272
|
+
this.instance['handleErrorCodeIfPresent']()
|
|
273
|
+
}, message)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private static async runChunkSizeOne() {
|
|
277
|
+
return await this.runInletWithOptions({
|
|
278
|
+
chunkSize: 1,
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private static async runInletWithOptions(
|
|
283
|
+
options?: Partial<StreamInletOptions>
|
|
284
|
+
) {
|
|
285
|
+
const inlet = this.LslStreamInlet(options)
|
|
286
|
+
await this.startThenStop(inlet)
|
|
287
|
+
return inlet
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private static async startThenStop(inlet?: SpyStreamInlet) {
|
|
291
|
+
const instance = inlet || this.instance
|
|
292
|
+
instance.startPulling()
|
|
293
|
+
await this.wait(10)
|
|
294
|
+
instance.stopPulling()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private static startPulling() {
|
|
298
|
+
this.instance.startPulling()
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private static stopPulling() {
|
|
302
|
+
this.instance.stopPulling()
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private static get isRunning() {
|
|
306
|
+
return this.instance.isRunning
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private static get boundInlet() {
|
|
310
|
+
return this.instance.getBoundInlet()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private static onData = (
|
|
314
|
+
samples: Float32Array,
|
|
315
|
+
timestamps: Float64Array
|
|
316
|
+
) => {
|
|
317
|
+
this.callsToOnData.push({ samples, timestamps })
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
protected static LslStreamInlet(options?: Partial<StreamInletOptions>) {
|
|
321
|
+
return AbstractPackageTest.LslStreamInlet(
|
|
322
|
+
{
|
|
323
|
+
...options,
|
|
324
|
+
},
|
|
325
|
+
this.onData
|
|
326
|
+
) as SpyStreamInlet
|
|
94
327
|
}
|
|
95
328
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { test, assert } from '@neurodevs/node-tdd'
|
|
2
2
|
|
|
3
|
+
import { ChannelFormat, LslSample } from 'impl/LiblslAdapter.js'
|
|
3
4
|
import { CHANNEL_FORMATS } from '../../consts.js'
|
|
4
5
|
import { StreamInfo } from '../../impl/LslStreamInfo.js'
|
|
5
6
|
import LslStreamOutlet, {
|
|
@@ -11,8 +12,8 @@ import {
|
|
|
11
12
|
TestChannelFormat,
|
|
12
13
|
} from '../../testDoubles/consts.js'
|
|
13
14
|
import generateRandomOutletOptions from '../../testDoubles/generateRandomOutletOptions.js'
|
|
15
|
+
import FakeLiblsl from '../../testDoubles/Liblsl/FakeLiblsl.js'
|
|
14
16
|
import FakeStreamInfo from '../../testDoubles/StreamInfo/FakeStreamInfo.js'
|
|
15
|
-
import { ChannelFormat, LslSample } from '../../types.js'
|
|
16
17
|
import AbstractPackageTest from '../AbstractPackageTest.js'
|
|
17
18
|
|
|
18
19
|
export default class LslStreamOutletTest extends AbstractPackageTest {
|
|
@@ -35,14 +36,14 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
@test()
|
|
38
|
-
protected static async
|
|
39
|
-
await this.LslStreamOutlet({
|
|
39
|
+
protected static async allowsZeroSampleRateHz() {
|
|
40
|
+
await this.LslStreamOutlet({ sampleRateHz: 0 })
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
@test()
|
|
43
|
-
protected static async
|
|
44
|
-
await this.
|
|
45
|
-
await this.
|
|
44
|
+
protected static async throwsWithInvalidSampleRateHz() {
|
|
45
|
+
await this.assertThrowsInvalidSampleRateHz(-1)
|
|
46
|
+
await this.assertThrowsInvalidSampleRateHz(-1.5)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
@test()
|
|
@@ -65,15 +66,15 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
@test()
|
|
68
|
-
protected static async
|
|
69
|
-
await this.LslStreamOutlet({
|
|
69
|
+
protected static async allowsZeroMaxBufferedMs() {
|
|
70
|
+
await this.LslStreamOutlet({ maxBufferedMs: 0 })
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
@test()
|
|
73
|
-
protected static async
|
|
74
|
-
await this.
|
|
75
|
-
await this.
|
|
76
|
-
await this.
|
|
74
|
+
protected static async throwsWithInvalidMaxBufferedMs() {
|
|
75
|
+
await this.assertThrowsInvalidMaxBufferedMs(-1)
|
|
76
|
+
await this.assertThrowsInvalidMaxBufferedMs(-1.5)
|
|
77
|
+
await this.assertThrowsInvalidMaxBufferedMs(1.5)
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
@test()
|
|
@@ -115,7 +116,7 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
|
|
|
115
116
|
assert.isEqualDeep(this.fakeLiblsl.lastCreateOutletOptions, {
|
|
116
117
|
info: this.fakeLiblsl.streamInfo,
|
|
117
118
|
chunkSize: this.randomOutletOptions.chunkSize,
|
|
118
|
-
|
|
119
|
+
maxBufferedMs: this.randomOutletOptions.maxBufferedMs,
|
|
119
120
|
})
|
|
120
121
|
assert.isNumber(
|
|
121
122
|
this.fakeLiblsl.lastPushSampleFloatTimestampOptions?.timestamp
|
|
@@ -168,11 +169,11 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
|
|
|
168
169
|
assert.isEqualDeep(FakeStreamInfo.callsToConstructor[0], {
|
|
169
170
|
channelNames: this.randomOutletOptions.channelNames,
|
|
170
171
|
channelFormat: this.randomOutletOptions.channelFormat,
|
|
171
|
-
|
|
172
|
+
sampleRateHz: this.randomOutletOptions.sampleRateHz,
|
|
172
173
|
name: this.randomOutletOptions.name,
|
|
173
174
|
type: this.randomOutletOptions.type,
|
|
174
175
|
sourceId: this.randomOutletOptions.sourceId,
|
|
175
|
-
units: this.randomOutletOptions.
|
|
176
|
+
units: this.randomOutletOptions.units,
|
|
176
177
|
})
|
|
177
178
|
}
|
|
178
179
|
|
|
@@ -208,7 +209,91 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
|
|
|
208
209
|
await this.LslStreamOutlet({ waitAfterConstructionMs: 10 })
|
|
209
210
|
const endMs = Date.now()
|
|
210
211
|
|
|
211
|
-
assert.isAbove(endMs - startMs,
|
|
212
|
+
assert.isAbove(endMs - startMs, 8)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@test()
|
|
216
|
+
protected static async defaultsMaxBufferedMsToSixMinutesInSeconds() {
|
|
217
|
+
const outlet = await this.LslStreamOutlet({ maxBufferedMs: undefined })
|
|
218
|
+
|
|
219
|
+
assert.isEqualDeep(
|
|
220
|
+
outlet['maxBufferedMs'],
|
|
221
|
+
360 * 1000,
|
|
222
|
+
'Did not set maxBufferedMs as 6 minutes!'
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@test()
|
|
227
|
+
protected static async defaultsManufacturerToNA() {
|
|
228
|
+
const outlet = await this.LslStreamOutlet({ manufacturer: undefined })
|
|
229
|
+
|
|
230
|
+
assert.isEqualDeep(
|
|
231
|
+
outlet['manufacturer'],
|
|
232
|
+
'N/A',
|
|
233
|
+
'Did not set manufacturer as N/A!'
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@test()
|
|
238
|
+
protected static async defaultsUnitsToNA() {
|
|
239
|
+
const outlet = await this.LslStreamOutlet({ units: undefined })
|
|
240
|
+
assert.isEqualDeep(outlet['units'], 'N/A', 'Did not set units as N/A!')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@test()
|
|
244
|
+
protected static async throwsWithUnknownErrorCode() {
|
|
245
|
+
await this.assertThrowsWithErrorCode(
|
|
246
|
+
-999,
|
|
247
|
+
`An unknown liblsl error has occurred!`
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@test()
|
|
252
|
+
protected static async throwsWithErrorCodeNegativeOne() {
|
|
253
|
+
await this.assertThrowsWithErrorCode(
|
|
254
|
+
-1,
|
|
255
|
+
`The liblsl operation failed due to a timeout!`
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@test()
|
|
260
|
+
protected static async throwsWithErrorCodeNegativeTwo() {
|
|
261
|
+
await this.assertThrowsWithErrorCode(
|
|
262
|
+
-2,
|
|
263
|
+
`The liblsl stream has been lost!`
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@test()
|
|
268
|
+
protected static async throwsWithErrorCodeNegativeThree() {
|
|
269
|
+
await this.assertThrowsWithErrorCode(
|
|
270
|
+
-3,
|
|
271
|
+
'A liblsl argument was incorrectly specified!'
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@test()
|
|
276
|
+
protected static async throwsWithErrorCodeNegativeFour() {
|
|
277
|
+
await this.assertThrowsWithErrorCode(
|
|
278
|
+
-4,
|
|
279
|
+
'An internal liblsl error has occurred!'
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private static async assertThrowsWithErrorCode(
|
|
284
|
+
errorCode: number,
|
|
285
|
+
message: string
|
|
286
|
+
) {
|
|
287
|
+
FakeLiblsl.fakeErrorCode = errorCode
|
|
288
|
+
|
|
289
|
+
const outlet =
|
|
290
|
+
Math.random() > 0.5
|
|
291
|
+
? await this.FloatOutlet()
|
|
292
|
+
: await this.StringOutlet()
|
|
293
|
+
|
|
294
|
+
assert.doesThrow(() => {
|
|
295
|
+
outlet.pushSample([1.0])
|
|
296
|
+
}, message)
|
|
212
297
|
}
|
|
213
298
|
|
|
214
299
|
private static async assertThrowsWithEmptyChannelNames() {
|
|
@@ -219,11 +304,11 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
|
|
|
219
304
|
)
|
|
220
305
|
}
|
|
221
306
|
|
|
222
|
-
private static async
|
|
307
|
+
private static async assertThrowsInvalidSampleRateHz(sampleRateHz: number) {
|
|
223
308
|
await this.createAndAssertThrows(
|
|
224
|
-
'
|
|
225
|
-
|
|
226
|
-
`Invalid sample rate! Must be a positive number or zero, not: ${
|
|
309
|
+
'sampleRateHz',
|
|
310
|
+
sampleRateHz,
|
|
311
|
+
`Invalid sample rate! Must be a positive number or zero, not: ${sampleRateHz}`
|
|
227
312
|
)
|
|
228
313
|
}
|
|
229
314
|
|
|
@@ -237,11 +322,13 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
|
|
|
237
322
|
)
|
|
238
323
|
}
|
|
239
324
|
|
|
240
|
-
private static async
|
|
325
|
+
private static async assertThrowsInvalidMaxBufferedMs(
|
|
326
|
+
maxBufferedMs: number
|
|
327
|
+
) {
|
|
241
328
|
await this.createAndAssertThrows(
|
|
242
|
-
'
|
|
243
|
-
|
|
244
|
-
`Invalid max buffered! Must be a positive integer or zero, not: ${
|
|
329
|
+
'maxBufferedMs',
|
|
330
|
+
maxBufferedMs,
|
|
331
|
+
`Invalid max buffered! Must be a positive integer or zero, not: ${maxBufferedMs}`
|
|
245
332
|
)
|
|
246
333
|
}
|
|
247
334
|
|
|
@@ -1,17 +1,27 @@
|
|
|
1
|
+
import { randomInt } from 'crypto'
|
|
2
|
+
|
|
1
3
|
import { test, assert } from '@neurodevs/node-tdd'
|
|
2
4
|
|
|
3
5
|
import LslWebSocketBridge, {
|
|
4
|
-
|
|
6
|
+
StreamTransportBridgeOptions,
|
|
5
7
|
} from '../../impl/LslWebSocketBridge.js'
|
|
8
|
+
import FakeStreamInlet from '../../testDoubles/StreamInlet/FakeStreamInlet.js'
|
|
9
|
+
import SpyLslWebSocketBridge from '../../testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.js'
|
|
10
|
+
import FakeWebSocket from '../../testDoubles/WebSockets/FakeWebSocket.js'
|
|
11
|
+
import FakeWebSocketServer from '../../testDoubles/WebSockets/FakeWebSocketServer.js'
|
|
6
12
|
import AbstractPackageTest from '../AbstractPackageTest.js'
|
|
7
13
|
|
|
8
14
|
export default class LslWebSocketBridgeTest extends AbstractPackageTest {
|
|
9
|
-
private static instance:
|
|
15
|
+
private static instance: SpyLslWebSocketBridge
|
|
10
16
|
|
|
11
17
|
protected static async beforeEach() {
|
|
12
18
|
await super.beforeEach()
|
|
13
19
|
|
|
14
|
-
this.
|
|
20
|
+
this.setFakeStreamInlet()
|
|
21
|
+
this.setFakeWebSocketServer()
|
|
22
|
+
this.setSpyLslWebSocketBridge()
|
|
23
|
+
|
|
24
|
+
this.instance = this.LslWebSocketBridge() as SpyLslWebSocketBridge
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
@test()
|
|
@@ -19,7 +29,155 @@ export default class LslWebSocketBridgeTest extends AbstractPackageTest {
|
|
|
19
29
|
assert.isTruthy(this.instance, 'Failed to create instance!')
|
|
20
30
|
}
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
@test()
|
|
33
|
+
protected static async createsLslStreamInlet() {
|
|
34
|
+
const { channelNames, channelFormat, sampleRateHz, chunkSize } =
|
|
35
|
+
FakeStreamInlet.callsToConstructor[0]?.options ?? {}
|
|
36
|
+
|
|
37
|
+
assert.isEqualDeep(
|
|
38
|
+
{
|
|
39
|
+
channelNames,
|
|
40
|
+
channelFormat,
|
|
41
|
+
sampleRateHz,
|
|
42
|
+
chunkSize,
|
|
43
|
+
},
|
|
44
|
+
this.baseOptions
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@test()
|
|
49
|
+
protected static async activateCallsStartPullingOnInlet() {
|
|
50
|
+
this.activate()
|
|
51
|
+
|
|
52
|
+
assert.isEqual(
|
|
53
|
+
FakeStreamInlet.numCallsToStartPulling,
|
|
54
|
+
1,
|
|
55
|
+
'Did not call startPulling on inlet!'
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@test()
|
|
60
|
+
protected static async deactivateCallsStopPullingOnInlet() {
|
|
61
|
+
this.activate()
|
|
62
|
+
this.deactivate()
|
|
63
|
+
|
|
64
|
+
assert.isEqual(
|
|
65
|
+
FakeStreamInlet.numCallsToStopPulling,
|
|
66
|
+
1,
|
|
67
|
+
'Did not call stopPulling on inlet!'
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@test()
|
|
72
|
+
protected static async destroyCallsDestroyOnInlet() {
|
|
73
|
+
this.destroy()
|
|
74
|
+
|
|
75
|
+
assert.isEqual(
|
|
76
|
+
FakeStreamInlet.numCallsToDestroy,
|
|
77
|
+
1,
|
|
78
|
+
'Did not call destroy on inlet!'
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@test()
|
|
83
|
+
protected static async createsWebSocketServer() {
|
|
84
|
+
assert.isEqualDeep(
|
|
85
|
+
FakeWebSocketServer.callsToConstructor[0],
|
|
86
|
+
{ port: this.wssPort },
|
|
87
|
+
'Did not create WebSocketServer!'
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@test()
|
|
92
|
+
protected static async activateSendsDataToWebSocketClients() {
|
|
93
|
+
this.activate()
|
|
94
|
+
|
|
95
|
+
const { samples, timestamps } = this.simulateOnDataCallback()
|
|
96
|
+
|
|
97
|
+
for (const client of FakeWebSocketServer.clients) {
|
|
98
|
+
const callToClient = FakeWebSocket.callsToSend.filter(
|
|
99
|
+
(c) => c.id === client.id
|
|
100
|
+
)[0]
|
|
101
|
+
|
|
102
|
+
assert.isEqualDeep(
|
|
103
|
+
callToClient?.data,
|
|
104
|
+
JSON.stringify({
|
|
105
|
+
samples,
|
|
106
|
+
timestamps,
|
|
107
|
+
}),
|
|
108
|
+
'Did not send data to client!'
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@test()
|
|
114
|
+
protected static async doesNotSendToClientsThatAreNotReady() {
|
|
115
|
+
for (const client of FakeWebSocketServer.clients) {
|
|
116
|
+
client.readyState = WebSocket.CONNECTING
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.activate()
|
|
120
|
+
this.simulateOnDataCallback()
|
|
121
|
+
|
|
122
|
+
assert.isEqual(
|
|
123
|
+
FakeWebSocket.callsToSend.length,
|
|
124
|
+
0,
|
|
125
|
+
'Sent data to clients that are not ready!'
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@test()
|
|
130
|
+
protected static async destroyClosesWebSocketServer() {
|
|
131
|
+
this.destroy()
|
|
132
|
+
|
|
133
|
+
assert.isEqual(
|
|
134
|
+
FakeWebSocketServer.numCallsToClose,
|
|
135
|
+
1,
|
|
136
|
+
'Did not call close on WebSocketServer!'
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@test()
|
|
141
|
+
protected static async throwsIfActivateIsCalledAfterDestroy() {
|
|
142
|
+
this.destroy()
|
|
143
|
+
|
|
144
|
+
assert.doesThrow(() => {
|
|
145
|
+
this.activate()
|
|
146
|
+
}, `\n\n Cannot re-activate bridge after destroying it! \n\n Please create and activate a new instance. \n`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private static activate() {
|
|
150
|
+
this.instance.activate()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private static deactivate() {
|
|
154
|
+
this.instance.deactivate()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private static destroy() {
|
|
158
|
+
this.instance.destroy()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private static simulateOnDataCallback() {
|
|
162
|
+
const samples = new Float32Array([1, 2, 3, 4, 5, 6])
|
|
163
|
+
const timestamps = new Float64Array([7, 8])
|
|
164
|
+
|
|
165
|
+
FakeStreamInlet.callsToConstructor[0]?.onData!(samples, timestamps)
|
|
166
|
+
return { samples, timestamps }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private static readonly wssPort = 8080
|
|
170
|
+
|
|
171
|
+
private static readonly baseOptions = {
|
|
172
|
+
channelNames: this.channelNames,
|
|
173
|
+
channelFormat: 'float32' as const,
|
|
174
|
+
sampleRateHz: 100 * Math.random(),
|
|
175
|
+
chunkSize: randomInt(1, 100),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private static LslWebSocketBridge(
|
|
179
|
+
options?: Partial<StreamTransportBridgeOptions>
|
|
180
|
+
) {
|
|
181
|
+
return LslWebSocketBridge.Create({ ...this.baseOptions, ...options })
|
|
24
182
|
}
|
|
25
183
|
}
|