Merge pull request #245 from afh/pull/DocTests
Make more examples from documentation testable
This commit is contained in:
commit
1ec9f71479
2 changed files with 131 additions and 49 deletions
102
doc/ledger3.texi
102
doc/ledger3.texi
|
|
@ -56,7 +56,8 @@
|
|||
@c the documentation itself, in that case the journal example data
|
||||
@c needs to be specially marked as well using @smallexample @c input:UUID,
|
||||
@c again with the UUID being the UUID of the corresponding ledger example
|
||||
@c command, e.g.:
|
||||
@c command. If multiple inputs with the same UUID are found they will be
|
||||
@c concatenated together and given as one set of data to the example command.
|
||||
@c
|
||||
@c @smallexample @c input:35CB2A3
|
||||
@c 2014/02/09 The Italian Place
|
||||
|
|
@ -72,7 +73,19 @@
|
|||
@c Assets:Cash
|
||||
@c Expenses:Food:Dining
|
||||
@c @end smallexample
|
||||
@c
|
||||
@c
|
||||
@c To use different example commands with the same input from the documentation
|
||||
@c add with_input:UUID to the example command, where UUID is the UUID of the input,
|
||||
@c e.g.:
|
||||
@c
|
||||
@c @smallexample @c command:94FD2B6,with_input:35CB2A3
|
||||
@c $ ledger -f inline.dat bal expenses
|
||||
@c @end smallexample
|
||||
@c
|
||||
@c @smallexample @c output:94FD2B6
|
||||
@c $ 36.84 Expenses:Food:Dining
|
||||
@c @end smallexample
|
||||
@c
|
||||
@c Additionally DocTests.py will pass --init-file /dev/null to ledger to
|
||||
@c ignore any default arguments to ledger the user running the tests
|
||||
@c has configured.
|
||||
|
|
@ -306,7 +319,7 @@ And just for the sake of example---as a starting point for those who
|
|||
want to dive in head-first---here are the journal transactions from
|
||||
above, formatted as the Ledger program wishes to see them:
|
||||
|
||||
@smallexample
|
||||
@smallexample @c input:48DDF26
|
||||
2004/09/29 Pacific Bell
|
||||
Expenses:Pacific Bell $23.00
|
||||
Assets:Checking
|
||||
|
|
@ -315,12 +328,37 @@ above, formatted as the Ledger program wishes to see them:
|
|||
The account balances and registers in this file, if saved as
|
||||
@file{ledger.dat}, could be reported using:
|
||||
|
||||
@smallexample
|
||||
@smallexample @c command:48DDF26
|
||||
$ ledger -f ledger.dat balance
|
||||
@end smallexample
|
||||
|
||||
@smallexample @c output:48DDF26
|
||||
$-23.00 Assets:Checking
|
||||
$23.00 Expenses:Pacific Bell
|
||||
--------------------
|
||||
0
|
||||
@end smallexample
|
||||
|
||||
Or
|
||||
|
||||
@smallexample @c command:8C7295F,with_input:48DDF26
|
||||
$ ledger -f ledger.dat register checking
|
||||
@end smallexample
|
||||
|
||||
@smallexample @c output:8C7295F
|
||||
04-Sep-29 Pacific Bell Assets:Checking $-23.00 $-23.00
|
||||
@end smallexample
|
||||
|
||||
And even:
|
||||
|
||||
@smallexample @c command:BB32EF2,with_input:48DDF26
|
||||
$ ledger -f ledger.dat register Bell
|
||||
@end smallexample
|
||||
|
||||
@smallexample @c output:BB32EF2
|
||||
04-Sep-29 Pacific Bell Expenses:Pacific Bell $23.00 $23.00
|
||||
@end smallexample
|
||||
|
||||
An important difference between Ledger and other finance packages is
|
||||
that Ledger will never alter your input file. You can create and edit
|
||||
that file in any way you prefer, but Ledger is only for analyzing the
|
||||
|
|
@ -548,7 +586,7 @@ If you would like to find transaction to only a certain payee use
|
|||
$ ledger -f drewr3.dat register payee "Organic"
|
||||
@end smallexample
|
||||
|
||||
@smallexample @c output:C10BC57E
|
||||
@smallexample @c output:C6BC57E
|
||||
10-Dec-20 Organic Co-op Expense:Food:Groceries $ 37.50 $ 37.50
|
||||
Expense:Food:Groceries $ 37.50 $ 75.00
|
||||
Expense:Food:Groceries $ 37.50 $ 112.50
|
||||
|
|
@ -700,7 +738,7 @@ owe. ``Liabilities'' is just a more inclusive name for Debts.
|
|||
An Asset is typically increased by transferring money from an Income
|
||||
account, such as when you get paid. Here is a typical transaction:
|
||||
|
||||
@smallexample
|
||||
@smallexample @c input:6B43DD4
|
||||
2004/09/29 My Employer
|
||||
Assets:Checking $500.00
|
||||
Income:Salary
|
||||
|
|
@ -715,7 +753,7 @@ borrow money to buy something, or if you owe someone money. Here is
|
|||
an example of increasing a MasterCard liability by spending money with
|
||||
it:
|
||||
|
||||
@smallexample
|
||||
@smallexample @c input:6B43DD4
|
||||
2004/09/30 Restaurant
|
||||
Expenses:Dining $25.00
|
||||
Liabilities:MasterCard
|
||||
|
|
@ -729,10 +767,17 @@ offsets the value of your assets.
|
|||
The combined total of your Assets and Liabilities is your net worth.
|
||||
So to see your current net worth, use this command:
|
||||
|
||||
@smallexample
|
||||
@smallexample @c command:6B43DD4
|
||||
$ ledger balance ^assets ^liabilities
|
||||
@end smallexample
|
||||
|
||||
@smallexample @c output:6B43DD4
|
||||
$500.00 Assets:Checking
|
||||
$-25.00 Liabilities:MasterCard
|
||||
--------------------
|
||||
$475.00
|
||||
@end smallexample
|
||||
|
||||
In a similar vein, your Income accounts show up negative, because they
|
||||
transfer money @emph{from} an account in order to increase your
|
||||
assets. Your Expenses show up positive because that is where the
|
||||
|
|
@ -741,10 +786,17 @@ flow. A positive cash flow means you are spending more than you make,
|
|||
since income is always a negative figure. To see your current cash
|
||||
flow, use this command:
|
||||
|
||||
@smallexample
|
||||
@smallexample @c command:DB128F3,with_input:6B43DD4
|
||||
$ ledger balance ^income ^expenses
|
||||
@end smallexample
|
||||
|
||||
@smallexample @c output:DB128F3
|
||||
$25.00 Expenses:Dining
|
||||
$-500.00 Income:Salary
|
||||
--------------------
|
||||
$-475.00
|
||||
@end smallexample
|
||||
|
||||
Another common question to ask of your expenses is: How much do I
|
||||
spend each month on X? Ledger provides a simple way of displaying
|
||||
monthly totals for any account. Here is an example that summarizes
|
||||
|
|
@ -1820,7 +1872,7 @@ function on a transaction-wide or per-posting basis.
|
|||
Lastly, you can specify the valuation function/value for any specific
|
||||
amount using the @samp{(( ))} commodity annotation.
|
||||
|
||||
@smallexample
|
||||
@smallexample @c input:814A366
|
||||
2012-03-02 KFC
|
||||
Expenses:Food2 $1 ((2 EUR))
|
||||
Assets:Cash2
|
||||
|
|
@ -1856,20 +1908,24 @@ amount using the @samp{(( ))} commodity annotation.
|
|||
Assets:Cash9
|
||||
@end smallexample
|
||||
|
||||
@smallexample
|
||||
ledger reg -V food
|
||||
@smallexample @c command:814A366
|
||||
$ ledger reg -V food
|
||||
@end smallexample
|
||||
|
||||
@smallexample @c output:814A366
|
||||
12-Mar-02 KFC Expenses:Food2 2 EUR 2 EUR
|
||||
12-Mar-03 KFC <Adjustment> -1 EUR 1 EUR
|
||||
Expenses:Food3 3 EUR 4 EUR
|
||||
12-Mar-04 KFC <Adjustment> -2 EUR 2 EUR
|
||||
Expenses:Food4 4 EUR 6 EUR
|
||||
12-Mar-05 KFC <Adjustment> -3 EUR 3 EUR
|
||||
Expenses:Food5 5 EUR 8 EUR
|
||||
12-Mar-06 KFC <Adjustment> -4 EUR 4 EUR
|
||||
Expenses:Food6 6 EUR 10 EUR
|
||||
12-Mar-07 KFC Expenses:Food7 7 EUR 17 EUR
|
||||
12-Mar-08 XACT Expenses:Food8 8 EUR 25 EUR
|
||||
12-Mar-09 POST (Expenses:Food9) 9 EUR 34 EUR
|
||||
12-Mar-03 KFC Expenses:Food3 3 EUR 5 EUR
|
||||
12-Mar-04 KFC Expenses:Food4 4 EUR 9 EUR
|
||||
12-Mar-05 KFC Expenses:Food5 $1 $1
|
||||
9 EUR
|
||||
12-Mar-06 KFC Expenses:Food6 $1 $2
|
||||
9 EUR
|
||||
12-Mar-07 KFC Expenses:Food7 1 CAD $2
|
||||
1 CAD
|
||||
9 EUR
|
||||
12-Mar-08 XACT Expenses:Food8 $1 $3
|
||||
1 CAD
|
||||
9 EUR
|
||||
@end smallexample
|
||||
|
||||
@node Keeping it Consistent, Journal Format, Currency and Commodities, Keeping a Journal
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class DocTests:
|
|||
self.testin_token = 'command'
|
||||
self.testout_token = 'output'
|
||||
self.testdat_token = 'input'
|
||||
self.testwithdat_token = 'with_input'
|
||||
|
||||
def read_example(self):
|
||||
endexample = re.compile(r'^@end\s+smallexample\s*$')
|
||||
|
|
@ -37,7 +38,7 @@ class DocTests:
|
|||
return hashlib.sha1(example.rstrip()).hexdigest()[0:7].upper()
|
||||
|
||||
def find_examples(self):
|
||||
startexample = re.compile(r'^@smallexample\s+@c\s+(%s|%s|%s)(?::([\dA-Fa-f]+))?'
|
||||
startexample = re.compile(r'^@smallexample\s+@c\s+(%s|%s|%s)(?::([\dA-Fa-f]+))?(?:,(.*))?'
|
||||
% (self.testin_token, self.testout_token, self.testdat_token))
|
||||
while True:
|
||||
line = self.file.readline()
|
||||
|
|
@ -50,6 +51,13 @@ class DocTests:
|
|||
test_begin_line = self.current_line
|
||||
test_kind = startmatch.group(1)
|
||||
test_id = startmatch.group(2)
|
||||
test_options = dict()
|
||||
for pair in re.split(r',\s*', str(startmatch.group(3))):
|
||||
kv = re.split(r':\s*', pair, 2)
|
||||
try:
|
||||
test_options[kv[0]] = kv[1]
|
||||
except IndexError:
|
||||
pass
|
||||
example = self.read_example()
|
||||
test_end_pos = self.file.tell()
|
||||
test_end_line = self.current_line
|
||||
|
|
@ -68,22 +76,52 @@ class DocTests:
|
|||
except KeyError:
|
||||
self.examples[test_id] = dict()
|
||||
|
||||
try:
|
||||
example = self.examples[test_id][test_kind][test_kind] + example
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self.examples[test_id][test_kind] = {
|
||||
'bpos': test_begin_pos,
|
||||
'epos': test_end_pos,
|
||||
'blin': test_begin_line,
|
||||
'elin': test_end_line,
|
||||
'opts': test_options,
|
||||
test_kind: example,
|
||||
}
|
||||
|
||||
def parse_command(self, test_id, example):
|
||||
try:
|
||||
command = example[self.testin_token][self.testin_token]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
command = command.rstrip().split()
|
||||
if command[0] == '$': command.remove('$')
|
||||
index = command.index('ledger')
|
||||
command[index] = self.ledger
|
||||
command.insert(index+1, '--init-file')
|
||||
command.insert(index+2, '/dev/null')
|
||||
try:
|
||||
findex = command.index('-f')
|
||||
except ValueError:
|
||||
try:
|
||||
findex = command.index('--file')
|
||||
except ValueError:
|
||||
findex = index+1
|
||||
command.insert(findex, '--file')
|
||||
command.insert(findex+1, test_id + '.dat')
|
||||
return (command, findex+1)
|
||||
|
||||
def test_examples(self):
|
||||
failed = set()
|
||||
for test_id in self.examples:
|
||||
example = self.examples[test_id]
|
||||
try:
|
||||
command = example[self.testin_token][self.testin_token]
|
||||
except KeyError:
|
||||
command = None
|
||||
(command, findex) = self.parse_command(test_id, example)
|
||||
except TypeError:
|
||||
failed.add(test_id)
|
||||
continue
|
||||
|
||||
try:
|
||||
output = example[self.testout_token][self.testout_token]
|
||||
|
|
@ -93,44 +131,32 @@ class DocTests:
|
|||
try:
|
||||
input = example[self.testdat_token][self.testdat_token]
|
||||
except KeyError:
|
||||
input = None
|
||||
try:
|
||||
with_input = example[self.testin_token]['opts'][self.testwithdat_token]
|
||||
input = self.examples[with_input][self.testdat_token][self.testdat_token]
|
||||
except KeyError:
|
||||
input = None
|
||||
|
||||
if command and output:
|
||||
command = command.rstrip().split()
|
||||
if command[0] == '$': command.remove('$')
|
||||
index = command.index('ledger')
|
||||
command[index] = self.ledger
|
||||
command.insert(index+1, '--init-file')
|
||||
command.insert(index+2, '/dev/null')
|
||||
try:
|
||||
findex = command.index('-f')
|
||||
except ValueError:
|
||||
try:
|
||||
findex = command.index('--file')
|
||||
except ValueError:
|
||||
findex = index+1
|
||||
command.insert(findex, '--file')
|
||||
command.insert(findex+1, test_id + '.dat')
|
||||
|
||||
test_file_created = False
|
||||
if findex:
|
||||
scriptpath = os.path.dirname(os.path.realpath(__file__))
|
||||
test_input_dir = scriptpath + '/../test/input/'
|
||||
test_file = command[findex+1]
|
||||
test_file_created = False
|
||||
test_file = command[findex]
|
||||
if not os.path.exists(test_file):
|
||||
if input:
|
||||
test_file_created = True
|
||||
with open(test_file, 'w') as f:
|
||||
f.write(input)
|
||||
elif os.path.exists(test_input_dir + test_file):
|
||||
command[findex+1] = test_input_dir + test_file
|
||||
command[findex] = test_input_dir + test_file
|
||||
try:
|
||||
verify = subprocess.check_output(command)
|
||||
except:
|
||||
verify = str()
|
||||
if test_file_created:
|
||||
os.remove(test_file)
|
||||
valid = (output == verify)
|
||||
if valid and test_file_created:
|
||||
os.remove(test_file)
|
||||
if self.verbose > 0:
|
||||
print test_id, ':', 'Passed' if valid else 'FAILED'
|
||||
else:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue