Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 34 additions & 22 deletions nodescraper/models/taskresult.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,40 +235,52 @@ def _event_occurrence_count(event: Event) -> int:
return 1
return max(1, n)

@staticmethod
def _format_limited_descriptions(msg_counts: dict[str, int], prefix: str) -> str:
"""Format the 3 most frequent descriptions, noting how many were left out."""
items = sorted(msg_counts.items(), key=lambda kv: kv[1], reverse=True)
details = [f"{msg} (x{count})" if count > 1 else msg for msg, count in items[:3]]
summary = f"{prefix}: {', '.join(details)}"
omitted = len(items) - 3
if omitted > 0:
summary += f" (omitted {omitted} descriptions)"
return summary

def _get_event_summary(self) -> str:
"""Get summary string for events

Returns:
str: event summary with counts and descriptions
str: warning count, error count with the 3 most frequent error
descriptions, and the 3 most frequent critical descriptions
"""
total_warnings = 0
total_errors = 0
error_msg_counts: dict[str, int] = {}
warning_msg_counts: dict[str, int] = {}

critical_msg_counts: dict[str, int] = {}
for event in self.events:
n = self._event_occurrence_count(event)
if event.priority == EventPriority.WARNING:
warning_msg_counts[event.description] = (
warning_msg_counts.get(event.description, 0) + n
if event.priority == EventPriority.CRITICAL:
critical_msg_counts[event.description] = (
critical_msg_counts.get(event.description, 0) + n
)
elif event.priority >= EventPriority.ERROR:
elif event.priority == EventPriority.ERROR:
error_msg_counts[event.description] = error_msg_counts.get(event.description, 0) + n
if event.priority == EventPriority.WARNING:
total_warnings += n
elif event.priority >= EventPriority.ERROR:
total_errors += n

summary_parts = []

if warning_msg_counts:
total_warnings = sum(warning_msg_counts.values())
warning_details = [
f"{msg} (x{count})" if count > 1 else msg
for msg, count in warning_msg_counts.items()
]
summary_parts.append(f"{total_warnings} warnings: {', '.join(warning_details)}")

if error_msg_counts:
total_errors = sum(error_msg_counts.values())
error_details = [
f"{msg} (x{count})" if count > 1 else msg for msg, count in error_msg_counts.items()
]
summary_parts.append(f"{total_errors} errors: {', '.join(error_details)}")
if total_warnings:
summary_parts.append(f"{total_warnings} warnings")
if total_errors:
error_summary = f"{total_errors} errors"
if error_msg_counts:
error_summary = self._format_limited_descriptions(error_msg_counts, error_summary)
summary_parts.append(error_summary)

if critical_msg_counts:
summary_parts.append(self._format_limited_descriptions(critical_msg_counts, "critical"))

return "; ".join(summary_parts)

Expand Down
66 changes: 66 additions & 0 deletions test/unit/framework/test_dataanalyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@
# SOFTWARE.
#
###############################################################################
from typing import Optional

from nodescraper.enums.eventpriority import EventPriority
from nodescraper.enums.executionstatus import ExecutionStatus
from nodescraper.interfaces.dataanalyzertask import analyze_decorator
from nodescraper.models.event import Event
from nodescraper.models.taskresult import TaskResult


def test_invalid_data(mock_analyzer, dummy_data_model, system_info):
Expand Down Expand Up @@ -117,3 +121,65 @@ def test_analyzer_subclass_without_data_model_raises_type_error():
class MissingModelAnalyzer(DataAnalyzer):
def analyze_data(self, data, args=None):
return self.result


def _event(priority: EventPriority, description: str, count: Optional[int] = None) -> Event:
data = {"count": count} if count is not None else {}
return Event(category="OS", description=description, priority=priority, data=data)


def test_event_summary_empty_when_no_events():
assert TaskResult()._get_event_summary() == ""


def test_event_summary_counts_warnings_and_errors():
result = TaskResult(
events=[
_event(EventPriority.WARNING, "w1"),
_event(EventPriority.WARNING, "w2", count=2),
_event(EventPriority.ERROR, "e1"),
]
)
assert result._get_event_summary() == "3 warnings; 1 errors: e1"


def test_event_summary_top_three_errors_by_count_with_omission_note():
result = TaskResult(
events=[
_event(EventPriority.ERROR, "e1"),
_event(EventPriority.ERROR, "e2", count=4),
_event(EventPriority.ERROR, "e1"),
_event(EventPriority.ERROR, "e3"),
_event(EventPriority.ERROR, "e4"),
]
)
assert result._get_event_summary() == (
"8 errors: e2 (x4), e1 (x2), e3 (omitted 1 descriptions)"
)


def test_event_summary_critical_counted_as_error_and_listed():
result = TaskResult(events=[_event(EventPriority.CRITICAL, "c1")])
assert result._get_event_summary() == "1 errors; critical: c1"


def test_event_summary_three_critical_no_omission_note():
result = TaskResult(events=[_event(EventPriority.CRITICAL, f"c{i}") for i in range(3)])
summary = result._get_event_summary()
assert summary == "3 errors; critical: c0, c1, c2"
assert "omitted" not in summary


def test_event_summary_top_three_critical_by_count_with_omission_note():
result = TaskResult(
events=[
_event(EventPriority.CRITICAL, "rare"),
_event(EventPriority.CRITICAL, "common", count=5),
_event(EventPriority.CRITICAL, "mid", count=3),
_event(EventPriority.CRITICAL, "also_rare"),
_event(EventPriority.CRITICAL, "second", count=4),
]
)
assert result._get_event_summary() == (
"14 errors; critical: common (x5), second (x4), mid (x3) (omitted 2 descriptions)"
)
14 changes: 11 additions & 3 deletions test/unit/plugin/test_package_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def test_data_version_regex(package_analyzer, default_data_lib):


def test_data_multiple_errors_regex(package_analyzer, default_data_lib):
"""Test that detailed error messages are shown for multiple package errors"""
"""Test that the first package error descriptions are included in the summary"""
args = PackageAnalyzerArgs(
exp_package_ver={
"missing-package": None,
Expand All @@ -118,8 +118,16 @@ def test_data_multiple_errors_regex(package_analyzer, default_data_lib):
)
res = package_analyzer.analyze_data(default_data_lib, args=args)
assert res.status == ExecutionStatus.ERROR
assert "missing-package" in res.message
assert "another-missing" in res.message
assert res.message == (
"Package version mismatch (3 errors: "
"Package missing-package not found in the package list, "
r"Package test-ubuntu-package\.x86_64 Version Mismatch, "
r"Expected 2\.\d+ but found 1.11-1.xx11, "
"Package another-missing not found in the package list)"
)
descriptions = " ".join(event.description for event in res.events)
assert "missing-package" in descriptions
assert "another-missing" in descriptions
assert len(res.events) == 3


Expand Down
9 changes: 5 additions & 4 deletions test/unit/plugin/test_regex_search_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ def test_regex_search_analyzer_match(system_info):
)
result = analyzer.analyze_data(data, args)
assert result.status == ExecutionStatus.ERROR
assert "task detected errors" in result.message
assert "fatal seen" in result.message
assert result.message == "task detected errors (1 errors: fatal seen)"
assert len(result.events) == 1
assert result.events[0].description == "fatal seen"

Expand Down Expand Up @@ -167,8 +166,10 @@ def test_regex_search_plugin_analyzer_only(system_info, logger):
},
)
assert out.status == ExecutionStatus.ERROR
assert "Analysis error:" in out.message
assert "found" in out.message
assert out.message.startswith(
"Analysis error: task detected errors (1 errors: found [file: "
)
assert out.message.endswith("])")
assert out.result_data.analysis_result.status == ExecutionStatus.ERROR
assert len(out.result_data.analysis_result.events) == 1
desc = out.result_data.analysis_result.events[0].description
Expand Down
7 changes: 4 additions & 3 deletions test/unit/plugin/test_sysctl_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def test_analyzer_mismatch(analyzer, correct_data):
args = SysctlAnalyzerArgs(exp_vm_swappiness=3, exp_vm_numa_balancing=4)
result = analyzer.analyze_data(correct_data, args)
assert result.status == ExecutionStatus.ERROR
assert "2 sysctl parameter(s) mismatched." in result.message
assert "1 errors" in result.message
assert "Sysctl mismatch detected" in result.message
assert result.message == (
"2 sysctl parameter(s) mismatched. (1 errors: Sysctl mismatch detected)"
)
assert result.events[0].description == "Sysctl mismatch detected"
Loading