@neurodevs/ndx-native 0.0.1
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/.nvmrc +1 -0
- package/.vscode/launch.json +58 -0
- package/.vscode/settings.json +67 -0
- package/.vscode/tasks.json +130 -0
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/build/.spruce/settings.json +11 -0
- package/build/__tests__/AbstractPackageTest.d.ts +7 -0
- package/build/__tests__/AbstractPackageTest.js +14 -0
- package/build/__tests__/AbstractPackageTest.js.map +1 -0
- package/build/__tests__/impl/LabrecorderAdapter.test.d.ts +30 -0
- package/build/__tests__/impl/LabrecorderAdapter.test.js +164 -0
- package/build/__tests__/impl/LabrecorderAdapter.test.js.map +1 -0
- package/build/__tests__/impl/LiblslAdapter.test.d.ts +84 -0
- package/build/__tests__/impl/LiblslAdapter.test.js +750 -0
- package/build/__tests__/impl/LiblslAdapter.test.js.map +1 -0
- package/build/__tests__/impl/LibxdfAdapter.test.d.ts +45 -0
- package/build/__tests__/impl/LibxdfAdapter.test.js +243 -0
- package/build/__tests__/impl/LibxdfAdapter.test.js.map +1 -0
- package/build/consts.d.ts +11 -0
- package/build/consts.js +21 -0
- package/build/consts.js.map +1 -0
- package/build/impl/LabrecorderAdapter.d.ts +30 -0
- package/build/impl/LabrecorderAdapter.js +69 -0
- package/build/impl/LabrecorderAdapter.js.map +1 -0
- package/build/impl/LiblslAdapter.d.ts +172 -0
- package/build/impl/LiblslAdapter.js +360 -0
- package/build/impl/LiblslAdapter.js.map +1 -0
- package/build/impl/LibxdfAdapter.d.ts +92 -0
- package/build/impl/LibxdfAdapter.js +130 -0
- package/build/impl/LibxdfAdapter.js.map +1 -0
- package/build/index.d.ts +14 -0
- package/build/index.js +18 -0
- package/build/index.js.map +1 -0
- package/build/lib/handleError.d.ts +8 -0
- package/build/lib/handleError.js +25 -0
- package/build/lib/handleError.js.map +1 -0
- package/build/testDoubles/Labrecorder/FakeLabrecorder.d.ts +15 -0
- package/build/testDoubles/Labrecorder/FakeLabrecorder.js +26 -0
- package/build/testDoubles/Labrecorder/FakeLabrecorder.js.map +1 -0
- package/build/testDoubles/Liblsl/FakeLiblsl.d.ts +57 -0
- package/build/testDoubles/Liblsl/FakeLiblsl.js +140 -0
- package/build/testDoubles/Liblsl/FakeLiblsl.js.map +1 -0
- package/build/testDoubles/Libxdf/FakeLibxdf.d.ts +13 -0
- package/build/testDoubles/Libxdf/FakeLibxdf.js +25 -0
- package/build/testDoubles/Libxdf/FakeLibxdf.js.map +1 -0
- package/build/testDoubles/Libxdf/SpyLibxdf.d.ts +6 -0
- package/build/testDoubles/Libxdf/SpyLibxdf.js +10 -0
- package/build/testDoubles/Libxdf/SpyLibxdf.js.map +1 -0
- package/eslint.config.mjs +3 -0
- package/package.json +84 -0
- package/src/.spruce/settings.json +11 -0
- package/src/__tests__/AbstractPackageTest.ts +16 -0
- package/src/__tests__/impl/LabrecorderAdapter.test.ts +197 -0
- package/src/__tests__/impl/LiblslAdapter.test.ts +907 -0
- package/src/__tests__/impl/LibxdfAdapter.test.ts +290 -0
- package/src/consts.ts +21 -0
- package/src/impl/LabrecorderAdapter.ts +100 -0
- package/src/impl/LiblslAdapter.ts +616 -0
- package/src/impl/LibxdfAdapter.ts +227 -0
- package/src/index.ts +26 -0
- package/src/lib/handleError.ts +24 -0
- package/src/testDoubles/Labrecorder/FakeLabrecorder.ts +37 -0
- package/src/testDoubles/Liblsl/FakeLiblsl.ts +204 -0
- package/src/testDoubles/Libxdf/FakeLibxdf.ts +33 -0
- package/src/testDoubles/Libxdf/SpyLibxdf.ts +13 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
import { randomInt } from 'crypto'
|
|
2
|
+
import { test, assert } from '@neurodevs/node-tdd'
|
|
3
|
+
import {
|
|
4
|
+
createPointer,
|
|
5
|
+
DataType,
|
|
6
|
+
FieldType,
|
|
7
|
+
FuncObj,
|
|
8
|
+
OpenParams,
|
|
9
|
+
unwrapPointer,
|
|
10
|
+
} from 'ffi-rs'
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
ChildHandle,
|
|
14
|
+
DescriptionHandle,
|
|
15
|
+
InletHandle,
|
|
16
|
+
OutletHandle,
|
|
17
|
+
InfoHandle,
|
|
18
|
+
Liblsl,
|
|
19
|
+
LiblslBindings,
|
|
20
|
+
LslChannel,
|
|
21
|
+
ResolveByPropOptions,
|
|
22
|
+
} from 'impl/LiblslAdapter.js'
|
|
23
|
+
import LiblslAdapter from '../../impl/LiblslAdapter.js'
|
|
24
|
+
import FakeLiblsl from '../../testDoubles/Liblsl/FakeLiblsl.js'
|
|
25
|
+
import AbstractPackageTest from '../AbstractPackageTest.js'
|
|
26
|
+
|
|
27
|
+
export default class LiblslAdapterTest extends AbstractPackageTest {
|
|
28
|
+
private static instance: Liblsl
|
|
29
|
+
private static libraryPath?: string
|
|
30
|
+
private static libraryOptions?: Record<string, any>
|
|
31
|
+
|
|
32
|
+
private static fakeBindings: LiblslBindings
|
|
33
|
+
private static shouldThrowWhenCreatingBindings: boolean
|
|
34
|
+
|
|
35
|
+
private static fakeInfoHandle: InfoHandle
|
|
36
|
+
private static fakeOutletHandle: OutletHandle
|
|
37
|
+
private static fakeInletHandle: InletHandle = {}
|
|
38
|
+
private static fakeDescHandle: DescriptionHandle
|
|
39
|
+
private static fakeChildHandle: ChildHandle
|
|
40
|
+
private static fakeChildNamedChannel: ChildHandle
|
|
41
|
+
|
|
42
|
+
private static createStreamInfoParams?: any[]
|
|
43
|
+
private static destroyStreamInfoParams?: any[]
|
|
44
|
+
private static appendChildParams: any[] = []
|
|
45
|
+
private static appendChildValueParams: any[]
|
|
46
|
+
private static appendChildHitCount: number
|
|
47
|
+
private static getDescriptionParams?: [InfoHandle]
|
|
48
|
+
private static getChannelCountParams?: [InfoHandle]
|
|
49
|
+
|
|
50
|
+
private static createOutletParams?: any[]
|
|
51
|
+
private static pushSampleFloatTimestampParams?: any[]
|
|
52
|
+
private static pushSampleStringTimestampParams?: any[]
|
|
53
|
+
private static destroyOutletParams?: any[]
|
|
54
|
+
|
|
55
|
+
private static createInletParams?: any[]
|
|
56
|
+
private static flushInletParams?: any[]
|
|
57
|
+
private static destroyInletParams?: any[]
|
|
58
|
+
|
|
59
|
+
private static localClockParams?: any[]
|
|
60
|
+
|
|
61
|
+
private static ffiRsOpenOptions?: OpenParams
|
|
62
|
+
private static ffiRsDefineOptions: FfiRsDefineOptions
|
|
63
|
+
private static ffiRsLoadOptions?: Record<string, any>
|
|
64
|
+
|
|
65
|
+
protected static async beforeEach() {
|
|
66
|
+
await super.beforeEach()
|
|
67
|
+
|
|
68
|
+
delete this.libraryPath
|
|
69
|
+
delete this.libraryOptions
|
|
70
|
+
delete this.createStreamInfoParams
|
|
71
|
+
delete this.createOutletParams
|
|
72
|
+
delete this.destroyOutletParams
|
|
73
|
+
delete this.createInletParams
|
|
74
|
+
delete this.destroyInletParams
|
|
75
|
+
delete this.pushSampleFloatTimestampParams
|
|
76
|
+
delete this.getDescriptionParams
|
|
77
|
+
delete this.getChannelCountParams
|
|
78
|
+
|
|
79
|
+
this.appendChildParams = []
|
|
80
|
+
this.appendChildValueParams = []
|
|
81
|
+
|
|
82
|
+
process.env.LIBLSL_PATH = this.generateId()
|
|
83
|
+
|
|
84
|
+
this.fakeInfoHandle = {}
|
|
85
|
+
this.fakeDescHandle = {}
|
|
86
|
+
this.fakeOutletHandle = {}
|
|
87
|
+
this.fakeChildHandle = {}
|
|
88
|
+
this.fakeChildNamedChannel = {}
|
|
89
|
+
this.fakeBindings = this.FakeBindings()
|
|
90
|
+
|
|
91
|
+
this.shouldThrowWhenCreatingBindings = false
|
|
92
|
+
|
|
93
|
+
this.appendChildHitCount = 0
|
|
94
|
+
|
|
95
|
+
delete this.ffiRsOpenOptions
|
|
96
|
+
LiblslAdapter.open = (options) => {
|
|
97
|
+
this.ffiRsOpenOptions = options
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
LiblslAdapter.define = (options) => {
|
|
101
|
+
this.ffiRsDefineOptions = options as FfiRsDefineOptions
|
|
102
|
+
if (this.shouldThrowWhenCreatingBindings) {
|
|
103
|
+
throw new Error('Failed to create bindings!')
|
|
104
|
+
}
|
|
105
|
+
return this.fakeBindings as any
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
LiblslAdapter.load = (options) => {
|
|
109
|
+
this.ffiRsLoadOptions = options
|
|
110
|
+
return this.fakeNumResolveResults as any
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
LiblslAdapter.resetInstance()
|
|
114
|
+
this.instance = LiblslAdapter.getInstance()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@test()
|
|
118
|
+
protected static async createsInstance() {
|
|
119
|
+
assert.isTruthy(this.instance, 'Failed to create instance!')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@test()
|
|
123
|
+
protected static async throwsWhenBindingsFailToLoad() {
|
|
124
|
+
this.shouldThrowWhenCreatingBindings = true
|
|
125
|
+
LiblslAdapter.resetInstance()
|
|
126
|
+
|
|
127
|
+
const err = assert.doesThrow(() => LiblslAdapter.getInstance())
|
|
128
|
+
|
|
129
|
+
assert.isTrue(
|
|
130
|
+
err.message.includes(this.generateFailedMessage()),
|
|
131
|
+
'Did not receive the expected error!'
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@test()
|
|
136
|
+
protected static async callsOpenOnFfiRs() {
|
|
137
|
+
assert.isEqualDeep(this.ffiRsOpenOptions, {
|
|
138
|
+
library: 'lsl',
|
|
139
|
+
path: process.env.LIBLSL_PATH,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@test()
|
|
144
|
+
protected static async worksAsASingleton() {
|
|
145
|
+
const liblsl = LiblslAdapter.getInstance()
|
|
146
|
+
//@ts-ignore
|
|
147
|
+
assert.isInstanceOf(liblsl, LiblslAdapter)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@test()
|
|
151
|
+
protected static async singletonIsTheSame() {
|
|
152
|
+
assert.isEqual(LiblslAdapter.getInstance(), LiblslAdapter.getInstance())
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@test()
|
|
156
|
+
protected static canSetInstance() {
|
|
157
|
+
const fake = new FakeLiblsl()
|
|
158
|
+
LiblslAdapter.setInstance(fake)
|
|
159
|
+
assert.isEqual(LiblslAdapter.getInstance(), fake)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@test()
|
|
163
|
+
protected static async createsExpectedBindingsWithFfiRs() {
|
|
164
|
+
assert.isEqualDeep(this.ffiRsDefineOptions, {
|
|
165
|
+
lsl_create_streaminfo: {
|
|
166
|
+
library: 'lsl',
|
|
167
|
+
retType: DataType.External,
|
|
168
|
+
paramsType: [
|
|
169
|
+
DataType.String,
|
|
170
|
+
DataType.String,
|
|
171
|
+
DataType.I32,
|
|
172
|
+
DataType.Double,
|
|
173
|
+
DataType.I32,
|
|
174
|
+
DataType.String,
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
lsl_destroy_streaminfo: {
|
|
178
|
+
library: 'lsl',
|
|
179
|
+
retType: DataType.Void,
|
|
180
|
+
paramsType: [DataType.External],
|
|
181
|
+
},
|
|
182
|
+
lsl_create_outlet: {
|
|
183
|
+
library: 'lsl',
|
|
184
|
+
retType: DataType.External,
|
|
185
|
+
paramsType: [DataType.External, DataType.I32, DataType.I32],
|
|
186
|
+
},
|
|
187
|
+
lsl_push_sample_ft: {
|
|
188
|
+
library: 'lsl',
|
|
189
|
+
retType: DataType.I32,
|
|
190
|
+
paramsType: [
|
|
191
|
+
DataType.External,
|
|
192
|
+
DataType.FloatArray,
|
|
193
|
+
DataType.Double,
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
lsl_push_sample_strt: {
|
|
197
|
+
library: 'lsl',
|
|
198
|
+
retType: DataType.I32,
|
|
199
|
+
paramsType: [
|
|
200
|
+
DataType.External,
|
|
201
|
+
DataType.StringArray,
|
|
202
|
+
DataType.Double,
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
lsl_destroy_outlet: {
|
|
206
|
+
library: 'lsl',
|
|
207
|
+
retType: DataType.Void,
|
|
208
|
+
paramsType: [DataType.External],
|
|
209
|
+
},
|
|
210
|
+
lsl_create_inlet: {
|
|
211
|
+
library: 'lsl',
|
|
212
|
+
retType: DataType.External,
|
|
213
|
+
paramsType: [
|
|
214
|
+
DataType.External,
|
|
215
|
+
DataType.I32,
|
|
216
|
+
DataType.I32,
|
|
217
|
+
DataType.I32,
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
lsl_inlet_flush: {
|
|
221
|
+
library: 'lsl',
|
|
222
|
+
retType: DataType.I32,
|
|
223
|
+
paramsType: [DataType.External],
|
|
224
|
+
},
|
|
225
|
+
lsl_destroy_inlet: {
|
|
226
|
+
library: 'lsl',
|
|
227
|
+
retType: DataType.Void,
|
|
228
|
+
paramsType: [DataType.External],
|
|
229
|
+
},
|
|
230
|
+
lsl_get_desc: {
|
|
231
|
+
library: 'lsl',
|
|
232
|
+
retType: DataType.External,
|
|
233
|
+
paramsType: [DataType.External],
|
|
234
|
+
},
|
|
235
|
+
lsl_get_channel_count: {
|
|
236
|
+
library: 'lsl',
|
|
237
|
+
retType: DataType.I32,
|
|
238
|
+
paramsType: [DataType.External],
|
|
239
|
+
},
|
|
240
|
+
lsl_append_child: {
|
|
241
|
+
library: 'lsl',
|
|
242
|
+
retType: DataType.External,
|
|
243
|
+
paramsType: [DataType.External, DataType.String],
|
|
244
|
+
},
|
|
245
|
+
lsl_append_child_value: {
|
|
246
|
+
library: 'lsl',
|
|
247
|
+
retType: DataType.External,
|
|
248
|
+
paramsType: [
|
|
249
|
+
DataType.External,
|
|
250
|
+
DataType.String,
|
|
251
|
+
DataType.String,
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
lsl_local_clock: {
|
|
255
|
+
library: 'lsl',
|
|
256
|
+
retType: DataType.Double,
|
|
257
|
+
paramsType: [],
|
|
258
|
+
},
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@test()
|
|
263
|
+
protected static async createsStreamInfoWithRequiredParams() {
|
|
264
|
+
const options = this.generateRandomCreateStreamInfoOptions()
|
|
265
|
+
const actual = this.instance.createStreamInfo(options)
|
|
266
|
+
|
|
267
|
+
assert.isEqual(actual, this.fakeInfoHandle)
|
|
268
|
+
assert.isEqualDeep(this.createStreamInfoParams, Object.values(options))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@test()
|
|
272
|
+
protected static async destroyStreamInfoPasses() {
|
|
273
|
+
const infoHandle = this.createRandomInfoHandle()
|
|
274
|
+
this.instance.destroyStreamInfo({ infoHandle })
|
|
275
|
+
|
|
276
|
+
assert.isEqualDeep(
|
|
277
|
+
this.destroyStreamInfoParams,
|
|
278
|
+
[infoHandle],
|
|
279
|
+
'Did not call destroyStreamInfo with expected params!'
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@test()
|
|
284
|
+
protected static async resolvesStreamInfoByProp() {
|
|
285
|
+
await this.resolveByProp()
|
|
286
|
+
|
|
287
|
+
assert.isEqualDeep(
|
|
288
|
+
this.ffiRsLoadOptions,
|
|
289
|
+
{
|
|
290
|
+
library: 'lsl',
|
|
291
|
+
funcName: 'lsl_resolve_byprop',
|
|
292
|
+
retType: DataType.I32,
|
|
293
|
+
paramsType: [
|
|
294
|
+
DataType.External,
|
|
295
|
+
DataType.I32,
|
|
296
|
+
DataType.String,
|
|
297
|
+
DataType.String,
|
|
298
|
+
DataType.I32,
|
|
299
|
+
DataType.Double,
|
|
300
|
+
],
|
|
301
|
+
paramsValue: [
|
|
302
|
+
this.resultsBufferPtr,
|
|
303
|
+
this.maxResults,
|
|
304
|
+
this.prop,
|
|
305
|
+
this.value,
|
|
306
|
+
this.minResults,
|
|
307
|
+
this.timeoutMs / 1000,
|
|
308
|
+
],
|
|
309
|
+
runInNewThread: true,
|
|
310
|
+
},
|
|
311
|
+
'Did not call resolveByProp with expected params!'
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@test()
|
|
316
|
+
protected static async resolveByPropAcceptsOptionalArgs() {
|
|
317
|
+
const minResults = randomInt(0, 10)
|
|
318
|
+
const timeoutMs = Math.random() * 1000
|
|
319
|
+
|
|
320
|
+
await this.resolveByProp({
|
|
321
|
+
minResults,
|
|
322
|
+
timeoutMs,
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
assert.isEqualDeep(
|
|
326
|
+
{
|
|
327
|
+
minResults: this.ffiRsLoadOptions?.['paramsValue']?.[4],
|
|
328
|
+
timeoutMs: this.ffiRsLoadOptions?.['paramsValue']?.[5],
|
|
329
|
+
},
|
|
330
|
+
{ minResults, timeoutMs: timeoutMs / 1000 },
|
|
331
|
+
'Did not call resolveByProp with expected params!'
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@test()
|
|
336
|
+
protected static async returnsHandlesForResolvedStreamInfos() {
|
|
337
|
+
LiblslAdapter.alloc = (size: number) => {
|
|
338
|
+
const buffer = Buffer.alloc(size)
|
|
339
|
+
|
|
340
|
+
for (let i = 0; i < this.fakeNumResolveResults; i++) {
|
|
341
|
+
const offset = i * this.bytesPerPointer
|
|
342
|
+
buffer.writeBigUInt64LE(BigInt(i + 1), offset)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return buffer
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const expectedHandles: bigint[] = []
|
|
349
|
+
|
|
350
|
+
for (let i = 0; i < this.fakeNumResolveResults; i++) {
|
|
351
|
+
expectedHandles.push(BigInt(i + 1))
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const actualHandles = await this.resolveByProp()
|
|
355
|
+
|
|
356
|
+
const actual = actualHandles.map((_h, i) => (i + 1).toString())
|
|
357
|
+
const expected = expectedHandles.map((h) => h.toString())
|
|
358
|
+
|
|
359
|
+
assert.isEqualDeep(
|
|
360
|
+
actual,
|
|
361
|
+
expected,
|
|
362
|
+
'Did not receive expected resolved stream info handles!'
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@test()
|
|
367
|
+
protected static async createsOutletWithRequiredParams() {
|
|
368
|
+
const { options, outletHandle } = this.createRandomOutlet()
|
|
369
|
+
|
|
370
|
+
const expected = {
|
|
371
|
+
...options,
|
|
372
|
+
maxBufferedMs: options.maxBufferedMs / 1000,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
assert.isEqualDeep(this.createOutletParams, Object.values(expected))
|
|
376
|
+
assert.isEqual(outletHandle, this.fakeOutletHandle)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
@test()
|
|
380
|
+
protected static async pushesFloatSample() {
|
|
381
|
+
const expected = [1.0, 2.0, 3.0]
|
|
382
|
+
const timestamp = randomInt(100)
|
|
383
|
+
|
|
384
|
+
const options = {
|
|
385
|
+
outletHandle: this.fakeOutletHandle,
|
|
386
|
+
sample: expected,
|
|
387
|
+
timestamp,
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
this.instance.pushSampleFloatTimestamp(options)
|
|
391
|
+
|
|
392
|
+
assert.isEqualDeep(this.pushSampleFloatTimestampParams, [
|
|
393
|
+
this.fakeOutletHandle,
|
|
394
|
+
expected,
|
|
395
|
+
timestamp,
|
|
396
|
+
])
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
@test()
|
|
400
|
+
protected static async pushesStringSample() {
|
|
401
|
+
const expected = [this.generateId()]
|
|
402
|
+
const timestamp = randomInt(100)
|
|
403
|
+
|
|
404
|
+
const options = {
|
|
405
|
+
outletHandle: this.fakeOutletHandle,
|
|
406
|
+
sample: expected,
|
|
407
|
+
timestamp,
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
this.instance.pushSampleStringTimestamp(options)
|
|
411
|
+
assert.isEqual(
|
|
412
|
+
this.pushSampleStringTimestampParams?.[0],
|
|
413
|
+
this.fakeOutletHandle
|
|
414
|
+
)
|
|
415
|
+
assert.isEqualDeep(this.pushSampleStringTimestampParams?.[1], expected)
|
|
416
|
+
assert.isEqual(this.pushSampleStringTimestampParams?.[2], timestamp)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
@test()
|
|
420
|
+
protected static async addingSingleChannelGetsDescription() {
|
|
421
|
+
const infoHandle = this.createRandomInfoHandle()
|
|
422
|
+
const channel: LslChannel = this.generateRandomChannelValues()
|
|
423
|
+
|
|
424
|
+
this.instance.appendChannelsToStreamInfo({
|
|
425
|
+
infoHandle,
|
|
426
|
+
channels: [channel],
|
|
427
|
+
})
|
|
428
|
+
assert.isEqualDeep(this.getDescriptionParams?.[0], [infoHandle])
|
|
429
|
+
|
|
430
|
+
assert.isEqual(this.appendChildParams?.[0][0], this.fakeDescHandle)
|
|
431
|
+
assert.isEqual(this.appendChildParams?.[0][1], 'channels')
|
|
432
|
+
|
|
433
|
+
assert.isEqual(this.appendChildParams?.[1][0], this.fakeChildHandle)
|
|
434
|
+
assert.isEqual(this.appendChildParams?.[1][1], 'channel')
|
|
435
|
+
|
|
436
|
+
assert.isLength(this.appendChildValueParams, 3)
|
|
437
|
+
|
|
438
|
+
for (let i = 0; i < 3; i++) {
|
|
439
|
+
const param = this.appendChildValueParams[i]
|
|
440
|
+
assert.isEqual(param[0], this.fakeChildNamedChannel)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
assert.isEqual(this.appendChildValueParams[0][1], 'label')
|
|
444
|
+
assert.isEqual(this.appendChildValueParams[1][1], 'unit')
|
|
445
|
+
assert.isEqual(this.appendChildValueParams[2][1], 'type')
|
|
446
|
+
|
|
447
|
+
assert.isEqual(this.appendChildValueParams[0][2], channel.label)
|
|
448
|
+
assert.isEqual(this.appendChildValueParams[1][2], channel.units)
|
|
449
|
+
assert.isEqual(this.appendChildValueParams[2][2], channel.type)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
@test()
|
|
453
|
+
protected static async addingMultpleChannelsAddsChildrenToChannelsChild() {
|
|
454
|
+
const infoHandle = this.createRandomInfoHandle()
|
|
455
|
+
const channel1 = this.generateRandomChannelValues()
|
|
456
|
+
const channel2 = this.generateRandomChannelValues()
|
|
457
|
+
|
|
458
|
+
this.instance.appendChannelsToStreamInfo({
|
|
459
|
+
infoHandle,
|
|
460
|
+
channels: [channel1, channel2],
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
assert.isEqual(this.appendChildParams?.[2][0], this.fakeChildHandle)
|
|
464
|
+
assert.isEqual(this.appendChildParams?.[2][1], 'channel')
|
|
465
|
+
|
|
466
|
+
assert.isEqual(this.appendChildValueParams[3][2], channel2.label)
|
|
467
|
+
assert.isEqual(this.appendChildValueParams[4][2], channel2.units)
|
|
468
|
+
assert.isEqual(this.appendChildValueParams[5][2], channel2.type)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
@test()
|
|
472
|
+
protected static async canDestroyOutlet() {
|
|
473
|
+
const { outletHandle } = this.createRandomOutlet()
|
|
474
|
+
const options = { outletHandle }
|
|
475
|
+
this.instance.destroyOutlet(options)
|
|
476
|
+
|
|
477
|
+
assert.isEqualDeep(this.destroyOutletParams, Object.values(options))
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
@test()
|
|
481
|
+
protected static async callingLocalClockTwiceReturnsDifferentTimestamps() {
|
|
482
|
+
const t1 = this.instance.localClock()
|
|
483
|
+
await this.wait(10)
|
|
484
|
+
const t2 = this.instance.localClock()
|
|
485
|
+
assert.isNotEqual(t1, t2)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
@test()
|
|
489
|
+
protected static async localClockBindingReceivesEmptyArray() {
|
|
490
|
+
delete this.localClockParams
|
|
491
|
+
this.instance.localClock()
|
|
492
|
+
assert.isEqualDeep(this.localClockParams, [])
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
@test()
|
|
496
|
+
protected static async defaultsToMacOsPath() {
|
|
497
|
+
delete process.env.LIBLSL_PATH
|
|
498
|
+
|
|
499
|
+
LiblslAdapter.resetInstance()
|
|
500
|
+
this.instance = LiblslAdapter.getInstance() as FakeLiblsl
|
|
501
|
+
|
|
502
|
+
assert.isEqual(
|
|
503
|
+
this.ffiRsOpenOptions?.path,
|
|
504
|
+
'/opt/homebrew/Cellar/lsl/1.16.2/lib/liblsl.1.16.2.dylib'
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
@test()
|
|
509
|
+
protected static async createInletWithRequiredParams() {
|
|
510
|
+
const { options, inletHandle } = this.createRandomInlet()
|
|
511
|
+
const { maxBufferedMs } = options
|
|
512
|
+
|
|
513
|
+
const expected = {
|
|
514
|
+
...options,
|
|
515
|
+
maxBufferedMs: maxBufferedMs / 1000,
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const maxChunkSize = 0
|
|
519
|
+
const shouldRecover = 1
|
|
520
|
+
|
|
521
|
+
assert.isEqualDeep(
|
|
522
|
+
this.createInletParams,
|
|
523
|
+
[...Object.values(expected), maxChunkSize, shouldRecover],
|
|
524
|
+
'Did not call createInlet with expected params!'
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
assert.isEqual(
|
|
528
|
+
inletHandle,
|
|
529
|
+
this.fakeInletHandle,
|
|
530
|
+
'Did not receive expected inlet!'
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
@test()
|
|
535
|
+
protected static async getsChannelCountFromInfoHandle() {
|
|
536
|
+
const infoHandle = this.createRandomInfoHandle()
|
|
537
|
+
|
|
538
|
+
const channelCount = this.instance.getChannelCount({ infoHandle })
|
|
539
|
+
assert.isEqual(channelCount, this.channelCount)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
@test()
|
|
543
|
+
protected static async openStreamCallsBinding() {
|
|
544
|
+
const { inletHandle } = this.createRandomInlet()
|
|
545
|
+
|
|
546
|
+
const timeoutMs = randomInt(1000)
|
|
547
|
+
|
|
548
|
+
const errorCodePtr = unwrapPointer(
|
|
549
|
+
createPointer({
|
|
550
|
+
paramsType: [DataType.U8Array],
|
|
551
|
+
paramsValue: [new Int32Array(1)],
|
|
552
|
+
})
|
|
553
|
+
)[0]
|
|
554
|
+
|
|
555
|
+
await this.instance.openStream({
|
|
556
|
+
inletHandle,
|
|
557
|
+
timeoutMs,
|
|
558
|
+
errorCodePtr,
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
assert.isEqualDeep(
|
|
562
|
+
this.ffiRsLoadOptions,
|
|
563
|
+
{
|
|
564
|
+
library: 'lsl',
|
|
565
|
+
funcName: 'lsl_open_stream',
|
|
566
|
+
retType: DataType.Void,
|
|
567
|
+
paramsType: [
|
|
568
|
+
DataType.External,
|
|
569
|
+
DataType.Double,
|
|
570
|
+
DataType.External,
|
|
571
|
+
],
|
|
572
|
+
paramsValue: [inletHandle, timeoutMs / 1000, errorCodePtr],
|
|
573
|
+
runInNewThread: true,
|
|
574
|
+
},
|
|
575
|
+
'Did not call openStream with expected options!'
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
@test()
|
|
580
|
+
protected static async closeStreamCallsBinding() {
|
|
581
|
+
const { inletHandle } = this.createRandomInlet()
|
|
582
|
+
|
|
583
|
+
this.instance.closeStream({
|
|
584
|
+
inletHandle,
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
assert.isEqualDeep(
|
|
588
|
+
this.ffiRsLoadOptions,
|
|
589
|
+
{
|
|
590
|
+
library: 'lsl',
|
|
591
|
+
funcName: 'lsl_close_stream',
|
|
592
|
+
retType: DataType.Void,
|
|
593
|
+
paramsType: [DataType.External],
|
|
594
|
+
paramsValue: [inletHandle],
|
|
595
|
+
},
|
|
596
|
+
'Did not call closeStream with expected options!'
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
@test()
|
|
601
|
+
protected static async pullSampleCallsBinding() {
|
|
602
|
+
const { inletHandle } = this.createRandomInlet()
|
|
603
|
+
|
|
604
|
+
const sampleBuffer = Buffer.alloc(4 * this.channelCount)
|
|
605
|
+
const sampleBufferElements = this.channelCount
|
|
606
|
+
const timeoutMs = 0.0
|
|
607
|
+
|
|
608
|
+
const sampleBufferPtr = unwrapPointer(
|
|
609
|
+
createPointer({
|
|
610
|
+
paramsType: [DataType.U8Array],
|
|
611
|
+
paramsValue: [sampleBuffer],
|
|
612
|
+
})
|
|
613
|
+
)[0]
|
|
614
|
+
|
|
615
|
+
const errorCodePtr = unwrapPointer(
|
|
616
|
+
createPointer({
|
|
617
|
+
paramsType: [DataType.U8Array],
|
|
618
|
+
paramsValue: [new Int32Array(1)],
|
|
619
|
+
})
|
|
620
|
+
)[0]
|
|
621
|
+
|
|
622
|
+
this.instance.pullSample({
|
|
623
|
+
inletHandle,
|
|
624
|
+
sampleBufferPtr,
|
|
625
|
+
sampleBufferElements,
|
|
626
|
+
timeoutMs,
|
|
627
|
+
errorCodePtr,
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
assert.isEqualDeep(
|
|
631
|
+
this.ffiRsLoadOptions,
|
|
632
|
+
{
|
|
633
|
+
library: 'lsl',
|
|
634
|
+
funcName: 'lsl_pull_sample_f',
|
|
635
|
+
retType: DataType.Double,
|
|
636
|
+
paramsType: [
|
|
637
|
+
DataType.External,
|
|
638
|
+
DataType.External,
|
|
639
|
+
DataType.I32,
|
|
640
|
+
DataType.Double,
|
|
641
|
+
DataType.External,
|
|
642
|
+
],
|
|
643
|
+
paramsValue: [
|
|
644
|
+
inletHandle,
|
|
645
|
+
sampleBufferPtr,
|
|
646
|
+
sampleBufferElements,
|
|
647
|
+
timeoutMs,
|
|
648
|
+
errorCodePtr,
|
|
649
|
+
],
|
|
650
|
+
},
|
|
651
|
+
'Did not call pullSample with expected options!'
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
@test()
|
|
656
|
+
protected static async pullChunkCallsBinding() {
|
|
657
|
+
const { inletHandle } = this.createRandomInlet()
|
|
658
|
+
|
|
659
|
+
const sampleBuffer = Buffer.alloc(
|
|
660
|
+
4 * this.chunkSize * this.channelCount
|
|
661
|
+
)
|
|
662
|
+
const timestampBuffer = Buffer.alloc(8 * this.chunkSize)
|
|
663
|
+
const sampleBufferElements = this.chunkSize * this.channelCount
|
|
664
|
+
const timestampBufferElements = this.chunkSize
|
|
665
|
+
const timeoutMs = 0.0
|
|
666
|
+
|
|
667
|
+
const sampleBufferPtr = unwrapPointer(
|
|
668
|
+
createPointer({
|
|
669
|
+
paramsType: [DataType.U8Array],
|
|
670
|
+
paramsValue: [sampleBuffer],
|
|
671
|
+
})
|
|
672
|
+
)[0]
|
|
673
|
+
|
|
674
|
+
const timestampBufferPtr = unwrapPointer(
|
|
675
|
+
createPointer({
|
|
676
|
+
paramsType: [DataType.U8Array],
|
|
677
|
+
paramsValue: [timestampBuffer],
|
|
678
|
+
})
|
|
679
|
+
)[0]
|
|
680
|
+
|
|
681
|
+
const errorCodePtr = unwrapPointer(
|
|
682
|
+
createPointer({
|
|
683
|
+
paramsType: [DataType.U8Array],
|
|
684
|
+
paramsValue: [new Int32Array(1)],
|
|
685
|
+
})
|
|
686
|
+
)[0]
|
|
687
|
+
|
|
688
|
+
this.instance.pullChunk({
|
|
689
|
+
inletHandle,
|
|
690
|
+
sampleBufferPtr,
|
|
691
|
+
timestampBufferPtr,
|
|
692
|
+
sampleBufferElements,
|
|
693
|
+
timestampBufferElements,
|
|
694
|
+
timeoutMs,
|
|
695
|
+
errorCodePtr,
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
assert.isEqualDeep(
|
|
699
|
+
this.ffiRsLoadOptions,
|
|
700
|
+
{
|
|
701
|
+
library: 'lsl',
|
|
702
|
+
funcName: 'lsl_pull_chunk_f',
|
|
703
|
+
retType: DataType.Double,
|
|
704
|
+
paramsType: [
|
|
705
|
+
DataType.External,
|
|
706
|
+
DataType.External,
|
|
707
|
+
DataType.External,
|
|
708
|
+
DataType.I32,
|
|
709
|
+
DataType.I32,
|
|
710
|
+
DataType.Double,
|
|
711
|
+
DataType.External,
|
|
712
|
+
],
|
|
713
|
+
paramsValue: [
|
|
714
|
+
inletHandle,
|
|
715
|
+
sampleBufferPtr,
|
|
716
|
+
timestampBufferPtr,
|
|
717
|
+
sampleBufferElements,
|
|
718
|
+
timestampBufferElements,
|
|
719
|
+
timeoutMs,
|
|
720
|
+
errorCodePtr,
|
|
721
|
+
],
|
|
722
|
+
},
|
|
723
|
+
'Did not call pullChunk with expected options!'
|
|
724
|
+
)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
@test()
|
|
728
|
+
protected static async flushInletCallsBinding() {
|
|
729
|
+
const { inletHandle } = this.createRandomInlet()
|
|
730
|
+
this.instance.flushInlet({ inletHandle })
|
|
731
|
+
|
|
732
|
+
assert.isEqualDeep(
|
|
733
|
+
this.flushInletParams,
|
|
734
|
+
[inletHandle],
|
|
735
|
+
'Should have called flushInlet with expected params!'
|
|
736
|
+
)
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
@test()
|
|
740
|
+
protected static async destroyInletCallsBinding() {
|
|
741
|
+
const { inletHandle } = this.createRandomInlet()
|
|
742
|
+
this.instance.destroyInlet({ inletHandle })
|
|
743
|
+
|
|
744
|
+
assert.isEqualDeep(
|
|
745
|
+
this.destroyInletParams,
|
|
746
|
+
[inletHandle],
|
|
747
|
+
'Should have called destroyInlet with expected params!'
|
|
748
|
+
)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
private static createRandomInfoHandle() {
|
|
752
|
+
return this.instance.createStreamInfo(
|
|
753
|
+
this.generateRandomCreateStreamInfoOptions()
|
|
754
|
+
)
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
private static async resolveByProp(
|
|
758
|
+
options?: Partial<ResolveByPropOptions>
|
|
759
|
+
) {
|
|
760
|
+
return this.instance.resolveByProp({
|
|
761
|
+
prop: this.prop,
|
|
762
|
+
value: this.value,
|
|
763
|
+
minResults: this.minResults,
|
|
764
|
+
timeoutMs: this.timeoutMs,
|
|
765
|
+
...options,
|
|
766
|
+
})
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
private static createRandomOutlet() {
|
|
770
|
+
const options = this.createRandomOutletOptions()
|
|
771
|
+
const outletHandle = this.instance.createOutlet(options)
|
|
772
|
+
return { options, outletHandle }
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
private static createRandomOutletOptions() {
|
|
776
|
+
const infoHandle = this.createRandomInfoHandle()
|
|
777
|
+
|
|
778
|
+
return {
|
|
779
|
+
infoHandle,
|
|
780
|
+
chunkSize: randomInt(10),
|
|
781
|
+
maxBufferedMs: randomInt(10),
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
private static createRandomInlet() {
|
|
786
|
+
const options = this.createRandomInletOptions()
|
|
787
|
+
const inletHandle = this.instance.createInlet(options)
|
|
788
|
+
return { options, inletHandle }
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
private static createRandomInletOptions() {
|
|
792
|
+
const infoHandle = this.createRandomInfoHandle()
|
|
793
|
+
|
|
794
|
+
return {
|
|
795
|
+
infoHandle,
|
|
796
|
+
maxBufferedMs: randomInt(10),
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
private static generateRandomChannelValues() {
|
|
801
|
+
return {
|
|
802
|
+
label: this.generateId(),
|
|
803
|
+
type: this.generateId(),
|
|
804
|
+
units: this.generateId(),
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
private static generateRandomCreateStreamInfoOptions() {
|
|
809
|
+
return {
|
|
810
|
+
name: this.generateId(),
|
|
811
|
+
type: this.generateId(),
|
|
812
|
+
channelCount: randomInt(1, 10),
|
|
813
|
+
sampleRateHz: randomInt(100),
|
|
814
|
+
channelFormat: randomInt(7),
|
|
815
|
+
sourceId: this.generateId(),
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// resolveByProp
|
|
820
|
+
private static readonly maxResults = 1024
|
|
821
|
+
private static readonly bytesPerPointer = 8
|
|
822
|
+
private static readonly minResults = 1
|
|
823
|
+
private static readonly timeoutMs = 1000
|
|
824
|
+
private static readonly prop = this.generateId()
|
|
825
|
+
private static readonly value = this.generateId()
|
|
826
|
+
private static readonly fakeNumResolveResults = 2
|
|
827
|
+
|
|
828
|
+
private static readonly resultsBuffer = Buffer.alloc(
|
|
829
|
+
this.maxResults * this.bytesPerPointer
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
private static readonly resultsBufferRef = createPointer({
|
|
833
|
+
paramsType: [DataType.U8Array],
|
|
834
|
+
paramsValue: [this.resultsBuffer],
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
private static readonly resultsBufferPtr = unwrapPointer(
|
|
838
|
+
this.resultsBufferRef
|
|
839
|
+
)[0]
|
|
840
|
+
|
|
841
|
+
private static generateFailedMessage() {
|
|
842
|
+
return `Loading the liblsl dylib failed! I tried to load it from ${process.env.LIBLSL_PATH}.`
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
private static FakeBindings() {
|
|
846
|
+
return {
|
|
847
|
+
lsl_create_streaminfo: (params: any[]) => {
|
|
848
|
+
this.createStreamInfoParams = params
|
|
849
|
+
return this.fakeInfoHandle
|
|
850
|
+
},
|
|
851
|
+
lsl_destroy_streaminfo: (params: any[]) => {
|
|
852
|
+
this.destroyStreamInfoParams = params
|
|
853
|
+
},
|
|
854
|
+
lsl_create_outlet: (params: any[]) => {
|
|
855
|
+
this.createOutletParams = params
|
|
856
|
+
return this.fakeOutletHandle
|
|
857
|
+
},
|
|
858
|
+
lsl_push_sample_ft: (params: any[]) => {
|
|
859
|
+
this.pushSampleFloatTimestampParams = params
|
|
860
|
+
return 0
|
|
861
|
+
},
|
|
862
|
+
lsl_push_sample_strt: (params: any[]) => {
|
|
863
|
+
this.pushSampleStringTimestampParams = params
|
|
864
|
+
return 0
|
|
865
|
+
},
|
|
866
|
+
lsl_destroy_outlet: (params: any[]) => {
|
|
867
|
+
this.destroyOutletParams = params
|
|
868
|
+
},
|
|
869
|
+
lsl_create_inlet: (params: any[]) => {
|
|
870
|
+
this.createInletParams = params
|
|
871
|
+
return this.fakeInletHandle
|
|
872
|
+
},
|
|
873
|
+
lsl_inlet_flush: (params: any[]) => {
|
|
874
|
+
this.flushInletParams = params
|
|
875
|
+
return 0
|
|
876
|
+
},
|
|
877
|
+
lsl_destroy_inlet: (params: any[]) => {
|
|
878
|
+
this.destroyInletParams = params
|
|
879
|
+
},
|
|
880
|
+
lsl_local_clock: (params: []) => {
|
|
881
|
+
this.localClockParams = params
|
|
882
|
+
return new Date().getTime()
|
|
883
|
+
},
|
|
884
|
+
lsl_get_channel_count: (info: InfoHandle) => {
|
|
885
|
+
this.getChannelCountParams = [info]
|
|
886
|
+
return this.channelCount
|
|
887
|
+
},
|
|
888
|
+
lsl_get_desc: (info: InfoHandle) => {
|
|
889
|
+
this.getDescriptionParams = [info]
|
|
890
|
+
return this.fakeDescHandle
|
|
891
|
+
},
|
|
892
|
+
lsl_append_child: (params: any) => {
|
|
893
|
+
this.appendChildParams.push(params)
|
|
894
|
+
if (this.appendChildHitCount === 0) {
|
|
895
|
+
this.appendChildHitCount++
|
|
896
|
+
return this.fakeChildHandle
|
|
897
|
+
}
|
|
898
|
+
return this.fakeChildNamedChannel
|
|
899
|
+
},
|
|
900
|
+
lsl_append_child_value: (params: any[]) => {
|
|
901
|
+
this.appendChildValueParams.push(params)
|
|
902
|
+
},
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
export type FfiRsDefineOptions = FuncObj<FieldType, boolean | undefined>
|