github.com/git-amp/amp-sdk-go@v0.7.5/stdlib/symbol/tests/stress-test.go (about)

     1  package tests
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"strconv"
     7  	"sync"
     8  	"sync/atomic"
     9  	"testing"
    10  
    11  	"github.com/git-amp/amp-sdk-go/stdlib/symbol"
    12  )
    13  
    14  const kTotalEntries = 1001447
    15  
    16  func DoTableTest(t *testing.T, totalEntries int, opener func() (symbol.Table, error)) {
    17  	if totalEntries == 0 {
    18  		totalEntries = kTotalEntries
    19  	}
    20  
    21  	tt := tableTester{
    22  		errs: make(chan error),
    23  	}
    24  	tt.setupTestData(totalEntries)
    25  
    26  	go func() {
    27  
    28  		// 1) tortuously fill and write a table
    29  		table, err := opener()
    30  		if err != nil {
    31  			tt.errs <- err
    32  		}
    33  		tt.fillTable(table)
    34  		table.Close()
    35  
    36  		// 2) tortuously read and check the table
    37  		table, err = opener()
    38  		if err != nil {
    39  			tt.errs <- err
    40  		}
    41  		tt.checkTable(table)
    42  		table.Close()
    43  
    44  		close(tt.errs)
    45  	}()
    46  
    47  	// wait for test to finish, fail the test on any errors
    48  	err := <-tt.errs
    49  	if err != nil {
    50  		t.Fatal(err)
    51  	}
    52  }
    53  
    54  type tableTester struct {
    55  	errs chan error
    56  	vals [][]byte
    57  	IDs  []symbol.ID
    58  }
    59  
    60  func (tt *tableTester) setupTestData(totalEntries int) {
    61  	if len(tt.vals) != totalEntries {
    62  		tt.vals = make([][]byte, totalEntries)
    63  		for i := range tt.vals {
    64  			tt.vals[i] = []byte(strconv.Itoa(i))
    65  		}
    66  	}
    67  	if cap(tt.IDs) < totalEntries {
    68  		tt.IDs = make([]symbol.ID, totalEntries)
    69  	} else {
    70  		tt.IDs = tt.IDs[:totalEntries]
    71  		for i := range tt.IDs {
    72  			tt.IDs[i] = 0
    73  		}
    74  	}
    75  }
    76  
    77  var (
    78  	hardwireStart     = symbol.DefaultIssuerMin - hardwireTestCount
    79  	hardwireTestCount = 101
    80  )
    81  
    82  func (tt *tableTester) fillTable(table symbol.Table) {
    83  	vals := tt.vals
    84  	totalEntries := len(vals)
    85  
    86  	// Test reserved symbol ID space -- set symbol IDs less than symbol.MinIssuedID
    87  	// Do multiple write passes to check overwrites don't cause issues.
    88  	for k := 0; k < 3; k++ {
    89  		for j := 0; j < hardwireTestCount; j++ {
    90  			idx := hardwireStart + j
    91  			symID := symbol.ID(idx)
    92  			symID_got := table.SetSymbolID(vals[idx], symID)
    93  			if symID_got != symID {
    94  				tt.errs <- errors.New("SetSymbolID failed setup check")
    95  			}
    96  		}
    97  	}
    98  
    99  	hardwireCount := int32(0)
   100  	hardwireCountPtr := &hardwireCount
   101  
   102  	// Populate the table with multiple workers all setting values at once
   103  	{
   104  		running := &sync.WaitGroup{}
   105  		numWorkers := 5
   106  		for i := 0; i < numWorkers; i++ {
   107  			running.Add(1)
   108  			startAt := len(vals) * i / numWorkers
   109  			go func() {
   110  				var symBuf [128]byte
   111  				for j := 0; j < totalEntries; j++ {
   112  					idx := (startAt + j) % totalEntries
   113  					symID := table.GetSymbolID(vals[idx], true)
   114  					if symID < symbol.DefaultIssuerMin {
   115  						atomic.AddInt32(hardwireCountPtr, 1)
   116  					}
   117  					stored := table.GetSymbol(symID, symBuf[:0])
   118  					if !bytes.Equal(stored, vals[idx]) {
   119  						tt.errs <- errors.New("LookupID failed setup check")
   120  					}
   121  					symID_got := table.SetSymbolID(vals[idx], symID)
   122  					if symID_got != symID {
   123  						tt.errs <- errors.New("SetSymbolID failed setup check")
   124  					}
   125  				}
   126  				running.Done()
   127  			}()
   128  		}
   129  
   130  		running.Wait()
   131  
   132  		if int(hardwireCount) != numWorkers*hardwireTestCount {
   133  			tt.errs <- errors.New("hardwire test count failed")
   134  		}
   135  
   136  	}
   137  
   138  	var symBuf [128]byte
   139  
   140  	// Verify all the tokens are valid
   141  	IDs := tt.IDs
   142  	for i, k := range vals {
   143  		IDs[i] = table.GetSymbolID(k, false)
   144  		if IDs[i] == 0 {
   145  			tt.errs <- errors.New("GetSymbolID failed final verification")
   146  		}
   147  		stored := table.GetSymbol(IDs[i], symBuf[:0])
   148  		if !bytes.Equal(stored, vals[i]) {
   149  			tt.errs <- errors.New("LookupID failed final verification")
   150  		}
   151  	}
   152  
   153  	if table.GetSymbol(123456789, nil) != nil {
   154  		tt.errs <- errors.New("bad ID returns value")
   155  	}
   156  	if table.GetSymbolID([]byte{4, 5, 6, 7, 8, 9, 10, 11}, false) != 0 {
   157  		tt.errs <- errors.New("bad value returns ID")
   158  	}
   159  }
   160  
   161  func (tt *tableTester) checkTable(table symbol.Table) {
   162  	vals := tt.vals
   163  	totalEntries := len(vals)
   164  
   165  	// Check that all the tokens are present
   166  	{
   167  		IDs := tt.IDs
   168  		running := &sync.WaitGroup{}
   169  		numWorkers := 5
   170  		for i := 0; i < numWorkers; i++ {
   171  			running.Add(1)
   172  			startAt := len(vals) * i / numWorkers
   173  			go func() {
   174  				var symBuf [128]byte
   175  				for j := 0; j < totalEntries; j++ {
   176  					idx := (startAt + j) % totalEntries
   177  
   178  					if (j % numWorkers) == 0 {
   179  						symID := table.GetSymbolID(vals[idx], false)
   180  						if symID != IDs[idx] {
   181  							tt.errs <- errors.New("GetSymbolID failed readback check")
   182  						}
   183  					} else {
   184  						stored := table.GetSymbol(IDs[idx], symBuf[:0])
   185  						if !bytes.Equal(stored, vals[idx]) {
   186  							tt.errs <- errors.New("LookupID failed readback check")
   187  						}
   188  					}
   189  				}
   190  				running.Done()
   191  			}()
   192  		}
   193  
   194  		running.Wait()
   195  	}
   196  }