github.com/ergo-services/ergo@v1.999.224/tests/saga_test.go (about)

     1  package tests
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/ergo-services/ergo"
    10  	"github.com/ergo-services/ergo/etf"
    11  	"github.com/ergo-services/ergo/gen"
    12  	"github.com/ergo-services/ergo/node"
    13  )
    14  
    15  //
    16  // Worker
    17  //
    18  type testSagaWorker struct {
    19  	gen.SagaWorker
    20  }
    21  
    22  func (w *testSagaWorker) HandleJobStart(process *gen.SagaWorkerProcess, job gen.SagaJob) error {
    23  	values := job.Value.([]int)
    24  	result := sumSlice(values)
    25  	if err := process.SendResult(result); err != nil {
    26  		panic(err)
    27  	}
    28  	return nil
    29  }
    30  func (w *testSagaWorker) HandleJobCancel(process *gen.SagaWorkerProcess, reason string) {
    31  	return
    32  }
    33  
    34  //
    35  // Saga
    36  //
    37  type testSaga struct {
    38  	gen.Saga
    39  	res    chan interface{}
    40  	result int
    41  }
    42  
    43  type testSagaState struct {
    44  	txs map[gen.SagaTransactionID]*txjobs
    45  }
    46  
    47  type txjobs struct {
    48  	result int
    49  	jobs   map[gen.SagaJobID]bool
    50  }
    51  
    52  func (gs *testSaga) InitSaga(process *gen.SagaProcess, args ...etf.Term) (gen.SagaOptions, error) {
    53  	opts := gen.SagaOptions{
    54  		Worker: &testSagaWorker{},
    55  	}
    56  	gs.res = make(chan interface{}, 2)
    57  	process.State = &testSagaState{
    58  		txs: make(map[gen.SagaTransactionID]*txjobs),
    59  	}
    60  	return opts, nil
    61  }
    62  
    63  func (gs *testSaga) HandleTxNew(process *gen.SagaProcess, id gen.SagaTransactionID, value interface{}) gen.SagaStatus {
    64  	task := value.(taskTX)
    65  	values := splitSlice(task.value, task.chunks)
    66  	state := process.State.(*testSagaState)
    67  	j := txjobs{
    68  		jobs: make(map[gen.SagaJobID]bool),
    69  	}
    70  	for i := range values {
    71  		job_id, err := process.StartJob(id, gen.SagaJobOptions{}, values[i])
    72  		if err != nil {
    73  			return err
    74  		}
    75  		j.jobs[job_id] = true
    76  	}
    77  	state.txs[id] = &j
    78  	return gen.SagaStatusOK
    79  }
    80  
    81  func (gs *testSaga) HandleTxDone(process *gen.SagaProcess, id gen.SagaTransactionID, result interface{}) (interface{}, gen.SagaStatus) {
    82  	state := process.State.(*testSagaState)
    83  
    84  	gs.result += result.(int)
    85  	delete(state.txs, id)
    86  	if len(state.txs) == 0 {
    87  		gs.res <- gs.result
    88  	}
    89  	return nil, gen.SagaStatusOK
    90  }
    91  
    92  func (gs *testSaga) HandleTxCancel(process *gen.SagaProcess, id gen.SagaTransactionID, reason string) gen.SagaStatus {
    93  	return gen.SagaStatusOK
    94  }
    95  
    96  func (gs *testSaga) HandleTxResult(process *gen.SagaProcess, id gen.SagaTransactionID, from gen.SagaNextID, result interface{}) gen.SagaStatus {
    97  	return gen.SagaStatusOK
    98  }
    99  
   100  func (gs *testSaga) HandleTxInterim(process *gen.SagaProcess, id gen.SagaTransactionID, from gen.SagaNextID, interim interface{}) gen.SagaStatus {
   101  
   102  	return gen.SagaStatusOK
   103  }
   104  
   105  func (gs *testSaga) HandleJobResult(process *gen.SagaProcess, id gen.SagaTransactionID, from gen.SagaJobID, result interface{}) gen.SagaStatus {
   106  	state := process.State.(*testSagaState)
   107  	j := state.txs[id]
   108  	j.result += result.(int)
   109  	delete(j.jobs, from)
   110  
   111  	if len(j.jobs) == 0 {
   112  		process.SendResult(id, j.result)
   113  	}
   114  	return gen.SagaStatusOK
   115  }
   116  
   117  type task struct {
   118  	value  []int
   119  	split  int
   120  	chunks int
   121  }
   122  
   123  type taskTX struct {
   124  	value  []int
   125  	chunks int
   126  }
   127  
   128  func (gs *testSaga) HandleSagaDirect(process *gen.SagaProcess, ref etf.Ref, message interface{}) (interface{}, gen.DirectStatus) {
   129  	switch m := message.(type) {
   130  	case task:
   131  		values := splitSlice(m.value, m.split)
   132  		fmt.Printf("    process %v txs with %v value(s) each and chunk size %v: ", len(values), m.split, m.chunks)
   133  		for i := range values {
   134  			txValue := taskTX{
   135  				value:  values[i],
   136  				chunks: m.chunks,
   137  			}
   138  			process.StartTransaction(gen.SagaTransactionOptions{}, txValue)
   139  		}
   140  
   141  		return nil, gen.DirectStatusOK
   142  	}
   143  
   144  	return nil, fmt.Errorf("unknown request %#v", message)
   145  }
   146  
   147  func TestSagaSimple(t *testing.T) {
   148  	fmt.Printf("\n=== Test GenSagaSimple\n")
   149  	fmt.Printf("Starting node: nodeGenSagaSimple01@localhost...")
   150  
   151  	node, _ := ergo.StartNode("nodeGenSagaSimple01@localhost", "cookies", node.Options{})
   152  
   153  	if node == nil {
   154  		t.Fatal("can't start node")
   155  		return
   156  	}
   157  	fmt.Println("OK")
   158  
   159  	fmt.Printf("... Starting Saga processes: ")
   160  	saga := &testSaga{}
   161  	saga_process, err := node.Spawn("saga", gen.ProcessOptions{MailboxSize: 10000}, saga)
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	fmt.Println("OK")
   166  
   167  	rand.Seed(time.Now().Unix())
   168  
   169  	slice1 := rand.Perm(1000)
   170  	sum1 := sumSlice(slice1)
   171  	startTask1 := task{
   172  		value:  slice1,
   173  		split:  10, // 10 items per tx
   174  		chunks: 5,  // size of slice for worker
   175  	}
   176  	_, err = saga_process.Direct(startTask1)
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  	waitForResultWithValue(t, saga.res, sum1)
   181  
   182  	saga.result = 0
   183  	slice2 := rand.Perm(100)
   184  	sum2 := sumSlice(slice2)
   185  	startTask2 := task{
   186  		value:  slice2,
   187  		split:  1, // 1 items per tx
   188  		chunks: 5, // size of slice for worker
   189  	}
   190  	_, err = saga_process.Direct(startTask2)
   191  	if err != nil {
   192  		t.Fatal(err)
   193  	}
   194  	waitForResultWithValue(t, saga.res, sum2)
   195  
   196  	saga.result = 0
   197  	slice3 := rand.Perm(100)
   198  	sum3 := sumSlice(slice3)
   199  	startTask3 := task{
   200  		value:  slice3,
   201  		split:  100, // 100 items per tx
   202  		chunks: 5,   // size of slice for worker
   203  	}
   204  	_, err = saga_process.Direct(startTask3)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	waitForResultWithValue(t, saga.res, sum3)
   209  
   210  	saga.result = 0
   211  	slice4 := rand.Perm(10000)
   212  	sum4 := sumSlice(slice4)
   213  	startTask4 := task{
   214  		value:  slice4,
   215  		split:  100, // 100 items per tx
   216  		chunks: 5,   // size of slice for worker
   217  	}
   218  	_, err = saga_process.Direct(startTask4)
   219  	if err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	waitForResultWithValue(t, saga.res, sum4)
   223  	node.Stop()
   224  	node.Wait()
   225  }
   226  
   227  func splitSlice(slice []int, size int) [][]int {
   228  	var chunks [][]int
   229  	for i := 0; i < len(slice); i += size {
   230  		end := i + size
   231  		if end > len(slice) {
   232  			end = len(slice)
   233  		}
   234  		chunks = append(chunks, slice[i:end])
   235  	}
   236  	return chunks
   237  }
   238  
   239  func sumSlice(slice []int) int {
   240  	var result int
   241  	for i := range slice {
   242  		result += slice[i]
   243  	}
   244  	return result
   245  }