Skip to content

Commit 3d27418

Browse files
committed
Merge #445: [0.17] Duplicate bitcoin functional tests
ef039f0 Add travis build that tests bitcoin functional tests (Gregory Sanders) Pull request description: We're going to keep these tests alive as long as possible to increase modularity/QA of the codebase. Tree-SHA512: c0639422fe518cd9a5f6eb06f24a4c812d38bdde95f37a6787b10ab009e6ee9ff22b30ea8f79e6cf47581bd0d7577fe149f3e636c4c6f0488c9c6555730b8315
2 parents e63e500 + ef039f0 commit 3d27418

File tree

127 files changed

+27825
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+27825
-0
lines changed

.travis.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ script:
6363
- if [ "$RUN_BENCH" = "true" ]; then BEGIN_FOLD bench; DOCKER_EXEC LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib $OUTDIR/bin/bench_bitcoin -scaling=0.001 ; END_FOLD; fi
6464
- if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --exclude feature_pruning,feature_dbcrash"; fi
6565
- if [ "$RUN_TESTS" = "true" ]; then BEGIN_FOLD functional-tests; DOCKER_EXEC test/functional/test_runner.py --combinedlogslen=4000 --coverage --quiet --failfast ${extended}; END_FOLD; fi
66+
- if [ "$RUN_BITCOIN_TESTS" = "true" ]; then BEGIN_FOLD bitcoin-functional-tests; DOCKER_EXEC test/bitcoin_functional/functional/test_runner.py --combinedlogslen=4000 --coverage --quiet --failfast ${extended}; END_FOLD; fi
6667
after_script:
6768
- echo $TRAVIS_COMMIT_RANGE
6869
- echo $TRAVIS_COMMIT_LOG
@@ -124,6 +125,15 @@ jobs:
124125
RUN_TESTS=true
125126
GOAL="install"
126127
BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER"
128+
# x86_64 Linux w/ Bitcoin functional tests (Qt5 & system libs)
129+
- stage: test
130+
env: >-
131+
HOST=x86_64-unknown-linux-gnu
132+
PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev"
133+
NO_DEPENDS=1
134+
RUN_BITCOIN_TESTS=true
135+
GOAL="install"
136+
BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER"
127137
# x86_64 Linux, No wallet
128138
- stage: test
129139
env: >-

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,7 @@ AC_CONFIG_FILES([contrib/devtools/split-debug.sh],[chmod +x contrib/devtools/spl
14011401
AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([doc/Doxyfile])])
14021402
AC_CONFIG_LINKS([contrib/filter-lcov.py:contrib/filter-lcov.py])
14031403
AC_CONFIG_LINKS([test/functional/test_runner.py:test/functional/test_runner.py])
1404+
AC_CONFIG_LINKS([test/bitcoin_functional/functional/test_runner.py:test/bitcoin_functional/functional/test_runner.py])
14041405
AC_CONFIG_LINKS([test/util/bitcoin-util-test.py:test/util/bitcoin-util-test.py])
14051406
AC_CONFIG_LINKS([test/util/rpcauth-test.py:test/util/rpcauth-test.py])
14061407

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.pyc
2+
cache
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Functional tests
2+
3+
### Writing Functional Tests
4+
5+
#### Example test
6+
7+
The [example_test.py](example_test.py) is a heavily commented example of a test case that uses both
8+
the RPC and P2P interfaces. If you are writing your first test, copy that file
9+
and modify to fit your needs.
10+
11+
#### Coverage
12+
13+
Running `test_runner.py` with the `--coverage` argument tracks which RPCs are
14+
called by the tests and prints a report of uncovered RPCs in the summary. This
15+
can be used (along with the `--extended` argument) to find out which RPCs we
16+
don't have test cases for.
17+
18+
#### Style guidelines
19+
20+
- Where possible, try to adhere to [PEP-8 guidelines](https://www.python.org/dev/peps/pep-0008/)
21+
- Use a python linter like flake8 before submitting PRs to catch common style
22+
nits (eg trailing whitespace, unused imports, etc)
23+
- See [the python lint script](/test/lint/lint-python.sh) that checks for violations that
24+
could lead to bugs and issues in the test code.
25+
- Avoid wildcard imports where possible
26+
- Use a module-level docstring to describe what the test is testing, and how it
27+
is testing it.
28+
- When subclassing the BitcoinTestFramwork, place overrides for the
29+
`set_test_params()`, `add_options()` and `setup_xxxx()` methods at the top of
30+
the subclass, then locally-defined helper methods, then the `run_test()` method.
31+
- Use `'{}'.format(x)` for string formatting, not `'%s' % x`.
32+
33+
#### Naming guidelines
34+
35+
- Name the test `<area>_test.py`, where area can be one of the following:
36+
- `feature` for tests for full features that aren't wallet/mining/mempool, eg `feature_rbf.py`
37+
- `interface` for tests for other interfaces (REST, ZMQ, etc), eg `interface_rest.py`
38+
- `mempool` for tests for mempool behaviour, eg `mempool_reorg.py`
39+
- `mining` for tests for mining features, eg `mining_prioritisetransaction.py`
40+
- `p2p` for tests that explicitly test the p2p interface, eg `p2p_disconnect_ban.py`
41+
- `rpc` for tests for individual RPC methods or features, eg `rpc_listtransactions.py`
42+
- `wallet` for tests for wallet features, eg `wallet_keypool.py`
43+
- use an underscore to separate words
44+
- exception: for tests for specific RPCs or command line options which don't include underscores, name the test after the exact RPC or argument name, eg `rpc_decodescript.py`, not `rpc_decode_script.py`
45+
- Don't use the redundant word `test` in the name, eg `interface_zmq.py`, not `interface_zmq_test.py`
46+
47+
#### General test-writing advice
48+
49+
- Set `self.num_nodes` to the minimum number of nodes necessary for the test.
50+
Having additional unrequired nodes adds to the execution time of the test as
51+
well as memory/CPU/disk requirements (which is important when running tests in
52+
parallel or on Travis).
53+
- Avoid stop-starting the nodes multiple times during the test if possible. A
54+
stop-start takes several seconds, so doing it several times blows up the
55+
runtime of the test.
56+
- Set the `self.setup_clean_chain` variable in `set_test_params()` to control whether
57+
or not to use the cached data directories. The cached data directories
58+
contain a 200-block pre-mined blockchain and wallets for four nodes. Each node
59+
has 25 mature blocks (25x50=1250 BTC) in its wallet.
60+
- When calling RPCs with lots of arguments, consider using named keyword
61+
arguments instead of positional arguments to make the intent of the call
62+
clear to readers.
63+
64+
#### RPC and P2P definitions
65+
66+
Test writers may find it helpful to refer to the definitions for the RPC and
67+
P2P messages. These can be found in the following source files:
68+
69+
- `/src/rpc/*` for RPCs
70+
- `/src/wallet/rpc*` for wallet RPCs
71+
- `ProcessMessage()` in `/src/net_processing.cpp` for parsing P2P messages
72+
73+
#### Using the P2P interface
74+
75+
- `mininode.py` contains all the definitions for objects that pass
76+
over the network (`CBlock`, `CTransaction`, etc, along with the network-level
77+
wrappers for them, `msg_block`, `msg_tx`, etc).
78+
79+
- P2P tests have two threads. One thread handles all network communication
80+
with the bitcoind(s) being tested in a callback-based event loop; the other
81+
implements the test logic.
82+
83+
- `P2PConnection` is the class used to connect to a bitcoind. `P2PInterface`
84+
contains the higher level logic for processing P2P payloads and connecting to
85+
the Bitcoin Core node application logic. For custom behaviour, subclass the
86+
P2PInterface object and override the callback methods.
87+
88+
- Can be used to write tests where specific P2P protocol behavior is tested.
89+
Examples tests are `p2p_unrequested_blocks.py`, `p2p_compactblocks.py`.
90+
91+
### test-framework modules
92+
93+
#### [test_framework/authproxy.py](test_framework/authproxy.py)
94+
Taken from the [python-bitcoinrpc repository](https://github.com/jgarzik/python-bitcoinrpc).
95+
96+
#### [test_framework/test_framework.py](test_framework/test_framework.py)
97+
Base class for functional tests.
98+
99+
#### [test_framework/util.py](test_framework/util.py)
100+
Generally useful functions.
101+
102+
#### [test_framework/mininode.py](test_framework/mininode.py)
103+
Basic code to support P2P connectivity to a bitcoind.
104+
105+
#### [test_framework/script.py](test_framework/script.py)
106+
Utilities for manipulating transaction scripts (originally from python-bitcoinlib)
107+
108+
#### [test_framework/key.py](test_framework/key.py)
109+
Wrapper around OpenSSL EC_Key (originally from python-bitcoinlib)
110+
111+
#### [test_framework/bignum.py](test_framework/bignum.py)
112+
Helpers for script.py
113+
114+
#### [test_framework/blocktools.py](test_framework/blocktools.py)
115+
Helper functions for creating blocks and transactions.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
"""Combine logs from multiple bitcoin nodes as well as the test_framework log.
3+
4+
This streams the combined log output to stdout. Use combine_logs.py > outputfile
5+
to write to an outputfile."""
6+
7+
import argparse
8+
from collections import defaultdict, namedtuple
9+
import heapq
10+
import itertools
11+
import os
12+
import re
13+
import sys
14+
15+
# Matches on the date format at the start of the log event
16+
TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z")
17+
18+
LogEvent = namedtuple('LogEvent', ['timestamp', 'source', 'event'])
19+
20+
def main():
21+
"""Main function. Parses args, reads the log files and renders them as text or html."""
22+
23+
parser = argparse.ArgumentParser(usage='%(prog)s [options] <test temporary directory>', description=__doc__)
24+
parser.add_argument('-c', '--color', dest='color', action='store_true', help='outputs the combined log with events colored by source (requires posix terminal colors. Use less -r for viewing)')
25+
parser.add_argument('--html', dest='html', action='store_true', help='outputs the combined log as html. Requires jinja2. pip install jinja2')
26+
parser.add_argument('--chain', dest='chain', help='selected chain in the tests (default: regtest2)', default='regtest2')
27+
args, unknown_args = parser.parse_known_args()
28+
29+
if args.color and os.name != 'posix':
30+
print("Color output requires posix terminal colors.")
31+
sys.exit(1)
32+
33+
if args.html and args.color:
34+
print("Only one out of --color or --html should be specified")
35+
sys.exit(1)
36+
37+
# There should only be one unknown argument - the path of the temporary test directory
38+
if len(unknown_args) != 1:
39+
print("Unexpected arguments" + str(unknown_args))
40+
sys.exit(1)
41+
42+
log_events = read_logs(unknown_args[0], args.chain)
43+
44+
print_logs(log_events, color=args.color, html=args.html)
45+
46+
def read_logs(tmp_dir, chain):
47+
"""Reads log files.
48+
49+
Delegates to generator function get_log_events() to provide individual log events
50+
for each of the input log files."""
51+
52+
files = [("test", "%s/test_framework.log" % tmp_dir)]
53+
for i in itertools.count():
54+
logfile = "{}/node{}/{}/debug.log".format(tmp_dir, i, chain)
55+
if not os.path.isfile(logfile):
56+
break
57+
files.append(("node%d" % i, logfile))
58+
59+
return heapq.merge(*[get_log_events(source, f) for source, f in files])
60+
61+
def get_log_events(source, logfile):
62+
"""Generator function that returns individual log events.
63+
64+
Log events may be split over multiple lines. We use the timestamp
65+
regex match as the marker for a new log event."""
66+
try:
67+
with open(logfile, 'r', encoding='utf-8') as infile:
68+
event = ''
69+
timestamp = ''
70+
for line in infile:
71+
# skip blank lines
72+
if line == '\n':
73+
continue
74+
# if this line has a timestamp, it's the start of a new log event.
75+
time_match = TIMESTAMP_PATTERN.match(line)
76+
if time_match:
77+
if event:
78+
yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip())
79+
event = line
80+
timestamp = time_match.group()
81+
# if it doesn't have a timestamp, it's a continuation line of the previous log.
82+
else:
83+
event += "\n" + line
84+
# Flush the final event
85+
yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip())
86+
except FileNotFoundError:
87+
print("File %s could not be opened. Continuing without it." % logfile, file=sys.stderr)
88+
89+
def print_logs(log_events, color=False, html=False):
90+
"""Renders the iterator of log events into text or html."""
91+
if not html:
92+
colors = defaultdict(lambda: '')
93+
if color:
94+
colors["test"] = "\033[0;36m" # CYAN
95+
colors["node0"] = "\033[0;34m" # BLUE
96+
colors["node1"] = "\033[0;32m" # GREEN
97+
colors["node2"] = "\033[0;31m" # RED
98+
colors["node3"] = "\033[0;33m" # YELLOW
99+
colors["reset"] = "\033[0m" # Reset font color
100+
101+
for event in log_events:
102+
print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, event.event, colors["reset"]))
103+
104+
else:
105+
try:
106+
import jinja2
107+
except ImportError:
108+
print("jinja2 not found. Try `pip install jinja2`")
109+
sys.exit(1)
110+
print(jinja2.Environment(loader=jinja2.FileSystemLoader('./'))
111+
.get_template('combined_log_template.html')
112+
.render(title="Combined Logs from testcase", log_events=[event._asdict() for event in log_events]))
113+
114+
if __name__ == '__main__':
115+
main()
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<html lang="en">
2+
<head>
3+
<title> {{ title }} </title>
4+
<style>
5+
ul {
6+
list-style-type: none;
7+
font-family: monospace;
8+
}
9+
li {
10+
border: 1px solid slategray;
11+
margin-bottom: 1px;
12+
}
13+
li:hover {
14+
filter: brightness(85%);
15+
}
16+
li.log-test {
17+
background-color: cyan;
18+
}
19+
li.log-node0 {
20+
background-color: lightblue;
21+
}
22+
li.log-node1 {
23+
background-color: lightgreen;
24+
}
25+
li.log-node2 {
26+
background-color: lightsalmon;
27+
}
28+
li.log-node3 {
29+
background-color: lightyellow;
30+
}
31+
</style>
32+
</head>
33+
<body>
34+
<ul>
35+
{% for event in log_events %}
36+
<li class="log-{{ event.source }}"> {{ event.source }} {{ event.timestamp }} {{event.event}}</li>
37+
{% endfor %}
38+
</ul>
39+
</body>
40+
</html>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2016-2018 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+
"""Create a blockchain cache.
6+
7+
Creating a cache of the blockchain speeds up test execution when running
8+
multiple functional tests. This helper script is executed by test_runner when multiple
9+
tests are being run in parallel.
10+
"""
11+
12+
from test_framework.test_framework import BitcoinTestFramework
13+
14+
class CreateCache(BitcoinTestFramework):
15+
# Test network and test nodes are not required:
16+
17+
def set_test_params(self):
18+
self.num_nodes = 0
19+
self.supports_cli = True
20+
21+
def setup_network(self):
22+
pass
23+
24+
def run_test(self):
25+
pass
26+
27+
if __name__ == '__main__':
28+
CreateCache().main()

0 commit comments

Comments
 (0)