The tree was formatted with an older gofmt; go1.27's gofmt additionally wants: EOF exactly one newline (no trailing blank lines), imports sorted alphabetically within a block, mixed-precedence binary expressions re-spaced for grouping ((a+b)/c), single-field composite literals un-aligned, adjacent one-line method signatures aligned, and one-line bodies containing a compound statement expanded. Applied repo-wide (31 files under internal/); pure formatting, no semantic changes — build and the full test suite pass.
803 lines
18 KiB
Go
803 lines
18 KiB
Go
package pool
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"pad/internal/io/pool/mock"
|
|
"pad/internal/io/pool/types"
|
|
)
|
|
|
|
// stubTask is a minimal task implementation for testing the worker pool.
|
|
type stubTask struct {
|
|
id string
|
|
taskType TaskType
|
|
priority Priority
|
|
data any
|
|
err error
|
|
execute func() (any, error)
|
|
}
|
|
|
|
func (t *stubTask) Execute() Result {
|
|
if t.execute != nil {
|
|
data, err := t.execute()
|
|
return Result{
|
|
TaskID: t.id,
|
|
TaskType: t.taskType,
|
|
Success: err == nil,
|
|
Data: data,
|
|
Error: err,
|
|
}
|
|
}
|
|
return Result{
|
|
TaskID: t.id,
|
|
TaskType: t.taskType,
|
|
Success: t.err == nil,
|
|
Data: t.data,
|
|
Error: t.err,
|
|
}
|
|
}
|
|
|
|
func (t *stubTask) Priority() Priority { return t.priority }
|
|
func (t *stubTask) TaskID() string { return t.id }
|
|
func (t *stubTask) TaskType() TaskType { return t.taskType }
|
|
func (t *stubTask) DirPath() string { return "" }
|
|
func (t *stubTask) Context() context.Context { return context.Background() }
|
|
func (t *stubTask) Cancel() {}
|
|
func (t *stubTask) Timeout() time.Duration { return 5 * time.Second }
|
|
|
|
func TestWorkerPool_StartStop(t *testing.T) {
|
|
pool := NewWorkerPool(4)
|
|
pool.Start()
|
|
|
|
if !pool.IsRunning() {
|
|
t.Error("Pool should be running after Start()")
|
|
}
|
|
|
|
pool.Stop()
|
|
|
|
if pool.IsRunning() {
|
|
t.Error("Pool should not be running after Stop()")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_StartMultiple(t *testing.T) {
|
|
pool := NewWorkerPool(4)
|
|
pool.Start()
|
|
pool.Start() // Should be a no-op
|
|
pool.Start() // Should be a no-op
|
|
|
|
if pool.WorkerCount() != 4 {
|
|
t.Errorf("WorkerCount = %d, want 4", pool.WorkerCount())
|
|
}
|
|
|
|
pool.Stop()
|
|
}
|
|
|
|
func TestWorkerPool_StopMultiple(t *testing.T) {
|
|
pool := NewWorkerPool(4)
|
|
pool.Start()
|
|
pool.Stop()
|
|
pool.Stop() // Should be a no-op (stopOnce)
|
|
pool.Stop() // Should be a no-op
|
|
|
|
if pool.IsRunning() {
|
|
t.Error("Pool should not be running after Stop()")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_DispatchHighPriority(t *testing.T) {
|
|
pool := NewWorkerPool(2)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
task := &stubTask{
|
|
id: "test-high",
|
|
taskType: TypeReadFile,
|
|
priority: HighPriority,
|
|
data: "result",
|
|
}
|
|
|
|
pool.Dispatch(task)
|
|
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
if result.TaskID != "test-high" {
|
|
t.Errorf("Result TaskID = %q, want %q", result.TaskID, "test-high")
|
|
}
|
|
if !result.IsSuccess() {
|
|
t.Errorf("Result should be successful, got error: %v", result.Error)
|
|
}
|
|
if result.Data != "result" {
|
|
t.Errorf("Result.Data = %v, want %v", result.Data, "result")
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Timed out waiting for result")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_DispatchLowPriority(t *testing.T) {
|
|
pool := NewWorkerPool(2)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
task := &stubTask{
|
|
id: "test-low",
|
|
taskType: TypeSaveState,
|
|
priority: LowPriority,
|
|
data: "saved",
|
|
}
|
|
|
|
pool.Dispatch(task)
|
|
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
if result.TaskID != "test-low" {
|
|
t.Errorf("Result TaskID = %q, want %q", result.TaskID, "test-low")
|
|
}
|
|
if !result.IsSuccess() {
|
|
t.Errorf("Result should be successful, got error: %v", result.Error)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Timed out waiting for result")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_PriorityPreemption(t *testing.T) {
|
|
// Use 1 worker to force serialization
|
|
pool := NewWorkerPool(1)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
var lowExecuted atomic.Bool
|
|
var highExecuted atomic.Bool
|
|
|
|
// Gate task: occupies the single worker until we release it, so that the
|
|
// low- and high-priority test tasks both sit in their queues first.
|
|
gateStarted := make(chan struct{})
|
|
release := make(chan struct{})
|
|
gateTask := &stubTask{
|
|
id: "gate",
|
|
taskType: TypeSaveState,
|
|
priority: HighPriority,
|
|
execute: func() (any, error) {
|
|
close(gateStarted)
|
|
<-release
|
|
return nil, nil
|
|
},
|
|
}
|
|
pool.Dispatch(gateTask)
|
|
|
|
// Wait until the worker is inside the gate task.
|
|
select {
|
|
case <-gateStarted:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Timed out waiting for gate task to start")
|
|
}
|
|
|
|
// Queue a low-priority task, then a high-priority one.
|
|
lowTask := &stubTask{
|
|
id: "low",
|
|
taskType: TypeSaveState,
|
|
priority: LowPriority,
|
|
execute: func() (any, error) {
|
|
lowExecuted.Store(true)
|
|
return nil, nil
|
|
},
|
|
}
|
|
highTask := &stubTask{
|
|
id: "high",
|
|
taskType: TypeReadFile,
|
|
priority: HighPriority,
|
|
execute: func() (any, error) {
|
|
highExecuted.Store(true)
|
|
return "fast", nil
|
|
},
|
|
}
|
|
pool.Dispatch(lowTask)
|
|
pool.Dispatch(highTask)
|
|
|
|
// Release the worker; it must pick the high-priority task first.
|
|
close(release)
|
|
|
|
// Gate result first.
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
if result.TaskID != "gate" {
|
|
t.Fatalf("First result TaskID = %q, want %q", result.TaskID, "gate")
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Timed out waiting for gate result")
|
|
}
|
|
|
|
// High priority result before low priority.
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
if result.TaskID != "high" {
|
|
t.Errorf("Second result TaskID = %q, want %q", result.TaskID, "high")
|
|
}
|
|
if !highExecuted.Load() {
|
|
t.Error("High priority task should have executed before low")
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Timed out waiting for high priority result")
|
|
}
|
|
|
|
// Low priority result last.
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
if result.TaskID != "low" {
|
|
t.Errorf("Third result TaskID = %q, want %q", result.TaskID, "low")
|
|
}
|
|
if !lowExecuted.Load() {
|
|
t.Error("Low priority task should have executed last")
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Timed out waiting for low priority result")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultOrderHighPriority(t *testing.T) {
|
|
// Use 1 worker to force serialization
|
|
pool := NewWorkerPool(1)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
taskCount := 5
|
|
var results []string
|
|
var mu sync.Mutex
|
|
|
|
for i := 0; i < taskCount; i++ {
|
|
task := &stubTask{
|
|
id: fmt.Sprintf("high-%d", i),
|
|
taskType: TypeReadFile,
|
|
priority: HighPriority,
|
|
}
|
|
pool.Dispatch(task)
|
|
}
|
|
|
|
for i := 0; i < taskCount; i++ {
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
mu.Lock()
|
|
results = append(results, result.TaskID)
|
|
mu.Unlock()
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Timed out waiting for result %d", i)
|
|
}
|
|
}
|
|
|
|
if len(results) != taskCount {
|
|
t.Errorf("Got %d results, want %d", len(results), taskCount)
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultOrderMixedPriority(t *testing.T) {
|
|
// Use 1 worker to force serialization
|
|
pool := NewWorkerPool(1)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
// Dispatch 3 low priority tasks first
|
|
for i := 0; i < 3; i++ {
|
|
task := &stubTask{
|
|
id: fmt.Sprintf("low-%d", i),
|
|
taskType: TypeSaveState,
|
|
priority: LowPriority,
|
|
}
|
|
pool.Dispatch(task)
|
|
}
|
|
|
|
// Dispatch 2 high priority tasks
|
|
for i := 0; i < 2; i++ {
|
|
task := &stubTask{
|
|
id: fmt.Sprintf("high-%d", i),
|
|
taskType: TypeReadFile,
|
|
priority: HighPriority,
|
|
}
|
|
pool.Dispatch(task)
|
|
}
|
|
|
|
// Collect results in order
|
|
var results []string
|
|
var mu sync.Mutex
|
|
|
|
for i := 0; i < 5; i++ {
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
mu.Lock()
|
|
results = append(results, result.TaskID)
|
|
mu.Unlock()
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Timed out waiting for result %d", i)
|
|
}
|
|
}
|
|
|
|
// High priority tasks should complete before low priority ones
|
|
// (they are dispatched to highWorkChan which is checked first)
|
|
if len(results) != 5 {
|
|
t.Errorf("Got %d results, want 5", len(results))
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_DispatchNonBlocking(t *testing.T) {
|
|
pool := NewWorkerPool(1)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
// First dispatch should succeed
|
|
task1 := &stubTask{
|
|
id: "test-1",
|
|
taskType: TypeReadFile,
|
|
priority: HighPriority,
|
|
}
|
|
if !pool.DispatchNonBlocking(task1) {
|
|
t.Error("First DispatchNonBlocking should succeed")
|
|
}
|
|
|
|
// Collect the result
|
|
select {
|
|
case <-pool.resultChan:
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatal("Timed out waiting for first result")
|
|
}
|
|
|
|
// Second dispatch should also succeed
|
|
task2 := &stubTask{
|
|
id: "test-2",
|
|
taskType: TypeReadFile,
|
|
priority: HighPriority,
|
|
}
|
|
if !pool.DispatchNonBlocking(task2) {
|
|
t.Error("Second DispatchNonBlocking should succeed")
|
|
}
|
|
|
|
// Collect the result
|
|
select {
|
|
case <-pool.resultChan:
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatal("Timed out waiting for second result")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_TaskCounter(t *testing.T) {
|
|
pool := NewWorkerPool(2)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
for i := 0; i < 10; i++ {
|
|
task := &stubTask{
|
|
id: fmt.Sprintf("test-%d", i),
|
|
taskType: TypeReadFile,
|
|
priority: HighPriority,
|
|
}
|
|
pool.Dispatch(task)
|
|
}
|
|
|
|
if pool.TaskCount() != 10 {
|
|
t.Errorf("TaskCount = %d, want 10", pool.TaskCount())
|
|
}
|
|
|
|
// Collect all results
|
|
for i := 0; i < 10; i++ {
|
|
select {
|
|
case <-pool.resultChan:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Timed out waiting for result %d", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ConcurrentDispatch(t *testing.T) {
|
|
pool := NewWorkerPool(4)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
// Result channel buffer = workerCount * 4 = 16
|
|
// Use fewer tasks to avoid dropped results
|
|
taskCount := 16
|
|
|
|
for i := 0; i < taskCount; i++ {
|
|
wg.Add(1)
|
|
go func(n int) {
|
|
defer wg.Done()
|
|
task := &stubTask{
|
|
id: fmt.Sprintf("concurrent-%d", n),
|
|
taskType: TypeReadFile,
|
|
priority: HighPriority,
|
|
}
|
|
pool.Dispatch(task)
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
if pool.TaskCount() != int64(taskCount) {
|
|
t.Errorf("TaskCount = %d, want %d", pool.TaskCount(), taskCount)
|
|
}
|
|
|
|
// Collect all results
|
|
for i := 0; i < taskCount; i++ {
|
|
select {
|
|
case <-pool.resultChan:
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatalf("Timed out waiting for result %d", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_GracefulShutdown(t *testing.T) {
|
|
pool := NewWorkerPool(2)
|
|
pool.Start()
|
|
|
|
var completed atomic.Int64
|
|
|
|
// Dispatch tasks that take time
|
|
for i := 0; i < 5; i++ {
|
|
task := &stubTask{
|
|
id: fmt.Sprintf("shutdown-%d", i),
|
|
taskType: TypeReadFile,
|
|
priority: HighPriority,
|
|
execute: func() (any, error) {
|
|
time.Sleep(50 * time.Millisecond)
|
|
completed.Add(1)
|
|
return nil, nil
|
|
},
|
|
}
|
|
pool.Dispatch(task)
|
|
}
|
|
|
|
// Stop the pool (should wait for in-flight tasks)
|
|
pool.Stop()
|
|
|
|
// All tasks should have completed
|
|
if completed.Load() != 5 {
|
|
t.Errorf("Completed = %d, want 5", completed.Load())
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultWithRealTasks(t *testing.T) {
|
|
fs := mock.NewFileSystem()
|
|
fs.CreateFile("/test/file.txt", []byte("content"))
|
|
fs.CreateDir("/test/dir")
|
|
fs.CreateFile("/test/dir/a.txt", []byte("a"))
|
|
fs.CreateFile("/test/dir/b.txt", []byte("b"))
|
|
|
|
pool := NewWorkerPool(2)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
// Read file task
|
|
readTask := NewReadFileTask("/test/file.txt", fs)
|
|
pool.Dispatch(readTask)
|
|
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
if !result.IsSuccess() {
|
|
t.Errorf("ReadFile task failed: %v", result.Error)
|
|
}
|
|
content, ok := result.Data.([]byte)
|
|
if !ok {
|
|
t.Fatalf("Result data is not []byte, got %T", result.Data)
|
|
}
|
|
if string(content) != "content" {
|
|
t.Errorf("Content = %q, want %q", string(content), "content")
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Timed out waiting for read file result")
|
|
}
|
|
|
|
// Read dir task
|
|
readDirTask := NewReadDirTask("/test/dir", fs)
|
|
pool.Dispatch(readDirTask)
|
|
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
if !result.IsSuccess() {
|
|
t.Errorf("ReadDir task failed: %v", result.Error)
|
|
}
|
|
entries, ok := result.Data.([]types.DirEntry)
|
|
if !ok {
|
|
t.Fatalf("Result data is not []types.DirEntry, got %T", result.Data)
|
|
}
|
|
if len(entries) != 2 {
|
|
t.Errorf("Entries count = %d, want 2", len(entries))
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Timed out waiting for read dir result")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultWithRealTasksAndDelay(t *testing.T) {
|
|
fs := mock.NewFileSystem()
|
|
fs.SetDelay(20 * time.Millisecond)
|
|
fs.CreateFile("/test/delayed.txt", []byte("delayed"))
|
|
|
|
pool := NewWorkerPool(2)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
task := NewReadFileTask("/test/delayed.txt", fs)
|
|
start := time.Now()
|
|
pool.Dispatch(task)
|
|
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
elapsed := time.Since(start)
|
|
if !result.IsSuccess() {
|
|
t.Errorf("ReadFile task failed: %v", result.Error)
|
|
}
|
|
if elapsed < 15*time.Millisecond {
|
|
t.Errorf("Expected delay ~20ms, got %v", elapsed)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Timed out waiting for delayed result")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_OnlyHighPriority(t *testing.T) {
|
|
fs := mock.NewFileSystem()
|
|
// Create all files upfront
|
|
for i := 0; i < 5; i++ {
|
|
fs.CreateFile(fmt.Sprintf("/test/file%d.txt", i), []byte(fmt.Sprintf("file%d", i)))
|
|
}
|
|
fs.CreateFile("/test/state.json", []byte("{}"))
|
|
|
|
pool := NewWorkerPool(2)
|
|
pool.Start()
|
|
defer pool.Stop()
|
|
|
|
// Only dispatch high priority tasks
|
|
for i := 0; i < 5; i++ {
|
|
task := NewReadFileTask(fmt.Sprintf("/test/file%d.txt", i), fs)
|
|
pool.Dispatch(task)
|
|
}
|
|
|
|
// Dispatch a low priority task
|
|
saveTask := NewSaveStateTask("/test/state.json", []byte("{}"), fs)
|
|
pool.Dispatch(saveTask)
|
|
|
|
// Collect results - high priority should complete first
|
|
for i := 0; i < 6; i++ {
|
|
select {
|
|
case result := <-pool.resultChan:
|
|
// Just verify we got results
|
|
if !result.IsSuccess() {
|
|
t.Errorf("Result %d failed: %v", i, result.Error)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Timed out waiting for result %d", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ChangeEvents(t *testing.T) {
|
|
fs := mock.NewFileSystem()
|
|
ch := make(chan mock.FileChangeEvent, 10)
|
|
fs.SetChangeEvents(ch)
|
|
|
|
fs.CreateFile("/test/notify.txt", []byte("data"))
|
|
|
|
select {
|
|
case event := <-ch:
|
|
if event.Path != "/test/notify.txt" {
|
|
t.Errorf("Event path = %q, want %q", event.Path, "/test/notify.txt")
|
|
}
|
|
if event.EventType != "Created" {
|
|
t.Errorf("Event type = %q, want %q", event.EventType, "Created")
|
|
}
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatal("Expected change event, got none")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ChangeEventsOnModify(t *testing.T) {
|
|
fs := mock.NewFileSystem()
|
|
ch := make(chan mock.FileChangeEvent, 10)
|
|
fs.SetChangeEvents(ch)
|
|
|
|
fs.CreateFile("/test/modify.txt", []byte("original"))
|
|
|
|
// Consume the "Created" event
|
|
<-ch
|
|
|
|
// Modify the file
|
|
fs.WriteFile("/test/modify.txt", []byte("modified"))
|
|
|
|
select {
|
|
case event := <-ch:
|
|
if event.EventType != "Modified" {
|
|
t.Errorf("Event type = %q, want %q", event.EventType, "Modified")
|
|
}
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatal("Expected modified event, got none")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ChangeEventsOnDelete(t *testing.T) {
|
|
fs := mock.NewFileSystem()
|
|
ch := make(chan mock.FileChangeEvent, 10)
|
|
fs.SetChangeEvents(ch)
|
|
|
|
fs.CreateFile("/test/delete.txt", []byte("data"))
|
|
|
|
// Consume the "Created" event
|
|
<-ch
|
|
|
|
// Delete the file
|
|
fs.DeleteFile("/test/delete.txt")
|
|
|
|
select {
|
|
case event := <-ch:
|
|
if event.EventType != "Deleted" {
|
|
t.Errorf("Event type = %q, want %q", event.EventType, "Deleted")
|
|
}
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatal("Expected deleted event, got none")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ChangeEventsOnDirCreate(t *testing.T) {
|
|
fs := mock.NewFileSystem()
|
|
ch := make(chan mock.FileChangeEvent, 10)
|
|
fs.SetChangeEvents(ch)
|
|
|
|
fs.CreateDir("/test/newdir")
|
|
|
|
select {
|
|
case event := <-ch:
|
|
if event.EventType != "Created" {
|
|
t.Errorf("Event type = %q, want %q", event.EventType, "Created")
|
|
}
|
|
if !fs.DirExists("/test/newdir") {
|
|
t.Error("Directory should exist after create")
|
|
}
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatal("Expected created event, got none")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultIsSuccess(t *testing.T) {
|
|
result := Result{
|
|
TaskID: "test",
|
|
Success: true,
|
|
}
|
|
if !result.IsSuccess() {
|
|
t.Error("Result should be successful")
|
|
}
|
|
|
|
result2 := Result{
|
|
TaskID: "test",
|
|
Success: false,
|
|
Error: fmt.Errorf("error"),
|
|
}
|
|
if result2.IsSuccess() {
|
|
t.Error("Result should not be successful")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultIsError(t *testing.T) {
|
|
result := Result{
|
|
TaskID: "test",
|
|
Success: false,
|
|
Error: fmt.Errorf("error"),
|
|
}
|
|
if !result.IsError() {
|
|
t.Error("Result should have error")
|
|
}
|
|
|
|
result2 := Result{
|
|
TaskID: "test",
|
|
Success: true,
|
|
}
|
|
if result2.IsError() {
|
|
t.Error("Result should not have error")
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultIsBrowserResult(t *testing.T) {
|
|
tests := []struct {
|
|
taskType TaskType
|
|
want bool
|
|
}{
|
|
{TypeReadDir, true},
|
|
{TypeBuildIndex, true},
|
|
{TypeLoadIndex, true},
|
|
{TypeLoadPages, true},
|
|
{TypeStatDir, true},
|
|
{TypeReadFile, false},
|
|
{TypeWriteFile, false},
|
|
{TypeSaveState, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := Result{TaskType: tt.taskType}
|
|
if result.IsBrowserResult() != tt.want {
|
|
t.Errorf("Result.TaskType=%v IsBrowserResult()=%v, want %v",
|
|
tt.taskType, result.IsBrowserResult(), tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultIsFileResult(t *testing.T) {
|
|
tests := []struct {
|
|
taskType TaskType
|
|
want bool
|
|
}{
|
|
{TypeReadFile, true},
|
|
{TypeWriteFile, true},
|
|
{TypeStatFile, true},
|
|
{TypeReadDir, false},
|
|
{TypeBuildIndex, false},
|
|
{TypeSaveState, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := Result{TaskType: tt.taskType}
|
|
if result.IsFileResult() != tt.want {
|
|
t.Errorf("Result.TaskType=%v IsFileResult()=%v, want %v",
|
|
tt.taskType, result.IsFileResult(), tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultIsCacheResult(t *testing.T) {
|
|
tests := []struct {
|
|
taskType TaskType
|
|
want bool
|
|
}{
|
|
{TypeWriteCache, true},
|
|
{TypeReadCache, true},
|
|
{TypeInvalidate, true},
|
|
{TypeReadFile, false},
|
|
{TypeSaveState, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := Result{TaskType: tt.taskType}
|
|
if result.IsCacheResult() != tt.want {
|
|
t.Errorf("Result.TaskType=%v IsCacheResult()=%v, want %v",
|
|
tt.taskType, result.IsCacheResult(), tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_ResultIsStateResult(t *testing.T) {
|
|
tests := []struct {
|
|
taskType TaskType
|
|
want bool
|
|
}{
|
|
{TypeSaveState, true},
|
|
{TypeSaveUndo, true},
|
|
{TypeReadFile, false},
|
|
{TypeBuildIndex, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := Result{TaskType: tt.taskType}
|
|
if result.IsStateResult() != tt.want {
|
|
t.Errorf("Result.TaskType=%v IsStateResult()=%v, want %v",
|
|
tt.taskType, result.IsStateResult(), tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_DefaultWorkerCount(t *testing.T) {
|
|
pool := NewWorkerPool(0)
|
|
if pool.WorkerCount() != 4 {
|
|
t.Errorf("WorkerCount = %d, want 4 (default)", pool.WorkerCount())
|
|
}
|
|
}
|
|
|
|
func TestWorkerPool_NegativeWorkerCount(t *testing.T) {
|
|
pool := NewWorkerPool(-1)
|
|
if pool.WorkerCount() != 4 {
|
|
t.Errorf("WorkerCount = %d, want 4 (default)", pool.WorkerCount())
|
|
}
|
|
}
|