From 0afd433c420fe115eac0700d983cc43372346ce3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 20 Dec 2025 17:28:58 +0000 Subject: [PATCH] tests: properly clean-up MockLNWallets after tests finish --- tests/__init__.py | 32 ++++++++++++++++++++-- tests/test_lnchannel.py | 54 ++++++++++++++++++++++++++----------- tests/test_lnpeer.py | 17 +++--------- tests/test_onion_message.py | 7 +++-- 4 files changed, 75 insertions(+), 35 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index b2b649b78..fec824018 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,14 +6,19 @@ import tempfile import shutil import functools import inspect +from typing import TYPE_CHECKING, List import electrum import electrum.logging from electrum import constants from electrum import util +from electrum.util import OldTaskGroup from electrum.logging import Logger 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. # 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. raise Exception("timed out waiting for test_lock") 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?!" + self._lnworkers_created = [] # type: List[MockLNWallet] async def asyncSetUp(self): await super().asyncSetUp() @@ -75,13 +83,33 @@ class ElectrumTestCase(unittest.IsolatedAsyncioTestCase, Logger): loop.set_debug(False) 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): util.callback_mgr.clear_all_callbacks() - shutil.rmtree(self.electrum_path) + shutil.rmtree(self.unittest_base_path) super().tearDown() util._asyncio_event_loop = None # cleared here, at the ~last possible moment. asyncTearDown is too early. 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): """Function decorator to run a single unit test in testnet mode. diff --git a/tests/test_lnchannel.py b/tests/test_lnchannel.py index 7a81dcd77..2081dd758 100644 --- a/tests/test_lnchannel.py +++ b/tests/test_lnchannel.py @@ -123,8 +123,8 @@ def create_channel_state( def create_test_channels( *, - alice_lnwallet: 'MockLNWallet' = None, - bob_lnwallet: 'MockLNWallet' = None, + alice_lnwallet: 'MockLNWallet', + bob_lnwallet: 'MockLNWallet', feerate=6000, local_msat=None, remote_msat=None, @@ -137,12 +137,6 @@ def create_test_channels( if random_seed is None: # needed for deterministic randomness random_seed = os.urandom(32) 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 bob_name = bob_lnwallet.name alice_pubkey = alice_lnwallet.node_keypair.pubkey @@ -267,12 +261,21 @@ class TestFee(ElectrumTestCase): test 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): alice_channel, bob_channel = create_test_channels( feerate=253, local_msat=10_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 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): 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 # unittest. The channel will be funded evenly with Alice 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 paymentHash = bitcoin.sha256(self.paymentPreimage) @@ -785,8 +792,14 @@ class TestChannelAnchors(TestChannel): 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): - 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(500000000000, bob_channel.available_to_spend(LOCAL)) @@ -837,7 +850,10 @@ class TestAvailableToSpend(ElectrumTestCase): local_msat=4000000000, remote_msat=4000000000, 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 self.assertEqual(2000000000, alice_channel.available_to_spend(LOCAL)) @@ -893,8 +909,11 @@ class TestAvailableToSpendAnchors(TestAvailableToSpend): class TestChanReserve(ElectrumTestCase): - def setUp(self): - alice_channel, bob_channel = create_test_channels(anchor_outputs=False) + async def asyncSetUp(self): + 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) # We set Bob's channel reserve to a value that is larger than # his current balance in the channel. This will ensure that @@ -1027,9 +1046,14 @@ class TestChanReserveAnchors(TestChanReserve): 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): """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_bob = bob_channel.config[LOCAL].dust_limit_sat self.assertLess(dust_limit_alice, dust_limit_bob) diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py index 0b14a6a29..31923980c 100644 --- a/tests/test_lnpeer.py +++ b/tests/test_lnpeer.py @@ -137,9 +137,8 @@ class MockStandardWallet(Standard_Wallet): assert passphrase return passphrase # lol, super secure name -def create_mock_lnwallet(*, name, has_anchors) -> 'MockLNWallet': - _user_dir = tempfile.mkdtemp(prefix="electrum-lnpeer-test-") # TODO clean-up after?? - config = SimpleConfig({}, read_user_dir_function=lambda: _user_dir) +def _create_mock_lnwallet(*, name, has_anchors, data_dir: str) -> 'MockLNWallet': + config = SimpleConfig({}, read_user_dir_function=lambda: data_dir) config.ENABLE_ANCHOR_CHANNELS = has_anchors config.INITIAL_TRAMPOLINE_FEE_LEVEL = 0 @@ -154,7 +153,6 @@ def create_mock_lnwallet(*, name, has_anchors) -> 'MockLNWallet': lnworker = wallet.lnworker assert isinstance(lnworker, MockLNWallet), f"{lnworker=!r}" - lnworker._user_dir = _user_dir lnworker.lnpeermgr.network = network lnworker.logger.info(f"created LNWallet[{name}] with nodeID={lnworker.node_keypair.pubkey.hex()}") return lnworker @@ -410,16 +408,8 @@ class TestPeer(ElectrumTestCase): def setUp(self): super().setUp() self.GRAPH_DEFINITIONS = copy.deepcopy(_GRAPH_DEFINITIONS) - self._lnworkers_created = [] # type: List[MockLNWallet] 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 = {} await super().asyncTearDown() @@ -501,8 +491,7 @@ class TestPeer(ElectrumTestCase): def prepare_lnwallets(self, graph_definition) -> Mapping[str, MockLNWallet]: workers = {} # type: Dict[str, MockLNWallet] for a, definition in graph_definition.items(): - workers[a] = create_mock_lnwallet(name=a, has_anchors=self.TEST_ANCHOR_CHANNELS) - self._lnworkers_created.extend(list(workers.values())) + workers[a] = self.create_mock_lnwallet(name=a, has_anchors=self.TEST_ANCHOR_CHANNELS) return workers def prepare_chans_and_peers_in_graph( diff --git a/tests/test_onion_message.py b/tests/test_onion_message.py index 058e6b802..5f35b351c 100644 --- a/tests/test_onion_message.py +++ b/tests/test_onion_message.py @@ -25,7 +25,6 @@ from electrum.util import bfh, read_json_file, OldTaskGroup, get_asyncio_loop from electrum.logging import console_stderr_handler from . import ElectrumTestCase -from .test_lnpeer import create_mock_lnwallet TIME_STEP = 0.01 # run tests 100 x faster @@ -353,7 +352,7 @@ class TestOnionMessageManager(ElectrumTestCase): async def test_request_and_reply(self): 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): time.sleep(2*TIME_STEP) @@ -399,7 +398,7 @@ class TestOnionMessageManager(ElectrumTestCase): async def test_forward(self): 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 self.was_sent = False @@ -436,7 +435,7 @@ class TestOnionMessageManager(ElectrumTestCase): async def test_receive_unsolicited(self): 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 t = OnionMessageManager(lnw)