write tests for streamer
This commit is contained in:
parent
b586bc5309
commit
3d76b656d2
|
@ -49,7 +49,6 @@ class Connector:
|
||||||
socket.AF_UNIX if isinstance(self.address, str) else socket.AF_INET
|
socket.AF_UNIX if isinstance(self.address, str) else socket.AF_INET
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
print("-----", self.address)
|
|
||||||
self.socket = self.socket_class(family, socket.SOCK_STREAM)
|
self.socket = self.socket_class(family, socket.SOCK_STREAM)
|
||||||
self.socket.connect(self.address)
|
self.socket.connect(self.address)
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -173,7 +173,7 @@ class Streamer:
|
||||||
|
|
||||||
self.check_zombie_process()
|
self.check_zombie_process()
|
||||||
self.process = subprocess.Popen(args, stderr=subprocess.STDOUT)
|
self.process = subprocess.Popen(args, stderr=subprocess.STDOUT)
|
||||||
atexit.register(lambda: self.kill_process())
|
atexit.register(self.kill_process)
|
||||||
|
|
||||||
def kill_process(self):
|
def kill_process(self):
|
||||||
if self.process:
|
if self.process:
|
||||||
|
@ -184,6 +184,7 @@ class Streamer:
|
||||||
)
|
)
|
||||||
self.process.kill()
|
self.process.kill()
|
||||||
self.process = None
|
self.process = None
|
||||||
|
atexit.unregister(self.kill_process)
|
||||||
|
|
||||||
def wait_process(self):
|
def wait_process(self):
|
||||||
"""Wait for the process to terminate if there is a process."""
|
"""Wait for the process to terminate if there is a process."""
|
||||||
|
|
0
aircox_streamer/tests/__init__.py
Normal file
0
aircox_streamer/tests/__init__.py
Normal file
|
@ -1,3 +1,5 @@
|
||||||
|
import os
|
||||||
|
|
||||||
from datetime import datetime, time
|
from datetime import datetime, time
|
||||||
import tzlocal
|
import tzlocal
|
||||||
|
|
||||||
|
@ -11,6 +13,9 @@ from aircox_streamer.connector import Connector
|
||||||
local_tz = tzlocal.get_localzone()
|
local_tz = tzlocal.get_localzone()
|
||||||
|
|
||||||
|
|
||||||
|
working_dir = os.path.join(os.path.dirname(__file__), "working_dir")
|
||||||
|
|
||||||
|
|
||||||
class FakeSocket:
|
class FakeSocket:
|
||||||
FAILING_ADDRESS = -1
|
FAILING_ADDRESS = -1
|
||||||
"""Connect with this address fails."""
|
"""Connect with this address fails."""
|
||||||
|
@ -62,7 +67,7 @@ class FakeSocket:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def station():
|
def station():
|
||||||
station = models.Station(
|
station = models.Station(
|
||||||
name="test", path="/tmp", default=True, active=True
|
name="test", path=working_dir, default=True, active=True
|
||||||
)
|
)
|
||||||
station.save()
|
station.save()
|
||||||
return station
|
return station
|
||||||
|
@ -123,7 +128,7 @@ def sounds(program):
|
||||||
# -- connectors
|
# -- connectors
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def connector():
|
def connector():
|
||||||
obj = Connector("/tmp/test.sock")
|
obj = Connector(os.path.join(working_dir, "test.sock"))
|
||||||
obj.socket_class = FakeSocket
|
obj.socket_class = FakeSocket
|
||||||
yield obj
|
yield obj
|
||||||
obj.close()
|
obj.close()
|
||||||
|
|
39
aircox_streamer/tests/fake_modules/__init__.py
Normal file
39
aircox_streamer/tests/fake_modules/__init__.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import atexit as o_atexit
|
||||||
|
import subprocess as o_subprocess
|
||||||
|
import psutil as o_psutil
|
||||||
|
|
||||||
|
from . import atexit, subprocess, psutil
|
||||||
|
|
||||||
|
modules = [
|
||||||
|
(o_atexit, atexit, {}),
|
||||||
|
(o_subprocess, subprocess, {}),
|
||||||
|
(o_psutil, psutil, {}),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def init_mappings():
|
||||||
|
for original, spoof, mapping in modules:
|
||||||
|
if mapping:
|
||||||
|
continue
|
||||||
|
mapping.update(
|
||||||
|
{
|
||||||
|
attr: (getattr(original, attr, None), spoofed)
|
||||||
|
for attr, spoofed in vars(spoof).items()
|
||||||
|
if not attr.startswith("_") and hasattr(original, attr)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
for original, spoof, mappings in modules:
|
||||||
|
for attr, (orig, spoofed) in mappings.items():
|
||||||
|
setattr(original, attr, spoofed)
|
||||||
|
|
||||||
|
|
||||||
|
def setdown():
|
||||||
|
for original, spoof, mappings in modules:
|
||||||
|
for attr, (orig, spoofed) in mappings.items():
|
||||||
|
setattr(original, attr, orig)
|
||||||
|
|
||||||
|
|
||||||
|
init_mappings()
|
10
aircox_streamer/tests/fake_modules/atexit.py
Normal file
10
aircox_streamer/tests/fake_modules/atexit.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
registered = []
|
||||||
|
"""Items registered by register()"""
|
||||||
|
|
||||||
|
|
||||||
|
def register(func, *args, **kwargs):
|
||||||
|
registered.append(func)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister(func):
|
||||||
|
registered.remove(func)
|
15
aircox_streamer/tests/fake_modules/psutil.py
Normal file
15
aircox_streamer/tests/fake_modules/psutil.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
"""Spoof psutil module in order to run and check tests."""
|
||||||
|
|
||||||
|
|
||||||
|
class FakeNetConnection:
|
||||||
|
def __init__(self, laddr, pid=None):
|
||||||
|
self.laddr = laddr
|
||||||
|
self.pid = pid
|
||||||
|
|
||||||
|
|
||||||
|
def net_connections(*args, **kwargs):
|
||||||
|
return net_connections.result
|
||||||
|
|
||||||
|
|
||||||
|
net_connections.result = []
|
||||||
|
"""Result value of net_connections call."""
|
39
aircox_streamer/tests/fake_modules/subprocess.py
Normal file
39
aircox_streamer/tests/fake_modules/subprocess.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
"""Spoof psutil module in order to run and check tests Resulting values of
|
||||||
|
method calls are set inside `fixtures` module."""
|
||||||
|
|
||||||
|
STDOUT = 1
|
||||||
|
STDERR = 2
|
||||||
|
STDIN = 3
|
||||||
|
|
||||||
|
|
||||||
|
class FakeProcess:
|
||||||
|
args = None
|
||||||
|
kwargs = None
|
||||||
|
"""Kwargs passed to Popen."""
|
||||||
|
killed = False
|
||||||
|
"""kill() have been called."""
|
||||||
|
waited = False
|
||||||
|
"""wait() have been called."""
|
||||||
|
polled = False
|
||||||
|
"""poll() have been called."""
|
||||||
|
poll_result = None
|
||||||
|
"""Result of poll() method."""
|
||||||
|
|
||||||
|
def __init__(self, args=[], kwargs={}):
|
||||||
|
self.pid = -13
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def kill(self):
|
||||||
|
self.killed = True
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
self.waited = True
|
||||||
|
|
||||||
|
def poll(self):
|
||||||
|
self.polled = True
|
||||||
|
return self.poll_result
|
||||||
|
|
||||||
|
|
||||||
|
def Popen(args, **kwargs):
|
||||||
|
return FakeProcess(args, kwargs)
|
|
@ -1,6 +1,9 @@
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
from .conftest import working_dir
|
||||||
|
|
||||||
|
|
||||||
class TestConnector:
|
class TestConnector:
|
||||||
payload = "non_value_info\n" 'a="value_1"\n' 'b="value_b"\n' "END"
|
payload = "non_value_info\n" 'a="value_1"\n' 'b="value_b"\n' "END"
|
||||||
|
@ -13,7 +16,9 @@ class TestConnector:
|
||||||
assert connector.is_open
|
assert connector.is_open
|
||||||
assert connector.socket.family == socket.AF_UNIX
|
assert connector.socket.family == socket.AF_UNIX
|
||||||
assert connector.socket.type == socket.SOCK_STREAM
|
assert connector.socket.type == socket.SOCK_STREAM
|
||||||
assert connector.socket.address == "/tmp/test.sock"
|
assert connector.socket.address == os.path.join(
|
||||||
|
working_dir, "test.sock"
|
||||||
|
)
|
||||||
connector.close()
|
connector.close()
|
||||||
|
|
||||||
def test_open_af_inet(self, connector):
|
def test_open_af_inet(self, connector):
|
||||||
|
|
|
@ -1,50 +1,150 @@
|
||||||
# import pytest
|
import os
|
||||||
|
|
||||||
# from aircox_streamer import controllers
|
import pytest
|
||||||
|
|
||||||
|
from aircox_streamer import controllers
|
||||||
|
from . import fake_modules
|
||||||
|
from .fake_modules import atexit, subprocess, psutil
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSource:
|
||||||
|
synced = False
|
||||||
|
|
||||||
|
def sync(self):
|
||||||
|
self.synced = True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def streamer(station, connector, station_ports, stream):
|
||||||
|
fake_modules.setup()
|
||||||
|
streamer = controllers.Streamer(station, connector)
|
||||||
|
psutil.net_connections.result = [
|
||||||
|
psutil.FakeNetConnection(streamer.socket_path, None),
|
||||||
|
]
|
||||||
|
yield streamer
|
||||||
|
fake_modules.setdown()
|
||||||
|
|
||||||
|
|
||||||
class TestStreamer:
|
class TestStreamer:
|
||||||
def test_socket_path(self):
|
@pytest.mark.django_db
|
||||||
|
def test_socket_path(self, streamer):
|
||||||
|
assert streamer.socket_path == streamer.connector.address
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_is_ready(self, streamer, socket):
|
||||||
|
socket.recv_data = "item 1\nEND"
|
||||||
|
assert streamer.is_ready
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_is_ready_false(self, streamer, socket):
|
||||||
|
socket.recv_data = ""
|
||||||
|
assert not streamer.is_ready
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_is_running(self, streamer):
|
||||||
|
streamer.process = subprocess.FakeProcess()
|
||||||
|
streamer.process.poll_result = None
|
||||||
|
assert streamer.is_running
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_is_running_no_process(self, streamer):
|
||||||
|
streamer.process = None
|
||||||
|
assert not streamer.is_running
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_is_running_process_died(self, streamer):
|
||||||
|
process = subprocess.FakeProcess()
|
||||||
|
process.poll_result = 1
|
||||||
|
streamer.process = process
|
||||||
|
assert not streamer.is_running
|
||||||
|
assert streamer.process is None
|
||||||
|
assert process.polled
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_playlists(self, streamer, program):
|
||||||
|
result = list(streamer.playlists)
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
result = result[0]
|
||||||
|
assert isinstance(result, controllers.PlaylistSource)
|
||||||
|
assert result.program == program
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_queues(self, streamer):
|
||||||
|
result = list(streamer.queues)
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0] == streamer.dealer
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_init_sources(self, streamer, program):
|
||||||
|
streamer.init_sources()
|
||||||
|
assert isinstance(streamer.dealer, controllers.QueueSource)
|
||||||
|
# one for dealer, one for program
|
||||||
|
assert len(streamer.sources) == 2
|
||||||
|
assert streamer.sources[1].program == program
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_make_config(self, streamer):
|
||||||
|
streamer.make_config()
|
||||||
|
assert os.path.exists(streamer.path)
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_sync(self, streamer):
|
||||||
|
streamer.sources = [FakeSource(), FakeSource()]
|
||||||
|
streamer.sync()
|
||||||
|
assert all(source.synced for source in streamer.sources)
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_fetch(self, streamer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_is_ready(self):
|
@pytest.mark.django_db
|
||||||
pass
|
def test_get_process_args(self, streamer):
|
||||||
|
assert streamer.get_process_args() == [
|
||||||
|
"liquidsoap",
|
||||||
|
"-v",
|
||||||
|
streamer.path,
|
||||||
|
]
|
||||||
|
|
||||||
def test_is_running(self):
|
@pytest.mark.django_db
|
||||||
pass
|
def test_check_zombie_process(self, streamer):
|
||||||
|
with open(streamer.socket_path, "w+") as file:
|
||||||
|
file.write("data")
|
||||||
|
# This test is incomplete, but we can not go further because os module
|
||||||
|
# is not spoofed (too much work) to check if os.kill is called.
|
||||||
|
streamer.check_zombie_process()
|
||||||
|
|
||||||
def test_playlists(self):
|
@pytest.mark.django_db
|
||||||
pass
|
def test_check_zombie_process_no_socket(self, streamer):
|
||||||
|
if os.path.exists(streamer.socket_path):
|
||||||
|
os.remove(streamer.socket_path)
|
||||||
|
streamer.check_zombie_process()
|
||||||
|
|
||||||
def test_queues(self):
|
@pytest.mark.django_db
|
||||||
pass
|
def test_run_process(self, streamer):
|
||||||
|
if os.path.exists(streamer.socket_path):
|
||||||
|
os.remove(streamer.socket_path)
|
||||||
|
streamer.run_process()
|
||||||
|
process = streamer.process
|
||||||
|
|
||||||
def test_send(self):
|
assert process.args == streamer.get_process_args()
|
||||||
pass
|
assert streamer.kill_process in atexit.registered
|
||||||
|
|
||||||
def test_init_sources(self):
|
@pytest.mark.django_db
|
||||||
pass
|
def test_kill_process(self, streamer):
|
||||||
|
streamer.run_process()
|
||||||
|
process = streamer.process
|
||||||
|
streamer.kill_process()
|
||||||
|
|
||||||
def test_make_config(self):
|
assert process.killed
|
||||||
pass
|
assert streamer.process is None
|
||||||
|
assert streamer.kill_process not in atexit.registered
|
||||||
|
|
||||||
def test_sync(self):
|
@pytest.mark.django_db
|
||||||
pass
|
def test_wait_process(self, streamer):
|
||||||
|
process = subprocess.FakeProcess()
|
||||||
|
streamer.process = process
|
||||||
|
streamer.wait_process()
|
||||||
|
|
||||||
def test_fetch(self):
|
assert process.waited
|
||||||
pass
|
assert streamer.process is None
|
||||||
|
|
||||||
def test_get_process_args(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_check_zombie_process(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_run_process(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_kill_process(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_wait_process(self):
|
|
||||||
pass
|
|
||||||
|
|
0
aircox_streamer/tests/working_dir/keepme.txt
Normal file
0
aircox_streamer/tests/working_dir/keepme.txt
Normal file
Loading…
Reference in New Issue
Block a user