1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
# Copyright 2024 Caleb Connolly
# SPDX-License-Identifier: GPL-3.0-or-later
import shutil
import tempfile
from pathlib import Path
import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
from _pytest.tmpdir import TempPathFactory
import pmb.core
from pmb.core.arch import Arch
from pmb.core.context import Context, get_context
from pmb.helpers.args import init as init_args
from pmb.types import PmbArgs
_testdir = Path(__file__).parent / "data/tests"
# request can be specified using parameterize from test cases
# e.g. @pytest.mark.parametrize("config_file", ["no-repos"], indirect=True)
# will set request.param to "no-repos"
@pytest.fixture
def config_file(tmp_path_factory: TempPathFactory, request: FixtureRequest) -> Path:
"""Fixture to create a temporary pmbootstrap_v3.cfg file."""
tmp_path = tmp_path_factory.mktemp("pmbootstrap")
flavour = "default"
if hasattr(request, "param") and request.param:
flavour = request.param
out_file = tmp_path / "pmbootstrap_v3.cfg"
workdir = tmp_path / "work"
workdir.mkdir()
configs = {"default": f"aports = {workdir / 'cache_git' / 'pmaports'}", "no-repos": "aports = "}
file = _testdir / "pmbootstrap_v3.cfg"
print(f"CONFIG: {out_file}")
cfg = configs[flavour]
contents = file.read_text().format(workdir, cfg)
out_file.write_text(contents)
return out_file
@pytest.fixture
def device_package(config_file: Path) -> Path:
"""Fixture to create a temporary deviceinfo file."""
mock_device = "qemu-amd64"
pkgdir = config_file.parent / f"device-{mock_device}"
pkgdir.mkdir()
for file in ["APKBUILD", "deviceinfo"]:
shutil.copy(_testdir / f"{file}.{mock_device}", pkgdir / file)
return pkgdir
@pytest.fixture(autouse=True)
def find_required_programs() -> None:
"""Fixture to find required programs for pmbootstrap."""
pmb.config.require_programs()
@pytest.fixture
def mock_devices_find_path(device_package: Path, monkeypatch: MonkeyPatch) -> None:
"""Fixture to mock pmb.helpers.devices.find_path()"""
def mock_find_path(codename: str, file: str = "") -> Path | None:
print(f"mock_find_path({codename}, {file})")
out = device_package / file
if not out.exists():
return None
return out
monkeypatch.setattr("pmb.helpers.devices.find_path", mock_find_path)
@pytest.fixture(autouse=True)
def logfile(tmp_path_factory: TempPathFactory) -> Path:
"""Setup logging for all tests."""
from pmb.helpers import logging
tmp_path = tmp_path_factory.getbasetemp()
logfile = tmp_path / "log_testsuite.txt"
logging.init(logfile, verbose=True)
return logfile
@pytest.fixture(autouse=True)
def setup_mock_ask(monkeypatch: MonkeyPatch) -> None:
"""Common setup to mock cli.ask() to avoid reading from stdin"""
import pmb.helpers.cli
def mock_ask(
question: str = "Continue?",
choices: list[str] = ["y", "n"],
default: str = "n",
lowercase_answer: bool = True,
validation_regex: None = None,
complete: None = None,
) -> str:
return default
monkeypatch.setattr(pmb.helpers.cli, "ask", mock_ask)
# FIXME: get/set_context() is a bad hack :(
@pytest.fixture
def mock_context(monkeypatch: MonkeyPatch) -> None:
"""
Mock set_context() to bypass sanity checks. Ideally we would
mock get_context() as well, but since every submodule of pmb imports
it like "from pmb.core.context import get_context()", we can't
actually override it with monkeypatch.setattr(). So this is the
best we can do... set_context() is only called from one place and is
done so with the full namespace, so this works.
"""
def mock_set_context(ctx: Context) -> None:
print(f"mock_set_context({ctx})")
setattr(pmb.core.context, "__context", ctx)
monkeypatch.setattr("pmb.core.context.set_context", mock_set_context)
# FIXME: get_context() at runtime somehow doesn't return the
# custom context we set up here.
@pytest.fixture
def pmb_args(config_file: Path, mock_context: None, logfile: Path) -> None:
"""
This is (still) a hack, since a bunch of the codebase still
expects some global state to be initialised. We do that here.
"""
args = PmbArgs()
args.config = config_file
args.aports = None
args.timeout = 900
args.details_to_stdout = False
args.quiet = False
args.verbose = True
args.offline = False
args.action = "init"
args.cross = False
args.ccache = True
args.log = logfile
init_args(args)
print(f"WORK: {get_context().config.work}")
# Sanity check
assert ".pytest_tmp" in get_context().config.work.parts
@pytest.fixture
def foreign_arch() -> Arch:
"""Fixture to return the foreign arch."""
if Arch.native() == Arch.x86_64:
return Arch.aarch64
return Arch.x86_64
@pytest.fixture
def pmaports(pmb_args: None, monkeypatch: MonkeyPatch) -> None:
"""Fixture to clone pmaports."""
from pmb.core import Config
from pmb.core.context import get_context
cfg = get_context().config
# As an optimisation, we check the default workdir for pmaports
# and clone it from there if it exists. This saves a bunch of bandwidth
# and time.
if Config.aports[-1].exists():
# Override the URL to the local path so we can later look up the
# remote by URL
pmb.config.git_repos["pmaports"] = [str(Config.aports[-1])]
if not cfg.aports[-1].exists():
pmb.helpers.git.clone("pmaports")
# Now operate on the cloned repo
assert pmb.helpers.run.user(["git", "switch", "master"], working_dir=cfg.aports[-1]) == 0
@pytest.fixture
def tmp_file(tmp_path: Path) -> Path:
return Path(tempfile.mkstemp(dir=tmp_path)[1])
|