vyre 0.1.0

GPU bytecode condition engine
Documentation
//! Bytecode serialization and validation tests.

use rulefire::vm::bytecode::{Instruction, Opcode, Program, BYTECODE_MAGIC};

#[test]
fn bytecode_magic_is_correct() {
    assert_eq!(&BYTECODE_MAGIC, b"YBC0");
}

#[test]
fn instruction_new_creates_correct_opcode() {
    let inst = Instruction::new(Opcode::PushTrue, 42);
    assert_eq!(inst.opcode, 1);
    assert_eq!(inst.operand, 42);
}

#[test]
fn opcode_from_u32_valid_values() {
    assert_eq!(Opcode::from_u32(1).unwrap(), Opcode::PushTrue);
    assert_eq!(Opcode::from_u32(2).unwrap(), Opcode::PushFalse);
    assert_eq!(Opcode::from_u32(30).unwrap(), Opcode::Halt);
}

#[test]
fn opcode_from_u32_invalid_value() {
    let result = Opcode::from_u32(0);
    assert!(result.is_err());
    // 31-47 are valid opcodes.
    let result = Opcode::from_u32(50);
    assert!(result.is_err());
    let result = Opcode::from_u32(999);
    assert!(result.is_err());
}

#[test]
fn program_to_bytes_empty() {
    let program = Program {
        instructions: vec![Instruction::new(Opcode::Halt, 0)],
    };
    let bytes = program.to_bytes();
    assert!(bytes.len() >= 8); // Magic + count
    assert_eq!(&bytes[..4], BYTECODE_MAGIC);
}

#[test]
fn program_roundtrip_single_instruction() {
    let program = Program {
        instructions: vec![
            Instruction::new(Opcode::PushTrue, 0),
            Instruction::new(Opcode::Halt, 0),
        ],
    };
    let bytes = program.to_bytes();
    let roundtrip = Program::from_bytes(&bytes).unwrap();
    assert_eq!(roundtrip.instructions, program.instructions);
}

#[test]
fn program_roundtrip_all_opcodes() {
    let program = Program {
        instructions: vec![
            Instruction::new(Opcode::PushTrue, 0),
            Instruction::new(Opcode::PushFalse, 0),
            Instruction::new(Opcode::PushStringMatched, 1),
            Instruction::new(Opcode::PushStringCount, 2),
            Instruction::new(Opcode::PushStringOffset, 3),
            Instruction::new(Opcode::PushStringLength, 4),
            Instruction::new(Opcode::PushFileSize, 0),
            Instruction::new(Opcode::PushEntryCount, 0),
            Instruction::new(Opcode::PushImmediate, 9),
            Instruction::new(Opcode::PushNumStrings, 0),
            Instruction::new(Opcode::And, 0),
            Instruction::new(Opcode::Or, 0),
            Instruction::new(Opcode::Not, 0),
            Instruction::new(Opcode::Eq, 0),
            Instruction::new(Opcode::Neq, 0),
            Instruction::new(Opcode::Lt, 0),
            Instruction::new(Opcode::Gt, 0),
            Instruction::new(Opcode::Lte, 0),
            Instruction::new(Opcode::Gte, 0),
            Instruction::new(Opcode::Add, 0),
            Instruction::new(Opcode::Sub, 0),
            Instruction::new(Opcode::CountOf, (2 << 16) | 1),
            Instruction::new(Opcode::AllOf, 2),
            Instruction::new(Opcode::AnyOf, 2),
            Instruction::new(Opcode::StringAt, 1),
            Instruction::new(Opcode::StringIn, 1),
            Instruction::new(Opcode::ForAny, 1 << 16),
            Instruction::new(Opcode::EndFor, 0),
            Instruction::new(Opcode::ForAll, 1 << 16),
            Instruction::new(Opcode::EndFor, 0),
            Instruction::new(Opcode::Halt, 0),
        ],
    };
    let bytes = program.to_bytes();
    let roundtrip = Program::from_bytes(&bytes).unwrap();
    assert_eq!(roundtrip.instructions, program.instructions);
}

#[test]
fn program_from_bytes_missing_magic() {
    let bytes = b"XXXX";
    let result = Program::from_bytes(bytes);
    assert!(result.is_err());
}

#[test]
fn program_from_bytes_truncated() {
    let mut bytes = BYTECODE_MAGIC.to_vec();
    bytes.extend_from_slice(&1u32.to_le_bytes()); // count = 1
                                                  // Missing instruction data
    let result = Program::from_bytes(&bytes);
    assert!(result.is_err());
}

#[test]
fn program_from_bytes_size_mismatch() {
    let mut bytes = BYTECODE_MAGIC.to_vec();
    bytes.extend_from_slice(&2u32.to_le_bytes()); // count = 2
    bytes.extend_from_slice(&1u32.to_le_bytes()); // opcode
    bytes.extend_from_slice(&0u32.to_le_bytes()); // operand
                                                  // Only 1 instruction but claimed 2
    let result = Program::from_bytes(&bytes);
    assert!(result.is_err());
}

#[test]
fn program_validate_empty() {
    let program = Program {
        instructions: vec![],
    };
    let result = program.validate();
    assert!(result.is_err());
}

#[test]
fn program_validate_no_halt() {
    let program = Program {
        instructions: vec![Instruction::new(Opcode::PushTrue, 0)],
    };
    let result = program.validate();
    assert!(result.is_err());
}

#[test]
fn program_validate_halt_not_last() {
    let program = Program {
        instructions: vec![
            Instruction::new(Opcode::Halt, 0),
            Instruction::new(Opcode::PushTrue, 0),
        ],
    };
    let result = program.validate();
    assert!(result.is_err());
}

#[test]
fn program_validate_valid() {
    let program = Program {
        instructions: vec![
            Instruction::new(Opcode::PushTrue, 0),
            Instruction::new(Opcode::Halt, 0),
        ],
    };
    let result = program.validate();
    assert!(result.is_ok());
}

#[test]
fn program_validate_nested_loops_ok() {
    let program = Program {
        instructions: vec![
            Instruction::new(Opcode::ForAny, 4 << 16),
            Instruction::new(Opcode::ForAll, 2 << 16),
            Instruction::new(Opcode::PushTrue, 0),
            Instruction::new(Opcode::EndFor, 0),
            Instruction::new(Opcode::EndFor, 0),
            Instruction::new(Opcode::Halt, 0),
        ],
    };
    let result = program.validate();
    assert!(result.is_ok());
}

#[test]
fn program_validate_unmatched_for_any() {
    let program = Program {
        instructions: vec![
            Instruction::new(Opcode::ForAny, 1 << 16),
            Instruction::new(Opcode::PushTrue, 0),
            // Missing END_FOR
            Instruction::new(Opcode::Halt, 0),
        ],
    };
    let result = program.validate();
    assert!(result.is_err());
}

#[test]
fn program_validate_unmatched_endfor() {
    let program = Program {
        instructions: vec![
            Instruction::new(Opcode::EndFor, 0),
            Instruction::new(Opcode::Halt, 0),
        ],
    };
    let result = program.validate();
    assert!(result.is_err());
}

#[test]
fn instruction_kind_returns_correct_opcode() {
    let inst = Instruction::new(Opcode::PushTrue, 0);
    assert_eq!(inst.kind().unwrap(), Opcode::PushTrue);

    let inst = Instruction::new(Opcode::Halt, 0);
    assert_eq!(inst.kind().unwrap(), Opcode::Halt);
}

#[test]
fn instruction_kind_invalid_opcode() {
    let inst = Instruction {
        opcode: 999,
        operand: 0,
    };
    assert!(inst.kind().is_err());
}

#[test]
fn program_with_large_operand_roundtrips() {
    let program = Program {
        instructions: vec![
            Instruction::new(Opcode::PushImmediate, u32::MAX),
            Instruction::new(Opcode::Halt, 0),
        ],
    };
    let bytes = program.to_bytes();
    let roundtrip = Program::from_bytes(&bytes).unwrap();
    assert_eq!(roundtrip.instructions[0].operand, u32::MAX);
}