1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
|
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module SendfileSpec where
import Control.Concurrent
import Control.Exception
import Control.Monad
import Control.Monad.Trans.Resource (MonadResource, runResourceT)
import Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as BL
import Data.Conduit
import Data.Conduit.Binary as CB
import Data.Conduit.List as CL
import Data.Conduit.Network
import Data.IORef
import Network.Sendfile
import Network.Socket
import System.Directory
import System.IO
import System.Timeout
import System.EasyFile
import Test.Hspec
----------------------------------------------------------------
spec :: Spec
spec = do
describe "sendfile" $ do
it "sends an entire file" $ do
sendFile EntireFile `shouldReturn` True
it "sends a part of file" $ do
sendFile (PartOfFile 2000 1000000) `shouldReturn` True
it "terminates even if length is over" $ do
shouldTerminate $ sendIllegal (PartOfFile 2000 5000000)
it "terminates even if offset is over" $ do
shouldTerminate $ sendIllegal (PartOfFile 5000000 6000000)
-- On Windows, setFileSize throws an exception due to the
-- access permission. The test case will be finished but it is
-- not a right test.
it "terminates even if the file is truncated" $ do
shouldTerminate truncateFile
describe "sendfileWithHeader" $ do
it "sends an header and an entire file" $ do
sendFileH EntireFile `shouldReturn` True
it "sends an header and a part of file" $ do
sendFileH (PartOfFile 2000 1000000) `shouldReturn` True
it "sends a large header and an entire file" $ do
sendFileHLarge EntireFile `shouldReturn` True
it "sends a large header and a part of file" $ do
sendFileHLarge (PartOfFile 2000 1000000) `shouldReturn` True
it "terminates even if length is over" $ do
shouldTerminate $ sendIllegalH (PartOfFile 2000 5000000)
it "terminates even if offset is over" $ do
shouldTerminate $ sendIllegalH (PartOfFile 5000000 6000000)
-- On Windows, setFileSize throws an exception due to the
-- access permission. The test case will be finished but it is
-- not a right test.
it "terminates even if the file is truncated" $ do
shouldTerminate truncateFileH
where
fiveSecs = 5000000
shouldTerminate body = timeout fiveSecs body `shouldReturn` Just ()
----------------------------------------------------------------
sendFile :: FileRange -> IO Bool
sendFile range = sendFileCore range []
sendFileH :: FileRange -> IO Bool
sendFileH range = sendFileCore range headers
where
headers = [
BS.replicate 100 'a'
, "\n"
, BS.replicate 200 'b'
, "\n"
, BS.replicate 300 'c'
, "\n"
]
sendFileHLarge :: FileRange -> IO Bool
sendFileHLarge range = sendFileCore range headers
where
headers = [
BS.replicate 10000 'a'
, "\n"
, BS.replicate 20000 'b'
, "\n"
, BS.replicate 30000 'c'
, "\n"
]
sendFileCore :: FileRange -> [ByteString] -> IO Bool
sendFileCore range headers = bracket setup teardown $ \(s2,_) -> do
#if MIN_VERSION_conduit(1,3,0)
runResourceT $ runConduit (sourceSocket s2 .| sinkFile outputFile)
#else
runResourceT $ sourceSocket s2 $$ sinkFile outputFile
#endif
runResourceT $ copyfile range
!f1 <- BL.readFile outputFile
!f2 <- BL.readFile expectedFile
return $! (f1 == f2)
where
copyfile EntireFile = do
-- of course, we can use <> here
#if MIN_VERSION_conduit(1,3,0)
runConduit (sourceList headers .| sinkFile expectedFile)
runConduit (sourceFile inputFile .| sinkAppendFile expectedFile)
#else
sourceList headers $$ sinkFile expectedFile
sourceFile inputFile $$ sinkAppendFile expectedFile
#endif
copyfile (PartOfFile off len) = do
#if MIN_VERSION_conduit(1,3,0)
runConduit (sourceList headers .| sinkFile expectedFile)
runConduit (sourceFile inputFile
.| CB.isolate (off' + len')
.| (CB.take off' >> sinkAppendFile expectedFile))
#else
sourceList headers $$ sinkFile expectedFile
sourceFile inputFile $= CB.isolate (off' + len')
$$ (CB.take off' >> sinkAppendFile expectedFile)
#endif
where
off' = fromIntegral off
len' = fromIntegral len
setup = do
(s1,s2) <- socketPair AF_UNIX Stream 0
tid <- forkIO (sf s1 `finally` sendEOF s1)
return (s2,tid)
where
sf s1
| headers == [] = sendfile s1 inputFile range (return ())
| otherwise = sendfileWithHeader s1 inputFile range (return ()) headers
sendEOF = close
teardown (s2,tid) = do
close s2
killThread tid
removeFileIfExists outputFile
removeFileIfExists expectedFile
inputFile = "test/inputFile"
outputFile = "test/outputFile"
expectedFile = "test/expectedFile"
----------------------------------------------------------------
sendIllegal :: FileRange -> IO ()
sendIllegal range = sendIllegalCore range []
sendIllegalH :: FileRange -> IO ()
sendIllegalH range = sendIllegalCore range headers
where
headers = [
BS.replicate 100 'a'
, "\n"
, BS.replicate 200 'b'
, "\n"
, BS.replicate 300 'c'
, "\n"
]
sendIllegalCore :: FileRange -> [ByteString] -> IO ()
sendIllegalCore range headers = bracket setup teardown $ \(s2,_) -> do
#if MIN_VERSION_conduit(1,3,0)
runResourceT $ runConduit (sourceSocket s2 .| sinkFile outputFile)
#else
runResourceT $ sourceSocket s2 $$ sinkFile outputFile
#endif
return ()
where
setup = do
(s1,s2) <- socketPair AF_UNIX Stream 0
tid <- forkIO (sf s1 `finally` sendEOF s1)
return (s2,tid)
where
sf s1
| headers == [] = sendfile s1 inputFile range (return ())
| otherwise = sendfileWithHeader s1 inputFile range (return ()) headers
sendEOF = close
teardown (s2,tid) = do
close s2
killThread tid
removeFileIfExists outputFile
inputFile = "test/inputFile"
outputFile = "test/outputFile"
----------------------------------------------------------------
truncateFile :: IO ()
truncateFile = truncateFileCore []
truncateFileH :: IO ()
truncateFileH = truncateFileCore headers
where
headers = [
BS.replicate 100 'a'
, "\n"
, BS.replicate 200 'b'
, "\n"
, BS.replicate 300 'c'
, "\n"
]
truncateFileCore :: [ByteString] -> IO ()
truncateFileCore headers = bracket setup teardown $ \(s2,_) -> do
#if MIN_VERSION_conduit(1,3,0)
runResourceT $ runConduit (sourceSocket s2 .| sinkFile outputFile)
#else
runResourceT $ sourceSocket s2 $$ sinkFile outputFile
#endif
return ()
where
setup = do
#if MIN_VERSION_conduit(1,3,0)
runResourceT $ runConduit (sourceFile inputFile .| sinkFile tempFile)
#else
runResourceT $ sourceFile inputFile $$ sinkFile tempFile
#endif
(s1,s2) <- socketPair AF_UNIX Stream 0
ref <- newIORef (1 :: Int)
tid <- forkIO (sf s1 ref `finally` sendEOF s1)
return (s2,tid)
where
sf s1 ref
| headers == [] = sendfile s1 tempFile range (hook ref)
| otherwise = sendfileWithHeader s1 tempFile range (hook ref) headers
sendEOF = close
hook ref = do
n <- readIORef ref
when (n == 10) $ setFileSize tempFile 900000
writeIORef ref (n+1)
teardown (s2,tid) = do
close s2
killThread tid
removeFileIfExists tempFile
removeFileIfExists outputFile
inputFile = "test/inputFile"
tempFile = "test/tempFile"
outputFile = "test/outputFile"
range = EntireFile
----------------------------------------------------------------
removeFileIfExists :: FilePath -> IO ()
removeFileIfExists file = do
exist <- doesFileExist file
when exist $ removeFile file
sinkAppendFile :: MonadResource m
=> FilePath
#if MIN_VERSION_conduit(1,3,0)
-> ConduitT ByteString Void m ()
#else
-> Sink ByteString m ()
#endif
sinkAppendFile fp = sinkIOHandle (openBinaryFile fp AppendMode)
|