aboutsummaryrefslogtreecommitdiff
path: root/stm_test.go
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2025-01-22 12:31:30 -0300
committerEuAndreh <eu@euandre.org>2025-01-22 12:31:30 -0300
commit59d879ef4e654ce53c2450e000ffa435f06c2f0e (patch)
tree05ae996bf799b1e51f891a5586b3b72fa9bdfe3f /stm_test.go
parentSetup Makefile build skeleton (diff)
downloadstm-59d879ef4e654ce53c2450e000ffa435f06c2f0e.tar.gz
stm-59d879ef4e654ce53c2450e000ffa435f06c2f0e.tar.xz
Unify code into default repo format
Diffstat (limited to 'stm_test.go')
-rw-r--r--stm_test.go274
1 files changed, 0 insertions, 274 deletions
diff --git a/stm_test.go b/stm_test.go
deleted file mode 100644
index a98685b..0000000
--- a/stm_test.go
+++ /dev/null
@@ -1,274 +0,0 @@
-package stm
-
-import (
- "sync"
- "testing"
- "time"
-
- _ "github.com/anacrolix/envpprof"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestDecrement(t *testing.T) {
- x := NewVar(1000)
- for i := 0; i < 500; i++ {
- go Atomically(VoidOperation(func(tx *Tx) {
- cur := x.Get(tx)
- x.Set(tx, cur-1)
- }))
- }
- done := make(chan struct{})
- go func() {
- Atomically(VoidOperation(func(tx *Tx) {
- tx.Assert(x.Get(tx) == 500)
- }))
- close(done)
- }()
- select {
- case <-done:
- case <-time.After(10 * time.Second):
- t.Fatal("decrement did not complete in time")
- }
-}
-
-// read-only transaction aren't exempt from calling tx.inputsChanged
-func TestReadVerify(t *testing.T) {
- read := make(chan struct{})
- x, y := NewVar(1), NewVar(2)
-
- // spawn a transaction that writes to x
- go func() {
- <-read
- AtomicSet(x, 3)
- read <- struct{}{}
- // other tx should retry, so we need to read/send again
- read <- <-read
- }()
-
- // spawn a transaction that reads x, then y. The other tx will modify x in
- // between the reads, causing this tx to retry.
- var x2, y2 int
- Atomically(VoidOperation(func(tx *Tx) {
- x2 = x.Get(tx)
- read <- struct{}{}
- <-read // wait for other tx to complete
- y2 = y.Get(tx)
- }))
- if x2 == 1 && y2 == 2 {
- t.Fatal("read was not verified")
- }
-}
-
-func TestRetry(t *testing.T) {
- x := NewVar(10)
- // spawn 10 transactions, one every 10 milliseconds. This will decrement x
- // to 0 over the course of 100 milliseconds.
- go func() {
- for i := 0; i < 10; i++ {
- time.Sleep(10 * time.Millisecond)
- Atomically(VoidOperation(func(tx *Tx) {
- cur := x.Get(tx)
- x.Set(tx, cur-1)
- }))
- }
- }()
- // Each time we read x before the above loop has finished, we need to
- // retry. This should result in no more than 1 retry per transaction.
- retry := 0
- Atomically(VoidOperation(func(tx *Tx) {
- cur := x.Get(tx)
- if cur != 0 {
- retry++
- tx.Retry()
- }
- }))
- if retry > 10 {
- t.Fatal("should have retried at most 10 times, got", retry)
- }
-}
-
-func TestVerify(t *testing.T) {
- // tx.inputsChanged should check more than pointer equality
- type foo struct {
- i int
- }
- x := NewVar(&foo{3})
- read := make(chan struct{})
-
- // spawn a transaction that modifies x
- go func() {
- Atomically(VoidOperation(func(tx *Tx) {
- <-read
- rx := x.Get(tx)
- rx.i = 7
- x.Set(tx, rx)
- }))
- read <- struct{}{}
- // other tx should retry, so we need to read/send again
- read <- <-read
- }()
-
- // spawn a transaction that reads x, then y. The other tx will modify x in
- // between the reads, causing this tx to retry.
- var i int
- Atomically(VoidOperation(func(tx *Tx) {
- f := x.Get(tx)
- i = f.i
- read <- struct{}{}
- <-read // wait for other tx to complete
- }))
- if i == 3 {
- t.Fatal("inputsChanged did not retry despite modified Var", i)
- }
-}
-
-func TestSelect(t *testing.T) {
- // empty Select should panic
- require.Panics(t, func() { Atomically(Select[struct{}]()) })
-
- // with one arg, Select adds no effect
- x := NewVar(2)
- Atomically(Select(VoidOperation(func(tx *Tx) {
- tx.Assert(x.Get(tx) == 2)
- })))
-
- picked := Atomically(Select(
- // always blocks; should never be selected
- func(tx *Tx) int {
- tx.Retry()
- panic("unreachable")
- },
- // always succeeds; should always be selected
- func(tx *Tx) int {
- return 2
- },
- // always succeeds; should never be selected
- func(tx *Tx) int {
- return 3
- },
- ))
- assert.EqualValues(t, 2, picked)
-}
-
-func TestCompose(t *testing.T) {
- nums := make([]int, 100)
- fns := make([]Operation[struct{}], 100)
- for i := range fns {
- fns[i] = func(x int) Operation[struct{}] {
- return VoidOperation(func(*Tx) { nums[x] = x })
- }(i) // capture loop var
- }
- Atomically(Compose(fns...))
- for i := range nums {
- if nums[i] != i {
- t.Error("Compose failed:", nums[i], i)
- }
- }
-}
-
-func TestPanic(t *testing.T) {
- // normal panics should escape Atomically
- assert.PanicsWithValue(t, "foo", func() {
- Atomically(func(*Tx) any {
- panic("foo")
- })
- })
-}
-
-func TestReadWritten(t *testing.T) {
- // reading a variable written in the same transaction should return the
- // previously written value
- x := NewVar(3)
- Atomically(VoidOperation(func(tx *Tx) {
- x.Set(tx, 5)
- tx.Assert(x.Get(tx) == 5)
- }))
-}
-
-func TestAtomicSetRetry(t *testing.T) {
- // AtomicSet should cause waiting transactions to retry
- x := NewVar(3)
- done := make(chan struct{})
- go func() {
- Atomically(VoidOperation(func(tx *Tx) {
- tx.Assert(x.Get(tx) == 5)
- }))
- done <- struct{}{}
- }()
- time.Sleep(10 * time.Millisecond)
- AtomicSet(x, 5)
- select {
- case <-done:
- case <-time.After(time.Second):
- t.Fatal("AtomicSet did not wake up a waiting transaction")
- }
-}
-
-func testPingPong(t testing.TB, n int, afterHit func(string)) {
- ball := NewBuiltinEqVar(false)
- doneVar := NewVar(false)
- hits := NewVar(0)
- ready := NewVar(true) // The ball is ready for hitting.
- var wg sync.WaitGroup
- bat := func(from, to bool, noise string) {
- defer wg.Done()
- for !Atomically(func(tx *Tx) any {
- if doneVar.Get(tx) {
- return true
- }
- tx.Assert(ready.Get(tx))
- if ball.Get(tx) == from {
- ball.Set(tx, to)
- hits.Set(tx, hits.Get(tx)+1)
- ready.Set(tx, false)
- return false
- }
- return tx.Retry()
- }).(bool) {
- afterHit(noise)
- AtomicSet(ready, true)
- }
- }
- wg.Add(2)
- go bat(false, true, "ping!")
- go bat(true, false, "pong!")
- Atomically(VoidOperation(func(tx *Tx) {
- tx.Assert(hits.Get(tx) >= n)
- doneVar.Set(tx, true)
- }))
- wg.Wait()
-}
-
-func TestPingPong(t *testing.T) {
- testPingPong(t, 42, func(s string) { t.Log(s) })
-}
-
-func TestSleepingBeauty(t *testing.T) {
- require.Panics(t, func() {
- Atomically(func(tx *Tx) any {
- tx.Assert(false)
- return nil
- })
- })
-}
-
-//func TestRetryStack(t *testing.T) {
-// v := NewVar[int](nil)
-// go func() {
-// i := 0
-// for {
-// AtomicSet(v, i)
-// i++
-// }
-// }()
-// Atomically(func(tx *Tx) any {
-// debug.PrintStack()
-// ret := func() {
-// defer Atomically(nil)
-// }
-// v.Get(tx)
-// tx.Assert(false)
-// return ret
-// })
-//}