#!/usr/bin/env python3 """Tests for GlyphOS Event System Tests verify: - EventBus subscription and publishing - Event history and filtering - Global event bus singleton - Functional API (on, emit, get_event_bus) - Cognitive Kernel event integration """ import sys import tempfile from pathlib import Path sys.path.insert(0, str(Path.cwd())) from glyphos.events import ( EventBus, get_event_bus, emit, on, Event, EventType, ) from glyphos.cognitive_kernel import get_kernel, run_gx from gx_cli.main import main as gx_main def test_eventbus_initialization(): """Test EventBus initialization.""" bus = EventBus() if bus is None: print("FAIL: Could not create EventBus") return False if bus._subscribers != {}: print("FAIL: Subscribers should be empty initially") return False if bus._history != []: print("FAIL: History should be empty initially") return False print("PASS: EventBus initializes correctly") return True def test_eventbus_subscribe(): """Test EventBus subscription.""" bus = EventBus() called = [] def handler(event: Event) -> None: called.append(event) bus.subscribe("kernel.warmup.completed", handler) if "kernel.warmup.completed" not in bus._subscribers: print("FAIL: Event type not registered") return False if handler not in bus._subscribers["kernel.warmup.completed"]: print("FAIL: Handler not registered") return False print("PASS: EventBus.subscribe() registers handlers") return True def test_eventbus_publish(): """Test EventBus publishing.""" bus = EventBus() called = [] def handler(event: Event) -> None: called.append(event) bus.subscribe("test.event", handler) event = bus.publish("test.event", {"key": "value"}) if event is None: print("FAIL: publish() returned None") return False if event.get("type") != "test.event": print("FAIL: Event type mismatch") return False if "timestamp" not in event: print("FAIL: Event missing timestamp") return False if event.get("payload") != {"key": "value"}: print("FAIL: Event payload mismatch") return False if len(called) != 1: print("FAIL: Handler not called") return False if called[0] != event: print("FAIL: Handler called with wrong event") return False print("PASS: EventBus.publish() works correctly") return True def test_eventbus_unsubscribe(): """Test EventBus unsubscription.""" bus = EventBus() called = [] def handler(event: Event) -> None: called.append(event) bus.subscribe("test.event", handler) bus.unsubscribe("test.event", handler) bus.publish("test.event", {}) if len(called) != 0: print("FAIL: Handler was called after unsubscribe") return False print("PASS: EventBus.unsubscribe() works correctly") return True def test_eventbus_history(): """Test EventBus history tracking.""" bus = EventBus() bus.publish("event1", {"data": 1}) bus.publish("event2", {"data": 2}) bus.publish("event1", {"data": 3}) history = bus.get_history() if len(history) != 3: print(f"FAIL: Expected 3 events, got {len(history)}") return False if history[0]["payload"]["data"] != 1: print("FAIL: History order wrong") return False if history[2]["payload"]["data"] != 3: print("FAIL: History order wrong") return False print("PASS: EventBus.get_history() tracks events in order") return True def test_eventbus_history_filter(): """Test EventBus history filtering.""" bus = EventBus() bus.publish("event1", {"data": 1}) bus.publish("event2", {"data": 2}) bus.publish("event1", {"data": 3}) history = bus.get_history("event1") if len(history) != 2: print(f"FAIL: Expected 2 event1s, got {len(history)}") return False if history[0]["type"] != "event1": print("FAIL: Filtered event has wrong type") return False if history[1]["payload"]["data"] != 3: print("FAIL: Filtered history order wrong") return False print("PASS: EventBus.get_history(type) filters correctly") return True def test_eventbus_history_limit(): """Test EventBus history limit.""" bus = EventBus() for i in range(10): bus.publish("test.event", {"n": i}) history = bus.get_history(limit=3) if len(history) != 3: print(f"FAIL: Expected 3 events, got {len(history)}") return False if history[-1]["payload"]["n"] != 9: print("FAIL: Should return most recent events") return False print("PASS: EventBus.get_history(limit) respects limit") return True def test_eventbus_clear_history(): """Test EventBus history clearing.""" bus = EventBus() bus.publish("test.event", {}) bus.publish("test.event", {}) history = bus.get_history() if len(history) != 2: print("FAIL: History not populated") return False bus.clear_history() history = bus.get_history() if len(history) != 0: print("FAIL: History not cleared") return False print("PASS: EventBus.clear_history() works correctly") return True def test_eventbus_handler_error_handling(): """Test that handler errors don't cascade.""" bus = EventBus() called = [] def bad_handler(event: Event) -> None: raise ValueError("Test error") def good_handler(event: Event) -> None: called.append(event) bus.subscribe("test.event", bad_handler) bus.subscribe("test.event", good_handler) try: bus.publish("test.event", {}) except Exception as e: print(f"FAIL: Exception raised: {e}") return False if len(called) != 1: print("FAIL: Good handler not called after bad handler error") return False print("PASS: EventBus handles handler errors gracefully") return True def test_get_event_bus_singleton(): """Test get_event_bus() singleton behavior.""" bus1 = get_event_bus() bus2 = get_event_bus() if bus1 is None or bus2 is None: print("FAIL: get_event_bus returned None") return False if bus1 is not bus2: print("FAIL: get_event_bus should return same instance") return False print("PASS: get_event_bus() returns singleton") return True def test_functional_api_emit(): """Test emit() convenience function.""" # Reset bus for clean test from glyphos import events events._GLOBAL_BUS = None called = [] def handler(event: Event) -> None: called.append(event) on("test.event", handler) event = emit("test.event", {"data": "test"}) if event is None: print("FAIL: emit() returned None") return False if len(called) != 1: print("FAIL: Handler not called by emit()") return False if called[0]["payload"]["data"] != "test": print("FAIL: emit() payload mismatch") return False print("PASS: emit() and on() convenience functions work") return True def test_functional_api_on(): """Test on() convenience function.""" from glyphos import events events._GLOBAL_BUS = None called = [] def handler(event: Event) -> None: called.append(event) on("test.event", handler) emit("test.event", {}) if len(called) != 1: print("FAIL: on() subscription didn't work") return False print("PASS: on() subscribes to global bus") return True def _create_test_gx() -> Path: """Create a test .gx file for kernel execution tests. Returns: Path to compiled .gx file """ source = Path("/home/dave/sample_code.py") if not source.exists(): raise FileNotFoundError("Sample code not found") temp_gx = Path(tempfile.gettempdir()) / "test_events.gx" if temp_gx.exists(): temp_gx.unlink() exit_code = gx_main(["compile", str(source), "-o", str(temp_gx)]) if exit_code != 0: raise RuntimeError(f"Compilation failed with exit code {exit_code}") if not temp_gx.exists(): raise RuntimeError("Compilation did not produce output file") return temp_gx def test_kernel_warmup_event(): """Test that kernel.warmup.completed event is emitted.""" from glyphos import events events._GLOBAL_BUS = None events_caught = [] def handler(event: Event) -> None: events_caught.append(event) on("kernel.warmup.completed", handler) kernel = get_kernel() # If already warmed up, create new one from glyphos.cognitive_kernel import CognitiveKernel fresh_kernel = CognitiveKernel(auto_load_glyphs=True) fresh_kernel.warmup() if len(events_caught) == 0: print("FAIL: kernel.warmup.completed not emitted") return False event = events_caught[0] if event.get("type") != "kernel.warmup.completed": print(f"FAIL: Wrong event type: {event.get('type')}") return False payload = event.get("payload", {}) if "glyph_stats" not in payload: print("FAIL: glyph_stats not in payload") return False if payload["glyph_stats"]["total_glyphs"] != 600: print("FAIL: glyph_stats total_glyphs should be 600") return False print("PASS: kernel.warmup.completed event emitted correctly") return True def test_cognition_started_event(): """Test that cognition.started event is emitted.""" from glyphos import events events._GLOBAL_BUS = None events_caught = [] def handler(event: Event) -> None: events_caught.append(event) on("cognition.started", handler) try: gx_path = _create_test_gx() except Exception as e: print(f"FAIL: Could not create test .gx: {e}") return False run_gx(str(gx_path)) if len(events_caught) == 0: print("FAIL: cognition.started not emitted") return False event = events_caught[0] if event.get("type") != "cognition.started": print(f"FAIL: Wrong event type: {event.get('type')}") return False payload = event.get("payload", {}) if "gx_path" not in payload: print("FAIL: gx_path not in payload") return False if "mode" not in payload: print("FAIL: mode not in payload") return False print("PASS: cognition.started event emitted correctly") return True def test_cognition_completed_event(): """Test that cognition.completed event is emitted.""" from glyphos import events events._GLOBAL_BUS = None events_caught = [] def handler(event: Event) -> None: events_caught.append(event) on("cognition.completed", handler) try: gx_path = _create_test_gx() except Exception as e: print(f"FAIL: Could not create test .gx: {e}") return False run_gx(str(gx_path)) if len(events_caught) == 0: print("FAIL: cognition.completed not emitted") return False event = events_caught[0] if event.get("type") != "cognition.completed": print(f"FAIL: Wrong event type: {event.get('type')}") return False payload = event.get("payload", {}) if "gx_path" not in payload: print("FAIL: gx_path not in payload") return False if "elapsed" not in payload: print("FAIL: elapsed not in payload") return False if "summary" not in payload: print("FAIL: summary not in payload") return False print("PASS: cognition.completed event emitted correctly") return True def test_glyph_resonance_event(): """Test that glyph.resonance.updated event is emitted when glyph resonance present.""" from glyphos import events events._GLOBAL_BUS = None events_caught = [] def handler(event: Event) -> None: events_caught.append(event) on("glyph.resonance.updated", handler) try: gx_path = _create_test_gx() except Exception as e: print(f"FAIL: Could not create test .gx: {e}") return False # Note: This will only emit if glyph_resonance is in diagnostics # The test file may not have a glyph, so we just verify the mechanism works run_gx(str(gx_path)) # Check if event was emitted (it may or may not be, depending on glyph context) # The important thing is that if glyph resonance is present, the event is emitted if len(events_caught) > 0: event = events_caught[0] if event.get("type") != "glyph.resonance.updated": print(f"FAIL: Wrong event type: {event.get('type')}") return False payload = event.get("payload", {}) if "glyph_resonance" not in payload: print("FAIL: glyph_resonance not in payload") return False print("PASS: glyph.resonance.updated event mechanism works") return True def main_test(): print("[TEST SUITE] test_events.py") print() tests = [ ("EventBus initialization", test_eventbus_initialization), ("EventBus.subscribe()", test_eventbus_subscribe), ("EventBus.publish()", test_eventbus_publish), ("EventBus.unsubscribe()", test_eventbus_unsubscribe), ("EventBus.get_history()", test_eventbus_history), ("EventBus.get_history(type)", test_eventbus_history_filter), ("EventBus.get_history(limit)", test_eventbus_history_limit), ("EventBus.clear_history()", test_eventbus_clear_history), ("Handler error handling", test_eventbus_handler_error_handling), ("get_event_bus() singleton", test_get_event_bus_singleton), ("emit() and on() API", test_functional_api_emit), ("on() subscription", test_functional_api_on), ("Kernel warmup event", test_kernel_warmup_event), ("Cognition started event", test_cognition_started_event), ("Cognition completed event", test_cognition_completed_event), ("Glyph resonance event", test_glyph_resonance_event), ] passed = 0 failed = 0 for name, test_func in tests: print(f"Running: {name}...", end=" ") try: if test_func(): passed += 1 else: failed += 1 except Exception as e: print(f"FAIL: {e}") failed += 1 print() print(f"Results: {passed} passed, {failed} failed") return 0 if failed == 0 else 1 if __name__ == "__main__": sys.exit(main_test())