Skip to content

Commit 52bdc77

Browse files
committed
Fix tests for Windows
On Windows there are several different behaviors around subprocess. - It cannot pickle locally defined functions/classes. (I thought this was also the case for Linux/macOS, but seems not) - Module-level global variables do not inherit their values from the parent process. - Even for simple async pipeline with single concurrency, the output order can be changed. - In subprocess call, quoting is different. - When writing to a NamedTemporaryFile, the file must be closed before writing.
1 parent 97d30d3 commit 52bdc77

15 files changed

Lines changed: 101 additions & 65 deletions

tests/spdl_unittest/cuda/buffer_transfer_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
DEFAULT_CUDA = 0
2424

2525
CMDS = {
26-
"audio": f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i 'sine=frequency=1000:sample_rate=48000:duration=3' -c:a pcm_s16le sample.wav",
26+
"audio": f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i \"sine=frequency=1000:sample_rate=48000:duration=3\" -c:a pcm_s16le sample.wav",
2727
"video": f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i testsrc -frames:v 1000 sample.mp4",
2828
"image": f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i color=0x000000,format=gray -frames:v 1 sample.png",
2929
}

tests/spdl_unittest/dataloader/iterator_test.py

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -611,55 +611,59 @@ def done():
611611
assert result.status == _Status.ITERATION_FINISHED
612612

613613

614-
def test_terate_in_subprocess_initializer_failure():
615-
def src_fn() -> Iterable[int]:
616-
return SourceIterable(10)
614+
def _src1() -> Iterable[int]:
615+
return SourceIterable(10)
617616

618-
def fail() -> None:
619-
raise ValueError("Failed!")
620617

618+
def _init1() -> None:
619+
raise ValueError("Failed!")
620+
621+
622+
def test_iterate_in_subprocess_initializer_failure():
621623
with pytest.raises(RuntimeError, match=r"Initializer failed"):
622-
iterate_in_subprocess(src_fn, buffer_size=1, timeout=3, initializer=fail)
624+
iterate_in_subprocess(_src1, buffer_size=1, timeout=3, initializer=_init1)
623625

624626

625-
def test_iterate_in_subprocess_iterator_initialize_failure():
626-
def src_fn() -> Iterator[int]:
627+
def _src2() -> Iterator[int]:
628+
if True:
627629
raise ValueError("Failed!")
628-
return SourceIterable(10)
630+
return SourceIterable(10)
631+
629632

633+
def test_iterate_in_subprocess_iterator_initialize_failure():
630634
with pytest.raises(RuntimeError, match=r"Failed to create the iterable"):
631-
iterate_in_subprocess(src_fn, buffer_size=1, timeout=3)
635+
iterate_in_subprocess(_src2, buffer_size=1, timeout=3)
632636

633637

634-
def test_iterate_in_subprocess_generator_fail():
638+
def _src3() -> Iterable[int]:
635639
class SourceIterableFails(SourceIterable):
636640
def __iter__(self) -> Iterator[int]:
637641
raise ValueError("Failed!")
638642
yield from range(self.n)
639643

640-
def src_fn() -> Iterable[int]:
641-
return SourceIterableFails(10)
644+
return SourceIterableFails(10)
645+
642646

643-
ite = iter(iterate_in_subprocess(src_fn, buffer_size=1, timeout=3))
647+
def test_iterate_in_subprocess_generator_fail():
648+
ite = iter(iterate_in_subprocess(_src3, buffer_size=1, timeout=3))
644649

645650
with pytest.raises(RuntimeError, match=r"Failed to fetch the next item"):
646651
next(ite)
647652

648653

649-
def test_iterate_in_subprocess_fail_after_n():
650-
N = 10
651-
654+
def _src4() -> Iterable[int]:
652655
class SourceIterableFails(SourceIterable):
653656
def __iter__(self) -> Iterator[int]:
654657
for v in range(self.n):
655658
yield v
656659
if v == 2:
657660
raise ValueError("Failed!")
658661

659-
def src_fn() -> Iterable[int]:
660-
return SourceIterableFails(N)
662+
return SourceIterableFails(10)
663+
661664

662-
ite = iter(iterate_in_subprocess(src_fn, buffer_size=1, timeout=3))
665+
def test_iterate_in_subprocess_fail_after_n():
666+
ite = iter(iterate_in_subprocess(_src4, buffer_size=1, timeout=3))
663667
assert next(ite) == 0
664668
assert next(ite) == 1
665669
assert next(ite) == 2
@@ -668,13 +672,14 @@ def src_fn() -> Iterable[int]:
668672
next(ite)
669673

670674

675+
def _src5(N) -> Iterable[int]:
676+
return SourceIterable(N)
677+
678+
671679
def test_iterate_in_subprocess_success():
672680
N = 3
673681

674-
def src_fn() -> Iterable[int]:
675-
return SourceIterable(N)
676-
677-
hyp = list(iterate_in_subprocess(src_fn, buffer_size=-1, timeout=3))
682+
hyp = list(iterate_in_subprocess(partial(_src5, N), buffer_size=-1, timeout=3))
678683
assert hyp == list(range(N))
679684

680685

@@ -684,13 +689,12 @@ def __iter__(self):
684689
yield 0
685690

686691

687-
def test_iterate_in_subprocess_timeout():
688-
N = 3
692+
def _src6() -> Iterable[int]:
693+
return SleepSourceIterable(3)
689694

690-
def src_fn() -> Iterable[int]:
691-
return SleepSourceIterable(N)
692695

693-
iterable = iterate_in_subprocess(src_fn, buffer_size=-1, timeout=3)
696+
def test_iterate_in_subprocess_timeout():
697+
iterable = iterate_in_subprocess(_src6, buffer_size=-1, timeout=3)
694698
iterator = iter(iterable)
695699
with pytest.raises(
696700
RuntimeError, match=r"The worker process did not produce any data for"

tests/spdl_unittest/dataloader/pipeline_test.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import platform
1212
import random
1313
import re
14+
import sys
1415
import threading
1516
import time
1617
from collections.abc import Iterator
@@ -19,6 +20,7 @@
1920
from functools import partial
2021
from multiprocessing import Process
2122
from typing import TypeVar
23+
from unittest import skipIf
2224

2325
import pytest
2426
from spdl.pipeline import (
@@ -344,7 +346,7 @@ async def test(concurrency):
344346
# 1, 2, 3 and 4 in output_queue.
345347
remain, output = asyncio.run(test(4))
346348
assert remain == []
347-
assert output == [1, 2, 3, 4]
349+
assert set(output) == {1, 2, 3, 4}
348350

349351

350352
def test_async_pipe_concurrency_throughput():
@@ -377,7 +379,8 @@ async def test(concurrency):
377379

378380
result = _flush_aqueue(output_queue)
379381

380-
assert result == ref
382+
assert set(result) == set(ref)
383+
assert result[-1] == ref[-1] == _EOF
381384

382385
return elapsed
383386

@@ -1944,11 +1947,20 @@ def test_run_pipeline_in_subprocess_state():
19441947
assert src.src.seed == 2
19451948

19461949

1947-
def _validate_pipeline_id(val: int) -> Iterator[int]:
1948-
assert _build._PIPELINE_ID == val
1949-
yield 0
1950+
class _validate_pipeline_id:
1951+
def __init__(self, val: int) -> None:
1952+
self.val = val
1953+
1954+
def __iter__(self) -> Iterator[int]:
1955+
if _build._PIPELINE_ID != self.val:
1956+
raise AssertionError(f"{_build._PIPELINE_ID=} != {self.val=}")
1957+
yield 0
19501958

19511959

1960+
# TODO: Fix this.
1961+
@skipIf(
1962+
sys.platform == "win32", "On Windows module-level global variable is not inherited."
1963+
)
19521964
def test_run_pipeline_in_subprocess_pipeline_id():
19531965
"""The pipeline construdted in a subprocess inherits the global ID from the main process"""
19541966
# Set to a number that's not zero and something unlikely to happen during the testing

tests/spdl_unittest/fixture.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ class SrcInfo:
5050

5151
def get_sample(cmd: str) -> SrcInfo:
5252
samples = get_samples(cmd)
53-
assert len(samples) == 1
53+
assert len(samples) == 1, f"There must be one sample. Found: {len(samples)}"
5454
return samples[0]
5555

5656

5757
def get_samples(cmd: str) -> list[SrcInfo]:
5858
tmp_dir = TemporaryDirectory()
5959
tmp_path = Path(tmp_dir.name)
6060

61-
_run_in_tmpdir(cmd, tmp_path)
61+
_run_in_tmpdir(cmd.strip(), tmp_path)
6262
return [SrcInfo(str(f), tmp_dir) for f in tmp_path.glob("**/*") if f.is_file()]
6363

6464

tests/spdl_unittest/io/async_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def _test_decode(demux_fn, timestamps):
5252

5353
def test_decode_audio_clips():
5454
"""Can decode audio clips."""
55-
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i 'sine=frequency=1000:sample_rate=48000:duration=3' -c:a pcm_s16le sample.wav"
55+
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i sine=frequency=1000:sample_rate=48000:duration=3 -c:a pcm_s16le sample.wav"
5656
sample = get_sample(cmd)
5757

5858
def _test():
@@ -71,7 +71,7 @@ def _test():
7171

7272
def test_decode_audio_clips_num_frames():
7373
"""Can decode audio clips with padding/dropping."""
74-
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i 'sine=frequency=1000:sample_rate=16000:duration=1' -c:a pcm_s16le sample.wav"
74+
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i sine=frequency=1000:sample_rate=16000:duration=1 -c:a pcm_s16le sample.wav"
7575
sample = get_sample(cmd)
7676

7777
def _decode(src, num_frames=None):
@@ -284,7 +284,7 @@ def _test(src):
284284

285285
def test_convert_audio():
286286
"""convert_frames can convert AudioFrames to Buffer"""
287-
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i 'sine=frequency=1000:sample_rate=48000:duration=3' -c:a pcm_s16le sample.wav"
287+
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i sine=frequency=1000:sample_rate=48000:duration=3 -c:a pcm_s16le sample.wav"
288288
sample = get_sample(cmd)
289289

290290
def _test(src):

tests/spdl_unittest/io/audio_decoding_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ def test_load_audio(sample_fmt):
3232
# fmt: off
3333
cmd = f"""
3434
{FFMPEG_CLI} -hide_banner -y \
35-
-f lavfi -i 'sine=sample_rate=8000:frequency=305:duration=5' \
36-
-f lavfi -i 'sine=sample_rate=8000:frequency=300:duration=5' \
35+
-f lavfi -i sine=sample_rate=8000:frequency=305:duration=5 \
36+
-f lavfi -i sine=sample_rate=8000:frequency=300:duration=5 \
3737
-filter_complex amerge -c:a pcm_s16le sample.wav
3838
"""
3939
# fmt: on
@@ -58,8 +58,8 @@ def test_batch_audio_conversion():
5858
# fmt: off
5959
cmd = f"""
6060
{FFMPEG_CLI} -hide_banner -y \
61-
-f lavfi -i 'sine=sample_rate=8000:frequency=305:duration=5' \
62-
-f lavfi -i 'sine=sample_rate=8000:frequency=300:duration=5' \
61+
-f lavfi -i sine=sample_rate=8000:frequency=305:duration=5 \
62+
-f lavfi -i sine=sample_rate=8000:frequency=300:duration=5 \
6363
-filter_complex amerge -c:a pcm_s16le sample.wav
6464
"""
6565
# fmt: on

tests/spdl_unittest/io/audio_encoding_test.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ def test_encode_audio_integer(sample_fmt):
4444
ref = np.random.randint(ii.min, ii.max, size=shape, dtype=dtype)
4545

4646
with NamedTemporaryFile(suffix=".wav") as f:
47+
f.close() # for windows
48+
4749
muxer = spdl.io.Muxer(f.name)
4850
encoder = muxer.add_encode_stream(
4951
config=spdl.io.audio_encode_config(
@@ -99,6 +101,8 @@ def test_encode_audio_float(sample_fmt):
99101
ref = np.random.rand(*shape).astype(dtype=dtype)
100102

101103
with NamedTemporaryFile(suffix=".wav") as f:
104+
f.close() # for windows
105+
102106
muxer = spdl.io.Muxer(f.name)
103107
encoder = muxer.add_encode_stream(
104108
config=spdl.io.audio_encode_config(
@@ -154,6 +158,8 @@ def test_encode_audio_integer_planar(sample_fmt):
154158
ref = np.random.randint(ii.min, ii.max, size=shape, dtype=dtype)
155159

156160
with NamedTemporaryFile(suffix=".nut") as f:
161+
f.close() # windows
162+
157163
muxer = spdl.io.Muxer(f.name)
158164
encoder = muxer.add_encode_stream(
159165
config=spdl.io.audio_encode_config(
@@ -219,13 +225,19 @@ def test_encode_audio_smoke_test(ext, sample_fmt):
219225
ref = np.random.random(shape).astype(dtype)
220226

221227
with NamedTemporaryFile(suffix=ext) as f:
228+
f.close() # for windows
229+
222230
muxer = spdl.io.Muxer(f.name)
223231
encoder = muxer.add_encode_stream(
224232
config=spdl.io.audio_encode_config(
225233
num_channels=num_channels,
226234
sample_fmt=sample_fmt,
227235
sample_rate=sample_rate,
228236
),
237+
# on Windows, the default might be mp3_mf, which
238+
# does not support planar format.
239+
# So we specify lame
240+
encoder="libmp3lame" if ext == ".mp3" else None
229241
)
230242

231243
frame_size = encoder.frame_size or 1024
@@ -259,8 +271,8 @@ def test_remux_audio():
259271
# fmt: off
260272
cmd = f"""
261273
{FFMPEG_CLI} -hide_banner -y \
262-
-f lavfi -i 'sine=sample_rate=8000:frequency=305:duration=5' \
263-
-f lavfi -i 'sine=sample_rate=8000:frequency=300:duration=5' \
274+
-f lavfi -i sine=sample_rate=8000:frequency=305:duration=5 \
275+
-f lavfi -i sine=sample_rate=8000:frequency=300:duration=5 \
264276
-filter_complex amerge -c:a pcm_s16le sample.wav
265277
"""
266278
# fmt: on
@@ -269,6 +281,8 @@ def test_remux_audio():
269281
demuxer = spdl.io.Demuxer(sample.path)
270282

271283
with NamedTemporaryFile(suffix=".wav") as f:
284+
f.close() # for windows
285+
272286
muxer = spdl.io.Muxer(f.name)
273287
muxer.add_remux_stream(demuxer.audio_codec)
274288

tests/spdl_unittest/io/configs_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
def test_demux_config_smoketest():
1616
""""""
17-
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i 'sine=frequency=1000:sample_rate=48000:duration=3' -c:a pcm_s16le sample.wav"
17+
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i sine=frequency=1000:sample_rate=48000:duration=3 -c:a pcm_s16le sample.wav"
1818
sample = get_sample(cmd)
1919

2020
demux_config = spdl.io.demux_config()
@@ -32,7 +32,7 @@ def test_demux_config_smoketest():
3232

3333
def test_demux_config_headless():
3434
"""Providing demux_config allows to load headeless audio"""
35-
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i 'sine=frequency=1000:sample_rate=48000:duration=3' -f s16le -c:a pcm_s16le sample.raw"
35+
cmd = f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i sine=frequency=1000:sample_rate=48000:duration=3 -f s16le -c:a pcm_s16le sample.raw"
3636
sample = get_sample(cmd)
3737

3838
with pytest.raises(RuntimeError):

tests/spdl_unittest/io/demuxer_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
def test_demuxer_query_codec():
1515
"""Can fetch the codec properly."""
1616
cmd = (
17-
f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i testsrc -f lavfi -i sine -t 5 sample.mp4",
17+
f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i testsrc -f lavfi -i sine -t 5 sample.mp4"
1818
)
1919

2020
sample = get_sample(cmd)
@@ -40,7 +40,7 @@ def test_demuxer_query_codec():
4040
def test_demuxer_query_stream_index():
4141
"""Can fetch the stream index properly."""
4242
cmd = (
43-
f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i testsrc -f lavfi -i sine -t 5 sample.mp4",
43+
f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i testsrc -f lavfi -i sine -t 5 sample.mp4"
4444
)
4545

4646
sample = get_sample(cmd)
@@ -50,7 +50,7 @@ def test_demuxer_query_stream_index():
5050
assert demuxer.audio_stream_index == 1
5151

5252
cmd = (
53-
f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i sine -f lavfi -i testsrc -t 5 -map 0:a -map 1:v sample.mp4",
53+
f"{FFMPEG_CLI} -hide_banner -y -f lavfi -i sine -f lavfi -i testsrc -t 5 -map 0:a -map 1:v sample.mp4"
5454
)
5555

5656
sample = get_sample(cmd)

tests/spdl_unittest/io/encoding_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def test_encode_image_parity_simple(pix_fmt, torch_tensor):
2626
ref = np.random.randint(256, size=shape, dtype=np.uint8)
2727

2828
with NamedTemporaryFile(suffix=".png") as f:
29+
f.close() # for windows
2930
spdl.io.save_image(
3031
f.name,
3132
torch.from_numpy(ref) if torch_tensor else ref,
@@ -44,6 +45,7 @@ def test_encode_image_parity_png_gray16be():
4445
ref = ref.byteswap()
4546

4647
with NamedTemporaryFile(suffix=".png") as f:
48+
f.close() # for windows
4749
spdl.io.save_image(
4850
f.name,
4951
ref,
@@ -62,6 +64,7 @@ def test_encode_image_parity_png_gray16be():
6264
def _test_rejects(pix_fmt, dtype):
6365
data = np.ones((32, 64), dtype=dtype)
6466
with NamedTemporaryFile(suffix=".png") as f:
67+
f.close() # for windows
6568
with pytest.raises(ValueError):
6669
spdl.io.save_image(
6770
f.name,

0 commit comments

Comments
 (0)