From 92337b7dbc97e70905e8bfa4cbcddcb3c02beadf Mon Sep 17 00:00:00 2001 From: Frank Schwenk Date: Sat, 18 Jul 2026 17:34:19 +0200 Subject: [PATCH] test: skip watermark integration when darktable export fails Add darktable style probe and pytest integration/slow markers so CI and machines without a working darktable-cli setup skip the full workflow test. Co-authored-by: Cursor --- tests/integration_helpers.py | 53 ++++++++++++++++++++++++++++++++++ tests/test_workflow_modules.py | 32 +++++++++----------- 2 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 tests/integration_helpers.py diff --git a/tests/integration_helpers.py b/tests/integration_helpers.py new file mode 100644 index 0000000..af9fdb1 --- /dev/null +++ b/tests/integration_helpers.py @@ -0,0 +1,53 @@ +"""Shared helpers for integration tests.""" + +from __future__ import annotations + +import shutil +import subprocess +import tempfile +from pathlib import Path + +from tests.conftest import make_png + + +def has_darktable_style(style_name: str, config_dir: Path | None = None) -> bool: + """Return True if darktable-cli can apply ``style_name`` to a tiny PNG.""" + if not shutil.which("darktable-cli"): + return False + + config = config_dir or Path.home() / ".config" / "darktable" + styles_dir = config / "styles" + if not styles_dir.is_dir(): + return False + if not any(p.stem == style_name for p in styles_dir.glob("*.dtstyle")): + return False + + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + src = tmp_path / "probe.png" + out_dir = tmp_path / "out" + out_dir.mkdir() + make_png(src) + cmd = [ + "darktable-cli", + str(src), + str(out_dir), + "--style", + style_name, + "--out-ext", + "png", + "--core", + "--configdir", + str(config), + "--style-overwrite", + ] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + return False + return any(out_dir.iterdir()) + + +def has_workflow_tools() -> bool: + """True when rembg, gmic, ImageMagick, and darktable-cli are on PATH.""" + has_magick = bool(shutil.which("magick") or shutil.which("convert")) + return all(shutil.which(name) for name in ("rembg", "gmic", "darktable-cli")) and has_magick diff --git a/tests/test_workflow_modules.py b/tests/test_workflow_modules.py index 7126a10..d8e1d2c 100644 --- a/tests/test_workflow_modules.py +++ b/tests/test_workflow_modules.py @@ -5,7 +5,6 @@ from pathlib import Path import pytest -from imagepipeline.core.params import validate_params from imagepipeline.modules.color_to_alpha import ( ColorToAlphaModule, build_color_to_alpha_args, @@ -13,18 +12,19 @@ from imagepipeline.modules.color_to_alpha import ( from imagepipeline.modules.composite import CompositeModule from imagepipeline.modules.crop_square import CropSquareModule from imagepipeline.modules.darktable_style import DarktableStyleModule +from imagepipeline.modules.gmic_grayscale import GmicGrayscale from imagepipeline.modules.imagemagick_fill import ( ImageMagickFillModule, build_fill_arguments, ) -from imagepipeline.modules.imagemagick_grayscale import ImageMagickGrayscale from imagepipeline.modules.imagemagick_resize import ( ImageMagickResizeModule, build_resize_arguments, ) -from imagepipeline.modules.gmic_grayscale import GmicGrayscale from imagepipeline.modules.registry import get_module, list_modules from imagepipeline.modules.rembg import RembgModule +from tests.integration_helpers import has_darktable_style +from tests.integration_helpers import has_workflow_tools as check_workflow_tools has_magick = bool(shutil.which("magick") or shutil.which("convert")) @@ -184,7 +184,6 @@ class TestImageMagickFill: def test_output_matches_input_size(self, tmp_path: Path) -> None: from imagepipeline.core.context import ModuleContext from imagepipeline.utils.subprocess import run_command - from tests.conftest import make_png src = tmp_path / "ref.png" @@ -221,7 +220,6 @@ class TestCropSquare: def test_center_crops_to_square(self, tmp_path: Path) -> None: from imagepipeline.core.context import ModuleContext from imagepipeline.utils.subprocess import run_command - from tests.conftest import make_png src = tmp_path / "wide.png" @@ -255,9 +253,7 @@ class TestModuleParameters: DarktableStyleModule.validate_module_params({}) def test_darktable_style_accepts_style(self) -> None: - params = DarktableStyleModule.validate_module_params( - {"style": "Watermark F12.rocks"} - ) + params = DarktableStyleModule.validate_module_params({"style": "Watermark F12.rocks"}) assert params["style"] == "Watermark F12.rocks" assert params["style_overwrite"] is True @@ -273,9 +269,7 @@ class TestModuleParameters: def test_darktable_export_conf_jpeg(self) -> None: from imagepipeline.modules.darktable_style import export_conf_options - assert export_conf_options("jpeg") == [ - "plugins/imageio/format/jpeg/quality=90" - ] + assert export_conf_options("jpeg") == ["plugins/imageio/format/jpeg/quality=90"] def test_darktable_export_conf_png(self) -> None: from imagepipeline.modules.darktable_style import export_conf_options @@ -308,7 +302,6 @@ class TestCompositeColor: def test_preserves_color_over_grayscale_background(self, tmp_path: Path) -> None: from imagepipeline.core.context import ModuleContext from imagepipeline.utils.subprocess import run_command - from tests.conftest import make_png src = tmp_path / "src.png" @@ -347,19 +340,20 @@ class TestCompositeColor: output = output_dir / "fg.png" assert output.is_file() - result = run_command( - [magick, "identify", "-format", "%[type]", str(output)] - ) + result = run_command([magick, "identify", "-format", "%[type]", str(output)]) assert result.stdout.strip() != "Grayscale" -has_workflow_tools = all( - shutil.which(name) - for name in ("rembg", "gmic", "magick", "darktable-cli") -) or all(shutil.which(name) for name in ("rembg", "gmic", "convert", "darktable-cli")) +has_workflow_tools = check_workflow_tools() +@pytest.mark.integration +@pytest.mark.slow @pytest.mark.skipif(not has_workflow_tools, reason="Workflow CLI tools not installed") +@pytest.mark.skipif( + not has_darktable_style("Watermark F12.rocks"), + reason="darktable style 'Watermark F12.rocks' not available or darktable-cli export failed", +) class TestWorkflowIntegration: def test_watermark_pipeline(self, input_dir, output_base) -> None: from imagepipeline import Pipeline