start implementation on new architecture
This commit is contained in:
227
test/background_entry_test.py
Normal file
227
test/background_entry_test.py
Normal file
@@ -0,0 +1,227 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
import platform
|
||||
|
||||
from sia.background_entry import BackgroundEntry
|
||||
|
||||
class BackgroundEntryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with fixed id and timestamp"""
|
||||
self.test_id = "test-id-1234"
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
|
||||
# Create temporary files for testing
|
||||
self.test_filename = "test_output.txt"
|
||||
self.counter_file = "counter.txt"
|
||||
self.cleanup_files()
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up test files and ensure no processes are left running"""
|
||||
self.cleanup_files()
|
||||
|
||||
def cleanup_files(self):
|
||||
"""Helper to remove test files"""
|
||||
for filename in [self.test_filename, self.counter_file]:
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
def wait_for_process_start(self, entry, timeout=1.0):
|
||||
"""Wait for process to start, with timeout"""
|
||||
start_time = time.time()
|
||||
while entry.process is None and time.time() - start_time < timeout:
|
||||
entry.update()
|
||||
time.sleep(0.1)
|
||||
self.assertIsNotNone(entry.process, "Process failed to start")
|
||||
|
||||
def wait_for_process_exit(self, entry, timeout=1.0):
|
||||
"""Wait for process to exit, with timeout"""
|
||||
start_time = time.time()
|
||||
while entry.process is not None and time.time() - start_time < timeout:
|
||||
entry.update()
|
||||
time.sleep(0.1)
|
||||
self.assertIsNone(entry.process, "Process failed to exit")
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test entry initialization"""
|
||||
script = "echo test"
|
||||
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
self.assertEqual(entry.script, script)
|
||||
self.assertEqual(entry.stdout, "")
|
||||
self.assertEqual(entry.stderr, "")
|
||||
self.assertIsNone(entry.process)
|
||||
self.assertEqual(entry.id, self.test_id)
|
||||
self.assertEqual(entry.timestamp, self.test_timestamp)
|
||||
|
||||
def test_short_running_process(self):
|
||||
"""Test execution of a quick process"""
|
||||
script = "echo 'test'"
|
||||
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
# Start and wait for completion
|
||||
entry.update()
|
||||
self.wait_for_process_start(entry)
|
||||
self.wait_for_process_exit(entry)
|
||||
|
||||
# Verify output
|
||||
self.assertEqual(entry._accumulated_stdout.strip(), "test")
|
||||
self.assertEqual(entry._accumulated_stderr, "")
|
||||
self.assertEqual(entry._exit_code, 0)
|
||||
|
||||
def test_continuous_output(self):
|
||||
"""Test process that generates continuous output"""
|
||||
# Create a Python script that counts to 3 with minimal delay
|
||||
script = (
|
||||
'python3 -c "'
|
||||
'import sys\n'
|
||||
'for i in range(3):\n'
|
||||
' sys.stdout.write(f\\\"count {i+1}\\n\\\")\n'
|
||||
' sys.stdout.flush()\n'
|
||||
'"'
|
||||
)
|
||||
|
||||
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
# Start process and wait for completion
|
||||
entry.update()
|
||||
self.wait_for_process_start(entry)
|
||||
self.wait_for_process_exit(entry, timeout=2.0)
|
||||
|
||||
# One more update to ensure we get all output
|
||||
entry.update()
|
||||
|
||||
# Verify all output was captured
|
||||
output = entry._accumulated_stdout
|
||||
self.assertIn('count 1', output, "Missing count 1")
|
||||
self.assertIn('count 2', output, "Missing count 2")
|
||||
self.assertIn('count 3', output, "Missing count 3")
|
||||
|
||||
def test_process_failure(self):
|
||||
"""Test handling of process failures"""
|
||||
entry = BackgroundEntry("nonexistentcommand", self.test_id, self.test_timestamp)
|
||||
|
||||
# Start and wait for failure
|
||||
entry.update()
|
||||
self.wait_for_process_exit(entry)
|
||||
|
||||
# Verify error was captured
|
||||
self.assertTrue(len(entry._accumulated_stderr) > 0)
|
||||
self.assertEqual(entry._accumulated_stdout, "")
|
||||
self.assertNotEqual(entry._exit_code, 0)
|
||||
|
||||
def test_generate_context_running(self):
|
||||
"""Test XML context generation for running process"""
|
||||
script = "sleep 1"
|
||||
|
||||
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
|
||||
entry.update() # Start process
|
||||
self.wait_for_process_start(entry)
|
||||
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "background")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
self.assertIsNotNone(element.get("pid"))
|
||||
self.assertIsNone(element.get("exit_code"))
|
||||
|
||||
# Clean up
|
||||
entry._cleanup_process()
|
||||
|
||||
def test_generate_context_completed(self):
|
||||
"""Test XML context generation for completed process"""
|
||||
script = "echo 'test'"
|
||||
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
# Start and wait for completion
|
||||
entry.update()
|
||||
self.wait_for_process_start(entry)
|
||||
self.wait_for_process_exit(entry)
|
||||
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "background")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
self.assertIsNone(element.get("pid"))
|
||||
self.assertEqual(element.get("exit_code"), "0")
|
||||
|
||||
stdout_text = element.find("stdout").text
|
||||
self.assertTrue(stdout_text.startswith("<![CDATA["))
|
||||
self.assertIn("test", stdout_text)
|
||||
self.assertTrue(stdout_text.endswith("]]>"))
|
||||
|
||||
def test_cleanup_on_deletion(self):
|
||||
"""Test process cleanup when entry is deleted"""
|
||||
script = "sleep 10"
|
||||
|
||||
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
self.wait_for_process_start(entry)
|
||||
|
||||
# Verify process is running
|
||||
self.assertIsNotNone(entry.process)
|
||||
pid = entry.process.pid
|
||||
|
||||
# Delete the entry
|
||||
entry._cleanup_process()
|
||||
del entry
|
||||
|
||||
# Give process time to be cleaned up
|
||||
time.sleep(0.1)
|
||||
|
||||
# Verify process was terminated
|
||||
with self.assertRaises(ProcessLookupError):
|
||||
os.kill(pid, 0) # Check if process exists
|
||||
|
||||
def test_cleanup_running_process(self):
|
||||
"""Test cleanup of running process"""
|
||||
script = "sleep 10"
|
||||
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
# Start the process
|
||||
entry.update()
|
||||
self.assertIsNotNone(entry.process)
|
||||
process_pid = entry.process.pid
|
||||
|
||||
# Cleanup should terminate process
|
||||
entry.cleanup()
|
||||
|
||||
# Verify process was terminated
|
||||
self.assertIsNone(entry.process)
|
||||
time.sleep(0.1) # Give process time to terminate
|
||||
with self.assertRaises(ProcessLookupError):
|
||||
import os
|
||||
os.kill(process_pid, 0)
|
||||
|
||||
def test_cleanup_completed_process(self):
|
||||
"""Test cleanup of already completed process"""
|
||||
script = "echo 'test'"
|
||||
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
# Run and wait for completion
|
||||
entry.update()
|
||||
self.wait_for_process_exit(entry)
|
||||
|
||||
# Cleanup should be safe for completed process
|
||||
entry.cleanup()
|
||||
self.assertIsNone(entry.process)
|
||||
|
||||
def test_multiple_cleanup_calls(self):
|
||||
"""Test that multiple cleanup calls are safe"""
|
||||
script = "sleep 10"
|
||||
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
# Start process
|
||||
entry.update()
|
||||
self.assertIsNotNone(entry.process)
|
||||
|
||||
# Multiple cleanups should be safe
|
||||
entry.cleanup()
|
||||
entry.cleanup()
|
||||
entry.cleanup()
|
||||
|
||||
self.assertIsNone(entry.process)
|
||||
@@ -1,33 +0,0 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from sia.context_template import generate_context
|
||||
|
||||
class TestContextTemplate(unittest.TestCase):
|
||||
def test_empty_containers(self):
|
||||
"""Test context generation with no containers."""
|
||||
context = generate_context([])
|
||||
self.assertIn("<context>", context)
|
||||
self.assertIn("<containers/>", context)
|
||||
self.assertIn("</context>", context)
|
||||
|
||||
def test_single_container(self):
|
||||
"""Test context generation with a single container."""
|
||||
container_status = {
|
||||
'name': 'test-container',
|
||||
'status': 'running',
|
||||
'started_at': '2024-10-25T10:00:00Z',
|
||||
'stdout_size': 100,
|
||||
'stderr_size': 50
|
||||
}
|
||||
context = generate_context([container_status])
|
||||
self.assertIn("<context>", context)
|
||||
self.assertIn("<containers", context)
|
||||
self.assertIn('name="test-container"', context)
|
||||
self.assertIn('status="running"', context)
|
||||
self.assertIn('started_at="2024-10-25T10:00:00Z"', context)
|
||||
self.assertIn('stdout="100"', context)
|
||||
self.assertIn('stderr="50"', context)
|
||||
self.assertIn("</context>", context)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
66
test/delete_command_test.py
Normal file
66
test/delete_command_test.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from sia.working_memory import WorkingMemory
|
||||
from sia.delete_command import DeleteCommand
|
||||
from sia.reasoning_entry import ReasoningEntry
|
||||
from sia.background_entry import BackgroundEntry
|
||||
|
||||
class DeleteCommandTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with a fresh working memory"""
|
||||
self.memory = WorkingMemory()
|
||||
self.test_id = "test-id-1234"
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
|
||||
def test_delete_existing_entry(self):
|
||||
"""Test deleting an existing entry"""
|
||||
# Add test entry
|
||||
entry = ReasoningEntry("test content", self.test_id, self.test_timestamp)
|
||||
self.memory.add_entry(entry)
|
||||
|
||||
# Execute delete command
|
||||
command = DeleteCommand(self.test_id)
|
||||
result = command.execute(self.memory)
|
||||
|
||||
# Verify result and memory state
|
||||
self.assertTrue(result.success)
|
||||
self.assertFalse(result.should_stop)
|
||||
self.assertEqual(result.message, "")
|
||||
self.assertEqual(self.memory.get_entries_count(), 0)
|
||||
|
||||
def test_delete_running_background_entry(self):
|
||||
"""Test deleting a background entry that is still running"""
|
||||
# Create a background entry with a long-running command
|
||||
entry = BackgroundEntry("sleep 10", self.test_id, self.test_timestamp)
|
||||
self.memory.add_entry(entry)
|
||||
|
||||
# Start the process
|
||||
entry.update()
|
||||
self.assertIsNotNone(entry.process)
|
||||
process_pid = entry.process.pid
|
||||
|
||||
# Execute delete command
|
||||
command = DeleteCommand(self.test_id)
|
||||
result = command.execute(self.memory)
|
||||
|
||||
# Verify process was terminated
|
||||
time.sleep(0.1) # Give process time to terminate
|
||||
self.assertTrue(result.success)
|
||||
self.assertIsNone(entry.process)
|
||||
|
||||
# Verify process no longer exists
|
||||
with self.assertRaises(ProcessLookupError):
|
||||
import os
|
||||
os.kill(process_pid, 0)
|
||||
|
||||
def test_delete_nonexistent_entry(self):
|
||||
"""Test attempting to delete a nonexistent entry"""
|
||||
command = DeleteCommand("nonexistent-id")
|
||||
result = command.execute(self.memory)
|
||||
|
||||
# Verify error result
|
||||
self.assertFalse(result.success)
|
||||
self.assertFalse(result.should_stop)
|
||||
self.assertIn("not found", result.message)
|
||||
@@ -1,121 +0,0 @@
|
||||
import unittest
|
||||
import time
|
||||
from sia.docker_module import DockerModule
|
||||
|
||||
class DockerModuleTest(unittest.TestCase):
|
||||
def test_initialization(self):
|
||||
"""Test DockerModule initialization."""
|
||||
with DockerModule() as docker_module:
|
||||
self.assertIsInstance(docker_module, DockerModule)
|
||||
|
||||
def test_start_container_short_lived(self):
|
||||
"""Test starting a short-lived container."""
|
||||
with DockerModule() as docker_module:
|
||||
image = "busybox:latest"
|
||||
timeout = 1000
|
||||
command = "echo"
|
||||
arguments = ["Hello World"]
|
||||
output = docker_module.start_container(
|
||||
image=image,
|
||||
timeout=timeout,
|
||||
command=command,
|
||||
arguments=arguments
|
||||
)
|
||||
self.assertEqual(output, "Hello World\n")
|
||||
|
||||
def test_start_container_long_running(self):
|
||||
"""Test starting a long-running container."""
|
||||
with DockerModule() as docker_module:
|
||||
name = "test_start_container_long_running"
|
||||
image = "busybox:latest"
|
||||
container_name = docker_module.start_container(
|
||||
image=image,
|
||||
name=name
|
||||
)
|
||||
self.assertEqual(container_name, name)
|
||||
self.assertIn(name, docker_module.containers)
|
||||
docker_module.wait_container(container_name, 0)
|
||||
self.assertNotIn(name, docker_module.containers)
|
||||
|
||||
def test_start_container_with_volumes(self):
|
||||
"""Test starting a container with volume mappings."""
|
||||
with DockerModule() as docker_module:
|
||||
arguments = ["sh", "-c", "echo 'Hello World' > /write_here/test_start_container_with_volumes"]
|
||||
volumes = {"/tmp": "/write_here"}
|
||||
docker_module.start_container(
|
||||
image="busybox:latest",
|
||||
timeout=1000,
|
||||
arguments=arguments,
|
||||
volumes=volumes
|
||||
)
|
||||
with open("/tmp/test_start_container_with_volumes", "r") as f:
|
||||
self.assertEqual(f.read(), "Hello World\n")
|
||||
|
||||
def test_invalid_container_start(self):
|
||||
"""Test error handling for invalid container start."""
|
||||
with DockerModule() as docker_module:
|
||||
with self.assertRaises(ValueError):
|
||||
docker_module.start_container(image="busybox:latest")
|
||||
|
||||
def test_container_io_operations(self):
|
||||
"""Test container I/O operations."""
|
||||
with DockerModule() as docker_module:
|
||||
arguments = [
|
||||
"sh",
|
||||
"-c",
|
||||
"read input; echo \"$input\" | tr '[:lower:]' '[:upper:]'; echo \"$input\" | tr '[:upper:]' '[:lower:]' >&2"
|
||||
]
|
||||
name = "test_container_io_operations"
|
||||
container_name = docker_module.start_container(
|
||||
image="busybox:latest",
|
||||
name=name,
|
||||
arguments=arguments
|
||||
)
|
||||
test_input = "Test Input\n"
|
||||
docker_module.write_container_stdin(name, test_input)
|
||||
time.sleep(1) # for processing
|
||||
stdout = docker_module.read_container_stdout(name)
|
||||
self.assertEqual(stdout, "TEST INPUT\n")
|
||||
stderr = docker_module.read_container_stderr(name)
|
||||
self.assertEqual(stderr, "test input\n")
|
||||
docker_module.wait_container(container_name, 0)
|
||||
|
||||
def test_wait_container(self):
|
||||
"""Test waiting for container completion."""
|
||||
with DockerModule() as docker_module:
|
||||
name = "test_wait_container"
|
||||
docker_module.start_container(
|
||||
image="busybox:latest",
|
||||
name=name
|
||||
)
|
||||
exit_code, output = docker_module.wait_container(name, timeout=1000)
|
||||
self.assertEqual(exit_code, -1)
|
||||
self.assertEqual(output, "")
|
||||
|
||||
def test_get_container_status(self):
|
||||
"""Test getting container status information."""
|
||||
with DockerModule() as docker_module:
|
||||
arguments = [
|
||||
"sh",
|
||||
"-c",
|
||||
"echo 'standard output'; echo 'standard error' >&2; sleep 3"
|
||||
]
|
||||
name = "test_get_container_status"
|
||||
docker_module.start_container(
|
||||
image="busybox:latest",
|
||||
name=name,
|
||||
arguments=arguments
|
||||
)
|
||||
time.sleep(1)
|
||||
status = docker_module.get_container_status(name)
|
||||
self.assertEqual(status['name'], name)
|
||||
self.assertEqual(status['status'], "running")
|
||||
self.assertGreater(len(status['started_at']), 0)
|
||||
self.assertEqual(status['exit_code'], 0)
|
||||
self.assertEqual(status['error'], "")
|
||||
self.assertEqual(status['stdout_size'], 16)
|
||||
self.assertEqual(status['stderr_size'], 15)
|
||||
docker_module.wait_container(name, 0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -3,7 +3,6 @@ import unittest
|
||||
from itertools import tee
|
||||
|
||||
from . import test_data
|
||||
from . import test_util
|
||||
|
||||
from sia.llm_engine import LlmEngine
|
||||
|
||||
|
||||
88
test/parse_error_entry_test.py
Normal file
88
test/parse_error_entry_test.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
from sia.parse_error_entry import ParseErrorEntry
|
||||
|
||||
class ParseErrorEntryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with fixed id and timestamp"""
|
||||
self.test_id = "test-id-1234"
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test entry initialization"""
|
||||
content = "invalid content"
|
||||
error = "parsing failed"
|
||||
entry = ParseErrorEntry(content, error, self.test_id, self.test_timestamp)
|
||||
|
||||
self.assertEqual(entry.content, content)
|
||||
self.assertEqual(entry.error, error)
|
||||
self.assertEqual(entry.id, self.test_id)
|
||||
self.assertEqual(entry.timestamp, self.test_timestamp)
|
||||
|
||||
def test_update_does_nothing(self):
|
||||
"""Test that update operation has no effect"""
|
||||
content = "invalid content"
|
||||
error = "parsing failed"
|
||||
entry = ParseErrorEntry(content, error, self.test_id, self.test_timestamp)
|
||||
|
||||
# Store initial state
|
||||
initial_content = entry.content
|
||||
initial_error = entry.error
|
||||
|
||||
# Perform update
|
||||
entry.update()
|
||||
|
||||
# Verify state hasn't changed
|
||||
self.assertEqual(entry.content, initial_content)
|
||||
self.assertEqual(entry.error, initial_error)
|
||||
|
||||
def test_generate_context(self):
|
||||
"""Test XML context generation"""
|
||||
content = "invalid content"
|
||||
error = "parsing failed"
|
||||
entry = ParseErrorEntry(content, error, self.test_id, self.test_timestamp)
|
||||
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "parse_error")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
|
||||
# Verify error element
|
||||
error_elem = element.find("error")
|
||||
self.assertIsNotNone(error_elem)
|
||||
self.assertEqual(error_elem.text, f"<![CDATA[{error}]]>")
|
||||
|
||||
# Verify content element
|
||||
content_elem = element.find("content")
|
||||
self.assertIsNotNone(content_elem)
|
||||
self.assertEqual(content_elem.text, f"<![CDATA[{content}]]>")
|
||||
|
||||
def test_special_characters(self):
|
||||
"""Test handling content and errors with special characters"""
|
||||
content = "Special content: \n\t\r\'\"\\"
|
||||
error = "Special error: \n\t\r\'\"\\"
|
||||
entry = ParseErrorEntry(content, error, self.test_id, self.test_timestamp)
|
||||
|
||||
element = entry.generate_context()
|
||||
self.assertEqual(element.find("content").text, f"<![CDATA[{content}]]>")
|
||||
self.assertEqual(element.find("error").text, f"<![CDATA[{error}]]>")
|
||||
|
||||
def test_empty_content_and_error(self):
|
||||
"""Test handling empty content and error messages"""
|
||||
entry = ParseErrorEntry("", "", self.test_id, self.test_timestamp)
|
||||
|
||||
element = entry.generate_context()
|
||||
self.assertEqual(element.find("content").text, "<![CDATA[]]>")
|
||||
self.assertEqual(element.find("error").text, "<![CDATA[]]>")
|
||||
|
||||
def test_large_content(self):
|
||||
"""Test handling large content and error messages"""
|
||||
content = "x" * 10000
|
||||
error = "y" * 10000
|
||||
entry = ParseErrorEntry(content, error, self.test_id, self.test_timestamp)
|
||||
|
||||
element = entry.generate_context()
|
||||
self.assertEqual(element.find("content").text, f"<![CDATA[{content}]]>")
|
||||
self.assertEqual(element.find("error").text, f"<![CDATA[{error}]]>")
|
||||
130
test/read_entry_test.py
Normal file
130
test/read_entry_test.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
from sia.web_io_buffer import WebIOBuffer
|
||||
from sia.read_entry import ReadEntry
|
||||
|
||||
class ReadEntryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with fixed id, timestamp, and IO buffer"""
|
||||
self.test_id = "test-id-1234"
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
self.io_buffer = WebIOBuffer()
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test entry initialization"""
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
|
||||
self.assertEqual(entry.content, "")
|
||||
self.assertEqual(entry.id, self.test_id)
|
||||
self.assertEqual(entry.timestamp, self.test_timestamp)
|
||||
self.assertFalse(entry._read)
|
||||
|
||||
def test_single_read(self):
|
||||
"""Test reading content once"""
|
||||
test_input = "test message"
|
||||
self.io_buffer.append_stdin(test_input)
|
||||
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
# Verify content was read
|
||||
self.assertEqual(entry.content, test_input)
|
||||
self.assertTrue(entry._read)
|
||||
self.assertEqual(self.io_buffer.buffer_length(), 0) # Buffer should be cleared
|
||||
|
||||
def test_multiple_updates(self):
|
||||
"""Test that content is only read once even with multiple updates"""
|
||||
test_input = "initial input"
|
||||
self.io_buffer.append_stdin(test_input)
|
||||
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
initial_content = entry.content
|
||||
|
||||
# Add more input and update again
|
||||
self.io_buffer.append_stdin("additional input")
|
||||
entry.update()
|
||||
entry.update()
|
||||
|
||||
# Verify content hasn't changed after first read
|
||||
self.assertEqual(entry.content, initial_content)
|
||||
self.assertTrue(entry._read)
|
||||
|
||||
def test_empty_input(self):
|
||||
"""Test reading when no input is available"""
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(entry.content, "")
|
||||
self.assertTrue(entry._read)
|
||||
|
||||
def test_special_characters(self):
|
||||
"""Test reading content with special characters"""
|
||||
test_input = "Special chars: \n\t\r\'\"\\"
|
||||
self.io_buffer.append_stdin(test_input)
|
||||
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(entry.content, test_input)
|
||||
|
||||
def test_large_content(self):
|
||||
"""Test reading large content"""
|
||||
test_input = "x" * 10000
|
||||
self.io_buffer.append_stdin(test_input)
|
||||
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(entry.content, test_input)
|
||||
|
||||
def test_generate_context_before_read(self):
|
||||
"""Test XML context generation before reading"""
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "read_stdin")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
self.assertIsNone(element.text) # No content before read
|
||||
|
||||
def test_generate_context_after_read(self):
|
||||
"""Test XML context generation after reading"""
|
||||
test_input = "test message"
|
||||
self.io_buffer.append_stdin(test_input)
|
||||
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "read_stdin")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
self.assertEqual(element.text, f"<![CDATA[{test_input}]]>")
|
||||
|
||||
def test_unicode_content(self):
|
||||
"""Test reading Unicode content"""
|
||||
test_input = "Hello 世界 😊"
|
||||
self.io_buffer.append_stdin(test_input)
|
||||
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(entry.content, test_input)
|
||||
|
||||
def test_multiline_content(self):
|
||||
"""Test reading multiline content"""
|
||||
test_input = """Line 1
|
||||
Line 2
|
||||
Line 3"""
|
||||
self.io_buffer.append_stdin(test_input)
|
||||
|
||||
entry = ReadEntry(self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(entry.content, test_input)
|
||||
|
||||
# Verify XML escaping for multiline content
|
||||
element = entry.generate_context()
|
||||
self.assertEqual(element.text, f"<![CDATA[{test_input}]]>")
|
||||
78
test/reasoning_entry_test.py
Normal file
78
test/reasoning_entry_test.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
from sia.reasoning_entry import ReasoningEntry
|
||||
|
||||
class ReasoningEntryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with fixed id and timestamp"""
|
||||
self.test_id = "test-id-1234"
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test entry initialization"""
|
||||
content = "test reasoning"
|
||||
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
|
||||
|
||||
self.assertEqual(entry.content, content)
|
||||
self.assertEqual(entry.id, self.test_id)
|
||||
self.assertEqual(entry.timestamp, self.test_timestamp)
|
||||
|
||||
def test_update_does_nothing(self):
|
||||
"""Test that update operation has no effect"""
|
||||
content = "test reasoning"
|
||||
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
|
||||
|
||||
# Store initial state
|
||||
initial_content = entry.content
|
||||
|
||||
# Perform update
|
||||
entry.update()
|
||||
|
||||
# Verify state hasn't changed
|
||||
self.assertEqual(entry.content, initial_content)
|
||||
|
||||
def test_generate_context(self):
|
||||
"""Test XML context generation"""
|
||||
content = "test reasoning"
|
||||
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
|
||||
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "reasoning")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
self.assertEqual(element.text, f"<![CDATA[{content}]]>")
|
||||
|
||||
def test_special_characters(self):
|
||||
"""Test handling reasoning text with special characters"""
|
||||
content = "Special reasoning: \n\t\r\'\"\\"
|
||||
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
|
||||
|
||||
element = entry.generate_context()
|
||||
self.assertEqual(element.text, f"<![CDATA[{content}]]>")
|
||||
|
||||
def test_empty_content(self):
|
||||
"""Test handling empty reasoning text"""
|
||||
entry = ReasoningEntry("", self.test_id, self.test_timestamp)
|
||||
|
||||
element = entry.generate_context()
|
||||
self.assertEqual(element.text, "<![CDATA[]]>")
|
||||
|
||||
def test_large_content(self):
|
||||
"""Test handling large reasoning text"""
|
||||
content = "x" * 10000
|
||||
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
|
||||
|
||||
element = entry.generate_context()
|
||||
self.assertEqual(element.text, f"<![CDATA[{content}]]>")
|
||||
|
||||
def test_multiline_content(self):
|
||||
"""Test handling multiline reasoning text"""
|
||||
content = """Line 1
|
||||
Line 2
|
||||
Line 3"""
|
||||
entry = ReasoningEntry(content, self.test_id, self.test_timestamp)
|
||||
|
||||
element = entry.generate_context()
|
||||
self.assertEqual(element.text, f"<![CDATA[{content}]]>")
|
||||
164
test/repeat_entry_test.py
Normal file
164
test/repeat_entry_test.py
Normal file
@@ -0,0 +1,164 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
from sia.repeat_entry import RepeatEntry
|
||||
|
||||
class RepeatEntryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with fixed id and timestamp"""
|
||||
self.test_id = "test-id-1234"
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
|
||||
# Create a temporary file for testing
|
||||
self.test_filename = "test_output.txt"
|
||||
self.counter_file = "counter.txt"
|
||||
self.cleanup_files()
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up any test files"""
|
||||
self.cleanup_files()
|
||||
|
||||
def cleanup_files(self):
|
||||
"""Helper to remove test files"""
|
||||
for filename in [self.test_filename, self.counter_file]:
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test entry initialization"""
|
||||
script = "echo test"
|
||||
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
self.assertEqual(entry.script, script)
|
||||
self.assertEqual(entry.stdout, "")
|
||||
self.assertEqual(entry.stderr, "")
|
||||
self.assertIsNone(entry.exit_code)
|
||||
self.assertEqual(entry.id, self.test_id)
|
||||
self.assertEqual(entry.timestamp, self.test_timestamp)
|
||||
|
||||
def test_successful_execution(self):
|
||||
"""Test successful script execution"""
|
||||
script = "echo 'test'"
|
||||
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
# Verify entry state
|
||||
self.assertEqual(entry.stdout.strip(), "test")
|
||||
self.assertEqual(entry.stderr, "")
|
||||
self.assertEqual(entry.exit_code, 0)
|
||||
|
||||
def test_failed_execution(self):
|
||||
"""Test failed script execution with invalid command"""
|
||||
entry = RepeatEntry("nonexistentcommand", self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
# Verify entry state
|
||||
self.assertEqual(entry.stdout, "")
|
||||
self.assertTrue(len(entry.stderr) > 0) # Error message will vary by OS
|
||||
self.assertNotEqual(entry.exit_code, 0) # Exit code will be non-zero
|
||||
|
||||
def test_repeated_execution(self):
|
||||
"""Test that script executes on every update"""
|
||||
# Create a counter file
|
||||
with open(self.counter_file, 'w') as f:
|
||||
f.write('0')
|
||||
|
||||
# Script that increments and reads the counter
|
||||
script = (
|
||||
f'python3 -c "'
|
||||
f'import os; '
|
||||
f'f=open(\'{self.counter_file}\', \'r+\'); '
|
||||
f'n=int(f.read()); '
|
||||
f'f.seek(0); '
|
||||
f'f.write(str(n+1)); '
|
||||
f'f.truncate(); '
|
||||
f'f.close(); '
|
||||
f'print(n+1)'
|
||||
f'"'
|
||||
)
|
||||
|
||||
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
# Run update multiple times
|
||||
outputs = []
|
||||
for _ in range(3):
|
||||
entry.update()
|
||||
outputs.append(int(entry.stdout.strip()))
|
||||
|
||||
# Verify outputs are sequential numbers
|
||||
self.assertEqual(outputs, [1, 2, 3])
|
||||
|
||||
def test_generate_context(self):
|
||||
"""Test XML context generation"""
|
||||
script = "echo 'test'"
|
||||
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "repeat")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
self.assertEqual(element.text, f"<![CDATA[{script}]]>")
|
||||
self.assertEqual(element.get("exit_code"), "0")
|
||||
|
||||
# Get stdout and remove trailing newline for comparison
|
||||
stdout_text = element.find("stdout").text
|
||||
self.assertTrue(stdout_text.startswith("<![CDATA[test"))
|
||||
self.assertTrue(stdout_text.endswith("]]>"))
|
||||
|
||||
self.assertEqual(element.find("stderr").text, "<![CDATA[]]>")
|
||||
|
||||
def test_changing_output(self):
|
||||
"""Test handling of changing command output"""
|
||||
# Create a counter file
|
||||
with open(self.counter_file, 'w') as f:
|
||||
f.write('0')
|
||||
|
||||
# Script that outputs an incrementing number
|
||||
script = (
|
||||
f'python3 -c "'
|
||||
f'import os; '
|
||||
f'f=open(\'{self.counter_file}\', \'r+\'); '
|
||||
f'n=int(f.read()); '
|
||||
f'f.seek(0); '
|
||||
f'f.write(str(n+1)); '
|
||||
f'f.truncate(); '
|
||||
f'f.close(); '
|
||||
f'print(\'Output\', n+1)'
|
||||
f'"'
|
||||
)
|
||||
|
||||
entry = RepeatEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
# Execute multiple times and verify output changes
|
||||
entry.update()
|
||||
first_output = entry.stdout.strip()
|
||||
|
||||
entry.update()
|
||||
second_output = entry.stdout.strip()
|
||||
|
||||
entry.update()
|
||||
third_output = entry.stdout.strip()
|
||||
|
||||
# Outputs should be different for each run
|
||||
outputs = [first_output, second_output, third_output]
|
||||
unique_outputs = set(outputs)
|
||||
self.assertEqual(len(unique_outputs), 3)
|
||||
self.assertEqual(outputs, ['Output 1', 'Output 2', 'Output 3'])
|
||||
|
||||
def test_error_recovery(self):
|
||||
"""Test that entry recovers from errors in subsequent updates"""
|
||||
# Start with a failing command
|
||||
entry = RepeatEntry("nonexistentcommand", self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
self.assertNotEqual(entry.exit_code, 0)
|
||||
|
||||
# Change to a working command
|
||||
entry.script = "echo 'test'"
|
||||
entry.update()
|
||||
|
||||
# Verify recovery
|
||||
self.assertEqual(entry.exit_code, 0)
|
||||
self.assertEqual(entry.stdout.strip(), "test")
|
||||
self.assertEqual(entry.stderr, "")
|
||||
141
test/single_shot_entry_test.py
Normal file
141
test/single_shot_entry_test.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
from sia.single_shot_entry import SingleShotEntry
|
||||
|
||||
class SingleShotEntryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with fixed id and timestamp"""
|
||||
self.test_id = "test-id-1234"
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
|
||||
# Create a temporary file for testing
|
||||
self.test_filename = "test_output.txt"
|
||||
self.cleanup_files()
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up any test files"""
|
||||
self.cleanup_files()
|
||||
|
||||
def cleanup_files(self):
|
||||
"""Helper to remove test files"""
|
||||
if os.path.exists(self.test_filename):
|
||||
os.remove(self.test_filename)
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test entry initialization"""
|
||||
script = "echo test"
|
||||
entry = SingleShotEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
self.assertEqual(entry.script, script)
|
||||
self.assertEqual(entry.stdout, "")
|
||||
self.assertEqual(entry.stderr, "")
|
||||
self.assertIsNone(entry.exit_code)
|
||||
self.assertEqual(entry.id, self.test_id)
|
||||
self.assertEqual(entry.timestamp, self.test_timestamp)
|
||||
self.assertFalse(entry._executed)
|
||||
|
||||
def test_successful_execution(self):
|
||||
"""Test successful script execution"""
|
||||
script = "echo 'test'"
|
||||
|
||||
entry = SingleShotEntry(script, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
# Verify entry state
|
||||
self.assertEqual(entry.stdout.strip(), "test")
|
||||
self.assertEqual(entry.stderr, "")
|
||||
self.assertEqual(entry.exit_code, 0)
|
||||
self.assertTrue(entry._executed)
|
||||
|
||||
def test_failed_execution(self):
|
||||
"""Test failed script execution with invalid command"""
|
||||
entry = SingleShotEntry("nonexistentcommand", self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
# Verify entry state
|
||||
self.assertEqual(entry.stdout, "")
|
||||
self.assertTrue(len(entry.stderr) > 0) # Error message will vary by OS
|
||||
self.assertNotEqual(entry.exit_code, 0) # Exit code will be non-zero
|
||||
self.assertTrue(entry._executed)
|
||||
|
||||
def test_file_creation(self):
|
||||
"""Test script that creates a file"""
|
||||
script = f"echo 'test' > {self.test_filename}"
|
||||
|
||||
entry = SingleShotEntry(script, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
# Verify file was created and contains expected content
|
||||
self.assertTrue(os.path.exists(self.test_filename))
|
||||
with open(self.test_filename, 'r') as f:
|
||||
content = f.read().strip()
|
||||
self.assertEqual(content, "test")
|
||||
|
||||
def test_single_execution(self):
|
||||
"""Test that script only executes once"""
|
||||
script = f"echo 'test' >> {self.test_filename}"
|
||||
|
||||
entry = SingleShotEntry(script, self.test_id, self.test_timestamp)
|
||||
|
||||
# Run update multiple times
|
||||
entry.update()
|
||||
entry.update()
|
||||
entry.update()
|
||||
|
||||
# Verify file only contains one line
|
||||
with open(self.test_filename, 'r') as f:
|
||||
lines = f.readlines()
|
||||
self.assertEqual(len(lines), 1)
|
||||
self.assertEqual(lines[0].strip(), "test")
|
||||
|
||||
def test_generate_context_before_execution(self):
|
||||
"""Test XML context generation before execution"""
|
||||
entry = SingleShotEntry("echo test", self.test_id, self.test_timestamp)
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "single_shot")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
self.assertEqual(element.text, "<![CDATA[echo test]]>")
|
||||
|
||||
# Verify no output elements exist
|
||||
self.assertIsNone(element.find("stdout"))
|
||||
self.assertIsNone(element.find("stderr"))
|
||||
self.assertIsNone(element.get("exit_code"))
|
||||
|
||||
def test_generate_context_after_execution(self):
|
||||
"""Test XML context generation after execution"""
|
||||
script = "echo 'test'"
|
||||
|
||||
entry = SingleShotEntry(script, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "single_shot")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
self.assertEqual(element.text, f"<![CDATA[{script}]]>")
|
||||
|
||||
# Get stdout and remove trailing newline for comparison
|
||||
stdout_text = element.find("stdout").text
|
||||
self.assertTrue(stdout_text.startswith("<![CDATA[test"))
|
||||
self.assertTrue(stdout_text.endswith("]]>"))
|
||||
|
||||
self.assertEqual(element.find("stderr").text, "<![CDATA[]]>")
|
||||
self.assertEqual(element.get("exit_code"), "0")
|
||||
|
||||
def test_multiple_commands(self):
|
||||
"""Test executing multiple commands in one script"""
|
||||
script = f"echo 'first' > {self.test_filename} && echo 'second' >> {self.test_filename}"
|
||||
|
||||
entry = SingleShotEntry(script, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
# Verify file contains both lines
|
||||
with open(self.test_filename, 'r') as f:
|
||||
lines = f.readlines()
|
||||
self.assertEqual(len(lines), 2)
|
||||
self.assertEqual(lines[0].strip(), "first")
|
||||
self.assertEqual(lines[1].strip(), "second")
|
||||
180
test/standard_io_buffer_test.py
Normal file
180
test/standard_io_buffer_test.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import unittest
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
class StandardIOBufferTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Create temporary directory for test files"""
|
||||
self.test_dir = tempfile.mkdtemp()
|
||||
self.sia_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
self.write_test_script()
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up temporary test files"""
|
||||
for file in Path(self.test_dir).glob('*'):
|
||||
file.unlink()
|
||||
os.rmdir(self.test_dir)
|
||||
|
||||
def write_test_script(self):
|
||||
"""Create a Python script that uses StandardIOBuffer"""
|
||||
script_path = os.path.join(self.test_dir, 'test_script.py')
|
||||
with open(script_path, 'w', newline='') as f:
|
||||
f.write(f'''
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
# Add the sia package directory to Python path
|
||||
sys.path.insert(0, {repr(self.sia_path)})
|
||||
|
||||
from sia.standard_io_buffer import StandardIOBuffer
|
||||
|
||||
def run_buffer_test(test_type):
|
||||
buffer = StandardIOBuffer()
|
||||
|
||||
if test_type == 'read':
|
||||
content = buffer.read()
|
||||
sys.stdout.buffer.write(f"Read: {{content}}".encode('utf-8'))
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
elif test_type == 'write':
|
||||
buffer.write("Test output")
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
elif test_type == 'multiple_write':
|
||||
buffer.write("Part 1")
|
||||
buffer.write("Part 2")
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
elif test_type == 'buffer_length':
|
||||
buffer._input_buffer = "test"
|
||||
sys.stdout.buffer.write(str(buffer.buffer_length()).encode('utf-8'))
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
elif test_type == 'read_lines':
|
||||
content1 = buffer.read()
|
||||
sys.stdout.buffer.write(f"First read: {{content1}}".encode('utf-8'))
|
||||
sys.stdout.buffer.flush()
|
||||
time.sleep(0.1)
|
||||
content2 = buffer.read()
|
||||
sys.stdout.buffer.write(f"Second read: {{content2}}".encode('utf-8'))
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
elif test_type == 'debug':
|
||||
print("Python path:", sys.path)
|
||||
print("Current directory:", os.getcwd())
|
||||
print("Buffer import successful")
|
||||
print("Python version:", sys.version)
|
||||
print("Platform:", sys.platform)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print("Test type required")
|
||||
sys.exit(1)
|
||||
test_type = sys.argv[1]
|
||||
run_buffer_test(test_type)
|
||||
''')
|
||||
self.script_path = script_path
|
||||
|
||||
def run_script(self, test_type, input_data=None):
|
||||
"""Run test script with given input and return output"""
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, self.script_path, test_type],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env={**os.environ, 'PYTHONPATH': self.sia_path},
|
||||
# Use binary mode for consistent line endings
|
||||
universal_newlines=False
|
||||
)
|
||||
|
||||
try:
|
||||
# Convert input data to bytes if provided
|
||||
input_bytes = input_data.encode('utf-8') if input_data is not None else None
|
||||
stdout_bytes, stderr_bytes = process.communicate(input=input_bytes, timeout=5)
|
||||
|
||||
# Decode output using utf-8, preserving special characters
|
||||
stdout = stdout_bytes.decode('utf-8')
|
||||
stderr = stderr_bytes.decode('utf-8')
|
||||
|
||||
return stdout, stderr, process.returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
stdout_bytes, stderr_bytes = process.communicate()
|
||||
stdout = stdout_bytes.decode('utf-8')
|
||||
stderr = stderr_bytes.decode('utf-8')
|
||||
return stdout, stderr, -1
|
||||
|
||||
def test_debug_environment(self):
|
||||
"""Test that the environment is properly set up"""
|
||||
stdout, stderr, code = self.run_script('debug')
|
||||
self.assertEqual(code, 0, f"Debug script failed with stderr: {stderr}")
|
||||
self.assertIn("Buffer import successful", stdout)
|
||||
|
||||
def test_read_available_input(self):
|
||||
"""Test reading available input without mocks"""
|
||||
stdout, stderr, code = self.run_script('read', 'test input\n')
|
||||
self.assertEqual(code, 0, f"Script failed with stderr: {stderr}")
|
||||
self.assertEqual(stdout, 'Read: test input\n')
|
||||
|
||||
def test_read_no_input(self):
|
||||
"""Test reading when no input is provided"""
|
||||
stdout, stderr, code = self.run_script('read')
|
||||
self.assertEqual(code, 0, f"Script failed with stderr: {stderr}")
|
||||
self.assertEqual(stdout, 'Read: ')
|
||||
|
||||
def test_write_output(self):
|
||||
"""Test writing output without mocks"""
|
||||
stdout, stderr, code = self.run_script('write')
|
||||
self.assertEqual(code, 0, f"Script failed with stderr: {stderr}")
|
||||
self.assertEqual(stdout, 'Test output')
|
||||
|
||||
def test_multiple_writes(self):
|
||||
"""Test multiple write operations"""
|
||||
stdout, stderr, code = self.run_script('multiple_write')
|
||||
self.assertEqual(code, 0, f"Script failed with stderr: {stderr}")
|
||||
self.assertEqual(stdout, 'Part 1Part 2')
|
||||
|
||||
def test_buffer_length(self):
|
||||
"""Test buffer length reporting"""
|
||||
stdout, stderr, code = self.run_script('buffer_length')
|
||||
self.assertEqual(code, 0, f"Script failed with stderr: {stderr}")
|
||||
self.assertEqual(stdout.strip(), '4')
|
||||
|
||||
def test_multiple_reads(self):
|
||||
"""Test multiple read operations with actual input"""
|
||||
input_data = 'line1\nline2\n'
|
||||
stdout, stderr, code = self.run_script('read_lines', input_data)
|
||||
self.assertEqual(code, 0, f"Script failed with stderr: {stderr}")
|
||||
self.assertIn('First read: line1\nline2\n', stdout)
|
||||
self.assertIn('Second read: ', stdout)
|
||||
|
||||
def test_special_characters(self):
|
||||
"""Test handling special characters in input/output"""
|
||||
# Use raw string to ensure exact character representation
|
||||
special_chars = 'Special chars: \n\t\r\'"'
|
||||
stdout, stderr, code = self.run_script('read', special_chars)
|
||||
self.assertEqual(code, 0, f"Script failed with stderr: {stderr}")
|
||||
expected = f'Read: {special_chars}'
|
||||
# Print actual bytes for debugging
|
||||
if stdout != expected:
|
||||
print("\nDebug output:")
|
||||
print("Expected bytes:", expected.encode('utf-8'))
|
||||
print("Actual bytes:", stdout.encode('utf-8'))
|
||||
self.assertEqual(stdout, expected)
|
||||
|
||||
def test_empty_input(self):
|
||||
"""Test handling empty input"""
|
||||
stdout, stderr, code = self.run_script('read', '')
|
||||
self.assertEqual(code, 0, f"Script failed with stderr: {stderr}")
|
||||
self.assertEqual(stdout, 'Read: ')
|
||||
|
||||
def test_large_input(self):
|
||||
"""Test handling large input"""
|
||||
large_input = 'x' * 10000
|
||||
stdout, stderr, code = self.run_script('read', large_input)
|
||||
self.assertEqual(code, 0, f"Script failed with stderr: {stderr}")
|
||||
self.assertEqual(stdout, f'Read: {large_input}')
|
||||
45
test/stop_command_test.py
Normal file
45
test/stop_command_test.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from sia.working_memory import WorkingMemory
|
||||
from sia.stop_command import StopCommand
|
||||
from sia.reasoning_entry import ReasoningEntry
|
||||
from sia.background_entry import BackgroundEntry
|
||||
|
||||
class StopCommandTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with a fresh working memory"""
|
||||
self.memory = WorkingMemory()
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
|
||||
def test_stop_command_cleanup(self):
|
||||
"""Test stop command cleans up and removes all entries"""
|
||||
# Add a mix of entry types including a background process
|
||||
entries = [
|
||||
ReasoningEntry("test content", "id-1", self.test_timestamp),
|
||||
BackgroundEntry("sleep 10", "id-2", self.test_timestamp)
|
||||
]
|
||||
|
||||
for entry in entries:
|
||||
self.memory.add_entry(entry)
|
||||
|
||||
# Start the background process
|
||||
background_entry = self.memory.get_entry("id-2")
|
||||
background_entry.update()
|
||||
self.assertIsNotNone(background_entry.process)
|
||||
process_pid = background_entry.process.pid
|
||||
|
||||
# Execute stop command
|
||||
command = StopCommand()
|
||||
result = command.execute(self.memory)
|
||||
|
||||
# Verify all entries removed
|
||||
self.assertTrue(result.should_stop)
|
||||
self.assertEqual(self.memory.get_entries_count(), 0)
|
||||
|
||||
# Verify background process was terminated
|
||||
time.sleep(0.1) # Give process time to terminate
|
||||
with self.assertRaises(ProcessLookupError):
|
||||
import os
|
||||
os.kill(process_pid, 0)
|
||||
@@ -1,8 +0,0 @@
|
||||
import unittest
|
||||
|
||||
class SequentialTestLoader(unittest.TestLoader):
|
||||
def getTestCaseNames(self, testCaseClass):
|
||||
test_names = super().getTestCaseNames(testCaseClass)
|
||||
testcase_methods = list(testCaseClass.__dict__.keys())
|
||||
test_names.sort(key=testcase_methods.index)
|
||||
return test_names
|
||||
@@ -1,17 +1,118 @@
|
||||
import unittest
|
||||
from typing import Iterator
|
||||
|
||||
from . import test_data
|
||||
|
||||
from sia import util
|
||||
|
||||
class UtilTest(unittest.TestCase):
|
||||
def test_get_valid_root_elements_single(self):
|
||||
valid_elements = util.get_valid_root_elements(test_data.echo_action_schema)
|
||||
self.assertEqual(valid_elements, {'test_tag'})
|
||||
def test_stop_before_value(self):
|
||||
# Helper function to create iterator from list
|
||||
def create_iterator(items: list) -> Iterator[str]:
|
||||
for item in items:
|
||||
yield item
|
||||
|
||||
def test_split_response_single_element(self):
|
||||
response = "Some reasoning here\n<test_tag>content</test_tag>"
|
||||
valid_elements = {'test_tag'}
|
||||
result = util.split_response(response, valid_elements)
|
||||
self.assertEqual(result.reasoning, "Some reasoning here")
|
||||
self.assertEqual(result.actions, "<test_tag>content</test_tag>")
|
||||
# Test case 1: Stop value in middle of sequence
|
||||
input_sequence = ['hello', 'world', 'STOP', 'ignored']
|
||||
result = list(util.stop_before_value(create_iterator(input_sequence), 'STOP'))
|
||||
self.assertEqual(result, ['hello', 'world'])
|
||||
|
||||
# Test case 2: Stop value not in sequence
|
||||
input_sequence = ['hello', 'world']
|
||||
result = list(util.stop_before_value(create_iterator(input_sequence), 'STOP'))
|
||||
self.assertEqual(result, ['hello', 'world'])
|
||||
|
||||
# Test case 3: Stop value at start of sequence
|
||||
input_sequence = ['STOP', 'ignored']
|
||||
result = list(util.stop_before_value(create_iterator(input_sequence), 'STOP'))
|
||||
self.assertEqual(result, [])
|
||||
|
||||
# Test case 4: Stop value as part of an item
|
||||
input_sequence = ['hello', 'woSTOPrld', 'ignored']
|
||||
result = list(util.stop_before_value(create_iterator(input_sequence), 'STOP'))
|
||||
self.assertEqual(result, ['hello', 'wo'])
|
||||
|
||||
# Test case 5: Empty sequence
|
||||
input_sequence = []
|
||||
result = list(util.stop_before_value(create_iterator(input_sequence), 'STOP'))
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_none_value(self):
|
||||
"""Test that None values are converted to empty strings"""
|
||||
self.assertEqual(util.escape_text_for_xml(None), '')
|
||||
|
||||
def test_number_values(self):
|
||||
"""Test that numbers are properly converted to strings and wrapped in CDATA"""
|
||||
self.assertEqual(util.escape_text_for_xml(42), '<![CDATA[42]]>')
|
||||
self.assertEqual(util.escape_text_for_xml(3.14), '<![CDATA[3.14]]>')
|
||||
self.assertEqual(util.escape_text_for_xml(-100), '<![CDATA[-100]]>')
|
||||
self.assertEqual(util.escape_text_for_xml(0), '<![CDATA[0]]>')
|
||||
|
||||
def test_simple_strings(self):
|
||||
"""Test that simple strings are wrapped in CDATA"""
|
||||
self.assertEqual(util.escape_text_for_xml('hello'), '<![CDATA[hello]]>')
|
||||
self.assertEqual(util.escape_text_for_xml(''), '<![CDATA[]]>')
|
||||
self.assertEqual(util.escape_text_for_xml(' '), '<![CDATA[ ]]>')
|
||||
|
||||
def test_strings_with_special_characters(self):
|
||||
"""Test strings containing XML special characters but no CDATA end sequence"""
|
||||
self.assertEqual(util.escape_text_for_xml('<hello>'), '<![CDATA[<hello>]]>')
|
||||
self.assertEqual(util.escape_text_for_xml('a & b'), '<![CDATA[a & b]]>')
|
||||
self.assertEqual(util.escape_text_for_xml('"quoted"'), '<![CDATA["quoted"]]>')
|
||||
self.assertEqual(util.escape_text_for_xml("'single'"), "<![CDATA['single']]>")
|
||||
self.assertEqual(util.escape_text_for_xml('<![CDATA['), '<![CDATA[<![CDATA[]]>')
|
||||
|
||||
def test_strings_with_cdata_end_sequence(self):
|
||||
"""Test strings containing CDATA end sequence are properly escaped"""
|
||||
# Simple case with just the CDATA end sequence
|
||||
self.assertEqual(util.escape_text_for_xml(']]>'), ']]>')
|
||||
|
||||
# CDATA end sequence with other special characters
|
||||
self.assertEqual(
|
||||
util.escape_text_for_xml('Text with ]]> and <tags> & ampersands'),
|
||||
'Text with ]]> and <tags> & ampersands'
|
||||
)
|
||||
|
||||
# Multiple CDATA end sequences
|
||||
self.assertEqual(
|
||||
util.escape_text_for_xml('Multiple ]]> end ]]> sequences'),
|
||||
'Multiple ]]> end ]]> sequences'
|
||||
)
|
||||
|
||||
def test_multiline_strings(self):
|
||||
"""Test multiline strings are properly handled"""
|
||||
multiline = """Line 1
|
||||
Line 2
|
||||
Line 3"""
|
||||
self.assertEqual(util.escape_text_for_xml(multiline), '<![CDATA[Line 1\n Line 2\n Line 3]]>')
|
||||
|
||||
multiline_with_cdata = """Line 1
|
||||
Line ]]> 2
|
||||
Line 3"""
|
||||
self.assertEqual(
|
||||
util.escape_text_for_xml(multiline_with_cdata),
|
||||
'Line 1\n Line ]]> 2\n Line 3'
|
||||
)
|
||||
|
||||
def test_edge_cases(self):
|
||||
"""Test edge cases and unusual inputs"""
|
||||
# Empty string
|
||||
self.assertEqual(util.escape_text_for_xml(''), '<![CDATA[]]>')
|
||||
|
||||
# String with only whitespace
|
||||
self.assertEqual(util.escape_text_for_xml('\n\t '), '<![CDATA[\n\t ]]>')
|
||||
|
||||
# String with Unicode characters
|
||||
self.assertEqual(util.escape_text_for_xml('Hello 世界'), '<![CDATA[Hello 世界]]>')
|
||||
|
||||
# String with control characters
|
||||
self.assertEqual(util.escape_text_for_xml('Hello\0World'), '<![CDATA[Hello\0World]]>')
|
||||
|
||||
# Very long string
|
||||
long_string = 'x' * 1000
|
||||
self.assertEqual(util.escape_text_for_xml(long_string), f'<![CDATA[{long_string}]]>')
|
||||
|
||||
def test_boolean_values(self):
|
||||
"""Test boolean values are properly converted"""
|
||||
self.assertEqual(util.escape_text_for_xml(True), '<![CDATA[True]]>')
|
||||
self.assertEqual(util.escape_text_for_xml(False), '<![CDATA[False]]>')
|
||||
106
test/web_io_buffer_test.py
Normal file
106
test/web_io_buffer_test.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import unittest
|
||||
|
||||
from sia.web_io_buffer import WebIOBuffer
|
||||
|
||||
class WebIOBufferTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up a fresh WebIOBuffer for each test."""
|
||||
self.buffer = WebIOBuffer()
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test initial state of buffer"""
|
||||
self.assertEqual(self.buffer.buffer_length(), 0)
|
||||
self.assertEqual(self.buffer.get_stdout(), "")
|
||||
self.assertEqual(self.buffer.read(), "")
|
||||
|
||||
def test_append_stdin(self):
|
||||
"""Test appending to stdin buffer"""
|
||||
test_input = "test input"
|
||||
self.buffer.append_stdin(test_input)
|
||||
self.assertEqual(self.buffer.buffer_length(), len(test_input))
|
||||
|
||||
def test_read_clears_buffer(self):
|
||||
"""Test that reading clears the stdin buffer"""
|
||||
test_input = "test input"
|
||||
self.buffer.append_stdin(test_input)
|
||||
content = self.buffer.read()
|
||||
self.assertEqual(content, test_input)
|
||||
self.assertEqual(self.buffer.buffer_length(), 0)
|
||||
self.assertEqual(self.buffer.read(), "")
|
||||
|
||||
def test_write_output(self):
|
||||
"""Test writing to stdout buffer"""
|
||||
test_output = "test output"
|
||||
self.buffer.write(test_output)
|
||||
self.assertEqual(self.buffer.get_stdout(), test_output)
|
||||
|
||||
def test_multiple_writes(self):
|
||||
"""Test multiple write operations"""
|
||||
self.buffer.write("Part 1")
|
||||
self.buffer.write("Part 2")
|
||||
self.assertEqual(self.buffer.get_stdout(), "Part 1Part 2")
|
||||
|
||||
def test_clear_stdout(self):
|
||||
"""Test clearing stdout buffer"""
|
||||
self.buffer.write("test output")
|
||||
self.buffer.clear_stdout()
|
||||
self.assertEqual(self.buffer.get_stdout(), "")
|
||||
|
||||
def test_multiple_stdin_appends(self):
|
||||
"""Test multiple stdin append operations"""
|
||||
self.buffer.append_stdin("Line 1\n")
|
||||
self.buffer.append_stdin("Line 2\n")
|
||||
content = self.buffer.read()
|
||||
self.assertEqual(content, "Line 1\nLine 2\n")
|
||||
|
||||
def test_empty_write(self):
|
||||
"""Test writing empty content"""
|
||||
self.buffer.write("")
|
||||
self.assertEqual(self.buffer.get_stdout(), "")
|
||||
|
||||
def test_empty_append_stdin(self):
|
||||
"""Test appending empty content to stdin"""
|
||||
self.buffer.append_stdin("")
|
||||
self.assertEqual(self.buffer.buffer_length(), 0)
|
||||
|
||||
def test_special_characters(self):
|
||||
"""Test handling special characters"""
|
||||
special_chars = "Special chars: \n\t\r\'\"\\"
|
||||
self.buffer.append_stdin(special_chars)
|
||||
content = self.buffer.read()
|
||||
self.assertEqual(content, special_chars)
|
||||
|
||||
self.buffer.write(special_chars)
|
||||
self.assertEqual(self.buffer.get_stdout(), special_chars)
|
||||
|
||||
def test_large_content(self):
|
||||
"""Test handling large content"""
|
||||
large_input = "x" * 10000
|
||||
self.buffer.append_stdin(large_input)
|
||||
content = self.buffer.read()
|
||||
self.assertEqual(content, large_input)
|
||||
|
||||
self.buffer.write(large_input)
|
||||
self.assertEqual(self.buffer.get_stdout(), large_input)
|
||||
|
||||
def test_multiple_operations(self):
|
||||
"""Test mixed read/write operations"""
|
||||
# Write some output
|
||||
self.buffer.write("Output 1\n")
|
||||
self.buffer.write("Output 2\n")
|
||||
|
||||
# Add some input
|
||||
self.buffer.append_stdin("Input 1\n")
|
||||
self.buffer.append_stdin("Input 2\n")
|
||||
|
||||
# Verify stdout accumulated correctly
|
||||
self.assertEqual(self.buffer.get_stdout(), "Output 1\nOutput 2\n")
|
||||
|
||||
# Read input and verify it's cleared
|
||||
content = self.buffer.read()
|
||||
self.assertEqual(content, "Input 1\nInput 2\n")
|
||||
self.assertEqual(self.buffer.buffer_length(), 0)
|
||||
|
||||
# Clear stdout and verify
|
||||
self.buffer.clear_stdout()
|
||||
self.assertEqual(self.buffer.get_stdout(), "")
|
||||
246
test/working_memory_test.py
Normal file
246
test/working_memory_test.py
Normal file
@@ -0,0 +1,246 @@
|
||||
from datetime import datetime
|
||||
import time
|
||||
import unittest
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from sia.background_entry import BackgroundEntry
|
||||
from sia.entry import Entry
|
||||
from sia.parse_error_entry import ParseErrorEntry
|
||||
from sia.read_entry import ReadEntry
|
||||
from sia.reasoning_entry import ReasoningEntry
|
||||
from sia.web_io_buffer import WebIOBuffer
|
||||
from sia.working_memory import WorkingMemory
|
||||
from sia.write_entry import WriteEntry
|
||||
|
||||
class MockEntry(Entry):
|
||||
"""Mock entry class for testing"""
|
||||
def __init__(self, id: str, timestamp: datetime):
|
||||
super().__init__(id, timestamp)
|
||||
self.updated = False
|
||||
|
||||
def update(self) -> None:
|
||||
self.updated = True
|
||||
|
||||
def generate_context(self) -> ET.Element:
|
||||
elem = ET.Element("mock", {"id": self.id})
|
||||
elem.text = "<![CDATA[mock content]]>"
|
||||
return elem
|
||||
|
||||
class WorkingMemoryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with a fresh working memory"""
|
||||
self.memory = WorkingMemory()
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test initial state of working memory"""
|
||||
self.assertEqual(self.memory.get_entries_count(), 0)
|
||||
self.assertEqual(self.memory.generate_context(), [])
|
||||
|
||||
def test_add_entry(self):
|
||||
"""Test adding entries"""
|
||||
entry = MockEntry("test-id-1", self.test_timestamp)
|
||||
self.memory.add_entry(entry)
|
||||
|
||||
self.assertEqual(self.memory.get_entries_count(), 1)
|
||||
self.assertEqual(self.memory.get_entry("test-id-1"), entry)
|
||||
|
||||
def test_add_invalid_entry(self):
|
||||
"""Test adding invalid entry type"""
|
||||
with self.assertRaises(TypeError):
|
||||
self.memory.add_entry("not an entry")
|
||||
|
||||
def test_remove_entry(self):
|
||||
"""Test removing entries"""
|
||||
entry1 = MockEntry("test-id-1", self.test_timestamp)
|
||||
entry2 = MockEntry("test-id-2", self.test_timestamp)
|
||||
|
||||
self.memory.add_entry(entry1)
|
||||
self.memory.add_entry(entry2)
|
||||
self.memory.remove_entry("test-id-1")
|
||||
|
||||
self.assertEqual(self.memory.get_entries_count(), 1)
|
||||
self.assertIsNone(self.memory.get_entry("test-id-1"))
|
||||
self.assertEqual(self.memory.get_entry("test-id-2"), entry2)
|
||||
|
||||
def test_remove_nonexistent_entry(self):
|
||||
"""Test removing entry that doesn't exist"""
|
||||
entry = MockEntry("test-id-1", self.test_timestamp)
|
||||
self.memory.add_entry(entry)
|
||||
self.memory.remove_entry("nonexistent-id")
|
||||
|
||||
self.assertEqual(self.memory.get_entries_count(), 1)
|
||||
self.assertEqual(self.memory.get_entry("test-id-1"), entry)
|
||||
|
||||
def test_update_entries(self):
|
||||
"""Test updating all entries"""
|
||||
entries = [
|
||||
MockEntry(f"test-id-{i}", self.test_timestamp)
|
||||
for i in range(3)
|
||||
]
|
||||
|
||||
for entry in entries:
|
||||
self.memory.add_entry(entry)
|
||||
|
||||
self.memory.update()
|
||||
|
||||
for entry in entries:
|
||||
self.assertTrue(entry.updated)
|
||||
|
||||
def test_generate_context(self):
|
||||
"""Test generating XML context"""
|
||||
entry1 = MockEntry("test-id-1", self.test_timestamp)
|
||||
entry2 = MockEntry("test-id-2", self.test_timestamp)
|
||||
|
||||
self.memory.add_entry(entry1)
|
||||
self.memory.add_entry(entry2)
|
||||
|
||||
context = self.memory.generate_context()
|
||||
|
||||
self.assertEqual(len(context), 2)
|
||||
self.assertEqual(context[0].tag, "mock")
|
||||
self.assertEqual(context[0].get("id"), "test-id-1")
|
||||
self.assertEqual(context[1].tag, "mock")
|
||||
self.assertEqual(context[1].get("id"), "test-id-2")
|
||||
|
||||
def test_get_entries_by_type(self):
|
||||
"""Test retrieving entries by type"""
|
||||
# Create IO buffer for IO entries
|
||||
io_buffer = WebIOBuffer()
|
||||
|
||||
# Add different types of entries
|
||||
entries = [
|
||||
ReasoningEntry("test reasoning", "reasoning-1", self.test_timestamp),
|
||||
ParseErrorEntry("bad content", "error msg", "error-1", self.test_timestamp),
|
||||
ReadEntry(io_buffer, "read-1", self.test_timestamp),
|
||||
WriteEntry("test output", io_buffer, "write-1", self.test_timestamp)
|
||||
]
|
||||
|
||||
for entry in entries:
|
||||
self.memory.add_entry(entry)
|
||||
|
||||
# Test filtering by each type
|
||||
reasoning_entries = self.memory.get_entries_by_type(ReasoningEntry)
|
||||
self.assertEqual(len(reasoning_entries), 1)
|
||||
self.assertIsInstance(reasoning_entries[0], ReasoningEntry)
|
||||
|
||||
error_entries = self.memory.get_entries_by_type(ParseErrorEntry)
|
||||
self.assertEqual(len(error_entries), 1)
|
||||
self.assertIsInstance(error_entries[0], ParseErrorEntry)
|
||||
|
||||
read_entries = self.memory.get_entries_by_type(ReadEntry)
|
||||
self.assertEqual(len(read_entries), 1)
|
||||
self.assertIsInstance(read_entries[0], ReadEntry)
|
||||
|
||||
write_entries = self.memory.get_entries_by_type(WriteEntry)
|
||||
self.assertEqual(len(write_entries), 1)
|
||||
self.assertIsInstance(write_entries[0], WriteEntry)
|
||||
|
||||
def test_empty_context_generation(self):
|
||||
"""Test context generation with no entries"""
|
||||
context = self.memory.generate_context()
|
||||
self.assertEqual(context, [])
|
||||
|
||||
def test_get_nonexistent_entry(self):
|
||||
"""Test retrieving entry that doesn't exist"""
|
||||
self.assertIsNone(self.memory.get_entry("nonexistent-id"))
|
||||
|
||||
def test_remove_entry_cleanup(self):
|
||||
"""Test that removing an entry triggers cleanup"""
|
||||
# Create a background process entry
|
||||
entry = BackgroundEntry("sleep 10", "test-id", datetime(2024, 10, 31, 12, 0, 0))
|
||||
self.memory.add_entry(entry)
|
||||
|
||||
# Start the process
|
||||
entry.update()
|
||||
self.assertIsNotNone(entry.process)
|
||||
process_pid = entry.process.pid
|
||||
|
||||
# Remove entry should trigger cleanup
|
||||
self.memory.remove_entry("test-id")
|
||||
|
||||
# Verify process was terminated
|
||||
time.sleep(0.1) # Give process time to terminate
|
||||
with self.assertRaises(ProcessLookupError):
|
||||
import os
|
||||
os.kill(process_pid, 0)
|
||||
|
||||
def test_cleanup_on_memory_clear(self):
|
||||
"""Test that clearing memory properly cleans up all entries"""
|
||||
# Add multiple background processes
|
||||
processes = []
|
||||
for i in range(3):
|
||||
entry = BackgroundEntry(
|
||||
"sleep 10",
|
||||
f"id-{i}",
|
||||
datetime(2024, 10, 31, 12, 0, 0)
|
||||
)
|
||||
self.memory.add_entry(entry)
|
||||
entry.update()
|
||||
processes.append(entry.process.pid)
|
||||
|
||||
# Clear memory
|
||||
self.memory.clear()
|
||||
|
||||
# Verify all processes were terminated
|
||||
time.sleep(0.1) # Give processes time to terminate
|
||||
for pid in processes:
|
||||
with self.assertRaises(ProcessLookupError):
|
||||
import os
|
||||
os.kill(pid, 0)
|
||||
|
||||
self.assertEqual(self.memory.get_entries_count(), 0)
|
||||
|
||||
def test_cleanup_on_memory_deletion(self):
|
||||
"""Test that deleting memory properly cleans up all entries"""
|
||||
# Add a background process
|
||||
entry = BackgroundEntry(
|
||||
"sleep 10",
|
||||
"test-id",
|
||||
datetime(2024, 10, 31, 12, 0, 0)
|
||||
)
|
||||
self.memory.add_entry(entry)
|
||||
entry.update()
|
||||
process_pid = entry.process.pid
|
||||
|
||||
# Delete memory
|
||||
del self.memory
|
||||
|
||||
# Verify process was terminated
|
||||
time.sleep(0.1) # Give process time to terminate
|
||||
with self.assertRaises(ProcessLookupError):
|
||||
import os
|
||||
os.kill(process_pid, 0)
|
||||
|
||||
def test_get_entries(self):
|
||||
"""Test getting all entries"""
|
||||
# Add multiple entries
|
||||
entries = [
|
||||
ReasoningEntry(f"content {i}", f"id-{i}", self.test_timestamp)
|
||||
for i in range(3)
|
||||
]
|
||||
|
||||
for entry in entries:
|
||||
self.memory.add_entry(entry)
|
||||
|
||||
# Get all entries
|
||||
retrieved_entries = self.memory.get_entries()
|
||||
|
||||
# Verify count and contents
|
||||
self.assertEqual(len(retrieved_entries), len(entries))
|
||||
for entry in entries:
|
||||
self.assertIn(entry, retrieved_entries)
|
||||
|
||||
def test_get_entries_returns_copy(self):
|
||||
"""Test that get_entries returns a copy of the list"""
|
||||
# Add an entry
|
||||
entry = ReasoningEntry("test content", "test-id", self.test_timestamp)
|
||||
self.memory.add_entry(entry)
|
||||
|
||||
# Get entries and modify the returned list
|
||||
entries = self.memory.get_entries()
|
||||
entries.clear()
|
||||
|
||||
# Verify original memory is unchanged
|
||||
self.assertEqual(self.memory.get_entries_count(), 1)
|
||||
self.assertIsNotNone(self.memory.get_entry("test-id"))
|
||||
117
test/write_entry_test.py
Normal file
117
test/write_entry_test.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
from sia.web_io_buffer import WebIOBuffer
|
||||
from sia.write_entry import WriteEntry
|
||||
|
||||
class WriteEntryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test cases with fixed id, timestamp, and IO buffer"""
|
||||
self.test_id = "test-id-1234"
|
||||
self.test_timestamp = datetime(2024, 10, 31, 12, 0, 0)
|
||||
self.io_buffer = WebIOBuffer()
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test entry initialization"""
|
||||
content = "test message"
|
||||
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
|
||||
|
||||
self.assertEqual(entry.content, content)
|
||||
self.assertEqual(entry.id, self.test_id)
|
||||
self.assertEqual(entry.timestamp, self.test_timestamp)
|
||||
self.assertFalse(entry._written)
|
||||
self.assertEqual(self.io_buffer.get_stdout(), "")
|
||||
|
||||
def test_single_write(self):
|
||||
"""Test writing content once"""
|
||||
content = "test message"
|
||||
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
|
||||
|
||||
# Perform update and verify content was written
|
||||
entry.update()
|
||||
self.assertEqual(self.io_buffer.get_stdout(), content)
|
||||
self.assertTrue(entry._written)
|
||||
|
||||
def test_multiple_updates(self):
|
||||
"""Test that content is only written once even with multiple updates"""
|
||||
content = "test message"
|
||||
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
|
||||
|
||||
# Perform multiple updates
|
||||
entry.update()
|
||||
initial_output = self.io_buffer.get_stdout()
|
||||
self.io_buffer.clear_stdout()
|
||||
|
||||
entry.update()
|
||||
entry.update()
|
||||
|
||||
# Verify no additional content was written
|
||||
self.assertEqual(self.io_buffer.get_stdout(), "")
|
||||
self.assertTrue(entry._written)
|
||||
self.assertEqual(initial_output, content)
|
||||
|
||||
def test_empty_content(self):
|
||||
"""Test writing empty content"""
|
||||
entry = WriteEntry("", self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(self.io_buffer.get_stdout(), "")
|
||||
self.assertTrue(entry._written)
|
||||
|
||||
def test_special_characters(self):
|
||||
"""Test writing content with special characters"""
|
||||
content = "Special chars: \n\t\r\'\"\\"
|
||||
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(self.io_buffer.get_stdout(), content)
|
||||
|
||||
def test_large_content(self):
|
||||
"""Test writing large content"""
|
||||
content = "x" * 10000
|
||||
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(self.io_buffer.get_stdout(), content)
|
||||
|
||||
def test_generate_context(self):
|
||||
"""Test XML context generation"""
|
||||
content = "test message"
|
||||
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
|
||||
|
||||
# Generate context before writing
|
||||
element = entry.generate_context()
|
||||
|
||||
# Verify XML structure
|
||||
self.assertEqual(element.tag, "write_stdout")
|
||||
self.assertEqual(element.get("id"), self.test_id)
|
||||
self.assertEqual(element.text, f"<![CDATA[{content}]]>")
|
||||
|
||||
# Write content and verify context remains the same
|
||||
entry.update()
|
||||
element_after = entry.generate_context()
|
||||
self.assertEqual(element_after.tag, "write_stdout")
|
||||
self.assertEqual(element_after.get("id"), self.test_id)
|
||||
self.assertEqual(element_after.text, f"<![CDATA[{content}]]>")
|
||||
|
||||
def test_unicode_content(self):
|
||||
"""Test writing Unicode content"""
|
||||
content = "Hello 世界 😊"
|
||||
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(self.io_buffer.get_stdout(), content)
|
||||
|
||||
def test_multiline_content(self):
|
||||
"""Test writing multiline content"""
|
||||
content = """Line 1
|
||||
Line 2
|
||||
Line 3"""
|
||||
entry = WriteEntry(content, self.io_buffer, self.test_id, self.test_timestamp)
|
||||
entry.update()
|
||||
|
||||
self.assertEqual(self.io_buffer.get_stdout(), content)
|
||||
|
||||
# Verify XML escaping for multiline content
|
||||
element = entry.generate_context()
|
||||
self.assertEqual(element.text, f"<![CDATA[{content}]]>")
|
||||
116
test/xml_validator_test.py
Normal file
116
test/xml_validator_test.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import unittest
|
||||
|
||||
from sia.xml_validator import XMLValidator
|
||||
|
||||
class XMLValidatorTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test schema"""
|
||||
self.test_schema = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="test_element">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute name="id" type="xs:string" use="required"/>
|
||||
<xs:attribute name="count" type="xs:integer"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="simple_element">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string"/>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>"""
|
||||
|
||||
self.validator = XMLValidator(self.test_schema)
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test validator initialization and root element extraction"""
|
||||
valid_elements = self.validator.get_valid_root_elements()
|
||||
self.assertEqual(valid_elements, {'test_element', 'simple_element'})
|
||||
|
||||
def test_invalid_schema(self):
|
||||
"""Test handling invalid schema"""
|
||||
with self.assertRaises(ValueError):
|
||||
XMLValidator("invalid schema")
|
||||
|
||||
def test_valid_xml(self):
|
||||
"""Test validation of valid XML"""
|
||||
xml = '<test_element id="123" count="42">test content</test_element>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test without optional attribute
|
||||
xml = '<test_element id="123">test content</test_element>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test simple element
|
||||
xml = '<simple_element>test content</simple_element>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_invalid_root_element(self):
|
||||
"""Test validation with invalid root element"""
|
||||
xml = '<invalid_element>test content</invalid_element>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertIn("Invalid root element", result)
|
||||
|
||||
def test_missing_required_attribute(self):
|
||||
"""Test validation with missing required attribute"""
|
||||
xml = '<test_element>test content</test_element>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertIn("Missing required attribute", result)
|
||||
|
||||
def test_invalid_integer_attribute(self):
|
||||
"""Test validation with invalid integer attribute"""
|
||||
xml = '<test_element id="123" count="not_a_number">test content</test_element>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertIn("Invalid integer value", result)
|
||||
|
||||
def test_unexpected_attribute(self):
|
||||
"""Test validation with unexpected attribute"""
|
||||
xml = '<test_element id="123" invalid_attr="value">test content</test_element>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertIn("Unexpected attribute", result)
|
||||
|
||||
def test_invalid_xml_syntax(self):
|
||||
"""Test validation with invalid XML syntax"""
|
||||
xml = '<test_element>unclosed tag'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertIn("Invalid XML", result)
|
||||
|
||||
def test_whitespace_handling(self):
|
||||
"""Test validation with whitespace in XML"""
|
||||
xml = """
|
||||
<test_element id="123">
|
||||
test content
|
||||
</test_element>
|
||||
"""
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_empty_content(self):
|
||||
"""Test validation with empty element content"""
|
||||
xml = '<test_element id="123"></test_element>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNone(result)
|
||||
|
||||
xml = '<test_element id="123"/>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_special_characters(self):
|
||||
"""Test validation with special characters"""
|
||||
xml = '<test_element id="123"><![CDATA[Special & < > " \' chars]]></test_element>'
|
||||
result = self.validator.validate(xml)
|
||||
self.assertIsNone(result)
|
||||
Reference in New Issue
Block a user