mirror of
https://github.com/beetbox/beets.git
synced 2026-02-08 16:34:12 +01:00
Merge 9372371004 into cdfb813910
This commit is contained in:
commit
8c42600354
30 changed files with 279 additions and 334 deletions
|
|
@ -14,10 +14,13 @@
|
|||
|
||||
"""Some common functionality for beets' test cases."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import beets
|
||||
import beets.library
|
||||
|
|
@ -28,6 +31,9 @@ from beets import importer, logging, util
|
|||
from beets.ui import commands
|
||||
from beets.util import syspath
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import pytest
|
||||
|
||||
beetsplug.__path__ = [
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
|
|
@ -118,85 +124,55 @@ def import_session(lib=None, loghandler=None, paths=[], query=[], cli=False):
|
|||
# Mock I/O.
|
||||
|
||||
|
||||
class InputError(Exception):
|
||||
def __init__(self, output=None):
|
||||
self.output = output
|
||||
|
||||
def __str__(self):
|
||||
msg = "Attempt to read with no input provided."
|
||||
if self.output is not None:
|
||||
msg += f" Output: {self.output!r}"
|
||||
return msg
|
||||
|
||||
|
||||
class DummyOut:
|
||||
encoding = "utf-8"
|
||||
|
||||
def __init__(self):
|
||||
self.buf = []
|
||||
|
||||
def write(self, s):
|
||||
self.buf.append(s)
|
||||
|
||||
def get(self):
|
||||
return "".join(self.buf)
|
||||
|
||||
def flush(self):
|
||||
self.clear()
|
||||
|
||||
def clear(self):
|
||||
self.buf = []
|
||||
class InputError(IOError):
|
||||
def __str__(self) -> str:
|
||||
return "Attempt to read with no input provided."
|
||||
|
||||
|
||||
class DummyIn:
|
||||
encoding = "utf-8"
|
||||
|
||||
def __init__(self, out=None):
|
||||
self.buf = []
|
||||
self.reads = 0
|
||||
self.out = out
|
||||
def __init__(self) -> None:
|
||||
self.buf: list[str] = []
|
||||
|
||||
def add(self, s):
|
||||
def add(self, s: str) -> None:
|
||||
self.buf.append(f"{s}\n")
|
||||
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
pass
|
||||
|
||||
def readline(self):
|
||||
def readline(self) -> str:
|
||||
if not self.buf:
|
||||
if self.out:
|
||||
raise InputError(self.out.get())
|
||||
else:
|
||||
raise InputError()
|
||||
self.reads += 1
|
||||
raise InputError
|
||||
|
||||
return self.buf.pop(0)
|
||||
|
||||
|
||||
class DummyIO:
|
||||
"""Mocks input and output streams for testing UI code."""
|
||||
"""Test helper that manages standard input and output."""
|
||||
|
||||
def __init__(self):
|
||||
self.stdout = DummyOut()
|
||||
self.stdin = DummyIn(self.stdout)
|
||||
def __init__(
|
||||
self,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capteesys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
self._capteesys = capteesys
|
||||
self.stdin = DummyIn()
|
||||
|
||||
def addinput(self, s):
|
||||
self.stdin.add(s)
|
||||
monkeypatch.setattr("sys.stdin", self.stdin)
|
||||
|
||||
def getoutput(self):
|
||||
res = self.stdout.get()
|
||||
self.stdout.clear()
|
||||
return res
|
||||
def addinput(self, text: str) -> None:
|
||||
"""Simulate user typing into stdin."""
|
||||
self.stdin.add(text)
|
||||
|
||||
def readcount(self):
|
||||
return self.stdin.reads
|
||||
def getoutput(self) -> str:
|
||||
"""Get the standard output captured so far.
|
||||
|
||||
def install(self):
|
||||
sys.stdin = self.stdin
|
||||
sys.stdout = self.stdout
|
||||
|
||||
def restore(self):
|
||||
sys.stdin = sys.__stdin__
|
||||
sys.stdout = sys.__stdout__
|
||||
Note: it clears the internal buffer, so subsequent calls will only
|
||||
return *new* output.
|
||||
"""
|
||||
# Using capteesys allows you to see output in the console if the test fails
|
||||
return self._capteesys.readouterr().out
|
||||
|
||||
|
||||
# Utility.
|
||||
|
|
|
|||
|
|
@ -15,9 +15,6 @@
|
|||
"""This module includes various helpers that provide fixtures, capture
|
||||
information or mock the environment.
|
||||
|
||||
- The `control_stdin` and `capture_stdout` context managers allow one to
|
||||
interact with the user interface.
|
||||
|
||||
- `has_program` checks the presence of a command on the system.
|
||||
|
||||
- The `ImportSessionFixture` allows one to run importer code while
|
||||
|
|
@ -38,12 +35,12 @@ from contextlib import contextmanager
|
|||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from functools import cached_property
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from tempfile import gettempdir, mkdtemp, mkstemp
|
||||
from typing import Any, ClassVar
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from mediafile import Image, MediaFile
|
||||
|
||||
|
|
@ -83,41 +80,6 @@ def capture_log(logger="beets"):
|
|||
log.removeHandler(capture)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def control_stdin(input=None):
|
||||
"""Sends ``input`` to stdin.
|
||||
|
||||
>>> with control_stdin('yes'):
|
||||
... input()
|
||||
'yes'
|
||||
"""
|
||||
org = sys.stdin
|
||||
sys.stdin = StringIO(input)
|
||||
try:
|
||||
yield sys.stdin
|
||||
finally:
|
||||
sys.stdin = org
|
||||
|
||||
|
||||
@contextmanager
|
||||
def capture_stdout():
|
||||
"""Save stdout in a StringIO.
|
||||
|
||||
>>> with capture_stdout() as output:
|
||||
... print('spam')
|
||||
...
|
||||
>>> output.getvalue()
|
||||
'spam'
|
||||
"""
|
||||
org = sys.stdout
|
||||
sys.stdout = capture = StringIO()
|
||||
try:
|
||||
yield sys.stdout
|
||||
finally:
|
||||
sys.stdout = org
|
||||
print(capture.getvalue())
|
||||
|
||||
|
||||
def has_program(cmd, args=["--version"]):
|
||||
"""Returns `True` if `cmd` can be executed."""
|
||||
full_cmd = [cmd, *args]
|
||||
|
|
@ -163,21 +125,31 @@ NEEDS_REFLINK = unittest.skipUnless(
|
|||
)
|
||||
|
||||
|
||||
class IOMixin:
|
||||
@cached_property
|
||||
def io(self) -> _common.DummyIO:
|
||||
return _common.DummyIO()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.io.install()
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
self.io.restore()
|
||||
class RunMixin:
|
||||
def run_command(self, *args, **kwargs):
|
||||
"""Run a beets command with an arbitrary amount of arguments. The
|
||||
Library` defaults to `self.lib`, but can be overridden with
|
||||
the keyword argument `lib`.
|
||||
"""
|
||||
sys.argv = ["beet"] # avoid leakage from test suite args
|
||||
lib = None
|
||||
if hasattr(self, "lib"):
|
||||
lib = self.lib
|
||||
lib = kwargs.get("lib", lib)
|
||||
beets.ui._raw_main(list(args), lib)
|
||||
|
||||
|
||||
class TestHelper(ConfigMixin):
|
||||
@pytest.mark.usefixtures("io")
|
||||
class IOMixin(RunMixin):
|
||||
io: _common.DummyIO
|
||||
|
||||
def run_with_output(self, *args):
|
||||
self.io.getoutput()
|
||||
self.run_command(*args)
|
||||
return self.io.getoutput()
|
||||
|
||||
|
||||
class TestHelper(RunMixin, ConfigMixin):
|
||||
"""Helper mixin for high-level cli and plugin tests.
|
||||
|
||||
This mixin provides methods to isolate beets' global state provide
|
||||
|
|
@ -392,25 +364,6 @@ class TestHelper(ConfigMixin):
|
|||
|
||||
return path
|
||||
|
||||
# Running beets commands
|
||||
|
||||
def run_command(self, *args, **kwargs):
|
||||
"""Run a beets command with an arbitrary amount of arguments. The
|
||||
Library` defaults to `self.lib`, but can be overridden with
|
||||
the keyword argument `lib`.
|
||||
"""
|
||||
sys.argv = ["beet"] # avoid leakage from test suite args
|
||||
lib = None
|
||||
if hasattr(self, "lib"):
|
||||
lib = self.lib
|
||||
lib = kwargs.get("lib", lib)
|
||||
beets.ui._raw_main(list(args), lib)
|
||||
|
||||
def run_with_output(self, *args):
|
||||
with capture_stdout() as out:
|
||||
self.run_command(*args)
|
||||
return out.getvalue()
|
||||
|
||||
# Safe file operations
|
||||
|
||||
def create_temp_dir(self, **kwargs) -> str:
|
||||
|
|
@ -758,10 +711,7 @@ class TerminalImportSessionFixture(TerminalImportSession):
|
|||
class TerminalImportMixin(IOMixin, ImportHelper):
|
||||
"""Provides_a terminal importer for the import session."""
|
||||
|
||||
io: _common.DummyIO
|
||||
|
||||
def _get_import_session(self, import_dir: bytes) -> importer.ImportSession:
|
||||
self.io.install()
|
||||
return TerminalImportSessionFixture(
|
||||
self.lib,
|
||||
loghandler=None,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import pytest
|
|||
|
||||
from beets.autotag.distance import Distance
|
||||
from beets.dbcore.query import Query
|
||||
from beets.test._common import DummyIO
|
||||
from beets.test.helper import ConfigMixin
|
||||
from beets.util import cached_classproperty
|
||||
|
||||
|
|
@ -60,3 +61,24 @@ def clear_cached_classproperty():
|
|||
def config():
|
||||
"""Provide a fresh beets configuration for a module, when requested."""
|
||||
return ConfigMixin().config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def io(
|
||||
request: pytest.FixtureRequest,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capteesys: pytest.CaptureFixture[str],
|
||||
) -> DummyIO:
|
||||
"""Fixture for tests that need controllable stdin and captured stdout.
|
||||
|
||||
This fixture builds a per-test ``DummyIO`` helper and exposes it to the
|
||||
test. When used on a test class, it attaches the helper as ``self.io``
|
||||
attribute to make it available to all test methods, including
|
||||
``unittest.TestCase``-based ones.
|
||||
"""
|
||||
io = DummyIO(monkeypatch, capteesys)
|
||||
|
||||
if request.instance:
|
||||
request.instance.io = io
|
||||
|
||||
return io
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
"""Tests for the 'bareasc' plugin."""
|
||||
|
||||
from beets import logging
|
||||
from beets.test.helper import PluginTestCase, capture_stdout
|
||||
from beets.test.helper import IOMixin, PluginTestCase
|
||||
|
||||
|
||||
class BareascPluginTest(PluginTestCase):
|
||||
class BareascPluginTest(IOMixin, PluginTestCase):
|
||||
"""Test bare ASCII query matching."""
|
||||
|
||||
plugin = "bareasc"
|
||||
|
|
@ -65,16 +65,12 @@ class BareascPluginTest(PluginTestCase):
|
|||
|
||||
def test_bareasc_list_output(self):
|
||||
"""Bare-ASCII version of list command - check output."""
|
||||
with capture_stdout() as output:
|
||||
self.run_command("bareasc", "with accents")
|
||||
self.run_command("bareasc", "with accents")
|
||||
|
||||
assert "Antonin Dvorak" in output.getvalue()
|
||||
assert "Antonin Dvorak" in self.io.getoutput()
|
||||
|
||||
def test_bareasc_format_output(self):
|
||||
"""Bare-ASCII version of list -f command - check output."""
|
||||
with capture_stdout() as output:
|
||||
self.run_command(
|
||||
"bareasc", "with accents", "-f", "$artist:: $title"
|
||||
)
|
||||
self.run_command("bareasc", "with accents", "-f", "$artist:: $title")
|
||||
|
||||
assert "Antonin Dvorak:: with accents\n" == output.getvalue()
|
||||
assert "Antonin Dvorak:: with accents\n" == self.io.getoutput()
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import yaml
|
|||
from beets.test.helper import PluginTestCase
|
||||
from beets.util import bluelet
|
||||
|
||||
bpd = pytest.importorskip("beetsplug.bpd")
|
||||
bpd = pytest.importorskip("beetsplug.bpd", exc_type=ImportError)
|
||||
|
||||
|
||||
class CommandParseTest(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ from beets.test import _common
|
|||
from beets.test.helper import (
|
||||
AsIsImporterMixin,
|
||||
ImportHelper,
|
||||
IOMixin,
|
||||
PluginTestCase,
|
||||
capture_log,
|
||||
control_stdin,
|
||||
)
|
||||
from beetsplug import convert
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ class ConvertMixin:
|
|||
return path.read_bytes().endswith(tag.encode("utf-8"))
|
||||
|
||||
|
||||
class ConvertTestCase(ConvertMixin, PluginTestCase):
|
||||
class ConvertTestCase(IOMixin, ConvertMixin, PluginTestCase):
|
||||
db_on_disk = True
|
||||
plugin = "convert"
|
||||
|
||||
|
|
@ -157,8 +157,8 @@ class ConvertCliTest(ConvertTestCase, ConvertCommand):
|
|||
}
|
||||
|
||||
def test_convert(self):
|
||||
with control_stdin("y"):
|
||||
self.run_convert()
|
||||
self.io.addinput("y")
|
||||
self.run_convert()
|
||||
assert self.file_endswith(self.converted_mp3, "mp3")
|
||||
|
||||
def test_convert_with_auto_confirmation(self):
|
||||
|
|
@ -166,22 +166,22 @@ class ConvertCliTest(ConvertTestCase, ConvertCommand):
|
|||
assert self.file_endswith(self.converted_mp3, "mp3")
|
||||
|
||||
def test_reject_confirmation(self):
|
||||
with control_stdin("n"):
|
||||
self.run_convert()
|
||||
self.io.addinput("n")
|
||||
self.run_convert()
|
||||
assert not self.converted_mp3.exists()
|
||||
|
||||
def test_convert_keep_new(self):
|
||||
assert os.path.splitext(self.item.path)[1] == b".ogg"
|
||||
|
||||
with control_stdin("y"):
|
||||
self.run_convert("--keep-new")
|
||||
self.io.addinput("y")
|
||||
self.run_convert("--keep-new")
|
||||
|
||||
self.item.load()
|
||||
assert os.path.splitext(self.item.path)[1] == b".mp3"
|
||||
|
||||
def test_format_option(self):
|
||||
with control_stdin("y"):
|
||||
self.run_convert("--format", "opus")
|
||||
self.io.addinput("y")
|
||||
self.run_convert("--format", "opus")
|
||||
assert self.file_endswith(self.convert_dest / "converted.ops", "opus")
|
||||
|
||||
def test_embed_album_art(self):
|
||||
|
|
@ -192,8 +192,8 @@ class ConvertCliTest(ConvertTestCase, ConvertCommand):
|
|||
with open(os.path.join(image_path), "rb") as f:
|
||||
image_data = f.read()
|
||||
|
||||
with control_stdin("y"):
|
||||
self.run_convert()
|
||||
self.io.addinput("y")
|
||||
self.run_convert()
|
||||
mediafile = MediaFile(self.converted_mp3)
|
||||
assert mediafile.images[0].data == image_data
|
||||
|
||||
|
|
@ -215,26 +215,26 @@ class ConvertCliTest(ConvertTestCase, ConvertCommand):
|
|||
|
||||
def test_no_transcode_when_maxbr_set_high_and_different_formats(self):
|
||||
self.config["convert"]["max_bitrate"] = 5000
|
||||
with control_stdin("y"):
|
||||
self.run_convert()
|
||||
self.io.addinput("y")
|
||||
self.run_convert()
|
||||
assert self.file_endswith(self.converted_mp3, "mp3")
|
||||
|
||||
def test_transcode_when_maxbr_set_low_and_different_formats(self):
|
||||
self.config["convert"]["max_bitrate"] = 5
|
||||
with control_stdin("y"):
|
||||
self.run_convert()
|
||||
self.io.addinput("y")
|
||||
self.run_convert()
|
||||
assert self.file_endswith(self.converted_mp3, "mp3")
|
||||
|
||||
def test_transcode_when_maxbr_set_to_none_and_different_formats(self):
|
||||
with control_stdin("y"):
|
||||
self.run_convert()
|
||||
self.io.addinput("y")
|
||||
self.run_convert()
|
||||
assert self.file_endswith(self.converted_mp3, "mp3")
|
||||
|
||||
def test_no_transcode_when_maxbr_set_high_and_same_formats(self):
|
||||
self.config["convert"]["max_bitrate"] = 5000
|
||||
self.config["convert"]["format"] = "ogg"
|
||||
with control_stdin("y"):
|
||||
self.run_convert()
|
||||
self.io.addinput("y")
|
||||
self.run_convert()
|
||||
assert not self.file_endswith(
|
||||
self.convert_dest / "converted.ogg", "ogg"
|
||||
)
|
||||
|
|
@ -243,8 +243,8 @@ class ConvertCliTest(ConvertTestCase, ConvertCommand):
|
|||
self.config["convert"]["max_bitrate"] = 5000
|
||||
self.config["convert"]["format"] = "ogg"
|
||||
|
||||
with control_stdin("y"):
|
||||
self.run_convert("--force")
|
||||
self.io.addinput("y")
|
||||
self.run_convert("--force")
|
||||
|
||||
converted = self.convert_dest / "converted.ogg"
|
||||
assert self.file_endswith(converted, "ogg")
|
||||
|
|
@ -252,21 +252,21 @@ class ConvertCliTest(ConvertTestCase, ConvertCommand):
|
|||
def test_transcode_when_maxbr_set_low_and_same_formats(self):
|
||||
self.config["convert"]["max_bitrate"] = 5
|
||||
self.config["convert"]["format"] = "ogg"
|
||||
with control_stdin("y"):
|
||||
self.run_convert()
|
||||
self.io.addinput("y")
|
||||
self.run_convert()
|
||||
assert self.file_endswith(self.convert_dest / "converted.ogg", "ogg")
|
||||
|
||||
def test_transcode_when_maxbr_set_to_none_and_same_formats(self):
|
||||
self.config["convert"]["format"] = "ogg"
|
||||
with control_stdin("y"):
|
||||
self.run_convert()
|
||||
self.io.addinput("y")
|
||||
self.run_convert()
|
||||
assert not self.file_endswith(
|
||||
self.convert_dest / "converted.ogg", "ogg"
|
||||
)
|
||||
|
||||
def test_playlist(self):
|
||||
with control_stdin("y"):
|
||||
self.run_convert("--playlist", "playlist.m3u8")
|
||||
self.io.addinput("y")
|
||||
self.run_convert("--playlist", "playlist.m3u8")
|
||||
assert (self.convert_dest / "playlist.m3u8").exists()
|
||||
|
||||
def test_playlist_pretend(self):
|
||||
|
|
@ -282,8 +282,8 @@ class ConvertCliTest(ConvertTestCase, ConvertCommand):
|
|||
|
||||
[item] = self.add_item_fixtures(ext="ogg")
|
||||
|
||||
with control_stdin("y"):
|
||||
self.run_convert_path(item, "--format", "opus", "--force")
|
||||
self.io.addinput("y")
|
||||
self.run_convert_path(item, "--format", "opus", "--force")
|
||||
|
||||
converted = self.convert_dest / "converted.ops"
|
||||
assert self.file_endswith(converted, "opus")
|
||||
|
|
@ -309,23 +309,23 @@ class NeverConvertLossyFilesTest(ConvertTestCase, ConvertCommand):
|
|||
|
||||
def test_transcode_from_lossless(self):
|
||||
[item] = self.add_item_fixtures(ext="flac")
|
||||
with control_stdin("y"):
|
||||
self.run_convert_path(item)
|
||||
self.io.addinput("y")
|
||||
self.run_convert_path(item)
|
||||
converted = self.convert_dest / "converted.mp3"
|
||||
assert self.file_endswith(converted, "mp3")
|
||||
|
||||
def test_transcode_from_lossy(self):
|
||||
self.config["convert"]["never_convert_lossy_files"] = False
|
||||
[item] = self.add_item_fixtures(ext="ogg")
|
||||
with control_stdin("y"):
|
||||
self.run_convert_path(item)
|
||||
self.io.addinput("y")
|
||||
self.run_convert_path(item)
|
||||
converted = self.convert_dest / "converted.mp3"
|
||||
assert self.file_endswith(converted, "mp3")
|
||||
|
||||
def test_transcode_from_lossy_prevented(self):
|
||||
[item] = self.add_item_fixtures(ext="ogg")
|
||||
with control_stdin("y"):
|
||||
self.run_convert_path(item)
|
||||
self.io.addinput("y")
|
||||
self.run_convert_path(item)
|
||||
converted = self.convert_dest / "converted.ogg"
|
||||
assert not self.file_endswith(converted, "mp3")
|
||||
|
||||
|
|
@ -336,8 +336,8 @@ class NeverConvertLossyFilesTest(ConvertTestCase, ConvertCommand):
|
|||
}
|
||||
[item] = self.add_item_fixtures(ext="ogg")
|
||||
|
||||
with control_stdin("y"):
|
||||
self.run_convert_path(item, "--format", "opus", "--force")
|
||||
self.io.addinput("y")
|
||||
self.run_convert_path(item, "--format", "opus", "--force")
|
||||
|
||||
converted = self.convert_dest / "converted.ops"
|
||||
assert self.file_endswith(converted, "opus")
|
||||
|
|
|
|||
|
|
@ -17,15 +17,16 @@ from typing import ClassVar
|
|||
from unittest.mock import patch
|
||||
|
||||
from beets.dbcore.query import TrueQuery
|
||||
from beets.importer import Action
|
||||
from beets.library import Item
|
||||
from beets.test import _common
|
||||
from beets.test.helper import (
|
||||
AutotagImportTestCase,
|
||||
AutotagStub,
|
||||
BeetsTestCase,
|
||||
IOMixin,
|
||||
PluginMixin,
|
||||
TerminalImportMixin,
|
||||
control_stdin,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -103,24 +104,26 @@ class EditMixin(PluginMixin):
|
|||
"""
|
||||
m = ModifyFileMocker(**modify_file_args)
|
||||
with patch("beetsplug.edit.edit", side_effect=m.action):
|
||||
with control_stdin("\n".join(stdin)):
|
||||
self.importer.run()
|
||||
for char in stdin:
|
||||
self.importer.add_choice(char)
|
||||
self.importer.run()
|
||||
|
||||
def run_mocked_command(self, modify_file_args={}, stdin=[], args=[]):
|
||||
"""Run the edit command, with mocked stdin and yaml writing, and
|
||||
passing `args` to `run_command`."""
|
||||
m = ModifyFileMocker(**modify_file_args)
|
||||
with patch("beetsplug.edit.edit", side_effect=m.action):
|
||||
with control_stdin("\n".join(stdin)):
|
||||
self.run_command("edit", *args)
|
||||
for char in stdin:
|
||||
self.io.addinput(char)
|
||||
self.run_command("edit", *args)
|
||||
|
||||
|
||||
@_common.slow_test()
|
||||
@patch("beets.library.Item.write")
|
||||
class EditCommandTest(EditMixin, BeetsTestCase):
|
||||
class EditCommandTest(IOMixin, EditMixin, BeetsTestCase):
|
||||
"""Black box tests for `beetsplug.edit`. Command line interaction is
|
||||
simulated using `test.helper.control_stdin()`, and yaml editing via an
|
||||
external editor is simulated using `ModifyFileMocker`.
|
||||
simulated using mocked stdin, and yaml editing via an external editor is
|
||||
simulated using `ModifyFileMocker`.
|
||||
"""
|
||||
|
||||
ALBUM_COUNT = 1
|
||||
|
|
@ -412,7 +415,7 @@ class EditDuringImporterNonSingletonTest(EditDuringImporterTestCase):
|
|||
self.run_mocked_interpreter(
|
||||
{},
|
||||
# 1, Apply changes.
|
||||
["1", "a"],
|
||||
["1", Action.APPLY],
|
||||
)
|
||||
|
||||
# Retag and edit track titles. On retag, the importer will reset items
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ import re # used to test csv format
|
|||
from xml.etree import ElementTree
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
from beets.test.helper import PluginTestCase
|
||||
from beets.test.helper import IOMixin, PluginTestCase
|
||||
|
||||
|
||||
class ExportPluginTest(PluginTestCase):
|
||||
class ExportPluginTest(IOMixin, PluginTestCase):
|
||||
plugin = "export"
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ import os
|
|||
import sys
|
||||
|
||||
from beets import util
|
||||
from beets.test.helper import PluginTestCase
|
||||
from beets.test.helper import IOMixin, PluginTestCase
|
||||
|
||||
|
||||
class FetchartCliTest(PluginTestCase):
|
||||
class FetchartCliTest(IOMixin, PluginTestCase):
|
||||
plugin = "fetchart"
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -454,7 +454,9 @@ def test_custom_words(
|
|||
assert ftintitle.contains_feat(given, custom_words) is expected
|
||||
|
||||
|
||||
def test_album_template_value():
|
||||
def test_album_template_value(config):
|
||||
config["ftintitle"]["custom_words"] = []
|
||||
|
||||
album = Album()
|
||||
album["albumartist"] = "Foo ft. Bar"
|
||||
assert ftintitle._album_artist_no_feat(album) == "Foo"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import os
|
|||
import time
|
||||
|
||||
from beets import importer, plugins
|
||||
from beets.test.helper import AutotagImportTestCase, PluginMixin, control_stdin
|
||||
from beets.test.helper import AutotagImportTestCase, IOMixin, PluginMixin
|
||||
from beets.util import syspath
|
||||
from beetsplug.importsource import ImportSourcePlugin
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ def preserve_plugin_listeners():
|
|||
ImportSourcePlugin.listeners = _listeners
|
||||
|
||||
|
||||
class ImportSourceTest(PluginMixin, AutotagImportTestCase):
|
||||
class ImportSourceTest(IOMixin, PluginMixin, AutotagImportTestCase):
|
||||
plugin = "importsource"
|
||||
preload_plugin = False
|
||||
|
||||
|
|
@ -50,31 +50,29 @@ class ImportSourceTest(PluginMixin, AutotagImportTestCase):
|
|||
self.all_items = self.lib.albums().get().items()
|
||||
self.item_to_remove = self.all_items[0]
|
||||
|
||||
def interact(self, stdin_input: str):
|
||||
with control_stdin(stdin_input):
|
||||
self.run_command(
|
||||
"remove",
|
||||
f"path:{syspath(self.item_to_remove.path)}",
|
||||
)
|
||||
def interact(self, stdin: list[str]):
|
||||
for char in stdin:
|
||||
self.io.addinput(char)
|
||||
self.run_command("remove", f"path:{syspath(self.item_to_remove.path)}")
|
||||
|
||||
def test_do_nothing(self):
|
||||
self.interact("N")
|
||||
self.interact(["N"])
|
||||
|
||||
assert os.path.exists(self.item_to_remove.source_path)
|
||||
|
||||
def test_remove_single(self):
|
||||
self.interact("y\nD")
|
||||
self.interact(["y", "D"])
|
||||
|
||||
assert not os.path.exists(self.item_to_remove.source_path)
|
||||
|
||||
def test_remove_all_from_single(self):
|
||||
self.interact("y\nR\ny")
|
||||
self.interact(["y", "R", "y"])
|
||||
|
||||
for item in self.all_items:
|
||||
assert not os.path.exists(item.source_path)
|
||||
|
||||
def test_stop_suggesting(self):
|
||||
self.interact("y\nS")
|
||||
self.interact(["y", "S"])
|
||||
|
||||
for item in self.all_items:
|
||||
assert os.path.exists(item.source_path)
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@
|
|||
|
||||
from mediafile import MediaFile
|
||||
|
||||
from beets.test.helper import PluginTestCase
|
||||
from beets.test.helper import IOMixin, PluginTestCase
|
||||
from beets.util import displayable_path
|
||||
|
||||
|
||||
class InfoTest(PluginTestCase):
|
||||
class InfoTest(IOMixin, PluginTestCase):
|
||||
plugin = "info"
|
||||
|
||||
def test_path(self):
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ from unittest.mock import Mock, patch
|
|||
import pytest
|
||||
|
||||
from beets.test import _common
|
||||
from beets.test.helper import PluginTestCase
|
||||
from beets.test.helper import IOMixin, PluginTestCase
|
||||
from beetsplug import lastgenre
|
||||
|
||||
|
||||
class LastGenrePluginTest(PluginTestCase):
|
||||
class LastGenrePluginTest(IOMixin, PluginTestCase):
|
||||
plugin = "lastgenre"
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@
|
|||
|
||||
"""Tests for the 'limit' plugin."""
|
||||
|
||||
from beets.test.helper import PluginTestCase
|
||||
from beets.test.helper import IOMixin, PluginTestCase
|
||||
|
||||
|
||||
class LimitPluginTest(PluginTestCase):
|
||||
class LimitPluginTest(IOMixin, PluginTestCase):
|
||||
"""Unit tests for LimitPlugin
|
||||
|
||||
Note: query prefix tests do not work correctly with `run_with_output`.
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ from beets.test.helper import (
|
|||
AutotagImportTestCase,
|
||||
PluginMixin,
|
||||
TerminalImportMixin,
|
||||
capture_stdout,
|
||||
control_stdin,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -34,10 +32,10 @@ class MBSubmitPluginTest(
|
|||
|
||||
def test_print_tracks_output(self):
|
||||
"""Test the output of the "print tracks" choice."""
|
||||
with capture_stdout() as output:
|
||||
with control_stdin("\n".join(["p", "s"])):
|
||||
# Print tracks; Skip
|
||||
self.importer.run()
|
||||
self.io.addinput("p")
|
||||
self.io.addinput("s")
|
||||
# Print tracks; Skip
|
||||
self.importer.run()
|
||||
|
||||
# Manually build the string for comparing the output.
|
||||
tracklist = (
|
||||
|
|
@ -45,17 +43,19 @@ class MBSubmitPluginTest(
|
|||
"01. Tag Track 1 - Tag Artist (0:01)\n"
|
||||
"02. Tag Track 2 - Tag Artist (0:01)"
|
||||
)
|
||||
assert tracklist in output.getvalue()
|
||||
assert tracklist in self.io.getoutput()
|
||||
|
||||
def test_print_tracks_output_as_tracks(self):
|
||||
"""Test the output of the "print tracks" choice, as singletons."""
|
||||
with capture_stdout() as output:
|
||||
with control_stdin("\n".join(["t", "s", "p", "s"])):
|
||||
# as Tracks; Skip; Print tracks; Skip
|
||||
self.importer.run()
|
||||
self.io.addinput("t")
|
||||
self.io.addinput("s")
|
||||
self.io.addinput("p")
|
||||
self.io.addinput("s")
|
||||
# as Tracks; Skip; Print tracks; Skip
|
||||
self.importer.run()
|
||||
|
||||
# Manually build the string for comparing the output.
|
||||
tracklist = (
|
||||
"Open files with Picard? 02. Tag Track 2 - Tag Artist (0:01)"
|
||||
)
|
||||
assert tracklist in output.getvalue()
|
||||
assert tracklist in self.io.getoutput()
|
||||
|
|
|
|||
|
|
@ -3,20 +3,23 @@ import uuid
|
|||
import pytest
|
||||
|
||||
from beets.library import Album
|
||||
from beets.test.helper import PluginMixin, TestHelper
|
||||
from beets.test.helper import IOMixin, PluginMixin, TestHelper
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def helper():
|
||||
def helper(request):
|
||||
helper = TestHelper()
|
||||
helper.setup_beets()
|
||||
|
||||
yield helper
|
||||
request.instance.lib = helper.lib
|
||||
|
||||
yield
|
||||
|
||||
helper.teardown_beets()
|
||||
|
||||
|
||||
class TestMissingAlbums(PluginMixin):
|
||||
@pytest.mark.usefixtures("helper")
|
||||
class TestMissingAlbums(IOMixin, PluginMixin):
|
||||
plugin = "missing"
|
||||
album_in_lib = Album(
|
||||
album="Album",
|
||||
|
|
@ -47,15 +50,13 @@ class TestMissingAlbums(PluginMixin):
|
|||
],
|
||||
)
|
||||
def test_missing_artist_albums(
|
||||
self, requests_mock, helper, release_from_mb, expected_output
|
||||
self, requests_mock, release_from_mb, expected_output
|
||||
):
|
||||
helper.lib.add(self.album_in_lib)
|
||||
self.lib.add(self.album_in_lib)
|
||||
requests_mock.get(
|
||||
f"/ws/2/release-group?artist={self.album_in_lib.mb_albumartistid}",
|
||||
json={"release-groups": [release_from_mb]},
|
||||
)
|
||||
|
||||
with self.configure_plugin({}):
|
||||
assert (
|
||||
helper.run_with_output("missing", "--album") == expected_output
|
||||
)
|
||||
assert self.run_with_output("missing", "--album") == expected_output
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ from unittest.mock import ANY, patch
|
|||
|
||||
import pytest
|
||||
|
||||
from beets.test.helper import CleanupModulesMixin, PluginTestCase, control_stdin
|
||||
from beets.test.helper import CleanupModulesMixin, IOMixin, PluginTestCase
|
||||
from beets.ui import UserError
|
||||
from beets.util import open_anything
|
||||
from beetsplug.play import PlayPlugin
|
||||
|
||||
|
||||
@patch("beetsplug.play.util.interactive_open")
|
||||
class PlayPluginTest(CleanupModulesMixin, PluginTestCase):
|
||||
class PlayPluginTest(IOMixin, CleanupModulesMixin, PluginTestCase):
|
||||
modules = (PlayPlugin.__module__,)
|
||||
plugin = "play"
|
||||
|
||||
|
|
@ -127,8 +127,8 @@ class PlayPluginTest(CleanupModulesMixin, PluginTestCase):
|
|||
self.config["play"]["warning_threshold"] = 1
|
||||
self.add_item(title="another NiceTitle")
|
||||
|
||||
with control_stdin("a"):
|
||||
self.run_command("play", "nice")
|
||||
self.io.addinput("a")
|
||||
self.run_command("play", "nice")
|
||||
|
||||
open_mock.assert_not_called()
|
||||
|
||||
|
|
@ -138,12 +138,12 @@ class PlayPluginTest(CleanupModulesMixin, PluginTestCase):
|
|||
|
||||
expected_playlist = f"{self.item.filepath}\n{self.other_item.filepath}"
|
||||
|
||||
with control_stdin("a"):
|
||||
self.run_and_assert(
|
||||
open_mock,
|
||||
["-y", "NiceTitle"],
|
||||
expected_playlist=expected_playlist,
|
||||
)
|
||||
self.io.addinput("a")
|
||||
self.run_and_assert(
|
||||
open_mock,
|
||||
["-y", "NiceTitle"],
|
||||
expected_playlist=expected_playlist,
|
||||
)
|
||||
|
||||
def test_command_failed(self, open_mock):
|
||||
open_mock.side_effect = OSError("some reason")
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import pytest
|
|||
from beets import config
|
||||
from beets.dbcore.query import FixedFieldSort, MultipleSort, NullSort
|
||||
from beets.library import Album, Item, parse_query_string
|
||||
from beets.test.helper import BeetsTestCase, PluginTestCase
|
||||
from beets.test.helper import BeetsTestCase, IOMixin, PluginTestCase
|
||||
from beets.ui import UserError
|
||||
from beets.util import CHAR_REPLACE, syspath
|
||||
from beetsplug.smartplaylist import SmartPlaylistPlugin
|
||||
|
|
@ -458,7 +458,7 @@ class SmartPlaylistTest(BeetsTestCase):
|
|||
assert content.count(b"/item2.mp3") == 1
|
||||
|
||||
|
||||
class SmartPlaylistCLITest(PluginTestCase):
|
||||
class SmartPlaylistCLITest(IOMixin, PluginTestCase):
|
||||
plugin = "smartplaylist"
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ from datetime import datetime
|
|||
import pytest
|
||||
from confuse import ConfigValueError
|
||||
|
||||
from beets.test.helper import PluginTestCase
|
||||
from beets.test.helper import IOMixin, PluginTestCase
|
||||
|
||||
|
||||
class TypesPluginTest(PluginTestCase):
|
||||
class TypesPluginTest(IOMixin, PluginTestCase):
|
||||
plugin = "types"
|
||||
|
||||
def test_integer_modify_and_query(self):
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
from mediafile import MediaFile
|
||||
|
||||
from beets.library import Item
|
||||
from beets.test.helper import PluginTestCase, control_stdin
|
||||
from beets.test.helper import IOMixin, PluginTestCase
|
||||
from beets.util import syspath
|
||||
from beetsplug.zero import ZeroPlugin
|
||||
|
||||
|
||||
class ZeroPluginTest(PluginTestCase):
|
||||
class ZeroPluginTest(IOMixin, PluginTestCase):
|
||||
plugin = "zero"
|
||||
preload_plugin = False
|
||||
|
||||
|
|
@ -102,12 +102,10 @@ class ZeroPluginTest(PluginTestCase):
|
|||
item.write()
|
||||
item_id = item.id
|
||||
|
||||
with (
|
||||
self.configure_plugin(
|
||||
{"fields": ["comments"], "update_database": True, "auto": False}
|
||||
),
|
||||
control_stdin("y"),
|
||||
with self.configure_plugin(
|
||||
{"fields": ["comments"], "update_database": True, "auto": False}
|
||||
):
|
||||
self.io.addinput("y")
|
||||
self.run_command("zero")
|
||||
|
||||
mf = MediaFile(syspath(item.path))
|
||||
|
|
@ -125,16 +123,14 @@ class ZeroPluginTest(PluginTestCase):
|
|||
item.write()
|
||||
item_id = item.id
|
||||
|
||||
with (
|
||||
self.configure_plugin(
|
||||
{
|
||||
"fields": ["comments"],
|
||||
"update_database": False,
|
||||
"auto": False,
|
||||
}
|
||||
),
|
||||
control_stdin("y"),
|
||||
with self.configure_plugin(
|
||||
{
|
||||
"fields": ["comments"],
|
||||
"update_database": False,
|
||||
"auto": False,
|
||||
}
|
||||
):
|
||||
self.io.addinput("y")
|
||||
self.run_command("zero")
|
||||
|
||||
mf = MediaFile(syspath(item.path))
|
||||
|
|
@ -187,7 +183,8 @@ class ZeroPluginTest(PluginTestCase):
|
|||
|
||||
item_id = item.id
|
||||
|
||||
with self.configure_plugin({"fields": []}), control_stdin("y"):
|
||||
with self.configure_plugin({"fields": []}):
|
||||
self.io.addinput("y")
|
||||
self.run_command("zero")
|
||||
|
||||
item = self.lib.get_item(item_id)
|
||||
|
|
@ -203,12 +200,10 @@ class ZeroPluginTest(PluginTestCase):
|
|||
|
||||
item_id = item.id
|
||||
|
||||
with (
|
||||
self.configure_plugin(
|
||||
{"fields": ["year"], "keep_fields": ["comments"]}
|
||||
),
|
||||
control_stdin("y"),
|
||||
with self.configure_plugin(
|
||||
{"fields": ["year"], "keep_fields": ["comments"]}
|
||||
):
|
||||
self.io.addinput("y")
|
||||
self.run_command("zero")
|
||||
|
||||
item = self.lib.get_item(item_id)
|
||||
|
|
@ -303,12 +298,10 @@ class ZeroPluginTest(PluginTestCase):
|
|||
)
|
||||
item.write()
|
||||
item_id = item.id
|
||||
with (
|
||||
self.configure_plugin(
|
||||
{"fields": ["comments"], "update_database": True, "auto": False}
|
||||
),
|
||||
control_stdin("n"),
|
||||
with self.configure_plugin(
|
||||
{"fields": ["comments"], "update_database": True, "auto": False}
|
||||
):
|
||||
self.io.addinput("n")
|
||||
self.run_command("zero")
|
||||
|
||||
mf = MediaFile(syspath(item.path))
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from datetime import datetime
|
|||
|
||||
from beets.library import Item
|
||||
from beets.test import _common
|
||||
from beets.test.helper import PluginTestCase
|
||||
from beets.test.helper import IOMixin, PluginTestCase
|
||||
|
||||
|
||||
def _parsetime(s):
|
||||
|
|
@ -31,7 +31,7 @@ def _is_windows():
|
|||
return platform.system() == "Windows"
|
||||
|
||||
|
||||
class MetaSyncTest(PluginTestCase):
|
||||
class MetaSyncTest(IOMixin, PluginTestCase):
|
||||
plugin = "metasync"
|
||||
itunes_library_unix = os.path.join(_common.RSRC, b"itunes_library_unix.xml")
|
||||
itunes_library_windows = os.path.join(
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ from beets.test import helper
|
|||
from beets.test.helper import (
|
||||
AutotagStub,
|
||||
ImportHelper,
|
||||
IOMixin,
|
||||
PluginMixin,
|
||||
PluginTestCase,
|
||||
TerminalImportMixin,
|
||||
|
|
@ -45,7 +46,7 @@ from beets.test.helper import (
|
|||
from beets.util import PromptChoice, displayable_path, syspath
|
||||
|
||||
|
||||
class TestPluginRegistration(PluginTestCase):
|
||||
class TestPluginRegistration(IOMixin, PluginTestCase):
|
||||
class RatingPlugin(plugins.BeetsPlugin):
|
||||
item_types: ClassVar[dict[str, types.Type]] = {
|
||||
"rating": types.Float(),
|
||||
|
|
@ -429,8 +430,9 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
|
|||
|
||||
# DummyPlugin.foo() should be called once
|
||||
with patch.object(DummyPlugin, "foo", autospec=True) as mock_foo:
|
||||
with helper.control_stdin("\n".join(["f", "s"])):
|
||||
self.importer.run()
|
||||
self.io.addinput("f")
|
||||
self.io.addinput("n")
|
||||
self.importer.run()
|
||||
assert mock_foo.call_count == 1
|
||||
|
||||
# input_options should be called twice, as foo() returns None
|
||||
|
|
@ -471,8 +473,8 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
|
|||
)
|
||||
|
||||
# DummyPlugin.foo() should be called once
|
||||
with helper.control_stdin("f\n"):
|
||||
self.importer.run()
|
||||
self.io.addinput("f")
|
||||
self.importer.run()
|
||||
|
||||
# input_options should be called once, as foo() returns SKIP
|
||||
self.mock_input_options.assert_called_once_with(
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ class CompletionTest(IOMixin, TestPluginTestCase):
|
|||
# Load completion script.
|
||||
self.run_command("completion", lib=None)
|
||||
completion_script = self.io.getoutput().encode("utf-8")
|
||||
self.io.restore()
|
||||
tester.stdin.writelines(completion_script.splitlines(True))
|
||||
|
||||
# Load test suite.
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import pytest
|
|||
import yaml
|
||||
|
||||
from beets import config, ui
|
||||
from beets.test.helper import BeetsTestCase
|
||||
from beets.test.helper import BeetsTestCase, IOMixin
|
||||
|
||||
|
||||
class ConfigCommandTest(BeetsTestCase):
|
||||
class ConfigCommandTest(IOMixin, BeetsTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
for k in ("VISUAL", "EDITOR"):
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class FieldsTest(IOMixin, ItemInDBTestCase):
|
|||
items = library.Item.all_keys()
|
||||
albums = library.Album.all_keys()
|
||||
|
||||
output = self.io.stdout.get().split()
|
||||
output = self.io.getoutput().split()
|
||||
self.remove_keys(items, output)
|
||||
self.remove_keys(albums, output)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
from beets.test import _common
|
||||
from beets.test.helper import BeetsTestCase, capture_stdout
|
||||
from beets.test.helper import BeetsTestCase, IOMixin
|
||||
from beets.ui.commands.list import list_items
|
||||
|
||||
|
||||
class ListTest(BeetsTestCase):
|
||||
class ListTest(IOMixin, BeetsTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.item = _common.item()
|
||||
|
|
@ -12,13 +12,12 @@ class ListTest(BeetsTestCase):
|
|||
self.lib.add_album([self.item])
|
||||
|
||||
def _run_list(self, query="", album=False, path=False, fmt=""):
|
||||
with capture_stdout() as stdout:
|
||||
list_items(self.lib, query, album, fmt)
|
||||
return stdout
|
||||
list_items(self.lib, query, album, fmt)
|
||||
return self.io.getoutput()
|
||||
|
||||
def test_list_outputs_item(self):
|
||||
stdout = self._run_list()
|
||||
assert "the title" in stdout.getvalue()
|
||||
assert "the title" in stdout
|
||||
|
||||
def test_list_unicode_query(self):
|
||||
self.item.title = "na\xefve"
|
||||
|
|
@ -26,44 +25,44 @@ class ListTest(BeetsTestCase):
|
|||
self.lib._connection().commit()
|
||||
|
||||
stdout = self._run_list(["na\xefve"])
|
||||
out = stdout.getvalue()
|
||||
out = stdout
|
||||
assert "na\xefve" in out
|
||||
|
||||
def test_list_item_path(self):
|
||||
stdout = self._run_list(fmt="$path")
|
||||
assert stdout.getvalue().strip() == "xxx/yyy"
|
||||
assert stdout.strip() == "xxx/yyy"
|
||||
|
||||
def test_list_album_outputs_something(self):
|
||||
stdout = self._run_list(album=True)
|
||||
assert len(stdout.getvalue()) > 0
|
||||
assert len(stdout) > 0
|
||||
|
||||
def test_list_album_path(self):
|
||||
stdout = self._run_list(album=True, fmt="$path")
|
||||
assert stdout.getvalue().strip() == "xxx"
|
||||
assert stdout.strip() == "xxx"
|
||||
|
||||
def test_list_album_omits_title(self):
|
||||
stdout = self._run_list(album=True)
|
||||
assert "the title" not in stdout.getvalue()
|
||||
assert "the title" not in stdout
|
||||
|
||||
def test_list_uses_track_artist(self):
|
||||
stdout = self._run_list()
|
||||
assert "the artist" in stdout.getvalue()
|
||||
assert "the album artist" not in stdout.getvalue()
|
||||
assert "the artist" in stdout
|
||||
assert "the album artist" not in stdout
|
||||
|
||||
def test_list_album_uses_album_artist(self):
|
||||
stdout = self._run_list(album=True)
|
||||
assert "the artist" not in stdout.getvalue()
|
||||
assert "the album artist" in stdout.getvalue()
|
||||
assert "the artist" not in stdout
|
||||
assert "the album artist" in stdout
|
||||
|
||||
def test_list_item_format_artist(self):
|
||||
stdout = self._run_list(fmt="$artist")
|
||||
assert "the artist" in stdout.getvalue()
|
||||
assert "the artist" in stdout
|
||||
|
||||
def test_list_item_format_multiple(self):
|
||||
stdout = self._run_list(fmt="$artist - $album - $year")
|
||||
assert "the artist - the album - 0001" == stdout.getvalue().strip()
|
||||
assert "the artist - the album - 0001" == stdout.strip()
|
||||
|
||||
def test_list_album_format(self):
|
||||
stdout = self._run_list(album=True, fmt="$genre")
|
||||
assert "the genre" in stdout.getvalue()
|
||||
assert "the album" not in stdout.getvalue()
|
||||
assert "the genre" in stdout
|
||||
assert "the album" not in stdout
|
||||
|
|
|
|||
|
|
@ -2,23 +2,24 @@ import unittest
|
|||
|
||||
from mediafile import MediaFile
|
||||
|
||||
from beets.test.helper import BeetsTestCase, control_stdin
|
||||
from beets.test.helper import BeetsTestCase, IOMixin
|
||||
from beets.ui.commands.modify import modify_parse_args
|
||||
from beets.util import syspath
|
||||
|
||||
|
||||
class ModifyTest(BeetsTestCase):
|
||||
class ModifyTest(IOMixin, BeetsTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.album = self.add_album_fixture()
|
||||
[self.item] = self.album.items()
|
||||
|
||||
def modify_inp(self, inp, *args):
|
||||
with control_stdin(inp):
|
||||
self.run_command("modify", *args)
|
||||
def modify_inp(self, inp: list[str], *args):
|
||||
for chat in inp:
|
||||
self.io.addinput(chat)
|
||||
self.run_command("modify", *args)
|
||||
|
||||
def modify(self, *args):
|
||||
self.modify_inp("y", *args)
|
||||
self.modify_inp(["y"], *args)
|
||||
|
||||
# Item tests
|
||||
|
||||
|
|
@ -30,14 +31,14 @@ class ModifyTest(BeetsTestCase):
|
|||
def test_modify_item_abort(self):
|
||||
item = self.lib.items().get()
|
||||
title = item.title
|
||||
self.modify_inp("n", "title=newTitle")
|
||||
self.modify_inp(["n"], "title=newTitle")
|
||||
item = self.lib.items().get()
|
||||
assert item.title == title
|
||||
|
||||
def test_modify_item_no_change(self):
|
||||
title = "Tracktitle"
|
||||
item = self.add_item_fixture(title=title)
|
||||
self.modify_inp("y", "title", f"title={title}")
|
||||
self.modify_inp(["y"], "title", f"title={title}")
|
||||
item = self.lib.items(title).get()
|
||||
assert item.title == title
|
||||
|
||||
|
|
@ -96,7 +97,9 @@ class ModifyTest(BeetsTestCase):
|
|||
title=f"{title}{i}", artist=original_artist, album=album
|
||||
)
|
||||
self.modify_inp(
|
||||
"s\ny\ny\ny\nn\nn\ny\ny\ny\ny\nn", title, f"artist={new_artist}"
|
||||
["s", "y", "y", "y", "n", "n", "y", "y", "y", "y", "n"],
|
||||
title,
|
||||
f"artist={new_artist}",
|
||||
)
|
||||
original_items = self.lib.items(f"artist:{original_artist}")
|
||||
new_items = self.lib.items(f"artist:{new_artist}")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from beets.test.helper import BeetsTestCase
|
||||
from beets.test.helper import BeetsTestCase, IOMixin
|
||||
|
||||
|
||||
class WriteTest(BeetsTestCase):
|
||||
class WriteTest(IOMixin, BeetsTestCase):
|
||||
def write_cmd(self, *args):
|
||||
return self.run_with_output("write", *args)
|
||||
|
||||
|
|
|
|||
|
|
@ -71,11 +71,11 @@ class TestPluginTestCase(PluginTestCase):
|
|||
plugin = "test"
|
||||
|
||||
def setUp(self):
|
||||
self.config["pluginpath"] = [_common.PLUGINPATH]
|
||||
super().setUp()
|
||||
config["pluginpath"] = [_common.PLUGINPATH]
|
||||
|
||||
|
||||
class ConfigTest(TestPluginTestCase):
|
||||
class ConfigTest(IOMixin, TestPluginTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
|
|
@ -162,6 +162,7 @@ class ConfigTest(TestPluginTestCase):
|
|||
with self.write_config_file() as config:
|
||||
config.write("library: /xxx/yyy/not/a/real/path")
|
||||
|
||||
self.io.addinput("n")
|
||||
with pytest.raises(ui.UserError):
|
||||
self.run_command("test", lib=None)
|
||||
|
||||
|
|
@ -397,7 +398,7 @@ class PluginTest(TestPluginTestCase):
|
|||
self.run_command("test", lib=None)
|
||||
|
||||
|
||||
class CommonOptionsParserCliTest(BeetsTestCase):
|
||||
class CommonOptionsParserCliTest(IOMixin, BeetsTestCase):
|
||||
"""Test CommonOptionsParser and formatting LibModel formatting on 'list'
|
||||
command.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from random import random
|
|||
|
||||
from beets import config, ui
|
||||
from beets.test import _common
|
||||
from beets.test.helper import BeetsTestCase, IOMixin, control_stdin
|
||||
from beets.test.helper import BeetsTestCase, IOMixin
|
||||
|
||||
|
||||
class InputMethodsTest(IOMixin, unittest.TestCase):
|
||||
|
|
@ -85,7 +85,7 @@ class InputMethodsTest(IOMixin, unittest.TestCase):
|
|||
assert items == ["1", "3"]
|
||||
|
||||
|
||||
class ParentalDirCreation(BeetsTestCase):
|
||||
class ParentalDirCreation(IOMixin, BeetsTestCase):
|
||||
def test_create_yes(self):
|
||||
non_exist_path = _common.os.fsdecode(
|
||||
os.path.join(self.temp_dir, b"nonexist", str(random()).encode())
|
||||
|
|
@ -94,8 +94,8 @@ class ParentalDirCreation(BeetsTestCase):
|
|||
# occur; wish I can use a golang defer here.
|
||||
test_config = deepcopy(config)
|
||||
test_config["library"] = non_exist_path
|
||||
with control_stdin("y"):
|
||||
lib = ui._open_library(test_config)
|
||||
self.io.addinput("y")
|
||||
lib = ui._open_library(test_config)
|
||||
lib._close()
|
||||
|
||||
def test_create_no(self):
|
||||
|
|
@ -108,14 +108,14 @@ class ParentalDirCreation(BeetsTestCase):
|
|||
test_config = deepcopy(config)
|
||||
test_config["library"] = non_exist_path
|
||||
|
||||
with control_stdin("n"):
|
||||
try:
|
||||
lib = ui._open_library(test_config)
|
||||
except ui.UserError:
|
||||
if os.path.exists(non_exist_path_parent):
|
||||
shutil.rmtree(non_exist_path_parent)
|
||||
raise OSError("Parent directories should not be created.")
|
||||
else:
|
||||
if lib:
|
||||
lib._close()
|
||||
self.io.addinput("n")
|
||||
try:
|
||||
lib = ui._open_library(test_config)
|
||||
except ui.UserError:
|
||||
if os.path.exists(non_exist_path_parent):
|
||||
shutil.rmtree(non_exist_path_parent)
|
||||
raise OSError("Parent directories should not be created.")
|
||||
else:
|
||||
if lib:
|
||||
lib._close()
|
||||
raise OSError("Parent directories should not be created.")
|
||||
|
|
|
|||
Loading…
Reference in a new issue