Skip to content

Commit 179d142

Browse files
committed
Add lib_libwasmvm_test.go
1 parent 0243050 commit 179d142

File tree

1 file changed

+247
-21
lines changed

1 file changed

+247
-21
lines changed

lib_libwasmvm_test.go

Lines changed: 247 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
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-
5435
func 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

Comments
 (0)