emmental/tests/db/test_tagger.py

412 lines
17 KiB
Python

# Copyright 2022 (c) Anna Schumaker
"""Tests our Mutagen wrapper."""
import pathlib
import unittest.mock
import emmental.db.tagger
import emmental.thread
import tests.util
class TestTags(tests.util.TestCase):
"""Test the Tags object."""
def setUp(self):
"""Set up common variables."""
super().setUp()
for tbl in self.sql.playlist_tables():
tbl.queue.enabled = False
tbl.load()
self.library = self.sql.libraries.create("/a/b")
self.file = pathlib.Path("/a/b/c/track.ogg")
def make_tags(self, raw_tags: dict, *, length: int = 0,
mtime: float = 0.0) -> emmental.db.tagger.Tags:
"""Set up and return our Tags object."""
audio_tags = emmental.audio.tagger._Tags(self.file, raw_tags,
length, mtime)
return emmental.db.tagger.Tags(self.sql, audio_tags, self.library)
def test_init(self):
"""Test that the Tags object was set up properly."""
tags = self.make_tags({})
self.assertEqual(tags.db, self.sql)
self.assertIsNone(tags.album)
self.assertIsNone(tags.decade)
self.assertIsNone(tags.medium)
self.assertIsNone(tags.year)
self.assertIsNone(tags.track)
self.assertListEqual(tags.album_artists, [])
self.assertListEqual(tags.artists, [])
self.assertListEqual(tags.genres, [])
def test_album(self):
"""Test that the album was tagged properly."""
raw_tags = {"album": ["Album Name"],
"musicbrainz_releasegroupid": ["ab-cd-ef"],
"albumartist": ["Album Artist"],
"date": ["1988-06"]}
with unittest.mock.patch.object(pathlib.Path, "is_file",
return_value=True):
tags = self.make_tags(raw_tags)
self.assertIsInstance(tags.album, emmental.db.albums.Album)
self.assertEqual(tags.album.name, "Album Name")
self.assertEqual(tags.album.mbid, "ab-cd-ef")
self.assertEqual(tags.album.artist, "Album Artist")
self.assertEqual(tags.album.release, "1988-06")
self.assertEqual(tags.album.cover, pathlib.Path("/a/b/c/cover.jpg"))
self.assertEqual(self.make_tags(raw_tags).album, tags.album)
self.assertIsNone(tags.album.cover)
def test_album_artists(self):
"""Test that album artists were tagged and updated properly."""
raw_tags = {"album": ["Album Name"],
"musicbrainz_releasegroupid": ["ab-cd-ef"],
"albumartist": ["Artist 1", "Artist 2"],
"musicbrainz_albumartistid": ["gh-ij-kl", "mn-op-qr"],
"date": ["1988-06"]}
tags = self.make_tags(raw_tags)
self.assertEqual(len(tags.album_artists), 2)
for i, artist in enumerate(tags.album_artists):
with self.subTest(i=i):
self.assertIsInstance(artist, emmental.db.artists.Artist)
self.assertEqual(artist.name, f"Artist {i+1}")
self.assertEqual(artist.mbid,
"gh-ij-kl" if i == 0 else "mn-op-qr")
self.assertListEqual(tags.album.get_artists(), tags.album_artists)
self.assertListEqual(self.make_tags(raw_tags).album_artists,
tags.album_artists)
raw_tags["albumartist"] = ["Artist 1", "Artist 3"]
raw_tags["musicbrainz_albumartistid"] = ["gh-ij-kl", "st-uv-wx"]
updated = self.make_tags(raw_tags)
self.assertEqual(updated.album, tags.album)
self.assertListEqual(tags.album.get_artists(), updated.album_artists)
def test_artists(self):
"""Test that artists were tagged properly."""
raw_tags = {"artists": ["Artist 1", "Artist 2"],
"albumartist": ["Artist 3"]}
tags = self.make_tags(raw_tags)
self.assertEqual(len(tags.artists), 3)
for i, artist in enumerate(tags.artists):
with self.subTest(i=i):
self.assertIsInstance(artist, emmental.db.artists.Artist)
self.assertEqual(artist.name, f"Artist {i+1}")
self.assertListEqual(self.make_tags(raw_tags).artists, tags.artists)
def test_decade(self):
"""Test that the decade was tagged properly."""
raw_tags = {"date": ["1988-06-17"]}
tags = self.make_tags(raw_tags)
self.assertIsInstance(tags.decade, emmental.db.decades.Decade)
self.assertEqual(tags.decade.decade, 1980)
self.assertEqual(self.make_tags(raw_tags).decade, tags.decade)
def test_genres(self):
"""Test that genres were tagged properly."""
self.sql.genres.autodelete = False
raw_tags = {"genre": ["Genre 1", "Genre 2"]}
tags = self.make_tags(raw_tags)
self.assertEqual(len(tags.genres), 2)
for i, genre in enumerate(tags.genres):
with self.subTest(i=i):
self.assertIsInstance(genre, emmental.db.genres.Genre)
self.assertEqual(genre.name, f"Genre {i+1}")
self.assertListEqual(self.make_tags(raw_tags).genres, tags.genres)
def test_medium(self):
"""Test that the medium was tagged properly."""
raw_tags = {"album": ["Album Name"],
"musicbrainz_releasegroupid": ["ab-cd-ef"],
"albumartist": ["Album Artist"],
"date": ["1988-06"],
"discnumber": ["2"],
"discsubtitle": ["Subtitle"],
"media": ["CD"]}
tags = self.make_tags(raw_tags)
self.assertIsInstance(tags.medium, emmental.db.media.Medium)
self.assertEqual(tags.medium.number, 2)
self.assertEqual(tags.medium.name, "Subtitle")
self.assertEqual(tags.medium.type, "CD")
raw_tags["discsubtitle"] = ["New Subtitle"]
self.assertEqual(self.make_tags(raw_tags).medium, tags.medium)
self.assertEqual(tags.medium.name, "New Subtitle")
def test_track(self):
"""Test that the Track was tagged properly."""
raw_tags = {"album": ["Album Name"],
"artist": ["Track Artist"],
"date": ["1988-06"],
"title": ["Test Title"],
"tracknumber": ["3"],
"musicbrainz_releasetrackid": ["ab-cd-ef"]}
tags = self.make_tags(raw_tags, length=42, mtime=1.234)
self.assertIsInstance(tags.track, emmental.db.tracks.Track)
self.assertEqual(tags.track.get_library(), self.library)
self.assertEqual(tags.track.get_medium(), tags.medium)
self.assertEqual(tags.track.get_year(), tags.year)
self.assertEqual(tags.track.path, self.file)
self.assertEqual(tags.track.title, "Test Title")
self.assertEqual(tags.track.artist, "Track Artist")
self.assertEqual(tags.track.mbid, "ab-cd-ef")
self.assertEqual(tags.track.number, 3)
self.assertEqual(tags.track.length, 42)
self.assertEqual(tags.track.mtime, 1.234)
self.assertTrue(self.sql.playlists.collection.has_track(tags.track))
self.assertTrue(self.sql.playlists.new_tracks.has_track(tags.track))
self.assertTrue(self.sql.playlists.unplayed.has_track(tags.track))
self.assertTrue(self.library.has_track(tags.track))
raw_tags["artist"] = ["New Artist"]
raw_tags["date"] = ["1985-08"]
raw_tags["discnumber"] = ["2"]
raw_tags["title"] = ["New Title"]
raw_tags["tracknumber"] = ["4"]
raw_tags["musicbrainz_releasetrackid"] = ["gh-ij-kl"]
for playlist in self.sql.playlists:
playlist.tracks.trackids = set()
new_tags = self.make_tags(raw_tags, length=53, mtime=5.6789)
self.assertEqual(new_tags.track, tags.track)
self.assertEqual(new_tags.track.get_medium(), new_tags.medium)
self.assertEqual(new_tags.track.get_year(), new_tags.year)
self.assertEqual(new_tags.track.title, "New Title")
self.assertEqual(new_tags.track.artist, "New Artist")
self.assertEqual(new_tags.track.mbid, "gh-ij-kl")
self.assertEqual(new_tags.track.number, 4)
self.assertEqual(new_tags.track.length, 53.0)
self.assertEqual(new_tags.track.mtime, 5.6789)
for playlist in self.sql.playlists:
with self.subTest(playlist=playlist.name):
self.assertFalse(playlist.has_track(new_tags.track))
def test_track_playlist_update(self):
"""Test updating Track Playlists."""
album = self.sql.albums.create("Album Name", "Artist 2", "1988-06")
medium = self.sql.media.create(album, "", number=1)
decade = self.sql.decades.create(1980)
year = self.sql.years.create(1988)
raw_tags = {"album": ["Album Name"],
"artist": ["Artist 2"],
"artists": ["Artist 1", "Artist 2"],
"date": ["1988-06"],
"discnumber": ["1"],
"genre": ["Genre 1", "Genre 2"]}
tags = self.make_tags(raw_tags)
self.assertListEqual(tags.track.get_artists(), tags.artists)
self.assertListEqual(tags.track.get_genres(), tags.genres)
self.assertTrue(album.has_track(tags.track))
self.assertTrue(medium.has_track(tags.track))
self.assertTrue(decade.has_track(tags.track))
self.assertTrue(year.has_track(tags.track))
new_album = self.sql.albums.create("New Album Name", "Artist 2",
"1992-10")
new_medium = self.sql.media.create(new_album, "", number=2)
new_decade = self.sql.decades.create(1990)
new_year = self.sql.years.create(1992)
raw_tags["album"] = ["New Album Name"]
raw_tags["artists"] = ["Artist 2", "Artist 3"]
raw_tags["date"] = ["1992-10"]
raw_tags["discnumber"] = ["2"]
raw_tags["genre"] = ["Genre 2", "Genre 3"]
new_tags = self.make_tags(raw_tags)
self.assertEqual(new_tags.track, tags.track)
self.assertListEqual(tags.track.get_artists(), new_tags.artists)
self.assertListEqual(tags.track.get_genres(), new_tags.genres)
self.assertFalse(album.has_track(tags.track))
self.assertFalse(medium.has_track(tags.track))
self.assertFalse(decade.has_track(tags.track))
self.assertFalse(year.has_track(tags.track))
self.assertTrue(new_album.has_track(tags.track))
self.assertTrue(new_medium.has_track(tags.track))
self.assertTrue(new_decade.has_track(tags.track))
self.assertTrue(new_year.has_track(tags.track))
def test_year(self):
"""Test that the year was tagged properly."""
raw_tags = {"date": ["1988-06-17"]}
tags = self.make_tags(raw_tags)
self.assertIsInstance(tags.year, emmental.db.years.Year)
self.assertEqual(tags.year.year, 1988)
self.assertEqual(self.make_tags(raw_tags).year, tags.year)
@unittest.mock.patch("emmental.audio.tagger.tag_file")
class TestTaggerThread(tests.util.TestCase):
"""Test the tagger thread behavior."""
def setUp(self):
"""Set up common variables."""
super().setUp()
self.library = self.sql.libraries.create("/a/b")
self.tagger = emmental.db.tagger.Thread()
self.tags = dict()
def tearDown(self):
"""Clean up."""
super().tearDown()
self.tagger.stop()
def make_tags(self, tags: dict) -> emmental.db.tagger.Tags:
"""Set up and return our Tags object."""
return emmental.audio.tagger._Tags(pathlib.Path("/a/b/c.ogg"), tags)
def test_init(self, mock_file: unittest.mock.Mock):
"""Test that the tagger thread was initialized properly."""
self.assertIsInstance(self.tagger, emmental.thread.Thread)
self.assertIsNone(self.tagger._connection)
self.assertTrue(self.tagger.is_alive())
def test_stop(self, mock_file: unittest.mock.Mock):
"""Test the stop function."""
mock_connection = unittest.mock.Mock()
mock_connection.close = unittest.mock.Mock()
self.tagger._connection = mock_connection
self.tagger.stop()
self.assertIsNone(self.tagger._connection)
mock_connection.close.assert_called()
def test_tag_file(self, mock_file: unittest.mock.Mock):
"""Test asking the thread to tag a file."""
path = pathlib.Path("/a/b/c.ogg")
mock_file.return_value = None
self.tagger.ready.set()
self.tagger.tag_file(path, mtime=None)
self.assertEqual(self.tagger._task, {"path": path, "mtime": None})
self.tagger.ready.wait()
mock_file.assert_called_with(pathlib.Path("/a/b/c.ogg"), None)
mock_file.return_value = self.make_tags(dict())
self.tagger.tag_file(path, mtime=12345)
self.assertEqual(self.tagger._task, {"path": path, "mtime": 12345})
self.tagger.ready.wait()
mock_file.assert_called_with(path, 12345)
def test_get_result(self, mock_file: unittest.mock.Mock):
"""Test creating a Tags structure after tagging."""
mock_file.return_value = None
self.assertIsNone(self.tagger.get_result(db=self.sql,
library=self.library))
track_path = pathlib.Path("/a/b/c.ogg")
self.tagger.tag_file(track_path, mtime=None)
self.tagger.ready.wait()
self.assertTupleEqual(self.tagger.get_result(db=self.sql,
library=self.library),
(track_path, None))
mock_file.return_value = self.make_tags(dict())
self.tagger.tag_file(track_path, mtime=None)
self.tagger.ready.wait()
res = self.tagger.get_result(db=self.sql, library=self.library)
self.assertTupleEqual(res, (track_path, res[1]))
@unittest.mock.patch("emmental.db.connection.Connection.__call__")
@unittest.mock.patch("musicbrainzngs.get_artist_by_id")
def test_tag_file_lookup_mbid(self, mock_get_artist: unittest.mock.Mock,
mock_connection: unittest.mock.Mock,
mock_file: unittest.mock.Mock):
"""Test looking up artists with an MBID but no name after tagging."""
audio_tags = self.make_tags({"albumartist": ["No Artist"],
"musicbrainz_albumartistid":
["ab-cd-ef", "gh-ij-kl"]})
mock_file.return_value = audio_tags
mock_get_artist.return_value = {"artist": {"name": "Some Artist"}}
mock_cursor = unittest.mock.Mock()
mock_cursor.fetchone = unittest.mock.Mock(return_value=None)
mock_connection.return_value = mock_cursor
self.tagger.tag_file(pathlib.Path("/a/b/c.ogg"), mtime=None)
self.tagger.ready.wait()
self.assertEqual(audio_tags.artists[0].name, "Some Artist")
self.assertEqual(audio_tags.artists[1].name, "Some Artist")
@unittest.mock.patch("emmental.db.connection.Connection.__call__")
@unittest.mock.patch("musicbrainzngs.get_artist_by_id")
def test_tag_file_lookup_sql(self, mock_get_artist: unittest.mock.Mock,
mock_connection: unittest.mock.Mock,
mock_file: unittest.mock.Mock):
"""Test looking up unnamed artists in the database."""
audio_tags = self.make_tags({"albumartist": ["No Artist"],
"musicbrainz_albumartistid":
["ab-cd-ef", "gh-ij-kl"]})
mock_file.return_value = audio_tags
mock_get_artist.return_value = {"artist": {"name": None}}
mock_row = {"name": "Some Artist"}
mock_cursor = unittest.mock.Mock()
mock_cursor.fetchone = unittest.mock.Mock(return_value=mock_row)
mock_connection.return_value = mock_cursor
self.assertIsNone(self.tagger._connection)
self.tagger.tag_file(pathlib.Path("/a/b/c.ogg"), mtime=None)
self.tagger.ready.wait()
self.assertIsInstance(self.tagger._connection,
emmental.db.connection.Connection)
self.assertEqual(audio_tags.artists[0].name, "Some Artist")
self.assertEqual(audio_tags.artists[1].name, "Some Artist")
class TestUntag(tests.util.TestCase):
"""Test untagging a Track."""
def setUp(self):
"""Set up common variables."""
super().setUp()
self.sql.playlists.load(now=True)
self.file = pathlib.Path("/a/b/c.ogg")
self.library = self.sql.libraries.create(self.file.parent)
self.raw_tags = {"album": ["Album Name"],
"artist": ["Track Artist"],
"date": ["1988-06"],
"title": ["Test Title"],
"tracknumber": ["3"],
"musicbrainz_releasetrackid": ["ab-cd-ef"]}
self.audio_tags = emmental.audio.tagger._Tags(self.file, self.raw_tags,
12345, 678.9)
self.tagged = emmental.db.tagger.Tags(self.sql, self.audio_tags,
self.library)
for tbl in self.sql.playlist_tables():
tbl.queue.complete()
def test_untag_track(self):
"""Test untagging a Track before deleting."""
emmental.db.tagger.untag_track(self.sql, self.tagged.track)
for tbl in self.sql.playlist_tables():
for plist in tbl.store:
with self.subTest(table=tbl, playlist=plist.name):
self.assertFalse(plist.has_track(self.tagged.track))