1# -*- coding: utf-8 -*- 2# FIXME: This test suite is a mess, a lot of it dates from PyMusepack so 3# it doesn't match the other Mutagen test conventions/quality. 4 5import os 6import shutil 7 8from tests import TestCase, DATA_DIR, get_temp_copy 9 10import mutagen.apev2 11from mutagen import MutagenError 12from mutagen._compat import PY3, text_type, izip, xrange 13from mutagen.apev2 import APEv2File, APEv2, is_valid_apev2_key, \ 14 APEBadItemError, error as APEv2Error 15 16 17SAMPLE = os.path.join(DATA_DIR, "click.mpc") 18OLD = os.path.join(DATA_DIR, "oldtag.apev2") 19BROKEN = os.path.join(DATA_DIR, "brokentag.apev2") 20LYRICS2 = os.path.join(DATA_DIR, "apev2-lyricsv2.mp3") 21INVAL_ITEM_COUNT = os.path.join(DATA_DIR, "145-invalid-item-count.apev2") 22 23 24class Tis_valid_apev2_key(TestCase): 25 26 def test_yes(self): 27 for key in ["foo", "Foo", " f ~~~"]: 28 self.failUnless(is_valid_apev2_key(key)) 29 30 def test_no(self): 31 for key in ["\x11hi", "ffoo\xFF", u"\u1234", "a", "", "foo" * 100]: 32 self.failIf(is_valid_apev2_key(key)) 33 34 if PY3: 35 def test_py3(self): 36 self.assertRaises(TypeError, is_valid_apev2_key, b"abc") 37 38 39class TAPEInvalidItemCount(TestCase): 40 # https://github.com/quodlibet/mutagen/issues/145 41 42 def test_load(self): 43 x = mutagen.apev2.APEv2(INVAL_ITEM_COUNT) 44 self.failUnlessEqual(len(x.keys()), 17) 45 46 47class TAPEWriter(TestCase): 48 offset = 0 49 50 def setUp(self): 51 shutil.copy(SAMPLE, SAMPLE + ".new") 52 shutil.copy(BROKEN, BROKEN + ".new") 53 tag = mutagen.apev2.APEv2() 54 self.values = {"artist": "Joe Wreschnig\0unittest", 55 "album": "Mutagen tests", 56 "title": "Not really a song"} 57 for k, v in self.values.items(): 58 tag[k] = v 59 tag.save(SAMPLE + ".new") 60 tag.save(SAMPLE + ".justtag") 61 tag.save(SAMPLE + ".tag_at_start") 62 fileobj = open(SAMPLE + ".tag_at_start", "ab") 63 fileobj.write(b"tag garbage" * 1000) 64 fileobj.close() 65 self.tag = mutagen.apev2.APEv2(SAMPLE + ".new") 66 67 def test_changed(self): 68 size = os.path.getsize(SAMPLE + ".new") 69 self.tag.save() 70 self.failUnlessEqual( 71 os.path.getsize(SAMPLE + ".new"), size - self.offset) 72 73 def test_fix_broken(self): 74 # Clean up garbage from a bug in pre-Mutagen APEv2. 75 # This also tests removing ID3v1 tags on writes. 76 self.failIfEqual(os.path.getsize(OLD), os.path.getsize(BROKEN)) 77 tag = mutagen.apev2.APEv2(BROKEN) 78 tag.save(BROKEN + ".new") 79 self.failUnlessEqual( 80 os.path.getsize(OLD), os.path.getsize(BROKEN + ".new")) 81 82 def test_readback(self): 83 for k, v in self.tag.items(): 84 self.failUnlessEqual(str(v), self.values[k]) 85 86 def test_size(self): 87 self.failUnlessEqual( 88 os.path.getsize(SAMPLE + ".new"), 89 os.path.getsize(SAMPLE) + os.path.getsize(SAMPLE + ".justtag")) 90 91 def test_delete(self): 92 mutagen.apev2.delete(SAMPLE + ".justtag") 93 tag = mutagen.apev2.APEv2(SAMPLE + ".new") 94 tag.delete() 95 self.failUnlessEqual(os.path.getsize(SAMPLE + ".justtag"), self.offset) 96 self.failUnlessEqual(os.path.getsize(SAMPLE) + self.offset, 97 os.path.getsize(SAMPLE + ".new")) 98 self.failIf(tag) 99 100 def test_empty(self): 101 self.failUnlessRaises( 102 APEv2Error, mutagen.apev2.APEv2, 103 os.path.join(DATA_DIR, "emptyfile.mp3")) 104 105 def test_tag_at_start(self): 106 filename = SAMPLE + ".tag_at_start" 107 tag = mutagen.apev2.APEv2(filename) 108 self.failUnlessEqual(tag["album"], "Mutagen tests") 109 110 def test_tag_at_start_write(self): 111 filename = SAMPLE + ".tag_at_start" 112 tag = mutagen.apev2.APEv2(filename) 113 tag.save() 114 tag = mutagen.apev2.APEv2(filename) 115 self.failUnlessEqual(tag["album"], "Mutagen tests") 116 self.failUnlessEqual( 117 os.path.getsize(SAMPLE + ".justtag"), 118 os.path.getsize(filename) - (len("tag garbage") * 1000)) 119 120 def test_tag_at_start_delete(self): 121 filename = SAMPLE + ".tag_at_start" 122 tag = mutagen.apev2.APEv2(filename) 123 tag.delete() 124 self.failUnlessRaises(APEv2Error, mutagen.apev2.APEv2, filename) 125 self.failUnlessEqual( 126 os.path.getsize(filename), len("tag garbage") * 1000) 127 128 def test_case_preservation(self): 129 mutagen.apev2.delete(SAMPLE + ".justtag") 130 tag = mutagen.apev2.APEv2(SAMPLE + ".new") 131 tag["FoObaR"] = "Quux" 132 tag.save() 133 tag = mutagen.apev2.APEv2(SAMPLE + ".new") 134 self.failUnless("FoObaR" in tag.keys()) 135 self.failIf("foobar" in tag.keys()) 136 137 def test_unicode_key(self): 138 # https://github.com/quodlibet/mutagen/issues/123 139 tag = mutagen.apev2.APEv2(SAMPLE + ".new") 140 tag["abc"] = u'\xf6\xe4\xfc' 141 tag[u"cba"] = "abc" 142 tag.save() 143 144 def test_save_sort_is_deterministic(self): 145 tag = mutagen.apev2.APEv2(SAMPLE + ".new") 146 tag["cba"] = "my cba value" 147 tag["abc"] = "my abc value" 148 tag.save() 149 with open(SAMPLE + ".new", 'rb') as fobj: 150 content = fobj.read() 151 self.assertTrue(content.index(b"abc") < content.index(b"cba")) 152 153 def tearDown(self): 154 os.unlink(SAMPLE + ".new") 155 os.unlink(BROKEN + ".new") 156 os.unlink(SAMPLE + ".justtag") 157 os.unlink(SAMPLE + ".tag_at_start") 158 159 160class TAPEv2ThenID3v1Writer(TAPEWriter): 161 offset = 128 162 163 def setUp(self): 164 super(TAPEv2ThenID3v1Writer, self).setUp() 165 f = open(SAMPLE + ".new", "ab+") 166 f.write(b"TAG" + b"\x00" * 125) 167 f.close() 168 f = open(BROKEN + ".new", "ab+") 169 f.write(b"TAG" + b"\x00" * 125) 170 f.close() 171 f = open(SAMPLE + ".justtag", "ab+") 172 f.write(b"TAG" + b"\x00" * 125) 173 f.close() 174 175 def test_tag_at_start_write(self): 176 pass 177 178 179class TAPEv2(TestCase): 180 181 def setUp(self): 182 self.filename = get_temp_copy(OLD) 183 self.audio = APEv2(self.filename) 184 185 def tearDown(self): 186 os.unlink(self.filename) 187 188 def test_invalid_key(self): 189 self.failUnlessRaises( 190 KeyError, self.audio.__setitem__, u"\u1234", "foo") 191 192 def test_guess_text(self): 193 from mutagen.apev2 import APETextValue 194 self.audio["test"] = u"foobar" 195 self.failUnlessEqual(self.audio["test"], "foobar") 196 self.failUnless(isinstance(self.audio["test"], APETextValue)) 197 198 def test_guess_text_list(self): 199 from mutagen.apev2 import APETextValue 200 self.audio["test"] = [u"foobar", "quuxbarz"] 201 self.failUnlessEqual(self.audio["test"], "foobar\x00quuxbarz") 202 self.failUnless(isinstance(self.audio["test"], APETextValue)) 203 204 def test_guess_utf8(self): 205 from mutagen.apev2 import APETextValue 206 self.audio["test"] = "foobar" 207 self.failUnlessEqual(self.audio["test"], "foobar") 208 self.failUnless(isinstance(self.audio["test"], APETextValue)) 209 210 def test_guess_not_utf8(self): 211 from mutagen.apev2 import APEBinaryValue 212 self.audio["test"] = b"\xa4woo" 213 self.failUnless(isinstance(self.audio["test"], APEBinaryValue)) 214 self.failUnlessEqual(4, len(self.audio["test"])) 215 216 def test_bad_value_type(self): 217 from mutagen.apev2 import APEValue 218 self.failUnlessRaises(ValueError, APEValue, "foo", 99) 219 220 def test_module_delete_empty(self): 221 from mutagen.apev2 import delete 222 delete(os.path.join(DATA_DIR, "emptyfile.mp3")) 223 224 def test_invalid(self): 225 self.failUnlessRaises(MutagenError, mutagen.apev2.APEv2, "dne") 226 227 def test_no_tag(self): 228 self.failUnlessRaises(MutagenError, mutagen.apev2.APEv2, 229 os.path.join(DATA_DIR, "empty.mp3")) 230 231 def test_cases(self): 232 self.failUnlessEqual(self.audio["artist"], self.audio["ARTIST"]) 233 self.failUnless("artist" in self.audio) 234 self.failUnless("artisT" in self.audio) 235 236 def test_keys(self): 237 self.failUnless("Track" in self.audio.keys()) 238 self.failUnless("AnArtist" in self.audio.values()) 239 240 self.failUnlessEqual( 241 self.audio.items(), 242 list(izip(self.audio.keys(), self.audio.values()))) 243 244 def test_key_type(self): 245 key = self.audio.keys()[0] 246 if PY3: 247 self.assertTrue(isinstance(key, text_type)) 248 else: 249 self.assertTrue(isinstance(key, bytes)) 250 251 def test_invalid_keys(self): 252 self.failUnlessRaises(KeyError, self.audio.__getitem__, "\x00") 253 self.failUnlessRaises(KeyError, self.audio.__setitem__, "\x00", "") 254 self.failUnlessRaises(KeyError, self.audio.__delitem__, "\x00") 255 256 def test_dictlike(self): 257 self.failUnless(self.audio.get("track")) 258 self.failUnless(self.audio.get("Track")) 259 260 def test_del(self): 261 s = self.audio["artist"] 262 del(self.audio["artist"]) 263 self.failIf("artist" in self.audio) 264 self.failUnlessRaises(KeyError, self.audio.__getitem__, "artist") 265 self.audio["Artist"] = s 266 self.failUnlessEqual(self.audio["artist"], "AnArtist") 267 268 def test_values(self): 269 self.failUnlessEqual(self.audio["artist"], self.audio["artist"]) 270 self.failUnless(self.audio["artist"] < self.audio["title"]) 271 self.failUnlessEqual(self.audio["artist"], "AnArtist") 272 self.failUnlessEqual(self.audio["title"], "Some Music") 273 self.failUnlessEqual(self.audio["album"], "A test case") 274 self.failUnlessEqual("07", self.audio["track"]) 275 276 self.failIfEqual(self.audio["album"], "A test Case") 277 278 def test_pprint(self): 279 self.failUnless(self.audio.pprint()) 280 281 282class TAPEv2ThenID3v1(TAPEv2): 283 284 def setUp(self): 285 super(TAPEv2ThenID3v1, self).setUp() 286 f = open(self.filename, "ab+") 287 f.write(b"TAG" + b"\x00" * 125) 288 f.close() 289 self.audio = APEv2(self.filename) 290 291 292class TAPEv2WithLyrics2(TestCase): 293 294 def setUp(self): 295 self.tag = mutagen.apev2.APEv2(LYRICS2) 296 297 def test_values(self): 298 self.failUnlessEqual(self.tag["MP3GAIN_MINMAX"], "000,179") 299 self.failUnlessEqual(self.tag["REPLAYGAIN_TRACK_GAIN"], "-4.080000 dB") 300 self.failUnlessEqual(self.tag["REPLAYGAIN_TRACK_PEAK"], "1.008101") 301 302 303class TAPEBinaryValue(TestCase): 304 305 from mutagen.apev2 import APEBinaryValue as BV 306 BV = BV 307 308 def setUp(self): 309 self.sample = b"\x12\x45\xde" 310 self.value = mutagen.apev2.APEValue(self.sample, mutagen.apev2.BINARY) 311 312 def test_type(self): 313 self.failUnless(isinstance(self.value, self.BV)) 314 315 def test_const(self): 316 self.failUnlessEqual(self.sample, bytes(self.value)) 317 318 def test_repr(self): 319 repr(self.value) 320 321 def test_pprint(self): 322 self.assertEqual(self.value.pprint(), "[3 bytes]") 323 324 def test_type2(self): 325 self.assertRaises(TypeError, 326 mutagen.apev2.APEValue, u"abc", mutagen.apev2.BINARY) 327 328 329class TAPETextValue(TestCase): 330 331 from mutagen.apev2 import APETextValue as TV 332 TV = TV 333 334 def setUp(self): 335 self.sample = ["foo", "bar", "baz"] 336 self.value = mutagen.apev2.APEValue( 337 "\0".join(self.sample), mutagen.apev2.TEXT) 338 339 def test_parse(self): 340 self.assertRaises(APEBadItemError, self.TV._new, b"\xff") 341 342 def test_type(self): 343 self.failUnless(isinstance(self.value, self.TV)) 344 345 def test_construct(self): 346 self.assertEqual(text_type(self.TV(u"foo")), u"foo") 347 if not PY3: 348 self.assertEqual(text_type(self.TV(b"foo")), u"foo") 349 self.assertRaises(ValueError, self.TV, b"\xff") 350 351 def test_list(self): 352 self.failUnlessEqual(self.sample, list(self.value)) 353 354 def test_setitem_list(self): 355 self.value[2] = self.sample[2] = 'quux' 356 self.test_list() 357 self.test_getitem() 358 self.value[2] = self.sample[2] = 'baz' 359 360 def test_getitem(self): 361 for i in xrange(len(self.value)): 362 self.failUnlessEqual(self.sample[i], self.value[i]) 363 364 def test_delitem(self): 365 del self.sample[1] 366 self.assertEqual(list(self.sample), ["foo", "baz"]) 367 del self.sample[1:] 368 self.assertEqual(list(self.sample), ["foo"]) 369 370 def test_insert(self): 371 self.sample.insert(0, "a") 372 self.assertEqual(len(self.sample), 4) 373 self.assertEqual(self.sample[0], "a") 374 if PY3: 375 self.assertRaises(TypeError, self.value.insert, 2, b"abc") 376 377 def test_types(self): 378 if PY3: 379 self.assertRaises(TypeError, self.value.__setitem__, 2, b"abc") 380 self.assertRaises( 381 TypeError, mutagen.apev2.APEValue, b"abc", mutagen.apev2.TEXT) 382 383 def test_repr(self): 384 repr(self.value) 385 386 def test_str(self): 387 self.assertEqual(text_type(self.value), u"foo\x00bar\x00baz") 388 389 def test_pprint(self): 390 self.assertEqual(self.value.pprint(), "foo / bar / baz") 391 392 393class TAPEExtValue(TestCase): 394 395 from mutagen.apev2 import APEExtValue as EV 396 EV = EV 397 398 def setUp(self): 399 self.sample = "http://foo" 400 self.value = mutagen.apev2.APEValue( 401 self.sample, mutagen.apev2.EXTERNAL) 402 403 def test_type(self): 404 self.failUnless(isinstance(self.value, self.EV)) 405 406 def test_const(self): 407 self.failUnlessEqual(self.sample, self.value) 408 409 def test_repr(self): 410 repr(self.value) 411 412 if PY3: 413 def test_py3(self): 414 self.assertRaises( 415 TypeError, mutagen.apev2.APEValue, b"abc", 416 mutagen.apev2.EXTERNAL) 417 418 def test_pprint(self): 419 self.assertEqual(self.value.pprint(), "[External] http://foo") 420 421 422class TAPEv2File(TestCase): 423 424 def setUp(self): 425 self.audio = APEv2File(os.path.join(DATA_DIR, "click.mpc")) 426 427 def test_empty(self): 428 f = APEv2File(os.path.join(DATA_DIR, "xing.mp3")) 429 self.assertFalse(f.items()) 430 431 def test_add_tags(self): 432 self.failUnless(self.audio.tags is None) 433 self.audio.add_tags() 434 self.failUnless(self.audio.tags is not None) 435 self.failUnlessRaises(APEv2Error, self.audio.add_tags) 436 437 def test_unknown_info(self): 438 info = self.audio.info 439 info.pprint() 440