From b0a885fca19249d78d7bc0d8a81e1973a357925f Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sat, 25 Sep 2021 20:30:38 +0200 Subject: [PATCH 1/6] Add skip command --- src/bot.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/bot.py b/src/bot.py index 79dc9c0..1440562 100644 --- a/src/bot.py +++ b/src/bot.py @@ -76,6 +76,13 @@ class Music(commands.Cog): if ctx.voice_client.is_playing(): await ctx.send(f"Queued: {title}") + @commands.command() + async def skip(self, ctx): + async with ctx.typing(): + if not ctx.voice_client.is_playing(): + raise commands.CommandError("Cannot skip: No song playing") + ctx.voice_client.stop() + @commands.command() async def stop(self, ctx): await ctx.voice_client.disconnect() From c6ea351fa5110393f12a5139a5ba13d980b976ce Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sat, 25 Sep 2021 20:30:44 +0200 Subject: [PATCH 2/6] Add debug configuration --- .vscode/launch.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index b6a65fa..7dd7d90 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,13 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "bot", + "type": "python", + "request": "launch", + "program": "src/bot.py", + "console": "integratedTerminal" + }, { "name": "pytest", "type": "python", From 01cc686520a26886320d43d3e1b2b8900fcc89bc Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sat, 25 Sep 2021 20:36:15 +0200 Subject: [PATCH 3/6] Fix AttributeError if no voice client --- src/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot.py b/src/bot.py index 1440562..a21b94d 100644 --- a/src/bot.py +++ b/src/bot.py @@ -79,7 +79,7 @@ class Music(commands.Cog): @commands.command() async def skip(self, ctx): async with ctx.typing(): - if not ctx.voice_client.is_playing(): + if ctx.voice_client is None or not ctx.voice_client.is_playing(): raise commands.CommandError("Cannot skip: No song playing") ctx.voice_client.stop() From 57fbd247da696616957905dd83a0a713d0ac308c Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sat, 25 Sep 2021 20:41:33 +0200 Subject: [PATCH 4/6] Better test assertion --- test/test_bot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_bot.py b/test/test_bot.py index 4595652..b7d4968 100644 --- a/test/test_bot.py +++ b/test/test_bot.py @@ -94,5 +94,5 @@ async def test_bot_playback(mbot, ctx): ctx.voice_client.play.call_args.kwargs["after"](None) # Execute callback for song finish event await asyncio.sleep(0) assert \ - ffmpeg_pcm_audio.call_args.args == (url,), \ - f"FFmpegPCMAudio was not called with {url}" + ctx.voice_client.play.call_args.args == (time_of_dying_audio,), \ + "Did not queue next song" From 645e6556a436a31c6194d4766ecc4429bdbc3d7d Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sat, 25 Sep 2021 20:58:09 +0200 Subject: [PATCH 5/6] Add skip test --- test/conftest.py | 4 +++- test/test_bot.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index 65744e7..6902468 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -29,7 +29,9 @@ def mbot(bot): @pytest.fixture def ctx(mocker: MockerFixture): - yield mocker.patch('discord.ext.commands.Context', autospec=True) + ctx_mock = mocker.patch('discord.ext.commands.Context', autospec=True) + ctx_mock.voice_client.stop = lambda: ctx_mock.voice_client.play.call_args.kwargs["after"](None) + return ctx_mock @pytest.fixture diff --git a/test/test_bot.py b/test/test_bot.py index b7d4968..7152ed2 100644 --- a/test/test_bot.py +++ b/test/test_bot.py @@ -96,3 +96,51 @@ async def test_bot_playback(mbot, ctx): assert \ ctx.voice_client.play.call_args.args == (time_of_dying_audio,), \ "Did not queue next song" + + +@pytest.mark.asyncio +async def test_bot_skip(mbot, ctx): + with patch.object(mbot, '_ytdl') as ytdl: + with patch('discord.FFmpegPCMAudio') as ffmpeg_pcm_audio: + + ctx.voice_client.is_playing.return_value = False + url = "https://www.youtube.com/watch?v=Wr9LZ1hAFpQ" + title = "In Flames - Deliver Us (Official Video)" + mock_ytdl_extract_info(ytdl, url, title) + deliver_us_audio = mock_ffmpeg_pcm_audio(ffmpeg_pcm_audio) + query = 'in flames deliver us' + # pylint: disable=too-many-function-args + await mbot.play(mbot, ctx, query=query) + await asyncio.sleep(0) + assert \ + ctx.voice_client.play.call_args.args == (deliver_us_audio,), \ + "Did not playback correct audio" + + ctx.voice_client.is_playing.return_value = True + url = "https://www.youtube.com/watch?v=pMDcYX2wRSg" + title = "Three Days Grace - Time of Dying (lyrics)" + mock_ytdl_extract_info(ytdl, url, title) + time_of_dying_audio = mock_ffmpeg_pcm_audio(ffmpeg_pcm_audio) + # pylint: disable=too-many-function-args + query = "three days grace time of dying" + await mbot.play(mbot, ctx, query=query) + await asyncio.sleep(0) + assert \ + not ctx.voice_client.play.call_args.args == (time_of_dying_audio,), \ + "Did immediately playback audio instead of being queued" + + await mbot.skip(mbot, ctx) + await asyncio.sleep(0) + assert \ + ctx.voice_client.play.call_args.args == (time_of_dying_audio,), \ + "Did not skip song" + + # TEST: Error if no song playing + ctx.voice_client.is_playing.return_value = False + with pytest.raises(commands.CommandError): + await mbot.skip(mbot, ctx) + + # TEST: Error if no voice client + ctx.voice_client = None + with pytest.raises(commands.CommandError): + await mbot.skip(mbot, ctx) From a2a84d5e33b5096423a187b9a17b42353bc8c7de Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sat, 25 Sep 2021 20:59:02 +0200 Subject: [PATCH 6/6] Add comments --- test/test_bot.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/test/test_bot.py b/test/test_bot.py index 7152ed2..20d9225 100644 --- a/test/test_bot.py +++ b/test/test_bot.py @@ -7,14 +7,14 @@ from discord.ext import commands @pytest.mark.asyncio async def test_bot_ensure_voice(mbot, ctx): - # Connects to voice channel of author if possible + # TEST: Connects to voice channel of author if possible ctx.voice_client = None ctx.author.voice = AsyncMock() await mbot.ensure_voice(ctx) assert ctx.author.voice.channel.connect.call_count == 1, "Did not connect to voice channel of author" ctx.reset_mock(return_value=True) - # Error if author not inside a channel + # TEST: Error if author not inside a channel ctx.voice_client = None ctx.author.voice = None with pytest.raises(commands.CommandError): @@ -35,6 +35,8 @@ def mock_ffmpeg_pcm_audio(ffmpeg_pcm_audio): async def test_bot_playback(mbot, ctx): with patch.object(mbot, '_ytdl') as ytdl: with patch('discord.FFmpegPCMAudio') as ffmpeg_pcm_audio: + + # TEST: First song queued is immediately played ctx.voice_client.is_playing.return_value = False url = "https://www.youtube.com/watch?v=Wr9LZ1hAFpQ" title = "In Flames - Deliver Us (Official Video)" @@ -66,6 +68,7 @@ async def test_bot_playback(mbot, ctx): ctx.send.call_args.args == (f"Now playing: {title}",), \ "Did not send 'Now playing:' message" + # TEST: Following songs are put inside a queue ctx.voice_client.is_playing.return_value = True url = "https://www.youtube.com/watch?v=pMDcYX2wRSg" title = "Three Days Grace - Time of Dying (lyrics)" @@ -87,11 +90,12 @@ async def test_bot_playback(mbot, ctx): ctx.send.call_args.args == (f"Queued: {title}",), \ "Did not send 'Queued:' message" await asyncio.sleep(0) - # Still no playback because previous song not finished + # Assert that there is still no playback because previous song is not finished yet assert \ not ffmpeg_pcm_audio.call_args.args == (url,), \ f"FFmpegPCMAudio was called with {url} before previous song finished" - ctx.voice_client.play.call_args.kwargs["after"](None) # Execute callback for song finish event + # Execute callback for song finish event + ctx.voice_client.play.call_args.kwargs["after"](None) await asyncio.sleep(0) assert \ ctx.voice_client.play.call_args.args == (time_of_dying_audio,), \ @@ -103,6 +107,9 @@ async def test_bot_skip(mbot, ctx): with patch.object(mbot, '_ytdl') as ytdl: with patch('discord.FFmpegPCMAudio') as ffmpeg_pcm_audio: + # TEST: One can skip songs to immediately play the next song in queue + + # Queue first song ctx.voice_client.is_playing.return_value = False url = "https://www.youtube.com/watch?v=Wr9LZ1hAFpQ" title = "In Flames - Deliver Us (Official Video)" @@ -116,6 +123,7 @@ async def test_bot_skip(mbot, ctx): ctx.voice_client.play.call_args.args == (deliver_us_audio,), \ "Did not playback correct audio" + # Queue second song ctx.voice_client.is_playing.return_value = True url = "https://www.youtube.com/watch?v=pMDcYX2wRSg" title = "Three Days Grace - Time of Dying (lyrics)" @@ -129,6 +137,7 @@ async def test_bot_skip(mbot, ctx): not ctx.voice_client.play.call_args.args == (time_of_dying_audio,), \ "Did immediately playback audio instead of being queued" + # Now skip first song await mbot.skip(mbot, ctx) await asyncio.sleep(0) assert \