Merge branch 'master' into tidal

This commit is contained in:
Alok Saboo
2026-06-17 14:06:35 -04:00
committed by GitHub
19 changed files with 237 additions and 289 deletions

View File

@@ -37,7 +37,7 @@ from dataclasses import dataclass
from functools import cache, cached_property
from pathlib import Path
from tempfile import gettempdir, mkdtemp, mkstemp
from typing import TYPE_CHECKING, Any, ClassVar
from typing import TYPE_CHECKING, Any, ClassVar, Literal
from unittest.mock import Mock, patch
import pytest
@@ -59,7 +59,10 @@ from beets.util import (
)
if TYPE_CHECKING:
from types import TracebackType
from requests_mock.mocker import Mocker
from typing_extensions import Self
RUNNING_IN_CI = os.environ.get("GITHUB_ACTIONS") == "true"
@@ -149,12 +152,29 @@ class IOMixin(RunMixin):
class TestHelper(RunMixin, ConfigMixin):
"""Helper mixin for high-level cli and plugin tests.
This mixin provides methods to isolate beets' global state provide
fixtures.
This mixin provides methods to isolate beets' global state.
You may use it as a context manager in pytest fixtures in order to setup
tests at a class or module level. See ``module_helper`` and ``class_helper``
fixtures, for example.
"""
request: pytest.FixtureRequest
def __enter__(self) -> Self:
self.setup_beets()
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> Literal[False]:
self.teardown_beets()
# return False/None to propagate exceptions
return False
@pytest.fixture(autouse=True)
def setup(self, request: pytest.FixtureRequest):
self.request = request

View File

@@ -6,7 +6,7 @@ import pytest
from beets.autotag import Distance
from beets.dbcore.query import Query
from beets.test._common import DummyIO
from beets.test.helper import RUNNING_IN_CI, ConfigMixin
from beets.test.helper import RUNNING_IN_CI, ConfigMixin, TestHelper
from beets.test.helper import is_importable as check_import
from beets.util import cached_classproperty
@@ -129,3 +129,45 @@ def is_importable():
"""Fixture that provides a function to check if a module can be imported."""
return check_import
# Shared fixtures amortize the expensive TestHelper setup across multiple tests.
#
# TestHelper resets and reloads the beets configuration, so recreating it for
# every test can slow large suites noticeably.
#
# Inheriting from TestHelper gives each test function isolated state. Use the
# fixtures below instead when a broader scope is safe and the suite benefits
# from reusing the same helper instance.
@pytest.fixture(scope="session")
def session_helper():
"""Share beets test state across the full test session.
Use this for suites that tolerate shared library contents and global
configuration. Tests should target specific records rather than assume a
completely fresh overall state.
"""
with TestHelper() as helper:
yield helper
@pytest.fixture(scope="module")
def module_helper():
"""Share beets test state within one test module.
Use this when tests in the same file can reuse setup and side effects, but
later modules should still begin from a clean environment.
"""
with TestHelper() as helper:
yield helper
@pytest.fixture(scope="class")
def class_helper():
"""Share beets test state within one test class.
Use this when methods in a class can build on the same setup, while nearby
classes still need independent libraries, files, or configuration.
"""
with TestHelper() as helper:
yield helper

View File

@@ -8,14 +8,63 @@ from beets.dbcore import types
from beets.library import migrations
from beets.library.models import Album, Item
from beets.test.helper import TestHelper
from beets.util import path_as_posix
from beets.util import cached_classproperty, path_as_posix
Migration = tuple[
type[migrations.Migration], tuple[type[Item] | type[Album], ...]
]
class TestMultiGenreFieldMigration:
@pytest.fixture
def helper(self, monkeypatch):
class MigrationTestHelper(TestHelper):
"""Provide a shared harness for exercising one library migration at a time.
The helper builds a library that starts in a pre-migration state, then
re-enables the migration under test so each subclass can verify the data
transformation in isolation.
"""
migration: ClassVar[Migration]
@classmethod
def setup_previous_state(cls, monkeypatch: pytest.MonkeyPatch) -> None:
"""Shape the library into the legacy state expected by the migration.
Subclasses override this hook to create older schema or metadata
layouts before the test library is initialized.
"""
@pytest.fixture(autouse=True)
def setup(self, monkeypatch):
"""Initialize the test library around the migration under test.
The fixture first prevents automatic migrations so legacy test data can
be created safely, then restores the target migration and tears down the
temporary library after each test.
"""
# do not apply migrations upon library initialization
monkeypatch.setattr("beets.library.library.Library._migrations", ())
self.setup_previous_state(monkeypatch)
self.setup_beets()
# and now configure the migrations to be tested
monkeypatch.setattr(
"beets.library.library.Library._migrations", (self.migration,)
)
try:
yield
finally:
self.teardown_beets()
class TestMultiGenreFieldMigration(MigrationTestHelper):
"""Verify legacy single-value genres are promoted to multi-value fields."""
migration = (migrations.MultiGenreFieldMigration, (Item, Album))
@classmethod
def setup_previous_state(cls, monkeypatch: pytest.MonkeyPatch) -> None:
"""Expose the legacy genre columns required to exercise the migration."""
# add genre field to both models to make sure this column is created
monkeypatch.setattr(
"beets.library.models.Item._fields",
@@ -28,20 +77,10 @@ class TestMultiGenreFieldMigration:
monkeypatch.setattr(
"beets.library.models.Album.item_keys", {*Album.item_keys, "genre"}
)
helper = TestHelper()
helper.setup_beets()
# and now configure the migrations to be tested
monkeypatch.setattr(
"beets.library.library.Library._migrations",
((migrations.MultiGenreFieldMigration, (Item, Album)),),
)
yield helper
helper.teardown_beets()
def test_migrate(self, helper: TestHelper):
helper.config["lastgenre"]["separator"] = " - "
def test_migrate(self):
"""Ensure genre data is split once and preserved when already populated."""
self.config["lastgenre"]["separator"] = " - "
expected_item_genres = []
for genre, initial_genres, expected_genres in [
@@ -54,55 +93,51 @@ class TestMultiGenreFieldMigration:
# multiple genres are split the first (lastgenre) separator ONLY
("Item - Rock, Alternative", (), ("Item", "Rock, Alternative")),
]:
helper.add_item(genre=genre, genres=initial_genres)
self.add_item(genre=genre, genres=initial_genres)
expected_item_genres.append(expected_genres)
unmigrated_album = helper.add_album(
unmigrated_album = self.add_album(
genre="Album Rock / Alternative", genres=[]
)
expected_item_genres.append(("Album Rock", "Alternative"))
helper.lib._migrate()
self.lib._migrate()
actual_item_genres = [tuple(i.genres) for i in helper.lib.items()]
actual_item_genres = [tuple(i.genres) for i in self.lib.items()]
assert actual_item_genres == expected_item_genres
unmigrated_album.load()
assert unmigrated_album.genres == ["Album Rock", "Alternative"]
# remove cached initial db tables data
del helper.lib.db_tables
assert helper.lib.migration_exists("multi_genre_field", "items")
assert helper.lib.migration_exists("multi_genre_field", "albums")
del self.lib.db_tables
assert self.lib.migration_exists("multi_genre_field", "items")
assert self.lib.migration_exists("multi_genre_field", "albums")
class MultiArtistFieldMigrationTestMixin:
class MultiArtistFieldMigrationTestMixin(MigrationTestHelper):
"""Share coverage for migrations that split artist-like text into lists."""
str_field: ClassVar[str]
list_field: ClassVar[str]
migration_cls: ClassVar[type[migrations.MultiValueFieldMigration]]
@pytest.fixture
def helper(self, monkeypatch):
# do not apply migrations upon library initialization
monkeypatch.setattr("beets.library.library.Library._migrations", ())
@cached_classproperty
def migration(cls) -> Migration:
"""Bind each concrete test class to its corresponding migration."""
return (cls.migration_cls, (Item,))
@classmethod
def setup_previous_state(cls, monkeypatch: pytest.MonkeyPatch) -> None:
"""Expose the legacy scalar field expected by the migration."""
# add legacy str field to make sure this column is created
monkeypatch.setattr(
"beets.library.models.Item._fields",
{**Item._fields, self.str_field: types.STRING},
{**Item._fields, cls.str_field: types.STRING},
)
helper = TestHelper()
helper.setup_beets()
# and now configure the migrations to be tested
monkeypatch.setattr(
"beets.library.library.Library._migrations",
((self.migration_cls, (Item,)),),
)
yield helper
helper.teardown_beets()
def test_migrate(self, helper: TestHelper):
def test_migrate(self):
"""Ensure existing list values win and legacy text splits consistently."""
expected_list_values = []
for str_value, initial_list_value, expected_list_value in [
# already existing value is not overwritten
@@ -118,19 +153,19 @@ class MultiArtistFieldMigrationTestMixin:
self.str_field: str_value,
self.list_field: initial_list_value,
}
helper.add_item(**data)
self.add_item(**data)
expected_list_values.append(expected_list_value)
helper.lib._migrate()
self.lib._migrate()
actual_list_values = [
tuple(i[self.list_field]) for i in helper.lib.items()
tuple(i[self.list_field]) for i in self.lib.items()
]
assert actual_list_values == expected_list_values
# remove cached initial db tables data
del helper.lib.db_tables
assert helper.lib.migration_exists(
del self.lib.db_tables
assert self.lib.migration_exists(
f"multi_{self.str_field}_field", "items"
)
@@ -159,26 +194,14 @@ class TestMultiArrangerFieldMigration(MultiArtistFieldMigrationTestMixin):
migration_cls = migrations.MultiArrangerFieldMigration
class TestLyricsMetadataInFlexFieldsMigration:
@pytest.fixture
def helper(self, monkeypatch):
# do not apply migrations upon library initialization
monkeypatch.setattr("beets.library.library.Library._migrations", ())
class TestLyricsMetadataInFlexFieldsMigration(MigrationTestHelper):
"""Verify embedded lyrics metadata moves into dedicated flexible fields."""
helper = TestHelper()
helper.setup_beets()
migration = (migrations.LyricsMetadataInFlexFieldsMigration, (Item,))
# and now configure the migrations to be tested
monkeypatch.setattr(
"beets.library.library.Library._migrations",
((migrations.LyricsMetadataInFlexFieldsMigration, (Item,)),),
)
yield helper
helper.teardown_beets()
def test_migrate(self, helper: TestHelper, is_importable):
lyrics_item = helper.add_item(
def test_migrate(self, is_importable):
"""Ensure extracted metadata is preserved without mutating plain lyrics."""
lyrics_item = self.add_item(
lyrics=textwrap.dedent("""
[00:00.00] Some synced lyrics / Quelques paroles synchronisées
[00:00.50]
@@ -186,9 +209,9 @@ class TestLyricsMetadataInFlexFieldsMigration:
Source: https://lrclib.net/api/1/""")
)
instrumental_lyrics_item = helper.add_item(lyrics="[Instrumental]")
instrumental_lyrics_item = self.add_item(lyrics="[Instrumental]")
helper.lib._migrate()
self.lib._migrate()
lyrics_item.load()
@@ -220,37 +243,25 @@ class TestLyricsMetadataInFlexFieldsMigration:
instrumental_lyrics_item.lyrics_translation_language
# remove cached initial db tables data
del helper.lib.db_tables
assert helper.lib.migration_exists(
del self.lib.db_tables
assert self.lib.migration_exists(
"lyrics_metadata_in_flex_fields", "items"
)
class TestRelativePathMigration:
@pytest.fixture
def helper(self, monkeypatch):
# do not apply migrations upon library initialization
monkeypatch.setattr("beets.library.library.Library._migrations", ())
class TestRelativePathMigration(MigrationTestHelper):
"""Verify stored item paths are rewritten to library-relative values."""
helper = TestHelper()
helper.setup_beets()
migration = (migrations.RelativePathMigration, (Item,))
# and now configure the migrations to be tested
monkeypatch.setattr(
"beets.library.library.Library._migrations",
((migrations.RelativePathMigration, (Item,)),),
)
yield helper
helper.teardown_beets()
def test_migrate(self, helper: TestHelper):
def test_migrate(self):
"""Ensure stored paths become relative while the public API stays stable."""
relative_path = os.path.join("foo", "bar", "baz.mp3")
abs_string_path = str(helper.lib_path / relative_path)
abs_string_path = str(self.lib_path / relative_path)
abs_bytes_path = os.fsencode(abs_string_path)
# need to insert the path directly into the database to bypass the path setter
helper.lib._connection().executemany(
self.lib._connection().executemany(
"INSERT INTO items (id, path) VALUES (?, ?)",
(
(1, abs_bytes_path),
@@ -259,20 +270,20 @@ class TestRelativePathMigration:
),
)
old_stored_path = (
helper.lib._connection()
self.lib._connection()
.execute("select path from items where id=?", (1,))
.fetchone()[0]
)
assert old_stored_path == abs_bytes_path
helper.lib._migrate()
self.lib._migrate()
item = helper.lib.get_item(1)
item = self.lib.get_item(1)
assert item
# and now we have a relative path
stored_path = (
helper.lib._connection()
self.lib._connection()
.execute("select path from items where id=?", (item.id,))
.fetchone()[0]
)
@@ -281,6 +292,6 @@ class TestRelativePathMigration:
assert item.path == abs_bytes_path
# also check the string path was migrated correctly
str_item = helper.lib.get_item(2)
str_item = self.lib.get_item(2)
assert str_item
assert str_item.path == abs_bytes_path

View File

@@ -19,8 +19,6 @@ import pytest
from beets.test.helper import PluginTestCase
from beets.ui import UserError
PLUGIN_NAME = "advancedrewrite"
class AdvancedRewritePluginTest(PluginTestCase):
plugin = "advancedrewrite"

View File

@@ -7,21 +7,23 @@ from typing import TYPE_CHECKING, Any
import pytest
from beets.test.helper import TestHelper
if TYPE_CHECKING:
from flask.testing import Client
@pytest.fixture(scope="session", autouse=True)
def helper():
helper = TestHelper()
helper.setup_beets()
yield helper
helper.teardown_beets()
@pytest.fixture(scope="session")
def helper(session_helper):
"""Keep the helper temp dir alive past the module-scoped Flask app.
``create_app`` opens a configured SQLite library before tests replace it
with ``helper.lib``. On Windows, module teardown can otherwise try to
delete ``library.db`` while the app still holds a file handle. The API
assertions filter known data, so sharing state for the session is harmless.
"""
return session_helper
@pytest.fixture(scope="module")
def app(helper):
from beetsplug.aura import create_app
@@ -30,7 +32,7 @@ def app(helper):
return app
@pytest.fixture(scope="session")
@pytest.fixture(scope="module")
def item(helper):
return helper.add_item_fixture(
album="Album",
@@ -40,12 +42,12 @@ def item(helper):
)
@pytest.fixture(scope="session")
@pytest.fixture(scope="module")
def album(helper, item):
return helper.lib.add_album([item])
@pytest.fixture(scope="session", autouse=True)
@pytest.fixture(scope="module", autouse=True)
def _other_album_and_item(helper):
"""Add another item and album to prove that filtering works."""
item = helper.add_item_fixture(

View File

@@ -124,7 +124,6 @@ class EditCommandTest(IOMixin, EditMixin, BeetsTestCase):
simulated using `ModifyFileMocker`.
"""
ALBUM_COUNT = 1
TRACK_COUNT = 10
def setUp(self):

View File

@@ -33,7 +33,7 @@ from beets.test.helper import (
FetchImageHelper,
ImportHelper,
IOMixin,
PluginTestHelper,
PluginMixin,
)
from beets.util import bytestring_path, displayable_path, syspath
from beets.util.artresizer import ArtResizer
@@ -78,18 +78,7 @@ def require_artresizer_compare(test):
return wrapper
class PytestImportHelper(PluginTestHelper, ImportHelper):
@pytest.fixture(autouse=True)
def setup_import_helper(self, setup):
self.import_media = []
self.lib.path_formats = [
("default", os.path.join("$artist", "$album", "$title")),
("singleton:true", os.path.join("singletons", "$title")),
("comp:true", os.path.join("compilations", "$album", "$title")),
]
class TestEmbedartCli(PytestImportHelper, IOMixin, FetchImageHelper):
class TestEmbedartCli(PluginMixin, IOMixin, ImportHelper, FetchImageHelper):
plugin = "embedart"
small_artpath = os.path.join(_common.RSRC, b"image-2x3.jpg")
abbey_artpath = os.path.join(_common.RSRC, b"abbey.jpg")

View File

@@ -15,24 +15,10 @@
import pytest
from beets.test.helper import PluginMixin, TestHelper
from beets.test.helper import PluginTestHelper
@pytest.fixture
def helper(request):
helper = TestHelper()
helper.setup_beets()
request.instance.lib = helper.lib
request.instance.add_item = helper.add_item
yield
helper.teardown_beets()
@pytest.mark.usefixtures("helper")
class TestFuzzyPlugin(PluginMixin):
class TestFuzzyPlugin(PluginTestHelper):
plugin = "fuzzy"
@pytest.mark.parametrize(

View File

@@ -27,7 +27,7 @@ import pytest
import requests
from beets.library import Item
from beets.test.helper import PluginMixin, TestHelper
from beets.test.helper import PluginMixin
from beets.util.lyrics import Lyrics
from beetsplug import lyrics
@@ -46,11 +46,13 @@ PHRASE_BY_TITLE = {
@pytest.fixture(scope="module")
def helper():
helper = TestHelper()
helper.setup_beets()
yield helper
helper.teardown_beets()
def helper(module_helper):
"""Reuse one module helper for explicit item and media-write checks.
Helper-backed tests mutate known items and write an MP3 fixture, but they do
not assert against the full library shared with other tests in this module.
"""
return module_helper
class TestLyricsUtils:

View File

@@ -8,27 +8,16 @@ import pytest
from beets.autotag import AlbumInfo, TrackInfo
from beets.library import Album, Item
from beets.test.helper import IOMixin, PluginMixin, TestHelper
from beets.test.helper import IOMixin, PluginTestHelper
@pytest.fixture
def helper(request):
helper = TestHelper()
helper.setup_beets()
request.instance.lib = helper.lib
yield
helper.teardown_beets()
@pytest.mark.usefixtures("helper")
class TestMissingAlbums(IOMixin, PluginMixin):
"""Tests for missing albums functionality."""
class MissingTestHelper(IOMixin, PluginTestHelper):
plugin = "missing"
class TestMissingAlbums(MissingTestHelper):
"""Tests for missing albums functionality."""
@pytest.mark.parametrize(
"release_from_mb,expected_output",
[
@@ -176,12 +165,9 @@ class TestMissingAlbums(IOMixin, PluginMixin):
assert output == "1\n"
@pytest.mark.usefixtures("helper")
class TestMissingTracks(IOMixin, PluginMixin):
class TestMissingTracks(MissingTestHelper):
"""Tests for missing tracks functionality."""
plugin = "missing"
@pytest.mark.parametrize(
"total,count,expected",
[(True, False, "1\n"), (False, True, "artist - album: 1")],

View File

@@ -51,10 +51,6 @@ def label_info_factory(**kwargs) -> mb.LabelInfo:
return factories.LabelInfoFactory.build(**kwargs)
def release_group_factory(**kwargs) -> mb.ReleaseGroup:
return factories.ReleaseGroupFactory.build(**kwargs)
def recording_factory(**kwargs) -> mb.Recording:
return factories.RecordingFactory.build(**kwargs)

View File

@@ -19,18 +19,17 @@ import random
import pytest
from beets.test.helper import TestHelper
from beetsplug.random import _equal_chance_permutation, random_objs
@pytest.fixture(scope="class")
def helper():
helper = TestHelper()
helper.setup_beets()
def helper(class_helper):
"""Use class scope because each random test class owns its item list.
yield helper
helper.teardown_beets()
The helper creates lightweight item objects for class setup; tests do not
need a fresh beets temp dir per method or shared state between classes.
"""
return class_helper
@pytest.fixture(scope="module", autouse=True)

View File

@@ -1,7 +1,6 @@
"""Tests for the 'subsonic' plugin."""
import unittest
from urllib.parse import parse_qs, urlparse
import responses
@@ -9,21 +8,6 @@ from beets import config
from beetsplug import subsonicupdate
class ArgumentsMock:
"""Argument mocks for tests."""
def __init__(self, mode, show_failures):
"""Constructs ArgumentsMock."""
self.mode = mode
self.show_failures = show_failures
self.verbose = 1
def _params(url):
"""Get the query parameters from a URL."""
return parse_qs(urlparse(url).query)
class SubsonicPluginTest(unittest.TestCase):
"""Test class for subsonicupdate."""

View File

@@ -22,38 +22,6 @@ from beets.library import Item
from beets.test.helper import PluginTestCase
from beetsplug.titlecase import TitlecasePlugin
titlecase_fields_testcases = [
(
{
"fields": [
"artist",
"albumartist",
"title",
"album",
"mb_albumd",
"year",
],
"force_lowercase": True,
},
Item(
artist="OPHIDIAN",
albumartist="ophiDIAN",
format="CD",
year=2003,
album="BLACKBOX",
title="KhAmElEoN",
),
Item(
artist="Ophidian",
albumartist="Ophidian",
format="CD",
year=2003,
album="Blackbox",
title="Khameleon",
),
)
]
class TestTitlecasePlugin(PluginTestCase):
plugin = "titlecase"

View File

@@ -12,22 +12,13 @@ from beetsplug._utils.playcount import (
LOGGER_NAME = "beets.test_playcount"
@pytest.fixture
def helper(request):
helper = TestHelper()
helper.setup_beets()
class TestPlayCount(TestHelper):
@pytest.fixture
def log(self):
log = logging.getLogger(LOGGER_NAME)
log.set_global_level(logging.DEBUG)
return log
request.instance.lib = helper.lib
request.instance.log = logging.getLogger(LOGGER_NAME)
request.instance.log.set_global_level(logging.DEBUG)
yield helper
helper.teardown_beets()
@pytest.mark.usefixtures("helper")
class TestPlayCount:
def track(self, **overrides):
return {
"mbid": "",
@@ -96,19 +87,17 @@ class TestPlayCount:
],
)
def test_get_items_matches_supported_query_paths(
self, item_kwargs, track_kwargs
self, log, item_kwargs, track_kwargs
):
item = self.add_item(**item_kwargs)
matched_ids = [
matched.id
for matched in get_items(
self.lib, self.track(**track_kwargs), self.log
)
for matched in get_items(self.lib, self.track(**track_kwargs), log)
]
assert matched_ids == [item.id]
def test_process_track_updates_every_matching_song(self):
def test_process_track_updates_every_matching_song(self, log):
first = self.add_item(
title="Song", artist="Artist", album="First Album", play_count=1
)
@@ -117,14 +106,14 @@ class TestPlayCount:
)
assert (
process_track(self.lib, self.track(playcount=0), self.log, "lastfm")
process_track(self.lib, self.track(playcount=0), log, "lastfm")
is True
)
assert self.get_playcount(first.id) == 0
assert self.get_playcount(second.id) == 0
def test_process_track_returns_false_when_nothing_matches(self):
def test_process_track_returns_false_when_nothing_matches(self, log):
assert (
process_track(
self.lib,
@@ -134,22 +123,19 @@ class TestPlayCount:
album="Missing Album",
playcount=4,
),
self.log,
log,
"lastfm",
)
is False
)
def test_process_track_updates_requested_source_field(self):
def test_process_track_updates_requested_source_field(self, log):
new_count = 6
item = self.add_item(play_count=1, source="lastfm")
assert (
process_track(
self.lib,
self.track(playcount=new_count),
self.log,
"listenbrainz",
self.lib, self.track(playcount=new_count), log, "listenbrainz"
)
is True
)
@@ -179,6 +165,7 @@ class TestPlayCount:
)
def test_update_play_counts_counts_and_logs_summary(
self,
log,
tracks,
expected_counts,
expected_summary,
@@ -192,7 +179,7 @@ class TestPlayCount:
update_play_counts(
self.lib,
[self.track(**track) for track in tracks],
self.log,
log,
"lastfm",
)
== expected_counts

View File

@@ -40,20 +40,12 @@ class DummyIMBackend(IMBackend):
self.compare_cmd = ["magick", "compare"]
class DummyPILBackend(PILBackend):
"""An `PILBackend` which pretends that PIL is available."""
def __init__(self):
"""Init a dummy backend class for mocked PIL tests."""
class ArtResizerFileSizeTest(CleanupModulesMixin, BeetsTestCase):
"""Unittest test case for Art Resizer to a specific filesize."""
modules = (IMBackend.__module__,)
IMG_225x225 = os.path.join(_common.RSRC, b"abbey.jpg")
IMG_225x225_SIZE = os.stat(syspath(IMG_225x225)).st_size
def _test_img_resize(self, backend):
"""Test resizing based on file size, given a resize_func."""

View File

@@ -147,10 +147,6 @@ class ModelFixture5(ModelFixture1):
}
class DatabaseFixture5(dbcore.Database):
_models = (ModelFixture5,)
class DatabaseFixtureTwoModels(dbcore.Database):
_models = (ModelFixture2, AnotherModelFixture)

View File

@@ -98,21 +98,13 @@ class TestPluginRegistration(IOMixin, PluginTestHelper):
assert out == "one; two; three\n"
class PytestImportHelper(ImportHelper, PluginTestHelper):
@pytest.fixture(autouse=True)
def setup_import_helper(self, setup):
self.import_media = []
self.lib.path_formats = [
("default", os.path.join("$artist", "$album", "$title")),
("singleton:true", os.path.join("singletons", "$title")),
("comp:true", os.path.join("compilations", "$album", "$title")),
]
#
class PluginImportHelper(PluginMixin, ImportHelper):
def setup_beets(self):
super().setup_beets()
self.prepare_album_for_import(2)
class TestEvents(PytestImportHelper):
class TestEvents(PluginImportHelper):
def test_import_task_created(self, caplog):
self.importer = self.setup_importer(pretend=True)
@@ -282,7 +274,7 @@ class TestListeners(PluginTestHelper):
plugins.send("event9", foo=5)
class TestPromptChoices(TerminalImportMixin, PytestImportHelper):
class TestPromptChoices(TerminalImportMixin, PluginImportHelper):
@pytest.fixture(autouse=True)
def setup_prompt_choice(self, io):
self.setup_importer()

View File

@@ -44,7 +44,6 @@ from beets.dbcore.query import (
)
from beets.library import Item
from beets.test import _common
from beets.test.helper import TestHelper
# Because the absolute path begins with something like C:, we
# can't disambiguate it from an ordinary query.
@@ -54,13 +53,13 @@ _p = pytest.param
@pytest.fixture(scope="class")
def helper():
helper = TestHelper()
helper.setup_beets()
def helper(class_helper):
"""Use class scope because each query class owns a library shape.
yield helper
helper.teardown_beets()
Class fixtures add rows, albums, flex types, paths, or media files and then
query whole collections, so sibling class data would change expectations.
"""
return class_helper
class TestGet: