Skip to content

Commit ad03b35

Browse files
authored
Fix integration tests (#6724)
This PR tightens the integration test workflow so CI runs the dependency-heavy checks that are usually skipped in local environments. ### What changed - Install the system packages and Poetry extras needed by integration-only coverage, including ffmpeg/ffprobe, ImageMagick/Pillow-backed art resizing, chroma, docs, replaygain, reflink, lyrics, and related plugin dependencies. - Run `poe docs` before `poe test` in the integration workflow and set `LYRICS_UPDATED=1` so lyrics tests are included in CI. - Centralize CI detection in `beets.test.helper.RUNNING_IN_CI` and reuse shared import/program checks across test collection and helpers. - Keep dependency-heavy tests strict in the beetbox GitHub Actions environment, while skipping them locally when optional tools/modules are unavailable. - Mark ffprobe-, artresizer-, chroma-, pandoc-, and completion-related cases with focused availability gates so local test runs fail less often for missing optional dependencies. - Update docs link-check ignores and stale documentation URLs so `poe check-docs-links` is less noisy in CI.
2 parents eaba5aa + 01792ec commit ad03b35

14 files changed

Lines changed: 87 additions & 35 deletions

File tree

.github/workflows/integration_test.yaml

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,30 @@ jobs:
1919
python-version: ${{ env.PYTHON_VERSION }}
2020
cache: poetry
2121

22+
- name: Install system dependencies
23+
run: |
24+
sudo apt update
25+
sudo apt install --yes --no-install-recommends \
26+
ffmpeg \
27+
gobject-introspection \
28+
gstreamer1.0-plugins-base \
29+
imagemagick \
30+
libcairo2-dev \
31+
libgirepository-2.0-dev \
32+
mp3gain \
33+
pandoc \
34+
python3-gst-1.0
35+
2236
- name: Install dependencies
23-
run: poetry install
37+
run: poetry install --extras=autobpm --extras=discogs --extras=lyrics --extras=docs --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate
2438

2539
- name: Test
2640
env:
2741
INTEGRATION_TEST: 1
28-
run: poe test
42+
LYRICS_UPDATED: 1
43+
run: |
44+
poe docs
45+
poe test
2946
3047
- name: Check external links in docs
3148
run: poe check-docs-links
@@ -34,7 +51,7 @@ jobs:
3451
if: ${{ failure() }}
3552
env:
3653
ZULIP_BOT_CREDENTIALS: ${{ secrets.ZULIP_BOT_CREDENTIALS }}
37-
run: |
54+
run: |-
3855
if [ -z "${ZULIP_BOT_CREDENTIALS}" ]; then
3956
echo "Skipping notify, ZULIP_BOT_CREDENTIALS is unset"
4057
exit 0

beets/test/helper.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
from __future__ import annotations
2727

28+
import importlib.util
2829
import os
2930
import os.path
3031
import shutil
@@ -34,7 +35,7 @@
3435
from contextlib import contextmanager
3536
from dataclasses import dataclass
3637
from enum import Enum
37-
from functools import cached_property
38+
from functools import cache, cached_property
3839
from pathlib import Path
3940
from tempfile import gettempdir, mkdtemp, mkstemp
4041
from typing import TYPE_CHECKING, Any, ClassVar
@@ -61,6 +62,8 @@
6162
if TYPE_CHECKING:
6263
from requests_mock.mocker import Mocker
6364

65+
RUNNING_IN_CI = os.environ.get("GITHUB_ACTIONS") == "true"
66+
6467

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

100103

104+
@cache
105+
def is_importable(modname: str) -> bool:
106+
return bool(importlib.util.find_spec(modname))
107+
108+
101109
def check_reflink_support(path: str) -> bool:
102110
try:
103111
import reflink
@@ -107,6 +115,15 @@ def check_reflink_support(path: str) -> bool:
107115
return reflink.supported_at(path)
108116

109117

118+
NEEDS_REFLINK = pytest.mark.skipif(
119+
not check_reflink_support(gettempdir()), reason="need reflink"
120+
)
121+
NEEDS_FFPROBE = pytest.mark.skipif(
122+
not has_program("ffprobe") and not RUNNING_IN_CI,
123+
reason="ffprobe (ffmpeg) is not available",
124+
)
125+
126+
110127
class ConfigMixin:
111128
@cached_property
112129
def config(self) -> beets.IncludeLazyConfig:
@@ -122,11 +139,6 @@ def config(self) -> beets.IncludeLazyConfig:
122139
return config
123140

124141

125-
NEEDS_REFLINK = pytest.mark.skipif(
126-
not check_reflink_support(gettempdir()), reason="need reflink"
127-
)
128-
129-
130142
class RunMixin:
131143
lib: Library
132144

docs/conf.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,14 @@
7474
r"https://[^/]*fanart\.tv/", # blocks requests
7575
r"https://[^/]*fandom\.com/", # blocks requests
7676
r"https://imgur\.com/", # not accessible from the UK
77-
r"https://www.discogs.com/settings/developers", # requires login
77+
r"https://(www\.)?discogs.com.*", # blocks requests
78+
r"https://essentia.upf.edu/", # times out in CI
79+
r"https://flask.palletsprojects.com.*", # times out in CI
80+
r"https://search.worldcat.org.*", # blocks requests
81+
r"https://tidal.com.*", # blocks requests
82+
r"https://www.tekstowo.pl/", # blocks requests
83+
r"https://www.gnu.org.*", # sometimes unreachable
84+
r"https://www.nongnu.org.*", # sometimes unreachable
7885
]
7986

8087
# Options for HTML output

docs/faq.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ follow these guidelines when reporting an issue:
171171

172172
- Most importantly: if beets is crashing, please `include the traceback
173173
<https://imgur.com/jacoj>`__. Tracebacks can be more readable if you put them
174-
in a pastebin (e.g., `Gist <https://gist.github.com/>`__ or `Hastebin
174+
in a pastebin (e.g., `Gist <https://gist.github.com/discover>`__ or `Hastebin
175175
<https://www.toptal.com/developers/hastebin>`__), especially when
176176
communicating over IRC.
177177
- Turn on beets' debug output (using the -v option: for example, ``beet -v

docs/plugins/fetchart.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ To use the google image search backend you need to `register for a Google API
221221
key`_. Set the ``google_key`` configuration option to your key, then add
222222
``google`` to the list of sources in your configuration.
223223

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

226226
Optionally, you can `define a custom search engine`_. Get your search engine's
227227
token and use it for your ``google_engine`` configuration option. The default

docs/plugins/lyrics.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ Activate Google Custom Search
188188
-----------------------------
189189

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

194194
Then add ``google`` to the list of sources in your configuration (or use default
195195
list, which includes it as long as you have an API key). If you use default

test/conftest.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
1-
import importlib.util
21
import inspect
32
import os
4-
from functools import cache
53

64
import pytest
75

86
from beets.autotag import Distance
97
from beets.dbcore.query import Query
108
from beets.test._common import DummyIO
11-
from beets.test.helper import ConfigMixin
9+
from beets.test.helper import RUNNING_IN_CI, ConfigMixin
10+
from beets.test.helper import is_importable as check_import
1211
from beets.util import cached_classproperty
1312

1413

15-
@cache
16-
def _is_importable(modname: str) -> bool:
17-
return bool(importlib.util.find_spec(modname))
18-
19-
2014
def skip_marked_items(items: list[pytest.Item], marker_name: str, reason: str):
2115
for item in (i for i in items if i.get_closest_marker(marker_name)):
2216
test_name = item.nodeid.split("::", 1)[-1]
@@ -41,15 +35,15 @@ def pytest_collection_modifyitems(
4135
force_ci = marker.kwargs.get("force_ci", True)
4236
if (
4337
force_ci
44-
and os.environ.get("GITHUB_ACTIONS") == "true"
38+
and RUNNING_IN_CI
4539
# only apply this to our repository, to allow other projects to
4640
# run tests without installing all dependencies
4741
and os.environ.get("GITHUB_REPOSITORY", "") == "beetbox/beets"
4842
):
4943
continue
5044

5145
modname = marker.args[0]
52-
if not _is_importable(modname):
46+
if not check_import(modname):
5347
test_name = item.nodeid.split("::", 1)[-1]
5448
item.add_marker(
5549
pytest.mark.skip(
@@ -134,4 +128,4 @@ def io(
134128
def is_importable():
135129
"""Fixture that provides a function to check if a module can be imported."""
136130

137-
return _is_importable
131+
return check_import

test/plugins/test_art.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,28 @@
3131
from beets.autotag import AlbumInfo, AlbumMatch, Distance
3232
from beets.library import Album
3333
from beets.test import _common
34-
from beets.test.helper import FetchImageHelper, TestHelper
34+
from beets.test.helper import (
35+
RUNNING_IN_CI,
36+
FetchImageHelper,
37+
TestHelper,
38+
has_program,
39+
is_importable,
40+
)
3541
from beets.util import clean_module_tempdir, syspath
3642
from beets.util.artresizer import ArtResizer
3743
from beetsplug import fetchart
3844

39-
logger = logging.getLogger("beets.test_art")
40-
41-
4245
if TYPE_CHECKING:
4346
from collections.abc import Iterator, Sequence
4447
from unittest.mock import MagicMock
4548

49+
logger = logging.getLogger("beets.test_art")
50+
51+
REQUIRES_ARTRESIZER = pytest.mark.skipif(
52+
not (has_program("magick") or is_importable("PIL")) and not RUNNING_IN_CI,
53+
reason="requires ImageMagick or Pillow",
54+
)
55+
4656

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

9941004

1005+
@REQUIRES_ARTRESIZER
9951006
class TestAlbumArtOperationConfiguration(AlbumArtOperationMixin):
9961007
"""Check that scale & filesize configuration is respected.
9971008
@@ -1033,6 +1044,7 @@ def test_enforce_ratio_with_percent_margin(self):
10331044
assert self.get_album_art()
10341045

10351046

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

test/plugins/test_chroma.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
from beets.autotag import AlbumInfo, TrackInfo
2121
from beets.library import Item
2222
from beets.test.helper import ImportHelper, IOMixin, PluginMixin
23-
from beetsplug import chroma
23+
24+
chroma = pytest.importorskip("beetsplug.chroma", exc_type=ImportError)
2425

2526
TEST_TITLE_1 = "TEST_TITLE_1"
2627
TEST_TITLE_2 = "TEST_TITLE_2"

test/plugins/test_embedart.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from beets.exceptions import UserError
3030
from beets.test import _common
3131
from beets.test.helper import (
32+
NEEDS_FFPROBE,
3233
FetchImageHelper,
3334
ImportHelper,
3435
IOMixin,
@@ -308,6 +309,7 @@ def test_embed_art_from_url_not_image(
308309
mediafile = MediaFile(syspath(item.path))
309310
assert not mediafile.images
310311

312+
@NEEDS_FFPROBE
311313
def test_clearart_on_import_disabled(self):
312314
file_path = self.create_mediafile_fixture(
313315
images=["jpg"], target_dir=self.import_path
@@ -320,6 +322,7 @@ def test_clearart_on_import_disabled(self):
320322
item = self.lib.items()[0]
321323
assert MediaFile(os.path.join(item.path)).images
322324

325+
@NEEDS_FFPROBE
323326
def test_clearart_on_import_enabled(self):
324327
file_path = self.create_mediafile_fixture(
325328
images=["jpg"], target_dir=self.import_path

0 commit comments

Comments
 (0)