Skip to content

Commit

Permalink
[beets/test] Share code between unittest and pytest impls
Browse files Browse the repository at this point in the history
  • Loading branch information
Arav K. committed Oct 12, 2024
1 parent 86085f2 commit 5785354
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 57 deletions.
77 changes: 48 additions & 29 deletions beets/test/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,58 @@
This module provides `pytest`-based fixtures for testing Beets.
"""

from __future__ import annotations

from contextlib import contextmanager
from pathlib import Path
from typing import Iterator

import pytest
from confuse import Configuration

import beets
import beets.util as util
from beets.library import Library


@contextmanager
def setup_config(lib_dir: Path) -> Iterator[Configuration]:
# Initialize configuration.
config = beets.config
config.sources = []
config.read(user=False, defaults=True)

# Set configuration defaults.
config["plugins"] = []
config["verbose"] = 1
config["ui"]["color"] = False
config["threaded"] = False
config["directory"] = str(lib_dir)

# Provide the configuration to the test.
yield config

# Reset the global configuration state.
beets.config.clear()
beets.config._materialized = False


@contextmanager
def setup_library(
lib_dir: Path,
db_loc: Path | None = None,
) -> Iterator[Library]:
# Create the Beets library object.
db_loc = util.bytestring_path(db_loc) if db_loc is not None else ":memory:"

Check failure on line 61 in beets/test/fixtures.py

View workflow job for this annotation

GitHub Actions / Check types with mypy

Incompatible types in assignment (expression has type "Union[bytes, str]", variable has type "Optional[Path]")
lib = Library(db_loc, str(lib_dir))

# Provide the library to the test.
yield lib

# Clean up the library.
lib._close()


@pytest.fixture(scope="session")
def resource_dir(pytestconfig: pytest.Config) -> Path:
"""
Expand Down Expand Up @@ -66,42 +108,19 @@ def config(

# 'confuse' looks at `HOME`, so we set it to a tmpdir.
monkeypatch.setenv("HOME", str(tmp_path))

# Initialize configuration.
config = beets.config
config.sources = []
config.read(user=False, defaults=True)

# Set configuration defaults.
config["plugins"] = []
config["verbose"] = 1
config["ui"]["color"] = False
config["threaded"] = False
config["directory"] = str(tmp_path)

# Provide the configuration to the test.
yield config

# Reset the global configuration state.
beets.config.clear()
beets.config._materialized = False
with setup_config(tmp_path / "libdir") as config:
yield config


@pytest.fixture
@pytest.mark.usefixtures("config")
def library(
tmp_path_factory: pytest.TempPathFactory,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> Iterator[Library]:
# Beets needs a location to store library contents.
lib_dir = tmp_path_factory.mktemp("lib_dir")
lib_dir = tmp_path / "libdir"
monkeypatch.setenv("BEETSDIR", str(lib_dir))

# Create the Beets library object.
lib = Library(":memory:", str(lib_dir))

# Provide the library to the test.
yield lib

# Clean up the library.
lib._close()
with setup_library(lib_dir, db_loc=None) as library:
yield library
67 changes: 39 additions & 28 deletions beets/test/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@
import shutil
import subprocess
import sys
import typing
import unittest
from contextlib import contextmanager
from contextlib import AbstractContextManager, contextmanager
from enum import Enum
from functools import cached_property
from io import StringIO
Expand All @@ -64,6 +65,8 @@
syspath,
)

from .fixtures import setup_config, setup_library


class LogCapture(logging.Handler):
def __init__(self):
Expand Down Expand Up @@ -169,9 +172,19 @@ class TestHelper(_common.Assertions):
"""

db_on_disk: ClassVar[bool] = False
contexts: list[AbstractContextManager[Any]]

# TODO automate teardown through hook registration

ContextValue = typing.TypeVar("ContextValue")

def setup_context(
self, manager: AbstractContextManager[ContextValue]
) -> ContextValue:
value = manager.__enter__()
self.contexts.append(manager)
return value

def setup_beets(self):
"""Setup pristine global configuration and library for testing.
Expand All @@ -194,46 +207,44 @@ def setup_beets(self):
Make sure you call ``teardown_beets()`` afterwards.
"""
self.create_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.sources = []
self.config.read(user=False, defaults=True)
self.contexts = []

self.config["plugins"] = []
self.config["verbose"] = 1
self.config["ui"]["color"] = False
self.config["threaded"] = False
# Set up the basic configuration with a HOME directory.
self.create_temp_dir()
temp_dir = Path(os.fsdecode(self.temp_dir))
self.libdir = temp_dir / "libdir"
self.libdir.mkdir(exist_ok=True)

self.setup_context(
patch.dict(
"os.environ",
{
"BEETSDIR": str(temp_dir),
"HOME": str(temp_dir),
},
)
)

self.libdir = os.path.join(self.temp_dir, b"libdir")
os.mkdir(syspath(self.libdir))
self.config["directory"] = os.fsdecode(self.libdir)
self.config = self.setup_context(setup_config(self.libdir))

if self.db_on_disk:
dbpath = util.bytestring_path(self.config["library"].as_filename())
db_loc = self.config["library"].as_filename()
else:
dbpath = ":memory:"
self.lib = Library(dbpath, self.libdir)
db_loc = None

self.lib = self.setup_context(setup_library(self.libdir, db_loc))

self.libdir = bytes(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()
while len(self.contexts) != 0:
self.contexts.pop().__exit__(None, None, None)
self.remove_temp_dir()
beets.config.clear()
beets.config._materialized = False

# Library fixtures methods

Expand Down

0 comments on commit 5785354

Please sign in to comment.