PPysolate SpineREAD THE CODE · UNDERSTAND THE SYSTEM
课程与章节
源码索引 / CURRENT IMPLEMENTATION

learning/go-patterns/stages/06-bounded/main_test.go

113 行 · 点击行号可定位;函数链接随声明位置更新,不手填行号。

跳到函数 / 声明
package main

import (
	"fmt"
	"sync"
	"testing"
	"time"
)

func TestRunBoundedHonorsTheConcurrencyLimit(t *testing.T) {
	const (
		taskCount = 6
		limit     = 2
	)

	started := make(chan struct{}, taskCount)
	release := make(chan struct{})
	var mu sync.Mutex
	active := 0
	maxActive := 0
	tasks := make([]Task, taskCount)
	for index := range tasks {
		index := index
		tasks[index] = func() (string, error) {
			mu.Lock()
			active++
			if active > maxActive {
				maxActive = active
			}
			mu.Unlock()
			started <- struct{}{}

			<-release

			mu.Lock()
			active--
			mu.Unlock()
			return fmt.Sprintf("job-%d", index), nil
		}
	}

	done := make(chan struct{})
	var values []string
	var err error
	go func() {
		values, err = RunBounded(tasks, limit)
		close(done)
	}()

	for i := 0; i < limit; i++ {
		select {
		case <-started:
		case <-time.After(time.Second):
			t.Fatalf("timed out waiting for worker %d to start", i+1)
		}
	}
	select {
	case <-started:
		t.Fatal("a third task started while the limit was full")
	default:
	}

	close(release)
	select {
	case <-done:
	case <-time.After(time.Second):
		t.Fatal("RunBounded did not finish after tasks were released")
	}

	if err != nil {
		t.Fatalf("RunBounded returned error: %v", err)
	}
	if len(values) != taskCount {
		t.Fatalf("got %d results, want %d", len(values), taskCount)
	}
	mu.Lock()
	gotMaxActive := maxActive
	mu.Unlock()
	if gotMaxActive > limit {
		t.Fatalf("max active = %d, want at most %d", gotMaxActive, limit)
	}
}

func TestRunBoundedReturnsAnErrorAfterTheWholeBatchFinishes(t *testing.T) {
	const taskCount = 4
	completed := make(chan struct{}, taskCount)
	tasks := make([]Task, taskCount)
	for index := range tasks {
		index := index
		tasks[index] = func() (string, error) {
			completed <- struct{}{}
			if index == 1 {
				return "", fmt.Errorf("job %d failed", index)
			}
			return fmt.Sprintf("job %d", index), nil
		}
	}

	values, err := RunBounded(tasks, 2)
	if err == nil || err.Error() != "job 1 failed" {
		t.Fatalf("error = %v, want job 1 failed", err)
	}
	for index := 0; index < taskCount; index++ {
		select {
		case <-completed:
		case <-time.After(time.Second):
			t.Fatalf("task %d did not complete before RunBounded returned", index)
		}
	}
	if values[0] != "job 0" || values[2] != "job 2" {
		t.Fatalf("values = %#v, want successful results preserved", values)
	}
}