forked from rc/aircox
		
	!111 Co-authored-by: bkfox <thomas bkfox net> Reviewed-on: rc/aircox#114
This commit is contained in:
		
							
								
								
									
										3
									
								
								aircox/tests/controllers/playlist.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								aircox/tests/controllers/playlist.csv
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
Artist 1;Title 1;1;0;tag1,tag12;info1
 | 
			
		||||
Artist 2;Title 2;2;1;tag2,tag12;info2
 | 
			
		||||
Artist 3;Title 3;3;2;;
 | 
			
		||||
		
		
			
  | 
							
								
								
									
										110
									
								
								aircox/tests/controllers/test_log_archiver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								aircox/tests/controllers/test_log_archiver.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,110 @@
 | 
			
		||||
from django.utils import timezone as tz
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
from model_bakery import baker
 | 
			
		||||
 | 
			
		||||
from aircox import models
 | 
			
		||||
from aircox.test import Interface, File
 | 
			
		||||
from aircox.controllers import log_archiver
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def diffusions(episodes):
 | 
			
		||||
    items = [
 | 
			
		||||
        baker.prepare(
 | 
			
		||||
            models.Diffusion,
 | 
			
		||||
            program=episode.program,
 | 
			
		||||
            episode=episode,
 | 
			
		||||
            type=models.Diffusion.TYPE_ON_AIR,
 | 
			
		||||
        )
 | 
			
		||||
        for episode in episodes
 | 
			
		||||
    ]
 | 
			
		||||
    models.Diffusion.objects.bulk_create(items)
 | 
			
		||||
    return items
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def logs(diffusions, sound, tracks):
 | 
			
		||||
    now = tz.now()
 | 
			
		||||
    station = diffusions[0].program.station
 | 
			
		||||
    items = [
 | 
			
		||||
        models.Log(
 | 
			
		||||
            station=diffusion.program.station,
 | 
			
		||||
            type=models.Log.TYPE_START,
 | 
			
		||||
            date=now + tz.timedelta(hours=-10, minutes=i),
 | 
			
		||||
            source="13",
 | 
			
		||||
            diffusion=diffusion,
 | 
			
		||||
        )
 | 
			
		||||
        for i, diffusion in enumerate(diffusions)
 | 
			
		||||
    ]
 | 
			
		||||
    items += [
 | 
			
		||||
        models.Log(
 | 
			
		||||
            station=station,
 | 
			
		||||
            type=models.Log.TYPE_ON_AIR,
 | 
			
		||||
            date=now + tz.timedelta(hours=-9, minutes=i),
 | 
			
		||||
            source="14",
 | 
			
		||||
            track=track,
 | 
			
		||||
            sound=track.sound,
 | 
			
		||||
        )
 | 
			
		||||
        for i, track in enumerate(tracks)
 | 
			
		||||
    ]
 | 
			
		||||
    models.Log.objects.bulk_create(items)
 | 
			
		||||
    return items
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def logs_qs(logs):
 | 
			
		||||
    return models.Log.objects.filter(pk__in=(r.pk for r in logs))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def file():
 | 
			
		||||
    return File(data=b"")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def gzip(file):
 | 
			
		||||
    gzip = Interface.inject(log_archiver, "gzip", {"open": file})
 | 
			
		||||
    yield gzip
 | 
			
		||||
    gzip._irelease()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def archiver():
 | 
			
		||||
    return log_archiver.LogArchiver()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestLogArchiver:
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_archive_then_load_file(self, archiver, file, gzip, logs, logs_qs):
 | 
			
		||||
        # before logs are deleted from db, get data
 | 
			
		||||
        sorted = archiver.sort_logs(logs_qs)
 | 
			
		||||
        paths = {
 | 
			
		||||
            archiver.get_path(station, date) for station, date in sorted.keys()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        count = archiver.archive(logs_qs, keep=False)
 | 
			
		||||
        assert count == len(logs)
 | 
			
		||||
        assert not logs_qs.count()
 | 
			
		||||
        assert all(
 | 
			
		||||
            path in paths for path, *_ in gzip._traces("open", args=True)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        results = archiver.load_file("dummy path")
 | 
			
		||||
        assert results
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_archive_no_qs(self, archiver):
 | 
			
		||||
        count = archiver.archive(models.Log.objects.none())
 | 
			
		||||
        assert not count
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_sort_log(self, archiver, logs_qs):
 | 
			
		||||
        sorted = archiver.sort_logs(logs_qs)
 | 
			
		||||
 | 
			
		||||
        assert sorted
 | 
			
		||||
        for (station, date), logs in sorted.items():
 | 
			
		||||
            assert all(
 | 
			
		||||
                log.station == station and log.date.date() == date
 | 
			
		||||
                for log in logs
 | 
			
		||||
            )
 | 
			
		||||
							
								
								
									
										64
									
								
								aircox/tests/controllers/test_playlist_import.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								aircox/tests/controllers/test_playlist_import.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
import os
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from aircox.test import Interface
 | 
			
		||||
from aircox.controllers import playlist_import
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
csv_data = [
 | 
			
		||||
    {
 | 
			
		||||
        "artist": "Artist 1",
 | 
			
		||||
        "title": "Title 1",
 | 
			
		||||
        "minutes": "1",
 | 
			
		||||
        "seconds": "0",
 | 
			
		||||
        "tags": "tag1,tag12",
 | 
			
		||||
        "info": "info1",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "artist": "Artist 2",
 | 
			
		||||
        "title": "Title 2",
 | 
			
		||||
        "minutes": "2",
 | 
			
		||||
        "seconds": "1",
 | 
			
		||||
        "tags": "tag2,tag12",
 | 
			
		||||
        "info": "info2",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "artist": "Artist 3",
 | 
			
		||||
        "title": "Title 3",
 | 
			
		||||
        "minutes": "3",
 | 
			
		||||
        "seconds": "2",
 | 
			
		||||
        "tags": "",
 | 
			
		||||
        "info": "",
 | 
			
		||||
    },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def importer(sound):
 | 
			
		||||
    path = os.path.join(os.path.dirname(__file__), "playlist.csv")
 | 
			
		||||
    return playlist_import.PlaylistImport(path, sound=sound)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPlaylistImport:
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_run(self, importer):
 | 
			
		||||
        iface = Interface(None, {"read": None, "make_playlist": None})
 | 
			
		||||
        importer.read = iface.read
 | 
			
		||||
        importer.make_playlist = iface.make_playlist
 | 
			
		||||
        importer.run()
 | 
			
		||||
        assert iface._trace("read")
 | 
			
		||||
        assert iface._trace("make_playlist")
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_read(self, importer):
 | 
			
		||||
        importer.read()
 | 
			
		||||
        assert importer.data == csv_data
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test_make_playlist(self, importer, sound):
 | 
			
		||||
        importer.data = csv_data
 | 
			
		||||
        importer.make_playlist()
 | 
			
		||||
        track_artists = sound.track_set.all().values_list("artist", flat=True)
 | 
			
		||||
        csv_artists = {r["artist"] for r in csv_data}
 | 
			
		||||
        assert set(track_artists) == csv_artists
 | 
			
		||||
        # TODO: check other values
 | 
			
		||||
							
								
								
									
										111
									
								
								aircox/tests/controllers/test_sound_file.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								aircox/tests/controllers/test_sound_file.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
 | 
			
		||||
from django.conf import settings as conf
 | 
			
		||||
from django.utils import timezone as tz
 | 
			
		||||
 | 
			
		||||
from aircox import models
 | 
			
		||||
from aircox.controllers.sound_file import SoundFile
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def path_infos():
 | 
			
		||||
    return {
 | 
			
		||||
        "test/20220101_10h13_1_sample_1.mp3": {
 | 
			
		||||
            "year": 2022,
 | 
			
		||||
            "month": 1,
 | 
			
		||||
            "day": 1,
 | 
			
		||||
            "hour": 10,
 | 
			
		||||
            "minute": 13,
 | 
			
		||||
            "n": 1,
 | 
			
		||||
            "name": "Sample 1",
 | 
			
		||||
        },
 | 
			
		||||
        "test/20220102_10h13_sample_2.mp3": {
 | 
			
		||||
            "year": 2022,
 | 
			
		||||
            "month": 1,
 | 
			
		||||
            "day": 2,
 | 
			
		||||
            "hour": 10,
 | 
			
		||||
            "minute": 13,
 | 
			
		||||
            "name": "Sample 2",
 | 
			
		||||
        },
 | 
			
		||||
        "test/20220103_1_sample_3.mp3": {
 | 
			
		||||
            "year": 2022,
 | 
			
		||||
            "month": 1,
 | 
			
		||||
            "day": 3,
 | 
			
		||||
            "n": 1,
 | 
			
		||||
            "name": "Sample 3",
 | 
			
		||||
        },
 | 
			
		||||
        "test/20220104_sample_4.mp3": {
 | 
			
		||||
            "year": 2022,
 | 
			
		||||
            "month": 1,
 | 
			
		||||
            "day": 4,
 | 
			
		||||
            "name": "Sample 4",
 | 
			
		||||
        },
 | 
			
		||||
        "test/20220105.mp3": {
 | 
			
		||||
            "year": 2022,
 | 
			
		||||
            "month": 1,
 | 
			
		||||
            "day": 5,
 | 
			
		||||
            "name": "20220105",
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def sound_files(path_infos):
 | 
			
		||||
    return {
 | 
			
		||||
        k: r
 | 
			
		||||
        for k, r in (
 | 
			
		||||
            (path, SoundFile(conf.MEDIA_ROOT + "/" + path))
 | 
			
		||||
            for path in path_infos.keys()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sound_path(sound_files):
 | 
			
		||||
    for path, sound_file in sound_files.items():
 | 
			
		||||
        assert path == sound_file.sound_path
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_read_path(path_infos, sound_files):
 | 
			
		||||
    for path, sound_file in sound_files.items():
 | 
			
		||||
        expected = path_infos[path]
 | 
			
		||||
        result = sound_file.read_path(path)
 | 
			
		||||
        # remove None values
 | 
			
		||||
        result = {k: v for k, v in result.items() if v is not None}
 | 
			
		||||
        assert expected == result, "path: {}".format(path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _setup_diff(program, info):
 | 
			
		||||
    episode = models.Episode(program=program, title="test-episode")
 | 
			
		||||
    at = tz.datetime(
 | 
			
		||||
        **{
 | 
			
		||||
            k: info[k]
 | 
			
		||||
            for k in ("year", "month", "day", "hour", "minute")
 | 
			
		||||
            if info.get(k)
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    at = tz.make_aware(at)
 | 
			
		||||
    diff = models.Diffusion(
 | 
			
		||||
        episode=episode, start=at, end=at + timedelta(hours=1)
 | 
			
		||||
    )
 | 
			
		||||
    episode.save()
 | 
			
		||||
    diff.save()
 | 
			
		||||
    return diff
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.django_db(transaction=True)
 | 
			
		||||
def test_find_episode(sound_files):
 | 
			
		||||
    station = models.Station(name="test-station")
 | 
			
		||||
    program = models.Program(station=station, title="test")
 | 
			
		||||
    station.save()
 | 
			
		||||
    program.save()
 | 
			
		||||
 | 
			
		||||
    for path, sound_file in sound_files.items():
 | 
			
		||||
        infos = sound_file.read_path(path)
 | 
			
		||||
        diff = _setup_diff(program, infos)
 | 
			
		||||
        sound = models.Sound(program=diff.program, file=path)
 | 
			
		||||
        result = sound_file.find_episode(sound, infos)
 | 
			
		||||
        assert diff.episode == result
 | 
			
		||||
 | 
			
		||||
    # TODO: find_playlist, sync
 | 
			
		||||
							
								
								
									
										265
									
								
								aircox/tests/controllers/test_sound_monitor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								aircox/tests/controllers/test_sound_monitor.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,265 @@
 | 
			
		||||
from concurrent import futures
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
now = tz.datetime.now()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def event():
 | 
			
		||||
    return Interface(src_path="/tmp/src_path", dest_path="/tmp/dest_path")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def interfaces():
 | 
			
		||||
    items = {
 | 
			
		||||
        "SoundFile": Interface.inject(
 | 
			
		||||
            sound_monitor,
 | 
			
		||||
            "SoundFile",
 | 
			
		||||
            {
 | 
			
		||||
                "sync": None,
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        "time": Interface.inject(
 | 
			
		||||
            sound_monitor,
 | 
			
		||||
            "time",
 | 
			
		||||
            {
 | 
			
		||||
                "sleep": None,
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        "datetime": Interface.inject(sound_monitor, "datetime", {"now": now}),
 | 
			
		||||
    }
 | 
			
		||||
    yield items
 | 
			
		||||
    for item in items.values():
 | 
			
		||||
        item._irelease()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def task(interfaces):
 | 
			
		||||
    return sound_monitor.Task()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def delete_task(interfaces):
 | 
			
		||||
    return sound_monitor.DeleteTask()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def move_task(interfaces):
 | 
			
		||||
    return sound_monitor.MoveTask()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
    def test_ping(self, task):
 | 
			
		||||
        task.timestamp = None
 | 
			
		||||
        task.ping()
 | 
			
		||||
        assert task.timestamp >= now
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test___call__(self, logger, task, event):
 | 
			
		||||
        task.log_msg = "--{event.src_path}--"
 | 
			
		||||
        sound_file = task(event, logger=logger, kw=13)
 | 
			
		||||
        assert sound_file._trace("sync", kw=True) == {"kw": 13}
 | 
			
		||||
        assert logger._trace("info", args=True) == (
 | 
			
		||||
            task.log_msg.format(event=event),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestDeleteTask:
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test___call__(self, delete_task, logger, task, event):
 | 
			
		||||
        sound_file = delete_task(event, logger=logger)
 | 
			
		||||
        assert sound_file._trace("sync", kw=True) == {"deleted": True}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestMoveTask:
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test__call___with_sound(self, move_task, sound, event, logger):
 | 
			
		||||
        event.src_path = sound.file.name
 | 
			
		||||
        sound_file = move_task(event, logger=logger)
 | 
			
		||||
        assert isinstance(sound_file._trace("sync", kw="sound"), Sound)
 | 
			
		||||
        assert sound_file.path == sound.file.name
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.django_db
 | 
			
		||||
    def test__call___no_sound(self, move_task, event, logger):
 | 
			
		||||
        sound_file = move_task(event, logger=logger)
 | 
			
		||||
        assert sound_file._trace("sync", kw=True) == {}
 | 
			
		||||
        assert sound_file.path == event.dest_path
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestModifiedTask:
 | 
			
		||||
    def test_wait(self, modified_task):
 | 
			
		||||
        dt_now = now + modified_task.timeout_delta - tz.timedelta(hours=10)
 | 
			
		||||
        datetime = Interface.inject(sound_monitor, "datetime", {"now": dt_now})
 | 
			
		||||
 | 
			
		||||
        def sleep(imeta, n):
 | 
			
		||||
            datetime._imeta.funcs[
 | 
			
		||||
                "now"
 | 
			
		||||
            ] = modified_task.timestamp + tz.timedelta(hours=10)
 | 
			
		||||
 | 
			
		||||
        time = Interface.inject(sound_monitor, "time", {"sleep": sleep})
 | 
			
		||||
        modified_task.wait()
 | 
			
		||||
        assert time._trace("sleep", args=True)
 | 
			
		||||
 | 
			
		||||
        datetime._imeta.release()
 | 
			
		||||
 | 
			
		||||
    def test__call__(self, modified_task, event):
 | 
			
		||||
        interface(modified_task, {"wait": None})
 | 
			
		||||
        modified_task(event)
 | 
			
		||||
        assert modified_task.calls["wait"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestMonitorHandler:
 | 
			
		||||
    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_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_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_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__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},
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        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, 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, monitor, program, logger):
 | 
			
		||||
        interface = Interface(None, {"scan_for_program": None})
 | 
			
		||||
        monitor.scan_for_program = interface.scan_for_program
 | 
			
		||||
        dirs = monitor.scan(logger)
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
							
								
								
									
										123
									
								
								aircox/tests/controllers/test_sound_stats.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								aircox/tests/controllers/test_sound_stats.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,123 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from aircox.test import Interface
 | 
			
		||||
from aircox.controllers import sound_stats
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sox_output = """
 | 
			
		||||
    DC offset   0.000000\n
 | 
			
		||||
    Min level   0.000000\n
 | 
			
		||||
    Max level   0.000000\n
 | 
			
		||||
    Pk lev dB       -inf\n
 | 
			
		||||
    RMS lev dB      -inf\n
 | 
			
		||||
    RMS Pk dB       -inf\n
 | 
			
		||||
    RMS Tr dB       -inf\n
 | 
			
		||||
    Crest factor    1.00\n
 | 
			
		||||
    Flat factor   179.37\n
 | 
			
		||||
    Pk count       1.86G\n
 | 
			
		||||
    Bit-depth       0/0\n
 | 
			
		||||
    Num samples     930M\n
 | 
			
		||||
    Length s   19383.312\n
 | 
			
		||||
    Scale max   1.000000\n
 | 
			
		||||
    Window s       0.050\n
 | 
			
		||||
"""
 | 
			
		||||
sox_values = {
 | 
			
		||||
    "DC offset": 0.0,
 | 
			
		||||
    "Min level": 0.0,
 | 
			
		||||
    "Max level": 0.0,
 | 
			
		||||
    "Pk lev dB": float("-inf"),
 | 
			
		||||
    "RMS lev dB": float("-inf"),
 | 
			
		||||
    "RMS Pk dB": float("-inf"),
 | 
			
		||||
    "RMS Tr dB": float("-inf"),
 | 
			
		||||
    "Flat factor": 179.37,
 | 
			
		||||
    "length": 19383.312,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def sox_interfaces():
 | 
			
		||||
    process = Interface(
 | 
			
		||||
        None, {"communicate": ("", sox_output.encode("utf-8"))}
 | 
			
		||||
    )
 | 
			
		||||
    subprocess = Interface.inject(
 | 
			
		||||
        sound_stats, "subprocess", {"Popen": lambda *_, **__: process}
 | 
			
		||||
    )
 | 
			
		||||
    yield {"process": process, "subprocess": subprocess}
 | 
			
		||||
    subprocess._irelease()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def sox_stats(sox_interfaces):
 | 
			
		||||
    return sound_stats.SoxStats()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def stats():
 | 
			
		||||
    return sound_stats.SoundStats("/tmp/audio.wav", sample_length=10)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def stats_interfaces(stats):
 | 
			
		||||
    def iw(path, **kw):
 | 
			
		||||
        kw["path"] = path
 | 
			
		||||
        kw.setdefault("length", stats.sample_length * 2)
 | 
			
		||||
        return kw
 | 
			
		||||
 | 
			
		||||
    SxS = sound_stats.SoxStats
 | 
			
		||||
    sound_stats.SoxStats = iw
 | 
			
		||||
    yield iw
 | 
			
		||||
    sound_stats.SoxStats = SxS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestSoxStats:
 | 
			
		||||
    def test_parse(self, sox_stats):
 | 
			
		||||
        values = sox_stats.parse(sox_output)
 | 
			
		||||
        assert values == sox_values
 | 
			
		||||
 | 
			
		||||
    def test_analyse(self, sox_stats, sox_interfaces):
 | 
			
		||||
        sox_stats.analyse("fake_path", 1, 2)
 | 
			
		||||
        assert sox_interfaces["subprocess"]._trace("Popen") == (
 | 
			
		||||
            (["sox", "fake_path", "-n", "trim", "1", "2", "stats"],),
 | 
			
		||||
            {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE},
 | 
			
		||||
        )
 | 
			
		||||
        assert sox_stats.values == sox_values
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestSoundStats:
 | 
			
		||||
    def test_get_file_stats(self, stats):
 | 
			
		||||
        file_stats = {"a": 134}
 | 
			
		||||
        stats.stats = [file_stats]
 | 
			
		||||
        assert stats.get_file_stats() is file_stats
 | 
			
		||||
 | 
			
		||||
    def test_get_file_stats_none(self, stats):
 | 
			
		||||
        stats.stats = []
 | 
			
		||||
        assert stats.get_file_stats() is None
 | 
			
		||||
 | 
			
		||||
    def test_analyse(self, stats, stats_interfaces):
 | 
			
		||||
        stats.analyse()
 | 
			
		||||
        assert stats.stats == [
 | 
			
		||||
            {"path": stats.path, "length": stats.sample_length * 2},
 | 
			
		||||
            {"path": stats.path, "at": 0, "length": stats.sample_length},
 | 
			
		||||
            {"path": stats.path, "at": 10, "length": stats.sample_length},
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def test_analyse_no_sample_length(self, stats, stats_interfaces):
 | 
			
		||||
        stats.sample_length = 0
 | 
			
		||||
        stats.analyse()
 | 
			
		||||
        assert stats.stats == [{"length": 0, "path": stats.path}]
 | 
			
		||||
 | 
			
		||||
    def test_check(self, stats):
 | 
			
		||||
        good = [{"val": i} for i in range(0, 11)]
 | 
			
		||||
        bad = [{"val": i} for i in range(-10, 0)] + [
 | 
			
		||||
            {"val": i} for i in range(11, 20)
 | 
			
		||||
        ]
 | 
			
		||||
        stats.stats = good + bad
 | 
			
		||||
        calls = {}
 | 
			
		||||
        stats.resume = lambda *_: calls.setdefault("resume", True)
 | 
			
		||||
        stats.check("val", 0, 10)
 | 
			
		||||
 | 
			
		||||
        assert calls == {"resume": True}
 | 
			
		||||
        assert all(i < len(good) for i in stats.good)
 | 
			
		||||
        assert all(i >= len(good) for i in stats.bad)
 | 
			
		||||
		Reference in New Issue
	
	Block a user