|
7 | 7 | import enum
|
8 | 8 | import functools
|
9 | 9 | import itertools
|
| 10 | +import operator |
10 | 11 | import os
|
11 | 12 | import re
|
12 | 13 | import shutil
|
@@ -1852,8 +1853,8 @@ class Plan(
|
1852 | 1853 | _original_plan: Optional['Plan'] = field(default=None, internal=True)
|
1853 | 1854 | _original_plan_fmf_id: Optional[FmfId] = field(default=None, internal=True)
|
1854 | 1855 |
|
1855 |
| - _imported_plan_fmf_id: Optional[FmfId] = field(default=None, internal=True) |
1856 |
| - _imported_plan: Optional['Plan'] = field(default=None, internal=True) |
| 1856 | + _imported_plan_fmf_ids: list[FmfId] = field(default_factory=list, internal=True) |
| 1857 | + _imported_plans: list['Plan'] = field(default_factory=list, internal=True) |
1857 | 1858 |
|
1858 | 1859 | _derived_plans: list['Plan'] = field(default_factory=list, internal=True)
|
1859 | 1860 | derived_id: Optional[int] = field(default=None, internal=True)
|
@@ -1901,12 +1902,14 @@ def __init__(
|
1901 | 1902 | # set, incorrect default value is generated, and the field ends up being
|
1902 | 1903 | # set to `None`. See https://github.com/teemtee/tmt/issues/2630.
|
1903 | 1904 | self._applied_cli_invocations = []
|
| 1905 | + self._imported_plan_fmf_ids = [] |
| 1906 | + self._imported_plans = [] |
1904 | 1907 | self._derived_plans = []
|
1905 | 1908 |
|
1906 | 1909 | # Check for possible remote plan reference first
|
1907 | 1910 | reference = self.node.get(['plan', 'import'])
|
1908 | 1911 | if reference is not None:
|
1909 |
| - self._imported_plan_fmf_id = FmfId.from_spec(reference) |
| 1912 | + self._imported_plan_fmf_ids = [FmfId.from_spec(reference)] |
1910 | 1913 |
|
1911 | 1914 | # Save the run, prepare worktree and plan data directory
|
1912 | 1915 | self.my_run = run
|
@@ -2345,19 +2348,23 @@ def show(self) -> None:
|
2345 | 2348 | self._show_additional_keys()
|
2346 | 2349 |
|
2347 | 2350 | # Show fmf id of the remote plan in verbose mode
|
2348 |
| - if (self._original_plan or self._imported_plan_fmf_id) and self.verbosity_level: |
| 2351 | + if (self._original_plan or self._imported_plan_fmf_ids) and self.verbosity_level: |
2349 | 2352 | # Pick fmf id from the original plan by default, use the
|
2350 | 2353 | # current plan in shallow mode when no plans are fetched.
|
2351 | 2354 |
|
| 2355 | + def _show_imported(fmf_id: FmfId) -> None: |
| 2356 | + echo(tmt.utils.format('import', '', key_color='blue')) |
| 2357 | + |
| 2358 | + for key, value in fmf_id.items(): |
| 2359 | + echo(tmt.utils.format(key, value, key_color='green')) |
| 2360 | + |
2352 | 2361 | if self._original_plan is not None:
|
2353 |
| - fmf_id = self._original_plan._imported_plan_fmf_id |
2354 |
| - else: |
2355 |
| - fmf_id = self._imported_plan_fmf_id |
| 2362 | + for fmf_id in self._original_plan._imported_plan_fmf_ids: |
| 2363 | + _show_imported(fmf_id) |
2356 | 2364 |
|
2357 |
| - echo(tmt.utils.format('import', '', key_color='blue')) |
2358 |
| - assert fmf_id is not None # narrow type |
2359 |
| - for key, value in fmf_id.items(): |
2360 |
| - echo(tmt.utils.format(key, value, key_color='green')) |
| 2365 | + else: |
| 2366 | + for fmf_id in self._imported_plan_fmf_ids: |
| 2367 | + _show_imported(fmf_id) |
2361 | 2368 |
|
2362 | 2369 | # FIXME - Make additional attributes configurable
|
2363 | 2370 | def lint_unknown_keys(self) -> LinterReturn:
|
@@ -2735,20 +2742,16 @@ def is_remote_plan_reference(self) -> bool:
|
2735 | 2742 | """
|
2736 | 2743 | Check whether the plan is a remote plan reference
|
2737 | 2744 | """
|
2738 |
| - return self._imported_plan_fmf_id is not None |
| 2745 | + return bool(self._imported_plan_fmf_ids) |
2739 | 2746 |
|
2740 |
| - def import_plan(self) -> Optional['Plan']: |
2741 |
| - """ |
2742 |
| - Import plan from a remote repository, return a Plan instance |
| 2747 | + def _resolve_import(self, plan_id: FmfId) -> Iterator['Plan']: |
2743 | 2748 | """
|
2744 |
| - if not self.is_remote_plan_reference: |
2745 |
| - return None |
| 2749 | + Discover and import plans matching a given fmf id. |
2746 | 2750 |
|
2747 |
| - if self._imported_plan: |
2748 |
| - return self._imported_plan |
| 2751 | + :param plan_id: fmf id representing one or more plans to import. |
| 2752 | + :yields: new :py:class:`Plan` for each imported plan. |
| 2753 | + """ |
2749 | 2754 |
|
2750 |
| - assert self._imported_plan_fmf_id is not None # narrow type |
2751 |
| - plan_id = self._imported_plan_fmf_id |
2752 | 2755 | self.debug(f"Import remote plan '{plan_id.name}' from '{plan_id.url}'.", level=3)
|
2753 | 2756 |
|
2754 | 2757 | # Clone the whole git repository if executing tests (run is attached)
|
@@ -2842,15 +2845,32 @@ def import_plan(self) -> Optional['Plan']:
|
2842 | 2845 |
|
2843 | 2846 | # Override the plan name with the local one to ensure unique names
|
2844 | 2847 | node.name = self.name
|
2845 |
| - # Create the plan object, save links between both plans |
2846 |
| - self._imported_plan = Plan(node=node, run=self.my_run, logger=self._logger) |
2847 |
| - self._imported_plan._original_plan = self |
2848 |
| - self._imported_plan._original_plan_fmf_id = self.fmf_id |
2849 | 2848 |
|
2850 | 2849 | with self.environment.as_environ():
|
2851 | 2850 | expand_node_data(node.data, self._fmf_context)
|
2852 | 2851 |
|
2853 |
| - return self._imported_plan |
| 2852 | + yield Plan(node=node, run=self.my_run, logger=self._logger) |
| 2853 | + |
| 2854 | + def resolve_imports(self) -> list['Plan']: |
| 2855 | + """ |
| 2856 | + Resolve possible references to remote plans. |
| 2857 | +
|
| 2858 | + :returns: one or more plans replacing the current one. The |
| 2859 | + current plan may also be one of the returned ones. |
| 2860 | + """ |
| 2861 | + |
| 2862 | + if not self.is_remote_plan_reference: |
| 2863 | + return [self] |
| 2864 | + |
| 2865 | + if not self._imported_plans: |
| 2866 | + for plan_id in self._imported_plan_fmf_ids: |
| 2867 | + for imported_plan in self._resolve_import(plan_id): |
| 2868 | + imported_plan._original_plan = self |
| 2869 | + imported_plan._original_plan_fmf_id = self.fmf_id |
| 2870 | + |
| 2871 | + self._imported_plans.append(imported_plan) |
| 2872 | + |
| 2873 | + return self._imported_plans |
2854 | 2874 |
|
2855 | 2875 | def derive_plan(self, derived_id: int, tests: dict[str, list[Test]]) -> 'Plan':
|
2856 | 2876 | """
|
@@ -3580,7 +3600,7 @@ def plans(
|
3580 | 3600 | ]
|
3581 | 3601 |
|
3582 | 3602 | if not Plan._opt('shallow'):
|
3583 |
| - plans = [plan.import_plan() or plan for plan in plans] |
| 3603 | + plans = functools.reduce(operator.iadd, (plan.resolve_imports() for plan in plans), []) |
3584 | 3604 |
|
3585 | 3605 | return self._filters_conditions(
|
3586 | 3606 | sorted(plans, key=lambda plan: plan.order), filters, conditions, links, excludes
|
|
0 commit comments