From 9df08cbe18cd825348d97bf6e8b4ce35ca092695 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 7 Feb 2026 08:24:23 -0700 Subject: [PATCH] Step 56 TDD test: Robust Emacs command integration Tests ElispCommandBuilder for escaping, findFile/saveBuffer Elisp generation, paths with spaces, and EmacsConnection daemon management with ensureDaemon and error handling. Co-Authored-By: Claude Opus 4.6 --- editor/CMakeLists.txt | 3 + editor/tests/step56_test.cpp | 132 +++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 editor/tests/step56_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 2288f91..2ba3599 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -163,6 +163,9 @@ target_include_directories(step51_test PRIVATE src) add_executable(step53_test tests/step53_test.cpp) target_include_directories(step53_test PRIVATE src) +add_executable(step56_test tests/step56_test.cpp) +target_include_directories(step56_test PRIVATE src) + add_executable(whetstone_editor src/main.cpp) target_include_directories(whetstone_editor PRIVATE src) # find_package(SDL2 REQUIRED) # Commented out for now to avoid build issues diff --git a/editor/tests/step56_test.cpp b/editor/tests/step56_test.cpp new file mode 100644 index 0000000..af09a97 --- /dev/null +++ b/editor/tests/step56_test.cpp @@ -0,0 +1,132 @@ +// Step 56 TDD Test: Robust Emacs command integration +// +// Tests that the Emacs integration handles commands properly: +// 1. Elisp command builder escapes special characters +// 2. Emacs commands trigger orchestrator RPCs (loadFile, saveFile) +// 3. Error handling: detect daemon crash +// 4. Auto-restart on daemon failure +// 5. Command builder produces valid Elisp strings +// +// Will fail until the robust command integration is implemented. + +#include +#include +#include + +static bool contains(const std::string& haystack, const std::string& needle) { + return haystack.find(needle) != std::string::npos; +} + +// Forward declaration — ElispCommandBuilder that doesn't exist yet +class ElispCommandBuilder { +public: + // Build an Elisp expression to open a file + static std::string findFile(const std::string& path); + + // Build an Elisp expression to save current buffer + static std::string saveBuffer(); + + // Build an Elisp expression to evaluate arbitrary Elisp + static std::string eval(const std::string& elisp); + + // Escape special characters in strings for Elisp embedding + static std::string escapeString(const std::string& str); +}; + +// Forward declaration — EmacsConnection for daemon management +class EmacsConnection { +public: + // Start Emacs daemon + bool startDaemon(); + + // Check if daemon is running + bool isDaemonAlive() const; + + // Send command to Emacs and get result + std::string sendCommand(const std::string& elispCommand); + + // Auto-restart daemon if it died + bool ensureDaemon(); + + // Get the last error (if sendCommand failed) + std::string getLastError() const; +}; + +int main() { + int passed = 0; + int failed = 0; + + // --- Test 1: ElispCommandBuilder escapes special characters --- + { + std::string escaped = ElispCommandBuilder::escapeString("path\\with\"quotes"); + assert(contains(escaped, "\\\\") && "Backslashes should be escaped"); + assert(contains(escaped, "\\\"") && "Quotes should be escaped"); + + std::cout << "Test 1 PASS: Special characters escaped correctly" << std::endl; + ++passed; + } + + // --- Test 2: findFile builds correct Elisp --- + { + std::string cmd = ElispCommandBuilder::findFile("/home/user/test.py"); + assert(contains(cmd, "find-file") && "Should use find-file command"); + assert(contains(cmd, "/home/user/test.py") && "Should contain the file path"); + // Should be wrapped in parentheses for valid Elisp + assert(cmd.front() == '(' && "Should start with '('"); + + std::cout << "Test 2 PASS: findFile builds valid Elisp" << std::endl; + ++passed; + } + + // --- Test 3: saveBuffer builds correct Elisp --- + { + std::string cmd = ElispCommandBuilder::saveBuffer(); + assert(contains(cmd, "save-buffer") && "Should use save-buffer command"); + assert(cmd.front() == '(' && "Should start with '('"); + + std::cout << "Test 3 PASS: saveBuffer builds valid Elisp" << std::endl; + ++passed; + } + + // --- Test 4: File paths with spaces are properly escaped --- + { + std::string cmd = ElispCommandBuilder::findFile("/home/user/my project/test.py"); + assert(contains(cmd, "my project") && "Path with spaces should be preserved"); + // The path should be inside a string literal + assert(contains(cmd, "\"") && "Path should be in a string literal"); + + std::cout << "Test 4 PASS: Paths with spaces handled correctly" << std::endl; + ++passed; + } + + // --- Test 5: EmacsConnection.ensureDaemon starts daemon if not running --- + { + EmacsConnection conn; + // Initially daemon may not be running + bool result = conn.ensureDaemon(); + assert(result && "ensureDaemon should succeed (start daemon if needed)"); + assert(conn.isDaemonAlive() && "Daemon should be alive after ensureDaemon"); + + std::cout << "Test 5 PASS: ensureDaemon starts daemon if needed" << std::endl; + ++passed; + } + + // --- Test 6: sendCommand returns error on invalid Elisp --- + { + EmacsConnection conn; + conn.ensureDaemon(); + + std::string result = conn.sendCommand("(this-is-not-a-real-function)"); + std::string error = conn.getLastError(); + // Should have some error indication (either in result or error string) + assert(!error.empty() || contains(result, "error") || contains(result, "void") && + "Invalid command should produce an error"); + + std::cout << "Test 6 PASS: Invalid Elisp returns error" << std::endl; + ++passed; + } + + // --- Summary --- + std::cout << "\n=== Step 56 Results: " << passed << " passed, " << failed << " failed ===" << std::endl; + return failed > 0 ? 1 : 0; +}