Step 555: add breakpoint system with edit reconciliation
This commit is contained in:
@@ -3874,4 +3874,13 @@ target_link_libraries(step554_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step555_test tests/step555_test.cpp)
|
||||
target_include_directories(step555_test PRIVATE src)
|
||||
target_link_libraries(step555_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)
|
||||
|
||||
118
editor/src/BreakpointSystem.h
Normal file
118
editor/src/BreakpointSystem.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
// Step 555: Breakpoint System
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Breakpoint {
|
||||
std::string id;
|
||||
std::string filePath;
|
||||
int line = 0;
|
||||
bool enabled = true;
|
||||
std::string condition;
|
||||
int hitCountTarget = 0;
|
||||
int currentHits = 0;
|
||||
};
|
||||
|
||||
class BreakpointSystem {
|
||||
public:
|
||||
bool addLineBreakpoint(const std::string& id,
|
||||
const std::string& filePath,
|
||||
int line) {
|
||||
return addBreakpoint({id, filePath, line, true, "", 0, 0});
|
||||
}
|
||||
|
||||
bool addConditionalBreakpoint(const std::string& id,
|
||||
const std::string& filePath,
|
||||
int line,
|
||||
const std::string& condition) {
|
||||
return addBreakpoint({id, filePath, line, true, condition, 0, 0});
|
||||
}
|
||||
|
||||
bool addHitCountBreakpoint(const std::string& id,
|
||||
const std::string& filePath,
|
||||
int line,
|
||||
int hitCountTarget) {
|
||||
if (hitCountTarget <= 0) return false;
|
||||
return addBreakpoint({id, filePath, line, true, "", hitCountTarget, 0});
|
||||
}
|
||||
|
||||
bool enable(const std::string& id) {
|
||||
auto* bp = findMutable(id);
|
||||
if (!bp) return false;
|
||||
bp->enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool disable(const std::string& id) {
|
||||
auto* bp = findMutable(id);
|
||||
if (!bp) return false;
|
||||
bp->enabled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool remove(const std::string& id) {
|
||||
auto it = index_.find(id);
|
||||
if (it == index_.end()) return false;
|
||||
breakpoints_.erase(breakpoints_.begin() + static_cast<long>(it->second));
|
||||
index_.erase(it);
|
||||
rebuildIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool shouldBreak(const std::string& id, bool conditionEvaluatedTrue) {
|
||||
auto* bp = findMutable(id);
|
||||
if (!bp || !bp->enabled) return false;
|
||||
|
||||
if (!bp->condition.empty() && !conditionEvaluatedTrue) return false;
|
||||
|
||||
if (bp->hitCountTarget > 0) {
|
||||
++bp->currentHits;
|
||||
return bp->currentHits >= bp->hitCountTarget;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void reconcileOnFileEdit(const std::string& filePath,
|
||||
int fromLine,
|
||||
int lineDelta) {
|
||||
for (auto& bp : breakpoints_) {
|
||||
if (bp.filePath != filePath) continue;
|
||||
if (bp.line >= fromLine) {
|
||||
bp.line += lineDelta;
|
||||
if (bp.line < 1) bp.line = 1;
|
||||
}
|
||||
}
|
||||
rebuildIndex();
|
||||
}
|
||||
|
||||
std::vector<Breakpoint> list() const { return breakpoints_; }
|
||||
|
||||
private:
|
||||
std::vector<Breakpoint> breakpoints_;
|
||||
std::map<std::string, size_t> index_;
|
||||
|
||||
bool addBreakpoint(const Breakpoint& bp) {
|
||||
if (bp.id.empty() || bp.filePath.empty() || bp.line <= 0) return false;
|
||||
if (index_.count(bp.id) != 0) return false;
|
||||
breakpoints_.push_back(bp);
|
||||
index_[bp.id] = breakpoints_.size() - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
Breakpoint* findMutable(const std::string& id) {
|
||||
auto it = index_.find(id);
|
||||
if (it == index_.end()) return nullptr;
|
||||
return &breakpoints_[it->second];
|
||||
}
|
||||
|
||||
void rebuildIndex() {
|
||||
index_.clear();
|
||||
for (size_t i = 0; i < breakpoints_.size(); ++i) {
|
||||
index_[breakpoints_[i].id] = i;
|
||||
}
|
||||
}
|
||||
};
|
||||
150
editor/tests/step555_test.cpp
Normal file
150
editor/tests/step555_test.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
// Step 555: Breakpoint System (12 tests)
|
||||
|
||||
#include "BreakpointSystem.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 const Breakpoint* findBp(const std::vector<Breakpoint>& list, const std::string& id) {
|
||||
for (const auto& bp : list) if (bp.id == id) return &bp;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void test_add_line_breakpoint() {
|
||||
TEST(add_line_breakpoint);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "line breakpoint should add");
|
||||
CHECK(b.list().size() == 1, "one breakpoint expected");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_add_conditional_breakpoint() {
|
||||
TEST(add_conditional_breakpoint);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addConditionalBreakpoint("bp1", "a.cpp", 12, "x > 0"), "conditional breakpoint should add");
|
||||
auto bp = findBp(b.list(), "bp1");
|
||||
CHECK(bp && bp->condition == "x > 0", "condition should persist");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_add_hit_count_breakpoint() {
|
||||
TEST(add_hit_count_breakpoint);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addHitCountBreakpoint("bp1", "a.cpp", 5, 3), "hit-count breakpoint should add");
|
||||
auto bp = findBp(b.list(), "bp1");
|
||||
CHECK(bp && bp->hitCountTarget == 3, "hit target should persist");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_add_invalid_hit_count_breakpoint_fails() {
|
||||
TEST(add_invalid_hit_count_breakpoint_fails);
|
||||
BreakpointSystem b;
|
||||
CHECK(!b.addHitCountBreakpoint("bp1", "a.cpp", 5, 0), "zero hit count should fail");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_enable_disable_persistence() {
|
||||
TEST(enable_disable_persistence);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add should work");
|
||||
CHECK(b.disable("bp1"), "disable should work");
|
||||
auto bp = findBp(b.list(), "bp1");
|
||||
CHECK(bp && !bp->enabled, "breakpoint should be disabled");
|
||||
CHECK(b.enable("bp1"), "enable should work");
|
||||
bp = findBp(b.list(), "bp1");
|
||||
CHECK(bp && bp->enabled, "breakpoint should be re-enabled");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_should_break_simple_enabled_breakpoint() {
|
||||
TEST(should_break_simple_enabled_breakpoint);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add should work");
|
||||
CHECK(b.shouldBreak("bp1", true), "enabled line breakpoint should break");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_should_not_break_when_disabled() {
|
||||
TEST(should_not_break_when_disabled);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add should work");
|
||||
CHECK(b.disable("bp1"), "disable should work");
|
||||
CHECK(!b.shouldBreak("bp1", true), "disabled breakpoint should not break");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_conditional_breakpoint_requires_true_condition() {
|
||||
TEST(conditional_breakpoint_requires_true_condition);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addConditionalBreakpoint("bp1", "a.cpp", 10, "x > 0"), "add should work");
|
||||
CHECK(!b.shouldBreak("bp1", false), "false condition should not break");
|
||||
CHECK(b.shouldBreak("bp1", true), "true condition should break");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_hit_count_breakpoint_triggers_at_threshold() {
|
||||
TEST(hit_count_breakpoint_triggers_at_threshold);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addHitCountBreakpoint("bp1", "a.cpp", 10, 3), "add should work");
|
||||
CHECK(!b.shouldBreak("bp1", true), "hit1 should not break");
|
||||
CHECK(!b.shouldBreak("bp1", true), "hit2 should not break");
|
||||
CHECK(b.shouldBreak("bp1", true), "hit3 should break");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_reconcile_on_file_edit_shifts_lines() {
|
||||
TEST(reconcile_on_file_edit_shifts_lines);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add bp1");
|
||||
CHECK(b.addLineBreakpoint("bp2", "a.cpp", 20), "add bp2");
|
||||
b.reconcileOnFileEdit("a.cpp", 15, 3);
|
||||
auto bp1 = findBp(b.list(), "bp1");
|
||||
auto bp2 = findBp(b.list(), "bp2");
|
||||
CHECK(bp1 && bp1->line == 10, "bp1 should remain before edit line");
|
||||
CHECK(bp2 && bp2->line == 23, "bp2 should shift by +3");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_reconcile_clamps_line_to_one() {
|
||||
TEST(reconcile_clamps_line_to_one);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 2), "add should work");
|
||||
b.reconcileOnFileEdit("a.cpp", 1, -10);
|
||||
auto bp = findBp(b.list(), "bp1");
|
||||
CHECK(bp && bp->line == 1, "line should clamp to 1");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_remove_breakpoint() {
|
||||
TEST(remove_breakpoint);
|
||||
BreakpointSystem b;
|
||||
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add should work");
|
||||
CHECK(b.remove("bp1"), "remove should work");
|
||||
CHECK(b.list().empty(), "list should be empty after remove");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 555: Breakpoint System\n";
|
||||
|
||||
test_add_line_breakpoint(); // 1
|
||||
test_add_conditional_breakpoint(); // 2
|
||||
test_add_hit_count_breakpoint(); // 3
|
||||
test_add_invalid_hit_count_breakpoint_fails(); // 4
|
||||
test_enable_disable_persistence(); // 5
|
||||
test_should_break_simple_enabled_breakpoint(); // 6
|
||||
test_should_not_break_when_disabled(); // 7
|
||||
test_conditional_breakpoint_requires_true_condition(); // 8
|
||||
test_hit_count_breakpoint_triggers_at_threshold(); // 9
|
||||
test_reconcile_on_file_edit_shifts_lines(); // 10
|
||||
test_reconcile_clamps_line_to_one(); // 11
|
||||
test_remove_breakpoint(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
34
progress.md
34
progress.md
@@ -9802,3 +9802,37 @@ resume/stop transitions and multi-target per-buffer context support.
|
||||
- `editor/src/DebugSessionModel.h` within header-size limit (`106` <= `600`)
|
||||
- `editor/tests/step554_test.cpp` within test-file size guidance (`140` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 555: Breakpoint System
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implements breakpoint management for line, conditional, and hit-count
|
||||
breakpoints, including enable/disable persistence and line reconciliation across
|
||||
file edits.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/BreakpointSystem.h` - breakpoint subsystem:
|
||||
- line, conditional, and hit-count breakpoint creation
|
||||
- enable/disable/remove operations
|
||||
- runtime break-decision evaluation (condition + hit-count semantics)
|
||||
- file-edit reconciliation with line-shift/clamp behavior
|
||||
- `editor/tests/step555_test.cpp` - 12 tests covering:
|
||||
- breakpoint creation variants and invalid input guardrails
|
||||
- enable/disable persistence behavior
|
||||
- simple/conditional/hit-count break evaluation behavior
|
||||
- file-edit reconciliation behavior
|
||||
- removal behavior
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` - `step555_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake -S editor -B editor/build-native` - PASS
|
||||
- `cmake --build editor/build-native --target step555_test step554_test` - PASS
|
||||
- `./editor/build-native/step555_test` - PASS (12/12)
|
||||
- `./editor/build-native/step554_test` - PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `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`
|
||||
|
||||
Reference in New Issue
Block a user