- Changed instances of t.Error to t.Fatal in job test files to ensure immediate test termination when no executions are found, improving test reliability and clarity.
319 lines
7.4 KiB
Go
319 lines
7.4 KiB
Go
package job_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/yaoapp/yao/config"
|
|
"github.com/yaoapp/yao/job"
|
|
"github.com/yaoapp/yao/test"
|
|
)
|
|
|
|
// TestWorkerManagerLifecycle tests worker manager lifecycle
|
|
func TestWorkerManagerLifecycle(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
// Create a new worker manager for testing (not singleton)
|
|
wm := job.NewWorkerManagerForTest(2)
|
|
if wm == nil {
|
|
t.Fatal("Failed to create worker manager")
|
|
}
|
|
|
|
// Test initial state
|
|
if wm.GetActiveWorkers() != 0 {
|
|
t.Error("Expected 0 active workers initially")
|
|
}
|
|
|
|
// Test start
|
|
wm.Start()
|
|
if wm.GetActiveWorkers() == 0 {
|
|
t.Error("Expected active workers after start")
|
|
}
|
|
|
|
// Test stop
|
|
wm.Stop()
|
|
// Note: Workers might still be active briefly after stop due to cleanup time
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Test restart
|
|
wm.Start()
|
|
if wm.GetActiveWorkers() == 0 {
|
|
t.Error("Expected active workers after restart")
|
|
}
|
|
|
|
// Final cleanup
|
|
wm.Stop()
|
|
}
|
|
|
|
// TestWorkerJobSubmission tests job submission to workers
|
|
func TestWorkerJobSubmission(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
// Create test job
|
|
testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{
|
|
"name": "Test Worker Submission Job",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test job: %v", err)
|
|
}
|
|
|
|
// Test handler
|
|
executed := make(chan bool, 1)
|
|
testHandler := func(ctx context.Context, execution *job.Execution) error {
|
|
execution.Info("Test handler executed")
|
|
executed <- true
|
|
return nil
|
|
}
|
|
|
|
// Add handler and save job
|
|
err = testJob.Add(1, testHandler)
|
|
if err != nil {
|
|
t.Fatalf("Failed to add handler: %v", err)
|
|
}
|
|
|
|
// Create worker manager for testing
|
|
wm := job.NewWorkerManagerForTest(2)
|
|
wm.Start()
|
|
defer wm.Stop()
|
|
|
|
// Submit job
|
|
err = wm.SubmitJob(testJob, testHandler)
|
|
if err != nil {
|
|
t.Fatalf("Failed to submit job: %v", err)
|
|
}
|
|
|
|
// Wait for execution
|
|
select {
|
|
case <-executed:
|
|
t.Log("Job executed successfully")
|
|
case <-time.After(5 * time.Second):
|
|
t.Error("Job execution timeout")
|
|
}
|
|
|
|
// Check executions
|
|
executions, err := testJob.GetExecutions()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get executions: %v", err)
|
|
}
|
|
if len(executions) == 0 {
|
|
t.Error("Expected at least one execution")
|
|
}
|
|
}
|
|
|
|
// TestWorkerModes tests different worker execution modes
|
|
func TestWorkerModes(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
wm := job.NewWorkerManagerForTest(4)
|
|
wm.Start()
|
|
defer wm.Stop()
|
|
|
|
// Test GOROUTINE mode
|
|
goroutineJob, err := job.Once(job.GOROUTINE, map[string]interface{}{
|
|
"name": "Test Goroutine Mode Job",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create goroutine job: %v", err)
|
|
}
|
|
|
|
goroutineExecuted := make(chan bool, 1)
|
|
goroutineHandler := func(ctx context.Context, execution *job.Execution) error {
|
|
if execution.Job != nil && execution.Job.Mode != job.GOROUTINE {
|
|
t.Errorf("Expected GOROUTINE mode, got %v", execution.Job.Mode)
|
|
}
|
|
goroutineExecuted <- true
|
|
return nil
|
|
}
|
|
|
|
err = goroutineJob.Add(1, goroutineHandler)
|
|
if err != nil {
|
|
t.Fatalf("Failed to add goroutine handler: %v", err)
|
|
}
|
|
|
|
goroutineJob.SetWorkerManager(wm)
|
|
|
|
err = goroutineJob.Start()
|
|
if err != nil {
|
|
t.Fatalf("Failed to start goroutine job: %v", err)
|
|
}
|
|
|
|
// Test PROCESS mode
|
|
processJob, err := job.Once(job.PROCESS, map[string]interface{}{
|
|
"name": "Test Process Mode Job",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create process job: %v", err)
|
|
}
|
|
|
|
processExecuted := make(chan bool, 1)
|
|
processHandler := func(ctx context.Context, execution *job.Execution) error {
|
|
if execution.Job != nil && execution.Job.Mode != job.PROCESS {
|
|
t.Errorf("Expected PROCESS mode, got %v", execution.Job.Mode)
|
|
}
|
|
processExecuted <- true
|
|
return nil
|
|
}
|
|
|
|
err = processJob.Add(1, processHandler)
|
|
if err != nil {
|
|
t.Fatalf("Failed to add process handler: %v", err)
|
|
}
|
|
|
|
processJob.SetWorkerManager(wm)
|
|
|
|
err = processJob.Start()
|
|
if err != nil {
|
|
t.Fatalf("Failed to start process job: %v", err)
|
|
}
|
|
|
|
// Wait for both executions
|
|
timeout := time.After(10 * time.Second)
|
|
goroutineDone := false
|
|
processDone := false
|
|
|
|
for !goroutineDone || !processDone {
|
|
select {
|
|
case <-goroutineExecuted:
|
|
goroutineDone = true
|
|
t.Log("Goroutine job executed successfully")
|
|
case <-processExecuted:
|
|
processDone = true
|
|
t.Log("Process job executed successfully")
|
|
case <-timeout:
|
|
t.Error("Jobs execution timeout")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Give extra time for database operations to complete
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
// TestWorkerErrorHandling tests worker error handling
|
|
func TestWorkerErrorHandling(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
// Create test job
|
|
errorJob, err := job.Once(job.GOROUTINE, map[string]interface{}{
|
|
"name": "Test Error Handling Job",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create error job: %v", err)
|
|
}
|
|
|
|
// Error handler
|
|
errorHandler := func(ctx context.Context, execution *job.Execution) error {
|
|
execution.Error("Test error occurred")
|
|
return fmt.Errorf("intentional test error")
|
|
}
|
|
|
|
err = errorJob.Add(1, errorHandler)
|
|
if err != nil {
|
|
t.Fatalf("Failed to add error handler: %v", err)
|
|
}
|
|
|
|
wm := job.NewWorkerManagerForTest(4)
|
|
wm.Start()
|
|
defer wm.Stop()
|
|
errorJob.SetWorkerManager(wm)
|
|
|
|
err = errorJob.Start()
|
|
if err != nil {
|
|
t.Fatalf("Failed to start error job: %v", err)
|
|
}
|
|
|
|
// Wait for execution to complete
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Give extra time for database operations to complete
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Check execution status
|
|
executions, err := errorJob.GetExecutions()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get executions: %v", err)
|
|
}
|
|
if len(executions) == 0 {
|
|
t.Fatal("Expected at least one execution")
|
|
}
|
|
|
|
execution := executions[0]
|
|
if execution.Status != "failed" {
|
|
t.Errorf("Expected execution status 'failed', got '%s'", execution.Status)
|
|
}
|
|
|
|
if execution.ErrorInfo == nil {
|
|
t.Error("Expected error info to be set")
|
|
}
|
|
}
|
|
|
|
// TestWorkerConcurrency tests worker concurrency
|
|
func TestWorkerConcurrency(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
wm := job.NewWorkerManagerForTest(4)
|
|
wm.Start()
|
|
defer wm.Stop()
|
|
|
|
const numJobs = 5
|
|
executed := make(chan bool, numJobs)
|
|
|
|
// Create and submit multiple jobs concurrently
|
|
for i := 0; i < numJobs; i++ {
|
|
testJob, err := job.Once(job.GOROUTINE, map[string]interface{}{
|
|
"name": fmt.Sprintf("Concurrent Test Job %d", i+1),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test job %d: %v", i+1, err)
|
|
}
|
|
|
|
jobID := i + 1
|
|
testHandler := func(ctx context.Context, execution *job.Execution) error {
|
|
execution.Info("Concurrent job %d executed", jobID)
|
|
time.Sleep(100 * time.Millisecond) // Simulate work
|
|
executed <- true
|
|
return nil
|
|
}
|
|
|
|
err = testJob.Add(1, testHandler)
|
|
if err != nil {
|
|
t.Fatalf("Failed to add handler for job %d: %v", i+1, err)
|
|
}
|
|
|
|
testJob.SetWorkerManager(wm)
|
|
|
|
err = testJob.Start()
|
|
if err != nil {
|
|
t.Fatalf("Failed to start job %d: %v", i+1, err)
|
|
}
|
|
}
|
|
|
|
// Wait for all jobs to complete
|
|
completed := 0
|
|
timeout := time.After(10 * time.Second)
|
|
|
|
for completed < numJobs {
|
|
select {
|
|
case <-executed:
|
|
completed++
|
|
t.Logf("Job %d completed", completed)
|
|
case <-timeout:
|
|
t.Errorf("Timeout: only %d out of %d jobs completed", completed, numJobs)
|
|
return
|
|
}
|
|
}
|
|
|
|
if completed != numJobs {
|
|
t.Errorf("Expected %d jobs to complete, got %d", numJobs, completed)
|
|
}
|
|
|
|
// Give extra time for database operations to complete
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|