test: refactor, document, organise easytests, port amountp tests (#812)
This commit is contained in:
		
							parent
							
								
									717a24a76d
								
							
						
					
					
						commit
						50d666d5a0
					
				| @ -1,14 +1,20 @@ | |||||||
|  | {-# LANGUAGE OverloadedStrings #-} | ||||||
|  | 
 | ||||||
| module Hledger ( | module Hledger ( | ||||||
|   module X |   module X | ||||||
|  ,tests_Hledger |  ,tests_Hledger | ||||||
|  |  ,Hledger.easytests | ||||||
| ) | ) | ||||||
| where | where | ||||||
| import           Test.HUnit | import           Test.HUnit | ||||||
|  | import           EasyTest | ||||||
| 
 | 
 | ||||||
| import           Hledger.Data    as X | import           Hledger.Data    as X hiding (easytests) | ||||||
| import           Hledger.Query   as X | import qualified Hledger.Data    (easytests) | ||||||
| import           Hledger.Read    as X hiding (samplejournal) | import           Hledger.Read    as X hiding (samplejournal, easytests) | ||||||
|  | import qualified Hledger.Read    (easytests) | ||||||
| import           Hledger.Reports as X | import           Hledger.Reports as X | ||||||
|  | import           Hledger.Query   as X | ||||||
| import           Hledger.Utils   as X | import           Hledger.Utils   as X | ||||||
| 
 | 
 | ||||||
| tests_Hledger = TestList | tests_Hledger = TestList | ||||||
| @ -19,3 +25,8 @@ tests_Hledger = TestList | |||||||
|     ,tests_Hledger_Reports |     ,tests_Hledger_Reports | ||||||
|     ,tests_Hledger_Utils |     ,tests_Hledger_Utils | ||||||
|     ] |     ] | ||||||
|  | 
 | ||||||
|  | easytests = scope "Hledger" $ tests [ | ||||||
|  |    Hledger.Data.easytests | ||||||
|  |   ,Hledger.Read.easytests | ||||||
|  |   ] | ||||||
|  | |||||||
| @ -69,6 +69,7 @@ module Hledger.Data.Journal ( | |||||||
|   -- * Tests |   -- * Tests | ||||||
|   samplejournal, |   samplejournal, | ||||||
|   tests_Hledger_Data_Journal, |   tests_Hledger_Data_Journal, | ||||||
|  |   easytests, | ||||||
| ) | ) | ||||||
| where | where | ||||||
| import Control.Applicative (Const(..)) | import Control.Applicative (Const(..)) | ||||||
| @ -91,6 +92,7 @@ import Data.Ord | |||||||
| import qualified Data.Semigroup as Sem | import qualified Data.Semigroup as Sem | ||||||
| import Data.Text (Text) | import Data.Text (Text) | ||||||
| import qualified Data.Text as T | import qualified Data.Text as T | ||||||
|  | import EasyTest | ||||||
| import Safe (headMay, headDef) | import Safe (headMay, headDef) | ||||||
| import Data.Time.Calendar | import Data.Time.Calendar | ||||||
| import Data.Tree | import Data.Tree | ||||||
| @ -1085,3 +1087,24 @@ tests_Hledger_Data_Journal = TestList $ | |||||||
|   --   journalBalanceSheetAccountNames j `is` ["assets","assets:a","equity","equity:q","equity:q:qq","liabilities","liabilities:l"] |   --   journalBalanceSheetAccountNames j `is` ["assets","assets:a","equity","equity:q","equity:q:qq","liabilities","liabilities:l"] | ||||||
|   --   journalProfitAndLossAccountNames j `is` ["expenses","expenses:e","income","income:i"] |   --   journalProfitAndLossAccountNames j `is` ["expenses","expenses:e","income","income:i"] | ||||||
|  ] |  ] | ||||||
|  | 
 | ||||||
|  | easytests = scope "Journal" $ tests [ | ||||||
|  |   scope "standard account types" $ do | ||||||
|  |     let | ||||||
|  |       j = samplejournal | ||||||
|  |       journalAccountNamesMatching :: Query -> Journal -> [AccountName] | ||||||
|  |       journalAccountNamesMatching q = filter (q `matchesAccount`) . journalAccountNames | ||||||
|  |       namesfrom qfunc = journalAccountNamesMatching (qfunc j) j | ||||||
|  |     tests | ||||||
|  |       [ scope "assets" $ | ||||||
|  |         expectEq (namesfrom journalAssetAccountQuery)     ["assets","assets:bank","assets:bank:checking","assets:bank:saving","assets:cash"] | ||||||
|  |       , scope "liabilities" $ | ||||||
|  |         expectEq (namesfrom journalLiabilityAccountQuery) ["liabilities","liabilities:debts"] | ||||||
|  |       , scope "equity" $ | ||||||
|  |         expectEq (namesfrom journalEquityAccountQuery)    [] | ||||||
|  |       , scope "income" $ | ||||||
|  |         expectEq (namesfrom journalIncomeAccountQuery)    ["income","income:gifts","income:salary"] | ||||||
|  |       , scope "expenses" $ | ||||||
|  |         expectEq (namesfrom journalExpenseAccountQuery)   ["expenses","expenses:food","expenses:supplies"] | ||||||
|  |       ] | ||||||
|  |   ] | ||||||
|  | |||||||
| @ -31,6 +31,7 @@ module Hledger.Read ( | |||||||
|   -- * Tests |   -- * Tests | ||||||
|   samplejournal, |   samplejournal, | ||||||
|   tests_Hledger_Read, |   tests_Hledger_Read, | ||||||
|  |   easytests, | ||||||
| 
 | 
 | ||||||
| ) where | ) where | ||||||
| 
 | 
 | ||||||
| @ -44,6 +45,7 @@ import Data.Ord | |||||||
| import Data.Text (Text) | import Data.Text (Text) | ||||||
| import qualified Data.Text as T | import qualified Data.Text as T | ||||||
| import Data.Time (Day) | import Data.Time (Day) | ||||||
|  | import EasyTest | ||||||
| import Safe | import Safe | ||||||
| import System.Directory (doesFileExist, getHomeDirectory) | import System.Directory (doesFileExist, getHomeDirectory) | ||||||
| import System.Environment (getEnv) | import System.Environment (getEnv) | ||||||
| @ -55,7 +57,8 @@ import Text.Printf | |||||||
| 
 | 
 | ||||||
| import Hledger.Data.Dates (getCurrentDay, parsedate, showDate) | import Hledger.Data.Dates (getCurrentDay, parsedate, showDate) | ||||||
| import Hledger.Data.Types | import Hledger.Data.Types | ||||||
| import Hledger.Read.Common | import Hledger.Read.Common hiding (easytests) | ||||||
|  | import qualified Hledger.Read.Common (easytests) | ||||||
| import qualified Hledger.Read.JournalReader   as JournalReader | import qualified Hledger.Read.JournalReader   as JournalReader | ||||||
| -- import qualified Hledger.Read.LedgerReader    as LedgerReader | -- import qualified Hledger.Read.LedgerReader    as LedgerReader | ||||||
| import qualified Hledger.Read.TimedotReader   as TimedotReader | import qualified Hledger.Read.TimedotReader   as TimedotReader | ||||||
| @ -360,3 +363,7 @@ tests_Hledger_Read = TestList $ | |||||||
|     either error' (assertBool "journalp parsing an empty file should give an empty journal" . null . jtxns) jE |     either error' (assertBool "journalp parsing an empty file should give an empty journal" . null . jtxns) jE | ||||||
| 
 | 
 | ||||||
|   ] |   ] | ||||||
|  | 
 | ||||||
|  | easytests = scope "Read" $ tests [ | ||||||
|  |   Hledger.Read.Common.easytests | ||||||
|  |   ] | ||||||
|  | |||||||
| @ -92,7 +92,8 @@ module Hledger.Read.Common ( | |||||||
|   singlespacep, |   singlespacep, | ||||||
| 
 | 
 | ||||||
|   -- * tests |   -- * tests | ||||||
|   tests_Hledger_Read_Common |   tests_Hledger_Read_Common, | ||||||
|  |   Hledger.Read.Common.easytests | ||||||
| ) | ) | ||||||
| where | where | ||||||
| --- * imports | --- * imports | ||||||
| @ -118,12 +119,13 @@ import Data.Time.Calendar | |||||||
| import Data.Time.LocalTime | import Data.Time.LocalTime | ||||||
| import System.Time (getClockTime) | import System.Time (getClockTime) | ||||||
| import Test.HUnit | import Test.HUnit | ||||||
|  | import EasyTest hiding (char, char') | ||||||
| import Text.Megaparsec | import Text.Megaparsec | ||||||
| import Text.Megaparsec.Char | import Text.Megaparsec.Char | ||||||
| import Text.Megaparsec.Char.Lexer (decimal) | import Text.Megaparsec.Char.Lexer (decimal) | ||||||
| import Text.Megaparsec.Custom | import Text.Megaparsec.Custom | ||||||
| 
 | 
 | ||||||
| import Hledger.Data | import Hledger.Data hiding (easytests) | ||||||
| import Hledger.Utils | import Hledger.Utils | ||||||
| 
 | 
 | ||||||
| -- $setup | -- $setup | ||||||
| @ -589,18 +591,6 @@ amountwithoutpricep = do | |||||||
|           Left errMsg -> uncurry parseErrorAtRegion posRegion errMsg |           Left errMsg -> uncurry parseErrorAtRegion posRegion errMsg | ||||||
|           Right res -> pure res |           Right res -> pure res | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| test_amountp = TestCase $ do |  | ||||||
|     assertParseEqual' (parseWithState mempty amountp "$47.18") (usd 47.18) |  | ||||||
|     assertParseEqual' (parseWithState mempty amountp "$1.") (usd 1 `withPrecision` 0) |  | ||||||
| -- TODO |  | ||||||
| --    assertParseEqual'' "amount with unit price" |  | ||||||
| --      (parseWithState mempty amountp "$10 @ €0.5") |  | ||||||
| --      (usd 10 `withPrecision` 0 `at` (eur 0.5 `withPrecision` 1)) |  | ||||||
| --    assertParseEqual'' "amount with total price" |  | ||||||
| --     (parseWithState mempty amountp "$10 @@ €5") |  | ||||||
| --     (usd 10 `withPrecision` 0 @@ (eur 5 `withPrecision` 0)) |  | ||||||
| 
 |  | ||||||
| -- | Parse an amount from a string, or get an error. | -- | Parse an amount from a string, or get an error. | ||||||
| amountp' :: String -> Amount | amountp' :: String -> Amount | ||||||
| amountp' s = | amountp' s = | ||||||
| @ -1250,4 +1240,15 @@ match' p = do | |||||||
|   (!txt, p) <- match p |   (!txt, p) <- match p | ||||||
|   pure (txt, p) |   pure (txt, p) | ||||||
| 
 | 
 | ||||||
| tests_Hledger_Read_Common = TestList [test_spaceandamountormissingp, test_amountp] | tests_Hledger_Read_Common = TestList [ | ||||||
|  |   test_spaceandamountormissingp | ||||||
|  |   ] | ||||||
|  | 
 | ||||||
|  | easytests = scope "Common" $ tests [ | ||||||
|  |   scope "amountp" $ tests [ | ||||||
|  |     scope "basic"                  $ expectParseEq amountp "$47.18"     (usd 47.18) | ||||||
|  |    ,scope "ends with decimal mark" $ expectParseEq amountp "$1."        (usd 1  `withPrecision` 0) | ||||||
|  | --   ,scope "with unit price"        $ expectParseEq amountp "$10 @ €0.5" (usd 10 `withPrecision` 0 `at` (eur 0.5 `withPrecision` 1))  | ||||||
|  | --   ,scope "with total price"       $ expectParseEq amountp "$10 @@ €5"  (usd 10 `withPrecision` 0 @@ (eur 5 `withPrecision` 0)) | ||||||
|  |     ] | ||||||
|  |   ] | ||||||
|  | |||||||
| @ -1,23 +1,37 @@ | |||||||
|  | {-# LANGUAGE OverloadedStrings #-} | ||||||
|  | {-# LANGUAGE ScopedTypeVariables #-} | ||||||
|  | 
 | ||||||
| module Hledger.Utils.Test where | module Hledger.Utils.Test where | ||||||
| 
 | 
 | ||||||
|  | import Control.Exception | ||||||
|  | import Control.Monad | ||||||
| import Data.Functor.Identity | import Data.Functor.Identity | ||||||
| import Test.HUnit | import Data.List | ||||||
|  | import qualified Data.Text as T | ||||||
|  | import EasyTest | ||||||
|  | import Safe  | ||||||
|  | import System.Exit | ||||||
|  | import System.IO | ||||||
|  | import Test.HUnit as HUnit | ||||||
| import Text.Megaparsec | import Text.Megaparsec | ||||||
|  | 
 | ||||||
| import Hledger.Utils.Debug (pshow) | import Hledger.Utils.Debug (pshow) | ||||||
|  | import Hledger.Utils.Parse (parseWithState) | ||||||
|  | import Hledger.Utils.UTF8IOCompat (error') | ||||||
| 
 | 
 | ||||||
| -- | Get a Test's label, or the empty string. | -- | Get a Test's label, or the empty string. | ||||||
| testName :: Test -> String | testName :: HUnit.Test -> String | ||||||
| testName (TestLabel n _) = n | testName (TestLabel n _) = n | ||||||
| testName _ = "" | testName _ = "" | ||||||
| 
 | 
 | ||||||
| -- | Flatten a Test containing TestLists into a list of single tests. | -- | Flatten a Test containing TestLists into a list of single tests. | ||||||
| flattenTests :: Test -> [Test] | flattenTests :: HUnit.Test -> [HUnit.Test] | ||||||
| flattenTests (TestLabel _ t@(TestList _)) = flattenTests t | flattenTests (TestLabel _ t@(TestList _)) = flattenTests t | ||||||
| flattenTests (TestList ts) = concatMap flattenTests ts | flattenTests (TestList ts) = concatMap flattenTests ts | ||||||
| flattenTests t = [t] | flattenTests t = [t] | ||||||
| 
 | 
 | ||||||
| -- | Filter TestLists in a Test, recursively, preserving the structure. | -- | Filter TestLists in a Test, recursively, preserving the structure. | ||||||
| filterTests :: (Test -> Bool) -> Test -> Test | filterTests :: (HUnit.Test -> Bool) -> HUnit.Test -> HUnit.Test | ||||||
| filterTests p (TestLabel l ts) = TestLabel l (filterTests p ts) | filterTests p (TestLabel l ts) = TestLabel l (filterTests p ts) | ||||||
| filterTests p (TestList ts) = TestList $ filter (any p . flattenTests) $ map (filterTests p) ts | filterTests p (TestList ts) = TestList $ filter (any p . flattenTests) $ map (filterTests p) ts | ||||||
| filterTests _ t = t | filterTests _ t = t | ||||||
| @ -58,3 +72,100 @@ assertParseEqual'' label parse expected = | |||||||
| printParseError :: (Show a) => a -> IO () | printParseError :: (Show a) => a -> IO () | ||||||
| printParseError e = do putStr "parse error at "; print e | printParseError e = do putStr "parse error at "; print e | ||||||
| 
 | 
 | ||||||
|  | -- | Run some easytests, returning True if there was a problem. Catches ExitCode. | ||||||
|  | -- With arguments, runs only tests in the scope named by the first argument | ||||||
|  | -- (case sensitive).  | ||||||
|  | -- If there is a second argument, it should be an integer and will be used | ||||||
|  | -- as the seed for randomness.  | ||||||
|  | runEasyTests :: [String] -> EasyTest.Test () -> IO Bool | ||||||
|  | runEasyTests args easytests = (do | ||||||
|  |   case args of | ||||||
|  |     []    -> EasyTest.run easytests | ||||||
|  |     [a]   -> EasyTest.runOnly (T.pack a) easytests | ||||||
|  |     a:b:_ -> do | ||||||
|  |       case readMay b :: Maybe Int of | ||||||
|  |         Nothing   -> error' "the second argument should be an integer (a seed for easytest)" | ||||||
|  |         Just seed -> EasyTest.rerunOnly seed (T.pack a) easytests | ||||||
|  |   return False | ||||||
|  |   ) | ||||||
|  |   `catch` (\(_::ExitCode) -> return True) | ||||||
|  | 
 | ||||||
|  | expectParseEq parser input expected = do | ||||||
|  |   let ep = runIdentity $ parseWithState mempty parser input | ||||||
|  |   scope "parse succeeded" $ expectRight ep | ||||||
|  |   let Right p = ep | ||||||
|  |   scope "parse result" $ expectEq p expected | ||||||
|  | 
 | ||||||
|  | -- | Run some hunit tests, returning True if there was a problem. | ||||||
|  | -- With arguments, runs only tests whose names contain the first argument | ||||||
|  | -- (case sensitive).  | ||||||
|  | runHunitTests :: [String] -> HUnit.Test -> IO Bool | ||||||
|  | runHunitTests args hunittests = do | ||||||
|  |   let ts =  | ||||||
|  |         (case args of | ||||||
|  |           a:_ -> filterTests ((a `isInfixOf`) . testName) | ||||||
|  |           _   -> id | ||||||
|  |         ) hunittests | ||||||
|  |   results <- liftM (fst . flip (,) 0) $ runTestTTStdout ts | ||||||
|  |   return $ errors results > 0 || failures results > 0 | ||||||
|  |   where | ||||||
|  |     -- | Like runTestTT but prints to stdout. | ||||||
|  |     runTestTTStdout t = do | ||||||
|  |       (counts, 0) <- HUnit.runTestText (putTextToHandle stdout True) t | ||||||
|  |       return counts | ||||||
|  | 
 | ||||||
|  | --    matchedTests opts ts  | ||||||
|  | --      | tree_ $ reportopts_ opts =  | ||||||
|  | --        -- Tests, filtered by any arguments, in a flat list with simple names. | ||||||
|  | --        TestList $ | ||||||
|  | --          filter (matchesAccount (queryFromOpts nulldate $ reportopts_ opts) . T.pack . testName) $  | ||||||
|  | --          flattenTests ts | ||||||
|  | --      | otherwise =  | ||||||
|  | --        -- Tests, filtered by any arguments, in the original suites with hierarchical names. | ||||||
|  | --        filterTests (matchesAccount (queryFromOpts nulldate $ reportopts_ opts) . T.pack . testName)  | ||||||
|  | --        ts | ||||||
|  | 
 | ||||||
|  | -- -- | Like runTestTT but can optionally not erase progress output. | ||||||
|  | -- runTestTT' verbose t = do | ||||||
|  | --   (counts, 0) <- runTestText' (f stderr True) t | ||||||
|  | --   return counts | ||||||
|  | --   where f | verbose   = putTextToHandle' | ||||||
|  | --           | otherwise = putTextToHandle | ||||||
|  | 
 | ||||||
|  | -- -- | Like runTestText but also prints test names if any. | ||||||
|  | -- runTestText' :: PutText st -> Test -> IO (Counts, st) | ||||||
|  | -- runTestText' _pt _t@(TestLabel _label _) = error "HERE"  -- hPutStrLn stderr label >> runTestText pt t | ||||||
|  | -- runTestText' pt t = runTestText pt t | ||||||
|  | 
 | ||||||
|  | -- -- runTestText' (PutText put us0) t = do | ||||||
|  | -- --   (counts', us1) <- trace "XXX" $ performTest reportStart reportError reportFailure us0 t | ||||||
|  | -- --   us2 <- put (showCounts counts' ++ " :::: " ++ testName t) True us1 | ||||||
|  | -- --   return (counts', us2) | ||||||
|  | -- --  where | ||||||
|  | -- --   reportStart ss us = put (showCounts (counts ss)) False us | ||||||
|  | -- --   reportError   = reportProblem "Error:"   "Error in:   " | ||||||
|  | -- --   reportFailure = reportProblem "Failure:" "Failure in: " | ||||||
|  | -- --   reportProblem p0 p1 loc msg ss us = put line True us | ||||||
|  | -- --    where line  = "### " ++ kind ++ path' ++ "\n" ++ formatLocation loc ++ msg | ||||||
|  | -- --          kind  = if null path' then p0 else p1 | ||||||
|  | -- --          path' = showPath (path ss) | ||||||
|  | 
 | ||||||
|  | -- -- formatLocation :: Maybe SrcLoc -> String | ||||||
|  | -- -- formatLocation Nothing = "" | ||||||
|  | -- -- formatLocation (Just loc) = srcLocFile loc ++ ":" ++ show (srcLocStartLine loc) ++ "\n" | ||||||
|  | 
 | ||||||
|  | -- -- | Like putTextToHandle but does not erase progress lines. | ||||||
|  | -- putTextToHandle' | ||||||
|  | --     :: Handle | ||||||
|  | --     -> Bool -- ^ Write progress lines to handle? | ||||||
|  | --     -> PutText Int | ||||||
|  | -- putTextToHandle' handle showProgress = PutText put initCnt | ||||||
|  | --  where | ||||||
|  | --   initCnt = if showProgress then 0 else -1 | ||||||
|  | --   put line pers (-1) = do when pers (hPutStrLn handle line); return (-1) | ||||||
|  | --   put line True  cnt = do hPutStrLn handle (erase cnt ++ line); return 0 | ||||||
|  | --   put line False _   = do hPutStr handle ('\n' : line); return (length line) | ||||||
|  | --     -- The "erasing" strategy with a single '\r' relies on the fact that the | ||||||
|  | --     -- lengths of successive summary lines are monotonically nondecreasing. | ||||||
|  | --   erase cnt = if cnt == 0 then "" else "\r" ++ replicate cnt ' ' ++ "\r" | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| -- | -- | ||||||
| -- see: https://github.com/sol/hpack | -- see: https://github.com/sol/hpack | ||||||
| -- | -- | ||||||
| -- hash: afb7a8b69691588056deb8465bec29cc05326218651e83f7f47d169e4c46aa95 | -- hash: b52d450888004e007b3689cfe42d916ab8e8af3bc91a6a374ff022a719e86611 | ||||||
| 
 | 
 | ||||||
| name:           hledger-lib | name:           hledger-lib | ||||||
| version:        1.10.99 | version:        1.10.99 | ||||||
| @ -117,6 +117,7 @@ library | |||||||
|     , data-default >=0.5 |     , data-default >=0.5 | ||||||
|     , deepseq |     , deepseq | ||||||
|     , directory |     , directory | ||||||
|  |     , easytest | ||||||
|     , extra |     , extra | ||||||
|     , filepath |     , filepath | ||||||
|     , hashtables >=1.2.3.1 |     , hashtables >=1.2.3.1 | ||||||
| @ -215,6 +216,7 @@ test-suite doctests | |||||||
|     , deepseq |     , deepseq | ||||||
|     , directory |     , directory | ||||||
|     , doctest >=0.8 |     , doctest >=0.8 | ||||||
|  |     , easytest | ||||||
|     , extra |     , extra | ||||||
|     , filepath |     , filepath | ||||||
|     , hashtables >=1.2.3.1 |     , hashtables >=1.2.3.1 | ||||||
| @ -411,6 +413,7 @@ test-suite hunittests | |||||||
|     , data-default >=0.5 |     , data-default >=0.5 | ||||||
|     , deepseq |     , deepseq | ||||||
|     , directory |     , directory | ||||||
|  |     , easytest | ||||||
|     , extra |     , extra | ||||||
|     , filepath |     , filepath | ||||||
|     , hashtables >=1.2.3.1 |     , hashtables >=1.2.3.1 | ||||||
|  | |||||||
| @ -52,6 +52,7 @@ dependencies: | |||||||
| - Decimal | - Decimal | ||||||
| - deepseq | - deepseq | ||||||
| - directory | - directory | ||||||
|  | - easytest | ||||||
| - filepath | - filepath | ||||||
| - hashtables >=1.2.3.1 | - hashtables >=1.2.3.1 | ||||||
| - megaparsec >=6.4.1 | - megaparsec >=6.4.1 | ||||||
| @ -177,4 +178,3 @@ tests: | |||||||
|     source-dirs: tests |     source-dirs: tests | ||||||
|     dependencies: |     dependencies: | ||||||
|     - hledger-lib |     - hledger-lib | ||||||
|     - easytest |  | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ hledger's built-in commands, and helpers for printing the commands list. | |||||||
| -} | -} | ||||||
| 
 | 
 | ||||||
| {-# LANGUAGE OverloadedStrings #-} | {-# LANGUAGE OverloadedStrings #-} | ||||||
| {-# LANGUAGE ScopedTypeVariables #-} |  | ||||||
| {-# LANGUAGE QuasiQuotes #-} | {-# LANGUAGE QuasiQuotes #-} | ||||||
| {-# LANGUAGE CPP #-} | {-# LANGUAGE CPP #-} | ||||||
| 
 | 
 | ||||||
| @ -38,7 +37,6 @@ module Hledger.Cli.Commands ( | |||||||
| where | where | ||||||
| 
 | 
 | ||||||
| -- import Control.Concurrent | -- import Control.Concurrent | ||||||
| import Control.Exception |  | ||||||
| import Control.Monad | import Control.Monad | ||||||
| import Data.Default | import Data.Default | ||||||
| -- import Data.CallStack | -- import Data.CallStack | ||||||
| @ -53,9 +51,7 @@ import qualified Data.Text as T | |||||||
| import Data.Time.Calendar | import Data.Time.Calendar | ||||||
| import System.Console.CmdArgs.Explicit as C | import System.Console.CmdArgs.Explicit as C | ||||||
| import System.Exit | import System.Exit | ||||||
| import System.IO (stdout) | import Test.HUnit as HUnit | ||||||
| import EasyTest |  | ||||||
| import Test.HUnit |  | ||||||
| 
 | 
 | ||||||
| import Hledger | import Hledger | ||||||
| import Hledger.Cli.CliOptions | import Hledger.Cli.CliOptions | ||||||
| @ -219,141 +215,46 @@ commandsFromCommandsList s = | |||||||
|   concatMap (splitOn "|") [w | ' ':l <- lines s, let w:_ = words l] |   concatMap (splitOn "|") [w | ' ':l <- lines s, let w:_ = words l] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | -- The test command, defined here for easy access to other modules' tests. | ||||||
| 
 | 
 | ||||||
| -- The test command, defined here so it can access other commands' tests. | testmode = hledgerCommandMode | ||||||
|  |   [here| test | ||||||
|  | Run the unit tests built in to hledger-lib and hledger,  | ||||||
|  | printing results on stdout and exiting with success or failure. | ||||||
| 
 | 
 | ||||||
| testmode = (defCommandMode ["test"]) { | Tests are run in two batches: easytest-based and hunit-based tests. | ||||||
|   modeHelp = "run built-in self-tests" | If any test fails or gives an error, the exit code will be non-zero. | ||||||
|  ,modeArgs = ([], Just $ argsFlag "[REGEXPS]") |  | ||||||
|  ,modeGroupFlags = Group { |  | ||||||
|      groupUnnamed = [] |  | ||||||
|     ,groupHidden = [ |  | ||||||
|         flagNone ["tree"] (\opts -> setboolopt "tree" opts) "show tests hierarchically" |  | ||||||
|        ,flagNone ["flat"] (\opts -> setboolopt "flat" opts) "show tests as a flat list" |  | ||||||
|       ] |  | ||||||
|     ,groupNamed = [generalflagsgroup3] |  | ||||||
|     } |  | ||||||
|  } |  | ||||||
| 
 | 
 | ||||||
| -- | Run some or all hledger-lib and hledger unit tests, and exit with success or failure. | If a pattern argument (case sensitive) is provided, only easytests  | ||||||
|  | in that scope and only hunit tests whose name contains it are run. | ||||||
|  | 
 | ||||||
|  | If a numeric second argument is provided, it will set the randomness | ||||||
|  | seed for easytests.  | ||||||
|  | 
 | ||||||
|  | FLAGS | ||||||
|  |   |] | ||||||
|  |   [] | ||||||
|  |   [generalflagsgroup3] | ||||||
|  |   [] | ||||||
|  |   ([], Just $ argsFlag "[TESTPATTERN] [SEED]") | ||||||
|  | 
 | ||||||
|  | -- | See testmode. | ||||||
| -- | -- | ||||||
| -- Unlike other hledger commands, this one does not operate on the user's Journal. | -- Unlike other hledger commands, this one does not operate on the user's Journal. | ||||||
| -- For ease of implementation the Journal parameter remains in the type signature,  | -- For ease of implementation the Journal parameter remains in the type signature.  | ||||||
| -- but it will raise an error if used. |  | ||||||
| testcmd :: CliOpts -> Journal -> IO () | testcmd :: CliOpts -> Journal -> IO () | ||||||
| testcmd opts _donotuse = do | testcmd opts _undefined = do  | ||||||
|  |   let args = words' $ query_ $ reportopts_ opts | ||||||
|   putStrLn "\n=== easytest tests: ===\n" |   putStrLn "\n=== easytest tests: ===\n" | ||||||
|   runEasyTests opts |   e1 <- runEasyTests args easytests | ||||||
| 
 |   when (not e1) $ putStr "\n" | ||||||
|   putStrLn "\n\n=== hunit tests: ===\n" |   putStrLn "=== hunit tests: ===\n" | ||||||
|   runHunitTests opts |   e2 <- runHunitTests args tests_Hledger_Cli_Commands | ||||||
|     -- hide exit exception output when running tests from ghci/ghcid |  | ||||||
|     `catch` (\(_::ExitCode) -> return ()) |  | ||||||
| 
 |  | ||||||
|   -- whitespace to separate test results from ghcid status |  | ||||||
|   putStrLn "" |   putStrLn "" | ||||||
|  |   if or [e1, e2] then exitFailure else exitSuccess | ||||||
| 
 | 
 | ||||||
| -- | Run some easytests. | -- collected hledger-lib + hledger hunit tests | ||||||
| -- XXX Just duplicates the ones in hledger-lib/tests/easytest.hs for now. |  | ||||||
| runEasyTests _opts = do |  | ||||||
|   run |  | ||||||
|   -- rerun "journal.standard account types.queries.assets" |  | ||||||
|   -- rerunOnly 2686786430487349354 "journal.standard account types.queries.assets" |  | ||||||
|     $ tests [ |  | ||||||
| 
 | 
 | ||||||
|       scope "journal.standard account types.queries" $ |  | ||||||
|         let |  | ||||||
|           j = samplejournal |  | ||||||
|           journalAccountNamesMatching :: Query -> Journal -> [AccountName] |  | ||||||
|           journalAccountNamesMatching q = filter (q `matchesAccount`) . journalAccountNames |  | ||||||
|           namesfrom qfunc = journalAccountNamesMatching (qfunc j) j |  | ||||||
|         in |  | ||||||
|           tests |  | ||||||
|             [ scope "assets" $ |  | ||||||
|               expectEq (namesfrom journalAssetAccountQuery)     ["assets","assets:bank","assets:bank:checking","assets:bank:saving","assets:cash"] |  | ||||||
|             , scope "liabilities" $ |  | ||||||
|               expectEq (namesfrom journalLiabilityAccountQuery) ["liabilities","liabilities:debts"] |  | ||||||
|             , scope "equity" $ |  | ||||||
|               expectEq (namesfrom journalEquityAccountQuery)    [] |  | ||||||
|             , scope "income" $ |  | ||||||
|               expectEq (namesfrom journalIncomeAccountQuery)    ["income","income:gifts","income:salary"] |  | ||||||
|             , scope "expenses" $ |  | ||||||
|               expectEq (namesfrom journalExpenseAccountQuery)   ["expenses","expenses:food","expenses:supplies"] |  | ||||||
|             ] |  | ||||||
| 
 |  | ||||||
|     ] |  | ||||||
| 
 |  | ||||||
| runHunitTests opts = do |  | ||||||
|   let ts =  |  | ||||||
|         (if tree_ $ reportopts_ opts then matchedTestsTree else matchedTestsFlat)  |  | ||||||
|           opts tests_Hledger_Cli_Commands |  | ||||||
|   results <- liftM (fst . flip (,) 0) $ runTestTTStdout ts |  | ||||||
|   if errors results > 0 || failures results > 0 |  | ||||||
|     then exitFailure |  | ||||||
|     else exitWith ExitSuccess |  | ||||||
| 
 |  | ||||||
| -- | Like runTestTT but prints to stdout. |  | ||||||
| runTestTTStdout t = do |  | ||||||
|   (counts, 0) <- runTestText (putTextToHandle stdout True) t |  | ||||||
|   return counts |  | ||||||
| 
 |  | ||||||
| -- -- | Like runTestTT but can optionally not erase progress output. |  | ||||||
| -- runTestTT' verbose t = do |  | ||||||
| --   (counts, 0) <- runTestText' (f stderr True) t |  | ||||||
| --   return counts |  | ||||||
| --   where f | verbose   = putTextToHandle' |  | ||||||
| --           | otherwise = putTextToHandle |  | ||||||
| 
 |  | ||||||
| -- -- | Like runTestText but also prints test names if any. |  | ||||||
| -- runTestText' :: PutText st -> Test -> IO (Counts, st) |  | ||||||
| -- runTestText' _pt _t@(TestLabel _label _) = error "HERE"  -- hPutStrLn stderr label >> runTestText pt t |  | ||||||
| -- runTestText' pt t = runTestText pt t |  | ||||||
| 
 |  | ||||||
| -- -- runTestText' (PutText put us0) t = do |  | ||||||
| -- --   (counts', us1) <- trace "XXX" $ performTest reportStart reportError reportFailure us0 t |  | ||||||
| -- --   us2 <- put (showCounts counts' ++ " :::: " ++ testName t) True us1 |  | ||||||
| -- --   return (counts', us2) |  | ||||||
| -- --  where |  | ||||||
| -- --   reportStart ss us = put (showCounts (counts ss)) False us |  | ||||||
| -- --   reportError   = reportProblem "Error:"   "Error in:   " |  | ||||||
| -- --   reportFailure = reportProblem "Failure:" "Failure in: " |  | ||||||
| -- --   reportProblem p0 p1 loc msg ss us = put line True us |  | ||||||
| -- --    where line  = "### " ++ kind ++ path' ++ "\n" ++ formatLocation loc ++ msg |  | ||||||
| -- --          kind  = if null path' then p0 else p1 |  | ||||||
| -- --          path' = showPath (path ss) |  | ||||||
| 
 |  | ||||||
| -- -- formatLocation :: Maybe SrcLoc -> String |  | ||||||
| -- -- formatLocation Nothing = "" |  | ||||||
| -- -- formatLocation (Just loc) = srcLocFile loc ++ ":" ++ show (srcLocStartLine loc) ++ "\n" |  | ||||||
| 
 |  | ||||||
| -- -- | Like putTextToHandle but does not erase progress lines. |  | ||||||
| -- putTextToHandle' |  | ||||||
| --     :: Handle |  | ||||||
| --     -> Bool -- ^ Write progress lines to handle? |  | ||||||
| --     -> PutText Int |  | ||||||
| -- putTextToHandle' handle showProgress = PutText put initCnt |  | ||||||
| --  where |  | ||||||
| --   initCnt = if showProgress then 0 else -1 |  | ||||||
| --   put line pers (-1) = do when pers (hPutStrLn handle line); return (-1) |  | ||||||
| --   put line True  cnt = do hPutStrLn handle (erase cnt ++ line); return 0 |  | ||||||
| --   put line False _   = do hPutStr handle ('\n' : line); return (length line) |  | ||||||
| --     -- The "erasing" strategy with a single '\r' relies on the fact that the |  | ||||||
| --     -- lengths of successive summary lines are monotonically nondecreasing. |  | ||||||
| --   erase cnt = if cnt == 0 then "" else "\r" ++ replicate cnt ' ' ++ "\r" |  | ||||||
| 
 |  | ||||||
| -- | All or pattern-matched tests, as a flat list to show simple names. |  | ||||||
| matchedTestsFlat opts = TestList .  |  | ||||||
|   filter (matchesAccount (queryFromOpts nulldate $ reportopts_ opts) . T.pack . testName) .  |  | ||||||
|   flattenTests |  | ||||||
| 
 |  | ||||||
| -- | All or pattern-matched tests, in the original suites to show hierarchical names. |  | ||||||
| matchedTestsTree opts =  |  | ||||||
|   filterTests (matchesAccount (queryFromOpts nulldate $ reportopts_ opts) . T.pack . testName)  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| -- collected hledger-lib + hledger unit tests |  | ||||||
| 
 |  | ||||||
| tests_Hledger_Cli_Commands :: Test.HUnit.Test |  | ||||||
| tests_Hledger_Cli_Commands = TestList [ | tests_Hledger_Cli_Commands = TestList [ | ||||||
|    tests_Hledger |    tests_Hledger | ||||||
|   ,tests_Hledger_Cli_CliOptions |   ,tests_Hledger_Cli_CliOptions | ||||||
|  | |||||||
| @ -787,14 +787,20 @@ With additional QUERY arguments, only transactions matching the query are consid | |||||||
| ## test | ## test | ||||||
| Run built-in unit tests. | Run built-in unit tests. | ||||||
| 
 | 
 | ||||||
| ```shell |  | ||||||
| $ hledger test |  | ||||||
| Cases: 74  Tried: 74  Errors: 0  Failures: 0 |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| This command runs hledger's built-in unit tests and displays a quick report. | This command runs hledger's built-in unit tests and displays a quick report. | ||||||
| With a regular expression argument, it selects only tests with matching names. | It's mainly used during development, but it's also nice to be able to | ||||||
| It's mainly used in development, but it's also nice to be able to | sanity-check your installed hledger executable at any time. | ||||||
| check your hledger executable for smoke at any time. | 
 | ||||||
|  | It runs the unit tests built in to hledger-lib and hledger,  | ||||||
|  | printing results on stdout and exiting with success or failure. | ||||||
|  | 
 | ||||||
|  | Tests are run in two batches: easytest-based and hunit-based tests. | ||||||
|  | If any test fails or gives an error, the exit code will be non-zero. | ||||||
|  | 
 | ||||||
|  | If a pattern argument (case sensitive) is provided, only easytests  | ||||||
|  | in that scope and only hunit tests whose name contains it are run. | ||||||
|  | 
 | ||||||
|  | If a numeric second argument is provided, it will set the randomness | ||||||
|  | seed for easytests.  | ||||||
| 
 | 
 | ||||||
| _include_(hledger_addons.m4.md) | _include_(hledger_addons.m4.md) | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user