Skip to content

Commit

Permalink
test: adopt craft-application test fixtures
Browse files Browse the repository at this point in the history
  • Loading branch information
syu-w committed Feb 20, 2024
1 parent 505e03b commit 4f492aa
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 1 deletion.
218 changes: 218 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,27 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations

import os
import pathlib
import shutil
from importlib import metadata
from typing import TYPE_CHECKING, Any

import craft_application
import craft_parts
import keyring
import pytest
import xdg.BaseDirectory
from craft_application import application, models, services, util
from craft_cli import EmitterMode, emit
from craft_providers import bases
from craft_store.auth import MemoryKeyring

if TYPE_CHECKING: # pragma: no cover
from collections.abc import Iterator


@pytest.fixture(autouse=True)
def temp_xdg(tmpdir, mocker):
Expand Down Expand Up @@ -59,3 +72,208 @@ def memory_keyring():
keyring.set_keyring(MemoryKeyring())
yield
keyring.set_keyring(current_keyring)


class Platform(models.CraftBaseModel):
"""Platform definition."""

build_on: str
build_for: str


class MyBuildPlanner(models.BuildPlanner):
"""Build planner definition for tests."""

def get_build_plan(self) -> list[models.BuildInfo]:
arch = util.get_host_architecture()
return [
models.BuildInfo(
"ubuntu-22.04", arch, arch, bases.BaseName("ubuntu", "22.04")
),
models.BuildInfo(
"ubuntu-24.04", arch, arch, bases.BaseName("ubuntu", "24.04")
),
]


@pytest.fixture()
def features(request) -> dict[str, bool]:
"""Fixture that controls the enabled features.
To use it, mark the test with the features that should be enabled. For example:
@pytest.mark.enable_features("build_secrets")
def test_with_build_secrets(...)
"""
features = {}

for feature_marker in request.node.iter_markers("enable_features"):
for feature_name in feature_marker.args:
features[feature_name] = True

return features


@pytest.fixture()
def app_metadata(features) -> craft_application.AppMetadata:
with pytest.MonkeyPatch.context() as m:
m.setattr(metadata, "version", lambda _: "3.14159")
return craft_application.AppMetadata(
"testcraft",
"A fake app for testing snapcraft",
BuildPlannerClass=MyBuildPlanner,
source_ignore_patterns=["*.snap", "*.charm", "*.starcraft"],
features=craft_application.AppFeatures(**features),
)


@pytest.fixture()
def fake_project() -> models.Project:
arch = util.get_host_architecture()
return models.Project(
name="full-project", # pyright: ignore[reportArgumentType]
title="A fully-defined project", # pyright: ignore[reportArgumentType]
base="core24",
version="1.0.0.post64+git12345678", # pyright: ignore[reportArgumentType]
contact="author@project.org",
issues="https://github.com/canonical/snapcraft/issues",
source_code="https://github.com/canonical/snapcraft", # pyright: ignore[reportArgumentType]
summary="A fully-defined snapcraft project.", # pyright: ignore[reportArgumentType]
description="A fully-defined snapcraft project. (description)",
license="GPLv3",
parts={"my-part": {"plugin": "nil"}},
platforms={"foo": Platform(build_on=arch, build_for=arch)},
package_repositories=None,
)


@pytest.fixture()
def fake_build_plan() -> list[models.BuildInfo]:
arch = util.get_host_architecture()
return [models.BuildInfo("foo", arch, arch, bases.BaseName("ubuntu", "22.04"))]


@pytest.fixture()
def enable_overlay() -> Iterator[craft_parts.Features]:
"""Enable the overlay feature in craft_parts for the relevant test."""
if not os.getenv("CI") and not shutil.which("fuse-overlayfs"):
pytest.skip("fuse-overlayfs not installed, skipping overlay tests.")
craft_parts.Features.reset()
yield craft_parts.Features(enable_overlay=True)
craft_parts.Features.reset()


@pytest.fixture()
def lifecycle_service(
app_metadata, fake_project, fake_services, tmp_path
) -> services.LifecycleService:
work_dir = tmp_path / "work"
cache_dir = tmp_path / "cache"
build_for = util.get_host_architecture()

service = services.LifecycleService(
app_metadata,
fake_services,
project=fake_project,
work_dir=work_dir,
cache_dir=cache_dir,
platform=None,
build_for=build_for,
)
service.setup()
return service


@pytest.fixture()
def request_service(app_metadata, fake_services) -> services.RequestService:
"""A working version of the requests service."""
return services.RequestService(app=app_metadata, services=fake_services)


@pytest.fixture(params=list(EmitterMode))
def emitter_verbosity(request):
reset_verbosity = emit.get_mode()
emit.set_mode(request.param)
yield request.param
emit.set_mode(reset_verbosity)


@pytest.fixture()
def fake_provider_service_class(fake_build_plan):
class FakeProviderService(services.ProviderService):
"""A fake provider service."""

def __init__(
self,
app: application.AppMetadata,
services: services.ServiceFactory,
*,
project: models.Project,
):
super().__init__(
app,
services,
project=project,
work_dir=pathlib.Path(),
build_plan=fake_build_plan,
)

return FakeProviderService


@pytest.fixture()
def fake_package_service_class():
class FakePackageService(services.PackageService):
"""A fake package service."""

def pack(
self, prime_dir: pathlib.Path, dest: pathlib.Path
) -> list[pathlib.Path]:
assert prime_dir.exists()
pkg = dest / "package.tar.zst"
pkg.touch()
return [pkg]

@property
def metadata(self) -> models.BaseMetadata:
return models.BaseMetadata()

return FakePackageService


@pytest.fixture()
def fake_lifecycle_service_class(tmp_path):
class FakeLifecycleService(services.LifecycleService):
"""A fake lifecycle service."""

def __init__(
self,
app: application.AppMetadata,
project: models.Project,
services: services.ServiceFactory,
**lifecycle_kwargs: Any,
):
super().__init__(
app,
services,
project=project,
work_dir=tmp_path / "work",
cache_dir=tmp_path / "cache",
platform=None,
build_for=util.get_host_architecture(),
**lifecycle_kwargs,
)

return FakeLifecycleService


@pytest.fixture()
def fake_services(
app_metadata, fake_project, fake_lifecycle_service_class, fake_package_service_class
):
return services.ServiceFactory(
app_metadata,
project=fake_project,
PackageClass=fake_package_service_class,
LifecycleClass=fake_lifecycle_service_class,
)
28 changes: 27 additions & 1 deletion tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from __future__ import annotations

import base64
import contextlib
import textwrap
from pathlib import Path
from typing import Any, Dict, Optional, Tuple
from unittest import mock
from unittest.mock import Mock

import pytest
import yaml
from craft_application import services
from craft_providers import Executor, Provider
from craft_providers.base import Base
from overrides import override
Expand Down Expand Up @@ -426,4 +430,26 @@ def package_service(default_project, default_factory):
)


# pylint: enable=import-outside-toplevel
@pytest.fixture()
def provider_service(
app_metadata, fake_project, fake_build_plan, fake_services, tmp_path
):
return services.ProviderService(
app_metadata,
fake_services,
project=fake_project,
work_dir=tmp_path,
build_plan=fake_build_plan,
)


@pytest.fixture()
def mock_services(app_metadata, fake_project, fake_package_service_class):
factory = services.ServiceFactory(
app_metadata, project=fake_project, PackageClass=fake_package_service_class
)
factory.lifecycle = mock.Mock(spec=services.LifecycleService)
factory.package = mock.Mock(spec=services.PackageService)
factory.provider = mock.Mock(spec=services.ProviderService)
factory.remote_build = mock.Mock(spec_set=services.RemoteBuildService)
return factory

0 comments on commit 4f492aa

Please sign in to comment.