2016-03-19 16:58:06 -03:00
#!/usr/bin/env python3
2021-07-28 07:57:16 -04:00
# Copyright (c) 2014-2021 The Bitcoin Core developers
2015-08-26 07:05:36 -03:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2017-03-09 11:53:26 -03:00
""" Run regression test suite.
2015-10-11 02:41:19 -03:00
This module calls down into individual test cases via subprocess . It will
2017-01-31 16:32:49 -03:00
forward all unrecognized arguments onto the individual test scripts .
2015-10-11 02:41:19 -03:00
For a description of arguments recognized by test scripts , see
2017-03-09 11:53:26 -03:00
` test / functional / test_framework / test_framework . py : BitcoinTestFramework . main ` .
2015-10-11 02:41:19 -03:00
"""
2015-08-26 07:05:36 -03:00
2017-01-31 15:15:40 -03:00
import argparse
2017-11-29 15:25:23 -03:00
from collections import deque
2017-01-30 19:57:27 -03:00
import configparser
2017-05-18 17:33:33 -04:00
import datetime
2015-08-26 07:05:36 -03:00
import os
2015-11-30 10:53:07 -03:00
import time
2015-10-11 02:41:19 -03:00
import shutil
2021-06-14 03:44:11 -04:00
import signal
2015-08-26 07:05:36 -03:00
import subprocess
2020-03-20 13:08:33 -03:00
import sys
2015-10-11 02:41:19 -03:00
import tempfile
2015-08-26 07:05:36 -03:00
import re
2017-02-16 16:00:35 -03:00
import logging
2020-03-26 17:57:53 -03:00
import unittest
2015-10-11 02:41:19 -03:00
2017-07-25 15:39:39 -04:00
# Formatting. Default colors to empty strings.
2022-04-06 17:44:07 -04:00
DEFAULT , BOLD , GREEN , RED = ( " " , " " ) , ( " " , " " ) , ( " " , " " ) , ( " " , " " )
2017-04-17 14:46:20 -03:00
try :
# Make sure python thinks it can write unicode to its stdout
" \u2713 " . encode ( " utf_8 " ) . decode ( sys . stdout . encoding )
TICK = " ✓ "
CROSS = " ✖ "
CIRCLE = " ○ "
except UnicodeDecodeError :
TICK = " P "
CROSS = " x "
CIRCLE = " o "
2021-08-24 12:05:43 -04:00
if os . name != ' nt ' or sys . getwindowsversion ( ) > = ( 10 , 0 , 14393 ) : #type:ignore
2018-08-19 15:54:54 -03:00
if os . name == ' nt ' :
import ctypes
2020-02-25 16:05:48 -03:00
kernel32 = ctypes . windll . kernel32 # type: ignore
2018-08-19 15:54:54 -03:00
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
STD_OUTPUT_HANDLE = - 11
STD_ERROR_HANDLE = - 12
# Enable ascii color control to stdout
stdout = kernel32 . GetStdHandle ( STD_OUTPUT_HANDLE )
stdout_mode = ctypes . c_int32 ( )
kernel32 . GetConsoleMode ( stdout , ctypes . byref ( stdout_mode ) )
kernel32 . SetConsoleMode ( stdout , stdout_mode . value | ENABLE_VIRTUAL_TERMINAL_PROCESSING )
# Enable ascii color control to stderr
stderr = kernel32 . GetStdHandle ( STD_ERROR_HANDLE )
stderr_mode = ctypes . c_int32 ( )
kernel32 . GetConsoleMode ( stderr , ctypes . byref ( stderr_mode ) )
kernel32 . SetConsoleMode ( stderr , stderr_mode . value | ENABLE_VIRTUAL_TERMINAL_PROCESSING )
2017-04-05 16:19:26 -03:00
# primitive formatting on supported
# terminal via ANSI escape sequences:
2022-04-06 17:44:07 -04:00
DEFAULT = ( ' \033 [0m ' , ' \033 [0m ' )
2017-04-05 16:19:26 -03:00
BOLD = ( ' \033 [0m ' , ' \033 [1m ' )
2018-08-19 15:54:54 -03:00
GREEN = ( ' \033 [0m ' , ' \033 [0;32m ' )
2017-07-25 15:39:39 -04:00
RED = ( ' \033 [0m ' , ' \033 [0;31m ' )
2017-04-05 16:19:26 -03:00
2017-03-22 11:26:02 -03:00
TEST_EXIT_PASSED = 0
TEST_EXIT_SKIPPED = 77
2020-03-26 17:57:53 -03:00
TEST_FRAMEWORK_MODULES = [
2020-05-19 21:00:26 -04:00
" address " ,
2020-05-27 13:05:34 -04:00
" blocktools " ,
2020-05-31 14:31:17 -04:00
" muhash " ,
2020-09-11 18:34:24 -03:00
" key " ,
2020-03-26 17:57:53 -03:00
" script " ,
2020-06-11 13:42:37 -04:00
" segwit_addr " ,
2020-06-11 08:59:08 -04:00
" util " ,
2020-03-26 17:57:53 -03:00
]
2019-05-03 16:16:37 -04:00
EXTENDED_SCRIPTS = [
2019-06-19 11:39:27 -04:00
# These tests are not run by default.
2019-05-03 16:16:37 -04:00
# Longest test should go first, to favor running tests in parallel
' feature_pruning.py ' ,
' feature_dbcrash.py ' ,
2021-05-13 13:33:08 -04:00
' feature_index_prune.py ' ,
2019-05-03 16:16:37 -04:00
]
2017-11-17 14:54:39 -03:00
BASE_SCRIPTS = [
2019-06-19 11:39:27 -04:00
# Scripts that are run by default.
2017-02-17 16:22:56 -03:00
# Longest test should go first, to favor running tests in parallel
2020-11-06 16:22:05 -03:00
' wallet_hd.py --legacy-wallet ' ,
2019-07-16 15:33:35 -04:00
' wallet_hd.py --descriptors ' ,
2020-11-06 16:22:05 -03:00
' wallet_backup.py --legacy-wallet ' ,
2020-04-03 19:49:36 -03:00
' wallet_backup.py --descriptors ' ,
2016-12-03 17:46:33 -03:00
# vv Tests less than 5m vv
2018-09-15 08:13:26 -03:00
' mining_getblocktemplate_longpoll.py ' ,
' feature_maxuploadtarget.py ' ,
2018-01-24 20:44:29 -03:00
' feature_block.py ' ,
2020-11-06 16:22:05 -03:00
' rpc_fundrawtransaction.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' rpc_fundrawtransaction.py --descriptors ' ,
2018-01-24 20:44:29 -03:00
' p2p_compactblocks.py ' ,
2021-08-08 04:07:58 -04:00
' p2p_compactblocks_blocksonly.py ' ,
2020-04-27 15:38:20 -04:00
' feature_segwit.py --legacy-wallet ' ,
2021-10-19 19:43:18 -03:00
' feature_segwit.py --descriptors ' ,
2016-12-03 17:46:33 -03:00
# vv Tests less than 2m vv
2020-11-06 16:22:05 -03:00
' wallet_basic.py --legacy-wallet ' ,
2019-07-16 15:33:35 -04:00
' wallet_basic.py --descriptors ' ,
2020-11-06 16:22:05 -03:00
' wallet_labels.py --legacy-wallet ' ,
2019-07-16 15:33:35 -04:00
' wallet_labels.py --descriptors ' ,
2018-01-24 20:44:29 -03:00
' p2p_segwit.py ' ,
2018-09-15 08:13:26 -03:00
' p2p_timeouts.py ' ,
2019-05-28 17:52:55 -04:00
' p2p_tx_download.py ' ,
2020-03-31 06:54:49 -03:00
' mempool_updatefromblock.py ' ,
2020-04-27 15:38:20 -04:00
' wallet_dump.py --legacy-wallet ' ,
2020-09-14 02:20:17 -03:00
' feature_taproot.py ' ,
2021-03-18 10:17:39 -03:00
' rpc_signer.py ' ,
2019-04-27 13:11:43 -04:00
' wallet_signer.py --descriptors ' ,
2016-12-03 17:46:33 -03:00
# vv Tests less than 60s vv
2018-01-24 20:44:29 -03:00
' p2p_sendheaders.py ' ,
2020-04-27 15:38:20 -04:00
' wallet_importmulti.py --legacy-wallet ' ,
2016-12-03 17:46:33 -03:00
' mempool_limit.py ' ,
2018-01-24 20:44:29 -03:00
' rpc_txoutproof.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_listreceivedby.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' wallet_listreceivedby.py --descriptors ' ,
2020-11-06 16:22:05 -03:00
' wallet_abandonconflict.py --legacy-wallet ' ,
2021-05-28 16:45:15 -04:00
' p2p_dns_seeds.py ' ,
2020-04-27 15:38:20 -04:00
' wallet_abandonconflict.py --descriptors ' ,
2018-01-24 20:44:29 -03:00
' feature_csv_activation.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_address_types.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' wallet_address_types.py --descriptors ' ,
2018-09-15 08:13:26 -03:00
' feature_bip68_sequence.py ' ,
' p2p_feefilter.py ' ,
2021-07-20 06:49:43 -04:00
' rpc_packages.py ' ,
2018-01-24 20:44:29 -03:00
' feature_reindex.py ' ,
2019-01-31 18:06:07 -03:00
' feature_abortnode.py ' ,
2016-12-03 17:46:33 -03:00
# vv Tests less than 30s vv
2020-11-06 16:22:05 -03:00
' wallet_keypool_topup.py --legacy-wallet ' ,
2019-07-16 15:33:35 -04:00
' wallet_keypool_topup.py --descriptors ' ,
2019-08-02 11:23:16 -04:00
' feature_fee_estimation.py ' ,
2018-01-24 20:44:30 -03:00
' interface_zmq.py ' ,
2021-01-02 21:45:25 -03:00
' rpc_invalid_address_message.py ' ,
2021-12-06 16:00:37 -03:00
' interface_bitcoin_cli.py --legacy-wallet ' ,
' interface_bitcoin_cli.py --descriptors ' ,
2020-10-22 15:34:31 -03:00
' feature_bind_extra.py ' ,
2018-01-24 20:44:30 -03:00
' mempool_resurrect.py ' ,
2018-01-24 20:44:29 -03:00
' wallet_txn_doublespend.py --mineblock ' ,
2020-11-06 16:22:05 -03:00
' tool_wallet.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' tool_wallet.py --descriptors ' ,
2022-03-14 08:21:43 -03:00
' tool_signet_miner.py --legacy-wallet ' ,
' tool_signet_miner.py --descriptors ' ,
2018-01-24 20:44:29 -03:00
' wallet_txn_clone.py ' ,
' wallet_txn_clone.py --segwit ' ,
2018-01-24 20:44:29 -03:00
' rpc_getchaintips.py ' ,
2019-01-31 16:45:27 -03:00
' rpc_misc.py ' ,
2018-01-24 20:44:30 -03:00
' interface_rest.py ' ,
' mempool_spend_coinbase.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_avoidreuse.py --legacy-wallet ' ,
2019-07-16 15:33:35 -04:00
' wallet_avoidreuse.py --descriptors ' ,
2022-03-14 15:21:57 -03:00
' wallet_avoid_mixing_output_types.py --descriptors ' ,
2015-08-26 22:15:04 -03:00
' mempool_reorg.py ' ,
2017-03-09 19:14:55 -03:00
' mempool_persist.py ' ,
2022-01-26 17:59:09 -03:00
' p2p_block_sync.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_multiwallet.py --legacy-wallet ' ,
2020-10-07 15:50:05 -03:00
' wallet_multiwallet.py --descriptors ' ,
2018-01-24 20:44:29 -03:00
' wallet_multiwallet.py --usecli ' ,
2020-11-06 16:22:05 -03:00
' wallet_createwallet.py --legacy-wallet ' ,
2019-02-06 23:26:55 -03:00
' wallet_createwallet.py --usecli ' ,
2020-04-27 15:38:20 -04:00
' wallet_createwallet.py --descriptors ' ,
2021-06-18 12:09:27 -04:00
' wallet_listtransactions.py --legacy-wallet ' ,
' wallet_listtransactions.py --descriptors ' ,
2020-04-27 15:38:20 -04:00
' wallet_watchonly.py --legacy-wallet ' ,
' wallet_watchonly.py --usecli --legacy-wallet ' ,
2019-08-22 16:39:48 -04:00
' wallet_reorgsrestore.py ' ,
2018-01-24 20:44:30 -03:00
' interface_http.py ' ,
2018-11-21 07:26:27 -03:00
' interface_rpc.py ' ,
2022-04-14 13:41:18 -04:00
' interface_usdt_coinselection.py ' ,
2022-02-15 15:59:09 -03:00
' interface_usdt_net.py ' ,
2022-02-15 18:15:37 -03:00
' interface_usdt_utxocache.py ' ,
2022-02-15 19:10:44 -03:00
' interface_usdt_validation.py ' ,
2020-11-06 16:22:05 -03:00
' rpc_psbt.py --legacy-wallet ' ,
2019-07-16 15:33:35 -04:00
' rpc_psbt.py --descriptors ' ,
2018-01-24 20:44:29 -03:00
' rpc_users.py ' ,
2019-12-12 16:21:54 -03:00
' rpc_whitelist.py ' ,
2018-01-24 20:44:29 -03:00
' feature_proxy.py ' ,
2021-10-01 10:53:59 -03:00
' feature_syscall_sandbox.py ' ,
2022-06-30 09:42:01 -04:00
' wallet_signrawtransactionwithwallet.py --legacy-wallet ' ,
' wallet_signrawtransactionwithwallet.py --descriptors ' ,
' rpc_signrawtransactionwithkey.py ' ,
2022-08-11 16:31:08 -04:00
' p2p_headers_sync_with_minchainwork.py ' ,
2021-07-14 10:08:21 -04:00
' rpc_rawtransaction.py --legacy-wallet ' ,
2020-11-06 16:22:05 -03:00
' wallet_groups.py --legacy-wallet ' ,
2021-11-25 13:34:34 -03:00
' wallet_transactiontime_rescan.py --descriptors ' ,
' wallet_transactiontime_rescan.py --legacy-wallet ' ,
2020-05-20 06:05:18 -04:00
' p2p_addrv2_relay.py ' ,
2020-04-27 15:38:20 -04:00
' wallet_groups.py --descriptors ' ,
2021-06-08 21:03:46 -04:00
' p2p_compactblocks_hb.py ' ,
2018-01-24 20:44:29 -03:00
' p2p_disconnect_ban.py ' ,
2018-01-24 20:44:29 -03:00
' rpc_decodescript.py ' ,
' rpc_blockchain.py ' ,
' rpc_deprecated.py ' ,
2022-03-22 04:13:09 -03:00
' wallet_disable.py ' ,
2020-04-02 11:09:30 -03:00
' p2p_addr_relay.py ' ,
2020-05-16 21:06:22 -04:00
' p2p_getaddr_caching.py ' ,
2020-04-27 17:52:10 -04:00
' p2p_getdata.py ' ,
2021-05-31 16:52:08 -04:00
' p2p_addrfetch.py ' ,
2018-01-24 20:44:29 -03:00
' rpc_net.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_keypool.py --legacy-wallet ' ,
2019-07-16 15:33:35 -04:00
' wallet_keypool.py --descriptors ' ,
2020-10-29 13:28:07 -03:00
' wallet_descriptor.py --descriptors ' ,
2022-01-14 10:25:55 -03:00
' wallet_miniscript.py ' ,
2022-01-25 09:28:02 -03:00
' feature_maxtipage.py ' ,
2020-05-26 18:35:58 -04:00
' p2p_nobloomfilter_messages.py ' ,
2020-03-12 14:36:46 -03:00
' p2p_filter.py ' ,
2019-08-15 10:01:32 -04:00
' rpc_setban.py ' ,
2019-05-09 10:42:56 -04:00
' p2p_blocksonly.py ' ,
2018-01-24 20:44:30 -03:00
' mining_prioritisetransaction.py ' ,
2018-08-08 11:24:59 -04:00
' p2p_invalid_locator.py ' ,
2018-01-24 20:44:29 -03:00
' p2p_invalid_block.py ' ,
2018-10-19 14:34:52 -03:00
' p2p_invalid_messages.py ' ,
2018-01-24 20:44:29 -03:00
' p2p_invalid_tx.py ' ,
2018-09-15 08:13:26 -03:00
' feature_assumevalid.py ' ,
' example_test.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_txn_doublespend.py --legacy-wallet ' ,
2021-05-26 10:36:02 -04:00
' wallet_multisig_descriptor_psbt.py ' ,
2020-04-27 15:38:20 -04:00
' wallet_txn_doublespend.py --descriptors ' ,
2020-11-06 16:22:05 -03:00
' feature_backwards_compatibility.py --legacy-wallet ' ,
2020-10-27 16:26:55 -03:00
' feature_backwards_compatibility.py --descriptors ' ,
2018-09-15 08:13:26 -03:00
' wallet_txn_clone.py --mineblock ' ,
' feature_notifications.py ' ,
2018-08-31 17:28:12 -03:00
' rpc_getblockfilter.py ' ,
2021-05-13 12:51:47 -04:00
' rpc_getblockfrompeer.py ' ,
2018-09-15 08:13:26 -03:00
' rpc_invalidateblock.py ' ,
2021-01-13 20:10:04 -03:00
' feature_utxo_set_hash.py ' ,
2021-11-26 13:16:08 -03:00
' feature_rbf.py --legacy-wallet ' ,
' feature_rbf.py --descriptors ' ,
2018-09-15 08:13:26 -03:00
' mempool_packages.py ' ,
2019-03-26 17:40:58 -03:00
' mempool_package_onemore.py ' ,
2022-03-22 05:47:51 -03:00
' rpc_createmultisig.py ' ,
2021-07-15 02:29:26 -04:00
' mempool_package_limits.py ' ,
2018-01-24 20:44:29 -03:00
' feature_versionbits_warning.py ' ,
2018-01-24 20:44:29 -03:00
' rpc_preciousblock.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_importprunedfunds.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' wallet_importprunedfunds.py --descriptors ' ,
2018-09-15 21:01:20 -03:00
' p2p_leak_tx.py ' ,
2019-08-22 13:10:40 -04:00
' p2p_eviction.py ' ,
2021-08-05 17:31:12 -04:00
' wallet_signmessagewithaddress.py ' ,
' rpc_signmessagewithprivkey.py ' ,
2020-07-05 23:39:40 -04:00
' rpc_generate.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_balance.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' wallet_balance.py --descriptors ' ,
2022-07-27 11:47:13 -04:00
' p2p_initial_headers_sync.py ' ,
2022-06-24 08:26:53 -04:00
' feature_nulldummy.py ' ,
2017-11-17 14:54:39 -03:00
' mempool_accept.py ' ,
2020-02-17 20:28:06 -03:00
' mempool_expiry.py ' ,
2020-04-27 15:38:20 -04:00
' wallet_import_rescan.py --legacy-wallet ' ,
' wallet_import_with_label.py --legacy-wallet ' ,
2020-10-29 13:28:07 -03:00
' wallet_importdescriptors.py --descriptors ' ,
2020-11-06 17:34:06 -03:00
' wallet_upgradewallet.py --legacy-wallet ' ,
2018-02-22 11:43:26 -03:00
' rpc_bind.py --ipv4 ' ,
' rpc_bind.py --ipv6 ' ,
' rpc_bind.py --nonloopback ' ,
2020-04-06 16:26:20 -04:00
' wallet_crosschain.py ' ,
2018-01-24 20:44:30 -03:00
' mining_basic.py ' ,
2019-08-05 11:01:54 -04:00
' feature_signet.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_bumpfee.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' wallet_bumpfee.py --descriptors ' ,
' wallet_implicitsegwit.py --legacy-wallet ' ,
2018-01-24 20:44:29 -03:00
' rpc_named_arguments.py ' ,
2021-11-14 11:00:29 -03:00
' feature_startupnotify.py ' ,
2021-08-20 02:09:03 -04:00
' wallet_simulaterawtx.py --legacy-wallet ' ,
' wallet_simulaterawtx.py --descriptors ' ,
2020-11-06 16:22:05 -03:00
' wallet_listsinceblock.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' wallet_listsinceblock.py --descriptors ' ,
2020-10-09 04:24:20 -03:00
' wallet_listdescriptors.py --descriptors ' ,
2018-01-24 20:44:29 -03:00
' p2p_leak.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_encryption.py --legacy-wallet ' ,
2019-07-16 15:33:35 -04:00
' wallet_encryption.py --descriptors ' ,
2018-01-24 20:44:29 -03:00
' feature_dersig.py ' ,
' feature_cltv.py ' ,
2018-01-24 20:44:29 -03:00
' rpc_uptime.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_resendwallettransactions.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' wallet_resendwallettransactions.py --descriptors ' ,
2020-11-06 16:22:05 -03:00
' wallet_fallbackfee.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' wallet_fallbackfee.py --descriptors ' ,
2019-09-17 11:57:46 -03:00
' rpc_dumptxoutset.py ' ,
2018-01-24 20:44:29 -03:00
' feature_minchainwork.py ' ,
2018-07-17 15:48:09 -04:00
' rpc_estimatefee.py ' ,
2017-06-20 21:08:05 -04:00
' rpc_getblockstats.py ' ,
2020-10-18 09:45:35 -03:00
' feature_bind_port_externalip.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_create_tx.py --legacy-wallet ' ,
2020-11-06 17:34:41 -03:00
' wallet_send.py --legacy-wallet ' ,
' wallet_send.py --descriptors ' ,
Add sendall RPC née sweep
_Motivation_
Currently, the wallet uses a fSubtractFeeAmount (SFFO) flag on the
recipients objects for all forms of sending calls. According to the
commit discussion, this flag was chiefly introduced to permit sweeping
without manually calculating the fees of transactions. However, the flag
leads to unintuitive behavior and makes it more complicated to test
many wallet RPCs exhaustively. We proposed to introduce a dedicated
`sendall` RPC with the intention to cover this functionality.
Since the proposal, it was discovered in further discussion that our
proposed `sendall` rpc and SFFO have subtly different scopes of
operation.
• sendall:
Use _specific UTXOs_ to pay a destination the remainder after fees.
• SFFO:
Use a _specific budget_ to pay an address the remainder after fees.
While `sendall` will simplify cases of spending from specific UTXOs,
emptying a wallet, or burning dust, we realized that there are some
cases in which SFFO is used to pay other parties from a limited budget,
which can often lead to the creation of change outputs. This cannot be
easily replicated using `sendall` as it would require manual computation
of the appropriate change amount.
As such, sendall cannot replace all uses of SFFO, but it still has a
different use case and will aid in simplifying some wallet calls and
numerous wallet tests.
_Sendall call details_
The proposed sendall call builds a transaction from a specific subset of
the wallet's UTXO pool (by default all of them) and assigns the funds to
one or more receivers. Receivers can either be specified with a specific
amount or receive an equal share of the remaining unassigned funds. At
least one recipient must be provided without assigned amount to collect
the remainder. The `sendall` call will never create change. The call has
a `send_max` option that changes the default behavior of spending all
UTXOs ("no UTXO left behind"), to maximizing the output amount of the
transaction by skipping uneconomic UTXOs. The `send_max` option is
incompatible with providing a specific set of inputs.
2022-01-12 15:59:41 -03:00
' wallet_sendall.py --legacy-wallet ' ,
' wallet_sendall.py --descriptors ' ,
2020-04-27 15:38:20 -04:00
' wallet_create_tx.py --descriptors ' ,
2021-02-28 01:34:09 -03:00
' wallet_taproot.py ' ,
2021-10-18 17:24:24 -03:00
' wallet_inactive_hdchains.py ' ,
2018-01-24 20:44:29 -03:00
' p2p_fingerprint.py ' ,
2018-01-24 20:44:29 -03:00
' feature_uacomment.py ' ,
2021-10-15 18:29:48 -03:00
' feature_init.py ' ,
2020-11-06 16:22:05 -03:00
' wallet_coinbase_category.py --legacy-wallet ' ,
2020-04-27 15:38:20 -04:00
' wallet_coinbase_category.py --descriptors ' ,
2018-10-07 13:24:20 -03:00
' feature_filelock.py ' ,
2019-10-03 15:53:21 -03:00
' feature_loadblock.py ' ,
2019-07-31 14:34:04 -04:00
' p2p_dos_header_tree.py ' ,
2020-09-18 18:41:18 -03:00
' p2p_add_connections.py ' ,
2020-10-19 10:32:54 -03:00
' feature_bind_port_discover.py ' ,
2018-01-24 20:44:29 -03:00
' p2p_unrequested_blocks.py ' ,
2020-05-04 14:10:18 -04:00
' p2p_blockfilters.py ' ,
2020-07-23 14:36:17 -04:00
' p2p_message_capture.py ' ,
2017-05-01 22:26:33 -03:00
' feature_includeconf.py ' ,
2021-09-03 10:44:58 -04:00
' feature_addrman.py ' ,
2020-01-29 12:38:25 -03:00
' feature_asmap.py ' ,
2020-01-29 13:23:10 -03:00
' mempool_unbroadcast.py ' ,
2020-06-03 04:56:08 -04:00
' mempool_compatibility.py ' ,
2021-06-11 09:49:19 -04:00
' mempool_accept_wtxid.py ' ,
2019-01-09 16:16:46 -03:00
' rpc_deriveaddresses.py ' ,
' rpc_deriveaddresses.py --usecli ' ,
2020-04-14 13:24:18 -04:00
' p2p_ping.py ' ,
2018-01-16 03:34:07 -03:00
' rpc_scantxoutset.py ' ,
2021-09-20 09:41:32 -03:00
' feature_txindex_compatibility.py ' ,
2022-02-02 09:23:02 -03:00
' feature_unsupported_utxo_db.py ' ,
2017-11-28 11:38:46 -03:00
' feature_logging.py ' ,
2021-03-02 10:52:02 -03:00
' feature_anchors.py ' ,
2022-08-06 01:57:14 -04:00
' mempool_datacarrier.py ' ,
2022-03-22 04:13:09 -03:00
' feature_coinstatsindex.py ' ,
2020-04-28 03:26:09 -04:00
' wallet_orphanedreward.py ' ,
2022-01-14 07:51:38 -03:00
' wallet_timelock.py ' ,
2018-01-24 20:44:29 -03:00
' p2p_node_network_limited.py ' ,
2019-06-21 00:15:26 -04:00
' p2p_permissions.py ' ,
2018-03-09 01:43:55 -03:00
' feature_blocksdir.py ' ,
2019-05-01 15:12:44 -04:00
' wallet_startup.py ' ,
2021-06-23 04:57:28 -04:00
' p2p_i2p_ports.py ' ,
2022-06-09 06:22:24 -04:00
' p2p_i2p_sessions.py ' ,
2018-01-24 20:44:29 -03:00
' feature_config_args.py ' ,
2021-02-05 17:10:39 -03:00
' feature_presegwit_node_upgrade.py ' ,
2019-04-28 19:08:26 -04:00
' feature_settings.py ' ,
2019-02-19 12:14:27 -03:00
' rpc_getdescriptorinfo.py ' ,
2022-04-25 04:29:25 -04:00
' rpc_mempool_info.py ' ,
2018-08-21 21:33:34 -03:00
' rpc_help.py ' ,
2022-02-16 12:31:41 -03:00
' feature_dirsymlinks.py ' ,
2018-03-30 12:36:38 -03:00
' feature_help.py ' ,
2018-11-20 14:59:07 -03:00
' feature_shutdown.py ' ,
2020-07-27 16:00:11 -04:00
' wallet_migration.py ' ,
2020-06-19 12:49:51 -04:00
' p2p_ibd_txrelay.py ' ,
2017-12-20 17:05:31 -03:00
# Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time
2015-08-26 07:05:36 -03:00
]
2016-04-09 17:17:52 -03:00
2017-04-17 19:20:35 -03:00
# Place EXTENDED_SCRIPTS first since it has the 3 longest running tests
2017-03-24 19:36:55 -03:00
ALL_SCRIPTS = EXTENDED_SCRIPTS + BASE_SCRIPTS
2016-04-29 07:51:15 -03:00
2017-03-27 12:33:00 -03:00
NON_SCRIPTS = [
# These are python files that live in the functional tests directory, but are not test scripts.
" combine_logs.py " ,
" create_cache.py " ,
" test_runner.py " ,
]
2017-02-06 11:07:14 -03:00
def main ( ) :
# Parse arguments and pass through unrecognised args
parser = argparse . ArgumentParser ( add_help = False ,
2017-03-09 11:53:26 -03:00
usage = ' %(prog)s [test_runner.py options] [script options] [scripts] ' ,
2017-02-06 11:07:14 -03:00
description = __doc__ ,
epilog = '''
Help text and arguments for individual test script : ''' ,
formatter_class = argparse . RawTextHelpFormatter )
2019-08-07 04:12:30 -04:00
parser . add_argument ( ' --ansi ' , action = ' store_true ' , default = sys . stdout . isatty ( ) , help = " Use ANSI colors and dots in output (enabled by default when standard output is a TTY) " )
2018-10-31 10:39:25 -03:00
parser . add_argument ( ' --combinedlogslen ' , ' -c ' , type = int , default = 0 , metavar = ' n ' , help = ' On failure, print a log (of length n lines) to the console, combined from the test framework and all test nodes. ' )
2017-02-06 11:07:14 -03:00
parser . add_argument ( ' --coverage ' , action = ' store_true ' , help = ' generate a basic coverage report for the RPC interface ' )
2018-11-01 11:52:17 -03:00
parser . add_argument ( ' --ci ' , action = ' store_true ' , help = ' Run checks and code that are usually only enabled in a continuous integration environment ' )
2017-08-15 19:24:39 -03:00
parser . add_argument ( ' --exclude ' , ' -x ' , help = ' specify a comma-separated-list of scripts to exclude. ' )
2017-02-06 11:07:14 -03:00
parser . add_argument ( ' --extended ' , action = ' store_true ' , help = ' run the extended test suite in addition to the basic tests ' )
parser . add_argument ( ' --help ' , ' -h ' , ' -? ' , action = ' store_true ' , help = ' print help text and exit ' )
parser . add_argument ( ' --jobs ' , ' -j ' , type = int , default = 4 , help = ' how many test scripts to run in parallel. Default=4. ' )
2017-04-12 12:01:31 -03:00
parser . add_argument ( ' --keepcache ' , ' -k ' , action = ' store_true ' , help = ' the default behavior is to flush the cache directory on startup. --keepcache retains the cache from the previous testrun. ' )
2018-10-30 10:06:44 -03:00
parser . add_argument ( ' --quiet ' , ' -q ' , action = ' store_true ' , help = ' only print dots, results summary and failure logs ' )
2017-05-18 17:33:33 -04:00
parser . add_argument ( ' --tmpdirprefix ' , ' -t ' , default = tempfile . gettempdir ( ) , help = " Root directory for datadirs " )
2021-10-01 07:51:17 -03:00
parser . add_argument ( ' --failfast ' , ' -F ' , action = ' store_true ' , help = ' stop execution after the first test failure ' )
2019-07-14 18:11:55 -04:00
parser . add_argument ( ' --filter ' , help = ' filter scripts to run by regular expression ' )
2019-08-07 04:12:30 -04:00
2017-02-17 16:22:56 -03:00
args , unknown_args = parser . parse_known_args ( )
2019-08-07 04:12:30 -04:00
if not args . ansi :
2022-04-06 17:44:07 -04:00
global DEFAULT , BOLD , GREEN , RED
DEFAULT = ( " " , " " )
2019-08-07 04:12:30 -04:00
BOLD = ( " " , " " )
GREEN = ( " " , " " )
RED = ( " " , " " )
2017-02-06 11:07:14 -03:00
2017-05-09 11:39:37 -03:00
# args to be passed on always start with two dashes; tests are the remaining unknown args
tests = [ arg for arg in unknown_args if arg [ : 2 ] != " -- " ]
2017-02-06 11:07:14 -03:00
passon_args = [ arg for arg in unknown_args if arg [ : 2 ] == " -- " ]
# Read config generated by configure.
config = configparser . ConfigParser ( )
2017-03-21 15:47:20 -03:00
configfile = os . path . abspath ( os . path . dirname ( __file__ ) ) + " /../config.ini "
2018-06-12 11:49:20 -04:00
config . read_file ( open ( configfile , encoding = " utf8 " ) )
2017-05-01 16:12:49 -03:00
passon_args . append ( " --configfile= %s " % configfile )
2017-02-06 11:07:14 -03:00
2017-02-16 16:00:35 -03:00
# Set up logging
logging_level = logging . INFO if args . quiet else logging . DEBUG
logging . basicConfig ( format = ' %(message)s ' , level = logging_level )
2017-05-18 17:33:33 -04:00
# Create base test directory
2018-08-03 09:12:16 -04:00
tmpdir = " %s /test_runner_₿_🏃_ %s " % ( args . tmpdirprefix , datetime . datetime . now ( ) . strftime ( " % Y % m %d _ % H % M % S " ) )
2018-08-19 15:54:54 -03:00
2017-05-18 17:33:33 -04:00
os . makedirs ( tmpdir )
logging . debug ( " Temporary test directory at %s " % tmpdir )
2017-02-17 16:22:56 -03:00
enable_bitcoind = config [ " components " ] . getboolean ( " ENABLE_BITCOIND " )
2017-02-06 11:07:14 -03:00
2018-09-09 14:32:37 -03:00
if not enable_bitcoind :
print ( " No functional tests to run. " )
print ( " Rerun ./configure with --with-daemon and then make " )
2017-02-06 11:07:14 -03:00
sys . exit ( 0 )
2017-01-31 15:15:40 -03:00
# Build list of tests
2018-03-17 15:40:56 -03:00
test_list = [ ]
2017-02-17 16:22:56 -03:00
if tests :
2017-01-31 15:15:40 -03:00
# Individual tests have been specified. Run specified tests that exist
2019-07-11 14:36:02 -04:00
# in the ALL_SCRIPTS list. Accept names with or without a .py extension.
# Specified tests can contain wildcards, but in that case the supplied
# paths should be coherent, e.g. the same path as that provided to call
# test_runner.py. Examples:
# `test/functional/test_runner.py test/functional/wallet*`
# `test/functional/test_runner.py ./test/functional/wallet*`
# `test_runner.py wallet*`
# but not:
# `test/functional/test_runner.py wallet*`
# Multiple wildcards can be passed:
# `test_runner.py tool* mempool*`
2018-02-14 22:24:42 -03:00
for test in tests :
2019-07-11 14:36:02 -04:00
script = test . split ( " / " ) [ - 1 ]
script = script + " .py " if " .py " not in script else script
2021-09-25 21:58:19 -03:00
matching_scripts = [ s for s in ALL_SCRIPTS if s . startswith ( script ) ]
if matching_scripts :
test_list . extend ( matching_scripts )
2017-05-09 11:39:37 -03:00
else :
2018-02-14 22:24:42 -03:00
print ( " {} WARNING! {} Test ' {} ' not found in full test list. " . format ( BOLD [ 1 ] , BOLD [ 0 ] , test ) )
elif args . extended :
# Include extended tests
2018-03-17 15:40:56 -03:00
test_list + = ALL_SCRIPTS
2016-04-27 17:29:52 -03:00
else :
2018-02-14 22:24:42 -03:00
# Run base tests only
2018-03-17 15:40:56 -03:00
test_list + = BASE_SCRIPTS
2017-01-31 15:15:40 -03:00
2017-02-15 11:59:19 -03:00
# Remove the test cases that the user has explicitly asked to exclude.
if args . exclude :
2018-09-24 23:51:46 -03:00
exclude_tests = [ test . split ( ' .py ' ) [ 0 ] for test in args . exclude . split ( ' , ' ) ]
2018-02-14 22:24:42 -03:00
for exclude_test in exclude_tests :
2018-09-24 23:51:46 -03:00
# Remove <test_name>.py and <test_name>.py --arg from the test list
exclude_list = [ test for test in test_list if test . split ( ' .py ' ) [ 0 ] == exclude_test ]
for exclude_item in exclude_list :
test_list . remove ( exclude_item )
if not exclude_list :
2017-05-09 11:39:37 -03:00
print ( " {} WARNING! {} Test ' {} ' not found in current test list. " . format ( BOLD [ 1 ] , BOLD [ 0 ] , exclude_test ) )
2017-02-15 11:59:19 -03:00
2019-07-14 18:11:55 -04:00
if args . filter :
test_list = list ( filter ( re . compile ( args . filter ) . search , test_list ) )
2017-02-15 11:59:19 -03:00
if not test_list :
print ( " No valid test scripts specified. Check that your test is in one "
2017-03-09 11:53:26 -03:00
" of the test lists in test_runner.py, or run test_runner.py with no arguments to run all tests " )
2017-02-15 11:59:19 -03:00
sys . exit ( 0 )
2017-01-31 15:15:40 -03:00
if args . help :
2017-03-23 14:54:18 -03:00
# Print help for test_runner.py, then print help of the first script (with args removed) and exit.
2017-01-31 16:32:49 -03:00
parser . print_help ( )
2017-12-09 20:39:06 -03:00
subprocess . check_call ( [ sys . executable , os . path . join ( config [ " environment " ] [ " SRCDIR " ] , ' test ' , ' functional ' , test_list [ 0 ] . split ( ) [ 0 ] ) , ' -h ' ] )
2016-04-27 17:29:52 -03:00
sys . exit ( 0 )
2018-11-01 11:52:17 -03:00
check_script_list ( src_dir = config [ " environment " ] [ " SRCDIR " ] , fail_on_warn = args . ci )
2017-11-30 07:11:18 -03:00
check_script_prefixes ( )
2017-03-27 12:33:00 -03:00
2017-04-12 12:01:31 -03:00
if not args . keepcache :
shutil . rmtree ( " %s /test/cache " % config [ " environment " ] [ " BUILDDIR " ] , ignore_errors = True )
2018-04-27 13:50:45 -03:00
run_tests (
2018-11-01 11:52:17 -03:00
test_list = test_list ,
src_dir = config [ " environment " ] [ " SRCDIR " ] ,
build_dir = config [ " environment " ] [ " BUILDDIR " ] ,
tmpdir = tmpdir ,
2018-04-27 13:50:45 -03:00
jobs = args . jobs ,
enable_coverage = args . coverage ,
args = passon_args ,
combined_logs_len = args . combinedlogslen ,
2018-11-01 11:52:17 -03:00
failfast = args . failfast ,
2019-08-07 04:12:30 -04:00
use_term_control = args . ansi ,
2018-04-27 13:50:45 -03:00
)
2020-03-20 13:08:33 -03:00
def run_tests ( * , test_list , src_dir , build_dir , tmpdir , jobs = 1 , enable_coverage = False , args = None , combined_logs_len = 0 , failfast = False , use_term_control ) :
2018-04-27 13:50:45 -03:00
args = args or [ ]
2017-02-06 11:07:14 -03:00
2019-11-15 13:38:27 -03:00
# Warn if bitcoind is already running
2017-04-12 11:41:13 -03:00
try :
2020-06-23 22:08:41 -04:00
# pgrep exits with code zero when one or more matching processes found
if subprocess . run ( [ " pgrep " , " -x " , " bitcoind " ] , stdout = subprocess . DEVNULL ) . returncode == 0 :
2017-04-12 11:41:13 -03:00
print ( " %s WARNING! %s There is already a bitcoind process running on this system. Tests may fail unexpectedly due to resource contention! " % ( BOLD [ 1 ] , BOLD [ 0 ] ) )
2020-06-23 22:08:41 -04:00
except OSError :
# pgrep not supported
2017-04-12 11:41:13 -03:00
pass
# Warn if there is a cache directory
cache_dir = " %s /test/cache " % build_dir
if os . path . isdir ( cache_dir ) :
print ( " %s WARNING! %s There is a cache directory here: %s . If tests fail unexpectedly, try deleting the cache directory. " % ( BOLD [ 1 ] , BOLD [ 0 ] , cache_dir ) )
2017-02-06 11:07:14 -03:00
2020-03-26 17:57:53 -03:00
# Test Framework Tests
print ( " Running Unit Tests for Test Framework Modules " )
test_framework_tests = unittest . TestSuite ( )
for module in TEST_FRAMEWORK_MODULES :
test_framework_tests . addTest ( unittest . TestLoader ( ) . loadTestsFromName ( " test_framework. {} " . format ( module ) ) )
result = unittest . TextTestRunner ( verbosity = 1 , failfast = True ) . run ( test_framework_tests )
if not result . wasSuccessful ( ) :
logging . debug ( " Early exiting after failure in TestFramework unit tests " )
sys . exit ( False )
2017-03-09 11:44:57 -03:00
tests_dir = src_dir + ' /test/functional/ '
2015-10-11 02:41:19 -03:00
2018-05-08 13:56:13 -03:00
flags = [ ' --cachedir= {} ' . format ( cache_dir ) ] + args
2017-02-06 11:07:14 -03:00
if enable_coverage :
2015-10-11 02:41:19 -03:00
coverage = RPCCoverage ( )
2016-04-29 07:51:15 -03:00
flags . append ( coverage . flag )
2017-02-16 16:00:35 -03:00
logging . debug ( " Initializing coverage directory at %s " % coverage . dir )
2017-02-06 11:07:14 -03:00
else :
coverage = None
2016-04-29 07:51:15 -03:00
2017-01-31 16:32:49 -03:00
if len ( test_list ) > 1 and jobs > 1 :
2016-04-29 07:51:15 -03:00
# Populate cache
2017-10-13 07:19:20 -03:00
try :
2017-12-09 20:39:06 -03:00
subprocess . check_output ( [ sys . executable , tests_dir + ' create_cache.py ' ] + flags + [ " --tmpdir= %s /cache " % tmpdir ] )
2018-01-19 15:04:03 -03:00
except subprocess . CalledProcessError as e :
sys . stdout . buffer . write ( e . output )
raise
2016-04-09 17:17:52 -03:00
#Run Tests
2018-11-01 11:52:17 -03:00
job_queue = TestHandler (
num_tests_parallel = jobs ,
tests_dir = tests_dir ,
tmpdir = tmpdir ,
test_list = test_list ,
flags = flags ,
2019-08-07 04:12:30 -04:00
use_term_control = use_term_control ,
2018-11-01 11:52:17 -03:00
)
2018-02-14 22:24:42 -03:00
start_time = time . time ( )
2017-04-05 16:19:26 -03:00
test_results = [ ]
2017-02-06 11:07:14 -03:00
max_len_name = len ( max ( test_list , key = len ) )
2018-10-17 11:12:42 -03:00
test_count = len ( test_list )
2022-01-28 11:17:35 -03:00
all_passed = True
2021-12-16 17:19:52 -03:00
i = 0
while i < test_count :
2022-01-28 11:17:35 -03:00
if failfast and not all_passed :
break
2021-12-16 17:19:52 -03:00
for test_result , testdir , stdout , stderr in job_queue . get_next ( ) :
test_results . append ( test_result )
i + = 1
done_str = " {} / {} - {} {} {} " . format ( i , test_count , BOLD [ 1 ] , test_result . name , BOLD [ 0 ] )
if test_result . status == " Passed " :
logging . debug ( " %s passed, Duration: %s s " % ( done_str , test_result . time ) )
elif test_result . status == " Skipped " :
logging . debug ( " %s skipped " % ( done_str ) )
else :
2022-01-28 11:17:35 -03:00
all_passed = False
2021-12-16 17:19:52 -03:00
print ( " %s failed, Duration: %s s \n " % ( done_str , test_result . time ) )
print ( BOLD [ 1 ] + ' stdout: \n ' + BOLD [ 0 ] + stdout + ' \n ' )
print ( BOLD [ 1 ] + ' stderr: \n ' + BOLD [ 0 ] + stderr + ' \n ' )
if combined_logs_len and os . path . isdir ( testdir ) :
# Print the final `combinedlogslen` lines of the combined logs
print ( ' {} Combine the logs and print the last {} lines ... {} ' . format ( BOLD [ 1 ] , combined_logs_len , BOLD [ 0 ] ) )
print ( ' \n ============ ' )
print ( ' {} Combined log for {} : {} ' . format ( BOLD [ 1 ] , testdir , BOLD [ 0 ] ) )
print ( ' ============ \n ' )
combined_logs_args = [ sys . executable , os . path . join ( tests_dir , ' combine_logs.py ' ) , testdir ]
if BOLD [ 0 ] :
combined_logs_args + = [ ' --color ' ]
combined_logs , _ = subprocess . Popen ( combined_logs_args , universal_newlines = True , stdout = subprocess . PIPE ) . communicate ( )
print ( " \n " . join ( deque ( combined_logs . splitlines ( ) , combined_logs_len ) ) )
if failfast :
logging . debug ( " Early exiting after test failure " )
break
2018-04-27 13:50:45 -03:00
2018-02-14 22:24:42 -03:00
print_results ( test_results , max_len_name , ( int ( time . time ( ) - start_time ) ) )
2016-04-09 17:17:52 -03:00
if coverage :
2019-05-02 16:48:05 -04:00
coverage_passed = coverage . report_rpc_coverage ( )
2016-04-09 17:17:52 -03:00
2017-02-16 16:00:35 -03:00
logging . debug ( " Cleaning up coverage data " )
2016-04-09 17:17:52 -03:00
coverage . cleanup ( )
2019-05-02 16:48:05 -04:00
else :
coverage_passed = True
2015-10-11 02:41:19 -03:00
2017-05-18 17:33:33 -04:00
# Clear up the temp directory if all subdirectories are gone
if not os . listdir ( tmpdir ) :
os . rmdir ( tmpdir )
2022-01-28 11:17:35 -03:00
all_passed = all_passed and coverage_passed
2017-04-05 16:19:26 -03:00
2021-06-14 03:44:11 -04:00
# Clean up dangling processes if any. This may only happen with --failfast option.
# Killing the process group will also terminate the current process but that is
# not an issue
2022-03-15 15:19:50 -03:00
if not os . getenv ( " CI_FAILFAST_TEST_LEAVE_DANGLING " ) and len ( job_queue . jobs ) :
2021-06-14 03:44:11 -04:00
os . killpg ( os . getpgid ( 0 ) , signal . SIGKILL )
2018-04-27 13:50:45 -03:00
2016-04-29 07:51:15 -03:00
sys . exit ( not all_passed )
2022-03-15 15:19:50 -03:00
2017-04-05 16:19:26 -03:00
def print_results ( test_results , max_len_name , runtime ) :
2017-07-25 15:39:39 -04:00
results = " \n " + BOLD [ 1 ] + " %s | %s | %s \n \n " % ( " TEST " . ljust ( max_len_name ) , " STATUS " , " DURATION " ) + BOLD [ 0 ]
2017-04-05 16:19:26 -03:00
2018-03-28 06:55:34 -03:00
test_results . sort ( key = TestResult . sort_key )
2017-04-05 16:19:26 -03:00
all_passed = True
time_sum = 0
for test_result in test_results :
2017-04-11 14:40:54 -03:00
all_passed = all_passed and test_result . was_successful
2017-04-05 16:19:26 -03:00
time_sum + = test_result . time
test_result . padding = max_len_name
results + = str ( test_result )
2017-04-07 10:15:29 -03:00
status = TICK + " Passed " if all_passed else CROSS + " Failed "
2018-03-28 06:17:36 -03:00
if not all_passed :
results + = RED [ 1 ]
2017-07-25 15:39:39 -04:00
results + = BOLD [ 1 ] + " \n %s | %s | %s s (accumulated) \n " % ( " ALL " . ljust ( max_len_name ) , status . ljust ( 9 ) , time_sum ) + BOLD [ 0 ]
2018-03-28 06:17:36 -03:00
if not all_passed :
results + = RED [ 0 ]
2017-04-05 16:19:26 -03:00
results + = " Runtime: %s s \n " % ( runtime )
print ( results )
2017-03-09 11:44:57 -03:00
class TestHandler :
2016-04-29 07:51:15 -03:00
"""
2017-07-01 00:00:51 -04:00
Trigger the test scripts passed in via the list .
2016-04-29 07:51:15 -03:00
"""
2020-03-20 13:08:33 -03:00
def __init__ ( self , * , num_tests_parallel , tests_dir , tmpdir , test_list , flags , use_term_control ) :
2018-11-01 11:52:17 -03:00
assert num_tests_parallel > = 1
2016-04-29 07:51:15 -03:00
self . num_jobs = num_tests_parallel
2017-02-06 11:07:14 -03:00
self . tests_dir = tests_dir
2017-05-18 17:33:33 -04:00
self . tmpdir = tmpdir
2016-04-29 07:51:15 -03:00
self . test_list = test_list
self . flags = flags
self . num_running = 0
self . jobs = [ ]
2019-08-07 04:12:30 -04:00
self . use_term_control = use_term_control
2016-04-29 07:51:15 -03:00
def get_next ( self ) :
while self . num_running < self . num_jobs and self . test_list :
# Add tests
self . num_running + = 1
2018-02-14 22:24:42 -03:00
test = self . test_list . pop ( 0 )
2018-06-07 11:30:58 -04:00
portseed = len ( self . test_list )
2017-05-18 17:33:33 -04:00
portseed_arg = [ " --portseed= {} " . format ( portseed ) ]
2016-09-17 06:47:51 -03:00
log_stdout = tempfile . SpooledTemporaryFile ( max_size = 2 * * 16 )
log_stderr = tempfile . SpooledTemporaryFile ( max_size = 2 * * 16 )
2018-02-14 22:24:42 -03:00
test_argv = test . split ( )
2017-11-29 15:25:23 -03:00
testdir = " {} / {} _ {} " . format ( self . tmpdir , re . sub ( " .py$ " , " " , test_argv [ 0 ] ) , portseed )
tmpdir_arg = [ " --tmpdir= {} " . format ( testdir ) ]
2018-02-14 22:24:42 -03:00
self . jobs . append ( ( test ,
2016-04-29 07:51:15 -03:00
time . time ( ) ,
2017-12-09 20:39:06 -03:00
subprocess . Popen ( [ sys . executable , self . tests_dir + test_argv [ 0 ] ] + test_argv [ 1 : ] + self . flags + portseed_arg + tmpdir_arg ,
2016-04-29 07:51:15 -03:00
universal_newlines = True ,
2016-09-17 06:47:51 -03:00
stdout = log_stdout ,
stderr = log_stderr ) ,
2017-11-29 15:25:23 -03:00
testdir ,
2016-09-17 06:47:51 -03:00
log_stdout ,
log_stderr ) )
2016-04-29 07:51:15 -03:00
if not self . jobs :
2016-05-09 16:29:18 -03:00
raise IndexError ( ' pop from empty list ' )
2019-02-22 15:06:34 -03:00
# Print remaining running jobs when all jobs have been started.
if not self . test_list :
print ( " Remaining jobs: [ {} ] " . format ( " , " . join ( j [ 0 ] for j in self . jobs ) ) )
2018-10-17 11:12:42 -03:00
dot_count = 0
2016-04-29 07:51:15 -03:00
while True :
2021-12-16 17:19:52 -03:00
# Return all procs that have finished, if any. Otherwise sleep until there is one.
2016-04-29 07:51:15 -03:00
time . sleep ( .5 )
2021-12-16 17:19:52 -03:00
ret = [ ]
2018-02-14 22:24:42 -03:00
for job in self . jobs :
( name , start_time , proc , testdir , log_out , log_err ) = job
2016-04-29 07:51:15 -03:00
if proc . poll ( ) is not None :
2016-09-17 06:47:51 -03:00
log_out . seek ( 0 ) , log_err . seek ( 0 )
2018-03-18 19:54:29 -03:00
[ stdout , stderr ] = [ log_file . read ( ) . decode ( ' utf-8 ' ) for log_file in ( log_out , log_err ) ]
2016-09-17 06:47:51 -03:00
log_out . close ( ) , log_err . close ( )
2017-03-22 11:26:02 -03:00
if proc . returncode == TEST_EXIT_PASSED and stderr == " " :
2017-07-25 15:39:39 -04:00
status = " Passed "
2017-03-22 11:26:02 -03:00
elif proc . returncode == TEST_EXIT_SKIPPED :
2017-07-25 15:39:39 -04:00
status = " Skipped "
2017-03-22 11:26:02 -03:00
else :
2017-07-25 15:39:39 -04:00
status = " Failed "
2016-04-29 07:51:15 -03:00
self . num_running - = 1
2018-02-14 22:24:42 -03:00
self . jobs . remove ( job )
2019-08-07 04:12:30 -04:00
if self . use_term_control :
clearline = ' \r ' + ( ' ' * dot_count ) + ' \r '
print ( clearline , end = ' ' , flush = True )
2018-10-30 10:06:44 -03:00
dot_count = 0
2021-12-16 17:19:52 -03:00
ret . append ( ( TestResult ( name , status , int ( time . time ( ) - start_time ) ) , testdir , stdout , stderr ) )
if ret :
return ret
2019-08-07 04:12:30 -04:00
if self . use_term_control :
print ( ' . ' , end = ' ' , flush = True )
2018-10-30 10:06:44 -03:00
dot_count + = 1
2016-04-29 07:51:15 -03:00
2018-04-27 13:50:45 -03:00
2017-04-05 16:19:26 -03:00
class TestResult ( ) :
def __init__ ( self , name , status , time ) :
self . name = name
self . status = status
self . time = time
self . padding = 0
2018-03-28 06:55:34 -03:00
def sort_key ( self ) :
if self . status == " Passed " :
return 0 , self . name . lower ( )
elif self . status == " Failed " :
return 2 , self . name . lower ( )
elif self . status == " Skipped " :
return 1 , self . name . lower ( )
2017-04-05 16:19:26 -03:00
def __repr__ ( self ) :
2017-07-25 15:39:39 -04:00
if self . status == " Passed " :
2018-08-19 15:54:54 -03:00
color = GREEN
2017-04-07 10:15:29 -03:00
glyph = TICK
2017-07-25 15:39:39 -04:00
elif self . status == " Failed " :
2017-04-20 15:56:37 -03:00
color = RED
glyph = CROSS
2017-07-25 15:39:39 -04:00
elif self . status == " Skipped " :
2022-04-06 17:44:07 -04:00
color = DEFAULT
2017-07-25 15:39:39 -04:00
glyph = CIRCLE
2017-04-06 14:31:47 -03:00
2017-07-25 15:39:39 -04:00
return color [ 1 ] + " %s | %s %s | %s s \n " % ( self . name . ljust ( self . padding ) , glyph , self . status . ljust ( 7 ) , self . time ) + color [ 0 ]
2017-04-05 16:19:26 -03:00
2017-04-11 14:40:54 -03:00
@property
def was_successful ( self ) :
return self . status != " Failed "
2017-04-05 16:19:26 -03:00
2017-11-30 07:11:18 -03:00
def check_script_prefixes ( ) :
2018-01-23 17:16:20 -03:00
""" Check that test scripts start with one of the allowed name prefixes. """
2017-11-30 07:11:18 -03:00
2020-03-26 17:57:53 -03:00
good_prefixes_re = re . compile ( " ^(example|feature|interface|mempool|mining|p2p|rpc|wallet|tool)_ " )
2017-11-30 07:11:18 -03:00
bad_script_names = [ script for script in ALL_SCRIPTS if good_prefixes_re . match ( script ) is None ]
2018-01-23 17:16:20 -03:00
if bad_script_names :
print ( " %s ERROR: %s %d tests not meeting naming conventions: " % ( BOLD [ 1 ] , BOLD [ 0 ] , len ( bad_script_names ) ) )
2017-11-30 07:11:18 -03:00
print ( " %s " % ( " \n " . join ( sorted ( bad_script_names ) ) ) )
2018-01-23 17:16:20 -03:00
raise AssertionError ( " Some tests are not following naming convention! " )
2017-11-30 07:11:18 -03:00
2018-11-01 11:52:17 -03:00
def check_script_list ( * , src_dir , fail_on_warn ) :
2017-03-27 12:33:00 -03:00
""" Check scripts directory.
Check that there are no scripts in the functional tests directory which are
not being run by pull - tester . py . """
script_dir = src_dir + ' /test/functional/ '
2018-03-18 19:54:29 -03:00
python_files = set ( [ test_file for test_file in os . listdir ( script_dir ) if test_file . endswith ( " .py " ) ] )
2017-03-27 12:33:00 -03:00
missed_tests = list ( python_files - set ( map ( lambda x : x . split ( ) [ 0 ] , ALL_SCRIPTS + NON_SCRIPTS ) ) )
if len ( missed_tests ) != 0 :
2017-04-12 11:41:13 -03:00
print ( " %s WARNING! %s The following scripts are not being run: %s . Check the test lists in test_runner.py. " % ( BOLD [ 1 ] , BOLD [ 0 ] , str ( missed_tests ) ) )
2018-11-01 11:52:17 -03:00
if fail_on_warn :
2019-06-19 11:39:27 -04:00
# On CI this warning is an error to prevent merging incomplete commits into master
2017-04-12 11:41:13 -03:00
sys . exit ( 1 )
2015-10-11 02:41:19 -03:00
2018-11-01 11:52:17 -03:00
2017-10-16 22:46:23 -03:00
class RPCCoverage ( ) :
2015-10-11 02:41:19 -03:00
"""
2017-03-09 11:53:26 -03:00
Coverage reporting utilities for test_runner .
2015-10-11 02:41:19 -03:00
Coverage calculation works by having each test script subprocess write
coverage files into a particular directory . These files contain the RPC
commands invoked during testing , as well as a complete listing of RPC
commands per ` bitcoin - cli help ` ( ` rpc_interface . txt ` ) .
After all tests complete , the commands run are combined and diff ' d against
the complete list to calculate uncovered RPC commands .
2017-03-09 11:44:57 -03:00
See also : test / functional / test_framework / coverage . py
2015-10-11 02:41:19 -03:00
"""
def __init__ ( self ) :
self . dir = tempfile . mkdtemp ( prefix = " coverage " )
2016-04-29 07:51:15 -03:00
self . flag = ' --coveragedir= %s ' % self . dir
2015-10-11 02:41:19 -03:00
def report_rpc_coverage ( self ) :
"""
Print out RPC commands that were unexercised by tests .
"""
uncovered = self . _get_uncovered_rpc_commands ( )
if uncovered :
print ( " Uncovered RPC commands: " )
2018-02-14 22:24:42 -03:00
print ( " " . join ( ( " - %s \n " % command ) for command in sorted ( uncovered ) ) )
2019-05-02 16:48:05 -04:00
return False
2015-10-11 02:41:19 -03:00
else :
print ( " All RPC commands covered. " )
2019-05-02 16:48:05 -04:00
return True
2015-10-11 02:41:19 -03:00
def cleanup ( self ) :
return shutil . rmtree ( self . dir )
def _get_uncovered_rpc_commands ( self ) :
"""
Return a set of currently untested RPC commands .
"""
2020-07-07 05:56:54 -04:00
# This is shared from `test/functional/test_framework/coverage.py`
2017-02-06 11:07:14 -03:00
reference_filename = ' rpc_interface.txt '
coverage_file_prefix = ' coverage. '
2015-10-11 02:41:19 -03:00
2017-02-06 11:07:14 -03:00
coverage_ref_filename = os . path . join ( self . dir , reference_filename )
2015-10-11 02:41:19 -03:00
coverage_filenames = set ( )
all_cmds = set ( )
2020-07-07 05:56:54 -04:00
# Consider RPC generate covered, because it is overloaded in
# test_framework/test_node.py and not seen by the coverage check.
covered_cmds = set ( { ' generate ' } )
2015-10-11 02:41:19 -03:00
if not os . path . isfile ( coverage_ref_filename ) :
raise RuntimeError ( " No coverage reference found " )
2018-06-12 11:49:20 -04:00
with open ( coverage_ref_filename , ' r ' , encoding = " utf8 " ) as coverage_ref_file :
2018-03-18 19:54:29 -03:00
all_cmds . update ( [ line . strip ( ) for line in coverage_ref_file . readlines ( ) ] )
2015-10-11 02:41:19 -03:00
2018-09-24 17:45:58 -03:00
for root , _ , files in os . walk ( self . dir ) :
2015-10-11 02:41:19 -03:00
for filename in files :
2017-02-06 11:07:14 -03:00
if filename . startswith ( coverage_file_prefix ) :
2015-10-11 02:41:19 -03:00
coverage_filenames . add ( os . path . join ( root , filename ) )
for filename in coverage_filenames :
2018-06-12 11:49:20 -04:00
with open ( filename , ' r ' , encoding = " utf8 " ) as coverage_file :
2018-03-18 19:54:29 -03:00
covered_cmds . update ( [ line . strip ( ) for line in coverage_file . readlines ( ) ] )
2015-10-11 02:41:19 -03:00
return all_cmds - covered_cmds
if __name__ == ' __main__ ' :
2017-02-06 11:07:14 -03:00
main ( )