mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-10 14:16:41 +03:00
197 lines
6.9 KiB
Haskell
197 lines
6.9 KiB
Haskell
{-# LANGUAGE CPP #-}
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
|
|
module Hledger.Utils.Test (
|
|
module Test.Tasty
|
|
,module Test.Tasty.HUnit
|
|
-- ,module QC
|
|
-- ,module SC
|
|
,tests
|
|
,test
|
|
,assertLeft
|
|
,assertRight
|
|
,assertParse
|
|
,assertParseEq
|
|
,assertParseEqOn
|
|
,assertParseError
|
|
,assertParseE
|
|
,assertParseEqE
|
|
,assertParseErrorE
|
|
,assertParseStateOn
|
|
)
|
|
where
|
|
|
|
import Control.Monad.Except (ExceptT, runExceptT)
|
|
import Control.Monad.State.Strict (StateT, evalStateT, execStateT)
|
|
import Data.Default (Default(..))
|
|
#if !(MIN_VERSION_base(4,11,0))
|
|
import Data.Semigroup ((<>))
|
|
#endif
|
|
-- import Data.CallStack
|
|
import Data.List (isInfixOf)
|
|
import qualified Data.Text as T
|
|
import Test.Tasty hiding (defaultMain)
|
|
import Test.Tasty.HUnit
|
|
-- import Test.Tasty.QuickCheck as QC
|
|
-- import Test.Tasty.SmallCheck as SC
|
|
import Text.Megaparsec
|
|
import Text.Megaparsec.Custom
|
|
( CustomErr,
|
|
FinalParseError,
|
|
attachSource,
|
|
customErrorBundlePretty,
|
|
finalErrorBundlePretty,
|
|
)
|
|
|
|
import Hledger.Utils.Debug (pshow)
|
|
-- import Hledger.Utils.UTF8IOCompat (error')
|
|
|
|
-- * tasty helpers
|
|
|
|
-- TODO: pretty-print values in failure messages
|
|
|
|
|
|
-- | Name and group a list of tests. Shorter alias for Test.Tasty.HUnit.testGroup.
|
|
tests :: String -> [TestTree] -> TestTree
|
|
tests = testGroup
|
|
|
|
-- | Name an assertion or sequence of assertions. Shorter alias for Test.Tasty.HUnit.testCase.
|
|
test :: String -> Assertion -> TestTree
|
|
test = testCase
|
|
|
|
-- | Assert any Left value.
|
|
assertLeft :: (HasCallStack, Eq b, Show b) => Either a b -> Assertion
|
|
assertLeft (Left _) = return ()
|
|
assertLeft (Right b) = assertFailure $ "expected Left, got (Right " ++ show b ++ ")"
|
|
|
|
-- | Assert any Right value.
|
|
assertRight :: (HasCallStack, Eq a, Show a) => Either a b -> Assertion
|
|
assertRight (Right _) = return ()
|
|
assertRight (Left a) = assertFailure $ "expected Right, got (Left " ++ show a ++ ")"
|
|
|
|
-- | Assert that this stateful parser runnable in IO successfully parses
|
|
-- all of the given input text, showing the parse error if it fails.
|
|
-- Suitable for hledger's JournalParser parsers.
|
|
assertParse :: (HasCallStack, Eq a, Show a, Default st) =>
|
|
StateT st (ParsecT CustomErr T.Text IO) a -> T.Text -> Assertion
|
|
assertParse parser input = do
|
|
ep <- runParserT (evalStateT (parser <* eof) def) "" input
|
|
either (assertFailure.(++"\n").("\nparse error at "++).customErrorBundlePretty)
|
|
(const $ return ())
|
|
ep
|
|
|
|
-- | Assert a parser produces an expected value.
|
|
assertParseEq :: (HasCallStack, Eq a, Show a, Default st) =>
|
|
StateT st (ParsecT CustomErr T.Text IO) a -> T.Text -> a -> Assertion
|
|
assertParseEq parser input expected = assertParseEqOn parser input id expected
|
|
|
|
-- | Like assertParseEq, but transform the parse result with the given function
|
|
-- before comparing it.
|
|
assertParseEqOn :: (HasCallStack, Eq b, Show b, Default st) =>
|
|
StateT st (ParsecT CustomErr T.Text IO) a -> T.Text -> (a -> b) -> b -> Assertion
|
|
assertParseEqOn parser input f expected = do
|
|
ep <- runParserT (evalStateT (parser <* eof) def) "" input
|
|
either (assertFailure . (++"\n") . ("\nparse error at "++) . customErrorBundlePretty)
|
|
(assertEqual "" expected . f)
|
|
ep
|
|
|
|
-- | Assert that this stateful parser runnable in IO fails to parse
|
|
-- the given input text, with a parse error containing the given string.
|
|
assertParseError :: (HasCallStack, Eq a, Show a, Default st) =>
|
|
StateT st (ParsecT CustomErr T.Text IO) a -> String -> String -> Assertion
|
|
assertParseError parser input errstr = do
|
|
ep <- runParserT (evalStateT parser def) "" (T.pack input)
|
|
case ep of
|
|
Right v -> assertFailure $ "\nparse succeeded unexpectedly, producing:\n" ++ pshow v ++ "\n"
|
|
Left e -> do
|
|
let e' = customErrorBundlePretty e
|
|
if errstr `isInfixOf` e'
|
|
then return ()
|
|
else assertFailure $ "\nparse error is not as expected:\n" ++ e' ++ "\n"
|
|
|
|
-- | Run a stateful parser in IO like assertParse, then assert that the
|
|
-- final state (the wrapped state, not megaparsec's internal state),
|
|
-- transformed by the given function, matches the given expected value.
|
|
assertParseStateOn :: (HasCallStack, Eq b, Show b, Default st) =>
|
|
StateT st (ParsecT CustomErr T.Text IO) a
|
|
-> T.Text
|
|
-> (st -> b)
|
|
-> b
|
|
-> Assertion
|
|
assertParseStateOn parser input f expected = do
|
|
es <- runParserT (execStateT (parser <* eof) def) "" input
|
|
case es of
|
|
Left err -> assertFailure $ (++"\n") $ ("\nparse error at "++) $ customErrorBundlePretty err
|
|
Right s -> assertEqual "" expected $ f s
|
|
|
|
-- | These "E" variants of the above are suitable for hledger's ErroringJournalParser parsers.
|
|
assertParseE
|
|
:: (HasCallStack, Eq a, Show a, Default st)
|
|
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
|
|
-> T.Text
|
|
-> Assertion
|
|
assertParseE parser input = do
|
|
let filepath = ""
|
|
eep <- runExceptT $
|
|
runParserT (evalStateT (parser <* eof) def) filepath input
|
|
case eep of
|
|
Left finalErr ->
|
|
let prettyErr = finalErrorBundlePretty $ attachSource filepath input finalErr
|
|
in assertFailure $ "parse error at " <> prettyErr
|
|
Right ep ->
|
|
either (assertFailure.(++"\n").("\nparse error at "++).customErrorBundlePretty)
|
|
(const $ return ())
|
|
ep
|
|
|
|
assertParseEqE
|
|
:: (Default st, Eq a, Show a, HasCallStack)
|
|
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
|
|
-> T.Text
|
|
-> a
|
|
-> Assertion
|
|
assertParseEqE parser input expected = assertParseEqOnE parser input id expected
|
|
|
|
assertParseEqOnE
|
|
:: (HasCallStack, Eq b, Show b, Default st)
|
|
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
|
|
-> T.Text
|
|
-> (a -> b)
|
|
-> b
|
|
-> Assertion
|
|
assertParseEqOnE parser input f expected = do
|
|
let filepath = ""
|
|
eep <- runExceptT $ runParserT (evalStateT (parser <* eof) def) filepath input
|
|
case eep of
|
|
Left finalErr ->
|
|
let prettyErr = finalErrorBundlePretty $ attachSource filepath input finalErr
|
|
in assertFailure $ "parse error at " <> prettyErr
|
|
Right ep ->
|
|
either (assertFailure . (++"\n") . ("\nparse error at "++) . customErrorBundlePretty)
|
|
(assertEqual "" expected . f)
|
|
ep
|
|
|
|
assertParseErrorE
|
|
:: (Default st, Eq a, Show a, HasCallStack)
|
|
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
|
|
-> T.Text
|
|
-> String
|
|
-> Assertion
|
|
assertParseErrorE parser input errstr = do
|
|
let filepath = ""
|
|
eep <- runExceptT $ runParserT (evalStateT parser def) filepath input
|
|
case eep of
|
|
Left finalErr -> do
|
|
let prettyErr = finalErrorBundlePretty $ attachSource filepath input finalErr
|
|
if errstr `isInfixOf` prettyErr
|
|
then return ()
|
|
else assertFailure $ "\nparse error is not as expected:\n" ++ prettyErr ++ "\n"
|
|
Right ep -> case ep of
|
|
Right v -> assertFailure $ "\nparse succeeded unexpectedly, producing:\n" ++ pshow v ++ "\n"
|
|
Left e -> do
|
|
let e' = customErrorBundlePretty e
|
|
if errstr `isInfixOf` e'
|
|
then return ()
|
|
else assertFailure $ "\nparse error is not as expected:\n" ++ e' ++ "\n"
|