diff --git a/test/test_mediafile.py b/test/test_mediafile.py index f87eaf132..0735a8e71 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -12,280 +12,525 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Specific, edge-case tests for the MediaFile metadata layer. +"""Automatically-generated blanket testing for the MediaFile metadata +layer. """ import os import shutil +import tempfile +import datetime +import time import _common from _common import unittest -import beets.mediafile +from beets.mediafile import MediaFile -class EdgeTest(unittest.TestCase): - def test_emptylist(self): - # Some files have an ID3 frame that has a list with no elements. - # This is very hard to produce, so this is just the first 8192 - # bytes of a file found "in the wild". - emptylist = beets.mediafile.MediaFile( - os.path.join(_common.RSRC, 'emptylist.mp3')) - genre = emptylist.genre - self.assertEqual(genre, '') +class ArtTestMixin(object): + """Test reads and writes of the ``art`` property. + """ - def test_release_time_with_space(self): - # Ensures that release times delimited by spaces are ignored. - # Amie Street produces such files. - space_time = beets.mediafile.MediaFile( - os.path.join(_common.RSRC, 'space_time.mp3')) - self.assertEqual(space_time.year, 2009) - self.assertEqual(space_time.month, 9) - self.assertEqual(space_time.day, 4) + @property + def png_data(self): + if not self._png_data: + with open(os.path.join(_common.RSRC, 'image-2x3.png'), 'rb') as f: + self._png_data = f.read() + return self._png_data + _png_data = None - def test_release_time_with_t(self): - # Ensures that release times delimited by Ts are ignored. - # The iTunes Store produces such files. - t_time = beets.mediafile.MediaFile( - os.path.join(_common.RSRC, 't_time.m4a')) - self.assertEqual(t_time.year, 1987) - self.assertEqual(t_time.month, 3) - self.assertEqual(t_time.day, 31) + @property + def jpg_data(self): + if not self._jpg_data: + with open(os.path.join(_common.RSRC, 'image-2x3.jpg'), 'rb') as f: + self._jpg_data = f.read() + return self._jpg_data + _jpg_data = None - def test_tempo_with_bpm(self): - # Some files have a string like "128 BPM" in the tempo field - # rather than just a number. - f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'bpm.mp3')) - self.assertEqual(f.bpm, 128) + def test_set_png_art(self): + mediafile = self._mediafile_fixture('empty') + mediafile.art = self.png_data + mediafile.save() - def test_discc_alternate_field(self): - # Different taggers use different vorbis comments to reflect - # the disc and disc count fields: ensure that the alternative - # style works. - f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'discc.ogg')) - self.assertEqual(f.disc, 4) - self.assertEqual(f.disctotal, 5) + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.art, self.png_data) - def test_old_ape_version_bitrate(self): - f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'oldape.ape')) - self.assertEqual(f.bitrate, 0) + def test_set_jpg_art(self): + mediafile = self._mediafile_fixture('empty') + mediafile.art = self.jpg_data + mediafile.save() + + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.art, self.jpg_data) -_sc = beets.mediafile._safe_cast -class InvalidValueToleranceTest(unittest.TestCase): +class LazySaveTestMixin(object): + """Mediafile should only write changes when tags have changed + """ - def test_safe_cast_string_to_int(self): - self.assertEqual(_sc(int, 'something'), 0) + def test_unmodified(self): + mediafile = self._mediafile_fixture('full') + mtime = self._set_past_mtime(mediafile.path) + self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - def test_safe_cast_int_string_to_int(self): - self.assertEqual(_sc(int, '20'), 20) + mediafile.save() + self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - def test_safe_cast_string_to_bool(self): - self.assertEqual(_sc(bool, 'whatever'), False) + def test_same_tag_value(self): + mediafile = self._mediafile_fixture('full') + mtime = self._set_past_mtime(mediafile.path) + self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - def test_safe_cast_intstring_to_bool(self): - self.assertEqual(_sc(bool, '5'), True) + mediafile.title = mediafile.title + mediafile.save() + self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - def test_safe_cast_string_to_float(self): - self.assertAlmostEqual(_sc(float, '1.234'), 1.234) + def test_tag_value_change(self): + mediafile = self._mediafile_fixture('full') + mtime = self._set_past_mtime(mediafile.path) + self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - def test_safe_cast_int_to_float(self): - self.assertAlmostEqual(_sc(float, 2), 2.0) + mediafile.title = mediafile.title + mediafile.album = 'another' + mediafile.save() + self.assertNotEqual(os.stat(mediafile.path).st_mtime, mtime) - def test_safe_cast_string_with_cruft_to_float(self): - self.assertAlmostEqual(_sc(float, '1.234stuff'), 1.234) - - def test_safe_cast_negative_string_to_float(self): - self.assertAlmostEqual(_sc(float, '-1.234'), -1.234) - - def test_safe_cast_special_chars_to_unicode(self): - us = _sc(unicode, 'caf\xc3\xa9') - self.assertTrue(isinstance(us, unicode)) - self.assertTrue(us.startswith(u'caf')) + def _set_past_mtime(self, path): + mtime = round(time.time()-10000) + os.utime(path, (mtime, mtime)) + return mtime -class SafetyTest(unittest.TestCase): - def _exccheck(self, fn, exc, data=''): - fn = os.path.join(_common.RSRC, fn) - with open(fn, 'w') as f: - f.write(data) - try: - self.assertRaises(exc, beets.mediafile.MediaFile, fn) - finally: - os.unlink(fn) # delete the temporary file +class ReadWriteTestBase(ArtTestMixin, LazySaveTestMixin): + """Test writing and reading tags. Subclasses must set ``extension`` and + ``audio_properties``. + """ - def test_corrupt_mp3_raises_unreadablefileerror(self): - # Make sure we catch Mutagen reading errors appropriately. - self._exccheck('corrupt.mp3', beets.mediafile.UnreadableFileError) + full_initial_tags = { + 'title': u'full', + 'artist': u'the artist', + 'album': u'the album', + 'genre': u'the genre', + 'composer': u'the composer', + 'grouping': u'the grouping', + 'year': 2001, + 'month': 0, + 'day': 0, + 'date': datetime.date(2001, 1, 1), + 'track': 2, + 'tracktotal': 3, + 'disc': 4, + 'disctotal': 5, + 'lyrics': u'the lyrics', + 'comments': u'the comments', + 'bpm': 6, + 'comp': True, + 'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e', + 'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628', + 'mb_artistid':'7cf0ea9d-86b9-4dad-ba9e-2355a64899ea', + 'art': None, + 'label': u'the label', + } - def test_corrupt_mp4_raises_unreadablefileerror(self): - self._exccheck('corrupt.m4a', beets.mediafile.UnreadableFileError) + empty_tags = { + 'title': u'', + 'artist': u'', + 'album': u'', + 'genre': u'', + 'composer': u'', + 'grouping': u'', + 'year': 0, + 'month': 0, + 'day': 0, + 'date': datetime.date.min, + 'track': 0, + 'tracktotal': 0, + 'disc': 0, + 'disctotal': 0, + 'lyrics': u'', + 'comments': u'', + 'bpm': 0, + 'comp': False, + 'mb_trackid': u'', + 'mb_albumid': u'', + 'mb_artistid':u'', + 'art': None, + 'label': u'', - def test_corrupt_flac_raises_unreadablefileerror(self): - self._exccheck('corrupt.flac', beets.mediafile.UnreadableFileError) + # Additional, non-iTunes fields. + 'rg_track_peak': 0.0, + 'rg_track_gain': 0.0, + 'rg_album_peak': 0.0, + 'rg_album_gain': 0.0, + 'albumartist': u'', + 'mb_albumartistid': u'', + 'artist_sort': u'', + 'albumartist_sort': u'', + 'acoustid_fingerprint': u'', + 'acoustid_id': u'', + 'mb_releasegroupid': u'', + 'asin': u'', + 'catalognum': u'', + 'disctitle': u'', + 'script': u'', + 'language': u'', + 'country': u'', + 'albumstatus': u'', + 'media': u'', + 'albumdisambig': u'', + 'artist_credit': u'', + 'albumartist_credit': u'', + 'original_year': 0, + 'original_month': 0, + 'original_day': 0, + 'original_date': datetime.date.min, + } - def test_corrupt_ogg_raises_unreadablefileerror(self): - self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError) - - def test_invalid_ogg_header_raises_unreadablefileerror(self): - self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError, - 'OggS\x01vorbis') - - def test_corrupt_monkeys_raises_unreadablefileerror(self): - self._exccheck('corrupt.ape', beets.mediafile.UnreadableFileError) - - def test_invalid_extension_raises_filetypeerror(self): - self._exccheck('something.unknown', beets.mediafile.FileTypeError) - - def test_magic_xml_raises_unreadablefileerror(self): - self._exccheck('nothing.xml', beets.mediafile.UnreadableFileError, - "ftyp") - - def test_broken_symlink(self): - fn = os.path.join(_common.RSRC, 'brokenlink') - os.symlink('does_not_exist', fn) - try: - self.assertRaises(IOError, - beets.mediafile.MediaFile, fn) - finally: - os.unlink(fn) - - -class SideEffectsTest(unittest.TestCase): def setUp(self): - self.empty = os.path.join(_common.RSRC, 'empty.mp3') - - def test_opening_tagless_file_leaves_untouched(self): - old_mtime = os.stat(self.empty).st_mtime - beets.mediafile.MediaFile(self.empty) - new_mtime = os.stat(self.empty).st_mtime - self.assertEqual(old_mtime, new_mtime) - - -class EncodingTest(unittest.TestCase): - def setUp(self): - src = os.path.join(_common.RSRC, 'full.m4a') - self.path = os.path.join(_common.RSRC, 'test.m4a') - shutil.copy(src, self.path) - - self.mf = beets.mediafile.MediaFile(self.path) + self.temp_dir = tempfile.mkdtemp() def tearDown(self): - os.remove(self.path) + if os.path.isdir(self.temp_dir): + shutil.rmtree(self.temp_dir) - def test_unicode_label_in_m4a(self): - self.mf.label = u'foo\xe8bar' - self.mf.save() - new_mf = beets.mediafile.MediaFile(self.path) - self.assertEqual(new_mf.label, u'foo\xe8bar') + def test_read_audio_properties(self): + mediafile = self._mediafile_fixture('full') + for key, value in self.audio_properties.items(): + if isinstance(value, float): + self.assertAlmostEqual(getattr(mediafile, key), value, delta=0.1) + else: + self.assertEqual(getattr(mediafile, key), value) + + def test_read_full(self): + mediafile = self._mediafile_fixture('full') + self.assertTags(mediafile, self.full_initial_tags) + + def test_read_empty(self): + mediafile = self._mediafile_fixture('empty') + self.assertTags(mediafile, self.empty_tags) + + def test_write_empty(self): + mediafile = self._mediafile_fixture('empty') + tags = self._generate_tags() + + for key, value in tags.items(): + setattr(mediafile, key, value) + mediafile.save() + + mediafile = MediaFile(mediafile.path) + self.assertTags(mediafile, tags) + + def test_overwrite_full(self): + mediafile = self._mediafile_fixture('full') + tags = self._generate_tags() + + for key, value in tags.items(): + setattr(mediafile, key, value) + mediafile.save() + + # Make sure the tags are already set when writing a second time + for key, value in tags.items(): + setattr(mediafile, key, value) + mediafile.save() + + mediafile = MediaFile(mediafile.path) + self.assertTags(mediafile, tags) + + def test_write_date_components(self): + mediafile = self._mediafile_fixture('full') + mediafile.year = 2001 + mediafile.month = 1 + mediafile.day = 2 + mediafile.original_year = 1999 + mediafile.original_month = 12 + mediafile.original_day = 30 + mediafile.save() + + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.year, 2001) + self.assertEqual(mediafile.month, 1) + self.assertEqual(mediafile.day, 2) + self.assertEqual(mediafile.date, datetime.date(2001,1,2)) + self.assertEqual(mediafile.original_year, 1999) + self.assertEqual(mediafile.original_month, 12) + self.assertEqual(mediafile.original_day, 30) + self.assertEqual(mediafile.original_date, datetime.date(1999,12,30)) + + def test_write_dates(self): + mediafile = self._mediafile_fixture('full') + mediafile.date = datetime.date(2001,1,2) + mediafile.original_date = datetime.date(1999,12,30) + mediafile.save() + + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.year, 2001) + self.assertEqual(mediafile.month, 1) + self.assertEqual(mediafile.day, 2) + self.assertEqual(mediafile.date, datetime.date(2001,1,2)) + self.assertEqual(mediafile.original_year, 1999) + self.assertEqual(mediafile.original_month, 12) + self.assertEqual(mediafile.original_day, 30) + self.assertEqual(mediafile.original_date, datetime.date(1999,12,30)) + + def test_read_write_float_none(self): + mediafile = self._mediafile_fixture('full') + mediafile.rg_track_gain = None + mediafile.rg_track_peak = None + mediafile.original_year = None + mediafile.original_month = None + mediafile.original_day = None + mediafile.save() + + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.rg_track_gain, 0) + self.assertEqual(mediafile.rg_track_peak, 0) + self.assertEqual(mediafile.original_year, 0) + self.assertEqual(mediafile.original_month, 0) + self.assertEqual(mediafile.original_day, 0) + + def test_write_packed(self): + mediafile = self._mediafile_fixture('empty') + + mediafile.tracktotal = 2 + mediafile.track = 1 + mediafile.save() + + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.track, 1) + self.assertEqual(mediafile.tracktotal, 2) + + def test_write_counters_without_total(self): + mediafile = self._mediafile_fixture('full') + self.assertEqual(mediafile.track, 2) + self.assertEqual(mediafile.tracktotal, 3) + self.assertEqual(mediafile.disc, 4) + self.assertEqual(mediafile.disctotal, 5) + + mediafile.track = 10 + mediafile.tracktotal = None + mediafile.disc = 10 + mediafile.disctotal = None + mediafile.save() + + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.track, 10) + self.assertEqual(mediafile.tracktotal, 0) + self.assertEqual(mediafile.disc, 10) + self.assertEqual(mediafile.disctotal, 0) -class ZeroLengthMediaFile(beets.mediafile.MediaFile): - @property - def length(self): - return 0.0 + def assertTags(self, mediafile, tags): + __unittest = True + errors = [] + for key, value in tags.items(): + try: + value2 = getattr(mediafile, key) + except AttributeError: + errors.append('Tag %s does not exist' % key) + else: + if value2 != value: + errors.append('Tag %s: %s != %s' % + (key, value2, value)) + if any(errors): + errors = ['Tags did not match'] + errors + self.fail('\n '.join(errors)) + + def _mediafile_fixture(self, name): + name = name + '.' + self.extension + src = os.path.join(_common.RSRC, name) + target = os.path.join(self.temp_dir, name) + shutil.copy(src, target) + return MediaFile(target) + + def _generate_tags(self, base=None): + """Make a dict of tags with correct values and consitent dates. + """ + tags = {} + if base is None: + base = self.empty_tags + + for key, value in base.items(): + if key == 'art': + tags[key] = self.jpg_data + elif isinstance(value, unicode): + tags[key] = 'value %s' % key + elif isinstance(value, int): + tags[key] = 1 + elif isinstance(value, float): + tags[key] = 1.0 + elif isinstance(value, bool): + tags[key] = True + + date = datetime.date(2001, 4, 3) + tags['date'] = date + tags['year'] = date.year + tags['month'] = date.month + tags['day'] = date.day + + original_date = datetime.date(1999, 5, 6) + tags['original_date'] = original_date + tags['original_year'] = original_date.year + tags['original_month'] = original_date.month + tags['original_day'] = original_date.day + return tags -class MissingAudioDataTest(unittest.TestCase): - def setUp(self): - super(MissingAudioDataTest, self).setUp() - path = os.path.join(_common.RSRC, 'full.mp3') - self.mf = ZeroLengthMediaFile(path) +class PartialTestMixin(object): + tags_without_total = { + 'track': 2, + 'tracktotal': 0, + 'disc': 4, + 'disctotal': 0, + } - def test_bitrate_with_zero_length(self): - del self.mf.mgfile.info.bitrate # Not available directly. - self.assertEqual(self.mf.bitrate, 0) + def test_read_track_without_total(self): + mediafile = self._mediafile_fixture('partial') + self.assertEqual(mediafile.track, 2) + self.assertEqual(mediafile.tracktotal, 0) + self.assertEqual(mediafile.disc, 4) + self.assertEqual(mediafile.disctotal, 0) -class TypeTest(unittest.TestCase): - def setUp(self): - super(TypeTest, self).setUp() - path = os.path.join(_common.RSRC, 'full.mp3') - self.mf = beets.mediafile.MediaFile(path) +class GenreListTestMixin(object): + """Tests access to the ``genres`` property as a list. + """ - def test_year_integer_in_string(self): - self.mf.year = '2009' - self.assertEqual(self.mf.year, 2009) + def test_read_genre_list(self): + mediafile = self._mediafile_fixture('full') + self.assertEqual(mediafile.genres, ['the genre']) - def test_set_replaygain_gain_to_none(self): - self.mf.rg_track_gain = None - self.assertEqual(self.mf.rg_track_gain, 0.0) + def test_write_genre_list(self): + mediafile = self._mediafile_fixture('empty') + mediafile.genres = ['one', 'two'] + mediafile.save() - def test_set_replaygain_peak_to_none(self): - self.mf.rg_track_peak = None - self.assertEqual(self.mf.rg_track_peak, 0.0) + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.genres, ['one', 'two']) - def test_set_year_to_none(self): - self.mf.year = None - self.assertEqual(self.mf.year, 0) + def test_write_genre_list_get_first(self): + mediafile = self._mediafile_fixture('empty') + mediafile.genres = ['one', 'two'] + mediafile.save() - def test_set_track_to_none(self): - self.mf.track = None - self.assertEqual(self.mf.track, 0) + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.genre, 'one') + + def test_append_genre_list(self): + mediafile = self._mediafile_fixture('full') + self.assertEqual(mediafile.genre, 'the genre') + mediafile.genres += ['another'] + mediafile.save() + + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.genres, ['the genre', 'another']) -class SoundCheckTest(unittest.TestCase): - def test_round_trip(self): - data = beets.mediafile._sc_encode(1.0, 1.0) - gain, peak = beets.mediafile._sc_decode(data) - self.assertEqual(gain, 1.0) - self.assertEqual(peak, 1.0) - - def test_decode_zero(self): - data = u' 80000000 80000000 00000000 00000000 00000000 00000000 ' \ - u'00000000 00000000 00000000 00000000' - gain, peak = beets.mediafile._sc_decode(data) - self.assertEqual(gain, 0.0) - self.assertEqual(peak, 0.0) - - def test_malformatted(self): - gain, peak = beets.mediafile._sc_decode(u'foo') - self.assertEqual(gain, 0.0) - self.assertEqual(peak, 0.0) - - -class ID3v23Test(unittest.TestCase): - def _make_test(self, ext='mp3'): - src = os.path.join(_common.RSRC, 'full.{0}'.format(ext)) - self.path = os.path.join(_common.RSRC, 'test.{0}'.format(ext)) - shutil.copy(src, self.path) - return beets.mediafile.MediaFile(self.path) - - def _delete_test(self): - os.remove(self.path) - - def test_v24_year_tag(self): - mf = self._make_test() - try: - mf.year = 2013 - mf.save(id3v23=False) - frame = mf.mgfile['TDRC'] - self.assertTrue('2013' in str(frame)) - self.assertTrue('TYER' not in mf.mgfile) - finally: - self._delete_test() - - def test_v23_year_tag(self): - mf = self._make_test() - try: - mf.year = 2013 - mf.save(id3v23=True) - frame = mf.mgfile['TYER'] - self.assertTrue('2013' in str(frame)) - self.assertTrue('TDRC' not in mf.mgfile) - finally: - self._delete_test() - - def test_v23_on_non_mp3_is_noop(self): - mf = self._make_test('m4a') - try: - mf.year = 2013 - mf.save(id3v23=True) - finally: - self._delete_test() +class MP3Test(ReadWriteTestBase, PartialTestMixin, + GenreListTestMixin, unittest.TestCase): + extension = 'mp3' + audio_properties = { + 'length': 1.0, + 'bitrate': 80000, + 'format': 'MP3', + 'samplerate': 44100, + 'bitdepth': 0, + 'channels': 1, + } +class MP4Test(ReadWriteTestBase, PartialTestMixin, + GenreListTestMixin, unittest.TestCase): + extension = 'm4a' + audio_properties = { + 'length': 1.0, + 'bitrate': 64000, + 'format': 'AAC', + 'samplerate': 44100, + 'bitdepth': 16, + 'channels': 2, + } +class AlacTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): + extension = 'alac.m4a' + audio_properties = { + 'length': 1.0, + 'bitrate': 55072, + 'format': 'ALAC', + 'samplerate': 0, + 'bitdepth': 0, + 'channels': 0, + } +class MusepackTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): + extension = 'mpc' + audio_properties = { + 'length': 1.0, + 'bitrate': 23458, + 'format': 'Musepack', + 'samplerate': 44100, + 'bitdepth': 0, + 'channels': 2, + } +class WMATest(ReadWriteTestBase, unittest.TestCase): + extension = 'wma' + audio_properties = { + 'length': 1.0, + 'bitrate': 128000, + 'format': 'Windows Media', + 'samplerate': 44100, + 'bitdepth': 0, + 'channels': 1, + } +class OggTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): + extension = 'ogg' + audio_properties = { + 'length': 1.0, + 'bitrate': 48000, + 'format': 'OGG', + 'samplerate': 44100, + 'bitdepth': 0, + 'channels': 1, + } +class FlacTest(ReadWriteTestBase, PartialTestMixin, + GenreListTestMixin, unittest.TestCase): + extension = 'flac' + audio_properties = { + 'length': 1.0, + 'bitrate': 175120, + 'format': 'FLAC', + 'samplerate': 44100, + 'bitdepth': 16, + 'channels': 1, + } +class ApeTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): + extension = 'ape' + audio_properties = { + 'length': 1.0, + 'bitrate': 112040, + 'format': 'APE', + 'samplerate': 44100, + 'bitdepth': 16, + 'channels': 1, + } +class WavpackTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): + extension = 'wv' + audio_properties = { + 'length': 1.0, + 'bitrate': 108744, + 'format': 'WavPack', + 'samplerate': 44100, + 'bitdepth': 0, + 'channels': 1, + } +class OpusTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): + extension = 'opus' + audio_properties = { + 'length': 1.0, + 'bitrate': 57984, + 'format': 'Opus', + 'samplerate': 48000, + 'bitdepth': 0, + 'channels': 1, + } def suite(): return unittest.TestLoader().loadTestsFromName(__name__) - if __name__ == '__main__': unittest.main(defaultTest='suite') diff --git a/test/test_mediafile_basic.py b/test/test_mediafile_basic.py deleted file mode 100644 index 0735a8e71..000000000 --- a/test/test_mediafile_basic.py +++ /dev/null @@ -1,536 +0,0 @@ -# This file is part of beets. -# Copyright 2013, Adrian Sampson. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. - -"""Automatically-generated blanket testing for the MediaFile metadata -layer. -""" -import os -import shutil -import tempfile -import datetime -import time - -import _common -from _common import unittest -from beets.mediafile import MediaFile - - -class ArtTestMixin(object): - """Test reads and writes of the ``art`` property. - """ - - @property - def png_data(self): - if not self._png_data: - with open(os.path.join(_common.RSRC, 'image-2x3.png'), 'rb') as f: - self._png_data = f.read() - return self._png_data - _png_data = None - - @property - def jpg_data(self): - if not self._jpg_data: - with open(os.path.join(_common.RSRC, 'image-2x3.jpg'), 'rb') as f: - self._jpg_data = f.read() - return self._jpg_data - _jpg_data = None - - def test_set_png_art(self): - mediafile = self._mediafile_fixture('empty') - mediafile.art = self.png_data - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.art, self.png_data) - - def test_set_jpg_art(self): - mediafile = self._mediafile_fixture('empty') - mediafile.art = self.jpg_data - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.art, self.jpg_data) - - -class LazySaveTestMixin(object): - """Mediafile should only write changes when tags have changed - """ - - def test_unmodified(self): - mediafile = self._mediafile_fixture('full') - mtime = self._set_past_mtime(mediafile.path) - self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - - mediafile.save() - self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - - def test_same_tag_value(self): - mediafile = self._mediafile_fixture('full') - mtime = self._set_past_mtime(mediafile.path) - self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - - mediafile.title = mediafile.title - mediafile.save() - self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - - def test_tag_value_change(self): - mediafile = self._mediafile_fixture('full') - mtime = self._set_past_mtime(mediafile.path) - self.assertEqual(os.stat(mediafile.path).st_mtime, mtime) - - mediafile.title = mediafile.title - mediafile.album = 'another' - mediafile.save() - self.assertNotEqual(os.stat(mediafile.path).st_mtime, mtime) - - def _set_past_mtime(self, path): - mtime = round(time.time()-10000) - os.utime(path, (mtime, mtime)) - return mtime - - -class ReadWriteTestBase(ArtTestMixin, LazySaveTestMixin): - """Test writing and reading tags. Subclasses must set ``extension`` and - ``audio_properties``. - """ - - full_initial_tags = { - 'title': u'full', - 'artist': u'the artist', - 'album': u'the album', - 'genre': u'the genre', - 'composer': u'the composer', - 'grouping': u'the grouping', - 'year': 2001, - 'month': 0, - 'day': 0, - 'date': datetime.date(2001, 1, 1), - 'track': 2, - 'tracktotal': 3, - 'disc': 4, - 'disctotal': 5, - 'lyrics': u'the lyrics', - 'comments': u'the comments', - 'bpm': 6, - 'comp': True, - 'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e', - 'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628', - 'mb_artistid':'7cf0ea9d-86b9-4dad-ba9e-2355a64899ea', - 'art': None, - 'label': u'the label', - } - - empty_tags = { - 'title': u'', - 'artist': u'', - 'album': u'', - 'genre': u'', - 'composer': u'', - 'grouping': u'', - 'year': 0, - 'month': 0, - 'day': 0, - 'date': datetime.date.min, - 'track': 0, - 'tracktotal': 0, - 'disc': 0, - 'disctotal': 0, - 'lyrics': u'', - 'comments': u'', - 'bpm': 0, - 'comp': False, - 'mb_trackid': u'', - 'mb_albumid': u'', - 'mb_artistid':u'', - 'art': None, - 'label': u'', - - # Additional, non-iTunes fields. - 'rg_track_peak': 0.0, - 'rg_track_gain': 0.0, - 'rg_album_peak': 0.0, - 'rg_album_gain': 0.0, - 'albumartist': u'', - 'mb_albumartistid': u'', - 'artist_sort': u'', - 'albumartist_sort': u'', - 'acoustid_fingerprint': u'', - 'acoustid_id': u'', - 'mb_releasegroupid': u'', - 'asin': u'', - 'catalognum': u'', - 'disctitle': u'', - 'script': u'', - 'language': u'', - 'country': u'', - 'albumstatus': u'', - 'media': u'', - 'albumdisambig': u'', - 'artist_credit': u'', - 'albumartist_credit': u'', - 'original_year': 0, - 'original_month': 0, - 'original_day': 0, - 'original_date': datetime.date.min, - } - - def setUp(self): - self.temp_dir = tempfile.mkdtemp() - - def tearDown(self): - if os.path.isdir(self.temp_dir): - shutil.rmtree(self.temp_dir) - - def test_read_audio_properties(self): - mediafile = self._mediafile_fixture('full') - for key, value in self.audio_properties.items(): - if isinstance(value, float): - self.assertAlmostEqual(getattr(mediafile, key), value, delta=0.1) - else: - self.assertEqual(getattr(mediafile, key), value) - - def test_read_full(self): - mediafile = self._mediafile_fixture('full') - self.assertTags(mediafile, self.full_initial_tags) - - def test_read_empty(self): - mediafile = self._mediafile_fixture('empty') - self.assertTags(mediafile, self.empty_tags) - - def test_write_empty(self): - mediafile = self._mediafile_fixture('empty') - tags = self._generate_tags() - - for key, value in tags.items(): - setattr(mediafile, key, value) - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertTags(mediafile, tags) - - def test_overwrite_full(self): - mediafile = self._mediafile_fixture('full') - tags = self._generate_tags() - - for key, value in tags.items(): - setattr(mediafile, key, value) - mediafile.save() - - # Make sure the tags are already set when writing a second time - for key, value in tags.items(): - setattr(mediafile, key, value) - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertTags(mediafile, tags) - - def test_write_date_components(self): - mediafile = self._mediafile_fixture('full') - mediafile.year = 2001 - mediafile.month = 1 - mediafile.day = 2 - mediafile.original_year = 1999 - mediafile.original_month = 12 - mediafile.original_day = 30 - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.year, 2001) - self.assertEqual(mediafile.month, 1) - self.assertEqual(mediafile.day, 2) - self.assertEqual(mediafile.date, datetime.date(2001,1,2)) - self.assertEqual(mediafile.original_year, 1999) - self.assertEqual(mediafile.original_month, 12) - self.assertEqual(mediafile.original_day, 30) - self.assertEqual(mediafile.original_date, datetime.date(1999,12,30)) - - def test_write_dates(self): - mediafile = self._mediafile_fixture('full') - mediafile.date = datetime.date(2001,1,2) - mediafile.original_date = datetime.date(1999,12,30) - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.year, 2001) - self.assertEqual(mediafile.month, 1) - self.assertEqual(mediafile.day, 2) - self.assertEqual(mediafile.date, datetime.date(2001,1,2)) - self.assertEqual(mediafile.original_year, 1999) - self.assertEqual(mediafile.original_month, 12) - self.assertEqual(mediafile.original_day, 30) - self.assertEqual(mediafile.original_date, datetime.date(1999,12,30)) - - def test_read_write_float_none(self): - mediafile = self._mediafile_fixture('full') - mediafile.rg_track_gain = None - mediafile.rg_track_peak = None - mediafile.original_year = None - mediafile.original_month = None - mediafile.original_day = None - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.rg_track_gain, 0) - self.assertEqual(mediafile.rg_track_peak, 0) - self.assertEqual(mediafile.original_year, 0) - self.assertEqual(mediafile.original_month, 0) - self.assertEqual(mediafile.original_day, 0) - - def test_write_packed(self): - mediafile = self._mediafile_fixture('empty') - - mediafile.tracktotal = 2 - mediafile.track = 1 - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.track, 1) - self.assertEqual(mediafile.tracktotal, 2) - - def test_write_counters_without_total(self): - mediafile = self._mediafile_fixture('full') - self.assertEqual(mediafile.track, 2) - self.assertEqual(mediafile.tracktotal, 3) - self.assertEqual(mediafile.disc, 4) - self.assertEqual(mediafile.disctotal, 5) - - mediafile.track = 10 - mediafile.tracktotal = None - mediafile.disc = 10 - mediafile.disctotal = None - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.track, 10) - self.assertEqual(mediafile.tracktotal, 0) - self.assertEqual(mediafile.disc, 10) - self.assertEqual(mediafile.disctotal, 0) - - - def assertTags(self, mediafile, tags): - __unittest = True - errors = [] - for key, value in tags.items(): - try: - value2 = getattr(mediafile, key) - except AttributeError: - errors.append('Tag %s does not exist' % key) - else: - if value2 != value: - errors.append('Tag %s: %s != %s' % - (key, value2, value)) - if any(errors): - errors = ['Tags did not match'] + errors - self.fail('\n '.join(errors)) - - def _mediafile_fixture(self, name): - name = name + '.' + self.extension - src = os.path.join(_common.RSRC, name) - target = os.path.join(self.temp_dir, name) - shutil.copy(src, target) - return MediaFile(target) - - def _generate_tags(self, base=None): - """Make a dict of tags with correct values and consitent dates. - """ - tags = {} - if base is None: - base = self.empty_tags - - for key, value in base.items(): - if key == 'art': - tags[key] = self.jpg_data - elif isinstance(value, unicode): - tags[key] = 'value %s' % key - elif isinstance(value, int): - tags[key] = 1 - elif isinstance(value, float): - tags[key] = 1.0 - elif isinstance(value, bool): - tags[key] = True - - date = datetime.date(2001, 4, 3) - tags['date'] = date - tags['year'] = date.year - tags['month'] = date.month - tags['day'] = date.day - - original_date = datetime.date(1999, 5, 6) - tags['original_date'] = original_date - tags['original_year'] = original_date.year - tags['original_month'] = original_date.month - tags['original_day'] = original_date.day - return tags - - -class PartialTestMixin(object): - tags_without_total = { - 'track': 2, - 'tracktotal': 0, - 'disc': 4, - 'disctotal': 0, - } - - def test_read_track_without_total(self): - mediafile = self._mediafile_fixture('partial') - self.assertEqual(mediafile.track, 2) - self.assertEqual(mediafile.tracktotal, 0) - self.assertEqual(mediafile.disc, 4) - self.assertEqual(mediafile.disctotal, 0) - - -class GenreListTestMixin(object): - """Tests access to the ``genres`` property as a list. - """ - - def test_read_genre_list(self): - mediafile = self._mediafile_fixture('full') - self.assertEqual(mediafile.genres, ['the genre']) - - def test_write_genre_list(self): - mediafile = self._mediafile_fixture('empty') - mediafile.genres = ['one', 'two'] - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.genres, ['one', 'two']) - - def test_write_genre_list_get_first(self): - mediafile = self._mediafile_fixture('empty') - mediafile.genres = ['one', 'two'] - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.genre, 'one') - - def test_append_genre_list(self): - mediafile = self._mediafile_fixture('full') - self.assertEqual(mediafile.genre, 'the genre') - mediafile.genres += ['another'] - mediafile.save() - - mediafile = MediaFile(mediafile.path) - self.assertEqual(mediafile.genres, ['the genre', 'another']) - - -class MP3Test(ReadWriteTestBase, PartialTestMixin, - GenreListTestMixin, unittest.TestCase): - extension = 'mp3' - audio_properties = { - 'length': 1.0, - 'bitrate': 80000, - 'format': 'MP3', - 'samplerate': 44100, - 'bitdepth': 0, - 'channels': 1, - } -class MP4Test(ReadWriteTestBase, PartialTestMixin, - GenreListTestMixin, unittest.TestCase): - extension = 'm4a' - audio_properties = { - 'length': 1.0, - 'bitrate': 64000, - 'format': 'AAC', - 'samplerate': 44100, - 'bitdepth': 16, - 'channels': 2, - } -class AlacTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): - extension = 'alac.m4a' - audio_properties = { - 'length': 1.0, - 'bitrate': 55072, - 'format': 'ALAC', - 'samplerate': 0, - 'bitdepth': 0, - 'channels': 0, - } -class MusepackTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): - extension = 'mpc' - audio_properties = { - 'length': 1.0, - 'bitrate': 23458, - 'format': 'Musepack', - 'samplerate': 44100, - 'bitdepth': 0, - 'channels': 2, - } -class WMATest(ReadWriteTestBase, unittest.TestCase): - extension = 'wma' - audio_properties = { - 'length': 1.0, - 'bitrate': 128000, - 'format': 'Windows Media', - 'samplerate': 44100, - 'bitdepth': 0, - 'channels': 1, - } -class OggTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): - extension = 'ogg' - audio_properties = { - 'length': 1.0, - 'bitrate': 48000, - 'format': 'OGG', - 'samplerate': 44100, - 'bitdepth': 0, - 'channels': 1, - } -class FlacTest(ReadWriteTestBase, PartialTestMixin, - GenreListTestMixin, unittest.TestCase): - extension = 'flac' - audio_properties = { - 'length': 1.0, - 'bitrate': 175120, - 'format': 'FLAC', - 'samplerate': 44100, - 'bitdepth': 16, - 'channels': 1, - } -class ApeTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): - extension = 'ape' - audio_properties = { - 'length': 1.0, - 'bitrate': 112040, - 'format': 'APE', - 'samplerate': 44100, - 'bitdepth': 16, - 'channels': 1, - } -class WavpackTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): - extension = 'wv' - audio_properties = { - 'length': 1.0, - 'bitrate': 108744, - 'format': 'WavPack', - 'samplerate': 44100, - 'bitdepth': 0, - 'channels': 1, - } -class OpusTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase): - extension = 'opus' - audio_properties = { - 'length': 1.0, - 'bitrate': 57984, - 'format': 'Opus', - 'samplerate': 48000, - 'bitdepth': 0, - 'channels': 1, - } - - -def suite(): - return unittest.TestLoader().loadTestsFromName(__name__) - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/test/test_mediafile_edge.py b/test/test_mediafile_edge.py new file mode 100644 index 000000000..f87eaf132 --- /dev/null +++ b/test/test_mediafile_edge.py @@ -0,0 +1,291 @@ +# This file is part of beets. +# Copyright 2013, Adrian Sampson. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Specific, edge-case tests for the MediaFile metadata layer. +""" +import os +import shutil + +import _common +from _common import unittest +import beets.mediafile + + +class EdgeTest(unittest.TestCase): + def test_emptylist(self): + # Some files have an ID3 frame that has a list with no elements. + # This is very hard to produce, so this is just the first 8192 + # bytes of a file found "in the wild". + emptylist = beets.mediafile.MediaFile( + os.path.join(_common.RSRC, 'emptylist.mp3')) + genre = emptylist.genre + self.assertEqual(genre, '') + + def test_release_time_with_space(self): + # Ensures that release times delimited by spaces are ignored. + # Amie Street produces such files. + space_time = beets.mediafile.MediaFile( + os.path.join(_common.RSRC, 'space_time.mp3')) + self.assertEqual(space_time.year, 2009) + self.assertEqual(space_time.month, 9) + self.assertEqual(space_time.day, 4) + + def test_release_time_with_t(self): + # Ensures that release times delimited by Ts are ignored. + # The iTunes Store produces such files. + t_time = beets.mediafile.MediaFile( + os.path.join(_common.RSRC, 't_time.m4a')) + self.assertEqual(t_time.year, 1987) + self.assertEqual(t_time.month, 3) + self.assertEqual(t_time.day, 31) + + def test_tempo_with_bpm(self): + # Some files have a string like "128 BPM" in the tempo field + # rather than just a number. + f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'bpm.mp3')) + self.assertEqual(f.bpm, 128) + + def test_discc_alternate_field(self): + # Different taggers use different vorbis comments to reflect + # the disc and disc count fields: ensure that the alternative + # style works. + f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'discc.ogg')) + self.assertEqual(f.disc, 4) + self.assertEqual(f.disctotal, 5) + + def test_old_ape_version_bitrate(self): + f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'oldape.ape')) + self.assertEqual(f.bitrate, 0) + + +_sc = beets.mediafile._safe_cast +class InvalidValueToleranceTest(unittest.TestCase): + + def test_safe_cast_string_to_int(self): + self.assertEqual(_sc(int, 'something'), 0) + + def test_safe_cast_int_string_to_int(self): + self.assertEqual(_sc(int, '20'), 20) + + def test_safe_cast_string_to_bool(self): + self.assertEqual(_sc(bool, 'whatever'), False) + + def test_safe_cast_intstring_to_bool(self): + self.assertEqual(_sc(bool, '5'), True) + + def test_safe_cast_string_to_float(self): + self.assertAlmostEqual(_sc(float, '1.234'), 1.234) + + def test_safe_cast_int_to_float(self): + self.assertAlmostEqual(_sc(float, 2), 2.0) + + def test_safe_cast_string_with_cruft_to_float(self): + self.assertAlmostEqual(_sc(float, '1.234stuff'), 1.234) + + def test_safe_cast_negative_string_to_float(self): + self.assertAlmostEqual(_sc(float, '-1.234'), -1.234) + + def test_safe_cast_special_chars_to_unicode(self): + us = _sc(unicode, 'caf\xc3\xa9') + self.assertTrue(isinstance(us, unicode)) + self.assertTrue(us.startswith(u'caf')) + + +class SafetyTest(unittest.TestCase): + def _exccheck(self, fn, exc, data=''): + fn = os.path.join(_common.RSRC, fn) + with open(fn, 'w') as f: + f.write(data) + try: + self.assertRaises(exc, beets.mediafile.MediaFile, fn) + finally: + os.unlink(fn) # delete the temporary file + + def test_corrupt_mp3_raises_unreadablefileerror(self): + # Make sure we catch Mutagen reading errors appropriately. + self._exccheck('corrupt.mp3', beets.mediafile.UnreadableFileError) + + def test_corrupt_mp4_raises_unreadablefileerror(self): + self._exccheck('corrupt.m4a', beets.mediafile.UnreadableFileError) + + def test_corrupt_flac_raises_unreadablefileerror(self): + self._exccheck('corrupt.flac', beets.mediafile.UnreadableFileError) + + def test_corrupt_ogg_raises_unreadablefileerror(self): + self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError) + + def test_invalid_ogg_header_raises_unreadablefileerror(self): + self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError, + 'OggS\x01vorbis') + + def test_corrupt_monkeys_raises_unreadablefileerror(self): + self._exccheck('corrupt.ape', beets.mediafile.UnreadableFileError) + + def test_invalid_extension_raises_filetypeerror(self): + self._exccheck('something.unknown', beets.mediafile.FileTypeError) + + def test_magic_xml_raises_unreadablefileerror(self): + self._exccheck('nothing.xml', beets.mediafile.UnreadableFileError, + "ftyp") + + def test_broken_symlink(self): + fn = os.path.join(_common.RSRC, 'brokenlink') + os.symlink('does_not_exist', fn) + try: + self.assertRaises(IOError, + beets.mediafile.MediaFile, fn) + finally: + os.unlink(fn) + + +class SideEffectsTest(unittest.TestCase): + def setUp(self): + self.empty = os.path.join(_common.RSRC, 'empty.mp3') + + def test_opening_tagless_file_leaves_untouched(self): + old_mtime = os.stat(self.empty).st_mtime + beets.mediafile.MediaFile(self.empty) + new_mtime = os.stat(self.empty).st_mtime + self.assertEqual(old_mtime, new_mtime) + + +class EncodingTest(unittest.TestCase): + def setUp(self): + src = os.path.join(_common.RSRC, 'full.m4a') + self.path = os.path.join(_common.RSRC, 'test.m4a') + shutil.copy(src, self.path) + + self.mf = beets.mediafile.MediaFile(self.path) + + def tearDown(self): + os.remove(self.path) + + def test_unicode_label_in_m4a(self): + self.mf.label = u'foo\xe8bar' + self.mf.save() + new_mf = beets.mediafile.MediaFile(self.path) + self.assertEqual(new_mf.label, u'foo\xe8bar') + + +class ZeroLengthMediaFile(beets.mediafile.MediaFile): + @property + def length(self): + return 0.0 + + +class MissingAudioDataTest(unittest.TestCase): + def setUp(self): + super(MissingAudioDataTest, self).setUp() + path = os.path.join(_common.RSRC, 'full.mp3') + self.mf = ZeroLengthMediaFile(path) + + def test_bitrate_with_zero_length(self): + del self.mf.mgfile.info.bitrate # Not available directly. + self.assertEqual(self.mf.bitrate, 0) + + +class TypeTest(unittest.TestCase): + def setUp(self): + super(TypeTest, self).setUp() + path = os.path.join(_common.RSRC, 'full.mp3') + self.mf = beets.mediafile.MediaFile(path) + + def test_year_integer_in_string(self): + self.mf.year = '2009' + self.assertEqual(self.mf.year, 2009) + + def test_set_replaygain_gain_to_none(self): + self.mf.rg_track_gain = None + self.assertEqual(self.mf.rg_track_gain, 0.0) + + def test_set_replaygain_peak_to_none(self): + self.mf.rg_track_peak = None + self.assertEqual(self.mf.rg_track_peak, 0.0) + + def test_set_year_to_none(self): + self.mf.year = None + self.assertEqual(self.mf.year, 0) + + def test_set_track_to_none(self): + self.mf.track = None + self.assertEqual(self.mf.track, 0) + + +class SoundCheckTest(unittest.TestCase): + def test_round_trip(self): + data = beets.mediafile._sc_encode(1.0, 1.0) + gain, peak = beets.mediafile._sc_decode(data) + self.assertEqual(gain, 1.0) + self.assertEqual(peak, 1.0) + + def test_decode_zero(self): + data = u' 80000000 80000000 00000000 00000000 00000000 00000000 ' \ + u'00000000 00000000 00000000 00000000' + gain, peak = beets.mediafile._sc_decode(data) + self.assertEqual(gain, 0.0) + self.assertEqual(peak, 0.0) + + def test_malformatted(self): + gain, peak = beets.mediafile._sc_decode(u'foo') + self.assertEqual(gain, 0.0) + self.assertEqual(peak, 0.0) + + +class ID3v23Test(unittest.TestCase): + def _make_test(self, ext='mp3'): + src = os.path.join(_common.RSRC, 'full.{0}'.format(ext)) + self.path = os.path.join(_common.RSRC, 'test.{0}'.format(ext)) + shutil.copy(src, self.path) + return beets.mediafile.MediaFile(self.path) + + def _delete_test(self): + os.remove(self.path) + + def test_v24_year_tag(self): + mf = self._make_test() + try: + mf.year = 2013 + mf.save(id3v23=False) + frame = mf.mgfile['TDRC'] + self.assertTrue('2013' in str(frame)) + self.assertTrue('TYER' not in mf.mgfile) + finally: + self._delete_test() + + def test_v23_year_tag(self): + mf = self._make_test() + try: + mf.year = 2013 + mf.save(id3v23=True) + frame = mf.mgfile['TYER'] + self.assertTrue('2013' in str(frame)) + self.assertTrue('TDRC' not in mf.mgfile) + finally: + self._delete_test() + + def test_v23_on_non_mp3_is_noop(self): + mf = self._make_test('m4a') + try: + mf.year = 2013 + mf.save(id3v23=True) + finally: + self._delete_test() + + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) + + +if __name__ == '__main__': + unittest.main(defaultTest='suite')