Add transcript test

This commit is contained in:
Fabio Labella 2023-01-20 01:12:23 +00:00
parent 17c0d8d5d8
commit aeaf55b5e0
2 changed files with 387 additions and 9 deletions

View File

@ -1,19 +1,148 @@
Script to test the basic functionality of CAS and Promise.
# tests for Promise and CAS on Refs
Ref support a CAS operation that can be used as a building block to
change state atomically without locks.
```unison
foo =
use Nat drop
drop 6 5
bar =
use Nat eq
eq 3 4
```
```unison
casTest: '{io2.IO} [Result]
casTest _ =
test _ =
casTest = do
test = do
ref = IO.ref 0
ticket = Ref.readForCas ref
v1 = Ref.cas ref ticket 5
check "CAS is successful is there were no conflicting writes" v1
Ref.write ref 10
v2 = Ref.cas ref ticket 15
check "CAS fails when there was an intervening write" v2
check "CAS fails when there was an intervening write" (not v2)
runTest test
```
```ucm
.> add
.> io.test casTest
```
Promise is a simple one-shot awaitable condition.
```unison
promiseSequentialTest : '{IO} [Result]
promiseSequentialTest = do
test = do
use Nat eq
use Promise read write
p = !Promise.new
write p 0 |> void
v1 = read p
check "Should read a value that's been written" (eq v1 0)
write p 1 |> void
v2 = read p
check "Promise can only be written to once" (eq v2 0)
runTest test
promiseConcurrentTest : '{IO} [Result]
promiseConcurrentTest = do
use Nat eq
test = do
p = !Promise.new
_ = forkComp '(Promise.write p 5)
v = Promise.read p
check "Reads awaits for completion of the Promise" (eq v 5)
runTest test
```
```ucm
.> add
.> io.test promiseSequentialTest
.> io.test promiseConcurrentTest
```
CAS can be used to write an atomic update function.
```unison
atomicUpdate : Ref {IO} a -> (a -> a) ->{IO} ()
atomicUpdate ref f =
ticket = Ref.readForCas ref
value = f (Ticket.read ticket)
if Ref.cas ref ticket value then () else atomicUpdate ref f
```
```ucm
.> add
```
Promise can be used to write an operation that spawns N concurrent
tasks and collects their results
```unison
spawnN : Nat -> '{IO} a ->{IO} [a]
spawnN n fa =
use Nat eq drop
go i acc =
if eq i 0
then acc
else
value = !Promise.new
_ = forkComp do Promise.write value !fa
go (drop i 1) (acc :+ value)
map Promise.read (go n [])
```
```ucm
.> add
```
We can use these primitives to write a more interesting example, where
multiple threads repeatedly update an atomic counter, we check that
the value of the counter is correct after all threads are done.
```unison
repeat: Nat -> '{e} a ->{e} [a]
repeat n fa =
go i acc =
if (Nat.eq i n)
then acc
else go (i + 1) (acc :+ !fa)
go 0 []
fullTest : '{IO} [Result]
fullTest = do
use Nat * + eq drop
numThreads = 100
iterations = 100
expected = numThreads * iterations
test = do
state = IO.ref 0
thread n =
if eq n 0
then ()
else
atomicUpdate state (v -> v + 1)
thread (drop n 1)
void (spawnN numThreads '(thread iterations))
result = Ref.read state
check "The state of the counter is consistent "(eq result expected)
runTest test
```
```ucm
.> add
.> io.test fullTest
```

View File

@ -1,20 +1,42 @@
Script to test the basic functionality of CAS and Promise.
# tests for Promise and CAS on Refs
Ref support a CAS operation that can be used as a building block to
change state atomically without locks.
```unison
foo =
use Nat drop
drop 6 5
bar =
use Nat eq
eq 3 4
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
bar : Boolean
foo : Nat
```
```unison
casTest: '{io2.IO} [Result]
casTest _ =
test _ =
casTest = do
test = do
ref = IO.ref 0
ticket = Ref.readForCas ref
v1 = Ref.cas ref ticket 5
check "CAS is successful is there were no conflicting writes" v1
Ref.write ref 10
v2 = Ref.cas ref ticket 15
check "CAS fails when there was an intervening write" v2
check "CAS fails when there was an intervening write" (not v2)
runTest test
```
@ -29,3 +51,230 @@ casTest _ =
casTest : '{IO} [Result]
```
```ucm
.> add
⍟ I've added these definitions:
casTest : '{IO} [Result]
.> io.test casTest
New test results:
◉ casTest CAS is successful is there were no conflicting writes
◉ casTest CAS fails when there was an intervening write
✅ 2 test(s) passing
Tip: Use view casTest to view the source of a test.
```
Promise is a simple one-shot awaitable condition.
```unison
promiseSequentialTest : '{IO} [Result]
promiseSequentialTest = do
test = do
use Nat eq
use Promise read write
p = !Promise.new
write p 0 |> void
v1 = read p
check "Should read a value that's been written" (eq v1 0)
write p 1 |> void
v2 = read p
check "Promise can only be written to once" (eq v2 0)
runTest test
promiseConcurrentTest : '{IO} [Result]
promiseConcurrentTest = do
use Nat eq
test = do
p = !Promise.new
_ = forkComp '(Promise.write p 5)
v = Promise.read p
check "Reads awaits for completion of the Promise" (eq v 5)
runTest test
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
promiseConcurrentTest : '{IO} [Result]
promiseSequentialTest : '{IO} [Result]
```
```ucm
.> add
⍟ I've added these definitions:
promiseConcurrentTest : '{IO} [Result]
promiseSequentialTest : '{IO} [Result]
.> io.test promiseSequentialTest
New test results:
◉ promiseSequentialTest Should read a value that's been written
◉ promiseSequentialTest Promise can only be written to once
✅ 2 test(s) passing
Tip: Use view promiseSequentialTest to view the source of a
test.
.> io.test promiseConcurrentTest
New test results:
◉ promiseConcurrentTest Reads awaits for completion of the Promise
✅ 1 test(s) passing
Tip: Use view promiseConcurrentTest to view the source of a
test.
```
CAS can be used to write an atomic update function.
```unison
atomicUpdate : Ref {IO} a -> (a -> a) ->{IO} ()
atomicUpdate ref f =
ticket = Ref.readForCas ref
value = f (Ticket.read ticket)
if Ref.cas ref ticket value then () else atomicUpdate ref f
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
atomicUpdate : Ref {IO} a -> (a -> a) ->{IO} ()
```
```ucm
.> add
⍟ I've added these definitions:
atomicUpdate : Ref {IO} a -> (a -> a) ->{IO} ()
```
Promise can be used to write an operation that spawns N concurrent
tasks and collects their results
```unison
spawnN : Nat -> '{IO} a ->{IO} [a]
spawnN n fa =
use Nat eq drop
go i acc =
if eq i 0
then acc
else
value = !Promise.new
_ = forkComp do Promise.write value !fa
go (drop i 1) (acc :+ value)
map Promise.read (go n [])
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
spawnN : Nat -> '{IO} a ->{IO} [a]
```
```ucm
.> add
⍟ I've added these definitions:
spawnN : Nat -> '{IO} a ->{IO} [a]
```
We can use these primitives to write a more interesting example, where
multiple threads repeatedly update an atomic counter, we check that
the value of the counter is correct after all threads are done.
```unison
repeat: Nat -> '{e} a ->{e} [a]
repeat n fa =
go i acc =
if (Nat.eq i n)
then acc
else go (i + 1) (acc :+ !fa)
go 0 []
fullTest : '{IO} [Result]
fullTest = do
use Nat * + eq drop
numThreads = 100
iterations = 100
expected = numThreads * iterations
test = do
state = IO.ref 0
thread n =
if eq n 0
then ()
else
atomicUpdate state (v -> v + 1)
thread (drop n 1)
void (spawnN numThreads '(thread iterations))
result = Ref.read state
check "The state of the counter is consistent "(eq result expected)
runTest test
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
fullTest : '{IO} [Result]
repeat : Nat -> '{e} a ->{e} [a]
```
```ucm
.> add
⍟ I've added these definitions:
fullTest : '{IO} [Result]
repeat : Nat -> '{e} a ->{e} [a]
.> io.test fullTest
New test results:
◉ fullTest The state of the counter is consistent
✅ 1 test(s) passing
Tip: Use view fullTest to view the source of a test.
```