- Introduced comprehensive test plan to guide development and ensure coverage. - Documented test principles, execution harness, build steps, and test catalog. - Categorized test cases by functionality (e.g., filesystem I/O, PieceTable semantics, buffer editing, undo system, etc.). - Outlined regression tests and performance/stress scenarios. - Provided a phased roadmap for implementing planned test cases.
12 KiB
12 KiB
Unit testing plan (headless, no interactive frontend)
Principles
- Headless-only: exercise core components directly (
PieceTable,Buffer,UndoSystem,OptimizedSearch, and minimalEditorflows) without startingkteorkge. - Deterministic and fast: avoid timers, GUI, environment-specific behavior; prefer in-memory operations and temporary files.
- Regression-focused: encode prior failures (save/newline mismatch, legacy
rows_writes) as explicit tests to prevent recurrences.
Harness and execution
- Single binary: use target
kte_tests(already present) to compile and run all tests undertests/with the minimal in-tree framework (tests/Test.h,tests/TestRunner.cc). - No GUI/ncurses deps: link only engine sources (PieceTable/Buffer/Undo/Search/Undo* and syntax minimal set), not frontends.
- How to build/run:
- Debug profile:
cmake -S /Users/kyle/src/kte -B /Users/kyle/src/kte/cmake-build-debug -DBUILD_TESTS=ON && \ cmake --build /Users/kyle/src/kte/cmake-build-debug --target kte_tests && \ /Users/kyle/src/kte/cmake-build-debug/kte_tests - Release profile:
cmake -S /Users/kyle/src/kte -B /Users/kyle/src/kte/cmake-build-release -DBUILD_TESTS=ON && \ cmake --build /Users/kyle/src/kte/cmake-build-release --target kte_tests && \ /Users/kyle/src/kte/cmake-build-release/kte_tests
- Debug profile:
Test catalog (summary table)
The table below catalogs all unit tests defined in this plan. It is headless-only and maps directly to the suites A–H described later. “Implemented” reflects current coverage in kte_tests.
| Suite | ID | Name | Description (1‑line) | Headless | Implemented |
|---|---|---|---|---|---|
| A | 1 | SaveAs then Save (append) | New buffer → write two lines → SaveAs → append → Save; verify exact bytes. |
Yes | ✓ |
| A | 2 | Open existing then Save | Open seeded file, append, Save; verify overwrite bytes. |
Yes | ✓ |
| A | 3 | Open non-existent then SaveAs | Start from non-existent path, insert hello, world\n, SaveAs; verify bytes. |
Yes | ✓ |
| A | 4 | Trailing newline preservation | Verify saving preserves presence/absence of final \n. |
Yes | Planned |
| A | 5 | Empty buffer saves | Empty → SaveAs → 0 bytes; then insert \n → Save → 1 byte. |
Yes | Planned |
| A | 6 | Large file streaming | 1–4 MiB with periodic newlines; size and content integrity. | Yes | Planned |
| A | 7 | Tilde expansion | SaveAs with ~/...; re-open to confirm path/content. |
Yes | Planned |
| A | 8 | Error propagation | Save to unwritable path → expect failure and error message. | Yes | Planned |
| B | 1 | Insert/Delete LineCount | Basic inserts/deletes and line counting sanity. | Yes | ✓ |
| B | 2 | Line/Col conversions | LineColToByteOffset and reverse around boundaries. |
Yes | ✓ |
| B | 3 | Delete spanning newlines | Delete ranges that cross line breaks; verify bytes/lines. | Yes | Planned |
| B | 4 | Split/Join equivalence | split_line followed by join_lines yields original bytes. |
Yes | Planned |
| B | 5 | Stream vs Data equivalence | WriteToStream matches GetRange/Data() after edits. |
Yes | Planned |
| B | 6 | UTF‑8 bytes stability | Multibyte sequences behave correctly (byte-based ops). | Yes | Planned |
| C | 1 | insert_text/delete_text | Edits at start/middle/end; Rows() mirrors PieceTable. |
Yes | Planned |
| C | 2 | split_line/join_lines | Effects and snapshots across multiple positions. | Yes | Planned |
| C | 3 | insert_row/delete_row | Replace paragraph by row ops; verify bytes/linecount. | Yes | Planned |
| C | 4 | Cache invalidation | After each mutation, Rows() matches LineCount(). |
Yes | Planned |
| D | 1 | Grouped insert undo | Contiguous typing undone/redone as a group. | Yes | Planned |
| D | 2 | Delete/Newline undo/redo | Backspace/Delete and Newline transitions across undo/redo. | Yes | Planned |
| D | 3 | Mark saved & dirty | Dirty/save markers interact correctly with undo/redo. | Yes | Planned |
| E | 1 | Search parity basic | OptimizedSearch::find_all vs std::string reference. |
Yes | ✓ |
| E | 2 | Large text search | ~1 MiB random text/patterns parity. | Yes | Planned |
| F | 1 | Editor open & reload | Open via Editor, modify, reload, verify on-disk bytes. |
Yes | Planned |
| F | 2 | Read-only toggle | Toggle and verify enforcement/behavior of saves. | Yes | Planned |
| F | 3 | Prompt lifecycle | Start/Accept/Cancel prompt doesn’t corrupt state. | Yes | Planned |
| G | 1 | Saved only newline regression | Insert text + newline; Save includes both bytes. |
Yes | Planned |
| G | 2 | Backspace crash regression | PieceTable-backed delete/join path remains stable. | Yes | Planned |
| G | 3 | Overwrite-confirm path | Saving over existing path succeeds and is correct. | Yes | Planned |
| H | 1 | Many small edits | 10k small edits; final bytes correct within time bounds. | Yes | Planned |
| H | 2 | Consolidation equivalence | After many edits, stream vs data produce identical bytes. | Yes | Planned |
Legend: Implemented = ✓, Planned = to be added per Coverage roadmap.
Test suites and cases
A) Filesystem I/O via Buffer
- SaveAs then Save (append)
- New buffer →
insert_texttwo lines (explicit\n) →SaveAs(tmp)→ insert a third line →Save(). - Assert file bytes equal exact expected string.
- New buffer →
- Open existing then Save
- Seed a file on disk;
OpenFromFile(path)→ append line →Save(). - Assert file bytes updated exactly.
- Seed a file on disk;
- Open non-existent then SaveAs
OpenFromFile(nonexistent)→ assertIsFileBacked()==false→ insert"hello, world\n"→SaveAs(path).- Read back exact bytes.
- Trailing newline preservation
- Case (a) last line without
\n; (b) last line with\n→ save and verify bytes unchanged.
- Case (a) last line without
- Empty buffer saves
SaveAs(tmp)on empty buffer → 0-byte file. Then insert"\n"andSave()→ 1-byte file.
- Large file streaming
- Insert ~1–4 MiB of data with periodic newlines.
SaveAsthenSave; verify size matchescontent_.Size()and bytes integrity.
- Insert ~1–4 MiB of data with periodic newlines.
- Path normalization and tilde expansion
SaveAs("~/.../file.txt")→ verify path expands to$HOMEand file content round-trips withOpenFromFile.
- Error propagation (guarded)
- Attempt save into a non-writable path; expect
Save/SaveAsreturns false with non-empty error. Mark as skipped in environments lacking such path.
- Attempt save into a non-writable path; expect
B) PieceTable semantics
- Line counting and deletion across lines
- Insert
"abc\n123\nxyz"→ 3 lines; delete middle line range → 2 lines; validateGetLinecontents.
- Insert
- Position conversions
- Validate
LineColToByteOffsetandByteOffsetToLineColat start/end of lines and EOF, especially around\n.
- Validate
- Delete spanning newlines
- Remove a range that crosses line boundaries; verify resulting bytes,
LineCountand line contents.
- Remove a range that crosses line boundaries; verify resulting bytes,
- Split/join equivalence
- Split at various columns; then join adjacent lines; verify bytes equal original.
- WriteToStream vs materialized
Data()- After multiple inserts/deletes (without forcing
Data()), stream tostd::ostringstream; compare withGetRange(0, Size()), then callData()and re-compare.
- After multiple inserts/deletes (without forcing
- UTF-8 bytes stability
- Insert multibyte sequences (e.g.,
"héllo","中文", emoji) as raw bytes; ensure line counting and conversions behave (byte-based API; no crashes/corruption).
- Insert multibyte sequences (e.g.,
C) Buffer editing helpers and rows cache correctness
insert_text/delete_text- Apply at start/middle/end of lines; immediately call
Rows()and validate contents/lengths mirror PieceTable.
- Apply at start/middle/end of lines; immediately call
split_lineandjoin_lines- Verify content effects and
Rows()snapshots for multiple positions and consecutive operations.
- Verify content effects and
insert_row/delete_row- Replace a paragraph by deleting N rows then inserting N′ rows; verify bytes and
LineCount.
- Replace a paragraph by deleting N rows then inserting N′ rows; verify bytes and
- Cache invalidation
- After each mutation, fetch
Rows(); assertNrows() == content.LineCount()and no stale data remains.
- After each mutation, fetch
D) UndoSystem semantics
- Grouped contiguous insert undo
- Emulate typing at a single location via repeated
insert_text; oneundo()should remove the whole run;redo()restores it.
- Emulate typing at a single location via repeated
- Delete/newline undo/redo
- Simulate backspace/delete (
delete_textandjoin_lines) and newline (split_line); verify content transitions acrossundo()/redo().
- Simulate backspace/delete (
- Mark saved and dirty flag
- After successful save, call
UndoSystem::mark_saved()(via existing pathways) and ensure dirty state pairing behaves as intended (at least:SetDirty(false)plus save does not break undo/redo).
- After successful save, call
E) Search algorithms
- Parity with
std::string::find- Use
OptimizedSearch::find_allacross edge cases (empty needle/text, overlaps like"aaaaa"vs"aa", Unicode byte sequences). Compare to reference implementation.
- Use
- Large text
- Random ASCII text ~1 MiB; random patterns; results match reference.
F) Editor non-interactive flows (no frontend)
- Open and reload
- Through
Editor, open file; modify the underlyingBufferdirectly; invoke reload (Buffer::OpenFromFileorcmd_reload_bufferif you bringCommand.ccinto the test target). Verify bytes match the on-disk file after reload.
- Through
- Read-only toggle
- Toggle
Buffer::ToggleReadOnly(); confirm flag value changes and that subsequent saves still execute when not read-only (or, if enforcement exists, that mutations are appropriately restricted).
- Toggle
- Prompt lifecycle (headless)
- Exercise
StartPrompt→AcceptPrompt→CancelPrompt; ensure state resets and does not corrupt buffer/editor state.
- Exercise
G) Regression tests for reported bugs
- “Saved only newline”
- Build buffer content via
insert_textfollowed bysplit_linefor newline;Savethen validate bytes include both the text and newline.
- Build buffer content via
- Backspace crash path
- Mimic backspace behavior using PieceTable-backed helpers (
delete_text/join_lines); ensure no dependency on legacyrows_mutation and no memory issues.
- Mimic backspace behavior using PieceTable-backed helpers (
- Overwrite-confirm path behavior
- Start with non-file-backed buffer named to collide with an existing file; perform
SaveAs(existing_path)and assert success and correctness on disk (unit test bypasses interactive confirm, validating underlying write path).
- Start with non-file-backed buffer named to collide with an existing file; perform
H) Performance/stress sanity
- Many small edits
- 10k single-char inserts and interleaved deletes; assert final bytes; keep within conservative runtime bounds.
- Consolidation heuristics
- After many edits, call both
WriteToStreamandData()and verify identical bytes.
- After many edits, call both
Coverage roadmap
- Phase 1 (already implemented and passing):
- Buffer I/O basics (A.1–A.3), PieceTable basics (B.1–B.2), Search parity (E.1).
- Phase 2 (add next):
- Buffer I/O edge cases (A.4–A.7), deeper PieceTable ops (B.3–B.6), Buffer helpers and cache (C.1–C.4), Undo semantics (D.1–D.2), Regression set (G.1–G.3).
- Phase 3:
- Editor flows (F.1–F.3), performance/stress (H.1–H.2), and optional integration of
Command.ccinto the test target to exercise non-interactive command execution paths directly.
- Editor flows (F.1–F.3), performance/stress (H.1–H.2), and optional integration of
Notes
- Use per-test temp files under the repo root or a unique temp directory; ensure cleanup after assertions.
- For HOME-dependent tests (tilde expansion), set
HOMEin the test process if not present or skip with a clear message. - On macOS Debug, a benign allocator warning may appear; rely on process exit code for pass/fail.