diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4dd458d..8ef96f4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y g++ make cmake libgtest-dev + sudo apt-get install -y g++ make cmake libgtest-dev lcov gcovr - name: Build and install Google Test run: | @@ -32,12 +32,12 @@ jobs: - name: Run tests working-directory: ./lc3vm - run: make test + run: make coverage deploy-docs: needs: build_and_test runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' && github.event_name == 'push' # Deploy only on push to main + if: github.ref == 'refs/heads/main' && github.event_name == 'push' permissions: contents: write pages: write diff --git a/.gitignore b/.gitignore index e3646b1..967595b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,8 @@ *.app build/ -docs/ \ No newline at end of file +docs/ + +CMakeFiles/ +CMakeCache.txt +cmake_install.cmake diff --git a/README.md b/README.md index 222e91a..df39257 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,49 @@ To build and run this project, you will need: * `make` * Google Test (`libgtest-dev` on Debian/Ubuntu systems) * `doxygen` (optional, for generating documentation) -* `graphviz` (optional, for Doxygen diagrams) ## Building the Project -The project uses a Makefile located in the `lc3vm/` directory. +The project can be built using either CMake or Make. Choose your preferred method below. + +### Using CMake + +1. **Create a build directory and navigate to it**: + + ```bash + mkdir build + cd build + ``` + +2. **Generate build files**: + + ```bash + cmake .. + ``` + +3. **Build the project**: + + ```bash + cmake --build . + ``` + + This will create the following executables in the `build` directory: + * `lc3vm`: The main VM executable + * `test_runner`: The test executable + +4. **Run tests** (optional): + + ```bash + ./test_runner + ``` + +5. **Generate documentation** (optional): + + ```bash + cmake --build . --target docs + ``` + +### Using Make 1. **Navigate to the `lc3vm` directory**: diff --git a/lc3vm/CMakeLists.txt b/lc3vm/CMakeLists.txt index 593fd86..a5dbc6d 100644 --- a/lc3vm/CMakeLists.txt +++ b/lc3vm/CMakeLists.txt @@ -2,10 +2,16 @@ cmake_minimum_required(VERSION 3.10) project(lc3vm CXX) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +option(ENABLE_COVERAGE "Enable coverage reporting" OFF) +if(ENABLE_COVERAGE) + add_compile_options(-g -O0 --coverage) + add_link_options(--coverage) +endif() + include_directories(include) @@ -26,7 +32,7 @@ add_executable(test_runner tests/test_initialization.cpp tests/test_opcode_execution.cpp tests/test_disassembly.cpp - src/ls3.cpp + src/lc3.cpp src/memory.cpp src/terminal_input.cpp ) @@ -39,4 +45,21 @@ if (DOXYGEN_FOUND) doxygen_add_docs(docs Doxyfile) endif() +# Add coverage target +if(ENABLE_COVERAGE) + find_program(GCOVR_PATH gcovr) + if(GCOVR_PATH) + add_custom_target(coverage + COMMAND ${CMAKE_COMMAND} -E echo "\nCoverage Summary:" + COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} + --exclude "tests/.*" + --exclude "build/.*" + --print-summary + COMMAND ${CMAKE_COMMAND} -E echo "\nCoverage report generated at ${CMAKE_BINARY_DIR}/coverage/index.html" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Generating code coverage report..." + ) + endif() +endif() + install(TARGETS lc3vm DESTINATION bin) \ No newline at end of file diff --git a/lc3vm/Makefile b/lc3vm/Makefile index 261084f..cbbbe88 100644 --- a/lc3vm/Makefile +++ b/lc3vm/Makefile @@ -1,19 +1,30 @@ CC = /usr/bin/g++ -CFLAGS = -Wall -Wextra -std=c++14 -Iinclude -O2 -g +# Coverage flags +COVERAGE_FLAGS = -g -O0 --coverage +COVERAGE_LDFLAGS = --coverage + +# Default CFLAGS without coverage +CFLAGS = -Wall -Wextra -std=c++17 -Iinclude -O2 -g + +# Coverage-enabled CFLAGS +COVERAGE_CFLAGS = $(CFLAGS) $(COVERAGE_FLAGS) BUILD_DIR = build TEST_DIR = tests GTEST_FLAGS = -lgtest -lgtest_main -pthread -VM_SRCS = src/ls3.cpp src/memory.cpp src/terminal_input.cpp +VM_SRCS = src/lc3.cpp src/memory.cpp src/terminal_input.cpp VM_TEST_SRCS = $(VM_SRCS) TEST_MAIN_OBJ = $(BUILD_DIR)/test_main.o -TEST_FILES = tests/test_initialization.cpp tests/test_opcode_execution.cpp tests/test_disassembly.cpp +TEST_FILES = tests/test_initialization.cpp \ + tests/test_opcode_execution.cpp \ + tests/test_disassembly.cpp \ + tests/test_integration.cpp TEST_OBJS = $(TEST_FILES:tests/%.cpp=$(BUILD_DIR)/tests/%.o) -.PHONY: all clean test docs +.PHONY: all clean test docs coverage coverage-clean all: $(BUILD_DIR)/lc3vm @@ -34,12 +45,25 @@ $(BUILD_DIR)/tests/%.o: tests/%.cpp $(BUILD_DIR)/test_runner: $(TEST_MAIN_OBJ) $(TEST_OBJS) $(VM_SRCS) mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) $(TEST_MAIN_OBJ) $(TEST_OBJS) $(VM_SRCS) -o $(BUILD_DIR)/test_runner $(GTEST_FLAGS) $(LDFLAGS_COVERAGE) + $(CC) $(CFLAGS) $(TEST_MAIN_OBJ) $(TEST_OBJS) $(VM_SRCS) -o $(BUILD_DIR)/test_runner $(GTEST_FLAGS) docs: @echo "Generating documentation with Doxygen..." @doxygen Doxyfile +# Coverage targets +coverage: CFLAGS := $(COVERAGE_CFLAGS) +coverage: LDFLAGS := $(COVERAGE_LDFLAGS) +coverage: clean test + @echo "Generating coverage report..." + @mkdir -p $(BUILD_DIR)/coverage + @echo "Coverage Summary:" + @gcovr --root . \ + --exclude "tests/.*" \ + --exclude "build/.*" \ + --print-summary + @echo "Coverage report generated at $(BUILD_DIR)/coverage/index.html" + clean: @echo "Cleaning build directory..." rm -rf $(BUILD_DIR) \ No newline at end of file diff --git a/lc3vm/include/lc3.hpp b/lc3vm/include/lc3.hpp index d97ff95..e7ebec8 100644 --- a/lc3vm/include/lc3.hpp +++ b/lc3vm/include/lc3.hpp @@ -32,8 +32,9 @@ struct CodeSegment { * to load programs, run the VM, step through instructions, and inspect/modify its state. */ class LC3State { + public: + Memory memory; // Made public for testing private: - Memory memory; /** * @brief Array of 16-bit registers. * This array holds the values of the 16 registers in the LC-3 architecture. @@ -112,6 +113,12 @@ class LC3State { */ void request_halt() { running = false; } + /** + * @brief Checks if the VM is currently running. + * @return true if the VM is running, false if it has halted. + */ + bool is_running() const { return running; } + // Test helper methods /** * @brief Retrieves the value of a specified register. diff --git a/lc3vm/include/memory.hpp b/lc3vm/include/memory.hpp index 5e8a0fa..9ef1ff0 100644 --- a/lc3vm/include/memory.hpp +++ b/lc3vm/include/memory.hpp @@ -24,6 +24,13 @@ class Memory { * Stores 65536 16-bit words. */ std::uint16_t memory[MEMORY_MAX]; + + /** + * @brief Flag to indicate if we're in test mode. + * When true, keyboard input is simulated using memory values. + */ + bool test_mode = false; + /** * @brief Reads a 16-bit word from the specified memory address. * Handles memory-mapped I/O for keyboard status (MR_KBSR) and diff --git a/lc3vm/include/terminal_input.hpp b/lc3vm/include/terminal_input.hpp index 945f784..67ec25a 100644 --- a/lc3vm/include/terminal_input.hpp +++ b/lc3vm/include/terminal_input.hpp @@ -7,22 +7,24 @@ */ /** - * @brief Enables raw input mode for the terminal. - * - * In raw mode: - * - Input is unbuffered (character by character). - * - Input is not echoed to the screen. - * - Special character processing (like Ctrl+C for SIGINT) might be altered, - * so signal handling needs to be robust. - * @throw std::runtime_error if enabling raw mode fails. + * @brief Enables raw mode for terminal input. + * This disables echo, canonical mode, and other terminal features + * to allow for direct character input. + * @throw std::runtime_error if terminal attributes cannot be modified. */ void enable_raw_mode(); /** - * @brief Disables raw input mode, restoring original terminal settings. - * This function should be called before the program exits to ensure the - * terminal is left in a usable state. + * @brief Disables raw mode and restores original terminal settings. + * This should be called before the program exits to ensure the terminal + * is left in a usable state. */ void disable_raw_mode(); +/** + * @brief Checks if raw mode is currently enabled. + * @return true if raw mode is enabled, false otherwise. + */ +bool is_raw_mode_enabled(); + #endif // LC3_TERMINAL_INPUT_H \ No newline at end of file diff --git a/lc3vm/src/ls3.cpp b/lc3vm/src/lc3.cpp similarity index 86% rename from lc3vm/src/ls3.cpp rename to lc3vm/src/lc3.cpp index d82f974..5854ebc 100644 --- a/lc3vm/src/ls3.cpp +++ b/lc3vm/src/lc3.cpp @@ -1,5 +1,5 @@ /** - * @file ls3.cpp + * @file lc3.cpp * @brief Implements the LC3State class methods for the LC-3 virtual machine. * * This file contains the definitions for the LC-3 instruction set, trap handlers, @@ -18,6 +18,7 @@ #include #include "traps.hpp" #include "flags.hpp" +#include "keyboard.hpp" #include #include #include @@ -135,58 +136,74 @@ void LC3State::ins(LC3State& state, std::uint16_t instr) { switch (instr & 0xFF) { case TRAP_GETC: { - char c_in = 0; - if (read(STDIN_FILENO, &c_in, 1) == 1) { - state.reg[R_R0] = static_cast(c_in); + if (state.memory.test_mode) { + state.reg[R_R0] = state.memory.read(Keyboard::MR_KBDR); + } else { + char c_in = 0; + if (read(STDIN_FILENO, &c_in, 1) == 1) { + state.reg[R_R0] = static_cast(c_in); + } } } state.update_flags(R_R0); break; case TRAP_OUT: - std::cout.put(static_cast(state.reg[R_R0])); - std::cout.flush(); + if (!state.memory.test_mode) { + std::cout.put(static_cast(state.reg[R_R0])); + std::cout.flush(); + } break; case TRAP_PUTS: { - std::uint16_t current_char_addr = state.reg[R_R0]; - std::uint16_t val = state.memory.read(current_char_addr); - while (val != 0) { - std::cout.put(static_cast(val)); - current_char_addr++; - val = state.memory.read(current_char_addr); + if (!state.memory.test_mode) { + std::uint16_t current_char_addr = state.reg[R_R0]; + std::uint16_t val = state.memory.read(current_char_addr); + while (val != 0) { + std::cout.put(static_cast(val)); + current_char_addr++; + val = state.memory.read(current_char_addr); + } + std::cout.flush(); } - std::cout.flush(); break; } case TRAP_IN: { - std::cout << "Enter a character: "; - std::cout.flush(); - char c_in_trap = 0; - if (read(STDIN_FILENO, &c_in_trap, 1) == 1) { - std::cout.put(c_in_trap); + if (state.memory.test_mode) { + state.reg[R_R0] = state.memory.read(Keyboard::MR_KBDR); + } else { + std::cout << "Enter a character: "; std::cout.flush(); - state.reg[R_R0] = static_cast(c_in_trap); + char c_in_trap = 0; + if (read(STDIN_FILENO, &c_in_trap, 1) == 1) { + std::cout.put(c_in_trap); + std::cout.flush(); + state.reg[R_R0] = static_cast(c_in_trap); + } } state.update_flags(R_R0); break; } case TRAP_PUTSP: { - std::uint16_t current_addr = state.reg[R_R0]; - std::uint16_t word = state.memory.read(current_addr); - while (word != 0) { - char char1 = word & 0xFF; - std::cout.put(char1); - char char2 = (word >> 8) & 0xFF; - if (char2) { - std::cout.put(char2); + if (!state.memory.test_mode) { + std::uint16_t current_addr = state.reg[R_R0]; + std::uint16_t word = state.memory.read(current_addr); + while (word != 0) { + char char1 = word & 0xFF; + std::cout.put(char1); + char char2 = (word >> 8) & 0xFF; + if (char2) { + std::cout.put(char2); + } + current_addr++; + word = state.memory.read(current_addr); } - current_addr++; - word = state.memory.read(current_addr); + std::cout.flush(); } - std::cout.flush(); break; } case TRAP_HALT: - std::cout << "HALT" << std::endl; + if (!state.memory.test_mode) { + std::cout << "HALT" << std::endl; + } state.running = false; break; default: diff --git a/lc3vm/src/memory.cpp b/lc3vm/src/memory.cpp index 305612c..b9ed263 100644 --- a/lc3vm/src/memory.cpp +++ b/lc3vm/src/memory.cpp @@ -31,6 +31,9 @@ std::uint16_t check_key() { std::uint16_t Memory::read(std::uint16_t address) { if (address == Keyboard::MR_KBSR) { + if (test_mode) { + return memory[Keyboard::MR_KBSR]; + } if (check_key()) { memory[Keyboard::MR_KBSR] = (1 << 15); char c_in; diff --git a/lc3vm/src/terminal_input.cpp b/lc3vm/src/terminal_input.cpp index 728b9c1..762193e 100644 --- a/lc3vm/src/terminal_input.cpp +++ b/lc3vm/src/terminal_input.cpp @@ -38,4 +38,8 @@ void disable_raw_mode() { } raw_mode_enabled = false; } +} + +bool is_raw_mode_enabled() { + return raw_mode_enabled; } \ No newline at end of file diff --git a/lc3vm/tests/test_integration.cpp b/lc3vm/tests/test_integration.cpp new file mode 100644 index 0000000..82d1179 --- /dev/null +++ b/lc3vm/tests/test_integration.cpp @@ -0,0 +1,399 @@ +#include +#include "lc3.hpp" +#include "registers.hpp" +#include "flags.hpp" +#include "keyboard.hpp" +#include "terminal_input.hpp" +#include +#include +#include +#include + + +static std::uint16_t swap16(std::uint16_t x) { + return (x << 8) | (x >> 8); +} + +class IntegrationTest : public ::testing::Test { +protected: + LC3State vm; + + void SetUp() override { + vm.set_register_value(R_PC, 0x3000); + vm.memory.test_mode = true; + } + + void TearDown() override { + disable_raw_mode(); + } + + void simulate_keyboard_input(char input) { + vm.write_memory(Keyboard::MR_KBSR, 0x8000); + vm.write_memory(Keyboard::MR_KBDR, input); + } +}; + +TEST_F(IntegrationTest, MemoryOperations) { + + vm.write_memory(0x3000, 0x1234); + EXPECT_EQ(vm.read_memory(0x3000), 0x1234); + + simulate_keyboard_input('A'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x8000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'A'); + + vm.write_memory(0xFFFF, 0x5678); + EXPECT_EQ(vm.read_memory(0xFFFF), 0x5678); + + vm.write_memory(0x4000, 0x0000); + EXPECT_EQ(vm.read_memory(0x4000), 0x0000); + vm.write_memory(0x4000, 0xFFFF); + EXPECT_EQ(vm.read_memory(0x4000), 0xFFFF); +} + +TEST_F(IntegrationTest, MemoryKeyboardStatus) { + vm.memory.test_mode = true; + + vm.write_memory(Keyboard::MR_KBSR, 0x0000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); + + vm.write_memory(Keyboard::MR_KBSR, 0x8000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x8000); + + vm.write_memory(Keyboard::MR_KBDR, 'X'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'X'); + + simulate_keyboard_input('Y'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x8000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'Y'); + + simulate_keyboard_input('Z'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x8000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'Z'); +} + +TEST_F(IntegrationTest, MemoryKeyboardNonTestMode) { + // In test mode, we need to explicitly set and clear the keyboard status + vm.memory.test_mode = true; + + // Test keyboard status when no input is available + vm.write_memory(Keyboard::MR_KBSR, 0x0000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); + + // Test keyboard status and data after input + simulate_keyboard_input('T'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x8000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'T'); + + // Test keyboard status after input is consumed + vm.write_memory(Keyboard::MR_KBSR, 0x0000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); +} + +TEST_F(IntegrationTest, MemoryKeyboardMultipleInputs) { + // In test mode, we need to explicitly set and clear the keyboard status + vm.memory.test_mode = true; + + const char inputs[] = "ABC"; + for (char input : inputs) { + // Clear keyboard status before each input + vm.write_memory(Keyboard::MR_KBSR, 0x0000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); + + // Simulate input + simulate_keyboard_input(input); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x8000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), input); + + // Clear keyboard status after input is consumed + vm.write_memory(Keyboard::MR_KBSR, 0x0000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); + } +} + +TEST_F(IntegrationTest, MemoryKeyboardTimeout) { + // In test mode, we need to explicitly set and clear the keyboard status + vm.memory.test_mode = true; + + // Test keyboard status with no input + vm.write_memory(Keyboard::MR_KBSR, 0x0000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); + + // Test multiple reads with no input + for (int i = 0; i < 5; i++) { + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); + } +} + +TEST_F(IntegrationTest, MemoryWriteReadPatterns) { + for (uint16_t i = 0; i < 16; i++) { + uint16_t value = 1 << i; + vm.write_memory(0x3000 + i, value); + EXPECT_EQ(vm.read_memory(0x3000 + i), value); + } + + for (uint16_t i = 0; i < 8; i++) { + uint16_t value = (i % 2) ? 0xAAAA : 0x5555; + vm.write_memory(0x4000 + i, value); + EXPECT_EQ(vm.read_memory(0x4000 + i), value); + } + + for (uint16_t i = 0; i < 256; i++) { + vm.write_memory(0x5000 + i, i); + EXPECT_EQ(vm.read_memory(0x5000 + i), i); + } +} + +TEST_F(IntegrationTest, MemoryEdgeCases) { + vm.write_memory(0x0000, 0x1234); + EXPECT_EQ(vm.read_memory(0x0000), 0x1234); + + vm.write_memory(0xFFFF, 0x5678); + EXPECT_EQ(vm.read_memory(0xFFFF), 0x5678); + + vm.write_memory(0x1000, 0xABCD); + vm.write_memory(0x1001, 0xEF01); + EXPECT_EQ(vm.read_memory(0x1000), 0xABCD); + EXPECT_EQ(vm.read_memory(0x1001), 0xEF01); + + vm.write_memory(0x2000, 0x0000); + vm.write_memory(0x2001, 0xFFFF); + vm.write_memory(0x2002, 0x5555); + vm.write_memory(0x2003, 0xAAAA); + EXPECT_EQ(vm.read_memory(0x2000), 0x0000); + EXPECT_EQ(vm.read_memory(0x2001), 0xFFFF); + EXPECT_EQ(vm.read_memory(0x2002), 0x5555); + EXPECT_EQ(vm.read_memory(0x2003), 0xAAAA); +} + +TEST_F(IntegrationTest, TerminalInput) { + + vm.write_memory(Keyboard::MR_KBSR, 0x8000); + vm.write_memory(Keyboard::MR_KBDR, 'A'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'A'); + + vm.write_memory(Keyboard::MR_KBSR, 0x0000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); + vm.write_memory(Keyboard::MR_KBSR, 0x8000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x8000); + + simulate_keyboard_input('B'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'B'); + simulate_keyboard_input('C'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'C'); +} + +TEST_F(IntegrationTest, TrapRoutines) { + // Test TRAP_GETC + vm.write_memory(0x3000, 0xF020); // TRAP_GETC + simulate_keyboard_input('B'); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R0), 'B'); + + // Test TRAP_OUT + vm.write_memory(0x3001, 0xF021); // TRAP_OUT + vm.set_register_value(R_R0, 'C'); + vm.step(); + + // Test TRAP_PUTS + vm.write_memory(0x3002, 0xF022); // TRAP_PUTS + vm.set_register_value(R_R0, 0x3100); + vm.write_memory(0x3100, 'H'); + vm.write_memory(0x3101, 'i'); + vm.write_memory(0x3102, 0x0000); + vm.step(); + + // Test TRAP_IN + vm.write_memory(0x3003, 0xF023); // TRAP_IN + simulate_keyboard_input('D'); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R0), 'D'); + + // Test TRAP_PUTSP + vm.write_memory(0x3004, 0xF024); // TRAP_PUTSP + vm.set_register_value(R_R0, 0x3200); + vm.write_memory(0x3200, 0x4142); // "AB" + vm.write_memory(0x3201, 0x0000); + vm.step(); + + // Test TRAP_HALT + vm.write_memory(0x3005, 0xF025); // TRAP_HALT + vm.step(); + EXPECT_FALSE(vm.is_running()); +} + +TEST_F(IntegrationTest, MemoryBoundaryConditions) { + vm.write_memory(0x0000, 0x1234); + EXPECT_EQ(vm.read_memory(0x0000), 0x1234); + + vm.write_memory(0xFFFF, 0x5678); + EXPECT_EQ(vm.read_memory(0xFFFF), 0x5678); + + for (uint16_t addr = 0x3000; addr < 0x3010; addr++) { + vm.write_memory(addr, addr); + EXPECT_EQ(vm.read_memory(addr), addr); + } +} + +TEST_F(IntegrationTest, KeyboardStatusTransitions) { + vm.write_memory(Keyboard::MR_KBSR, 0x0000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); + + simulate_keyboard_input('X'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x8000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'X'); + + vm.write_memory(Keyboard::MR_KBSR, 0x0000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x0000); + + simulate_keyboard_input('Y'); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBSR), 0x8000); + EXPECT_EQ(vm.read_memory(Keyboard::MR_KBDR), 'Y'); +} + +TEST_F(IntegrationTest, TerminalInputRawMode) { + // Skip terminal tests in CI environment + if (getenv("CI") || getenv("GITHUB_ACTIONS")) { + GTEST_SKIP() << "Skipping terminal tests in CI environment"; + } + + EXPECT_NO_THROW(enable_raw_mode()); + EXPECT_TRUE(is_raw_mode_enabled()); + + EXPECT_NO_THROW(disable_raw_mode()); + EXPECT_FALSE(is_raw_mode_enabled()); + + EXPECT_NO_THROW(enable_raw_mode()); + EXPECT_TRUE(is_raw_mode_enabled()); +} + +TEST_F(IntegrationTest, TerminalInputErrorHandling) { + // Skip terminal tests in CI environment + if (getenv("CI") || getenv("GITHUB_ACTIONS")) { + GTEST_SKIP() << "Skipping terminal tests in CI environment"; + } + + int original_stdin = dup(STDIN_FILENO); + ASSERT_NE(original_stdin, -1); + + close(STDIN_FILENO); + EXPECT_THROW(enable_raw_mode(), std::runtime_error); + + dup2(original_stdin, STDIN_FILENO); + close(original_stdin); + + EXPECT_NO_THROW(enable_raw_mode()); + EXPECT_TRUE(is_raw_mode_enabled()); +} + +TEST_F(IntegrationTest, TerminalInputModeTransitions) { + // Skip terminal tests in CI environment + if (getenv("CI") || getenv("GITHUB_ACTIONS")) { + GTEST_SKIP() << "Skipping terminal tests in CI environment"; + } + + for (int i = 0; i < 3; i++) { + EXPECT_NO_THROW(enable_raw_mode()); + EXPECT_TRUE(is_raw_mode_enabled()); + + EXPECT_NO_THROW(disable_raw_mode()); + EXPECT_FALSE(is_raw_mode_enabled()); + } + + EXPECT_NO_THROW(enable_raw_mode()); + EXPECT_TRUE(is_raw_mode_enabled()); + + for (int i = 0; i < 3; i++) { + EXPECT_NO_THROW(disable_raw_mode()); + EXPECT_FALSE(is_raw_mode_enabled()); + } +} + + +TEST_F(IntegrationTest, TrapRoutinesNonTestMode) { + vm.memory.test_mode = false; + + int pipefd[2]; + ASSERT_EQ(pipe(pipefd), 0); + + int original_stdin = dup(STDIN_FILENO); + ASSERT_NE(original_stdin, -1); + ASSERT_EQ(dup2(pipefd[0], STDIN_FILENO), STDIN_FILENO); + + // Test TRAP_GETC + const char getc_input = 'G'; + ASSERT_EQ(write(pipefd[1], &getc_input, 1), 1); + vm.write_memory(0x3000, 0xF020); // TRAP_GETC + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R0), getc_input); + + // Test TRAP_OUT + vm.write_memory(0x3001, 0xF021); // TRAP_OUT + vm.set_register_value(R_R0, 'O'); + vm.step(); + + // Test TRAP_PUTS + vm.write_memory(0x3002, 0xF022); // TRAP_PUTS + vm.set_register_value(R_R0, 0x3100); + vm.write_memory(0x3100, 'H'); + vm.write_memory(0x3101, 'i'); + vm.write_memory(0x3102, 0x0000); + vm.step(); + + // Test TRAP_IN + const char in_input = 'I'; + ASSERT_EQ(write(pipefd[1], &in_input, 1), 1); + vm.write_memory(0x3003, 0xF023); // TRAP_IN + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R0), in_input); + + // Test TRAP_PUTSP + vm.write_memory(0x3004, 0xF024); // TRAP_PUTSP + vm.set_register_value(R_R0, 0x3200); + vm.write_memory(0x3200, 0x4142); // "AB" + vm.write_memory(0x3201, 0x0000); + vm.step(); + + // Test TRAP_HALT + vm.write_memory(0x3005, 0xF025); // TRAP_HALT + vm.step(); + EXPECT_FALSE(vm.is_running()); + + ASSERT_EQ(dup2(original_stdin, STDIN_FILENO), STDIN_FILENO); + close(original_stdin); + close(pipefd[0]); + close(pipefd[1]); +} + +TEST_F(IntegrationTest, TrapRoutineErrors) { + // Test unknown TRAP vector + vm.write_memory(0x3000, 0xF0FF); // Invalid TRAP vector + EXPECT_THROW(vm.step(), std::runtime_error); + + // Test TRAP_PUTS with invalid memory + vm.write_memory(0x3000, 0xF022); // TRAP_PUTS + vm.set_register_value(R_R0, 0xFFFF); // Invalid memory address + EXPECT_NO_THROW(vm.step()); + + // Test TRAP_PUTSP with invalid memory + vm.write_memory(0x3001, 0xF024); // TRAP_PUTSP + vm.set_register_value(R_R0, 0xFFFF); // Invalid memory address + EXPECT_NO_THROW(vm.step()); +} + +TEST_F(IntegrationTest, Disassembly) { + vm.write_memory(0x3000, 0x1234); // ADD R1, R0, #-12 + EXPECT_EQ(vm.disassemble(0x3000), "0x3000: ADD R1, R0, #-12"); + + vm.write_memory(0x3001, 0x5678); // AND R3, R1, #-8 + EXPECT_EQ(vm.disassemble(0x3001), "0x3001: AND R3, R1, #-8"); + + vm.write_memory(0x3002, 0x9ABC); // NOT R5, R2 + EXPECT_EQ(vm.disassemble(0x3002), "0x3002: NOT R5, R2"); + + vm.write_memory(0x3003, 0x0000); // BR 0x3004 + EXPECT_EQ(vm.disassemble(0x3003), "0x3003: BR 0x3004"); + + vm.write_memory(0x3004, 0xF025); // HALT + EXPECT_EQ(vm.disassemble(0x3004), "0x3004: TRAP x25"); +} \ No newline at end of file