{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Main (main) where
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import System.Directory (createDirectoryIfMissing, getTemporaryDirectory)
import qualified System.Exit as Exit
import System.FilePath ((</>))
import System.IO (hPutStrLn, stderr)
import Test.HUnit (
Test (..),
assertEqual,
errors,
failures,
runTestTT,
)
import qualified DataFrame.Fusion.Typed as Fusion
import qualified DataFrame.Operations.Core as Core
import DataFrame.Typed.Freeze (thaw)
-- | A minimal smoke schema: id (int), score (double), name (text).
type CsvCols =
'[ '("id", Int)
, '("score", Double)
, '("name", T.Text)
]
testCsv :: T.Text
testCsv =
T.unlines
[ "id,score,name"
, "1,1.5,alice"
, "2,2.5,bob"
, "3,3.5,carol"
, "4,4.5,dan"
]
-- | Companion CSV for the join test: department per id.
deptsCsv :: T.Text
deptsCsv =
T.unlines
[ "id,dept"
, "1,eng"
, "2,eng"
, "3,sales"
]
type DeptCols =
'[ '("id", Int)
, '("dept", T.Text)
]
testFixtureDir :: IO FilePath
testFixtureDir = do
base <- getTemporaryDirectory
let dir = base </> "dataframe-fusion-tests"
createDirectoryIfMissing True dir
return dir
writeTestCsv :: IO FilePath
writeTestCsv = do
dir <- testFixtureDir
let path = dir </> "smoke.csv"
TIO.writeFile path testCsv
return path
writeDeptsCsv :: IO FilePath
writeDeptsCsv = do
dir <- testFixtureDir
let path = dir </> "depts.csv"
TIO.writeFile path deptsCsv
return path
main :: IO ()
main = do
path <- writeTestCsv
deptPath <- writeDeptsCsv
ctx <- Fusion.newContext
let scan = Fusion.scanCsv @CsvCols ctx (T.pack path)
scanDepts = Fusion.scanCsv @DeptCols ctx (T.pack deptPath)
let tests =
TestList
[ TestLabel "scanCsv + run round-trips column count" $ TestCase $ do
fdf <- scan
tdf <- Fusion.run fdf
let df = thaw tdf
assertEqual "row count" 4 (Core.nRows df)
assertEqual "column count" 3 (Core.nColumns df)
, TestLabel "take limits row count" $ TestCase $ do
fdf <- scan
fdf' <- Fusion.take 2 fdf
df <- thaw <$> Fusion.run fdf'
assertEqual "row count after take 2" 2 (Core.nRows df)
, TestLabel "select projects to fewer columns" $ TestCase $ do
fdf <- scan
fdf' <- Fusion.select @'["id", "name"] fdf
df <- thaw <$> Fusion.run fdf'
assertEqual "columns after select" 2 (Core.nColumns df)
, TestLabel "filter drops non-matching rows" $ TestCase $ do
fdf <- scan
let pred_ = Fusion.col @"id" Fusion..>. Fusion.lit (2 :: Int)
fdf' <- Fusion.filter pred_ fdf
df <- thaw <$> Fusion.run fdf'
assertEqual "row count after filter id > 2" 2 (Core.nRows df)
, TestLabel "derive adds a computed column" $ TestCase $ do
fdf <- scan
let doubled = Fusion.col @"score" Fusion..*. Fusion.lit (2.0 :: Double)
fdf' <- Fusion.derive @"doubled" doubled fdf
df <- thaw <$> Fusion.run fdf'
assertEqual "row count after derive" 4 (Core.nRows df)
assertEqual "column count after derive" 4 (Core.nColumns df)
, TestLabel "sortBy reorders rows" $ TestCase $ do
fdf <- scan
fdf' <- Fusion.sortBy [("score", Fusion.Descending)] fdf
df <- thaw <$> Fusion.run fdf'
assertEqual "row count after sort" 4 (Core.nRows df)
, TestLabel "groupBy + aggregate produces one row per key" $ TestCase $ do
fdf <- scan
-- group by name, sum scores (each name unique here, so 4 groups)
fdf' <-
Fusion.aggregate
(Fusion.as @"total" (Fusion.sum (Fusion.col @"score")))
(Fusion.groupBy @'["name"] fdf)
df <- thaw <$> Fusion.run fdf'
assertEqual "row count after groupBy" 4 (Core.nRows df)
assertEqual "column count after groupBy" 2 (Core.nColumns df)
, TestLabel "innerJoin matches rows on id" $ TestCase $ do
fdf <- scan
fdf2 <- scanDepts
joined <- Fusion.innerJoin "id" "id" fdf fdf2
df <- thaw <$> Fusion.run joined
-- 3 ids in depts.csv match against 4 ids in smoke.csv -> 3 rows
assertEqual "row count after inner join" 3 (Core.nRows df)
]
counts <- runTestTT tests
if errors counts + failures counts == 0
then return ()
else do
hPutStrLn stderr "FAILED"
Exit.exitWith (Exit.ExitFailure 1)