-
Notifications
You must be signed in to change notification settings - Fork 58
Add testing #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Add testing #88
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| autopep8 | ||
| python-dbusmock | ||
| jeepney | ||
| lyriq | ||
| pycodestyle | ||
| pylint | ||
| pytest | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added 2 packages -- not sure why I added them in these specific lines, weird. But anyway, |
||
| setuptools | ||
| twine | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -135,27 +135,28 @@ def add_arguments(): | |
|
|
||
| def get_arguments(): | ||
| return [ | ||
| ("--version", "shows version number"), | ||
| ("--status", "shows song name and artist"), | ||
| ("--version", "shows version number", False), | ||
| ("--status", "shows song name and artist", False), | ||
| ( | ||
| "--statusposition", | ||
| "shows song name and artist, with current playback position" | ||
| "shows song name and artist, with current playback position", | ||
| False | ||
| ), | ||
| ("--statusshort", "shows status in a short way"), | ||
| ("--song", "shows the song name"), | ||
| ("--songshort", "shows the song name in a short way"), | ||
| ("--artist", "shows artist name"), | ||
| ("--artistshort", "shows artist name in a short way"), | ||
| ("--album", "shows album name"), | ||
| ("--position", "shows song position"), | ||
| ("--arturl", "shows album image url"), | ||
| ("--playbackstatus", "shows playback status"), | ||
| ("--play", "plays the song"), | ||
| ("--pause", "pauses the song"), | ||
| ("--playpause", "plays or pauses the song (toggles a state)"), | ||
| ("--lyrics", "shows the lyrics for the song"), | ||
| ("--next", "plays the next song"), | ||
| ("--prev", "plays the previous song"), | ||
| ("--statusshort", "shows status in a short way", False), | ||
| ("--song", "shows the song name", False), | ||
| ("--songshort", "shows the song name in a short way", False), | ||
| ("--artist", "shows artist name", False), | ||
| ("--artistshort", "shows artist name in a short way", False), | ||
| ("--album", "shows album name", False), | ||
| ("--position", "shows song position", False), | ||
| ("--arturl", "shows album image url", False), | ||
| ("--playbackstatus", "shows playback status", False), | ||
| ("--play", "plays the song", False), | ||
| ("--pause", "pauses the song", False), | ||
| ("--playpause", "plays or pauses the song (toggles a state)", False), | ||
| ("--lyrics", "shows the lyrics for the song", False), | ||
| ("--next", "plays the next song", False), | ||
| ("--prev", "plays the previous song", False), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these are still needed, right? I can't quite tell where they went. But if I'm wrong and you removed them intentionally, let me know and I'll be happy to update it! |
||
| ("--songuri", "plays the track at the provided Uri", True), | ||
| ("--listuri", "plays the playlist at the provided Uri", True), | ||
| ] | ||
|
|
@@ -167,7 +168,9 @@ def show_version(): | |
|
|
||
| def get_song(): | ||
| metadata = get_spotify_property("Metadata") | ||
| artist = ", ".join(metadata['xesam:artist'][1]) | ||
| artist = metadata['xesam:artist'][1] | ||
| if isinstance(artist, list): | ||
| artist = ", ".join(artist) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, so this change is an artifact of dbus message mocking. I'll share more detail in a following comment here, but as it is right now, the |
||
| title = metadata['xesam:title'][1] | ||
| return artist, title | ||
|
|
||
|
|
@@ -192,9 +195,9 @@ def show_status_position(): | |
| artist, title = get_song() | ||
|
|
||
| # Both values are in microseconds | ||
| position = datetime.timedelta(milliseconds=position_raw / 1000) | ||
| position = datetime.timedelta(milliseconds=int(position_raw) / 1000) | ||
| length = datetime.timedelta( | ||
| milliseconds=metadata['mpris:length'][1] / 1000 | ||
| milliseconds=int(metadata['mpris:length'][1]) / 1000 | ||
| ) | ||
|
|
||
| p_hours, p_minutes, p_seconds = convert_timedelta(position) | ||
|
|
@@ -350,9 +353,9 @@ def show_position(): | |
| metadata = get_spotify_property("Metadata") | ||
| position_raw = get_spotify_property("Position") | ||
| # Both values are in microseconds | ||
| position = datetime.timedelta(milliseconds=position_raw / 1000) | ||
| position = datetime.timedelta(milliseconds=int(position_raw) / 1000) | ||
| length = datetime.timedelta( | ||
| milliseconds=metadata['mpris:length'][1] / 1000 | ||
| milliseconds=int(metadata['mpris:length'][1]) / 1000 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And these |
||
| ) | ||
|
|
||
| p_hours, p_minutes, p_seconds = convert_timedelta(position) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| if args[1] == 'Metadata': | ||
| ret = { | ||
| 'xesam:artist': ['', 'The Beatles'], | ||
| 'xesam:title': ['', 'Yesterday'], | ||
| 'xesam:album': ['', 'Help!'], | ||
| 'mpris:length': ['', '10000000'], | ||
| 'mpris:artUrl': ['', 'https://github.com/pwittchen/spotify-cli-linux'], | ||
| } | ||
| elif args[1] == 'PlaybackStatus': | ||
| ret = 'Playing' | ||
| elif args[1] == 'Position': | ||
| ret = 1000000 | ||
| else: | ||
| ret = args | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. Here's the meat of the testing work: building out a pretend dbus response based on the lookup argument. What is
|
||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,163 @@ | ||||
| from io import StringIO | ||||
| from pathlib import Path | ||||
| from subprocess import PIPE | ||||
| from unittest import skip | ||||
| from unittest.mock import patch | ||||
|
|
||||
| import dbus | ||||
| import dbusmock | ||||
|
|
||||
| from spotifycli.spotifycli import main | ||||
| from spotifycli.version import __version__ | ||||
|
|
||||
|
|
||||
| class TestSpotifyCLI(dbusmock.DBusTestCase): | ||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we are creating a new unit test class that inherits from the dbusmock test case for convenient access to things like |
||||
| @classmethod | ||||
| def setUpClass(cls): | ||||
| cls.start_session_bus() | ||||
| cls.dbus_con = cls.get_dbus(system_bus=False) | ||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Each time we start running unit tests, this |
||||
|
|
||||
| def setUp(self): | ||||
| self.dbus_spotify_server_mock = self.spawn_server( | ||||
| "org.mpris.MediaPlayer2.spotify", | ||||
| "/org/mpris/MediaPlayer2", | ||||
| "org.freedesktop.DBus.Properties", | ||||
| system_bus=False, | ||||
| stdout=PIPE | ||||
| ) | ||||
| self.dbus_spotify_interface_mock = dbus.Interface( | ||||
| self.dbus_con.get_object( | ||||
| 'org.mpris.MediaPlayer2.spotify', '/org/mpris/MediaPlayer2' | ||||
| ), | ||||
| dbusmock.MOCK_IFACE | ||||
| ) | ||||
| metadata_file = Path(__file__).resolve().parent / 'dbus_metadata_response.py' | ||||
| self.dbus_spotify_interface_mock.AddMethod( | ||||
| '', 'Get', 'ss', 'v', | ||||
| metadata_file.read_text(), | ||||
| ) | ||||
| # TODO: Mock up the controls bus/interface, since I think it is slightly different | ||||
| # self.dbus_spotify_interface_mock.AddMethod( | ||||
| # '', 'Play', '', '', '' | ||||
| # ) | ||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For each unit test run, we spin up the actual spotify dbus session server and build up an interface where we can stand up mock methods to be callable. We then use What are the other args in AddMethod?The second argument is the name of the method to add. The third is the argument type being passed into the dbus call. In this case, it is two strings, as found in this line: spotify-cli-linux/spotifycli/spotifycli.py Line 293 in 43143c2
These arguments are marshaled as strings ( The fourth argument is a |
||||
|
|
||||
| def tearDown(self): | ||||
| self.dbus_spotify_server_mock.stdout.close() | ||||
| self.dbus_spotify_server_mock.terminate() | ||||
| self.dbus_spotify_server_mock.wait() | ||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After each unit test, clean up the instance data. |
||||
|
|
||||
| def test_cli_usage(self): | ||||
| with patch('sys.argv', ["spotifycli", "--help"]), patch("sys.exit") as mock_exit, patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| self.assertIn("usage", buffer.getvalue()) | ||||
| mock_exit.assert_called_with(0) | ||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, here's a whole list of individual unit tests that test each of the different command line options. I'll describe it here once in general:
|
||||
|
|
||||
| def test_cli_version(self): | ||||
| with patch('sys.argv', ["spotifycli", "--version"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| self.assertIn(__version__, buffer.getvalue()) | ||||
|
|
||||
| def test_cli_status(self): | ||||
| with patch('sys.argv', ["spotifycli", "--status"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| self.assertIn("The Beatles - Yesterday", buffer.getvalue()) | ||||
|
|
||||
| def test_cli_status_position(self): | ||||
| with patch('sys.argv', ["spotifycli", "--statusposition"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| output = buffer.getvalue() | ||||
| self.assertIn('00:01', output) | ||||
| self.assertIn('00:10', output) | ||||
|
|
||||
| def test_cli_status_short(self): | ||||
| with patch('sys.argv', ["spotifycli", "--statusshort"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| output = buffer.getvalue() | ||||
| self.assertIn('Beatles', output) | ||||
| self.assertIn('Yesterday', output) | ||||
|
|
||||
| def test_cli_song(self): | ||||
| with patch('sys.argv', ["spotifycli", "--song"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| self.assertIn("Yesterday", buffer.getvalue()) | ||||
|
|
||||
| def test_cli_song_short(self): | ||||
| # TODO: Change to a long song title to check the trimming | ||||
| with patch('sys.argv', ["spotifycli", "--songshort"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| self.assertIn("Yesterday", buffer.getvalue()) | ||||
|
|
||||
| def test_cli_artist(self): | ||||
| with patch('sys.argv', ["spotifycli", "--artist"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| self.assertIn("Beatles", buffer.getvalue()) | ||||
|
|
||||
| def test_cli_artist_short(self): | ||||
| # TODO: Change to a long artist name to check the trimming | ||||
| with patch('sys.argv', ["spotifycli", "--artistshort"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| self.assertIn("Beatles", buffer.getvalue()) | ||||
|
|
||||
| def test_cli_album(self): | ||||
| with patch('sys.argv', ["spotifycli", "--album"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| self.assertIn("Help!", buffer.getvalue()) | ||||
|
|
||||
| def test_cli_position(self): | ||||
| with patch('sys.argv', ["spotifycli", "--position"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| output = buffer.getvalue() | ||||
| self.assertIn('00:01', output) | ||||
| self.assertIn('00:10', output) | ||||
|
|
||||
| def test_cli_art_url(self): | ||||
| with patch('sys.argv', ["spotifycli", "--arturl"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| self.assertIn('http', buffer.getvalue()) | ||||
|
|
||||
| def test_cli_playback_status(self): | ||||
| with patch('sys.argv', ["spotifycli", "--playbackstatus"]), patch('sys.stdout', new_callable=StringIO) as buffer: | ||||
| main() | ||||
| playback_symbols = ['▶', '▮▮', '■'] | ||||
| output = buffer.getvalue() | ||||
| self.assertTrue(any([x in output for x in playback_symbols])) | ||||
|
|
||||
| @skip('Lyrics dont seem to be working right now...is it still valid?') | ||||
| def test_cli_lyrics(self): | ||||
| with patch('sys.argv', ["spotifycli", "--lyrics"]): | ||||
| main() | ||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The following tests are decorated with For this one, I'm not convinced the lyrics database is still available -- maybe I'm missing something. But if not, that command line switch should probably just be hidden away for now. |
||||
|
|
||||
| @skip('TODO: Need to mock up the spotify_action dbus interface') | ||||
| def test_cli_play(self): | ||||
| with patch('sys.argv', ["spotifycli", "--play"]): | ||||
| main() | ||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rest of these are |
||||
|
|
||||
| @skip('TODO: Need to mock up the spotify_action dbus interface') | ||||
| def test_cli_pause(self): | ||||
| with patch('sys.argv', ["spotifycli", "--pause"]): | ||||
| main() | ||||
|
|
||||
| @skip('TODO: Need to mock up the spotify_action dbus interface') | ||||
| def test_cli_play_pause(self): | ||||
| with patch('sys.argv', ["spotifycli", "--playpause"]): | ||||
| main() | ||||
|
|
||||
| @skip('TODO: Need to mock up the spotify_action dbus interface') | ||||
| def test_cli_next(self): | ||||
| with patch('sys.argv', ["spotifycli", "--next"]): | ||||
| main() | ||||
|
|
||||
| @skip('TODO: Need to mock up the spotify_action dbus interface') | ||||
| def test_cli_prev(self): | ||||
| with patch('sys.argv', ["spotifycli", "--prev"]): | ||||
| main() | ||||
|
|
||||
| @skip('TODO: Need to mock up the spotify_action dbus interface') | ||||
| def test_cli_songuri(self): | ||||
| with patch('sys.argv', ["spotifycli", "--songuri"]): | ||||
| main() | ||||
|
|
||||
| @skip('TODO: Need to mock up the spotify_action dbus interface') | ||||
| def test_cli_listuri(self): | ||||
| with patch('sys.argv', ["spotifycli", "--listuri"]): | ||||
| main() | ||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a new follow-up command to run
pytest, which will find all the tests inside the package.