1 {- |
    2 hledger's test suite. Most tests are HUnit-based, and defined in the
    3 @tests@ list below. These tests are built in to hledger and can be run at
    4 any time with @hledger test@.
    5 
    6 In addition, we have tests in doctest format, which can be run with @make
    7 doctest@ in the hledger source tree. These have some advantages:
    8 
    9 - easier to read and write than hunit, for functional/shell tests
   10 
   11 - easier to read multi-line output from failing tests
   12 
   13 - can also appear in, and test, docs
   14 
   15 and disadvantages:
   16 
   17 - not included in hledger's built-in tests
   18 
   19 - not platform independent
   20 
   21 Here are the hledger doctests (some may reappear in other modules as
   22 examples):
   23 
   24 Run a few with c++ ledger first:
   25 
   26 @
   27 $ ledger -f sample.ledger balance
   28                  $-1  assets
   29                   $1    bank:saving
   30                  $-2    cash
   31                   $2  expenses
   32                   $1    food
   33                   $1    supplies
   34                  $-2  income
   35                  $-1    gifts
   36                  $-1    salary
   37                   $1  liabilities:debts
   38 @
   39 
   40 @
   41 $ ledger -f sample.ledger balance o
   42                   $1  expenses:food
   43                  $-2  income
   44                  $-1    gifts
   45                  $-1    salary
   46 --------------------
   47                  $-1
   48 @
   49 
   50 Then hledger:
   51 
   52 @
   53 $ hledger -f sample.ledger balance
   54                  $-1  assets
   55                   $1    bank:saving
   56                  $-2    cash
   57                   $2  expenses
   58                   $1    food
   59                   $1    supplies
   60                  $-2  income
   61                  $-1    gifts
   62                  $-1    salary
   63                   $1  liabilities:debts
   64 @
   65 
   66 @
   67 $ hledger -f sample.ledger balance o
   68                   $1  expenses:food
   69                  $-2  income
   70                  $-1    gifts
   71                  $-1    salary
   72 --------------------
   73                  $-1
   74 @
   75 
   76 @
   77 $ hledger -f sample.ledger balance --depth 1
   78                  $-1  assets
   79                   $2  expenses
   80                  $-2  income
   81                   $1  liabilities
   82 @
   83 -}
   84 {-
   85 @
   86 $ printf "2009/1/1 a\n  b  1.1\n  c  -1\n" | runhaskell hledger.hs -f- reg 2>&1 ; true
   87 hledger.hs: could not balance this transaction, amounts do not add up to zero:
   88 2009/01/01 a
   89     b                                            1.1
   90     c                                             -1
   91 
   92 
   93 @
   94 
   95 @
   96 $ printf "2009/1/1 x\n  (virtual)  100\n  a  1\n  b\n" | runhaskell hledger.hs -f- print 2>&1 ; true
   97 2009/01/01 x
   98     (virtual)                                    100
   99     a                                              1
  100     b
  101 
  102 @
  103 
  104 Unicode input/output tests
  105 
  106 -- layout of the balance command with unicode names
  107 @
  108 $ printf "2009-01-01 проверка\n  τράπεζα  10 руб\n  नकद\n" | hledger -f - bal
  109               10 руб  τράπεζα
  110              -10 руб  नकद
  111 @
  112 
  113 -- layout of the register command with unicode names
  114 @
  115 $ printf "2009-01-01 проверка\n  τράπεζα  10 руб\n  नकद\n" | hledger -f - reg
  116 2009/01/01 проверка             τράπεζα                      10 руб       10 руб
  117                                 नकद                         -10 руб            0
  118 @
  119 
  120 -- layout of the print command with unicode names
  121 @
  122 $ printf "2009-01-01 проверка\n счёт:первый  1\n счёт:второй\n" | hledger -f - print
  123 2009/01/01 проверка
  124     счёт:первый                                    1
  125     счёт:второй
  126 
  127 @
  128 
  129 -- search for unicode account names
  130 @
  131 $ printf "2009-01-01 проверка\n  τράπεζα  10 руб\n  नकद\n" | hledger -f - reg τράπ
  132 2009/01/01 проверка             τράπεζα                      10 руб       10 руб
  133 @
  134 
  135 -- search for unicode descriptions (should choose only the first entry)
  136 @
  137 $ printf "2009-01-01 аура (cyrillic letters)\n  bank  10\n  cash\n2010-01-01 aypa (roman letters)\n  bank  20\n  cash\n" | hledger -f - reg desc:аура
  138 2009/01/01 аура (cyrillic let.. bank                             10           10
  139                                 cash                            -10            0
  140 @
  141 
  142 -- error message with unicode in ledger
  143 -- not implemented yet
  144 --@
  145 $ printf "2009-01-01 broken entry\n  дебит  1\n  кредит  -2\n" | hledger -f - 2>&1 ; true
  146 hledger: could not balance this transaction, amounts do not add up to zero:
  147 2009/01/01 broken entry
  148     дебит                                          1
  149     кредит                                        -2
  150 
  151 
  152 --@
  153 
  154 @
  155 $ printf "2009-01-01 x\n  a  2\n  b (b) b  -1\n  c\n" | hledger -f - print 2>&1; true
  156 2009/01/01 x
  157     a                                              2
  158     b (b) b                                       -1
  159     c
  160 
  161 @
  162 
  163 -}
  164 -- other test tools:
  165 -- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/test-framework
  166 -- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/HTF
  167 
  168 module Tests
  169 where
  170 import qualified Data.Map as Map
  171 import Data.Time.Format
  172 import Locale (defaultTimeLocale)
  173 import Text.ParserCombinators.Parsec
  174 import Test.HUnit
  175 import Test.HUnit.Tools (assertRaises, runVerboseTests)
  176 
  177 import Commands.All
  178 import Ledger
  179 import Options
  180 import Utils
  181 
  182 
  183 runtests opts args = runner flattests
  184     where
  185       runner | (Verbose `elem` opts) = runVerboseTests
  186              | otherwise = \t -> runTestTT t >>= return . (flip (,) 0)
  187       flattests = TestList $ filter matchname $ concatMap tflatten tests
  188       deeptests = tfilter matchname $ TestList tests
  189       matchname = matchpats args . tname
  190 
  191 -- | Get a Test's label, or the empty string.
  192 tname :: Test -> String
  193 tname (TestLabel n _) = n
  194 tname _ = ""
  195 
  196 -- | Flatten a Test containing TestLists into a list of single tests.
  197 tflatten :: Test -> [Test]
  198 tflatten (TestLabel _ t@(TestList _)) = tflatten t
  199 tflatten (TestList ts) = concatMap tflatten ts
  200 tflatten t = [t]
  201 
  202 -- | Filter TestLists in a Test, recursively, preserving the structure.
  203 tfilter :: (Test -> Bool) -> Test -> Test
  204 tfilter p (TestLabel l ts) = TestLabel l (tfilter p ts)
  205 tfilter p (TestList ts) = TestList $ filter (any p . tflatten) $ map (tfilter p) ts
  206 tfilter _ t = t
  207 
  208 -- | Simple way to assert something is some expected value, with no label.
  209 is :: (Eq a, Show a) => a -> a -> Assertion
  210 a `is` e = assertEqual "" e a
  211 
  212 -- | Assert a parse result is some expected value, or print a parse error.
  213 parseis :: (Show a, Eq a) => (Either ParseError a) -> a -> Assertion
  214 parse `parseis` expected = either printParseError (`is` expected) parse
  215 
  216 parseWithCtx :: GenParser Char LedgerFileCtx a -> String -> Either ParseError a
  217 parseWithCtx p ts = runParser p emptyCtx "" ts
  218 
  219 ------------------------------------------------------------------------------
  220 -- | Tests for any function or topic. Mostly ordered by test name.
  221 tests :: [Test]
  222 tests = [
  223 
  224    "account directive" ~: 
  225    let sameParse str1 str2 = do l1 <- rawLedgerFromString str1
  226                                 l2 <- rawLedgerFromString str2
  227                                 l1 `is` l2
  228    in TestList
  229    [
  230     "account directive 1" ~: sameParse 
  231                           "2008/12/07 One\n  test:from  $-1\n  test:to  $1\n"
  232                           "!account test\n2008/12/07 One\n  from  $-1\n  to  $1\n"
  233 
  234    ,"account directive 2" ~: sameParse 
  235                            "2008/12/07 One\n  test:foo:from  $-1\n  test:foo:to  $1\n"
  236                            "!account test\n!account foo\n2008/12/07 One\n  from  $-1\n  to  $1\n"
  237 
  238    ,"account directive 3" ~: sameParse 
  239                            "2008/12/07 One\n  test:from  $-1\n  test:to  $1\n"
  240                            "!account test\n!account foo\n!end\n2008/12/07 One\n  from  $-1\n  to  $1\n"
  241 
  242    ,"account directive 4" ~: sameParse 
  243                            ("2008/12/07 One\n  alpha  $-1\n  beta  $1\n" ++
  244                             "!account outer\n2008/12/07 Two\n  aigh  $-2\n  bee  $2\n" ++
  245                             "!account inner\n2008/12/07 Three\n  gamma  $-3\n  delta  $3\n" ++
  246                             "!end\n2008/12/07 Four\n  why  $-4\n  zed  $4\n" ++
  247                             "!end\n2008/12/07 Five\n  foo  $-5\n  bar  $5\n"
  248                            )
  249                            ("2008/12/07 One\n  alpha  $-1\n  beta  $1\n" ++
  250                             "2008/12/07 Two\n  outer:aigh  $-2\n  outer:bee  $2\n" ++
  251                             "2008/12/07 Three\n  outer:inner:gamma  $-3\n  outer:inner:delta  $3\n" ++
  252                             "2008/12/07 Four\n  outer:why  $-4\n  outer:zed  $4\n" ++
  253                             "2008/12/07 Five\n  foo  $-5\n  bar  $5\n"
  254                            )
  255    ]
  256 
  257   ,"accountnames" ~: do
  258     accountnames ledger7 `is`
  259      ["assets","assets:cash","assets:checking","assets:saving","equity","equity:opening balances",
  260       "expenses","expenses:food","expenses:food:dining","expenses:phone","expenses:vacation",
  261       "liabilities","liabilities:credit cards","liabilities:credit cards:discover"]
  262 
  263   ,"accountNameTreeFrom" ~: do
  264     accountNameTreeFrom ["a"]       `is` Node "top" [Node "a" []]
  265     accountNameTreeFrom ["a","b"]   `is` Node "top" [Node "a" [], Node "b" []]
  266     accountNameTreeFrom ["a","a:b"] `is` Node "top" [Node "a" [Node "a:b" []]]
  267     accountNameTreeFrom ["a:b:c"]   `is` Node "top" [Node "a" [Node "a:b" [Node "a:b:c" []]]]
  268 
  269   ,"amount arithmetic" ~: do
  270     let a1 = dollars 1.23
  271     let a2 = Amount (comm "$") (-1.23) Nothing
  272     let a3 = Amount (comm "$") (-1.23) Nothing
  273     (a1 + a2) `is` Amount (comm "$") 0 Nothing
  274     (a1 + a3) `is` Amount (comm "$") 0 Nothing
  275     (a2 + a3) `is` Amount (comm "$") (-2.46) Nothing
  276     (a3 + a3) `is` Amount (comm "$") (-2.46) Nothing
  277     (sum [a2,a3]) `is` Amount (comm "$") (-2.46) Nothing
  278     (sum [a3,a3]) `is` Amount (comm "$") (-2.46) Nothing
  279     (sum [a1,a2,a3,-a3]) `is` Amount (comm "$") 0 Nothing
  280 
  281   ,"balance report tests" ~:
  282    let (opts,args) `gives` es = do 
  283         l <- sampleledgerwithopts opts args
  284         showBalanceReport opts args l `is` unlines es
  285    in TestList
  286    [
  287 
  288     "balance report with no args" ~:
  289     ([], []) `gives`
  290     ["                 $-1  assets"
  291     ,"                  $1    bank:saving"
  292     ,"                 $-2    cash"
  293     ,"                  $2  expenses"
  294     ,"                  $1    food"
  295     ,"                  $1    supplies"
  296     ,"                 $-2  income"
  297     ,"                 $-1    gifts"
  298     ,"                 $-1    salary"
  299     ,"                  $1  liabilities:debts"
  300     ]
  301 
  302    ,"balance report can be limited with --depth" ~:
  303     ([Depth "1"], []) `gives`
  304     ["                 $-1  assets"
  305     ,"                  $2  expenses"
  306     ,"                 $-2  income"
  307     ,"                  $1  liabilities"
  308     ]
  309     
  310    ,"balance report with account pattern o" ~:
  311     ([SubTotal], ["o"]) `gives`
  312     ["                  $1  expenses:food"
  313     ,"                 $-2  income"
  314     ,"                 $-1    gifts"
  315     ,"                 $-1    salary"
  316     ,"--------------------"
  317     ,"                 $-1"
  318     ]
  319 
  320    ,"balance report with account pattern o and --depth 1" ~:
  321     ([Depth "1"], ["o"]) `gives`
  322     ["                  $1  expenses"
  323     ,"                 $-2  income"
  324     ,"--------------------"
  325     ,"                 $-1"
  326     ]
  327 
  328    ,"balance report with account pattern a" ~:
  329     ([], ["a"]) `gives`
  330     ["                 $-1  assets"
  331     ,"                  $1    bank:saving"
  332     ,"                 $-2    cash"
  333     ,"                 $-1  income:salary"
  334     ,"                  $1  liabilities:debts"
  335     ,"--------------------"
  336     ,"                 $-1"
  337     ]
  338 
  339    ,"balance report with account pattern e" ~:
  340     ([], ["e"]) `gives`
  341     ["                 $-1  assets"
  342     ,"                  $1    bank:saving"
  343     ,"                 $-2    cash"
  344     ,"                  $2  expenses"
  345     ,"                  $1    food"
  346     ,"                  $1    supplies"
  347     ,"                 $-2  income"
  348     ,"                 $-1    gifts"
  349     ,"                 $-1    salary"
  350     ,"                  $1  liabilities:debts"
  351     ]
  352 
  353    ,"balance report with unmatched parent of two matched subaccounts" ~: 
  354     ([], ["cash","saving"]) `gives`
  355     ["                 $-1  assets"
  356     ,"                  $1    bank:saving"
  357     ,"                 $-2    cash"
  358     ,"--------------------"
  359     ,"                 $-1"
  360     ]
  361 
  362    ,"balance report with multi-part account name" ~: 
  363     ([], ["expenses:food"]) `gives`
  364     ["                  $1  expenses:food"
  365     ,"--------------------"
  366     ,"                  $1"
  367     ]
  368 
  369    ,"balance report with negative account pattern" ~:
  370     ([], ["not:assets"]) `gives`
  371     ["                  $2  expenses"
  372     ,"                  $1    food"
  373     ,"                  $1    supplies"
  374     ,"                 $-2  income"
  375     ,"                 $-1    gifts"
  376     ,"                 $-1    salary"
  377     ,"                  $1  liabilities:debts"
  378     ,"--------------------"
  379     ,"                  $1"
  380     ]
  381 
  382    ,"balance report negative account pattern always matches full name" ~: 
  383     ([], ["not:e"]) `gives` []
  384 
  385    ,"balance report negative patterns affect totals" ~: 
  386     ([], ["expenses","not:food"]) `gives`
  387     ["                  $1  expenses:supplies"
  388     ,"--------------------"
  389     ,"                  $1"
  390     ]
  391 
  392    ,"balance report with -E shows zero-balance accounts" ~:
  393     ([SubTotal,Empty], ["assets"]) `gives`
  394     ["                 $-1  assets"
  395     ,"                  $1    bank"
  396     ,"                  $0      checking"
  397     ,"                  $1      saving"
  398     ,"                 $-2    cash"
  399     ,"--------------------"
  400     ,"                 $-1"
  401     ]
  402 
  403    ,"balance report with cost basis" ~: do
  404       rl <- rawLedgerFromString $ unlines
  405              [""
  406              ,"2008/1/1 test           "
  407              ,"  a:b          10h @ $50"
  408              ,"  c:d                   "
  409              ,""
  410              ]
  411       let l = cacheLedger [] $ 
  412               filterRawLedger (DateSpan Nothing Nothing) [] Nothing False $ 
  413               canonicaliseAmounts True rl -- enable cost basis adjustment            
  414       showBalanceReport [] [] l `is` 
  415        unlines
  416         ["                $500  a:b"
  417         ,"               $-500  c:d"
  418         ]
  419 
  420    ,"balance report elides zero-balance root account(s)" ~: do
  421       l <- ledgerFromStringWithOpts [] [] sampletime
  422              (unlines
  423               ["2008/1/1 one"
  424               ,"  test:a  1"
  425               ,"  test:b"
  426               ])
  427       showBalanceReport [] [] l `is`
  428        unlines
  429         ["                   1  test:a"
  430         ,"                  -1  test:b"
  431         ]
  432 
  433    ]
  434 
  435   ,"balanceLedgerTransaction" ~: do
  436      assertBool "detect unbalanced entry, sign error"
  437                     (isLeft $ balanceLedgerTransaction
  438                            (LedgerTransaction (parsedate "2007/01/28") False "" "test" ""
  439                             [Posting False "a" (Mixed [dollars 1]) "" RegularPosting, 
  440                              Posting False "b" (Mixed [dollars 1]) "" RegularPosting
  441                             ] ""))
  442      assertBool "detect unbalanced entry, multiple missing amounts"
  443                     (isLeft $ balanceLedgerTransaction
  444                            (LedgerTransaction (parsedate "2007/01/28") False "" "test" ""
  445                             [Posting False "a" missingamt "" RegularPosting, 
  446                              Posting False "b" missingamt "" RegularPosting
  447                             ] ""))
  448      let e = balanceLedgerTransaction (LedgerTransaction (parsedate "2007/01/28") False "" "test" ""
  449                            [Posting False "a" (Mixed [dollars 1]) "" RegularPosting, 
  450                             Posting False "b" missingamt "" RegularPosting
  451                            ] "")
  452      assertBool "one missing amount should be ok" (isRight e)
  453      assertEqual "balancing amount is added" 
  454                      (Mixed [dollars (-1)])
  455                      (case e of
  456                         Right e' -> (pamount $ last $ ltpostings e')
  457                         Left _ -> error "should not happen")
  458 
  459   ,"cacheLedger" ~: do
  460     (length $ Map.keys $ accountmap $ cacheLedger [] rawledger7) `is` 15
  461 
  462   ,"canonicaliseAmounts" ~:
  463    "use the greatest precision" ~: do
  464     (rawLedgerPrecisions $ canonicaliseAmounts False $ rawLedgerWithAmounts ["1","2.00"]) `is` [2,2]
  465 
  466   ,"commodities" ~: do
  467     commodities ledger7 `is` [Commodity {symbol="$", side=L, spaced=False, comma=False, precision=2}]
  468 
  469   ,"dateSpanFromOpts" ~: do
  470     let todaysdate = parsedate "2008/11/26"
  471     let opts `gives` spans = show (dateSpanFromOpts todaysdate opts) `is` spans
  472     [] `gives` "DateSpan Nothing Nothing"
  473     [Begin "2008", End "2009"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
  474     [Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
  475     [Begin "2005", End "2007",Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
  476 
  477   ,"entriesFromTimeLogEntries" ~: do
  478      today <- getCurrentDay
  479      now' <- getCurrentTime
  480      tz <- getCurrentTimeZone
  481      let now = utcToLocalTime tz now'
  482          nowstr = showtime now
  483          yesterday = prevday today
  484          clockin t a = TimeLogEntry In t a
  485          clockout t = TimeLogEntry Out t ""
  486          mktime d s = LocalTime d $ fromMaybe midnight $ parseTime defaultTimeLocale "%H:%M:%S" s
  487          showtime t = formatTime defaultTimeLocale "%H:%M" t
  488          assertEntriesGiveStrings name es ss = assertEqual name ss (map ltdescription $ entriesFromTimeLogEntries now es)
  489 
  490      assertEntriesGiveStrings "started yesterday, split session at midnight"
  491                                   [clockin (mktime yesterday "23:00:00") ""]
  492                                   ["23:00-23:59","00:00-"++nowstr]
  493      assertEntriesGiveStrings "split multi-day sessions at each midnight"
  494                                   [clockin (mktime (addDays (-2) today) "23:00:00") ""]
  495                                   ["23:00-23:59","00:00-23:59","00:00-"++nowstr]
  496      assertEntriesGiveStrings "auto-clock-out if needed" 
  497                                   [clockin (mktime today "00:00:00") ""] 
  498                                   ["00:00-"++nowstr]
  499      let future = utcToLocalTime tz $ addUTCTime 100 now'
  500          futurestr = showtime future
  501      assertEntriesGiveStrings "use the clockin time for auto-clockout if it's in the future"
  502                                   [clockin future ""]
  503                                   [printf "%s-%s" futurestr futurestr]
  504 
  505   ,"expandAccountNames" ~: do
  506     expandAccountNames ["assets:cash","assets:checking","expenses:vacation"] `is`
  507      ["assets","assets:cash","assets:checking","expenses","expenses:vacation"]
  508 
  509   ,"intervalFromOpts" ~: do
  510     let opts `gives` interval = intervalFromOpts opts `is` interval
  511     [] `gives` NoInterval
  512     [WeeklyOpt] `gives` Weekly
  513     [MonthlyOpt] `gives` Monthly
  514     [QuarterlyOpt] `gives` Quarterly
  515     [YearlyOpt] `gives` Yearly
  516     [Period "weekly"] `gives` Weekly
  517     [Period "monthly"] `gives` Monthly
  518     [Period "quarterly"] `gives` Quarterly
  519     [WeeklyOpt, Period "yearly"] `gives` Yearly
  520 
  521   ,"isAccountNamePrefixOf" ~: do
  522     "assets" `isAccountNamePrefixOf` "assets" `is` False
  523     "assets" `isAccountNamePrefixOf` "assets:bank" `is` True
  524     "assets" `isAccountNamePrefixOf` "assets:bank:checking" `is` True
  525     "my assets" `isAccountNamePrefixOf` "assets:bank" `is` False
  526 
  527   ,"isLedgerTransactionBalanced" ~: do
  528      assertBool "detect balanced"
  529         (isLedgerTransactionBalanced
  530         (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
  531          [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
  532          ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting
  533          ] ""))
  534      assertBool "detect unbalanced"
  535         (not $ isLedgerTransactionBalanced
  536         (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
  537          [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
  538          ,Posting False "c" (Mixed [dollars (-1.01)]) "" RegularPosting
  539          ] ""))
  540      assertBool "detect unbalanced, one posting"
  541         (not $ isLedgerTransactionBalanced
  542         (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
  543          [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
  544          ] ""))
  545      assertBool "one zero posting is considered balanced for now"
  546         (isLedgerTransactionBalanced
  547         (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
  548          [Posting False "b" (Mixed [dollars 0]) "" RegularPosting
  549          ] ""))
  550      assertBool "virtual postings don't need to balance"
  551         (isLedgerTransactionBalanced
  552         (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
  553          [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
  554          ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting
  555          ,Posting False "d" (Mixed [dollars 100]) "" VirtualPosting
  556          ] ""))
  557      assertBool "balanced virtual postings need to balance among themselves"
  558         (not $ isLedgerTransactionBalanced
  559         (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
  560          [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
  561          ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting
  562          ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting
  563          ] ""))
  564      assertBool "balanced virtual postings need to balance among themselves (2)"
  565         (isLedgerTransactionBalanced
  566         (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
  567          [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
  568          ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting
  569          ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting
  570          ,Posting False "e" (Mixed [dollars (-100)]) "" BalancedVirtualPosting
  571          ] ""))
  572 
  573   ,"isSubAccountNameOf" ~: do
  574     "assets" `isSubAccountNameOf` "assets" `is` False
  575     "assets:bank" `isSubAccountNameOf` "assets" `is` True
  576     "assets:bank:checking" `isSubAccountNameOf` "assets" `is` False
  577     "assets:bank" `isSubAccountNameOf` "my assets" `is` False
  578 
  579   ,"default year" ~: do
  580     rl <- rawLedgerFromString defaultyear_ledger_str
  581     (ltdate $ head $ ledger_txns rl) `is` fromGregorian 2009 1 1
  582     return ()
  583 
  584   ,"ledgerFile" ~: do
  585     let now = getCurrentLocalTime
  586     assertBool "ledgerFile should parse an empty file" $ (isRight $ parseWithCtx ledgerFile "")
  587     r <- rawLedgerFromString "" -- don't know how to get it from ledgerFile
  588     assertBool "ledgerFile parsing an empty file should give an empty ledger" $ null $ ledger_txns r
  589 
  590   ,"ledgerHistoricalPrice" ~: do
  591     parseWithCtx ledgerHistoricalPrice price1_str `parseis` price1
  592 
  593   ,"ledgerTransaction" ~: do
  594     parseWithCtx ledgerTransaction entry1_str `parseis` entry1
  595     assertBool "ledgerTransaction should not parse just a date"
  596                    $ isLeft $ parseWithCtx ledgerTransaction "2009/1/1\n"
  597     assertBool "ledgerTransaction should require some postings"
  598                    $ isLeft $ parseWithCtx ledgerTransaction "2009/1/1 a\n"
  599     let t = parseWithCtx ledgerTransaction "2009/1/1 a ;comment\n b 1\n"
  600     assertBool "ledgerTransaction should not include a comment in the description"
  601                    $ either (const False) ((== "a") . ltdescription) t
  602 
  603   ,"ledgerposting" ~: do
  604     parseWithCtx ledgerposting rawposting1_str `parseis` rawposting1
  605 
  606   ,"parsedate" ~: do
  607     parsedate "2008/02/03" `is` parsetimewith "%Y/%m/%d" "2008/02/03" sampledate
  608     parsedate "2008-02-03" `is` parsetimewith "%Y/%m/%d" "2008/02/03" sampledate
  609 
  610   ,"period expressions" ~: do
  611     let todaysdate = parsedate "2008/11/26"
  612     let str `gives` result = (show $ parsewith (periodexpr todaysdate) str) `is` ("Right "++result)
  613     "from aug to oct"           `gives` "(NoInterval,DateSpan (Just 2008-08-01) (Just 2008-10-01))"
  614     "aug to oct"                `gives` "(NoInterval,DateSpan (Just 2008-08-01) (Just 2008-10-01))"
  615     "every day from aug to oct" `gives` "(Daily,DateSpan (Just 2008-08-01) (Just 2008-10-01))"
  616     "daily from aug"            `gives` "(Daily,DateSpan (Just 2008-08-01) Nothing)"
  617     "every week to 2009"        `gives` "(Weekly,DateSpan Nothing (Just 2009-01-01))"
  618 
  619   ,"print report tests" ~: TestList
  620   [
  621 
  622    "print expenses" ~:
  623    do 
  624     let args = ["expenses"]
  625     l <- sampleledgerwithopts [] args
  626     showLedgerTransactions [] args l `is` unlines 
  627      ["2008/06/03 * eat & shop"
  628      ,"    expenses:food                                 $1"
  629      ,"    expenses:supplies                             $1"
  630      ,"    assets:cash"
  631      ,""
  632      ]
  633 
  634   , "print report with depth arg" ~:
  635    do 
  636     l <- sampleledger
  637     showLedgerTransactions [Depth "2"] [] l `is` unlines
  638       ["2008/01/01 income"
  639       ,"    income:salary                                $-1"
  640       ,""
  641       ,"2008/06/01 gift"
  642       ,"    income:gifts                                 $-1"
  643       ,""
  644       ,"2008/06/03 * eat & shop"
  645       ,"    expenses:food                                 $1"
  646       ,"    expenses:supplies                             $1"
  647       ,"    assets:cash"
  648       ,""
  649       ,"2008/12/31 * pay off"
  650       ,"    liabilities:debts                             $1"
  651       ,""
  652       ]
  653 
  654   ]
  655 
  656   ,"punctuatethousands 1" ~: punctuatethousands "" `is` ""
  657 
  658   ,"punctuatethousands 2" ~: punctuatethousands "1234567.8901" `is` "1,234,567.8901"
  659 
  660   ,"punctuatethousands 3" ~: punctuatethousands "-100" `is` "-100"
  661 
  662   ,"register report tests" ~:
  663   let registerdates = filter (not . null) .  map (strip . take 10) . lines
  664   in
  665   TestList
  666   [
  667 
  668    "register report with no args" ~:
  669    do 
  670     l <- sampleledger
  671     showRegisterReport [] [] l `is` unlines
  672      ["2008/01/01 income               assets:bank:checking             $1           $1"
  673      ,"                                income:salary                   $-1            0"
  674      ,"2008/06/01 gift                 assets:bank:checking             $1           $1"
  675      ,"                                income:gifts                    $-1            0"
  676      ,"2008/06/02 save                 assets:bank:saving               $1           $1"
  677      ,"                                assets:bank:checking            $-1            0"
  678      ,"2008/06/03 eat & shop           expenses:food                    $1           $1"
  679      ,"                                expenses:supplies                $1           $2"
  680      ,"                                assets:cash                     $-2            0"
  681      ,"2008/12/31 pay off              liabilities:debts                $1           $1"
  682      ,"                                assets:bank:checking            $-1            0"
  683      ]
  684 
  685   ,"register report with cleared arg" ~:
  686    do 
  687     l <- ledgerFromStringWithOpts [Cleared] [] sampletime sample_ledger_str
  688     showRegisterReport [Cleared] [] l `is` unlines
  689      ["2008/06/03 eat & shop           expenses:food                    $1           $1"
  690      ,"                                expenses:supplies                $1           $2"
  691      ,"                                assets:cash                     $-2            0"
  692      ,"2008/12/31 pay off              liabilities:debts                $1           $1"
  693      ,"                                assets:bank:checking            $-1            0"
  694      ]
  695 
  696   ,"register report with uncleared arg" ~:
  697    do 
  698     l <- ledgerFromStringWithOpts [UnCleared] [] sampletime sample_ledger_str
  699     showRegisterReport [UnCleared] [] l `is` unlines
  700      ["2008/01/01 income               assets:bank:checking             $1           $1"
  701      ,"                                income:salary                   $-1            0"
  702      ,"2008/06/01 gift                 assets:bank:checking             $1           $1"
  703      ,"                                income:gifts                    $-1            0"
  704      ,"2008/06/02 save                 assets:bank:saving               $1           $1"
  705      ,"                                assets:bank:checking            $-1            0"
  706      ]
  707 
  708   ,"register report sorts by date" ~:
  709    do 
  710     l <- ledgerFromStringWithOpts [] [] sampletime $ unlines
  711         ["2008/02/02 a"
  712         ,"  b  1"
  713         ,"  c"
  714         ,""
  715         ,"2008/01/01 d"
  716         ,"  e  1"
  717         ,"  f"
  718         ]
  719     registerdates (showRegisterReport [] [] l) `is` ["2008/01/01","2008/02/02"]
  720 
  721   ,"register report with account pattern" ~:
  722    do
  723     l <- sampleledger
  724     showRegisterReport [] ["cash"] l `is` unlines
  725      ["2008/06/03 eat & shop           assets:cash                     $-2          $-2"
  726      ]
  727 
  728   ,"register report with account pattern, case insensitive" ~:
  729    do 
  730     l <- sampleledger
  731     showRegisterReport [] ["cAsH"] l `is` unlines
  732      ["2008/06/03 eat & shop           assets:cash                     $-2          $-2"
  733      ]
  734 
  735   ,"register report with display expression" ~:
  736    do 
  737     l <- sampleledger
  738     let displayexpr `gives` dates = 
  739             registerdates (showRegisterReport [Display displayexpr] [] l) `is` dates
  740     "d<[2008/6/2]"  `gives` ["2008/01/01","2008/06/01"]
  741     "d<=[2008/6/2]" `gives` ["2008/01/01","2008/06/01","2008/06/02"]
  742     "d=[2008/6/2]"  `gives` ["2008/06/02"]
  743     "d>=[2008/6/2]" `gives` ["2008/06/02","2008/06/03","2008/12/31"]
  744     "d>[2008/6/2]"  `gives` ["2008/06/03","2008/12/31"]
  745 
  746   ,"register report with period expression" ~:
  747    do 
  748     l <- sampleledger    
  749     let periodexpr `gives` dates = do
  750           lopts <- sampleledgerwithopts [Period periodexpr] []
  751           registerdates (showRegisterReport [Period periodexpr] [] lopts) `is` dates
  752     ""     `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]
  753     "2008" `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]
  754     "2007" `gives` []
  755     "june" `gives` ["2008/06/01","2008/06/02","2008/06/03"]
  756     "monthly" `gives` ["2008/01/01","2008/06/01","2008/12/01"]
  757     "quarterly" `gives` ["2008/01/01","2008/04/01","2008/10/01"]
  758     showRegisterReport [Period "yearly"] [] l `is` unlines
  759      ["2008/01/01 - 2008/12/31         assets:bank:saving               $1           $1"
  760      ,"                                assets:cash                     $-2          $-1"
  761      ,"                                expenses:food                    $1            0"
  762      ,"                                expenses:supplies                $1           $1"
  763      ,"                                income:gifts                    $-1            0"
  764      ,"                                income:salary                   $-1          $-1"
  765      ,"                                liabilities:debts                $1            0"
  766      ]
  767     registerdates (showRegisterReport [Period "quarterly"] [] l) `is` ["2008/01/01","2008/04/01","2008/10/01"]
  768     registerdates (showRegisterReport [Period "quarterly",Empty] [] l) `is` ["2008/01/01","2008/04/01","2008/07/01","2008/10/01"]
  769 
  770   ]
  771 
  772   , "register report with depth arg" ~:
  773    do 
  774     l <- sampleledger
  775     showRegisterReport [Depth "2"] [] l `is` unlines
  776      ["2008/01/01 income               income:salary                   $-1          $-1"
  777      ,"2008/06/01 gift                 income:gifts                    $-1          $-2"
  778      ,"2008/06/03 eat & shop           expenses:food                    $1          $-1"
  779      ,"                                expenses:supplies                $1            0"
  780      ,"                                assets:cash                     $-2          $-2"
  781      ,"2008/12/31 pay off              liabilities:debts                $1          $-1"
  782      ]
  783 
  784   ,"show dollars" ~: show (dollars 1) ~?= "$1.00"
  785 
  786   ,"show hours" ~: show (hours 1) ~?= "1.0h"
  787 
  788   ,"showLedgerTransaction" ~: do
  789      assertEqual "show a balanced transaction, eliding last amount"
  790        (unlines
  791         ["2007/01/28 coopportunity"
  792         ,"    expenses:food:groceries                   $47.18"
  793         ,"    assets:checking"
  794         ,""
  795         ])
  796        (showLedgerTransaction
  797         (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
  798          [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting
  799          ,Posting False "assets:checking" (Mixed [dollars (-47.18)]) "" RegularPosting
  800          ] ""))
  801      -- document some cases that arise in debug/testing:
  802      assertEqual "show an unbalanced transaction, should not elide"
  803        (unlines
  804         ["2007/01/28 coopportunity"
  805         ,"    expenses:food:groceries                   $47.18"
  806         ,"    assets:checking                          $-47.19"
  807         ,""
  808         ])
  809        (showLedgerTransaction
  810         (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
  811          [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting
  812          ,Posting False "assets:checking" (Mixed [dollars (-47.19)]) "" RegularPosting
  813          ] ""))
  814      assertEqual "show an unbalanced transaction with one posting, should not elide"
  815        (unlines
  816         ["2007/01/28 coopportunity"
  817         ,"    expenses:food:groceries                   $47.18"
  818         ,""
  819         ])
  820        (showLedgerTransaction
  821         (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
  822          [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting
  823          ] ""))
  824      assertEqual "show a transaction with one posting and a missing amount"
  825        (unlines
  826         ["2007/01/28 coopportunity"
  827         ,"    expenses:food:groceries                         "
  828         ,""
  829         ])
  830        (showLedgerTransaction
  831         (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
  832          [Posting False "expenses:food:groceries" missingamt "" RegularPosting
  833          ] ""))
  834 
  835   ,"unicode in balance layout" ~: do
  836     l <- ledgerFromStringWithOpts [] [] sampletime
  837       "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"
  838     showBalanceReport [] [] l `is` unlines
  839       ["                -100  актив:наличные"
  840       ,"                 100  расходы:покупки"]
  841 
  842   ,"unicode in register layout" ~: do
  843     l <- ledgerFromStringWithOpts [] [] sampletime
  844       "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"
  845     showRegisterReport [] [] l `is` unlines
  846       ["2009/01/01 медвежья шкура       расходы:покупки                 100          100"
  847       ,"                                актив:наличные                 -100            0"]
  848 
  849   ,"smart dates" ~: do
  850     let str `gives` datestr = fixSmartDateStr (parsedate "2008/11/26") str `is` datestr
  851     "1999-12-02"   `gives` "1999/12/02"
  852     "1999.12.02"   `gives` "1999/12/02"
  853     "1999/3/2"     `gives` "1999/03/02"
  854     "19990302"     `gives` "1999/03/02"
  855     "2008/2"       `gives` "2008/02/01"
  856     "20/2"         `gives` "0020/02/01"
  857     "1000"         `gives` "1000/01/01"
  858     "4/2"          `gives` "2008/04/02"
  859     "2"            `gives` "2008/11/02"
  860     "January"      `gives` "2008/01/01"
  861     "feb"          `gives` "2008/02/01"
  862     "today"        `gives` "2008/11/26"
  863     "yesterday"    `gives` "2008/11/25"
  864     "tomorrow"     `gives` "2008/11/27"
  865     "this day"     `gives` "2008/11/26"
  866     "last day"     `gives` "2008/11/25"
  867     "next day"     `gives` "2008/11/27"
  868     "this week"    `gives` "2008/11/24" -- last monday
  869     "last week"    `gives` "2008/11/17" -- previous monday
  870     "next week"    `gives` "2008/12/01" -- next monday
  871     "this month"   `gives` "2008/11/01"
  872     "last month"   `gives` "2008/10/01"
  873     "next month"   `gives` "2008/12/01"
  874     "this quarter" `gives` "2008/10/01"
  875     "last quarter" `gives` "2008/07/01"
  876     "next quarter" `gives` "2009/01/01"
  877     "this year"    `gives` "2008/01/01"
  878     "last year"    `gives` "2007/01/01"
  879     "next year"    `gives` "2009/01/01"
  880 --     "last wed"     `gives` "2008/11/19"
  881 --     "next friday"  `gives` "2008/11/28"
  882 --     "next january" `gives` "2009/01/01"
  883 
  884   ,"splitSpan" ~: do
  885     let (interval,span) `gives` spans = splitSpan interval span `is` spans
  886     (NoInterval,mkdatespan "2008/01/01" "2009/01/01") `gives`
  887      [mkdatespan "2008/01/01" "2009/01/01"]
  888     (Quarterly,mkdatespan "2008/01/01" "2009/01/01") `gives`
  889      [mkdatespan "2008/01/01" "2008/04/01"
  890      ,mkdatespan "2008/04/01" "2008/07/01"
  891      ,mkdatespan "2008/07/01" "2008/10/01"
  892      ,mkdatespan "2008/10/01" "2009/01/01"
  893      ]
  894     (Quarterly,nulldatespan) `gives`
  895      [nulldatespan]
  896     (Daily,mkdatespan "2008/01/01" "2008/01/01") `gives`
  897      [mkdatespan "2008/01/01" "2008/01/01"]
  898     (Quarterly,mkdatespan "2008/01/01" "2008/01/01") `gives`
  899      [mkdatespan "2008/01/01" "2008/01/01"]
  900 
  901   ,"subAccounts" ~: do
  902     l <- sampleledger
  903     let a = ledgerAccount l "assets"
  904     (map aname $ ledgerSubAccounts l a) `is` ["assets:bank","assets:cash"]
  905 
  906   ,"summariseTransactionsInDateSpan" ~: do
  907     let (b,e,tnum,depth,showempty,ts) `gives` summaryts = 
  908             summariseTransactionsInDateSpan (mkdatespan b e) tnum depth showempty ts `is` summaryts
  909     let ts =
  910             [
  911              nulltxn{tdescription="desc",taccount="expenses:food:groceries",tamount=Mixed [dollars 1]}
  912             ,nulltxn{tdescription="desc",taccount="expenses:food:dining",   tamount=Mixed [dollars 2]}
  913             ,nulltxn{tdescription="desc",taccount="expenses:food",          tamount=Mixed [dollars 4]}
  914             ,nulltxn{tdescription="desc",taccount="expenses:food:dining",   tamount=Mixed [dollars 8]}
  915             ]
  916     ("2008/01/01","2009/01/01",0,9999,False,[]) `gives` 
  917      []
  918     ("2008/01/01","2009/01/01",0,9999,True,[]) `gives` 
  919      [
  920       nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31"}
  921      ]
  922     ("2008/01/01","2009/01/01",0,9999,False,ts) `gives` 
  923      [
  924       nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food",          tamount=Mixed [dollars 4]}
  925      ,nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food:dining",   tamount=Mixed [dollars 10]}
  926      ,nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food:groceries",tamount=Mixed [dollars 1]}
  927      ]
  928     ("2008/01/01","2009/01/01",0,2,False,ts) `gives` 
  929      [
  930       nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food",tamount=Mixed [dollars 15]}
  931      ]
  932     ("2008/01/01","2009/01/01",0,1,False,ts) `gives` 
  933      [
  934       nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses",tamount=Mixed [dollars 15]}
  935      ]
  936     ("2008/01/01","2009/01/01",0,0,False,ts) `gives` 
  937      [
  938       nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="",tamount=Mixed [dollars 15]}
  939      ]
  940 
  941   ,"postingamount" ~: do
  942     parseWithCtx postingamount " $47.18" `parseis` Mixed [dollars 47.18]
  943     parseWithCtx postingamount " $1." `parseis` 
  944      Mixed [Amount (Commodity {symbol="$",side=L,spaced=False,comma=False,precision=0}) 1 Nothing]
  945 
  946   ]
  947 
  948   
  949 ------------------------------------------------------------------------------
  950 -- test data
  951 
  952 sampledate = parsedate "2008/11/26"
  953 sampletime = LocalTime sampledate midday
  954 sampleledger = ledgerFromStringWithOpts [] [] sampletime sample_ledger_str
  955 sampleledgerwithopts opts args = ledgerFromStringWithOpts opts args sampletime sample_ledger_str
  956 
  957 sample_ledger_str = unlines
  958  ["; A sample ledger file."
  959  ,";"
  960  ,"; Sets up this account tree:"
  961  ,"; assets"
  962  ,";   bank"
  963  ,";     checking"
  964  ,";     saving"
  965  ,";   cash"
  966  ,"; expenses"
  967  ,";   food"
  968  ,";   supplies"
  969  ,"; income"
  970  ,";   gifts"
  971  ,";   salary"
  972  ,"; liabilities"
  973  ,";   debts"
  974  ,""
  975  ,"2008/01/01 income"
  976  ,"    assets:bank:checking  $1"
  977  ,"    income:salary"
  978  ,""
  979  ,"2008/06/01 gift"
  980  ,"    assets:bank:checking  $1"
  981  ,"    income:gifts"
  982  ,""
  983  ,"2008/06/02 save"
  984  ,"    assets:bank:saving  $1"
  985  ,"    assets:bank:checking"
  986  ,""
  987  ,"2008/06/03 * eat & shop"
  988  ,"    expenses:food      $1"
  989  ,"    expenses:supplies  $1"
  990  ,"    assets:cash"
  991  ,""
  992  ,"2008/12/31 * pay off"
  993  ,"    liabilities:debts  $1"
  994  ,"    assets:bank:checking"
  995  ,""
  996  ,""
  997  ,";final comment"
  998  ]
  999 
 1000 defaultyear_ledger_str = unlines
 1001  ["Y2009"
 1002  ,""
 1003  ,"01/01 A"
 1004  ,"    a  $1"
 1005  ,"    b"
 1006  ]
 1007 
 1008 write_sample_ledger = writeFile "sample.ledger" sample_ledger_str
 1009 
 1010 rawposting1_str  = "  expenses:food:dining  $10.00\n"
 1011 
 1012 rawposting1 = Posting False "expenses:food:dining" (Mixed [dollars 10]) "" RegularPosting
 1013 
 1014 entry1_str = unlines
 1015  ["2007/01/28 coopportunity"
 1016  ,"    expenses:food:groceries                   $47.18"
 1017  ,"    assets:checking"
 1018  ,""
 1019  ]
 1020 
 1021 entry1 =
 1022     (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
 1023      [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting, 
 1024       Posting False "assets:checking" (Mixed [dollars (-47.18)]) "" RegularPosting] "")
 1025 
 1026 
 1027 entry2_str = unlines
 1028  ["2007/01/27 * joes diner"
 1029  ,"    expenses:food:dining                      $10.00"
 1030  ,"    expenses:gifts                            $10.00"
 1031  ,"    assets:checking                          $-20.00"
 1032  ,""
 1033  ]
 1034 
 1035 entry3_str = unlines
 1036  ["2007/01/01 * opening balance"
 1037  ,"    assets:cash                                $4.82"
 1038  ,"    equity:opening balances"
 1039  ,""
 1040  ,"2007/01/01 * opening balance"
 1041  ,"    assets:cash                                $4.82"
 1042  ,"    equity:opening balances"
 1043  ,""
 1044  ,"2007/01/28 coopportunity"
 1045  ,"  expenses:food:groceries                 $47.18"
 1046  ,"  assets:checking"
 1047  ,""
 1048  ]
 1049 
 1050 periodic_entry1_str = unlines
 1051  ["~ monthly from 2007/2/2"
 1052  ,"  assets:saving            $200.00"
 1053  ,"  assets:checking"
 1054  ,""
 1055  ]
 1056 
 1057 periodic_entry2_str = unlines
 1058  ["~ monthly from 2007/2/2"
 1059  ,"  assets:saving            $200.00         ;auto savings"
 1060  ,"  assets:checking"
 1061  ,""
 1062  ]
 1063 
 1064 periodic_entry3_str = unlines
 1065  ["~ monthly from 2007/01/01"
 1066  ,"    assets:cash                                $4.82"
 1067  ,"    equity:opening balances"
 1068  ,""
 1069  ,"~ monthly from 2007/01/01"
 1070  ,"    assets:cash                                $4.82"
 1071  ,"    equity:opening balances"
 1072  ,""
 1073  ]
 1074 
 1075 ledger1_str = unlines
 1076  [""
 1077  ,"2007/01/27 * joes diner"
 1078  ,"  expenses:food:dining                    $10.00"
 1079  ,"  expenses:gifts                          $10.00"
 1080  ,"  assets:checking                        $-20.00"
 1081  ,""
 1082  ,""
 1083  ,"2007/01/28 coopportunity"
 1084  ,"  expenses:food:groceries                 $47.18"
 1085  ,"  assets:checking                        $-47.18"
 1086  ,""
 1087  ,""
 1088  ]
 1089 
 1090 ledger2_str = unlines
 1091  [";comment"
 1092  ,"2007/01/27 * joes diner"
 1093  ,"  expenses:food:dining                    $10.00"
 1094  ,"  assets:checking                        $-47.18"
 1095  ,""
 1096  ]
 1097 
 1098 ledger3_str = unlines
 1099  ["2007/01/27 * joes diner"
 1100  ,"  expenses:food:dining                    $10.00"
 1101  ,";intra-entry comment"
 1102  ,"  assets:checking                        $-47.18"
 1103  ,""
 1104  ]
 1105 
 1106 ledger4_str = unlines
 1107  ["!include \"somefile\""
 1108  ,"2007/01/27 * joes diner"
 1109  ,"  expenses:food:dining                    $10.00"
 1110  ,"  assets:checking                        $-47.18"
 1111  ,""
 1112  ]
 1113 
 1114 ledger5_str = ""
 1115 
 1116 ledger6_str = unlines
 1117  ["~ monthly from 2007/1/21"
 1118  ,"    expenses:entertainment  $16.23        ;netflix"
 1119  ,"    assets:checking"
 1120  ,""
 1121  ,"; 2007/01/01 * opening balance"
 1122  ,";     assets:saving                            $200.04"
 1123  ,";     equity:opening balances                         "
 1124  ,""
 1125  ]
 1126 
 1127 ledger7_str = unlines
 1128  ["2007/01/01 * opening balance"
 1129  ,"    assets:cash                                $4.82"
 1130  ,"    equity:opening balances                         "
 1131  ,""
 1132  ,"2007/01/01 * opening balance"
 1133  ,"    income:interest                                $-4.82"
 1134  ,"    equity:opening balances                         "
 1135  ,""
 1136  ,"2007/01/02 * ayres suites"
 1137  ,"    expenses:vacation                        $179.92"
 1138  ,"    assets:checking                                 "
 1139  ,""
 1140  ,"2007/01/02 * auto transfer to savings"
 1141  ,"    assets:saving                            $200.00"
 1142  ,"    assets:checking                                 "
 1143  ,""
 1144  ,"2007/01/03 * poquito mas"
 1145  ,"    expenses:food:dining                       $4.82"
 1146  ,"    assets:cash                                     "
 1147  ,""
 1148  ,"2007/01/03 * verizon"
 1149  ,"    expenses:phone                            $95.11"
 1150  ,"    assets:checking                                 "
 1151  ,""
 1152  ,"2007/01/03 * discover"
 1153  ,"    liabilities:credit cards:discover         $80.00"
 1154  ,"    assets:checking                                 "
 1155  ,""
 1156  ,"2007/01/04 * blue cross"
 1157  ,"    expenses:health:insurance                 $90.00"
 1158  ,"    assets:checking                                 "
 1159  ,""
 1160  ,"2007/01/05 * village market liquor"
 1161  ,"    expenses:food:dining                       $6.48"
 1162  ,"    assets:checking                                 "
 1163  ,""
 1164  ]
 1165 
 1166 rawledger7 = RawLedger
 1167           [] 
 1168           [] 
 1169           [
 1170            LedgerTransaction {
 1171              ltdate= parsedate "2007/01/01", 
 1172              ltstatus=False, 
 1173              ltcode="*", 
 1174              ltdescription="opening balance", 
 1175              ltcomment="",
 1176              ltpostings=[
 1177               Posting {
 1178                 pstatus=False,
 1179                 paccount="assets:cash", 
 1180                 pamount=(Mixed [dollars 4.82]),
 1181                 pcomment="",
 1182                 ptype=RegularPosting
 1183               },
 1184               Posting {
 1185                 pstatus=False,
 1186                 paccount="equity:opening balances", 
 1187                 pamount=(Mixed [dollars (-4.82)]),
 1188                 pcomment="",
 1189                 ptype=RegularPosting
 1190               }
 1191              ],
 1192              ltpreceding_comment_lines=""
 1193            }
 1194           ,
 1195            LedgerTransaction {
 1196              ltdate= parsedate "2007/02/01", 
 1197              ltstatus=False, 
 1198              ltcode="*", 
 1199              ltdescription="ayres suites", 
 1200              ltcomment="",
 1201              ltpostings=[
 1202               Posting {
 1203                 pstatus=False,
 1204                 paccount="expenses:vacation", 
 1205                 pamount=(Mixed [dollars 179.92]),
 1206                 pcomment="",
 1207                 ptype=RegularPosting
 1208               },
 1209               Posting {
 1210                 pstatus=False,
 1211                 paccount="assets:checking", 
 1212                 pamount=(Mixed [dollars (-179.92)]),
 1213                 pcomment="",
 1214                 ptype=RegularPosting
 1215               }
 1216              ],
 1217              ltpreceding_comment_lines=""
 1218            }
 1219           ,
 1220            LedgerTransaction {
 1221              ltdate=parsedate "2007/01/02", 
 1222              ltstatus=False, 
 1223              ltcode="*", 
 1224              ltdescription="auto transfer to savings", 
 1225              ltcomment="",
 1226              ltpostings=[
 1227               Posting {
 1228                 pstatus=False,
 1229                 paccount="assets:saving", 
 1230                 pamount=(Mixed [dollars 200]),
 1231                 pcomment="",
 1232                 ptype=RegularPosting
 1233               },
 1234               Posting {
 1235                 pstatus=False,
 1236                 paccount="assets:checking", 
 1237                 pamount=(Mixed [dollars (-200)]),
 1238                 pcomment="",
 1239                 ptype=RegularPosting
 1240               }
 1241              ],
 1242              ltpreceding_comment_lines=""
 1243            }
 1244           ,
 1245            LedgerTransaction {
 1246              ltdate=parsedate "2007/01/03", 
 1247              ltstatus=False, 
 1248              ltcode="*", 
 1249              ltdescription="poquito mas", 
 1250              ltcomment="",
 1251              ltpostings=[
 1252               Posting {
 1253                 pstatus=False,
 1254                 paccount="expenses:food:dining", 
 1255                 pamount=(Mixed [dollars 4.82]),
 1256                 pcomment="",
 1257                 ptype=RegularPosting
 1258               },
 1259               Posting {
 1260                 pstatus=False,
 1261                 paccount="assets:cash", 
 1262                 pamount=(Mixed [dollars (-4.82)]),
 1263                 pcomment="",
 1264                 ptype=RegularPosting
 1265               }
 1266              ],
 1267              ltpreceding_comment_lines=""
 1268            }
 1269           ,
 1270            LedgerTransaction {
 1271              ltdate=parsedate "2007/01/03", 
 1272              ltstatus=False, 
 1273              ltcode="*", 
 1274              ltdescription="verizon", 
 1275              ltcomment="",
 1276              ltpostings=[
 1277               Posting {
 1278                 pstatus=False,
 1279                 paccount="expenses:phone", 
 1280                 pamount=(Mixed [dollars 95.11]),
 1281                 pcomment="",
 1282                 ptype=RegularPosting
 1283               },
 1284               Posting {
 1285                 pstatus=False,
 1286                 paccount="assets:checking", 
 1287                 pamount=(Mixed [dollars (-95.11)]),
 1288                 pcomment="",
 1289                 ptype=RegularPosting
 1290               }
 1291              ],
 1292              ltpreceding_comment_lines=""
 1293            }
 1294           ,
 1295            LedgerTransaction {
 1296              ltdate=parsedate "2007/01/03", 
 1297              ltstatus=False, 
 1298              ltcode="*", 
 1299              ltdescription="discover", 
 1300              ltcomment="",
 1301              ltpostings=[
 1302               Posting {
 1303                 pstatus=False,
 1304                 paccount="liabilities:credit cards:discover", 
 1305                 pamount=(Mixed [dollars 80]),
 1306                 pcomment="",
 1307                 ptype=RegularPosting
 1308               },
 1309               Posting {
 1310                 pstatus=False,
 1311                 paccount="assets:checking", 
 1312                 pamount=(Mixed [dollars (-80)]),
 1313                 pcomment="",
 1314                 ptype=RegularPosting
 1315               }
 1316              ],
 1317              ltpreceding_comment_lines=""
 1318            }
 1319           ] 
 1320           []
 1321           []
 1322           ""
 1323           ""
 1324 
 1325 ledger7 = cacheLedger [] rawledger7 
 1326 
 1327 ledger8_str = unlines
 1328  ["2008/1/1 test           "
 1329  ,"  a:b          10h @ $40"
 1330  ,"  c:d                   "
 1331  ,""
 1332  ]
 1333 
 1334 timelogentry1_str  = "i 2007/03/11 16:19:00 hledger\n"
 1335 timelogentry1 = TimeLogEntry In (parsedatetime "2007/03/11 16:19:00") "hledger"
 1336 
 1337 timelogentry2_str  = "o 2007/03/11 16:30:00\n"
 1338 timelogentry2 = TimeLogEntry Out (parsedatetime "2007/03/11 16:30:00") ""
 1339 
 1340 price1_str = "P 2004/05/01 XYZ $55\n"
 1341 price1 = HistoricalPrice (parsedate "2004/05/01") "XYZ" "$" 55
 1342 
 1343 a1 = Mixed [(hours 1){price=Just $ Mixed [Amount (comm "$") 10 Nothing]}]
 1344 a2 = Mixed [(hours 2){price=Just $ Mixed [Amount (comm "EUR") 10 Nothing]}]
 1345 a3 = Mixed $ (amounts a1) ++ (amounts a2)
 1346 
 1347 rawLedgerWithAmounts :: [String] -> RawLedger
 1348 rawLedgerWithAmounts as = 
 1349         RawLedger 
 1350         [] 
 1351         [] 
 1352         [nullledgertxn{ltdescription=a,ltpostings=[nullrawposting{pamount=parse a}]} | a <- as]
 1353         []
 1354         []
 1355         ""
 1356         ""
 1357     where parse = fromparse . parseWithCtx postingamount . (" "++)
 1358