From be79db9cf402cc2af80d6082a42342a7e6c686cf Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 5 Dec 2025 09:06:50 -0600 Subject: [PATCH 1/3] adding functional tests for build_from_model functionality --- .../plugins/inband/os/analyzer_args.py | 2 +- .../plugins/inband/rocm/analyzer_args.py | 4 +- .../test_reference_config_workflow.py | 344 ++++++++++++++++++ .../test_analyzer_args_build_from_model.py | 206 +++++++++++ 4 files changed, 554 insertions(+), 2 deletions(-) create mode 100644 test/functional/test_reference_config_workflow.py create mode 100644 test/unit/plugin/test_analyzer_args_build_from_model.py diff --git a/nodescraper/plugins/inband/os/analyzer_args.py b/nodescraper/plugins/inband/os/analyzer_args.py index 366bb8d3..52fd1124 100644 --- a/nodescraper/plugins/inband/os/analyzer_args.py +++ b/nodescraper/plugins/inband/os/analyzer_args.py @@ -61,4 +61,4 @@ def build_from_model(cls, datamodel: OsDataModel) -> "OsAnalyzerArgs": Returns: OsAnalyzerArgs: instance of analyzer args class """ - return cls(exp_os=datamodel.os_name) + return cls(exp_os=datamodel.os_name, exact_match=True) diff --git a/nodescraper/plugins/inband/rocm/analyzer_args.py b/nodescraper/plugins/inband/rocm/analyzer_args.py index d76a2421..ff0751eb 100644 --- a/nodescraper/plugins/inband/rocm/analyzer_args.py +++ b/nodescraper/plugins/inband/rocm/analyzer_args.py @@ -61,4 +61,6 @@ def build_from_model(cls, datamodel: RocmDataModel) -> "RocmAnalyzerArgs": Returns: RocmAnalyzerArgs: instance of analyzer args class """ - return cls(exp_rocm=datamodel.rocm_version) + return cls( + exp_rocm=datamodel.rocm_version, exp_rocm_latest=datamodel.rocm_latest_versioned_path + ) diff --git a/test/functional/test_reference_config_workflow.py b/test/functional/test_reference_config_workflow.py new file mode 100644 index 00000000..6da2be5e --- /dev/null +++ b/test/functional/test_reference_config_workflow.py @@ -0,0 +1,344 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### +""" +Functional tests for reference config generation and usage workflow. + +Tests the complete workflow: +1. Generate reference config from system using --gen-reference-config +2. Use the generated config with --plugin-configs +""" +import json +from pathlib import Path + +import pytest + +from nodescraper.pluginregistry import PluginRegistry + + +def find_reference_config(log_path): + """Find reference_config.json in timestamped log directory. + + Args: + log_path: Base log path where logs are stored + + Returns: + Path to reference_config.json or None if not found + """ + log_path = Path(log_path) + if not log_path.exists(): + return None + + log_dirs = list(log_path.glob("scraper_logs_*")) + if not log_dirs: + return None + + most_recent = max(log_dirs, key=lambda p: p.stat().st_mtime) + + reference_config = most_recent / "reference_config.json" + if reference_config.exists(): + return reference_config + + return None + + +@pytest.fixture(scope="module") +def all_plugin_names(): + """Get list of all available plugin names.""" + registry = PluginRegistry() + return sorted(registry.plugins.keys()) + + +def test_gen_reference_config_all_plugins(run_cli_command, tmp_path, all_plugin_names): + """Test generating reference config with all plugins via run-plugins subcommand. + + Note: When running all plugins, some may fail but as long as at least one succeeds, + the reference config should be generated. + """ + log_path = str(tmp_path / "logs_gen_ref_all") + + result = run_cli_command( + [ + "--log-path", + log_path, + "--gen-reference-config", + "run-plugins", + ] + + all_plugin_names, + check=False, + ) + + assert result.returncode in [0, 1, 2, 120], ( + f"Unexpected return code: {result.returncode}\n" + f"stdout: {result.stdout[:500]}\nstderr: {result.stderr[:500]}" + ) + + reference_config_path = find_reference_config(log_path) + + if reference_config_path is None: + pytest.skip( + "reference_config.json was not created - likely all plugins failed or timed out. " + "This can happen in test environments." + ) + + assert reference_config_path.exists() + + with open(reference_config_path) as f: + config = json.load(f) + assert "plugins" in config + assert isinstance(config["plugins"], dict) + assert len(config["plugins"]) > 0 + + +def test_gen_reference_config_subset_plugins(run_cli_command, tmp_path): + """Test generating reference config with a subset of plugins.""" + log_path = str(tmp_path / "logs_gen_ref_subset") + plugins = ["BiosPlugin", "OsPlugin", "KernelPlugin"] + + result = run_cli_command( + ["--log-path", log_path, "--gen-reference-config", "run-plugins"] + plugins, + check=False, + ) + + assert result.returncode in [0, 1, 2] + + reference_config_path = find_reference_config(log_path) + assert reference_config_path is not None, "reference_config.json was not created" + assert reference_config_path.exists() + + with open(reference_config_path) as f: + config = json.load(f) + assert "plugins" in config + + +def test_use_generated_reference_config(run_cli_command, tmp_path): + """Test using a generated reference config with --plugin-configs.""" + gen_log_path = str(tmp_path / "logs_gen") + use_log_path = str(tmp_path / "logs_use") + + plugins = ["BiosPlugin", "OsPlugin", "UptimePlugin"] + + gen_result = run_cli_command( + ["--log-path", gen_log_path, "--gen-reference-config", "run-plugins"] + plugins, + check=False, + ) + + assert gen_result.returncode in [0, 1, 2] + + reference_config_path = find_reference_config(gen_log_path) + assert reference_config_path is not None, "reference_config.json was not created" + assert reference_config_path.exists() + + use_result = run_cli_command( + ["--log-path", use_log_path, "--plugin-configs", str(reference_config_path)], + check=False, + ) + + assert use_result.returncode in [0, 1, 2] + output = use_result.stdout + use_result.stderr + assert len(output) > 0 + + +def test_full_workflow_all_plugins(run_cli_command, tmp_path, all_plugin_names): + """ + Test complete workflow: generate reference config from all plugins, + then use it with --plugin-configs. + + Note: May skip if plugins fail to generate config in test environment. + """ + gen_log_path = str(tmp_path / "logs_gen_workflow") + use_log_path = str(tmp_path / "logs_use_workflow") + + gen_result = run_cli_command( + [ + "--log-path", + gen_log_path, + "--gen-reference-config", + "run-plugins", + ] + + all_plugin_names, + check=False, + ) + + assert gen_result.returncode in [0, 1, 2, 120], ( + f"Generation failed with return code {gen_result.returncode}\n" + f"stdout: {gen_result.stdout[:500]}\n" + f"stderr: {gen_result.stderr[:500]}" + ) + + reference_config_path = find_reference_config(gen_log_path) + + if reference_config_path is None: + pytest.skip( + "reference_config.json was not generated - plugins may have failed in test environment" + ) + + assert reference_config_path.exists() + + with open(reference_config_path) as f: + config = json.load(f) + assert "plugins" in config, "Config missing 'plugins' key" + + for _plugin_name, plugin_config in config["plugins"].items(): + if "analysis_args" in plugin_config: + assert isinstance(plugin_config["analysis_args"], dict) + + use_result = run_cli_command( + ["--log-path", use_log_path, "--plugin-configs", str(reference_config_path)], + check=False, + ) + + assert use_result.returncode in [0, 1, 2], ( + f"Using config failed with return code {use_result.returncode}\n" + f"stdout: {use_result.stdout}\n" + f"stderr: {use_result.stderr}" + ) + + output = use_result.stdout + use_result.stderr + assert len(output) > 0, "No output generated when using reference config" + + use_log_dirs = list(Path(tmp_path).glob("logs_use_workflow*")) + assert len(use_log_dirs) > 0, "No log directory created when using config" + + +def test_reference_config_with_analysis_args(run_cli_command, tmp_path): + """Test that generated reference config includes analysis_args where available.""" + log_path = str(tmp_path / "logs_analysis_args") + + plugins_with_build_from_model = [ + "BiosPlugin", + "CmdlinePlugin", + "DeviceEnumerationPlugin", + "DkmsPlugin", + "KernelPlugin", + "KernelModulePlugin", + "OsPlugin", + "PackagePlugin", + "ProcessPlugin", + "RocmPlugin", + "SysctlPlugin", + ] + + result = run_cli_command( + ["--log-path", log_path, "--gen-reference-config", "run-plugins"] + + plugins_with_build_from_model, + check=False, + ) + + assert result.returncode in [0, 1, 2, 120] + + reference_config_path = find_reference_config(log_path) + + if reference_config_path is None: + pytest.skip( + "reference_config.json was not created - plugins may have failed in test environment" + ) + + assert reference_config_path.exists() + + with open(reference_config_path) as f: + config = json.load(f) + plugins_with_args = [ + name for name, conf in config["plugins"].items() if "analysis_args" in conf + ] + assert len(plugins_with_args) > 0, "No plugins have analysis_args in generated config" + + +def test_reference_config_structure(run_cli_command, tmp_path): + """Test that generated reference config has correct structure.""" + log_path = str(tmp_path / "logs_structure") + + result = run_cli_command( + ["--log-path", log_path, "--gen-reference-config", "run-plugins", "OsPlugin"], + check=False, + ) + + assert result.returncode in [0, 1, 2] + + reference_config_path = find_reference_config(log_path) + assert reference_config_path is not None, "reference_config.json was not created" + assert reference_config_path.exists() + + with open(reference_config_path) as f: + config = json.load(f) + + assert "plugins" in config + assert isinstance(config["plugins"], dict) + + if "OsPlugin" in config["plugins"]: + os_config = config["plugins"]["OsPlugin"] + if "analysis_args" in os_config: + assert "exp_os" in os_config["analysis_args"] + + +def test_gen_reference_config_without_run_plugins(run_cli_command, tmp_path): + """Test generating reference config without specifying plugins (uses default).""" + log_path = str(tmp_path / "logs_default") + + result = run_cli_command( + ["--log-path", log_path, "--gen-reference-config"], + check=False, + ) + + assert result.returncode in [0, 1, 2] + + reference_config_path = find_reference_config(log_path) + assert reference_config_path is not None, "reference_config.json was not created" + assert reference_config_path.exists() + + with open(reference_config_path) as f: + config = json.load(f) + assert "plugins" in config + + +def test_reference_config_json_valid(run_cli_command, tmp_path): + """Test that generated reference config is valid JSON.""" + log_path = str(tmp_path / "logs_valid_json") + + result = run_cli_command( + [ + "--log-path", + log_path, + "--gen-reference-config", + "run-plugins", + "BiosPlugin", + "OsPlugin", + ], + check=False, + ) + + assert result.returncode in [0, 1, 2] + + reference_config_path = find_reference_config(log_path) + assert reference_config_path is not None, "reference_config.json was not created" + assert reference_config_path.exists() + + with open(reference_config_path) as f: + config = json.load(f) + json_str = json.dumps(config, indent=2) + assert len(json_str) > 0 + + reparsed = json.loads(json_str) + assert reparsed == config diff --git a/test/unit/plugin/test_analyzer_args_build_from_model.py b/test/unit/plugin/test_analyzer_args_build_from_model.py new file mode 100644 index 00000000..201f2be2 --- /dev/null +++ b/test/unit/plugin/test_analyzer_args_build_from_model.py @@ -0,0 +1,206 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### +""" +Test suite for all analyzer_args build_from_model methods. +Ensures that build_from_model includes all required fields. +""" + +from nodescraper.plugins.inband.bios.analyzer_args import BiosAnalyzerArgs +from nodescraper.plugins.inband.bios.biosdata import BiosDataModel +from nodescraper.plugins.inband.cmdline.analyzer_args import CmdlineAnalyzerArgs +from nodescraper.plugins.inband.cmdline.cmdlinedata import CmdlineDataModel +from nodescraper.plugins.inband.device_enumeration.analyzer_args import ( + DeviceEnumerationAnalyzerArgs, +) +from nodescraper.plugins.inband.device_enumeration.deviceenumdata import ( + DeviceEnumerationDataModel, +) +from nodescraper.plugins.inband.dkms.analyzer_args import DkmsAnalyzerArgs +from nodescraper.plugins.inband.dkms.dkmsdata import DkmsDataModel +from nodescraper.plugins.inband.kernel.analyzer_args import KernelAnalyzerArgs +from nodescraper.plugins.inband.kernel.kerneldata import KernelDataModel +from nodescraper.plugins.inband.kernel_module.analyzer_args import ( + KernelModuleAnalyzerArgs, +) +from nodescraper.plugins.inband.kernel_module.kernel_module_data import ( + KernelModuleDataModel, +) +from nodescraper.plugins.inband.os.analyzer_args import OsAnalyzerArgs +from nodescraper.plugins.inband.os.osdata import OsDataModel +from nodescraper.plugins.inband.package.analyzer_args import PackageAnalyzerArgs +from nodescraper.plugins.inband.package.packagedata import PackageDataModel +from nodescraper.plugins.inband.process.analyzer_args import ProcessAnalyzerArgs +from nodescraper.plugins.inband.process.processdata import ProcessDataModel +from nodescraper.plugins.inband.rocm.analyzer_args import RocmAnalyzerArgs +from nodescraper.plugins.inband.rocm.rocmdata import RocmDataModel +from nodescraper.plugins.inband.sysctl.analyzer_args import SysctlAnalyzerArgs +from nodescraper.plugins.inband.sysctl.sysctldata import SysctlDataModel + + +def test_package_analyzer_args_build_from_model(): + """Test PackageAnalyzerArgs.build_from_model includes all fields""" + datamodel = PackageDataModel(version_info={"package1": "1.0.0", "package2": "2.0.0"}) + args = PackageAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, PackageAnalyzerArgs) + assert args.exp_package_ver == {"package1": "1.0.0", "package2": "2.0.0"} + + +def test_device_enumeration_analyzer_args_build_from_model(): + """Test DeviceEnumerationAnalyzerArgs.build_from_model includes all fields""" + datamodel = DeviceEnumerationDataModel(cpu_count=2, gpu_count=8, vf_count=0) + args = DeviceEnumerationAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, DeviceEnumerationAnalyzerArgs) + assert args.cpu_count == [2] + assert args.gpu_count == [8] + assert args.vf_count == [0] + + +def test_device_enumeration_analyzer_args_build_from_model_with_none(): + """Test DeviceEnumerationAnalyzerArgs.build_from_model with None values""" + datamodel = DeviceEnumerationDataModel(cpu_count=None, gpu_count=4, vf_count=None) + args = DeviceEnumerationAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, DeviceEnumerationAnalyzerArgs) + assert args.cpu_count is None + assert args.gpu_count == [4] + assert args.vf_count is None + + +def test_kernel_analyzer_args_build_from_model(): + """Test KernelAnalyzerArgs.build_from_model includes all fields""" + datamodel = KernelDataModel(kernel_info="5.15.0-56-generic", kernel_version="5.15.0-56-generic") + args = KernelAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, KernelAnalyzerArgs) + assert args.exp_kernel == ["5.15.0-56-generic"] + + +def test_rocm_analyzer_args_build_from_model(): + """Test RocmAnalyzerArgs.build_from_model includes all fields""" + datamodel = RocmDataModel(rocm_version="5.4.0", rocm_latest_versioned_path="/opt/rocm-5.4.0") + args = RocmAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, RocmAnalyzerArgs) + assert args.exp_rocm == ["5.4.0"] + assert args.exp_rocm_latest == "/opt/rocm-5.4.0" + + +def test_os_analyzer_args_build_from_model(): + """Test OsAnalyzerArgs.build_from_model includes all fields""" + datamodel = OsDataModel(os_name="Ubuntu 22.04", os_version="22.04") + args = OsAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, OsAnalyzerArgs) + assert args.exp_os == ["Ubuntu 22.04"] + + +def test_bios_analyzer_args_build_from_model(): + """Test BiosAnalyzerArgs.build_from_model includes all fields""" + datamodel = BiosDataModel(bios_version="1.2.3") + args = BiosAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, BiosAnalyzerArgs) + assert args.exp_bios_version == ["1.2.3"] + + +def test_cmdline_analyzer_args_build_from_model(): + """Test CmdlineAnalyzerArgs.build_from_model includes all fields""" + datamodel = CmdlineDataModel(cmdline="iommu=pt intel_iommu=on") + args = CmdlineAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, CmdlineAnalyzerArgs) + assert args.required_cmdline == ["iommu=pt intel_iommu=on"] + + +def test_dkms_analyzer_args_build_from_model(): + """Test DkmsAnalyzerArgs.build_from_model includes all fields""" + datamodel = DkmsDataModel(status="installed", version="6.8.5-6.8.5") + args = DkmsAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, DkmsAnalyzerArgs) + assert args.dkms_status == ["installed"] + assert args.dkms_version == ["6.8.5-6.8.5"] + + +def test_sysctl_analyzer_args_build_from_model(): + """Test SysctlAnalyzerArgs.build_from_model includes all fields""" + datamodel = SysctlDataModel( + vm_swappiness=60, + vm_numa_balancing=1, + vm_oom_kill_allocating_task=0, + vm_compaction_proactiveness=20, + vm_compact_unevictable_allowed=1, + vm_extfrag_threshold=500, + vm_zone_reclaim_mode=0, + vm_dirty_background_ratio=10, + vm_dirty_ratio=20, + vm_dirty_writeback_centisecs=500, + kernel_numa_balancing=1, + ) + args = SysctlAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, SysctlAnalyzerArgs) + assert args.exp_vm_swappiness == 60 + assert args.exp_vm_numa_balancing == 1 + assert args.exp_vm_oom_kill_allocating_task == 0 + assert args.exp_vm_compaction_proactiveness == 20 + assert args.exp_vm_compact_unevictable_allowed == 1 + assert args.exp_vm_extfrag_threshold == 500 + assert args.exp_vm_zone_reclaim_mode == 0 + assert args.exp_vm_dirty_background_ratio == 10 + assert args.exp_vm_dirty_ratio == 20 + assert args.exp_vm_dirty_writeback_centisecs == 500 + assert args.exp_kernel_numa_balancing == 1 + + +def test_process_analyzer_args_build_from_model(): + """Test ProcessAnalyzerArgs.build_from_model includes all fields""" + datamodel = ProcessDataModel(kfd_process=5, cpu_usage=15.5) + args = ProcessAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, ProcessAnalyzerArgs) + assert args.max_kfd_processes == 5 + assert args.max_cpu_usage == 15.5 + + +def test_kernel_module_analyzer_args_build_from_model(): + """Test KernelModuleAnalyzerArgs.build_from_model includes all fields""" + datamodel = KernelModuleDataModel( + kernel_modules={ + "amdgpu": {"size": 1024, "used": 0}, + "amd_iommu": {"size": 512, "used": 1}, + "other_module": {"size": 256, "used": 0}, + } + ) + args = KernelModuleAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, KernelModuleAnalyzerArgs) + assert "amdgpu" in args.kernel_modules + assert "amd_iommu" in args.kernel_modules + assert "other_module" not in args.kernel_modules + assert args.regex_filter == [] From 5c380e015f1b23dc032ed818a5eeb8209aaffd81 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 5 Dec 2025 09:22:23 -0600 Subject: [PATCH 2/3] fixed kernel plugin bug --- nodescraper/plugins/inband/kernel/analyzer_args.py | 2 +- test/unit/plugin/test_analyzer_args_build_from_model.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/nodescraper/plugins/inband/kernel/analyzer_args.py b/nodescraper/plugins/inband/kernel/analyzer_args.py index eab86c95..e8f4cd61 100644 --- a/nodescraper/plugins/inband/kernel/analyzer_args.py +++ b/nodescraper/plugins/inband/kernel/analyzer_args.py @@ -61,4 +61,4 @@ def build_from_model(cls, datamodel: KernelDataModel) -> "KernelAnalyzerArgs": Returns: KernelAnalyzerArgs: instance of analyzer args class """ - return cls(exp_kernel=datamodel.kernel_info) + return cls(exp_kernel=datamodel.kernel_version) diff --git a/test/unit/plugin/test_analyzer_args_build_from_model.py b/test/unit/plugin/test_analyzer_args_build_from_model.py index 201f2be2..dc949a12 100644 --- a/test/unit/plugin/test_analyzer_args_build_from_model.py +++ b/test/unit/plugin/test_analyzer_args_build_from_model.py @@ -93,7 +93,10 @@ def test_device_enumeration_analyzer_args_build_from_model_with_none(): def test_kernel_analyzer_args_build_from_model(): """Test KernelAnalyzerArgs.build_from_model includes all fields""" - datamodel = KernelDataModel(kernel_info="5.15.0-56-generic", kernel_version="5.15.0-56-generic") + datamodel = KernelDataModel( + kernel_info="Linux hostname 5.15.0-56-generic #62-Ubuntu SMP x86_64 GNU/Linux", + kernel_version="5.15.0-56-generic", + ) args = KernelAnalyzerArgs.build_from_model(datamodel) assert isinstance(args, KernelAnalyzerArgs) From 80f65b97dc971d47a61312f6d7c03cccd359a198 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 5 Dec 2025 09:38:40 -0600 Subject: [PATCH 3/3] added build_from_model for MemoryPlugin, to overwrite upper bound with --gen-plugin-config --- nodescraper/plugins/inband/memory/analyzer_args.py | 14 ++++++++++++++ nodescraper/plugins/inband/memory/memory_plugin.py | 2 ++ test/functional/test_reference_config_workflow.py | 1 + .../plugin/test_analyzer_args_build_from_model.py | 11 +++++++++++ 4 files changed, 28 insertions(+) diff --git a/nodescraper/plugins/inband/memory/analyzer_args.py b/nodescraper/plugins/inband/memory/analyzer_args.py index 83563476..968641ca 100644 --- a/nodescraper/plugins/inband/memory/analyzer_args.py +++ b/nodescraper/plugins/inband/memory/analyzer_args.py @@ -25,7 +25,21 @@ ############################################################################### from nodescraper.models.analyzerargs import AnalyzerArgs +from .memorydata import MemoryDataModel + class MemoryAnalyzerArgs(AnalyzerArgs): ratio: float = 0.66 memory_threshold: str = "30Gi" + + @classmethod + def build_from_model(cls, datamodel: MemoryDataModel) -> "MemoryAnalyzerArgs": + """build analyzer args from data model + + Args: + datamodel (MemoryDataModel): data model for plugin + + Returns: + MemoryAnalyzerArgs: instance of analyzer args class + """ + return cls(memory_threshold=datamodel.mem_total) diff --git a/nodescraper/plugins/inband/memory/memory_plugin.py b/nodescraper/plugins/inband/memory/memory_plugin.py index 84a04de3..9162bd0a 100644 --- a/nodescraper/plugins/inband/memory/memory_plugin.py +++ b/nodescraper/plugins/inband/memory/memory_plugin.py @@ -39,3 +39,5 @@ class MemoryPlugin(InBandDataPlugin[MemoryDataModel, None, MemoryAnalyzerArgs]): COLLECTOR = MemoryCollector ANALYZER = MemoryAnalyzer + + ANALYZER_ARGS = MemoryAnalyzerArgs diff --git a/test/functional/test_reference_config_workflow.py b/test/functional/test_reference_config_workflow.py index 6da2be5e..44362149 100644 --- a/test/functional/test_reference_config_workflow.py +++ b/test/functional/test_reference_config_workflow.py @@ -234,6 +234,7 @@ def test_reference_config_with_analysis_args(run_cli_command, tmp_path): "DkmsPlugin", "KernelPlugin", "KernelModulePlugin", + "MemoryPlugin", "OsPlugin", "PackagePlugin", "ProcessPlugin", diff --git a/test/unit/plugin/test_analyzer_args_build_from_model.py b/test/unit/plugin/test_analyzer_args_build_from_model.py index dc949a12..e6eb7485 100644 --- a/test/unit/plugin/test_analyzer_args_build_from_model.py +++ b/test/unit/plugin/test_analyzer_args_build_from_model.py @@ -48,6 +48,8 @@ from nodescraper.plugins.inband.kernel_module.kernel_module_data import ( KernelModuleDataModel, ) +from nodescraper.plugins.inband.memory.analyzer_args import MemoryAnalyzerArgs +from nodescraper.plugins.inband.memory.memorydata import MemoryDataModel from nodescraper.plugins.inband.os.analyzer_args import OsAnalyzerArgs from nodescraper.plugins.inband.os.osdata import OsDataModel from nodescraper.plugins.inband.package.analyzer_args import PackageAnalyzerArgs @@ -207,3 +209,12 @@ def test_kernel_module_analyzer_args_build_from_model(): assert "amd_iommu" in args.kernel_modules assert "other_module" not in args.kernel_modules assert args.regex_filter == [] + + +def test_memory_analyzer_args_build_from_model(): + """Test MemoryAnalyzerArgs.build_from_model includes all fields""" + datamodel = MemoryDataModel(mem_free="128Gi", mem_total="256Gi") + args = MemoryAnalyzerArgs.build_from_model(datamodel) + + assert isinstance(args, MemoryAnalyzerArgs) + assert args.memory_threshold == "256Gi"