/** * @file step_controller_test.cc * @brief Unit tests for the 65816 step controller (call stack tracking) * * Tests the StepOver and StepOut functionality that enables AI-assisted * debugging with proper subroutine tracking. */ #include "app/emu/debug/step_controller.h" #include #include #include #include namespace yaze { namespace emu { namespace debug { namespace { class StepControllerTest : public ::testing::Test { protected: void SetUp() override { // Reset program state pc_ = 0; instruction_count_ = 0; } // Simulates a simple memory with program code void SetupProgram(const std::vector& code, uint32_t base = 0) { memory_ = code; base_address_ = base; pc_ = base; controller_.SetMemoryReader([this](uint32_t addr) -> uint8_t { uint32_t offset = addr - base_address_; if (offset < memory_.size()) { return memory_[offset]; } return 0; }); controller_.SetPcGetter([this]() -> uint32_t { return pc_; }); controller_.SetSingleStepper([this]() { // Simulate executing one instruction by advancing PC // This is a simplified simulation - real stepping would be more complex if (pc_ >= base_address_ && pc_ < base_address_ + memory_.size()) { uint8_t opcode = memory_[pc_ - base_address_]; uint8_t size = GetSimulatedInstructionSize(opcode); pc_ += size; instruction_count_++; } }); } // Simplified instruction size for testing uint8_t GetSimulatedInstructionSize(uint8_t opcode) { switch (opcode) { // Implied (1 byte) case 0xEA: // NOP case 0x60: // RTS case 0x6B: // RTL case 0x40: // RTI case 0x18: // CLC case 0x38: // SEC case 0x78: // SEI return 1; // Branch (2 bytes) case 0xD0: // BNE case 0xF0: // BEQ case 0x80: // BRA case 0xA9: // LDA #imm (8-bit) return 2; // Absolute (3 bytes) case 0x20: // JSR case 0x4C: // JMP case 0xAD: // LDA abs case 0x8D: // STA abs return 3; // Long (4 bytes) case 0x22: // JSL case 0x5C: // JMP long return 4; default: return 1; } } StepController controller_; std::vector memory_; uint32_t base_address_ = 0; uint32_t pc_ = 0; uint32_t instruction_count_ = 0; }; // --- Basic Classification Tests --- TEST_F(StepControllerTest, ClassifyCallInstructions) { EXPECT_TRUE(StepController::IsCallInstruction(0x20)); // JSR EXPECT_TRUE(StepController::IsCallInstruction(0x22)); // JSL EXPECT_TRUE(StepController::IsCallInstruction(0xFC)); // JSR (abs,X) EXPECT_FALSE(StepController::IsCallInstruction(0xEA)); // NOP EXPECT_FALSE(StepController::IsCallInstruction(0x4C)); // JMP EXPECT_FALSE(StepController::IsCallInstruction(0x60)); // RTS } TEST_F(StepControllerTest, ClassifyReturnInstructions) { EXPECT_TRUE(StepController::IsReturnInstruction(0x60)); // RTS EXPECT_TRUE(StepController::IsReturnInstruction(0x6B)); // RTL EXPECT_TRUE(StepController::IsReturnInstruction(0x40)); // RTI EXPECT_FALSE(StepController::IsReturnInstruction(0xEA)); // NOP EXPECT_FALSE(StepController::IsReturnInstruction(0x20)); // JSR EXPECT_FALSE(StepController::IsReturnInstruction(0x4C)); // JMP } TEST_F(StepControllerTest, ClassifyBranchInstructions) { EXPECT_TRUE(StepController::IsBranchInstruction(0x80)); // BRA EXPECT_TRUE(StepController::IsBranchInstruction(0xD0)); // BNE EXPECT_TRUE(StepController::IsBranchInstruction(0xF0)); // BEQ EXPECT_TRUE(StepController::IsBranchInstruction(0x4C)); // JMP abs EXPECT_TRUE(StepController::IsBranchInstruction(0x5C)); // JMP long EXPECT_FALSE(StepController::IsBranchInstruction(0xEA)); // NOP EXPECT_FALSE(StepController::IsBranchInstruction(0x20)); // JSR EXPECT_FALSE(StepController::IsBranchInstruction(0x60)); // RTS } // --- StepInto Tests --- TEST_F(StepControllerTest, StepIntoSimpleInstruction) { // Simple program: NOP NOP NOP SetupProgram({0xEA, 0xEA, 0xEA}); auto result = controller_.StepInto(); EXPECT_TRUE(result.success); EXPECT_EQ(result.instructions_executed, 1u); EXPECT_EQ(result.new_pc, 1u); // PC advanced by 1 (NOP size) EXPECT_FALSE(result.call.has_value()); EXPECT_FALSE(result.ret.has_value()); } TEST_F(StepControllerTest, StepIntoTracksCallStack) { // Program: JSR $0010 at address 0 // JSR opcode (0x20) + 2-byte address = 3 bytes SetupProgram({0x20, 0x10, 0x00}); // JSR $0010 auto result = controller_.StepInto(); EXPECT_TRUE(result.success); EXPECT_TRUE(result.call.has_value()); EXPECT_EQ(result.call->target_address, 0x0010u); EXPECT_EQ(controller_.GetCallDepth(), 1u); } // --- Call Stack Management Tests --- TEST_F(StepControllerTest, CallStackPushesOnJSR) { SetupProgram({0x20, 0x10, 0x00}); // JSR $0010 EXPECT_EQ(controller_.GetCallDepth(), 0u); controller_.StepInto(); EXPECT_EQ(controller_.GetCallDepth(), 1u); const auto& stack = controller_.GetCallStack(); EXPECT_EQ(stack.back().target_address, 0x0010u); EXPECT_FALSE(stack.back().is_long); } TEST_F(StepControllerTest, CallStackPushesOnJSL) { SetupProgram({0x22, 0x00, 0x80, 0x01}); // JSL $018000 controller_.StepInto(); EXPECT_EQ(controller_.GetCallDepth(), 1u); const auto& stack = controller_.GetCallStack(); EXPECT_EQ(stack.back().target_address, 0x018000u); EXPECT_TRUE(stack.back().is_long); // JSL is a long call } TEST_F(StepControllerTest, ClearCallStackWorks) { SetupProgram({0x20, 0x10, 0x00}); // JSR $0010 controller_.StepInto(); EXPECT_EQ(controller_.GetCallDepth(), 1u); controller_.ClearCallStack(); EXPECT_EQ(controller_.GetCallDepth(), 0u); } // --- GetInstructionSize Tests --- TEST_F(StepControllerTest, InstructionSizeImplied) { // Implied addressing (1 byte) EXPECT_EQ(StepController::GetInstructionSize(0xEA, true, true), 1u); // NOP EXPECT_EQ(StepController::GetInstructionSize(0x60, true, true), 1u); // RTS EXPECT_EQ(StepController::GetInstructionSize(0x6B, true, true), 1u); // RTL EXPECT_EQ(StepController::GetInstructionSize(0x40, true, true), 1u); // RTI EXPECT_EQ(StepController::GetInstructionSize(0x18, true, true), 1u); // CLC EXPECT_EQ(StepController::GetInstructionSize(0xFB, true, true), 1u); // XCE } TEST_F(StepControllerTest, InstructionSizeBranch) { // Relative branch (2 bytes) EXPECT_EQ(StepController::GetInstructionSize(0x80, true, true), 2u); // BRA EXPECT_EQ(StepController::GetInstructionSize(0xD0, true, true), 2u); // BNE EXPECT_EQ(StepController::GetInstructionSize(0xF0, true, true), 2u); // BEQ EXPECT_EQ(StepController::GetInstructionSize(0x10, true, true), 2u); // BPL // Relative long (3 bytes) EXPECT_EQ(StepController::GetInstructionSize(0x82, true, true), 3u); // BRL } TEST_F(StepControllerTest, InstructionSizeJumpCall) { // JSR/JMP absolute (3 bytes) EXPECT_EQ(StepController::GetInstructionSize(0x20, true, true), 3u); // JSR EXPECT_EQ(StepController::GetInstructionSize(0x4C, true, true), 3u); // JMP abs EXPECT_EQ(StepController::GetInstructionSize(0xFC, true, true), 3u); // JSR (abs,X) // Long (4 bytes) EXPECT_EQ(StepController::GetInstructionSize(0x22, true, true), 4u); // JSL EXPECT_EQ(StepController::GetInstructionSize(0x5C, true, true), 4u); // JMP long } // --- Error Handling Tests --- TEST_F(StepControllerTest, StepIntoFailsWithoutConfiguration) { // Don't call SetupProgram - controller is unconfigured auto result = controller_.StepInto(); EXPECT_FALSE(result.success); EXPECT_EQ(result.instructions_executed, 0u); } TEST_F(StepControllerTest, StepOutFailsWithEmptyCallStack) { SetupProgram({0xEA, 0xEA, 0xEA}); // Just NOPs // Don't execute any calls, so stack is empty auto result = controller_.StepOut(100); EXPECT_FALSE(result.success); EXPECT_TRUE(result.message.find("empty") != std::string::npos); } // --- StepOver Non-Call Instruction --- TEST_F(StepControllerTest, StepOverNonCallIsSameAsStepInto) { // Program: NOP NOP SetupProgram({0xEA, 0xEA}); auto result = controller_.StepOver(1000); EXPECT_TRUE(result.success); EXPECT_EQ(result.instructions_executed, 1u); EXPECT_EQ(result.new_pc, 1u); } } // namespace } // namespace debug } // namespace emu } // namespace yaze