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 }