-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathbuffer_integrity_test.go
More file actions
440 lines (367 loc) · 14.4 KB
/
Copy pathbuffer_integrity_test.go
File metadata and controls
440 lines (367 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
// SPDX-FileCopyrightText: 2025 Tomi P. Hakala
// SPDX-License-Identifier: LGPL-2.1-or-later
package engine
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestDFTStage_BufferIntegrity verifies that output buffers remain valid
// after subsequent calls to Process() or Flush().
// This is a regression test for the buffer reuse bug that corrupted output
// when callers stored results and then called Process/Flush again.
func TestDFTStage_BufferIntegrity(t *testing.T) {
stage, err := NewDFTStage[float64](2, QualityHigh)
require.NoError(t, err, "Failed to create DFT stage")
// Generate test signal
input := make([]float64, 1000)
for i := range input {
input[i] = math.Sin(2.0 * math.Pi * float64(i) / 100)
}
// First process call
output1, err := stage.Process(input)
require.NoError(t, err, "First Process() failed")
require.NotEmpty(t, output1, "First output should not be empty")
// Save first few values for comparison
savedValues := make([]float64, min(10, len(output1)))
copy(savedValues, output1)
// Second process call - this should NOT corrupt output1
input2 := make([]float64, 500)
for i := range input2 {
input2[i] = math.Cos(2.0 * math.Pi * float64(i) / 50)
}
_, err = stage.Process(input2)
require.NoError(t, err, "Second Process() failed")
// Verify output1 was not corrupted
for i, expected := range savedValues {
assert.InDelta(t, expected, output1[i], 1e-15,
"output1[%d] was corrupted after second Process() call: expected %f, got %f",
i, expected, output1[i])
}
t.Log("DFT stage buffer integrity verified: output survives subsequent Process() calls")
}
// TestDFTStage_FlushDoesNotCorruptOutput verifies that calling Flush()
// after Process() does not corrupt the previously returned output.
func TestDFTStage_FlushDoesNotCorruptOutput(t *testing.T) {
stage, err := NewDFTStage[float64](2, QualityHigh)
require.NoError(t, err, "Failed to create DFT stage")
// Generate test signal
input := make([]float64, 1000)
for i := range input {
input[i] = math.Sin(2.0 * math.Pi * float64(i) / 100)
}
// Process the signal
output, err := stage.Process(input)
require.NoError(t, err, "Process() failed")
require.NotEmpty(t, output, "Output should not be empty")
// Save all values for comparison
savedOutput := make([]float64, len(output))
copy(savedOutput, output)
// Flush the stage - this should NOT corrupt the previous output
flush, err := stage.Flush()
require.NoError(t, err, "Flush() failed")
// Verify output was not corrupted
for i, expected := range savedOutput {
assert.InDelta(t, expected, output[i], 1e-15,
"output[%d] was corrupted after Flush(): expected %f, got %f",
i, expected, output[i])
}
// Also verify flush output is valid
for i, v := range flush {
assert.False(t, math.IsNaN(v), "flush[%d] is NaN", i)
assert.False(t, math.IsInf(v, 0), "flush[%d] is Inf", i)
}
t.Log("DFT stage: output survives Flush() call")
}
// TestPolyphaseStage_BufferIntegrity verifies that output buffers remain valid
// after subsequent calls to Process() or Flush().
func TestPolyphaseStage_BufferIntegrity(t *testing.T) {
// Create polyphase stage for non-integer ratio
stage, err := NewPolyphaseStage[float64](1.088435374, 0.459375, true, QualityHigh)
require.NoError(t, err, "Failed to create polyphase stage")
// Generate test signal
input := make([]float64, 2000)
for i := range input {
input[i] = math.Sin(2.0 * math.Pi * float64(i) / 100)
}
// First process call
output1, err := stage.Process(input)
require.NoError(t, err, "First Process() failed")
require.NotEmpty(t, output1, "First output should not be empty")
// Save first few values for comparison
savedValues := make([]float64, min(10, len(output1)))
copy(savedValues, output1)
// Second process call - this should NOT corrupt output1
input2 := make([]float64, 1000)
for i := range input2 {
input2[i] = math.Cos(2.0 * math.Pi * float64(i) / 50)
}
_, err = stage.Process(input2)
require.NoError(t, err, "Second Process() failed")
// Verify output1 was not corrupted
for i, expected := range savedValues {
assert.InDelta(t, expected, output1[i], 1e-15,
"output1[%d] was corrupted after second Process() call: expected %f, got %f",
i, expected, output1[i])
}
t.Log("Polyphase stage buffer integrity verified: output survives subsequent Process() calls")
}
// TestPolyphaseStage_FlushDoesNotCorruptOutput verifies that calling Flush()
// after Process() does not corrupt the previously returned output.
func TestPolyphaseStage_FlushDoesNotCorruptOutput(t *testing.T) {
stage, err := NewPolyphaseStage[float64](1.088435374, 0.459375, true, QualityHigh)
require.NoError(t, err, "Failed to create polyphase stage")
// Generate test signal
input := make([]float64, 2000)
for i := range input {
input[i] = math.Sin(2.0 * math.Pi * float64(i) / 100)
}
// Process the signal
output, err := stage.Process(input)
require.NoError(t, err, "Process() failed")
require.NotEmpty(t, output, "Output should not be empty")
// Save all values for comparison
savedOutput := make([]float64, len(output))
copy(savedOutput, output)
// Flush the stage - this should NOT corrupt the previous output
flush, err := stage.Flush()
require.NoError(t, err, "Flush() failed")
// Verify output was not corrupted
for i, expected := range savedOutput {
assert.InDelta(t, expected, output[i], 1e-15,
"output[%d] was corrupted after Flush(): expected %f, got %f",
i, expected, output[i])
}
// Also verify flush output is valid
for i, v := range flush {
assert.False(t, math.IsNaN(v), "flush[%d] is NaN", i)
assert.False(t, math.IsInf(v, 0), "flush[%d] is Inf", i)
}
t.Log("Polyphase stage: output survives Flush() call")
}
// TestResampler_BufferIntegrity verifies buffer integrity for full resampler.
func TestResampler_BufferIntegrity(t *testing.T) {
testCases := []struct {
name string
inputRate float64
outputRate float64
}{
{"44100_to_48000", 44100, 48000},
{"48000_to_44100", 48000, 44100},
{"44100_to_96000", 44100, 96000},
{"96000_to_48000", 96000, 48000},
{"44100_to_88200", 44100, 88200}, // 2x integer ratio
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resampler, err := NewResampler[float64](tc.inputRate, tc.outputRate, QualityHigh)
require.NoError(t, err, "Failed to create resampler")
// Generate test signal
input := make([]float64, 4000)
for i := range input {
input[i] = math.Sin(2.0 * math.Pi * 1000 * float64(i) / tc.inputRate)
}
// First process call
output1, err := resampler.Process(input)
require.NoError(t, err, "First Process() failed")
// Save values for comparison
savedOutput := make([]float64, len(output1))
copy(savedOutput, output1)
// Second process call
input2 := make([]float64, 2000)
for i := range input2 {
input2[i] = math.Cos(2.0 * math.Pi * 500 * float64(i) / tc.inputRate)
}
_, err = resampler.Process(input2)
require.NoError(t, err, "Second Process() failed")
// Verify output1 was not corrupted
for i, expected := range savedOutput {
assert.InDelta(t, expected, output1[i], 1e-15,
"output1[%d] was corrupted: expected %f, got %f", i, expected, output1[i])
}
t.Logf("%s: buffer integrity verified", tc.name)
})
}
}
// TestResampler_ProcessAndFlushSequence tests typical usage pattern of
// Process() followed by Flush() to ensure output is not corrupted.
func TestResampler_ProcessAndFlushSequence(t *testing.T) {
testCases := []struct {
name string
inputRate float64
outputRate float64
}{
{"44100_to_48000", 44100, 48000},
{"44100_to_96000", 44100, 96000},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resampler, err := NewResampler[float64](tc.inputRate, tc.outputRate, QualityHigh)
require.NoError(t, err, "Failed to create resampler")
// Generate test signal
input := make([]float64, 4000)
for i := range input {
input[i] = math.Sin(2.0 * math.Pi * 1000 * float64(i) / tc.inputRate)
}
// Process the signal
output, err := resampler.Process(input)
require.NoError(t, err, "Process() failed")
// Save for comparison
savedOutput := make([]float64, len(output))
copy(savedOutput, output)
// Flush - this should NOT corrupt output
flush, err := resampler.Flush()
require.NoError(t, err, "Flush() failed")
// Verify output was not corrupted
for i, expected := range savedOutput {
assert.InDelta(t, expected, output[i], 1e-15,
"output[%d] was corrupted after Flush()", i)
}
// Append flush and verify both are valid
combined := make([]float64, len(output)+len(flush))
copy(combined, output)
copy(combined[len(output):], flush)
for i, v := range combined {
assert.False(t, math.IsNaN(v), "combined[%d] is NaN", i)
assert.False(t, math.IsInf(v, 0), "combined[%d] is Inf", i)
}
t.Logf("%s: Process+Flush sequence verified, output=%d, flush=%d",
tc.name, len(output), len(flush))
})
}
}
// TestDFTStage_MultipleProcessCalls tests that multiple consecutive Process()
// calls work correctly, don't corrupt earlier returned outputs, and are
// deterministic: an identical call sequence on a second fresh instance must
// reproduce the same outputs bit-exactly.
func TestDFTStage_MultipleProcessCalls(t *testing.T) {
const numCalls = 10
const samplesPerCall = 500
// makeInput builds the same deterministic per-call input for both
// instances below, so the two runs are directly comparable.
makeInput := func(callIdx int) []float64 {
input := make([]float64, samplesPerCall)
for j := range input {
// Different phase for each call to detect any cross-contamination
input[j] = math.Sin(2.0*math.Pi*float64(j)/100 + float64(callIdx)*math.Pi/5)
}
return input
}
runSequence := func(stage *DFTStage[float64]) (raw, saved [][]float64) {
raw = make([][]float64, numCalls)
saved = make([][]float64, numCalls)
for i := range numCalls {
output, err := stage.Process(makeInput(i))
require.NoError(t, err, "Process() call %d failed", i)
raw[i] = output
saved[i] = make([]float64, len(output))
copy(saved[i], output)
}
return raw, saved
}
stageA, err := NewDFTStage[float64](2, QualityHigh)
require.NoError(t, err, "Failed to create DFT stage")
rawA, savedA := runSequence(stageA)
// Every earlier call's returned slice must still hold the values it held
// right after that call: a later Process() reusing the same backing
// array without properly copying out would silently corrupt it.
for i := range numCalls {
require.Len(t, rawA[i], len(savedA[i]), "call %d output length changed", i)
for j := range rawA[i] {
// require (not assert) stops at the first mismatch: a real
// corruption bug can differ across most of a call's samples,
// and letting the loop run to completion floods the test log.
require.InDelta(t, savedA[i][j], rawA[i][j], 1e-15,
"call %d output[%d] was corrupted by a later Process() call", i, j)
}
}
// Determinism: an identical call sequence on a second, fresh instance
// must produce bit-identical output per call.
stageB, err := NewDFTStage[float64](2, QualityHigh)
require.NoError(t, err, "Failed to create second DFT stage")
_, savedB := runSequence(stageB)
for i := range numCalls {
require.Len(t, savedB[i], len(savedA[i]), "call %d length differs between two fresh instances", i)
for j := range savedA[i] {
require.InDelta(t, savedA[i][j], savedB[i][j], 1e-15,
"call %d output[%d] differs between two fresh instances given identical input", i, j)
}
}
t.Logf("DFT stage: %d consecutive Process() calls verified bit-identical and uncorrupted", numCalls)
}
// TestPolyphaseStage_MultipleProcessCalls tests that multiple consecutive
// Process() calls work correctly, don't corrupt earlier returned outputs,
// and are deterministic across a second fresh instance.
func TestPolyphaseStage_MultipleProcessCalls(t *testing.T) {
const numCalls = 10
const samplesPerCall = 1000
makeInput := func(callIdx int) []float64 {
input := make([]float64, samplesPerCall)
for j := range input {
input[j] = math.Sin(2.0*math.Pi*float64(j)/100 + float64(callIdx)*math.Pi/5)
}
return input
}
runSequence := func(stage *PolyphaseStage[float64]) (raw, saved [][]float64) {
raw = make([][]float64, numCalls)
saved = make([][]float64, numCalls)
for i := range numCalls {
output, err := stage.Process(makeInput(i))
require.NoError(t, err, "Process() call %d failed", i)
raw[i] = output
saved[i] = make([]float64, len(output))
copy(saved[i], output)
}
return raw, saved
}
stageA, err := NewPolyphaseStage[float64](1.088435374, 0.459375, true, QualityHigh)
require.NoError(t, err, "Failed to create polyphase stage")
rawA, savedA := runSequence(stageA)
for i := range numCalls {
require.Len(t, rawA[i], len(savedA[i]), "call %d output length changed", i)
for j := range rawA[i] {
require.InDelta(t, savedA[i][j], rawA[i][j], 1e-15,
"call %d output[%d] was corrupted by a later Process() call", i, j)
}
}
stageB, err := NewPolyphaseStage[float64](1.088435374, 0.459375, true, QualityHigh)
require.NoError(t, err, "Failed to create second polyphase stage")
_, savedB := runSequence(stageB)
for i := range numCalls {
require.Len(t, savedB[i], len(savedA[i]), "call %d length differs between two fresh instances", i)
for j := range savedA[i] {
require.InDelta(t, savedA[i][j], savedB[i][j], 1e-15,
"call %d output[%d] differs between two fresh instances given identical input", i, j)
}
}
t.Logf("Polyphase stage: %d consecutive Process() calls verified bit-identical and uncorrupted", numCalls)
}
// TestCubicStage_BufferIntegrity verifies CubicStage doesn't have buffer issues.
func TestCubicStage_BufferIntegrity(t *testing.T) {
stage := NewCubicStage[float64](2.0)
// Generate test signal
input := make([]float64, 1000)
for i := range input {
input[i] = math.Sin(2.0 * math.Pi * float64(i) / 100)
}
// First process call
output1, err := stage.Process(input)
require.NoError(t, err, "First Process() failed")
// Save values
savedOutput := make([]float64, len(output1))
copy(savedOutput, output1)
// Second process call
input2 := make([]float64, 500)
for i := range input2 {
input2[i] = math.Cos(2.0 * math.Pi * float64(i) / 50)
}
_, err = stage.Process(input2)
require.NoError(t, err, "Second Process() failed")
// Verify output1 was not corrupted
for i, expected := range savedOutput {
assert.InDelta(t, expected, output1[i], 1e-15,
"output1[%d] was corrupted", i)
}
t.Log("Cubic stage buffer integrity verified")
}