7
7
"fmt"
8
8
"math"
9
9
"os"
10
+ "runtime"
11
+ "sync"
10
12
"testing"
11
13
12
14
"github.com/stretchr/testify/assert"
@@ -30,27 +32,6 @@ const (
30
32
HACKATOM_TEST_CONTRACT = "./testdata/hackatom.wasm"
31
33
)
32
34
33
- func withVM (t * testing.T ) * VM {
34
- t .Helper ()
35
- tmpdir := t .TempDir ()
36
- vm , err := NewVM (tmpdir , TESTING_CAPABILITIES , TESTING_MEMORY_LIMIT , TESTING_PRINT_DEBUG , TESTING_CACHE_SIZE )
37
- require .NoError (t , err )
38
-
39
- t .Cleanup (func () {
40
- vm .Cleanup ()
41
- })
42
- return vm
43
- }
44
-
45
- func createTestContract (t * testing.T , vm * VM , path string ) Checksum {
46
- t .Helper ()
47
- wasm , err := os .ReadFile (path )
48
- require .NoError (t , err )
49
- checksum , _ , err := vm .StoreCode (wasm , TESTING_GAS_LIMIT )
50
- require .NoError (t , err )
51
- return checksum
52
- }
53
-
54
35
func TestStoreCode (t * testing.T ) {
55
36
vm := withVM (t )
56
37
@@ -444,3 +425,248 @@ func TestLongPayloadDeserialization(t *testing.T) {
444
425
require .Error (t , err )
445
426
require .Contains (t , err .Error (), "payload" )
446
427
}
428
+
429
+ // getMemoryStats returns current heap allocation and counters
430
+ func getMemoryStats () (heapAlloc , mallocs , frees uint64 ) {
431
+ runtime .GC ()
432
+ var m runtime.MemStats
433
+ runtime .ReadMemStats (& m )
434
+ return m .HeapAlloc , m .Mallocs , m .Frees
435
+ }
436
+
437
+ func withVM (t * testing.T ) * VM {
438
+ t .Helper ()
439
+ tmpdir , err := os .MkdirTemp ("" , "wasmvm-testing" )
440
+ require .NoError (t , err )
441
+ vm , err := NewVM (tmpdir , TESTING_CAPABILITIES , TESTING_MEMORY_LIMIT , TESTING_PRINT_DEBUG , TESTING_CACHE_SIZE )
442
+ require .NoError (t , err )
443
+
444
+ t .Cleanup (func () {
445
+ vm .Cleanup ()
446
+ os .RemoveAll (tmpdir )
447
+ })
448
+ return vm
449
+ }
450
+
451
+ func createTestContract (t * testing.T , vm * VM , path string ) Checksum {
452
+ t .Helper ()
453
+ wasm , err := os .ReadFile (path )
454
+ require .NoError (t , err )
455
+ checksum , _ , err := vm .StoreCode (wasm , TESTING_GAS_LIMIT )
456
+ require .NoError (t , err )
457
+ return checksum
458
+ }
459
+
460
+ // Existing tests remain unchanged until we add new ones...
461
+
462
+ // TestStoreCodeStress tests memory stability under repeated contract storage
463
+ func TestStoreCodeStress (t * testing.T ) {
464
+ if testing .Short () {
465
+ t .Skip ("Skipping stress test in short mode" )
466
+ }
467
+
468
+ vm := withVM (t )
469
+ wasm , err := os .ReadFile (HACKATOM_TEST_CONTRACT )
470
+ require .NoError (t , err )
471
+
472
+ baseAlloc , baseMallocs , baseFrees := getMemoryStats ()
473
+ t .Logf ("Baseline: Heap=%d bytes, Mallocs=%d, Frees=%d" , baseAlloc , baseMallocs , baseFrees )
474
+
475
+ const iterations = 5000
476
+ checksums := make ([]Checksum , 0 , iterations )
477
+
478
+ for i := 0 ; i < iterations ; i ++ {
479
+ checksum , _ , err := vm .StoreCode (wasm , TESTING_GAS_LIMIT )
480
+ require .NoError (t , err )
481
+ checksums = append (checksums , checksum )
482
+
483
+ if i % 100 == 0 {
484
+ alloc , mallocs , frees := getMemoryStats ()
485
+ t .Logf ("Iter %d: Heap=%d bytes (+%d), Net allocs=%d" ,
486
+ i , alloc , alloc - baseAlloc , (mallocs - frees )- (baseMallocs - baseFrees ))
487
+ require .Less (t , alloc , baseAlloc * 2 , "Memory doubled at iteration %d" , i )
488
+ }
489
+ }
490
+
491
+ // Cleanup some contracts to test removal
492
+ for i , checksum := range checksums {
493
+ if i % 2 == 0 { // Remove half to test memory reclamation
494
+ err := vm .RemoveCode (checksum )
495
+ require .NoError (t , err )
496
+ }
497
+ }
498
+
499
+ finalAlloc , finalMallocs , finalFrees := getMemoryStats ()
500
+ t .Logf ("Final: Heap=%d bytes (+%d), Net allocs=%d" ,
501
+ finalAlloc , finalAlloc - baseAlloc , (finalMallocs - finalFrees )- (baseMallocs - baseFrees ))
502
+ require .Less (t , finalAlloc , baseAlloc + 20 * 1024 * 1024 , "Significant memory leak detected" )
503
+ }
504
+
505
+ // TestConcurrentContractOperations tests memory under concurrent operations
506
+ func TestConcurrentContractOperations (t * testing.T ) {
507
+ if testing .Short () {
508
+ t .Skip ("Skipping concurrent test in short mode" )
509
+ }
510
+
511
+ vm := withVM (t )
512
+ wasm , err := os .ReadFile (HACKATOM_TEST_CONTRACT )
513
+ require .NoError (t , err )
514
+ checksum , _ , err := vm .StoreCode (wasm , TESTING_GAS_LIMIT )
515
+ require .NoError (t , err )
516
+
517
+ const goroutines = 20
518
+ const operations = 1000
519
+ var wg sync.WaitGroup
520
+
521
+ baseAlloc , _ , _ := getMemoryStats ()
522
+ deserCost := types.UFraction {Numerator : 1 , Denominator : 1 }
523
+ env := api .MockEnv ()
524
+ goapi := api .NewMockAPI ()
525
+ balance := types.Array [types.Coin ]{types .NewCoin (250 , "ATOM" )}
526
+ querier := api .DefaultQuerier (api .MOCK_CONTRACT_ADDR , balance )
527
+
528
+ for i := 0 ; i < goroutines ; i ++ {
529
+ wg .Add (1 )
530
+ go func (gid int ) {
531
+ defer wg .Done ()
532
+ gasMeter := api .NewMockGasMeter (TESTING_GAS_LIMIT )
533
+ store := api .NewLookup (gasMeter )
534
+ info := api .MockInfo (fmt .Sprintf ("creator%d" , gid ), nil )
535
+
536
+ for j := 0 ; j < operations ; j ++ {
537
+ msg := []byte (fmt .Sprintf (`{"verifier": "test%d", "beneficiary": "test%d"}` , gid , j ))
538
+ _ , _ , err := vm .Instantiate (checksum , env , info , msg , store , * goapi , querier , gasMeter , TESTING_GAS_LIMIT , deserCost )
539
+ assert .NoError (t , err )
540
+
541
+ // Occasionally execute to mix operations
542
+ if j % 10 == 0 {
543
+ // Recreate gas meter instead of resetting
544
+ gasMeter = api .NewMockGasMeter (TESTING_GAS_LIMIT )
545
+ store = api .NewLookup (gasMeter ) // New store with fresh gas meter
546
+ _ , _ , err = vm .Execute (checksum , env , info , []byte (`{"release":{}}` ), store , * goapi , querier , gasMeter , TESTING_GAS_LIMIT , deserCost )
547
+ assert .NoError (t , err )
548
+ }
549
+ }
550
+ }(i )
551
+ }
552
+
553
+ wg .Wait ()
554
+ finalAlloc , finalMallocs , finalFrees := getMemoryStats ()
555
+ t .Logf ("Concurrent test: Initial=%d bytes, Final=%d bytes, Net allocs=%d" ,
556
+ baseAlloc , finalAlloc , finalMallocs - finalFrees )
557
+ require .Less (t , finalAlloc , baseAlloc + 30 * 1024 * 1024 , "Concurrent operations leaked memory" )
558
+ }
559
+
560
+ // TestMemoryLeakWithPinning tests memory behavior with pinning/unpinning
561
+ func TestMemoryLeakWithPinning (t * testing.T ) {
562
+ if testing .Short () {
563
+ t .Skip ("Skipping pinning leak test in short mode" )
564
+ }
565
+
566
+ vm := withVM (t )
567
+ wasm , err := os .ReadFile (HACKATOM_TEST_CONTRACT )
568
+ require .NoError (t , err )
569
+ checksum , _ , err := vm .StoreCode (wasm , TESTING_GAS_LIMIT )
570
+ require .NoError (t , err )
571
+
572
+ baseAlloc , baseMallocs , baseFrees := getMemoryStats ()
573
+ const iterations = 1000
574
+
575
+ deserCost := types.UFraction {Numerator : 1 , Denominator : 1 }
576
+ gasMeter := api .NewMockGasMeter (TESTING_GAS_LIMIT )
577
+ store := api .NewLookup (gasMeter )
578
+ goapi := api .NewMockAPI ()
579
+ querier := api .DefaultQuerier (api .MOCK_CONTRACT_ADDR , types.Array [types.Coin ]{types .NewCoin (250 , "ATOM" )})
580
+ env := api .MockEnv ()
581
+ info := api .MockInfo ("creator" , nil )
582
+
583
+ for i := 0 ; i < iterations ; i ++ {
584
+ // Pin and unpin repeatedly
585
+ err = vm .Pin (checksum )
586
+ require .NoError (t , err )
587
+
588
+ // Perform an operation while pinned
589
+ msg := []byte (fmt .Sprintf (`{"verifier": "test%d", "beneficiary": "test"}` , i ))
590
+ _ , _ , err := vm .Instantiate (checksum , env , info , msg , store , * goapi , querier , gasMeter , TESTING_GAS_LIMIT , deserCost )
591
+ require .NoError (t , err )
592
+
593
+ err = vm .Unpin (checksum )
594
+ require .NoError (t , err )
595
+
596
+ if i % 100 == 0 {
597
+ alloc , mallocs , frees := getMemoryStats ()
598
+ t .Logf ("Iter %d: Heap=%d bytes (+%d), Net allocs=%d" ,
599
+ i , alloc , alloc - baseAlloc , (mallocs - frees )- (baseMallocs - baseFrees ))
600
+
601
+ metrics , err := vm .GetMetrics ()
602
+ require .NoError (t , err )
603
+ t .Logf ("Metrics: Pinned=%d, Memory=%d, SizePinned=%d, SizeMemory=%d" ,
604
+ metrics .ElementsPinnedMemoryCache , metrics .ElementsMemoryCache ,
605
+ metrics .SizePinnedMemoryCache , metrics .SizeMemoryCache )
606
+ }
607
+ }
608
+
609
+ finalAlloc , finalMallocs , finalFrees := getMemoryStats ()
610
+ t .Logf ("Final: Heap=%d bytes (+%d), Net allocs=%d" ,
611
+ finalAlloc , finalAlloc - baseAlloc , (finalMallocs - finalFrees )- (baseMallocs - baseFrees ))
612
+ require .Less (t , finalAlloc , baseAlloc + 15 * 1024 * 1024 , "Pinning operations leaked memory" )
613
+ }
614
+
615
+ // TestLongRunningOperations tests memory stability over extended mixed operations
616
+ func TestLongRunningOperations (t * testing.T ) {
617
+ if testing .Short () {
618
+ t .Skip ("Skipping long-running test in short mode" )
619
+ }
620
+
621
+ vm := withVM (t )
622
+ wasm , err := os .ReadFile (HACKATOM_TEST_CONTRACT )
623
+ require .NoError (t , err )
624
+ checksum , _ , err := vm .StoreCode (wasm , TESTING_GAS_LIMIT )
625
+ require .NoError (t , err )
626
+
627
+ baseAlloc , baseMallocs , baseFrees := getMemoryStats ()
628
+ const iterations = 10000
629
+
630
+ deserCost := types.UFraction {Numerator : 1 , Denominator : 1 }
631
+ gasMeter := api .NewMockGasMeter (TESTING_GAS_LIMIT )
632
+ store := api .NewLookup (gasMeter )
633
+ goapi := api .NewMockAPI ()
634
+ querier := api .DefaultQuerier (api .MOCK_CONTRACT_ADDR , types.Array [types.Coin ]{types .NewCoin (250 , "ATOM" )})
635
+ env := api .MockEnv ()
636
+ info := api .MockInfo ("creator" , nil )
637
+
638
+ for i := 0 ; i < iterations ; i ++ {
639
+ switch i % 4 {
640
+ case 0 : // Instantiate
641
+ msg := []byte (fmt .Sprintf (`{"verifier": "test%d", "beneficiary": "test"}` , i ))
642
+ _ , _ , err := vm .Instantiate (checksum , env , info , msg , store , * goapi , querier , gasMeter , TESTING_GAS_LIMIT , deserCost )
643
+ require .NoError (t , err )
644
+ case 1 : // Execute
645
+ // Recreate gas meter instead of resetting
646
+ gasMeter = api .NewMockGasMeter (TESTING_GAS_LIMIT )
647
+ store = api .NewLookup (gasMeter ) // New store with fresh gas meter
648
+ _ , _ , err := vm .Execute (checksum , env , info , []byte (`{"release":{}}` ), store , * goapi , querier , gasMeter , TESTING_GAS_LIMIT , deserCost )
649
+ require .NoError (t , err )
650
+ case 2 : // Pin/Unpin
651
+ err := vm .Pin (checksum )
652
+ require .NoError (t , err )
653
+ err = vm .Unpin (checksum )
654
+ require .NoError (t , err )
655
+ case 3 : // GetCode
656
+ _ , err := vm .GetCode (checksum )
657
+ require .NoError (t , err )
658
+ }
659
+
660
+ if i % 1000 == 0 {
661
+ alloc , mallocs , frees := getMemoryStats ()
662
+ t .Logf ("Iter %d: Heap=%d bytes (+%d), Net allocs=%d" ,
663
+ i , alloc , alloc - baseAlloc , (mallocs - frees )- (baseMallocs - baseFrees ))
664
+ require .Less (t , alloc , baseAlloc * 2 , "Memory growth too high at iteration %d" , i )
665
+ }
666
+ }
667
+
668
+ finalAlloc , finalMallocs , finalFrees := getMemoryStats ()
669
+ t .Logf ("Final: Heap=%d bytes (+%d), Net allocs=%d" ,
670
+ finalAlloc , finalAlloc - baseAlloc , (finalMallocs - finalFrees )- (baseMallocs - baseFrees ))
671
+ require .Less (t , finalAlloc , baseAlloc + 25 * 1024 * 1024 , "Long-running operations leaked memory" )
672
+ }
0 commit comments