mirror of
https://github.com/beetbox/beets.git
synced 2026-02-09 00:41:57 +01:00
Replace capture_output with io.getoutput
This commit is contained in:
parent
cbfec8de66
commit
9372371004
20 changed files with 94 additions and 123 deletions
|
|
@ -15,9 +15,6 @@
|
|||
"""This module includes various helpers that provide fixtures, capture
|
||||
information or mock the environment.
|
||||
|
||||
- `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,7 +35,6 @@ 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
|
||||
|
|
@ -84,25 +80,6 @@ def capture_log(logger="beets"):
|
|||
log.removeHandler(capture)
|
||||
|
||||
|
||||
@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]
|
||||
|
|
@ -148,12 +125,31 @@ NEEDS_REFLINK = unittest.skipUnless(
|
|||
)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("io")
|
||||
class IOMixin:
|
||||
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(ConfigMixin):
|
||||
|
||||
class TestHelper(RunMixin, ConfigMixin):
|
||||
"""Helper mixin for high-level cli and plugin tests.
|
||||
|
||||
This mixin provides methods to isolate beets' global state provide
|
||||
|
|
@ -366,25 +362,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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ from beets.test import _common
|
|||
from beets.test.helper import (
|
||||
AsIsImporterMixin,
|
||||
ImportHelper,
|
||||
PluginTestCase,
|
||||
IOMixin,
|
||||
PluginTestCase,
|
||||
capture_log,
|
||||
)
|
||||
from beetsplug import convert
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class ModifyFileMocker:
|
|||
f.write(contents)
|
||||
|
||||
|
||||
class EditMixin(IOMixin, PluginMixin):
|
||||
class EditMixin(PluginMixin):
|
||||
"""Helper containing some common functionality used for the Edit tests."""
|
||||
|
||||
plugin = "edit"
|
||||
|
|
@ -120,7 +120,7 @@ class EditMixin(IOMixin, PluginMixin):
|
|||
|
||||
@_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 mocked stdin, and yaml editing via an external editor is
|
||||
simulated using `ModifyFileMocker`.
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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,7 +17,6 @@ from beets.test.helper import (
|
|||
AutotagImportTestCase,
|
||||
PluginMixin,
|
||||
TerminalImportMixin,
|
||||
capture_stdout,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -33,11 +32,10 @@ class MBSubmitPluginTest(
|
|||
|
||||
def test_print_tracks_output(self):
|
||||
"""Test the output of the "print tracks" choice."""
|
||||
with capture_stdout() as output:
|
||||
self.io.addinput("p")
|
||||
self.io.addinput("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,20 +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:
|
||||
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()
|
||||
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,7 +21,7 @@ from unittest.mock import ANY, patch
|
|||
|
||||
import pytest
|
||||
|
||||
from beets.test.helper import CleanupModulesMixin, PluginTestCase, IOMixin
|
||||
from beets.test.helper import CleanupModulesMixin, IOMixin, PluginTestCase
|
||||
from beets.ui import UserError
|
||||
from beets.util import open_anything
|
||||
from beetsplug.play import PlayPlugin
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -398,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.
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue