File: __init__.py

package info (click to toggle)
quodlibet 4.6.0-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 18,016 kB
  • sloc: python: 85,817; sh: 385; xml: 110; makefile: 91
file content (315 lines) | stat: -rw-r--r-- 9,022 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
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
309
310
311
312
313
314
315
# 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.

import os
import sys
import unittest
import tempfile
import shutil
import atexit
import subprocess
import locale

try:
    import pytest
except ImportError:
    raise SystemExit("pytest missing: sudo apt-get install python3-pytest")

try:
    import pyvirtualdisplay
except ImportError:
    pyvirtualdisplay = None

import quodlibet
from quodlibet.util.path import xdg_get_cache_home
from quodlibet import util

from senf import fsnative, path2fsn
from unittest import TestCase as OrigTestCase


class TestCase(OrigTestCase):
    """Adds aliases for equality-type methods.
    Also swaps first and second parameters to support our mostly-favoured
    assertion style e.g. `assertEqual(actual, expected)`"""

    def assertEqual(self, first, second, msg=None):
        super().assertEqual(second, first, msg)

    def assertNotEqual(self, first, second, msg=None):
        super().assertNotEqual(second, first, msg)

    def assertAlmostEqual(self, first, second, places=None, msg=None,
                          delta=None):
        super().assertAlmostEqual(second, first, places, msg, delta)

    def assertNotAlmostEqual(self, first, second, places=None, msg=None,
                             delta=None):
        super().assertNotAlmostEqual(second, first, places, msg, delta)

    # silence deprec warnings about useless renames
    failUnless = OrigTestCase.assertTrue
    failIf = OrigTestCase.assertFalse
    failUnlessRaises = OrigTestCase.assertRaises

    assertEquals = assertEqual
    failUnlessEqual = assertEqual
    failIfEqual = assertNotEqual
    failUnlessAlmostEqual = assertAlmostEqual
    failIfAlmostEqual = assertNotAlmostEqual


skip = unittest.skip
skipUnless = unittest.skipUnless
skipIf = unittest.skipIf


def is_ci():
    """Guesses if this is being run in (Travis, maybe other) CI.
       See https://docs.travis-ci.com/user/environment-variables
    """
    return os.environ.get('CI', "").lower() == 'true'

_DATA_DIR = os.path.join(util.get_module_dir(), "data")
assert isinstance(_DATA_DIR, fsnative)
_TEMP_DIR = None


def get_data_path(filename):
    return os.path.join(_DATA_DIR, path2fsn(filename))


def _wrap_tempfile(func):
    def wrap(*args, **kwargs):
        if kwargs.get("dir") is None and _TEMP_DIR is not None:
            assert isinstance(_TEMP_DIR, fsnative)
            kwargs["dir"] = _TEMP_DIR
        return func(*args, **kwargs)
    return wrap


NamedTemporaryFile = _wrap_tempfile(tempfile.NamedTemporaryFile)


def mkdtemp(*args, **kwargs):
    path = _wrap_tempfile(tempfile.mkdtemp)(*args, **kwargs)
    assert isinstance(path, fsnative)
    return path


def mkstemp(*args, **kwargs):
    fd, filename = _wrap_tempfile(tempfile.mkstemp)(*args, **kwargs)
    assert isinstance(filename, fsnative)
    return (fd, filename)


def init_fake_app():
    from quodlibet import app

    from quodlibet import browsers
    from quodlibet.player.nullbe import NullPlayer
    from quodlibet.library import SongFileLibrary
    from quodlibet.library.librarians import SongLibrarian
    from quodlibet.qltk.quodlibetwindow import QuodLibetWindow, PlayerOptions
    from quodlibet.util.cover import CoverManager

    browsers.init()
    app.name = "Quod Libet"
    app.id = "io.github.quodlibet.QuodLibet"
    app.player = NullPlayer()
    app.library = SongFileLibrary()
    app.library.librarian = SongLibrarian()
    app.cover_manager = CoverManager()
    app.window = QuodLibetWindow(app.library, app.player, headless=True)
    app.player_options = PlayerOptions(app.window)


def destroy_fake_app():
    from quodlibet import app

    app.window.destroy()
    app.library.destroy()
    app.library.librarian.destroy()
    app.player.destroy()

    app.window = app.library = app.player = app.name = app.id = None
    app.cover_manager = None


def dbus_launch_user():
    """Returns a dict with env vars, or an empty dict"""

    try:
        out = subprocess.check_output([
            "dbus-daemon", "--session", "--fork", "--print-address=1",
            "--print-pid=1"])
    except (subprocess.CalledProcessError, OSError):
        return {}
    else:
        out = out.decode("utf-8")
        addr, pid = out.splitlines()
        return {"DBUS_SESSION_BUS_PID": pid, "DBUS_SESSION_BUS_ADDRESS": addr}


def dbus_kill_user(info):
    """Kills the dbus daemon used for testing"""

    if not info:
        return

    try:
        subprocess.check_call(
            ["kill", "-9", info["DBUS_SESSION_BUS_PID"]])
    except (subprocess.CalledProcessError, OSError):
        pass


_BUS_INFO = None
_VDISPLAY = None


def init_test_environ():
    """This needs to be called before any test can be run.

    Before exiting the process call exit_test_environ() to clean up
    any resources created.
    """

    global _TEMP_DIR, _BUS_INFO, _VDISPLAY

    # create a user dir in /tmp and set env vars
    _TEMP_DIR = tempfile.mkdtemp(prefix=fsnative(u"QL-TEST-"))

    # force the old cache dir so that GStreamer can re-use the GstRegistry
    # cache file
    os.environ["XDG_CACHE_HOME"] = xdg_get_cache_home()
    # GStreamer will update the cache if the environment has changed
    # (in Gst.init()). Since it takes 0.5s here and doesn't add much,
    # disable it. If the registry cache is missing it will be created
    # despite this setting.
    os.environ["GST_REGISTRY_UPDATE"] = fsnative(u"no")

    # In flatpak we might get a registry from a different/old flatpak build
    # when testing new versions etc. Better always update in that case.
    if util.is_flatpak():
        del os.environ["GST_REGISTRY_UPDATE"]

    # set HOME and remove all XDG vars that default to it if not set
    home_dir = tempfile.mkdtemp(prefix=fsnative(u"HOME-"), dir=_TEMP_DIR)
    os.environ["HOME"] = home_dir

    # set to new default
    os.environ.pop("XDG_DATA_HOME", None)
    os.environ.pop("XDG_CONFIG_HOME", None)

    # don't use dconf
    os.environ["GSETTINGS_BACKEND"] = "memory"

    # don't use dconf
    os.environ["GSETTINGS_BACKEND"] = "memory"

    # Force the default theme so broken themes don't affect the tests
    os.environ["GTK_THEME"] = "Adwaita"

    if pyvirtualdisplay is not None:
        _VDISPLAY = pyvirtualdisplay.Display()
        _VDISPLAY.start()

    _BUS_INFO = None
    if os.name != "nt" and sys.platform != "darwin":
        _BUS_INFO = dbus_launch_user()
        os.environ.update(_BUS_INFO)

    quodlibet.init(no_translations=True, no_excepthook=True)
    quodlibet.app.name = "QL Tests"

    # try to make things the same in case a different locale is active.
    # LANG for gettext, setlocale for number formatting etc.
    # Note: setlocale has to be called after Gtk.init()
    try:
        if os.name != "nt":
            os.environ["LANG"] = locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
        else:
            os.environ["LANG"] = "en_US.utf8"
            locale.setlocale(locale.LC_ALL, "english")
    except locale.Error:
        pass


def exit_test_environ():
    """Call after init_test_environ() and all tests are finished"""

    global _TEMP_DIR, _BUS_INFO, _VDISPLAY

    try:
        shutil.rmtree(_TEMP_DIR)
    except EnvironmentError:
        pass

    dbus_kill_user(_BUS_INFO)

    if _VDISPLAY is not None:
        _VDISPLAY.stop()
        _VDISPLAY = None


# we have to do this on import so the tests work with other test runners
# like py.test which don't know about out setup code and just import
init_test_environ()
atexit.register(exit_test_environ)


def unit(run=[], suite=None, strict=False, exitfirst=False, network=True,
         quality=True):
    """Returns 0 if everything passed"""

    # make glib warnings fatal
    if strict:
        from gi.repository import GLib
        GLib.log_set_always_fatal(
            GLib.LogLevelFlags.LEVEL_CRITICAL |
            GLib.LogLevelFlags.LEVEL_ERROR |
            GLib.LogLevelFlags.LEVEL_WARNING)

    args = []

    if is_ci():
        args.extend(["-p", "no:cacheprovider"])
        args.extend(["-p", "no:stepwise"])

    if run:
        args.append("-k")
        args.append(" or ".join(run))

    skip_markers = []

    if not quality:
        skip_markers.append("quality")

    if not network:
        skip_markers.append("network")

    if skip_markers:
        args.append("-m")
        args.append(" and ".join(["not %s" % m for m in skip_markers]))

    if exitfirst:
        args.append("-x")

    if suite is None:
        args.append("tests")
    else:
        args.append(os.path.join("tests", suite))

    return pytest.main(args=args)


def run_gtk_loop():
    """Exhausts the GTK main loop of any events"""

    # Import late as various version / init checks fail otherwise
    from gi.repository import Gtk
    while Gtk.events_pending():
        Gtk.main_iteration()