зеркало из https://github.com/Azure/go-asynctask.git
197 строки
6.4 KiB
Go
197 строки
6.4 KiB
Go
package asynctask_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Azure/go-asynctask"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const testContextKey string = "testing"
|
|
const countingTaskDefaultStepLatency time.Duration = 20 * time.Millisecond
|
|
|
|
func newTestContext(t *testing.T) context.Context {
|
|
return context.WithValue(context.TODO(), testContextKey, t)
|
|
}
|
|
|
|
func newTestContextWithTimeout(t *testing.T, timeout time.Duration) (context.Context, context.CancelFunc) {
|
|
return context.WithTimeout(context.WithValue(context.TODO(), testContextKey, t), timeout)
|
|
}
|
|
|
|
func getCountingTask(countTo int, taskId string, sleepInterval time.Duration) asynctask.AsyncFunc[int] {
|
|
return func(ctx context.Context) (int, error) {
|
|
t := ctx.Value(testContextKey).(*testing.T)
|
|
|
|
result := 0
|
|
for i := 0; i < countTo; i++ {
|
|
select {
|
|
case <-time.After(sleepInterval):
|
|
t.Logf("[%s]: counting %d", taskId, i)
|
|
result = i
|
|
case <-ctx.Done():
|
|
// testing.Logf would cause DataRace error when test is already finished: https://github.com/golang/go/issues/40343
|
|
// leave minor time buffer before exit test to finish this last logging at least.
|
|
t.Logf("[%s]: work canceled", taskId)
|
|
return result, nil
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
func TestEasyGenericCase(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancelFunc := newTestContextWithTimeout(t, 3*time.Second)
|
|
defer cancelFunc()
|
|
|
|
t1 := asynctask.Start(ctx, getCountingTask(10, "easyTask", countingTaskDefaultStepLatency))
|
|
assert.Equal(t, asynctask.StateRunning, t1.State(), "Task should queued to Running")
|
|
|
|
rawResult, err := t1.Result(ctx)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, asynctask.StateCompleted, t1.State(), "Task should complete by now")
|
|
assert.NotNil(t, rawResult)
|
|
assert.Equal(t, rawResult, 9)
|
|
|
|
// wait Again,
|
|
start := time.Now()
|
|
rawResult, err = t1.Result(ctx)
|
|
elapsed := time.Since(start)
|
|
// nothing should change
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, asynctask.StateCompleted, t1.State(), "Task should complete by now")
|
|
assert.NotNil(t, rawResult)
|
|
assert.Equal(t, rawResult, 9)
|
|
|
|
// Result should be returned immediately
|
|
assert.True(t, elapsed.Milliseconds() < 1, fmt.Sprintf("Second wait should have return immediately: %s", elapsed))
|
|
}
|
|
|
|
func TestCancelFuncOnGeneric(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancelFunc := newTestContextWithTimeout(t, 3*time.Second)
|
|
defer cancelFunc()
|
|
|
|
t1 := asynctask.Start(ctx, getCountingTask(10, "cancelTask", countingTaskDefaultStepLatency))
|
|
assert.Equal(t, asynctask.StateRunning, t1.State(), "Task should queued to Running")
|
|
|
|
time.Sleep(countingTaskDefaultStepLatency)
|
|
t1.Cancel()
|
|
|
|
_, err := t1.Result(ctx)
|
|
assert.Equal(t, asynctask.ErrCanceled, err, "should return reason of error")
|
|
assert.Equal(t, asynctask.StateCanceled, t1.State(), "Task should remain in cancel state")
|
|
|
|
// I can cancel again, and nothing changes
|
|
time.Sleep(countingTaskDefaultStepLatency)
|
|
t1.Cancel()
|
|
_, err = t1.Result(ctx)
|
|
assert.Equal(t, asynctask.ErrCanceled, err, "should return reason of error")
|
|
assert.Equal(t, asynctask.StateCanceled, t1.State(), "Task should remain in cancel state")
|
|
|
|
// cancel a task shouldn't cancel it's parent context.
|
|
select {
|
|
case <-ctx.Done():
|
|
assert.Fail(t, "parent context got canceled")
|
|
default:
|
|
t.Log("parent context still running")
|
|
}
|
|
}
|
|
|
|
func TestConsistentResultAfterCancelGenericTask(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancelFunc := newTestContextWithTimeout(t, 3*time.Second)
|
|
defer cancelFunc()
|
|
|
|
t1 := asynctask.Start(ctx, getCountingTask(10, "consistentTask1", countingTaskDefaultStepLatency))
|
|
t2 := asynctask.Start(ctx, getCountingTask(10, "consistentTask2", countingTaskDefaultStepLatency))
|
|
assert.Equal(t, asynctask.StateRunning, t1.State(), "Task should queued to Running")
|
|
|
|
time.Sleep(countingTaskDefaultStepLatency)
|
|
start := time.Now()
|
|
t1.Cancel()
|
|
duration := time.Since(start)
|
|
assert.Equal(t, asynctask.StateCanceled, t1.State(), "t1 should turn to Canceled")
|
|
assert.True(t, duration < 1*time.Millisecond, "cancel shouldn't take that long")
|
|
|
|
// wait til routine finish
|
|
rawResult, err := t2.Result(ctx)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, asynctask.StateCompleted, t2.State(), "t2 should complete")
|
|
assert.Equal(t, rawResult, 9)
|
|
|
|
// t1 should remain canceled and
|
|
rawResult, err = t1.Result(ctx)
|
|
assert.Equal(t, asynctask.ErrCanceled, err, "should return reason of error")
|
|
assert.Equal(t, asynctask.StateCanceled, t1.State(), "Task should remain in cancel state")
|
|
assert.Equal(t, rawResult, 0) // default value for int
|
|
}
|
|
|
|
func TestCompletedGenericTask(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancelFunc := newTestContextWithTimeout(t, 3*time.Second)
|
|
defer cancelFunc()
|
|
|
|
result := "something"
|
|
tsk := asynctask.NewCompletedTask(&result)
|
|
assert.Equal(t, asynctask.StateCompleted, tsk.State(), "Task should in CompletedState")
|
|
|
|
// nothing should happen
|
|
tsk.Cancel()
|
|
assert.Equal(t, asynctask.StateCompleted, tsk.State(), "Task should still in CompletedState")
|
|
|
|
// you get nil result and nil error
|
|
resultGet, err := tsk.Result(ctx)
|
|
assert.Equal(t, asynctask.StateCompleted, tsk.State(), "Task should still in CompletedState")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, *resultGet, result)
|
|
}
|
|
|
|
func TestActionToFunc(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
action := func(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
ctx := context.Background()
|
|
task := asynctask.Start(ctx, asynctask.ActionToFunc(action))
|
|
result, err := task.Result(ctx)
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestCrazyCaseGeneric(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancelFunc := newTestContextWithTimeout(t, 3*time.Second)
|
|
defer cancelFunc()
|
|
|
|
numOfTasks := 100 // if you have --race switch on: limit on 8128 simultaneously alive goroutines is exceeded, dying
|
|
tasks := map[int]*asynctask.Task[int]{}
|
|
for i := 0; i < numOfTasks; i++ {
|
|
tasks[i] = asynctask.Start(ctx, getCountingTask(10, fmt.Sprintf("CrazyTask%d", i), countingTaskDefaultStepLatency))
|
|
}
|
|
|
|
// sleep 1 step, then cancel task with even number
|
|
time.Sleep(countingTaskDefaultStepLatency)
|
|
for i := 0; i < numOfTasks; i += 2 {
|
|
tasks[i].Cancel()
|
|
}
|
|
|
|
time.Sleep(time.Duration(numOfTasks) * 2 * time.Microsecond)
|
|
for i := 0; i < numOfTasks; i += 1 {
|
|
rawResult, err := tasks[i].Result(ctx)
|
|
|
|
if i%2 == 0 {
|
|
assert.Equal(t, asynctask.ErrCanceled, err, fmt.Sprintf("task %s should be canceled, but it finished with %+v", fmt.Sprintf("CrazyTask%d", i), rawResult))
|
|
assert.Equal(t, rawResult, 0)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, rawResult, 9)
|
|
}
|
|
}
|
|
}
|