mirror of
https://github.com/unisonweb/unison.git
synced 2024-10-26 11:07:48 +03:00
Add transcript test
This commit is contained in:
parent
17c0d8d5d8
commit
aeaf55b5e0
@ -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
|
||||
```
|
||||
|
@ -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.
|
||||
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user