Files
fuzzforge_ai/fuzzforge-common/tests/unit/engines/test_podman.py
tduhamel42 d786c6dab1 fix: block Podman on macOS and remove ghcr.io default (#39)
* fix: block Podman on macOS and remove ghcr.io default

- Add platform check in PodmanCLI.__init__() that raises FuzzForgeError
  on macOS with instructions to use Docker instead
- Change RegistrySettings.url default from "ghcr.io/fuzzinglabs" to ""
  (empty string) for local-only mode since no images are published yet
- Update _ensure_module_image() to show helpful error when image not
  found locally and no registry configured
- Update tests to mock Linux platform for Podman tests
- Add root ruff.toml to fix broken configuration in fuzzforge-runner

* rewrite guides for module architecture and update repo links

---------

Co-authored-by: AFredefon <antoinefredefon@yahoo.fr>
2026-02-03 10:15:16 +01:00

150 lines
4.9 KiB
Python

"""Tests for the PodmanCLI engine (OSS container engine)."""
import os
import shutil
import sys
import uuid
from pathlib import Path
from unittest import mock
import pytest
from fuzzforge_common.exceptions import FuzzForgeError
from fuzzforge_common.sandboxes.engines.podman.cli import PodmanCLI, _is_running_under_snap
# Helper to mock Linux platform for testing (since Podman is Linux-only)
def _mock_linux_platform() -> mock._patch[str]:
"""Context manager to mock sys.platform as 'linux'."""
return mock.patch.object(sys, "platform", "linux")
@pytest.fixture
def podman_cli_engine() -> PodmanCLI:
"""Create a PodmanCLI engine with temporary storage.
Uses short paths in /tmp to avoid podman's 50-char runroot limit.
Simulates Snap environment to test custom storage paths.
Mocks Linux platform since Podman is Linux-only.
"""
short_id = str(uuid.uuid4())[:8]
graphroot = Path(f"/tmp/ff-{short_id}/storage")
runroot = Path(f"/tmp/ff-{short_id}/run")
# Simulate Snap environment for testing on Linux
with _mock_linux_platform(), mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
engine = PodmanCLI(graphroot=graphroot, runroot=runroot)
yield engine
# Cleanup
parent = graphroot.parent
if parent.exists():
shutil.rmtree(parent, ignore_errors=True)
def test_snap_detection_when_snap_set() -> None:
"""Test that SNAP environment is detected."""
with mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
assert _is_running_under_snap() is True
def test_snap_detection_when_snap_not_set() -> None:
"""Test that non-Snap environment is detected."""
env = os.environ.copy()
env.pop("SNAP", None)
with mock.patch.dict(os.environ, env, clear=True):
assert _is_running_under_snap() is False
def test_podman_cli_blocks_macos() -> None:
"""Test that PodmanCLI raises error on macOS."""
with mock.patch.object(sys, "platform", "darwin"):
with pytest.raises(FuzzForgeError) as exc_info:
PodmanCLI()
assert "Podman is not supported on macOS" in str(exc_info.value)
assert "Docker" in str(exc_info.value)
def test_podman_cli_creates_storage_directories_under_snap() -> None:
"""Test that PodmanCLI creates storage directories when under Snap."""
short_id = str(uuid.uuid4())[:8]
graphroot = Path(f"/tmp/ff-{short_id}/storage")
runroot = Path(f"/tmp/ff-{short_id}/run")
assert not graphroot.exists()
assert not runroot.exists()
with _mock_linux_platform(), mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
PodmanCLI(graphroot=graphroot, runroot=runroot)
assert graphroot.exists()
assert runroot.exists()
# Cleanup
shutil.rmtree(graphroot.parent, ignore_errors=True)
def test_podman_cli_base_cmd_under_snap() -> None:
"""Test that base command includes --root/--runroot under Snap."""
short_id = str(uuid.uuid4())[:8]
graphroot = Path(f"/tmp/ff-{short_id}/storage")
runroot = Path(f"/tmp/ff-{short_id}/run")
with _mock_linux_platform(), mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
engine = PodmanCLI(graphroot=graphroot, runroot=runroot)
base_cmd = engine._base_cmd()
assert "podman" in base_cmd
assert "--root" in base_cmd
assert "--runroot" in base_cmd
# Cleanup
shutil.rmtree(graphroot.parent, ignore_errors=True)
def test_podman_cli_base_cmd_without_snap() -> None:
"""Test that base command is plain 'podman' when not under Snap."""
short_id = str(uuid.uuid4())[:8]
graphroot = Path(f"/tmp/ff-{short_id}/storage")
runroot = Path(f"/tmp/ff-{short_id}/run")
env = os.environ.copy()
env.pop("SNAP", None)
with _mock_linux_platform(), mock.patch.dict(os.environ, env, clear=True):
engine = PodmanCLI(graphroot=graphroot, runroot=runroot)
base_cmd = engine._base_cmd()
assert base_cmd == ["podman"]
assert "--root" not in base_cmd
# Directories should NOT be created when not under Snap
assert not graphroot.exists()
def test_podman_cli_default_mode() -> None:
"""Test PodmanCLI without custom storage paths."""
with _mock_linux_platform():
engine = PodmanCLI() # No paths provided
base_cmd = engine._base_cmd()
assert base_cmd == ["podman"]
assert "--root" not in base_cmd
def test_podman_cli_list_images_returns_list(podman_cli_engine: PodmanCLI) -> None:
"""Test that list_images returns a list (even if empty)."""
images = podman_cli_engine.list_images()
assert isinstance(images, list)
@pytest.mark.skip(reason="Requires pulling images, slow integration test")
def test_podman_cli_can_pull_and_list_image(podman_cli_engine: PodmanCLI) -> None:
"""Test pulling an image and listing it."""
# Pull a small image
podman_cli_engine._run(["pull", "docker.io/library/alpine:latest"])
images = podman_cli_engine.list_images()
assert any("alpine" in img.identifier for img in images)