From d55a9fdf2a51aaff3f68b07b60b51e0312569d74 Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 13 May 2026 15:39:34 +0200 Subject: [PATCH 1/3] [TEMP] Use the fmf PR Signed-off-by: Cristian Le --- .packit.yaml | 2 +- .pre-commit-config.yaml | 26 +++++++++++++------------- pyproject.toml | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) 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", From 4bc72e4db33ea38aa172469aeed0d079c03b837b Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 13 May 2026 15:36:03 +0200 Subject: [PATCH 2/3] Drop `case_sensitive` flag Signed-off-by: Cristian Le --- tmt/base/core.py | 2 -- tmt/base/plan.py | 1 - tmt/export/nitrate.py | 2 +- tmt/trying.py | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/tmt/base/core.py b/tmt/base/core.py index 3015530c9e..f793e09c18 100644 --- a/tmt/base/core.py +++ b/tmt/base/core.py @@ -2388,7 +2388,6 @@ def tree(self) -> fmf.Tree: # Adjust metadata for current fmf context self._tree.adjust( fmf.context.Context(**self.fmf_context), - case_sensitive=False, decision_callback=create_adjust_callback(self._logger), additional_rules=self._additional_rules, ) @@ -3533,7 +3532,6 @@ def resolve_dynamic_ref( 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, 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..905f965938 100644 --- a/tmt/base/plan.py +++ b/tmt/base/plan.py @@ -1531,7 +1531,6 @@ def _convert_node(node: fmf.Tree) -> 'Plan': # action, including the adjust-plans rules. node.adjust( fmf.context.Context(**alteration_fmf_context), - case_sensitive=False, additional_rules=reference.adjust_plans, ) diff --git a/tmt/export/nitrate.py b/tmt/export/nitrate.py index 5d041e42a2..04713816d2 100644 --- a/tmt/export/nitrate.py +++ b/tmt/export/nitrate.py @@ -291,7 +291,7 @@ def enabled_for_environment(test: 'tmt.base.core.Test', tcms_notes: str) -> bool try: context = fmf.context.Context(**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/trying.py b/tmt/trying.py index d3f9d5f099..7ed5df6ba7 100644 --- a/tmt/trying.py +++ b/tmt/trying.py @@ -306,7 +306,6 @@ def get_default_plans(self, run: tmt.base.run.Run) -> list[Plan]: for user_plan in user_plans: user_plan.adjust( fmf.context.Context(**self.tree.fmf_context), - case_sensitive=False, decision_callback=tmt.base.core.create_adjust_callback(self._logger), additional_rules=self.tree._additional_rules, ) From ef39518607e7df07f58e678aeb110e48de1e26de Mon Sep 17 00:00:00 2001 From: Cristian Le Date: Wed, 13 May 2026 16:21:20 +0200 Subject: [PATCH 3/3] Introduce TmtContext Signed-off-by: Cristian Le --- tmt/base/core.py | 6 +++--- tmt/base/plan.py | 4 ++-- tmt/cli/about.py | 1 + tmt/context/__init__.py | 31 +++++++++++++++++++++++++++++++ tmt/export/nitrate.py | 5 +++-- tmt/plugins/__init__.py | 1 + tmt/steps/__init__.py | 3 ++- tmt/trying.py | 4 ++-- 8 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 tmt/context/__init__.py diff --git a/tmt/base/core.py b/tmt/base/core.py index f793e09c18..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,7 +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), + TmtContext(**self.fmf_context), decision_callback=create_adjust_callback(self._logger), additional_rules=self._additional_rules, ) @@ -3531,7 +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), + 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 905f965938..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,7 +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), + TmtContext(**alteration_fmf_context), additional_rules=reference.adjust_plans, ) diff --git a/tmt/cli/about.py b/tmt/cli/about.py index 5e8df6ffb8..c0be3cc991 100644 --- a/tmt/cli/about.py +++ b/tmt/cli/about.py @@ -39,6 +39,7 @@ 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'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/export/nitrate.py b/tmt/export/nitrate.py index 04713816d2..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,7 +290,7 @@ 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) return tmt.Test(node=test_node, logger=test._logger).enabled diff --git a/tmt/plugins/__init__.py b/tmt/plugins/__init__.py index 384e2b2e06..e8ff483672 100644 --- a/tmt/plugins/__init__.py +++ b/tmt/plugins/__init__.py @@ -82,6 +82,7 @@ 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')), ] 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 7ed5df6ba7..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,7 +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), + TmtContext(**self.tree.fmf_context), decision_callback=tmt.base.core.create_adjust_callback(self._logger), additional_rules=self.tree._additional_rules, )