diff --git a/beets/test/_common.py b/beets/test/_common.py index 2be33de95..790ea9422 100644 --- a/beets/test/_common.py +++ b/beets/test/_common.py @@ -15,7 +15,6 @@ """Some common functionality for beets' test cases.""" import os -import shutil import sys import tempfile import unittest @@ -185,70 +184,6 @@ class Assertions: ) -# A test harness for all beets tests. -# Provides temporary, isolated configuration. -class TestCase(unittest.TestCase, Assertions): - """A unittest.TestCase subclass that saves and restores beets' - global configuration. This allows tests to make temporary - modifications that will then be automatically removed when the test - completes. Also provides some additional assertion methods, a - temporary directory, and a DummyIO. - """ - - def setUp(self): - # A "clean" source list including only the defaults. - beets.config.sources = [] - beets.config.read(user=False, defaults=True) - - # Direct paths to a temporary directory. Tests can also use this - # temporary directory. - self.temp_dir = util.bytestring_path(tempfile.mkdtemp()) - - beets.config["statefile"] = os.fsdecode( - os.path.join(self.temp_dir, b"state.pickle") - ) - beets.config["library"] = os.fsdecode( - os.path.join(self.temp_dir, b"library.db") - ) - beets.config["directory"] = os.fsdecode( - os.path.join(self.temp_dir, b"libdir") - ) - - # Set $HOME, which is used by Confuse to create directories. - self._old_home = os.environ.get("HOME") - os.environ["HOME"] = os.fsdecode(self.temp_dir) - - # Initialize, but don't install, a DummyIO. - self.io = DummyIO() - - def tearDown(self): - if os.path.isdir(syspath(self.temp_dir)): - shutil.rmtree(syspath(self.temp_dir)) - if self._old_home is None: - del os.environ["HOME"] - else: - os.environ["HOME"] = self._old_home - self.io.restore() - - beets.config.clear() - beets.config._materialized = False - - -class LibTestCase(TestCase): - """A test case that includes an in-memory library object (`lib`) and - an item added to the library (`i`). - """ - - def setUp(self): - super().setUp() - self.lib = beets.library.Library(":memory:") - self.i = item(self.lib) - - def tearDown(self): - self.lib._connection().close() - super().tearDown() - - # Mock I/O. diff --git a/beets/test/helper.py b/beets/test/helper.py index bd01c0e3a..6cb45e828 100644 --- a/beets/test/helper.py +++ b/beets/test/helper.py @@ -36,11 +36,13 @@ import os.path import shutil import subprocess import sys +import unittest from contextlib import contextmanager from enum import Enum from io import StringIO from tempfile import mkdtemp, mkstemp from typing import ClassVar +from unittest.mock import patch import responses from mediafile import Image, MediaFile @@ -174,11 +176,19 @@ class TestHelper(_common.Assertions): Make sure you call ``teardown_beets()`` afterwards. """ self.create_temp_dir() - os.environ["BEETSDIR"] = os.fsdecode(self.temp_dir) + temp_dir_str = os.fsdecode(self.temp_dir) + self.env_patcher = patch.dict( + "os.environ", + { + "BEETSDIR": temp_dir_str, + "HOME": temp_dir_str, # used by Confuse to create directories. + }, + ) + self.env_patcher.start() self.config = beets.config - self.config.clear() - self.config.read() + self.config.sources = [] + self.config.read(user=False, defaults=True) self.config["plugins"] = [] self.config["verbose"] = 1 @@ -195,13 +205,16 @@ class TestHelper(_common.Assertions): dbpath = ":memory:" self.lib = Library(dbpath, self.libdir) + # Initialize, but don't install, a DummyIO. + self.io = _common.DummyIO() + def teardown_beets(self): + self.env_patcher.stop() + self.io.restore() self.lib._close() - if "BEETSDIR" in os.environ: - del os.environ["BEETSDIR"] self.remove_temp_dir() - self.config.clear() - beets.config.read(user=False, defaults=True) + beets.config.clear() + beets.config._materialized = False def load_plugins(self, *plugins): """Load and initialize plugins by names. @@ -490,6 +503,38 @@ class TestHelper(_common.Assertions): return path +# A test harness for all beets tests. +# Provides temporary, isolated configuration. +class BeetsTestCase(unittest.TestCase, TestHelper): + """A unittest.TestCase subclass that saves and restores beets' + global configuration. This allows tests to make temporary + modifications that will then be automatically removed when the test + completes. Also provides some additional assertion methods, a + temporary directory, and a DummyIO. + """ + + def setUp(self): + self.setup_beets() + + def tearDown(self): + self.teardown_beets() + + +class LibTestCase(BeetsTestCase): + """A test case that includes an in-memory library object (`lib`) and + an item added to the library (`i`). + """ + + def setUp(self): + super().setUp() + self.lib = beets.library.Library(":memory:") + self.i = _common.item(self.lib) + + def tearDown(self): + self.lib._connection().close() + super().tearDown() + + class ImportHelper(TestHelper): """Provides tools to setup a library, a directory containing files that are to be imported and an import session. The class also provides stubs for the diff --git a/test/plugins/test_art.py b/test/plugins/test_art.py index 8a2aa5870..4d631aa38 100644 --- a/test/plugins/test_art.py +++ b/test/plugins/test_art.py @@ -26,7 +26,12 @@ import responses from beets import config, importer, library, logging, util from beets.autotag import AlbumInfo, AlbumMatch from beets.test import _common -from beets.test.helper import CleanupModulesMixin, FetchImageHelper, capture_log +from beets.test.helper import ( + BeetsTestCase, + CleanupModulesMixin, + FetchImageHelper, + capture_log, +) from beets.util import syspath from beets.util.artresizer import ArtResizer from beetsplug import fetchart @@ -44,7 +49,7 @@ class Settings: setattr(self, k, v) -class UseThePlugin(CleanupModulesMixin, _common.TestCase): +class UseThePlugin(CleanupModulesMixin, BeetsTestCase): modules = (fetchart.__name__, ArtResizer.__module__) def setUp(self): @@ -977,14 +982,14 @@ class ArtForAlbumTest(UseThePlugin): self._assert_image_operated(self.IMG_348x348, self.RESIZE_OP, True) -class DeprecatedConfigTest(_common.TestCase): +class DeprecatedConfigTest(BeetsTestCase): """While refactoring the plugin, the remote_priority option was deprecated, and a new codepath should translate its effect. Check that it actually does so. """ # If we subclassed UseThePlugin, the configuration change would either be - # overwritten by _common.TestCase or be set after constructing the + # overwritten by BeetsTestCase or be set after constructing the # plugin object def setUp(self): super().setUp() @@ -995,7 +1000,7 @@ class DeprecatedConfigTest(_common.TestCase): self.assertEqual(type(self.plugin.sources[-1]), fetchart.FileSystem) -class EnforceRatioConfigTest(_common.TestCase): +class EnforceRatioConfigTest(BeetsTestCase): """Throw some data at the regexes.""" def _load_with_config(self, values, should_raise): diff --git a/test/plugins/test_beatport.py b/test/plugins/test_beatport.py index fd75f2154..9b07ebb0d 100644 --- a/test/plugins/test_beatport.py +++ b/test/plugins/test_beatport.py @@ -20,11 +20,11 @@ from datetime import timedelta from beets import library from beets.test import _common -from beets.test.helper import TestHelper +from beets.test.helper import BeetsTestCase from beetsplug import beatport -class BeatportTest(_common.TestCase, TestHelper): +class BeatportTest(BeetsTestCase): def _make_release_response(self): """Returns a dict that mimics a response from the beatport API. @@ -601,7 +601,7 @@ class BeatportTest(_common.TestCase, TestHelper): self.assertEqual(track.genre, test_track.genre) -class BeatportResponseEmptyTest(_common.TestCase, TestHelper): +class BeatportResponseEmptyTest(BeetsTestCase): def _make_tracks_response(self): results = [ { diff --git a/test/plugins/test_convert.py b/test/plugins/test_convert.py index bc13b6fec..12b72bf85 100644 --- a/test/plugins/test_convert.py +++ b/test/plugins/test_convert.py @@ -22,8 +22,8 @@ import unittest from mediafile import MediaFile from beets import util -from beets.test import _common, helper -from beets.test.helper import capture_log, control_stdin +from beets.test import _common +from beets.test.helper import BeetsTestCase, capture_log, control_stdin from beets.util import bytestring_path, displayable_path @@ -33,7 +33,7 @@ def shell_quote(text): return shlex.quote(text) -class TestHelper(helper.TestHelper): +class ConvertMixin: def tagged_copy_cmd(self, tag): """Return a conversion command that copies files and appends `tag` to the copy. @@ -84,8 +84,12 @@ class TestHelper(helper.TestHelper): ) +class ConvertTestCase(BeetsTestCase, ConvertMixin): + pass + + @_common.slow_test() -class ImportConvertTest(_common.TestCase, TestHelper): +class ImportConvertTest(ConvertTestCase): def setUp(self): self.setup_beets(disk=True) # Converter is threaded self.importer = self.create_importer() @@ -163,7 +167,7 @@ class ConvertCommand: @_common.slow_test() -class ConvertCliTest(_common.TestCase, TestHelper, ConvertCommand): +class ConvertCliTest(ConvertTestCase, ConvertCommand): def setUp(self): self.setup_beets(disk=True) # Converter is threaded self.album = self.add_album_fixture(ext="ogg") @@ -310,7 +314,7 @@ class ConvertCliTest(_common.TestCase, TestHelper, ConvertCommand): @_common.slow_test() -class NeverConvertLossyFilesTest(_common.TestCase, TestHelper, ConvertCommand): +class NeverConvertLossyFilesTest(ConvertTestCase, ConvertCommand): """Test the effect of the `never_convert_lossy_files` option.""" def setUp(self): diff --git a/test/plugins/test_discogs.py b/test/plugins/test_discogs.py index 6ee57dcd9..203a8a376 100644 --- a/test/plugins/test_discogs.py +++ b/test/plugins/test_discogs.py @@ -18,14 +18,13 @@ import unittest from beets import config -from beets.test import _common from beets.test._common import Bag -from beets.test.helper import capture_log +from beets.test.helper import BeetsTestCase, capture_log from beets.util.id_extractors import extract_discogs_id_regex from beetsplug.discogs import DiscogsPlugin -class DGAlbumInfoTest(_common.TestCase): +class DGAlbumInfoTest(BeetsTestCase): def _make_release(self, tracks=None): """Returns a Bag that mimics a discogs_client.Release. The list of elements on the returned Bag is incomplete, including just diff --git a/test/plugins/test_embedart.py b/test/plugins/test_embedart.py index 48a110295..d280fefd9 100644 --- a/test/plugins/test_embedart.py +++ b/test/plugins/test_embedart.py @@ -47,7 +47,6 @@ class EmbedartCliTest(TestHelper, FetchImageHelper): abbey_differentpath = os.path.join(_common.RSRC, b"abbey-different.jpg") def setUp(self): - self.io = _common.DummyIO() self.io.install() self.setup_beets() # Converter is threaded self.load_plugins("embedart") diff --git a/test/plugins/test_hook.py b/test/plugins/test_hook.py index 1364028f6..ee79b3599 100644 --- a/test/plugins/test_hook.py +++ b/test/plugins/test_hook.py @@ -19,8 +19,7 @@ import tempfile import unittest from beets import config, plugins -from beets.test import _common -from beets.test.helper import TestHelper, capture_log +from beets.test.helper import BeetsTestCase, capture_log def get_temporary_path(): @@ -30,7 +29,7 @@ def get_temporary_path(): return os.path.join(temporary_directory, temporary_name) -class HookTest(_common.TestCase, TestHelper): +class HookTest(BeetsTestCase): TEST_HOOK_COUNT = 5 def setUp(self): diff --git a/test/plugins/test_plugin_mediafield.py b/test/plugins/test_plugin_mediafield.py index 0e03886cf..938f1244b 100644 --- a/test/plugins/test_plugin_mediafield.py +++ b/test/plugins/test_plugin_mediafield.py @@ -24,6 +24,7 @@ import mediafile from beets.library import Item from beets.plugins import BeetsPlugin from beets.test import _common +from beets.test.helper import BeetsTestCase from beets.util import bytestring_path, syspath field_extension = mediafile.MediaField( @@ -41,7 +42,7 @@ list_field_extension = mediafile.ListMediaField( ) -class ExtendedFieldTestMixin(_common.TestCase): +class ExtendedFieldTestMixin(BeetsTestCase): def _mediafile_fixture(self, name, extension="mp3"): name = bytestring_path(name + "." + extension) src = os.path.join(_common.RSRC, name) diff --git a/test/plugins/test_smartplaylist.py b/test/plugins/test_smartplaylist.py index 68c11c910..9655f3f90 100644 --- a/test/plugins/test_smartplaylist.py +++ b/test/plugins/test_smartplaylist.py @@ -23,14 +23,13 @@ from beets import config from beets.dbcore import OrQuery from beets.dbcore.query import FixedFieldSort, MultipleSort, NullSort from beets.library import Album, Item, parse_query_string -from beets.test import _common -from beets.test.helper import TestHelper +from beets.test.helper import BeetsTestCase from beets.ui import UserError from beets.util import CHAR_REPLACE, bytestring_path, syspath from beetsplug.smartplaylist import SmartPlaylistPlugin -class SmartPlaylistTest(_common.TestCase): +class SmartPlaylistTest(BeetsTestCase): def test_build_queries(self): spl = SmartPlaylistPlugin() self.assertIsNone(spl._matched_playlists) @@ -339,7 +338,7 @@ class SmartPlaylistTest(_common.TestCase): self.assertEqual(content, b"http://beets:8337/item/3/file\n") -class SmartPlaylistCLITest(_common.TestCase, TestHelper): +class SmartPlaylistCLITest(BeetsTestCase): def setUp(self): self.setup_beets() diff --git a/test/plugins/test_spotify.py b/test/plugins/test_spotify.py index ae5ce5228..c2fcc0722 100644 --- a/test/plugins/test_spotify.py +++ b/test/plugins/test_spotify.py @@ -9,7 +9,7 @@ import responses from beets import config from beets.library import Item from beets.test import _common -from beets.test.helper import TestHelper +from beets.test.helper import BeetsTestCase from beetsplug import spotify @@ -25,7 +25,7 @@ def _params(url): return parse_qs(urlparse(url).query) -class SpotifyPluginTest(_common.TestCase, TestHelper): +class SpotifyPluginTest(BeetsTestCase): @responses.activate def setUp(self): config.clear() diff --git a/test/plugins/test_subsonicupdate.py b/test/plugins/test_subsonicupdate.py index 3f84d848f..be7f979ac 100644 --- a/test/plugins/test_subsonicupdate.py +++ b/test/plugins/test_subsonicupdate.py @@ -6,8 +6,7 @@ from urllib.parse import parse_qs, urlparse import responses from beets import config -from beets.test import _common -from beets.test.helper import TestHelper +from beets.test.helper import BeetsTestCase from beetsplug import subsonicupdate @@ -26,7 +25,7 @@ def _params(url): return parse_qs(urlparse(url).query) -class SubsonicPluginTest(_common.TestCase, TestHelper): +class SubsonicPluginTest(BeetsTestCase): """Test class for subsonicupdate.""" @responses.activate diff --git a/test/plugins/test_the.py b/test/plugins/test_the.py index e6d510774..4f23d6f13 100644 --- a/test/plugins/test_the.py +++ b/test/plugins/test_the.py @@ -3,11 +3,11 @@ import unittest from beets import config -from beets.test import _common +from beets.test.helper import BeetsTestCase from beetsplug.the import FORMAT, PATTERN_A, PATTERN_THE, ThePlugin -class ThePluginTest(_common.TestCase): +class ThePluginTest(BeetsTestCase): def test_unthe_with_default_patterns(self): self.assertEqual(ThePlugin().unthe("", PATTERN_THE), "") self.assertEqual( diff --git a/test/plugins/test_web.py b/test/plugins/test_web.py index afd1ed706..c34de05d2 100644 --- a/test/plugins/test_web.py +++ b/test/plugins/test_web.py @@ -9,10 +9,11 @@ import unittest from beets import logging from beets.library import Album, Item from beets.test import _common +from beets.test.helper import LibTestCase from beetsplug import web -class WebPluginTest(_common.LibTestCase): +class WebPluginTest(LibTestCase): def setUp(self): super().setUp() self.log = logging.getLogger("beets.web") diff --git a/test/test_art_resize.py b/test/test_art_resize.py index ac9463cba..e7bee02dc 100644 --- a/test/test_art_resize.py +++ b/test/test_art_resize.py @@ -20,7 +20,7 @@ import unittest from unittest.mock import patch from beets.test import _common -from beets.test.helper import CleanupModulesMixin, TestHelper +from beets.test.helper import BeetsTestCase, CleanupModulesMixin from beets.util import command_output, syspath from beets.util.artresizer import IMBackend, PILBackend @@ -48,7 +48,7 @@ class DummyPILBackend(PILBackend): pass -class ArtResizerFileSizeTest(CleanupModulesMixin, _common.TestCase, TestHelper): +class ArtResizerFileSizeTest(CleanupModulesMixin, BeetsTestCase): """Unittest test case for Art Resizer to a specific filesize.""" modules = (IMBackend.__module__,) diff --git a/test/test_autotag.py b/test/test_autotag.py index e9b44458c..8ff613cff 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -22,11 +22,11 @@ from beets import autotag, config from beets.autotag import AlbumInfo, TrackInfo, match from beets.autotag.hooks import Distance, string_dist from beets.library import Item -from beets.test import _common +from beets.test.helper import BeetsTestCase from beets.util import plurality -class PluralityTest(_common.TestCase): +class PluralityTest(BeetsTestCase): def test_plurality_consensus(self): objs = [1, 1, 1, 1] obj, freq = plurality(objs) @@ -146,7 +146,7 @@ def _clear_weights(): Distance.__dict__["_weights"].cache = {} -class DistanceTest(_common.TestCase): +class DistanceTest(BeetsTestCase): def tearDown(self): super().tearDown() _clear_weights() @@ -330,7 +330,7 @@ class DistanceTest(_common.TestCase): ) -class TrackDistanceTest(_common.TestCase): +class TrackDistanceTest(BeetsTestCase): def test_identical_tracks(self): item = _make_item("one", 1) info = _make_trackinfo()[0] @@ -358,7 +358,7 @@ class TrackDistanceTest(_common.TestCase): self.assertEqual(dist, 0.0) -class AlbumDistanceTest(_common.TestCase): +class AlbumDistanceTest(BeetsTestCase): def _mapping(self, items, info): out = {} for i, t in zip(items, info.tracks): @@ -664,7 +664,7 @@ class ApplyTestUtil: autotag.apply_metadata(info, mapping) -class ApplyTest(_common.TestCase, ApplyTestUtil): +class ApplyTest(BeetsTestCase, ApplyTestUtil): def setUp(self): super().setUp() @@ -925,7 +925,7 @@ class ApplyTest(_common.TestCase, ApplyTestUtil): self.assertEqual(self.items[0].data_source, "MusicBrainz") -class ApplyCompilationTest(_common.TestCase, ApplyTestUtil): +class ApplyCompilationTest(BeetsTestCase, ApplyTestUtil): def setUp(self): super().setUp() @@ -1073,7 +1073,7 @@ class StringDistanceTest(unittest.TestCase): self.assertEqual(dist, 0.0) -class EnumTest(_common.TestCase): +class EnumTest(BeetsTestCase): """ Test Enum Subclasses defined in beets.util.enumeration """ diff --git a/test/test_datequery.py b/test/test_datequery.py index 2b666f0d1..56c85e05d 100644 --- a/test/test_datequery.py +++ b/test/test_datequery.py @@ -25,7 +25,7 @@ from beets.dbcore.query import ( InvalidQueryArgumentValueError, _parse_periods, ) -from beets.test import _common +from beets.test.helper import LibTestCase def _date(string): @@ -152,7 +152,7 @@ def _parsetime(s): return time.mktime(datetime.strptime(s, "%Y-%m-%d %H:%M").timetuple()) -class DateQueryTest(_common.LibTestCase): +class DateQueryTest(LibTestCase): def setUp(self): super().setUp() self.i.added = _parsetime("2013-03-30 22:21") @@ -187,7 +187,7 @@ class DateQueryTest(_common.LibTestCase): self.assertEqual(len(matched), 0) -class DateQueryTestRelative(_common.LibTestCase): +class DateQueryTestRelative(LibTestCase): def setUp(self): super().setUp() @@ -233,7 +233,7 @@ class DateQueryTestRelative(_common.LibTestCase): self.assertEqual(len(matched), 0) -class DateQueryTestRelativeMore(_common.LibTestCase): +class DateQueryTestRelativeMore(LibTestCase): def setUp(self): super().setUp() self.i.added = _parsetime(datetime.now().strftime("%Y-%m-%d %H:%M")) diff --git a/test/test_files.py b/test/test_files.py index 46aebe54f..68971cd85 100644 --- a/test/test_files.py +++ b/test/test_files.py @@ -25,10 +25,11 @@ import beets.library from beets import util from beets.test import _common from beets.test._common import item, touch +from beets.test.helper import BeetsTestCase from beets.util import MoveOperation, bytestring_path, syspath -class MoveTest(_common.TestCase): +class MoveTest(BeetsTestCase): def setUp(self): super().setUp() @@ -207,7 +208,7 @@ class MoveTest(_common.TestCase): self.assertEqual(self.i.path, util.normpath(self.dest)) -class HelperTest(_common.TestCase): +class HelperTest(BeetsTestCase): def test_ancestry_works_on_file(self): p = "/a/b/c" a = ["/", "/a", "/a/b"] @@ -244,7 +245,7 @@ class HelperTest(_common.TestCase): self.assertEqual(util.path_as_posix(p), a) -class AlbumFileTest(_common.TestCase): +class AlbumFileTest(BeetsTestCase): def setUp(self): super().setUp() @@ -311,7 +312,7 @@ class AlbumFileTest(_common.TestCase): self.assertIn(b"testotherdir", self.i.path) -class ArtFileTest(_common.TestCase): +class ArtFileTest(BeetsTestCase): def setUp(self): super().setUp() @@ -485,7 +486,7 @@ class ArtFileTest(_common.TestCase): self.assertExists(oldartpath) -class RemoveTest(_common.TestCase): +class RemoveTest(BeetsTestCase): def setUp(self): super().setUp() @@ -546,7 +547,7 @@ class RemoveTest(_common.TestCase): # Tests that we can "delete" nonexistent files. -class SoftRemoveTest(_common.TestCase): +class SoftRemoveTest(BeetsTestCase): def setUp(self): super().setUp() @@ -564,7 +565,7 @@ class SoftRemoveTest(_common.TestCase): self.fail("OSError when removing path") -class SafeMoveCopyTest(_common.TestCase): +class SafeMoveCopyTest(BeetsTestCase): def setUp(self): super().setUp() @@ -612,7 +613,7 @@ class SafeMoveCopyTest(_common.TestCase): self.assertExists(self.path) -class PruneTest(_common.TestCase): +class PruneTest(BeetsTestCase): def setUp(self): super().setUp() @@ -632,7 +633,7 @@ class PruneTest(_common.TestCase): self.assertNotExists(self.sub) -class WalkTest(_common.TestCase): +class WalkTest(BeetsTestCase): def setUp(self): super().setUp() @@ -666,7 +667,7 @@ class WalkTest(_common.TestCase): self.assertEqual(res[0], (self.base, [], [])) -class UniquePathTest(_common.TestCase): +class UniquePathTest(BeetsTestCase): def setUp(self): super().setUp() @@ -694,7 +695,7 @@ class UniquePathTest(_common.TestCase): self.assertEqual(path, os.path.join(self.base, b"x.3.mp3")) -class MkDirAllTest(_common.TestCase): +class MkDirAllTest(BeetsTestCase): def test_parent_exists(self): path = os.path.join(self.temp_dir, b"foo", b"bar", b"baz", b"qux.mp3") util.mkdirall(path) diff --git a/test/test_importer.py b/test/test_importer.py index 87af62a71..8c2b474e0 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -36,6 +36,7 @@ from beets.importer import albums_in_dir from beets.test import _common from beets.test.helper import ( AutotagStub, + BeetsTestCase, ImportHelper, TestHelper, capture_log, @@ -44,7 +45,7 @@ from beets.test.helper import ( from beets.util import bytestring_path, displayable_path, syspath -class ScrubbedImportTest(_common.TestCase, ImportHelper): +class ScrubbedImportTest(BeetsTestCase, ImportHelper): def setUp(self): self.setup_beets(disk=True) self.load_plugins("scrub") @@ -99,7 +100,7 @@ class ScrubbedImportTest(_common.TestCase, ImportHelper): @_common.slow_test() -class NonAutotaggedImportTest(_common.TestCase, ImportHelper): +class NonAutotaggedImportTest(BeetsTestCase, ImportHelper): def setUp(self): self.setup_beets(disk=True) self._create_import_dir(2) @@ -344,7 +345,7 @@ class ImportPasswordRarTest(ImportZipTest): return os.path.join(_common.RSRC, b"password.rar") -class ImportSingletonTest(_common.TestCase, ImportHelper): +class ImportSingletonTest(BeetsTestCase, ImportHelper): """Test ``APPLY`` and ``ASIS`` choices for an import session with singletons config set to True. """ @@ -467,7 +468,7 @@ class ImportSingletonTest(_common.TestCase, ImportHelper): self.assertEqual(item.title, "Applied Title 1 - formatted") -class ImportTest(_common.TestCase, ImportHelper): +class ImportTest(BeetsTestCase, ImportHelper): """Test APPLY, ASIS and SKIP choices.""" def setUp(self): @@ -681,7 +682,7 @@ class ImportTest(_common.TestCase, ImportHelper): ) -class ImportTracksTest(_common.TestCase, ImportHelper): +class ImportTracksTest(BeetsTestCase, ImportHelper): """Test TRACKS and APPLY choice.""" def setUp(self): @@ -715,7 +716,7 @@ class ImportTracksTest(_common.TestCase, ImportHelper): self.assert_file_in_lib(b"singletons", b"Applied Title 1.mp3") -class ImportCompilationTest(_common.TestCase, ImportHelper): +class ImportCompilationTest(BeetsTestCase, ImportHelper): """Test ASIS import of a folder containing tracks with different artists.""" def setUp(self): @@ -834,7 +835,7 @@ class ImportCompilationTest(_common.TestCase, ImportHelper): self.assertTrue(asserted_multi_artists_0 and asserted_multi_artists_1) -class ImportExistingTest(_common.TestCase, ImportHelper): +class ImportExistingTest(BeetsTestCase, ImportHelper): """Test importing files that are already in the library directory.""" def setUp(self): @@ -959,7 +960,7 @@ class ImportExistingTest(_common.TestCase, ImportHelper): self.assertNotExists(self.import_media[0].path) -class GroupAlbumsImportTest(_common.TestCase, ImportHelper): +class GroupAlbumsImportTest(BeetsTestCase, ImportHelper): def setUp(self): self.setup_beets() self._create_import_dir(3) @@ -1031,7 +1032,7 @@ class GlobalGroupAlbumsImportTest(GroupAlbumsImportTest): config["import"]["group_albums"] = True -class ChooseCandidateTest(_common.TestCase, ImportHelper): +class ChooseCandidateTest(BeetsTestCase, ImportHelper): def setUp(self): self.setup_beets() self._create_import_dir(1) @@ -1054,7 +1055,7 @@ class ChooseCandidateTest(_common.TestCase, ImportHelper): self.assertEqual(self.lib.albums().get().album, "Applied Album MM") -class InferAlbumDataTest(_common.TestCase): +class InferAlbumDataTest(BeetsTestCase): def setUp(self): super().setUp() @@ -1365,7 +1366,7 @@ class ImportDuplicateSingletonTest(unittest.TestCase, TestHelper): return item -class TagLogTest(_common.TestCase): +class TagLogTest(BeetsTestCase): def test_tag_log_line(self): sio = StringIO() handler = logging.StreamHandler(sio) @@ -1484,7 +1485,7 @@ def _mkmp3(path): ) -class AlbumsInDirTest(_common.TestCase): +class AlbumsInDirTest(BeetsTestCase): def setUp(self): super().setUp() @@ -1526,7 +1527,7 @@ class AlbumsInDirTest(_common.TestCase): self.assertEqual(len(album), 1) -class MultiDiscAlbumsInDirTest(_common.TestCase): +class MultiDiscAlbumsInDirTest(BeetsTestCase): def create_music(self, files=True, ascii=True): """Create some music in multiple album directories. @@ -1751,7 +1752,7 @@ class ReimportTest(unittest.TestCase, ImportHelper): self.assertEqual(self._album().data_source, "match_source") -class ImportPretendTest(_common.TestCase, ImportHelper): +class ImportPretendTest(BeetsTestCase, ImportHelper): """Test the pretend commandline option""" def __init__(self, method_name="runTest"): @@ -1944,7 +1945,7 @@ def mocked_get_recording_by_id( "musicbrainzngs.get_release_by_id", Mock(side_effect=mocked_get_release_by_id), ) -class ImportMusicBrainzIdTest(_common.TestCase, ImportHelper): +class ImportMusicBrainzIdTest(BeetsTestCase, ImportHelper): """Test the --musicbrainzid argument.""" MB_RELEASE_PREFIX = "https://musicbrainz.org/release/" diff --git a/test/test_library.py b/test/test_library.py index c9d0440b5..179927dc9 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -32,14 +32,14 @@ import beets.library from beets import config, plugins, util from beets.test import _common from beets.test._common import item -from beets.test.helper import TestHelper +from beets.test.helper import BeetsTestCase, LibTestCase, TestHelper from beets.util import bytestring_path, syspath # Shortcut to path normalization. np = util.normpath -class LoadTest(_common.LibTestCase): +class LoadTest(LibTestCase): def test_load_restores_data_from_db(self): original_title = self.i.title self.i.title = "something" @@ -53,7 +53,7 @@ class LoadTest(_common.LibTestCase): self.assertNotIn("artist", self.i._dirty) -class StoreTest(_common.LibTestCase): +class StoreTest(LibTestCase): def test_store_changes_database_value(self): self.i.year = 1987 self.i.store() @@ -94,7 +94,7 @@ class StoreTest(_common.LibTestCase): self.assertNotIn("flex1", album.items()[0]) -class AddTest(_common.TestCase): +class AddTest(BeetsTestCase): def setUp(self): super().setUp() self.lib = beets.library.Library(":memory:") @@ -126,14 +126,14 @@ class AddTest(_common.TestCase): self.assertEqual(new_grouping, self.i.grouping) -class RemoveTest(_common.LibTestCase): +class RemoveTest(LibTestCase): def test_remove_deletes_from_db(self): self.i.remove() c = self.lib._connection().execute("select * from items") self.assertIsNone(c.fetchone()) -class GetSetTest(_common.TestCase): +class GetSetTest(BeetsTestCase): def setUp(self): super().setUp() self.i = item() @@ -169,7 +169,7 @@ class GetSetTest(_common.TestCase): self.assertIsNone(i.get("flexx")) -class DestinationTest(_common.TestCase): +class DestinationTest(BeetsTestCase): def setUp(self): super().setUp() # default directory is ~/Music and the only reason why it was switched @@ -182,10 +182,6 @@ class DestinationTest(_common.TestCase): super().tearDown() self.lib._connection().close() - # Reset config if it was changed in test cases - config.clear() - config.read(user=False, defaults=True) - def test_directory_works_with_trailing_slash(self): self.lib.directory = b"one/" self.lib.path_formats = [("default", "two")] @@ -551,7 +547,7 @@ class DestinationTest(_common.TestCase): self.assertEqual(self.i.destination(), np("one/foo/two")) -class ItemFormattedMappingTest(_common.LibTestCase): +class ItemFormattedMappingTest(LibTestCase): def test_formatted_item_value(self): formatted = self.i.formatted() self.assertEqual(formatted["artist"], "the artist") @@ -624,7 +620,7 @@ class PathFormattingMixin: self.assertEqual(actual, dest) -class DestinationFunctionTest(_common.TestCase, PathFormattingMixin): +class DestinationFunctionTest(BeetsTestCase, PathFormattingMixin): def setUp(self): super().setUp() self.lib = beets.library.Library(":memory:") @@ -733,7 +729,7 @@ class DestinationFunctionTest(_common.TestCase, PathFormattingMixin): self._assert_dest(b"/base/Alice & Bob") -class DisambiguationTest(_common.TestCase, PathFormattingMixin): +class DisambiguationTest(BeetsTestCase, PathFormattingMixin): def setUp(self): super().setUp() self.lib = beets.library.Library(":memory:") @@ -822,7 +818,7 @@ class DisambiguationTest(_common.TestCase, PathFormattingMixin): self._assert_dest(b"/base/foo/the title", self.i1) -class SingletonDisambiguationTest(_common.TestCase, PathFormattingMixin): +class SingletonDisambiguationTest(BeetsTestCase, PathFormattingMixin): def setUp(self): super().setUp() self.lib = beets.library.Library(":memory:") @@ -907,7 +903,7 @@ class SingletonDisambiguationTest(_common.TestCase, PathFormattingMixin): self._assert_dest(b"/base/foo/the title", self.i1) -class PluginDestinationTest(_common.TestCase): +class PluginDestinationTest(BeetsTestCase): def setUp(self): super().setUp() @@ -959,7 +955,7 @@ class PluginDestinationTest(_common.TestCase): self._assert_dest(b"the artist bar_baz") -class AlbumInfoTest(_common.TestCase): +class AlbumInfoTest(BeetsTestCase): def setUp(self): super().setUp() self.lib = beets.library.Library(":memory:") @@ -1064,7 +1060,7 @@ class AlbumInfoTest(_common.TestCase): self.assertEqual(i.album, ai.album) -class ArtDestinationTest(_common.TestCase): +class ArtDestinationTest(BeetsTestCase): def setUp(self): super().setUp() config["art_filename"] = "artimage" @@ -1092,7 +1088,7 @@ class ArtDestinationTest(_common.TestCase): self.assertIn(b"artYimage", art) -class PathStringTest(_common.TestCase): +class PathStringTest(BeetsTestCase): def setUp(self): super().setUp() self.lib = beets.library.Library(":memory:") @@ -1178,7 +1174,7 @@ class PathStringTest(_common.TestCase): self.assertTrue(isinstance(alb.artpath, bytes)) -class MtimeTest(_common.TestCase): +class MtimeTest(BeetsTestCase): def setUp(self): super().setUp() self.ipath = os.path.join(self.temp_dir, b"testfile.mp3") @@ -1216,7 +1212,7 @@ class MtimeTest(_common.TestCase): self.assertGreaterEqual(self.i.mtime, self._mtime()) -class ImportTimeTest(_common.TestCase): +class ImportTimeTest(BeetsTestCase): def setUp(self): super().setUp() self.lib = beets.library.Library(":memory:") @@ -1232,7 +1228,7 @@ class ImportTimeTest(_common.TestCase): self.assertGreater(self.singleton.added, 0) -class TemplateTest(_common.LibTestCase): +class TemplateTest(LibTestCase): def test_year_formatted_in_template(self): self.i.year = 123 self.i.store() @@ -1262,7 +1258,7 @@ class TemplateTest(_common.LibTestCase): self.assertEqual(f"{item:$tagada}", "togodo") -class UnicodePathTest(_common.LibTestCase): +class UnicodePathTest(LibTestCase): def test_unicode_path(self): self.i.path = os.path.join(_common.RSRC, "unicode\u2019d.mp3".encode()) # If there are any problems with unicode paths, we will raise diff --git a/test/test_logging.py b/test/test_logging.py index 58be799e0..db7f33bfd 100644 --- a/test/test_logging.py +++ b/test/test_logging.py @@ -10,10 +10,10 @@ import beets.logging as blog import beetsplug from beets import plugins, ui from beets.test import _common, helper -from beets.test._common import TestCase +from beets.test.helper import BeetsTestCase -class LoggingTest(TestCase): +class LoggingTest(BeetsTestCase): def test_logging_management(self): l1 = log.getLogger("foo123") l2 = blog.getLogger("foo123") @@ -162,7 +162,7 @@ class LoggingLevelTest(unittest.TestCase, helper.TestHelper): @_common.slow_test() -class ConcurrentEventsTest(TestCase, helper.TestHelper): +class ConcurrentEventsTest(BeetsTestCase): """Similar to LoggingLevelTest but lower-level and focused on multiple events interaction. Since this is a bit heavy we don't do it in LoggingLevelTest. diff --git a/test/test_mb.py b/test/test_mb.py index efd620522..9922cbfa0 100644 --- a/test/test_mb.py +++ b/test/test_mb.py @@ -20,10 +20,10 @@ from unittest import mock from beets import config from beets.autotag import mb -from beets.test import _common +from beets.test.helper import BeetsTestCase -class MBAlbumInfoTest(_common.TestCase): +class MBAlbumInfoTest(BeetsTestCase): def _make_release( self, date_str="2009", @@ -662,7 +662,7 @@ class MBAlbumInfoTest(_common.TestCase): self.assertEqual(t[1].trackdisambig, "SECOND TRACK") -class ParseIDTest(_common.TestCase): +class ParseIDTest(BeetsTestCase): def test_parse_id_correct(self): id_string = "28e32c71-1450-463e-92bf-e0a46446fc11" out = mb._parse_id(id_string) @@ -680,7 +680,7 @@ class ParseIDTest(_common.TestCase): self.assertEqual(out, id_string) -class ArtistFlatteningTest(_common.TestCase): +class ArtistFlatteningTest(BeetsTestCase): def _credit_dict(self, suffix=""): return { "artist": { diff --git a/test/test_metasync.py b/test/test_metasync.py index 9dae099af..a3bc3acb2 100644 --- a/test/test_metasync.py +++ b/test/test_metasync.py @@ -21,7 +21,7 @@ from datetime import datetime from beets.library import Item from beets.test import _common -from beets.test.helper import TestHelper +from beets.test.helper import BeetsTestCase def _parsetime(s): @@ -32,7 +32,7 @@ def _is_windows(): return platform.system() == "Windows" -class MetaSyncTest(_common.TestCase, TestHelper): +class MetaSyncTest(BeetsTestCase): itunes_library_unix = os.path.join(_common.RSRC, b"itunes_library_unix.xml") itunes_library_windows = os.path.join( _common.RSRC, b"itunes_library_windows.xml" diff --git a/test/test_query.py b/test/test_query.py index 69277cfcd..897b247dc 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -31,6 +31,7 @@ from beets.dbcore.query import ( ) from beets.library import Item, Library from beets.test import _common, helper +from beets.test.helper import BeetsTestCase, LibTestCase from beets.util import syspath # Because the absolute path begins with something like C:, we @@ -48,7 +49,7 @@ class TestHelper(helper.TestHelper): self.assertNotIn(item.id, result_ids) -class AnyFieldQueryTest(_common.LibTestCase): +class AnyFieldQueryTest(LibTestCase): def test_no_restriction(self): q = dbcore.query.AnyFieldQuery( "title", @@ -92,7 +93,7 @@ class AssertsMixin: # A test case class providing a library with some dummy data and some # assertions involving that data. -class DummyDataTestCase(_common.TestCase, AssertsMixin): +class DummyDataTestCase(BeetsTestCase, AssertsMixin): def setUp(self): super().setUp() self.lib = beets.library.Library(":memory:") @@ -418,7 +419,7 @@ class GetTest(DummyDataTestCase): self.assertIsInstance(raised.exception, ParsingError) -class MatchTest(_common.TestCase): +class MatchTest(BeetsTestCase): def setUp(self): super().setUp() self.item = _common.item() @@ -487,7 +488,7 @@ class MatchTest(_common.TestCase): self.assertNotEqual(q3, q4) -class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): +class PathQueryTest(LibTestCase, TestHelper, AssertsMixin): def setUp(self): super().setUp() @@ -871,7 +872,7 @@ class NoneQueryTest(unittest.TestCase, TestHelper): self.assertInResult(item, matched) -class NotQueryMatchTest(_common.TestCase): +class NotQueryMatchTest(BeetsTestCase): """Test `query.NotQuery` matching against a single item, using the same cases and assertions as on `MatchTest`, plus assertion on the negated queries (ie. assertTrue(q) -> assertFalse(NotQuery(q))). @@ -1129,7 +1130,7 @@ class NotQueryTest(DummyDataTestCase): pass -class RelatedQueriesTest(_common.TestCase, AssertsMixin): +class RelatedQueriesTest(BeetsTestCase, AssertsMixin): """Test album-level queries with track-level filters and vice-versa.""" def setUp(self): diff --git a/test/test_sort.py b/test/test_sort.py index 52fa02600..b222c93f9 100644 --- a/test/test_sort.py +++ b/test/test_sort.py @@ -20,11 +20,12 @@ import unittest import beets.library from beets import config, dbcore from beets.test import _common +from beets.test.helper import BeetsTestCase # A test case class providing a library with some dummy data and some # assertions involving that data. -class DummyDataTestCase(_common.TestCase): +class DummyDataTestCase(BeetsTestCase): def setUp(self): super().setUp() self.lib = beets.library.Library(":memory:") @@ -373,7 +374,7 @@ class ConfigSortTest(DummyDataTestCase): self.assertGreater(results[0].albumartist, results[1].albumartist) -class CaseSensitivityTest(DummyDataTestCase, _common.TestCase): +class CaseSensitivityTest(DummyDataTestCase, BeetsTestCase): """If case_insensitive is false, lower-case values should be placed after all upper-case values. E.g., `Foo Qux bar` """ diff --git a/test/test_ui.py b/test/test_ui.py index 4b37c73df..9ca68992a 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -31,6 +31,7 @@ from beets import autotag, config, library, plugins, ui, util from beets.autotag.match import distance from beets.test import _common from beets.test.helper import ( + BeetsTestCase, TestHelper, capture_stdout, control_stdin, @@ -108,7 +109,7 @@ class ListTest(unittest.TestCase): self.assertNotIn("the album", stdout.getvalue()) -class RemoveTest(_common.TestCase, TestHelper): +class RemoveTest(BeetsTestCase): def setUp(self): super().setUp() @@ -454,7 +455,7 @@ class WriteTest(unittest.TestCase, TestHelper): self.assertIn(f"{old_title} -> new title", output) -class MoveTest(_common.TestCase): +class MoveTest(BeetsTestCase): def setUp(self): super().setUp() @@ -562,7 +563,7 @@ class MoveTest(_common.TestCase): self.assertNotExists(self.otherdir) -class UpdateTest(_common.TestCase): +class UpdateTest(BeetsTestCase): def setUp(self): super().setUp() @@ -763,7 +764,7 @@ class UpdateTest(_common.TestCase): self.assertNotEqual(item.lyrics, "new lyrics") -class PrintTest(_common.TestCase): +class PrintTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() @@ -802,7 +803,7 @@ class PrintTest(_common.TestCase): del os.environ["LC_CTYPE"] -class ImportTest(_common.TestCase): +class ImportTest(BeetsTestCase): def test_quiet_timid_disallowed(self): config["import"]["quiet"] = True config["import"]["timid"] = True @@ -1145,7 +1146,7 @@ class ConfigTest(unittest.TestCase, TestHelper): ) -class ShowModelChangeTest(_common.TestCase): +class ShowModelChangeTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() @@ -1197,7 +1198,7 @@ class ShowModelChangeTest(_common.TestCase): self.assertIn("bar", out) -class ShowChangeTest(_common.TestCase): +class ShowChangeTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() @@ -1358,7 +1359,7 @@ class ShowChangeTest(_common.TestCase): @patch("beets.library.Item.try_filesize", Mock(return_value=987)) -class SummarizeItemsTest(_common.TestCase): +class SummarizeItemsTest(BeetsTestCase): def setUp(self): super().setUp() item = library.Item() @@ -1395,7 +1396,7 @@ class SummarizeItemsTest(_common.TestCase): self.assertEqual(summary, "3 items, G 2, F 1, 4kbps, 32:42, 2.9 KiB") -class PathFormatTest(_common.TestCase): +class PathFormatTest(BeetsTestCase): def test_custom_paths_prepend(self): default_formats = ui.get_path_formats() @@ -1408,7 +1409,7 @@ class PathFormatTest(_common.TestCase): @_common.slow_test() -class PluginTest(_common.TestCase, TestHelper): +class PluginTest(BeetsTestCase): def test_plugin_command_from_pluginpath(self): config["pluginpath"] = [_common.PLUGINPATH] config["plugins"] = ["test"] @@ -1416,7 +1417,7 @@ class PluginTest(_common.TestCase, TestHelper): @_common.slow_test() -class CompletionTest(_common.TestCase, TestHelper): +class CompletionTest(BeetsTestCase): def test_completion(self): # Load plugin commands config["pluginpath"] = [_common.PLUGINPATH] @@ -1652,7 +1653,7 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): ) -class EncodingTest(_common.TestCase): +class EncodingTest(BeetsTestCase): """Tests for the `terminal_encoding` config option and our `_in_encoding` and `_out_encoding` utility functions. """ diff --git a/test/test_ui_commands.py b/test/test_ui_commands.py index f371a1ab1..dfd8ffad0 100644 --- a/test/test_ui_commands.py +++ b/test/test_ui_commands.py @@ -22,11 +22,12 @@ import unittest from beets import library, ui from beets.test import _common +from beets.test.helper import BeetsTestCase, LibTestCase from beets.ui import commands from beets.util import syspath -class QueryTest(_common.TestCase): +class QueryTest(BeetsTestCase): def setUp(self): super().setUp() @@ -87,7 +88,7 @@ class QueryTest(_common.TestCase): self.check_do_query(0, 2, album=True, also_items=False) -class FieldsTest(_common.LibTestCase): +class FieldsTest(LibTestCase): def setUp(self): super().setUp() diff --git a/test/test_ui_init.py b/test/test_ui_init.py index 77c9c784b..df1f36b97 100644 --- a/test/test_ui_init.py +++ b/test/test_ui_init.py @@ -23,10 +23,10 @@ from random import random from beets import config, ui from beets.test import _common -from beets.test.helper import control_stdin +from beets.test.helper import BeetsTestCase, LibTestCase, control_stdin -class InputMethodsTest(_common.TestCase): +class InputMethodsTest(BeetsTestCase): def setUp(self): super().setUp() self.io.install() @@ -90,7 +90,7 @@ class InputMethodsTest(_common.TestCase): self.assertEqual(items, ["1", "3"]) -class InitTest(_common.LibTestCase): +class InitTest(LibTestCase): def setUp(self): super().setUp() @@ -129,7 +129,7 @@ class InitTest(_common.LibTestCase): self.assertEqual(h, ui.human_seconds(i)) -class ParentalDirCreation(_common.TestCase): +class ParentalDirCreation(BeetsTestCase): def test_create_yes(self): non_exist_path = _common.os.fsdecode( os.path.join(self.temp_dir, b"nonexist", str(random()).encode()) diff --git a/test/test_util.py b/test/test_util.py index d2a7caea0..63de482ff 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -24,6 +24,7 @@ from unittest.mock import Mock, patch from beets import util from beets.test import _common +from beets.test.helper import BeetsTestCase class UtilTest(unittest.TestCase): @@ -157,7 +158,7 @@ class UtilTest(unittest.TestCase): pass -class PathConversionTest(_common.TestCase): +class PathConversionTest(BeetsTestCase): def test_syspath_windows_format(self): with _common.platform_windows(): path = os.path.join("a", "b", "c") @@ -200,7 +201,7 @@ class PathConversionTest(_common.TestCase): self.assertEqual(outpath, "C:\\caf\xe9".encode()) -class PathTruncationTest(_common.TestCase): +class PathTruncationTest(BeetsTestCase): def test_truncate_bytestring(self): with _common.platform_posix(): p = util.truncate_path(b"abcde/fgh", 4) diff --git a/test/test_vfs.py b/test/test_vfs.py index 789356157..939d44f73 100644 --- a/test/test_vfs.py +++ b/test/test_vfs.py @@ -18,9 +18,10 @@ import unittest from beets import library, vfs from beets.test import _common +from beets.test.helper import BeetsTestCase -class VFSTest(_common.TestCase): +class VFSTest(BeetsTestCase): def setUp(self): super().setUp() self.lib = library.Library(