add aircox.test utilities
This commit is contained in:
parent
95f5dca683
commit
a1ca0e70cf
33
aircox/test.py
Normal file
33
aircox/test.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
"""This module provide test utilities."""
|
||||
|
||||
|
||||
__all__ = ("interface",)
|
||||
|
||||
|
||||
def interface(obj, funcs):
|
||||
"""Override provided object's functions using dict of funcs, as
|
||||
``{func_name: return_value}``.
|
||||
|
||||
Attribute ``obj.calls`` is a dict with all call done using those
|
||||
methods, as ``{func_name: (args, kwargs) | list[(args, kwargs]]}``.
|
||||
"""
|
||||
for attr, value in funcs.items():
|
||||
interface_wrap(obj, attr, value)
|
||||
|
||||
|
||||
def interface_wrap(obj, attr, value):
|
||||
if not isinstance(getattr(obj, "calls", None), dict):
|
||||
obj.calls = {}
|
||||
obj.calls[attr] = None
|
||||
|
||||
def wrapper(*a, **kw):
|
||||
call = obj.calls.get(attr)
|
||||
if call is None:
|
||||
obj.calls[attr] = (a, kw)
|
||||
elif isinstance(call, tuple):
|
||||
obj.calls[attr] = [call, (a, kw)]
|
||||
else:
|
||||
call.append((a, kw))
|
||||
return value
|
||||
|
||||
setattr(obj, attr, wrapper)
|
|
@ -5,14 +5,10 @@
|
|||
# x when liquidsoap fails to start/exists: exit
|
||||
# - handle restart after failure
|
||||
# - is stream restart after live ok?
|
||||
import pytz
|
||||
from django.utils import timezone as tz
|
||||
|
||||
from aircox.models import Diffusion, Log, Sound, Track
|
||||
|
||||
# force using UTC
|
||||
tz.activate(pytz.UTC)
|
||||
|
||||
|
||||
class Monitor:
|
||||
"""Log and launch diffusions for the given station.
|
||||
|
@ -33,9 +29,9 @@ class Monitor:
|
|||
""" Timedelta: minimal delay between two call of monitor. """
|
||||
logs = None
|
||||
"""Queryset to station's logs (ordered by -pk)"""
|
||||
cancel_timeout = 20
|
||||
cancel_timeout = tz.timedelta(minutes=20)
|
||||
"""Timeout in minutes before cancelling a diffusion."""
|
||||
sync_timeout = 5
|
||||
sync_timeout = tz.timedelta(minutes=5)
|
||||
"""Timeout in minutes between two streamer's sync."""
|
||||
sync_next = None
|
||||
"""Datetime of the next sync."""
|
||||
|
@ -56,11 +52,10 @@ class Monitor:
|
|||
"""Log of last triggered item (sound or diffusion)."""
|
||||
return self.logs.start().with_diff().first()
|
||||
|
||||
def __init__(self, streamer, delay, cancel_timeout, **kwargs):
|
||||
def __init__(self, streamer, delay, **kwargs):
|
||||
self.streamer = streamer
|
||||
# adding time ensure all calculation have a margin
|
||||
# adding time ensures all calculations have a margin
|
||||
self.delay = delay + tz.timedelta(seconds=5)
|
||||
self.cancel_timeout = cancel_timeout
|
||||
self.__dict__.update(kwargs)
|
||||
self.logs = self.get_logs_queryset()
|
||||
self.init_last_sound_logs()
|
||||
|
@ -117,18 +112,6 @@ class Monitor:
|
|||
self.handle_diffusions()
|
||||
self.sync()
|
||||
|
||||
def log(self, source, **kwargs):
|
||||
"""Create a log using **kwargs, and print info."""
|
||||
kwargs.setdefault("station", self.station)
|
||||
kwargs.setdefault("date", tz.now())
|
||||
log = Log(source=source, **kwargs)
|
||||
log.save()
|
||||
log.print()
|
||||
|
||||
if log.sound:
|
||||
self.last_sound_logs[source] = log
|
||||
return log
|
||||
|
||||
def trace_sound(self, source):
|
||||
"""Return on air sound log (create if not present)."""
|
||||
air_uri, air_time = source.uri, source.air_time
|
||||
|
@ -246,6 +229,18 @@ class Monitor:
|
|||
if diff.start < now - self.cancel_timeout:
|
||||
self.cancel_diff(dealer, diff)
|
||||
|
||||
def log(self, source, **kwargs):
|
||||
"""Create a log using **kwargs, and print info."""
|
||||
kwargs.setdefault("station", self.station)
|
||||
kwargs.setdefault("date", tz.now())
|
||||
log = Log(source=source, **kwargs)
|
||||
log.save()
|
||||
log.print()
|
||||
|
||||
if log.sound:
|
||||
self.last_sound_logs[source] = log
|
||||
return log
|
||||
|
||||
def start_diff(self, source, diff):
|
||||
playlist = Sound.objects.episode(id=diff.episode_id).playlist()
|
||||
source.push(*playlist)
|
||||
|
@ -272,7 +267,7 @@ class Monitor:
|
|||
if self.sync_next is not None and now < self.sync_next:
|
||||
return
|
||||
|
||||
self.sync_next = now + tz.timedelta(minutes=self.sync_timeout)
|
||||
self.sync_next = now + self.sync_timeout
|
||||
|
||||
for source in self.streamer.playlists:
|
||||
source.sync()
|
||||
|
|
|
@ -17,6 +17,7 @@ from django.utils import timezone as tz
|
|||
from aircox.models import Station
|
||||
from aircox_streamer.controllers import Monitor, Streamer
|
||||
|
||||
|
||||
# force using UTC
|
||||
tz.activate(pytz.UTC)
|
||||
|
||||
|
@ -68,7 +69,7 @@ class Command(BaseCommand):
|
|||
"-t",
|
||||
"--timeout",
|
||||
type=float,
|
||||
default=Monitor.cancel_timeout,
|
||||
default=Monitor.cancel_timeout.total_seconds() / 60,
|
||||
help="time to wait in MINUTES before canceling a diffusion that "
|
||||
"should have ran but did not. ",
|
||||
)
|
||||
|
@ -106,7 +107,8 @@ class Command(BaseCommand):
|
|||
delay = tz.timedelta(milliseconds=delay)
|
||||
timeout = tz.timedelta(minutes=timeout)
|
||||
monitors = [
|
||||
Monitor(streamer, delay, timeout) for streamer in streamers
|
||||
Monitor(streamer, delay, cancel_timeout=timeout)
|
||||
for streamer in streamers
|
||||
]
|
||||
|
||||
while not run or streamer.is_running:
|
||||
|
|
|
@ -5,6 +5,7 @@ from datetime import datetime, time
|
|||
import tzlocal
|
||||
|
||||
import pytest
|
||||
from model_bakery import baker
|
||||
|
||||
from aircox import models
|
||||
from aircox_streamer import controllers
|
||||
|
@ -17,6 +18,33 @@ local_tz = tzlocal.get_localzone()
|
|||
working_dir = os.path.join(os.path.dirname(__file__), "working_dir")
|
||||
|
||||
|
||||
def interface(self, obj, funcs):
|
||||
"""Override provided object's functions using dict of funcs, as ``{
|
||||
func_name: return_value}``.
|
||||
|
||||
Attribute ``obj.calls`` is a dict
|
||||
with all call done using those methods, as
|
||||
``{func_name: (args, kwargs)}``.
|
||||
"""
|
||||
if not isinstance(getattr(obj, "calls", None), dict):
|
||||
obj.calls = {}
|
||||
|
||||
for attr, value in funcs.items():
|
||||
|
||||
def func(*a, **kw):
|
||||
call = obj.calls.get(attr)
|
||||
if call is None:
|
||||
obj.calls[attr] = (a, kw)
|
||||
elif isinstance(call, tuple):
|
||||
obj.calls[attr] = [call, (a, kw)]
|
||||
else:
|
||||
call.append((a, kw))
|
||||
return value
|
||||
|
||||
obj.calls[attr] = None
|
||||
setattr(obj, attr, func)
|
||||
|
||||
|
||||
class FakeSocket:
|
||||
FAILING_ADDRESS = -1
|
||||
"""Connect with this address fails."""
|
||||
|
@ -142,6 +170,26 @@ def stream(program):
|
|||
return stream
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def episode(program):
|
||||
episode = baker.make(models.Episode, title="test episode", program=program)
|
||||
episode.playlist = lambda: ["/tmp/a", "/tmp/b"]
|
||||
return episode
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sound(program, episode):
|
||||
return baker.make(
|
||||
models.Sound,
|
||||
program=program,
|
||||
episode=episode,
|
||||
name="sound",
|
||||
type=models.Sound.TYPE_ARCHIVE,
|
||||
position=0,
|
||||
file="sound.mp3",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sounds(program):
|
||||
items = [
|
||||
|
@ -219,6 +267,9 @@ def metadata_string(metadata_data):
|
|||
class FakeStreamer(controllers.Streamer):
|
||||
calls = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(**kwargs)
|
||||
|
||||
def fetch(self):
|
||||
self.calls["fetch"] = True
|
||||
|
||||
|
@ -236,7 +287,7 @@ class FakeSource(controllers.Source):
|
|||
def sync(self):
|
||||
self.calls["sync"] = True
|
||||
|
||||
def push(self, path):
|
||||
def push(self, *path):
|
||||
self.calls["push"] = path
|
||||
return path
|
||||
|
||||
|
@ -250,6 +301,24 @@ class FakeSource(controllers.Source):
|
|||
self.calls["seek"] = c
|
||||
|
||||
|
||||
class FakePlaylist(FakeSource, controllers.PlaylistSource):
|
||||
pass
|
||||
|
||||
|
||||
class FakeQueueSource(FakeSource, controllers.QueueSource):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def streamer(station, station_ports):
|
||||
streamer = FakeStreamer(station)
|
||||
streamer.sources = [
|
||||
FakePlaylist(i, uri=f"source-{i}") for i in range(0, 3)
|
||||
]
|
||||
streamer.sources.append(FakeQueueSource(len(streamer.sources)))
|
||||
return streamer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def streamers(stations, stations_ports):
|
||||
streamers = controllers.Streamers(streamer_class=FakeStreamer)
|
||||
|
@ -257,6 +326,9 @@ def streamers(stations, stations_ports):
|
|||
streamers.streamers = {
|
||||
station.pk: FakeStreamer(station) for station in stations
|
||||
}
|
||||
for streamer in streamers.values():
|
||||
streamer.sources = [FakeSource(i) for i in range(0, 3)]
|
||||
for j, streamer in enumerate(streamers.values()):
|
||||
streamer.sources = [
|
||||
FakePlaylist(i, uri=f"source-{j}-{i}") for i in range(0, 3)
|
||||
]
|
||||
streamer.sources.append(FakeQueueSource(len(streamer.sources)))
|
||||
return streamers
|
||||
|
|
Loading…
Reference in New Issue
Block a user