File: test_daemon.py

package info (click to toggle)
playerctl 2.4.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 704 kB
  • sloc: ansic: 5,157; python: 1,107; xml: 198; sh: 133; makefile: 62
file content (308 lines) | stat: -rw-r--r-- 10,607 bytes parent folder | download | duplicates (3)
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
import pytest

import os
from .mpris import setup_mpris
from .playerctl import PlayerctlCli
from dbus_next.aio import MessageBus
from dbus_next import Message, MessageType

import asyncio
from asyncio import Queue
from subprocess import run as run_process


async def start_playerctld(bus_address, debug=False):
    pkill = await asyncio.create_subprocess_shell('pkill playerctld')
    await pkill.wait()
    env = os.environ.copy()
    env['DBUS_SESSION_BUS_ADDRESS'] = bus_address
    env['G_MESSAGES_DEBUG'] = 'playerctl'
    proc = await asyncio.create_subprocess_shell(
        'playerctld',
        env=env,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.STDOUT)

    async def printer(stream):
        while True:
            line = await stream.readline()
            print(line)
            if not line:
                break

    if debug:
        asyncio.get_event_loop().create_task(printer(proc.stdout))

    return proc


async def get_playerctld(bus):
    path = '/com/github/altdesktop/playerctld'
    interface = 'com.github.altdesktop.playerctld'
    introspection = await bus.introspect('org.mpris.MediaPlayer2.playerctld',
                                         path)
    obj = bus.get_proxy_object(interface, path, introspection)
    return obj.get_interface('org.freedesktop.DBus.Properties')


@pytest.mark.asyncio
async def test_daemon_commands(bus_address):
    playerctl = PlayerctlCli(bus_address)

    async def run(cmd):
        return await playerctl.run('-p playerctld ' + cmd)

    # with no other players running, these should error because there's no
    # active player (not no players found). This tests activation and property
    # errors as well.
    results = await asyncio.gather(*(run(cmd)
                                     for cmd in ('play', 'pause', 'play-pause',
                                                 'stop', 'next', 'previous',
                                                 'position', 'volume',
                                                 'status', 'metadata', 'loop',
                                                 'shuffle')))
    for result in results:
        assert result.returncode == 1
        assert 'No player could handle this command' in result.stderr.splitlines(
        )

    # restart playerctld so we can manage the process and see debug info
    playerctld_proc = await start_playerctld(bus_address)

    [mpris1, mpris2, mpris3] = await setup_mpris('daemon1',
                                                 'daemon2',
                                                 'daemon3',
                                                 bus_address=bus_address)
    await mpris2.set_artist_title('artist', 'title')
    cmd = await run('play')
    assert cmd.returncode == 0, cmd.stdout
    assert mpris2.play_called, cmd.stdout
    mpris2.reset()

    await mpris1.set_artist_title('artist', 'title')
    cmd = await run('play')
    assert cmd.returncode == 0, cmd.stderr
    assert mpris1.play_called
    mpris1.reset()

    await mpris3.set_artist_title('artist', 'title')
    cmd = await run('play')
    assert cmd.returncode == 0, cmd.stderr
    assert mpris3.play_called
    mpris3.reset()

    await mpris3.disconnect()
    cmd = await run('play')
    assert cmd.returncode == 0, cmd.stderr
    assert mpris1.play_called
    mpris1.reset()

    await asyncio.gather(mpris1.disconnect(), mpris2.disconnect())

    playerctld_proc.terminate()
    await playerctld_proc.wait()


@pytest.mark.asyncio
async def test_daemon_follow(bus_address):
    playerctld_proc = await start_playerctld(bus_address)

    [mpris1, mpris2] = await setup_mpris('player1',
                                         'player2',
                                         bus_address=bus_address)
    playerctl = PlayerctlCli(bus_address)
    pctl_cmd = '--player playerctld metadata --format "{{playerInstance}}: {{artist}} - {{title}}" --follow'
    proc = await playerctl.start(pctl_cmd)

    await mpris1.set_artist_title('artist1', 'title1')
    line = await proc.queue.get()
    assert line == 'playerctld: artist1 - title1', proc.queue

    await mpris2.set_artist_title('artist2', 'title2')
    line = await proc.queue.get()
    assert line == 'playerctld: artist2 - title2', proc.queue

    [mpris3] = await setup_mpris('player3', bus_address=bus_address)
    await mpris3.set_artist_title('artist3', 'title3')
    line = await proc.queue.get()

    if line == '':
        # the line might be blank here because of the test setup
        line = await proc.queue.get()

    assert line == 'playerctld: artist3 - title3', proc.queue

    await mpris1.set_artist_title('artist4', 'title4')
    line = await proc.queue.get()
    assert line == 'playerctld: artist4 - title4', proc.queue

    await mpris1.set_artist_title('artist5', 'title5')
    line = await proc.queue.get()
    assert line == 'playerctld: artist5 - title5', proc.queue

    await mpris1.disconnect()
    line = await proc.queue.get()
    assert line == 'playerctld: artist3 - title3', proc.queue

    await asyncio.gather(mpris2.disconnect(), mpris3.disconnect())

    playerctld_proc.terminate()
    proc.proc.terminate()
    await proc.proc.wait()
    await playerctld_proc.wait()


async def playerctld_shift(bus_address, reverse=False):
    env = os.environ.copy()
    env['DBUS_SESSION_BUS_ADDRESS'] = bus_address
    env['G_MESSAGES_DEBUG'] = 'playerctl'
    cmd = 'playerctld unshift' if reverse else 'playerctld shift'
    shift = await asyncio.create_subprocess_shell(
        cmd,
        env=env,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.STDOUT)
    return await shift.wait()


@pytest.mark.asyncio
async def test_daemon_shift_simple(bus_address):
    playerctld_proc = await start_playerctld(bus_address)

    mprises = await setup_mpris('player1',
                                'player2',
                                'player3',
                                bus_address=bus_address)
    [mpris1, mpris2, mpris3] = mprises

    playerctl = PlayerctlCli(bus_address)
    pctl_cmd = '--player playerctld metadata --format "{{playerInstance}}: {{artist}} - {{title}}" --follow'
    proc = await playerctl.start(pctl_cmd)

    await mpris1.set_artist_title('artist1', 'title1')
    line = await proc.queue.get()
    assert line == 'playerctld: artist1 - title1', proc.queue

    await mpris2.set_artist_title('artist2', 'title2')
    line = await proc.queue.get()
    assert line == 'playerctld: artist2 - title2', proc.queue

    await mpris3.set_artist_title('artist3', 'title3')
    line = await proc.queue.get()
    assert line == 'playerctld: artist3 - title3', proc.queue

    code = await playerctld_shift(bus_address)
    assert code == 0
    line = await proc.queue.get()
    assert line == 'playerctld: artist2 - title2', proc.queue

    code = await playerctld_shift(bus_address)
    assert code == 0
    line = await proc.queue.get()
    assert line == 'playerctld: artist1 - title1', proc.queue

    code = await playerctld_shift(bus_address, reverse=True)
    assert code == 0
    line = await proc.queue.get()
    assert line == 'playerctld: artist2 - title2', proc.queue

    code = await playerctld_shift(bus_address, reverse=True)
    assert code == 0
    line = await proc.queue.get()
    assert line == 'playerctld: artist3 - title3', proc.queue

    playerctld_proc.terminate()
    proc.proc.terminate()
    await asyncio.gather(mpris1.disconnect(), mpris2.disconnect(),
                         playerctld_proc.wait(), proc.proc.wait())


@pytest.mark.asyncio
async def test_daemon_shift_no_player(bus_address):
    playerctld_proc = await start_playerctld(bus_address)

    playerctl = PlayerctlCli(bus_address)
    pctl_cmd = '--player playerctld metadata --format "{{playerInstance}}: {{artist}} - {{title}}" --follow'
    proc = await playerctl.start(pctl_cmd)

    code = await playerctld_shift(bus_address)
    assert code == 1

    [mpris1] = await setup_mpris('player1', bus_address=bus_address)
    code = await playerctld_shift(bus_address)
    assert code == 0

    await mpris1.disconnect()
    code = await playerctld_shift(bus_address)
    assert code == 1

    code = await playerctld_shift(bus_address, reverse=True)
    assert code == 1

    [mpris1] = await setup_mpris('player1', bus_address=bus_address)
    code = await playerctld_shift(bus_address, reverse=True)
    assert code == 0

    await mpris1.disconnect()
    code = await playerctld_shift(bus_address, reverse=True)
    assert code == 1

    playerctld_proc.terminate()
    await playerctld_proc.wait()


@pytest.mark.asyncio
async def test_active_player_change(bus_address):
    queue = Queue()
    playerctld_proc = await start_playerctld(bus_address)

    bus = await MessageBus(bus_address=bus_address).connect()

    reply = await bus.call(
        Message(destination='org.freedesktop.DBus',
                interface='org.freedesktop.DBus',
                path='/org/freedesktop/DBus',
                member='AddMatch',
                signature='s',
                body=["sender='org.mpris.MediaPlayer2.playerctld'"]))

    assert reply.message_type == MessageType.METHOD_RETURN, reply.body

    def message_handler(message):
        if message.member == 'PropertiesChanged' and message.body[
                0] == 'com.github.altdesktop.playerctld' and 'PlayerNames' in message.body[
                    1]:
            queue.put_nowait(message.body[1]['PlayerNames'].value)

    def player_list(*args):
        return [f'org.mpris.MediaPlayer2.{name}' for name in args]

    bus.add_message_handler(message_handler)

    [mpris1] = await setup_mpris('player1', bus_address=bus_address)

    assert player_list('player1') == await queue.get()

    [mpris2] = await setup_mpris('player2', bus_address=bus_address)

    assert player_list('player2', 'player1') == await queue.get()

    # changing artist/title should bump the player up
    await mpris1.set_artist_title('artist1', 'title1', '/1')

    assert player_list('player1', 'player2') == await queue.get()

    # if properties are not actually different, it shouldn't update
    await mpris2.set_artist_title('artist2', 'title2', '/2')
    assert player_list('player2', 'player1') == await queue.get()

    await mpris1.set_artist_title('artist1', 'title1', '/1')
    await mpris1.ping()
    assert queue.empty()

    bus.disconnect()
    await asyncio.gather(mpris1.disconnect(), mpris2.disconnect(),
                         bus.wait_for_disconnect())

    playerctld_proc.terminate()
    await playerctld_proc.wait()