Step 556: add deterministic debug step engine
This commit is contained in:
@@ -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
135
editor/src/StepEngine.h
Normal 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;
|
||||
}
|
||||
};
|
||||
154
editor/tests/step556_test.cpp
Normal file
154
editor/tests/step556_test.cpp
Normal 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;
|
||||
}
|
||||
35
progress.md
35
progress.md
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user