Private instance vars, public get properties

This commit is contained in:
2024-11-01 15:45:54 +01:00
parent d80171de15
commit a95c9676b4
18 changed files with 429 additions and 427 deletions

View File

@@ -2,8 +2,7 @@ import unittest
from datetime import datetime
import os
import time
import signal
import platform
from typing import Optional
from sia.background_entry import BackgroundEntry
@@ -19,7 +18,7 @@ class BackgroundEntryTest(unittest.TestCase):
self.cleanup_files()
def tearDown(self):
"""Clean up test files and ensure no processes are left running"""
"""Clean up test files"""
self.cleanup_files()
def cleanup_files(self):
@@ -27,53 +26,45 @@ class BackgroundEntryTest(unittest.TestCase):
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"""
def wait_for_condition(self, entry: BackgroundEntry, condition: callable, timeout: float = 1.0) -> bool:
"""Wait for a condition on the entry context to be met"""
start_time = time.time()
while entry.process is None and time.time() - start_time < timeout:
while time.time() - start_time < timeout:
entry.update()
context = entry.generate_context()
if condition(context):
return True
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")
return False
def test_initialization(self):
"""Test entry initialization"""
script = "echo test"
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
entry = BackgroundEntry("echo test", self.test_id, self.test_timestamp)
context = entry.generate_context()
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)
# Initial context should have script but no pid or exit_code
self.assertEqual(context.text, "<![CDATA[echo test]]>")
self.assertIsNone(context.get("pid"))
self.assertIsNone(context.get("exit_code"))
def test_short_running_process(self):
"""Test execution of a quick process"""
script = "echo 'test'"
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
entry = BackgroundEntry("echo 'test'", 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)
# Wait for process to complete
def is_complete(ctx):
return ctx.get("exit_code") == "0"
self.assertTrue(self.wait_for_condition(entry, is_complete))
# Verify output
self.assertEqual(entry._accumulated_stdout.strip(), "test")
self.assertEqual(entry._accumulated_stderr, "")
self.assertEqual(entry._exit_code, 0)
context = entry.generate_context()
self.assertTrue("<![CDATA[test" in context.find("stdout").text)
self.assertEqual(context.find("stderr").text, "<![CDATA[]]>")
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'
@@ -85,143 +76,83 @@ class BackgroundEntryTest(unittest.TestCase):
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")
# Wait for process to complete
def has_all_output(ctx):
stdout = ctx.find("stdout").text
return all(f"count {i}" in stdout for i in range(1, 4))
self.assertTrue(self.wait_for_condition(entry, has_all_output))
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"
# Wait for process to fail
def has_failed(ctx):
return ctx.get("exit_code") is not None and ctx.get("exit_code") != "0"
entry = BackgroundEntry(script, self.test_id, self.test_timestamp)
entry.update() # Start process
self.wait_for_process_start(entry)
self.assertTrue(self.wait_for_condition(entry, has_failed))
element = entry.generate_context()
# Verify error captured
context = entry.generate_context()
self.assertTrue(len(context.find("stderr").text) > 0)
self.assertEqual(context.find("stdout").text, "<![CDATA[]]>")
# 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)
entry = BackgroundEntry("sleep 10", self.test_id, self.test_timestamp)
# Start the process
entry.update()
self.assertIsNotNone(entry.process)
process_pid = entry.process.pid
# Wait for process to start
def has_started(ctx):
return ctx.get("pid") is not None
# Cleanup should terminate process
self.assertTrue(self.wait_for_condition(entry, has_started))
# Get PID before cleanup
context = entry.generate_context()
pid = int(context.get("pid"))
# Clean up and verify process terminated
entry.cleanup()
# Verify process was terminated
self.assertIsNone(entry.process)
# Process should no longer exist
time.sleep(0.1) # Give process time to terminate
with self.assertRaises(ProcessLookupError):
import os
os.kill(process_pid, 0)
os.kill(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)
entry = BackgroundEntry("echo test", self.test_id, self.test_timestamp)
# Run and wait for completion
entry.update()
self.wait_for_process_exit(entry)
# Wait for completion
def is_complete(ctx):
return ctx.get("exit_code") == "0"
self.assertTrue(self.wait_for_condition(entry, is_complete))
# Cleanup should be safe for completed process
# Cleanup should be safe
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)
entry = BackgroundEntry("sleep 10", self.test_id, self.test_timestamp)
# Start process
entry.update()
self.assertIsNotNone(entry.process)
# Wait for process to start
def has_started(ctx):
return ctx.get("pid") is not None
self.assertTrue(self.wait_for_condition(entry, has_started))
# Get PID before cleanup
context = entry.generate_context()
pid = int(context.get("pid"))
# Multiple cleanups should be safe
entry.cleanup()
entry.cleanup()
entry.cleanup()
self.assertIsNone(entry.process)
# Process should no longer exist
time.sleep(0.1)
with self.assertRaises(ProcessLookupError):
os.kill(pid, 0)