Response parser and split up class diagrams
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from itertools import tee
|
||||
|
||||
from . import test_data
|
||||
|
||||
from sia.llm_engine import LlmEngine
|
||||
|
||||
class LlmEngineTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.model_path = "/root/model"
|
||||
|
||||
def test_initialization(self):
|
||||
llm_engine = LlmEngine(self.model_path)
|
||||
self.assertIsInstance(llm_engine, LlmEngine)
|
||||
|
||||
def test_infer(self):
|
||||
main_context = "This is a test"
|
||||
llm_engine = LlmEngine(self.model_path)
|
||||
tokens = llm_engine.infer(test_data.echo_system_prompt, main_context)
|
||||
print_tokens, result_tokens = tee(tokens)
|
||||
for token in print_tokens:
|
||||
print(token, end="", flush=True)
|
||||
result = ''.join(result_tokens)
|
||||
self.assertEqual(result, f"{main_context}<test_tag>{main_context}</test_tag>")
|
||||
215
test/response_parser_test.py
Normal file
215
test/response_parser_test.py
Normal file
@@ -0,0 +1,215 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from sia.command import Command
|
||||
from sia.delete_command import DeleteCommand
|
||||
from sia.stop_command import StopCommand
|
||||
from sia.entry import Entry
|
||||
from sia.background_entry import BackgroundEntry
|
||||
from sia.parse_error_entry import ParseErrorEntry
|
||||
from sia.read_entry import ReadEntry
|
||||
from sia.reasoning_entry import ReasoningEntry
|
||||
from sia.repeat_entry import RepeatEntry
|
||||
from sia.single_shot_entry import SingleShotEntry
|
||||
from sia.write_entry import WriteEntry
|
||||
from sia.web_io_buffer import WebIOBuffer
|
||||
from sia.response_parser import ResponseParser
|
||||
|
||||
class ResponseParserTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with parser and IO buffer"""
|
||||
self.io_buffer = WebIOBuffer()
|
||||
self.parser = ResponseParser(self.io_buffer)
|
||||
|
||||
def test_delete_command(self):
|
||||
"""Test parsing delete command"""
|
||||
xml = '<delete id="test-id-123"/>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, DeleteCommand)
|
||||
self.assertEqual(result.id, "test-id-123")
|
||||
|
||||
def test_delete_command_missing_id(self):
|
||||
"""Test parsing delete command without ID returns error entry"""
|
||||
xml = '<delete/>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ParseErrorEntry)
|
||||
self.assertEqual(result.content, xml)
|
||||
self.assertIn("missing required 'id'", result.error)
|
||||
|
||||
def test_stop_command(self):
|
||||
"""Test parsing stop command"""
|
||||
xml = '<stop/>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, StopCommand)
|
||||
|
||||
def test_background_entry(self):
|
||||
"""Test parsing background entry"""
|
||||
xml = '<background>echo test</background>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, BackgroundEntry)
|
||||
self.assertEqual(result.script, "echo test")
|
||||
|
||||
def test_background_entry_empty_script(self):
|
||||
"""Test parsing background entry with empty script returns error entry"""
|
||||
xml = '<background/>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ParseErrorEntry)
|
||||
self.assertEqual(result.content, xml)
|
||||
self.assertIn("missing script content", result.error)
|
||||
|
||||
def test_repeat_entry(self):
|
||||
"""Test parsing repeat entry"""
|
||||
xml = '<repeat>ls -l</repeat>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, RepeatEntry)
|
||||
self.assertEqual(result.script, "ls -l")
|
||||
|
||||
def test_repeat_entry_empty_script(self):
|
||||
"""Test parsing repeat entry with empty script returns error entry"""
|
||||
xml = '<repeat/>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ParseErrorEntry)
|
||||
self.assertEqual(result.content, xml)
|
||||
self.assertIn("missing script content", result.error)
|
||||
|
||||
def test_single_shot_entry(self):
|
||||
"""Test parsing single shot entry"""
|
||||
xml = '<single_shot>echo "one time"</single_shot>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, SingleShotEntry)
|
||||
self.assertEqual(result.script, 'echo "one time"')
|
||||
|
||||
def test_single_shot_entry_empty_script(self):
|
||||
"""Test parsing single shot entry with empty script returns error entry"""
|
||||
xml = '<single_shot/>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ParseErrorEntry)
|
||||
self.assertEqual(result.content, xml)
|
||||
self.assertIn("missing script content", result.error)
|
||||
|
||||
def test_reasoning_entry(self):
|
||||
"""Test parsing reasoning entry"""
|
||||
xml = '<reasoning>test reasoning</reasoning>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ReasoningEntry)
|
||||
self.assertEqual(result.content, "test reasoning")
|
||||
|
||||
def test_reasoning_entry_empty_content(self):
|
||||
"""Test parsing reasoning entry with empty content returns error entry"""
|
||||
xml = '<reasoning/>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ParseErrorEntry)
|
||||
self.assertEqual(result.content, xml)
|
||||
self.assertIn("missing content", result.error)
|
||||
|
||||
def test_read_stdin_entry(self):
|
||||
"""Test parsing read stdin entry"""
|
||||
xml = '<read_stdin/>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ReadEntry)
|
||||
self.assertEqual(result.io_buffer, self.io_buffer)
|
||||
|
||||
def test_write_stdout_entry(self):
|
||||
"""Test parsing write stdout entry"""
|
||||
xml = '<write_stdout>test output</write_stdout>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, WriteEntry)
|
||||
self.assertEqual(result.content, "test output")
|
||||
self.assertEqual(result.io_buffer, self.io_buffer)
|
||||
|
||||
def test_write_stdout_entry_empty_content(self):
|
||||
"""Test parsing write stdout entry with empty content returns error entry"""
|
||||
xml = '<write_stdout/>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ParseErrorEntry)
|
||||
self.assertEqual(result.content, xml)
|
||||
self.assertIn("missing content", result.error)
|
||||
|
||||
def test_unknown_element(self):
|
||||
"""Test parsing unknown element returns error entry"""
|
||||
xml = '<unknown>test</unknown>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ParseErrorEntry)
|
||||
self.assertEqual(result.content, xml)
|
||||
self.assertIn("Unknown root element", result.error)
|
||||
|
||||
def test_malformed_xml(self):
|
||||
"""Test parsing malformed XML returns error entry"""
|
||||
xml = '<unclosed>'
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ParseErrorEntry)
|
||||
self.assertEqual(result.content, xml)
|
||||
self.assertIn("Invalid XML", result.error)
|
||||
|
||||
def test_entry_id_generation(self):
|
||||
"""Test that unique IDs are generated for entries"""
|
||||
xml1 = '<reasoning>test 1</reasoning>'
|
||||
xml2 = '<reasoning>test 2</reasoning>'
|
||||
|
||||
result1 = self.parser.parse(xml1)
|
||||
result2 = self.parser.parse(xml2)
|
||||
|
||||
self.assertNotEqual(result1.id, result2.id)
|
||||
|
||||
def test_whitespace_handling(self):
|
||||
"""Test handling of whitespace in XML"""
|
||||
xml = """
|
||||
<reasoning>
|
||||
test content
|
||||
with multiple lines
|
||||
</reasoning>
|
||||
"""
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ReasoningEntry)
|
||||
self.assertTrue(result.content.strip())
|
||||
|
||||
def test_script_with_special_characters(self):
|
||||
"""Test parsing script with special characters"""
|
||||
script = 'echo "Special chars: \n\t\r\'\"\\"'
|
||||
xml = f'<single_shot>{script}</single_shot>'
|
||||
|
||||
result = self.parser.parse(xml)
|
||||
self.assertIsInstance(result, SingleShotEntry)
|
||||
self.assertEqual(result.script, script)
|
||||
|
||||
def test_cdata_content(self):
|
||||
"""Test parsing content with CDATA sections"""
|
||||
xml = """
|
||||
<reasoning><![CDATA[Test content with <special> & chars]]></reasoning>
|
||||
"""
|
||||
result = self.parser.parse(xml)
|
||||
self.assertIsInstance(result, ReasoningEntry)
|
||||
self.assertEqual(result.content, "Test content with <special> & chars")
|
||||
|
||||
def test_empty_element_with_attributes(self):
|
||||
"""Test parsing empty element with attributes"""
|
||||
xml = '<read_stdin id="123" timestamp="2024-01-01"/>'
|
||||
result = self.parser.parse(xml)
|
||||
self.assertIsInstance(result, ReadEntry)
|
||||
|
||||
def test_error_in_content_extraction(self):
|
||||
"""Test handling errors during content extraction"""
|
||||
xml = '<single_shot><![CDATA[test]]>' # Malformed CDATA
|
||||
result = self.parser.parse(xml)
|
||||
|
||||
self.assertIsInstance(result, ParseErrorEntry)
|
||||
self.assertEqual(result.content, xml)
|
||||
self.assertIn("Invalid XML", result.error)
|
||||
186
test/system_metrics_test.py
Normal file
186
test/system_metrics_test.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import unittest
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
import psutil
|
||||
import multiprocessing
|
||||
import math
|
||||
from typing import List, Tuple
|
||||
|
||||
from sia.system_metrics import SystemMetrics
|
||||
|
||||
def cpu_load_process():
|
||||
"""Helper process that generates CPU load"""
|
||||
while True:
|
||||
math.factorial(100000)
|
||||
|
||||
class SystemMetricsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Create metrics instance with faster sampling for tests"""
|
||||
self.metrics = SystemMetrics(sample_interval=0.01)
|
||||
|
||||
def tearDown(self):
|
||||
"""Ensure metrics monitoring is stopped"""
|
||||
self.metrics.stop()
|
||||
|
||||
def validate_timestamp(self, timestamp: str):
|
||||
"""Validate timestamp format and reasonableness"""
|
||||
try:
|
||||
# Check format
|
||||
self.assertRegex(
|
||||
timestamp,
|
||||
r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$'
|
||||
)
|
||||
|
||||
# Check timestamp is recent (within last minute)
|
||||
time_parts = timestamp[:-1].split('T') # Remove Z
|
||||
time_str = f"{time_parts[0]} {time_parts[1]}"
|
||||
timestamp_time = time.strptime(time_str, "%Y-%m-%d %H:%M:%S")
|
||||
now = time.gmtime()
|
||||
|
||||
# Convert to seconds since epoch for comparison
|
||||
timestamp_secs = time.mktime(timestamp_time)
|
||||
now_secs = time.mktime(now)
|
||||
|
||||
self.assertLess(abs(now_secs - timestamp_secs), 60)
|
||||
|
||||
except Exception as e:
|
||||
self.fail(f"Invalid timestamp format: {timestamp}")
|
||||
|
||||
def validate_memory_metrics(self, used: int, total: int):
|
||||
"""Validate memory metrics are reasonable"""
|
||||
# Check types
|
||||
self.assertIsInstance(used, int)
|
||||
self.assertIsInstance(total, int)
|
||||
|
||||
# Used memory should be positive and less than total
|
||||
self.assertGreater(used, 0)
|
||||
self.assertGreater(total, 0)
|
||||
self.assertLessEqual(used, total)
|
||||
|
||||
# Compare with actual system memory
|
||||
memory = psutil.virtual_memory()
|
||||
self.assertEqual(total, memory.total)
|
||||
# Used memory should be within 20% of current usage
|
||||
# (allowing for normal fluctuation)
|
||||
self.assertLess(abs(used - memory.used) / memory.total, 0.2)
|
||||
|
||||
def validate_disk_metrics(self, used: int, total: int):
|
||||
"""Validate disk metrics are reasonable"""
|
||||
# Check types
|
||||
self.assertIsInstance(used, int)
|
||||
self.assertIsInstance(total, int)
|
||||
|
||||
# Used space should be positive and less than total
|
||||
self.assertGreater(used, 0)
|
||||
self.assertGreater(total, 0)
|
||||
self.assertLessEqual(used, total)
|
||||
|
||||
# Compare with actual disk usage
|
||||
disk = psutil.disk_usage('/')
|
||||
self.assertEqual(total, disk.total)
|
||||
# Used space should match within 1% of actual usage
|
||||
self.assertLess(abs(used - disk.used) / disk.total, 0.01)
|
||||
|
||||
def validate_usage_values(self, values: List[Tuple[str, int]]):
|
||||
"""Validate usage percentage values are in valid range"""
|
||||
for name, value in values:
|
||||
self.assertIsInstance(value, int)
|
||||
self.assertGreaterEqual(
|
||||
value, 0,
|
||||
f"{name} below valid range: {value}"
|
||||
)
|
||||
self.assertLessEqual(
|
||||
value, 100,
|
||||
f"{name} above valid range: {value}"
|
||||
)
|
||||
|
||||
def test_initial_context(self):
|
||||
"""Test initial context generation"""
|
||||
context = self.metrics.generate_context(0.5)
|
||||
|
||||
# Verify it's an XML element
|
||||
self.assertIsInstance(context, ET.Element)
|
||||
self.assertEqual(context.tag, "context")
|
||||
|
||||
# Validate timestamp
|
||||
self.validate_timestamp(context.get("time"))
|
||||
|
||||
# Validate memory metrics
|
||||
self.validate_memory_metrics(
|
||||
int(context.get("memory_used")),
|
||||
int(context.get("memory_total"))
|
||||
)
|
||||
|
||||
# Validate disk metrics
|
||||
self.validate_disk_metrics(
|
||||
int(context.get("disk_used")),
|
||||
int(context.get("disk_total"))
|
||||
)
|
||||
|
||||
# Validate usage percentages
|
||||
self.validate_usage_values([
|
||||
("CPU", int(context.get("cpu"))),
|
||||
("GPU", int(context.get("gpu"))),
|
||||
("Context", int(context.get("context")))
|
||||
])
|
||||
|
||||
# Validate stdin buffer size
|
||||
self.assertEqual(context.get("stdin"), "0")
|
||||
|
||||
def test_multiple_context_generations(self):
|
||||
"""Test multiple context generations have different timestamps"""
|
||||
context1 = self.metrics.generate_context(0.5)
|
||||
time.sleep(1)
|
||||
context2 = self.metrics.generate_context(0.5)
|
||||
|
||||
self.assertNotEqual(
|
||||
context1.get("time"),
|
||||
context2.get("time")
|
||||
)
|
||||
|
||||
def test_cpu_usage_detection(self):
|
||||
"""Test CPU usage increases under load"""
|
||||
# Get initial CPU usage
|
||||
context1 = self.metrics.generate_context(0.5)
|
||||
initial_cpu = int(context1.get("cpu"))
|
||||
|
||||
# Generate CPU load in separate process
|
||||
process = multiprocessing.Process(target=cpu_load_process)
|
||||
process.start()
|
||||
|
||||
try:
|
||||
# Wait for samples to accumulate
|
||||
time.sleep(0.5)
|
||||
|
||||
# Get CPU usage under load
|
||||
context2 = self.metrics.generate_context(0.5)
|
||||
load_cpu = int(context2.get("cpu"))
|
||||
|
||||
# CPU usage should increase
|
||||
self.assertGreater(load_cpu, initial_cpu)
|
||||
|
||||
finally:
|
||||
process.terminate()
|
||||
process.join()
|
||||
|
||||
def test_context_usage_reflection(self):
|
||||
"""Test context usage parameter is reflected accurately"""
|
||||
test_values = [0.0, 0.42, 0.8, 1.0]
|
||||
|
||||
for value in test_values:
|
||||
context = self.metrics.generate_context(value)
|
||||
self.assertEqual(
|
||||
int(context.get("context")),
|
||||
round(value * 100)
|
||||
)
|
||||
|
||||
def test_cleanup(self):
|
||||
"""Test monitoring stops properly"""
|
||||
self.metrics.stop()
|
||||
|
||||
# Monitor thread should finish
|
||||
self.assertFalse(self.metrics._monitor_thread.is_alive())
|
||||
|
||||
# Should still generate valid context after stopping
|
||||
context = self.metrics.generate_context(0.5)
|
||||
self.assertIsInstance(context, ET.Element)
|
||||
Reference in New Issue
Block a user