streamlink/tests/session/test_session.py

258 lines
10 KiB
Python

import re
from pathlib import Path
from unittest.mock import Mock
import pytest
import requests_mock as rm
import tests.plugin
from streamlink.exceptions import NoPluginError, StreamlinkDeprecationWarning
from streamlink.options import Options
from streamlink.plugin import HIGH_PRIORITY, LOW_PRIORITY, NO_PRIORITY, NORMAL_PRIORITY, Plugin, pluginmatcher
from streamlink.session import Streamlink
from streamlink.stream.hls import HLSStream
from streamlink.stream.http import HTTPStream
PATH_TESTPLUGINS = Path(tests.plugin.__path__[0])
class TestPluginsDeprecations:
def test_get_plugins(self, monkeypatch: pytest.MonkeyPatch, recwarn: pytest.WarningsRecorder, session: Streamlink):
mock = Mock(return_value={})
monkeypatch.setattr(session.plugins, "get_loaded", mock)
assert session.get_plugins() == {}
assert mock.call_count == 1
assert [(record.filename, record.category, str(record.message)) for record in recwarn.list] == [
(
__file__,
StreamlinkDeprecationWarning,
"`Streamlink.get_plugins()` has been deprecated in favor of `Streamlink.plugins.get_loaded()`",
),
]
def test_load_builtin_plugins(self, monkeypatch: pytest.MonkeyPatch, recwarn: pytest.WarningsRecorder, session: Streamlink):
mock = Mock(return_value={})
monkeypatch.setattr(session.plugins, "load_builtin", mock)
assert session.load_builtin_plugins() is None
assert mock.call_count == 1
assert [(record.filename, record.category, str(record.message)) for record in recwarn.list] == [
(
__file__,
StreamlinkDeprecationWarning,
"`Streamlink.load_builtin_plugins()` has been deprecated in favor of the `plugins_builtin` keyword argument",
),
]
def test_load_plugins(self, recwarn: pytest.WarningsRecorder, session: Streamlink):
session.load_plugins(str(PATH_TESTPLUGINS))
assert session.plugins.get_names() == ["testplugin"]
assert session.plugins["testplugin"].__name__ == "TestPlugin"
assert session.plugins["testplugin"].__module__ == "streamlink.plugins.testplugin"
assert [(record.filename, record.category, str(record.message)) for record in recwarn.list] == [
(
__file__,
StreamlinkDeprecationWarning,
"`Streamlink.load_plugins()` has been deprecated in favor of `Streamlink.plugins.load_path()`",
),
]
class _EmptyPlugin(Plugin):
def _get_streams(self):
pass # pragma: no cover
class TestResolveURL:
@pytest.fixture(autouse=True)
def _load_plugins(self, session: Streamlink):
session.plugins.load_path(PATH_TESTPLUGINS)
@pytest.fixture(autouse=True)
def requests_mock(self, requests_mock: rm.Mocker):
return requests_mock
def test_resolve_url(self, recwarn: pytest.WarningsRecorder, session: Streamlink):
_pluginname, pluginclass, resolved_url = session.resolve_url("http://test.se/channel")
assert issubclass(pluginclass, Plugin)
assert pluginclass is session.plugins["testplugin"]
assert resolved_url == "http://test.se/channel"
assert hasattr(session.resolve_url, "cache_info"), "resolve_url has a lookup cache"
assert recwarn.list == []
def test_resolve_url_noplugin(self, requests_mock: rm.Mocker, session: Streamlink):
requests_mock.get("http://invalid2", status_code=301, headers={"Location": "http://invalid3"})
with pytest.raises(NoPluginError):
session.resolve_url("http://invalid1")
with pytest.raises(NoPluginError):
session.resolve_url("http://invalid2")
def test_resolve_url_redirected(self, requests_mock: rm.Mocker, session: Streamlink):
requests_mock.request("HEAD", "http://redirect1", status_code=501)
requests_mock.request("GET", "http://redirect1", status_code=301, headers={"Location": "http://redirect2"})
requests_mock.request("GET", "http://redirect2", status_code=301, headers={"Location": "http://test.se/channel"})
requests_mock.request("GET", "http://test.se/channel", content=b"")
_pluginname, pluginclass, resolved_url = session.resolve_url("http://redirect1")
assert issubclass(pluginclass, Plugin)
assert pluginclass is session.plugins["testplugin"]
assert resolved_url == "http://test.se/channel"
def test_resolve_url_no_redirect(self, session: Streamlink):
_pluginname, pluginclass, resolved_url = session.resolve_url_no_redirect("http://test.se/channel")
assert issubclass(pluginclass, Plugin)
assert pluginclass is session.plugins["testplugin"]
assert resolved_url == "http://test.se/channel"
def test_resolve_url_no_redirect_noplugin(self, session: Streamlink):
with pytest.raises(NoPluginError):
session.resolve_url_no_redirect("http://invalid")
def test_resolve_url_scheme(self, session: Streamlink):
@pluginmatcher(re.compile("http://insecure"))
class PluginHttp(_EmptyPlugin):
pass
@pluginmatcher(re.compile("https://secure"))
class PluginHttps(_EmptyPlugin):
pass
session.plugins.update({
"insecure": PluginHttp,
"secure": PluginHttps,
})
with pytest.raises(NoPluginError):
session.resolve_url("insecure")
assert session.resolve_url("http://insecure")[1] is PluginHttp
with pytest.raises(NoPluginError):
session.resolve_url("https://insecure")
assert session.resolve_url("secure")[1] is PluginHttps
with pytest.raises(NoPluginError):
session.resolve_url("http://secure")
assert session.resolve_url("https://secure")[1] is PluginHttps
def test_resolve_url_priority(self, session: Streamlink):
@pluginmatcher(priority=HIGH_PRIORITY, pattern=re.compile(
"https://(high|normal|low|no)$",
))
class HighPriority(_EmptyPlugin):
pass
@pluginmatcher(priority=NORMAL_PRIORITY, pattern=re.compile(
"https://(normal|low|no)$",
))
class NormalPriority(_EmptyPlugin):
pass
@pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(
"https://(low|no)$",
))
class LowPriority(_EmptyPlugin):
pass
@pluginmatcher(priority=NO_PRIORITY, pattern=re.compile(
"https://(no)$",
))
class NoPriority(_EmptyPlugin):
pass
session.plugins.update({
"high": HighPriority,
"normal": NormalPriority,
"low": LowPriority,
"no": NoPriority,
})
no = session.resolve_url_no_redirect("no")[1]
low = session.resolve_url_no_redirect("low")[1]
normal = session.resolve_url_no_redirect("normal")[1]
high = session.resolve_url_no_redirect("high")[1]
assert no is HighPriority
assert low is HighPriority
assert normal is HighPriority
assert high is HighPriority
session.resolve_url.cache_clear()
session.plugins.clear()
session.plugins.update({
"no": NoPriority,
})
with pytest.raises(NoPluginError):
session.resolve_url_no_redirect("no")
class TestStreams:
@pytest.fixture(autouse=True)
def _load_plugins(self, session: Streamlink):
session.plugins.load_path(PATH_TESTPLUGINS)
def test_streams(self, session: Streamlink):
streams = session.streams("http://test.se/channel")
assert "best" in streams
assert "worst" in streams
assert streams["best"] is streams["1080p"]
assert streams["worst"] is streams["350k"]
assert isinstance(streams["http"], HTTPStream)
assert isinstance(streams["hls"], HLSStream)
def test_streams_options(self, session: Streamlink):
streams = session.streams("http://test.se/fromoptions", Options({"streamurl": "http://foo/"}))
assert sorted(streams.keys()) == ["best", "fromoptions", "worst"]
assert isinstance(streams["fromoptions"], HTTPStream)
assert streams["fromoptions"].url == "http://foo/"
def test_stream_types(self, session: Streamlink):
streams = session.streams("http://test.se/channel", stream_types=["http", "hls"])
assert isinstance(streams["480p"], HTTPStream)
assert isinstance(streams["480p_hls"], HLSStream)
streams = session.streams("http://test.se/channel", stream_types=["hls", "http"])
assert isinstance(streams["480p"], HLSStream)
assert isinstance(streams["480p_http"], HTTPStream)
def test_stream_sorting_excludes(self, session: Streamlink):
streams = session.streams("http://test.se/channel", sorting_excludes=[])
assert "best" in streams
assert "worst" in streams
assert "best-unfiltered" not in streams
assert "worst-unfiltered" not in streams
assert streams["worst"] is streams["350k"]
assert streams["best"] is streams["1080p"]
streams = session.streams("http://test.se/channel", sorting_excludes=["1080p", "3000k"])
assert "best" in streams
assert "worst" in streams
assert "best-unfiltered" not in streams
assert "worst-unfiltered" not in streams
assert streams["worst"] is streams["350k"]
assert streams["best"] is streams["1500k"]
streams = session.streams("http://test.se/channel", sorting_excludes=[">=1080p", ">1500k"])
assert streams["best"] is streams["1500k"]
streams = session.streams("http://test.se/channel", sorting_excludes=lambda q: not q.endswith("p"))
assert streams["best"] is streams["3000k"]
streams = session.streams("http://test.se/channel", sorting_excludes=lambda q: False)
assert "best" not in streams
assert "worst" not in streams
assert "best-unfiltered" in streams
assert "worst-unfiltered" in streams
assert streams["worst-unfiltered"] is streams["350k"]
assert streams["best-unfiltered"] is streams["1080p"]
streams = session.streams("http://test.se/UnsortableStreamNames")
assert "best" not in streams
assert "worst" not in streams
assert "best-unfiltered" not in streams
assert "worst-unfiltered" not in streams
assert "vod" in streams
assert "vod_alt" in streams
assert "vod_alt2" in streams