Step 556: add deterministic debug step engine

This commit is contained in:
Bill
2026-02-17 10:29:55 -07:00
parent a3cfbb8f58
commit 3f75daf8ba
4 changed files with 333 additions and 0 deletions

View File

@@ -3883,4 +3883,13 @@ target_link_libraries(step555_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step556_test tests/step556_test.cpp)
target_include_directories(step556_test PRIVATE src)
target_link_libraries(step556_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

135
editor/src/StepEngine.h Normal file
View File

@@ -0,0 +1,135 @@
#pragma once
// Step 556: Step Engine
#include <string>
#include <vector>
enum class DebugRunState {
Running,
Paused,
Completed
};
struct StepEngineState {
DebugRunState runState = DebugRunState::Paused;
int stackDepth = 1;
int instructionPointer = 0;
std::string lastCommand;
};
struct StepResult {
bool ok = false;
StepEngineState state;
std::vector<std::string> diagnostics;
};
class StepEngine {
public:
StepEngine() = default;
const StepEngineState& state() const { return state_; }
StepResult execute(const std::string& command) {
StepResult out;
out.state = state_;
if (state_.runState == DebugRunState::Completed) {
out.ok = false;
out.diagnostics.push_back("session_completed");
return out;
}
if (command == "continue") {
return onContinue();
}
if (command == "step_over") {
return onStepOver();
}
if (command == "step_into") {
return onStepInto();
}
if (command == "step_out") {
return onStepOut();
}
out.ok = false;
out.diagnostics.push_back("unknown_command");
return out;
}
bool pause() {
if (state_.runState == DebugRunState::Completed) return false;
state_.runState = DebugRunState::Paused;
return true;
}
private:
StepEngineState state_;
StepResult onContinue() {
StepResult out;
state_.runState = DebugRunState::Running;
state_.instructionPointer += 5;
state_.lastCommand = "continue";
// Deterministic stop point after "running" burst.
state_.runState = DebugRunState::Paused;
out.ok = true;
out.state = state_;
return out;
}
StepResult onStepOver() {
StepResult out;
if (state_.runState != DebugRunState::Paused) {
out.ok = false;
out.diagnostics.push_back("must_be_paused");
out.state = state_;
return out;
}
state_.instructionPointer += 1;
state_.lastCommand = "step_over";
out.ok = true;
out.state = state_;
return out;
}
StepResult onStepInto() {
StepResult out;
if (state_.runState != DebugRunState::Paused) {
out.ok = false;
out.diagnostics.push_back("must_be_paused");
out.state = state_;
return out;
}
state_.instructionPointer += 1;
state_.stackDepth += 1;
state_.lastCommand = "step_into";
out.ok = true;
out.state = state_;
return out;
}
StepResult onStepOut() {
StepResult out;
if (state_.runState != DebugRunState::Paused) {
out.ok = false;
out.diagnostics.push_back("must_be_paused");
out.state = state_;
return out;
}
if (state_.stackDepth <= 1) {
state_.runState = DebugRunState::Completed;
state_.lastCommand = "step_out";
out.ok = true;
out.state = state_;
out.diagnostics.push_back("session_completed");
return out;
}
state_.stackDepth -= 1;
state_.instructionPointer += 1;
state_.lastCommand = "step_out";
out.ok = true;
out.state = state_;
return out;
}
};

View File

@@ -0,0 +1,154 @@
// Step 556: Step Engine (12 tests)
#include "StepEngine.h"
#include <iostream>
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
static bool hasDiag(const StepResult& r, const std::string& d) {
for (const auto& x : r.diagnostics) if (x == d) return true;
return false;
}
void test_step_over_advances_instruction_pointer() {
TEST(step_over_advances_instruction_pointer);
StepEngine e;
auto before = e.state().instructionPointer;
auto r = e.execute("step_over");
CHECK(r.ok, "step_over should succeed");
CHECK(r.state.instructionPointer == before + 1, "ip should advance by one");
PASS();
}
void test_step_into_advances_ip_and_stack_depth() {
TEST(step_into_advances_ip_and_stack_depth);
StepEngine e;
auto r = e.execute("step_into");
CHECK(r.ok, "step_into should succeed");
CHECK(r.state.instructionPointer == 1, "ip should advance");
CHECK(r.state.stackDepth == 2, "stack depth should increase");
PASS();
}
void test_step_out_reduces_stack_depth_when_nested() {
TEST(step_out_reduces_stack_depth_when_nested);
StepEngine e;
CHECK(e.execute("step_into").ok, "step_into should nest");
auto r = e.execute("step_out");
CHECK(r.ok, "step_out should succeed");
CHECK(r.state.stackDepth == 1, "stack should reduce");
PASS();
}
void test_step_out_from_root_completes_session() {
TEST(step_out_from_root_completes_session);
StepEngine e;
auto r = e.execute("step_out");
CHECK(r.ok, "step_out root should succeed");
CHECK(r.state.runState == DebugRunState::Completed, "session should complete");
CHECK(hasDiag(r, "session_completed"), "completion diag expected");
PASS();
}
void test_continue_runs_and_pauses_deterministically() {
TEST(continue_runs_and_pauses_deterministically);
StepEngine e;
auto before = e.state().instructionPointer;
auto r = e.execute("continue");
CHECK(r.ok, "continue should succeed");
CHECK(r.state.runState == DebugRunState::Paused, "engine should return to paused state");
CHECK(r.state.instructionPointer == before + 5, "continue should advance by deterministic burst");
PASS();
}
void test_unknown_command_fails() {
TEST(unknown_command_fails);
StepEngine e;
auto r = e.execute("teleport");
CHECK(!r.ok, "unknown command should fail");
CHECK(hasDiag(r, "unknown_command"), "unknown command diag expected");
PASS();
}
void test_pause_keeps_engine_paused() {
TEST(pause_keeps_engine_paused);
StepEngine e;
CHECK(e.pause(), "pause should succeed");
CHECK(e.state().runState == DebugRunState::Paused, "engine should remain paused");
PASS();
}
void test_commands_fail_after_completion() {
TEST(commands_fail_after_completion);
StepEngine e;
CHECK(e.execute("step_out").ok, "root step_out completes session");
auto r = e.execute("step_over");
CHECK(!r.ok, "commands should fail after completion");
CHECK(hasDiag(r, "session_completed"), "completion diag expected");
PASS();
}
void test_last_command_tracks_step_actions() {
TEST(last_command_tracks_step_actions);
StepEngine e;
CHECK(e.execute("step_into").ok, "step_into should succeed");
CHECK(e.state().lastCommand == "step_into", "last command mismatch");
CHECK(e.execute("step_over").ok, "step_over should succeed");
CHECK(e.state().lastCommand == "step_over", "last command mismatch");
PASS();
}
void test_continue_updates_last_command() {
TEST(continue_updates_last_command);
StepEngine e;
CHECK(e.execute("continue").ok, "continue should succeed");
CHECK(e.state().lastCommand == "continue", "last command should be continue");
PASS();
}
void test_multiple_step_into_accumulates_stack_depth() {
TEST(multiple_step_into_accumulates_stack_depth);
StepEngine e;
CHECK(e.execute("step_into").ok, "step_into #1");
CHECK(e.execute("step_into").ok, "step_into #2");
CHECK(e.state().stackDepth == 3, "stack depth should accumulate");
PASS();
}
void test_instruction_pointer_progression_is_monotonic() {
TEST(instruction_pointer_progression_is_monotonic);
StepEngine e;
CHECK(e.execute("step_over").ok, "step_over");
int a = e.state().instructionPointer;
CHECK(e.execute("step_into").ok, "step_into");
int b = e.state().instructionPointer;
CHECK(e.execute("continue").ok, "continue");
int c = e.state().instructionPointer;
CHECK(a < b && b < c, "ip progression should be monotonic");
PASS();
}
int main() {
std::cout << "Step 556: Step Engine\n";
test_step_over_advances_instruction_pointer(); // 1
test_step_into_advances_ip_and_stack_depth(); // 2
test_step_out_reduces_stack_depth_when_nested(); // 3
test_step_out_from_root_completes_session(); // 4
test_continue_runs_and_pauses_deterministically(); // 5
test_unknown_command_fails(); // 6
test_pause_keeps_engine_paused(); // 7
test_commands_fail_after_completion(); // 8
test_last_command_tracks_step_actions(); // 9
test_continue_updates_last_command(); // 10
test_multiple_step_into_accumulates_stack_depth(); // 11
test_instruction_pointer_progression_is_monotonic(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -9836,3 +9836,38 @@ file edits.
- `editor/src/BreakpointSystem.h` within header-size limit (`118` <= `600`)
- `editor/tests/step555_test.cpp` within test-file size guidance (`150` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
### Step 556: Step Engine
**Status:** PASS (12/12 tests)
Implements deterministic stepping semantics for debug control with `step_into`,
`step_over`, `step_out`, and `continue` command mapping and explicit runtime
state transitions.
**Files added:**
- `editor/src/StepEngine.h` - stepping control-plane module:
- deterministic command execution mapping
- state transitions across paused/running/completed states
- stack depth and instruction pointer progression semantics
- command validity guards for unknown/invalid lifecycle states
- `editor/tests/step556_test.cpp` - 12 tests covering:
- per-command transition behavior
- root/ nested `step_out` behavior
- deterministic continue behavior
- unknown command behavior
- completion-state command guard behavior
- command tracking and progression monotonicity
**Files modified:**
- `editor/CMakeLists.txt` - `step556_test` target
**Verification run:**
- `cmake -S editor -B editor/build-native` - PASS
- `cmake --build editor/build-native --target step556_test step555_test` - PASS
- `./editor/build-native/step556_test` - PASS (12/12)
- `./editor/build-native/step555_test` - PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/StepEngine.h` within header-size limit (`135` <= `600`)
- `editor/tests/step556_test.cpp` within test-file size guidance (`154` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`