Files
aego/script/vm/keyframe_snapshot_test.go
sanspie a46347e2c6
ci-platforms / darwin arm64 smoke (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 12m19s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
github-native / gitea-pending (push) Skipped
github-native / darwin amd64 native (push) Skipped
github-native / linux amd64 native (push) Skipped
github-native / windows amd64 native (push) Skipped
github-native / darwin arm64 native (push) Skipped
github-native / gitea-result (push) Skipped
ci / checks (push) Failing after 23m57s
ci / release (push) Skipped
GitHub / windows amd64 native GitHub-hosted native CI: failure
GitHub / darwin arm64 native GitHub-hosted native CI: failure
GitHub / darwin amd64 native GitHub-hosted native CI: failure
GitHub / linux amd64 native GitHub-hosted native CI: success
editor: add art studio and stage 12.5 workflows
2026-11-08 18:47:57 +03:00

152 lines
4.1 KiB
Go

package vm
import (
"math/rand"
"testing"
"aego/core/rewind"
)
type snapshotRoots []Value
func (r snapshotRoots) MarkRoots(m *Marker) {
for _, v := range r {
m.Mark(v)
}
}
func TestKeyframeSliceCounterAcrossAllocationCollectionAndRestore(t *testing.T) {
for _, seed := range []int64{1, 2, 2026} {
rng := rand.New(rand.NewSource(seed))
m := New(Options{})
var roots snapshotRoots
var saved State
for step := 0; step < 500; step++ {
switch rng.Intn(5) {
case 0:
roots = append(roots, m.NewString("snapshot"))
case 1:
v := m.heap.newSlice(1, 0, rng.Intn(5))
o := m.heap.get(v.A)
if cap(o.elems) > 0 {
o.elems = append(o.elems, Int(int64(step)))
}
roots = append(roots, v)
case 2:
if len(roots) > 0 {
roots = append(roots[:0], roots[len(roots)/2:]...)
}
if !m.Collect(roots) {
t.Fatal("collection refused")
}
default:
m.Capture(&saved)
want := snapshotHash(m)
m.heap.newSlice(1, 0, 2)
m.NewString("after capture")
if !m.Collect(nil) {
t.Fatal("collection refused")
}
m.Restore(&saved)
if snapshotHash(m) != want {
t.Fatalf("seed %d step %d: restore changed heap", seed, step)
}
}
live := 0
for i := range m.heap.objs {
o := &m.heap.objs[i]
if o.live && !o.str {
live++
}
if (!o.live || o.str) && o.elems != nil {
t.Fatalf("seed %d step %d: unexpected slice in a string/dead object", seed, step)
}
}
if m.heap.sliceCount != live {
t.Fatalf("seed %d step %d: count %d, actual %d", seed, step, m.heap.sliceCount, live)
}
}
}
}
func snapshotHash(m *Machine) [32]byte {
h := rewind.NewHasher()
m.Hash(h)
return h.Sum()
}
func TestKeyframeAcrossStringAndSliceHeaps(t *testing.T) {
m := New(Options{})
word := m.NewString("kept")
for i := 0; i < 40; i++ {
m.NewString("temporary")
}
if m.heap.sliceCount != 0 {
t.Fatal("strings counted as slices")
}
var onlyStrings State
m.Capture(&onlyStrings)
if onlyStrings.sliceCount != 0 || len(onlyStrings.lens) != 0 {
t.Fatal("string-only snapshot did not use the compact path")
}
stringsHash := snapshotHash(m)
values := m.heap.newSlice(1, 0, 4)
obj := m.heap.get(values.A)
obj.elems = append(obj.elems, Int(3), Int(7))
empty := m.heap.newSlice(1, 0, 0)
unused := m.heap.newSlice(1, 0, 3)
if m.heap.sliceCount != 3 {
t.Fatalf("live slices: %d", m.heap.sliceCount)
}
var withSlices State
m.Capture(&withSlices)
slicesHash := snapshotHash(m)
obj.elems[0] = Int(99)
if !m.Collect(snapshotRoots{word, values, empty}) {
t.Fatal("collection refused")
}
if m.heap.sliceCount != 2 || m.heap.get(unused.A) != nil {
t.Fatal("GC failed to update live slice count")
}
m.Restore(&withSlices)
if got := snapshotHash(m); got != slicesHash || m.heap.sliceCount != 3 {
t.Fatal("slice restore changed heap state")
}
obj = m.heap.get(values.A)
if obj == nil || len(obj.elems) != 2 || cap(obj.elems) != 4 || obj.elems[0] != Int(3) || obj.elems[1] != Int(7) {
t.Fatalf("snapshot aliases live slice: %+v", obj)
}
if o := m.heap.get(empty.A); o == nil || len(o.elems) != 0 || cap(o.elems) != 0 {
t.Fatal("empty slice was not restored")
}
// Switch between independently held snapshots repeatedly; reusing a State
// for Capture must also clear its previous slice bookkeeping.
for range 3 {
m.Restore(&onlyStrings)
if m.heap.sliceCount != 0 || snapshotHash(m) != stringsHash || m.heap.get(values.A) != nil {
t.Fatal("string-only restore changed heap state")
}
var reused State
m.Capture(&reused)
m.Restore(&withSlices)
if m.heap.sliceCount != 3 || snapshotHash(m) != slicesHash {
t.Fatal("repeated slice restore changed heap state")
}
m.Capture(&reused)
m.Restore(&onlyStrings)
m.Capture(&reused)
if reused.sliceCount != 0 || len(reused.lens) != 0 || len(reused.elems) != 0 {
t.Fatal("reused snapshot retained slice metadata")
}
}
m.Restore(&withSlices)
if !m.Collect(snapshotRoots{word}) || m.heap.sliceCount != 0 {
t.Fatal("collecting all slices did not restore the fast path")
}
var afterGC State
m.Capture(&afterGC)
if afterGC.sliceCount != 0 || len(afterGC.lens) != 0 {
t.Fatal("GC left stale slice bookkeeping")
}
}