BAPSicle/tests/test_player.py

293 lines
9.6 KiB
Python
Raw Normal View History

2021-04-08 21:56:57 +00:00
from helpers.os_environment import isMacOS
2021-04-05 23:32:58 +00:00
from queue import Empty
import unittest
import multiprocessing
import time
2021-04-06 21:39:08 +00:00
import os
import json
2021-04-05 23:32:58 +00:00
from player import Player
from helpers.logging_manager import LoggingManager
2021-04-08 19:53:51 +00:00
2021-04-05 23:32:58 +00:00
# How long to wait (by default) in secs for the player to respond.
TIMEOUT_MSG_MAX_S = 10
TIMEOUT_QUIT_S = 10
2021-04-06 21:39:08 +00:00
test_dir = dir_path = os.path.dirname(os.path.realpath(__file__)) + "/"
resource_dir = test_dir + "resources/"
2021-04-06 23:07:15 +00:00
2021-04-08 21:05:25 +00:00
2021-04-06 23:07:15 +00:00
# All because constant dicts are still mutable in python :/
def getPlanItem(length: int, weight: int):
2021-04-08 19:53:51 +00:00
if length not in [1, 2, 5]:
2021-04-06 23:07:15 +00:00
raise ValueError("Invalid length dummy planitem.")
# TODO: This assumes we're handling one channel where timeslotitemid is unique
item = {
"timeslotitemid": weight,
"managedid": str(length),
2021-04-08 19:53:51 +00:00
"filename": resource_dir + str(length) + "sec.mp3",
2021-04-06 23:07:15 +00:00
"weight": weight,
2021-04-08 19:53:51 +00:00
"title": str(length) + "sec",
"length": "00:00:0{}".format(length),
2021-04-06 23:07:15 +00:00
}
return item
2021-04-08 19:53:51 +00:00
2021-04-06 23:07:15 +00:00
def getPlanItemJSON(length: int, weight: int):
return str(json.dumps(getPlanItem(length, weight)))
2021-04-08 19:53:51 +00:00
2021-04-05 23:32:58 +00:00
class TestPlayer(unittest.TestCase):
player: multiprocessing.Process
player_from_q: multiprocessing.Queue
player_to_q: multiprocessing.Queue
logger: LoggingManager
# initialization logic for the test suite declared in the test module
# code that is executed before all tests in one test run
@classmethod
def setUpClass(cls):
cls.logger = LoggingManager("Test_Player")
# clean up logic for the test suite declared in the test module
# code that is executed after all tests in one test run
@classmethod
def tearDownClass(cls):
pass
# initialization logic
# code that is executed before each test
def setUp(self):
self.player_from_q = multiprocessing.Queue()
self.player_to_q = multiprocessing.Queue()
2021-04-08 19:53:51 +00:00
self.player = multiprocessing.Process(
target=Player, args=(-1, self.player_to_q, self.player_from_q)
)
2021-04-05 23:32:58 +00:00
self.player.start()
2021-04-08 19:53:51 +00:00
self._send_msg_wait_OKAY("CLEAR") # Empty any previous track items.
2021-04-06 23:07:15 +00:00
self._send_msg_wait_OKAY("STOP")
self._send_msg_wait_OKAY("UNLOAD")
self._send_msg_wait_OKAY("PLAYONLOAD:False")
self._send_msg_wait_OKAY("REPEAT:none")
self._send_msg_wait_OKAY("AUTOADVANCE:True")
2021-04-05 23:32:58 +00:00
# clean up logic
# code that is executed after each test
def tearDown(self):
# Try to kill it, waits the timeout.
if self._send_msg_and_wait("QUIT"):
self.player.join(timeout=TIMEOUT_QUIT_S)
self.logger.log.info("Player quit successfully.")
else:
self.logger.log.error("No response on teardown, terminating player.")
# It's brain dead :/
self.player.terminate()
def _send_msg(self, msg: str):
self.player_to_q.put("TEST:{}".format(msg))
2021-04-08 19:53:51 +00:00
def _wait_for_msg(
self, msg: str, sources_filter=["TEST"], timeout: int = TIMEOUT_MSG_MAX_S
):
2021-04-05 23:32:58 +00:00
elapsed = 0
got_anything = False
while elapsed < timeout:
try:
response: str = self.player_from_q.get_nowait()
if response:
2021-04-08 19:53:51 +00:00
self.logger.log.info(
"Received response: {}\nWas looking for {}:{}".format(
response, sources_filter, msg
)
)
2021-04-05 23:32:58 +00:00
got_anything = True
2021-04-08 19:53:51 +00:00
source = response[: response.index(":")]
2021-04-05 23:32:58 +00:00
if source in sources_filter:
2021-04-08 19:53:51 +00:00
return response[
2021-04-08 21:48:38 +00:00
len(source + ":" + msg) + 1:
2021-04-08 19:53:51 +00:00
] # +1 to remove trailing : on source.
2021-04-08 21:48:38 +00:00
except Empty:
2021-04-05 23:32:58 +00:00
pass
finally:
time.sleep(0.01)
elapsed += 0.01
2021-04-05 23:32:58 +00:00
return False if got_anything else None
2021-04-08 19:53:51 +00:00
def _send_msg_and_wait(
self, msg: str, sources_filter=["TEST"], timeout: int = TIMEOUT_MSG_MAX_S
):
2021-04-05 23:32:58 +00:00
self._send_msg(msg)
return self._wait_for_msg(msg, sources_filter, timeout)
2021-04-08 19:53:51 +00:00
def _send_msg_wait_OKAY(
self, msg: str, sources_filter=["TEST"], timeout: int = TIMEOUT_MSG_MAX_S
):
2021-04-06 21:39:08 +00:00
response = self._send_msg_and_wait(msg, sources_filter, timeout)
self.assertTrue(response)
self.assertTrue(isinstance(response, str))
response = response.split(":", 1)
self.assertEqual(response[0], "OKAY")
if len(response) > 1:
return response[1]
return None
2021-04-05 23:32:58 +00:00
def test_player_running(self):
2021-04-06 21:39:08 +00:00
response = self._send_msg_wait_OKAY("STATUS")
self.assertTrue(response)
json_obj = json.loads(response)
self.assertTrue(json_obj["initialised"])
def test_player_play(self):
2021-04-06 23:07:15 +00:00
2021-04-08 19:53:51 +00:00
response = self._send_msg_wait_OKAY("ADD:" + getPlanItemJSON(2, 0))
2021-04-06 23:07:15 +00:00
2021-04-06 21:39:08 +00:00
# Should return nothing, just OKAY.
self.assertFalse(response)
# Check we can load the file
response = self._send_msg_wait_OKAY("LOAD:0")
self.assertFalse(response)
# Check we can play the file
response = self._send_msg_wait_OKAY("PLAY")
self.assertFalse(response)
time.sleep(1)
response = self._send_msg_wait_OKAY("STATUS")
self.assertTrue(response)
json_obj = json.loads(response)
self.assertTrue(json_obj["playing"])
# Check the file stops playing.
# TODO: Make sure replay / play on load not enabled.
time.sleep(2)
response = self._send_msg_wait_OKAY("STATUS")
2021-04-05 23:32:58 +00:00
self.assertTrue(response)
2021-04-06 21:39:08 +00:00
json_obj = json.loads(response)
self.assertFalse(json_obj["playing"])
# This test checks if the player progresses to the next item and plays on load.
def test_play_on_load(self):
2021-04-08 19:53:51 +00:00
self._send_msg_wait_OKAY("ADD:" + getPlanItemJSON(5, 0))
2021-04-08 19:53:51 +00:00
self._send_msg_wait_OKAY("ADD:" + getPlanItemJSON(5, 1))
self._send_msg_wait_OKAY("PLAYONLOAD:True")
self._send_msg_wait_OKAY("LOAD:0")
# We should be playing the first item.
response = self._send_msg_wait_OKAY("STATUS")
self.assertTrue(response)
json_obj = json.loads(response)
self.assertTrue(json_obj["playing"])
self.assertEqual(json_obj["loaded_item"]["weight"], 0)
2021-04-08 19:28:35 +00:00
time.sleep(5)
# Now we should be onto playing the second item.
response = self._send_msg_wait_OKAY("STATUS")
self.assertTrue(response)
json_obj = json.loads(response)
self.assertTrue(json_obj["playing"])
self.assertEqual(json_obj["loaded_item"]["weight"], 1)
# Okay, now stop. Test if play on load causes havok with auto advance.
self._send_msg_wait_OKAY("STOP")
self._send_msg_wait_OKAY("AUTOADVANCE:False")
self._send_msg_wait_OKAY("LOAD:0")
2021-04-08 19:28:35 +00:00
time.sleep(6)
# Now, we've not auto-advanced, but we've not loaded a new item.
# Therefore, we shouldn't have played a second time. Leave repeat-one for that.
response = self._send_msg_wait_OKAY("STATUS")
self.assertTrue(response)
json_obj = json.loads(response)
self.assertFalse(json_obj["playing"])
self.assertEqual(json_obj["loaded_item"]["weight"], 0)
# This test checks that the player repeats the first item without moving onto the second.
def test_repeat_one(self):
2021-04-08 19:53:51 +00:00
self._send_msg_wait_OKAY("ADD:" + getPlanItemJSON(5, 0))
# Add a second item to make sure we don't load this one when repeat one.
2021-04-08 19:53:51 +00:00
self._send_msg_wait_OKAY("ADD:" + getPlanItemJSON(5, 1))
# TODO Test without play on load? What's the behaviour here?
self._send_msg_wait_OKAY("PLAYONLOAD:True")
self._send_msg_wait_OKAY("REPEAT:one")
self._send_msg_wait_OKAY("LOAD:0")
2021-04-08 19:28:35 +00:00
time.sleep(0.5)
# Try 3 repeats to make sure.
for repeat in range(3):
# We should be playing the first item.
response = self._send_msg_wait_OKAY("STATUS")
self.assertTrue(response)
json_obj = json.loads(response)
self.assertTrue(json_obj["playing"])
# Check we're not playing the second item.
self.assertEqual(json_obj["loaded_item"]["weight"], 0)
2021-04-08 19:28:35 +00:00
time.sleep(5)
# This test checks that the player repeats all plan items before playing the first again.
def test_repeat_all(self):
# Add two items to repeat all between
2021-04-08 19:53:51 +00:00
self._send_msg_wait_OKAY("ADD:" + getPlanItemJSON(5, 0))
self._send_msg_wait_OKAY("ADD:" + getPlanItemJSON(5, 1))
# TODO Test without play on load? What's the behaviour here?
self._send_msg_wait_OKAY("PLAYONLOAD:True")
self._send_msg_wait_OKAY("REPEAT:all")
self._send_msg_wait_OKAY("LOAD:0")
2021-04-08 19:28:35 +00:00
time.sleep(0.5)
# Try 3 repeats to make sure.
for repeat in range(3):
# We should be playing the first item.
response = self._send_msg_wait_OKAY("STATUS")
self.assertTrue(response)
json_obj = json.loads(response)
self.assertTrue(json_obj["playing"])
self.assertEqual(json_obj["loaded_item"]["weight"], 0)
2021-04-08 19:28:35 +00:00
time.sleep(5)
# We should be playing the second item.
response = self._send_msg_wait_OKAY("STATUS")
self.assertTrue(response)
json_obj = json.loads(response)
self.assertTrue(json_obj["playing"])
self.assertEqual(json_obj["loaded_item"]["weight"], 1)
2021-04-08 19:28:35 +00:00
time.sleep(5)
2021-04-05 23:32:58 +00:00
# runs the unit tests in the module
2021-04-08 19:53:51 +00:00
if __name__ == "__main__":
2021-04-08 21:56:57 +00:00
# Fixes fork error.
if isMacOS():
multiprocessing.set_start_method("spawn", True)
2021-04-08 21:05:25 +00:00
unittest.main()