Skip to content

Commit e4707e9

Browse files
committed
added utest
1 parent 57d7486 commit e4707e9

File tree

6 files changed

+213
-103
lines changed

6 files changed

+213
-103
lines changed

nodescraper/cli/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def build_parser(
156156
"--gen-reference-config-from-logs",
157157
dest="reference_config_from_logs",
158158
type=log_path_arg,
159-
help="Generate reference config from previous run. Writes to ./reference_config.json.",
159+
help="Generate reference config from previous run logfiles. Writes to ./reference_config.json.",
160160
)
161161

162162
subparsers = parser.add_subparsers(dest="subcmd", help="Subcommands")

nodescraper/cli/helper.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,16 +323,26 @@ def generate_reference_config(
323323
return plugin_config
324324

325325

326-
def generate_reference_config_from_logs(path, plugin_reg, logger):
326+
def generate_reference_config_from_logs(
327+
path: str, plugin_reg: PluginRegistry, logger: logging.Logger
328+
) -> PluginConfig:
329+
"""Parse previous log files and generate plugin config with populated analyzer args
330+
331+
Args:
332+
path (str): path to log files
333+
plugin_reg (PluginRegistry): plugin registry instance
334+
logger (logging.Logger): logger instance
335+
336+
Returns:
337+
PluginConfig: instance of plugin config
338+
"""
327339
found = find_datamodel_and_result(path)
328340
plugin_config = PluginConfig()
329341
plugins = {}
330342
for dm, res in found:
331343
result_path = Path(res)
332344
res_payload = json.loads(result_path.read_text(encoding="utf-8"))
333345
task_res = TaskResult(**res_payload)
334-
# print(json.dumps(res_payload, indent=2))
335-
# print(task_res.parent)
336346
dm_path = Path(dm)
337347
dm_payload = json.loads(dm_path.read_text(encoding="utf-8"))
338348
plugin = plugin_reg.plugins.get(task_res.parent)
@@ -355,14 +365,19 @@ def generate_reference_config_from_logs(path, plugin_reg, logger):
355365
plugins[task_res.parent] = {"analysis_args": {}}
356366
plugins[task_res.parent]["analysis_args"] = args.model_dump(exclude_none=True)
357367

358-
# print("Datamodel:", dm)
359-
# print("Result: ", res)
360368
plugin_config.plugins = plugins
361369
return plugin_config
362370

363371

364372
def find_datamodel_and_result(base_path: str) -> list[Tuple[str, str]]:
365-
""" """
373+
"""Get datamodel and result files
374+
375+
Args:
376+
base_path (str): location of previous run logs
377+
378+
Returns:
379+
list[Tuple[str, str]]: tuple of datamodel and result json files
380+
"""
366381
tuple_list: list[Tuple[str, str, str]] = []
367382
for root, _, files in os.walk(base_path):
368383
if "collector" in os.path.basename(root).lower():
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"bios_version": "M17"
3+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"status": "OK",
3+
"message": "BIOS: M17",
4+
"task": "BiosCollector",
5+
"parent": "BiosPlugin",
6+
"start_time": "2025-07-07T11:11:08.186472",
7+
"end_time": "2025-07-07T11:11:08.329110"
8+
}

test/unit/framework/test_cli.py

Lines changed: 2 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,14 @@
2424
#
2525
###############################################################################
2626
import argparse
27-
import logging
2827
import os
2928

3029
import pytest
31-
from common.shared_utils import DummyDataModel
3230
from pydantic import BaseModel
3331

3432
from nodescraper.cli import cli, inputargtypes
35-
from nodescraper.cli.helper import build_config
36-
from nodescraper.configregistry import ConfigRegistry
37-
from nodescraper.enums import ExecutionStatus, SystemInteractionLevel, SystemLocation
38-
from nodescraper.models import PluginConfig, SystemInfo, TaskResult
39-
from nodescraper.models.datapluginresult import DataPluginResult
40-
from nodescraper.models.pluginresult import PluginResult
33+
from nodescraper.enums import SystemLocation
34+
from nodescraper.models import SystemInfo
4135

4236

4337
def test_log_path_arg():
@@ -155,91 +149,3 @@ def test_system_info_builder():
155149
)
156150
def test_process_args(raw_arg_input, plugin_names, exp_output):
157151
assert cli.process_args(raw_arg_input, plugin_names) == exp_output
158-
159-
160-
def test_get_plugin_configs():
161-
with pytest.raises(argparse.ArgumentTypeError):
162-
cli.get_plugin_configs(
163-
system_interaction_level="INVALID",
164-
plugin_config_input=[],
165-
built_in_configs={},
166-
parsed_plugin_args={},
167-
plugin_subparser_map={},
168-
)
169-
170-
plugin_configs = cli.get_plugin_configs(
171-
system_interaction_level="PASSIVE",
172-
plugin_config_input=[],
173-
built_in_configs={},
174-
parsed_plugin_args={
175-
"TestPlugin1": argparse.Namespace(arg1="test123"),
176-
"TestPlugin2": argparse.Namespace(arg2="testabc", model_arg1="123", model_arg2="abc"),
177-
},
178-
plugin_subparser_map={
179-
"TestPlugin1": (argparse.ArgumentParser(), {}),
180-
"TestPlugin2": (
181-
argparse.ArgumentParser(),
182-
{"model_arg1": "my_model", "model_arg2": "my_model"},
183-
),
184-
},
185-
)
186-
187-
assert plugin_configs == [
188-
PluginConfig(
189-
global_args={"system_interaction_level": SystemInteractionLevel.PASSIVE},
190-
plugins={},
191-
result_collators={"TableSummary": {}},
192-
),
193-
PluginConfig(
194-
plugins={
195-
"TestPlugin1": {"arg1": "test123"},
196-
"TestPlugin2": {
197-
"arg2": "testabc",
198-
"my_model": {"model_arg1": "123", "model_arg2": "abc"},
199-
},
200-
},
201-
),
202-
]
203-
204-
205-
def test_config_builder(plugin_registry):
206-
207-
config = build_config(
208-
config_reg=ConfigRegistry(config_path=os.path.join(os.path.dirname(__file__), "fixtures")),
209-
plugin_reg=plugin_registry,
210-
logger=logging.getLogger(),
211-
plugins=["TestPluginA"],
212-
built_in_configs=["ExampleConfig"],
213-
)
214-
assert config.plugins == {
215-
"TestPluginA": {
216-
"test_bool_arg": True,
217-
"test_str_arg": "test",
218-
"test_model_arg": {"model_attr": 123},
219-
},
220-
"ExamplePlugin": {},
221-
}
222-
223-
224-
def test_generate_reference_config(plugin_registry):
225-
results = [
226-
PluginResult(
227-
status=ExecutionStatus.OK,
228-
source="TestPluginA",
229-
message="Plugin tasks completed successfully",
230-
result_data=DataPluginResult(
231-
system_data=DummyDataModel(some_version="M17"),
232-
collection_result=TaskResult(
233-
status=ExecutionStatus.OK,
234-
message="BIOS: M17",
235-
task="BiosCollector",
236-
parent="TestPluginA",
237-
artifacts=[],
238-
),
239-
),
240-
)
241-
]
242-
243-
ref_config = cli.generate_reference_config(results, plugin_registry, logging.getLogger())
244-
dump = ref_config.dict()
245-
assert dump["plugins"] == {"TestPluginA": {"analysis_args": {"model_attr": 123}}}

test/unit/framework/test_helper.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
#
25+
###############################################################################
26+
import argparse
27+
import json
28+
import logging
29+
import os
30+
from pathlib import Path
31+
from types import SimpleNamespace
32+
33+
import pytest
34+
from common.shared_utils import DummyDataModel
35+
from pydantic import BaseModel
36+
37+
from nodescraper.cli import cli
38+
from nodescraper.cli.helper import build_config, find_datamodel_and_result
39+
from nodescraper.configregistry import ConfigRegistry
40+
from nodescraper.enums import ExecutionStatus, SystemInteractionLevel
41+
from nodescraper.models import PluginConfig, TaskResult
42+
from nodescraper.models.datapluginresult import DataPluginResult
43+
from nodescraper.models.pluginresult import PluginResult
44+
45+
46+
def test_generate_reference_config(plugin_registry):
47+
results = [
48+
PluginResult(
49+
status=ExecutionStatus.OK,
50+
source="TestPluginA",
51+
message="Plugin tasks completed successfully",
52+
result_data=DataPluginResult(
53+
system_data=DummyDataModel(some_version="M17"),
54+
collection_result=TaskResult(
55+
status=ExecutionStatus.OK,
56+
message="BIOS: M17",
57+
task="BiosCollector",
58+
parent="TestPluginA",
59+
artifacts=[],
60+
),
61+
),
62+
)
63+
]
64+
65+
ref_config = cli.generate_reference_config(results, plugin_registry, logging.getLogger())
66+
dump = ref_config.dict()
67+
assert dump["plugins"] == {"TestPluginA": {"analysis_args": {"model_attr": 123}}}
68+
69+
70+
def test_get_plugin_configs():
71+
with pytest.raises(argparse.ArgumentTypeError):
72+
cli.get_plugin_configs(
73+
system_interaction_level="INVALID",
74+
plugin_config_input=[],
75+
built_in_configs={},
76+
parsed_plugin_args={},
77+
plugin_subparser_map={},
78+
)
79+
80+
plugin_configs = cli.get_plugin_configs(
81+
system_interaction_level="PASSIVE",
82+
plugin_config_input=[],
83+
built_in_configs={},
84+
parsed_plugin_args={
85+
"TestPlugin1": argparse.Namespace(arg1="test123"),
86+
"TestPlugin2": argparse.Namespace(arg2="testabc", model_arg1="123", model_arg2="abc"),
87+
},
88+
plugin_subparser_map={
89+
"TestPlugin1": (argparse.ArgumentParser(), {}),
90+
"TestPlugin2": (
91+
argparse.ArgumentParser(),
92+
{"model_arg1": "my_model", "model_arg2": "my_model"},
93+
),
94+
},
95+
)
96+
97+
assert plugin_configs == [
98+
PluginConfig(
99+
global_args={"system_interaction_level": SystemInteractionLevel.PASSIVE},
100+
plugins={},
101+
result_collators={"TableSummary": {}},
102+
),
103+
PluginConfig(
104+
plugins={
105+
"TestPlugin1": {"arg1": "test123"},
106+
"TestPlugin2": {
107+
"arg2": "testabc",
108+
"my_model": {"model_arg1": "123", "model_arg2": "abc"},
109+
},
110+
},
111+
),
112+
]
113+
114+
115+
def test_config_builder(plugin_registry):
116+
117+
config = build_config(
118+
config_reg=ConfigRegistry(config_path=os.path.join(os.path.dirname(__file__), "fixtures")),
119+
plugin_reg=plugin_registry,
120+
logger=logging.getLogger(),
121+
plugins=["TestPluginA"],
122+
built_in_configs=["ExampleConfig"],
123+
)
124+
assert config.plugins == {
125+
"TestPluginA": {
126+
"test_bool_arg": True,
127+
"test_str_arg": "test",
128+
"test_model_arg": {"model_attr": 123},
129+
},
130+
"ExamplePlugin": {},
131+
}
132+
133+
134+
def test_find_datamodel_and_result_with_fixture(framework_fixtures_path):
135+
base_dir = framework_fixtures_path / "log_dir"
136+
assert (base_dir / "collector/biosdatamodel.json").exists()
137+
assert (base_dir / "collector/result.json").exists()
138+
139+
pairs = find_datamodel_and_result(str(base_dir))
140+
assert len(pairs) == 1
141+
142+
datamodel_path, result_path = pairs[0]
143+
dm = Path(datamodel_path)
144+
rt = Path(result_path)
145+
146+
assert dm.parent == base_dir / "collector"
147+
assert rt.parent == base_dir / "collector"
148+
149+
assert dm.name == "biosdatamodel.json"
150+
assert rt.name == "result.json"
151+
152+
153+
def test_generate_reference_config_from_logs(framework_fixtures_path):
154+
logger = logging.getLogger()
155+
res_payload = json.loads(
156+
(framework_fixtures_path / "log_dir/collector/result.json").read_text(encoding="utf-8")
157+
)
158+
parent = res_payload["parent"]
159+
160+
class FakeDataModel:
161+
@classmethod
162+
def model_validate(cls, payload):
163+
return payload
164+
165+
class FakeArgs(BaseModel):
166+
@classmethod
167+
def build_from_model(cls, datamodel):
168+
return cls()
169+
170+
plugin_reg = SimpleNamespace(
171+
plugins={parent: SimpleNamespace(DATA_MODEL=FakeDataModel, ANALYZER_ARGS=FakeArgs)}
172+
)
173+
174+
cfg = cli.generate_reference_config_from_logs(str(framework_fixtures_path), plugin_reg, logger)
175+
176+
assert isinstance(cfg, PluginConfig)
177+
assert set(cfg.plugins) == {parent}
178+
assert cfg.plugins[parent]["analysis_args"] == {}

0 commit comments

Comments
 (0)