Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .github/workflows/integration_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,30 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
cache: poetry

- name: Install system dependencies
run: |
sudo apt update
sudo apt install --yes --no-install-recommends \
ffmpeg \
gobject-introspection \
gstreamer1.0-plugins-base \
imagemagick \
libcairo2-dev \
libgirepository-2.0-dev \
mp3gain \
pandoc \
python3-gst-1.0

- name: Install dependencies
run: poetry install
run: poetry install --extras=autobpm --extras=discogs --extras=lyrics --extras=docs --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate

- name: Test
env:
INTEGRATION_TEST: 1
run: poe test
LYRICS_UPDATED: 1
run: |
poe docs
poe test

- name: Check external links in docs
run: poe check-docs-links
Expand All @@ -34,7 +51,7 @@ jobs:
if: ${{ failure() }}
env:
ZULIP_BOT_CREDENTIALS: ${{ secrets.ZULIP_BOT_CREDENTIALS }}
run: |
run: |-
if [ -z "${ZULIP_BOT_CREDENTIALS}" ]; then
echo "Skipping notify, ZULIP_BOT_CREDENTIALS is unset"
exit 0
Expand Down
24 changes: 18 additions & 6 deletions beets/test/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from __future__ import annotations

import importlib.util
import os
import os.path
import shutil
Expand All @@ -34,7 +35,7 @@
from contextlib import contextmanager
from dataclasses import dataclass
from enum import Enum
from functools import cached_property
from functools import cache, cached_property
from pathlib import Path
from tempfile import gettempdir, mkdtemp, mkstemp
from typing import TYPE_CHECKING, Any, ClassVar
Expand All @@ -61,6 +62,8 @@
if TYPE_CHECKING:
from requests_mock.mocker import Mocker

RUNNING_IN_CI = os.environ.get("GITHUB_ACTIONS") == "true"


class LogCapture(logging.Handler):
def __init__(self):
Expand Down Expand Up @@ -98,6 +101,11 @@ def has_program(cmd, args=["--version"]):
return True


@cache
def is_importable(modname: str) -> bool:
return bool(importlib.util.find_spec(modname))


def check_reflink_support(path: str) -> bool:
try:
import reflink
Expand All @@ -107,6 +115,15 @@ def check_reflink_support(path: str) -> bool:
return reflink.supported_at(path)


NEEDS_REFLINK = pytest.mark.skipif(
not check_reflink_support(gettempdir()), reason="need reflink"
)
NEEDS_FFPROBE = pytest.mark.skipif(
not has_program("ffprobe") and not RUNNING_IN_CI,
reason="ffprobe (ffmpeg) is not available",
)


class ConfigMixin:
@cached_property
def config(self) -> beets.IncludeLazyConfig:
Expand All @@ -122,11 +139,6 @@ def config(self) -> beets.IncludeLazyConfig:
return config


NEEDS_REFLINK = pytest.mark.skipif(
not check_reflink_support(gettempdir()), reason="need reflink"
)


class RunMixin:
lib: Library

Expand Down
9 changes: 8 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,14 @@
r"https://[^/]*fanart\.tv/", # blocks requests
r"https://[^/]*fandom\.com/", # blocks requests
r"https://imgur\.com/", # not accessible from the UK
r"https://www.discogs.com/settings/developers", # requires login
r"https://(www\.)?discogs.com.*", # blocks requests
r"https://essentia.upf.edu/", # times out in CI
r"https://flask.palletsprojects.com.*", # times out in CI
r"https://search.worldcat.org.*", # blocks requests
r"https://tidal.com.*", # blocks requests
r"https://www.tekstowo.pl/", # blocks requests
r"https://www.gnu.org.*", # sometimes unreachable
r"https://www.nongnu.org.*", # sometimes unreachable
]

# Options for HTML output
Expand Down
2 changes: 1 addition & 1 deletion docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ follow these guidelines when reporting an issue:

- Most importantly: if beets is crashing, please `include the traceback
<https://imgur.com/jacoj>`__. Tracebacks can be more readable if you put them
in a pastebin (e.g., `Gist <https://gist.github.com/>`__ or `Hastebin
in a pastebin (e.g., `Gist <https://gist.github.com/discover>`__ or `Hastebin
<https://www.toptal.com/developers/hastebin>`__), especially when
communicating over IRC.
- Turn on beets' debug output (using the -v option: for example, ``beet -v
Expand Down
2 changes: 1 addition & 1 deletion docs/plugins/fetchart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ To use the google image search backend you need to `register for a Google API
key`_. Set the ``google_key`` configuration option to your key, then add
``google`` to the list of sources in your configuration.

.. _register for a google api key: https://console.developers.google.com.
.. _register for a google api key: https://console.cloud.google.com/apis/credentials

Optionally, you can `define a custom search engine`_. Get your search engine's
token and use it for your ``google_engine`` configuration option. The default
Expand Down
4 changes: 2 additions & 2 deletions docs/plugins/lyrics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ Activate Google Custom Search
-----------------------------

You need to `register for a Google API key
<https://console.developers.google.com/>`__. Set the ``google_API_key``
configuration option to your key.
<https://console.cloud.google.com/apis/credentials>`__. Set the
``google_API_key`` configuration option to your key.

Then add ``google`` to the list of sources in your configuration (or use default
list, which includes it as long as you have an API key). If you use default
Expand Down
16 changes: 5 additions & 11 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import importlib.util
import inspect
import os
from functools import cache

import pytest

from beets.autotag import Distance
from beets.dbcore.query import Query
from beets.test._common import DummyIO
from beets.test.helper import ConfigMixin
from beets.test.helper import RUNNING_IN_CI, ConfigMixin
from beets.test.helper import is_importable as check_import
from beets.util import cached_classproperty


@cache
def _is_importable(modname: str) -> bool:
return bool(importlib.util.find_spec(modname))


def skip_marked_items(items: list[pytest.Item], marker_name: str, reason: str):
for item in (i for i in items if i.get_closest_marker(marker_name)):
test_name = item.nodeid.split("::", 1)[-1]
Expand All @@ -41,15 +35,15 @@ def pytest_collection_modifyitems(
force_ci = marker.kwargs.get("force_ci", True)
if (
force_ci
and os.environ.get("GITHUB_ACTIONS") == "true"
and RUNNING_IN_CI
# only apply this to our repository, to allow other projects to
# run tests without installing all dependencies
and os.environ.get("GITHUB_REPOSITORY", "") == "beetbox/beets"
):
continue

modname = marker.args[0]
if not _is_importable(modname):
if not check_import(modname):
test_name = item.nodeid.split("::", 1)[-1]
item.add_marker(
pytest.mark.skip(
Expand Down Expand Up @@ -134,4 +128,4 @@ def io(
def is_importable():
"""Fixture that provides a function to check if a module can be imported."""

return _is_importable
return check_import
20 changes: 16 additions & 4 deletions test/plugins/test_art.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,28 @@
from beets.autotag import AlbumInfo, AlbumMatch, Distance
from beets.library import Album
from beets.test import _common
from beets.test.helper import FetchImageHelper, TestHelper
from beets.test.helper import (
RUNNING_IN_CI,
FetchImageHelper,
TestHelper,
has_program,
is_importable,
)
from beets.util import clean_module_tempdir, syspath
from beets.util.artresizer import ArtResizer
from beetsplug import fetchart

logger = logging.getLogger("beets.test_art")


if TYPE_CHECKING:
from collections.abc import Iterator, Sequence
from unittest.mock import MagicMock

logger = logging.getLogger("beets.test_art")

REQUIRES_ARTRESIZER = pytest.mark.skipif(
not (has_program("magick") or is_importable("PIL")) and not RUNNING_IN_CI,
reason="requires ImageMagick or Pillow",
)


class Settings(fetchart.FetchArtPlugin):
"""Used to pass settings to the ArtSources when the plugin isn't fully
Expand Down Expand Up @@ -992,6 +1002,7 @@ def get_album_art(self):
return self.plugin.art_for_album(Album(), [""], True)


@REQUIRES_ARTRESIZER
class TestAlbumArtOperationConfiguration(AlbumArtOperationMixin):
"""Check that scale & filesize configuration is respected.

Expand Down Expand Up @@ -1033,6 +1044,7 @@ def test_enforce_ratio_with_percent_margin(self):
assert self.get_album_art()


@REQUIRES_ARTRESIZER
class TestAlbumArtPerformOperation(AlbumArtOperationMixin):
"""Test that the art is resized and deinterlaced if necessary."""

Expand Down
3 changes: 2 additions & 1 deletion test/plugins/test_chroma.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
from beets.autotag import AlbumInfo, TrackInfo
from beets.library import Item
from beets.test.helper import ImportHelper, IOMixin, PluginMixin
from beetsplug import chroma

chroma = pytest.importorskip("beetsplug.chroma", exc_type=ImportError)

TEST_TITLE_1 = "TEST_TITLE_1"
TEST_TITLE_2 = "TEST_TITLE_2"
Expand Down
3 changes: 3 additions & 0 deletions test/plugins/test_embedart.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from beets.exceptions import UserError
from beets.test import _common
from beets.test.helper import (
NEEDS_FFPROBE,
FetchImageHelper,
ImportHelper,
IOMixin,
Expand Down Expand Up @@ -308,6 +309,7 @@ def test_embed_art_from_url_not_image(
mediafile = MediaFile(syspath(item.path))
assert not mediafile.images

@NEEDS_FFPROBE
def test_clearart_on_import_disabled(self):
file_path = self.create_mediafile_fixture(
images=["jpg"], target_dir=self.import_path
Expand All @@ -320,6 +322,7 @@ def test_clearart_on_import_disabled(self):
item = self.lib.items()[0]
assert MediaFile(os.path.join(item.path)).images

@NEEDS_FFPROBE
def test_clearart_on_import_enabled(self):
file_path = self.create_mediafile_fixture(
images=["jpg"], target_dir=self.import_path
Expand Down
4 changes: 4 additions & 0 deletions test/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from beets.importer.tasks import albums_in_dir
from beets.test import _common
from beets.test.helper import (
NEEDS_FFPROBE,
NEEDS_REFLINK,
AsIsImporterMixin,
AutotagImportTestCase,
Expand Down Expand Up @@ -518,6 +519,7 @@ def test_skip_does_not_add_track(self):

assert not self.lib.items()

@NEEDS_FFPROBE
def test_skip_non_album_dirs(self):
assert (self.import_path / "album").exists()
self.touch(b"cruft", dir_=self.import_dir)
Expand All @@ -533,6 +535,7 @@ def test_unmatched_tracks_not_added(self):
self.importer.run()
assert len(self.lib.items()) == 1

@NEEDS_FFPROBE
def test_empty_directory_warning(self):
import_dir = os.path.join(self.temp_dir, b"empty")
self.touch(b"non-audio", dir_=import_dir)
Expand All @@ -543,6 +546,7 @@ def test_empty_directory_warning(self):
import_dir = displayable_path(import_dir)
assert f"No files imported from {import_dir}" in logs

@NEEDS_FFPROBE
def test_empty_directory_singleton_warning(self):
import_dir = os.path.join(self.temp_dir, b"empty")
self.touch(b"non-audio", dir_=import_dir)
Expand Down
3 changes: 2 additions & 1 deletion test/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
)
from beets.library import Item
from beets.test.helper import (
RUNNING_IN_CI,
AutotagStub,
ImportHelper,
IOMixin,
Expand Down Expand Up @@ -532,7 +533,7 @@ def cleanup(self):
self.unload_plugins()

@pytest.mark.skipif(
os.environ.get("GITHUB_ACTIONS") != "true",
not RUNNING_IN_CI,
reason=(
"Requires all dependencies to be installed, which we can't"
" guarantee in the local environment."
Expand Down
5 changes: 3 additions & 2 deletions test/test_release.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
"""Tests for the release utils."""

import os
import shutil
import sys

import pytest

from beets.test.helper import RUNNING_IN_CI

release = pytest.importorskip("extra.release")


pytestmark = pytest.mark.skipif(
not (
(os.environ.get("GITHUB_ACTIONS") == "true" and sys.platform != "win32")
(RUNNING_IN_CI and sys.platform != "win32")
or bool(shutil.which("pandoc"))
),
reason="pandoc isn't available",
Expand Down
4 changes: 2 additions & 2 deletions test/ui/commands/test_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
import pytest

from beets.test import _common
from beets.test.helper import IOMixin, has_program
from beets.test.helper import RUNNING_IN_CI, IOMixin, has_program
from beets.ui.commands.completion import BASH_COMPLETION_PATHS
from beets.util import syspath

from ..test_ui import TestPluginTestCase


@pytest.mark.xfail(
os.environ.get("GITHUB_ACTIONS") == "true" and sys.platform == "linux",
RUNNING_IN_CI and sys.platform == "linux",
reason="Completion is for some reason unhappy on Ubuntu 24.04 in CI",
)
class CompletionTest(IOMixin, TestPluginTestCase):
Expand Down
Loading