import unittest import os import time from typing import Optional from sia.entry.background_entry import BackgroundEntry class BackgroundEntryTest(unittest.TestCase): def setUp(self): """Set up test cases with fixed id""" self.test_id = "test-id-1234" # 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""" 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_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 time.time() - start_time < timeout: entry.update() context = entry.generate_context() if condition(context): return True time.sleep(0.1) return False def test_initialization(self): """Test entry initialization""" entry = BackgroundEntry(self.test_id, os.getcwd(), "echo test") context = entry.generate_context() # Initial context should have script but no pid or exit_code self.assertEqual(context.text, "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""" entry = BackgroundEntry(self.test_id, os.getcwd(), "echo 'test'") # 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 context = entry.generate_context() self.assertEqual(context.find("stdout").text, "test\n") self.assertEqual(context.find("stderr").text, "") def test_continuous_output(self): """Test process that generates continuous output""" 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(self.test_id, os.getcwd(), script) # 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(self.test_id, os.getcwd(), "nonexistentcommand") # Wait for process to fail def has_failed(ctx): return ctx.get("exit_code") is not None and ctx.get("exit_code") != "0" self.assertTrue(self.wait_for_condition(entry, has_failed)) # Verify error captured context = entry.generate_context() self.assertTrue(len(context.find("stderr").text) > 0) self.assertEqual(context.find("stdout").text, "") def test_cleanup_running_process(self): """Test cleanup of running process""" entry = BackgroundEntry(self.test_id, os.getcwd(), "sleep 10") # 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")) # Clean up and verify process terminated entry.cleanup() # Process should no longer exist time.sleep(0.1) # Give process time to terminate with self.assertRaises(ProcessLookupError): os.kill(pid, 0) def test_cleanup_completed_process(self): """Test cleanup of already completed process""" entry = BackgroundEntry(self.test_id, os.getcwd(), "echo test") # 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 entry.cleanup() def test_multiple_cleanup_calls(self): """Test that multiple cleanup calls are safe""" entry = BackgroundEntry(self.test_id, os.getcwd(), "sleep 10") # 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() # Process should no longer exist time.sleep(0.1) with self.assertRaises(ProcessLookupError): os.kill(pid, 0)