1
0

tests: properly clean-up MockLNWallets after tests finish

This commit is contained in:
SomberNight
2025-12-20 17:28:58 +00:00
parent c3e373a3b2
commit 0afd433c42
4 changed files with 75 additions and 35 deletions

View File

@@ -6,14 +6,19 @@ import tempfile
import shutil import shutil
import functools import functools
import inspect import inspect
from typing import TYPE_CHECKING, List
import electrum import electrum
import electrum.logging import electrum.logging
from electrum import constants from electrum import constants
from electrum import util from electrum import util
from electrum.util import OldTaskGroup
from electrum.logging import Logger from electrum.logging import Logger
from electrum.wallet import restore_wallet_from_text from electrum.wallet import restore_wallet_from_text
if TYPE_CHECKING:
from .test_lnpeer import MockLNWallet
# Set this locally to make the test suite run faster. # Set this locally to make the test suite run faster.
# If set, unit tests that would normally test functions with multiple implementations, # If set, unit tests that would normally test functions with multiple implementations,
@@ -64,8 +69,11 @@ class ElectrumTestCase(unittest.IsolatedAsyncioTestCase, Logger):
# or if a prior test raised during `setUp` or `asyncSetUp` and never released the lock. # or if a prior test raised during `setUp` or `asyncSetUp` and never released the lock.
raise Exception("timed out waiting for test_lock") raise Exception("timed out waiting for test_lock")
super().setUp() super().setUp()
self.electrum_path = tempfile.mkdtemp(prefix="electrum-unittest-base-") self.unittest_base_path = tempfile.mkdtemp(prefix="electrum-unittest-base-")
self.electrum_path = os.path.join(self.unittest_base_path, "electrum")
util.make_dir(self.electrum_path)
assert util._asyncio_event_loop is None, "global event loop already set?!" assert util._asyncio_event_loop is None, "global event loop already set?!"
self._lnworkers_created = [] # type: List[MockLNWallet]
async def asyncSetUp(self): async def asyncSetUp(self):
await super().asyncSetUp() await super().asyncSetUp()
@@ -75,13 +83,33 @@ class ElectrumTestCase(unittest.IsolatedAsyncioTestCase, Logger):
loop.set_debug(False) loop.set_debug(False)
util._asyncio_event_loop = loop util._asyncio_event_loop = loop
async def asyncTearDown(self):
# clean up lnworkers
async with OldTaskGroup() as group:
for lnworker in self._lnworkers_created:
await group.spawn(lnworker.stop())
self._lnworkers_created.clear()
await super().asyncTearDown()
def tearDown(self): def tearDown(self):
util.callback_mgr.clear_all_callbacks() util.callback_mgr.clear_all_callbacks()
shutil.rmtree(self.electrum_path) shutil.rmtree(self.unittest_base_path)
super().tearDown() super().tearDown()
util._asyncio_event_loop = None # cleared here, at the ~last possible moment. asyncTearDown is too early. util._asyncio_event_loop = None # cleared here, at the ~last possible moment. asyncTearDown is too early.
self._test_lock.release() self._test_lock.release()
def create_mock_lnwallet(
self,
*,
name: str,
has_anchors: bool,
) -> 'MockLNWallet':
from .test_lnpeer import _create_mock_lnwallet
data_dir = tempfile.mkdtemp(prefix="lnwallet-", dir=self.unittest_base_path)
lnwallet = _create_mock_lnwallet(name=name, has_anchors=has_anchors, data_dir=data_dir)
self._lnworkers_created.append(lnwallet)
return lnwallet
def as_testnet(func): def as_testnet(func):
"""Function decorator to run a single unit test in testnet mode. """Function decorator to run a single unit test in testnet mode.

View File

@@ -123,8 +123,8 @@ def create_channel_state(
def create_test_channels( def create_test_channels(
*, *,
alice_lnwallet: 'MockLNWallet' = None, alice_lnwallet: 'MockLNWallet',
bob_lnwallet: 'MockLNWallet' = None, bob_lnwallet: 'MockLNWallet',
feerate=6000, feerate=6000,
local_msat=None, local_msat=None,
remote_msat=None, remote_msat=None,
@@ -137,12 +137,6 @@ def create_test_channels(
if random_seed is None: # needed for deterministic randomness if random_seed is None: # needed for deterministic randomness
random_seed = os.urandom(32) random_seed = os.urandom(32)
random_gen = PRNG(random_seed) random_gen = PRNG(random_seed)
if alice_lnwallet is None:
from .test_lnpeer import create_mock_lnwallet
alice_lnwallet = create_mock_lnwallet(name="alice", has_anchors=anchor_outputs)
if bob_lnwallet is None:
from .test_lnpeer import create_mock_lnwallet
bob_lnwallet = create_mock_lnwallet(name="bob", has_anchors=anchor_outputs)
alice_name = alice_lnwallet.name alice_name = alice_lnwallet.name
bob_name = bob_lnwallet.name bob_name = bob_lnwallet.name
alice_pubkey = alice_lnwallet.node_keypair.pubkey alice_pubkey = alice_lnwallet.node_keypair.pubkey
@@ -267,12 +261,21 @@ class TestFee(ElectrumTestCase):
test test
https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2 https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2
""" """
async def asyncSetUp(self):
await super().asyncSetUp()
self.alice_lnwallet = self.create_mock_lnwallet(name="alice", has_anchors=self.TEST_ANCHOR_CHANNELS)
self.bob_lnwallet = self.create_mock_lnwallet(name="bob", has_anchors=self.TEST_ANCHOR_CHANNELS)
async def test_fee(self): async def test_fee(self):
alice_channel, bob_channel = create_test_channels( alice_channel, bob_channel = create_test_channels(
feerate=253, feerate=253,
local_msat=10_000_000_000, local_msat=10_000_000_000,
remote_msat=5_000_000_000, remote_msat=5_000_000_000,
anchor_outputs=self.TEST_ANCHOR_CHANNELS) anchor_outputs=self.TEST_ANCHOR_CHANNELS,
alice_lnwallet=self.alice_lnwallet,
bob_lnwallet=self.bob_lnwallet,
)
expected_value = 9_999_056 if self.TEST_ANCHOR_CHANNELS else 9_999_817 expected_value = 9_999_056 if self.TEST_ANCHOR_CHANNELS else 9_999_817
self.assertIn(expected_value, [x.value for x in alice_channel.get_latest_commitment(LOCAL).outputs()]) self.assertIn(expected_value, [x.value for x in alice_channel.get_latest_commitment(LOCAL).outputs()])
@@ -297,10 +300,14 @@ class TestChannel(ElectrumTestCase):
async def asyncSetUp(self): async def asyncSetUp(self):
await super().asyncSetUp() await super().asyncSetUp()
self.alice_lnwallet = self.create_mock_lnwallet(name="alice", has_anchors=self.TEST_ANCHOR_CHANNELS)
self.bob_lnwallet = self.create_mock_lnwallet(name="bob", has_anchors=self.TEST_ANCHOR_CHANNELS)
# Create a test channel which will be used for the duration of this # Create a test channel which will be used for the duration of this
# unittest. The channel will be funded evenly with Alice having 5 BTC, # unittest. The channel will be funded evenly with Alice having 5 BTC,
# and Bob having 5 BTC. # and Bob having 5 BTC.
self.alice_channel, self.bob_channel = create_test_channels(anchor_outputs=self.TEST_ANCHOR_CHANNELS) self.alice_channel, self.bob_channel = create_test_channels(
anchor_outputs=self.TEST_ANCHOR_CHANNELS, alice_lnwallet=self.alice_lnwallet, bob_lnwallet=self.bob_lnwallet)
self.paymentPreimage = b"\x01" * 32 self.paymentPreimage = b"\x01" * 32
paymentHash = bitcoin.sha256(self.paymentPreimage) paymentHash = bitcoin.sha256(self.paymentPreimage)
@@ -785,8 +792,14 @@ class TestChannelAnchors(TestChannel):
class TestAvailableToSpend(ElectrumTestCase): class TestAvailableToSpend(ElectrumTestCase):
async def asyncSetUp(self):
await super().asyncSetUp()
self.alice_lnwallet = self.create_mock_lnwallet(name="alice", has_anchors=self.TEST_ANCHOR_CHANNELS)
self.bob_lnwallet = self.create_mock_lnwallet(name="bob", has_anchors=self.TEST_ANCHOR_CHANNELS)
async def test_DesyncHTLCs(self): async def test_DesyncHTLCs(self):
alice_channel, bob_channel = create_test_channels(anchor_outputs=self.TEST_ANCHOR_CHANNELS) alice_channel, bob_channel = create_test_channels(
anchor_outputs=self.TEST_ANCHOR_CHANNELS, alice_lnwallet=self.alice_lnwallet, bob_lnwallet=self.bob_lnwallet)
self.assertEqual(499986152000 if not alice_channel.has_anchors() else 499980692000, alice_channel.available_to_spend(LOCAL)) self.assertEqual(499986152000 if not alice_channel.has_anchors() else 499980692000, alice_channel.available_to_spend(LOCAL))
self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL)) self.assertEqual(500000000000, bob_channel.available_to_spend(LOCAL))
@@ -837,7 +850,10 @@ class TestAvailableToSpend(ElectrumTestCase):
local_msat=4000000000, local_msat=4000000000,
remote_msat=4000000000, remote_msat=4000000000,
local_max_inflight=1000000000, local_max_inflight=1000000000,
remote_max_inflight=2000000000) remote_max_inflight=2000000000,
alice_lnwallet=self.alice_lnwallet,
bob_lnwallet=self.bob_lnwallet,
)
# alice can send 20 but bob can only receive 10, because of stricter receiving rules # alice can send 20 but bob can only receive 10, because of stricter receiving rules
self.assertEqual(2000000000, alice_channel.available_to_spend(LOCAL)) self.assertEqual(2000000000, alice_channel.available_to_spend(LOCAL))
@@ -893,8 +909,11 @@ class TestAvailableToSpendAnchors(TestAvailableToSpend):
class TestChanReserve(ElectrumTestCase): class TestChanReserve(ElectrumTestCase):
def setUp(self): async def asyncSetUp(self):
alice_channel, bob_channel = create_test_channels(anchor_outputs=False) await super().asyncSetUp()
alice_lnwallet = self.create_mock_lnwallet(name="alice", has_anchors=self.TEST_ANCHOR_CHANNELS)
bob_lnwallet = self.create_mock_lnwallet(name="bob", has_anchors=self.TEST_ANCHOR_CHANNELS)
alice_channel, bob_channel = create_test_channels(anchor_outputs=False, alice_lnwallet=alice_lnwallet, bob_lnwallet=bob_lnwallet)
alice_min_reserve = int(.5 * one_bitcoin_in_msat // 1000) alice_min_reserve = int(.5 * one_bitcoin_in_msat // 1000)
# We set Bob's channel reserve to a value that is larger than # We set Bob's channel reserve to a value that is larger than
# his current balance in the channel. This will ensure that # his current balance in the channel. This will ensure that
@@ -1027,9 +1046,14 @@ class TestChanReserveAnchors(TestChanReserve):
class TestDust(ElectrumTestCase): class TestDust(ElectrumTestCase):
async def asyncSetUp(self):
await super().asyncSetUp()
self.alice_lnwallet = self.create_mock_lnwallet(name="alice", has_anchors=self.TEST_ANCHOR_CHANNELS)
self.bob_lnwallet = self.create_mock_lnwallet(name="bob", has_anchors=self.TEST_ANCHOR_CHANNELS)
async def test_DustLimit(self): async def test_DustLimit(self):
"""Test that addition of an HTLC below the dust limit changes the balances.""" """Test that addition of an HTLC below the dust limit changes the balances."""
alice_channel, bob_channel = create_test_channels(anchor_outputs=self.TEST_ANCHOR_CHANNELS) alice_channel, bob_channel = create_test_channels(anchor_outputs=self.TEST_ANCHOR_CHANNELS, alice_lnwallet=self.alice_lnwallet, bob_lnwallet=self.bob_lnwallet)
dust_limit_alice = alice_channel.config[LOCAL].dust_limit_sat dust_limit_alice = alice_channel.config[LOCAL].dust_limit_sat
dust_limit_bob = bob_channel.config[LOCAL].dust_limit_sat dust_limit_bob = bob_channel.config[LOCAL].dust_limit_sat
self.assertLess(dust_limit_alice, dust_limit_bob) self.assertLess(dust_limit_alice, dust_limit_bob)

View File

@@ -137,9 +137,8 @@ class MockStandardWallet(Standard_Wallet):
assert passphrase assert passphrase
return passphrase # lol, super secure name return passphrase # lol, super secure name
def create_mock_lnwallet(*, name, has_anchors) -> 'MockLNWallet': def _create_mock_lnwallet(*, name, has_anchors, data_dir: str) -> 'MockLNWallet':
_user_dir = tempfile.mkdtemp(prefix="electrum-lnpeer-test-") # TODO clean-up after?? config = SimpleConfig({}, read_user_dir_function=lambda: data_dir)
config = SimpleConfig({}, read_user_dir_function=lambda: _user_dir)
config.ENABLE_ANCHOR_CHANNELS = has_anchors config.ENABLE_ANCHOR_CHANNELS = has_anchors
config.INITIAL_TRAMPOLINE_FEE_LEVEL = 0 config.INITIAL_TRAMPOLINE_FEE_LEVEL = 0
@@ -154,7 +153,6 @@ def create_mock_lnwallet(*, name, has_anchors) -> 'MockLNWallet':
lnworker = wallet.lnworker lnworker = wallet.lnworker
assert isinstance(lnworker, MockLNWallet), f"{lnworker=!r}" assert isinstance(lnworker, MockLNWallet), f"{lnworker=!r}"
lnworker._user_dir = _user_dir
lnworker.lnpeermgr.network = network lnworker.lnpeermgr.network = network
lnworker.logger.info(f"created LNWallet[{name}] with nodeID={lnworker.node_keypair.pubkey.hex()}") lnworker.logger.info(f"created LNWallet[{name}] with nodeID={lnworker.node_keypair.pubkey.hex()}")
return lnworker return lnworker
@@ -410,16 +408,8 @@ class TestPeer(ElectrumTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.GRAPH_DEFINITIONS = copy.deepcopy(_GRAPH_DEFINITIONS) self.GRAPH_DEFINITIONS = copy.deepcopy(_GRAPH_DEFINITIONS)
self._lnworkers_created = [] # type: List[MockLNWallet]
async def asyncTearDown(self): async def asyncTearDown(self):
# clean up lnworkers
async with OldTaskGroup() as group:
for lnworker in self._lnworkers_created:
await group.spawn(lnworker.stop())
for lnworker in self._lnworkers_created:
shutil.rmtree(lnworker._user_dir)
self._lnworkers_created.clear()
electrum.trampoline._TRAMPOLINE_NODES_UNITTESTS = {} electrum.trampoline._TRAMPOLINE_NODES_UNITTESTS = {}
await super().asyncTearDown() await super().asyncTearDown()
@@ -501,8 +491,7 @@ class TestPeer(ElectrumTestCase):
def prepare_lnwallets(self, graph_definition) -> Mapping[str, MockLNWallet]: def prepare_lnwallets(self, graph_definition) -> Mapping[str, MockLNWallet]:
workers = {} # type: Dict[str, MockLNWallet] workers = {} # type: Dict[str, MockLNWallet]
for a, definition in graph_definition.items(): for a, definition in graph_definition.items():
workers[a] = create_mock_lnwallet(name=a, has_anchors=self.TEST_ANCHOR_CHANNELS) workers[a] = self.create_mock_lnwallet(name=a, has_anchors=self.TEST_ANCHOR_CHANNELS)
self._lnworkers_created.extend(list(workers.values()))
return workers return workers
def prepare_chans_and_peers_in_graph( def prepare_chans_and_peers_in_graph(

View File

@@ -25,7 +25,6 @@ from electrum.util import bfh, read_json_file, OldTaskGroup, get_asyncio_loop
from electrum.logging import console_stderr_handler from electrum.logging import console_stderr_handler
from . import ElectrumTestCase from . import ElectrumTestCase
from .test_lnpeer import create_mock_lnwallet
TIME_STEP = 0.01 # run tests 100 x faster TIME_STEP = 0.01 # run tests 100 x faster
@@ -353,7 +352,7 @@ class TestOnionMessageManager(ElectrumTestCase):
async def test_request_and_reply(self): async def test_request_and_reply(self):
n = MockNetwork() n = MockNetwork()
lnw = create_mock_lnwallet(name='test_request_and_reply', has_anchors=False) lnw = self.create_mock_lnwallet(name='test_request_and_reply', has_anchors=False)
def slow(*args, **kwargs): def slow(*args, **kwargs):
time.sleep(2*TIME_STEP) time.sleep(2*TIME_STEP)
@@ -399,7 +398,7 @@ class TestOnionMessageManager(ElectrumTestCase):
async def test_forward(self): async def test_forward(self):
n = MockNetwork() n = MockNetwork()
lnw = create_mock_lnwallet(name='alice', has_anchors=False) lnw = self.create_mock_lnwallet(name='alice', has_anchors=False)
lnw.node_keypair = self.alice lnw.node_keypair = self.alice
self.was_sent = False self.was_sent = False
@@ -436,7 +435,7 @@ class TestOnionMessageManager(ElectrumTestCase):
async def test_receive_unsolicited(self): async def test_receive_unsolicited(self):
n = MockNetwork() n = MockNetwork()
lnw = create_mock_lnwallet(name='dave', has_anchors=False) lnw = self.create_mock_lnwallet(name='dave', has_anchors=False)
lnw.node_keypair = self.dave lnw.node_keypair = self.dave
t = OnionMessageManager(lnw) t = OnionMessageManager(lnw)