360 lines
8.3 KiB
Go
360 lines
8.3 KiB
Go
//go:build linux || freebsd
|
|||
|
|
// +build linux freebsd
|
||
|
|
|
||
|
|
package queue
|
||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestNewQueue(t *testing.T) {
|
||
|
|
q, err := NewQueue(nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create queue: %v", err)
|
||
|
|
}
|
||
|
|
if q == nil {
|
||
|
|
t.Fatal("Queue is nil")
|
||
|
|
}
|
||
|
|
if q.config.MaxParallel != 3 {
|
||
|
|
t.Errorf("Expected MaxParallel=3, got %d", q.config.MaxParallel)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueAdd(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
queueFile := filepath.Join(tmpDir, "queue.json")
|
||
|
|
|
||
|
|
q, err := NewQueue(&QueueConfig{QueueFile: queueFile, AutoSave: true})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
item := q.Add("https://example.com/file.zip", "/tmp/file.zip", 5)
|
||
|
|
if item == nil {
|
||
|
|
t.Fatal("Added item is nil")
|
||
|
|
}
|
||
|
|
if item.URL != "https://example.com/file.zip" {
|
||
|
|
t.Errorf("Expected URL https://example.com/file.zip, got %s", item.URL)
|
||
|
|
}
|
||
|
|
if item.Priority != 5 {
|
||
|
|
t.Errorf("Expected priority 5, got %d", item.Priority)
|
||
|
|
}
|
||
|
|
if item.Status != "pending" {
|
||
|
|
t.Errorf("Expected status pending, got %s", item.Status)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueAddPriority(t *testing.T) {
|
||
|
|
q, err := NewQueue(nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Test priority clamping
|
||
|
|
item1 := q.Add("https://example.com/1.zip", "/tmp/1.zip", 0)
|
||
|
|
if item1.Priority != 5 {
|
||
|
|
t.Errorf("Expected priority 5 (default), got %d", item1.Priority)
|
||
|
|
}
|
||
|
|
|
||
|
|
item2 := q.Add("https://example.com/2.zip", "/tmp/2.zip", 15)
|
||
|
|
if item2.Priority != 10 {
|
||
|
|
t.Errorf("Expected priority 10 (max), got %d", item2.Priority)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueRemove(t *testing.T) {
|
||
|
|
q, _ := NewQueue(nil)
|
||
|
|
|
||
|
|
item := q.Add("https://example.com/file.zip", "/tmp/file.zip", 5)
|
||
|
|
initialLen := q.Len()
|
||
|
|
|
||
|
|
if !q.Remove(item.ID) {
|
||
|
|
t.Error("Failed to remove item")
|
||
|
|
}
|
||
|
|
if q.Len() != initialLen-1 {
|
||
|
|
t.Errorf("Expected queue length %d, got %d", initialLen-1, q.Len())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueGetNext(t *testing.T) {
|
||
|
|
q, _ := NewQueue(nil)
|
||
|
|
|
||
|
|
// Add items with different priorities
|
||
|
|
q.Add("https://example.com/low.zip", "/tmp/low.zip", 3)
|
||
|
|
q.Add("https://example.com/high.zip", "/tmp/high.zip", 10)
|
||
|
|
q.Add("https://example.com/med.zip", "/tmp/med.zip", 5)
|
||
|
|
|
||
|
|
next := q.GetNext()
|
||
|
|
if next == nil {
|
||
|
|
t.Fatal("GetNext returned nil")
|
||
|
|
}
|
||
|
|
// Should return highest priority (10)
|
||
|
|
if next.Priority < 8 {
|
||
|
|
t.Errorf("Expected high priority item (>=8), got %d", next.Priority)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueUpdateStatus(t *testing.T) {
|
||
|
|
q, err := NewQueue(nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
item := q.Add("https://example.com/file.zip", "/tmp/file.zip", 5)
|
||
|
|
|
||
|
|
if !q.UpdateStatus(item.ID, "downloading") {
|
||
|
|
t.Error("Failed to update status")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Refresh item from queue
|
||
|
|
items := q.GetAll()
|
||
|
|
var updated *QueueItem
|
||
|
|
for _, it := range items {
|
||
|
|
if it.ID == item.ID {
|
||
|
|
updated = it
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if updated == nil || updated.Status != "downloading" {
|
||
|
|
t.Errorf("Expected status downloading, got %s", updated.Status)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueGetPending(t *testing.T) {
|
||
|
|
q, _ := NewQueue(nil)
|
||
|
|
|
||
|
|
q.Add("https://example.com/1.zip", "/tmp/1.zip", 5)
|
||
|
|
q.Add("https://example.com/2.zip", "/tmp/2.zip", 5)
|
||
|
|
|
||
|
|
items := q.GetAll()
|
||
|
|
if len(items) >= 2 {
|
||
|
|
q.UpdateStatus(items[0].ID, "completed")
|
||
|
|
}
|
||
|
|
|
||
|
|
pending := q.GetPending()
|
||
|
|
if len(pending) < 1 {
|
||
|
|
t.Errorf("Expected at least 1 pending item, got %d", len(pending))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueGetStats(t *testing.T) {
|
||
|
|
q, _ := NewQueue(nil)
|
||
|
|
initialLen := q.Len()
|
||
|
|
|
||
|
|
q.Add("https://example.com/1.zip", "/tmp/1.zip", 5)
|
||
|
|
q.Add("https://example.com/2.zip", "/tmp/2.zip", 5)
|
||
|
|
q.Add("https://example.com/3.zip", "/tmp/3.zip", 5)
|
||
|
|
|
||
|
|
items := q.GetAll()
|
||
|
|
completedCount := 0
|
||
|
|
for _, item := range items {
|
||
|
|
if item.Status == "pending" && completedCount == 0 {
|
||
|
|
q.UpdateStatus(item.ID, "completed")
|
||
|
|
completedCount++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
stats := q.GetStats()
|
||
|
|
if stats.Total < initialLen+3 {
|
||
|
|
t.Errorf("Expected at least %d total, got %d", initialLen+3, stats.Total)
|
||
|
|
}
|
||
|
|
if stats.Completed < 1 {
|
||
|
|
t.Errorf("Expected at least 1 completed, got %d", stats.Completed)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueSaveLoad(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
queueFile := filepath.Join(tmpDir, "queue.json")
|
||
|
|
|
||
|
|
// Create and save queue
|
||
|
|
q1, err := NewQueue(&QueueConfig{QueueFile: queueFile, AutoSave: false})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
q1.Add("https://example.com/file.zip", "/tmp/file.zip", 7)
|
||
|
|
q1.Add("https://example.com/other.zip", "/tmp/other.zip", 3)
|
||
|
|
|
||
|
|
if err := q1.Save(); err != nil {
|
||
|
|
t.Fatalf("Failed to save queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Load queue
|
||
|
|
q2, err := NewQueue(&QueueConfig{QueueFile: queueFile})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to load queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if q2.Len() != 2 {
|
||
|
|
t.Errorf("Expected 2 items after load, got %d", q2.Len())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueClearCompleted(t *testing.T) {
|
||
|
|
q, _ := NewQueue(nil)
|
||
|
|
initialLen := q.Len()
|
||
|
|
|
||
|
|
q.Add("https://example.com/1.zip", "/tmp/1.zip", 5)
|
||
|
|
q.Add("https://example.com/2.zip", "/tmp/2.zip", 5)
|
||
|
|
q.Add("https://example.com/3.zip", "/tmp/3.zip", 5)
|
||
|
|
|
||
|
|
items := q.GetAll()
|
||
|
|
completedCount := 0
|
||
|
|
for _, item := range items {
|
||
|
|
if item.Status == "pending" && completedCount == 0 {
|
||
|
|
q.UpdateStatus(item.ID, "completed")
|
||
|
|
completedCount++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
count := q.ClearCompleted()
|
||
|
|
if count < 1 {
|
||
|
|
t.Errorf("Expected to clear at least 1 item, cleared %d", count)
|
||
|
|
}
|
||
|
|
if q.Len() >= initialLen+3 {
|
||
|
|
t.Errorf("Expected less than %d items remaining, got %d", initialLen+3, q.Len())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueRetry(t *testing.T) {
|
||
|
|
q, err := NewQueue(nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
item := q.Add("https://example.com/file.zip", "/tmp/file.zip", 5)
|
||
|
|
q.UpdateStatus(item.ID, "failed")
|
||
|
|
|
||
|
|
// Should be able to retry
|
||
|
|
if !q.Retry(item.ID) {
|
||
|
|
t.Error("Failed to retry item")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check status is back to pending
|
||
|
|
items := q.GetAll()
|
||
|
|
for _, it := range items {
|
||
|
|
if it.ID == item.ID {
|
||
|
|
if it.Status != "pending" {
|
||
|
|
t.Errorf("Expected status pending after retry, got %s", it.Status)
|
||
|
|
}
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueRetryMaxRetries(t *testing.T) {
|
||
|
|
q, err := NewQueue(&QueueConfig{DefaultMaxRetries: 2})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
item := q.Add("https://example.com/file.zip", "/tmp/file.zip", 5)
|
||
|
|
q.UpdateStatus(item.ID, "failed")
|
||
|
|
|
||
|
|
// First retry should work
|
||
|
|
if !q.Retry(item.ID) {
|
||
|
|
t.Error("First retry should succeed")
|
||
|
|
}
|
||
|
|
q.UpdateStatus(item.ID, "failed")
|
||
|
|
|
||
|
|
// Second retry should work
|
||
|
|
if !q.Retry(item.ID) {
|
||
|
|
t.Error("Second retry should succeed")
|
||
|
|
}
|
||
|
|
q.UpdateStatus(item.ID, "failed")
|
||
|
|
|
||
|
|
// Third retry should fail (max retries reached)
|
||
|
|
if q.Retry(item.ID) {
|
||
|
|
t.Error("Third retry should fail (max retries exceeded)")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGenerateID(t *testing.T) {
|
||
|
|
id1 := generateID()
|
||
|
|
id2 := generateID()
|
||
|
|
|
||
|
|
if id1 == id2 {
|
||
|
|
t.Error("Generated IDs should be unique")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check ID format
|
||
|
|
if len(id1) < 3 {
|
||
|
|
t.Errorf("ID too short: %s", id1)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDefaultQueueFile(t *testing.T) {
|
||
|
|
path := getDefaultQueueFile()
|
||
|
|
if path == "" {
|
||
|
|
t.Error("Default queue file path should not be empty")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueConcurrentAccess(t *testing.T) {
|
||
|
|
q, _ := NewQueue(nil)
|
||
|
|
initialLen := q.Len()
|
||
|
|
|
||
|
|
done := make(chan bool, 10)
|
||
|
|
|
||
|
|
// Concurrent adds
|
||
|
|
for i := 0; i < 10; i++ {
|
||
|
|
go func(n int) {
|
||
|
|
q.Add("https://example.com/"+string(rune('a'+n))+".zip", "/tmp/"+string(rune('a'+n))+".zip", 5)
|
||
|
|
done <- true
|
||
|
|
}(i)
|
||
|
|
}
|
||
|
|
|
||
|
|
for i := 0; i < 10; i++ {
|
||
|
|
<-done
|
||
|
|
}
|
||
|
|
|
||
|
|
if q.Len() < initialLen+10 {
|
||
|
|
t.Errorf("Expected at least %d items after concurrent adds, got %d", initialLen+10, q.Len())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueAutoSave(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
queueFile := filepath.Join(tmpDir, "queue.json")
|
||
|
|
|
||
|
|
q, err := NewQueue(&QueueConfig{QueueFile: queueFile, AutoSave: true})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
q.Add("https://example.com/file.zip", "/tmp/file.zip", 5)
|
||
|
|
|
||
|
|
// Check file exists
|
||
|
|
if _, err := os.Stat(queueFile); os.IsNotExist(err) {
|
||
|
|
t.Error("Queue file should exist with AutoSave enabled")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQueueProgressUpdate(t *testing.T) {
|
||
|
|
q, err := NewQueue(nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create queue: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
item := q.Add("https://example.com/file.zip", "/tmp/file.zip", 5)
|
||
|
|
|
||
|
|
q.UpdateProgress(item.ID, 500, 1000)
|
||
|
|
|
||
|
|
items := q.GetAll()
|
||
|
|
for _, it := range items {
|
||
|
|
if it.ID == item.ID {
|
||
|
|
if it.Downloaded != 500 {
|
||
|
|
t.Errorf("Expected downloaded 500, got %d", it.Downloaded)
|
||
|
|
}
|
||
|
|
if it.Size != 1000 {
|
||
|
|
t.Errorf("Expected size 1000, got %d", it.Size)
|
||
|
|
}
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|