diff --git a/.packit.yaml b/.packit.yaml index 9f3dc0e629..baa9124324 100644 --- a/.packit.yaml +++ b/.packit.yaml @@ -50,7 +50,7 @@ _: - &copr-under-packit job: copr_build additional_repos: - - copr://@teemtee/stable + - copr://packit/teemtee-fmf-293 # Copr jobs under the teemtee project - &copr-under-teemtee diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8aee266f0e..07df9b7723 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -254,16 +254,16 @@ repos: - '--rst-directives' - 'versionadded,versionchanged' - - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.10.9 - hooks: - # Keep pyproject.toml and uv.lock in sync - - id: uv-lock - # Keep pylock.toml in sync - - id: uv-export - args: - - "--frozen" - - "--format=pylock.toml" - - "--output-file=pylock.toml" - - "--all-extras" - - "--quiet" +# - repo: https://github.com/astral-sh/uv-pre-commit +# rev: 0.10.9 +# hooks: +# # Keep pyproject.toml and uv.lock in sync +# - id: uv-lock +# # Keep pylock.toml in sync +# - id: uv-export +# args: +# - "--frozen" +# - "--format=pylock.toml" +# - "--output-file=pylock.toml" +# - "--all-extras" +# - "--quiet" diff --git a/pyproject.toml b/pyproject.toml index d6a72e8c4d..9f5554b06a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "importlib-resources; python_version < '3.12'", # MultiplexedPath is broken on earlier versions "click>=8.0.3", "docutils>=0.16", - "fmf>=1.7.0", + "fmf>=1.8.0", "jinja2>=2.11.3", "packaging>=20.9", "pint>=0.16.1", diff --git a/tmt/base/core.py b/tmt/base/core.py index 3015530c9e..9fdad567af 100644 --- a/tmt/base/core.py +++ b/tmt/base/core.py @@ -28,7 +28,6 @@ import fmf import fmf.base -import fmf.context import fmf.utils import jsonschema from click import confirm, echo @@ -60,6 +59,7 @@ container_fields, field, ) +from tmt.context import TmtContext from tmt.lint import LinterOutcome, LinterReturn from tmt.result import ResultInterpret from tmt.utils import ( @@ -2387,8 +2387,7 @@ def tree(self) -> fmf.Tree: raise tmt.utils.GeneralError("Invalid yaml syntax.") from error # Adjust metadata for current fmf context self._tree.adjust( - fmf.context.Context(**self.fmf_context), - case_sensitive=False, + TmtContext(**self.fmf_context), decision_callback=create_adjust_callback(self._logger), additional_rules=self._additional_rules, ) @@ -3532,8 +3531,7 @@ def resolve_dynamic_ref( if not plan: raise tmt.utils.FileError("Cannot get plan fmf context to evaluate dynamic ref.") reference_tree.adjust( - fmf.context.Context(**plan.fmf_context), - case_sensitive=False, + TmtContext(**plan.fmf_context), decision_callback=create_adjust_callback(logger), ) # Also temporarily build a plan so that env and context variables are expanded diff --git a/tmt/base/plan.py b/tmt/base/plan.py index 37aecb46a1..5a0a07a914 100644 --- a/tmt/base/plan.py +++ b/tmt/base/plan.py @@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast import fmf -import fmf.context import fmf.utils from click import echo from fmf.utils import listed # pyright: ignore[reportUnknownVariableType] @@ -47,6 +46,7 @@ ) from tmt.base.run import Run from tmt.container import SpecBasedContainer, container, field +from tmt.context import TmtContext from tmt.lint import LinterOutcome, LinterReturn from tmt.utils import ( Command, @@ -1530,8 +1530,7 @@ def _convert_node(node: fmf.Tree) -> 'Plan': # Adjust the imported tree, to let any `adjust` rules defined in it take # action, including the adjust-plans rules. node.adjust( - fmf.context.Context(**alteration_fmf_context), - case_sensitive=False, + TmtContext(**alteration_fmf_context), additional_rules=reference.adjust_plans, ) diff --git a/tmt/cli/about.py b/tmt/cli/about.py index 5e8df6ffb8..ec5e6c6096 100644 --- a/tmt/cli/about.py +++ b/tmt/cli/about.py @@ -39,6 +39,8 @@ def _render_plugins_list_rest(logger: tmt.log.Logger) -> str: r'prepare.feature': 'prepare/feature plugins', r'prepare.install': 'prepare/install plugins', r'prepare.artifact.providers': 'prepare/artifact provider plugins', + r'context': 'Fmf context plugins', + r'context.distro': 'Distro context plugins', r'step\.([a-z]+)': '{{ MATCH.group(1).capitalize() }} step plugins', } diff --git a/tmt/context/__init__.py b/tmt/context/__init__.py new file mode 100644 index 0000000000..2c9406080c --- /dev/null +++ b/tmt/context/__init__.py @@ -0,0 +1,31 @@ +from typing import Generic, TypeVar + +from fmf.context import Context, ContextDimension, DefaultContextDimension + +import tmt.utils +from tmt.plugins import PluginRegistry + +T = TypeVar("T") + + +class ContextError(tmt.utils.GeneralError): + """ + Error trying to create a context + """ + + +class TmtDefaultContextDimension(DefaultContextDimension): + case_sensitive = False + + +class TmtContextDimension(ContextDimension[T], Generic[T]): + _default_dimension_cls = TmtDefaultContextDimension + _registrar = {} + + +class TmtContext(Context): + _context_dimensions = TmtContextDimension + + +_CONTEXT_REGISTRY = PluginRegistry('context') +provides_context = _CONTEXT_REGISTRY.create_decorator() diff --git a/tmt/context/distro/__init__.py b/tmt/context/distro/__init__.py new file mode 100644 index 0000000000..eaaa5bac9f --- /dev/null +++ b/tmt/context/distro/__init__.py @@ -0,0 +1,274 @@ +import abc +import re +from typing import ClassVar, Literal, Optional, Union + +from fmf.context import CannotDecide + +from tmt.container import container +from tmt.context import ContextError, TmtContextDimension, provides_context +from tmt.plugins import PluginRegistry as PluginRegistry + + +@container +class Version: + """ + Distro version defining comparisons + """ + + parts: tuple[int, ...] + + def __str__(self) -> str: + return ".".join([*(str(p) for p in self.parts)]) + + @classmethod + def from_str(cls, raw: str) -> Optional["Version"]: + try: + parts = tuple(int(p) for p in raw.split(".")) + except ValueError: + return None + return cls(parts) + + def _compare_version(self, other: "Version", minor_mode: bool = False) -> Literal[-1, 0, 1]: + """ + Main comparison logic for different versions + + :arg other: the other version to compare to + :arg minor_mode: whether to compare within the same major version + :return: + - 1 if this version is greater than the ``other`` + - 0 if this version is equal to the ``other`` + - -1 if this version is less than the ``other`` + :raises CannotDecide: when comparison is not supported + """ + if minor_mode and len(self.parts) < len(other.parts): + raise CannotDecide( + f"Version '{self}' does not have enough parts to minor-mode compare to '{other}'" + ) + if minor_mode: + major_parts = len(other.parts) - 1 + # Compare the major parts. When there are no major parts, e.g. `9`, this + # compares 2 empty tuples and falls through to the normal comparison mode + if self.parts[:major_parts] != other.parts[:major_parts]: + raise CannotDecide( + f"Versions '{self}' and '{other}' have mismatched major components" + ) + + # Get the significant bits + part_to_compare = 1 + while self.parts[:part_to_compare] == other.parts[:part_to_compare]: + # Check if we ran out of significant parts to compare + if len(other.parts) == part_to_compare: + break + part_to_compare += 1 + + # Checking equality we must limit to the significant bits, + # otherwise tuple comparison fails when there are different number of parts + if self.parts[:part_to_compare] == other.parts[:part_to_compare]: + return 0 + # Here we can just use the `>` comparison of tuples which works for different lengths + if self.parts > other.parts: + return 1 + return -1 + + +FALLBACK_DISTRO_PATTERN = re.compile(r"^(?P[a-z]+)-?(?P[\d.]*)$") + +SANITIZE_DISTRO = re.compile(r"[^a-z\d.]+") + + +def sanitize_distro_name(raw_name: str) -> str: + """ + Convert the distro name to lower case and replace any separators to ``-``. + """ + return SANITIZE_DISTRO.sub("-", raw_name.lower()) + + +@container +class Distro(abc.ABC): + """ + Wrapper around distro and distro aliases defining comparisons. + """ + + #: Raw value used to construct the + _raw_value: str + + #: Distro's ``ID``. See ``/etc/os-release``. + id: str + + #: Distro's version number, name or alias. + #: The comparison of non-semver alias must be defined in the ordering operators. + version: Optional[Union[str, Version]] = None + + #: Registrar to identify the :py:class:`Distro` from supported regex patterns. + _registrar: ClassVar[dict[re.Pattern[str], type["Distro"]]] = {} + + #: The regex pattern that identifies this distro + _DISTRO_PATTERN: ClassVar[re.Pattern[str]] + + def __str__(self) -> str: + return self._raw_value + + @classmethod + @abc.abstractmethod + def _create_distro(cls, raw_id: str, match: re.Match[str]) -> "Distro": + """ + Actual constructor + """ + raise NotImplementedError + + def _assert_compatible_distro(self, other: "Distro") -> None: + """ + Check if other distro is compatible for comparison + + :raises CannotDecide: if distros cannot be compared to each other + """ + if self.id != other.id: + raise CannotDecide(f"Cannot compare distro '{self}' with '{other}'") + + def _compare_version(self, other: "Distro", minor_mode: bool = False) -> Literal[-1, 0, 1]: + """ + Main comparison logic for different distro versions + + The base implementation does not support non-:py:class:`Version` comparisons. This must + be defined by overriding this function. + + :arg other: the other distro to compare to + :arg minor_mode: whether to compare within the same major version + :return: + - 1 if this version is greater than the ``other`` + - 0 if this version is equal to the ``other`` + - -1 if this version is less than the ``other`` + :raises CannotDecide: when comparison is not supported + """ + assert self.version # narrow type + assert other.version # narrow type + if isinstance(self.version, str) or isinstance(other.version, str): + raise CannotDecide + return self.version._compare_version(other.version, minor_mode=minor_mode) + + def _eq_unversioned(self, other: "Distro") -> bool: + """ + Check equality against another distro ID + """ + assert not other.version # narrow type + # By default, the `_assert_compatible_distro` ensures we have the same id, + # so we know that at this point the distros are equal + return True + + def _op_eq(self, other: "Distro", minor_mode: bool = False) -> bool: + self._assert_compatible_distro(other) + # Comparator does not care what the main object's version is + if not other.version: + return self._eq_unversioned(other) + # The main object does not have a specific version, we cannot compare + if not self.version: + raise CannotDecide( + f"Distro '{self}' does not have a version to compare with '{other}'" + ) + return self._compare_version(other, minor_mode=minor_mode) == 0 + + def _op_greater(self, other: "Distro", minor_mode: bool = False) -> bool: + self._assert_compatible_distro(other) + # Either comparator or main object does not have a version + if not self.version or not other.version: + raise CannotDecide( + f"Distro '{self}' does not have a version to compare with '{other}'" + ) + return self._compare_version(other, minor_mode=minor_mode) == 1 + + @classmethod + def create(cls, raw_id: str) -> "Distro": + """ + Generic constructor + """ + id_sanitized = sanitize_distro_name(raw_id) + for pattern, distro_cls in cls._registrar.items(): + if match := pattern.match(id_sanitized): + return distro_cls._create_distro(id_sanitized, match) + if not (match := FALLBACK_DISTRO_PATTERN.match(id_sanitized)): + raise ContextError(f"Could not parse '{raw_id}' as a distro") + raw_version = match.group("version") + version = Version.from_str(raw_version) or raw_version or None + return UnknownDistro( + _raw_value=raw_id, + id=match.group("id"), + version=version, + ) + + @classmethod + def __init_subclass__(cls) -> None: + if hasattr(cls, "_DISTRO_PATTERN"): + cls._registrar[cls._DISTRO_PATTERN] = cls + + +@container +class UnknownDistro(Distro): + """ + Fallback distro if no registered distro matched. + """ + + @classmethod + def _create_distro(cls, raw_id: str, match: re.Match[str]) -> "Distro": + raise AssertionError("UnknownDistro must be constructed manually") + + +_DISTRO_CONTEXT_REGISTRY = PluginRegistry("context.distro") +provides_distro_context = _DISTRO_CONTEXT_REGISTRY.create_decorator() + + +@provides_context("distro") +class DistroContextDimension(TmtContextDimension[Distro]): + _dimension_name = "distro" + + @classmethod + def _make_value(cls, raw_value: str) -> Distro: + return Distro.create(raw_value) + + def _op_eq(self, other: str) -> bool: + try: + other_value = self._make_value(other) + except ContextError as err: + raise CannotDecide from err + return self.value._op_eq(other_value, minor_mode=False) + + def _op_greater(self, other: str) -> bool: + try: + other_value = self._make_value(other) + except ContextError as err: + raise CannotDecide from err + return self.value._op_greater(other_value, minor_mode=False) + + def _op_minor_eq(self, other: str) -> bool: + try: + other_value = self._make_value(other) + except ContextError as err: + raise CannotDecide from err + return self.value._op_eq(other_value, minor_mode=True) + + def _op_minor_greater(self, other: str) -> bool: + try: + other_value = self._make_value(other) + except ContextError as err: + raise CannotDecide from err + return self.value._op_greater(other_value, minor_mode=True) + + # TODO: Move these into fmf and have a @total_ordering-like decorator + # Note order of short-circuit matters to avoid going through a branch that, + # would raise CannotDecide. `_op_eq` must always be tested first for the `or_equal` + def _op_less(self, other: str) -> bool: + return not self._op_greater(other) and not self._op_eq(other) + + def _op_less_or_equal(self, other: str) -> bool: + return self._op_eq(other) or not self._op_greater(other) + + def _op_greater_or_equal(self, other: str) -> bool: + return self._op_eq(other) or self._op_greater(other) + + def _op_minor_less(self, other: str) -> bool: + return not self._op_minor_greater(other) and not self._op_minor_eq(other) + + def _op_minor_less_or_equal(self, other: str) -> bool: + return self._op_minor_eq(other) or not self._op_minor_greater(other) + + def _op_minor_greater_or_equal(self, other: str) -> bool: + return self._op_minor_eq(other) or self._op_minor_greater(other) diff --git a/tmt/context/distro/_rhel_like.py b/tmt/context/distro/_rhel_like.py new file mode 100644 index 0000000000..a6a1c34141 --- /dev/null +++ b/tmt/context/distro/_rhel_like.py @@ -0,0 +1,32 @@ +import typing +from abc import ABC +from typing import ClassVar, Literal + +from fmf.context import CannotDecide + +from tmt._compat.typing import TypeGuard +from tmt.container import container +from tmt.context.distro import Distro + +RhelFamily = Literal["fedora", "centos", "rhel"] +RHEL_FAMILIES: list[RhelFamily] = list(typing.get_args(RhelFamily)) + + +def is_rhel_family(val: str) -> TypeGuard[RhelFamily]: + return val in RHEL_FAMILIES + + +@container +class RhelLikeDistro(Distro, ABC): + """ + Base class for comparing RHEL-like distros. + """ + + #: Distro family to help determine how to compare distros + family: ClassVar[RhelFamily] + + def _assert_compatible_distro(self, other: Distro) -> None: + if not isinstance(other, RhelLikeDistro): + raise CannotDecide( + f"Cannot compare rhel-like distro '{self}' with non-rhel-like '{other}'" + ) diff --git a/tmt/context/distro/centos.py b/tmt/context/distro/centos.py new file mode 100644 index 0000000000..ae23e85f7a --- /dev/null +++ b/tmt/context/distro/centos.py @@ -0,0 +1,85 @@ +import re +from typing import Literal + +from tmt.container import container +from tmt.context.distro import Distro, Version, provides_distro_context +from tmt.context.distro._rhel_like import RhelLikeDistro + + +@provides_distro_context("centos") +@container +class CentosDistro(RhelLikeDistro): + _DISTRO_PATTERN = re.compile(r"^centos-?(?Pstream)?-?(?P[\d.]*])$") + family = "centos" + stream: bool = False + + @classmethod + def _create_distro(cls, raw_id: str, match: re.Match[str]) -> Distro: + stream = bool(match.group("stream")) + return CentosDistro( + _raw_value=raw_id, + id="centos", + version=Version.from_str(match.group("version")), + stream=stream, + ) + + def _eq_unversioned(self, other: Distro) -> bool: + assert isinstance(other, RhelLikeDistro) # narrow type + assert other.version is None # narrow type + + # centos is not Fedora + if other.family == "fedora": + return False + + # centos is rhel-like + if other.family == "rhel": + return True + + assert isinstance(other, CentosDistro) # narrow type + if other.stream: + # Centos-stream-X is centos-stream + # Centos-X is not centos-stream + return self.stream + # All centos are centos + return True + + def _compare_version(self, other: Distro, minor_mode: bool = False) -> Literal[-1, 0, 1]: + assert isinstance(other, RhelLikeDistro) # narrow type + assert self.version # narrow type + assert other.version # narrow type + + # Fedora is always newer than rhel + if other.family == "fedora": + return -1 + + # Comparing against rhel + if other.family == "rhel": + assert isinstance(other.version, Version) # narrow type + version_comp = self.version._compare_version(other.version, minor_mode=minor_mode) + + # centos < rhel with higher version + if version_comp == -1: + return -1 + # centos > rhel with same version or lower + return 1 + + # Remaining case is comparing centos versions, use default logic + assert isinstance(other, CentosDistro) # narrow type + version_comp = super()._compare_version(other, minor_mode=minor_mode) + # Same types return comparison as it should be + if self.stream == other.stream: + return version_comp + # Comparing centos stream against non-stream + if self.stream: + # Stream version is newer than non-stream with same version + if version_comp == 0: + return 1 + # Otherwise normal comparison holds + return version_comp + # Comparing centos non-stream against stream + assert other.stream + # Non-stream version is older than stream with same version + if version_comp == 0: + return -1 + # Otherwise normal comparison holds + return version_comp diff --git a/tmt/context/distro/fedora.py b/tmt/context/distro/fedora.py new file mode 100644 index 0000000000..eb1db86437 --- /dev/null +++ b/tmt/context/distro/fedora.py @@ -0,0 +1,94 @@ +import re +import typing +from typing import Literal + +from tmt._compat.typing import TypeGuard +from tmt.context import ContextError +from tmt.context.distro import Distro, Version, provides_distro_context +from tmt.context.distro._rhel_like import RhelLikeDistro + +# TODO: Can support latest alias using fedora-distro-aliases +FedoraAlias = Literal["rawhide", "eln"] +FEDORA_ALIASES: list[FedoraAlias] = list(typing.get_args(FedoraAlias)) + + +def is_fedora_alias(val: str) -> TypeGuard[FedoraAlias]: + return val in FEDORA_ALIASES + + +@provides_distro_context("fedora") +class FedoraDistro(RhelLikeDistro): + _DISTRO_PATTERN = re.compile(r"^fedora-?(?P.*)$") + family = "fedora" + + @classmethod + def _create_distro(cls, raw_id: str, match: re.Match[str]) -> Distro: + version_or_alias = match.group("version") + if not version_or_alias: + return FedoraDistro( + _raw_value=raw_id, + id="fedora", + version=None, + ) + if is_fedora_alias(version_or_alias): + return FedoraDistro( + _raw_value=raw_id, + id="fedora", + version=version_or_alias, + ) + version = Version.from_str(version_or_alias) + if not version: + raise ContextError(f"Could not determine fedora version of context '{raw_id}'") + if len(version.parts) > 1: + raise ContextError(f"Fedora does not have minor versions: '{raw_id}'") + return FedoraDistro( + _raw_value=raw_id, + id="fedora", + version=version, + ) + + def _eq_unversioned(self, other: Distro) -> bool: + assert isinstance(other, RhelLikeDistro) # narrow type + assert other.version is None # narrow type + + # eln is the same as fedora and other rhel-like + if self.version == "eln": + return True + # everything else is *only* fedora + return other.family == "fedora" + + def _compare_version(self, other: Distro, minor_mode: bool = False) -> Literal[-1, 0, 1]: + assert isinstance(other, RhelLikeDistro) # narrow type + assert self.version # narrow type + assert other.version # narrow type + + # Fedora and eln are always newer than rhel or centos + if other.family != "fedora": + return 1 + + # Comparing against rawhide + if other.version == "rawhide": + if self.version == "rawhide": + return 0 + # rawhide > any version + return -1 + + # Comparing against eln target + if other.version == "eln": + # Rawhide > eln + if self.version == "rawhide": + return 1 + # eln == eln (of course) + if self.version == "eln": + return 0 + # Any versioned Fedora < eln + assert isinstance(other.version, Version) + return -1 + + assert isinstance(other.version, Version) + if not isinstance(self.version, Version): + # rawhide and eln > any versioned Fedora + return 1 + + # Remaining case is comparing fedora versions, use default logic + return super()._compare_version(other, minor_mode=minor_mode) diff --git a/tmt/context/distro/rhel.py b/tmt/context/distro/rhel.py new file mode 100644 index 0000000000..5f86964330 --- /dev/null +++ b/tmt/context/distro/rhel.py @@ -0,0 +1,50 @@ +import re +from typing import Literal + +from tmt.context.distro import Distro, Version, provides_distro_context +from tmt.context.distro._rhel_like import RhelLikeDistro + + +@provides_distro_context("rhel") +class RhelDistro(RhelLikeDistro): + _DISTRO_PATTERN = re.compile(r"^rhel-?(?P[\d.]*)$") + family = "rhel" + + @classmethod + def _create_distro(cls, raw_id: str, match: re.Match[str]) -> Distro: + return RhelDistro( + _raw_value=raw_id, + id="rhel", + version=Version.from_str(match.group("version")), + ) + + def _eq_unversioned(self, other: Distro) -> bool: + assert isinstance(other, RhelLikeDistro) # narrow type + assert other.version is None # narrow type + + # rhel is only rhel + return other.family == "rhel" + + def _compare_version(self, other: Distro, minor_mode: bool = False) -> Literal[-1, 0, 1]: + assert isinstance(other, RhelLikeDistro) # narrow type + assert isinstance(self.version, Version) # narrow type + assert other.version # narrow type + + # Fedora is always newer than rhel + if other.family == "fedora": + return -1 + + # Comparing against centos + if other.family == "centos": + assert isinstance(other.version, Version) # narrow type + version_comp = self.version._compare_version(other.version, minor_mode=minor_mode) + + # rhel > centos with lower version + if version_comp == 1: + return 1 + + # rhel < centos with same version or higher + return -1 + + # Remaining case is comparing rhel versions, use default logic + return super()._compare_version(other, minor_mode) diff --git a/tmt/export/nitrate.py b/tmt/export/nitrate.py index 5d041e42a2..a87751bc2c 100644 --- a/tmt/export/nitrate.py +++ b/tmt/export/nitrate.py @@ -14,11 +14,12 @@ cast, ) -import fmf.context +import fmf.utils from click import echo import tmt.export import tmt.utils +from tmt.context import TmtContext from tmt.utils import ConvertError, Path from tmt.utils.structured_field import StructuredField from tmt.utils.themes import style @@ -289,9 +290,9 @@ def enabled_for_environment(test: 'tmt.base.core.Test', tcms_notes: str) -> bool return True try: - context = fmf.context.Context(**context_dict) + context = TmtContext(**context_dict) test_node = test.node.copy() - test_node.adjust(context, case_sensitive=False) + test_node.adjust(context) return tmt.Test(node=test_node, logger=test._logger).enabled except BaseException as exception: log.debug(f"Failed to process adjust: {exception}") diff --git a/tmt/plugins/__init__.py b/tmt/plugins/__init__.py index 384e2b2e06..094e3b839f 100644 --- a/tmt/plugins/__init__.py +++ b/tmt/plugins/__init__.py @@ -82,6 +82,8 @@ def _discover_packages() -> list[tuple[str, Path]]: ('tmt.steps.prepare.feature', Path('steps/prepare/feature')), ('tmt.steps.prepare.artifact.providers', Path('steps/prepare/artifact/providers')), ('tmt.plugins.plan_shapers', Path('plugins/plan_shapers')), + ('tmt.context', Path('context')), + ('tmt.context.distro', Path('context/distro')), ] diff --git a/tmt/steps/__init__.py b/tmt/steps/__init__.py index 051fe092d6..b88445b0a3 100644 --- a/tmt/steps/__init__.py +++ b/tmt/steps/__init__.py @@ -56,6 +56,7 @@ option_to_key, simple_field, ) +from tmt.context import TmtContext from tmt.options import ClickOptionDecoratorType, option from tmt.result import ResultOutcome from tmt.utils import ( @@ -2305,7 +2306,7 @@ def enabled_by_when(self) -> bool: Check if the plugin is enabled by 'when' keyword """ - fmf_context = fmf.context.Context(**self.step.plan.fmf_context) + fmf_context = TmtContext(**self.step.plan.fmf_context) when_rules = self.get('when', []) if not when_rules: # No 'when' -> enabled everywhere diff --git a/tmt/trying.py b/tmt/trying.py index d3f9d5f099..4de33d1d78 100644 --- a/tmt/trying.py +++ b/tmt/trying.py @@ -12,7 +12,6 @@ from typing import Any, Callable, ClassVar, Optional, Union, cast import fmf -import fmf.context import fmf.utils import tmt @@ -28,6 +27,7 @@ import tmt.utils from tmt import Plan from tmt.base.run import RunData +from tmt.context import TmtContext from tmt.steps.prepare import PreparePlugin from tmt.utils import Command, GeneralError, MetadataError, Path from tmt.utils.themes import style @@ -305,8 +305,7 @@ def get_default_plans(self, run: tmt.base.run.Run) -> list[Plan]: # each user plan in separation and merge them to the main tree. for user_plan in user_plans: user_plan.adjust( - fmf.context.Context(**self.tree.fmf_context), - case_sensitive=False, + TmtContext(**self.tree.fmf_context), decision_callback=tmt.base.core.create_adjust_callback(self._logger), additional_rules=self.tree._additional_rules, )