unison/unison-src/transcripts/io.md

427 lines
11 KiB
Markdown
Raw Permalink Normal View History

# tests for built-in IO functions
```ucm:hide
scratch/main> builtins.merge
scratch/main> builtins.mergeio
scratch/main> load unison-src/transcripts-using-base/base.u
scratch/main> add
```
2021-02-08 23:02:10 +03:00
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.
```ucm:hide
scratch/main> add
```
## Basic File Functions
### Creating/Deleting/Renaming Directories
Tests:
- createDirectory,
- isDirectory,
- fileExists,
- renameDirectory,
- deleteDirectory
```unison
testCreateRename : '{io2.IO} [Result]
testCreateRename _ =
test = 'let
tempDir = newTempDir "fileio"
fooDir = tempDir ++ "/foo"
barDir = tempDir ++ "/bar"
2022-12-02 08:51:11 +03:00
void x = ()
void (createDirectory.impl fooDir)
check "create a foo directory" (isDirectory fooDir)
check "directory should exist" (fileExists fooDir)
renameDirectory fooDir barDir
check "foo should no longer exist" (not (fileExists fooDir))
check "directory should no longer exist" (not (fileExists fooDir))
check "bar should now exist" (fileExists barDir)
bazDir = barDir ++ "/baz"
2022-12-02 08:51:11 +03:00
void (createDirectory.impl bazDir)
void (removeDirectory.impl barDir)
check "removeDirectory works recursively" (not (isDirectory barDir))
check "removeDirectory works recursively" (not (isDirectory bazDir))
runTest test
```
```ucm
scratch/main> add
scratch/main> io.test testCreateRename
```
### Opening / Closing files
Tests:
- openFile
- closeFile
- isFileOpen
```unison
testOpenClose : '{io2.IO} [Result]
testOpenClose _ =
test = 'let
tempDir = (newTempDir "seek")
fooFile = tempDir ++ "/foo"
handle1 = openFile fooFile FileMode.Write
check "file should be open" (isFileOpen handle1)
setBuffering handle1 (SizedBlockBuffering 1024)
check "file handle buffering should match what we just set." (getBuffering handle1 == SizedBlockBuffering 1024)
setBuffering handle1 (getBuffering handle1)
putBytes handle1 0xs01
setBuffering handle1 NoBuffering
setBuffering handle1 (getBuffering handle1)
putBytes handle1 0xs23
setBuffering handle1 BlockBuffering
setBuffering handle1 (getBuffering handle1)
putBytes handle1 0xs45
setBuffering handle1 LineBuffering
setBuffering handle1 (getBuffering handle1)
putBytes handle1 0xs67
closeFile handle1
check "file should be closed" (not (isFileOpen handle1))
-- make sure the bytes have been written
handle2 = openFile fooFile FileMode.Read
check "bytes have been written" (getBytes handle2 4 == 0xs01234567)
closeFile handle2
-- checking that ReadWrite mode works fine
handle3 = openFile fooFile FileMode.ReadWrite
check "bytes have been written" (getBytes handle3 4 == 0xs01234567)
closeFile handle3
check "file should be closed" (not (isFileOpen handle1))
runTest test
```
```ucm
scratch/main> add
scratch/main> io.test testOpenClose
```
### Reading files with getSomeBytes
Tests:
- getSomeBytes
- putBytes
- isFileOpen
- seekHandle
```unison
testGetSomeBytes : '{io2.IO} [Result]
testGetSomeBytes _ =
test = 'let
tempDir = (newTempDir "getSomeBytes")
fooFile = tempDir ++ "/foo"
testData = "0123456789"
testSize = size testData
chunkSize = 7
check "chunk size splits data into 2 uneven sides" ((chunkSize > (testSize / 2)) && (chunkSize < testSize))
-- write testData to a temporary file
fooWrite = openFile fooFile Write
putBytes fooWrite (toUtf8 testData)
closeFile fooWrite
check "file should be closed" (not (isFileOpen fooWrite))
-- reopen for reading back the data in chunks
fooRead = openFile fooFile Read
-- read first part of file
chunk1 = getSomeBytes fooRead chunkSize |> fromUtf8
check "first chunk matches first part of testData" (chunk1 == take chunkSize testData)
-- read rest of file
chunk2 = getSomeBytes fooRead chunkSize |> fromUtf8
check "second chunk matches rest of testData" (chunk2 == drop chunkSize testData)
check "should be at end of file" (isFileEOF fooRead)
readAtEOF = getSomeBytes fooRead chunkSize
check "reading at end of file results in Bytes.empty" (readAtEOF == Bytes.empty)
-- request many bytes from the start of the file
seekHandle fooRead AbsoluteSeek +0
bigRead = getSomeBytes fooRead (testSize * 999) |> fromUtf8
check "requesting many bytes results in what's available" (bigRead == testData)
closeFile fooRead
check "file should be closed" (not (isFileOpen fooRead))
runTest test
```
```ucm
scratch/main> add
scratch/main> io.test testGetSomeBytes
```
### Seeking in open files
Tests:
- openFile
- putBytes
- closeFile
- isSeekable
- isFileEOF
- seekHandle
- getBytes
- getLine
```unison
testSeek : '{io2.IO} [Result]
testSeek _ =
test = 'let
tempDir = newTempDir "seek"
emit (Ok "seeked")
fooFile = tempDir ++ "/foo"
handle1 = openFile fooFile FileMode.Append
putBytes handle1 (toUtf8 "12345678")
closeFile handle1
handle3 = openFile fooFile FileMode.Read
check "readable file should be seekable" (isSeekable handle3)
check "shouldn't be the EOF" (not (isFileEOF handle3))
expectU "we should be at position 0" 0 (handlePosition handle3)
seekHandle handle3 AbsoluteSeek +1
expectU "we should be at position 1" 1 (handlePosition handle3)
bytes3a = getBytes handle3 1000
text3a = Text.fromUtf8 bytes3a
expectU "should be able to read our temporary file after seeking" "2345678" text3a
closeFile handle3
barFile = tempDir ++ "/bar"
handle4 = openFile barFile FileMode.Append
putBytes handle4 (toUtf8 "foobar\n")
closeFile handle4
handle5 = openFile barFile FileMode.Read
expectU "getLine should get a line" "foobar" (getLine handle5)
closeFile handle5
runTest test
testAppend : '{io2.IO} [Result]
testAppend _ =
test = 'let
tempDir = newTempDir "openFile"
fooFile = tempDir ++ "/foo"
handle1 = openFile fooFile FileMode.Write
putBytes handle1 (toUtf8 "test1")
closeFile handle1
handle2 = openFile fooFile FileMode.Append
putBytes handle2 (toUtf8 "test2")
closeFile handle2
handle3 = openFile fooFile FileMode.Read
bytes3 = getBytes handle3 1000
text3 = Text.fromUtf8 bytes3
expectU "should be able to read our temporary file" "test1test2" text3
closeFile handle3
runTest test
```
```ucm
scratch/main> add
scratch/main> io.test testSeek
scratch/main> io.test testAppend
```
### SystemTime
```unison
testSystemTime : '{io2.IO} [Result]
testSystemTime _ =
test = 'let
t = !systemTime
check "systemTime should be sane" ((t > 1600000000) && (t < 2000000000))
runTest test
```
```ucm
scratch/main> add
scratch/main> io.test testSystemTime
```
2021-05-22 03:30:22 +03:00
### Get temp directory
```unison:hide
testGetTempDirectory : '{io2.IO} [Result]
testGetTempDirectory _ =
test = 'let
tempDir = reraise !getTempDirectory.impl
check "Temp directory is directory" (isDirectory tempDir)
check "Temp directory should exist" (fileExists tempDir)
runTest test
```
```ucm
scratch/main> add
scratch/main> io.test testGetTempDirectory
```
### Get current directory
```unison:hide
testGetCurrentDirectory : '{io2.IO} [Result]
testGetCurrentDirectory _ =
test = 'let
currentDir = reraise !getCurrentDirectory.impl
check "Current directory is directory" (isDirectory currentDir)
check "Current directory should exist" (fileExists currentDir)
runTest test
```
```ucm
scratch/main> add
scratch/main> io.test testGetCurrentDirectory
```
2021-05-22 03:30:22 +03:00
### Get directory contents
2021-05-26 19:47:51 +03:00
```unison:hide
2021-05-22 03:30:22 +03:00
testDirContents : '{io2.IO} [Result]
2021-05-26 19:47:51 +03:00
testDirContents _ =
2021-05-22 03:30:22 +03:00
test = 'let
tempDir = newTempDir "dircontents"
c = reraise (directoryContents.impl tempDir)
check "directory size should be" (size c == 2)
check "directory contents should have current directory and parent" let
(c == [".", ".."]) || (c == ["..", "."])
2021-05-22 03:30:22 +03:00
runTest test
```
2021-05-22 03:30:22 +03:00
```ucm
scratch/main> add
scratch/main> io.test testDirContents
2021-05-22 03:30:22 +03:00
```
### Read environment variables
```unison:hide
testGetEnv : '{io2.IO} [Result]
testGetEnv _ =
test = 'let
path = reraise (getEnv.impl "PATH") -- PATH exists on windows, mac and linux.
check "PATH environent variable should be set" (size path > 0)
match getEnv.impl "DOESNTEXIST" with
Right _ -> emit (Fail "env var shouldn't exist")
Left _ -> emit (Ok "DOESNTEXIST didn't exist")
runTest test
```
```ucm
scratch/main> add
scratch/main> io.test testGetEnv
```
### Read command line args
`runMeWithNoArgs`, `runMeWithOneArg`, and `runMeWithTwoArgs` raise exceptions
unless they called with the right number of arguments.
```unison:hide
testGetArgs.fail : Text -> Failure
testGetArgs.fail descr = Failure (typeLink IOFailure) descr !Any
testGetArgs.runMeWithNoArgs : '{io2.IO, Exception} ()
testGetArgs.runMeWithNoArgs = 'let
args = reraise !getArgs.impl
match args with
[] -> printLine "called with no args"
_ -> raise (testGetArgs.fail "called with args")
testGetArgs.runMeWithOneArg : '{io2.IO, Exception} ()
testGetArgs.runMeWithOneArg = 'let
args = reraise !getArgs.impl
match args with
[] -> raise (testGetArgs.fail "called with no args")
[_] -> printLine "called with one arg"
_ -> raise (testGetArgs.fail "called with too many args")
testGetArgs.runMeWithTwoArgs : '{io2.IO, Exception} ()
testGetArgs.runMeWithTwoArgs = 'let
args = reraise !getArgs.impl
match args with
[] -> raise (testGetArgs.fail "called with no args")
[_] -> raise (testGetArgs.fail "called with one arg")
[_, _] -> printLine "called with two args"
_ -> raise (testGetArgs.fail "called with too many args")
```
Test that they can be run with the right number of args.
```ucm
scratch/main> add
scratch/main> run runMeWithNoArgs
scratch/main> run runMeWithOneArg foo
scratch/main> run runMeWithTwoArgs foo bar
```
Calling our examples with the wrong number of args will error.
```ucm:error
scratch/main> run runMeWithNoArgs foo
```
```ucm:error
scratch/main> run runMeWithOneArg
```
```ucm:error
scratch/main> run runMeWithOneArg foo bar
```
```ucm:error
scratch/main> run runMeWithTwoArgs
```
2023-06-22 07:46:40 +03:00
### Get the time zone
```unison:hide
testTimeZone = do
(offset, summer, name) = Clock.internals.systemTimeZone +0
2023-06-22 10:02:49 +03:00
_ = (offset : Int, summer : Nat, name : Text)
()
2023-06-22 07:46:40 +03:00
```
```ucm
scratch/main> add
scratch/main> run testTimeZone
2023-06-22 07:46:40 +03:00
```
2023-07-02 07:31:11 +03:00
### Get some random bytes
```unison:hide
testRandom : '{io2.IO} [Result]
testRandom = do
test = do
bytes = IO.randomBytes 10
check "randomBytes returns the right number of bytes" (size bytes == 10)
runTest test
```
```ucm
scratch/main> add
scratch/main> io.test testGetEnv
2023-07-02 07:31:11 +03:00
```