Pad/internal/io/pool/task_test.go
Greg Pomerantz 615957196e feat: virtual scrolling with chunked buffer for large files
- Add ChunkedBuffer for 64KB chunked file access with dirty-chunk
  eviction protection
- Add LineIndex for precise byte-offset-to-line-number mapping
- Refactor IO task system with context cancellation, typed priorities,
  and new task types (ReadChunk, BuildLineIndex, StatFile)
- Add ReadFileAt to FileSystem interface (mock + real implementations)
- Integrate virtual scrolling into editor layout
- Add comprehensive tests for chunked buffer eviction, dirty-chunk
  safety, and full edit lifecycle
2026-06-05 09:11:32 -04:00

417 lines
10 KiB
Go

package pool
import (
"testing"
"time"
"pad/internal/io/pool/mock"
"pad/internal/io/pool/types"
)
func TestReadDirTask_Execute_Success(t *testing.T) {
fs := mock.NewFileSystem()
fs.CreateDir("/test/dir")
fs.CreateFile("/test/dir/a.txt", []byte("a"))
fs.CreateFile("/test/dir/b.txt", []byte("b"))
task := NewReadDirTask("/test/dir", fs)
result := task.Execute()
if !result.IsSuccess() {
t.Fatalf("Expected success, got error: %v", result.Error)
}
if result.TaskType != TypeReadDir {
t.Errorf("TaskType = %v, want %v", result.TaskType, TypeReadDir)
}
if result.TaskID == "" {
t.Error("TaskID should not be empty")
}
entries, ok := result.Data.([]types.DirEntry)
if !ok {
t.Fatalf("Data is not []types.DirEntry, got %T", result.Data)
}
if len(entries) != 2 {
t.Errorf("Expected 2 entries, got %d", len(entries))
}
}
func TestReadDirTask_Execute_NotFound(t *testing.T) {
fs := mock.NewFileSystem()
task := NewReadDirTask("/nonexistent", fs)
result := task.Execute()
if result.IsSuccess() {
t.Fatal("Expected failure for nonexistent directory")
}
if result.Error == nil {
t.Error("Expected error, got nil")
}
}
func TestBuildIndexTask_Execute(t *testing.T) {
fs := mock.NewFileSystem()
fs.CreateDir("/test/dir")
fs.CreateFile("/test/dir/file.txt", []byte("content"))
task := NewBuildIndexTask("/test/dir", fs)
result := task.Execute()
if !result.IsSuccess() {
t.Fatalf("Expected success, got error: %v", result.Error)
}
if result.TaskType != TypeBuildIndex {
t.Errorf("TaskType = %v, want %v", result.TaskType, TypeBuildIndex)
}
}
func TestLoadPagesTask_Execute(t *testing.T) {
fs := mock.NewFileSystem()
task := NewLoadPagesTask("/test/dir", []int{0, 1, 2}, fs)
result := task.Execute()
if !result.IsSuccess() {
t.Fatalf("Expected success, got error: %v", result.Error)
}
if result.TaskType != TypeLoadPages {
t.Errorf("TaskType = %v, want %v", result.TaskType, TypeLoadPages)
}
pages, ok := result.Data.([]int)
if !ok {
t.Fatalf("Data is not []int, got %T", result.Data)
}
if len(pages) != 3 {
t.Errorf("Expected 3 pages, got %d", len(pages))
}
}
func TestReadFileTask_Execute_Success(t *testing.T) {
fs := mock.NewFileSystem()
fs.CreateFile("/test/file.txt", []byte("hello world"))
task := NewReadFileTask("/test/file.txt", fs)
result := task.Execute()
if !result.IsSuccess() {
t.Fatalf("Expected success, got error: %v", result.Error)
}
if result.TaskType != TypeReadFile {
t.Errorf("TaskType = %v, want %v", result.TaskType, TypeReadFile)
}
content, ok := result.Data.([]byte)
if !ok {
t.Fatalf("Data is not []byte, got %T", result.Data)
}
if string(content) != "hello world" {
t.Errorf("Content = %q, want %q", string(content), "hello world")
}
}
func TestReadFileTask_Execute_NotFound(t *testing.T) {
fs := mock.NewFileSystem()
task := NewReadFileTask("/nonexistent.txt", fs)
result := task.Execute()
if result.IsSuccess() {
t.Fatal("Expected failure for nonexistent file")
}
}
func TestWriteFileTask_Execute(t *testing.T) {
fs := mock.NewFileSystem()
fs.CreateFile("/test/file.txt", []byte("original"))
task := NewWriteFileTask("/test/file.txt", []byte("updated"), fs)
result := task.Execute()
if !result.IsSuccess() {
t.Fatalf("Expected success, got error: %v", result.Error)
}
if result.TaskType != TypeWriteFile {
t.Errorf("TaskType = %v, want %v", result.TaskType, TypeWriteFile)
}
// Verify file was actually written
content, err := fs.ReadFile("/test/file.txt")
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if string(content) != "updated" {
t.Errorf("File content = %q, want %q", string(content), "updated")
}
}
func TestWriteFileTask_Execute_CreatesFile(t *testing.T) {
fs := mock.NewFileSystem()
task := NewWriteFileTask("/newfile.txt", []byte("data"), fs)
result := task.Execute()
if !result.IsSuccess() {
t.Fatalf("Expected success (write creates file), got error: %v", result.Error)
}
// Verify the file was actually created
content, err := fs.ReadFile("/newfile.txt")
if err != nil {
t.Fatalf("File should exist after write: %v", err)
}
if string(content) != "data" {
t.Errorf("Content = %q, want %q", string(content), "data")
}
}
func TestSaveStateTask_Execute(t *testing.T) {
fs := mock.NewFileSystem()
fs.CreateFile("/test/state.json", []byte("{}"))
task := NewSaveStateTask("/test/state.json", []byte(`{"key":"value"}`), fs)
result := task.Execute()
if !result.IsSuccess() {
t.Fatalf("Expected success, got error: %v", result.Error)
}
if result.TaskType != TypeSaveState {
t.Errorf("TaskType = %v, want %v", result.TaskType, TypeSaveState)
}
// Verify state was saved
content, err := fs.ReadFile("/test/state.json")
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if string(content) != `{"key":"value"}` {
t.Errorf("State content = %q, want %q", string(content), `{"key":"value"}`)
}
}
func TestSaveUndoTask_Execute(t *testing.T) {
fs := mock.NewFileSystem()
fs.CreateFile("/test/undo.json", []byte("[]"))
task := NewSaveUndoTask("/test/undo.json", []byte(`[{"type":"insert"}]`), fs)
result := task.Execute()
if !result.IsSuccess() {
t.Fatalf("Expected success, got error: %v", result.Error)
}
if result.TaskType != TypeSaveUndo {
t.Errorf("TaskType = %v, want %v", result.TaskType, TypeSaveUndo)
}
}
func TestTask_Priority_ReadDir(t *testing.T) {
fs := mock.NewFileSystem()
task := NewReadDirTask("/test", fs)
if task.Priority() != HighPriority {
t.Errorf("Priority = %v, want %v", task.Priority(), HighPriority)
}
}
func TestTask_Priority_BuildIndex(t *testing.T) {
fs := mock.NewFileSystem()
task := NewBuildIndexTask("/test", fs)
if task.Priority() != HighPriority {
t.Errorf("Priority = %v, want %v", task.Priority(), HighPriority)
}
}
func TestTask_Priority_LoadPages(t *testing.T) {
fs := mock.NewFileSystem()
task := NewLoadPagesTask("/test", []int{0}, fs)
if task.Priority() != HighPriority {
t.Errorf("Priority = %v, want %v", task.Priority(), HighPriority)
}
}
func TestTask_Priority_ReadFile(t *testing.T) {
fs := mock.NewFileSystem()
task := NewReadFileTask("/test/file.txt", fs)
if task.Priority() != HighPriority {
t.Errorf("Priority = %v, want %v", task.Priority(), HighPriority)
}
}
func TestTask_Priority_WriteFile(t *testing.T) {
fs := mock.NewFileSystem()
task := NewWriteFileTask("/test/file.txt", []byte("data"), fs)
if task.Priority() != LowPriority {
t.Errorf("Priority = %v, want %v", task.Priority(), LowPriority)
}
}
func TestTask_Priority_SaveState(t *testing.T) {
fs := mock.NewFileSystem()
task := NewSaveStateTask("/test/state.json", []byte("{}"), fs)
if task.Priority() != LowPriority {
t.Errorf("Priority = %v, want %v", task.Priority(), LowPriority)
}
}
func TestTask_Priority_SaveUndo(t *testing.T) {
fs := mock.NewFileSystem()
task := NewSaveUndoTask("/test/undo.json", []byte("[]"), fs)
if task.Priority() != LowPriority {
t.Errorf("Priority = %v, want %v", task.Priority(), LowPriority)
}
}
func TestTask_DirPath(t *testing.T) {
fs := mock.NewFileSystem()
task := NewReadDirTask("/test/dir", fs)
if task.DirPath() != "/test/dir" {
t.Errorf("DirPath = %q, want %q", task.DirPath(), "/test/dir")
}
task2 := NewReadFileTask("/test/dir/file.txt", fs)
if task2.DirPath() != "/test/dir" {
t.Errorf("DirPath = %q, want %q", task2.DirPath(), "/test/dir")
}
}
func TestTask_TaskID_Unique(t *testing.T) {
fs := mock.NewFileSystem()
task1 := NewReadDirTask("/test/dir", fs)
task2 := NewReadDirTask("/test/dir", fs)
if task1.TaskID() == task2.TaskID() {
t.Error("Task IDs should be unique")
}
}
func TestTask_TaskType_String(t *testing.T) {
tests := []struct {
taskType TaskType
want string
}{
{TypeReadDir, "read_dir"},
{TypeBuildIndex, "build_index"},
{TypeLoadIndex, "load_index"},
{TypeLoadPages, "load_pages"},
{TypeStatDir, "stat_dir"},
{TypeReadFile, "read_file"},
{TypeWriteFile, "write_file"},
{TypeStatFile, "stat_file"},
{TypeWriteCache, "write_cache"},
{TypeReadCache, "read_cache"},
{TypeInvalidate, "invalidate_cache"},
{TypeSaveState, "save_state"},
{TypeSaveUndo, "save_undo"},
}
for _, tt := range tests {
if tt.taskType.String() != tt.want {
t.Errorf("TaskType %v = %q, want %q", tt.taskType, tt.taskType.String(), tt.want)
}
}
}
func TestPriority_String(t *testing.T) {
tests := []struct {
priority Priority
want string
}{
{HighPriority, "high"},
{LowPriority, "low"},
}
for _, tt := range tests {
if tt.priority.String() != tt.want {
t.Errorf("Priority %v.String() = %q, want %q", tt.priority, tt.priority.String(), tt.want)
}
}
}
func TestReadFileTask_WithDelay(t *testing.T) {
fs := mock.NewFileSystem()
fs.SetDelay(10 * time.Millisecond)
fs.CreateFile("/test/delayed.txt", []byte("delayed"))
task := NewReadFileTask("/test/delayed.txt", fs)
start := time.Now()
result := task.Execute()
elapsed := time.Since(start)
if !result.IsSuccess() {
t.Fatalf("Expected success, got error: %v", result.Error)
}
if elapsed < 5*time.Millisecond {
t.Errorf("Expected delay ~10ms, got %v", elapsed)
}
}
func TestCreateFile_WithDelay(t *testing.T) {
fs := mock.NewFileSystem()
fs.SetDelay(20 * time.Millisecond)
start := time.Now()
err := fs.CreateFile("/test/delayed.txt", []byte("test"))
elapsed := time.Since(start)
if err != nil {
t.Fatalf("CreateFile failed: %v", err)
}
if elapsed < 15*time.Millisecond {
t.Errorf("Expected delay ~20ms, got %v", elapsed)
}
}
func TestWriteFile_WithDelay(t *testing.T) {
fs := mock.NewFileSystem()
fs.SetDelay(15 * time.Millisecond)
fs.CreateFile("/test/delayed.txt", []byte("original"))
start := time.Now()
err := fs.WriteFile("/test/delayed.txt", []byte("updated"))
elapsed := time.Since(start)
if err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
if elapsed < 10*time.Millisecond {
t.Errorf("Expected delay ~15ms, got %v", elapsed)
}
}
func TestDeleteFile_WithDelay(t *testing.T) {
fs := mock.NewFileSystem()
fs.SetDelay(10 * time.Millisecond)
fs.CreateFile("/test/delayed.txt", []byte("test"))
start := time.Now()
err := fs.DeleteFile("/test/delayed.txt")
elapsed := time.Since(start)
if err != nil {
t.Fatalf("DeleteFile failed: %v", err)
}
if elapsed < 5*time.Millisecond {
t.Errorf("Expected delay ~10ms, got %v", elapsed)
}
}
func TestReadDir_WithDelay(t *testing.T) {
fs := mock.NewFileSystem()
fs.SetDelay(10 * time.Millisecond)
fs.CreateDir("/test/dir")
fs.CreateFile("/test/dir/a.txt", []byte("a"))
start := time.Now()
_, err := fs.ReadDir("/test/dir")
elapsed := time.Since(start)
if err != nil {
t.Fatalf("ReadDir failed: %v", err)
}
if elapsed < 5*time.Millisecond {
t.Errorf("Expected delay ~10ms, got %v", elapsed)
}
}