Skip to content

Commit e7ba6c1

Browse files
committed
[tests] add example test
1 parent 76859e6 commit e7ba6c1

File tree

2 files changed

+220
-0
lines changed

2 files changed

+220
-0
lines changed

test/functional/example_test.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2017 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""An example functional test
6+
7+
The module-level docstring should include a high-level description of
8+
what the test is doing. It's the first thing people see when they open
9+
the file and should give the reader information about *what* the test
10+
is testing and *how* it's being tested
11+
"""
12+
# Imports should be in PEP8 ordering (std library first, then third party
13+
# libraries then local imports).
14+
from collections import defaultdict
15+
16+
# Avoid wildcard * imports if possible
17+
from test_framework.blocktools import (create_block, create_coinbase)
18+
from test_framework.mininode import (
19+
CInv,
20+
NetworkThread,
21+
NodeConn,
22+
NodeConnCB,
23+
mininode_lock,
24+
msg_block,
25+
msg_getdata,
26+
wait_until,
27+
)
28+
from test_framework.test_framework import BitcoinTestFramework
29+
from test_framework.util import (
30+
assert_equal,
31+
connect_nodes,
32+
p2p_port,
33+
)
34+
35+
# NodeConnCB is a class containing callbacks to be executed when a P2P
36+
# message is received from the node-under-test. Subclass NodeConnCB and
37+
# override the on_*() methods if you need custom behaviour.
38+
class BaseNode(NodeConnCB):
39+
def __init__(self):
40+
"""Initialize the NodeConnCB
41+
42+
Used to inialize custom properties for the Node that aren't
43+
included by default in the base class. Be aware that the NodeConnCB
44+
base class already stores a counter for each P2P message type and the
45+
last received message of each type, which should be sufficient for the
46+
needs of most tests.
47+
48+
Call super().__init__() first for standard initialization and then
49+
initialize custom properties."""
50+
super().__init__()
51+
# Stores a dictionary of all blocks received
52+
self.block_receive_map = defaultdict(int)
53+
54+
def on_block(self, conn, message):
55+
"""Override the standard on_block callback
56+
57+
Store the hash of a received block in the dictionary."""
58+
message.block.calc_sha256()
59+
self.block_receive_map[message.block.sha256] += 1
60+
61+
def custom_function():
62+
"""Do some custom behaviour
63+
64+
If this function is more generally useful for other tests, consider
65+
moving it to a module in test_framework."""
66+
# self.log.info("running custom_function") # Oops! Can't run self.log outside the BitcoinTestFramework
67+
pass
68+
69+
class ExampleTest(BitcoinTestFramework):
70+
# Each functional test is a subclass of the BitcoinTestFramework class.
71+
72+
# Override the __init__(), add_options(), setup_chain(), setup_network()
73+
# and setup_nodes() methods to customize the test setup as required.
74+
75+
def __init__(self):
76+
"""Initialize the test
77+
78+
Call super().__init__() first, and then override any test parameters
79+
for your individual test."""
80+
super().__init__()
81+
self.setup_clean_chain = True
82+
self.num_nodes = 3
83+
# Use self.extra_args to change command-line arguments for the nodes
84+
self.extra_args = [[], ["-logips"], []]
85+
86+
# self.log.info("I've finished __init__") # Oops! Can't run self.log before run_test()
87+
88+
# Use add_options() to add specific command-line options for your test.
89+
# In practice this is not used very much, since the tests are mostly written
90+
# to be run in automated environments without command-line options.
91+
# def add_options()
92+
# pass
93+
94+
# Use setup_chain() to customize the node data directories. In practice
95+
# this is not used very much since the default behaviour is almost always
96+
# fine
97+
# def setup_chain():
98+
# pass
99+
100+
def setup_network(self):
101+
"""Setup the test network topology
102+
103+
Often you won't need to override this, since the standard network topology
104+
(linear: node0 <-> node1 <-> node2 <-> ...) is fine for most tests.
105+
106+
If you do override this method, remember to start the nodes, assign
107+
them to self.nodes, connect them and then sync."""
108+
109+
self.setup_nodes()
110+
111+
# In this test, we're not connecting node2 to node0 or node1. Calls to
112+
# sync_all() should not include node2, since we're not expecting it to
113+
# sync.
114+
connect_nodes(self.nodes[0], 1)
115+
self.sync_all([self.nodes[0:1]])
116+
117+
# Use setup_nodes() to customize the node start behaviour (for example if
118+
# you don't want to start all nodes at the start of the test).
119+
# def setup_nodes():
120+
# pass
121+
122+
def custom_method(self):
123+
"""Do some custom behaviour for this test
124+
125+
Define it in a method here because you're going to use it repeatedly.
126+
If you think it's useful in general, consider moving it to the base
127+
BitcoinTestFramework class so other tests can use it."""
128+
129+
self.log.info("Running custom_method")
130+
131+
def run_test(self):
132+
"""Main test logic"""
133+
134+
# Create a P2P connection to one of the nodes
135+
node0 = BaseNode()
136+
connections = []
137+
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
138+
node0.add_connection(connections[0])
139+
140+
# Start up network handling in another thread. This needs to be called
141+
# after the P2P connections have been created.
142+
NetworkThread().start()
143+
# wait_for_verack ensures that the P2P connection is fully up.
144+
node0.wait_for_verack()
145+
146+
# Generating a block on one of the nodes will get us out of IBD
147+
blocks = [int(self.nodes[0].generate(nblocks=1)[0], 16)]
148+
self.sync_all([self.nodes[0:1]])
149+
150+
# Notice above how we called an RPC by calling a method with the same
151+
# name on the node object. Notice also how we used a keyword argument
152+
# to specify a named RPC argument. Neither of those are defined on the
153+
# node object. Instead there's some __getattr__() magic going on under
154+
# the covers to dispatch unrecognised attribute calls to the RPC
155+
# interface.
156+
157+
# Logs are nice. Do plenty of them. They can be used in place of comments for
158+
# breaking the test into sub-sections.
159+
self.log.info("Starting test!")
160+
161+
self.log.info("Calling a custom function")
162+
custom_function()
163+
164+
self.log.info("Calling a custom method")
165+
self.custom_method()
166+
167+
self.log.info("Create some blocks")
168+
self.tip = int(self.nodes[0].getbestblockhash(), 16)
169+
self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
170+
171+
height = 1
172+
173+
for i in range(10):
174+
# Use the mininode and blocktools functionality to manually build a block
175+
# Calling the generate() rpc is easier, but this allows us to exactly
176+
# control the blocks and transactions.
177+
block = create_block(self.tip, create_coinbase(height), self.block_time)
178+
block.solve()
179+
block_message = msg_block(block)
180+
# Send message is used to send a P2P message to the node over our NodeConn connection
181+
node0.send_message(block_message)
182+
self.tip = block.sha256
183+
blocks.append(self.tip)
184+
self.block_time += 1
185+
height += 1
186+
187+
self.log.info("Wait for node1 to reach current tip (height 11) using RPC")
188+
self.nodes[1].waitforblockheight(11)
189+
190+
self.log.info("Connect node2 and node1")
191+
connect_nodes(self.nodes[1], 2)
192+
193+
self.log.info("Add P2P connection to node2")
194+
node2 = BaseNode()
195+
connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2))
196+
node2.add_connection(connections[1])
197+
node2.wait_for_verack()
198+
199+
self.log.info("Wait for node2 reach current tip. Test that it has propogated all the blocks to us")
200+
201+
for block in blocks:
202+
getdata_request = msg_getdata()
203+
getdata_request.inv.append(CInv(2, block))
204+
node2.send_message(getdata_request)
205+
206+
# wait_until() will loop until a predicate condition is met. Use it to test properties of the
207+
# NodeConnCB objects.
208+
assert wait_until(lambda: sorted(blocks) == sorted(list(node2.block_receive_map.keys())), timeout=5)
209+
210+
self.log.info("Check that each block was received only once")
211+
# The network thread uses a global lock on data access to the NodeConn objects when sending and receiving
212+
# messages. The test thread should acquire the global lock before accessing any NodeConn data to avoid locking
213+
# and synchronization issues. Note wait_until() acquires this global lock when testing the predicate.
214+
with mininode_lock:
215+
for block in node2.block_receive_map.values():
216+
assert_equal(block, 1)
217+
218+
if __name__ == '__main__':
219+
ExampleTest().main()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
'bip65-cltv-p2p.py',
138138
'bipdersig-p2p.py',
139139
'bipdersig.py',
140+
'example_test.py',
140141
'getblocktemplate_proposals.py',
141142
'txn_doublespend.py',
142143
'txn_clone.py --mineblock',

0 commit comments

Comments
 (0)