pytest: rework test_real_data and test_real_biases to be parallel.
What changed, and why it matters
This commit only changes test code. It rewrites two slow pytest test functions to submit RPC calls in parallel using a thread executor, which makes one test run faster and exercises Core Lightning's askrene parallel-routing code path. There is no change to production code, no bug fix, and no security relevance visible in the diff or commit message.
No security action needed. Treat as a normal test-performance/parallelism improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies tests/test_askrene.py to run getroutes RPC calls concurrently via executor.submit() in test_real_data and test_real_biases. It restructures loops to collect futures first, then wait on results, and adds an assertion that askrene throttles concurrent requests (‘Too many running at once (4 vs 4): waiting’). The production RPC interface and routing logic are untouched.
Changed components
tests/test_askrene.pyInspect captured patch +65 / −47
diff --git a/tests/test_askrene.py b/tests/test_askrene.py
index 332d0012..09a669e7 100644
--- a/tests/test_askrene.py
+++ b/tests/test_askrene.py
@@ -1442,7 +1442,7 @@ canned_gossmap_badnodes = {
@pytest.mark.slow_test
-def test_real_data(node_factory, bitcoind):
+def test_real_data(node_factory, bitcoind, executor):
# Route from Rusty's node to the top nodes
# From tests/data/gossip-store-2026-02-03-node-map.xz:
# Me: 2134:024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605:BLUEIRON
@@ -1475,47 +1475,53 @@ def test_real_data(node_factory, bitcoind):
limit = 100
expected = (11, 95, 8026484, 925406, 89)
- fees = {}
+ # 0.5% is the norm
+ MAX_FEE = AMOUNT // 200
+
+ # Do these in parallel.
+ futs = {}
for n in range(0, limit):
- # 0.5% is the norm
- MAX_FEE = AMOUNT // 200
+ futs[n] = executor.submit(l1.rpc.getroutes,
+ source=l1.info['id'],
+ destination=nodeids[n],
+ amount_msat=AMOUNT,
+ layers=['auto.sourcefree', 'auto.localchans'],
+ maxfee_msat=MAX_FEE,
+ final_cltv=18)
+ fees = {}
+ prevs = {}
+ for n in range(0, limit):
+ fees[n] = []
if n in canned_gossmap_badnodes:
with pytest.raises(RpcError, match=canned_gossmap_badnodes[n]):
- l1.rpc.getroutes(source=l1.info['id'],
- destination=nodeids[n],
- amount_msat=AMOUNT,
- layers=['auto.sourcefree', 'auto.localchans'],
- maxfee_msat=MAX_FEE,
- final_cltv=18)
- fees[n] = []
- continue
-
- try:
- prev = l1.rpc.getroutes(source=l1.info['id'],
- destination=nodeids[n],
- amount_msat=AMOUNT,
- layers=['auto.sourcefree', 'auto.localchans'],
- maxfee_msat=MAX_FEE,
- final_cltv=18)
- except RpcError:
- fees[n] = []
+ futs[n].result(TIMEOUT)
continue
- # Now stress it, by asking it to spend 1msat less!
- fees[n] = [sum([r['path'][0]['amount_msat'] for r in prev['routes']]) - AMOUNT]
+ prevs[n] = futs[n].result(TIMEOUT)
+
+ # Stress it by asking harder for each one which succeeded
+ while prevs != {}:
+ futs = {}
+ for n, prev in prevs.items():
+ # Record fees
+ fees[n].append(sum([r['path'][0]['amount_msat'] for r in prev['routes']]) - AMOUNT)
+ # Now stress it, by asking it to spend 1msat less!
+ futs[n] = executor.submit(l1.rpc.getroutes,
+ source=l1.info['id'],
+ destination=nodeids[n],
+ amount_msat=AMOUNT,
+ layers=['auto.sourcefree', 'auto.localchans'],
+ maxfee_msat=fees[n][-1] - 1,
+ final_cltv=18)
- while True:
- # Keep making it harder...
+ for n, fut in futs.items():
try:
- routes = l1.rpc.getroutes(source=l1.info['id'],
- destination=nodeids[n],
- amount_msat=AMOUNT,
- layers=['auto.sourcefree', 'auto.localchans'],
- maxfee_msat=fees[n][-1] - 1,
- final_cltv=18)
+ routes = fut.result(TIMEOUT)
except RpcError:
- break
+ # Too much, this one is one.
+ del prevs[n]
+ continue
fee = sum([r['path'][0]['amount_msat'] for r in routes['routes']]) - AMOUNT
# Should get less expensive
@@ -1523,10 +1529,8 @@ def test_real_data(node_factory, bitcoind):
# Should get less likely (Note! This is violated because once we care
# about fees, the total is reduced, leading to better prob!).
-# assert routes['probability_ppm'] < prev['probability_ppm']
-
- fees[n].append(fee)
- prev = routes
+# assert routes['probability_ppm'] < prevs[n]['probability_ppm']
+ prevs[n] = routes
# Which succeeded in improving
improved = [n for n in fees if len(fees[n]) > 1]
@@ -1544,10 +1548,12 @@ def test_real_data(node_factory, bitcoind):
best = n
assert (len(fees[best]), len(improved), total_first_fee, total_final_fee, percent_fee_reduction) == expected
+ # askrene will have restricted how many we run
+ assert l1.daemon.is_in_log(r"Too many running at once \(4 vs 4\): waiting")
@pytest.mark.slow_test
-def test_real_biases(node_factory, bitcoind):
+def test_real_biases(node_factory, bitcoind, executor):
# Route from Rusty's node to the top 100.
# From tests/data/gossip-store-2026-02-03-node-map.xz:
# Me: 2134:024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605:BLUEIRON
@@ -1582,22 +1588,34 @@ def test_real_biases(node_factory, bitcoind):
num_changed = {}
bias_ineffective = 0
- for bias in (1, 2, 4, 8, 16, 32, 64, 100):
- num_changed[bias] = 0
- for n in range(0, limit):
- # 0.5% is the norm
- MAX_FEE = AMOUNT // 200
-
- if n in canned_gossmap_badnodes:
- continue
+ # 0.5% is the norm
+ MAX_FEE = AMOUNT // 200
- route = l1.rpc.getroutes(source=l1.info['id'],
+ # To exercise parallelism, do bases all at once.
+ futures = {}
+ for n in range(0, limit):
+ if n in canned_gossmap_badnodes:
+ continue
+ futures[n] = executor.submit(l1.rpc.getroutes,
+ source=l1.info['id'],
destination=nodeids[n],
amount_msat=AMOUNT,
layers=['auto.sourcefree', 'auto.localchans'],
maxfee_msat=MAX_FEE,
final_cltv=18)
+ base_routes = {}
+ for n in range(0, limit):
+ if n in canned_gossmap_badnodes:
+ continue
+ base_routes[n] = futures[n].result(TIMEOUT)
+
+ for bias in (1, 2, 4, 8, 16, 32, 64, 100):
+ num_changed[bias] = 0
+ for n in range(0, limit):
+ if n in canned_gossmap_badnodes:
+ continue
+ route = base_routes[n]
# Now add bias against final channel, see if it changes.
chan = route['routes'][0]['path'][-1]['short_channel_id_dir']
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.