packages feed

disco-0.2: test/Tests.hs

module Main where

import Control.Monad (filterM, void)
import qualified Data.ByteString as BS
import Data.Function (on)
import Data.List (sort)
import qualified Data.List.NonEmpty as NE
import System.Directory (
  doesFileExist,
  getDirectoryContents,
 )
import System.FilePath (isPathSeparator, (</>))
import System.IO (hGetContents)
import System.Process (
  StdStream (CreatePipe),
  createProcess,
  shell,
  std_out,
  system,
 )
import Test.Tasty
import Test.Tasty.Golden.Advanced
import Text.Printf

main :: IO ()
main = do
  testDirs <-
    getDirectoryContents "test"
      >>= filterM (doesFileExist . (\d -> "test" </> d </> "input"))
  let testDirs' =
        NE.groupBy ((==) `on` extractGroup)
          . sort
          . filter (\f -> f /= "." && f /= "..")
          $ testDirs
  let testTree = testGroup "disco" $ map mkGroup testDirs'
  defaultMain testTree
 where
  mkGroup ds = testGroup (extractGroup (NE.head ds)) $ map mkGolden (NE.toList ds)

extractGroup :: FilePath -> String
extractGroup = takeWhile (/= '-')

extractName :: FilePath -> String
extractName = takeWhile (not . isPathSeparator) . drop 1 . dropWhile (/= '-')

mkGolden :: FilePath -> TestTree
mkGolden relDir =
  goldenVsFileWithDiff
    (extractName relDir)
    (dir </> "expected")
    (dir </> "output")
    (void $ system ("disco -f " ++ (dir </> "input") ++ " > " ++ (dir </> "output")))
 where
  dir = "test" </> relDir

-- | A variant of goldenVsFile that prints the result of @diff@ if
--   the files are different, so we don't have to manually call @diff@
--   every time there is a test failure.
goldenVsFileWithDiff ::
  -- | test name
  TestName ->
  -- | path to the «golden» file (the file that contains correct output)
  FilePath ->
  -- | path to the output file
  FilePath ->
  -- | action that creates the output file
  IO () ->
  -- | the test verifies that the output file contents is the same as the golden file contents
  TestTree
goldenVsFileWithDiff name ref new act =
  goldenTest
    name
    (BS.readFile ref)
    (act >> BS.readFile new)
    cmp
    upd
 where
  cmp = cmpWithDiff ref new
  upd = BS.writeFile ref

cmpWithDiff :: Eq a => FilePath -> FilePath -> a -> a -> IO (Maybe String)
cmpWithDiff f1 f2 x y = do
  if x == y
    then return Nothing
    else do
      (_, Just hout, _, _) <-
        createProcess (shell $ printf "diff %s %s" f1 f2) {std_out = CreatePipe}
      diffStr <- hGetContents hout
      return $
        Just $
          unlines
            [ printf "Files '%s' and '%s' differ:" f1 f2
            , diffStr
            ]