Skip to content

Commit 356cfe8

Browse files
committed
Python-based regression tests
skeleton.py : a do-nothing test skeleton listtransactions.py : start of regression test for listtransactions call
1 parent 260cf5c commit 356cfe8

File tree

5 files changed

+392
-14
lines changed

5 files changed

+392
-14
lines changed

qa/rpc-tests/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.pyc
2+
cache

qa/rpc-tests/README.md

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
11
Regression tests of RPC interface
22
=================================
33

4-
Bash scripts that use the RPC interface and command-line bitcoin-cli to test
5-
full functionality in -regtest mode.
4+
python-bitcoinrpc: git subtree of https://github.com/jgarzik/python-bitcoinrpc
5+
Changes to python-bitcoinrpc should be made upstream, and then
6+
pulled here using git subtree
67

7-
wallet.sh : Exercise wallet send/receive code.
8+
skeleton.py : Copy this to create new regression tests.
89

9-
txnmall.sh : Test proper accounting of malleable transactions
10+
listtransactions.py : Tests for the listtransactions RPC call
11+
12+
util.py : generally useful functions
1013

14+
Bash-based tests, to be ported to Python:
15+
-----------------------------------------
16+
wallet.sh : Exercise wallet send/receive code.
17+
walletbackup.sh : Exercise wallet backup / dump / import
18+
txnmall.sh : Test proper accounting of malleable transactions
1119
conflictedbalance.sh : More testing of malleable transaction handling
1220

13-
util.sh : useful re-usable bash functions
21+
Notes
22+
=====
1423

24+
A 200-block -regtest blockchain and wallets for four nodes
25+
is created the first time a regression test is run and
26+
is stored in the cache/ directory. Each node has 25 mature
27+
blocks (25*50=1250 BTC) in their wallet.
1528

16-
Tips for creating new tests
17-
===========================
29+
After the first run, the cache/ blockchain and wallets are
30+
copied into a temporary directory and used as the initial
31+
test state.
1832

19-
To cleanup after a failed or interrupted test:
33+
If you get into a bad state, you should be able
34+
to recover with:
35+
rm -rf cache
2036
killall bitcoind
21-
rm -rf test.*
22-
23-
The most difficult part of writing reproducible tests is
24-
keeping multiple nodes in sync. See WaitBlocks,
25-
WaitPeers, and WaitMemPools for how other tests
26-
deal with this.

qa/rpc-tests/listtransactions.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/usr/bin/env python
2+
3+
# Exercise the listtransactions API
4+
5+
# Add python-bitcoinrpc to module search path:
6+
import os
7+
import sys
8+
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "python-bitcoinrpc"))
9+
10+
import json
11+
import shutil
12+
import subprocess
13+
import tempfile
14+
import traceback
15+
16+
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
17+
from util import *
18+
19+
20+
def check_array_result(object_array, to_match, expected):
21+
"""
22+
Pass in array of JSON objects, a dictionary with key/value pairs
23+
to match against, and another dictionary with expected key/value
24+
pairs.
25+
"""
26+
num_matched = 0
27+
for item in object_array:
28+
all_match = True
29+
for key,value in to_match.items():
30+
if item[key] != value:
31+
all_match = False
32+
if not all_match:
33+
continue
34+
for key,value in expected.items():
35+
if item[key] != value:
36+
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
37+
num_matched = num_matched+1
38+
if num_matched == 0:
39+
raise AssertionError("No objects matched %s"%(str(to_match)))
40+
41+
def run_test(nodes):
42+
# Simple send, 0 to 1:
43+
txid = nodes[0].sendtoaddress(nodes[1].getnewaddress(), 0.1)
44+
sync_mempools(nodes)
45+
check_array_result(nodes[0].listtransactions(),
46+
{"txid":txid},
47+
{"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":0})
48+
check_array_result(nodes[1].listtransactions(),
49+
{"txid":txid},
50+
{"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":0})
51+
# mine a block, confirmations should change:
52+
nodes[0].setgenerate(True, 1)
53+
sync_blocks(nodes)
54+
check_array_result(nodes[0].listtransactions(),
55+
{"txid":txid},
56+
{"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":1})
57+
check_array_result(nodes[1].listtransactions(),
58+
{"txid":txid},
59+
{"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":1})
60+
61+
# send-to-self:
62+
txid = nodes[0].sendtoaddress(nodes[0].getnewaddress(), 0.2)
63+
check_array_result(nodes[0].listtransactions(),
64+
{"txid":txid, "category":"send"},
65+
{"amount":Decimal("-0.2")})
66+
check_array_result(nodes[0].listtransactions(),
67+
{"txid":txid, "category":"receive"},
68+
{"amount":Decimal("0.2")})
69+
70+
# sendmany from node1: twice to self, twice to node2:
71+
send_to = { nodes[0].getnewaddress() : 0.11, nodes[1].getnewaddress() : 0.22,
72+
nodes[0].getaccountaddress("from1") : 0.33, nodes[1].getaccountaddress("toself") : 0.44 }
73+
txid = nodes[1].sendmany("", send_to)
74+
sync_mempools(nodes)
75+
check_array_result(nodes[1].listtransactions(),
76+
{"category":"send","amount":Decimal("-0.11")},
77+
{"txid":txid} )
78+
check_array_result(nodes[0].listtransactions(),
79+
{"category":"receive","amount":Decimal("0.11")},
80+
{"txid":txid} )
81+
check_array_result(nodes[1].listtransactions(),
82+
{"category":"send","amount":Decimal("-0.22")},
83+
{"txid":txid} )
84+
check_array_result(nodes[1].listtransactions(),
85+
{"category":"receive","amount":Decimal("0.22")},
86+
{"txid":txid} )
87+
check_array_result(nodes[1].listtransactions(),
88+
{"category":"send","amount":Decimal("-0.33")},
89+
{"txid":txid} )
90+
check_array_result(nodes[0].listtransactions(),
91+
{"category":"receive","amount":Decimal("0.33")},
92+
{"txid":txid, "account" : "from1"} )
93+
check_array_result(nodes[1].listtransactions(),
94+
{"category":"send","amount":Decimal("-0.44")},
95+
{"txid":txid, "account" : ""} )
96+
check_array_result(nodes[1].listtransactions(),
97+
{"category":"receive","amount":Decimal("0.44")},
98+
{"txid":txid, "account" : "toself"} )
99+
100+
101+
def main():
102+
import optparse
103+
104+
parser = optparse.OptionParser(usage="%prog [options]")
105+
parser.add_option("--nocleanup", dest="nocleanup", default=False, action="store_true",
106+
help="Leave bitcoinds and test.* datadir on exit or error")
107+
parser.add_option("--srcdir", dest="srcdir", default="../../src",
108+
help="Source directory containing bitcoind/bitcoin-cli (default: %default%)")
109+
parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"),
110+
help="Root directory for datadirs")
111+
(options, args) = parser.parse_args()
112+
113+
os.environ['PATH'] = options.srcdir+":"+os.environ['PATH']
114+
115+
check_json_precision()
116+
117+
success = False
118+
try:
119+
print("Initializing test directory "+options.tmpdir)
120+
if not os.path.isdir(options.tmpdir):
121+
os.makedirs(options.tmpdir)
122+
initialize_chain(options.tmpdir)
123+
124+
nodes = start_nodes(2, options.tmpdir)
125+
connect_nodes(nodes[1], 0)
126+
sync_blocks(nodes)
127+
run_test(nodes)
128+
129+
success = True
130+
131+
except AssertionError as e:
132+
print("Assertion failed: "+e.message)
133+
except Exception as e:
134+
print("Unexpected exception caught during testing: "+str(e))
135+
stack = traceback.extract_tb(sys.exc_info()[2])
136+
print(stack[-1])
137+
138+
if not options.nocleanup:
139+
print("Cleaning up")
140+
stop_nodes()
141+
shutil.rmtree(options.tmpdir)
142+
143+
if success:
144+
print("Tests successful")
145+
sys.exit(0)
146+
else:
147+
print("Failed")
148+
sys.exit(1)
149+
150+
if __name__ == '__main__':
151+
main()

qa/rpc-tests/skeleton.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python
2+
3+
# Skeleton for python-based regression tests using
4+
# JSON-RPC
5+
6+
7+
# Add python-bitcoinrpc to module search path:
8+
import os
9+
import sys
10+
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "python-bitcoinrpc"))
11+
12+
import json
13+
import shutil
14+
import subprocess
15+
import tempfile
16+
import traceback
17+
18+
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
19+
from util import *
20+
21+
22+
def run_test(nodes):
23+
# Replace this as appropriate
24+
for node in nodes:
25+
assert_equal(node.getblockcount(), 200)
26+
assert_equal(node.getbalance(), 25*50)
27+
28+
def main():
29+
import optparse
30+
31+
parser = optparse.OptionParser(usage="%prog [options]")
32+
parser.add_option("--nocleanup", dest="nocleanup", default=False, action="store_true",
33+
help="Leave bitcoinds and test.* datadir on exit or error")
34+
parser.add_option("--srcdir", dest="srcdir", default="../../src",
35+
help="Source directory containing bitcoind/bitcoin-cli (default: %default%)")
36+
parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"),
37+
help="Root directory for datadirs")
38+
(options, args) = parser.parse_args()
39+
40+
os.environ['PATH'] = options.srcdir+":"+os.environ['PATH']
41+
42+
check_json_precision()
43+
44+
success = False
45+
try:
46+
print("Initializing test directory "+options.tmpdir)
47+
if not os.path.isdir(options.tmpdir):
48+
os.makedirs(options.tmpdir)
49+
initialize_chain(options.tmpdir)
50+
51+
nodes = start_nodes(2, options.tmpdir)
52+
connect_nodes(nodes[1], 0)
53+
sync_blocks(nodes)
54+
55+
run_test(nodes)
56+
57+
success = True
58+
59+
except AssertionError as e:
60+
print("Assertion failed: "+e.message)
61+
except Exception as e:
62+
print("Unexpected exception caught during testing: "+str(e))
63+
stack = traceback.extract_tb(sys.exc_info()[2])
64+
print(stack[-1])
65+
66+
if not options.nocleanup:
67+
print("Cleaning up")
68+
stop_nodes()
69+
shutil.rmtree(options.tmpdir)
70+
71+
if success:
72+
print("Tests successful")
73+
sys.exit(0)
74+
else:
75+
print("Failed")
76+
sys.exit(1)
77+
78+
if __name__ == '__main__':
79+
main()

0 commit comments

Comments
 (0)