- Define pool.FileSystem interface in internal/io/pool/filesystem.go - Move DirEntry interface to internal/io/pool/types/types.go to break the pool <-> mock import cycle - Implement real.FileSystem with atomic writes (temp file + os.Rename) - Update mock.FileSystem to satisfy pool.FileSystem interface - Update worker pool tasks to accept pool.FileSystem interface - Update main.go to use RealFileSystem by default, with -root flag - Update all browser/editor/test references to types.DirEntry
408 lines
10 KiB
Go
408 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_NotFound(t *testing.T) {
|
|
fs := mock.NewFileSystem()
|
|
|
|
task := NewWriteFileTask("/nonexistent.txt", []byte("data"), fs)
|
|
result := task.Execute()
|
|
|
|
if result.IsSuccess() {
|
|
t.Fatal("Expected failure for nonexistent file")
|
|
}
|
|
}
|
|
|
|
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 string(tt.taskType) != tt.want {
|
|
t.Errorf("TaskType %v = %q, want %q", tt.taskType, string(tt.taskType), 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)
|
|
}
|
|
}
|