""" Unit tests for XIC v2 control flow (IF, MATCH, LOOP). Tests predicate evaluation, control operations, and guardrails. """ import pytest import sys from pathlib import Path # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent)) from xic_ops import XICContext, op_IF, op_MATCH, op_LOOP, op_PUSH_GLYPH_CONTEXT, op_CALL_GLYPH from glyphos.control.predicate import eval_predicate from glyphos.symbolic_pipeline import SymbolicPipelineResult, FusedSymbol, GlyphResonanceMap, GlyphResonanceMetrics def create_fake_pipeline_result(global_score: float = 0.9, glyph_ids: list = None): """Helper to create a fake symbolic pipeline result for testing.""" if glyph_ids is None: glyph_ids = ["glyph://a", "glyph://b"] # Create fake resonance map resonance_map = GlyphResonanceMap(global_resonance_score=global_score) for glyph_id in glyph_ids: resonance_map.resonances[glyph_id] = GlyphResonanceMetrics( weight=0.8, lineage_score=0.7, contributor_score=0.75, frequency_score=0.8, grammar_score=0.85, ) fused = FusedSymbol( summary="Test output", glyph_ids=glyph_ids, resonance_map=resonance_map, ) return SymbolicPipelineResult( steps=[], output_text="Test output", fused_symbol=fused, ) class TestPredicateEvaluator: """Test safe predicate evaluation.""" def test_simple_comparison(self): """Test simple numeric comparison.""" fused = {"global_resonance_score": 0.9} result = eval_predicate("fused.global_resonance_score > 0.8", fused) assert result is True def test_comparison_false(self): """Test comparison that evaluates to False.""" fused = {"global_resonance_score": 0.5} result = eval_predicate("fused.global_resonance_score > 0.8", fused) assert result is False def test_boolean_and(self): """Test AND operator.""" fused = {"global_resonance_score": 0.9, "count": 3} result = eval_predicate( "fused.global_resonance_score > 0.8 and fused.count > 2", fused ) assert result is True def test_boolean_or(self): """Test OR operator.""" fused = {"global_resonance_score": 0.5} result = eval_predicate( "fused.global_resonance_score > 0.8 or fused.global_resonance_score > 0.4", fused ) assert result is True def test_dominant_contains(self): """Test dominant_contains helper function.""" dominant = [("glyph://entropy", 0.95), ("glyph://compression", 0.8)] result = eval_predicate( "dominant_contains('glyph://entropy')", {}, dominant ) assert result is True def test_dominant_contains_not_found(self): """Test dominant_contains when glyph not present.""" dominant = [("glyph://entropy", 0.95)] result = eval_predicate( "dominant_contains('glyph://other')", {}, dominant ) assert result is False def test_unsafe_import(self): """Test that import statements are blocked.""" with pytest.raises(ValueError): eval_predicate("__import__('os')") def test_syntax_error(self): """Test handling of syntax errors.""" with pytest.raises(ValueError, match="Invalid predicate syntax"): eval_predicate("fused.score >") class TestIFOperation: """Test IF control flow operation.""" def test_if_then_true(self): """Test IF with true condition and then branch.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=0.9) op_IF(ctx, "fused.global_resonance_score > 0.8", "then_chain", "else_chain") assert ctx.pop_next_chain() == "then_chain" assert ctx.pop_next_chain() is None def test_if_else_true(self): """Test IF with false condition and else branch.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=0.5) op_IF(ctx, "fused.global_resonance_score > 0.8", "then_chain", "else_chain") assert ctx.pop_next_chain() == "else_chain" def test_if_no_else(self): """Test IF without else branch.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=0.5) op_IF(ctx, "fused.global_resonance_score > 0.8", "then_chain") assert ctx.pop_next_chain() is None def test_if_logs_control_step(self): """Test that IF logs control steps for observability.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=0.9) op_IF(ctx, "fused.global_resonance_score > 0.8", "then_chain") control_steps = ctx._state.get("control_steps", []) assert len(control_steps) > 0 assert control_steps[0]["type"] == "if" assert control_steps[0]["result"] is True def test_if_with_dominant_contains(self): """Test IF using dominant_contains helper.""" ctx = XICContext() result = create_fake_pipeline_result(glyph_ids=["glyph://entropy", "glyph://compression"]) ctx._state["last_symbolic_pipeline"] = result op_IF(ctx, "dominant_contains('glyph://entropy')", "found_chain") assert ctx.pop_next_chain() == "found_chain" class TestMATCHOperation: """Test MATCH control flow operation.""" def test_match_glyph_ids_found(self): """Test MATCH when pattern is found in glyph_ids.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result( glyph_ids=["glyph://a", "glyph://b", "glyph://c"] ) op_MATCH(ctx, "fused.glyph_ids", "glyph://b", "found_chain") assert ctx.pop_next_chain() == "found_chain" def test_match_glyph_ids_not_found(self): """Test MATCH when pattern is not in glyph_ids.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result( glyph_ids=["glyph://a", "glyph://b"] ) op_MATCH(ctx, "fused.glyph_ids", "glyph://x", "found_chain") assert ctx.pop_next_chain() is None def test_match_logs_step(self): """Test that MATCH logs symbolic steps.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result() op_MATCH(ctx, "fused.glyph_ids", "glyph://a", "chain") steps = ctx._state.get("symbolic_steps", []) assert any(s["kind"] == "control_match" for s in steps) class TestLOOPOperation: """Test LOOP control flow operation.""" def test_loop_iterations(self): """Test LOOP schedules multiple iterations.""" ctx = XICContext() ctx.params["max_loop_iterations"] = 3 # Create a pipeline that will be true for the loop condition ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=1.0) op_LOOP(ctx, "fused.global_resonance_score > 0.5", "body_chain", 3) # Should have enqueued 3 iterations chains = [] while True: c = ctx.pop_next_chain() if c is None: break chains.append(c) assert chains == ["body_chain", "body_chain", "body_chain"] def test_loop_predicate_false(self): """Test LOOP stops when predicate is false.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=0.3) op_LOOP(ctx, "fused.global_resonance_score > 0.8", "body_chain", 10) # Should not enqueue anything assert ctx.pop_next_chain() is None def test_loop_max_iterations_guardrail(self): """Test LOOP guardrail triggers at max iterations.""" ctx = XICContext() ctx.params["max_loop_iterations"] = 2 ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=1.0) op_LOOP(ctx, "fused.global_resonance_score > 0.5", "body_chain", 2) # Should trigger guardrail guardrails = ctx._state.get("guardrails", []) assert "max_loop_iterations_exceeded" in guardrails def test_loop_max_total_steps_guardrail(self): """Test LOOP respects max_total_steps guardrail.""" ctx = XICContext() ctx.params["max_total_steps"] = 5 ctx._state["total_steps"] = 5 # Already at limit ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=1.0) op_LOOP(ctx, "fused.global_resonance_score > 0.5", "body_chain", 10) # Should trigger guardrail immediately guardrails = ctx._state.get("guardrails", []) assert "max_total_steps_exceeded" in guardrails # Should not enqueue any chains assert ctx.pop_next_chain() is None def test_loop_emits_steps(self): """Test LOOP emits symbolic steps for each iteration.""" ctx = XICContext() ctx.params["max_loop_iterations"] = 2 ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=1.0) op_LOOP(ctx, "fused.global_resonance_score > 0.5", "body_chain", 2) steps = ctx._state.get("symbolic_steps", []) loop_steps = [s for s in steps if s["kind"] == "control_loop"] assert len(loop_steps) >= 2 class TestQueueHelpers: """Test XICContext queue helper methods.""" def test_enqueue_chain(self): """Test enqueue_chain adds to queue.""" ctx = XICContext() ctx.enqueue_chain("chain1") assert ctx.pop_next_chain() == "chain1" def test_fifo_order(self): """Test chains dequeue in FIFO order.""" ctx = XICContext() ctx.enqueue_chain("chain1") ctx.enqueue_chain("chain2") ctx.enqueue_chain("chain3") assert ctx.pop_next_chain() == "chain1" assert ctx.pop_next_chain() == "chain2" assert ctx.pop_next_chain() == "chain3" assert ctx.pop_next_chain() is None def test_jump_to(self): """Test jump_to clears and replaces queue.""" ctx = XICContext() ctx.enqueue_chain("chain1") ctx.enqueue_chain("chain2") ctx.jump_to("chain3") # Should only have chain3 assert ctx.pop_next_chain() == "chain3" assert ctx.pop_next_chain() is None def test_pop_empty_queue(self): """Test pop_next_chain on empty queue returns None.""" ctx = XICContext() assert ctx.pop_next_chain() is None class TestIntegration: """Integration tests combining multiple operations.""" def test_if_with_call_glyph(self): """Test IF depends on CALL_GLYPH result.""" ctx = XICContext() ctx.symbolic_mode = True # Simulate CALL_GLYPH execution (would set last_symbolic_pipeline) ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=0.95) # IF should work with the result op_IF(ctx, "fused.global_resonance_score > 0.9", "high_resonance") assert ctx.pop_next_chain() == "high_resonance" def test_guardrail_enforcement(self): """Test that guardrails stop execution.""" ctx = XICContext() ctx._state["guardrails"] = ["max_total_steps_exceeded"] ctx.params["max_total_steps"] = 10 ctx._state["total_steps"] = 10 # LOOP should respect the guardrail ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result(global_score=1.0) op_LOOP(ctx, "fused.global_resonance_score > 0.5", "body", 100) # No chains should be enqueued due to guardrail assert ctx.pop_next_chain() is None class TestErrorHandling: """Test error handling in control flow.""" def test_if_without_pipeline(self): """Test IF gracefully handles missing pipeline.""" ctx = XICContext() # No pipeline set op_IF(ctx, "fused.global_resonance_score > 0.5", "then") # Should complete without error, but not enqueue (predicate fails safely) assert True # Successfully didn't crash def test_malformed_predicate(self): """Test that malformed predicates are caught.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result() # Invalid syntax op_IF(ctx, "fused.score >", "then") # Should handle error gracefully (may not enqueue) assert True # Successfully didn't crash def test_loop_with_bad_predicate(self): """Test LOOP handles predicate evaluation errors.""" ctx = XICContext() ctx._state["last_symbolic_pipeline"] = create_fake_pipeline_result() op_LOOP(ctx, "fused.nonexistent > 0.5", "body", 2) # Should handle error gracefully assert True # Successfully didn't crash if __name__ == "__main__": pytest.main([__file__, "-v", "--tb=short"])