"""Tests for dfgraph/categories.py — opcode-to-category mapping. Tests verify: - Every opcode in MNEMONIC_TO_OP has a valid category via categorise() - ArithOp members map to ARITHMETIC - LogicOp logic ops map to LOGIC - LogicOp comparison ops map to COMPARISON - RoutingOp members (except CONST/FREE_FRAME) map to ROUTING - RoutingOp.CONST and FREE_FRAME map to CONFIG - MemOp members map to MEMORY - Every OpcodeCategory has a colour in CATEGORY_COLOURS """ import pytest from cm_inst import ArithOp, LogicOp, MemOp, RoutingOp from asm.opcodes import MNEMONIC_TO_OP from dfgraph.categories import categorise, OpcodeCategory, CATEGORY_COLOURS class TestCategoriseArithOp: """Tests for ArithOp -> ARITHMETIC category mapping.""" @pytest.mark.parametrize("op", [ ArithOp.ADD, ArithOp.SUB, ArithOp.INC, ArithOp.DEC, ArithOp.SHL, ArithOp.SHR, ArithOp.ASR, ]) def test_arith_ops_map_to_arithmetic(self, op): """All ArithOp members map to ARITHMETIC category.""" assert categorise(op) == OpcodeCategory.ARITHMETIC class TestCategoriseLogicOp: """Tests for LogicOp -> LOGIC or COMPARISON category mapping.""" @pytest.mark.parametrize("op", [ LogicOp.AND, LogicOp.OR, LogicOp.XOR, LogicOp.NOT, ]) def test_logic_ops_map_to_logic(self, op): """Pure logic opcodes (AND, OR, XOR, NOT) map to LOGIC category.""" assert categorise(op) == OpcodeCategory.LOGIC @pytest.mark.parametrize("op", [ LogicOp.EQ, LogicOp.LT, LogicOp.LTE, LogicOp.GT, LogicOp.GTE, ]) def test_comparison_ops_map_to_comparison(self, op): """Comparison opcodes (EQ, LT, LTE, GT, GTE) map to COMPARISON category.""" assert categorise(op) == OpcodeCategory.COMPARISON class TestCategoriseRoutingOp: """Tests for RoutingOp -> ROUTING or CONFIG category mapping.""" @pytest.mark.parametrize("op", [ RoutingOp.BREQ, RoutingOp.BRGT, RoutingOp.BRGE, RoutingOp.BROF, RoutingOp.SWEQ, RoutingOp.SWGT, RoutingOp.SWGE, RoutingOp.SWOF, RoutingOp.GATE, RoutingOp.PASS, RoutingOp.SEL, RoutingOp.MRGE, ]) def test_routing_ops_map_to_routing(self, op): """Routing/branch/switch/control opcodes map to ROUTING category.""" assert categorise(op) == OpcodeCategory.ROUTING @pytest.mark.parametrize("op", [ RoutingOp.CONST, RoutingOp.FREE_FRAME, ]) def test_config_routing_ops_map_to_config(self, op): """RoutingOp.CONST and RoutingOp.FREE_FRAME map to CONFIG category.""" assert categorise(op) == OpcodeCategory.CONFIG class TestCategoriseMemOp: """Tests for MemOp -> MEMORY category mapping.""" @pytest.mark.parametrize("op", [ MemOp.READ, MemOp.WRITE, MemOp.CLEAR, MemOp.ALLOC, MemOp.FREE, MemOp.RD_INC, MemOp.RD_DEC, MemOp.CMP_SW, MemOp.EXEC, MemOp.RAW_READ, MemOp.SET_PAGE, MemOp.WRITE_IMM, MemOp.EXT, ]) def test_memory_ops_map_to_memory(self, op): """All MemOp members map to MEMORY category.""" assert categorise(op) == OpcodeCategory.MEMORY class TestCategoriseMnemonicToOp: """Tests for all opcodes in MNEMONIC_TO_OP.""" @pytest.mark.parametrize("mnemonic, op", MNEMONIC_TO_OP.items()) def test_all_mnemonics_have_category(self, mnemonic, op): """Every opcode in MNEMONIC_TO_OP has a valid category via categorise().""" # Should not raise ValueError category = categorise(op) assert isinstance(category, OpcodeCategory) class TestCategoryColours: """Tests for CATEGORY_COLOURS mapping.""" def test_all_categories_have_colour(self): """Every OpcodeCategory has a colour in CATEGORY_COLOURS.""" for category in OpcodeCategory: assert category in CATEGORY_COLOURS colour = CATEGORY_COLOURS[category] assert isinstance(colour, str) assert colour.startswith("#") def test_colours_are_valid_hex(self): """All colours are valid 6-digit hex codes.""" for category, colour in CATEGORY_COLOURS.items(): assert len(colour) == 7, f"Colour for {category} is not 7 chars: {colour}" assert colour[0] == "#", f"Colour for {category} doesn't start with #: {colour}" try: int(colour[1:], 16) except ValueError: pytest.fail(f"Colour for {category} is not valid hex: {colour}") def test_expected_colour_values(self): """Verify the design-specified colours are present.""" assert CATEGORY_COLOURS[OpcodeCategory.ARITHMETIC] == "#4a90d9" assert CATEGORY_COLOURS[OpcodeCategory.LOGIC] == "#4caf50" assert CATEGORY_COLOURS[OpcodeCategory.COMPARISON] == "#ff9800" assert CATEGORY_COLOURS[OpcodeCategory.ROUTING] == "#9c27b0" assert CATEGORY_COLOURS[OpcodeCategory.MEMORY] == "#ff5722" assert CATEGORY_COLOURS[OpcodeCategory.IO] == "#009688" assert CATEGORY_COLOURS[OpcodeCategory.CONFIG] == "#9e9e9e" class TestCategoriseErrors: """Tests for error handling in categorise().""" def test_unknown_type_raises_valueerror(self): """Passing an unknown type to categorise() raises ValueError.""" class UnknownOp: pass with pytest.raises(ValueError, match="Unknown opcode type"): categorise(UnknownOp()) # type: ignore