packages feed

pipes-csv-1.4.2: test/test-pipes-csv.hs

{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-unused-do-bind -fno-warn-orphans -fno-warn-unused-imports -fno-warn-unused-binds #-}
{-# LANGUAGE OverloadedStrings #-}

module Main (
    main
 ) where

import           Test.Framework as TF
import           Test.Framework.Providers.HUnit (testCase)
import           Test.HUnit

import           Data.List.Utils (replace)
import           Control.Applicative
import           Control.Monad
import           Control.Arrow (left)
import qualified Data.ByteString.Char8 as C
import qualified Data.Vector as DV

import           Data.Csv ((.:), (.!), FromNamedRecord(..), FromRecord(..),
                           Record, HasHeader(..))
import           Pipes.ByteString (stdin, ByteString)
import           Pipes.Csv (decode, decodeByName)
import           Pipes
import qualified Pipes.Prelude as P

-- helper functions
right :: (Monad m) => Pipe (Either a b) b m r
right = loop
  where
    loop = await >>= \s -> case s of
      Left _  -> loop
      Right v -> yield v >> loop

-- decode to vector
decoder :: Monad m
        => Producer ByteString m ()
        -> Producer (Either String Record) m ()
decoder = decode NoHeader

decoderStream =
    [ ""
    , "1"
    , "\n"
    , "1,2\n"
    , "1,2,3\n"
    , "\n" -- ignores empty line
    , "no line end needed on last chunk"
    ]

decoderResult =
    [
        ["1"]
      , ["1","2"]
      , ["1","2","3"]
      , ["no line end needed on last chunk"]
    ]

testDecoder :: Assertion
testDecoder =
    map DV.fromList decoderResult @=?
    P.toList (decoder (each $ map C.pack decoderStream) >-> right)

ioDecoder = runEffect $
    for (decoder (each $ map C.pack decoderStream)) (lift . print)

-- decode to Int tuple
decoderTuple :: Monad m
         => Producer ByteString m ()
         -> Producer (Either String (Int, Int)) m ()
decoderTuple = decode NoHeader

decoderTupleStream =
    [ ""
    , "1,2"
    , "\n"
    , "1,2\n"
    , "2,3\n"
    , "\n" -- ignores empty line
    , "one,two\n"
    , "no line end needed on last chunk\n"
    , "0,0"
    ]

decoderTupleResult =
    [ Right (1,2)
    , Right (1,2)
    , Right (2,3)
    , Left "expected Int, got \"one\" (Failed reading: takeWhile1)"
    , Left "cannot unpack array of length 1 into a pair. Input record: [\"no line end needed on last chunk\"]"
    , Right (0,0)
    ]

testDecoderTuple :: Assertion
testDecoderTuple =
    let res = P.toList (decoderTuple (each $ map C.pack decoderTupleStream))
        fixup = replace "fromList " "" -- vector <0.11 compatibility
    in decoderTupleResult @=? map (left fixup) res

data Person = Person String Int
            deriving (Show, Eq)

instance FromRecord Person where
  parseRecord v =
    Person <$> v .! 0
           <*> v .! 1

decoderPerson :: Monad m
        => Producer ByteString m ()
        -> Producer (Either String Person) m ()
decoderPerson = decode NoHeader

decoderPersonStream =
    [ ""
    , "Jack,21\nJill,55\n"
    , "Jimbo,21,beyond this is discarded,and this\n"
    , "Bill,1\n"
    , "Bill"
    , "y"
    , ","
    , "2"
    , "\n"
    , "Sue,22abc\n" -- consumes 22 and discards "abc"
    -- , "zz\n" -- throws an exception
    , "space allowed, 99\n"
    , "eof,99"
    ]

decoderPersonResult =
    [ Right (Person "Jack" 21)
    , Right (Person "Jill" 55)
    , Right (Person "Jimbo" 21)
    , Right (Person "Bill" 1)
    , Right (Person "Billy" 2)
    , Left "expected Int, got \"22abc\" (incomplete field parse, leftover: [97,98,99])"
    , Right (Person "space allowed" 99)
    , Right (Person "eof" 99)
    ]

testDecoderPerson :: Assertion
testDecoderPerson =
    decoderPersonResult @=?
    P.toList (decoderPerson (each $ map C.pack decoderPersonStream))

ioDecoderPerson = runEffect $
    for (decoderPerson (each $ map C.pack decoderPersonStream)) (lift . print)

-- data record version of Person

data Person' = Person' {
    name :: String
  , age :: Int
  } deriving (Show)

instance FromRecord Person' where
  parseRecord v =
    Person' <$> v .! 0
            <*> v .! 1

decoderPerson' :: Monad m
        => Producer ByteString m ()
        -> Producer (Either String Person') m ()
decoderPerson' = decode NoHeader

ioDecoderPerson' = runEffect $
    for (decoderPerson' (each $ map C.pack decoderPersonStream)) (lift . print)

decoderPerson'Result = [21,55,21,1,2,99,99]

testDecoderPerson' :: Assertion
testDecoderPerson' =
    decoderPerson'Result @=?
    P.toList (decoderPerson' (each $ map C.pack decoderPersonStream)
        >-> right >-> P.map age)

-- named person record
instance FromNamedRecord Person where
  parseNamedRecord p =
    Person <$> p .: "name"
           <*> p .: "age"

decoderNamedPerson :: Monad m
        => Producer ByteString m ()
        -> Producer (Either String Person) m ()
decoderNamedPerson = decodeByName

decoderNamedPersonStream = "Name,Age\n" : decoderPersonStream

ioDecoderNamedPerson = runEffect $
    for (decoderNamedPerson (each $ map C.pack decoderNamedPersonStream))
        (lift . print)

-- test frame
main :: IO ()
main = defaultMain tests

tests :: [TF.Test]
tests =
  [
    testGroup "Pipes.Csv." decodeTests
  ]

decodeTests = [ testCase "HUnit" testDecoder
              , testCase "HUnit" testDecoderTuple
              , testCase "HUnit" testDecoderPerson
              , testCase "HUnit" testDecoderPerson'
              ]