github.com/arcology-network/consensus-engine@v1.9.0/libs/async/async_test.go (about)

     1  package async
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync/atomic"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  )
    12  
    13  func TestParallel(t *testing.T) {
    14  
    15  	// Create tasks.
    16  	var counter = new(int32)
    17  	var tasks = make([]Task, 100*1000)
    18  	for i := 0; i < len(tasks); i++ {
    19  		tasks[i] = func(i int) (res interface{}, abort bool, err error) {
    20  			atomic.AddInt32(counter, 1)
    21  			return -1 * i, false, nil
    22  		}
    23  	}
    24  
    25  	// Run in parallel.
    26  	var trs, ok = Parallel(tasks...)
    27  	assert.True(t, ok)
    28  
    29  	// Verify.
    30  	assert.Equal(t, int(*counter), len(tasks), "Each task should have incremented the counter already")
    31  	var failedTasks int
    32  	for i := 0; i < len(tasks); i++ {
    33  		taskResult, ok := trs.LatestResult(i)
    34  		switch {
    35  		case !ok:
    36  			assert.Fail(t, "Task #%v did not complete.", i)
    37  			failedTasks++
    38  		case taskResult.Error != nil:
    39  			assert.Fail(t, "Task should not have errored but got %v", taskResult.Error)
    40  			failedTasks++
    41  		case !assert.Equal(t, -1*i, taskResult.Value.(int)):
    42  			assert.Fail(t, "Task should have returned %v but got %v", -1*i, taskResult.Value.(int))
    43  			failedTasks++
    44  		}
    45  		// else {
    46  		// Good!
    47  		// }
    48  	}
    49  	assert.Equal(t, failedTasks, 0, "No task should have failed")
    50  	assert.Nil(t, trs.FirstError(), "There should be no errors")
    51  	assert.Equal(t, 0, trs.FirstValue(), "First value should be 0")
    52  }
    53  
    54  func TestParallelAbort(t *testing.T) {
    55  
    56  	var flow1 = make(chan struct{}, 1)
    57  	var flow2 = make(chan struct{}, 1)
    58  	var flow3 = make(chan struct{}, 1) // Cap must be > 0 to prevent blocking.
    59  	var flow4 = make(chan struct{}, 1)
    60  
    61  	// Create tasks.
    62  	var tasks = []Task{
    63  		func(i int) (res interface{}, abort bool, err error) {
    64  			assert.Equal(t, i, 0)
    65  			flow1 <- struct{}{}
    66  			return 0, false, nil
    67  		},
    68  		func(i int) (res interface{}, abort bool, err error) {
    69  			assert.Equal(t, i, 1)
    70  			flow2 <- <-flow1
    71  			return 1, false, errors.New("some error")
    72  		},
    73  		func(i int) (res interface{}, abort bool, err error) {
    74  			assert.Equal(t, i, 2)
    75  			flow3 <- <-flow2
    76  			return 2, true, nil
    77  		},
    78  		func(i int) (res interface{}, abort bool, err error) {
    79  			assert.Equal(t, i, 3)
    80  			<-flow4
    81  			return 3, false, nil
    82  		},
    83  	}
    84  
    85  	// Run in parallel.
    86  	var taskResultSet, ok = Parallel(tasks...)
    87  	assert.False(t, ok, "ok should be false since we aborted task #2.")
    88  
    89  	// Verify task #3.
    90  	// Initially taskResultSet.chz[3] sends nothing since flow4 didn't send.
    91  	waitTimeout(t, taskResultSet.chz[3], "Task #3")
    92  
    93  	// Now let the last task (#3) complete after abort.
    94  	flow4 <- <-flow3
    95  
    96  	// Wait until all tasks have returned or panic'd.
    97  	taskResultSet.Wait()
    98  
    99  	// Verify task #0, #1, #2.
   100  	checkResult(t, taskResultSet, 0, 0, nil, nil)
   101  	checkResult(t, taskResultSet, 1, 1, errors.New("some error"), nil)
   102  	checkResult(t, taskResultSet, 2, 2, nil, nil)
   103  	checkResult(t, taskResultSet, 3, 3, nil, nil)
   104  }
   105  
   106  func TestParallelRecover(t *testing.T) {
   107  
   108  	// Create tasks.
   109  	var tasks = []Task{
   110  		func(i int) (res interface{}, abort bool, err error) {
   111  			return 0, false, nil
   112  		},
   113  		func(i int) (res interface{}, abort bool, err error) {
   114  			return 1, false, errors.New("some error")
   115  		},
   116  		func(i int) (res interface{}, abort bool, err error) {
   117  			panic(2)
   118  		},
   119  	}
   120  
   121  	// Run in parallel.
   122  	var taskResultSet, ok = Parallel(tasks...)
   123  	assert.False(t, ok, "ok should be false since we panic'd in task #2.")
   124  
   125  	// Verify task #0, #1, #2.
   126  	checkResult(t, taskResultSet, 0, 0, nil, nil)
   127  	checkResult(t, taskResultSet, 1, 1, errors.New("some error"), nil)
   128  	checkResult(t, taskResultSet, 2, nil, nil, fmt.Errorf("panic in task %v", 2).Error())
   129  }
   130  
   131  // Wait for result
   132  func checkResult(t *testing.T, taskResultSet *TaskResultSet, index int,
   133  	val interface{}, err error, pnk interface{}) {
   134  	taskResult, ok := taskResultSet.LatestResult(index)
   135  	taskName := fmt.Sprintf("Task #%v", index)
   136  	assert.True(t, ok, "TaskResultCh unexpectedly closed for %v", taskName)
   137  	assert.Equal(t, val, taskResult.Value, taskName)
   138  	switch {
   139  	case err != nil:
   140  		assert.Equal(t, err.Error(), taskResult.Error.Error(), taskName)
   141  	case pnk != nil:
   142  		assert.Contains(t, taskResult.Error.Error(), pnk, taskName)
   143  	default:
   144  		assert.Nil(t, taskResult.Error, taskName)
   145  	}
   146  }
   147  
   148  // Wait for timeout (no result)
   149  func waitTimeout(t *testing.T, taskResultCh TaskResultCh, taskName string) {
   150  	select {
   151  	case _, ok := <-taskResultCh:
   152  		if !ok {
   153  			assert.Fail(t, "TaskResultCh unexpectedly closed (%v)", taskName)
   154  		} else {
   155  			assert.Fail(t, "TaskResultCh unexpectedly returned for %v", taskName)
   156  		}
   157  	case <-time.After(1 * time.Second): // TODO use deterministic time?
   158  		// Good!
   159  	}
   160  }