2020-12-02 00:05:29 +03:00
|
|
|
# tests for IO / MVar
|
|
|
|
|
|
|
|
Tests for IO builtins which wired to foreign haskell calls.
|
|
|
|
|
|
|
|
## Setup
|
|
|
|
|
|
|
|
You can skip the section which is just needed to make the transcript self-contained.
|
|
|
|
|
|
|
|
TempDirs/autoCleaned is an ability/hanlder which allows you to easily
|
|
|
|
create a scratch directory which will automatically get cleaned up.
|
|
|
|
|
|
|
|
```unison
|
|
|
|
use .builtin.io2 Failure
|
|
|
|
|
|
|
|
filter: (a -> Boolean) -> [a] -> [a]
|
|
|
|
filter f all =
|
|
|
|
go acc = cases
|
|
|
|
a +: as -> if (f a) then go (cons a acc) as else go acc as
|
|
|
|
[] -> acc
|
|
|
|
go [] all
|
|
|
|
|
|
|
|
ability Exception e where raise : e ->{Exception e} a
|
|
|
|
|
|
|
|
toException : Either e a ->{Exception e} a
|
|
|
|
toException = cases
|
|
|
|
Left e -> raise e
|
|
|
|
Right a -> a
|
|
|
|
|
|
|
|
|
|
|
|
Exception.toEither.handler : Request {Exception e} a -> Either e a
|
|
|
|
Exception.toEither.handler = cases
|
|
|
|
{ a } -> Right a
|
|
|
|
{raise e -> _} -> Left e
|
|
|
|
|
|
|
|
Exception.toEither : '{g, Exception e} a ->{g} Either e a
|
|
|
|
Exception.toEither a = handle !a with Exception.toEither.handler
|
|
|
|
|
|
|
|
isNone = cases
|
|
|
|
Some _ -> false
|
|
|
|
None -> true
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
-- An ability that facilitates creating temoporary directories that can be
|
|
|
|
-- automatically cleaned up
|
2020-12-02 00:05:29 +03:00
|
|
|
ability TempDirs where
|
|
|
|
newTempDir: Text -> Either Failure Text
|
|
|
|
removeDir: Text -> Either Failure ()
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
-- A handler for TempDirs which cleans up temporary directories
|
|
|
|
-- This will be useful for IO tests which need to interact with
|
|
|
|
-- the filesystem
|
2020-12-02 00:05:29 +03:00
|
|
|
autoCleaned.handler: '{io2.IO} (Request {TempDirs} r -> r)
|
|
|
|
autoCleaned.handler _ =
|
|
|
|
remover : [Text] -> {io2.IO} ()
|
|
|
|
remover = cases
|
|
|
|
a +: as -> removeDirectory a
|
|
|
|
remover as
|
|
|
|
[] -> ()
|
|
|
|
|
|
|
|
go : [Text] -> {io2.IO} Request {TempDirs} r -> r
|
|
|
|
go dirs = cases
|
|
|
|
{ a } -> remover dirs
|
|
|
|
a
|
|
|
|
{ TempDirs.newTempDir prefix -> k } ->
|
|
|
|
dir = createTempDirectory prefix
|
|
|
|
match dir with
|
|
|
|
Right dir' -> handle k dir with go (dir' +: dirs)
|
|
|
|
Left _ -> handle k dir with go dirs
|
|
|
|
|
|
|
|
{ TempDirs.removeDir dir -> k } ->
|
|
|
|
handle k (removeDirectory dir) with go (filter (d -> not (d == dir)) dirs)
|
|
|
|
|
|
|
|
go []
|
|
|
|
|
|
|
|
autoCleaned: '{io2.IO, TempDirs} r -> r
|
|
|
|
autoCleaned comp = handle !comp with !autoCleaned.handler
|
|
|
|
|
|
|
|
ability Stream a where
|
|
|
|
emit: a -> ()
|
|
|
|
|
|
|
|
Stream.toList.handler : Request {Stream a} r -> [a]
|
|
|
|
Stream.toList.handler =
|
|
|
|
go : [a] -> Request {Stream a} r -> [a]
|
|
|
|
go acc = cases
|
|
|
|
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
|
|
|
|
{ _ } -> acc
|
|
|
|
|
|
|
|
go []
|
|
|
|
|
|
|
|
Stream.toList : '{Stream a} r -> [a]
|
|
|
|
Stream.toList s = handle !s with toList.handler
|
|
|
|
|
|
|
|
Stream.collect.handler : Request {Stream a} r -> ([a],r)
|
|
|
|
Stream.collect.handler =
|
|
|
|
go : [a] -> Request {Stream a} r -> ([a],r)
|
|
|
|
go acc = cases
|
|
|
|
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
|
|
|
|
{ r } -> (acc, r)
|
|
|
|
|
|
|
|
go []
|
|
|
|
|
|
|
|
Stream.collect : '{e, Stream a} r -> {e} ([a],r)
|
|
|
|
Stream.collect s =
|
|
|
|
handle !s with Stream.collect.handler
|
|
|
|
|
|
|
|
stdout = IO.stdHandle StdErr
|
|
|
|
printText : Text -> {io2.IO} Either Failure ()
|
|
|
|
printText t = putBytes stdout (toUtf8 t)
|
|
|
|
|
|
|
|
expect : Text -> (a -> a -> Boolean) -> a -> a -> {Stream Result} ()
|
|
|
|
expect msg compare expected actual = if compare expected actual then emit (Ok msg) else emit (Fail msg)
|
|
|
|
|
|
|
|
expectU : Text -> a -> a -> {Stream Result} ()
|
|
|
|
expectU msg expected actual = expect msg (==) expected actual
|
|
|
|
|
|
|
|
check: Text -> Boolean -> {Stream Result} ()
|
|
|
|
check msg test = if test then emit (Ok msg) else emit (Fail msg)
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
-- Run tests which might fail, might create temporary directores and Stream out
|
|
|
|
-- results, returns the Results and the result of the test
|
2020-12-02 00:05:29 +03:00
|
|
|
evalTest: '{Stream Result, Exception Failure, io2.IO, TempDirs} a -> ([Result], Either Failure a)
|
|
|
|
evalTest a = handle
|
|
|
|
(handle
|
|
|
|
(handle !a with Exception.toEither.handler)
|
|
|
|
with Stream.collect.handler)
|
|
|
|
with !autoCleaned.handler
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
-- Run tests which might fail, might create temporary directores and Stream out
|
|
|
|
-- results, but ignore the produced value and only return the test Results
|
2020-12-02 00:05:29 +03:00
|
|
|
runTest: '{Stream Result, Exception Failure, io2.IO, TempDirs} a -> [Result]
|
|
|
|
runTest t = match evalTest t with
|
|
|
|
(results, Right _) -> results
|
|
|
|
(results, Left (Failure _ t)) -> results :+ (Fail t)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
## Who watches the watchers?
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
First lets do some basic testing of our test harness to make sure its
|
|
|
|
working.
|
2020-12-02 00:05:29 +03:00
|
|
|
|
|
|
|
```unison
|
|
|
|
testAutoClean : '{io2.IO}[Result]
|
|
|
|
testAutoClean _ =
|
|
|
|
go: '{Stream Result, Exception Failure, io2.IO, TempDirs} Text
|
|
|
|
go _ =
|
|
|
|
dir = toException (newTempDir "autoclean")
|
|
|
|
check "our temporary directory should exist" (toException (isDirectory dir))
|
|
|
|
dir
|
|
|
|
|
|
|
|
match evalTest go with
|
|
|
|
(results, Left (Failure _ t)) -> results :+ (Fail t)
|
|
|
|
(results, Right dir) ->
|
|
|
|
match isDirectory dir with
|
|
|
|
Right b -> if b
|
|
|
|
then results :+ (Fail "our temporary directory should no longer exist")
|
|
|
|
else results :+ (Ok "our temporary directory should no longer exist")
|
|
|
|
Left (Failure _ t) -> results :+ (Fail t)
|
|
|
|
```
|
|
|
|
|
|
|
|
```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`:
|
|
|
|
|
|
|
|
testAutoClean : '{io2.IO} [Result]
|
|
|
|
|
|
|
|
```
|
|
|
|
```ucm
|
|
|
|
.> add
|
|
|
|
|
|
|
|
⍟ I've added these definitions:
|
|
|
|
|
|
|
|
testAutoClean : '{io2.IO} [Result]
|
|
|
|
|
|
|
|
.> io.test testAutoClean
|
|
|
|
|
|
|
|
New test results:
|
|
|
|
|
|
|
|
◉ testAutoClean our temporary directory should exist
|
|
|
|
◉ testAutoClean our temporary directory should no longer exist
|
|
|
|
|
|
|
|
✅ 2 test(s) passing
|
|
|
|
|
|
|
|
Tip: Use view testAutoClean to view the source of a test.
|
|
|
|
|
|
|
|
```
|
|
|
|
## Basic File Functions
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
### Creating/Deleting/Renaming Directories
|
|
|
|
|
|
|
|
Tests: createDirectory,
|
|
|
|
isDirectory,
|
|
|
|
fileExists,
|
|
|
|
renameDirectory,
|
|
|
|
deleteDirectory
|
|
|
|
|
2020-12-02 00:05:29 +03:00
|
|
|
```unison
|
|
|
|
testCreateRename : '{io2.IO} [Result]
|
|
|
|
testCreateRename _ =
|
|
|
|
test = 'let
|
|
|
|
tempDir = toException (newTempDir "fileio")
|
|
|
|
fooDir = tempDir ++ "/foo"
|
|
|
|
barDir = tempDir ++ "/bar"
|
|
|
|
toException let createDirectory fooDir
|
|
|
|
check "create a foo directory" (toException (isDirectory fooDir))
|
2020-12-02 22:03:21 +03:00
|
|
|
check "directory should exist" (toException (fileExists fooDir))
|
2020-12-02 00:05:29 +03:00
|
|
|
toException let renameDirectory fooDir barDir
|
|
|
|
check "foo should no longer exist" (not (toException (fileExists fooDir)))
|
2020-12-02 22:03:21 +03:00
|
|
|
check "directory should no longer exist" (not (toException (fileExists fooDir)))
|
2020-12-02 00:05:29 +03:00
|
|
|
check "bar should now exist" (toException (fileExists barDir))
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
bazDir = barDir ++ "/baz"
|
|
|
|
toException let createDirectory bazDir
|
|
|
|
toException let removeDirectory barDir
|
|
|
|
|
|
|
|
check "removeDirectory works recursively" (not (toException (isDirectory barDir)))
|
|
|
|
check "removeDirectory works recursively" (not (toException (isDirectory bazDir)))
|
|
|
|
|
2020-12-02 00:05:29 +03:00
|
|
|
runTest test
|
2020-12-02 22:03:21 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
```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`:
|
|
|
|
|
|
|
|
testCreateRename : '{io2.IO} [Result]
|
|
|
|
|
|
|
|
```
|
|
|
|
```ucm
|
|
|
|
.> add
|
|
|
|
|
|
|
|
⍟ I've added these definitions:
|
|
|
|
|
|
|
|
testCreateRename : '{io2.IO} [Result]
|
|
|
|
|
|
|
|
.> io.test testCreateRename
|
|
|
|
|
|
|
|
New test results:
|
|
|
|
|
|
|
|
◉ testCreateRename create a foo directory
|
|
|
|
◉ testCreateRename directory should exist
|
|
|
|
◉ testCreateRename foo should no longer exist
|
|
|
|
◉ testCreateRename directory should no longer exist
|
|
|
|
◉ testCreateRename bar should now exist
|
|
|
|
◉ testCreateRename removeDirectory works recursively
|
|
|
|
◉ testCreateRename removeDirectory works recursively
|
|
|
|
|
|
|
|
✅ 7 test(s) passing
|
|
|
|
|
|
|
|
Tip: Use view testCreateRename to view the source of a test.
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
```
|
|
|
|
### Opening / Closing files
|
|
|
|
|
|
|
|
Tests: openFile
|
|
|
|
closeFile
|
|
|
|
isFileOpen
|
|
|
|
|
|
|
|
```unison
|
2020-12-02 00:05:29 +03:00
|
|
|
testOpenClose : '{io2.IO} [Result]
|
|
|
|
testOpenClose _ =
|
|
|
|
test = 'let
|
|
|
|
tempDir = toException (newTempDir "seek")
|
|
|
|
fooFile = tempDir ++ "/foo"
|
|
|
|
handle1 = toException (openFile fooFile FileMode.Write)
|
|
|
|
check "file should be open" (toException (isFileOpen handle1))
|
|
|
|
toException (closeFile handle1)
|
|
|
|
check "file should be closed" (not (toException (isFileOpen handle1)))
|
|
|
|
|
|
|
|
runTest test
|
2020-12-02 22:03:21 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
```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`:
|
|
|
|
|
|
|
|
testOpenClose : '{io2.IO} [Result]
|
|
|
|
|
|
|
|
```
|
|
|
|
```ucm
|
|
|
|
.> add
|
|
|
|
|
|
|
|
⍟ I've added these definitions:
|
|
|
|
|
|
|
|
testOpenClose : '{io2.IO} [Result]
|
|
|
|
|
|
|
|
.> io.test testOpenClose
|
|
|
|
|
|
|
|
New test results:
|
|
|
|
|
|
|
|
◉ testOpenClose file should be open
|
|
|
|
◉ testOpenClose file should be closed
|
|
|
|
|
|
|
|
✅ 2 test(s) passing
|
|
|
|
|
|
|
|
Tip: Use view testOpenClose to view the source of a test.
|
|
|
|
|
|
|
|
```
|
|
|
|
### Seeking in open files
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
Tests: openFile
|
|
|
|
putBytes
|
|
|
|
closeFile
|
|
|
|
isSeekable
|
|
|
|
isFileEOF
|
|
|
|
seekHandle
|
|
|
|
getBytes
|
|
|
|
|
|
|
|
```unison
|
2020-12-02 00:05:29 +03:00
|
|
|
testSeek : '{io2.IO} [Result]
|
|
|
|
testSeek _ =
|
|
|
|
test = 'let
|
|
|
|
tempDir = toException (newTempDir "seek")
|
|
|
|
fooFile = tempDir ++ "/foo"
|
|
|
|
handle1 = toException (openFile fooFile FileMode.Write)
|
|
|
|
putBytes handle1 (toUtf8 "12345678")
|
|
|
|
closeFile handle1
|
|
|
|
|
|
|
|
handle3 = toException (openFile fooFile FileMode.Read)
|
|
|
|
check "readable file should be seekable" (toException (isSeekable handle3))
|
|
|
|
check "shouldn't be the EOF" (not (toException (isFileEOF handle3)))
|
|
|
|
expectU "we should be at position 0" +0 (toException (handlePosition handle3))
|
|
|
|
|
|
|
|
toException (seekHandle handle3 AbsoluteSeek +1)
|
|
|
|
expectU "we should be at position 1" +1 (toException (handlePosition handle3))
|
|
|
|
bytes3a = toException (getBytes handle3 1000)
|
2020-12-03 01:11:54 +03:00
|
|
|
text3a = toException (Text.fromUtf8 bytes3a)
|
2020-12-02 00:05:29 +03:00
|
|
|
expectU "should be able to read our temporary file after seeking" "2345678" text3a
|
|
|
|
closeFile handle3
|
|
|
|
|
|
|
|
runTest test
|
|
|
|
|
|
|
|
testAppend : '{io2.IO} [Result]
|
|
|
|
testAppend _ =
|
|
|
|
test = 'let
|
|
|
|
tempDir = toException (newTempDir "openFile")
|
|
|
|
|
|
|
|
fooFile = tempDir ++ "/foo"
|
|
|
|
handle1 = toException (openFile fooFile FileMode.Write)
|
|
|
|
putBytes handle1 (toUtf8 "test1")
|
|
|
|
closeFile handle1
|
|
|
|
|
|
|
|
handle2 = toException (openFile fooFile FileMode.Append)
|
|
|
|
putBytes handle2 (toUtf8 "test2")
|
|
|
|
expectU "we should be at position 4" +4 (toException (handlePosition handle2))
|
|
|
|
check "which is the EOF" (toException (isFileEOF handle2))
|
|
|
|
closeFile handle2
|
|
|
|
|
|
|
|
handle3 = toException (openFile fooFile FileMode.Read)
|
|
|
|
bytes3 = toException (getBytes handle3 1000)
|
2020-12-03 01:11:54 +03:00
|
|
|
text3 = toException (Text.fromUtf8 bytes3)
|
2020-12-02 00:05:29 +03:00
|
|
|
expectU "should be able to read our temporary file" "test1test2" text3
|
|
|
|
|
|
|
|
closeFile handle3
|
|
|
|
|
|
|
|
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`:
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
testAppend : '{io2.IO} [Result]
|
|
|
|
testSeek : '{io2.IO} [Result]
|
2020-12-02 00:05:29 +03:00
|
|
|
|
|
|
|
```
|
|
|
|
```ucm
|
|
|
|
.> add
|
|
|
|
|
|
|
|
⍟ I've added these definitions:
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
testAppend : '{io2.IO} [Result]
|
|
|
|
testSeek : '{io2.IO} [Result]
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
.> io.test testSeek
|
2020-12-02 00:05:29 +03:00
|
|
|
|
|
|
|
New test results:
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
◉ testSeek readable file should be seekable
|
|
|
|
◉ testSeek shouldn't be the EOF
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
✅ 2 test(s) passing
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
Tip: Use view testSeek to view the source of a test.
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
.> io.test testAppend
|
2020-12-02 00:05:29 +03:00
|
|
|
|
|
|
|
New test results:
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
😶 No tests available.
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
```
|
|
|
|
### SystemTime
|
|
|
|
```unison
|
|
|
|
testSystemTime : '{io2.IO} [Result]
|
|
|
|
testSystemTime _ =
|
|
|
|
test = 'let
|
|
|
|
t = toException !io2.IO.systemTime
|
|
|
|
check "systemTime should be sane" ((t > 1600000000) && (t > 2000000000))
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
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:
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
⍟ These new definitions are ok to `add`:
|
|
|
|
|
|
|
|
testSystemTime : '{io2.IO} [Result]
|
|
|
|
|
|
|
|
```
|
|
|
|
```ucm
|
|
|
|
.> add
|
|
|
|
|
|
|
|
⍟ I've added these definitions:
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
testSystemTime : '{io2.IO} [Result]
|
2020-12-02 00:05:29 +03:00
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
.> io.test testSystemTime
|
2020-12-02 00:05:29 +03:00
|
|
|
|
|
|
|
New test results:
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
◉ testSystemTime systemTime should be sane
|
|
|
|
|
|
|
|
✅ 1 test(s) passing
|
|
|
|
|
|
|
|
Tip: Use view testSystemTime to view the source of a test.
|
2020-12-02 00:05:29 +03:00
|
|
|
|
|
|
|
```
|
|
|
|
## MVars
|
|
|
|
|
2020-12-02 22:03:21 +03:00
|
|
|
MVars are threadsafe mutable locations which at any time may or may not
|
|
|
|
contain a signle typed value. They are a building block on which many
|
|
|
|
concurrency primitives can be built that allow multiple threads to
|
|
|
|
synchronize and share data.
|
|
|
|
|
2020-12-02 00:05:29 +03:00
|
|
|
```unison
|
|
|
|
testMvars: '{io2.IO}[Result]
|
|
|
|
testMvars _ =
|
|
|
|
test = 'let
|
|
|
|
test = "test"
|
|
|
|
test2 = "test2"
|
|
|
|
ma = MVar.new test
|
|
|
|
check "ma should not be empty" (not (isEmpty ma))
|
|
|
|
test' = toException (take ma)
|
|
|
|
expectU "should reap what you sow" test test'
|
|
|
|
check "ma should be empty" (isEmpty ma)
|
|
|
|
toException (put ma test)
|
|
|
|
test'' = toException (swap ma test2)
|
|
|
|
expectU "swap returns old contents" test test''
|
|
|
|
test''' = toException (swap ma test)
|
|
|
|
expectU "swap returns old contents" test2 test'''
|
|
|
|
|
|
|
|
ma2 = !MVar.newEmpty
|
|
|
|
check "tryTake should succeed when not empty" (not (isNone (tryTake ma)))
|
|
|
|
check "tryTake should not succeed when empty" (isNone (tryTake ma))
|
|
|
|
|
|
|
|
check "ma2 should be empty" (isEmpty ma2)
|
|
|
|
check "tryTake should fail when empty" (isNone (tryTake ma2))
|
|
|
|
|
|
|
|
|
|
|
|
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`:
|
|
|
|
|
|
|
|
testMvars : '{io2.IO} [Result]
|
|
|
|
|
|
|
|
```
|
|
|
|
```ucm
|
|
|
|
.> add
|
|
|
|
|
|
|
|
⍟ I've added these definitions:
|
|
|
|
|
|
|
|
testMvars : '{io2.IO} [Result]
|
|
|
|
|
|
|
|
.> io.test testMvars
|
|
|
|
|
|
|
|
New test results:
|
|
|
|
|
|
|
|
◉ testMvars ma should not be empty
|
|
|
|
◉ testMvars should reap what you sow
|
|
|
|
◉ testMvars ma should be empty
|
|
|
|
◉ testMvars swap returns old contents
|
|
|
|
◉ testMvars swap returns old contents
|
|
|
|
◉ testMvars tryTake should succeed when not empty
|
|
|
|
◉ testMvars tryTake should not succeed when empty
|
|
|
|
◉ testMvars ma2 should be empty
|
|
|
|
◉ testMvars tryTake should fail when empty
|
|
|
|
|
|
|
|
✅ 9 test(s) passing
|
|
|
|
|
|
|
|
Tip: Use view testMvars to view the source of a test.
|
|
|
|
|
|
|
|
```
|