77 "fmt"
88 "math"
99 "os"
10+ "runtime"
11+ "sync"
1012 "testing"
1113
1214 "github.com/stretchr/testify/assert"
@@ -30,27 +32,6 @@ const (
3032 HACKATOM_TEST_CONTRACT = "./testdata/hackatom.wasm"
3133)
3234
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-
5435func TestStoreCode (t * testing.T ) {
5536 vm := withVM (t )
5637
@@ -444,3 +425,248 @@ func TestLongPayloadDeserialization(t *testing.T) {
444425 require .Error (t , err )
445426 require .Contains (t , err .Error (), "payload" )
446427}
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