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 the documentation itself, in that case the journal example data
|
||||||
@c needs to be specially marked as well using @smallexample @c input:UUID,
|
@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 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
|
||||||
@c @smallexample @c input:35CB2A3
|
@c @smallexample @c input:35CB2A3
|
||||||
@c 2014/02/09 The Italian Place
|
@c 2014/02/09 The Italian Place
|
||||||
|
|
@ -72,7 +73,19 @@
|
||||||
@c Assets:Cash
|
@c Assets:Cash
|
||||||
@c Expenses:Food:Dining
|
@c Expenses:Food:Dining
|
||||||
@c @end smallexample
|
@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 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 ignore any default arguments to ledger the user running the tests
|
||||||
@c has configured.
|
@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
|
want to dive in head-first---here are the journal transactions from
|
||||||
above, formatted as the Ledger program wishes to see them:
|
above, formatted as the Ledger program wishes to see them:
|
||||||
|
|
||||||
@smallexample
|
@smallexample @c input:48DDF26
|
||||||
2004/09/29 Pacific Bell
|
2004/09/29 Pacific Bell
|
||||||
Expenses:Pacific Bell $23.00
|
Expenses:Pacific Bell $23.00
|
||||||
Assets:Checking
|
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
|
The account balances and registers in this file, if saved as
|
||||||
@file{ledger.dat}, could be reported using:
|
@file{ledger.dat}, could be reported using:
|
||||||
|
|
||||||
@smallexample
|
@smallexample @c command:48DDF26
|
||||||
$ ledger -f ledger.dat balance
|
$ 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
|
$ 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
|
$ ledger -f ledger.dat register Bell
|
||||||
@end smallexample
|
@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
|
An important difference between Ledger and other finance packages is
|
||||||
that Ledger will never alter your input file. You can create and edit
|
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
|
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"
|
$ ledger -f drewr3.dat register payee "Organic"
|
||||||
@end smallexample
|
@end smallexample
|
||||||
|
|
||||||
@smallexample @c output:C10BC57E
|
@smallexample @c output:C6BC57E
|
||||||
10-Dec-20 Organic Co-op Expense:Food:Groceries $ 37.50 $ 37.50
|
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 $ 75.00
|
||||||
Expense:Food:Groceries $ 37.50 $ 112.50
|
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
|
An Asset is typically increased by transferring money from an Income
|
||||||
account, such as when you get paid. Here is a typical transaction:
|
account, such as when you get paid. Here is a typical transaction:
|
||||||
|
|
||||||
@smallexample
|
@smallexample @c input:6B43DD4
|
||||||
2004/09/29 My Employer
|
2004/09/29 My Employer
|
||||||
Assets:Checking $500.00
|
Assets:Checking $500.00
|
||||||
Income:Salary
|
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
|
an example of increasing a MasterCard liability by spending money with
|
||||||
it:
|
it:
|
||||||
|
|
||||||
@smallexample
|
@smallexample @c input:6B43DD4
|
||||||
2004/09/30 Restaurant
|
2004/09/30 Restaurant
|
||||||
Expenses:Dining $25.00
|
Expenses:Dining $25.00
|
||||||
Liabilities:MasterCard
|
Liabilities:MasterCard
|
||||||
|
|
@ -729,10 +767,17 @@ offsets the value of your assets.
|
||||||
The combined total of your Assets and Liabilities is your net worth.
|
The combined total of your Assets and Liabilities is your net worth.
|
||||||
So to see your current net worth, use this command:
|
So to see your current net worth, use this command:
|
||||||
|
|
||||||
@smallexample
|
@smallexample @c command:6B43DD4
|
||||||
$ ledger balance ^assets ^liabilities
|
$ ledger balance ^assets ^liabilities
|
||||||
@end smallexample
|
@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
|
In a similar vein, your Income accounts show up negative, because they
|
||||||
transfer money @emph{from} an account in order to increase your
|
transfer money @emph{from} an account in order to increase your
|
||||||
assets. Your Expenses show up positive because that is where the
|
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
|
since income is always a negative figure. To see your current cash
|
||||||
flow, use this command:
|
flow, use this command:
|
||||||
|
|
||||||
@smallexample
|
@smallexample @c command:DB128F3,with_input:6B43DD4
|
||||||
$ ledger balance ^income ^expenses
|
$ ledger balance ^income ^expenses
|
||||||
@end smallexample
|
@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
|
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
|
spend each month on X? Ledger provides a simple way of displaying
|
||||||
monthly totals for any account. Here is an example that summarizes
|
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
|
Lastly, you can specify the valuation function/value for any specific
|
||||||
amount using the @samp{(( ))} commodity annotation.
|
amount using the @samp{(( ))} commodity annotation.
|
||||||
|
|
||||||
@smallexample
|
@smallexample @c input:814A366
|
||||||
2012-03-02 KFC
|
2012-03-02 KFC
|
||||||
Expenses:Food2 $1 ((2 EUR))
|
Expenses:Food2 $1 ((2 EUR))
|
||||||
Assets:Cash2
|
Assets:Cash2
|
||||||
|
|
@ -1856,20 +1908,24 @@ amount using the @samp{(( ))} commodity annotation.
|
||||||
Assets:Cash9
|
Assets:Cash9
|
||||||
@end smallexample
|
@end smallexample
|
||||||
|
|
||||||
@smallexample
|
@smallexample @c command:814A366
|
||||||
ledger reg -V food
|
$ ledger reg -V food
|
||||||
|
@end smallexample
|
||||||
|
|
||||||
|
@smallexample @c output:814A366
|
||||||
12-Mar-02 KFC Expenses:Food2 2 EUR 2 EUR
|
12-Mar-02 KFC Expenses:Food2 2 EUR 2 EUR
|
||||||
12-Mar-03 KFC <Adjustment> -1 EUR 1 EUR
|
12-Mar-03 KFC Expenses:Food3 3 EUR 5 EUR
|
||||||
Expenses:Food3 3 EUR 4 EUR
|
12-Mar-04 KFC Expenses:Food4 4 EUR 9 EUR
|
||||||
12-Mar-04 KFC <Adjustment> -2 EUR 2 EUR
|
12-Mar-05 KFC Expenses:Food5 $1 $1
|
||||||
Expenses:Food4 4 EUR 6 EUR
|
9 EUR
|
||||||
12-Mar-05 KFC <Adjustment> -3 EUR 3 EUR
|
12-Mar-06 KFC Expenses:Food6 $1 $2
|
||||||
Expenses:Food5 5 EUR 8 EUR
|
9 EUR
|
||||||
12-Mar-06 KFC <Adjustment> -4 EUR 4 EUR
|
12-Mar-07 KFC Expenses:Food7 1 CAD $2
|
||||||
Expenses:Food6 6 EUR 10 EUR
|
1 CAD
|
||||||
12-Mar-07 KFC Expenses:Food7 7 EUR 17 EUR
|
9 EUR
|
||||||
12-Mar-08 XACT Expenses:Food8 8 EUR 25 EUR
|
12-Mar-08 XACT Expenses:Food8 $1 $3
|
||||||
12-Mar-09 POST (Expenses:Food9) 9 EUR 34 EUR
|
1 CAD
|
||||||
|
9 EUR
|
||||||
@end smallexample
|
@end smallexample
|
||||||
|
|
||||||
@node Keeping it Consistent, Journal Format, Currency and Commodities, Keeping a Journal
|
@node Keeping it Consistent, Journal Format, Currency and Commodities, Keeping a Journal
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ class DocTests:
|
||||||
self.testin_token = 'command'
|
self.testin_token = 'command'
|
||||||
self.testout_token = 'output'
|
self.testout_token = 'output'
|
||||||
self.testdat_token = 'input'
|
self.testdat_token = 'input'
|
||||||
|
self.testwithdat_token = 'with_input'
|
||||||
|
|
||||||
def read_example(self):
|
def read_example(self):
|
||||||
endexample = re.compile(r'^@end\s+smallexample\s*$')
|
endexample = re.compile(r'^@end\s+smallexample\s*$')
|
||||||
|
|
@ -37,7 +38,7 @@ class DocTests:
|
||||||
return hashlib.sha1(example.rstrip()).hexdigest()[0:7].upper()
|
return hashlib.sha1(example.rstrip()).hexdigest()[0:7].upper()
|
||||||
|
|
||||||
def find_examples(self):
|
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))
|
% (self.testin_token, self.testout_token, self.testdat_token))
|
||||||
while True:
|
while True:
|
||||||
line = self.file.readline()
|
line = self.file.readline()
|
||||||
|
|
@ -50,6 +51,13 @@ class DocTests:
|
||||||
test_begin_line = self.current_line
|
test_begin_line = self.current_line
|
||||||
test_kind = startmatch.group(1)
|
test_kind = startmatch.group(1)
|
||||||
test_id = startmatch.group(2)
|
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()
|
example = self.read_example()
|
||||||
test_end_pos = self.file.tell()
|
test_end_pos = self.file.tell()
|
||||||
test_end_line = self.current_line
|
test_end_line = self.current_line
|
||||||
|
|
@ -68,22 +76,52 @@ class DocTests:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.examples[test_id] = dict()
|
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] = {
|
self.examples[test_id][test_kind] = {
|
||||||
'bpos': test_begin_pos,
|
'bpos': test_begin_pos,
|
||||||
'epos': test_end_pos,
|
'epos': test_end_pos,
|
||||||
'blin': test_begin_line,
|
'blin': test_begin_line,
|
||||||
'elin': test_end_line,
|
'elin': test_end_line,
|
||||||
|
'opts': test_options,
|
||||||
test_kind: example,
|
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):
|
def test_examples(self):
|
||||||
failed = set()
|
failed = set()
|
||||||
for test_id in self.examples:
|
for test_id in self.examples:
|
||||||
example = self.examples[test_id]
|
example = self.examples[test_id]
|
||||||
try:
|
try:
|
||||||
command = example[self.testin_token][self.testin_token]
|
(command, findex) = self.parse_command(test_id, example)
|
||||||
except KeyError:
|
except TypeError:
|
||||||
command = None
|
failed.add(test_id)
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
output = example[self.testout_token][self.testout_token]
|
output = example[self.testout_token][self.testout_token]
|
||||||
|
|
@ -93,44 +131,32 @@ class DocTests:
|
||||||
try:
|
try:
|
||||||
input = example[self.testdat_token][self.testdat_token]
|
input = example[self.testdat_token][self.testdat_token]
|
||||||
except KeyError:
|
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:
|
if command and output:
|
||||||
command = command.rstrip().split()
|
test_file_created = False
|
||||||
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')
|
|
||||||
|
|
||||||
if findex:
|
if findex:
|
||||||
scriptpath = os.path.dirname(os.path.realpath(__file__))
|
scriptpath = os.path.dirname(os.path.realpath(__file__))
|
||||||
test_input_dir = scriptpath + '/../test/input/'
|
test_input_dir = scriptpath + '/../test/input/'
|
||||||
test_file = command[findex+1]
|
test_file = command[findex]
|
||||||
test_file_created = False
|
|
||||||
if not os.path.exists(test_file):
|
if not os.path.exists(test_file):
|
||||||
if input:
|
if input:
|
||||||
test_file_created = True
|
test_file_created = True
|
||||||
with open(test_file, 'w') as f:
|
with open(test_file, 'w') as f:
|
||||||
f.write(input)
|
f.write(input)
|
||||||
elif os.path.exists(test_input_dir + test_file):
|
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:
|
try:
|
||||||
verify = subprocess.check_output(command)
|
verify = subprocess.check_output(command)
|
||||||
except:
|
except:
|
||||||
verify = str()
|
verify = str()
|
||||||
if test_file_created:
|
|
||||||
os.remove(test_file)
|
|
||||||
valid = (output == verify)
|
valid = (output == verify)
|
||||||
|
if valid and test_file_created:
|
||||||
|
os.remove(test_file)
|
||||||
if self.verbose > 0:
|
if self.verbose > 0:
|
||||||
print test_id, ':', 'Passed' if valid else 'FAILED'
|
print test_id, ':', 'Passed' if valid else 'FAILED'
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue