|
| 1 | +Test Wrapper for Interactive Environments |
| 2 | +========================================= |
| 3 | + |
| 4 | +This document describes the usage of the `TestWrapper` submodule in the `test_framework` module of the functional test framework. |
| 5 | + |
| 6 | +The TestWrapper submodule extends the `BitcoinTestFramework` functionality to external interactive environments for prototyping and educational purposes. Just like `BitcoinTestFramework`, the TestWrapper allows the user to: |
| 7 | + |
| 8 | +* Manage regtest bitcoind subprocesses. |
| 9 | +* Access RPC interfaces of these bitcoind instances. |
| 10 | +* Log events to functional test logging utility. |
| 11 | + |
| 12 | +The `TestWrapper` can be useful in interactive environments such as the Python3 command-line interpreter or [Jupyter](https://jupyter.org/) notebooks running a Python3 kernel, where is is necessary to extend the object lifetime of the underlying `BitcoinTestFramework` between user inputs. |
| 13 | + |
| 14 | +## 1. Requirements |
| 15 | + |
| 16 | +* Python3 |
| 17 | +* `bitcoind` built in the same bitcoin repository as the TestWrapper. |
| 18 | + |
| 19 | +## 2. Importing TestWrapper from the Bitcoin Core repository |
| 20 | + |
| 21 | +We can import the TestWrapper by adding the path of the Bitcoin Core `test_framework` module to the beginning of the PATH variable, and then importing the `TestWrapper` class from the `test_wrapper` sub-package. |
| 22 | + |
| 23 | +``` |
| 24 | +>>> import sys |
| 25 | +>>> sys.path.insert(0, "/path/to/bitcoin/test/functional/test_framework") |
| 26 | +>>> from test_framework.test_wrapper import TestWrapper |
| 27 | +``` |
| 28 | +<!-- >>> sys.path.insert(0, "/Users/jamesc/Dropbox/repos/bitcoin/test/functional/test_framework") --> |
| 29 | + |
| 30 | +The following TestWrapper methods manage the lifetime of the underlying bitcoind processes and logging utilities. |
| 31 | + |
| 32 | +* `TestWrapper.setup()` |
| 33 | +* `TestWrapper.shutdown()` |
| 34 | + |
| 35 | +Exceptions which are raised can be forwarded to the TestWrapper for logging purposes. |
| 36 | +* `TestWrapper.handle_exception(e)` |
| 37 | + |
| 38 | +The TestWrapper inherits all BitcoinTestFramework members and methods, such as: |
| 39 | +* `TestWrapper.nodes` |
| 40 | +* `TestWrapper.log.info("Custom log message")` |
| 41 | +* `TestWrapper.sync_all()` |
| 42 | +* `TestWrapper.success` |
| 43 | +* ... |
| 44 | + |
| 45 | +The following sections demonstrate how to initialize, run and shutdown a TestWrapper object in an interactive Python3 environment. |
| 46 | + |
| 47 | +## 3. Initializing a TestWrapper object |
| 48 | + |
| 49 | +``` |
| 50 | +>>> test = TestWrapper() |
| 51 | +>>> test.setup("num_nodes"=2) |
| 52 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (INFO): Initializing test directory /tmp/folders/../bitcoin_func_test_XXXXXXX |
| 53 | +``` |
| 54 | +The TestWrapper supports all functional test parameters of the Bitcoin TestFramework class. The full set of argument keywords which can be used to initialize the TestWrapper can be found [here](../test/functional/test_framework/test_wrapper.py). |
| 55 | + |
| 56 | +**Note: Running multiple instances of TestWrapper is not allowed.** |
| 57 | +This also ensures that logging remains consolidated in the same temporary folder. If you need more bitcoind nodes than set by default (1), simply increase the `num_nodes` parameter during setup. |
| 58 | + |
| 59 | +``` |
| 60 | +>>> test2 = TestWrapper() |
| 61 | +>>> test2.setup() |
| 62 | +TestWrapper is already running! |
| 63 | +``` |
| 64 | + |
| 65 | +## 4. Interacting with the TestWrapper |
| 66 | + |
| 67 | +Unlike the BitcoinTestFramework class, the TestWrapper keeps the underlying Bitcoind subprocesses (nodes) and logging utilities running, until the user explicitly shuts down the TestWrapper object. |
| 68 | + |
| 69 | +During the time between the `setup` and `shutdown` calls, all `bitcoind` node processes and BitcoinTestFramework convenience methods can be accessed interactively. |
| 70 | + |
| 71 | +**Example: Mining a regtest chain** |
| 72 | + |
| 73 | +By default, the TestWrapper nodes are initialized with a clean chain. This means that each node has at block height 0 after initialization of the TestWrapper. |
| 74 | + |
| 75 | +``` |
| 76 | +test.nodes[0].getblockchaininfo()["blocks"] |
| 77 | +>>> 0 |
| 78 | +``` |
| 79 | + |
| 80 | +We now generate 101 regtest blocks, and send these to a wallet address owned by the first node. |
| 81 | + |
| 82 | +``` |
| 83 | +address = test.nodes[0].getnewaddress() |
| 84 | +test.nodes[0].generatetoaddress(101, address) |
| 85 | +>>> ['2b98dd0044aae6f1cca7f88a0acf366a4bfe053c7f7b00da3c0d115f03d67efb', ... |
| 86 | +``` |
| 87 | +Since the nodes are initialized to connect to each other during `setup`, the second node will receive the newly mined blocks after they propagate. |
| 88 | + |
| 89 | +``` |
| 90 | +test.nodes[1].getblockchaininfo()["blocks"] |
| 91 | +>>> 101 |
| 92 | +``` |
| 93 | +The block rewards of the first block are now spendable by the wallet of the first node. |
| 94 | + |
| 95 | +``` |
| 96 | +>>> test.nodes[0].getbalance() |
| 97 | +Decimal('50.00000000') |
| 98 | +``` |
| 99 | + |
| 100 | +## 5. Safely shutting down the TestWrapper. |
| 101 | + |
| 102 | +``` |
| 103 | +>>> test.shutdown() |
| 104 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (INFO): Stopping nodes |
| 105 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (INFO): Cleaning up /var/folders/../bitcoin_func_test_XXXXXXX on exit |
| 106 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (INFO): Tests successful |
| 107 | +``` |
| 108 | +To prevent the logs from being removed after a successful shutdown, simply set the `TestWrapper.options.nocleanup` member to `True`. |
| 109 | +``` |
| 110 | +>>> test.options.nocleanup = True |
| 111 | +>>> test.shutdown() |
| 112 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (INFO): Stopping nodes |
| 113 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (INFO): Not cleaning up dir /tmp/folders/../bitcoin_func_test_XXXXXXX on exit |
| 114 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (INFO): Tests successful |
| 115 | +``` |
| 116 | + |
| 117 | +This allows consolidation of logs after shutting down the TestWrapper object by running: |
| 118 | + |
| 119 | +* `/path/to/bitcoin/test/functional/combine_logs.py '/tmp/folders/../bitcoin_func_test_XXXXXXX'` |
| 120 | + |
| 121 | +## 6. Handling exceptions with TestWrapper. |
| 122 | + |
| 123 | +During the lifetime of the TestWrapper object, it is also possible to capture exceptions raised outside of the TestWrapper and forward them to the `TestWrapper.handle_exception()` method, so the exception type and traceback can be logged by the TestWrapper logging utility. |
| 124 | + |
| 125 | + |
| 126 | +**Note:** `BaseException` is the exception base class in Python and can be used to forward all Exception types to the TestWrapper. |
| 127 | + |
| 128 | +``` |
| 129 | +>>> try: |
| 130 | +... 3/0 |
| 131 | +... except BaseException as e: |
| 132 | +... test.handle_exception(e) |
| 133 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (ERROR): Unexpected exception caught during testing |
| 134 | +Traceback (most recent call last): |
| 135 | + File "<stdin>", line 2, in <module> |
| 136 | +ZeroDivisionError: division by zero |
| 137 | +``` |
| 138 | +Passing an exception to TestWrapper will automatically set the success status to `FAILED`. |
| 139 | +``` |
| 140 | +>>> test.success |
| 141 | +<TestStatus.FAILED: 2> |
| 142 | +``` |
| 143 | +A `FAILED` status prevents the TestWrapper from removing the test folder upon shutdown. |
| 144 | +``` |
| 145 | +>>> test.shutdown() |
| 146 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (INFO): Stopping nodes |
| 147 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (WARNING): Not cleaning up dir /tmp/folders/../bitcoin_func_test_XXXXXXX |
| 148 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (ERROR): Test failed. Test logging available at /tmp/folders/../bitcoin_func_test_XXXXXXX/test_framework.log |
| 149 | +20XX-XX-24TXX:XX:XX.XXXXXXX TestFramework (ERROR): Hint: Call /path/to/bitcoin/test/functional/combine_logs.py '/tmp/folders/../bitcoin_func_test_XXXXXXX' to consolidate all logs |
| 150 | +``` |
0 commit comments