aboutsummaryrefslogtreecommitdiffstats
path: root/solo-tool-project/test
diff options
context:
space:
mode:
authorEddy Pedroni <epedroni@pm.me>2024-11-09 20:35:56 +0100
committerEddy Pedroni <epedroni@pm.me>2024-11-09 20:35:56 +0100
commitcda8197669409689be291660f93cb288ab2d31b3 (patch)
tree81db9b0c7c0491e0737cbffb39af6b935c0dfeb8 /solo-tool-project/test
parenta2257a900d4fffd6f94b73f1c48c62370ed1d684 (diff)
Migrate to project-based structure
Diffstat (limited to 'solo-tool-project/test')
-rw-r--r--solo-tool-project/test/abcontroller_unittest.py272
-rw-r--r--solo-tool-project/test/midi_launchpad_mini_integrationtest.py387
-rw-r--r--solo-tool-project/test/notifier_unittest.py100
-rw-r--r--solo-tool-project/test/player_mock.py71
-rw-r--r--solo-tool-project/test/playlist_unittest.py148
-rw-r--r--solo-tool-project/test/session_manager_unittest.py163
-rw-r--r--solo-tool-project/test/solo_tool_integrationtest.py594
-rw-r--r--solo-tool-project/test/test.flacbin0 -> 31743252 bytes
-rw-r--r--solo-tool-project/test/test.mp3bin0 -> 5389533 bytes
-rw-r--r--solo-tool-project/test/test_session.json13
10 files changed, 1748 insertions, 0 deletions
diff --git a/solo-tool-project/test/abcontroller_unittest.py b/solo-tool-project/test/abcontroller_unittest.py
new file mode 100644
index 0000000..d2b7d31
--- /dev/null
+++ b/solo-tool-project/test/abcontroller_unittest.py
@@ -0,0 +1,272 @@
+from solo_tool.abcontroller import ABController
+from collections import namedtuple
+
+TCase = namedtuple("TCase", ["currentPosition", "requestedPosition"])
+AB = namedtuple("AB", ["a", "b"])
+abLimits = AB(0.2, 0.4)
+
+def _checkLimits(uut, tests):
+ requestedPosition = None
+ def callback(newPosition):
+ nonlocal requestedPosition
+ requestedPosition = newPosition
+
+ originalCallback = uut._setPositionCallback
+ uut._setPositionCallback = callback
+
+ for t in tests:
+ uut.positionChanged(t.currentPosition)
+ assert requestedPosition == t.requestedPosition
+
+ uut._setPositionCallback = originalCallback
+
+def checkLimits(uut, aLimit, bLimit, fail=False):
+ tests = [
+ TCase(aLimit - 0.1, None),
+ TCase(aLimit, None),
+ TCase(bLimit - 0.1, None),
+ TCase(bLimit, None),
+ TCase(bLimit + 0.1, aLimit if not fail else None)
+ ]
+ _checkLimits(uut, tests)
+ if not fail:
+ assert uut.getCurrentLimits()[0] == aLimit
+ assert uut.getCurrentLimits()[1] == bLimit
+
+def checkDefaultLimits(uut):
+ tests = [
+ TCase(0.0, None),
+ TCase(0.1, 0.0),
+ TCase(0.5, 0.0)
+ ]
+ _checkLimits(uut, tests)
+
+def test_oneSetOfLimits():
+ song = "/path/to/song"
+
+ uut = ABController()
+ uut.setCurrentSong(song)
+ uut.storeLimits(abLimits.a, abLimits.b)
+ uut.loadLimits(0)
+ assert uut.getLoadedIndex() == 0
+
+ checkLimits(uut, abLimits.a, abLimits.b)
+ assert uut.getStoredLimits(song) == [abLimits]
+
+def test_multipleSetsOfLimits():
+ song = "/path/to/song"
+ abLimits = [
+ AB(0.2, 0.4),
+ AB(0.3, 0.5),
+ AB(0.0, 1.2)
+ ]
+
+ uut = ABController()
+ uut.setCurrentSong(song)
+ for l in abLimits:
+ uut.storeLimits(l.a, l.b)
+
+ for i, l in enumerate(abLimits):
+ uut.loadLimits(i)
+ assert uut.getLoadedIndex() == i
+ checkLimits(uut, l.a, l.b)
+
+ assert uut.getStoredLimits(song) == abLimits
+
+def test_multipleSongs():
+ songs = [
+ "/path/to/song",
+ "/path/to/another/song"
+ ]
+ abLimits = [
+ AB(0.2, 0.4),
+ AB(0.3, 0.5)
+ ]
+ uut = ABController()
+ for i, s in enumerate(songs):
+ uut.storeLimits(abLimits[i].a, abLimits[i].b, s)
+
+ for i, s in enumerate(songs):
+ uut.setCurrentSong(s)
+ uut.loadLimits(0)
+ assert uut.getLoadedIndex() == 0
+
+ checkLimits(uut, abLimits[i].a, abLimits[i].b)
+ assert uut.getStoredLimits(s) == [abLimits[i]]
+
+def test_disableAbRepeat():
+ song = "/path/to/song"
+
+ uut = ABController()
+ uut.setCurrentSong(song)
+ uut.storeLimits(abLimits.a, abLimits.b)
+ uut.loadLimits(0)
+ assert uut.getLoadedIndex() == 0
+
+ assert uut.isEnabled()
+
+ uut.setEnable(False)
+ checkLimits(uut, abLimits.a, abLimits.b, fail=True)
+ assert not uut.isEnabled()
+
+ uut.setEnable(True)
+ checkLimits(uut, abLimits.a, abLimits.b)
+ assert uut.isEnabled()
+
+def test_storeLimitsToSpecificSong():
+ song = "/path/to/song"
+
+ uut = ABController()
+ uut.storeLimits(abLimits.a, abLimits.b, song)
+ uut.setCurrentSong(song)
+ uut.loadLimits(0)
+ assert uut.getLoadedIndex() == 0
+
+ checkLimits(uut, abLimits.a, abLimits.b)
+
+def test_storeLimitsWithoutCurrentSong():
+ uut = ABController()
+ uut.storeLimits(abLimits.a, abLimits.b)
+ uut.loadLimits(0)
+ assert uut.getLoadedIndex() == None
+
+ checkDefaultLimits(uut)
+
+def test_storeLimitsToSongWithoutCurrentSong():
+ song = "/path/to/song"
+ uut = ABController()
+ uut.storeLimits(abLimits.a, abLimits.b, song)
+ uut.loadLimits(0)
+ assert uut.getLoadedIndex() == None
+
+ checkDefaultLimits(uut)
+
+ uut.setCurrentSong(song)
+
+ checkDefaultLimits(uut)
+
+ uut.loadLimits(0)
+ assert uut.getLoadedIndex() == 0
+
+ checkLimits(uut, abLimits.a, abLimits.b)
+
+def test_storeLimitsToCurrentSongButDoNotSetCurrentLimits():
+ song = "/path/to/song"
+ uut = ABController()
+ uut.setCurrentSong(song)
+ uut.storeLimits(abLimits.a, abLimits.b)
+ assert uut.getLoadedIndex() == None
+
+ checkDefaultLimits(uut)
+
+ uut.loadLimits(0)
+ assert uut.getLoadedIndex() == 0
+
+ checkLimits(uut, abLimits.a, abLimits.b)
+
+def test_getStoredLimitsOfInexistentSong():
+ song = "/path/to/song"
+ uut = ABController()
+ assert uut.getStoredLimits(song) == None
+
+def test_clearAbController():
+ songsWithLimits = [
+ ("/path/to/song", AB(0.2, 0.4)),
+ ("/path/to/another/song", AB(0.3, 0.5))
+ ]
+
+ uut = ABController()
+ for s in songsWithLimits:
+ uut.storeLimits(s[1].a, s[1].b, s[0])
+
+ for i, s in enumerate(songsWithLimits):
+ assert uut.getStoredLimits(s[0]) == [s[1]]
+
+ uut.clear()
+
+ for i, s in enumerate(songsWithLimits):
+ assert uut.getStoredLimits(s[0]) == None
+
+def test_setTemporaryLimits():
+ abLimits = [
+ AB(0.2, 0.4),
+ AB(0.3, 0.5),
+ AB(0.0, 1.2)
+ ]
+ uut = ABController()
+
+ for l in abLimits:
+ uut.setLimits(l.a, l.b)
+ assert uut.getLoadedIndex() == None
+ checkLimits(uut, l.a, l.b)
+
+def test_setTemporaryLimitsWithCurrentSong():
+ songLimits = AB(0.2, 0.4)
+ abLimits = [
+ AB(0.2, 0.4),
+ AB(0.3, 0.5),
+ AB(0.0, 1.2)
+ ]
+ song = "/path/to/song"
+ uut = ABController()
+ uut.setCurrentSong(song)
+ uut.storeLimits(songLimits.a, songLimits.b)
+ uut.loadLimits(0)
+ assert uut.getLoadedIndex() == 0
+
+ for l in abLimits:
+ uut.setLimits(l.a, l.b)
+ checkLimits(uut, l.a, l.b)
+
+def test_defaultBehaviour():
+ uut = ABController()
+ checkDefaultLimits(uut)
+
+def test_nextStoredLimit():
+ song = "/path/to/song"
+ abLimits = [
+ AB(0.2, 0.4),
+ AB(0.3, 0.5)
+ ]
+
+ uut = ABController()
+ uut.setCurrentSong(song)
+ for l in abLimits:
+ uut.storeLimits(l.a, l.b)
+
+ checkDefaultLimits(uut)
+
+ uut.nextStoredAbLimits()
+ checkLimits(uut, abLimits[0].a, abLimits[0].b)
+
+ uut.nextStoredAbLimits()
+ checkLimits(uut, abLimits[1].a, abLimits[1].b)
+
+ uut.nextStoredAbLimits()
+ checkLimits(uut, abLimits[1].a, abLimits[1].b)
+
+def test_previousStoredLimit():
+ song = "/path/to/song"
+ abLimits = [
+ AB(0.2, 0.4),
+ AB(0.3, 0.5)
+ ]
+
+ uut = ABController()
+ uut.setCurrentSong(song)
+ for l in abLimits:
+ uut.storeLimits(l.a, l.b)
+
+ checkDefaultLimits(uut)
+
+ uut.previousStoredAbLimits()
+ checkLimits(uut, abLimits[0].a, abLimits[0].b)
+
+ uut.previousStoredAbLimits()
+ checkLimits(uut, abLimits[0].a, abLimits[0].b)
+
+ uut.loadLimits(1)
+ checkLimits(uut, abLimits[1].a, abLimits[1].b)
+
+ uut.previousStoredAbLimits()
+ checkLimits(uut, abLimits[0].a, abLimits[0].b)
diff --git a/solo-tool-project/test/midi_launchpad_mini_integrationtest.py b/solo-tool-project/test/midi_launchpad_mini_integrationtest.py
new file mode 100644
index 0000000..8542aae
--- /dev/null
+++ b/solo-tool-project/test/midi_launchpad_mini_integrationtest.py
@@ -0,0 +1,387 @@
+import pytest
+
+from solo_tool.midi_controller_launchpad_mini import MidiController
+from solo_tool.solo_tool import SoloTool
+from player_mock import Player as PlayerMock
+
+LED_RED = 3
+LED_YELLOW = 126
+LED_GREEN = 124
+LED_OFF = 0
+
+nextSongButton = 119
+previousSongButton = 118
+playPauseButton = 112
+stopButton = 96
+nextLimitButton = 103
+previousLimitButton = 102
+abToggleButton = 101
+jumpToAButton = 99
+
+class MidiWrapperMock:
+ def __init__(self):
+ self.callback = None
+ self.connectedDevice = None
+ self.sentMessages = list()
+
+ def setCallback(self, callback):
+ self.callback = callback
+
+ def connect(self, deviceName):
+ self.connectedDevice = deviceName
+
+ def sendMessage(self, note, velocity, channel):
+ self.sentMessages.append((note, velocity, channel))
+
+ def simulateInput(self, note, velocity=127, channel=0):
+ if self.callback is not None:
+ from mido import Message
+ msg = Message("note_on", note=note, velocity=velocity, channel=channel)
+ self.callback(msg)
+
+ def getLatestMessage(self):
+ return self.sentMessages[-1]
+
+@pytest.fixture
+def playerMock():
+ return PlayerMock()
+
+@pytest.fixture
+def soloTool(playerMock):
+ return SoloTool(playerMock)
+
+@pytest.fixture
+def midiWrapperMock():
+ return MidiWrapperMock()
+
+@pytest.fixture
+def uut(soloTool, midiWrapperMock):
+ return MidiController(soloTool, midiWrapperMock)
+
+def test_connect(uut, midiWrapperMock):
+ expectedDevice = "Launchpad Mini MIDI 1"
+ uut.connect()
+
+ assert midiWrapperMock.connectedDevice == expectedDevice
+
+def test_startStopAndPauseButtons(uut, midiWrapperMock, playerMock):
+ uut.connect()
+
+ assert playerMock.state == PlayerMock.STOPPED
+
+ midiWrapperMock.simulateInput(playPauseButton)
+ assert playerMock.state == PlayerMock.PLAYING
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0)
+
+ midiWrapperMock.simulateInput(stopButton)
+ assert playerMock.state == PlayerMock.STOPPED
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0)
+
+ midiWrapperMock.simulateInput(playPauseButton)
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0)
+
+ midiWrapperMock.simulateInput(playPauseButton)
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0)
+ assert playerMock.state == PlayerMock.PAUSED
+
+ midiWrapperMock.simulateInput(playPauseButton)
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0)
+ assert playerMock.state == PlayerMock.PLAYING
+
+ midiWrapperMock.simulateInput(playPauseButton)
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0)
+
+ midiWrapperMock.simulateInput(stopButton)
+ assert playerMock.state == PlayerMock.STOPPED
+
+def test_startPauseButtonLed(uut, midiWrapperMock, playerMock, soloTool):
+ uut.connect()
+
+ assert playerMock.state == PlayerMock.STOPPED
+
+ playerMock.state = PlayerMock.PLAYING
+ playerMock.simulatePlayingStateChanged()
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0)
+
+ playerMock.state = PlayerMock.STOPPED
+ playerMock.simulatePlayingStateChanged()
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0)
+
+ playerMock.state = PlayerMock.PAUSED
+ playerMock.simulatePlayingStateChanged()
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_YELLOW, 0)
+
+ playerMock.state = PlayerMock.PLAYING
+ playerMock.simulatePlayingStateChanged()
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0)
+
+def test_abToggleButton(uut, midiWrapperMock, soloTool):
+ uut.connect()
+
+ midiWrapperMock.simulateInput(abToggleButton)
+ assert soloTool.isAbLimitEnabled()
+ assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_GREEN, 0)
+
+ midiWrapperMock.simulateInput(abToggleButton)
+ assert not soloTool.isAbLimitEnabled()
+ assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_RED, 0)
+
+def test_abToggleButtonLed(uut, midiWrapperMock, soloTool):
+ uut.connect()
+
+ soloTool.setAbLimitEnable(True)
+ assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_GREEN, 0)
+
+ soloTool.setAbLimitEnable(False)
+ assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_RED, 0)
+
+def test_jumpToAButton(uut, midiWrapperMock, soloTool, playerMock):
+ ab = (0.5, 0.6)
+ uut.connect()
+
+ soloTool.setAbLimits(ab[0], ab[1])
+ assert playerMock.position == 0.0
+
+ midiWrapperMock.simulateInput(jumpToAButton)
+ assert playerMock.position == ab[0]
+
+def test_previousAndNextSongButtons(uut, midiWrapperMock, soloTool, playerMock):
+ songs = [
+ "test.flac",
+ "test.mp3"
+ ]
+ for s in songs:
+ soloTool.addSong(s)
+ uut.connect()
+
+ assert playerMock.currentSong == None
+ midiWrapperMock.simulateInput(nextSongButton)
+ assert playerMock.currentSong == songs[0]
+
+ midiWrapperMock.simulateInput(nextSongButton)
+ assert playerMock.currentSong == songs[1]
+
+ midiWrapperMock.simulateInput(previousSongButton)
+ assert playerMock.currentSong == songs[0]
+
+ midiWrapperMock.simulateInput(previousSongButton)
+ assert playerMock.currentSong == songs[0]
+
+def test_previousAndNextAbButtons(uut, midiWrapperMock, soloTool, playerMock):
+ song = "test.flac"
+ abLimits = [
+ [0.2, 0.4],
+ [0.1, 0.3]
+ ]
+
+ soloTool.addSong(song)
+ soloTool.setSong(0)
+ soloTool.setAbLimitEnable(True)
+
+ for ab in abLimits:
+ soloTool.storeAbLimits(ab[0], ab[1])
+
+ uut.connect()
+
+ def checkLimit(aLimit, bLimit):
+ playerMock.position = bLimit - 0.1
+ soloTool.tick()
+ assert playerMock.position == bLimit - 0.1
+
+ playerMock.position = bLimit + 0.1
+ soloTool.tick()
+ assert playerMock.position == aLimit
+
+ checkLimit(0.0, 0.0)
+
+ midiWrapperMock.simulateInput(nextLimitButton)
+ checkLimit(abLimits[0][0], abLimits[0][1])
+
+ midiWrapperMock.simulateInput(nextLimitButton)
+ checkLimit(abLimits[1][0], abLimits[1][1])
+
+ midiWrapperMock.simulateInput(nextLimitButton)
+ checkLimit(abLimits[1][0], abLimits[1][1])
+
+ midiWrapperMock.simulateInput(previousLimitButton)
+ checkLimit(abLimits[0][0], abLimits[0][1])
+
+ midiWrapperMock.simulateInput(previousLimitButton)
+ checkLimit(abLimits[0][0], abLimits[0][1])
+
+def test_playbackRateButtons(uut, midiWrapperMock, soloTool, playerMock):
+ playbackRateOptions = {
+ 16 : (0.5, [LED_YELLOW] * 1 + [LED_OFF] * 7),
+ 17 : (0.6, [LED_YELLOW] * 2 + [LED_OFF] * 6),
+ 18 : (0.7, [LED_YELLOW] * 3 + [LED_OFF] * 5),
+ 19 : (0.8, [LED_YELLOW] * 4 + [LED_OFF] * 4),
+ 20 : (0.9, [LED_YELLOW] * 5 + [LED_OFF] * 3),
+ 21 : (1.0, [LED_YELLOW] * 6 + [LED_OFF] * 2),
+ 22 : (1.1, [LED_YELLOW] * 7 + [LED_OFF] * 1),
+ 23 : (1.2, [LED_YELLOW] * 8)
+ }
+ uut.connect()
+ assert playerMock.rate == 1.0
+
+ for t, button in enumerate(playbackRateOptions):
+ midiWrapperMock.sentMessages.clear()
+
+ midiWrapperMock.simulateInput(button)
+ assert playerMock.rate == playbackRateOptions[button][0]
+
+ for i, colour in enumerate(playbackRateOptions[button][1]):
+ assert midiWrapperMock.sentMessages[i] == (16 + i, colour, 0)
+
+def test_playbackRateLeds(uut, midiWrapperMock, soloTool, playerMock):
+ playbackRateOptions = [
+ (0.00, [LED_OFF] * 8),
+ (0.49, [LED_OFF] * 8),
+
+ (0.50, [LED_YELLOW] * 1 + [LED_OFF] * 7),
+ (0.59, [LED_YELLOW] * 1 + [LED_OFF] * 7),
+
+ (0.60, [LED_YELLOW] * 2 + [LED_OFF] * 6),
+ (0.69, [LED_YELLOW] * 2 + [LED_OFF] * 6),
+
+ (0.70, [LED_YELLOW] * 3 + [LED_OFF] * 5),
+ (0.79, [LED_YELLOW] * 3 + [LED_OFF] * 5),
+
+ (0.80, [LED_YELLOW] * 4 + [LED_OFF] * 4),
+ (0.89, [LED_YELLOW] * 4 + [LED_OFF] * 4),
+
+ (0.90, [LED_YELLOW] * 5 + [LED_OFF] * 3),
+ (0.99, [LED_YELLOW] * 5 + [LED_OFF] * 3),
+
+ (1.00, [LED_YELLOW] * 6 + [LED_OFF] * 2),
+ (1.09, [LED_YELLOW] * 6 + [LED_OFF] * 2),
+
+ (1.10, [LED_YELLOW] * 7 + [LED_OFF] * 1),
+ (1.19, [LED_YELLOW] * 7 + [LED_OFF] * 1),
+
+ (1.2, [LED_YELLOW] * 8),
+ (1.5, [LED_YELLOW] * 8)
+ ]
+ uut.connect()
+ assert playerMock.rate == 1.0
+
+ for t, (rate, leds) in enumerate(playbackRateOptions):
+ midiWrapperMock.sentMessages.clear()
+
+ soloTool.setPlaybackRate(rate)
+ assert playerMock.rate == rate
+
+ for i, colour in enumerate(leds):
+ assert midiWrapperMock.sentMessages[i] == (16 + i, colour, 0)
+
+def test_playbackVolumeButtons(uut, midiWrapperMock, soloTool, playerMock):
+ playbackVolumeOptions = {
+ 0 : (0.5, [LED_GREEN] * 1 + [LED_OFF] * 7),
+ 1 : (0.6, [LED_GREEN] * 2 + [LED_OFF] * 6),
+ 2 : (0.7, [LED_GREEN] * 3 + [LED_OFF] * 5),
+ 3 : (0.8, [LED_GREEN] * 4 + [LED_OFF] * 4),
+ 4 : (0.9, [LED_GREEN] * 5 + [LED_OFF] * 3),
+ 5 : (1.0, [LED_GREEN] * 6 + [LED_OFF] * 2),
+ 6 : (1.1, [LED_GREEN] * 7 + [LED_OFF] * 1),
+ 7 : (1.2, [LED_GREEN] * 8)
+ }
+ uut.connect()
+ assert playerMock.volume == 1.0
+
+ for t, button in enumerate(playbackVolumeOptions):
+ midiWrapperMock.sentMessages.clear()
+
+ midiWrapperMock.simulateInput(button)
+ assert playerMock.volume == playbackVolumeOptions[button][0]
+
+ for i, colour in enumerate(playbackVolumeOptions[button][1]):
+ assert midiWrapperMock.sentMessages[i] == (i, colour, 0)
+
+def test_playbackVolumeLeds(uut, midiWrapperMock, soloTool, playerMock):
+ playbackVolumeOptions = [
+ (0.00, [LED_OFF] * 8),
+ (0.49, [LED_OFF] * 8),
+
+ (0.50, [LED_GREEN] * 1 + [LED_OFF] * 7),
+ (0.59, [LED_GREEN] * 1 + [LED_OFF] * 7),
+
+ (0.60, [LED_GREEN] * 2 + [LED_OFF] * 6),
+ (0.69, [LED_GREEN] * 2 + [LED_OFF] * 6),
+
+ (0.70, [LED_GREEN] * 3 + [LED_OFF] * 5),
+ (0.79, [LED_GREEN] * 3 + [LED_OFF] * 5),
+
+ (0.80, [LED_GREEN] * 4 + [LED_OFF] * 4),
+ (0.89, [LED_GREEN] * 4 + [LED_OFF] * 4),
+
+ (0.90, [LED_GREEN] * 5 + [LED_OFF] * 3),
+ (0.99, [LED_GREEN] * 5 + [LED_OFF] * 3),
+
+ (1.00, [LED_GREEN] * 6 + [LED_OFF] * 2),
+ (1.09, [LED_GREEN] * 6 + [LED_OFF] * 2),
+
+ (1.10, [LED_GREEN] * 7 + [LED_OFF] * 1),
+ (1.19, [LED_GREEN] * 7 + [LED_OFF] * 1),
+
+ (1.2, [LED_GREEN] * 8),
+ (1.5, [LED_GREEN] * 8)
+ ]
+ uut.connect()
+ assert playerMock.volume == 1.0
+
+ for t, (volume, leds) in enumerate(playbackVolumeOptions):
+ midiWrapperMock.sentMessages.clear()
+
+ soloTool.setPlaybackVolume(volume)
+ assert playerMock.volume == volume
+
+ for i, colour in enumerate(leds):
+ assert midiWrapperMock.sentMessages[i] == (i, colour, 0)
+
+def test_unassignedButton(uut, midiWrapperMock):
+ unassignedButton = 48
+ uut.connect()
+
+ # expect no crash
+ midiWrapperMock.simulateInput(unassignedButton)
+ # XXX would be better to assert that nothing changed in the solo tool
+
+def test_initializationMessages(uut, midiWrapperMock):
+ expectedMessages = set(
+ [(int(i / 8) * 16 + (i % 8), LED_OFF, 0) for i in range(0, 64)] + # clear all
+ [(i, LED_GREEN, 0) for i in range(0, 6)] + # volume row
+ [(i, LED_YELLOW, 0) for i in range(16, 22)] + # playback rate row
+ [
+ (stopButton, LED_RED, 0),
+ (playPauseButton, LED_YELLOW, 0),
+ (abToggleButton, LED_RED, 0),
+ (jumpToAButton, LED_YELLOW, 0),
+ (previousLimitButton, LED_RED, 0),
+ (nextLimitButton, LED_GREEN, 0),
+ (previousSongButton, LED_RED, 0),
+ (nextSongButton, LED_GREEN, 0)
+ ]
+ )
+
+ uut.connect()
+
+ sentMessagesSet = set(midiWrapperMock.sentMessages)
+ assert sentMessagesSet == expectedMessages
+
+def test_playingFeedbackWhenChangingSong(uut, midiWrapperMock, soloTool, playerMock):
+ songs = [
+ "test.flac",
+ "test.mp3"
+ ]
+ for s in songs:
+ soloTool.addSong(s)
+ uut.connect()
+
+ soloTool.setSong(0)
+ soloTool.play()
+ assert playerMock.state == PlayerMock.PLAYING
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, LED_GREEN, 0)
+
+ soloTool.nextSong()
+ assert playerMock.state == PlayerMock.STOPPED
+ assert midiWrapperMock.getLatestMessage() == (playPauseButton, LED_YELLOW, 0)
+
diff --git a/solo-tool-project/test/notifier_unittest.py b/solo-tool-project/test/notifier_unittest.py
new file mode 100644
index 0000000..8a6e988
--- /dev/null
+++ b/solo-tool-project/test/notifier_unittest.py
@@ -0,0 +1,100 @@
+import pytest
+
+from solo_tool.notifier import Notifier
+from player_mock import Player
+
+@pytest.fixture
+def mockPlayer():
+ return Player()
+
+@pytest.fixture
+def uut(mockPlayer):
+ return Notifier(mockPlayer)
+
+def test_allEvents(uut):
+ def checkEvent(uut, event):
+ callbacks = 2
+ calledFlags = [False] * callbacks
+ values = [None] * callbacks
+
+ def createCallback(i):
+ def cb(value):
+ nonlocal calledFlags, values
+ calledFlags[i] = True
+ values[i] = value
+
+ return cb
+
+ for i in range(0, callbacks):
+ uut.registerCallback(event, createCallback(i))
+
+ assert not any(calledFlags)
+ uut.notify(event, 123)
+ assert all(calledFlags)
+ assert values == [123] * callbacks
+
+ checkEvent(uut, Notifier.PLAYING_STATE_EVENT)
+ checkEvent(uut, Notifier.PLAYBACK_VOLUME_EVENT)
+ checkEvent(uut, Notifier.PLAYBACK_RATE_EVENT)
+ checkEvent(uut, Notifier.CURRENT_SONG_EVENT)
+ checkEvent(uut, Notifier.CURRENT_AB_EVENT)
+ checkEvent(uut, Notifier.AB_LIMIT_ENABLED_EVENT)
+
+def test_eventWithoutRegisteredCallbacks(uut):
+ uut.notify(Notifier.PLAYING_STATE_EVENT, 0)
+ # expect no crash
+
+def test_eventsWithMockPlayer(uut, mockPlayer):
+ def checkEvent(eventCode, simulateEvent, expectedValue):
+ called = False
+ receivedValue = None
+ def callback(value):
+ nonlocal called, receivedValue
+ called = True
+ receivedValue = value
+
+ uut.registerCallback(eventCode, callback)
+
+ assert not called
+ simulateEvent()
+ assert called
+ assert receivedValue == expectedValue
+
+ mockPlayer.state = 1
+ mockPlayer.volume = 75
+
+ checkEvent(Notifier.PLAYING_STATE_EVENT, mockPlayer.simulatePlayingStateChanged, True)
+ checkEvent(Notifier.PLAYBACK_VOLUME_EVENT, mockPlayer.simulatePlaybackVolumeChanged, 75)
+
+def test_singleEventNotification(uut):
+ playingStateCalled = False
+ def playingStateCallback(value):
+ nonlocal playingStateCalled
+ playingStateCalled = True
+
+ volumeCalled = False
+ def volumeCallback(value):
+ nonlocal volumeCalled
+ volumeCalled = True
+
+ uut.registerCallback(Notifier.PLAYING_STATE_EVENT, playingStateCallback)
+ uut.registerCallback(Notifier.PLAYBACK_VOLUME_EVENT, volumeCallback)
+
+ assert not playingStateCalled
+ assert not volumeCalled
+
+ uut.notify(Notifier.PLAYING_STATE_EVENT, 0)
+ assert playingStateCalled
+ assert not volumeCalled
+
+ playingStateCalled = False
+
+ uut.notify(Notifier.PLAYBACK_VOLUME_EVENT, 0)
+ assert not playingStateCalled
+ assert volumeCalled
+
+ volumeCalled = False
+
+ uut.notify(Notifier.PLAYBACK_RATE_EVENT, 0)
+ assert not playingStateCalled
+ assert not volumeCalled
diff --git a/solo-tool-project/test/player_mock.py b/solo-tool-project/test/player_mock.py
new file mode 100644
index 0000000..3162e0f
--- /dev/null
+++ b/solo-tool-project/test/player_mock.py
@@ -0,0 +1,71 @@
+class Player():
+ STOPPED = 0
+ PLAYING = 1
+ PAUSED = 2
+
+ def __init__(self):
+ self.state = Player.STOPPED
+ self.rate = 1.0
+ self.position = 0.0
+ self.volume = 1.0
+ self.currentSong = None
+ self.playingStateChangedCallback = None
+ self.playbackVolumeChangedCallback = None
+
+ def play(self):
+ previousState = self.state
+ self.state = Player.PLAYING
+ if previousState != Player.PLAYING:
+ self.playingStateChangedCallback()
+
+ def stop(self):
+ previousState = self.state
+ self.state = Player.STOPPED
+ if previousState != Player.STOPPED:
+ self.playingStateChangedCallback()
+
+ def pause(self):
+ previousState = self.state
+ self.state = Player.PAUSED
+ if previousState != Player.PAUSED:
+ self.playingStateChangedCallback()
+
+ def isPlaying(self):
+ return self.state == Player.PLAYING
+
+ def setPlaybackRate(self, rate):
+ self.rate = rate
+
+ def getPlaybackRate(self):
+ return self.rate
+
+ def setPlaybackPosition(self, position):
+ self.position = position
+
+ def getPlaybackPosition(self):
+ return self.position
+
+ def setPlaybackVolume(self, volume):
+ changed = self.volume != volume
+ self.volume = volume
+ if changed:
+ self.playbackVolumeChangedCallback()
+
+ def getPlaybackVolume(self):
+ return self.volume
+
+ def setCurrentSong(self, path):
+ self.stop()
+ self.currentSong = path
+
+ def setPlayingStateChangedCallback(self, callback):
+ self.playingStateChangedCallback = callback
+
+ def simulatePlayingStateChanged(self):
+ self.playingStateChangedCallback()
+
+ def setPlaybackVolumeChangedCallback(self, callback):
+ self.playbackVolumeChangedCallback = callback
+
+ def simulatePlaybackVolumeChanged(self):
+ self.playbackVolumeChangedCallback()
diff --git a/solo-tool-project/test/playlist_unittest.py b/solo-tool-project/test/playlist_unittest.py
new file mode 100644
index 0000000..842ce51
--- /dev/null
+++ b/solo-tool-project/test/playlist_unittest.py
@@ -0,0 +1,148 @@
+from solo_tool.playlist import Playlist
+
+def test_addAndSelectOneSong():
+ songAddedByUser = "/path/to/song"
+ songSetByCallback = None
+
+ def testCallback(song):
+ nonlocal songSetByCallback
+ songSetByCallback = song
+
+ uut = Playlist(testCallback)
+ uut.addSong(songAddedByUser)
+ uut.setCurrentSong(0)
+
+ assert songAddedByUser == songSetByCallback
+ assert uut.getCurrentSong() == songAddedByUser
+ assert uut.getCurrentSongIndex() == 0
+ assert uut.getSongs() == [songAddedByUser]
+
+def test_addTwoSongsAndSelectBoth():
+ songAddedByUser = ["/path/to/song", "/path/to/second/song"]
+ songSetByCallback = None
+
+ def testCallback(song):
+ nonlocal songSetByCallback
+ songSetByCallback = song
+
+ uut = Playlist(testCallback)
+ uut.addSong(songAddedByUser[0])
+ uut.addSong(songAddedByUser[1])
+ assert uut.getSongs() == songAddedByUser
+
+ uut.setCurrentSong(0)
+ assert songAddedByUser[0] == songSetByCallback
+ assert uut.getCurrentSong() == songAddedByUser[0]
+ assert uut.getCurrentSongIndex() == 0
+
+ uut.setCurrentSong(1)
+ assert songAddedByUser[1] == songSetByCallback
+ assert uut.getCurrentSong() == songAddedByUser[1]
+ assert uut.getCurrentSongIndex() == 1
+
+def test_firstAddedSongIsNotSelected():
+ songAddedByUser = "/path/to/song"
+ songSetByCallback = None
+
+ def testCallback(song):
+ nonlocal songSetByCallback
+ songSetByCallback = song
+
+ uut = Playlist(testCallback)
+ uut.addSong(songAddedByUser)
+
+ assert songSetByCallback == None
+ assert uut.getCurrentSong() == None
+ assert uut.getCurrentSongIndex() == None
+ assert uut.getSongs() == [songAddedByUser]
+
+def test_invalidSongSelection():
+ songAddedByUser = "/path/to/song"
+ songSetByCallback = None
+
+ def testCallback(song):
+ nonlocal songSetByCallback
+ songSetByCallback = song
+
+ uut = Playlist(testCallback)
+ assert songSetByCallback == None
+ assert uut.getCurrentSong() == None
+ assert uut.getCurrentSongIndex() == None
+
+ uut.setCurrentSong(10)
+ assert songSetByCallback == None
+ assert uut.getCurrentSong() == None
+ assert uut.getCurrentSongIndex() == None
+
+ uut.addSong(songAddedByUser)
+ uut.setCurrentSong(10)
+ assert songSetByCallback == None
+ assert uut.getCurrentSong() == None
+ assert uut.getCurrentSongIndex() == None
+ assert uut.getSongs() == [songAddedByUser]
+
+def test_clearPlaylist():
+ songAddedByUser = ["/path/to/song", "/path/to/second/song"]
+
+ def dummy(index):
+ pass
+
+ uut = Playlist(dummy)
+ for s in songAddedByUser:
+ uut.addSong(s)
+ uut.setCurrentSong(0)
+
+ assert uut.getSongs() == songAddedByUser
+ assert uut.getCurrentSong() == songAddedByUser[0]
+ assert uut.getCurrentSongIndex() == 0
+
+ uut.clear()
+
+ assert uut.getSongs() == []
+ assert uut.getCurrentSong() == None
+ assert uut.getCurrentSongIndex() == None
+
+def test_nextSong():
+ songAddedByUser = ["/path/to/song", "/path/to/second/song"]
+
+ uut = Playlist(lambda index: None)
+ for s in songAddedByUser:
+ uut.addSong(s)
+ assert uut.getCurrentSong() == None
+ assert uut.getCurrentSongIndex() == None
+
+ uut.nextSong()
+ assert uut.getCurrentSong() == songAddedByUser[0]
+ assert uut.getCurrentSongIndex() == 0
+
+ uut.nextSong()
+ assert uut.getCurrentSong() == songAddedByUser[1]
+ assert uut.getCurrentSongIndex() == 1
+
+ uut.nextSong()
+ assert uut.getCurrentSong() == songAddedByUser[1]
+ assert uut.getCurrentSongIndex() == 1
+
+def test_previousSong():
+ songAddedByUser = ["/path/to/song", "/path/to/second/song"]
+
+ uut = Playlist(lambda index: None)
+ for s in songAddedByUser:
+ uut.addSong(s)
+ assert uut.getCurrentSong() == None
+ assert uut.getCurrentSongIndex() == None
+
+ uut.previousSong()
+ assert uut.getCurrentSong() == songAddedByUser[0]
+ assert uut.getCurrentSongIndex() == 0
+
+ uut.previousSong()
+ assert uut.getCurrentSong() == songAddedByUser[0]
+ assert uut.getCurrentSongIndex() == 0
+
+ uut.setCurrentSong(1)
+ assert uut.getCurrentSong() == songAddedByUser[1]
+ assert uut.getCurrentSongIndex() == 1
+ uut.previousSong()
+ assert uut.getCurrentSong() == songAddedByUser[0]
+ assert uut.getCurrentSongIndex() == 0
diff --git a/solo-tool-project/test/session_manager_unittest.py b/solo-tool-project/test/session_manager_unittest.py
new file mode 100644
index 0000000..5468880
--- /dev/null
+++ b/solo-tool-project/test/session_manager_unittest.py
@@ -0,0 +1,163 @@
+from solo_tool.session_manager import SessionManager
+from json import loads, dumps
+
+testSession = [
+ {
+ "path" : "/path/to/another/song",
+ "ab_limits" : None
+ },
+ {
+ "path" : "/path/to/song",
+ "ab_limits" : [
+ [0.1, 0.2],
+ [0.3, 0.4]
+ ]
+ },
+ {
+ "path" : "/path/to/something",
+ "ab_limits" : [
+ [0.1, 0.2]
+ ]
+ }
+]
+
+class PlaylistMock:
+ def __init__(self):
+ self.lastAddedSong = None
+ self.songs = list()
+
+ def addSong(self, s):
+ self.songs.append(s)
+ self.lastAddedSong = s
+
+ def getSongs(self):
+ return self.songs
+
+ def clear(self):
+ self.__init__()
+
+class ABControllerMock:
+ def __init__(self):
+ self.limits = dict()
+
+ def storeLimits(self, aLimit, bLimit, song="current"):
+ if song not in self.limits:
+ self.limits[song] = list()
+ self.limits[song].append([aLimit, bLimit])
+
+ def getStoredLimits(self, song):
+ return self.limits.get(song)
+
+ def clear(self):
+ self.__init__()
+
+class MockFile:
+ def __init__(self, init=""):
+ self.contents = init
+
+ def open(self, *args):
+ pass
+
+ def write(self, s):
+ self.contents += s
+
+ def read(self):
+ return self.contents
+
+
+def test_addSongs():
+ songs = [
+ "/path/to/song",
+ "/path/to/another/song"
+ ]
+
+ playlistMock = PlaylistMock()
+ uut = SessionManager(playlistMock, None)
+
+ for s in songs:
+ uut.addSong(s)
+ assert playlistMock.lastAddedSong == s
+
+def test_addAbLimits():
+ abLimits = [
+ [0.1, 0.2],
+ [0.3, 0.4]
+ ]
+
+ abControllerMock = ABControllerMock()
+ uut = SessionManager(None, abControllerMock)
+
+ for i, ab in enumerate(abLimits):
+ uut.storeLimits(ab[0], ab[1])
+ assert abControllerMock.limits["current"][i] == ab
+
+def test_loadSession():
+ playlistMock = PlaylistMock()
+ abControllerMock = ABControllerMock()
+ uut = SessionManager(playlistMock, abControllerMock)
+
+ sessionFile = MockFile(dumps(testSession))
+ uut.loadSession(sessionFile)
+
+ for i, entry in enumerate(testSession):
+ expectedSong = entry["path"]
+ expectedLimits = entry["ab_limits"]
+ loadedSong = playlistMock.songs[i]
+ loadedLimits = abControllerMock.limits.get(expectedSong)
+
+ assert loadedSong == expectedSong
+ assert loadedLimits == expectedLimits
+
+def test_saveSession():
+ playlistMock = PlaylistMock()
+ abControllerMock = ABControllerMock()
+ uut = SessionManager(playlistMock, abControllerMock)
+
+ for i, entry in enumerate(testSession):
+ song = entry["path"]
+ playlistMock.addSong(song)
+
+ abLimits = entry["ab_limits"]
+ if abLimits is not None:
+ for l in abLimits:
+ abControllerMock.storeLimits(l[0], l[1], song)
+
+ sessionFile = MockFile()
+ uut.saveSession(sessionFile)
+
+ savedSession = loads(sessionFile.read())
+ assert savedSession == testSession
+
+def test_loadAndSaveEmptySession():
+ playlistMock = PlaylistMock()
+ abControllerMock = ABControllerMock()
+ uut = SessionManager(playlistMock, abControllerMock)
+
+ sessionFile = MockFile()
+
+ uut.saveSession(sessionFile)
+ assert loads(sessionFile.read()) == list()
+
+ uut.loadSession(sessionFile)
+
+ songs = playlistMock.getSongs()
+ assert songs == list()
+ for s in songs:
+ assert abControllerMock.getStoredLimits(s) == None
+
+def test_loadSessionNotAdditive():
+ playlistMock = PlaylistMock()
+ abControllerMock = ABControllerMock()
+ uut = SessionManager(playlistMock, abControllerMock)
+
+ sessionFile = MockFile(dumps(testSession))
+ uut.loadSession(sessionFile)
+ uut.loadSession(sessionFile)
+
+ songs = playlistMock.getSongs()
+ assert len(songs) == len(set(songs))
+ for s in songs:
+ abLimits = abControllerMock.getStoredLimits(s)
+ if abLimits is not None:
+ abLimitStr = [f"[{l[0]}, {l[1]}] " for l in abLimits]
+ assert len(abLimitStr) == len(set(abLimitStr))
diff --git a/solo-tool-project/test/solo_tool_integrationtest.py b/solo-tool-project/test/solo_tool_integrationtest.py
new file mode 100644
index 0000000..5903abf
--- /dev/null
+++ b/solo-tool-project/test/solo_tool_integrationtest.py
@@ -0,0 +1,594 @@
+import pathlib
+import shutil
+import pytest
+
+from solo_tool.solo_tool import SoloTool
+from player_mock import Player as MockPlayer
+
+@pytest.fixture
+def mockPlayer():
+ return MockPlayer()
+
+@pytest.fixture
+def uut(mockPlayer):
+ return SoloTool(mockPlayer)
+
+@pytest.fixture
+def prepared_tmp_path(tmp_path):
+ testFiles = [
+ "test.flac",
+ "test.mp3",
+ "test_session.json"
+ ]
+ for f in testFiles:
+ shutil.copy(pathlib.Path(f), tmp_path)
+
+ return tmp_path
+
+def checkLimit(uut, mockPlayer, aLimit, bLimit):
+ mockPlayer.position = bLimit - 0.1
+ uut.tick()
+ assert mockPlayer.position == bLimit - 0.1
+
+ mockPlayer.position = bLimit + 0.1
+ uut.tick()
+ assert mockPlayer.position == aLimit
+
+def test_playerControls(uut, mockPlayer):
+ assert mockPlayer.state == MockPlayer.STOPPED
+ assert uut.isPlaying() == False
+ uut.play()
+ assert mockPlayer.state == MockPlayer.PLAYING
+ assert uut.isPlaying() == True
+ uut.pause()
+ assert mockPlayer.state == MockPlayer.PAUSED
+ assert uut.isPlaying() == False
+ uut.stop()
+ assert mockPlayer.state == MockPlayer.STOPPED
+ assert uut.isPlaying() == False
+
+ assert mockPlayer.rate == 1.0
+ uut.setPlaybackRate(0.5)
+ assert mockPlayer.rate == 0.5
+
+ assert mockPlayer.position == 0.0
+ uut.setPlaybackPosition(0.5)
+ assert mockPlayer.position == 0.5
+
+ assert mockPlayer.volume == 1.0
+ uut.setPlaybackVolume(0.5)
+ assert mockPlayer.volume == 0.5
+
+def test_addAndSetSongs(uut, mockPlayer):
+ songs = [
+ "test.flac",
+ "test.mp3"
+ ]
+
+ for s in songs:
+ uut.addSong(s)
+ assert mockPlayer.currentSong == None
+
+ for i, s in enumerate(songs):
+ uut.setSong(i)
+ assert mockPlayer.currentSong == songs[i]
+
+def test_nextAndPreviousSong(uut, mockPlayer):
+ songs = [
+ "test.flac",
+ "test.mp3"
+ ]
+
+ for s in songs:
+ uut.addSong(s)
+ assert mockPlayer.currentSong == None
+
+ uut.nextSong()
+ assert mockPlayer.currentSong == songs[0]
+
+ uut.previousSong()
+ assert mockPlayer.currentSong == songs[0]
+
+ uut.nextSong()
+ assert mockPlayer.currentSong == songs[1]
+
+ uut.nextSong()
+ assert mockPlayer.currentSong == songs[1]
+
+def test_addAndSetAbLimits(uut, mockPlayer):
+ song = "test.flac"
+ abLimits = [
+ [0.2, 0.4],
+ [0.1, 0.3]
+ ]
+
+ uut.addSong(song)
+ uut.setSong(0)
+
+ for ab in abLimits:
+ uut.storeAbLimits(ab[0], ab[1])
+
+ mockPlayer.position = 0.0
+ uut.tick()
+ assert mockPlayer.position == 0.0
+
+ mockPlayer.position = 0.5
+ uut.tick()
+ assert mockPlayer.position == 0.5
+
+ uut.loadAbLimits(0)
+
+ uut.tick()
+ assert mockPlayer.position == 0.5
+
+ uut.setAbLimitEnable(True)
+
+ uut.tick()
+ assert mockPlayer.position == 0.2
+
+ uut.tick()
+ assert mockPlayer.position == 0.2
+
+ uut.loadAbLimits(1)
+ uut.tick()
+ assert mockPlayer.position == 0.2
+
+ mockPlayer.position = 0.8
+ uut.tick()
+ assert mockPlayer.position == 0.1
+
+def test_abLimitEnabledGetter(uut):
+ assert not uut.isAbLimitEnabled()
+
+ uut.setAbLimitEnable(True)
+ assert uut.isAbLimitEnabled()
+
+ uut.setAbLimitEnable(False)
+ assert not uut.isAbLimitEnabled()
+
+def test_multipleSongsAndAbLimits(uut, mockPlayer):
+ songs = [
+ "test.flac",
+ "test.mp3"
+ ]
+ abLimits = [
+ [0.2, 0.4],
+ [0.5, 0.7]
+ ]
+
+ for s in songs:
+ uut.addSong(s)
+
+ for i, l in enumerate(abLimits):
+ uut.setSong(i)
+ uut.storeAbLimits(l[0], l[1])
+
+ uut.setAbLimitEnable(True)
+
+ for i, l in enumerate(abLimits):
+ uut.setSong(i)
+ uut.loadAbLimits(0)
+
+ mockPlayer.position = l[0]
+ uut.tick()
+ assert mockPlayer.position == l[0]
+
+ mockPlayer.position = l[1] + 0.1
+ uut.tick()
+ assert mockPlayer.position == l[0]
+
+def test_storeAbLimitsWithoutSong(uut, mockPlayer):
+ song = "test.flac"
+ abLimit = [0.2, 0.4]
+ overflow = abLimit[1] + 0.1
+ default = 0.0
+ mockPlayer.position = overflow
+ uut.setAbLimitEnable(True)
+
+ uut.storeAbLimits(abLimit[0], abLimit[1])
+ uut.tick()
+ assert mockPlayer.position == default
+ mockPlayer.position = overflow
+
+ uut.loadAbLimits(0)
+ uut.tick()
+ assert mockPlayer.position == default
+ mockPlayer.position = overflow
+
+ uut.addSong(song)
+ uut.tick()
+ assert mockPlayer.position == default
+ mockPlayer.position = overflow
+
+ uut.loadAbLimits(0)
+ uut.tick()
+ assert mockPlayer.position == default
+ mockPlayer.position = overflow
+
+ uut.setSong(0)
+ uut.tick()
+ assert mockPlayer.position == default
+ mockPlayer.position = overflow
+
+ uut.loadAbLimits(0)
+ uut.tick()
+ assert mockPlayer.position == default
+ mockPlayer.position = overflow
+
+ uut.storeAbLimits(abLimit[0], abLimit[1])
+ uut.tick()
+ assert mockPlayer.position == default
+ mockPlayer.position = overflow
+
+ uut.loadAbLimits(0)
+ uut.tick()
+ assert mockPlayer.position == abLimit[0]
+
+def test_nextAndPreviousAbLimit(uut, mockPlayer):
+ song = "test.flac"
+ abLimits = [
+ [0.2, 0.4],
+ [0.1, 0.3]
+ ]
+
+ uut.addSong(song)
+ uut.setSong(0)
+ uut.setAbLimitEnable(True)
+
+ for ab in abLimits:
+ uut.storeAbLimits(ab[0], ab[1])
+
+ checkLimit(uut, mockPlayer, 0.0, 0.0) # default limits
+
+ uut.nextStoredAbLimits()
+ checkLimit(uut, mockPlayer, abLimits[0][0], abLimits[0][1])
+
+ uut.nextStoredAbLimits()
+ checkLimit(uut, mockPlayer, abLimits[1][0], abLimits[1][1])
+
+ uut.nextStoredAbLimits()
+ checkLimit(uut, mockPlayer, abLimits[1][0], abLimits[1][1])
+
+ uut.previousStoredAbLimits()
+ checkLimit(uut, mockPlayer, abLimits[0][0], abLimits[0][1])
+
+ uut.previousStoredAbLimits()
+ checkLimit(uut, mockPlayer, abLimits[0][0], abLimits[0][1])
+
+def test_abLimitsWhenChangingSongs(uut, mockPlayer):
+ songs = [
+ "test.flac",
+ "test.mp3"
+ ]
+ abLimits = [
+ [0.2, 0.4],
+ [0.1, 0.3],
+ [0.7, 0.8]
+ ]
+ uut.setAbLimitEnable(True)
+
+ for s in songs:
+ uut.addSong(s)
+
+ uut.setSong(0)
+ for ab in abLimits:
+ uut.storeAbLimits(ab[0], ab[1])
+
+ uut.setSong(1)
+ uut.storeAbLimits(abLimits[0][0], abLimits[0][1])
+
+ uut.setSong(0)
+ uut.loadAbLimits(len(abLimits) - 1)
+ checkLimit(uut, mockPlayer, abLimits[-1][0], abLimits[-1][1])
+
+ uut.setSong(1)
+ checkLimit(uut, mockPlayer, abLimits[-1][0], abLimits[-1][1])
+
+ uut.previousStoredAbLimits()
+ checkLimit(uut, mockPlayer, abLimits[0][0], abLimits[0][1])
+
+def test_loadAndSaveSession(prepared_tmp_path):
+ mockPlayer = MockPlayer()
+ uut = SoloTool(mockPlayer)
+
+ loadedSessionFile = prepared_tmp_path / "test_session.json"
+ savedSessionFile = prepared_tmp_path / "test_session_save.json"
+
+ uut.loadSession(loadedSessionFile)
+ uut.saveSession(savedSessionFile)
+
+ import json
+ with open(loadedSessionFile, "r") as f:
+ loadedSession = json.loads(f.read())
+
+ with open(savedSessionFile, "r") as f:
+ savedSession = json.loads(f.read())
+
+ assert loadedSession == savedSession
+
+def test_addInexistentFile(uut, mockPlayer):
+ song = "not/a/real/file"
+
+ uut.addSong(song)
+ uut.setSong(0)
+
+ assert mockPlayer.currentSong == None
+
+def test_getters(uut, mockPlayer):
+ song = "test.flac"
+ abLimit = [0.2, 0.4]
+
+ uut.addSong(song)
+ uut.setSong(0)
+ uut.storeAbLimits(abLimit[0], abLimit[1])
+
+ assert uut.getSongs() == [song]
+
+ limits = uut.getStoredAbLimits()
+ assert len(limits) == 1
+ assert limits[0][0] == abLimit[0]
+ assert limits[0][1] == abLimit[1]
+
+ mockPlayer.position = 0.8
+ assert uut.getPlaybackPosition() == 0.8
+
+ mockPlayer.volume = 0.8
+ assert uut.getPlaybackVolume() == 0.8
+
+ mockPlayer.rate = 0.5
+ assert uut.getPlaybackRate() == 0.5
+
+def test_setTemporaryLimits(uut, mockPlayer):
+ song = "test.flac"
+ abLimits = [
+ [0.2, 0.4],
+ [0.1, 0.4]
+ ]
+ overflow = 0.5
+
+ uut.setAbLimitEnable(True)
+ mockPlayer.position = overflow
+ uut.addSong(song)
+ uut.setSong(0)
+ uut.storeAbLimits(abLimits[0][0], abLimits[0][1])
+ uut.loadAbLimits(0)
+
+ uut.setAbLimits(abLimits[1][0], abLimits[1][1])
+ uut.tick()
+ assert mockPlayer.position == abLimits[1][0]
+
+def test_jumpToA(uut, mockPlayer):
+ abLimits = (0.2, 0.4)
+ initialPosition = 0.8
+
+ mockPlayer.position = initialPosition
+
+ uut.jumpToA()
+ assert mockPlayer.position == 0.0 # default AB controller A limit
+
+ uut.setAbLimits(abLimits[0], abLimits[1])
+ uut.jumpToA()
+ assert mockPlayer.position == abLimits[0]
+
+def test_playingStateNotification(uut, mockPlayer):
+ song = "test.flac"
+ uut.addSong(song)
+ uut.setSong(0)
+
+ called = False
+ receivedValue = None
+ def callback(value):
+ nonlocal called, receivedValue
+ called = True
+ receivedValue = value
+
+ uut.registerPlayingStateCallback(callback)
+
+ assert mockPlayer.state == MockPlayer.STOPPED
+ assert not called
+
+ uut.play()
+ assert called
+ assert receivedValue == True
+ called = False
+ uut.play()
+ assert not called
+
+ uut.pause()
+ assert called
+ assert receivedValue == False
+ called = False
+ uut.pause()
+ assert not called
+
+ uut.play()
+ assert called
+ assert receivedValue == True
+ called = False
+
+ uut.stop()
+ assert called
+ assert receivedValue == False
+ called = False
+ uut.stop()
+ assert not called
+
+def test_playbackVolumeNotification(uut, mockPlayer):
+ song = "test.flac"
+ uut.addSong(song)
+ uut.setSong(0)
+
+ called = False
+ receivedValue = None
+ def callback(value):
+ nonlocal called, receivedValue
+ called = True
+ receivedValue = value
+
+ uut.registerPlaybackVolumeCallback(callback)
+
+ assert not called
+
+ uut.setPlaybackVolume(0.3)
+ assert called
+ assert receivedValue == 0.3
+ called = False
+
+ uut.setPlaybackVolume(0.3)
+ assert not called
+
+def test_playbackRateNotification(uut, mockPlayer):
+ song = "test.flac"
+ uut.addSong(song)
+ uut.setSong(0)
+
+ called = False
+ receivedValue = None
+ def callback(value):
+ nonlocal called, receivedValue
+ called = True
+ receivedValue = value
+
+ uut.registerPlaybackRateCallback(callback)
+
+ assert not called
+
+ uut.setPlaybackRate(0.5)
+ assert called
+ assert receivedValue == 0.5
+ called = False
+
+ uut.setPlaybackRate(0.5)
+ assert not called
+
+def test_currentSongNotification(uut):
+ called = False
+ receivedValue = None
+ def callback(value):
+ nonlocal called, receivedValue
+ called = True
+ receivedValue = value
+
+ uut.registerCurrentSongCallback(callback)
+ assert not called
+
+ songs = [
+ "test.flac",
+ "test.mp3"
+ ]
+ uut.addSong(songs[0])
+ assert not called
+
+ uut.setSong(0)
+ assert called
+ assert receivedValue == 0
+ called = False
+
+ uut.addSong(songs[1])
+ assert not called
+
+ uut.setSong(0)
+ assert not called
+
+ uut.setSong(1)
+ assert called
+ assert receivedValue == 1
+ called = False
+
+ uut.previousSong()
+ assert called
+ assert receivedValue == 0
+ called = False
+
+ uut.previousSong()
+ assert not called
+
+ uut.nextSong()
+ assert called
+ assert receivedValue == 1
+ called = False
+
+ uut.nextSong()
+ assert not called
+
+def test_currentAbNotification(uut):
+ called = False
+ receivedValue = None
+ def callback(value):
+ nonlocal called, receivedValue
+ called = True
+ receivedValue = value
+
+ uut.registerCurrentAbLimitsCallback(callback)
+ assert not called
+
+ song = "test.flac"
+ uut.addSong(song)
+ uut.setSong(0)
+
+ abLimits = [
+ (0.2, 0.3),
+ (0.4, 0.5)
+ ]
+ uut.storeAbLimits(abLimits[0][0], abLimits[0][1])
+ assert not called
+ uut.storeAbLimits(abLimits[1][0], abLimits[1][1])
+ assert not called
+
+ uut.loadAbLimits(0)
+ assert called
+ assert receivedValue == 0
+ called = False
+
+ uut.loadAbLimits(0)
+ assert not called
+
+ uut.loadAbLimits(1)
+ assert called
+ assert receivedValue == 1
+ called = False
+
+ uut.previousStoredAbLimits()
+ assert called
+ assert receivedValue == 0
+ called = False
+
+ uut.previousStoredAbLimits()
+ assert not called
+
+ uut.nextStoredAbLimits()
+ assert called
+ assert receivedValue == 1
+ called = False
+
+ uut.nextStoredAbLimits()
+ assert not called
+
+def test_abLimitEnabledNotification(uut):
+ called = False
+ receivedValue = None
+ def callback(value):
+ nonlocal called, receivedValue
+ called = True
+ receivedValue = value
+
+ uut.registerAbLimitEnabledCallback(callback)
+ assert not called
+
+ uut.setAbLimitEnable(False)
+ assert not called
+ assert receivedValue is None
+
+ uut.setAbLimitEnable(True)
+ assert called
+ assert receivedValue == True
+ called = False
+ receivedValue = None
+
+ uut.setAbLimitEnable(True)
+ assert not called
+ assert receivedValue is None
+
+ uut.setAbLimitEnable(False)
+ assert called
+ assert receivedValue == False
diff --git a/solo-tool-project/test/test.flac b/solo-tool-project/test/test.flac
new file mode 100644
index 0000000..9164735
--- /dev/null
+++ b/solo-tool-project/test/test.flac
Binary files differ
diff --git a/solo-tool-project/test/test.mp3 b/solo-tool-project/test/test.mp3
new file mode 100644
index 0000000..3c353b7
--- /dev/null
+++ b/solo-tool-project/test/test.mp3
Binary files differ
diff --git a/solo-tool-project/test/test_session.json b/solo-tool-project/test/test_session.json
new file mode 100644
index 0000000..f48b792
--- /dev/null
+++ b/solo-tool-project/test/test_session.json
@@ -0,0 +1,13 @@
+[
+ {
+ "path" : "test.flac",
+ "ab_limits" : null
+ },
+ {
+ "path" : "test.mp3",
+ "ab_limits" : [
+ [0.1, 0.2],
+ [0.3, 0.4]
+ ]
+ }
+]