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&", "<&>", "&", "&", "<&testing&>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>&</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