Skip to content

Commit 207f6c7

Browse files
committed
squash: save disabled phases as well, refactoring
1 parent 98ba16b commit 207f6c7

File tree

4 files changed

+105
-60
lines changed

4 files changed

+105
-60
lines changed

tests/recipe/data/import.yaml

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,59 @@ plans:
6969
result: respect
7070
provision:
7171
enabled: false
72-
phases: []
72+
phases:
73+
- name: default-0
74+
how: local
75+
order: 50
76+
become: false
7377
prepare:
7478
enabled: false
75-
phases: []
79+
phases:
80+
- name: enable-latest-copr
81+
how: install
82+
order: 50
83+
summary: Enable copr repo with the latest tmt bits
84+
copr:
85+
- '@teemtee/latest'
86+
missing: fail
87+
- name: disable-tag-repo
88+
how: shell
89+
order: 50
90+
summary: Completely disable the tag repository
91+
script:
92+
- "dnf repolist | grep testing-farm-tag-repository || exit 0\nif command
93+
-v dnf5; then\n dnf config-manager setopt testing-farm-tag-repository.enabled=0\n\
94+
else\n dnf config-manager --set-disabled testing-farm-tag-repository\n\
95+
fi\n"
7696
execute:
7797
enabled: false
78-
phases: []
98+
phases:
99+
- name: default-0
100+
how: tmt
101+
order: 50
102+
duration: 1h
103+
ignore-duration: false
104+
exit-first: false
105+
interactive: false
106+
restraint-compatible: false
107+
no-progress-bar: false
79108
results-path: plans/import/execute/results.yaml
80109
report:
81110
enabled: false
82-
phases: []
111+
phases:
112+
- name: default-0
113+
how: display
114+
order: 50
115+
display-guest: auto
83116
finish:
84117
enabled: false
85-
phases: []
118+
phases:
119+
- name: default-0
120+
how: shell
121+
order: 50
86122
cleanup:
87123
enabled: false
88-
phases: []
124+
phases:
125+
- name: default-0
126+
how: tmt
127+
order: 50

tmt/base/core.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2553,13 +2553,9 @@ def plans(
25532553

25542554
# Handle defaults, apply possible command line options
25552555
logger = logger or (run._logger if run is not None else self._logger)
2556-
if run is not None and run.recipe:
2557-
names = names or [plan.name for plan in run.recipe.plans]
2558-
local_plan_keys = []
2559-
else:
2560-
names = names or []
2561-
local_plan_keys = (keys or []) + ['execute']
2556+
local_plan_keys = (keys or []) + ['execute']
25622557
remote_plan_keys = (keys or []) + ['plan']
2558+
names = names or []
25632559
filters = filters or []
25642560
conditions = conditions or []
25652561
# FIXME: cast() - typeless "dispatcher" method
@@ -2923,10 +2919,9 @@ def __init__(
29232919

29242920
self.policies = policies or []
29252921
self.recipe_manager = RecipeManager(logger)
2926-
self.recipe = self.recipe_manager.load(recipe_path) if recipe_path else None
2927-
if self.recipe is not None and self._tree is not None:
2928-
self.recipe_manager.update_tree(self.recipe, self._tree.tree)
2929-
self.remove = self.remove or self.recipe.run.remove
2922+
self.recipe = self.recipe_manager.load(
2923+
self, recipe_path, self._tree.tree if self._tree is not None else None
2924+
)
29302925

29312926
@property
29322927
def run_workdir(self) -> Path:
@@ -3037,6 +3032,17 @@ def _environment_from_cli(self) -> Environment:
30373032
logger=self._logger,
30383033
)
30393034

3035+
@property
3036+
def _environment_from_recipe(self) -> Environment:
3037+
"""
3038+
Environment variables from the recipe.
3039+
"""
3040+
3041+
if self.recipe is None:
3042+
return Environment()
3043+
3044+
return self.recipe.run.environment.copy()
3045+
30403046
@property
30413047
def environment(self) -> Environment:
30423048
"""
@@ -3047,21 +3053,15 @@ def environment(self) -> Environment:
30473053
30483054
* run's environment, saved from the previous runs in the same
30493055
workdir,
3056+
* run's environment, loaded from the recipe file.
30503057
* run's environment, ``--environment`` and ``--environment-file``
30513058
options.
30523059
"""
30533060

3054-
if self.recipe is not None:
3055-
return Environment(
3056-
{
3057-
**self.recipe.run.environment,
3058-
**self._environment_from_cli,
3059-
}
3060-
)
3061-
30623061
return Environment(
30633062
{
30643063
**self._environment_from_workdir,
3064+
**self._environment_from_recipe,
30653065
**self._environment_from_cli,
30663066
}
30673067
)

tmt/recipe.py

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import TYPE_CHECKING, Any, Callable, Optional, TypedDict, cast
22

3-
import fmf
3+
from fmf import Tree
44

55
import tmt.utils
66
from tmt.checks import Check, _RawCheck, normalize_test_checks
@@ -14,7 +14,7 @@
1414
)
1515
from tmt.log import Logger
1616
from tmt.result import ResultInterpret
17-
from tmt.steps import Step, _RawStepData
17+
from tmt.steps import STEPS, Step, _RawStepData
1818
from tmt.steps.discover import Discover, TestOrigin
1919
from tmt.utils import Common, Environment, FmfContext, NormalizeKeysMixin, Path, ShellScript
2020

@@ -276,17 +276,18 @@ class _RecipeStep(SpecBasedContainer[_RawRecipeStep, _RawRecipeStep], Serializab
276276

277277
@classmethod
278278
def from_step(cls, step: 'Step') -> '_RecipeStep':
279-
enabled = bool(step.enabled)
280279
return _RecipeStep(
281-
enabled=enabled,
282-
phases=[phase.to_minimal_spec() for phase in step.data] if enabled else [],
280+
enabled=bool(step.enabled),
281+
phases=[phase.to_minimal_spec() for phase in step.data],
283282
)
284283

285284
# ignore[override]: does not match the signature on purpose, we need to pass logger
286285
@classmethod
287286
def from_spec(cls, spec: _RawRecipeStep, logger: Logger) -> '_RecipeStep': # type: ignore[override]
288-
enabled = bool(spec.get('enabled', False))
289-
return _RecipeStep(enabled=enabled, phases=spec.get('phases', []) if enabled else [])
287+
return _RecipeStep(
288+
enabled=bool(spec.get('enabled', False)),
289+
phases=spec.get('phases', []),
290+
)
290291

291292
def to_spec(self) -> _RawRecipeStep:
292293
return _RawRecipeStep(enabled=self.enabled, phases=self.phases)
@@ -316,10 +317,9 @@ def to_spec(self) -> _RawRecipeStep:
316317
# ignore[override]: does not match the signature on purpose, we need to pass logger
317318
@classmethod
318319
def from_spec(cls, spec: _RawRecipeStep, logger: Logger) -> '_RecipeDiscoverStep': # type: ignore[override]
319-
enabled = bool(spec.get('enabled', False))
320320
return _RecipeDiscoverStep(
321-
enabled=enabled,
322-
phases=spec.get('phases', []) if enabled else [],
321+
enabled=bool(spec.get('enabled', False)),
322+
phases=spec.get('phases', []),
323323
tests=[
324324
_RecipeTest.from_spec(test, logger)
325325
for test in cast(list[_RawRecipeTest], spec.get('tests', []))
@@ -329,10 +329,9 @@ def from_spec(cls, spec: _RawRecipeStep, logger: Logger) -> '_RecipeDiscoverStep
329329
@classmethod
330330
def from_step(cls, step: 'Step') -> '_RecipeDiscoverStep':
331331
assert isinstance(step, Discover)
332-
enabled = bool(step.enabled)
333332
return _RecipeDiscoverStep(
334-
enabled=enabled,
335-
phases=[phase.to_minimal_spec() for phase in step.data] if enabled else [],
333+
enabled=bool(step.enabled),
334+
phases=[phase.to_minimal_spec() for phase in step.data],
336335
tests=[_RecipeTest.from_test_origin(test_origin) for test_origin in step.tests()],
337336
)
338337

@@ -354,20 +353,18 @@ def to_spec(self) -> _RawRecipeStep:
354353
# ignore[override]: does not match the signature on purpose, we need to pass logger
355354
@classmethod
356355
def from_spec(cls, spec: _RawRecipeStep, logger: Logger) -> '_RecipeExecuteStep': # type: ignore[override]
357-
enabled = bool(spec.get('enabled', False))
358356
results_path = cast(Optional[str], spec.get('results-path', None))
359357
return _RecipeExecuteStep(
360-
enabled=enabled,
361-
phases=spec.get('phases', []) if enabled else [],
358+
enabled=bool(spec.get('enabled', False)),
359+
phases=spec.get('phases', []),
362360
results_path=Path(results_path) if results_path else None,
363361
)
364362

365363
@classmethod
366364
def from_step(cls, step: 'Step') -> '_RecipeExecuteStep':
367-
enabled = bool(step.enabled)
368365
return _RecipeExecuteStep(
369-
enabled=enabled,
370-
phases=[phase.to_minimal_spec() for phase in step.data] if enabled else [],
366+
enabled=bool(step.enabled),
367+
phases=[phase.to_minimal_spec() for phase in step.data],
371368
results_path=(step.step_workdir / 'results.yaml').relative_to(step.run_workdir),
372369
)
373370

@@ -549,23 +546,25 @@ def to_spec(self) -> _RawRecipe:
549546
'plans': [plan.to_minimal_spec() for plan in self.plans],
550547
}
551548

552-
def get_plan_by_name(self, name: str) -> _RecipePlan:
553-
plans = [plan for plan in self.plans if plan.name == name]
554-
if len(plans) != 1:
555-
raise tmt.utils.GeneralError(
556-
f"Unable to find the correct plan in the recipe: '{name}'"
557-
)
558-
return plans[0]
559-
560549

561550
class RecipeManager(Common):
562551
def __init__(self, logger: Logger):
563552
super().__init__(logger=logger)
564553

565-
def load(self, path: Path) -> Recipe:
566-
return Recipe.from_spec(
567-
cast(_RawRecipe, tmt.utils.yaml_to_dict(self.read(path))), self._logger
554+
def load(
555+
self, run: 'Run', recipe_path: Optional[Path], fmf_tree: Optional[Tree]
556+
) -> Optional[Recipe]:
557+
if recipe_path is None or fmf_tree is None:
558+
return None
559+
560+
recipe = Recipe.from_spec(
561+
cast(_RawRecipe, tmt.utils.yaml_to_dict(self.read(recipe_path))), self._logger
568562
)
563+
self._update_tree(recipe, fmf_tree)
564+
self._update_cli_context(recipe)
565+
run.remove = run.remove or recipe.run.remove
566+
567+
return recipe
569568

570569
def save(self, run: 'Run') -> None:
571570
recipe = Recipe(
@@ -596,9 +595,21 @@ def tests(self, recipe: Recipe, plan_name: str) -> list[TestOrigin]:
596595
raise tmt.utils.GeneralError(f"Plan '{plan_name}' not found in the recipe.")
597596

598597
@staticmethod
599-
def update_tree(recipe: Recipe, tree: fmf.Tree) -> None:
598+
def _update_tree(recipe: Recipe, tree: Tree) -> None:
600599
"""
601600
Load the plans from the recipe and update the given fmf tree with their specifications.
602601
"""
603602
tree.children.clear()
604603
tree.update({plan.name: plan.to_fmf_spec() for plan in recipe.plans})
604+
605+
def _update_cli_context(self, recipe: Recipe) -> None:
606+
"""
607+
Load the names of enabled steps from the recipe and save them to the CLI context.
608+
"""
609+
if self._cli_context_object is None:
610+
return
611+
for plan in recipe.plans:
612+
for step_name in STEPS:
613+
recipe_step = plan.get_step_by_name(step_name)
614+
if recipe_step.enabled:
615+
self._cli_context_object.steps.add(step_name)

tmt/steps/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -747,11 +747,6 @@ def enabled(self) -> Optional[bool]:
747747
if self.plan.my_run is None or self.plan.my_run._cli_context_object is None:
748748
return None
749749

750-
if self.plan.my_run.recipe:
751-
plan = self.plan.my_run.recipe.get_plan_by_name(self.plan.name)
752-
step = plan.get_step_by_name(self.step_name)
753-
return step.enabled
754-
755750
return self.name in self.plan.my_run._cli_context_object.steps
756751

757752
@functools.cached_property

0 commit comments

Comments
 (0)