mirror of https://github.com/streamlink/streamlink
chore: fix PT009 ruff rule
This commit is contained in:
parent
cc7159611f
commit
6c2d710eda
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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":{}}}'
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 Ω"}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue