11 KiB
tests for built-in IO functions
scratch/main> builtins.merge
scratch/main> builtins.mergeio
scratch/main> load unison-src/transcripts-using-base/base.u
scratch/main> add
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.
scratch/main> add
Basic File Functions
Creating/Deleting/Renaming Directories
Tests: createDirectory, isDirectory, fileExists, renameDirectory, deleteDirectory
testCreateRename : '{io2.IO} [Result]
testCreateRename _ =
test = 'let
tempDir = newTempDir "fileio"
fooDir = tempDir ++ "/foo"
barDir = tempDir ++ "/bar"
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"
void (createDirectory.impl bazDir)
void (removeDirectory.impl barDir)
check "removeDirectory works recursively" (not (isDirectory barDir))
check "removeDirectory works recursively" (not (isDirectory bazDir))
runTest test
scratch/main> add
scratch/main> io.test testCreateRename
Opening / Closing files
Tests: openFile closeFile isFileOpen
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
scratch/main> add
scratch/main> io.test testOpenClose
Reading files with getSomeBytes
Tests: getSomeBytes putBytes isFileOpen seekHandle
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
scratch/main> add
scratch/main> io.test testGetSomeBytes
Seeking in open files
Tests: openFile putBytes closeFile isSeekable isFileEOF seekHandle getBytes getLine
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
scratch/main> add
scratch/main> io.test testSeek
scratch/main> io.test testAppend
SystemTime
testSystemTime : '{io2.IO} [Result]
testSystemTime _ =
test = 'let
t = !systemTime
check "systemTime should be sane" ((t > 1600000000) && (t < 2000000000))
runTest test
scratch/main> add
scratch/main> io.test testSystemTime
Get temp directory
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
scratch/main> add
scratch/main> io.test testGetTempDirectory
Get current directory
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
scratch/main> add
scratch/main> io.test testGetCurrentDirectory
Get directory contents
testDirContents : '{io2.IO} [Result]
testDirContents _ =
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 == ["..", "."])
runTest test
scratch/main> add
scratch/main> io.test testDirContents
Read environment variables
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
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.
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 (fail "called with args")
testGetArgs.runMeWithOneArg : '{io2.IO, Exception} ()
testGetArgs.runMeWithOneArg = 'let
args = reraise !getArgs.impl
match args with
[] -> raise (fail "called with no args")
[_] -> printLine "called with one arg"
_ -> raise (fail "called with too many args")
testGetArgs.runMeWithTwoArgs : '{io2.IO, Exception} ()
testGetArgs.runMeWithTwoArgs = 'let
args = reraise !getArgs.impl
match args with
[] -> raise (fail "called with no args")
[_] -> raise (fail "called with one arg")
[_, _] -> printLine "called with two args"
_ -> raise (fail "called with too many args")
Test that they can be run with the right number of args.
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.
scratch/main> run runMeWithNoArgs foo
scratch/main> run runMeWithOneArg
scratch/main> run runMeWithOneArg foo bar
scratch/main> run runMeWithTwoArgs
Get the time zone
testTimeZone = do
(offset, summer, name) = Clock.internals.systemTimeZone +0
_ = (offset : Int, summer : Nat, name : Text)
()
scratch/main> add
scratch/main> run testTimeZone
Get some random bytes
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
scratch/main> add
scratch/main> io.test testGetEnv