From 9320cda566b0e6251a30e13c63baee3e576fff70 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 7 Oct 2025 10:50:11 +0200 Subject: [PATCH 01/14] Lint: Fix type issues in features module --- src/gardenlinux/features/__main__.py | 19 ++++++++++--------- src/gardenlinux/features/parser.py | 4 +--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/gardenlinux/features/__main__.py b/src/gardenlinux/features/__main__.py index 2d9c6260..831d9860 100644 --- a/src/gardenlinux/features/__main__.py +++ b/src/gardenlinux/features/__main__.py @@ -8,8 +8,6 @@ import argparse import logging import os -import re -import sys from functools import reduce from os import path from typing import Any, List, Set @@ -100,9 +98,9 @@ def main() -> None: commit_id = cname.commit_id version = cname.version - input_features = Parser.get_cname_as_feature_set(flavor) + _ = Parser.get_cname_as_feature_set(flavor) else: - input_features = args.features + _ = args.features if arch is None or arch == "" and (args.type in ("cname", "arch")): raise RuntimeError( @@ -124,7 +122,7 @@ def main() -> None: print(arch) elif args.type in ("cname_base", "cname", "graph"): graph = Parser(gardenlinux_root, feature_dir_name).filter( - flavor, additional_filter_func=additional_filter_func + flavor, additional_filter_func=additional_filter_func # type: ignore ) sorted_features = Parser.sort_graph_nodes(graph) @@ -140,10 +138,10 @@ def main() -> None: cname = flavor if arch is not None: - cname += f"-{arch}" + cname += f"-{arch}" # type: ignore - None check is carried out. if commit_id is not None: - cname += f"-{version}-{commit_id}" + cname += f"-{version}-{commit_id}" # type: ignore - None check is carried out. print(cname) elif args.type == "graph": @@ -173,7 +171,7 @@ def main() -> None: print(f"{version}-{commit_id}") -def get_cname_base(sorted_features: Set[str]): +def get_cname_base(sorted_features: List[str]): """ Get the base cname for the feature set given. @@ -228,7 +226,7 @@ def get_minimal_feature_set(graph: Any) -> Set[str]: return set([node for (node, degree) in graph.in_degree() if degree == 0]) -def graph_as_mermaid_markup(flavor: str, graph: Any) -> str: +def graph_as_mermaid_markup(flavor: str | None, graph: Any) -> str: """ Generates a mermaid.js representation of the graph. This is helpful to identify dependencies between features. @@ -243,6 +241,9 @@ def graph_as_mermaid_markup(flavor: str, graph: Any) -> str: :since: 0.7.0 """ + if flavor is None: + return "No flavor provided. Skipping." + markup = f"---\ntitle: Dependency Graph for Feature {flavor}\n---\ngraph TD;\n" for u, v in graph.edges: diff --git a/src/gardenlinux/features/parser.py b/src/gardenlinux/features/parser.py index 2fb6cb9e..7242d165 100644 --- a/src/gardenlinux/features/parser.py +++ b/src/gardenlinux/features/parser.py @@ -6,8 +6,6 @@ import logging import os -import re -import subprocess from glob import glob from typing import Callable, Optional @@ -240,7 +238,7 @@ def filter_as_string( return ",".join(features) - def _exclude_from_filter_set(graph, input_features, filter_set): + def _exclude_from_filter_set(self, graph, input_features, filter_set): """ Removes the given `filter_set` out of `input_features`. From 14fd09178dfab4ace9016668eb636bb63f0a1993 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 7 Oct 2025 16:40:49 +0200 Subject: [PATCH 02/14] Fix redundant Optional type hints in Parser init --- src/gardenlinux/features/parser.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/gardenlinux/features/parser.py b/src/gardenlinux/features/parser.py index 7242d165..61fa61b6 100644 --- a/src/gardenlinux/features/parser.py +++ b/src/gardenlinux/features/parser.py @@ -7,6 +7,7 @@ import logging import os from glob import glob +from pathlib import Path from typing import Callable, Optional import networkx @@ -40,8 +41,8 @@ class Parser(object): def __init__( self, - gardenlinux_root: Optional[str] = None, - feature_dir_name: Optional[str] = "features", + gardenlinux_root: str = _GARDENLINUX_ROOT, + feature_dir_name: str = "features", logger: Optional[logging.Logger] = None, ): """ @@ -54,10 +55,10 @@ def __init__( :since: 0.7.0 """ - if gardenlinux_root is None: - gardenlinux_root = Parser._GARDENLINUX_ROOT + feature_base_dir = Path(gardenlinux_root) / feature_dir_name - feature_base_dir = os.path.join(gardenlinux_root, feature_dir_name) + if not feature_base_dir.is_dir(): + raise ValueError(f"Feature direcotry is invalid: {feature_base_dir}") if not os.access(feature_base_dir, os.R_OK): raise ValueError( @@ -160,7 +161,7 @@ def filter( ) if not ignore_excludes: - Parser._exclude_from_filter_set(graph, input_features, filter_set) + Parser._exclude_from_filter_set(self, graph, input_features, filter_set) return graph From 52d4341712c4a2fa99a6181b08ed59cc76fbb602 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 7 Oct 2025 16:44:19 +0200 Subject: [PATCH 03/14] Fix undefined feature_dir call in ValueError raise in graph function --- src/gardenlinux/features/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gardenlinux/features/parser.py b/src/gardenlinux/features/parser.py index 61fa61b6..45c3e758 100644 --- a/src/gardenlinux/features/parser.py +++ b/src/gardenlinux/features/parser.py @@ -107,7 +107,7 @@ def graph(self) -> networkx.Graph: "{0}/{1}/info.yaml".format(self._feature_base_dir, ref) ): raise ValueError( - f"feature {node} references feature {ref}, but {feature_dir}/{ref}/info.yaml does not exist" + f"feature {node} references feature {ref}, but {self._feature_base_dir}/{ref}/info.yaml does not exist" ) feature_graph.add_edge(node, ref, attr=attr) From f6451ab97a54ed435bcd9697969e02e152af8199 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 7 Oct 2025 16:50:37 +0200 Subject: [PATCH 04/14] Fix Callable type annotation in filter functions --- src/gardenlinux/features/parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gardenlinux/features/parser.py b/src/gardenlinux/features/parser.py index 45c3e758..f5008309 100644 --- a/src/gardenlinux/features/parser.py +++ b/src/gardenlinux/features/parser.py @@ -123,7 +123,7 @@ def filter( self, cname: str, ignore_excludes: bool = False, - additional_filter_func: Optional[Callable[(str,), bool]] = None, + additional_filter_func: Optional[Callable[[str], bool]] = None, ) -> networkx.Graph: """ Filters the features graph. @@ -169,7 +169,7 @@ def filter_as_dict( self, cname: str, ignore_excludes: bool = False, - additional_filter_func: Optional[Callable[(str,), bool]] = None, + additional_filter_func: Optional[Callable[[str], bool]] = None, ) -> dict: """ Filters the features graph and returns it as a dict. @@ -201,7 +201,7 @@ def filter_as_list( self, cname: str, ignore_excludes: bool = False, - additional_filter_func: Optional[Callable[(str,), bool]] = None, + additional_filter_func: Optional[Callable[[str], bool]] = None, ) -> list: """ Filters the features graph and returns it as a list. @@ -221,7 +221,7 @@ def filter_as_string( self, cname: str, ignore_excludes: bool = False, - additional_filter_func: Optional[Callable[(str,), bool]] = None, + additional_filter_func: Optional[Callable[[str], bool]] = None, ) -> str: """ Filters the features graph and returns it as a string. From 27db0ab59c38c7dbfe80a6d93229fe4522e97078 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 7 Oct 2025 16:52:25 +0200 Subject: [PATCH 05/14] Fix unused import in Parser.py --- src/gardenlinux/features/parser.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/gardenlinux/features/parser.py b/src/gardenlinux/features/parser.py index f5008309..d260594e 100644 --- a/src/gardenlinux/features/parser.py +++ b/src/gardenlinux/features/parser.py @@ -13,11 +13,7 @@ import networkx import yaml -from ..constants import ( - ARCHS, - BARE_FLAVOR_FEATURE_CONTENT, - BARE_FLAVOR_LIBC_FEATURE_CONTENT, -) +from ..constants import BARE_FLAVOR_FEATURE_CONTENT, BARE_FLAVOR_LIBC_FEATURE_CONTENT from ..logger import LoggerSetup From 0398b4bc921ad4f25047aadee07e98913eb4a039 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 7 Oct 2025 16:54:37 +0200 Subject: [PATCH 06/14] Explicitly cast exclude_graph_view to DiGraph to fix unknown member --- src/gardenlinux/features/parser.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gardenlinux/features/parser.py b/src/gardenlinux/features/parser.py index d260594e..f6566787 100644 --- a/src/gardenlinux/features/parser.py +++ b/src/gardenlinux/features/parser.py @@ -8,7 +8,7 @@ import os from glob import glob from pathlib import Path -from typing import Callable, Optional +from typing import Callable, Optional, cast import networkx import yaml @@ -245,7 +245,9 @@ def _exclude_from_filter_set(self, graph, input_features, filter_set): :since: 0.7.0 """ - exclude_graph_view = Parser._get_graph_view_for_attr(graph, "exclude") + exclude_graph_view = cast( + networkx.DiGraph, Parser._get_graph_view_for_attr(graph, "exclude") + ) exclude_list = [] for node in networkx.lexicographical_topological_sort(graph): From ebdfe762ce9f61bd6368981ec5ca449eae357299 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 7 Oct 2025 16:55:33 +0200 Subject: [PATCH 07/14] Raise runtime error for unknow flavor * fixes passing of None value to non-noneable parameters in filter function --- src/gardenlinux/features/__main__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gardenlinux/features/__main__.py b/src/gardenlinux/features/__main__.py index 831d9860..0704e5ce 100644 --- a/src/gardenlinux/features/__main__.py +++ b/src/gardenlinux/features/__main__.py @@ -118,6 +118,9 @@ def main() -> None: additional_filter_func = lambda node: node not in args.ignore + if flavor is None or "": + raise RuntimeError("Flavor could not be determined") + if args.type == "arch": print(arch) elif args.type in ("cname_base", "cname", "graph"): From 14faa4d7d4355e29adc4b42e18a15a4a99212f5c Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 7 Oct 2025 16:57:01 +0200 Subject: [PATCH 08/14] cname_main: Fix unused import --- src/gardenlinux/features/cname_main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gardenlinux/features/cname_main.py b/src/gardenlinux/features/cname_main.py index 8013286a..ddc224b2 100644 --- a/src/gardenlinux/features/cname_main.py +++ b/src/gardenlinux/features/cname_main.py @@ -8,7 +8,6 @@ import argparse import logging import re -from functools import reduce from os.path import basename, dirname from .__main__ import ( From 026fd475ec8a5ec2a7dec7f0c3cc4e5b3aeeb11b Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 7 Oct 2025 17:04:21 +0200 Subject: [PATCH 09/14] cname_main: Remove None check that is always true --- src/gardenlinux/features/cname_main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gardenlinux/features/cname_main.py b/src/gardenlinux/features/cname_main.py index ddc224b2..c94c2bb0 100644 --- a/src/gardenlinux/features/cname_main.py +++ b/src/gardenlinux/features/cname_main.py @@ -80,8 +80,7 @@ def main(): generated_cname = get_cname_base(sorted_minimal_features) - if cname.arch is not None: - generated_cname += f"-{cname.arch}" + generated_cname += f"-{cname.arch}" if cname.version_and_commit_id is not None: generated_cname += f"-{cname.version_and_commit_id}" From 832e5a6a8ce978a0897b17d4d2153af66644cc31 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Wed, 8 Oct 2025 09:52:32 +0200 Subject: [PATCH 10/14] Allow empty flavor --- src/gardenlinux/features/__main__.py | 5 +---- src/gardenlinux/features/parser.py | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/gardenlinux/features/__main__.py b/src/gardenlinux/features/__main__.py index 0704e5ce..b0989617 100644 --- a/src/gardenlinux/features/__main__.py +++ b/src/gardenlinux/features/__main__.py @@ -118,14 +118,11 @@ def main() -> None: additional_filter_func = lambda node: node not in args.ignore - if flavor is None or "": - raise RuntimeError("Flavor could not be determined") - if args.type == "arch": print(arch) elif args.type in ("cname_base", "cname", "graph"): graph = Parser(gardenlinux_root, feature_dir_name).filter( - flavor, additional_filter_func=additional_filter_func # type: ignore + flavor, additional_filter_func=additional_filter_func ) sorted_features = Parser.sort_graph_nodes(graph) diff --git a/src/gardenlinux/features/parser.py b/src/gardenlinux/features/parser.py index f6566787..de5e0d1f 100644 --- a/src/gardenlinux/features/parser.py +++ b/src/gardenlinux/features/parser.py @@ -117,7 +117,7 @@ def graph(self) -> networkx.Graph: def filter( self, - cname: str, + cname: str | None, ignore_excludes: bool = False, additional_filter_func: Optional[Callable[[str], bool]] = None, ) -> networkx.Graph: @@ -163,7 +163,7 @@ def filter( def filter_as_dict( self, - cname: str, + cname: str | None, ignore_excludes: bool = False, additional_filter_func: Optional[Callable[[str], bool]] = None, ) -> dict: @@ -195,7 +195,7 @@ def filter_as_dict( def filter_as_list( self, - cname: str, + cname: str | None, ignore_excludes: bool = False, additional_filter_func: Optional[Callable[[str], bool]] = None, ) -> list: @@ -215,7 +215,7 @@ def filter_as_list( def filter_as_string( self, - cname: str, + cname: str | None, ignore_excludes: bool = False, additional_filter_func: Optional[Callable[[str], bool]] = None, ) -> str: From 355fbabb7fddb596c13074aab4e27743c87d008a Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Mon, 13 Oct 2025 15:24:15 +0200 Subject: [PATCH 11/14] CName: Fix issues in CName class --- src/gardenlinux/features/cname.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gardenlinux/features/cname.py b/src/gardenlinux/features/cname.py index f245b1d2..d572d895 100644 --- a/src/gardenlinux/features/cname.py +++ b/src/gardenlinux/features/cname.py @@ -92,13 +92,13 @@ def cname(self) -> str: :return: (str) CName """ - + assert self._flavor is not None, "CName flavor is not set!" cname = self._flavor if self._arch is not None: cname += f"-{self._arch}" - if self._commit_id is not None: + if self._commit_id is not None and self._version is not None: cname += f"-{self.version_and_commit_id}" return cname @@ -114,7 +114,7 @@ def commit_id(self) -> Optional[str]: return self._commit_id @property - def flavor(self) -> str: + def flavor(self) -> str | None: """ Returns the flavor for the cname parsed. @@ -140,6 +140,7 @@ def platform(self) -> str: :return: (str) Flavor """ + assert self._flavor is not None, "Flavor not set!" return re.split("[_-]", self._flavor, maxsplit=1)[0] From fe37ce7572dbb2adeb6af71d7a3c6e41d265630f Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Mon, 13 Oct 2025 15:47:09 +0200 Subject: [PATCH 12/14] Parser: Fix broken assumption about root dir --- src/gardenlinux/features/parser.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/gardenlinux/features/parser.py b/src/gardenlinux/features/parser.py index de5e0d1f..db9859f3 100644 --- a/src/gardenlinux/features/parser.py +++ b/src/gardenlinux/features/parser.py @@ -37,7 +37,7 @@ class Parser(object): def __init__( self, - gardenlinux_root: str = _GARDENLINUX_ROOT, + gardenlinux_root: str | None = None, feature_dir_name: str = "features", logger: Optional[logging.Logger] = None, ): @@ -51,10 +51,10 @@ def __init__( :since: 0.7.0 """ - feature_base_dir = Path(gardenlinux_root) / feature_dir_name + if gardenlinux_root is None: + gardenlinux_root = Parser._GARDENLINUX_ROOT - if not feature_base_dir.is_dir(): - raise ValueError(f"Feature direcotry is invalid: {feature_base_dir}") + feature_base_dir = Path(gardenlinux_root).resolve() / feature_dir_name if not os.access(feature_base_dir, os.R_OK): raise ValueError( @@ -65,7 +65,6 @@ def __init__( logger = LoggerSetup.get_logger("gardenlinux.features") self._feature_base_dir = feature_base_dir - self._graph = None self._logger = logger From e72bc4dd58de6ff99f963f322c2dfc3ed1e569d6 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 14 Oct 2025 14:37:32 +0200 Subject: [PATCH 13/14] Raise RuntimeError in graph function if flavor is None --- src/gardenlinux/features/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gardenlinux/features/__main__.py b/src/gardenlinux/features/__main__.py index b0989617..69e190fc 100644 --- a/src/gardenlinux/features/__main__.py +++ b/src/gardenlinux/features/__main__.py @@ -242,7 +242,7 @@ def graph_as_mermaid_markup(flavor: str | None, graph: Any) -> str: """ if flavor is None: - return "No flavor provided. Skipping." + raise RuntimeError("Error while generating graph: Flavor is None!") markup = f"---\ntitle: Dependency Graph for Feature {flavor}\n---\ngraph TD;\n" From bf24225ed7d3e79ab837fc993c3e3bb1fc4256e4 Mon Sep 17 00:00:00 2001 From: Tiara Lena Hock Date: Tue, 14 Oct 2025 14:37:56 +0200 Subject: [PATCH 14/14] Tests: Cover bad path for mermaid function --- tests/features/test_main.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/features/test_main.py b/tests/features/test_main.py index 0184b582..3cddf17f 100644 --- a/tests/features/test_main.py +++ b/tests/features/test_main.py @@ -77,6 +77,18 @@ class FakeGraph: assert "b-->c" in markup +def test_graph_mermaid_raises_no_flavor(): + # Arrange + class MockGraph: + edges = [("x", "y"), ("y", "z")] + + # Act / Assert + with pytest.raises( + RuntimeError, match="Error while generating graph: Flavor is None!" + ): + fema.graph_as_mermaid_markup(None, MockGraph()) + + def test_get_minimal_feature_set_filters(): # Arrange class FakeGraph: