Skip to content

Commit

Permalink
Centralize test setup (#5362)
Browse files Browse the repository at this point in the history
This PR deduplicates shared tests setup that was used across test files.
It attempts to centralise the setup into a single source of truth
implementation in `beets.test.helper`.

This works towards the eventual migration to `pytest` (#5361) making it
easier to replace the tests setup with `pytest` fixtures.

It builds upon the temporary files cleanup in #5345.

## Details

### Test setup

* Mostly duplicate test setup implementations in
`beets.test._common.TestCase` and `beets.test.helper.TestHelper` were
merged into a single implementation as
`beets.test.helper.BeetsTestCase`.
* Replaced `unittest.TestCase` and `beets.test.helper.TestHelper`
combination with `beets.test.helper.ImportTestCase`
* Refactored `test/test_plugins.py` removing duplicate import files
setup.
* Centralised setting up the database on disk.
* Centralised plugin test setup into `beets.test.helpers.PluginMixin`.

### Removals

* Around 1/3 of the removed lines correspond to the removal of `def
suite()` functions that were previously used for tests collection.
* Removed redundant `setUp` and `tearDown` methods from many test files
given that the functions that they performed had been centralised in
`beets.test.helper`.
* Removed redundant library initialisations


### Importing

* Centralised import directory creation (only gets created on the first
access).
* Deduplicated `_setup_import_session` implementations in
`TerminalImportSessionSetup` and `ImportHelper`.
* Merged `ImportHelper._create_import_dir` and
`TestHelper.create_importer` implementations into
`ImportHelper.setup_importer`.
* Merged various takes on import files prep into
`ImportHelper.prepare_albums_for_import` and
`ImportHelper.prepare_album_for_import`.
* Seeing that many tests used it, defined `AsIsImporterMixin` which
provides a method `run_asis_importer` to setup _asis_ (non-autotag)
importer and run it
  • Loading branch information
snejus authored Jul 31, 2024
2 parents e33b513 + 5f395ab commit c05b3cf
Show file tree
Hide file tree
Showing 74 changed files with 1,250 additions and 2,658 deletions.
131 changes: 6 additions & 125 deletions beets/test/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,19 @@
"""Some common functionality for beets' test cases."""

import os
import shutil
import sys
import tempfile
import time
import unittest
from contextlib import contextmanager

import beets # noqa: E402
import beets.library # noqa: E402
import beets
import beets.library

# Make sure the development versions of the plugins are used
import beetsplug # noqa: E402
from beets import util # noqa: E402
from beets import importer, logging # noqa: E402
from beets.ui import commands # noqa: E402
from beets.util import bytestring_path, syspath # noqa: E402
import beetsplug
from beets import importer, logging, util
from beets.ui import commands
from beets.util import syspath

beetsplug.__path__ = [
os.path.abspath(
Expand Down Expand Up @@ -121,9 +118,6 @@ def item(lib=None):
return i


_album_ident = 0


def album(lib=None):
global _item_ident
_item_ident += 1
Expand Down Expand Up @@ -190,100 +184,6 @@ def assert_equal_path(self, a, b):
)


# 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 timing.


class Timecop:
"""Mocks the timing system (namely time() and sleep()) for testing.
Inspired by the Ruby timecop library.
"""

def __init__(self):
self.now = time.time()

def time(self):
return self.now

def sleep(self, amount):
self.now += amount

def install(self):
self.orig = {
"time": time.time,
"sleep": time.sleep,
}
time.time = self.time
time.sleep = self.sleep

def restore(self):
time.time = self.orig["time"]
time.sleep = self.orig["sleep"]


# Mock I/O.


Expand Down Expand Up @@ -388,25 +288,6 @@ def __getattr__(self, key):
return self.fields.get(key)


# Convenience methods for setting up a temporary sandbox directory for tests
# that need to interact with the filesystem.


class TempDirMixin:
"""Text mixin for creating and deleting a temporary directory."""

def create_temp_dir(self):
"""Create a temporary directory and assign it into `self.temp_dir`.
Call `remove_temp_dir` later to delete it.
"""
self.temp_dir = bytestring_path(tempfile.mkdtemp())

def remove_temp_dir(self):
"""Delete the temporary directory created by `create_temp_dir`."""
if os.path.isdir(syspath(self.temp_dir)):
shutil.rmtree(syspath(self.temp_dir))


# Platform mocking.


Expand Down
Loading

0 comments on commit c05b3cf

Please sign in to comment.