amine@400
|
1 import pytest
|
amine@330
|
2 from functools import partial
|
amine@330
|
3 import sys
|
amine@330
|
4 import wave
|
amine@330
|
5 from auditok import (
|
amine@330
|
6 dataset,
|
amine@330
|
7 ADSFactory,
|
amine@330
|
8 AudioDataSource,
|
amine@330
|
9 AudioReader,
|
amine@330
|
10 Recorder,
|
amine@330
|
11 BufferAudioSource,
|
amine@330
|
12 WaveAudioSource,
|
amine@330
|
13 DuplicateArgument,
|
amine@330
|
14 )
|
amine@330
|
15
|
amine@330
|
16
|
amine@400
|
17 class TestADSFactoryFileAudioSource:
|
amine@400
|
18 def setup_method(self):
|
amine@330
|
19 self.audio_source = WaveAudioSource(
|
amine@330
|
20 filename=dataset.one_to_six_arabic_16000_mono_bc_noise
|
amine@330
|
21 )
|
amine@330
|
22
|
amine@330
|
23 def test_ADS_type(self):
|
amine@330
|
24 ads = ADSFactory.ads(audio_source=self.audio_source)
|
amine@400
|
25 err_msg = (
|
amine@400
|
26 "wrong type for ads object, expected: 'AudioDataSource', found: {0}"
|
amine@330
|
27 )
|
amine@400
|
28 assert isinstance(ads, AudioDataSource), err_msg.format(type(ads))
|
amine@330
|
29
|
amine@330
|
30 def test_default_block_size(self):
|
amine@330
|
31 ads = ADSFactory.ads(audio_source=self.audio_source)
|
amine@330
|
32 size = ads.block_size
|
amine@400
|
33 assert (
|
amine@400
|
34 size == 160
|
amine@400
|
35 ), "Wrong default block_size, expected: 160, found: {0}".format(size)
|
amine@330
|
36
|
amine@330
|
37 def test_block_size(self):
|
amine@330
|
38 ads = ADSFactory.ads(audio_source=self.audio_source, block_size=512)
|
amine@330
|
39 size = ads.block_size
|
amine@400
|
40 assert (
|
amine@400
|
41 size == 512
|
amine@400
|
42 ), "Wrong block_size, expected: 512, found: {0}".format(size)
|
amine@330
|
43
|
amine@330
|
44 # with alias keyword
|
amine@330
|
45 ads = ADSFactory.ads(audio_source=self.audio_source, bs=160)
|
amine@330
|
46 size = ads.block_size
|
amine@400
|
47 assert (
|
amine@400
|
48 size == 160
|
amine@400
|
49 ), "Wrong block_size, expected: 160, found: {0}".format(size)
|
amine@330
|
50
|
amine@330
|
51 def test_block_duration(self):
|
amine@330
|
52 ads = ADSFactory.ads(
|
amine@330
|
53 audio_source=self.audio_source, block_dur=0.01
|
amine@330
|
54 ) # 10 ms
|
amine@330
|
55 size = ads.block_size
|
amine@400
|
56 assert (
|
amine@400
|
57 size == 160
|
amine@400
|
58 ), "Wrong block_size, expected: 160, found: {0}".format(size)
|
amine@330
|
59
|
amine@330
|
60 # with alias keyword
|
amine@330
|
61 ads = ADSFactory.ads(audio_source=self.audio_source, bd=0.025) # 25 ms
|
amine@330
|
62 size = ads.block_size
|
amine@400
|
63 assert (
|
amine@400
|
64 size == 400
|
amine@400
|
65 ), "Wrong block_size, expected: 400, found: {0}".format(size)
|
amine@330
|
66
|
amine@330
|
67 def test_hop_duration(self):
|
amine@330
|
68 ads = ADSFactory.ads(
|
amine@330
|
69 audio_source=self.audio_source, block_dur=0.02, hop_dur=0.01
|
amine@330
|
70 ) # 10 ms
|
amine@330
|
71 size = ads.hop_size
|
amine@400
|
72 assert size == 160, "Wrong hop_size, expected: 160, found: {0}".format(
|
amine@400
|
73 size
|
amine@330
|
74 )
|
amine@330
|
75
|
amine@330
|
76 # with alias keyword
|
amine@330
|
77 ads = ADSFactory.ads(
|
amine@330
|
78 audio_source=self.audio_source, bd=0.025, hop_dur=0.015
|
amine@330
|
79 ) # 15 ms
|
amine@330
|
80 size = ads.hop_size
|
amine@400
|
81 assert (
|
amine@400
|
82 size == 240
|
amine@400
|
83 ), "Wrong block_size, expected: 240, found: {0}".format(size)
|
amine@330
|
84
|
amine@330
|
85 def test_sampling_rate(self):
|
amine@330
|
86 ads = ADSFactory.ads(audio_source=self.audio_source)
|
amine@330
|
87 srate = ads.sampling_rate
|
amine@400
|
88 assert (
|
amine@400
|
89 srate == 16000
|
amine@400
|
90 ), "Wrong sampling rate, expected: 16000, found: {0}".format(srate)
|
amine@330
|
91
|
amine@330
|
92 def test_sample_width(self):
|
amine@330
|
93 ads = ADSFactory.ads(audio_source=self.audio_source)
|
amine@330
|
94 swidth = ads.sample_width
|
amine@400
|
95 assert (
|
amine@400
|
96 swidth == 2
|
amine@400
|
97 ), "Wrong sample width, expected: 2, found: {0}".format(swidth)
|
amine@330
|
98
|
amine@330
|
99 def test_channels(self):
|
amine@330
|
100 ads = ADSFactory.ads(audio_source=self.audio_source)
|
amine@330
|
101 channels = ads.channels
|
amine@400
|
102 assert (
|
amine@400
|
103 channels == 1
|
amine@400
|
104 ), "Wrong number of channels, expected: 1, found: {0}".format(channels)
|
amine@330
|
105
|
amine@330
|
106 def test_read(self):
|
amine@330
|
107 ads = ADSFactory.ads(audio_source=self.audio_source, block_size=256)
|
amine@330
|
108 ads.open()
|
amine@330
|
109 ads_data = ads.read()
|
amine@330
|
110 ads.close()
|
amine@330
|
111
|
amine@330
|
112 audio_source = WaveAudioSource(
|
amine@330
|
113 filename=dataset.one_to_six_arabic_16000_mono_bc_noise
|
amine@330
|
114 )
|
amine@330
|
115 audio_source.open()
|
amine@330
|
116 audio_source_data = audio_source.read(256)
|
amine@330
|
117 audio_source.close()
|
amine@330
|
118
|
amine@400
|
119 assert ads_data == audio_source_data, "Unexpected data read from ads"
|
amine@330
|
120
|
amine@330
|
121 def test_Limiter_Deco_read(self):
|
amine@330
|
122 # read a maximum of 0.75 seconds from audio source
|
amine@330
|
123 ads = ADSFactory.ads(audio_source=self.audio_source, max_time=0.75)
|
amine@330
|
124 ads_data = []
|
amine@330
|
125 ads.open()
|
amine@330
|
126 while True:
|
amine@330
|
127 block = ads.read()
|
amine@330
|
128 if block is None:
|
amine@330
|
129 break
|
amine@330
|
130 ads_data.append(block)
|
amine@330
|
131 ads.close()
|
amine@330
|
132 ads_data = b"".join(ads_data)
|
amine@330
|
133
|
amine@330
|
134 audio_source = WaveAudioSource(
|
amine@330
|
135 filename=dataset.one_to_six_arabic_16000_mono_bc_noise
|
amine@330
|
136 )
|
amine@330
|
137 audio_source.open()
|
amine@330
|
138 audio_source_data = audio_source.read(int(16000 * 0.75))
|
amine@330
|
139 audio_source.close()
|
amine@330
|
140
|
amine@400
|
141 assert (
|
amine@400
|
142 ads_data == audio_source_data
|
amine@400
|
143 ), "Unexpected data read from LimiterADS"
|
amine@330
|
144
|
amine@330
|
145 def test_Limiter_Deco_read_limit(self):
|
amine@330
|
146 # read a maximum of 1.191 seconds from audio source
|
amine@330
|
147 ads = ADSFactory.ads(audio_source=self.audio_source, max_time=1.191)
|
amine@330
|
148 total_samples = round(ads.sampling_rate * 1.191)
|
amine@330
|
149 nb_full_blocks, last_block_size = divmod(total_samples, ads.block_size)
|
amine@330
|
150 total_samples_with_overlap = (
|
amine@330
|
151 nb_full_blocks * ads.block_size + last_block_size
|
amine@330
|
152 )
|
amine@400
|
153 expected_read_bytes = total_samples_with_overlap * ads.sw * ads.channels
|
amine@330
|
154
|
amine@330
|
155 total_read = 0
|
amine@330
|
156 ads.open()
|
amine@330
|
157 i = 0
|
amine@330
|
158 while True:
|
amine@330
|
159 block = ads.read()
|
amine@330
|
160 if block is None:
|
amine@330
|
161 break
|
amine@330
|
162 i += 1
|
amine@330
|
163 total_read += len(block)
|
amine@330
|
164
|
amine@330
|
165 ads.close()
|
amine@400
|
166 err_msg = (
|
amine@400
|
167 "Wrong data length read from LimiterADS, expected: {0}, found: {1}"
|
amine@400
|
168 )
|
amine@400
|
169 assert total_read == expected_read_bytes, err_msg.format(
|
amine@400
|
170 expected_read_bytes, total_read
|
amine@330
|
171 )
|
amine@330
|
172
|
amine@330
|
173 def test_Recorder_Deco_read(self):
|
amine@330
|
174 ads = ADSFactory.ads(
|
amine@330
|
175 audio_source=self.audio_source, record=True, block_size=500
|
amine@330
|
176 )
|
amine@330
|
177 ads_data = []
|
amine@330
|
178 ads.open()
|
amine@330
|
179 for i in range(10):
|
amine@330
|
180 block = ads.read()
|
amine@330
|
181 if block is None:
|
amine@330
|
182 break
|
amine@330
|
183 ads_data.append(block)
|
amine@330
|
184 ads.close()
|
amine@330
|
185 ads_data = b"".join(ads_data)
|
amine@330
|
186
|
amine@330
|
187 audio_source = WaveAudioSource(
|
amine@330
|
188 filename=dataset.one_to_six_arabic_16000_mono_bc_noise
|
amine@330
|
189 )
|
amine@330
|
190 audio_source.open()
|
amine@330
|
191 audio_source_data = audio_source.read(500 * 10)
|
amine@330
|
192 audio_source.close()
|
amine@330
|
193
|
amine@400
|
194 assert (
|
amine@400
|
195 ads_data == audio_source_data
|
amine@400
|
196 ), "Unexpected data read from RecorderADS"
|
amine@330
|
197
|
amine@330
|
198 def test_Recorder_Deco_is_rewindable(self):
|
amine@330
|
199 ads = ADSFactory.ads(audio_source=self.audio_source, record=True)
|
amine@400
|
200 assert ads.rewindable, "RecorderADS.is_rewindable should return True"
|
amine@330
|
201
|
amine@330
|
202 def test_Recorder_Deco_rewind_and_read(self):
|
amine@330
|
203 ads = ADSFactory.ads(
|
amine@330
|
204 audio_source=self.audio_source, record=True, block_size=320
|
amine@330
|
205 )
|
amine@330
|
206 ads.open()
|
amine@330
|
207 for i in range(10):
|
amine@330
|
208 ads.read()
|
amine@330
|
209
|
amine@330
|
210 ads.rewind()
|
amine@330
|
211
|
amine@330
|
212 # read all available data after rewind
|
amine@330
|
213 ads_data = []
|
amine@330
|
214 while True:
|
amine@330
|
215 block = ads.read()
|
amine@330
|
216 if block is None:
|
amine@330
|
217 break
|
amine@330
|
218 ads_data.append(block)
|
amine@330
|
219 ads.close()
|
amine@330
|
220 ads_data = b"".join(ads_data)
|
amine@330
|
221
|
amine@330
|
222 audio_source = WaveAudioSource(
|
amine@330
|
223 filename=dataset.one_to_six_arabic_16000_mono_bc_noise
|
amine@330
|
224 )
|
amine@330
|
225 audio_source.open()
|
amine@330
|
226 audio_source_data = audio_source.read(320 * 10)
|
amine@330
|
227 audio_source.close()
|
amine@330
|
228
|
amine@400
|
229 assert (
|
amine@400
|
230 ads_data == audio_source_data
|
amine@400
|
231 ), "Unexpected data read from RecorderADS"
|
amine@330
|
232
|
amine@330
|
233 def test_Overlap_Deco_read(self):
|
amine@330
|
234 # Use arbitrary valid block_size and hop_size
|
amine@330
|
235 block_size = 1714
|
amine@330
|
236 hop_size = 313
|
amine@330
|
237
|
amine@330
|
238 ads = ADSFactory.ads(
|
amine@330
|
239 audio_source=self.audio_source,
|
amine@330
|
240 block_size=block_size,
|
amine@330
|
241 hop_size=hop_size,
|
amine@330
|
242 )
|
amine@330
|
243
|
amine@330
|
244 # Read all available data overlapping blocks
|
amine@330
|
245 ads.open()
|
amine@330
|
246 ads_data = []
|
amine@330
|
247 while True:
|
amine@330
|
248 block = ads.read()
|
amine@330
|
249 if block is None:
|
amine@330
|
250 break
|
amine@330
|
251 ads_data.append(block)
|
amine@330
|
252 ads.close()
|
amine@330
|
253
|
amine@330
|
254 # Read all data from file and build a BufferAudioSource
|
amine@330
|
255 fp = wave.open(dataset.one_to_six_arabic_16000_mono_bc_noise, "r")
|
amine@330
|
256 wave_data = fp.readframes(fp.getnframes())
|
amine@330
|
257 fp.close()
|
amine@330
|
258 audio_source = BufferAudioSource(
|
amine@330
|
259 wave_data, ads.sampling_rate, ads.sample_width, ads.channels
|
amine@330
|
260 )
|
amine@330
|
261 audio_source.open()
|
amine@330
|
262
|
amine@400
|
263 # Compare all blocks read from OverlapADS to those read from an audio source with a manual position setting
|
amine@330
|
264 for i, block in enumerate(ads_data):
|
amine@330
|
265 tmp = audio_source.read(block_size)
|
amine@400
|
266 assert (
|
amine@400
|
267 block == tmp
|
amine@400
|
268 ), "Unexpected block (N={0}) read from OverlapADS".format(i)
|
amine@330
|
269 audio_source.position = (i + 1) * hop_size
|
amine@330
|
270
|
amine@330
|
271 audio_source.close()
|
amine@330
|
272
|
amine@330
|
273 def test_Limiter_Overlap_Deco_read(self):
|
amine@330
|
274 block_size = 256
|
amine@330
|
275 hop_size = 200
|
amine@330
|
276
|
amine@330
|
277 ads = ADSFactory.ads(
|
amine@330
|
278 audio_source=self.audio_source,
|
amine@330
|
279 max_time=0.50,
|
amine@330
|
280 block_size=block_size,
|
amine@330
|
281 hop_size=hop_size,
|
amine@330
|
282 )
|
amine@330
|
283
|
amine@330
|
284 # Read all available data overlapping blocks
|
amine@330
|
285 ads.open()
|
amine@330
|
286 ads_data = []
|
amine@330
|
287 while True:
|
amine@330
|
288 block = ads.read()
|
amine@330
|
289 if block is None:
|
amine@330
|
290 break
|
amine@330
|
291 ads_data.append(block)
|
amine@330
|
292 ads.close()
|
amine@330
|
293
|
amine@330
|
294 # Read all data from file and build a BufferAudioSource
|
amine@330
|
295 fp = wave.open(dataset.one_to_six_arabic_16000_mono_bc_noise, "r")
|
amine@330
|
296 wave_data = fp.readframes(fp.getnframes())
|
amine@330
|
297 fp.close()
|
amine@330
|
298 audio_source = BufferAudioSource(
|
amine@330
|
299 wave_data, ads.sampling_rate, ads.sample_width, ads.channels
|
amine@330
|
300 )
|
amine@330
|
301 audio_source.open()
|
amine@330
|
302
|
amine@400
|
303 # Compare all blocks read from OverlapADS to those read from an audio source with a manual position setting
|
amine@330
|
304 for i, block in enumerate(ads_data):
|
amine@330
|
305 tmp = audio_source.read(len(block) // (ads.sw * ads.ch))
|
amine@400
|
306 assert len(block) == len(
|
amine@400
|
307 tmp
|
amine@400
|
308 ), "Unexpected block (N={0}) read from OverlapADS".format(i)
|
amine@330
|
309 audio_source.position = (i + 1) * hop_size
|
amine@330
|
310
|
amine@330
|
311 audio_source.close()
|
amine@330
|
312
|
amine@330
|
313 def test_Limiter_Overlap_Deco_read_limit(self):
|
amine@330
|
314 block_size = 313
|
amine@330
|
315 hop_size = 207
|
amine@330
|
316 ads = ADSFactory.ads(
|
amine@330
|
317 audio_source=self.audio_source,
|
amine@330
|
318 max_time=1.932,
|
amine@330
|
319 block_size=block_size,
|
amine@330
|
320 hop_size=hop_size,
|
amine@330
|
321 )
|
amine@330
|
322
|
amine@330
|
323 total_samples = round(ads.sampling_rate * 1.932)
|
amine@330
|
324 first_read_size = block_size
|
amine@330
|
325 next_read_size = block_size - hop_size
|
amine@330
|
326 nb_next_blocks, last_block_size = divmod(
|
amine@330
|
327 (total_samples - first_read_size), next_read_size
|
amine@330
|
328 )
|
amine@330
|
329 total_samples_with_overlap = (
|
amine@330
|
330 first_read_size + next_read_size * nb_next_blocks + last_block_size
|
amine@330
|
331 )
|
amine@400
|
332 expected_read_bytes = total_samples_with_overlap * ads.sw * ads.channels
|
amine@330
|
333
|
amine@330
|
334 cache_size = (block_size - hop_size) * ads.sample_width * ads.channels
|
amine@330
|
335 total_read = cache_size
|
amine@330
|
336
|
amine@330
|
337 ads.open()
|
amine@330
|
338 i = 0
|
amine@330
|
339 while True:
|
amine@330
|
340 block = ads.read()
|
amine@330
|
341 if block is None:
|
amine@330
|
342 break
|
amine@330
|
343 i += 1
|
amine@330
|
344 total_read += len(block) - cache_size
|
amine@330
|
345
|
amine@330
|
346 ads.close()
|
amine@400
|
347 err_msg = (
|
amine@400
|
348 "Wrong data length read from LimiterADS, expected: {0}, found: {1}"
|
amine@400
|
349 )
|
amine@400
|
350 assert total_read == expected_read_bytes, err_msg.format(
|
amine@400
|
351 expected_read_bytes, total_read
|
amine@330
|
352 )
|
amine@330
|
353
|
amine@330
|
354 def test_Recorder_Overlap_Deco_is_rewindable(self):
|
amine@330
|
355 ads = ADSFactory.ads(
|
amine@330
|
356 audio_source=self.audio_source,
|
amine@330
|
357 block_size=320,
|
amine@330
|
358 hop_size=160,
|
amine@330
|
359 record=True,
|
amine@330
|
360 )
|
amine@400
|
361 assert ads.rewindable, "RecorderADS.is_rewindable should return True"
|
amine@330
|
362
|
amine@330
|
363 def test_Recorder_Overlap_Deco_rewind_and_read(self):
|
amine@330
|
364 # Use arbitrary valid block_size and hop_size
|
amine@330
|
365 block_size = 1600
|
amine@330
|
366 hop_size = 400
|
amine@330
|
367
|
amine@330
|
368 ads = ADSFactory.ads(
|
amine@330
|
369 audio_source=self.audio_source,
|
amine@330
|
370 block_size=block_size,
|
amine@330
|
371 hop_size=hop_size,
|
amine@330
|
372 record=True,
|
amine@330
|
373 )
|
amine@330
|
374
|
amine@330
|
375 # Read all available data overlapping blocks
|
amine@330
|
376 ads.open()
|
amine@330
|
377 i = 0
|
amine@330
|
378 while True:
|
amine@330
|
379 block = ads.read()
|
amine@330
|
380 if block is None:
|
amine@330
|
381 break
|
amine@330
|
382 i += 1
|
amine@330
|
383
|
amine@330
|
384 ads.rewind()
|
amine@330
|
385
|
amine@330
|
386 # Read all data from file and build a BufferAudioSource
|
amine@330
|
387 fp = wave.open(dataset.one_to_six_arabic_16000_mono_bc_noise, "r")
|
amine@330
|
388 wave_data = fp.readframes(fp.getnframes())
|
amine@330
|
389 fp.close()
|
amine@330
|
390 audio_source = BufferAudioSource(
|
amine@330
|
391 wave_data, ads.sampling_rate, ads.sample_width, ads.channels
|
amine@330
|
392 )
|
amine@330
|
393 audio_source.open()
|
amine@330
|
394
|
amine@400
|
395 # Compare all blocks read from OverlapADS to those read from an audio source with a manual position setting
|
amine@330
|
396 for j in range(i):
|
amine@330
|
397 tmp = audio_source.read(block_size)
|
amine@400
|
398 assert (
|
amine@400
|
399 ads.read() == tmp
|
amine@400
|
400 ), "Unexpected block (N={0}) read from OverlapADS".format(i)
|
amine@330
|
401 audio_source.position = (j + 1) * hop_size
|
amine@330
|
402
|
amine@330
|
403 ads.close()
|
amine@330
|
404 audio_source.close()
|
amine@330
|
405
|
amine@330
|
406 def test_Limiter_Recorder_Overlap_Deco_rewind_and_read(self):
|
amine@330
|
407 # Use arbitrary valid block_size and hop_size
|
amine@330
|
408 block_size = 1600
|
amine@330
|
409 hop_size = 400
|
amine@330
|
410
|
amine@330
|
411 ads = ADSFactory.ads(
|
amine@330
|
412 audio_source=self.audio_source,
|
amine@330
|
413 max_time=1.50,
|
amine@330
|
414 block_size=block_size,
|
amine@330
|
415 hop_size=hop_size,
|
amine@330
|
416 record=True,
|
amine@330
|
417 )
|
amine@330
|
418
|
amine@330
|
419 # Read all available data overlapping blocks
|
amine@330
|
420 ads.open()
|
amine@330
|
421 i = 0
|
amine@330
|
422 while True:
|
amine@330
|
423 block = ads.read()
|
amine@330
|
424 if block is None:
|
amine@330
|
425 break
|
amine@330
|
426 i += 1
|
amine@330
|
427
|
amine@330
|
428 ads.rewind()
|
amine@330
|
429
|
amine@330
|
430 # Read all data from file and build a BufferAudioSource
|
amine@330
|
431 fp = wave.open(dataset.one_to_six_arabic_16000_mono_bc_noise, "r")
|
amine@330
|
432 wave_data = fp.readframes(fp.getnframes())
|
amine@330
|
433 fp.close()
|
amine@330
|
434 audio_source = BufferAudioSource(
|
amine@330
|
435 wave_data, ads.sampling_rate, ads.sample_width, ads.channels
|
amine@330
|
436 )
|
amine@330
|
437 audio_source.open()
|
amine@330
|
438
|
amine@400
|
439 # Compare all blocks read from OverlapADS to those read from an audio source with a manual position setting
|
amine@330
|
440 for j in range(i):
|
amine@330
|
441 tmp = audio_source.read(block_size)
|
amine@400
|
442 assert (
|
amine@400
|
443 ads.read() == tmp
|
amine@400
|
444 ), "Unexpected block (N={0}) read from OverlapADS".format(i)
|
amine@330
|
445 audio_source.position = (j + 1) * hop_size
|
amine@330
|
446
|
amine@330
|
447 ads.close()
|
amine@330
|
448 audio_source.close()
|
amine@330
|
449
|
amine@330
|
450 def test_Limiter_Recorder_Overlap_Deco_rewind_and_read_limit(self):
|
amine@330
|
451 # Use arbitrary valid block_size and hop_size
|
amine@330
|
452 block_size = 1000
|
amine@330
|
453 hop_size = 200
|
amine@330
|
454
|
amine@330
|
455 ads = ADSFactory.ads(
|
amine@330
|
456 audio_source=self.audio_source,
|
amine@330
|
457 max_time=1.317,
|
amine@330
|
458 block_size=block_size,
|
amine@330
|
459 hop_size=hop_size,
|
amine@330
|
460 record=True,
|
amine@330
|
461 )
|
amine@330
|
462 total_samples = round(ads.sampling_rate * 1.317)
|
amine@330
|
463 first_read_size = block_size
|
amine@330
|
464 next_read_size = block_size - hop_size
|
amine@330
|
465 nb_next_blocks, last_block_size = divmod(
|
amine@330
|
466 (total_samples - first_read_size), next_read_size
|
amine@330
|
467 )
|
amine@330
|
468 total_samples_with_overlap = (
|
amine@330
|
469 first_read_size + next_read_size * nb_next_blocks + last_block_size
|
amine@330
|
470 )
|
amine@400
|
471 expected_read_bytes = total_samples_with_overlap * ads.sw * ads.channels
|
amine@330
|
472
|
amine@330
|
473 cache_size = (block_size - hop_size) * ads.sample_width * ads.channels
|
amine@330
|
474 total_read = cache_size
|
amine@330
|
475
|
amine@330
|
476 ads.open()
|
amine@330
|
477 i = 0
|
amine@330
|
478 while True:
|
amine@330
|
479 block = ads.read()
|
amine@330
|
480 if block is None:
|
amine@330
|
481 break
|
amine@330
|
482 i += 1
|
amine@330
|
483 total_read += len(block) - cache_size
|
amine@330
|
484
|
amine@330
|
485 ads.close()
|
amine@400
|
486 err_msg = (
|
amine@400
|
487 "Wrong data length read from LimiterADS, expected: {0}, found: {1}"
|
amine@400
|
488 )
|
amine@400
|
489 assert total_read == expected_read_bytes, err_msg.format(
|
amine@400
|
490 expected_read_bytes, total_read
|
amine@330
|
491 )
|
amine@330
|
492
|
amine@330
|
493
|
amine@400
|
494 class TestADSFactoryBufferAudioSource:
|
amine@400
|
495 def setup_method(self):
|
amine@330
|
496 self.signal = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
|
amine@330
|
497 self.ads = ADSFactory.ads(
|
amine@330
|
498 data_buffer=self.signal,
|
amine@330
|
499 sampling_rate=16,
|
amine@330
|
500 sample_width=2,
|
amine@330
|
501 channels=1,
|
amine@330
|
502 block_size=4,
|
amine@330
|
503 )
|
amine@330
|
504
|
amine@330
|
505 def test_ADS_BAS_sampling_rate(self):
|
amine@330
|
506 srate = self.ads.sampling_rate
|
amine@400
|
507 assert (
|
amine@400
|
508 srate == 16
|
amine@400
|
509 ), "Wrong sampling rate, expected: 16000, found: {0}".format(srate)
|
amine@330
|
510
|
amine@335
|
511 def test_ADS_BAS_sample_width(self):
|
amine@330
|
512 swidth = self.ads.sample_width
|
amine@400
|
513 assert (
|
amine@400
|
514 swidth == 2
|
amine@400
|
515 ), "Wrong sample width, expected: 2, found: {0}".format(swidth)
|
amine@330
|
516
|
amine@335
|
517 def test_ADS_BAS_channels(self):
|
amine@330
|
518 channels = self.ads.channels
|
amine@400
|
519 assert (
|
amine@400
|
520 channels == 1
|
amine@400
|
521 ), "Wrong number of channels, expected: 1, found: {0}".format(channels)
|
amine@330
|
522
|
amine@330
|
523 def test_Limiter_Recorder_Overlap_Deco_rewind_and_read(self):
|
amine@330
|
524 # Use arbitrary valid block_size and hop_size
|
amine@330
|
525 block_size = 5
|
amine@330
|
526 hop_size = 4
|
amine@330
|
527
|
amine@330
|
528 ads = ADSFactory.ads(
|
amine@330
|
529 data_buffer=self.signal,
|
amine@330
|
530 sampling_rate=16,
|
amine@330
|
531 sample_width=2,
|
amine@330
|
532 channels=1,
|
amine@330
|
533 max_time=0.80,
|
amine@330
|
534 block_size=block_size,
|
amine@330
|
535 hop_size=hop_size,
|
amine@330
|
536 record=True,
|
amine@330
|
537 )
|
amine@330
|
538
|
amine@330
|
539 # Read all available data overlapping blocks
|
amine@330
|
540 ads.open()
|
amine@330
|
541 i = 0
|
amine@330
|
542 while True:
|
amine@330
|
543 block = ads.read()
|
amine@330
|
544 if block is None:
|
amine@330
|
545 break
|
amine@330
|
546 i += 1
|
amine@330
|
547
|
amine@330
|
548 ads.rewind()
|
amine@330
|
549
|
amine@330
|
550 # Build a BufferAudioSource
|
amine@330
|
551 audio_source = BufferAudioSource(
|
amine@330
|
552 self.signal, ads.sampling_rate, ads.sample_width, ads.channels
|
amine@330
|
553 )
|
amine@330
|
554 audio_source.open()
|
amine@330
|
555
|
amine@400
|
556 # Compare all blocks read from OverlapADS to those read from an audio source with a manual position setting
|
amine@330
|
557 for j in range(i):
|
amine@330
|
558 tmp = audio_source.read(block_size)
|
amine@330
|
559 block = ads.read()
|
amine@400
|
560 assert (
|
amine@400
|
561 block == tmp
|
amine@400
|
562 ), "Unexpected block '{}' (N={}) read from OverlapADS".format(
|
amine@400
|
563 block, i
|
amine@330
|
564 )
|
amine@330
|
565 audio_source.position = (j + 1) * hop_size
|
amine@330
|
566
|
amine@330
|
567 ads.close()
|
amine@330
|
568 audio_source.close()
|
amine@330
|
569
|
amine@330
|
570
|
amine@400
|
571 class TestADSFactoryAlias:
|
amine@400
|
572 def setup_method(self):
|
amine@330
|
573 self.signal = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
|
amine@330
|
574
|
amine@330
|
575 def test_sampling_rate_alias(self):
|
amine@330
|
576 ads = ADSFactory.ads(
|
amine@330
|
577 data_buffer=self.signal,
|
amine@330
|
578 sr=16,
|
amine@330
|
579 sample_width=2,
|
amine@330
|
580 channels=1,
|
amine@330
|
581 block_dur=0.5,
|
amine@330
|
582 )
|
amine@330
|
583 srate = ads.sampling_rate
|
amine@400
|
584 assert (
|
amine@400
|
585 srate == 16
|
amine@400
|
586 ), "Wrong sampling rate, expected: 16000, found: {0}".format(srate)
|
amine@330
|
587
|
amine@330
|
588 def test_sampling_rate_duplicate(self):
|
amine@330
|
589 func = partial(
|
amine@330
|
590 ADSFactory.ads,
|
amine@330
|
591 data_buffer=self.signal,
|
amine@330
|
592 sr=16,
|
amine@330
|
593 sampling_rate=16,
|
amine@330
|
594 sample_width=2,
|
amine@330
|
595 channels=1,
|
amine@330
|
596 )
|
amine@400
|
597 with pytest.raises(DuplicateArgument):
|
amine@400
|
598 func()
|
amine@330
|
599
|
amine@330
|
600 def test_sample_width_alias(self):
|
amine@330
|
601 ads = ADSFactory.ads(
|
amine@330
|
602 data_buffer=self.signal,
|
amine@330
|
603 sampling_rate=16,
|
amine@330
|
604 sw=2,
|
amine@330
|
605 channels=1,
|
amine@330
|
606 block_dur=0.5,
|
amine@330
|
607 )
|
amine@330
|
608 swidth = ads.sample_width
|
amine@400
|
609 assert (
|
amine@400
|
610 swidth == 2
|
amine@400
|
611 ), "Wrong sample width, expected: 2, found: {0}".format(swidth)
|
amine@330
|
612
|
amine@330
|
613 def test_sample_width_duplicate(self):
|
amine@330
|
614 func = partial(
|
amine@330
|
615 ADSFactory.ads,
|
amine@330
|
616 data_buffer=self.signal,
|
amine@330
|
617 sampling_rate=16,
|
amine@330
|
618 sw=2,
|
amine@330
|
619 sample_width=2,
|
amine@330
|
620 channels=1,
|
amine@330
|
621 )
|
amine@400
|
622 with pytest.raises(DuplicateArgument):
|
amine@400
|
623 func()
|
amine@330
|
624
|
amine@330
|
625 def test_channels_alias(self):
|
amine@330
|
626 ads = ADSFactory.ads(
|
amine@330
|
627 data_buffer=self.signal,
|
amine@330
|
628 sampling_rate=16,
|
amine@330
|
629 sample_width=2,
|
amine@330
|
630 ch=1,
|
amine@330
|
631 block_dur=4,
|
amine@330
|
632 )
|
amine@330
|
633 channels = ads.channels
|
amine@400
|
634 assert (
|
amine@400
|
635 channels == 1
|
amine@400
|
636 ), "Wrong number of channels, expected: 1, found: {0}".format(channels)
|
amine@330
|
637
|
amine@330
|
638 def test_channels_duplicate(self):
|
amine@330
|
639 func = partial(
|
amine@330
|
640 ADSFactory.ads,
|
amine@330
|
641 data_buffer=self.signal,
|
amine@330
|
642 sampling_rate=16,
|
amine@330
|
643 sample_width=2,
|
amine@330
|
644 ch=1,
|
amine@330
|
645 channels=1,
|
amine@330
|
646 )
|
amine@400
|
647 with pytest.raises(DuplicateArgument):
|
amine@400
|
648 func()
|
amine@330
|
649
|
amine@330
|
650 def test_block_size_alias(self):
|
amine@330
|
651 ads = ADSFactory.ads(
|
amine@330
|
652 data_buffer=self.signal,
|
amine@330
|
653 sampling_rate=16,
|
amine@330
|
654 sample_width=2,
|
amine@330
|
655 channels=1,
|
amine@330
|
656 bs=8,
|
amine@330
|
657 )
|
amine@330
|
658 size = ads.block_size
|
amine@400
|
659 assert (
|
amine@400
|
660 size == 8
|
amine@400
|
661 ), "Wrong block_size using bs alias, expected: 8, found: {0}".format(
|
amine@400
|
662 size
|
amine@330
|
663 )
|
amine@330
|
664
|
amine@330
|
665 def test_block_size_duplicate(self):
|
amine@330
|
666 func = partial(
|
amine@330
|
667 ADSFactory.ads,
|
amine@330
|
668 data_buffer=self.signal,
|
amine@330
|
669 sampling_rate=16,
|
amine@330
|
670 sample_width=2,
|
amine@330
|
671 channels=1,
|
amine@330
|
672 bs=4,
|
amine@330
|
673 block_size=4,
|
amine@330
|
674 )
|
amine@400
|
675 with pytest.raises(DuplicateArgument):
|
amine@400
|
676 func()
|
amine@330
|
677
|
amine@330
|
678 def test_block_duration_alias(self):
|
amine@330
|
679 ads = ADSFactory.ads(
|
amine@330
|
680 data_buffer=self.signal,
|
amine@330
|
681 sampling_rate=16,
|
amine@330
|
682 sample_width=2,
|
amine@330
|
683 channels=1,
|
amine@330
|
684 bd=0.75,
|
amine@330
|
685 )
|
amine@330
|
686 size = ads.block_size
|
amine@400
|
687 err_msg = "Wrong block_size set with a block_dur alias 'bd', expected: 8, found: {0}"
|
amine@400
|
688 assert size == 12, err_msg.format(size)
|
amine@330
|
689
|
amine@330
|
690 def test_block_duration_duplicate(self):
|
amine@330
|
691 func = partial(
|
amine@330
|
692 ADSFactory.ads,
|
amine@330
|
693 data_buffer=self.signal,
|
amine@330
|
694 sampling_rate=16,
|
amine@330
|
695 sample_width=2,
|
amine@330
|
696 channels=1,
|
amine@330
|
697 bd=4,
|
amine@330
|
698 block_dur=4,
|
amine@330
|
699 )
|
amine@400
|
700 with pytest.raises(DuplicateArgument):
|
amine@400
|
701 func()
|
amine@330
|
702
|
amine@330
|
703 def test_block_size_duration_duplicate(self):
|
amine@330
|
704 func = partial(
|
amine@330
|
705 ADSFactory.ads,
|
amine@330
|
706 data_buffer=self.signal,
|
amine@330
|
707 sampling_rate=16,
|
amine@330
|
708 sample_width=2,
|
amine@330
|
709 channels=1,
|
amine@330
|
710 bd=4,
|
amine@330
|
711 bs=12,
|
amine@330
|
712 )
|
amine@400
|
713 with pytest.raises(DuplicateArgument):
|
amine@400
|
714 func()
|
amine@330
|
715
|
amine@330
|
716 def test_hop_duration_alias(self):
|
amine@330
|
717 ads = ADSFactory.ads(
|
amine@330
|
718 data_buffer=self.signal,
|
amine@330
|
719 sampling_rate=16,
|
amine@330
|
720 sample_width=2,
|
amine@330
|
721 channels=1,
|
amine@330
|
722 bd=0.75,
|
amine@330
|
723 hd=0.5,
|
amine@330
|
724 )
|
amine@330
|
725 size = ads.hop_size
|
amine@400
|
726 assert (
|
amine@400
|
727 size == 8
|
amine@400
|
728 ), "Wrong block_size using bs alias, expected: 8, found: {0}".format(
|
amine@400
|
729 size
|
amine@330
|
730 )
|
amine@330
|
731
|
amine@330
|
732 def test_hop_duration_duplicate(self):
|
amine@330
|
733 func = partial(
|
amine@330
|
734 ADSFactory.ads,
|
amine@330
|
735 data_buffer=self.signal,
|
amine@330
|
736 sampling_rate=16,
|
amine@330
|
737 sample_width=2,
|
amine@330
|
738 channels=1,
|
amine@330
|
739 bd=0.75,
|
amine@330
|
740 hd=0.5,
|
amine@330
|
741 hop_dur=0.5,
|
amine@330
|
742 )
|
amine@400
|
743 with pytest.raises(DuplicateArgument):
|
amine@400
|
744 func()
|
amine@330
|
745
|
amine@330
|
746 def test_hop_size_duration_duplicate(self):
|
amine@330
|
747 func = partial(
|
amine@330
|
748 ADSFactory.ads,
|
amine@330
|
749 data_buffer=self.signal,
|
amine@330
|
750 sampling_rate=16,
|
amine@330
|
751 sample_width=2,
|
amine@330
|
752 channels=1,
|
amine@330
|
753 bs=8,
|
amine@330
|
754 hs=4,
|
amine@330
|
755 hd=1,
|
amine@330
|
756 )
|
amine@400
|
757 with pytest.raises(DuplicateArgument):
|
amine@400
|
758 func()
|
amine@330
|
759
|
amine@330
|
760 def test_hop_size_greater_than_block_size(self):
|
amine@330
|
761 func = partial(
|
amine@330
|
762 ADSFactory.ads,
|
amine@330
|
763 data_buffer=self.signal,
|
amine@330
|
764 sampling_rate=16,
|
amine@330
|
765 sample_width=2,
|
amine@330
|
766 channels=1,
|
amine@330
|
767 bs=4,
|
amine@330
|
768 hs=8,
|
amine@330
|
769 )
|
amine@400
|
770 with pytest.raises(ValueError):
|
amine@400
|
771 func()
|
amine@330
|
772
|
amine@330
|
773 def test_filename_duplicate(self):
|
amine@330
|
774 func = partial(
|
amine@330
|
775 ADSFactory.ads,
|
amine@330
|
776 fn=dataset.one_to_six_arabic_16000_mono_bc_noise,
|
amine@330
|
777 filename=dataset.one_to_six_arabic_16000_mono_bc_noise,
|
amine@330
|
778 )
|
amine@400
|
779 with pytest.raises(DuplicateArgument):
|
amine@400
|
780 func()
|
amine@330
|
781
|
amine@330
|
782 def test_data_buffer_duplicate(self):
|
amine@330
|
783 func = partial(
|
amine@330
|
784 ADSFactory.ads,
|
amine@330
|
785 data_buffer=self.signal,
|
amine@330
|
786 db=self.signal,
|
amine@330
|
787 sampling_rate=16,
|
amine@330
|
788 sample_width=2,
|
amine@330
|
789 channels=1,
|
amine@330
|
790 )
|
amine@400
|
791 with pytest.raises(DuplicateArgument):
|
amine@400
|
792 func()
|
amine@330
|
793
|
amine@330
|
794 def test_max_time_alias(self):
|
amine@330
|
795 ads = ADSFactory.ads(
|
amine@330
|
796 data_buffer=self.signal,
|
amine@330
|
797 sampling_rate=16,
|
amine@330
|
798 sample_width=2,
|
amine@330
|
799 channels=1,
|
amine@330
|
800 mt=10,
|
amine@330
|
801 block_dur=0.5,
|
amine@330
|
802 )
|
amine@400
|
803 assert (
|
amine@400
|
804 ads.max_read == 10
|
amine@400
|
805 ), "Wrong AudioDataSource.max_read, expected: 10, found: {}".format(
|
amine@400
|
806 ads.max_read
|
amine@330
|
807 )
|
amine@330
|
808
|
amine@330
|
809 def test_max_time_duplicate(self):
|
amine@330
|
810 func = partial(
|
amine@330
|
811 ADSFactory.ads,
|
amine@330
|
812 data_buffer=self.signal,
|
amine@330
|
813 sampling_rate=16,
|
amine@330
|
814 sample_width=2,
|
amine@330
|
815 channels=1,
|
amine@330
|
816 mt=True,
|
amine@330
|
817 max_time=True,
|
amine@330
|
818 )
|
amine@400
|
819 with pytest.raises(DuplicateArgument):
|
amine@400
|
820 func()
|
amine@330
|
821
|
amine@330
|
822 def test_record_alias(self):
|
amine@330
|
823 ads = ADSFactory.ads(
|
amine@330
|
824 data_buffer=self.signal,
|
amine@330
|
825 sampling_rate=16,
|
amine@330
|
826 sample_width=2,
|
amine@330
|
827 channels=1,
|
amine@330
|
828 rec=True,
|
amine@330
|
829 block_dur=0.5,
|
amine@330
|
830 )
|
amine@400
|
831 assert ads.rewindable, "AudioDataSource.rewindable expected to be True"
|
amine@330
|
832
|
amine@330
|
833 def test_record_duplicate(self):
|
amine@330
|
834 func = partial(
|
amine@330
|
835 ADSFactory.ads,
|
amine@330
|
836 data_buffer=self.signal,
|
amine@330
|
837 sampling_rate=16,
|
amine@330
|
838 sample_width=2,
|
amine@330
|
839 channels=1,
|
amine@330
|
840 rec=True,
|
amine@330
|
841 record=True,
|
amine@330
|
842 )
|
amine@400
|
843 with pytest.raises(DuplicateArgument):
|
amine@400
|
844 func()
|
amine@330
|
845
|
amine@330
|
846 def test_Limiter_Recorder_Overlap_Deco_rewind_and_read_alias(self):
|
amine@330
|
847 # Use arbitrary valid block_size and hop_size
|
amine@330
|
848 block_size = 5
|
amine@330
|
849 hop_size = 4
|
amine@330
|
850
|
amine@330
|
851 ads = ADSFactory.ads(
|
amine@330
|
852 db=self.signal,
|
amine@330
|
853 sr=16,
|
amine@330
|
854 sw=2,
|
amine@330
|
855 ch=1,
|
amine@330
|
856 mt=0.80,
|
amine@330
|
857 bs=block_size,
|
amine@330
|
858 hs=hop_size,
|
amine@330
|
859 rec=True,
|
amine@330
|
860 )
|
amine@330
|
861
|
amine@330
|
862 # Read all available data overlapping blocks
|
amine@330
|
863 ads.open()
|
amine@330
|
864 i = 0
|
amine@330
|
865 while True:
|
amine@330
|
866 block = ads.read()
|
amine@330
|
867 if block is None:
|
amine@330
|
868 break
|
amine@330
|
869 i += 1
|
amine@330
|
870
|
amine@330
|
871 ads.rewind()
|
amine@330
|
872
|
amine@330
|
873 # Build a BufferAudioSource
|
amine@330
|
874 audio_source = BufferAudioSource(
|
amine@330
|
875 self.signal, ads.sampling_rate, ads.sample_width, ads.channels
|
amine@330
|
876 )
|
amine@330
|
877 audio_source.open()
|
amine@330
|
878
|
amine@400
|
879 # Compare all blocks read from AudioDataSource to those read from an audio source with manual position definition
|
amine@330
|
880 for j in range(i):
|
amine@330
|
881 tmp = audio_source.read(block_size)
|
amine@330
|
882 block = ads.read()
|
amine@400
|
883 assert (
|
amine@400
|
884 block == tmp
|
amine@400
|
885 ), "Unexpected block (N={0}) read from OverlapADS".format(i)
|
amine@330
|
886 audio_source.position = (j + 1) * hop_size
|
amine@330
|
887 ads.close()
|
amine@330
|
888 audio_source.close()
|
amine@330
|
889
|
amine@330
|
890
|
amine@330
|
891 def _read_all_data(reader):
|
amine@330
|
892 blocks = []
|
amine@330
|
893 while True:
|
amine@330
|
894 data = reader.read()
|
amine@330
|
895 if data is None:
|
amine@330
|
896 break
|
amine@330
|
897 blocks.append(data)
|
amine@330
|
898 return b"".join(blocks)
|
amine@330
|
899
|
amine@330
|
900
|
amine@400
|
901 @pytest.mark.parametrize(
|
amine@400
|
902 "file_id, max_read, size",
|
amine@400
|
903 [
|
amine@400
|
904 ("mono_400", 0.5, 16000), # mono
|
amine@400
|
905 ("3channel_400-800-1600", 0.5, 16000 * 3), # multichannel
|
amine@400
|
906 ],
|
amine@400
|
907 ids=["mono", "multichannel"],
|
amine@400
|
908 )
|
amine@400
|
909 def test_Limiter(file_id, max_read, size):
|
amine@400
|
910 input_wav = "tests/data/test_16KHZ_{}Hz.wav".format(file_id)
|
amine@400
|
911 input_raw = "tests/data/test_16KHZ_{}Hz.raw".format(file_id)
|
amine@400
|
912 with open(input_raw, "rb") as fp:
|
amine@400
|
913 expected = fp.read(size)
|
amine@330
|
914
|
amine@400
|
915 reader = AudioReader(input_wav, block_dur=0.1, max_read=max_read)
|
amine@400
|
916 reader.open()
|
amine@400
|
917 data = _read_all_data(reader)
|
amine@400
|
918 reader.close()
|
amine@400
|
919 assert data == expected
|
amine@330
|
920
|
amine@330
|
921
|
amine@400
|
922 @pytest.mark.parametrize(
|
amine@400
|
923 "file_id",
|
amine@400
|
924 [
|
amine@400
|
925 "mono_400", # mono
|
amine@400
|
926 "3channel_400-800-1600", # multichannel
|
amine@400
|
927 ],
|
amine@400
|
928 ids=["mono", "multichannel"],
|
amine@400
|
929 )
|
amine@400
|
930 def test_Recorder(file_id):
|
amine@400
|
931 input_wav = "tests/data/test_16KHZ_{}Hz.wav".format(file_id)
|
amine@400
|
932 input_raw = "tests/data/test_16KHZ_{}Hz.raw".format(file_id)
|
amine@400
|
933 with open(input_raw, "rb") as fp:
|
amine@400
|
934 expected = fp.read()
|
amine@400
|
935
|
amine@400
|
936 reader = AudioReader(input_wav, block_dur=0.1, record=True)
|
amine@400
|
937 reader.open()
|
amine@400
|
938 data = _read_all_data(reader)
|
amine@400
|
939 assert data == expected
|
amine@400
|
940
|
amine@400
|
941 # rewind many times
|
amine@400
|
942 for _ in range(3):
|
amine@400
|
943 reader.rewind()
|
amine@330
|
944 data = _read_all_data(reader)
|
amine@400
|
945 assert data == expected
|
amine@400
|
946 assert data == reader.data
|
amine@400
|
947 reader.close()
|
amine@330
|
948
|
amine@330
|
949
|
amine@400
|
950 @pytest.mark.parametrize(
|
amine@400
|
951 "file_id",
|
amine@400
|
952 [
|
amine@400
|
953 "mono_400", # mono
|
amine@400
|
954 "3channel_400-800-1600", # multichannel
|
amine@400
|
955 ],
|
amine@400
|
956 ids=["mono", "multichannel"],
|
amine@400
|
957 )
|
amine@400
|
958 def test_Recorder_alias(file_id):
|
amine@400
|
959 input_wav = "tests/data/test_16KHZ_{}Hz.wav".format(file_id)
|
amine@400
|
960 input_raw = "tests/data/test_16KHZ_{}Hz.raw".format(file_id)
|
amine@400
|
961 with open(input_raw, "rb") as fp:
|
amine@400
|
962 expected = fp.read()
|
amine@400
|
963
|
amine@400
|
964 reader = Recorder(input_wav, block_dur=0.1)
|
amine@400
|
965 reader.open()
|
amine@400
|
966 data = _read_all_data(reader)
|
amine@400
|
967 assert data == expected
|
amine@400
|
968
|
amine@400
|
969 # rewind many times
|
amine@400
|
970 for _ in range(3):
|
amine@400
|
971 reader.rewind()
|
amine@330
|
972 data = _read_all_data(reader)
|
amine@400
|
973 assert data == expected
|
amine@400
|
974 assert data == reader.data
|
amine@400
|
975 reader.close()
|