floskell-0.9.0: src/main/Test.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Test the pretty printer.
module Main where
import Control.Monad ( forM_, guard )
import Data.ByteString ( ByteString )
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Builder as L
import qualified Data.ByteString.UTF8 as UTF8
import Data.Maybe ( mapMaybe )
import qualified Data.Text as T
import Floskell
import Language.Haskell.Exts ( Language(Haskell2010) )
import Markdone ( Markdone(..) )
import qualified Markdone as MD
import System.Environment ( getArgs )
import Test.Hspec
data TestTree =
TestSection String [TestTree] | TestSnippet ByteString | TestMismatchMarker
-- | Prints a string without quoting and escaping.
newtype Readable = Readable ByteString
deriving ( Eq )
instance Show Readable where
show (Readable x) = "\n" ++ UTF8.toString x
-- | Version of 'shouldBe' that prints strings in a readable way,
-- better for our use-case.
shouldBeReadable :: ByteString -> ByteString -> Expectation
shouldBeReadable x y = Readable x `shouldBe` Readable y
haskell :: ByteString
haskell = "haskell"
referenceFile :: Style -> String
referenceFile style = "styles/" ++ name ++ ".md"
where
name = T.unpack $ styleName style
loadMarkdone :: String -> IO [Markdone]
loadMarkdone filename = do
bytes <- S.readFile filename
MD.parse (MD.tokenize bytes)
saveMarkdone :: String -> [Markdone] -> IO ()
saveMarkdone filename doc =
S.writeFile filename $ L.toStrict $ L.toLazyByteString $ MD.print doc
-- | Extract code snippets from a Markdone document.
extractSnippets :: ByteString -> [Markdone] -> [TestTree]
extractSnippets lang = mapMaybe convert
where
convert (Section name children) =
return $ TestSection (UTF8.toString name) $
extractSnippets lang children
convert (CodeFence l c) = do
guard $ l == lang
return $ TestSnippet c
convert _ = Nothing
-- | Load haskell code snippets from Markdone document.
loadSnippets :: String -> IO [TestTree]
loadSnippets filename = do
doc <- loadMarkdone filename
return $ extractSnippets haskell doc
-- | Some styles are broken and will fail the idempotency test.
expectedFailures :: [(T.Text, [Int])]
expectedFailures = []
-- | Convert the Markdone document to Spec benchmarks.
toSpec :: Style -> [Int] -> [TestTree] -> [TestTree] -> Spec
toSpec style path inp ref =
forM_ (zip3 [ 1 :: Int .. ] inp (ref ++ repeat TestMismatchMarker)) $ \case
(n, TestSection title children, TestSection _ children') ->
describe title $ toSpec style (path ++ [ n ]) children children'
(n, TestSnippet code, TestSnippet code') -> do
let path' = (styleName style, path ++ [ n ])
it (name n "formats as expected") $
case reformatSnippet style code of
Left e -> error e
Right b -> b `shouldBeReadable` code'
it (name n "formatting is idempotent") $
if path' `elem` expectedFailures
then pending
else case reformatSnippet style
code >>= reformatSnippet style of
Left e -> error e
Right b -> b `shouldBeReadable` code'
(n, _, _) -> error $ name n "structure mismatch in reference file"
where
name n desc = "Snippet " ++ show n ++ " - " ++ desc
-- | Main tests.
testAll :: IO ()
testAll = do
input <- loadSnippets "TEST.md"
refs <- mapM loadRef styles
hspec $ forM_ refs $
\(name, style, ref) -> context name $ toSpec style [] input ref
where
loadRef style = do
let name = T.unpack $ styleName style
tree <- loadSnippets $ referenceFile style
return (name, style, tree)
reformatSnippet :: Style -> ByteString -> Either String ByteString
reformatSnippet style code = L.toStrict
<$> reformat style Haskell2010 defaultExtensions (Just "TEST.md") code
regenerate :: Style -> [Markdone] -> [Markdone]
regenerate style = map fmt
where
fmt (CodeFence lang code) =
if lang == haskell
then CodeFence lang $ either (UTF8.fromString . ("-- " ++) . show) id $
reformatSnippet style code
else CodeFence lang code
fmt (Section heading children) =
Section heading $ regenerate style children
fmt x = x
-- | Regenerate style reference files.
regenerateAll :: IO ()
regenerateAll = do
doc <- loadMarkdone "TEST.md"
forM_ styles $ \style -> saveMarkdone (referenceFile style) $
regenerate style doc
main :: IO ()
main = do
args <- getArgs
case args of
[ "regenerate" ] -> regenerateAll
_ -> testAll