File: test_plugins_playlist.py

package info (click to toggle)
quodlibet 4.7.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 19,228 kB
  • sloc: python: 89,728; sh: 381; xml: 110; makefile: 91
file content (195 lines) | stat: -rw-r--r-- 6,760 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

"""TODO: Share better with, i.e. test MenuItemPlugin directly"""

import os
import shutil

from gi.repository import Gtk
from quodlibet.browsers import Browser

from quodlibet.library import SongLibrary
from quodlibet.plugins.playlist import PlaylistPlugin, PlaylistPluginHandler
from quodlibet.util.collection import Playlist
from tests import TestCase, mkstemp, mkdtemp
from quodlibet.plugins import PluginManager, Plugin
from tests.helper import capture_output

MAX_PLAYLISTS = 50
TEST_PLAYLIST = Playlist("foo")


def generate_playlists(n):
    return [Playlist("Playlist %d" % x) for x in range(n)]


class TPlaylistPlugins(TestCase):
    class MockBrowser(Browser):
        def __init__(self):
            super().__init__()
            self.activated = False

        def activate(self):
            self.activated = True

        def get_toplevel(self):
            return self

        def is_toplevel(self):
            return True

    def _confirmer(self, *args):
        self.confirmed = True

    def setUp(self):
        self.tempdir = mkdtemp()
        self.pm = PluginManager(folders=[self.tempdir])
        self.confirmed = False
        self.mock_browser = self.MockBrowser()
        self.handler = PlaylistPluginHandler(self._confirmer)
        self.pm.register_handler(self.handler)
        self.pm.rescan()
        self.assertEqual(self.pm.plugins, [])
        self.library = SongLibrary("foo")

    def tearDown(self):
        self.library.destroy()
        self.pm.quit()
        shutil.rmtree(self.tempdir)

    def create_plugin(self, id="", name="", desc="", icon="", funcs=None, mod=False):
        fd, fn = mkstemp(suffix=".py", text=True, dir=self.tempdir)
        file = os.fdopen(fd, "w")

        if mod:
            indent = ""
        else:
            file.write("from quodlibet.plugins.playlist import PlaylistPlugin\n")
            file.write(f"class {name}(PlaylistPlugin):\n")
            indent = "    "
            file.write(f"{indent}pass\n")

        if name:
            file.write(f"{indent}PLUGIN_ID = {name!r}\n")
        if name:
            file.write(f"{indent}PLUGIN_NAME = {name!r}\n")
        if desc:
            file.write(f"{indent}PLUGIN_DESC = {desc!r}\n")
        if icon:
            file.write(f"{indent}PLUGIN_ICON = {icon!r}\n")
        for f in funcs or []:
            if f in ["__init__"]:
                file.write(
                    f"{indent}def {f}(self, *args): super().__init__("
                    '*args); raise Exception("as expected.")\n'
                )
            else:
                file.write(f"{indent}def {f}(*args): return args\n")
        file.flush()
        file.close()

    def test_empty_has_no_plugins(self):
        self.pm.rescan()
        self.assertEqual(self.pm.plugins, [])

    def test_name_and_desc_plus_func_is_one(self):
        self.create_plugin(name="Name", desc="Desc", funcs=["plugin_playlist"])
        self.pm.rescan()
        self.assertEqual(len(self.pm.plugins), 1)

    def test_additional_functions_still_only_one(self):
        self.create_plugin(
            name="Name", desc="Desc", funcs=["plugin_playlist", "plugin_playlists"]
        )
        self.pm.rescan()
        self.assertEqual(len(self.pm.plugins), 1)

    def test_two_plugins_are_two(self):
        self.create_plugin(name="Name", desc="Desc", funcs=["plugin_playlist"])
        self.create_plugin(name="Name2", desc="Desc2", funcs=["plugin_albums"])
        self.pm.rescan()
        self.assertEqual(len(self.pm.plugins), 2)

    def test_disables_plugin(self):
        self.create_plugin(name="Name", desc="Desc", funcs=["plugin_playlist"])
        self.pm.rescan()
        assert not self.pm.enabled(self.pm.plugins[0])

    def test_enabledisable_plugin(self):
        self.create_plugin(name="Name", desc="Desc", funcs=["plugin_playlist"])
        self.pm.rescan()
        plug = self.pm.plugins[0]
        self.pm.enable(plug, True)
        assert self.pm.enabled(plug)
        self.pm.enable(plug, False)
        assert not self.pm.enabled(plug)

    def test_ignores_broken_plugin(self):
        self.create_plugin(
            name="Broken", desc="Desc", funcs=["__init__", "plugin_playlist"]
        )

        self.pm.rescan()
        plug = self.pm.plugins[0]
        self.pm.enable(plug, True)
        menu = Gtk.Menu()
        with capture_output():
            self.handler.populate_menu(menu, None, self.mock_browser, [TEST_PLAYLIST])
        self.assertEqual(
            len(menu.get_children()), 0, msg="Shouldn't have enabled a broken plugin"
        )

    def test_populate_menu(self):
        plugin = Plugin(FakePlaylistPlugin)
        self.handler.plugin_enable(plugin)
        menu = Gtk.Menu()
        self.handler.populate_menu(menu, None, self.mock_browser, [TEST_PLAYLIST])
        # Don't forget the separator
        num = len(menu.get_children()) - 1
        self.assertEqual(num, 1, msg="Need 1 plugin not %d" % num)

    def test_handling_playlists_without_confirmation(self):
        plugin = Plugin(FakePlaylistPlugin)
        self.handler.plugin_enable(plugin)
        playlists = generate_playlists(MAX_PLAYLISTS)
        self.handler.handle(plugin.id, self.library, self.mock_browser, playlists)
        self.assertTrue("Didn't execute plugin", FakePlaylistPlugin.total > 0)
        self.assertFalse(
            self.confirmed,
            ("Wasn't expecting a confirmation for %d invocations" % len(playlists)),
        )

    def test_handling_lots_of_songs_with_confirmation(self):
        plugin = Plugin(FakePlaylistPlugin)
        self.handler.plugin_enable(plugin)
        playlists = generate_playlists(MAX_PLAYLISTS + 1)
        self.handler.handle(plugin.id, self.library, self.mock_browser, playlists)
        self.assertTrue(
            self.confirmed,
            (
                "Should have confirmed %d invocations (Max=%d)."
                % (len(playlists), MAX_PLAYLISTS)
            ),
        )


class FakePlaylistPlugin(PlaylistPlugin):
    PLUGIN_NAME = "Fake Playlist Plugin"
    PLUGIN_ID = "PlaylistMunger"
    MAX_INVOCATIONS = MAX_PLAYLISTS
    total = 0

    def __init__(self, playlists, library):
        super().__init__(playlists, library)
        self.total = 0

    def plugin_playlist(self, _):
        self.total += 1
        if self.total > self.MAX_INVOCATIONS:
            raise ValueError(
                "Shouldn't have called me on this many songs"
                " (%d > %d)" % (self.total, self.MAX_INVOCATIONS)
            )