package jobqueue import ( "errors" "fmt" "math/rand" "testing" "time" ) func TestEnqueueJobAlreadyExists(t *testing.T) { runner := &DummyRunnable{} job := NewJob[DummyResult](JobID("1"), runner) q := NewQueue(nil) _ = q.Enqueue(job) err := q.Enqueue(job) if err != ErrJobAlreadyExists { t.Fatalf("Expected ErrJobAlreadyExists, got %v", err) } } func TestEnqueueAndDequeue(t *testing.T) { runner := &DummyRunnable{} q := NewQueue(nil) job1 := NewJob[DummyResult](JobID("1"), runner) job1.SetPriority(PriorityHigh) job2 := NewJob[DummyResult](JobID("2"), runner) _ = q.Enqueue(job1) _ = q.Enqueue(job2) dequeuedJob, err := q.Dequeue() if err != nil || dequeuedJob.GetID() != JobID("1") { t.Fatalf("Unexpected dequeue result: jobID %s, err %v", dequeuedJob.GetID(), err) } } func TestEnqueueAndDequeue2(t *testing.T) { runner := &DummyRunnable{} q := NewQueue(nil) job1 := NewJob[DummyResult](JobID("1"), runner) job2 := NewJob[DummyResult](JobID("2"), runner) job2.AddDependency(JobID("1")) _ = q.Enqueue(job1) _ = q.Enqueue(job2) dequeuedJob, err := q.Dequeue() if err != nil { t.Fatalf("Unexpected error: %v", err) } if dequeuedJob.GetID() != JobID("1") { t.Fatalf("Unexpected dequeue result: jobID %s", dequeuedJob.GetID()) } } func TestDependencyResolution(t *testing.T) { runner := &DummyRunnable{} q := NewQueue(nil) job1 := NewJob[DummyResult](JobID("1"), runner) job2 := NewJob[DummyResult](JobID("2"), runner) job3 := NewJob[DummyResult](JobID("3"), runner) _ = q.Enqueue(job3) _ = q.Enqueue(job2) _ = q.Enqueue(job1) contains := func(arr []JobID, id JobID) bool { for _, v := range arr { if v == id { return true } } return false } possibleJobIDs := []JobID{"1", "2", "3"} job, _ := q.Dequeue() if !contains(possibleJobIDs, job.GetID()) { t.Fatalf("Expected jobID in %v, got %s", possibleJobIDs, job.GetID()) } // remove jobID from possibleJobIDs for i, v := range possibleJobIDs { if v == job.GetID() { possibleJobIDs = append(possibleJobIDs[:i], possibleJobIDs[i+1:]...) } } job, _ = q.Dequeue() if !contains(possibleJobIDs, job.GetID()) { t.Fatalf("Expected jobID in %v, got %s", possibleJobIDs, job.GetID()) } // remove jobID from possibleJobIDs for i, v := range possibleJobIDs { if v == job.GetID() { possibleJobIDs = append(possibleJobIDs[:i], possibleJobIDs[i+1:]...) } } job, _ = q.Dequeue() if !contains(possibleJobIDs, job.GetID()) { t.Fatalf("Expected jobID in %v, got %s", possibleJobIDs, job.GetID()) } // remove jobID from possibleJobIDs for i, v := range possibleJobIDs { if v == job.GetID() { possibleJobIDs = append(possibleJobIDs[:i], possibleJobIDs[i+1:]...) } } if len(possibleJobIDs) != 0 { t.Fatalf("Expected no jobIDs left in %v", possibleJobIDs) } } func TestDequeueEmptyQueue(t *testing.T) { q := NewQueue(nil) _, err := q.Dequeue() if err != ErrQueueEmpty { t.Fatalf("Expected ErrQueueEmpty, got %v", err) } } func TestProcessedJobs(t *testing.T) { q := NewQueue(nil) runner := &DummyRunnable{} job1 := NewJob[DummyResult](JobID("1"), runner) job2 := NewJob[DummyResult](JobID("2"), runner) _ = q.Enqueue(job1) _, _ = q.Dequeue() _ = q.Enqueue(job2) _, err := q.Dequeue() if err != nil { t.Fatalf("Dequeue failed: %v", err) } if _, exists := q.processedJobs[job1.GetID()]; !exists { t.Fatalf("Job 1 not marked as processed") } } func TestCyclicDependencies(t *testing.T) { runner := &DummyRunnable{} q := NewQueue(nil) job1 := NewJob[DummyResult](JobID("1"), runner) job2 := NewJob[DummyResult](JobID("2"), runner) job3 := NewJob[DummyResult](JobID("3"), runner) job1.AddDependency(JobID("2")) job2.AddDependency(JobID("3")) job3.AddDependency(JobID("1")) err := q.Enqueue(job1) if err != nil { t.Fatalf("Enqueue failed: %v", err) } err = q.Enqueue(job2) if err != nil { t.Fatalf("Enqueue failed: %v", err) } err = q.Enqueue(job3) if err == nil || err != ErrCycleDetected { t.Fatalf("Expected ErrCyclicDependency, got %v", err) } } func TestDuplicateDependencies(t *testing.T) { runner := &DummyRunnable{} q := NewQueue(nil) job1 := NewJob[DummyResult](JobID("1"), runner) job2 := NewJob[DummyResult](JobID("2"), runner) job2.AddDependency(JobID("1")) job2.AddDependency(JobID("1")) _ = q.Enqueue(job1) err := q.Enqueue(job2) if err != nil { t.Fatalf("Enqueue failed: %v", err) } _, err = q.Dequeue() if err != nil { t.Fatalf("Dequeue failed: %v", err) } _, err = q.Dequeue() if err != nil { t.Fatalf("Dequeue failed: %v", err) } if len(q.processedJobs) != 2 { t.Fatalf("Expected 2 processed jobs, got %d", len(q.processedJobs)) } } func TestJobWithSelfAsDependency(t *testing.T) { runner := &DummyRunnable{} q := NewQueue(nil) job1 := NewJob[DummyResult](JobID("1"), runner) job1.AddDependency(JobID("1")) err := q.Enqueue(job1) if err == nil || err != ErrCycleDetected { t.Fatalf("Expected ErrCycleDetected, got %v", err) } } func TestEnqueueDequeue(t *testing.T) { q := NewQueue(nil) job1 := NewJob[DummyResult]("job1", nil) job2 := NewJob[DummyResult]("job2", nil) job3 := NewJob[DummyResult]("job3", nil) job2.dependencies = []JobID{"job1"} job3.dependencies = []JobID{"job2"} // Enqueue a job with dependencies that haven't been enqueued yet if err := q.Enqueue(job3); err != nil { t.Fatalf("Failed to enqueue job3: %v", err) } // Try to dequeue from an empty readyQueue if _, err := q.Dequeue(); !errors.Is(err, ErrQueueEmpty) { t.Fatalf("Expected ErrQueueEmpty, got: %v", err) } // Enqueue a job with no dependencies if err := q.Enqueue(job1); err != nil { t.Fatalf("Failed to enqueue job1: %v", err) } // Enqueue a job that has its dependencies met if err := q.Enqueue(job2); err != nil { t.Fatalf("Failed to enqueue job2: %v", err) } // Try to dequeue jobs in the expected order if job, err := q.Dequeue(); err != nil || job.GetID() != "job1" { t.Fatalf("Expected job1, got: %v, %v", job, err) } if job, err := q.Dequeue(); err != nil || job.GetID() != "job2" { t.Fatalf("Expected job2, got: %v, %v", job, err) } if job, err := q.Dequeue(); err != nil || job.GetID() != "job3" { t.Fatalf("Expected job3, got: %v, %v", job, err) } } func TestDuplicateEnqueue(t *testing.T) { q := NewQueue(nil) job1 := NewJob[DummyResult]("job1", nil) // Enqueue the same job twice if err := q.Enqueue(job1); err != nil { t.Fatalf("Failed to enqueue job1: %v", err) } if err := q.Enqueue(job1); !errors.Is(err, ErrJobAlreadyExists) { t.Fatalf("Expected ErrJobAlreadyExists, got: %v", err) } } func TestCircularDependency(t *testing.T) { q := NewQueue(nil) job1 := NewJob[DummyResult]("job1", nil) job2 := NewJob[DummyResult]("job2", nil) job1.dependencies = []JobID{"job2"} job2.dependencies = []JobID{"job1"} if err := q.Enqueue(job1); err != nil { t.Fatalf("Failed to enqueue job1: %v", err) } // This enqueue should fail if you have logic to detect circular dependencies if err := q.Enqueue(job2); err == nil { t.Fatalf("Expected an error due to circular dependency, got: %v", err) } } // TestFuzzyEnqueueDequeue tests the queue by generating random jobs and // you can run it with: // go test -v -fuzz TestFuzzyEnqueueDequeue // or with a fuzztime of 10 seconds: // go test -v -run -fuzz TestFuzzyEnqueueDequeue -fuzztime=10s func TestFuzzyEnqueueDequeue(t *testing.T) { rand.Seed(time.Now().UnixNano()) q := NewQueue(nil) // Generate random jobs jobCount := 1000 jobs := make([]GenericJob, jobCount) for i := 0; i < jobCount; i++ { jobID := JobID(fmt.Sprintf("job%d", i)) jobs[i] = NewJob[DummyResult](jobID, nil) } // Randomly set dependencies for each job for _, job := range jobs { depCount := rand.Intn(5) // up to 5 dependencies deps := make([]JobID, depCount) for i := 0; i < depCount; i++ { depIndex := rand.Intn(jobCount) deps[i] = jobs[depIndex].GetID() } job.(*Job[DummyResult]).dependencies = deps } // Randomly enqueue jobs for i := 0; i < jobCount; i++ { index := rand.Intn(len(jobs)) job := jobs[index] q.Enqueue(job) jobs = append(jobs[:index], jobs[index+1:]...) } // Randomly dequeue jobs, ignoring errors for this fuzzy test for i := 0; i < jobCount; i++ { _, _ = q.Dequeue() } // Verify that the queue is empty if _, err := q.Dequeue(); !errors.Is(err, ErrQueueEmpty) { t.Fatalf("Expected ErrQueueEmpty, got: %v", err) } } func TestEnqueueDequeueOrder(t *testing.T) { eventBus := NewEventBus() // Ihre EventBus-Initialisierung q := NewQueue(eventBus) job1 := NewJob[DummyResult]("job1", nil) job2 := NewJob[DummyResult]("job2", nil) job3 := NewJob[DummyResult]("job3", nil) job2.AddDependency(JobID("job1")) job3.AddDependency(JobID("job2")) if err := q.Enqueue(job1); err != nil { t.Fatalf("Failed to enqueue job1: %s", err) } if err := q.Enqueue(job3); err != nil { t.Fatalf("Failed to enqueue job3: %s", err) } if err := q.Enqueue(job2); err != nil { t.Fatalf("Failed to enqueue job2: %s", err) } dequeuedJob, err := q.Dequeue() if err != nil { t.Fatalf("Failed to dequeue: %s", err) } if dequeuedJob.GetID() != "job1" { t.Errorf("Expected job1, got %s", dequeuedJob.GetID()) } dequeuedJob, err = q.Dequeue() if err != nil { t.Fatalf("Failed to dequeue: %s", err) } if dequeuedJob.GetID() != "job2" { t.Errorf("Expected job2, got %s", dequeuedJob.GetID()) } dequeuedJob, err = q.Dequeue() if err != nil { t.Fatalf("Failed to dequeue: %s", err) } if dequeuedJob.GetID() != "job3" { t.Errorf("Expected job3, got %s", dequeuedJob.GetID()) } }