mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-08 17:40:28 +00:00
567 lines
19 KiB
Go
567 lines
19 KiB
Go
package download
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/GopeedLab/gopeed/pkg/base"
|
|
"github.com/GopeedLab/gopeed/pkg/util"
|
|
)
|
|
|
|
type failNextTaskPutStorage struct {
|
|
Storage
|
|
fail atomic.Bool
|
|
failBucket string
|
|
beforeTaskPut func(*Task)
|
|
}
|
|
|
|
func (s *failNextTaskPutStorage) Put(bucket string, key string, value any) error {
|
|
if bucket == bucketTask && s.beforeTaskPut != nil {
|
|
s.beforeTaskPut(value.(*Task))
|
|
}
|
|
failBucket := s.failBucket
|
|
if failBucket == "" {
|
|
failBucket = bucketTask
|
|
}
|
|
if bucket == failBucket && (bucket != bucketTask || !value.(*Task).NeedsPreparation) && s.fail.CompareAndSwap(true, false) {
|
|
return errors.New("task snapshot write failed")
|
|
}
|
|
return s.Storage.Put(bucket, key, value)
|
|
}
|
|
|
|
func TestDownloader_PreparationSaveFailureCanRetry(t *testing.T) {
|
|
for _, failBucket := range []string{bucketSave, bucketTask} {
|
|
for _, restart := range []bool{false, true} {
|
|
t.Run(fmt.Sprintf("%s/restart=%t", failBucket, restart), func(t *testing.T) {
|
|
url := startCategoryFileServer(t, "setup.data", []byte("0123456789"))
|
|
downloadDir := t.TempDir()
|
|
storageDir := t.TempDir()
|
|
storage := &failNextTaskPutStorage{Storage: NewBoltStorage(storageDir), failBucket: failBucket}
|
|
downloader := NewDownloader(&DownloaderConfig{Storage: storage, StorageDir: storageDir})
|
|
if err := downloader.Setup(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { downloader.Close() })
|
|
categoryDir := putCategoryConfig(t, downloader, downloadDir, true, []string{"data"})
|
|
if err := os.MkdirAll(categoryDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(categoryDir, "setup.data"), []byte("existing"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
failed := watchTaskError(t, downloader)
|
|
storage.fail.Store(true)
|
|
id, err := downloader.CreateDirect(&base.Request{URL: url}, &base.Options{Path: downloadDir})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitTaskError(t, failed)
|
|
task := downloader.GetTask(id)
|
|
if !task.NeedsPreparation || task.Meta.Opts.Path != downloadDir || task.Meta.Opts.Name != "" {
|
|
t.Fatal("failed preparation changed the original directory, name or flag")
|
|
}
|
|
if restart {
|
|
if err := downloader.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
downloader = NewDownloader(&DownloaderConfig{Storage: NewBoltStorage(storageDir), StorageDir: storageDir})
|
|
if err := downloader.Setup(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
done := watchTaskDone(t, downloader)
|
|
if err := downloader.Continue(&TaskFilter{IDs: []string{id}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitTaskDone(t, done)
|
|
task = downloader.GetTask(id)
|
|
if task.NeedsPreparation || task.Meta.Opts.Path != categoryDir || task.Meta.Opts.Name != "setup (1).data" {
|
|
t.Fatalf("retry did not prepare the final directory and duplicate name: %+v", task.Meta.Opts)
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(categoryDir, "setup (1).data"))
|
|
if err != nil || string(data) != "0123456789" {
|
|
t.Fatalf("downloaded content = %q, %v", data, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// watchTaskError subscribes to the error event, so a failing task can be
|
|
// awaited instead of polled.
|
|
func watchTaskError(t *testing.T, d *Downloader) <-chan struct{} {
|
|
t.Helper()
|
|
failed := make(chan struct{}, 1)
|
|
d.Listener(func(event *Event) {
|
|
if event.Key == EventKeyError {
|
|
select {
|
|
case failed <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
})
|
|
return failed
|
|
}
|
|
|
|
func waitTaskError(t *testing.T, failed <-chan struct{}) {
|
|
t.Helper()
|
|
select {
|
|
case <-failed:
|
|
case <-time.After(30 * time.Second):
|
|
t.Fatal("task did not fail")
|
|
}
|
|
}
|
|
|
|
// waitTaskStatus waits until the task reaches one of the given statuses.
|
|
func waitTaskStatus(t *testing.T, d *Downloader, task *Task, statuses ...base.Status) base.Status {
|
|
t.Helper()
|
|
deadline := time.Now().Add(30 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
status := d.taskStatus(task)
|
|
for _, want := range statuses {
|
|
if status == want {
|
|
return status
|
|
}
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
t.Fatalf("task status = %q, want one of %v", d.taskStatus(task), statuses)
|
|
return ""
|
|
}
|
|
|
|
// setMaxRunning limits the number of tasks that run at the same time.
|
|
func setMaxRunning(t *testing.T, d *Downloader, max int) {
|
|
t.Helper()
|
|
cfg, err := d.GetConfig()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg.MaxRunning = max
|
|
if err := d.PutConfig(cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// A task that waits in the queue is prepared when it starts, the first
|
|
// preparation must not be skipped by the wait status.
|
|
func TestDownloader_QueuedTaskIsPreparedWhenItStarts(t *testing.T) {
|
|
release := make(chan struct{})
|
|
slow := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
<-release
|
|
w.Header().Set("Content-Disposition", `attachment; filename="slow.bin"`)
|
|
w.Write([]byte("slow"))
|
|
}))
|
|
defer slow.Close()
|
|
fast := startCategoryFileServer(t, "setup.data", []byte("0123456789"))
|
|
|
|
downloadDir := t.TempDir()
|
|
downloader := newSetupCategoryDownloader(t)
|
|
categoryDir := putCategoryConfig(t, downloader, downloadDir, true, []string{"data"})
|
|
setMaxRunning(t, downloader, 1)
|
|
|
|
// The blocker takes the only running slot.
|
|
blockerID, err := downloader.CreateDirect(&base.Request{URL: slow.URL}, &base.Options{Path: downloadDir})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
blocker := downloader.GetTask(blockerID)
|
|
if blocker == nil {
|
|
t.Fatal("blocker task not found")
|
|
}
|
|
waitTaskStatus(t, downloader, blocker, base.DownloadStatusRunning)
|
|
|
|
queuedID, err := downloader.CreateDirect(&base.Request{URL: fast}, &base.Options{Path: downloadDir})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
queued := downloader.GetTask(queuedID)
|
|
if queued == nil {
|
|
t.Fatal("task not found")
|
|
}
|
|
if queued.Status != base.DownloadStatusWait {
|
|
t.Fatalf("queued task status = %q, want wait", queued.Status)
|
|
}
|
|
|
|
close(release)
|
|
waitTaskStatus(t, downloader, queued, base.DownloadStatusDone, base.DownloadStatusError)
|
|
|
|
if queued.Meta.Opts.Path != categoryDir {
|
|
t.Fatalf("queued task path = %q, want the category dir %q", queued.Meta.Opts.Path, categoryDir)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(categoryDir, "setup.data")); err != nil {
|
|
t.Fatalf("categorized file missing: %v", err)
|
|
}
|
|
}
|
|
|
|
// A task that failed before it was prepared is prepared when it continues.
|
|
func TestDownloader_FailedTaskIsPreparedWhenItContinues(t *testing.T) {
|
|
url := startCategoryFileServer(t, "setup.data", []byte("0123456789"))
|
|
downloadDir := t.TempDir()
|
|
allowedDir := t.TempDir()
|
|
|
|
downloader := newSetupCategoryDownloader(t)
|
|
categoryDir := putCategoryConfig(t, downloader, downloadDir, true, []string{"data"})
|
|
// The white list rejects the base directory, so the first start fails.
|
|
downloader.cfg.WhiteDownloadDirs = []string{allowedDir}
|
|
|
|
failed := watchTaskError(t, downloader)
|
|
taskID, err := downloader.CreateDirect(&base.Request{URL: url}, &base.Options{Path: downloadDir})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
task := downloader.GetTask(taskID)
|
|
if task == nil {
|
|
t.Fatal("task not found")
|
|
}
|
|
waitTaskError(t, failed)
|
|
waitTaskStatus(t, downloader, task, base.DownloadStatusError)
|
|
if !task.NeedsPreparation {
|
|
t.Fatal("a failed preparation must keep the needs preparation flag")
|
|
}
|
|
|
|
// The white list accepts the base directory now, the retry prepares the
|
|
// task again.
|
|
downloader.cfg.WhiteDownloadDirs = []string{allowedDir, downloadDir, categoryDir}
|
|
done := watchTaskDone(t, downloader)
|
|
if err := downloader.Continue(&TaskFilter{IDs: []string{taskID}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitTaskDone(t, done)
|
|
|
|
if task.Meta.Opts.Path != categoryDir {
|
|
t.Fatalf("retried task path = %q, want the category dir %q", task.Meta.Opts.Path, categoryDir)
|
|
}
|
|
if task.NeedsPreparation {
|
|
t.Fatal("a prepared task must clear the needs preparation flag")
|
|
}
|
|
}
|
|
|
|
// A failed preparation save leaves the live task untouched, so the next
|
|
// attempt starts from the original base directory and name.
|
|
func TestDownloader_PreparationSaveFailureKeepsOriginalData(t *testing.T) {
|
|
url := startCategoryFileServer(t, "setup.data", []byte("0123456789"))
|
|
downloadDir := t.TempDir()
|
|
storage := &failNextTaskPutStorage{Storage: NewMemStorage()}
|
|
downloader := NewDownloader(&DownloaderConfig{Storage: storage})
|
|
if err := downloader.Setup(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
downloader.Delete(nil, true)
|
|
downloader.Clear()
|
|
})
|
|
categoryDir := putCategoryConfig(t, downloader, downloadDir, true, []string{"data"})
|
|
setMaxRunning(t, downloader, 0)
|
|
|
|
result, err := downloader.Resolve(&base.Request{URL: url})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
taskID, err := downloader.CreateWithOptions(result.ID, &base.Options{
|
|
Path: downloadDir,
|
|
Name: "renamed?.data",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
task := downloader.GetTask(taskID)
|
|
if task == nil {
|
|
t.Fatal("task not found")
|
|
}
|
|
originalMeta := util.DeepClone(task.Meta)
|
|
originalRequest := task.Meta.Req
|
|
storage.beforeTaskPut = func(snapshot *Task) {
|
|
if !task.NeedsPreparation || !reflect.DeepEqual(task.Meta, originalMeta) {
|
|
t.Error("preparation was published before the snapshot was saved")
|
|
}
|
|
if snapshot.NeedsPreparation || snapshot.Meta.Opts.Path != categoryDir {
|
|
t.Error("the snapshot must contain the prepared directory and cleared flag")
|
|
}
|
|
}
|
|
|
|
storage.fail.Store(true)
|
|
if err := downloader.prepareTask(task); err == nil {
|
|
t.Fatal("prepareTask() succeeded, want a storage error")
|
|
}
|
|
if !task.NeedsPreparation {
|
|
t.Fatal("a failed save must keep the needs preparation flag")
|
|
}
|
|
if !reflect.DeepEqual(task.Meta, originalMeta) {
|
|
t.Fatalf("task metadata changed after a failed save:\n got: %#v\nwant: %#v", task.Meta, originalMeta)
|
|
}
|
|
if !reflect.DeepEqual(task.fetcher.Meta(), originalMeta) {
|
|
t.Fatalf("fetcher metadata changed after a failed save:\n got: %#v\nwant: %#v", task.fetcher.Meta(), originalMeta)
|
|
}
|
|
|
|
if err := downloader.prepareTask(task); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if task.NeedsPreparation {
|
|
t.Fatal("a successful retry must clear the needs preparation flag")
|
|
}
|
|
if task.Meta.Opts.Path != categoryDir {
|
|
t.Fatalf("retried task path = %q, want %q", task.Meta.Opts.Path, categoryDir)
|
|
}
|
|
if task.Meta != task.fetcher.Meta() || task.Meta.Req != originalRequest {
|
|
t.Fatal("preparation must preserve the shared metadata and resolved request")
|
|
}
|
|
storage.beforeTaskPut = nil
|
|
task.Meta.Res.Size++
|
|
var saved Task
|
|
if _, err := storage.Get(bucketTask, taskID, &saved); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if saved.Meta.Res.Size != originalMeta.Res.Size {
|
|
t.Fatal("live metadata changes must not alter the saved task snapshot")
|
|
}
|
|
}
|
|
|
|
// The white list is checked on every start, a task that was rejected once
|
|
// keeps failing instead of writing into the rejected directory.
|
|
func TestDownloader_WhiteListIsCheckedOnEveryStart(t *testing.T) {
|
|
url := startCategoryFileServer(t, "setup.data", []byte("0123456789"))
|
|
downloadDir := t.TempDir()
|
|
allowedDir := t.TempDir()
|
|
|
|
downloader := newSetupCategoryDownloader(t)
|
|
putCategoryConfig(t, downloader, downloadDir, true, []string{"data"})
|
|
downloader.cfg.WhiteDownloadDirs = []string{allowedDir}
|
|
|
|
failed := watchTaskError(t, downloader)
|
|
taskID, err := downloader.CreateDirect(&base.Request{URL: url}, &base.Options{Path: downloadDir})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
task := downloader.GetTask(taskID)
|
|
if task == nil {
|
|
t.Fatal("task not found")
|
|
}
|
|
waitTaskError(t, failed)
|
|
waitTaskStatus(t, downloader, task, base.DownloadStatusError)
|
|
|
|
// The task is continued, the white list still rejects the directory.
|
|
failed = watchTaskError(t, downloader)
|
|
if err := downloader.Continue(&TaskFilter{IDs: []string{taskID}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitTaskError(t, failed)
|
|
waitTaskStatus(t, downloader, task, base.DownloadStatusError)
|
|
|
|
if _, err := os.Stat(filepath.Join(downloadDir, "setup.data")); err == nil {
|
|
t.Fatalf("the file was written into the rejected dir %q", downloadDir)
|
|
}
|
|
}
|
|
|
|
// The white list is checked on every start, even for a task that was prepared
|
|
// before the white list changed.
|
|
func TestDownloader_WhiteListIsCheckedForAPreparedTask(t *testing.T) {
|
|
// The first request is truncated, so the download fails after the task was
|
|
// prepared and the retry can show that the directory is checked again.
|
|
var requests int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Disposition", `attachment; filename="setup.data"`)
|
|
w.Header().Set("Content-Length", "8")
|
|
w.WriteHeader(http.StatusOK)
|
|
if atomic.AddInt32(&requests, 1) == 1 {
|
|
w.Write([]byte("0123"))
|
|
return
|
|
}
|
|
w.Write([]byte("01234567"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
downloadDir := t.TempDir()
|
|
allowedDir := t.TempDir()
|
|
downloader := newSetupCategoryDownloader(t)
|
|
categoryDir := putCategoryConfig(t, downloader, downloadDir, true, []string{"data"})
|
|
downloader.cfg.WhiteDownloadDirs = []string{allowedDir, downloadDir, categoryDir}
|
|
|
|
failed := watchTaskError(t, downloader)
|
|
taskID, err := downloader.CreateDirect(&base.Request{URL: server.URL}, &base.Options{Path: downloadDir})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
task := downloader.GetTask(taskID)
|
|
if task == nil {
|
|
t.Fatal("task not found")
|
|
}
|
|
waitTaskError(t, failed)
|
|
waitTaskStatus(t, downloader, task, base.DownloadStatusError)
|
|
if task.Meta.Opts.Path != categoryDir {
|
|
t.Fatalf("task path = %q, want the category dir %q", task.Meta.Opts.Path, categoryDir)
|
|
}
|
|
|
|
// The white list no longer accepts the prepared directory, the task must
|
|
// not download into it.
|
|
downloader.cfg.WhiteDownloadDirs = []string{allowedDir}
|
|
requestsBeforeRetry := atomic.LoadInt32(&requests)
|
|
failed = watchTaskError(t, downloader)
|
|
if err := downloader.Continue(&TaskFilter{IDs: []string{taskID}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitTaskError(t, failed)
|
|
waitTaskStatus(t, downloader, task, base.DownloadStatusError)
|
|
|
|
if atomic.LoadInt32(&requests) != requestsBeforeRetry {
|
|
t.Fatalf("the task downloaded into the rejected dir %q", categoryDir)
|
|
}
|
|
}
|
|
|
|
// A prepared task keeps its directory, a later start never classifies it
|
|
// again.
|
|
func TestDownloader_PreparedTaskKeepsItsDirectory(t *testing.T) {
|
|
// The first request is truncated, so the download fails after the task was
|
|
// prepared and the retry can show that the directory is kept.
|
|
var requests int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Disposition", `attachment; filename="setup.data"`)
|
|
w.Header().Set("Content-Length", "8")
|
|
w.WriteHeader(http.StatusOK)
|
|
if atomic.AddInt32(&requests, 1) == 1 {
|
|
w.Write([]byte("0123"))
|
|
return
|
|
}
|
|
w.Write([]byte("01234567"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
downloadDir := t.TempDir()
|
|
downloader := newSetupCategoryDownloader(t)
|
|
categoryDir := putCategoryConfig(t, downloader, downloadDir, true, []string{"data"})
|
|
|
|
failed := watchTaskError(t, downloader)
|
|
taskID, err := downloader.CreateDirect(&base.Request{URL: server.URL}, &base.Options{Path: downloadDir})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
task := downloader.GetTask(taskID)
|
|
if task == nil {
|
|
t.Fatal("task not found")
|
|
}
|
|
waitTaskError(t, failed)
|
|
waitTaskStatus(t, downloader, task, base.DownloadStatusError)
|
|
if task.Meta.Opts.Path != categoryDir {
|
|
t.Fatalf("task path = %q, want the category dir %q", task.Meta.Opts.Path, categoryDir)
|
|
}
|
|
if task.NeedsPreparation {
|
|
t.Fatal("a prepared task must clear the needs preparation flag")
|
|
}
|
|
|
|
// The category no longer matches the file, the prepared task keeps the
|
|
// directory it was prepared with.
|
|
putCategoryConfig(t, downloader, downloadDir, true, []string{"iso"})
|
|
done := watchTaskDone(t, downloader)
|
|
if err := downloader.Continue(&TaskFilter{IDs: []string{taskID}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitTaskDone(t, done)
|
|
|
|
if task.Meta.Opts.Path != categoryDir {
|
|
t.Fatalf("task path = %q, want the prepared dir %q", task.Meta.Opts.Path, categoryDir)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(categoryDir, "setup.data")); err != nil {
|
|
t.Fatalf("the file was not written into the prepared dir: %v", err)
|
|
}
|
|
}
|
|
|
|
// The global default download directory is only saved when the user named the
|
|
// directory explicitly, an auto categorized task never becomes the default.
|
|
func TestDownloader_DefaultPathNeedsTheSkipFlag(t *testing.T) {
|
|
url := startCategoryFileServer(t, "setup.data", []byte("0123456789"))
|
|
downloadDir := t.TempDir()
|
|
chosenDir := t.TempDir()
|
|
downloader := newSetupCategoryDownloader(t)
|
|
putCategoryConfig(t, downloader, downloadDir, true, []string{"data"})
|
|
done := watchTaskDone(t, downloader)
|
|
|
|
// The skip flag is missing, the task keeps the auto categorize decision
|
|
// and the global default stays untouched.
|
|
if _, err := downloader.CreateDirect(&base.Request{URL: url}, &base.Options{
|
|
Path: chosenDir,
|
|
AsDefaultPath: true,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitTaskDone(t, done)
|
|
cfg, err := downloader.GetConfig()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.DownloadDir == chosenDir {
|
|
t.Fatalf("default download dir = %q, want it unchanged", cfg.DownloadDir)
|
|
}
|
|
|
|
// The user named the directory, so it becomes the global default.
|
|
if _, err := downloader.CreateDirect(&base.Request{URL: url}, &base.Options{
|
|
Path: chosenDir,
|
|
AsDefaultPath: true,
|
|
SkipAutoCategorize: true,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitTaskDone(t, done)
|
|
cfg, err = downloader.GetConfig()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.DownloadDir != chosenDir {
|
|
t.Fatalf("default download dir = %q, want %q", cfg.DownloadDir, chosenDir)
|
|
}
|
|
}
|
|
|
|
// Every task of a batch decides its own directory, the shared options of the
|
|
// batch do not leak into the single tasks.
|
|
func TestDownloader_BatchTasksDecideTheirOwnDirectory(t *testing.T) {
|
|
url := startCategoryFileServer(t, "setup.data", []byte("0123456789"))
|
|
downloadDir := t.TempDir()
|
|
chosenDir := t.TempDir()
|
|
downloader := newSetupCategoryDownloader(t)
|
|
categoryDir := putCategoryConfig(t, downloader, downloadDir, true, []string{"data"})
|
|
done := watchTaskDone(t, downloader)
|
|
|
|
taskIDs, err := downloader.CreateDirectBatch(&base.CreateTaskBatch{
|
|
Reqs: []*base.CreateTaskBatchItem{
|
|
{
|
|
Req: &base.Request{URL: url},
|
|
Opts: &base.Options{Path: chosenDir, SkipAutoCategorize: true},
|
|
},
|
|
{
|
|
Req: &base.Request{URL: url},
|
|
Opts: &base.Options{Path: downloadDir},
|
|
},
|
|
},
|
|
Opts: &base.Options{Path: downloadDir},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(taskIDs) != 2 {
|
|
t.Fatalf("task count = %d, want 2", len(taskIDs))
|
|
}
|
|
waitTaskDone(t, done)
|
|
waitTaskDone(t, done)
|
|
|
|
skipped := downloader.GetTask(taskIDs[0])
|
|
if skipped == nil {
|
|
t.Fatal("task not found")
|
|
}
|
|
if skipped.Meta.Opts.Path != chosenDir {
|
|
t.Fatalf("skipped task path = %q, want %q", skipped.Meta.Opts.Path, chosenDir)
|
|
}
|
|
categorized := downloader.GetTask(taskIDs[1])
|
|
if categorized == nil {
|
|
t.Fatal("task not found")
|
|
}
|
|
if categorized.Meta.Opts.Path != categoryDir {
|
|
t.Fatalf("categorized task path = %q, want %q", categorized.Meta.Opts.Path, categoryDir)
|
|
}
|
|
}
|