github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/it/race_cud_test.go (about)

     1  /*
     2   * Copyright (c) 2021-present unTill Pro, Ltd.
     3   */
     4  
     5  // The goal of package is  to ensure there are no Race Condition/Race Data errors in Voedger read/write operations
     6  // All tests should be run with -race
     7  package sys_it
     8  
     9  import (
    10  	"fmt"
    11  	"strconv"
    12  	"sync"
    13  	"testing"
    14  
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/voedger/voedger/pkg/istructs"
    18  	coreutils "github.com/voedger/voedger/pkg/utils"
    19  	wsdescutil "github.com/voedger/voedger/pkg/utils/testwsdesc"
    20  	it "github.com/voedger/voedger/pkg/vit"
    21  	sys_test_template "github.com/voedger/voedger/pkg/vit/testdata"
    22  )
    23  
    24  const (
    25  	readCnt  = 10
    26  	writeCnt = 20
    27  )
    28  
    29  var cfg = it.NewSharedVITConfig(
    30  	it.WithApp(istructs.AppQName_test1_app1, it.ProvideApp1,
    31  		it.WithWorkspaceTemplate(it.QNameApp1_TestWSKind, "test_template", sys_test_template.TestTemplateFS),
    32  		it.WithUserLogin("login", "pwd"),
    33  		it.WithChildWorkspace(it.QNameApp1_TestWSKind, "test_ws", "test_template", "", "login", map[string]interface{}{"IntFld": 42}),
    34  	),
    35  	it.WithPostInit(func(vit *it.VIT) {
    36  		as, err := vit.AppStructs(istructs.AppQName_test1_app1)
    37  		require.NoError(vit.T, err)
    38  		plogOffsets := map[istructs.PartitionID]istructs.Offset{}
    39  		for wsNum := 0; wsNum < writeCnt; wsNum++ {
    40  			wsid := istructs.WSID(wsNum + int(istructs.MaxPseudoBaseWSID))
    41  			partNum, err := vit.IAppPartitions.AppWorkspacePartitionID(istructs.AppQName_test1_app1, wsid)
    42  			require.NoError(vit.T, err)
    43  			pLogOffset := plogOffsets[partNum]
    44  			err = wsdescutil.CreateCDocWorkspaceDescriptorStub(as, partNum, istructs.WSID(wsNum+int(istructs.MaxPseudoBaseWSID)), it.QNameApp1_TestWSKind, pLogOffset, 1)
    45  			require.NoError(vit.T, err)
    46  			plogOffsets[partNum]++
    47  		}
    48  	}),
    49  )
    50  
    51  // One WSID
    52  //*****************************************
    53  
    54  // Read from many goroutines.
    55  // Read result does not matter.
    56  
    57  func Test_Race_CUDSimpleRead(t *testing.T) {
    58  	if coreutils.IsCassandraStorage() {
    59  		return
    60  	}
    61  	vit := it.NewVIT(t, &it.SharedConfig_App1)
    62  	defer vit.TearDown()
    63  
    64  	cnt := readCnt
    65  	wg := sync.WaitGroup{}
    66  	wg.Add(cnt)
    67  	ws := vit.WS(istructs.AppQName_test1_app1, "test_ws")
    68  	for i := 0; i < cnt; i++ {
    69  		go func() {
    70  			defer wg.Done()
    71  			writeArt(ws, vit)
    72  			readArt(vit, ws)
    73  		}()
    74  	}
    75  	wg.Wait()
    76  }
    77  
    78  // Write from many goroutines
    79  func Test_Race_CUDSimpleWrite(t *testing.T) {
    80  	if coreutils.IsCassandraStorage() {
    81  		return
    82  	}
    83  	vit := it.NewVIT(t, &it.SharedConfig_App1)
    84  	defer vit.TearDown()
    85  
    86  	cnt := writeCnt
    87  	wg := sync.WaitGroup{}
    88  	wg.Add(cnt)
    89  	ws := vit.WS(istructs.AppQName_test1_app1, "test_ws")
    90  	for i := 0; i < cnt; i++ {
    91  		go func() {
    92  			defer wg.Done()
    93  			writeArt(ws, vit)
    94  		}()
    95  	}
    96  	wg.Wait()
    97  }
    98  func Test_Race_CUDOneWriteManyRead(t *testing.T) {
    99  	if coreutils.IsCassandraStorage() {
   100  		return
   101  	}
   102  	vit := it.NewVIT(t, &it.SharedConfig_App1)
   103  	defer vit.TearDown()
   104  
   105  	wg := sync.WaitGroup{}
   106  	wg.Add(1)
   107  	ws := vit.WS(istructs.AppQName_test1_app1, "test_ws")
   108  	go func() {
   109  		defer wg.Done()
   110  		writeArt(ws, vit)
   111  	}()
   112  
   113  	for i := 0; i < readCnt; i++ {
   114  		wg.Add(1)
   115  		go func(_ *testing.T, _ int) {
   116  			defer wg.Done()
   117  			readArt(vit, ws)
   118  		}(t, i)
   119  	}
   120  	wg.Wait()
   121  }
   122  
   123  // Write from many goroutines, one read after all writes are finished
   124  // Read result: only status = OK checked
   125  func Test_Race_CUDManyWriteOneRead(t *testing.T) {
   126  	if coreutils.IsCassandraStorage() {
   127  		return
   128  	}
   129  	vit := it.NewVIT(t, &it.SharedConfig_App1)
   130  	defer vit.TearDown()
   131  
   132  	cnt := writeCnt
   133  	wg := sync.WaitGroup{}
   134  	ws := vit.WS(istructs.AppQName_test1_app1, "test_ws")
   135  	for i := 0; i < cnt; i++ {
   136  		wg.Add(1)
   137  		go func() {
   138  			defer wg.Done()
   139  			writeArt(ws, vit)
   140  		}()
   141  	}
   142  	wg.Wait()
   143  
   144  	wgr := sync.WaitGroup{}
   145  	wgr.Add(1)
   146  	go func(_ *testing.T) {
   147  		defer wgr.Done()
   148  		readArt(vit, ws)
   149  	}(t)
   150  	wgr.Wait()
   151  }
   152  
   153  // Write from many goroutines, and simultaneous read from many goroutines
   154  // Read result: only status = OK checked
   155  func Test_Race_CUDManyWriteManyReadNoResult(t *testing.T) {
   156  	if coreutils.IsCassandraStorage() {
   157  		return
   158  	}
   159  	vit := it.NewVIT(t, &it.SharedConfig_App1)
   160  	defer vit.TearDown()
   161  
   162  	cnt := writeCnt
   163  	wg := sync.WaitGroup{}
   164  	wg.Add(2 * cnt)
   165  	ws := vit.WS(istructs.AppQName_test1_app1, "test_ws")
   166  	for i := 0; i < cnt; i++ {
   167  		go func() {
   168  			defer wg.Done()
   169  			writeArt(ws, vit)
   170  		}()
   171  		go func() {
   172  			defer wg.Done()
   173  			readArt(vit, ws)
   174  		}()
   175  	}
   176  	wg.Wait()
   177  }
   178  
   179  // Write from many goroutines & read from many goroutines after all data has been written
   180  // Read result: Checks all written data are correct
   181  func Test_Race_CUDManyWriteManyReadCheckResult(t *testing.T) {
   182  	if coreutils.IsCassandraStorage() {
   183  		return
   184  	}
   185  	vit := it.NewVIT(t, &it.SharedConfig_App1)
   186  	defer vit.TearDown()
   187  
   188  	cnt := writeCnt
   189  	wgW := sync.WaitGroup{}
   190  	wgW.Add(cnt)
   191  	ws := vit.WS(istructs.AppQName_test1_app1, "test_ws")
   192  	artNumbers := make(chan int, cnt)
   193  	for i := 0; i < cnt; i++ {
   194  		go func() {
   195  			defer wgW.Done()
   196  			artNumbers <- writeArt(ws, vit)
   197  		}()
   198  	}
   199  	wgW.Wait()
   200  	close(artNumbers)
   201  
   202  	wgR := sync.WaitGroup{}
   203  	wgR.Add(cnt)
   204  	for i := 0; i < cnt; i++ {
   205  		go func(at *testing.T) {
   206  			defer wgR.Done()
   207  			readAndCheckArt(at, <-artNumbers, vit, ws)
   208  		}(t)
   209  	}
   210  	wgR.Wait()
   211  }
   212  
   213  // Write from many goroutines
   214  // Read & Update from many goroutines with different pauses after all data has been written
   215  // Read result: only status = OK checked
   216  func Test_Race_CUDManyUpdateManyReadCheckResult(t *testing.T) {
   217  	if testing.Short() {
   218  		t.Skip()
   219  	}
   220  	if coreutils.IsCassandraStorage() {
   221  		return
   222  	}
   223  	vit := it.NewVIT(t, &it.SharedConfig_App1)
   224  	defer vit.TearDown()
   225  
   226  	cntw := writeCnt
   227  	wgW := sync.WaitGroup{}
   228  	wgW.Add(cntw)
   229  	ws := vit.WS(istructs.AppQName_test1_app1, "test_ws")
   230  	artNumbers := make(chan int, cntw)
   231  	for i := 0; i < cntw; i++ {
   232  		go func() {
   233  			defer wgW.Done()
   234  			artNumbers <- writeArt(ws, vit)
   235  		}()
   236  	}
   237  	wgW.Wait()
   238  	close(artNumbers)
   239  
   240  	for k := 0; k < 5; k++ {
   241  		wgUR := sync.WaitGroup{}
   242  		for i := 0; i < cntw; i++ {
   243  			wgUR.Add(1)
   244  			go func(acnt int) {
   245  				defer wgUR.Done()
   246  				updateArtByName(<-artNumbers, acnt, vit, ws)
   247  			}(cntw)
   248  		}
   249  
   250  		cntr := writeCnt
   251  		for i := 0; i < cntr; i++ {
   252  			wgUR.Add(1)
   253  			go func() {
   254  				defer wgUR.Done()
   255  				readArt(vit, ws)
   256  			}()
   257  		}
   258  
   259  		wgUR.Wait()
   260  	}
   261  }
   262  
   263  // Many WSIDs
   264  //*****************************************
   265  
   266  // Read from many goroutines with different WSID
   267  func Test_Race_CUDManyReadCheckResult(t *testing.T) {
   268  	if coreutils.IsCassandraStorage() {
   269  		return
   270  	}
   271  	vit := it.NewVIT(t, &cfg)
   272  	defer vit.TearDown()
   273  
   274  	var cntWS int = readCnt
   275  	sysPrn := vit.GetSystemPrincipal(istructs.AppQName_test1_app1)
   276  
   277  	wg := sync.WaitGroup{}
   278  	for prtIdx := istructs.WSID(1); int(prtIdx) < cntWS; prtIdx++ {
   279  		wg.Add(1)
   280  		go func(wsidNum istructs.WSID) {
   281  			defer wg.Done()
   282  			wsid := wsidNum + istructs.MaxPseudoBaseWSID
   283  			dummyWS := it.DummyWS(it.QNameApp1_TestWSKind, wsid, sysPrn)
   284  			readArt(vit, dummyWS)
   285  		}(prtIdx)
   286  	}
   287  	wg.Wait()
   288  }
   289  
   290  // Write from many goroutines with different WSID
   291  func Test_Race_CUDManyWriteCheckResult(t *testing.T) {
   292  	if coreutils.IsCassandraStorage() {
   293  		return
   294  	}
   295  	vit := it.NewVIT(t, &cfg)
   296  	defer vit.TearDown()
   297  
   298  	var cntWS int = writeCnt
   299  	var prtIdx istructs.WSID
   300  	sysPrn := vit.GetSystemPrincipal(istructs.AppQName_test1_app1)
   301  
   302  	wg := sync.WaitGroup{}
   303  	for prtIdx = 1; int(prtIdx) < cntWS; prtIdx++ {
   304  		wg.Add(1)
   305  		go func(wsidNum istructs.WSID) {
   306  			defer wg.Done()
   307  			wsid := wsidNum + istructs.MaxPseudoBaseWSID
   308  			dummyWS := it.DummyWS(it.QNameApp1_TestWSKind, wsid, sysPrn)
   309  			writeArt(dummyWS, vit)
   310  		}(prtIdx)
   311  	}
   312  	wg.Wait()
   313  }
   314  
   315  // Read & Write from many goroutines with different WSID
   316  func Test_Race_CUDManyWriteReadCheckResult(t *testing.T) {
   317  	if testing.Short() {
   318  		t.Skip()
   319  	}
   320  	if coreutils.IsCassandraStorage() {
   321  		return
   322  	}
   323  	vit := it.NewVIT(t, &cfg)
   324  	defer vit.TearDown()
   325  
   326  	var cntWS int = writeCnt
   327  	var prtIdx istructs.WSID
   328  	sysPrn := vit.GetSystemPrincipal(istructs.AppQName_test1_app1)
   329  
   330  	for k := 1; k < 10; k++ {
   331  		wg := sync.WaitGroup{}
   332  		for prtIdx = 1; int(prtIdx) < cntWS; prtIdx++ {
   333  			wg.Add(1)
   334  			go func(wsidNum istructs.WSID) {
   335  				defer wg.Done()
   336  				wsid := wsidNum + istructs.MaxPseudoBaseWSID
   337  				dummyWS := it.DummyWS(it.QNameApp1_TestWSKind, wsid, sysPrn)
   338  				writeArt(dummyWS, vit)
   339  			}(prtIdx)
   340  		}
   341  		for prtIdx = 1; int(prtIdx) < cntWS; prtIdx++ {
   342  			wg.Add(1)
   343  			go func(wsidNum istructs.WSID) {
   344  				defer wg.Done()
   345  				wsid := wsidNum + istructs.MaxPseudoBaseWSID
   346  				dummyWS := it.DummyWS(it.QNameApp1_TestWSKind, wsid, sysPrn)
   347  				readArt(vit, dummyWS)
   348  			}(prtIdx)
   349  		}
   350  		wg.Wait()
   351  	}
   352  }
   353  
   354  func writeArt(ws *it.AppWorkspace, vit *it.VIT) (artNumber int) {
   355  	artNumber = vit.NextNumber()
   356  	idstr := strconv.Itoa(artNumber)
   357  	artname := "cola" + idstr
   358  	body := `
   359  		{
   360  			"cuds": [
   361  				{
   362  					"fields": {
   363  						"sys.ID": ` + idstr + `,
   364  						"sys.QName": "app1pkg.articles",
   365  						"name": "` + artname + `",
   366  						"article_manual": 1,
   367  						"article_hash": 2,
   368  						"hideonhold": 3,
   369  						"time_active": 4,
   370  						"control_active": 5
   371  					}
   372  				}
   373  			]
   374  		}`
   375  	vit.PostWS(ws, "c.sys.CUD", body)
   376  	return
   377  }
   378  
   379  func readArt(vit *it.VIT, ws *it.AppWorkspace) *coreutils.FuncResponse {
   380  	body := `
   381  	{
   382  		"args":{
   383  			"Schema":"app1pkg.articles"
   384  		},
   385  		"elements":[
   386  			{
   387  				"fields": ["name", "control_active", "sys.ID"]
   388  			}
   389  		],
   390  		"orderBy":[{"field":"name"}]
   391  	}`
   392  	return vit.PostWS(ws, "q.sys.Collection", body)
   393  }
   394  
   395  func updateArtByName(idx, num int, vit *it.VIT, ws *it.AppWorkspace) {
   396  	artname := "cola" + strconv.Itoa(idx)
   397  	resp := readArt(vit, ws)
   398  
   399  	var actualName string
   400  	for i := 0; i < num; i++ {
   401  		actualName = resp.SectionRow(i)[0].(string)
   402  		if artname == actualName {
   403  			id := resp.SectionRow()[2].(float64)
   404  			updateArt(id, vit, ws)
   405  			break
   406  		}
   407  	}
   408  }
   409  
   410  func updateArt(id float64, vit *it.VIT, ws *it.AppWorkspace) {
   411  	body := fmt.Sprintf(`
   412  	{
   413  		"cuds": [
   414  			{
   415  				"sys.ID": %d,
   416  				"fields": {
   417  					"article_manual": 110,
   418  					"article_hash": 210,
   419  					"hideonhold": 310,
   420  					"time_active": 410,
   421  					"control_active": 510
   422  				}
   423  			}
   424  		]
   425  	}`, int64(id))
   426  	vit.PostWS(ws, "c.sys.CUD", body)
   427  }
   428  
   429  func readAndCheckArt(t *testing.T, idx int, vit *it.VIT, ws *it.AppWorkspace) {
   430  	idstr := strconv.Itoa(idx)
   431  	artname := "cola" + idstr
   432  	require := require.New(t)
   433  	var id float64
   434  
   435  	resp := readArt(vit, ws)
   436  
   437  	var actualName string
   438  	var actualControlActive float64
   439  	i := 0
   440  	for artname != actualName {
   441  		actualName = resp.SectionRow(i)[0].(string)
   442  		actualControlActive = resp.SectionRow(i)[1].(float64)
   443  		id = resp.SectionRow(i)[2].(float64)
   444  		require.NotEqual(0, id)
   445  		i++
   446  	}
   447  	require.Equal(artname, actualName)
   448  	require.Equal(float64(5), actualControlActive)
   449  }