write sound monitor tests
This commit is contained in:
parent
d15ca98447
commit
93e57d746c
|
@ -24,7 +24,7 @@ parameters given by the setting SOUND_QUALITY. This script requires
|
|||
Sox (and soxi).
|
||||
"""
|
||||
import atexit
|
||||
import concurrent.futures as futures
|
||||
from concurrent import futures
|
||||
import logging
|
||||
import time
|
||||
import os
|
||||
|
@ -142,9 +142,9 @@ class MonitorHandler(PatternMatchingEventHandler):
|
|||
"""
|
||||
|
||||
pool = None
|
||||
jobs = {}
|
||||
jobs = None
|
||||
|
||||
def __init__(self, subdir, pool, **sync_kw):
|
||||
def __init__(self, subdir, pool, jobs=None, **sync_kw):
|
||||
"""
|
||||
:param str subdir: sub-directory in program dirs to monitor \
|
||||
(SOUND_ARCHIVES_SUBDIR or SOUND_EXCERPTS_SUBDIR);
|
||||
|
@ -154,6 +154,7 @@ class MonitorHandler(PatternMatchingEventHandler):
|
|||
"""
|
||||
self.subdir = subdir
|
||||
self.pool = pool
|
||||
self.jobs = jobs or {}
|
||||
self.sync_kw = sync_kw
|
||||
|
||||
patterns = [
|
||||
|
@ -199,29 +200,27 @@ class MonitorHandler(PatternMatchingEventHandler):
|
|||
|
||||
class SoundMonitor:
|
||||
"""Monitor for filesystem changes in order to synchronise database and
|
||||
analyse files."""
|
||||
analyse files of a provided program."""
|
||||
|
||||
def report(self, program=None, component=None, logger=logging, *content):
|
||||
if not component:
|
||||
logger.info(
|
||||
"%s: %s", str(program), " ".join([str(c) for c in content])
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
"%s, %s: %s",
|
||||
str(program),
|
||||
str(component),
|
||||
" ".join([str(c) for c in content]),
|
||||
)
|
||||
def report(self, program=None, component=None, *content, logger=logging):
|
||||
content = " ".join([str(c) for c in content])
|
||||
logger.info(
|
||||
f"{program}: {content}"
|
||||
if not component
|
||||
else f"{program}, {component}: {content}"
|
||||
)
|
||||
|
||||
def scan(self, logger=logging):
|
||||
"""For all programs, scan dirs."""
|
||||
"""For all programs, scan dirs.
|
||||
|
||||
Return scanned directories.
|
||||
"""
|
||||
logger.info("scan all programs...")
|
||||
programs = Program.objects.filter()
|
||||
|
||||
dirs = []
|
||||
for program in programs:
|
||||
logger.info("#%d %s", program.id, program.title)
|
||||
logger.info(f"#{program.id} {program.title}")
|
||||
self.scan_for_program(
|
||||
program,
|
||||
settings.SOUND_ARCHIVES_SUBDIR,
|
||||
|
@ -234,7 +233,7 @@ class SoundMonitor:
|
|||
logger=logger,
|
||||
type=Sound.TYPE_EXCERPT,
|
||||
)
|
||||
dirs.append(os.path.join(program.abspath))
|
||||
dirs.append(program.abspath)
|
||||
return dirs
|
||||
|
||||
def scan_for_program(
|
||||
|
@ -272,7 +271,12 @@ class SoundMonitor:
|
|||
if sound.check_on_file():
|
||||
SoundFile(sound.file.path).sync(sound=sound, **sync_kwargs)
|
||||
|
||||
_running = False
|
||||
|
||||
def monitor(self, logger=logging):
|
||||
if self._running:
|
||||
raise RuntimeError("already running")
|
||||
|
||||
"""Run in monitor mode."""
|
||||
with futures.ThreadPoolExecutor() as pool:
|
||||
archives_handler = MonitorHandler(
|
||||
|
@ -307,5 +311,13 @@ class SoundMonitor:
|
|||
|
||||
atexit.register(leave)
|
||||
|
||||
while True:
|
||||
self._running = True
|
||||
while self._running:
|
||||
time.sleep(1)
|
||||
|
||||
leave()
|
||||
atexit.unregister(leave)
|
||||
|
||||
def stop(self):
|
||||
"""Stop monitor() loop."""
|
||||
self._running = False
|
||||
|
|
100
aircox/test.py
100
aircox/test.py
|
@ -44,38 +44,64 @@ InterfaceTarget = namedtuple(
|
|||
|
||||
|
||||
class WrapperMixin:
|
||||
def __init__(self, target=None, ns=None, ns_attr=None, **kwargs):
|
||||
type_interface = None
|
||||
"""For instance of class wrapped by an Interface, this is the wrapping
|
||||
interface of the class."""
|
||||
instances = None
|
||||
ns = None
|
||||
ns_attr = None
|
||||
|
||||
def __init__(
|
||||
self, target=None, ns=None, ns_attr=None, type_interface=None, **kwargs
|
||||
):
|
||||
self.target = target
|
||||
if ns:
|
||||
self.inject(ns, ns_attr)
|
||||
if self.type_interface:
|
||||
self._set_type_interface(type_interface)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def _set_type_interface(self, type_interface):
|
||||
if self.type_interface:
|
||||
raise RuntimeError("a type interface is already assigned")
|
||||
|
||||
self.type_interface = type_interface
|
||||
if not type_interface.instances:
|
||||
type_interface.instances = [self]
|
||||
else:
|
||||
type_interface.instances.append(self)
|
||||
|
||||
@property
|
||||
def ns_target(self):
|
||||
"""Actual namespace's target (using ns.ns_attr)"""
|
||||
if self.ns and self.ns_attr:
|
||||
return getattr(self.ns, self.ns_attr, None)
|
||||
return None
|
||||
|
||||
def inject(self, ns=None, ns_attr=None):
|
||||
if ns and ns_attr:
|
||||
ns_target = getattr(ns, ns_attr, None)
|
||||
if self.target is ns_target:
|
||||
return
|
||||
elif self.target is not None:
|
||||
raise RuntimeError(
|
||||
"self target already injected. It must be "
|
||||
"`release` before `inject`."
|
||||
)
|
||||
self.target = ns_target
|
||||
setattr(ns, ns_attr, self.parent)
|
||||
elif not ns or not ns_attr:
|
||||
"""Inject interface into namespace at given key."""
|
||||
if not (ns and ns_attr):
|
||||
raise ValueError("ns and ns_attr must be provided together")
|
||||
|
||||
ns_target = getattr(ns, ns_attr, None)
|
||||
if self.target is ns_target:
|
||||
return
|
||||
elif self.target is not None:
|
||||
raise RuntimeError(
|
||||
"self target already injected. It must be "
|
||||
"`release` before `inject`."
|
||||
)
|
||||
|
||||
self.target = ns_target
|
||||
setattr(ns, ns_attr, self.interface)
|
||||
|
||||
self.ns = ns
|
||||
self.ns_attr = ns_attr
|
||||
|
||||
def release(self):
|
||||
if self.ns_target is self:
|
||||
setattr(self.target.namespace, self.target.name, self.target)
|
||||
"""Remove injection from previously injected parent, reset target."""
|
||||
if self.ns_target is self.interface:
|
||||
setattr(self.ns, self.ns_attr, self.target)
|
||||
self.target = None
|
||||
|
||||
|
||||
|
@ -83,7 +109,9 @@ class SpoofMixin:
|
|||
traces = None
|
||||
|
||||
def __init__(self, funcs=None, **kwargs):
|
||||
self.reset(funcs or {})
|
||||
self.reset(
|
||||
funcs or {},
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def reset(self, funcs=None):
|
||||
|
@ -152,23 +180,19 @@ class SpoofMixin:
|
|||
"""
|
||||
func = self.funcs[name]
|
||||
if callable(func):
|
||||
return func(*a, **kw)
|
||||
return func(self, *a, **kw)
|
||||
return func
|
||||
|
||||
|
||||
class InterfaceMeta(SpoofMixin, WrapperMixin):
|
||||
calls = None
|
||||
"""Calls done."""
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
self.parent = parent
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self.traces[name]
|
||||
|
||||
|
||||
class Interface:
|
||||
class IMeta(SpoofMixin, WrapperMixin):
|
||||
def __init__(self, interface, **kwargs):
|
||||
self.interface = interface
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self.traces[name]
|
||||
|
||||
_imeta = None
|
||||
"""This contains a InterfaceMeta instance related to Interface one.
|
||||
|
||||
|
@ -182,7 +206,7 @@ class Interface:
|
|||
_imeta_kw.setdefault("funcs", _funcs)
|
||||
if _target is not None:
|
||||
_imeta_kw.setdefault("target", _target)
|
||||
self._imeta = InterfaceMeta(self, **_imeta_kw)
|
||||
self._imeta = self.IMeta(self, **_imeta_kw)
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
@property
|
||||
|
@ -195,19 +219,29 @@ class Interface:
|
|||
return cls(**kwargs)
|
||||
|
||||
def _irelease(self):
|
||||
"""Shortcut to `self._imeta.release`."""
|
||||
self._imeta.release()
|
||||
|
||||
def _trace(self, *args, **kw):
|
||||
"""Shortcut to `self._imeta.get_trace`."""
|
||||
return self._imeta.get_trace(*args, **kw)
|
||||
|
||||
def _traces(self, *args, **kw):
|
||||
"""Shortcut to `self._imeta.get_traces`."""
|
||||
return self._imeta.get_traces(*args, **kw)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
target = self._imeta.target
|
||||
print("is it class?", self, target, inspect.isclass(target))
|
||||
if inspect.isclass(target):
|
||||
target = target(*args, **kwargs)
|
||||
return type(self)(target, _imeta_kw={"funcs": self._imeta.funcs})
|
||||
return type(self)(
|
||||
target,
|
||||
_imeta_kw={"type_interface": self, "funcs": self._imeta.funcs},
|
||||
)
|
||||
|
||||
if "__call__" in self._imeta.funcs:
|
||||
return self._imeta.call("__call__", args, kwargs)
|
||||
|
||||
self._imeta.add("__call__", args, kwargs)
|
||||
return self._imeta.target(*args, **kwargs)
|
||||
|
@ -216,3 +250,7 @@ class Interface:
|
|||
if attr in self._imeta.funcs:
|
||||
return lambda *args, **kwargs: self._imeta.call(attr, args, kwargs)
|
||||
return getattr(self._imeta.target, attr)
|
||||
|
||||
def __str__(self):
|
||||
iface = super().__str__()
|
||||
return f"{iface}::{self._imeta.target}"
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from concurrent import futures
|
||||
import logging
|
||||
import pytest
|
||||
|
||||
from django.utils import timezone as tz
|
||||
|
||||
from aircox.conf import settings
|
||||
from aircox.models import Sound
|
||||
from aircox.controllers import sound_monitor
|
||||
from aircox.test import Interface, interface
|
||||
|
@ -25,8 +27,8 @@ def logger():
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def interfaces(logger):
|
||||
return {
|
||||
def interfaces():
|
||||
items = {
|
||||
"SoundFile": Interface.inject(
|
||||
sound_monitor,
|
||||
"SoundFile",
|
||||
|
@ -41,8 +43,11 @@ def interfaces(logger):
|
|||
"sleep": None,
|
||||
},
|
||||
),
|
||||
"datetime": Interface.inject(sound_monitor, "datetime", {now: now}),
|
||||
"datetime": Interface.inject(sound_monitor, "datetime", {"now": now}),
|
||||
}
|
||||
yield items
|
||||
for item in items.values():
|
||||
item._irelease()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -65,6 +70,23 @@ def modified_task(interfaces):
|
|||
return sound_monitor.ModifiedTask()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def monitor_handler(interfaces):
|
||||
pool = Interface(
|
||||
None,
|
||||
{
|
||||
"submit": lambda imeta, *a, **kw: Interface(
|
||||
None,
|
||||
{
|
||||
"add_done_callback": None,
|
||||
"done": False,
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
return sound_monitor.MonitorHandler("/tmp", pool=pool, sync_kw=13)
|
||||
|
||||
|
||||
class TestTask:
|
||||
def test___init__(self, task):
|
||||
assert task.timestamp is not None
|
||||
|
@ -111,7 +133,7 @@ class TestModifiedTask:
|
|||
dt_now = now + modified_task.timeout_delta - tz.timedelta(hours=10)
|
||||
datetime = Interface.inject(sound_monitor, "datetime", {"now": dt_now})
|
||||
|
||||
def sleep(n):
|
||||
def sleep(imeta, n):
|
||||
datetime._imeta.funcs[
|
||||
"now"
|
||||
] = modified_task.timestamp + tz.timedelta(hours=10)
|
||||
|
@ -129,31 +151,124 @@ class TestModifiedTask:
|
|||
|
||||
|
||||
class TestMonitorHandler:
|
||||
def test_monitor___init__(self):
|
||||
pass
|
||||
def test_on_created(self, monitor_handler, event):
|
||||
monitor_handler._submit = monitor_handler.pool.submit
|
||||
monitor_handler.on_created(event)
|
||||
trace_args, trace_kwargs = monitor_handler.pool._trace("submit")
|
||||
assert isinstance(trace_args[0], sound_monitor.CreateTask)
|
||||
assert trace_args[1:] == (event, "new")
|
||||
assert trace_kwargs == monitor_handler.sync_kw
|
||||
|
||||
def test_on_created(self):
|
||||
pass
|
||||
def test_on_deleted(self, monitor_handler, event):
|
||||
monitor_handler._submit = monitor_handler.pool.submit
|
||||
monitor_handler.on_deleted(event)
|
||||
trace_args, _ = monitor_handler.pool._trace("submit")
|
||||
assert isinstance(trace_args[0], sound_monitor.DeleteTask)
|
||||
assert trace_args[1:] == (event, "del")
|
||||
|
||||
def test_on_deleted(self):
|
||||
pass
|
||||
def test_on_moved(self, monitor_handler, event):
|
||||
monitor_handler._submit = monitor_handler.pool.submit
|
||||
monitor_handler.on_moved(event)
|
||||
trace_args, trace_kwargs = monitor_handler.pool._trace("submit")
|
||||
assert isinstance(trace_args[0], sound_monitor.MoveTask)
|
||||
assert trace_args[1:] == (event, "mv")
|
||||
assert trace_kwargs == monitor_handler.sync_kw
|
||||
|
||||
def test_on_moved(self):
|
||||
pass
|
||||
def test_on_modified(self, monitor_handler, event):
|
||||
monitor_handler._submit = monitor_handler.pool.submit
|
||||
monitor_handler.on_modified(event)
|
||||
trace_args, trace_kwargs = monitor_handler.pool._trace("submit")
|
||||
assert isinstance(trace_args[0], sound_monitor.ModifiedTask)
|
||||
assert trace_args[1:] == (event, "up")
|
||||
assert trace_kwargs == monitor_handler.sync_kw
|
||||
|
||||
def test_on_modified(self):
|
||||
pass
|
||||
def test__submit(self, monitor_handler, event):
|
||||
handler = Interface()
|
||||
handler, created = monitor_handler._submit(
|
||||
handler, event, "prefix", kw=13
|
||||
)
|
||||
assert created
|
||||
assert handler.future._trace("add_done_callback")
|
||||
assert monitor_handler.pool._trace("submit") == (
|
||||
(handler, event),
|
||||
{"kw": 13},
|
||||
)
|
||||
|
||||
def test__submit(self):
|
||||
pass
|
||||
key = f"prefix:{event.src_path}"
|
||||
assert monitor_handler.jobs.get(key) == handler
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def monitor_interfaces():
|
||||
items = {
|
||||
"atexit": Interface.inject(
|
||||
sound_monitor, "atexit", {"register": None, "leave": None}
|
||||
),
|
||||
"observer": Interface.inject(
|
||||
sound_monitor,
|
||||
"Observer",
|
||||
{
|
||||
"schedule": None,
|
||||
"start": None,
|
||||
},
|
||||
),
|
||||
}
|
||||
yield items
|
||||
for item in items.values():
|
||||
item.release()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def monitor():
|
||||
yield sound_monitor.SoundMonitor()
|
||||
|
||||
|
||||
class SoundMonitor:
|
||||
def test_report(self):
|
||||
pass
|
||||
def test_report(self, monitor, program, logger):
|
||||
monitor.report(program, "component", "content", logger=logger)
|
||||
msg = f"{program}, component: content"
|
||||
assert logger._trace("info", args=True) == (msg,)
|
||||
|
||||
def test_scan(self):
|
||||
pass
|
||||
def test_scan(self, monitor, program, logger):
|
||||
interface = Interface(None, {"scan_for_program": None})
|
||||
monitor.scan_for_program = interface.scan_for_program
|
||||
dirs = monitor.scan(logger)
|
||||
|
||||
def test_monitor(self):
|
||||
pass
|
||||
assert logger._traces("info") == (
|
||||
"scan all programs...",
|
||||
f"#{program.id} {program.title}",
|
||||
)
|
||||
assert dirs == [program.abspath]
|
||||
assert interface._traces("scan_for_program") == (
|
||||
((program, settings.SOUND_ARCHIVES_SUBDIR), {"logger": logger})(
|
||||
(program, settings.SOUND_EXCERPTS_SUBDIR), {"logger": logger}
|
||||
)
|
||||
)
|
||||
|
||||
def test_monitor(self, monitor, monitor_interfaces, logger):
|
||||
def sleep(*args, **kwargs):
|
||||
monitor.stop()
|
||||
|
||||
time = Interface.inject(sound_monitor, "time", {"sleep": sleep})
|
||||
monitor.monitor(logger=logger)
|
||||
time._irelease()
|
||||
|
||||
observers = monitor_interfaces["observer"].instances
|
||||
observer = observers and observers[0]
|
||||
assert observer
|
||||
schedules = observer._traces("schedule")
|
||||
for (handler, *_), kwargs in schedules:
|
||||
assert isinstance(handler, sound_monitor.MonitorHandler)
|
||||
assert isinstance(handler.pool, futures.ThreadPoolExecutor)
|
||||
assert (handler.subdir, handler.type) in (
|
||||
(settings.SOUND_ARCHIVES_SUBDIR, Sound.TYPE_ARCHIVE),
|
||||
(settings.SOUND_EXCERPTS_SUBDIR, Sound.TYPE_EXCERPT),
|
||||
)
|
||||
|
||||
assert observer._trace("start")
|
||||
|
||||
atexit = monitor_interfaces["atexit"]
|
||||
assert atexit._trace("register")
|
||||
assert atexit._trace("unregister")
|
||||
|
||||
assert observers
|
||||
|
|
Loading…
Reference in New Issue
Block a user