mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
These tests were written when I knew almost nothing about Python and even less about unittest. The class-generating magic never worked with nose for a crazy reason I won't get into here. This has a bit more copypasta but the workings are more obvious and we no longer generate enormous numbers of independent tests. There should be a more representative number of dots in the test runner output now.
416 lines
12 KiB
Python
416 lines
12 KiB
Python
# This file is part of beets.
|
|
# Copyright 2012, 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 datetime
|
|
|
|
import _common
|
|
from _common import unittest
|
|
import beets.mediafile
|
|
|
|
CORRECT_DICTS = {
|
|
|
|
# All of the fields iTunes supports that we do also.
|
|
'full': {
|
|
'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',
|
|
},
|
|
|
|
# Additional coverage for common cases when "total" fields are unset.
|
|
# Created with iTunes. (Also tests unset MusicBrainz fields.)
|
|
'partial': {
|
|
'track': 2,
|
|
'tracktotal': 0,
|
|
'disc': 4,
|
|
'disctotal': 0,
|
|
'mb_trackid': '',
|
|
'mb_albumid': '',
|
|
'mb_artistid':'',
|
|
},
|
|
'min': {
|
|
'track': 0,
|
|
'tracktotal': 0,
|
|
'disc': 0,
|
|
'disctotal': 0
|
|
},
|
|
|
|
# ID3 tag deleted with `mp3info -d`. Tests default values.
|
|
'empty': {
|
|
'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'',
|
|
'encoder': u'',
|
|
'script': u'',
|
|
'language': u'',
|
|
'country': u'',
|
|
'albumstatus': u'',
|
|
'media': u'',
|
|
'albumdisambig': u'',
|
|
'artist_credit': u'',
|
|
'albumartist_credit': u'',
|
|
},
|
|
|
|
# Full release date.
|
|
'date': {
|
|
'year': 1987,
|
|
'month': 3,
|
|
'day': 31,
|
|
'date': datetime.date(1987, 3, 31)
|
|
},
|
|
|
|
}
|
|
|
|
READ_ONLY_CORRECT_DICTS = {
|
|
'full.mp3': {
|
|
'length': 1.0,
|
|
'bitrate': 80000,
|
|
'format': 'MP3',
|
|
'samplerate': 44100,
|
|
'bitdepth': 0,
|
|
'channels': 1,
|
|
},
|
|
|
|
'full.flac': {
|
|
'length': 1.0,
|
|
'bitrate': 175120,
|
|
'format': 'FLAC',
|
|
'samplerate': 44100,
|
|
'bitdepth': 16,
|
|
'channels': 1,
|
|
},
|
|
|
|
'full.m4a': {
|
|
'length': 1.0,
|
|
'bitrate': 64000,
|
|
'format': 'AAC',
|
|
'samplerate': 44100,
|
|
'bitdepth': 16,
|
|
'channels': 2,
|
|
},
|
|
|
|
'full.ogg': {
|
|
'length': 1.0,
|
|
'bitrate': 48000,
|
|
'format': 'OGG',
|
|
'samplerate': 44100,
|
|
'bitdepth': 0,
|
|
'channels': 1,
|
|
},
|
|
|
|
'full.ape': {
|
|
'length': 1.0,
|
|
'bitrate': 112040,
|
|
'format': 'APE',
|
|
'samplerate': 44100,
|
|
'bitdepth': 16,
|
|
'channels': 1,
|
|
},
|
|
|
|
'full.wv': {
|
|
'length': 1.0,
|
|
'bitrate': 108744,
|
|
'format': 'WavPack',
|
|
'samplerate': 44100,
|
|
'bitdepth': 0,
|
|
'channels': 1,
|
|
},
|
|
|
|
'full.mpc': {
|
|
'length': 1.0,
|
|
'bitrate': 23,
|
|
'format': 'Musepack',
|
|
'samplerate': 44100,
|
|
'bitdepth': 0,
|
|
'channels': 2,
|
|
},
|
|
}
|
|
|
|
TEST_FILES = {
|
|
'm4a': ['full', 'partial', 'min'],
|
|
'mp3': ['full', 'partial', 'min'],
|
|
'flac': ['full', 'partial', 'min'],
|
|
'ogg': ['full'],
|
|
'ape': ['full'],
|
|
'wv': ['full'],
|
|
'mpc': ['full'],
|
|
}
|
|
|
|
class AllFilesMixin(object):
|
|
"""This is a dumb bit of copypasta but unittest has no supported
|
|
method of generating tests at runtime.
|
|
"""
|
|
def test_m4a_full(self):
|
|
self._run('full', 'm4a')
|
|
|
|
def test_m4a_partial(self):
|
|
self._run('partial', 'm4a')
|
|
|
|
def test_m4a_min(self):
|
|
self._run('min', 'm4a')
|
|
|
|
def test_mp3_full(self):
|
|
self._run('full', 'mp3')
|
|
|
|
def test_mp3_partial(self):
|
|
self._run('partial', 'mp3')
|
|
|
|
def test_mp3_min(self):
|
|
self._run('min', 'mp3')
|
|
|
|
def test_flac_full(self):
|
|
self._run('full', 'flac')
|
|
|
|
def test_flac_partial(self):
|
|
self._run('partial', 'flac')
|
|
|
|
def test_flac_min(self):
|
|
self._run('min', 'flac')
|
|
|
|
def test_ogg(self):
|
|
self._run('full', 'ogg')
|
|
|
|
def test_ape(self):
|
|
self._run('full', 'ape')
|
|
|
|
def test_wv(self):
|
|
self._run('full', 'wv')
|
|
|
|
def test_mpc(self):
|
|
self._run('full', 'mpc')
|
|
|
|
# Special test for advanced release date.
|
|
def test_date_mp3(self):
|
|
self._run('date', 'mp3')
|
|
|
|
class ReadingTest(unittest.TestCase, AllFilesMixin):
|
|
def _read_field(self, mf, correct_dict, field):
|
|
got = getattr(mf, field)
|
|
correct = correct_dict[field]
|
|
message = field + ' incorrect (expected ' + repr(correct) + \
|
|
', got ' + repr(got) + ')'
|
|
if isinstance(correct, float):
|
|
self.assertAlmostEqual(got, correct, msg=message)
|
|
else:
|
|
self.assertEqual(got, correct, message)
|
|
|
|
def _run(self, tagset, kind):
|
|
correct_dict = CORRECT_DICTS[tagset]
|
|
path = os.path.join(_common.RSRC, tagset + '.' + kind)
|
|
f = beets.mediafile.MediaFile(path)
|
|
for field in correct_dict:
|
|
if 'm4a' in path and field.startswith('rg_'):
|
|
# MPEG-4 files: ReplayGain values not implemented.
|
|
continue
|
|
self._read_field(f, correct_dict, field)
|
|
|
|
# Special test for missing ID3 tag.
|
|
def test_empy_mp3(self):
|
|
self._run('empty', 'mp3')
|
|
|
|
class WritingTest(unittest.TestCase, AllFilesMixin):
|
|
def _write_field(self, tpath, field, value, correct_dict):
|
|
# Write new tag.
|
|
a = beets.mediafile.MediaFile(tpath)
|
|
setattr(a, field, value)
|
|
a.save()
|
|
|
|
# Verify ALL tags are correct with modification.
|
|
b = beets.mediafile.MediaFile(tpath)
|
|
for readfield in correct_dict.keys():
|
|
got = getattr(b, readfield)
|
|
|
|
# Make sure the modified field was changed correctly...
|
|
if readfield == field:
|
|
message = field + ' modified incorrectly (changed to ' + \
|
|
repr(value) + ' but read ' + repr(got) + ')'
|
|
if isinstance(value, float):
|
|
self.assertAlmostEqual(got, value, msg=message)
|
|
else:
|
|
self.assertEqual(got, value, message)
|
|
|
|
# ... and that no other field was changed.
|
|
else:
|
|
# MPEG-4: ReplayGain not implented.
|
|
if 'm4a' in tpath and readfield.startswith('rg_'):
|
|
continue
|
|
|
|
# The value should be what it was originally most of the
|
|
# time.
|
|
correct = correct_dict[readfield]
|
|
|
|
# The date field, however, is modified when its components
|
|
# change.
|
|
if readfield=='date' and field in ('year', 'month', 'day'):
|
|
try:
|
|
correct = datetime.date(
|
|
value if field=='year' else correct.year,
|
|
value if field=='month' else correct.month,
|
|
value if field=='day' else correct.day
|
|
)
|
|
except ValueError:
|
|
correct = datetime.date.min
|
|
# And vice-versa.
|
|
if field=='date' and readfield in ('year', 'month', 'day'):
|
|
correct = getattr(value, readfield)
|
|
|
|
message = readfield + ' changed when it should not have' \
|
|
' (expected ' + repr(correct) + ', got ' + \
|
|
repr(got) + ') when modifying ' + field
|
|
if isinstance(correct, float):
|
|
self.assertAlmostEqual(got, correct, msg=message)
|
|
else:
|
|
self.assertEqual(got, correct, message)
|
|
|
|
def _run(self, tagset, kind):
|
|
correct_dict = CORRECT_DICTS[tagset]
|
|
path = os.path.join(_common.RSRC, tagset + '.' + kind)
|
|
|
|
for field in correct_dict:
|
|
if field == 'month' and correct_dict['year'] == 0 or \
|
|
field == 'day' and correct_dict['month'] == 0:
|
|
continue
|
|
|
|
# Generate the new value we'll try storing.
|
|
if field == 'art':
|
|
value = 'xxx'
|
|
elif type(correct_dict[field]) is unicode:
|
|
value = u'TestValue: ' + field
|
|
elif type(correct_dict[field]) is int:
|
|
value = correct_dict[field] + 42
|
|
elif type(correct_dict[field]) is bool:
|
|
value = not correct_dict[field]
|
|
elif type(correct_dict[field]) is datetime.date:
|
|
value = correct_dict[field] + datetime.timedelta(42)
|
|
elif type(correct_dict[field]) is str:
|
|
value = 'TestValue-' + str(field)
|
|
elif type(correct_dict[field]) is float:
|
|
value = 9.87
|
|
else:
|
|
raise ValueError('unknown field type ' + \
|
|
str(type(correct_dict[field])))
|
|
|
|
# Make a copy of the file we'll work on.
|
|
root, ext = os.path.splitext(path)
|
|
tpath = root + '_test' + ext
|
|
shutil.copy(path, tpath)
|
|
|
|
try:
|
|
self._write_field(tpath, field, value, correct_dict)
|
|
finally:
|
|
os.remove(tpath)
|
|
|
|
class ReadOnlyTest(unittest.TestCase):
|
|
def _read_field(self, mf, field, value):
|
|
got = getattr(mf, field)
|
|
fail_msg = field + ' incorrect (expected ' + \
|
|
repr(value) + ', got ' + repr(got) + ')'
|
|
if field == 'length':
|
|
self.assertTrue(value-0.1 < got < value+0.1, fail_msg)
|
|
else:
|
|
self.assertEqual(got, value, fail_msg)
|
|
|
|
def _run(self, filename):
|
|
path = os.path.join(_common.RSRC, filename)
|
|
f = beets.mediafile.MediaFile(path)
|
|
correct_dict = READ_ONLY_CORRECT_DICTS[filename]
|
|
for field, value in correct_dict.items():
|
|
self._read_field(f, field, value)
|
|
|
|
def test_mp3(self):
|
|
self._run('full.mp3')
|
|
|
|
def test_m4a(self):
|
|
self._run('full.m4a')
|
|
|
|
def test_flac(self):
|
|
self._run('full.flac')
|
|
|
|
def test_ogg(self):
|
|
self._run('full.ogg')
|
|
|
|
def test_ape(self):
|
|
self._run('full.ape')
|
|
|
|
def test_wv(self):
|
|
self._run('full.wv')
|
|
|
|
def test_mpc(self):
|
|
self._run('full.mpc')
|
|
|
|
def suite():
|
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main(defaultTest='suite')
|