chore: fix PT009 ruff rule

This commit is contained in:
bastimeyer 2023-02-16 00:51:25 +01:00 committed by Forrest
parent cc7159611f
commit 6c2d710eda
28 changed files with 1113 additions and 1020 deletions

View File

@ -5,6 +5,8 @@ import unittest
from pathlib import Path
from unittest.mock import Mock, call, patch
import pytest
from streamlink_cli.output import FileOutput, PlayerOutput
from tests import posix_only, windows_only
@ -22,15 +24,15 @@ class TestFileOutput(unittest.TestCase):
mock_path = Mock(spec=Path("foo", "bar"))
fo_main, fo_record = self.subject(mock_path, mock_stdout)
self.assertEqual(fo_main.opened, False)
self.assertIs(fo_main.filename, mock_path)
self.assertIs(fo_main.fd, None)
self.assertIs(fo_main.record, fo_record)
assert not fo_main.opened
assert fo_main.filename is mock_path
assert fo_main.fd is None
assert fo_main.record is fo_record
self.assertEqual(fo_main.record.opened, False)
self.assertIs(fo_main.record.filename, None)
self.assertIs(fo_main.record.fd, mock_stdout)
self.assertIs(fo_main.record.record, None)
assert not fo_main.record.opened
assert fo_main.record.filename is None
assert fo_main.record.fd is mock_stdout
assert fo_main.record.record is None
def test_early_close(self, mock_stdout: Mock):
mock_path = Mock(spec=Path("foo", "bar"))
@ -42,9 +44,8 @@ class TestFileOutput(unittest.TestCase):
mock_path = Mock(spec=Path("foo", "bar"))
fo_main, fo_record = self.subject(mock_path, mock_stdout)
with self.assertRaises(OSError) as cm:
with pytest.raises(OSError, match=r"^Output is not opened$"):
fo_main.write(b"foo")
self.assertEqual(str(cm.exception), "Output is not opened")
def _test_open(self, mock_open: Mock, mock_stdout: Mock):
mock_path = Mock(spec=Path("foo", "bar"))
@ -52,20 +53,20 @@ class TestFileOutput(unittest.TestCase):
fo_main, fo_record = self.subject(mock_path, mock_stdout)
fo_main.open()
self.assertEqual(fo_main.opened, True)
self.assertEqual(fo_main.record.opened, True)
self.assertEqual(mock_path.parent.mkdir.call_args_list, [call(parents=True, exist_ok=True)])
self.assertIs(fo_main.fd, mock_fd)
assert fo_main.opened
assert fo_main.record.opened
assert mock_path.parent.mkdir.call_args_list == [call(parents=True, exist_ok=True)]
assert fo_main.fd is mock_fd
fo_main.write(b"foo")
self.assertEqual(mock_fd.write.call_args_list, [call(b"foo")])
self.assertEqual(mock_stdout.write.call_args_list, [call(b"foo")])
assert mock_fd.write.call_args_list == [call(b"foo")]
assert mock_stdout.write.call_args_list == [call(b"foo")]
fo_main.close()
self.assertEqual(mock_fd.close.call_args_list, [call()])
self.assertEqual(mock_stdout.close.call_args_list, [])
self.assertEqual(fo_main.opened, False)
self.assertEqual(fo_main.record.opened, False)
assert mock_fd.close.call_args_list == [call()]
assert mock_stdout.close.call_args_list == []
assert not fo_main.opened
assert not fo_main.record.opened
return mock_path
@ -79,67 +80,47 @@ class TestFileOutput(unittest.TestCase):
@patch("builtins.open")
def test_open_windows(self, mock_open: Mock, mock_msvcrt: Mock, mock_stdout: Mock):
mock_path = self._test_open(mock_open, mock_stdout)
self.assertEqual(mock_msvcrt.setmode.call_args_list, [
assert mock_msvcrt.setmode.call_args_list == [
call(mock_stdout.fileno(), os.O_BINARY),
call(mock_open(mock_path, "wb").fileno(), os.O_BINARY),
])
]
class TestPlayerOutput(unittest.TestCase):
def test_supported_player_generic(self):
self.assertEqual("vlc",
PlayerOutput.supported_player("vlc"))
self.assertEqual("mpv",
PlayerOutput.supported_player("mpv"))
self.assertEqual("potplayer",
PlayerOutput.supported_player("potplayermini.exe"))
assert PlayerOutput.supported_player("vlc") == "vlc"
assert PlayerOutput.supported_player("mpv") == "mpv"
assert PlayerOutput.supported_player("potplayermini.exe") == "potplayer"
@patch("streamlink_cli.output.os.path.basename", new=ntpath.basename)
def test_supported_player_win32(self):
self.assertEqual("mpv",
PlayerOutput.supported_player("C:\\MPV\\mpv.exe"))
self.assertEqual("vlc",
PlayerOutput.supported_player("C:\\VLC\\vlc.exe"))
self.assertEqual("potplayer",
PlayerOutput.supported_player("C:\\PotPlayer\\PotPlayerMini64.exe"))
assert PlayerOutput.supported_player("C:\\MPV\\mpv.exe") == "mpv"
assert PlayerOutput.supported_player("C:\\VLC\\vlc.exe") == "vlc"
assert PlayerOutput.supported_player("C:\\PotPlayer\\PotPlayerMini64.exe") == "potplayer"
@patch("streamlink_cli.output.os.path.basename", new=posixpath.basename)
def test_supported_player_posix(self):
self.assertEqual("mpv",
PlayerOutput.supported_player("/usr/bin/mpv"))
self.assertEqual("vlc",
PlayerOutput.supported_player("/usr/bin/vlc"))
assert PlayerOutput.supported_player("/usr/bin/mpv") == "mpv"
assert PlayerOutput.supported_player("/usr/bin/vlc") == "vlc"
@patch("streamlink_cli.output.os.path.basename", new=ntpath.basename)
def test_supported_player_args_win32(self):
self.assertEqual("mpv",
PlayerOutput.supported_player("C:\\MPV\\mpv.exe --argh"))
self.assertEqual("vlc",
PlayerOutput.supported_player("C:\\VLC\\vlc.exe --argh"))
self.assertEqual("potplayer",
PlayerOutput.supported_player("C:\\PotPlayer\\PotPlayerMini64.exe --argh"))
assert PlayerOutput.supported_player("C:\\MPV\\mpv.exe --argh") == "mpv"
assert PlayerOutput.supported_player("C:\\VLC\\vlc.exe --argh") == "vlc"
assert PlayerOutput.supported_player("C:\\PotPlayer\\PotPlayerMini64.exe --argh") == "potplayer"
@patch("streamlink_cli.output.os.path.basename", new=posixpath.basename)
def test_supported_player_args_posix(self):
self.assertEqual("mpv",
PlayerOutput.supported_player("/usr/bin/mpv --argh"))
self.assertEqual("vlc",
PlayerOutput.supported_player("/usr/bin/vlc --argh"))
assert PlayerOutput.supported_player("/usr/bin/mpv --argh") == "mpv"
assert PlayerOutput.supported_player("/usr/bin/vlc --argh") == "vlc"
@patch("streamlink_cli.output.os.path.basename", new=posixpath.basename)
def test_supported_player_negative_posix(self):
self.assertEqual(None,
PlayerOutput.supported_player("/usr/bin/xmpvideo"))
self.assertEqual(None,
PlayerOutput.supported_player("/usr/bin/echo"))
assert PlayerOutput.supported_player("/usr/bin/xmpvideo") is None
assert PlayerOutput.supported_player("/usr/bin/echo") is None
@patch("streamlink_cli.output.os.path.basename", new=ntpath.basename)
def test_supported_player_negative_win32(self):
self.assertEqual(None,
PlayerOutput.supported_player("C:\\mpc\\mpc-hd.exe"))
self.assertEqual(None,
PlayerOutput.supported_player("C:\\mplayer\\not-vlc.exe"))
self.assertEqual(None,
PlayerOutput.supported_player("C:\\NotPlayer\\NotPlayerMini64.exe"))
assert PlayerOutput.supported_player("C:\\mpc\\mpc-hd.exe") is None
assert PlayerOutput.supported_player("C:\\mplayer\\not-vlc.exe") is None
assert PlayerOutput.supported_player("C:\\NotPlayer\\NotPlayerMini64.exe") is None

View File

@ -12,27 +12,27 @@ class TestConsoleOutput(unittest.TestCase):
console = ConsoleOutput(output)
console.msg("foo")
console.msg_json({"test": 1})
self.assertEqual("foo\n", output.getvalue())
assert output.getvalue() == "foo\n"
def test_msg_json(self):
output = StringIO()
console = ConsoleOutput(output, json=True)
console.msg("foo")
console.msg_json({"test": 1})
self.assertEqual('{\n "test": 1\n}\n', output.getvalue())
assert output.getvalue() == '{\n "test": 1\n}\n'
def test_msg_json_object(self):
output = StringIO()
console = ConsoleOutput(output, json=True)
console.msg_json(Mock(__json__=Mock(return_value={"test": 1})))
self.assertEqual('{\n "test": 1\n}\n', output.getvalue())
assert output.getvalue() == '{\n "test": 1\n}\n'
def test_msg_json_list(self):
output = StringIO()
console = ConsoleOutput(output, json=True)
test_list = ["foo", "bar"]
console.msg_json(test_list)
self.assertEqual('[\n "foo",\n "bar"\n]\n', output.getvalue())
assert output.getvalue() == '[\n "foo",\n "bar"\n]\n'
def test_msg_json_merge_object(self):
output = StringIO()
@ -47,7 +47,7 @@ class TestConsoleOutput(unittest.TestCase):
"baz": "qux"
}
""").lstrip()
self.assertEqual([("test", 1), ("foo", "foo")], list(test_obj1.items()))
assert list(test_obj1.items()) == [("test", 1), ("foo", "foo")]
def test_msg_json_merge_list(self):
output = StringIO()
@ -72,14 +72,14 @@ class TestConsoleOutput(unittest.TestCase):
}
]
""").lstrip()
self.assertEqual(["foo", "bar"], test_list1)
assert test_list1 == ["foo", "bar"]
@patch("streamlink_cli.console.sys.exit")
def test_msg_json_error(self, mock_exit):
output = StringIO()
console = ConsoleOutput(output, json=True)
console.msg_json({"error": "bad"})
self.assertEqual('{\n "error": "bad"\n}\n', output.getvalue())
assert output.getvalue() == '{\n "error": "bad"\n}\n'
mock_exit.assert_called_with(1)
@patch("streamlink_cli.console.sys.exit")
@ -87,7 +87,7 @@ class TestConsoleOutput(unittest.TestCase):
output = StringIO()
console = ConsoleOutput(output)
console.exit("error")
self.assertEqual("error: error\n", output.getvalue())
assert output.getvalue() == "error: error\n"
mock_exit.assert_called_with(1)
@patch("streamlink_cli.console.sys.exit")
@ -95,7 +95,7 @@ class TestConsoleOutput(unittest.TestCase):
output = StringIO()
console = ConsoleOutput(output, json=True)
console.exit("error")
self.assertEqual('{\n "error": "error"\n}\n', output.getvalue())
assert output.getvalue() == '{\n "error": "error"\n}\n'
mock_exit.assert_called_with(1)
@patch("streamlink_cli.console.input", Mock(return_value="hello"))
@ -103,16 +103,16 @@ class TestConsoleOutput(unittest.TestCase):
def test_ask(self):
output = StringIO()
console = ConsoleOutput(output)
self.assertEqual("hello", console.ask("test: "))
self.assertEqual("test: ", output.getvalue())
assert console.ask("test: ") == "hello"
assert output.getvalue() == "test: "
@patch("streamlink_cli.console.input")
@patch("streamlink_cli.console.sys.stdin.isatty", Mock(return_value=False))
def test_ask_no_tty(self, mock_input: Mock):
output = StringIO()
console = ConsoleOutput(output)
self.assertIsNone(console.ask("test: "))
self.assertEqual("", output.getvalue())
assert console.ask("test: ") is None
assert output.getvalue() == ""
mock_input.assert_not_called()
@patch("streamlink_cli.console.input", Mock(side_effect=ValueError))
@ -120,8 +120,8 @@ class TestConsoleOutput(unittest.TestCase):
def test_ask_input_exception(self):
output = StringIO()
console = ConsoleOutput(output)
self.assertIsNone(console.ask("test: "))
self.assertEqual("test: ", output.getvalue())
assert console.ask("test: ") is None
assert output.getvalue() == "test: "
@patch("streamlink_cli.console.getpass")
@patch("streamlink_cli.console.sys.stdin.isatty", Mock(return_value=True))
@ -133,11 +133,11 @@ class TestConsoleOutput(unittest.TestCase):
output = StringIO()
console = ConsoleOutput(output)
mock_getpass.side_effect = getpass
self.assertEqual("hello", console.askpass("test: "))
self.assertEqual("test: ", output.getvalue())
assert console.askpass("test: ") == "hello"
assert output.getvalue() == "test: "
@patch("streamlink_cli.console.sys.stdin.isatty", Mock(return_value=False))
def test_askpass_no_tty(self):
output = StringIO()
console = ConsoleOutput(output)
self.assertIsNone(console.askpass("test: "))
assert console.askpass("test: ") is None

View File

@ -1,6 +1,7 @@
import datetime
import logging
import os
import re
import sys
import unittest
from pathlib import Path, PosixPath, WindowsPath
@ -65,16 +66,16 @@ class TestCLIMain(unittest.TestCase):
"best-unfiltered": e,
}
self.assertEqual(resolve_stream_name(streams, "unknown"), "unknown")
self.assertEqual(resolve_stream_name(streams, "160p"), "160p")
self.assertEqual(resolve_stream_name(streams, "360p"), "360p")
self.assertEqual(resolve_stream_name(streams, "480p"), "480p")
self.assertEqual(resolve_stream_name(streams, "720p"), "720p")
self.assertEqual(resolve_stream_name(streams, "1080p"), "1080p")
self.assertEqual(resolve_stream_name(streams, "worst"), "360p")
self.assertEqual(resolve_stream_name(streams, "best"), "720p")
self.assertEqual(resolve_stream_name(streams, "worst-unfiltered"), "160p")
self.assertEqual(resolve_stream_name(streams, "best-unfiltered"), "1080p")
assert resolve_stream_name(streams, "unknown") == "unknown"
assert resolve_stream_name(streams, "160p") == "160p"
assert resolve_stream_name(streams, "360p") == "360p"
assert resolve_stream_name(streams, "480p") == "480p"
assert resolve_stream_name(streams, "720p") == "720p"
assert resolve_stream_name(streams, "1080p") == "1080p"
assert resolve_stream_name(streams, "worst") == "360p"
assert resolve_stream_name(streams, "best") == "720p"
assert resolve_stream_name(streams, "worst-unfiltered") == "160p"
assert resolve_stream_name(streams, "best-unfiltered") == "1080p"
def test_format_valid_streams(self):
a = Mock()
@ -88,14 +89,11 @@ class TestCLIMain(unittest.TestCase):
"worst": b,
"best": c,
}
self.assertEqual(
format_valid_streams(_TestPlugin, streams),
", ".join([
"audio",
"720p (worst)",
"1080p (best)",
]),
)
assert format_valid_streams(_TestPlugin, streams) == ", ".join([
"audio",
"720p (worst)",
"1080p (best)",
])
streams = {
"audio": a,
@ -104,14 +102,11 @@ class TestCLIMain(unittest.TestCase):
"worst-unfiltered": b,
"best-unfiltered": c,
}
self.assertEqual(
format_valid_streams(_TestPlugin, streams),
", ".join([
"audio",
"720p (worst-unfiltered)",
"1080p (best-unfiltered)",
]),
)
assert format_valid_streams(_TestPlugin, streams) == ", ".join([
"audio",
"720p (worst-unfiltered)",
"1080p (best-unfiltered)",
])
class TestCLIMainHandleUrl:
@ -139,8 +134,8 @@ class TestCLIMainJsonAndStreamUrl(unittest.TestCase):
plugin._streams = streams
handle_stream(plugin, streams, "best")
self.assertEqual(console.msg.mock_calls, [])
self.assertEqual(console.msg_json.mock_calls, [call(
assert console.msg.mock_calls == []
assert console.msg_json.mock_calls == [call(
stream,
metadata=dict(
id="test-id-1234-5678",
@ -148,22 +143,22 @@ class TestCLIMainJsonAndStreamUrl(unittest.TestCase):
category=None,
title="Test Title",
),
)])
self.assertEqual(console.error.mock_calls, [])
)]
assert console.error.mock_calls == []
console.msg_json.mock_calls.clear()
args.json = False
handle_stream(plugin, streams, "best")
self.assertEqual(console.msg.mock_calls, [call(stream.to_url())])
self.assertEqual(console.msg_json.mock_calls, [])
self.assertEqual(console.error.mock_calls, [])
assert console.msg.mock_calls == [call(stream.to_url())]
assert console.msg_json.mock_calls == []
assert console.error.mock_calls == []
console.msg.mock_calls.clear()
stream.to_url.side_effect = TypeError()
handle_stream(plugin, streams, "best")
self.assertEqual(console.msg.mock_calls, [])
self.assertEqual(console.msg_json.mock_calls, [])
self.assertEqual(console.exit.mock_calls, [call("The stream specified cannot be translated to a URL")])
assert console.msg.mock_calls == []
assert console.msg_json.mock_calls == []
assert console.exit.mock_calls == [call("The stream specified cannot be translated to a URL")]
@patch("streamlink_cli.main.args", json=True, stream_url=True, stream=[], default_stream=[], retry_max=0, retry_streams=0)
@patch("streamlink_cli.main.console")
@ -177,8 +172,8 @@ class TestCLIMainJsonAndStreamUrl(unittest.TestCase):
with patch("streamlink_cli.main.streamlink", resolve_url=Mock(return_value=("fake", _FakePlugin, ""))):
handle_url()
self.assertEqual(console.msg.mock_calls, [])
self.assertEqual(console.msg_json.mock_calls, [call(
assert console.msg.mock_calls == []
assert console.msg_json.mock_calls == [call(
plugin="fake",
metadata=dict(
id="test-id-1234-5678",
@ -187,22 +182,22 @@ class TestCLIMainJsonAndStreamUrl(unittest.TestCase):
title="Test Title",
),
streams=streams,
)])
self.assertEqual(console.error.mock_calls, [])
)]
assert console.error.mock_calls == []
console.msg_json.mock_calls.clear()
args.json = False
handle_url()
self.assertEqual(console.msg.mock_calls, [call(stream.to_manifest_url())])
self.assertEqual(console.msg_json.mock_calls, [])
self.assertEqual(console.error.mock_calls, [])
assert console.msg.mock_calls == [call(stream.to_manifest_url())]
assert console.msg_json.mock_calls == []
assert console.error.mock_calls == []
console.msg.mock_calls.clear()
stream.to_manifest_url.side_effect = TypeError()
handle_url()
self.assertEqual(console.msg.mock_calls, [])
self.assertEqual(console.msg_json.mock_calls, [])
self.assertEqual(console.exit.mock_calls, [call("The stream specified cannot be translated to a URL")])
assert console.msg.mock_calls == []
assert console.msg_json.mock_calls == []
assert console.exit.mock_calls == [call("The stream specified cannot be translated to a URL")]
console.exit.mock_calls.clear()
@ -220,16 +215,16 @@ class TestCLIMainCheckFileOutput(unittest.TestCase):
def test_check_file_output(self, mock_log: Mock):
path = self.mock_path("foo", is_file=False, resolve="/path/to/foo")
output = check_file_output(path, False)
self.assertIsInstance(output, FileOutput)
self.assertIs(output.filename, path)
self.assertEqual(mock_log.info.call_args_list, [call("Writing output to\n/path/to/foo")])
self.assertEqual(mock_log.debug.call_args_list, [call("Checking file output")])
assert isinstance(output, FileOutput)
assert output.filename is path
assert mock_log.info.call_args_list == [call("Writing output to\n/path/to/foo")]
assert mock_log.debug.call_args_list == [call("Checking file output")]
def test_check_file_output_exists_force(self):
path = self.mock_path("foo", is_file=True)
output = check_file_output(path, True)
self.assertIsInstance(output, FileOutput)
self.assertIs(output.filename, path)
assert isinstance(output, FileOutput)
assert output.filename is path
@patch("streamlink_cli.main.console")
@patch("streamlink_cli.main.sys")
@ -238,9 +233,9 @@ class TestCLIMainCheckFileOutput(unittest.TestCase):
mock_console.ask = Mock(return_value="y")
path = self.mock_path("foo", is_file=True)
output = check_file_output(path, False)
self.assertEqual(mock_console.ask.call_args_list, [call("File foo already exists! Overwrite it? [y/N] ")])
self.assertIsInstance(output, FileOutput)
self.assertIs(output.filename, path)
assert mock_console.ask.call_args_list == [call("File foo already exists! Overwrite it? [y/N] ")]
assert isinstance(output, FileOutput)
assert output.filename is path
@patch("streamlink_cli.main.console")
@patch("streamlink_cli.main.sys")
@ -249,9 +244,9 @@ class TestCLIMainCheckFileOutput(unittest.TestCase):
mock_sys.exit.side_effect = SystemExit
mock_console.ask = Mock(return_value="N")
path = self.mock_path("foo", is_file=True)
with self.assertRaises(SystemExit):
with pytest.raises(SystemExit):
check_file_output(path, False)
self.assertEqual(mock_console.ask.call_args_list, [call("File foo already exists! Overwrite it? [y/N] ")])
assert mock_console.ask.call_args_list == [call("File foo already exists! Overwrite it? [y/N] ")]
@patch("streamlink_cli.main.console")
@patch("streamlink_cli.main.sys")
@ -260,9 +255,9 @@ class TestCLIMainCheckFileOutput(unittest.TestCase):
mock_sys.exit.side_effect = SystemExit
mock_console.ask = Mock(return_value=None)
path = self.mock_path("foo", is_file=True)
with self.assertRaises(SystemExit):
with pytest.raises(SystemExit):
check_file_output(path, False)
self.assertEqual(mock_console.ask.call_args_list, [call("File foo already exists! Overwrite it? [y/N] ")])
assert mock_console.ask.call_args_list == [call("File foo already exists! Overwrite it? [y/N] ")]
@patch("streamlink_cli.main.console")
@patch("streamlink_cli.main.sys")
@ -270,9 +265,9 @@ class TestCLIMainCheckFileOutput(unittest.TestCase):
mock_sys.stdin.isatty.return_value = False
mock_sys.exit.side_effect = SystemExit
path = self.mock_path("foo", is_file=True)
with self.assertRaises(SystemExit):
with pytest.raises(SystemExit):
check_file_output(path, False)
self.assertEqual(mock_console.ask.call_args_list, [])
assert mock_console.ask.call_args_list == []
# TODO: don't use Mock() for mocking args, use a custom argparse.Namespace instead
@ -450,12 +445,9 @@ class TestCLIMainCreateOutput(unittest.TestCase):
args.record_and_pipe = False
args.player = None
console.exit.side_effect = SystemExit
with self.assertRaises(SystemExit):
with pytest.raises(SystemExit):
create_output(formatter)
self.assertRegex(
console.exit.call_args_list[0][0][0],
r"^The default player \(\w+\) does not seem to be installed\.",
)
assert re.search(r"^The default player \(\w+\) does not seem to be installed\.", console.exit.call_args_list[0][0][0])
class TestCLIMainHandleStream(unittest.TestCase):
@ -478,10 +470,10 @@ class TestCLIMainHandleStream(unittest.TestCase):
streams = {"best": stream}
handle_stream(plugin, streams, "best")
self.assertEqual(mock_output_stream.call_count, 1)
assert mock_output_stream.call_count == 1
paramStream, paramFormatter = mock_output_stream.call_args[0]
self.assertIs(paramStream, stream)
self.assertIsInstance(paramFormatter, Formatter)
assert paramStream is stream
assert isinstance(paramFormatter, Formatter)
class TestCLIMainOutputStream(unittest.TestCase):
@ -500,14 +492,14 @@ class TestCLIMainOutputStream(unittest.TestCase):
patch("streamlink_cli.main.create_output", return_value=output):
output_stream(stream, formatter)
self.assertEqual(mock_log.error.call_args_list, [
assert mock_log.error.call_args_list == [
call("Try 1/2: Could not open stream fake-stream (Could not open stream: failure)"),
call("Try 2/2: Could not open stream fake-stream (Could not open stream: failure)"),
])
self.assertEqual(mock_console.exit.call_args_list, [
]
assert mock_console.exit.call_args_list == [
call("Could not open stream fake-stream, tried 2 times, exiting"),
])
self.assertFalse(output.open.called, "Does not open the output on stream error")
]
assert not output.open.called, "Does not open the output on stream error"
class _TestCLIMainLogging(unittest.TestCase):
@ -552,9 +544,9 @@ class _TestCLIMainLogging(unittest.TestCase):
def write_file_and_assert(self, mock_mkdir: Mock, mock_write: Mock, mock_stdout: Mock):
streamlink_cli.main.log.info("foo")
streamlink_cli.main.console.msg("bar")
self.assertEqual(mock_mkdir.mock_calls, [call(parents=True, exist_ok=True)])
self.assertEqual(mock_write.mock_calls, self._write_calls)
self.assertFalse(mock_stdout.write.called)
assert mock_mkdir.mock_calls == [call(parents=True, exist_ok=True)]
assert mock_write.mock_calls == self._write_calls
assert not mock_stdout.write.called
class TestCLIMainLoggingStreams(_TestCLIMainLogging):
@ -569,14 +561,14 @@ class TestCLIMainLoggingStreams(_TestCLIMainLogging):
super().subject(argv)
childlogger = logging.getLogger("streamlink.test_cli_main")
with self.assertRaises(SystemExit):
streamlink_cli.main.log.info("foo")
childlogger.error("baz")
streamlink_cli.main.log.info("foo")
childlogger.error("baz")
with pytest.raises(SystemExit):
streamlink_cli.main.console.exit("bar")
self.assertIs(streamlink_cli.main.log.parent.handlers[0].stream, stream)
self.assertIs(childlogger.parent.handlers[0].stream, stream)
self.assertIs(streamlink_cli.main.console.output, stream)
assert streamlink_cli.main.log.parent.handlers[0].stream is stream
assert childlogger.parent.handlers[0].stream is stream
assert streamlink_cli.main.console.output is stream
@patch("sys.stderr")
@patch("sys.stdout")
@ -612,31 +604,37 @@ class TestCLIMainLoggingStreams(_TestCLIMainLogging):
@patch("sys.stdout")
def test_no_pipe_no_json(self, mock_stdout: Mock, mock_stderr: Mock):
self.subject(["streamlink"], mock_stdout)
self.assertEqual(mock_stdout.write.mock_calls,
self._write_call_log_cli_info + self._write_call_log_testcli_err + self._write_call_console_msg_error)
self.assertEqual(mock_stderr.write.mock_calls, [])
assert mock_stdout.write.mock_calls == (
self._write_call_log_cli_info
+ self._write_call_log_testcli_err
+ self._write_call_console_msg_error
)
assert mock_stderr.write.mock_calls == []
@patch("sys.stderr")
@patch("sys.stdout")
def test_no_pipe_json(self, mock_stdout: Mock, mock_stderr: Mock):
self.subject(["streamlink", "--json"], mock_stdout)
self.assertEqual(mock_stdout.write.mock_calls, self._write_call_console_msg_json)
self.assertEqual(mock_stderr.write.mock_calls, [])
assert mock_stdout.write.mock_calls == self._write_call_console_msg_json
assert mock_stderr.write.mock_calls == []
@patch("sys.stderr")
@patch("sys.stdout")
def test_pipe_no_json(self, mock_stdout: Mock, mock_stderr: Mock):
self.subject(["streamlink", "--stdout"], mock_stderr)
self.assertEqual(mock_stdout.write.mock_calls, [])
self.assertEqual(mock_stderr.write.mock_calls,
self._write_call_log_cli_info + self._write_call_log_testcli_err + self._write_call_console_msg_error)
assert mock_stdout.write.mock_calls == []
assert mock_stderr.write.mock_calls == (
self._write_call_log_cli_info
+ self._write_call_log_testcli_err
+ self._write_call_console_msg_error
)
@patch("sys.stderr")
@patch("sys.stdout")
def test_pipe_json(self, mock_stdout: Mock, mock_stderr: Mock):
self.subject(["streamlink", "--stdout", "--json"], mock_stderr)
self.assertEqual(mock_stdout.write.mock_calls, [])
self.assertEqual(mock_stderr.write.mock_calls, self._write_call_console_msg_json)
assert mock_stdout.write.mock_calls == []
assert mock_stderr.write.mock_calls == self._write_call_console_msg_json
class TestCLIMainLoggingInfos(_TestCLIMainLogging):
@ -644,7 +642,7 @@ class TestCLIMainLoggingInfos(_TestCLIMainLogging):
@patch("streamlink_cli.main.log")
def test_log_root_warning(self, mock_log):
self.subject(["streamlink"], euid=0)
self.assertEqual(mock_log.info.mock_calls, [call("streamlink is running as root! Be careful!")])
assert mock_log.info.mock_calls == [call("streamlink is running as root! Be careful!")]
@patch("streamlink_cli.main.log")
@patch("streamlink_cli.main.streamlink_version", "streamlink")
@ -667,7 +665,7 @@ class TestCLIMainLoggingInfos(_TestCLIMainLogging):
mock_importlib_metadata.version.side_effect = version
self.subject(["streamlink", "--loglevel", "info"])
self.assertEqual(mock_log.debug.mock_calls, [], "Doesn't log anything if not debug logging")
assert mock_log.debug.mock_calls == [], "Doesn't log anything if not debug logging"
with patch("sys.platform", "linux"), \
patch("platform.platform", Mock(return_value="linux")):
@ -721,7 +719,7 @@ class TestCLIMainLoggingInfos(_TestCLIMainLogging):
"streamlink",
"--loglevel", "info",
])
self.assertEqual(mock_log.debug.mock_calls, [], "Doesn't log anything if not debug logging")
assert mock_log.debug.mock_calls == [], "Doesn't log anything if not debug logging"
self.subject([
"streamlink",
@ -732,18 +730,15 @@ class TestCLIMainLoggingInfos(_TestCLIMainLogging):
"test.se/channel",
"best,worst",
])
self.assertEqual(
mock_log.debug.mock_calls[-7:],
[
call("Arguments:"),
call(" url=test.se/channel"),
call(" stream=['best', 'worst']"),
call(" --loglevel=debug"),
call(" --player=custom"),
call(" --testplugin-bool=True"),
call(" --testplugin-password=********"),
],
)
assert mock_log.debug.mock_calls[-7:] == [
call("Arguments:"),
call(" url=test.se/channel"),
call(" stream=['best', 'worst']"),
call(" --loglevel=debug"),
call(" --player=custom"),
call(" --testplugin-bool=True"),
call(" --testplugin-password=********"),
]
class TestCLIMainLoggingLogfile(_TestCLIMainLogging):
@ -753,9 +748,9 @@ class TestCLIMainLoggingLogfile(_TestCLIMainLogging):
self.subject(["streamlink"])
streamlink_cli.main.log.info("foo")
streamlink_cli.main.console.msg("bar")
self.assertEqual(streamlink_cli.main.console.output, sys.stdout)
self.assertFalse(mock_open.called)
self.assertEqual(mock_stdout.write.mock_calls, self._write_calls)
assert streamlink_cli.main.console.output == sys.stdout
assert not mock_open.called
assert mock_stdout.write.mock_calls == self._write_calls
@patch("sys.stdout")
@patch("builtins.open")
@ -763,9 +758,9 @@ class TestCLIMainLoggingLogfile(_TestCLIMainLogging):
self.subject(["streamlink", "--loglevel", "none", "--logfile", "foo"])
streamlink_cli.main.log.info("foo")
streamlink_cli.main.console.msg("bar")
self.assertEqual(streamlink_cli.main.console.output, sys.stdout)
self.assertFalse(mock_open.called)
self.assertEqual(mock_stdout.write.mock_calls, [call("bar\n")])
assert streamlink_cli.main.console.output == sys.stdout
assert not mock_open.called
assert mock_stdout.write.mock_calls == [call("bar\n")]
@patch("sys.stdout")
@patch("builtins.open")
@ -871,9 +866,9 @@ class TestCLIMainPrint(unittest.TestCase):
patch("streamlink_cli.main.setup_streamlink"), \
patch("streamlink_cli.main.setup_plugins"), \
patch("streamlink_cli.main.setup_signals"):
with self.assertRaises(SystemExit) as cm:
with pytest.raises(SystemExit) as cm:
streamlink_cli.main.main()
self.assertEqual(cm.exception.code, 0)
assert cm.value.code == 0
mock_resolve_url.assert_not_called()
mock_resolve_url_no_redirect.assert_not_called()
@ -885,47 +880,38 @@ class TestCLIMainPrint(unittest.TestCase):
@patch("sys.argv", ["streamlink"])
def test_print_usage(self, mock_stdout):
self.subject()
self.assertEqual(
self.get_stdout(mock_stdout),
"usage: streamlink [OPTIONS] <URL> [STREAM]\n\n"
+ "Use -h/--help to see the available options or read the manual at https://streamlink.github.io\n",
)
assert self.get_stdout(mock_stdout) == dedent("""
usage: streamlink [OPTIONS] <URL> [STREAM]
Use -h/--help to see the available options or read the manual at https://streamlink.github.io
""").lstrip()
@patch("sys.stdout")
@patch("sys.argv", ["streamlink", "--help"])
def test_print_help(self, mock_stdout):
self.subject()
output = self.get_stdout(mock_stdout)
self.assertIn(
"usage: streamlink [OPTIONS] <URL> [STREAM]",
output,
)
self.assertIn(
dedent("""
Streamlink is a command-line utility that extracts streams from various
services and pipes them into a video player of choice.
"""),
output,
)
self.assertIn(
dedent("""
For more in-depth documentation see:
https://streamlink.github.io
assert "usage: streamlink [OPTIONS] <URL> [STREAM]" in output
assert dedent("""
Streamlink is a command-line utility that extracts streams from various
services and pipes them into a video player of choice.
""") in output
assert dedent("""
For more in-depth documentation see:
https://streamlink.github.io
Please report broken plugins or bugs to the issue tracker on Github:
https://github.com/streamlink/streamlink/issues
"""),
output,
)
Please report broken plugins or bugs to the issue tracker on Github:
https://github.com/streamlink/streamlink/issues
""") in output
@patch("sys.stdout")
@patch("sys.argv", ["streamlink", "--plugins"])
def test_print_plugins(self, mock_stdout):
self.subject()
self.assertEqual(self.get_stdout(mock_stdout), "Loaded plugins: testplugin\n")
assert self.get_stdout(mock_stdout) == "Loaded plugins: testplugin\n"
@patch("sys.stdout")
@patch("sys.argv", ["streamlink", "--plugins", "--json"])
def test_print_plugins_json(self, mock_stdout):
self.subject()
self.assertEqual(self.get_stdout(mock_stdout), """[\n "testplugin"\n]\n""")
assert self.get_stdout(mock_stdout) == '[\n "testplugin"\n]\n'

View File

@ -220,7 +220,7 @@ class TestMixinStreamHLS(unittest.TestCase):
thread.reader.writer.join(timeout)
thread.reader.worker.join(timeout)
thread.join(timeout)
self.assertTrue(self.thread.reader.closed, "Stream reader is closed")
assert self.thread.reader.closed, "Stream reader is closed"
# make write calls on the write-thread and wait until it has finished
def await_write(self, write_calls=1, timeout=TIMEOUT_AWAIT_WRITE) -> None:

View File

@ -19,7 +19,4 @@ class TestPluginCanHandleUrlBBCiPlayer(PluginCanHandleUrl):
class TestPluginBBCiPlayer(unittest.TestCase):
def test_vpid_hash(self):
self.assertEqual(
"71c345435589c6ddeea70d6f252e2a52281ecbf3",
BBCiPlayer._hash_vpid("1234567890"),
)
assert BBCiPlayer._hash_vpid("1234567890") == "71c345435589c6ddeea70d6f252e2a52281ecbf3"

View File

@ -49,8 +49,8 @@ def test_get_streams(parse_manifest, url, expected):
class TestPluginMPEGDASH(unittest.TestCase):
def test_stream_weight(self):
self.assertAlmostEqual(MPEGDASH.stream_weight("720p"), (720, "pixels"))
self.assertAlmostEqual(MPEGDASH.stream_weight("1080p"), (1080, "pixels"))
self.assertAlmostEqual(MPEGDASH.stream_weight("720p+a128k"), (720 + 128, "pixels"))
self.assertAlmostEqual(MPEGDASH.stream_weight("720p+a0k"), (720, "pixels"))
self.assertAlmostEqual(MPEGDASH.stream_weight("a128k"), (128 / BIT_RATE_WEIGHT_RATIO, "bitrate"))
assert MPEGDASH.stream_weight("720p") == pytest.approx((720, "pixels"))
assert MPEGDASH.stream_weight("1080p") == pytest.approx((1080, "pixels"))
assert MPEGDASH.stream_weight("720p+a128k") == pytest.approx((720 + 128, "pixels"))
assert MPEGDASH.stream_weight("720p+a0k") == pytest.approx((720, "pixels"))
assert MPEGDASH.stream_weight("a128k") == pytest.approx((128 / BIT_RATE_WEIGHT_RATIO, "bitrate"))

View File

@ -62,16 +62,16 @@ class TestRTPPlay(unittest.TestCase):
def test_empty(self):
streams = self.subject("https://www.rtp.pt/play/id/title", "")
self.assertEqual(streams, None)
assert streams is None
def test_invalid(self):
streams = self.subject("https://www.rtp.pt/play/id/title", self._content_pre + self._content_invalid)
self.assertEqual(streams, None)
assert streams is None
def test_valid(self):
streams = self.subject("https://www.rtp.pt/play/id/title", self._content_pre + self._content_valid)
self.assertIsInstance(next(iter(streams.values())), HLSStream)
assert isinstance(next(iter(streams.values())), HLSStream)
def test_valid_b64(self):
streams = self.subject("https://www.rtp.pt/play/id/title", self._content_pre + self._content_valid_b64)
self.assertIsInstance(next(iter(streams.values())), HLSStream)
assert isinstance(next(iter(streams.values())), HLSStream)

View File

@ -21,7 +21,7 @@ class TestPluginStream(unittest.TestCase):
def assertDictHas(self, a, b):
for key, value in a.items():
self.assertEqual(b[key], value)
assert b[key] == value
@patch("streamlink.stream.HLSStream.parse_variant_playlist")
def _test_hls(self, surl, url, mock_parse):
@ -29,12 +29,12 @@ class TestPluginStream(unittest.TestCase):
streams = self.session.streams(surl)
self.assertIn("live", streams)
assert "live" in streams
mock_parse.assert_called_with(self.session, url)
stream = streams["live"]
self.assertIsInstance(stream, HLSStream)
self.assertEqual(stream.url, url)
assert isinstance(stream, HLSStream)
assert stream.url == url
@patch("streamlink.stream.HLSStream.parse_variant_playlist")
def _test_hlsvariant(self, surl, url, mock_parse):
@ -44,21 +44,21 @@ class TestPluginStream(unittest.TestCase):
mock_parse.assert_called_with(self.session, url)
self.assertNotIn("live", streams)
self.assertIn("best", streams)
assert "live" not in streams
assert "best" in streams
stream = streams["best"]
self.assertIsInstance(stream, HLSStream)
self.assertEqual(stream.url, url)
assert isinstance(stream, HLSStream)
assert stream.url == url
def _test_http(self, surl, url, params):
streams = self.session.streams(surl)
self.assertIn("live", streams)
assert "live" in streams
stream = streams["live"]
self.assertIsInstance(stream, HTTPStream)
self.assertEqual(stream.url, url)
assert isinstance(stream, HTTPStream)
assert stream.url == url
self.assertDictHas(params, stream.args)
def test_plugin_hls(self):
@ -85,55 +85,29 @@ class TestPluginStream(unittest.TestCase):
"https://hostname.se/auth.php?key=a+value", dict(verify=False, params=dict(key="a value")))
def test_parse_params(self):
self.assertEqual({}, parse_params())
self.assertEqual(
dict(verify=False, params=dict(key="a value")),
parse_params("""verify=False params={'key': 'a value'}"""),
)
self.assertEqual(
dict(verify=False),
parse_params("""verify=False"""),
)
self.assertEqual(
dict(conn=["B:1", "S:authMe", "O:1", "NN:code:1.23", "NS:flag:ok", "O:0"]),
parse_params(""""conn=['B:1', 'S:authMe', 'O:1', 'NN:code:1.23', 'NS:flag:ok', 'O:0']"""),
)
assert parse_params() \
== {}
assert parse_params("verify=False params={'key': 'a value'}") \
== dict(verify=False, params=dict(key="a value"))
assert parse_params("verify=False") \
== dict(verify=False)
assert parse_params("\"conn=['B:1', 'S:authMe', 'O:1', 'NN:code:1.23', 'NS:flag:ok', 'O:0']") \
== dict(conn=["B:1", "S:authMe", "O:1", "NN:code:1.23", "NS:flag:ok", "O:0"])
def test_stream_weight_value(self):
self.assertEqual((720, "pixels"),
stream_weight("720p"))
self.assertEqual((721, "pixels"),
stream_weight("720p+"))
self.assertEqual((780, "pixels"),
stream_weight("720p60"))
assert stream_weight("720p") == (720, "pixels")
assert stream_weight("720p+") == (721, "pixels")
assert stream_weight("720p60") == (780, "pixels")
def test_stream_weight(self):
self.assertGreater(stream_weight("720p+"),
stream_weight("720p"))
self.assertGreater(stream_weight("720p_3000k"),
stream_weight("720p_2500k"))
self.assertGreater(stream_weight("720p60_3000k"),
stream_weight("720p_3000k"))
self.assertGreater(stream_weight("3000k"),
stream_weight("2500k"))
self.assertEqual(stream_weight("720p"),
stream_weight("720p"))
self.assertLess(stream_weight("720p_3000k"),
stream_weight("720p+_3000k"))
assert stream_weight("720p+") > stream_weight("720p")
assert stream_weight("720p_3000k") > stream_weight("720p_2500k")
assert stream_weight("720p60_3000k") > stream_weight("720p_3000k")
assert stream_weight("3000k") > stream_weight("2500k")
assert stream_weight("720p") == stream_weight("720p")
assert stream_weight("720p_3000k") < stream_weight("720p+_3000k")
def test_stream_weight_and_audio(self):
self.assertGreater(stream_weight("720p+a256k"),
stream_weight("720p+a128k"))
self.assertGreater(stream_weight("720p+a256k"),
stream_weight("720p+a128k"))
self.assertGreater(stream_weight("720p+a128k"),
stream_weight("360p+a256k"))
assert stream_weight("720p+a256k") > stream_weight("720p+a128k")
assert stream_weight("720p+a256k") > stream_weight("720p+a128k")
assert stream_weight("720p+a128k") > stream_weight("360p+a256k")

View File

@ -135,8 +135,9 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
], disable_ads=True, low_latency=False)
self.await_write(2)
self.assertEqual(self.await_read(read_all=True), self.content(segments), "Doesn't filter out segments")
self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
data = self.await_read(read_all=True)
assert data == self.content(segments), "Doesn't filter out segments"
assert all(self.called(s) for s in segments.values()), "Downloads all segments"
def test_hls_disable_ads_daterange_by_class(self):
daterange = TagDateRangeAd(start=DATETIME_BASE, duration=1, id="foo", classname="twitch-stitched-ad", custom=None)
@ -145,8 +146,9 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
], disable_ads=True, low_latency=False)
self.await_write(2)
self.assertEqual(self.await_read(read_all=True), segments[1].content, "Filters out ad segments")
self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
data = self.await_read(read_all=True)
assert data == segments[1].content, "Filters out ad segments"
assert all(self.called(s) for s in segments.values()), "Downloads all segments"
def test_hls_disable_ads_daterange_by_id(self):
daterange = TagDateRangeAd(start=DATETIME_BASE, duration=1, id="stitched-ad-1234", classname="/", custom=None)
@ -155,8 +157,9 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
], disable_ads=True, low_latency=False)
self.await_write(2)
self.assertEqual(self.await_read(read_all=True), segments[1].content, "Filters out ad segments")
self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
data = self.await_read(read_all=True)
assert data == segments[1].content, "Filters out ad segments"
assert all(self.called(s) for s in segments.values()), "Downloads all segments"
def test_hls_disable_ads_daterange_by_attr(self):
daterange = TagDateRangeAd(start=DATETIME_BASE, duration=1, id="foo", classname="/", custom={"X-TV-TWITCH-AD-URL": "/"})
@ -165,8 +168,9 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
], disable_ads=True, low_latency=False)
self.await_write(2)
self.assertEqual(self.await_read(read_all=True), segments[1].content, "Filters out ad segments")
self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
data = self.await_read(read_all=True)
assert data == segments[1].content, "Filters out ad segments"
assert all(self.called(s) for s in segments.values()), "Downloads all segments"
@patch("streamlink.plugins.twitch.log")
def test_hls_disable_ads_has_preroll(self, mock_log):
@ -178,17 +182,13 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
], disable_ads=True, low_latency=False)
self.await_write(6)
self.assertEqual(
self.await_read(read_all=True),
self.content(segments, cond=lambda s: s.num >= 4),
"Filters out preroll ad segments",
)
self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
self.assertEqual(mock_log.info.mock_calls, [
data = self.await_read(read_all=True)
assert data == self.content(segments, cond=lambda s: s.num >= 4), "Filters out preroll ad segments"
assert all(self.called(s) for s in segments.values()), "Downloads all segments"
assert mock_log.info.mock_calls == [
call("Will skip ad segments"),
call("Waiting for pre-roll ads to finish, be patient"),
])
]
@patch("streamlink.plugins.twitch.log")
def test_hls_disable_ads_has_midstream(self, mock_log):
@ -200,16 +200,12 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
], disable_ads=True, low_latency=False)
self.await_write(6)
self.assertEqual(
self.await_read(read_all=True),
self.content(segments, cond=lambda s: s.num != 2 and s.num != 3),
"Filters out mid-stream ad segments",
)
self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
self.assertEqual(mock_log.info.mock_calls, [
data = self.await_read(read_all=True)
assert data == self.content(segments, cond=lambda s: s.num != 2 and s.num != 3), "Filters out mid-stream ad segments"
assert all(self.called(s) for s in segments.values()), "Downloads all segments"
assert mock_log.info.mock_calls == [
call("Will skip ad segments"),
])
]
@patch("streamlink.plugins.twitch.log")
def test_hls_no_disable_ads_has_preroll(self, mock_log):
@ -220,14 +216,10 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
], disable_ads=False, low_latency=False)
self.await_write(4)
self.assertEqual(
self.await_read(read_all=True),
self.content(segments),
"Doesn't filter out segments",
)
self.assertTrue(all(self.called(s) for s in segments.values()), "Downloads all segments")
self.assertEqual(mock_log.info.mock_calls, [], "Doesn't log anything")
data = self.await_read(read_all=True)
assert data == self.content(segments), "Doesn't filter out segments"
assert all(self.called(s) for s in segments.values()), "Downloads all segments"
assert mock_log.info.mock_calls == [], "Doesn't log anything"
@patch("streamlink.plugins.twitch.log")
def test_hls_low_latency_has_prefetch(self, mock_log):
@ -236,22 +228,17 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
Playlist(4, [Segment(4), Segment(5), Segment(6), Segment(7), SegmentPrefetch(8), SegmentPrefetch(9)], end=True),
], disable_ads=False, low_latency=True)
self.assertEqual(2, self.session.options.get("hls-live-edge"))
self.assertEqual(True, self.session.options.get("hls-segment-stream-data"))
assert self.session.options.get("hls-live-edge") == 2
assert self.session.options.get("hls-segment-stream-data")
self.await_write(6)
self.assertEqual(
self.await_read(read_all=True),
self.content(segments, cond=lambda s: s.num >= 4),
"Skips first four segments due to reduced live-edge",
)
self.assertFalse(any(self.called(s) for s in segments.values() if s.num < 4), "Doesn't download old segments")
self.assertTrue(all(self.called(s) for s in segments.values() if s.num >= 4), "Downloads all remaining segments")
self.assertEqual(mock_log.info.mock_calls, [
data = self.await_read(read_all=True)
assert data == self.content(segments, cond=lambda s: s.num >= 4), "Skips first four segments due to reduced live-edge"
assert not any(self.called(s) for s in segments.values() if s.num < 4), "Doesn't download old segments"
assert all(self.called(s) for s in segments.values() if s.num >= 4), "Downloads all remaining segments"
assert mock_log.info.mock_calls == [
call("Low latency streaming (HLS live edge: 2)"),
])
]
@patch("streamlink.plugins.twitch.log")
def test_hls_no_low_latency_has_prefetch(self, mock_log):
@ -260,20 +247,15 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
Playlist(4, [Segment(4), Segment(5), Segment(6), Segment(7), SegmentPrefetch(8), SegmentPrefetch(9)], end=True),
], disable_ads=False, low_latency=False)
self.assertEqual(4, self.session.options.get("hls-live-edge"))
self.assertEqual(False, self.session.options.get("hls-segment-stream-data"))
assert self.session.options.get("hls-live-edge") == 4
assert not self.session.options.get("hls-segment-stream-data")
self.await_write(8)
self.assertEqual(
self.await_read(read_all=True),
self.content(segments, cond=lambda s: s.num < 8),
"Ignores prefetch segments",
)
self.assertTrue(all(self.called(s) for s in segments.values() if s.num <= 7), "Ignores prefetch segments")
self.assertFalse(any(self.called(s) for s in segments.values() if s.num > 7), "Ignores prefetch segments")
self.assertEqual(mock_log.info.mock_calls, [], "Doesn't log anything")
data = self.await_read(read_all=True)
assert data == self.content(segments, cond=lambda s: s.num < 8), "Ignores prefetch segments"
assert all(self.called(s) for s in segments.values() if s.num <= 7), "Ignores prefetch segments"
assert not any(self.called(s) for s in segments.values() if s.num > 7), "Ignores prefetch segments"
assert mock_log.info.mock_calls == [], "Doesn't log anything"
@patch("streamlink.plugins.twitch.log")
def test_hls_low_latency_no_prefetch(self, mock_log):
@ -282,15 +264,15 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
Playlist(4, [Segment(4), Segment(5), Segment(6), Segment(7)], end=True),
], disable_ads=False, low_latency=True)
self.assertTrue(self.session.get_plugin_option("twitch", "low-latency"))
self.assertFalse(self.session.get_plugin_option("twitch", "disable-ads"))
assert self.session.get_plugin_option("twitch", "low-latency")
assert not self.session.get_plugin_option("twitch", "disable-ads")
self.await_write(6)
self.await_read(read_all=True)
self.assertEqual(mock_log.info.mock_calls, [
assert mock_log.info.mock_calls == [
call("Low latency streaming (HLS live edge: 2)"),
call("This is not a low latency stream"),
])
]
@patch("streamlink.plugins.twitch.log")
def test_hls_low_latency_has_prefetch_has_preroll(self, mock_log):
@ -301,18 +283,11 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
], disable_ads=False, low_latency=True)
self.await_write(8)
self.assertEqual(
self.await_read(read_all=True),
self.content(segments, cond=lambda s: s.num > 1),
"Skips first two segments due to reduced live-edge",
)
self.assertFalse(any(self.called(s) for s in segments.values() if s.num < 2), "Skips first two preroll segments")
self.assertTrue(all(self.called(s) for s in segments.values() if s.num >= 2), "Downloads all remaining segments")
self.assertEqual(mock_log.info.mock_calls, [
call("Low latency streaming (HLS live edge: 2)"),
])
data = self.await_read(read_all=True)
assert data == self.content(segments, cond=lambda s: s.num > 1), "Skips first two segments due to reduced live-edge"
assert not any(self.called(s) for s in segments.values() if s.num < 2), "Skips first two preroll segments"
assert all(self.called(s) for s in segments.values() if s.num >= 2), "Downloads all remaining segments"
assert mock_log.info.mock_calls == [call("Low latency streaming (HLS live edge: 2)")]
@patch("streamlink.plugins.twitch.log")
def test_hls_low_latency_has_prefetch_disable_ads_has_preroll(self, mock_log):
@ -324,11 +299,11 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
self.await_write(8)
self.await_read(read_all=True)
self.assertEqual(mock_log.info.mock_calls, [
assert mock_log.info.mock_calls == [
call("Will skip ad segments"),
call("Low latency streaming (HLS live edge: 2)"),
call("Waiting for pre-roll ads to finish, be patient"),
])
]
@patch("streamlink.plugins.twitch.log")
def test_hls_low_latency_has_prefetch_disable_ads_no_preroll_with_prefetch_ads(self, mock_log):
@ -357,8 +332,8 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
], disable_ads=True, low_latency=True)
self.await_write(11)
content = self.await_read(read_all=True)
assert content == self.content(segments, cond=lambda s: 2 <= s.num <= 3 or 7 <= s.num)
data = self.await_read(read_all=True)
assert data == self.content(segments, cond=lambda s: 2 <= s.num <= 3 or 7 <= s.num)
assert mock_log.info.mock_calls == [
call("Will skip ad segments"),
call("Low latency streaming (HLS live edge: 2)"),
@ -374,12 +349,12 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
self.await_write(6)
self.await_read(read_all=True)
self.assertEqual(mock_log.info.mock_calls, [
assert mock_log.info.mock_calls == [
call("Will skip ad segments"),
call("Low latency streaming (HLS live edge: 2)"),
call("Waiting for pre-roll ads to finish, be patient"),
call("This is not a low latency stream"),
])
]
def test_hls_low_latency_no_ads_reload_time(self):
Seg, SegPre = Segment, SegmentPrefetch
@ -389,7 +364,7 @@ class TestTwitchHLSStream(TestMixinStreamHLS, unittest.TestCase):
self.await_write(4)
self.await_read(read_all=True)
self.assertEqual(self.thread.reader.worker.playlist_reload_time, 23 / 3)
assert self.thread.reader.worker.playlist_reload_time == pytest.approx(23 / 3)
class TestTwitchAPIAccessToken:
@ -621,12 +596,12 @@ class TestTwitchMetadata(unittest.TestCase):
def test_metadata_channel(self):
mock = self.mock_request_channel()
_id, author, category, title = self.subject("https://twitch.tv/foo")
self.assertEqual(_id, "stream id")
self.assertEqual(author, "channel name")
self.assertEqual(category, "channel game")
self.assertEqual(title, "channel status")
self.assertEqual(mock.call_count, 1)
self.assertEqual(mock.request_history[0].json(), [
assert _id == "stream id"
assert author == "channel name"
assert category == "channel game"
assert title == "channel status"
assert mock.call_count == 1
assert mock.request_history[0].json() == [
{
"operationName": "ChannelShell",
"extensions": {
@ -652,58 +627,55 @@ class TestTwitchMetadata(unittest.TestCase):
"channelLogin": "foo",
},
},
])
]
def test_metadata_channel_no_data(self):
self.mock_request_channel(data=False)
_id, author, category, title = self.subject("https://twitch.tv/foo")
self.assertEqual(_id, None)
self.assertEqual(author, None)
self.assertEqual(category, None)
self.assertEqual(title, None)
assert _id is None
assert author is None
assert category is None
assert title is None
def test_metadata_video(self):
mock = self.mock_request_video()
_id, author, category, title = self.subject("https://twitch.tv/videos/1337")
self.assertEqual(_id, "video id")
self.assertEqual(author, "channel name")
self.assertEqual(category, "video game")
self.assertEqual(title, "video title")
self.assertEqual(mock.call_count, 1)
self.assertEqual(
mock.request_history[0].json(),
{
"operationName": "VideoMetadata",
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "cb3b1eb2f2d2b2f65b8389ba446ec521d76c3aa44f5424a1b1d235fe21eb4806",
},
},
"variables": {
"channelLogin": "",
"videoID": "1337",
assert _id == "video id"
assert author == "channel name"
assert category == "video game"
assert title == "video title"
assert mock.call_count == 1
assert mock.request_history[0].json() == {
"operationName": "VideoMetadata",
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "cb3b1eb2f2d2b2f65b8389ba446ec521d76c3aa44f5424a1b1d235fe21eb4806",
},
},
)
"variables": {
"channelLogin": "",
"videoID": "1337",
},
}
def test_metadata_video_no_data(self):
self.mock_request_video(data=False)
_id, author, category, title = self.subject("https://twitch.tv/videos/1337")
self.assertEqual(_id, None)
self.assertEqual(author, None)
self.assertEqual(category, None)
self.assertEqual(title, None)
assert _id is None
assert author is None
assert category is None
assert title is None
def test_metadata_clip(self):
mock = self.mock_request_clip()
_id, author, category, title = self.subject("https://clips.twitch.tv/foo")
self.assertEqual(_id, "clip id")
self.assertEqual(author, "channel name")
self.assertEqual(category, "game name")
self.assertEqual(title, "clip title")
self.assertEqual(mock.call_count, 1)
self.assertEqual(mock.request_history[0].json(), [
assert _id == "clip id"
assert author == "channel name"
assert category == "game name"
assert title == "clip title"
assert mock.call_count == 1
assert mock.request_history[0].json() == [
{
"operationName": "ClipsView",
"extensions": {
@ -728,15 +700,15 @@ class TestTwitchMetadata(unittest.TestCase):
"slug": "foo",
},
},
])
]
def test_metadata_clip_no_data(self):
self.mock_request_clip(data=False)
_id, author, category, title = self.subject("https://clips.twitch.tv/foo")
self.assertEqual(_id, None)
self.assertEqual(author, None)
self.assertEqual(category, None)
self.assertEqual(title, None)
assert _id is None
assert author is None
assert category is None
assert title is None
@patch("streamlink.plugins.twitch.log")
@ -753,17 +725,17 @@ class TestTwitchReruns(unittest.TestCase):
return plugin._check_for_rerun()
def test_disable_reruns_live(self, mock_log):
self.assertFalse(self.subject())
self.assertNotIn(self.log_call, mock_log.info.call_args_list)
assert not self.subject()
assert self.log_call not in mock_log.info.call_args_list
def test_disable_reruns_not_live(self, mock_log):
self.assertTrue(self.subject(stream_type="rerun"))
self.assertIn(self.log_call, mock_log.info.call_args_list)
assert self.subject(stream_type="rerun")
assert self.log_call in mock_log.info.call_args_list
def test_disable_reruns_offline(self, mock_log):
self.assertFalse(self.subject(offline=True))
self.assertNotIn(self.log_call, mock_log.info.call_args_list)
assert not self.subject(offline=True)
assert self.log_call not in mock_log.info.call_args_list
def test_enable_reruns(self, mock_log):
self.assertFalse(self.subject(stream_type="rerun", disable=False))
self.assertNotIn(self.log_call, mock_log.info.call_args_list)
assert not self.subject(stream_type="rerun", disable=False)
assert self.log_call not in mock_log.info.call_args_list

View File

@ -17,26 +17,22 @@ class TestPluginUSTVNow(unittest.TestCase):
key = "80035ad42d7d-bb08-7a14-f726-78403b29"
iv = "3157b5680927cc4a"
self.assertEqual(
assert USTVNow.encrypt_data(
b'{"login_id":"test@test.com","login_key":"testtest1234","login_mode":"1","manufacturer":"123"}',
key,
iv,
) == (
b"uawIc5n+TnmsmR+aP2iEDKG/eMKji6EKzjI4mE+zMhlyCbHm7K4hz7IDJDWwM3aE+Ro4ydSsgJf4ZInnoW6gqvXvG0qB"
+ b"/J2WJeypTSt4W124zkJpvfoJJmGAvBg2t0HT",
USTVNow.encrypt_data(
b'{"login_id":"test@test.com","login_key":"testtest1234","login_mode":"1","manufacturer":"123"}',
key,
iv,
),
+ b"/J2WJeypTSt4W124zkJpvfoJJmGAvBg2t0HT"
)
def test_decrypt_data(self):
key = "80035ad42d7d-bb08-7a14-f726-78403b29"
iv = "3157b5680927cc4a"
self.assertEqual(
b'{"status":false,"error":{"code":-2,"type":"","message":"Invalid credentials.","details":{}}}',
USTVNow.decrypt_data(
b"KcRLETVAmHlosM0OyUd5hdTQ6WhBRTe/YRAHiLJWrzf94OLkSueXTtQ9QZ1fjOLCbpX2qteEPUWVnzvvSgVDkQmRUttN"
+ b"/royoxW2aL0gYQSoH1NWoDV8sIgvS5vDiQ85",
key,
iv,
),
)
assert USTVNow.decrypt_data(
b"KcRLETVAmHlosM0OyUd5hdTQ6WhBRTe/YRAHiLJWrzf94OLkSueXTtQ9QZ1fjOLCbpX2qteEPUWVnzvvSgVDkQmRUttN"
+ b"/royoxW2aL0gYQSoH1NWoDV8sIgvS5vDiQ85",
key,
iv,
) == b'{"status":false,"error":{"code":-2,"type":"","message":"Invalid credentials.","details":{}}}'

View File

@ -44,4 +44,4 @@ class TestPluginWelt(unittest.TestCase):
</script>
</body></html>
""")
self.assertEqual(hls_url, "https://foo.bar/baz.m3u8?qux", "Finds the correct HLS live URL")
assert hls_url == "https://foo.bar/baz.m3u8?qux"

View File

@ -31,10 +31,7 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(
sorted(streams.keys()),
sorted(["720p", "1080p"]),
)
assert sorted(streams.keys()) == sorted(["720p", "1080p"])
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_audio_only(self, mpdClass):
@ -51,10 +48,7 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(
sorted(streams.keys()),
sorted(["a128k", "a256k"]),
)
assert sorted(streams.keys()) == sorted(["a128k", "a256k"])
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_audio_single(self, mpdClass):
@ -72,10 +66,7 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(
sorted(streams.keys()),
sorted(["720p", "1080p"]),
)
assert sorted(streams.keys()) == sorted(["720p", "1080p"])
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_audio_multi(self, mpdClass):
@ -94,10 +85,7 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(
sorted(streams.keys()),
sorted(["720p+a128k", "1080p+a128k", "720p+a256k", "1080p+a256k"]),
)
assert sorted(streams.keys()) == sorted(["720p+a128k", "1080p+a128k", "720p+a256k", "1080p+a256k"])
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_audio_multi_lang(self, mpdClass):
@ -116,13 +104,10 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(
sorted(streams.keys()),
sorted(["720p", "1080p"]),
)
assert sorted(streams.keys()) == sorted(["720p", "1080p"])
self.assertEqual(streams["720p"].audio_representation.lang, "en")
self.assertEqual(streams["1080p"].audio_representation.lang, "en")
assert streams["720p"].audio_representation.lang == "en"
assert streams["1080p"].audio_representation.lang == "en"
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_audio_multi_lang_alpha3(self, mpdClass):
@ -141,13 +126,10 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(
sorted(streams.keys()),
sorted(["720p", "1080p"]),
)
assert sorted(streams.keys()) == sorted(["720p", "1080p"])
self.assertEqual(streams["720p"].audio_representation.lang, "eng")
self.assertEqual(streams["1080p"].audio_representation.lang, "eng")
assert streams["720p"].audio_representation.lang == "eng"
assert streams["1080p"].audio_representation.lang == "eng"
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_audio_invalid_lang(self, mpdClass):
@ -165,13 +147,10 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(
sorted(streams.keys()),
sorted(["720p", "1080p"]),
)
assert sorted(streams.keys()) == sorted(["720p", "1080p"])
self.assertEqual(streams["720p"].audio_representation.lang, "en_no_voice")
self.assertEqual(streams["1080p"].audio_representation.lang, "en_no_voice")
assert streams["720p"].audio_representation.lang == "en_no_voice"
assert streams["1080p"].audio_representation.lang == "en_no_voice"
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_audio_multi_lang_locale(self, mpdClass):
@ -193,21 +172,17 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(
sorted(streams.keys()),
sorted(["720p", "1080p"]),
)
assert sorted(streams.keys()) == sorted(["720p", "1080p"])
self.assertEqual(streams["720p"].audio_representation.lang, "es")
self.assertEqual(streams["1080p"].audio_representation.lang, "es")
assert streams["720p"].audio_representation.lang == "es"
assert streams["1080p"].audio_representation.lang == "es"
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_drm(self, mpdClass):
mpdClass.return_value = Mock(periods=[Mock(adaptationSets=[Mock(contentProtection="DRM")])])
self.assertRaises(PluginError,
DASHStream.parse_manifest,
self.session, self.test_url)
with pytest.raises(PluginError):
DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
def test_parse_manifest_string(self):
@ -215,7 +190,7 @@ class TestDASHStream(unittest.TestCase):
test_manifest = mpd_txt.read()
streams = DASHStream.parse_manifest(self.session, test_manifest)
self.assertSequenceEqual(list(streams.keys()), ["2500k"])
assert list(streams.keys()) == ["2500k"]
@patch("streamlink.stream.dash.DASHStreamReader")
@patch("streamlink.stream.dash.FFMPEGMuxer")
@ -237,12 +212,16 @@ class TestDASHStream(unittest.TestCase):
stream.open()
self.assertSequenceEqual(reader.mock_calls, [call(stream, 1, "video/mp4"),
call().open(),
call(stream, 2, "audio/mp3"),
call().open()])
self.assertSequenceEqual(muxer.mock_calls, [call(self.session, open_reader, open_reader, copyts=True),
call().open()])
assert reader.mock_calls == [
call(stream, 1, "video/mp4"),
call().open(),
call(stream, 2, "audio/mp3"),
call().open(),
]
assert muxer.mock_calls == [
call(self.session, open_reader, open_reader, copyts=True),
call().open(),
]
@patch("streamlink.stream.dash.MPD")
def test_segments_number_time(self, mpdClass):
@ -252,7 +231,7 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(list(streams.keys()), ["2500k"])
assert list(streams.keys()) == ["2500k"]
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_with_duplicated_resolutions(self, mpdClass):
@ -274,10 +253,7 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertSequenceEqual(
sorted(streams.keys()),
sorted(["720p", "1080p", "1080p_alt", "1080p_alt2"]),
)
assert sorted(streams.keys()) == sorted(["720p", "1080p", "1080p_alt", "1080p_alt2"])
@patch("streamlink.stream.dash.MPD")
def test_parse_manifest_with_duplicated_resolutions_sorted_bandwidth(self, mpdClass):
@ -298,9 +274,9 @@ class TestDASHStream(unittest.TestCase):
streams = DASHStream.parse_manifest(self.session, self.test_url)
mpdClass.assert_called_with(ANY, base_url="http://test.bar", url="http://test.bar/foo.mpd")
self.assertEqual(streams["1080p"].video_representation.bandwidth, 128.0)
self.assertEqual(streams["1080p_alt"].video_representation.bandwidth, 64.0)
self.assertEqual(streams["1080p_alt2"].video_representation.bandwidth, 32.0)
assert streams["1080p"].video_representation.bandwidth == pytest.approx(128.0)
assert streams["1080p_alt"].video_representation.bandwidth == pytest.approx(64.0)
assert streams["1080p_alt2"].video_representation.bandwidth == pytest.approx(32.0)
class TestDASHStreamWorker:

View File

@ -4,6 +4,7 @@ import unittest
from operator import attrgetter
from unittest.mock import Mock
import pytest
from freezegun import freeze_time
from freezegun.api import FakeDatetime # type: ignore[attr-defined]
@ -13,57 +14,50 @@ from tests.resources import xml
class TestMPDParsers(unittest.TestCase):
def test_utc(self):
self.assertEqual(utc.tzname(None), "UTC")
self.assertEqual(utc.dst(None), datetime.timedelta(0))
self.assertEqual(utc.utcoffset(None), datetime.timedelta(0))
assert utc.tzname(None) == "UTC"
assert utc.dst(None) == datetime.timedelta(0)
assert utc.utcoffset(None) == datetime.timedelta(0)
def test_bool_str(self):
self.assertEqual(MPDParsers.bool_str("true"), True)
self.assertEqual(MPDParsers.bool_str("TRUE"), True)
self.assertEqual(MPDParsers.bool_str("True"), True)
assert MPDParsers.bool_str("true")
assert MPDParsers.bool_str("TRUE")
assert MPDParsers.bool_str("True")
self.assertEqual(MPDParsers.bool_str("0"), False)
self.assertEqual(MPDParsers.bool_str("False"), False)
self.assertEqual(MPDParsers.bool_str("false"), False)
self.assertEqual(MPDParsers.bool_str("FALSE"), False)
assert not MPDParsers.bool_str("0")
assert not MPDParsers.bool_str("False")
assert not MPDParsers.bool_str("false")
assert not MPDParsers.bool_str("FALSE")
def test_type(self):
self.assertEqual(MPDParsers.type("dynamic"), "dynamic")
self.assertEqual(MPDParsers.type("static"), "static")
with self.assertRaises(MPDParsingError):
assert MPDParsers.type("dynamic") == "dynamic"
assert MPDParsers.type("static") == "static"
with pytest.raises(MPDParsingError):
MPDParsers.type("other")
def test_duration(self):
self.assertEqual(MPDParsers.duration("PT1S"), datetime.timedelta(0, 1))
assert MPDParsers.duration("PT1S") == datetime.timedelta(0, 1)
def test_datetime(self):
self.assertEqual(MPDParsers.datetime("2018-01-01T00:00:00Z"),
datetime.datetime(2018, 1, 1, 0, 0, 0, tzinfo=utc))
assert MPDParsers.datetime("2018-01-01T00:00:00Z") == datetime.datetime(2018, 1, 1, 0, 0, 0, tzinfo=utc)
def test_segment_template(self):
self.assertEqual(MPDParsers.segment_template("$Time$-$Number$-$Other$")(Time=1, Number=2, Other=3),
"1-2-3")
self.assertEqual(MPDParsers.segment_template("$Number%05d$")(Number=123),
"00123")
self.assertEqual(MPDParsers.segment_template("$Time%0.02f$")(Time=100.234),
"100.23")
assert MPDParsers.segment_template("$Time$-$Number$-$Other$")(Time=1, Number=2, Other=3) == "1-2-3"
assert MPDParsers.segment_template("$Number%05d$")(Number=123) == "00123"
assert MPDParsers.segment_template("$Time%0.02f$")(Time=100.234) == "100.23"
def test_frame_rate(self):
self.assertAlmostEqual(MPDParsers.frame_rate("1/25"),
1 / 25.0)
self.assertAlmostEqual(MPDParsers.frame_rate("0.2"),
0.2)
assert MPDParsers.frame_rate("1/25") == pytest.approx(1.0 / 25.0)
assert MPDParsers.frame_rate("0.2") == pytest.approx(0.2)
def test_timedelta(self):
self.assertEqual(MPDParsers.timedelta(1)(100),
datetime.timedelta(0, 100.0))
self.assertEqual(MPDParsers.timedelta(10)(100),
datetime.timedelta(0, 10.0))
assert MPDParsers.timedelta(1)(100) == datetime.timedelta(0, 100.0)
assert MPDParsers.timedelta(10)(100) == datetime.timedelta(0, 10.0)
def test_range(self):
self.assertEqual(MPDParsers.range("100-"), (100, None))
self.assertEqual(MPDParsers.range("100-199"), (100, 100))
self.assertRaises(MPDParsingError, MPDParsers.range, "100")
assert MPDParsers.range("100-") == (100, None)
assert MPDParsers.range("100-199") == (100, 100)
with pytest.raises(MPDParsingError):
MPDParsers.range("100")
class TestMPDParser(unittest.TestCase):
@ -75,14 +69,15 @@ class TestMPDParser(unittest.TestCase):
segments = mpd.periods[0].adaptationSets[0].representations[0].segments()
init_segment = next(segments)
self.assertEqual(init_segment.url, "http://test.se/tracks-v3/init-1526842800.g_m4v")
assert init_segment.url == "http://test.se/tracks-v3/init-1526842800.g_m4v"
video_segments = list(map(attrgetter("url"), (itertools.islice(segments, 5))))
# suggested delay is 11 seconds, each segment is 5 seconds long - so there should be 3
self.assertSequenceEqual(video_segments,
["http://test.se/tracks-v3/dvr-1526842800-698.g_m4v?t=3403000",
"http://test.se/tracks-v3/dvr-1526842800-699.g_m4v?t=3408000",
"http://test.se/tracks-v3/dvr-1526842800-700.g_m4v?t=3413000"])
assert video_segments == [
"http://test.se/tracks-v3/dvr-1526842800-698.g_m4v?t=3403000",
"http://test.se/tracks-v3/dvr-1526842800-699.g_m4v?t=3408000",
"http://test.se/tracks-v3/dvr-1526842800-700.g_m4v?t=3413000",
]
def test_segments_static_number(self):
with xml("dash/test_2.mpd") as mpd_xml:
@ -90,16 +85,17 @@ class TestMPDParser(unittest.TestCase):
segments = mpd.periods[0].adaptationSets[3].representations[0].segments()
init_segment = next(segments)
self.assertEqual(init_segment.url, "http://test.se/video/250kbit/init.mp4")
assert init_segment.url == "http://test.se/video/250kbit/init.mp4"
video_segments = list(map(attrgetter("url"), (itertools.islice(segments, 100000))))
self.assertEqual(len(video_segments), 444)
self.assertSequenceEqual(video_segments[:5],
["http://test.se/video/250kbit/segment_1.m4s",
"http://test.se/video/250kbit/segment_2.m4s",
"http://test.se/video/250kbit/segment_3.m4s",
"http://test.se/video/250kbit/segment_4.m4s",
"http://test.se/video/250kbit/segment_5.m4s"])
assert len(video_segments) == 444
assert video_segments[:5] == [
"http://test.se/video/250kbit/segment_1.m4s",
"http://test.se/video/250kbit/segment_2.m4s",
"http://test.se/video/250kbit/segment_3.m4s",
"http://test.se/video/250kbit/segment_4.m4s",
"http://test.se/video/250kbit/segment_5.m4s",
]
def test_segments_dynamic_time(self):
with xml("dash/test_3.mpd") as mpd_xml:
@ -107,12 +103,13 @@ class TestMPDParser(unittest.TestCase):
segments = mpd.periods[0].adaptationSets[0].representations[0].segments()
init_segment = next(segments)
self.assertEqual(init_segment.url, "http://test.se/video-2800000-0.mp4?z32=")
assert init_segment.url == "http://test.se/video-2800000-0.mp4?z32="
video_segments = list(map(attrgetter("url"), (itertools.islice(segments, 3))))
# default suggested delay is 3 seconds, each segment is 4 seconds long - so there should be 1 segment
self.assertSequenceEqual(video_segments,
["http://test.se/video-time=1525450872000-2800000-0.m4s?z32="])
assert video_segments == [
"http://test.se/video-time=1525450872000-2800000-0.m4s?z32=",
]
def test_segments_dynamic_number(self):
with freeze_time(FakeDatetime(2018, 5, 22, 13, 37, 0, tzinfo=utc)):
@ -121,7 +118,7 @@ class TestMPDParser(unittest.TestCase):
segments = mpd.periods[0].adaptationSets[0].representations[0].segments()
init_segment = next(segments)
self.assertEqual(init_segment.url, "http://test.se/hd-5-init.mp4")
assert init_segment.url == "http://test.se/hd-5-init.mp4"
video_segments = []
for _ in range(3):
@ -129,14 +126,20 @@ class TestMPDParser(unittest.TestCase):
video_segments.append((seg.url,
seg.available_at))
self.assertSequenceEqual(video_segments,
[("http://test.se/hd-5_000311235.mp4",
datetime.datetime(2018, 5, 22, 13, 37, 0, tzinfo=utc)),
("http://test.se/hd-5_000311236.mp4",
datetime.datetime(2018, 5, 22, 13, 37, 5, tzinfo=utc)),
("http://test.se/hd-5_000311237.mp4",
datetime.datetime(2018, 5, 22, 13, 37, 10, tzinfo=utc)),
])
assert video_segments == [
(
"http://test.se/hd-5_000311235.mp4",
datetime.datetime(2018, 5, 22, 13, 37, 0, tzinfo=utc),
),
(
"http://test.se/hd-5_000311236.mp4",
datetime.datetime(2018, 5, 22, 13, 37, 5, tzinfo=utc),
),
(
"http://test.se/hd-5_000311237.mp4",
datetime.datetime(2018, 5, 22, 13, 37, 10, tzinfo=utc),
),
]
def test_segments_static_no_publish_time(self):
with xml("dash/test_5.mpd") as mpd_xml:
@ -144,14 +147,14 @@ class TestMPDParser(unittest.TestCase):
segments = mpd.periods[0].adaptationSets[1].representations[0].segments()
init_segment = next(segments)
self.assertEqual(init_segment.url, "http://test.se/dash/150633-video_eng=194000.dash")
assert init_segment.url == "http://test.se/dash/150633-video_eng=194000.dash"
video_segments = [x.url for x in itertools.islice(segments, 3)]
self.assertSequenceEqual(video_segments,
["http://test.se/dash/150633-video_eng=194000-0.dash",
"http://test.se/dash/150633-video_eng=194000-2000.dash",
"http://test.se/dash/150633-video_eng=194000-4000.dash",
])
assert video_segments == [
"http://test.se/dash/150633-video_eng=194000-0.dash",
"http://test.se/dash/150633-video_eng=194000-2000.dash",
"http://test.se/dash/150633-video_eng=194000-4000.dash",
]
def test_segments_list(self):
with xml("dash/test_7.mpd") as mpd_xml:
@ -159,14 +162,14 @@ class TestMPDParser(unittest.TestCase):
segments = mpd.periods[0].adaptationSets[0].representations[0].segments()
init_segment = next(segments)
self.assertEqual(init_segment.url, "http://test.se/chunk_ctvideo_ridp0va0br4332748_cinit_mpd.m4s")
assert init_segment.url == "http://test.se/chunk_ctvideo_ridp0va0br4332748_cinit_mpd.m4s"
video_segments = [x.url for x in itertools.islice(segments, 3)]
self.assertSequenceEqual(video_segments,
["http://test.se/chunk_ctvideo_ridp0va0br4332748_cn1_mpd.m4s",
"http://test.se/chunk_ctvideo_ridp0va0br4332748_cn2_mpd.m4s",
"http://test.se/chunk_ctvideo_ridp0va0br4332748_cn3_mpd.m4s",
])
assert video_segments == [
"http://test.se/chunk_ctvideo_ridp0va0br4332748_cn1_mpd.m4s",
"http://test.se/chunk_ctvideo_ridp0va0br4332748_cn2_mpd.m4s",
"http://test.se/chunk_ctvideo_ridp0va0br4332748_cn3_mpd.m4s",
]
def test_segments_dynamic_timeline_continue(self):
with xml("dash/test_6_p1.mpd") as mpd_xml_p1:
@ -175,15 +178,16 @@ class TestMPDParser(unittest.TestCase):
segments_p1 = mpd_p1.periods[0].adaptationSets[0].representations[0].segments()
init_segment = next(segments_p1)
self.assertEqual(init_segment.url, "http://test.se/video/init.mp4")
assert init_segment.url == "http://test.se/video/init.mp4"
video_segments_p1 = [x.url for x in itertools.islice(segments_p1, 100)]
self.assertSequenceEqual(video_segments_p1,
["http://test.se/video/1006000.mp4",
"http://test.se/video/1007000.mp4",
"http://test.se/video/1008000.mp4",
"http://test.se/video/1009000.mp4",
"http://test.se/video/1010000.mp4"])
assert video_segments_p1 == [
"http://test.se/video/1006000.mp4",
"http://test.se/video/1007000.mp4",
"http://test.se/video/1008000.mp4",
"http://test.se/video/1009000.mp4",
"http://test.se/video/1010000.mp4",
]
# Continue in the next manifest
mpd_p2 = MPD(mpd_xml_p2,
@ -193,12 +197,13 @@ class TestMPDParser(unittest.TestCase):
segments_p2 = mpd_p2.periods[0].adaptationSets[0].representations[0].segments(init=False)
video_segments_p2 = [x.url for x in itertools.islice(segments_p2, 100)]
self.assertSequenceEqual(video_segments_p2,
["http://test.se/video/1011000.mp4",
"http://test.se/video/1012000.mp4",
"http://test.se/video/1013000.mp4",
"http://test.se/video/1014000.mp4",
"http://test.se/video/1015000.mp4"])
assert video_segments_p2 == [
"http://test.se/video/1011000.mp4",
"http://test.se/video/1012000.mp4",
"http://test.se/video/1013000.mp4",
"http://test.se/video/1014000.mp4",
"http://test.se/video/1015000.mp4",
]
def test_tsegment_t_is_none_1895(self):
"""
@ -209,14 +214,14 @@ class TestMPDParser(unittest.TestCase):
segments = mpd.periods[0].adaptationSets[0].representations[0].segments()
init_segment = next(segments)
self.assertEqual(init_segment.url, "http://test.se/video-2799000-0.mp4?z32=CENSORED_SESSION")
assert init_segment.url == "http://test.se/video-2799000-0.mp4?z32=CENSORED_SESSION"
video_segments = [x.url for x in itertools.islice(segments, 3)]
self.assertSequenceEqual(video_segments,
["http://test.se/video-time=0-2799000-0.m4s?z32=CENSORED_SESSION",
"http://test.se/video-time=4000-2799000-0.m4s?z32=CENSORED_SESSION",
"http://test.se/video-time=8000-2799000-0.m4s?z32=CENSORED_SESSION",
])
assert video_segments == [
"http://test.se/video-time=0-2799000-0.m4s?z32=CENSORED_SESSION",
"http://test.se/video-time=4000-2799000-0.m4s?z32=CENSORED_SESSION",
"http://test.se/video-time=8000-2799000-0.m4s?z32=CENSORED_SESSION",
]
def test_bitrate_rounded(self):
def mock_rep(bandwidth):
@ -231,10 +236,10 @@ class TestMPDParser(unittest.TestCase):
node.findall.return_value = []
return Representation(node)
self.assertEqual(mock_rep(1.2 * 1000.0).bandwidth_rounded, 1.2)
self.assertEqual(mock_rep(45.6 * 1000.0).bandwidth_rounded, 46.0)
self.assertEqual(mock_rep(134.0 * 1000.0).bandwidth_rounded, 130.0)
self.assertEqual(mock_rep(1324.0 * 1000.0).bandwidth_rounded, 1300.0)
assert mock_rep(1.2 * 1000.0).bandwidth_rounded == pytest.approx(1.2)
assert mock_rep(45.6 * 1000.0).bandwidth_rounded == pytest.approx(46.0)
assert mock_rep(134.0 * 1000.0).bandwidth_rounded == pytest.approx(130.0)
assert mock_rep(1324.0 * 1000.0).bandwidth_rounded == pytest.approx(1300.0)
def test_duplicated_resolutions(self):
"""
@ -244,11 +249,11 @@ class TestMPDParser(unittest.TestCase):
mpd = MPD(mpd_xml, base_url="http://test.se/", url="http://test.se/manifest.mpd")
representations_0 = mpd.periods[0].adaptationSets[0].representations[0]
self.assertEqual(representations_0.height, 804)
self.assertEqual(representations_0.bandwidth, 10000.0)
assert representations_0.height == 804
assert representations_0.bandwidth == pytest.approx(10000.0)
representations_1 = mpd.periods[0].adaptationSets[0].representations[1]
self.assertEqual(representations_1.height, 804)
self.assertEqual(representations_1.bandwidth, 8000.0)
assert representations_1.height == 804
assert representations_1.bandwidth == pytest.approx(8000.0)
def test_segments_static_periods_duration(self):
"""
@ -257,4 +262,4 @@ class TestMPDParser(unittest.TestCase):
with xml("dash/test_11_static.mpd") as mpd_xml:
mpd = MPD(mpd_xml, base_url="http://test.se/", url="http://test.se/manifest.mpd")
duration = mpd.periods[0].duration.total_seconds()
self.assertEqual(duration, 204.32)
assert duration == pytest.approx(204.32)

View File

@ -19,4 +19,4 @@ class TestFileStream(unittest.TestCase):
def test_open_fileobj(self):
fileobj = Mock()
s = FileStream(self.session, fileobj=fileobj)
self.assertEqual(fileobj, s.open())
assert s.open() is fileobj

View File

@ -72,10 +72,10 @@ class TestHLSStreamRepr(unittest.TestCase):
session = Streamlink()
stream = HLSStream(session, "https://foo.bar/playlist.m3u8")
self.assertEqual(repr(stream), "<HLSStream ['hls', 'https://foo.bar/playlist.m3u8']>")
assert repr(stream) == "<HLSStream ['hls', 'https://foo.bar/playlist.m3u8']>"
stream = HLSStream(session, "https://foo.bar/playlist.m3u8", "https://foo.bar/master.m3u8")
self.assertEqual(repr(stream), "<HLSStream ['hls', 'https://foo.bar/playlist.m3u8', 'https://foo.bar/master.m3u8']>")
assert repr(stream) == "<HLSStream ['hls', 'https://foo.bar/playlist.m3u8', 'https://foo.bar/master.m3u8']>"
class TestHLSVariantPlaylist(unittest.TestCase):
@ -139,9 +139,9 @@ class TestHLSStream(TestMixinStreamHLS, unittest.TestCase):
], streamoptions={"start_offset": 1, "duration": 1})
data = self.await_read(read_all=True)
self.assertEqual(data, self.content(segments, cond=lambda s: 0 < s.num < 3), "Respects the offset and duration")
self.assertTrue(all(self.called(s) for s in segments.values() if 0 < s.num < 3), "Downloads second and third segment")
self.assertFalse(any(self.called(s) for s in segments.values() if 0 > s.num > 3), "Skips other segments")
assert data == self.content(segments, cond=lambda s: 0 < s.num < 3), "Respects the offset and duration"
assert all(self.called(s) for s in segments.values() if 0 < s.num < 3), "Downloads second and third segment"
assert not any(self.called(s) for s in segments.values() if 0 > s.num > 3), "Skips other segments"
def test_map(self):
discontinuity = Tag("EXT-X-DISCONTINUITY")
@ -156,12 +156,12 @@ class TestHLSStream(TestMixinStreamHLS, unittest.TestCase):
])
data = self.await_read(read_all=True, timeout=None)
self.assertEqual(data, self.content([
assert data == self.content([
map1, segments[1], map1, segments[2], map1, segments[3],
map1, segments[4], map2, segments[5], map2, segments[6], segments[7],
]))
self.assertTrue(self.called(map1, once=True), "Downloads first map only once")
self.assertTrue(self.called(map2, once=True), "Downloads second map only once")
])
assert self.called(map1, once=True), "Downloads first map only once"
assert self.called(map2, once=True), "Downloads second map only once"
@patch("streamlink.stream.hls.HLSStreamWorker.wait", Mock(return_value=True))
@ -186,10 +186,10 @@ class TestHLSStreamByterange(TestMixinStreamHLS, unittest.TestCase):
self.await_write(2 - 1)
self.thread.close()
self.assertEqual(mock_log.error.call_args_list, [
assert mock_log.error.call_args_list == [
call("Failed to fetch segment 0: Missing BYTERANGE offset"),
])
self.assertFalse(self.called(Segment(0)))
]
assert not self.called(Segment(0))
@patch("streamlink.stream.hls.log")
def test_unknown_offset_map(self, mock_log: Mock):
@ -206,10 +206,10 @@ class TestHLSStreamByterange(TestMixinStreamHLS, unittest.TestCase):
self.await_write(3 - 1)
self.thread.close()
self.assertEqual(mock_log.error.call_args_list, [
assert mock_log.error.call_args_list == [
call("Failed to fetch map for segment 1: Missing BYTERANGE offset"),
])
self.assertFalse(self.called(map1))
]
assert not self.called(map1)
@patch("streamlink.stream.hls.log")
def test_invalid_offset_reference(self, mock_log: Mock):
@ -225,11 +225,11 @@ class TestHLSStreamByterange(TestMixinStreamHLS, unittest.TestCase):
self.await_write(4 - 1)
self.thread.close()
self.assertEqual(mock_log.error.call_args_list, [
assert mock_log.error.call_args_list == [
call("Failed to fetch segment 2: Missing BYTERANGE offset"),
])
self.assertEqual(self.mocks[self.url(Segment(0))].last_request._request.headers["Range"], "bytes=0-2")
self.assertFalse(self.called(Segment(2)))
]
assert self.mocks[self.url(Segment(0))].last_request._request.headers["Range"] == "bytes=0-2"
assert not self.called(Segment(2))
def test_offsets(self):
map1 = TagMap(1, self.id(), {"BYTERANGE": "\"1234@0\""})
@ -252,13 +252,13 @@ class TestHLSStreamByterange(TestMixinStreamHLS, unittest.TestCase):
self.await_write(5 * 2)
self.await_read(read_all=True)
self.assertEqual(self.mocks[self.url(map1)].last_request._request.headers["Range"], "bytes=0-1233")
self.assertEqual(self.mocks[self.url(map2)].last_request._request.headers["Range"], "bytes=1337-1378")
self.assertEqual(self.mocks[self.url(s1)].last_request._request.headers["Range"], "bytes=3-7")
self.assertEqual(self.mocks[self.url(s2)].last_request._request.headers["Range"], "bytes=8-14")
self.assertEqual(self.mocks[self.url(s3)].last_request._request.headers["Range"], "bytes=15-25")
self.assertEqual(self.mocks[self.url(s4)].last_request._request.headers["Range"], "bytes=13-29")
self.assertEqual(self.mocks[self.url(s5)].last_request._request.headers["Range"], "bytes=30-48")
assert self.mocks[self.url(map1)].last_request._request.headers["Range"] == "bytes=0-1233"
assert self.mocks[self.url(map2)].last_request._request.headers["Range"] == "bytes=1337-1378"
assert self.mocks[self.url(s1)].last_request._request.headers["Range"] == "bytes=3-7"
assert self.mocks[self.url(s2)].last_request._request.headers["Range"] == "bytes=8-14"
assert self.mocks[self.url(s3)].last_request._request.headers["Range"] == "bytes=15-25"
assert self.mocks[self.url(s4)].last_request._request.headers["Range"] == "bytes=13-29"
assert self.mocks[self.url(s5)].last_request._request.headers["Range"] == "bytes=30-48"
@patch("streamlink.stream.hls.HLSStreamWorker.wait", Mock(return_value=True))
@ -324,12 +324,12 @@ class TestHLSStreamEncrypted(TestMixinStreamHLS, unittest.TestCase):
self.await_write(3 + 4)
data = self.await_read(read_all=True)
expected = self.content(segments, prop="content_plain", cond=lambda s: s.num >= 1)
self.assertEqual(data, expected, "Decrypts the AES-128 identity stream")
self.assertTrue(self.called(key, once=True), "Downloads encryption key only once")
self.assertEqual(self.get_mock(key).last_request._request.headers.get("X-FOO"), "BAR")
self.assertFalse(any(self.called(s) for s in segments.values() if s.num < 1), "Skips first segment")
self.assertTrue(all(self.called(s) for s in segments.values() if s.num >= 1), "Downloads all remaining segments")
self.assertEqual(self.get_mock(segments[1]).last_request._request.headers.get("X-FOO"), "BAR")
assert data == expected, "Decrypts the AES-128 identity stream"
assert self.called(key, once=True), "Downloads encryption key only once"
assert self.get_mock(key).last_request._request.headers.get("X-FOO") == "BAR"
assert not any(self.called(s) for s in segments.values() if s.num < 1), "Skips first segment"
assert all(self.called(s) for s in segments.values() if s.num >= 1), "Downloads all remaining segments"
assert self.get_mock(segments[1]).last_request._request.headers.get("X-FOO") == "BAR"
def test_hls_encrypted_aes128_with_map(self):
aesKey, aesIv, key = self.gen_key()
@ -346,9 +346,9 @@ class TestHLSStreamEncrypted(TestMixinStreamHLS, unittest.TestCase):
self.await_write(2 * 2 + 2 * 2)
data = self.await_read(read_all=True)
self.assertEqual(data, self.content([
assert data == self.content([
map1, segments[0], map1, segments[1], map2, segments[2], map2, segments[3],
], prop="content_plain"))
], prop="content_plain")
def test_hls_encrypted_aes128_key_uri_override(self):
aesKey, aesIv, key = self.gen_key(uri="http://real-mocked/{namespace}/encryption.key?foo=bar")
@ -364,10 +364,10 @@ class TestHLSStreamEncrypted(TestMixinStreamHLS, unittest.TestCase):
self.await_write(3 + 4)
data = self.await_read(read_all=True)
expected = self.content(segments, prop="content_plain", cond=lambda s: s.num >= 1)
self.assertEqual(data, expected, "Decrypts stream from custom key")
self.assertFalse(self.called(key_invalid), "Skips encryption key")
self.assertTrue(self.called(key, once=True), "Downloads custom encryption key")
self.assertEqual(self.get_mock(key).last_request._request.headers.get("X-FOO"), "BAR")
assert data == expected, "Decrypts stream from custom key"
assert not self.called(key_invalid), "Skips encryption key"
assert self.called(key, once=True), "Downloads custom encryption key"
assert self.get_mock(key).last_request._request.headers.get("X-FOO") == "BAR"
@patch("streamlink.stream.hls.log")
def test_hls_encrypted_aes128_incorrect_block_length(self, mock_log: Mock):
@ -475,47 +475,47 @@ class TestHlsPlaylistReloadTime(TestMixinStreamHLS, unittest.TestCase):
def test_hls_playlist_reload_time_default(self):
time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="default")
self.assertEqual(time, 4, "default sets the reload time to the playlist's target duration")
assert time == 4, "default sets the reload time to the playlist's target duration"
def test_hls_playlist_reload_time_segment(self):
time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="segment")
self.assertEqual(time, 3, "segment sets the reload time to the playlist's last segment")
assert time == 3, "segment sets the reload time to the playlist's last segment"
def test_hls_playlist_reload_time_segment_no_segments(self):
time = self.subject([Playlist(0, [], end=True, targetduration=4)], reload_time="segment")
self.assertEqual(time, 4, "segment sets the reload time to the targetduration if no segments are available")
assert time == 4, "segment sets the reload time to the targetduration if no segments are available"
def test_hls_playlist_reload_time_segment_no_segments_no_targetduration(self):
time = self.subject([Playlist(0, [], end=True, targetduration=0)], reload_time="segment")
self.assertEqual(time, 6, "sets reload time to 6 seconds when no segments and no targetduration are available")
assert time == 6, "sets reload time to 6 seconds when no segments and no targetduration are available"
def test_hls_playlist_reload_time_live_edge(self):
time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="live-edge")
self.assertEqual(time, 8, "live-edge sets the reload time to the sum of the number of segments of the live-edge")
assert time == 8, "live-edge sets the reload time to the sum of the number of segments of the live-edge"
def test_hls_playlist_reload_time_live_edge_no_segments(self):
time = self.subject([Playlist(0, [], end=True, targetduration=4)], reload_time="live-edge")
self.assertEqual(time, 4, "live-edge sets the reload time to the targetduration if no segments are available")
assert time == 4, "live-edge sets the reload time to the targetduration if no segments are available"
def test_hls_playlist_reload_time_live_edge_no_segments_no_targetduration(self):
time = self.subject([Playlist(0, [], end=True, targetduration=0)], reload_time="live-edge")
self.assertEqual(time, 6, "sets reload time to 6 seconds when no segments and no targetduration are available")
assert time == 6, "sets reload time to 6 seconds when no segments and no targetduration are available"
def test_hls_playlist_reload_time_number(self):
time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="2")
self.assertEqual(time, 2, "number values override the reload time")
assert time == 2, "number values override the reload time"
def test_hls_playlist_reload_time_number_invalid(self):
time = self.subject([Playlist(0, self.segments, end=True, targetduration=4)], reload_time="0")
self.assertEqual(time, 4, "invalid number values set the reload time to the playlist's targetduration")
assert time == 4, "invalid number values set the reload time to the playlist's targetduration"
def test_hls_playlist_reload_time_no_target_duration(self):
time = self.subject([Playlist(0, self.segments, end=True, targetduration=0)], reload_time="default")
self.assertEqual(time, 8, "uses the live-edge sum if the playlist is missing the targetduration data")
assert time == 8, "uses the live-edge sum if the playlist is missing the targetduration data"
def test_hls_playlist_reload_time_no_data(self):
time = self.subject([Playlist(0, [], end=True, targetduration=0)], reload_time="default")
self.assertEqual(time, 6, "sets reload time to 6 seconds when no data is available")
assert time == 6, "sets reload time to 6 seconds when no data is available"
@patch("streamlink.stream.hls.log")
@ -533,11 +533,11 @@ class TestHlsPlaylistParseErrors(TestMixinStreamHLS, unittest.TestCase):
def test_generic(self, mock_log):
self.subject([self.InvalidPlaylist()])
self.assertEqual(self.await_read(read_all=True), b"")
assert self.await_read(read_all=True) == b""
self.await_close()
self.assertTrue(self.thread.reader.buffer.closed, "Closes the stream on initial playlist parsing error")
self.assertEqual(mock_log.debug.mock_calls, [call("Reloading playlist")])
self.assertEqual(mock_log.error.mock_calls, [call("Missing #EXTM3U header")])
assert self.thread.reader.buffer.closed, "Closes the stream on initial playlist parsing error"
assert mock_log.debug.mock_calls == [call("Reloading playlist")]
assert mock_log.error.mock_calls == [call("Missing #EXTM3U header")]
def test_reload(self, mock_log):
thread, segments = self.subject([
@ -548,33 +548,33 @@ class TestHlsPlaylistParseErrors(TestMixinStreamHLS, unittest.TestCase):
])
self.await_write(2)
data = self.await_read(read_all=True)
self.assertEqual(data, self.content(segments))
assert data == self.content(segments)
self.close()
self.await_close()
self.assertEqual(mock_log.warning.mock_calls, [
assert mock_log.warning.mock_calls == [
call("Failed to reload playlist: Missing #EXTM3U header"),
call("Failed to reload playlist: Missing #EXTM3U header"),
])
]
@patch("streamlink.stream.hls.HLSStreamWorker._reload_playlist", Mock(return_value=FakePlaylist(is_master=True)))
def test_is_master(self, mock_log):
self.subject([Playlist()])
self.assertEqual(self.await_read(read_all=True), b"")
assert self.await_read(read_all=True) == b""
self.await_close()
self.assertTrue(self.thread.reader.buffer.closed, "Closes the stream on initial playlist parsing error")
self.assertEqual(mock_log.debug.mock_calls, [call("Reloading playlist")])
self.assertEqual(mock_log.error.mock_calls, [
assert self.thread.reader.buffer.closed, "Closes the stream on initial playlist parsing error"
assert mock_log.debug.mock_calls == [call("Reloading playlist")]
assert mock_log.error.mock_calls == [
call(f"Attempted to play a variant playlist, use 'hls://{self.stream.url}' instead"),
])
]
@patch("streamlink.stream.hls.HLSStreamWorker._reload_playlist", Mock(return_value=FakePlaylist(iframes_only=True)))
def test_iframes_only(self, mock_log):
self.subject([Playlist()])
self.assertEqual(self.await_read(read_all=True), b"")
assert self.await_read(read_all=True) == b""
self.await_close()
self.assertTrue(self.thread.reader.buffer.closed, "Closes the stream on initial playlist parsing error")
self.assertEqual(mock_log.debug.mock_calls, [call("Reloading playlist")])
self.assertEqual(mock_log.error.mock_calls, [call("Streams containing I-frames only are not playable")])
assert self.thread.reader.buffer.closed, "Closes the stream on initial playlist parsing error"
assert mock_log.debug.mock_calls == [call("Reloading playlist")]
assert mock_log.error.mock_calls == [call("Streams containing I-frames only are not playable")]
@patch("streamlink.stream.hls.FFMPEGMuxer.is_usable", Mock(return_value=True))
@ -621,7 +621,7 @@ class TestHlsExtAudio(unittest.TestCase):
result = [x.url for x in substreams]
# Check result
self.assertEqual(result, expected)
assert result == expected
def test_hls_ext_audio_es(self):
"""
@ -641,7 +641,7 @@ class TestHlsExtAudio(unittest.TestCase):
result = [x.url for x in substreams]
# Check result
self.assertEqual(result, expected)
assert result == expected
def test_hls_ext_audio_all(self):
"""
@ -661,7 +661,7 @@ class TestHlsExtAudio(unittest.TestCase):
result = [x.url for x in substreams]
# Check result
self.assertEqual(result, expected)
assert result == expected
def test_hls_ext_audio_wildcard(self):
master_url = "http://mocked/path/master.m3u8"
@ -676,7 +676,7 @@ class TestHlsExtAudio(unittest.TestCase):
result = [x.url for x in substreams]
# Check result
self.assertEqual(result, expected)
assert result == expected
class TestM3U8ParserLogging:

View File

@ -2,6 +2,8 @@ import unittest
from threading import Event
from unittest.mock import MagicMock, call, patch
import pytest
from streamlink.stream.hls import HLSStream, HLSStreamReader
from tests.mixins.stream_hls import EventedHLSStreamWriter, Playlist, Segment, TestMixinStreamHLS
@ -52,8 +54,8 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
self.await_write(2)
data = self.await_read()
self.assertEqual(data, self.content(segments), "Does not filter by default")
self.assertTrue(reader.filter_wait(timeout=0))
assert data == self.content(segments), "Does not filter by default"
assert reader.filter_wait(timeout=0)
@patch("streamlink.stream.hls.HLSStreamWriter.should_filter_sequence", new=filter_sequence)
@patch("streamlink.stream.hls.log")
@ -119,13 +121,12 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
self.await_write()
data = self.await_read()
self.assertEqual(data, segments[0].content, "Has read the first segment")
assert data == segments[0].content, "Has read the first segment"
# simulate a timeout by having an empty buffer
# timeout value is set to 0
with self.assertRaises(OSError) as cm:
with pytest.raises(OSError, match=r"^Read timeout$"):
self.await_read()
self.assertEqual(str(cm.exception), "Read timeout", "Raises a timeout error when no data is available to read")
@patch("streamlink.stream.hls.HLSStreamWriter.should_filter_sequence", new=filter_sequence)
def test_filtered_no_timeout(self):
@ -134,26 +135,26 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
Playlist(2, [Segment(2), Segment(3)], end=True),
])
self.assertFalse(reader.is_paused(), "Doesn't let the reader wait if not filtering")
assert not reader.is_paused(), "Doesn't let the reader wait if not filtering"
self.await_write(2)
self.assertTrue(reader.is_paused(), "Lets the reader wait if filtering")
assert reader.is_paused(), "Lets the reader wait if filtering"
# test the reader's filter_wait() method
self.assertFalse(reader.filter_wait(timeout=0), "Is filtering")
assert not reader.filter_wait(timeout=0), "Is filtering"
# make reader read (no data available yet)
thread.handshake.go()
# once data becomes available, the reader continues reading
self.await_write()
self.assertFalse(reader.is_paused(), "Reader is not waiting anymore")
assert not reader.is_paused(), "Reader is not waiting anymore"
assert thread.handshake.wait_done(TIMEOUT_HANDSHAKE), "Doesn't time out when filtering"
assert b"".join(thread.data) == segments[2].content, "Reads next available buffer data"
self.await_write()
data = self.await_read()
self.assertEqual(data, self.content(segments, cond=lambda s: s.num >= 2))
assert data == self.content(segments, cond=lambda s: s.num >= 2)
@patch("streamlink.stream.hls.HLSStreamWriter.should_filter_sequence", new=filter_sequence)
def test_filtered_closed(self):
@ -173,9 +174,9 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
self.start()
# write first filtered segment and trigger the event_filter's lock
self.assertFalse(reader.is_paused(), "Doesn't let the reader wait if not filtering")
assert not reader.is_paused(), "Doesn't let the reader wait if not filtering"
self.await_write()
self.assertTrue(reader.is_paused(), "Lets the reader wait if filtering")
assert reader.is_paused(), "Lets the reader wait if filtering"
# make reader read (no data available yet)
thread.handshake.go()
@ -197,4 +198,5 @@ class TestFilteredHLSStream(TestMixinStreamHLS, unittest.TestCase):
]})
self.await_write(4)
self.assertEqual(self.await_read(), self.content(segments, cond=lambda s: s.num % 2 > 0))
data = self.await_read()
assert data == self.content(segments, cond=lambda s: s.num % 2 > 0)

View File

@ -202,61 +202,171 @@ class TestHLSPlaylist(unittest.TestCase):
with text("hls/test_1.m3u8") as m3u8_fh:
playlist = load(m3u8_fh.read(), "http://test.se/")
self.assertEqual(
playlist.media,
[
Media(uri="http://test.se/audio/stereo/en/128kbit.m3u8", type="AUDIO", group_id="stereo",
language="en", name="English", default=True, autoselect=True, forced=False,
characteristics=None),
Media(uri="http://test.se/audio/stereo/none/128kbit.m3u8", type="AUDIO", group_id="stereo",
language="dubbing", name="Dubbing", default=False, autoselect=True, forced=False,
characteristics=None),
Media(uri="http://test.se/audio/surround/en/320kbit.m3u8", type="AUDIO", group_id="surround",
language="en", name="English", default=True, autoselect=True, forced=False,
characteristics=None),
Media(uri="http://test.se/audio/stereo/none/128kbit.m3u8", type="AUDIO", group_id="surround",
language="dubbing", name="Dubbing", default=False, autoselect=True, forced=False,
characteristics=None),
Media(uri="http://test.se/subtitles_de.m3u8", type="SUBTITLES", group_id="subs", language="de",
name="Deutsch", default=False, autoselect=True, forced=False, characteristics=None),
Media(uri="http://test.se/subtitles_en.m3u8", type="SUBTITLES", group_id="subs", language="en",
name="English", default=True, autoselect=True, forced=False, characteristics=None),
Media(uri="http://test.se/subtitles_es.m3u8", type="SUBTITLES", group_id="subs", language="es",
name="Espanol", default=False, autoselect=True, forced=False, characteristics=None),
Media(uri="http://test.se/subtitles_fr.m3u8", type="SUBTITLES", group_id="subs", language="fr",
name="Français", default=False, autoselect=True, forced=False, characteristics=None),
],
)
assert playlist.media == [
Media(
uri="http://test.se/audio/stereo/en/128kbit.m3u8",
type="AUDIO",
group_id="stereo",
language="en",
name="English",
default=True,
autoselect=True,
forced=False,
characteristics=None,
),
Media(
uri="http://test.se/audio/stereo/none/128kbit.m3u8",
type="AUDIO",
group_id="stereo",
language="dubbing",
name="Dubbing",
default=False,
autoselect=True,
forced=False,
characteristics=None,
),
Media(
uri="http://test.se/audio/surround/en/320kbit.m3u8",
type="AUDIO",
group_id="surround",
language="en",
name="English",
default=True,
autoselect=True,
forced=False,
characteristics=None,
),
Media(
uri="http://test.se/audio/stereo/none/128kbit.m3u8",
type="AUDIO",
group_id="surround",
language="dubbing",
name="Dubbing",
default=False,
autoselect=True,
forced=False,
characteristics=None,
),
Media(
uri="http://test.se/subtitles_de.m3u8",
type="SUBTITLES",
group_id="subs",
language="de",
name="Deutsch",
default=False,
autoselect=True,
forced=False,
characteristics=None,
),
Media(
uri="http://test.se/subtitles_en.m3u8",
type="SUBTITLES",
group_id="subs",
language="en",
name="English",
default=True,
autoselect=True,
forced=False,
characteristics=None,
),
Media(
uri="http://test.se/subtitles_es.m3u8",
type="SUBTITLES",
group_id="subs",
language="es",
name="Espanol",
default=False,
autoselect=True,
forced=False,
characteristics=None,
),
Media(
uri="http://test.se/subtitles_fr.m3u8",
type="SUBTITLES",
group_id="subs",
language="fr",
name="Français",
default=False,
autoselect=True,
forced=False,
characteristics=None,
),
]
self.assertEqual(
[p.stream_info for p in playlist.playlists],
[
StreamInfo(bandwidth=260000, program_id="1", codecs=["avc1.4d400d", "mp4a.40.2"],
resolution=Resolution(width=422, height=180), audio="stereo", video=None,
subtitles="subs"),
StreamInfo(bandwidth=520000, program_id="1", codecs=["avc1.4d4015", "mp4a.40.2"],
resolution=Resolution(width=638, height=272), audio="stereo", video=None,
subtitles="subs"),
StreamInfo(bandwidth=830000, program_id="1", codecs=["avc1.4d4015", "mp4a.40.2"],
resolution=Resolution(width=638, height=272), audio="stereo", video=None,
subtitles="subs"),
StreamInfo(bandwidth=1100000, program_id="1", codecs=["avc1.4d401f", "mp4a.40.2"],
resolution=Resolution(width=958, height=408), audio="surround", video=None,
subtitles="subs"),
StreamInfo(bandwidth=1600000, program_id="1", codecs=["avc1.4d401f", "mp4a.40.2"],
resolution=Resolution(width=1277, height=554), audio="surround", video=None,
subtitles="subs"),
StreamInfo(bandwidth=4100000, program_id="1", codecs=["avc1.4d4028", "mp4a.40.2"],
resolution=Resolution(width=1921, height=818), audio="surround", video=None,
subtitles="subs"),
StreamInfo(bandwidth=6200000, program_id="1", codecs=["avc1.4d4028", "mp4a.40.2"],
resolution=Resolution(width=1921, height=818), audio="surround", video=None,
subtitles="subs"),
StreamInfo(bandwidth=10000000, program_id="1", codecs=["avc1.4d4033", "mp4a.40.2"],
resolution=Resolution(width=4096, height=1744), audio="surround", video=None,
subtitles="subs"),
],
)
assert [p.stream_info for p in playlist.playlists] == [
StreamInfo(
bandwidth=260000,
program_id="1",
codecs=["avc1.4d400d", "mp4a.40.2"],
resolution=Resolution(width=422, height=180),
audio="stereo",
video=None,
subtitles="subs",
),
StreamInfo(
bandwidth=520000,
program_id="1",
codecs=["avc1.4d4015", "mp4a.40.2"],
resolution=Resolution(width=638, height=272),
audio="stereo",
video=None,
subtitles="subs",
),
StreamInfo(
bandwidth=830000,
program_id="1",
codecs=["avc1.4d4015", "mp4a.40.2"],
resolution=Resolution(width=638, height=272),
audio="stereo",
video=None,
subtitles="subs",
),
StreamInfo(
bandwidth=1100000,
program_id="1",
codecs=["avc1.4d401f", "mp4a.40.2"],
resolution=Resolution(width=958, height=408),
audio="surround",
video=None,
subtitles="subs",
),
StreamInfo(
bandwidth=1600000,
program_id="1",
codecs=["avc1.4d401f", "mp4a.40.2"],
resolution=Resolution(width=1277, height=554),
audio="surround",
video=None,
subtitles="subs",
),
StreamInfo(
bandwidth=4100000,
program_id="1",
codecs=["avc1.4d4028", "mp4a.40.2"],
resolution=Resolution(width=1921, height=818),
audio="surround",
video=None,
subtitles="subs",
),
StreamInfo(
bandwidth=6200000,
program_id="1",
codecs=["avc1.4d4028", "mp4a.40.2"],
resolution=Resolution(width=1921, height=818),
audio="surround",
video=None,
subtitles="subs",
),
StreamInfo(
bandwidth=10000000,
program_id="1",
codecs=["avc1.4d4033", "mp4a.40.2"],
resolution=Resolution(width=4096, height=1744),
audio="surround",
video=None,
subtitles="subs",
),
]
def test_parse_date(self):
with text("hls/test_date.m3u8") as m3u8_fh:
@ -268,70 +378,158 @@ class TestHLSPlaylist(unittest.TestCase):
delta_30 = timedelta(seconds=30, milliseconds=500)
delta_60 = timedelta(seconds=60)
self.assertEqual(playlist.target_duration, 120)
assert playlist.target_duration == 120
self.assertEqual(
list(playlist.dateranges),
[
DateRange(id="start-invalid",
start_date=None,
classname=None, end_date=None, duration=None, planned_duration=None, end_on_next=False, x={}),
DateRange(id="start-no-frac",
start_date=start_date,
classname=None, end_date=None, duration=None, planned_duration=None, end_on_next=False, x={}),
DateRange(id="start-with-frac",
start_date=start_date,
classname=None, end_date=None, duration=None, planned_duration=None, end_on_next=False, x={}),
DateRange(id="with-class",
start_date=start_date, classname="bar",
end_date=None, duration=None, planned_duration=None, end_on_next=False, x={}),
DateRange(id="duration",
start_date=start_date, duration=delta_30,
classname=None, end_date=None, planned_duration=None, end_on_next=False, x={}),
DateRange(id="planned-duration",
start_date=start_date, planned_duration=delta_15,
classname=None, end_date=None, duration=None, end_on_next=False, x={}),
DateRange(id="duration-precedence",
start_date=start_date, duration=delta_30, planned_duration=delta_15,
classname=None, end_date=None, end_on_next=False, x={}),
DateRange(id="end",
start_date=start_date, end_date=end_date,
classname=None, duration=None, planned_duration=None, end_on_next=False, x={}),
DateRange(id="end-precedence",
start_date=start_date, end_date=end_date, duration=delta_30,
classname=None, planned_duration=None, end_on_next=False, x={}),
DateRange(x={"X-CUSTOM": "value"},
id=None, start_date=None, end_date=None, duration=None,
classname=None, planned_duration=None, end_on_next=False),
],
)
self.assertEqual(
list(playlist.segments),
[
Segment(uri="http://test.se/segment0-15.ts", duration=15.0, title="live", date=start_date,
key=None, discontinuity=False, byterange=None, map=None),
Segment(uri="http://test.se/segment15-30.5.ts", duration=15.5, title="live", date=start_date + delta_15,
key=None, discontinuity=False, byterange=None, map=None),
Segment(uri="http://test.se/segment30.5-60.ts", duration=29.5, title="live", date=start_date + delta_30,
key=None, discontinuity=False, byterange=None, map=None),
Segment(uri="http://test.se/segment60-.ts", duration=60.0, title="live", date=start_date + delta_60,
key=None, discontinuity=False, byterange=None, map=None),
],
)
assert list(playlist.dateranges) == [
DateRange(
id="start-invalid",
start_date=None,
classname=None,
end_date=None,
duration=None,
planned_duration=None,
end_on_next=False,
x={},
),
DateRange(
id="start-no-frac",
start_date=start_date,
classname=None,
end_date=None,
duration=None,
planned_duration=None,
end_on_next=False,
x={},
),
DateRange(
id="start-with-frac",
start_date=start_date,
classname=None,
end_date=None,
duration=None,
planned_duration=None,
end_on_next=False,
x={},
),
DateRange(
id="with-class",
start_date=start_date,
classname="bar",
end_date=None,
duration=None,
planned_duration=None,
end_on_next=False,
x={},
),
DateRange(
id="duration",
start_date=start_date,
duration=delta_30,
classname=None,
end_date=None,
planned_duration=None,
end_on_next=False,
x={},
),
DateRange(
id="planned-duration",
start_date=start_date,
planned_duration=delta_15,
classname=None,
end_date=None,
duration=None,
end_on_next=False,
x={},
),
DateRange(
id="duration-precedence",
start_date=start_date,
duration=delta_30,
planned_duration=delta_15,
classname=None,
end_date=None,
end_on_next=False,
x={},
),
DateRange(
id="end",
start_date=start_date,
end_date=end_date,
classname=None,
duration=None,
planned_duration=None,
end_on_next=False,
x={},
),
DateRange(
id="end-precedence",
start_date=start_date,
end_date=end_date,
duration=delta_30,
classname=None,
planned_duration=None,
end_on_next=False,
x={},
),
DateRange(
id=None,
start_date=None,
end_date=None,
duration=None,
classname=None,
planned_duration=None,
end_on_next=False,
x={"X-CUSTOM": "value"},
),
]
assert list(playlist.segments) == [
Segment(
uri="http://test.se/segment0-15.ts",
duration=15.0,
title="live",
date=start_date,
key=None,
discontinuity=False,
byterange=None,
map=None,
),
Segment(
uri="http://test.se/segment15-30.5.ts",
duration=15.5,
title="live",
date=start_date + delta_15,
key=None,
discontinuity=False,
byterange=None,
map=None,
),
Segment(
uri="http://test.se/segment30.5-60.ts",
duration=29.5,
title="live",
date=start_date + delta_30,
key=None,
discontinuity=False,
byterange=None,
map=None,
),
Segment(
uri="http://test.se/segment60-.ts",
duration=60.0,
title="live",
date=start_date + delta_60,
key=None,
discontinuity=False,
byterange=None,
map=None,
),
]
self.assertEqual(
[playlist.is_date_in_daterange(playlist.segments[0].date, daterange) for daterange in playlist.dateranges],
[None, True, True, True, True, True, True, True, True, None],
)
self.assertEqual(
[playlist.is_date_in_daterange(playlist.segments[1].date, daterange) for daterange in playlist.dateranges],
[None, True, True, True, True, False, True, True, True, None],
)
self.assertEqual(
[playlist.is_date_in_daterange(playlist.segments[2].date, daterange) for daterange in playlist.dateranges],
[None, True, True, True, False, False, False, True, True, None],
)
self.assertEqual(
[playlist.is_date_in_daterange(playlist.segments[3].date, daterange) for daterange in playlist.dateranges],
[None, True, True, True, False, False, False, False, False, None],
)
assert [playlist.is_date_in_daterange(playlist.segments[0].date, daterange) for daterange in playlist.dateranges] \
== [None, True, True, True, True, True, True, True, True, None]
assert [playlist.is_date_in_daterange(playlist.segments[1].date, daterange) for daterange in playlist.dateranges] \
== [None, True, True, True, True, False, True, True, True, None]
assert [playlist.is_date_in_daterange(playlist.segments[2].date, daterange) for daterange in playlist.dateranges] \
== [None, True, True, True, False, False, False, True, True, None]
assert [playlist.is_date_in_daterange(playlist.segments[3].date, daterange) for daterange in playlist.dateranges] \
== [None, True, True, True, False, False, False, False, False, None]

View File

@ -11,10 +11,10 @@ class TestPluginStream(unittest.TestCase):
yield b"3" * 2048
fd = StreamIOIterWrapper(generator())
self.assertEqual(fd.read(4096), b"1" * 4096)
self.assertEqual(fd.read(2048), b"1" * 2048)
self.assertEqual(fd.read(2048), b"1" * 2048)
self.assertEqual(fd.read(1), b"2")
self.assertEqual(fd.read(4095), b"2" * 4095)
self.assertEqual(fd.read(1536), b"3" * 1536)
self.assertEqual(fd.read(), b"3" * 512)
assert fd.read(4096) == b"1" * 4096
assert fd.read(2048) == b"1" * 2048
assert fd.read(2048) == b"1" * 2048
assert fd.read(1) == b"2"
assert fd.read(4095) == b"2" * 4095
assert fd.read(1536) == b"3" * 1536
assert fd.read() == b"3" * 512

View File

@ -36,29 +36,28 @@ class TestUrllib3Overrides:
class TestPluginAPIHTTPSession(unittest.TestCase):
def test_session_init(self):
session = HTTPSession()
self.assertEqual(session.headers.get("User-Agent"), FIREFOX)
self.assertEqual(session.timeout, 20.0)
self.assertIn("file://", session.adapters.keys())
assert session.headers.get("User-Agent") == FIREFOX
assert session.timeout == 20.0
assert "file://" in session.adapters.keys()
@patch("streamlink.plugin.api.http_session.time.sleep")
@patch("streamlink.plugin.api.http_session.Session.request", side_effect=requests.Timeout)
def test_read_timeout(self, mock_request, mock_sleep):
session = HTTPSession()
with self.assertRaises(PluginError) as cm:
with pytest.raises(PluginError, match=r"^Unable to open URL: http://localhost/"):
session.get("http://localhost/", timeout=123, retries=3, retry_backoff=2, retry_max_backoff=5)
self.assertTrue(str(cm.exception).startswith("Unable to open URL: http://localhost/"))
self.assertEqual(mock_request.mock_calls, [
assert mock_request.mock_calls == [
call("GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
call("GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
call("GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
call("GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
])
self.assertEqual(mock_sleep.mock_calls, [
]
assert mock_sleep.mock_calls == [
call(2),
call(4),
call(5),
])
]
def test_json_encoding(self):
json_str = "{\"test\": \"Α and Ω\"}"
@ -69,7 +68,7 @@ class TestPluginAPIHTTPSession(unittest.TestCase):
mock_content.return_value = json_str.encode(encoding)
res = requests.Response()
self.assertEqual(HTTPSession.json(res), {"test": "\u0391 and \u03a9"})
assert HTTPSession.json(res) == {"test": "Α and Ω"}
def test_json_encoding_override(self):
json_text = "{\"test\": \"Α and Ω\"}".encode("cp949")
@ -79,4 +78,4 @@ class TestPluginAPIHTTPSession(unittest.TestCase):
res = requests.Response()
res.encoding = "cp949"
self.assertEqual(HTTPSession.json(res), {"test": "\u0391 and \u03a9"})
assert HTTPSession.json(res) == {"test": "Α and Ω"}

View File

@ -19,24 +19,24 @@ class TestOptions(unittest.TestCase):
})
def test_options(self):
self.assertEqual(self.options.get("a_default"), "default")
self.assertEqual(self.options.get("non_existing"), None)
assert self.options.get("a_default") == "default"
assert self.options.get("non_existing") is None
self.options.set("a_option", "option")
self.assertEqual(self.options.get("a_option"), "option")
assert self.options.get("a_option") == "option"
def test_options_update(self):
self.assertEqual(self.options.get("a_default"), "default")
self.assertEqual(self.options.get("non_existing"), None)
assert self.options.get("a_default") == "default"
assert self.options.get("non_existing") is None
self.options.update({"a_option": "option"})
self.assertEqual(self.options.get("a_option"), "option")
assert self.options.get("a_option") == "option"
def test_options_name_normalised(self):
self.assertEqual(self.options.get("a_default"), "default")
self.assertEqual(self.options.get("a-default"), "default")
self.assertEqual(self.options.get("another-default"), "default2")
self.assertEqual(self.options.get("another_default"), "default2")
assert self.options.get("a_default") == "default"
assert self.options.get("a-default") == "default"
assert self.options.get("another-default") == "default2"
assert self.options.get("another_default") == "default2"
class TestMappedOptions:
@ -94,19 +94,19 @@ class TestMappedOptions:
class TestArgument(unittest.TestCase):
def test_name(self):
self.assertEqual(Argument("test-arg").argument_name("plugin"), "--plugin-test-arg")
self.assertEqual(Argument("test-arg").namespace_dest("plugin"), "plugin_test_arg")
self.assertEqual(Argument("test-arg").dest, "test_arg")
assert Argument("test-arg").argument_name("plugin") == "--plugin-test-arg"
assert Argument("test-arg").namespace_dest("plugin") == "plugin_test_arg"
assert Argument("test-arg").dest == "test_arg"
def test_name_plugin(self):
self.assertEqual(Argument("test-arg").argument_name("test_plugin"), "--test-plugin-test-arg")
self.assertEqual(Argument("test-arg").namespace_dest("test_plugin"), "test_plugin_test_arg")
self.assertEqual(Argument("test-arg").dest, "test_arg")
assert Argument("test-arg").argument_name("test_plugin") == "--test-plugin-test-arg"
assert Argument("test-arg").namespace_dest("test_plugin") == "test_plugin_test_arg"
assert Argument("test-arg").dest == "test_arg"
def test_name_override(self):
self.assertEqual(Argument("test", argument_name="override-name").argument_name("plugin"), "--override-name")
self.assertEqual(Argument("test", argument_name="override-name").namespace_dest("plugin"), "override_name")
self.assertEqual(Argument("test", argument_name="override-name").dest, "test")
assert Argument("test", argument_name="override-name").argument_name("plugin") == "--override-name"
assert Argument("test", argument_name="override-name").namespace_dest("plugin") == "override_name"
assert Argument("test", argument_name="override-name").dest == "test"
class TestArguments(unittest.TestCase):
@ -115,9 +115,9 @@ class TestArguments(unittest.TestCase):
test2 = Argument("test2")
args = Arguments(test1, test2)
self.assertEqual(args.get("test1"), test1)
self.assertEqual(args.get("test2"), test2)
self.assertEqual(args.get("test3"), None)
assert args.get("test1") == test1
assert args.get("test2") == test2
assert args.get("test3") is None
def test_iter(self):
test1 = Argument("test1")
@ -126,8 +126,8 @@ class TestArguments(unittest.TestCase):
i_args = iter(args)
self.assertEqual(next(i_args), test1)
self.assertEqual(next(i_args), test2)
assert next(i_args) == test1
assert next(i_args) == test2
def test_requires(self):
test1 = Argument("test1", requires="test2")
@ -136,14 +136,15 @@ class TestArguments(unittest.TestCase):
args = Arguments(test1, test2, test3)
self.assertEqual(list(args.requires("test1")), [test2, test3])
assert list(args.requires("test1")) == [test2, test3]
def test_requires_invalid(self):
test1 = Argument("test1", requires="test2")
args = Arguments(test1)
self.assertRaises(KeyError, lambda: list(args.requires("test1")))
with pytest.raises(KeyError):
list(args.requires("test1"))
def test_requires_cycle(self):
test1 = Argument("test1", requires="test2")
@ -151,7 +152,8 @@ class TestArguments(unittest.TestCase):
args = Arguments(test1, test2)
self.assertRaises(RuntimeError, lambda: list(args.requires("test1")))
with pytest.raises(RuntimeError):
list(args.requires("test1"))
def test_requires_cycle_deep(self):
test1 = Argument("test1", requires="test-2")
@ -160,14 +162,16 @@ class TestArguments(unittest.TestCase):
args = Arguments(test1, test2, test3)
self.assertRaises(RuntimeError, lambda: list(args.requires("test1")))
with pytest.raises(RuntimeError):
list(args.requires("test1"))
def test_requires_cycle_self(self):
test1 = Argument("test1", requires="test1")
args = Arguments(test1)
self.assertRaises(RuntimeError, lambda: list(args.requires("test1")))
with pytest.raises(RuntimeError):
list(args.requires("test1"))
class TestSetupOptions:

View File

@ -260,14 +260,14 @@ class TestSession(unittest.TestCase):
def test_options(self):
session = self.subject()
session.set_option("test_option", "option")
self.assertEqual(session.get_option("test_option"), "option")
self.assertEqual(session.get_option("non_existing"), None)
assert session.get_option("test_option") == "option"
assert session.get_option("non_existing") is None
self.assertEqual(session.get_plugin_option("testplugin", "a_option"), "default")
assert session.get_plugin_option("testplugin", "a_option") == "default"
session.set_plugin_option("testplugin", "another_option", "test")
self.assertEqual(session.get_plugin_option("testplugin", "another_option"), "test")
self.assertEqual(session.get_plugin_option("non_existing", "non_existing"), None)
self.assertEqual(session.get_plugin_option("testplugin", "non_existing"), None)
assert session.get_plugin_option("testplugin", "another_option") == "test"
assert session.get_plugin_option("non_existing", "non_existing") is None
assert session.get_plugin_option("testplugin", "non_existing") is None
def test_streams(self):
session = self.subject()
@ -336,9 +336,9 @@ class TestSession(unittest.TestCase):
def test_set_and_get_locale(self):
session = Streamlink()
session.set_option("locale", "en_US")
self.assertEqual(session.localization.country.alpha2, "US")
self.assertEqual(session.localization.language.alpha2, "en")
self.assertEqual(session.localization.language_code, "en_US")
assert session.localization.country.alpha2 == "US"
assert session.localization.language.alpha2 == "en"
assert session.localization.language_code == "en_US"
@patch("streamlink.session.HTTPSession")
def test_interface(self, mock_httpsession):
@ -351,19 +351,19 @@ class TestSession(unittest.TestCase):
"foo://": adapter_foo,
})
session = self.subject(load_plugins=False)
self.assertEqual(session.get_option("interface"), None)
assert session.get_option("interface") is None
session.set_option("interface", "my-interface")
self.assertEqual(adapter_http.poolmanager.connection_pool_kw, {"source_address": ("my-interface", 0)})
self.assertEqual(adapter_https.poolmanager.connection_pool_kw, {"source_address": ("my-interface", 0)})
self.assertEqual(adapter_foo.poolmanager.connection_pool_kw, {})
self.assertEqual(session.get_option("interface"), "my-interface")
assert adapter_http.poolmanager.connection_pool_kw == {"source_address": ("my-interface", 0)}
assert adapter_https.poolmanager.connection_pool_kw == {"source_address": ("my-interface", 0)}
assert adapter_foo.poolmanager.connection_pool_kw == {}
assert session.get_option("interface") == "my-interface"
session.set_option("interface", None)
self.assertEqual(adapter_http.poolmanager.connection_pool_kw, {})
self.assertEqual(adapter_https.poolmanager.connection_pool_kw, {})
self.assertEqual(adapter_foo.poolmanager.connection_pool_kw, {})
self.assertEqual(session.get_option("interface"), None)
assert adapter_http.poolmanager.connection_pool_kw == {}
assert adapter_https.poolmanager.connection_pool_kw == {}
assert adapter_foo.poolmanager.connection_pool_kw == {}
assert session.get_option("interface") is None
@patch("streamlink.session.urllib3_util_connection", allowed_gai_family=_original_allowed_gai_family)
def test_ipv4_ipv6(self, mock_urllib3_util_connection):

View File

@ -18,28 +18,28 @@ def get_session():
class TestStreamlinkAPI(unittest.TestCase):
@patch("streamlink.api.Streamlink", side_effect=get_session)
def test_find_test_plugin(self, session):
self.assertIn("hls", streams("test.se"))
assert "hls" in streams("test.se")
@patch("streamlink.api.Streamlink", side_effect=get_session)
def test_no_streams_exception(self, session):
self.assertEqual({}, streams("test.se/NoStreamsError"))
assert streams("test.se/NoStreamsError") == {}
@patch("streamlink.api.Streamlink", side_effect=get_session)
def test_no_streams(self, session):
self.assertEqual({}, streams("test.se/empty"))
assert streams("test.se/empty") == {}
@patch("streamlink.api.Streamlink", side_effect=get_session)
def test_stream_type_filter(self, session):
stream_types = ["hls"]
available_streams = streams("test.se", stream_types=stream_types)
self.assertIn("hls", available_streams)
self.assertNotIn("test", available_streams)
self.assertNotIn("http", available_streams)
assert "hls" in available_streams
assert "test" not in available_streams
assert "http" not in available_streams
@patch("streamlink.api.Streamlink", side_effect=get_session)
def test_stream_type_wildcard(self, session):
stream_types = ["hls", "*"]
available_streams = streams("test.se", stream_types=stream_types)
self.assertIn("hls", available_streams)
self.assertIn("test", available_streams)
self.assertIn("http", available_streams)
assert "hls" in available_streams
assert "test" in available_streams
assert "http" in available_streams

View File

@ -1,32 +1,32 @@
import unittest
from argparse import ArgumentTypeError
import pytest
from streamlink.utils.args import boolean, comma_list, comma_list_filter, filesize, keyvalue, num
class TestUtilsArgs(unittest.TestCase):
def test_boolean_true(self):
self.assertEqual(boolean("1"), True)
self.assertEqual(boolean("on"), True)
self.assertEqual(boolean("true"), True)
self.assertEqual(boolean("yes"), True)
self.assertEqual(boolean("Yes"), True)
assert boolean("1") is True
assert boolean("on") is True
assert boolean("true") is True
assert boolean("yes") is True
assert boolean("Yes") is True
def test_boolean_false(self):
self.assertEqual(boolean("0"), False)
self.assertEqual(boolean("false"), False)
self.assertEqual(boolean("no"), False)
self.assertEqual(boolean("No"), False)
self.assertEqual(boolean("off"), False)
assert boolean("0") is False
assert boolean("false") is False
assert boolean("no") is False
assert boolean("No") is False
assert boolean("off") is False
def test_boolean_error(self):
with self.assertRaises(ArgumentTypeError):
with pytest.raises(ArgumentTypeError):
boolean("yesno")
with self.assertRaises(ArgumentTypeError):
with pytest.raises(ArgumentTypeError):
boolean("FOO")
with self.assertRaises(ArgumentTypeError):
with pytest.raises(ArgumentTypeError):
boolean("2")
def test_comma_list(self):
@ -39,7 +39,7 @@ class TestUtilsArgs(unittest.TestCase):
]
for _v, _r in test_data:
self.assertEqual(comma_list(_v), _r)
assert comma_list(_v) == _r
def test_comma_list_filter(self):
# (acceptable, values, result)
@ -53,21 +53,20 @@ class TestUtilsArgs(unittest.TestCase):
for _a, _v, _r in test_data:
func = comma_list_filter(_a)
self.assertEqual(func(_v), _r)
assert func(_v) == _r
def test_filesize(self):
self.assertEqual(filesize("2000"), 2000)
self.assertEqual(filesize("11KB"), 1024 * 11)
self.assertEqual(filesize("12MB"), 1024 * 1024 * 12)
self.assertEqual(filesize("1KB"), 1024)
self.assertEqual(filesize("1MB"), 1024 * 1024)
self.assertEqual(filesize("2KB"), 1024 * 2)
assert filesize("2000") == 2000
assert filesize("11KB") == 1024 * 11
assert filesize("12MB") == 1024 * 1024 * 12
assert filesize("1KB") == 1024
assert filesize("1MB") == 1024 * 1024
assert filesize("2KB") == 1024 * 2
def test_filesize_error(self):
with self.assertRaises(ValueError):
with pytest.raises(ValueError): # noqa: PT011
filesize("FOO")
with self.assertRaises(ValueError):
with pytest.raises(ValueError): # noqa: PT011
filesize("0.00000")
def test_keyvalue(self):
@ -83,10 +82,10 @@ class TestUtilsArgs(unittest.TestCase):
]
for _v, _r in test_data:
self.assertEqual(keyvalue(_v), _r)
assert keyvalue(_v) == _r
def test_keyvalue_error(self):
with self.assertRaises(ValueError):
with pytest.raises(ValueError): # noqa: PT011
keyvalue("127.0.0.1")
def test_num(self):
@ -98,21 +97,21 @@ class TestUtilsArgs(unittest.TestCase):
]
for _v, _f, _r in test_data:
self.assertEqual(_f(_v), _r)
assert _f(_v) == _r
def test_num_error(self):
with self.assertRaises(ArgumentTypeError):
func = num(int, 5, 10)
func = num(int, 5, 10)
with pytest.raises(ArgumentTypeError):
func("3")
with self.assertRaises(ArgumentTypeError):
func = num(int, max=11)
func = num(int, max=11)
with pytest.raises(ArgumentTypeError):
func("12")
with self.assertRaises(ArgumentTypeError):
func = num(int, min=15)
func = num(int, min=15)
with pytest.raises(ArgumentTypeError):
func("8")
with self.assertRaises(ArgumentTypeError):
func = num(float, 10, 20)
func = num(float, 10, 20)
with pytest.raises(ArgumentTypeError):
func("40.222")

View File

@ -6,15 +6,13 @@ from streamlink.utils.crypto import decrypt_openssl, evp_bytestokey
class TestUtil(unittest.TestCase):
def test_evp_bytestokey(self):
self.assertEqual((b"]A@*\xbcK*v\xb9q\x9d\x91\x10\x17\xc5\x92",
b"(\xb4n\xd3\xc1\x11\xe8Q\x02\x90\x9b\x1c\xfbP\xea\x0f"),
evp_bytestokey(b"hello", b"", 16, 16))
assert evp_bytestokey(b"hello", b"", 16, 16) == (
b"]A@*\xbcK*v\xb9q\x9d\x91\x10\x17\xc5\x92",
b"(\xb4n\xd3\xc1\x11\xe8Q\x02\x90\x9b\x1c\xfbP\xea\x0f",
)
def test_decrpyt(self):
# data generated with:
# echo "this is a test" | openssl enc -aes-256-cbc -pass pass:"streamlink" -base64
data = base64.b64decode("U2FsdGVkX18nVyJ6Y+ksOASMSHKuRoQ9b4DKHuPbyQc=")
self.assertEqual(
b"this is a test\n",
decrypt_openssl(data, b"streamlink"),
)
assert decrypt_openssl(data, b"streamlink") == b"this is a test\n"

View File

@ -2,6 +2,8 @@ import os.path
import sys
import unittest
import pytest
from streamlink.utils.module import load_module
@ -11,10 +13,9 @@ __test_marker__ = "test_marker"
class TestUtilsModule(unittest.TestCase):
def test_load_module_non_existent(self):
self.assertRaises(ImportError, load_module, "non_existent_module", os.path.dirname(__file__))
with pytest.raises(ImportError):
load_module("non_existent_module", os.path.dirname(__file__))
def test_load_module(self):
self.assertEqual(
sys.modules[__name__].__test_marker__,
load_module(__name__.split(".")[-1], os.path.dirname(__file__)).__test_marker__,
)
assert load_module(__name__.split(".")[-1], os.path.dirname(__file__)).__test_marker__ \
== sys.modules[__name__].__test_marker__

View File

@ -2,6 +2,8 @@ import threading
import unittest
from unittest.mock import Mock, call, patch
import pytest
from streamlink.utils.named_pipe import NamedPipe, NamedPipeBase, NamedPipePosix, NamedPipeWindows
from tests import posix_only, windows_only
@ -70,68 +72,67 @@ class TestNamedPipe(unittest.TestCase):
def test_name(self, mock_log):
NamedPipe()
NamedPipe()
self.assertEqual(mock_log.info.mock_calls, [
assert mock_log.info.mock_calls == [
call("Creating pipe streamlinkpipe-12345-1-67890"),
call("Creating pipe streamlinkpipe-12345-2-67890"),
])
]
@posix_only
class TestNamedPipePosix(unittest.TestCase):
def test_export(self):
self.assertEqual(NamedPipe, NamedPipePosix)
assert NamedPipe is NamedPipePosix
@patch("streamlink.utils.named_pipe.os.mkfifo")
def test_create(self, mock_mkfifo):
mock_mkfifo.side_effect = OSError()
with self.assertRaises(OSError):
mock_mkfifo.side_effect = OSError
with pytest.raises(OSError): # noqa: PT011
NamedPipePosix()
self.assertEqual(mock_mkfifo.call_args[0][1:], (0o660,))
assert mock_mkfifo.call_args[0][1:] == (0o660,)
def test_close_before_open(self):
pipe = NamedPipePosix()
self.assertTrue(pipe.path.is_fifo())
assert pipe.path.is_fifo()
pipe.close()
self.assertFalse(pipe.path.is_fifo())
assert not pipe.path.is_fifo()
# closing twice doesn't raise
pipe.close()
def test_write_before_open(self):
pipe = NamedPipePosix()
self.assertTrue(pipe.path.is_fifo())
with self.assertRaises(Exception):
assert pipe.path.is_fifo()
with pytest.raises(AttributeError):
pipe.write(b"foo")
pipe.close()
def test_named_pipe(self):
pipe = NamedPipePosix()
self.assertTrue(pipe.path.is_fifo())
assert pipe.path.is_fifo()
reader = ReadNamedPipeThreadPosix(pipe)
reader.start()
pipe.open()
self.assertEqual(pipe.write(b"foo"), 3)
self.assertEqual(pipe.write(b"bar"), 3)
assert pipe.write(b"foo") == 3
assert pipe.write(b"bar") == 3
pipe.close()
self.assertFalse(pipe.path.is_fifo())
assert not pipe.path.is_fifo()
reader.done.wait(4000)
self.assertEqual(reader.error, None)
self.assertEqual(reader.data, b"foobar")
self.assertFalse(reader.is_alive())
assert reader.error is None
assert reader.data == b"foobar"
assert not reader.is_alive()
@windows_only
class TestNamedPipeWindows(unittest.TestCase):
def test_export(self):
self.assertEqual(NamedPipe, NamedPipeWindows)
assert NamedPipe is NamedPipeWindows
@patch("streamlink.utils.named_pipe.windll.kernel32")
def test_create(self, mock_kernel32):
mock_kernel32.CreateNamedPipeW.return_value = NamedPipeWindows.INVALID_HANDLE_VALUE
mock_kernel32.GetLastError.return_value = 12345
with self.assertRaises(OSError) as cm:
with pytest.raises(OSError, match=r"^Named pipe error code 0x00003039$"):
NamedPipeWindows()
self.assertEqual(str(cm.exception), "Named pipe error code 0x00003039")
self.assertEqual(mock_kernel32.CreateNamedPipeW.call_args[0][1:], (
assert mock_kernel32.CreateNamedPipeW.call_args[0][1:] == (
0x00000002,
0x00000000,
255,
@ -139,16 +140,16 @@ class TestNamedPipeWindows(unittest.TestCase):
8192,
0,
None,
))
)
def test_close_before_open(self):
pipe = NamedPipeWindows()
handle = windll.kernel32.CreateFileW(str(pipe.path), GENERIC_READ, 0, None, OPEN_EXISTING, 0, None)
self.assertNotEqual(handle, NamedPipeWindows.INVALID_HANDLE_VALUE)
assert handle != NamedPipeWindows.INVALID_HANDLE_VALUE
windll.kernel32.CloseHandle(handle)
pipe.close()
handle = windll.kernel32.CreateFileW(str(pipe.path), GENERIC_READ, 0, None, OPEN_EXISTING, 0, None)
self.assertEqual(handle, NamedPipeWindows.INVALID_HANDLE_VALUE)
assert handle == NamedPipeWindows.INVALID_HANDLE_VALUE
# closing twice doesn't raise
pipe.close()
@ -157,11 +158,11 @@ class TestNamedPipeWindows(unittest.TestCase):
reader = ReadNamedPipeThreadWindows(pipe)
reader.start()
pipe.open()
self.assertEqual(pipe.write(b"foo"), 3)
self.assertEqual(pipe.write(b"bar"), 3)
self.assertEqual(pipe.write(b"\0"), 1)
assert pipe.write(b"foo") == 3
assert pipe.write(b"bar") == 3
assert pipe.write(b"\x00") == 1
reader.done.wait(4000)
self.assertEqual(reader.error, None)
self.assertEqual(reader.data, b"foobar")
self.assertFalse(reader.is_alive())
assert reader.error is None
assert reader.data == b"foobar"
assert not reader.is_alive()
pipe.close()

View File

@ -1,5 +1,6 @@
import unittest
import pytest
from lxml.etree import Element
from streamlink.exceptions import PluginError
@ -10,42 +11,47 @@ from streamlink.utils.parse import parse_html, parse_json, parse_qsd, parse_xml
class TestUtilsParse(unittest.TestCase):
def test_parse_json(self):
self.assertEqual({}, parse_json("{}"))
self.assertEqual({"test": 1}, parse_json("""{"test": 1}"""))
self.assertEqual({"test": 1}, parse_json("""{"test": 1}""", schema=validate.Schema({"test": 1})))
self.assertRaises(PluginError, parse_json, """{"test: 1}""")
self.assertRaises(IOError, parse_json, """{"test: 1}""", exception=IOError)
self.assertRaises(PluginError, parse_json, """{"test: 1}""" * 10)
assert parse_json("{}") == {}
assert parse_json('{"test": 1}') == {"test": 1}
assert parse_json('{"test": 1}', schema=validate.Schema({"test": 1})) == {"test": 1}
with pytest.raises(PluginError):
parse_json("""{"test: 1}""")
with pytest.raises(SyntaxError):
parse_json("""{"test: 1}""", exception=SyntaxError)
with pytest.raises(PluginError):
parse_json("""{"test: 1}""" * 10)
def test_parse_xml(self):
expected = Element("test", {"foo": "bar"})
actual = parse_xml("""<test foo="bar"/>""", ignore_ns=True)
self.assertEqual(expected.tag, actual.tag)
self.assertEqual(expected.attrib, actual.attrib)
assert actual.tag == expected.tag
assert actual.attrib == expected.attrib
def test_parse_xml_ns_ignore(self):
expected = Element("test", {"foo": "bar"})
actual = parse_xml("""<test foo="bar" xmlns="foo:bar"/>""", ignore_ns=True)
self.assertEqual(expected.tag, actual.tag)
self.assertEqual(expected.attrib, actual.attrib)
assert actual.tag == expected.tag
assert actual.attrib == expected.attrib
actual = parse_xml("""<test foo="bar" xmlns="foo:bar"/>""", ignore_ns=True)
self.assertEqual(expected.tag, actual.tag)
self.assertEqual(expected.attrib, actual.attrib)
assert actual.tag == expected.tag
assert actual.attrib == expected.attrib
actual = parse_xml("""<test\nfoo="bar"\nxmlns="foo:bar"/>""", ignore_ns=True)
self.assertEqual(expected.tag, actual.tag)
self.assertEqual(expected.attrib, actual.attrib)
assert actual.tag == expected.tag
assert actual.attrib == expected.attrib
def test_parse_xml_ns(self):
expected = Element("{foo:bar}test", {"foo": "bar"})
actual = parse_xml("""<h:test foo="bar" xmlns:h="foo:bar"/>""")
self.assertEqual(expected.tag, actual.tag)
self.assertEqual(expected.attrib, actual.attrib)
assert actual.tag == expected.tag
assert actual.attrib == expected.attrib
def test_parse_xml_fail(self):
self.assertRaises(PluginError, parse_xml, "1" * 1000)
self.assertRaises(IOError, parse_xml, "1" * 1000, exception=IOError)
with pytest.raises(PluginError):
parse_xml("1" * 1000)
with pytest.raises(SyntaxError):
parse_xml("1" * 1000, exception=SyntaxError)
def test_parse_xml_validate(self):
expected = Element("test", {"foo": "bar"})
@ -53,11 +59,12 @@ class TestUtilsParse(unittest.TestCase):
"""<test foo="bar"/>""",
schema=validate.Schema(xml_element(tag="test", attrib={"foo": str})),
)
self.assertEqual(expected.tag, actual.tag)
self.assertEqual(expected.attrib, actual.attrib)
assert actual.tag == expected.tag
assert actual.attrib == expected.attrib
def test_parse_xml_entities_fail(self):
self.assertRaises(PluginError, parse_xml, """<test foo="bar &"/>""")
with pytest.raises(PluginError):
parse_xml("""<test foo="bar &"/>""")
def test_parse_xml_entities(self):
expected = Element("test", {"foo": "bar &"})
@ -66,37 +73,34 @@ class TestUtilsParse(unittest.TestCase):
schema=validate.Schema(xml_element(tag="test", attrib={"foo": str})),
invalid_char_entities=True,
)
self.assertEqual(expected.tag, actual.tag)
self.assertEqual(expected.attrib, actual.attrib)
assert actual.tag == expected.tag
assert actual.attrib == expected.attrib
def test_parse_xml_encoding(self):
tree = parse_xml("""<?xml version="1.0" encoding="UTF-8"?><test>ä</test>""")
self.assertEqual(tree.xpath(".//text()"), ["ä"])
assert tree.xpath(".//text()") == ["ä"]
tree = parse_xml("""<test>ä</test>""")
self.assertEqual(tree.xpath(".//text()"), ["ä"])
assert tree.xpath(".//text()") == ["ä"]
tree = parse_xml(b"""<?xml version="1.0" encoding="UTF-8"?><test>\xC3\xA4</test>""")
self.assertEqual(tree.xpath(".//text()"), ["ä"])
assert tree.xpath(".//text()") == ["ä"]
tree = parse_xml(b"""<test>\xC3\xA4</test>""")
self.assertEqual(tree.xpath(".//text()"), ["ä"])
assert tree.xpath(".//text()") == ["ä"]
def test_parse_html_encoding(self):
tree = parse_html("""<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>ä</body></html>""")
self.assertEqual(tree.xpath(".//body/text()"), ["ä"])
assert tree.xpath(".//body/text()") == ["ä"]
tree = parse_html("""<!DOCTYPE html><html><body>ä</body></html>""")
self.assertEqual(tree.xpath(".//body/text()"), ["ä"])
assert tree.xpath(".//body/text()") == ["ä"]
tree = parse_html(b"""<!DOCTYPE html><html><meta charset="utf-8"/><body>\xC3\xA4</body></html>""")
self.assertEqual(tree.xpath(".//body/text()"), ["ä"])
assert tree.xpath(".//body/text()") == ["ä"]
tree = parse_html(b"""<!DOCTYPE html><html><body>\xC3\xA4</body></html>""")
self.assertEqual(tree.xpath(".//body/text()"), ["ä"])
assert tree.xpath(".//body/text()") == ["ä"]
def test_parse_html_xhtml5(self):
tree = parse_html("""<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html><html><body>ä?></body></html>""")
self.assertEqual(tree.xpath(".//body/text()"), ["ä?>"])
assert tree.xpath(".//body/text()") == ["ä?>"]
tree = parse_html(b"""<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html><html><body>\xC3\xA4?></body></html>""")
self.assertEqual(tree.xpath(".//body/text()"), ["ä?>"])
assert tree.xpath(".//body/text()") == ["ä?>"]
def test_parse_qsd(self):
self.assertEqual(
{"test": "1", "foo": "bar"},
parse_qsd("test=1&foo=bar", schema=validate.Schema({"test": str, "foo": "bar"})),
)
assert parse_qsd("test=1&foo=bar", schema=validate.Schema({"test": str, "foo": "bar"})) == {"test": "1", "foo": "bar"}

View File

@ -1,46 +1,46 @@
import unittest
import pytest
from streamlink.utils.times import hours_minutes_seconds, seconds_to_hhmmss
class TestUtilsTimes(unittest.TestCase):
def test_hours_minutes_seconds(self):
self.assertEqual(hours_minutes_seconds("00:01:30"), 90)
self.assertEqual(hours_minutes_seconds("01:20:15"), 4815)
self.assertEqual(hours_minutes_seconds("26:00:00"), 93600)
assert hours_minutes_seconds("00:01:30") == 90
assert hours_minutes_seconds("01:20:15") == 4815
assert hours_minutes_seconds("26:00:00") == 93600
self.assertEqual(hours_minutes_seconds("07"), 7)
self.assertEqual(hours_minutes_seconds("444"), 444)
self.assertEqual(hours_minutes_seconds("8888"), 8888)
assert hours_minutes_seconds("07") == 7
assert hours_minutes_seconds("444") == 444
assert hours_minutes_seconds("8888") == 8888
self.assertEqual(hours_minutes_seconds("01h"), 3600)
self.assertEqual(hours_minutes_seconds("01h22m33s"), 4953)
self.assertEqual(hours_minutes_seconds("01H22M37S"), 4957)
self.assertEqual(hours_minutes_seconds("01h30s"), 3630)
self.assertEqual(hours_minutes_seconds("1m33s"), 93)
self.assertEqual(hours_minutes_seconds("55s"), 55)
assert hours_minutes_seconds("01h") == 3600
assert hours_minutes_seconds("01h22m33s") == 4953
assert hours_minutes_seconds("01H22M37S") == 4957
assert hours_minutes_seconds("01h30s") == 3630
assert hours_minutes_seconds("1m33s") == 93
assert hours_minutes_seconds("55s") == 55
self.assertEqual(hours_minutes_seconds("-00:01:40"), 100)
self.assertEqual(hours_minutes_seconds("-00h02m30s"), 150)
assert hours_minutes_seconds("-00:01:40") == 100
assert hours_minutes_seconds("-00h02m30s") == 150
self.assertEqual(hours_minutes_seconds("02:04"), 124)
self.assertEqual(hours_minutes_seconds("1:10"), 70)
self.assertEqual(hours_minutes_seconds("10:00"), 600)
assert hours_minutes_seconds("02:04") == 124
assert hours_minutes_seconds("1:10") == 70
assert hours_minutes_seconds("10:00") == 600
with self.assertRaises(ValueError):
with pytest.raises(ValueError): # noqa: PT011
hours_minutes_seconds("FOO")
with self.assertRaises(ValueError):
with pytest.raises(ValueError): # noqa: PT011
hours_minutes_seconds("BAR")
with self.assertRaises(ValueError):
with pytest.raises(ValueError): # noqa: PT011
hours_minutes_seconds("11:ERR:00")
def test_seconds_to_hhmmss(self):
self.assertEqual(seconds_to_hhmmss(0), "00:00:00")
self.assertEqual(seconds_to_hhmmss(1), "00:00:01")
self.assertEqual(seconds_to_hhmmss(60), "00:01:00")
self.assertEqual(seconds_to_hhmmss(3600), "01:00:00")
assert seconds_to_hhmmss(0) == "00:00:00"
assert seconds_to_hhmmss(1) == "00:00:01"
assert seconds_to_hhmmss(60) == "00:01:00"
assert seconds_to_hhmmss(3600) == "01:00:00"
self.assertEqual(seconds_to_hhmmss(13997), "03:53:17")
self.assertEqual(seconds_to_hhmmss(13997.4), "03:53:17.4")
assert seconds_to_hhmmss(13997) == "03:53:17"
assert seconds_to_hhmmss(13997.4) == "03:53:17.4"