What changed, and why it matters
This commit is a code cleanup that changes the way some Python test helper functions are written so that a linting tool (Ruff) can enforce a style rule. It does not change what the software does, only how the test code is formatted. There is no security issue here.
No security action needed. This is a routine lint/style cleanup.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit removes the E731 Ruff ignore rule, which discourages assigning lambdas to variables. It replaces assigned lambda expressions with equivalent local def functions across several functional test files and the test framework. In feature_reindex_readonly.py, it also refactors platform-specific immutable-file cleanup to store the command as data (undo_immutable_cmd) rather than as a conditional callback. These are purely stylistic/refactoring changes in test code; no runtime behavior of Bitcoin Core is altered.
Changed components
test/functional/feature_addrman.pytest/functional/feature_reindex_readonly.pytest/functional/feature_taproot.pytest/functional/mempool_spend_coinbase.pytest/functional/mining_template_verification.pytest/functional/p2p_sendheaders.pytest/functional/rpc_generate.pytest/functional/rpc_net.pytest/functional/test_framework/p2p.pyruff.tomlInspect captured patch +56 / −30
diff --git a/ruff.toml b/ruff.toml
index a252c2f4..61cf6d24 100644
--- a/ruff.toml
+++ b/ruff.toml
@@ -12,6 +12,5 @@ select = [
]
ignore = [
"E501", # line too long
- "E731", # lambda assignment
"E741", # ambiguous-variable-name
]
diff --git a/test/functional/feature_addrman.py b/test/functional/feature_addrman.py
index a80261ec..1930a277 100755
--- a/test/functional/feature_addrman.py
+++ b/test/functional/feature_addrman.py
@@ -52,12 +52,13 @@ class AddrmanTest(BitcoinTestFramework):
def run_test(self):
peers_dat = os.path.join(self.nodes[0].chain_path, "peers.dat")
- init_error = lambda reason: (
- f"Error: Invalid or corrupt peers.dat \\({reason}\\). If you believe this "
- f"is a bug, please report it to {self.config['environment']['CLIENT_BUGREPORT']}. "
- f'As a workaround, you can move the file \\("{re.escape(peers_dat)}"\\) out of the way \\(rename, '
- "move, or delete\\) to have a new one created on the next start."
- )
+ def init_error(reason):
+ return (
+ f"Error: Invalid or corrupt peers.dat \\({reason}\\). If you believe this "
+ f"is a bug, please report it to {self.config['environment']['CLIENT_BUGREPORT']}. "
+ f'As a workaround, you can move the file \\("{re.escape(peers_dat)}"\\) out of the way \\(rename, '
+ "move, or delete\\) to have a new one created on the next start."
+ )
self.log.info("Check that mocked addrman is valid")
self.stop_node(0)
diff --git a/test/functional/feature_reindex_readonly.py b/test/functional/feature_reindex_readonly.py
index 889d0c2a..336fab69 100755
--- a/test/functional/feature_reindex_readonly.py
+++ b/test/functional/feature_reindex_readonly.py
@@ -35,13 +35,14 @@ class BlockstoreReindexTest(BitcoinTestFramework):
filename = self.nodes[0].chain_path / "blocks" / "blk00000.dat"
filename.chmod(stat.S_IREAD)
- undo_immutable = lambda: None
+ undo_immutable_cmd = None
+ should_reindex = True
# Linux
try:
subprocess.run(['chattr'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
subprocess.run(['chattr', '+i', filename], capture_output=True, check=True)
- undo_immutable = lambda: subprocess.check_call(['chattr', '-i', filename])
+ undo_immutable_cmd = ['chattr', '-i', filename]
self.log.info("Made file immutable with chattr")
except subprocess.CalledProcessError as e:
self.log.warning(str(e))
@@ -53,14 +54,14 @@ class BlockstoreReindexTest(BitcoinTestFramework):
self.log.warning("Return early on Linux under root, because chattr failed.")
self.log.warning("This should only happen due to missing capabilities in a container.")
self.log.warning("Make sure to --cap-add LINUX_IMMUTABLE if you want to run this test.")
- undo_immutable = False
+ should_reindex = False
except Exception:
# macOS, and *BSD
try:
subprocess.run(['chflags'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
subprocess.run(['chflags', 'uchg', filename], capture_output=True, check=True)
- undo_immutable = lambda: subprocess.check_call(['chflags', 'nouchg', filename])
+ undo_immutable_cmd = ['chflags', 'nouchg', filename]
self.log.info("Made file immutable with chflags")
except subprocess.CalledProcessError as e:
self.log.warning(str(e))
@@ -70,16 +71,17 @@ class BlockstoreReindexTest(BitcoinTestFramework):
self.log.warning(f"stderr: {e.stderr}")
if os.getuid() == 0:
self.log.warning("Return early on BSD under root, because chflags failed.")
- undo_immutable = False
+ should_reindex = False
except Exception:
pass
- if undo_immutable:
+ if should_reindex:
self.log.debug("Attempt to restart and reindex the node with the unwritable block file")
with self.nodes[0].assert_debug_log(["Reindexing finished"], timeout=60):
self.start_node(0, extra_args=['-reindex', '-fastprune'])
assert_equal(block_count, self.nodes[0].getblockcount())
- undo_immutable()
+ if undo_immutable_cmd is not None:
+ subprocess.check_call(undo_immutable_cmd)
filename.chmod(0o777)
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
index 1a41527e..a0c11fd6 100755
--- a/test/functional/feature_taproot.py
+++ b/test/functional/feature_taproot.py
@@ -764,7 +764,9 @@ def spenders_taproot_active():
add_spender(spenders, "sighash/scriptpath_hashtype_mis_%x" % hashtype, tap=tap, leaf="s0", key=secs[1], annex=annex, standard=no_annex, hashtype_actual=random.choice(VALID_SIGHASHES_TAPROOT_NO_SINGLE), **SINGLE_SIG, failure={"hashtype_actual": hashtype}, **ERR_SCHNORR_SIG_HASHTYPE, need_vin_vout_mismatch=True)
# Test OP_CODESEPARATOR impact on sighashing.
- hashtype = lambda _: random.choice(VALID_SIGHASHES_TAPROOT)
+ def hashtype(_):
+ return random.choice(VALID_SIGHASHES_TAPROOT)
+
common = {"annex": annex, "hashtype": hashtype, "standard": no_annex}
scripts = [
("pk_codesep", CScript(random_checksig_style(pubs[1]) + bytes([OP_CODESEPARATOR]))), # codesep after checksig
@@ -792,7 +794,9 @@ def spenders_taproot_active():
add_spender(spenders, "sighash/keypath", tap=tap, key=secs[0], **common, failure={"sighash": override(default_sighash, leaf="pk_codesep")}, **ERR_SCHNORR_SIG)
# Test that invalid hashtypes don't work, both in key path and script path spends
- hashtype = lambda _: random.choice(VALID_SIGHASHES_TAPROOT)
+ def hashtype(_):
+ return random.choice(VALID_SIGHASHES_TAPROOT)
+
for invalid_hashtype in [x for x in range(0x100) if x not in VALID_SIGHASHES_TAPROOT]:
add_spender(spenders, "sighash/keypath_unk_hashtype_%x" % invalid_hashtype, tap=tap, key=secs[0], hashtype=hashtype, failure={"hashtype": invalid_hashtype}, **ERR_SCHNORR_SIG_HASHTYPE)
add_spender(spenders, "sighash/scriptpath_unk_hashtype_%x" % invalid_hashtype, tap=tap, leaf="pk_codesep", key=secs[1], **SINGLE_SIG, hashtype=hashtype, failure={"hashtype": invalid_hashtype}, **ERR_SCHNORR_SIG_HASHTYPE)
@@ -1170,7 +1174,9 @@ def spenders_taproot_active():
add_spender(spenders, "unkver/1001inputs", standard=False, tap=tap, leaf="bare_unkver", inputs=[b'']*1001, failure={"leaf": "bare_c0"}, **ERR_STACK_SIZE)
# OP_SUCCESSx tests.
- hashtype = lambda _: random.choice(VALID_SIGHASHES_TAPROOT)
+ def hashtype(_):
+ return random.choice(VALID_SIGHASHES_TAPROOT)
+
for opval in range(76, 0x100):
opcode = CScriptOp(opval)
if not is_op_success(opcode):
@@ -1215,7 +1221,9 @@ def spenders_taproot_active():
# == Test case for https://github.com/bitcoin/bitcoin/issues/24765 ==
- zero_fn = lambda h: bytes([0 for _ in range(32)])
+ def zero_fn(h):
+ return bytes([0] * 32)
+
tap = taproot_construct(pubs[0], [("leaf", CScript([pubs[1], OP_CHECKSIG, pubs[1], OP_CHECKSIGADD, OP_2, OP_EQUAL])), zero_fn])
add_spender(spenders, "case24765", tap=tap, leaf="leaf", inputs=[getter("sign"), getter("sign")], key=secs[1], no_fail=True)
diff --git a/test/functional/mempool_spend_coinbase.py b/test/functional/mempool_spend_coinbase.py
index b09041c7..3dac5e9d 100755
--- a/test/functional/mempool_spend_coinbase.py
+++ b/test/functional/mempool_spend_coinbase.py
@@ -32,7 +32,8 @@ class MempoolSpendCoinbaseTest(BitcoinTestFramework):
# Coinbase at height chain_height-100+1 ok in mempool, should
# get mined. Coinbase at height chain_height-100+2 is
# too immature to spend.
- coinbase_txid = lambda h: self.nodes[0].getblock(self.nodes[0].getblockhash(h))['tx'][0]
+ def coinbase_txid(h):
+ return self.nodes[0].getblock(self.nodes[0].getblockhash(h))['tx'][0]
utxo_mature = wallet.get_utxo(txid=coinbase_txid(chain_height - 100 + 1))
utxo_immature = wallet.get_utxo(txid=coinbase_txid(chain_height - 100 + 2))
diff --git a/test/functional/mining_template_verification.py b/test/functional/mining_template_verification.py
index f00268f6..ad2a42b8 100755
--- a/test/functional/mining_template_verification.py
+++ b/test/functional/mining_template_verification.py
@@ -253,10 +253,11 @@ class MiningTemplateVerificationTest(BitcoinTestFramework):
def parallel_test(self, node, block_3):
# Ensure that getblocktemplate can be called concurrently by many threads.
self.log.info("Check blocks in parallel")
- check_50_blocks = lambda n: [
- assert_template(n, block_3, "bad-txns-inputs-missingorspent", submit=False)
- for _ in range(50)
- ]
+ def check_50_blocks(n):
+ return [
+ assert_template(n, block_3, "bad-txns-inputs-missingorspent", submit=False)
+ for _ in range(50)
+ ]
rpcs = [node.cli for _ in range(6)]
with ThreadPoolExecutor(max_workers=len(rpcs)) as threads:
list(threads.map(check_50_blocks, rpcs))
diff --git a/test/functional/p2p_sendheaders.py b/test/functional/p2p_sendheaders.py
index cc28fc05..acef0329 100755
--- a/test/functional/p2p_sendheaders.py
+++ b/test/functional/p2p_sendheaders.py
@@ -139,7 +139,9 @@ class BaseNode(P2PInterface):
self.send_without_ping(getblocks_message)
def wait_for_block_announcement(self, block_hash, timeout=60):
- test_function = lambda: self.last_blockhash_announced == block_hash
+ def test_function():
+ return self.last_blockhash_announced == block_hash
+
self.wait_until(test_function, timeout=timeout)
def on_inv(self, message):
@@ -165,7 +167,9 @@ class BaseNode(P2PInterface):
def check_last_headers_announcement(self, headers):
"""Test whether the last headers announcements received are right.
Headers may be announced across more than one message."""
- test_function = lambda: (len(self.recent_headers_announced) >= len(headers))
+ def test_function():
+ return len(self.recent_headers_announced) >= len(headers)
+
self.wait_until(test_function)
with p2p_lock:
assert_equal(self.recent_headers_announced, headers)
@@ -177,7 +181,9 @@ class BaseNode(P2PInterface):
"""Test whether the last announcement received had the right inv.
inv should be a list of block hashes."""
- test_function = lambda: self.block_announced
+ def test_function():
+ return self.block_announced
+
self.wait_until(test_function)
with p2p_lock:
diff --git a/test/functional/rpc_generate.py b/test/functional/rpc_generate.py
index a68d9c32..97ebf936 100755
--- a/test/functional/rpc_generate.py
+++ b/test/functional/rpc_generate.py
@@ -87,7 +87,9 @@ class RPCGenerateTest(BitcoinTestFramework):
# Ensure that generateblock can be called concurrently by many threads.
self.log.info('Generate blocks in parallel')
- generate_50_blocks = lambda n: [n.generateblock(output=address, transactions=[]) for _ in range(50)]
+ def generate_50_blocks(n):
+ return [n.generateblock(output=address, transactions=[]) for _ in range(50)]
+
rpcs = [node.cli for _ in range(6)]
with ThreadPoolExecutor(max_workers=len(rpcs)) as threads:
list(threads.map(generate_50_blocks, rpcs))
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 405d21fc..81cf4da7 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -202,7 +202,9 @@ class NetTest(BitcoinTestFramework):
self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytesrecv'] >= net_totals_before['totalbytesrecv'] + ping_size * 2), timeout=1)
for peer_before in peer_info_before:
- peer_after = lambda: next(p for p in self.nodes[0].getpeerinfo() if p['id'] == peer_before['id'])
+ def peer_after():
+ return next(p for p in self.nodes[0].getpeerinfo() if p['id'] == peer_before['id'])
+
self.wait_until(lambda: peer_after()['bytesrecv_per_msg'].get('pong', 0) >= peer_before['bytesrecv_per_msg'].get('pong', 0) + ping_size, timeout=1)
self.wait_until(lambda: peer_after()['bytessent_per_msg'].get('ping', 0) >= peer_before['bytessent_per_msg'].get('ping', 0) + ping_size, timeout=1)
diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
index 80319479..65260b7d 100755
--- a/test/functional/test_framework/p2p.py
+++ b/test/functional/test_framework/p2p.py
@@ -610,11 +610,15 @@ class P2PInterface(P2PConnection):
wait_until_helper_internal(test_function, timeout=timeout, lock=p2p_lock, timeout_factor=self.timeout_factor, check_interval=check_interval)
def wait_for_connect(self, *, timeout=60):
- test_function = lambda: self.is_connected
+ def test_function():
+ return self.is_connected
+
self.wait_until(test_function, timeout=timeout, check_connected=False)
def wait_for_disconnect(self, *, timeout=60):
- test_function = lambda: not self.is_connected
+ def test_function():
+ return not self.is_connected
+
self.wait_until(test_function, timeout=timeout, check_connected=False)
def wait_for_reconnect(self, *, timeout=60):
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.