Skip to content

Commit d28752d

Browse files
Copilotlucemia
andcommitted
Add tests to improve coverage from 79% to 80%
Co-authored-by: lucemia <432851+lucemia@users.noreply.github.com>
1 parent 1318356 commit d28752d

File tree

6 files changed

+484
-1
lines changed

6 files changed

+484
-1
lines changed

coverage.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"""Tests for FFmpeg filter schema definitions."""
2+
3+
from ...common.schema import (
4+
FFMpegFilter,
5+
FFMpegIOType,
6+
StreamType,
7+
)
8+
9+
10+
def test_filter_pre_dict_property() -> None:
11+
"""Test that pre_dict property converts pre list to dict."""
12+
filter_def = FFMpegFilter(
13+
name="scale",
14+
description="Scale video",
15+
pre=(("width", "1280"), ("height", "720")),
16+
stream_typings_input=(),
17+
stream_typings_output=(),
18+
)
19+
20+
pre_dict = filter_def.pre_dict
21+
assert isinstance(pre_dict, dict)
22+
assert pre_dict["width"] == "1280"
23+
assert pre_dict["height"] == "720"
24+
25+
26+
def test_filter_to_def_property() -> None:
27+
"""Test that to_def property creates FFMpegFilterDef."""
28+
from ...common.schema import FFMpegFilterDef
29+
30+
filter_def = FFMpegFilter(
31+
name="scale",
32+
description="Scale video",
33+
stream_typings_input=(
34+
FFMpegIOType(name="default", type=StreamType.video),
35+
),
36+
stream_typings_output=(
37+
FFMpegIOType(name="default", type=StreamType.video),
38+
),
39+
)
40+
41+
simple_def = filter_def.to_def
42+
assert isinstance(simple_def, FFMpegFilterDef)
43+
assert simple_def.name == "scale"
44+
assert simple_def.typings_input == ("video",)
45+
assert simple_def.typings_output == ("video",)
46+
47+
48+
def test_filter_input_typings_source() -> None:
49+
"""Test input_typings for filter source (no inputs)."""
50+
filter_def = FFMpegFilter(
51+
name="color",
52+
description="Generate color",
53+
stream_typings_input=(),
54+
stream_typings_output=(),
55+
is_filter_source=True,
56+
)
57+
58+
typings = filter_def.input_typings
59+
assert isinstance(typings, set)
60+
assert len(typings) == 0
61+
62+
63+
def test_filter_input_typings_static() -> None:
64+
"""Test input_typings for static input filter."""
65+
filter_def = FFMpegFilter(
66+
name="scale",
67+
description="Scale video",
68+
stream_typings_input=(
69+
FFMpegIOType(name="default", type=StreamType.video),
70+
),
71+
stream_typings_output=(),
72+
)
73+
74+
typings = filter_def.input_typings
75+
assert StreamType.video in typings
76+
assert len(typings) == 1
77+
78+
79+
def test_filter_input_typings_dynamic_video() -> None:
80+
"""Test input_typings for dynamic input filter with video only."""
81+
filter_def = FFMpegFilter(
82+
name="concat",
83+
description="Concatenate videos",
84+
stream_typings_input=(),
85+
stream_typings_output=(),
86+
is_dynamic_input=True,
87+
formula_typings_input="video",
88+
)
89+
90+
typings = filter_def.input_typings
91+
assert StreamType.video in typings
92+
assert StreamType.audio not in typings
93+
94+
95+
def test_filter_input_typings_dynamic_audio() -> None:
96+
"""Test input_typings for dynamic input filter with audio only."""
97+
filter_def = FFMpegFilter(
98+
name="amix",
99+
description="Mix audio",
100+
stream_typings_input=(),
101+
stream_typings_output=(),
102+
is_dynamic_input=True,
103+
formula_typings_input="audio",
104+
)
105+
106+
typings = filter_def.input_typings
107+
assert StreamType.audio in typings
108+
assert StreamType.video not in typings
109+
110+
111+
def test_filter_input_typings_dynamic_both() -> None:
112+
"""Test input_typings for dynamic input filter with both video and audio."""
113+
filter_def = FFMpegFilter(
114+
name="amerge",
115+
description="Merge audio and video",
116+
stream_typings_input=(),
117+
stream_typings_output=(),
118+
is_dynamic_input=True,
119+
formula_typings_input="videoaudio",
120+
)
121+
122+
typings = filter_def.input_typings
123+
assert StreamType.video in typings
124+
assert StreamType.audio in typings
125+
126+
127+
def test_filter_output_typings_static() -> None:
128+
"""Test output_typings for static output filter."""
129+
filter_def = FFMpegFilter(
130+
name="scale",
131+
description="Scale video",
132+
stream_typings_input=(),
133+
stream_typings_output=(
134+
FFMpegIOType(name="default", type=StreamType.video),
135+
),
136+
)
137+
138+
typings = filter_def.output_typings
139+
assert StreamType.video in typings
140+
assert len(typings) == 1
141+
142+
143+
def test_filter_output_typings_multiple() -> None:
144+
"""Test output_typings for filter with multiple outputs."""
145+
filter_def = FFMpegFilter(
146+
name="split",
147+
description="Split video",
148+
stream_typings_input=(),
149+
stream_typings_output=(
150+
FFMpegIOType(name="output1", type=StreamType.video),
151+
FFMpegIOType(name="output2", type=StreamType.video),
152+
),
153+
)
154+
155+
typings = filter_def.output_typings
156+
assert StreamType.video in typings
157+
assert len(typings) == 1 # Both outputs are video, so set has 1 element
158+
159+
160+
def test_filter_output_typings_dynamic() -> None:
161+
"""Test output_typings for dynamic output filter."""
162+
filter_def = FFMpegFilter(
163+
name="split",
164+
description="Split stream",
165+
stream_typings_input=(),
166+
stream_typings_output=(),
167+
is_dynamic_output=True,
168+
formula_typings_output="video",
169+
)
170+
171+
typings = filter_def.output_typings
172+
assert StreamType.video in typings

src/ffmpeg/dag/global_runnable/tests/test_runnable.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,69 @@ async def test_run_async_awaitable_is_awaitable(self) -> None:
343343
process = await result
344344
assert isinstance(process, asyncio.subprocess.Process)
345345
await process.wait()
346+
347+
348+
class TestMergeOutputs:
349+
"""Tests for the merge_outputs method."""
350+
351+
def test_merge_outputs_basic(self) -> None:
352+
"""Test that merge_outputs combines multiple output streams."""
353+
video = input("input.mp4").video
354+
output1 = video.output(filename="output1.mp4")
355+
output2 = video.output(filename="output2.webm")
356+
357+
merged = output1.merge_outputs(output2)
358+
359+
# Verify that the merged output is a GlobalStream
360+
from ...nodes import GlobalStream
361+
362+
assert isinstance(merged, GlobalStream)
363+
364+
# Verify compilation includes both outputs
365+
args = merged.compile()
366+
assert "output1.mp4" in args
367+
assert "output2.webm" in args
368+
369+
def test_merge_outputs_multiple(self) -> None:
370+
"""Test that merge_outputs can handle multiple streams."""
371+
video = input("input.mp4").video
372+
output1 = video.output(filename="out1.mp4")
373+
output2 = video.output(filename="out2.mp4")
374+
output3 = video.output(filename="out3.mp4")
375+
376+
merged = output1.merge_outputs(output2, output3)
377+
378+
# Verify all three outputs are in the compiled command
379+
args = merged.compile()
380+
assert "out1.mp4" in args
381+
assert "out2.mp4" in args
382+
assert "out3.mp4" in args
383+
384+
385+
class TestOverwriteOutput:
386+
"""Tests for the overwrite_output method."""
387+
388+
def test_overwrite_output_adds_y_flag(self) -> None:
389+
"""Test that overwrite_output adds the -y flag."""
390+
stream = input("input.mp4").output(filename="output.mp4")
391+
overwrite_stream = stream.overwrite_output()
392+
393+
# Verify the compiled command includes -y
394+
args = overwrite_stream.compile()
395+
assert "-y" in args
396+
397+
398+
class TestRunWithInput:
399+
"""Tests for run method with input parameter."""
400+
401+
def test_run_with_stdin_input(self) -> None:
402+
"""Test that run method accepts input parameter."""
403+
import inspect
404+
405+
stream = input("test.mp4").output(filename="output.mp4")
406+
sig = inspect.signature(stream.run)
407+
408+
assert "input" in sig.parameters
409+
# Check the type annotation
410+
param = sig.parameters["input"]
411+
assert param.default is None
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Tests for audio channel layout definitions."""
2+
3+
from ..channel_layout import CHANNEL_LAYOUT
4+
5+
6+
def test_channel_layout_basic() -> None:
7+
"""Test basic channel layout definitions."""
8+
assert CHANNEL_LAYOUT["mono"] == 1
9+
assert CHANNEL_LAYOUT["stereo"] == 2
10+
11+
12+
def test_channel_layout_surround() -> None:
13+
"""Test surround sound channel layouts."""
14+
assert CHANNEL_LAYOUT["5.1"] == 6
15+
assert CHANNEL_LAYOUT["7.1"] == 8
16+
17+
18+
def test_channel_layout_complex() -> None:
19+
"""Test complex channel layouts."""
20+
assert CHANNEL_LAYOUT["5.1.2"] == 8
21+
assert CHANNEL_LAYOUT["7.1.4"] == 12
22+
assert CHANNEL_LAYOUT["22.2"] == 24
23+
24+
25+
def test_channel_layout_special() -> None:
26+
"""Test special channel layouts."""
27+
assert CHANNEL_LAYOUT["quad"] == 4
28+
assert CHANNEL_LAYOUT["hexagonal"] == 6
29+
assert CHANNEL_LAYOUT["octagonal"] == 8
30+
assert CHANNEL_LAYOUT["hexadecagonal"] == 16
31+
32+
33+
def test_channel_layout_downmix() -> None:
34+
"""Test downmix channel layout."""
35+
assert CHANNEL_LAYOUT["downmix"] == 2
36+
37+
38+
def test_channel_layout_all_defined() -> None:
39+
"""Test that all channel layouts are defined with valid channel counts."""
40+
for layout, channels in CHANNEL_LAYOUT.items():
41+
assert isinstance(layout, str)
42+
assert isinstance(channels, int)
43+
assert channels > 0
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""Tests for FFmpeg exception classes."""
2+
3+
import pytest
4+
5+
from ..exceptions import (
6+
FFMpegError,
7+
FFMpegExecuteError,
8+
FFMpegTypeError,
9+
FFMpegValueError,
10+
)
11+
12+
13+
def test_ffmpeg_error_base() -> None:
14+
"""Test base FFMpegError exception."""
15+
error = FFMpegError("Test error message")
16+
assert str(error) == "Test error message"
17+
assert isinstance(error, Exception)
18+
19+
20+
def test_ffmpeg_type_error() -> None:
21+
"""Test FFMpegTypeError exception."""
22+
error = FFMpegTypeError("Invalid type provided")
23+
assert str(error) == "Invalid type provided"
24+
assert isinstance(error, FFMpegError)
25+
assert isinstance(error, TypeError)
26+
27+
28+
def test_ffmpeg_value_error() -> None:
29+
"""Test FFMpegValueError exception."""
30+
error = FFMpegValueError("Invalid value provided")
31+
assert str(error) == "Invalid value provided"
32+
assert isinstance(error, FFMpegError)
33+
assert isinstance(error, ValueError)
34+
35+
36+
def test_ffmpeg_execute_error() -> None:
37+
"""Test FFMpegExecuteError exception."""
38+
cmd = "ffmpeg -i input.mp4 output.mp4"
39+
stdout = b"Output from ffmpeg"
40+
stderr = b"Error from ffmpeg"
41+
retcode = 1
42+
43+
error = FFMpegExecuteError(retcode, cmd, stdout, stderr)
44+
45+
assert error.retcode == retcode
46+
assert error.cmd == cmd
47+
assert error.stdout == stdout
48+
assert error.stderr == stderr
49+
assert isinstance(error, FFMpegError)
50+
51+
52+
def test_ffmpeg_execute_error_message() -> None:
53+
"""Test FFMpegExecuteError error message format."""
54+
cmd = "ffmpeg -i input.mp4"
55+
stdout = b""
56+
stderr = b"Invalid file format"
57+
retcode = 1
58+
59+
error = FFMpegExecuteError(retcode, cmd, stdout, stderr)
60+
error_msg = str(error)
61+
62+
assert cmd in error_msg
63+
assert "error" in error_msg.lower()
64+
assert "stderr" in error_msg.lower()
65+
66+
67+
def test_ffmpeg_execute_error_with_none_retcode() -> None:
68+
"""Test FFMpegExecuteError with None return code."""
69+
cmd = "ffmpeg -i input.mp4"
70+
stdout = b""
71+
stderr = b"Process terminated"
72+
retcode = None
73+
74+
error = FFMpegExecuteError(retcode, cmd, stdout, stderr)
75+
76+
assert error.retcode is None
77+
assert error.cmd == cmd
78+
79+
80+
def test_ffmpeg_errors_can_be_raised() -> None:
81+
"""Test that all FFmpeg exceptions can be raised and caught."""
82+
with pytest.raises(FFMpegError):
83+
raise FFMpegError("Test error")
84+
85+
with pytest.raises(FFMpegTypeError):
86+
raise FFMpegTypeError("Test type error")
87+
88+
with pytest.raises(FFMpegValueError):
89+
raise FFMpegValueError("Test value error")
90+
91+
with pytest.raises(FFMpegExecuteError):
92+
raise FFMpegExecuteError(1, "cmd", b"", b"")
93+
94+
95+
def test_ffmpeg_error_hierarchy() -> None:
96+
"""Test the exception hierarchy."""
97+
# FFMpegTypeError is both FFMpegError and TypeError
98+
with pytest.raises(FFMpegError):
99+
raise FFMpegTypeError("Type error")
100+
101+
with pytest.raises(TypeError):
102+
raise FFMpegTypeError("Type error")
103+
104+
# FFMpegValueError is both FFMpegError and ValueError
105+
with pytest.raises(FFMpegError):
106+
raise FFMpegValueError("Value error")
107+
108+
with pytest.raises(ValueError):
109+
raise FFMpegValueError("Value error")

0 commit comments

Comments
 (0)