1# This program is free software; you can redistribute it and/or modify
2# it under the terms of the GNU General Public License as published by
3# the Free Software Foundation; either version 2 of the License, or
4# (at your option) any later version.
5
6import uuid
7import tempfile
8import os
9import sys
10import threading
11import traceback
12import time
13import logging
14
15from senf import getcwd, fsnative, fsn2bytes, bytes2fsn, mkdtemp, environ
16
17from quodlibet import _
18from quodlibet.config import HardCodedRatingsPrefs, DurationFormat
19from quodlibet import config
20from quodlibet import util
21from quodlibet.util.dprint import print_exc, format_exception, extract_tb, \
22    PrintHandler
23from quodlibet.util import format_time_long as f_t_l, format_time_preferred, \
24    format_time_display, format_time_seconds
25from quodlibet.util import re_escape
26from quodlibet.util.library import set_scan_dirs, get_scan_dirs
27from quodlibet.util.path import fsn2glib, glib2fsn, \
28    parse_xdg_user_dirs, xdg_get_system_data_dirs, escape_filename, \
29    strip_win32_incompat_from_path, xdg_get_cache_home, \
30    xdg_get_data_home, unexpand, expanduser, xdg_get_user_dirs, \
31    xdg_get_config_home, get_temp_cover_file, mkdir, mtime
32from quodlibet.util.string import decode, encode, split_escape, join_escape
33
34from . import TestCase, skipIf
35from .helper import capture_output, locale_numeric_conv
36
37
38is_win = os.name == "nt"
39
40
41class Tmkdir(TestCase):
42    def test_exists(self):
43        mkdir(".")
44
45    def test_notdirectory(self):
46        self.failUnlessRaises(OSError, mkdir, __file__)
47
48    def test_manydeep(self):
49        self.failUnless(not os.path.isdir("nonext"))
50        t = mkdtemp()
51        path = os.path.join(t, "nonext", "test", "test2", "test3")
52        mkdir(path)
53        try:
54            self.failUnless(os.path.isdir(path))
55        finally:
56            os.rmdir(path)
57            path = os.path.dirname(path)
58            os.rmdir(path)
59            path = os.path.dirname(path)
60            os.rmdir(path)
61            path = os.path.dirname(path)
62            os.rmdir(path)
63            os.rmdir(t)
64
65
66class Tgetcwd(TestCase):
67
68    def test_Tgetcwd(self):
69        self.assertTrue(isinstance(getcwd(), fsnative))
70
71
72class Tmtime(TestCase):
73    def test_equal(self):
74        self.failUnlessEqual(mtime("."), os.path.getmtime("."))
75
76    def test_bad(self):
77        self.failIf(os.path.exists("/dev/doesnotexist"))
78        self.failUnlessEqual(mtime("/dev/doesnotexist"), 0)
79
80
81class Tformat_locale(TestCase):
82
83    def test_format_int_locale(self):
84        assert isinstance(util.format_int_locale(1024), str)
85
86    def test_format_float_locale(self):
87        assert isinstance(util.format_float_locale(1024.1024), str)
88
89    def test_format_time_seconds(self):
90        assert isinstance(util.format_time_seconds(1024), str)
91
92        with locale_numeric_conv():
93            assert format_time_seconds(1024) == "1,024 seconds"
94            assert format_time_seconds(1) == "1 second"
95
96
97class Tunexpand(TestCase):
98    d = expanduser("~")
99    u = unexpand(d)
100
101    def test_base(self):
102        path = unexpand(self.d)
103        if is_win:
104            self.failUnlessEqual(path, "%USERPROFILE%")
105        else:
106            self.failUnlessEqual(path, "~")
107
108    def test_only_profile_case(self):
109        assert isinstance(unexpand(expanduser(fsnative(u"~"))), fsnative)
110
111    def test_base_trailing(self):
112        path = unexpand(self.d + os.path.sep)
113        self.failUnlessEqual(path, self.u + os.path.sep)
114
115    def test_noprefix(self):
116        path = unexpand(self.d + "foobar" + os.path.sep)
117        self.failUnlessEqual(path, self.d + "foobar" + os.path.sep)
118
119    def test_subfile(self):
120        path = unexpand(os.path.join(self.d, "la", "la"))
121        self.failUnlessEqual(path, os.path.join(self.u, "la", "la"))
122
123
124class Tformat_rating(TestCase):
125    def setUp(self):
126        self.r = config.RATINGS = HardCodedRatingsPrefs()
127
128    def test_empty(self):
129        self.failUnlessEqual(util.format_rating(0, blank=False), "")
130
131    def test_full(self):
132        self.failUnlessEqual(
133            len(util.format_rating(1, blank=False)),
134            int(1 / self.r.precision))
135
136    def test_rating_length(self):
137        config.RATINGS.number = 4
138        for i in range(0, int(1 / self.r.precision + 1)):
139            self.failUnlessEqual(
140                i, len(util.format_rating(i * self.r.precision, blank=False)))
141
142    def test_bogus(self):
143        max_length = int(1 / self.r.precision)
144        self.failUnlessEqual(len(util.format_rating(2 ** 32 - 1, blank=False)),
145                             max_length)
146        self.failUnlessEqual(len(util.format_rating(-4.2, blank=False)), 0)
147
148    def test_blank_lengths(self):
149        """Check that there are no unsuspected edge-cases
150        for various rating precisions"""
151        for self.r.number in [1, 5, 4, 3, 2]:
152            steps = self.r.number
153            self.failUnlessEqual(len(util.format_rating(1)), steps)
154            self.failUnlessEqual(len(util.format_rating(0)), steps)
155            self.failUnlessEqual(len(util.format_rating(0.5)), steps)
156            self.failUnlessEqual(len(util.format_rating(1 / 3.0)), steps)
157
158    def test_blank_values(self):
159        self.r.number = 5
160        self.r.blank_symbol = "0"
161        self.r.full_symbol = "1"
162        # Easy ones first
163        self.failUnlessEqual(util.format_rating(0.0), "00000")
164        self.failUnlessEqual(util.format_rating(0.2), "10000")
165        self.failUnlessEqual(util.format_rating(0.8), "11110")
166        self.failUnlessEqual(util.format_rating(1.0), "11111")
167        # A bit arbitrary, but standard behaviour
168        self.failUnlessEqual(util.format_rating(0.51), "11100")
169        # Test rounding down...
170        self.failUnlessEqual(util.format_rating(0.6), "11100")
171        # Test rounding up...
172        self.failUnlessEqual(util.format_rating(0.91), "11111")
173        # You never know...
174        self.failUnlessEqual(util.format_rating(3.0), "11111")
175        self.failUnlessEqual(util.format_rating(-0.5), "00000")
176
177
178class Tpango(TestCase):
179    def test_escape_empty(self):
180        self.failUnlessEqual(util.escape(""), "")
181
182    def test_roundtrip(self):
183        for s in ["foo&amp;", "<&>", "&", "&amp;", "<&testing&amp;>amp;"]:
184            esc = util.escape(s)
185            self.failIfEqual(s, esc)
186            self.failUnlessEqual(s, util.unescape(esc))
187
188    def test_unescape_empty(self):
189        self.failUnlessEqual(util.unescape(""), "")
190
191    def test_format(self):
192        self.assertEqual(util.bold("foo"), "<b>foo</b>")
193        self.assertEqual(util.italic("foo"), "<i>foo</i>")
194        self.assertEqual(util.monospace("foo"), "<tt>foo</tt>")
195
196
197class Tre_esc(TestCase):
198    def test_empty(self):
199        self.failUnlessEqual(re_escape(b""), b"")
200        self.assertTrue(isinstance(re_escape(b""), bytes))
201
202    def test_empty_unicode(self):
203        self.failUnlessEqual(re_escape(u""), u"")
204        self.assertTrue(isinstance(re_escape(u""), str))
205
206    def test_safe(self):
207        self.failUnlessEqual(re_escape("fo o"), "fo o")
208
209    def test_unsafe(self):
210        self.failUnlessEqual(re_escape("!bar"), r"\!bar")
211
212    def test_many_unsafe(self):
213        self.failUnlessEqual(
214            re_escape("*quux#argh?woo"), r"\*quux\#argh\?woo")
215
216
217class Tdecode(TestCase):
218    def test_empty(self):
219        self.failUnlessEqual(decode(b""), "")
220
221    def test_safe(self):
222        self.failUnlessEqual(decode(b"foo!"), "foo!")
223
224    def test_invalid(self):
225        self.failUnlessEqual(
226            decode(b"fo\xde"), u'fo\ufffd [Invalid Encoding]')
227
228
229class Tencode(TestCase):
230    def test_empty(self):
231        self.failUnlessEqual(encode(""), b"")
232
233    def test_unicode(self):
234        self.failUnlessEqual(encode(u"abcde"), b"abcde")
235
236
237class Tcapitalize(TestCase):
238    def test_empty(self):
239        self.failUnlessEqual(util.capitalize(""), "")
240
241    def test_firstword(self):
242        self.failUnlessEqual(util.capitalize("aa b"), "Aa b")
243
244    def test_preserve(self):
245        self.failUnlessEqual(util.capitalize("aa B"), "Aa B")
246
247    def test_nonalphabet(self):
248        self.failUnlessEqual(util.capitalize("!aa B"), "!aa B")
249
250
251class Thuman_sort(TestCase):
252    def smaller(self, x, y):
253        return util.human_sort_key(x) < util.human_sort_key(y)
254
255    def equal(self, x, y):
256        return util.human_sort_key(x) == util.human_sort_key(y)
257
258    def test_human(self):
259        self.failUnlessEqual(self.smaller(u"2", u"15"), True)
260        self.failUnlessEqual(self.smaller(u" 2", u"15 "), True)
261        self.failUnlessEqual(self.smaller(u"a2 g", u"a 2z"), True)
262        self.failUnlessEqual(self.smaller(u"a2zz", u"a2.1z"), True)
263
264        self.failUnlessEqual(self.smaller(u"42o", u"42\xf6"), True)
265        self.failUnlessEqual(self.smaller(u"42\xf6", u"42p"), True)
266
267        self.failUnlessEqual(self.smaller(u"bbb", u"zzz3"), True)
268
269        self.assertTrue(self.equal(" foo", "foo"))
270        self.assertTrue(self.equal(" ", ""))
271        self.assertTrue(self.smaller("", "."))
272        self.assertTrue(self.smaller("a", "b"))
273        self.assertTrue(self.smaller("A", "b"))
274
275    def test_false(self):
276        # album browser needs that to sort albums without artist/title
277        # to the bottom
278        self.failIf(util.human_sort_key(""))
279
280    def test_white(self):
281        self.failUnlessEqual(
282            util.human_sort_key(u"  3foo    bar6 42.8"),
283            util.human_sort_key(u"3 foo bar6  42.8  "))
284        self.failUnless(64.0 in util.human_sort_key(u"64. 8"))
285
286
287class Tformat_time(TestCase):
288    def test_seconds(self):
289        self.failUnlessEqual(util.format_time(0), "0:00")
290        self.failUnlessEqual(util.format_time(59), "0:59")
291
292    def test_minutes(self):
293        self.failUnlessEqual(util.format_time(60), "1:00")
294        self.failUnlessEqual(util.format_time(60 * 59 + 59), "59:59")
295
296    def test_hourss(self):
297        self.failUnlessEqual(util.format_time(60 * 60), "1:00:00")
298        self.failUnlessEqual(
299            util.format_time(60 * 60 + 60 * 59 + 59), "1:59:59")
300
301    def test_negative(self):
302        self.failUnlessEqual(util.format_time(-124), "-2:04")
303
304
305class Tparse_time(TestCase):
306    def test_invalid(self):
307        self.failUnlessEqual(util.parse_time("not a time"), 0)
308
309    def test_except(self):
310        self.failUnlessRaises(ValueError, util.parse_time, "not a time", None)
311
312    def test_empty(self):
313        self.failUnlessEqual(util.parse_time(""), 0)
314
315    def test_roundtrip(self):
316        # The values are the ones tested for Tformat_time, so we know they
317        # will be formatted correctly. They're also representative of
318        # all the major patterns.
319        for i in [0, 59, 60, 60 * 59 + 59, 60 * 60, 60 * 60 + 60 * 59 + 59]:
320            self.failUnlessEqual(util.parse_time(util.format_time(i)), i)
321
322    def test_negative(self):
323        self.failUnlessEqual(util.parse_time("-2:04"), -124)
324
325
326class Tparse_date(TestCase):
327
328    def test_invalid(self):
329        self.assertRaises(ValueError, util.parse_date, "not a date")
330        self.assertRaises(ValueError, util.parse_date, "0")
331        self.assertRaises(ValueError, util.parse_date, "2000-13")
332        self.assertRaises(ValueError, util.parse_date, "2000-01-32")
333        self.assertRaises(ValueError, util.parse_date, "2000-01-0")
334        self.assertRaises(ValueError, util.parse_date, "2000-0-01")
335
336    def test_valid(self):
337        ref = time.mktime(time.strptime("2004", "%Y"))
338        self.assertEqual(util.parse_date("2004"), ref)
339        self.assertEqual(util.parse_date("2004-01-01"), ref)
340        self.assertEqual(util.parse_date("2004-1-1"), ref)
341        self.assertTrue(
342            util.parse_date("2004-01-01") < util.parse_date("2004-01-02"))
343
344
345class Tdate_key(TestCase):
346
347    def test_compare(self):
348        date_key = util.date_key
349        self.assertTrue(date_key("2004") == date_key("2004-01-01"))
350        self.assertTrue(date_key("2004") == date_key("2004-01"))
351        self.assertTrue(date_key("2004") < date_key("2004-01-02"))
352        self.assertTrue(date_key("2099-02-02") < date_key("2099-03-30"))
353
354        self.assertTrue(date_key("2004-01-foo") == date_key("2004-01"))
355
356    def test_validate(self):
357        validate = util.validate_query_date
358
359        for valid in ["2004", "2005-01", "3000-3-4"]:
360            self.assertTrue(validate(valid))
361
362        for invalid in ["", "-", "3000-", "9-0", "8-1-0", "1-13-1", "1-1-32",
363                        "1-1-1-1-1", "a", "1-a", "1-1-a"]:
364            self.assertFalse(validate(invalid))
365
366
367class Tformat_size(TestCase):
368    def t_dict(self, d):
369        for key, value in d.items():
370            formatted = util.format_size(key)
371            self.failUnlessEqual(formatted, value)
372            assert isinstance(formatted, str)
373
374    def test_bytes(self):
375        self.t_dict({0: "0 B", 1: "1 B", 1023: "1023 B"})
376
377    def test_kbytes(self):
378        self.t_dict({
379            1024: "1.00 KB",
380            1536: "1.50 KB",
381            10240: "10 KB",
382            15360: "15 KB"
383        })
384
385    def test_mbytes(self):
386        self.t_dict({
387            1024 * 1024: "1.00 MB",
388            1024 * 1536: "1.50 MB",
389            1024 * 10240: "10.0 MB",
390            1024 * 15360: "15.0 MB",
391            123456 * 1024: "121 MB",
392            765432 * 1024: "747 MB"})
393
394    def test_gbytes(self):
395        self.t_dict({
396            1024 * 1024 * 1024: "1.0 GB",
397            1024 * 1024 * 1536: "1.5 GB",
398            1024 * 1024 * 10240: "10.0 GB",
399            1024 * 1024 * 15360: "15.0 GB"
400        })
401
402
403class Ttag(TestCase):
404    def test_empty(self):
405        self.failUnlessEqual(util.tag(""), "Invalid tag")
406
407    def test_basic(self):
408        self.failUnlessEqual(util.tag("title"), "Title")
409
410    def test_basic_nocap(self):
411        self.failUnlessEqual(util.tag("title", False), "title")
412
413    def test_internal(self):
414        self.failUnlessEqual(util.tag("~year"), "Year")
415
416    def test_numeric(self):
417        self.failUnlessEqual(util.tag("~#year"), "Year")
418
419    def test_two(self):
420        self.failUnlessEqual(util.tag("title~version"), "Title / Version")
421
422    def test_two_nocap(self):
423        self.failUnlessEqual(
424            util.tag("title~version", False), "title / version")
425
426    def test_precap_handling(self):
427        self.failUnlessEqual(util.tag("labelid"), "Label ID")
428        self.failUnlessEqual(util.tag("labelid", False), "label ID")
429
430
431class Ttagsplit(TestCase):
432
433    def test_single_tag(self):
434        self.failUnlessEqual(util.tagsplit("foo"), ["foo"])
435
436    def test_synth_tag(self):
437        self.failUnlessEqual(util.tagsplit("~foo"), ["~foo"])
438
439    def test_two_tags(self):
440        self.failUnlessEqual(util.tagsplit("foo~bar"), ["foo", "bar"])
441
442    def test_two_prefix(self):
443        self.failUnlessEqual(util.tagsplit("~foo~bar"), ["foo", "bar"])
444
445    def test_synth(self):
446        self.failUnlessEqual(util.tagsplit("~foo~~bar"), ["foo", "~bar"])
447
448    def test_numeric(self):
449        self.failUnlessEqual(util.tagsplit("~#bar"), ["~#bar"])
450
451    def test_two_numeric(self):
452        self.failUnlessEqual(util.tagsplit("~#foo~~#bar"), ["~#foo", "~#bar"])
453
454    def test_two_synth_start(self):
455        self.failUnlessEqual(
456            util.tagsplit("~~people~album"), ["~people", "album"])
457
458
459class Tpattern(TestCase):
460
461    def test_empty(self):
462        self.failUnlessEqual(util.pattern(""), "")
463
464    def test_basic(self):
465        self.failUnlessEqual(util.pattern("<title>"), "Title")
466
467    def test_basic_nocap(self):
468        self.failUnlessEqual(util.pattern("<title>", False), "title")
469
470    def test_internal(self):
471        self.failUnlessEqual(util.pattern("<~plays>"), "Plays")
472
473    def test_tied(self):
474        self.failUnlessEqual(util.pattern("<~title~album>"), "Title - Album")
475
476    def test_unknown(self):
477        self.failUnlessEqual(util.pattern("<foobarbaz>"), "Foobarbaz")
478
479    def test_condition(self):
480        self.failUnlessEqual(util.pattern("<~year|<~year> - <album>|<album>>"),
481                             "Year - Album")
482
483    def test_escape(self):
484        self.failUnlessEqual(util.pattern(r"\<i\><&>\</i\>", esc=True),
485                            "<i>&amp;</i>")
486
487    def test_invalid(self):
488        self.failUnlessEqual(util.pattern("<date"), "")
489        util.pattern("<d\\")
490
491    def test_complex_condition(self):
492        self.assertEqual(util.pattern(r"<#(bitrate \> 150)|HQ|LQ>"), "LQ")
493
494    def test_escape_condition(self):
495        self.assertEqual(
496            util.pattern(r"<~filename=/\/adsad\/sadads/|BLA|BLU>"), "BLU")
497
498
499class Tformat_time_long(TestCase):
500
501    def test_second(s):
502        s.assertEquals(f_t_l(1).split(", ")[0], _("1 second"))
503
504    def test_seconds(s):
505        s.assertEquals(f_t_l(2).split(", ")[0], _("%d seconds") % 2)
506
507    def test_notminutes(s):
508        s.assertEquals(f_t_l(59).split(", ")[0], _("%d seconds") % 59)
509
510    def test_minute(s):
511        s.assertEquals(f_t_l(60), _("1 minute"))
512
513    def test_minutes(s):
514        s.assertEquals(f_t_l(120).split(", ")[0], _("%d minutes") % 2)
515
516    def test_nothours(s):
517        s.assertEquals(f_t_l(3599).split(", ")[0], _("%d minutes") % 59)
518
519    def test_hour(s):
520        s.assertEquals(f_t_l(3600), _("1 hour"))
521
522    def test_hours(s):
523        s.assertEquals(f_t_l(7200), _("%d hours") % 2)
524
525    def test_notdays(s):
526        s.assertEquals(f_t_l(86399).split(", ")[0], _("%d hours") % 23)
527
528    def test_seconds_dropped(s):
529        s.assertEquals(len(f_t_l(3601).split(", ")), 2)
530
531    def test_day(s):
532        s.assertEquals(f_t_l(86400), _("1 day"))
533
534    def test_days(s):
535        s.assertEquals(f_t_l(172800).split(", ")[0], _("%d days") % 2)
536
537    def test_notyears(s):
538        s.assertEquals(f_t_l(31535999).split(", ")[0], _("%d days") % 364)
539
540    def test_year(s):
541        s.assertEquals(f_t_l(31536000), _("1 year"))
542
543    def test_years(s):
544        s.assertEquals(f_t_l(63072000).split(", ")[0], _("%d years") % 2)
545
546    def test_drop_zero(s):
547        s.assertEquals(f_t_l(3601), ", ".join([_("1 hour"), _("1 second")]))
548
549    def test_limit_zero(s):
550        s.assertEquals(f_t_l(1, limit=0), _("1 second"))
551
552    def test_limit(s):
553        s.assertEquals(len(f_t_l(2 ** 31).split(", ")), 2)
554
555
556class TFormatTimePreferred(TestCase):
557
558    def test_default_setting_is_standard(s):
559        s.assertEquals(config.DURATION.format, DurationFormat.STANDARD)
560
561    def test_raw_config_is_standard(s):
562        s.assertEquals(config.get('display', 'duration_format'),
563                       DurationFormat.STANDARD)
564
565    def test_acts_like_long(s):
566        s._fuzz_loop(format_time_preferred, f_t_l)
567
568    def _fuzz_loop(s, f, f2):
569        x = 1
570        while x < 100000000:
571            s.assertEquals(f(x), f2(x))
572            x = x * 3 / 2 + 1
573
574    def test_acts_like_display(s):
575        def fmt_numeric(x):
576            return format_time_preferred(x, DurationFormat.NUMERIC)
577        s._fuzz_loop(fmt_numeric, format_time_display)
578
579    def test_seconds(s):
580        def fmt_seconds(x):
581            return format_time_preferred(x, DurationFormat.SECONDS)
582        s._fuzz_loop(fmt_seconds, format_time_seconds)
583
584
585class Tspawn(TestCase):
586
587    def test_simple(self):
588        if is_win:
589            return
590        self.failUnless(util.spawn(["ls", "."], stdout=True))
591
592    def test_invalid(self):
593        from gi.repository import GLib
594        self.failUnlessRaises(GLib.GError, util.spawn, ["not a command"])
595
596    def test_get_output(self):
597        if is_win:
598            return
599        fileobj = util.spawn(["echo", "'$1'", '"$2"', ">3"], stdout=True)
600        self.failUnlessEqual(fileobj.read().split(), ["'$1'", '"$2"', ">3"])
601
602
603class Txdg_dirs(TestCase):
604
605    def test_system_data_dirs_posix(self):
606        if is_win:
607            return
608
609        os.environ["XDG_DATA_DIRS"] = "/xyz"
610        self.failUnlessEqual(xdg_get_system_data_dirs()[0], "/xyz")
611        del os.environ["XDG_DATA_DIRS"]
612        dirs = xdg_get_system_data_dirs()
613        self.failUnlessEqual(dirs[0], "/usr/local/share/")
614        self.failUnlessEqual(dirs[1], "/usr/share/")
615
616    def test_data_home(self):
617        if is_win:
618            return
619
620        os.environ["XDG_DATA_HOME"] = "/xyz"
621        self.failUnlessEqual(xdg_get_data_home(), "/xyz")
622        del os.environ["XDG_DATA_HOME"]
623        should = os.path.join(os.path.expanduser("~"), ".local", "share")
624        self.failUnlessEqual(xdg_get_data_home(), should)
625
626    def test_get_user_dirs(self):
627        xdg_get_user_dirs()
628
629    def test_parse_xdg_user_dirs(self):
630        data = b'# foo\nBLA="$HOME/blah"\n'
631        vars_ = parse_xdg_user_dirs(data)
632        self.assertTrue(b"BLA" in vars_)
633        expected = os.path.join(environ.get("HOME", ""), "blah")
634        self.assertEqual(vars_[b"BLA"], expected)
635
636        vars_ = parse_xdg_user_dirs(b'BLA="$HOME/"')
637        self.assertTrue(b"BLA" in vars_)
638        self.assertEqual(vars_[b"BLA"], environ.get("HOME", ""))
639
640        # some invalid
641        self.assertFalse(parse_xdg_user_dirs(b"foo"))
642        self.assertFalse(parse_xdg_user_dirs(b"foo=foo bar"))
643        self.assertFalse(parse_xdg_user_dirs(b"foo='foo"))
644
645    def test_on_windows(self):
646        self.assertTrue(xdg_get_system_data_dirs())
647        self.assertTrue(xdg_get_cache_home())
648        self.assertTrue(xdg_get_data_home())
649        self.assertTrue(xdg_get_config_home())
650
651
652class Tlibrary(TestCase):
653    def setUp(self):
654        config.init()
655
656    def tearDown(self):
657        config.quit()
658
659    def test_basic(self):
660        self.failIf(get_scan_dirs())
661        if os.name == "nt":
662            set_scan_dirs([u"C:\\foo", u"D:\\bar", u""])
663            self.failUnlessEqual(get_scan_dirs(), [u"C:\\foo", u"D:\\bar"])
664        else:
665            set_scan_dirs(["foo", "bar", ""])
666            self.failUnlessEqual(get_scan_dirs(), ["foo", "bar"])
667
668
669class TNormalizePath(TestCase):
670
671    def test_default(self):
672        from quodlibet.util.path import normalize_path as norm
673
674        name = norm(tempfile.mkdtemp())
675        try:
676            self.failUnlessEqual(norm(name), name)
677            self.failUnlessEqual(norm(os.path.join(name, "foo", "..")), name)
678        finally:
679            os.rmdir(name)
680
681    def test_types(self):
682        from quodlibet.util.path import normalize_path
683
684        assert isinstance(normalize_path(fsnative(u"foo"), False), fsnative)
685        assert isinstance(normalize_path("foo", False), fsnative)
686        assert isinstance(normalize_path(fsnative(u"foo"), True), fsnative)
687        assert isinstance(normalize_path("foo", True), fsnative)
688
689    def test_canonicalise(self):
690        from quodlibet.util.path import normalize_path as norm
691
692        f, path = tempfile.mkstemp()
693        path = os.path.realpath(path)  # on osx tmp is a symlink
694        os.close(f)
695        path = norm(path)
696
697        link_dir = mkdtemp()
698        link = None
699        if not is_win:
700            link = os.path.join(link_dir, str(uuid.uuid4()))
701            os.symlink(path, link)
702
703        try:
704            self.failUnlessEqual(norm(path, canonicalise=True), path)
705            self.failUnlessEqual(norm(os.path.join(path, "foo", ".."), True),
706                                 path)
707            if link:
708                self.failUnlessEqual(norm(link, True), path)
709                # A symlink shouldn't be resolved unless asked for
710                self.failIfEqual(norm(link, False), path)
711                # And the other behaviour should also work
712                unnormalised_path = os.path.join(link, "foo", "..")
713                self.failUnlessEqual(norm(unnormalised_path, True), path)
714        finally:
715            if link:
716                os.remove(link)
717            os.remove(path)
718            os.rmdir(link_dir)
719
720
721class Tescape_filename(TestCase):
722
723    def test_str(self):
724        result = escape_filename("\x00\x01")
725        self.assertEqual(result, "%00%01")
726        self.assertTrue(isinstance(result, fsnative))
727
728    def test_unicode(self):
729        result = escape_filename(u'abc\xe4')
730        self.assertEqual(result, "abc%C3%A4")
731        self.assertTrue(isinstance(result, fsnative))
732
733
734@skipIf(is_win, "not on Windows")
735class Tload_library(TestCase):
736
737    def test_libc(self):
738        lib, name = util.load_library(["c"])
739        self.assertEqual(name, "c")
740
741        lib2, name = util.load_library(["c"])
742        self.assertTrue(lib is lib2)
743
744        lib3, name = util.load_library(["c"], shared=False)
745        self.assertTrue(lib2 is not lib3)
746
747    def test_glib(self):
748        if sys.platform == "darwin":
749            fn = "libglib-2.0.0.dylib"
750        else:
751            fn = "libglib-2.0.so.0"
752        lib, name = util.load_library([fn])
753        self.assertEqual(name, fn)
754        self.assertTrue(lib)
755
756
757class Tstrip_win32_incompat_from_path(TestCase):
758
759    def test_types(self):
760        v = strip_win32_incompat_from_path(fsnative(u""))
761        self.assertTrue(isinstance(v, fsnative))
762        v = strip_win32_incompat_from_path(fsnative(u"foo"))
763        self.assertTrue(isinstance(v, fsnative))
764
765        v = strip_win32_incompat_from_path(u"")
766        self.assertTrue(isinstance(v, str))
767        v = strip_win32_incompat_from_path(u"foo")
768        self.assertTrue(isinstance(v, str))
769
770    def test_basic(self):
771        if is_win:
772            v = strip_win32_incompat_from_path(u"C:\\foo\\<>/a")
773            self.assertEqual(v, u"C:\\foo\\___a")
774        else:
775            v = strip_win32_incompat_from_path("/foo/<>a")
776            self.assertEqual(v, "/foo/__a")
777
778
779class TPathHandling(TestCase):
780
781    def test_main(self):
782        v = fsnative(u"foo")
783        self.assertTrue(isinstance(v, fsnative))
784
785        v2 = glib2fsn(fsn2glib(v))
786        self.assertTrue(isinstance(v2, fsnative))
787        self.assertEqual(v, v2)
788
789        v3 = bytes2fsn(fsn2bytes(v, "utf-8"), "utf-8")
790        self.assertTrue(isinstance(v3, fsnative))
791        self.assertEqual(v, v3)
792
793
794class Tget_temp_cover_file(TestCase):
795
796    def test_main(self):
797        fobj = get_temp_cover_file(b"foobar")
798        try:
799            self.assertTrue(isinstance(fobj.name, fsnative))
800        finally:
801            fobj.close()
802
803
804class Tsplit_escape(TestCase):
805
806    def test_split_escape(self):
807        # from mutagen
808
809        inout = [
810            (("", ":"), [""]),
811            ((":", ":"), ["", ""]),
812            ((":", ":", 0), [":"]),
813            ((":b:c:", ":", 0), [":b:c:"]),
814            ((":b:c:", ":", 1), ["", "b:c:"]),
815            ((":b:c:", ":", 2), ["", "b", "c:"]),
816            ((":b:c:", ":", 3), ["", "b", "c", ""]),
817            (("a\\:b:c", ":"), ["a:b", "c"]),
818            (("a\\\\:b:c", ":"), ["a\\", "b", "c"]),
819            (("a\\\\\\:b:c\\:", ":"), ["a\\:b", "c:"]),
820            (("\\", ":"), [""]),
821            (("\\\\", ":"), ["\\"]),
822            (("\\\\a\\b", ":"), ["\\a\\b"]),
823        ]
824
825        for inargs, out in inout:
826            self.assertEqual(split_escape(*inargs), out)
827
828    def test_types(self):
829        parts = split_escape(b"\xff:\xff", b":")
830        self.assertEqual(parts, [b"\xff", b"\xff"])
831        self.assertTrue(isinstance(parts[0], bytes))
832
833        parts = split_escape(u"a:b", u":")
834        self.assertEqual(parts, [u"a", u"b"])
835        self.assertTrue(all(isinstance(p, str) for p in parts))
836
837        parts = split_escape(u"", u":")
838        self.assertEqual(parts, [u""])
839        self.assertTrue(all(isinstance(p, str) for p in parts))
840
841        parts = split_escape(u":", u":")
842        self.assertEqual(parts, [u"", u""])
843        self.assertTrue(all(isinstance(p, str) for p in parts))
844
845    def test_join_escape_types(self):
846        self.assertEqual(join_escape([], b":"), b"")
847        self.assertTrue(isinstance(join_escape([], b":"), bytes))
848        self.assertTrue(isinstance(join_escape([], u":"), str))
849        self.assertEqual(join_escape([b"\xff", b"\xff"], b":"), b"\xff:\xff")
850        self.assertEqual(join_escape([u'\xe4', u'\xe4'], ":"), u'\xe4:\xe4')
851
852    def test_join_escape(self):
853        self.assertEqual(join_escape([b":"], b":"), b"\\:")
854        self.assertEqual(join_escape([b"\\:", b":"], b":"), b"\\\\\\::\\:")
855
856    def test_roundtrip(self):
857        values = [b"\\:", b":"]
858        joined = join_escape(values, b":")
859        self.assertEqual(split_escape(joined, b":"), values)
860
861
862class TMainRunner(TestCase):
863
864    def test_abort_before_call(self):
865        runner = util.MainRunner()
866
867        def worker():
868            self.assertRaises(
869                util.MainRunnerAbortedError, runner.call, lambda: None)
870
871        thread = threading.Thread(target=worker)
872        runner.abort()
873        thread.start()
874        thread.join()
875
876    def test_timeout(self):
877        runner = util.MainRunner()
878
879        def worker():
880            self.assertRaises(
881                util.MainRunnerTimeoutError, runner.call, lambda: None,
882                timeout=0.00001)
883
884        for i in range(3):
885            thread = threading.Thread(target=worker)
886            thread.start()
887            thread.join()
888        runner.abort()
889
890    def test_call_exception(self):
891        from gi.repository import GLib
892
893        runner = util.MainRunner()
894        loop = GLib.MainLoop()
895
896        def func():
897            raise KeyError
898
899        def worker():
900            try:
901                self.assertRaises(util.MainRunnerError, runner.call, func)
902            finally:
903                loop.quit()
904
905        thread = threading.Thread(target=worker)
906        thread.start()
907        loop.run()
908        runner.abort()
909        thread.join()
910
911    def test_from_main_loop(self):
912        from gi.repository import GLib
913
914        runner = util.MainRunner()
915        loop = GLib.MainLoop()
916
917        def in_main_loop():
918            try:
919                self.assertRaises(
920                    util.MainRunnerError, runner.call, lambda: None, foo=0)
921                self.assertEqual(
922                    runner.call(lambda i: i + 1, 42, priority=0), 43)
923                self.assertEqual(runner.call(lambda i: i - 1, 42), 41)
924            finally:
925                loop.quit()
926
927        GLib.idle_add(in_main_loop)
928        loop.run()
929
930    def test_ok(self):
931        from gi.repository import GLib
932
933        runner = util.MainRunner()
934        loop = GLib.MainLoop()
935
936        def func(i):
937            self.assertTrue(util.is_main_thread())
938            return i + 1
939
940        def worker():
941            try:
942                self.assertEqual(runner.call(func, 42), 43)
943            finally:
944                loop.quit()
945
946        thread = threading.Thread(target=worker)
947        thread.start()
948
949        loop.run()
950        thread.join()
951        runner.abort()
952
953    def test_multi_abort(self):
954        runner = util.MainRunner()
955        runner.abort()
956        runner.abort()
957
958        def worker():
959            self.assertRaises(util.MainRunnerError, runner.call, lambda: None)
960
961        thread = threading.Thread(target=worker)
962        thread.start()
963        thread.join()
964
965
966class Tconnect_destroy(TestCase):
967
968    def test_main(self):
969        from gi.repository import Gtk
970
971        b = Gtk.Button()
972
973        class A(Gtk.Button):
974
975            def foo(self):
976                pass
977
978        a = A()
979        ref = sys.getrefcount(a)
980        util.connect_destroy(b, "clicked", a.foo)
981        self.assertEqual(sys.getrefcount(a), ref + 1)
982        a.destroy()
983        self.assertEqual(sys.getrefcount(a), ref)
984
985
986class Tcached_property(TestCase):
987
988    def test_main(self):
989
990        class A(object):
991            @util.cached_property
992            def foo(self):
993                return object()
994
995        a = A()
996        first = a.foo
997        self.assertTrue(first is a.foo)
998        del a.__dict__["foo"]
999        self.assertFalse(first is a.foo)
1000
1001    def test_dunder(self):
1002
1003        def define_class():
1004
1005            class A(object):
1006                @util.cached_property
1007                def __foo_(self):
1008                    return object()
1009
1010        self.assertRaises(AssertionError, define_class)
1011
1012
1013@util.enum
1014class Foo(str):
1015    FOO = "blah"
1016    BAR = "not foo"
1017    BAZ = "baz!"
1018
1019
1020class Tenum(TestCase):
1021
1022    def test_main(self):
1023
1024        @util.enum
1025        class IntFoo(int):
1026            FOO = 0
1027            BAR = 1
1028
1029        self.assertTrue(issubclass(IntFoo, int))
1030        self.assertTrue(isinstance(IntFoo.BAR, IntFoo))
1031        self.assertTrue(isinstance(IntFoo.FOO, IntFoo))
1032        self.assertEqual(IntFoo.FOO, 0)
1033        self.assertEqual(IntFoo.BAR, 1)
1034
1035    def test_str(self):
1036        self.assertTrue(issubclass(Foo, str))
1037        self.assertTrue(isinstance(Foo.BAR, Foo))
1038        self.assertEqual(Foo.FOO, "blah")
1039        self.assertEqual(repr(Foo.BAR), "Foo.BAR")
1040
1041    def test_values(self):
1042        self.assertEqual(Foo.values, {Foo.FOO, Foo.BAR, Foo.BAZ})
1043
1044    def test_value_of(self):
1045        self.assertEqual(Foo.value_of("blah"), Foo.FOO)
1046        self.assertEqual(Foo.value_of("baz!"), Foo.BAZ)
1047
1048    def test_value_of_raises_for_unknown(self):
1049        self.assertRaises(ValueError, Foo.value_of, "??")
1050
1051    def test_value_of_uses_default(self):
1052        self.assertEquals(Foo.value_of("??", "default"), "default")
1053
1054
1055class Tlist_unique(TestCase):
1056
1057    def test_main(self):
1058        self.assertEqual(util.list_unique([]), [])
1059        self.assertEqual(util.list_unique(iter([])), [])
1060        self.assertEqual(util.list_unique([1, 2, 3]), [1, 2, 3])
1061        self.assertEqual(util.list_unique([1, 2, 1, 4]), [1, 2, 4])
1062        self.assertEqual(util.list_unique([1, 1, 1, 2]), [1, 2])
1063
1064
1065class Treraise(TestCase):
1066
1067    def test_reraise(self):
1068        try:
1069            try:
1070                raise ValueError("foo")
1071            except Exception as e:
1072                util.reraise(TypeError, e)
1073        except Exception as e:
1074            self.assertTrue(isinstance(e, TypeError))
1075            self.assertTrue("ValueError" in traceback.format_exc())
1076        else:
1077            self.assertTrue(False)
1078
1079
1080class Tenviron(TestCase):
1081
1082    def test_main(self):
1083        for v in environ.values():
1084            if os.name == "nt":
1085                self.assertTrue(isinstance(v, str))
1086            else:
1087                self.assertTrue(isinstance(v, str))
1088
1089
1090class Tget_module_dir(TestCase):
1091
1092    def test_self(self):
1093        path = util.get_module_dir()
1094        self.assertTrue(isinstance(path, fsnative))
1095        self.assertTrue(os.path.exists(path))
1096
1097    def test_other(self):
1098        path = util.get_module_dir(util)
1099        self.assertTrue(isinstance(path, fsnative))
1100        self.assertTrue(os.path.exists(path))
1101
1102
1103class Tget_ca_file(TestCase):
1104
1105    def test_main(self):
1106        path = util.get_ca_file()
1107        if path is not None:
1108            self.assertTrue(isinstance(path, fsnative))
1109            self.assertTrue(os.path.exists(path))
1110
1111
1112class Tprint_exc(TestCase):
1113
1114    def test_main(self):
1115        try:
1116            1 / 0
1117        except:
1118            with capture_output():
1119                print_exc()
1120
1121    def test_pass_exc_info(self):
1122        try:
1123            1 / 0
1124        except:
1125            with capture_output():
1126                print_exc(exc_info=sys.exc_info(), context="foo")
1127
1128
1129class TPrintHandler(TestCase):
1130
1131    def test_main(self):
1132        handler = PrintHandler()
1133        for level in range(0, 70, 10):
1134            record = logging.LogRecord(
1135                "foo", level, "a.py", 45, "bar", None, None)
1136            with capture_output():
1137                handler.handle(record)
1138
1139
1140class Tformat_exception(TestCase):
1141
1142    def test_main(self):
1143        try:
1144            1 / 0
1145        except:
1146            result = format_exception(*sys.exc_info())
1147            self.assertTrue(isinstance(result, list))
1148            self.assertTrue(all([isinstance(l, str) for l in result]))
1149
1150
1151class Textract_tb(TestCase):
1152
1153    def test_main(self):
1154        try:
1155            1 / 0
1156        except:
1157            result = extract_tb(sys.exc_info()[2])
1158            self.assertTrue(isinstance(result, list))
1159            for fn, l, fu, text in result:
1160                self.assertTrue(isinstance(fn, fsnative))
1161                self.assertTrue(isinstance(l, int))
1162                self.assertTrue(isinstance(fu, str))
1163                self.assertTrue(isinstance(text, str))
1164