packages feed

hledger-0.6: Tests.hs

{- |
hledger's test suite. Most tests are HUnit-based, and defined in the
@tests@ list below. These tests are built in to hledger and can be run at
any time with @hledger test@.

In addition, we have tests in doctest format, which can be run with @make
doctest@ in the hledger source tree. These have some advantages:

- easier to read and write than hunit, for functional/shell tests

- easier to read multi-line output from failing tests

- can also appear in, and test, docs

and disadvantages:

- not included in hledger's built-in tests

- not platform independent

Here are the hledger doctests (some may reappear in other modules as
examples):

Run a few with c++ ledger first:

@
$ ledger -f sample.ledger balance
                 $-1  assets
                  $1    bank:saving
                 $-2    cash
                  $2  expenses
                  $1    food
                  $1    supplies
                 $-2  income
                 $-1    gifts
                 $-1    salary
                  $1  liabilities:debts
@

@
$ ledger -f sample.ledger balance o
                  $1  expenses:food
                 $-2  income
                 $-1    gifts
                 $-1    salary
--------------------
                 $-1
@

Then hledger:

@
$ hledger -f sample.ledger balance
                 $-1  assets
                  $1    bank:saving
                 $-2    cash
                  $2  expenses
                  $1    food
                  $1    supplies
                 $-2  income
                 $-1    gifts
                 $-1    salary
                  $1  liabilities:debts
@

@
$ hledger -f sample.ledger balance o
                  $1  expenses:food
                 $-2  income
                 $-1    gifts
                 $-1    salary
--------------------
                 $-1
@

@
$ hledger -f sample.ledger balance --depth 1
                 $-1  assets
                  $2  expenses
                 $-2  income
                  $1  liabilities
@
-}
{-
@
$ printf "2009/1/1 a\n  b  1.1\n  c  -1\n" | runhaskell hledger.hs -f- reg 2>&1 ; true
"-" (line 4, column 1):
unexpected end of input
could not balance this transaction, amounts do not add up to zero:
2009/01/01 a
    b                                            1.1
    c                                             -1


@

@
$ printf "2009/1/1 x\n  (virtual)  100\n  a  1\n  b\n" | runhaskell hledger.hs -f- print 2>&1 ; true
2009/01/01 x
    (virtual)                                    100
    a                                              1
    b

@

Unicode input/output tests

-- layout of the balance command with unicode names
@
$ printf "2009-01-01 проверка\n  τράπεζα  10 руб\n  नकद\n" | hledger -f - bal
              10 руб  τράπεζα
             -10 руб  नकद
@

-- layout of the register command with unicode names
@
$ printf "2009-01-01 проверка\n  τράπεζα  10 руб\n  नकद\n" | hledger -f - reg
2009/01/01 проверка             τράπεζα                      10 руб       10 руб
                                नकद                         -10 руб            0
@

-- layout of the print command with unicode names
@
$ printf "2009-01-01 проверка\n счёт:первый  1\n счёт:второй\n" | hledger -f - print
2009/01/01 проверка
    счёт:первый                                    1
    счёт:второй

@

-- search for unicode account names
@
$ printf "2009-01-01 проверка\n  τράπεζα  10 руб\n  नकद\n" | hledger -f - reg τράπ
2009/01/01 проверка             τράπεζα                      10 руб       10 руб
@

-- search for unicode descriptions (should choose only the first entry)
@
$ 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:аура
2009/01/01 аура (cyrillic let.. bank                             10           10
                                cash                            -10            0
@

-- error message with unicode in ledger
-- not implemented yet
--@
$ printf "2009-01-01 broken entry\n  дебит  1\n  кредит  -2\n" | hledger -f - 2>&1 ; true
hledger: could not balance this transaction, amounts do not add up to zero:
2009/01/01 broken entry
    дебит                                          1
    кредит                                        -2


--@

@
$ printf "2009-01-01 x\n  a  2\n  b (b) b  -1\n  c\n" | hledger -f - print 2>&1; true
2009/01/01 x
    a                                              2
    b (b) b                                       -1
    c

@

Nafai's bug
@
$ printf "2009/1/1 x\n a:  13\n b\n" | hledger -f - bal -E 2>&1
"-" (line 2, column 1):
unexpected " "
accountname seems ill-formed: a:
@

Eliding, general layout
@
$ printf "2009/1/1 x\n aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa  €1\n b\n" | hledger -f - bal 2>&1
                  €1  aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa
                 €-1  b
@

--@
$ printf "2009/1/1 x\n aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa  €1\n b\n" | hledger -f - reg 2>&1
2009/01/01 x                    aa:aa:aaaaaaaaaaaaaaaa           €1           €1
                                b                               €-1            0
@

--@
$ printf "2009/1/1 x\n aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa  €1\n b\n" | hledger -f - print 2>&1
2009/01/01 x
    aa:aaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa
    b
@

-}
-- other test tools:
-- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/test-framework
-- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/HTF

module Tests
where
import qualified Data.Map as Map
import Data.Time.Format
import Locale (defaultTimeLocale)
import Text.ParserCombinators.Parsec
import Test.HUnit.Tools (runVerboseTests)
import System.Exit (exitFailure,exitSuccess)

import Commands.All
import Ledger
import Options
import Utils


runtests opts args = do
  (counts,_) <- runner ts
  if errors counts > 0 || (failures counts > 0)
   then exitFailure
   else exitSuccess
    where
      runner | (Verbose `elem` opts) = runVerboseTests
             | otherwise = \t -> runTestTT t >>= return . (flip (,) 0)
      ts = TestList $ filter matchname $ concatMap tflatten tests
      --ts = tfilter matchname $ TestList tests -- unflattened
      matchname = matchpats args . tname

-- | Get a Test's label, or the empty string.
tname :: Test -> String
tname (TestLabel n _) = n
tname _ = ""

-- | Flatten a Test containing TestLists into a list of single tests.
tflatten :: Test -> [Test]
tflatten (TestLabel _ t@(TestList _)) = tflatten t
tflatten (TestList ts) = concatMap tflatten ts
tflatten t = [t]

-- | Filter TestLists in a Test, recursively, preserving the structure.
tfilter :: (Test -> Bool) -> Test -> Test
tfilter p (TestLabel l ts) = TestLabel l (tfilter p ts)
tfilter p (TestList ts) = TestList $ filter (any p . tflatten) $ map (tfilter p) ts
tfilter _ t = t

-- | Simple way to assert something is some expected value, with no label.
is :: (Eq a, Show a) => a -> a -> Assertion
a `is` e = assertEqual "" e a

-- | Assert a parse result is some expected value, or print a parse error.
parseis :: (Show a, Eq a) => (Either ParseError a) -> a -> Assertion
parse `parseis` expected = either printParseError (`is` expected) parse

parseWithCtx :: GenParser Char LedgerFileCtx a -> String -> Either ParseError a
parseWithCtx p ts = runParser p emptyCtx "" ts

------------------------------------------------------------------------------
-- | Tests for any function or topic. Mostly ordered by test name.
tests :: [Test]
tests = [

   "account directive" ~: 
   let sameParse str1 str2 = do l1 <- rawLedgerFromString str1
                                l2 <- rawLedgerFromString str2
                                l1 `is` l2
   in TestList
   [
    "account directive 1" ~: sameParse 
                          "2008/12/07 One\n  test:from  $-1\n  test:to  $1\n"
                          "!account test\n2008/12/07 One\n  from  $-1\n  to  $1\n"

   ,"account directive 2" ~: sameParse 
                           "2008/12/07 One\n  test:foo:from  $-1\n  test:foo:to  $1\n"
                           "!account test\n!account foo\n2008/12/07 One\n  from  $-1\n  to  $1\n"

   ,"account directive 3" ~: sameParse 
                           "2008/12/07 One\n  test:from  $-1\n  test:to  $1\n"
                           "!account test\n!account foo\n!end\n2008/12/07 One\n  from  $-1\n  to  $1\n"

   ,"account directive 4" ~: sameParse 
                           ("2008/12/07 One\n  alpha  $-1\n  beta  $1\n" ++
                            "!account outer\n2008/12/07 Two\n  aigh  $-2\n  bee  $2\n" ++
                            "!account inner\n2008/12/07 Three\n  gamma  $-3\n  delta  $3\n" ++
                            "!end\n2008/12/07 Four\n  why  $-4\n  zed  $4\n" ++
                            "!end\n2008/12/07 Five\n  foo  $-5\n  bar  $5\n"
                           )
                           ("2008/12/07 One\n  alpha  $-1\n  beta  $1\n" ++
                            "2008/12/07 Two\n  outer:aigh  $-2\n  outer:bee  $2\n" ++
                            "2008/12/07 Three\n  outer:inner:gamma  $-3\n  outer:inner:delta  $3\n" ++
                            "2008/12/07 Four\n  outer:why  $-4\n  outer:zed  $4\n" ++
                            "2008/12/07 Five\n  foo  $-5\n  bar  $5\n"
                           )
   ]

  ,"accountnames" ~: do
    accountnames ledger7 `is`
     ["assets","assets:cash","assets:checking","assets:saving","equity","equity:opening balances",
      "expenses","expenses:food","expenses:food:dining","expenses:phone","expenses:vacation",
      "liabilities","liabilities:credit cards","liabilities:credit cards:discover"]

  ,"accountNameTreeFrom" ~: do
    accountNameTreeFrom ["a"]       `is` Node "top" [Node "a" []]
    accountNameTreeFrom ["a","b"]   `is` Node "top" [Node "a" [], Node "b" []]
    accountNameTreeFrom ["a","a:b"] `is` Node "top" [Node "a" [Node "a:b" []]]
    accountNameTreeFrom ["a:b:c"]   `is` Node "top" [Node "a" [Node "a:b" [Node "a:b:c" []]]]

  ,"amount arithmetic" ~: do
    let a1 = dollars 1.23
    let a2 = Amount (comm "$") (-1.23) Nothing
    let a3 = Amount (comm "$") (-1.23) Nothing
    (a1 + a2) `is` Amount (comm "$") 0 Nothing
    (a1 + a3) `is` Amount (comm "$") 0 Nothing
    (a2 + a3) `is` Amount (comm "$") (-2.46) Nothing
    (a3 + a3) `is` Amount (comm "$") (-2.46) Nothing
    (sum [a2,a3]) `is` Amount (comm "$") (-2.46) Nothing
    (sum [a3,a3]) `is` Amount (comm "$") (-2.46) Nothing
    (sum [a1,a2,a3,-a3]) `is` Amount (comm "$") 0 Nothing

  ,"balance report tests" ~:
   let (opts,args) `gives` es = do 
        l <- sampleledgerwithopts opts args
        showBalanceReport opts args l `is` unlines es
   in TestList
   [

    "balance report with no args" ~:
    ([], []) `gives`
    ["                 $-1  assets"
    ,"                  $1    bank:saving"
    ,"                 $-2    cash"
    ,"                  $2  expenses"
    ,"                  $1    food"
    ,"                  $1    supplies"
    ,"                 $-2  income"
    ,"                 $-1    gifts"
    ,"                 $-1    salary"
    ,"                  $1  liabilities:debts"
    ]

   ,"balance report can be limited with --depth" ~:
    ([Depth "1"], []) `gives`
    ["                 $-1  assets"
    ,"                  $2  expenses"
    ,"                 $-2  income"
    ,"                  $1  liabilities"
    ]
    
   ,"balance report with account pattern o" ~:
    ([SubTotal], ["o"]) `gives`
    ["                  $1  expenses:food"
    ,"                 $-2  income"
    ,"                 $-1    gifts"
    ,"                 $-1    salary"
    ,"--------------------"
    ,"                 $-1"
    ]

   ,"balance report with account pattern o and --depth 1" ~:
    ([Depth "1"], ["o"]) `gives`
    ["                  $1  expenses"
    ,"                 $-2  income"
    ,"--------------------"
    ,"                 $-1"
    ]

   ,"balance report with account pattern a" ~:
    ([], ["a"]) `gives`
    ["                 $-1  assets"
    ,"                  $1    bank:saving"
    ,"                 $-2    cash"
    ,"                 $-1  income:salary"
    ,"                  $1  liabilities:debts"
    ,"--------------------"
    ,"                 $-1"
    ]

   ,"balance report with account pattern e" ~:
    ([], ["e"]) `gives`
    ["                 $-1  assets"
    ,"                  $1    bank:saving"
    ,"                 $-2    cash"
    ,"                  $2  expenses"
    ,"                  $1    food"
    ,"                  $1    supplies"
    ,"                 $-2  income"
    ,"                 $-1    gifts"
    ,"                 $-1    salary"
    ,"                  $1  liabilities:debts"
    ]

   ,"balance report with unmatched parent of two matched subaccounts" ~: 
    ([], ["cash","saving"]) `gives`
    ["                 $-1  assets"
    ,"                  $1    bank:saving"
    ,"                 $-2    cash"
    ,"--------------------"
    ,"                 $-1"
    ]

   ,"balance report with multi-part account name" ~: 
    ([], ["expenses:food"]) `gives`
    ["                  $1  expenses:food"
    ,"--------------------"
    ,"                  $1"
    ]

   ,"balance report with negative account pattern" ~:
    ([], ["not:assets"]) `gives`
    ["                  $2  expenses"
    ,"                  $1    food"
    ,"                  $1    supplies"
    ,"                 $-2  income"
    ,"                 $-1    gifts"
    ,"                 $-1    salary"
    ,"                  $1  liabilities:debts"
    ,"--------------------"
    ,"                  $1"
    ]

   ,"balance report negative account pattern always matches full name" ~: 
    ([], ["not:e"]) `gives` []

   ,"balance report negative patterns affect totals" ~: 
    ([], ["expenses","not:food"]) `gives`
    ["                  $1  expenses:supplies"
    ,"--------------------"
    ,"                  $1"
    ]

   ,"balance report with -E shows zero-balance accounts" ~:
    ([SubTotal,Empty], ["assets"]) `gives`
    ["                 $-1  assets"
    ,"                  $1    bank"
    ,"                  $0      checking"
    ,"                  $1      saving"
    ,"                 $-2    cash"
    ,"--------------------"
    ,"                 $-1"
    ]

   ,"balance report with cost basis" ~: do
      rl <- rawLedgerFromString $ unlines
             [""
             ,"2008/1/1 test           "
             ,"  a:b          10h @ $50"
             ,"  c:d                   "
             ,""
             ]
      let l = cacheLedger [] $ 
              filterRawLedger (DateSpan Nothing Nothing) [] Nothing False $ 
              canonicaliseAmounts True rl -- enable cost basis adjustment            
      showBalanceReport [] [] l `is` 
       unlines
        ["                $500  a:b"
        ,"               $-500  c:d"
        ]

   ,"balance report elides zero-balance root account(s)" ~: do
      l <- ledgerFromStringWithOpts [] [] sampletime
             (unlines
              ["2008/1/1 one"
              ,"  test:a  1"
              ,"  test:b"
              ])
      showBalanceReport [] [] l `is`
       unlines
        ["                   1  test:a"
        ,"                  -1  test:b"
        ]

   ]

  ,"balanceLedgerTransaction" ~: do
     assertBool "detect unbalanced entry, sign error"
                    (isLeft $ balanceLedgerTransaction
                           (LedgerTransaction (parsedate "2007/01/28") False "" "test" ""
                            [Posting False "a" (Mixed [dollars 1]) "" RegularPosting, 
                             Posting False "b" (Mixed [dollars 1]) "" RegularPosting
                            ] ""))
     assertBool "detect unbalanced entry, multiple missing amounts"
                    (isLeft $ balanceLedgerTransaction
                           (LedgerTransaction (parsedate "2007/01/28") False "" "test" ""
                            [Posting False "a" missingamt "" RegularPosting, 
                             Posting False "b" missingamt "" RegularPosting
                            ] ""))
     let e = balanceLedgerTransaction (LedgerTransaction (parsedate "2007/01/28") False "" "test" ""
                           [Posting False "a" (Mixed [dollars 1]) "" RegularPosting, 
                            Posting False "b" missingamt "" RegularPosting
                           ] "")
     assertBool "one missing amount should be ok" (isRight e)
     assertEqual "balancing amount is added" 
                     (Mixed [dollars (-1)])
                     (case e of
                        Right e' -> (pamount $ last $ ltpostings e')
                        Left _ -> error "should not happen")

  ,"cacheLedger" ~: do
    (length $ Map.keys $ accountmap $ cacheLedger [] rawledger7) `is` 15

  ,"canonicaliseAmounts" ~:
   "use the greatest precision" ~: do
    (rawLedgerPrecisions $ canonicaliseAmounts False $ rawLedgerWithAmounts ["1","2.00"]) `is` [2,2]

  ,"commodities" ~: do
    commodities ledger7 `is` [Commodity {symbol="$", side=L, spaced=False, comma=False, precision=2}]

  ,"dateSpanFromOpts" ~: do
    let todaysdate = parsedate "2008/11/26"
    let opts `gives` spans = show (dateSpanFromOpts todaysdate opts) `is` spans
    [] `gives` "DateSpan Nothing Nothing"
    [Begin "2008", End "2009"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
    [Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
    [Begin "2005", End "2007",Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"

  -- don't know what this should do
  -- ,"elideAccountName" ~: do
  --    (elideAccountName 50 "aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa"
  --     `is` "aa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa")
  --    (elideAccountName 20 "aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa"
  --     `is` "aa:aa:aaaaaaaaaaaaaa")

  ,"entriesFromTimeLogEntries" ~: do
     today <- getCurrentDay
     now' <- getCurrentTime
     tz <- getCurrentTimeZone
     let now = utcToLocalTime tz now'
         nowstr = showtime now
         yesterday = prevday today
         clockin t a = TimeLogEntry In t a
         mktime d s = LocalTime d $ fromMaybe midnight $ parseTime defaultTimeLocale "%H:%M:%S" s
         showtime t = formatTime defaultTimeLocale "%H:%M" t
         assertEntriesGiveStrings name es ss = assertEqual name ss (map ltdescription $ entriesFromTimeLogEntries now es)

     assertEntriesGiveStrings "started yesterday, split session at midnight"
                                  [clockin (mktime yesterday "23:00:00") ""]
                                  ["23:00-23:59","00:00-"++nowstr]
     assertEntriesGiveStrings "split multi-day sessions at each midnight"
                                  [clockin (mktime (addDays (-2) today) "23:00:00") ""]
                                  ["23:00-23:59","00:00-23:59","00:00-"++nowstr]
     assertEntriesGiveStrings "auto-clock-out if needed" 
                                  [clockin (mktime today "00:00:00") ""] 
                                  ["00:00-"++nowstr]
     let future = utcToLocalTime tz $ addUTCTime 100 now'
         futurestr = showtime future
     assertEntriesGiveStrings "use the clockin time for auto-clockout if it's in the future"
                                  [clockin future ""]
                                  [printf "%s-%s" futurestr futurestr]

  ,"expandAccountNames" ~: do
    expandAccountNames ["assets:cash","assets:checking","expenses:vacation"] `is`
     ["assets","assets:cash","assets:checking","expenses","expenses:vacation"]

  ,"intervalFromOpts" ~: do
    let opts `gives` interval = intervalFromOpts opts `is` interval
    [] `gives` NoInterval
    [WeeklyOpt] `gives` Weekly
    [MonthlyOpt] `gives` Monthly
    [QuarterlyOpt] `gives` Quarterly
    [YearlyOpt] `gives` Yearly
    [Period "weekly"] `gives` Weekly
    [Period "monthly"] `gives` Monthly
    [Period "quarterly"] `gives` Quarterly
    [WeeklyOpt, Period "yearly"] `gives` Yearly

  ,"isAccountNamePrefixOf" ~: do
    "assets" `isAccountNamePrefixOf` "assets" `is` False
    "assets" `isAccountNamePrefixOf` "assets:bank" `is` True
    "assets" `isAccountNamePrefixOf` "assets:bank:checking" `is` True
    "my assets" `isAccountNamePrefixOf` "assets:bank" `is` False

  ,"isLedgerTransactionBalanced" ~: do
     assertBool "detect balanced"
        (isLedgerTransactionBalanced
        (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
         [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
         ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting
         ] ""))
     assertBool "detect unbalanced"
        (not $ isLedgerTransactionBalanced
        (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
         [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
         ,Posting False "c" (Mixed [dollars (-1.01)]) "" RegularPosting
         ] ""))
     assertBool "detect unbalanced, one posting"
        (not $ isLedgerTransactionBalanced
        (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
         [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
         ] ""))
     assertBool "one zero posting is considered balanced for now"
        (isLedgerTransactionBalanced
        (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
         [Posting False "b" (Mixed [dollars 0]) "" RegularPosting
         ] ""))
     assertBool "virtual postings don't need to balance"
        (isLedgerTransactionBalanced
        (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
         [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
         ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting
         ,Posting False "d" (Mixed [dollars 100]) "" VirtualPosting
         ] ""))
     assertBool "balanced virtual postings need to balance among themselves"
        (not $ isLedgerTransactionBalanced
        (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
         [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
         ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting
         ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting
         ] ""))
     assertBool "balanced virtual postings need to balance among themselves (2)"
        (isLedgerTransactionBalanced
        (LedgerTransaction (parsedate "2009/01/01") False "" "a" ""
         [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting
         ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting
         ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting
         ,Posting False "e" (Mixed [dollars (-100)]) "" BalancedVirtualPosting
         ] ""))

  ,"isSubAccountNameOf" ~: do
    "assets" `isSubAccountNameOf` "assets" `is` False
    "assets:bank" `isSubAccountNameOf` "assets" `is` True
    "assets:bank:checking" `isSubAccountNameOf` "assets" `is` False
    "assets:bank" `isSubAccountNameOf` "my assets" `is` False

  ,"default year" ~: do
    rl <- rawLedgerFromString defaultyear_ledger_str
    (ltdate $ head $ ledger_txns rl) `is` fromGregorian 2009 1 1
    return ()

  ,"ledgerFile" ~: do
    assertBool "ledgerFile should parse an empty file" $ (isRight $ parseWithCtx ledgerFile "")
    r <- rawLedgerFromString "" -- don't know how to get it from ledgerFile
    assertBool "ledgerFile parsing an empty file should give an empty ledger" $ null $ ledger_txns r

  ,"ledgerHistoricalPrice" ~: do
    parseWithCtx ledgerHistoricalPrice price1_str `parseis` price1

  ,"ledgerTransaction" ~: do
    parseWithCtx ledgerTransaction entry1_str `parseis` entry1
    assertBool "ledgerTransaction should not parse just a date"
                   $ isLeft $ parseWithCtx ledgerTransaction "2009/1/1\n"
    assertBool "ledgerTransaction should require some postings"
                   $ isLeft $ parseWithCtx ledgerTransaction "2009/1/1 a\n"
    let t = parseWithCtx ledgerTransaction "2009/1/1 a ;comment\n b 1\n"
    assertBool "ledgerTransaction should not include a comment in the description"
                   $ either (const False) ((== "a") . ltdescription) t

  ,"ledgeraccountname" ~: do
    assertBool "ledgeraccountname parses a normal accountname" $ (isRight $ parsewith ledgeraccountname "a:b:c")
    assertBool "ledgeraccountname rejects an empty inner component" $ (isLeft $ parsewith ledgeraccountname "a::c")
    assertBool "ledgeraccountname rejects an empty leading component" $ (isLeft $ parsewith ledgeraccountname ":b:c")
    assertBool "ledgeraccountname rejects an empty trailing component" $ (isLeft $ parsewith ledgeraccountname "a:b:")

  ,"ledgerposting" ~: do
    parseWithCtx ledgerposting rawposting1_str `parseis` rawposting1

  ,"parsedate" ~: do
    parsedate "2008/02/03" `is` parsetimewith "%Y/%m/%d" "2008/02/03" sampledate
    parsedate "2008-02-03" `is` parsetimewith "%Y/%m/%d" "2008/02/03" sampledate

  ,"period expressions" ~: do
    let todaysdate = parsedate "2008/11/26"
    let str `gives` result = (show $ parsewith (periodexpr todaysdate) str) `is` ("Right "++result)
    "from aug to oct"           `gives` "(NoInterval,DateSpan (Just 2008-08-01) (Just 2008-10-01))"
    "aug to oct"                `gives` "(NoInterval,DateSpan (Just 2008-08-01) (Just 2008-10-01))"
    "every day from aug to oct" `gives` "(Daily,DateSpan (Just 2008-08-01) (Just 2008-10-01))"
    "daily from aug"            `gives` "(Daily,DateSpan (Just 2008-08-01) Nothing)"
    "every week to 2009"        `gives` "(Weekly,DateSpan Nothing (Just 2009-01-01))"

  ,"print report tests" ~: TestList
  [

   "print expenses" ~:
   do 
    let args = ["expenses"]
    l <- sampleledgerwithopts [] args
    showLedgerTransactions [] args l `is` unlines 
     ["2008/06/03 * eat & shop"
     ,"    expenses:food                                 $1"
     ,"    expenses:supplies                             $1"
     ,"    assets:cash"
     ,""
     ]

  , "print report with depth arg" ~:
   do 
    l <- sampleledger
    showLedgerTransactions [Depth "2"] [] l `is` unlines
      ["2008/01/01 income"
      ,"    income:salary                                $-1"
      ,""
      ,"2008/06/01 gift"
      ,"    income:gifts                                 $-1"
      ,""
      ,"2008/06/03 * eat & shop"
      ,"    expenses:food                                 $1"
      ,"    expenses:supplies                             $1"
      ,"    assets:cash"
      ,""
      ,"2008/12/31 * pay off"
      ,"    liabilities:debts                             $1"
      ,""
      ]

  ]

  ,"punctuatethousands 1" ~: punctuatethousands "" `is` ""

  ,"punctuatethousands 2" ~: punctuatethousands "1234567.8901" `is` "1,234,567.8901"

  ,"punctuatethousands 3" ~: punctuatethousands "-100" `is` "-100"

  ,"register report tests" ~:
  let registerdates = filter (not . null) .  map (strip . take 10) . lines
  in
  TestList
  [

   "register report with no args" ~:
   do 
    l <- sampleledger
    showRegisterReport [] [] l `is` unlines
     ["2008/01/01 income               assets:bank:checking             $1           $1"
     ,"                                income:salary                   $-1            0"
     ,"2008/06/01 gift                 assets:bank:checking             $1           $1"
     ,"                                income:gifts                    $-1            0"
     ,"2008/06/02 save                 assets:bank:saving               $1           $1"
     ,"                                assets:bank:checking            $-1            0"
     ,"2008/06/03 eat & shop           expenses:food                    $1           $1"
     ,"                                expenses:supplies                $1           $2"
     ,"                                assets:cash                     $-2            0"
     ,"2008/12/31 pay off              liabilities:debts                $1           $1"
     ,"                                assets:bank:checking            $-1            0"
     ]

  ,"register report with cleared arg" ~:
   do 
    l <- ledgerFromStringWithOpts [Cleared] [] sampletime sample_ledger_str
    showRegisterReport [Cleared] [] l `is` unlines
     ["2008/06/03 eat & shop           expenses:food                    $1           $1"
     ,"                                expenses:supplies                $1           $2"
     ,"                                assets:cash                     $-2            0"
     ,"2008/12/31 pay off              liabilities:debts                $1           $1"
     ,"                                assets:bank:checking            $-1            0"
     ]

  ,"register report with uncleared arg" ~:
   do 
    l <- ledgerFromStringWithOpts [UnCleared] [] sampletime sample_ledger_str
    showRegisterReport [UnCleared] [] l `is` unlines
     ["2008/01/01 income               assets:bank:checking             $1           $1"
     ,"                                income:salary                   $-1            0"
     ,"2008/06/01 gift                 assets:bank:checking             $1           $1"
     ,"                                income:gifts                    $-1            0"
     ,"2008/06/02 save                 assets:bank:saving               $1           $1"
     ,"                                assets:bank:checking            $-1            0"
     ]

  ,"register report sorts by date" ~:
   do 
    l <- ledgerFromStringWithOpts [] [] sampletime $ unlines
        ["2008/02/02 a"
        ,"  b  1"
        ,"  c"
        ,""
        ,"2008/01/01 d"
        ,"  e  1"
        ,"  f"
        ]
    registerdates (showRegisterReport [] [] l) `is` ["2008/01/01","2008/02/02"]

  ,"register report with account pattern" ~:
   do
    l <- sampleledger
    showRegisterReport [] ["cash"] l `is` unlines
     ["2008/06/03 eat & shop           assets:cash                     $-2          $-2"
     ]

  ,"register report with account pattern, case insensitive" ~:
   do 
    l <- sampleledger
    showRegisterReport [] ["cAsH"] l `is` unlines
     ["2008/06/03 eat & shop           assets:cash                     $-2          $-2"
     ]

  ,"register report with display expression" ~:
   do 
    l <- sampleledger
    let displayexpr `gives` dates = 
            registerdates (showRegisterReport [Display displayexpr] [] l) `is` dates
    "d<[2008/6/2]"  `gives` ["2008/01/01","2008/06/01"]
    "d<=[2008/6/2]" `gives` ["2008/01/01","2008/06/01","2008/06/02"]
    "d=[2008/6/2]"  `gives` ["2008/06/02"]
    "d>=[2008/6/2]" `gives` ["2008/06/02","2008/06/03","2008/12/31"]
    "d>[2008/6/2]"  `gives` ["2008/06/03","2008/12/31"]

  ,"register report with period expression" ~:
   do 
    l <- sampleledger    
    let periodexpr `gives` dates = do
          lopts <- sampleledgerwithopts [Period periodexpr] []
          registerdates (showRegisterReport [Period periodexpr] [] lopts) `is` dates
    ""     `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]
    "2008" `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]
    "2007" `gives` []
    "june" `gives` ["2008/06/01","2008/06/02","2008/06/03"]
    "monthly" `gives` ["2008/01/01","2008/06/01","2008/12/01"]
    "quarterly" `gives` ["2008/01/01","2008/04/01","2008/10/01"]
    showRegisterReport [Period "yearly"] [] l `is` unlines
     ["2008/01/01 - 2008/12/31         assets:bank:saving               $1           $1"
     ,"                                assets:cash                     $-2          $-1"
     ,"                                expenses:food                    $1            0"
     ,"                                expenses:supplies                $1           $1"
     ,"                                income:gifts                    $-1            0"
     ,"                                income:salary                   $-1          $-1"
     ,"                                liabilities:debts                $1            0"
     ]
    registerdates (showRegisterReport [Period "quarterly"] [] l) `is` ["2008/01/01","2008/04/01","2008/10/01"]
    registerdates (showRegisterReport [Period "quarterly",Empty] [] l) `is` ["2008/01/01","2008/04/01","2008/07/01","2008/10/01"]

  ]

  , "register report with depth arg" ~:
   do 
    l <- sampleledger
    showRegisterReport [Depth "2"] [] l `is` unlines
     ["2008/01/01 income               income:salary                   $-1          $-1"
     ,"2008/06/01 gift                 income:gifts                    $-1          $-2"
     ,"2008/06/03 eat & shop           expenses:food                    $1          $-1"
     ,"                                expenses:supplies                $1            0"
     ,"                                assets:cash                     $-2          $-2"
     ,"2008/12/31 pay off              liabilities:debts                $1          $-1"
     ]

  ,"show dollars" ~: show (dollars 1) ~?= "$1.00"

  ,"show hours" ~: show (hours 1) ~?= "1.0h"

  ,"showLedgerTransaction" ~: do
     assertEqual "show a balanced transaction, eliding last amount"
       (unlines
        ["2007/01/28 coopportunity"
        ,"    expenses:food:groceries                   $47.18"
        ,"    assets:checking"
        ,""
        ])
       (showLedgerTransaction
        (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
         [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting
         ,Posting False "assets:checking" (Mixed [dollars (-47.18)]) "" RegularPosting
         ] ""))
     -- document some cases that arise in debug/testing:
     assertEqual "show an unbalanced transaction, should not elide"
       (unlines
        ["2007/01/28 coopportunity"
        ,"    expenses:food:groceries                   $47.18"
        ,"    assets:checking                          $-47.19"
        ,""
        ])
       (showLedgerTransaction
        (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
         [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting
         ,Posting False "assets:checking" (Mixed [dollars (-47.19)]) "" RegularPosting
         ] ""))
     assertEqual "show an unbalanced transaction with one posting, should not elide"
       (unlines
        ["2007/01/28 coopportunity"
        ,"    expenses:food:groceries                   $47.18"
        ,""
        ])
       (showLedgerTransaction
        (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
         [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting
         ] ""))
     assertEqual "show a transaction with one posting and a missing amount"
       (unlines
        ["2007/01/28 coopportunity"
        ,"    expenses:food:groceries                         "
        ,""
        ])
       (showLedgerTransaction
        (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
         [Posting False "expenses:food:groceries" missingamt "" RegularPosting
         ] ""))

  ,"unicode in balance layout" ~: do
    l <- ledgerFromStringWithOpts [] [] sampletime
      "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"
    showBalanceReport [] [] l `is` unlines
      ["                -100  актив:наличные"
      ,"                 100  расходы:покупки"]

  ,"unicode in register layout" ~: do
    l <- ledgerFromStringWithOpts [] [] sampletime
      "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"
    showRegisterReport [] [] l `is` unlines
      ["2009/01/01 медвежья шкура       расходы:покупки                 100          100"
      ,"                                актив:наличные                 -100            0"]

  ,"smart dates" ~: do
    let str `gives` datestr = fixSmartDateStr (parsedate "2008/11/26") str `is` datestr
    "1999-12-02"   `gives` "1999/12/02"
    "1999.12.02"   `gives` "1999/12/02"
    "1999/3/2"     `gives` "1999/03/02"
    "19990302"     `gives` "1999/03/02"
    "2008/2"       `gives` "2008/02/01"
    "20/2"         `gives` "0020/02/01"
    "1000"         `gives` "1000/01/01"
    "4/2"          `gives` "2008/04/02"
    "2"            `gives` "2008/11/02"
    "January"      `gives` "2008/01/01"
    "feb"          `gives` "2008/02/01"
    "today"        `gives` "2008/11/26"
    "yesterday"    `gives` "2008/11/25"
    "tomorrow"     `gives` "2008/11/27"
    "this day"     `gives` "2008/11/26"
    "last day"     `gives` "2008/11/25"
    "next day"     `gives` "2008/11/27"
    "this week"    `gives` "2008/11/24" -- last monday
    "last week"    `gives` "2008/11/17" -- previous monday
    "next week"    `gives` "2008/12/01" -- next monday
    "this month"   `gives` "2008/11/01"
    "last month"   `gives` "2008/10/01"
    "next month"   `gives` "2008/12/01"
    "this quarter" `gives` "2008/10/01"
    "last quarter" `gives` "2008/07/01"
    "next quarter" `gives` "2009/01/01"
    "this year"    `gives` "2008/01/01"
    "last year"    `gives` "2007/01/01"
    "next year"    `gives` "2009/01/01"
--     "last wed"     `gives` "2008/11/19"
--     "next friday"  `gives` "2008/11/28"
--     "next january" `gives` "2009/01/01"

  ,"splitSpan" ~: do
    let (interval,span) `gives` spans = splitSpan interval span `is` spans
    (NoInterval,mkdatespan "2008/01/01" "2009/01/01") `gives`
     [mkdatespan "2008/01/01" "2009/01/01"]
    (Quarterly,mkdatespan "2008/01/01" "2009/01/01") `gives`
     [mkdatespan "2008/01/01" "2008/04/01"
     ,mkdatespan "2008/04/01" "2008/07/01"
     ,mkdatespan "2008/07/01" "2008/10/01"
     ,mkdatespan "2008/10/01" "2009/01/01"
     ]
    (Quarterly,nulldatespan) `gives`
     [nulldatespan]
    (Daily,mkdatespan "2008/01/01" "2008/01/01") `gives`
     [mkdatespan "2008/01/01" "2008/01/01"]
    (Quarterly,mkdatespan "2008/01/01" "2008/01/01") `gives`
     [mkdatespan "2008/01/01" "2008/01/01"]

  ,"subAccounts" ~: do
    l <- sampleledger
    let a = ledgerAccount l "assets"
    (map aname $ ledgerSubAccounts l a) `is` ["assets:bank","assets:cash"]

  ,"summariseTransactionsInDateSpan" ~: do
    let (b,e,tnum,depth,showempty,ts) `gives` summaryts = 
            summariseTransactionsInDateSpan (mkdatespan b e) tnum depth showempty ts `is` summaryts
    let ts =
            [
             nulltxn{tdescription="desc",taccount="expenses:food:groceries",tamount=Mixed [dollars 1]}
            ,nulltxn{tdescription="desc",taccount="expenses:food:dining",   tamount=Mixed [dollars 2]}
            ,nulltxn{tdescription="desc",taccount="expenses:food",          tamount=Mixed [dollars 4]}
            ,nulltxn{tdescription="desc",taccount="expenses:food:dining",   tamount=Mixed [dollars 8]}
            ]
    ("2008/01/01","2009/01/01",0,9999,False,[]) `gives` 
     []
    ("2008/01/01","2009/01/01",0,9999,True,[]) `gives` 
     [
      nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31"}
     ]
    ("2008/01/01","2009/01/01",0,9999,False,ts) `gives` 
     [
      nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food",          tamount=Mixed [dollars 4]}
     ,nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food:dining",   tamount=Mixed [dollars 10]}
     ,nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food:groceries",tamount=Mixed [dollars 1]}
     ]
    ("2008/01/01","2009/01/01",0,2,False,ts) `gives` 
     [
      nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food",tamount=Mixed [dollars 15]}
     ]
    ("2008/01/01","2009/01/01",0,1,False,ts) `gives` 
     [
      nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses",tamount=Mixed [dollars 15]}
     ]
    ("2008/01/01","2009/01/01",0,0,False,ts) `gives` 
     [
      nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="",tamount=Mixed [dollars 15]}
     ]

  ,"postingamount" ~: do
    parseWithCtx postingamount " $47.18" `parseis` Mixed [dollars 47.18]
    parseWithCtx postingamount " $1." `parseis` 
     Mixed [Amount (Commodity {symbol="$",side=L,spaced=False,comma=False,precision=0}) 1 Nothing]

  ]

  
------------------------------------------------------------------------------
-- test data

sampledate = parsedate "2008/11/26"
sampletime = LocalTime sampledate midday
sampleledger = ledgerFromStringWithOpts [] [] sampletime sample_ledger_str
sampleledgerwithopts opts args = ledgerFromStringWithOpts opts args sampletime sample_ledger_str

sample_ledger_str = unlines
 ["; A sample ledger file."
 ,";"
 ,"; Sets up this account tree:"
 ,"; assets"
 ,";   bank"
 ,";     checking"
 ,";     saving"
 ,";   cash"
 ,"; expenses"
 ,";   food"
 ,";   supplies"
 ,"; income"
 ,";   gifts"
 ,";   salary"
 ,"; liabilities"
 ,";   debts"
 ,""
 ,"2008/01/01 income"
 ,"    assets:bank:checking  $1"
 ,"    income:salary"
 ,""
 ,"2008/06/01 gift"
 ,"    assets:bank:checking  $1"
 ,"    income:gifts"
 ,""
 ,"2008/06/02 save"
 ,"    assets:bank:saving  $1"
 ,"    assets:bank:checking"
 ,""
 ,"2008/06/03 * eat & shop"
 ,"    expenses:food      $1"
 ,"    expenses:supplies  $1"
 ,"    assets:cash"
 ,""
 ,"2008/12/31 * pay off"
 ,"    liabilities:debts  $1"
 ,"    assets:bank:checking"
 ,""
 ,""
 ,";final comment"
 ]

defaultyear_ledger_str = unlines
 ["Y2009"
 ,""
 ,"01/01 A"
 ,"    a  $1"
 ,"    b"
 ]

write_sample_ledger = writeFile "sample.ledger" sample_ledger_str

rawposting1_str  = "  expenses:food:dining  $10.00\n"

rawposting1 = Posting False "expenses:food:dining" (Mixed [dollars 10]) "" RegularPosting

entry1_str = unlines
 ["2007/01/28 coopportunity"
 ,"    expenses:food:groceries                   $47.18"
 ,"    assets:checking"
 ,""
 ]

entry1 =
    (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" ""
     [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting, 
      Posting False "assets:checking" (Mixed [dollars (-47.18)]) "" RegularPosting] "")


entry2_str = unlines
 ["2007/01/27 * joes diner"
 ,"    expenses:food:dining                      $10.00"
 ,"    expenses:gifts                            $10.00"
 ,"    assets:checking                          $-20.00"
 ,""
 ]

entry3_str = unlines
 ["2007/01/01 * opening balance"
 ,"    assets:cash                                $4.82"
 ,"    equity:opening balances"
 ,""
 ,"2007/01/01 * opening balance"
 ,"    assets:cash                                $4.82"
 ,"    equity:opening balances"
 ,""
 ,"2007/01/28 coopportunity"
 ,"  expenses:food:groceries                 $47.18"
 ,"  assets:checking"
 ,""
 ]

periodic_entry1_str = unlines
 ["~ monthly from 2007/2/2"
 ,"  assets:saving            $200.00"
 ,"  assets:checking"
 ,""
 ]

periodic_entry2_str = unlines
 ["~ monthly from 2007/2/2"
 ,"  assets:saving            $200.00         ;auto savings"
 ,"  assets:checking"
 ,""
 ]

periodic_entry3_str = unlines
 ["~ monthly from 2007/01/01"
 ,"    assets:cash                                $4.82"
 ,"    equity:opening balances"
 ,""
 ,"~ monthly from 2007/01/01"
 ,"    assets:cash                                $4.82"
 ,"    equity:opening balances"
 ,""
 ]

ledger1_str = unlines
 [""
 ,"2007/01/27 * joes diner"
 ,"  expenses:food:dining                    $10.00"
 ,"  expenses:gifts                          $10.00"
 ,"  assets:checking                        $-20.00"
 ,""
 ,""
 ,"2007/01/28 coopportunity"
 ,"  expenses:food:groceries                 $47.18"
 ,"  assets:checking                        $-47.18"
 ,""
 ,""
 ]

ledger2_str = unlines
 [";comment"
 ,"2007/01/27 * joes diner"
 ,"  expenses:food:dining                    $10.00"
 ,"  assets:checking                        $-47.18"
 ,""
 ]

ledger3_str = unlines
 ["2007/01/27 * joes diner"
 ,"  expenses:food:dining                    $10.00"
 ,";intra-entry comment"
 ,"  assets:checking                        $-47.18"
 ,""
 ]

ledger4_str = unlines
 ["!include \"somefile\""
 ,"2007/01/27 * joes diner"
 ,"  expenses:food:dining                    $10.00"
 ,"  assets:checking                        $-47.18"
 ,""
 ]

ledger5_str = ""

ledger6_str = unlines
 ["~ monthly from 2007/1/21"
 ,"    expenses:entertainment  $16.23        ;netflix"
 ,"    assets:checking"
 ,""
 ,"; 2007/01/01 * opening balance"
 ,";     assets:saving                            $200.04"
 ,";     equity:opening balances                         "
 ,""
 ]

ledger7_str = unlines
 ["2007/01/01 * opening balance"
 ,"    assets:cash                                $4.82"
 ,"    equity:opening balances                         "
 ,""
 ,"2007/01/01 * opening balance"
 ,"    income:interest                                $-4.82"
 ,"    equity:opening balances                         "
 ,""
 ,"2007/01/02 * ayres suites"
 ,"    expenses:vacation                        $179.92"
 ,"    assets:checking                                 "
 ,""
 ,"2007/01/02 * auto transfer to savings"
 ,"    assets:saving                            $200.00"
 ,"    assets:checking                                 "
 ,""
 ,"2007/01/03 * poquito mas"
 ,"    expenses:food:dining                       $4.82"
 ,"    assets:cash                                     "
 ,""
 ,"2007/01/03 * verizon"
 ,"    expenses:phone                            $95.11"
 ,"    assets:checking                                 "
 ,""
 ,"2007/01/03 * discover"
 ,"    liabilities:credit cards:discover         $80.00"
 ,"    assets:checking                                 "
 ,""
 ,"2007/01/04 * blue cross"
 ,"    expenses:health:insurance                 $90.00"
 ,"    assets:checking                                 "
 ,""
 ,"2007/01/05 * village market liquor"
 ,"    expenses:food:dining                       $6.48"
 ,"    assets:checking                                 "
 ,""
 ]

rawledger7 = RawLedger
          [] 
          [] 
          [
           LedgerTransaction {
             ltdate= parsedate "2007/01/01", 
             ltstatus=False, 
             ltcode="*", 
             ltdescription="opening balance", 
             ltcomment="",
             ltpostings=[
              Posting {
                pstatus=False,
                paccount="assets:cash", 
                pamount=(Mixed [dollars 4.82]),
                pcomment="",
                ptype=RegularPosting
              },
              Posting {
                pstatus=False,
                paccount="equity:opening balances", 
                pamount=(Mixed [dollars (-4.82)]),
                pcomment="",
                ptype=RegularPosting
              }
             ],
             ltpreceding_comment_lines=""
           }
          ,
           LedgerTransaction {
             ltdate= parsedate "2007/02/01", 
             ltstatus=False, 
             ltcode="*", 
             ltdescription="ayres suites", 
             ltcomment="",
             ltpostings=[
              Posting {
                pstatus=False,
                paccount="expenses:vacation", 
                pamount=(Mixed [dollars 179.92]),
                pcomment="",
                ptype=RegularPosting
              },
              Posting {
                pstatus=False,
                paccount="assets:checking", 
                pamount=(Mixed [dollars (-179.92)]),
                pcomment="",
                ptype=RegularPosting
              }
             ],
             ltpreceding_comment_lines=""
           }
          ,
           LedgerTransaction {
             ltdate=parsedate "2007/01/02", 
             ltstatus=False, 
             ltcode="*", 
             ltdescription="auto transfer to savings", 
             ltcomment="",
             ltpostings=[
              Posting {
                pstatus=False,
                paccount="assets:saving", 
                pamount=(Mixed [dollars 200]),
                pcomment="",
                ptype=RegularPosting
              },
              Posting {
                pstatus=False,
                paccount="assets:checking", 
                pamount=(Mixed [dollars (-200)]),
                pcomment="",
                ptype=RegularPosting
              }
             ],
             ltpreceding_comment_lines=""
           }
          ,
           LedgerTransaction {
             ltdate=parsedate "2007/01/03", 
             ltstatus=False, 
             ltcode="*", 
             ltdescription="poquito mas", 
             ltcomment="",
             ltpostings=[
              Posting {
                pstatus=False,
                paccount="expenses:food:dining", 
                pamount=(Mixed [dollars 4.82]),
                pcomment="",
                ptype=RegularPosting
              },
              Posting {
                pstatus=False,
                paccount="assets:cash", 
                pamount=(Mixed [dollars (-4.82)]),
                pcomment="",
                ptype=RegularPosting
              }
             ],
             ltpreceding_comment_lines=""
           }
          ,
           LedgerTransaction {
             ltdate=parsedate "2007/01/03", 
             ltstatus=False, 
             ltcode="*", 
             ltdescription="verizon", 
             ltcomment="",
             ltpostings=[
              Posting {
                pstatus=False,
                paccount="expenses:phone", 
                pamount=(Mixed [dollars 95.11]),
                pcomment="",
                ptype=RegularPosting
              },
              Posting {
                pstatus=False,
                paccount="assets:checking", 
                pamount=(Mixed [dollars (-95.11)]),
                pcomment="",
                ptype=RegularPosting
              }
             ],
             ltpreceding_comment_lines=""
           }
          ,
           LedgerTransaction {
             ltdate=parsedate "2007/01/03", 
             ltstatus=False, 
             ltcode="*", 
             ltdescription="discover", 
             ltcomment="",
             ltpostings=[
              Posting {
                pstatus=False,
                paccount="liabilities:credit cards:discover", 
                pamount=(Mixed [dollars 80]),
                pcomment="",
                ptype=RegularPosting
              },
              Posting {
                pstatus=False,
                paccount="assets:checking", 
                pamount=(Mixed [dollars (-80)]),
                pcomment="",
                ptype=RegularPosting
              }
             ],
             ltpreceding_comment_lines=""
           }
          ] 
          []
          []
          ""
          ""

ledger7 = cacheLedger [] rawledger7 

ledger8_str = unlines
 ["2008/1/1 test           "
 ,"  a:b          10h @ $40"
 ,"  c:d                   "
 ,""
 ]

timelogentry1_str  = "i 2007/03/11 16:19:00 hledger\n"
timelogentry1 = TimeLogEntry In (parsedatetime "2007/03/11 16:19:00") "hledger"

timelogentry2_str  = "o 2007/03/11 16:30:00\n"
timelogentry2 = TimeLogEntry Out (parsedatetime "2007/03/11 16:30:00") ""

price1_str = "P 2004/05/01 XYZ $55\n"
price1 = HistoricalPrice (parsedate "2004/05/01") "XYZ" "$" 55

a1 = Mixed [(hours 1){price=Just $ Mixed [Amount (comm "$") 10 Nothing]}]
a2 = Mixed [(hours 2){price=Just $ Mixed [Amount (comm "EUR") 10 Nothing]}]
a3 = Mixed $ (amounts a1) ++ (amounts a2)

rawLedgerWithAmounts :: [String] -> RawLedger
rawLedgerWithAmounts as = 
        RawLedger 
        [] 
        [] 
        [nullledgertxn{ltdescription=a,ltpostings=[nullrawposting{pamount=parse a}]} | a <- as]
        []
        []
        ""
        ""
    where parse = fromparse . parseWithCtx postingamount . (" "++)